+/fuzz-commit-graph
/fuzz_corpora
/fuzz-pack-headers
/fuzz-pack-idx
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.
+
+
+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.
+
+
+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).
+
+ * 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).
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
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
-------------------------------------------
+
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.
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::
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
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:
------------
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
....
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]
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.
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
------------------
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
--------------------
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).
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
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>::
`<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.
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).
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)
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;
}
* 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);
}
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;
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);
}
/*
read_index(r->index);
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;
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"
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");
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);
}
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 */
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"));
* 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, &header,
+ 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);
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;
}
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");
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"));
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;
}
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);
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);
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;
}
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);
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);
}
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,
const struct object_id *oid,
const char *filename, const char *path)
{
- struct repository submodule;
+ struct repository subrepo;
+ const struct submodule *sub = submodule_from_path(superproject,
+ &null_oid, path);
+
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();
if (oid) {
init_tree_desc(&tree, data, size);
hit = grep_tree(opt, pathspec, &tree, &base, base.len,
- object->type == OBJ_COMMIT, &submodule);
+ object->type == OBJ_COMMIT, &subrepo);
strbuf_release(&base);
free(data);
} else {
- hit = grep_cache(opt, &submodule, pathspec, 1);
+ hit = grep_cache(opt, &subrepo, pathspec, 1);
}
- repo_clear(&submodule);
+ repo_clear(&subrepo);
return hit;
}
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);
check_attr, repo);
free(data);
} else if (recurse_submodules && S_ISGITLINK(entry.mode)) {
- hit |= grep_submodule(opt, repo, pathspec, entry.oid,
+ hit |= grep_submodule(opt, repo, pathspec, &entry.oid,
base->buf, base->buf + tn_len);
}
* 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);
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;
}
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
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);
}
*/
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);
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;
/* 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)
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++;
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);
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)
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);
unsigned n;
if (use_delta_islands)
- resolve_tree_islands(progress, &to_pack);
+ resolve_tree_islands(the_repository, progress, &to_pack);
get_object_details();
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"));
}
}
- 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);
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);
* 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");
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);
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)
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;
#define RESET_HEAD_DETACH (1<<0)
#define RESET_HEAD_HARD (1<<1)
+#define RESET_HEAD_RUN_POST_CHECKOUT_HOOK (1<<2)
static int reset_head(struct object_id *oid, const char *action,
const char *switch_to_branch, unsigned flags,
{
unsigned detach_head = flags & RESET_HEAD_DETACH;
unsigned reset_hard = flags & RESET_HEAD_HARD;
+ unsigned run_hook = flags & RESET_HEAD_RUN_POST_CHECKOUT_HOOK;
struct object_id head_oid;
struct tree_desc desc[2] = { { NULL }, { NULL } };
struct lock_file lock = LOCK_INIT;
}
tree = parse_tree_indirect(oid);
- prime_cache_tree(the_repository->index, tree);
+ 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"));
ret = update_ref(reflog_head, "HEAD", oid, NULL, 0,
UPDATE_REFS_MSG_ON_ERR);
}
+ 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);
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;
&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;
}
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--)
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;
die(_("error: 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 "
update_index_if_able(&the_index, &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;
}
}
- 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,
}
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;
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;
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)
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);
}
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)) {
#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"));
/* 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 argc, const char **argv)
+int create_bundle(struct repository *r, struct bundle_header *header,
+ const char *path, int argc, const char **argv)
{
struct lock_file lock = LOCK_INIT;
int bundle_fd = -1;
/* 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, struct bundle_header *header,
+ 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);
/* 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];
#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);
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)
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
#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;
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)
#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))
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
# 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;
--- /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'
}
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) {
}
static int ident_to_git(const char *path, const char *src, size_t len,
- struct strbuf *buf, int ident)
+ struct strbuf *buf, int ident)
{
char *dst, *dollar;
}
static int ident_to_worktree(const char *path, const char *src, size_t len,
- struct strbuf *buf, int ident)
+ struct strbuf *buf, int ident)
{
struct object_id oid;
char *to_free = NULL, *dollar, *spc;
}
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;
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;
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);
#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;
+}
#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
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 () {
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 () {
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
# 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'")"
+
+ test -n "$reschedule_failed_exec" &&
+ die "$(gettext "error: cannot combine '--preserve-merges' with '--reschedule-failed-exec'")"
fi
if test -n "$rebase_merges"
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?"
"$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 () {
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)
/* 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:
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)) {
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)
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;
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,
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;
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);
{
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,
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,
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) {
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"));
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(prev_path1));
+ if (prev_path2)
+ remove_file(o, 1, prev_path2,
+ o->call_depth || would_lose_untracked(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(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(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(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
}
else
remove_file_from_cache(b->path);
- } else if (handle_file(o, a, 2, ci) || handle_file(o, b, 3, ci))
- return -1;
+ } 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);
}
/*
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;
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;
/*
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;
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
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);
#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);
}
/* 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",
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
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
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
#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;
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)
#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; \
}
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
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) \
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;
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 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)
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;
}
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);
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);
/*
* 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 (setup_rerere(merge_rr, RERERE_READONLY))
+ if (setup_rerere(r, merge_rr, RERERE_READONLY))
return 0;
if (read_index(r->index) < 0)
return error(_("index file corrupt"));
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);
if (read_index(r->index) < 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"))
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 */
{
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;
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;
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;
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;
};
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_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 index_state *istate, struct replay_opts *opts)
{
- if (read_cache_unmerged())
+ if (read_index_unmerged(istate))
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))
+ read_index(r->index);
+ 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)
if (hold_locked_index(&index_lock, LOCK_REPORT_ON_ERROR) < 0)
return -1;
- read_cache();
+ read_index(r->index);
init_merge_options(&o);
o.ancestor = base ? base_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)
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(),
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->index, 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) {
+ if (read_index(r->index) < 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;
opts->signoff = 1;
}
+ if (file_exists(rebase_path_reschedule_failed_exec()))
+ opts->reschedule_failed_exec = 1;
+
read_strategy_opts(opts, &buf);
strbuf_release(&buf);
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 || read_index(r->index) < 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;
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 (read_index_unmerged(r->index)) {
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) ?
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 ||
+ read_index(r->index) < 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();
+ read_index(r->index);
init_merge_options(&o);
o.branch1 = "HEAD";
o.branch2 = ref_name.buf;
*/
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) {
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;
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->index, 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 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,
#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);
--- /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);
}
#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
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;
}
+ /\bcp\s+-a/ and err 'cp -a is not portable';
/\bsed\s+-i/ and err 'sed -i is not portable';
/\becho\s+-[neE]/ and err 'echo with option is not portable (use printf)';
/^\s*declare\s+/ and err 'arrays/declare not portable';
--- /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 },
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
--- /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
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
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
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
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
<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
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 &&
. ./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
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
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_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
'
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;
}
* 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);
+int traverse_trees(struct index_state *istate, int n, struct tree_desc *t, struct traverse_info *info);
enum follow_symlinks_result {
FOUND = 0, /* This includes out-of-tree links */
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
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 */
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 */
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;
}
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);
+ refresh_index(r->index, REFRESH_QUIET, NULL, NULL, NULL);
if (0 <= fd)
- update_index_if_able(&the_index, &lock_file);
+ update_index_if_able(r->index, &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 */