+/fuzz-commit-graph
/fuzz_corpora
/fuzz-pack-headers
/fuzz-pack-idx
/git-rebase--am
/git-rebase--common
/git-rebase--interactive
-/git-rebase--merge
/git-rebase--preserve-merges
/git-receive-pack
/git-reflog
Benoit Sigoure <tsunanet@gmail.com> <tsuna@lrde.epita.fr>
Bernt Hansen <bernt@norang.ca> <bernt@alumni.uwaterloo.ca>
Brandon Casey <drafnel@gmail.com> <casey@nrlssc.navy.mil>
+Brandon Williams <bwilliams.eng@gmail.com> <bmwill@google.com>
brian m. carlson <sandals@crustytoothpaste.net>
brian m. carlson <sandals@crustytoothpaste.net> <sandals@crustytoothpaste.ath.cx>
Bryan Larsen <bryan@larsen.st> <bryan.larsen@gmail.com>
MAN7_TXT += gittutorial.txt
MAN7_TXT += gitworkflows.txt
+ifdef MAN_FILTER
+MAN_TXT = $(filter $(MAN_FILTER),$(MAN1_TXT) $(MAN5_TXT) $(MAN7_TXT))
+else
MAN_TXT = $(MAN1_TXT) $(MAN5_TXT) $(MAN7_TXT)
+MAN_FILTER = $(MAN_TXT)
+endif
+
MAN_XML = $(patsubst %.txt,%.xml,$(MAN_TXT))
MAN_HTML = $(patsubst %.txt,%.html,$(MAN_TXT))
GIT_MAN_REF = master
OBSOLETE_HTML += everyday.html
OBSOLETE_HTML += git-remote-helpers.html
-DOC_HTML = $(MAN_HTML) $(OBSOLETE_HTML)
ARTICLES += howto-index
ARTICLES += git-tools
SP_ARTICLES += $(TECH_DOCS)
SP_ARTICLES += technical/api-index
-DOC_HTML += $(patsubst %,%.html,$(ARTICLES) $(SP_ARTICLES))
+ARTICLES_HTML += $(patsubst %,%.html,$(ARTICLES) $(SP_ARTICLES))
+HTML_FILTER ?= $(ARTICLES_HTML) $(OBSOLETE_HTML)
+DOC_HTML = $(MAN_HTML) $(filter $(HTML_FILTER),$(ARTICLES_HTML) $(OBSOLETE_HTML))
-DOC_MAN1 = $(patsubst %.txt,%.1,$(MAN1_TXT))
-DOC_MAN5 = $(patsubst %.txt,%.5,$(MAN5_TXT))
-DOC_MAN7 = $(patsubst %.txt,%.7,$(MAN7_TXT))
+DOC_MAN1 = $(patsubst %.txt,%.1,$(filter $(MAN_FILTER),$(MAN1_TXT)))
+DOC_MAN5 = $(patsubst %.txt,%.5,$(filter $(MAN_FILTER),$(MAN5_TXT)))
+DOC_MAN7 = $(patsubst %.txt,%.7,$(filter $(MAN_FILTER),$(MAN7_TXT)))
prefix ?= $(HOME)
bindir ?= $(prefix)/bin
lint-docs::
$(QUIET_LINT)$(PERL_PATH) lint-gitlink.perl
+ifeq ($(wildcard po/Makefile),po/Makefile)
+doc-l10n install-l10n::
+ $(MAKE) -C po $@
+endif
+
.PHONY: FORCE
--- /dev/null
+Git 2.21 Release Notes
+======================
+
+Backward Compatibility Notes
+----------------------------
+
+
+Updates since v2.20
+-------------------
+
+UI, Workflows & Features
+
+ * The "http.version" configuration variable can be used with recent
+ enough cURL library to force the version of HTTP used to talk when
+ fetching and pushing.
+
+ * Small fixes and features for fast-export and fast-import, mostly on
+ the fast-export side.
+
+ * "git push $there $src:$dst" rejects when $dst is not a fully
+ qualified refname and not clear what the end user meant. The
+ codepath has been taught to give a clearer error message, and also
+ guess where the push should go by taking the type of the pushed
+ object into account (e.g. a tag object would want to go under
+ refs/tags/).
+
+ * "git checkout [<tree-ish>] path..." learned to report the number of
+ paths that have been checked out of the index or the tree-ish,
+ which gives it the same degree of noisy-ness as the case in which
+ the command checks out a branch.
+
+ * "git quiltimport" learned "--keep-non-patch" option.
+
+ * "git worktree remove" and "git worktree move" refused to work when
+ there is a submodule involved. This has been loosened to ignore
+ uninitialized submodules.
+
+ * "git cherry-pick -m1" was forbidden when picking a non-merge
+ commit, even though there _is_ parent number 1 for such a commit.
+ This was done to avoid mistakes back when "cherry-pick" was about
+ picking a single commit, but is no longer useful with "cherry-pick"
+ that can pick a range of commits. Now the "-m$num" option is
+ allowed when picking any commit, as long as $num names an existing
+ parent of the commit.
+
+ * Update "git multimail" from the upstream.
+
+ * "git p4" update.
+
+ * The "--format=<placeholder>" option of for-each-ref, branch and tag
+ learned to show a few more traits of objects that can be learned by
+ the object_info API.
+
+ * "git rebase -i" learned to re-execute a command given with 'exec'
+ to run after it failed the last time.
+
+ * "git diff --color-moved-ws" updates.
+
+ * Custom userformat "log --format" learned %S atom that stands for
+ the tip the traversal reached the commit from, i.e. --source.
+
+ * "git instaweb" learned to drive http.server that comes with
+ "batteries included" Python installation (both Python2 & 3).
+
+
+Performance, Internal Implementation, Development Support etc.
+
+ * Code clean-up with optimization for the codepath that checks
+ (non-)existence of loose objects.
+
+ * More codepaths become aware of working with in-core repository
+ instance other than the default "the_repository".
+
+ * The "strncat()" function is now among the banned functions.
+
+ * Portability updates for the HPE NonStop platform.
+
+ * Earlier we added "-Wformat-security" to developer builds, assuming
+ that "-Wall" (which includes "-Wformat" which in turn is required
+ to use "-Wformat-security") is always in effect. This is not true
+ when config.mak.autogen is in use, unfortunately. This has been
+ fixed by unconditionally adding "-Wall" to developer builds.
+
+ * The loose object cache used to optimize existence look-up has been
+ updated.
+
+ * Flaky tests can now be repeatedly run under load with the
+ "--stress" option.
+ (merge fb7d1e3ac8 sg/stress-test later to maint).
+
+ * Documentation/Makefile is getting prepared for manpage
+ localization.
+
+ * "git fetch-pack" now can talk the version 2 protocol.
+
+ * sha-256 hash has been added and plumbed through the code to allow
+ building Git with the "NewHash".
+
+ * Debugging help for http transport.
+
+ * "git fetch --deepen=<more>" has been corrected to work over v2
+ protocol.
+
+ * The code to walk tree objects has been taught that we may be
+ working with object names that are not computed with SHA-1.
+
+ * The in-core repository instances are passed through more codepaths.
+
+ * Update the protocol message specification to allow only the limited
+ use of scaled quantities. This is ensure potential compatibility
+ issues will not go out of hand.
+
+ * Micro-optimize the code that prepares commit objects to be walked
+ by "git rev-list" when the commit-graph is available.
+
+ * "git fetch" and "git upload-pack" learned to send all exchange over
+ the sideband channel while talking the v2 protocol.
+
+ * The codepath to write out commit-graph has been optimized by
+ following the usual pattern of visiting objects in in-pack order.
+
+ * The codepath to show progress meter while writing out commit-graph
+ file has been improved.
+
+ * Cocci rules have been updated to encourage use of strbuf_addbuf().
+
+
+Fixes since v2.20
+-----------------
+
+ * Updates for corner cases in merge-recursive.
+ (merge cc4cb0902c en/merge-path-collision later to maint).
+
+ * "git checkout frotz" (without any double-dash) avoids ambiguity by
+ making sure 'frotz' cannot be interpreted as a revision and as a
+ path at the same time. This safety has been updated to check also
+ a unique remote-tracking branch 'frotz' in a remote, when dwimming
+ to create a local branch 'frotz' out of a remote-tracking branch
+ 'frotz' from a remote.
+ (merge be4908f103 nd/checkout-dwim-fix later to maint).
+
+ * Refspecs configured with "git -c var=val clone" did not propagate
+ to the resulting repository, which has been corrected.
+ (merge 7eae4a3ac4 sg/clone-initial-fetch-configuration later to maint).
+
+ * A properly configured username/email is required under
+ user.useConfigOnly in order to create commits; now "git stash"
+ (even though it creates commit objects to represent stash entries)
+ command is exempt from the requirement.
+ (merge 3bc2111fc2 sd/stash-wo-user-name later to maint).
+
+ * The http-backend CGI process did not correctly clean up the child
+ processes it spawns to run upload-pack etc. when it dies itself,
+ which has been corrected.
+ (merge 02818a98d7 mk/http-backend-kill-children-before-exit later to maint).
+
+ * "git rev-list --exclude-promisor-objects" had to take an object
+ that does not exist locally (and is lazily available) from the
+ command line without barfing, but the code dereferenced NULL.
+ (merge 4cf67869b2 md/list-lazy-objects-fix later to maint).
+
+ * The traversal over tree objects has learned to honor
+ ":(attr:label)" pathspec match, which has been implemented only for
+ enumerating paths on the filesystem.
+ (merge 5a0b97b34c nd/attr-pathspec-in-tree-walk later to maint).
+
+ * BSD port updates.
+ (merge 4e3ecbd439 cb/openbsd-allows-reading-directory later to maint).
+ (merge b6bdc2a0f5 cb/t5004-empty-tar-archive-fix later to maint).
+ (merge 82cbc8cde2 cb/test-lint-cp-a later to maint).
+
+ * Lines that begin with a certain keyword that come over the wire, as
+ well as lines that consist only of one of these keywords, ought to
+ be painted in color for easier eyeballing, but the latter was
+ broken ever since the feature was introduced in 2.19, which has
+ been corrected.
+ (merge 1f67290450 hn/highlight-sideband-keywords later to maint).
+
+ * "git log -G<regex>" looked for a hunk in the "git log -p" patch
+ output that contained a string that matches the given pattern.
+ Optimize this code to ignore binary files, which by default will
+ not show any hunk that would match any pattern (unless textconv or
+ the --text option is in effect, that is).
+ (merge e0e7cb8080 tb/log-G-binary later to maint).
+
+ * "git submodule update" ought to use a single job unless asked, but
+ by mistake used multiple jobs, which has been fixed.
+ (merge e3a9d1aca9 sb/submodule-fetchjobs-default-to-one later to maint).
+
+ * "git stripspace" should be usable outside a git repository, but
+ under the "-s" or "-c" mode, it didn't.
+ (merge 957da75802 jn/stripspace-wo-repository later to maint).
+
+ * Some of the documentation pages formatted incorrectly with
+ Asciidoctor, which have been fixed.
+ (merge b62eb1d2f4 ma/asciidoctor later to maint).
+
+ * The core.worktree setting in a submodule repository should not be
+ pointing at a directory when the submodule loses its working tree
+ (e.g. getting deinit'ed), but the code did not properly maintain
+ this invariant.
+
+ * With zsh, "git cmd path<TAB>" was completed to "git cmd path name"
+ when the completed path has a special character like SP in it,
+ without any attempt to keep "path name" a single filename. This
+ has been fixed to complete it to "git cmd path\ name" just like
+ Bash completion does.
+
+ * The test suite tried to see if it is run under bash, but the check
+ itself failed under some other implementations of shell (notably
+ under NetBSD). This has been corrected.
+ (merge 54ea72f09c sg/test-bash-version-fix later to maint).
+
+ * "git gc" and "git repack" did not close the open packfiles that
+ they found unneeded before removing them, which didn't work on a
+ platform incapable of removing an open file. This has been
+ corrected.
+ (merge 5bdece0d70 js/gc-repack-close-before-remove later to maint).
+
+ * The code to drive GIT_EXTERNAL_DIFF command relied on the string
+ returned from getenv() to be non-volatile, which is not true, that
+ has been corrected.
+ (merge 6776a84dae kg/external-diff-save-env later to maint).
+
+ * There were many places the code relied on the string returned from
+ getenv() to be non-volatile, which is not true, that have been
+ corrected.
+ (merge 0da0e9268b jk/save-getenv-result later to maint).
+
+ * The v2 upload-pack protocol implementation failed to honor
+ hidden-ref configuration, which has been corrected.
+ (merge e20b4192a3 jk/proto-v2-hidden-refs-fix later to maint).
+
+ * "git fetch --recurse-submodules" may not fetch the necessary commit
+ that is bound to the superproject, which is getting corrected.
+ (merge be76c21282 sb/submodule-recursive-fetch-gets-the-tip later to maint).
+
+ * "git rebase" internally runs "checkout" to switch between branches,
+ and the command used to call the post-checkout hook, but the
+ reimplementation stopped doing so, which is getting fixed.
+
+ * "git add -e" got confused when the change it wants to let the user
+ edit is smaller than the previous change that was left over in a
+ temporary file.
+ (merge fa6f225e01 js/add-e-clear-patch-before-stating later to maint).
+
+ * "git p4" failed to update a shelved change when there were moved
+ files, which has been corrected.
+ (merge 7a10946ab9 ld/git-p4-shelve-update-fix later to maint).
+
+ * The codepath to read from the commit-graph file attempted to read
+ past the end of it when the file's table-of-contents was corrupt.
+
+ * The compat/obstack code had casts that -Wcast-function-type
+ compilation option found questionable.
+ (merge 764473d257 sg/obstack-cast-function-type-fix later to maint).
+
+ * An obvious typo in an assertion error message has been fixed.
+ (merge 3c27e2e059 cc/test-ref-store-typofix later to maint).
+
+ * In Git for Windows, "git clone \\server\share\path" etc. that uses
+ UNC paths from command line had bad interaction with its shell
+ emulation.
+
+ * "git add --ignore-errors" did not work as advertised and instead
+ worked as an unintended synonym for "git add --renormalize", which
+ has been fixed.
+ (merge 9e5da3d055 jk/add-ignore-errors-bit-assignment-fix later to maint).
+
+ * On a case-insensitive filesystem, we failed to compare the part of
+ the path that is above the worktree directory in an absolute
+ pathname, which has been corrected.
+
+ * Asking "git check-attr" about a macro (e.g. "binary") on a specific
+ path did not work correctly, even though "git check-attr -a" listed
+ such a macro correctly. This has been corrected.
+ (merge 7b95849be4 jk/attr-macro-fix later to maint).
+
+ * "git pack-objects" incorrectly used uninitialized mutex, which has
+ been corrected.
+ (merge edb673cf10 ph/pack-objects-mutex-fix later to maint).
+
+ * "git checkout -b <new> [HEAD]" to create a new branch from the
+ current commit and check it out ought to be a no-op in the index
+ and the working tree in normal cases, but there are corner cases
+ that do require updates to the index and the working tree. Running
+ it immediately after "git clone --no-checkout" is one of these
+ cases that an earlier optimization kicked in incorrectly, which has
+ been fixed.
+ (merge 8424bfd45b bp/checkout-new-branch-optim later to maint).
+
+ * "git diff --color-moved --cc --stat -p" did not work well due to
+ funny interaction between a bug in color-moved and the rest, which
+ has been fixed.
+ (merge dac03b5518 jk/diff-cc-stat-fixes later to maint).
+
+ * When GIT_SEQUENCE_EDITOR is set, the command was incorrectly
+ started when modes of "git rebase" that implicitly uses the
+ machinery for the interactive rebase are run, which has been
+ corrected.
+ (merge 891d4a0313 pw/no-editor-in-rebase-i-implicit later to maint).
+
+ * The commit-graph facility did not work when in-core objects that
+ are promoted from unknown type to commit (e.g. a commit that is
+ accessed via a tag that refers to it) were involved, which has been
+ corrected.
+ (merge 4468d4435c sg/object-as-type-commit-graph-fix later to maint).
+
+ * "git fetch" output cleanup.
+ (merge dc40b24df4 nd/fetch-compact-update later to maint).
+
+ * Code cleanup, docfix, build fix, etc.
+ (merge 89ba9a79ae hb/t0061-dot-in-path-fix later to maint).
+ (merge d173e799ea sb/diff-color-moved-config-option-fixup later to maint).
+ (merge a8f5a59067 en/directory-renames-nothanks-doc-update later to maint).
+ (merge ec36c42a63 nd/indentation-fix later to maint).
+ (merge f116ee21cd do/gitweb-strict-export-conf-doc later to maint).
+ (merge 112ea42663 fd/gitweb-snapshot-conf-doc-fix later to maint).
+ (merge 1cadad6f65 tb/use-common-win32-pathfuncs-on-cygwin later to maint).
+ (merge 57e9dcaa65 km/rebase-doc-typofix later to maint).
+ (merge b8b4cb27e6 ds/gc-doc-typofix later to maint).
+ (merge 3b3357626e nd/style-opening-brace later to maint).
+ (merge b4583d5595 es/doc-worktree-guessremote-config later to maint).
+ (merge cce99cd8c6 ds/commit-graph-assert-missing-parents later to maint).
+ (merge 0650614982 cy/completion-typofix later to maint).
+ (merge 6881925ef5 rs/sha1-file-close-mapped-file-on-error later to maint).
+ (merge bd8d6f0def en/show-ref-doc-fix later to maint).
+ (merge 1747125e2c cc/parial-clone-doc-typofix later to maint).
+ (merge e01378753d cc/fetch-error-message-fix later to maint).
+ (merge 54e8c11215 jk/remote-insteadof-cleanup later to maint).
+ (merge d609615f48 js/test-git-installed later to maint).
+ (merge ba170517be ja/doc-style-fix later to maint).
tries to overwrite a remote ref that points at an
object that is not a commit-ish, or make the remote
ref point at an object that is not a commit-ish.
+ pushUnqualifiedRefname::
+ Shown when linkgit:git-push[1] gives up trying to
+ guess based on the source and destination refs what
+ remote ref namespace the source belongs in, but where
+ we can still suggest that the user push to either
+ refs/heads/* or refs/tags/* based on the type of the
+ source object.
statusHints::
Show directions on how to proceed from the current
state in the output of linkgit:git-status[1], in
core.eol::
Sets the line ending type to use in the working directory for
- files that have the `text` property set when core.autocrlf is false.
+ files that are marked as text (either by having the `text`
+ attribute set, or by having `text=auto` and Git auto-detecting
+ the contents as text).
Alternatives are 'lf', 'crlf' and 'native', which uses the platform's
native line ending. The default value is `native`. See
linkgit:gitattributes[5] for more information on end-of-line
- conversion.
+ conversion. Note that this value is ignored if `core.autocrlf`
+ is set to `true` or `input`.
core.safecrlf::
If true, makes Git check if converting `CRLF` is reversible when
If set, store cookies received during requests to the file specified by
http.cookieFile. Has no effect if http.cookieFile is unset.
+http.version::
+ Use the specified HTTP protocol version when communicating with a server.
+ If you want to force the default. The available and default version depend
+ on libcurl. Actually the possible values of
+ this option are:
+
+ - HTTP/2
+ - HTTP/1.1
+
http.sslVersion::
The SSL version to use when negotiating an SSL connection, if you
want to force the default. The available and default version
true. You should not generally need to turn this off unless
you are debugging pack bitmaps.
+pack.useSparse::
+ When true, git will default to using the '--sparse' option in
+ 'git pack-objects' when the '--revs' option is present. This
+ algorithm only walks trees that appear in paths that introduce new
+ objects. This can have significant performance benefits when
+ computing a pack to send a small change. However, it is possible
+ that extra objects are added to the pack-file if the included
+ commits contain certain types of direct renames.
+
pack.writeBitmaps (deprecated)::
This is a deprecated synonym for `repack.writeBitmaps`.
-------------------------------------------
+
Defaults to false.
+
+rebase.rescheduleFailedExec::
+ Automatically reschedule `exec` commands that failed. This only makes
+ sense in interactive mode (or when an `--exec` option was provided).
+ This is the same as specifying the `--reschedule-failed-exec` option.
worktree.guessRemote::
- With `add`, if no branch argument, and neither of `-b` nor
- `-B` nor `--detach` are given, the command defaults to
+ If no branch is specified and neither `-b` nor `-B` nor
+ `--detach` is used, then `git worktree add` defaults to
creating a new branch from HEAD. If `worktree.guessRemote` is
set to true, `worktree add` tries to find a remote-tracking
branch whose name uniquely matches the new branch name. If
`dimmed_zebra` is a deprecated synonym.
--
+--no-color-moved::
+ Turn off move detection. This can be used to override configuration
+ settings. It is the same as `--color-moved=no`.
+
--color-moved-ws=<modes>::
- This configures how white spaces are ignored when performing the
+ This configures how whitespace is ignored when performing the
move detection for `--color-moved`.
ifdef::git-diff[]
It can be set by the `diff.colorMovedWS` configuration setting.
These modes can be given as a comma separated list:
+
--
+no::
+ Do not ignore whitespace when performing move detection.
ignore-space-at-eol::
Ignore changes in whitespace at EOL.
ignore-space-change::
Ignore whitespace when comparing lines. This ignores differences
even if one line has whitespace where the other line has none.
allow-indentation-change::
- Initially ignore any white spaces in the move detection, then
+ Initially ignore any whitespace in the move detection, then
group the moved code blocks only into a block if the change in
whitespace is the same per line. This is incompatible with the
other modes.
--
+--no-color-moved-ws::
+ Do not ignore whitespace when performing move detection. This can be
+ used to override configuration settings. It is the same as
+ `--color-moved-ws=no`.
+
--word-diff[=<mode>]::
Show a word diff, using the <mode> to delimit changed words.
By default, words are delimited by whitespace; see
came into being: use the feature iteratively to feed the interesting
block in the preimage back into `-S`, and keep going until you get the
very first version of the block.
++
+Binary files are searched as well.
-G<regex>::
Look for differences whose patch text contains added/removed
-S"regexec\(regexp" --pickaxe-regex` will not (because the number of
occurrences of that string did not change).
+
+Unless `--text` is supplied patches of binary files without a textconv
+filter will be ignored.
++
See the 'pickaxe' entry in linkgit:gitdiffcore[7] for more
information.
specifying `dir` will record not just a file `dir/file1`
modified in the working tree, a file `dir/file2` added to
the working tree, but also a file `dir/file3` removed from
- the working tree. Note that older versions of Git used
+ the working tree). Note that older versions of Git used
to ignore removed files; use `--no-all` option if you want
- to add modified or new files but ignore removed ones.
+ to add modified or new files but ignore removed ones.
+
For more details about the <pathspec> syntax, see the 'pathspec' entry
in linkgit:gitglossary[7].
--no-ignore-removal::
Update the index not only where the working tree has a file
matching <pathspec> but also where the index already has an
- entry. This adds, modifies, and removes index entries to
+ entry. This adds, modifies, and removes index entries to
match the working tree.
+
If no <pathspec> is given when `-A` option is used, all
--------
* Adds content from all `*.txt` files under `Documentation` directory
-and its subdirectories:
+ and its subdirectories:
+
------------
$ git add Documentation/\*.txt
------------
+
<1> This step and the next one could be combined into a single step with
-"checkout -b my2.6.14 v2.6.14".
+ "checkout -b my2.6.14 v2.6.14".
Delete an unneeded branch::
+
------------
+
<1> Delete the remote-tracking branches "todo", "html" and "man". The next
-'fetch' or 'pull' will create them again unless you configure them not to.
-See linkgit:git-fetch[1].
+ 'fetch' or 'pull' will create them again unless you configure them not to.
+ See linkgit:git-fetch[1].
<2> Delete the "test" branch even if the "master" branch (or whichever branch
-is currently checked out) does not have all commits from the test branch.
+ is currently checked out) does not have all commits from the test branch.
NOTES
stdin, and the SHA-1, type, and size of each object is printed on stdout. The
output format can be overridden using the optional `<format>` argument. If
either `--textconv` or `--filters` was specified, the input is expected to
-list the object names followed by the path name, separated by a single white
-space, so that the appropriate drivers can be determined.
+list the object names followed by the path name, separated by a single
+whitespace, so that the appropriate drivers can be determined.
OPTIONS
-------
Print object information and contents for each object provided
on stdin. May not be combined with any other options or arguments
except `--textconv` or `--filters`, in which case the input lines
- also need to specify the path, separated by white space. See the
+ also need to specify the path, separated by whitespace. See the
section `BATCH OUTPUT` below for details.
--batch-check::
Print object information for each object provided on stdin. May
not be combined with any other options or arguments except
`--textconv` or `--filters`, in which case the input lines also
- need to specify the path, separated by white space. See the
+ need to specify the path, separated by whitespace. See the
section `BATCH OUTPUT` below for details.
--batch-all-objects::
<object> SP missing LF
------------
+If a name is specified that might refer to more than one object (an ambiguous short sha), then `cat-file` will ignore any custom format and print:
+
+------------
+<object> SP ambiguous LF
+------------
+
If --follow-symlinks is used, and a symlink in the repository points
outside the repository, then `cat-file` will ignore any custom format
and print:
Just like linkgit:git-submodule[1], this will detach the
submodules HEAD.
+--no-guess::
+ Do not attempt to create a branch if a remote tracking branch
+ of the same name exists.
+
<branch>::
Branch to checkout; if it refers to a branch (i.e., a name that,
when prepended with "refs/heads/", is a valid ref), then that
------------
<1> creates a new branch 'foo', which refers to commit 'f', and then
-updates HEAD to refer to branch 'foo'. In other words, we'll no longer
-be in detached HEAD state after this command.
+ updates HEAD to refer to branch 'foo'. In other words, we'll no longer
+ be in detached HEAD state after this command.
<2> similarly creates a new branch 'foo', which refers to commit 'f',
-but leaves HEAD detached.
+ but leaves HEAD detached.
<3> creates a new tag 'foo', which refers to commit 'f',
-leaving HEAD detached.
+ leaving HEAD detached.
If we have moved away from commit 'f', then we must first recover its object
name (typically by using git reflog), and then we can create a reference to
--------
. The following sequence checks out the `master` branch, reverts
-the `Makefile` to two revisions back, deletes hello.c by
-mistake, and gets it back from the index.
+ the `Makefile` to two revisions back, deletes hello.c by
+ mistake, and gets it back from the index.
+
------------
$ git checkout master <1>
------------
. After working in the wrong branch, switching to the correct
-branch would be done using:
+ branch would be done using:
+
------------
$ git checkout mytopic
changes you made since the tip of the new branch.
. When a merge conflict happens during switching branches with
-the `-m` option, you would see something like this:
+ the `-m` option, you would see something like this:
+
------------
$ git checkout -m mytopic
$ git cherry-pick -Xpatience topic^ <4>
------------
<1> apply the change that would be shown by `git show topic^`.
-In this example, the patch does not apply cleanly, so
-information about the conflict is written to the index and
-working tree and no new commit results.
+ In this example, the patch does not apply cleanly, so
+ information about the conflict is written to the index and
+ working tree and no new commit results.
<2> summarize changes to be reconciled
<3> cancel the cherry-pick. In other words, return to the
-pre-cherry-pick state, preserving any local modifications you had in
-the working tree.
+ pre-cherry-pick state, preserving any local modifications
+ you had in the working tree.
<4> try to apply the change introduced by `topic^` again,
-spending extra time to avoid mistakes based on incorrectly matching
-context lines.
+ spending extra time to avoid mistakes based on incorrectly
+ matching context lines.
SEE ALSO
--------
values are given for the same key, each value will be written to
the config file. This makes it safe, for example, to add
additional fetch refspecs to the origin remote.
++
+Due to limitations of the current implementation, some configuration
+variables do not take effect until after the initial fetch and checkout.
+Configuration variables known to not take effect are:
+`remote.<name>.mirror` and `remote.<name>.tagOpt`. Use the
+corresponding `--mirror` and `--no-tags` options instead.
--depth <depth>::
Create a 'shallow' clone with a history truncated to the
The number of spaces between columns. One space by default.
EXAMPLES
-------
+--------
Format data by columns:
------------
+
<1> Changes in the working tree not yet staged for the next commit.
<2> Changes between the index and your last commit; what you
-would be committing if you run "git commit" without "-a" option.
+ would be committing if you run "git commit" without "-a" option.
<3> Changes in the working tree since your last commit; what you
-would be committing if you run "git commit -a"
+ would be committing if you run "git commit -a"
Comparing with arbitrary commits::
+
------------
+
<1> Instead of using the tip of the current branch, compare with the
-tip of "test" branch.
+ tip of "test" branch.
<2> Instead of comparing with the tip of "test" branch, compare with
-the tip of the current branch, but limit the comparison to the
-file "test".
+ the tip of the current branch, but limit the comparison to the
+ file "test".
<3> Compare the version before the last commit and the last commit.
Comparing branches::
<1> Changes between the tips of the topic and the master branches.
<2> Same as above.
<3> Changes that occurred on the master branch since when the topic
-branch was started off it.
+ branch was started off it.
Limiting the diff output::
+
------------
+
<1> Show only modification, rename, and copy, but not addition
-or deletion.
+ or deletion.
<2> Show only names and the nature of change, but not actual
-diff output.
+ diff output.
<3> Limit diff output to named subtrees.
Munging the diff output::
------------
+
<1> Spend extra cycles to find renames, copies and complete
-rewrites (very expensive).
+ rewrites (very expensive).
<2> Output diff in reverse.
SEE ALSO
the shape of the history and stored tree. See the section on
`ANONYMIZING` below.
+--reference-excluded-parents::
+ By default, running a command such as `git fast-export
+ master~5..master` will not include the commit master{tilde}5
+ and will make master{tilde}4 no longer have master{tilde}5 as
+ a parent (though both the old master{tilde}4 and new
+ master{tilde}4 will have all the same files). Use
+ --reference-excluded-parents to instead have the the stream
+ refer to commits in the excluded range of history by their
+ sha1sum. Note that the resulting stream can only be used by a
+ repository which already contains the necessary parent
+ commits.
+
+--show-original-ids::
+ Add an extra directive to the output for commits and blobs,
+ `original-oid <SHA1SUM>`. While such directives will likely be
+ ignored by importers such as git-fast-import, it may be useful
+ for intermediary filters (e.g. for rewriting commit messages
+ which refer to older commits, or for stripping blobs by id).
+
--refspec::
Apply the specified refspec to each ref exported. Multiple of them can
be specified.
'git rev-list', that specifies the specific objects and references
to export. For example, `master~10..master` causes the
current master reference to be exported along with all objects
- added since its 10th ancestor commit.
+ added since its 10th ancestor commit and (unless the
+ --reference-excluded-parents option is specified) all files
+ common to master{tilde}9 and master{tilde}10.
EXAMPLES
--------
not contain the old commit).
--quiet::
- Disable all non-fatal output, making fast-import silent when it
- is successful. This option disables the output shown by
- --stats.
+ Disable the output shown by --stats, making fast-import usually
+ be silent when it is successful. However, if the import stream
+ has directives intended to show user output (e.g. `progress`
+ directives), the corresponding messages will still be shown.
--stats::
Display some basic statistics about the objects fast-import has
....
'commit' SP <ref> LF
mark?
+ original-oid?
('author' (SP <name>)? SP LT <email> GT SP <when> LF)?
'committer' (SP <name>)? SP LT <email> GT SP <when> LF
data
to another object simply by reusing the same `<idnum>` in another
`mark` command.
+`original-oid`
+~~~~~~~~~~~~~~
+Provides the name of the object in the original source control system.
+fast-import will simply ignore this directive, but filter processes
+which operate on and modify the stream before feeding to fast-import
+may have uses for this information
+
+....
+ 'original-oid' SP <object-identifier> LF
+....
+
+where `<object-identifer>` is any string not containing LF.
+
`tag`
~~~~~
Creates an annotated tag referring to a specific commit. To create
....
'tag' SP <name> LF
'from' SP <commit-ish> LF
+ original-oid?
'tagger' (SP <name>)? SP LT <email> GT SP <when> LF
data
....
....
'blob' LF
mark?
+ original-oid?
data
....
because it is prefixed with a plus sign; `tmp` will not be.
* Peek at a remote's branch, without configuring the remote in your local
-repository:
+ repository:
+
------------------------------------------------
$ git fetch git://git.kernel.org/pub/scm/git/git.git maint
objectsize::
The size of the object (the same as 'git cat-file -s' reports).
-
+ Append `:disk` to get the size, in bytes, that the object takes up on
+ disk. See the note about on-disk sizes in the `CAVEATS` section below.
objectname::
The object name (aka SHA-1).
For a non-ambiguous abbreviation of the object name append `:short`.
For an abbreviation of the object name with desired length append
`:short=<length>`, where the minimum length is MINIMUM_ABBREV. The
length may be exceeded to ensure unique object names.
+deltabase::
+ This expands to the object name of the delta base for the
+ given object, if it is stored as a delta. Otherwise it
+ expands to the null object name (all zeroes).
upstream::
The name of a local ref which can be considered ``upstream''
git for-each-ref --format="%(refname)%(if)%(authorname)%(then) Authored by: %(authorname)%(end)"
------------
+CAVEATS
+-------
+
+Note that the sizes of objects on disk are reported accurately, but care
+should be taken in drawing conclusions about which refs or objects are
+responsible for disk usage. The size of a packed non-delta object may be
+much larger than the size of objects which delta against it, but the
+choice of which object is the base and which is the delta is arbitrary
+and is subject to change during a repack.
+
+Note also that multiple copies of an object may be present in the object
+database; in this case, it is undefined which copy's size or delta base
+will be reported.
+
SEE ALSO
--------
linkgit:git-show-ref[1]
"mailnews.wraplength" and set the value to 0.
3. Disable the use of format=flowed:
-Edit..Preferences..Advanced..Config Editor. Search for
-"mailnews.send_plaintext_flowed".
-Toggle it to make sure it is set to `false`.
+ Edit..Preferences..Advanced..Config Editor. Search for
+ "mailnews.send_plaintext_flowed".
+ Toggle it to make sure it is set to `false`.
After that is done, you should be able to compose email as you
otherwise would (cut + paste, 'git format-patch' | 'git imap-send', etc),
--------
* Extract commits between revisions R1 and R2, and apply them on top of
-the current branch using 'git am' to cherry-pick them:
+ the current branch using 'git am' to cherry-pick them:
+
------------
$ git format-patch -k --stdout R1..R2 | git am -3 -k
------------
* Extract all commits which are in the current branch but not in the
-origin branch:
+ origin branch:
+
------------
$ git format-patch origin
For each commit a separate file is created in the current directory.
* Extract all commits that lead to 'origin' since the inception of the
-project:
+ project:
+
------------
$ git format-patch --root origin
use it only when you know the recipient uses Git to apply your patch.
* Extract three topmost commits from the current branch and format them
-as e-mailable patches:
+ as e-mailable patches:
+
------------
$ git format-patch -3
it within all non-bare repos or it can be set to a boolean value.
This defaults to true.
-The optional configuration variable `gc.commitGraph` determines if
+The optional configuration variable `gc.writeCommitGraph` determines if
'git gc' should run 'git commit-graph write'. This can be set to a
boolean value. This defaults to false.
* "man": use the 'man' program as usual,
* "woman": use 'emacsclient' to launch the "woman" mode in emacs
-(this only works starting with emacsclient versions 22),
+ (this only works starting with emacsclient versions 22),
* "konqueror": use 'kfmclient' to open the man page in a new konqueror
-tab (see 'Note about konqueror' below).
+ tab (see 'Note about konqueror' below).
Values for other tools can be used if there is a corresponding
`man.<tool>.cmd` configuration entry (see below).
OPTIONS
-------
---
-
-q::
--quiet::
If you provide a 'directory', the command is run inside it. If this directory
does not exist, it will be created.
---
-
TEMPLATE DIRECTORY
------------------
The default template directory includes some directory structure, suggested
"exclude patterns" (see linkgit:gitignore[5]), and sample hook files.
-The sample hooks are all disabled by default, To enable one of the
+The sample hooks are all disabled by default. To enable one of the
sample hooks rename it by removing its `.sample` suffix.
See linkgit:githooks[5] for more general info on hook execution.
The HTTP daemon command-line that will be executed.
Command-line options may be specified here, and the
configuration file will be added at the end of the command-line.
- Currently apache2, lighttpd, mongoose, plackup and webrick are supported.
+ Currently apache2, lighttpd, mongoose, plackup, python and
+ webrick are supported.
(Default: lighttpd)
-m::
Default format for human-readable dates. (Compare the
`--date` option.) Defaults to "default", which means to write
dates like `Sat May 8 19:35:34 2010 -0500`.
++
+If the format is set to "auto:foo" and the pager is in use, format
+"foo" will be the used for the date format. Otherwise "default" will
+be used.
log.follow::
If `true`, `git log` will act as if the `--follow` option was used when
------------
This:
-1. Creates an empty Git repository in a subdirectory called 'project'.
+1. Creates an empty Git repository in a subdirectory called 'project'.
+
-2. Imports the full contents of the head revision from the given p4
-depot path into a single commit in the Git branch 'refs/remotes/p4/master'.
+2. Imports the full contents of the head revision from the given p4
+ depot path into a single commit in the Git branch 'refs/remotes/p4/master'.
+
-3. Creates a local branch, 'master' from this remote and checks it out.
+3. Creates a local branch, 'master' from this remote and checks it out.
To reproduce the entire p4 history in Git, use the '@all' modifier on
the depot path:
[--local] [--incremental] [--window=<n>] [--depth=<n>]
[--revs [--unpacked | --all]] [--keep-pack=<pack-name>]
[--stdout [--filter=<filter-spec>] | base-name]
- [--shallow] [--keep-true-parents] < object-list
+ [--shallow] [--keep-true-parents] [--sparse] < object-list
DESCRIPTION
Add --no-reuse-object if you want to force a uniform compression
level on all data no matter the source.
+--sparse::
+ Use the "sparse" algorithm to determine which objects to include in
+ the pack, when combined with the "--revs" option. This algorithm
+ only walks trees that appear in paths that introduce new objects.
+ This can have significant performance benefits when computing
+ a pack to send a small change. However, it is possible that extra
+ objects are added to the pack-file if the included commits contain
+ certain types of direct renames.
+
--thin::
Create a "thin" pack by omitting the common objects between a
sender and a receiver in order to reduce network transfer. This
without any `<refspec>` on the command line. Otherwise, missing
`:<dst>` means to update the same ref as the `<src>`.
+
+If <dst> doesn't start with `refs/` (e.g. `refs/heads/master`) we will
+try to infer where in `refs/*` on the destination <repository> it
+belongs based on the the type of <src> being pushed and whether <dst>
+is ambiguous.
++
+--
+* If <dst> unambiguously refers to a ref on the <repository> remote,
+ then push to that ref.
+
+* If <src> resolves to a ref starting with refs/heads/ or refs/tags/,
+ then prepend that to <dst>.
+
+* Other ambiguity resolutions might be added in the future, but for
+ now any other cases will error out with an error indicating what we
+ tried, and depending on the `advice.pushUnqualifiedRefname`
+ configuration (see linkgit:git-config[1]) suggest what refs/
+ namespace you may have wanted to push to.
+
+--
++
The object referenced by <src> is used to update the <dst> reference
on the remote side. Whether this is allowed depends on where in
`refs/*` the <dst> reference lives as described in detail below, in
`refs/remotes/satellite/master`) in the `mothership` repository;
do the same for `dev` and `satellite/dev`.
+
+See the section describing `<refspec>...` above for a discussion of
+the matching semantics.
++
This is to emulate `git fetch` run on the `mothership` using `git
push` that is run in the opposite direction in order to integrate
the work done on `satellite`, and is often necessary when you can
--------
[verse]
'git quiltimport' [--dry-run | -n] [--author <author>] [--patches <dir>]
- [--series <file>]
+ [--series <file>] [--keep-non-patch]
DESCRIPTION
or the value of the `$QUILT_SERIES` environment
variable.
+--keep-non-patch::
+ Pass `-b` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]).
+
GIT
---
Part of the linkgit:git[1] suite
+
See also INCOMPATIBLE OPTIONS below.
+-y <cmd>::
+ This is the same as passing `--reschedule-failed-exec` before
+ `-x <cmd>`, i.e. it appends the specified `exec` command and
+ turns on the mode where failed `exec` commands are automatically
+ rescheduled.
+
--root::
Rebase all commits reachable from <branch>, instead of
limiting them with an <upstream>. This allows you to rebase
with care: the final stash application after a successful
rebase might result in non-trivial conflicts.
+--reschedule-failed-exec::
+--no-reschedule-failed-exec::
+ Automatically reschedule `exec` commands that failed. This only makes
+ sense in interactive mode (or when an `--exec` option was provided).
+
INCOMPATIBLE OPTIONS
--------------------
-git-rebase has many flags that are incompatible with each other,
-predominantly due to the fact that it has three different underlying
-implementations:
-
- * one based on linkgit:git-am[1] (the default)
- * one based on git-merge-recursive (merge backend)
- * one based on linkgit:git-cherry-pick[1] (interactive backend)
-
-Flags only understood by the am backend:
+The following options:
* --committer-date-is-author-date
* --ignore-date
* --ignore-whitespace
* -C
-Flags understood by both merge and interactive backends:
+are incompatible with the following options:
* --merge
* --strategy
* --strategy-option
* --allow-empty-message
-
-Flags only understood by the interactive backend:
-
* --[no-]autosquash
* --rebase-merges
* --preserve-merges
* --edit-todo
* --root when used in combination with --onto
-Other incompatible flag pairs:
+In addition, the following pairs of options are incompatible:
* --preserve-merges and --interactive
* --preserve-merges and --signoff
Directory rename detection
~~~~~~~~~~~~~~~~~~~~~~~~~~
-The merge and interactive backends work fine with
-directory rename detection. The am backend sometimes does not.
+Directory rename heuristics are enabled in the merge and interactive
+backends. Due to the lack of accurate tree information, directory
+rename detection is disabled in the am backend.
include::merge-strategies.txt[]
At this time, the `merge` command will *always* use the `recursive`
merge strategy for regular merges, and `octopus` for octopus merges,
-strategy, with no way to choose a different one. To work around
+with no way to choose a different one. To work around
this, an `exec` command can be used to call `git merge` explicitly,
using the fact that the labels are worktree-local refs (the ref
`refs/rewritten/onto` would correspond to the label `onto`, for example).
------------
+
<1> You are happily working on something, and find the changes
-in these files are in good order. You do not want to see them
-when you run `git diff`, because you plan to work on other files
-and changes with these files are distracting.
+ in these files are in good order. You do not want to see them
+ when you run `git diff`, because you plan to work on other files
+ and changes with these files are distracting.
<2> Somebody asks you to pull, and the changes sound worthy of merging.
<3> However, you already dirtied the index (i.e. your index does
-not match the `HEAD` commit). But you know the pull you are going
-to make does not affect `frotz.c` or `filfre.c`, so you revert the
-index changes for these two files. Your changes in working tree
-remain there.
+ not match the `HEAD` commit). But you know the pull you are going
+ to make does not affect `frotz.c` or `filfre.c`, so you revert the
+ index changes for these two files. Your changes in working tree
+ remain there.
<4> Then you can pull and merge, leaving `frotz.c` and `filfre.c`
-changes still in the working tree.
+ changes still in the working tree.
Undo a commit and redo::
+
------------
+
<1> This is most often done when you remembered what you
-just committed is incomplete, or you misspelled your commit
-message, or both. Leaves working tree as it was before "reset".
+ just committed is incomplete, or you misspelled your commit
+ message, or both. Leaves working tree as it was before "reset".
<2> Make corrections to working tree files.
<3> "reset" copies the old head to `.git/ORIG_HEAD`; redo the
-commit by starting with its log message. If you do not need to
-edit the message further, you can give `-C` option instead.
+ commit by starting with its log message. If you do not need to
+ edit the message further, you can give `-C` option instead.
+
See also the `--amend` option to linkgit:git-commit[1].
------------
+
<1> You have made some commits, but realize they were premature
-to be in the `master` branch. You want to continue polishing
-them in a topic branch, so create `topic/wip` branch off of the
-current `HEAD`.
+ to be in the `master` branch. You want to continue polishing
+ them in a topic branch, so create `topic/wip` branch off of the
+ current `HEAD`.
<2> Rewind the master branch to get rid of those three commits.
<3> Switch to `topic/wip` branch and keep working.
------------
+
<1> The last three commits (`HEAD`, `HEAD^`, and `HEAD~2`) were bad
-and you do not want to ever see them again. Do *not* do this if
-you have already given these commits to somebody else. (See the
-"RECOVERING FROM UPSTREAM REBASE" section in linkgit:git-rebase[1] for
-the implications of doing so.)
+ and you do not want to ever see them again. Do *not* do this if
+ you have already given these commits to somebody else. (See the
+ "RECOVERING FROM UPSTREAM REBASE" section in linkgit:git-rebase[1]
+ for the implications of doing so.)
Undo a merge or pull::
+
------------
+
<1> Try to update from the upstream resulted in a lot of
-conflicts; you were not ready to spend a lot of time merging
-right now, so you decide to do that later.
+ conflicts; you were not ready to spend a lot of time merging
+ right now, so you decide to do that later.
<2> "pull" has not made merge commit, so `git reset --hard`
-which is a synonym for `git reset --hard HEAD` clears the mess
-from the index file and the working tree.
+ which is a synonym for `git reset --hard HEAD` clears the mess
+ from the index file and the working tree.
<3> Merge a topic branch into the current branch, which resulted
-in a fast-forward.
+ in a fast-forward.
<4> But you decided that the topic branch is not ready for public
-consumption yet. "pull" or "merge" always leaves the original
-tip of the current branch in `ORIG_HEAD`, so resetting hard to it
-brings your index file and the working tree back to that state,
-and resets the tip of the branch to that commit.
+ consumption yet. "pull" or "merge" always leaves the original
+ tip of the current branch in `ORIG_HEAD`, so resetting hard to it
+ brings your index file and the working tree back to that state,
+ and resets the tip of the branch to that commit.
Undo a merge or pull inside a dirty working tree::
+
------------
+
<1> Even if you may have local modifications in your
-working tree, you can safely say `git pull` when you know
-that the change in the other branch does not overlap with
-them.
+ working tree, you can safely say `git pull` when you know
+ that the change in the other branch does not overlap with
+ them.
<2> After inspecting the result of the merge, you may find
-that the change in the other branch is unsatisfactory. Running
-`git reset --hard ORIG_HEAD` will let you go back to where you
-were, but it will discard your local changes, which you do not
-want. `git reset --merge` keeps your local changes.
+ that the change in the other branch is unsatisfactory. Running
+ `git reset --hard ORIG_HEAD` will let you go back to where you
+ were, but it will discard your local changes, which you do not
+ want. `git reset --merge` keeps your local changes.
Interrupted workflow::
formatting are ignored.
2. The original format used by Greg Kroah-Hartman's 'send_lots_of_email.pl'
-script
+ script
+
This format expects the first line of the file to contain the "Cc:" value
and the "Subject:" of the message as the second line.
Show the HEAD reference, even if it would normally be filtered out.
---tags::
--heads::
+--tags::
Limit to "refs/heads" and "refs/tags", respectively. These options
are not mutually exclusive; when given both, references stored in
Ignored files are not listed, unless `--ignored` option is in effect,
in which case `XY` are `!!`.
- X Y Meaning
- -------------------------------------------------
- [AMD] not updated
- M [ MD] updated in index
- A [ MD] added to index
- D deleted from index
- R [ MD] renamed in index
- C [ MD] copied in index
- [MARC] index and work tree matches
- [ MARC] M work tree changed since index
- [ MARC] D deleted in work tree
- [ D] R renamed in work tree
- [ D] C copied in work tree
- -------------------------------------------------
- D D unmerged, both deleted
- A U unmerged, added by us
- U D unmerged, deleted by them
- U A unmerged, added by them
- D U unmerged, deleted by us
- A A unmerged, both added
- U U unmerged, both modified
- -------------------------------------------------
- ? ? untracked
- ! ! ignored
- -------------------------------------------------
+....
+X Y Meaning
+-------------------------------------------------
+ [AMD] not updated
+M [ MD] updated in index
+A [ MD] added to index
+D deleted from index
+R [ MD] renamed in index
+C [ MD] copied in index
+[MARC] index and work tree matches
+[ MARC] M work tree changed since index
+[ MARC] D deleted in work tree
+[ D] R renamed in work tree
+[ D] C copied in work tree
+-------------------------------------------------
+D D unmerged, both deleted
+A U unmerged, added by us
+U D unmerged, deleted by them
+U A unmerged, added by them
+D U unmerged, deleted by us
+A A unmerged, both added
+U U unmerged, both modified
+-------------------------------------------------
+? ? untracked
+! ! ignored
+-------------------------------------------------
+....
Submodules have more state and instead report
M the submodule has a different HEAD than
If `--branch` is given, a series of header lines are printed with
information about the current branch.
- Line Notes
- ------------------------------------------------------------
- # branch.oid <commit> | (initial) Current commit.
- # branch.head <branch> | (detached) Current branch.
- # branch.upstream <upstream_branch> If upstream is set.
- # branch.ab +<ahead> -<behind> If upstream is set and
- the commit is present.
- ------------------------------------------------------------
+....
+Line Notes
+------------------------------------------------------------
+# branch.oid <commit> | (initial) Current commit.
+# branch.head <branch> | (detached) Current branch.
+# branch.upstream <upstream_branch> If upstream is set.
+# branch.ab +<ahead> -<behind> If upstream is set and
+ the commit is present.
+------------------------------------------------------------
+....
### Changed Tracked Entries
2 <XY> <sub> <mH> <mI> <mW> <hH> <hI> <X><score> <path><sep><origPath>
- Field Meaning
- --------------------------------------------------------
- <XY> A 2 character field containing the staged and
- unstaged XY values described in the short format,
- with unchanged indicated by a "." rather than
- a space.
- <sub> A 4 character field describing the submodule state.
- "N..." when the entry is not a submodule.
- "S<c><m><u>" when the entry is a submodule.
- <c> is "C" if the commit changed; otherwise ".".
- <m> is "M" if it has tracked changes; otherwise ".".
- <u> is "U" if there are untracked changes; otherwise ".".
- <mH> The octal file mode in HEAD.
- <mI> The octal file mode in the index.
- <mW> The octal file mode in the worktree.
- <hH> The object name in HEAD.
- <hI> The object name in the index.
- <X><score> The rename or copy score (denoting the percentage
- of similarity between the source and target of the
- move or copy). For example "R100" or "C75".
- <path> The pathname. In a renamed/copied entry, this
- is the target path.
- <sep> When the `-z` option is used, the 2 pathnames are separated
- with a NUL (ASCII 0x00) byte; otherwise, a tab (ASCII 0x09)
- byte separates them.
- <origPath> The pathname in the commit at HEAD or in the index.
- This is only present in a renamed/copied entry, and
- tells where the renamed/copied contents came from.
- --------------------------------------------------------
+....
+Field Meaning
+--------------------------------------------------------
+<XY> A 2 character field containing the staged and
+ unstaged XY values described in the short format,
+ with unchanged indicated by a "." rather than
+ a space.
+<sub> A 4 character field describing the submodule state.
+ "N..." when the entry is not a submodule.
+ "S<c><m><u>" when the entry is a submodule.
+ <c> is "C" if the commit changed; otherwise ".".
+ <m> is "M" if it has tracked changes; otherwise ".".
+ <u> is "U" if there are untracked changes; otherwise ".".
+<mH> The octal file mode in HEAD.
+<mI> The octal file mode in the index.
+<mW> The octal file mode in the worktree.
+<hH> The object name in HEAD.
+<hI> The object name in the index.
+<X><score> The rename or copy score (denoting the percentage
+ of similarity between the source and target of the
+ move or copy). For example "R100" or "C75".
+<path> The pathname. In a renamed/copied entry, this
+ is the target path.
+<sep> When the `-z` option is used, the 2 pathnames are separated
+ with a NUL (ASCII 0x00) byte; otherwise, a tab (ASCII 0x09)
+ byte separates them.
+<origPath> The pathname in the commit at HEAD or in the index.
+ This is only present in a renamed/copied entry, and
+ tells where the renamed/copied contents came from.
+--------------------------------------------------------
+....
Unmerged entries have the following format; the first character is
a "u" to distinguish from ordinary changed entries.
u <xy> <sub> <m1> <m2> <m3> <mW> <h1> <h2> <h3> <path>
- Field Meaning
- --------------------------------------------------------
- <XY> A 2 character field describing the conflict type
- as described in the short format.
- <sub> A 4 character field describing the submodule state
- as described above.
- <m1> The octal file mode in stage 1.
- <m2> The octal file mode in stage 2.
- <m3> The octal file mode in stage 3.
- <mW> The octal file mode in the worktree.
- <h1> The object name in stage 1.
- <h2> The object name in stage 2.
- <h3> The object name in stage 3.
- <path> The pathname.
- --------------------------------------------------------
+....
+Field Meaning
+--------------------------------------------------------
+<XY> A 2 character field describing the conflict type
+ as described in the short format.
+<sub> A 4 character field describing the submodule state
+ as described above.
+<m1> The octal file mode in stage 1.
+<m2> The octal file mode in stage 2.
+<m3> The octal file mode in stage 3.
+<mW> The octal file mode in the worktree.
+<h1> The object name in stage 1.
+<h2> The object name in stage 2.
+<h3> The object name in stage 3.
+<path> The pathname.
+--------------------------------------------------------
+....
### Other Items
the old tag. In that case you can do one of two things:
. The sane thing.
-Just admit you screwed up, and use a different name. Others have
-already seen one tag-name, and if you keep the same name, you
-may be in the situation that two people both have "version X",
-but they actually have 'different' "X"'s. So just call it "X.1"
-and be done with it.
+ Just admit you screwed up, and use a different name. Others have
+ already seen one tag-name, and if you keep the same name, you
+ may be in the situation that two people both have "version X",
+ but they actually have 'different' "X"'s. So just call it "X.1"
+ and be done with it.
. The insane thing.
-You really want to call the new version "X" too, 'even though'
-others have already seen the old one. So just use 'git tag -f'
-again, as if you hadn't already published the old one.
+ You really want to call the new version "X" too, 'even though'
+ others have already seen the old one. So just use 'git tag -f'
+ again, as if you hadn't already published the old one.
However, Git does *not* (and it should not) change tags behind
users back. So if somebody already got the old tag, doing a
program pair is meant to be used to pull updates from a remote
repository. For push operations, see 'git send-pack'.
-
OPTIONS
-------
repository. To control what line ending style is used in the working
directory, use the `eol` attribute for a single file and the
`core.eol` configuration variable for all text files.
-Note that `core.autocrlf` overrides `core.eol`
+Note that setting `core.autocrlf` to `true` or `input` overrides
+`core.eol` (see the definitions of those options in
+linkgit:git-config[1]).
Set::
Use the following attributes if your '*.ps1' files are UTF-16 little
endian encoded without BOM and you want Git to use Windows line endings
-in the working directory. Please note, it is highly recommended to
+in the working directory (use `UTF-16-LE-BOM` instead of `UTF-16LE` if
+you want UTF-16 little endian with BOM).
+Please note, it is highly recommended to
explicitly define the line endings with `eol` if the `working-tree-encoding`
attribute is used to avoid ambiguity.
regular expression. This means that it will detect in-file (or what
rename-detection considers the same file) moves, which is noise. The
implementation runs diff twice and greps, and this can be quite
-expensive.
+expensive. To speed things up binary files without textconv filters
+will be ignored.
When `-S` or `-G` are used without `--pickaxe-all`, only filepairs
that match their respective criterion are kept in the output. When
$strict_export::
Only allow viewing of repositories also shown on the overview page.
- This for example makes `$gitweb_export_ok` file decide if repository is
- available and not only if it is shown. If `$gitweb_list` points to
+ This for example makes `$export_ok` file decide if repository is
+ available and not only if it is shown. If `$projects_list` points to
file with list of project, only those repositories listed would be
available for gitweb. Can be set during building gitweb via
`GITWEB_STRICT_EXPORT`. By default this variable is not set, which
a definitive list. By default only "tgz" is offered.
+
This feature can be configured on a per-repository basis via
-repository's `gitweb.blame` configuration variable, which contains
+repository's `gitweb.snapshot` configuration variable, which contains
a comma separated list of formats or "none" to disable snapshots.
Unknown values are ignored.
- "`!ATTR`" requires that the attribute `ATTR` be
unspecified.
+
+Note that when matching against a tree object, attributes are still
+obtained from working tree, not from the given tree object.
exclude;;
After a path matches any non-exclude pathspec, it will be run
- '%cI': committer date, strict ISO 8601 format
- '%d': ref names, like the --decorate option of linkgit:git-log[1]
- '%D': ref names without the " (", ")" wrapping.
+- '%S': ref name given on the command line by which the commit was reached
+ (like `git log --source`), only works with `git log`
- '%e': encoding
- '%s': subject
- '%f': sanitized subject line, suitable for a filename
Note that these are applied before commit
ordering and formatting options, such as `--reverse`.
---
-
-<number>::
-n <number>::
--max-count=<number>::
+
--
1. If the starting point is specified as `ref@{Nth}`, show the index
-format.
+ format.
+
2. If the starting point was specified as `ref@{now}`, show the
-timestamp format.
+ timestamp format.
+
3. If neither was used, but `--date` was given on the command line, show
-the timestamp in the format requested by `--date`.
+ the timestamp in the format requested by `--date`.
+
4. Otherwise, show the index format.
--
`<header>` text will be printed with each progress update.
endif::git-rev-list[]
---
-
History Simplification
~~~~~~~~~~~~~~~~~~~~~~
+
The form '--filter=tree:<depth>' omits all blobs and trees whose depth
from the root tree is >= <depth> (minimum depth if an object is located
-at multiple depths in the commits traversed). Currently, only <depth>=0
-is supported, which omits all blobs and trees.
+at multiple depths in the commits traversed). <depth>=0 will not include
+any trees or blobs unless included explicitly in the command-line (or
+standard input when --stdin is used). <depth>=1 will include only the
+tree and blobs which are referenced directly by a commit reachable from
+<commit> or an explicitly-given object. <depth>=2 is like <depth>=1
+while also including trees and blobs one more level removed from an
+explicitly-given commit or tree.
--no-filter::
Turn off any previous `--filter=` argument.
value (which is always measured in UTC), but does switch the accompanying
timezone value.
+
+`--date=human` shows the timezone if the timezone does not match the
+current time-zone, and doesn't print the whole date if that matches
+(ie skip printing year for dates that are "this year", but also skip
+the whole date itself if it's in the last few days and we can just say
+what weekday it was). For older dates the hour and minute is also
+omitted.
++
`--date=unix` shows the date as a Unix epoch timestamp (seconds since
1970). As with `--raw`, this is always in UTC and therefore `-local`
has no effect.
is not sorted, this function has the side effect of sorting
it.
+`oid_array_filter`::
+ Apply the callback function `want` to each entry in the array,
+ retaining only the entries for which the function returns true.
+ Preserve the order of the entries that are retained.
+
Examples
--------
of the ith commit. Stores value 0x7000000 if no parent in that
position. If there are more than two parents, the second value
has its most-significant bit on and the other bits store an array
- position into the Large Edge List chunk.
+ position into the Extra Edge List chunk.
* The next 8 bytes store the generation number of the commit and
the commit time in seconds since EPOCH. The generation number
uses the higher 30 bits of the first 4 bytes, while the commit
2 bits of the lowest byte, storing the 33rd and 34th bit of the
commit time.
- Large Edge List (ID: {'E', 'D', 'G', 'E'}) [Optional]
+ Extra Edge List (ID: {'E', 'D', 'G', 'E'}) [Optional]
This list of 4-byte values store the second through nth parents for
all octopus merges. The second parent value in the commit data stores
an array position within this list along with the most-significant bit
otherwise noted the usual pkt-line LF rules apply: the sender SHOULD
include a LF, but the receiver MUST NOT complain if it is not present.
+An error packet is a special pkt-line that contains an error string.
+
+----
+ error-line = PKT-LINE("ERR" SP explanation-text)
+----
+
+Throughout the protocol, where `PKT-LINE(...)` is expected, an error packet MAY
+be sent. Once this packet is sent by a client or a server, the data transfer
+process defined in this protocol is terminated.
+
Transports
----------
There are three transports over which the packfile protocol is
"0039git-upload-pack /schacon/gitbook.git\0host=example.com\0" |
nc -v example.com 9418
-If the server refuses the request for some reasons, it could abort
-gracefully with an error message.
-
-----
- error-line = PKT-LINE("ERR" SP explanation-text)
-----
-
SSH Transport
-------------
Then the server will start sending its packfile data.
----
- server-response = *ack_multi ack / nak / error-line
+ server-response = *ack_multi ack / nak
ack_multi = PKT-LINE("ACK" SP obj-id ack_status)
ack_status = "continue" / "common" / "ready"
ack = PKT-LINE("ACK" SP obj-id)
nak = PKT-LINE("NAK")
- error-line = PKT-LINE("ERR" SP explanation-text)
----
A simple clone may look like this (with no 'have' lines):
that it has, either because the local repository has that object in one of
its promisor packfiles, or because another promisor object refers to it.
+
-When Git encounters a missing object, Git can see if it a promisor object
+When Git encounters a missing object, Git can see if it is a promisor object
and handle it appropriately. If not, Git can report a corruption.
+
This means that there is no need for the client to explicitly maintain an
Request that various objects from the packfile be omitted
using one of several filtering techniques. These are intended
for use with partial clone and partial fetch operations. See
- `rev-list` for possible "filter-spec" values.
+ `rev-list` for possible "filter-spec" values. When communicating
+ with other processes, senders SHOULD translate scaled integers
+ (e.g. "1k") into a fully-expanded form (e.g. "1024") to aid
+ interoperability with older receivers that may not understand
+ newly-invented scaling suffixes. However, receivers SHOULD
+ accept the following suffixes: 'k', 'm', and 'g' for 1024,
+ 1048576, and 1073741824, respectively.
If the 'ref-in-want' feature is advertised, the following argument can
be included in the client's request as well as the potential addition of
particular ref, where <ref> is the full name of a ref on the
server.
+If the 'sideband-all' feature is advertised, the following argument can be
+included in the client's request:
+
+ sideband-all
+ Instruct the server to send the whole response multiplexed, not just
+ the packfile section. All non-flush and non-delim PKT-LINE in the
+ response (not only in the packfile section) will then start with a byte
+ indicating its sideband (1, 2, or 3), and the server may send "0005\2"
+ (a PKT-LINE of sideband 2 with no payload) as a keepalive packet.
+
The response of `fetch` is broken into a number of sections separated by
delimiter packets (0001), with each section beginning with its section
header.
#!/bin/sh
GVF=GIT-VERSION-FILE
-DEF_VER=v2.20.1
+DEF_VER=v2.20.GIT
LF='
'
# in one call to the platform's SHA1_Update(). e.g. APPLE_COMMON_CRYPTO
# wants 'SHA1_MAX_BLOCK_SIZE=1024L*1024L*1024L' defined.
#
+# Define BLK_SHA256 to use the built-in SHA-256 routines.
+#
+# Define GCRYPT_SHA256 to use the SHA-256 routines in libgcrypt.
+#
+# Define OPENSSL_SHA256 to use the SHA-256 routines in OpenSSL.
+#
# Define NEEDS_CRYPTO_WITH_SSL if you need -lcrypto when using -lssl (Darwin).
#
# Define NEEDS_SSL_WITH_CRYPTO if you need -lssl when using -lcrypto (Darwin).
SCRIPT_LIB += git-rebase--am
SCRIPT_LIB += git-rebase--common
SCRIPT_LIB += git-rebase--preserve-merges
-SCRIPT_LIB += git-rebase--merge
SCRIPT_LIB += git-sh-setup
SCRIPT_LIB += git-sh-i18n
ETAGS_TARGET = TAGS
+FUZZ_OBJS += fuzz-commit-graph.o
FUZZ_OBJS += fuzz-pack-headers.o
FUZZ_OBJS += fuzz-pack-idx.o
TEST_BUILTINS_OBJS += test-dump-untracked-cache.o
TEST_BUILTINS_OBJS += test-example-decorate.o
TEST_BUILTINS_OBJS += test-genrandom.o
+TEST_BUILTINS_OBJS += test-hash.o
TEST_BUILTINS_OBJS += test-hashmap.o
+TEST_BUILTINS_OBJS += test-hash-speed.o
TEST_BUILTINS_OBJS += test-index-version.o
TEST_BUILTINS_OBJS += test-json-writer.o
TEST_BUILTINS_OBJS += test-lazy-init-name-hash.o
TEST_BUILTINS_OBJS += test-scrap-cache-tree.o
TEST_BUILTINS_OBJS += test-sha1.o
TEST_BUILTINS_OBJS += test-sha1-array.o
+TEST_BUILTINS_OBJS += test-sha256.o
TEST_BUILTINS_OBJS += test-sigchain.o
TEST_BUILTINS_OBJS += test-strcmp-offset.o
TEST_BUILTINS_OBJS += test-string-list.o
endif
endif
+ifdef OPENSSL_SHA256
+ EXTLIBS += $(LIB_4_CRYPTO)
+ BASIC_CFLAGS += -DSHA256_OPENSSL
+else
+ifdef GCRYPT_SHA256
+ BASIC_CFLAGS += -DSHA256_GCRYPT
+ EXTLIBS += -lgcrypt
+else
+ LIB_OBJS += sha256/block/sha256.o
+ BASIC_CFLAGS += -DSHA256_BLK
+endif
+endif
+
ifdef SHA1_MAX_BLOCK_SIZE
LIB_OBJS += compat/sha1-chunked.o
BASIC_CFLAGS += -DSHA1_MAX_BLOCK_SIZE="$(SHA1_MAX_BLOCK_SIZE)"
# An example command to build against libFuzzer from LLVM 4.0.0:
#
# make CC=clang CXX=clang++ \
-# FUZZ_CXXFLAGS="-fsanitize-coverage=trace-pc-guard -fsanitize=address" \
+# CFLAGS="-fsanitize-coverage=trace-pc-guard -fsanitize=address" \
# LIB_FUZZING_ENGINE=/usr/lib/llvm-4.0/lib/libFuzzer.a \
# fuzz-all
#
-Documentation/RelNotes/2.20.1.txt
\ No newline at end of file
+Documentation/RelNotes/2.21.0.txt
\ No newline at end of file
int advice_push_already_exists = 1;
int advice_push_fetch_first = 1;
int advice_push_needs_force = 1;
+int advice_push_unqualified_ref_name = 1;
int advice_status_hints = 1;
int advice_status_u_option = 1;
int advice_commit_before_merge = 1;
{ "pushAlreadyExists", &advice_push_already_exists },
{ "pushFetchFirst", &advice_push_fetch_first },
{ "pushNeedsForce", &advice_push_needs_force },
+ { "pushUnqualifiedRefName", &advice_push_unqualified_ref_name },
{ "statusHints", &advice_status_hints },
{ "statusUoption", &advice_status_u_option },
{ "commitBeforeMerge", &advice_commit_before_merge },
extern int advice_push_already_exists;
extern int advice_push_fetch_first;
extern int advice_push_needs_force;
+extern int advice_push_unqualified_ref_name;
extern int advice_status_hints;
extern int advice_status_u_option;
extern int advice_commit_before_merge;
#define SPLIT_CMDLINE_BAD_ENDING 1
#define SPLIT_CMDLINE_UNCLOSED_QUOTE 2
static const char *split_cmdline_errors[] = {
- "cmdline ends with \\",
- "unclosed quote"
+ N_("cmdline ends with \\"),
+ N_("unclosed quote")
};
int split_cmdline(char *cmdline, const char ***argv)
return obj;
}
-unsigned int alloc_commit_index(struct repository *r)
+static unsigned int alloc_commit_index(struct repository *r)
{
return r->parsed_objects->commit_count++;
}
-void *alloc_commit_node(struct repository *r)
+void init_commit_node(struct repository *r, struct commit *c)
{
- struct commit *c = alloc_node(r->parsed_objects->commit_state, sizeof(struct commit));
c->object.type = OBJ_COMMIT;
c->index = alloc_commit_index(r);
c->graph_pos = COMMIT_NOT_FROM_GRAPH;
c->generation = GENERATION_NUMBER_INFINITY;
+}
+
+void *alloc_commit_node(struct repository *r)
+{
+ struct commit *c = alloc_node(r->parsed_objects->commit_state, sizeof(struct commit));
+ init_commit_node(r, c);
return c;
}
void *alloc_blob_node(struct repository *r);
void *alloc_tree_node(struct repository *r);
+void init_commit_node(struct repository *r, struct commit *c);
void *alloc_commit_node(struct repository *r);
void *alloc_tag_node(struct repository *r);
void *alloc_object_node(struct repository *r);
void alloc_report(struct repository *r);
-unsigned int alloc_commit_index(struct repository *r);
struct alloc_state *allocate_alloc_state(void);
void clear_alloc_state(struct alloc_state *s);
static char *find_name_gnu(struct apply_state *state,
const char *line,
- const char *def,
int p_value)
{
struct strbuf name = STRBUF_INIT;
int terminate)
{
if (*line == '"') {
- char *name = find_name_gnu(state, line, def, p_value);
+ char *name = find_name_gnu(state, line, p_value);
if (name)
return name;
}
size_t date_len;
if (*line == '"') {
- char *name = find_name_gnu(state, line, def, p_value);
+ char *name = find_name_gnu(state, line, p_value);
if (name)
return name;
}
costate.refresh_cache = 1;
costate.istate = istate;
- if (checkout_entry(ce, &costate, NULL) || lstat(ce->name, st))
+ if (checkout_entry(ce, &costate, NULL, NULL) ||
+ lstat(ce->name, st))
return error(_("cannot checkout %s"), ce->name);
return 0;
}
return read_index_from(state->repo->index, state->index_file,
get_git_dir());
else
- return read_index(state->repo->index);
+ return repo_read_index(state->repo);
}
/* This function tries to read the object name from the current index */
state->index_file,
LOCK_DIE_ON_ERROR);
else
- hold_locked_index(&state->lock_file, LOCK_DIE_ON_ERROR);
+ repo_hold_locked_index(state->repo, &state->lock_file,
+ LOCK_DIE_ON_ERROR);
}
if (state->check_index && read_apply_cache(state) < 0) {
* string and appends it to a struct strbuf.
*/
static void strbuf_append_ext_header(struct strbuf *sb, const char *keyword,
- const char *value, unsigned int valuelen)
+ const char *value, unsigned int valuelen)
{
int len, tmp;
}
static void format_subst(const struct commit *commit,
- const char *src, size_t len,
- struct strbuf *buf)
+ const char *src, size_t len,
+ struct strbuf *buf)
{
char *to_free = NULL;
struct strbuf fmt = STRBUF_INIT;
git_attr_set_direction(GIT_ATTR_INDEX);
}
- err = read_tree_recursive(args->tree, "", 0, 0, &args->pathspec,
+ err = read_tree_recursive(args->repo, args->tree, "",
+ 0, 0, &args->pathspec,
queue_or_write_archive_entry,
&context);
if (err == READ_TREE_RECURSIVE)
ctx.args = args;
parse_pathspec(&ctx.pathspec, 0, 0, "", paths);
ctx.pathspec.recursive = 1;
- ret = read_tree_recursive(args->tree, "", 0, 0, &ctx.pathspec,
+ ret = read_tree_recursive(args->repo, args->tree, "",
+ 0, 0, &ctx.pathspec,
reject_entry, &ctx);
clear_pathspec(&ctx.pathspec);
return ret != 0;
int refnamelen = colon - name;
if (!dwim_ref(name, refnamelen, &oid, &ref))
- die("no such ref: %.*s", refnamelen, name);
+ die(_("no such ref: %.*s"), refnamelen, name);
free(ref);
}
if (get_oid(name, &oid))
- die("Not a valid object name");
+ die(_("not a valid object name: %s"), name);
commit = lookup_commit_reference_gently(ar_args->repo, &oid, 1);
if (commit) {
tree = parse_tree_indirect(&oid);
if (tree == NULL)
- die("not a tree object");
+ die(_("not a tree object: %s"), oid_to_hex(&oid));
if (prefix) {
struct object_id tree_oid;
err = get_tree_entry(&tree->object.oid, prefix, &tree_oid,
&mode);
if (err || !S_ISDIR(mode))
- die("current working directory is untracked");
+ die(_("current working directory is untracked"));
tree = parse_tree_indirect(&tree_oid);
}
* an insanely large number of attributes.
*/
-#define NO_THE_INDEX_COMPATIBILITY_MACROS
#include "cache.h"
#include "config.h"
#include "exec-cmd.h"
if (strlen(ATTRIBUTE_MACRO_PREFIX) < namelen &&
starts_with(name, ATTRIBUTE_MACRO_PREFIX)) {
if (!macro_ok) {
- fprintf(stderr, "%s not allowed: %s:%d\n",
- name, src, lineno);
+ fprintf_ln(stderr, _("%s not allowed: %s:%d"),
+ name, src, lineno);
goto fail_return;
}
is_macro = 1;
const char *path,
struct attr_check *check)
{
- int i, pathlen, rem, dirlen;
+ int pathlen, rem, dirlen;
const char *cp, *last_slash = NULL;
int basename_offset;
all_attrs_init(&g_attr_hashmap, check);
determine_macros(check->all_attrs, check->stack);
- if (check->nr) {
- rem = 0;
- for (i = 0; i < check->nr; i++) {
- int n = check->items[i].attr->attr_nr;
- struct all_attrs_item *item = &check->all_attrs[n];
- if (item->macro) {
- item->value = ATTR__UNSET;
- rem++;
- }
- }
- if (rem == check->nr)
- return;
- }
-
rem = check->all_attrs_nr;
fill(path, pathlen, basename_offset, check->stack, check->all_attrs, rem);
}
#define strcat(x,y) BANNED(strcat)
#undef strncpy
#define strncpy(x,y,n) BANNED(strncpy)
+#undef strncat
+#define strncat(x,y,n) BANNED(strncat)
#undef sprintf
#undef vsprintf
* is increased by one between each call, but that should not matter
* for this application.
*/
-static unsigned get_prn(unsigned count) {
+static unsigned get_prn(unsigned count)
+{
count = count * 1103515245 + 12345;
return (count/65536) % PRN_MODULO;
}
return skip_away(list, count);
}
-static void bisect_rev_setup(struct rev_info *revs, const char *prefix,
+static void bisect_rev_setup(struct repository *r, struct rev_info *revs,
+ const char *prefix,
const char *bad_format, const char *good_format,
int read_paths)
{
struct argv_array rev_argv = ARGV_ARRAY_INIT;
int i;
- repo_init_revisions(the_repository, revs, prefix);
+ repo_init_revisions(r, revs, prefix);
revs->abbrev = 0;
revs->commit_format = CMIT_FMT_UNSPECIFIED;
if (prepare_revision_walk(revs))
die("revision walk setup failed");
if (revs->tree_objects)
- mark_edges_uninteresting(revs, NULL);
+ mark_edges_uninteresting(revs, NULL, 0);
}
static void exit_if_skipped_commits(struct commit_list *tried,
return run_command_v_opt(argv_show_branch, RUN_GIT_CMD);
}
-static struct commit *get_commit_reference(const struct object_id *oid)
+static struct commit *get_commit_reference(struct repository *r,
+ const struct object_id *oid)
{
- struct commit *r = lookup_commit_reference(the_repository, oid);
- if (!r)
+ struct commit *c = lookup_commit_reference(r, oid);
+ if (!c)
die(_("Not a valid commit name %s"), oid_to_hex(oid));
- return r;
+ return c;
}
-static struct commit **get_bad_and_good_commits(int *rev_nr)
+static struct commit **get_bad_and_good_commits(struct repository *r,
+ int *rev_nr)
{
struct commit **rev;
int i, n = 0;
ALLOC_ARRAY(rev, 1 + good_revs.nr);
- rev[n++] = get_commit_reference(current_bad_oid);
+ rev[n++] = get_commit_reference(r, current_bad_oid);
for (i = 0; i < good_revs.nr; i++)
- rev[n++] = get_commit_reference(good_revs.oid + i);
+ rev[n++] = get_commit_reference(r, good_revs.oid + i);
*rev_nr = n;
return rev;
free_commit_list(result);
}
-static int check_ancestors(int rev_nr, struct commit **rev, const char *prefix)
+static int check_ancestors(struct repository *r, int rev_nr,
+ struct commit **rev, const char *prefix)
{
struct rev_info revs;
int res;
- bisect_rev_setup(&revs, prefix, "^%s", "%s", 0);
+ bisect_rev_setup(r, &revs, prefix, "^%s", "%s", 0);
bisect_common(&revs);
res = (revs.commits != NULL);
* If a merge base must be tested by the user, its source code will be
* checked out to be tested by the user and we will exit.
*/
-static void check_good_are_ancestors_of_bad(const char *prefix, int no_checkout)
+static void check_good_are_ancestors_of_bad(struct repository *r,
+ const char *prefix,
+ int no_checkout)
{
char *filename = git_pathdup("BISECT_ANCESTORS_OK");
struct stat st;
goto done;
/* Check if all good revs are ancestor of the bad rev. */
- rev = get_bad_and_good_commits(&rev_nr);
- if (check_ancestors(rev_nr, rev, prefix))
+ rev = get_bad_and_good_commits(r, &rev_nr);
+ if (check_ancestors(r, rev_nr, rev, prefix))
check_merge_bases(rev_nr, rev, no_checkout);
free(rev);
/*
* This does "git diff-tree --pretty COMMIT" without one fork+exec.
*/
-static void show_diff_tree(const char *prefix, struct commit *commit)
+static void show_diff_tree(struct repository *r,
+ const char *prefix,
+ struct commit *commit)
{
struct rev_info opt;
/* diff-tree init */
- repo_init_revisions(the_repository, &opt, prefix);
+ repo_init_revisions(r, &opt, prefix);
git_config(git_diff_basic_config, NULL); /* no "diff" UI options */
opt.abbrev = 0;
opt.diff = 1;
* If no_checkout is non-zero, the bisection process does not
* checkout the trial commit but instead simply updates BISECT_HEAD.
*/
-int bisect_next_all(const char *prefix, int no_checkout)
+int bisect_next_all(struct repository *r, const char *prefix, int no_checkout)
{
struct rev_info revs;
struct commit_list *tried;
if (read_bisect_refs())
die(_("reading bisect refs failed"));
- check_good_are_ancestors_of_bad(prefix, no_checkout);
+ check_good_are_ancestors_of_bad(r, prefix, no_checkout);
- bisect_rev_setup(&revs, prefix, "%s", "^%s", 1);
+ bisect_rev_setup(r, &revs, prefix, "%s", "^%s", 1);
revs.limited = 1;
bisect_common(&revs);
exit_if_skipped_commits(tried, current_bad_oid);
printf("%s is the first %s commit\n", oid_to_hex(bisect_rev),
term_bad);
- show_diff_tree(prefix, revs.commits->item);
+ show_diff_tree(r, prefix, revs.commits->item);
/* This means the bisection process succeeded. */
exit(10);
}
#define BISECT_H
struct commit_list;
+struct repository;
/*
* Find bisection. If something is found, `reaches` will be the number of
const char *header_prefix;
};
-extern int bisect_next_all(const char *prefix, int no_checkout);
+extern int bisect_next_all(struct repository *r,
+ const char *prefix,
+ int no_checkout);
extern int estimate_bisect_steps(int all);
die("no such path '%s' in HEAD", path);
}
-static struct commit_list **append_parent(struct commit_list **tail, const struct object_id *oid)
+static struct commit_list **append_parent(struct repository *r,
+ struct commit_list **tail,
+ const struct object_id *oid)
{
struct commit *parent;
- parent = lookup_commit_reference(the_repository, oid);
+ parent = lookup_commit_reference(r, oid);
if (!parent)
die("no such commit %s", oid_to_hex(oid));
return &commit_list_insert(parent, tail)->next;
}
-static void append_merge_parents(struct commit_list **tail)
+static void append_merge_parents(struct repository *r,
+ struct commit_list **tail)
{
int merge_head;
struct strbuf line = STRBUF_INIT;
- merge_head = open(git_path_merge_head(the_repository), O_RDONLY);
+ merge_head = open(git_path_merge_head(r), O_RDONLY);
if (merge_head < 0) {
if (errno == ENOENT)
return;
die("cannot open '%s' for reading",
- git_path_merge_head(the_repository));
+ git_path_merge_head(r));
}
while (!strbuf_getwholeline_fd(&line, merge_head, '\n')) {
struct object_id oid;
if (line.len < GIT_SHA1_HEXSZ || get_oid_hex(line.buf, &oid))
die("unknown line in '%s': %s",
- git_path_merge_head(the_repository), line.buf);
- tail = append_parent(tail, &oid);
+ git_path_merge_head(r), line.buf);
+ tail = append_parent(r, tail, &oid);
}
close(merge_head);
strbuf_release(&line);
* want to transfer ownership of the buffer to the commit (so we
* must use detach).
*/
-static void set_commit_buffer_from_strbuf(struct commit *c, struct strbuf *sb)
+static void set_commit_buffer_from_strbuf(struct repository *r,
+ struct commit *c,
+ struct strbuf *sb)
{
size_t len;
void *buf = strbuf_detach(sb, &len);
- set_commit_buffer(the_repository, c, buf, len);
+ set_commit_buffer(r, c, buf, len);
}
/*
unsigned mode;
struct strbuf msg = STRBUF_INIT;
- read_index(r->index);
+ repo_read_index(r);
time(&now);
- commit = alloc_commit_node(the_repository);
+ commit = alloc_commit_node(r);
commit->object.parsed = 1;
commit->date = now;
parent_tail = &commit->parents;
if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, &head_oid, NULL))
die("no such ref: HEAD");
- parent_tail = append_parent(parent_tail, &head_oid);
- append_merge_parents(parent_tail);
+ parent_tail = append_parent(r, parent_tail, &head_oid);
+ append_merge_parents(r, parent_tail);
verify_working_tree_path(r, commit, path);
origin = make_origin(commit, path);
ident, ident, path,
(!contents_from ? path :
(!strcmp(contents_from, "-") ? "standard input" : contents_from)));
- set_commit_buffer_from_strbuf(commit, &msg);
+ set_commit_buffer_from_strbuf(r, commit, &msg);
if (!contents_from || strcmp("-", contents_from)) {
struct stat st;
* want to run "diff-index --cached".
*/
discard_index(r->index);
- read_index(r->index);
+ repo_read_index(r);
len = strlen(path);
if (!mode) {
struct object *obj = revs->pending.objects[i].item;
if (obj->flags & UNINTERESTING)
continue;
- obj = deref_tag(the_repository, obj, NULL, 0);
+ obj = deref_tag(revs->repo, obj, NULL, 0);
if (obj->type != OBJ_COMMIT)
die("Non commit %s?", revs->pending.objects[i].name);
if (found)
/* Is that sole rev a committish? */
obj = revs->pending.objects[0].item;
- obj = deref_tag(the_repository, obj, NULL, 0);
+ obj = deref_tag(revs->repo, obj, NULL, 0);
if (obj->type != OBJ_COMMIT)
return NULL;
/* Do we have HEAD? */
if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, &head_oid, NULL))
return NULL;
- head_commit = lookup_commit_reference_gently(the_repository,
+ head_commit = lookup_commit_reference_gently(revs->repo,
&head_oid, 1);
if (!head_commit)
return NULL;
struct object *obj = revs->pending.objects[i].item;
if (!(obj->flags & UNINTERESTING))
continue;
- obj = deref_tag(the_repository, obj, NULL, 0);
+ obj = deref_tag(revs->repo, obj, NULL, 0);
if (obj->type != OBJ_COMMIT)
die("Non commit %s?", revs->pending.objects[i].name);
if (found)
"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(const char *name, const char *start_name,
+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)
{
break;
}
- if ((commit = lookup_commit_reference(the_repository, &oid)) == NULL)
+ if ((commit = lookup_commit_reference(r, &oid)) == NULL)
die(_("Not a valid branch point: '%s'."), start_name);
oidcpy(&oid, &commit->object.oid);
free(real_ref);
}
-void remove_branch_state(void)
+void remove_branch_state(struct repository *r)
{
- unlink(git_path_cherry_pick_head(the_repository));
- unlink(git_path_revert_head(the_repository));
- unlink(git_path_merge_head(the_repository));
- unlink(git_path_merge_rr(the_repository));
- unlink(git_path_merge_msg(the_repository));
- unlink(git_path_merge_mode(the_repository));
- unlink(git_path_squash_msg(the_repository));
+ unlink(git_path_cherry_pick_head(r));
+ unlink(git_path_revert_head(r));
+ unlink(git_path_merge_head(r));
+ unlink(git_path_merge_rr(r));
+ unlink(git_path_merge_msg(r));
+ unlink(git_path_merge_mode(r));
+ unlink(git_path_squash_msg(r));
}
void die_if_checked_out(const char *branch, int ignore_current_worktree)
#ifndef BRANCH_H
#define BRANCH_H
+struct repository;
struct strbuf;
enum branch_track {
/*
* Creates a new branch, where:
*
+ * - r is the repository to add a branch to
+ *
* - name is the new branch name
*
* - start_name is the name of the existing branch that the new branch should
* that start_name is a tracking branch for (if any).
*
*/
-void create_branch(const char *name, const char *start_name,
+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);
* Remove information about the state of working on the current
* branch. (E.g., MERGE_HEAD)
*/
-void remove_branch_state(void);
+void remove_branch_state(struct repository *r);
/*
* Configure local branch "local" as downstream to branch "remote"
*
* Copyright (C) 2006 Linus Torvalds
*/
+#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "cache.h"
#include "config.h"
#include "builtin.h"
continue; /* do not touch non blobs */
if (pathspec && !ce_path_match(&the_index, ce, pathspec, NULL))
continue;
- retval |= add_file_to_cache(ce->name, flags | HASH_RENORMALIZE);
+ retval |= add_file_to_cache(ce->name, flags | ADD_CACHE_RENORMALIZE);
}
return retval;
die(_("pathspec '%s' did not match any files"),
pathspec->items[i].match);
}
- free(seen);
+ free(seen);
}
int run_add_interactive(const char *revision, const char *patch_mode,
rev.diffopt.output_format = DIFF_FORMAT_PATCH;
rev.diffopt.use_color = 0;
rev.diffopt.flags.ignore_dirty_submodules = 1;
- out = open(file, O_CREAT | O_WRONLY, 0666);
+ out = open(file, O_CREAT | O_WRONLY | O_TRUNC, 0666);
if (out < 0)
die(_("Could not open '%s' for writing."), file);
rev.diffopt.file = xfdopen(out, "w");
*
* Based on git-am.sh by Junio C Hamano.
*/
+#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "cache.h"
#include "config.h"
#include "builtin.h"
#include "packfile.h"
#include "repository.h"
-/**
- * Returns 1 if the file is empty or does not exist, 0 otherwise.
- */
-static int is_empty_file(const char *filename)
-{
- struct stat st;
-
- if (stat(filename, &st) < 0) {
- if (errno == ENOENT)
- return 1;
- die_errno(_("could not stat %s"), filename);
- }
-
- return !st.st_size;
-}
-
/**
* Returns the length of the first line of msg.
*/
}
finish:
- finish_copy_notes_for_rewrite(c, msg);
+ finish_copy_notes_for_rewrite(the_repository, c, msg);
fclose(fp);
strbuf_release(&sb);
return ret;
goto finish;
}
- if (is_empty_file(am_path(state, "patch"))) {
+ if (is_empty_or_missing_file(am_path(state, "patch"))) {
printf_ln(_("Patch is empty."));
die_user_resolve(state);
}
* changes.
*/
- init_merge_options(&o);
+ init_merge_options(&o, the_repository);
o.branch1 = "HEAD";
their_tree_name = xstrfmt("%.*s", linelen(state->msg), state->msg);
refresh_and_write_cache();
- if (index_has_changes(&the_index, NULL, &sb)) {
+ if (repo_index_has_changes(the_repository, NULL, &sb)) {
write_state_bool(state, "dirtyindex", 1);
die(_("Dirty index: cannot apply patches (dirty: %s)"), sb.buf);
}
* the result may have produced the same tree as ours.
*/
if (!apply_status &&
- !index_has_changes(&the_index, NULL, NULL)) {
+ !repo_index_has_changes(the_repository, NULL, NULL)) {
say(state, stdout, _("No changes -- Patch already applied."));
goto next;
}
resume = 0;
}
- if (!is_empty_file(am_path(state, "rewritten"))) {
+ if (!is_empty_or_missing_file(am_path(state, "rewritten"))) {
assert(state->rebasing);
copy_notes_for_rebase(state);
run_post_rewrite_hook(state);
say(state, stdout, _("Applying: %.*s"), linelen(state->msg), state->msg);
- if (!index_has_changes(&the_index, NULL, NULL)) {
+ if (!repo_index_has_changes(the_repository, NULL, NULL)) {
printf_ln(_("No changes - did you forget to use 'git add'?\n"
"If there is nothing left to stage, chances are that something else\n"
"already introduced the same changes; you might want to skip this patch."));
if (merge_tree(remote_tree))
return -1;
- remove_branch_state();
+ remove_branch_state(the_repository);
return 0;
}
static void am_rerere_clear(void)
{
struct string_list merge_rr = STRING_LIST_INIT_DUP;
- rerere_clear(&merge_rr);
+ rerere_clear(the_repository, &merge_rr);
string_list_clear(&merge_rr, 1);
}
if (clean_index(&head, &head))
die(_("failed to clean index"));
+ if (state->rebasing) {
+ FILE *fp = xfopen(am_path(state, "rewritten"), "a");
+
+ assert(!is_null_oid(&state->orig_commit));
+ fprintf(fp, "%s ", oid_to_hex(&state->orig_commit));
+ fprintf(fp, "%s\n", oid_to_hex(&head));
+ fclose(fp);
+ }
+
am_next(state);
am_load(state);
am_run(state, 0);
/* Ensure a valid committer ident can be constructed */
git_committer_info(IDENT_STRICT);
- if (read_index_preload(&the_index, NULL, 0) < 0)
+ if (repo_read_index_preload(the_repository, NULL, 0) < 0)
die(_("failed to read the index"));
if (in_progress) {
const char *remote, const char *exec,
const char *name_hint)
{
- char *buf;
int fd[2], i, rv;
struct transport *transport;
struct remote *_remote;
+ struct packet_reader reader;
_remote = remote_get(remote);
if (!_remote->url[0])
packet_write_fmt(fd[1], "argument %s\n", argv[i]);
packet_flush(fd[1]);
- buf = packet_read_line(fd[0], NULL);
- if (!buf)
+ packet_reader_init(&reader, fd[0], NULL, 0,
+ PACKET_READ_CHOMP_NEWLINE |
+ PACKET_READ_DIE_ON_ERR_PACKET);
+
+ if (packet_reader_read(&reader) != PACKET_READ_NORMAL)
die(_("git archive: expected ACK/NAK, got a flush packet"));
- if (strcmp(buf, "ACK")) {
- if (starts_with(buf, "NACK "))
- die(_("git archive: NACK %s"), buf + 5);
- if (starts_with(buf, "ERR "))
- die(_("remote error: %s"), buf + 4);
+ if (strcmp(reader.line, "ACK")) {
+ if (starts_with(reader.line, "NACK "))
+ die(_("git archive: NACK %s"), reader.line + 5);
die(_("git archive: protocol error"));
}
- if (packet_read_line(fd[0], NULL))
+ if (packet_reader_read(&reader) != PACKET_READ_FLUSH)
die(_("git archive: expected a flush"));
/* Now, start reading from fd[0] and spit it out to stdout */
#include "parse-options.h"
#include "bisect.h"
#include "refs.h"
+#include "dir.h"
+#include "argv-array.h"
+#include "run-command.h"
+#include "prompt.h"
+#include "quote.h"
static GIT_PATH_FUNC(git_path_bisect_terms, "BISECT_TERMS")
static GIT_PATH_FUNC(git_path_bisect_expected_rev, "BISECT_EXPECTED_REV")
static GIT_PATH_FUNC(git_path_bisect_ancestors_ok, "BISECT_ANCESTORS_OK")
+static GIT_PATH_FUNC(git_path_bisect_start, "BISECT_START")
+static GIT_PATH_FUNC(git_path_bisect_head, "BISECT_HEAD")
+static GIT_PATH_FUNC(git_path_bisect_log, "BISECT_LOG")
+static GIT_PATH_FUNC(git_path_head_name, "head-name")
+static GIT_PATH_FUNC(git_path_bisect_names, "BISECT_NAMES")
static const char * const git_bisect_helper_usage[] = {
N_("git bisect--helper --next-all [--no-checkout]"),
N_("git bisect--helper --write-terms <bad_term> <good_term>"),
N_("git bisect--helper --bisect-clean-state"),
+ N_("git bisect--helper --bisect-reset [<commit>]"),
+ N_("git bisect--helper --bisect-write [--no-log] <state> <revision> <good_term> <bad_term>"),
+ N_("git bisect--helper --bisect-check-and-set-terms <command> <good_term> <bad_term>"),
+ N_("git bisect--helper --bisect-next-check <good_term> <bad_term> [<term>]"),
+ N_("git bisect--helper --bisect-terms [--term-good | --term-old | --term-bad | --term-new]"),
+ N_("git bisect--helper --bisect-start [--term-{old,good}=<term> --term-{new,bad}=<term>]"
+ "[--no-checkout] [<bad> [<good>...]] [--] [<paths>...]"),
NULL
};
+struct bisect_terms {
+ char *term_good;
+ char *term_bad;
+};
+
+static void free_terms(struct bisect_terms *terms)
+{
+ FREE_AND_NULL(terms->term_good);
+ FREE_AND_NULL(terms->term_bad);
+}
+
+static void set_terms(struct bisect_terms *terms, const char *bad,
+ const char *good)
+{
+ free((void *)terms->term_good);
+ terms->term_good = xstrdup(good);
+ free((void *)terms->term_bad);
+ terms->term_bad = xstrdup(bad);
+}
+
+static const char *vocab_bad = "bad|new";
+static const char *vocab_good = "good|old";
+
/*
* Check whether the string `term` belongs to the set of strings
* included in the variable arguments.
}
}
+static int bisect_reset(const char *commit)
+{
+ struct strbuf branch = STRBUF_INIT;
+
+ if (!commit) {
+ if (strbuf_read_file(&branch, git_path_bisect_start(), 0) < 1) {
+ printf(_("We are not bisecting.\n"));
+ return 0;
+ }
+ strbuf_rtrim(&branch);
+ } else {
+ struct object_id oid;
+
+ if (get_oid_commit(commit, &oid))
+ return error(_("'%s' is not a valid commit"), commit);
+ strbuf_addstr(&branch, commit);
+ }
+
+ if (!file_exists(git_path_bisect_head())) {
+ struct argv_array argv = ARGV_ARRAY_INIT;
+
+ argv_array_pushl(&argv, "checkout", branch.buf, "--", NULL);
+ if (run_command_v_opt(argv.argv, RUN_GIT_CMD)) {
+ strbuf_release(&branch);
+ argv_array_clear(&argv);
+ return error(_("could not check out original"
+ " HEAD '%s'. Try 'git bisect"
+ "reset <commit>'."), branch.buf);
+ }
+ argv_array_clear(&argv);
+ }
+
+ strbuf_release(&branch);
+ return bisect_clean_state();
+}
+
+static void log_commit(FILE *fp, char *fmt, const char *state,
+ struct commit *commit)
+{
+ struct pretty_print_context pp = {0};
+ struct strbuf commit_msg = STRBUF_INIT;
+ char *label = xstrfmt(fmt, state);
+
+ format_commit_message(commit, "%s", &commit_msg, &pp);
+
+ fprintf(fp, "# %s: [%s] %s\n", label, oid_to_hex(&commit->object.oid),
+ commit_msg.buf);
+
+ strbuf_release(&commit_msg);
+ free(label);
+}
+
+static int bisect_write(const char *state, const char *rev,
+ const struct bisect_terms *terms, int nolog)
+{
+ struct strbuf tag = STRBUF_INIT;
+ struct object_id oid;
+ struct commit *commit;
+ FILE *fp = NULL;
+ int retval = 0;
+
+ if (!strcmp(state, terms->term_bad)) {
+ strbuf_addf(&tag, "refs/bisect/%s", state);
+ } else if (one_of(state, terms->term_good, "skip", NULL)) {
+ strbuf_addf(&tag, "refs/bisect/%s-%s", state, rev);
+ } else {
+ retval = error(_("Bad bisect_write argument: %s"), state);
+ goto finish;
+ }
+
+ if (get_oid(rev, &oid)) {
+ retval = error(_("couldn't get the oid of the rev '%s'"), rev);
+ goto finish;
+ }
+
+ if (update_ref(NULL, tag.buf, &oid, NULL, 0,
+ UPDATE_REFS_MSG_ON_ERR)) {
+ retval = -1;
+ goto finish;
+ }
+
+ fp = fopen(git_path_bisect_log(), "a");
+ if (!fp) {
+ retval = error_errno(_("couldn't open the file '%s'"), git_path_bisect_log());
+ goto finish;
+ }
+
+ commit = lookup_commit_reference(the_repository, &oid);
+ log_commit(fp, "%s", state, commit);
+
+ if (!nolog)
+ fprintf(fp, "git bisect %s %s\n", state, rev);
+
+finish:
+ if (fp)
+ fclose(fp);
+ strbuf_release(&tag);
+ return retval;
+}
+
+static int check_and_set_terms(struct bisect_terms *terms, const char *cmd)
+{
+ int has_term_file = !is_empty_or_missing_file(git_path_bisect_terms());
+
+ if (one_of(cmd, "skip", "start", "terms", NULL))
+ return 0;
+
+ if (has_term_file && strcmp(cmd, terms->term_bad) &&
+ strcmp(cmd, terms->term_good))
+ return error(_("Invalid command: you're currently in a "
+ "%s/%s bisect"), terms->term_bad,
+ terms->term_good);
+
+ if (!has_term_file) {
+ if (one_of(cmd, "bad", "good", NULL)) {
+ set_terms(terms, "bad", "good");
+ return write_terms(terms->term_bad, terms->term_good);
+ }
+ if (one_of(cmd, "new", "old", NULL)) {
+ set_terms(terms, "new", "old");
+ return write_terms(terms->term_bad, terms->term_good);
+ }
+ }
+
+ return 0;
+}
+
+static int mark_good(const char *refname, const struct object_id *oid,
+ int flag, void *cb_data)
+{
+ int *m_good = (int *)cb_data;
+ *m_good = 0;
+ return 1;
+}
+
+static const char *need_bad_and_good_revision_warning =
+ N_("You need to give me at least one %s and %s revision.\n"
+ "You can use \"git bisect %s\" and \"git bisect %s\" for that.");
+
+static const char *need_bisect_start_warning =
+ N_("You need to start by \"git bisect start\".\n"
+ "You then need to give me at least one %s and %s revision.\n"
+ "You can use \"git bisect %s\" and \"git bisect %s\" for that.");
+
+static int bisect_next_check(const struct bisect_terms *terms,
+ const char *current_term)
+{
+ int missing_good = 1, missing_bad = 1, retval = 0;
+ const char *bad_ref = xstrfmt("refs/bisect/%s", terms->term_bad);
+ const char *good_glob = xstrfmt("%s-*", terms->term_good);
+
+ if (ref_exists(bad_ref))
+ missing_bad = 0;
+
+ for_each_glob_ref_in(mark_good, good_glob, "refs/bisect/",
+ (void *) &missing_good);
+
+ if (!missing_good && !missing_bad)
+ goto finish;
+
+ if (!current_term) {
+ retval = -1;
+ goto finish;
+ }
+
+ if (missing_good && !missing_bad &&
+ !strcmp(current_term, terms->term_good)) {
+ char *yesno;
+ /*
+ * have bad (or new) but not good (or old). We could bisect
+ * although this is less optimum.
+ */
+ warning(_("bisecting only with a %s commit"), terms->term_bad);
+ if (!isatty(0))
+ goto finish;
+ /*
+ * TRANSLATORS: Make sure to include [Y] and [n] in your
+ * translation. The program will only accept English input
+ * at this point.
+ */
+ yesno = git_prompt(_("Are you sure [Y/n]? "), PROMPT_ECHO);
+ if (starts_with(yesno, "N") || starts_with(yesno, "n"))
+ retval = -1;
+ goto finish;
+ }
+ if (!is_empty_or_missing_file(git_path_bisect_start())) {
+ retval = error(_(need_bad_and_good_revision_warning),
+ vocab_bad, vocab_good, vocab_bad, vocab_good);
+ } else {
+ retval = error(_(need_bisect_start_warning),
+ vocab_good, vocab_bad, vocab_good, vocab_bad);
+ }
+
+finish:
+ free((void *) good_glob);
+ free((void *) bad_ref);
+ return retval;
+}
+
+static int get_terms(struct bisect_terms *terms)
+{
+ struct strbuf str = STRBUF_INIT;
+ FILE *fp = NULL;
+ int res = 0;
+
+ fp = fopen(git_path_bisect_terms(), "r");
+ if (!fp) {
+ res = -1;
+ goto finish;
+ }
+
+ free_terms(terms);
+ strbuf_getline_lf(&str, fp);
+ terms->term_bad = strbuf_detach(&str, NULL);
+ strbuf_getline_lf(&str, fp);
+ terms->term_good = strbuf_detach(&str, NULL);
+
+finish:
+ if (fp)
+ fclose(fp);
+ strbuf_release(&str);
+ return res;
+}
+
+static int bisect_terms(struct bisect_terms *terms, const char *option)
+{
+ if (get_terms(terms))
+ return error(_("no terms defined"));
+
+ if (option == NULL) {
+ printf(_("Your current terms are %s for the old state\n"
+ "and %s for the new state.\n"),
+ terms->term_good, terms->term_bad);
+ return 0;
+ }
+ if (one_of(option, "--term-good", "--term-old", NULL))
+ printf("%s\n", terms->term_good);
+ else if (one_of(option, "--term-bad", "--term-new", NULL))
+ printf("%s\n", terms->term_bad);
+ else
+ return error(_("invalid argument %s for 'git bisect terms'.\n"
+ "Supported options are: "
+ "--term-good|--term-old and "
+ "--term-bad|--term-new."), option);
+
+ return 0;
+}
+
+static int bisect_append_log_quoted(const char **argv)
+{
+ int retval = 0;
+ FILE *fp = fopen(git_path_bisect_log(), "a");
+ struct strbuf orig_args = STRBUF_INIT;
+
+ if (!fp)
+ return -1;
+
+ if (fprintf(fp, "git bisect start") < 1) {
+ retval = -1;
+ goto finish;
+ }
+
+ sq_quote_argv(&orig_args, argv);
+ if (fprintf(fp, "%s\n", orig_args.buf) < 1)
+ retval = -1;
+
+finish:
+ fclose(fp);
+ strbuf_release(&orig_args);
+ return retval;
+}
+
+static int bisect_start(struct bisect_terms *terms, int no_checkout,
+ const char **argv, int argc)
+{
+ int i, has_double_dash = 0, must_write_terms = 0, bad_seen = 0;
+ int flags, pathspec_pos, retval = 0;
+ struct string_list revs = STRING_LIST_INIT_DUP;
+ struct string_list states = STRING_LIST_INIT_DUP;
+ struct strbuf start_head = STRBUF_INIT;
+ struct strbuf bisect_names = STRBUF_INIT;
+ struct object_id head_oid;
+ struct object_id oid;
+ const char *head;
+
+ if (is_bare_repository())
+ no_checkout = 1;
+
+ /*
+ * Check for one bad and then some good revisions
+ */
+ for (i = 0; i < argc; i++) {
+ if (!strcmp(argv[i], "--")) {
+ has_double_dash = 1;
+ break;
+ }
+ }
+
+ for (i = 0; i < argc; i++) {
+ const char *arg = argv[i];
+ if (!strcmp(argv[i], "--")) {
+ break;
+ } else if (!strcmp(arg, "--no-checkout")) {
+ no_checkout = 1;
+ } else if (!strcmp(arg, "--term-good") ||
+ !strcmp(arg, "--term-old")) {
+ must_write_terms = 1;
+ free((void *) terms->term_good);
+ terms->term_good = xstrdup(argv[++i]);
+ } else if (skip_prefix(arg, "--term-good=", &arg) ||
+ skip_prefix(arg, "--term-old=", &arg)) {
+ must_write_terms = 1;
+ free((void *) terms->term_good);
+ terms->term_good = xstrdup(arg);
+ } else if (!strcmp(arg, "--term-bad") ||
+ !strcmp(arg, "--term-new")) {
+ must_write_terms = 1;
+ free((void *) terms->term_bad);
+ terms->term_bad = xstrdup(argv[++i]);
+ } else if (skip_prefix(arg, "--term-bad=", &arg) ||
+ skip_prefix(arg, "--term-new=", &arg)) {
+ must_write_terms = 1;
+ free((void *) terms->term_bad);
+ terms->term_bad = xstrdup(arg);
+ } else if (starts_with(arg, "--") &&
+ !one_of(arg, "--term-good", "--term-bad", NULL)) {
+ return error(_("unrecognized option: '%s'"), arg);
+ } else {
+ char *commit_id = xstrfmt("%s^{commit}", arg);
+ if (get_oid(commit_id, &oid) && has_double_dash)
+ die(_("'%s' does not appear to be a valid "
+ "revision"), arg);
+
+ string_list_append(&revs, oid_to_hex(&oid));
+ free(commit_id);
+ }
+ }
+ pathspec_pos = i;
+
+ /*
+ * The user ran "git bisect start <sha1> <sha1>", hence did not
+ * explicitly specify the terms, but we are already starting to
+ * set references named with the default terms, and won't be able
+ * to change afterwards.
+ */
+ if (revs.nr)
+ must_write_terms = 1;
+ for (i = 0; i < revs.nr; i++) {
+ if (bad_seen) {
+ string_list_append(&states, terms->term_good);
+ } else {
+ bad_seen = 1;
+ string_list_append(&states, terms->term_bad);
+ }
+ }
+
+ /*
+ * Verify HEAD
+ */
+ head = resolve_ref_unsafe("HEAD", 0, &head_oid, &flags);
+ if (!head)
+ if (get_oid("HEAD", &head_oid))
+ return error(_("bad HEAD - I need a HEAD"));
+
+ /*
+ * Check if we are bisecting
+ */
+ if (!is_empty_or_missing_file(git_path_bisect_start())) {
+ /* Reset to the rev from where we started */
+ strbuf_read_file(&start_head, git_path_bisect_start(), 0);
+ strbuf_trim(&start_head);
+ if (!no_checkout) {
+ struct argv_array argv = ARGV_ARRAY_INIT;
+
+ argv_array_pushl(&argv, "checkout", start_head.buf,
+ "--", NULL);
+ if (run_command_v_opt(argv.argv, RUN_GIT_CMD)) {
+ retval = error(_("checking out '%s' failed."
+ " Try 'git bisect start "
+ "<valid-branch>'."),
+ start_head.buf);
+ goto finish;
+ }
+ }
+ } else {
+ /* Get the rev from where we start. */
+ if (!get_oid(head, &head_oid) &&
+ !starts_with(head, "refs/heads/")) {
+ strbuf_reset(&start_head);
+ strbuf_addstr(&start_head, oid_to_hex(&head_oid));
+ } else if (!get_oid(head, &head_oid) &&
+ skip_prefix(head, "refs/heads/", &head)) {
+ /*
+ * This error message should only be triggered by
+ * cogito usage, and cogito users should understand
+ * it relates to cg-seek.
+ */
+ if (!is_empty_or_missing_file(git_path_head_name()))
+ return error(_("won't bisect on cg-seek'ed tree"));
+ strbuf_addstr(&start_head, head);
+ } else {
+ return error(_("bad HEAD - strange symbolic ref"));
+ }
+ }
+
+ /*
+ * Get rid of any old bisect state.
+ */
+ if (bisect_clean_state())
+ return -1;
+
+ /*
+ * In case of mistaken revs or checkout error, or signals received,
+ * "bisect_auto_next" below may exit or misbehave.
+ * We have to trap this to be able to clean up using
+ * "bisect_clean_state".
+ */
+
+ /*
+ * Write new start state
+ */
+ write_file(git_path_bisect_start(), "%s\n", start_head.buf);
+
+ if (no_checkout) {
+ get_oid(start_head.buf, &oid);
+ if (update_ref(NULL, "BISECT_HEAD", &oid, NULL, 0,
+ UPDATE_REFS_MSG_ON_ERR)) {
+ retval = -1;
+ goto finish;
+ }
+ }
+
+ if (pathspec_pos < argc - 1)
+ sq_quote_argv(&bisect_names, argv + pathspec_pos);
+ write_file(git_path_bisect_names(), "%s\n", bisect_names.buf);
+
+ for (i = 0; i < states.nr; i++)
+ if (bisect_write(states.items[i].string,
+ revs.items[i].string, terms, 1)) {
+ retval = -1;
+ goto finish;
+ }
+
+ if (must_write_terms && write_terms(terms->term_bad,
+ terms->term_good)) {
+ retval = -1;
+ goto finish;
+ }
+
+ retval = bisect_append_log_quoted(argv);
+ if (retval)
+ retval = -1;
+
+finish:
+ string_list_clear(&revs, 0);
+ string_list_clear(&states, 0);
+ strbuf_release(&start_head);
+ strbuf_release(&bisect_names);
+ return retval;
+}
+
int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
{
enum {
NEXT_ALL = 1,
WRITE_TERMS,
BISECT_CLEAN_STATE,
- CHECK_EXPECTED_REVS
+ CHECK_EXPECTED_REVS,
+ BISECT_RESET,
+ BISECT_WRITE,
+ CHECK_AND_SET_TERMS,
+ BISECT_NEXT_CHECK,
+ BISECT_TERMS,
+ BISECT_START
} cmdmode = 0;
- int no_checkout = 0;
+ int no_checkout = 0, res = 0, nolog = 0;
struct option options[] = {
OPT_CMDMODE(0, "next-all", &cmdmode,
N_("perform 'git bisect next'"), NEXT_ALL),
N_("cleanup the bisection state"), BISECT_CLEAN_STATE),
OPT_CMDMODE(0, "check-expected-revs", &cmdmode,
N_("check for expected revs"), CHECK_EXPECTED_REVS),
+ OPT_CMDMODE(0, "bisect-reset", &cmdmode,
+ N_("reset the bisection state"), BISECT_RESET),
+ OPT_CMDMODE(0, "bisect-write", &cmdmode,
+ N_("write out the bisection state in BISECT_LOG"), BISECT_WRITE),
+ OPT_CMDMODE(0, "check-and-set-terms", &cmdmode,
+ N_("check and set terms in a bisection state"), CHECK_AND_SET_TERMS),
+ OPT_CMDMODE(0, "bisect-next-check", &cmdmode,
+ N_("check whether bad or good terms exist"), BISECT_NEXT_CHECK),
+ OPT_CMDMODE(0, "bisect-terms", &cmdmode,
+ N_("print out the bisect terms"), BISECT_TERMS),
+ OPT_CMDMODE(0, "bisect-start", &cmdmode,
+ N_("start the bisect session"), BISECT_START),
OPT_BOOL(0, "no-checkout", &no_checkout,
N_("update BISECT_HEAD instead of checking out the current commit")),
+ OPT_BOOL(0, "no-log", &nolog,
+ N_("no log for BISECT_WRITE ")),
OPT_END()
};
+ struct bisect_terms terms = { .term_good = NULL, .term_bad = NULL };
argc = parse_options(argc, argv, prefix, options,
- git_bisect_helper_usage, 0);
+ git_bisect_helper_usage,
+ PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_UNKNOWN);
if (!cmdmode)
usage_with_options(git_bisect_helper_usage, options);
switch (cmdmode) {
case NEXT_ALL:
- return bisect_next_all(prefix, no_checkout);
+ return bisect_next_all(the_repository, prefix, no_checkout);
case WRITE_TERMS:
if (argc != 2)
return error(_("--write-terms requires two arguments"));
case CHECK_EXPECTED_REVS:
check_expected_revs(argv, argc);
return 0;
+ case BISECT_RESET:
+ if (argc > 1)
+ return error(_("--bisect-reset requires either no argument or a commit"));
+ return !!bisect_reset(argc ? argv[0] : NULL);
+ case BISECT_WRITE:
+ if (argc != 4 && argc != 5)
+ return error(_("--bisect-write requires either 4 or 5 arguments"));
+ set_terms(&terms, argv[3], argv[2]);
+ res = bisect_write(argv[0], argv[1], &terms, nolog);
+ break;
+ case CHECK_AND_SET_TERMS:
+ if (argc != 3)
+ return error(_("--check-and-set-terms requires 3 arguments"));
+ set_terms(&terms, argv[2], argv[1]);
+ res = check_and_set_terms(&terms, argv[0]);
+ break;
+ case BISECT_NEXT_CHECK:
+ if (argc != 2 && argc != 3)
+ return error(_("--bisect-next-check requires 2 or 3 arguments"));
+ set_terms(&terms, argv[1], argv[0]);
+ res = bisect_next_check(&terms, argc == 3 ? argv[2] : NULL);
+ break;
+ case BISECT_TERMS:
+ if (argc > 1)
+ return error(_("--bisect-terms requires 0 or 1 argument"));
+ res = bisect_terms(&terms, argc == 1 ? argv[0] : NULL);
+ break;
+ case BISECT_START:
+ set_terms(&terms, "bad", "good");
+ res = bisect_start(&terms, no_checkout, argv, argc);
+ break;
default:
return error("BUG: unknown subcommand '%d'", cmdmode);
}
- return 0;
+ free_terms(&terms);
+ return !!res;
}
*/
blame_date_width = utf8_strwidth(_("4 years, 11 months ago")) + 1; /* add the null */
break;
+ case DATE_HUMAN:
+ /* If the year is shown, no time is shown */
+ blame_date_width = sizeof("Thu Oct 19 16:00");
+ break;
case DATE_NORMAL:
blame_date_width = sizeof("Thu Oct 19 16:00:04 2006 -0700");
break;
long bottom, top;
if (parse_range_arg(range_list.items[range_i].string,
nth_line_cb, &sb, lno, anchor,
- &bottom, &top, sb.path, &the_index))
+ &bottom, &top, sb.path,
+ the_repository->index))
usage(blame_usage);
if ((!lno && (top || bottom)) || lno < bottom)
die(Q_("file %s has only %lu line",
* create_branch takes care of setting up the tracking
* info and making sure new_upstream is correct
*/
- create_branch(branch->name, new_upstream, 0, 0, 0, quiet, BRANCH_TRACK_OVERRIDE);
+ create_branch(the_repository, branch->name, new_upstream,
+ 0, 0, 0, quiet, BRANCH_TRACK_OVERRIDE);
} else if (unset_upstream) {
struct branch *branch = branch_get(argv[0]);
struct strbuf buf = STRBUF_INIT;
if (track == BRANCH_TRACK_OVERRIDE)
die(_("the '--set-upstream' option is no longer supported. Please use '--track' or '--set-upstream-to' instead."));
- create_branch(argv[0], (argc == 2) ? argv[1] : head,
+ create_branch(the_repository,
+ argv[0], (argc == 2) ? argv[1] : head,
force, 0, reflog, quiet, track);
} else
usage(builtin_bundle_usage);
return 1;
}
- if (verify_bundle(&header, 1))
+ if (verify_bundle(the_repository, &header, 1))
return 1;
fprintf(stderr, _("%s is okay\n"), bundle_file);
return 0;
}
if (!startup_info->have_repository)
die(_("Need a repository to create a bundle."));
- return !!create_bundle(&header, bundle_file, argc, argv);
+ return !!create_bundle(the_repository, bundle_file, argc, argv);
} else if (!strcmp(cmd, "unbundle")) {
if (!startup_info->have_repository)
die(_("Need a repository to unbundle."));
- return !!unbundle(&header, bundle_fd, 0) ||
+ return !!unbundle(the_repository, &header, bundle_fd, 0) ||
list_bundle_refs(&header, argc, argv);
} else
usage(builtin_bundle_usage);
*
* Copyright (C) Linus Torvalds, 2005
*/
+#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "cache.h"
#include "config.h"
#include "builtin.h"
if (unknown_type)
flags |= OBJECT_INFO_ALLOW_UNKNOWN_TYPE;
- if (get_oid_with_context(obj_name, GET_OID_RECORD_PATH,
+ if (get_oid_with_context(the_repository, obj_name,
+ GET_OID_RECORD_PATH,
&oid, &obj_context))
die("Not a valid object name %s", obj_name);
{
struct object_context ctx;
int flags = opt->follow_symlinks ? GET_OID_FOLLOW_SYMLINKS : 0;
- enum follow_symlinks_result result;
+ enum get_oid_result result;
- result = get_oid_with_context(obj_name, flags, &data->oid, &ctx);
+ result = get_oid_with_context(the_repository, obj_name,
+ flags, &data->oid, &ctx);
if (result != FOUND) {
switch (result) {
case MISSING_OBJECT:
printf("%s missing\n", obj_name);
break;
+ case SHORT_NAME_AMBIGUOUS:
+ printf("%s ambiguous\n", obj_name);
+ break;
case DANGLING_SYMLINK:
printf("dangling %"PRIuMAX"\n%s\n",
(uintmax_t)strlen(obj_name), obj_name);
+#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "builtin.h"
#include "cache.h"
#include "config.h"
+#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "builtin.h"
#include "cache.h"
#include "config.h"
* Copyright (C) 2005 Linus Torvalds
*
*/
+#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "builtin.h"
#include "config.h"
#include "lockfile.h"
continue;
did_checkout = 1;
if (checkout_entry(ce, &state,
- to_tempfile ? topath[ce_stage(ce)] : NULL) < 0)
+ to_tempfile ? topath[ce_stage(ce)] : NULL,
+ NULL) < 0)
errs++;
}
write_tempfile_record(last_ce->name, prefix);
}
if (checkout_entry(ce, &state,
- to_tempfile ? topath[ce_stage(ce)] : NULL) < 0)
+ to_tempfile ? topath[ce_stage(ce)] : NULL,
+ NULL) < 0)
errs++;
last_ce = ce;
}
+#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "builtin.h"
#include "config.h"
#include "checkout.h"
int ignore_skipworktree;
int ignore_other_worktrees;
int show_progress;
+ int count_checkout_paths;
/*
* If new checkout options are added, skip_merge_working_tree
* should be updated accordingly.
static int read_tree_some(struct tree *tree, const struct pathspec *pathspec)
{
- read_tree_recursive(tree, "", 0, 0, pathspec, update_some, NULL);
+ read_tree_recursive(the_repository, tree, "", 0, 0,
+ pathspec, update_some, NULL);
/* update the index with the given tree's info
* for all args, expanding wildcards, and exit
}
static int checkout_stage(int stage, const struct cache_entry *ce, int pos,
- const struct checkout *state)
+ const struct checkout *state, int *nr_checkouts)
{
while (pos < active_nr &&
!strcmp(active_cache[pos]->name, ce->name)) {
if (ce_stage(active_cache[pos]) == stage)
- return checkout_entry(active_cache[pos], state, NULL);
+ return checkout_entry(active_cache[pos], state,
+ NULL, nr_checkouts);
pos++;
}
if (stage == 2)
return error(_("path '%s' does not have their version"), ce->name);
}
-static int checkout_merged(int pos, const struct checkout *state)
+static int checkout_merged(int pos, const struct checkout *state, int *nr_checkouts)
{
struct cache_entry *ce = active_cache[pos];
const char *path = ce->name;
ce = make_transient_cache_entry(mode, &oid, path, 2);
if (!ce)
die(_("make_cache_entry failed for path '%s'"), path);
- status = checkout_entry(ce, state, NULL);
+ status = checkout_entry(ce, state, NULL, nr_checkouts);
discard_cache_entry(ce);
return status;
}
struct commit *head;
int errs = 0;
struct lock_file lock_file = LOCK_INIT;
+ int nr_checkouts = 0;
if (opts->track != BRANCH_TRACK_UNSPECIFIED)
die(_("'%s' cannot be used with updating paths"), "--track");
return run_add_interactive(revision, "--patch=checkout",
&opts->pathspec);
- hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR);
+ repo_hold_locked_index(the_repository, &lock_file, LOCK_DIE_ON_ERROR);
if (read_cache_preload(&opts->pathspec) < 0)
return error(_("index file corrupt"));
struct cache_entry *ce = active_cache[pos];
if (ce->ce_flags & CE_MATCHED) {
if (!ce_stage(ce)) {
- errs |= checkout_entry(ce, &state, NULL);
+ errs |= checkout_entry(ce, &state,
+ NULL, &nr_checkouts);
continue;
}
if (opts->writeout_stage)
- errs |= checkout_stage(opts->writeout_stage, ce, pos, &state);
+ errs |= checkout_stage(opts->writeout_stage,
+ ce, pos,
+ &state, &nr_checkouts);
else if (opts->merge)
- errs |= checkout_merged(pos, &state);
+ errs |= checkout_merged(pos, &state,
+ &nr_checkouts);
pos = skip_same_name(ce, pos) - 1;
}
}
- errs |= finish_delayed_checkout(&state);
+ errs |= finish_delayed_checkout(&state, &nr_checkouts);
+
+ if (opts->count_checkout_paths) {
+ if (opts->source_tree)
+ fprintf_ln(stderr, Q_("Checked out %d path out of %s",
+ "Checked out %d paths out of %s",
+ nr_checkouts),
+ nr_checkouts,
+ find_unique_abbrev(&opts->source_tree->object.oid,
+ DEFAULT_ABBREV));
+ else
+ fprintf_ln(stderr, Q_("Checked out %d path out of the index",
+ "Checked out %d paths out of the index",
+ nr_checkouts),
+ nr_checkouts);
+ }
if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
die(_("unable to write new index file"));
* Remaining variables are not checkout options but used to track state
*/
+ /*
+ * Do the merge if this is the initial checkout. We cannot use
+ * is_cache_unborn() here because the index hasn't been loaded yet
+ * so cache_nr and timestamp.sec are always zero.
+ */
+ if (!file_exists(get_index_file()))
+ return 0;
+
return 1;
}
* a pain; plumb in an option to set
* o.renormalize?
*/
- init_merge_options(&o);
+ init_merge_options(&o, the_repository);
o.verbosity = 0;
work = write_tree_from_memory(&o);
free(refname);
}
else
- create_branch(opts->new_branch, new_branch_info->name,
+ create_branch(the_repository,
+ opts->new_branch, new_branch_info->name,
opts->new_branch_force ? 1 : 0,
opts->new_branch_force ? 1 : 0,
opts->new_branch_log,
delete_reflog(old_branch_info->path);
}
}
- remove_branch_state();
+ remove_branch_state(the_repository);
strbuf_release(&msg);
if (!opts->quiet &&
(new_branch_info->path || (!opts->force_detach && !strcmp(new_branch_info->name, "HEAD"))))
has_dash_dash = 1; /* case (3) or (1) */
else if (dash_dash_pos >= 2)
die(_("only one reference expected, %d given."), dash_dash_pos);
+ opts->count_checkout_paths = !opts->quiet && !has_dash_dash;
if (!strcmp(arg, "-"))
arg = "@{-1}";
*/
int recover_with_dwim = dwim_new_local_branch_ok;
- if (!has_dash_dash &&
- (check_filename(opts->prefix, arg) || !no_wildcard(arg)))
+ int could_be_checkout_paths = !has_dash_dash &&
+ check_filename(opts->prefix, arg);
+
+ if (!has_dash_dash && !no_wildcard(arg))
recover_with_dwim = 0;
+
/*
* Accept "git checkout foo" and "git checkout foo --"
* as candidates for dwim.
const char *remote = unique_tracking_name(arg, rev,
dwim_remotes_matched);
if (remote) {
+ if (could_be_checkout_paths)
+ die(_("'%s' could be both a local file and a tracking branch.\n"
+ "Please use -- (and optionally --no-guess) to disambiguate"),
+ arg);
*new_branch = arg;
arg = remote;
/* DWIMmed to create local branch, case (3).(b) */
struct checkout_opts opts;
struct branch_info new_branch_info;
char *conflict_style = NULL;
- int dwim_new_local_branch = 1;
+ int dwim_new_local_branch, no_dwim_new_local_branch = 0;
int dwim_remotes_matched = 0;
struct option options[] = {
OPT__QUIET(&opts.quiet, N_("suppress progress reporting")),
OPT_BOOL('p', "patch", &opts.patch_mode, N_("select hunks interactively")),
OPT_BOOL(0, "ignore-skip-worktree-bits", &opts.ignore_skipworktree,
N_("do not limit pathspecs to sparse entries only")),
- OPT_HIDDEN_BOOL(0, "guess", &dwim_new_local_branch,
- N_("second guess 'git checkout <no-such-branch>'")),
+ OPT_BOOL(0, "no-guess", &no_dwim_new_local_branch,
+ N_("do not second guess 'git checkout <no-such-branch>'")),
OPT_BOOL(0, "ignore-other-worktrees", &opts.ignore_other_worktrees,
N_("do not check if another worktree is holding the given ref")),
{ OPTION_CALLBACK, 0, "recurse-submodules", NULL,
argc = parse_options(argc, argv, prefix, options, checkout_usage,
PARSE_OPT_KEEP_DASHDASH);
+ dwim_new_local_branch = !no_dwim_new_local_branch;
if (opts.show_progress < 0) {
if (opts.quiet)
opts.show_progress = 0;
* Based on git-clean.sh by Pavel Roskin
*/
+#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "builtin.h"
#include "cache.h"
#include "config.h"
* Clone a repository into a different directory that does not yet exist.
*/
+#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "builtin.h"
#include "config.h"
#include "lockfile.h"
}
static struct ref *wanted_peer_refs(const struct ref *refs,
- struct refspec_item *refspec)
+ struct refspec *refspec)
{
struct ref *head = copy_ref(find_ref_by_name(refs, "HEAD"));
struct ref *local_refs = head;
warning(_("Could not find remote branch %s to clone."),
option_branch);
else {
- get_fetch_map(remote_head, refspec, &tail, 0);
+ int i;
+ for (i = 0; i < refspec->nr; i++)
+ get_fetch_map(remote_head, &refspec->items[i],
+ &tail, 0);
/* if --branch=tag, pull the requested tag explicitly */
get_fetch_map(remote_head, tag_refspec, &tail, 0);
}
- } else
- get_fetch_map(refs, refspec, &tail, 0);
+ } else {
+ int i;
+ for (i = 0; i < refspec->nr; i++)
+ get_fetch_map(refs, &refspec->items[i], &tail, 0);
+ }
if (!option_mirror && !option_single_branch && !option_no_tags)
get_fetch_map(refs, tag_refspec, &tail, 0);
const struct ref *our_head_points_at;
struct ref *mapped_refs;
const struct ref *ref;
- struct strbuf key = STRBUF_INIT, value = STRBUF_INIT;
+ struct strbuf key = STRBUF_INIT;
+ struct strbuf default_refspec = STRBUF_INIT;
struct strbuf branch_top = STRBUF_INIT, reflog_msg = STRBUF_INIT;
struct transport *transport = NULL;
const char *src_ref_prefix = "refs/heads/";
int err = 0, complete_refs_before_fetch = 1;
int submodule_progress;
- struct refspec rs = REFSPEC_INIT_FETCH;
struct argv_array ref_prefixes = ARGV_ARRAY_INIT;
fetch_if_missing = 0;
strbuf_addf(&branch_top, "refs/remotes/%s/", option_origin);
}
- strbuf_addf(&value, "+%s*:%s*", src_ref_prefix, branch_top.buf);
strbuf_addf(&key, "remote.%s.url", option_origin);
git_config_set(key.buf, repo);
strbuf_reset(&key);
if (option_required_reference.nr || option_optional_reference.nr)
setup_reference();
- refspec_append(&rs, value.buf);
+ remote = remote_get(option_origin);
- strbuf_reset(&value);
+ strbuf_addf(&default_refspec, "+%s*:%s*", src_ref_prefix,
+ branch_top.buf);
+ refspec_append(&remote->fetch, default_refspec.buf);
- remote = remote_get(option_origin);
transport = transport_get(remote, remote->url[0]);
transport_set_verbosity(transport, option_verbosity, option_progress);
transport->family = family;
option_upload_pack);
if (filter_options.choice) {
+ struct strbuf expanded_filter_spec = STRBUF_INIT;
+ expand_list_objects_filter_spec(&filter_options,
+ &expanded_filter_spec);
transport_set_option(transport, TRANS_OPT_LIST_OBJECTS_FILTER,
- filter_options.filter_spec);
+ expanded_filter_spec.buf);
transport_set_option(transport, TRANS_OPT_FROM_PROMISOR, "1");
+ strbuf_release(&expanded_filter_spec);
}
if (transport->smart_options && !deepen && !filter_options.choice)
argv_array_push(&ref_prefixes, "HEAD");
- refspec_ref_prefixes(&rs, &ref_prefixes);
+ refspec_ref_prefixes(&remote->fetch, &ref_prefixes);
if (option_branch)
expand_ref_prefix(&ref_prefixes, option_branch);
if (!option_no_tags)
refs = transport_get_remote_refs(transport, &ref_prefixes);
if (refs) {
- mapped_refs = wanted_peer_refs(refs, &rs.items[0]);
+ 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
strbuf_release(&reflog_msg);
strbuf_release(&branch_top);
strbuf_release(&key);
- strbuf_release(&value);
+ strbuf_release(&default_refspec);
junk_mode = JUNK_LEAVE_ALL;
- refspec_clear(&rs);
argv_array_clear(&ref_prefixes);
return err;
}
printf(" oid_lookup");
if (graph->chunk_commit_data)
printf(" commit_metadata");
- if (graph->chunk_large_edges)
- printf(" large_edges");
+ if (graph->chunk_extra_edges)
+ printf(" extra_edges");
printf("\n");
UNLEAK(graph);
* Based on git-commit.sh by Junio C Hamano and Linus Torvalds
*/
+#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "cache.h"
#include "config.h"
#include "lockfile.h"
static void status_init_config(struct wt_status *s, config_fn_t fn)
{
- wt_status_prepare(s);
+ wt_status_prepare(the_repository, s);
init_diff_ui_defaults();
git_config(fn, s);
determine_whence(s);
if (write_locked_index(&the_index, &index_lock, 0))
die(_("unable to create temporary index"));
- old_index_env = getenv(INDEX_ENVIRONMENT);
+ old_index_env = xstrdup_or_null(getenv(INDEX_ENVIRONMENT));
setenv(INDEX_ENVIRONMENT, get_lock_file_path(&index_lock), 1);
if (interactive_add(argc, argv, prefix, patch_interactive) != 0)
setenv(INDEX_ENVIRONMENT, old_index_env, 1);
else
unsetenv(INDEX_ENVIRONMENT);
+ FREE_AND_NULL(old_index_env);
discard_cache();
read_cache_from(get_lock_file_path(&index_lock));
if (ignore_submodule_arg &&
!strcmp(ignore_submodule_arg, "all"))
flags.ignore_submodules = 1;
- committable = index_differs_from(parent, &flags, 1);
+ committable = index_differs_from(the_repository,
+ parent, &flags, 1);
}
}
strbuf_release(&committer_ident);
if (status_format != STATUS_FORMAT_PORCELAIN &&
status_format != STATUS_FORMAT_PORCELAIN_V2)
progress_flag = REFRESH_PROGRESS;
- read_index(&the_index);
+ repo_read_index(the_repository);
refresh_index(&the_index,
REFRESH_QUIET|REFRESH_UNMERGED|progress_flag,
&s.pathspec, NULL, NULL);
wt_status_collect(&s);
if (0 <= fd)
- update_index_if_able(&the_index, &index_lock);
+ repo_update_index_if_able(the_repository, &index_lock);
if (s.relative_paths)
s.prefix = prefix;
run_command_v_opt(argv_gc_auto, RUN_GIT_CMD);
run_commit_hook(use_editor, get_index_file(), "post-commit", NULL);
if (amend && !no_post_rewrite) {
- commit_post_rewrite(current_head, &oid);
+ commit_post_rewrite(the_repository, current_head, &oid);
}
if (!quiet) {
unsigned int flags = 0;
flags |= SUMMARY_INITIAL_COMMIT;
if (author_date_is_interesting())
flags |= SUMMARY_SHOW_AUTHOR_DATE;
- print_commit_summary(prefix, &oid, flags);
+ print_commit_summary(the_repository, prefix,
+ &oid, flags);
}
UNLEAK(err);
usage_with_options(builtin_config_usage, builtin_config_options);
}
-static void check_argc(int argc, int min, int max) {
+static void check_argc(int argc, int min, int max)
+{
if (argc >= min && argc <= max)
return;
if (min == max)
int nongit = !startup_info->have_repository;
char *value;
- given_config_source.file = getenv(CONFIG_ENVIRONMENT);
+ given_config_source.file = xstrdup_or_null(getenv(CONFIG_ENVIRONMENT));
argc = parse_options(argc, argv, prefix, builtin_config_options,
builtin_config_usage,
return 0;
}
-static int print_alternate(struct alternate_object_database *alt, void *data)
+static int print_alternate(struct object_directory *odb, void *data)
{
printf("alternate: ");
- quote_c_style(alt->path, NULL, stdout, 0);
+ quote_c_style(odb->path, NULL, stdout, 0);
putchar('\n');
return 0;
}
+#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "cache.h"
#include "config.h"
#include "lockfile.h"
NULL, NULL, NULL);
fd = hold_locked_index(&index_lock, 0);
if (0 <= fd)
- update_index_if_able(&the_index, &index_lock);
+ repo_update_index_if_able(the_repository, &index_lock);
repo_init_revisions(the_repository, &revs, prefix);
argv_array_pushv(&args, diff_index_args);
*
* Copyright (C) Linus Torvalds, 2005
*/
+#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "cache.h"
#include "config.h"
#include "diff.h"
+#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "cache.h"
#include "config.h"
#include "diff.h"
+#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "cache.h"
#include "config.h"
#include "diff.h"
if (opt->diffopt.detect_rename) {
if (!the_index.cache)
- read_index(&the_index);
+ repo_read_index(the_repository);
opt->diffopt.setup |= DIFF_SETUP_USE_SIZE_CACHE;
}
while (fgets(line, sizeof(line), stdin)) {
*
* Copyright (c) 2006 Junio C Hamano
*/
+#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "cache.h"
#include "config.h"
#include "lockfile.h"
discard_cache();
read_cache();
refresh_cache(REFRESH_QUIET|REFRESH_UNMERGED);
- update_index_if_able(&the_index, &lock_file);
+ repo_update_index_if_able(the_repository, &lock_file);
}
static int builtin_diff_files(struct rev_info *revs, int argc, const char **argv)
*
* Copyright (C) 2016 Johannes Schindelin
*/
+#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "cache.h"
#include "config.h"
#include "builtin.h"
int ret;
ce = make_transient_cache_entry(mode, oid, path, 0);
- ret = checkout_entry(ce, state, NULL);
+ ret = checkout_entry(ce, state, NULL, NULL);
discard_cache_entry(ce);
return ret;
};
static int progress;
-static enum { ABORT, VERBATIM, WARN, WARN_STRIP, STRIP } signed_tag_mode = ABORT;
-static enum { ERROR, DROP, REWRITE } tag_of_filtered_mode = ERROR;
+static enum { SIGNED_TAG_ABORT, VERBATIM, WARN, WARN_STRIP, STRIP } signed_tag_mode = SIGNED_TAG_ABORT;
+static enum { TAG_FILTERING_ABORT, DROP, REWRITE } tag_of_filtered_mode = TAG_FILTERING_ABORT;
static int fake_missing_tagger;
static int use_done_feature;
static int no_data;
static int full_tree;
+static int reference_excluded_commits;
+static int show_original_ids;
static struct string_list extra_refs = STRING_LIST_INIT_NODUP;
+static struct string_list tag_refs = STRING_LIST_INIT_NODUP;
static struct refspec refspecs = REFSPEC_INIT_FETCH;
static int anonymize;
static struct revision_sources revision_sources;
const char *arg, int unset)
{
if (unset || !strcmp(arg, "abort"))
- signed_tag_mode = ABORT;
+ signed_tag_mode = SIGNED_TAG_ABORT;
else if (!strcmp(arg, "verbatim") || !strcmp(arg, "ignore"))
signed_tag_mode = VERBATIM;
else if (!strcmp(arg, "warn"))
const char *arg, int unset)
{
if (unset || !strcmp(arg, "abort"))
- tag_of_filtered_mode = ERROR;
+ tag_of_filtered_mode = TAG_FILTERING_ABORT;
else if (!strcmp(arg, "drop"))
tag_of_filtered_mode = DROP;
else if (!strcmp(arg, "rewrite"))
return ptr_to_mark(decoration);
}
+static struct commit *rewrite_commit(struct commit *p)
+{
+ for (;;) {
+ if (p->parents && p->parents->next)
+ break;
+ if (p->object.flags & UNINTERESTING)
+ break;
+ if (!(p->object.flags & TREESAME))
+ break;
+ if (!p->parents)
+ return NULL;
+ p = p->parents->item;
+ }
+ return p;
+}
+
static void show_progress(void)
{
static int counter = 0;
if (!buf)
die("could not read blob %s", oid_to_hex(oid));
if (check_object_signature(oid, buf, size, type_name(type)) < 0)
- die("sha1 mismatch in blob %s", oid_to_hex(oid));
+ die("oid mismatch in blob %s", oid_to_hex(oid));
object = parse_object_buffer(the_repository, oid, type,
size, buf, &eaten);
}
mark_next_object(object);
- printf("blob\nmark :%"PRIu32"\ndata %"PRIuMAX"\n", last_idnum, (uintmax_t)size);
+ printf("blob\nmark :%"PRIu32"\n", last_idnum);
+ if (show_original_ids)
+ printf("original-oid %s\n", oid_to_hex(oid));
+ printf("data %"PRIuMAX"\n", (uintmax_t)size);
if (size && fwrite(buf, size, 1, stdout) != 1)
die_errno("could not write blob '%s'", oid_to_hex(oid));
printf("\n");
static void *generate_fake_oid(const void *old, size_t *len)
{
- static uint32_t counter = 1; /* avoid null sha1 */
- unsigned char *out = xcalloc(GIT_SHA1_RAWSZ, 1);
- put_be32(out + GIT_SHA1_RAWSZ - 4, counter++);
+ static uint32_t counter = 1; /* avoid null oid */
+ const unsigned hashsz = the_hash_algo->rawsz;
+ unsigned char *out = xcalloc(hashsz, 1);
+ put_be32(out + hashsz - 4, counter++);
return out;
}
-static const unsigned char *anonymize_sha1(const struct object_id *oid)
+static const struct object_id *anonymize_oid(const struct object_id *oid)
{
- static struct hashmap sha1s;
- size_t len = GIT_SHA1_RAWSZ;
- return anonymize_mem(&sha1s, generate_fake_oid, oid, &len);
+ static struct hashmap objs;
+ size_t len = the_hash_algo->rawsz;
+ return anonymize_mem(&objs, generate_fake_oid, oid, &len);
}
static void show_filemodify(struct diff_queue_struct *q,
*/
if (no_data || S_ISGITLINK(spec->mode))
printf("M %06o %s ", spec->mode,
- sha1_to_hex(anonymize ?
- anonymize_sha1(&spec->oid) :
- spec->oid.hash));
+ oid_to_hex(anonymize ?
+ anonymize_oid(&spec->oid) :
+ &spec->oid));
else {
struct object *object = lookup_object(the_repository,
spec->oid.hash);
message += 2;
if (commit->parents &&
- get_object_mark(&commit->parents->item->object) != 0 &&
+ (get_object_mark(&commit->parents->item->object) != 0 ||
+ reference_excluded_commits) &&
!full_tree) {
parse_commit_or_die(commit->parents->item);
diff_tree_oid(get_commit_tree_oid(commit->parents->item),
export_blob(&diff_queued_diff.queue[i]->two->oid);
refname = *revision_sources_at(&revision_sources, commit);
+ /*
+ * FIXME: string_list_remove() below for each ref is overall
+ * O(N^2). Compared to a history walk and diffing trees, this is
+ * just lost in the noise in practice. However, theoretically a
+ * repo may have enough refs for this to become slow.
+ */
+ string_list_remove(&extra_refs, refname, 0);
if (anonymize) {
refname = anonymize_refname(refname);
anonymize_ident_line(&committer, &committer_end);
reencoded = reencode_string(message, "UTF-8", encoding);
if (!commit->parents)
printf("reset %s\n", refname);
- printf("commit %s\nmark :%"PRIu32"\n%.*s\n%.*s\ndata %u\n%s",
- refname, last_idnum,
+ printf("commit %s\nmark :%"PRIu32"\n", refname, last_idnum);
+ if (show_original_ids)
+ printf("original-oid %s\n", oid_to_hex(&commit->object.oid));
+ printf("%.*s\n%.*s\ndata %u\n%s",
(int)(author_end - author), author,
(int)(committer_end - committer), committer,
(unsigned)(reencoded
unuse_commit_buffer(commit, commit_buffer);
for (i = 0, p = commit->parents; p; p = p->next) {
- int mark = get_object_mark(&p->item->object);
- if (!mark)
+ struct object *obj = &p->item->object;
+ int mark = get_object_mark(obj);
+
+ if (!mark && !reference_excluded_commits)
continue;
if (i == 0)
- printf("from :%d\n", mark);
+ printf("from ");
+ else
+ printf("merge ");
+ if (mark)
+ printf(":%d\n", mark);
else
- printf("merge :%d\n", mark);
+ printf("%s\n", oid_to_hex(anonymize ?
+ anonymize_oid(&obj->oid) :
+ &obj->oid));
i++;
}
"\n-----BEGIN PGP SIGNATURE-----\n");
if (signature)
switch(signed_tag_mode) {
- case ABORT:
+ case SIGNED_TAG_ABORT:
die("encountered signed tag %s; use "
"--signed-tags=<mode> to handle it",
oid_to_hex(&tag->object.oid));
tagged_mark = get_object_mark(tagged);
if (!tagged_mark) {
switch(tag_of_filtered_mode) {
- case ABORT:
+ case TAG_FILTERING_ABORT:
die("tag %s tags unexported object; use "
"--tag-of-filtered-object=<mode> to handle it",
oid_to_hex(&tag->object.oid));
oid_to_hex(&tag->object.oid),
type_name(tagged->type));
}
- p = (struct commit *)tagged;
- for (;;) {
- if (p->parents && p->parents->next)
- break;
- if (p->object.flags & UNINTERESTING)
- break;
- if (!(p->object.flags & TREESAME))
- break;
- if (!p->parents)
- die("can't find replacement commit for tag %s",
- oid_to_hex(&tag->object.oid));
- p = p->parents->item;
+ p = rewrite_commit((struct commit *)tagged);
+ if (!p) {
+ printf("reset %s\nfrom %s\n\n",
+ name, oid_to_hex(&null_oid));
+ free(buf);
+ return;
}
tagged_mark = get_object_mark(&p->object);
}
if (starts_with(name, "refs/tags/"))
name += 10;
- printf("tag %s\nfrom :%d\n%.*s%sdata %d\n%.*s\n",
- name, tagged_mark,
+ printf("tag %s\nfrom :%d\n", name, tagged_mark);
+ if (show_original_ids)
+ printf("original-oid %s\n", oid_to_hex(&tag->object.oid));
+ printf("%.*s%sdata %d\n%.*s\n",
(int)(tagger_end - tagger), tagger,
tagger == tagger_end ? "" : "\n",
(int)message_size, (int)message_size, message ? message : "");
/* handle nested tags */
while (tag && tag->object.type == OBJ_TAG) {
parse_object(the_repository, &tag->object.oid);
- string_list_append(&extra_refs, full_name)->util = tag;
+ string_list_append(&tag_refs, full_name)->util = tag;
tag = (struct tag *)tag->tagged;
}
if (!tag)
}
/*
- * This ref will not be updated through a commit, lets make
- * sure it gets properly updated eventually.
+ * Make sure this ref gets properly updated eventually, whether
+ * through a commit or manually at the end.
*/
- if (*revision_sources_at(&revision_sources, commit) ||
- commit->object.flags & SHOWN)
+ if (e->item->type != OBJ_TAG)
string_list_append(&extra_refs, full_name)->util = commit;
+
if (!*revision_sources_at(&revision_sources, commit))
*revision_sources_at(&revision_sources, commit) = full_name;
}
+
+ string_list_sort(&extra_refs);
+ string_list_remove_duplicates(&extra_refs, 0);
}
-static void handle_tags_and_duplicates(void)
+static void handle_tags_and_duplicates(struct string_list *extras)
{
struct commit *commit;
int i;
- for (i = extra_refs.nr - 1; i >= 0; i--) {
- const char *name = extra_refs.items[i].string;
- struct object *object = extra_refs.items[i].util;
+ for (i = extras->nr - 1; i >= 0; i--) {
+ const char *name = extras->items[i].string;
+ struct object *object = extras->items[i].util;
+ int mark;
+
switch (object->type) {
case OBJ_TAG:
handle_tag(name, (struct tag *)object);
if (anonymize)
name = anonymize_refname(name);
/* create refs pointing to already seen commits */
- commit = (struct commit *)object;
- printf("reset %s\nfrom :%d\n\n", name,
- get_object_mark(&commit->object));
+ commit = rewrite_commit((struct commit *)object);
+ if (!commit) {
+ /*
+ * Neither this object nor any of its
+ * ancestors touch any relevant paths, so
+ * it has been filtered to nothing. Delete
+ * it.
+ */
+ printf("reset %s\nfrom %s\n\n",
+ name, oid_to_hex(&null_oid));
+ continue;
+ }
+
+ mark = get_object_mark(&commit->object);
+ if (!mark) {
+ /*
+ * Getting here means we have a commit which
+ * was excluded by a negative refspec (e.g.
+ * fast-export ^master master). If we are
+ * referencing excluded commits, set the ref
+ * to the exact commit. Otherwise, the user
+ * wants the branch exported but every commit
+ * in its history to be deleted, which basically
+ * just means deletion of the ref.
+ */
+ if (!reference_excluded_commits) {
+ /* delete the ref */
+ printf("reset %s\nfrom %s\n\n",
+ name, oid_to_hex(&null_oid));
+ continue;
+ }
+ /* set ref to commit using oid, not mark */
+ printf("reset %s\nfrom %s\n\n", name,
+ oid_to_hex(&commit->object.oid));
+ continue;
+ }
+
+ printf("reset %s\nfrom :%d\n\n", name, mark
+ );
show_progress();
break;
}
continue;
printf("reset %s\nfrom %s\n\n",
- refspec->dst, sha1_to_hex(null_sha1));
+ refspec->dst, oid_to_hex(&null_oid));
}
}
OPT_STRING_LIST(0, "refspec", &refspecs_list, N_("refspec"),
N_("Apply refspec to exported refs")),
OPT_BOOL(0, "anonymize", &anonymize, N_("anonymize output")),
+ OPT_BOOL(0, "reference-excluded-parents",
+ &reference_excluded_commits, N_("Reference parents which are not in fast-export stream by object id")),
+ OPT_BOOL(0, "show-original-ids", &show_original_ids,
+ N_("Show original object ids of blobs/commits")),
+
OPT_END()
};
}
}
- handle_tags_and_duplicates();
+ handle_tags_and_duplicates(&extra_refs);
+ handle_tags_and_duplicates(&tag_refs);
handle_deletes();
if (export_filename && lastimportid != last_idnum)
struct oid_array shallow = OID_ARRAY_INIT;
struct string_list deepen_not = STRING_LIST_INIT_DUP;
struct packet_reader reader;
+ enum protocol_version version;
fetch_if_missing = 0;
packet_reader_init(&reader, fd[0], NULL, 0,
PACKET_READ_CHOMP_NEWLINE |
- PACKET_READ_GENTLE_ON_EOF);
+ PACKET_READ_GENTLE_ON_EOF |
+ PACKET_READ_DIE_ON_ERR_PACKET);
- switch (discover_version(&reader)) {
+ version = discover_version(&reader);
+ switch (version) {
case protocol_v2:
- die("support for protocol v2 not implemented yet");
+ get_remote_refs(fd[1], &reader, &ref, 0, NULL, NULL);
+ break;
case protocol_v1:
case protocol_v0:
get_remote_heads(&reader, &ref, 0, NULL, &shallow);
}
ref = fetch_pack(&args, fd, conn, ref, dest, sought, nr_sought,
- &shallow, pack_lockfile_ptr, protocol_v0);
+ &shallow, pack_lockfile_ptr, version);
if (pack_lockfile) {
printf("lock %s\n", pack_lockfile);
fflush(stdout);
const char *needle,
const char *placeholder)
{
- const char *p = strstr(haystack->buf, needle);
+ const char *p = NULL;
int plen, nlen;
+ nlen = strlen(needle);
+ if (ends_with(haystack->buf, needle))
+ p = haystack->buf + haystack->len - nlen;
+ else
+ p = strstr(haystack->buf, needle);
if (!p)
return 0;
return 0;
plen = strlen(p);
- nlen = strlen(needle);
if (plen > nlen && p[nlen] != '/')
return 0;
what = _("[new ref]");
}
- if ((recurse_submodules != RECURSE_SUBMODULES_OFF) &&
- (recurse_submodules != RECURSE_SUBMODULES_ON))
- check_for_new_submodule_commits(&ref->new_oid);
r = s_update_ref(msg, ref, 0);
format_display(display, r ? '!' : '*', what,
r ? _("unable to update local ref") : NULL,
strbuf_add_unique_abbrev(&quickref, ¤t->object.oid, DEFAULT_ABBREV);
strbuf_addstr(&quickref, "..");
strbuf_add_unique_abbrev(&quickref, &ref->new_oid, DEFAULT_ABBREV);
- if ((recurse_submodules != RECURSE_SUBMODULES_OFF) &&
- (recurse_submodules != RECURSE_SUBMODULES_ON))
- check_for_new_submodule_commits(&ref->new_oid);
r = s_update_ref("fast-forward", ref, 1);
format_display(display, r ? '!' : ' ', quickref.buf,
r ? _("unable to update local ref") : NULL,
strbuf_add_unique_abbrev(&quickref, ¤t->object.oid, DEFAULT_ABBREV);
strbuf_addstr(&quickref, "...");
strbuf_add_unique_abbrev(&quickref, &ref->new_oid, DEFAULT_ABBREV);
- if ((recurse_submodules != RECURSE_SUBMODULES_OFF) &&
- (recurse_submodules != RECURSE_SUBMODULES_ON))
- check_for_new_submodule_commits(&ref->new_oid);
r = s_update_ref("forced-update", ref, 1);
format_display(display, r ? '!' : '+', quickref.buf,
r ? _("unable to update local ref") : _("forced update"),
ref->force = rm->peer_ref->force;
}
+ if (recurse_submodules != RECURSE_SUBMODULES_OFF)
+ check_for_new_submodule_commits(&rm->old_oid);
if (!strcmp(rm->name, "HEAD")) {
kind = "";
static struct transport *prepare_transport(struct remote *remote, int deepen)
{
struct transport *transport;
+
transport = transport_get(remote, NULL);
transport_set_verbosity(transport, verbosity, progress);
transport->family = family;
if (update_shallow)
set_option(transport, TRANS_OPT_UPDATE_SHALLOW, "yes");
if (filter_options.choice) {
+ struct strbuf expanded_filter_spec = STRBUF_INIT;
+ expand_list_objects_filter_spec(&filter_options,
+ &expanded_filter_spec);
set_option(transport, TRANS_OPT_LIST_OBJECTS_FILTER,
- filter_options.filter_spec);
+ expanded_filter_spec.buf);
set_option(transport, TRANS_OPT_FROM_PROMISOR, "1");
+ strbuf_release(&expanded_filter_spec);
}
if (negotiation_tip.nr) {
if (transport->smart_options)
*/
if (strcmp(remote->name, repository_format_partial_clone)) {
if (filter_options.choice)
- die(_("--filter can only be used with the remote configured in core.partialClone"));
+ die(_("--filter can only be used with the remote "
+ "configured in extensions.partialclone"));
return;
}
result = fetch_one(remote, argc, argv, prune_tags_ok);
} else {
if (filter_options.choice)
- die(_("--filter can only be used with the remote configured in core.partialClone"));
+ die(_("--filter can only be used with the remote "
+ "configured in extensions.partialclone"));
/* TODO should this also die if we have a previous partial-clone? */
result = fetch_multiple(&list);
}
+#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "builtin.h"
#include "cache.h"
#include "repository.h"
static const char *describe_object(struct object *obj)
{
- static struct strbuf buf = STRBUF_INIT;
- char *name = name_objects ?
- lookup_decoration(fsck_walk_options.object_names, obj) : NULL;
+ static struct strbuf bufs[] = {
+ STRBUF_INIT, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
+ };
+ static int b = 0;
+ struct strbuf *buf;
+ char *name = NULL;
- strbuf_reset(&buf);
- strbuf_addstr(&buf, oid_to_hex(&obj->oid));
+ if (name_objects)
+ name = lookup_decoration(fsck_walk_options.object_names, obj);
+
+ buf = bufs + b;
+ b = (b + 1) % ARRAY_SIZE(bufs);
+ strbuf_reset(buf);
+ strbuf_addstr(buf, oid_to_hex(&obj->oid));
if (name)
- strbuf_addf(&buf, " (%s)", name);
+ strbuf_addf(buf, " (%s)", name);
- return buf.buf;
+ return buf->buf;
}
static const char *printable_type(struct object *obj)
ret = type_name(obj->type);
if (!ret)
- ret = "unknown";
+ ret = _("unknown");
return ret;
}
return git_default_config(var, value, cb);
}
-static void objreport(struct object *obj, const char *msg_type,
- const char *err)
-{
- fprintf(stderr, "%s in %s %s: %s\n",
- msg_type, printable_type(obj), describe_object(obj), err);
-}
-
static int objerror(struct object *obj, const char *err)
{
errors_found |= ERROR_OBJECT;
- objreport(obj, "error", err);
+ /* TRANSLATORS: e.g. error in tree 01bfda: <more explanation> */
+ fprintf_ln(stderr, _("error in %s %s: %s"),
+ printable_type(obj), describe_object(obj), err);
return -1;
}
static int fsck_error_func(struct fsck_options *o,
struct object *obj, int type, const char *message)
{
- objreport(obj, (type == FSCK_WARN) ? "warning" : "error", message);
- return (type == FSCK_WARN) ? 0 : 1;
+ switch (type) {
+ case FSCK_WARN:
+ /* TRANSLATORS: e.g. warning in tree 01bfda: <more explanation> */
+ fprintf_ln(stderr, _("warning in %s %s: %s"),
+ printable_type(obj), describe_object(obj), message);
+ return 0;
+ case FSCK_ERROR:
+ /* TRANSLATORS: e.g. error in tree 01bfda: <more explanation> */
+ fprintf_ln(stderr, _("error in %s %s: %s"),
+ printable_type(obj), describe_object(obj), message);
+ return 1;
+ default:
+ BUG("%d (FSCK_IGNORE?) should never trigger this callback", type);
+ }
}
static struct object_array pending;
*/
if (!obj) {
/* ... these references to parent->fld are safe here */
- printf("broken link from %7s %s\n",
- printable_type(parent), describe_object(parent));
- printf("broken link from %7s %s\n",
- (type == OBJ_ANY ? "unknown" : type_name(type)), "unknown");
+ printf_ln(_("broken link from %7s %s"),
+ printable_type(parent), describe_object(parent));
+ printf_ln(_("broken link from %7s %s"),
+ (type == OBJ_ANY ? _("unknown") : type_name(type)),
+ _("unknown"));
errors_found |= ERROR_REACHABLE;
return 1;
}
if (type != OBJ_ANY && obj->type != type)
/* ... and the reference to parent is safe here */
- objerror(parent, "wrong object type in link");
+ objerror(parent, _("wrong object type in link"));
if (obj->flags & REACHABLE)
return 0;
if (!(obj->flags & HAS_OBJ)) {
if (parent && !has_object_file(&obj->oid)) {
- printf("broken link from %7s %s\n",
- printable_type(parent), describe_object(parent));
- printf(" to %7s %s\n",
- printable_type(obj), describe_object(obj));
+ printf_ln(_("broken link from %7s %s\n"
+ " to %7s %s"),
+ printable_type(parent),
+ describe_object(parent),
+ printable_type(obj),
+ describe_object(obj));
errors_found |= ERROR_REACHABLE;
}
return 1;
return;
if (has_object_pack(&obj->oid))
return; /* it is in pack - forget about it */
- printf("missing %s %s\n", printable_type(obj),
- describe_object(obj));
+ printf_ln(_("missing %s %s"), printable_type(obj),
+ describe_object(obj));
errors_found |= ERROR_REACHABLE;
return;
}
* since this is something that is prunable.
*/
if (show_unreachable) {
- printf("unreachable %s %s\n", printable_type(obj),
- describe_object(obj));
+ printf_ln(_("unreachable %s %s"), printable_type(obj),
+ describe_object(obj));
return;
}
*/
if (!(obj->flags & USED)) {
if (show_dangling)
- printf("dangling %s %s\n", printable_type(obj),
- describe_object(obj));
+ printf_ln(_("dangling %s %s"), printable_type(obj),
+ describe_object(obj));
if (write_lost_and_found) {
char *filename = git_pathdup("lost-found/%s/%s",
obj->type == OBJ_COMMIT ? "commit" : "other",
FILE *f;
if (safe_create_leading_directories_const(filename)) {
- error("Could not create lost-found");
+ error(_("could not create lost-found"));
free(filename);
return;
}
f = xfopen(filename, "w");
if (obj->type == OBJ_BLOB) {
if (stream_blob_to_fd(fileno(f), &obj->oid, NULL, 1))
- die_errno("Could not write '%s'", filename);
+ die_errno(_("could not write '%s'"), filename);
} else
fprintf(f, "%s\n", describe_object(obj));
if (fclose(f))
- die_errno("Could not finish '%s'",
+ die_errno(_("could not finish '%s'"),
filename);
free(filename);
}
static void check_object(struct object *obj)
{
if (verbose)
- fprintf(stderr, "Checking %s\n", describe_object(obj));
+ fprintf_ln(stderr, _("Checking %s"), describe_object(obj));
if (obj->flags & REACHABLE)
check_reachable_object(obj);
/* Look up all the requirements, warn about missing objects.. */
max = get_max_object_index();
if (verbose)
- fprintf(stderr, "Checking connectivity (%d objects)\n", max);
+ fprintf_ln(stderr, _("Checking connectivity (%d objects)"), max);
for (i = 0; i < max; i++) {
struct object *obj = get_indexed_object(i);
obj->flags |= SEEN;
if (verbose)
- fprintf(stderr, "Checking %s %s\n",
- printable_type(obj), describe_object(obj));
+ fprintf_ln(stderr, _("Checking %s %s"),
+ printable_type(obj), describe_object(obj));
if (fsck_walk(obj, NULL, &fsck_obj_options))
- objerror(obj, "broken links");
+ objerror(obj, _("broken links"));
err = fsck_object(obj, buffer, size, &fsck_obj_options);
if (err)
goto out;
struct commit *commit = (struct commit *) obj;
if (!commit->parents && show_root)
- printf("root %s\n", describe_object(&commit->object));
+ printf_ln(_("root %s"),
+ describe_object(&commit->object));
}
if (obj->type == OBJ_TAG) {
struct tag *tag = (struct tag *) obj;
if (show_tags && tag->tagged) {
- printf("tagged %s %s", printable_type(tag->tagged),
- describe_object(tag->tagged));
- printf(" (%s) in %s\n", tag->tag,
- describe_object(&tag->object));
+ printf_ln(_("tagged %s %s (%s) in %s"),
+ printable_type(tag->tagged),
+ describe_object(tag->tagged),
+ tag->tag,
+ describe_object(&tag->object));
}
}
if (obj->type == OBJ_TREE)
free_tree_buffer((struct tree *)obj);
if (obj->type == OBJ_COMMIT)
- free_commit_buffer((struct commit *)obj);
+ free_commit_buffer(the_repository->parsed_objects,
+ (struct commit *)obj);
return err;
}
eaten);
if (!obj) {
errors_found |= ERROR_OBJECT;
- return error("%s: object corrupt or missing", oid_to_hex(oid));
+ return error(_("%s: object corrupt or missing"),
+ oid_to_hex(oid));
}
obj->flags &= ~(REACHABLE | SEEN);
obj->flags |= HAS_OBJ;
obj->flags |= USED;
mark_object_reachable(obj);
} else if (!is_promisor_object(oid)) {
- error("%s: invalid reflog entry %s", refname, oid_to_hex(oid));
+ error(_("%s: invalid reflog entry %s"),
+ refname, oid_to_hex(oid));
errors_found |= ERROR_REACHABLE;
}
}
const char *refname = cb_data;
if (verbose)
- fprintf(stderr, "Checking reflog %s->%s\n",
- oid_to_hex(ooid), oid_to_hex(noid));
+ fprintf_ln(stderr, _("Checking reflog %s->%s"),
+ oid_to_hex(ooid), oid_to_hex(noid));
fsck_handle_reflog_oid(refname, ooid, 0);
fsck_handle_reflog_oid(refname, noid, timestamp);
default_refs++;
return 0;
}
- error("%s: invalid sha1 pointer %s", refname, oid_to_hex(oid));
+ error(_("%s: invalid sha1 pointer %s"),
+ refname, oid_to_hex(oid));
errors_found |= ERROR_REACHABLE;
/* We'll continue with the rest despite the error.. */
return 0;
}
if (obj->type != OBJ_COMMIT && is_branch(refname)) {
- error("%s: not a commit", refname);
+ error(_("%s: not a commit"), refname);
errors_found |= ERROR_REFS;
}
default_refs++;
* "show_unreachable" flag.
*/
if (!default_refs) {
- fprintf(stderr, "notice: No default references\n");
+ fprintf_ln(stderr, _("notice: No default references"));
show_unreachable = 0;
}
}
if (read_loose_object(path, oid, &type, &size, &contents) < 0) {
errors_found |= ERROR_OBJECT;
- error("%s: object corrupt or missing: %s",
+ error(_("%s: object corrupt or missing: %s"),
oid_to_hex(oid), path);
return 0; /* keep checking other objects */
}
if (!obj) {
errors_found |= ERROR_OBJECT;
- error("%s: object could not be parsed: %s",
+ error(_("%s: object could not be parsed: %s"),
oid_to_hex(oid), path);
if (!eaten)
free(contents);
static int fsck_cruft(const char *basename, const char *path, void *data)
{
if (!starts_with(basename, "tmp_obj_"))
- fprintf(stderr, "bad sha1 file: %s\n", path);
+ fprintf_ln(stderr, _("bad sha1 file: %s"), path);
return 0;
}
struct progress *progress = NULL;
if (verbose)
- fprintf(stderr, "Checking object directory\n");
+ fprintf_ln(stderr, _("Checking object directory"));
if (show_progress)
progress = start_progress(_("Checking object directories"), 256);
int null_is_error = 0;
if (verbose)
- fprintf(stderr, "Checking %s link\n", head_ref_name);
+ fprintf_ln(stderr, _("Checking %s link"), head_ref_name);
*head_points_at = resolve_ref_unsafe(head_ref_name, 0, head_oid, NULL);
if (!*head_points_at) {
errors_found |= ERROR_REFS;
- return error("Invalid %s", head_ref_name);
+ return error(_("invalid %s"), head_ref_name);
}
if (!strcmp(*head_points_at, head_ref_name))
/* detached HEAD */
null_is_error = 1;
else if (!starts_with(*head_points_at, "refs/heads/")) {
errors_found |= ERROR_REFS;
- return error("%s points to something strange (%s)",
+ return error(_("%s points to something strange (%s)"),
head_ref_name, *head_points_at);
}
if (is_null_oid(head_oid)) {
if (null_is_error) {
errors_found |= ERROR_REFS;
- return error("%s: detached HEAD points at nothing",
+ return error(_("%s: detached HEAD points at nothing"),
head_ref_name);
}
- fprintf(stderr, "notice: %s points to an unborn branch (%s)\n",
- head_ref_name, *head_points_at + 11);
+ fprintf_ln(stderr,
+ _("notice: %s points to an unborn branch (%s)"),
+ head_ref_name, *head_points_at + 11);
}
return 0;
}
int err = 0;
if (verbose)
- fprintf(stderr, "Checking cache tree\n");
+ fprintf_ln(stderr, _("Checking cache tree"));
if (0 <= it->entry_count) {
struct object *obj = parse_object(the_repository, &it->oid);
if (!obj) {
- error("%s: invalid sha1 pointer in cache-tree",
+ error(_("%s: invalid sha1 pointer in cache-tree"),
oid_to_hex(&it->oid));
errors_found |= ERROR_REFS;
return 1;
obj, xstrdup(":"));
mark_object_reachable(obj);
if (obj->type != OBJ_TREE)
- err |= objerror(obj, "non-tree in cache-tree");
+ err |= objerror(obj, _("non-tree in cache-tree"));
}
for (i = 0; i < it->subtree_nr; i++)
err |= fsck_cache_tree(it->down[i]->cache_tree);
int cmd_fsck(int argc, const char **argv, const char *prefix)
{
int i;
- struct alternate_object_database *alt;
+ struct object_directory *odb;
/* fsck knows how to handle missing promisor objects */
fetch_if_missing = 0;
for_each_loose_object(mark_loose_for_connectivity, NULL, 0);
for_each_packed_object(mark_packed_for_connectivity, NULL, 0);
} else {
- struct alternate_object_database *alt_odb_list;
-
- fsck_object_dir(get_object_directory());
-
prepare_alt_odb(the_repository);
- alt_odb_list = the_repository->objects->alt_odb_list;
- for (alt = alt_odb_list; alt; alt = alt->next)
- fsck_object_dir(alt->path);
+ for (odb = the_repository->objects->odb; odb; odb = odb->next)
+ fsck_object_dir(odb->path);
if (check_full) {
struct packed_git *p;
for (p = get_all_packs(the_repository); p;
p = p->next) {
/* verify gives error messages itself */
- if (verify_pack(p, fsck_obj_buffer,
+ if (verify_pack(the_repository,
+ p, fsck_obj_buffer,
progress, count))
errors_found |= ERROR_PACK;
count += p->num_objects;
if (!obj || !(obj->flags & HAS_OBJ)) {
if (is_promisor_object(&oid))
continue;
- error("%s: object missing", oid_to_hex(&oid));
+ error(_("%s: object missing"), oid_to_hex(&oid));
errors_found |= ERROR_OBJECT;
continue;
}
mark_object_reachable(obj);
continue;
}
- error("invalid parameter: expected sha1, got '%s'", arg);
+ error(_("invalid parameter: expected sha1, got '%s'"), arg);
errors_found |= ERROR_OBJECT;
}
struct child_process commit_graph_verify = CHILD_PROCESS_INIT;
const char *verify_argv[] = { "commit-graph", "verify", NULL, NULL, NULL };
- commit_graph_verify.argv = verify_argv;
- commit_graph_verify.git_cmd = 1;
- if (run_command(&commit_graph_verify))
- errors_found |= ERROR_COMMIT_GRAPH;
-
prepare_alt_odb(the_repository);
- for (alt = the_repository->objects->alt_odb_list; alt; alt = alt->next) {
+ for (odb = the_repository->objects->odb; odb; odb = odb->next) {
+ child_process_init(&commit_graph_verify);
+ commit_graph_verify.argv = verify_argv;
+ commit_graph_verify.git_cmd = 1;
verify_argv[2] = "--object-dir";
- verify_argv[3] = alt->path;
+ verify_argv[3] = odb->path;
if (run_command(&commit_graph_verify))
errors_found |= ERROR_COMMIT_GRAPH;
}
struct child_process midx_verify = CHILD_PROCESS_INIT;
const char *midx_argv[] = { "multi-pack-index", "verify", NULL, NULL, NULL };
- midx_verify.argv = midx_argv;
- midx_verify.git_cmd = 1;
- if (run_command(&midx_verify))
- errors_found |= ERROR_COMMIT_GRAPH;
-
prepare_alt_odb(the_repository);
- for (alt = the_repository->objects->alt_odb_list; alt; alt = alt->next) {
+ for (odb = the_repository->objects->odb; odb; odb = odb->next) {
+ child_process_init(&midx_verify);
+ midx_verify.argv = midx_argv;
+ midx_verify.git_cmd = 1;
midx_argv[2] = "--object-dir";
- midx_argv[3] = alt->path;
+ midx_argv[3] = odb->path;
if (run_command(&midx_verify))
errors_found |= ERROR_COMMIT_GRAPH;
}
static void add_repack_incremental_option(void)
{
- argv_array_push(&repack, "--no-write-bitmap-index");
+ argv_array_push(&repack, "--no-write-bitmap-index");
}
static int need_to_gc(void)
report_garbage = report_pack_garbage;
reprepare_packed_git(the_repository);
- if (pack_garbage.nr > 0)
+ if (pack_garbage.nr > 0) {
+ close_all_packs(the_repository->objects);
clean_pack_garbage();
+ }
if (gc_write_commit_graph)
write_commit_graph_reachable(get_object_directory(), 0,
*
* Copyright (c) 2006 Junio C Hamano
*/
+#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "cache.h"
#include "repository.h"
#include "config.h"
exit(status);
}
-static int grep_cache(struct grep_opt *opt, struct repository *repo,
+static int grep_cache(struct grep_opt *opt,
const struct pathspec *pathspec, int cached);
static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec,
struct tree_desc *tree, struct strbuf *base, int tn_len,
- int check_attr, struct repository *repo);
+ int check_attr);
-static int grep_submodule(struct grep_opt *opt, struct repository *superproject,
+static int grep_submodule(struct grep_opt *opt,
const struct pathspec *pathspec,
const struct object_id *oid,
const char *filename, const char *path)
{
- struct repository submodule;
+ struct repository subrepo;
+ struct repository *superproject = opt->repo;
+ const struct submodule *sub = submodule_from_path(superproject,
+ &null_oid, path);
+ struct grep_opt subopt;
int hit;
/*
return 0;
}
- if (repo_submodule_init(&submodule, superproject, path)) {
+ if (repo_submodule_init(&subrepo, superproject, sub)) {
grep_read_unlock();
return 0;
}
- repo_read_gitmodules(&submodule);
+ repo_read_gitmodules(&subrepo);
/*
* NEEDSWORK: This adds the submodule's object directory to the list of
* store is no longer global and instead is a member of the repository
* object.
*/
- add_to_alternates_memory(submodule.objects->objectdir);
+ add_to_alternates_memory(subrepo.objects->odb->path);
grep_read_unlock();
+ memcpy(&subopt, opt, sizeof(subopt));
+ subopt.repo = &subrepo;
+
if (oid) {
struct object *object;
struct tree_desc tree;
strbuf_addch(&base, '/');
init_tree_desc(&tree, data, size);
- hit = grep_tree(opt, pathspec, &tree, &base, base.len,
- object->type == OBJ_COMMIT, &submodule);
+ hit = grep_tree(&subopt, pathspec, &tree, &base, base.len,
+ object->type == OBJ_COMMIT);
strbuf_release(&base);
free(data);
} else {
- hit = grep_cache(opt, &submodule, pathspec, 1);
+ hit = grep_cache(&subopt, pathspec, 1);
}
- repo_clear(&submodule);
+ repo_clear(&subrepo);
return hit;
}
-static int grep_cache(struct grep_opt *opt, struct repository *repo,
+static int grep_cache(struct grep_opt *opt,
const struct pathspec *pathspec, int cached)
{
+ struct repository *repo = opt->repo;
int hit = 0;
int nr;
struct strbuf name = STRBUF_INIT;
}
} else if (recurse_submodules && S_ISGITLINK(ce->ce_mode) &&
submodule_path_match(repo->index, pathspec, name.buf, NULL)) {
- hit |= grep_submodule(opt, repo, pathspec, NULL, ce->name, ce->name);
+ hit |= grep_submodule(opt, pathspec, NULL, ce->name, ce->name);
} else {
continue;
}
static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec,
struct tree_desc *tree, struct strbuf *base, int tn_len,
- int check_attr, struct repository *repo)
+ int check_attr)
{
+ struct repository *repo = opt->repo;
int hit = 0;
enum interesting match = entry_not_interesting;
struct name_entry entry;
if (match != all_entries_interesting) {
strbuf_addstr(&name, base->buf + tn_len);
- match = tree_entry_interesting(&entry, &name,
+ match = tree_entry_interesting(repo->index,
+ &entry, &name,
0, pathspec);
strbuf_setlen(&name, name_base_len);
strbuf_add(base, entry.path, te_len);
if (S_ISREG(entry.mode)) {
- hit |= grep_oid(opt, entry.oid, base->buf, tn_len,
+ hit |= grep_oid(opt, &entry.oid, base->buf, tn_len,
check_attr ? base->buf + tn_len : NULL);
} else if (S_ISDIR(entry.mode)) {
enum object_type type;
void *data;
unsigned long size;
- data = lock_and_read_oid_file(entry.oid, &type, &size);
+ data = lock_and_read_oid_file(&entry.oid, &type, &size);
if (!data)
die(_("unable to read tree (%s)"),
- oid_to_hex(entry.oid));
+ oid_to_hex(&entry.oid));
strbuf_addch(base, '/');
init_tree_desc(&sub, data, size);
hit |= grep_tree(opt, pathspec, &sub, base, tn_len,
- check_attr, repo);
+ check_attr);
free(data);
} else if (recurse_submodules && S_ISGITLINK(entry.mode)) {
- hit |= grep_submodule(opt, repo, pathspec, entry.oid,
+ hit |= grep_submodule(opt, pathspec, &entry.oid,
base->buf, base->buf + tn_len);
}
}
init_tree_desc(&tree, data, size);
hit = grep_tree(opt, pathspec, &tree, &base, base.len,
- obj->type == OBJ_COMMIT, the_repository);
+ obj->type == OBJ_COMMIT);
strbuf_release(&base);
free(data);
return hit;
for (i = 0; i < nr; i++) {
struct object *real_obj;
- real_obj = deref_tag(the_repository, list->objects[i].item,
+ real_obj = deref_tag(opt->repo, list->objects[i].item,
NULL, 0);
/* load the gitmodules file for this rev */
if (recurse_submodules) {
- submodule_free(the_repository);
+ submodule_free(opt->repo);
gitmodules_config_oid(&real_obj->oid);
}
if (grep_object(opt, pathspec, real_obj, list->objects[i].name,
if (exc_std)
setup_standard_excludes(&dir);
- fill_directory(&dir, &the_index, pathspec);
+ fill_directory(&dir, opt->repo->index, pathspec);
for (i = 0; i < dir.nr; i++) {
- if (!dir_path_match(&the_index, dir.entries[i], pathspec, 0, NULL))
+ if (!dir_path_match(opt->repo->index, dir.entries[i], pathspec, 0, NULL))
continue;
hit |= grep_file(opt, dir.entries[i]->name);
if (hit && opt->status_only)
break;
}
- if (get_oid_with_context(arg, GET_OID_RECORD_PATH,
+ if (get_oid_with_context(the_repository, arg,
+ GET_OID_RECORD_PATH,
&oid, &oc)) {
if (seen_dashdash)
die(_("unable to resolve revision: %s"), arg);
if (!cached)
setup_work_tree();
- hit = grep_cache(&opt, the_repository, &pathspec, cached);
+ hit = grep_cache(&opt, &pathspec, cached);
} else {
if (cached)
die(_("both --cached and trees are given"));
if (fstat(fd, &st) < 0 ||
(literally
? hash_literally(&oid, fd, type, flags)
- : index_fd(&the_index, &oid, fd, &st, type_from_string(type), path, flags)))
+ : index_fd(the_repository->index, &oid, fd, &st,
+ type_from_string(type), path, flags)))
die((flags & HASH_WRITE_OBJECT)
? "Unable to add %s to database"
: "Unable to hash %s", path);
* GIT_WORK_TREE makes sense only in conjunction with GIT_DIR
* without --bare. Catch the error early.
*/
- git_dir = getenv(GIT_DIR_ENVIRONMENT);
- work_tree = getenv(GIT_WORK_TREE_ENVIRONMENT);
+ git_dir = xstrdup_or_null(getenv(GIT_DIR_ENVIRONMENT));
+ work_tree = xstrdup_or_null(getenv(GIT_WORK_TREE_ENVIRONMENT));
if ((!git_dir || is_bare_repository_cfg == 1) && work_tree)
die(_("%s (or --work-tree=<directory>) not allowed without "
"specifying %s (or --git-dir=<directory>)"),
}
UNLEAK(real_git_dir);
+ UNLEAK(git_dir);
+ UNLEAK(work_tree);
flags |= INIT_DB_EXIST_OK;
return init_db(git_dir, real_git_dir, template_dir, flags);
* (C) Copyright 2006 Linus Torvalds
* 2006 Junio Hamano
*/
+#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "cache.h"
#include "config.h"
#include "refs.h"
rev->diffopt.filter || rev->diffopt.flags.follow_renames)
rev->always_show_header = 0;
- if (source) {
+ if (source || w.source) {
init_revision_sources(&revision_sources);
rev->sources = &revision_sources;
}
* We may show a given commit multiple times when
* walking the reflogs.
*/
- free_commit_buffer(commit);
+ free_commit_buffer(the_repository->parsed_objects,
+ commit);
free_commit_list(commit->parents);
commit->parents = NULL;
}
!rev->diffopt.flags.allow_textconv)
return stream_blob_to_fd(1, oid, NULL, 0);
- if (get_oid_with_context(obj_name, GET_OID_RECORD_PATH,
+ if (get_oid_with_context(the_repository, obj_name,
+ GET_OID_RECORD_PATH,
&oidc, &obj_context))
die(_("Not a valid object name %s"), obj_name);
if (!obj_context.path ||
diff_get_color_opt(&rev.diffopt, DIFF_COMMIT),
name,
diff_get_color_opt(&rev.diffopt, DIFF_RESET));
- read_tree_recursive((struct tree *)o, "", 0, 0, &match_all,
- show_tree_object, rev.diffopt.file);
+ read_tree_recursive(the_repository, (struct tree *)o, "",
+ 0, 0, &match_all, show_tree_object,
+ rev.diffopt.file);
rev.shown_one = 1;
break;
case OBJ_COMMIT:
open_next_file(rev.numbered_files ? NULL : commit, NULL, &rev, quiet))
die(_("Failed to create output files"));
shown = log_tree_commit(&rev, commit);
- free_commit_buffer(commit);
+ free_commit_buffer(the_repository->parsed_objects,
+ commit);
/* We put one extra blank line between formatted
* patches and this flag is used by log-tree code
*
* Copyright (C) Linus Torvalds, 2005
*/
-#define NO_THE_INDEX_COMPATIBILITY_MACROS
#include "cache.h"
#include "repository.h"
#include "config.h"
static void show_submodule(struct repository *superproject,
struct dir_struct *dir, const char *path)
{
- struct repository submodule;
+ struct repository subrepo;
+ const struct submodule *sub = submodule_from_path(superproject,
+ &null_oid, path);
- if (repo_submodule_init(&submodule, superproject, path))
+ if (repo_submodule_init(&subrepo, superproject, sub))
return;
- if (repo_read_index(&submodule) < 0)
+ if (repo_read_index(&subrepo) < 0)
die("index file corrupt");
- show_files(&submodule, dir);
+ show_files(&subrepo, dir);
- repo_clear(&submodule);
+ repo_clear(&subrepo);
}
static void show_ce(struct repository *repo, struct dir_struct *dir,
PATHSPEC_PREFER_CWD, prefix, matchbuf);
} else
memset(&pathspec, 0, sizeof(pathspec));
- if (read_tree(tree, 1, &pathspec, istate))
+ if (read_tree(the_repository, tree, 1, &pathspec, istate))
die("unable to read tree entries %s", tree_name);
for (i = 0; i < istate->cache_nr; i++) {
tree = parse_tree_indirect(&oid);
if (!tree)
die("not a tree object");
- return !!read_tree_recursive(tree, "", 0, 0, &pathspec, show_tree, NULL);
+ return !!read_tree_recursive(the_repository, tree, "", 0, 0,
+ &pathspec, show_tree, NULL);
}
+#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "builtin.h"
#include "run-command.h"
*
* Pretend we resolved the heads, but declare our tree trumps everybody else.
*/
+#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "git-compat-util.h"
#include "builtin.h"
#include "diff.h"
*/
if (read_cache() < 0)
die_errno("read_cache failed");
- if (index_differs_from("HEAD", NULL, 0))
+ if (index_differs_from(the_repository, "HEAD", NULL, 0))
exit(2);
exit(0);
}
static const char builtin_merge_recursive_usage[] =
"git %s <base>... -- <head> <remote> ...";
-static const char *better_branch_name(const char *branch)
+static char *better_branch_name(const char *branch)
{
static char githead_env[8 + GIT_MAX_HEXSZ + 1];
char *name;
if (strlen(branch) != the_hash_algo->hexsz)
- return branch;
+ return xstrdup(branch);
xsnprintf(githead_env, sizeof(githead_env), "GITHEAD_%s", branch);
name = getenv(githead_env);
- return name ? name : branch;
+ return xstrdup(name ? name : branch);
}
int cmd_merge_recursive(int argc, const char **argv, const char *prefix)
int i, failed;
struct object_id h1, h2;
struct merge_options o;
+ char *better1, *better2;
struct commit *result;
- init_merge_options(&o);
+ init_merge_options(&o, the_repository);
if (argv[0] && ends_with(argv[0], "-subtree"))
o.subtree_shift = "";
if (get_oid(o.branch2, &h2))
die(_("could not resolve ref '%s'"), o.branch2);
- o.branch1 = better_branch_name(o.branch1);
- o.branch2 = better_branch_name(o.branch2);
+ o.branch1 = better1 = better_branch_name(o.branch1);
+ o.branch2 = better2 = better_branch_name(o.branch2);
if (o.verbosity >= 3)
printf(_("Merging %s with %s\n"), o.branch1, o.branch2);
failed = merge_recursive_generic(&o, &h1, &h2, bases_count, bases, &result);
+
+ free(better1);
+ free(better2);
+
if (failed < 0)
return 128; /* die() error code */
return failed;
+#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "builtin.h"
#include "tree-walk.h"
#include "xdiff-interface.h"
their = NULL;
if (entry)
their = entry->blob;
- return merge_blobs(&the_index, path, base, our, their, size);
+ return merge_blobs(the_repository->index, path,
+ base, our, their, size);
}
static void *origin(struct merge_list *entry, unsigned long *size)
/* An empty entry never compares same, not even to another empty entry */
static int same_entry(struct name_entry *a, struct name_entry *b)
{
- return a->oid &&
- b->oid &&
- oideq(a->oid, b->oid) &&
+ return !is_null_oid(&a->oid) &&
+ !is_null_oid(&b->oid) &&
+ oideq(&a->oid, &b->oid) &&
a->mode == b->mode;
}
static int both_empty(struct name_entry *a, struct name_entry *b)
{
- return !(a->oid || b->oid);
+ return is_null_oid(&a->oid) && is_null_oid(&b->oid);
}
static struct merge_list *create_entry(unsigned stage, unsigned mode, const struct object_id *oid, const char *path)
static char *traverse_path(const struct traverse_info *info, const struct name_entry *n)
{
- char *path = xmallocz(traverse_path_len(info, n));
+ char *path = xmallocz(traverse_path_len(info, n) + the_hash_algo->rawsz);
return make_traverse_path(path, info, n);
}
return;
path = traverse_path(info, result);
- orig = create_entry(2, ours->mode, ours->oid, path);
- final = create_entry(0, result->mode, result->oid, path);
+ orig = create_entry(2, ours->mode, &ours->oid, path);
+ final = create_entry(0, result->mode, &result->oid, path);
final->link = orig;
newbase = traverse_path(info, p);
-#define ENTRY_OID(e) (((e)->mode && S_ISDIR((e)->mode)) ? (e)->oid : NULL)
+#define ENTRY_OID(e) (((e)->mode && S_ISDIR((e)->mode)) ? &(e)->oid : NULL)
buf0 = fill_tree_descriptor(t + 0, ENTRY_OID(n + 0));
buf1 = fill_tree_descriptor(t + 1, ENTRY_OID(n + 1));
buf2 = fill_tree_descriptor(t + 2, ENTRY_OID(n + 2));
path = entry->path;
else
path = traverse_path(info, n);
- link = create_entry(stage, n->mode, n->oid, path);
+ link = create_entry(stage, n->mode, &n->oid, path);
link->link = entry;
return link;
}
}
if (same_entry(entry+0, entry+1)) {
- if (entry[2].oid && !S_ISDIR(entry[2].mode)) {
+ if (!is_null_oid(&entry[2].oid) && !S_ISDIR(entry[2].mode)) {
/* We did not touch, they modified -- take theirs */
resolve(info, entry+1, entry+2);
return mask;
setup_traverse_info(&info, base);
info.fn = threeway_callback;
- traverse_trees(3, t, &info);
+ traverse_trees(&the_index, 3, t, &info);
}
static void *get_tree_descriptor(struct tree_desc *desc, const char *rev)
* Based on git-merge.sh by Junio C Hamano.
*/
+#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "cache.h"
#include "config.h"
#include "parse-options.h"
ctx->argc--;
arg = *++ctx->argv;
} else
- return opterror(opt, "requires a value", 0);
+ return error(_("option `%s' requires a value"), opt->long_name);
if (buf->len)
strbuf_addch(buf, '\n');
argc = split_cmdline(bmo, &argv);
if (argc < 0)
die(_("Bad branch.%s.mergeoptions string: %s"), branch,
- split_cmdline_strerror(argc));
+ _(split_cmdline_strerror(argc)));
REALLOC_ARRAY(argv, argc + 2);
MOVE_ARRAY(argv + 1, argv, argc + 1);
argc++;
return 2;
}
- init_merge_options(&o);
+ init_merge_options(&o, the_repository);
if (!strcmp(strategy, "subtree"))
o.subtree_shift = "";
filename = git_path_merge_msg(the_repository);
fp = xfopen(filename, "a");
- append_conflicts_hint(&msgbuf);
+ append_conflicts_hint(&the_index, &msgbuf);
fputs(msgbuf.buf, fp);
strbuf_release(&msgbuf);
fclose(fp);
*
* Copyright (C) 2006 Johannes Schindelin
*/
+#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "builtin.h"
#include "config.h"
#include "pathspec.h"
}
if (!rewrite_cmd) {
- commit_notes(t, msg);
+ commit_notes(the_repository, t, msg);
free_notes(t);
} else {
- finish_copy_notes_for_rewrite(c, msg);
+ finish_copy_notes_for_rewrite(the_repository, c, msg);
}
strbuf_release(&buf);
return ret;
write_note_data(&d, &new_note);
if (add_note(t, &object, &new_note, combine_notes_overwrite))
BUG("combine_notes_overwrite failed");
- commit_notes(t, "Notes added by 'git notes add'");
+ commit_notes(the_repository, t,
+ "Notes added by 'git notes add'");
} else {
fprintf(stderr, _("Removing note for object %s\n"),
oid_to_hex(&object));
remove_note(t, object.hash);
- commit_notes(t, "Notes removed by 'git notes add'");
+ commit_notes(the_repository, t,
+ "Notes removed by 'git notes add'");
}
free_note_data(&d);
if (add_note(t, &object, from_note, combine_notes_overwrite))
BUG("combine_notes_overwrite failed");
- commit_notes(t, "Notes added by 'git notes copy'");
+ commit_notes(the_repository, t,
+ "Notes added by 'git notes copy'");
out:
free_notes(t);
return retval;
remove_note(t, object.hash);
logmsg = xstrfmt("Notes removed by 'git notes %s'", argv[0]);
}
- commit_notes(t, logmsg);
+ commit_notes(the_repository, t, logmsg);
free(logmsg);
free_note_data(&d);
usage_with_options(git_notes_merge_usage, options);
}
- init_notes_merge_options(&o);
+ init_notes_merge_options(the_repository, &o);
o.verbosity = verbosity + NOTES_MERGE_VERBOSITY_DEFAULT;
if (do_abort)
strbuf_release(&sb);
}
if (!retval)
- commit_notes(t, "Notes removed by 'git notes remove'");
+ commit_notes(the_repository, t,
+ "Notes removed by 'git notes remove'");
free_notes(t);
return retval;
}
prune_notes(t, (verbose ? NOTES_PRUNE_VERBOSE : 0) |
(show_only ? NOTES_PRUNE_VERBOSE|NOTES_PRUNE_DRYRUN : 0) );
if (!show_only)
- commit_notes(t, "Notes removed by 'git notes prune'");
+ commit_notes(the_repository, t,
+ "Notes removed by 'git notes prune'");
free_notes(t);
return 0;
}
static int depth = 50;
static int delta_search_threads;
static int pack_to_stdout;
+static int sparse;
static int thin;
static int num_preferred_base;
static struct progress *progress_state;
if (!check)
check = attr_check_initl("delta", NULL);
- git_check_attr(&the_index, path, check);
+ git_check_attr(the_repository->index, path, check);
if (ATTR_FALSE(check->items[0].value))
return 1;
return 0;
if (cmp < 0)
return;
if (name[cmplen] != '/') {
- add_object_entry(entry.oid,
+ add_object_entry(&entry.oid,
object_type(entry.mode),
fullname, 1);
return;
const char *down = name+cmplen+1;
int downlen = name_cmp_len(down);
- tree = pbase_tree_get(entry.oid);
+ tree = pbase_tree_get(&entry.oid);
if (!tree)
return;
init_tree_desc(&sub, tree->tree_data, tree->tree_size);
return 0;
}
-/* Protect access to object database */
-static pthread_mutex_t read_mutex;
-#define read_lock() pthread_mutex_lock(&read_mutex)
-#define read_unlock() pthread_mutex_unlock(&read_mutex)
-
/* Protect delta_cache_size */
static pthread_mutex_t cache_mutex;
#define cache_lock() pthread_mutex_lock(&cache_mutex)
unsigned long used, avail, size;
if (e->type_ != OBJ_OFS_DELTA && e->type_ != OBJ_REF_DELTA) {
- read_lock();
+ packing_data_lock(&to_pack);
if (oid_object_info(the_repository, &e->idx.oid, &size) < 0)
die(_("unable to get size of %s"),
oid_to_hex(&e->idx.oid));
- read_unlock();
+ packing_data_unlock(&to_pack);
return size;
}
if (!p)
BUG("when e->type is a delta, it must belong to a pack");
- read_lock();
+ packing_data_lock(&to_pack);
w_curs = NULL;
buf = use_pack(p, &w_curs, e->in_pack_offset, &avail);
used = unpack_object_header_buffer(buf, avail, &type, &size);
oid_to_hex(&e->idx.oid));
unuse_pack(&w_curs);
- read_unlock();
+ packing_data_unlock(&to_pack);
return size;
}
/* Load data if not already done */
if (!trg->data) {
- read_lock();
+ packing_data_lock(&to_pack);
trg->data = read_object_file(&trg_entry->idx.oid, &type, &sz);
- read_unlock();
+ packing_data_unlock(&to_pack);
if (!trg->data)
die(_("object %s cannot be read"),
oid_to_hex(&trg_entry->idx.oid));
*mem_usage += sz;
}
if (!src->data) {
- read_lock();
+ packing_data_lock(&to_pack);
src->data = read_object_file(&src_entry->idx.oid, &type, &sz);
- read_unlock();
+ packing_data_unlock(&to_pack);
if (!src->data) {
if (src_entry->preferred_base) {
static int warned = 0;
static void try_to_free_from_threads(size_t size)
{
- read_lock();
+ packing_data_lock(&to_pack);
release_pack_memory(size);
- read_unlock();
+ packing_data_unlock(&to_pack);
}
static try_to_free_t old_try_to_free_routine;
*/
static void init_threaded_search(void)
{
- init_recursive_mutex(&read_mutex);
pthread_mutex_init(&cache_mutex, NULL);
pthread_mutex_init(&progress_mutex, NULL);
pthread_cond_init(&progress_cond, NULL);
{
set_try_to_free_routine(old_try_to_free_routine);
pthread_cond_destroy(&progress_cond);
- pthread_mutex_destroy(&read_mutex);
pthread_mutex_destroy(&cache_mutex);
pthread_mutex_destroy(&progress_mutex);
}
unsigned n;
if (use_delta_islands)
- resolve_tree_islands(progress, &to_pack);
+ resolve_tree_islands(the_repository, progress, &to_pack);
get_object_details();
use_bitmap_index_default = git_config_bool(k, v);
return 0;
}
+ if (!strcmp(k, "pack.usesparse")) {
+ sparse = git_config_bool(k, v);
+ return 0;
+ }
if (!strcmp(k, "pack.threads")) {
delta_search_threads = git_config_int(k, v);
if (delta_search_threads < 0)
static void get_object_list(int ac, const char **av)
{
struct rev_info revs;
+ struct setup_revision_opt s_r_opt = {
+ .allow_exclude_promisor_objects = 1,
+ };
char line[1000];
int flags = 0;
int save_warning;
repo_init_revisions(the_repository, &revs, NULL);
save_commit_buffer = 0;
- revs.allow_exclude_promisor_objects_opt = 1;
- setup_revisions(ac, av, &revs, NULL);
+ setup_revisions(ac, av, &revs, &s_r_opt);
/* make sure shallows are read */
is_repository_shallow(the_repository);
return;
if (use_delta_islands)
- load_delta_islands();
+ load_delta_islands(the_repository);
if (prepare_revision_walk(&revs))
die(_("revision walk setup failed"));
- mark_edges_uninteresting(&revs, show_edge);
+ mark_edges_uninteresting(&revs, show_edge, sparse);
if (!fn_show_object)
fn_show_object = show_object;
{ OPTION_CALLBACK, 0, "unpack-unreachable", NULL, N_("time"),
N_("unpack unreachable objects newer than <time>"),
PARSE_OPT_OPTARG, option_parse_unpack_unreachable },
+ OPT_BOOL(0, "sparse", &sparse,
+ N_("use the sparse reachability algorithm")),
OPT_BOOL(0, "thin", &thin,
N_("create thin packs")),
OPT_BOOL(0, "shallow", &shallow,
read_replace_refs = 0;
+ sparse = git_env_bool("GIT_TEST_PACK_SPARSE", 0);
reset_pack_idx_option(&pack_idx_opts);
git_config(git_pack_config, NULL);
}
}
- prepare_packing_data(&to_pack);
+ prepare_packing_data(the_repository, &to_pack);
if (progress)
progress_state = start_progress(_("Enumerating objects"), 0);
save_commit_buffer = 0;
read_replace_refs = 0;
ref_paranoia = 1;
- revs.allow_exclude_promisor_objects_opt = 1;
repo_init_revisions(the_repository, &revs, prefix);
argc = parse_options(argc, argv, prefix, options, prune_usage, 0);
*
* Fetch one or more remote refs and merge it/them into the current HEAD.
*/
+#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "cache.h"
#include "config.h"
#include "builtin.h"
die(_("Updating an unborn branch with changes added to the index."));
if (!autostash)
- require_clean_work_tree(N_("pull with rebase"),
+ require_clean_work_tree(the_repository,
+ N_("pull with rebase"),
_("please commit or stash them."), 1, 0);
if (get_rebase_fork_point(&rebase_fork_point, repo, *refspecs))
return remote->url_nr;
}
-static NORETURN int die_push_simple(struct branch *branch, struct remote *remote) {
+static NORETURN int die_push_simple(struct branch *branch,
+ struct remote *remote)
+{
/*
* There's no point in using shorten_unambiguous_ref here,
* as the ambiguity would be on the remote side, not what
if (verbosity > 0)
fprintf(stderr, _("Pushing to %s\n"), transport->url);
- err = transport_push(transport, rs, flags, &reject_reasons);
+ err = transport_push(the_repository, transport,
+ rs, flags, &reject_reasons);
if (err != 0) {
fprintf(stderr, "%s", push_get_color(PUSH_COLOR_ERROR));
error(_("failed to push some refs to '%s'"), transport->url);
* Copyright (C) Linus Torvalds, 2005
*/
+#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "cache.h"
#include "config.h"
#include "lockfile.h"
* what came from the tree.
*/
if (nr_trees == 1 && !opts.prefix)
- prime_cache_tree(&the_index, trees[0]);
+ prime_cache_tree(the_repository,
+ the_repository->index,
+ trees[0]);
if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
die("unable to write new index file");
+#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "builtin.h"
#include "cache.h"
#include "config.h"
if (restrict_revision)
argv_array_push(&make_script_args, restrict_revision);
- ret = sequencer_make_script(todo_list,
+ ret = sequencer_make_script(the_repository, todo_list,
make_script_args.argc, make_script_args.argv,
flags);
fclose(todo_list);
error(_("could not generate todo list"));
else {
discard_cache();
- ret = complete_action(opts, flags, shortrevisions, onto_name, onto,
+ ret = complete_action(the_repository, opts, flags,
+ shortrevisions, onto_name, onto,
head_hash, cmd, autosquash);
}
OPT_STRING(0, "onto-name", &onto_name, N_("onto-name"), N_("onto name")),
OPT_STRING(0, "cmd", &cmd, N_("cmd"), N_("the command to run")),
OPT_RERERE_AUTOUPDATE(&opts.allow_rerere_auto),
+ OPT_BOOL(0, "reschedule-failed-exec", &opts.reschedule_failed_exec,
+ N_("automatically re-schedule any `exec` that fails")),
OPT_END()
};
case SKIP: {
struct string_list merge_rr = STRING_LIST_INIT_DUP;
- rerere_clear(&merge_rr);
+ rerere_clear(the_repository, &merge_rr);
/* fallthrough */
case CONTINUE:
- ret = sequencer_continue(&opts);
+ ret = sequencer_continue(the_repository, &opts);
break;
}
case EDIT_TODO:
- ret = edit_todo_list(flags);
+ ret = edit_todo_list(the_repository, flags);
break;
case SHOW_CURRENT_PATCH: {
struct child_process cmd = CHILD_PROCESS_INIT;
}
case SHORTEN_OIDS:
case EXPAND_OIDS:
- ret = transform_todos(flags);
+ ret = transform_todos(the_repository, flags);
break;
case CHECK_TODO_LIST:
- ret = check_todo_list();
+ ret = check_todo_list(the_repository);
break;
case REARRANGE_SQUASH:
- ret = rearrange_squash();
+ ret = rearrange_squash(the_repository);
break;
case ADD_EXEC:
- ret = sequencer_add_exec_commands(cmd);
+ ret = sequencer_add_exec_commands(the_repository, cmd);
break;
default:
BUG("invalid command '%d'", command);
* Copyright (c) 2018 Pratik Karki
*/
+#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "builtin.h"
#include "run-command.h"
#include "exec-cmd.h"
int rebase_merges, rebase_cousins;
char *strategy, *strategy_opts;
struct strbuf git_format_patch_opt;
+ int reschedule_failed_exec;
};
static int is_interactive(struct rebase_options *opts)
case REBASE_PRESERVE_MERGES:
break;
case REBASE_MERGE:
- /* we silently *upgrade* --merge to --interactive if needed */
+ /* we now implement --merge via --interactive */
default:
opts->type = REBASE_INTERACTIVE; /* implied */
break;
if (get_oid(buf.buf, &opts->orig_head))
return error(_("invalid orig-head: '%s'"), buf.buf);
- strbuf_reset(&buf);
- if (read_one(state_dir_path("quiet", opts), &buf))
- return -1;
- if (buf.len)
+ if (file_exists(state_dir_path("quiet", opts)))
opts->flags &= ~REBASE_NO_QUIET;
else
opts->flags |= REBASE_NO_QUIET;
return 0;
}
+static int write_basic_state(struct rebase_options *opts)
+{
+ write_file(state_dir_path("head-name", opts), "%s",
+ opts->head_name ? opts->head_name : "detached HEAD");
+ write_file(state_dir_path("onto", opts), "%s",
+ opts->onto ? oid_to_hex(&opts->onto->object.oid) : "");
+ write_file(state_dir_path("orig-head", opts), "%s",
+ oid_to_hex(&opts->orig_head));
+ write_file(state_dir_path("quiet", opts), "%s",
+ opts->flags & REBASE_NO_QUIET ? "" : "t");
+ if (opts->flags & REBASE_VERBOSE)
+ write_file(state_dir_path("verbose", opts), "%s", "");
+ if (opts->strategy)
+ write_file(state_dir_path("strategy", opts), "%s",
+ opts->strategy);
+ if (opts->strategy_opts)
+ write_file(state_dir_path("strategy_opts", opts), "%s",
+ opts->strategy_opts);
+ if (opts->allow_rerere_autoupdate >= 0)
+ write_file(state_dir_path("allow_rerere_autoupdate", opts),
+ "-%s-rerere-autoupdate",
+ opts->allow_rerere_autoupdate ? "" : "-no");
+ if (opts->gpg_sign_opt)
+ write_file(state_dir_path("gpg_sign_opt", opts), "%s",
+ opts->gpg_sign_opt);
+ if (opts->signoff)
+ write_file(state_dir_path("strategy", opts), "--signoff");
+
+ return 0;
+}
+
static int apply_autostash(struct rebase_options *opts)
{
const char *path = state_dir_path("autostash", opts);
}
}
+#define GIT_REFLOG_ACTION_ENVIRONMENT "GIT_REFLOG_ACTION"
+
+#define RESET_HEAD_DETACH (1<<0)
+#define RESET_HEAD_HARD (1<<1)
+#define RESET_HEAD_RUN_POST_CHECKOUT_HOOK (1<<2)
+#define RESET_HEAD_REFS_ONLY (1<<3)
+
+static int reset_head(struct object_id *oid, const char *action,
+ const char *switch_to_branch, unsigned flags,
+ const char *reflog_orig_head, const char *reflog_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;
+ struct object_id head_oid;
+ struct tree_desc desc[2] = { { NULL }, { NULL } };
+ struct lock_file lock = LOCK_INIT;
+ struct unpack_trees_options unpack_tree_opts;
+ 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;
+ 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 (!refs_only && hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0) {
+ ret = -1;
+ goto leave_reset_head;
+ }
+
+ if ((!oid || !reset_hard) && get_oid("HEAD", &head_oid)) {
+ ret = error(_("could not determine HEAD revision"));
+ goto leave_reset_head;
+ }
+
+ if (!oid)
+ oid = &head_oid;
+
+ if (refs_only)
+ goto reset_head_refs;
+
+ memset(&unpack_tree_opts, 0, sizeof(unpack_tree_opts));
+ setup_unpack_trees_porcelain(&unpack_tree_opts, action);
+ unpack_tree_opts.head_idx = 1;
+ unpack_tree_opts.src_index = the_repository->index;
+ unpack_tree_opts.dst_index = the_repository->index;
+ unpack_tree_opts.fn = reset_hard ? oneway_merge : twoway_merge;
+ unpack_tree_opts.update = 1;
+ unpack_tree_opts.merge = 1;
+ if (!detach_head)
+ unpack_tree_opts.reset = 1;
+
+ if (repo_read_index_unmerged(the_repository) < 0) {
+ ret = error(_("could not read index"));
+ goto leave_reset_head;
+ }
+
+ if (!reset_hard && !fill_tree_descriptor(&desc[nr++], &head_oid)) {
+ ret = error(_("failed to find tree of %s"),
+ oid_to_hex(&head_oid));
+ goto leave_reset_head;
+ }
+
+ if (!fill_tree_descriptor(&desc[nr++], oid)) {
+ ret = error(_("failed to find tree of %s"), oid_to_hex(oid));
+ goto leave_reset_head;
+ }
+
+ if (unpack_trees(nr, desc, &unpack_tree_opts)) {
+ ret = -1;
+ goto leave_reset_head;
+ }
+
+ tree = parse_tree_indirect(oid);
+ prime_cache_tree(the_repository, the_repository->index, tree);
+
+ if (write_locked_index(the_repository->index, &lock, COMMIT_LOCK) < 0) {
+ ret = error(_("could not write index"));
+ goto leave_reset_head;
+ }
+
+reset_head_refs:
+ reflog_action = getenv(GIT_REFLOG_ACTION_ENVIRONMENT);
+ strbuf_addf(&msg, "%s: ", reflog_action ? reflog_action : "rebase");
+ prefix_len = msg.len;
+
+ 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_orig_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);
+
+leave_reset_head:
+ strbuf_release(&msg);
+ rollback_lock_file(&lock);
+ while (nr)
+ free((void *)desc[--nr].buffer);
+ return ret;
+}
+
+static int move_to_original_branch(struct rebase_options *opts)
+{
+ struct strbuf orig_head_reflog = STRBUF_INIT, head_reflog = STRBUF_INIT;
+ int ret;
+
+ if (!opts->head_name)
+ return 0; /* nothing to move back to */
+
+ if (!opts->onto)
+ BUG("move_to_original_branch without onto");
+
+ strbuf_addf(&orig_head_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(NULL, "", opts->head_name, RESET_HEAD_REFS_ONLY,
+ orig_head_reflog.buf, head_reflog.buf);
+
+ strbuf_release(&orig_head_reflog);
+ strbuf_release(&head_reflog);
+ return ret;
+}
+
static const char *resolvemsg =
N_("Resolve all conflicts manually, mark them as resolved with\n"
"\"git add/rm <conflicted_files>\", then run \"git rebase --continue\".\n"
"To abort and get back to the state before \"git rebase\", run "
"\"git rebase --abort\".");
+static int run_am(struct rebase_options *opts)
+{
+ struct child_process am = CHILD_PROCESS_INIT;
+ struct child_process format_patch = CHILD_PROCESS_INIT;
+ struct strbuf revisions = STRBUF_INIT;
+ int status;
+ char *rebased_patches;
+
+ am.git_cmd = 1;
+ argv_array_push(&am.args, "am");
+
+ if (opts->action && !strcmp("continue", opts->action)) {
+ argv_array_push(&am.args, "--resolved");
+ argv_array_pushf(&am.args, "--resolvemsg=%s", resolvemsg);
+ if (opts->gpg_sign_opt)
+ argv_array_push(&am.args, opts->gpg_sign_opt);
+ status = run_command(&am);
+ if (status)
+ return status;
+
+ return move_to_original_branch(opts);
+ }
+ if (opts->action && !strcmp("skip", opts->action)) {
+ argv_array_push(&am.args, "--skip");
+ argv_array_pushf(&am.args, "--resolvemsg=%s", resolvemsg);
+ status = run_command(&am);
+ if (status)
+ return status;
+
+ return move_to_original_branch(opts);
+ }
+ if (opts->action && !strcmp("show-current-patch", opts->action)) {
+ argv_array_push(&am.args, "--show-current-patch");
+ return run_command(&am);
+ }
+
+ strbuf_addf(&revisions, "%s...%s",
+ oid_to_hex(opts->root ?
+ /* this is now equivalent to !opts->upstream */
+ &opts->onto->object.oid :
+ &opts->upstream->object.oid),
+ oid_to_hex(&opts->orig_head));
+
+ rebased_patches = xstrdup(git_path("rebased-patches"));
+ format_patch.out = open(rebased_patches,
+ O_WRONLY | O_CREAT | O_TRUNC, 0666);
+ if (format_patch.out < 0) {
+ status = error_errno(_("could not open '%s' for writing"),
+ rebased_patches);
+ free(rebased_patches);
+ argv_array_clear(&am.args);
+ return status;
+ }
+
+ format_patch.git_cmd = 1;
+ argv_array_pushl(&format_patch.args, "format-patch", "-k", "--stdout",
+ "--full-index", "--cherry-pick", "--right-only",
+ "--src-prefix=a/", "--dst-prefix=b/", "--no-renames",
+ "--no-cover-letter", "--pretty=mboxrd", "--topo-order", NULL);
+ if (opts->git_format_patch_opt.len)
+ argv_array_split(&format_patch.args,
+ opts->git_format_patch_opt.buf);
+ argv_array_push(&format_patch.args, revisions.buf);
+ if (opts->restrict_revision)
+ argv_array_pushf(&format_patch.args, "^%s",
+ oid_to_hex(&opts->restrict_revision->object.oid));
+
+ status = run_command(&format_patch);
+ if (status) {
+ unlink(rebased_patches);
+ free(rebased_patches);
+ argv_array_clear(&am.args);
+
+ reset_head(&opts->orig_head, "checkout", opts->head_name, 0,
+ "HEAD", NULL);
+ error(_("\ngit encountered an error while preparing the "
+ "patches to replay\n"
+ "these revisions:\n"
+ "\n %s\n\n"
+ "As a result, git cannot rebase them."),
+ opts->revisions);
+
+ strbuf_release(&revisions);
+ return status;
+ }
+ strbuf_release(&revisions);
+
+ am.in = open(rebased_patches, O_RDONLY);
+ if (am.in < 0) {
+ status = error_errno(_("could not open '%s' for reading"),
+ rebased_patches);
+ free(rebased_patches);
+ argv_array_clear(&am.args);
+ return status;
+ }
+
+ argv_array_pushv(&am.args, opts->git_am_opts.argv);
+ argv_array_push(&am.args, "--rebasing");
+ argv_array_pushf(&am.args, "--resolvemsg=%s", resolvemsg);
+ argv_array_push(&am.args, "--patch-format=mboxrd");
+ if (opts->allow_rerere_autoupdate > 0)
+ argv_array_push(&am.args, "--rerere-autoupdate");
+ else if (opts->allow_rerere_autoupdate == 0)
+ argv_array_push(&am.args, "--no-rerere-autoupdate");
+ if (opts->gpg_sign_opt)
+ argv_array_push(&am.args, opts->gpg_sign_opt);
+ status = run_command(&am);
+ unlink(rebased_patches);
+ free(rebased_patches);
+
+ if (!status) {
+ return move_to_original_branch(opts);
+ }
+
+ if (is_directory(opts->state_dir))
+ write_basic_state(opts);
+
+ return status;
+}
+
static int run_specific_rebase(struct rebase_options *opts)
{
const char *argv[] = { NULL, NULL };
argv_array_pushf(&child.env_array, "GIT_CHERRY_PICK_HELP=%s",
resolvemsg);
if (!(opts->flags & REBASE_INTERACTIVE_EXPLICIT)) {
- argv_array_push(&child.env_array, "GIT_EDITOR=:");
+ argv_array_push(&child.env_array,
+ "GIT_SEQUENCE_EDITOR=:");
opts->autosquash = 0;
}
argv_array_push(&child.args, opts->gpg_sign_opt);
if (opts->signoff)
argv_array_push(&child.args, "--signoff");
+ if (opts->reschedule_failed_exec)
+ argv_array_push(&child.args, "--reschedule-failed-exec");
status = run_command(&child);
goto finished_rebase;
}
+ if (opts->type == REBASE_AM) {
+ status = run_am(opts);
+ goto finished_rebase;
+ }
+
add_var(&script_snippet, "GIT_DIR", absolute_path(get_git_dir()));
add_var(&script_snippet, "state_dir", opts->state_dir);
if (is_interactive(opts) &&
!(opts->flags & REBASE_INTERACTIVE_EXPLICIT)) {
strbuf_addstr(&script_snippet,
- "GIT_EDITOR=:; export GIT_EDITOR; ");
+ "GIT_SEQUENCE_EDITOR=:; export GIT_SEQUENCE_EDITOR; ");
opts->autosquash = 0;
}
backend = "git-rebase--am";
backend_func = "git_rebase__am";
break;
- case REBASE_MERGE:
- backend = "git-rebase--merge";
- backend_func = "git_rebase__merge";
- break;
case REBASE_PRESERVE_MERGES:
backend = "git-rebase--preserve-merges";
backend_func = "git_rebase__preserve_merges";
return status ? -1 : 0;
}
-#define GIT_REFLOG_ACTION_ENVIRONMENT "GIT_REFLOG_ACTION"
-
-#define RESET_HEAD_DETACH (1<<0)
-#define RESET_HEAD_HARD (1<<1)
-
-static int reset_head(struct object_id *oid, const char *action,
- const char *switch_to_branch, unsigned flags,
- const char *reflog_orig_head, const char *reflog_head)
-{
- unsigned detach_head = flags & RESET_HEAD_DETACH;
- unsigned reset_hard = flags & RESET_HEAD_HARD;
- struct object_id head_oid;
- struct tree_desc desc[2] = { { NULL }, { NULL } };
- struct lock_file lock = LOCK_INIT;
- struct unpack_trees_options unpack_tree_opts;
- 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;
- 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 (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0) {
- ret = -1;
- goto leave_reset_head;
- }
-
- if ((!oid || !reset_hard) && get_oid("HEAD", &head_oid)) {
- ret = error(_("could not determine HEAD revision"));
- goto leave_reset_head;
- }
-
- if (!oid)
- oid = &head_oid;
-
- memset(&unpack_tree_opts, 0, sizeof(unpack_tree_opts));
- setup_unpack_trees_porcelain(&unpack_tree_opts, action);
- unpack_tree_opts.head_idx = 1;
- unpack_tree_opts.src_index = the_repository->index;
- unpack_tree_opts.dst_index = the_repository->index;
- unpack_tree_opts.fn = reset_hard ? oneway_merge : twoway_merge;
- unpack_tree_opts.update = 1;
- unpack_tree_opts.merge = 1;
- if (!detach_head)
- unpack_tree_opts.reset = 1;
-
- if (read_index_unmerged(the_repository->index) < 0) {
- ret = error(_("could not read index"));
- goto leave_reset_head;
- }
-
- if (!reset_hard && !fill_tree_descriptor(&desc[nr++], &head_oid)) {
- ret = error(_("failed to find tree of %s"),
- oid_to_hex(&head_oid));
- goto leave_reset_head;
- }
-
- if (!fill_tree_descriptor(&desc[nr++], oid)) {
- ret = error(_("failed to find tree of %s"), oid_to_hex(oid));
- goto leave_reset_head;
- }
-
- if (unpack_trees(nr, desc, &unpack_tree_opts)) {
- ret = -1;
- goto leave_reset_head;
- }
-
- tree = parse_tree_indirect(oid);
- prime_cache_tree(the_repository->index, tree);
-
- if (write_locked_index(the_repository->index, &lock, COMMIT_LOCK) < 0) {
- ret = error(_("could not write index"));
- goto leave_reset_head;
- }
-
- reflog_action = getenv(GIT_REFLOG_ACTION_ENVIRONMENT);
- strbuf_addf(&msg, "%s: ", reflog_action ? reflog_action : "rebase");
- prefix_len = msg.len;
-
- 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 = create_symref("HEAD", switch_to_branch, msg.buf);
- if (!ret)
- ret = update_ref(reflog_head, "HEAD", oid, NULL, 0,
- UPDATE_REFS_MSG_ON_ERR);
- }
-
-leave_reset_head:
- strbuf_release(&msg);
- rollback_lock_file(&lock);
- while (nr)
- free((void *)desc[--nr].buffer);
- return ret;
-}
-
static int rebase_config(const char *var, const char *value, void *data)
{
struct rebase_options *opts = data;
return 0;
}
+ if (!strcmp(var, "rebase.reschedulefailedexec")) {
+ opts->reschedule_failed_exec = git_config_bool(var, value);
+ return 0;
+ }
+
return git_default_config(var, value, data);
}
return 0;
}
+struct opt_y {
+ struct string_list *list;
+ struct rebase_options *options;
+};
+
+static int parse_opt_y(const struct option *opt, const char *arg, int unset)
+{
+ struct opt_y *o = opt->value;
+
+ if (unset || !arg)
+ return -1;
+
+ o->options->reschedule_failed_exec = 1;
+ string_list_append(o->list, arg);
+ return 0;
+}
+
static void NORETURN error_on_missing_default_upstream(void)
{
struct branch *current_branch = branch_get(NULL);
struct string_list strategy_options = STRING_LIST_INIT_NODUP;
struct object_id squash_onto;
char *squash_onto_name = NULL;
+ struct opt_y opt_y = { .list = &exec, .options = &options };
struct option builtin_rebase_options[] = {
OPT_STRING(0, "onto", &options.onto_name,
N_("revision"),
OPT_STRING_LIST('x', "exec", &exec, N_("exec"),
N_("add exec lines after each commit of the "
"editable list")),
+ { OPTION_CALLBACK, 'y', NULL, &opt_y, N_("<cmd>"),
+ N_("same as --reschedule-failed-exec -x <cmd>"),
+ PARSE_OPT_NONEG, parse_opt_y },
OPT_BOOL(0, "allow-empty-message",
&options.allow_empty_message,
N_("allow rebasing commits with empty messages")),
"strategy")),
OPT_BOOL(0, "root", &options.root,
N_("rebase all reachable commits up to the root(s)")),
+ OPT_BOOL(0, "reschedule-failed-exec",
+ &options.reschedule_failed_exec,
+ N_("automatically re-schedule any `exec` that fails")),
OPT_END(),
};
int i;
die(_("Cannot read HEAD"));
fd = hold_locked_index(&lock_file, 0);
- if (read_index(the_repository->index) < 0)
+ if (repo_read_index(the_repository) < 0)
die(_("could not read index"));
refresh_index(the_repository->index, REFRESH_QUIET, NULL, NULL,
NULL);
if (0 <= fd)
- update_index_if_able(the_repository->index,
- &lock_file);
+ repo_update_index_if_able(the_repository, &lock_file);
rollback_lock_file(&lock_file);
- if (has_unstaged_changes(1)) {
+ if (has_unstaged_changes(the_repository, 1)) {
puts(_("You must edit all merge conflicts and then\n"
"mark them as resolved using git add"));
exit(1);
options.action = "skip";
set_reflog_action(&options);
- rerere_clear(&merge_rr);
+ rerere_clear(the_repository, &merge_rr);
string_list_clear(&merge_rr, 1);
if (reset_head(NULL, "reset", NULL, RESET_HEAD_HARD,
NULL, NULL) < 0)
die(_("could not discard worktree changes"));
- remove_branch_state();
+ remove_branch_state(the_repository);
if (read_basic_state(&options))
exit(1);
goto run_rebase;
options.action = "abort";
set_reflog_action(&options);
- rerere_clear(&merge_rr);
+ rerere_clear(the_repository, &merge_rr);
string_list_clear(&merge_rr, 1);
if (read_basic_state(&options))
NULL, NULL) < 0)
die(_("could not move back to %s"),
oid_to_hex(&options.orig_head));
- remove_branch_state();
+ remove_branch_state(the_repository);
ret = finish_rebase(&options);
goto cleanup;
}
}
}
+ if (options.type == REBASE_MERGE)
+ imply_interactive(&options, "--merge");
+
if (options.root && !options.onto_name)
imply_interactive(&options, "--root without --onto");
break;
}
+ if (options.reschedule_failed_exec && !is_interactive(&options))
+ die(_("--reschedule-failed-exec requires an interactive rebase"));
+
if (options.git_am_opts.argc) {
/* all am options except -q are compatible only with --am */
for (i = options.git_am_opts.argc - 1; i >= 0; i--)
break;
if (is_interactive(&options) && i >= 0)
- die(_("error: cannot combine interactive options "
- "(--interactive, --exec, --rebase-merges, "
- "--preserve-merges, --keep-empty, --root + "
- "--onto) with am options (%s)"), buf.buf);
- if (options.type == REBASE_MERGE && i >= 0)
- die(_("error: cannot combine merge options (--merge, "
- "--strategy, --strategy-option) with am options "
- "(%s)"), buf.buf);
+ die(_("cannot combine am options with either "
+ "interactive or merge options"));
}
if (options.signoff) {
options.flags |= REBASE_FORCE;
}
- if (options.type == REBASE_PRESERVE_MERGES)
+ if (options.type == REBASE_PRESERVE_MERGES) {
/*
* Note: incompatibility with --signoff handled in signoff block above
* Note: incompatibility with --interactive is just a strong warning;
* git-rebase.txt caveats with "unless you know what you are doing"
*/
if (options.rebase_merges)
- die(_("error: cannot combine '--preserve-merges' with "
+ die(_("cannot combine '--preserve-merges' with "
"'--rebase-merges'"));
+ if (options.reschedule_failed_exec)
+ die(_("error: cannot combine '--preserve-merges' with "
+ "'--reschedule-failed-exec'"));
+ }
+
if (options.rebase_merges) {
if (strategy_options.nr)
- die(_("error: cannot combine '--rebase-merges' with "
+ die(_("cannot combine '--rebase-merges' with "
"'--strategy-option'"));
if (options.strategy)
- die(_("error: cannot combine '--rebase-merges' with "
+ die(_("cannot combine '--rebase-merges' with "
"'--strategy'"));
}
get_fork_point(options.upstream_name, head);
}
- if (read_index(the_repository->index) < 0)
+ if (repo_read_index(the_repository) < 0)
die(_("could not read index"));
if (options.autostash) {
fd = hold_locked_index(&lock_file, 0);
refresh_cache(REFRESH_QUIET);
if (0 <= fd)
- update_index_if_able(&the_index, &lock_file);
+ repo_update_index_if_able(the_repository, &lock_file);
rollback_lock_file(&lock_file);
- if (has_unstaged_changes(1) || has_uncommitted_changes(1)) {
+ if (has_unstaged_changes(the_repository, 1) ||
+ has_uncommitted_changes(the_repository, 1)) {
const char *autostash =
state_dir_path("autostash", &options);
struct child_process stash = CHILD_PROCESS_INIT;
putchar('\n');
if (discard_index(the_repository->index) < 0 ||
- read_index(the_repository->index) < 0)
+ repo_read_index(the_repository) < 0)
die(_("could not read index"));
}
}
- if (require_clean_work_tree("rebase",
+ if (require_clean_work_tree(the_repository, "rebase",
_("Please commit or stash them."), 1, 1)) {
ret = 1;
goto cleanup;
getenv(GIT_REFLOG_ACTION_ENVIRONMENT),
options.switch_to);
if (reset_head(&oid, "checkout",
- options.head_name, 0,
+ options.head_name,
+ RESET_HEAD_RUN_POST_CHECKOUT_HOOK,
NULL, buf.buf) < 0) {
ret = !!error(_("could not switch to "
"%s"),
strbuf_addf(&msg, "%s: checkout %s",
getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options.onto_name);
if (reset_head(&options.onto->object.oid, "checkout", NULL,
- RESET_HEAD_DETACH, NULL, msg.buf))
+ RESET_HEAD_DETACH | RESET_HEAD_RUN_POST_CHECKOUT_HOOK,
+ NULL, msg.buf))
die(_("Could not detach HEAD"));
strbuf_release(&msg);
}
}
-static struct command *read_head_info(struct oid_array *shallow)
+static struct command *read_head_info(struct packet_reader *reader,
+ struct oid_array *shallow)
{
struct command *commands = NULL;
struct command **p = &commands;
for (;;) {
- char *line;
- int len, linelen;
+ int linelen;
- line = packet_read_line(0, &len);
- if (!line)
+ if (packet_reader_read(reader) != PACKET_READ_NORMAL)
break;
- if (len > 8 && starts_with(line, "shallow ")) {
+ if (reader->pktlen > 8 && starts_with(reader->line, "shallow ")) {
struct object_id oid;
- if (get_oid_hex(line + 8, &oid))
+ if (get_oid_hex(reader->line + 8, &oid))
die("protocol error: expected shallow sha, got '%s'",
- line + 8);
+ reader->line + 8);
oid_array_append(shallow, &oid);
continue;
}
- linelen = strlen(line);
- if (linelen < len) {
- const char *feature_list = line + linelen + 1;
+ linelen = strlen(reader->line);
+ if (linelen < reader->pktlen) {
+ const char *feature_list = reader->line + linelen + 1;
if (parse_feature_request(feature_list, "report-status"))
report_status = 1;
if (parse_feature_request(feature_list, "side-band-64k"))
use_push_options = 1;
}
- if (!strcmp(line, "push-cert")) {
+ if (!strcmp(reader->line, "push-cert")) {
int true_flush = 0;
- char certbuf[1024];
+ int saved_options = reader->options;
+ reader->options &= ~PACKET_READ_CHOMP_NEWLINE;
for (;;) {
- len = packet_read(0, NULL, NULL,
- certbuf, sizeof(certbuf), 0);
- if (!len) {
+ packet_reader_read(reader);
+ if (reader->status == PACKET_READ_FLUSH) {
true_flush = 1;
break;
}
- if (!strcmp(certbuf, "push-cert-end\n"))
+ if (reader->status != PACKET_READ_NORMAL) {
+ die("protocol error: got an unexpected packet");
+ }
+ if (!strcmp(reader->line, "push-cert-end\n"))
break; /* end of cert */
- strbuf_addstr(&push_cert, certbuf);
+ strbuf_addstr(&push_cert, reader->line);
}
+ reader->options = saved_options;
if (true_flush)
break;
continue;
}
- p = queue_command(p, line, linelen);
+ p = queue_command(p, reader->line, linelen);
}
if (push_cert.len)
return commands;
}
-static void read_push_options(struct string_list *options)
+static void read_push_options(struct packet_reader *reader,
+ struct string_list *options)
{
while (1) {
- char *line;
- int len;
-
- line = packet_read_line(0, &len);
-
- if (!line)
+ if (packet_reader_read(reader) != PACKET_READ_NORMAL)
break;
- string_list_append(options, line);
+ string_list_append(options, reader->line);
}
}
struct oid_array shallow = OID_ARRAY_INIT;
struct oid_array ref = OID_ARRAY_INIT;
struct shallow_info si;
+ struct packet_reader reader;
struct option options[] = {
OPT__QUIET(&quiet, N_("quiet")),
if (advertise_refs)
return 0;
- if ((commands = read_head_info(&shallow)) != NULL) {
+ packet_reader_init(&reader, 0, NULL, 0,
+ PACKET_READ_CHOMP_NEWLINE |
+ PACKET_READ_DIE_ON_ERR_PACKET);
+
+ if ((commands = read_head_info(&reader, &shallow)) != NULL) {
const char *unpack_status = NULL;
struct string_list push_options = STRING_LIST_INIT_DUP;
if (use_push_options)
- read_push_options(&push_options);
+ read_push_options(&reader, &push_options);
if (!check_cert_push_options(&push_options)) {
struct command *cmd;
for (cmd = commands; cmd; cmd = cmd->next)
/* NEEDSWORK: switch to using parse_options */
static const char reflog_expire_usage[] =
-"git reflog expire [--expire=<time>] [--expire-unreachable=<time>] [--rewrite] [--updateref] [--stale-fix] [--dry-run | -n] [--verbose] [--all] <refs>...";
+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[] =
-"git reflog delete [--rewrite] [--updateref] [--dry-run | -n] [--verbose] <refs>...";
+N_("git reflog delete [--rewrite] [--updateref] "
+ "[--dry-run | -n] [--verbose] <refs>...");
static const char reflog_exists_usage[] =
-"git reflog exists <ref>";
+N_("git reflog exists <ref>");
static timestamp_t default_reflog_expire;
static timestamp_t default_reflog_expire_unreachable;
init_tree_desc(&desc, tree->buffer, tree->size);
complete = 1;
while (tree_entry(&desc, &entry)) {
- if (!has_sha1_file(entry.oid->hash) ||
- (S_ISDIR(entry.mode) && !tree_is_complete(entry.oid))) {
+ if (!has_sha1_file(entry.oid.hash) ||
+ (S_ISDIR(entry.mode) && !tree_is_complete(&entry.oid))) {
tree->object.flags |= INCOMPLETE;
complete = 0;
}
break;
}
else if (arg[0] == '-')
- usage(reflog_expire_usage);
+ usage(_(reflog_expire_usage));
else
break;
}
if (cb.cmd.stalefix) {
repo_init_revisions(the_repository, &cb.cmd.revs, prefix);
if (flags & EXPIRE_REFLOGS_VERBOSE)
- printf("Marking reachable objects...");
+ printf(_("Marking reachable objects..."));
mark_reachable_objects(&cb.cmd.revs, 0, 0, NULL);
if (flags & EXPIRE_REFLOGS_VERBOSE)
putchar('\n');
char *ref;
struct object_id oid;
if (!dwim_log(argv[i], strlen(argv[i]), &oid, &ref)) {
- status |= error("%s points nowhere!", argv[i]);
+ status |= error(_("%s points nowhere!"), argv[i]);
continue;
}
set_reflog_expiry_param(&cb.cmd, explicit_expiry, ref);
break;
}
else if (arg[0] == '-')
- usage(reflog_delete_usage);
+ usage(_(reflog_delete_usage));
else
break;
}
if (argc - i < 1)
- return error("Nothing to delete?");
+ return error(_("no reflog specified to delete"));
for ( ; i < argc; i++) {
const char *spec = strstr(argv[i], "@{");
int recno;
if (!spec) {
- status |= error("Not a reflog: %s", argv[i]);
+ status |= error(_("not a reflog: %s"), argv[i]);
continue;
}
if (!dwim_log(argv[i], spec - argv[i], &oid, &ref)) {
- status |= error("no reflog for '%s'", argv[i]);
+ status |= error(_("no reflog for '%s'"), argv[i]);
continue;
}
break;
}
else if (arg[0] == '-')
- usage(reflog_exists_usage);
+ usage(_(reflog_exists_usage));
else
break;
}
start = i;
if (argc - start != 1)
- usage(reflog_exists_usage);
+ usage(_(reflog_exists_usage));
if (check_refname_format(argv[start], REFNAME_ALLOW_ONELEVEL))
- die("invalid ref format: %s", argv[start]);
+ die(_("invalid ref format: %s"), argv[start]);
return !reflog_exists(argv[start]);
}
*/
static const char reflog_usage[] =
-"git reflog [ show | expire | delete | exists ]";
+N_("git reflog [ show | expire | delete | exists ]");
int cmd_reflog(int argc, const char **argv, const char *prefix)
{
if (argc > 1 && !strcmp(argv[1], "-h"))
- usage(reflog_usage);
+ usage(_(reflog_usage));
/* With no command, we default to showing it. */
if (argc < 2 || *argv[1] == '-')
if (cmd->in == -1) {
if (start_command(cmd))
- die("Could not start pack-objects to repack promisor objects");
+ die(_("could not start pack-objects to repack promisor objects"));
}
xwrite(cmd->in, oid_to_hex(oid), GIT_SHA1_HEXSZ);
char *promisor_name;
int fd;
if (line.len != the_hash_algo->hexsz)
- die("repack: Expecting full hex object ID lines only from pack-objects.");
+ die(_("repack: Expecting full hex object ID lines only from pack-objects."));
string_list_append(names, line.buf);
/*
line.buf);
fd = open(promisor_name, O_CREAT|O_EXCL|O_WRONLY, 0600);
if (fd < 0)
- die_errno("unable to create '%s'", promisor_name);
+ die_errno(_("unable to create '%s'"), promisor_name);
close(fd);
free(promisor_name);
}
fclose(out);
if (finish_command(&cmd))
- die("Could not finish pack-objects to repack promisor objects");
+ die(_("could not finish pack-objects to repack promisor objects"));
}
#define ALL_INTO_ONE 1
out = xfdopen(cmd.out, "r");
while (strbuf_getline_lf(&line, out) != EOF) {
if (line.len != the_hash_algo->hexsz)
- die("repack: Expecting full hex object ID lines only from pack-objects.");
+ die(_("repack: Expecting full hex object ID lines only from pack-objects."));
string_list_append(&names, line.buf);
}
fclose(out);
return ret;
if (!names.nr && !po_args.quiet)
- printf("Nothing new to pack.\n");
+ printf_ln(_("Nothing new to pack."));
+
+ close_all_packs(the_repository->objects);
/*
* Ok we have prepared all new packfiles.
if (rollback_failure.nr) {
int i;
fprintf(stderr,
- "WARNING: Some packs in use have been renamed by\n"
- "WARNING: prefixing old- to their name, in order to\n"
- "WARNING: replace them with the new version of the\n"
- "WARNING: file. But the operation failed, and the\n"
- "WARNING: attempt to rename them back to their\n"
- "WARNING: original names also failed.\n"
- "WARNING: Please rename them in %s manually:\n", packdir);
+ _("WARNING: Some packs in use have been renamed by\n"
+ "WARNING: prefixing old- to their name, in order to\n"
+ "WARNING: replace them with the new version of the\n"
+ "WARNING: file. But the operation failed, and the\n"
+ "WARNING: attempt to rename them back to their\n"
+ "WARNING: original names also failed.\n"
+ "WARNING: Please rename them in %s manually:\n"), packdir);
for (i = 0; i < rollback_failure.nr; i++)
fprintf(stderr, "WARNING: old-%s -> %s\n",
rollback_failure.items[i].string,
close(fd);
return -1;
}
- if (index_fd(&the_index, oid, fd, &st, type, NULL, flags) < 0)
+ if (index_fd(the_repository->index, oid, fd, &st, type, NULL, flags) < 0)
return error(_("unable to write object to database"));
/* index_fd close()s fd for us */
}
}
if (!strcmp(argv[0], "clear")) {
- rerere_clear(&merge_rr);
+ rerere_clear(the_repository, &merge_rr);
} else if (!strcmp(argv[0], "gc"))
- rerere_gc(&merge_rr);
+ rerere_gc(the_repository, &merge_rr);
else if (!strcmp(argv[0], "status")) {
- if (setup_rerere(&merge_rr, flags | RERERE_READONLY) < 0)
+ if (setup_rerere(the_repository, &merge_rr,
+ flags | RERERE_READONLY) < 0)
return 0;
for (i = 0; i < merge_rr.nr; i++)
printf("%s\n", merge_rr.items[i].string);
merge_rr.items[i].util = NULL;
}
} else if (!strcmp(argv[0], "diff")) {
- if (setup_rerere(&merge_rr, flags | RERERE_READONLY) < 0)
+ if (setup_rerere(the_repository, &merge_rr,
+ flags | RERERE_READONLY) < 0)
return 0;
for (i = 0; i < merge_rr.nr; i++) {
const char *path = merge_rr.items[i].string;
*
* Copyright (c) 2005, 2006 Linus Torvalds and Junio C Hamano
*/
+#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "builtin.h"
#include "config.h"
#include "lockfile.h"
if (reset_type == MIXED || reset_type == HARD) {
tree = parse_tree_indirect(oid);
- prime_cache_tree(&the_index, tree);
+ prime_cache_tree(the_repository, the_repository->index, tree);
}
ret = 0;
print_new_head_line(lookup_commit_reference(the_repository, &oid));
}
if (!pathspec.nr)
- remove_branch_state();
+ remove_branch_state(the_repository);
return update_ref_status;
}
free_commit_list(commit->parents);
commit->parents = NULL;
}
- free_commit_buffer(commit);
+ free_commit_buffer(the_repository->parsed_objects,
+ commit);
}
static inline void finish_object__ma(struct object *obj)
{
struct rev_info revs;
struct rev_list_info info;
+ struct setup_revision_opt s_r_opt = {
+ .allow_exclude_promisor_objects = 1,
+ };
int i;
int bisect_list = 0;
int bisect_show_vars = 0;
git_config(git_default_config, NULL);
repo_init_revisions(the_repository, &revs, prefix);
revs.abbrev = DEFAULT_ABBREV;
- revs.allow_exclude_promisor_objects_opt = 1;
revs.commit_format = CMIT_FMT_UNSPECIFIED;
revs.do_not_die_on_missing_tree = 1;
}
}
- argc = setup_revisions(argc, argv, &revs, NULL);
+ argc = setup_revisions(argc, argv, &revs, &s_r_opt);
memset(&info, 0, sizeof(info));
info.revs = &revs;
if (prepare_revision_walk(&revs))
die("revision walk setup failed");
if (revs.tree_objects)
- mark_edges_uninteresting(&revs, show_edge);
+ mark_edges_uninteresting(&revs, show_edge, 0);
if (bisect_list) {
int reaches, all;
*
* Copyright (C) Linus Torvalds, 2005
*/
+#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "cache.h"
#include "config.h"
#include "commit.h"
name++;
type = REVERSED;
}
- if (!get_oid_with_context(name, flags, &oid, &unused)) {
+ if (!get_oid_with_context(the_repository, name,
+ flags, &oid, &unused)) {
if (verify)
revs_count++;
else
replay->mainline = strtol(arg, &end, 10);
if (*end || replay->mainline <= 0)
- return opterror(opt, "expects a number greater than zero", 0);
+ return error(_("option `%s' expects a number greater than zero"),
+ opt->long_name);
return 0;
}
if (cmd == 'q') {
int ret = sequencer_remove_state(opts);
if (!ret)
- remove_branch_state();
+ remove_branch_state(the_repository);
return ret;
}
if (cmd == 'c')
- return sequencer_continue(opts);
+ return sequencer_continue(the_repository, opts);
if (cmd == 'a')
- return sequencer_rollback(opts);
- return sequencer_pick_revisions(opts);
+ return sequencer_rollback(the_repository, opts);
+ return sequencer_pick_revisions(the_repository, opts);
}
int cmd_revert(int argc, const char **argv, const char *prefix)
*
* Copyright (C) Linus Torvalds 2006
*/
+#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "builtin.h"
#include "config.h"
#include "lockfile.h"
packet_reader_init(&reader, fd[0], NULL, 0,
PACKET_READ_CHOMP_NEWLINE |
- PACKET_READ_GENTLE_ON_EOF);
+ PACKET_READ_GENTLE_ON_EOF |
+ PACKET_READ_DIE_ON_ERR_PACKET);
switch (discover_version(&reader)) {
case protocol_v2:
{
struct strbuf buf = STRBUF_INIT;
enum stripspace_mode mode = STRIP_DEFAULT;
+ int nongit;
const struct option options[] = {
OPT_CMDMODE('s', "strip-comments", &mode,
usage_with_options(stripspace_usage, options);
if (mode == STRIP_COMMENTS || mode == COMMENT_LINES) {
- setup_git_directory_gently(NULL);
+ setup_git_directory_gently(&nongit);
git_config(git_default_config, NULL);
}
+#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "builtin.h"
#include "repository.h"
#include "cache.h"
if (!(flags & OPT_QUIET))
printf(format, displaypath);
+ submodule_unset_core_worktree(sub);
+
strbuf_release(&sb_rm);
}
SUBMODULE_ALTERNATE_ERROR_IGNORE, NULL }
static int add_possible_reference_from_superproject(
- struct alternate_object_database *alt, void *sas_cb)
+ struct object_directory *odb, void *sas_cb)
{
struct submodule_alternate_setup *sas = sas_cb;
+ size_t len;
/*
* If the alternate object store is another repository, try the
* standard layout with .git/(modules/<name>)+/objects
*/
- if (ends_with(alt->path, "/objects")) {
+ if (strip_suffix(odb->path, "/objects", &len)) {
char *sm_alternate;
struct strbuf sb = STRBUF_INIT;
struct strbuf err = STRBUF_INIT;
- strbuf_add(&sb, alt->path, strlen(alt->path) - strlen("objects"));
+ strbuf_add(&sb, odb->path, len);
/*
* We need to end the new path with '/' to mark it as a dir,
* as the last part of a missing submodule reference would
* be taken as a file name.
*/
- strbuf_addf(&sb, "modules/%s/", sas->submodule_name);
+ strbuf_addf(&sb, "/modules/%s/", sas->submodule_name);
sm_alternate = compute_alternate_path(sb.buf, &err);
if (sm_alternate) {
#define SUBMODULE_UPDATE_CLONE_INIT {0, MODULE_LIST_INIT, 0, \
SUBMODULE_UPDATE_STRATEGY_INIT, 0, 0, -1, STRING_LIST_INIT_DUP, 0, \
NULL, NULL, NULL, \
- NULL, 0, 0, 0, NULL, 0, 0, 0}
+ NULL, 0, 0, 0, NULL, 0, 0, 1}
static void next_submodule_warn_missing(struct submodule_update_clone *suc,
struct repository subrepo;
if (argc != 2)
- BUG("submodule--helper connect-gitdir-workingtree <name> <path>");
+ BUG("submodule--helper ensure-core-worktree <path>");
path = argv[1];
if (!sub)
BUG("We could get the submodule handle before?");
- if (repo_submodule_init(&subrepo, the_repository, path))
+ if (repo_submodule_init(&subrepo, the_repository, sub))
die(_("could not get a repository handle for submodule '%s'"), path);
if (!repo_config_get_string(&subrepo, "core.worktree", &cw)) {
*
* Copyright (C) Linus Torvalds, 2005
*/
+#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "cache.h"
#include "config.h"
#include "lockfile.h"
#include "refs.h"
#include "run-command.h"
#include "sigchain.h"
+#include "submodule.h"
#include "refs.h"
#include "utf8.h"
#include "worktree.h"
static void validate_no_submodules(const struct worktree *wt)
{
struct index_state istate = { NULL };
+ struct strbuf path = STRBUF_INIT;
int i, found_submodules = 0;
- if (read_index_from(&istate, worktree_git_path(wt, "index"),
- get_worktree_git_dir(wt)) > 0) {
+ if (is_directory(worktree_git_path(wt, "modules"))) {
+ /*
+ * There could be false positives, e.g. the "modules"
+ * directory exists but is empty. But it's a rare case and
+ * this simpler check is probably good enough for now.
+ */
+ found_submodules = 1;
+ } else if (read_index_from(&istate, worktree_git_path(wt, "index"),
+ get_worktree_git_dir(wt)) > 0) {
for (i = 0; i < istate.cache_nr; i++) {
struct cache_entry *ce = istate.cache[i];
+ int err;
- if (S_ISGITLINK(ce->ce_mode)) {
- found_submodules = 1;
- break;
- }
+ if (!S_ISGITLINK(ce->ce_mode))
+ continue;
+
+ strbuf_reset(&path);
+ strbuf_addf(&path, "%s/%s", wt->path, ce->name);
+ if (!is_submodule_populated_gently(path.buf, &err))
+ continue;
+
+ found_submodules = 1;
+ break;
}
}
discard_index(&istate);
+ strbuf_release(&path);
if (found_submodules)
die(_("working trees containing submodules cannot be moved or removed"));
*
* Copyright (C) Linus Torvalds, 2005
*/
+#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "builtin.h"
#include "cache.h"
#include "config.h"
/* Remember to update object flag allocation in object.h */
#define PREREQ_MARK (1u<<16)
-int verify_bundle(struct bundle_header *header, int verbose)
+int verify_bundle(struct repository *r,
+ struct bundle_header *header,
+ int verbose)
{
/*
* Do fast check, then if any prereqs are missing then go line by line
int i, ret = 0, req_nr;
const char *message = _("Repository lacks these prerequisite commits:");
- repo_init_revisions(the_repository, &revs, NULL);
+ repo_init_revisions(r, &revs, NULL);
for (i = 0; i < p->nr; i++) {
struct ref_list_entry *e = p->list + i;
- struct object *o = parse_object(the_repository, &e->oid);
+ struct object *o = parse_object(r, &e->oid);
if (o) {
o->flags |= PREREQ_MARK;
add_pending_object(&revs, o, e->name);
for (i = 0; i < p->nr; i++) {
struct ref_list_entry *e = p->list + i;
- struct object *o = parse_object(the_repository, &e->oid);
+ struct object *o = parse_object(r, &e->oid);
assert(o); /* otherwise we'd have returned early */
if (o->flags & SHOWN)
continue;
/* Clean up objects used, as they will be reused. */
for (i = 0; i < p->nr; i++) {
struct ref_list_entry *e = p->list + i;
- commit = lookup_commit_reference_gently(the_repository, &e->oid, 1);
+ commit = lookup_commit_reference_gently(r, &e->oid, 1);
if (commit)
clear_commit_marks(commit, ALL_REV_FLAGS);
}
* in terms of a tag (e.g. v2.0 from the range
* "v1.0..v2.0")?
*/
- struct commit *one = lookup_commit_reference(the_repository,
- &oid);
+ struct commit *one = lookup_commit_reference(revs->repo, &oid);
struct object *obj;
if (e->item == &(one->object)) {
return ref_count;
}
-int create_bundle(struct bundle_header *header, const char *path,
+int create_bundle(struct repository *r, const char *path,
int argc, const char **argv)
{
struct lock_file lock = LOCK_INIT;
/* init revs to list objects for pack-objects later */
save_commit_buffer = 0;
- repo_init_revisions(the_repository, &revs, NULL);
+ repo_init_revisions(r, &revs, NULL);
/* write prerequisites */
if (compute_and_write_prerequisites(bundle_fd, &revs, argc, argv))
return -1;
}
-int unbundle(struct bundle_header *header, int bundle_fd, int flags)
+int unbundle(struct repository *r, struct bundle_header *header,
+ int bundle_fd, int flags)
{
const char *argv_index_pack[] = {"index-pack",
"--fix-thin", "--stdin", NULL, NULL};
if (flags & BUNDLE_VERBOSE)
argv_index_pack[3] = "-v";
- if (verify_bundle(header, 0))
+ if (verify_bundle(r, header, 0))
return -1;
ip.argv = argv_index_pack;
ip.in = bundle_fd;
int is_bundle(const char *path, int quiet);
int read_bundle_header(const char *path, struct bundle_header *header);
-int create_bundle(struct bundle_header *header, const char *path,
- int argc, const char **argv);
-int verify_bundle(struct bundle_header *header, int verbose);
+int create_bundle(struct repository *r, const char *path,
+ int argc, const char **argv);
+int verify_bundle(struct repository *r, struct bundle_header *header, int verbose);
#define BUNDLE_VERBOSE 1
-int unbundle(struct bundle_header *header, int bundle_fd, int flags);
+int unbundle(struct repository *r, struct bundle_header *header,
+ int bundle_fd, int flags);
int list_bundle_refs(struct bundle_header *header,
int argc, const char **argv);
}
static void write_one(struct strbuf *buffer, struct cache_tree *it,
- const char *path, int pathlen)
+ const char *path, int pathlen)
{
int i;
return ret;
}
-static void prime_cache_tree_rec(struct cache_tree *it, struct tree *tree)
+static void prime_cache_tree_rec(struct repository *r,
+ struct cache_tree *it,
+ struct tree *tree)
{
struct tree_desc desc;
struct name_entry entry;
cnt++;
else {
struct cache_tree_sub *sub;
- struct tree *subtree = lookup_tree(the_repository,
- entry.oid);
+ struct tree *subtree = lookup_tree(r, &entry.oid);
if (!subtree->object.parsed)
parse_tree(subtree);
sub = cache_tree_sub(it, entry.path);
sub->cache_tree = cache_tree();
- prime_cache_tree_rec(sub->cache_tree, subtree);
+ prime_cache_tree_rec(r, sub->cache_tree, subtree);
cnt += sub->cache_tree->entry_count;
}
}
it->entry_count = cnt;
}
-void prime_cache_tree(struct index_state *istate, struct tree *tree)
+void prime_cache_tree(struct repository *r,
+ struct index_state *istate,
+ struct tree *tree)
{
cache_tree_free(&istate->cache_tree);
istate->cache_tree = cache_tree();
- prime_cache_tree_rec(istate->cache_tree, tree);
+ prime_cache_tree_rec(r, istate->cache_tree, tree);
istate->cache_changed |= CACHE_TREE_CHANGED;
}
it = find_cache_tree_from_traversal(root, info);
it = cache_tree_find(it, ent->path);
- if (it && it->entry_count > 0 && oideq(ent->oid, &it->oid))
+ if (it && it->entry_count > 0 && oideq(&ent->oid, &it->oid))
return it->entry_count;
return 0;
}
-static void verify_one(struct index_state *istate,
+static void verify_one(struct repository *r,
+ struct index_state *istate,
struct cache_tree *it,
struct strbuf *path)
{
for (i = 0; i < it->subtree_nr; i++) {
strbuf_addf(path, "%s/", it->down[i]->name);
- verify_one(istate, it->down[i]->cache_tree, path);
+ verify_one(r, istate, it->down[i]->cache_tree, path);
strbuf_setlen(path, len);
}
if (it->entry_count < 0 ||
/* no verification on tests (t7003) that replace trees */
- lookup_replace_object(the_repository, &it->oid) != &it->oid)
+ lookup_replace_object(r, &it->oid) != &it->oid)
return;
if (path->len) {
strbuf_release(&tree_buf);
}
-void cache_tree_verify(struct index_state *istate)
+void cache_tree_verify(struct repository *r, struct index_state *istate)
{
struct strbuf path = STRBUF_INIT;
if (!istate->cache_tree)
return;
- verify_one(istate, istate->cache_tree, &path);
+ verify_one(r, istate, istate->cache_tree, &path);
strbuf_release(&path);
}
int cache_tree_fully_valid(struct cache_tree *);
int cache_tree_update(struct index_state *, int);
-void cache_tree_verify(struct index_state *);
+void cache_tree_verify(struct repository *, struct index_state *);
/* bitmasks to write_cache_as_tree flags */
#define WRITE_TREE_MISSING_OK 1
#define WRITE_TREE_PREFIX_ERROR (-3)
int write_index_as_tree(struct object_id *oid, struct index_state *index_state, const char *index_path, int flags, const char *prefix);
-void prime_cache_tree(struct index_state *, struct tree *);
+void prime_cache_tree(struct repository *, struct index_state *, struct tree *);
int cache_tree_matches_traversal(struct cache_tree *, struct name_entry *ent, struct traverse_info *info);
-#ifndef NO_THE_INDEX_COMPATIBILITY_MACROS
+#ifdef USE_THE_INDEX_COMPATIBILITY_MACROS
static inline int write_cache_as_tree(struct object_id *oid, int flags, const char *prefix)
{
return write_index_as_tree(oid, &the_index, get_index_file(), flags, prefix);
/* The length in bytes and in hex digits of an object name (SHA-1 value). */
#define GIT_SHA1_RAWSZ 20
#define GIT_SHA1_HEXSZ (2 * GIT_SHA1_RAWSZ)
+/* The block size of SHA-1. */
+#define GIT_SHA1_BLKSZ 64
+
+/* The length in bytes and in hex digits of an object name (SHA-256 value). */
+#define GIT_SHA256_RAWSZ 32
+#define GIT_SHA256_HEXSZ (2 * GIT_SHA256_RAWSZ)
+/* The block size of SHA-256. */
+#define GIT_SHA256_BLKSZ 64
/* The length in byte and in hex digits of the largest possible hash value. */
-#define GIT_MAX_RAWSZ GIT_SHA1_RAWSZ
-#define GIT_MAX_HEXSZ GIT_SHA1_HEXSZ
+#define GIT_MAX_RAWSZ GIT_SHA256_RAWSZ
+#define GIT_MAX_HEXSZ GIT_SHA256_HEXSZ
+/* The largest possible block size for any supported hash. */
+#define GIT_MAX_BLKSZ GIT_SHA256_BLKSZ
struct object_id {
unsigned char hash[GIT_MAX_RAWSZ];
struct mem_pool *ce_mem_pool;
};
-extern struct index_state the_index;
-
/* Name hashing */
extern int test_lazy_init_name_hash(struct index_state *istate, int try_threaded);
extern void add_name_hash(struct index_state *istate, struct cache_entry *ce);
*/
void validate_cache_entries(const struct index_state *istate);
-#ifndef NO_THE_INDEX_COMPATIBILITY_MACROS
+#ifdef USE_THE_INDEX_COMPATIBILITY_MACROS
+extern struct index_state the_index;
+
#define active_cache (the_index.cache)
#define active_nr (the_index.cache_nr)
#define active_alloc (the_index.cache_alloc)
#define active_cache_changed (the_index.cache_changed)
#define active_cache_tree (the_index.cache_tree)
-#define read_cache() read_index(&the_index)
+#define read_cache() repo_read_index(the_repository)
#define read_cache_from(path) read_index_from(&the_index, (path), (get_git_dir()))
-#define read_cache_preload(pathspec) read_index_preload(&the_index, (pathspec), 0)
+#define read_cache_preload(pathspec) repo_read_index_preload(the_repository, (pathspec), 0)
#define is_cache_unborn() is_index_unborn(&the_index)
-#define read_cache_unmerged() read_index_unmerged(&the_index)
+#define read_cache_unmerged() repo_read_index_unmerged(the_repository)
#define discard_cache() discard_index(&the_index)
#define unmerged_cache() unmerged_index(&the_index)
#define cache_name_pos(name, namelen) index_name_pos(&the_index,(name),(namelen))
#define unmerge_cache_entry_at(at) unmerge_index_entry_at(&the_index, at)
#define unmerge_cache(pathspec) unmerge_index(&the_index, pathspec)
#define read_blob_data_from_cache(path, sz) read_blob_data_from_index(&the_index, (path), (sz))
+#define hold_locked_index(lock_file, flags) repo_hold_locked_index(the_repository, (lock_file), (flags))
#endif
#define TYPE_BITS 3
/* Initialize and use the cache information */
struct lock_file;
-extern int read_index(struct index_state *);
extern void preload_index(struct index_state *index,
const struct pathspec *pathspec,
unsigned int refresh_flags);
-extern int read_index_preload(struct index_state *,
- const struct pathspec *pathspec,
- unsigned int refresh_flags);
extern int do_read_index(struct index_state *istate, const char *path,
int must_exist); /* for testting only! */
extern int read_index_from(struct index_state *, const char *path,
const char *gitdir);
extern int is_index_unborn(struct index_state *);
-extern int read_index_unmerged(struct index_state *);
/* For use with `write_locked_index()`. */
#define COMMIT_LOCK (1 << 0)
* provided, the space-separated list of files that differ will be appended
* to it.
*/
-extern int index_has_changes(struct index_state *istate,
- struct tree *tree,
- struct strbuf *sb);
+extern int repo_index_has_changes(struct repository *repo,
+ struct tree *tree,
+ struct strbuf *sb);
extern int verify_path(const char *path, unsigned mode);
extern int strcmp_offset(const char *s1, const char *s2, size_t *first_change);
#define ADD_CACHE_JUST_APPEND 8 /* Append only; tree.c::read_tree() */
#define ADD_CACHE_NEW_ONLY 16 /* Do not replace existing ones */
#define ADD_CACHE_KEEP_CACHE_TREE 32 /* Do not invalidate cache-tree */
+#define ADD_CACHE_RENORMALIZE 64 /* Pass along HASH_RENORMALIZE */
extern int add_index_entry(struct index_state *, struct cache_entry *ce, int option);
extern void rename_index_entry_at(struct index_state *, int pos, const char *new_name);
extern int refresh_index(struct index_state *, unsigned int flags, const struct pathspec *pathspec, char *seen, const char *header_msg);
extern struct cache_entry *refresh_cache_entry(struct index_state *, struct cache_entry *, unsigned int);
-/*
- * Opportunistically update the index but do not complain if we can't.
- * The lockfile is always committed or rolled back.
- */
-extern void update_index_if_able(struct index_state *, struct lock_file *);
-
-extern int hold_locked_index(struct lock_file *, int);
extern void set_alternate_index_output(const char *);
extern int verify_index_checksum;
static inline int hashcmp(const unsigned char *sha1, const unsigned char *sha2)
{
/*
- * This is a temporary optimization hack. By asserting the size here,
- * we let the compiler know that it's always going to be 20, which lets
- * it turn this fixed-size memcmp into a few inline instructions.
- *
- * This will need to be extended or ripped out when we learn about
- * hashes of different sizes.
+ * Teach the compiler that there are only two possibilities of hash size
+ * here, so that it can optimize for this case as much as possible.
*/
- if (the_hash_algo->rawsz != 20)
- BUG("hash size not yet supported by hashcmp");
- return memcmp(sha1, sha2, the_hash_algo->rawsz);
+ if (the_hash_algo->rawsz == GIT_MAX_RAWSZ)
+ return memcmp(sha1, sha2, GIT_MAX_RAWSZ);
+ return memcmp(sha1, sha2, GIT_SHA1_RAWSZ);
}
static inline int oidcmp(const struct object_id *oid1, const struct object_id *oid2)
static inline int hasheq(const unsigned char *sha1, const unsigned char *sha2)
{
- return !hashcmp(sha1, sha2);
+ /*
+ * We write this here instead of deferring to hashcmp so that the
+ * compiler can properly inline it and avoid calling memcmp.
+ */
+ if (the_hash_algo->rawsz == GIT_MAX_RAWSZ)
+ return !memcmp(sha1, sha2, GIT_MAX_RAWSZ);
+ return !memcmp(sha1, sha2, GIT_SHA1_RAWSZ);
}
static inline int oideq(const struct object_id *oid1, const struct object_id *oid2)
static inline void oidcpy(struct object_id *dst, const struct object_id *src)
{
- hashcpy(dst->hash, src->hash);
+ memcpy(dst->hash, src->hash, GIT_MAX_RAWSZ);
}
static inline struct object_id *oiddup(const struct object_id *src)
GET_OID_TREE | GET_OID_TREEISH | \
GET_OID_BLOB)
+enum get_oid_result {
+ FOUND = 0,
+ MISSING_OBJECT = -1, /* The requested object is missing */
+ SHORT_NAME_AMBIGUOUS = -2,
+ /* The following only apply when symlinks are followed */
+ DANGLING_SYMLINK = -4, /*
+ * The initial symlink is there, but
+ * (transitively) points to a missing
+ * in-tree file
+ */
+ SYMLINK_LOOP = -5,
+ NOT_DIR = -6, /*
+ * Somewhere along the symlink chain, a path is
+ * requested which contains a file as a
+ * non-final element.
+ */
+};
+
extern int get_oid(const char *str, struct object_id *oid);
extern int get_oid_commit(const char *str, struct object_id *oid);
extern int get_oid_committish(const char *str, struct object_id *oid);
extern int get_oid_treeish(const char *str, struct object_id *oid);
extern int get_oid_blob(const char *str, struct object_id *oid);
extern void maybe_die_on_misspelt_object_name(const char *name, const char *prefix);
-extern int get_oid_with_context(const char *str, unsigned flags, struct object_id *oid, struct object_context *oc);
-
+extern enum get_oid_result get_oid_with_context(struct repository *repo, const char *str,
+ unsigned flags, struct object_id *oid,
+ struct object_context *oc);
typedef int each_abbrev_fn(const struct object_id *oid, void *);
extern int for_each_abbrev(const char *prefix, each_abbrev_fn, void *);
extern int hex_to_bytes(unsigned char *binary, const char *hex, size_t len);
/*
- * Convert a binary sha1 to its hex equivalent. The `_r` variant is reentrant,
+ * Convert a binary hash to its hex equivalent. The `_r` variant is reentrant,
* and writes the NUL-terminated output to the buffer `out`, which must be at
- * least `GIT_SHA1_HEXSZ + 1` bytes, and returns a pointer to out for
+ * least `GIT_MAX_HEXSZ + 1` bytes, and returns a pointer to out for
* convenience.
*
* The non-`_r` variant returns a static buffer, but uses a ring of 4
*
* printf("%s -> %s", sha1_to_hex(one), sha1_to_hex(two));
*/
-extern char *sha1_to_hex_r(char *out, const unsigned char *sha1);
-extern char *oid_to_hex_r(char *out, const struct object_id *oid);
-extern char *sha1_to_hex(const unsigned char *sha1); /* static buffer result! */
-extern char *oid_to_hex(const struct object_id *oid); /* same static buffer as sha1_to_hex */
+char *hash_to_hex_algop_r(char *buffer, const unsigned char *hash, const struct git_hash_algo *);
+char *sha1_to_hex_r(char *out, const unsigned char *sha1);
+char *oid_to_hex_r(char *out, const struct object_id *oid);
+char *hash_to_hex_algop(const unsigned char *hash, const struct git_hash_algo *); /* static buffer result! */
+char *sha1_to_hex(const unsigned char *sha1); /* same static buffer */
+char *hash_to_hex(const unsigned char *hash); /* same static buffer */
+char *oid_to_hex(const struct object_id *oid); /* same static buffer */
/*
* Parse a 40-character hexadecimal object ID starting from hex, updating the
enum date_mode_type {
DATE_NORMAL = 0,
+ DATE_HUMAN,
DATE_RELATIVE,
DATE_SHORT,
DATE_ISO8601,
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, int tz, const struct timeval *now,
+void show_date_relative(timestamp_t time, const struct timeval *now,
+ 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);
#define CHECKOUT_INIT { NULL, "" }
#define TEMPORARY_FILENAME_LENGTH 25
-extern int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *topath);
+extern int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *topath, int *nr_checkouts);
extern void enable_delayed_checkout(struct checkout *state);
-extern int finish_delayed_checkout(struct checkout *state);
+extern int finish_delayed_checkout(struct checkout *state, int *nr_checkouts);
struct cache_def {
struct strbuf path;
*/
extern int print_sha1_ellipsis(void);
+/* Return 1 if the file is empty or does not exists, 0 otherwise. */
+extern int is_empty_or_missing_file(const char *filename);
+
#endif /* CACHE_H */
};
/* return length of 's' in letters, ANSI escapes stripped */
-static int item_length(unsigned int colopts, const char *s)
+static int item_length(const char *s)
{
int len, i = 0;
struct strbuf str = STRBUF_INIT;
ALLOC_ARRAY(data.len, list->nr);
for (i = 0; i < list->nr; i++)
- data.len[i] = item_length(colopts, list->items[i].string);
+ data.len[i] = item_length(list->items[i].string);
layout(&data, &initial_width);
if (!userdiff)
userdiff = userdiff_find_by_name("default");
if (opt->flags.allow_textconv)
- textconv = userdiff_get_textconv(userdiff);
+ textconv = userdiff_get_textconv(opt->repo, userdiff);
/* Read the result of merge first */
if (!working_tree_file)
return path->path;
}
+/*
+ * Diff stat formats which we always compute solely against the first parent.
+ */
+#define STAT_FORMAT_MASK (DIFF_FORMAT_NUMSTAT \
+ | DIFF_FORMAT_SHORTSTAT \
+ | DIFF_FORMAT_SUMMARY \
+ | DIFF_FORMAT_DIRSTAT \
+ | DIFF_FORMAT_DIFFSTAT)
/* find set of paths that every parent touches */
static struct combine_diff_path *find_paths_generic(const struct object_id *oid,
* show stat against the first parent even when doing
* combined diff.
*/
- int stat_opt = (output_format &
- (DIFF_FORMAT_NUMSTAT|DIFF_FORMAT_DIFFSTAT));
+ int stat_opt = output_format & STAT_FORMAT_MASK;
if (i == 0 && stat_opt)
opt->output_format = stat_opt;
else
* show stat against the first parent even
* when doing combined diff.
*/
- stat_opt = (opt->output_format &
- (DIFF_FORMAT_NUMSTAT|DIFF_FORMAT_DIFFSTAT));
+ stat_opt = opt->output_format & STAT_FORMAT_MASK;
if (stat_opt) {
diffopts.output_format = stat_opt;
show_raw_diff(p, num_parent, rev);
needsep = 1;
}
- else if (opt->output_format &
- (DIFF_FORMAT_NUMSTAT|DIFF_FORMAT_DIFFSTAT))
+ else if (opt->output_format & STAT_FORMAT_MASK)
needsep = 1;
else if (opt->output_format & DIFF_FORMAT_CALLBACK)
handle_combined_callback(opt, paths, num_parent, num_paths);
#define GRAPH_CHUNKID_OIDFANOUT 0x4f494446 /* "OIDF" */
#define GRAPH_CHUNKID_OIDLOOKUP 0x4f49444c /* "OIDL" */
#define GRAPH_CHUNKID_DATA 0x43444154 /* "CDAT" */
-#define GRAPH_CHUNKID_LARGEEDGES 0x45444745 /* "EDGE" */
+#define GRAPH_CHUNKID_EXTRAEDGES 0x45444745 /* "EDGE" */
-#define GRAPH_DATA_WIDTH 36
+#define GRAPH_DATA_WIDTH (the_hash_algo->rawsz + 16)
#define GRAPH_VERSION_1 0x1
#define GRAPH_VERSION GRAPH_VERSION_1
-#define GRAPH_OID_VERSION_SHA1 1
-#define GRAPH_OID_LEN_SHA1 GIT_SHA1_RAWSZ
-#define GRAPH_OID_VERSION GRAPH_OID_VERSION_SHA1
-#define GRAPH_OID_LEN GRAPH_OID_LEN_SHA1
-
-#define GRAPH_OCTOPUS_EDGES_NEEDED 0x80000000
-#define GRAPH_PARENT_MISSING 0x7fffffff
+#define GRAPH_EXTRA_EDGES_NEEDED 0x80000000
#define GRAPH_EDGE_LAST_MASK 0x7fffffff
#define GRAPH_PARENT_NONE 0x70000000
#define GRAPH_FANOUT_SIZE (4 * 256)
#define GRAPH_CHUNKLOOKUP_WIDTH 12
#define GRAPH_MIN_SIZE (GRAPH_HEADER_SIZE + 4 * GRAPH_CHUNKLOOKUP_WIDTH \
- + GRAPH_FANOUT_SIZE + GRAPH_OID_LEN)
+ + GRAPH_FANOUT_SIZE + the_hash_algo->rawsz)
char *get_commit_graph_filename(const char *obj_dir)
{
return xstrfmt("%s/info/commit-graph", obj_dir);
}
+static uint8_t oid_version(void)
+{
+ return 1;
+}
+
static struct commit_graph *alloc_commit_graph(void)
{
struct commit_graph *g = xcalloc(1, sizeof(*g));
struct commit_graph *load_commit_graph_one(const char *graph_file)
{
void *graph_map;
- const unsigned char *data, *chunk_lookup;
size_t graph_size;
struct stat st;
- uint32_t i;
- struct commit_graph *graph;
+ struct commit_graph *ret;
int fd = git_open(graph_file);
- uint64_t last_chunk_offset;
- uint32_t last_chunk_id;
- uint32_t graph_signature;
- unsigned char graph_version, hash_version;
if (fd < 0)
return NULL;
die(_("graph file %s is too small"), graph_file);
}
graph_map = xmmap(NULL, graph_size, PROT_READ, MAP_PRIVATE, fd, 0);
+ ret = parse_commit_graph(graph_map, fd, graph_size);
+
+ if (!ret) {
+ munmap(graph_map, graph_size);
+ close(fd);
+ exit(1);
+ }
+
+ return ret;
+}
+
+struct commit_graph *parse_commit_graph(void *graph_map, int fd,
+ size_t graph_size)
+{
+ const unsigned char *data, *chunk_lookup;
+ uint32_t i;
+ struct commit_graph *graph;
+ uint64_t last_chunk_offset;
+ uint32_t last_chunk_id;
+ uint32_t graph_signature;
+ unsigned char graph_version, hash_version;
+
+ if (!graph_map)
+ return NULL;
+
+ if (graph_size < GRAPH_MIN_SIZE)
+ return NULL;
+
data = (const unsigned char *)graph_map;
graph_signature = get_be32(data);
if (graph_signature != GRAPH_SIGNATURE) {
error(_("graph signature %X does not match signature %X"),
graph_signature, GRAPH_SIGNATURE);
- goto cleanup_fail;
+ return NULL;
}
graph_version = *(unsigned char*)(data + 4);
if (graph_version != GRAPH_VERSION) {
error(_("graph version %X does not match version %X"),
graph_version, GRAPH_VERSION);
- goto cleanup_fail;
+ return NULL;
}
hash_version = *(unsigned char*)(data + 5);
- if (hash_version != GRAPH_OID_VERSION) {
+ if (hash_version != oid_version()) {
error(_("hash version %X does not match version %X"),
- hash_version, GRAPH_OID_VERSION);
- goto cleanup_fail;
+ hash_version, oid_version());
+ return NULL;
}
graph = alloc_commit_graph();
- graph->hash_len = GRAPH_OID_LEN;
+ graph->hash_len = the_hash_algo->rawsz;
graph->num_chunks = *(unsigned char*)(data + 6);
graph->graph_fd = fd;
graph->data = graph_map;
last_chunk_offset = 8;
chunk_lookup = data + 8;
for (i = 0; i < graph->num_chunks; i++) {
- uint32_t chunk_id = get_be32(chunk_lookup + 0);
- uint64_t chunk_offset = get_be64(chunk_lookup + 4);
+ uint32_t chunk_id;
+ uint64_t chunk_offset;
int chunk_repeated = 0;
+ if (data + graph_size - chunk_lookup <
+ GRAPH_CHUNKLOOKUP_WIDTH) {
+ error(_("chunk lookup table entry missing; graph file may be incomplete"));
+ free(graph);
+ return NULL;
+ }
+
+ chunk_id = get_be32(chunk_lookup + 0);
+ chunk_offset = get_be64(chunk_lookup + 4);
+
chunk_lookup += GRAPH_CHUNKLOOKUP_WIDTH;
- if (chunk_offset > graph_size - GIT_MAX_RAWSZ) {
+ if (chunk_offset > graph_size - the_hash_algo->rawsz) {
error(_("improper chunk offset %08x%08x"), (uint32_t)(chunk_offset >> 32),
(uint32_t)chunk_offset);
- goto cleanup_fail;
+ free(graph);
+ return NULL;
}
switch (chunk_id) {
graph->chunk_commit_data = data + chunk_offset;
break;
- case GRAPH_CHUNKID_LARGEEDGES:
- if (graph->chunk_large_edges)
+ case GRAPH_CHUNKID_EXTRAEDGES:
+ if (graph->chunk_extra_edges)
chunk_repeated = 1;
else
- graph->chunk_large_edges = data + chunk_offset;
+ graph->chunk_extra_edges = data + chunk_offset;
break;
}
if (chunk_repeated) {
error(_("chunk id %08x appears multiple times"), chunk_id);
- goto cleanup_fail;
+ free(graph);
+ return NULL;
}
if (last_chunk_id == GRAPH_CHUNKID_OIDLOOKUP)
}
return graph;
-
-cleanup_fail:
- munmap(graph_map, graph_size);
- close(fd);
- exit(1);
}
static void prepare_commit_graph_one(struct repository *r, const char *obj_dir)
*/
static int prepare_commit_graph(struct repository *r)
{
- struct alternate_object_database *alt;
- char *obj_dir;
+ struct object_directory *odb;
int config_value;
if (r->objects->commit_graph_attempted)
if (!commit_graph_compatible(r))
return 0;
- obj_dir = r->objects->objectdir;
- prepare_commit_graph_one(r, obj_dir);
prepare_alt_odb(r);
- for (alt = r->objects->alt_odb_list;
- !r->objects->commit_graph && alt;
- alt = alt->next)
- prepare_commit_graph_one(r, alt->path);
+ for (odb = r->objects->odb;
+ !r->objects->commit_graph && odb;
+ odb = odb->next)
+ prepare_commit_graph_one(r, odb->path);
return !!r->objects->commit_graph;
}
g->chunk_oid_lookup, g->hash_len, pos);
}
-static struct commit_list **insert_parent_or_die(struct commit_graph *g,
+static struct commit_list **insert_parent_or_die(struct repository *r,
+ struct commit_graph *g,
uint64_t pos,
struct commit_list **pptr)
{
die("invalid parent position %"PRIu64, pos);
hashcpy(oid.hash, g->chunk_oid_lookup + g->hash_len * pos);
- c = lookup_commit(the_repository, &oid);
+ c = lookup_commit(r, &oid);
if (!c)
die(_("could not find commit %s"), oid_to_hex(&oid));
c->graph_pos = pos;
item->generation = get_be32(commit_data + g->hash_len + 8) >> 2;
}
-static int fill_commit_in_graph(struct commit *item, struct commit_graph *g, uint32_t pos)
+static int fill_commit_in_graph(struct repository *r,
+ struct commit *item,
+ struct commit_graph *g, uint32_t pos)
{
uint32_t edge_value;
uint32_t *parent_data_ptr;
edge_value = get_be32(commit_data + g->hash_len);
if (edge_value == GRAPH_PARENT_NONE)
return 1;
- pptr = insert_parent_or_die(g, edge_value, pptr);
+ pptr = insert_parent_or_die(r, g, edge_value, pptr);
edge_value = get_be32(commit_data + g->hash_len + 4);
if (edge_value == GRAPH_PARENT_NONE)
return 1;
- if (!(edge_value & GRAPH_OCTOPUS_EDGES_NEEDED)) {
- pptr = insert_parent_or_die(g, edge_value, pptr);
+ if (!(edge_value & GRAPH_EXTRA_EDGES_NEEDED)) {
+ pptr = insert_parent_or_die(r, g, edge_value, pptr);
return 1;
}
- parent_data_ptr = (uint32_t*)(g->chunk_large_edges +
+ parent_data_ptr = (uint32_t*)(g->chunk_extra_edges +
4 * (uint64_t)(edge_value & GRAPH_EDGE_LAST_MASK));
do {
edge_value = get_be32(parent_data_ptr);
- pptr = insert_parent_or_die(g,
+ pptr = insert_parent_or_die(r, g,
edge_value & GRAPH_EDGE_LAST_MASK,
pptr);
parent_data_ptr++;
}
}
-static int parse_commit_in_graph_one(struct commit_graph *g, struct commit *item)
+static int parse_commit_in_graph_one(struct repository *r,
+ struct commit_graph *g,
+ struct commit *item)
{
uint32_t pos;
return 1;
if (find_commit_in_graph(item, g, &pos))
- return fill_commit_in_graph(item, g, pos);
+ return fill_commit_in_graph(r, item, g, pos);
return 0;
}
{
if (!prepare_commit_graph(r))
return 0;
- return parse_commit_in_graph_one(r->objects->commit_graph, item);
+ return parse_commit_in_graph_one(r, r->objects->commit_graph, item);
}
void load_commit_graph_info(struct repository *r, struct commit *item)
fill_commit_graph_info(item, r->objects->commit_graph, pos);
}
-static struct tree *load_tree_for_commit(struct commit_graph *g, struct commit *c)
+static struct tree *load_tree_for_commit(struct repository *r,
+ struct commit_graph *g,
+ struct commit *c)
{
struct object_id oid;
const unsigned char *commit_data = g->chunk_commit_data +
GRAPH_DATA_WIDTH * (c->graph_pos);
hashcpy(oid.hash, commit_data);
- c->maybe_tree = lookup_tree(the_repository, &oid);
+ c->maybe_tree = lookup_tree(r, &oid);
return c->maybe_tree;
}
-static struct tree *get_commit_tree_in_graph_one(struct commit_graph *g,
+static struct tree *get_commit_tree_in_graph_one(struct repository *r,
+ struct commit_graph *g,
const struct commit *c)
{
if (c->maybe_tree)
if (c->graph_pos == COMMIT_NOT_FROM_GRAPH)
BUG("get_commit_tree_in_graph_one called from non-commit-graph commit");
- return load_tree_for_commit(g, (struct commit *)c);
+ return load_tree_for_commit(r, g, (struct commit *)c);
}
struct tree *get_commit_tree_in_graph(struct repository *r, const struct commit *c)
{
- return get_commit_tree_in_graph_one(r->objects->commit_graph, c);
+ return get_commit_tree_in_graph_one(r, r->objects->commit_graph, c);
}
static void write_graph_chunk_fanout(struct hashfile *f,
struct commit **commits,
- int nr_commits)
+ int nr_commits,
+ struct progress *progress,
+ uint64_t *progress_cnt)
{
int i, count = 0;
struct commit **list = commits;
while (count < nr_commits) {
if ((*list)->object.oid.hash[0] != i)
break;
+ display_progress(progress, ++*progress_cnt);
count++;
list++;
}
}
static void write_graph_chunk_oids(struct hashfile *f, int hash_len,
- struct commit **commits, int nr_commits)
+ struct commit **commits, int nr_commits,
+ struct progress *progress,
+ uint64_t *progress_cnt)
{
struct commit **list = commits;
int count;
- for (count = 0; count < nr_commits; count++, list++)
+ for (count = 0; count < nr_commits; count++, list++) {
+ display_progress(progress, ++*progress_cnt);
hashwrite(f, (*list)->object.oid.hash, (int)hash_len);
+ }
}
static const unsigned char *commit_to_sha1(size_t index, void *table)
}
static void write_graph_chunk_data(struct hashfile *f, int hash_len,
- struct commit **commits, int nr_commits)
+ struct commit **commits, int nr_commits,
+ struct progress *progress,
+ uint64_t *progress_cnt)
{
struct commit **list = commits;
struct commit **last = commits + nr_commits;
struct commit_list *parent;
int edge_value;
uint32_t packedDate[2];
+ display_progress(progress, ++*progress_cnt);
parse_commit(*list);
hashwrite(f, get_commit_tree_oid(*list)->hash, hash_len);
commit_to_sha1);
if (edge_value < 0)
- edge_value = GRAPH_PARENT_MISSING;
+ BUG("missing parent %s for commit %s",
+ oid_to_hex(&parent->item->object.oid),
+ oid_to_hex(&(*list)->object.oid));
}
hashwrite_be32(f, edge_value);
if (!parent)
edge_value = GRAPH_PARENT_NONE;
else if (parent->next)
- edge_value = GRAPH_OCTOPUS_EDGES_NEEDED | num_extra_edges;
+ edge_value = GRAPH_EXTRA_EDGES_NEEDED | num_extra_edges;
else {
edge_value = sha1_pos(parent->item->object.oid.hash,
commits,
nr_commits,
commit_to_sha1);
if (edge_value < 0)
- edge_value = GRAPH_PARENT_MISSING;
+ BUG("missing parent %s for commit %s",
+ oid_to_hex(&parent->item->object.oid),
+ oid_to_hex(&(*list)->object.oid));
}
hashwrite_be32(f, edge_value);
- if (edge_value & GRAPH_OCTOPUS_EDGES_NEEDED) {
+ if (edge_value & GRAPH_EXTRA_EDGES_NEEDED) {
do {
num_extra_edges++;
parent = parent->next;
}
}
-static void write_graph_chunk_large_edges(struct hashfile *f,
+static void write_graph_chunk_extra_edges(struct hashfile *f,
struct commit **commits,
- int nr_commits)
+ int nr_commits,
+ struct progress *progress,
+ uint64_t *progress_cnt)
{
struct commit **list = commits;
struct commit **last = commits + nr_commits;
while (list < last) {
int num_parents = 0;
+
+ display_progress(progress, ++*progress_cnt);
+
for (parent = (*list)->parents; num_parents < 3 && parent;
parent = parent->next)
num_parents++;
commit_to_sha1);
if (edge_value < 0)
- edge_value = GRAPH_PARENT_MISSING;
+ BUG("missing parent %s for commit %s",
+ oid_to_hex(&parent->item->object.oid),
+ oid_to_hex(&(*list)->object.oid));
else if (!parent->next)
edge_value |= GRAPH_LAST_EDGE;
int i;
struct commit *commit;
struct progress *progress = NULL;
- int j = 0;
if (report_progress)
progress = start_delayed_progress(
- _("Annotating commits in commit graph"), 0);
+ _("Loading known commits in commit graph"), oids->nr);
for (i = 0; i < oids->nr; i++) {
- display_progress(progress, ++j);
+ display_progress(progress, i + 1);
commit = lookup_commit(the_repository, &oids->list[i]);
if (commit)
commit->object.flags |= UNINTERESTING;
}
+ stop_progress(&progress);
/*
* As this loop runs, oids->nr may grow, but not more
* than the number of missing commits in the reachable
* closure.
*/
+ if (report_progress)
+ progress = start_delayed_progress(
+ _("Expanding reachable commits in commit graph"), oids->nr);
for (i = 0; i < oids->nr; i++) {
- display_progress(progress, ++j);
+ display_progress(progress, i + 1);
commit = lookup_commit(the_repository, &oids->list[i]);
if (commit && !parse_commit(commit))
add_missing_parents(oids, commit);
}
+ stop_progress(&progress);
+ if (report_progress)
+ progress = start_delayed_progress(
+ _("Clearing commit marks in commit graph"), oids->nr);
for (i = 0; i < oids->nr; i++) {
- display_progress(progress, ++j);
+ display_progress(progress, i + 1);
commit = lookup_commit(the_repository, &oids->list[i]);
if (commit)
int num_extra_edges;
struct commit_list *parent;
struct progress *progress = NULL;
+ const unsigned hashsz = the_hash_algo->rawsz;
+ uint64_t progress_cnt = 0;
+ struct strbuf progress_title = STRBUF_INIT;
+ unsigned long approx_nr_objects;
if (!commit_graph_compatible(the_repository))
return;
oids.nr = 0;
- oids.alloc = approximate_object_count() / 32;
+ approx_nr_objects = approximate_object_count();
+ oids.alloc = approx_nr_objects / 32;
oids.progress = NULL;
oids.progress_done = 0;
strbuf_addf(&packname, "%s/pack/", obj_dir);
dirlen = packname.len;
if (report_progress) {
- oids.progress = start_delayed_progress(
- _("Finding commits for commit graph"), 0);
+ strbuf_addf(&progress_title,
+ Q_("Finding commits for commit graph in %d pack",
+ "Finding commits for commit graph in %d packs",
+ pack_indexes->nr),
+ pack_indexes->nr);
+ oids.progress = start_delayed_progress(progress_title.buf, 0);
oids.progress_done = 0;
}
for (i = 0; i < pack_indexes->nr; i++) {
die(_("error adding pack %s"), packname.buf);
if (open_pack_index(p))
die(_("error opening index for %s"), packname.buf);
- for_each_object_in_pack(p, add_packed_commits, &oids, 0);
+ for_each_object_in_pack(p, add_packed_commits, &oids,
+ FOR_EACH_OBJECT_PACK_ORDER);
close_pack(p);
free(p);
}
stop_progress(&oids.progress);
+ strbuf_reset(&progress_title);
strbuf_release(&packname);
}
if (commit_hex) {
- if (report_progress)
- progress = start_delayed_progress(
- _("Finding commits for commit graph"),
- commit_hex->nr);
+ if (report_progress) {
+ strbuf_addf(&progress_title,
+ Q_("Finding commits for commit graph from %d ref",
+ "Finding commits for commit graph from %d refs",
+ commit_hex->nr),
+ commit_hex->nr);
+ progress = start_delayed_progress(progress_title.buf,
+ commit_hex->nr);
+ }
for (i = 0; i < commit_hex->nr; i++) {
const char *end;
struct object_id oid;
}
}
stop_progress(&progress);
+ strbuf_reset(&progress_title);
}
if (!pack_indexes && !commit_hex) {
if (report_progress)
oids.progress = start_delayed_progress(
- _("Finding commits for commit graph"), 0);
- for_each_packed_object(add_packed_commits, &oids, 0);
+ _("Finding commits for commit graph among packed objects"),
+ approx_nr_objects);
+ for_each_packed_object(add_packed_commits, &oids,
+ FOR_EACH_OBJECT_PACK_ORDER);
+ if (oids.progress_done < approx_nr_objects)
+ display_progress(oids.progress, approx_nr_objects);
stop_progress(&oids.progress);
}
close_reachable(&oids, report_progress);
+ if (report_progress)
+ progress = start_delayed_progress(
+ _("Counting distinct commits in commit graph"),
+ oids.nr);
+ display_progress(progress, 0); /* TODO: Measure QSORT() progress */
QSORT(oids.list, oids.nr, commit_compare);
-
count_distinct = 1;
for (i = 1; i < oids.nr; i++) {
+ display_progress(progress, i + 1);
if (!oideq(&oids.list[i - 1], &oids.list[i]))
count_distinct++;
}
+ stop_progress(&progress);
- if (count_distinct >= GRAPH_PARENT_MISSING)
+ if (count_distinct >= GRAPH_EDGE_LAST_MASK)
die(_("the commit graph format cannot write %d commits"), count_distinct);
commits.nr = 0;
ALLOC_ARRAY(commits.list, commits.alloc);
num_extra_edges = 0;
+ if (report_progress)
+ progress = start_delayed_progress(
+ _("Finding extra edges in commit graph"),
+ oids.nr);
for (i = 0; i < oids.nr; i++) {
int num_parents = 0;
+ display_progress(progress, i + 1);
if (i > 0 && oideq(&oids.list[i - 1], &oids.list[i]))
continue;
commits.nr++;
}
num_chunks = num_extra_edges ? 4 : 3;
+ stop_progress(&progress);
- if (commits.nr >= GRAPH_PARENT_MISSING)
+ if (commits.nr >= GRAPH_EDGE_LAST_MASK)
die(_("too many commits to write graph"));
compute_generation_numbers(&commits, report_progress);
hashwrite_be32(f, GRAPH_SIGNATURE);
hashwrite_u8(f, GRAPH_VERSION);
- hashwrite_u8(f, GRAPH_OID_VERSION);
+ hashwrite_u8(f, oid_version());
hashwrite_u8(f, num_chunks);
hashwrite_u8(f, 0); /* unused padding byte */
chunk_ids[1] = GRAPH_CHUNKID_OIDLOOKUP;
chunk_ids[2] = GRAPH_CHUNKID_DATA;
if (num_extra_edges)
- chunk_ids[3] = GRAPH_CHUNKID_LARGEEDGES;
+ chunk_ids[3] = GRAPH_CHUNKID_EXTRAEDGES;
else
chunk_ids[3] = 0;
chunk_ids[4] = 0;
chunk_offsets[0] = 8 + (num_chunks + 1) * GRAPH_CHUNKLOOKUP_WIDTH;
chunk_offsets[1] = chunk_offsets[0] + GRAPH_FANOUT_SIZE;
- chunk_offsets[2] = chunk_offsets[1] + GRAPH_OID_LEN * commits.nr;
- chunk_offsets[3] = chunk_offsets[2] + (GRAPH_OID_LEN + 16) * commits.nr;
+ chunk_offsets[2] = chunk_offsets[1] + hashsz * commits.nr;
+ chunk_offsets[3] = chunk_offsets[2] + (hashsz + 16) * commits.nr;
chunk_offsets[4] = chunk_offsets[3] + 4 * num_extra_edges;
for (i = 0; i <= num_chunks; i++) {
hashwrite(f, chunk_write, 12);
}
- write_graph_chunk_fanout(f, commits.list, commits.nr);
- write_graph_chunk_oids(f, GRAPH_OID_LEN, commits.list, commits.nr);
- write_graph_chunk_data(f, GRAPH_OID_LEN, commits.list, commits.nr);
- write_graph_chunk_large_edges(f, commits.list, commits.nr);
+ if (report_progress) {
+ strbuf_addf(&progress_title,
+ Q_("Writing out commit graph in %d pass",
+ "Writing out commit graph in %d passes",
+ num_chunks),
+ num_chunks);
+ progress = start_delayed_progress(
+ progress_title.buf,
+ num_chunks * commits.nr);
+ }
+ write_graph_chunk_fanout(f, commits.list, commits.nr, progress, &progress_cnt);
+ write_graph_chunk_oids(f, hashsz, commits.list, commits.nr, progress, &progress_cnt);
+ write_graph_chunk_data(f, hashsz, commits.list, commits.nr, progress, &progress_cnt);
+ if (num_extra_edges)
+ write_graph_chunk_extra_edges(f, commits.list, commits.nr, progress, &progress_cnt);
+ stop_progress(&progress);
+ strbuf_release(&progress_title);
close_commit_graph(the_repository);
finalize_hashfile(f, NULL, CSUM_HASH_IN_STREAM | CSUM_FSYNC);
}
graph_commit = lookup_commit(r, &cur_oid);
- if (!parse_commit_in_graph_one(g, graph_commit))
+ if (!parse_commit_in_graph_one(r, g, graph_commit))
graph_report("failed to parse %s from commit-graph",
oid_to_hex(&cur_oid));
}
continue;
}
- if (!oideq(&get_commit_tree_in_graph_one(g, graph_commit)->object.oid,
+ if (!oideq(&get_commit_tree_in_graph_one(r, g, graph_commit)->object.oid,
get_commit_tree_oid(odb_commit)))
graph_report("root tree OID for commit %s in commit-graph is %s != %s",
oid_to_hex(&cur_oid),
const uint32_t *chunk_oid_fanout;
const unsigned char *chunk_oid_lookup;
const unsigned char *chunk_commit_data;
- const unsigned char *chunk_large_edges;
+ const unsigned char *chunk_extra_edges;
};
struct commit_graph *load_commit_graph_one(const char *graph_file);
+struct commit_graph *parse_commit_graph(void *graph_map, int fd,
+ size_t graph_size);
+
/*
* Return 1 if and only if the repository has a commit-graph
* file and generation numbers are computed in that file.
}
/* all input commits in one and twos[] must have been parsed! */
-static struct commit_list *paint_down_to_common(struct commit *one, int n,
+static struct commit_list *paint_down_to_common(struct repository *r,
+ struct commit *one, int n,
struct commit **twos,
int min_generation)
{
parents = parents->next;
if ((p->object.flags & flags) == flags)
continue;
- if (parse_commit(p))
+ if (repo_parse_commit(r, p))
return NULL;
p->object.flags |= flags;
prio_queue_put(&queue, p);
return result;
}
-static struct commit_list *merge_bases_many(struct commit *one, int n, struct commit **twos)
+static struct commit_list *merge_bases_many(struct repository *r,
+ struct commit *one, int n,
+ struct commit **twos)
{
struct commit_list *list = NULL;
struct commit_list *result = NULL;
return commit_list_insert(one, &result);
}
- if (parse_commit(one))
+ if (repo_parse_commit(r, one))
return NULL;
for (i = 0; i < n; i++) {
- if (parse_commit(twos[i]))
+ if (repo_parse_commit(r, twos[i]))
return NULL;
}
- list = paint_down_to_common(one, n, twos, 0);
+ list = paint_down_to_common(r, one, n, twos, 0);
while (list) {
struct commit *commit = pop_commit(&list);
return ret;
}
-static int remove_redundant(struct commit **array, int cnt)
+static int remove_redundant(struct repository *r, struct commit **array, int cnt)
{
/*
* Some commit in the array may be an ancestor of
ALLOC_ARRAY(filled_index, cnt - 1);
for (i = 0; i < cnt; i++)
- parse_commit(array[i]);
+ repo_parse_commit(r, array[i]);
for (i = 0; i < cnt; i++) {
struct commit_list *common;
uint32_t min_generation = array[i]->generation;
if (array[j]->generation < min_generation)
min_generation = array[j]->generation;
}
- common = paint_down_to_common(array[i], filled, work,
- min_generation);
+ common = paint_down_to_common(r, array[i], filled,
+ work, min_generation);
if (array[i]->object.flags & PARENT2)
redundant[i] = 1;
for (j = 0; j < filled; j++)
return filled;
}
-static struct commit_list *get_merge_bases_many_0(struct commit *one,
+static struct commit_list *get_merge_bases_many_0(struct repository *r,
+ struct commit *one,
int n,
struct commit **twos,
int cleanup)
struct commit_list *result;
int cnt, i;
- result = merge_bases_many(one, n, twos);
+ result = merge_bases_many(r, one, n, twos);
for (i = 0; i < n; i++) {
if (one == twos[i])
return result;
clear_commit_marks(one, all_flags);
clear_commit_marks_many(n, twos, all_flags);
- cnt = remove_redundant(rslt, cnt);
+ cnt = remove_redundant(r, rslt, cnt);
result = NULL;
for (i = 0; i < cnt; i++)
commit_list_insert_by_date(rslt[i], &result);
return result;
}
-struct commit_list *get_merge_bases_many(struct commit *one,
- int n,
- struct commit **twos)
+struct commit_list *repo_get_merge_bases_many(struct repository *r,
+ struct commit *one,
+ int n,
+ struct commit **twos)
{
- return get_merge_bases_many_0(one, n, twos, 1);
+ return get_merge_bases_many_0(r, one, n, twos, 1);
}
-struct commit_list *get_merge_bases_many_dirty(struct commit *one,
- int n,
- struct commit **twos)
+struct commit_list *repo_get_merge_bases_many_dirty(struct repository *r,
+ struct commit *one,
+ int n,
+ struct commit **twos)
{
- return get_merge_bases_many_0(one, n, twos, 0);
+ return get_merge_bases_many_0(r, one, n, twos, 0);
}
-struct commit_list *get_merge_bases(struct commit *one, struct commit *two)
+struct commit_list *repo_get_merge_bases(struct repository *r,
+ struct commit *one,
+ struct commit *two)
{
- return get_merge_bases_many_0(one, 1, &two, 1);
+ return get_merge_bases_many_0(r, one, 1, &two, 1);
}
/*
/*
* Is "commit" an ancestor of one of the "references"?
*/
-int in_merge_bases_many(struct commit *commit, int nr_reference, struct commit **reference)
+int repo_in_merge_bases_many(struct repository *r, struct commit *commit,
+ int nr_reference, struct commit **reference)
{
struct commit_list *bases;
int ret = 0, i;
uint32_t min_generation = GENERATION_NUMBER_INFINITY;
- if (parse_commit(commit))
+ if (repo_parse_commit(r, commit))
return ret;
for (i = 0; i < nr_reference; i++) {
- if (parse_commit(reference[i]))
+ if (repo_parse_commit(r, reference[i]))
return ret;
if (reference[i]->generation < min_generation)
min_generation = reference[i]->generation;
if (commit->generation > min_generation)
return ret;
- bases = paint_down_to_common(commit, nr_reference, reference, commit->generation);
+ bases = paint_down_to_common(r, commit,
+ nr_reference, reference,
+ commit->generation);
if (commit->object.flags & PARENT2)
ret = 1;
clear_commit_marks(commit, all_flags);
/*
* Is "commit" an ancestor of (i.e. reachable from) the "reference"?
*/
-int in_merge_bases(struct commit *commit, struct commit *reference)
+int repo_in_merge_bases(struct repository *r,
+ struct commit *commit,
+ struct commit *reference)
{
- return in_merge_bases_many(commit, 1, &reference);
+ return repo_in_merge_bases_many(r, commit, 1, &reference);
}
struct commit_list *reduce_heads(struct commit_list *heads)
p->item->object.flags &= ~STALE;
}
}
- num_head = remove_redundant(array, num_head);
+ num_head = remove_redundant(the_repository, array, num_head);
for (i = 0; i < num_head; i++)
tail = &commit_list_insert(array[i], tail)->next;
free(array);
struct object_id;
struct object_array;
-struct commit_list *get_merge_bases_many(struct commit *one,
- int n,
- struct commit **twos);
-struct commit_list *get_merge_bases_many_dirty(struct commit *one,
- int n,
- struct commit **twos);
-struct commit_list *get_merge_bases(struct commit *one, struct commit *two);
-struct commit_list *get_octopus_merge_bases(struct commit_list *in);
-
+struct commit_list *repo_get_merge_bases(struct repository *r,
+ struct commit *rev1,
+ struct commit *rev2);
+struct commit_list *repo_get_merge_bases_many(struct repository *r,
+ struct commit *one, int n,
+ struct commit **twos);
/* To be used only when object flags after this call no longer matter */
-struct commit_list *get_merge_bases_many_dirty(struct commit *one, int n, struct commit **twos);
+struct commit_list *repo_get_merge_bases_many_dirty(struct repository *r,
+ struct commit *one, int n,
+ struct commit **twos);
+#ifndef NO_THE_REPOSITORY_COMPATIBILITY_MACROS
+#define get_merge_bases(r1, r2) repo_get_merge_bases(the_repository, r1, r2)
+#define get_merge_bases_many(one, n, two) repo_get_merge_bases_many(the_repository, one, n, two)
+#define get_merge_bases_many_dirty(one, n, twos) repo_get_merge_bases_many_dirty(the_repository, one, n, twos)
+#endif
+
+struct commit_list *get_octopus_merge_bases(struct commit_list *in);
int is_descendant_of(struct commit *commit, struct commit_list *with_commit);
-int in_merge_bases_many(struct commit *commit, int nr_reference, struct commit **reference);
-int in_merge_bases(struct commit *commit, struct commit *reference);
+int repo_in_merge_bases(struct repository *r,
+ struct commit *commit,
+ struct commit *reference);
+int repo_in_merge_bases_many(struct repository *r,
+ struct commit *commit,
+ int nr_reference, struct commit **reference);
+#ifndef NO_THE_REPOSITORY_COMPATIBILITY_MACROS
+#define in_merge_bases(c1, c2) repo_in_merge_bases(the_repository, c1, c2)
+#define in_merge_bases_many(c1, n, cs) repo_in_merge_bases_many(the_repository, c1, n, cs)
+#endif
/*
* Takes a list of commits and returns a new list where those
return v->buffer;
}
-const void *get_commit_buffer(const struct commit *commit, unsigned long *sizep)
+const void *repo_get_commit_buffer(struct repository *r,
+ const struct commit *commit,
+ unsigned long *sizep)
{
- const void *ret = get_cached_commit_buffer(the_repository, commit, sizep);
+ const void *ret = get_cached_commit_buffer(r, commit, sizep);
if (!ret) {
enum object_type type;
unsigned long size;
- ret = read_object_file(&commit->object.oid, &type, &size);
+ ret = repo_read_object_file(r, &commit->object.oid, &type, &size);
if (!ret)
die("cannot read commit object %s",
oid_to_hex(&commit->object.oid));
return ret;
}
-void unuse_commit_buffer(const struct commit *commit, const void *buffer)
+void repo_unuse_commit_buffer(struct repository *r,
+ const struct commit *commit,
+ const void *buffer)
{
struct commit_buffer *v = buffer_slab_peek(
- the_repository->parsed_objects->buffer_slab, commit);
+ r->parsed_objects->buffer_slab, commit);
if (!(v && v->buffer == buffer))
free((void *)buffer);
}
-void free_commit_buffer(struct commit *commit)
+void free_commit_buffer(struct parsed_object_pool *pool, struct commit *commit)
{
struct commit_buffer *v = buffer_slab_peek(
- the_repository->parsed_objects->buffer_slab, commit);
+ pool->buffer_slab, commit);
if (v) {
FREE_AND_NULL(v->buffer);
v->size = 0;
return &get_commit_tree(commit)->object.oid;
}
-void release_commit_memory(struct commit *c)
+void release_commit_memory(struct parsed_object_pool *pool, struct commit *c)
{
c->maybe_tree = NULL;
c->index = 0;
- free_commit_buffer(c);
+ free_commit_buffer(pool, c);
free_commit_list(c->parents);
- /* TODO: what about commit->util? */
c->object.parsed = 0;
}
return 0;
}
-int parse_commit_internal(struct commit *item, int quiet_on_missing, int use_commit_graph)
+int repo_parse_commit_internal(struct repository *r,
+ struct commit *item,
+ int quiet_on_missing,
+ int use_commit_graph)
{
enum object_type type;
void *buffer;
return -1;
if (item->object.parsed)
return 0;
- if (use_commit_graph && parse_commit_in_graph(the_repository, item))
+ if (use_commit_graph && parse_commit_in_graph(r, item))
return 0;
- buffer = read_object_file(&item->object.oid, &type, &size);
+ buffer = repo_read_object_file(r, &item->object.oid, &type, &size);
if (!buffer)
return quiet_on_missing ? -1 :
error("Could not read %s",
oid_to_hex(&item->object.oid));
}
- ret = parse_commit_buffer(the_repository, item, buffer, size, 0);
+ ret = parse_commit_buffer(r, item, buffer, size, 0);
if (save_commit_buffer && !ret) {
- set_commit_buffer(the_repository, item, buffer, size);
+ set_commit_buffer(r, item, buffer, size);
return 0;
}
free(buffer);
return ret;
}
-int parse_commit_gently(struct commit *item, int quiet_on_missing)
+int repo_parse_commit_gently(struct repository *r,
+ struct commit *item, int quiet_on_missing)
{
- return parse_commit_internal(item, quiet_on_missing, 1);
+ return repo_parse_commit_internal(r, item, quiet_on_missing, 1);
}
void parse_commit_or_die(struct commit *item)
struct commit *lookup_commit_or_die(const struct object_id *oid, const char *ref_name);
int parse_commit_buffer(struct repository *r, struct commit *item, const void *buffer, unsigned long size, int check_graph);
-int parse_commit_internal(struct commit *item, int quiet_on_missing, int use_commit_graph);
-int parse_commit_gently(struct commit *item, int quiet_on_missing);
-static inline int parse_commit(struct commit *item)
+int repo_parse_commit_internal(struct repository *r, struct commit *item,
+ int quiet_on_missing, int use_commit_graph);
+int repo_parse_commit_gently(struct repository *r,
+ struct commit *item,
+ int quiet_on_missing);
+static inline int repo_parse_commit(struct repository *r, struct commit *item)
{
- return parse_commit_gently(item, 0);
+ return repo_parse_commit_gently(r, item, 0);
}
+#ifndef NO_THE_REPOSITORY_COMPATIBILITY_MACROS
+#define parse_commit_internal(item, quiet, use) repo_parse_commit_internal(the_repository, item, quiet, use)
+#define parse_commit_gently(item, quiet) repo_parse_commit_gently(the_repository, item, quiet)
+#define parse_commit(item) repo_parse_commit(the_repository, item)
+#endif
+
void parse_commit_or_die(struct commit *item);
struct buffer_slab;
* from disk. The resulting memory should not be modified, and must be given
* to unuse_commit_buffer when the caller is done.
*/
-const void *get_commit_buffer(const struct commit *, unsigned long *size);
+const void *repo_get_commit_buffer(struct repository *r,
+ const struct commit *,
+ unsigned long *size);
+#ifndef NO_THE_REPOSITORY_COMPATIBILITY_MACROS
+#define get_commit_buffer(c, s) repo_get_commit_buffer(the_repository, c, s)
+#endif
/*
* Tell the commit subsytem that we are done with a particular commit buffer.
* from an earlier call to get_commit_buffer. The buffer may or may not be
* freed by this call; callers should not access the memory afterwards.
*/
-void unuse_commit_buffer(const struct commit *, const void *buffer);
+void repo_unuse_commit_buffer(struct repository *r,
+ const struct commit *,
+ const void *buffer);
+#ifndef NO_THE_REPOSITORY_COMPATIBILITY_MACROS
+#define unuse_commit_buffer(c, b) repo_unuse_commit_buffer(the_repository, c, b)
+#endif
/*
* Free any cached object buffer associated with the commit.
*/
-void free_commit_buffer(struct commit *);
+void free_commit_buffer(struct parsed_object_pool *pool, struct commit *);
struct tree *get_commit_tree(const struct commit *);
struct object_id *get_commit_tree_oid(const struct commit *);
* Release memory related to a commit, including the parent list and
* any cached object buffer.
*/
-void release_commit_memory(struct commit *c);
+void release_commit_memory(struct parsed_object_pool *pool, struct commit *c);
/*
* Disassociate any cached object buffer from the commit, but do not free it.
extern const char *logmsg_reencode(const struct commit *commit,
char **commit_encoding,
const char *output_encoding);
+const char *repo_logmsg_reencode(struct repository *r,
+ const struct commit *commit,
+ char **commit_encoding,
+ const char *output_encoding);
+#ifndef NO_THE_REPOSITORY_COMPATIBILITY_MACROS
+#define logmsg_reencode(c, enc, out) repo_logmsg_reencode(the_repository, c, enc, out)
+#endif
+
extern const char *skip_blank_lines(const char *msg);
/** Removes the first commit from a list sorted by date, and adds all
+++ /dev/null
-#include "../git-compat-util.h"
-#include "../cache.h"
-
-int cygwin_offset_1st_component(const char *path)
-{
- const char *pos = path;
- /* unc paths */
- if (is_dir_sep(pos[0]) && is_dir_sep(pos[1])) {
- /* skip server name */
- pos = strchr(pos + 2, '/');
- if (!pos)
- return 0; /* Error: malformed unc path */
-
- do {
- pos++;
- } while (*pos && pos[0] != '/');
- }
- return pos + is_dir_sep(*pos) - path;
-}
+++ /dev/null
-int cygwin_offset_1st_component(const char *path);
-#define offset_1st_component cygwin_offset_1st_component
#include "../cache.h"
#include "win32/lazyload.h"
#include "../config.h"
+#include "dir.h"
#define HCAST(type, handle) ((type)(intptr_t)handle)
return 0;
/* We cannot use basename(), as it would remove trailing slashes */
- mingw_skip_dos_drive_prefix((char **)&path);
+ win32_skip_dos_drive_prefix((char **)&path);
if (!*path)
return 0;
* See "Parsing C++ Command-Line Arguments" at Microsoft's Docs:
* https://docs.microsoft.com/en-us/cpp/cpp/parsing-cpp-command-line-arguments
*/
-static const char *quote_arg(const char *arg)
+static const char *quote_arg_msvc(const char *arg)
{
/* count chars to quote */
int len = 0, n = 0;
return q;
}
+#include "quote.h"
+
+static const char *quote_arg_msys2(const char *arg)
+{
+ struct strbuf buf = STRBUF_INIT;
+ const char *p2 = arg, *p;
+
+ for (p = arg; *p; p++) {
+ int ws = isspace(*p);
+ if (!ws && *p != '\\' && *p != '"' && *p != '{')
+ continue;
+ if (!buf.len)
+ strbuf_addch(&buf, '"');
+ if (p != p2)
+ strbuf_add(&buf, p2, p - p2);
+ if (!ws && *p != '{')
+ strbuf_addch(&buf, '\\');
+ p2 = p;
+ }
+
+ if (p == arg)
+ strbuf_addch(&buf, '"');
+ else if (!buf.len)
+ return arg;
+ else
+ strbuf_add(&buf, p2, p - p2),
+
+ strbuf_addch(&buf, '"');
+ return strbuf_detach(&buf, 0);
+}
+
static const char *parse_interpreter(const char *cmd)
{
static char buf[100];
static struct pinfo_t *pinfo = NULL;
CRITICAL_SECTION pinfo_cs;
+/* Used to match and chomp off path components */
+static inline int match_last_path_component(const char *path, size_t *len,
+ const char *component)
+{
+ size_t component_len = strlen(component);
+ if (*len < component_len + 1 ||
+ !is_dir_sep(path[*len - component_len - 1]) ||
+ fspathncmp(path + *len - component_len, component, component_len))
+ return 0;
+ *len -= component_len + 1;
+ /* chomp off repeated dir separators */
+ while (*len > 0 && is_dir_sep(path[*len - 1]))
+ (*len)--;
+ return 1;
+}
+
+static int is_msys2_sh(const char *cmd)
+{
+ if (cmd && !strcmp(cmd, "sh")) {
+ static int ret = -1;
+ char *p;
+
+ if (ret >= 0)
+ return ret;
+
+ p = path_lookup(cmd, 0);
+ if (!p)
+ ret = 0;
+ else {
+ size_t len = strlen(p);
+
+ ret = match_last_path_component(p, &len, "sh.exe") &&
+ match_last_path_component(p, &len, "bin") &&
+ match_last_path_component(p, &len, "usr");
+ free(p);
+ }
+ return ret;
+ }
+ return 0;
+}
+
static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaenv,
const char *dir,
int prepend_cmd, int fhin, int fhout, int fherr)
unsigned flags = CREATE_UNICODE_ENVIRONMENT;
BOOL ret;
HANDLE cons;
+ const char *(*quote_arg)(const char *arg) =
+ is_msys2_sh(*argv) ? quote_arg_msys2 : quote_arg_msvc;
do_unset_environment_variables();
return -1;
}
-int mingw_skip_dos_drive_prefix(char **path)
-{
- int ret = has_dos_drive_prefix(*path);
- *path += ret;
- return ret;
-}
-
-int mingw_offset_1st_component(const char *path)
-{
- char *pos = (char *)path;
-
- /* unc paths */
- if (!skip_dos_drive_prefix(&pos) &&
- is_dir_sep(pos[0]) && is_dir_sep(pos[1])) {
- /* skip server name */
- pos = strpbrk(pos + 2, "\\/");
- if (!pos)
- return 0; /* Error: malformed unc path */
-
- do {
- pos++;
- } while (*pos && !is_dir_sep(*pos));
- }
-
- return pos + is_dir_sep(*pos) - path;
-}
-
int xutftowcsn(wchar_t *wcs, const char *utfs, size_t wcslen, int utflen)
{
int upos = 0, wpos = 0;
* git specific compatibility
*/
-#define has_dos_drive_prefix(path) \
- (isalpha(*(path)) && (path)[1] == ':' ? 2 : 0)
-int mingw_skip_dos_drive_prefix(char **path);
-#define skip_dos_drive_prefix mingw_skip_dos_drive_prefix
-static inline int mingw_is_dir_sep(int c)
-{
- return c == '/' || c == '\\';
-}
-#define is_dir_sep mingw_is_dir_sep
-static inline char *mingw_find_last_dir_sep(const char *path)
-{
- char *ret = NULL;
- for (; *path; ++path)
- if (is_dir_sep(*path))
- ret = (char *)path;
- return ret;
-}
static inline void convert_slashes(char *path)
{
for (; *path; path++)
if (*path == '\\')
*path = '/';
}
-#define find_last_dir_sep mingw_find_last_dir_sep
-int mingw_offset_1st_component(const char *path);
-#define offset_1st_component mingw_offset_1st_component
#define PATH_SEP ';'
extern char *mingw_query_user_email(void);
#define query_user_email mingw_query_user_email
# define CALL_CHUNKFUN(h, size) \
(((h) -> use_extra_arg) \
- ? (*(h)->chunkfun) ((h)->extra_arg, (size)) \
- : (*(struct _obstack_chunk *(*) (long)) (h)->chunkfun) ((size)))
+ ? (*(h)->chunkfun.extra) ((h)->extra_arg, (size)) \
+ : (*(h)->chunkfun.plain) ((size)))
# define CALL_FREEFUN(h, old_chunk) \
do { \
if ((h) -> use_extra_arg) \
- (*(h)->freefun) ((h)->extra_arg, (old_chunk)); \
+ (*(h)->freefun.extra) ((h)->extra_arg, (old_chunk)); \
else \
- (*(void (*) (void *)) (h)->freefun) ((old_chunk)); \
+ (*(h)->freefun.plain) ((old_chunk)); \
} while (0)
\f
size = 4096 - extra;
}
- h->chunkfun = (struct _obstack_chunk * (*)(void *, long)) chunkfun;
- h->freefun = (void (*) (void *, struct _obstack_chunk *)) freefun;
+ h->chunkfun.plain = chunkfun;
+ h->freefun.plain = freefun;
h->chunk_size = size;
h->alignment_mask = alignment - 1;
h->use_extra_arg = 0;
size = 4096 - extra;
}
- h->chunkfun = (struct _obstack_chunk * (*)(void *,long)) chunkfun;
- h->freefun = (void (*) (void *, struct _obstack_chunk *)) freefun;
+ h->chunkfun.extra = (struct _obstack_chunk * (*)(void *,long)) chunkfun;
+ h->freefun.extra = (void (*) (void *, struct _obstack_chunk *)) freefun;
+
h->chunk_size = size;
h->alignment_mask = alignment - 1;
h->extra_arg = arg;
void *tempptr;
} temp; /* Temporary for some macros. */
int alignment_mask; /* Mask of alignment for each object. */
- /* These prototypes vary based on `use_extra_arg', and we use
- casts to the prototypeless function type in all assignments,
- but having prototypes here quiets -Wstrict-prototypes. */
- struct _obstack_chunk *(*chunkfun) (void *, long);
- void (*freefun) (void *, struct _obstack_chunk *);
+ /* These prototypes vary based on `use_extra_arg'. */
+ union {
+ void *(*plain) (long);
+ struct _obstack_chunk *(*extra) (void *, long);
+ } chunkfun;
+ union {
+ void (*plain) (void *);
+ void (*extra) (void *, struct _obstack_chunk *);
+ } freefun;
void *extra_arg; /* first arg for chunk alloc/dealloc funcs */
unsigned use_extra_arg:1; /* chunk alloc/dealloc funcs take extra arg */
unsigned maybe_empty_object:1;/* There is a possibility that the current
(void (*) (void *, void *)) (freefun), (arg))
#define obstack_chunkfun(h, newchunkfun) \
- ((h) -> chunkfun = (struct _obstack_chunk *(*)(void *, long)) (newchunkfun))
+ ((h)->chunkfun.extra = (struct _obstack_chunk *(*)(void *, long)) (newchunkfun))
#define obstack_freefun(h, newfreefun) \
- ((h) -> freefun = (void (*)(void *, struct _obstack_chunk *)) (newfreefun))
+ ((h)->freefun.extra = (void (*)(void *, struct _obstack_chunk *)) (newfreefun))
#define obstack_1grow_fast(h,achar) (*((h)->next_free)++ = (achar))
size_t namelen;
oldarg = argv[i];
if (has_non_ascii(oldarg, (size_t)-1, &namelen)) {
- newarg = reencode_string_iconv(oldarg, namelen, ic_precompose, NULL);
+ newarg = reencode_string_iconv(oldarg, namelen, ic_precompose, 0, NULL);
if (newarg)
argv[i] = newarg;
}
License along with the GNU C Library; if not, see
<http://www.gnu.org/licenses/>. */
+#if defined __TANDEM
+ /* This is currently duplicated from git-compat-utils.h */
+# ifdef NO_INTPTR_T
+ typedef long intptr_t;
+ typedef unsigned long uintptr_t;
+# endif
+#endif
+
static reg_errcode_t re_compile_internal (regex_t *preg, const char * pattern,
size_t length, reg_syntax_t syntax);
static void re_compile_fastmap_iter (regex_t *bufp,
--- /dev/null
+#include "../../git-compat-util.h"
+
+int win32_skip_dos_drive_prefix(char **path)
+{
+ int ret = has_dos_drive_prefix(*path);
+ *path += ret;
+ return ret;
+}
+
+int win32_offset_1st_component(const char *path)
+{
+ char *pos = (char *)path;
+
+ /* unc paths */
+ if (!skip_dos_drive_prefix(&pos) &&
+ is_dir_sep(pos[0]) && is_dir_sep(pos[1])) {
+ /* skip server name */
+ pos = strpbrk(pos + 2, "\\/");
+ if (!pos)
+ return 0; /* Error: malformed unc path */
+
+ do {
+ pos++;
+ } while (*pos && !is_dir_sep(*pos));
+ }
+
+ return pos + is_dir_sep(*pos) - path;
+}
--- /dev/null
+#define has_dos_drive_prefix(path) \
+ (isalpha(*(path)) && (path)[1] == ':' ? 2 : 0)
+int win32_skip_dos_drive_prefix(char **path);
+#define skip_dos_drive_prefix win32_skip_dos_drive_prefix
+static inline int win32_is_dir_sep(int c)
+{
+ return c == '/' || c == '\\';
+}
+#define is_dir_sep win32_is_dir_sep
+static inline char *win32_find_last_dir_sep(const char *path)
+{
+ char *ret = NULL;
+ for (; *path; ++path)
+ if (is_dir_sep(*path))
+ ret = (char *)path;
+ return ret;
+}
+#define find_last_dir_sep win32_find_last_dir_sep
+int win32_offset_1st_component(const char *path);
+#define offset_1st_component win32_offset_1st_component
* entry (which all are to be removed).
*/
static void maybe_remove_section(struct config_store_data *store,
- const char *contents,
size_t *begin_offset, size_t *end_offset,
int *seen_ptr)
{
replace_end = store.parsed[j].end;
copy_end = store.parsed[j].begin;
if (!value)
- maybe_remove_section(&store, contents,
+ maybe_remove_section(&store,
©_end,
&replace_end, &i);
/*
# don't warn for each N_ use
CFLAGS += -DUSE_PARENS_AROUND_GETTEXT_N=0
endif
+CFLAGS += -Wall
CFLAGS += -Wdeclaration-after-statement
CFLAGS += -Wformat-security
CFLAGS += -Wno-format-zero-length
UNRELIABLE_FSTAT = UnfortunatelyYes
OBJECT_CREATION_USES_RENAMES = UnfortunatelyNeedsTo
MMAP_PREVENTS_DELETE = UnfortunatelyYes
- COMPAT_OBJS += compat/cygwin.o
+ COMPAT_OBJS += compat/win32/path-utils.o
FREAD_READS_DIRECTORIES = UnfortunatelyYes
endif
ifeq ($(uname_S),FreeBSD)
HAVE_BSD_SYSCTL = YesPlease
HAVE_BSD_KERN_PROC_SYSCTL = YesPlease
PROCFS_EXECUTABLE_PATH = /proc/curproc/file
+ FREAD_READS_DIRECTORIES = UnfortunatelyYes
endif
ifeq ($(uname_S),MirBSD)
NO_STRCASESTR = YesPlease
# INLINE='' would just replace one set of warnings with another and
# still not compile in c89 mode, due to non-const array initializations.
CC = cc -c99
+ # Build down-rev compatible objects that don't use our new getopt_long.
+ ifeq ($(uname_R).$(uname_V),J06.21)
+ CC += -WRVU=J06.20
+ endif
+ ifeq ($(uname_R).$(uname_V),L17.02)
+ CC += -WRVU=L16.05
+ endif
# Disable all optimization, seems to result in bad code, with -O or -O2
# or even -O1 (default), /usr/local/libexec/git-core/git-pack-objects
# abends on "git push". Needs more investigation.
- CFLAGS = -g -O0
+ CFLAGS = -g -O0 -Winline
# We'd want it to be here.
prefix = /usr/local
- # Our's are in ${prefix}/bin (perl might also be in /usr/bin/perl).
- PERL_PATH = ${prefix}/bin/perl
- PYTHON_PATH = ${prefix}/bin/python
-
+ # perl and python must be in /usr/bin on NonStop - supplied by HPE
+ # with operating system in that managed directory.
+ PERL_PATH = /usr/bin/perl
+ PYTHON_PATH = /usr/bin/python
+ # The current /usr/coreutils/rm at lowest support level does not work
+ # with the git test structure. Long paths as in
+ # 'trash directory...' cause rm to terminate prematurely without fully
+ # removing the directory at OS releases J06.21 and L17.02.
+ # Default to the older rm until those two releases are deprecated.
+ RM = /bin/rm -f
# As detected by './configure'.
# Missdetected, hence commented out, see below.
#NO_CURL = YesPlease
# Added manually, see above.
+ NEEDS_SSL_WITH_CURL = YesPlease
+ NEEDS_CRYPTO_WITH_SSL = YesPlease
+ HAVE_DEV_TTY = YesPlease
HAVE_LIBCHARSET_H = YesPlease
HAVE_STRINGS_H = YesPlease
NEEDS_LIBICONV = YesPlease
NEEDS_LIBINTL_BEFORE_LIBICONV = YesPlease
NO_SYS_SELECT_H = UnfortunatelyYes
NO_D_TYPE_IN_DIRENT = YesPlease
+ NO_GETTEXT = YesPlease
NO_HSTRERROR = YesPlease
NO_STRCASESTR = YesPlease
NO_MEMMEM = YesPlease
NO_MKDTEMP = YesPlease
# Currently libiconv-1.9.1.
OLD_ICONV = UnfortunatelyYes
- NO_REGEX = YesPlease
+ NO_REGEX = NeedsStartEnd
NO_PTHREADS = UnfortunatelyYes
# Not detected (nor checked for) by './configure'.
COMPAT_CFLAGS += -DNOGDI -Icompat -Icompat/win32
COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\"
COMPAT_OBJS += compat/mingw.o compat/winansi.o \
+ compat/win32/path-utils.o \
compat/win32/pthread.o compat/win32/syslog.o \
compat/win32/dirent.o
BASIC_CFLAGS += -DWIN32 -DPROTECT_NTFS_DEFAULT=1
struct ref **orig_list = list;
int len = 0;
enum get_remote_heads_state state = EXPECTING_FIRST_REF;
- const char *arg;
*list = NULL;
die_initial_contact(1);
case PACKET_READ_NORMAL:
len = reader->pktlen;
- if (len > 4 && skip_prefix(reader->line, "ERR ", &arg))
- die(_("remote error: %s"), arg);
break;
case PACKET_READ_FLUSH:
state = EXPECTING_DONE;
- hashcmp(OID.hash, OIDPTR->hash)
+ oidcmp(&OID, OIDPTR)
-@@
-struct object_id OID1, OID2;
-@@
-- hashcpy(OID1.hash, OID2.hash)
-+ oidcpy(&OID1, &OID2)
-
-@@
-identifier f != oidcpy;
-struct object_id *OIDPTR1;
-struct object_id *OIDPTR2;
-@@
- f(...) {<...
-- hashcpy(OIDPTR1->hash, OIDPTR2->hash)
-+ oidcpy(OIDPTR1, OIDPTR2)
- ...>}
-
-@@
-struct object_id *OIDPTR;
-struct object_id OID;
-@@
-- hashcpy(OIDPTR->hash, OID.hash)
-+ oidcpy(OIDPTR, &OID)
-
-@@
-struct object_id *OIDPTR;
-struct object_id OID;
-@@
-- hashcpy(OID.hash, OIDPTR->hash)
-+ oidcpy(&OID, OIDPTR)
-
@@
struct object_id *OIDPTR1;
struct object_id *OIDPTR2;
)
);
+@@
+expression E;
+struct strbuf SB;
+format F =~ "s";
+@@
+- strbuf_addf(E, "%@F@", SB.buf);
++ strbuf_addbuf(E, &SB);
+
+@@
+expression E;
+struct strbuf *SBP;
+format F =~ "s";
+@@
+- strbuf_addf(E, "%@F@", SBP->buf);
++ strbuf_addbuf(E, SBP);
+
+@@
+expression E;
+struct strbuf SB;
+@@
+- strbuf_addstr(E, SB.buf);
++ strbuf_addbuf(E, &SB);
+
+@@
+expression E;
+struct strbuf *SBP;
+@@
+- strbuf_addstr(E, SBP->buf);
++ strbuf_addbuf(E, SBP);
+
@@
expression E1, E2;
format F =~ "s";
--- /dev/null
+// This file is used for the ongoing refactoring of
+// bringing the index or repository struct in all of
+// our code base.
+
+@@
+expression E;
+expression F;
+expression G;
+@@
+- read_object_file(
++ repo_read_object_file(the_repository,
+ E, F, G)
+
+@@
+expression E;
+@@
+- has_sha1_file(
++ repo_has_sha1_file(the_repository,
+ E)
+
+@@
+expression E;
+expression F;
+@@
+- has_sha1_file_with_flags(
++ repo_has_sha1_file_with_flags(the_repository,
+ E)
+
+@@
+expression E;
+@@
+- has_object_file(
++ repo_has_object_file(the_repository,
+ E)
+
+@@
+expression E;
+expression F;
+@@
+- has_object_file_with_flags(
++ repo_has_object_file_with_flags(the_repository,
+ E)
+
+@@
+expression E;
+expression F;
+expression G;
+@@
+- parse_commit_internal(
++ repo_parse_commit_internal(the_repository,
+ E, F, G)
+
+@@
+expression E;
+expression F;
+@@
+- parse_commit_gently(
++ repo_parse_commit_gently(the_repository,
+ E, F)
+
+@@
+expression E;
+@@
+- parse_commit(
++ repo_parse_commit(the_repository,
+ E)
+
+@@
+expression E;
+expression F;
+@@
+- get_merge_bases(
++ repo_get_merge_bases(the_repository,
+ E, F);
+
+@@
+expression E;
+expression F;
+expression G;
+@@
+- get_merge_bases_many(
++ repo_get_merge_bases_many(the_repository,
+ E, F, G);
+
+@@
+expression E;
+expression F;
+expression G;
+@@
+- get_merge_bases_many_dirty(
++ repo_get_merge_bases_many_dirty(the_repository,
+ E, F, G);
+
+@@
+expression E;
+expression F;
+@@
+- in_merge_bases(
++ repo_in_merge_bases(the_repository,
+ E, F);
+
+@@
+expression E;
+expression F;
+expression G;
+@@
+- in_merge_bases_many(
++ repo_in_merge_bases_many(the_repository,
+ E, F, G);
+
+@@
+expression E;
+expression F;
+@@
+- get_commit_buffer(
++ repo_get_commit_buffer(the_repository,
+ E, F);
+
+@@
+expression E;
+expression F;
+@@
+- unuse_commit_buffer(
++ repo_unuse_commit_buffer(the_repository,
+ E, F);
+
+@@
+expression E;
+expression F;
+expression G;
+@@
+- logmsg_reencode(
++ repo_logmsg_reencode(the_repository,
+ E, F, G);
+
+@@
+expression E;
+expression F;
+expression G;
+expression H;
+@@
+- format_commit_message(
++ repo_format_commit_message(the_repository,
+ E, F, G, H);
# Callers must take care of providing only paths that match the current path
# to be completed and adding any prefix path components, if necessary.
# 1: List of newline-separated matching paths, complete with all prefix
-# path componens.
+# path components.
__gitcomp_file_direct ()
{
local IFS=$'\n'
__git_complete_revlist_file ()
{
- local pfx ls ref cur_="$cur"
+ local dequoted_word pfx ls ref cur_="$cur"
case "$cur_" in
*..?*:*)
return
?*:*)
ref="${cur_%%:*}"
cur_="${cur_#*:}"
- case "$cur_" in
+
+ __git_dequote "$cur_"
+
+ case "$dequoted_word" in
?*/*)
- pfx="${cur_%/*}"
- cur_="${cur_##*/}"
+ pfx="${dequoted_word%/*}"
+ cur_="${dequoted_word##*/}"
ls="$ref:$pfx"
pfx="$pfx/"
;;
*)
+ cur_="$dequoted_word"
ls="$ref"
;;
esac
*) pfx="$ref:$pfx" ;;
esac
- __gitcomp_nl "$(__git ls-tree "$ls" \
- | sed '/^100... blob /{
- s,^.* ,,
- s,$, ,
- }
- /^120000 blob /{
- s,^.* ,,
- s,$, ,
- }
- /^040000 tree /{
- s,^.* ,,
- s,$,/,
- }
- s/^.* //')" \
- "$pfx" "$cur_" ""
+ __gitcomp_file "$(__git ls-tree "$ls" \
+ | sed 's/^.* //
+ s/$//')" \
+ "$pfx" "$cur_"
;;
*...*)
pfx="${cur_%...*}..."
local IFS=$'\n'
compset -P '*[=:]'
- compadd -Q -f -- ${=1} && _ret=0
+ compadd -f -- ${=1} && _ret=0
}
__gitcomp_file ()
local IFS=$'\n'
compset -P '*[=:]'
- compadd -Q -p "${2-}" -f -- ${=1} && _ret=0
+ compadd -p "${2-}" -f -- ${=1} && _ret=0
}
_git ()
local IFS=$'\n'
compset -P '*[=:]'
- compadd -Q -f -- ${=1} && _ret=0
+ compadd -f -- ${=1} && _ret=0
}
__gitcomp_file ()
local IFS=$'\n'
compset -P '*[=:]'
- compadd -Q -p "${2-}" -f -- ${=1} && _ret=0
+ compadd -p "${2-}" -f -- ${=1} && _ret=0
}
__git_zsh_bash_func ()
+Release 1.5.0
+=============
+
+Backward-incompatible change
+----------------------------
+
+The name of classes for environment was misnamed as `*Environement`.
+It is now `*Environment`.
+
+New features
+------------
+
+* A Thread-Index header is now added to each email sent (except for
+ combined emails where it would not make sense), so that MS Outlook
+ properly groups messages by threads even though they have a
+ different subject line. Unfortunately, even adding this header the
+ threading still seems to be unreliable, but it is unclear whether
+ this is an issue on our side or on MS Outlook's side (see discussion
+ here: https://github.com/git-multimail/git-multimail/pull/194).
+
+* A new variable multimailhook.ExcludeMergeRevisions was added to send
+ notification emails only for non-merge commits.
+
+* For gitolite environment, it is now possible to specify the mail map
+ in a separate file in addition to gitolite.conf, using the variable
+ multimailhook.MailaddressMap.
+
+Internal changes
+----------------
+
+* The testsuite now uses GIT_PRINT_SHA1_ELLIPSIS where needed for
+ compatibility with recent Git versions. Only tests are affected.
+
+* We don't try to install pyflakes in the continuous integration job
+ for old Python versions where it's no longer available.
+
+* Stop using the deprecated cgi.escape in Python 3.
+
+* New flake8 warnings have been fixed.
+
+* Python 3.6 is now tested against on Travis-CI.
+
+* A bunch of lgtm.com warnings have been fixed.
+
+Bug fixes
+---------
+
+* SMTPMailer logs in only once now. It used to re-login for each email
+ sent which triggered errors for some SMTP servers.
+
+* migrate-mailhook-config was broken by internal refactoring, it
+ should now work again.
+
+This version was tested with Python 2.6 to 3.7. It was tested with Git
+1.7.10.406.gdc801, 2.15.1 and 2.20.1.98.gecbdaf0.
+
Release 1.4.0
=============
git-multimail is an open-source project, built by volunteers. We would
welcome your help!
-The current maintainers are Matthieu Moy
-<matthieu.moy@grenoble-inp.fr> and Michael Haggerty
-<mhagger@alum.mit.edu>.
+The current maintainers are `Matthieu Moy <http://matthieu-moy.fr>`__ and
+`Michael Haggerty <https://github.com/mhagger>`__.
Please note that although a copy of git-multimail is distributed in
the "contrib" section of the main Git project, development takes place
Please CC emails regarding git-multimail to the maintainers so that we
don't overlook them.
+Help needed: testers/maintainer for specific environments/OS
+------------------------------------------------------------
+
+The current maintainer uses and tests git-multimail on Linux with the
+Generic environment. More testers, or better contributors are needed
+to test git-multimail on other real-life setups:
+
+* Mac OS X, Windows: git-multimail is currently not supported on these
+ platforms. But since we have no external dependencies and try to
+ write code as portable as possible, it is possible that
+ git-multimail already runs there and if not, it is likely that it
+ could be ported easily.
+
+ Patches to improve support for Windows and OS X are welcome.
+ Ideally, there would be a sub-maintainer for each OS who would test
+ at least once before each release (around twice a year).
+
+* Gerrit, Stash, Gitolite environments: although the testsuite
+ contains tests for these environments, a tester/maintainer for each
+ environment would be welcome to test and report failure (or success)
+ on real-life environments periodically (here also, feedback before
+ each release would be highly appreciated).
+
.. _`git-multimail repository on GitHub`: https://github.com/git-multimail/git-multimail
.. _`Git mailing list`: git@vger.kernel.org
+++ /dev/null
-git-multimail version 1.4.0
-===========================
-
-.. image:: https://travis-ci.org/git-multimail/git-multimail.svg?branch=master
- :target: https://travis-ci.org/git-multimail/git-multimail
-
-git-multimail is a tool for sending notification emails on pushes to a
-Git repository. It includes a Python module called ``git_multimail.py``,
-which can either be used as a hook script directly or can be imported
-as a Python module into another script.
-
-git-multimail is derived from the Git project's old
-contrib/hooks/post-receive-email, and is mostly compatible with that
-script. See README.migrate-from-post-receive-email for details about
-the differences and for how to migrate from post-receive-email to
-git-multimail.
-
-git-multimail, like the rest of the Git project, is licensed under
-GPLv2 (see the COPYING file for details).
-
-Please note: although, as a convenience, git-multimail may be
-distributed along with the main Git project, development of
-git-multimail takes place in its own, separate project. See section
-"Getting involved" below for more information.
-
-
-By default, for each push received by the repository, git-multimail:
-
-1. Outputs one email summarizing each reference that was changed.
- These "reference change" (called "refchange" below) emails describe
- the nature of the change (e.g., was the reference created, deleted,
- fast-forwarded, etc.) and include a one-line summary of each commit
- that was added to the reference.
-
-2. Outputs one email for each new commit that was introduced by the
- reference change. These "commit" emails include a list of the
- files changed by the commit, followed by the diffs of files
- modified by the commit. The commit emails are threaded to the
- corresponding reference change email via "In-Reply-To". This style
- (similar to the "git format-patch" style used on the Git mailing
- list) makes it easy to scan through the emails, jump to patches
- that need further attention, and write comments about specific
- commits. Commits are handled in reverse topological order (i.e.,
- parents shown before children). For example::
-
- [git] branch master updated
- + [git] 01/08: doc: fix xref link from api docs to manual pages
- + [git] 02/08: api-credentials.txt: show the big picture first
- + [git] 03/08: api-credentials.txt: mention credential.helper explicitly
- + [git] 04/08: api-credentials.txt: add "see also" section
- + [git] 05/08: t3510 (cherry-pick-sequence): add missing '&&'
- + [git] 06/08: Merge branch 'rr/maint-t3510-cascade-fix'
- + [git] 07/08: Merge branch 'mm/api-credentials-doc'
- + [git] 08/08: Git 1.7.11-rc2
-
- By default, each commit appears in exactly one commit email, the
- first time that it is pushed to the repository. If a commit is later
- merged into another branch, then a one-line summary of the commit
- is included in the reference change email (as usual), but no
- additional commit email is generated. See
- `multimailhook.refFilter(Inclusion|Exclusion|DoSend|DontSend)Regex`
- below to configure which branches and tags are watched by the hook.
-
- By default, reference change emails have their "Reply-To" field set
- to the person who pushed the change, and commit emails have their
- "Reply-To" field set to the author of the commit.
-
-3. Output one "announce" mail for each new annotated tag, including
- information about the tag and optionally a shortlog describing the
- changes since the previous tag. Such emails might be useful if you
- use annotated tags to mark releases of your project.
-
-
-Requirements
-------------
-
-* Python 2.x, version 2.4 or later. No non-standard Python modules
- are required. git-multimail has preliminary support for Python 3
- (but it has been better tested with Python 2).
-
-* The ``git`` command must be in your PATH. git-multimail is known to
- work with Git versions back to 1.7.1. (Earlier versions have not
- been tested; if you do so, please report your results.)
-
-* To send emails using the default configuration, a standard sendmail
- program must be located at '/usr/sbin/sendmail' or
- '/usr/lib/sendmail' and must be configured correctly to send emails.
- If this is not the case, set multimailhook.sendmailCommand, or see
- the multimailhook.mailer configuration variable below for how to
- configure git-multimail to send emails via an SMTP server.
-
-
-Invocation
-----------
-
-``git_multimail.py`` is designed to be used as a ``post-receive`` hook in a
-Git repository (see githooks(5)). Link or copy it to
-$GIT_DIR/hooks/post-receive within the repository for which email
-notifications are desired. Usually it should be installed on the
-central repository for a project, to which all commits are eventually
-pushed.
-
-For use on pre-v1.5.1 Git servers, ``git_multimail.py`` can also work as
-an ``update`` hook, taking its arguments on the command line. To use
-this script in this manner, link or copy it to $GIT_DIR/hooks/update.
-Please note that the script is not completely reliable in this mode
-[1]_.
-
-Alternatively, ``git_multimail.py`` can be imported as a Python module
-into your own Python post-receive script. This method is a bit more
-work, but allows the behavior of the hook to be customized using
-arbitrary Python code. For example, you can use a custom environment
-(perhaps inheriting from GenericEnvironment or GitoliteEnvironment) to
-
-* change how the user who did the push is determined
-
-* read users' email addresses from an LDAP server or from a database
-
-* decide which users should be notified about which commits based on
- the contents of the commits (e.g., for users who want to be notified
- only about changes affecting particular files or subdirectories)
-
-Or you can change how emails are sent by writing your own Mailer
-class. The ``post-receive`` script in this directory demonstrates how
-to use ``git_multimail.py`` as a Python module. (If you make interesting
-changes of this type, please consider sharing them with the
-community.)
-
-
-Troubleshooting/FAQ
--------------------
-
-Please read `<doc/troubleshooting.rst>`__ for frequently asked
-questions and common issues with git-multimail.
-
-
-Configuration
--------------
-
-By default, git-multimail mostly takes its configuration from the
-following ``git config`` settings:
-
-multimailhook.environment
- This describes the general environment of the repository. In most
- cases, you do not need to specify a value for this variable:
- `git-multimail` will autodetect which environment to use.
- Currently supported values:
-
- generic
- the username of the pusher is read from $USER or $USERNAME and
- the repository name is derived from the repository's path.
-
- gitolite
- Environment to use when ``git-multimail`` is ran as a gitolite_
- hook.
-
- The username of the pusher is read from $GL_USER, the repository
- name is read from $GL_REPO, and the From: header value is
- optionally read from gitolite.conf (see multimailhook.from).
-
- For more information about gitolite and git-multimail, read
- `<doc/gitolite.rst>`__
-
- stash
- Environment to use when ``git-multimail`` is ran as an Atlassian
- BitBucket Server (formerly known as Atlassian Stash) hook.
-
- **Warning:** this mode was provided by a third-party contributor
- and never tested by the git-multimail maintainers. It is
- provided as-is and may or may not work for you.
-
- This value is automatically assumed when the stash-specific
- flags (``--stash-user`` and ``--stash-repo``) are specified on
- the command line. When this environment is active, the username
- and repo come from these two command line flags, which must be
- specified.
-
- gerrit
- Environment to use when ``git-multimail`` is ran as a
- ``ref-updated`` Gerrit hook.
-
- This value is used when the gerrit-specific command line flags
- (``--oldrev``, ``--newrev``, ``--refname``, ``--project``) for
- gerrit's ref-updated hook are present. When this environment is
- active, the username of the pusher is taken from the
- ``--submitter`` argument if that command line option is passed,
- otherwise 'Gerrit' is used. The repository name is taken from
- the ``--project`` option on the command line, which must be passed.
-
- For more information about gerrit and git-multimail, read
- `<doc/gerrit.rst>`__
-
- If none of these environments is suitable for your setup, then you
- can implement a Python class that inherits from Environment and
- instantiate it via a script that looks like the example
- post-receive script.
-
- The environment value can be specified on the command line using
- the ``--environment`` option. If it is not specified on the
- command line or by ``multimailhook.environment``, the value is
- guessed as follows:
-
- * If stash-specific (respectively gerrit-specific) command flags
- are present on the command-line, then ``stash`` (respectively
- ``gerrit``) is used.
-
- * If the environment variables $GL_USER and $GL_REPO are set, then
- ``gitolite`` is used.
-
- * If none of the above apply, then ``generic`` is used.
-
-multimailhook.repoName
- A short name of this Git repository, to be used in various places
- in the notification email text. The default is to use $GL_REPO
- for gitolite repositories, or otherwise to derive this value from
- the repository path name.
-
-multimailhook.mailingList
- The list of email addresses to which notification emails should be
- sent, as RFC 2822 email addresses separated by commas. This
- configuration option can be multivalued. Leave it unset or set it
- to the empty string to not send emails by default. The next few
- settings can be used to configure specific address lists for
- specific types of notification email.
-
-multimailhook.refchangeList
- The list of email addresses to which summary emails about
- reference changes should be sent, as RFC 2822 email addresses
- separated by commas. This configuration option can be
- multivalued. The default is the value in
- multimailhook.mailingList. Set this value to "none" (or the empty
- string) to prevent reference change emails from being sent even if
- multimailhook.mailingList is set.
-
-multimailhook.announceList
- The list of email addresses to which emails about new annotated
- tags should be sent, as RFC 2822 email addresses separated by
- commas. This configuration option can be multivalued. The
- default is the value in multimailhook.refchangeList or
- multimailhook.mailingList. Set this value to "none" (or the empty
- string) to prevent annotated tag announcement emails from being sent
- even if one of the other values is set.
-
-multimailhook.commitList
- The list of email addresses to which emails about individual new
- commits should be sent, as RFC 2822 email addresses separated by
- commas. This configuration option can be multivalued. The
- default is the value in multimailhook.mailingList. Set this value
- to "none" (or the empty string) to prevent notification emails about
- individual commits from being sent even if
- multimailhook.mailingList is set.
-
-multimailhook.announceShortlog
- If this option is set to true, then emails about changes to
- annotated tags include a shortlog of changes since the previous
- tag. This can be useful if the annotated tags represent releases;
- then the shortlog will be a kind of rough summary of what has
- happened since the last release. But if your tagging policy is
- not so straightforward, then the shortlog might be confusing
- rather than useful. Default is false.
-
-multimailhook.commitEmailFormat
- The format of email messages for the individual commits, can be "text" or
- "html". In the latter case, the emails will include diffs using colorized
- HTML instead of plain text used by default. Note that this currently the
- ref change emails are always sent in plain text.
-
- Note that when using "html", the formatting is done by parsing the
- output of ``git log`` with ``-p``. When using
- ``multimailhook.commitLogOpts`` to specify a ``--format`` for
- ``git log``, one may get false positive (e.g. lines in the body of
- the message starting with ``+++`` or ``---`` colored in red or
- green).
-
- By default, all the message is HTML-escaped. See
- ``multimailhook.htmlInIntro`` to change this behavior.
-
-multimailhook.commitBrowseURL
- Used to generate a link to an online repository browser in commit
- emails. This variable must be a string. Format directives like
- ``%(<variable>)s`` will be expanded the same way as template
- strings. In particular, ``%(id)s`` will be replaced by the full
- Git commit identifier (40-chars hexadecimal).
-
- If the string does not contain any format directive, then
- ``%(id)s`` will be automatically added to the string. If you don't
- want ``%(id)s`` to be automatically added, use the empty format
- directive ``%()s`` anywhere in the string.
-
- For example, a suitable value for the git-multimail project itself
- would be
- ``https://github.com/git-multimail/git-multimail/commit/%(id)s``.
-
-multimailhook.htmlInIntro, multimailhook.htmlInFooter
- When generating an HTML message, git-multimail escapes any HTML
- sequence by default. This means that if a template contains HTML
- like ``<a href="foo">link</a>``, the reader will see the HTML
- source code and not a proper link.
-
- Set ``multimailhook.htmlInIntro`` to true to allow writing HTML
- formatting in introduction templates. Similarly, set
- ``multimailhook.htmlInFooter`` for HTML in the footer.
-
- Variables expanded in the template are still escaped. For example,
- if a repository's path contains a ``<``, it will be rendered as
- such in the message.
-
- Read `<doc/customizing-emails.rst>`__ for more details and
- examples.
-
-multimailhook.refchangeShowGraph
- If this option is set to true, then summary emails about reference
- changes will additionally include:
-
- * a graph of the added commits (if any)
-
- * a graph of the discarded commits (if any)
-
- The log is generated by running ``git log --graph`` with the options
- specified in graphOpts. The default is false.
-
-multimailhook.refchangeShowLog
- If this option is set to true, then summary emails about reference
- changes will include a detailed log of the added commits in
- addition to the one line summary. The log is generated by running
- ``git log`` with the options specified in multimailhook.logOpts.
- Default is false.
-
-multimailhook.mailer
- This option changes the way emails are sent. Accepted values are:
-
- * **sendmail (the default)**: use the command ``/usr/sbin/sendmail`` or
- ``/usr/lib/sendmail`` (or sendmailCommand, if configured). This
- mode can be further customized via the following options:
-
- multimailhook.sendmailCommand
- The command used by mailer ``sendmail`` to send emails. Shell
- quoting is allowed in the value of this setting, but remember that
- Git requires double-quotes to be escaped; e.g.::
-
- git config multimailhook.sendmailcommand '/usr/sbin/sendmail -oi -t -F \"Git Repo\"'
-
- Default is '/usr/sbin/sendmail -oi -t' or
- '/usr/lib/sendmail -oi -t' (depending on which file is
- present and executable).
-
- multimailhook.envelopeSender
- If set then pass this value to sendmail via the -f option to set
- the envelope sender address.
-
- * **smtp**: use Python's smtplib. This is useful when the sendmail
- command is not available on the system. This mode can be
- further customized via the following options:
-
- multimailhook.smtpServer
- The name of the SMTP server to connect to. The value can
- also include a colon and a port number; e.g.,
- ``mail.example.com:25``. Default is 'localhost' using port 25.
-
- multimailhook.smtpUser, multimailhook.smtpPass
- Server username and password. Required if smtpEncryption is 'ssl'.
- Note that the username and password currently need to be
- set cleartext in the configuration file, which is not
- recommended. If you need to use this option, be sure your
- configuration file is read-only.
-
- multimailhook.envelopeSender
- The sender address to be passed to the SMTP server. If
- unset, then the value of multimailhook.from is used.
-
- multimailhook.smtpServerTimeout
- Timeout in seconds.
-
- multimailhook.smtpEncryption
- Set the security type. Allowed values: ``none``, ``ssl``, ``tls`` (starttls).
- Default is ``none``.
-
- multimailhook.smtpCACerts
- Set the path to a list of trusted CA certificate to verify the
- server certificate, only supported when ``smtpEncryption`` is
- ``tls``. If unset or empty, the server certificate is not
- verified. If it targets a file containing a list of trusted CA
- certificates (PEM format) these CAs will be used to verify the
- server certificate. For debian, you can set
- ``/etc/ssl/certs/ca-certificates.crt`` for using the system
- trusted CAs. For self-signed server, you can add your server
- certificate to the system store::
-
- cd /usr/local/share/ca-certificates/
- openssl s_client -starttls smtp \
- -connect mail.example.net:587 -showcerts \
- </dev/null 2>/dev/null \
- | openssl x509 -outform PEM >mail.example.net.crt
- update-ca-certificates
-
- and used the updated ``/etc/ssl/certs/ca-certificates.crt``. Or
- directly use your ``/path/to/mail.example.net.crt``. Default is
- unset.
-
- multimailhook.smtpServerDebugLevel
- Integer number. Set to greater than 0 to activate debugging.
-
-multimailhook.from, multimailhook.fromCommit, multimailhook.fromRefchange
- If set, use this value in the From: field of generated emails.
- ``fromCommit`` is used for commit emails, ``fromRefchange`` is
- used for refchange emails, and ``from`` is used as fall-back in
- all cases.
-
- The value for these variables can be either:
-
- - An email address, which will be used directly.
-
- - The value ``pusher``, in which case the pusher's address (if
- available) will be used.
-
- - The value ``author`` (meaningful only for ``fromCommit``), in which
- case the commit author's address will be used.
-
- If config values are unset, the value of the From: header is
- determined as follows:
-
- 1. (gitolite environment only) Parse gitolite.conf, looking for a
- block of comments that looks like this::
-
- # BEGIN USER EMAILS
- # username Firstname Lastname <email@example.com>
- # END USER EMAILS
-
- If that block exists, and there is a line between the BEGIN
- USER EMAILS and END USER EMAILS lines where the first field
- matches the gitolite username ($GL_USER), use the rest of the
- line for the From: header.
-
- 2. If the user.email configuration setting is set, use its value
- (and the value of user.name, if set).
-
- 3. Use the value of multimailhook.envelopeSender.
-
-multimailhook.administrator
- The name and/or email address of the administrator of the Git
- repository; used in FOOTER_TEMPLATE. Default is
- multimailhook.envelopesender if it is set; otherwise a generic
- string is used.
-
-multimailhook.emailPrefix
- All emails have this string prepended to their subjects, to aid
- email filtering (though filtering based on the X-Git-* email
- headers is probably more robust). Default is the short name of
- the repository in square brackets; e.g., ``[myrepo]``. Set this
- value to the empty string to suppress the email prefix. You may
- use the placeholder ``%(repo_shortname)s`` for the short name of
- the repository.
-
-multimailhook.emailMaxLines
- The maximum number of lines that should be included in the body of
- a generated email. If not specified, there is no limit. Lines
- beyond the limit are suppressed and counted, and a final line is
- added indicating the number of suppressed lines.
-
-multimailhook.emailMaxLineLength
- The maximum length of a line in the email body. Lines longer than
- this limit are truncated to this length with a trailing ``[...]``
- added to indicate the missing text. The default is 500, because
- (a) diffs with longer lines are probably from binary files, for
- which a diff is useless, and (b) even if a text file has such long
- lines, the diffs are probably unreadable anyway. To disable line
- truncation, set this option to 0.
-
-multimailhook.subjectMaxLength
- The maximum length of the subject line (i.e. the ``oneline`` field
- in templates, not including the prefix). Lines longer than this
- limit are truncated to this length with a trailing ``[...]`` added
- to indicate the missing text. This option The default is to use
- ``multimailhook.emailMaxLineLength``. This option avoids sending
- emails with overly long subject lines, but should not be needed if
- the commit messages follow the Git convention (one short subject
- line, then a blank line, then the message body). To disable line
- truncation, set this option to 0.
-
-multimailhook.maxCommitEmails
- The maximum number of commit emails to send for a given change.
- When the number of patches is larger that this value, only the
- summary refchange email is sent. This can avoid accidental
- mailbombing, for example on an initial push. To disable commit
- emails limit, set this option to 0. The default is 500.
-
-multimailhook.emailStrictUTF8
- If this boolean option is set to `true`, then the main part of the
- email body is forced to be valid UTF-8. Any characters that are
- not valid UTF-8 are converted to the Unicode replacement
- character, U+FFFD. The default is `true`.
-
- This option is ineffective with Python 3, where non-UTF-8
- characters are unconditionally replaced.
-
-multimailhook.diffOpts
- Options passed to ``git diff-tree`` when generating the summary
- information for ReferenceChange emails. Default is ``--stat
- --summary --find-copies-harder``. Add -p to those options to
- include a unified diff of changes in addition to the usual summary
- output. Shell quoting is allowed; see ``multimailhook.logOpts`` for
- details.
-
-multimailhook.graphOpts
- Options passed to ``git log --graph`` when generating graphs for the
- reference change summary emails (used only if refchangeShowGraph
- is true). The default is '--oneline --decorate'.
-
- Shell quoting is allowed; see logOpts for details.
-
-multimailhook.logOpts
- Options passed to ``git log`` to generate additional info for
- reference change emails (used only if refchangeShowLog is set).
- For example, adding -p will show each commit's complete diff. The
- default is empty.
-
- Shell quoting is allowed; for example, a log format that contains
- spaces can be specified using something like::
-
- git config multimailhook.logopts '--pretty=format:"%h %aN <%aE>%n%s%n%n%b%n"'
-
- If you want to set this by editing your configuration file
- directly, remember that Git requires double-quotes to be escaped
- (see git-config(1) for more information)::
-
- [multimailhook]
- logopts = --pretty=format:\"%h %aN <%aE>%n%s%n%n%b%n\"
-
-multimailhook.commitLogOpts
- Options passed to ``git log`` to generate additional info for
- revision change emails. For example, adding --ignore-all-spaces
- will suppress whitespace changes. The default options are ``-C
- --stat -p --cc``. Shell quoting is allowed; see
- multimailhook.logOpts for details.
-
-multimailhook.dateSubstitute
- String to use as a substitute for ``Date:`` in the output of ``git
- log`` while formatting commit messages. This is useful to avoid
- emitting a line that can be interpreted by mailers as the start of
- a cited message (Zimbra webmail in particular). Defaults to
- ``CommitDate:``. Set to an empty string or ``none`` to deactivate
- the behavior.
-
-multimailhook.emailDomain
- Domain name appended to the username of the person doing the push
- to convert it into an email address
- (via ``"%s@%s" % (username, emaildomain)``). More complicated
- schemes can be implemented by overriding Environment and
- overriding its get_pusher_email() method.
-
-multimailhook.replyTo, multimailhook.replyToCommit, multimailhook.replyToRefchange
- Addresses to use in the Reply-To: field for commit emails
- (replyToCommit) and refchange emails (replyToRefchange).
- multimailhook.replyTo is used as default when replyToCommit or
- replyToRefchange is not set. The shortcuts ``pusher`` and
- ``author`` are allowed with the same semantics as for
- ``multimailhook.from``. In addition, the value ``none`` can be
- used to omit the ``Reply-To:`` field.
-
- The default is ``pusher`` for refchange emails, and ``author`` for
- commit emails.
-
-multimailhook.quiet
- Do not output the list of email recipients from the hook
-
-multimailhook.stdout
- For debugging, send emails to stdout rather than to the
- mailer. Equivalent to the --stdout command line option
-
-multimailhook.scanCommitForCc
- If this option is set to true, than recipients from lines in commit body
- that starts with ``CC:`` will be added to CC list.
- Default: false
-
-multimailhook.combineWhenSingleCommit
- If this option is set to true and a single new commit is pushed to
- a branch, combine the summary and commit email messages into a
- single email.
- Default: true
-
-multimailhook.refFilterInclusionRegex, multimailhook.refFilterExclusionRegex, multimailhook.refFilterDoSendRegex, multimailhook.refFilterDontSendRegex
- **Warning:** these options are experimental. They should work, but
- the user-interface is not stable yet (in particular, the option
- names may change). If you want to participate in stabilizing the
- feature, please contact the maintainers and/or send pull-requests.
- If you are happy with the current shape of the feature, please
- report it too.
-
- Regular expressions that can be used to limit refs for which email
- updates will be sent. It is an error to specify both an inclusion
- and an exclusion regex. If a ``refFilterInclusionRegex`` is
- specified, emails will only be sent for refs which match this
- regex. If a ``refFilterExclusionRegex`` regex is specified,
- emails will be sent for all refs except those that match this
- regex (or that match a predefined regex specific to the
- environment, such as "^refs/notes" for most environments and
- "^refs/notes|^refs/changes" for the gerrit environment).
-
- The expressions are matched against the complete refname, and is
- considered to match if any substring matches. For example, to
- filter-out all tags, set ``refFilterExclusionRegex`` to
- ``^refs/tags/`` (note the leading ``^`` but no trailing ``$``). If
- you set ``refFilterExclusionRegex`` to ``master``, then any ref
- containing ``master`` will be excluded (the ``master`` branch, but
- also ``refs/tags/master`` or ``refs/heads/foo-master-bar``).
-
- ``refFilterDoSendRegex`` and ``refFilterDontSendRegex`` are
- analogous to ``refFilterInclusionRegex`` and
- ``refFilterExclusionRegex`` with one difference: with
- ``refFilterDoSendRegex`` and ``refFilterDontSendRegex``, commits
- introduced by one excluded ref will not be considered as new when
- they reach an included ref. Typically, if you add a branch ``foo``
- to ``refFilterDontSendRegex``, push commits to this branch, and
- later merge branch ``foo`` into ``master``, then the notification
- email for ``master`` will contain a commit email only for the
- merge commit. If you include ``foo`` in
- ``refFilterExclusionRegex``, then at the time of merge, you will
- receive one commit email per commit in the branch.
-
- These variables can be multi-valued, like::
-
- [multimailhook]
- refFilterExclusionRegex = ^refs/tags/
- refFilterExclusionRegex = ^refs/heads/master$
-
- You can also provide a whitespace-separated list like::
-
- [multimailhook]
- refFilterExclusionRegex = ^refs/tags/ ^refs/heads/master$
-
- Both examples exclude tags and the master branch, and are
- equivalent to::
-
- [multimailhook]
- refFilterExclusionRegex = ^refs/tags/|^refs/heads/master$
-
- ``refFilterInclusionRegex`` and ``refFilterExclusionRegex`` are
- strictly stronger than ``refFilterDoSendRegex`` and
- ``refFilterDontSendRegex``. In other words, adding a ref to a
- DoSend/DontSend regex has no effect if it is already excluded by a
- Exclusion/Inclusion regex.
-
-multimailhook.logFile, multimailhook.errorLogFile, multimailhook.debugLogFile
-
- When set, these variable designate path to files where
- git-multimail will log some messages. Normal messages and error
- messages are sent to ``logFile``, and error messages are also sent
- to ``errorLogFile``. Debug messages and all other messages are
- sent to ``debugLogFile``. The recommended way is to set only one
- of these variables, but it is also possible to set several of them
- (part of the information is then duplicated in several log files,
- for example errors are duplicated to all log files).
-
- Relative path are relative to the Git repository where the push is
- done.
-
-multimailhook.verbose
-
- Verbosity level of git-multimail on its standard output. By
- default, show only error and info messages. If set to true, show
- also debug messages.
-
-Email filtering aids
---------------------
-
-All emails include extra headers to enable fine tuned filtering and
-give information for debugging. All emails include the headers
-``X-Git-Host``, ``X-Git-Repo``, ``X-Git-Refname``, and ``X-Git-Reftype``.
-ReferenceChange emails also include headers ``X-Git-Oldrev`` and ``X-Git-Newrev``;
-Revision emails also include header ``X-Git-Rev``.
-
-
-Customizing email contents
---------------------------
-
-git-multimail mostly generates emails by expanding templates. The
-templates can be customized. To avoid the need to edit
-``git_multimail.py`` directly, the preferred way to change the templates
-is to write a separate Python script that imports ``git_multimail.py`` as
-a module, then replaces the templates in place. See the provided
-post-receive script for an example of how this is done.
-
-
-Customizing git-multimail for your environment
-----------------------------------------------
-
-git-multimail is mostly customized via an "environment" that describes
-the local environment in which Git is running. Two types of
-environment are built in:
-
-GenericEnvironment
- a stand-alone Git repository.
-
-GitoliteEnvironment
- a Git repository that is managed by gitolite_. For such
- repositories, the identity of the pusher is read from
- environment variable $GL_USER, the name of the repository is read
- from $GL_REPO (if it is not overridden by multimailhook.reponame),
- and the From: header value is optionally read from gitolite.conf
- (see multimailhook.from).
-
-By default, git-multimail assumes GitoliteEnvironment if $GL_USER and
-$GL_REPO are set, and otherwise assumes GenericEnvironment.
-Alternatively, you can choose one of these two environments explicitly
-by setting a ``multimailhook.environment`` config setting (which can
-have the value `generic` or `gitolite`) or by passing an --environment
-option to the script.
-
-If you need to customize the script in ways that are not supported by
-the existing environments, you can define your own environment class
-class using arbitrary Python code. To do so, you need to import
-``git_multimail.py`` as a Python module, as demonstrated by the example
-post-receive script. Then implement your environment class; it should
-usually inherit from one of the existing Environment classes and
-possibly one or more of the EnvironmentMixin classes. Then set the
-``environment`` variable to an instance of your own environment class
-and pass it to ``run_as_post_receive_hook()``.
-
-The standard environment classes, GenericEnvironment and
-GitoliteEnvironment, are in fact themselves put together out of a
-number of mixin classes, each of which handles one aspect of the
-customization. For the finest control over your configuration, you
-can specify exactly which mixin classes your own environment class
-should inherit from, and override individual methods (or even add your
-own mixin classes) to implement entirely new behaviors. If you
-implement any mixins that might be useful to other people, please
-consider sharing them with the community!
-
-
-Getting involved
-----------------
-
-Please, read `<CONTRIBUTING.rst>`__ for instructions on how to
-contribute to git-multimail.
-
-
-Footnotes
----------
-
-.. [1] Because of the way information is passed to update hooks, the
- script's method of determining whether a commit has already
- been seen does not work when it is used as an ``update`` script.
- In particular, no notification email will be generated for a
- new commit that is added to multiple references in the same
- push. A workaround is to use --force-send to force sending the
- emails.
-
-.. _gitolite: https://github.com/sitaramc/gitolite
https://github.com/git-multimail/git-multimail
The version in this directory was obtained from the upstream project
-on August 17 2016 and consists of the "git-multimail" subdirectory from
+on January 07 2019 and consists of the "git-multimail" subdirectory from
revision
- 07b1cb6bfd7be156c62e1afa17cae13b850a869f refs/tags/1.4.0
+ 04e80e6c40be465cc62b6c246f0fcb8fd2cfd454 refs/tags/1.5.0
Please see the README file in this directory for information about how
to report bugs or contribute to git-multimail.
--- /dev/null
+git-multimail version 1.5.0
+===========================
+
+.. image:: https://travis-ci.org/git-multimail/git-multimail.svg?branch=master
+ :target: https://travis-ci.org/git-multimail/git-multimail
+
+git-multimail is a tool for sending notification emails on pushes to a
+Git repository. It includes a Python module called ``git_multimail.py``,
+which can either be used as a hook script directly or can be imported
+as a Python module into another script.
+
+git-multimail is derived from the Git project's old
+contrib/hooks/post-receive-email, and is mostly compatible with that
+script. See README.migrate-from-post-receive-email for details about
+the differences and for how to migrate from post-receive-email to
+git-multimail.
+
+git-multimail, like the rest of the Git project, is licensed under
+GPLv2 (see the COPYING file for details).
+
+Please note: although, as a convenience, git-multimail may be
+distributed along with the main Git project, development of
+git-multimail takes place in its own, separate project. Please, read
+`<CONTRIBUTING.rst>`__ for more information.
+
+
+By default, for each push received by the repository, git-multimail:
+
+1. Outputs one email summarizing each reference that was changed.
+ These "reference change" (called "refchange" below) emails describe
+ the nature of the change (e.g., was the reference created, deleted,
+ fast-forwarded, etc.) and include a one-line summary of each commit
+ that was added to the reference.
+
+2. Outputs one email for each new commit that was introduced by the
+ reference change. These "commit" emails include a list of the
+ files changed by the commit, followed by the diffs of files
+ modified by the commit. The commit emails are threaded to the
+ corresponding reference change email via "In-Reply-To". This style
+ (similar to the "git format-patch" style used on the Git mailing
+ list) makes it easy to scan through the emails, jump to patches
+ that need further attention, and write comments about specific
+ commits. Commits are handled in reverse topological order (i.e.,
+ parents shown before children). For example::
+
+ [git] branch master updated
+ + [git] 01/08: doc: fix xref link from api docs to manual pages
+ + [git] 02/08: api-credentials.txt: show the big picture first
+ + [git] 03/08: api-credentials.txt: mention credential.helper explicitly
+ + [git] 04/08: api-credentials.txt: add "see also" section
+ + [git] 05/08: t3510 (cherry-pick-sequence): add missing '&&'
+ + [git] 06/08: Merge branch 'rr/maint-t3510-cascade-fix'
+ + [git] 07/08: Merge branch 'mm/api-credentials-doc'
+ + [git] 08/08: Git 1.7.11-rc2
+
+ By default, each commit appears in exactly one commit email, the
+ first time that it is pushed to the repository. If a commit is later
+ merged into another branch, then a one-line summary of the commit
+ is included in the reference change email (as usual), but no
+ additional commit email is generated. See
+ `multimailhook.refFilter(Inclusion|Exclusion|DoSend|DontSend)Regex`
+ below to configure which branches and tags are watched by the hook.
+
+ By default, reference change emails have their "Reply-To" field set
+ to the person who pushed the change, and commit emails have their
+ "Reply-To" field set to the author of the commit.
+
+3. Output one "announce" mail for each new annotated tag, including
+ information about the tag and optionally a shortlog describing the
+ changes since the previous tag. Such emails might be useful if you
+ use annotated tags to mark releases of your project.
+
+
+Requirements
+------------
+
+* Python 2.x, version 2.4 or later. No non-standard Python modules
+ are required. git-multimail has preliminary support for Python 3
+ (but it has been better tested with Python 2).
+
+* The ``git`` command must be in your PATH. git-multimail is known to
+ work with Git versions back to 1.7.1. (Earlier versions have not
+ been tested; if you do so, please report your results.)
+
+* To send emails using the default configuration, a standard sendmail
+ program must be located at '/usr/sbin/sendmail' or
+ '/usr/lib/sendmail' and must be configured correctly to send emails.
+ If this is not the case, set multimailhook.sendmailCommand, or see
+ the multimailhook.mailer configuration variable below for how to
+ configure git-multimail to send emails via an SMTP server.
+
+* git-multimail is currently tested only on Linux. It may or may not
+ work on other platforms such as Windows and Mac OS. See
+ `<CONTRIBUTING.rst>`__ to improve the situation.
+
+
+Invocation
+----------
+
+``git_multimail.py`` is designed to be used as a ``post-receive`` hook in a
+Git repository (see githooks(5)). Link or copy it to
+$GIT_DIR/hooks/post-receive within the repository for which email
+notifications are desired. Usually it should be installed on the
+central repository for a project, to which all commits are eventually
+pushed.
+
+For use on pre-v1.5.1 Git servers, ``git_multimail.py`` can also work as
+an ``update`` hook, taking its arguments on the command line. To use
+this script in this manner, link or copy it to $GIT_DIR/hooks/update.
+Please note that the script is not completely reliable in this mode
+[1]_.
+
+Alternatively, ``git_multimail.py`` can be imported as a Python module
+into your own Python post-receive script. This method is a bit more
+work, but allows the behavior of the hook to be customized using
+arbitrary Python code. For example, you can use a custom environment
+(perhaps inheriting from GenericEnvironment or GitoliteEnvironment) to
+
+* change how the user who did the push is determined
+
+* read users' email addresses from an LDAP server or from a database
+
+* decide which users should be notified about which commits based on
+ the contents of the commits (e.g., for users who want to be notified
+ only about changes affecting particular files or subdirectories)
+
+Or you can change how emails are sent by writing your own Mailer
+class. The ``post-receive`` script in this directory demonstrates how
+to use ``git_multimail.py`` as a Python module. (If you make interesting
+changes of this type, please consider sharing them with the
+community.)
+
+
+Troubleshooting/FAQ
+-------------------
+
+Please read `<doc/troubleshooting.rst>`__ for frequently asked
+questions and common issues with git-multimail.
+
+
+Configuration
+-------------
+
+By default, git-multimail mostly takes its configuration from the
+following ``git config`` settings:
+
+multimailhook.environment
+ This describes the general environment of the repository. In most
+ cases, you do not need to specify a value for this variable:
+ `git-multimail` will autodetect which environment to use.
+ Currently supported values:
+
+ generic
+ the username of the pusher is read from $USER or $USERNAME and
+ the repository name is derived from the repository's path.
+
+ gitolite
+ Environment to use when ``git-multimail`` is ran as a gitolite_
+ hook.
+
+ The username of the pusher is read from $GL_USER, the repository
+ name is read from $GL_REPO, and the From: header value is
+ optionally read from gitolite.conf (see multimailhook.from).
+
+ For more information about gitolite and git-multimail, read
+ `<doc/gitolite.rst>`__
+
+ stash
+ Environment to use when ``git-multimail`` is ran as an Atlassian
+ BitBucket Server (formerly known as Atlassian Stash) hook.
+
+ **Warning:** this mode was provided by a third-party contributor
+ and never tested by the git-multimail maintainers. It is
+ provided as-is and may or may not work for you.
+
+ This value is automatically assumed when the stash-specific
+ flags (``--stash-user`` and ``--stash-repo``) are specified on
+ the command line. When this environment is active, the username
+ and repo come from these two command line flags, which must be
+ specified.
+
+ gerrit
+ Environment to use when ``git-multimail`` is ran as a
+ ``ref-updated`` Gerrit hook.
+
+ This value is used when the gerrit-specific command line flags
+ (``--oldrev``, ``--newrev``, ``--refname``, ``--project``) for
+ gerrit's ref-updated hook are present. When this environment is
+ active, the username of the pusher is taken from the
+ ``--submitter`` argument if that command line option is passed,
+ otherwise 'Gerrit' is used. The repository name is taken from
+ the ``--project`` option on the command line, which must be passed.
+
+ For more information about gerrit and git-multimail, read
+ `<doc/gerrit.rst>`__
+
+ If none of these environments is suitable for your setup, then you
+ can implement a Python class that inherits from Environment and
+ instantiate it via a script that looks like the example
+ post-receive script.
+
+ The environment value can be specified on the command line using
+ the ``--environment`` option. If it is not specified on the
+ command line or by ``multimailhook.environment``, the value is
+ guessed as follows:
+
+ * If stash-specific (respectively gerrit-specific) command flags
+ are present on the command-line, then ``stash`` (respectively
+ ``gerrit``) is used.
+
+ * If the environment variables $GL_USER and $GL_REPO are set, then
+ ``gitolite`` is used.
+
+ * If none of the above apply, then ``generic`` is used.
+
+multimailhook.repoName
+ A short name of this Git repository, to be used in various places
+ in the notification email text. The default is to use $GL_REPO
+ for gitolite repositories, or otherwise to derive this value from
+ the repository path name.
+
+multimailhook.mailingList
+ The list of email addresses to which notification emails should be
+ sent, as RFC 2822 email addresses separated by commas. This
+ configuration option can be multivalued. Leave it unset or set it
+ to the empty string to not send emails by default. The next few
+ settings can be used to configure specific address lists for
+ specific types of notification email.
+
+multimailhook.refchangeList
+ The list of email addresses to which summary emails about
+ reference changes should be sent, as RFC 2822 email addresses
+ separated by commas. This configuration option can be
+ multivalued. The default is the value in
+ multimailhook.mailingList. Set this value to "none" (or the empty
+ string) to prevent reference change emails from being sent even if
+ multimailhook.mailingList is set.
+
+multimailhook.announceList
+ The list of email addresses to which emails about new annotated
+ tags should be sent, as RFC 2822 email addresses separated by
+ commas. This configuration option can be multivalued. The
+ default is the value in multimailhook.refchangeList or
+ multimailhook.mailingList. Set this value to "none" (or the empty
+ string) to prevent annotated tag announcement emails from being sent
+ even if one of the other values is set.
+
+multimailhook.commitList
+ The list of email addresses to which emails about individual new
+ commits should be sent, as RFC 2822 email addresses separated by
+ commas. This configuration option can be multivalued. The
+ default is the value in multimailhook.mailingList. Set this value
+ to "none" (or the empty string) to prevent notification emails about
+ individual commits from being sent even if
+ multimailhook.mailingList is set.
+
+multimailhook.announceShortlog
+ If this option is set to true, then emails about changes to
+ annotated tags include a shortlog of changes since the previous
+ tag. This can be useful if the annotated tags represent releases;
+ then the shortlog will be a kind of rough summary of what has
+ happened since the last release. But if your tagging policy is
+ not so straightforward, then the shortlog might be confusing
+ rather than useful. Default is false.
+
+multimailhook.commitEmailFormat
+ The format of email messages for the individual commits, can be "text" or
+ "html". In the latter case, the emails will include diffs using colorized
+ HTML instead of plain text used by default. Note that this currently the
+ ref change emails are always sent in plain text.
+
+ Note that when using "html", the formatting is done by parsing the
+ output of ``git log`` with ``-p``. When using
+ ``multimailhook.commitLogOpts`` to specify a ``--format`` for
+ ``git log``, one may get false positive (e.g. lines in the body of
+ the message starting with ``+++`` or ``---`` colored in red or
+ green).
+
+ By default, all the message is HTML-escaped. See
+ ``multimailhook.htmlInIntro`` to change this behavior.
+
+multimailhook.commitBrowseURL
+ Used to generate a link to an online repository browser in commit
+ emails. This variable must be a string. Format directives like
+ ``%(<variable>)s`` will be expanded the same way as template
+ strings. In particular, ``%(id)s`` will be replaced by the full
+ Git commit identifier (40-chars hexadecimal).
+
+ If the string does not contain any format directive, then
+ ``%(id)s`` will be automatically added to the string. If you don't
+ want ``%(id)s`` to be automatically added, use the empty format
+ directive ``%()s`` anywhere in the string.
+
+ For example, a suitable value for the git-multimail project itself
+ would be
+ ``https://github.com/git-multimail/git-multimail/commit/%(id)s``.
+
+multimailhook.htmlInIntro, multimailhook.htmlInFooter
+ When generating an HTML message, git-multimail escapes any HTML
+ sequence by default. This means that if a template contains HTML
+ like ``<a href="foo">link</a>``, the reader will see the HTML
+ source code and not a proper link.
+
+ Set ``multimailhook.htmlInIntro`` to true to allow writing HTML
+ formatting in introduction templates. Similarly, set
+ ``multimailhook.htmlInFooter`` for HTML in the footer.
+
+ Variables expanded in the template are still escaped. For example,
+ if a repository's path contains a ``<``, it will be rendered as
+ such in the message.
+
+ Read `<doc/customizing-emails.rst>`__ for more details and
+ examples.
+
+multimailhook.refchangeShowGraph
+ If this option is set to true, then summary emails about reference
+ changes will additionally include:
+
+ * a graph of the added commits (if any)
+
+ * a graph of the discarded commits (if any)
+
+ The log is generated by running ``git log --graph`` with the options
+ specified in graphOpts. The default is false.
+
+multimailhook.refchangeShowLog
+ If this option is set to true, then summary emails about reference
+ changes will include a detailed log of the added commits in
+ addition to the one line summary. The log is generated by running
+ ``git log`` with the options specified in multimailhook.logOpts.
+ Default is false.
+
+multimailhook.mailer
+ This option changes the way emails are sent. Accepted values are:
+
+ * **sendmail (the default)**: use the command ``/usr/sbin/sendmail`` or
+ ``/usr/lib/sendmail`` (or sendmailCommand, if configured). This
+ mode can be further customized via the following options:
+
+ multimailhook.sendmailCommand
+ The command used by mailer ``sendmail`` to send emails. Shell
+ quoting is allowed in the value of this setting, but remember that
+ Git requires double-quotes to be escaped; e.g.::
+
+ git config multimailhook.sendmailcommand '/usr/sbin/sendmail -oi -t -F \"Git Repo\"'
+
+ Default is '/usr/sbin/sendmail -oi -t' or
+ '/usr/lib/sendmail -oi -t' (depending on which file is
+ present and executable).
+
+ multimailhook.envelopeSender
+ If set then pass this value to sendmail via the -f option to set
+ the envelope sender address.
+
+ * **smtp**: use Python's smtplib. This is useful when the sendmail
+ command is not available on the system. This mode can be
+ further customized via the following options:
+
+ multimailhook.smtpServer
+ The name of the SMTP server to connect to. The value can
+ also include a colon and a port number; e.g.,
+ ``mail.example.com:25``. Default is 'localhost' using port 25.
+
+ multimailhook.smtpUser, multimailhook.smtpPass
+ Server username and password. Required if smtpEncryption is 'ssl'.
+ Note that the username and password currently need to be
+ set cleartext in the configuration file, which is not
+ recommended. If you need to use this option, be sure your
+ configuration file is read-only.
+
+ multimailhook.envelopeSender
+ The sender address to be passed to the SMTP server. If
+ unset, then the value of multimailhook.from is used.
+
+ multimailhook.smtpServerTimeout
+ Timeout in seconds. Default is 10.
+
+ multimailhook.smtpEncryption
+ Set the security type. Allowed values: ``none``, ``ssl``, ``tls`` (starttls).
+ Default is ``none``.
+
+ multimailhook.smtpCACerts
+ Set the path to a list of trusted CA certificate to verify the
+ server certificate, only supported when ``smtpEncryption`` is
+ ``tls``. If unset or empty, the server certificate is not
+ verified. If it targets a file containing a list of trusted CA
+ certificates (PEM format) these CAs will be used to verify the
+ server certificate. For debian, you can set
+ ``/etc/ssl/certs/ca-certificates.crt`` for using the system
+ trusted CAs. For self-signed server, you can add your server
+ certificate to the system store::
+
+ cd /usr/local/share/ca-certificates/
+ openssl s_client -starttls smtp \
+ -connect mail.example.net:587 -showcerts \
+ </dev/null 2>/dev/null \
+ | openssl x509 -outform PEM >mail.example.net.crt
+ update-ca-certificates
+
+ and used the updated ``/etc/ssl/certs/ca-certificates.crt``. Or
+ directly use your ``/path/to/mail.example.net.crt``. Default is
+ unset.
+
+ multimailhook.smtpServerDebugLevel
+ Integer number. Set to greater than 0 to activate debugging.
+
+multimailhook.from, multimailhook.fromCommit, multimailhook.fromRefchange
+ If set, use this value in the From: field of generated emails.
+ ``fromCommit`` is used for commit emails, ``fromRefchange`` is
+ used for refchange emails, and ``from`` is used as fall-back in
+ all cases.
+
+ The value for these variables can be either:
+
+ - An email address, which will be used directly.
+
+ - The value ``pusher``, in which case the pusher's address (if
+ available) will be used.
+
+ - The value ``author`` (meaningful only for ``fromCommit``), in which
+ case the commit author's address will be used.
+
+ If config values are unset, the value of the From: header is
+ determined as follows:
+
+ 1. (gitolite environment only)
+ 1.a) If ``multimailhook.MailaddressMap`` is set, and is a path
+ to an existing file (if relative, it is considered relative to
+ the place where ``gitolite.conf`` is located), then this file
+ should contain lines like::
+
+ username Firstname Lastname <email@example.com>
+
+ git-multimail will then look for a line where ``$GL_USER``
+ matches the ``username`` part, and use the rest of the line for
+ the ``From:`` header.
+
+ 1.b) Parse gitolite.conf, looking for a block of comments that
+ looks like this::
+
+ # BEGIN USER EMAILS
+ # username Firstname Lastname <email@example.com>
+ # END USER EMAILS
+
+ If that block exists, and there is a line between the BEGIN
+ USER EMAILS and END USER EMAILS lines where the first field
+ matches the gitolite username ($GL_USER), use the rest of the
+ line for the From: header.
+
+ 2. If the user.email configuration setting is set, use its value
+ (and the value of user.name, if set).
+
+ 3. Use the value of multimailhook.envelopeSender.
+
+multimailhook.MailaddressMap
+ (gitolite environment only)
+ File to look for a ``From:`` address based on the user doing the
+ push. Defaults to unset. See ``multimailhook.from`` for details.
+
+multimailhook.administrator
+ The name and/or email address of the administrator of the Git
+ repository; used in FOOTER_TEMPLATE. Default is
+ multimailhook.envelopesender if it is set; otherwise a generic
+ string is used.
+
+multimailhook.emailPrefix
+ All emails have this string prepended to their subjects, to aid
+ email filtering (though filtering based on the X-Git-* email
+ headers is probably more robust). Default is the short name of
+ the repository in square brackets; e.g., ``[myrepo]``. Set this
+ value to the empty string to suppress the email prefix. You may
+ use the placeholder ``%(repo_shortname)s`` for the short name of
+ the repository.
+
+multimailhook.emailMaxLines
+ The maximum number of lines that should be included in the body of
+ a generated email. If not specified, there is no limit. Lines
+ beyond the limit are suppressed and counted, and a final line is
+ added indicating the number of suppressed lines.
+
+multimailhook.emailMaxLineLength
+ The maximum length of a line in the email body. Lines longer than
+ this limit are truncated to this length with a trailing ``[...]``
+ added to indicate the missing text. The default is 500, because
+ (a) diffs with longer lines are probably from binary files, for
+ which a diff is useless, and (b) even if a text file has such long
+ lines, the diffs are probably unreadable anyway. To disable line
+ truncation, set this option to 0.
+
+multimailhook.subjectMaxLength
+ The maximum length of the subject line (i.e. the ``oneline`` field
+ in templates, not including the prefix). Lines longer than this
+ limit are truncated to this length with a trailing ``[...]`` added
+ to indicate the missing text. This option The default is to use
+ ``multimailhook.emailMaxLineLength``. This option avoids sending
+ emails with overly long subject lines, but should not be needed if
+ the commit messages follow the Git convention (one short subject
+ line, then a blank line, then the message body). To disable line
+ truncation, set this option to 0.
+
+multimailhook.maxCommitEmails
+ The maximum number of commit emails to send for a given change.
+ When the number of patches is larger that this value, only the
+ summary refchange email is sent. This can avoid accidental
+ mailbombing, for example on an initial push. To disable commit
+ emails limit, set this option to 0. The default is 500.
+
+multimailhook.excludeMergeRevisions
+ When sending out revision emails, do not consider merge commits (the
+ functional equivalent of `rev-list --no-merges`).
+ The default is `false` (send merge commit emails).
+
+multimailhook.emailStrictUTF8
+ If this boolean option is set to `true`, then the main part of the
+ email body is forced to be valid UTF-8. Any characters that are
+ not valid UTF-8 are converted to the Unicode replacement
+ character, U+FFFD. The default is `true`.
+
+ This option is ineffective with Python 3, where non-UTF-8
+ characters are unconditionally replaced.
+
+multimailhook.diffOpts
+ Options passed to ``git diff-tree`` when generating the summary
+ information for ReferenceChange emails. Default is ``--stat
+ --summary --find-copies-harder``. Add -p to those options to
+ include a unified diff of changes in addition to the usual summary
+ output. Shell quoting is allowed; see ``multimailhook.logOpts`` for
+ details.
+
+multimailhook.graphOpts
+ Options passed to ``git log --graph`` when generating graphs for the
+ reference change summary emails (used only if refchangeShowGraph
+ is true). The default is '--oneline --decorate'.
+
+ Shell quoting is allowed; see logOpts for details.
+
+multimailhook.logOpts
+ Options passed to ``git log`` to generate additional info for
+ reference change emails (used only if refchangeShowLog is set).
+ For example, adding -p will show each commit's complete diff. The
+ default is empty.
+
+ Shell quoting is allowed; for example, a log format that contains
+ spaces can be specified using something like::
+
+ git config multimailhook.logopts '--pretty=format:"%h %aN <%aE>%n%s%n%n%b%n"'
+
+ If you want to set this by editing your configuration file
+ directly, remember that Git requires double-quotes to be escaped
+ (see git-config(1) for more information)::
+
+ [multimailhook]
+ logopts = --pretty=format:\"%h %aN <%aE>%n%s%n%n%b%n\"
+
+multimailhook.commitLogOpts
+ Options passed to ``git log`` to generate additional info for
+ revision change emails. For example, adding --ignore-all-spaces
+ will suppress whitespace changes. The default options are ``-C
+ --stat -p --cc``. Shell quoting is allowed; see
+ multimailhook.logOpts for details.
+
+multimailhook.dateSubstitute
+ String to use as a substitute for ``Date:`` in the output of ``git
+ log`` while formatting commit messages. This is useful to avoid
+ emitting a line that can be interpreted by mailers as the start of
+ a cited message (Zimbra webmail in particular). Defaults to
+ ``CommitDate:``. Set to an empty string or ``none`` to deactivate
+ the behavior.
+
+multimailhook.emailDomain
+ Domain name appended to the username of the person doing the push
+ to convert it into an email address
+ (via ``"%s@%s" % (username, emaildomain)``). More complicated
+ schemes can be implemented by overriding Environment and
+ overriding its get_pusher_email() method.
+
+multimailhook.replyTo, multimailhook.replyToCommit, multimailhook.replyToRefchange
+ Addresses to use in the Reply-To: field for commit emails
+ (replyToCommit) and refchange emails (replyToRefchange).
+ multimailhook.replyTo is used as default when replyToCommit or
+ replyToRefchange is not set. The shortcuts ``pusher`` and
+ ``author`` are allowed with the same semantics as for
+ ``multimailhook.from``. In addition, the value ``none`` can be
+ used to omit the ``Reply-To:`` field.
+
+ The default is ``pusher`` for refchange emails, and ``author`` for
+ commit emails.
+
+multimailhook.quiet
+ Do not output the list of email recipients from the hook
+
+multimailhook.stdout
+ For debugging, send emails to stdout rather than to the
+ mailer. Equivalent to the --stdout command line option
+
+multimailhook.scanCommitForCc
+ If this option is set to true, than recipients from lines in commit body
+ that starts with ``CC:`` will be added to CC list.
+ Default: false
+
+multimailhook.combineWhenSingleCommit
+ If this option is set to true and a single new commit is pushed to
+ a branch, combine the summary and commit email messages into a
+ single email.
+ Default: true
+
+multimailhook.refFilterInclusionRegex, multimailhook.refFilterExclusionRegex, multimailhook.refFilterDoSendRegex, multimailhook.refFilterDontSendRegex
+ **Warning:** these options are experimental. They should work, but
+ the user-interface is not stable yet (in particular, the option
+ names may change). If you want to participate in stabilizing the
+ feature, please contact the maintainers and/or send pull-requests.
+ If you are happy with the current shape of the feature, please
+ report it too.
+
+ Regular expressions that can be used to limit refs for which email
+ updates will be sent. It is an error to specify both an inclusion
+ and an exclusion regex. If a ``refFilterInclusionRegex`` is
+ specified, emails will only be sent for refs which match this
+ regex. If a ``refFilterExclusionRegex`` regex is specified,
+ emails will be sent for all refs except those that match this
+ regex (or that match a predefined regex specific to the
+ environment, such as "^refs/notes" for most environments and
+ "^refs/notes|^refs/changes" for the gerrit environment).
+
+ The expressions are matched against the complete refname, and is
+ considered to match if any substring matches. For example, to
+ filter-out all tags, set ``refFilterExclusionRegex`` to
+ ``^refs/tags/`` (note the leading ``^`` but no trailing ``$``). If
+ you set ``refFilterExclusionRegex`` to ``master``, then any ref
+ containing ``master`` will be excluded (the ``master`` branch, but
+ also ``refs/tags/master`` or ``refs/heads/foo-master-bar``).
+
+ ``refFilterDoSendRegex`` and ``refFilterDontSendRegex`` are
+ analogous to ``refFilterInclusionRegex`` and
+ ``refFilterExclusionRegex`` with one difference: with
+ ``refFilterDoSendRegex`` and ``refFilterDontSendRegex``, commits
+ introduced by one excluded ref will not be considered as new when
+ they reach an included ref. Typically, if you add a branch ``foo``
+ to ``refFilterDontSendRegex``, push commits to this branch, and
+ later merge branch ``foo`` into ``master``, then the notification
+ email for ``master`` will contain a commit email only for the
+ merge commit. If you include ``foo`` in
+ ``refFilterExclusionRegex``, then at the time of merge, you will
+ receive one commit email per commit in the branch.
+
+ These variables can be multi-valued, like::
+
+ [multimailhook]
+ refFilterExclusionRegex = ^refs/tags/
+ refFilterExclusionRegex = ^refs/heads/master$
+
+ You can also provide a whitespace-separated list like::
+
+ [multimailhook]
+ refFilterExclusionRegex = ^refs/tags/ ^refs/heads/master$
+
+ Both examples exclude tags and the master branch, and are
+ equivalent to::
+
+ [multimailhook]
+ refFilterExclusionRegex = ^refs/tags/|^refs/heads/master$
+
+ ``refFilterInclusionRegex`` and ``refFilterExclusionRegex`` are
+ strictly stronger than ``refFilterDoSendRegex`` and
+ ``refFilterDontSendRegex``. In other words, adding a ref to a
+ DoSend/DontSend regex has no effect if it is already excluded by a
+ Exclusion/Inclusion regex.
+
+multimailhook.logFile, multimailhook.errorLogFile, multimailhook.debugLogFile
+
+ When set, these variable designate path to files where
+ git-multimail will log some messages. Normal messages and error
+ messages are sent to ``logFile``, and error messages are also sent
+ to ``errorLogFile``. Debug messages and all other messages are
+ sent to ``debugLogFile``. The recommended way is to set only one
+ of these variables, but it is also possible to set several of them
+ (part of the information is then duplicated in several log files,
+ for example errors are duplicated to all log files).
+
+ Relative path are relative to the Git repository where the push is
+ done.
+
+multimailhook.verbose
+
+ Verbosity level of git-multimail on its standard output. By
+ default, show only error and info messages. If set to true, show
+ also debug messages.
+
+Email filtering aids
+--------------------
+
+All emails include extra headers to enable fine tuned filtering and
+give information for debugging. All emails include the headers
+``X-Git-Host``, ``X-Git-Repo``, ``X-Git-Refname``, and ``X-Git-Reftype``.
+ReferenceChange emails also include headers ``X-Git-Oldrev`` and ``X-Git-Newrev``;
+Revision emails also include header ``X-Git-Rev``.
+
+
+Customizing email contents
+--------------------------
+
+git-multimail mostly generates emails by expanding templates. The
+templates can be customized. To avoid the need to edit
+``git_multimail.py`` directly, the preferred way to change the templates
+is to write a separate Python script that imports ``git_multimail.py`` as
+a module, then replaces the templates in place. See the provided
+post-receive script for an example of how this is done.
+
+
+Customizing git-multimail for your environment
+----------------------------------------------
+
+git-multimail is mostly customized via an "environment" that describes
+the local environment in which Git is running. Two types of
+environment are built in:
+
+GenericEnvironment
+ a stand-alone Git repository.
+
+GitoliteEnvironment
+ a Git repository that is managed by gitolite_. For such
+ repositories, the identity of the pusher is read from
+ environment variable $GL_USER, the name of the repository is read
+ from $GL_REPO (if it is not overridden by multimailhook.reponame),
+ and the From: header value is optionally read from gitolite.conf
+ (see multimailhook.from).
+
+By default, git-multimail assumes GitoliteEnvironment if $GL_USER and
+$GL_REPO are set, and otherwise assumes GenericEnvironment.
+Alternatively, you can choose one of these two environments explicitly
+by setting a ``multimailhook.environment`` config setting (which can
+have the value `generic` or `gitolite`) or by passing an --environment
+option to the script.
+
+If you need to customize the script in ways that are not supported by
+the existing environments, you can define your own environment class
+class using arbitrary Python code. To do so, you need to import
+``git_multimail.py`` as a Python module, as demonstrated by the example
+post-receive script. Then implement your environment class; it should
+usually inherit from one of the existing Environment classes and
+possibly one or more of the EnvironmentMixin classes. Then set the
+``environment`` variable to an instance of your own environment class
+and pass it to ``run_as_post_receive_hook()``.
+
+The standard environment classes, GenericEnvironment and
+GitoliteEnvironment, are in fact themselves put together out of a
+number of mixin classes, each of which handles one aspect of the
+customization. For the finest control over your configuration, you
+can specify exactly which mixin classes your own environment class
+should inherit from, and override individual methods (or even add your
+own mixin classes) to implement entirely new behaviors. If you
+implement any mixins that might be useful to other people, please
+consider sharing them with the community!
+
+
+Getting involved
+----------------
+
+Please, read `<CONTRIBUTING.rst>`__ for instructions on how to
+contribute to git-multimail.
+
+
+Footnotes
+---------
+
+.. [1] Because of the way information is passed to update hooks, the
+ script's method of determining whether a commit has already
+ been seen does not work when it is used as an ``update`` script.
+ In particular, no notification email will be generated for a
+ new commit that is added to multiple references in the same
+ push. A workaround is to use --force-send to force sending the
+ emails.
+
+.. _gitolite: https://github.com/sitaramc/gitolite
config multimailhook.mailingList = # Where emails should be sent
config multimailhook.from = # From address to use
+Note that by default, gitolite forbids ``<`` and ``>`` in variable
+values (for security/paranoia reasons, see
+`compensating for UNSAFE_PATT
+<http://gitolite.com/gitolite/git-config/index.html#compensating-for-unsafe95patt>`__
+in gitolite's documentation for explanations and a way to disable
+this). As a consequence, you will not be able to use ``First Last
+<First.Last@example.com>`` as recipient email, but specifying
+``First.Last@example.com`` alone works.
+
Obviously, you can customize all parameters on a per-repository basis by
adding these ``config multimailhook.*`` lines in the section
corresponding to a repository or set of repositories.
#! /usr/bin/env python
-__version__ = '1.4.0'
+__version__ = '1.5.0'
# Copyright (c) 2015-2016 Matthieu Moy and others
# Copyright (c) 2012-2014 Michael Haggerty and others
# Python < 2.6 do not have ssl, but that's OK if we don't use it.
pass
import time
-import cgi
+
+import uuid
+import base64
PYTHON3 = sys.version_info >= (3, 0)
for element in iterable:
if not element:
return False
- return True
+ return True
def is_ascii(s):
return out.decode(sys.getdefaultencoding())
except UnicodeEncodeError:
return out.decode(ENCODING)
+
+ import html
+
+ def html_escape(s):
+ return html.escape(s)
+
else:
def is_string(s):
try:
def next(it):
return it.next()
+ import cgi
+
+ def html_escape(s):
+ return cgi.escape(s, True)
try:
from email.charset import Charset
Message-ID: %(msgid)s
From: %(fromaddr)s
Reply-To: %(reply_to)s
+Thread-Index: %(thread_index)s
X-Git-Host: %(fqdn)s
X-Git-Repo: %(repo_shortname)s
X-Git-Refname: %(refname)s
Reply-To: %(reply_to)s
In-Reply-To: %(reply_to_msgid)s
References: %(reply_to_msgid)s
+Thread-Index: %(thread_index)s
X-Git-Host: %(fqdn)s
X-Git-Repo: %(repo_shortname)s
X-Git-Refname: %(refname)s
def __eq__(self, other):
return isinstance(other, GitObject) and self.sha1 == other.sha1
+ def __ne__(self, other):
+ return not self == other
+
def __hash__(self):
return hash(self.sha1)
if html_escape_val:
for k in values:
if is_string(values[k]):
- values[k] = cgi.escape(values[k], True)
+ values[k] = html_escape(values[k])
for line in template.splitlines(True):
yield line % values
raise NotImplementedError()
- def generate_email_body(self):
+ def generate_email_body(self, push):
"""Generate the main part of the email body, a line at a time.
The text in the body might be truncated after a specified
yield "<pre style='margin:0'>\n"
for line in lines:
- yield cgi.escape(line)
+ yield html_escape(line)
yield '</pre>\n'
else:
fgcolor = '404040'
# Chop the trailing LF, we don't want it inside <pre>.
- line = cgi.escape(line[:-1])
+ line = html_escape(line[:-1])
if bgcolor or fgcolor:
style = 'display:block; white-space:pre;'
self.author = read_git_output(['log', '--no-walk', '--format=%aN <%aE>', self.rev.sha1])
self.recipients = self.environment.get_revision_recipients(self)
+ # -s is short for --no-patch, but -s works on older git's (e.g. 1.7)
+ self.parents = read_git_lines(['show', '-s', '--format=%P',
+ self.rev.sha1])[0].split()
+
self.cc_recipients = ''
if self.environment.get_scancommitforcc():
self.cc_recipients = ', '.join(to.strip() for to in self._cc_recipients())
oneline = oneline[:max_subject_length - 6] + ' [...]'
values['rev'] = self.rev.sha1
+ values['parents'] = ' '.join(self.parents)
values['rev_short'] = self.rev.short
values['change_type'] = self.change_type
values['refname'] = self.refname
values['short_refname'] = self.reference_change.short_refname
values['refname_type'] = self.reference_change.refname_type
values['reply_to_msgid'] = self.reference_change.msgid
+ values['thread_index'] = self.reference_change.thread_index
values['num'] = self.num
values['tot'] = self.tot
values['recipients'] = self.recipients
old=old, new=new, rev=rev,
)
+ @staticmethod
+ def make_thread_index():
+ """Return a string appropriate for the Thread-Index header,
+ needed by MS Outlook to get threading right.
+
+ The format is (base64-encoded):
+ - 1 byte must be 1
+ - 5 bytes encode a date (hardcoded here)
+ - 16 bytes for a globally unique identifier
+
+ FIXME: Unfortunately, even with the Thread-Index field, MS
+ Outlook doesn't seem to do the threading reliably (see
+ https://github.com/git-multimail/git-multimail/pull/194).
+ """
+ thread_index = b'\x01\x00\x00\x12\x34\x56' + uuid.uuid4().bytes
+ return base64.standard_b64encode(thread_index).decode('ascii')
+
def __init__(self, environment, refname, short_refname, old, new, rev):
Change.__init__(self, environment)
self.change_type = {
self.new = new
self.rev = rev
self.msgid = make_msgid()
+ self.thread_index = self.make_thread_index()
self.diffopts = environment.diffopts
self.graphopts = environment.graphopts
self.logopts = environment.logopts
values['refname'] = self.refname
values['short_refname'] = self.short_refname
values['msgid'] = self.msgid
+ values['thread_index'] = self.thread_index
values['recipients'] = self.recipients
values['oldrev'] = str(self.old)
values['oldrev_short'] = self.old.short
def __init__(self, environment):
self.environment = environment
+ def close(self):
+ pass
+
def send(self, lines, to_addrs):
"""Send an email consisting of lines.
self.username = smtpuser
self.password = smtppass
self.smtpcacerts = smtpcacerts
+ self.loggedin = False
try:
def call(klass, server, timeout):
try:
% (self.smtpserver, sys.exc_info()[1]))
sys.exit(1)
- def __del__(self):
+ def close(self):
if hasattr(self, 'smtp'):
self.smtp.quit()
del self.smtp
+ def __del__(self):
+ self.close()
+
def send(self, lines, to_addrs):
try:
if self.username or self.password:
- self.smtp.login(self.username, self.password)
+ if not self.loggedin:
+ self.smtp.login(self.username, self.password)
+ self.loggedin = True
msg = ''.join(lines)
# turn comma-separated list into Python list if needed.
if is_string(to_addrs):
to_addrs = [email for (name, email) in getaddresses([to_addrs])]
self.smtp.sendmail(self.envelopesender, to_addrs, msg)
+ except socket.timeout:
+ self.environment.get_logger().error(
+ '*** Error sending email ***\n'
+ '*** SMTP server timed out (timeout is %s)\n'
+ % self.smtpservertimeout)
except smtplib.SMTPResponseException:
err = sys.exc_info()[1]
self.environment.get_logger().error(
SEPARATOR = '=' * 75 + '\n'
- def __init__(self, f):
+ def __init__(self, f, environment=None):
+ super(OutputMailer, self).__init__(environment=environment)
self.f = f
def send(self, lines, to_addrs):
self.html_in_footer = False
self.commitBrowseURL = None
self.maxcommitemails = 500
+ self.excludemergerevisions = False
self.diffopts = ['--stat', '--summary', '--find-copies-harder']
self.graphopts = ['--oneline', '--decorate']
self.logopts = []
self.commitBrowseURL = config.get('commitBrowseURL')
+ self.excludemergerevisions = config.get('excludeMergeRevisions')
+
maxcommitemails = config.get('maxcommitemails')
if maxcommitemails is not None:
try:
return self.osenv.get('GL_USER', 'unknown user')
-class GitoliteEnvironmentLowPrecMixin(Environment):
+class GitoliteEnvironmentLowPrecMixin(
+ ConfigEnvironmentMixin,
+ Environment):
+
def get_repo_shortname(self):
# The gitolite environment variable $GL_REPO is a pretty good
# repo_shortname (though it's probably not as good as a value
super(GitoliteEnvironmentLowPrecMixin, self).get_repo_shortname()
)
+ @staticmethod
+ def _compile_regex(re_template):
+ return (
+ re.compile(re_template % x)
+ for x in (
+ r'BEGIN\s+USER\s+EMAILS',
+ r'([^\s]+)\s+(.*)',
+ r'END\s+USER\s+EMAILS',
+ ))
+
def get_fromaddr(self, change=None):
GL_USER = self.osenv.get('GL_USER')
if GL_USER is not None:
GL_CONF = self.osenv.get(
'GL_CONF',
os.path.join(GL_ADMINDIR, 'conf', 'gitolite.conf'))
+
+ mailaddress_map = self.config.get('MailaddressMap')
+ # If relative, consider relative to GL_CONF:
+ if mailaddress_map:
+ mailaddress_map = os.path.join(os.path.dirname(GL_CONF),
+ mailaddress_map)
+ if os.path.isfile(mailaddress_map):
+ f = open(mailaddress_map, 'rU')
+ try:
+ # Leading '#' is optional
+ re_begin, re_user, re_end = self._compile_regex(
+ r'^(?:\s*#)?\s*%s\s*$')
+ for l in f:
+ l = l.rstrip('\n')
+ if re_begin.match(l) or re_end.match(l):
+ continue # Ignore these lines
+ m = re_user.match(l)
+ if m:
+ if m.group(1) == GL_USER:
+ return m.group(2)
+ else:
+ continue # Not this user, but not an error
+ raise ConfigurationException(
+ "Syntax error in mail address map.\n"
+ "Check file {}.\n"
+ "Line: {}".format(mailaddress_map, l))
+
+ finally:
+ f.close()
+
if os.path.isfile(GL_CONF):
f = open(GL_CONF, 'rU')
try:
in_user_emails_section = False
- re_template = r'^\s*#\s*%s\s*$'
- re_begin, re_user, re_end = (
- re.compile(re_template % x)
- for x in (
- r'BEGIN\s+USER\s+EMAILS',
- re.escape(GL_USER) + r'\s+(.*)',
- r'END\s+USER\s+EMAILS',
- ))
+ re_begin, re_user, re_end = self._compile_regex(
+ r'^\s*#\s*%s\s*$')
for l in f:
l = l.rstrip('\n')
if not in_user_emails_section:
if re_end.match(l):
break
m = re_user.match(l)
- if m:
- return m.group(1)
+ if m and m.group(1) == GL_USER:
+ return m.group(2)
finally:
f.close()
return super(GitoliteEnvironmentLowPrecMixin, self).get_fromaddr(change)
self.__repo = repo
def get_pusher(self):
- return re.match('(.*?)\s*<', self.__user).group(1)
+ return re.match(r'(.*?)\s*<', self.__user).group(1)
def get_pusher_email(self):
return self.__user
if self.__submitter.find('<') != -1:
# Submitter has a configured email, we transformed
# __submitter into an RFC 2822 string already.
- return re.match('(.*?)\s*<', self.__submitter).group(1)
+ return re.match(r'(.*?)\s*<', self.__submitter).group(1)
else:
# Submitter has no configured email, it's just his name.
return self.__submitter
for (num, sha1) in enumerate(sha1s):
rev = Revision(change, GitObject(sha1), num=num + 1, tot=len(sha1s))
+ if len(rev.parents) > 1 and change.environment.excludemergerevisions:
+ # skipping a merge commit
+ continue
if not rev.recipients and rev.cc_recipients:
change.environment.log_msg('*** Replacing Cc: with To:')
rev.recipients = rev.cc_recipients
changes.append(
ReferenceChange.create(environment, oldrev, newrev, refname)
)
- if changes:
- push = Push(environment, changes)
+ if not changes:
+ mailer.close()
+ return
+ push = Push(environment, changes)
+ try:
push.send_emails(mailer, body_filter=environment.filter_body)
- if hasattr(mailer, '__del__'):
- mailer.__del__()
+ finally:
+ mailer.close()
def run_as_update_hook(environment, mailer, refname, oldrev, newrev, force_send=False):
refname,
),
]
+ if not changes:
+ mailer.close()
+ return
push = Push(environment, changes, force_send)
- push.send_emails(mailer, body_filter=environment.filter_body)
- if hasattr(mailer, '__del__'):
- mailer.__del__()
+ try:
+ push.send_emails(mailer, body_filter=environment.filter_body)
+ finally:
+ mailer.close()
def check_ref_filter(environment):
low_prec_mixin = known_env['lowprec']
environment_mixins.append(low_prec_mixin)
environment_mixins.append(Environment)
- klass_name = env_name.capitalize() + 'Environement'
+ klass_name = env_name.capitalize() + 'Environment'
environment_klass = type(
klass_name,
tuple(environment_mixins),
environment, 'git_multimail.error', environment.error_log_file, logging.ERROR)
self.loggers.append(error_log_file)
- def info(self, msg):
+ def info(self, msg, *args, **kwargs):
for l in self.loggers:
- l.info(msg)
+ l.info(msg, *args, **kwargs)
- def debug(self, msg):
+ def debug(self, msg, *args, **kwargs):
for l in self.loggers:
- l.debug(msg)
+ l.debug(msg, *args, **kwargs)
- def warning(self, msg):
+ def warning(self, msg, *args, **kwargs):
for l in self.loggers:
- l.warning(msg)
+ l.warning(msg, *args, **kwargs)
- def error(self, msg):
+ def error(self, msg, *args, **kwargs):
for l in self.loggers:
- l.error(msg)
+ l.error(msg, *args, **kwargs)
def main(args):
show_env(environment, sys.stderr)
if options.stdout or environment.stdout:
- mailer = OutputMailer(sys.stdout)
+ mailer = OutputMailer(sys.stdout, environment)
else:
mailer = choose_mailer(config, environment)
sys.stderr.write(msg)
sys.exit(1)
+
if __name__ == '__main__':
main(sys.argv[1:])
try:
read_output(
- ['git', 'config']
- + local_option
- + ['--get-regexp', '^%s\.' % (section,)]
+ ['git', 'config'] +
+ local_option +
+ ['--get-regexp', '^%s\.' % (section,)]
)
- except CommandError, e:
+ except CommandError:
+ t, e, traceback = sys.exc_info()
if e.retcode == 1:
# This means that no settings were found.
return True
sys.stderr.write(
'...copying "%s.%s" to "%s.%s"\n' % (old.section, name, new.section, name)
)
- new.set_recipients(name, old.get_recipients(name))
+ old_recipients = old.get_all(name, default=None)
+ old_recipients = ', '.join(o.strip() for o in old_recipients)
+ new.set_recipients(name, old_recipients)
if strict:
sys.stderr.write(
"""
import sys
-import os
# If necessary, add the path to the directory containing
# git_multimail.py to the Python path as follows. (This is not
# Use Python's smtplib to send emails. Both arguments are required.
#mailer = git_multimail.SMTPMailer(
+# environment=environment,
# envelopesender='git-repo@example.com',
# # The smtpserver argument can also include a port number; e.g.,
# # smtpserver='mail.example.com:25'
-#define NO_THE_INDEX_COMPATIBILITY_MACROS
#include "cache.h"
#include "config.h"
#include "object-store.h"
* The same heuristics as diff.c::mmfile_is_binary()
* We treat files with bare CR as binary
*/
-static int convert_is_binary(unsigned long size, const struct text_stat *stats)
+static int convert_is_binary(const struct text_stat *stats)
{
if (stats->lonecr)
return 1;
if (!data || !size)
return 0;
gather_stats(data, size, &stats);
- if (convert_is_binary(size, &stats))
+ if (convert_is_binary(&stats))
ret |= CONVERT_STAT_BITS_BIN;
if (stats.crlf)
ret |= CONVERT_STAT_BITS_TXT_CRLF;
return has_crlf;
}
-static int will_convert_lf_to_crlf(size_t len, struct text_stat *stats,
+static int will_convert_lf_to_crlf(struct text_stat *stats,
enum crlf_action crlf_action)
{
if (output_eol(crlf_action) != EOL_CRLF)
if (stats->lonecr || stats->crlf)
return 0;
- if (convert_is_binary(len, stats))
+ if (convert_is_binary(stats))
return 0;
}
return 1;
convert_crlf_into_lf = !!stats.crlf;
if (crlf_action == CRLF_AUTO || crlf_action == CRLF_AUTO_INPUT || crlf_action == CRLF_AUTO_CRLF) {
- if (convert_is_binary(len, &stats))
+ if (convert_is_binary(&stats))
return 0;
/*
* If the file in the index has any CR in it, do not
new_stats.crlf = 0;
}
/* simulate "git checkout" */
- if (will_convert_lf_to_crlf(len, &new_stats, crlf_action)) {
+ if (will_convert_lf_to_crlf(&new_stats, crlf_action)) {
new_stats.crlf += new_stats.lonelf;
new_stats.lonelf = 0;
}
return 1;
}
-static int crlf_to_worktree(const char *path, const char *src, size_t len,
+static int crlf_to_worktree(const char *src, size_t len,
struct strbuf *buf, enum crlf_action crlf_action)
{
char *to_free = NULL;
return 0;
gather_stats(src, len, &stats);
- if (!will_convert_lf_to_crlf(len, &stats, crlf_action))
+ if (!will_convert_lf_to_crlf(&stats, crlf_action))
return 0;
/* are we "faking" in place editing ? */
}
static int apply_single_file_filter(const char *path, const char *src, size_t len, int fd,
- struct strbuf *dst, const char *cmd)
+ struct strbuf *dst, const char *cmd)
{
/*
* Create a pipeline to have the command filter the buffer's
static void handle_filter_error(const struct strbuf *filter_status,
struct cmd2process *entry,
- const unsigned int wanted_capability) {
+ const unsigned int wanted_capability)
+{
if (!strcmp(filter_status->buf, "error"))
; /* The filter signaled a problem with the file. */
else if (!strcmp(filter_status->buf, "abort") && wanted_capability) {
return cnt;
}
-static int ident_to_git(const char *path, const char *src, size_t len,
- struct strbuf *buf, int ident)
+static int ident_to_git(const char *src, size_t len,
+ struct strbuf *buf, int ident)
{
char *dst, *dollar;
return 1;
}
-static int ident_to_worktree(const char *path, const char *src, size_t len,
- struct strbuf *buf, int ident)
+static int ident_to_worktree(const char *src, size_t len,
+ struct strbuf *buf, int ident)
{
struct object_id oid;
char *to_free = NULL, *dollar, *spc;
len = dst->len;
}
}
- return ret | ident_to_git(path, src, len, dst, ca.ident);
+ return ret | ident_to_git(src, len, dst, ca.ident);
}
void convert_to_git_filter_fd(const struct index_state *istate,
encode_to_git(path, dst->buf, dst->len, dst, ca.working_tree_encoding, conv_flags);
crlf_to_git(istate, path, dst->buf, dst->len, dst, ca.crlf_action, conv_flags);
- ident_to_git(path, dst->buf, dst->len, dst, ca.ident);
+ ident_to_git(dst->buf, dst->len, dst, ca.ident);
}
static int convert_to_working_tree_internal(const struct index_state *istate,
convert_attrs(istate, &ca, path);
- ret |= ident_to_worktree(path, src, len, dst, ca.ident);
+ ret |= ident_to_worktree(src, len, dst, ca.ident);
if (ret) {
src = dst->buf;
len = dst->len;
* support smudge). The filters might expect CRLFs.
*/
if ((ca.drv && (ca.drv->smudge || ca.drv->process)) || !normalizing) {
- ret |= crlf_to_worktree(path, src, len, dst, ca.crlf_action);
+ ret |= crlf_to_worktree(src, len, dst, ca.crlf_action);
if (ret) {
src = dst->buf;
len = dst->len;
}
static int read_request(FILE *fh, struct credential *c,
- struct strbuf *action, int *timeout) {
+ struct strbuf *action, int *timeout)
+{
static struct strbuf item = STRBUF_INIT;
const char *p;
}
/*
- * What value of "tz" was in effect back then at "time" in the
- * local timezone?
+ * Fill in the localtime 'struct tm' for the supplied time,
+ * and return the local tz.
*/
-static int local_tzoffset(timestamp_t time)
+static int local_time_tzoffset(time_t t, struct tm *tm)
{
- time_t t, t_local;
- struct tm tm;
+ time_t t_local;
int offset, eastwest;
- if (date_overflows(time))
- die("Timestamp too large for this system: %"PRItime, time);
-
- t = (time_t)time;
- localtime_r(&t, &tm);
- t_local = tm_to_time_t(&tm);
-
+ localtime_r(&t, tm);
+ t_local = tm_to_time_t(tm);
if (t_local == -1)
return 0; /* error; just use +0000 */
if (t_local < t) {
return offset * eastwest;
}
-void show_date_relative(timestamp_t time, int tz,
- const struct timeval *now,
- struct strbuf *timebuf)
+/*
+ * What value of "tz" was in effect back then at "time" in the
+ * local timezone?
+ */
+static int local_tzoffset(timestamp_t time)
+{
+ struct tm tm;
+
+ if (date_overflows(time))
+ die("Timestamp too large for this system: %"PRItime, time);
+
+ return local_time_tzoffset((time_t)time, &tm);
+}
+
+static void get_time(struct timeval *now)
+{
+ const char *x;
+
+ x = getenv("GIT_TEST_DATE_NOW");
+ if (x) {
+ now->tv_sec = atoi(x);
+ now->tv_usec = 0;
+ }
+ else
+ gettimeofday(now, NULL);
+}
+
+void show_date_relative(timestamp_t time,
+ const struct timeval *now,
+ struct strbuf *timebuf)
{
timestamp_t diff;
if (now->tv_sec < time) {
return &mode;
}
+static void show_date_normal(struct strbuf *buf, timestamp_t time, struct tm *tm, int tz, struct tm *human_tm, int human_tz, int local)
+{
+ struct {
+ unsigned int year:1,
+ date:1,
+ wday:1,
+ time:1,
+ seconds:1,
+ tz:1;
+ } hide = { 0 };
+
+ hide.tz = local || tz == human_tz;
+ hide.year = tm->tm_year == human_tm->tm_year;
+ if (hide.year) {
+ if (tm->tm_mon == human_tm->tm_mon) {
+ if (tm->tm_mday > human_tm->tm_mday) {
+ /* Future date: think timezones */
+ } else if (tm->tm_mday == human_tm->tm_mday) {
+ hide.date = hide.wday = 1;
+ } else if (tm->tm_mday + 5 > human_tm->tm_mday) {
+ /* Leave just weekday if it was a few days ago */
+ hide.date = 1;
+ }
+ }
+ }
+
+ /* Show "today" times as just relative times */
+ if (hide.wday) {
+ struct timeval now;
+ get_time(&now);
+ show_date_relative(time, &now, buf);
+ return;
+ }
+
+ /*
+ * Always hide seconds for human-readable.
+ * Hide timezone if showing date.
+ * Hide weekday and time if showing year.
+ *
+ * The logic here is two-fold:
+ * (a) only show details when recent enough to matter
+ * (b) keep the maximum length "similar", and in check
+ */
+ if (human_tm->tm_year) {
+ hide.seconds = 1;
+ hide.tz |= !hide.date;
+ hide.wday = hide.time = !hide.year;
+ }
+
+ if (!hide.wday)
+ strbuf_addf(buf, "%.3s ", weekday_names[tm->tm_wday]);
+ if (!hide.date)
+ strbuf_addf(buf, "%.3s %d ", month_names[tm->tm_mon], tm->tm_mday);
+
+ /* Do we want AM/PM depending on locale? */
+ if (!hide.time) {
+ strbuf_addf(buf, "%02d:%02d", tm->tm_hour, tm->tm_min);
+ if (!hide.seconds)
+ strbuf_addf(buf, ":%02d", tm->tm_sec);
+ } else
+ strbuf_rtrim(buf);
+
+ if (!hide.year)
+ strbuf_addf(buf, " %d", tm->tm_year + 1900);
+
+ if (!hide.tz)
+ strbuf_addf(buf, " %+05d", tz);
+}
+
const char *show_date(timestamp_t time, int tz, const struct date_mode *mode)
{
struct tm *tm;
+ struct tm human_tm = { 0 };
+ int human_tz = -1;
static struct strbuf timebuf = STRBUF_INIT;
if (mode->type == DATE_UNIX) {
return timebuf.buf;
}
+ if (mode->type == DATE_HUMAN) {
+ struct timeval now;
+
+ get_time(&now);
+
+ /* Fill in the data for "current time" in human_tz and human_tm */
+ human_tz = local_time_tzoffset(now.tv_sec, &human_tm);
+ }
+
if (mode->local)
tz = local_tzoffset(time);
struct timeval now;
strbuf_reset(&timebuf);
- gettimeofday(&now, NULL);
- show_date_relative(time, tz, &now, &timebuf);
+ get_time(&now);
+ show_date_relative(time, &now, &timebuf);
return timebuf.buf;
}
strbuf_addftime(&timebuf, mode->strftime_fmt, tm, tz,
!mode->local);
else
- strbuf_addf(&timebuf, "%.3s %.3s %d %02d:%02d:%02d %d%c%+05d",
- weekday_names[tm->tm_wday],
- month_names[tm->tm_mon],
- tm->tm_mday,
- tm->tm_hour, tm->tm_min, tm->tm_sec,
- tm->tm_year + 1900,
- mode->local ? 0 : ' ',
- tz);
+ show_date_normal(&timebuf, time, tm, tz, &human_tm, human_tz, mode->local);
return timebuf.buf;
}
return DATE_SHORT;
if (skip_prefix(format, "default", end))
return DATE_NORMAL;
+ if (skip_prefix(format, "human", end))
+ return DATE_HUMAN;
if (skip_prefix(format, "raw", end))
return DATE_RAW;
if (skip_prefix(format, "unix", end))
{
const char *p;
+ /* "auto:foo" is "if tty/pager, then foo, otherwise normal" */
+ if (skip_prefix(format, "auto:", &p)) {
+ if (isatty(1) || pager_in_use())
+ format = p;
+ else
+ format = "default";
+ }
+
/* historical alias */
if (!strcmp(format, "local"))
format = "default-local";
return timestamp;
}
- gettimeofday(&tv, NULL);
+ get_time(&tv);
return approxidate_str(date, &tv, error_ret);
}
island_bitmap_or(b, marks);
}
-static void mark_remote_island_1(struct remote_island *rl, int is_core_island)
+static void mark_remote_island_1(struct repository *r,
+ struct remote_island *rl,
+ int is_core_island)
{
uint32_t i;
for (i = 0; i < rl->oids.nr; ++i) {
struct island_bitmap *marks;
- struct object *obj = parse_object(the_repository, &rl->oids.oid[i]);
+ struct object *obj = parse_object(r, &rl->oids.oid[i]);
if (!obj)
continue;
while (obj && obj->type == OBJ_TAG) {
obj = ((struct tag *)obj)->tagged;
if (obj) {
- parse_object(the_repository, &obj->oid);
+ parse_object(r, &obj->oid);
marks = create_or_get_island_marks(obj);
island_bitmap_set(marks, island_counter);
}
return todo_a->depth - todo_b->depth;
}
-void resolve_tree_islands(int progress, struct packing_data *to_pack)
+void resolve_tree_islands(struct repository *r,
+ int progress,
+ struct packing_data *to_pack)
{
struct progress *progress_state = NULL;
struct tree_islands_todo *todo;
root_marks = kh_value(island_marks, pos);
- tree = lookup_tree(the_repository, &ent->idx.oid);
+ tree = lookup_tree(r, &ent->idx.oid);
if (!tree || parse_tree(tree) < 0)
die(_("bad tree object %s"), oid_to_hex(&ent->idx.oid));
if (S_ISGITLINK(entry.mode))
continue;
- obj = lookup_object(the_repository, entry.oid->hash);
+ obj = lookup_object(r, entry.oid.hash);
if (!obj)
continue;
return NULL;
}
-static void deduplicate_islands(void)
+static void deduplicate_islands(struct repository *r)
{
struct remote_island *island, *core = NULL, **list;
unsigned int island_count, dst, src, ref, i = 0;
core = get_core_island();
for (i = 0; i < island_count; ++i) {
- mark_remote_island_1(list[i], core && list[i]->hash == core->hash);
+ mark_remote_island_1(r, list[i], core && list[i]->hash == core->hash);
}
free(list);
}
-void load_delta_islands(void)
+void load_delta_islands(struct repository *r)
{
island_marks = kh_init_sha1();
remote_islands = kh_init_str();
git_config(island_config_callback, NULL);
for_each_ref(find_island_for_ref, NULL);
- deduplicate_islands();
+ deduplicate_islands(r);
fprintf(stderr, _("Marked %d islands, done.\n"), island_counter);
}
#ifndef DELTA_ISLANDS_H
#define DELTA_ISLANDS_H
+struct commit;
struct object_id;
struct packing_data;
-struct commit;
+struct repository;
int island_delta_cmp(const struct object_id *a, const struct object_id *b);
int in_same_island(const struct object_id *, const struct object_id *);
-void resolve_tree_islands(int progress, struct packing_data *to_pack);
-void load_delta_islands(void);
+void resolve_tree_islands(struct repository *r,
+ int progress,
+ struct packing_data *to_pack);
+void load_delta_islands(struct repository *r);
void propagate_island_marks(struct commit *commit);
int compute_pack_layers(struct packing_data *to_pack);
{
struct rev_info revs;
- repo_init_revisions(the_repository, &revs, NULL);
+ repo_init_revisions(opt->repo, &revs, NULL);
copy_pathspec(&revs.prune_data, &opt->pathspec);
revs.diffopt = *opt;
return 0;
}
-int index_differs_from(const char *def, const struct diff_flags *flags,
+int index_differs_from(struct repository *r,
+ const char *def, const struct diff_flags *flags,
int ita_invisible_in_index)
{
struct rev_info rev;
struct setup_revision_opt opt;
- repo_init_revisions(the_repository, &rev, NULL);
+ repo_init_revisions(r, &rev, NULL);
memset(&opt, 0, sizeof(opt));
opt.def = def;
setup_revisions(0, NULL, &rev, &opt);
return error(_("color moved setting must be one of 'no', 'default', 'blocks', 'zebra', 'dimmed-zebra', 'plain'"));
}
-static int parse_color_moved_ws(const char *arg)
+static unsigned parse_color_moved_ws(const char *arg)
{
int ret = 0;
struct string_list l = STRING_LIST_INIT_DUP;
strbuf_addstr(&sb, i->string);
strbuf_trim(&sb);
- if (!strcmp(sb.buf, "ignore-space-change"))
+ if (!strcmp(sb.buf, "no"))
+ ret = 0;
+ else if (!strcmp(sb.buf, "ignore-space-change"))
ret |= XDF_IGNORE_WHITESPACE_CHANGE;
else if (!strcmp(sb.buf, "ignore-space-at-eol"))
ret |= XDF_IGNORE_WHITESPACE_AT_EOL;
ret |= XDF_IGNORE_WHITESPACE;
else if (!strcmp(sb.buf, "allow-indentation-change"))
ret |= COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE;
- else
- error(_("ignoring unknown color-moved-ws mode '%s'"), sb.buf);
+ else {
+ ret |= COLOR_MOVED_WS_ERROR;
+ error(_("unknown color-moved-ws mode '%s', possible values are 'ignore-space-change', 'ignore-space-at-eol', 'ignore-all-space', 'allow-indentation-change'"), sb.buf);
+ }
strbuf_release(&sb);
}
if ((ret & COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE) &&
- (ret & XDF_WHITESPACE_FLAGS))
- die(_("color-moved-ws: allow-indentation-change cannot be combined with other white space modes"));
+ (ret & XDF_WHITESPACE_FLAGS)) {
+ error(_("color-moved-ws: allow-indentation-change cannot be combined with other whitespace modes"));
+ ret |= COLOR_MOVED_WS_ERROR;
+ }
string_list_clear(&l, 0);
return 0;
}
if (!strcmp(var, "diff.colormovedws")) {
- int cm = parse_color_moved_ws(value);
- if (cm < 0)
+ unsigned cm = parse_color_moved_ws(value);
+ if (cm & COLOR_MOVED_WS_ERROR)
return -1;
diff_color_moved_ws_default = cm;
return 0;
if (done_preparing)
return external_diff_cmd;
- external_diff_cmd = getenv("GIT_EXTERNAL_DIFF");
+ external_diff_cmd = xstrdup_or_null(getenv("GIT_EXTERNAL_DIFF"));
if (!external_diff_cmd)
external_diff_cmd = external_diff_cmd_cfg;
done_preparing = 1;
const char *line;
int len;
int flags;
+ int indent_off; /* Offset to first non-whitespace character */
+ int indent_width; /* The visual width of the indentation */
enum diff_symbol s;
};
#define EMITTED_DIFF_SYMBOL_INIT {NULL}
struct moved_entry *next_line;
};
-/**
- * The struct ws_delta holds white space differences between moved lines, i.e.
- * between '+' and '-' lines that have been detected to be a move.
- * The string contains the difference in leading white spaces, before the
- * rest of the line is compared using the white space config for move
- * coloring. The current_longer indicates if the first string in the
- * comparision is longer than the second.
- */
-struct ws_delta {
- char *string;
- unsigned int current_longer : 1;
-};
-#define WS_DELTA_INIT { NULL, 0 }
-
struct moved_block {
struct moved_entry *match;
- struct ws_delta wsd;
+ int wsd; /* The whitespace delta of this block */
};
static void moved_block_clear(struct moved_block *b)
{
- FREE_AND_NULL(b->wsd.string);
- b->match = NULL;
+ memset(b, 0, sizeof(*b));
}
-static int compute_ws_delta(const struct emitted_diff_symbol *a,
- const struct emitted_diff_symbol *b,
- struct ws_delta *out)
+#define INDENT_BLANKLINE INT_MIN
+
+static void fill_es_indent_data(struct emitted_diff_symbol *es)
{
- const struct emitted_diff_symbol *longer = a->len > b->len ? a : b;
- const struct emitted_diff_symbol *shorter = a->len > b->len ? b : a;
- int d = longer->len - shorter->len;
+ unsigned int off = 0, i;
+ int width = 0, tab_width = es->flags & WS_TAB_WIDTH_MASK;
+ const char *s = es->line;
+ const int len = es->len;
+
+ /* skip any \v \f \r at start of indentation */
+ while (s[off] == '\f' || s[off] == '\v' ||
+ (s[off] == '\r' && off < len - 1))
+ off++;
+
+ /* calculate the visual width of indentation */
+ while(1) {
+ if (s[off] == ' ') {
+ width++;
+ off++;
+ } else if (s[off] == '\t') {
+ width += tab_width - (width % tab_width);
+ while (s[++off] == '\t')
+ width += tab_width;
+ } else {
+ break;
+ }
+ }
+
+ /* check if this line is blank */
+ for (i = off; i < len; i++)
+ if (!isspace(s[i]))
+ break;
+
+ if (i == len) {
+ es->indent_width = INDENT_BLANKLINE;
+ es->indent_off = len;
+ } else {
+ es->indent_off = off;
+ es->indent_width = width;
+ }
+}
- if (strncmp(longer->line + d, shorter->line, shorter->len))
+static int compute_ws_delta(const struct emitted_diff_symbol *a,
+ const struct emitted_diff_symbol *b,
+ int *out)
+{
+ int a_len = a->len,
+ b_len = b->len,
+ a_off = a->indent_off,
+ a_width = a->indent_width,
+ b_off = b->indent_off,
+ b_width = b->indent_width;
+ int delta;
+
+ if (a_width == INDENT_BLANKLINE && b_width == INDENT_BLANKLINE) {
+ *out = INDENT_BLANKLINE;
+ return 1;
+ }
+
+ if (a->s == DIFF_SYMBOL_PLUS)
+ delta = a_width - b_width;
+ else
+ delta = b_width - a_width;
+
+ if (a_len - a_off != b_len - b_off ||
+ memcmp(a->line + a_off, b->line + b_off, a_len - a_off))
return 0;
- out->string = xmemdupz(longer->line, d);
- out->current_longer = (a == longer);
+ *out = delta;
return 1;
}
int n)
{
struct emitted_diff_symbol *l = &o->emitted_symbols->buf[n];
- int al = cur->es->len, cl = l->len;
+ int al = cur->es->len, bl = match->es->len, cl = l->len;
const char *a = cur->es->line,
*b = match->es->line,
*c = l->line;
-
- int wslen;
+ int a_off = cur->es->indent_off,
+ a_width = cur->es->indent_width,
+ c_off = l->indent_off,
+ c_width = l->indent_width;
+ int delta;
/*
- * We need to check if 'cur' is equal to 'match'.
- * As those are from the same (+/-) side, we do not need to adjust for
- * indent changes. However these were found using fuzzy matching
- * so we do have to check if they are equal.
+ * We need to check if 'cur' is equal to 'match'. As those
+ * are from the same (+/-) side, we do not need to adjust for
+ * indent changes. However these were found using fuzzy
+ * matching so we do have to check if they are equal. Here we
+ * just check the lengths. We delay calling memcmp() to check
+ * the contents until later as if the length comparison for a
+ * and c fails we can avoid the call all together.
*/
- if (strcmp(a, b))
+ if (al != bl)
return 1;
- if (!pmb->wsd.string)
- /*
- * The white space delta is not active? This can happen
- * when we exit early in this function.
- */
- return 1;
+ /* If 'l' and 'cur' are both blank then they match. */
+ if (a_width == INDENT_BLANKLINE && c_width == INDENT_BLANKLINE)
+ return 0;
/*
- * The indent changes of the block are known and stored in
- * pmb->wsd; however we need to check if the indent changes of the
- * current line are still the same as before.
- *
- * To do so we need to compare 'l' to 'cur', adjusting the
- * one of them for the white spaces, depending which was longer.
+ * The indent changes of the block are known and stored in pmb->wsd;
+ * however we need to check if the indent changes of the current line
+ * match those of the current block and that the text of 'l' and 'cur'
+ * after the indentation match.
*/
+ if (cur->es->s == DIFF_SYMBOL_PLUS)
+ delta = a_width - c_width;
+ else
+ delta = c_width - a_width;
- wslen = strlen(pmb->wsd.string);
- if (pmb->wsd.current_longer) {
- c += wslen;
- cl -= wslen;
- } else {
- a += wslen;
- al -= wslen;
- }
-
- if (al != cl || memcmp(a, c, al))
- return 1;
+ /*
+ * If the previous lines of this block were all blank then set its
+ * whitespace delta.
+ */
+ if (pmb->wsd == INDENT_BLANKLINE)
+ pmb->wsd = delta;
- return 0;
+ return !(delta == pmb->wsd && al - a_off == cl - c_off &&
+ !memcmp(a, b, al) && !
+ memcmp(a + a_off, c + c_off, al - a_off));
}
static int moved_entry_cmp(const void *hashmap_cmp_fn_data,
continue;
}
+ if (o->color_moved_ws_handling &
+ COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE)
+ fill_es_indent_data(&o->emitted_symbols->buf[n]);
key = prepare_entry(o, n);
if (prev_line && prev_line->es->s == o->emitted_symbols->buf[n].s)
prev_line->next_line = key;
if (lp < pmb_nr && rp > -1 && lp < rp) {
pmb[lp] = pmb[rp];
- pmb[rp].match = NULL;
- pmb[rp].wsd.string = NULL;
+ memset(&pmb[rp], 0, sizeof(pmb[rp]));
rp--;
lp++;
}
* The last block consists of the (n - block_length)'th line up to but not
* including the nth line.
*
+ * Returns 0 if the last block is empty or is unset by this function, non zero
+ * otherwise.
+ *
* NEEDSWORK: This uses the same heuristic as blame_entry_score() in blame.c.
* Think of a way to unify them.
*/
-static void adjust_last_block(struct diff_options *o, int n, int block_length)
+static int adjust_last_block(struct diff_options *o, int n, int block_length)
{
int i, alnum_count = 0;
if (o->color_moved == COLOR_MOVED_PLAIN)
- return;
+ return block_length;
for (i = 1; i < block_length + 1; i++) {
const char *c = o->emitted_symbols->buf[n - i].line;
for (; *c; c++) {
continue;
alnum_count++;
if (alnum_count >= COLOR_MOVED_MIN_ALNUM_COUNT)
- return;
+ return 1;
}
}
for (i = 1; i < block_length + 1; i++)
o->emitted_symbols->buf[n - i].flags &= ~DIFF_SYMBOL_MOVED_LINE;
+ return 0;
}
/* Find blocks of moved code, delegate actual coloring decision to helper */
{
struct moved_block *pmb = NULL; /* potentially moved blocks */
int pmb_nr = 0, pmb_alloc = 0;
- int n, flipped_block = 1, block_length = 0;
+ int n, flipped_block = 0, block_length = 0;
for (n = 0; n < o->emitted_symbols->nr; n++) {
struct moved_entry *key;
struct moved_entry *match = NULL;
struct emitted_diff_symbol *l = &o->emitted_symbols->buf[n];
+ enum diff_symbol last_symbol = 0;
switch (l->s) {
case DIFF_SYMBOL_PLUS:
free(key);
break;
default:
- flipped_block = 1;
+ flipped_block = 0;
}
if (!match) {
moved_block_clear(&pmb[i]);
pmb_nr = 0;
block_length = 0;
+ flipped_block = 0;
+ last_symbol = l->s;
continue;
}
- l->flags |= DIFF_SYMBOL_MOVED_LINE;
-
- if (o->color_moved == COLOR_MOVED_PLAIN)
+ if (o->color_moved == COLOR_MOVED_PLAIN) {
+ last_symbol = l->s;
+ l->flags |= DIFF_SYMBOL_MOVED_LINE;
continue;
+ }
if (o->color_moved_ws_handling &
COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE)
&pmb[pmb_nr].wsd))
pmb[pmb_nr++].match = match;
} else {
- pmb[pmb_nr].wsd.string = NULL;
+ pmb[pmb_nr].wsd = 0;
pmb[pmb_nr++].match = match;
}
}
- flipped_block = (flipped_block + 1) % 2;
+ if (adjust_last_block(o, n, block_length) &&
+ pmb_nr && last_symbol != l->s)
+ flipped_block = (flipped_block + 1) % 2;
+ else
+ flipped_block = 0;
- adjust_last_block(o, n, block_length);
block_length = 0;
}
- block_length++;
-
- if (flipped_block && o->color_moved != COLOR_MOVED_BLOCKS)
- l->flags |= DIFF_SYMBOL_MOVED_LINE_ALT;
+ if (pmb_nr) {
+ block_length++;
+ l->flags |= DIFF_SYMBOL_MOVED_LINE;
+ if (flipped_block && o->color_moved != COLOR_MOVED_BLOCKS)
+ l->flags |= DIFF_SYMBOL_MOVED_LINE_ALT;
+ }
+ last_symbol = l->s;
}
adjust_last_block(o, n, block_length);
static void emit_diff_symbol(struct diff_options *o, enum diff_symbol s,
const char *line, int len, unsigned flags)
{
- struct emitted_diff_symbol e = {line, len, flags, s};
+ struct emitted_diff_symbol e = {line, len, flags, 0, 0, s};
if (o->emitted_symbols)
append_emitted_diff_symbol(o, &e);
strbuf_release(&msgbuf);
}
-static struct diff_tempfile *claim_diff_tempfile(void) {
+static struct diff_tempfile *claim_diff_tempfile(void)
+{
int i;
for (i = 0; i < ARRAY_SIZE(diff_temp); i++)
if (!diff_temp[i].name)
options->b_prefix = b;
}
-struct userdiff_driver *get_textconv(struct index_state *istate,
+struct userdiff_driver *get_textconv(struct repository *r,
struct diff_filespec *one)
{
if (!DIFF_FILE_VALID(one))
return NULL;
- diff_filespec_load_driver(one, istate);
- return userdiff_get_textconv(one->driver);
+ diff_filespec_load_driver(one, r->index);
+ return userdiff_get_textconv(r, one->driver);
}
static void builtin_diff(const char *name_a,
}
if (o->flags.allow_textconv) {
- textconv_one = get_textconv(o->repo->index, one);
- textconv_two = get_textconv(o->repo->index, two);
+ textconv_one = get_textconv(o->repo, one);
+ textconv_two = get_textconv(o->repo, two);
}
/* Never use a non-valid filename anywhere if at all possible */
o->found_changes = 1;
} else {
/* Crazy xdl interfaces.. */
- const char *diffopts = getenv("GIT_DIFF_OPTS");
+ const char *diffopts;
const char *v;
xpparam_t xpp;
xdemitconf_t xecfg;
xecfg.flags |= XDL_EMIT_FUNCCONTEXT;
if (pe)
xdiff_set_find_func(&xecfg, pe->pattern, pe->cflags);
+
+ diffopts = getenv("GIT_DIFF_OPTS");
if (!diffopts)
;
else if (skip_prefix(diffopts, "--unified=", &v))
xecfg.ctxlen = strtoul(v, NULL, 10);
else if (skip_prefix(diffopts, "-u", &v))
xecfg.ctxlen = strtoul(v, NULL, 10);
+
if (o->word_diff)
init_diff_words_data(&ecbdata, o, one, two);
if (xdi_diff_outf(&mf1, &mf2, NULL, fn_out_consume,
return 0;
}
-static void enable_patch_output(int *fmt) {
+static void enable_patch_output(int *fmt)
+{
*fmt &= ~DIFF_FORMAT_NO_OUTPUT;
*fmt |= DIFF_FORMAT_PATCH;
}
else if (skip_prefix(arg, "--color-moved=", &arg)) {
int cm = parse_color_moved(arg);
if (cm < 0)
- die("bad --color-moved argument: %s", arg);
+ return error("bad --color-moved argument: %s", arg);
options->color_moved = cm;
+ } else if (!strcmp(arg, "--no-color-moved-ws")) {
+ options->color_moved_ws_handling = 0;
} else if (skip_prefix(arg, "--color-moved-ws=", &arg)) {
- options->color_moved_ws_handling = parse_color_moved_ws(arg);
+ unsigned cm = parse_color_moved_ws(arg);
+ if (cm & COLOR_MOVED_WS_ERROR)
+ return -1;
+ options->color_moved_ws_handling = cm;
} else if (skip_to_optional_arg_default(arg, "--color-words", &options->word_regex, NULL)) {
options->use_color = 1;
options->word_diff = DIFF_WORDS_COLOR;
for (i = 0; i < esm.nr; i++)
free((void *)esm.buf[i].line);
+ esm.nr = 0;
+
+ o->emitted_symbols = NULL;
}
- esm.nr = 0;
}
void diff_flush(struct diff_options *options)
df = alloc_filespec(path);
fill_filespec(df, oid, oid_valid, mode);
- textconv = get_textconv(r->index, df);
+ textconv = get_textconv(r, df);
if (!textconv) {
free_filespec(df);
return 0;
/* XDF_WHITESPACE_FLAGS regarding block detection are set at 2, 3, 4 */
#define COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE (1<<5)
- int color_moved_ws_handling;
+ #define COLOR_MOVED_WS_ERROR (1<<0)
+ unsigned color_moved_ws_handling;
struct repository *repo;
};
void diff_no_index(struct repository *, struct rev_info *, int, const char **);
-int index_differs_from(const char *def, const struct diff_flags *flags,
+int index_differs_from(struct repository *r, const char *def,
+ const struct diff_flags *flags,
int ita_invisible_in_index);
/*
* and only if it has textconv enabled (otherwise return NULL). The result
* can be passed to fill_textconv().
*/
-struct userdiff_driver *get_textconv(struct index_state *istate,
+struct userdiff_driver *get_textconv(struct repository *r,
struct diff_filespec *one);
/*
return 0;
if (o->flags.allow_textconv) {
- textconv_one = get_textconv(o->repo->index, p->one);
- textconv_two = get_textconv(o->repo->index, p->two);
+ textconv_one = get_textconv(o->repo, p->one);
+ textconv_two = get_textconv(o->repo, p->two);
}
/*
if (textconv_one == textconv_two && diff_unmodified_pair(p))
return 0;
+ if ((o->pickaxe_opts & DIFF_PICKAXE_KIND_G) &&
+ !o->flags.text &&
+ ((!textconv_one && diff_filespec_is_binary(o->repo, p->one)) ||
+ (!textconv_two && diff_filespec_is_binary(o->repo, p->two))))
+ return 0;
+
mf1.size = fill_textconv(o->repo, textconv_one, p->one, &mf1.ptr);
mf2.size = fill_textconv(o->repo, textconv_two, p->two, &mf2.ptr);
* Copyright (C) Linus Torvalds, 2005-2006
* Junio Hamano, 2005-2006
*/
-#define NO_THE_INDEX_COMPATIBILITY_MACROS
#include "cache.h"
#include "config.h"
#include "dir.h"
#define DO_MATCH_DIRECTORY (1<<1)
#define DO_MATCH_SUBMODULE (1<<2)
-static int match_attrs(const struct index_state *istate,
- const char *name, int namelen,
- const struct pathspec_item *item)
-{
- int i;
- char *to_free = NULL;
-
- if (name[namelen])
- name = to_free = xmemdupz(name, namelen);
-
- git_check_attr(istate, name, item->attr_check);
-
- free(to_free);
-
- for (i = 0; i < item->attr_match_nr; i++) {
- const char *value;
- int matched;
- enum attr_match_mode match_mode;
-
- value = item->attr_check->items[i].value;
- match_mode = item->attr_match[i].match_mode;
-
- if (ATTR_TRUE(value))
- matched = (match_mode == MATCH_SET);
- else if (ATTR_FALSE(value))
- matched = (match_mode == MATCH_UNSET);
- else if (ATTR_UNSET(value))
- matched = (match_mode == MATCH_UNSPECIFIED);
- else
- matched = (match_mode == MATCH_VALUE &&
- !strcmp(item->attr_match[i].value, value));
- if (!matched)
- return 0;
- }
-
- return 1;
-}
-
/*
* Does 'match' match the given name?
* A match is found if
strncmp(item->match, name - prefix, item->prefix))
return 0;
- if (item->attr_match_nr && !match_attrs(istate, name, namelen, item))
+ if (item->attr_match_nr &&
+ !match_pathspec_attrs(istate, name, namelen, item))
return 0;
/* If the match was just the prefix, we matched */
return !available;
}
-int finish_delayed_checkout(struct checkout *state)
+int finish_delayed_checkout(struct checkout *state, int *nr_checkouts)
{
int errs = 0;
unsigned delayed_object_count;
ce = index_file_exists(state->istate, path->string,
strlen(path->string), 0);
if (ce) {
- errs |= checkout_entry(ce, state, NULL);
+ errs |= checkout_entry(ce, state, NULL, nr_checkouts);
filtered_bytes += ce->ce_stat_data.sd_size;
display_throughput(progress, filtered_bytes);
} else
* its name is returned in topath[], which must be able to hold at
* least TEMPORARY_FILENAME_LENGTH bytes long.
*/
-int checkout_entry(struct cache_entry *ce,
- const struct checkout *state, char *topath)
+int checkout_entry(struct cache_entry *ce, const struct checkout *state,
+ char *topath, int *nr_checkouts)
{
static struct strbuf path = STRBUF_INIT;
struct stat st;
return 0;
create_directories(path.buf, path.len, state);
+ if (nr_checkouts)
+ (*nr_checkouts)++;
return write_entry(ce, path.buf, state, 0);
}
static char *git_namespace;
-static const char *super_prefix;
+static char *super_prefix;
/*
* Repository-local GIT_* environment variables; see cache.h for details.
{
static int initialized;
if (!initialized) {
- super_prefix = getenv(GIT_SUPER_PREFIX_ENVIRONMENT);
+ super_prefix = xstrdup_or_null(getenv(GIT_SUPER_PREFIX_ENVIRONMENT));
initialized = 1;
}
return super_prefix;
char *get_object_directory(void)
{
- if (!the_repository->objects->objectdir)
+ if (!the_repository->objects->odb)
BUG("git environment hasn't been setup");
- return the_repository->objects->objectdir;
+ return the_repository->objects->odb->path;
}
int odb_mkstemp(struct strbuf *temp_filename, const char *pattern)
-/*
-(See Documentation/git-fast-import.txt for maintained documentation.)
-Format of STDIN stream:
-
- stream ::= cmd*;
-
- cmd ::= new_blob
- | new_commit
- | new_tag
- | reset_branch
- | checkpoint
- | progress
- ;
-
- new_blob ::= 'blob' lf
- mark?
- file_content;
- file_content ::= data;
-
- new_commit ::= 'commit' sp ref_str lf
- mark?
- ('author' (sp name)? sp '<' email '>' sp when lf)?
- 'committer' (sp name)? sp '<' email '>' sp when lf
- commit_msg
- ('from' sp commit-ish lf)?
- ('merge' sp commit-ish lf)*
- (file_change | ls)*
- lf?;
- commit_msg ::= data;
-
- ls ::= 'ls' sp '"' quoted(path) '"' lf;
-
- file_change ::= file_clr
- | file_del
- | file_rnm
- | file_cpy
- | file_obm
- | file_inm;
- file_clr ::= 'deleteall' lf;
- file_del ::= 'D' sp path_str lf;
- file_rnm ::= 'R' sp path_str sp path_str lf;
- file_cpy ::= 'C' sp path_str sp path_str lf;
- file_obm ::= 'M' sp mode sp (hexsha1 | idnum) sp path_str lf;
- file_inm ::= 'M' sp mode sp 'inline' sp path_str lf
- data;
- note_obm ::= 'N' sp (hexsha1 | idnum) sp commit-ish lf;
- note_inm ::= 'N' sp 'inline' sp commit-ish lf
- data;
-
- new_tag ::= 'tag' sp tag_str lf
- 'from' sp commit-ish lf
- ('tagger' (sp name)? sp '<' email '>' sp when lf)?
- tag_msg;
- tag_msg ::= data;
-
- reset_branch ::= 'reset' sp ref_str lf
- ('from' sp commit-ish lf)?
- lf?;
-
- checkpoint ::= 'checkpoint' lf
- lf?;
-
- progress ::= 'progress' sp not_lf* lf
- lf?;
-
- # note: the first idnum in a stream should be 1 and subsequent
- # idnums should not have gaps between values as this will cause
- # the stream parser to reserve space for the gapped values. An
- # idnum can be updated in the future to a new object by issuing
- # a new mark directive with the old idnum.
- #
- mark ::= 'mark' sp idnum lf;
- data ::= (delimited_data | exact_data)
- lf?;
-
- # note: delim may be any string but must not contain lf.
- # data_line may contain any data but must not be exactly
- # delim.
- delimited_data ::= 'data' sp '<<' delim lf
- (data_line lf)*
- delim lf;
-
- # note: declen indicates the length of binary_data in bytes.
- # declen does not include the lf preceding the binary data.
- #
- exact_data ::= 'data' sp declen lf
- binary_data;
-
- # note: quoted strings are C-style quoting supporting \c for
- # common escapes of 'c' (e..g \n, \t, \\, \") or \nnn where nnn
- # is the signed byte value in octal. Note that the only
- # characters which must actually be escaped to protect the
- # stream formatting is: \, " and LF. Otherwise these values
- # are UTF8.
- #
- commit-ish ::= (ref_str | hexsha1 | sha1exp_str | idnum);
- ref_str ::= ref;
- sha1exp_str ::= sha1exp;
- tag_str ::= tag;
- path_str ::= path | '"' quoted(path) '"' ;
- mode ::= '100644' | '644'
- | '100755' | '755'
- | '120000'
- ;
-
- declen ::= # unsigned 32 bit value, ascii base10 notation;
- bigint ::= # unsigned integer value, ascii base10 notation;
- binary_data ::= # file content, not interpreted;
-
- when ::= raw_when | rfc2822_when;
- raw_when ::= ts sp tz;
- rfc2822_when ::= # Valid RFC 2822 date and time;
-
- sp ::= # ASCII space character;
- lf ::= # ASCII newline (LF) character;
-
- # note: a colon (':') must precede the numerical value assigned to
- # an idnum. This is to distinguish it from a ref or tag name as
- # GIT does not permit ':' in ref or tag strings.
- #
- idnum ::= ':' bigint;
- path ::= # GIT style file path, e.g. "a/b/c";
- ref ::= # GIT ref name, e.g. "refs/heads/MOZ_GECKO_EXPERIMENT";
- tag ::= # GIT tag name, e.g. "FIREFOX_1_5";
- sha1exp ::= # Any valid GIT SHA1 expression;
- hexsha1 ::= # SHA1 in hexadecimal format;
-
- # note: name and email are UTF8 strings, however name must not
- # contain '<' or lf and email must not contain any of the
- # following: '<', '>', lf.
- #
- name ::= # valid GIT author/committer name;
- email ::= # valid GIT author/committer email;
- ts ::= # time since the epoch in seconds, ascii base10 notation;
- tz ::= # GIT style timezone;
-
- # note: comments, get-mark, ls-tree, and cat-blob requests may
- # appear anywhere in the input, except within a data command. Any
- # form of the data command always escapes the related input from
- # comment processing.
- #
- # In case it is not clear, the '#' that starts the comment
- # must be the first character on that line (an lf
- # preceded it).
- #
-
- get_mark ::= 'get-mark' sp idnum lf;
- cat_blob ::= 'cat-blob' sp (hexsha1 | idnum) lf;
- ls_tree ::= 'ls' sp (hexsha1 | idnum) sp path_str lf;
-
- comment ::= '#' not_lf* lf;
- not_lf ::= # Any byte that is not ASCII newline (LF);
-*/
-
#include "builtin.h"
#include "cache.h"
#include "repository.h"
next_mark = 0;
}
+static void parse_original_identifier(void)
+{
+ const char *v;
+ if (skip_prefix(command_buf.buf, "original-oid ", &v))
+ read_next_command();
+}
+
static int parse_data(struct strbuf *sb, uintmax_t limit, uintmax_t *len_res)
{
const char *data;
{
read_next_command();
parse_mark();
+ parse_original_identifier();
parse_and_store_blob(&last_blob, NULL, next_mark);
}
read_next_command();
parse_mark();
+ parse_original_identifier();
if (skip_prefix(command_buf.buf, "author ", &v)) {
author = parse_ident(v);
read_next_command();
die("Invalid ref name or SHA1 expression: %s", from);
read_next_command();
+ /* original-oid ... */
+ parse_original_identifier();
+
/* tagger ... */
if (skip_prefix(command_buf.buf, "tagger ", &v)) {
tagger = parse_ident(v);
ACK_ready
};
-static void consume_shallow_list(struct fetch_pack_args *args, int fd)
+static void consume_shallow_list(struct fetch_pack_args *args,
+ struct packet_reader *reader)
{
if (args->stateless_rpc && args->deepen) {
/* If we sent a depth we will get back "duplicate"
* shallow and unshallow commands every time there
* is a block of have lines exchanged.
*/
- char *line;
- while ((line = packet_read_line(fd, NULL))) {
- if (starts_with(line, "shallow "))
+ while (packet_reader_read(reader) == PACKET_READ_NORMAL) {
+ if (starts_with(reader->line, "shallow "))
continue;
- if (starts_with(line, "unshallow "))
+ if (starts_with(reader->line, "unshallow "))
continue;
die(_("git fetch-pack: expected shallow list"));
}
+ if (reader->status != PACKET_READ_FLUSH)
+ die(_("git fetch-pack: expected a flush packet after shallow list"));
}
}
-static enum ack_type get_ack(int fd, struct object_id *result_oid)
+static enum ack_type get_ack(struct packet_reader *reader,
+ struct object_id *result_oid)
{
int len;
- char *line = packet_read_line(fd, &len);
const char *arg;
- if (!line)
+ if (packet_reader_read(reader) != PACKET_READ_NORMAL)
die(_("git fetch-pack: expected ACK/NAK, got a flush packet"));
- if (!strcmp(line, "NAK"))
+ len = reader->pktlen;
+
+ if (!strcmp(reader->line, "NAK"))
return NAK;
- if (skip_prefix(line, "ACK ", &arg)) {
+ if (skip_prefix(reader->line, "ACK ", &arg)) {
if (!get_oid_hex(arg, result_oid)) {
arg += 40;
- len -= arg - line;
+ len -= arg - reader->line;
if (len < 1)
return ACK;
if (strstr(arg, "continue"))
return ACK;
}
}
- if (skip_prefix(line, "ERR ", &arg))
- die(_("remote error: %s"), arg);
- die(_("git fetch-pack: expected ACK/NAK, got '%s'"), line);
+ die(_("git fetch-pack: expected ACK/NAK, got '%s'"), reader->line);
}
static void send_request(struct fetch_pack_args *args,
int got_ready = 0;
struct strbuf req_buf = STRBUF_INIT;
size_t state_len = 0;
+ struct packet_reader reader;
if (args->stateless_rpc && multi_ack == 1)
die(_("--stateless-rpc requires multi_ack_detailed"));
+ packet_reader_init(&reader, fd[0], NULL, 0,
+ PACKET_READ_CHOMP_NEWLINE |
+ PACKET_READ_DIE_ON_ERR_PACKET);
+
if (!args->no_dependents) {
mark_tips(negotiator, args->negotiation_tips);
for_each_cached_alternate(negotiator, insert_one_alternate_object);
packet_buf_write(&req_buf, "deepen-not %s", s->string);
}
}
- if (server_supports_filtering && args->filter_options.choice)
+ if (server_supports_filtering && args->filter_options.choice) {
+ struct strbuf expanded_filter_spec = STRBUF_INIT;
+ expand_list_objects_filter_spec(&args->filter_options,
+ &expanded_filter_spec);
packet_buf_write(&req_buf, "filter %s",
- args->filter_options.filter_spec);
+ expanded_filter_spec.buf);
+ strbuf_release(&expanded_filter_spec);
+ }
packet_buf_flush(&req_buf);
state_len = req_buf.len;
if (args->deepen) {
- char *line;
const char *arg;
struct object_id oid;
send_request(args, fd[1], &req_buf);
- while ((line = packet_read_line(fd[0], NULL))) {
- if (skip_prefix(line, "shallow ", &arg)) {
+ while (packet_reader_read(&reader) == PACKET_READ_NORMAL) {
+ if (skip_prefix(reader.line, "shallow ", &arg)) {
if (get_oid_hex(arg, &oid))
- die(_("invalid shallow line: %s"), line);
+ die(_("invalid shallow line: %s"), reader.line);
register_shallow(the_repository, &oid);
continue;
}
- if (skip_prefix(line, "unshallow ", &arg)) {
+ if (skip_prefix(reader.line, "unshallow ", &arg)) {
if (get_oid_hex(arg, &oid))
- die(_("invalid unshallow line: %s"), line);
+ die(_("invalid unshallow line: %s"), reader.line);
if (!lookup_object(the_repository, oid.hash))
- die(_("object not found: %s"), line);
+ die(_("object not found: %s"), reader.line);
/* make sure that it is parsed as shallow */
if (!parse_object(the_repository, &oid))
- die(_("error in object: %s"), line);
+ die(_("error in object: %s"), reader.line);
if (unregister_shallow(&oid))
- die(_("no shallow found: %s"), line);
+ die(_("no shallow found: %s"), reader.line);
continue;
}
- die(_("expected shallow/unshallow, got %s"), line);
+ die(_("expected shallow/unshallow, got %s"), reader.line);
}
} else if (!args->stateless_rpc)
send_request(args, fd[1], &req_buf);
if (!args->stateless_rpc && count == INITIAL_FLUSH)
continue;
- consume_shallow_list(args, fd[0]);
+ consume_shallow_list(args, &reader);
do {
- ack = get_ack(fd[0], result_oid);
+ ack = get_ack(&reader, result_oid);
if (ack)
print_verbose(args, _("got %s %d %s"), "ack",
ack, oid_to_hex(result_oid));
strbuf_release(&req_buf);
if (!got_ready || !no_done)
- consume_shallow_list(args, fd[0]);
+ consume_shallow_list(args, &reader);
while (flushes || multi_ack) {
- int ack = get_ack(fd[0], result_oid);
+ int ack = get_ack(&reader, result_oid);
if (ack) {
print_verbose(args, _("got %s (%d) %s"), "ack",
ack, oid_to_hex(result_oid));
struct ref *refs;
};
-/*
- * If the number of refs is not larger than the number of loose objects,
- * this function stops inserting.
- */
-static int add_loose_objects_to_set(const struct object_id *oid,
- const char *path,
- void *data)
-{
- struct loose_object_iter *iter = data;
- oidset_insert(iter->loose_object_set, oid);
- if (iter->refs == NULL)
- return 1;
-
- iter->refs = iter->refs->next;
- return 0;
-}
-
/*
* Mark recent commits available locally and reachable from a local ref as
* COMPLETE. If args->no_dependents is false, also mark COMPLETE remote refs as
struct ref *ref;
int old_save_commit_buffer = save_commit_buffer;
timestamp_t cutoff = 0;
- struct oidset loose_oid_set = OIDSET_INIT;
- int use_oidset = 0;
- struct loose_object_iter iter = {&loose_oid_set, *refs};
-
- /* Enumerate all loose objects or know refs are not so many. */
- use_oidset = !for_each_loose_object(add_loose_objects_to_set,
- &iter, 0);
save_commit_buffer = 0;
for (ref = *refs; ref; ref = ref->next) {
struct object *o;
- unsigned int flags = OBJECT_INFO_QUICK;
- if (use_oidset &&
- !oidset_contains(&loose_oid_set, &ref->old_oid)) {
- /*
- * I know this does not exist in the loose form,
- * so check if it exists in a non-loose form.
- */
- flags |= OBJECT_INFO_IGNORE_LOOSE;
- }
-
- if (!has_object_file_with_flags(&ref->old_oid, flags))
+ if (!has_object_file_with_flags(&ref->old_oid,
+ OBJECT_INFO_QUICK))
continue;
o = parse_object(the_repository, &ref->old_oid);
if (!o)
}
}
- oidset_clear(&loose_oid_set);
-
if (!args->deepen) {
for_each_ref(mark_complete_oid, NULL);
for_each_cached_alternate(NULL, mark_alternate_complete);
packet_buf_write(req_buf, "deepen-not %s", s->string);
}
}
+ if (args->deepen_relative)
+ packet_buf_write(req_buf, "deepen-relative\n");
}
static void add_wants(int no_dependents, const struct ref *wants, struct strbuf *req_buf)
static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,
const struct fetch_pack_args *args,
const struct ref *wants, struct oidset *common,
- int *haves_to_send, int *in_vain)
+ int *haves_to_send, int *in_vain,
+ int sideband_all)
{
int ret = 0;
struct strbuf req_buf = STRBUF_INIT;
packet_buf_write(&req_buf, "include-tag");
if (prefer_ofs_delta)
packet_buf_write(&req_buf, "ofs-delta");
+ if (sideband_all)
+ packet_buf_write(&req_buf, "sideband-all");
/* Add shallow-info and deepen request */
if (server_supports_feature("fetch", "shallow", 0))
/* Add filter */
if (server_supports_feature("fetch", "filter", 0) &&
args->filter_options.choice) {
+ struct strbuf expanded_filter_spec = STRBUF_INIT;
print_verbose(args, _("Server supports filter"));
+ expand_list_objects_filter_spec(&args->filter_options,
+ &expanded_filter_spec);
packet_buf_write(&req_buf, "filter %s",
- args->filter_options.filter_spec);
+ expanded_filter_spec.buf);
+ strbuf_release(&expanded_filter_spec);
} else if (args->filter_options.choice) {
warning("filtering not recognized by server, ignoring");
}
static void receive_shallow_info(struct fetch_pack_args *args,
struct packet_reader *reader)
{
+ int line_received = 0;
+
process_section_header(reader, "shallow-info", 0);
while (packet_reader_read(reader) == PACKET_READ_NORMAL) {
const char *arg;
if (get_oid_hex(arg, &oid))
die(_("invalid shallow line: %s"), reader->line);
register_shallow(the_repository, &oid);
+ line_received = 1;
continue;
}
if (skip_prefix(reader->line, "unshallow ", &arg)) {
die(_("error in object: %s"), reader->line);
if (unregister_shallow(&oid))
die(_("no shallow found: %s"), reader->line);
+ line_received = 1;
continue;
}
die(_("expected shallow/unshallow, got %s"), reader->line);
reader->status != PACKET_READ_DELIM)
die(_("error processing shallow info: %d"), reader->status);
- setup_alternate_shallow(&shallow_lock, &alternate_shallow_file, NULL);
- args->deepen = 1;
+ if (line_received) {
+ setup_alternate_shallow(&shallow_lock, &alternate_shallow_file,
+ NULL);
+ args->deepen = 1;
+ }
}
static void receive_wanted_refs(struct packet_reader *reader,
struct fetch_negotiator negotiator;
fetch_negotiator_init(&negotiator, negotiation_algorithm);
packet_reader_init(&reader, fd[0], NULL, 0,
- PACKET_READ_CHOMP_NEWLINE);
+ PACKET_READ_CHOMP_NEWLINE |
+ PACKET_READ_DIE_ON_ERR_PACKET);
+ if (git_env_bool("GIT_TEST_SIDEBAND_ALL", 1) &&
+ server_supports_feature("fetch", "sideband-all", 0)) {
+ reader.use_sideband = 1;
+ reader.me = "fetch-pack";
+ }
while (state != FETCH_DONE) {
switch (state) {
case FETCH_SEND_REQUEST:
if (send_fetch_request(&negotiator, fd[1], args, ref,
&common,
- &haves_to_send, &in_vain))
+ &haves_to_send, &in_vain,
+ reader.use_sideband))
state = FETCH_GET_PACK;
else
state = FETCH_PROCESS_ACKS;
continue;
if (S_ISDIR(entry.mode)) {
- obj = (struct object *)lookup_tree(the_repository, entry.oid);
+ obj = (struct object *)lookup_tree(the_repository, &entry.oid);
if (name && obj)
put_object_name(options, obj, "%s%s/", name,
entry.path);
result = options->walk(obj, OBJ_TREE, data, options);
}
else if (S_ISREG(entry.mode) || S_ISLNK(entry.mode)) {
- obj = (struct object *)lookup_blob(the_repository, entry.oid);
+ obj = (struct object *)lookup_blob(the_repository, &entry.oid);
if (name && obj)
put_object_name(options, obj, "%s%s", name,
entry.path);
--- /dev/null
+#include "commit-graph.h"
+
+struct commit_graph *parse_commit_graph(void *graph_map, int fd,
+ size_t graph_size);
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+{
+ struct commit_graph *g;
+
+ g = parse_commit_graph((void *)data, -1, size);
+ free(g);
+
+ return 0;
+}
}
bisect_start() {
- #
- # Check for one bad and then some good revisions.
- #
- has_double_dash=0
- for arg; do
- case "$arg" in --) has_double_dash=1; break ;; esac
- done
- orig_args=$(git rev-parse --sq-quote "$@")
- bad_seen=0
- eval=''
- must_write_terms=0
- revs=''
- if test "z$(git rev-parse --is-bare-repository)" != zfalse
- then
- mode=--no-checkout
- else
- mode=''
- fi
- while [ $# -gt 0 ]; do
- arg="$1"
- case "$arg" in
- --)
- shift
- break
- ;;
- --no-checkout)
- mode=--no-checkout
- shift ;;
- --term-good|--term-old)
- shift
- must_write_terms=1
- TERM_GOOD=$1
- shift ;;
- --term-good=*|--term-old=*)
- must_write_terms=1
- TERM_GOOD=${1#*=}
- shift ;;
- --term-bad|--term-new)
- shift
- must_write_terms=1
- TERM_BAD=$1
- shift ;;
- --term-bad=*|--term-new=*)
- must_write_terms=1
- TERM_BAD=${1#*=}
- shift ;;
- --*)
- die "$(eval_gettext "unrecognised option: '\$arg'")" ;;
- *)
- rev=$(git rev-parse -q --verify "$arg^{commit}") || {
- test $has_double_dash -eq 1 &&
- die "$(eval_gettext "'\$arg' does not appear to be a valid revision")"
- break
- }
- revs="$revs $rev"
- shift
- ;;
- esac
- done
-
- for rev in $revs
- do
- # The user ran "git bisect start <sha1>
- # <sha1>", hence did not explicitly specify
- # the terms, but we are already starting to
- # set references named with the default terms,
- # and won't be able to change afterwards.
- must_write_terms=1
-
- case $bad_seen in
- 0) state=$TERM_BAD ; bad_seen=1 ;;
- *) state=$TERM_GOOD ;;
- esac
- eval="$eval bisect_write '$state' '$rev' 'nolog' &&"
- done
- #
- # Verify HEAD.
- #
- head=$(GIT_DIR="$GIT_DIR" git symbolic-ref -q HEAD) ||
- head=$(GIT_DIR="$GIT_DIR" git rev-parse --verify HEAD) ||
- die "$(gettext "Bad HEAD - I need a HEAD")"
-
- #
- # Check if we are bisecting.
- #
- start_head=''
- if test -s "$GIT_DIR/BISECT_START"
- then
- # Reset to the rev from where we started.
- start_head=$(cat "$GIT_DIR/BISECT_START")
- if test "z$mode" != "z--no-checkout"
- then
- git checkout "$start_head" -- ||
- die "$(eval_gettext "Checking out '\$start_head' failed. Try 'git bisect reset <valid-branch>'.")"
- fi
- else
- # Get rev from where we start.
- case "$head" in
- refs/heads/*|$_x40)
- # This error message should only be triggered by
- # cogito usage, and cogito users should understand
- # it relates to cg-seek.
- [ -s "$GIT_DIR/head-name" ] &&
- die "$(gettext "won't bisect on cg-seek'ed tree")"
- start_head="${head#refs/heads/}"
- ;;
- *)
- die "$(gettext "Bad HEAD - strange symbolic ref")"
- ;;
- esac
- fi
-
- #
- # Get rid of any old bisect state.
- #
- git bisect--helper --bisect-clean-state || exit
+ git bisect--helper --bisect-start $@ || exit
#
# Change state.
trap 'git bisect--helper --bisect-clean-state' 0
trap 'exit 255' 1 2 3 15
- #
- # Write new start state.
- #
- echo "$start_head" >"$GIT_DIR/BISECT_START" && {
- test "z$mode" != "z--no-checkout" ||
- git update-ref --no-deref BISECT_HEAD "$start_head"
- } &&
- git rev-parse --sq-quote "$@" >"$GIT_DIR/BISECT_NAMES" &&
- eval "$eval true" &&
- if test $must_write_terms -eq 1
- then
- git bisect--helper --write-terms "$TERM_BAD" "$TERM_GOOD" || exit
- fi &&
- echo "git bisect start$orig_args" >>"$GIT_DIR/BISECT_LOG" || exit
#
# Check if we can proceed to the next bisect state.
#
+ get_terms
bisect_auto_next
trap '-' 0
}
-bisect_write() {
- state="$1"
- rev="$2"
- nolog="$3"
- case "$state" in
- "$TERM_BAD")
- tag="$state" ;;
- "$TERM_GOOD"|skip)
- tag="$state"-"$rev" ;;
- *)
- die "$(eval_gettext "Bad bisect_write argument: \$state")" ;;
- esac
- git update-ref "refs/bisect/$tag" "$rev" || exit
- echo "# $state: $(git show-branch $rev)" >>"$GIT_DIR/BISECT_LOG"
- test -n "$nolog" || echo "git bisect $state $rev" >>"$GIT_DIR/BISECT_LOG"
-}
-
bisect_skip() {
all=''
for arg in "$@"
bisect_state() {
bisect_autostart
state=$1
- check_and_set_terms $state
+ git bisect--helper --check-and-set-terms $state $TERM_GOOD $TERM_BAD || exit
+ get_terms
case "$#,$state" in
0,*)
die "Please call 'bisect_state' with at least one argument." ;;
bisected_head=$(bisect_head)
rev=$(git rev-parse --verify "$bisected_head") ||
die "$(eval_gettext "Bad rev input: \$bisected_head")"
- bisect_write "$state" "$rev"
+ git bisect--helper --bisect-write "$state" "$rev" "$TERM_GOOD" "$TERM_BAD" || exit
git bisect--helper --check-expected-revs "$rev" ;;
2,"$TERM_BAD"|*,"$TERM_GOOD"|*,skip)
shift
done
for rev in $hash_list
do
- bisect_write "$state" "$rev"
+ git bisect--helper --bisect-write "$state" "$rev" "$TERM_GOOD" "$TERM_BAD" || exit
done
git bisect--helper --check-expected-revs $hash_list ;;
*,"$TERM_BAD")
bisect_auto_next
}
-bisect_next_check() {
- missing_good= missing_bad=
- git show-ref -q --verify refs/bisect/$TERM_BAD || missing_bad=t
- test -n "$(git for-each-ref "refs/bisect/$TERM_GOOD-*")" || missing_good=t
-
- case "$missing_good,$missing_bad,$1" in
- ,,*)
- : have both $TERM_GOOD and $TERM_BAD - ok
- ;;
- *,)
- # do not have both but not asked to fail - just report.
- false
- ;;
- t,,"$TERM_GOOD")
- # have bad (or new) but not good (or old). we could bisect although
- # this is less optimum.
- eval_gettextln "Warning: bisecting only with a \$TERM_BAD commit." >&2
- if test -t 0
- then
- # TRANSLATORS: Make sure to include [Y] and [n] in your
- # translation. The program will only accept English input
- # at this point.
- gettext "Are you sure [Y/n]? " >&2
- read yesno
- case "$yesno" in [Nn]*) exit 1 ;; esac
- fi
- : bisect without $TERM_GOOD...
- ;;
- *)
- bad_syn=$(bisect_voc bad)
- good_syn=$(bisect_voc good)
- if test -s "$GIT_DIR/BISECT_START"
- then
-
- eval_gettextln "You need to give me at least one \$bad_syn and one \$good_syn revision.
-(You can use \"git bisect \$bad_syn\" and \"git bisect \$good_syn\" for that.)" >&2
- else
- eval_gettextln "You need to start by \"git bisect start\".
-You then need to give me at least one \$good_syn and one \$bad_syn revision.
-(You can use \"git bisect \$bad_syn\" and \"git bisect \$good_syn\" for that.)" >&2
- fi
- exit 1 ;;
- esac
-}
-
bisect_auto_next() {
- bisect_next_check && bisect_next || :
+ git bisect--helper --bisect-next-check $TERM_GOOD $TERM_BAD && bisect_next || :
}
bisect_next() {
case "$#" in 0) ;; *) usage ;; esac
bisect_autostart
- bisect_next_check $TERM_GOOD
+ git bisect--helper --bisect-next-check $TERM_GOOD $TERM_BAD $TERM_GOOD|| exit
# Perform all bisection computation, display and checkout
git bisect--helper --next-all $(test -f "$GIT_DIR/BISECT_HEAD" && echo --no-checkout)
}
bisect_visualize() {
- bisect_next_check fail
+ git bisect--helper --bisect-next-check $TERM_GOOD $TERM_BAD fail || exit
if test $# = 0
then
eval '"$@"' --bisect -- $(cat "$GIT_DIR/BISECT_NAMES")
}
-bisect_reset() {
- test -s "$GIT_DIR/BISECT_START" || {
- gettextln "We are not bisecting."
- return
- }
- case "$#" in
- 0) branch=$(cat "$GIT_DIR/BISECT_START") ;;
- 1) git rev-parse --quiet --verify "$1^{commit}" >/dev/null || {
- invalid="$1"
- die "$(eval_gettext "'\$invalid' is not a valid commit")"
- }
- branch="$1" ;;
- *)
- usage ;;
- esac
-
- if ! test -f "$GIT_DIR/BISECT_HEAD" && ! git checkout "$branch" --
- then
- die "$(eval_gettext "Could not check out original HEAD '\$branch'.
-Try 'git bisect reset <commit>'.")"
- fi
- git bisect--helper --bisect-clean-state || exit
-}
-
bisect_replay () {
file="$1"
test "$#" -eq 1 || die "$(gettext "No logfile given")"
test -r "$file" || die "$(eval_gettext "cannot read \$file for replaying")"
- bisect_reset
+ git bisect--helper --bisect-reset || exit
while read git bisect command rev
do
test "$git $bisect" = "git bisect" || test "$git" = "git-bisect" || continue
command="$bisect"
fi
get_terms
- check_and_set_terms "$command"
+ git bisect--helper --check-and-set-terms "$command" "$TERM_GOOD" "$TERM_BAD" || exit
+ get_terms
case "$command" in
start)
cmd="bisect_start $rev"
eval "$cmd" ;;
"$TERM_GOOD"|"$TERM_BAD"|skip)
- bisect_write "$command" "$rev" ;;
+ git bisect--helper --bisect-write "$command" "$rev" "$TERM_GOOD" "$TERM_BAD" || exit;;
terms)
- bisect_terms $rev ;;
+ git bisect--helper --bisect-terms $rev || exit;;
*)
die "$(gettext "?? what are you talking about?")" ;;
esac
}
bisect_run () {
- bisect_next_check fail
+ git bisect--helper --bisect-next-check $TERM_GOOD $TERM_BAD fail || exit
test -n "$*" || die "$(gettext "bisect run failed: no command provided.")"
fi
}
-check_and_set_terms () {
- cmd="$1"
- case "$cmd" in
- skip|start|terms) ;;
- *)
- if test -s "$GIT_DIR/BISECT_TERMS" && test "$cmd" != "$TERM_BAD" && test "$cmd" != "$TERM_GOOD"
- then
- die "$(eval_gettext "Invalid command: you're currently in a \$TERM_BAD/\$TERM_GOOD bisect.")"
- fi
- case "$cmd" in
- bad|good)
- if ! test -s "$GIT_DIR/BISECT_TERMS"
- then
- TERM_BAD=bad
- TERM_GOOD=good
- git bisect--helper --write-terms "$TERM_BAD" "$TERM_GOOD" || exit
- fi
- ;;
- new|old)
- if ! test -s "$GIT_DIR/BISECT_TERMS"
- then
- TERM_BAD=new
- TERM_GOOD=old
- git bisect--helper --write-terms "$TERM_BAD" "$TERM_GOOD" || exit
- fi
- ;;
- esac ;;
- esac
-}
-
-bisect_voc () {
- case "$1" in
- bad) echo "bad|new" ;;
- good) echo "good|old" ;;
- esac
-}
-
-bisect_terms () {
- get_terms
- if ! test -s "$GIT_DIR/BISECT_TERMS"
- then
- die "$(gettext "no terms defined")"
- fi
- case "$#" in
- 0)
- gettextln "Your current terms are $TERM_GOOD for the old state
-and $TERM_BAD for the new state."
- ;;
- 1)
- arg=$1
- case "$arg" in
- --term-good|--term-old)
- printf '%s\n' "$TERM_GOOD"
- ;;
- --term-bad|--term-new)
- printf '%s\n' "$TERM_BAD"
- ;;
- *)
- die "$(eval_gettext "invalid argument \$arg for 'git bisect terms'.
-Supported options are: --term-good|--term-old and --term-bad|--term-new.")"
- ;;
- esac
- ;;
- *)
- usage ;;
- esac
-}
-
case "$#" in
0)
usage ;;
visualize|view)
bisect_visualize "$@" ;;
reset)
- bisect_reset "$@" ;;
+ git bisect--helper --bisect-reset "$@" ;;
replay)
bisect_replay "$@" ;;
log)
run)
bisect_run "$@" ;;
terms)
- bisect_terms "$@" ;;
+ git bisect--helper --bisect-terms "$@" || exit;;
*)
usage ;;
esac
#endif
#if defined(__CYGWIN__)
-#include "compat/cygwin.h"
+#include "compat/win32/path-utils.h"
#endif
#if defined(__MINGW32__)
/* pull in Windows compatibility stuff */
+#include "compat/win32/path-utils.h"
#include "compat/mingw.h"
#elif defined(_MSC_VER)
#include "compat/msvc.h"
#define query_user_email() NULL
#endif
+#ifdef __TANDEM
+#include <floss.h(floss_execl,floss_execlp,floss_execv,floss_execvp)>
+#include <floss.h(floss_getpwuid)>
+#ifndef NSIG
+/*
+ * NonStop NSE and NSX do not provide NSIG. SIGGUARDIAN(99) is the highest
+ * known, by detective work using kill -l as a list is all signals
+ * instead of signal.h where it should be.
+ */
+# define NSIG 100
+#endif
+#endif
+
#if defined(__HP_cc) && (__HP_cc >= 61000)
#define NORETURN __attribute__((noreturn))
#define NORETURN_PTR
#ifdef NO_MEMMEM
#define memmem gitmemmem
void *gitmemmem(const void *haystack, size_t haystacklen,
- const void *needle, size_t needlelen);
+ const void *needle, size_t needlelen);
#endif
#ifdef OVERRIDE_STRDUP
httpd_only="${httpd%% *}" # cut on first space
return
;;
+ *python*)
+ # server is started by running via generated gitweb.py in
+ # $fqgitdir/gitweb
+ full_httpd="$fqgitdir/gitweb/gitweb.py"
+ httpd_only="${httpd%% *}" # cut on first space
+ return
+ ;;
esac
httpd_only="$(echo $httpd | cut -f1 -d' ')"
# don't quote $full_httpd, there can be arguments to it (-f)
case "$httpd" in
- *mongoose*|*plackup*)
+ *mongoose*|*plackup*|*python*)
#These servers don't have a daemon mode so we'll have to fork it
$full_httpd "$conf" &
#Save the pid before doing anything else (we'll print it later)
rm -f "$conf"
}
+python_conf() {
+ # Python's builtin http.server and its CGI support is very limited.
+ # CGI handler is capable of running CGI script only from inside a directory.
+ # Trying to set cgi_directories=["/"] will add double slash to SCRIPT_NAME
+ # and that in turn breaks gitweb's relative link generation.
+
+ # create a simple web root where $fqgitdir/gitweb/$httpd_only is our root
+ mkdir -p "$fqgitdir/gitweb/$httpd_only/cgi-bin"
+ # Python http.server follows the symlinks
+ ln -sf "$root/gitweb.cgi" "$fqgitdir/gitweb/$httpd_only/cgi-bin/gitweb.cgi"
+ ln -sf "$root/static" "$fqgitdir/gitweb/$httpd_only/"
+
+ # generate a standalone 'python http.server' script in $fqgitdir/gitweb
+ # This asumes that python is in user's $PATH
+ # This script is Python 2 and 3 compatible
+ cat > "$fqgitdir/gitweb/gitweb.py" <<EOF
+#!/usr/bin/env python
+import os
+import sys
+
+# Open log file in line buffering mode
+accesslogfile = open("$fqgitdir/gitweb/access.log", 'a', buffering=1)
+errorlogfile = open("$fqgitdir/gitweb/error.log", 'a', buffering=1)
+
+# and replace our stdout and stderr with log files
+# also do a lowlevel duplicate of the logfile file descriptors so that
+# our CGI child process writes any stderr warning also to the log file
+_orig_stdout_fd = sys.stdout.fileno()
+sys.stdout.close()
+os.dup2(accesslogfile.fileno(), _orig_stdout_fd)
+sys.stdout = accesslogfile
+
+_orig_stderr_fd = sys.stderr.fileno()
+sys.stderr.close()
+os.dup2(errorlogfile.fileno(), _orig_stderr_fd)
+sys.stderr = errorlogfile
+
+from functools import partial
+
+if sys.version_info < (3, 0): # Python 2
+ from CGIHTTPServer import CGIHTTPRequestHandler
+ from BaseHTTPServer import HTTPServer as ServerClass
+else: # Python 3
+ from http.server import CGIHTTPRequestHandler
+ from http.server import HTTPServer as ServerClass
+
+
+# Those environment variables will be passed to the cgi script
+os.environ.update({
+ "GIT_EXEC_PATH": "$GIT_EXEC_PATH",
+ "GIT_DIR": "$GIT_DIR",
+ "GITWEB_CONFIG": "$GITWEB_CONFIG"
+})
+
+
+class GitWebRequestHandler(CGIHTTPRequestHandler):
+
+ def log_message(self, format, *args):
+ # Write access logs to stdout
+ sys.stdout.write("%s - - [%s] %s\n" %
+ (self.address_string(),
+ self.log_date_time_string(),
+ format%args))
+
+ def do_HEAD(self):
+ self.redirect_path()
+ CGIHTTPRequestHandler.do_HEAD(self)
+
+ def do_GET(self):
+ if self.path == "/":
+ self.send_response(303, "See Other")
+ self.send_header("Location", "/cgi-bin/gitweb.cgi")
+ self.end_headers()
+ return
+ self.redirect_path()
+ CGIHTTPRequestHandler.do_GET(self)
+
+ def do_POST(self):
+ self.redirect_path()
+ CGIHTTPRequestHandler.do_POST(self)
+
+ # rewrite path of every request that is not gitweb.cgi to out of cgi-bin
+ def redirect_path(self):
+ if not self.path.startswith("/cgi-bin/gitweb.cgi"):
+ self.path = self.path.replace("/cgi-bin/", "/")
+
+ # gitweb.cgi is the only thing that is ever going to be run here.
+ # Ignore everything else
+ def is_cgi(self):
+ result = False
+ if self.path.startswith('/cgi-bin/gitweb.cgi'):
+ result = CGIHTTPRequestHandler.is_cgi(self)
+ return result
+
+
+bind = "127.0.0.1"
+if "$local" == "true":
+ bind = "0.0.0.0"
+
+# Set our http root directory
+# This is a work around for a missing directory argument in older Python versions
+# as this was added to SimpleHTTPRequestHandler in Python 3.7
+os.chdir("$fqgitdir/gitweb/$httpd_only/")
+
+GitWebRequestHandler.protocol_version = "HTTP/1.0"
+httpd = ServerClass((bind, $port), GitWebRequestHandler)
+
+sa = httpd.socket.getsockname()
+print("Serving HTTP on", sa[0], "port", sa[1], "...")
+httpd.serve_forever()
+EOF
+
+ chmod a+x "$fqgitdir/gitweb/gitweb.py"
+}
+
gitweb_conf() {
cat > "$fqgitdir/gitweb/gitweb_config.perl" <<EOF
#!@@PERL@@
*plackup*)
plackup_conf
;;
+ *python*)
+ python_conf
+ ;;
*)
echo "Unknown httpd specified: $httpd"
exit 1
m,merge! use merging strategies to rebase
i,interactive! let the user edit the list of commits to rebase
x,exec=! add exec lines after each commit of the editable list
+y=! same as --reschedule-failed-exec -x
k,keep-empty preserve empty commits during rebase
allow-empty-message allow rebasing commits with empty messages
stat! display a diffstat of what changed upstream
edit-todo! edit the todo list during an interactive rebase
quit! abort but keep HEAD where it is
show-current-patch! show the patch file being applied or merged
+reschedule-failed-exec automatically reschedule failed exec commands
"
. git-sh-setup
set_reflog_action rebase
keep_empty=
allow_empty_message=--allow-empty-message
signoff=
+reschedule_failed_exec=
test "$(git config --bool rebase.autosquash)" = "true" && autosquash=t
case "$(git config --bool commit.gpgsign)" in
true) gpg_sign_opt=-S ;;
*) gpg_sign_opt= ;;
esac
+test "$(git config --bool rebase.reschedulefailedexec)" = "true" &&
+reschedule_failed_exec=--reschedule-failed-exec
. git-rebase--common
read_basic_state () {
else
orig_head=$(cat "$state_dir"/head)
fi &&
- GIT_QUIET=$(cat "$state_dir"/quiet) &&
+ test -f "$state_dir"/quiet && GIT_QUIET=t
test -f "$state_dir"/verbose && verbose=t
test -f "$state_dir"/strategy && strategy="$(cat "$state_dir"/strategy)"
test -f "$state_dir"/strategy_opts &&
signoff="$(cat "$state_dir"/signoff)"
force_rebase=t
}
+ test -f "$state_dir"/reschedule-failed-exec &&
+ reschedule_failed_exec=t
}
finish_rebase () {
"$allow_empty_message" "$autosquash" "$verbose" \
"$force_rebase" "$onto_name" "$head_name" "$strategy" \
"$strategy_opts" "$cmd" "$switch_to" \
- "$allow_rerere_autoupdate" "$gpg_sign_opt" "$signoff"
+ "$allow_rerere_autoupdate" "$gpg_sign_opt" "$signoff" \
+ "$reschedule_failed_exec"
}
run_specific_rebase () {
if [ "$interactive_rebase" = implied ]; then
- GIT_EDITOR=:
- export GIT_EDITOR
+ GIT_SEQUENCE_EDITOR=:
+ export GIT_SEQUENCE_EDITOR
autosquash=
fi
state_dir="$apply_dir"
elif test -d "$merge_dir"
then
+ type=interactive
if test -d "$merge_dir"/rewritten
then
type=preserve-merges
preserve_merges=t
elif test -f "$merge_dir"/interactive
then
- type=interactive
interactive_rebase=explicit
- else
- type=merge
fi
state_dir="$merge_dir"
fi
cmd="${cmd}exec ${1#--exec=}${LF}"
test -z "$interactive_rebase" && interactive_rebase=implied
;;
+ -y*)
+ reschedule_failed_exec=--reschedule-failed-exec
+ cmd="${cmd}exec ${1#-y}${LF}"
+ test -z "$interactive_rebase" && interactive_rebase=implied
+ ;;
--interactive)
interactive_rebase=explicit
;;
--gpg-sign=*)
gpg_sign_opt="-S${1#--gpg-sign=}"
;;
+ --reschedule-failed-exec)
+ reschedule_failed_exec=--reschedule-failed-exec
+ ;;
+ --no-reschedule-failed-exec)
+ reschedule_failed_exec=
+ ;;
--)
shift
break
test -z "$interactive_rebase" && interactive_rebase=implied
fi
+actually_interactive=
if test -n "$interactive_rebase"
then
if test -z "$preserve_merges"
else
type=preserve-merges
fi
-
+ actually_interactive=t
state_dir="$merge_dir"
elif test -n "$do_merge"
then
- type=merge
+ interactive_rebase=implied
+ type=interactive
state_dir="$merge_dir"
else
type=am
git_format_patch_opt="$git_format_patch_opt --progress"
fi
-if test -n "$git_am_opt"; then
- incompatible_opts=$(echo " $git_am_opt " | \
- sed -e 's/ -q / /g' -e 's/^ \(.*\) $/\1/')
- if test -n "$interactive_rebase"
+incompatible_opts=$(echo " $git_am_opt " | \
+ sed -e 's/ -q / /g' -e 's/^ \(.*\) $/\1/')
+if test -n "$incompatible_opts"
+then
+ if test -n "$actually_interactive" || test "$do_merge"
then
- if test -n "$incompatible_opts"
- then
- die "$(gettext "error: cannot combine interactive options (--interactive, --exec, --rebase-merges, --preserve-merges, --keep-empty, --root + --onto) with am options ($incompatible_opts)")"
- fi
- fi
- if test -n "$do_merge"; then
- if test -n "$incompatible_opts"
- then
- die "$(gettext "error: cannot combine merge options (--merge, --strategy, --strategy-option) with am options ($incompatible_opts)")"
- fi
+ die "$(gettext "fatal: cannot combine am options with either interactive or merge options")"
fi
fi
if test -n "$signoff"
then
test -n "$preserve_merges" &&
- die "$(gettext "error: cannot combine '--signoff' with '--preserve-merges'")"
+ die "$(gettext "fatal: cannot combine '--signoff' with '--preserve-merges'")"
git_am_opt="$git_am_opt $signoff"
force_rebase=t
fi
# Note: incompatibility with --interactive is just a strong warning;
# git-rebase.txt caveats with "unless you know what you are doing"
test -n "$rebase_merges" &&
- die "$(gettext "error: cannot combine '--preserve-merges' with '--rebase-merges'")"
+ die "$(gettext "fatal: cannot combine '--preserve-merges' with '--rebase-merges'")"
+
+ test -n "$reschedule_failed_exec" &&
+ die "$(gettext "error: cannot combine '--preserve-merges' with '--reschedule-failed-exec'")"
fi
if test -n "$rebase_merges"
then
test -n "$strategy_opts" &&
- die "$(gettext "error: cannot combine '--rebase-merges' with '--strategy-option'")"
+ die "$(gettext "fatal: cannot combine '--rebase-merges' with '--strategy-option'")"
test -n "$strategy" &&
- die "$(gettext "error: cannot combine '--rebase-merges' with '--strategy'")"
+ die "$(gettext "fatal: cannot combine '--rebase-merges' with '--strategy'")"
fi
if test -z "$rebase_root"
# but this should be done only when upstream and onto are the same
# and if this is not an interactive rebase.
mb=$(git merge-base "$onto" "$orig_head")
-if test -z "$interactive_rebase" && test "$upstream" = "$onto" &&
+if test -z "$actually_interactive" && test "$upstream" = "$onto" &&
test "$mb" = "$onto" && test -z "$restrict_revision" &&
# linear history?
! (git rev-list --parents "$onto".."$orig_head" | sane_grep " .* ") > /dev/null
GIT_PAGER='' git diff --stat --summary "$mb_tree" "$onto"
fi
+if test -z "$actually_interactive" && test "$mb" = "$orig_head"
+then
+ say "$(eval_gettext "Fast-forwarded \$branch_name to \$onto_name.")"
+ GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $onto_name" \
+ git checkout -q "$onto^0" || die "could not detach HEAD"
+ # If the $onto is a proper descendant of the tip of the branch, then
+ # we just fast-forwarded.
+ git update-ref ORIG_HEAD $orig_head
+ move_to_original_branch
+ finish_rebase
+ exit 0
+fi
+
test -n "$interactive_rebase" && run_specific_rebase
# Detach HEAD and reset the tree
git checkout -q "$onto^0" || die "could not detach HEAD"
git update-ref ORIG_HEAD $orig_head
-# If the $onto is a proper descendant of the tip of the branch, then
-# we just fast-forwarded.
-if test "$mb" = "$orig_head"
-then
- say "$(eval_gettext "Fast-forwarded \$branch_name to \$onto_name.")"
- move_to_original_branch
- finish_rebase
- exit 0
-fi
-
if test -n "$rebase_root"
then
revisions="$onto..$orig_head"
die_bad_access("p4 error: {0}".format(data))
else:
die_bad_access("unknown error")
+ elif code == "info":
+ return
else:
die_bad_access("unknown error code {0}".format(code))
filesToAdd.remove(path)
elif modifier == "C":
src, dest = diff['src'], diff['dst']
+ all_files.append(dest)
p4_integrate(src, dest)
pureRenameCopy.add(dest)
if diff['src_sha1'] != diff['dst_sha1']:
editedFiles.add(dest)
elif modifier == "R":
src, dest = diff['src'], diff['dst']
+ all_files.append(dest)
if self.p4HasMoveCommand:
p4_edit(src) # src must be open before move
p4_move(src, dest) # opens for (move/delete, move/add)
author= author name and email address for patches without any
patches= path to the quilt patches
series= path to the quilt series file
+keep-non-patch Pass -b to git mailinfo
"
SUBDIRECTORY_ON=Yes
. git-sh-setup
shift
QUILT_SERIES="$1"
;;
+ --keep-non-patch)
+ MAILINFO_OPT="-b"
+ ;;
--)
shift
break;;
continue
fi
echo $patch_name
- git mailinfo "$tmp_msg" "$tmp_patch" \
+ git mailinfo $MAILINFO_OPT "$tmp_msg" "$tmp_patch" \
<"$QUILT_PATCHES/$patch_name" >"$tmp_info" || exit 3
test -s "$tmp_patch" || {
echo "Patch is empty. Was it split wrong?"
git format-patch -k --stdout --full-index --cherry-pick --right-only \
--src-prefix=a/ --dst-prefix=b/ --no-renames --no-cover-letter \
- --pretty=mboxrd \
+ --pretty=mboxrd --topo-order \
$git_format_patch_opt \
"$revisions" ${restrict_revision+^$restrict_revision} \
>"$GIT_DIR/rebased-patches"
echo "$head_name" > "$state_dir"/head-name &&
echo "$onto" > "$state_dir"/onto &&
echo "$orig_head" > "$state_dir"/orig-head &&
- echo "$GIT_QUIET" > "$state_dir"/quiet &&
+ test t = "$GIT_QUIET" && : > "$state_dir"/quiet
test t = "$verbose" && : > "$state_dir"/verbose
test -n "$strategy" && echo "$strategy" > "$state_dir"/strategy
test -n "$strategy_opts" && echo "$strategy_opts" > \
"$state_dir"/allow_rerere_autoupdate
test -n "$gpg_sign_opt" && echo "$gpg_sign_opt" > "$state_dir"/gpg_sign_opt
test -n "$signoff" && echo "$signoff" >"$state_dir"/signoff
+ test -n "$reschedule_failed_exec" && : > "$state_dir"/reschedule-failed-exec
}
apply_autostash () {
+++ /dev/null
-# This shell script fragment is sourced by git-rebase to implement
-# its merge-based non-interactive mode that copes well with renamed
-# files.
-#
-# Copyright (c) 2010 Junio C Hamano.
-#
-
-prec=4
-
-read_state () {
- onto_name=$(cat "$state_dir"/onto_name) &&
- end=$(cat "$state_dir"/end) &&
- msgnum=$(cat "$state_dir"/msgnum)
-}
-
-continue_merge () {
- test -d "$state_dir" || die "$state_dir directory does not exist"
-
- unmerged=$(git ls-files -u)
- if test -n "$unmerged"
- then
- echo "You still have unmerged paths in your index"
- echo "did you forget to use git add?"
- die "$resolvemsg"
- fi
-
- cmt=$(cat "$state_dir/current")
- if ! git diff-index --quiet --ignore-submodules HEAD --
- then
- if ! git commit ${gpg_sign_opt:+"$gpg_sign_opt"} $signoff $allow_empty_message \
- --no-verify -C "$cmt"
- then
- echo "Commit failed, please do not call \"git commit\""
- echo "directly, but instead do one of the following: "
- die "$resolvemsg"
- fi
- if test -z "$GIT_QUIET"
- then
- printf "Committed: %0${prec}d " $msgnum
- fi
- echo "$cmt $(git rev-parse HEAD^0)" >> "$state_dir/rewritten"
- else
- if test -z "$GIT_QUIET"
- then
- printf "Already applied: %0${prec}d " $msgnum
- fi
- fi
- test -z "$GIT_QUIET" &&
- GIT_PAGER='' git log --format=%s -1 "$cmt"
-
- # onto the next patch:
- msgnum=$(($msgnum + 1))
- echo "$msgnum" >"$state_dir/msgnum"
-}
-
-call_merge () {
- msgnum="$1"
- echo "$msgnum" >"$state_dir/msgnum"
- cmt="$(cat "$state_dir/cmt.$msgnum")"
- echo "$cmt" > "$state_dir/current"
- git update-ref REBASE_HEAD "$cmt"
- hd=$(git rev-parse --verify HEAD)
- cmt_name=$(git symbolic-ref HEAD 2> /dev/null || echo HEAD)
- eval GITHEAD_$cmt='"${cmt_name##refs/heads/}~$(($end - $msgnum))"'
- eval GITHEAD_$hd='$onto_name'
- export GITHEAD_$cmt GITHEAD_$hd
- if test -n "$GIT_QUIET"
- then
- GIT_MERGE_VERBOSITY=1 && export GIT_MERGE_VERBOSITY
- fi
- test -z "$strategy" && strategy=recursive
- # If cmt doesn't have a parent, don't include it as a base
- base=$(git rev-parse --verify --quiet $cmt^)
- eval 'git merge-$strategy' $strategy_opts $base ' -- "$hd" "$cmt"'
- rv=$?
- case "$rv" in
- 0)
- unset GITHEAD_$cmt GITHEAD_$hd
- return
- ;;
- 1)
- git rerere $allow_rerere_autoupdate
- die "$resolvemsg"
- ;;
- 2)
- echo "Strategy: $strategy failed, try another" 1>&2
- die "$resolvemsg"
- ;;
- *)
- die "Unknown exit code ($rv) from command:" \
- "git merge-$strategy $cmt^ -- HEAD $cmt"
- ;;
- esac
-}
-
-finish_rb_merge () {
- move_to_original_branch
- if test -s "$state_dir"/rewritten
- then
- git notes copy --for-rewrite=rebase <"$state_dir"/rewritten
- hook="$(git rev-parse --git-path hooks/post-rewrite)"
- test -x "$hook" && "$hook" rebase <"$state_dir"/rewritten
- fi
- say All done.
-}
-
-git_rebase__merge () {
-
-case "$action" in
-continue)
- read_state
- continue_merge
- while test "$msgnum" -le "$end"
- do
- call_merge "$msgnum"
- continue_merge
- done
- finish_rb_merge
- return
- ;;
-skip)
- read_state
- git rerere clear
- msgnum=$(($msgnum + 1))
- while test "$msgnum" -le "$end"
- do
- call_merge "$msgnum"
- continue_merge
- done
- finish_rb_merge
- return
- ;;
-show-current-patch)
- exec git show REBASE_HEAD --
- ;;
-esac
-
-mkdir -p "$state_dir"
-echo "$onto_name" > "$state_dir/onto_name"
-write_basic_state
-rm -f "$(git rev-parse --git-path REBASE_HEAD)"
-
-msgnum=0
-for cmt in $(git rev-list --reverse --no-merges "$revisions")
-do
- msgnum=$(($msgnum + 1))
- echo "$cmt" > "$state_dir/cmt.$msgnum"
-done
-
-echo 1 >"$state_dir/msgnum"
-echo $msgnum >"$state_dir/end"
-
-end=$msgnum
-msgnum=1
-
-while test "$msgnum" -le "$end"
-do
- call_merge "$msgnum"
- continue_merge
-done
-
-finish_rb_merge
-
-}
git ls-files -o $z $excl_opt -- "$@"
}
+prepare_fallback_ident () {
+ if ! git -c user.useconfigonly=yes var GIT_COMMITTER_IDENT >/dev/null 2>&1
+ then
+ GIT_AUTHOR_NAME="git stash"
+ GIT_AUTHOR_EMAIL=git@stash
+ GIT_COMMITTER_NAME="git stash"
+ GIT_COMMITTER_EMAIL=git@stash
+ export GIT_AUTHOR_NAME
+ export GIT_AUTHOR_EMAIL
+ export GIT_COMMITTER_NAME
+ export GIT_COMMITTER_EMAIL
+ fi
+}
+
clear_stash () {
if test $# != 0
then
}
create_stash () {
+
+ prepare_fallback_ident
+
stash_msg=
untracked=
while test $# != 0
return 0;
}
-static void commit_pager_choice(void) {
+static void commit_pager_choice(void)
+{
switch (use_pager) {
case 0:
setenv("GIT_PAGER", "cat", 1);
if (ret >= 0) /* normal exit */
exit(ret);
- die_errno("while expanding alias '%s': '%s'",
- alias_command, alias_string + 1);
+ die_errno(_("while expanding alias '%s': '%s'"),
+ alias_command, alias_string + 1);
}
count = split_cmdline(alias_string, &new_argv);
if (count < 0)
- die("Bad alias.%s string: %s", alias_command,
- split_cmdline_strerror(count));
+ die(_("bad alias.%s string: %s"), alias_command,
+ _(split_cmdline_strerror(count)));
option_count = handle_options(&new_argv, &count, &envchanged);
if (envchanged)
- die("alias '%s' changes environment variables.\n"
- "You can use '!git' in the alias to do this",
- alias_command);
+ die(_("alias '%s' changes environment variables.\n"
+ "You can use '!git' in the alias to do this"),
+ alias_command);
memmove(new_argv - option_count, new_argv,
count * sizeof(char *));
new_argv -= option_count;
if (count < 1)
- die("empty alias for %s", alias_command);
+ die(_("empty alias for %s"), alias_command);
if (!strcmp(alias_command, new_argv[0]))
- die("recursive alias: %s", alias_command);
+ die(_("recursive alias: %s"), alias_command);
trace_argv_printf(new_argv,
"trace: alias expansion: %s =>",
if (!help && get_super_prefix()) {
if (!(p->option & SUPPORT_SUPER_PREFIX))
- die("%s doesn't support --super-prefix", p->cmd);
+ die(_("%s doesn't support --super-prefix"), p->cmd);
}
if (!help && p->option & NEED_WORK_TREE)
trace_argv_printf(argv, "trace: built-in: git");
- validate_cache_entries(&the_index);
+ validate_cache_entries(the_repository->index);
status = p->fn(argc, argv, prefix);
- validate_cache_entries(&the_index);
+ validate_cache_entries(the_repository->index);
if (status)
return status;
/* Check for ENOSPC and EIO errors.. */
if (fflush(stdout))
- die_errno("write failure on standard output");
+ die_errno(_("write failure on standard output"));
if (ferror(stdout))
- die("unknown write failure on standard output");
+ die(_("unknown write failure on standard output"));
if (fclose(stdout))
- die_errno("close failed on standard output");
+ die_errno(_("close failed on standard output"));
return 0;
}
int status;
if (get_super_prefix())
- die("%s doesn't support --super-prefix", argv[0]);
+ die(_("%s doesn't support --super-prefix"), argv[0]);
if (use_pager == -1 && !is_builtin(argv[0]))
use_pager = check_pager_config(argv[0]);
if (skip_prefix(cmd, "git-", &cmd)) {
argv[0] = cmd;
handle_builtin(argc, argv);
- die("cannot handle %s as a builtin", cmd);
+ die(_("cannot handle %s as a builtin"), cmd);
}
/* Look for flags.. */
} else {
/* The user didn't specify a command; give them help */
commit_pager_choice();
- printf("usage: %s\n\n", git_usage_string);
+ printf(_("usage: %s\n\n"), git_usage_string);
list_common_cmds_help();
printf("\n%s\n", _(git_more_info_string));
exit(1);
* is not thread-safe.
*/
grep_attr_lock();
- textconv = userdiff_get_textconv(gs->driver);
+ textconv = userdiff_get_textconv(opt->repo, gs->driver);
grep_attr_unlock();
}
#include "block-sha1/sha1.h"
#endif
+#if defined(SHA256_GCRYPT)
+#include "sha256/gcrypt.h"
+#elif defined(SHA256_OPENSSL)
+#include <openssl/sha.h>
+#else
+#include "sha256/block/sha256.h"
+#endif
+
#ifndef platform_SHA_CTX
/*
* platform's underlying implementation of SHA-1; could be OpenSSL,
#define git_SHA1_Update platform_SHA1_Update
#define git_SHA1_Final platform_SHA1_Final
+#ifndef platform_SHA256_CTX
+#define platform_SHA256_CTX SHA256_CTX
+#define platform_SHA256_Init SHA256_Init
+#define platform_SHA256_Update SHA256_Update
+#define platform_SHA256_Final SHA256_Final
+#endif
+
+#define git_SHA256_CTX platform_SHA256_CTX
+#define git_SHA256_Init platform_SHA256_Init
+#define git_SHA256_Update platform_SHA256_Update
+#define git_SHA256_Final platform_SHA256_Final
+
#ifdef SHA1_MAX_BLOCK_SIZE
#include "compat/sha1-chunked.h"
#undef git_SHA1_Update
#define GIT_HASH_UNKNOWN 0
/* SHA-1 */
#define GIT_HASH_SHA1 1
+/* SHA-256 */
+#define GIT_HASH_SHA256 2
/* Number of algorithms supported (including unknown). */
-#define GIT_HASH_NALGOS (GIT_HASH_SHA1 + 1)
+#define GIT_HASH_NALGOS (GIT_HASH_SHA256 + 1)
/* A suitably aligned type for stack allocations of hash contexts. */
union git_hash_ctx {
git_SHA_CTX sha1;
+ git_SHA256_CTX sha256;
};
typedef union git_hash_ctx git_hash_ctx;
/* The length of the hash in hex characters. */
size_t hexsz;
+ /* The block size of the hash. */
+ size_t blksz;
+
/* The hash initialization function. */
git_hash_init_fn init_fn;
};
extern const struct git_hash_algo hash_algos[GIT_HASH_NALGOS];
+/*
+ * Return a GIT_HASH_* constant based on the name. Returns GIT_HASH_UNKNOWN if
+ * the name doesn't match a known algorithm.
+ */
+int hash_algo_by_name(const char *name);
+/* Identical, except based on the format ID. */
+int hash_algo_by_id(uint32_t format_id);
+/* Identical, except for a pointer to struct git_hash_algo. */
+static inline int hash_algo_by_ptr(const struct git_hash_algo *p)
+{
+ return p - hash_algos;
+}
+
#endif
return ret;
}
-char *sha1_to_hex_r(char *buffer, const unsigned char *sha1)
+char *hash_to_hex_algop_r(char *buffer, const unsigned char *hash,
+ const struct git_hash_algo *algop)
{
static const char hex[] = "0123456789abcdef";
char *buf = buffer;
int i;
- for (i = 0; i < the_hash_algo->rawsz; i++) {
- unsigned int val = *sha1++;
+ for (i = 0; i < algop->rawsz; i++) {
+ unsigned int val = *hash++;
*buf++ = hex[val >> 4];
*buf++ = hex[val & 0xf];
}
return buffer;
}
+char *sha1_to_hex_r(char *buffer, const unsigned char *sha1)
+{
+ return hash_to_hex_algop_r(buffer, sha1, &hash_algos[GIT_HASH_SHA1]);
+}
+
char *oid_to_hex_r(char *buffer, const struct object_id *oid)
{
- return sha1_to_hex_r(buffer, oid->hash);
+ return hash_to_hex_algop_r(buffer, oid->hash, the_hash_algo);
}
-char *sha1_to_hex(const unsigned char *sha1)
+char *hash_to_hex_algop(const unsigned char *hash, const struct git_hash_algo *algop)
{
static int bufno;
static char hexbuffer[4][GIT_MAX_HEXSZ + 1];
bufno = (bufno + 1) % ARRAY_SIZE(hexbuffer);
- return sha1_to_hex_r(hexbuffer[bufno], sha1);
+ return hash_to_hex_algop_r(hexbuffer[bufno], hash, algop);
+}
+
+char *sha1_to_hex(const unsigned char *sha1)
+{
+ return hash_to_hex_algop(sha1, &hash_algos[GIT_HASH_SHA1]);
+}
+
+char *hash_to_hex(const unsigned char *hash)
+{
+ return hash_to_hex_algop(hash, the_hash_algo);
}
char *oid_to_hex(const struct object_id *oid)
{
- return sha1_to_hex(oid->hash);
+ return hash_to_hex_algop(oid->hash, the_hash_algo);
}
if (buffer_input || gzipped_request || req_len >= 0)
cld.in = -1;
cld.git_cmd = 1;
+ cld.clean_on_exit = 1;
+ cld.wait_after_clean = 1;
if (start_command(&cld))
exit(1);
while (tree_entry(&desc, &entry))
switch (object_type(entry.mode)) {
case OBJ_TREE:
- p = process_tree(lookup_tree(the_repository, entry.oid),
+ p = process_tree(lookup_tree(the_repository, &entry.oid),
p);
break;
case OBJ_BLOB:
- p = process_blob(lookup_blob(the_repository, entry.oid),
+ p = process_blob(lookup_blob(the_repository, &entry.oid),
p);
break;
default:
pushing = 0;
if (prepare_revision_walk(&revs))
die("revision walk setup failed");
- mark_edges_uninteresting(&revs, NULL);
+ mark_edges_uninteresting(&revs, NULL, 0);
objects_to_send = get_delta(&revs, ref_lock);
finish_all_active_slots();
ret = error("File %s has bad hash", hex);
} else if (req->rename < 0) {
struct strbuf buf = STRBUF_INIT;
- sha1_file_name(the_repository, &buf, req->sha1);
+ loose_object_path(the_repository, &buf, req->sha1);
ret = error("unable to write sha1 filename %s", buf.buf);
strbuf_release(&buf);
}
static int curl_ssl_verify = -1;
static int curl_ssl_try;
+static const char *curl_http_version = NULL;
static const char *ssl_cert;
static const char *ssl_cipherlist;
static const char *ssl_version;
static int http_options(const char *var, const char *value, void *cb)
{
+ if (!strcmp("http.version", var)) {
+ return git_config_string(&curl_http_version, var, value);
+ }
if (!strcmp("http.sslverify", var)) {
curl_ssl_verify = git_config_bool(var, value);
return 0;
}
#endif
+#if LIBCURL_VERSION_NUM >=0x072f00
+static int get_curl_http_version_opt(const char *version_string, long *opt)
+{
+ int i;
+ static struct {
+ const char *name;
+ long opt_token;
+ } choice[] = {
+ { "HTTP/1.1", CURL_HTTP_VERSION_1_1 },
+ { "HTTP/2", CURL_HTTP_VERSION_2 }
+ };
+
+ for (i = 0; i < ARRAY_SIZE(choice); i++) {
+ if (!strcmp(version_string, choice[i].name)) {
+ *opt = choice[i].opt_token;
+ return 0;
+ }
+ }
+
+ warning("unknown value given to http.version: '%s'", version_string);
+ return -1; /* not found */
+}
+
+#endif
+
static CURL *get_curl_handle(void)
{
CURL *result = curl_easy_init();
curl_easy_setopt(result, CURLOPT_SSL_VERIFYHOST, 2);
}
+#if LIBCURL_VERSION_NUM >= 0x072f00 // 7.47.0
+ if (curl_http_version) {
+ long opt;
+ if (!get_curl_http_version_opt(curl_http_version, &opt)) {
+ /* Set request use http version */
+ curl_easy_setopt(result, CURLOPT_HTTP_VERSION, opt);
+ }
+ }
+#endif
+
#if LIBCURL_VERSION_NUM >= 0x070907
curl_easy_setopt(result, CURLOPT_NETRC, CURL_NETRC_OPTIONAL);
#endif
strbuf_addstr(&buf, "Pragma:");
if (options && options->no_cache)
strbuf_addstr(&buf, " no-cache");
- if (options && options->keep_error)
- curl_easy_setopt(slot->curl, CURLOPT_FAILONERROR, 0);
if (options && options->initial_request &&
http_follow_config == HTTP_FOLLOW_INITIAL)
curl_easy_setopt(slot->curl, CURLOPT_FOLLOWLOCATION, 1);
curl_easy_setopt(slot->curl, CURLOPT_URL, url);
curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(slot->curl, CURLOPT_ENCODING, "");
+ curl_easy_setopt(slot->curl, CURLOPT_FAILONERROR, 0);
ret = run_one_slot(slot, &results);
return ret;
/*
- * If we are using KEEP_ERROR, the previous request may have
- * put cruft into our output stream; we should clear it out before
- * making our next request. We only know how to do this for
- * the strbuf case, but that is enough to satisfy current callers.
+ * The previous request may have put cruft into our output stream; we
+ * should clear it out before making our next request.
*/
- if (options && options->keep_error) {
- switch (target) {
- case HTTP_REQUEST_STRBUF:
- strbuf_reset(result);
- break;
- default:
- BUG("HTTP_KEEP_ERROR is only supported with strbufs");
+ switch (target) {
+ case HTTP_REQUEST_STRBUF:
+ strbuf_reset(result);
+ break;
+ case HTTP_REQUEST_FILE:
+ if (fflush(result)) {
+ error_errno("unable to flush a file");
+ return HTTP_START_FAILED;
+ }
+ rewind(result);
+ if (ftruncate(fileno(result), 0) < 0) {
+ error_errno("unable to truncate a file");
+ return HTTP_START_FAILED;
}
+ break;
+ default:
+ BUG("Unknown http_request target");
}
credential_fill(&http_auth);
hashcpy(freq->sha1, sha1);
freq->localfile = -1;
- sha1_file_name(the_repository, &filename, sha1);
+ loose_object_path(the_repository, &filename, sha1);
strbuf_addf(&freq->tmpfile, "%s.temp", filename.buf);
strbuf_addf(&prevfile, "%s.prev", filename.buf);
unlink_or_warn(freq->tmpfile.buf);
return -1;
}
- sha1_file_name(the_repository, &filename, freq->sha1);
+ loose_object_path(the_repository, &filename, freq->sha1);
freq->rename = finalize_object_file(freq->tmpfile.buf, filename.buf);
strbuf_release(&filename);
/* Options for http_get_*() */
struct http_get_options {
unsigned no_cache:1,
- keep_error:1,
initial_request:1;
/* If non-NULL, returns the content-type of the response. */
}
static int curl_append_msgs_to_imap(struct imap_server_conf *server,
- struct strbuf* all_msgs, int total) {
+ struct strbuf* all_msgs, int total)
+{
int ofs = 0;
int n = 0;
struct buffer msgbuf = { STRBUF_INIT, 0 };
struct object *obj = revs->pending.objects[i].item;
if (obj->flags & UNINTERESTING)
continue;
- obj = deref_tag(the_repository, obj, NULL, 0);
+ obj = deref_tag(revs->repo, obj, NULL, 0);
if (obj->type != OBJ_COMMIT)
die("Non commit %s?", revs->pending.objects[i].name);
if (commit)
* See Documentation/rev-list-options.txt for allowed values for <arg>.
*
* Capture the given arg as the "filter_spec". This can be forwarded to
- * subordinate commands when necessary. We also "intern" the arg for
- * the convenience of the current command.
+ * subordinate commands when necessary (although it's better to pass it through
+ * expand_list_objects_filter_spec() first). We also "intern" the arg for the
+ * convenience of the current command.
*/
static int gently_parse_list_objects_filter(
struct list_objects_filter_options *filter_options,
}
} else if (skip_prefix(arg, "tree:", &v0)) {
- unsigned long depth;
- if (!git_parse_ulong(v0, &depth) || depth != 0) {
+ if (!git_parse_ulong(v0, &filter_options->tree_exclude_depth)) {
if (errbuf) {
strbuf_addstr(
errbuf,
- _("only 'tree:0' is supported"));
+ _("expected 'tree:<depth>'"));
}
return 1;
}
- filter_options->choice = LOFC_TREE_NONE;
+ filter_options->choice = LOFC_TREE_DEPTH;
return 0;
} else if (skip_prefix(arg, "sparse:oid=", &v0)) {
* command, but DO NOT complain if we don't have the blob or
* ref locally.
*/
- if (!get_oid_with_context(v0, GET_OID_BLOB,
+ if (!get_oid_with_context(the_repository, v0, GET_OID_BLOB,
&sparse_oid, &oc))
filter_options->sparse_oid_value = oiddup(&sparse_oid);
filter_options->choice = LOFC_SPARSE_OID;
return parse_list_objects_filter(filter_options, arg);
}
+void expand_list_objects_filter_spec(
+ const struct list_objects_filter_options *filter,
+ struct strbuf *expanded_spec)
+{
+ strbuf_init(expanded_spec, strlen(filter->filter_spec));
+ if (filter->choice == LOFC_BLOB_LIMIT)
+ strbuf_addf(expanded_spec, "blob:limit=%lu",
+ filter->blob_limit_value);
+ else if (filter->choice == LOFC_TREE_DEPTH)
+ strbuf_addf(expanded_spec, "tree:%lu",
+ filter->tree_exclude_depth);
+ else
+ strbuf_addstr(expanded_spec, filter->filter_spec);
+}
+
void list_objects_filter_release(
struct list_objects_filter_options *filter_options)
{
#define LIST_OBJECTS_FILTER_OPTIONS_H
#include "parse-options.h"
+#include "strbuf.h"
/*
* The list of defined filters for list-objects.
LOFC_DISABLED = 0,
LOFC_BLOB_NONE,
LOFC_BLOB_LIMIT,
- LOFC_TREE_NONE,
+ LOFC_TREE_DEPTH,
LOFC_SPARSE_OID,
LOFC_SPARSE_PATH,
LOFC__COUNT /* must be last */
/*
* 'filter_spec' is the raw argument value given on the command line
* or protocol request. (The part after the "--keyword=".) For
- * commands that launch filtering sub-processes, this value should be
- * passed to them as received by the current process.
+ * commands that launch filtering sub-processes, or for communication
+ * over the network, don't use this value; use the result of
+ * expand_list_objects_filter_spec() instead.
*/
char *filter_spec;
struct object_id *sparse_oid_value;
char *sparse_path_value;
unsigned long blob_limit_value;
+ unsigned long tree_exclude_depth;
};
/* Normalized command line arguments */
N_("object filtering"), 0, \
opt_parse_list_objects_filter }
+/*
+ * Translates abbreviated numbers in the filter's filter_spec into their
+ * fully-expanded forms (e.g., "limit:blob=1k" becomes "limit:blob=1024").
+ *
+ * This form should be used instead of the raw filter_spec field when
+ * communicating with a remote process or subprocess.
+ */
+void expand_list_objects_filter_spec(
+ const struct list_objects_filter_options *filter,
+ struct strbuf *expanded_spec);
+
void list_objects_filter_release(
struct list_objects_filter_options *filter_options);
#include "list-objects.h"
#include "list-objects-filter.h"
#include "list-objects-filter-options.h"
+#include "oidmap.h"
#include "oidset.h"
#include "object-store.h"
};
static enum list_objects_filter_result filter_blobs_none(
+ struct repository *r,
enum list_objects_filter_situation filter_situation,
struct object *obj,
const char *pathname,
* A filter for list-objects to omit ALL trees and blobs from the traversal.
* Can OPTIONALLY collect a list of the omitted OIDs.
*/
-struct filter_trees_none_data {
+struct filter_trees_depth_data {
struct oidset *omits;
+
+ /*
+ * Maps trees to the minimum depth at which they were seen. It is not
+ * necessary to re-traverse a tree at deeper or equal depths than it has
+ * already been traversed.
+ *
+ * We can't use LOFR_MARK_SEEN for tree objects since this will prevent
+ * it from being traversed at shallower depths.
+ */
+ struct oidmap seen_at_depth;
+
+ unsigned long exclude_depth;
+ unsigned long current_depth;
};
-static enum list_objects_filter_result filter_trees_none(
+struct seen_map_entry {
+ struct oidmap_entry base;
+ size_t depth;
+};
+
+/* Returns 1 if the oid was in the omits set before it was invoked. */
+static int filter_trees_update_omits(
+ struct object *obj,
+ struct filter_trees_depth_data *filter_data,
+ int include_it)
+{
+ if (!filter_data->omits)
+ return 0;
+
+ if (include_it)
+ return oidset_remove(filter_data->omits, &obj->oid);
+ else
+ return oidset_insert(filter_data->omits, &obj->oid);
+}
+
+static enum list_objects_filter_result filter_trees_depth(
+ struct repository *r,
enum list_objects_filter_situation filter_situation,
struct object *obj,
const char *pathname,
const char *filename,
void *filter_data_)
{
- struct filter_trees_none_data *filter_data = filter_data_;
+ struct filter_trees_depth_data *filter_data = filter_data_;
+ struct seen_map_entry *seen_info;
+ int include_it = filter_data->current_depth <
+ filter_data->exclude_depth;
+ int filter_res;
+ int already_seen;
+
+ /*
+ * Note that we do not use _MARK_SEEN in order to allow re-traversal in
+ * case we encounter a tree or blob again at a shallower depth.
+ */
switch (filter_situation) {
default:
BUG("unknown filter_situation: %d", filter_situation);
- case LOFS_BEGIN_TREE:
+ case LOFS_END_TREE:
+ assert(obj->type == OBJ_TREE);
+ filter_data->current_depth--;
+ return LOFR_ZERO;
+
case LOFS_BLOB:
- if (filter_data->omits) {
- oidset_insert(filter_data->omits, &obj->oid);
- /* _MARK_SEEN but not _DO_SHOW (hard omit) */
- return LOFR_MARK_SEEN;
+ filter_trees_update_omits(obj, filter_data, include_it);
+ return include_it ? LOFR_MARK_SEEN | LOFR_DO_SHOW : LOFR_ZERO;
+
+ case LOFS_BEGIN_TREE:
+ seen_info = oidmap_get(
+ &filter_data->seen_at_depth, &obj->oid);
+ if (!seen_info) {
+ seen_info = xcalloc(1, sizeof(*seen_info));
+ oidcpy(&seen_info->base.oid, &obj->oid);
+ seen_info->depth = filter_data->current_depth;
+ oidmap_put(&filter_data->seen_at_depth, seen_info);
+ already_seen = 0;
} else {
- /*
- * Not collecting omits so no need to to traverse tree.
- */
- return LOFR_SKIP_TREE | LOFR_MARK_SEEN;
+ already_seen =
+ filter_data->current_depth >= seen_info->depth;
}
- case LOFS_END_TREE:
- assert(obj->type == OBJ_TREE);
- return LOFR_ZERO;
+ if (already_seen) {
+ filter_res = LOFR_SKIP_TREE;
+ } else {
+ int been_omitted = filter_trees_update_omits(
+ obj, filter_data, include_it);
+ seen_info->depth = filter_data->current_depth;
+
+ if (include_it)
+ filter_res = LOFR_DO_SHOW;
+ else if (filter_data->omits && !been_omitted)
+ /*
+ * Must update omit information of children
+ * recursively; they have not been omitted yet.
+ */
+ filter_res = LOFR_ZERO;
+ else
+ filter_res = LOFR_SKIP_TREE;
+ }
+ filter_data->current_depth++;
+ return filter_res;
}
}
-static void* filter_trees_none__init(
+static void filter_trees_free(void *filter_data) {
+ struct filter_trees_depth_data *d = filter_data;
+ if (!d)
+ return;
+ oidmap_free(&d->seen_at_depth, 1);
+ free(d);
+}
+
+static void *filter_trees_depth__init(
struct oidset *omitted,
struct list_objects_filter_options *filter_options,
filter_object_fn *filter_fn,
filter_free_fn *filter_free_fn)
{
- struct filter_trees_none_data *d = xcalloc(1, sizeof(*d));
+ struct filter_trees_depth_data *d = xcalloc(1, sizeof(*d));
d->omits = omitted;
+ oidmap_init(&d->seen_at_depth, 0);
+ d->exclude_depth = filter_options->tree_exclude_depth;
+ d->current_depth = 0;
- *filter_fn = filter_trees_none;
- *filter_free_fn = free;
+ *filter_fn = filter_trees_depth;
+ *filter_free_fn = filter_trees_free;
return d;
}
};
static enum list_objects_filter_result filter_blobs_limit(
+ struct repository *r,
enum list_objects_filter_situation filter_situation,
struct object *obj,
const char *pathname,
assert(obj->type == OBJ_BLOB);
assert((obj->flags & SEEN) == 0);
- t = oid_object_info(the_repository, &obj->oid, &object_length);
+ t = oid_object_info(r, &obj->oid, &object_length);
if (t != OBJ_BLOB) { /* probably OBJ_NONE */
/*
* We DO NOT have the blob locally, so we cannot
};
static enum list_objects_filter_result filter_sparse(
+ struct repository *r,
enum list_objects_filter_situation filter_situation,
struct object *obj,
const char *pathname,
dtype = DT_DIR;
val = is_excluded_from_list(pathname, strlen(pathname),
filename, &dtype, &filter_data->el,
- &the_index);
+ r->index);
if (val < 0)
val = filter_data->array_frame[filter_data->nr].defval;
dtype = DT_REG;
val = is_excluded_from_list(pathname, strlen(pathname),
filename, &dtype, &filter_data->el,
- &the_index);
+ r->index);
if (val < 0)
val = frame->defval;
if (val > 0) {
NULL,
filter_blobs_none__init,
filter_blobs_limit__init,
- filter_trees_none__init,
+ filter_trees_depth__init,
filter_sparse_oid__init,
filter_sparse_path__init,
};
struct list_objects_filter_options;
struct object;
struct oidset;
+struct repository;
/*
* During list-object traversal we allow certain objects to be
};
typedef enum list_objects_filter_result (*filter_object_fn)(
+ struct repository *r,
enum list_objects_filter_situation filter_situation,
struct object *obj,
const char *pathname,
pathlen = path->len;
strbuf_addstr(path, name);
if ((obj->flags & NOT_USER_GIVEN) && ctx->filter_fn)
- r = ctx->filter_fn(LOFS_BLOB, obj,
+ r = ctx->filter_fn(ctx->revs->repo,
+ LOFS_BLOB, obj,
path->buf, &path->buf[pathlen],
ctx->filter_data);
if (r & LOFR_MARK_SEEN)
while (tree_entry(&desc, &entry)) {
if (match != all_entries_interesting) {
- match = tree_entry_interesting(&entry, base, 0,
+ match = tree_entry_interesting(ctx->revs->repo->index,
+ &entry, base, 0,
&ctx->revs->diffopt.pathspec);
if (match == all_entries_not_interesting)
break;
}
if (S_ISDIR(entry.mode)) {
- struct tree *t = lookup_tree(the_repository, entry.oid);
+ struct tree *t = lookup_tree(ctx->revs->repo, &entry.oid);
t->object.flags |= NOT_USER_GIVEN;
process_tree(ctx, t, base, entry.path);
}
else if (S_ISGITLINK(entry.mode))
- process_gitlink(ctx, entry.oid->hash,
+ process_gitlink(ctx, entry.oid.hash,
base, entry.path);
else {
- struct blob *b = lookup_blob(the_repository, entry.oid);
+ struct blob *b = lookup_blob(ctx->revs->repo, &entry.oid);
b->object.flags |= NOT_USER_GIVEN;
process_blob(ctx, b, base, entry.path);
}
strbuf_addstr(base, name);
if ((obj->flags & NOT_USER_GIVEN) && ctx->filter_fn)
- r = ctx->filter_fn(LOFS_BEGIN_TREE, obj,
+ r = ctx->filter_fn(ctx->revs->repo,
+ LOFS_BEGIN_TREE, obj,
base->buf, &base->buf[baselen],
ctx->filter_data);
if (r & LOFR_MARK_SEEN)
process_tree_contents(ctx, tree, base);
if ((obj->flags & NOT_USER_GIVEN) && ctx->filter_fn) {
- r = ctx->filter_fn(LOFS_END_TREE, obj,
+ r = ctx->filter_fn(ctx->revs->repo,
+ LOFS_END_TREE, obj,
base->buf, &base->buf[baselen],
ctx->filter_data);
if (r & LOFR_MARK_SEEN)
}
}
-void mark_edges_uninteresting(struct rev_info *revs, show_edge_fn show_edge)
+static void add_edge_parents(struct commit *commit,
+ struct rev_info *revs,
+ show_edge_fn show_edge,
+ struct oidset *set)
+{
+ struct commit_list *parents;
+
+ for (parents = commit->parents; parents; parents = parents->next) {
+ struct commit *parent = parents->item;
+ struct tree *tree = get_commit_tree(parent);
+
+ if (!tree)
+ continue;
+
+ oidset_insert(set, &tree->object.oid);
+
+ if (!(parent->object.flags & UNINTERESTING))
+ continue;
+ tree->object.flags |= UNINTERESTING;
+
+ if (revs->edge_hint && !(parent->object.flags & SHOWN)) {
+ parent->object.flags |= SHOWN;
+ show_edge(parent);
+ }
+ }
+}
+
+void mark_edges_uninteresting(struct rev_info *revs,
+ show_edge_fn show_edge,
+ int sparse)
{
struct commit_list *list;
int i;
- for (list = revs->commits; list; list = list->next) {
- struct commit *commit = list->item;
+ if (sparse) {
+ struct oidset set;
+ oidset_init(&set, 16);
- if (commit->object.flags & UNINTERESTING) {
- mark_tree_uninteresting(revs->repo,
- get_commit_tree(commit));
- if (revs->edge_hint_aggressive && !(commit->object.flags & SHOWN)) {
- commit->object.flags |= SHOWN;
- show_edge(commit);
+ for (list = revs->commits; list; list = list->next) {
+ struct commit *commit = list->item;
+ struct tree *tree = get_commit_tree(commit);
+
+ if (commit->object.flags & UNINTERESTING)
+ tree->object.flags |= UNINTERESTING;
+
+ oidset_insert(&set, &tree->object.oid);
+ add_edge_parents(commit, revs, show_edge, &set);
+ }
+
+ mark_trees_uninteresting_sparse(revs->repo, &set);
+ oidset_clear(&set);
+ } else {
+ for (list = revs->commits; list; list = list->next) {
+ struct commit *commit = list->item;
+ if (commit->object.flags & UNINTERESTING) {
+ mark_tree_uninteresting(revs->repo,
+ get_commit_tree(commit));
+ if (revs->edge_hint_aggressive && !(commit->object.flags & SHOWN)) {
+ commit->object.flags |= SHOWN;
+ show_edge(commit);
+ }
+ continue;
}
- continue;
+ mark_edge_parents_uninteresting(commit, revs, show_edge);
}
- mark_edge_parents_uninteresting(commit, revs, show_edge);
}
+
if (revs->edge_hint_aggressive) {
for (i = 0; i < revs->cmdline.nr; i++) {
struct object *obj = revs->cmdline.rev[i].item;
void traverse_commit_list(struct rev_info *, show_commit_fn, show_object_fn, void *);
typedef void (*show_edge_fn)(struct commit *);
-void mark_edges_uninteresting(struct rev_info *, show_edge_fn);
+void mark_edges_uninteresting(struct rev_info *revs,
+ show_edge_fn show_edge,
+ int sparse);
struct oidset;
struct list_objects_filter_options;
if (opts->virtual_ancestor) {
if (driver->recursive)
driver = find_ll_merge_driver(driver->recursive);
- marker_size += 2;
+ }
+ if (opts->extra_marker_size) {
+ marker_size += opts->extra_marker_size;
}
return driver->fn(driver, result_buf, path, ancestor, ancestor_label,
ours, our_label, theirs, their_label,
unsigned virtual_ancestor : 1;
unsigned variant : 2; /* favor ours, favor theirs, or union merge */
unsigned renormalize : 1;
+ unsigned extra_marker_size;
long xdl_opts;
};
ctx.color = opt->diffopt.use_color;
ctx.expand_tabs_in_log = opt->expand_tabs_in_log;
ctx.output_encoding = get_log_output_encoding();
+ ctx.rev = opt;
if (opt->from_ident.mail_begin && opt->from_ident.name_begin)
ctx.from_ident = &opt->from_ident;
if (opt->graph)
#include "argv-array.h"
#include "ls-refs.h"
#include "pkt-line.h"
+#include "config.h"
/*
* Check if one of the prefixes is a prefix of the ref.
const char *refname_nons = strip_namespace(refname);
struct strbuf refline = STRBUF_INIT;
- if (!ref_match(&data->prefixes, refname))
+ if (ref_is_hidden(refname_nons, refname))
+ return 0;
+
+ if (!ref_match(&data->prefixes, refname_nons))
return 0;
strbuf_addf(&refline, "%s %s", oid_to_hex(oid), refname_nons);
return 0;
}
+static int ls_refs_config(const char *var, const char *value, void *data)
+{
+ /*
+ * We only serve fetches over v2 for now, so respect only "uploadpack"
+ * config. This may need to eventually be expanded to "receive", but we
+ * don't yet know how that information will be passed to ls-refs.
+ */
+ return parse_hide_refs_config(var, value, "uploadpack");
+}
+
int ls_refs(struct repository *r, struct argv_array *keys,
struct packet_reader *request)
{
memset(&data, 0, sizeof(data));
+ git_config(ls_refs_config, NULL);
+
while (packet_reader_read(request) != PACKET_READ_FLUSH) {
const char *arg = request->line;
const char *out;
#include "tree-walk.h"
#include "object-store.h"
-static int score_missing(unsigned mode, const char *path)
+static int score_missing(unsigned mode)
{
int score;
return score;
}
-static int score_differs(unsigned mode1, unsigned mode2, const char *path)
+static int score_differs(unsigned mode1, unsigned mode2)
{
int score;
return score;
}
-static int score_matches(unsigned mode1, unsigned mode2, const char *path)
+static int score_matches(unsigned mode1, unsigned mode2)
{
int score;
if (cmp < 0) {
/* path1 does not appear in two */
- score += score_missing(one.entry.mode, one.entry.path);
+ score += score_missing(one.entry.mode);
update_tree_entry(&one);
} else if (cmp > 0) {
/* path2 does not appear in one */
- score += score_missing(two.entry.mode, two.entry.path);
+ score += score_missing(two.entry.mode);
update_tree_entry(&two);
} else {
/* path appears in both */
- if (!oideq(one.entry.oid, two.entry.oid)) {
+ if (!oideq(&one.entry.oid, &two.entry.oid)) {
/* they are different */
score += score_differs(one.entry.mode,
- two.entry.mode,
- one.entry.path);
+ two.entry.mode);
} else {
/* same subtree or blob */
score += score_matches(one.entry.mode,
- two.entry.mode,
- one.entry.path);
+ two.entry.mode);
}
update_tree_entry(&one);
update_tree_entry(&two);
char *buf;
unsigned long sz;
struct tree_desc desc;
- struct object_id *rewrite_here;
+ unsigned char *rewrite_here;
const struct object_id *rewrite_with;
struct object_id subtree;
enum object_type type;
while (desc.size) {
const char *name;
unsigned mode;
- const struct object_id *oid;
- oid = tree_entry_extract(&desc, &name, &mode);
+ tree_entry_extract(&desc, &name, &mode);
if (strlen(name) == toplen &&
!memcmp(name, prefix, toplen)) {
if (!S_ISDIR(mode))
die("entry %s in tree %s is not a tree", name,
oid_to_hex(oid1));
- rewrite_here = (struct object_id *)oid;
+
+ /*
+ * We cast here for two reasons:
+ *
+ * - to flip the "char *" (for the path) to "unsigned
+ * char *" (for the hash stored after it)
+ *
+ * - to discard the "const"; this is OK because we
+ * know it points into our non-const "buf"
+ */
+ rewrite_here = (unsigned char *)(desc.entry.path +
+ strlen(desc.entry.path) +
+ 1);
break;
}
update_tree_entry(&desc);
die("entry %.*s not found in tree %s", toplen, prefix,
oid_to_hex(oid1));
if (*subpath) {
- status = splice_tree(rewrite_here, subpath, oid2, &subtree);
+ struct object_id tree_oid;
+ hashcpy(tree_oid.hash, rewrite_here);
+ status = splice_tree(&tree_oid, subpath, oid2, &subtree);
if (status)
return status;
rewrite_with = &subtree;
} else {
rewrite_with = oid2;
}
- oidcpy(rewrite_here, rewrite_with);
+ hashcpy(rewrite_here, rewrite_with->hash);
status = write_object_file(buf, sz, tree_type, result);
free(buf);
return status;
return -1;
}
-static struct tree *shift_tree_object(struct tree *one, struct tree *two,
+static struct tree *shift_tree_object(struct repository *repo,
+ struct tree *one, struct tree *two,
const char *subtree_shift)
{
struct object_id shifted;
}
if (oideq(&two->object.oid, &shifted))
return two;
- return lookup_tree(the_repository, &shifted);
+ return lookup_tree(repo, &shifted);
}
-static struct commit *make_virtual_commit(struct tree *tree, const char *comment)
+static struct commit *make_virtual_commit(struct repository *repo,
+ struct tree *tree,
+ const char *comment)
{
- struct commit *commit = alloc_commit_node(the_repository);
+ struct commit *commit = alloc_commit_node(repo);
set_merge_remote_desc(commit, comment, (struct object *)commit);
commit->maybe_tree = tree;
enum rename_type {
RENAME_NORMAL = 0,
RENAME_VIA_DIR,
+ RENAME_ADD,
RENAME_DELETE,
RENAME_ONE_FILE_TO_ONE,
RENAME_ONE_FILE_TO_TWO,
struct stage_data *src_entry1,
struct stage_data *src_entry2)
{
+ int ostage1 = 0, ostage2;
struct rename_conflict_info *ci;
/*
dst_entry2->rename_conflict_info = ci;
}
- if (rename_type == RENAME_TWO_FILES_TO_ONE) {
- /*
- * For each rename, there could have been
- * modifications on the side of history where that
- * file was not renamed.
- */
- int ostage1 = o->branch1 == branch1 ? 3 : 2;
- int ostage2 = ostage1 ^ 1;
+ /*
+ * For each rename, there could have been
+ * modifications on the side of history where that
+ * file was not renamed.
+ */
+ if (rename_type == RENAME_ADD ||
+ rename_type == RENAME_TWO_FILES_TO_ONE) {
+ ostage1 = o->branch1 == branch1 ? 3 : 2;
ci->ren1_other.path = pair1->one->path;
oidcpy(&ci->ren1_other.oid, &src_entry1->stages[ostage1].oid);
ci->ren1_other.mode = src_entry1->stages[ostage1].mode;
+ }
+
+ if (rename_type == RENAME_TWO_FILES_TO_ONE) {
+ ostage2 = ostage1 ^ 1;
ci->ren2_other.path = pair2->one->path;
oidcpy(&ci->ren2_other.oid, &src_entry2->stages[ostage2].oid);
unsigned int mode, const struct object_id *oid,
const char *path, int stage, int refresh, int options)
{
+ struct index_state *istate = o->repo->index;
struct cache_entry *ce;
int ret;
- ce = make_cache_entry(&the_index, mode, oid ? oid : &null_oid, path, stage, 0);
+ ce = make_cache_entry(istate, mode, oid ? oid : &null_oid, path, stage, 0);
if (!ce)
return err(o, _("add_cacheinfo failed for path '%s'; merge aborting."), path);
- ret = add_cache_entry(ce, options);
+ ret = add_index_entry(istate, ce, options);
if (refresh) {
struct cache_entry *nce;
- nce = refresh_cache_entry(&the_index, ce, CE_MATCH_REFRESH | CE_MATCH_IGNORE_MISSING);
+ nce = refresh_cache_entry(istate, ce,
+ CE_MATCH_REFRESH | CE_MATCH_IGNORE_MISSING);
if (!nce)
return err(o, _("add_cacheinfo failed to refresh for path '%s'; merge aborting."), path);
if (nce != ce)
- ret = add_cache_entry(nce, options);
+ ret = add_index_entry(istate, nce, options);
}
return ret;
}
o->unpack_opts.merge = 1;
o->unpack_opts.head_idx = 2;
o->unpack_opts.fn = threeway_merge;
- o->unpack_opts.src_index = &the_index;
+ o->unpack_opts.src_index = o->repo->index;
o->unpack_opts.dst_index = &tmp_index;
o->unpack_opts.aggressive = !merge_detect_rename(o);
setup_unpack_trees_porcelain(&o->unpack_opts, "merge");
init_tree_desc_from_tree(t+2, merge);
rc = unpack_trees(3, t, &o->unpack_opts);
- cache_tree_free(&active_cache_tree);
+ cache_tree_free(&o->repo->index->cache_tree);
/*
- * Update the_index to match the new results, AFTER saving a copy
+ * Update o->repo->index to match the new results, AFTER saving a copy
* in o->orig_index. Update src_index to point to the saved copy.
* (verify_uptodate() checks src_index, and the original index is
* the one that had the necessary modification timestamps.)
*/
- o->orig_index = the_index;
- the_index = tmp_index;
+ o->orig_index = *o->repo->index;
+ *o->repo->index = tmp_index;
o->unpack_opts.src_index = &o->orig_index;
return rc;
struct tree *write_tree_from_memory(struct merge_options *o)
{
struct tree *result = NULL;
+ struct index_state *istate = o->repo->index;
- if (unmerged_cache()) {
+ if (unmerged_index(istate)) {
int i;
fprintf(stderr, "BUG: There are unmerged index entries:\n");
- for (i = 0; i < active_nr; i++) {
- const struct cache_entry *ce = active_cache[i];
+ for (i = 0; i < istate->cache_nr; i++) {
+ const struct cache_entry *ce = istate->cache[i];
if (ce_stage(ce))
fprintf(stderr, "BUG: %d %.*s\n", ce_stage(ce),
(int)ce_namelen(ce), ce->name);
BUG("unmerged index entries in merge-recursive.c");
}
- if (!active_cache_tree)
- active_cache_tree = cache_tree();
+ if (!istate->cache_tree)
+ istate->cache_tree = cache_tree();
- if (!cache_tree_fully_valid(active_cache_tree) &&
- cache_tree_update(&the_index, 0) < 0) {
+ if (!cache_tree_fully_valid(istate->cache_tree) &&
+ cache_tree_update(istate, 0) < 0) {
err(o, _("error building trees"));
return NULL;
}
- result = lookup_tree(the_repository, &active_cache_tree->oid);
+ result = lookup_tree(o->repo, &istate->cache_tree->oid);
return result;
}
{
struct pathspec match_all;
memset(&match_all, 0, sizeof(match_all));
- read_tree_recursive(tree, "", 0, 0, &match_all, save_files_dirs, o);
+ read_tree_recursive(the_repository, tree, "", 0, 0,
+ &match_all, save_files_dirs, o);
}
static int get_tree_entry_if_blob(const struct object_id *tree,
* Create a dictionary mapping file names to stage_data objects. The
* dictionary contains one entry for every path with a non-zero stage entry.
*/
-static struct string_list *get_unmerged(void)
+static struct string_list *get_unmerged(struct index_state *istate)
{
struct string_list *unmerged = xcalloc(1, sizeof(struct string_list));
int i;
unmerged->strdup_strings = 1;
- for (i = 0; i < active_nr; i++) {
+ for (i = 0; i < istate->cache_nr; i++) {
struct string_list_item *item;
struct stage_data *e;
- const struct cache_entry *ce = active_cache[i];
+ const struct cache_entry *ce = istate->cache[i];
if (!ce_stage(ce))
continue;
int clear = 1;
int options = ADD_CACHE_OK_TO_ADD | ADD_CACHE_SKIP_DFCHECK;
if (clear)
- if (remove_file_from_cache(path))
+ if (remove_file_from_index(opt->repo->index, path))
return -1;
if (o)
if (add_cacheinfo(opt, o->mode, &o->oid, path, 1, 0, options))
return 0;
}
-static int update_stages_for_stage_data(struct merge_options *opt,
- const char *path,
- const struct stage_data *stage_data)
-{
- struct diff_filespec o, a, b;
-
- o.mode = stage_data->stages[1].mode;
- oidcpy(&o.oid, &stage_data->stages[1].oid);
-
- a.mode = stage_data->stages[2].mode;
- oidcpy(&a.oid, &stage_data->stages[2].oid);
-
- b.mode = stage_data->stages[3].mode;
- oidcpy(&b.oid, &stage_data->stages[3].oid);
-
- return update_stages(opt, path,
- is_null_oid(&o.oid) ? NULL : &o,
- is_null_oid(&a.oid) ? NULL : &a,
- is_null_oid(&b.oid) ? NULL : &b);
-}
-
static void update_entry(struct stage_data *entry,
struct diff_filespec *o,
struct diff_filespec *a,
int update_working_directory = !o->call_depth && !no_wd;
if (update_cache) {
- if (remove_file_from_cache(path))
+ if (remove_file_from_index(o->repo->index, path))
return -1;
}
if (update_working_directory) {
if (ignore_case) {
struct cache_entry *ce;
- ce = cache_file_exists(path, strlen(path), ignore_case);
+ ce = index_file_exists(o->repo->index, path, strlen(path),
+ ignore_case);
if (ce && ce_stage(ce) == 0 && strcmp(path, ce->name))
return 0;
}
* check the working directory. If empty_ok is non-zero, also return
* 0 in the case where the working-tree dir exists but is empty.
*/
-static int dir_in_way(const char *path, int check_working_copy, int empty_ok)
+static int dir_in_way(struct index_state *istate, const char *path,
+ int check_working_copy, int empty_ok)
{
int pos;
struct strbuf dirpath = STRBUF_INIT;
strbuf_addstr(&dirpath, path);
strbuf_addch(&dirpath, '/');
- pos = cache_name_pos(dirpath.buf, dirpath.len);
+ pos = index_name_pos(istate, dirpath.buf, dirpath.len);
if (pos < 0)
pos = -1 - pos;
- if (pos < active_nr &&
- !strncmp(dirpath.buf, active_cache[pos]->name, dirpath.len)) {
+ if (pos < istate->cache_nr &&
+ !strncmp(dirpath.buf, istate->cache[pos]->name, dirpath.len)) {
strbuf_release(&dirpath);
return 1;
}
return 0;
}
-static int would_lose_untracked(const char *path)
+static int would_lose_untracked(struct merge_options *o, const char *path)
{
+ struct index_state *istate = o->repo->index;
+
/*
* This may look like it can be simplified to:
* return !was_tracked(o, path) && file_exists(path)
* update_file()/would_lose_untracked(); see every comment in this
* file which mentions "update_stages".
*/
- int pos = cache_name_pos(path, strlen(path));
+ int pos = index_name_pos(istate, path, strlen(path));
if (pos < 0)
pos = -1 - pos;
- while (pos < active_nr &&
- !strcmp(path, active_cache[pos]->name)) {
+ while (pos < istate->cache_nr &&
+ !strcmp(path, istate->cache[pos]->name)) {
/*
* If stage #0, it is definitely tracked.
* If it has stage #2 then it was tracked
* before this merge started. All other
* cases the path was not tracked.
*/
- switch (ce_stage(active_cache[pos])) {
+ switch (ce_stage(istate->cache[pos])) {
case 0:
case 2:
return 0;
* Do not unlink a file in the work tree if we are not
* tracking it.
*/
- if (would_lose_untracked(path))
+ if (would_lose_untracked(o, path))
return err(o, _("refusing to lose untracked file at '%s'"),
path);
}
if (S_ISREG(mode)) {
struct strbuf strbuf = STRBUF_INIT;
- if (convert_to_working_tree(&the_index, path, buf, size, &strbuf)) {
+ if (convert_to_working_tree(o->repo->index, path, buf, size, &strbuf)) {
free(buf);
size = strbuf.len;
buf = strbuf_detach(&strbuf, NULL);
const struct diff_filespec *a,
const struct diff_filespec *b,
const char *branch1,
- const char *branch2)
+ const char *branch2,
+ const int extra_marker_size)
{
mmfile_t orig, src1, src2;
struct ll_merge_options ll_opts = {0};
int merge_status;
ll_opts.renormalize = o->renormalize;
+ ll_opts.extra_marker_size = extra_marker_size;
ll_opts.xdl_opts = o->xdl_opts;
if (o->call_depth) {
merge_status = ll_merge(result_buf, a->path, &orig, base_name,
&src1, name1, &src2, name2,
- &the_index, &ll_opts);
+ o->repo->index, &ll_opts);
free(base_name);
free(name1);
return merge_status;
}
-static int find_first_merges(struct object_array *result, const char *path,
+static int find_first_merges(struct repository *repo,
+ struct object_array *result, const char *path,
struct commit *a, struct commit *b)
{
int i, j;
/* get all revisions that merge commit a */
xsnprintf(merged_revision, sizeof(merged_revision), "^%s",
oid_to_hex(&a->object.oid));
- repo_init_revisions(the_repository, &revs, NULL);
+ repo_init_revisions(repo, &revs, NULL);
rev_opts.submodule = path;
/* FIXME: can't handle linked worktrees in submodules yet */
revs.single_worktree = path != NULL;
return 0;
}
- if (!(commit_base = lookup_commit_reference(the_repository, base)) ||
- !(commit_a = lookup_commit_reference(the_repository, a)) ||
- !(commit_b = lookup_commit_reference(the_repository, b))) {
+ if (!(commit_base = lookup_commit_reference(o->repo, base)) ||
+ !(commit_a = lookup_commit_reference(o->repo, a)) ||
+ !(commit_b = lookup_commit_reference(o->repo, b))) {
output(o, 1, _("Failed to merge submodule %s (commits not present)"), path);
return 0;
}
return 0;
/* find commit which merges them */
- parent_count = find_first_merges(&merges, path, commit_a, commit_b);
+ parent_count = find_first_merges(o->repo, &merges, path,
+ commit_a, commit_b);
switch (parent_count) {
case 0:
output(o, 1, _("Failed to merge submodule %s (merge following commits not found)"), path);
const char *filename,
const char *branch1,
const char *branch2,
+ const int extra_marker_size,
struct merge_file_info *result)
{
if (o->branch1 != branch1) {
*/
return merge_mode_and_contents(o, one, b, a,
filename,
- branch2, branch1, result);
+ branch2, branch1,
+ extra_marker_size, result);
}
result->merge = 0;
int ret = 0, merge_status;
merge_status = merge_3way(o, &result_buf, one, a, b,
- branch1, branch2);
+ branch1, branch2,
+ extra_marker_size);
if ((merge_status < 0) || !result_buf.ptr)
ret = err(o, _("Failed to execute internal merge"));
*/
const struct diff_filespec *dest = pair->two;
- if (!o->call_depth && would_lose_untracked(dest->path)) {
+ if (!o->call_depth && would_lose_untracked(o, dest->path)) {
char *alt_path = unique_path(o, dest->path, rename_branch);
output(o, 1, _("Error: Refusing to lose untracked file at %s; "
const char *update_path = path;
int ret = 0;
- if (dir_in_way(path, !o->call_depth, 0) ||
- (!o->call_depth && would_lose_untracked(path))) {
+ if (dir_in_way(o->repo->index, path, !o->call_depth, 0) ||
+ (!o->call_depth && would_lose_untracked(o, path))) {
update_path = alt_path = unique_path(o, path, change_branch);
}
* correct; since there is no true "middle point" between
* them, simply reuse the base version for virtual merge base.
*/
- ret = remove_file_from_cache(path);
+ ret = remove_file_from_index(o->repo->index, path);
if (!ret)
ret = update_file(o, 0, o_oid, o_mode, update_path);
} else {
return -1;
if (o->call_depth)
- return remove_file_from_cache(dest->path);
+ return remove_file_from_index(o->repo->index, dest->path);
else
return update_stages(o, dest->path, NULL,
rename_branch == o->branch1 ? dest : NULL,
return target;
}
-static int handle_file(struct merge_options *o,
- struct diff_filespec *rename,
- int stage,
- struct rename_conflict_info *ci)
+static int handle_file_collision(struct merge_options *o,
+ const char *collide_path,
+ const char *prev_path1,
+ const char *prev_path2,
+ const char *branch1, const char *branch2,
+ const struct object_id *a_oid,
+ unsigned int a_mode,
+ const struct object_id *b_oid,
+ unsigned int b_mode)
{
- char *dst_name = rename->path;
- struct stage_data *dst_entry;
- const char *cur_branch, *other_branch;
- struct diff_filespec other;
- struct diff_filespec *add;
- int ret;
+ struct merge_file_info mfi;
+ struct diff_filespec null, a, b;
+ char *alt_path = NULL;
+ const char *update_path = collide_path;
- if (stage == 2) {
- dst_entry = ci->dst_entry1;
- cur_branch = ci->branch1;
- other_branch = ci->branch2;
- } else {
- dst_entry = ci->dst_entry2;
- cur_branch = ci->branch2;
- other_branch = ci->branch1;
+ /*
+ * It's easiest to get the correct things into stage 2 and 3, and
+ * to make sure that the content merge puts HEAD before the other
+ * branch if we just ensure that branch1 == o->branch1. So, simply
+ * flip arguments around if we don't have that.
+ */
+ if (branch1 != o->branch1) {
+ return handle_file_collision(o, collide_path,
+ prev_path2, prev_path1,
+ branch2, branch1,
+ b_oid, b_mode,
+ a_oid, a_mode);
}
- add = filespec_from_entry(&other, dst_entry, stage ^ 1);
- if (add) {
- int ren_src_was_dirty = was_dirty(o, rename->path);
- char *add_name = unique_path(o, rename->path, other_branch);
- if (update_file(o, 0, &add->oid, add->mode, add_name))
- return -1;
+ /*
+ * In the recursive case, we just opt to undo renames
+ */
+ if (o->call_depth && (prev_path1 || prev_path2)) {
+ /* Put first file (a_oid, a_mode) in its original spot */
+ if (prev_path1) {
+ if (update_file(o, 1, a_oid, a_mode, prev_path1))
+ return -1;
+ } else {
+ if (update_file(o, 1, a_oid, a_mode, collide_path))
+ return -1;
+ }
- if (ren_src_was_dirty) {
- output(o, 1, _("Refusing to lose dirty file at %s"),
- rename->path);
+ /* Put second file (b_oid, b_mode) in its original spot */
+ if (prev_path2) {
+ if (update_file(o, 1, b_oid, b_mode, prev_path2))
+ return -1;
+ } else {
+ if (update_file(o, 1, b_oid, b_mode, collide_path))
+ return -1;
}
+
+ /* Don't leave something at collision path if unrenaming both */
+ if (prev_path1 && prev_path2)
+ remove_file(o, 1, collide_path, 0);
+
+ return 0;
+ }
+
+ /* Remove rename sources if rename/add or rename/rename(2to1) */
+ if (prev_path1)
+ remove_file(o, 1, prev_path1,
+ o->call_depth || would_lose_untracked(o, prev_path1));
+ if (prev_path2)
+ remove_file(o, 1, prev_path2,
+ o->call_depth || would_lose_untracked(o, prev_path2));
+
+ /*
+ * Remove the collision path, if it wouldn't cause dirty contents
+ * or an untracked file to get lost. We'll either overwrite with
+ * merged contents, or just write out to differently named files.
+ */
+ if (was_dirty(o, collide_path)) {
+ output(o, 1, _("Refusing to lose dirty file at %s"),
+ collide_path);
+ update_path = alt_path = unique_path(o, collide_path, "merged");
+ } else if (would_lose_untracked(o, collide_path)) {
/*
- * Because the double negatives somehow keep confusing me...
- * 1) update_wd iff !ren_src_was_dirty.
- * 2) no_wd iff !update_wd
- * 3) so, no_wd == !!ren_src_was_dirty == ren_src_was_dirty
+ * Only way we get here is if both renames were from
+ * a directory rename AND user had an untracked file
+ * at the location where both files end up after the
+ * two directory renames. See testcase 10d of t6043.
*/
- remove_file(o, 0, rename->path, ren_src_was_dirty);
- dst_name = unique_path(o, rename->path, cur_branch);
+ output(o, 1, _("Refusing to lose untracked file at "
+ "%s, even though it's in the way."),
+ collide_path);
+ update_path = alt_path = unique_path(o, collide_path, "merged");
} else {
- if (dir_in_way(rename->path, !o->call_depth, 0)) {
- dst_name = unique_path(o, rename->path, cur_branch);
- output(o, 1, _("%s is a directory in %s adding as %s instead"),
- rename->path, other_branch, dst_name);
- } else if (!o->call_depth &&
- would_lose_untracked(rename->path)) {
- dst_name = unique_path(o, rename->path, cur_branch);
- output(o, 1, _("Refusing to lose untracked file at %s; "
- "adding as %s instead"),
- rename->path, dst_name);
- }
+ /*
+ * FIXME: It's possible that the two files are identical
+ * and that the current working copy happens to match, in
+ * which case we are unnecessarily touching the working
+ * tree file. It's not a likely enough scenario that I
+ * want to code up the checks for it and a better fix is
+ * available if we restructure how unpack_trees() and
+ * merge-recursive interoperate anyway, so punting for
+ * now...
+ */
+ remove_file(o, 0, collide_path, 0);
}
- if ((ret = update_file(o, 0, &rename->oid, rename->mode, dst_name)))
- ; /* fall through, do allow dst_name to be released */
- else if (stage == 2)
- ret = update_stages(o, rename->path, NULL, rename, add);
- else
- ret = update_stages(o, rename->path, NULL, add, rename);
- if (dst_name != rename->path)
- free(dst_name);
+ /* Store things in diff_filespecs for functions that need it */
+ memset(&a, 0, sizeof(struct diff_filespec));
+ memset(&b, 0, sizeof(struct diff_filespec));
+ null.path = a.path = b.path = (char *)collide_path;
+ oidcpy(&null.oid, &null_oid);
+ null.mode = 0;
+ oidcpy(&a.oid, a_oid);
+ a.mode = a_mode;
+ a.oid_valid = 1;
+ oidcpy(&b.oid, b_oid);
+ b.mode = b_mode;
+ b.oid_valid = 1;
- return ret;
+ if (merge_mode_and_contents(o, &null, &a, &b, collide_path,
+ branch1, branch2, o->call_depth * 2, &mfi))
+ return -1;
+ mfi.clean &= !alt_path;
+ if (update_file(o, mfi.clean, &mfi.oid, mfi.mode, update_path))
+ return -1;
+ if (!mfi.clean && !o->call_depth &&
+ update_stages(o, collide_path, NULL, &a, &b))
+ return -1;
+ free(alt_path);
+ /*
+ * FIXME: If both a & b both started with conflicts (only possible
+ * if they came from a rename/rename(2to1)), but had IDENTICAL
+ * contents including those conflicts, then in the next line we claim
+ * it was clean. If someone cares about this case, we should have the
+ * caller notify us if we started with conflicts.
+ */
+ return mfi.clean;
+}
+
+static int handle_rename_add(struct merge_options *o,
+ struct rename_conflict_info *ci)
+{
+ /* a was renamed to c, and a separate c was added. */
+ struct diff_filespec *a = ci->pair1->one;
+ struct diff_filespec *c = ci->pair1->two;
+ char *path = c->path;
+ char *prev_path_desc;
+ struct merge_file_info mfi;
+
+ int other_stage = (ci->branch1 == o->branch1 ? 3 : 2);
+
+ output(o, 1, _("CONFLICT (rename/add): "
+ "Rename %s->%s in %s. Added %s in %s"),
+ a->path, c->path, ci->branch1,
+ c->path, ci->branch2);
+
+ prev_path_desc = xstrfmt("version of %s from %s", path, a->path);
+ if (merge_mode_and_contents(o, a, c, &ci->ren1_other, prev_path_desc,
+ o->branch1, o->branch2,
+ 1 + o->call_depth * 2, &mfi))
+ return -1;
+ free(prev_path_desc);
+
+ return handle_file_collision(o,
+ c->path, a->path, NULL,
+ ci->branch1, ci->branch2,
+ &mfi.oid, mfi.mode,
+ &ci->dst_entry1->stages[other_stage].oid,
+ ci->dst_entry1->stages[other_stage].mode);
+}
+
+static char *find_path_for_conflict(struct merge_options *o,
+ const char *path,
+ const char *branch1,
+ const char *branch2)
+{
+ char *new_path = NULL;
+ if (dir_in_way(o->repo->index, path, !o->call_depth, 0)) {
+ new_path = unique_path(o, path, branch1);
+ output(o, 1, _("%s is a directory in %s adding "
+ "as %s instead"),
+ path, branch2, new_path);
+ } else if (would_lose_untracked(o, path)) {
+ new_path = unique_path(o, path, branch1);
+ output(o, 1, _("Refusing to lose untracked file"
+ " at %s; adding as %s instead"),
+ path, new_path);
+ }
+
+ return new_path;
}
static int handle_rename_rename_1to2(struct merge_options *o,
struct rename_conflict_info *ci)
{
/* One file was renamed in both branches, but to different names. */
+ struct merge_file_info mfi;
+ struct diff_filespec other;
+ struct diff_filespec *add;
struct diff_filespec *one = ci->pair1->one;
struct diff_filespec *a = ci->pair1->two;
struct diff_filespec *b = ci->pair2->two;
+ char *path_desc;
output(o, 1, _("CONFLICT (rename/rename): "
"Rename \"%s\"->\"%s\" in branch \"%s\" "
one->path, a->path, ci->branch1,
one->path, b->path, ci->branch2,
o->call_depth ? _(" (left unresolved)") : "");
- if (o->call_depth) {
- struct merge_file_info mfi;
- struct diff_filespec other;
- struct diff_filespec *add;
- if (merge_mode_and_contents(o, one, a, b, one->path,
- ci->branch1, ci->branch2, &mfi))
- return -1;
+ path_desc = xstrfmt("%s and %s, both renamed from %s",
+ a->path, b->path, one->path);
+ if (merge_mode_and_contents(o, one, a, b, path_desc,
+ ci->branch1, ci->branch2,
+ o->call_depth * 2, &mfi))
+ return -1;
+ free(path_desc);
+
+ if (o->call_depth) {
/*
* FIXME: For rename/add-source conflicts (if we could detect
* such), this is wrong. We should instead find a unique
return -1;
}
else
- remove_file_from_cache(a->path);
+ remove_file_from_index(o->repo->index, a->path);
add = filespec_from_entry(&other, ci->dst_entry2, 3 ^ 1);
if (add) {
if (update_file(o, 0, &add->oid, add->mode, b->path))
return -1;
}
else
- remove_file_from_cache(b->path);
- } else if (handle_file(o, a, 2, ci) || handle_file(o, b, 3, ci))
- return -1;
+ remove_file_from_index(o->repo->index, b->path);
+ } else {
+ /*
+ * For each destination path, we need to see if there is a
+ * rename/add collision. If not, we can write the file out
+ * to the specified location.
+ */
+ add = filespec_from_entry(&other, ci->dst_entry1, 2 ^ 1);
+ if (add) {
+ if (handle_file_collision(o, a->path,
+ NULL, NULL,
+ ci->branch1, ci->branch2,
+ &mfi.oid, mfi.mode,
+ &add->oid, add->mode) < 0)
+ return -1;
+ } else {
+ char *new_path = find_path_for_conflict(o, a->path,
+ ci->branch1,
+ ci->branch2);
+ if (update_file(o, 0, &mfi.oid, mfi.mode, new_path ? new_path : a->path))
+ return -1;
+ free(new_path);
+ if (update_stages(o, a->path, NULL, a, NULL))
+ return -1;
+ }
+
+ add = filespec_from_entry(&other, ci->dst_entry2, 3 ^ 1);
+ if (add) {
+ if (handle_file_collision(o, b->path,
+ NULL, NULL,
+ ci->branch1, ci->branch2,
+ &add->oid, add->mode,
+ &mfi.oid, mfi.mode) < 0)
+ return -1;
+ } else {
+ char *new_path = find_path_for_conflict(o, b->path,
+ ci->branch2,
+ ci->branch1);
+ if (update_file(o, 0, &mfi.oid, mfi.mode, new_path ? new_path : b->path))
+ return -1;
+ free(new_path);
+ if (update_stages(o, b->path, NULL, NULL, b))
+ return -1;
+ }
+ }
return 0;
}
char *path_side_2_desc;
struct merge_file_info mfi_c1;
struct merge_file_info mfi_c2;
- int ret;
output(o, 1, _("CONFLICT (rename/rename): "
"Rename %s->%s in %s. "
a->path, c1->path, ci->branch1,
b->path, c2->path, ci->branch2);
- remove_file(o, 1, a->path, o->call_depth || would_lose_untracked(a->path));
- remove_file(o, 1, b->path, o->call_depth || would_lose_untracked(b->path));
-
path_side_1_desc = xstrfmt("version of %s from %s", path, a->path);
path_side_2_desc = xstrfmt("version of %s from %s", path, b->path);
if (merge_mode_and_contents(o, a, c1, &ci->ren1_other, path_side_1_desc,
- o->branch1, o->branch2, &mfi_c1) ||
+ o->branch1, o->branch2,
+ 1 + o->call_depth * 2, &mfi_c1) ||
merge_mode_and_contents(o, b, &ci->ren2_other, c2, path_side_2_desc,
- o->branch1, o->branch2, &mfi_c2))
+ o->branch1, o->branch2,
+ 1 + o->call_depth * 2, &mfi_c2))
return -1;
free(path_side_1_desc);
free(path_side_2_desc);
- if (o->call_depth) {
- /*
- * If mfi_c1.clean && mfi_c2.clean, then it might make
- * sense to do a two-way merge of those results. But, I
- * think in all cases, it makes sense to have the virtual
- * merge base just undo the renames; they can be detected
- * again later for the non-recursive merge.
- */
- remove_file(o, 0, path, 0);
- ret = update_file(o, 0, &mfi_c1.oid, mfi_c1.mode, a->path);
- if (!ret)
- ret = update_file(o, 0, &mfi_c2.oid, mfi_c2.mode,
- b->path);
- } else {
- char *new_path1 = unique_path(o, path, ci->branch1);
- char *new_path2 = unique_path(o, path, ci->branch2);
- output(o, 1, _("Renaming %s to %s and %s to %s instead"),
- a->path, new_path1, b->path, new_path2);
- if (was_dirty(o, path))
- output(o, 1, _("Refusing to lose dirty file at %s"),
- path);
- else if (would_lose_untracked(path))
- /*
- * Only way we get here is if both renames were from
- * a directory rename AND user had an untracked file
- * at the location where both files end up after the
- * two directory renames. See testcase 10d of t6043.
- */
- output(o, 1, _("Refusing to lose untracked file at "
- "%s, even though it's in the way."),
- path);
- else
- remove_file(o, 0, path, 0);
- ret = update_file(o, 0, &mfi_c1.oid, mfi_c1.mode, new_path1);
- if (!ret)
- ret = update_file(o, 0, &mfi_c2.oid, mfi_c2.mode,
- new_path2);
- /*
- * unpack_trees() actually populates the index for us for
- * "normal" rename/rename(2to1) situtations so that the
- * correct entries are at the higher stages, which would
- * make the call below to update_stages_for_stage_data
- * unnecessary. However, if either of the renames came
- * from a directory rename, then unpack_trees() will not
- * have gotten the right data loaded into the index, so we
- * need to do so now. (While it'd be tempting to move this
- * call to update_stages_for_stage_data() to
- * apply_directory_rename_modifications(), that would break
- * our intermediate calls to would_lose_untracked() since
- * those rely on the current in-memory index. See also the
- * big "NOTE" in update_stages()).
- */
- if (update_stages_for_stage_data(o, path, ci->dst_entry1))
- ret = -1;
-
- free(new_path2);
- free(new_path1);
- }
-
- return ret;
+ return handle_file_collision(o, path, a->path, b->path,
+ ci->branch1, ci->branch2,
+ &mfi_c1.oid, mfi_c1.mode,
+ &mfi_c2.oid, mfi_c2.mode);
}
/*
struct diff_queue_struct *ret;
struct diff_options opts;
- repo_diff_setup(the_repository, &opts);
+ repo_diff_setup(o->repo, &opts);
opts.flags.recursive = 1;
opts.flags.rename_empty = 0;
opts.detect_rename = merge_detect_rename(o);
0 /* update_wd */))
clean_merge = -1;
} else if (!oid_eq(&dst_other.oid, &null_oid)) {
- clean_merge = 0;
- try_merge = 1;
- output(o, 1, _("CONFLICT (rename/add): Rename %s->%s in %s. "
- "%s added in %s"),
- ren1_src, ren1_dst, branch1,
- ren1_dst, branch2);
- if (o->call_depth) {
- struct merge_file_info mfi;
- struct diff_filespec one, a, b;
-
- oidcpy(&one.oid, &null_oid);
- one.mode = 0;
- one.path = ren1->pair->two->path;
-
- oidcpy(&a.oid, &ren1->pair->two->oid);
- a.mode = ren1->pair->two->mode;
- a.path = one.path;
-
- oidcpy(&b.oid, &dst_other.oid);
- b.mode = dst_other.mode;
- b.path = one.path;
-
- if (merge_mode_and_contents(o, &one, &a, &b, ren1_dst,
- branch1, branch2,
- &mfi)) {
- clean_merge = -1;
- goto cleanup_and_return;
- }
- output(o, 1, _("Adding merged %s"), ren1_dst);
- if (update_file(o, 0, &mfi.oid,
- mfi.mode, ren1_dst))
- clean_merge = -1;
- try_merge = 0;
- } else {
- char *new_path = unique_path(o, ren1_dst, branch2);
- output(o, 1, _("Adding as %s instead"), new_path);
- if (update_file(o, 0, &dst_other.oid,
- dst_other.mode, new_path))
- clean_merge = -1;
- free(new_path);
- }
+ /*
+ * Probably not a clean merge, but it's
+ * premature to set clean_merge to 0 here,
+ * because if the rename merges cleanly and
+ * the merge exactly matches the newly added
+ * file, then the merge will be clean.
+ */
+ setup_rename_conflict_info(RENAME_ADD,
+ ren1->pair,
+ NULL,
+ branch1,
+ branch2,
+ ren1->dst_entry,
+ NULL,
+ o,
+ ren1->src_entry,
+ NULL);
} else
try_merge = 1;
* performed. Comparison can be skipped if both files are
* unchanged since their sha1s have already been compared.
*/
- if (renormalize_buffer(&the_index, path, o.buf, o.len, &o) |
- renormalize_buffer(&the_index, path, a.buf, a.len, &a))
+ if (renormalize_buffer(opt->repo->index, path, o.buf, o.len, &o) |
+ renormalize_buffer(opt->repo->index, path, a.buf, a.len, &a))
ret = (o.len == a.len && !memcmp(o.buf, a.buf, o.len));
error_return:
a.path = (char *)path1;
b.path = (char *)path2;
- if (dir_in_way(path, !o->call_depth,
+ if (dir_in_way(o->repo->index, path, !o->call_depth,
S_ISGITLINK(pair1->two->mode)))
df_conflict_remains = 1;
}
if (merge_mode_and_contents(o, &one, &a, &b, path,
- o->branch1, o->branch2, &mfi))
+ o->branch1, o->branch2,
+ o->call_depth * 2, &mfi))
return -1;
/*
pos = index_name_pos(&o->orig_index, path, strlen(path));
ce = o->orig_index.cache[pos];
if (ce_skip_worktree(ce)) {
- pos = index_name_pos(&the_index, path, strlen(path));
- ce = the_index.cache[pos];
+ pos = index_name_pos(o->repo->index, path, strlen(path));
+ ce = o->repo->index->cache[pos];
ce->ce_flags |= CE_SKIP_WORKTREE;
}
return mfi.clean;
if (df_conflict_remains || is_dirty) {
char *new_path;
if (o->call_depth) {
- remove_file_from_cache(path);
+ remove_file_from_index(o->repo->index, path);
} else {
if (!mfi.clean) {
if (update_stages(o, path, &one, &a, &b))
conflict_info->branch2))
clean_merge = -1;
break;
+ case RENAME_ADD:
+ /*
+ * Probably unclean merge, but if the renamed file
+ * merges cleanly and the result can then be
+ * two-way merged cleanly with the added file, I
+ * guess it's a clean merge?
+ */
+ clean_merge = handle_rename_add(o, conflict_info);
+ break;
case RENAME_DELETE:
clean_merge = 0;
if (handle_rename_delete(o,
clean_merge = -1;
break;
case RENAME_TWO_FILES_TO_ONE:
- clean_merge = 0;
- if (handle_rename_rename_2to1(o, conflict_info))
- clean_merge = -1;
+ /*
+ * Probably unclean merge, but if the two renamed
+ * files merge cleanly and the two resulting files
+ * can then be two-way merged cleanly, I guess it's
+ * a clean merge?
+ */
+ clean_merge = handle_rename_rename_2to1(o,
+ conflict_info);
break;
default:
entry->processed = 0;
oid = b_oid;
conf = _("directory/file");
}
- if (dir_in_way(path,
+ if (dir_in_way(o->repo->index, path,
!o->call_depth && !S_ISGITLINK(a_mode),
0)) {
char *new_path = unique_path(o, path, add_branch);
if (update_file(o, 0, oid, mode, new_path))
clean_merge = -1;
else if (o->call_depth)
- remove_file_from_cache(path);
+ remove_file_from_index(o->repo->index, path);
free(new_path);
} else {
output(o, 2, _("Adding %s"), path);
clean_merge = -1;
}
} else if (a_oid && b_oid) {
- /* Case C: Added in both (check for same permissions) and */
- /* case D: Modified in both, but differently. */
- int is_dirty = 0; /* unpack_trees would have bailed if dirty */
- clean_merge = handle_content_merge(o, path, is_dirty,
- o_oid, o_mode,
- a_oid, a_mode,
- b_oid, b_mode,
- NULL);
+ if (!o_oid) {
+ /* Case C: Added in both (check for same permissions) */
+ output(o, 1,
+ _("CONFLICT (add/add): Merge conflict in %s"),
+ path);
+ clean_merge = handle_file_collision(o,
+ path, NULL, NULL,
+ o->branch1,
+ o->branch2,
+ a_oid, a_mode,
+ b_oid, b_mode);
+ } else {
+ /* case D: Modified in both, but differently. */
+ int is_dirty = 0; /* unpack_trees would have bailed if dirty */
+ clean_merge = handle_content_merge(o, path,
+ is_dirty,
+ o_oid, o_mode,
+ a_oid, a_mode,
+ b_oid, b_mode,
+ NULL);
+ }
} else if (!o_oid && !a_oid && !b_oid) {
/*
* this entry was deleted altogether. a_mode == 0 means
struct tree *common,
struct tree **result)
{
+ struct index_state *istate = o->repo->index;
int code, clean;
struct strbuf sb = STRBUF_INIT;
- if (!o->call_depth && index_has_changes(&the_index, head, &sb)) {
+ if (!o->call_depth && repo_index_has_changes(o->repo, head, &sb)) {
err(o, _("Your local changes to the following files would be overwritten by merge:\n %s"),
sb.buf);
return -1;
}
if (o->subtree_shift) {
- merge = shift_tree_object(head, merge, o->subtree_shift);
- common = shift_tree_object(head, common, o->subtree_shift);
+ merge = shift_tree_object(o->repo, head, merge, o->subtree_shift);
+ common = shift_tree_object(o->repo, head, common, o->subtree_shift);
}
if (oid_eq(&common->object.oid, &merge->object.oid)) {
return -1;
}
- if (unmerged_cache()) {
+ if (unmerged_index(istate)) {
struct string_list *entries;
struct rename_info re_info;
int i;
get_files_dirs(o, head);
get_files_dirs(o, merge);
- entries = get_unmerged();
+ entries = get_unmerged(o->repo->index);
clean = detect_and_process_renames(o, common, head, merge,
entries, &re_info);
record_df_conflict_files(o, entries);
/* if there is no common ancestor, use an empty tree */
struct tree *tree;
- tree = lookup_tree(the_repository, the_repository->hash_algo->empty_tree);
- merged_common_ancestors = make_virtual_commit(tree, "ancestor");
+ tree = lookup_tree(o->repo, o->repo->hash_algo->empty_tree);
+ merged_common_ancestors = make_virtual_commit(o->repo, tree, "ancestor");
}
for (iter = ca; iter; iter = iter->next) {
* overwritten it: the committed "conflicts" were
* already resolved.
*/
- discard_cache();
+ discard_index(o->repo->index);
saved_b1 = o->branch1;
saved_b2 = o->branch2;
o->branch1 = "Temporary merge branch 1";
return err(o, _("merge returned no commit"));
}
- discard_cache();
+ discard_index(o->repo->index);
if (!o->call_depth)
- read_cache();
+ repo_read_index(o->repo);
o->ancestor = "merged common ancestors";
clean = merge_trees(o, get_commit_tree(h1), get_commit_tree(h2),
}
if (o->call_depth) {
- *result = make_virtual_commit(mrtree, "merged tree");
+ *result = make_virtual_commit(o->repo, mrtree, "merged tree");
commit_list_insert(h1, &(*result)->parents);
commit_list_insert(h2, &(*result)->parents->next);
}
return clean;
}
-static struct commit *get_ref(const struct object_id *oid, const char *name)
+static struct commit *get_ref(struct repository *repo, const struct object_id *oid,
+ const char *name)
{
struct object *object;
- object = deref_tag(the_repository, parse_object(the_repository, oid),
- name,
- strlen(name));
+ object = deref_tag(repo, parse_object(repo, oid),
+ name, strlen(name));
if (!object)
return NULL;
if (object->type == OBJ_TREE)
- return make_virtual_commit((struct tree*)object, name);
+ return make_virtual_commit(repo, (struct tree*)object, name);
if (object->type != OBJ_COMMIT)
return NULL;
if (parse_commit((struct commit *)object))
{
int clean;
struct lock_file lock = LOCK_INIT;
- struct commit *head_commit = get_ref(head, o->branch1);
- struct commit *next_commit = get_ref(merge, o->branch2);
+ struct commit *head_commit = get_ref(o->repo, head, o->branch1);
+ struct commit *next_commit = get_ref(o->repo, merge, o->branch2);
struct commit_list *ca = NULL;
if (base_list) {
int i;
for (i = 0; i < num_base_list; ++i) {
struct commit *base;
- if (!(base = get_ref(base_list[i], oid_to_hex(base_list[i]))))
+ if (!(base = get_ref(o->repo, base_list[i], oid_to_hex(base_list[i]))))
return err(o, _("Could not parse object '%s'"),
oid_to_hex(base_list[i]));
commit_list_insert(base, &ca);
}
}
- hold_locked_index(&lock, LOCK_DIE_ON_ERROR);
+ repo_hold_locked_index(o->repo, &lock, LOCK_DIE_ON_ERROR);
clean = merge_recursive(o, head_commit, next_commit, ca,
result);
if (clean < 0) {
return clean;
}
- if (write_locked_index(&the_index, &lock,
+ if (write_locked_index(o->repo->index, &lock,
COMMIT_LOCK | SKIP_IF_UNCHANGED))
return err(o, _("Unable to write index."));
git_config(git_xmerge_config, NULL);
}
-void init_merge_options(struct merge_options *o)
+void init_merge_options(struct merge_options *o,
+ struct repository *repo)
{
const char *merge_verbosity;
memset(o, 0, sizeof(struct merge_options));
+ o->repo = repo;
o->verbosity = 2;
o->buffer_output = 1;
o->diff_rename_limit = -1;
struct commit;
+struct repository;
+
struct merge_options {
const char *ancestor;
const char *branch1;
struct string_list df_conflict_file_set;
struct unpack_trees_options unpack_opts;
struct index_state orig_index;
+ struct repository *repo;
};
/*
const struct object_id **ca,
struct commit **result);
-void init_merge_options(struct merge_options *o);
+void init_merge_options(struct merge_options *o,
+ struct repository *repo);
struct tree *write_tree_from_memory(struct merge_options *o);
int parse_merge_opt(struct merge_options *out, const char *s);
argv_array_clear(&args);
discard_index(r->index);
- if (read_index(r->index) < 0)
+ if (repo_read_index(r) < 0)
die(_("failed to read the cache"));
resolve_undo_clear_index(r->index);
refresh_index(r->index, REFRESH_QUIET, NULL, NULL, NULL);
- if (hold_locked_index(&lock_file, LOCK_REPORT_ON_ERROR) < 0)
+ if (repo_hold_locked_index(r, &lock_file, LOCK_REPORT_ON_ERROR) < 0)
return -1;
memset(&trees, 0, sizeof(trees));
void clear_midx_file(struct repository *r)
{
- char *midx = get_midx_filename(r->objects->objectdir);
+ char *midx = get_midx_filename(r->objects->odb->path);
if (r->objects && r->objects->multi_pack_index) {
close_midx(r->objects->multi_pack_index);
*
* Copyright (C) 2008 Linus Torvalds
*/
-#define NO_THE_INDEX_COMPATIBILITY_MACROS
#include "cache.h"
#include "thread-utils.h"
#include "commit.h"
#include "refs.h"
-static int notes_cache_match_validity(const char *ref, const char *validity)
+static int notes_cache_match_validity(struct repository *r,
+ const char *ref,
+ const char *validity)
{
struct object_id oid;
struct commit *commit;
if (read_ref(ref, &oid) < 0)
return 0;
- commit = lookup_commit_reference_gently(the_repository, &oid, 1);
+ commit = lookup_commit_reference_gently(r, &oid, 1);
if (!commit)
return 0;
return ret;
}
-void notes_cache_init(struct notes_cache *c, const char *name,
- const char *validity)
+void notes_cache_init(struct repository *r, struct notes_cache *c,
+ const char *name, const char *validity)
{
struct strbuf ref = STRBUF_INIT;
int flags = NOTES_INIT_WRITABLE;
c->validity = xstrdup(validity);
strbuf_addf(&ref, "refs/notes/%s", name);
- if (!notes_cache_match_validity(ref.buf, validity))
+ if (!notes_cache_match_validity(r, ref.buf, validity))
flags |= NOTES_INIT_EMPTY;
init_notes(&c->tree, ref.buf, combine_notes_overwrite, flags);
strbuf_release(&ref);
#include "notes.h"
+struct repository;
+
struct notes_cache {
struct notes_tree tree;
char *validity;
};
-void notes_cache_init(struct notes_cache *c, const char *name,
- const char *validity);
+void notes_cache_init(struct repository *r, struct notes_cache *c,
+ const char *name, const char *validity);
int notes_cache_write(struct notes_cache *c);
char *notes_cache_get(struct notes_cache *c, struct object_id *oid, size_t
struct object_id obj, base, local, remote;
};
-void init_notes_merge_options(struct notes_merge_options *o)
+void init_notes_merge_options(struct repository *r,
+ struct notes_merge_options *o)
{
memset(o, 0, sizeof(struct notes_merge_options));
strbuf_init(&(o->commit_msg), 0);
o->verbosity = NOTES_MERGE_VERBOSITY_DEFAULT;
+ o->repo = r;
}
static int path_to_oid(const char *path, struct object_id *oid)
trace_printf("\tdiff_tree_remote(base = %.7s, remote = %.7s)\n",
oid_to_hex(base), oid_to_hex(remote));
- repo_diff_setup(the_repository, &opt);
+ repo_diff_setup(o->repo, &opt);
opt.flags.recursive = 1;
opt.output_format = DIFF_FORMAT_NO_OUTPUT;
diff_setup_done(&opt);
trace_printf("\tdiff_tree_local(len = %i, base = %.7s, local = %.7s)\n",
len, oid_to_hex(base), oid_to_hex(local));
- repo_diff_setup(the_repository, &opt);
+ repo_diff_setup(o->repo, &opt);
opt.flags.recursive = 1;
opt.output_format = DIFF_FORMAT_NO_OUTPUT;
diff_setup_done(&opt);
status = ll_merge(&result_buf, oid_to_hex(&p->obj), &base, NULL,
&local, o->local_ref, &remote, o->remote_ref,
- &the_index, NULL);
+ o->repo->index, NULL);
free(base.ptr);
free(local.ptr);
else if (!check_refname_format(o->local_ref, 0) &&
is_null_oid(&local_oid))
local = NULL; /* local_oid == null_oid indicates unborn ref */
- else if (!(local = lookup_commit_reference(the_repository, &local_oid)))
+ else if (!(local = lookup_commit_reference(o->repo, &local_oid)))
die("Could not parse local commit %s (%s)",
oid_to_hex(&local_oid), o->local_ref);
trace_printf("\tlocal commit: %.7s\n", oid_to_hex(&local_oid));
die("Failed to resolve remote notes ref '%s'",
o->remote_ref);
}
- } else if (!(remote = lookup_commit_reference(the_repository, &remote_oid))) {
+ } else if (!(remote = lookup_commit_reference(o->repo, &remote_oid))) {
die("Could not parse remote commit %s (%s)",
oid_to_hex(&remote_oid), o->remote_ref);
}
struct commit_list *parents = NULL;
commit_list_insert(remote, &parents); /* LIFO order */
commit_list_insert(local, &parents);
- create_notes_commit(local_tree, parents, o->commit_msg.buf,
+ create_notes_commit(o->repo, local_tree, parents, o->commit_msg.buf,
o->commit_msg.len, result_oid);
}
/* write file as blob, and add to partial_tree */
if (stat(path.buf, &st))
die_errno("Failed to stat '%s'", path.buf);
- if (index_path(&the_index, &blob_oid, path.buf, &st, HASH_WRITE_OBJECT))
+ if (index_path(o->repo->index, &blob_oid, path.buf, &st, HASH_WRITE_OBJECT))
die("Failed to write blob object from '%s'", path.buf);
if (add_note(partial_tree, &obj_oid, &blob_oid, NULL))
die("Failed to add resolved note '%s' to notes tree",
strbuf_setlen(&path, baselen);
}
- create_notes_commit(partial_tree, partial_commit->parents, msg,
+ create_notes_commit(o->repo, partial_tree, partial_commit->parents, msg,
strlen(msg), result_oid);
unuse_commit_buffer(partial_commit, buffer);
if (o->verbosity >= 4)
struct commit;
struct object_id;
+struct repository;
#define NOTES_MERGE_WORKTREE "NOTES_MERGE_WORKTREE"
};
struct notes_merge_options {
+ struct repository *repo;
const char *local_ref;
const char *remote_ref;
struct strbuf commit_msg;
unsigned has_worktree:1;
};
-void init_notes_merge_options(struct notes_merge_options *o);
+void init_notes_merge_options(struct repository *r,
+ struct notes_merge_options *o);
/*
* Merge notes from o->remote_ref into o->local_ref
#include "notes-utils.h"
#include "repository.h"
-void create_notes_commit(struct notes_tree *t, struct commit_list *parents,
+void create_notes_commit(struct repository *r,
+ struct notes_tree *t,
+ struct commit_list *parents,
const char *msg, size_t msg_len,
struct object_id *result_oid)
{
/* Deduce parent commit from t->ref */
struct object_id parent_oid;
if (!read_ref(t->ref, &parent_oid)) {
- struct commit *parent = lookup_commit(the_repository,
- &parent_oid);
+ struct commit *parent = lookup_commit(r, &parent_oid);
if (parse_commit(parent))
die("Failed to find/parse commit %s", t->ref);
commit_list_insert(parent, &parents);
die("Failed to commit notes tree to database");
}
-void commit_notes(struct notes_tree *t, const char *msg)
+void commit_notes(struct repository *r, struct notes_tree *t, const char *msg)
{
struct strbuf buf = STRBUF_INIT;
struct object_id commit_oid;
strbuf_addstr(&buf, msg);
strbuf_complete_line(&buf);
- create_notes_commit(t, NULL, buf.buf, buf.len, &commit_oid);
+ create_notes_commit(r, t, NULL, buf.buf, buf.len, &commit_oid);
strbuf_insert(&buf, 0, "notes: ", 7); /* commit message starts at index 7 */
update_ref(buf.buf, t->update_ref, &commit_oid, NULL, 0,
UPDATE_REFS_DIE_ON_ERR);
return ret;
}
-void finish_copy_notes_for_rewrite(struct notes_rewrite_cfg *c, const char *msg)
+void finish_copy_notes_for_rewrite(struct repository *r,
+ struct notes_rewrite_cfg *c,
+ const char *msg)
{
int i;
for (i = 0; c->trees[i]; i++) {
- commit_notes(c->trees[i], msg);
+ commit_notes(r, c->trees[i], msg);
free_notes(c->trees[i]);
}
free(c->trees);
struct commit_list;
struct object_id;
+struct repository;
/*
* Create new notes commit from the given notes tree
*
* The resulting commit SHA1 is stored in result_sha1.
*/
-void create_notes_commit(struct notes_tree *t, struct commit_list *parents,
+void create_notes_commit(struct repository *r,
+ struct notes_tree *t,
+ struct commit_list *parents,
const char *msg, size_t msg_len,
struct object_id *result_oid);
-void commit_notes(struct notes_tree *t, const char *msg);
+void commit_notes(struct repository *r, struct notes_tree *t, const char *msg);
enum notes_merge_strategy {
NOTES_MERGE_RESOLVE_MANUAL = 0,
struct notes_rewrite_cfg *init_copy_notes_for_rewrite(const char *cmd);
int copy_note_for_rewrite(struct notes_rewrite_cfg *c,
const struct object_id *from_obj, const struct object_id *to_obj);
-void finish_copy_notes_for_rewrite(struct notes_rewrite_cfg *c, const char *msg);
+void finish_copy_notes_for_rewrite(struct repository *r,
+ struct notes_rewrite_cfg *c,
+ const char *msg);
#endif
l = xcalloc(1, sizeof(*l));
oidcpy(&l->key_oid, &object_oid);
- oidcpy(&l->val_oid, entry.oid);
+ oidcpy(&l->val_oid, &entry.oid);
if (note_tree_insert(t, node, n, l, type,
combine_notes_concatenate))
die("Failed to load %s %s into notes tree "
}
strbuf_addstr(&non_note_path, entry.path);
add_non_note(t, strbuf_detach(&non_note_path, NULL),
- entry.mode, entry.oid->hash);
+ entry.mode, entry.oid.hash);
}
}
free(buf);
#include "sha1-array.h"
#include "strbuf.h"
-struct alternate_object_database {
- struct alternate_object_database *next;
-
- /* see alt_scratch_buf() */
- struct strbuf scratch;
- size_t base_len;
+struct object_directory {
+ struct object_directory *next;
/*
- * Used to store the results of readdir(3) calls when searching
- * for unique abbreviated hashes. This cache is never
- * invalidated, thus it's racy and not necessarily accurate.
- * That's fine for its purpose; don't use it for tasks requiring
- * greater accuracy!
+ * Used to store the results of readdir(3) calls when we are OK
+ * sacrificing accuracy due to races for speed. That includes
+ * object existence with OBJECT_INFO_QUICK, as well as
+ * our search for unique abbreviated hashes. Don't use it for tasks
+ * requiring greater accuracy!
+ *
+ * Be sure to call odb_load_loose_cache() before using.
*/
char loose_objects_subdir_seen[256];
- struct oid_array loose_objects_cache;
+ struct oid_array loose_objects_cache[256];
/*
* Path to the alternative object store. If this is a relative path,
* it is relative to the current working directory.
*/
- char path[FLEX_ARRAY];
+ char *path;
};
+
void prepare_alt_odb(struct repository *r);
char *compute_alternate_path(const char *path, struct strbuf *err);
-typedef int alt_odb_fn(struct alternate_object_database *, void *);
+typedef int alt_odb_fn(struct object_directory *, void *);
int foreach_alt_odb(alt_odb_fn, void*);
-/*
- * Allocate a "struct alternate_object_database" but do _not_ actually
- * add it to the list of alternates.
- */
-struct alternate_object_database *alloc_alt_odb(const char *dir);
-
/*
* Add the directory to the on-disk alternates file; the new entry will also
* take effect in the current process.
void add_to_alternates_memory(const char *dir);
/*
- * Returns a scratch strbuf pre-filled with the alternate object directory,
- * including a trailing slash, which can be used to access paths in the
- * alternate. Always use this over direct access to alt->scratch, as it
- * cleans up any previous use of the scratch buffer.
+ * Populate and return the loose object cache array corresponding to the
+ * given object ID.
*/
-struct strbuf *alt_scratch_buf(struct alternate_object_database *alt);
+struct oid_array *odb_loose_cache(struct object_directory *odb,
+ const struct object_id *oid);
+
+/* Empty the loose object cache for the specified object directory. */
+void odb_clear_loose_cache(struct object_directory *odb);
struct packed_git {
struct packed_git *next;
struct raw_object_store {
/*
- * Path to the repository's object store.
- * Cannot be NULL after initialization.
+ * Set of all object directories; the main directory is first (and
+ * cannot be NULL after initialization). Subsequent directories are
+ * alternates.
*/
- char *objectdir;
+ struct object_directory *odb;
+ struct object_directory **odb_tail;
+ int loaded_alternates;
- /* Path to extra alternate object database if not NULL */
+ /*
+ * A list of alternate object directories loaded from the environment;
+ * this should not generally need to be accessed directly, but will
+ * populate the "odb" list when prepare_alt_odb() is run.
+ */
char *alternate_db;
- struct alternate_object_database *alt_odb_list;
- struct alternate_object_database **alt_odb_tail;
-
/*
* Objects that should be substituted by other objects
* (see git-replace(1)).
* Put in `buf` the name of the file in the local object database that
* would be used to store a loose object with the specified sha1.
*/
-void sha1_file_name(struct repository *r, struct strbuf *buf, const unsigned char *sha1);
+const char *loose_object_path(struct repository *r, struct strbuf *buf, const unsigned char *sha1);
void *map_sha1_file(struct repository *r, const unsigned char *sha1, unsigned long *size);
-extern void *read_object_file_extended(const struct object_id *oid,
+extern void *read_object_file_extended(struct repository *r,
+ const struct object_id *oid,
enum object_type *type,
unsigned long *size, int lookup_replace);
-static inline void *read_object_file(const struct object_id *oid, enum object_type *type, unsigned long *size)
+static inline void *repo_read_object_file(struct repository *r,
+ const struct object_id *oid,
+ enum object_type *type,
+ unsigned long *size)
{
- return read_object_file_extended(oid, type, size, 1);
+ return read_object_file_extended(r, oid, type, size, 1);
}
+#ifndef NO_THE_REPOSITORY_COMPATIBILITY_MACROS
+#define read_object_file(oid, type, size) repo_read_object_file(the_repository, oid, type, size)
+#endif
/* Read and unpack an object file into memory, write memory to an object file */
int oid_object_info(struct repository *r, const struct object_id *, unsigned long *);
* object_info. OBJECT_INFO_SKIP_CACHED is automatically set; pass
* nonzero flags to also set other flags.
*/
-extern int has_sha1_file_with_flags(const unsigned char *sha1, int flags);
-static inline int has_sha1_file(const unsigned char *sha1)
+int repo_has_sha1_file_with_flags(struct repository *r,
+ const unsigned char *sha1, int flags);
+static inline int repo_has_sha1_file(struct repository *r,
+ const unsigned char *sha1)
{
- return has_sha1_file_with_flags(sha1, 0);
+ return repo_has_sha1_file_with_flags(r, sha1, 0);
}
+#ifndef NO_THE_REPOSITORY_COMPATIBILITY_MACROS
+#define has_sha1_file_with_flags(sha1, flags) repo_has_sha1_file_with_flags(the_repository, sha1, flags)
+#define has_sha1_file(sha1) repo_has_sha1_file(the_repository, sha1)
+#endif
+
/* Same as the above, except for struct object_id. */
-extern int has_object_file(const struct object_id *oid);
-extern int has_object_file_with_flags(const struct object_id *oid, int flags);
+int repo_has_object_file(struct repository *r, const struct object_id *oid);
+int repo_has_object_file_with_flags(struct repository *r,
+ const struct object_id *oid, int flags);
+#ifndef NO_THE_REPOSITORY_COMPATIBILITY_MACROS
+#define has_object_file(oid) repo_has_object_file(the_repository, oid)
+#define has_object_file_with_flags(oid, flags) repo_has_object_file_with_flags(the_repository, oid, flags)
+#endif
/*
* Return true iff an alternate object database has a loose object
return obj;
else if (obj->type == OBJ_NONE) {
if (type == OBJ_COMMIT)
- ((struct commit *)obj)->index = alloc_commit_index(r);
- obj->type = type;
+ init_commit_node(r, (struct commit *) obj);
+ else
+ obj->type = type;
return obj;
}
else {
if (obj && obj->parsed)
return obj;
- if ((obj && obj->type == OBJ_BLOB && has_object_file(oid)) ||
- (!obj && has_object_file(oid) &&
+ if ((obj && obj->type == OBJ_BLOB && repo_has_object_file(r, oid)) ||
+ (!obj && repo_has_object_file(r, oid) &&
oid_object_info(r, oid, NULL) == OBJ_BLOB)) {
if (check_object_signature(repl, NULL, 0, NULL) < 0) {
error(_("sha1 mismatch %s"), oid_to_hex(oid));
return lookup_object(r, oid->hash);
}
- buffer = read_object_file(oid, &type, &size);
+ buffer = repo_read_object_file(r, oid, &type, &size);
if (buffer) {
if (check_object_signature(repl, buffer, size, type_name(type)) < 0) {
free(buffer);
return o;
}
-static void free_alt_odb(struct alternate_object_database *alt)
+static void free_object_directory(struct object_directory *odb)
{
- strbuf_release(&alt->scratch);
- oid_array_clear(&alt->loose_objects_cache);
- free(alt);
+ free(odb->path);
+ odb_clear_loose_cache(odb);
+ free(odb);
}
-static void free_alt_odbs(struct raw_object_store *o)
+static void free_object_directories(struct raw_object_store *o)
{
- while (o->alt_odb_list) {
- struct alternate_object_database *next;
+ while (o->odb) {
+ struct object_directory *next;
- next = o->alt_odb_list->next;
- free_alt_odb(o->alt_odb_list);
- o->alt_odb_list = next;
+ next = o->odb->next;
+ free_object_directory(o->odb);
+ o->odb = next;
}
}
void raw_object_store_clear(struct raw_object_store *o)
{
- FREE_AND_NULL(o->objectdir);
FREE_AND_NULL(o->alternate_db);
oidmap_free(o->replace_map, 1);
o->commit_graph = NULL;
o->commit_graph_attempted = 0;
- free_alt_odbs(o);
- o->alt_odb_tail = NULL;
+ free_object_directories(o);
+ o->odb_tail = NULL;
+ o->loaded_alternates = 0;
INIT_LIST_HEAD(&o->packed_git_mru);
close_all_packs(o);
if (obj->type == OBJ_TREE)
free_tree_buffer((struct tree*)obj);
else if (obj->type == OBJ_COMMIT)
- release_commit_memory((struct commit*)obj);
+ release_commit_memory(o, (struct commit*)obj);
else if (obj->type == OBJ_TAG)
release_tag_memory((struct tag*)obj);
}
break;
default:
- real_type = oid_object_info(the_repository,
+ real_type = oid_object_info(to_pack->repo,
&entry->idx.oid, NULL);
break;
}
if (writer.show_progress)
writer.progress = start_progress("Building bitmaps", writer.selected_nr);
- repo_init_revisions(the_repository, &revs, NULL);
+ repo_init_revisions(to_pack->repo, &revs, NULL);
revs.tag_objects = 1;
revs.tree_objects = 1;
revs.blob_objects = 1;
void bitmap_writer_reuse_bitmaps(struct packing_data *to_pack)
{
struct bitmap_index *bitmap_git;
- if (!(bitmap_git = prepare_bitmap_git()))
+ if (!(bitmap_git = prepare_bitmap_git(to_pack->repo)))
return;
writer.reused = kh_init_sha1();
return -1;
}
-static int open_pack_bitmap(struct bitmap_index *bitmap_git)
+static int open_pack_bitmap(struct repository *r,
+ struct bitmap_index *bitmap_git)
{
struct packed_git *p;
int ret = -1;
assert(!bitmap_git->map);
- for (p = get_all_packs(the_repository); p; p = p->next) {
+ for (p = get_all_packs(r); p; p = p->next) {
if (open_pack_bitmap_1(bitmap_git, p) == 0)
ret = 0;
}
return ret;
}
-struct bitmap_index *prepare_bitmap_git(void)
+struct bitmap_index *prepare_bitmap_git(struct repository *r)
{
struct bitmap_index *bitmap_git = xcalloc(1, sizeof(*bitmap_git));
- if (!open_pack_bitmap(bitmap_git) && !load_pack_bitmap(bitmap_git))
+ if (!open_pack_bitmap(r, bitmap_git) && !load_pack_bitmap(bitmap_git))
return bitmap_git;
free_bitmap_index(bitmap_git);
struct bitmap_index *bitmap_git = xcalloc(1, sizeof(*bitmap_git));
/* try to open a bitmapped pack, but don't parse it yet
* because we may not need to use it */
- if (open_pack_bitmap(bitmap_git) < 0)
+ if (open_pack_bitmap(revs->repo, bitmap_git) < 0)
goto cleanup;
for (i = 0; i < revs->pending.nr; ++i) {
struct bitmap_test_data tdata;
struct bitmap_index *bitmap_git;
- if (!(bitmap_git = prepare_bitmap_git()))
+ if (!(bitmap_git = prepare_bitmap_git(revs->repo)))
die("failed to load bitmap indexes");
if (revs->pending.nr != 1)
#include "pack-objects.h"
struct commit;
+struct repository;
struct rev_info;
struct bitmap_disk_header {
struct bitmap_index;
-struct bitmap_index *prepare_bitmap_git(void);
+struct bitmap_index *prepare_bitmap_git(struct repository *r);
void count_bitmap_commit_list(struct bitmap_index *, uint32_t *commits,
uint32_t *trees, uint32_t *blobs, uint32_t *tags);
void traverse_bitmap_commit_list(struct bitmap_index *,
return data_crc != ntohl(*index_crc);
}
-static int verify_packfile(struct packed_git *p,
+static int verify_packfile(struct repository *r,
+ struct packed_git *p,
struct pack_window **w_curs,
verify_fn fn,
struct progress *progress, uint32_t base_count)
data = NULL;
data_valid = 0;
} else {
- data = unpack_entry(the_repository, p, entries[i].offset, &type, &size);
+ data = unpack_entry(r, p, entries[i].offset, &type, &size);
data_valid = 1;
}
return err;
}
-int verify_pack(struct packed_git *p, verify_fn fn,
+int verify_pack(struct repository *r, struct packed_git *p, verify_fn fn,
struct progress *progress, uint32_t base_count)
{
int err = 0;
if (!p->index_data)
return -1;
- err |= verify_packfile(p, &w_curs, fn, progress, base_count);
+ err |= verify_packfile(r, p, &w_curs, fn, progress, base_count);
unuse_pack(&w_curs);
return err;
* (i.e. in_pack_idx also zero) should return NULL.
*/
mapping[cnt++] = NULL;
- for (p = get_all_packs(the_repository); p; p = p->next, cnt++) {
+ for (p = get_all_packs(pdata->repo); p; p = p->next, cnt++) {
if (cnt == nr) {
free(mapping);
return;
}
/* assume pdata is already zero'd by caller */
-void prepare_packing_data(struct packing_data *pdata)
+void prepare_packing_data(struct repository *r, struct packing_data *pdata)
{
+ pdata->repo = r;
+
if (git_env_bool("GIT_TEST_FULL_IN_PACK_ARRAY", 0)) {
/*
* do not initialize in_pack_by_idx[] to force the
1U << OE_SIZE_BITS);
pdata->oe_delta_size_limit = git_env_ulong("GIT_TEST_OE_DELTA_SIZE",
1UL << OE_DELTA_SIZE_BITS);
-#ifndef NO_PTHREADS
- pthread_mutex_init(&pdata->lock, NULL);
-#endif
+ init_recursive_mutex(&pdata->odb_lock);
}
struct object_entry *packlist_alloc(struct packing_data *pdata,
#include "thread-utils.h"
#include "pack.h"
+struct repository;
+
#define DEFAULT_DELTA_CACHE_SIZE (256 * 1024 * 1024)
#define OE_DFS_STATE_BITS 2
};
struct packing_data {
+ struct repository *repo;
struct object_entry *objects;
uint32_t nr_objects, nr_alloc;
struct packed_git **in_pack_by_idx;
struct packed_git **in_pack;
- pthread_mutex_t lock;
+ /*
+ * During packing with multiple threads, protect the in-core
+ * object database from concurrent accesses.
+ */
+ pthread_mutex_t odb_lock;
/*
* This list contains entries for bases which we know the other side
unsigned char *layer;
};
-void prepare_packing_data(struct packing_data *pdata);
+void prepare_packing_data(struct repository *r, struct packing_data *pdata);
+/* Protect access to object database */
static inline void packing_data_lock(struct packing_data *pdata)
{
- pthread_mutex_lock(&pdata->lock);
+ pthread_mutex_lock(&pdata->odb_lock);
}
static inline void packing_data_unlock(struct packing_data *pdata)
{
- pthread_mutex_unlock(&pdata->lock);
+ pthread_mutex_unlock(&pdata->odb_lock);
}
struct object_entry *packlist_alloc(struct packing_data *pdata,
#include "object.h"
#include "csum-file.h"
+struct repository;
+
/*
* Packed object header
*/
extern const char *write_idx_file(const char *index_name, struct pack_idx_entry **objects, int nr_objects, const struct pack_idx_option *, const unsigned char *sha1);
extern int check_pack_crc(struct packed_git *p, struct pack_window **w_curs, off_t offset, off_t len, unsigned int nr);
extern int verify_pack_index(struct packed_git *);
-extern int verify_pack(struct packed_git *, verify_fn fn, struct progress *, uint32_t);
+extern int verify_pack(struct repository *, struct packed_git *, verify_fn fn, struct progress *, uint32_t);
extern off_t write_pack_header(struct hashfile *f, uint32_t);
extern void fixup_pack_header_footer(int, unsigned char *, const char *, uint32_t, unsigned char *, off_t);
extern char *index_pack_lockfile(int fd);
static void prepare_packed_git(struct repository *r)
{
- struct alternate_object_database *alt;
+ struct object_directory *odb;
if (r->objects->packed_git_initialized)
return;
- prepare_multi_pack_index_one(r, r->objects->objectdir, 1);
- prepare_packed_git_one(r, r->objects->objectdir, 1);
+
prepare_alt_odb(r);
- for (alt = r->objects->alt_odb_list; alt; alt = alt->next) {
- prepare_multi_pack_index_one(r, alt->path, 0);
- prepare_packed_git_one(r, alt->path, 0);
+ for (odb = r->objects->odb; odb; odb = odb->next) {
+ int local = (odb == r->objects->odb);
+ prepare_multi_pack_index_one(r, odb->path, local);
+ prepare_packed_git_one(r, odb->path, local);
}
rearrange_packed_git(r);
void reprepare_packed_git(struct repository *r)
{
+ struct object_directory *odb;
+
+ for (odb = r->objects->odb; odb; odb = odb->next)
+ odb_clear_loose_cache(odb);
+
r->objects->approximate_object_count_valid = 0;
r->objects->packed_git_initialized = 0;
prepare_packed_git(r);
p->num_bad_objects++;
}
-const struct packed_git *has_packed_and_bad(const unsigned char *sha1)
+const struct packed_git *has_packed_and_bad(struct repository *r,
+ const unsigned char *sha1)
{
struct packed_git *p;
unsigned i;
- for (p = the_repository->objects->packed_git; p; p = p->next)
+ for (p = r->objects->packed_git; p; p = p->next)
for (i = 0; i < p->num_bad_objects; i++)
if (hasheq(sha1,
p->bad_object_sha1 + the_hash_algo->rawsz * i))
*/
return 0;
while (tree_entry_gently(&desc, &entry))
- oidset_insert(set, entry.oid);
+ oidset_insert(set, &entry.oid);
} else if (obj->type == OBJ_COMMIT) {
struct commit *commit = (struct commit *) obj;
struct commit_list *parents = commit->parents;
off_t offset, struct object_info *);
extern void mark_bad_packed_object(struct packed_git *p, const unsigned char *sha1);
-extern const struct packed_git *has_packed_and_bad(const unsigned char *sha1);
+extern const struct packed_git *has_packed_and_bad(struct repository *r, const unsigned char *sha1);
/*
* Iff a pack file in the given repository contains the object named by sha1,
} else {
v = strtol(arg, (char **)&arg, 10);
if (*arg)
- return opterror(opt, "expects a numerical value", 0);
+ return error(_("option `%s' expects a numerical value"),
+ opt->long_name);
if (v && v < MINIMUM_ABBREV)
v = MINIMUM_ABBREV;
else if (v > 40)
arg = unset ? "never" : (const char *)opt->defval;
value = git_config_colorbool(NULL, arg);
if (value < 0)
- return opterror(opt,
- "expects \"always\", \"auto\", or \"never\"", 0);
+ return error(_("option `%s' expects \"always\", \"auto\", or \"never\""),
+ opt->long_name);
*(int *)opt->value = value;
return 0;
}
p->argc--;
*arg = *++p->argv;
} else
- return opterror(opt, "requires a value", flags);
+ return error(_("%s requires a value"), optname(opt, flags));
return 0;
}
int flags)
{
const struct option *that;
- struct strbuf message = STRBUF_INIT;
struct strbuf that_name = STRBUF_INIT;
/*
strbuf_addf(&that_name, "--%s", that->long_name);
else
strbuf_addf(&that_name, "-%c", that->short_name);
- strbuf_addf(&message, ": incompatible with %s", that_name.buf);
+ error(_("%s is incompatible with %s"),
+ optname(opt, flags), that_name.buf);
strbuf_release(&that_name);
- opterror(opt, message.buf, flags);
- strbuf_release(&message);
return -1;
}
- return opterror(opt, ": incompatible with something else", flags);
+ return error(_("%s : incompatible with something else"),
+ optname(opt, flags));
}
static int get_value(struct parse_opt_ctx_t *p,
int err;
if (unset && p->opt)
- return opterror(opt, "takes no value", flags);
+ return error(_("%s takes no value"), optname(opt, flags));
if (unset && (opt->flags & PARSE_OPT_NONEG))
- return opterror(opt, "isn't available", flags);
+ return error(_("%s isn't available"), optname(opt, flags));
if (!(flags & OPT_SHORT) && p->opt && (opt->flags & PARSE_OPT_NOARG))
- return opterror(opt, "takes no value", flags);
+ return error(_("%s takes no value"), optname(opt, flags));
switch (opt->type) {
case OPTION_LOWLEVEL_CALLBACK:
return -1;
*(int *)opt->value = strtol(arg, (char **)&s, 10);
if (*s)
- return opterror(opt, "expects a numerical value", flags);
+ return error(_("%s expects a numerical value"),
+ optname(opt, flags));
return 0;
case OPTION_MAGNITUDE:
if (get_arg(p, opt, flags, &arg))
return -1;
if (!git_parse_ulong(arg, opt->value))
- return opterror(opt,
- "expects a non-negative integer value with an optional k/m/g suffix",
- flags);
+ return error(_("%s expects a non-negative integer value"
+ " with an optional k/m/g suffix"),
+ optname(opt, flags));
return 0;
default:
- die("should not happen, someone must be hit on the forehead");
+ BUG("opt->type %d should not happen", opt->type);
}
}
}
static int parse_long_opt(struct parse_opt_ctx_t *p, const char *arg,
- const struct option *options)
+ const struct option *options)
{
const struct option *all_opts = options;
const char *arg_end = strchrnul(arg, '=');
if (!rest)
continue;
if (*rest == '=')
- return opterror(options, "takes no value", flags);
+ return error(_("%s takes no value"),
+ optname(options, flags));
if (*rest)
continue;
p->out[p->cpidx++] = arg - 2;
}
if (ambiguous_option) {
- error("Ambiguous option: %s "
- "(could be --%s%s or --%s%s)",
+ error(_("ambiguous option: %s "
+ "(could be --%s%s or --%s%s)"),
arg,
(ambiguous_flags & OPT_UNSET) ? "no-" : "",
ambiguous_option->long_name,
return;
if (starts_with(arg, "no-")) {
- error ("did you mean `--%s` (with two dashes ?)", arg);
+ error(_("did you mean `--%s` (with two dashes ?)"), arg);
exit(129);
}
if (!options->long_name)
continue;
if (starts_with(options->long_name, arg)) {
- error ("did you mean `--%s` (with two dashes ?)", arg);
+ error(_("did you mean `--%s` (with two dashes ?)"), arg);
exit(129);
}
}
ctx->flags = flags;
if ((flags & PARSE_OPT_KEEP_UNKNOWN) &&
(flags & PARSE_OPT_STOP_AT_NON_OPTION))
- die("STOP_AT_NON_OPTION and KEEP_UNKNOWN don't go together");
+ BUG("STOP_AT_NON_OPTION and KEEP_UNKNOWN don't go together");
parse_options_check(options);
}
break;
default: /* PARSE_OPT_UNKNOWN */
if (ctx.argv[0][1] == '-') {
- error("unknown option `%s'", ctx.argv[0] + 2);
+ error(_("unknown option `%s'"), ctx.argv[0] + 2);
} else if (isascii(*ctx.opt)) {
- error("unknown switch `%c'", *ctx.opt);
+ error(_("unknown switch `%c'"), *ctx.opt);
} else {
- error("unknown non-ascii option in string: `%s'",
+ error(_("unknown non-ascii option in string: `%s'"),
ctx.argv[0]);
}
usage_with_options(usagestr, options);
usage_with_options(usagestr, options);
}
-#undef opterror
-int opterror(const struct option *opt, const char *reason, int flags)
+const char *optname(const struct option *opt, int flags)
{
+ static struct strbuf sb = STRBUF_INIT;
+
+ strbuf_reset(&sb);
if (flags & OPT_SHORT)
- return error("switch `%c' %s", opt->short_name, reason);
- if (flags & OPT_UNSET)
- return error("option `no-%s' %s", opt->long_name, reason);
- return error("option `%s' %s", opt->long_name, reason);
+ strbuf_addf(&sb, "switch `%c'", opt->short_name);
+ else if (flags & OPT_UNSET)
+ strbuf_addf(&sb, "option `no-%s'", opt->long_name);
+ else
+ strbuf_addf(&sb, "option `%s'", opt->long_name);
+
+ return sb.buf;
}
* Returns the number of arguments left in argv[].
*/
extern int parse_options(int argc, const char **argv, const char *prefix,
- const struct option *options,
- const char * const usagestr[], int flags);
+ const struct option *options,
+ const char * const usagestr[], int flags);
extern NORETURN void usage_with_options(const char * const *usagestr,
- const struct option *options);
+ const struct option *options);
extern NORETURN void usage_msg_opt(const char *msg,
const char * const *usagestr,
const struct option *options);
extern int optbug(const struct option *opt, const char *reason);
-extern int opterror(const struct option *opt, const char *reason, int flags);
-#if defined(__GNUC__)
-#define opterror(o,r,f) (opterror((o),(r),(f)), const_error())
-#endif
+const char *optname(const struct option *opt, int flags);
/*
* Use these assertions for callbacks that expect to be called with NONEG and
strbuf_splice(buf, 0, buf->len,
repo->index_file, strlen(repo->index_file));
else if (dir_prefix(base, "objects"))
- replace_dir(buf, git_dir_len + 7, repo->objects->objectdir);
+ replace_dir(buf, git_dir_len + 7, repo->objects->odb->path);
else if (git_hooks_path && dir_prefix(base, "hooks"))
replace_dir(buf, git_dir_len + 5, git_hooks_path);
else if (repo->different_commondir)
const char *git_path_##var(struct repository *r) \
{ \
if (!r->cached_paths.var) \
- r->cached_paths.var = git_pathdup(filename); \
+ r->cached_paths.var = repo_git_path(r, filename); \
return r->cached_paths.var; \
}
-#define NO_THE_INDEX_COMPATIBILITY_MACROS
#include "cache.h"
#include "config.h"
#include "dir.h"
FREE_AND_NULL(pathspec->items);
pathspec->nr = 0;
}
+
+int match_pathspec_attrs(const struct index_state *istate,
+ const char *name, int namelen,
+ const struct pathspec_item *item)
+{
+ int i;
+ char *to_free = NULL;
+
+ if (name[namelen])
+ name = to_free = xmemdupz(name, namelen);
+
+ git_check_attr(istate, name, item->attr_check);
+
+ free(to_free);
+
+ for (i = 0; i < item->attr_match_nr; i++) {
+ const char *value;
+ int matched;
+ enum attr_match_mode match_mode;
+
+ value = item->attr_check->items[i].value;
+ match_mode = item->attr_match[i].match_mode;
+
+ if (ATTR_TRUE(value))
+ matched = (match_mode == MATCH_SET);
+ else if (ATTR_FALSE(value))
+ matched = (match_mode == MATCH_UNSET);
+ else if (ATTR_UNSET(value))
+ matched = (match_mode == MATCH_UNSPECIFIED);
+ else
+ matched = (match_mode == MATCH_VALUE &&
+ !strcmp(item->attr_match[i].value, value));
+ if (!matched)
+ return 0;
+ }
+
+ return 1;
+}
* Any arguments used are copied. It is safe for the caller to modify
* or free 'prefix' and 'args' after calling this function.
*/
-extern void parse_pathspec(struct pathspec *pathspec,
- unsigned magic_mask,
- unsigned flags,
- const char *prefix,
- const char **args);
-extern void copy_pathspec(struct pathspec *dst, const struct pathspec *src);
-extern void clear_pathspec(struct pathspec *);
+void parse_pathspec(struct pathspec *pathspec,
+ unsigned magic_mask,
+ unsigned flags,
+ const char *prefix,
+ const char **args);
+void copy_pathspec(struct pathspec *dst, const struct pathspec *src);
+void clear_pathspec(struct pathspec *);
static inline int ps_strncmp(const struct pathspec_item *item,
const char *s1, const char *s2, size_t n)
return strcmp(s1, s2);
}
-extern void add_pathspec_matches_against_index(const struct pathspec *pathspec,
- const struct index_state *istate,
- char *seen);
-extern char *find_pathspecs_matching_against_index(const struct pathspec *pathspec,
- const struct index_state *istate);
+void add_pathspec_matches_against_index(const struct pathspec *pathspec,
+ const struct index_state *istate,
+ char *seen);
+char *find_pathspecs_matching_against_index(const struct pathspec *pathspec,
+ const struct index_state *istate);
+int match_pathspec_attrs(const struct index_state *istate,
+ const char *name, int namelen,
+ const struct pathspec_item *item);
#endif /* PATHSPEC_H */
#undef hex
}
-static void format_packet(struct strbuf *out, const char *fmt, va_list args)
+static void format_packet(struct strbuf *out, const char *prefix,
+ const char *fmt, va_list args)
{
size_t orig_len, n;
orig_len = out->len;
strbuf_addstr(out, "0000");
+ strbuf_addstr(out, prefix);
strbuf_vaddf(out, fmt, args);
n = out->len - orig_len;
packet_trace(out->buf + orig_len + 4, n - 4, 1);
}
-static int packet_write_fmt_1(int fd, int gently,
+static int packet_write_fmt_1(int fd, int gently, const char *prefix,
const char *fmt, va_list args)
{
static struct strbuf buf = STRBUF_INIT;
strbuf_reset(&buf);
- format_packet(&buf, fmt, args);
+ format_packet(&buf, prefix, fmt, args);
if (write_in_full(fd, buf.buf, buf.len) < 0) {
if (!gently) {
check_pipe(errno);
va_list args;
va_start(args, fmt);
- packet_write_fmt_1(fd, 0, fmt, args);
+ packet_write_fmt_1(fd, 0, "", fmt, args);
va_end(args);
}
va_list args;
va_start(args, fmt);
- status = packet_write_fmt_1(fd, 1, fmt, args);
+ status = packet_write_fmt_1(fd, 1, "", fmt, args);
va_end(args);
return status;
}
va_list args;
va_start(args, fmt);
- format_packet(buf, fmt, args);
+ format_packet(buf, "", fmt, args);
va_end(args);
}
return PACKET_READ_EOF;
}
+ if ((options & PACKET_READ_DIE_ON_ERR_PACKET) &&
+ starts_with(buffer, "ERR "))
+ die(_("remote error: %s"), buffer + 4);
+
if ((options & PACKET_READ_CHOMP_NEWLINE) &&
len && buffer[len-1] == '\n')
len--;
return sb_out->len - orig_len;
}
+int recv_sideband(const char *me, int in_stream, int out)
+{
+ char buf[LARGE_PACKET_MAX + 1];
+ int len;
+ struct strbuf scratch = STRBUF_INIT;
+ enum sideband_type sideband_type;
+
+ while (1) {
+ len = packet_read(in_stream, NULL, NULL, buf, LARGE_PACKET_MAX,
+ 0);
+ if (!demultiplex_sideband(me, buf, len, 0, &scratch,
+ &sideband_type))
+ continue;
+ switch (sideband_type) {
+ case SIDEBAND_PRIMARY:
+ write_or_die(out, buf + 1, len - 1);
+ break;
+ default: /* errors: message already written */
+ return sideband_type;
+ }
+ }
+}
+
/* Packet Reader Functions */
void packet_reader_init(struct packet_reader *reader, int fd,
char *src_buffer, size_t src_len,
reader->buffer = packet_buffer;
reader->buffer_size = sizeof(packet_buffer);
reader->options = options;
+ reader->me = "git";
}
enum packet_read_status packet_reader_read(struct packet_reader *reader)
{
+ struct strbuf scratch = STRBUF_INIT;
+
if (reader->line_peeked) {
reader->line_peeked = 0;
return reader->status;
}
- reader->status = packet_read_with_status(reader->fd,
- &reader->src_buffer,
- &reader->src_len,
- reader->buffer,
- reader->buffer_size,
- &reader->pktlen,
- reader->options);
+ /*
+ * Consume all progress packets until a primary payload packet is
+ * received
+ */
+ while (1) {
+ enum sideband_type sideband_type;
+ reader->status = packet_read_with_status(reader->fd,
+ &reader->src_buffer,
+ &reader->src_len,
+ reader->buffer,
+ reader->buffer_size,
+ &reader->pktlen,
+ reader->options);
+ if (!reader->use_sideband)
+ break;
+ if (demultiplex_sideband(reader->me, reader->buffer,
+ reader->pktlen, 1, &scratch,
+ &sideband_type))
+ break;
+ }
if (reader->status == PACKET_READ_NORMAL)
- reader->line = reader->buffer;
+ /* Skip the sideband designator if sideband is used */
+ reader->line = reader->use_sideband ?
+ reader->buffer + 1 : reader->buffer;
else
reader->line = NULL;
reader->line_peeked = 1;
return reader->status;
}
+
+void packet_writer_init(struct packet_writer *writer, int dest_fd)
+{
+ writer->dest_fd = dest_fd;
+ writer->use_sideband = 0;
+}
+
+void packet_writer_write(struct packet_writer *writer, const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ packet_write_fmt_1(writer->dest_fd, 0,
+ writer->use_sideband ? "\001" : "", fmt, args);
+ va_end(args);
+}
+
+void packet_writer_error(struct packet_writer *writer, const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ packet_write_fmt_1(writer->dest_fd, 0,
+ writer->use_sideband ? "\003" : "ERR ", fmt, args);
+ va_end(args);
+}
+
+void packet_writer_delim(struct packet_writer *writer)
+{
+ packet_delim(writer->dest_fd);
+}
+
+void packet_writer_flush(struct packet_writer *writer)
+{
+ packet_flush(writer->dest_fd);
+}
#include "git-compat-util.h"
#include "strbuf.h"
+#include "sideband.h"
/*
* Write a packetized stream, where each line is preceded by
*
* If options contains PACKET_READ_CHOMP_NEWLINE, a trailing newline (if
* present) is removed from the buffer before returning.
+ *
+ * If options contains PACKET_READ_DIE_ON_ERR_PACKET, it dies when it sees an
+ * ERR packet.
*/
-#define PACKET_READ_GENTLE_ON_EOF (1u<<0)
-#define PACKET_READ_CHOMP_NEWLINE (1u<<1)
+#define PACKET_READ_GENTLE_ON_EOF (1u<<0)
+#define PACKET_READ_CHOMP_NEWLINE (1u<<1)
+#define PACKET_READ_DIE_ON_ERR_PACKET (1u<<2)
int packet_read(int fd, char **src_buffer, size_t *src_len, char
*buffer, unsigned size, int options);
*/
ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out);
+/*
+ * Receive multiplexed output stream over git native protocol.
+ * in_stream is the input stream from the remote, which carries data
+ * in pkt_line format with band designator. Demultiplex it into out
+ * and err and return error appropriately. Band #1 carries the
+ * primary payload. Things coming over band #2 is not necessarily
+ * error; they are usually informative message on the standard error
+ * stream, aka "verbose"). A message over band #3 is a signal that
+ * the remote died unexpectedly. A flush() concludes the stream.
+ *
+ * Returns SIDEBAND_FLUSH upon a normal conclusion, and SIDEBAND_PROTOCOL_ERROR
+ * or SIDEBAND_REMOTE_ERROR if an error occurred.
+ */
+int recv_sideband(const char *me, int in_stream, int out);
+
struct packet_reader {
/* source file descriptor */
int fd;
/* indicates if a line has been peeked */
int line_peeked;
+
+ unsigned use_sideband : 1;
+ const char *me;
};
/*
#define LARGE_PACKET_DATA_MAX (LARGE_PACKET_MAX - 4)
extern char packet_buffer[LARGE_PACKET_MAX];
+struct packet_writer {
+ int dest_fd;
+ unsigned use_sideband : 1;
+};
+
+void packet_writer_init(struct packet_writer *writer, int dest_fd);
+
+/* These functions die upon failure. */
+__attribute__((format (printf, 2, 3)))
+void packet_writer_write(struct packet_writer *writer, const char *fmt, ...);
+__attribute__((format (printf, 2, 3)))
+void packet_writer_error(struct packet_writer *writer, const char *fmt, ...);
+void packet_writer_delim(struct packet_writer *writer);
+void packet_writer_flush(struct packet_writer *writer);
+
#endif
#include "config.h"
#include "progress.h"
#include "thread-utils.h"
+#include "repository.h"
/*
* Mostly randomly chosen maximum thread counts: we
trace_performance_leave("preload index");
}
-int read_index_preload(struct index_state *index,
- const struct pathspec *pathspec,
- unsigned int refresh_flags)
+int repo_read_index_preload(struct repository *repo,
+ const struct pathspec *pathspec,
+ unsigned int refresh_flags)
{
- int retval = read_index(index);
+ int retval = repo_read_index(repo);
- preload_index(index, pathspec, refresh_flags);
+ preload_index(repo->index, pathspec, refresh_flags);
return retval;
}
return strbuf_detach(&tmp, NULL);
}
-const char *logmsg_reencode(const struct commit *commit,
- char **commit_encoding,
- const char *output_encoding)
+const char *repo_logmsg_reencode(struct repository *r,
+ const struct commit *commit,
+ char **commit_encoding,
+ const char *output_encoding)
{
static const char *utf8 = "UTF-8";
const char *use_encoding;
char *encoding;
- const char *msg = get_commit_buffer(commit, NULL);
+ const char *msg = repo_get_commit_buffer(r, commit, NULL);
char *out;
if (!output_encoding || !*output_encoding) {
* the cached copy from get_commit_buffer, we need to duplicate it
* to avoid munging the cached copy.
*/
- if (msg == get_cached_commit_buffer(the_repository, commit, NULL))
+ if (msg == get_cached_commit_buffer(r, commit, NULL))
out = xstrdup(msg);
else
out = (char *)msg;
*/
out = reencode_string(msg, output_encoding, use_encoding);
if (out)
- unuse_commit_buffer(commit, msg);
+ repo_unuse_commit_buffer(r, commit, msg);
}
/*
struct commit_list *p;
const char *arg;
int ch;
+ char **slot;
/* these are independent of the commit */
switch (placeholder[0]) {
load_ref_decorations(NULL, DECORATE_SHORT_REFS);
format_decorations_extended(sb, commit, c->auto_color, "", ", ", "");
return 1;
+ case 'S': /* tag/branch like --source */
+ if (!(c->pretty_ctx->rev && c->pretty_ctx->rev->sources))
+ return 0;
+ slot = revision_sources_at(c->pretty_ctx->rev->sources, commit);
+ if (!(slot && *slot))
+ return 0;
+ strbuf_addstr(sb, *slot);
+ return 1;
case 'g': /* reflog info */
switch(placeholder[1]) {
case 'd': /* reflog selector */
case 'N':
w->notes = 1;
break;
+ case 'S':
+ w->source = 1;
+ break;
}
return 0;
}
strbuf_release(&dummy);
}
-void format_commit_message(const struct commit *commit,
- const char *format, struct strbuf *sb,
- const struct pretty_print_context *pretty_ctx)
+void repo_format_commit_message(struct repository *r,
+ const struct commit *commit,
+ const char *format, struct strbuf *sb,
+ const struct pretty_print_context *pretty_ctx)
{
struct format_commit_context context;
const char *output_enc = pretty_ctx->output_encoding;
* convert a commit message to UTF-8 first
* as far as 'format_commit_item' assumes it in UTF-8
*/
- context.message = logmsg_reencode(commit,
- &context.commit_encoding,
- utf8);
+ context.message = repo_logmsg_reencode(r, commit,
+ &context.commit_encoding,
+ utf8);
strbuf_expand(sb, format, format_commit_item, &context);
rewrap_message_tail(sb, &context, 0, 0, 0);
}
free(context.commit_encoding);
- unuse_commit_buffer(commit, context.message);
+ repo_unuse_commit_buffer(r, commit, context.message);
}
static void pp_header(struct pretty_print_context *pp,
struct userformat_want {
unsigned notes:1;
+ unsigned source:1;
};
/* Set the flag "w->notes" if there is placeholder %N in "fmt". */
* Put the result to "sb".
* Please use this function for custom formats.
*/
-void format_commit_message(const struct commit *commit,
+void repo_format_commit_message(struct repository *r,
+ const struct commit *commit,
const char *format, struct strbuf *sb,
const struct pretty_print_context *context);
+#ifndef NO_THE_REPOSITORY_COMPATIBILITY_MACROS
+#define format_commit_message(c, f, s, con) \
+ repo_format_commit_message(the_repository, c, f, s, con)
+#endif
/*
* Parse given arguments from "arg", check it for correctness and
* Return value is the same as in (1).
*/
static size_t quote_c_style_counted(const char *name, ssize_t maxlen,
- struct strbuf *sb, FILE *fp, int no_dq)
+ struct strbuf *sb, FILE *fp, int no_dq)
{
#undef EMIT
#define EMIT(c) \
*
* Copyright (C) Linus Torvalds, 2005
*/
-#define NO_THE_INDEX_COMPATIBILITY_MACROS
#include "cache.h"
#include "config.h"
#include "diff.h"
return *pool_ptr;
}
-struct index_state the_index;
static const char *alternate_index_output;
static void set_index_entry(struct index_state *istate, int nr, struct cache_entry *ce)
changed |= DATA_CHANGED;
return changed;
default:
- die("internal error: ce_mode is %o", ce->ce_mode);
+ BUG("unsupported ce_mode: %o", ce->ce_mode);
}
changed |= match_stat_data(&ce->ce_stat_data, st);
struct cache_entry *new_entry;
if (alias->ce_flags & CE_ADDED)
- die("Will not add file alias '%s' ('%s' already exists in index)", ce->name, alias->name);
+ die(_("will not add file alias '%s' ('%s' already exists in index)"),
+ ce->name, alias->name);
/* Ok, create the new entry using the name of the existing alias */
len = ce_namelen(alias);
{
struct object_id oid;
if (write_object_file("", 0, blob_type, &oid))
- die("cannot create an empty blob in the object database");
+ die(_("cannot create an empty blob in the object database"));
oidcpy(&ce->oid, &oid);
}
int intent_only = flags & ADD_CACHE_INTENT;
int add_option = (ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE|
(intent_only ? ADD_CACHE_NEW_ONLY : 0));
- int newflags = HASH_WRITE_OBJECT;
+ int hash_flags = HASH_WRITE_OBJECT;
- if (flags & HASH_RENORMALIZE)
- newflags |= HASH_RENORMALIZE;
+ if (flags & ADD_CACHE_RENORMALIZE)
+ hash_flags |= HASH_RENORMALIZE;
if (!S_ISREG(st_mode) && !S_ISLNK(st_mode) && !S_ISDIR(st_mode))
- return error("%s: can only add regular files, symbolic links or git-directories", path);
+ return error(_("%s: can only add regular files, symbolic links or git-directories"), path);
namelen = strlen(path);
if (S_ISDIR(st_mode)) {
}
}
if (!intent_only) {
- if (index_path(istate, &ce->oid, path, st, newflags)) {
+ if (index_path(istate, &ce->oid, path, st, hash_flags)) {
discard_cache_entry(ce);
- return error("unable to index file %s", path);
+ return error(_("unable to index file '%s'"), path);
}
} else
set_object_name_for_intent_to_add_entry(ce);
discard_cache_entry(ce);
else if (add_index_entry(istate, ce, add_option)) {
discard_cache_entry(ce);
- return error("unable to add %s to index", path);
+ return error(_("unable to add '%s' to index"), path);
}
if (verbose && !was_same)
printf("add '%s'\n", path);
{
struct stat st;
if (lstat(path, &st))
- die_errno("unable to stat '%s'", path);
+ die_errno(_("unable to stat '%s'"), path);
return add_to_index(istate, path, &st, flags);
}
int len;
if (!verify_path(path, mode)) {
- error("Invalid path '%s'", path);
+ error(_("invalid path '%s'"), path);
return NULL;
}
int len;
if (!verify_path(path, mode)) {
- error("Invalid path '%s'", path);
+ error(_("invalid path '%s'"), path);
return NULL;
}
if (!ok_to_add)
return -1;
if (!verify_path(ce->name, ce->ce_mode))
- return error("Invalid path '%s'", ce->name);
+ return error(_("invalid path '%s'"), ce->name);
if (!skip_df_check &&
check_file_directory_conflict(istate, ce, pos, ok_to_replace)) {
if (!ok_to_replace)
- return error("'%s' appears as both a file and as a directory",
+ return error(_("'%s' appears as both a file and as a directory"),
ce->name);
pos = index_name_stage_pos(istate, ce->name, ce_namelen(ce), ce_stage(ce));
pos = -pos-1;
istate->cache_nr);
trace_performance_enter();
- modified_fmt = (in_porcelain ? "M\t%s\n" : "%s: needs update\n");
- deleted_fmt = (in_porcelain ? "D\t%s\n" : "%s: needs update\n");
- typechange_fmt = (in_porcelain ? "T\t%s\n" : "%s needs update\n");
- added_fmt = (in_porcelain ? "A\t%s\n" : "%s needs update\n");
- unmerged_fmt = (in_porcelain ? "U\t%s\n" : "%s: needs merge\n");
+ modified_fmt = in_porcelain ? "M\t%s\n" : "%s: needs update\n";
+ deleted_fmt = in_porcelain ? "D\t%s\n" : "%s: needs update\n";
+ typechange_fmt = in_porcelain ? "T\t%s\n" : "%s: needs update\n";
+ added_fmt = in_porcelain ? "A\t%s\n" : "%s: needs update\n";
+ unmerged_fmt = in_porcelain ? "U\t%s\n" : "%s: needs merge\n";
/*
* Use the multi-threaded preload_index() to refresh most of the
* cache entries quickly then in the single threaded loop below,
int hdr_version;
if (hdr->hdr_signature != htonl(CACHE_SIGNATURE))
- return error("bad signature");
+ return error(_("bad signature 0x%08x"), hdr->hdr_signature);
hdr_version = ntohl(hdr->hdr_version);
if (hdr_version < INDEX_FORMAT_LB || INDEX_FORMAT_UB < hdr_version)
- return error("bad index version %d", hdr_version);
+ return error(_("bad index version %d"), hdr_version);
if (!verify_index_checksum)
return 0;
the_hash_algo->update_fn(&c, hdr, size - the_hash_algo->rawsz);
the_hash_algo->final_fn(hash, &c);
if (!hasheq(hash, (unsigned char *)hdr + size - the_hash_algo->rawsz))
- return error("bad index file sha1 signature");
+ return error(_("bad index file sha1 signature"));
return 0;
}
break;
default:
if (*ext < 'A' || 'Z' < *ext)
- return error("index uses %.4s extension, which we do not understand",
+ return error(_("index uses %.4s extension, which we do not understand"),
ext);
- fprintf(stderr, "ignoring %.4s extension\n", ext);
+ fprintf_ln(stderr, _("ignoring %.4s extension"), ext);
break;
}
return 0;
}
-int hold_locked_index(struct lock_file *lk, int lock_flags)
-{
- return hold_lock_file_for_update(lk, get_index_file(), lock_flags);
-}
-
-int read_index(struct index_state *istate)
-{
- return read_index_from(istate, get_index_file(), get_git_dir());
-}
-
static struct cache_entry *create_from_disk(struct mem_pool *ce_mem_pool,
unsigned int version,
struct ondisk_cache_entry *ondisk,
extended_flags = get_be16(&ondisk2->flags2) << 16;
/* We do not yet understand any bit out of CE_EXTENDED_FLAGS */
if (extended_flags & ~CE_EXTENDED_FLAGS)
- die("Unknown index entry format %08x", extended_flags);
+ die(_("unknown index entry format 0x%08x"), extended_flags);
flags |= extended_flags;
name = ondisk2->name;
}
int name_compare = strcmp(ce->name, next_ce->name);
if (0 < name_compare)
- die("unordered stage entries in index");
+ die(_("unordered stage entries in index"));
if (!name_compare) {
if (!ce_stage(ce))
- die("multiple stage entries for merged file '%s'",
+ die(_("multiple stage entries for merged file '%s'"),
ce->name);
if (ce_stage(ce) > ce_stage(next_ce))
- die("unordered stage entries for '%s'",
+ die(_("unordered stage entries for '%s'"),
ce->name);
}
}
if (fd < 0) {
if (!must_exist && errno == ENOENT)
return 0;
- die_errno("%s: index file open failed", path);
+ die_errno(_("%s: index file open failed"), path);
}
if (fstat(fd, &st))
- die_errno("cannot stat the open index");
+ die_errno(_("%s: cannot stat the open index"), path);
mmap_size = xsize_t(st.st_size);
if (mmap_size < sizeof(struct cache_header) + the_hash_algo->rawsz)
- die("index file smaller than expected");
+ die(_("%s: index file smaller than expected"), path);
mmap = xmmap(NULL, mmap_size, PROT_READ, MAP_PRIVATE, fd, 0);
if (mmap == MAP_FAILED)
- die_errno("unable to map index file");
+ die_errno(_("%s: unable to map index file"), path);
close(fd);
hdr = (const struct cache_header *)mmap;
unmap:
munmap((void *)mmap, mmap_size);
- die("index file corrupt");
+ die(_("index file corrupt"));
}
/*
static void freshen_shared_index(const char *shared_index, int warn)
{
if (!check_and_freshen_file(shared_index, 1) && warn)
- warning("could not freshen shared index '%s'", shared_index);
+ warning(_("could not freshen shared index '%s'"), shared_index);
}
int read_index_from(struct index_state *istate, const char *path,
base_path = xstrfmt("%s/sharedindex.%s", gitdir, base_oid_hex);
ret = do_read_index(split_index->base, base_path, 1);
if (!oideq(&split_index->base_oid, &split_index->base->oid))
- die("broken index, expect %s in %s, got %s",
+ die(_("broken index, expect %s in %s, got %s"),
base_oid_hex, base_path,
oid_to_hex(&split_index->base->oid));
for (i = 0; i < istate->cache_nr; i++) {
if (!istate) {
- die("internal error: cache entry is not allocated from expected memory pool");
+ BUG("cache entry is not allocated from expected memory pool");
} else if (!istate->ce_mem_pool ||
!mem_pool_contains(istate->ce_mem_pool, istate->cache[i])) {
if (!istate->split_index ||
!istate->split_index->base ||
!istate->split_index->base->ce_mem_pool ||
!mem_pool_contains(istate->split_index->base->ce_mem_pool, istate->cache[i])) {
- die("internal error: cache entry is not allocated from expected memory pool");
+ BUG("cache entry is not allocated from expected memory pool");
}
}
}
return 0;
}
-int index_has_changes(struct index_state *istate,
- struct tree *tree,
- struct strbuf *sb)
+int repo_index_has_changes(struct repository *repo,
+ struct tree *tree,
+ struct strbuf *sb)
{
+ struct index_state *istate = repo->index;
struct object_id cmp;
int i;
- if (istate != &the_index) {
- BUG("index_has_changes cannot yet accept istate != &the_index; do_diff_cache needs updating first.");
- }
if (tree)
cmp = tree->object.oid;
if (tree || !get_oid_tree("HEAD", &cmp)) {
struct diff_options opt;
- repo_diff_setup(the_repository, &opt);
+ repo_diff_setup(repo, &opt);
opt.flags.exit_with_status = 1;
if (!sb)
opt.flags.quick = 1;
return 0;
}
-static int verify_index(const struct index_state *istate)
+static int repo_verify_index(struct repository *repo)
{
- return verify_index_from(istate, get_index_file());
+ return verify_index_from(repo->index, repo->index_file);
}
static int has_racy_timestamp(struct index_state *istate)
return 0;
}
-void update_index_if_able(struct index_state *istate, struct lock_file *lockfile)
+void repo_update_index_if_able(struct repository *repo,
+ struct lock_file *lockfile)
{
- if ((istate->cache_changed || has_racy_timestamp(istate)) &&
- verify_index(istate))
- write_locked_index(istate, lockfile, COMMIT_LOCK);
+ if ((repo->index->cache_changed ||
+ has_racy_timestamp(repo->index)) &&
+ repo_verify_index(repo))
+ write_locked_index(repo->index, lockfile, COMMIT_LOCK);
else
rollback_lock_file(lockfile);
}
return ret;
ret = adjust_shared_perm(get_tempfile_path(*temp));
if (ret) {
- error("cannot fix permission bits on %s", get_tempfile_path(*temp));
+ error(_("cannot fix permission bits on '%s'"), get_tempfile_path(*temp));
return ret;
}
ret = rename_tempfile(temp,
struct split_index *si = istate->split_index;
if (git_env_bool("GIT_TEST_CHECK_CACHE_TREE", 0))
- cache_tree_verify(istate);
+ cache_tree_verify(the_repository, istate);
if ((flags & SKIP_IF_UNCHANGED) && !istate->cache_changed) {
if (flags & COMMIT_LOCK)
* state can call this and check its return value, instead of calling
* read_cache().
*/
-int read_index_unmerged(struct index_state *istate)
+int repo_read_index_unmerged(struct repository *repo)
{
+ struct index_state *istate;
int i;
int unmerged = 0;
- read_index(istate);
+ repo_read_index(repo);
+ istate = repo->index;
for (i = 0; i < istate->cache_nr; i++) {
struct cache_entry *ce = istate->cache[i];
struct cache_entry *new_ce;
new_ce->ce_namelen = len;
new_ce->ce_mode = ce->ce_mode;
if (add_index_entry(istate, new_ce, ADD_CACHE_SKIP_DFCHECK))
- return error("%s: cannot drop to stage #0",
+ return error(_("%s: cannot drop to stage #0"),
new_ce->name);
}
return unmerged;
static struct index_entry_offset_table *read_ieot_extension(const char *mmap, size_t mmap_size, size_t offset)
{
- const char *index = NULL;
- uint32_t extsize, ext_version;
- struct index_entry_offset_table *ieot;
- int i, nr;
-
- /* find the IEOT extension */
- if (!offset)
- return NULL;
- while (offset <= mmap_size - the_hash_algo->rawsz - 8) {
- extsize = get_be32(mmap + offset + 4);
- if (CACHE_EXT((mmap + offset)) == CACHE_EXT_INDEXENTRYOFFSETTABLE) {
- index = mmap + offset + 4 + 4;
- break;
- }
- offset += 8;
- offset += extsize;
- }
- if (!index)
- return NULL;
-
- /* validate the version is IEOT_VERSION */
- ext_version = get_be32(index);
- if (ext_version != IEOT_VERSION) {
- error("invalid IEOT version %d", ext_version);
- return NULL;
- }
- index += sizeof(uint32_t);
-
- /* extension size - version bytes / bytes per entry */
- nr = (extsize - sizeof(uint32_t)) / (sizeof(uint32_t) + sizeof(uint32_t));
- if (!nr) {
- error("invalid number of IEOT entries %d", nr);
- return NULL;
- }
- ieot = xmalloc(sizeof(struct index_entry_offset_table)
- + (nr * sizeof(struct index_entry_offset)));
- ieot->nr = nr;
- for (i = 0; i < nr; i++) {
- ieot->entries[i].offset = get_be32(index);
- index += sizeof(uint32_t);
- ieot->entries[i].nr = get_be32(index);
- index += sizeof(uint32_t);
- }
-
- return ieot;
+ const char *index = NULL;
+ uint32_t extsize, ext_version;
+ struct index_entry_offset_table *ieot;
+ int i, nr;
+
+ /* find the IEOT extension */
+ if (!offset)
+ return NULL;
+ while (offset <= mmap_size - the_hash_algo->rawsz - 8) {
+ extsize = get_be32(mmap + offset + 4);
+ if (CACHE_EXT((mmap + offset)) == CACHE_EXT_INDEXENTRYOFFSETTABLE) {
+ index = mmap + offset + 4 + 4;
+ break;
+ }
+ offset += 8;
+ offset += extsize;
+ }
+ if (!index)
+ return NULL;
+
+ /* validate the version is IEOT_VERSION */
+ ext_version = get_be32(index);
+ if (ext_version != IEOT_VERSION) {
+ error("invalid IEOT version %d", ext_version);
+ return NULL;
+ }
+ index += sizeof(uint32_t);
+
+ /* extension size - version bytes / bytes per entry */
+ nr = (extsize - sizeof(uint32_t)) / (sizeof(uint32_t) + sizeof(uint32_t));
+ if (!nr) {
+ error("invalid number of IEOT entries %d", nr);
+ return NULL;
+ }
+ ieot = xmalloc(sizeof(struct index_entry_offset_table)
+ + (nr * sizeof(struct index_entry_offset)));
+ ieot->nr = nr;
+ for (i = 0; i < nr; i++) {
+ ieot->entries[i].offset = get_be32(index);
+ index += sizeof(uint32_t);
+ ieot->entries[i].nr = get_be32(index);
+ index += sizeof(uint32_t);
+ }
+
+ return ieot;
}
static void write_ieot_extension(struct strbuf *sb, struct index_entry_offset_table *ieot)
{
- uint32_t buffer;
- int i;
+ uint32_t buffer;
+ int i;
- /* version */
- put_be32(&buffer, IEOT_VERSION);
- strbuf_add(sb, &buffer, sizeof(uint32_t));
+ /* version */
+ put_be32(&buffer, IEOT_VERSION);
+ strbuf_add(sb, &buffer, sizeof(uint32_t));
- /* ieot */
- for (i = 0; i < ieot->nr; i++) {
+ /* ieot */
+ for (i = 0; i < ieot->nr; i++) {
- /* offset */
- put_be32(&buffer, ieot->entries[i].offset);
- strbuf_add(sb, &buffer, sizeof(uint32_t));
+ /* offset */
+ put_be32(&buffer, ieot->entries[i].offset);
+ strbuf_add(sb, &buffer, sizeof(uint32_t));
- /* count */
- put_be32(&buffer, ieot->entries[i].nr);
- strbuf_add(sb, &buffer, sizeof(uint32_t));
- }
+ /* count */
+ put_be32(&buffer, ieot->entries[i].nr);
+ strbuf_add(sb, &buffer, sizeof(uint32_t));
+ }
}
}
}
-int edit_todo_list(unsigned flags)
+int edit_todo_list(struct repository *r, unsigned flags)
{
struct strbuf buf = STRBUF_INIT;
const char *todo_file = rebase_path_todo();
strbuf_release(&buf);
- transform_todos(flags | TODO_LIST_SHORTEN_IDS);
+ transform_todos(r, flags | TODO_LIST_SHORTEN_IDS);
if (strbuf_read_file(&buf, todo_file, 0) < 0)
return error_errno(_("could not read '%s'."), todo_file);
if (launch_sequence_editor(todo_file, NULL, NULL))
return -1;
- transform_todos(flags & ~(TODO_LIST_SHORTEN_IDS));
+ transform_todos(r, flags & ~(TODO_LIST_SHORTEN_IDS));
return 0;
}
#ifndef REBASE_INTERACTIVE_H
#define REBASE_INTERACTIVE_H
+struct strbuf;
+struct repository;
+
void append_todo_help(unsigned edit_todo, unsigned keep_empty,
struct strbuf *buf);
-int edit_todo_list(unsigned flags);
+int edit_todo_list(struct repository *r, unsigned flags);
#endif
static int objectsize_atom_parser(const struct ref_format *format, struct used_atom *atom,
const char *arg, struct strbuf *err)
+{
+ if (!arg) {
+ if (*atom->name == '*')
+ oi_deref.info.sizep = &oi_deref.size;
+ else
+ oi.info.sizep = &oi.size;
+ } else if (!strcmp(arg, "disk")) {
+ if (*atom->name == '*')
+ oi_deref.info.disk_sizep = &oi_deref.disk_size;
+ else
+ oi.info.disk_sizep = &oi.disk_size;
+ } else
+ return strbuf_addf_ret(err, -1, _("unrecognized %%(objectsize) argument: %s"), arg);
+ return 0;
+}
+
+static int deltabase_atom_parser(const struct ref_format *format, struct used_atom *atom,
+ const char *arg, struct strbuf *err)
{
if (arg)
- return strbuf_addf_ret(err, -1, _("%%(objectsize) does not take arguments"));
+ return strbuf_addf_ret(err, -1, _("%%(deltabase) does not take arguments"));
if (*atom->name == '*')
- oi_deref.info.sizep = &oi_deref.size;
+ oi_deref.info.delta_base_sha1 = oi_deref.delta_base_oid.hash;
else
- oi.info.sizep = &oi.size;
+ oi.info.delta_base_sha1 = oi.delta_base_oid.hash;
return 0;
}
{ "objecttype", SOURCE_OTHER, FIELD_STR, objecttype_atom_parser },
{ "objectsize", SOURCE_OTHER, FIELD_ULONG, objectsize_atom_parser },
{ "objectname", SOURCE_OTHER, FIELD_STR, objectname_atom_parser },
+ { "deltabase", SOURCE_OTHER, FIELD_STR, deltabase_atom_parser },
{ "tree", SOURCE_OBJ },
{ "parent", SOURCE_OBJ },
{ "numparent", SOURCE_OBJ, FIELD_ULONG },
name++;
if (!strcmp(name, "objecttype"))
v->s = xstrdup(type_name(oi->type));
- else if (!strcmp(name, "objectsize")) {
+ else if (!strcmp(name, "objectsize:disk")) {
+ v->value = oi->disk_size;
+ v->s = xstrfmt("%"PRIuMAX, (uintmax_t)oi->disk_size);
+ } else if (!strcmp(name, "objectsize")) {
v->value = oi->size;
v->s = xstrfmt("%"PRIuMAX , (uintmax_t)oi->size);
- }
+ } else if (!strcmp(name, "deltabase"))
+ v->s = xstrdup(oid_to_hex(&oi->delta_base_oid));
else if (deref)
grab_objectname(name, &oi->oid, v, &used_atom[i]);
}
struct strbuf desc = STRBUF_INIT;
struct wt_status_state state;
memset(&state, 0, sizeof(state));
- wt_status_get_state(&state, 1);
+ wt_status_get_state(the_repository, &state, 1);
if (state.rebase_in_progress ||
state.rebase_interactive_in_progress) {
if (state.branch)
OBJECT_INFO_LOOKUP_REPLACE))
return strbuf_addf_ret(err, -1, _("missing object %s for %s"),
oid_to_hex(&oi->oid), ref->refname);
+ if (oi->info.disk_sizep && oi->disk_size < 0)
+ BUG("Object size is less than zero.");
if (oi->info.contentp) {
*obj = parse_object_buffer(the_repository, &oi->oid, oi->type, oi->size, oi->content, &eaten);
if (rf->merge) {
if (no_merged) {
- return opterror(opt, "is incompatible with --merged", 0);
+ return error(_("option `%s' is incompatible with --merged"),
+ opt->long_name);
} else {
- return opterror(opt, "is incompatible with --no-merged", 0);
+ return error(_("option `%s' is incompatible with --no-merged"),
+ opt->long_name);
}
}
rf->merge_commit = lookup_commit_reference_gently(the_repository,
&oid, 0);
if (!rf->merge_commit)
- return opterror(opt, "must point to a commit", 0);
+ return error(_("option `%s' must point to a commit"), opt->long_name);
return 0;
}
packet_reader_init(&reader, -1, heads->buf, heads->len,
PACKET_READ_CHOMP_NEWLINE |
- PACKET_READ_GENTLE_ON_EOF);
+ PACKET_READ_GENTLE_ON_EOF |
+ PACKET_READ_DIE_ON_ERR_PACKET);
heads->version = discover_version(&reader);
switch (heads->version) {
http_options.extra_headers = &extra_headers;
http_options.initial_request = 1;
http_options.no_cache = 1;
- http_options.keep_error = 1;
http_ret = http_get_strbuf(refs_url.buf, &buffer, &http_options);
switch (http_ret) {
if (maybe_smart &&
(5 <= last->len && last->buf[4] == '#') &&
!strbuf_cmp(&exp, &type)) {
- char *line;
+ struct packet_reader reader;
+ packet_reader_init(&reader, -1, last->buf, last->len,
+ PACKET_READ_CHOMP_NEWLINE |
+ PACKET_READ_DIE_ON_ERR_PACKET);
/*
* smart HTTP response; validate that the service
* pkt-line matches our request.
*/
- line = packet_read_line_buf(&last->buf, &last->len, NULL);
- if (!line)
+ if (packet_reader_read(&reader) != PACKET_READ_NORMAL)
die("invalid server response; expected service, got flush packet");
strbuf_reset(&exp);
strbuf_addf(&exp, "# service=%s", service);
- if (strcmp(line, exp.buf))
- die("invalid server response; got '%s'", line);
+ if (strcmp(reader.line, exp.buf))
+ die("invalid server response; got '%s'", reader.line);
strbuf_release(&exp);
/* The header can include additional metadata lines, up
* until a packet flush marker. Ignore these now, but
* in the future we might start to scan them.
*/
- while (packet_read_line_buf(&last->buf, &last->len, NULL))
- ;
+ for (;;) {
+ packet_reader_read(&reader);
+ if (reader.pktlen <= 0) {
+ break;
+ }
+ }
+
+ last->buf = reader.src_buffer;
+ last->len = reader.src_len;
last->proto_git = 1;
} else if (maybe_smart &&
}
#endif
+struct rpc_in_data {
+ struct rpc_state *rpc;
+ struct active_request_slot *slot;
+};
+
+/*
+ * A callback for CURLOPT_WRITEFUNCTION. The return value is the bytes consumed
+ * from ptr.
+ */
static size_t rpc_in(char *ptr, size_t eltsize,
size_t nmemb, void *buffer_)
{
size_t size = eltsize * nmemb;
- struct rpc_state *rpc = buffer_;
+ struct rpc_in_data *data = buffer_;
+ long response_code;
+
+ if (curl_easy_getinfo(data->slot->curl, CURLINFO_RESPONSE_CODE,
+ &response_code) != CURLE_OK)
+ return size;
+ if (response_code >= 300)
+ return size;
if (size)
- rpc->any_written = 1;
- write_or_die(rpc->in, ptr, size);
+ data->rpc->any_written = 1;
+ write_or_die(data->rpc->in, ptr, size);
return size;
}
return err;
}
-static curl_off_t xcurl_off_t(size_t len) {
+static curl_off_t xcurl_off_t(size_t len)
+{
uintmax_t size = len;
if (size > maximum_signed_value_of_type(curl_off_t))
die("cannot handle pushes this big");
size_t gzip_size = 0;
int err, large_request = 0;
int needs_100_continue = 0;
+ struct rpc_in_data rpc_in_data;
/* Try to load the entire request, if we can fit it into the
* allocated buffer space we can use HTTP/1.0 and avoid the
curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, rpc_in);
- curl_easy_setopt(slot->curl, CURLOPT_FILE, rpc);
+ rpc_in_data.rpc = rpc;
+ rpc_in_data.slot = slot;
+ curl_easy_setopt(slot->curl, CURLOPT_FILE, &rpc_in_data);
+ curl_easy_setopt(slot->curl, CURLOPT_FAILONERROR, 0);
rpc->any_written = 0;
p->headers = curl_slist_append(p->headers, buf.buf);
packet_reader_init(&p->reader, p->in, NULL, 0,
- PACKET_READ_GENTLE_ON_EOF);
+ PACKET_READ_GENTLE_ON_EOF |
+ PACKET_READ_DIE_ON_ERR_PACKET);
strbuf_release(&buf);
}
#include "mergesort.h"
#include "argv-array.h"
#include "commit-reach.h"
+#include "advice.h"
enum map_direction { FROM_SRC, FROM_DST };
if (!name)
return 0;
if (!strcmp(subkey, "insteadof")) {
- rewrite = make_rewrite(&rewrites, name, namelen);
if (!value)
return config_error_nonbool(key);
+ rewrite = make_rewrite(&rewrites, name, namelen);
add_instead_of(rewrite, xstrdup(value));
} else if (!strcmp(subkey, "pushinsteadof")) {
- rewrite = make_rewrite(&rewrites_push, name, namelen);
if (!value)
return config_error_nonbool(key);
+ rewrite = make_rewrite(&rewrites_push, name, namelen);
add_instead_of(rewrite, xstrdup(value));
}
}
return 0;
/* Handle remote.<name>.* variables */
if (*name == '/') {
- warning("Config remote shorthand cannot begin with '/': %s",
+ warning(_("config remote shorthand cannot begin with '/': %s"),
name);
return 0;
}
if (!remote->receivepack)
remote->receivepack = v;
else
- error("more than one receivepack given, using the first");
+ error(_("more than one receivepack given, using the first"));
} else if (!strcmp(subkey, "uploadpack")) {
const char *v;
if (git_config_string(&v, key, value))
if (!remote->uploadpack)
remote->uploadpack = v;
else
- error("more than one uploadpack given, using the first");
+ error(_("more than one uploadpack given, using the first"));
} else if (!strcmp(subkey, "tagopt")) {
if (!strcmp(value, "--no-tags"))
remote->fetch_tags = -1;
* FETCH_HEAD_IGNORE entries always appear at
* the end of the list.
*/
- die(_("Internal error"));
+ BUG("Internal error");
}
}
free(ref2->peer_ref);
size_t namelen;
int ret;
if (!kstar)
- die("Key '%s' of pattern had no '*'", key);
+ die(_("key '%s' of pattern had no '*'"), key);
klen = kstar - key;
ksuffixlen = strlen(kstar + 1);
namelen = strlen(name);
struct strbuf sb = STRBUF_INIT;
const char *vstar = strchr(value, '*');
if (!vstar)
- die("Value '%s' of pattern has no '*'", value);
+ die(_("value '%s' of pattern has no '*'"), value);
strbuf_add(&sb, value, vstar - value);
strbuf_add(&sb, name + klen, namelen - klen - ksuffixlen);
strbuf_addstr(&sb, vstar + 1);
int find_src = !query->src;
if (find_src && !query->dst)
- error("query_refspecs_multiple: need either src or dst");
+ BUG("query_refspecs_multiple: need either src or dst");
for (i = 0; i < rs->nr; i++) {
struct refspec_item *refspec = &rs->items[i];
char **result = find_src ? &query->src : &query->dst;
if (find_src && !query->dst)
- return error("query_refspecs: need either src or dst");
+ BUG("query_refspecs: need either src or dst");
for (i = 0; i < rs->nr; i++) {
struct refspec_item *refspec = &rs->items[i];
if (!r)
return NULL;
- if (starts_with(r, "refs/heads/"))
+ if (starts_with(r, "refs/heads/")) {
strbuf_addstr(&buf, "refs/heads/");
- else if (starts_with(r, "refs/tags/"))
+ } else if (starts_with(r, "refs/tags/")) {
strbuf_addstr(&buf, "refs/tags/");
- else
+ } else {
return NULL;
+ }
strbuf_addstr(&buf, name);
return strbuf_detach(&buf, NULL);
* way to delete 'other' ref at the remote end.
*/
if (try_explicit_object_name(rs->src, match) < 0)
- return error("src refspec %s does not match any.", rs->src);
+ return error(_("src refspec %s does not match any"), rs->src);
if (allocated_match)
*allocated_match = 1;
return 0;
default:
- return error("src refspec %s matches more than one.", rs->src);
+ return error(_("src refspec %s matches more than one"), rs->src);
+ }
+}
+
+static void show_push_unqualified_ref_name_error(const char *dst_value,
+ const char *matched_src_name)
+{
+ struct object_id oid;
+ enum object_type type;
+
+ /*
+ * TRANSLATORS: "matches '%s'%" is the <dst> part of "git push
+ * <remote> <src>:<dst>" push, and "being pushed ('%s')" is
+ * the <src>.
+ */
+ error(_("The destination you provided is not a full refname (i.e.,\n"
+ "starting with \"refs/\"). We tried to guess what you meant by:\n"
+ "\n"
+ "- Looking for a ref that matches '%s' on the remote side.\n"
+ "- Checking if the <src> being pushed ('%s')\n"
+ " is a ref in \"refs/{heads,tags}/\". If so we add a corresponding\n"
+ " refs/{heads,tags}/ prefix on the remote side.\n"
+ "\n"
+ "Neither worked, so we gave up. You must fully qualify the ref."),
+ dst_value, matched_src_name);
+
+ if (!advice_push_unqualified_ref_name)
+ return;
+
+ if (get_oid(matched_src_name, &oid))
+ BUG("'%s' is not a valid object, "
+ "match_explicit_lhs() should catch this!",
+ matched_src_name);
+ type = oid_object_info(the_repository, &oid, NULL);
+ if (type == OBJ_COMMIT) {
+ advise(_("The <src> part of the refspec is a commit object.\n"
+ "Did you mean to create a new branch by pushing to\n"
+ "'%s:refs/heads/%s'?"),
+ matched_src_name, dst_value);
+ } else if (type == OBJ_TAG) {
+ advise(_("The <src> part of the refspec is a tag object.\n"
+ "Did you mean to create a new tag by pushing to\n"
+ "'%s:refs/tags/%s'?"),
+ matched_src_name, dst_value);
+ } else if (type == OBJ_TREE) {
+ advise(_("The <src> part of the refspec is a tree object.\n"
+ "Did you mean to tag a new tree by pushing to\n"
+ "'%s:refs/tags/%s'?"),
+ matched_src_name, dst_value);
+ } else if (type == OBJ_BLOB) {
+ advise(_("The <src> part of the refspec is a blob object.\n"
+ "Did you mean to tag a new blob by pushing to\n"
+ "'%s:refs/tags/%s'?"),
+ matched_src_name, dst_value);
+ } else {
+ BUG("'%s' should be commit/tag/tree/blob, is '%d'",
+ matched_src_name, type);
}
}
if (!dst_value ||
((flag & REF_ISSYMREF) &&
!starts_with(dst_value, "refs/heads/")))
- die("%s cannot be resolved to branch.",
+ die(_("%s cannot be resolved to branch"),
matched_src->name);
}
case 1:
break;
case 0:
- if (starts_with(dst_value, "refs/"))
+ if (starts_with(dst_value, "refs/")) {
matched_dst = make_linked_ref(dst_value, dst_tail);
- else if (is_null_oid(&matched_src->new_oid))
- error("unable to delete '%s': remote ref does not exist",
+ } else if (is_null_oid(&matched_src->new_oid)) {
+ error(_("unable to delete '%s': remote ref does not exist"),
dst_value);
- else if ((dst_guess = guess_ref(dst_value, matched_src))) {
+ } else if ((dst_guess = guess_ref(dst_value, matched_src))) {
matched_dst = make_linked_ref(dst_guess, dst_tail);
free(dst_guess);
- } else
- error("unable to push to unqualified destination: %s\n"
- "The destination refspec neither matches an "
- "existing ref on the remote nor\n"
- "begins with refs/, and we are unable to "
- "guess a prefix based on the source ref.",
- dst_value);
+ } else {
+ show_push_unqualified_ref_name_error(dst_value,
+ matched_src->name);
+ }
break;
default:
matched_dst = NULL;
- error("dst refspec %s matches more than one.",
+ error(_("dst refspec %s matches more than one"),
dst_value);
break;
}
if (!matched_dst)
return -1;
if (matched_dst->peer_ref)
- return error("dst ref %s receives from more than one src.",
- matched_dst->name);
+ return error(_("dst ref %s receives from more than one src"),
+ matched_dst->name);
else {
matched_dst->peer_ref = allocated_src ?
matched_src :
ref_map = get_remote_ref(remote_refs, name);
}
if (!missing_ok && !ref_map)
- die("Couldn't find remote ref %s", name);
+ die(_("couldn't find remote ref %s"), name);
if (ref_map) {
ref_map->peer_ref = get_local_ref(refspec->dst);
if (ref_map->peer_ref && refspec->force)
if (!starts_with((*rmp)->peer_ref->name, "refs/") ||
check_refname_format((*rmp)->peer_ref->name, 0)) {
struct ref *ignore = *rmp;
- error("* Ignoring funny ref '%s' locally",
+ error(_("* Ignoring funny ref '%s' locally"),
(*rmp)->peer_ref->name);
*rmp = (*rmp)->next;
free(ignore->peer_ref);
repo_init_revisions(the_repository, &revs, NULL);
setup_revisions(argv.argc, argv.argv, &revs, NULL);
if (prepare_revision_walk(&revs))
- die("revision walk setup failed");
+ die(_("revision walk setup failed"));
/* ... and count the commits on each side. */
while (1) {
else if (!colon[1])
oidclr(&entry->expect);
else if (get_oid(colon + 1, &entry->expect))
- return error("cannot parse expected object name '%s'", colon + 1);
+ return error(_("cannot parse expected object name '%s'"),
+ colon + 1);
return 0;
}
+/*
+ * not really _using_ the compat macros, just make sure the_index
+ * declaration matches the definition in this file.
+ */
+#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "cache.h"
#include "repository.h"
#include "object-store.h"
#include "config.h"
#include "object.h"
+#include "lockfile.h"
#include "submodule-config.h"
/* The main repository */
static struct repository the_repo;
struct repository *the_repository;
+struct index_state the_index;
void initialize_the_repository(void)
{
free(old_gitdir);
repo_set_commondir(repo, o->commondir);
- expand_base_dir(&repo->objects->objectdir, o->object_dir,
+
+ if (!repo->objects->odb) {
+ repo->objects->odb = xcalloc(1, sizeof(*repo->objects->odb));
+ repo->objects->odb_tail = &repo->objects->odb->next;
+ }
+ expand_base_dir(&repo->objects->odb->path, o->object_dir,
repo->commondir, "objects");
+
free(repo->objects->alternate_db);
repo->objects->alternate_db = xstrdup_or_null(o->alternate_db);
expand_base_dir(&repo->graft_file, o->graft_file,
return -1;
}
-/*
- * Initialize 'submodule' as the submodule given by 'path' in parent repository
- * 'superproject'.
- * Return 0 upon success and a non-zero value upon failure.
- */
-int repo_submodule_init(struct repository *submodule,
+int repo_submodule_init(struct repository *subrepo,
struct repository *superproject,
- const char *path)
+ const struct submodule *sub)
{
- const struct submodule *sub;
struct strbuf gitdir = STRBUF_INIT;
struct strbuf worktree = STRBUF_INIT;
int ret = 0;
- sub = submodule_from_path(superproject, &null_oid, path);
if (!sub) {
ret = -1;
goto out;
}
- strbuf_repo_worktree_path(&gitdir, superproject, "%s/.git", path);
- strbuf_repo_worktree_path(&worktree, superproject, "%s", path);
+ strbuf_repo_worktree_path(&gitdir, superproject, "%s/.git", sub->path);
+ strbuf_repo_worktree_path(&worktree, superproject, "%s", sub->path);
- if (repo_init(submodule, gitdir.buf, worktree.buf)) {
+ if (repo_init(subrepo, gitdir.buf, worktree.buf)) {
/*
* If initilization fails then it may be due to the submodule
* not being populated in the superproject's worktree. Instead
strbuf_repo_git_path(&gitdir, superproject,
"modules/%s", sub->name);
- if (repo_init(submodule, gitdir.buf, NULL)) {
+ if (repo_init(subrepo, gitdir.buf, NULL)) {
ret = -1;
goto out;
}
}
- submodule->submodule_prefix = xstrfmt("%s%s/",
- superproject->submodule_prefix ?
- superproject->submodule_prefix :
- "", path);
+ subrepo->submodule_prefix = xstrfmt("%s%s/",
+ superproject->submodule_prefix ?
+ superproject->submodule_prefix :
+ "", sub->path);
out:
strbuf_release(&gitdir);
return read_index_from(repo->index, repo->index_file, repo->gitdir);
}
+
+int repo_hold_locked_index(struct repository *repo,
+ struct lock_file *lf,
+ int flags)
+{
+ if (!repo->index_file)
+ BUG("the repo hasn't been setup");
+ return hold_lock_file_for_update(lf, repo->index_file, flags);
+}
struct config_set;
struct git_hash_algo;
struct index_state;
+struct lock_file;
+struct pathspec;
struct raw_object_store;
struct submodule_cache;
void repo_set_hash_algo(struct repository *repo, int algo);
void initialize_the_repository(void);
int repo_init(struct repository *r, const char *gitdir, const char *worktree);
-int repo_submodule_init(struct repository *submodule,
+
+/*
+ * Initialize the repository 'subrepo' as the submodule given by the
+ * struct submodule 'sub' in parent repository 'superproject'.
+ * Return 0 upon success and a non-zero value upon failure, which may happen
+ * if the submodule is not found, or 'sub' is NULL.
+ */
+struct submodule;
+int repo_submodule_init(struct repository *subrepo,
struct repository *superproject,
- const char *path);
+ const struct submodule *sub);
void repo_clear(struct repository *repo);
/*
* populated then the number of entries will simply be returned.
*/
int repo_read_index(struct repository *repo);
+int repo_hold_locked_index(struct repository *repo,
+ struct lock_file *lf,
+ int flags);
+
+int repo_read_index_preload(struct repository *,
+ const struct pathspec *pathspec,
+ unsigned refresh_flags);
+int repo_read_index_unmerged(struct repository *);
+/*
+ * Opportunistically update the index but do not complain if we can't.
+ * The lockfile is always committed or rolled back.
+ */
+void repo_update_index_if_able(struct repository *, struct lock_file *);
+
#endif /* REPOSITORY_H */
* work on (i.e. what is left by the previous invocation of "git
* rerere" during the current conflict resolution session).
*/
-static void read_rr(struct string_list *rr)
+static void read_rr(struct repository *r, struct string_list *rr)
{
struct strbuf buf = STRBUF_INIT;
- FILE *in = fopen_or_warn(git_path_merge_rr(the_repository), "r");
+ FILE *in = fopen_or_warn(git_path_merge_rr(r), "r");
if (!in)
return;
{
int i;
- if (read_index(r->index) < 0)
+ if (repo_read_index(r) < 0)
return error(_("index file corrupt"));
for (i = 0; i < r->index->cache_nr;) {
{
int i;
- if (setup_rerere(merge_rr, RERERE_READONLY))
+ if (setup_rerere(r, merge_rr, RERERE_READONLY))
return 0;
- if (read_index(r->index) < 0)
+ if (repo_read_index(r) < 0)
return error(_("index file corrupt"));
for (i = 0; i < r->index->cache_nr;) {
struct lock_file index_lock = LOCK_INIT;
int i;
- hold_locked_index(&index_lock, LOCK_DIE_ON_ERROR);
+ repo_hold_locked_index(r, &index_lock, LOCK_DIE_ON_ERROR);
for (i = 0; i < update->nr; i++) {
struct string_list_item *item = &update->items[i];
return 1;
}
-int setup_rerere(struct string_list *merge_rr, int flags)
+int setup_rerere(struct repository *r, struct string_list *merge_rr, int flags)
{
int fd;
fd = 0;
else
fd = hold_lock_file_for_update(&write_lock,
- git_path_merge_rr(the_repository),
+ git_path_merge_rr(r),
LOCK_DIE_ON_ERROR);
- read_rr(merge_rr);
+ read_rr(r, merge_rr);
return fd;
}
struct string_list merge_rr = STRING_LIST_INIT_DUP;
int fd, status;
- fd = setup_rerere(&merge_rr, flags);
+ fd = setup_rerere(r, &merge_rr, flags);
if (fd < 0)
return 0;
status = do_plain_rerere(r, &merge_rr, fd);
struct string_list conflict = STRING_LIST_INIT_DUP;
struct string_list merge_rr = STRING_LIST_INIT_DUP;
- if (read_index(r->index) < 0)
+ if (repo_read_index(r) < 0)
return error(_("index file corrupt"));
- fd = setup_rerere(&merge_rr, RERERE_NOAUTOUPDATE);
+ fd = setup_rerere(r, &merge_rr, RERERE_NOAUTOUPDATE);
if (fd < 0)
return 0;
unlink_rr_item(id);
}
-void rerere_gc(struct string_list *rr)
+void rerere_gc(struct repository *r, struct string_list *rr)
{
struct string_list to_remove = STRING_LIST_INIT_DUP;
DIR *dir;
timestamp_t cutoff_noresolve = now - 15 * 86400;
timestamp_t cutoff_resolve = now - 60 * 86400;
- if (setup_rerere(rr, 0) < 0)
+ if (setup_rerere(r, rr, 0) < 0)
return;
git_config_get_expiry_in_days("gc.rerereresolved", &cutoff_resolve, now);
*
* NEEDSWORK: shouldn't we be calling this from "reset --hard"?
*/
-void rerere_clear(struct string_list *merge_rr)
+void rerere_clear(struct repository *r, struct string_list *merge_rr)
{
int i;
- if (setup_rerere(merge_rr, 0) < 0)
+ if (setup_rerere(r, merge_rr, 0) < 0)
return;
for (i = 0; i < merge_rr->nr; i++) {
rmdir(rerere_path(id, NULL));
}
}
- unlink_or_warn(git_path_merge_rr(the_repository));
+ unlink_or_warn(git_path_merge_rr(r));
rollback_lock_file(&write_lock);
}
int variant;
};
-int setup_rerere(struct string_list *, int);
+int setup_rerere(struct repository *,struct string_list *, int);
#ifndef NO_THE_REPOSITORY_COMPATIBILITY_MACROS
#define rerere(flags) repo_rerere(the_repository, flags)
#endif
const char *rerere_path(const struct rerere_id *, const char *file);
int rerere_forget(struct repository *, struct pathspec *);
int rerere_remaining(struct repository *, struct string_list *);
-void rerere_clear(struct string_list *);
-void rerere_gc(struct string_list *);
+void rerere_clear(struct repository *, struct string_list *);
+void rerere_gc(struct repository *, struct string_list *);
#define OPT_RERERE_AUTOUPDATE(v) OPT_UYN(0, "rerere-autoupdate", (v), \
N_("update the index with reused conflict resolution if possible"))
#include "commit-reach.h"
#include "commit-graph.h"
#include "prio-queue.h"
+#include "hashmap.h"
volatile show_early_output_fn_t show_early_output;
while (tree_entry(&desc, &entry)) {
switch (object_type(entry.mode)) {
case OBJ_TREE:
- mark_tree_uninteresting(r, lookup_tree(r, entry.oid));
+ mark_tree_uninteresting(r, lookup_tree(r, &entry.oid));
break;
case OBJ_BLOB:
- mark_blob_uninteresting(lookup_blob(r, entry.oid));
+ mark_blob_uninteresting(lookup_blob(r, &entry.oid));
break;
default:
/* Subproject commit - not in this repository */
mark_tree_contents_uninteresting(r, tree);
}
+struct path_and_oids_entry {
+ struct hashmap_entry ent;
+ char *path;
+ struct oidset trees;
+};
+
+static int path_and_oids_cmp(const void *hashmap_cmp_fn_data,
+ const struct path_and_oids_entry *e1,
+ const struct path_and_oids_entry *e2,
+ const void *keydata)
+{
+ return strcmp(e1->path, e2->path);
+}
+
+static void paths_and_oids_init(struct hashmap *map)
+{
+ hashmap_init(map, (hashmap_cmp_fn) path_and_oids_cmp, NULL, 0);
+}
+
+static void paths_and_oids_clear(struct hashmap *map)
+{
+ struct hashmap_iter iter;
+ struct path_and_oids_entry *entry;
+ hashmap_iter_init(map, &iter);
+
+ while ((entry = (struct path_and_oids_entry *)hashmap_iter_next(&iter))) {
+ oidset_clear(&entry->trees);
+ free(entry->path);
+ }
+
+ hashmap_free(map, 1);
+}
+
+static void paths_and_oids_insert(struct hashmap *map,
+ const char *path,
+ const struct object_id *oid)
+{
+ int hash = strhash(path);
+ struct path_and_oids_entry key;
+ struct path_and_oids_entry *entry;
+
+ hashmap_entry_init(&key, hash);
+
+ /* use a shallow copy for the lookup */
+ key.path = (char *)path;
+ oidset_init(&key.trees, 0);
+
+ if (!(entry = (struct path_and_oids_entry *)hashmap_get(map, &key, NULL))) {
+ entry = xcalloc(1, sizeof(struct path_and_oids_entry));
+ hashmap_entry_init(entry, hash);
+ entry->path = xstrdup(key.path);
+ oidset_init(&entry->trees, 16);
+ hashmap_put(map, entry);
+ }
+
+ oidset_insert(&entry->trees, oid);
+}
+
+static void add_children_by_path(struct repository *r,
+ struct tree *tree,
+ struct hashmap *map)
+{
+ struct tree_desc desc;
+ struct name_entry entry;
+
+ if (!tree)
+ return;
+
+ if (parse_tree_gently(tree, 1) < 0)
+ return;
+
+ init_tree_desc(&desc, tree->buffer, tree->size);
+ while (tree_entry(&desc, &entry)) {
+ switch (object_type(entry.mode)) {
+ case OBJ_TREE:
+ paths_and_oids_insert(map, entry.path, &entry.oid);
+
+ if (tree->object.flags & UNINTERESTING) {
+ struct tree *child = lookup_tree(r, &entry.oid);
+ if (child)
+ child->object.flags |= UNINTERESTING;
+ }
+ break;
+ case OBJ_BLOB:
+ if (tree->object.flags & UNINTERESTING) {
+ struct blob *child = lookup_blob(r, &entry.oid);
+ if (child)
+ child->object.flags |= UNINTERESTING;
+ }
+ break;
+ default:
+ /* Subproject commit - not in this repository */
+ break;
+ }
+ }
+
+ free_tree_buffer(tree);
+}
+
+void mark_trees_uninteresting_sparse(struct repository *r,
+ struct oidset *trees)
+{
+ unsigned has_interesting = 0, has_uninteresting = 0;
+ struct hashmap map;
+ struct hashmap_iter map_iter;
+ struct path_and_oids_entry *entry;
+ struct object_id *oid;
+ struct oidset_iter iter;
+
+ oidset_iter_init(trees, &iter);
+ while ((!has_interesting || !has_uninteresting) &&
+ (oid = oidset_iter_next(&iter))) {
+ struct tree *tree = lookup_tree(r, oid);
+
+ if (!tree)
+ continue;
+
+ if (tree->object.flags & UNINTERESTING)
+ has_uninteresting = 1;
+ else
+ has_interesting = 1;
+ }
+
+ /* Do not walk unless we have both types of trees. */
+ if (!has_uninteresting || !has_interesting)
+ return;
+
+ paths_and_oids_init(&map);
+
+ oidset_iter_init(trees, &iter);
+ while ((oid = oidset_iter_next(&iter))) {
+ struct tree *tree = lookup_tree(r, oid);
+ add_children_by_path(r, tree, &map);
+ }
+
+ hashmap_iter_init(&map, &map_iter);
+ while ((entry = hashmap_iter_next(&map_iter)))
+ mark_trees_uninteresting_sparse(r, &entry->trees);
+
+ paths_and_oids_clear(&map);
+}
+
struct commit_stack {
struct commit **items;
size_t nr, alloc;
{
struct object *object;
- object = parse_object(revs->repo, oid);
+ /*
+ * If the repository has commit graphs, repo_parse_commit() avoids
+ * reading the object buffer, so use it whenever possible.
+ */
+ if (oid_object_info(revs->repo, oid, NULL) == OBJ_COMMIT) {
+ struct commit *c = lookup_commit(revs->repo, oid);
+ if (!repo_parse_commit(revs->repo, c))
+ object = (struct object *) c;
+ else
+ object = NULL;
+ } else {
+ object = parse_object(revs->repo, oid);
+ }
+
if (!object) {
if (revs->ignore_missing)
return object;
{
struct worktree **worktrees, **p;
- read_index(revs->repo->index);
+ repo_read_index(revs->repo);
do_add_index_objects_to_pending(revs, revs->repo->index, flags);
if (revs->single_worktree)
revs->abbrev = DEFAULT_ABBREV;
revs->ignore_merges = 1;
revs->simplify_history = 1;
+ revs->pruning.repo = r;
revs->pruning.flags.recursive = 1;
revs->pruning.flags.quick = 1;
revs->pruning.add_remove = file_add_remove;
}
static void add_pending_commit_list(struct rev_info *revs,
- struct commit_list *commit_list,
- unsigned int flags)
+ struct commit_list *commit_list,
+ unsigned int flags)
{
while (commit_list) {
struct object *object = &commit_list->item->object;
head->object.flags |= SYMMETRIC_LEFT;
if (!istate->cache_nr)
- read_index(istate);
+ repo_read_index(revs->repo);
for (i = 0; i < istate->cache_nr; i++) {
const struct cache_entry *ce = istate->cache[i];
if (!ce_stage(ce))
if (!*b_name)
b_name = "HEAD";
- if (get_oid_with_context(a_name, oc_flags, &a_oid, a_oc) ||
- get_oid_with_context(b_name, oc_flags, &b_oid, b_oc))
+ if (get_oid_with_context(revs->repo, a_name, oc_flags, &a_oid, a_oc) ||
+ get_oid_with_context(revs->repo, b_name, oc_flags, &b_oid, b_oc))
return -1;
if (!cant_be_filename) {
if (revarg_opt & REVARG_COMMITTISH)
get_sha1_flags |= GET_OID_COMMITTISH;
- if (get_oid_with_context(arg, get_sha1_flags, &oid, &oc))
+ if (get_oid_with_context(revs->repo, arg, get_sha1_flags, &oid, &oc))
return revs->ignore_missing ? 0 : -1;
if (!cant_be_filename)
verify_non_filename(revs->prefix, arg);
object = get_reference(revs, arg, &oid, flags ^ local_flags);
+ if (!object)
+ return revs->ignore_missing ? 0 : -1;
add_rev_cmdline(revs, object, arg_, REV_CMD_REV, flags ^ local_flags);
add_pending_object_with_path(revs, object, arg, oc.mode, oc.path);
free(oc.path);
}
static int handle_revision_opt(struct rev_info *revs, int argc, const char **argv,
- int *unkc, const char **unkv)
+ int *unkc, const char **unkv,
+ const struct setup_revision_opt* opt)
{
const char *arg = argv[0];
const char *optarg;
revs->limited = 1;
} else if (!strcmp(arg, "--ignore-missing")) {
revs->ignore_missing = 1;
- } else if (revs->allow_exclude_promisor_objects_opt &&
+ } else if (opt && opt->allow_exclude_promisor_objects &&
!strcmp(arg, "--exclude-promisor-objects")) {
if (fetch_if_missing)
BUG("exclude_promisor_objects can only be used when fetch_if_missing is 0");
const char * const usagestr[])
{
int n = handle_revision_opt(revs, ctx->argc, ctx->argv,
- &ctx->cpidx, ctx->out);
+ &ctx->cpidx, ctx->out, NULL);
if (n <= 0) {
error("unknown option `%s'", ctx->argv[0]);
usage_with_options(usagestr, options);
continue;
}
- opts = handle_revision_opt(revs, argc - i, argv + i, &left, argv);
+ opts = handle_revision_opt(revs, argc - i, argv + i,
+ &left, argv, opt);
if (opts > 0) {
i += opts - 1;
continue;
struct object_id oid;
struct object *object;
struct object_context oc;
- if (get_oid_with_context(revs->def, 0, &oid, &oc))
+ if (get_oid_with_context(revs->repo, revs->def, 0, &oid, &oc))
diagnose_missing_default(revs->def);
object = get_reference(revs, revs->def, &oid, 0);
add_pending_object_with_mode(revs, object, revs->def, oc.mode);
#define REVISION_WALK_NO_WALK_SORTED 1
#define REVISION_WALK_NO_WALK_UNSORTED 2
+struct oidset;
struct topo_walk_info;
struct rev_info {
do_not_die_on_missing_tree:1,
/* for internal use only */
- allow_exclude_promisor_objects_opt:1,
exclude_promisor_objects:1;
/* Diff flags */
const char *def;
void (*tweak)(struct rev_info *, struct setup_revision_opt *);
const char *submodule; /* TODO: drop this and use rev_info->repo */
- int assume_dashdash;
+ unsigned int assume_dashdash:1,
+ allow_exclude_promisor_objects:1;
unsigned revarg_opt;
};
void mark_parents_uninteresting(struct commit *commit);
void mark_tree_uninteresting(struct repository *r, struct tree *tree);
+void mark_trees_uninteresting_sparse(struct repository *r, struct oidset *trees);
void show_object_with_name(FILE *, struct object *, const char *);
return 0;
}
-static int receive_unpack_status(int in)
+static int receive_unpack_status(struct packet_reader *reader)
{
- const char *line = packet_read_line(in, NULL);
- if (!line)
+ if (packet_reader_read(reader) != PACKET_READ_NORMAL)
return error(_("unexpected flush packet while reading remote unpack status"));
- if (!skip_prefix(line, "unpack ", &line))
- return error(_("unable to parse remote unpack status: %s"), line);
- if (strcmp(line, "ok"))
- return error(_("remote unpack failed: %s"), line);
+ if (!skip_prefix(reader->line, "unpack ", &reader->line))
+ return error(_("unable to parse remote unpack status: %s"), reader->line);
+ if (strcmp(reader->line, "ok"))
+ return error(_("remote unpack failed: %s"), reader->line);
return 0;
}
-static int receive_status(int in, struct ref *refs)
+static int receive_status(struct packet_reader *reader, struct ref *refs)
{
struct ref *hint;
int ret;
hint = NULL;
- ret = receive_unpack_status(in);
+ ret = receive_unpack_status(reader);
while (1) {
- char *refname;
+ const char *refname;
char *msg;
- char *line = packet_read_line(in, NULL);
- if (!line)
+ if (packet_reader_read(reader) != PACKET_READ_NORMAL)
break;
- if (!starts_with(line, "ok ") && !starts_with(line, "ng ")) {
- error("invalid ref status from remote: %s", line);
+ if (!starts_with(reader->line, "ok ") && !starts_with(reader->line, "ng ")) {
+ error("invalid ref status from remote: %s", reader->line);
ret = -1;
break;
}
- refname = line + 3;
+ refname = reader->line + 3;
msg = strchr(refname, ' ');
if (msg)
*msg++ = '\0';
continue;
}
- if (line[0] == 'o' && line[1] == 'k')
+ if (reader->line[0] == 'o' && reader->line[1] == 'k')
hint->status = REF_STATUS_OK;
else {
hint->status = REF_STATUS_REMOTE_REJECT;
int ret;
struct async demux;
const char *push_cert_nonce = NULL;
+ struct packet_reader reader;
/* Does the other end support the reporting? */
if (server_supports("report-status"))
in = demux.out;
}
+ packet_reader_init(&reader, in, NULL, 0,
+ PACKET_READ_CHOMP_NEWLINE |
+ PACKET_READ_DIE_ON_ERR_PACKET);
+
if (need_pack_data && cmds_sent) {
if (pack_objects(out, remote_refs, extra_have, args) < 0) {
for (ref = remote_refs; ref; ref = ref->next)
* are failing, and just want the error() side effects.
*/
if (status_report)
- receive_unpack_status(in);
+ receive_unpack_status(&reader);
if (use_sideband) {
close(demux.out);
packet_flush(out);
if (status_report && cmds_sent)
- ret = receive_status(in, remote_refs);
+ ret = receive_status(&reader, remote_refs);
else
ret = 0;
if (args->stateless_rpc)
static GIT_PATH_FUNC(rebase_path_gpg_sign_opt, "rebase-merge/gpg_sign_opt")
static GIT_PATH_FUNC(rebase_path_orig_head, "rebase-merge/orig-head")
static GIT_PATH_FUNC(rebase_path_verbose, "rebase-merge/verbose")
+static GIT_PATH_FUNC(rebase_path_quiet, "rebase-merge/quiet")
static GIT_PATH_FUNC(rebase_path_signoff, "rebase-merge/signoff")
static GIT_PATH_FUNC(rebase_path_head_name, "rebase-merge/head-name")
static GIT_PATH_FUNC(rebase_path_onto, "rebase-merge/onto")
static GIT_PATH_FUNC(rebase_path_strategy, "rebase-merge/strategy")
static GIT_PATH_FUNC(rebase_path_strategy_opts, "rebase-merge/strategy_opts")
static GIT_PATH_FUNC(rebase_path_allow_rerere_autoupdate, "rebase-merge/allow_rerere_autoupdate")
-static GIT_PATH_FUNC(rebase_path_quiet, "rebase-merge/quiet")
+static GIT_PATH_FUNC(rebase_path_reschedule_failed_exec, "rebase-merge/reschedule-failed-exec")
static int git_sequencer_config(const char *k, const char *v, void *cb)
{
unuse_commit_buffer(commit, msg->message);
}
-static void print_advice(int show_hint, struct replay_opts *opts)
+static void print_advice(struct repository *r, int show_hint,
+ struct replay_opts *opts)
{
char *msg = getenv("GIT_CHERRY_PICK_HELP");
* (typically rebase --interactive) wants to take care
* of the commit itself so remove CHERRY_PICK_HEAD
*/
- unlink(git_path_cherry_pick_head(the_repository));
+ unlink(git_path_cherry_pick_head(r));
return;
}
return 1;
}
-static struct tree *empty_tree(void)
+static struct tree *empty_tree(struct repository *r)
{
- return lookup_tree(the_repository, the_repository->hash_algo->empty_tree);
+ return lookup_tree(r, the_hash_algo->empty_tree);
}
-static int error_dirty_index(struct replay_opts *opts)
+static int error_dirty_index(struct repository *repo, struct replay_opts *opts)
{
- if (read_cache_unmerged())
+ if (repo_read_index_unmerged(repo))
return error_resolve_conflict(_(action_name(opts)));
error(_("your local changes would be overwritten by %s."),
write_file(git_path_abort_safety_file(), "%s", "");
}
-static int fast_forward_to(const struct object_id *to, const struct object_id *from,
- int unborn, struct replay_opts *opts)
+static int fast_forward_to(struct repository *r,
+ const struct object_id *to,
+ const struct object_id *from,
+ int unborn,
+ struct replay_opts *opts)
{
struct ref_transaction *transaction;
struct strbuf sb = STRBUF_INIT;
struct strbuf err = STRBUF_INIT;
- read_index(&the_index);
- if (checkout_fast_forward(the_repository, from, to, 1))
+ repo_read_index(r);
+ if (checkout_fast_forward(r, from, to, 1))
return -1; /* the callee should have complained already */
strbuf_addf(&sb, _("%s: fast-forward"), _(action_name(opts)));
return 0;
}
-void append_conflicts_hint(struct strbuf *msgbuf)
+void append_conflicts_hint(struct index_state *istate,
+ struct strbuf *msgbuf)
{
int i;
strbuf_addch(msgbuf, '\n');
strbuf_commented_addf(msgbuf, "Conflicts:\n");
- for (i = 0; i < active_nr;) {
- const struct cache_entry *ce = active_cache[i++];
+ for (i = 0; i < istate->cache_nr;) {
+ const struct cache_entry *ce = istate->cache[i++];
if (ce_stage(ce)) {
strbuf_commented_addf(msgbuf, "\t%s\n", ce->name);
- while (i < active_nr && !strcmp(ce->name,
- active_cache[i]->name))
+ while (i < istate->cache_nr &&
+ !strcmp(ce->name, istate->cache[i]->name))
i++;
}
}
}
-static int do_recursive_merge(struct commit *base, struct commit *next,
+static int do_recursive_merge(struct repository *r,
+ struct commit *base, struct commit *next,
const char *base_label, const char *next_label,
struct object_id *head, struct strbuf *msgbuf,
struct replay_opts *opts)
char **xopt;
struct lock_file index_lock = LOCK_INIT;
- if (hold_locked_index(&index_lock, LOCK_REPORT_ON_ERROR) < 0)
+ if (repo_hold_locked_index(r, &index_lock, LOCK_REPORT_ON_ERROR) < 0)
return -1;
- read_cache();
+ repo_read_index(r);
- init_merge_options(&o);
+ init_merge_options(&o, r);
o.ancestor = base ? base_label : "(empty tree)";
o.branch1 = "HEAD";
o.branch2 = next ? next_label : "(empty tree)";
o.show_rename_progress = 1;
head_tree = parse_tree_indirect(head);
- next_tree = next ? get_commit_tree(next) : empty_tree();
- base_tree = base ? get_commit_tree(base) : empty_tree();
+ next_tree = next ? get_commit_tree(next) : empty_tree(r);
+ base_tree = base ? get_commit_tree(base) : empty_tree(r);
for (xopt = opts->xopts; xopt != opts->xopts + opts->xopts_nr; xopt++)
parse_merge_opt(&o, *xopt);
return clean;
}
- if (write_locked_index(&the_index, &index_lock,
+ if (write_locked_index(r->index, &index_lock,
COMMIT_LOCK | SKIP_IF_UNCHANGED))
/*
* TRANSLATORS: %s will be "revert", "cherry-pick" or
_(action_name(opts)));
if (!clean)
- append_conflicts_hint(msgbuf);
+ append_conflicts_hint(r->index, msgbuf);
return !clean;
}
-static struct object_id *get_cache_tree_oid(void)
+static struct object_id *get_cache_tree_oid(struct index_state *istate)
{
- if (!active_cache_tree)
- active_cache_tree = cache_tree();
+ if (!istate->cache_tree)
+ istate->cache_tree = cache_tree();
- if (!cache_tree_fully_valid(active_cache_tree))
- if (cache_tree_update(&the_index, 0)) {
+ if (!cache_tree_fully_valid(istate->cache_tree))
+ if (cache_tree_update(istate, 0)) {
error(_("unable to update cache tree"));
return NULL;
}
- return &active_cache_tree->oid;
+ return &istate->cache_tree->oid;
}
-static int is_index_unchanged(void)
+static int is_index_unchanged(struct repository *r)
{
struct object_id head_oid, *cache_tree_oid;
struct commit *head_commit;
+ struct index_state *istate = r->index;
if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, &head_oid, NULL))
return error(_("could not resolve HEAD commit"));
- head_commit = lookup_commit(the_repository, &head_oid);
+ head_commit = lookup_commit(r, &head_oid);
/*
* If head_commit is NULL, check_commit, called from
if (parse_commit(head_commit))
return -1;
- if (!(cache_tree_oid = get_cache_tree_oid()))
+ if (!(cache_tree_oid = get_cache_tree_oid(istate)))
return -1;
return oideq(cache_tree_oid, get_commit_tree_oid(head_commit));
* interactive rebase: in that case, we will want to retain the
* author metadata.
*/
-static int run_git_commit(const char *defmsg, struct replay_opts *opts,
+static int run_git_commit(struct repository *r,
+ const char *defmsg,
+ struct replay_opts *opts,
unsigned int flags)
{
struct child_process cmd = CHILD_PROCESS_INIT;
if (!defmsg)
BUG("root commit without message");
- if (!(cache_tree_oid = get_cache_tree_oid()))
+ if (!(cache_tree_oid = get_cache_tree_oid(r->index)))
res = -1;
if (!res)
return finish_command(&proc);
}
-void commit_post_rewrite(const struct commit *old_head,
+void commit_post_rewrite(struct repository *r,
+ const struct commit *old_head,
const struct object_id *new_head)
{
struct notes_rewrite_cfg *cfg;
if (cfg) {
/* we are amending, so old_head is not NULL */
copy_note_for_rewrite(cfg, &old_head->object.oid, new_head);
- finish_copy_notes_for_rewrite(cfg, "Notes added by 'git commit --amend'");
+ finish_copy_notes_for_rewrite(r, cfg, "Notes added by 'git commit --amend'");
}
run_rewrite_hook(&old_head->object.oid, new_head);
}
-static int run_prepare_commit_msg_hook(struct strbuf *msg, const char *commit)
+static int run_prepare_commit_msg_hook(struct repository *r,
+ struct strbuf *msg,
+ const char *commit)
{
struct argv_array hook_env = ARGV_ARRAY_INIT;
int ret;
if (write_message(msg->buf, msg->len, name, 0))
return -1;
- argv_array_pushf(&hook_env, "GIT_INDEX_FILE=%s", get_index_file());
+ argv_array_pushf(&hook_env, "GIT_INDEX_FILE=%s", r->index_file);
argv_array_push(&hook_env, "GIT_EDITOR=:");
if (commit)
ret = run_hook_le(hook_env.argv, "prepare-commit-msg", name,
}
-void print_commit_summary(const char *prefix, const struct object_id *oid,
+void print_commit_summary(struct repository *r,
+ const char *prefix,
+ const struct object_id *oid,
unsigned int flags)
{
struct rev_info rev;
struct strbuf author_ident = STRBUF_INIT;
struct strbuf committer_ident = STRBUF_INIT;
- commit = lookup_commit(the_repository, oid);
+ commit = lookup_commit(r, oid);
if (!commit)
die(_("couldn't look up newly created commit"));
if (parse_commit(commit))
strbuf_release(&author_ident);
strbuf_release(&committer_ident);
- repo_init_revisions(the_repository, &rev, prefix);
+ repo_init_revisions(r, &rev, prefix);
setup_revisions(0, NULL, &rev, NULL);
rev.diff = 1;
strbuf_release(&format);
}
-static int parse_head(struct commit **head)
+static int parse_head(struct repository *r, struct commit **head)
{
struct commit *current_head;
struct object_id oid;
if (get_oid("HEAD", &oid)) {
current_head = NULL;
} else {
- current_head = lookup_commit_reference(the_repository, &oid);
+ current_head = lookup_commit_reference(r, &oid);
if (!current_head)
return error(_("could not parse HEAD"));
if (!oideq(&oid, ¤t_head->object.oid)) {
* 0 - success
* 1 - run 'git commit'
*/
-static int try_to_commit(struct strbuf *msg, const char *author,
+static int try_to_commit(struct repository *r,
+ struct strbuf *msg, const char *author,
struct replay_opts *opts, unsigned int flags,
struct object_id *oid)
{
enum commit_msg_cleanup_mode cleanup;
int res = 0;
- if (parse_head(¤t_head))
+ if (parse_head(r, ¤t_head))
return -1;
if (flags & AMEND_MSG) {
commit_list_insert(current_head, &parents);
}
- if (write_index_as_tree(&tree, &the_index, get_index_file(), 0, NULL)) {
+ if (write_index_as_tree(&tree, r->index, r->index_file, 0, NULL)) {
res = error(_("git write-tree failed to write a tree"));
goto out;
}
}
if (find_hook("prepare-commit-msg")) {
- res = run_prepare_commit_msg_hook(msg, hook_commit);
+ res = run_prepare_commit_msg_hook(r, msg, hook_commit);
if (res)
goto out;
if (strbuf_read_file(&commit_msg, git_path_commit_editmsg(),
}
if (flags & AMEND_MSG)
- commit_post_rewrite(current_head, oid);
+ commit_post_rewrite(r, current_head, oid);
out:
free_commit_extra_headers(extra);
return res;
}
-static int do_commit(const char *msg_file, const char *author,
+static int do_commit(struct repository *r,
+ const char *msg_file, const char *author,
struct replay_opts *opts, unsigned int flags)
{
int res = 1;
"from '%s'"),
msg_file);
- res = try_to_commit(msg_file ? &sb : NULL, author, opts, flags,
- &oid);
+ res = try_to_commit(r, msg_file ? &sb : NULL,
+ author, opts, flags, &oid);
strbuf_release(&sb);
if (!res) {
- unlink(git_path_cherry_pick_head(the_repository));
- unlink(git_path_merge_msg(the_repository));
+ unlink(git_path_cherry_pick_head(r));
+ unlink(git_path_merge_msg(r));
if (!is_rebase_i(opts))
- print_commit_summary(NULL, &oid,
+ print_commit_summary(r, NULL, &oid,
SUMMARY_SHOW_AUTHOR_DATE);
return res;
}
}
if (res == 1)
- return run_git_commit(msg_file, opts, flags);
+ return run_git_commit(r, msg_file, opts, flags);
return res;
}
/*
* Do we run "git commit" with "--allow-empty"?
*/
-static int allow_empty(struct replay_opts *opts, struct commit *commit)
+static int allow_empty(struct repository *r,
+ struct replay_opts *opts,
+ struct commit *commit)
{
int index_unchanged, empty_commit;
if (!opts->allow_empty)
return 0; /* let "git commit" barf as necessary */
- index_unchanged = is_index_unchanged();
+ index_unchanged = is_index_unchanged(r);
if (index_unchanged < 0)
return index_unchanged;
if (!index_unchanged)
}
}
-static int update_squash_messages(enum todo_command command,
- struct commit *commit, struct replay_opts *opts)
+static int update_squash_messages(struct repository *r,
+ enum todo_command command,
+ struct commit *commit,
+ struct replay_opts *opts)
{
struct strbuf buf = STRBUF_INIT;
int res;
if (get_oid("HEAD", &head))
return error(_("need a HEAD to fixup"));
- if (!(head_commit = lookup_commit_reference(the_repository, &head)))
+ if (!(head_commit = lookup_commit_reference(r, &head)))
return error(_("could not read HEAD"));
if (!(head_message = get_commit_buffer(head_commit, NULL)))
return error(_("could not read HEAD's commit message"));
return res;
}
-static void flush_rewritten_pending(void) {
+static void flush_rewritten_pending(void)
+{
struct strbuf buf = STRBUF_INIT;
struct object_id newoid;
FILE *out;
}
static void record_in_rewritten(struct object_id *oid,
- enum todo_command next_command) {
+ enum todo_command next_command)
+{
FILE *out = fopen_or_warn(rebase_path_rewritten_pending(), "a");
if (!out)
flush_rewritten_pending();
}
-static int do_pick_commit(enum todo_command command, struct commit *commit,
- struct replay_opts *opts, int final_fixup)
+static int do_pick_commit(struct repository *r,
+ enum todo_command command,
+ struct commit *commit,
+ struct replay_opts *opts,
+ int final_fixup)
{
unsigned int flags = opts->edit ? EDIT_MSG : 0;
- const char *msg_file = opts->edit ? NULL : git_path_merge_msg(the_repository);
+ const char *msg_file = opts->edit ? NULL : git_path_merge_msg(r);
struct object_id head;
struct commit *base, *next, *parent;
const char *base_label, *next_label;
* that represents the "current" state for merge-recursive
* to work on.
*/
- if (write_index_as_tree(&head, &the_index, get_index_file(), 0, NULL))
+ if (write_index_as_tree(&head, r->index, r->index_file, 0, NULL))
return error(_("your index file is unmerged."));
} else {
unborn = get_oid("HEAD", &head);
unborn = 1;
} else if (unborn)
oidcpy(&head, the_hash_algo->empty_tree);
- if (index_differs_from(unborn ? empty_tree_oid_hex() : "HEAD",
+ if (index_differs_from(r, unborn ? empty_tree_oid_hex() : "HEAD",
NULL, 0))
- return error_dirty_index(opts);
+ return error_dirty_index(r, opts);
}
- discard_cache();
+ discard_index(r->index);
if (!commit->parents)
parent = NULL;
return error(_("commit %s does not have parent %d"),
oid_to_hex(&commit->object.oid), opts->mainline);
parent = p->item;
- } else if (0 < opts->mainline)
- return error(_("mainline was specified but commit %s is not a merge."),
- oid_to_hex(&commit->object.oid));
+ } else if (1 < opts->mainline)
+ /*
+ * Non-first parent explicitly specified as mainline for
+ * non-merge commit
+ */
+ return error(_("commit %s does not have parent %d"),
+ oid_to_hex(&commit->object.oid), opts->mainline);
else
parent = commit->parents->item;
(!parent && unborn))) {
if (is_rebase_i(opts))
write_author_script(msg.message);
- res = fast_forward_to(&commit->object.oid, &head, unborn,
+ res = fast_forward_to(r, &commit->object.oid, &head, unborn,
opts);
if (res || command != TODO_REWORD)
goto leave;
if (command == TODO_REWORD)
flags |= EDIT_MSG | VERIFY_MSG;
else if (is_fixup(command)) {
- if (update_squash_messages(command, commit, opts))
+ if (update_squash_messages(r, command, commit, opts))
return -1;
flags |= AMEND_MSG;
if (!final_fixup)
flags |= CLEANUP_MSG;
msg_file = rebase_path_fixup_msg();
} else {
- const char *dest = git_path_squash_msg(the_repository);
+ const char *dest = git_path_squash_msg(r);
unlink(dest);
if (copy_file(dest, rebase_path_squash_msg(), 0666))
return error(_("could not rename '%s' to '%s'"),
rebase_path_squash_msg(), dest);
- unlink(git_path_merge_msg(the_repository));
+ unlink(git_path_merge_msg(r));
msg_file = dest;
flags |= EDIT_MSG;
}
if (is_rebase_i(opts) && write_author_script(msg.message) < 0)
res = -1;
else if (!opts->strategy || !strcmp(opts->strategy, "recursive") || command == TODO_REVERT) {
- res = do_recursive_merge(base, next, base_label, next_label,
+ res = do_recursive_merge(r, base, next, base_label, next_label,
&head, &msgbuf, opts);
if (res < 0)
goto leave;
res |= write_message(msgbuf.buf, msgbuf.len,
- git_path_merge_msg(the_repository), 0);
+ git_path_merge_msg(r), 0);
} else {
struct commit_list *common = NULL;
struct commit_list *remotes = NULL;
res = write_message(msgbuf.buf, msgbuf.len,
- git_path_merge_msg(the_repository), 0);
+ git_path_merge_msg(r), 0);
commit_list_insert(base, &common);
commit_list_insert(next, &remotes);
- res |= try_merge_command(the_repository, opts->strategy,
+ res |= try_merge_command(r, opts->strategy,
opts->xopts_nr, (const char **)opts->xopts,
common, oid_to_hex(&head), remotes);
free_commit_list(common);
? _("could not revert %s... %s")
: _("could not apply %s... %s"),
short_commit_name(commit), msg.subject);
- print_advice(res == 1, opts);
- repo_rerere(the_repository, opts->allow_rerere_auto);
+ print_advice(r, res == 1, opts);
+ repo_rerere(r, opts->allow_rerere_auto);
goto leave;
}
- allow = allow_empty(opts, commit);
+ allow = allow_empty(r, opts, commit);
if (allow < 0) {
res = allow;
goto leave;
if (!opts->no_commit) {
fast_forward_edit:
if (author || command == TODO_REVERT || (flags & AMEND_MSG))
- res = do_commit(msg_file, author, opts, flags);
+ res = do_commit(r, msg_file, author, opts, flags);
else
res = error(_("unable to parse commit author"));
}
return 0;
}
-static int read_and_refresh_cache(struct replay_opts *opts)
+static int read_and_refresh_cache(struct repository *r,
+ struct replay_opts *opts)
{
struct lock_file index_lock = LOCK_INIT;
- int index_fd = hold_locked_index(&index_lock, 0);
- if (read_index(&the_index) < 0) {
+ int index_fd = repo_hold_locked_index(r, &index_lock, 0);
+ if (repo_read_index(r) < 0) {
rollback_lock_file(&index_lock);
return error(_("git %s: failed to read the index"),
_(action_name(opts)));
}
- refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, NULL, NULL, NULL);
+ refresh_index(r->index, REFRESH_QUIET|REFRESH_UNMERGED, NULL, NULL, NULL);
if (index_fd >= 0) {
- if (write_locked_index(&the_index, &index_lock,
+ if (write_locked_index(r->index, &index_lock,
COMMIT_LOCK | SKIP_IF_UNCHANGED)) {
return error(_("git %s: failed to refresh the index"),
_(action_name(opts)));
return todo_list->items + todo_list->nr++;
}
-static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)
+static int parse_insn_line(struct repository *r, struct todo_item *item,
+ const char *bol, char *eol)
{
struct object_id commit_oid;
char *end_of_object_name;
if (status < 0)
return -1;
- item->commit = lookup_commit_reference(the_repository, &commit_oid);
+ item->commit = lookup_commit_reference(r, &commit_oid);
return !item->commit;
}
-static int parse_insn_buffer(char *buf, struct todo_list *todo_list)
+static int parse_insn_buffer(struct repository *r, char *buf,
+ struct todo_list *todo_list)
{
struct todo_item *item;
char *p = buf, *next_p;
item = append_new_todo(todo_list);
item->offset_in_buf = p - todo_list->buf.buf;
- if (parse_insn_line(item, p, eol)) {
+ if (parse_insn_line(r, item, p, eol)) {
res = error(_("invalid line %d: %.*s"),
i, (int)(eol - p), p);
item->command = TODO_NOOP;
return len;
}
-static int read_populate_todo(struct todo_list *todo_list,
- struct replay_opts *opts)
+static int read_populate_todo(struct repository *r,
+ struct todo_list *todo_list,
+ struct replay_opts *opts)
{
struct stat st;
const char *todo_file = get_todo_path(opts);
return error(_("could not stat '%s'"), todo_file);
fill_stat_data(&todo_list->stat, &st);
- res = parse_insn_buffer(todo_list->buf.buf, todo_list);
+ res = parse_insn_buffer(r, todo_list->buf.buf, todo_list);
if (res) {
if (is_rebase_i(opts))
return error(_("please fix this using "
FILE *f = fopen_or_warn(rebase_path_msgtotal(), "w");
if (strbuf_read_file(&done.buf, rebase_path_done(), 0) > 0 &&
- !parse_insn_buffer(done.buf.buf, &done))
+ !parse_insn_buffer(r, done.buf.buf, &done))
todo_list->done_nr = count_commands(&done);
else
todo_list->done_nr = 0;
if (file_exists(rebase_path_verbose()))
opts->verbose = 1;
+ if (file_exists(rebase_path_quiet()))
+ opts->quiet = 1;
+
if (file_exists(rebase_path_signoff())) {
opts->allow_ff = 0;
opts->signoff = 1;
}
+ if (file_exists(rebase_path_reschedule_failed_exec()))
+ opts->reschedule_failed_exec = 1;
+
read_strategy_opts(opts, &buf);
strbuf_release(&buf);
if (quiet)
write_file(rebase_path_quiet(), "%s\n", quiet);
- else
- write_file(rebase_path_quiet(), "\n");
-
if (opts->verbose)
write_file(rebase_path_verbose(), "%s", "");
if (opts->strategy)
write_file(rebase_path_gpg_sign_opt(), "-S%s\n", opts->gpg_sign);
if (opts->signoff)
write_file(rebase_path_signoff(), "--signoff\n");
+ if (opts->reschedule_failed_exec)
+ write_file(rebase_path_reschedule_failed_exec(), "%s", "");
return 0;
}
return run_command_v_opt(argv, RUN_GIT_CMD);
}
-static int rollback_single_pick(void)
+static int rollback_single_pick(struct repository *r)
{
struct object_id head_oid;
- if (!file_exists(git_path_cherry_pick_head(the_repository)) &&
- !file_exists(git_path_revert_head(the_repository)))
+ if (!file_exists(git_path_cherry_pick_head(r)) &&
+ !file_exists(git_path_revert_head(r)))
return error(_("no cherry-pick or revert in progress"));
if (read_ref_full("HEAD", 0, &head_oid, NULL))
return error(_("cannot resolve HEAD"));
return reset_for_rollback(&head_oid);
}
-int sequencer_rollback(struct replay_opts *opts)
+int sequencer_rollback(struct repository *r, struct replay_opts *opts)
{
FILE *f;
struct object_id oid;
* If CHERRY_PICK_HEAD or REVERT_HEAD indicates
* a single-cherry-pick in progress, abort that.
*/
- return rollback_single_pick();
+ return rollback_single_pick(r);
}
if (!f)
return error_errno(_("cannot open '%s'"), git_path_head_file());
return res;
}
-static int make_patch(struct commit *commit, struct replay_opts *opts)
+static int make_patch(struct repository *r,
+ struct commit *commit,
+ struct replay_opts *opts)
{
struct strbuf buf = STRBUF_INIT;
struct rev_info log_tree_opt;
strbuf_addf(&buf, "%s/patch", get_dir(opts));
memset(&log_tree_opt, 0, sizeof(log_tree_opt));
- repo_init_revisions(the_repository, &log_tree_opt, NULL);
+ repo_init_revisions(r, &log_tree_opt, NULL);
log_tree_opt.abbrev = 0;
log_tree_opt.diff = 1;
log_tree_opt.diffopt.output_format = DIFF_FORMAT_PATCH;
return write_message(p, strlen(p), rebase_path_amend(), 1);
}
-static int error_with_patch(struct commit *commit,
- const char *subject, int subject_len,
- struct replay_opts *opts, int exit_code, int to_amend)
+static int error_with_patch(struct repository *r,
+ struct commit *commit,
+ const char *subject, int subject_len,
+ struct replay_opts *opts,
+ int exit_code, int to_amend)
{
if (commit) {
- if (make_patch(commit, opts))
+ if (make_patch(r, commit, opts))
return -1;
} else if (copy_file(rebase_path_message(),
- git_path_merge_msg(the_repository), 0666))
+ git_path_merge_msg(r), 0666))
return error(_("unable to copy '%s' to '%s'"),
- git_path_merge_msg(the_repository), rebase_path_message());
+ git_path_merge_msg(r), rebase_path_message());
if (to_amend) {
if (intend_to_amend())
return exit_code;
}
-static int error_failed_squash(struct commit *commit,
- struct replay_opts *opts, int subject_len, const char *subject)
+static int error_failed_squash(struct repository *r,
+ struct commit *commit,
+ struct replay_opts *opts,
+ int subject_len,
+ const char *subject)
{
if (copy_file(rebase_path_message(), rebase_path_squash_msg(), 0666))
return error(_("could not copy '%s' to '%s'"),
rebase_path_squash_msg(), rebase_path_message());
- unlink(git_path_merge_msg(the_repository));
- if (copy_file(git_path_merge_msg(the_repository), rebase_path_message(), 0666))
+ unlink(git_path_merge_msg(r));
+ if (copy_file(git_path_merge_msg(r), rebase_path_message(), 0666))
return error(_("could not copy '%s' to '%s'"),
rebase_path_message(),
- git_path_merge_msg(the_repository));
- return error_with_patch(commit, subject, subject_len, opts, 1, 0);
+ git_path_merge_msg(r));
+ return error_with_patch(r, commit, subject, subject_len, opts, 1, 0);
}
-static int do_exec(const char *command_line)
+static int do_exec(struct repository *r, const char *command_line)
{
struct argv_array child_env = ARGV_ARRAY_INIT;
const char *child_argv[] = { NULL, NULL };
child_env.argv);
/* force re-reading of the cache */
- if (discard_cache() < 0 || read_cache() < 0)
+ if (discard_index(r->index) < 0 || repo_read_index(r) < 0)
return error(_("could not read index"));
- dirty = require_clean_work_tree("rebase", NULL, 1, 1);
+ dirty = require_clean_work_tree(r, "rebase", NULL, 1, 1);
if (status) {
warning(_("execution failed: %s\n%s"
return 0;
}
-static int do_label(const char *name, int len)
+static int do_label(struct repository *r, const char *name, int len)
{
- struct ref_store *refs = get_main_ref_store(the_repository);
+ struct ref_store *refs = get_main_ref_store(r);
struct ref_transaction *transaction;
struct strbuf ref_name = STRBUF_INIT, err = STRBUF_INIT;
struct strbuf msg = STRBUF_INIT;
static const char *reflog_message(struct replay_opts *opts,
const char *sub_action, const char *fmt, ...);
-static int do_reset(const char *name, int len, struct replay_opts *opts)
+static int do_reset(struct repository *r,
+ const char *name, int len,
+ struct replay_opts *opts)
{
struct strbuf ref_name = STRBUF_INIT;
struct object_id oid;
struct unpack_trees_options unpack_tree_opts;
int ret = 0;
- if (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0)
+ if (repo_hold_locked_index(r, &lock, LOCK_REPORT_ON_ERROR) < 0)
return -1;
if (len == 10 && !strncmp("[new root]", name, len)) {
memset(&unpack_tree_opts, 0, sizeof(unpack_tree_opts));
setup_unpack_trees_porcelain(&unpack_tree_opts, "reset");
unpack_tree_opts.head_idx = 1;
- unpack_tree_opts.src_index = &the_index;
- unpack_tree_opts.dst_index = &the_index;
+ unpack_tree_opts.src_index = r->index;
+ unpack_tree_opts.dst_index = r->index;
unpack_tree_opts.fn = oneway_merge;
unpack_tree_opts.merge = 1;
unpack_tree_opts.update = 1;
- if (read_cache_unmerged()) {
+ if (repo_read_index_unmerged(r)) {
rollback_lock_file(&lock);
strbuf_release(&ref_name);
return error_resolve_conflict(_(action_name(opts)));
}
tree = parse_tree_indirect(&oid);
- prime_cache_tree(&the_index, tree);
+ prime_cache_tree(r, r->index, tree);
- if (write_locked_index(&the_index, &lock, COMMIT_LOCK) < 0)
+ if (write_locked_index(r->index, &lock, COMMIT_LOCK) < 0)
ret = error(_("could not write index"));
free((void *)desc.buffer);
return commit;
}
-static int do_merge(struct commit *commit, const char *arg, int arg_len,
+static int do_merge(struct repository *r,
+ struct commit *commit,
+ const char *arg, int arg_len,
int flags, struct replay_opts *opts)
{
int run_commit_flags = (flags & TODO_EDIT_MERGE_MSG) ?
static struct lock_file lock;
const char *p;
- if (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0) {
+ if (repo_hold_locked_index(r, &lock, LOCK_REPORT_ON_ERROR) < 0) {
ret = -1;
goto leave_merge;
}
ret = error(_("octopus merge cannot be executed on "
"top of a [new root]"));
else
- ret = fast_forward_to(&to_merge->item->object.oid,
+ ret = fast_forward_to(r, &to_merge->item->object.oid,
&head_commit->object.oid, 0,
opts);
goto leave_merge;
write_author_script(message);
find_commit_subject(message, &body);
len = strlen(body);
- ret = write_message(body, len, git_path_merge_msg(the_repository), 0);
+ ret = write_message(body, len, git_path_merge_msg(r), 0);
unuse_commit_buffer(commit, message);
if (ret) {
error_errno(_("could not write '%s'"),
- git_path_merge_msg(the_repository));
+ git_path_merge_msg(r));
goto leave_merge;
}
} else {
len = buf.len;
}
- ret = write_message(p, len, git_path_merge_msg(the_repository), 0);
+ ret = write_message(p, len, git_path_merge_msg(r), 0);
strbuf_release(&buf);
if (ret) {
error_errno(_("could not write '%s'"),
- git_path_merge_msg(the_repository));
+ git_path_merge_msg(r));
goto leave_merge;
}
}
if (can_fast_forward) {
rollback_lock_file(&lock);
- ret = fast_forward_to(&commit->object.oid,
+ ret = fast_forward_to(r, &commit->object.oid,
&head_commit->object.oid, 0, opts);
goto leave_merge;
}
argv_array_push(&cmd.args, "--no-log");
argv_array_push(&cmd.args, "--no-stat");
argv_array_push(&cmd.args, "-F");
- argv_array_push(&cmd.args, git_path_merge_msg(the_repository));
+ argv_array_push(&cmd.args, git_path_merge_msg(r));
if (opts->gpg_sign)
argv_array_push(&cmd.args, opts->gpg_sign);
oid_to_hex(&j->item->object.oid));
strbuf_release(&ref_name);
- unlink(git_path_cherry_pick_head(the_repository));
+ unlink(git_path_cherry_pick_head(r));
rollback_lock_file(&lock);
rollback_lock_file(&lock);
ret = run_command(&cmd);
/* force re-reading of the cache */
- if (!ret && (discard_cache() < 0 || read_cache() < 0))
+ if (!ret && (discard_index(r->index) < 0 ||
+ repo_read_index(r) < 0))
ret = error(_("could not read index"));
goto leave_merge;
}
}
write_message(oid_to_hex(&merge_commit->object.oid), GIT_SHA1_HEXSZ,
- git_path_merge_head(the_repository), 0);
- write_message("no-ff", 5, git_path_merge_mode(the_repository), 0);
+ git_path_merge_head(r), 0);
+ write_message("no-ff", 5, git_path_merge_mode(r), 0);
for (j = bases; j; j = j->next)
commit_list_insert(j->item, &reversed);
free_commit_list(bases);
- read_cache();
- init_merge_options(&o);
+ repo_read_index(r);
+ init_merge_options(&o, r);
o.branch1 = "HEAD";
o.branch2 = ref_name.buf;
o.buffer_output = 2;
*/
ret = !ret;
- if (active_cache_changed &&
- write_locked_index(&the_index, &lock, COMMIT_LOCK)) {
+ if (r->index->cache_changed &&
+ write_locked_index(r->index, &lock, COMMIT_LOCK)) {
ret = error(_("merge: Unable to write new index file"));
goto leave_merge;
}
rollback_lock_file(&lock);
if (ret)
- repo_rerere(the_repository, opts->allow_rerere_auto);
+ repo_rerere(r, opts->allow_rerere_auto);
else
/*
* In case of problems, we now want to return a positive
* value (a negative one would indicate that the `merge`
* command needs to be rescheduled).
*/
- ret = !!run_git_commit(git_path_merge_msg(the_repository), opts,
- run_commit_flags);
+ ret = !!run_git_commit(r, git_path_merge_msg(r), opts,
+ run_commit_flags);
leave_merge:
strbuf_release(&ref_name);
return update_ref(NULL, "ORIG_HEAD", &oid, NULL, 0, UPDATE_REFS_MSG_ON_ERR);
}
-static int stopped_at_head(void)
+static int stopped_at_head(struct repository *r)
{
struct object_id head;
struct commit *commit;
struct commit_message message;
if (get_oid("HEAD", &head) ||
- !(commit = lookup_commit(the_repository, &head)) ||
+ !(commit = lookup_commit(r, &head)) ||
parse_commit(commit) || get_message(commit, &message))
fprintf(stderr, _("Stopped at HEAD\n"));
else {
" git rebase --edit-todo\n"
" git rebase --continue\n");
-static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)
+static int pick_commits(struct repository *r,
+ struct todo_list *todo_list,
+ struct replay_opts *opts)
{
int res = 0, reschedule = 0;
if (opts->allow_ff)
assert(!(opts->signoff || opts->no_commit ||
opts->record_origin || opts->edit));
- if (read_and_refresh_cache(opts))
+ if (read_and_refresh_cache(r, opts))
return -1;
while (todo_list->current < todo_list->nr) {
fprintf(f, "%d\n", todo_list->done_nr);
fclose(f);
}
- fprintf(stderr, "Rebasing (%d/%d)%s",
- todo_list->done_nr,
- todo_list->total_nr,
- opts->verbose ? "\n" : "\r");
+ if (!opts->quiet)
+ fprintf(stderr, "Rebasing (%d/%d)%s",
+ todo_list->done_nr,
+ todo_list->total_nr,
+ opts->verbose ? "\n" : "\r");
}
unlink(rebase_path_message());
unlink(rebase_path_author_script());
delete_ref(NULL, "REBASE_HEAD", NULL, REF_NO_DEREF);
if (item->command == TODO_BREAK)
- return stopped_at_head();
+ return stopped_at_head(r);
}
if (item->command <= TODO_SQUASH) {
if (is_rebase_i(opts))
setenv("GIT_REFLOG_ACTION", reflog_message(opts,
command_to_string(item->command), NULL),
1);
- res = do_pick_commit(item->command, item->commit,
+ res = do_pick_commit(r, item->command, item->commit,
opts, is_final_fixup(todo_list));
if (is_rebase_i(opts) && res < 0) {
/* Reschedule */
_("Stopped at %s... %.*s\n"),
short_commit_name(commit),
item->arg_len, item->arg);
- return error_with_patch(commit,
+ return error_with_patch(r, commit,
item->arg, item->arg_len, opts, res,
!res);
}
if (res && is_fixup(item->command)) {
if (res == 1)
intend_to_amend();
- return error_failed_squash(item->commit, opts,
+ return error_failed_squash(r, item->commit, opts,
item->arg_len, item->arg);
} else if (res && is_rebase_i(opts) && item->commit) {
int to_amend = 0;
oideq(&opts->squash_onto, &oid))))
to_amend = 1;
- return res | error_with_patch(item->commit,
+ return res | error_with_patch(r, item->commit,
item->arg, item->arg_len, opts,
res, to_amend);
}
struct stat st;
*end_of_arg = '\0';
- res = do_exec(item->arg);
+ res = do_exec(r, item->arg);
*end_of_arg = saved;
/* Reread the todo file if it has changed. */
- if (res)
- ; /* fall through */
- else if (stat(get_todo_path(opts), &st))
+ if (res) {
+ if (opts->reschedule_failed_exec)
+ reschedule = 1;
+ } else if (stat(get_todo_path(opts), &st))
res = error_errno(_("could not stat '%s'"),
get_todo_path(opts));
else if (match_stat_data(&todo_list->stat, &st)) {
todo_list_release(todo_list);
- if (read_populate_todo(todo_list, opts))
+ if (read_populate_todo(r, todo_list, opts))
res = -1; /* message was printed */
/* `current` will be incremented below */
todo_list->current = -1;
}
} else if (item->command == TODO_LABEL) {
- if ((res = do_label(item->arg, item->arg_len)))
+ if ((res = do_label(r, item->arg, item->arg_len)))
reschedule = 1;
} else if (item->command == TODO_RESET) {
- if ((res = do_reset(item->arg, item->arg_len, opts)))
+ if ((res = do_reset(r, item->arg, item->arg_len, opts)))
reschedule = 1;
} else if (item->command == TODO_MERGE) {
- if ((res = do_merge(item->commit,
+ if ((res = do_merge(r, item->commit,
item->arg, item->arg_len,
item->flags, opts)) < 0)
reschedule = 1;
peek_command(todo_list, 1));
if (res > 0)
/* failed with merge conflicts */
- return error_with_patch(item->commit,
+ return error_with_patch(r, item->commit,
item->arg,
item->arg_len, opts,
res, 0);
if (save_todo(todo_list, opts))
return -1;
if (item->commit)
- return error_with_patch(item->commit,
+ return error_with_patch(r,
+ item->commit,
item->arg,
item->arg_len, opts,
res, 0);
struct object_id orig, head;
memset(&log_tree_opt, 0, sizeof(log_tree_opt));
- repo_init_revisions(the_repository, &log_tree_opt, NULL);
+ repo_init_revisions(r, &log_tree_opt, NULL);
log_tree_opt.diff = 1;
log_tree_opt.diffopt.output_format =
DIFF_FORMAT_DIFFSTAT;
}
apply_autostash(opts);
- fprintf(stderr, "Successfully rebased and updated %s.\n",
- head_ref.buf);
+ if (!opts->quiet)
+ fprintf(stderr,
+ "Successfully rebased and updated %s.\n",
+ head_ref.buf);
strbuf_release(&buf);
strbuf_release(&head_ref);
return sequencer_remove_state(opts);
}
-static int continue_single_pick(void)
+static int continue_single_pick(struct repository *r)
{
const char *argv[] = { "commit", NULL };
- if (!file_exists(git_path_cherry_pick_head(the_repository)) &&
- !file_exists(git_path_revert_head(the_repository)))
+ if (!file_exists(git_path_cherry_pick_head(r)) &&
+ !file_exists(git_path_revert_head(r)))
return error(_("no cherry-pick or revert in progress"));
return run_command_v_opt(argv, RUN_GIT_CMD);
}
-static int commit_staged_changes(struct replay_opts *opts,
+static int commit_staged_changes(struct repository *r,
+ struct replay_opts *opts,
struct todo_list *todo_list)
{
unsigned int flags = ALLOW_EMPTY | EDIT_MSG;
unsigned int final_fixup = 0, is_clean;
- if (has_unstaged_changes(1))
+ if (has_unstaged_changes(r, 1))
return error(_("cannot rebase: You have unstaged changes."));
- is_clean = !has_uncommitted_changes(0);
+ is_clean = !has_uncommitted_changes(r, 0);
if (file_exists(rebase_path_amend())) {
struct strbuf rev = STRBUF_INIT;
struct commit *commit;
const char *path = rebase_path_squash_msg();
- if (parse_head(&commit) ||
+ if (parse_head(r, &commit) ||
!(p = get_commit_buffer(commit, NULL)) ||
write_message(p, strlen(p), path, 0)) {
unuse_commit_buffer(commit, p);
}
if (is_clean) {
- const char *cherry_pick_head = git_path_cherry_pick_head(the_repository);
+ const char *cherry_pick_head = git_path_cherry_pick_head(r);
if (file_exists(cherry_pick_head) && unlink(cherry_pick_head))
return error(_("could not remove CHERRY_PICK_HEAD"));
return 0;
}
- if (run_git_commit(final_fixup ? NULL : rebase_path_message(),
+ if (run_git_commit(r, final_fixup ? NULL : rebase_path_message(),
opts, flags))
return error(_("could not commit staged changes."));
unlink(rebase_path_amend());
return 0;
}
-int sequencer_continue(struct replay_opts *opts)
+int sequencer_continue(struct repository *r, struct replay_opts *opts)
{
struct todo_list todo_list = TODO_LIST_INIT;
int res;
- if (read_and_refresh_cache(opts))
+ if (read_and_refresh_cache(r, opts))
return -1;
if (read_populate_opts(opts))
return -1;
if (is_rebase_i(opts)) {
- if ((res = read_populate_todo(&todo_list, opts)))
+ if ((res = read_populate_todo(r, &todo_list, opts)))
goto release_todo_list;
- if (commit_staged_changes(opts, &todo_list))
+ if (commit_staged_changes(r, opts, &todo_list))
return -1;
} else if (!file_exists(get_todo_path(opts)))
- return continue_single_pick();
- else if ((res = read_populate_todo(&todo_list, opts)))
+ return continue_single_pick(r);
+ else if ((res = read_populate_todo(r, &todo_list, opts)))
goto release_todo_list;
if (!is_rebase_i(opts)) {
/* Verify that the conflict has been resolved */
- if (file_exists(git_path_cherry_pick_head(the_repository)) ||
- file_exists(git_path_revert_head(the_repository))) {
- res = continue_single_pick();
+ if (file_exists(git_path_cherry_pick_head(r)) ||
+ file_exists(git_path_revert_head(r))) {
+ res = continue_single_pick(r);
if (res)
goto release_todo_list;
}
- if (index_differs_from("HEAD", NULL, 0)) {
- res = error_dirty_index(opts);
+ if (index_differs_from(r, "HEAD", NULL, 0)) {
+ res = error_dirty_index(r, opts);
goto release_todo_list;
}
todo_list.current++;
strbuf_release(&buf);
}
- res = pick_commits(&todo_list, opts);
+ res = pick_commits(r, &todo_list, opts);
release_todo_list:
todo_list_release(&todo_list);
return res;
}
-static int single_pick(struct commit *cmit, struct replay_opts *opts)
+static int single_pick(struct repository *r,
+ struct commit *cmit,
+ struct replay_opts *opts)
{
setenv(GIT_REFLOG_ACTION, action_name(opts), 0);
- return do_pick_commit(opts->action == REPLAY_PICK ?
+ return do_pick_commit(r, opts->action == REPLAY_PICK ?
TODO_PICK : TODO_REVERT, cmit, opts, 0);
}
-int sequencer_pick_revisions(struct replay_opts *opts)
+int sequencer_pick_revisions(struct repository *r,
+ struct replay_opts *opts)
{
struct todo_list todo_list = TODO_LIST_INIT;
struct object_id oid;
int i, res;
assert(opts->revs);
- if (read_and_refresh_cache(opts))
+ if (read_and_refresh_cache(r, opts))
return -1;
for (i = 0; i < opts->revs->pending.nr; i++) {
continue;
if (!get_oid(name, &oid)) {
- if (!lookup_commit_reference_gently(the_repository, &oid, 1)) {
- enum object_type type = oid_object_info(the_repository,
+ if (!lookup_commit_reference_gently(r, &oid, 1)) {
+ enum object_type type = oid_object_info(r,
&oid,
NULL);
return error(_("%s: can't cherry-pick a %s"),
return error(_("empty commit set passed"));
if (get_revision(opts->revs))
BUG("unexpected extra commit from walk");
- return single_pick(cmit, opts);
+ return single_pick(r, cmit, opts);
}
/*
if (save_opts(opts))
return -1;
update_abort_safety_file();
- res = pick_commits(&todo_list, opts);
+ res = pick_commits(r, &todo_list, opts);
todo_list_release(&todo_list);
return res;
}
return 0;
}
-int sequencer_make_script(FILE *out, int argc, const char **argv,
+int sequencer_make_script(struct repository *r, FILE *out,
+ int argc, const char **argv,
unsigned flags)
{
char *format = NULL;
const char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? "p" : "pick";
int rebase_merges = flags & TODO_LIST_REBASE_MERGES;
- repo_init_revisions(the_repository, &revs, NULL);
+ repo_init_revisions(r, &revs, NULL);
revs.verbose_header = 1;
if (!rebase_merges)
revs.max_parents = 1;
* Add commands after pick and (series of) squash/fixup commands
* in the todo list.
*/
-int sequencer_add_exec_commands(const char *commands)
+int sequencer_add_exec_commands(struct repository *r,
+ const char *commands)
{
const char *todo_file = rebase_path_todo();
struct todo_list todo_list = TODO_LIST_INIT;
if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0)
return error(_("could not read '%s'."), todo_file);
- if (parse_insn_buffer(todo_list.buf.buf, &todo_list)) {
+ if (parse_insn_buffer(r, todo_list.buf.buf, &todo_list)) {
todo_list_release(&todo_list);
return error(_("unusable todo list: '%s'"), todo_file);
}
return i;
}
-int transform_todos(unsigned flags)
+int transform_todos(struct repository *r, unsigned flags)
{
const char *todo_file = rebase_path_todo();
struct todo_list todo_list = TODO_LIST_INIT;
if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0)
return error(_("could not read '%s'."), todo_file);
- if (parse_insn_buffer(todo_list.buf.buf, &todo_list)) {
+ if (parse_insn_buffer(r, todo_list.buf.buf, &todo_list)) {
todo_list_release(&todo_list);
return error(_("unusable todo list: '%s'"), todo_file);
}
* Check if there is an unrecognized command or a
* bad SHA-1 in a command.
*/
-int check_todo_list(void)
+int check_todo_list(struct repository *r)
{
enum missing_commit_check_level check_level = get_missing_commit_check_level();
struct strbuf todo_file = STRBUF_INIT;
goto leave_check;
}
advise_to_edit_todo = res =
- parse_insn_buffer(todo_list.buf.buf, &todo_list);
+ parse_insn_buffer(r, todo_list.buf.buf, &todo_list);
if (res || check_level == MISSING_COMMIT_CHECK_IGNORE)
goto leave_check;
goto leave_check;
}
strbuf_release(&todo_file);
- res = !!parse_insn_buffer(todo_list.buf.buf, &todo_list);
+ res = !!parse_insn_buffer(r, todo_list.buf.buf, &todo_list);
/* Find commits in git-rebase-todo.backup yet unseen */
for (i = todo_list.nr - 1; i >= 0; i--) {
}
/* skip picking commits whose parents are unchanged */
-static int skip_unnecessary_picks(struct object_id *output_oid)
+static int skip_unnecessary_picks(struct repository *r, struct object_id *output_oid)
{
const char *todo_file = rebase_path_todo();
struct strbuf buf = STRBUF_INIT;
if (strbuf_read_file_or_whine(&todo_list.buf, todo_file) < 0)
return -1;
- if (parse_insn_buffer(todo_list.buf.buf, &todo_list) < 0) {
+ if (parse_insn_buffer(r, todo_list.buf.buf, &todo_list) < 0) {
todo_list_release(&todo_list);
return -1;
}
return 0;
}
-int complete_action(struct replay_opts *opts, unsigned flags,
+int complete_action(struct repository *r, struct replay_opts *opts, unsigned flags,
const char *shortrevisions, const char *onto_name,
const char *onto, const char *orig_head, const char *cmd,
unsigned autosquash)
write_message("noop\n", 5, todo_file, 0))
return -1;
- if (autosquash && rearrange_squash())
+ if (autosquash && rearrange_squash(r))
return -1;
if (cmd && *cmd)
- sequencer_add_exec_commands(cmd);
+ sequencer_add_exec_commands(r, cmd);
if (strbuf_read_file(buf, todo_file, 0) < 0)
return error_errno(_("could not read '%s'."), todo_file);
- if (parse_insn_buffer(buf->buf, &todo_list)) {
+ if (parse_insn_buffer(r, buf->buf, &todo_list)) {
todo_list_release(&todo_list);
return error(_("unusable todo list: '%s'"), todo_file);
}
return error(_("could not copy '%s' to '%s'."), todo_file,
rebase_path_todo_backup());
- if (transform_todos(flags | TODO_LIST_SHORTEN_IDS))
+ if (transform_todos(r, flags | TODO_LIST_SHORTEN_IDS))
return error(_("could not transform the todo list"));
strbuf_reset(buf);
todo_list_release(&todo_list);
- if (check_todo_list()) {
+ if (check_todo_list(r)) {
checkout_onto(opts, onto_name, onto, orig_head);
return -1;
}
- if (transform_todos(flags & ~(TODO_LIST_SHORTEN_IDS)))
+ if (transform_todos(r, flags & ~(TODO_LIST_SHORTEN_IDS)))
return error(_("could not transform the todo list"));
- if (opts->allow_ff && skip_unnecessary_picks(&oid))
+ if (opts->allow_ff && skip_unnecessary_picks(r, &oid))
return error(_("could not skip unnecessary pick commands"));
if (checkout_onto(opts, onto_name, oid_to_hex(&oid), orig_head))
return -1;
- if (require_clean_work_tree("rebase", "", 1, 1))
+ if (require_clean_work_tree(r, "rebase", "", 1, 1))
return -1;
- return sequencer_continue(opts);
+ return sequencer_continue(r, opts);
}
struct subject2item_entry {
* message will have to be retrieved from the commit (as the oneline in the
* script cannot be trusted) in order to normalize the autosquash arrangement.
*/
-int rearrange_squash(void)
+int rearrange_squash(struct repository *r)
{
const char *todo_file = rebase_path_todo();
struct todo_list todo_list = TODO_LIST_INIT;
if (strbuf_read_file_or_whine(&todo_list.buf, todo_file) < 0)
return -1;
- if (parse_insn_buffer(todo_list.buf.buf, &todo_list) < 0) {
+ if (parse_insn_buffer(r, todo_list.buf.buf, &todo_list) < 0) {
todo_list_release(&todo_list);
return -1;
}
#include "strbuf.h"
struct commit;
+struct repository;
const char *git_path_commit_editmsg(void);
const char *git_path_seq_dir(void);
int allow_empty_message;
int keep_redundant_commits;
int verbose;
+ int quiet;
+ int reschedule_failed_exec;
int mainline;
/* Call this to setup defaults before parsing command line options */
void sequencer_init_config(struct replay_opts *opts);
-int sequencer_pick_revisions(struct replay_opts *opts);
-int sequencer_continue(struct replay_opts *opts);
-int sequencer_rollback(struct replay_opts *opts);
+int sequencer_pick_revisions(struct repository *repo,
+ struct replay_opts *opts);
+int sequencer_continue(struct repository *repo, struct replay_opts *opts);
+int sequencer_rollback(struct repository *repo, struct replay_opts *opts);
int sequencer_remove_state(struct replay_opts *opts);
#define TODO_LIST_KEEP_EMPTY (1U << 0)
* commits should be rebased onto the new base, this flag needs to be passed.
*/
#define TODO_LIST_REBASE_COUSINS (1U << 4)
-int sequencer_make_script(FILE *out, int argc, const char **argv,
+int sequencer_make_script(struct repository *repo, FILE *out,
+ int argc, const char **argv,
unsigned flags);
-int sequencer_add_exec_commands(const char *command);
-int transform_todos(unsigned flags);
+int sequencer_add_exec_commands(struct repository *r, const char *command);
+int transform_todos(struct repository *r, unsigned flags);
enum missing_commit_check_level get_missing_commit_check_level(void);
-int check_todo_list(void);
-int complete_action(struct replay_opts *opts, unsigned flags,
+int check_todo_list(struct repository *r);
+int complete_action(struct repository *r, struct replay_opts *opts, unsigned flags,
const char *shortrevisions, const char *onto_name,
const char *onto, const char *orig_head, const char *cmd,
unsigned autosquash);
-int rearrange_squash(void);
+int rearrange_squash(struct repository *r);
extern const char sign_off_header[];
*/
void append_signoff(struct strbuf *msgbuf, size_t ignore_footer, unsigned flag);
-void append_conflicts_hint(struct strbuf *msgbuf);
+void append_conflicts_hint(struct index_state *istate, struct strbuf *msgbuf);
int message_is_empty(const struct strbuf *sb,
enum commit_msg_cleanup_mode cleanup_mode);
int template_untouched(const struct strbuf *sb, const char *template_file,
const struct object_id *new_head,
const char *action, const struct strbuf *msg,
struct strbuf *err);
-void commit_post_rewrite(const struct commit *current_head,
+void commit_post_rewrite(struct repository *r,
+ const struct commit *current_head,
const struct object_id *new_head);
int prepare_branch_to_be_rebased(struct replay_opts *opts, const char *commit);
#define SUMMARY_INITIAL_COMMIT (1 << 0)
#define SUMMARY_SHOW_AUTHOR_DATE (1 << 1)
-void print_commit_summary(const char *prefix, const struct object_id *oid,
+void print_commit_summary(struct repository *repo,
+ const char *prefix,
+ const struct object_id *oid,
unsigned int flags);
int read_author_script(const char *path, char **name, char **email, char **date,
packet_reader_init(&reader, 0, NULL, 0,
PACKET_READ_CHOMP_NEWLINE |
- PACKET_READ_GENTLE_ON_EOF);
+ PACKET_READ_GENTLE_ON_EOF |
+ PACKET_READ_DIE_ON_ERR_PACKET);
/*
* Check to see if the client closed their end before sending another
*/
if (packet_reader_peek(&reader) == PACKET_READ_EOF)
return 1;
- reader.options = PACKET_READ_CHOMP_NEWLINE;
+ reader.options &= ~PACKET_READ_GENTLE_ON_EOF;
while (state != PROCESS_REQUEST_DONE) {
switch (packet_reader_peek(&reader)) {
off = offset_1st_component(path);
/* check if work tree is already the prefix */
- if (wtlen <= len && !strncmp(path, work_tree, wtlen)) {
+ if (wtlen <= len && !fspathncmp(path, work_tree, wtlen)) {
if (path[wtlen] == '/') {
memmove(path, path + wtlen + 1, len - wtlen);
return 0;
path++;
if (*path == '/') {
*path = '\0';
- if (strcmp(real_path(path0), work_tree) == 0) {
+ if (fspathcmp(real_path(path0), work_tree) == 0) {
memmove(path0, path + 1, len - (path - path0));
return 0;
}
}
/* check whole path */
- if (strcmp(real_path(path0), work_tree) == 0) {
+ if (fspathcmp(real_path(path0), work_tree) == 0) {
*path0 = '\0';
return 0;
}
return NULL;
}
-static const char *setup_nongit(const char *cwd, int *nongit_ok)
-{
- if (!nongit_ok)
- die(_("not a git repository (or any of the parent directories): %s"), DEFAULT_GIT_DIR_ENVIRONMENT);
- if (chdir(cwd))
- die_errno(_("cannot come back to cwd"));
- *nongit_ok = 1;
- return NULL;
-}
-
static dev_t get_device_or_die(const char *path, const char *prefix, int prefix_len)
{
struct stat buf;
{
static struct strbuf cwd = STRBUF_INIT;
struct strbuf dir = STRBUF_INIT, gitdir = STRBUF_INIT;
- const char *prefix;
+ const char *prefix = NULL;
struct repository_format repo_fmt;
/*
strbuf_addbuf(&dir, &cwd);
switch (setup_git_directory_gently_1(&dir, &gitdir, 1)) {
- case GIT_DIR_NONE:
- prefix = NULL;
- break;
case GIT_DIR_EXPLICIT:
prefix = setup_explicit_git_dir(gitdir.buf, &cwd, &repo_fmt, nongit_ok);
break;
prefix = setup_bare_git_dir(&cwd, dir.len, &repo_fmt, nongit_ok);
break;
case GIT_DIR_HIT_CEILING:
- prefix = setup_nongit(cwd.buf, nongit_ok);
+ if (!nongit_ok)
+ die(_("not a git repository (or any of the parent directories): %s"),
+ DEFAULT_GIT_DIR_ENVIRONMENT);
+ *nongit_ok = 1;
break;
case GIT_DIR_HIT_MOUNT_POINT:
- if (nongit_ok) {
- *nongit_ok = 1;
- strbuf_release(&cwd);
- strbuf_release(&dir);
- return NULL;
- }
- die(_("not a git repository (or any parent up to mount point %s)\n"
- "Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set)."),
- dir.buf);
+ if (!nongit_ok)
+ die(_("not a git repository (or any parent up to mount point %s)\n"
+ "Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set)."),
+ dir.buf);
+ *nongit_ok = 1;
+ break;
+ case GIT_DIR_NONE:
+ /*
+ * As a safeguard against setup_git_directory_gently_1 returning
+ * this value, fallthrough to BUG. Otherwise it is possible to
+ * set startup_info->have_repository to 1 when we did nothing to
+ * find a repository.
+ */
default:
BUG("unhandled setup_git_directory_1() result");
}
- if (prefix)
- setenv(GIT_PREFIX_ENVIRONMENT, prefix, 1);
- else
+ /*
+ * At this point, nongit_ok is stable. If it is non-NULL and points
+ * to a non-zero value, then this means that we haven't found a
+ * repository and that the caller expects startup_info to reflect
+ * this.
+ *
+ * Regardless of the state of nongit_ok, startup_info->prefix and
+ * the GIT_PREFIX environment variable must always match. For details
+ * see Documentation/config/alias.txt.
+ */
+ if (nongit_ok && *nongit_ok) {
+ startup_info->have_repository = 0;
+ startup_info->prefix = NULL;
setenv(GIT_PREFIX_ENVIRONMENT, "", 1);
-
- startup_info->have_repository = !nongit_ok || !*nongit_ok;
- startup_info->prefix = prefix;
+ } else {
+ startup_info->have_repository = 1;
+ startup_info->prefix = prefix;
+ if (prefix)
+ setenv(GIT_PREFIX_ENVIRONMENT, prefix, 1);
+ else
+ setenv(GIT_PREFIX_ENVIRONMENT, "", 1);
+ }
/*
* Not all paths through the setup code will call 'set_git_dir()' (which
* the user has set GIT_DIR. It may be beneficial to disallow bogus
* GIT_DIR values at some point in the future.
*/
- if (startup_info->have_repository || getenv(GIT_DIR_ENVIRONMENT)) {
+ if (/* GIT_DIR_EXPLICIT, GIT_DIR_DISCOVERED, GIT_DIR_BARE */
+ startup_info->have_repository ||
+ /* GIT_DIR_EXPLICIT */
+ getenv(GIT_DIR_ENVIRONMENT)) {
if (!the_repository->gitdir) {
const char *gitdir = getenv(GIT_DIR_ENVIRONMENT);
if (!gitdir)
}
return 0;
}
+
+void oid_array_filter(struct oid_array *array,
+ for_each_oid_fn want,
+ void *cb_data)
+{
+ unsigned nr = array->nr, src, dst;
+ struct object_id *oids = array->oid;
+
+ for (src = dst = 0; src < nr; src++) {
+ if (want(&oids[src], cb_data)) {
+ if (src != dst)
+ oidcpy(&oids[dst], &oids[src]);
+ dst++;
+ }
+ }
+ array->nr = dst;
+}
int oid_array_for_each_unique(struct oid_array *array,
for_each_oid_fn fn,
void *data);
+void oid_array_filter(struct oid_array *array,
+ for_each_oid_fn want,
+ void *cbdata);
#endif /* SHA1_ARRAY_H */
#define EMPTY_TREE_SHA1_BIN_LITERAL \
"\x4b\x82\x5d\xc6\x42\xcb\x6e\xb9\xa0\x60" \
"\xe5\x4b\xf8\xd6\x92\x88\xfb\xee\x49\x04"
+#define EMPTY_TREE_SHA256_BIN_LITERAL \
+ "\x6e\xf1\x9b\x41\x22\x5c\x53\x69\xf1\xc1" \
+ "\x04\xd4\x5d\x8d\x85\xef\xa9\xb0\x57\xb5" \
+ "\x3b\x14\xb4\xb9\xb9\x39\xdd\x74\xde\xcc" \
+ "\x53\x21"
#define EMPTY_BLOB_SHA1_BIN_LITERAL \
"\xe6\x9d\xe2\x9b\xb2\xd1\xd6\x43\x4b\x8b" \
"\x29\xae\x77\x5a\xd8\xc2\xe4\x8c\x53\x91"
+#define EMPTY_BLOB_SHA256_BIN_LITERAL \
+ "\x47\x3a\x0f\x4c\x3b\xe8\xa9\x36\x81\xa2" \
+ "\x67\xe3\xb1\xe9\xa7\xdc\xda\x11\x85\x43" \
+ "\x6f\xe1\x41\xf7\x74\x91\x20\xa3\x03\x72" \
+ "\x18\x13"
const unsigned char null_sha1[GIT_MAX_RAWSZ];
const struct object_id null_oid;
static const struct object_id empty_blob_oid = {
EMPTY_BLOB_SHA1_BIN_LITERAL
};
+static const struct object_id empty_tree_oid_sha256 = {
+ EMPTY_TREE_SHA256_BIN_LITERAL
+};
+static const struct object_id empty_blob_oid_sha256 = {
+ EMPTY_BLOB_SHA256_BIN_LITERAL
+};
static void git_hash_sha1_init(git_hash_ctx *ctx)
{
git_SHA1_Final(hash, &ctx->sha1);
}
+
+static void git_hash_sha256_init(git_hash_ctx *ctx)
+{
+ git_SHA256_Init(&ctx->sha256);
+}
+
+static void git_hash_sha256_update(git_hash_ctx *ctx, const void *data, size_t len)
+{
+ git_SHA256_Update(&ctx->sha256, data, len);
+}
+
+static void git_hash_sha256_final(unsigned char *hash, git_hash_ctx *ctx)
+{
+ git_SHA256_Final(hash, &ctx->sha256);
+}
+
static void git_hash_unknown_init(git_hash_ctx *ctx)
{
BUG("trying to init unknown hash");
0x00000000,
0,
0,
+ 0,
git_hash_unknown_init,
git_hash_unknown_update,
git_hash_unknown_final,
NULL,
},
{
- "sha-1",
+ "sha1",
/* "sha1", big-endian */
0x73686131,
GIT_SHA1_RAWSZ,
GIT_SHA1_HEXSZ,
+ GIT_SHA1_BLKSZ,
git_hash_sha1_init,
git_hash_sha1_update,
git_hash_sha1_final,
&empty_tree_oid,
&empty_blob_oid,
},
+ {
+ "sha256",
+ /* "s256", big-endian */
+ 0x73323536,
+ GIT_SHA256_RAWSZ,
+ GIT_SHA256_HEXSZ,
+ GIT_SHA256_BLKSZ,
+ git_hash_sha256_init,
+ git_hash_sha256_update,
+ git_hash_sha256_final,
+ &empty_tree_oid_sha256,
+ &empty_blob_oid_sha256,
+ }
};
const char *empty_tree_oid_hex(void)
return oid_to_hex_r(buf, the_hash_algo->empty_blob);
}
+int hash_algo_by_name(const char *name)
+{
+ int i;
+ if (!name)
+ return GIT_HASH_UNKNOWN;
+ for (i = 1; i < GIT_HASH_NALGOS; i++)
+ if (!strcmp(name, hash_algos[i].name))
+ return i;
+ return GIT_HASH_UNKNOWN;
+}
+
+int hash_algo_by_id(uint32_t format_id)
+{
+ int i;
+ for (i = 1; i < GIT_HASH_NALGOS; i++)
+ if (format_id == hash_algos[i].format_id)
+ return i;
+ return GIT_HASH_UNKNOWN;
+}
+
+
/*
* This is meant to hold a *small* number of objects that you would
* want read_sha1_file() to be able to return, but yet you do not want
}
}
-void sha1_file_name(struct repository *r, struct strbuf *buf, const unsigned char *sha1)
+static const char *odb_loose_path(struct object_directory *odb,
+ struct strbuf *buf,
+ const unsigned char *sha1)
{
- strbuf_addstr(buf, r->objects->objectdir);
+ strbuf_reset(buf);
+ strbuf_addstr(buf, odb->path);
strbuf_addch(buf, '/');
fill_sha1_path(buf, sha1);
+ return buf->buf;
}
-struct strbuf *alt_scratch_buf(struct alternate_object_database *alt)
+const char *loose_object_path(struct repository *r, struct strbuf *buf,
+ const unsigned char *sha1)
{
- strbuf_setlen(&alt->scratch, alt->base_len);
- return &alt->scratch;
-}
-
-static const char *alt_sha1_path(struct alternate_object_database *alt,
- const unsigned char *sha1)
-{
- struct strbuf *buf = alt_scratch_buf(alt);
- fill_sha1_path(buf, sha1);
- return buf->buf;
+ return odb_loose_path(r->objects->odb, buf, sha1);
}
/*
struct strbuf *path,
const char *normalized_objdir)
{
- struct alternate_object_database *alt;
+ struct object_directory *odb;
/* Detect cases where alternate disappeared */
if (!is_directory(path->buf)) {
* Prevent the common mistake of listing the same
* thing twice, or object directory itself.
*/
- for (alt = o->alt_odb_list; alt; alt = alt->next) {
- if (!fspathcmp(path->buf, alt->path))
+ for (odb = o->odb; odb; odb = odb->next) {
+ if (!fspathcmp(path->buf, odb->path))
return 0;
}
if (!fspathcmp(path->buf, normalized_objdir))
* Prepare alternate object database registry.
*
* The variable alt_odb_list points at the list of struct
- * alternate_object_database. The elements on this list come from
+ * object_directory. The elements on this list come from
* non-empty elements from colon separated ALTERNATE_DB_ENVIRONMENT
* environment variable, and $GIT_OBJECT_DIRECTORY/info/alternates,
* whose contents is similar to that environment variable but can be
static int link_alt_odb_entry(struct repository *r, const char *entry,
const char *relative_base, int depth, const char *normalized_objdir)
{
- struct alternate_object_database *ent;
+ struct object_directory *ent;
struct strbuf pathbuf = STRBUF_INIT;
if (!is_absolute_path(entry) && relative_base) {
return -1;
}
- ent = alloc_alt_odb(pathbuf.buf);
+ ent = xcalloc(1, sizeof(*ent));
+ ent->path = xstrdup(pathbuf.buf);
/* add the alternate entry */
- *r->objects->alt_odb_tail = ent;
- r->objects->alt_odb_tail = &(ent->next);
+ *r->objects->odb_tail = ent;
+ r->objects->odb_tail = &(ent->next);
ent->next = NULL;
/* recursively add alternates */
return;
}
- strbuf_add_absolute_path(&objdirbuf, r->objects->objectdir);
+ strbuf_add_absolute_path(&objdirbuf, r->objects->odb->path);
if (strbuf_normalize_path(&objdirbuf) < 0)
die(_("unable to normalize object directory: %s"),
objdirbuf.buf);
free(path);
}
-struct alternate_object_database *alloc_alt_odb(const char *dir)
-{
- struct alternate_object_database *ent;
-
- FLEX_ALLOC_STR(ent, path, dir);
- strbuf_init(&ent->scratch, 0);
- strbuf_addf(&ent->scratch, "%s/", dir);
- ent->base_len = ent->scratch.len;
-
- return ent;
-}
-
void add_to_alternates_file(const char *reference)
{
struct lock_file lock = LOCK_INIT;
fprintf_or_die(out, "%s\n", reference);
if (commit_lock_file(&lock))
die_errno(_("unable to move new alternates file into place"));
- if (the_repository->objects->alt_odb_tail)
+ if (the_repository->objects->loaded_alternates)
link_alt_odb_entries(the_repository, reference,
'\n', NULL, 0);
}
int foreach_alt_odb(alt_odb_fn fn, void *cb)
{
- struct alternate_object_database *ent;
+ struct object_directory *ent;
int r = 0;
prepare_alt_odb(the_repository);
- for (ent = the_repository->objects->alt_odb_list; ent; ent = ent->next) {
+ for (ent = the_repository->objects->odb->next; ent; ent = ent->next) {
r = fn(ent, cb);
if (r)
break;
void prepare_alt_odb(struct repository *r)
{
- if (r->objects->alt_odb_tail)
+ if (r->objects->loaded_alternates)
return;
- r->objects->alt_odb_tail = &r->objects->alt_odb_list;
link_alt_odb_entries(r, r->objects->alternate_db, PATH_SEP, NULL, 0);
- read_info_alternates(r, r->objects->objectdir, 0);
+ read_info_alternates(r, r->objects->odb->path, 0);
+ r->objects->loaded_alternates = 1;
}
/* Returns 1 if we have successfully freshened the file, 0 otherwise. */
return 1;
}
-static int check_and_freshen_local(const struct object_id *oid, int freshen)
+static int check_and_freshen_odb(struct object_directory *odb,
+ const struct object_id *oid,
+ int freshen)
{
- static struct strbuf buf = STRBUF_INIT;
-
- strbuf_reset(&buf);
- sha1_file_name(the_repository, &buf, oid->hash);
+ static struct strbuf path = STRBUF_INIT;
+ odb_loose_path(odb, &path, oid->hash);
+ return check_and_freshen_file(path.buf, freshen);
+}
- return check_and_freshen_file(buf.buf, freshen);
+static int check_and_freshen_local(const struct object_id *oid, int freshen)
+{
+ return check_and_freshen_odb(the_repository->objects->odb, oid, freshen);
}
static int check_and_freshen_nonlocal(const struct object_id *oid, int freshen)
{
- struct alternate_object_database *alt;
+ struct object_directory *odb;
+
prepare_alt_odb(the_repository);
- for (alt = the_repository->objects->alt_odb_list; alt; alt = alt->next) {
- const char *path = alt_sha1_path(alt, oid->hash);
- if (check_and_freshen_file(path, freshen))
+ for (odb = the_repository->objects->odb->next; odb; odb = odb->next) {
+ if (check_and_freshen_odb(odb, oid, freshen))
return 1;
}
return 0;
*
* The "path" out-parameter will give the path of the object we found (if any).
* Note that it may point to static storage and is only valid until another
- * call to sha1_file_name(), etc.
+ * call to stat_sha1_file().
*/
static int stat_sha1_file(struct repository *r, const unsigned char *sha1,
struct stat *st, const char **path)
{
- struct alternate_object_database *alt;
+ struct object_directory *odb;
static struct strbuf buf = STRBUF_INIT;
- strbuf_reset(&buf);
- sha1_file_name(r, &buf, sha1);
- *path = buf.buf;
-
- if (!lstat(*path, st))
- return 0;
-
prepare_alt_odb(r);
- errno = ENOENT;
- for (alt = r->objects->alt_odb_list; alt; alt = alt->next) {
- *path = alt_sha1_path(alt, sha1);
+ for (odb = r->objects->odb; odb; odb = odb->next) {
+ *path = odb_loose_path(odb, &buf, sha1);
if (!lstat(*path, st))
return 0;
}
const unsigned char *sha1, const char **path)
{
int fd;
- struct alternate_object_database *alt;
- int most_interesting_errno;
+ struct object_directory *odb;
+ int most_interesting_errno = ENOENT;
static struct strbuf buf = STRBUF_INIT;
- strbuf_reset(&buf);
- sha1_file_name(r, &buf, sha1);
- *path = buf.buf;
-
- fd = git_open(*path);
- if (fd >= 0)
- return fd;
- most_interesting_errno = errno;
-
prepare_alt_odb(r);
- for (alt = r->objects->alt_odb_list; alt; alt = alt->next) {
- *path = alt_sha1_path(alt, sha1);
+ for (odb = r->objects->odb; odb; odb = odb->next) {
+ *path = odb_loose_path(odb, &buf, sha1);
fd = git_open(*path);
if (fd >= 0)
return fd;
+
if (most_interesting_errno == ENOENT)
most_interesting_errno = errno;
}
return -1;
}
+static int quick_has_loose(struct repository *r,
+ const unsigned char *sha1)
+{
+ struct object_id oid;
+ struct object_directory *odb;
+
+ hashcpy(oid.hash, sha1);
+
+ prepare_alt_odb(r);
+ for (odb = r->objects->odb; odb; odb = odb->next) {
+ if (oid_array_lookup(odb_loose_cache(odb, &oid), &oid) >= 0)
+ return 1;
+ }
+ return 0;
+}
+
/*
* Map the loose object at "path" if it is not NULL, or the path found by
* searching for a loose object named "sha1".
if (!*size) {
/* mmap() is forbidden on empty files */
error(_("object file %s is empty"), path);
+ close(fd);
return NULL;
}
map = xmmap(NULL, *size, PROT_READ, MAP_PRIVATE, fd, 0);
if (!oi->typep && !oi->type_name && !oi->sizep && !oi->contentp) {
const char *path;
struct stat st;
+ if (!oi->disk_sizep && (flags & OBJECT_INFO_QUICK))
+ return quick_has_loose(r, sha1) ? 0 : -1;
if (stat_sha1_file(r, sha1, &st, &path) < 0)
return -1;
if (oi->disk_sizep)
return type;
}
-static void *read_object(const unsigned char *sha1, enum object_type *type,
+static void *read_object(struct repository *r,
+ const unsigned char *sha1,
+ enum object_type *type,
unsigned long *size)
{
struct object_id oid;
hashcpy(oid.hash, sha1);
- if (oid_object_info_extended(the_repository, &oid, &oi, 0) < 0)
+ if (oid_object_info_extended(r, &oid, &oi, 0) < 0)
return NULL;
return content;
}
* deal with them should arrange to call read_object() and give error
* messages themselves.
*/
-void *read_object_file_extended(const struct object_id *oid,
+void *read_object_file_extended(struct repository *r,
+ const struct object_id *oid,
enum object_type *type,
unsigned long *size,
int lookup_replace)
const char *path;
struct stat st;
const struct object_id *repl = lookup_replace ?
- lookup_replace_object(the_repository, oid) : oid;
+ lookup_replace_object(r, oid) : oid;
errno = 0;
- data = read_object(repl->hash, type, size);
+ data = read_object(r, repl->hash, type, size);
if (data)
return data;
die(_("replacement %s not found for %s"),
oid_to_hex(repl), oid_to_hex(oid));
- if (!stat_sha1_file(the_repository, repl->hash, &st, &path))
+ if (!stat_sha1_file(r, repl->hash, &st, &path))
die(_("loose object %s (stored in %s) is corrupt"),
oid_to_hex(repl), path);
- if ((p = has_packed_and_bad(repl->hash)) != NULL)
+ if ((p = has_packed_and_bad(r, repl->hash)) != NULL)
die(_("packed object %s (stored in %s) is corrupt"),
oid_to_hex(repl), p->pack_name);
static struct strbuf tmp_file = STRBUF_INIT;
static struct strbuf filename = STRBUF_INIT;
- strbuf_reset(&filename);
- sha1_file_name(the_repository, &filename, oid->hash);
+ loose_object_path(the_repository, &filename, oid->hash);
fd = create_tmpfile(&tmp_file, filename.buf);
if (fd < 0) {
if (has_loose_object(oid))
return 0;
- buf = read_object(oid->hash, &type, &len);
+ buf = read_object(the_repository, oid->hash, &type, &len);
if (!buf)
return error(_("cannot read sha1_file for %s"), oid_to_hex(oid));
hdrlen = xsnprintf(hdr, sizeof(hdr), "%s %"PRIuMAX , type_name(type), (uintmax_t)len) + 1;
return ret;
}
-int has_sha1_file_with_flags(const unsigned char *sha1, int flags)
+int repo_has_sha1_file_with_flags(struct repository *r,
+ const unsigned char *sha1, int flags)
{
struct object_id oid;
if (!startup_info->have_repository)
return 0;
hashcpy(oid.hash, sha1);
- return oid_object_info_extended(the_repository, &oid, NULL,
+ return oid_object_info_extended(r, &oid, NULL,
flags | OBJECT_INFO_SKIP_CACHED) >= 0;
}
-int has_object_file(const struct object_id *oid)
+int repo_has_object_file(struct repository *r,
+ const struct object_id *oid)
{
- return has_sha1_file(oid->hash);
+ return repo_has_sha1_file(r, oid->hash);
}
-int has_object_file_with_flags(const struct object_id *oid, int flags)
+int repo_has_object_file_with_flags(struct repository *r,
+ const struct object_id *oid, int flags)
{
- return has_sha1_file_with_flags(oid->hash, flags);
+ return repo_has_sha1_file_with_flags(r, oid->hash, flags);
}
static void check_tree(const void *buf, size_t size)
return r;
}
-struct loose_alt_odb_data {
- each_loose_object_fn *cb;
- void *data;
-};
+int for_each_loose_object(each_loose_object_fn cb, void *data,
+ enum for_each_object_flags flags)
+{
+ struct object_directory *odb;
+
+ prepare_alt_odb(the_repository);
+ for (odb = the_repository->objects->odb; odb; odb = odb->next) {
+ int r = for_each_loose_file_in_objdir(odb->path, cb, NULL,
+ NULL, data);
+ if (r)
+ return r;
-static int loose_from_alt_odb(struct alternate_object_database *alt,
- void *vdata)
+ if (flags & FOR_EACH_OBJECT_LOCAL_ONLY)
+ break;
+ }
+
+ return 0;
+}
+
+static int append_loose_object(const struct object_id *oid, const char *path,
+ void *data)
{
- struct loose_alt_odb_data *data = vdata;
+ oid_array_append(data, oid);
+ return 0;
+}
+
+struct oid_array *odb_loose_cache(struct object_directory *odb,
+ const struct object_id *oid)
+{
+ int subdir_nr = oid->hash[0];
struct strbuf buf = STRBUF_INIT;
- int r;
- strbuf_addstr(&buf, alt->path);
- r = for_each_loose_file_in_objdir_buf(&buf,
- data->cb, NULL, NULL,
- data->data);
+ if (subdir_nr < 0 ||
+ subdir_nr >= ARRAY_SIZE(odb->loose_objects_subdir_seen))
+ BUG("subdir_nr out of range");
+
+ if (odb->loose_objects_subdir_seen[subdir_nr])
+ return &odb->loose_objects_cache[subdir_nr];
+
+ strbuf_addstr(&buf, odb->path);
+ for_each_file_in_obj_subdir(subdir_nr, &buf,
+ append_loose_object,
+ NULL, NULL,
+ &odb->loose_objects_cache[subdir_nr]);
+ odb->loose_objects_subdir_seen[subdir_nr] = 1;
strbuf_release(&buf);
- return r;
+ return &odb->loose_objects_cache[subdir_nr];
}
-int for_each_loose_object(each_loose_object_fn cb, void *data,
- enum for_each_object_flags flags)
+void odb_clear_loose_cache(struct object_directory *odb)
{
- struct loose_alt_odb_data alt;
- int r;
-
- r = for_each_loose_file_in_objdir(get_object_directory(),
- cb, NULL, NULL, data);
- if (r)
- return r;
-
- if (flags & FOR_EACH_OBJECT_LOCAL_ONLY)
- return 0;
+ int i;
- alt.cb = cb;
- alt.data = data;
- return foreach_alt_odb(loose_from_alt_odb, &alt);
+ for (i = 0; i < ARRAY_SIZE(odb->loose_objects_cache); i++)
+ oid_array_clear(&odb->loose_objects_cache[i]);
+ memset(&odb->loose_objects_subdir_seen, 0,
+ sizeof(odb->loose_objects_subdir_seen));
}
static int check_stream_sha1(git_zstream *stream,
/* otherwise, current can be discarded and candidate is still good */
}
-static int append_loose_object(const struct object_id *oid, const char *path,
- void *data)
-{
- oid_array_append(data, oid);
- return 0;
-}
-
static int match_sha(unsigned, const unsigned char *, const unsigned char *);
static void find_short_object_filename(struct disambiguate_state *ds)
{
- int subdir_nr = ds->bin_pfx.hash[0];
- struct alternate_object_database *alt;
- static struct alternate_object_database *fakeent;
+ struct object_directory *odb;
- if (!fakeent) {
- /*
- * Create a "fake" alternate object database that
- * points to our own object database, to make it
- * easier to get a temporary working space in
- * alt->name/alt->base while iterating over the
- * object databases including our own.
- */
- fakeent = alloc_alt_odb(get_object_directory());
- }
- fakeent->next = the_repository->objects->alt_odb_list;
-
- for (alt = fakeent; alt && !ds->ambiguous; alt = alt->next) {
+ for (odb = the_repository->objects->odb;
+ odb && !ds->ambiguous;
+ odb = odb->next) {
int pos;
+ struct oid_array *loose_objects;
- if (!alt->loose_objects_subdir_seen[subdir_nr]) {
- struct strbuf *buf = alt_scratch_buf(alt);
- for_each_file_in_obj_subdir(subdir_nr, buf,
- append_loose_object,
- NULL, NULL,
- &alt->loose_objects_cache);
- alt->loose_objects_subdir_seen[subdir_nr] = 1;
- }
-
- pos = oid_array_lookup(&alt->loose_objects_cache, &ds->bin_pfx);
+ loose_objects = odb_loose_cache(odb, &ds->bin_pfx);
+ pos = oid_array_lookup(loose_objects, &ds->bin_pfx);
if (pos < 0)
pos = -1 - pos;
- while (!ds->ambiguous && pos < alt->loose_objects_cache.nr) {
+ while (!ds->ambiguous && pos < loose_objects->nr) {
const struct object_id *oid;
- oid = alt->loose_objects_cache.oid + pos;
+ oid = loose_objects->oid + pos;
if (!match_sha(ds->len, ds->bin_pfx.hash, oid->hash))
break;
update_candidates(ds, oid);
unique_in_pack(p, ds);
}
-#define SHORT_NAME_NOT_FOUND (-1)
-#define SHORT_NAME_AMBIGUOUS (-2)
-
static int finish_object_disambiguation(struct disambiguate_state *ds,
struct object_id *oid)
{
return SHORT_NAME_AMBIGUOUS;
if (!ds->candidate_exists)
- return SHORT_NAME_NOT_FOUND;
+ return MISSING_OBJECT;
if (!ds->candidate_checked)
/*
return a_type_sort > b_type_sort ? 1 : -1;
}
-static int get_short_oid(const char *name, int len, struct object_id *oid,
- unsigned flags)
+static enum get_oid_result get_short_oid(const char *name, int len,
+ struct object_id *oid,
+ unsigned flags)
{
int status;
struct disambiguate_state ds;
return at_mark(string, len, suffix, ARRAY_SIZE(suffix));
}
-static int get_oid_1(const char *name, int len, struct object_id *oid, unsigned lookup_flags);
+static enum get_oid_result get_oid_1(const char *name, int len, struct object_id *oid, unsigned lookup_flags);
static int interpret_nth_prior_checkout(const char *name, int namelen, struct strbuf *buf);
static int get_oid_basic(const char *str, int len, struct object_id *oid,
return 0;
}
-static int get_parent(const char *name, int len,
- struct object_id *result, int idx)
+static enum get_oid_result get_parent(const char *name, int len,
+ struct object_id *result, int idx)
{
struct object_id oid;
- int ret = get_oid_1(name, len, &oid, GET_OID_COMMITTISH);
+ enum get_oid_result ret = get_oid_1(name, len, &oid,
+ GET_OID_COMMITTISH);
struct commit *commit;
struct commit_list *p;
return ret;
commit = lookup_commit_reference(the_repository, &oid);
if (parse_commit(commit))
- return -1;
+ return MISSING_OBJECT;
if (!idx) {
oidcpy(result, &commit->object.oid);
- return 0;
+ return FOUND;
}
p = commit->parents;
while (p) {
if (!--idx) {
oidcpy(result, &p->item->object.oid);
- return 0;
+ return FOUND;
}
p = p->next;
}
- return -1;
+ return MISSING_OBJECT;
}
-static int get_nth_ancestor(const char *name, int len,
- struct object_id *result, int generation)
+static enum get_oid_result get_nth_ancestor(const char *name, int len,
+ struct object_id *result,
+ int generation)
{
struct object_id oid;
struct commit *commit;
return ret;
commit = lookup_commit_reference(the_repository, &oid);
if (!commit)
- return -1;
+ return MISSING_OBJECT;
while (generation--) {
if (parse_commit(commit) || !commit->parents)
- return -1;
+ return MISSING_OBJECT;
commit = commit->parents->item;
}
oidcpy(result, &commit->object.oid);
- return 0;
+ return FOUND;
}
struct object *peel_to_type(const char *name, int namelen,
return -1;
}
-static int get_oid_1(const char *name, int len, struct object_id *oid, unsigned lookup_flags)
+static enum get_oid_result get_oid_1(const char *name, int len,
+ struct object_id *oid,
+ unsigned lookup_flags)
{
int ret, has_suffix;
const char *cp;
ret = peel_onion(name, len, oid, lookup_flags);
if (!ret)
- return 0;
+ return FOUND;
ret = get_oid_basic(name, len, oid, lookup_flags);
if (!ret)
- return 0;
+ return FOUND;
/* It could be describe output that is "SOMETHING-gXXXX" */
ret = get_describe_name(name, len, oid);
if (!ret)
- return 0;
+ return FOUND;
return get_short_oid(name, len, oid, lookup_flags);
}
int get_oid(const char *name, struct object_id *oid)
{
struct object_context unused;
- return get_oid_with_context(name, 0, oid, &unused);
+ return get_oid_with_context(the_repository, name, 0, oid, &unused);
}
int get_oid_committish(const char *name, struct object_id *oid)
{
struct object_context unused;
- return get_oid_with_context(name, GET_OID_COMMITTISH,
+ return get_oid_with_context(the_repository,
+ name, GET_OID_COMMITTISH,
oid, &unused);
}
int get_oid_treeish(const char *name, struct object_id *oid)
{
struct object_context unused;
- return get_oid_with_context(name, GET_OID_TREEISH,
+ return get_oid_with_context(the_repository,
+ name, GET_OID_TREEISH,
oid, &unused);
}
int get_oid_commit(const char *name, struct object_id *oid)
{
struct object_context unused;
- return get_oid_with_context(name, GET_OID_COMMIT,
+ return get_oid_with_context(the_repository,
+ name, GET_OID_COMMIT,
oid, &unused);
}
int get_oid_tree(const char *name, struct object_id *oid)
{
struct object_context unused;
- return get_oid_with_context(name, GET_OID_TREE,
+ return get_oid_with_context(the_repository,
+ name, GET_OID_TREE,
oid, &unused);
}
int get_oid_blob(const char *name, struct object_id *oid)
{
struct object_context unused;
- return get_oid_with_context(name, GET_OID_BLOB,
+ return get_oid_with_context(the_repository,
+ name, GET_OID_BLOB,
oid, &unused);
}
}
/* Must be called only when :stage:filename doesn't exist. */
-static void diagnose_invalid_index_path(int stage,
+static void diagnose_invalid_index_path(struct index_state *istate,
+ int stage,
const char *prefix,
const char *filename)
{
prefix = "";
/* Wrong stage number? */
- pos = cache_name_pos(filename, namelen);
+ pos = index_name_pos(istate, filename, namelen);
if (pos < 0)
pos = -pos - 1;
- if (pos < active_nr) {
- ce = active_cache[pos];
+ if (pos < istate->cache_nr) {
+ ce = istate->cache[pos];
if (ce_namelen(ce) == namelen &&
!memcmp(ce->name, filename, namelen))
die("Path '%s' is in the index, but not at stage %d.\n"
/* Confusion between relative and absolute filenames? */
strbuf_addstr(&fullname, prefix);
strbuf_addstr(&fullname, filename);
- pos = cache_name_pos(fullname.buf, fullname.len);
+ pos = index_name_pos(istate, fullname.buf, fullname.len);
if (pos < 0)
pos = -pos - 1;
- if (pos < active_nr) {
- ce = active_cache[pos];
+ if (pos < istate->cache_nr) {
+ ce = istate->cache[pos];
if (ce_namelen(ce) == fullname.len &&
!memcmp(ce->name, fullname.buf, fullname.len))
die("Path '%s' is in the index, but not '%s'.\n"
rel);
}
-static int get_oid_with_context_1(const char *name,
+static enum get_oid_result get_oid_with_context_1(struct repository *repo,
+ const char *name,
unsigned flags,
const char *prefix,
struct object_id *oid,
if (flags & GET_OID_RECORD_PATH)
oc->path = xstrdup(cp);
- if (!active_cache)
- read_cache();
- pos = cache_name_pos(cp, namelen);
+ if (!repo->index->cache)
+ repo_read_index(the_repository);
+ pos = index_name_pos(repo->index, cp, namelen);
if (pos < 0)
pos = -pos - 1;
- while (pos < active_nr) {
- ce = active_cache[pos];
+ while (pos < repo->index->cache_nr) {
+ ce = repo->index->cache[pos];
if (ce_namelen(ce) != namelen ||
memcmp(ce->name, cp, namelen))
break;
pos++;
}
if (only_to_die && name[1] && name[1] != '/')
- diagnose_invalid_index_path(stage, prefix, cp);
+ diagnose_invalid_index_path(repo->index, stage, prefix, cp);
free(new_path);
return -1;
}
{
struct object_context oc;
struct object_id oid;
- get_oid_with_context_1(name, GET_OID_ONLY_TO_DIE, prefix, &oid, &oc);
+ get_oid_with_context_1(the_repository, name, GET_OID_ONLY_TO_DIE,
+ prefix, &oid, &oc);
}
-int get_oid_with_context(const char *str, unsigned flags, struct object_id *oid, struct object_context *oc)
+int get_oid_with_context(struct repository *repo, const char *str,
+ unsigned flags, struct object_id *oid,
+ struct object_context *oc)
{
if (flags & GET_OID_FOLLOW_SYMLINKS && flags & GET_OID_ONLY_TO_DIE)
BUG("incompatible flags for get_sha1_with_context");
- return get_oid_with_context_1(str, flags, NULL, oid, oc);
+ return get_oid_with_context_1(repo, str, flags, NULL, oid, oc);
}
--- /dev/null
+#include "git-compat-util.h"
+#include "./sha256.h"
+
+#undef RND
+#undef BLKSIZE
+
+#define BLKSIZE blk_SHA256_BLKSIZE
+
+void blk_SHA256_Init(blk_SHA256_CTX *ctx)
+{
+ ctx->offset = 0;
+ ctx->size = 0;
+ ctx->state[0] = 0x6a09e667ul;
+ ctx->state[1] = 0xbb67ae85ul;
+ ctx->state[2] = 0x3c6ef372ul;
+ ctx->state[3] = 0xa54ff53aul;
+ ctx->state[4] = 0x510e527ful;
+ ctx->state[5] = 0x9b05688cul;
+ ctx->state[6] = 0x1f83d9abul;
+ ctx->state[7] = 0x5be0cd19ul;
+}
+
+static inline uint32_t ror(uint32_t x, unsigned n)
+{
+ return (x >> n) | (x << (32 - n));
+}
+
+static inline uint32_t ch(uint32_t x, uint32_t y, uint32_t z)
+{
+ return z ^ (x & (y ^ z));
+}
+
+static inline uint32_t maj(uint32_t x, uint32_t y, uint32_t z)
+{
+ return ((x | y) & z) | (x & y);
+}
+
+static inline uint32_t sigma0(uint32_t x)
+{
+ return ror(x, 2) ^ ror(x, 13) ^ ror(x, 22);
+}
+
+static inline uint32_t sigma1(uint32_t x)
+{
+ return ror(x, 6) ^ ror(x, 11) ^ ror(x, 25);
+}
+
+static inline uint32_t gamma0(uint32_t x)
+{
+ return ror(x, 7) ^ ror(x, 18) ^ (x >> 3);
+}
+
+static inline uint32_t gamma1(uint32_t x)
+{
+ return ror(x, 17) ^ ror(x, 19) ^ (x >> 10);
+}
+
+static void blk_SHA256_Transform(blk_SHA256_CTX *ctx, const unsigned char *buf)
+{
+
+ uint32_t S[8], W[64], t0, t1;
+ int i;
+
+ /* copy state into S */
+ for (i = 0; i < 8; i++)
+ S[i] = ctx->state[i];
+
+ /* copy the state into 512-bits into W[0..15] */
+ for (i = 0; i < 16; i++, buf += sizeof(uint32_t))
+ W[i] = get_be32(buf);
+
+ /* fill W[16..63] */
+ for (i = 16; i < 64; i++)
+ W[i] = gamma1(W[i - 2]) + W[i - 7] + gamma0(W[i - 15]) + W[i - 16];
+
+#define RND(a,b,c,d,e,f,g,h,i,ki) \
+ t0 = h + sigma1(e) + ch(e, f, g) + ki + W[i]; \
+ t1 = sigma0(a) + maj(a, b, c); \
+ d += t0; \
+ h = t0 + t1;
+
+ RND(S[0],S[1],S[2],S[3],S[4],S[5],S[6],S[7],0,0x428a2f98);
+ RND(S[7],S[0],S[1],S[2],S[3],S[4],S[5],S[6],1,0x71374491);
+ RND(S[6],S[7],S[0],S[1],S[2],S[3],S[4],S[5],2,0xb5c0fbcf);
+ RND(S[5],S[6],S[7],S[0],S[1],S[2],S[3],S[4],3,0xe9b5dba5);
+ RND(S[4],S[5],S[6],S[7],S[0],S[1],S[2],S[3],4,0x3956c25b);
+ RND(S[3],S[4],S[5],S[6],S[7],S[0],S[1],S[2],5,0x59f111f1);
+ RND(S[2],S[3],S[4],S[5],S[6],S[7],S[0],S[1],6,0x923f82a4);
+ RND(S[1],S[2],S[3],S[4],S[5],S[6],S[7],S[0],7,0xab1c5ed5);
+ RND(S[0],S[1],S[2],S[3],S[4],S[5],S[6],S[7],8,0xd807aa98);
+ RND(S[7],S[0],S[1],S[2],S[3],S[4],S[5],S[6],9,0x12835b01);
+ RND(S[6],S[7],S[0],S[1],S[2],S[3],S[4],S[5],10,0x243185be);
+ RND(S[5],S[6],S[7],S[0],S[1],S[2],S[3],S[4],11,0x550c7dc3);
+ RND(S[4],S[5],S[6],S[7],S[0],S[1],S[2],S[3],12,0x72be5d74);
+ RND(S[3],S[4],S[5],S[6],S[7],S[0],S[1],S[2],13,0x80deb1fe);
+ RND(S[2],S[3],S[4],S[5],S[6],S[7],S[0],S[1],14,0x9bdc06a7);
+ RND(S[1],S[2],S[3],S[4],S[5],S[6],S[7],S[0],15,0xc19bf174);
+ RND(S[0],S[1],S[2],S[3],S[4],S[5],S[6],S[7],16,0xe49b69c1);
+ RND(S[7],S[0],S[1],S[2],S[3],S[4],S[5],S[6],17,0xefbe4786);
+ RND(S[6],S[7],S[0],S[1],S[2],S[3],S[4],S[5],18,0x0fc19dc6);
+ RND(S[5],S[6],S[7],S[0],S[1],S[2],S[3],S[4],19,0x240ca1cc);
+ RND(S[4],S[5],S[6],S[7],S[0],S[1],S[2],S[3],20,0x2de92c6f);
+ RND(S[3],S[4],S[5],S[6],S[7],S[0],S[1],S[2],21,0x4a7484aa);
+ RND(S[2],S[3],S[4],S[5],S[6],S[7],S[0],S[1],22,0x5cb0a9dc);
+ RND(S[1],S[2],S[3],S[4],S[5],S[6],S[7],S[0],23,0x76f988da);
+ RND(S[0],S[1],S[2],S[3],S[4],S[5],S[6],S[7],24,0x983e5152);
+ RND(S[7],S[0],S[1],S[2],S[3],S[4],S[5],S[6],25,0xa831c66d);
+ RND(S[6],S[7],S[0],S[1],S[2],S[3],S[4],S[5],26,0xb00327c8);
+ RND(S[5],S[6],S[7],S[0],S[1],S[2],S[3],S[4],27,0xbf597fc7);
+ RND(S[4],S[5],S[6],S[7],S[0],S[1],S[2],S[3],28,0xc6e00bf3);
+ RND(S[3],S[4],S[5],S[6],S[7],S[0],S[1],S[2],29,0xd5a79147);
+ RND(S[2],S[3],S[4],S[5],S[6],S[7],S[0],S[1],30,0x06ca6351);
+ RND(S[1],S[2],S[3],S[4],S[5],S[6],S[7],S[0],31,0x14292967);
+ RND(S[0],S[1],S[2],S[3],S[4],S[5],S[6],S[7],32,0x27b70a85);
+ RND(S[7],S[0],S[1],S[2],S[3],S[4],S[5],S[6],33,0x2e1b2138);
+ RND(S[6],S[7],S[0],S[1],S[2],S[3],S[4],S[5],34,0x4d2c6dfc);
+ RND(S[5],S[6],S[7],S[0],S[1],S[2],S[3],S[4],35,0x53380d13);
+ RND(S[4],S[5],S[6],S[7],S[0],S[1],S[2],S[3],36,0x650a7354);
+ RND(S[3],S[4],S[5],S[6],S[7],S[0],S[1],S[2],37,0x766a0abb);
+ RND(S[2],S[3],S[4],S[5],S[6],S[7],S[0],S[1],38,0x81c2c92e);
+ RND(S[1],S[2],S[3],S[4],S[5],S[6],S[7],S[0],39,0x92722c85);
+ RND(S[0],S[1],S[2],S[3],S[4],S[5],S[6],S[7],40,0xa2bfe8a1);
+ RND(S[7],S[0],S[1],S[2],S[3],S[4],S[5],S[6],41,0xa81a664b);
+ RND(S[6],S[7],S[0],S[1],S[2],S[3],S[4],S[5],42,0xc24b8b70);
+ RND(S[5],S[6],S[7],S[0],S[1],S[2],S[3],S[4],43,0xc76c51a3);
+ RND(S[4],S[5],S[6],S[7],S[0],S[1],S[2],S[3],44,0xd192e819);
+ RND(S[3],S[4],S[5],S[6],S[7],S[0],S[1],S[2],45,0xd6990624);
+ RND(S[2],S[3],S[4],S[5],S[6],S[7],S[0],S[1],46,0xf40e3585);
+ RND(S[1],S[2],S[3],S[4],S[5],S[6],S[7],S[0],47,0x106aa070);
+ RND(S[0],S[1],S[2],S[3],S[4],S[5],S[6],S[7],48,0x19a4c116);
+ RND(S[7],S[0],S[1],S[2],S[3],S[4],S[5],S[6],49,0x1e376c08);
+ RND(S[6],S[7],S[0],S[1],S[2],S[3],S[4],S[5],50,0x2748774c);
+ RND(S[5],S[6],S[7],S[0],S[1],S[2],S[3],S[4],51,0x34b0bcb5);
+ RND(S[4],S[5],S[6],S[7],S[0],S[1],S[2],S[3],52,0x391c0cb3);
+ RND(S[3],S[4],S[5],S[6],S[7],S[0],S[1],S[2],53,0x4ed8aa4a);
+ RND(S[2],S[3],S[4],S[5],S[6],S[7],S[0],S[1],54,0x5b9cca4f);
+ RND(S[1],S[2],S[3],S[4],S[5],S[6],S[7],S[0],55,0x682e6ff3);
+ RND(S[0],S[1],S[2],S[3],S[4],S[5],S[6],S[7],56,0x748f82ee);
+ RND(S[7],S[0],S[1],S[2],S[3],S[4],S[5],S[6],57,0x78a5636f);
+ RND(S[6],S[7],S[0],S[1],S[2],S[3],S[4],S[5],58,0x84c87814);
+ RND(S[5],S[6],S[7],S[0],S[1],S[2],S[3],S[4],59,0x8cc70208);
+ RND(S[4],S[5],S[6],S[7],S[0],S[1],S[2],S[3],60,0x90befffa);
+ RND(S[3],S[4],S[5],S[6],S[7],S[0],S[1],S[2],61,0xa4506ceb);
+ RND(S[2],S[3],S[4],S[5],S[6],S[7],S[0],S[1],62,0xbef9a3f7);
+ RND(S[1],S[2],S[3],S[4],S[5],S[6],S[7],S[0],63,0xc67178f2);
+
+ for (i = 0; i < 8; i++)
+ ctx->state[i] += S[i];
+}
+
+void blk_SHA256_Update(blk_SHA256_CTX *ctx, const void *data, size_t len)
+{
+ unsigned int len_buf = ctx->size & 63;
+
+ ctx->size += len;
+
+ /* Read the data into buf and process blocks as they get full */
+ if (len_buf) {
+ unsigned int left = 64 - len_buf;
+ if (len < left)
+ left = len;
+ memcpy(len_buf + ctx->buf, data, left);
+ len_buf = (len_buf + left) & 63;
+ len -= left;
+ data = ((const char *)data + left);
+ if (len_buf)
+ return;
+ blk_SHA256_Transform(ctx, ctx->buf);
+ }
+ while (len >= 64) {
+ blk_SHA256_Transform(ctx, data);
+ data = ((const char *)data + 64);
+ len -= 64;
+ }
+ if (len)
+ memcpy(ctx->buf, data, len);
+}
+
+void blk_SHA256_Final(unsigned char *digest, blk_SHA256_CTX *ctx)
+{
+ static const unsigned char pad[64] = { 0x80 };
+ unsigned int padlen[2];
+ int i;
+
+ /* Pad with a binary 1 (ie 0x80), then zeroes, then length */
+ padlen[0] = htonl((uint32_t)(ctx->size >> 29));
+ padlen[1] = htonl((uint32_t)(ctx->size << 3));
+
+ i = ctx->size & 63;
+ blk_SHA256_Update(ctx, pad, 1 + (63 & (55 - i)));
+ blk_SHA256_Update(ctx, padlen, 8);
+
+ /* copy output */
+ for (i = 0; i < 8; i++, digest += sizeof(uint32_t))
+ put_be32(digest, ctx->state[i]);
+}
--- /dev/null
+#ifndef SHA256_BLOCK_SHA256_H
+#define SHA256_BLOCK_SHA256_H
+
+#define blk_SHA256_BLKSIZE 64
+
+struct blk_SHA256_CTX {
+ uint32_t state[8];
+ uint64_t size;
+ uint32_t offset;
+ uint8_t buf[blk_SHA256_BLKSIZE];
+};
+
+typedef struct blk_SHA256_CTX blk_SHA256_CTX;
+
+void blk_SHA256_Init(blk_SHA256_CTX *ctx);
+void blk_SHA256_Update(blk_SHA256_CTX *ctx, const void *data, size_t len);
+void blk_SHA256_Final(unsigned char *digest, blk_SHA256_CTX *ctx);
+
+#define platform_SHA256_CTX blk_SHA256_CTX
+#define platform_SHA256_Init blk_SHA256_Init
+#define platform_SHA256_Update blk_SHA256_Update
+#define platform_SHA256_Final blk_SHA256_Final
+
+#endif
--- /dev/null
+#ifndef SHA256_GCRYPT_H
+#define SHA256_GCRYPT_H
+
+#include <gcrypt.h>
+
+#define SHA256_DIGEST_SIZE 32
+
+typedef gcry_md_hd_t gcrypt_SHA256_CTX;
+
+inline void gcrypt_SHA256_Init(gcrypt_SHA256_CTX *ctx)
+{
+ gcry_md_open(ctx, GCRY_MD_SHA256, 0);
+}
+
+inline void gcrypt_SHA256_Update(gcrypt_SHA256_CTX *ctx, const void *data, size_t len)
+{
+ gcry_md_write(*ctx, data, len);
+}
+
+inline void gcrypt_SHA256_Final(unsigned char *digest, gcrypt_SHA256_CTX *ctx)
+{
+ memcpy(digest, gcry_md_read(*ctx, GCRY_MD_SHA256), SHA256_DIGEST_SIZE);
+}
+
+#define platform_SHA256_CTX gcrypt_SHA256_CTX
+#define platform_SHA256_Init gcrypt_SHA256_Init
+#define platform_SHA256_Update gcrypt_SHA256_Update
+#define platform_SHA256_Final gcrypt_SHA256_Final
+
+#endif
int is_repository_shallow(struct repository *r)
{
+ /*
+ * NEEDSWORK: This function updates
+ * r->parsed_objects->{is_shallow,shallow_stat} as a side effect but
+ * there is no corresponding function to clear them when the shallow
+ * file is updated.
+ */
+
FILE *fp;
char buf[1024];
const char *path = r->parsed_objects->alternate_shallow_file;
#include "cache.h"
#include "color.h"
#include "config.h"
-#include "pkt-line.h"
#include "sideband.h"
#include "help.h"
struct keyword_entry *p = keywords + i;
int len = strlen(p->keyword);
- if (n <= len)
+ if (n < len)
continue;
/*
* Match case insensitively, so we colorize output from existing
* messages. We only highlight the word precisely, so
* "successful" stays uncolored.
*/
- if (!strncasecmp(p->keyword, src, len) && !isalnum(src[len])) {
+ if (!strncasecmp(p->keyword, src, len) &&
+ (len == n || !isalnum(src[len]))) {
strbuf_addstr(dest, p->color);
strbuf_add(dest, src, len);
strbuf_addstr(dest, GIT_COLOR_RESET);
}
-/*
- * Receive multiplexed output stream over git native protocol.
- * in_stream is the input stream from the remote, which carries data
- * in pkt_line format with band designator. Demultiplex it into out
- * and err and return error appropriately. Band #1 carries the
- * primary payload. Things coming over band #2 is not necessarily
- * error; they are usually informative message on the standard error
- * stream, aka "verbose"). A message over band #3 is a signal that
- * the remote died unexpectedly. A flush() concludes the stream.
- */
-
#define DISPLAY_PREFIX "remote: "
#define ANSI_SUFFIX "\033[K"
#define DUMB_SUFFIX " "
-int recv_sideband(const char *me, int in_stream, int out)
+int demultiplex_sideband(const char *me, char *buf, int len,
+ int die_on_error,
+ struct strbuf *scratch,
+ enum sideband_type *sideband_type)
{
- const char *suffix;
- char buf[LARGE_PACKET_MAX + 1];
- struct strbuf outbuf = STRBUF_INIT;
- int retval = 0;
-
- if (isatty(2) && !is_terminal_dumb())
- suffix = ANSI_SUFFIX;
- else
- suffix = DUMB_SUFFIX;
-
- while (!retval) {
- const char *b, *brk;
- int band, len;
- len = packet_read(in_stream, NULL, NULL, buf, LARGE_PACKET_MAX, 0);
- if (len == 0)
- break;
- if (len < 1) {
- strbuf_addf(&outbuf,
- "%s%s: protocol error: no band designator",
- outbuf.len ? "\n" : "", me);
- retval = SIDEBAND_PROTOCOL_ERROR;
- break;
- }
- band = buf[0] & 0xff;
- buf[len] = '\0';
- len--;
- switch (band) {
- case 3:
- strbuf_addf(&outbuf, "%s%s", outbuf.len ? "\n" : "",
- DISPLAY_PREFIX);
- maybe_colorize_sideband(&outbuf, buf + 1, len);
-
- retval = SIDEBAND_REMOTE_ERROR;
- break;
- case 2:
- b = buf + 1;
-
- /*
- * Append a suffix to each nonempty line to clear the
- * end of the screen line.
- *
- * The output is accumulated in a buffer and
- * each line is printed to stderr using
- * write(2) to ensure inter-process atomicity.
- */
- while ((brk = strpbrk(b, "\n\r"))) {
- int linelen = brk - b;
-
- if (!outbuf.len)
- strbuf_addstr(&outbuf, DISPLAY_PREFIX);
- if (linelen > 0) {
- maybe_colorize_sideband(&outbuf, b, linelen);
- strbuf_addstr(&outbuf, suffix);
- }
-
- strbuf_addch(&outbuf, *brk);
- xwrite(2, outbuf.buf, outbuf.len);
- strbuf_reset(&outbuf);
-
- b = brk + 1;
- }
+ static const char *suffix;
+ const char *b, *brk;
+ int band;
+
+ if (!suffix) {
+ if (isatty(2) && !is_terminal_dumb())
+ suffix = ANSI_SUFFIX;
+ else
+ suffix = DUMB_SUFFIX;
+ }
- if (*b) {
- strbuf_addstr(&outbuf, outbuf.len ?
- "" : DISPLAY_PREFIX);
- maybe_colorize_sideband(&outbuf, b, strlen(b));
+ if (len == 0) {
+ *sideband_type = SIDEBAND_FLUSH;
+ goto cleanup;
+ }
+ if (len < 1) {
+ strbuf_addf(scratch,
+ "%s%s: protocol error: no band designator",
+ scratch->len ? "\n" : "", me);
+ *sideband_type = SIDEBAND_PROTOCOL_ERROR;
+ goto cleanup;
+ }
+ band = buf[0] & 0xff;
+ buf[len] = '\0';
+ len--;
+ switch (band) {
+ case 3:
+ if (die_on_error)
+ die("remote error: %s", buf + 1);
+ strbuf_addf(scratch, "%s%s", scratch->len ? "\n" : "",
+ DISPLAY_PREFIX);
+ maybe_colorize_sideband(scratch, buf + 1, len);
+
+ *sideband_type = SIDEBAND_REMOTE_ERROR;
+ break;
+ case 2:
+ b = buf + 1;
+
+ /*
+ * Append a suffix to each nonempty line to clear the
+ * end of the screen line.
+ *
+ * The output is accumulated in a buffer and
+ * each line is printed to stderr using
+ * write(2) to ensure inter-process atomicity.
+ */
+ while ((brk = strpbrk(b, "\n\r"))) {
+ int linelen = brk - b;
+
+ if (!scratch->len)
+ strbuf_addstr(scratch, DISPLAY_PREFIX);
+ if (linelen > 0) {
+ maybe_colorize_sideband(scratch, b, linelen);
+ strbuf_addstr(scratch, suffix);
}
- break;
- case 1:
- write_or_die(out, buf + 1, len);
- break;
- default:
- strbuf_addf(&outbuf, "%s%s: protocol error: bad band #%d",
- outbuf.len ? "\n" : "", me, band);
- retval = SIDEBAND_PROTOCOL_ERROR;
- break;
+
+ strbuf_addch(scratch, *brk);
+ xwrite(2, scratch->buf, scratch->len);
+ strbuf_reset(scratch);
+
+ b = brk + 1;
+ }
+
+ if (*b) {
+ strbuf_addstr(scratch, scratch->len ?
+ "" : DISPLAY_PREFIX);
+ maybe_colorize_sideband(scratch, b, strlen(b));
}
+ return 0;
+ case 1:
+ *sideband_type = SIDEBAND_PRIMARY;
+ break;
+ default:
+ strbuf_addf(scratch, "%s%s: protocol error: bad band #%d",
+ scratch->len ? "\n" : "", me, band);
+ *sideband_type = SIDEBAND_PROTOCOL_ERROR;
+ break;
}
- if (outbuf.len) {
- strbuf_addch(&outbuf, '\n');
- xwrite(2, outbuf.buf, outbuf.len);
+cleanup:
+ if (die_on_error && *sideband_type == SIDEBAND_PROTOCOL_ERROR)
+ die("%s", scratch->buf);
+ if (scratch->len) {
+ strbuf_addch(scratch, '\n');
+ xwrite(2, scratch->buf, scratch->len);
}
- strbuf_release(&outbuf);
- return retval;
+ strbuf_release(scratch);
+ return 1;
}
/*
#ifndef SIDEBAND_H
#define SIDEBAND_H
-#define SIDEBAND_PROTOCOL_ERROR -2
-#define SIDEBAND_REMOTE_ERROR -1
+enum sideband_type {
+ SIDEBAND_PROTOCOL_ERROR = -2,
+ SIDEBAND_REMOTE_ERROR = -1,
+ SIDEBAND_FLUSH = 0,
+ SIDEBAND_PRIMARY = 1
+};
+
+/*
+ * Inspects a multiplexed packet read from the remote. If this packet is a
+ * progress packet and thus should not be processed by the caller, returns 0.
+ * Otherwise, returns 1, releases scratch, and sets sideband_type.
+ *
+ * If this packet is SIDEBAND_PROTOCOL_ERROR, SIDEBAND_REMOTE_ERROR, or a
+ * progress packet, also prints a message to stderr.
+ *
+ * scratch must be a struct strbuf allocated by the caller. It is used to store
+ * progress messages split across multiple packets.
+ */
+int demultiplex_sideband(const char *me, char *buf, int len,
+ int die_on_error,
+ struct strbuf *scratch,
+ enum sideband_type *sideband_type);
-int recv_sideband(const char *me, int in_stream, int out);
void send_sideband(int fd, int band, const char *data, ssize_t sz, int packet_max);
#endif
static open_method_decl(incore)
{
- st->u.incore.buf = read_object_file_extended(oid, type, &st->size, 0);
+ st->u.incore.buf = read_object_file_extended(the_repository, oid, type, &st->size, 0);
st->u.incore.read_ptr = 0;
st->vtbl = &incore_vtbl;
return *item->string != '\0';
}
-void string_list_remove_empty_items(struct string_list *list, int free_util) {
+void string_list_remove_empty_items(struct string_list *list, int free_util)
+{
filter_string_list(list, free_util, item_is_not_empty, NULL);
}
-#define NO_THE_INDEX_COMPATIBILITY_MACROS
#include "cache.h"
#include "repository.h"
#include "commit-reach.h"
static int config_update_recurse_submodules = RECURSE_SUBMODULES_OFF;
-static struct string_list changed_submodule_names = STRING_LIST_INIT_DUP;
static int initialized_fetch_ref_tips;
static struct oid_array ref_tips_before_fetch;
static struct oid_array ref_tips_after_fetch;
return prepare_revision_walk(rev);
}
-static void print_submodule_summary(struct rev_info *rev, struct diff_options *o)
+static void print_submodule_summary(struct repository *r, struct rev_info *rev, struct diff_options *o)
{
static const char format[] = " %m %s";
struct strbuf sb = STRBUF_INIT;
ctx.date_mode = rev->date_mode;
ctx.output_encoding = get_log_output_encoding();
strbuf_setlen(&sb, 0);
- format_commit_message(commit, format, &sb, &ctx);
+ repo_format_commit_message(r, commit, format, &sb,
+ &ctx);
strbuf_addch(&sb, '\n');
if (commit->object.flags & SYMMETRIC_LEFT)
diff_emit_submodule_del(o, sb.buf);
DEFAULT_GIT_DIR_ENVIRONMENT);
}
-/* Helper function to display the submodule header line prior to the full
- * summary output. If it can locate the submodule objects directory it will
- * attempt to lookup both the left and right commits and put them into the
- * left and right pointers.
+static void prepare_submodule_repo_env_in_gitdir(struct argv_array *out)
+{
+ prepare_submodule_repo_env_no_git_dir(out);
+ argv_array_pushf(out, "%s=.", GIT_DIR_ENVIRONMENT);
+}
+
+/*
+ * Initialize a repository struct for a submodule based on the provided 'path'.
+ *
+ * Unlike repo_submodule_init, this tolerates submodules not present
+ * in .gitmodules. This function exists only to preserve historical behavior,
+ *
+ * Returns the repository struct on success,
+ * NULL when the submodule is not present.
*/
-static void show_submodule_header(struct diff_options *o, const char *path,
+static struct repository *open_submodule(const char *path)
+{
+ struct strbuf sb = STRBUF_INIT;
+ struct repository *out = xmalloc(sizeof(*out));
+
+ if (submodule_to_gitdir(&sb, path) || repo_init(out, sb.buf, NULL)) {
+ strbuf_release(&sb);
+ free(out);
+ return NULL;
+ }
+
+ /* Mark it as a submodule */
+ out->submodule_prefix = xstrdup(path);
+
+ strbuf_release(&sb);
+ return out;
+}
+
+/*
+ * Helper function to display the submodule header line prior to the full
+ * summary output.
+ *
+ * If it can locate the submodule git directory it will create a repository
+ * handle for the submodule and lookup both the left and right commits and
+ * put them into the left and right pointers.
+ */
+static void show_submodule_header(struct diff_options *o,
+ const char *path,
struct object_id *one, struct object_id *two,
unsigned dirty_submodule,
+ struct repository *sub,
struct commit **left, struct commit **right,
struct commit_list **merge_bases)
{
else if (is_null_oid(two))
message = "(submodule deleted)";
- if (add_submodule_odb(path)) {
+ if (!sub) {
if (!message)
message = "(commits not present)";
goto output_header;
* Attempt to lookup the commit references, and determine if this is
* a fast forward or fast backwards update.
*/
- *left = lookup_commit_reference(the_repository, one);
- *right = lookup_commit_reference(the_repository, two);
+ *left = lookup_commit_reference(sub, one);
+ *right = lookup_commit_reference(sub, two);
/*
* Warn about missing commits in the submodule project, but only if
(!is_null_oid(two) && !*right))
message = "(commits not present)";
- *merge_bases = get_merge_bases(*left, *right);
+ *merge_bases = repo_get_merge_bases(sub, *left, *right);
if (*merge_bases) {
if ((*merge_bases)->item == *left)
fast_forward = 1;
struct rev_info rev;
struct commit *left = NULL, *right = NULL;
struct commit_list *merge_bases = NULL;
+ struct repository *sub;
+ sub = open_submodule(path);
show_submodule_header(o, path, one, two, dirty_submodule,
- &left, &right, &merge_bases);
+ sub, &left, &right, &merge_bases);
/*
* If we don't have both a left and a right pointer, there is no
* reason to try and display a summary. The header line should contain
* all the information the user needs.
*/
- if (!left || !right)
+ if (!left || !right || !sub)
goto out;
/* Treat revision walker failure the same as missing commits */
goto out;
}
- print_submodule_summary(&rev, o);
+ print_submodule_summary(sub, &rev, o);
out:
if (merge_bases)
free_commit_list(merge_bases);
clear_commit_marks(left, ~0);
clear_commit_marks(right, ~0);
+ if (sub) {
+ repo_clear(sub);
+ free(sub);
+ }
}
void show_submodule_inline_diff(struct diff_options *o, const char *path,
struct commit_list *merge_bases = NULL;
struct child_process cp = CHILD_PROCESS_INIT;
struct strbuf sb = STRBUF_INIT;
+ struct repository *sub;
+ sub = open_submodule(path);
show_submodule_header(o, path, one, two, dirty_submodule,
- &left, &right, &merge_bases);
+ sub, &left, &right, &merge_bases);
/* We need a valid left and right commit to display a difference */
if (!(left || is_null_oid(one)) ||
clear_commit_marks(left, ~0);
if (right)
clear_commit_marks(right, ~0);
+ if (sub) {
+ repo_clear(sub);
+ free(sub);
+ }
}
int should_update_submodules(void)
const struct string_list *push_options,
int dry_run)
{
- if (add_submodule_odb(path))
- return 1;
-
if (for_each_remote_ref_submodule(path, has_remote, NULL) > 0) {
struct child_process cp = CHILD_PROCESS_INIT;
argv_array_push(&cp.args, "push");
oid_array_append(&ref_tips_after_fetch, oid);
}
-static void calculate_changed_submodule_paths(struct repository *r)
+static void calculate_changed_submodule_paths(struct repository *r,
+ struct string_list *changed_submodule_names)
{
struct argv_array argv = ARGV_ARRAY_INIT;
- struct string_list changed_submodules = STRING_LIST_INIT_DUP;
- const struct string_list_item *name;
+ struct string_list_item *name;
/* No need to check if there are no submodules configured */
if (!submodule_from_path(r, NULL, NULL))
* Collect all submodules (whether checked out or not) for which new
* commits have been recorded upstream in "changed_submodule_names".
*/
- collect_changed_submodules(r, &changed_submodules, &argv);
+ collect_changed_submodules(r, changed_submodule_names, &argv);
- for_each_string_list_item(name, &changed_submodules) {
+ for_each_string_list_item(name, changed_submodule_names) {
struct oid_array *commits = name->util;
const struct submodule *submodule;
const char *path = NULL;
if (!path)
continue;
- if (!submodule_has_commits(r, path, commits))
- string_list_append(&changed_submodule_names, name->string);
+ if (submodule_has_commits(r, path, commits)) {
+ oid_array_clear(commits);
+ *name->string = '\0';
+ }
}
- free_submodules_oids(&changed_submodules);
+ string_list_remove_empty_items(changed_submodule_names, 1);
+
argv_array_clear(&argv);
oid_array_clear(&ref_tips_before_fetch);
oid_array_clear(&ref_tips_after_fetch);
int default_option;
int quiet;
int result;
+
+ struct string_list changed_submodule_names;
+
+ /* Pending fetches by OIDs */
+ struct fetch_task **oid_fetch_tasks;
+ int oid_fetch_tasks_nr, oid_fetch_tasks_alloc;
};
-#define SPF_INIT {0, ARGV_ARRAY_INIT, NULL, NULL, 0, 0, 0, 0}
+#define SPF_INIT {0, ARGV_ARRAY_INIT, NULL, NULL, 0, 0, 0, 0, \
+ STRING_LIST_INIT_DUP, \
+ NULL, 0, 0}
static int get_fetch_recurse_config(const struct submodule *submodule,
struct submodule_parallel_fetch *spf)
return spf->default_option;
}
+/*
+ * Fetch in progress (if callback data) or
+ * pending (if in oid_fetch_tasks in struct submodule_parallel_fetch)
+ */
+struct fetch_task {
+ struct repository *repo;
+ const struct submodule *sub;
+ unsigned free_sub : 1; /* Do we need to free the submodule? */
+
+ struct oid_array *commits; /* Ensure these commits are fetched */
+};
+
+/**
+ * When a submodule is not defined in .gitmodules, we cannot access it
+ * via the regular submodule-config. Create a fake submodule, which we can
+ * work on.
+ */
+static const struct submodule *get_non_gitmodules_submodule(const char *path)
+{
+ struct submodule *ret = NULL;
+ const char *name = default_name_or_path(path);
+
+ if (!name)
+ return NULL;
+
+ ret = xmalloc(sizeof(*ret));
+ memset(ret, 0, sizeof(*ret));
+ ret->path = name;
+ ret->name = name;
+
+ return (const struct submodule *) ret;
+}
+
+static struct fetch_task *fetch_task_create(struct repository *r,
+ const char *path)
+{
+ struct fetch_task *task = xmalloc(sizeof(*task));
+ memset(task, 0, sizeof(*task));
+
+ task->sub = submodule_from_path(r, &null_oid, path);
+ if (!task->sub) {
+ /*
+ * No entry in .gitmodules? Technically not a submodule,
+ * but historically we supported repositories that happen to be
+ * in-place where a gitlink is. Keep supporting them.
+ */
+ task->sub = get_non_gitmodules_submodule(path);
+ if (!task->sub) {
+ free(task);
+ return NULL;
+ }
+
+ task->free_sub = 1;
+ }
+
+ return task;
+}
+
+static void fetch_task_release(struct fetch_task *p)
+{
+ if (p->free_sub)
+ free((void*)p->sub);
+ p->free_sub = 0;
+ p->sub = NULL;
+
+ if (p->repo)
+ repo_clear(p->repo);
+ FREE_AND_NULL(p->repo);
+}
+
+static struct repository *get_submodule_repo_for(struct repository *r,
+ const struct submodule *sub)
+{
+ struct repository *ret = xmalloc(sizeof(*ret));
+
+ if (repo_submodule_init(ret, r, sub)) {
+ /*
+ * No entry in .gitmodules? Technically not a submodule,
+ * but historically we supported repositories that happen to be
+ * in-place where a gitlink is. Keep supporting them.
+ */
+ struct strbuf gitdir = STRBUF_INIT;
+ strbuf_repo_worktree_path(&gitdir, r, "%s/.git", sub->path);
+ if (repo_init(ret, gitdir.buf, NULL)) {
+ strbuf_release(&gitdir);
+ free(ret);
+ return NULL;
+ }
+ strbuf_release(&gitdir);
+ }
+
+ return ret;
+}
+
static int get_next_submodule(struct child_process *cp,
struct strbuf *err, void *data, void **task_cb)
{
- int ret = 0;
struct submodule_parallel_fetch *spf = data;
for (; spf->count < spf->r->index->cache_nr; spf->count++) {
- struct strbuf submodule_path = STRBUF_INIT;
- struct strbuf submodule_git_dir = STRBUF_INIT;
- struct strbuf submodule_prefix = STRBUF_INIT;
const struct cache_entry *ce = spf->r->index->cache[spf->count];
- const char *git_dir, *default_argv;
- const struct submodule *submodule;
- struct submodule default_submodule = SUBMODULE_INIT;
+ const char *default_argv;
+ struct fetch_task *task;
if (!S_ISGITLINK(ce->ce_mode))
continue;
- submodule = submodule_from_path(spf->r, &null_oid, ce->name);
- if (!submodule) {
- const char *name = default_name_or_path(ce->name);
- if (name) {
- default_submodule.path = default_submodule.name = name;
- submodule = &default_submodule;
- }
- }
+ task = fetch_task_create(spf->r, ce->name);
+ if (!task)
+ continue;
- switch (get_fetch_recurse_config(submodule, spf))
+ switch (get_fetch_recurse_config(task->sub, spf))
{
default:
case RECURSE_SUBMODULES_DEFAULT:
case RECURSE_SUBMODULES_ON_DEMAND:
- if (!submodule || !unsorted_string_list_lookup(&changed_submodule_names,
- submodule->name))
+ if (!task->sub ||
+ !string_list_lookup(
+ &spf->changed_submodule_names,
+ task->sub->name))
continue;
default_argv = "on-demand";
break;
continue;
}
- strbuf_repo_worktree_path(&submodule_path, spf->r, "%s", ce->name);
- strbuf_addf(&submodule_git_dir, "%s/.git", submodule_path.buf);
- strbuf_addf(&submodule_prefix, "%s%s/", spf->prefix, ce->name);
- git_dir = read_gitfile(submodule_git_dir.buf);
- if (!git_dir)
- git_dir = submodule_git_dir.buf;
- if (is_directory(git_dir)) {
+ task->repo = get_submodule_repo_for(spf->r, task->sub);
+ if (task->repo) {
+ struct strbuf submodule_prefix = STRBUF_INIT;
child_process_init(cp);
- cp->dir = strbuf_detach(&submodule_path, NULL);
- prepare_submodule_repo_env(&cp->env_array);
+ cp->dir = task->repo->gitdir;
+ prepare_submodule_repo_env_in_gitdir(&cp->env_array);
cp->git_cmd = 1;
if (!spf->quiet)
strbuf_addf(err, "Fetching submodule %s%s\n",
argv_array_pushv(&cp->args, spf->args.argv);
argv_array_push(&cp->args, default_argv);
argv_array_push(&cp->args, "--submodule-prefix");
+
+ strbuf_addf(&submodule_prefix, "%s%s/",
+ spf->prefix,
+ task->sub->path);
argv_array_push(&cp->args, submodule_prefix.buf);
- ret = 1;
- }
- strbuf_release(&submodule_path);
- strbuf_release(&submodule_git_dir);
- strbuf_release(&submodule_prefix);
- if (ret) {
+
spf->count++;
+ *task_cb = task;
+
+ strbuf_release(&submodule_prefix);
return 1;
+ } else {
+
+ fetch_task_release(task);
+ free(task);
+
+ /*
+ * An empty directory is normal,
+ * the submodule is not initialized
+ */
+ if (S_ISGITLINK(ce->ce_mode) &&
+ !is_empty_dir(ce->name)) {
+ spf->result = 1;
+ strbuf_addf(err,
+ _("Could not access submodule '%s'"),
+ ce->name);
+ }
}
}
+
+ if (spf->oid_fetch_tasks_nr) {
+ struct fetch_task *task =
+ spf->oid_fetch_tasks[spf->oid_fetch_tasks_nr - 1];
+ struct strbuf submodule_prefix = STRBUF_INIT;
+ spf->oid_fetch_tasks_nr--;
+
+ strbuf_addf(&submodule_prefix, "%s%s/",
+ spf->prefix, task->sub->path);
+
+ child_process_init(cp);
+ prepare_submodule_repo_env_in_gitdir(&cp->env_array);
+ cp->git_cmd = 1;
+ cp->dir = task->repo->gitdir;
+
+ argv_array_init(&cp->args);
+ argv_array_pushv(&cp->args, spf->args.argv);
+ argv_array_push(&cp->args, "on-demand");
+ argv_array_push(&cp->args, "--submodule-prefix");
+ argv_array_push(&cp->args, submodule_prefix.buf);
+
+ /* NEEDSWORK: have get_default_remote from submodule--helper */
+ argv_array_push(&cp->args, "origin");
+ oid_array_for_each_unique(task->commits,
+ append_oid_to_argv, &cp->args);
+
+ *task_cb = task;
+ strbuf_release(&submodule_prefix);
+ return 1;
+ }
+
return 0;
}
void *cb, void *task_cb)
{
struct submodule_parallel_fetch *spf = cb;
+ struct fetch_task *task = task_cb;
spf->result = 1;
+ fetch_task_release(task);
return 0;
}
+static int commit_missing_in_sub(const struct object_id *oid, void *data)
+{
+ struct repository *subrepo = data;
+
+ enum object_type type = oid_object_info(subrepo, oid, NULL);
+
+ return type != OBJ_COMMIT;
+}
+
static int fetch_finish(int retvalue, struct strbuf *err,
void *cb, void *task_cb)
{
struct submodule_parallel_fetch *spf = cb;
+ struct fetch_task *task = task_cb;
+
+ struct string_list_item *it;
+ struct oid_array *commits;
if (retvalue)
spf->result = 1;
+ if (!task || !task->sub)
+ BUG("callback cookie bogus");
+
+ /* Is this the second time we process this submodule? */
+ if (task->commits)
+ goto out;
+
+ it = string_list_lookup(&spf->changed_submodule_names, task->sub->name);
+ if (!it)
+ /* Could be an unchanged submodule, not contained in the list */
+ goto out;
+
+ commits = it->util;
+ oid_array_filter(commits,
+ commit_missing_in_sub,
+ task->repo);
+
+ /* Are there commits we want, but do not exist? */
+ if (commits->nr) {
+ task->commits = commits;
+ ALLOC_GROW(spf->oid_fetch_tasks,
+ spf->oid_fetch_tasks_nr + 1,
+ spf->oid_fetch_tasks_alloc);
+ spf->oid_fetch_tasks[spf->oid_fetch_tasks_nr] = task;
+ spf->oid_fetch_tasks_nr++;
+ return 0;
+ }
+
+out:
+ fetch_task_release(task);
+
return 0;
}
argv_array_push(&spf.args, "--recurse-submodules-default");
/* default value, "--submodule-prefix" and its value are added later */
- calculate_changed_submodule_paths(r);
+ calculate_changed_submodule_paths(r, &spf.changed_submodule_names);
+ string_list_sort(&spf.changed_submodule_names);
run_processes_parallel(max_parallel_jobs,
get_next_submodule,
fetch_start_failure,
argv_array_clear(&spf.args);
out:
- string_list_clear(&changed_submodule_names, 1);
+ free_submodules_oids(&spf.changed_submodule_names);
return spf.result;
}
return ret;
}
+void submodule_unset_core_worktree(const struct submodule *sub)
+{
+ char *config_path = xstrfmt("%s/modules/%s/config",
+ get_git_common_dir(), sub->name);
+
+ if (git_config_set_in_file_gently(config_path, "core.worktree", NULL))
+ warning(_("Could not unset core.worktree setting in submodule '%s'"),
+ sub->path);
+
+ free(config_path);
+}
+
static const char *get_super_prefix_or_empty(void)
{
const char *s = get_super_prefix();
if (is_empty_dir(path))
rmdir_or_warn(path);
+
+ submodule_unset_core_worktree(sub);
}
}
out:
const char *new_head,
unsigned flags);
+void submodule_unset_core_worktree(const struct submodule *sub);
+
/*
* Prepare the "env_array" parameter of a "struct child_process" for executing
* a submodule by clearing any repo-specific environment variables, but
*/
int check_leading_path(const char *name, int len)
{
- return threaded_check_leading_path(&default_cache, name, len);
+ return threaded_check_leading_path(&default_cache, name, len);
}
/*
this feature by setting the GIT_TEST_CHAIN_LINT environment
variable to "1" or "0", respectively.
+--stress::
+--stress=<N>::
+ Run the test script repeatedly in multiple parallel jobs until
+ one of them fails. Useful for reproducing rare failures in
+ flaky tests. The number of parallel jobs is, in order of
+ precedence: <N>, or the value of the GIT_TEST_STRESS_LOAD
+ environment variable, or twice the number of available
+ processors (as shown by the 'getconf' utility), or 8.
+ Implies `--verbose -x --immediate` to get the most information
+ about the failure. Note that the verbose output of each test
+ job is saved to 't/test-results/$TEST_NAME.stress-<nr>.out',
+ and only the output of the failed test job is shown on the
+ terminal. The names of the trash directories get a
+ '.stress-<nr>' suffix, and the trash directory of the failed
+ test job is renamed to end with a '.stress-failed' suffix.
+
You can also set the GIT_TEST_INSTALLED environment variable to
the bindir of an existing git installation to test that installation.
You still need to have built this git sandbox, from which various
for the index version specified. Can be set to any valid version
(currently 2, 3, or 4).
+GIT_TEST_PACK_SPARSE=<boolean> if enabled will default the pack-objects
+builtin to use the sparse object walk. This can still be overridden by
+the --no-sparse command-line argument.
+
GIT_TEST_PRELOAD_INDEX=<boolean> exercises the preload-index code path
by overriding the minimum number of cache entries required per thread.
index to be written after every 'git repack' command, and overrides the
'core.multiPackIndex' setting to true.
+GIT_TEST_SIDEBAND_ALL=<boolean>, when true, overrides the
+'uploadpack.allowSidebandAll' setting to true, and when false, forces
+fetch-pack to not request sideband-all (even if the server advertises
+sideband-all).
+
Naming Tests
------------
- Creates an empty test directory with an empty .git/objects database
and chdir(2) into it. This directory is 't/trash
directory.$test_name_without_dotsh', with t/ subject to change by
- the --root option documented above.
+ the --root option documented above, and a '.stress-<N>' suffix
+ appended by the --stress option.
- Defines standard test helper functions for your scripts to
use. These functions are designed to make all scripts behave
chomp;
}
- /\bsed\s+-i/ and err 'sed -i is not portable';
+ /\bcp\s+-a/ and err 'cp -a is not portable';
+ /\bsed\s+-[^efn]\s+/ and err 'sed option not portable (use only -n, -e, -f)';
/\becho\s+-[neE]/ and err 'echo with option is not portable (use printf)';
/^\s*declare\s+/ and err 'arrays/declare not portable';
/^\s*[^#]\s*which\s/ and err 'which is not portable (use type)';
static const char *usage_msg = "\n"
" test-tool date relative [time_t]...\n"
+" test-tool date human [time_t]...\n"
" test-tool date show:<format> [time_t]...\n"
" test-tool date parse [date]...\n"
" test-tool date approxidate [date]...\n"
for (; *argv; argv++) {
time_t t = atoi(*argv);
- show_date_relative(t, 0, now, &buf);
+ show_date_relative(t, now, &buf);
printf("%s -> %s\n", *argv, buf.buf);
}
strbuf_release(&buf);
}
+static void show_human_dates(const char **argv)
+{
+ for (; *argv; argv++) {
+ time_t t = atoi(*argv);
+ printf("%s -> %s\n", *argv, show_date(t, 0, DATE_MODE(HUMAN)));
+ }
+}
+
static void show_dates(const char **argv, const char *format)
{
struct date_mode mode;
struct timeval now;
const char *x;
- x = getenv("TEST_DATE_NOW");
+ x = getenv("GIT_TEST_DATE_NOW");
if (x) {
now.tv_sec = atoi(x);
now.tv_usec = 0;
usage(usage_msg);
if (!strcmp(*argv, "relative"))
show_relative_dates(argv+1, &now);
+ else if (!strcmp(*argv, "human"))
+ show_human_dates(argv+1);
else if (skip_prefix(*argv, "show:", &x))
show_dates(argv+1, x);
else if (!strcmp(*argv, "parse"))
int cmd__dump_fsmonitor(int ac, const char **av)
{
- struct index_state *istate = &the_index;
+ struct index_state *istate = the_repository->index;
int i;
setup_git_directory();
- if (do_read_index(istate, get_index_file(), 0) < 0)
+ if (do_read_index(istate, the_repository->index_file, 0) < 0)
die("unable to read index file");
if (!istate->fsmonitor_last_update) {
printf("no fsmonitor\n");
+#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "test-tool.h"
#include "cache.h"
#include "dir.h"
--- /dev/null
+#include "test-tool.h"
+#include "cache.h"
+
+#define NUM_SECONDS 3
+
+static inline void compute_hash(const struct git_hash_algo *algo, git_hash_ctx *ctx, uint8_t *final, const void *p, size_t len)
+{
+ algo->init_fn(ctx);
+ algo->update_fn(ctx, p, len);
+ algo->final_fn(final, ctx);
+}
+
+int cmd__hash_speed(int ac, const char **av)
+{
+ git_hash_ctx ctx;
+ unsigned char hash[GIT_MAX_RAWSZ];
+ clock_t initial, start, end;
+ unsigned bufsizes[] = { 64, 256, 1024, 8192, 16384 };
+ int i;
+ void *p;
+ const struct git_hash_algo *algo = NULL;
+
+ if (ac == 2) {
+ for (i = 1; i < GIT_HASH_NALGOS; i++) {
+ if (!strcmp(av[1], hash_algos[i].name)) {
+ algo = &hash_algos[i];
+ break;
+ }
+ }
+ }
+ if (!algo)
+ die("usage: test-tool hash-speed algo_name");
+
+ /* Use this as an offset to make overflow less likely. */
+ initial = clock();
+
+ printf("algo: %s\n", algo->name);
+
+ for (i = 0; i < ARRAY_SIZE(bufsizes); i++) {
+ unsigned long j, kb;
+ double kb_per_sec;
+ p = xcalloc(1, bufsizes[i]);
+ start = end = clock() - initial;
+ for (j = 0; ((end - start) / CLOCKS_PER_SEC) < NUM_SECONDS; j++) {
+ compute_hash(algo, &ctx, hash, p, bufsizes[i]);
+
+ /*
+ * Only check elapsed time every 128 iterations to avoid
+ * dominating the runtime with system calls.
+ */
+ if (!(j & 127))
+ end = clock() - initial;
+ }
+ kb = j * bufsizes[i];
+ kb_per_sec = kb / (1024 * ((double)end - start) / CLOCKS_PER_SEC);
+ printf("size %u: %lu iters; %lu KiB; %0.2f KiB/s\n", bufsizes[i], j, kb, kb_per_sec);
+ free(p);
+ }
+
+ exit(0);
+}
--- /dev/null
+#include "test-tool.h"
+#include "cache.h"
+
+int cmd_hash_impl(int ac, const char **av, int algo)
+{
+ git_hash_ctx ctx;
+ unsigned char hash[GIT_MAX_HEXSZ];
+ unsigned bufsz = 8192;
+ int binary = 0;
+ char *buffer;
+ const struct git_hash_algo *algop = &hash_algos[algo];
+
+ if (ac == 2) {
+ if (!strcmp(av[1], "-b"))
+ binary = 1;
+ else
+ bufsz = strtoul(av[1], NULL, 10) * 1024 * 1024;
+ }
+
+ if (!bufsz)
+ bufsz = 8192;
+
+ while ((buffer = malloc(bufsz)) == NULL) {
+ fprintf(stderr, "bufsz %u is too big, halving...\n", bufsz);
+ bufsz /= 2;
+ if (bufsz < 1024)
+ die("OOPS");
+ }
+
+ algop->init_fn(&ctx);
+
+ while (1) {
+ ssize_t sz, this_sz;
+ char *cp = buffer;
+ unsigned room = bufsz;
+ this_sz = 0;
+ while (room) {
+ sz = xread(0, cp, room);
+ if (sz == 0)
+ break;
+ if (sz < 0)
+ die_errno("test-hash");
+ this_sz += sz;
+ cp += sz;
+ room -= sz;
+ }
+ if (this_sz == 0)
+ break;
+ algop->update_fn(&ctx, buffer, this_sz);
+ }
+ algop->final_fn(hash, &ctx);
+
+ if (binary)
+ fwrite(hash, 1, algop->rawsz, stdout);
+ else
+ puts(hash_to_hex_algop(hash, algop));
+ exit(0);
+}
{
const char *msg = notnull(*argv++, "msg");
const char *refname = notnull(*argv++, "refname");
- const char *new_sha1_buf = notnull(*argv++, "old-sha1");
+ const char *new_sha1_buf = notnull(*argv++, "new-sha1");
const char *old_sha1_buf = notnull(*argv++, "old-sha1");
unsigned int flags = arg_flags(*argv++, "flags");
struct object_id old_oid;
setup_git_env(gitdir);
+ memset(the_repository, 0, sizeof(*the_repository));
+
+ /* TODO: Needed for temporary hack in hashcmp, see 183a638b7da. */
+ repo_set_hash_algo(the_repository, GIT_HASH_SHA1);
+
if (repo_init(&r, gitdir, worktree))
die("Couldn't init repo");
setup_git_env(gitdir);
+ memset(the_repository, 0, sizeof(*the_repository));
+
+ /* TODO: Needed for temporary hack in hashcmp, see 183a638b7da. */
+ repo_set_hash_algo(the_repository, GIT_HASH_SHA1);
+
if (repo_init(&r, gitdir, worktree))
die("Couldn't init repo");
int cmd__sha1(int ac, const char **av)
{
- git_SHA_CTX ctx;
- unsigned char sha1[20];
- unsigned bufsz = 8192;
- int binary = 0;
- char *buffer;
-
- if (ac == 2) {
- if (!strcmp(av[1], "-b"))
- binary = 1;
- else
- bufsz = strtoul(av[1], NULL, 10) * 1024 * 1024;
- }
-
- if (!bufsz)
- bufsz = 8192;
-
- while ((buffer = malloc(bufsz)) == NULL) {
- fprintf(stderr, "bufsz %u is too big, halving...\n", bufsz);
- bufsz /= 2;
- if (bufsz < 1024)
- die("OOPS");
- }
-
- git_SHA1_Init(&ctx);
-
- while (1) {
- ssize_t sz, this_sz;
- char *cp = buffer;
- unsigned room = bufsz;
- this_sz = 0;
- while (room) {
- sz = xread(0, cp, room);
- if (sz == 0)
- break;
- if (sz < 0)
- die_errno("test-sha1");
- this_sz += sz;
- cp += sz;
- room -= sz;
- }
- if (this_sz == 0)
- break;
- git_SHA1_Update(&ctx, buffer, this_sz);
- }
- git_SHA1_Final(sha1, &ctx);
-
- if (binary)
- fwrite(sha1, 1, 20, stdout);
- else
- puts(sha1_to_hex(sha1));
- exit(0);
+ return cmd_hash_impl(ac, av, GIT_HASH_SHA1);
}
--- /dev/null
+#include "test-tool.h"
+#include "cache.h"
+
+int cmd__sha256(int ac, const char **av)
+{
+ return cmd_hash_impl(ac, av, GIT_HASH_SHA256);
+}
X(three)
#undef X
-int cmd__sigchain(int argc, const char **argv) {
+int cmd__sigchain(int argc, const char **argv)
+{
sigchain_push(SIGTERM, one);
sigchain_push(SIGTERM, two);
sigchain_push(SIGTERM, three);
int cmd__submodule_nested_repo_config(int argc, const char **argv)
{
- struct repository submodule;
+ struct repository subrepo;
+ const struct submodule *sub;
if (argc < 3)
die_usage(argc, argv, "Wrong number of arguments.");
setup_git_directory();
- if (repo_submodule_init(&submodule, the_repository, argv[1])) {
+ sub = submodule_from_path(the_repository, &null_oid, argv[1]);
+ if (repo_submodule_init(&subrepo, the_repository, sub)) {
die_usage(argc, argv, "Submodule not found.");
}
/* Read the config of _child_ submodules. */
- print_config_from_gitmodules(&submodule, argv[2]);
+ print_config_from_gitmodules(&subrepo, argv[2]);
submodule_free(the_repository);
{ "example-decorate", cmd__example_decorate },
{ "genrandom", cmd__genrandom },
{ "hashmap", cmd__hashmap },
+ { "hash-speed", cmd__hash_speed },
{ "index-version", cmd__index_version },
{ "json-writer", cmd__json_writer },
{ "lazy-init-name-hash", cmd__lazy_init_name_hash },
{ "scrap-cache-tree", cmd__scrap_cache_tree },
{ "sha1", cmd__sha1 },
{ "sha1-array", cmd__sha1_array },
+ { "sha256", cmd__sha256 },
{ "sigchain", cmd__sigchain },
{ "strcmp-offset", cmd__strcmp_offset },
{ "string-list", cmd__string_list },
#ifndef TEST_TOOL_H
#define TEST_TOOL_H
+#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "git-compat-util.h"
int cmd__chmtime(int argc, const char **argv);
int cmd__example_decorate(int argc, const char **argv);
int cmd__genrandom(int argc, const char **argv);
int cmd__hashmap(int argc, const char **argv);
+int cmd__hash_speed(int argc, const char **argv);
int cmd__index_version(int argc, const char **argv);
int cmd__json_writer(int argc, const char **argv);
int cmd__lazy_init_name_hash(int argc, const char **argv);
int cmd__scrap_cache_tree(int argc, const char **argv);
int cmd__sha1(int argc, const char **argv);
int cmd__sha1_array(int argc, const char **argv);
+int cmd__sha256(int argc, const char **argv);
int cmd__sigchain(int argc, const char **argv);
int cmd__strcmp_offset(int argc, const char **argv);
int cmd__string_list(int argc, const char **argv);
#endif
int cmd__write_cache(int argc, const char **argv);
+int cmd_hash_impl(int ac, const char **av, int algo);
+
#endif
test_skip_or_die $GIT_TEST_GIT_DAEMON "file system does not support FIFOs"
fi
-LIB_GIT_DAEMON_PORT=${LIB_GIT_DAEMON_PORT-${this_test#t}}
+test_set_port LIB_GIT_DAEMON_PORT
GIT_DAEMON_PID=
GIT_DAEMON_DOCUMENT_ROOT_PATH="$PWD"/repo
"$@" "$GIT_DAEMON_DOCUMENT_ROOT_PATH" \
>&3 2>git_daemon_output &
GIT_DAEMON_PID=$!
- >daemon.log
{
read -r line <&7
- printf "%s\n" "$line"
- printf >&4 "%s\n" "$line"
- (
- while read -r line <&7
- do
- printf "%s\n" "$line"
- printf >&4 "%s\n" "$line"
- done
- ) &
- } 7<git_daemon_output >>"$TRASH_DIRECTORY/daemon.log" &&
+ printf "%s\n" "$line" >&4
+ cat <&7 >&4 &
+ } 7<git_daemon_output &&
# Check expected output
if test x"$(expr "$line" : "\[[0-9]*\] \(.*\)")" != x"Ready to rumble"
(cd / && "$PYTHON_PATH" -c 'import time; print(int(time.time()))')
}
-# Try to pick a unique port: guess a large number, then hope
-# no more than one of each test is running.
-#
-# This does not handle the case where somebody else is running the
-# same tests and has chosen the same ports.
-testid=${this_test#t}
-git_p4_test_start=9800
-P4DPORT=$((10669 + ($testid - $git_p4_test_start)))
+test_set_port P4DPORT
P4PORT=localhost:$P4DPORT
P4CLIENT=client
GIT_DIR=$PWD/.git
GIT_SVN_DIR=$GIT_DIR/svn/refs/remotes/git-svn
SVN_TREE=$GIT_SVN_DIR/svn-tree
+test_set_port SVNSERVE_PORT
svn >/dev/null 2>&1
if test $? -ne 1
}
start_svnserve () {
- SVNSERVE_PORT=${SVNSERVE_PORT-${this_test#t}}
svnserve --listen-port $SVNSERVE_PORT \
--root "$rawsvnrepo" \
--listen-once \
esac
LIB_HTTPD_PATH=${LIB_HTTPD_PATH-"$DEFAULT_HTTPD_PATH"}
-LIB_HTTPD_PORT=${LIB_HTTPD_PORT-${this_test#t}}
+test_set_port LIB_HTTPD_PORT
TEST_PATH="$TEST_DIRECTORY"/lib-httpd
HTTPD_ROOT_PATH="$PWD"/httpd
PassEnv ASAN_OPTIONS
PassEnv GIT_TRACE
PassEnv GIT_CONFIG_NOSYSTEM
+PassEnv GIT_TEST_SIDEBAND_ALL
SetEnvIf Git-Protocol ".*" GIT_PROTOCOL=$0
SetEnv GIT_EXEC_PATH ${GIT_EXEC_PATH}
SetEnv GIT_HTTP_EXPORT_ALL
</LocationMatch>
+ScriptAliasMatch /error_git_upload_pack/(.*)/git-upload-pack error.sh/
ScriptAliasMatch /smart_*[^/]*/(.*) ${GIT_EXEC_PATH}/git-http-backend/$1
ScriptAlias /broken_smart/ broken-smart-http.sh/
ScriptAlias /error/ error.sh/
then
mkdir -p submodule_update/.git/modules/sub1/modules &&
cp -r submodule_update_repo/.git/modules/sub1/modules/sub2 submodule_update/.git/modules/sub1/modules/sub2
- GIT_WORK_TREE=. git -C submodule_update/.git/modules/sub1/modules/sub2 config --unset core.worktree
+ # core.worktree is unset for sub2 as it is not checked out
fi &&
# indicate we are interested in the submodule:
git -C submodule_update config submodule.sub1.url "bogus" &&
git branch -t remove_sub1 origin/remove_sub1 &&
$command remove_sub1 &&
test_superproject_content origin/remove_sub1 &&
- ! test -e sub1
+ ! test -e sub1 &&
+ test_must_fail git config -f .git/modules/sub1/config core.worktree
)
'
# ... absorbing a .git directory along the way.
)
'
+test_expect_success 'binary macro expanded by -a' '
+ echo "file binary" >.gitattributes &&
+ cat >expect <<-\EOF &&
+ file: binary: set
+ file: diff: unset
+ file: merge: unset
+ file: text: unset
+ EOF
+ git check-attr -a file >actual &&
+ test_cmp expect actual
+'
+
+
+test_expect_success 'query binary macro directly' '
+ echo "file binary" >.gitattributes &&
+ echo file: binary: set >expect &&
+ git check-attr binary file >actual &&
+ test_cmp expect actual
+'
+
test_done
. ./test-lib.sh
# arbitrary reference time: 2009-08-30 19:20:00
-TEST_DATE_NOW=1251660000; export TEST_DATE_NOW
+GIT_TEST_DATE_NOW=1251660000; export GIT_TEST_DATE_NOW
check_relative() {
- t=$(($TEST_DATE_NOW - $1))
+ t=$(($GIT_TEST_DATE_NOW - $1))
echo "$t -> $2" >expect
test_expect_${3:-success} "relative date ($2)" "
test-tool date relative $t >actual &&
check_approxidate '2008-12-01' '2008-12-01 19:20:00'
check_approxidate '2009-12-01' '2009-12-01 19:20:00'
+check_date_format_human() {
+ t=$(($GIT_TEST_DATE_NOW - $1))
+ echo "$t -> $2" >expect
+ test_expect_success "human date $t" '
+ test-tool date human $t >actual &&
+ test_i18ncmp expect actual
+'
+}
+
+check_date_format_human 18000 "5 hours ago" # 5 hours ago
+check_date_format_human 432000 "Tue Aug 25 19:20" # 5 days ago
+check_date_format_human 1728000 "Mon Aug 10 19:20" # 3 weeks ago
+check_date_format_human 13000000 "Thu Apr 2 08:13" # 5 months ago
+check_date_format_human 31449600 "Aug 31 2008" # 12 months ago
+check_date_format_human 37500000 "Jun 22 2008" # 1 year, 2 months ago
+check_date_format_human 55188000 "Dec 1 2007" # 1 year, 9 months ago
+check_date_format_human 630000000 "Sep 13 1989" # 20 years ago
+
test_done
--- /dev/null
+#!/bin/sh
+
+test_description='test basic hash implementation'
+. ./test-lib.sh
+
+
+test_expect_success 'test basic SHA-1 hash values' '
+ test-tool sha1 </dev/null >actual &&
+ grep da39a3ee5e6b4b0d3255bfef95601890afd80709 actual &&
+ printf "a" | test-tool sha1 >actual &&
+ grep 86f7e437faa5a7fce15d1ddcb9eaeaea377667b8 actual &&
+ printf "abc" | test-tool sha1 >actual &&
+ grep a9993e364706816aba3e25717850c26c9cd0d89d actual &&
+ printf "message digest" | test-tool sha1 >actual &&
+ grep c12252ceda8be8994d5fa0290a47231c1d16aae3 actual &&
+ printf "abcdefghijklmnopqrstuvwxyz" | test-tool sha1 >actual &&
+ grep 32d10c7b8cf96570ca04ce37f2a19d84240d3a89 actual &&
+ perl -e "$| = 1; print q{aaaaaaaaaa} for 1..100000;" | \
+ test-tool sha1 >actual &&
+ grep 34aa973cd4c4daa4f61eeb2bdbad27316534016f actual &&
+ printf "blob 0\0" | test-tool sha1 >actual &&
+ grep e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 actual &&
+ printf "blob 3\0abc" | test-tool sha1 >actual &&
+ grep f2ba8f84ab5c1bce84a7b441cb1959cfc7093b7f actual &&
+ printf "tree 0\0" | test-tool sha1 >actual &&
+ grep 4b825dc642cb6eb9a060e54bf8d69288fbee4904 actual
+'
+
+test_expect_success 'test basic SHA-256 hash values' '
+ test-tool sha256 </dev/null >actual &&
+ grep e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 actual &&
+ printf "a" | test-tool sha256 >actual &&
+ grep ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb actual &&
+ printf "abc" | test-tool sha256 >actual &&
+ grep ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad actual &&
+ printf "message digest" | test-tool sha256 >actual &&
+ grep f7846f55cf23e14eebeab5b4e1550cad5b509e3348fbc4efa3a1413d393cb650 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;" | \
+ test-tool sha256 >actual &&
+ grep cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0 actual &&
+ perl -e "$| = 1; print q{abcdefghijklmnopqrstuvwxyz} for 1..100000;" | \
+ test-tool sha256 >actual &&
+ grep e406ba321ca712ad35a698bf0af8d61fc4dc40eca6bdcea4697962724ccbde35 actual &&
+ printf "blob 0\0" | test-tool sha256 >actual &&
+ grep 473a0f4c3be8a93681a267e3b1e9a7dcda1185436fe141f7749120a303721813 actual &&
+ printf "blob 3\0abc" | test-tool sha256 >actual &&
+ grep c1cf6e465077930e88dc5136641d402f72a229ddd996f627d60e9639eaba35a6 actual &&
+ printf "tree 0\0" | test-tool sha256 >actual &&
+ grep 6ef19b41225c5369f1c104d45d8d85efa9b057b53b14b4b9b939dd74decc5321 actual
+'
+
+test_done
test_cmp expect actual
'
+test_expect_success 'ignore-errors not mistaken for renormalize' '
+ git reset --hard &&
+ echo "*.txt text=auto" >.gitattributes &&
+ git ls-files --eol >expect &&
+ git add --ignore-errors "*.txt" &&
+ git ls-files --eol >actual &&
+ test_cmp expect actual
+'
+
test_done
do
rm crlf_false_attr__$f.txt &&
if test -z "$ceol"; then
- git checkout crlf_false_attr__$f.txt
+ git checkout -- crlf_false_attr__$f.txt
else
- git -c core.eol=$ceol checkout crlf_false_attr__$f.txt
+ git -c core.eol=$ceol checkout -- crlf_false_attr__$f.txt
fi
done
text="hallo there!\ncan you read me?" &&
echo "*.utf16 text working-tree-encoding=utf-16" >.gitattributes &&
+ echo "*.utf16lebom text working-tree-encoding=UTF-16LE-BOM" >>.gitattributes &&
printf "$text" >test.utf8.raw &&
printf "$text" | iconv -f UTF-8 -t UTF-16 >test.utf16.raw &&
printf "$text" | iconv -f UTF-8 -t UTF-32 >test.utf32.raw &&
+ printf "\377\376" >test.utf16lebom.raw &&
+ printf "$text" | iconv -f UTF-8 -t UTF-32LE >>test.utf16lebom.raw &&
# Line ending tests
printf "one\ntwo\nthree\n" >lf.utf8.raw &&
# Add only UTF-16 file, we will add the UTF-32 file later
cp test.utf16.raw test.utf16 &&
cp test.utf32.raw test.utf32 &&
- git add .gitattributes test.utf16 &&
+ cp test.utf16lebom.raw test.utf16lebom &&
+ git add .gitattributes test.utf16 test.utf16lebom &&
git commit -m initial
'
test_cmp_bin test.utf16.raw test.utf16
'
+test_expect_success 're-encode to UTF-16-LE-BOM on checkout' '
+ rm test.utf16lebom &&
+ git checkout test.utf16lebom &&
+ test_cmp_bin test.utf16lebom.raw test.utf16lebom
+'
+
test_expect_success 'check $GIT_DIR/info/attributes support' '
test_when_finished "rm -f test.utf32.git" &&
test_when_finished "git reset --hard HEAD" &&
test_expect_success '-c with comment char defined in .git/config' '
test_config core.commentchar = &&
printf "= foo\n" >expect &&
- printf "foo" | (
- mkdir sub && cd sub && git stripspace -c
- ) >actual &&
+ rm -fr sub &&
+ mkdir sub &&
+ printf "foo" | git -C sub stripspace -c >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success '-c outside git repository' '
+ printf "# foo\n" >expect &&
+ printf "foo" | nongit git stripspace -c >actual &&
test_cmp expect actual
'
test_expect_success 'detect possible typos' '
test_must_fail test-tool parse-options -boolean >output 2>output.err &&
test_must_be_empty output &&
- test_cmp typo.err output.err
+ test_i18ncmp typo.err output.err
'
cat >typo.err <<\EOF
test_expect_success 'detect possible typos' '
test_must_fail test-tool parse-options -ambiguous >output 2>output.err &&
test_must_be_empty output &&
- test_cmp typo.err output.err
+ test_i18ncmp typo.err output.err
'
test_expect_success 'keep some options as arguments' '
test_must_be_empty err
'
-test_expect_success 'run_command is restricted to PATH' '
+
+test_lazy_prereq RUNS_COMMANDS_FROM_PWD '
+ write_script runs-commands-from-pwd <<-\EOF &&
+ true
+ EOF
+ runs-commands-from-pwd >/dev/null 2>&1
+'
+
+test_expect_success !RUNS_COMMANDS_FROM_PWD 'run_command is restricted to PATH' '
write_script should-not-run <<-\EOF &&
echo yikes
EOF
)
'
+test_expect_success MINGW 'verify curlies are quoted properly' '
+ : force the rev-parse through the MSYS2 Bash &&
+ git -c alias.r="!git rev-parse" r -- a{b}c >actual &&
+ cat >expect <<-\EOF &&
+ --
+ a{b}c
+ EOF
+ test_cmp expect actual
+'
+
test_done
grep $(git -C repo rev-parse bar) out # sanity check that some walking was done
'
-test_expect_success 'rev-list accepts missing and promised objects on command line' '
+test_expect_success 'rev-list dies for missing objects on cmd line' '
rm -rf repo &&
test_create_repo repo &&
test_commit -C repo foo &&
git -C repo config core.repositoryformatversion 1 &&
git -C repo config extensions.partialclone "arbitrary string" &&
- git -C repo rev-list --exclude-promisor-objects --objects "$COMMIT" "$TREE" "$BLOB"
+
+ for OBJ in "$COMMIT" "$TREE" "$BLOB"; do
+ test_must_fail git -C repo rev-list --objects \
+ --exclude-promisor-objects "$OBJ" &&
+ test_must_fail git -C repo rev-list --objects-edge-aggressive \
+ --exclude-promisor-objects "$OBJ" &&
+
+ # Do not die or crash when --ignore-missing is passed.
+ git -C repo rev-list --ignore-missing --objects \
+ --exclude-promisor-objects "$OBJ" &&
+ git -C repo rev-list --ignore-missing --objects-edge-aggressive \
+ --exclude-promisor-objects "$OBJ"
+ done
'
test_expect_success 'gc repacks promisor objects separately from non-promisor objects' '
! grep "$TREE_HASH" out
'
-LIB_HTTPD_PORT=12345 # default port, 410, cannot be used as non-root
. "$TEST_DIRECTORY"/lib-httpd.sh
start_httpd
}
check_fsck () {
- output=$(git fsck --full)
+ git fsck --full >fsck.output
case "$1" in
'')
- test -z "$output" ;;
+ test_must_be_empty fsck.output ;;
*)
- echo "$output" | grep "$1" ;;
+ test_i18ngrep "$1" fsck.output ;;
esac
}
test_must_fail git fsck 2>out &&
cat out &&
- grep "$sha.*corrupt" out
+ test_i18ngrep "$sha.*corrupt" out
'
test_expect_success 'branch pointing to non-commit' '
test_when_finished "git update-ref -d refs/heads/invalid" &&
test_must_fail git fsck 2>out &&
cat out &&
- grep "not a commit" out
+ test_i18ngrep "not a commit" out
'
test_expect_success 'HEAD link pointing at a funny object' '
# avoid corrupt/broken HEAD from interfering with repo discovery
test_must_fail env GIT_DIR=.git git fsck 2>out &&
cat out &&
- grep "detached HEAD points" out
+ test_i18ngrep "detached HEAD points" out
'
test_expect_success 'HEAD link pointing at a funny place' '
# avoid corrupt/broken HEAD from interfering with repo discovery
test_must_fail env GIT_DIR=.git git fsck 2>out &&
cat out &&
- grep "HEAD points to something strange" out
+ test_i18ngrep "HEAD points to something strange" out
'
test_expect_success 'HEAD link pointing at a funny object (from different wt)' '
echo $ZERO_OID >.git/HEAD &&
# avoid corrupt/broken HEAD from interfering with repo discovery
test_must_fail git -C wt fsck 2>out &&
- grep "main-worktree/HEAD: detached HEAD points" out
+ test_i18ngrep "main-worktree/HEAD: detached HEAD points" out
'
test_expect_success 'other worktree HEAD link pointing at a funny object' '
git worktree add other &&
echo $ZERO_OID >.git/worktrees/other/HEAD &&
test_must_fail git fsck 2>out &&
- grep "worktrees/other/HEAD: detached HEAD points" out
+ test_i18ngrep "worktrees/other/HEAD: detached HEAD points" out
'
test_expect_success 'other worktree HEAD link pointing at missing object' '
git worktree add other &&
echo "Contents missing from repo" | git hash-object --stdin >.git/worktrees/other/HEAD &&
test_must_fail git fsck 2>out &&
- grep "worktrees/other/HEAD: invalid sha1 pointer" out
+ test_i18ngrep "worktrees/other/HEAD: invalid sha1 pointer" out
'
test_expect_success 'other worktree HEAD link pointing at a funny place' '
git worktree add other &&
echo "ref: refs/funny/place" >.git/worktrees/other/HEAD &&
test_must_fail git fsck 2>out &&
- grep "worktrees/other/HEAD points to something strange" out
+ test_i18ngrep "worktrees/other/HEAD points to something strange" out
'
test_expect_success 'email without @ is okay' '
test_when_finished "git update-ref -d refs/heads/bogus" &&
test_must_fail git fsck 2>out &&
cat out &&
- grep "error in commit $new" out
+ test_i18ngrep "error in commit $new" out
'
test_expect_success 'missing < email delimiter is reported nicely' '
test_when_finished "git update-ref -d refs/heads/bogus" &&
test_must_fail git fsck 2>out &&
cat out &&
- grep "error in commit $new.* - bad name" out
+ test_i18ngrep "error in commit $new.* - bad name" out
'
test_expect_success 'missing email is reported nicely' '
test_when_finished "git update-ref -d refs/heads/bogus" &&
test_must_fail git fsck 2>out &&
cat out &&
- grep "error in commit $new.* - missing email" out
+ test_i18ngrep "error in commit $new.* - missing email" out
'
test_expect_success '> in name is reported' '
test_when_finished "git update-ref -d refs/heads/bogus" &&
test_must_fail git fsck 2>out &&
cat out &&
- grep "error in commit $new" out
+ test_i18ngrep "error in commit $new" out
'
# date is 2^64 + 1
test_when_finished "git update-ref -d refs/heads/bogus" &&
test_must_fail git fsck 2>out &&
cat out &&
- grep "error in commit $new.*integer overflow" out
+ test_i18ngrep "error in commit $new.*integer overflow" out
'
test_expect_success 'commit with NUL in header' '
test_when_finished "git update-ref -d refs/heads/bogus" &&
test_must_fail git fsck 2>out &&
cat out &&
- grep "error in commit $new.*unterminated header: NUL at offset" out
+ test_i18ngrep "error in commit $new.*unterminated header: NUL at offset" out
'
test_expect_success 'tree object with duplicate entries' '
git hash-object -w -t tree --stdin
) &&
test_must_fail git fsck 2>out &&
- grep "error in tree .*contains duplicate file entries" out
+ test_i18ngrep "error in tree .*contains duplicate file entries" out
'
test_expect_success 'unparseable tree object' '
test_when_finished "git update-ref -d refs/tags/invalid" &&
test_must_fail git fsck --tags >out &&
cat out &&
- grep "broken link" out
+ test_i18ngrep "broken link" out
'
test_expect_success 'tag pointing to something else than its type' '
warning in tag $tag: badTagName: invalid '\''tag'\'' name: wrong name format
warning in tag $tag: missingTaggerEntry: invalid format - expected '\''tagger'\'' line
EOF
- test_cmp expect out
+ test_i18ncmp expect out
'
test_expect_success 'tag with bad tagger' '
echo $tag >.git/refs/tags/wrong &&
test_when_finished "git update-ref -d refs/tags/wrong" &&
test_must_fail git fsck --tags 2>out &&
- grep "error in tag .*: invalid author/committer" out
+ test_i18ngrep "error in tag .*: invalid author/committer" out
'
test_expect_success 'tag with NUL in header' '
test_when_finished "git update-ref -d refs/tags/wrong" &&
test_must_fail git fsck --tags 2>out &&
cat out &&
- grep "error in tag $tag.*unterminated header: NUL at offset" out
+ test_i18ngrep "error in tag $tag.*unterminated header: NUL at offset" out
'
test_expect_success 'cleaned up' '
git hash-object -w --stdin -t tree) &&
git fsck 2>out &&
cat out &&
- grep "warning.*null sha1" out
+ test_i18ngrep "warning.*null sha1" out
)
'
git hash-object -w --stdin -t tree) &&
git fsck 2>out &&
cat out &&
- grep "warning.*null sha1" out
+ test_i18ngrep "warning.*null sha1" out
)
'
bad_tree=$(git mktree <bad) &&
git fsck 2>out &&
cat out &&
- grep "warning.*tree $bad_tree" out
+ test_i18ngrep "warning.*tree $bad_tree" out
)'
done <<-\EOF
100644 blob
git branch bad $(cat name) &&
test_must_fail git -c fsck.nulInCommit=error fsck 2>warn.1 &&
- grep nulInCommit warn.1 &&
+ test_i18ngrep nulInCommit warn.1 &&
git fsck 2>warn.2 &&
- grep nulInCommit warn.2
+ test_i18ngrep nulInCommit warn.2
)
'
remove_object $(git rev-parse julius:caesar.t) &&
test_must_fail git fsck --name-objects >out &&
tree=$(git rev-parse --verify julius:) &&
- egrep "$tree \((refs/heads/master|HEAD)@\{[0-9]*\}:" out
+ test_i18ngrep -E "$tree \((refs/heads/master|HEAD)@\{[0-9]*\}:" out
)
'
mkdir alt.git/objects/12 &&
>alt.git/objects/12/34567890123456789012345678901234567890 &&
test_must_fail git fsck >out 2>&1 &&
- grep alt.git out
+ test_i18ngrep alt.git out
'
test_expect_success 'fsck errors in packed objects' '
remove_object $one &&
remove_object $two &&
test_must_fail git fsck 2>out &&
- grep "error in commit $one.* - bad name" out &&
- grep "error in commit $two.* - bad name" out &&
+ test_i18ngrep "error in commit $one.* - bad name" out &&
+ test_i18ngrep "error in commit $two.* - bad name" out &&
! grep corrupt out
'
git fsck >actual &&
# the output order is non-deterministic, as it comes from a hash
sort <actual >actual.sorted &&
- test_cmp expect actual.sorted
+ test_i18ncmp expect actual.sorted
)
'
test_when_finished "mv .git/index.backup .git/index" &&
corrupt_index_checksum &&
test_must_fail git fsck --cache 2>errors &&
- grep "bad index file" errors
+ test_i18ngrep "bad index file" errors
'
test_done
done
'
+test_expect_success 'cat-file --batch and --batch-check show ambiguous' '
+ echo "0000 ambiguous" >expect &&
+ echo 0000 | git cat-file --batch-check >actual 2>err &&
+ test_cmp expect actual &&
+ test_i18ngrep hint: err &&
+ echo 0000 | git cat-file --batch >actual 2>err &&
+ test_cmp expect actual &&
+ test_i18ngrep hint: err
+'
+
test_done
test_dirty_mergeable
'
+test_expect_success 'checkout -b after clone --no-checkout does a checkout of HEAD' '
+ git init src &&
+ test_commit -C src a &&
+ rev="$(git -C src rev-parse HEAD)" &&
+ git clone --no-checkout src dest &&
+ git -C dest checkout "$rev" -b branch &&
+ test_path_is_file dest/a.t
+'
+
test_done
test_cmp expect actual
'
+test_expect_success 'reject when arg could be part of dwim branch' '
+ git remote add foo file://non-existent-place &&
+ git update-ref refs/remotes/foo/dwim-arg HEAD &&
+ echo foo >dwim-arg &&
+ git add dwim-arg &&
+ echo bar >dwim-arg &&
+ test_must_fail git checkout dwim-arg &&
+ test_must_fail git rev-parse refs/heads/dwim-arg -- &&
+ grep bar dwim-arg
+'
+
+test_expect_success 'disambiguate dwim branch and checkout path (1)' '
+ git update-ref refs/remotes/foo/dwim-arg1 HEAD &&
+ echo foo >dwim-arg1 &&
+ git add dwim-arg1 &&
+ echo bar >dwim-arg1 &&
+ git checkout -- dwim-arg1 &&
+ test_must_fail git rev-parse refs/heads/dwim-arg1 -- &&
+ grep foo dwim-arg1
+'
+
+test_expect_success 'disambiguate dwim branch and checkout path (2)' '
+ git update-ref refs/remotes/foo/dwim-arg2 HEAD &&
+ echo foo >dwim-arg2 &&
+ git add dwim-arg2 &&
+ echo bar >dwim-arg2 &&
+ git checkout dwim-arg2 -- &&
+ git rev-parse refs/heads/dwim-arg2 -- &&
+ grep bar dwim-arg2
+'
+
test_done
git worktree move --force --force flump ploof
'
+test_expect_success 'move a repo with uninitialized submodule' '
+ git init withsub &&
+ (
+ cd withsub &&
+ test_commit initial &&
+ git submodule add "$PWD"/.git sub &&
+ git commit -m withsub &&
+ git worktree add second HEAD &&
+ git worktree move second third
+ )
+'
+
+test_expect_success 'not move a repo with initialized submodule' '
+ (
+ cd withsub &&
+ git -C third submodule update &&
+ test_must_fail git worktree move third forth
+ )
+'
+
test_expect_success 'remove main worktree' '
test_must_fail git worktree remove .
'
)
'
+test_expect_success 'remove a repo with uninitialized submodule' '
+ (
+ cd withsub &&
+ git worktree add to-remove HEAD &&
+ git worktree remove to-remove
+ )
+'
+
+test_expect_success 'not remove a repo with initialized submodule' '
+ (
+ cd withsub &&
+ git worktree add to-remove HEAD &&
+ git -C to-remove submodule update &&
+ test_must_fail git worktree remove to-remove
+ )
+'
+
test_done
! grep "Maybe git-rebase is broken" actual
'
+test_expect_success 'implicit interactive rebase does not invoke sequence editor' '
+ test_when_finished "git rebase --abort ||:" &&
+ GIT_SEQUENCE_EDITOR="echo bad >" git rebase -x"echo one" @^
+'
+
test_expect_success 'no changes are a nop' '
git checkout branch2 &&
set_fake_editor &&
git tag start
'
-cat >expect <<\EOF
-Already applied: 0001 A
-Already applied: 0002 B
-Committed: 0003 Z
-EOF
-
test_expect_success 'rebase -m' '
git rebase -m master >report &&
+ >expect &&
sed -n -e "/^Already applied: /p" \
-e "/^Committed: /p" report >actual &&
test_cmp expect actual
test_path_is_file execed
'
+test_expect_success '--reschedule-failed-exec' '
+ test_when_finished "git rebase --abort" &&
+ test_must_fail git rebase -x false --reschedule-failed-exec HEAD^ &&
+ grep "^exec false" .git/rebase-merge/git-rebase-todo &&
+ git rebase --abort &&
+ test_must_fail git -c rebase.rescheduleFailedExec=true \
+ rebase -x false HEAD^ 2>err &&
+ grep "^exec false" .git/rebase-merge/git-rebase-todo &&
+ test_i18ngrep "has been rescheduled" err &&
+ git rebase --abort &&
+ test_must_fail git rebase -y false HEAD^ 2>err &&
+ test_i18ngrep "has been rescheduled" err
+'
+
test_done
EOF
}
-create_expected_success_merge () {
- cat >expected <<-EOF
- $(grep "^Created autostash: [0-9a-f][0-9a-f]*\$" actual)
- HEAD is now at $(git rev-parse --short feature-branch) third commit
- First, rewinding head to replay your work on top of it...
- Merging unrelated-onto-branch with HEAD~1
- Merging:
- $(git rev-parse --short unrelated-onto-branch) unrelated commit
- $(git rev-parse --short feature-branch^) second commit
- found 1 common ancestor:
- $(git rev-parse --short feature-branch~2) initial commit
- [detached HEAD $(git rev-parse --short rebased-feature-branch~1)] second commit
- Author: A U Thor <author@example.com>
- Date: Thu Apr 7 15:14:13 2005 -0700
- 2 files changed, 2 insertions(+)
- create mode 100644 file1
- create mode 100644 file2
- Committed: 0001 second commit
- Merging unrelated-onto-branch with HEAD~0
- Merging:
- $(git rev-parse --short rebased-feature-branch~1) second commit
- $(git rev-parse --short feature-branch) third commit
- found 1 common ancestor:
- $(git rev-parse --short feature-branch~1) second commit
- [detached HEAD $(git rev-parse --short rebased-feature-branch)] third commit
- Author: A U Thor <author@example.com>
- Date: Thu Apr 7 15:15:13 2005 -0700
- 1 file changed, 1 insertion(+)
- create mode 100644 file3
- Committed: 0002 third commit
- All done.
- Applied autostash.
- EOF
-}
-
create_expected_failure_am () {
cat >expected <<-EOF
$(grep "^Created autostash: [0-9a-f][0-9a-f]*\$" actual)
EOF
}
-create_expected_failure_merge () {
- cat >expected <<-EOF
- $(grep "^Created autostash: [0-9a-f][0-9a-f]*\$" actual)
- HEAD is now at $(git rev-parse --short feature-branch) third commit
- First, rewinding head to replay your work on top of it...
- Merging unrelated-onto-branch with HEAD~1
- Merging:
- $(git rev-parse --short unrelated-onto-branch) unrelated commit
- $(git rev-parse --short feature-branch^) second commit
- found 1 common ancestor:
- $(git rev-parse --short feature-branch~2) initial commit
- [detached HEAD $(git rev-parse --short rebased-feature-branch~1)] second commit
- Author: A U Thor <author@example.com>
- Date: Thu Apr 7 15:14:13 2005 -0700
- 2 files changed, 2 insertions(+)
- create mode 100644 file1
- create mode 100644 file2
- Committed: 0001 second commit
- Merging unrelated-onto-branch with HEAD~0
- Merging:
- $(git rev-parse --short rebased-feature-branch~1) second commit
- $(git rev-parse --short feature-branch) third commit
- found 1 common ancestor:
- $(git rev-parse --short feature-branch~1) second commit
- [detached HEAD $(git rev-parse --short rebased-feature-branch)] third commit
- Author: A U Thor <author@example.com>
- Date: Thu Apr 7 15:15:13 2005 -0700
- 1 file changed, 1 insertion(+)
- create mode 100644 file3
- Committed: 0002 third commit
- All done.
- Applying autostash resulted in conflicts.
- Your changes are safe in the stash.
- You can run "git stash pop" or "git stash drop" at any time.
- EOF
-}
-
testrebase () {
type=$1
dotest=$2
test_expect_success "rebase$type --autostash: check output" '
test_when_finished git branch -D rebased-feature-branch &&
suffix=${type#\ --} && suffix=${suffix:-am} &&
+ if test ${suffix} = "merge"; then
+ suffix=interactive
+ fi &&
create_expected_success_$suffix &&
test_i18ncmp expected actual
'
test_expect_success "rebase$type: check output with conflicting stash" '
test_when_finished git branch -D rebased-feature-branch &&
suffix=${type#\ --} && suffix=${suffix:-am} &&
+ if test ${suffix} = "merge"; then
+ suffix=interactive
+ fi &&
create_expected_failure_$suffix &&
test_i18ncmp expected actual
'
"
}
test_run_rebase success ''
-test_run_rebase failure -m
+test_run_rebase success -m
test_run_rebase success -i
test_have_prereq !REBASE_P || test_run_rebase success -p
"
}
test_run_rebase success ''
-test_run_rebase failure -m
+test_run_rebase success -m
test_run_rebase success -i
test_have_prereq !REBASE_P || test_run_rebase success -p
"
}
test_run_rebase success ''
-test_run_rebase failure -m
+test_run_rebase success -m
test_run_rebase success -i
test_have_prereq !REBASE_P || test_run_rebase success -p
"
}
test_run_rebase success ''
-test_run_rebase failure -m
+test_run_rebase success -m
test_run_rebase success -i
test_have_prereq !REBASE_P || test_run_rebase success -p
"
}
test_run_rebase success ''
-test_run_rebase failure -m
+test_run_rebase success -m
test_run_rebase success -i
test_have_prereq !REBASE_P || test_run_rebase failure -p
test_linear_range "\'"$expected"\'" d..
"
}
-#TODO: make order consistent across all flavors of rebase
-test_run_rebase success 'e n o' ''
-test_run_rebase success 'e n o' -m
+test_run_rebase success 'n o e' ''
+test_run_rebase success 'n o e' -m
test_run_rebase success 'n o e' -i
test_run_rebase () {
test_linear_range "\'"$expected"\'" c..
"
}
-#TODO: make order consistent across all flavors of rebase
-test_run_rebase success 'd e n o' ''
-test_run_rebase success 'd e n o' -m
+test_run_rebase success 'd n o e' ''
+test_run_rebase success 'd n o e' -m
test_run_rebase success 'd n o e' -i
test_run_rebase () {
test_linear_range "\'"$expected"\'" c..
"
}
-#TODO: make order consistent across all flavors of rebase
-test_run_rebase success 'd e n o' ''
-test_run_rebase success 'd e n o' -m
+test_run_rebase success 'd n o e' ''
+test_run_rebase success 'd n o e' -m
test_run_rebase success 'd n o e' -i
if ! test_have_prereq REBASE_P; then
: >dont-overwrite-untracked.t &&
echo "reset refs/tags/dont-overwrite-untracked" >script-from-scratch &&
test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
- test_must_fail git rebase -r HEAD &&
+ test_must_fail git rebase -ir HEAD &&
git rebase --abort
'
test_expect_code 129 git cherry-pick -m 0 b
'
-test_expect_success 'cherry-pick a non-merge with -m should fail' '
+test_expect_success 'cherry-pick explicit first parent of a non-merge' '
git reset --hard &&
git checkout a^0 &&
- test_expect_code 128 git cherry-pick -m 1 b &&
- git diff --exit-code a --
+ git cherry-pick -m 1 b &&
+ git diff --exit-code c --
'
'
-test_expect_success 'revert a non-merge with -m should fail' '
+test_expect_success 'revert explicit first parent of a non-merge' '
git reset --hard &&
git checkout c^0 &&
- test_must_fail git revert -m 1 b &&
- git diff --exit-code c
+ git revert -m 1 b &&
+ git diff --exit-code a --
'
git checkout -b new A
'
-test_expect_success 'cherry-pick a non-merge with --ff and -m should fail' '
+test_expect_success 'cherry-pick explicit first parent of a non-merge with --ff' '
git reset --hard A -- &&
- test_must_fail git cherry-pick --ff -m 1 B &&
- git diff --exit-code A --
+ git cherry-pick --ff -m 1 B &&
+ git diff --exit-code C --
'
test_expect_success 'cherry pick a merge with --ff but without -m should fail' '
test_expect_success 'cherry-pick persists opts correctly' '
pristine_detach initial &&
- test_expect_code 128 git cherry-pick -s -m 1 --strategy=recursive -X patience -X ours initial..anotherpick &&
+ # to make sure that the session to cherry-pick a sequence
+ # gets interrupted, use a high-enough number that is larger
+ # than the number of parents of any commit we have created
+ mainline=4 &&
+ test_expect_code 128 git cherry-pick -s -m $mainline --strategy=recursive -X patience -X ours initial..anotherpick &&
test_path_is_dir .git/sequencer &&
test_path_is_file .git/sequencer/head &&
test_path_is_file .git/sequencer/todo &&
echo "true" >expect &&
git config --file=.git/sequencer/opts --get-all options.signoff >actual &&
test_cmp expect actual &&
- echo "1" >expect &&
+ echo "$mainline" >expect &&
git config --file=.git/sequencer/opts --get-all options.mainline >actual &&
test_cmp expect actual &&
echo "recursive" >expect &&
test $(git ls-files --stage | grep ^100755 | wc -l) -eq 0
'
+test_expect_success CASE_INSENSITIVE_FS 'path is case-insensitive' '
+ path="$(pwd)/BLUB" &&
+ touch "$path" &&
+ downcased="$(echo "$path" | tr A-Z a-z)" &&
+ git add "$downcased"
+'
+
test_done
test_path_is_file subdir/untracked
'
+test_expect_success 'stash works when user.name and user.email are not set' '
+ git reset &&
+ >1 &&
+ git add 1 &&
+ echo "$GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL>" >expect &&
+ git stash &&
+ git show -s --format="%an <%ae>" refs/stash >actual &&
+ test_cmp expect actual &&
+ >2 &&
+ git add 2 &&
+ test_config user.useconfigonly true &&
+ test_config stash.usebuiltin true &&
+ (
+ sane_unset GIT_AUTHOR_NAME &&
+ sane_unset GIT_AUTHOR_EMAIL &&
+ sane_unset GIT_COMMITTER_NAME &&
+ sane_unset GIT_COMMITTER_EMAIL &&
+ test_unconfig user.email &&
+ test_unconfig user.name &&
+ test_must_fail git commit -m "should fail" &&
+ echo "git stash <git@stash>" >expect &&
+ >2 &&
+ git stash &&
+ git show -s --format="%an <%ae>" refs/stash >actual &&
+ test_cmp expect actual
+ )
+'
+
test_done
git commit -m binbin
'
-# test_expect_success '--stat output after text chmod' '
-# test_chmod -x rezrov &&
-# echo " 0 files changed" >expect &&
-# git diff HEAD --stat >actual &&
-# test_i18ncmp expect actual
-# '
-#
-# test_expect_success '--shortstat output after text chmod' '
-# git diff HEAD --shortstat >actual &&
-# test_i18ncmp expect actual
-# '
-#
-# test_expect_success '--stat output after binary chmod' '
-# test_chmod +x binbin &&
-# echo " 0 files changed" >expect &&
-# git diff HEAD --stat >actual &&
-# test_i18ncmp expect actual
-# '
-#
-# test_expect_success '--shortstat output after binary chmod' '
-# git diff HEAD --shortstat >actual &&
-# test_i18ncmp expect actual
-# '
+test_expect_success '--stat output after text chmod' '
+ test_chmod -x rezrov &&
+ cat >expect <<-\EOF &&
+ rezrov | 0
+ 1 file changed, 0 insertions(+), 0 deletions(-)
+ EOF
+ git diff HEAD --stat >actual &&
+ test_i18ncmp expect actual
+'
+
+test_expect_success '--shortstat output after text chmod' '
+ tail -n 1 <expect >expect.short &&
+ git diff HEAD --shortstat >actual &&
+ test_i18ncmp expect.short actual
+'
+
+test_expect_success '--stat output after binary chmod' '
+ test_chmod +x binbin &&
+ cat >expect <<-EOF &&
+ binbin | Bin
+ rezrov | 0
+ 2 files changed, 0 insertions(+), 0 deletions(-)
+ EOF
+ git diff HEAD --stat >actual &&
+ test_i18ncmp expect actual
+'
+
+test_expect_success '--shortstat output after binary chmod' '
+ tail -n 1 <expect >expect.short &&
+ git diff HEAD --shortstat >actual &&
+ test_i18ncmp expect.short actual
+'
test_done
git commit -m "update mode" &&
git checkout -f master &&
+ # Same merge as master, but with parents reversed. Hide it in a
+ # pseudo-ref to avoid impacting tests with --all.
+ commit=$(echo reverse |
+ git commit-tree -p master^2 -p master^1 master^{tree}) &&
+ git update-ref REVERSE $commit &&
+
git config diff.renames false &&
git show-branch
# stat summary should show the diffstat and summary with the first parent
diff-tree -c --stat --summary side
diff-tree --cc --stat --summary side
+diff-tree --cc --shortstat master
+diff-tree --cc --summary REVERSE
# improved by Timo's patch
diff-tree --cc --patch-with-stat master
# improved by Timo's patch
diff --dirstat master~1 master~2
diff --dirstat initial rearrange
diff --dirstat-by-file initial rearrange
+diff --dirstat --cc master~1 master
# No-index --abbrev and --no-abbrev
diff --raw initial
:noellipses diff --raw initial
--- /dev/null
+$ git diff-tree --cc --shortstat master
+59d314ad6f356dd08601a4cd5e530381da3e3c64
+ 2 files changed, 5 insertions(+)
+$
--- /dev/null
+$ git diff-tree --cc --summary REVERSE
+2562325a7ee916efb2481da93073b82cec801cbc
+ create mode 100644 file1
+ delete mode 100644 file2
+ delete mode 100644 file3
+$
--- /dev/null
+$ git diff --dirstat --cc master~1 master
+ 40.0% dir/
+$
<BOLD;MAGENTA>-a long line to exceed per-line minimum<RESET>
<BOLD;MAGENTA>-another long line to exceed per-line minimum<RESET>
<RED>-original file<RESET>
- <BOLD;YELLOW>+<RESET>Q<BOLD;YELLOW>a long line to exceed per-line minimum<RESET>
- <BOLD;YELLOW>+<RESET>Q<BOLD;YELLOW>another long line to exceed per-line minimum<RESET>
+ <BOLD;CYAN>+<RESET>Q<BOLD;CYAN>a long line to exceed per-line minimum<RESET>
+ <BOLD;CYAN>+<RESET>Q<BOLD;CYAN>another long line to exceed per-line minimum<RESET>
<GREEN>+<RESET><GREEN>new file<RESET>
EOF
test_cmp expected actual
QQQthat has similar lines
QQQto previous blocks, but with different indent
QQQYetQAnotherQoutlierQ
+ QLine with internal w h i t e s p a c e change
EOF
git add text.txt &&
QQthat has similar lines
QQto previous blocks, but with different indent
QQYetQAnotherQoutlier
+ QLine with internal whitespace change
EOF
git diff --color --color-moved --color-moved-ws=allow-indentation-change >actual.raw &&
<BOLD>diff --git a/text.txt b/text.txt<RESET>
<BOLD>--- a/text.txt<RESET>
<BOLD>+++ b/text.txt<RESET>
- <CYAN>@@ -1,14 +1,14 @@<RESET>
+ <CYAN>@@ -1,15 +1,15 @@<RESET>
<BOLD;MAGENTA>-QIndented<RESET>
<BOLD;MAGENTA>-QText across<RESET>
<BOLD;MAGENTA>-Qsome lines<RESET>
<BOLD;MAGENTA>-QQQthat has similar lines<RESET>
<BOLD;MAGENTA>-QQQto previous blocks, but with different indent<RESET>
<RED>-QQQYetQAnotherQoutlierQ<RESET>
+ <RED>-QLine with internal w h i t e s p a c e change<RESET>
<BOLD;CYAN>+<RESET>QQ<BOLD;CYAN>Indented<RESET>
<BOLD;CYAN>+<RESET>QQ<BOLD;CYAN>Text across<RESET>
<BOLD;CYAN>+<RESET>QQ<BOLD;CYAN>some lines<RESET>
<BOLD;CYAN>+<RESET>QQ<BOLD;CYAN>that has similar lines<RESET>
<BOLD;CYAN>+<RESET>QQ<BOLD;CYAN>to previous blocks, but with different indent<RESET>
<GREEN>+<RESET>QQ<GREEN>YetQAnotherQoutlier<RESET>
+ <GREEN>+<RESET>Q<GREEN>Line with internal whitespace change<RESET>
EOF
test_cmp expected actual
'
+test_expect_success 'bogus settings in move detection erroring out' '
+ test_must_fail git diff --color-moved=bogus 2>err &&
+ test_i18ngrep "must be one of" err &&
+ test_i18ngrep bogus err &&
+
+ test_must_fail git -c diff.colormoved=bogus diff 2>err &&
+ test_i18ngrep "must be one of" err &&
+ test_i18ngrep "from command-line config" err &&
+
+ test_must_fail git diff --color-moved-ws=bogus 2>err &&
+ test_i18ngrep "possible values" err &&
+ test_i18ngrep bogus err &&
+
+ test_must_fail git -c diff.colormovedws=bogus diff 2>err &&
+ test_i18ngrep "possible values" err &&
+ test_i18ngrep "from command-line config" err
+'
+
test_expect_success 'compare whitespace delta incompatible with other space options' '
test_must_fail git diff \
--color-moved-ws=allow-indentation-change,ignore-all-space \
test_i18ngrep allow-indentation-change err
'
+EMPTY=''
+test_expect_success 'compare mixed whitespace delta across moved blocks' '
+
+ git reset --hard &&
+ tr Q_ "\t " <<-EOF >text.txt &&
+ ${EMPTY}
+ ____too short without
+ ${EMPTY}
+ ___being grouped across blank line
+ ${EMPTY}
+ context
+ lines
+ to
+ anchor
+ ____Indented text to
+ _Q____be further indented by four spaces across
+ ____Qseveral lines
+ QQ____These two lines have had their
+ ____indentation reduced by four spaces
+ Qdifferent indentation change
+ ____too short
+ EOF
+
+ git add text.txt &&
+ git commit -m "add text.txt" &&
+
+ tr Q_ "\t " <<-EOF >text.txt &&
+ context
+ lines
+ to
+ anchor
+ QIndented text to
+ QQbe further indented by four spaces across
+ Q____several lines
+ ${EMPTY}
+ QQtoo short without
+ ${EMPTY}
+ Q_______being grouped across blank line
+ ${EMPTY}
+ Q_QThese two lines have had their
+ indentation reduced by four spaces
+ QQdifferent indentation change
+ __Qtoo short
+ EOF
+
+ git -c color.diff.whitespace="normal red" \
+ -c core.whitespace=space-before-tab \
+ diff --color --color-moved --ws-error-highlight=all \
+ --color-moved-ws=allow-indentation-change >actual.raw &&
+ grep -v "index" actual.raw | test_decode_color >actual &&
+
+ cat <<-\EOF >expected &&
+ <BOLD>diff --git a/text.txt b/text.txt<RESET>
+ <BOLD>--- a/text.txt<RESET>
+ <BOLD>+++ b/text.txt<RESET>
+ <CYAN>@@ -1,16 +1,16 @@<RESET>
+ <BOLD;MAGENTA>-<RESET>
+ <BOLD;MAGENTA>-<RESET><BOLD;MAGENTA> too short without<RESET>
+ <BOLD;MAGENTA>-<RESET>
+ <BOLD;MAGENTA>-<RESET><BOLD;MAGENTA> being grouped across blank line<RESET>
+ <BOLD;MAGENTA>-<RESET>
+ <RESET>context<RESET>
+ <RESET>lines<RESET>
+ <RESET>to<RESET>
+ <RESET>anchor<RESET>
+ <BOLD;MAGENTA>-<RESET><BOLD;MAGENTA> Indented text to<RESET>
+ <BOLD;MAGENTA>-<RESET><BRED> <RESET> <BOLD;MAGENTA> be further indented by four spaces across<RESET>
+ <BOLD;MAGENTA>-<RESET><BRED> <RESET> <BOLD;MAGENTA>several lines<RESET>
+ <BOLD;BLUE>-<RESET> <BOLD;BLUE> These two lines have had their<RESET>
+ <BOLD;BLUE>-<RESET><BOLD;BLUE> indentation reduced by four spaces<RESET>
+ <BOLD;MAGENTA>-<RESET> <BOLD;MAGENTA>different indentation change<RESET>
+ <RED>-<RESET><RED> too short<RESET>
+ <BOLD;CYAN>+<RESET> <BOLD;CYAN>Indented text to<RESET>
+ <BOLD;CYAN>+<RESET> <BOLD;CYAN>be further indented by four spaces across<RESET>
+ <BOLD;CYAN>+<RESET> <BOLD;CYAN> several lines<RESET>
+ <BOLD;YELLOW>+<RESET>
+ <BOLD;YELLOW>+<RESET> <BOLD;YELLOW>too short without<RESET>
+ <BOLD;YELLOW>+<RESET>
+ <BOLD;YELLOW>+<RESET> <BOLD;YELLOW> being grouped across blank line<RESET>
+ <BOLD;YELLOW>+<RESET>
+ <BOLD;CYAN>+<RESET> <BRED> <RESET> <BOLD;CYAN>These two lines have had their<RESET>
+ <BOLD;CYAN>+<RESET><BOLD;CYAN>indentation reduced by four spaces<RESET>
+ <BOLD;YELLOW>+<RESET> <BOLD;YELLOW>different indentation change<RESET>
+ <GREEN>+<RESET><BRED> <RESET> <GREEN>too short<RESET>
+ EOF
+
+ test_cmp expected actual
+'
+
test_done
--- /dev/null
+#!/bin/sh
+
+test_description='test combined/stat/moved interaction'
+. ./test-lib.sh
+
+# This test covers a weird 3-way interaction between "--cc -p", which will run
+# the combined diff code, along with "--stat", which will be computed as a
+# first-parent stat during the combined diff, and "--color-moved", which
+# enables the emitted_symbols list to store the diff in memory.
+
+test_expect_success 'set up history with a merge' '
+ test_commit A &&
+ test_commit B &&
+ git checkout -b side HEAD^ &&
+ test_commit C &&
+ git merge -m M master &&
+ test_commit D
+'
+
+test_expect_success 'log --cc -p --stat --color-moved' '
+ cat >expect <<-\EOF &&
+ commit D
+ ---
+ D.t | 1 +
+ 1 file changed, 1 insertion(+)
+
+ diff --git a/D.t b/D.t
+ new file mode 100644
+ index 0000000..1784810
+ --- /dev/null
+ +++ b/D.t
+ @@ -0,0 +1 @@
+ +D
+ commit M
+
+ B.t | 1 +
+ 1 file changed, 1 insertion(+)
+ commit C
+ ---
+ C.t | 1 +
+ 1 file changed, 1 insertion(+)
+
+ diff --git a/C.t b/C.t
+ new file mode 100644
+ index 0000000..3cc58df
+ --- /dev/null
+ +++ b/C.t
+ @@ -0,0 +1 @@
+ +C
+ commit B
+ ---
+ B.t | 1 +
+ 1 file changed, 1 insertion(+)
+
+ diff --git a/B.t b/B.t
+ new file mode 100644
+ index 0000000..223b783
+ --- /dev/null
+ +++ b/B.t
+ @@ -0,0 +1 @@
+ +B
+ commit A
+ ---
+ A.t | 1 +
+ 1 file changed, 1 insertion(+)
+
+ diff --git a/A.t b/A.t
+ new file mode 100644
+ index 0000000..f70f10e
+ --- /dev/null
+ +++ b/A.t
+ @@ -0,0 +1 @@
+ +A
+ EOF
+ git log --format="commit %s" --cc -p --stat --color-moved >actual &&
+ test_cmp expect actual
+'
+
+test_done
test_cmp expect actual
'
+test_expect_success 'set up %S tests' '
+ git checkout --orphan source-a &&
+ test_commit one &&
+ test_commit two &&
+ git checkout -b source-b HEAD^ &&
+ test_commit three
+'
+
+test_expect_success 'log --format=%S paints branch names' '
+ cat >expect <<-\EOF &&
+ source-b
+ source-a
+ source-b
+ EOF
+ git log --format=%S source-a source-b >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'log --format=%S paints tag names' '
+ git tag -m tagged source-tag &&
+ cat >expect <<-\EOF &&
+ source-tag
+ source-a
+ source-tag
+ EOF
+ git log --format=%S source-tag source-a >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'log --format=%S paints symmetric ranges' '
+ cat >expect <<-\EOF &&
+ source-b
+ source-a
+ EOF
+ git log --format=%S source-a...source-b >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success '%S in git log --format works with other placeholders (part 1)' '
+ git log --format="source-b %h" source-b >expect &&
+ git log --format="%S %h" source-b >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success '%S in git log --format works with other placeholders (part 2)' '
+ git log --format="%h source-b" source-b >expect &&
+ git log --format="%h %S" source-b >actual &&
+ test_cmp expect actual
+'
+
test_done
rm .gitattributes
'
+test_expect_success 'setup log -[GS] binary & --text' '
+ git checkout --orphan GS-binary-and-text &&
+ git read-tree --empty &&
+ printf "a\na\0a\n" >data.bin &&
+ git add data.bin &&
+ git commit -m "create binary file" data.bin &&
+ printf "a\na\0a\n" >>data.bin &&
+ git commit -m "modify binary file" data.bin &&
+ git rm data.bin &&
+ git commit -m "delete binary file" data.bin &&
+ git log >full-log
+'
+
+test_expect_success 'log -G ignores binary files' '
+ git log -Ga >log &&
+ test_must_be_empty log
+'
+
+test_expect_success 'log -G looks into binary files with -a' '
+ git log -a -Ga >log &&
+ test_cmp log full-log
+'
+
+test_expect_success 'log -G looks into binary files with textconv filter' '
+ test_when_finished "rm .gitattributes" &&
+ echo "* diff=bin" >.gitattributes &&
+ git -c diff.bin.textconv=cat log -Ga >log &&
+ test_cmp log full-log
+'
+
+test_expect_success 'log -S looks into binary files' '
+ git log -Sa >log &&
+ test_cmp log full-log
+'
+
test_done
test_bad_opts () {
test_expect_success "invalid args: $1" "
test_must_fail git log $1 2>errors &&
- grep '$2' errors
+ test_i18ngrep '$2' errors
"
}
test_description='test corner cases of git-archive'
. ./test-lib.sh
-test_expect_success 'create commit with empty tree' '
- git commit --allow-empty -m foo
+# the 10knuls.tar file is used to test for an empty git generated tar
+# without having to invoke tar because an otherwise valid empty GNU tar
+# will be considered broken by {Open,Net}BSD tar
+test_expect_success 'create commit with empty tree and fake empty tar' '
+ git commit --allow-empty -m foo &&
+ perl -e "print \"\\0\" x 10240" >10knuls.tar
'
# Make a dir and clean it up afterwards
test_expect_success 'tar archive of empty tree is empty' '
git archive --format=tar HEAD: >empty.tar &&
- perl -e "print \"\\0\" x 10240" >10knuls.tar &&
test_cmp_bin 10knuls.tar empty.tar
'
test_expect_success 'archive empty subtree with no pathspec' '
git archive --format=tar $root_tree >subtree-all.tar &&
- make_dir extract &&
- "$TAR" xf subtree-all.tar -C extract &&
- check_dir extract
+ test_cmp_bin 10knuls.tar subtree-all.tar
'
test_expect_success 'archive empty subtree by direct pathspec' '
git archive --format=tar $root_tree -- sub >subtree-path.tar &&
- make_dir extract &&
- "$TAR" xf subtree-path.tar -C extract &&
- check_dir extract
+ test_cmp_bin 10knuls.tar subtree-path.tar
'
ZIPINFO=zipinfo
cd "$TRASH_DIRECTORY/full" &&
git commit-graph write &&
test_path_is_file $objdir/info/commit-graph &&
- graph_read_expect "10" "large_edges"
+ graph_read_expect "10" "extra_edges"
'
graph_git_behavior 'merge 1 vs 2' full merge/1 merge/2
cd "$TRASH_DIRECTORY/full" &&
git commit-graph write &&
test_path_is_file $objdir/info/commit-graph &&
- graph_read_expect "11" "large_edges"
+ graph_read_expect "11" "extra_edges"
'
graph_git_behavior 'full graph, commit 8 vs merge 1' full commits/8 merge/1
cd "$TRASH_DIRECTORY/full" &&
git commit-graph write &&
test_path_is_file $objdir/info/commit-graph &&
- graph_read_expect "11" "large_edges"
+ graph_read_expect "11" "extra_edges"
'
graph_git_behavior 'cleared graph, commit 8 vs merge 1' full commits/8 merge/1
cd "$TRASH_DIRECTORY/full" &&
cat new-idx | git commit-graph write --stdin-packs &&
test_path_is_file $objdir/info/commit-graph &&
- graph_read_expect "9" "large_edges"
+ graph_read_expect "9" "extra_edges"
'
graph_git_behavior 'graph from pack, commit 8 vs merge 1' full commits/8 merge/1
cd "$TRASH_DIRECTORY/full" &&
git rev-parse merge/3 | git commit-graph write --stdin-commits --append &&
test_path_is_file $objdir/info/commit-graph &&
- graph_read_expect "10" "large_edges"
+ graph_read_expect "10" "extra_edges"
'
graph_git_behavior 'append graph, commit 8 vs merge 1' full commits/8 merge/1
cd "$TRASH_DIRECTORY/full" &&
git commit-graph write --reachable &&
test_path_is_file $objdir/info/commit-graph &&
- graph_read_expect "11" "large_edges"
+ graph_read_expect "11" "extra_edges"
'
graph_git_behavior 'append graph, commit 8 vs merge 1' full commits/8 merge/1
cd "$TRASH_DIRECTORY/bare" &&
git commit-graph write &&
test_path_is_file $baredir/info/commit-graph &&
- graph_read_expect "11" "large_edges"
+ graph_read_expect "11" "extra_edges"
'
graph_git_behavior 'bare repo with graph, commit 8 vs merge 1' bare commits/8 merge/1
GRAPH_BYTE_OCTOPUS=$(($GRAPH_OCTOPUS_DATA_OFFSET + 4))
GRAPH_BYTE_FOOTER=$(($GRAPH_OCTOPUS_DATA_OFFSET + 4 * $NUM_OCTOPUS_EDGES))
-# usage: corrupt_graph_and_verify <position> <data> <string>
+# usage: corrupt_graph_and_verify <position> <data> <string> [<zero_pos>]
# Manipulates the commit-graph file at the position
-# by inserting the data, then runs 'git commit-graph verify'
+# by inserting the data, optionally zeroing the file
+# starting at <zero_pos>, then runs 'git commit-graph verify'
# and places the output in the file 'err'. Test 'err' for
# the given string.
corrupt_graph_and_verify() {
data="${2:-\0}"
grepstr=$3
cd "$TRASH_DIRECTORY/full" &&
+ orig_size=$(wc -c < $objdir/info/commit-graph) &&
+ zero_pos=${4:-${orig_size}} &&
test_when_finished mv commit-graph-backup $objdir/info/commit-graph &&
cp $objdir/info/commit-graph commit-graph-backup &&
printf "$data" | dd of="$objdir/info/commit-graph" bs=1 seek="$pos" conv=notrunc &&
+ dd of="$objdir/info/commit-graph" bs=1 seek="$zero_pos" count=0 &&
+ dd if=/dev/zero of="$objdir/info/commit-graph" bs=1 seek="$zero_pos" count=$(($orig_size - $zero_pos)) &&
test_must_fail git commit-graph verify 2>test_err &&
- grep -v "^+" test_err >err
+ grep -v "^+" test_err >err &&
test_i18ngrep "$grepstr" err
}
"incorrect checksum"
'
+test_expect_success 'detect incorrect chunk count' '
+ corrupt_graph_and_verify $GRAPH_BYTE_CHUNK_COUNT "\377" \
+ "chunk lookup table entry missing" $GRAPH_CHUNK_LOOKUP_OFFSET
+'
+
test_expect_success 'git fsck (checks commit-graph)' '
cd "$TRASH_DIRECTORY/full" &&
git fsck &&
--- /dev/null
+#!/bin/sh
+
+test_description='pack-objects object selection using sparse algorithm'
+. ./test-lib.sh
+
+test_expect_success 'setup repo' '
+ test_commit initial &&
+ for i in $(test_seq 1 3)
+ do
+ mkdir f$i &&
+ for j in $(test_seq 1 3)
+ do
+ mkdir f$i/f$j &&
+ echo $j >f$i/f$j/data.txt
+ done
+ done &&
+ git add . &&
+ git commit -m "Initialized trees" &&
+ for i in $(test_seq 1 3)
+ do
+ git checkout -b topic$i master &&
+ echo change-$i >f$i/f$i/data.txt &&
+ git commit -a -m "Changed f$i/f$i/data.txt"
+ done &&
+ cat >packinput.txt <<-EOF &&
+ topic1
+ ^topic2
+ ^topic3
+ EOF
+ git rev-parse \
+ topic1 \
+ topic1^{tree} \
+ topic1:f1 \
+ topic1:f1/f1 \
+ topic1:f1/f1/data.txt | sort >expect_objects.txt
+'
+
+test_expect_success 'non-sparse pack-objects' '
+ git pack-objects --stdout --revs --no-sparse <packinput.txt >nonsparse.pack &&
+ git index-pack -o nonsparse.idx nonsparse.pack &&
+ git show-index <nonsparse.idx | awk "{print \$2}" >nonsparse_objects.txt &&
+ test_cmp expect_objects.txt nonsparse_objects.txt
+'
+
+test_expect_success 'sparse pack-objects' '
+ git pack-objects --stdout --revs --sparse <packinput.txt >sparse.pack &&
+ git index-pack -o sparse.idx sparse.pack &&
+ git show-index <sparse.idx | awk "{print \$2}" >sparse_objects.txt &&
+ test_cmp expect_objects.txt sparse_objects.txt
+'
+
+test_expect_success 'duplicate a folder from f3 and commit to topic1' '
+ git checkout topic1 &&
+ echo change-3 >f3/f3/data.txt &&
+ git commit -a -m "Changed f3/f3/data.txt" &&
+ git rev-parse \
+ topic1~1 \
+ topic1~1^{tree} \
+ topic1^{tree} \
+ topic1 \
+ topic1:f1 \
+ topic1:f1/f1 \
+ topic1:f1/f1/data.txt | sort >required_objects.txt
+'
+
+test_expect_success 'non-sparse pack-objects' '
+ git pack-objects --stdout --revs --no-sparse <packinput.txt >nonsparse.pack &&
+ git index-pack -o nonsparse.idx nonsparse.pack &&
+ git show-index <nonsparse.idx | awk "{print \$2}" >nonsparse_objects.txt &&
+ comm -1 -2 required_objects.txt nonsparse_objects.txt >nonsparse_required_objects.txt &&
+ test_cmp required_objects.txt nonsparse_required_objects.txt
+'
+
+test_expect_success 'sparse pack-objects' '
+ git pack-objects --stdout --revs --sparse <packinput.txt >sparse.pack &&
+ git index-pack -o sparse.idx sparse.pack &&
+ git show-index <sparse.idx | awk "{print \$2}" >sparse_objects.txt &&
+ comm -1 -2 required_objects.txt sparse_objects.txt >sparse_required_objects.txt &&
+ test_cmp required_objects.txt sparse_required_objects.txt
+'
+
+# Demonstrate that the algorithms differ when we copy a tree wholesale
+# from one folder to another.
+
+test_expect_success 'duplicate a folder from f1 into f3' '
+ mkdir f3/f4 &&
+ cp -r f1/f1/* f3/f4 &&
+ git add f3/f4 &&
+ git commit -m "Copied f1/f1 to f3/f4" &&
+ cat >packinput.txt <<-EOF &&
+ topic1
+ ^topic1~1
+ EOF
+ git rev-parse \
+ topic1 \
+ topic1^{tree} \
+ topic1:f3 | sort >required_objects.txt
+'
+
+test_expect_success 'non-sparse pack-objects' '
+ git pack-objects --stdout --revs --no-sparse <packinput.txt >nonsparse.pack &&
+ git index-pack -o nonsparse.idx nonsparse.pack &&
+ git show-index <nonsparse.idx | awk "{print \$2}" >nonsparse_objects.txt &&
+ comm -1 -2 required_objects.txt nonsparse_objects.txt >nonsparse_required_objects.txt &&
+ test_cmp required_objects.txt nonsparse_required_objects.txt
+'
+
+test_expect_success 'sparse pack-objects' '
+ git rev-parse \
+ topic1 \
+ topic1^{tree} \
+ topic1:f3 \
+ topic1:f3/f4 \
+ topic1:f3/f4/data.txt | sort >expect_sparse_objects.txt &&
+ git pack-objects --stdout --revs --sparse <packinput.txt >sparse.pack &&
+ git index-pack -o sparse.idx sparse.pack &&
+ git show-index <sparse.idx | awk "{print \$2}" >sparse_objects.txt &&
+ test_cmp expect_sparse_objects.txt sparse_objects.txt
+'
+
+test_expect_success 'pack.useSparse enables algorithm' '
+ git config pack.useSparse true &&
+ git pack-objects --stdout --revs <packinput.txt >sparse.pack &&
+ git index-pack -o sparse.idx sparse.pack &&
+ git show-index <sparse.idx | awk "{print \$2}" >sparse_objects.txt &&
+ test_cmp expect_sparse_objects.txt sparse_objects.txt
+'
+
+test_expect_success 'pack.useSparse overridden' '
+ git pack-objects --stdout --revs --no-sparse <packinput.txt >sparse.pack &&
+ git index-pack -o sparse.idx sparse.pack &&
+ git show-index <sparse.idx | awk "{print \$2}" >sparse_objects.txt &&
+ test_cmp required_objects.txt sparse_objects.txt
+'
+
+test_done
. ./test-lib.sh
test_expect_success setup '
- echo Data for commit0. >a &&
- echo Data for commit0. >b &&
- git update-index --add a &&
- git update-index --add b &&
- tree0=$(git write-tree) &&
- commit0=$(echo setup | git commit-tree $tree0) &&
- git update-ref refs/heads/master $commit0 &&
- git clone ./. clone1 &&
- git clone ./. clone2 &&
- GIT_DIR=clone2/.git git branch new2 &&
- echo Data for commit1. >clone2/b &&
- GIT_DIR=clone2/.git git add clone2/b &&
- GIT_DIR=clone2/.git git commit -m new2
-'
-
-for clone in 1 2; do
- cat >clone${clone}/.git/hooks/post-checkout <<'EOF'
-#!/bin/sh
-echo $@ > $GIT_DIR/post-checkout.args
-EOF
- chmod u+x clone${clone}/.git/hooks/post-checkout
-done
-
-test_expect_success 'post-checkout runs as expected ' '
- GIT_DIR=clone1/.git git checkout master &&
- test -e clone1/.git/post-checkout.args
+ mkdir -p .git/hooks &&
+ write_script .git/hooks/post-checkout <<-\EOF &&
+ echo "$@" >.git/post-checkout.args
+ EOF
+ test_commit one &&
+ test_commit two &&
+ test_commit rebase-on-me &&
+ git reset --hard HEAD^ &&
+ test_commit three
'
test_expect_success 'post-checkout receives the right arguments with HEAD unchanged ' '
- old=$(awk "{print \$1}" clone1/.git/post-checkout.args) &&
- new=$(awk "{print \$2}" clone1/.git/post-checkout.args) &&
- flag=$(awk "{print \$3}" clone1/.git/post-checkout.args) &&
+ test_when_finished "rm -f .git/post-checkout.args" &&
+ git checkout master &&
+ read old new flag <.git/post-checkout.args &&
test $old = $new && test $flag = 1
'
-test_expect_success 'post-checkout runs as expected ' '
- GIT_DIR=clone1/.git git checkout master &&
- test -e clone1/.git/post-checkout.args
-'
-
test_expect_success 'post-checkout args are correct with git checkout -b ' '
- GIT_DIR=clone1/.git git checkout -b new1 &&
- old=$(awk "{print \$1}" clone1/.git/post-checkout.args) &&
- new=$(awk "{print \$2}" clone1/.git/post-checkout.args) &&
- flag=$(awk "{print \$3}" clone1/.git/post-checkout.args) &&
+ test_when_finished "rm -f .git/post-checkout.args" &&
+ git checkout -b new1 &&
+ read old new flag <.git/post-checkout.args &&
test $old = $new && test $flag = 1
'
test_expect_success 'post-checkout receives the right args with HEAD changed ' '
- GIT_DIR=clone2/.git git checkout new2 &&
- old=$(awk "{print \$1}" clone2/.git/post-checkout.args) &&
- new=$(awk "{print \$2}" clone2/.git/post-checkout.args) &&
- flag=$(awk "{print \$3}" clone2/.git/post-checkout.args) &&
+ test_when_finished "rm -f .git/post-checkout.args" &&
+ git checkout two &&
+ read old new flag <.git/post-checkout.args &&
test $old != $new && test $flag = 1
'
test_expect_success 'post-checkout receives the right args when not switching branches ' '
- GIT_DIR=clone2/.git git checkout master b &&
- old=$(awk "{print \$1}" clone2/.git/post-checkout.args) &&
- new=$(awk "{print \$2}" clone2/.git/post-checkout.args) &&
- flag=$(awk "{print \$3}" clone2/.git/post-checkout.args) &&
+ test_when_finished "rm -f .git/post-checkout.args" &&
+ git checkout master -- three.t &&
+ read old new flag <.git/post-checkout.args &&
test $old = $new && test $flag = 0
'
-if test "$(git config --bool core.filemode)" = true; then
-mkdir -p templates/hooks
-cat >templates/hooks/post-checkout <<'EOF'
-#!/bin/sh
-echo $@ > $GIT_DIR/post-checkout.args
-EOF
-chmod +x templates/hooks/post-checkout
+test_expect_success 'post-checkout is triggered on rebase' '
+ test_when_finished "rm -f .git/post-checkout.args" &&
+ git checkout -b rebase-test master &&
+ 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_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_expect_success 'post-checkout hook is triggered by clone' '
+ mkdir -p templates/hooks &&
+ write_script templates/hooks/post-checkout <<-\EOF &&
+ echo "$@" >$GIT_DIR/post-checkout.args
+ EOF
git clone --template=templates . clone3 &&
test -f clone3/.git/post-checkout.args
'
-fi
test_done
git rebase --continue &&
echo rebase >expected.args &&
cat >expected.data <<-EOF &&
+ $(git rev-parse C) $(git rev-parse HEAD^)
$(git rev-parse D) $(git rev-parse HEAD)
EOF
verify_hook_input
echo rebase >expected.args &&
cat >expected.data <<-EOF &&
$(git rev-parse E) $(git rev-parse HEAD)
+ $(git rev-parse F) $(git rev-parse HEAD)
EOF
verify_hook_input
'
git rebase --continue &&
echo rebase >expected.args &&
cat >expected.data <<-EOF &&
+ $(git rev-parse C) $(git rev-parse HEAD^)
+ $(git rev-parse D) $(git rev-parse HEAD)
+ EOF
+ verify_hook_input
+'
+
+test_expect_success 'git rebase with implicit use of interactive backend' '
+ git reset --hard D &&
+ clear_hook_input &&
+ test_must_fail git rebase --keep --onto A B &&
+ echo C > foo &&
+ git add foo &&
+ git rebase --continue &&
+ echo rebase >expected.args &&
+ cat >expected.data <<-EOF &&
+ $(git rev-parse C) $(git rev-parse HEAD^)
+ $(git rev-parse D) $(git rev-parse HEAD)
+ EOF
+ verify_hook_input
+'
+
+test_expect_success 'git rebase --skip with implicit use of interactive backend' '
+ git reset --hard D &&
+ clear_hook_input &&
+ test_must_fail git rebase --keep --onto A B &&
+ test_must_fail git rebase --skip &&
+ echo D > foo &&
+ git add foo &&
+ git rebase --continue &&
+ echo rebase >expected.args &&
+ cat >expected.data <<-EOF &&
+ $(git rev-parse C) $(git rev-parse HEAD^)
$(git rev-parse D) $(git rev-parse HEAD)
EOF
verify_hook_input
echo " " "error: leading space"
echo " "
echo Err
+ echo SUCCESS
exit 0
EOF
echo 1 >file &&
grep "<BOLD;RED>error<RESET>: error" decoded &&
grep "<YELLOW>hint<RESET>:" decoded &&
grep "<BOLD;GREEN>success<RESET>:" decoded &&
+ grep "<BOLD;GREEN>SUCCESS<RESET>" decoded &&
grep "<BOLD;YELLOW>warning<RESET>:" decoded
'
) >input.dup
'
-test_expect_success 'fetch refs from cmdline' '
- (
- cd client &&
- git fetch-pack --no-progress .. $(cat ../input)
- ) >output &&
- cut -d " " -f 2 <output | sort >actual &&
- test_cmp expect actual
+test_expect_success 'setup fetch refs from cmdline v[12]' '
+ cp -r client client1 &&
+ cp -r client client2
'
+for version in '' 1 2
+do
+ test_expect_success "protocol.version=$version fetch refs from cmdline" "
+ (
+ cd client$version &&
+ GIT_TEST_PROTOCOL_VERSION=$version git fetch-pack --no-progress .. \$(cat ../input)
+ ) >output &&
+ cut -d ' ' -f 2 <output | sort >actual &&
+ test_cmp expect actual
+ "
+done
+
test_expect_success 'fetch refs from stdin' '
(
cd client &&
git remote add backup xyz@example.com
'
+test_expect_success 'unqualified <dst> refspec DWIM and advice' '
+ test_when_finished "(cd test && git tag -d some-tag)" &&
+ (
+ cd test &&
+ git tag -a -m "Some tag" some-tag master &&
+ exit_with=true &&
+ for type in commit tag tree blob
+ do
+ if test "$type" = "blob"
+ then
+ oid=$(git rev-parse some-tag:file)
+ else
+ oid=$(git rev-parse some-tag^{$type})
+ fi &&
+ test_must_fail git push origin $oid:dst 2>err &&
+ test_i18ngrep "error: The destination you" err &&
+ test_i18ngrep "hint: Did you mean" err &&
+ test_must_fail git -c advice.pushUnqualifiedRefName=false \
+ push origin $oid:dst 2>err &&
+ test_i18ngrep "error: The destination you" err &&
+ test_i18ngrep ! "hint: Did you mean" err ||
+ exit_with=false
+ done &&
+ $exit_with
+ )
+'
+
+test_expect_success 'refs/remotes/* <src> refspec and unqualified <dst> DWIM and advice' '
+ (
+ cd two &&
+ git tag -a -m "Some tag" my-tag master &&
+ git update-ref refs/trees/my-head-tree HEAD^{tree} &&
+ git update-ref refs/blobs/my-file-blob HEAD:file
+ ) &&
+ (
+ cd test &&
+ git config --add remote.two.fetch "+refs/tags/*:refs/remotes/tags-from-two/*" &&
+ git config --add remote.two.fetch "+refs/trees/*:refs/remotes/trees-from-two/*" &&
+ git config --add remote.two.fetch "+refs/blobs/*:refs/remotes/blobs-from-two/*" &&
+ git fetch --no-tags two &&
+
+ test_must_fail git push origin refs/remotes/two/another:dst 2>err &&
+ test_i18ngrep "error: The destination you" err &&
+
+ test_must_fail git push origin refs/remotes/tags-from-two/my-tag:dst-tag 2>err &&
+ test_i18ngrep "error: The destination you" err &&
+
+ test_must_fail git push origin refs/remotes/trees-from-two/my-head-tree:dst-tree 2>err &&
+ test_i18ngrep "error: The destination you" err &&
+
+ test_must_fail git push origin refs/remotes/blobs-from-two/my-file-blob:dst-blob 2>err &&
+ test_i18ngrep "error: The destination you" err
+ )
+'
+
test_done
grep refs/tags/magic actual
'
+test_expect_success 'protocol v2 supports hiderefs' '
+ test_config uploadpack.hiderefs refs/tags &&
+ git -c protocol.version=2 ls-remote . >actual &&
+ ! grep refs/tags actual
+'
+
test_expect_success 'ls-remote --symref' '
git fetch origin &&
cat >expect <<-EOF &&
# This test spawns a daemon, so run it only if the user would be OK with
# testing with git-daemon.
test_expect_success PIPE,JGIT,GIT_DAEMON 'indicate no refs in standards-compliant empty remote' '
- JGIT_DAEMON_PORT=${JGIT_DAEMON_PORT-${this_test#t}} &&
+ test_set_port JGIT_DAEMON_PORT &&
JGIT_DAEMON_PID= &&
git init --bare empty.git &&
>empty.git/git-daemon-export-ok &&
git config fetch.recurseSubmodules true &&
(
cd downstream &&
+ GIT_TRACE=$(pwd)/trace.out git fetch &&
+ grep "1 tasks" trace.out &&
GIT_TRACE=$(pwd)/trace.out git fetch --jobs 7 &&
grep "7 tasks" trace.out &&
git config submodule.fetchJobs 8 &&
test_cmp expect actual
'
+test_expect_success "fetch new submodule commits on-demand outside standard refspec" '
+ # add a second submodule and ensure it is around in downstream first
+ git clone submodule sub1 &&
+ git submodule add ./sub1 &&
+ git commit -m "adding a second submodule" &&
+ git -C downstream pull &&
+ git -C downstream submodule update --init --recursive &&
+
+ git checkout --detach &&
+
+ C=$(git -C submodule commit-tree -m "new change outside refs/heads" HEAD^{tree}) &&
+ git -C submodule update-ref refs/changes/1 $C &&
+ git update-index --cacheinfo 160000 $C submodule &&
+ test_tick &&
+
+ D=$(git -C sub1 commit-tree -m "new change outside refs/heads" HEAD^{tree}) &&
+ git -C sub1 update-ref refs/changes/2 $D &&
+ git update-index --cacheinfo 160000 $D sub1 &&
+
+ git commit -m "updated submodules outside of refs/heads" &&
+ E=$(git rev-parse HEAD) &&
+ git update-ref refs/changes/3 $E &&
+ (
+ cd downstream &&
+ git fetch --recurse-submodules origin refs/changes/3:refs/heads/my_branch &&
+ git -C submodule cat-file -t $C &&
+ git -C sub1 cat-file -t $D &&
+ git checkout --recurse-submodules FETCH_HEAD
+ )
+'
+
+test_expect_success 'fetch new submodule commit on-demand in FETCH_HEAD' '
+ # depends on the previous test for setup
+
+ C=$(git -C submodule commit-tree -m "another change outside refs/heads" HEAD^{tree}) &&
+ git -C submodule update-ref refs/changes/4 $C &&
+ git update-index --cacheinfo 160000 $C submodule &&
+ test_tick &&
+
+ D=$(git -C sub1 commit-tree -m "another change outside refs/heads" HEAD^{tree}) &&
+ git -C sub1 update-ref refs/changes/5 $D &&
+ git update-index --cacheinfo 160000 $D sub1 &&
+
+ git commit -m "updated submodules outside of refs/heads" &&
+ E=$(git rev-parse HEAD) &&
+ git update-ref refs/changes/6 $E &&
+ (
+ cd downstream &&
+ git fetch --recurse-submodules origin refs/changes/6 &&
+ git -C submodule cat-file -t $C &&
+ git -C sub1 cat-file -t $D &&
+ git checkout --recurse-submodules FETCH_HEAD
+ )
+'
+
+test_expect_success 'fetch new submodule commits on-demand without .gitmodules entry' '
+ # depends on the previous test for setup
+
+ git config -f .gitmodules --remove-section submodule.sub1 &&
+ git add .gitmodules &&
+ git commit -m "delete gitmodules file" &&
+ git checkout -B master &&
+ git -C downstream fetch &&
+ git -C downstream checkout origin/master &&
+
+ C=$(git -C submodule commit-tree -m "yet another change outside refs/heads" HEAD^{tree}) &&
+ git -C submodule update-ref refs/changes/7 $C &&
+ git update-index --cacheinfo 160000 $C submodule &&
+ test_tick &&
+
+ D=$(git -C sub1 commit-tree -m "yet another change outside refs/heads" HEAD^{tree}) &&
+ git -C sub1 update-ref refs/changes/8 $D &&
+ git update-index --cacheinfo 160000 $D sub1 &&
+
+ git commit -m "updated submodules outside of refs/heads" &&
+ E=$(git rev-parse HEAD) &&
+ git update-ref refs/changes/9 $E &&
+ (
+ cd downstream &&
+ git fetch --recurse-submodules origin refs/changes/9 &&
+ git -C submodule cat-file -t $C &&
+ git -C sub1 cat-file -t $D &&
+ git checkout --recurse-submodules FETCH_HEAD
+ )
+'
+
+test_expect_success 'fetch new submodule commit intermittently referenced by superproject' '
+ # depends on the previous test for setup
+
+ D=$(git -C sub1 commit-tree -m "change 10 outside refs/heads" HEAD^{tree}) &&
+ E=$(git -C sub1 commit-tree -m "change 11 outside refs/heads" HEAD^{tree}) &&
+ F=$(git -C sub1 commit-tree -m "change 12 outside refs/heads" HEAD^{tree}) &&
+
+ git -C sub1 update-ref refs/changes/10 $D &&
+ git update-index --cacheinfo 160000 $D sub1 &&
+ git commit -m "updated submodules outside of refs/heads" &&
+
+ git -C sub1 update-ref refs/changes/11 $E &&
+ git update-index --cacheinfo 160000 $E sub1 &&
+ git commit -m "updated submodules outside of refs/heads" &&
+
+ git -C sub1 update-ref refs/changes/12 $F &&
+ git update-index --cacheinfo 160000 $F sub1 &&
+ git commit -m "updated submodules outside of refs/heads" &&
+
+ G=$(git rev-parse HEAD) &&
+ git update-ref refs/changes/13 $G &&
+ (
+ cd downstream &&
+ git fetch --recurse-submodules origin refs/changes/13 &&
+
+ git -C sub1 cat-file -t $D &&
+ git -C sub1 cat-file -t $E &&
+ git -C sub1 cat-file -t $F
+ )
+'
+
test_done
"$(git -C "$REPO" rev-parse HEAD)" \
"$(git -C "$REPO" rev-parse HEAD^)" \
>"$HTTPD_ROOT_PATH/one-time-sed" &&
- test_must_fail git -C client fetch --depth=1 "$HTTPD_URL/one_time_sed/repo" \
+ test_must_fail env GIT_TEST_SIDEBAND_ALL=0 git -C client \
+ fetch --depth=1 "$HTTPD_URL/one_time_sed/repo" \
master:a_branch &&
# Ensure that the one-time-sed script was used.
git ls-remote "$GIT_DAEMON_URL/escape.git"
'
-test_expect_success 'daemon log records all attributes' '
- cat >expect <<-\EOF &&
- Extended attribute "host": localhost
- Extended attribute "protocol": version=1
- EOF
- >daemon.log &&
- GIT_OVERRIDE_VIRTUAL_HOST=localhost \
- git -c protocol.version=1 \
- ls-remote "$GIT_DAEMON_URL/interp.git" &&
- grep -i extended.attribute daemon.log | cut -d" " -f2- >actual &&
- test_cmp expect actual
-'
-
test_expect_success FAKENC 'hostname interpolation works after LF-stripping' '
{
printf "git-upload-pack /interp.git\n\0host=localhost" | packetize
git clone "file://$UNCPATH" clone
'
+test_expect_success 'clone with backslashed path' '
+ BACKSLASHED="$(echo "$UNCPATH" | tr / \\\\)" &&
+ git clone "$BACKSLASHED" backslashed
+'
+
test_expect_success push '
(
cd clone &&
--- /dev/null
+#!/bin/sh
+
+test_description='test GIT_CURL_VERBOSE'
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-httpd.sh
+start_httpd
+
+test_expect_success 'setup repository' '
+ mkdir "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
+ git -C "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" --bare init &&
+ git config push.default matching &&
+ echo content >file &&
+ git add file &&
+ git commit -m one &&
+ git remote add public "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
+ git push public master:master
+'
+
+test_expect_success 'failure in git-upload-pack is shown' '
+ test_might_fail env GIT_CURL_VERBOSE=1 \
+ git clone "$HTTPD_URL/error_git_upload_pack/smart/repo.git" \
+ 2>curl_log &&
+ grep "< HTTP/1.1 500 Intentional Breakage" curl_log
+'
+
+stop_httpd
+
+test_done
expect_ssh "$@"
}
-test_expect_success !MINGW 'clone c:temp is ssl' '
+test_expect_success !MINGW,!CYGWIN 'clone c:temp is ssl' '
test_clone_url c:temp c temp
'
test_cmp expect child/file
'
+test_expect_success 'clone -c remote.origin.fetch=<refspec> works' '
+ rm -rf child &&
+ git update-ref refs/grab/it refs/heads/master &&
+ git update-ref refs/leave/out refs/heads/master &&
+ git clone -c "remote.origin.fetch=+refs/grab/*:refs/grab/*" . child &&
+ git -C child for-each-ref --format="%(refname)" >actual &&
+
+ cat >expect <<-\EOF &&
+ refs/grab/it
+ refs/heads/master
+ refs/remotes/origin/HEAD
+ refs/remotes/origin/master
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'git -c remote.origin.fetch=<refspec> clone works' '
+ rm -rf child &&
+ git -c "remote.origin.fetch=+refs/grab/*:refs/grab/*" clone . child &&
+ git -C child for-each-ref --format="%(refname)" >actual &&
+
+ cat >expect <<-\EOF &&
+ refs/grab/it
+ refs/heads/master
+ refs/remotes/origin/HEAD
+ refs/remotes/origin/master
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'clone -c remote.<remote>.fetch=<refspec> --origin=<name>' '
+ rm -rf child &&
+ git clone --origin=upstream \
+ -c "remote.upstream.fetch=+refs/grab/*:refs/grab/*" \
+ -c "remote.origin.fetch=+refs/leave/*:refs/leave/*" \
+ . child &&
+ git -C child for-each-ref --format="%(refname)" >actual &&
+
+ cat >expect <<-\EOF &&
+ refs/grab/it
+ refs/heads/master
+ refs/remotes/upstream/HEAD
+ refs/remotes/upstream/master
+ EOF
+ test_cmp expect actual
+'
+
# Tests for the hidden file attribute on windows
is_hidden () {
# Use the output of `attrib`, ignore the absolute path
test_must_fail git -c protocol.version=2 clone \
--filter=blob:none $HTTPD_URL/one_time_sed/server repo 2>err &&
- grep "did not send all necessary objects" err &&
+ test_i18ngrep "did not send all necessary objects" err &&
# Ensure that the one-time-sed script was used.
! test -e "$HTTPD_ROOT_PATH/one-time-sed"
0000
EOF
- git serve --advertise-capabilities >out &&
+ GIT_TEST_SIDEBAND_ALL=0 git serve --advertise-capabilities >out &&
test-tool pkt-line unpack <out >actual &&
test_cmp expect actual
'
grep "fetch< version 2" trace
'
+test_expect_success 'ensure that multiple fetches in same process from a shallow repo works' '
+ rm -rf server client trace &&
+
+ test_create_repo server &&
+ test_commit -C server one &&
+ test_commit -C server two &&
+ test_commit -C server three &&
+ git clone --shallow-exclude two "file://$(pwd)/server" client &&
+
+ git -C server tag -a -m "an annotated tag" twotag two &&
+
+ # Triggers tag following (thus, 2 fetches in one process)
+ GIT_TRACE_PACKET="$(pwd)/trace" git -C client -c protocol.version=2 \
+ fetch --shallow-exclude one origin &&
+ # Ensure that protocol v2 is used
+ grep "fetch< version 2" trace
+'
+
+test_expect_success 'deepen-relative' '
+ rm -rf server client trace &&
+
+ test_create_repo server &&
+ test_commit -C server one &&
+ test_commit -C server two &&
+ test_commit -C server three &&
+ git clone --depth 1 "file://$(pwd)/server" client &&
+ test_commit -C server four &&
+
+ # Sanity check that only "three" is downloaded
+ git -C client log --pretty=tformat:%s master >actual &&
+ echo three >expected &&
+ test_cmp expected actual &&
+
+ GIT_TRACE_PACKET="$(pwd)/trace" git -C client -c protocol.version=2 \
+ fetch --deepen=1 origin &&
+ # Ensure that protocol v2 is used
+ grep "fetch< version 2" trace &&
+
+ git -C client log --pretty=tformat:%s origin/master >actual &&
+ cat >expected <<-\EOF &&
+ four
+ three
+ two
+ EOF
+ test_cmp expected actual
+'
+
# Test protocol v2 with 'http://' transport
#
. "$TEST_DIRECTORY"/lib-httpd.sh
grep "git< version 2" log
'
+test_expect_success 'fetch from namespaced repo respects namespaces' '
+ test_when_finished "rm -f log" &&
+
+ git init "$HTTPD_DOCUMENT_ROOT_PATH/nsrepo" &&
+ test_commit -C "$HTTPD_DOCUMENT_ROOT_PATH/nsrepo" one &&
+ test_commit -C "$HTTPD_DOCUMENT_ROOT_PATH/nsrepo" two &&
+ git -C "$HTTPD_DOCUMENT_ROOT_PATH/nsrepo" \
+ update-ref refs/namespaces/ns/refs/heads/master one &&
+
+ GIT_TRACE_PACKET="$(pwd)/log" git -C http_child -c protocol.version=2 \
+ fetch "$HTTPD_URL/smart_namespace/nsrepo" \
+ refs/heads/master:refs/heads/theirs &&
+
+ # Server responded using protocol v2
+ grep "fetch< version 2" log &&
+
+ git -C "$HTTPD_DOCUMENT_ROOT_PATH/nsrepo" rev-parse one >expect &&
+ git -C http_child rev-parse theirs >actual &&
+ test_cmp expect actual
+'
+
test_expect_success 'push with http:// and a config of v2 does not request v2' '
test_when_finished "rm -f log" &&
# Till v2 for push is designed, make sure that if a client has
test_must_fail env GIT_TRACE_PACKET="$(pwd)/log" git -C http_child \
-c protocol.version=2 \
fetch "$HTTPD_URL/one_time_sed/http_parent" 2> err &&
- grep "fetch< acknowledgments" log &&
- ! grep "fetch< ready" log &&
+ grep "fetch< .*acknowledgments" log &&
+ ! grep "fetch< .*ready" log &&
test_i18ngrep "expected no other sections to be sent after no .ready." err
'
cp -r "$LOCAL_PRISTINE" local &&
inconsistency master 1234567890123456789012345678901234567890 &&
test_must_fail git -C local fetch 2>err &&
- grep "ERR upload-pack: not our ref" err
+ test_i18ngrep "fatal: remote error: upload-pack: not our ref" err
'
test_expect_success 'server is initially ahead - ref in want' '
echo "s/master/raster/" >"$HTTPD_ROOT_PATH/one-time-sed" &&
test_must_fail git -C local fetch 2>err &&
- grep "ERR unknown ref refs/heads/raster" err
+ test_i18ngrep "fatal: remote error: unknown ref refs/heads/raster" err
'
stop_httpd
test_cmp expect actual
'
+test_expect_success '%S is not a placeholder for rev-list yet' '
+ git rev-list --format="%S" -1 master | grep "%S"
+'
+
test_expect_success 'advanced colors' '
cat >expect <<-EOF &&
commit $head2
test_must_fail git bisect terms only-one &&
test_must_fail git bisect terms 1 2 &&
test_must_fail git bisect terms 2>actual &&
- echo "no terms defined" >expected &&
+ echo "error: no terms defined" >expected &&
test_i18ncmp expected actual
'
git ls-files -u >out &&
test_line_count = 2 out &&
git ls-files -o >out &&
- test_line_count = 3 out &&
+ test_line_count = 1 out &&
git rev-parse >expect \
- L2:three R2:three \
L2:three R2:three &&
git rev-parse >actual \
:2:three :3:three &&
- git hash-object >>actual \
- three~HEAD three~R2^0 &&
test_cmp expect actual
)
'
git ls-files -u >out &&
test_line_count = 2 out &&
git ls-files -o >out &&
- test_line_count = 3 out &&
+ test_line_count = 1 out &&
git rev-parse >expect \
- L2:three R2:three \
L2:three R2:three &&
git rev-parse >actual \
:2:three :3:three &&
- git hash-object >>actual \
- three~HEAD three~R2^0 &&
test_cmp expect actual
)
'
git branch B &&
git checkout -b C &&
echo 10 >>a &&
- echo "other content" >>new_a &&
+ test_write_lines 0 1 2 3 4 5 6 7 foobar >new_a &&
git add a new_a &&
test_tick && git commit -m C &&
git checkout B^0 &&
test_must_fail git merge C &&
- git clean -f &&
+ git show :2:new_a >new_a &&
+ git add new_a &&
test_tick && git commit -m D &&
git tag D &&
git checkout C^0 &&
test_must_fail git merge B &&
- rm new_a~HEAD new_a &&
- printf "Incorrectly merged content" >>new_a &&
+ test_write_lines 0 1 2 3 4 5 6 7 bad_merge >new_a &&
git add -u &&
test_tick && git commit -m E &&
git tag E
test_line_count = 1 out &&
git rev-parse >expect \
- D:new_a E:new_a &&
+ C:new_a D:new_a E:new_a &&
git rev-parse >actual \
- :2:new_a :3:new_a &&
+ :1:new_a :2:new_a :3:new_a &&
test_cmp expect actual &&
- git cat-file -p C:new_a >ours &&
- git cat-file -p B:new_a >theirs &&
+ # Test that the two-way merge in new_a is as expected
+ git cat-file -p D:new_a >ours &&
+ git cat-file -p E:new_a >theirs &&
>empty &&
test_must_fail git merge-file \
- -L "Temporary merge branch 1" \
+ -L "HEAD" \
-L "" \
- -L "Temporary merge branch 2" \
+ -L "E^0" \
ours empty theirs &&
sed -e "s/^\([<=>]\)/\1\1\1/" ours >expect &&
- git cat-file -p :1:new_a >actual &&
+ git hash-object new_a >actual &&
+ git hash-object ours >expect &&
+ test_cmp expect actual
+ )
+'
+
+# Repeat the above testcase with precisely the same setup, other than with
+# the two merge bases having different orderings of commit timestamps so
+# that they are reversed in the order they are provided to merge-recursive,
+# so that we can improve code coverage.
+test_expect_success 'git detects differently handled merges conflict, swapped' '
+ (
+ cd rename-add &&
+
+ # Difference #1: Do cleanup from previous testrun
+ git reset --hard &&
+ git clean -fdqx &&
+
+ # Difference #2: Change commit timestamps
+ btime=$(git log --no-walk --date=raw --format=%cd B | awk "{print \$1}") &&
+ ctime=$(git log --no-walk --date=raw --format=%cd C | awk "{print \$1}") &&
+ newctime=$(($btime+1)) &&
+ git fast-export --no-data --all | sed -e s/$ctime/$newctime/ | git fast-import --force --quiet &&
+ # End of differences; rest is copy-paste of last test
+
+ git checkout D^0 &&
+ test_must_fail git merge -s recursive E^0 &&
+
+ git ls-files -s >out &&
+ test_line_count = 3 out &&
+ git ls-files -u >out &&
+ test_line_count = 3 out &&
+ git ls-files -o >out &&
+ test_line_count = 1 out &&
+
+ git rev-parse >expect \
+ C:new_a D:new_a E:new_a &&
+ git rev-parse >actual \
+ :1:new_a :2:new_a :3:new_a &&
+ test_cmp expect actual &&
+
+ # Test that the two-way merge in new_a is as expected
+ git cat-file -p D:new_a >ours &&
+ git cat-file -p E:new_a >theirs &&
+ >empty &&
+ test_must_fail git merge-file \
+ -L "HEAD" \
+ -L "" \
+ -L "E^0" \
+ ours empty theirs &&
+ sed -e "s/^\([<=>]\)/\1\1\1/" ours >expect &&
+ git hash-object new_a >actual &&
+ git hash-object ours >expect &&
test_cmp expect actual
)
'
)
'
+# Setup:
+# L1---L2
+# / \ / \
+# master X ?
+# \ / \ /
+# R1---R2
+#
+# Where:
+# master has two files, named 'b' and 'a'
+# branches L1 and R1 both modify each of the two files in conflicting ways
+#
+# L2 is a merge of R1 into L1; more on it later.
+# R2 is a merge of L1 into R1; more on it later.
+#
+# X is an auto-generated merge-base used when merging L2 and R2.
+# since X is a merge of L1 and R1, it has conflicting versions of each file
+#
+# More about L2 and R2:
+# - both resolve the conflicts in 'b' and 'a' differently
+# - L2 renames 'b' to 'm'
+# - R2 renames 'a' to 'm'
+#
+# In the end, in file 'm' we have four different conflicting files (from
+# two versions of 'b' and two of 'a'). In addition, if
+# merge.conflictstyle is diff3, then the base version also has
+# conflict markers of its own, leading to a total of three levels of
+# conflict markers. This is a pretty weird corner case, but we just want
+# to ensure that we handle it as well as practical.
+
+test_expect_success 'setup nested conflicts' '
+ test_create_repo nested_conflicts &&
+ (
+ cd nested_conflicts &&
+
+ # Create some related files now
+ for i in $(test_seq 1 10)
+ do
+ echo Random base content line $i
+ done >initial &&
+
+ cp initial b_L1 &&
+ cp initial b_R1 &&
+ cp initial b_L2 &&
+ cp initial b_R2 &&
+ cp initial a_L1 &&
+ cp initial a_R1 &&
+ cp initial a_L2 &&
+ cp initial a_R2 &&
+
+ test_write_lines b b_L1 >>b_L1 &&
+ test_write_lines b b_R1 >>b_R1 &&
+ test_write_lines b b_L2 >>b_L2 &&
+ test_write_lines b b_R2 >>b_R2 &&
+ test_write_lines a a_L1 >>a_L1 &&
+ test_write_lines a a_R1 >>a_R1 &&
+ test_write_lines a a_L2 >>a_L2 &&
+ test_write_lines a a_R2 >>a_R2 &&
+
+ # Setup original commit (or merge-base), consisting of
+ # files named "b" and "a"
+ cp initial b &&
+ cp initial a &&
+ echo b >>b &&
+ echo a >>a &&
+ git add b a &&
+ test_tick && git commit -m initial &&
+
+ git branch L &&
+ git branch R &&
+
+ # Handle the left side
+ git checkout L &&
+ mv -f b_L1 b &&
+ mv -f a_L1 a &&
+ git add b a &&
+ test_tick && git commit -m "version L1 of files" &&
+ git tag L1 &&
+
+ # Handle the right side
+ git checkout R &&
+ mv -f b_R1 b &&
+ mv -f a_R1 a &&
+ git add b a &&
+ test_tick && git commit -m "verson R1 of files" &&
+ git tag R1 &&
+
+ # Create first merge on left side
+ git checkout L &&
+ test_must_fail git merge R1 &&
+ mv -f b_L2 b &&
+ mv -f a_L2 a &&
+ git add b a &&
+ git mv b m &&
+ test_tick && git commit -m "left merge, rename b->m" &&
+ git tag L2 &&
+
+ # Create first merge on right side
+ git checkout R &&
+ test_must_fail git merge L1 &&
+ mv -f b_R2 b &&
+ mv -f a_R2 a &&
+ git add b a &&
+ git mv a m &&
+ test_tick && git commit -m "right merge, rename a->m" &&
+ git tag R2
+ )
+'
+
+test_expect_success 'check nested conflicts' '
+ (
+ cd nested_conflicts &&
+
+ git clean -f &&
+ git checkout L2^0 &&
+
+ # Merge must fail; there is a conflict
+ test_must_fail git -c merge.conflictstyle=diff3 merge -s recursive R2^0 &&
+
+ # Make sure the index has the right number of entries
+ git ls-files -s >out &&
+ test_line_count = 2 out &&
+ git ls-files -u >out &&
+ test_line_count = 2 out &&
+ # Ensure we have the correct number of untracked files
+ git ls-files -o >out &&
+ test_line_count = 1 out &&
+
+ # Create a and b from virtual merge base X
+ git cat-file -p master:a >base &&
+ git cat-file -p L1:a >ours &&
+ git cat-file -p R1:a >theirs &&
+ test_must_fail git merge-file --diff3 \
+ -L "Temporary merge branch 1" \
+ -L "merged common ancestors" \
+ -L "Temporary merge branch 2" \
+ ours \
+ base \
+ theirs &&
+ sed -e "s/^\([<|=>]\)/\1\1/" ours >vmb_a &&
+
+ git cat-file -p master:b >base &&
+ git cat-file -p L1:b >ours &&
+ git cat-file -p R1:b >theirs &&
+ test_must_fail git merge-file --diff3 \
+ -L "Temporary merge branch 1" \
+ -L "merged common ancestors" \
+ -L "Temporary merge branch 2" \
+ ours \
+ base \
+ theirs &&
+ sed -e "s/^\([<|=>]\)/\1\1/" ours >vmb_b &&
+
+ # Compare :2:m to expected values
+ git cat-file -p L2:m >ours &&
+ git cat-file -p R2:b >theirs &&
+ test_must_fail git merge-file --diff3 \
+ -L "HEAD:m" \
+ -L "merged common ancestors:b" \
+ -L "R2^0:b" \
+ ours \
+ vmb_b \
+ theirs &&
+ sed -e "s/^\([<|=>]\)/\1\1/" ours >m_stage_2 &&
+ git cat-file -p :2:m >actual &&
+ test_cmp m_stage_2 actual &&
+
+ # Compare :3:m to expected values
+ git cat-file -p L2:a >ours &&
+ git cat-file -p R2:m >theirs &&
+ test_must_fail git merge-file --diff3 \
+ -L "HEAD:a" \
+ -L "merged common ancestors:a" \
+ -L "R2^0:m" \
+ ours \
+ vmb_a \
+ theirs &&
+ sed -e "s/^\([<|=>]\)/\1\1/" ours >m_stage_3 &&
+ git cat-file -p :3:m >actual &&
+ test_cmp m_stage_3 actual &&
+
+ # Compare m to expected contents
+ >empty &&
+ cp m_stage_2 expected_final_m &&
+ test_must_fail git merge-file --diff3 \
+ -L "HEAD" \
+ -L "merged common ancestors" \
+ -L "R2^0" \
+ expected_final_m \
+ empty \
+ m_stage_3 &&
+ test_cmp expected_final_m m
+ )
+'
+
+# Setup:
+# L1---L2---L3
+# / \ / \ / \
+# master X1 X2 ?
+# \ / \ / \ /
+# R1---R2---R3
+#
+# Where:
+# master has one file named 'content'
+# branches L1 and R1 both modify each of the two files in conflicting ways
+#
+# L<n> (n>1) is a merge of R<n-1> into L<n-1>
+# R<n> (n>1) is a merge of L<n-1> into R<n-1>
+# L<n> and R<n> resolve the conflicts differently.
+#
+# X<n> is an auto-generated merge-base used when merging L<n+1> and R<n+1>.
+# By construction, X1 has conflict markers due to conflicting versions.
+# X2, due to using merge.conflictstyle=3, has nested conflict markers.
+#
+# So, merging R3 into L3 using merge.conflictstyle=3 should show the
+# nested conflict markers from X2 in the base version -- that means we
+# have three levels of conflict markers. Can we distinguish all three?
+
+test_expect_success 'setup virtual merge base with nested conflicts' '
+ test_create_repo virtual_merge_base_has_nested_conflicts &&
+ (
+ cd virtual_merge_base_has_nested_conflicts &&
+
+ # Create some related files now
+ for i in $(test_seq 1 10)
+ do
+ echo Random base content line $i
+ done >content &&
+
+ # Setup original commit
+ git add content &&
+ test_tick && git commit -m initial &&
+
+ git branch L &&
+ git branch R &&
+
+ # Create L1
+ git checkout L &&
+ echo left >>content &&
+ git add content &&
+ test_tick && git commit -m "version L1 of content" &&
+ git tag L1 &&
+
+ # Create R1
+ git checkout R &&
+ echo right >>content &&
+ git add content &&
+ test_tick && git commit -m "verson R1 of content" &&
+ git tag R1 &&
+
+ # Create L2
+ git checkout L &&
+ test_must_fail git -c merge.conflictstyle=diff3 merge R1 &&
+ git checkout L1 content &&
+ test_tick && git commit -m "version L2 of content" &&
+ git tag L2 &&
+
+ # Create R2
+ git checkout R &&
+ test_must_fail git -c merge.conflictstyle=diff3 merge L1 &&
+ git checkout R1 content &&
+ test_tick && git commit -m "version R2 of content" &&
+ git tag R2 &&
+
+ # Create L3
+ git checkout L &&
+ test_must_fail git -c merge.conflictstyle=diff3 merge R2 &&
+ git checkout L1 content &&
+ test_tick && git commit -m "version L3 of content" &&
+ git tag L3 &&
+
+ # Create R3
+ git checkout R &&
+ test_must_fail git -c merge.conflictstyle=diff3 merge L2 &&
+ git checkout R1 content &&
+ test_tick && git commit -m "version R3 of content" &&
+ git tag R3
+ )
+'
+
+test_expect_success 'check virtual merge base with nested conflicts' '
+ (
+ cd virtual_merge_base_has_nested_conflicts &&
+
+ git checkout L3^0 &&
+
+ # Merge must fail; there is a conflict
+ test_must_fail git -c merge.conflictstyle=diff3 merge -s recursive R3^0 &&
+
+ # Make sure the index has the right number of entries
+ git ls-files -s >out &&
+ test_line_count = 3 out &&
+ git ls-files -u >out &&
+ test_line_count = 3 out &&
+ # Ensure we have the correct number of untracked files
+ git ls-files -o >out &&
+ test_line_count = 1 out &&
+
+ # Compare :[23]:content to expected values
+ git rev-parse L1:content R1:content >expect &&
+ git rev-parse :2:content :3:content >actual &&
+ test_cmp expect actual &&
+
+ # Imitate X1 merge base, except without long enough conflict
+ # markers because a subsequent sed will modify them. Put
+ # result into vmb.
+ git cat-file -p master:content >base &&
+ git cat-file -p L:content >left &&
+ git cat-file -p R:content >right &&
+ cp left merged-once &&
+ test_must_fail git merge-file --diff3 \
+ -L "Temporary merge branch 1" \
+ -L "merged common ancestors" \
+ -L "Temporary merge branch 2" \
+ merged-once \
+ base \
+ right &&
+ sed -e "s/^\([<|=>]\)/\1\1\1/" merged-once >vmb &&
+
+ # Imitate X2 merge base, overwriting vmb. Note that we
+ # extend both sets of conflict markers to make them longer
+ # with the sed command.
+ cp left merged-twice &&
+ test_must_fail git merge-file --diff3 \
+ -L "Temporary merge branch 1" \
+ -L "merged common ancestors" \
+ -L "Temporary merge branch 2" \
+ merged-twice \
+ vmb \
+ right &&
+ sed -e "s/^\([<|=>]\)/\1\1\1/" merged-twice >vmb &&
+
+ # Compare :1:content to expected value
+ git cat-file -p :1:content >actual &&
+ test_cmp vmb actual &&
+
+ # Determine expected content in final outer merge, compare to
+ # what the merge generated.
+ cp -f left expect &&
+ test_must_fail git merge-file --diff3 \
+ -L "HEAD" -L "merged common ancestors" -L "R3^0" \
+ expect vmb right &&
+ test_cmp expect content
+ )
+'
+
test_done
git ls-files -u c >out &&
test_line_count = 2 out &&
git ls-files -o >out &&
- test_line_count = 3 out &&
+ test_line_count = 1 out &&
test_path_is_missing a &&
test_path_is_missing b &&
- test_path_is_file c~HEAD &&
- test_path_is_file c~C^0 &&
- git rev-parse >expect \
- C:a B:b &&
- git hash-object >actual \
- c~HEAD c~C^0 &&
+ git rev-parse >expect \
+ C:a B:b &&
+ git rev-parse >actual \
+ :2:c :3:c &&
+ test_cmp expect actual &&
+
+ # Test that the two-way merge in new_a is as expected
+ git cat-file -p :2:c >>ours &&
+ git cat-file -p :3:c >>theirs &&
+ >empty &&
+ test_must_fail git merge-file \
+ -L "HEAD" \
+ -L "" \
+ -L "C^0" \
+ ours empty theirs &&
+ git hash-object c >actual &&
+ git hash-object ours >expect &&
test_cmp expect actual
)
'
git ls-files -u c >out &&
test_line_count = 2 out &&
git ls-files -o >out &&
- test_line_count = 5 out &&
+ test_line_count = 1 out &&
git rev-parse >expect \
A:a C:b B:b C:c B:c &&
:1:a :2:b :3:b :2:c :3:c &&
test_cmp expect actual &&
- git rev-parse >expect \
- C:c B:c C:b B:b &&
- git hash-object >actual \
- c~HEAD c~B\^0 b~HEAD b~B\^0 &&
- test_cmp expect actual &&
+ # Record some contents for re-doing merges
+ git cat-file -p A:a >stuff &&
+ git cat-file -p C:b >important_info &&
+ git cat-file -p B:c >precious_data &&
+ >empty &&
- test_path_is_missing b &&
- test_path_is_missing c
+ # Test the merge in b
+ test_must_fail git merge-file \
+ -L "HEAD" \
+ -L "" \
+ -L "B^0" \
+ important_info empty stuff &&
+ test_cmp important_info b &&
+
+ # Test the merge in c
+ test_must_fail git merge-file \
+ -L "HEAD" \
+ -L "" \
+ -L "B^0" \
+ stuff empty precious_data &&
+ test_cmp stuff c
)
'
)
'
+test_conflicts_with_adds_and_renames() {
+ sideL=$1
+ sideR=$2
+
+ # Setup:
+ # L
+ # / \
+ # master ?
+ # \ /
+ # R
+ #
+ # Where:
+ # Both L and R have files named 'three' which collide. Each of
+ # the colliding files could have been involved in a rename, in
+ # which case there was a file named 'one' or 'two' that was
+ # modified on the opposite side of history and renamed into the
+ # collision on this side of history.
+ #
+ # Questions:
+ # 1) The index should contain both a stage 2 and stage 3 entry
+ # for the colliding file. Does it?
+ # 2) When renames are involved, the content merges are clean, so
+ # the index should reflect the content merges, not merely the
+ # version of the colliding file from the prior commit. Does
+ # it?
+ # 3) There should be a file in the worktree named 'three'
+ # containing the two-way merged contents of the content-merged
+ # versions of 'three' from each of the two colliding
+ # files. Is it present?
+ # 4) There should not be any three~* files in the working
+ # tree
+ test_expect_success "setup simple $sideL/$sideR conflict" '
+ test_create_repo simple_${sideL}_${sideR} &&
+ (
+ cd simple_${sideL}_${sideR} &&
+
+ # Create some related files now
+ for i in $(test_seq 1 10)
+ do
+ echo Random base content line $i
+ done >file_v1 &&
+ cp file_v1 file_v2 &&
+ echo modification >>file_v2 &&
+
+ cp file_v1 file_v3 &&
+ echo more stuff >>file_v3 &&
+ cp file_v3 file_v4 &&
+ echo yet more stuff >>file_v4 &&
+
+ # Use a tag to record both these files for simple
+ # access, and clean out these untracked files
+ git tag file_v1 $(git hash-object -w file_v1) &&
+ git tag file_v2 $(git hash-object -w file_v2) &&
+ git tag file_v3 $(git hash-object -w file_v3) &&
+ git tag file_v4 $(git hash-object -w file_v4) &&
+ git clean -f &&
+
+ # Setup original commit (or merge-base), consisting of
+ # files named "one" and "two" if renames were involved.
+ touch irrelevant_file &&
+ git add irrelevant_file &&
+ if [ $sideL = "rename" ]
+ then
+ git show file_v1 >one &&
+ git add one
+ fi &&
+ if [ $sideR = "rename" ]
+ then
+ git show file_v3 >two &&
+ git add two
+ fi &&
+ test_tick && git commit -m initial &&
+
+ git branch L &&
+ git branch R &&
+
+ # Handle the left side
+ git checkout L &&
+ if [ $sideL = "rename" ]
+ then
+ git mv one three
+ else
+ git show file_v2 >three &&
+ git add three
+ fi &&
+ if [ $sideR = "rename" ]
+ then
+ git show file_v4 >two &&
+ git add two
+ fi &&
+ test_tick && git commit -m L &&
+
+ # Handle the right side
+ git checkout R &&
+ if [ $sideL = "rename" ]
+ then
+ git show file_v2 >one &&
+ git add one
+ fi &&
+ if [ $sideR = "rename" ]
+ then
+ git mv two three
+ else
+ git show file_v4 >three &&
+ git add three
+ fi &&
+ test_tick && git commit -m R
+ )
+ '
+
+ test_expect_success "check simple $sideL/$sideR conflict" '
+ (
+ cd simple_${sideL}_${sideR} &&
+
+ git checkout L^0 &&
+
+ # Merge must fail; there is a conflict
+ test_must_fail git merge -s recursive R^0 &&
+
+ # Make sure the index has the right number of entries
+ git ls-files -s >out &&
+ test_line_count = 3 out &&
+ git ls-files -u >out &&
+ test_line_count = 2 out &&
+ # Ensure we have the correct number of untracked files
+ git ls-files -o >out &&
+ test_line_count = 1 out &&
+
+ # Nothing should have touched irrelevant_file
+ git rev-parse >actual \
+ :0:irrelevant_file \
+ :2:three \
+ :3:three &&
+ git rev-parse >expected \
+ master:irrelevant_file \
+ file_v2 \
+ file_v4 &&
+ test_cmp expected actual &&
+
+ # Make sure we have the correct merged contents for
+ # three
+ git show file_v1 >expected &&
+ cat <<-\EOF >>expected &&
+ <<<<<<< HEAD
+ modification
+ =======
+ more stuff
+ yet more stuff
+ >>>>>>> R^0
+ EOF
+
+ test_cmp expected three
+ )
+ '
+}
+
+test_conflicts_with_adds_and_renames rename rename
+test_conflicts_with_adds_and_renames rename add
+test_conflicts_with_adds_and_renames add rename
+test_conflicts_with_adds_and_renames add add
+
+# Setup:
+# L
+# / \
+# master ?
+# \ /
+# R
+#
+# Where:
+# master has two files, named 'one' and 'two'.
+# branches L and R both modify 'one', in conflicting ways.
+# branches L and R both modify 'two', in conflicting ways.
+# branch L also renames 'one' to 'three'.
+# branch R also renames 'two' to 'three'.
+#
+# So, we have four different conflicting files that all end up at path
+# 'three'.
+test_expect_success 'setup nested conflicts from rename/rename(2to1)' '
+ test_create_repo nested_conflicts_from_rename_rename &&
+ (
+ cd nested_conflicts_from_rename_rename &&
+
+ # Create some related files now
+ for i in $(test_seq 1 10)
+ do
+ echo Random base content line $i
+ done >file_v1 &&
+
+ cp file_v1 file_v2 &&
+ cp file_v1 file_v3 &&
+ cp file_v1 file_v4 &&
+ cp file_v1 file_v5 &&
+ cp file_v1 file_v6 &&
+
+ echo one >>file_v1 &&
+ echo uno >>file_v2 &&
+ echo eins >>file_v3 &&
+
+ echo two >>file_v4 &&
+ echo dos >>file_v5 &&
+ echo zwei >>file_v6 &&
+
+ # Setup original commit (or merge-base), consisting of
+ # files named "one" and "two".
+ mv file_v1 one &&
+ mv file_v4 two &&
+ git add one two &&
+ test_tick && git commit -m english &&
+
+ git branch L &&
+ git branch R &&
+
+ # Handle the left side
+ git checkout L &&
+ git rm one two &&
+ mv -f file_v2 three &&
+ mv -f file_v5 two &&
+ git add two three &&
+ test_tick && git commit -m spanish &&
+
+ # Handle the right side
+ git checkout R &&
+ git rm one two &&
+ mv -f file_v3 one &&
+ mv -f file_v6 three &&
+ git add one three &&
+ test_tick && git commit -m german
+ )
+'
+
+test_expect_success 'check nested conflicts from rename/rename(2to1)' '
+ (
+ cd nested_conflicts_from_rename_rename &&
+
+ git checkout L^0 &&
+
+ # Merge must fail; there is a conflict
+ test_must_fail git merge -s recursive R^0 &&
+
+ # Make sure the index has the right number of entries
+ git ls-files -s >out &&
+ test_line_count = 2 out &&
+ git ls-files -u >out &&
+ test_line_count = 2 out &&
+ # Ensure we have the correct number of untracked files
+ git ls-files -o >out &&
+ test_line_count = 1 out &&
+
+ # Compare :2:three to expected values
+ git cat-file -p master:one >base &&
+ git cat-file -p L:three >ours &&
+ git cat-file -p R:one >theirs &&
+ test_must_fail git merge-file \
+ -L "HEAD:three" -L "" -L "R^0:one" \
+ ours base theirs &&
+ sed -e "s/^\([<=>]\)/\1\1/" ours >L-three &&
+ git cat-file -p :2:three >expect &&
+ test_cmp expect L-three &&
+
+ # Compare :2:three to expected values
+ git cat-file -p master:two >base &&
+ git cat-file -p L:two >ours &&
+ git cat-file -p R:three >theirs &&
+ test_must_fail git merge-file \
+ -L "HEAD:two" -L "" -L "R^0:three" \
+ ours base theirs &&
+ sed -e "s/^\([<=>]\)/\1\1/" ours >R-three &&
+ git cat-file -p :3:three >expect &&
+ test_cmp expect R-three &&
+
+ # Compare three to expected contents
+ >empty &&
+ test_must_fail git merge-file \
+ -L "HEAD" -L "" -L "R^0" \
+ L-three empty R-three &&
+ test_cmp three L-three
+ )
+'
+
test_done
git ls-files -u >out &&
test_line_count = 2 out &&
git ls-files -o >out &&
- test_line_count = 3 out &&
+ test_line_count = 1 out &&
git rev-parse >actual \
:0:x/b :0:x/c :0:x/d :0:x/e :0:x/m :0:x/n &&
A:y/wham B:z/wham &&
test_cmp expect actual &&
- test_path_is_missing x/wham &&
- test_path_is_file x/wham~HEAD &&
- test_path_is_file x/wham~B^0 &&
-
- git hash-object >actual \
- x/wham~HEAD x/wham~B^0 &&
- git rev-parse >expect \
- A:y/wham B:z/wham &&
- test_cmp expect actual
+ # Test that the two-way merge in x/wham is as expected
+ git cat-file -p :2:x/wham >expect &&
+ git cat-file -p :3:x/wham >other &&
+ >empty &&
+ test_must_fail git merge-file \
+ -L "HEAD" \
+ -L "" \
+ -L "B^0" \
+ expect empty other &&
+ test_cmp expect x/wham
)
'
git ls-files -u >out &&
test_line_count = 6 out &&
git ls-files -o >out &&
- test_line_count = 3 out &&
+ test_line_count = 1 out &&
git rev-parse >actual \
:0:y/b :0:y/c :0:y/e &&
test_cmp expect actual &&
git hash-object >actual \
- w/d~HEAD w/d~B^0 z/d &&
+ z/d &&
git rev-parse >expect \
- O:x/d B:w/d O:x/d &&
+ O:x/d &&
test_cmp expect actual &&
test_path_is_missing x/d &&
test_path_is_file y/d &&
git ls-files -u >out &&
test_line_count = 2 out &&
git ls-files -o >out &&
- test_line_count = 3 out &&
+ test_line_count = 1 out &&
git rev-parse >actual \
:0:y/b :0:y/c :2:y/d :3:y/d &&
O:z/b O:z/c O:w/d O:x/d &&
test_cmp expect actual &&
- test_path_is_missing y/d &&
- test_path_is_file y/d~HEAD &&
- test_path_is_file y/d~B^0 &&
-
- git hash-object >actual \
- y/d~HEAD y/d~B^0 &&
- git rev-parse >expect \
- O:w/d O:x/d &&
- test_cmp expect actual
+ # Test that the two-way merge in y/d is as expected
+ git cat-file -p :2:y/d >expect &&
+ git cat-file -p :3:y/d >other &&
+ >empty &&
+ test_must_fail git merge-file \
+ -L "HEAD" \
+ -L "" \
+ -L "B^0" \
+ expect empty other &&
+ test_cmp expect y/d
)
'
)
'
+test_expect_success '10c-check: Overwrite untracked with dir rename/rename(1to2), other direction' '
+ (
+ cd 10c &&
+
+ git reset --hard &&
+ git clean -fdqx &&
+
+ git checkout B^0 &&
+ mkdir y &&
+ echo important >y/c &&
+
+ test_must_fail git merge -s recursive A^0 >out 2>err &&
+ test_i18ngrep "CONFLICT (rename/rename)" out &&
+ test_i18ngrep "Refusing to lose untracked file at y/c; adding as y/c~HEAD instead" out &&
+
+ git ls-files -s >out &&
+ test_line_count = 6 out &&
+ git ls-files -u >out &&
+ test_line_count = 3 out &&
+ git ls-files -o >out &&
+ test_line_count = 3 out &&
+
+ git rev-parse >actual \
+ :0:y/a :0:y/b :0:x/d :1:x/c :3:w/c :2:y/c &&
+ git rev-parse >expect \
+ O:z/a O:z/b O:x/d O:x/c O:x/c O:x/c &&
+ test_cmp expect actual &&
+
+ git hash-object y/c~HEAD >actual &&
+ git rev-parse O:x/c >expect &&
+ test_cmp expect actual &&
+
+ echo important >expect &&
+ test_cmp expect y/c
+ )
+'
+
# Testcase 10d, Delete untracked w/ dir rename/rename(2to1)
# Commit O: z/{a,b,c_1}, x/{d,e,f_2}
# Commit A: y/{a,b}, x/{d,e,f_2,wham_1} + untracked y/wham
# Commit B: z/{a,b,c_1,wham_2}, y/{d,e}
-# Expected: Failed Merge; y/{a,b,d,e} + untracked y/{wham,wham~B^0,wham~HEAD}+
+# Expected: Failed Merge; y/{a,b,d,e} + untracked y/{wham,wham~merged}+
# CONFLICT(rename/rename) z/c_1 vs x/f_2 -> y/wham
# ERROR_MSG(Refusing to lose untracked file at y/wham)
git ls-files -u >out &&
test_line_count = 2 out &&
git ls-files -o >out &&
- test_line_count = 4 out &&
+ test_line_count = 3 out &&
git rev-parse >actual \
:0:y/a :0:y/b :0:y/d :0:y/e :2:y/wham :3:y/wham &&
echo important >expect &&
test_cmp expect y/wham &&
- git hash-object >actual \
- y/wham~B^0 y/wham~HEAD &&
- git rev-parse >expect \
- O:x/f O:z/c &&
- test_cmp expect actual
+ # Test that the two-way merge in y/wham~merged is as expected
+ git cat-file -p :2:y/wham >expect &&
+ git cat-file -p :3:y/wham >other &&
+ >empty &&
+ test_must_fail git merge-file \
+ -L "HEAD" \
+ -L "" \
+ -L "B^0" \
+ expect empty other &&
+ test_cmp expect y/wham~merged
)
'
git ls-files -u >out &&
test_line_count = 4 out &&
git ls-files -o >out &&
- test_line_count = 4 out &&
+ test_line_count = 3 out &&
echo different >expected &&
echo mods >>expected &&
O:z/a O:z/b O:x/d O:x/c O:x/c A:y/c O:x/c &&
test_cmp expect actual &&
- git hash-object >actual \
- y/c~B^0 y/c~HEAD &&
- git rev-parse >expect \
- O:x/c A:y/c &&
- test_cmp expect actual
+ # See if y/c~merged has expected contents; requires manually
+ # doing the expected file merge
+ git cat-file -p A:y/c >c1 &&
+ git cat-file -p B:z/c >c2 &&
+ >empty &&
+ test_must_fail git merge-file \
+ -L "HEAD" \
+ -L "" \
+ -L "B^0" \
+ c1 empty c2 &&
+ test_cmp c1 y/c~merged
)
'
# Commit O: z/{a,b}, x/{c_1,d_2}
# Commit A: y/{a,b,wham_1}, x/d_2, except y/wham has uncommitted mods
# Commit B: z/{a,b,wham_2}, x/c_1
-# Expected: Failed Merge; y/{a,b} + untracked y/{wham~B^0,wham~B^HEAD} +
+# Expected: Failed Merge; y/{a,b} + untracked y/{wham~merged} +
# y/wham with dirty changes from before merge +
# CONFLICT(rename/rename) x/c vs x/d -> y/wham
# ERROR_MSG(Refusing to lose dirty file at y/wham)
git ls-files -u >out &&
test_line_count = 2 out &&
git ls-files -o >out &&
- test_line_count = 4 out &&
+ test_line_count = 3 out &&
test_seq 1 10 >expected &&
echo important >>expected &&
test_cmp expected y/wham &&
test_must_fail git rev-parse :1:y/wham &&
- git hash-object >actual \
- y/wham~B^0 y/wham~HEAD &&
- git rev-parse >expect \
- O:x/d O:x/c &&
- test_cmp expect actual &&
git rev-parse >actual \
:0:y/a :0:y/b :2:y/wham :3:y/wham &&
git rev-parse >expect \
O:z/a O:z/b O:x/c O:x/d &&
- test_cmp expect actual
+ test_cmp expect actual &&
+
+ # Test that the two-way merge in y/wham~merged is as expected
+ git cat-file -p :2:y/wham >expect &&
+ git cat-file -p :3:y/wham >other &&
+ >empty &&
+ test_must_fail git merge-file \
+ -L "HEAD" \
+ -L "" \
+ -L "B^0" \
+ expect empty other &&
+ test_cmp expect y/wham~merged
)
'
test_expect_success '"git fsck" works' '
git fsck master >fsck_master.out &&
- grep "dangling commit $R" fsck_master.out &&
- grep "dangling tag $(cat .git/refs/tags/mytag)" fsck_master.out &&
+ test_i18ngrep "dangling commit $R" fsck_master.out &&
+ test_i18ngrep "dangling tag $(cat .git/refs/tags/mytag)" fsck_master.out &&
test -z "$(git fsck)"
'
# Make sure tree:0 does not iterate through any trees.
-test_expect_success 'filter a GIANT tree through tree:0' '
+test_expect_success 'verify skipping tree iteration when not collecting omits' '
GIT_TRACE=1 git -C r3 rev-list \
--objects --filter=tree:0 HEAD 2>filter_trace &&
grep "Skipping contents of tree [.][.][.]" filter_trace >actual &&
! grep "Skipping contents of tree [^.]" filter_trace
'
+# Test tree:# filters.
+
+expect_has () {
+ commit=$1 &&
+ name=$2 &&
+
+ hash=$(git -C r3 rev-parse $commit:$name) &&
+ grep "^$hash $name$" actual
+}
+
+test_expect_success 'verify tree:1 includes root trees' '
+ git -C r3 rev-list --objects --filter=tree:1 HEAD >actual &&
+
+ # We should get two root directories and two commits.
+ expect_has HEAD "" &&
+ expect_has HEAD~1 "" &&
+ test_line_count = 4 actual
+'
+
+test_expect_success 'verify tree:2 includes root trees and immediate children' '
+ git -C r3 rev-list --objects --filter=tree:2 HEAD >actual &&
+
+ expect_has HEAD "" &&
+ expect_has HEAD~1 "" &&
+ expect_has HEAD dir1 &&
+ expect_has HEAD pattern &&
+ expect_has HEAD sparse1 &&
+ expect_has HEAD sparse2 &&
+
+ # There are also 2 commit objects
+ test_line_count = 8 actual
+'
+
+test_expect_success 'verify tree:3 includes everything expected' '
+ git -C r3 rev-list --objects --filter=tree:3 HEAD >actual &&
+
+ expect_has HEAD "" &&
+ expect_has HEAD~1 "" &&
+ expect_has HEAD dir1 &&
+ expect_has HEAD dir1/sparse1 &&
+ expect_has HEAD dir1/sparse2 &&
+ expect_has HEAD pattern &&
+ expect_has HEAD sparse1 &&
+ expect_has HEAD sparse2 &&
+
+ # There are also 2 commit objects
+ test_line_count = 10 actual
+'
+
+# Test provisional omit collection logic with a repo that has objects appearing
+# at multiple depths - first deeper than the filter's threshold, then shallow.
+
+test_expect_success 'setup r4' '
+ git init r4 &&
+
+ echo foo > r4/foo &&
+ mkdir r4/subdir &&
+ echo bar > r4/subdir/bar &&
+
+ mkdir r4/filt &&
+ cp -r r4/foo r4/subdir r4/filt &&
+
+ git -C r4 add foo subdir filt &&
+ git -C r4 commit -m "commit msg"
+'
+
+expect_has_with_different_name () {
+ repo=$1 &&
+ name=$2 &&
+
+ hash=$(git -C $repo rev-parse HEAD:$name) &&
+ ! grep "^$hash $name$" actual &&
+ grep "^$hash " actual &&
+ ! grep "~$hash" actual
+}
+
+test_expect_success 'test tree:# filter provisional omit for blob and tree' '
+ git -C r4 rev-list --objects --filter-print-omitted --filter=tree:2 \
+ HEAD >actual &&
+ expect_has_with_different_name r4 filt/foo &&
+ expect_has_with_different_name r4 filt/subdir
+'
+
+test_expect_success 'verify skipping tree iteration when collecting omits' '
+ GIT_TRACE=1 git -C r4 rev-list --filter-print-omitted \
+ --objects --filter=tree:0 HEAD 2>filter_trace &&
+ grep "^Skipping contents of tree " filter_trace >actual &&
+
+ echo "Skipping contents of tree subdir/..." >expect &&
+ test_cmp expect actual
+'
+
+# Test tree:<depth> where a tree is iterated to twice - once where a subentry is
+# too deep to be included, and again where the blob inside it is shallow enough
+# to be included. This makes sure we don't use LOFR_MARK_SEEN incorrectly (we
+# can't use it because a tree can be iterated over again at a lower depth).
+
+test_expect_success 'tree:<depth> where we iterate over tree at two levels' '
+ git init r5 &&
+
+ mkdir -p r5/a/subdir/b &&
+ echo foo > r5/a/subdir/b/foo &&
+
+ mkdir -p r5/subdir/b &&
+ echo foo > r5/subdir/b/foo &&
+
+ git -C r5 add a subdir &&
+ git -C r5 commit -m "commit msg" &&
+
+ git -C r5 rev-list --objects --filter=tree:4 HEAD >actual &&
+ expect_has_with_different_name r5 a/subdir/b/foo
+'
+
+test_expect_success 'tree:<depth> which filters out blob but given as arg' '
+ blob_hash=$(git -C r4 rev-parse HEAD:subdir/bar) &&
+
+ git -C r4 rev-list --objects --filter=tree:1 HEAD $blob_hash >actual &&
+ grep ^$blob_hash actual
+'
+
# Delete some loose objects and use rev-list, but WITHOUT any filtering.
# This models previously omitted objects that we did not receive.
git -C r1 rev-list --quiet --missing=allow-any --objects HEAD
'
+# Test expansion of filter specs.
+
+test_expect_success 'expand blob limit in protocol' '
+ git -C r2 config --local uploadpack.allowfilter 1 &&
+ GIT_TRACE_PACKET="$(pwd)/trace" git -c protocol.version=2 clone \
+ --filter=blob:limit=1k "file://$(pwd)/r2" limit &&
+ ! grep "blob:limit=1k" trace &&
+ grep "blob:limit=1024" trace
+'
+
+test_expect_success 'expand tree depth limit in protocol' '
+ GIT_TRACE_PACKET="$(pwd)/tree_trace" git -c protocol.version=2 clone \
+ --filter=tree:0k "file://$(pwd)/r2" tree &&
+ ! grep "tree:0k" tree_trace &&
+ grep "tree:0" tree_trace
+'
+
test_done
mkdir sub &&
while read path
do
- : >$path &&
+ echo content >$path &&
git add $path || return 1
done <expect &&
git commit -m "initial commit" &&
test_must_be_empty actual
'
+test_expect_success 'pathspec with labels and non existent .gitattributes (2)' '
+ test_must_fail git grep content HEAD -- ":(attr:label)"
+'
+
test_expect_success 'setup .gitattributes' '
cat <<-\EOF >.gitattributes &&
fileA labelA
test_cmp expect actual
'
+test_expect_success 'check specific set attr (2)' '
+ cat <<-\EOF >expect &&
+ HEAD:fileSetLabel
+ HEAD:sub/fileSetLabel
+ EOF
+ git grep -l content HEAD ":(attr:label)" >actual &&
+ test_cmp expect actual
+'
+
test_expect_success 'check specific unset attr' '
cat <<-\EOF >expect &&
fileUnsetLabel
test_cmp expect actual
'
+test_expect_success 'check specific unset attr (2)' '
+ cat <<-\EOF >expect &&
+ HEAD:fileUnsetLabel
+ HEAD:sub/fileUnsetLabel
+ EOF
+ git grep -l content HEAD ":(attr:-label)" >actual &&
+ test_cmp expect actual
+'
+
test_expect_success 'check specific value attr' '
cat <<-\EOF >expect &&
fileValue
test_must_be_empty actual
'
+test_expect_success 'check specific value attr (2)' '
+ cat <<-\EOF >expect &&
+ HEAD:fileValue
+ HEAD:sub/fileValue
+ EOF
+ git grep -l content HEAD ":(attr:label=foo)" >actual &&
+ test_cmp expect actual &&
+ test_must_fail git grep -l content HEAD ":(attr:label=bar)"
+'
+
test_expect_success 'check unspecified attr' '
cat <<-\EOF >expect &&
.gitattributes
test_cmp expect actual
'
+test_expect_success 'check unspecified attr (2)' '
+ cat <<-\EOF >expect &&
+ HEAD:.gitattributes
+ HEAD:fileA
+ HEAD:fileAB
+ HEAD:fileAC
+ HEAD:fileB
+ HEAD:fileBC
+ HEAD:fileC
+ HEAD:fileNoLabel
+ HEAD:fileWrongLabel
+ HEAD:sub/fileA
+ HEAD:sub/fileAB
+ HEAD:sub/fileAC
+ HEAD:sub/fileB
+ HEAD:sub/fileBC
+ HEAD:sub/fileC
+ HEAD:sub/fileNoLabel
+ HEAD:sub/fileWrongLabel
+ EOF
+ git grep -l ^ HEAD ":(attr:!label)" >actual &&
+ test_cmp expect actual
+'
+
test_expect_success 'check multiple unspecified attr' '
cat <<-\EOF >expect &&
.gitattributes
test_atom head push:strip=-1 master
test_atom head objecttype commit
test_atom head objectsize 171
+test_atom head objectsize:disk 138
+test_atom head deltabase 0000000000000000000000000000000000000000
test_atom head objectname $(git rev-parse refs/heads/master)
test_atom head objectname:short $(git rev-parse --short refs/heads/master)
test_atom head objectname:short=1 $(git rev-parse --short=1 refs/heads/master)
test_atom tag push ''
test_atom tag objecttype tag
test_atom tag objectsize 154
+test_atom tag objectsize:disk 138
+test_atom tag '*objectsize:disk' 138
+test_atom tag deltabase 0000000000000000000000000000000000000000
+test_atom tag '*deltabase' 0000000000000000000000000000000000000000
test_atom tag objectname $(git rev-parse refs/tags/testtag)
test_atom tag objectname:short $(git rev-parse --short refs/tags/testtag)
test_atom head objectname:short=1 $(git rev-parse --short=1 refs/heads/master)
rmdir init
'
+test_expect_success 'submodule deinit should unset core.worktree' '
+ test_path_is_file .git/modules/example/config &&
+ test_must_fail git config -f .git/modules/example/config core.worktree
+'
+
test_expect_success 'submodule deinit from subdirectory' '
git submodule update --init &&
git config submodule.example.foo bar &&
GIT_WORK_TREE=../../../nested git -C sub1/.git/modules/nested config \
core.worktree "../../../nested" &&
# make sure this re-setup is correct
- git status --ignore-submodules=none
+ git status --ignore-submodules=none &&
+
+ # also make sure this old setup does not regress
+ git submodule update --init --recursive >out 2>err &&
+ test_must_be_empty out &&
+ test_must_be_empty err
'
test_expect_success 'absorb the git dir in a nested submodule' '
# symlink detector; this grep string comes from the config
# variable name and will not be translated.
test_must_fail git fsck 2>output &&
- grep gitmodulesSymlink output
+ test_i18ngrep gitmodulesSymlink output
)
'
git ls-tree HEAD | sed s/subdir/.gitmodules/ | git mktree &&
test_must_fail git fsck 2>output &&
- grep gitmodulesBlob output
+ test_i18ngrep gitmodulesBlob output
)
'
git commit -m "broken gitmodules" &&
git fsck 2>output &&
- grep gitmodulesParse output &&
+ test_i18ngrep gitmodulesParse output &&
test_i18ngrep ! "bad config" output
)
'
test_rebase () {
expect=$1 &&
mode=$2 &&
- test_expect_$expect C_LOCALE_OUTPUT "with hook (rebase $mode)" '
+ test_expect_$expect C_LOCALE_OUTPUT "with hook (rebase ${mode:--i})" '
test_when_finished "\
git rebase --abort
git checkout -f master
GIT_EDITOR="\"$FAKE_EDITOR\"" &&
(
export GIT_SEQUENCE_EDITOR GIT_EDITOR &&
- test_must_fail git rebase $mode b &&
+ test_must_fail git rebase -i $mode b &&
echo x >a &&
git add a &&
test_must_fail git rebase --continue &&
git add b &&
git rebase --continue
) &&
- if test $mode = -p # reword amended after pick
+ if test "$mode" = -p # reword amended after pick
then
n=18
else
n=17
fi &&
git log --pretty=%s -g -n$n HEAD@{1} >actual &&
- test_cmp "$TEST_DIRECTORY/t7505/expected-rebase$mode" actual
+ test_cmp "$TEST_DIRECTORY/t7505/expected-rebase${mode:--i}" actual
'
}
-test_rebase success -i
+test_rebase success
test_have_prereq !REBASE_P || test_rebase success -p
test_expect_success 'with hook (cherry-pick)' '
'
+test_expect_success 'fast-export --reference-excluded-parents master~2..master' '
+
+ git fast-export --reference-excluded-parents master~2..master >actual &&
+ grep commit.refs/heads/master actual >commit-count &&
+ test_line_count = 2 commit-count &&
+ sed "s/master/rewrite/" actual |
+ (cd new &&
+ git fast-import &&
+ test $MASTER = $(git rev-parse --verify refs/heads/rewrite))
+'
+
+test_expect_success 'fast-export --show-original-ids' '
+
+ git fast-export --show-original-ids master >output &&
+ grep ^original-oid output| sed -e s/^original-oid.// | sort >actual &&
+ git rev-list --objects master muss >objects-and-names &&
+ awk "{print \$1}" objects-and-names | sort >commits-trees-blobs &&
+ comm -23 actual commits-trees-blobs >unfound &&
+ test_must_be_empty unfound
+'
+
+test_expect_success 'fast-export --show-original-ids | git fast-import' '
+
+ git fast-export --show-original-ids master muss | git fast-import --quiet &&
+ test $MASTER = $(git rev-parse --verify refs/heads/master) &&
+ test $MUSS = $(git rev-parse --verify refs/tags/muss)
+'
+
test_expect_success 'iso-8859-1' '
git config i18n.commitencoding ISO8859-1 &&
)
'
+test_expect_success 'rewrite tag predating pathspecs to nothing' '
+ test_create_repo rewrite_tag_predating_pathspecs &&
+ (
+ cd rewrite_tag_predating_pathspecs &&
+
+ test_commit initial &&
+
+ git tag -a -m "Some old tag" v0.0.0.0.0.0.1 &&
+
+ test_commit bar &&
+
+ git fast-export --tag-of-filtered-object=rewrite --all -- bar.t >output &&
+ grep from.$ZERO_OID output
+ )
+'
+
cat > limit-by-paths/expected << EOF
blob
mark :1
grep file0 actual
'
+test_expect_success 'avoid corrupt stream with non-existent mark' '
+ test_create_repo avoid_non_existent_mark &&
+ (
+ cd avoid_non_existent_mark &&
+
+ test_commit important-path &&
+
+ test_commit ignored &&
+
+ git branch A &&
+ git branch B &&
+
+ echo foo >>important-path.t &&
+ git add important-path.t &&
+ test_commit more changes &&
+
+ git fast-export --all -- important-path.t | git fast-import --force
+ )
+'
+
test_expect_success 'full-tree re-shows unmodified files' '
git checkout -f simple &&
git fast-export --full-tree simple >actual &&
test_cmp expected actual
'
-test_expect_success 'delete refspec' '
+test_expect_success 'delete ref because entire history excluded' '
git branch to-delete &&
- git fast-export --refspec :refs/heads/to-delete to-delete ^to-delete > actual &&
- cat > expected <<-EOF &&
+ git fast-export to-delete ^to-delete >actual &&
+ cat >expected <<-EOF &&
+ reset refs/heads/to-delete
+ from 0000000000000000000000000000000000000000
+
+ EOF
+ test_cmp expected actual
+'
+
+test_expect_success 'delete refspec' '
+ git fast-export --refspec :refs/heads/to-delete >actual &&
+ cat >expected <<-EOF &&
reset refs/heads/to-delete
from 0000000000000000000000000000000000000000
)
'
+last_shelve () {
+ p4 -G changes -s shelved -m 1 //depot/... | marshal_dump change
+}
+
make_shelved_cl() {
test_commit "$1" >/dev/null &&
git p4 submit --origin HEAD^ --shelve >/dev/null &&
) &&
(
cd "$cli" &&
- change=$(p4 -G changes -s shelved -m 1 //depot/... | \
- marshal_dump change) &&
+ change=$(last_shelve) &&
p4 unshelve -c $change -s $change &&
grep -q updated-line shelf.t &&
p4 describe -S $change | grep added-file.t &&
- test_path_is_missing shelved-change-1.t
+ test_path_is_missing shelved-change-1.t &&
+ p4 revert ...
+ )
+'
+
+test_expect_success 'update a shelve involving moved and copied files' '
+ test_when_finished cleanup_git &&
+ (
+ cd "$cli" &&
+ : >file_to_move &&
+ p4 add file_to_move &&
+ p4 submit -d "change1" &&
+ p4 edit file_to_move &&
+ echo change >>file_to_move &&
+ p4 submit -d "change2" &&
+ p4 opened
+ ) &&
+ git p4 clone --dest="$git" //depot &&
+ (
+ cd "$git" &&
+ git config git-p4.detectCopies true &&
+ git config git-p4.detectRenames true &&
+ git config git-p4.skipSubmitEdit true &&
+ mkdir moved &&
+ cp file_to_move copy_of_file &&
+ git add copy_of_file &&
+ git mv file_to_move moved/ &&
+ git commit -m "rename a file" &&
+ git p4 submit -M --shelve --origin HEAD^ &&
+ : >new_file &&
+ git add new_file &&
+ git commit --amend &&
+ git show --stat HEAD &&
+ change=$(last_shelve) &&
+ git p4 submit -M --update-shelve $change --commit HEAD
+ ) &&
+ (
+ cd "$cli" &&
+ change=$(last_shelve) &&
+ echo change=$change &&
+ p4 unshelve -s $change &&
+ p4 submit -d "Testing update-shelve" &&
+ test_path_is_file copy_of_file &&
+ test_path_is_file moved/file_to_move &&
+ test_path_is_missing file_to_move &&
+ test_path_is_file new_file &&
+ echo "unshelved and submitted change $change" &&
+ p4 changes moved/file_to_move | grep "Testing update-shelve" &&
+ p4 changes copy_of_file | grep "Testing update-shelve"
)
'
--ignore-other-worktrees Z
--recurse-submodules Z
--progress Z
- --no-quiet Z
+ --guess Z
+ --no-guess Z
--no-... Z
EOF
'
test_expect_success '<ref>: completes paths' '
test_completion "git show mytag:f" <<-\EOF
- file1 Z
- file2 Z
+ file1Z
+ file2Z
EOF
'
git add "name with spaces" &&
git commit -m spaces &&
test_completion "git show HEAD:nam" <<-\EOF
- name with spaces Z
+ name with spacesZ
EOF
'
git add "name with \${meta}" &&
git commit -m meta &&
test_completion "git show HEAD:nam" <<-\EOF
- name with ${meta} Z
- name with spaces Z
+ name with ${meta}Z
+ name with spacesZ
EOF
'
'
test_expect_success 'prompt - rebase merge' '
- printf " (b2|REBASE-m 1/3)" >expected &&
+ printf " (b2|REBASE-i 1/3)" >expected &&
git checkout b2 &&
test_when_finished "git checkout master" &&
test_must_fail git rebase --merge b1 b2 &&
fi &&
eval "printf '%s' \"\${$var}\""
}
+
+# Choose a port number based on the test script's number and store it in
+# the given variable name, unless that variable already contains a number.
+test_set_port () {
+ local var=$1 port
+
+ if test $# -ne 1 || test -z "$var"
+ then
+ BUG "test_set_port requires a variable name"
+ fi
+
+ eval port=\$$var
+ case "$port" in
+ "")
+ # No port is set in the given env var, use the test
+ # number as port number instead.
+ # Remove not only the leading 't', but all leading zeros
+ # as well, so the arithmetic below won't (mis)interpret
+ # a test number like '0123' as an octal value.
+ port=${this_test#${this_test%%[1-9]*}}
+ if test "${port:-0}" -lt 1024
+ then
+ # root-only port, use a larger one instead.
+ port=$(($port + 10000))
+ fi
+ ;;
+ *[^0-9]*|0*)
+ error >&7 "invalid port number: $port"
+ ;;
+ *)
+ # The user has specified the port.
+ ;;
+ esac
+
+ # Make sure that parallel '--stress' test jobs get different
+ # ports.
+ port=$(($port + ${GIT_TEST_STRESS_JOB_NR:-0}))
+ eval $var=$port
+}
exit 1
fi
+# Parse options while taking care to leave $@ intact, so we will still
+# have all the original command line options when executing the test
+# script again for '--tee' and '--verbose-log' below.
+store_arg_to=
+prev_opt=
+for opt
+do
+ if test -n "$store_arg_to"
+ then
+ eval $store_arg_to=\$opt
+ store_arg_to=
+ prev_opt=
+ continue
+ fi
+
+ case "$opt" in
+ -d|--d|--de|--deb|--debu|--debug)
+ debug=t ;;
+ -i|--i|--im|--imm|--imme|--immed|--immedi|--immedia|--immediat|--immediate)
+ immediate=t ;;
+ -l|--l|--lo|--lon|--long|--long-|--long-t|--long-te|--long-tes|--long-test|--long-tests)
+ GIT_TEST_LONG=t; export GIT_TEST_LONG ;;
+ -r)
+ store_arg_to=run_list
+ ;;
+ --run=*)
+ run_list=${opt#--*=} ;;
+ -h|--h|--he|--hel|--help)
+ help=t ;;
+ -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose)
+ verbose=t ;;
+ --verbose-only=*)
+ verbose_only=${opt#--*=}
+ ;;
+ -q|--q|--qu|--qui|--quie|--quiet)
+ # Ignore --quiet under a TAP::Harness. Saying how many tests
+ # passed without the ok/not ok details is always an error.
+ test -z "$HARNESS_ACTIVE" && quiet=t ;;
+ --with-dashes)
+ with_dashes=t ;;
+ --no-color)
+ color= ;;
+ --va|--val|--valg|--valgr|--valgri|--valgrin|--valgrind)
+ valgrind=memcheck
+ tee=t
+ ;;
+ --valgrind=*)
+ valgrind=${opt#--*=}
+ tee=t
+ ;;
+ --valgrind-only=*)
+ valgrind_only=${opt#--*=}
+ tee=t
+ ;;
+ --tee)
+ tee=t ;;
+ --root=*)
+ root=${opt#--*=} ;;
+ --chain-lint)
+ GIT_TEST_CHAIN_LINT=1 ;;
+ --no-chain-lint)
+ GIT_TEST_CHAIN_LINT=0 ;;
+ -x)
+ trace=t ;;
+ -V|--verbose-log)
+ verbose_log=t
+ tee=t
+ ;;
+ --stress)
+ stress=t ;;
+ --stress=*)
+ stress=${opt#--*=}
+ case "$stress" in
+ *[^0-9]*|0*|"")
+ echo "error: --stress=<N> requires the number of jobs to run" >&2
+ exit 1
+ ;;
+ *) # Good.
+ ;;
+ esac
+ ;;
+ *)
+ echo "error: unknown test option '$opt'" >&2; exit 1 ;;
+ esac
+
+ prev_opt=$opt
+done
+if test -n "$store_arg_to"
+then
+ echo "error: $prev_opt requires an argument" >&2
+ exit 1
+fi
+
+if test -n "$valgrind_only"
+then
+ test -z "$valgrind" && valgrind=memcheck
+ test -z "$verbose" && verbose_only="$valgrind_only"
+elif test -n "$valgrind"
+then
+ test -z "$verbose_log" && verbose=t
+fi
+
+if test -n "$stress"
+then
+ verbose=t
+ trace=t
+ immediate=t
+fi
+
+TEST_STRESS_JOB_SFX="${GIT_TEST_STRESS_JOB_NR:+.stress-$GIT_TEST_STRESS_JOB_NR}"
+TEST_NAME="$(basename "$0" .sh)"
+TEST_RESULTS_DIR="$TEST_OUTPUT_DIRECTORY/test-results"
+TEST_RESULTS_BASE="$TEST_RESULTS_DIR/$TEST_NAME$TEST_STRESS_JOB_SFX"
+TRASH_DIRECTORY="trash directory.$TEST_NAME$TEST_STRESS_JOB_SFX"
+test -n "$root" && TRASH_DIRECTORY="$root/$TRASH_DIRECTORY"
+case "$TRASH_DIRECTORY" in
+/*) ;; # absolute path is good
+ *) TRASH_DIRECTORY="$TEST_OUTPUT_DIRECTORY/$TRASH_DIRECTORY" ;;
+esac
+
+# If --stress was passed, run this test repeatedly in several parallel loops.
+if test "$GIT_TEST_STRESS_STARTED" = "done"
+then
+ : # Don't stress test again.
+elif test -n "$stress"
+then
+ if test "$stress" != t
+ then
+ job_count=$stress
+ elif test -n "$GIT_TEST_STRESS_LOAD"
+ then
+ job_count="$GIT_TEST_STRESS_LOAD"
+ elif job_count=$(getconf _NPROCESSORS_ONLN 2>/dev/null) &&
+ test -n "$job_count"
+ then
+ job_count=$((2 * $job_count))
+ else
+ job_count=8
+ fi
+
+ mkdir -p "$TEST_RESULTS_DIR"
+ stressfail="$TEST_RESULTS_BASE.stress-failed"
+ rm -f "$stressfail"
+
+ stress_exit=0
+ trap '
+ kill $job_pids 2>/dev/null
+ wait
+ stress_exit=1
+ ' TERM INT HUP
+
+ job_pids=
+ job_nr=0
+ while test $job_nr -lt "$job_count"
+ do
+ (
+ GIT_TEST_STRESS_STARTED=done
+ GIT_TEST_STRESS_JOB_NR=$job_nr
+ export GIT_TEST_STRESS_STARTED GIT_TEST_STRESS_JOB_NR
+
+ trap '
+ kill $test_pid 2>/dev/null
+ wait
+ exit 1
+ ' TERM INT
+
+ cnt=0
+ while ! test -e "$stressfail"
+ do
+ $TEST_SHELL_PATH "$0" "$@" >"$TEST_RESULTS_BASE.stress-$job_nr.out" 2>&1 &
+ test_pid=$!
+
+ if wait $test_pid
+ then
+ printf "OK %2d.%d\n" $GIT_TEST_STRESS_JOB_NR $cnt
+ else
+ echo $GIT_TEST_STRESS_JOB_NR >>"$stressfail"
+ printf "FAIL %2d.%d\n" $GIT_TEST_STRESS_JOB_NR $cnt
+ fi
+ cnt=$(($cnt + 1))
+ done
+ ) &
+ job_pids="$job_pids $!"
+ job_nr=$(($job_nr + 1))
+ done
+
+ wait
+
+ if test -f "$stressfail"
+ then
+ echo "Log(s) of failed test run(s):"
+ for failed_job_nr in $(sort -n "$stressfail")
+ do
+ echo "Contents of '$TEST_RESULTS_BASE.stress-$failed_job_nr.out':"
+ cat "$TEST_RESULTS_BASE.stress-$failed_job_nr.out"
+ done
+ rm -rf "$TRASH_DIRECTORY.stress-failed"
+ # Move the last one.
+ mv "$TRASH_DIRECTORY.stress-$failed_job_nr" "$TRASH_DIRECTORY.stress-failed"
+ fi
+
+ exit $stress_exit
+fi
+
# if --tee was passed, write the output not only to the terminal, but
# additionally to the file test-results/$BASENAME.out, too.
-case "$GIT_TEST_TEE_STARTED, $* " in
-done,*)
- # do not redirect again
- ;;
-*' --tee '*|*' --va'*|*' -V '*|*' --verbose-log '*)
- mkdir -p "$TEST_OUTPUT_DIRECTORY/test-results"
- BASE="$TEST_OUTPUT_DIRECTORY/test-results/$(basename "$0" .sh)"
+if test "$GIT_TEST_TEE_STARTED" = "done"
+then
+ : # do not redirect again
+elif test -n "$tee"
+then
+ mkdir -p "$TEST_RESULTS_DIR"
# Make this filename available to the sub-process in case it is using
# --verbose-log.
- GIT_TEST_TEE_OUTPUT_FILE=$BASE.out
+ GIT_TEST_TEE_OUTPUT_FILE=$TEST_RESULTS_BASE.out
export GIT_TEST_TEE_OUTPUT_FILE
# Truncate before calling "tee -a" to get rid of the results
>"$GIT_TEST_TEE_OUTPUT_FILE"
(GIT_TEST_TEE_STARTED=done ${TEST_SHELL_PATH} "$0" "$@" 2>&1;
- echo $? >"$BASE.exit") | tee -a "$GIT_TEST_TEE_OUTPUT_FILE"
- test "$(cat "$BASE.exit")" = 0
+ echo $? >"$TEST_RESULTS_BASE.exit") | tee -a "$GIT_TEST_TEE_OUTPUT_FILE"
+ test "$(cat "$TEST_RESULTS_BASE.exit")" = 0
exit
- ;;
-esac
+fi
+
+if test -n "$trace" && test -n "$test_untraceable"
+then
+ # '-x' tracing requested, but this test script can't be reliably
+ # traced, unless it is run with a Bash version supporting
+ # BASH_XTRACEFD (introduced in Bash v4.1).
+ #
+ # Perform this version check _after_ the test script was
+ # potentially re-executed with $TEST_SHELL_PATH for '--tee' or
+ # '--verbose-log', so the right shell is checked and the
+ # warning is issued only once.
+ if test -n "$BASH_VERSION" && eval '
+ test ${BASH_VERSINFO[0]} -gt 4 || {
+ test ${BASH_VERSINFO[0]} -eq 4 &&
+ test ${BASH_VERSINFO[1]} -ge 1
+ }
+ '
+ then
+ : Executed by a Bash version supporting BASH_XTRACEFD. Good.
+ else
+ echo >&2 "warning: ignoring -x; '$0' is untraceable without BASH_XTRACEFD"
+ trace=
+ fi
+fi
+if test -n "$trace" && test -z "$verbose_log"
+then
+ verbose=t
+fi
# For repeatability, reset the environment to known value.
# TERM is sanitized below, after saving color control sequences.
# Add libc MALLOC and MALLOC_PERTURB test
# only if we are not executing the test with valgrind
-if expr " $GIT_TEST_OPTS " : ".* --valgrind " >/dev/null ||
+if test -n "$valgrind" ||
test -n "$TEST_NO_MALLOC_CHECK"
then
setup_malloc_check () {
) &&
color=t
-while test "$#" -ne 0
-do
- case "$1" in
- -d|--d|--de|--deb|--debu|--debug)
- debug=t; shift ;;
- -i|--i|--im|--imm|--imme|--immed|--immedi|--immedia|--immediat|--immediate)
- immediate=t; shift ;;
- -l|--l|--lo|--lon|--long|--long-|--long-t|--long-te|--long-tes|--long-test|--long-tests)
- GIT_TEST_LONG=t; export GIT_TEST_LONG; shift ;;
- -r)
- shift; test "$#" -ne 0 || {
- echo 'error: -r requires an argument' >&2;
- exit 1;
- }
- run_list=$1; shift ;;
- --run=*)
- run_list=${1#--*=}; shift ;;
- -h|--h|--he|--hel|--help)
- help=t; shift ;;
- -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose)
- verbose=t; shift ;;
- --verbose-only=*)
- verbose_only=${1#--*=}
- shift ;;
- -q|--q|--qu|--qui|--quie|--quiet)
- # Ignore --quiet under a TAP::Harness. Saying how many tests
- # passed without the ok/not ok details is always an error.
- test -z "$HARNESS_ACTIVE" && quiet=t; shift ;;
- --with-dashes)
- with_dashes=t; shift ;;
- --no-color)
- color=; shift ;;
- --va|--val|--valg|--valgr|--valgri|--valgrin|--valgrind)
- valgrind=memcheck
- shift ;;
- --valgrind=*)
- valgrind=${1#--*=}
- shift ;;
- --valgrind-only=*)
- valgrind_only=${1#--*=}
- shift ;;
- --tee)
- shift ;; # was handled already
- --root=*)
- root=${1#--*=}
- shift ;;
- --chain-lint)
- GIT_TEST_CHAIN_LINT=1
- shift ;;
- --no-chain-lint)
- GIT_TEST_CHAIN_LINT=0
- shift ;;
- -x)
- # Some test scripts can't be reliably traced with '-x',
- # unless the test is run with a Bash version supporting
- # BASH_XTRACEFD (introduced in Bash v4.1). Check whether
- # this test is marked as such, and ignore '-x' if it
- # isn't executed with a suitable Bash version.
- if test -z "$test_untraceable" || {
- test -n "$BASH_VERSION" && {
- test ${BASH_VERSINFO[0]} -gt 4 || {
- test ${BASH_VERSINFO[0]} -eq 4 &&
- test ${BASH_VERSINFO[1]} -ge 1
- }
- }
- }
- then
- trace=t
- else
- echo >&2 "warning: ignoring -x; '$0' is untraceable without BASH_XTRACEFD"
- fi
- shift ;;
- -V|--verbose-log)
- verbose_log=t
- shift ;;
- *)
- echo "error: unknown test option '$1'" >&2; exit 1 ;;
- esac
-done
-
-if test -n "$valgrind_only"
-then
- test -z "$valgrind" && valgrind=memcheck
- test -z "$verbose" && verbose_only="$valgrind_only"
-elif test -n "$valgrind"
-then
- test -z "$verbose_log" && verbose=t
-fi
-
-if test -n "$trace" && test -z "$verbose_log"
-then
- verbose=t
-fi
-
if test -n "$color"
then
# Save the color control sequences now rather than run tput
GIT_EXIT_OK=
trap 'die' EXIT
-trap 'exit $?' INT
+trap 'exit $?' INT TERM HUP
# The user-facing functions are loaded from a separate file so that
# test_perf subshells can have them too
if test -z "$HARNESS_ACTIVE"
then
- test_results_dir="$TEST_OUTPUT_DIRECTORY/test-results"
- mkdir -p "$test_results_dir"
- base=${0##*/}
- test_results_path="$test_results_dir/${base%.sh}.counts"
+ mkdir -p "$TEST_RESULTS_DIR"
- cat >"$test_results_path" <<-EOF
+ cat >"$TEST_RESULTS_BASE.counts" <<-EOF
total $test_count
success $test_success
fixed $test_fixed
error "You haven't built things yet, have you?"
}
-if ! test -x "$GIT_BUILD_DIR"/t/helper/test-tool
+if ! test -x "$GIT_BUILD_DIR"/t/helper/test-tool$X
then
echo >&2 'You need to build test-tool:'
echo >&2 'Run "make t/helper/test-tool" in the source (toplevel) directory'
fi
# Test repository
-TRASH_DIRECTORY="trash directory.$(basename "$0" .sh)"
-test -n "$root" && TRASH_DIRECTORY="$root/$TRASH_DIRECTORY"
-case "$TRASH_DIRECTORY" in
-/*) ;; # absolute path is good
- *) TRASH_DIRECTORY="$TEST_OUTPUT_DIRECTORY/$TRASH_DIRECTORY" ;;
-esac
rm -fr "$TRASH_DIRECTORY" || {
GIT_EXIT_OK=t
echo >&5 "FATAL: Cannot prepare test area"
if (data->transport_options.update_shallow)
set_helper_option(transport, "update-shallow", "true");
- if (data->transport_options.filter_options.choice)
- set_helper_option(
- transport, "filter",
- data->transport_options.filter_options.filter_spec);
+ if (data->transport_options.filter_options.choice) {
+ struct strbuf expanded_filter_spec = STRBUF_INIT;
+ expand_list_objects_filter_spec(
+ &data->transport_options.filter_options,
+ &expanded_filter_spec);
+ set_helper_option(transport, "filter",
+ expanded_filter_spec.buf);
+ strbuf_release(&expanded_filter_spec);
+ }
if (data->transport_options.negotiation_tips)
warning("Ignoring --negotiation-tip because the protocol does not support it.");
}
-static int has_attribute(const char *attrs, const char *attr) {
+static int has_attribute(const char *attrs, const char *attr)
+{
int len;
if (!attrs)
return 0;
return 0; /* No space for more. */
transfer_debug("%s is readable", t->src_name);
- bytes = read(t->src, t->buf + t->bufuse, BUFFERSIZE - t->bufuse);
- if (bytes < 0 && errno != EWOULDBLOCK && errno != EAGAIN &&
- errno != EINTR) {
+ bytes = xread(t->src, t->buf + t->bufuse, BUFFERSIZE - t->bufuse);
+ if (bytes < 0) {
error_errno(_("read(%s) failed"), t->src_name);
return -1;
} else if (bytes == 0) {
transfer_debug("%s is writable", t->dest_name);
bytes = xwrite(t->dest, t->buf, t->bufuse);
- if (bytes < 0 && errno != EWOULDBLOCK) {
+ if (bytes < 0) {
error_errno(_("write(%s) failed"), t->dest_name);
return -1;
} else if (bytes > 0) {
int nr_heads, struct ref **to_fetch)
{
struct bundle_transport_data *data = transport->data;
- return unbundle(&data->header, data->fd,
+ return unbundle(the_repository, &data->header, data->fd,
transport->progress ? BUNDLE_VERBOSE : 0);
}
packet_reader_init(&reader, data->fd[0], NULL, 0,
PACKET_READ_CHOMP_NEWLINE |
- PACKET_READ_GENTLE_ON_EOF);
+ PACKET_READ_GENTLE_ON_EOF |
+ PACKET_READ_DIE_ON_ERR_PACKET);
data->version = discover_version(&reader);
switch (data->version) {
return ret;
}
-int transport_push(struct transport *transport,
+int transport_push(struct repository *r,
+ struct transport *transport,
struct refspec *rs, int flags,
unsigned int *reject_reasons)
{
oid_array_append(&commits,
&ref->new_oid);
- if (!push_unpushed_submodules(the_repository,
+ if (!push_unpushed_submodules(r,
&commits,
transport->remote,
rs,
oid_array_append(&commits,
&ref->new_oid);
- if (find_unpushed_submodules(the_repository,
+ if (find_unpushed_submodules(r,
&commits,
transport->remote->name,
&needs_pushing)) {
void *data;
};
-static int refs_from_alternate_cb(struct alternate_object_database *e,
+static int refs_from_alternate_cb(struct object_directory *e,
void *data)
{
struct strbuf path = STRBUF_INIT;
#define REJECT_FETCH_FIRST 0x08
#define REJECT_NEEDS_FORCE 0x10
-int transport_push(struct transport *connection,
+int transport_push(struct repository *repo,
+ struct transport *connection,
struct refspec *rs, int flags,
unsigned int * reject_reasons);
DIFF_STATUS_ADDED;
if (tpi_valid) {
- oid_i = tp[i].entry.oid;
+ oid_i = &tp[i].entry.oid;
mode_i = tp[i].entry.mode;
}
else {
/* same rule as in emitthis */
int tpi_valid = tp && !(tp[i].entry.mode & S_IFXMIN_NEQ);
- parents_oid[i] = tpi_valid ? tp[i].entry.oid : NULL;
+ parents_oid[i] = tpi_valid ? &tp[i].entry.oid : NULL;
}
strbuf_add(base, path, pathlen);
enum interesting match;
while (t->size) {
- match = tree_entry_interesting(&t->entry, base, 0, &opt->pathspec);
+ match = tree_entry_interesting(opt->repo->index, &t->entry,
+ base, 0, &opt->pathspec);
if (match) {
if (match == all_entries_not_interesting)
t->size = 0;
continue;
/* diff(t,pi) != ø */
- if (!oideq(t.entry.oid, tp[i].entry.oid) ||
+ if (!oideq(&t.entry.oid, &tp[i].entry.oid) ||
(t.entry.mode != tp[i].entry.mode))
continue;
/* Initialize the descriptor entry */
desc->entry.path = path;
desc->entry.mode = canon_mode(mode);
- desc->entry.oid = (const struct object_id *)(path + len);
+ desc->entry.pathlen = len - 1;
+ hashcpy(desc->entry.oid.hash, (const unsigned char *)path + len);
return 0;
}
static int update_tree_entry_internal(struct tree_desc *desc, struct strbuf *err)
{
const void *buf = desc->buffer;
- const unsigned char *end = desc->entry.oid->hash + the_hash_algo->rawsz;
+ const unsigned char *end = (const unsigned char *)desc->entry.path + desc->entry.pathlen + 1 + the_hash_algo->rawsz;
unsigned long size = desc->size;
unsigned long len = end - (const unsigned char *)buf;
pathlen--;
info->pathlen = pathlen ? pathlen + 1 : 0;
info->name.path = base;
- info->name.oid = (void *)(base + pathlen + 1);
- if (pathlen)
+ info->name.pathlen = pathlen;
+ if (pathlen) {
+ hashcpy(info->name.oid.hash, (const unsigned char *)base + pathlen + 1);
info->prev = &dummy;
+ }
}
char *make_traverse_path(char *path, const struct traverse_info *info, const struct name_entry *n)
}
}
-static inline int prune_traversal(struct name_entry *e,
+static inline int prune_traversal(struct index_state *istate,
+ struct name_entry *e,
struct traverse_info *info,
struct strbuf *base,
int still_interesting)
return 2;
if (still_interesting < 0)
return still_interesting;
- return tree_entry_interesting(e, base, 0, info->pathspec);
+ return tree_entry_interesting(istate, e, base,
+ 0, info->pathspec);
}
-int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info)
+int traverse_trees(struct index_state *istate,
+ int n, struct tree_desc *t,
+ struct traverse_info *info)
{
int error = 0;
struct name_entry *entry = xmalloc(n*sizeof(*entry));
}
if (!mask)
break;
- interesting = prune_traversal(e, info, &base, interesting);
+ interesting = prune_traversal(istate, e, info, &base, interesting);
if (interesting < 0)
break;
if (interesting) {
int namelen = strlen(name);
while (t->size) {
const char *entry;
- const struct object_id *oid;
+ struct object_id oid;
int entrylen, cmp;
- oid = tree_entry_extract(t, &entry, mode);
+ oidcpy(&oid, tree_entry_extract(t, &entry, mode));
entrylen = tree_entry_len(&t->entry);
update_tree_entry(t);
if (entrylen > namelen)
if (cmp < 0)
break;
if (entrylen == namelen) {
- oidcpy(result, oid);
+ oidcpy(result, &oid);
return 0;
}
if (name[entrylen] != '/')
if (!S_ISDIR(*mode))
break;
if (++entrylen == namelen) {
- oidcpy(result, oid);
+ oidcpy(result, &oid);
return 0;
}
- return get_tree_entry(oid, name + entrylen, result, mode);
+ return get_tree_entry(&oid, name + entrylen, result, mode);
}
return -1;
}
* with the sha1 of the found object, and *mode will hold the mode of
* the object.
*
- * See the code for enum follow_symlink_result for a description of
+ * See the code for enum get_oid_result for a description of
* the return values.
*/
-enum follow_symlinks_result get_tree_entry_follow_symlinks(struct object_id *tree_oid, const char *name, struct object_id *result, struct strbuf *result_path, unsigned *mode)
+enum get_oid_result get_tree_entry_follow_symlinks(struct object_id *tree_oid, const char *name, struct object_id *result, struct strbuf *result_path, unsigned *mode)
{
int retval = MISSING_OBJECT;
struct dir_state *parents = NULL;
* Pre-condition: either baselen == base_offset (i.e. empty path)
* or base[baselen-1] == '/' (i.e. with trailing slash).
*/
-static enum interesting do_match(const struct name_entry *entry,
+static enum interesting do_match(struct index_state *istate,
+ const struct name_entry *entry,
struct strbuf *base, int base_offset,
const struct pathspec *ps,
int exclude)
PATHSPEC_LITERAL |
PATHSPEC_GLOB |
PATHSPEC_ICASE |
- PATHSPEC_EXCLUDE);
+ PATHSPEC_EXCLUDE |
+ PATHSPEC_ATTR);
if (!ps->nr) {
if (!ps->recursive ||
if (!ps->recursive ||
!(ps->magic & PATHSPEC_MAXDEPTH) ||
- ps->max_depth == -1)
- return all_entries_interesting;
-
- return within_depth(base_str + matchlen + 1,
- baselen - matchlen - 1,
- !!S_ISDIR(entry->mode),
- ps->max_depth) ?
- entry_interesting : entry_not_interesting;
+ ps->max_depth == -1) {
+ if (!item->attr_match_nr)
+ return all_entries_interesting;
+ else
+ goto interesting;
+ }
+
+ if (within_depth(base_str + matchlen + 1,
+ baselen - matchlen - 1,
+ !!S_ISDIR(entry->mode),
+ ps->max_depth))
+ goto interesting;
+ else
+ return entry_not_interesting;
}
/* Either there must be no base, or the base must match. */
if (match_entry(item, entry, pathlen,
match + baselen, matchlen - baselen,
&never_interesting))
- return entry_interesting;
+ goto interesting;
if (item->nowildcard_len < item->len) {
if (!git_fnmatch(item, match + baselen, entry->path,
item->nowildcard_len - baselen))
- return entry_interesting;
+ goto interesting;
/*
* Match all directories. We'll try to
!ps_strncmp(item, match + baselen,
entry->path,
item->nowildcard_len - baselen))
- return entry_interesting;
+ goto interesting;
}
continue;
if (!git_fnmatch(item, match, base->buf + base_offset,
item->nowildcard_len)) {
strbuf_setlen(base, base_offset + baselen);
- return entry_interesting;
+ goto interesting;
}
/*
!ps_strncmp(item, match, base->buf + base_offset,
item->nowildcard_len)) {
strbuf_setlen(base, base_offset + baselen);
- return entry_interesting;
+ goto interesting;
}
strbuf_setlen(base, base_offset + baselen);
*/
if (ps->recursive && S_ISDIR(entry->mode))
return entry_interesting;
+ continue;
+interesting:
+ if (item->attr_match_nr) {
+ int ret;
+
+ /*
+ * Must not return all_entries_not_interesting
+ * prematurely. We do not know if all entries do not
+ * match some attributes with current attr API.
+ */
+ never_interesting = entry_not_interesting;
+
+ /*
+ * Consider all directories interesting (because some
+ * of those files inside may match some attributes
+ * even though the parent dir does not)
+ *
+ * FIXME: attributes _can_ match directories and we
+ * can probably return all_entries_interesting or
+ * all_entries_not_interesting here if matched.
+ */
+ if (S_ISDIR(entry->mode))
+ return entry_interesting;
+
+ strbuf_add(base, entry->path, pathlen);
+ ret = match_pathspec_attrs(istate, base->buf + base_offset,
+ base->len - base_offset, item);
+ strbuf_setlen(base, base_offset + baselen);
+ if (!ret)
+ continue;
+ }
+ return entry_interesting;
}
return never_interesting; /* No matches */
}
* Pre-condition: either baselen == base_offset (i.e. empty path)
* or base[baselen-1] == '/' (i.e. with trailing slash).
*/
-enum interesting tree_entry_interesting(const struct name_entry *entry,
+enum interesting tree_entry_interesting(struct index_state *istate,
+ const struct name_entry *entry,
struct strbuf *base, int base_offset,
const struct pathspec *ps)
{
enum interesting positive, negative;
- positive = do_match(entry, base, base_offset, ps, 0);
+ positive = do_match(istate, entry, base, base_offset, ps, 0);
/*
* case | entry | positive | negative | result
positive <= entry_not_interesting) /* #1, #2, #11, #12 */
return positive;
- negative = do_match(entry, base, base_offset, ps, 1);
+ negative = do_match(istate, entry, base, base_offset, ps, 1);
/* #8, #18 */
if (positive == all_entries_interesting &&
#ifndef TREE_WALK_H
#define TREE_WALK_H
-struct strbuf;
+#include "cache.h"
struct name_entry {
- const struct object_id *oid;
+ struct object_id oid;
const char *path;
+ int pathlen;
unsigned int mode;
};
{
*pathp = desc->entry.path;
*modep = desc->entry.mode;
- return desc->entry.oid;
+ return &desc->entry.oid;
}
static inline int tree_entry_len(const struct name_entry *ne)
{
- return (const char *)ne->oid - ne->path - 1;
+ return ne->pathlen;
}
/*
struct traverse_info;
typedef int (*traverse_callback_t)(int n, unsigned long mask, unsigned long dirmask, struct name_entry *entry, struct traverse_info *);
-int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info);
-
-enum follow_symlinks_result {
- FOUND = 0, /* This includes out-of-tree links */
- MISSING_OBJECT = -1, /* The initial symlink is missing */
- DANGLING_SYMLINK = -2, /*
- * The initial symlink is there, but
- * (transitively) points to a missing
- * in-tree file
- */
- SYMLINK_LOOP = -3,
- NOT_DIR = -4, /*
- * Somewhere along the symlink chain, a path is
- * requested which contains a file as a
- * non-final element.
- */
-};
+int traverse_trees(struct index_state *istate, int n, struct tree_desc *t, struct traverse_info *info);
-enum follow_symlinks_result get_tree_entry_follow_symlinks(struct object_id *tree_oid, const char *name, struct object_id *result, struct strbuf *result_path, unsigned *mode);
+enum get_oid_result get_tree_entry_follow_symlinks(struct object_id *tree_oid, const char *name, struct object_id *result, struct strbuf *result_path, unsigned *mode);
struct traverse_info {
const char *traverse_path;
all_entries_interesting = 2 /* yes, and all subsequent entries will be */
};
-extern enum interesting tree_entry_interesting(const struct name_entry *,
- struct strbuf *, int,
- const struct pathspec *ps);
+enum interesting tree_entry_interesting(struct index_state *istate,
+ const struct name_entry *,
+ struct strbuf *, int,
+ const struct pathspec *ps);
#endif
-#define NO_THE_INDEX_COMPATIBILITY_MACROS
#include "cache.h"
#include "cache-tree.h"
#include "tree.h"
ADD_CACHE_JUST_APPEND);
}
-static int read_tree_1(struct tree *tree, struct strbuf *base,
+static int read_tree_1(struct repository *r,
+ struct tree *tree, struct strbuf *base,
int stage, const struct pathspec *pathspec,
read_tree_fn_t fn, void *context)
{
while (tree_entry(&desc, &entry)) {
if (retval != all_entries_interesting) {
- retval = tree_entry_interesting(&entry, base, 0, pathspec);
+ retval = tree_entry_interesting(r->index, &entry,
+ base, 0, pathspec);
if (retval == all_entries_not_interesting)
break;
if (retval == entry_not_interesting)
continue;
}
- switch (fn(entry.oid, base,
+ switch (fn(&entry.oid, base,
entry.path, entry.mode, stage, context)) {
case 0:
continue;
}
if (S_ISDIR(entry.mode))
- oidcpy(&oid, entry.oid);
+ oidcpy(&oid, &entry.oid);
else if (S_ISGITLINK(entry.mode)) {
struct commit *commit;
- commit = lookup_commit(the_repository, entry.oid);
+ commit = lookup_commit(r, &entry.oid);
if (!commit)
die("Commit %s in submodule path %s%s not found",
- oid_to_hex(entry.oid),
+ oid_to_hex(&entry.oid),
base->buf, entry.path);
if (parse_commit(commit))
die("Invalid commit %s in submodule path %s%s",
- oid_to_hex(entry.oid),
+ oid_to_hex(&entry.oid),
base->buf, entry.path);
oidcpy(&oid, get_commit_tree_oid(commit));
len = tree_entry_len(&entry);
strbuf_add(base, entry.path, len);
strbuf_addch(base, '/');
- retval = read_tree_1(lookup_tree(the_repository, &oid),
+ retval = read_tree_1(r, lookup_tree(r, &oid),
base, stage, pathspec,
fn, context);
strbuf_setlen(base, oldlen);
return 0;
}
-int read_tree_recursive(struct tree *tree,
+int read_tree_recursive(struct repository *r,
+ struct tree *tree,
const char *base, int baselen,
int stage, const struct pathspec *pathspec,
read_tree_fn_t fn, void *context)
int ret;
strbuf_add(&sb, base, baselen);
- ret = read_tree_1(tree, &sb, stage, pathspec, fn, context);
+ ret = read_tree_1(r, tree, &sb, stage, pathspec, fn, context);
strbuf_release(&sb);
return ret;
}
ce2->name, ce2->ce_namelen, ce_stage(ce2));
}
-int read_tree(struct tree *tree, int stage, struct pathspec *match,
- struct index_state *istate)
+int read_tree(struct repository *r, struct tree *tree, int stage,
+ struct pathspec *match, struct index_state *istate)
{
read_tree_fn_t fn = NULL;
int i, err;
if (!fn)
fn = read_one_entry_quick;
- err = read_tree_recursive(tree, "", 0, stage, match, fn, istate);
+ err = read_tree_recursive(r, tree, "", 0, stage, match, fn, istate);
if (fn == read_one_entry || err)
return err;
#include "object.h"
-extern const char *tree_type;
+struct repository;
struct strbuf;
struct tree {
unsigned long size;
};
+extern const char *tree_type;
+
struct tree *lookup_tree(struct repository *r, const struct object_id *oid);
int parse_tree_buffer(struct tree *item, void *buffer, unsigned long size);
#define READ_TREE_RECURSIVE 1
typedef int (*read_tree_fn_t)(const struct object_id *, struct strbuf *, const char *, unsigned int, int, void *);
-extern int read_tree_recursive(struct tree *tree,
- const char *base, int baselen,
- int stage, const struct pathspec *pathspec,
- read_tree_fn_t fn, void *context);
+int read_tree_recursive(struct repository *r,
+ struct tree *tree,
+ const char *base, int baselen,
+ int stage, const struct pathspec *pathspec,
+ read_tree_fn_t fn, void *context);
-extern int read_tree(struct tree *tree, int stage, struct pathspec *pathspec,
- struct index_state *istate);
+int read_tree(struct repository *r, struct tree *tree,
+ int stage, struct pathspec *pathspec,
+ struct index_state *istate);
#endif /* TREE_H */
-#define NO_THE_INDEX_COMPATIBILITY_MACROS
#include "cache.h"
#include "argv-array.h"
#include "repository.h"
repo_read_gitmodules(the_repository);
} else if (state && (ce->ce_flags & CE_UPDATE)) {
submodule_free(the_repository);
- checkout_entry(ce, state, NULL);
+ checkout_entry(ce, state, NULL, NULL);
repo_read_gitmodules(the_repository);
}
}
display_progress(progress, ++cnt);
ce->ce_flags &= ~CE_UPDATE;
if (o->update && !o->dry_run) {
- errs |= checkout_entry(ce, &state, NULL);
+ errs |= checkout_entry(ce, &state, NULL, NULL);
}
}
}
stop_progress(&progress);
- errs |= finish_delayed_checkout(&state);
+ errs |= finish_delayed_checkout(&state, NULL);
if (o->update)
git_attr_set_direction(GIT_ATTR_CHECKIN);
static inline int are_same_oid(struct name_entry *name_j, struct name_entry *name_k)
{
- return name_j->oid && name_k->oid && oideq(name_j->oid, name_k->oid);
+ return !is_null_oid(&name_j->oid) && !is_null_oid(&name_k->oid) && oideq(&name_j->oid, &name_k->oid);
}
static int all_trees_same_as_cache_tree(int n, unsigned long dirmask,
struct name_entry *names,
struct traverse_info *info)
{
+ struct unpack_trees_options *o = info->data;
int i, ret, bottom;
int nr_buf = 0;
struct tree_desc t[MAX_UNPACK_TREES];
nr_entries = all_trees_same_as_cache_tree(n, dirmask, names, info);
if (nr_entries > 0) {
- struct unpack_trees_options *o = info->data;
int pos = index_pos_by_traverse_info(names, info);
if (!o->merge || df_conflicts)
else {
const struct object_id *oid = NULL;
if (dirmask & 1)
- oid = names[i].oid;
+ oid = &names[i].oid;
buf[nr_buf++] = fill_tree_descriptor(t + i, oid);
}
}
bottom = switch_cache_bottom(&newinfo);
- ret = traverse_trees(n, t, &newinfo);
+ ret = traverse_trees(o->src_index, n, t, &newinfo);
restore_cache_bottom(&newinfo, bottom);
for (i = 0; i < nr_buf; i++)
ce->ce_mode = create_ce_mode(n->mode);
ce->ce_flags = create_ce_flags(stage);
ce->ce_namelen = len;
- oidcpy(&ce->oid, n->oid);
+ oidcpy(&ce->oid, &n->oid);
make_traverse_path(ce->name, info, n);
return ce;
}
trace_performance_enter();
- ret = traverse_trees(len, t, &info);
+ ret = traverse_trees(o->src_index, len, t, &info);
trace_performance_leave("traverse_trees");
if (ret < 0)
goto return_failed;
move_index_extensions(&o->result, o->src_index);
if (!ret) {
if (git_env_bool("GIT_TEST_CHECK_CACHE_TREE", 0))
- cache_tree_verify(&o->result);
+ cache_tree_verify(the_repository, &o->result);
if (!o->result.cache_tree)
o->result.cache_tree = cache_tree();
if (!cache_tree_fully_valid(o->result.cache_tree))
static timestamp_t oldest_have;
-static int deepen_relative;
static int multi_ack;
static int no_done;
static int use_thin_pack, use_ofs_delta, use_include_tag;
static int allow_ref_in_want;
static struct list_objects_filter_options filter_options;
+static int allow_sideband_all;
+
static void reset_timeout(void)
{
alarm(timeout);
if (use_include_tag)
argv_array_push(&pack_objects.args, "--include-tag");
if (filter_options.filter_spec) {
+ struct strbuf expanded_filter_spec = STRBUF_INIT;
+ expand_list_objects_filter_spec(&filter_options,
+ &expanded_filter_spec);
if (pack_objects.use_shell) {
struct strbuf buf = STRBUF_INIT;
- sq_quote_buf(&buf, filter_options.filter_spec);
+ sq_quote_buf(&buf, expanded_filter_spec.buf);
argv_array_pushf(&pack_objects.args, "--filter=%s", buf.buf);
strbuf_release(&buf);
} else {
argv_array_pushf(&pack_objects.args, "--filter=%s",
- filter_options.filter_spec);
+ expanded_filter_spec.buf);
}
}
min_generation);
}
-static int get_common_commits(struct object_array *have_obj,
+static int get_common_commits(struct packet_reader *reader,
+ struct object_array *have_obj,
struct object_array *want_obj)
{
struct object_id oid;
save_commit_buffer = 0;
for (;;) {
- char *line = packet_read_line(0, NULL);
const char *arg;
reset_timeout();
- if (!line) {
+ if (packet_reader_read(reader) != PACKET_READ_NORMAL) {
if (multi_ack == 2 && got_common
&& !got_other && ok_to_give_up(have_obj, want_obj)) {
sent_ready = 1;
got_other = 0;
continue;
}
- if (skip_prefix(line, "have ", &arg)) {
+ if (skip_prefix(reader->line, "have ", &arg)) {
switch (got_oid(arg, &oid, have_obj)) {
case -1: /* they have what we do not */
got_other = 1;
}
continue;
}
- if (!strcmp(line, "done")) {
+ if (!strcmp(reader->line, "done")) {
if (have_obj->nr > 0) {
if (multi_ack)
packet_write_fmt(1, "ACK %s\n", last_hex);
packet_write_fmt(1, "NAK\n");
return -1;
}
- die("git upload-pack: expected SHA1 list, got '%s'", line);
+ die("git upload-pack: expected SHA1 list, got '%s'", reader->line);
}
}
}
}
-static void send_shallow(struct commit_list *result)
+static void send_shallow(struct packet_writer *writer,
+ struct commit_list *result)
{
while (result) {
struct object *object = &result->item->object;
if (!(object->flags & (CLIENT_SHALLOW|NOT_SHALLOW))) {
- packet_write_fmt(1, "shallow %s",
- oid_to_hex(&object->oid));
+ packet_writer_write(writer, "shallow %s",
+ oid_to_hex(&object->oid));
register_shallow(the_repository, &object->oid);
shallow_nr++;
}
}
}
-static void send_unshallow(const struct object_array *shallows,
+static void send_unshallow(struct packet_writer *writer,
+ const struct object_array *shallows,
struct object_array *want_obj)
{
int i;
struct object *object = shallows->objects[i].item;
if (object->flags & NOT_SHALLOW) {
struct commit_list *parents;
- packet_write_fmt(1, "unshallow %s",
- oid_to_hex(&object->oid));
+ packet_writer_write(writer, "unshallow %s",
+ oid_to_hex(&object->oid));
object->flags &= ~CLIENT_SHALLOW;
/*
* We want to _register_ "object" as shallow, but we
}
}
-static void deepen(int depth, int deepen_relative,
+static int check_ref(const char *refname_full, const struct object_id *oid,
+ int flag, void *cb_data);
+static void deepen(struct packet_writer *writer, int depth, int deepen_relative,
struct object_array *shallows, struct object_array *want_obj)
{
if (depth == INFINITE_DEPTH && !is_repository_shallow(the_repository)) {
struct object_array reachable_shallows = OBJECT_ARRAY_INIT;
struct commit_list *result;
+ /*
+ * Checking for reachable shallows requires that our refs be
+ * marked with OUR_REF.
+ */
+ head_ref_namespaced(check_ref, NULL);
+ for_each_namespaced_ref(check_ref, NULL);
+
get_reachable_list(shallows, &reachable_shallows);
result = get_shallow_commits(&reachable_shallows,
depth + 1,
SHALLOW, NOT_SHALLOW);
- send_shallow(result);
+ send_shallow(writer, result);
free_commit_list(result);
object_array_clear(&reachable_shallows);
} else {
result = get_shallow_commits(want_obj, depth,
SHALLOW, NOT_SHALLOW);
- send_shallow(result);
+ send_shallow(writer, result);
free_commit_list(result);
}
- send_unshallow(shallows, want_obj);
+ send_unshallow(writer, shallows, want_obj);
}
-static void deepen_by_rev_list(int ac, const char **av,
+static void deepen_by_rev_list(struct packet_writer *writer, int ac,
+ const char **av,
struct object_array *shallows,
struct object_array *want_obj)
{
close_commit_graph(the_repository);
result = get_shallow_commits_by_rev_list(ac, av, SHALLOW, NOT_SHALLOW);
- send_shallow(result);
+ send_shallow(writer, result);
free_commit_list(result);
- send_unshallow(shallows, want_obj);
+ send_unshallow(writer, shallows, want_obj);
}
/* Returns 1 if a shallow list is sent or 0 otherwise */
-static int send_shallow_list(int depth, int deepen_rev_list,
+static int send_shallow_list(struct packet_writer *writer,
+ int depth, int deepen_rev_list,
timestamp_t deepen_since,
struct string_list *deepen_not,
+ int deepen_relative,
struct object_array *shallows,
struct object_array *want_obj)
{
if (depth > 0 && deepen_rev_list)
die("git upload-pack: deepen and deepen-since (or deepen-not) cannot be used together");
if (depth > 0) {
- deepen(depth, deepen_relative, shallows, want_obj);
+ deepen(writer, depth, deepen_relative, shallows, want_obj);
ret = 1;
} else if (deepen_rev_list) {
struct argv_array av = ARGV_ARRAY_INIT;
struct object *o = want_obj->objects[i].item;
argv_array_push(&av, oid_to_hex(&o->oid));
}
- deepen_by_rev_list(av.argc, av.argv, shallows, want_obj);
+ deepen_by_rev_list(writer, av.argc, av.argv, shallows, want_obj);
argv_array_clear(&av);
ret = 1;
} else {
return 0;
}
-static void receive_needs(struct object_array *want_obj)
+static void receive_needs(struct packet_reader *reader, struct object_array *want_obj)
{
struct object_array shallows = OBJECT_ARRAY_INIT;
struct string_list deepen_not = STRING_LIST_INIT_DUP;
int has_non_tip = 0;
timestamp_t deepen_since = 0;
int deepen_rev_list = 0;
+ int deepen_relative = 0;
+ struct packet_writer writer;
shallow_nr = 0;
+ packet_writer_init(&writer, 1);
for (;;) {
struct object *o;
const char *features;
struct object_id oid_buf;
- char *line = packet_read_line(0, NULL);
const char *arg;
reset_timeout();
- if (!line)
+ if (packet_reader_read(reader) != PACKET_READ_NORMAL)
break;
- if (process_shallow(line, &shallows))
+ if (process_shallow(reader->line, &shallows))
continue;
- if (process_deepen(line, &depth))
+ if (process_deepen(reader->line, &depth))
continue;
- if (process_deepen_since(line, &deepen_since, &deepen_rev_list))
+ if (process_deepen_since(reader->line, &deepen_since, &deepen_rev_list))
continue;
- if (process_deepen_not(line, &deepen_not, &deepen_rev_list))
+ if (process_deepen_not(reader->line, &deepen_not, &deepen_rev_list))
continue;
- if (skip_prefix(line, "filter ", &arg)) {
+ if (skip_prefix(reader->line, "filter ", &arg)) {
if (!filter_capability_requested)
die("git upload-pack: filtering capability not negotiated");
parse_list_objects_filter(&filter_options, arg);
continue;
}
- if (!skip_prefix(line, "want ", &arg) ||
+ if (!skip_prefix(reader->line, "want ", &arg) ||
parse_oid_hex(arg, &oid_buf, &features))
die("git upload-pack: protocol error, "
- "expected to get object ID, not '%s'", line);
+ "expected to get object ID, not '%s'", reader->line);
if (parse_feature_request(features, "deepen-relative"))
deepen_relative = 1;
o = parse_object(the_repository, &oid_buf);
if (!o) {
- packet_write_fmt(1,
- "ERR upload-pack: not our ref %s",
- oid_to_hex(&oid_buf));
+ packet_writer_error(&writer,
+ "upload-pack: not our ref %s",
+ oid_to_hex(&oid_buf));
die("git upload-pack: not our ref %s",
oid_to_hex(&oid_buf));
}
if (depth == 0 && !deepen_rev_list && shallows.nr == 0)
return;
- if (send_shallow_list(depth, deepen_rev_list, deepen_since,
- &deepen_not, &shallows, want_obj))
+ if (send_shallow_list(&writer, depth, deepen_rev_list, deepen_since,
+ &deepen_not, deepen_relative, &shallows,
+ want_obj))
packet_flush(1);
object_array_clear(&shallows);
}
allow_filter = git_config_bool(var, value);
} else if (!strcmp("uploadpack.allowrefinwant", var)) {
allow_ref_in_want = git_config_bool(var, value);
+ } else if (!strcmp("uploadpack.allowsidebandall", var)) {
+ allow_sideband_all = git_config_bool(var, value);
}
if (current_config_scope() != CONFIG_SCOPE_REPO) {
{
struct string_list symref = STRING_LIST_INIT_DUP;
struct object_array want_obj = OBJECT_ARRAY_INIT;
+ struct packet_reader reader;
stateless_rpc = options->stateless_rpc;
timeout = options->timeout;
if (options->advertise_refs)
return;
- receive_needs(&want_obj);
+ packet_reader_init(&reader, 0, NULL, 0,
+ PACKET_READ_CHOMP_NEWLINE |
+ PACKET_READ_DIE_ON_ERR_PACKET);
+
+ receive_needs(&reader, &want_obj);
if (want_obj.nr) {
struct object_array have_obj = OBJECT_ARRAY_INIT;
- get_common_commits(&have_obj, &want_obj);
+ get_common_commits(&reader, &have_obj, &want_obj);
create_pack_file(&have_obj, &want_obj);
}
}
int deepen_rev_list;
int deepen_relative;
+ struct packet_writer writer;
+
unsigned stateless_rpc : 1;
unsigned use_thin_pack : 1;
data->haves = haves;
data->shallows = shallows;
data->deepen_not = deepen_not;
+ packet_writer_init(&data->writer, 1);
}
static void upload_pack_data_clear(struct upload_pack_data *data)
string_list_clear(&data->deepen_not, 0);
}
-static int parse_want(const char *line, struct object_array *want_obj)
+static int parse_want(struct packet_writer *writer, const char *line,
+ struct object_array *want_obj)
{
const char *arg;
if (skip_prefix(line, "want ", &arg)) {
o = parse_object(the_repository, &oid);
if (!o) {
- packet_write_fmt(1,
- "ERR upload-pack: not our ref %s",
- oid_to_hex(&oid));
+ packet_writer_error(writer,
+ "upload-pack: not our ref %s",
+ oid_to_hex(&oid));
die("git upload-pack: not our ref %s",
oid_to_hex(&oid));
}
return 0;
}
-static int parse_want_ref(const char *line, struct string_list *wanted_refs,
+static int parse_want_ref(struct packet_writer *writer, const char *line,
+ struct string_list *wanted_refs,
struct object_array *want_obj)
{
const char *arg;
struct object *o;
if (read_ref(arg, &oid)) {
- packet_write_fmt(1, "ERR unknown ref %s", arg);
+ packet_writer_error(writer, "unknown ref %s", arg);
die("unknown ref %s", arg);
}
const char *p;
/* process want */
- if (parse_want(arg, want_obj))
+ if (parse_want(&data->writer, arg, want_obj))
continue;
if (allow_ref_in_want &&
- parse_want_ref(arg, &data->wanted_refs, want_obj))
+ parse_want_ref(&data->writer, arg, &data->wanted_refs,
+ want_obj))
continue;
/* process have line */
if (parse_have(arg, &data->haves))
continue;
}
+ if ((git_env_bool("GIT_TEST_SIDEBAND_ALL", 0) ||
+ allow_sideband_all) &&
+ !strcmp(arg, "sideband-all")) {
+ data->writer.use_sideband = 1;
+ continue;
+ }
+
/* ignore unknown lines maybe? */
die("unexpected line: '%s'", arg);
}
return 0;
}
-static int send_acks(struct oid_array *acks, struct strbuf *response,
+static int send_acks(struct packet_writer *writer, struct oid_array *acks,
const struct object_array *have_obj,
struct object_array *want_obj)
{
int i;
- packet_buf_write(response, "acknowledgments\n");
+ packet_writer_write(writer, "acknowledgments\n");
/* Send Acks */
if (!acks->nr)
- packet_buf_write(response, "NAK\n");
+ packet_writer_write(writer, "NAK\n");
for (i = 0; i < acks->nr; i++) {
- packet_buf_write(response, "ACK %s\n",
- oid_to_hex(&acks->oid[i]));
+ packet_writer_write(writer, "ACK %s\n",
+ oid_to_hex(&acks->oid[i]));
}
if (ok_to_give_up(have_obj, want_obj)) {
/* Send Ready */
- packet_buf_write(response, "ready\n");
+ packet_writer_write(writer, "ready\n");
return 1;
}
struct object_array *want_obj)
{
struct oid_array common = OID_ARRAY_INIT;
- struct strbuf response = STRBUF_INIT;
int ret = 0;
process_haves(&data->haves, &common, have_obj);
if (data->done) {
ret = 1;
- } else if (send_acks(&common, &response, have_obj, want_obj)) {
- packet_buf_delim(&response);
+ } else if (send_acks(&data->writer, &common, have_obj, want_obj)) {
+ packet_writer_delim(&data->writer);
ret = 1;
} else {
/* Add Flush */
- packet_buf_flush(&response);
+ packet_writer_flush(&data->writer);
ret = 0;
}
- /* Send response */
- write_or_die(1, response.buf, response.len);
- strbuf_release(&response);
-
oid_array_clear(&data->haves);
oid_array_clear(&common);
return ret;
if (!data->wanted_refs.nr)
return;
- packet_write_fmt(1, "wanted-refs\n");
+ packet_writer_write(&data->writer, "wanted-refs\n");
for_each_string_list_item(item, &data->wanted_refs) {
- packet_write_fmt(1, "%s %s\n",
- oid_to_hex(item->util),
- item->string);
+ packet_writer_write(&data->writer, "%s %s\n",
+ oid_to_hex(item->util),
+ item->string);
}
- packet_delim(1);
+ packet_writer_delim(&data->writer);
}
static void send_shallow_info(struct upload_pack_data *data,
!is_repository_shallow(the_repository))
return;
- packet_write_fmt(1, "shallow-info\n");
+ packet_writer_write(&data->writer, "shallow-info\n");
- if (!send_shallow_list(data->depth, data->deepen_rev_list,
+ if (!send_shallow_list(&data->writer, data->depth,
+ data->deepen_rev_list,
data->deepen_since, &data->deepen_not,
+ data->deepen_relative,
&data->shallows, want_obj) &&
is_repository_shallow(the_repository))
- deepen(INFINITE_DEPTH, data->deepen_relative, &data->shallows,
- want_obj);
+ deepen(&data->writer, INFINITE_DEPTH, data->deepen_relative,
+ &data->shallows, want_obj);
packet_delim(1);
}
send_wanted_ref_info(&data);
send_shallow_info(&data, &want_obj);
- packet_write_fmt(1, "packfile\n");
+ packet_writer_write(&data.writer, "packfile\n");
create_pack_file(&have_obj, &want_obj);
state = FETCH_DONE;
break;
if (value) {
int allow_filter_value;
int allow_ref_in_want;
+ int allow_sideband_all_value;
strbuf_addstr(value, "shallow");
&allow_ref_in_want) &&
allow_ref_in_want)
strbuf_addstr(value, " ref-in-want");
+
+ if (git_env_bool("GIT_TEST_SIDEBAND_ALL", 0) ||
+ (!repo_config_get_bool(the_repository,
+ "uploadpack.allowsidebandall",
+ &allow_sideband_all_value) &&
+ allow_sideband_all_value))
+ strbuf_addstr(value, " sideband-all");
}
return 1;
strbuf_complete(buf, '/');
}
-void str_end_url_with_slash(const char *url, char **dest) {
+void str_end_url_with_slash(const char *url, char **dest)
+{
struct strbuf buf = STRBUF_INIT;
end_url_with_slash(&buf, url);
free(*dest);
return 0;
}
-struct userdiff_driver *userdiff_find_by_name(const char *name) {
+struct userdiff_driver *userdiff_find_by_name(const char *name)
+{
int len = strlen(name);
return userdiff_find_by_namelen(name, len);
}
return userdiff_find_by_name(check->items[0].value);
}
-struct userdiff_driver *userdiff_get_textconv(struct userdiff_driver *driver)
+struct userdiff_driver *userdiff_get_textconv(struct repository *r,
+ struct userdiff_driver *driver)
{
if (!driver->textconv)
return NULL;
struct strbuf name = STRBUF_INIT;
strbuf_addf(&name, "textconv/%s", driver->name);
- notes_cache_init(c, name.buf, driver->textconv);
+ notes_cache_init(r, c, name.buf, driver->textconv);
driver->textconv_cache = c;
strbuf_release(&name);
}
#include "notes-cache.h"
struct index_state;
+struct repository;
struct userdiff_funcname {
const char *pattern;
* Initialize any textconv-related fields in the driver and return it, or NULL
* if it does not have textconv enabled at all.
*/
-struct userdiff_driver *userdiff_get_textconv(struct userdiff_driver *driver);
+struct userdiff_driver *userdiff_get_textconv(struct repository *r,
+ struct userdiff_driver *driver);
#endif /* USERDIFF */
/* This code is originally from http://www.cl.cam.ac.uk/~mgk25/ucs/ */
+static const char utf16_be_bom[] = {'\xFE', '\xFF'};
+static const char utf16_le_bom[] = {'\xFF', '\xFE'};
+static const char utf32_be_bom[] = {'\0', '\0', '\xFE', '\xFF'};
+static const char utf32_le_bom[] = {'\xFF', '\xFE', '\0', '\0'};
+
struct interval {
ucs_char_t first;
ucs_char_t last;
#else
typedef char * iconv_ibp;
#endif
-char *reencode_string_iconv(const char *in, size_t insz, iconv_t conv, size_t *outsz_p)
+char *reencode_string_iconv(const char *in, size_t insz, iconv_t conv,
+ size_t bom_len, size_t *outsz_p)
{
size_t outsz, outalloc;
char *out, *outpos;
iconv_ibp cp;
outsz = insz;
- outalloc = st_add(outsz, 1); /* for terminating NUL */
+ outalloc = st_add(outsz, 1 + bom_len); /* for terminating NUL */
out = xmalloc(outalloc);
- outpos = out;
+ outpos = out + bom_len;
cp = (iconv_ibp)in;
while (1) {
{
iconv_t conv;
char *out;
+ const char *bom_str = NULL;
+ size_t bom_len = 0;
if (!in_encoding)
return NULL;
+ /* UTF-16LE-BOM is the same as UTF-16 for reading */
+ if (same_utf_encoding("UTF-16LE-BOM", in_encoding))
+ in_encoding = "UTF-16";
+
+ /*
+ * For writing, UTF-16 iconv typically creates "UTF-16BE-BOM"
+ * Some users under Windows want the little endian version
+ */
+ if (same_utf_encoding("UTF-16LE-BOM", out_encoding)) {
+ bom_str = utf16_le_bom;
+ bom_len = sizeof(utf16_le_bom);
+ out_encoding = "UTF-16LE";
+ } else if (same_utf_encoding("UTF-16BE-BOM", out_encoding)) {
+ bom_str = utf16_be_bom;
+ bom_len = sizeof(utf16_be_bom);
+ out_encoding = "UTF-16BE";
+ }
+
conv = iconv_open(out_encoding, in_encoding);
if (conv == (iconv_t) -1) {
in_encoding = fallback_encoding(in_encoding);
if (conv == (iconv_t) -1)
return NULL;
}
-
- out = reencode_string_iconv(in, insz, conv, outsz);
+ out = reencode_string_iconv(in, insz, conv, bom_len, outsz);
iconv_close(conv);
+ if (out && bom_str && bom_len)
+ memcpy(out, bom_str, bom_len);
return out;
}
#endif
return data && bom && (len >= bom_len) && !memcmp(data, bom, bom_len);
}
-static const char utf16_be_bom[] = {'\xFE', '\xFF'};
-static const char utf16_le_bom[] = {'\xFF', '\xFE'};
-static const char utf32_be_bom[] = {'\0', '\0', '\xFE', '\xFF'};
-static const char utf32_le_bom[] = {'\xFF', '\xFE', '\0', '\0'};
-
int has_prohibited_utf_bom(const char *enc, const char *data, size_t len)
{
return (
#ifndef NO_ICONV
char *reencode_string_iconv(const char *in, size_t insz,
- iconv_t conv, size_t *outsz);
+ iconv_t conv, size_t bom_len, size_t *outsz);
char *reencode_string_len(const char *in, size_t insz,
const char *out_encoding,
const char *in_encoding,
continue;
if (S_ISDIR(entry.mode)) {
struct tree *tree = lookup_tree(the_repository,
- entry.oid);
+ &entry.oid);
if (tree)
obj = &tree->object;
}
else {
struct blob *blob = lookup_blob(the_repository,
- entry.oid);
+ &entry.oid);
if (blob)
obj = &blob->object;
}
buf[len - 1] = 0;
return ret;
}
+
+int is_empty_or_missing_file(const char *filename)
+{
+ struct stat st;
+
+ if (stat(filename, &st) < 0) {
+ if (errno == ENOENT)
+ return 1;
+ die_errno(_("could not stat %s"), filename);
+ }
+
+ return !st.st_size;
+}
va_end(ap);
}
-void wt_status_prepare(struct wt_status *s)
+void wt_status_prepare(struct repository *r, struct wt_status *s)
{
memset(s, 0, sizeof(*s));
+ s->repo = r;
memcpy(s->color_palette, default_wt_status_colors,
sizeof(default_wt_status_colors));
s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES;
}
}
-static int unmerged_mask(const char *path)
+static int unmerged_mask(struct index_state *istate, const char *path)
{
int pos, mask;
const struct cache_entry *ce;
- pos = cache_name_pos(path, strlen(path));
+ pos = index_name_pos(istate, path, strlen(path));
if (0 <= pos)
return 0;
mask = 0;
pos = -pos-1;
- while (pos < active_nr) {
- ce = active_cache[pos++];
+ while (pos < istate->cache_nr) {
+ ce = istate->cache[pos++];
if (strcmp(ce->name, path) || !ce_stage(ce))
break;
mask |= (1 << (ce_stage(ce) - 1));
s->committable = 1;
break;
case DIFF_STATUS_UNMERGED:
- d->stagemask = unmerged_mask(p->two->path);
+ d->stagemask = unmerged_mask(s->repo->index,
+ p->two->path);
/*
* Don't bother setting {mode,oid}_{head,index} since the print
* code will output the stage values directly and not use the
{
struct rev_info rev;
- repo_init_revisions(the_repository, &rev, NULL);
+ repo_init_revisions(s->repo, &rev, NULL);
setup_revisions(0, NULL, &rev, NULL);
rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
rev.diffopt.flags.dirty_submodules = 1;
struct rev_info rev;
struct setup_revision_opt opt;
- repo_init_revisions(the_repository, &rev, NULL);
+ repo_init_revisions(s->repo, &rev, NULL);
memset(&opt, 0, sizeof(opt));
opt.def = s->is_initial ? empty_tree_oid_hex() : s->reference;
setup_revisions(0, NULL, &rev, &opt);
static void wt_status_collect_changes_initial(struct wt_status *s)
{
+ struct index_state *istate = s->repo->index;
int i;
- for (i = 0; i < active_nr; i++) {
+ for (i = 0; i < istate->cache_nr; i++) {
struct string_list_item *it;
struct wt_status_change_data *d;
- const struct cache_entry *ce = active_cache[i];
+ const struct cache_entry *ce = istate->cache[i];
- if (!ce_path_match(&the_index, ce, &s->pathspec, NULL))
+ if (!ce_path_match(istate, ce, &s->pathspec, NULL))
continue;
if (ce_intent_to_add(ce))
continue;
int i;
struct dir_struct dir;
uint64_t t_begin = getnanotime();
+ struct index_state *istate = s->repo->index;
if (!s->show_untracked_files)
return;
if (s->show_ignored_mode == SHOW_MATCHING_IGNORED)
dir.flags |= DIR_SHOW_IGNORED_TOO_MODE_MATCHING;
} else {
- dir.untracked = the_index.untracked;
+ dir.untracked = istate->untracked;
}
setup_standard_excludes(&dir);
- fill_directory(&dir, &the_index, &s->pathspec);
+ fill_directory(&dir, istate, &s->pathspec);
for (i = 0; i < dir.nr; i++) {
struct dir_entry *ent = dir.entries[i];
- if (cache_name_is_other(ent->name, ent->len) &&
- dir_path_match(&the_index, ent, &s->pathspec, 0, NULL))
+ if (index_name_is_other(istate, ent->name, ent->len) &&
+ dir_path_match(istate, ent, &s->pathspec, 0, NULL))
string_list_insert(&s->untracked, ent->name);
free(ent);
}
for (i = 0; i < dir.ignored_nr; i++) {
struct dir_entry *ent = dir.ignored[i];
- if (cache_name_is_other(ent->name, ent->len) &&
- dir_path_match(&the_index, ent, &s->pathspec, 0, NULL))
+ if (index_name_is_other(istate, ent->name, ent->len) &&
+ dir_path_match(istate, ent, &s->pathspec, 0, NULL))
string_list_insert(&s->ignored, ent->name);
free(ent);
}
wt_status_collect_changes_index(s);
wt_status_collect_untracked(s);
- wt_status_get_state(&s->state, s->branch && !strcmp(s->branch, "HEAD"));
+ wt_status_get_state(s->repo, &s->state, s->branch && !strcmp(s->branch, "HEAD"));
if (s->state.merge_in_progress && !has_unmerged(s))
s->committable = 1;
}
int dirty_submodules;
const char *c = color(WT_STATUS_HEADER, s);
- repo_init_revisions(the_repository, &rev, NULL);
+ repo_init_revisions(s->repo, &rev, NULL);
rev.diffopt.flags.allow_textconv = 1;
rev.diffopt.ita_invisible_in_index = 1;
_(" (use \"git rebase --abort\" to check out the original branch)"));
}
} else if (s->state.rebase_in_progress ||
- !stat(git_path_merge_msg(the_repository), &st)) {
+ !stat(git_path_merge_msg(s->repo), &st)) {
print_rebase_state(s, color);
if (s->hints)
status_printf_ln(s, color,
return 1;
}
-static void wt_status_get_detached_from(struct wt_status_state *state)
+static void wt_status_get_detached_from(struct repository *r,
+ struct wt_status_state *state)
{
struct grab_1st_switch_cbdata cb;
struct commit *commit;
/* sha1 is a commit? match without further lookup */
(oideq(&cb.noid, &oid) ||
/* perhaps sha1 is a tag, try to dereference to a commit */
- ((commit = lookup_commit_reference_gently(the_repository, &oid, 1)) != NULL &&
+ ((commit = lookup_commit_reference_gently(r, &oid, 1)) != NULL &&
oideq(&cb.noid, &commit->object.oid)))) {
const char *from = ref;
if (!skip_prefix(from, "refs/tags/", &from))
return 0;
}
-void wt_status_get_state(struct wt_status_state *state,
+void wt_status_get_state(struct repository *r,
+ struct wt_status_state *state,
int get_detached_from)
{
struct stat st;
struct object_id oid;
- if (!stat(git_path_merge_head(the_repository), &st)) {
+ if (!stat(git_path_merge_head(r), &st)) {
wt_status_check_rebase(NULL, state);
state->merge_in_progress = 1;
} else if (wt_status_check_rebase(NULL, state)) {
; /* all set */
- } else if (!stat(git_path_cherry_pick_head(the_repository), &st) &&
+ } else if (!stat(git_path_cherry_pick_head(r), &st) &&
!get_oid("CHERRY_PICK_HEAD", &oid)) {
state->cherry_pick_in_progress = 1;
oidcpy(&state->cherry_pick_head_oid, &oid);
}
wt_status_check_bisect(NULL, state);
- if (!stat(git_path_revert_head(the_repository), &st) &&
+ if (!stat(git_path_revert_head(r), &st) &&
!get_oid("REVERT_HEAD", &oid)) {
state->revert_in_progress = 1;
oidcpy(&state->revert_head_oid, &oid);
}
if (get_detached_from)
- wt_status_get_detached_from(state);
+ wt_status_get_detached_from(r, state);
}
static void wt_longstatus_print_state(struct wt_status *s)
struct wt_status *s)
{
struct wt_status_change_data *d = it->util;
+ struct index_state *istate = s->repo->index;
const struct cache_entry *ce;
struct strbuf buf_index = STRBUF_INIT;
const char *path_index = NULL;
*/
memset(stages, 0, sizeof(stages));
sum = 0;
- pos = cache_name_pos(it->string, strlen(it->string));
+ pos = index_name_pos(istate, it->string, strlen(it->string));
assert(pos < 0);
pos = -pos-1;
- while (pos < active_nr) {
- ce = active_cache[pos++];
+ while (pos < istate->cache_nr) {
+ ce = istate->cache[pos++];
stage = ce_stage(ce);
if (strcmp(ce->name, it->string) || !stage)
break;
/**
* Returns 1 if there are unstaged changes, 0 otherwise.
*/
-int has_unstaged_changes(int ignore_submodules)
+int has_unstaged_changes(struct repository *r, int ignore_submodules)
{
struct rev_info rev_info;
int result;
- repo_init_revisions(the_repository, &rev_info, NULL);
+ repo_init_revisions(r, &rev_info, NULL);
if (ignore_submodules) {
rev_info.diffopt.flags.ignore_submodules = 1;
rev_info.diffopt.flags.override_submodule_config = 1;
/**
* Returns 1 if there are uncommitted changes, 0 otherwise.
*/
-int has_uncommitted_changes(int ignore_submodules)
+int has_uncommitted_changes(struct repository *r,
+ int ignore_submodules)
{
struct rev_info rev_info;
int result;
- if (is_cache_unborn())
+ if (is_index_unborn(r->index))
return 0;
- repo_init_revisions(the_repository, &rev_info, NULL);
+ repo_init_revisions(r, &rev_info, NULL);
if (ignore_submodules)
rev_info.diffopt.flags.ignore_submodules = 1;
rev_info.diffopt.flags.quick = 1;
* We have no head (or it's corrupt); use the empty tree,
* which will complain if the index is non-empty.
*/
- struct tree *tree = lookup_tree(the_repository, the_hash_algo->empty_tree);
+ struct tree *tree = lookup_tree(r, the_hash_algo->empty_tree);
add_pending_object(&rev_info, &tree->object, "");
}
* If the work tree has unstaged or uncommitted changes, dies with the
* appropriate message.
*/
-int require_clean_work_tree(const char *action, const char *hint, int ignore_submodules, int gently)
+int require_clean_work_tree(struct repository *r,
+ const char *action,
+ const char *hint,
+ int ignore_submodules,
+ int gently)
{
struct lock_file lock_file = LOCK_INIT;
int err = 0, fd;
- fd = hold_locked_index(&lock_file, 0);
- refresh_cache(REFRESH_QUIET);
+ fd = repo_hold_locked_index(r, &lock_file, 0);
+ refresh_index(r->index, REFRESH_QUIET, NULL, NULL, NULL);
if (0 <= fd)
- update_index_if_able(&the_index, &lock_file);
+ repo_update_index_if_able(r, &lock_file);
rollback_lock_file(&lock_file);
- if (has_unstaged_changes(ignore_submodules)) {
+ if (has_unstaged_changes(r, ignore_submodules)) {
/* TRANSLATORS: the action is e.g. "pull with rebase" */
error(_("cannot %s: You have unstaged changes."), _(action));
err = 1;
}
- if (has_uncommitted_changes(ignore_submodules)) {
+ if (has_uncommitted_changes(r, ignore_submodules)) {
if (err)
error(_("additionally, your index contains uncommitted changes."));
else
#include "pathspec.h"
#include "remote.h"
+struct repository;
struct worktree;
enum color_wt_status {
};
struct wt_status {
+ struct repository *repo;
int is_initial;
char *branch;
const char *reference;
size_t wt_status_locate_end(const char *s, size_t len);
void wt_status_add_cut_line(FILE *fp);
-void wt_status_prepare(struct wt_status *s);
+void wt_status_prepare(struct repository *r, struct wt_status *s);
void wt_status_print(struct wt_status *s);
void wt_status_collect(struct wt_status *s);
void wt_status_collect_free_buffers(struct wt_status *s);
-void wt_status_get_state(struct wt_status_state *state, int get_detached_from);
+void wt_status_get_state(struct repository *repo,
+ struct wt_status_state *state,
+ int get_detached_from);
int wt_status_check_rebase(const struct worktree *wt,
struct wt_status_state *state);
int wt_status_check_bisect(const struct worktree *wt,
void status_printf(struct wt_status *s, const char *color, const char *fmt, ...);
/* The following functions expect that the caller took care of reading the index. */
-int has_unstaged_changes(int ignore_submodules);
-int has_uncommitted_changes(int ignore_submodules);
-int require_clean_work_tree(const char *action, const char *hint,
- int ignore_submodules, int gently);
+int has_unstaged_changes(struct repository *repo,
+ int ignore_submodules);
+int has_uncommitted_changes(struct repository *repo,
+ int ignore_submodules);
+int require_clean_work_tree(struct repository *repo,
+ const char *action,
+ const char *hint,
+ int ignore_submodules,
+ int gently);
#endif /* STATUS_H */