# Use tabs whenever we need to fill whitespace that spans at least from one tab
# stop to the next one.
+#
+# These settings are mirrored in .editorconfig. Keep them in sync.
UseTab: Always
TabWidth: 8
IndentWidth: 8
--- /dev/null
+[*]
+charset = utf-8
+insert_final_newline = true
+
+# The settings for C (*.c and *.h) files are mirrored in .clang-format. Keep
+# them in sync.
+[*.{c,h,sh,perl,pl,pm}]
+indent_style = tab
+tab_width = 8
+
+[*.py]
+indent_style = space
+indent_size = 4
+
+[COMMIT_EDITMSG]
+max_line_length = 72
/command-list.txt eol=lf
/GIT-VERSION-GEN eol=lf
/mergetools/* eol=lf
+/Documentation/git-merge.txt conflict-marker-size=32
+/Documentation/gitk.txt conflict-marker-size=32
+/Documentation/user-manual.txt conflict-marker-size=32
+/t/t????-*.sh conflict-marker-size=32
+/fuzz_corpora
+/fuzz-pack-headers
+/fuzz-pack-idx
/GIT-BUILD-OPTIONS
/GIT-CFLAGS
/GIT-LDFLAGS
/git-mergetool--lib
/git-mktag
/git-mktree
-/git-name-rev
+/git-multi-pack-index
/git-mv
+/git-name-rev
/git-notes
/git-p4
/git-pack-redundant
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>
-brian m. carlson <sandals@crustytoothpaste.net> Brian M. Carlson <sandals@crustytoothpaste.ath.cx>
+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>
Bryan Larsen <bryan@larsen.st> <bryanlarsen@yahoo.com>
do this
fi
+ - If a command sequence joined with && or || or | spans multiple
+ lines, put each command on a separate line and put && and || and |
+ operators at the end of each line, rather than the start. This
+ means you don't need to use \ to join lines, since the above
+ operators imply the sequence isn't finished.
+
+ (incorrect)
+ grep blob verify_pack_result \
+ | awk -f print_1.awk \
+ | sort >actual &&
+ ...
+
+ (correct)
+ grep blob verify_pack_result |
+ awk -f print_1.awk |
+ sort >actual &&
+ ...
+
- We prefer "test" over "[ ... ]".
- We do not write the noiseword "function" in front of shell
string_list for sorted string lists, a hash map (mapping struct
objects) named "struct decorate", amongst other things.
- - When you come up with an API, document it.
+ - When you come up with an API, document its functions and structures
+ in the header file that exposes the API to its callers. Use what is
+ in "strbuf.h" as a model for the appropriate tone and level of
+ detail.
- The first #include in C files, except in platform specific compat/
implementations, must be either "git-compat-util.h", "cache.h" or
mv $@+ $@
manpage-base-url.xsl: manpage-base-url.xsl.in
- sed "s|@@MAN_BASE_URL@@|$(MAN_BASE_URL)|" $< > $@
+ $(QUIET_GEN)sed "s|@@MAN_BASE_URL@@|$(MAN_BASE_URL)|" $< > $@
%.1 %.5 %.7 : %.xml manpage-base-url.xsl
$(QUIET_XMLTO)$(RM) $@ && \
--- /dev/null
+Git Release Notes
+=================
+
+Backward Compatibility Notes
+----------------------------
+
+ * "git branch -l <foo>" used to be a way to ask a reflog to be
+ created while creating a new branch, but that is no longer the
+ case. It is a short-hand for "git branch --list <foo>" now.
+
+ * "git push" into refs/tags/* hierarchy is rejected without getting
+ forced, but "git fetch" (misguidedly) used the "fast forwarding"
+ rule used for the refs/heads/* hierarchy; this has been corrected,
+ which means some fetches of tags that did not fail with older
+ version of Git will fail without "--force" with this version.
+
+ * "git help -a" now gives verbose output (same as "git help -av").
+ Those who want the old output may say "git help --no-verbose -a"..
+
+ * "git cpn --help", when "cpn" is an alias to, say, "cherry-pick -n",
+ reported only the alias expansion of "cpn" in earlier versions of
+ Git. It now runs "git cherry-pick --help" to show the manual page
+ of the command, while sending the alias expansion to the standard
+ error stream.
+
+
+Updates since v2.19
+-------------------
+
+UI, Workflows & Features
+
+ * Running "git clone" against a project that contain two files with
+ pathnames that differ only in cases on a case insensitive
+ filesystem would result in one of the files lost because the
+ underlying filesystem is incapable of holding both at the same
+ time. An attempt is made to detect such a case and warn.
+
+ * "git checkout -b newbranch [HEAD]" should not have to do as much as
+ checking out a commit different from HEAD. An attempt is made to
+ optimize this special case.
+
+ * "git rev-list --stdin </dev/null" used to be an error; it now shows
+ no output without an error. "git rev-list --stdin --default HEAD"
+ still falls back to the given default when nothing is given on the
+ standard input.
+
+ * Lift code from GitHub to restrict delta computation so that an
+ object that exists in one fork is not made into a delta against
+ another object that does not appear in the same forked repository.
+
+ * "git format-patch" learned new "--interdiff" and "--range-diff"
+ options to explain the difference between this version and the
+ previous attempt in the cover letter (or after the tree-dashes as
+ a comment).
+
+ * "git mailinfo" used in "git am" learned to make a best-effort
+ recovery of a patch corrupted by MUA that sends text/plain with
+ format=flawed option.
+ (merge 3aa4d81f88 rs/mailinfo-format-flowed later to maint).
+
+ * The rules used by "git push" and "git fetch" to determine if a ref
+ can or cannot be updated were inconsistent; specifically, fetching
+ to update existing tags were allowed even though tags are supposed
+ to be unmoving anchoring points. "git fetch" was taught to forbid
+ updates to existing tags without the "--force" option.
+
+ * "git multi-pack-index" learned to detect corruption in the .midx
+ file it uses, and this feature has been integrated into "git fsck".
+
+ * Generation of (experimental) commit-graph files have so far been
+ fairly silent, even though it takes noticeable amount of time in a
+ meaningfully large repository. The users will now see progress
+ output.
+
+ * The minimum version of Windows supported by Windows port fo Git is
+ now set to Vista.
+
+ * The completion script (in contrib/) learned to complete a handful of
+ options "git stash list" command takes.
+
+ * The completion script (in contrib/) learned that "git fetch
+ --multiple" only takes remote names as arguments and no refspecs.
+
+ * "git status" learns to show progress bar when refreshing the index
+ takes a long time.
+ (merge ae9af12287 nd/status-refresh-progress later to maint).
+
+ * "git help -a" and "git help -av" give different pieces of
+ information, and generally the "verbose" version is more friendly
+ to the new users. "git help -a" by default now uses the more
+ verbose output (with "--no-verbose", you can go back to the
+ original). Also "git help -av" now lists aliases and external
+ commands, which it did not used to.
+
+ * Unlike "grep", "git grep" by default recurses to the whole tree.
+ The command learned "git grep --recursive" option, so that "git
+ grep --no-recursive" can serve as a synonym to setting the
+ max-depth to 0.
+
+ * When pushing into a repository that borrows its objects from an
+ alternate object store, "git receive-pack" that responds to the
+ push request on the other side lists the tips of refs in the
+ alternate to reduce the amount of objects transferred. This
+ sometimes is detrimental when the number of refs in the alternate
+ is absurdly large, in which case the bandwidth saved in potentially
+ fewer objects transferred is wasted in excessively large ref
+ advertisement. The alternate refs that are advertised are now
+ configurable with a pair of configuration variables.
+
+ * "git cmd --help" when "cmd" is aliased used to only say "cmd is
+ aliased to ...". Now it shows that to the standard error stream
+ and runs "git $cmd --help" where $cmd is the first word of the
+ alias expansion.
+
+ * The documentation of "git gc" has been updated to mention that it
+ is no longer limited to "pruning away crufts" but also updates
+ ancillary files like commit-graph as a part of repository
+ optimization.
+
+ * "git p4 unshelve" improvements.
+
+ * The logic to select the default user name and e-mail on Windows has
+ been improved.
+ (merge 501afcb8b0 js/mingw-default-ident later to maint).
+
+
+Performance, Internal Implementation, Development Support etc.
+
+ * When there are too many packfiles in a repository (which is not
+ recommended), looking up an object in these would require
+ consulting many pack .idx files; a new mechanism to have a single
+ file that consolidates all of these .idx files is introduced.
+
+ * "git submodule update" is getting rewritten piece-by-piece into C.
+
+ * The code for computing history reachability has been shuffled,
+ obtained a bunch of new tests to cover them, and then being
+ improved.
+
+ * The unpack_trees() API used in checking out a branch and merging
+ walks one or more trees along with the index. When the cache-tree
+ in the index tells us that we are walking a tree whose flattened
+ contents is known (i.e. matches a span in the index), as linearly
+ scanning a span in the index is much more efficient than having to
+ open tree objects recursively and listing their entries, the walk
+ can be optimized, which has been done.
+
+ * When creating a thin pack, which allows objects to be made into a
+ delta against another object that is not in the resulting pack but
+ is known to be present on the receiving end, the code learned to
+ take advantage of the reachability bitmap; this allows the server
+ to send a delta against a base beyond the "boundary" commit.
+
+ * spatch transformation to replace boolean uses of !hashcmp() to
+ newly introduced oideq() is added, and applied, to regain
+ performance lost due to support of multiple hash algorithms.
+
+ * Fix a bug in which the same path could be registered under multiple
+ worktree entries if the path was missing (for instance, was removed
+ manually). Also, as a convenience, expand the number of cases in
+ which --force is applicable.
+
+ * Split Documentation/config.txt for easier maintenance.
+ (merge 6014363f0b nd/config-split later to maint).
+
+ * Test helper binaries clean-up.
+ (merge c9a1f4161f nd/test-tool later to maint).
+
+ * Various tests have been updated to make it easier to swap the
+ hash function used for object identification.
+ (merge ae0c89d41b bc/hash-independent-tests later to maint).
+
+ * Update fsck.skipList implementation and documentation.
+ (merge 371a655074 ab/fsck-skiplist later to maint).
+
+ * An alias that expands to another alias has so far been forbidden,
+ but now it is allowed to create such an alias.
+
+ * Various test scripts have been updated for style and also correct
+ handling of exit status of various commands.
+
+ * "gc --auto" ended up calling exit(-1) upon error, which has been
+ corrected to use exit(1). Also the error reporting behaviour when
+ daemonized has been updated to exit with zero status when stopping
+ due to a previously discovered error (which implies there is no
+ point running gc to improve the situation); we used to exit with
+ failure in such a case.
+
+ * Various codepaths in the core-ish part learned to work on an
+ arbitrary in-core index structure, not necessarily the default
+ instance "the_index".
+ (merge b3c7eef9b0 nd/the-index later to maint).
+
+ * Code clean-up in the internal machinery used by "git status" and
+ "git commit --dry-run".
+ (merge 73ba5d78b4 ss/wt-status-committable later to maint).
+
+ * Some environment variables that control the runtime options of Git
+ used during tests are getting renamed for consistency.
+ (merge 4231d1ba99 bp/rename-test-env-var later to maint).
+
+ * A new extension to the index file has been introduced, which allows
+ the index file to be read in parallel for performance.
+
+ * The oidset API was built on top of the oidmap API which in turn is
+ on the hashmap API. Replace the implementation to build on top of
+ the khash API and gain performance.
+
+ * Over some transports, fetching objects with an exact commit object
+ name can be done without first seeing the ref advertisements. The
+ code has been optimized to exploit this.
+
+ * In a partial clone that will lazily be hydrated from the
+ originating repository, we generally want to avoid "does this
+ object exist (locally)?" on objects that we deliberately omitted
+ when we created the clone. The cache-tree codepath (which is used
+ to write a tree object out of the index) however insisted that the
+ object exists, even for paths that are outside of the partial
+ checkout area. The code has been updated to avoid such a check.
+
+ * To help developers, an EditorConfig file that attempts to follow
+ the project convention has been added.
+ (merge b548d698a0 bc/editorconfig later to maint).
+
+ * The result of coverage test can be combined with "git blame" to
+ check the test coverage of code introduced recently with a new
+ 'coverage-diff' tool (in contrib/).
+ (merge 783faedd65 ds/coverage-diff later to maint).
+
+ * An experiment to fuzz test a few areas, hopefully we can gain more
+ coverage to various areas.
+
+
+Fixes since v2.19
+-----------------
+
+ * "git interpret-trailers" and its underlying machinery had a buggy
+ code that attempted to ignore patch text after commit log message,
+ which triggered in various codepaths that will always get the log
+ message alone and never get such an input.
+ (merge 66e83d9b41 jk/trailer-fixes later to maint).
+
+ * Malformed or crafted data in packstream can make our code attempt
+ to read or write past the allocated buffer and abort, instead of
+ reporting an error, which has been fixed.
+
+ * "git rebase -i" did not clear the state files correctly when a run
+ of "squash/fixup" is aborted and then the user manually amended the
+ commit instead, which has been corrected.
+ (merge 10d2f35436 js/rebase-i-autosquash-fix later to maint).
+
+ * When fsmonitor is in use, after operation on submodules updates
+ .gitmodules, we lost track of the fact that we did so and relied on
+ stale fsmonitor data.
+ (merge 43f1180814 bp/mv-submodules-with-fsmonitor later to maint).
+
+ * Fix for a long-standing bug that leaves the index file corrupt when
+ it shrinks during a partial commit.
+ (merge 6c003d6ffb jk/reopen-tempfile-truncate later to maint).
+
+ * Further fix for O_APPEND emulation on Windows
+ (merge eeaf7ddac7 js/mingw-o-append later to maint).
+
+ * A corner case bugfix in "git rerere" code.
+ (merge ad2bf0d9b4 en/rerere-multi-stage-1-fix later to maint).
+
+ * "git add ':(attr:foo)'" is not supported and is supposed to be
+ rejected while the command line arguments are parsed, but we fail
+ to reject such a command line upfront.
+ (merge 84d938b732 nd/attr-pathspec-fix later to maint).
+
+ * Recent update broke the reachability algorithm when refs (e.g.
+ tags) that point at objects that are not commit were involved,
+ which has been fixed.
+
+ * "git rebase" etc. in Git 2.19 fails to abort when given an empty
+ commit log message as result of editing, which has been corrected.
+ (merge a3ec9eaf38 en/sequencer-empty-edit-result-aborts later to maint).
+
+ * The code to backfill objects in lazily cloned repository did not
+ work correctly, which has been corrected.
+ (merge e68302011c jt/lazy-object-fetch-fix later to maint).
+
+ * Update error messages given by "git remote" and make them consistent.
+ (merge 5025425dff ms/remote-error-message-update later to maint).
+
+ * "git update-ref" learned to make both "--no-deref" and "--stdin"
+ work at the same time.
+ (merge d345e9fbe7 en/update-ref-no-deref-stdin later to maint).
+
+ * Recently added "range-diff" had a corner-case bug to cause it
+ segfault, which has been corrected.
+ (merge e467a90c7a tg/range-diff-corner-case-fix later to maint).
+
+ * The recently introduced commit-graph auxiliary data is incompatible
+ with mechanisms such as replace & grafts that "breaks" immutable
+ nature of the object reference relationship. Disable optimizations
+ based on its use (and updating existing commit-graph) when these
+ incompatible features are in use in the repository.
+ (merge 829a321569 ds/commit-graph-with-grafts later to maint).
+
+ * The mailmap file update.
+ (merge 255eb03edf jn/mailmap-update later to maint).
+
+ * The code in "git status" sometimes hit an assertion failure. This
+ was caused by a structure that was reused without cleaning the data
+ used for the first run, which has been corrected.
+ (merge 3e73cc62c0 en/status-multiple-renames-to-the-same-target-fix later to maint).
+
+ * "git fetch $repo $object" in a partial clone did not correctly
+ fetch the asked-for object that is referenced by an object in
+ promisor packfile, which has been fixed.
+
+ * A corner-case bugfix.
+ (merge c5cbb27cb5 sm/show-superproject-while-conflicted later to maint).
+
+ * Various fixes to "diff --color-moved-ws".
+
+ * A partial clone that is configured to lazily fetch missing objects
+ will on-demand issue a "git fetch" request to the originating
+ repository to fill not-yet-obtained objects. The request has been
+ optimized for requesting a tree object (and not the leaf blob
+ objects contained in it) by telling the originating repository that
+ no blobs are needed.
+ (merge 4c7f9567ea jt/non-blob-lazy-fetch later to maint).
+
+ * The codepath to support the experimental split-index mode had
+ remaining "racily clean" issues fixed.
+ (merge 4c490f3d32 sg/split-index-racefix later to maint).
+
+ * "git log --graph" showing an octopus merge sometimes miscounted the
+ number of display columns it is consuming to show the merge and its
+ parent commits, which has been corrected.
+ (merge 04005834ed np/log-graph-octopus-fix later to maint).
+
+ * Code cleanup, docfix, build fix, etc.
+ (merge 96a7501aad ts/doc-build-manpage-xsl-quietly later to maint).
+ (merge b9b07efdb2 tg/conflict-marker-size later to maint).
+ (merge fa0aeea770 sg/doc-trace-appends later to maint).
+ (merge d64324cb60 tb/void-check-attr later to maint).
+ (merge c3b9bc94b9 en/double-semicolon-fix later to maint).
+ (merge 79336116f5 sg/t3701-tighten-trace later to maint).
+ (merge 801fa63a90 jk/dev-build-format-security later to maint).
+ (merge 0597dd62ba sb/string-list-remove-unused later to maint).
+ (merge db2d36fad8 bw/protocol-v2 later to maint).
+ (merge 456d7cd3a9 sg/split-index-test later to maint).
+ (merge 7b6057c852 tq/refs-internal-comment-fix later to maint).
+ (merge 29e8dc50ad tg/t5551-with-curl-7.61.1 later to maint).
+ (merge 55f6bce2c9 fe/doc-updates later to maint).
+ (merge 7987d2232d jk/check-everything-connected-is-long-gone later to maint).
+ (merge 4ba3c9be47 dz/credential-doc-url-matching-rules later to maint).
+ (merge 4c399442f7 ma/commit-graph-docs later to maint).
+ (merge fc0503b04e ma/t1400-undebug-test later to maint).
+ (merge e56b53553a nd/packobjectshook-doc-fix later to maint).
+ (merge c56170a0c4 ma/mailing-list-address-in-git-help later to maint).
+ (merge 6e8fc70fce rs/sequencer-oidset-insert-avoids-dups later to maint).
+ (merge ad0b8f9575 mw/doc-typofixes later to maint).
+ (merge d9f079ad1a jc/how-to-document-api later to maint).
+ (merge b1492bf315 ma/t7005-bash-workaround later to maint).
+ (merge ac1f98a0df du/rev-parse-is-plumbing later to maint).
+ (merge ca8ed443a5 mm/doc-no-dashed-git later to maint).
+ (merge ce366a8144 du/get-tar-commit-id-is-plumbing later to maint).
+ (merge 61018fe9e0 du/cherry-is-plumbing later to maint).
Do not forget to update the documentation to describe the updated
behavior and make sure that the resulting documentation set formats
-well. It is currently a liberal mixture of US and UK English norms for
+well (try the Documentation/doc-diff script).
+
+We currently have a liberal mixture of US and UK English norms for
spelling and grammar, which is somewhat unfortunate. A huge patch that
touches the files all over the place only to correct the inconsistency
is not welcome, though. Potential clashes with other changes that can
false;; Boolean false literals are `no`, `off`, `false`,
`0` and the empty string.
+
-When converting value to the canonical form using `--bool` type
+When converting a value to its canonical form using the `--type=bool` type
specifier, 'git config' will ensure that the output is "true" or
"false" (spelled in lowercase).
This is sometimes needed to work with old scripts that
expect HEAD to be a symbolic link.
+core.alternateRefsCommand::
+ When advertising tips of available history from an alternate, use the shell to
+ execute the specified command instead of linkgit:git-for-each-ref[1]. The
+ first argument is the absolute path of the alternate. Output must contain one
+ hex object id per line (i.e., the same as produced by `git for-each-ref
+ --format='%(objectname)'`).
++
+Note that you cannot generally put `git for-each-ref` directly into the config
+value, as it does not take a repository path as an argument (but you can wrap
+the command above in a shell script).
+
+core.alternateRefsPrefixes::
+ When listing references from an alternate, list only references that begin
+ with the given prefix. Prefixes match as if they were given as arguments to
+ linkgit:git-for-each-ref[1]. To list multiple prefixes, separate them with
+ whitespace. If `core.alternateRefsCommand` is set, setting
+ `core.alternateRefsPrefixes` has no effect.
+
core.bare::
If true this repository is assumed to be 'bare' and has no
working directory associated with it. If this is the case a
all; -1 means to try indefinitely. Default is 1000 (i.e.,
retry for 1 second).
-sequence.editor::
- Text editor used by `git rebase -i` for editing the rebase instruction file.
- The value is meant to be interpreted by the shell when it is used.
- It can be overridden by the `GIT_SEQUENCE_EDITOR` environment variable.
- When not configured the default commit message editor is used instead.
-
core.pager::
Text viewer for use by Git commands (e.g., 'less'). The value
is meant to be interpreted by the shell. The order of preference
option was given on the command line. See linkgit:git[1] and
linkgit:git-replace[1] for more information.
+core.multiPackIndex::
+ Use the multi-pack-index file to track multiple packfiles using a
+ single index. See link:technical/multi-pack-index.html[the
+ multi-pack-index design document].
+
core.sparseCheckout::
Enable "sparse checkout" feature. See section "Sparse checkout" in
linkgit:git-read-tree[1] for more information.
remote branch. This setting might be used for other checkout-like
commands or functionality in the future.
+checkout.optimizeNewBranch::
+ Optimizes the performance of "git checkout -b <new_branch>" when
+ using sparse-checkout. When set to true, git will not update the
+ repo based on the current sparse-checkout settings. This means it
+ will not update the skip-worktree bit in the index nor add/remove
+ files in the working directory to reflect the current sparse checkout
+ settings nor will it show the local changes.
+
clean.requireForce::
A boolean to make git-clean do nothing unless given -f,
-i or -n. Defaults to true.
operation complete faster, especially on slow filesystems. If
not set, the value of `transfer.unpackLimit` is used instead.
-fetch.recurseSubmodules::
- This option can be either set to a boolean value or to 'on-demand'.
- Setting it to a boolean changes the behavior of fetch and pull to
- unconditionally recurse into submodules when set to true or to not
- recurse at all when set to false. When set to 'on-demand' (the default
- value), fetch and pull will only recurse into a populated submodule
- when its superproject retrieves a commit that updates the submodule's
- reference.
-
-fetch.fsckObjects::
- If it is set to true, git-fetch-pack will check all fetched
- objects. See `transfer.fsckObjects` for what's
- checked. Defaults to false. If not set, the value of
- `transfer.fsckObjects` is used instead.
-
-fetch.fsck.<msg-id>::
- Acts like `fsck.<msg-id>`, but is used by
- linkgit:git-fetch-pack[1] instead of linkgit:git-fsck[1]. See
- the `fsck.<msg-id>` documentation for details.
-
-fetch.fsck.skipList::
- Acts like `fsck.skipList`, but is used by
- linkgit:git-fetch-pack[1] instead of linkgit:git-fsck[1]. See
- the `fsck.skipList` documentation for details.
-
-fetch.unpackLimit::
- If the number of objects fetched over the Git native
- transfer is below this
- limit, then the objects will be unpacked into loose object
- files. However if the number of received objects equals or
- exceeds this limit then the received pack will be stored as
- a pack, after adding any missing delta bases. Storing the
- pack from a push can make the push operation complete faster,
- especially on slow filesystems. If not set, the value of
- `transfer.unpackLimit` is used instead.
-
-fetch.prune::
- If true, fetch will automatically behave as if the `--prune`
- option was given on the command line. See also `remote.<name>.prune`
- and the PRUNING section of linkgit:git-fetch[1].
-
-fetch.pruneTags::
- If true, fetch will automatically behave as if the
- `refs/tags/*:refs/tags/*` refspec was provided when pruning,
- if not set already. This allows for setting both this option
- and `fetch.prune` to maintain a 1=1 mapping to upstream
- refs. See also `remote.<name>.pruneTags` and the PRUNING
- section of linkgit:git-fetch[1].
-
-fetch.output::
- Control how ref update status is printed. Valid values are
- `full` and `compact`. Default value is `full`. See section
- OUTPUT in linkgit:git-fetch[1] for detail.
-
-fetch.negotiationAlgorithm::
- Control how information about the commits in the local repository is
- sent when negotiating the contents of the packfile to be sent by the
- server. Set to "skipping" to use an algorithm that skips commits in an
- effort to converge faster, but may result in a larger-than-necessary
- packfile; The default is "default" which instructs Git to use the default algorithm
- that never skips commits (unless the server has acknowledged it or one
- of its descendants).
- Unknown values will cause 'git fetch' to error out.
-+
-See also the `--negotiation-tip` option for linkgit:git-fetch[1].
-
-format.attach::
- Enable multipart/mixed attachments as the default for
- 'format-patch'. The value can also be a double quoted string
- which will enable attachments as the default and set the
- value as the boundary. See the --attach option in
- linkgit:git-format-patch[1].
-
-format.from::
- Provides the default value for the `--from` option to format-patch.
- Accepts a boolean value, or a name and email address. If false,
- format-patch defaults to `--no-from`, using commit authors directly in
- the "From:" field of patch mails. If true, format-patch defaults to
- `--from`, using your committer identity in the "From:" field of patch
- mails and including a "From:" field in the body of the patch mail if
- different. If set to a non-boolean value, format-patch uses that
- value instead of your committer identity. Defaults to false.
-
-format.numbered::
- A boolean which can enable or disable sequence numbers in patch
- subjects. It defaults to "auto" which enables it only if there
- is more than one patch. It can be enabled or disabled for all
- messages by setting it to "true" or "false". See --numbered
- option in linkgit:git-format-patch[1].
-
-format.headers::
- Additional email headers to include in a patch to be submitted
- by mail. See linkgit:git-format-patch[1].
-
-format.to::
-format.cc::
- Additional recipients to include in a patch to be submitted
- by mail. See the --to and --cc options in
- linkgit:git-format-patch[1].
-
-format.subjectPrefix::
- The default for format-patch is to output files with the '[PATCH]'
- subject prefix. Use this variable to change that prefix.
-
-format.signature::
- The default for format-patch is to output a signature containing
- the Git version number. Use this variable to change that default.
- Set this variable to the empty string ("") to suppress
- signature generation.
-
-format.signatureFile::
- Works just like format.signature except the contents of the
- file specified by this variable will be used as the signature.
-
-format.suffix::
- The default for format-patch is to output files with the suffix
- `.patch`. Use this variable to change that suffix (make sure to
- include the dot if you want it).
-
-format.pretty::
- The default pretty format for log/show/whatchanged command,
- See linkgit:git-log[1], linkgit:git-show[1],
- linkgit:git-whatchanged[1].
-
-format.thread::
- The default threading style for 'git format-patch'. Can be
- a boolean value, or `shallow` or `deep`. `shallow` threading
- makes every mail a reply to the head of the series,
- where the head is chosen from the cover letter, the
- `--in-reply-to`, and the first patch mail, in this order.
- `deep` threading makes every mail a reply to the previous one.
- A true boolean value is the same as `shallow`, and a false
- value disables threading.
-
-format.signOff::
- A boolean value which lets you enable the `-s/--signoff` option of
- format-patch by default. *Note:* Adding the Signed-off-by: line to a
- patch should be a conscious act and means that you certify you have
- the rights to submit this work under the same open source license.
- Please see the 'SubmittingPatches' document for further discussion.
-
-format.coverLetter::
- A boolean that controls whether to generate a cover-letter when
- format-patch is invoked, but in addition can be set to "auto", to
- generate a cover-letter only when there's more than one patch.
-
-format.outputDirectory::
- Set a custom directory to store the resulting files instead of the
- current working directory.
-
-format.useAutoBase::
- A boolean value which lets you enable the `--base=auto` option of
- format-patch by default.
+include::fetch-config.txt[]
+
+include::format-config.txt[]
filter.<driver>.clean::
The command which is used to convert the content of a worktree
will only cause git to warn.
fsck.skipList::
- The path to a sorted list of object names (i.e. one SHA-1 per
+ The path to a list of object names (i.e. one unabbreviated SHA-1 per
line) that are known to be broken in a non-fatal way and should
- be ignored. This feature is useful when an established project
- should be accepted despite early commits containing errors that
- can be safely ignored such as invalid committer email addresses.
- Note: corrupt objects cannot be skipped with this setting.
+ be ignored. On versions of Git 2.20 and later comments ('#'), empty
+ lines, and any leading and trailing whitespace is ignored. Everything
+ but a SHA-1 per line will error out on older versions.
++
+This feature is useful when an established project should be accepted
+despite early commits containing errors that can be safely ignored
+such as invalid committer email addresses. Note: corrupt objects
+cannot be skipped with this setting.
+
Like `fsck.<msg-id>` this variable has corresponding
`receive.fsck.skipList` and `fetch.fsck.skipList` variants.
fall back on the `fsck.skipList` configuration if they aren't set. To
uniformly configure the same fsck settings in different circumstances
all three of them they must all set to the same values.
++
+Older versions of Git (before 2.20) documented that the object names
+list should be sorted. This was never a requirement, the object names
+could appear in any order, but when reading the list we tracked whether
+the list was sorted for the purposes of an internal binary search
+implementation, which could save itself some work with an already sorted
+list. Unless you had a humongous list there was no reason to go out of
+your way to pre-sort the list. After Git version 2.20 a hash implementation
+is used instead, so there's now no reason to pre-sort the list.
gc.aggressiveDepth::
The depth parameter used in the delta compression
for details.
gc.logExpiry::
- If the file gc.log exists, then `git gc --auto` won't run
+ If the file gc.log exists, then `git gc --auto` will print
+ its content and exit with status zero instead of running
unless that file is more than 'gc.logExpiry' old. Default is
"1.day". See `gc.pruneExpire` for more ways to specify its
value.
You can also use more human-readable "1.month.ago", etc.
The default is 15 days. See linkgit:git-rerere[1].
-gitcvs.commitMsgAnnotation::
- Append this string to each commit message. Set to empty string
- to disable this feature. Defaults to "via git-CVS emulator".
-
-gitcvs.enabled::
- Whether the CVS server interface is enabled for this repository.
- See linkgit:git-cvsserver[1].
-
-gitcvs.logFile::
- Path to a log file where the CVS server interface well... logs
- various stuff. See linkgit:git-cvsserver[1].
-
-gitcvs.usecrlfattr::
- If true, the server will look up the end-of-line conversion
- attributes for files to determine the `-k` modes to use. If
- the attributes force Git to treat a file as text,
- the `-k` mode will be left blank so CVS clients will
- treat it as text. If they suppress text conversion, the file
- will be set with '-kb' mode, which suppresses any newline munging
- the client might otherwise do. If the attributes do not allow
- the file type to be determined, then `gitcvs.allBinary` is
- used. See linkgit:gitattributes[5].
-
-gitcvs.allBinary::
- This is used if `gitcvs.usecrlfattr` does not resolve
- the correct '-kb' mode to use. If true, all
- unresolved files are sent to the client in
- mode '-kb'. This causes the client to treat them
- as binary files, which suppresses any newline munging it
- otherwise might do. Alternatively, if it is set to "guess",
- then the contents of the file are examined to decide if
- it is binary, similar to `core.autocrlf`.
-
-gitcvs.dbName::
- Database used by git-cvsserver to cache revision information
- derived from the Git repository. The exact meaning depends on the
- used database driver, for SQLite (which is the default driver) this
- is a filename. Supports variable substitution (see
- linkgit:git-cvsserver[1] for details). May not contain semicolons (`;`).
- Default: '%Ggitcvs.%m.sqlite'
-
-gitcvs.dbDriver::
- Used Perl DBI driver. You can specify any available driver
- for this here, but it might not work. git-cvsserver is tested
- with 'DBD::SQLite', reported to work with 'DBD::Pg', and
- reported *not* to work with 'DBD::mysql'. Experimental feature.
- May not contain double colons (`:`). Default: 'SQLite'.
- See linkgit:git-cvsserver[1].
-
-gitcvs.dbUser, gitcvs.dbPass::
- Database user and password. Only useful if setting `gitcvs.dbDriver`,
- since SQLite has no concept of database users and/or passwords.
- 'gitcvs.dbUser' supports variable substitution (see
- linkgit:git-cvsserver[1] for details).
-
-gitcvs.dbTableNamePrefix::
- Database table name prefix. Prepended to the names of any
- database tables used, allowing a single database to be used
- for several repositories. Supports variable substitution (see
- linkgit:git-cvsserver[1] for details). Any non-alphabetic
- characters will be replaced with underscores.
-
-All gitcvs variables except for `gitcvs.usecrlfattr` and
-`gitcvs.allBinary` can also be specified as
-'gitcvs.<access_method>.<varname>' (where 'access_method'
-is one of "ext" and "pserver") to make them apply only for the given
-access method.
+include::gitcvs-config.txt[]
gitweb.category::
gitweb.description::
be used as a legacy synonym for `gpg.openpgp.program`. The default
value for `gpg.x509.program` is "gpgsm".
-gui.commitMsgWidth::
- Defines how wide the commit message window is in the
- linkgit:git-gui[1]. "75" is the default.
-
-gui.diffContext::
- Specifies how many context lines should be used in calls to diff
- made by the linkgit:git-gui[1]. The default is "5".
-
-gui.displayUntracked::
- Determines if linkgit:git-gui[1] shows untracked files
- in the file list. The default is "true".
-
-gui.encoding::
- Specifies the default encoding to use for displaying of
- file contents in linkgit:git-gui[1] and linkgit:gitk[1].
- It can be overridden by setting the 'encoding' attribute
- for relevant files (see linkgit:gitattributes[5]).
- If this option is not set, the tools default to the
- locale encoding.
-
-gui.matchTrackingBranch::
- Determines if new branches created with linkgit:git-gui[1] should
- default to tracking remote branches with matching names or
- not. Default: "false".
-
-gui.newBranchTemplate::
- Is used as suggested name when creating new branches using the
- linkgit:git-gui[1].
-
-gui.pruneDuringFetch::
- "true" if linkgit:git-gui[1] should prune remote-tracking branches when
- performing a fetch. The default value is "false".
-
-gui.trustmtime::
- Determines if linkgit:git-gui[1] should trust the file modification
- timestamp or not. By default the timestamps are not trusted.
-
-gui.spellingDictionary::
- Specifies the dictionary used for spell checking commit messages in
- the linkgit:git-gui[1]. When set to "none" spell checking is turned
- off.
-
-gui.fastCopyBlame::
- If true, 'git gui blame' uses `-C` instead of `-C -C` for original
- location detection. It makes blame significantly faster on huge
- repositories at the expense of less thorough copy detection.
-
-gui.copyBlameThreshold::
- Specifies the threshold to use in 'git gui blame' original location
- detection, measured in alphanumeric characters. See the
- linkgit:git-blame[1] manual for more information on copy detection.
-
-gui.blamehistoryctx::
- Specifies the radius of history context in days to show in
- linkgit:gitk[1] for the selected commit, when the `Show History
- Context` menu item is invoked from 'git gui blame'. If this
- variable is set to zero, the whole history is shown.
+include::gui-config.txt[]
guitool.<name>.cmd::
Specifies the shell command line to execute when the corresponding item
The configuration variables in the 'imap' section are described
in linkgit:git-imap-send[1].
+index.threads::
+ Specifies the number of threads to spawn when loading the index.
+ This is meant to reduce index load time on multiprocessor machines.
+ Specifying 0 or 'true' will cause Git to auto-detect the number of
+ CPU's and set the number of threads accordingly. Specifying 1 or
+ 'false' will disable multithreading. Defaults to 'true'.
+
index.version::
Specify the version with which new index files should be
initialized. This does not affect existing repositories.
all existing objects. You can force recompression by passing the -F option
to linkgit:git-repack[1].
+pack.island::
+ An extended regular expression configuring a set of delta
+ islands. See "DELTA ISLANDS" in linkgit:git-pack-objects[1]
+ for details.
+
+pack.islandCore::
+ Specify an island name which gets to have its objects be
+ packed first. This creates a kind of pseudo-pack at the front
+ of one pack, so that the objects from the specified island are
+ hopefully faster to copy into any pack that should be served
+ to a user requesting these objects. In practice this means
+ that the island specified should likely correspond to what is
+ the most commonly cloned in the repo. See also "DELTA ISLANDS"
+ in linkgit:git-pack-objects[1].
+
pack.deltaCacheSize::
The maximum memory in bytes used for caching deltas in
linkgit:git-pack-objects[1] before writing them out to a pack.
* `1` - the original wire protocol with the addition of a version string
in the initial response from the server.
---
-
-pull.ff::
- By default, Git does not create an extra merge commit when merging
- a commit that is a descendant of the current commit. Instead, the
- tip of the current branch is fast-forwarded. When set to `false`,
- this variable tells Git to create an extra merge commit in such
- a case (equivalent to giving the `--no-ff` option from the command
- line). When set to `only`, only such fast-forward merges are
- allowed (equivalent to giving the `--ff-only` option from the
- command line). This setting overrides `merge.ff` when pulling.
-
-pull.rebase::
- When true, rebase branches on top of the fetched branch, instead
- of merging the default branch from the default remote when "git
- pull" is run. See "branch.<name>.rebase" for setting this on a
- per-branch basis.
-+
-When `merges`, pass the `--rebase-merges` option to 'git rebase'
-so that the local merge commits are included in the rebase (see
-linkgit:git-rebase[1] for details).
-+
-When preserve, also pass `--preserve-merges` along to 'git rebase'
-so that locally committed merge commits will not be flattened
-by running 'git pull'.
-+
-When the value is `interactive`, the rebase is run in interactive mode.
-+
-*NOTE*: this is a possibly dangerous operation; do *not* use
-it unless you understand the implications (see linkgit:git-rebase[1]
-for details).
-
-pull.octopus::
- The default merge strategy to use when pulling multiple branches
- at once.
-
-pull.twohead::
- The default merge strategy to use when pulling a single branch.
-
-push.default::
- Defines the action `git push` should take if no refspec is
- explicitly given. Different values are well-suited for
- specific workflows; for instance, in a purely central workflow
- (i.e. the fetch source is equal to the push destination),
- `upstream` is probably what you want. Possible values are:
-+
---
-
-* `nothing` - do not push anything (error out) unless a refspec is
- explicitly given. This is primarily meant for people who want to
- avoid mistakes by always being explicit.
-
-* `current` - push the current branch to update a branch with the same
- name on the receiving end. Works in both central and non-central
- workflows.
-
-* `upstream` - push the current branch back to the branch whose
- changes are usually integrated into the current branch (which is
- called `@{upstream}`). This mode only makes sense if you are
- pushing to the same repository you would normally pull from
- (i.e. central workflow).
-
-* `tracking` - This is a deprecated synonym for `upstream`.
-
-* `simple` - in centralized workflow, work like `upstream` with an
- added safety to refuse to push if the upstream branch's name is
- different from the local one.
-+
-When pushing to a remote that is different from the remote you normally
-pull from, work as `current`. This is the safest option and is suited
-for beginners.
-+
-This mode has become the default in Git 2.0.
-
-* `matching` - push all branches having the same name on both ends.
- This makes the repository you are pushing to remember the set of
- branches that will be pushed out (e.g. if you always push 'maint'
- and 'master' there and no other branches, the repository you push
- to will have these two branches, and your local 'maint' and
- 'master' will be pushed there).
-+
-To use this mode effectively, you have to make sure _all_ the
-branches you would push out are ready to be pushed out before
-running 'git push', as the whole point of this mode is to allow you
-to push all of the branches in one go. If you usually finish work
-on only one branch and push out the result, while other branches are
-unfinished, this mode is not for you. Also this mode is not
-suitable for pushing into a shared central repository, as other
-people may add new branches there, or update the tip of existing
-branches outside your control.
-+
-This used to be the default, but not since Git 2.0 (`simple` is the
-new default).
+* `2` - link:technical/protocol-v2.html[wire protocol version 2].
--
-push.followTags::
- If set to true enable `--follow-tags` option by default. You
- may override this configuration at time of push by specifying
- `--no-follow-tags`.
-
-push.gpgSign::
- May be set to a boolean value, or the string 'if-asked'. A true
- value causes all pushes to be GPG signed, as if `--signed` is
- passed to linkgit:git-push[1]. The string 'if-asked' causes
- pushes to be signed if the server supports it, as if
- `--signed=if-asked` is passed to 'git push'. A false value may
- override a value from a lower-priority config file. An explicit
- command-line flag always overrides this config option.
-
-push.pushOption::
- When no `--push-option=<option>` argument is given from the
- command line, `git push` behaves as if each <value> of
- this variable is given as `--push-option=<value>`.
-+
-This is a multi-valued variable, and an empty value can be used in a
-higher priority configuration file (e.g. `.git/config` in a
-repository) to clear the values inherited from a lower priority
-configuration files (e.g. `$HOME/.gitconfig`).
-+
---
-
-Example:
-
-/etc/gitconfig
- push.pushoption = a
- push.pushoption = b
-
-~/.gitconfig
- push.pushoption = c
-
-repo/.git/config
- push.pushoption =
- push.pushoption = b
+include::pull-config.txt[]
-This will result in only b (a and c are cleared).
-
---
-
-push.recurseSubmodules::
- Make sure all submodule commits used by the revisions to be pushed
- are available on a remote-tracking branch. If the value is 'check'
- then Git will verify that all submodule commits that changed in the
- revisions to be pushed are available on at least one remote of the
- submodule. If any commits are missing, the push will be aborted and
- exit with non-zero status. If the value is 'on-demand' then all
- submodules that changed in the revisions to be pushed will be
- pushed. If on-demand was not able to push all necessary revisions
- it will also be aborted and exit with non-zero status. If the value
- is 'no' then default behavior of ignoring submodules when pushing
- is retained. You may override this configuration at time of push by
- specifying '--recurse-submodules=check|on-demand|no'.
+include::push-config.txt[]
include::rebase-config.txt[]
-receive.advertiseAtomic::
- By default, git-receive-pack will advertise the atomic push
- capability to its clients. If you don't want to advertise this
- capability, set this variable to false.
-
-receive.advertisePushOptions::
- When set to true, git-receive-pack will advertise the push options
- capability to its clients. False by default.
-
-receive.autogc::
- By default, git-receive-pack will run "git-gc --auto" after
- receiving data from git-push and updating refs. You can stop
- it by setting this variable to false.
-
-receive.certNonceSeed::
- By setting this variable to a string, `git receive-pack`
- will accept a `git push --signed` and verifies it by using
- a "nonce" protected by HMAC using this string as a secret
- key.
-
-receive.certNonceSlop::
- When a `git push --signed` sent a push certificate with a
- "nonce" that was issued by a receive-pack serving the same
- repository within this many seconds, export the "nonce"
- found in the certificate to `GIT_PUSH_CERT_NONCE` to the
- hooks (instead of what the receive-pack asked the sending
- side to include). This may allow writing checks in
- `pre-receive` and `post-receive` a bit easier. Instead of
- checking `GIT_PUSH_CERT_NONCE_SLOP` environment variable
- that records by how many seconds the nonce is stale to
- decide if they want to accept the certificate, they only
- can check `GIT_PUSH_CERT_NONCE_STATUS` is `OK`.
-
-receive.fsckObjects::
- If it is set to true, git-receive-pack will check all received
- objects. See `transfer.fsckObjects` for what's checked.
- Defaults to false. If not set, the value of
- `transfer.fsckObjects` is used instead.
-
-receive.fsck.<msg-id>::
- Acts like `fsck.<msg-id>`, but is used by
- linkgit:git-receive-pack[1] instead of
- linkgit:git-fsck[1]. See the `fsck.<msg-id>` documentation for
- details.
-
-receive.fsck.skipList::
- Acts like `fsck.skipList`, but is used by
- linkgit:git-receive-pack[1] instead of
- linkgit:git-fsck[1]. See the `fsck.skipList` documentation for
- details.
-
-receive.keepAlive::
- After receiving the pack from the client, `receive-pack` may
- produce no output (if `--quiet` was specified) while processing
- the pack, causing some networks to drop the TCP connection.
- With this option set, if `receive-pack` does not transmit
- any data in this phase for `receive.keepAlive` seconds, it will
- send a short keepalive packet. The default is 5 seconds; set
- to 0 to disable keepalives entirely.
-
-receive.unpackLimit::
- If the number of objects received in a push is below this
- limit then the objects will be unpacked into loose object
- files. However if the number of received objects equals or
- exceeds this limit then the received pack will be stored as
- a pack, after adding any missing delta bases. Storing the
- pack from a push can make the push operation complete faster,
- especially on slow filesystems. If not set, the value of
- `transfer.unpackLimit` is used instead.
-
-receive.maxInputSize::
- If the size of the incoming pack stream is larger than this
- limit, then git-receive-pack will error out, instead of
- accepting the pack file. If not set or set to 0, then the size
- is unlimited.
-
-receive.denyDeletes::
- If set to true, git-receive-pack will deny a ref update that deletes
- the ref. Use this to prevent such a ref deletion via a push.
-
-receive.denyDeleteCurrent::
- If set to true, git-receive-pack will deny a ref update that
- deletes the currently checked out branch of a non-bare repository.
-
-receive.denyCurrentBranch::
- If set to true or "refuse", git-receive-pack will deny a ref update
- to the currently checked out branch of a non-bare repository.
- Such a push is potentially dangerous because it brings the HEAD
- out of sync with the index and working tree. If set to "warn",
- print a warning of such a push to stderr, but allow the push to
- proceed. If set to false or "ignore", allow such pushes with no
- message. Defaults to "refuse".
-+
-Another option is "updateInstead" which will update the working
-tree if pushing into the current branch. This option is
-intended for synchronizing working directories when one side is not easily
-accessible via interactive ssh (e.g. a live web site, hence the requirement
-that the working directory be clean). This mode also comes in handy when
-developing inside a VM to test and fix code on different Operating Systems.
-+
-By default, "updateInstead" will refuse the push if the working tree or
-the index have any difference from the HEAD, but the `push-to-checkout`
-hook can be used to customize this. See linkgit:githooks[5].
-
-receive.denyNonFastForwards::
- If set to true, git-receive-pack will deny a ref update which is
- not a fast-forward. Use this to prevent such an update via a push,
- even if that push is forced. This configuration variable is
- set when initializing a shared repository.
-
-receive.hideRefs::
- This variable is the same as `transfer.hideRefs`, but applies
- only to `receive-pack` (and so affects pushes, but not fetches).
- An attempt to update or delete a hidden ref by `git push` is
- rejected.
-
-receive.updateServerInfo::
- If set to true, git-receive-pack will run git-update-server-info
- after receiving data from git-push and updating refs.
-
-receive.shallowUpdate::
- If set to true, .git/shallow can be updated when new refs
- require new shallow roots. Otherwise those refs are rejected.
+include::receive-config.txt[]
remote.pushDefault::
The remote to push to by default. Overrides
index is being written (either via `--write-bitmap-index` or
`repack.writeBitmaps`).
+repack.useDeltaIslands::
+ If set to true, makes `git repack` act as if `--delta-islands`
+ was passed. Defaults to `false`.
+
repack.writeBitmaps::
When true, git will write a bitmap index when packing all
objects to disk (e.g., when `git repack -a` is run). This
`$GIT_DIR`, e.g. if "rerere" was previously used in the
repository.
-sendemail.identity::
- A configuration identity. When given, causes values in the
- 'sendemail.<identity>' subsection to take precedence over
- values in the 'sendemail' section. The default identity is
- the value of `sendemail.identity`.
-
-sendemail.smtpEncryption::
- See linkgit:git-send-email[1] for description. Note that this
- setting is not subject to the 'identity' mechanism.
-
-sendemail.smtpssl (deprecated)::
- Deprecated alias for 'sendemail.smtpEncryption = ssl'.
-
-sendemail.smtpsslcertpath::
- Path to ca-certificates (either a directory or a single file).
- Set it to an empty string to disable certificate verification.
-
-sendemail.<identity>.*::
- Identity-specific versions of the 'sendemail.*' parameters
- found below, taking precedence over those when this
- identity is selected, through either the command-line or
- `sendemail.identity`.
-
-sendemail.aliasesFile::
-sendemail.aliasFileType::
-sendemail.annotate::
-sendemail.bcc::
-sendemail.cc::
-sendemail.ccCmd::
-sendemail.chainReplyTo::
-sendemail.confirm::
-sendemail.envelopeSender::
-sendemail.from::
-sendemail.multiEdit::
-sendemail.signedoffbycc::
-sendemail.smtpPass::
-sendemail.suppresscc::
-sendemail.suppressFrom::
-sendemail.to::
-sendemail.tocmd::
-sendemail.smtpDomain::
-sendemail.smtpServer::
-sendemail.smtpServerPort::
-sendemail.smtpServerOption::
-sendemail.smtpUser::
-sendemail.thread::
-sendemail.transferEncoding::
-sendemail.validate::
-sendemail.xmailer::
- See linkgit:git-send-email[1] for description.
-
-sendemail.signedoffcc (deprecated)::
- Deprecated alias for `sendemail.signedoffbycc`.
-
-sendemail.smtpBatchSize::
- Number of messages to be sent per connection, after that a relogin
- will happen. If the value is 0 or undefined, send all messages in
- one connection.
- See also the `--batch-size` option of linkgit:git-send-email[1].
-
-sendemail.smtpReloginDelay::
- Seconds wait before reconnecting to smtp server.
- See also the `--relogin-delay` option of linkgit:git-send-email[1].
-
-showbranch.default::
+include::sendemail-config.txt[]
+
+sequence.editor::
+ Text editor used by `git rebase -i` for editing the rebase instruction file.
+ The value is meant to be interpreted by the shell when it is used.
+ It can be overridden by the `GIT_SEQUENCE_EDITOR` environment variable.
+ When not configured the default commit message editor is used instead.
+
+showBranch.default::
The default set of branches for linkgit:git-show-branch[1].
See linkgit:git-show-branch[1].
option will show diffstat of the stash entry. Defaults to true.
See description of 'show' command in linkgit:git-stash[1].
-submodule.<name>.url::
- The URL for a submodule. This variable is copied from the .gitmodules
- file to the git config via 'git submodule init'. The user can change
- the configured URL before obtaining the submodule via 'git submodule
- update'. If neither submodule.<name>.active or submodule.active are
- set, the presence of this variable is used as a fallback to indicate
- whether the submodule is of interest to git commands.
- See linkgit:git-submodule[1] and linkgit:gitmodules[5] for details.
-
-submodule.<name>.update::
- The method by which a submodule is updated by 'git submodule update',
- which is the only affected command, others such as
- 'git checkout --recurse-submodules' are unaffected. It exists for
- historical reasons, when 'git submodule' was the only command to
- interact with submodules; settings like `submodule.active`
- and `pull.rebase` are more specific. It is populated by
- `git submodule init` from the linkgit:gitmodules[5] file.
- See description of 'update' command in linkgit:git-submodule[1].
-
-submodule.<name>.branch::
- The remote branch name for a submodule, used by `git submodule
- update --remote`. Set this option to override the value found in
- the `.gitmodules` file. See linkgit:git-submodule[1] and
- linkgit:gitmodules[5] for details.
-
-submodule.<name>.fetchRecurseSubmodules::
- This option can be used to control recursive fetching of this
- submodule. It can be overridden by using the --[no-]recurse-submodules
- command-line option to "git fetch" and "git pull".
- This setting will override that from in the linkgit:gitmodules[5]
- file.
-
-submodule.<name>.ignore::
- Defines under what circumstances "git status" and the diff family show
- a submodule as modified. When set to "all", it will never be considered
- modified (but it will nonetheless show up in the output of status and
- commit when it has been staged), "dirty" will ignore all changes
- to the submodules work tree and
- takes only differences between the HEAD of the submodule and the commit
- recorded in the superproject into account. "untracked" will additionally
- let submodules with modified tracked files in their work tree show up.
- Using "none" (the default when this option is not set) also shows
- submodules that have untracked files in their work tree as changed.
- This setting overrides any setting made in .gitmodules for this submodule,
- both settings can be overridden on the command line by using the
- "--ignore-submodules" option. The 'git submodule' commands are not
- affected by this setting.
-
-submodule.<name>.active::
- Boolean value indicating if the submodule is of interest to git
- commands. This config option takes precedence over the
- submodule.active config option. See linkgit:gitsubmodules[7] for
- details.
-
-submodule.active::
- A repeated field which contains a pathspec used to match against a
- submodule's path to determine if the submodule is of interest to git
- commands. See linkgit:gitsubmodules[7] for details.
-
-submodule.recurse::
- Specifies if commands recurse into submodules by default. This
- applies to all commands that have a `--recurse-submodules` option,
- except `clone`.
- Defaults to false.
-
-submodule.fetchJobs::
- Specifies how many submodules are fetched/cloned at the same time.
- A positive integer allows up to that number of submodules fetched
- in parallel. A value of 0 will give some reasonable default.
- If unset, it defaults to 1.
-
-submodule.alternateLocation::
- Specifies how the submodules obtain alternates when submodules are
- cloned. Possible values are `no`, `superproject`.
- By default `no` is assumed, which doesn't add references. When the
- value is set to `superproject` the submodule to be cloned computes
- its alternates location relative to the superprojects alternate.
-
-submodule.alternateErrorStrategy::
- Specifies how to treat errors with the alternates for a submodule
- as computed via `submodule.alternateLocation`. Possible values are
- `ignore`, `info`, `die`. Default is `die`.
+include::submodule-config.txt[]
tag.forceSignAnnotated::
A boolean to specify whether annotated tags created should be GPG signed.
was run. I.e., `upload-pack` will feed input intended for
`pack-objects` to the hook, and expects a completed packfile on
stdout.
-
-uploadpack.allowFilter::
- If this option is set, `upload-pack` will support partial
- clone and partial fetch object filtering.
+
Note that this configuration variable is ignored if it is seen in the
repository-level config (this is a safety measure against fetching from
untrusted repositories).
+uploadpack.allowFilter::
+ If this option is set, `upload-pack` will support partial
+ clone and partial fetch object filtering.
+
uploadpack.allowRefInWant::
If this option is set, `upload-pack` will support the `ref-in-want`
feature of the protocol version 2 `fetch` command. This feature
Any other value is treated as a custom diff tool and requires
that a corresponding difftool.<tool>.cmd variable is defined.
+diff.guitool::
+ Controls which diff tool is used by linkgit:git-difftool[1] when
+ the -g/--gui flag is specified. This variable overrides the value
+ configured in `merge.guitool`. The list below shows the valid
+ built-in values. Any other value is treated as a custom diff tool
+ and requires that a corresponding difftool.<guitool>.cmd variable
+ is defined.
+
include::mergetools-diff.txt[]
diff.indentHeuristic::
#!/bin/sh
+#
+# Build two documentation trees and diff the resulting formatted output.
+# Compared to a source diff, this can reveal mistakes in the formatting.
+# For example:
+#
+# ./doc-diff origin/master HEAD
+#
+# would show the differences introduced by a branch based on master.
OPTIONS_SPEC="\
doc-diff [options] <from> <to> [-- <diff-options>]
+doc-diff (-c|--clean)
--
j=n parallel argument to pass to make
f force rebuild; do not rely on cached results
+c,clean cleanup temporary working files
"
SUBDIRECTORY_OK=1
. "$(git --exec-path)/git-sh-setup"
parallel=
force=
+clean=
while test $# -gt 0
do
case "$1" in
-j)
parallel=$2; shift ;;
+ -c|--clean)
+ clean=t ;;
-f)
force=t ;;
--)
shift
done
+cd_to_toplevel
+tmp=Documentation/tmp-doc-diff
+
+if test -n "$clean"
+then
+ test $# -eq 0 || usage
+ git worktree remove --force "$tmp/worktree" 2>/dev/null
+ rm -rf "$tmp"
+ exit 0
+fi
+
if test -z "$parallel"
then
parallel=$(getconf _NPROCESSORS_ONLN 2>/dev/null)
from_oid=$(git rev-parse --verify "$from") || exit 1
to_oid=$(git rev-parse --verify "$to") || exit 1
-cd_to_toplevel
-tmp=Documentation/tmp-doc-diff
-
if test -n "$force"
then
rm -rf "$tmp"
# results that don't differ between the two trees.
if ! test -d "$tmp/worktree"
then
- git worktree add --detach "$tmp/worktree" "$from" &&
+ git worktree add -f --detach "$tmp/worktree" "$from" &&
dots=$(echo "$tmp/worktree" | sed 's#[^/]*#..#g') &&
ln -s "$dots/config.mak" "$tmp/worktree/config.mak"
fi
printf '%s: %s\n' "$dst" "$src"
printf '\t@echo >&2 " RENDER $(notdir $@)" && \\\n'
printf '\tmkdir -p $(dir $@) && \\\n'
- printf '\tMANWIDTH=80 man -l $< >$@+ && \\\n'
+ printf '\tMANWIDTH=80 man $< >$@+ && \\\n'
printf '\tmv $@+ $@\n'
done
}
-# render_tree <dirname> <committish>
+# render_tree <committish_oid>
render_tree () {
# Skip install-man entirely if we already have an installed directory.
# We can't rely on make here, since "install-man" unconditionally
# through.
if ! test -d "$tmp/installed/$1"
then
- git -C "$tmp/worktree" checkout "$2" &&
+ git -C "$tmp/worktree" checkout --detach "$1" &&
make -j$parallel -C "$tmp/worktree" \
GIT_VERSION=omitted \
SOURCE_DATE_EPOCH=0 \
fi
}
-render_tree $from_oid "$from" &&
-render_tree $to_oid "$to" &&
+render_tree $from_oid &&
+render_tree $to_oid &&
git -C $tmp/rendered diff --no-index "$@" $from_oid $to_oid
--- /dev/null
+fetch.recurseSubmodules::
+ This option can be either set to a boolean value or to 'on-demand'.
+ Setting it to a boolean changes the behavior of fetch and pull to
+ unconditionally recurse into submodules when set to true or to not
+ recurse at all when set to false. When set to 'on-demand' (the default
+ value), fetch and pull will only recurse into a populated submodule
+ when its superproject retrieves a commit that updates the submodule's
+ reference.
+
+fetch.fsckObjects::
+ If it is set to true, git-fetch-pack will check all fetched
+ objects. See `transfer.fsckObjects` for what's
+ checked. Defaults to false. If not set, the value of
+ `transfer.fsckObjects` is used instead.
+
+fetch.fsck.<msg-id>::
+ Acts like `fsck.<msg-id>`, but is used by
+ linkgit:git-fetch-pack[1] instead of linkgit:git-fsck[1]. See
+ the `fsck.<msg-id>` documentation for details.
+
+fetch.fsck.skipList::
+ Acts like `fsck.skipList`, but is used by
+ linkgit:git-fetch-pack[1] instead of linkgit:git-fsck[1]. See
+ the `fsck.skipList` documentation for details.
+
+fetch.unpackLimit::
+ If the number of objects fetched over the Git native
+ transfer is below this
+ limit, then the objects will be unpacked into loose object
+ files. However if the number of received objects equals or
+ exceeds this limit then the received pack will be stored as
+ a pack, after adding any missing delta bases. Storing the
+ pack from a push can make the push operation complete faster,
+ especially on slow filesystems. If not set, the value of
+ `transfer.unpackLimit` is used instead.
+
+fetch.prune::
+ If true, fetch will automatically behave as if the `--prune`
+ option was given on the command line. See also `remote.<name>.prune`
+ and the PRUNING section of linkgit:git-fetch[1].
+
+fetch.pruneTags::
+ If true, fetch will automatically behave as if the
+ `refs/tags/*:refs/tags/*` refspec was provided when pruning,
+ if not set already. This allows for setting both this option
+ and `fetch.prune` to maintain a 1=1 mapping to upstream
+ refs. See also `remote.<name>.pruneTags` and the PRUNING
+ section of linkgit:git-fetch[1].
+
+fetch.output::
+ Control how ref update status is printed. Valid values are
+ `full` and `compact`. Default value is `full`. See section
+ OUTPUT in linkgit:git-fetch[1] for detail.
+
+fetch.negotiationAlgorithm::
+ Control how information about the commits in the local repository is
+ sent when negotiating the contents of the packfile to be sent by the
+ server. Set to "skipping" to use an algorithm that skips commits in an
+ effort to converge faster, but may result in a larger-than-necessary
+ packfile; The default is "default" which instructs Git to use the default algorithm
+ that never skips commits (unless the server has acknowledged it or one
+ of its descendants).
+ Unknown values will cause 'git fetch' to error out.
++
+See also the `--negotiation-tip` option for linkgit:git-fetch[1].
-f::
--force::
- When 'git fetch' is used with `<rbranch>:<lbranch>`
- refspec, it refuses to update the local branch
- `<lbranch>` unless the remote branch `<rbranch>` it
- fetches is a descendant of `<lbranch>`. This option
- overrides that check.
+ When 'git fetch' is used with `<src>:<dst>` refspec it may
+ refuse to update the local branch as discussed
+ifdef::git-pull[]
+ in the `<refspec>` part of the linkgit:git-fetch[1]
+ documentation.
+endif::git-pull[]
+ifndef::git-pull[]
+ in the `<refspec>` part below.
+endif::git-pull[]
+ This option overrides that check.
-k::
--keep::
--- /dev/null
+format.attach::
+ Enable multipart/mixed attachments as the default for
+ 'format-patch'. The value can also be a double quoted string
+ which will enable attachments as the default and set the
+ value as the boundary. See the --attach option in
+ linkgit:git-format-patch[1].
+
+format.from::
+ Provides the default value for the `--from` option to format-patch.
+ Accepts a boolean value, or a name and email address. If false,
+ format-patch defaults to `--no-from`, using commit authors directly in
+ the "From:" field of patch mails. If true, format-patch defaults to
+ `--from`, using your committer identity in the "From:" field of patch
+ mails and including a "From:" field in the body of the patch mail if
+ different. If set to a non-boolean value, format-patch uses that
+ value instead of your committer identity. Defaults to false.
+
+format.numbered::
+ A boolean which can enable or disable sequence numbers in patch
+ subjects. It defaults to "auto" which enables it only if there
+ is more than one patch. It can be enabled or disabled for all
+ messages by setting it to "true" or "false". See --numbered
+ option in linkgit:git-format-patch[1].
+
+format.headers::
+ Additional email headers to include in a patch to be submitted
+ by mail. See linkgit:git-format-patch[1].
+
+format.to::
+format.cc::
+ Additional recipients to include in a patch to be submitted
+ by mail. See the --to and --cc options in
+ linkgit:git-format-patch[1].
+
+format.subjectPrefix::
+ The default for format-patch is to output files with the '[PATCH]'
+ subject prefix. Use this variable to change that prefix.
+
+format.signature::
+ The default for format-patch is to output a signature containing
+ the Git version number. Use this variable to change that default.
+ Set this variable to the empty string ("") to suppress
+ signature generation.
+
+format.signatureFile::
+ Works just like format.signature except the contents of the
+ file specified by this variable will be used as the signature.
+
+format.suffix::
+ The default for format-patch is to output files with the suffix
+ `.patch`. Use this variable to change that suffix (make sure to
+ include the dot if you want it).
+
+format.pretty::
+ The default pretty format for log/show/whatchanged command,
+ See linkgit:git-log[1], linkgit:git-show[1],
+ linkgit:git-whatchanged[1].
+
+format.thread::
+ The default threading style for 'git format-patch'. Can be
+ a boolean value, or `shallow` or `deep`. `shallow` threading
+ makes every mail a reply to the head of the series,
+ where the head is chosen from the cover letter, the
+ `--in-reply-to`, and the first patch mail, in this order.
+ `deep` threading makes every mail a reply to the previous one.
+ A true boolean value is the same as `shallow`, and a false
+ value disables threading.
+
+format.signOff::
+ A boolean value which lets you enable the `-s/--signoff` option of
+ format-patch by default. *Note:* Adding the Signed-off-by: line to a
+ patch should be a conscious act and means that you certify you have
+ the rights to submit this work under the same open source license.
+ Please see the 'SubmittingPatches' document for further discussion.
+
+format.coverLetter::
+ A boolean that controls whether to generate a cover-letter when
+ format-patch is invoked, but in addition can be set to "auto", to
+ generate a cover-letter only when there's more than one patch.
+
+format.outputDirectory::
+ Set a custom directory to store the resulting files instead of the
+ current working directory.
+
+format.useAutoBase::
+ A boolean value which lets you enable the `--base=auto` option of
+ format-patch by default.
NAME
----
-git-archimport - Import an Arch repository into Git
+git-archimport - Import a GNU Arch repository into Git
SYNOPSIS
DESCRIPTION
-----------
-Imports a project from one or more Arch repositories. It will follow branches
+Imports a project from one or more GNU Arch repositories.
+It will follow branches
and repositories within the namespaces defined by the <archive/branch>
parameters supplied. If it cannot find the remote branch a merge comes from
it will just import it as a regular commit. If it can find it, it will mark it
Let's take the following graph as an example:
-------------
- G-H-I-J
- / \
+ G-H-I-J
+ / \
A-B-C-D-E-F O
- \ /
- K-L-M-N
+ \ /
+ K-L-M-N
-------------
If we compute the following non optimal function on it:
we get:
-------------
- 4 3 2 1
- G-H-I-J
+ 4 3 2 1
+ G-H-I-J
1 2 3 4 5 6/ \0
A-B-C-D-E-F O
- \ /
- K-L-M-N
- 4 3 2 1
+ \ /
+ K-L-M-N
+ 4 3 2 1
-------------
but with the algorithm used by git bisect we get:
-------------
- 7 7 6 5
- G-H-I-J
+ 7 7 6 5
+ G-H-I-J
1 2 3 4 5 6/ \0
A-B-C-D-E-F O
- \ /
- K-L-M-N
- 7 7 6 5
+ \ /
+ K-L-M-N
+ 7 7 6 5
-------------
So we chose G, H, K or L as the best bisection point, which is better
-------------
A-B-C-D-E-F-G <--main
\
- H-I-J <--dev
+ H-I-J <--dev
-------------
The commit "D" is called a "merge base" for branch "main" and "dev"
[(--merged | --no-merged) [<commit>]]
[--contains [<commit]] [--no-contains [<commit>]]
[--points-at <object>] [--format=<format>] [<pattern>...]
-'git branch' [--track | --no-track] [-l] [-f] <branchname> [<start-point>]
+'git branch' [--track | --no-track] [-f] <branchname> [<start-point>]
'git branch' (--set-upstream-to=<upstream> | -u <upstream>) [<branchname>]
'git branch' --unset-upstream [<branchname>]
'git branch' (-m | -M) [<oldbranch>] <newbranch>
The negated form `--no-create-reflog` only overrides an earlier
`--create-reflog`, but currently does not negate the setting of
`core.logAllRefUpdates`.
-+
-The `-l` option is a deprecated synonym for `--create-reflog`.
-f::
--force::
--all::
List both remote-tracking branches and local branches.
+-l::
--list::
List branches. With optional `<pattern>...`, e.g. `git
branch --list 'maint-*'`, list only the branches that match
the pattern(s).
-+
-This should not be confused with `git branch -l <branchname>`,
-which creates a branch named `<branchname>` with a reflog.
-See `--create-reflog` above for details.
-v::
-vv::
commits, one of them tagged, and with branch 'master' checked out:
------------
- HEAD (refers to branch 'master')
- |
- v
+ HEAD (refers to branch 'master')
+ |
+ v
a---b---c branch 'master' (refers to commit 'c')
^
|
------------
$ edit; git add; git commit
- HEAD (refers to branch 'master')
- |
- v
+ HEAD (refers to branch 'master')
+ |
+ v
a---b---c---d branch 'master' (refers to commit 'd')
^
|
------------
$ git checkout master
- HEAD (refers to branch 'master')
+ HEAD (refers to branch 'master')
e---f |
/ v
a---b---c---d branch 'master' (refers to commit 'd')
DESCRIPTION
-----------
-This command formats its input into multiple columns.
+This command formats the lines of its standard input into a table with
+multiple columns. Each input line occupies one cell of the table. It
+is used internally by other git commands to format output into
+columns.
OPTIONS
-------
--mode=<mode>::
Specify layout mode. See configuration variable column.ui for option
- syntax.
+ syntax in linkgit:git-config[1].
--raw-mode=<n>::
Same as --mode but take mode encoded as a number. This is mainly used
--padding=<N>::
The number of spaces between columns. One space by default.
+EXAMPLES
+------
+
+Format data by columns:
+------------
+$ seq 1 24 | git column --mode=column --padding=5
+1 4 7 10 13 16 19 22
+2 5 8 11 14 17 20 23
+3 6 9 12 15 18 21 24
+------------
+
+Format data by rows:
+------------
+$ seq 1 21 | git column --mode=row --padding=5
+1 2 3 4 5 6 7
+8 9 10 11 12 13 14
+15 16 17 18 19 20 21
+------------
+
+List some tags in a table with unequal column widths:
+------------
+$ git tag --list 'v2.4.*' --column=row,dense
+v2.4.0 v2.4.0-rc0 v2.4.0-rc1 v2.4.0-rc2 v2.4.0-rc3
+v2.4.1 v2.4.10 v2.4.11 v2.4.12 v2.4.2
+v2.4.3 v2.4.4 v2.4.5 v2.4.6 v2.4.7
+v2.4.8 v2.4.9
+------------
+
GIT
---
Part of the linkgit:git[1] suite
NAME
----
-git-commit-graph - Write and verify Git commit graph files
+git-commit-graph - Write and verify Git commit-graph files
SYNOPSIS
DESCRIPTION
-----------
-Manage the serialized commit graph file.
+Manage the serialized commit-graph file.
OPTIONS
-------
--object-dir::
- Use given directory for the location of packfiles and commit graph
+ Use given directory for the location of packfiles and commit-graph
file. This parameter exists to specify the location of an alternate
- that only has the objects directory, not a full .git directory. The
- commit graph file is expected to be at <dir>/info/commit-graph and
- the packfiles are expected to be in <dir>/pack.
+ that only has the objects directory, not a full `.git` directory. The
+ commit-graph file is expected to be at `<dir>/info/commit-graph` and
+ the packfiles are expected to be in `<dir>/pack`.
COMMANDS
--------
'write'::
-Write a commit graph file based on the commits found in packfiles.
+Write a commit-graph file based on the commits found in packfiles.
+
With the `--stdin-packs` option, generate the new commit graph by
walking objects only in the specified pack-indexes. (Cannot be combined
'read'::
-Read a graph file given by the commit-graph file and output basic
-details about the graph file. Used for debugging purposes.
+Read the commit-graph file and output basic details about it.
+Used for debugging purposes.
'verify'::
EXAMPLES
--------
-* Write a commit graph file for the packed commits in your local .git folder.
+* Write a commit-graph file for the packed commits in your local `.git`
+ directory.
+
------------------------------------------------
$ git commit-graph write
------------------------------------------------
-* Write a graph file, extending the current graph file using commits
-* in <pack-index>.
+* Write a commit-graph file, extending the current commit-graph file
+ using commits in `<pack-index>`.
+
------------------------------------------------
$ echo <pack-index> | git commit-graph write --stdin-packs
------------------------------------------------
-* Write a graph file containing all reachable commits.
+* Write a commit-graph file containing all reachable commits.
+
------------------------------------------------
$ git show-ref -s | git commit-graph write --stdin-commits
------------------------------------------------
-* Write a graph file containing all commits in the current
-* commit-graph file along with those reachable from HEAD.
+* Write a commit-graph file containing all commits in the current
+ commit-graph file along with those reachable from `HEAD`.
+
------------------------------------------------
$ git rev-parse HEAD | git commit-graph write --stdin-commits --append
--bool-or-int::
--path::
--expiry-date::
- Historical options for selecting a type specifier. Prefer instead `--type`,
- (see: above).
+ Historical options for selecting a type specifier. Prefer instead `--type`
+ (see above).
--no-type::
Un-sets the previously set type specifier (if one was previously set). This
false, while it is set to `true` for all others:
------------
-% git config --bool --get-urlmatch http.sslverify https://good.example.com
+% git config --type=bool --get-urlmatch http.sslverify https://good.example.com
true
-% git config --bool --get-urlmatch http.sslverify https://weak.example.com
+% git config --type=bool --get-urlmatch http.sslverify https://weak.example.com
false
% git config --get-urlmatch http https://weak.example.com
http.cookieFile /tmp/cookie.txt
commit. If the tag points to the commit, then only the tag is
shown. Otherwise, it suffixes the tag name with the number of
additional commits on top of the tagged object and the
-abbreviated object name of the most recent commit.
+abbreviated object name of the most recent commit. The result
+is a "human-readable" object name which can also be used to
+identify the commit to other git commands.
By default (without --all or --tags) `git describe` only shows
annotated tags. For more information about creating annotated tags
This form is to view the changes on the branch containing
and up to the second <commit>, starting at a common ancestor
of both <commit>. "git diff A\...B" is equivalent to
- "git diff $(git-merge-base A B) B". You can omit any one
+ "git diff $(git merge-base A B) B". You can omit any one
of <commit>, which has the same effect as using HEAD instead.
-Just in case if you are doing something exotic, it should be
+Just in case you are doing something exotic, it should be
noted that all of the <commit> in the above description, except
in the last two forms that use ".." notations, can be any
<tree>.
[(--reroll-count|-v) <n>]
[--to=<email>] [--cc=<email>]
[--[no-]cover-letter] [--quiet] [--notes[=<ref>]]
+ [--interdiff=<previous>]
+ [--range-diff=<previous> [--creation-factor=<percent>]]
[--progress]
[<common diff options>]
[ <since> | <revision range> ]
containing the branch description, shortlog and the overall diffstat. You can
fill in a description in the file before sending it out.
+--interdiff=<previous>::
+ As a reviewer aid, insert an interdiff into the cover letter,
+ or as commentary of the lone patch of a 1-patch series, showing
+ the differences between the previous version of the patch series and
+ the series currently being formatted. `previous` is a single revision
+ naming the tip of the previous series which shares a common base with
+ the series being formatted (for example `git format-patch
+ --cover-letter --interdiff=feature/v1 -3 feature/v2`).
+
+--range-diff=<previous>::
+ As a reviewer aid, insert a range-diff (see linkgit:git-range-diff[1])
+ into the cover letter, or as commentary of the lone patch of a
+ 1-patch series, showing the differences between the previous
+ version of the patch series and the series currently being formatted.
+ `previous` can be a single revision naming the tip of the previous
+ series if it shares a common base with the series being formatted (for
+ example `git format-patch --cover-letter --range-diff=feature/v1 -3
+ feature/v2`), or a revision range if the two versions of the series are
+ disjoint (for example `git format-patch --cover-letter
+ --range-diff=feature/v1~3..feature/v1 -3 feature/v2`).
+
+--creation-factor=<percent>::
+ Used with `--range-diff`, tweak the heuristic which matches up commits
+ between the previous and current series of patches by adjusting the
+ creation/deletion cost fudge factor. See linkgit:git-range-diff[1])
+ for details.
+
--notes[=<ref>]::
Append the notes (see linkgit:git-notes[1]) for the commit
after the three-dash line.
such as compressing file revisions (to reduce disk space and increase
performance), removing unreachable objects which may have been
created from prior invocations of 'git add', packing refs, pruning
-reflog, rerere metadata or stale working trees.
+reflog, rerere metadata or stale working trees. May also update ancillary
+indexes such as the commit-graph.
Users are encouraged to run this task on a regular basis within
each repository to maintain good disk space utilization and good
[(-O | --open-files-in-pager) [<pager>]]
[-z | --null]
[ -o | --only-matching ] [-c | --count] [--all-match] [-q | --quiet]
- [--max-depth <depth>]
+ [--max-depth <depth>] [--[no-]recursive]
[--color[=<when>] | --no-color]
[--break] [--heading] [-p | --show-function]
[-A <post-context>] [-B <pre-context>] [-C <context>]
--max-depth <depth>::
For each <pathspec> given on command line, descend at most <depth>
- levels of directories. A negative value means no limit.
+ levels of directories. A value of -1 means no limit.
This option is ignored if <pathspec> contains active wildcards.
In other words if "a*" matches a directory named "a*",
"*" is matched literally so --max-depth is still effective.
+-r::
+--recursive::
+ Same as `--max-depth=-1`; this is the default.
+
+--no-recursive::
+ Same as `--max-depth=0`.
+
-w::
--word-regexp::
Match the pattern only at word boundary (either begin at the
SYNOPSIS
--------
[verse]
-'git help' [-a|--all [--verbose]] [-g|--guide]
+'git help' [-a|--all [--[no-]verbose]] [-g|--guide]
[-i|--info|-m|--man|-w|--web] [COMMAND|GUIDE]
DESCRIPTION
purpose, but this can be overridden by other options or configuration
variables.
+If an alias is given, git shows the definition of the alias on
+standard output. To get the manual page for the aliased command, use
+`git COMMAND --help`.
+
Note that `git --help ...` is identical to `git help ...` because the
former is internally converted into the latter.
--all::
Prints all the available commands on the standard output. This
option overrides any given command or guide name.
- When used with `--verbose` print description for all recognized
- commands.
+
+--verbose::
+ When used with `--all` print description for all recognized
+ commands. This is the default.
-c::
--config::
least 25% trailers.
The group must be preceded by one or more empty (or whitespace-only) lines.
The group must either be at the end of the message or be the last
-non-whitespace lines before a line that starts with '---'. Such three
-minus signs start the patch part of the message.
+non-whitespace lines before a line that starts with '---' (followed by a
+space or the end of the line). Such three minus signs start the patch
+part of the message. See also `--no-divider` below.
When reading trailers, there can be whitespaces after the
token, the separator and the value. There can also be whitespaces
A convenience alias for `--only-trailers --only-input
--unfold`.
+--no-divider::
+ Do not treat `---` as the end of the commit message. Use this
+ when you know your input contains just the commit message itself
+ (and not an email or the output of `git format-patch`).
+
CONFIGURATION VARIABLES
-----------------------
`origin/master` may have been rewound and rebuilt, leading to a
history of this shape:
- o---B2
+ o---B2
/
---o---o---B1--o---o---o---B (origin/master)
\
- B0
+ B0
\
- D0---D1---D (topic)
+ D0---D1---D (topic)
where `origin/master` used to point at commits B0, B1, B2 and now it
points at B, and your `topic` branch was started on top of it back
Prompt before each invocation of the merge resolution program
to give the user a chance to skip the path.
+-g::
+--gui::
+ When 'git-mergetool' is invoked with the `-g` or `--gui` option
+ the default merge tool will be read from the configured
+ `merge.guitool` variable instead of `merge.tool`.
+
+--no-gui::
+ This overrides a previous `-g` or `--gui` setting and reads the
+ default merge tool will be read from the configured `merge.tool`
+ variable.
+
-O<orderfile>::
Process files in the order specified in the
<orderfile>, which has one shell glob pattern per line.
--- /dev/null
+git-multi-pack-index(1)
+=======================
+
+NAME
+----
+git-multi-pack-index - Write and verify multi-pack-indexes
+
+
+SYNOPSIS
+--------
+[verse]
+'git multi-pack-index' [--object-dir=<dir>] <verb>
+
+DESCRIPTION
+-----------
+Write or verify a multi-pack-index (MIDX) file.
+
+OPTIONS
+-------
+
+--object-dir=<dir>::
+ Use given directory for the location of Git objects. We check
+ `<dir>/packs/multi-pack-index` for the current MIDX file, and
+ `<dir>/packs` for the pack-files to index.
+
+write::
+ When given as the verb, write a new MIDX file to
+ `<dir>/packs/multi-pack-index`.
+
+verify::
+ When given as the verb, verify the contents of the MIDX file
+ at `<dir>/packs/multi-pack-index`.
+
+
+EXAMPLES
+--------
+
+* Write a MIDX file for the packfiles in the current .git folder.
++
+-----------------------------------------------
+$ git multi-pack-index write
+-----------------------------------------------
+
+* Write a MIDX file for the packfiles in an alternate object store.
++
+-----------------------------------------------
+$ git multi-pack-index --object-dir <alt> write
+-----------------------------------------------
+
+* Verify the MIDX file for the packfiles in the current .git folder.
++
+-----------------------------------------------
+$ git multi-pack-index verify
+-----------------------------------------------
+
+
+SEE ALSO
+--------
+See link:technical/multi-pack-index.html[The Multi-Pack-Index Design
+Document] and link:technical/pack-format.html[The Multi-Pack-Index
+Format] for more information on the multi-pack-index feature.
+
+
+GIT
+---
+Part of the linkgit:git[1] suite
Unshelve
~~~~~~~~
Unshelving will take a shelved P4 changelist, and produce the equivalent git commit
-in the branch refs/remotes/p4/unshelved/<changelist>.
+in the branch refs/remotes/p4-unshelved/<changelist>.
The git commit is created relative to the current origin revision (HEAD by default).
-If the shelved changelist's parent revisions differ, git-p4 will refuse to unshelve;
-you need to be unshelving onto an equivalent tree.
+A parent commit is created based on the origin, and then the unshelve commit is
+created based on that.
The origin revision can be changed with the "--origin" option.
-If the target branch in refs/remotes/p4/unshelved already exists, the old one will
+If the target branch in refs/remotes/p4-unshelved already exists, the old one will
be renamed.
----
$ git p4 sync
$ git p4 unshelve 12345
-$ git show refs/remotes/p4/unshelved/12345
+$ git show p4-unshelved/12345
<submit more changes via p4 to the same files>
$ git p4 unshelve 12345
<refuses to unshelve until git is in sync with p4 again>
--unpack-unreachable::
Keep unreachable objects in loose form. This implies `--revs`.
+--delta-islands::
+ Restrict delta matches based on "islands". See DELTA ISLANDS
+ below.
+
+
+DELTA ISLANDS
+-------------
+
+When possible, `pack-objects` tries to reuse existing on-disk deltas to
+avoid having to search for new ones on the fly. This is an important
+optimization for serving fetches, because it means the server can avoid
+inflating most objects at all and just send the bytes directly from
+disk. This optimization can't work when an object is stored as a delta
+against a base which the receiver does not have (and which we are not
+already sending). In that case the server "breaks" the delta and has to
+find a new one, which has a high CPU cost. Therefore it's important for
+performance that the set of objects in on-disk delta relationships match
+what a client would fetch.
+
+In a normal repository, this tends to work automatically. The objects
+are mostly reachable from the branches and tags, and that's what clients
+fetch. Any deltas we find on the server are likely to be between objects
+the client has or will have.
+
+But in some repository setups, you may have several related but separate
+groups of ref tips, with clients tending to fetch those groups
+independently. For example, imagine that you are hosting several "forks"
+of a repository in a single shared object store, and letting clients
+view them as separate repositories through `GIT_NAMESPACE` or separate
+repos using the alternates mechanism. A naive repack may find that the
+optimal delta for an object is against a base that is only found in
+another fork. But when a client fetches, they will not have the base
+object, and we'll have to find a new delta on the fly.
+
+A similar situation may exist if you have many refs outside of
+`refs/heads/` and `refs/tags/` that point to related objects (e.g.,
+`refs/pull` or `refs/changes` used by some hosting providers). By
+default, clients fetch only heads and tags, and deltas against objects
+found only in those other groups cannot be sent as-is.
+
+Delta islands solve this problem by allowing you to group your refs into
+distinct "islands". Pack-objects computes which objects are reachable
+from which islands, and refuses to make a delta from an object `A`
+against a base which is not present in all of `A`'s islands. This
+results in slightly larger packs (because we miss some delta
+opportunities), but guarantees that a fetch of one island will not have
+to recompute deltas on the fly due to crossing island boundaries.
+
+When repacking with delta islands the delta window tends to get
+clogged with candidates that are forbidden by the config. Repacking
+with a big --window helps (and doesn't take as long as it otherwise
+might because we can reject some object pairs based on islands before
+doing any computation on the content).
+
+Islands are configured via the `pack.island` option, which can be
+specified multiple times. Each value is a left-anchored regular
+expressions matching refnames. For example:
+
+-------------------------------------------
+[pack]
+island = refs/heads/
+island = refs/tags/
+-------------------------------------------
+
+puts heads and tags into an island (whose name is the empty string; see
+below for more on naming). Any refs which do not match those regular
+expressions (e.g., `refs/pull/123`) is not in any island. Any object
+which is reachable only from `refs/pull/` (but not heads or tags) is
+therefore not a candidate to be used as a base for `refs/heads/`.
+
+Refs are grouped into islands based on their "names", and two regexes
+that produce the same name are considered to be in the same
+island. The names are computed from the regexes by concatenating any
+capture groups from the regex, with a '-' dash in between. (And if
+there are no capture groups, then the name is the empty string, as in
+the above example.) This allows you to create arbitrary numbers of
+islands. Only up to 14 such capture groups are supported though.
+
+For example, imagine you store the refs for each fork in
+`refs/virtual/ID`, where `ID` is a numeric identifier. You might then
+configure:
+
+-------------------------------------------
+[pack]
+island = refs/virtual/([0-9]+)/heads/
+island = refs/virtual/([0-9]+)/tags/
+island = refs/virtual/([0-9]+)/(pull)/
+-------------------------------------------
+
+That puts the heads and tags for each fork in their own island (named
+"1234" or similar), and the pull refs for each go into their own
+"1234-pull".
+
+Note that we pick a single island for each regex to go into, using "last
+one wins" ordering (which allows repo-specific config to take precedence
+over user-wide config, and so forth).
+
SEE ALSO
--------
linkgit:git-rev-list[1]
`:<dst>` means to update the same ref as the `<src>`.
+
The object referenced by <src> is used to update the <dst> reference
-on the remote side. By default this is only allowed if <dst> is not
-a tag (annotated or lightweight), and then only if it can fast-forward
-<dst>. By having the optional leading `+`, you can tell Git to update
-the <dst> ref even if it is not allowed by default (e.g., it is not a
-fast-forward.) This does *not* attempt to merge <src> into <dst>. See
-EXAMPLES below for details.
-+
-`tag <tag>` means the same as `refs/tags/<tag>:refs/tags/<tag>`.
-+
-Pushing an empty <src> allows you to delete the <dst> ref from
-the remote repository.
+on the remote side. Whether this is allowed depends on where in
+`refs/*` the <dst> reference lives as described in detail below, in
+those sections "update" means any modifications except deletes, which
+as noted after the next few sections are treated differently.
++
+The `refs/heads/*` namespace will only accept commit objects, and
+updates only if they can be fast-forwarded.
++
+The `refs/tags/*` namespace will accept any kind of object (as
+commits, trees and blobs can be tagged), and any updates to them will
+be rejected.
++
+It's possible to push any type of object to any namespace outside of
+`refs/{tags,heads}/*`. In the case of tags and commits, these will be
+treated as if they were the commits inside `refs/heads/*` for the
+purposes of whether the update is allowed.
++
+I.e. a fast-forward of commits and tags outside `refs/{tags,heads}/*`
+is allowed, even in cases where what's being fast-forwarded is not a
+commit, but a tag object which happens to point to a new commit which
+is a fast-forward of the commit the last tag (or commit) it's
+replacing. Replacing a tag with an entirely different tag is also
+allowed, if it points to the same commit, as well as pushing a peeled
+tag, i.e. pushing the commit that existing tag object points to, or a
+new tag object which an existing commit points to.
++
+Tree and blob objects outside of `refs/{tags,heads}/*` will be treated
+the same way as if they were inside `refs/tags/*`, any update of them
+will be rejected.
++
+All of the rules described above about what's not allowed as an update
+can be overridden by adding an the optional leading `+` to a refspec
+(or using `--force` command line option). The only exception to this
+is that no amount of forcing will make the `refs/heads/*` namespace
+accept a non-commit object. Hooks and configuration can also override
+or amend these rules, see e.g. `receive.denyNonFastForwards` in
+linkgit:git-config[1] and `pre-receive` and `update` in
+linkgit:githooks[5].
++
+Pushing an empty <src> allows you to delete the <dst> ref from the
+remote repository. Deletions are always accepted without a leading `+`
+in the refspec (or `--force`), except when forbidden by configuration
+or hooks. See `receive.denyDeletes` in linkgit:git-config[1] and
+`pre-receive` and `update` in linkgit:githooks[5].
+
The special refspec `:` (or `+:` to allow non-fast-forward updates)
directs Git to push "matching" branches: for every branch that exists on
the local side, the remote side is updated if a branch of the same name
already exists on the remote side.
++
+`tag <tag>` means the same as `refs/tags/<tag>:refs/tags/<tag>`.
--all::
Push all branches (i.e. refs under `refs/heads/`); cannot be
to proceed.
The `reset` command resets the HEAD, index and worktree to the specified
-revision. It is isimilar to an `exec git reset --hard <label>`, but
+revision. It is similar to an `exec git reset --hard <label>`, but
refuses to overwrite untracked files. If the `reset` command fails, it is
rescheduled immediately, with a helpful message how to edit the todo list
(this typically happens when a `reset` command was inserted into the todo
being removed. In addition, any unreachable loose objects will
be packed (and their loose counterparts removed).
+-i::
+--delta-islands::
+ Pass the `--delta-islands` option to `git-pack-objects`, see
+ linkgit:git-pack-objects[1].
+
Configuration
-------------
'git rerere' will be run by 'git rebase' to help you resolve this
conflict.
+[NOTE] 'git rerere' relies on the conflict markers in the file to
+detect the conflict. If the file already contains lines that look the
+same as lines with conflict markers, 'git rerere' may fail to record a
+conflict resolution. To work around this, the `conflict-marker-size`
+setting in linkgit:gitattributes[5] can be used.
+
GIT
---
Part of the linkgit:git[1] suite
auto-cc of:
+
--
-- 'author' will avoid including the patch author
-- 'self' will avoid including the sender
+- 'author' will avoid including the patch author.
+- 'self' will avoid including the sender.
- 'cc' will avoid including anyone mentioned in Cc lines in the patch header
except for self (use 'self' for that).
- 'bodycc' will avoid including anyone mentioned in Cc lines in the
patch body (commit message) except for self (use 'self' for that).
- 'sob' will avoid including anyone mentioned in Signed-off-by lines except
- for self (use 'self' for that).
+ for self (use 'self' for that).
+- 'misc-by' will avoid including anyone mentioned in Acked-by,
+ Reviewed-by, Tested-by and other "-by" lines in the patch body,
+ except Signed-off-by (use 'sob' for that).
- 'cccmd' will avoid running the --cc-cmd.
-- 'body' is equivalent to 'sob' + 'bodycc'
+- 'body' is equivalent to 'sob' + 'bodycc' + 'misc-by'.
- 'all' will suppress all auto cc values.
--
+
-----------
Shows the commit ancestry graph starting from the commits named
-with <rev>s or <globs>s (or all refs under refs/heads
+with <rev>s or <glob>s (or all refs under refs/heads
and/or refs/tags) semi-visually.
It cannot show more than 29 branches and commits at a time.
`--create-reflog`, but currently does not negate the setting of
`core.logAllRefUpdates`.
+--format=<format>::
+ A string that interpolates `%(fieldname)` from a tag ref being shown
+ and the object it points at. The format is the same as
+ that of linkgit:git-for-each-ref[1]. When unspecified,
+ defaults to `%(refname:strip=2)`.
+
<tagname>::
The name of the tag to create, delete, or describe.
The new tag name must pass all checks defined by
The object that the new tag will refer to, usually a commit.
Defaults to HEAD.
-<format>::
- A string that interpolates `%(fieldname)` from a tag ref being shown
- and the object it points at. The format is the same as
- that of linkgit:git-for-each-ref[1]. When unspecified,
- defaults to `%(refname:strip=2)`.
-
CONFIGURATION
-------------
By default, 'git tag' in sign-with-default mode (-s) will use your
SYNOPSIS
--------
[verse]
-'git update-ref' [-m <reason>] (-d <ref> [<oldvalue>] | [--no-deref] [--create-reflog] <ref> <newvalue> [<oldvalue>] | --stdin [-z])
+'git update-ref' [-m <reason>] [--no-deref] (-d <ref> [<oldvalue>] | [--create-reflog] <ref> <newvalue> [<oldvalue>] | --stdin [-z])
DESCRIPTION
-----------
symbolic refs before creating the log name) describing the change
in ref value. Log lines are formatted as:
- . oldsha1 SP newsha1 SP committer LF
-+
+ oldsha1 SP newsha1 SP committer LF
+
Where "oldsha1" is the 40 character hexadecimal value previously
stored in <ref>, "newsha1" is the 40 character hexadecimal value of
<newvalue> and "committer" is the committer's name, email address
Optionally with -m:
- . oldsha1 SP newsha1 SP committer TAB message LF
-+
+ oldsha1 SP newsha1 SP committer TAB message LF
+
Where all fields are as described above and "message" is the
value supplied to the -m option.
[verse]
'git-upload-pack' [--[no-]strict] [--timeout=<n>] [--stateless-rpc]
[--advertise-refs] <directory>
+
DESCRIPTION
-----------
Invoked by 'git fetch-pack', learns what
--force::
By default, `add` refuses to create a new working tree when
`<commit-ish>` is a branch name and is already checked out by
- another working tree and `remove` refuses to remove an unclean
- working tree. This option overrides these safeguards.
+ another working tree, or if `<path>` is already assigned to some
+ working tree but is missing (for instance, if `<path>` was deleted
+ manually). This option overrides these safeguards. To add a missing but
+ locked working tree path, specify `--force` twice.
++
+`move` refuses to move a locked working tree unless `--force` is specified
+twice.
++
+`remove` refuses to remove an unclean working tree unless `--force` is used.
+To remove a locked working tree, specify `--force` twice.
-b <new-branch>::
-B <new-branch>::
The porcelain format has a line per attribute. Attributes are listed with a
label and value separated by a single space. Boolean attributes (like 'bare'
and 'detached') are listed as a label only, and are only present if and only
-if the value is true. An empty line indicates the end of a worktree. For
-example:
+if the value is true. The first attribute of a worktree is always `worktree`,
+an empty line indicates the end of the record. For example:
------------
$ git worktree list --porcelain
`foo.bar` to the boolean true value (just like `[foo]bar` would in a
config file). Including the equals but with an empty value (like `git -c
foo.bar= ...`) sets `foo.bar` to the empty string which `git config
---bool` will convert to `false`.
+--type=bool` will convert to `false`.
--exec-path[=<path>]::
Path to wherever your core Git programs are installed.
of Git object directories which can be used to search for Git
objects. New objects will not be written to these directories.
+
- Entries that begin with `"` (double-quote) will be interpreted
- as C-style quoted paths, removing leading and trailing
- double-quotes and respecting backslash escapes. E.g., the value
- `"path-with-\"-and-:-in-it":vanilla-path` has two paths:
- `path-with-"-and-:-in-it` and `vanilla-path`.
+Entries that begin with `"` (double-quote) will be interpreted
+as C-style quoted paths, removing leading and trailing
+double-quotes and respecting backslash escapes. E.g., the value
+`"path-with-\"-and-:-in-it":vanilla-path` has two paths:
+`path-with-"-and-:-in-it` and `vanilla-path`.
`GIT_DIR`::
If the `GIT_DIR` environment variable is set then it
+
Alternatively, if the variable is set to an absolute path
(starting with a '/' character), Git will interpret this
-as a file path and will try to write the trace messages
-into it.
+as a file path and will try to append the trace messages
+to it.
+
Unsetting the variable, or setting it to empty, "0" or
"false" (case insensitive) disables trace messages.
Report bugs to the Git mailing list <git@vger.kernel.org> where the
development and maintenance is primarily done. You do not have to be
-subscribed to the list to send a message there.
+subscribed to the list to send a message there. See the list archive
+at https://public-inbox.org/git for previous bug reports and other
+discussions.
Issues which are security relevant should be disclosed privately to
the Git Security mailing list <git-security@googlegroups.com>.
attribute. If you decide to use the `working-tree-encoding` attribute
in your repository, then it is strongly recommended to ensure that all
clients working with the repository support it.
-
- For example, Microsoft Visual Studio resources files (`*.rc`) or
- PowerShell script files (`*.ps1`) are sometimes encoded in UTF-16.
- If you declare `*.ps1` as files as UTF-16 and you add `foo.ps1` with
- a `working-tree-encoding` enabled Git client, then `foo.ps1` will be
- stored as UTF-8 internally. A client without `working-tree-encoding`
- support will checkout `foo.ps1` as UTF-8 encoded file. This will
- typically cause trouble for the users of this file.
-
- If a Git client, that does not support the `working-tree-encoding`
- attribute, adds a new file `bar.ps1`, then `bar.ps1` will be
- stored "as-is" internally (in this example probably as UTF-16).
- A client with `working-tree-encoding` support will interpret the
- internal contents as UTF-8 and try to convert it to UTF-16 on checkout.
- That operation will fail and cause an error.
++
+For example, Microsoft Visual Studio resources files (`*.rc`) or
+PowerShell script files (`*.ps1`) are sometimes encoded in UTF-16.
+If you declare `*.ps1` as files as UTF-16 and you add `foo.ps1` with
+a `working-tree-encoding` enabled Git client, then `foo.ps1` will be
+stored as UTF-8 internally. A client without `working-tree-encoding`
+support will checkout `foo.ps1` as UTF-8 encoded file. This will
+typically cause trouble for the users of this file.
++
+If a Git client, that does not support the `working-tree-encoding`
+attribute, adds a new file `bar.ps1`, then `bar.ps1` will be
+stored "as-is" internally (in this example probably as UTF-16).
+A client with `working-tree-encoding` support will interpret the
+internal contents as UTF-8 and try to convert it to UTF-16 on checkout.
+That operation will fail and cause an error.
- Reencoding content to non-UTF encodings can cause errors as the
conversion might not be UTF-8 round trip safe. If you suspect your
the same domain. Likewise, a config entry for `http://example.com` would not
match: Git compares the protocols exactly.
+If the "pattern" URL does include a path component, then this too must match
+exactly: the context `https://example.com/bar/baz.git` will match a config
+entry for `https://example.com/bar/baz.git` (in addition to matching the config
+entry for `https://example.com`) but will not match a config entry for
+`https://example.com/bar`.
+
CONFIGURATION OPTIONS
---------------------
--- /dev/null
+gitcvs.commitMsgAnnotation::
+ Append this string to each commit message. Set to empty string
+ to disable this feature. Defaults to "via git-CVS emulator".
+
+gitcvs.enabled::
+ Whether the CVS server interface is enabled for this repository.
+ See linkgit:git-cvsserver[1].
+
+gitcvs.logFile::
+ Path to a log file where the CVS server interface well... logs
+ various stuff. See linkgit:git-cvsserver[1].
+
+gitcvs.usecrlfattr::
+ If true, the server will look up the end-of-line conversion
+ attributes for files to determine the `-k` modes to use. If
+ the attributes force Git to treat a file as text,
+ the `-k` mode will be left blank so CVS clients will
+ treat it as text. If they suppress text conversion, the file
+ will be set with '-kb' mode, which suppresses any newline munging
+ the client might otherwise do. If the attributes do not allow
+ the file type to be determined, then `gitcvs.allBinary` is
+ used. See linkgit:gitattributes[5].
+
+gitcvs.allBinary::
+ This is used if `gitcvs.usecrlfattr` does not resolve
+ the correct '-kb' mode to use. If true, all
+ unresolved files are sent to the client in
+ mode '-kb'. This causes the client to treat them
+ as binary files, which suppresses any newline munging it
+ otherwise might do. Alternatively, if it is set to "guess",
+ then the contents of the file are examined to decide if
+ it is binary, similar to `core.autocrlf`.
+
+gitcvs.dbName::
+ Database used by git-cvsserver to cache revision information
+ derived from the Git repository. The exact meaning depends on the
+ used database driver, for SQLite (which is the default driver) this
+ is a filename. Supports variable substitution (see
+ linkgit:git-cvsserver[1] for details). May not contain semicolons (`;`).
+ Default: '%Ggitcvs.%m.sqlite'
+
+gitcvs.dbDriver::
+ Used Perl DBI driver. You can specify any available driver
+ for this here, but it might not work. git-cvsserver is tested
+ with 'DBD::SQLite', reported to work with 'DBD::Pg', and
+ reported *not* to work with 'DBD::mysql'. Experimental feature.
+ May not contain double colons (`:`). Default: 'SQLite'.
+ See linkgit:git-cvsserver[1].
+
+gitcvs.dbUser, gitcvs.dbPass::
+ Database user and password. Only useful if setting `gitcvs.dbDriver`,
+ since SQLite has no concept of database users and/or passwords.
+ 'gitcvs.dbUser' supports variable substitution (see
+ linkgit:git-cvsserver[1] for details).
+
+gitcvs.dbTableNamePrefix::
+ Database table name prefix. Prepended to the names of any
+ database tables used, allowing a single database to be used
+ for several repositories. Supports variable substitution (see
+ linkgit:git-cvsserver[1] for details). Any non-alphabetic
+ characters will be replaced with underscores.
+
+All gitcvs variables except for `gitcvs.usecrlfattr` and
+`gitcvs.allBinary` can also be specified as
+'gitcvs.<access_method>.<varname>' (where 'access_method'
+is one of "ext" and "pserver") to make them apply only for the given
+access method.
submodule.<name>.ignore::
Defines under what circumstances "git status" and the diff family show
a submodule as modified. The following values are supported:
-
++
+--
all;; The submodule will never be considered modified (but will
nonetheless show up in the output of status and commit when it has
been staged).
differences, and modifications to tracked and untracked files are
shown. This is the default option.
- If this option is also present in the submodules entry in .git/config
- of the superproject, the setting there will override the one found in
- .gitmodules.
- Both settings can be overridden on the command line by using the
- "--ignore-submodule" option. The 'git submodule' commands are not
- affected by this setting.
+If this option is also present in the submodules entry in .git/config
+of the superproject, the setting there will override the one found in
+.gitmodules.
+
+Both settings can be overridden on the command line by using the
+"--ignore-submodule" option. The 'git submodule' commands are not
+affected by this setting.
+--
submodule.<name>.shallow::
When set to true, a clone of this submodule will be performed as a
reachable from that commit. For commands that walk the revision graph one can
also specify a range of revisions explicitly.
-In addition, some Git commands (such as linkgit:git-show[1]) also take
-revision parameters which denote other objects than commits, e.g. blobs
-("files") or trees ("directories of files").
+In addition, some Git commands (such as linkgit:git-show[1] and
+linkgit:git-push[1]) can also take revision parameters which denote
+other objects than commits, e.g. blobs ("files") or trees
+("directories of files").
include::revisions.txt[]
A submodule is considered active,
- (a) if `submodule.<name>.active` is set to `true`
- or
- (b) if the submodule's path matches the pathspec in `submodule.active`
- or
- (c) if `submodule.<name>.url` is set.
+ a. if `submodule.<name>.active` is set to `true`
++
+or
+
+ b. if the submodule's path matches the pathspec in `submodule.active`
++
+or
+
+ c. if `submodule.<name>.url` is set.
and these are evaluated in this order.
An example:
- # gitweb configuration file for http://git.example.org
- #
- our $projectroot = "/srv/git"; # FHS recommendation
- our $site_name = 'Example.org >> Repos';
+------------------------------------------------
+# gitweb configuration file for http://git.example.org
+#
+our $projectroot = "/srv/git"; # FHS recommendation
+our $site_name = 'Example.org >> Repos';
+------------------------------------------------
The configuration file is used to override the default settings that
+
For example, the following setting produces a breadcrumb trail like
"home / dev / projects / ..." where "projects" is the home link.
++
----------------------------------------------------------------------------
our @extra_breadcrumbs = (
[ 'home' => 'https://www.example.org/' ],
"zip" snapshots), while allowing individual projects to turn them off, put
the following in your GITWEB_CONFIG file:
- $feature{'blame'}{'default'} = [1];
- $feature{'blame'}{'override'} = 1;
+--------------------------------------------------------------------------------
+$feature{'blame'}{'default'} = [1];
+$feature{'blame'}{'override'} = 1;
- $feature{'pickaxe'}{'default'} = [1];
- $feature{'pickaxe'}{'override'} = 1;
+$feature{'pickaxe'}{'default'} = [1];
+$feature{'pickaxe'}{'override'} = 1;
- $feature{'snapshot'}{'default'} = ['zip', 'tgz'];
- $feature{'snapshot'}{'override'} = 1;
+$feature{'snapshot'}{'default'} = ['zip', 'tgz'];
+$feature{'snapshot'}{'override'} = 1;
+--------------------------------------------------------------------------------
If you allow overriding for the snapshot feature, you can specify which
snapshot formats are globally disabled. You can also add any command-line
--- /dev/null
+gui.commitMsgWidth::
+ Defines how wide the commit message window is in the
+ linkgit:git-gui[1]. "75" is the default.
+
+gui.diffContext::
+ Specifies how many context lines should be used in calls to diff
+ made by the linkgit:git-gui[1]. The default is "5".
+
+gui.displayUntracked::
+ Determines if linkgit:git-gui[1] shows untracked files
+ in the file list. The default is "true".
+
+gui.encoding::
+ Specifies the default encoding to use for displaying of
+ file contents in linkgit:git-gui[1] and linkgit:gitk[1].
+ It can be overridden by setting the 'encoding' attribute
+ for relevant files (see linkgit:gitattributes[5]).
+ If this option is not set, the tools default to the
+ locale encoding.
+
+gui.matchTrackingBranch::
+ Determines if new branches created with linkgit:git-gui[1] should
+ default to tracking remote branches with matching names or
+ not. Default: "false".
+
+gui.newBranchTemplate::
+ Is used as suggested name when creating new branches using the
+ linkgit:git-gui[1].
+
+gui.pruneDuringFetch::
+ "true" if linkgit:git-gui[1] should prune remote-tracking branches when
+ performing a fetch. The default value is "false".
+
+gui.trustmtime::
+ Determines if linkgit:git-gui[1] should trust the file modification
+ timestamp or not. By default the timestamps are not trusted.
+
+gui.spellingDictionary::
+ Specifies the dictionary used for spell checking commit messages in
+ the linkgit:git-gui[1]. When set to "none" spell checking is turned
+ off.
+
+gui.fastCopyBlame::
+ If true, 'git gui blame' uses `-C` instead of `-C -C` for original
+ location detection. It makes blame significantly faster on huge
+ repositories at the expense of less thorough copy detection.
+
+gui.copyBlameThreshold::
+ Specifies the threshold to use in 'git gui blame' original location
+ detection, measured in alphanumeric characters. See the
+ linkgit:git-blame[1] manual for more information on copy detection.
+
+gui.blamehistoryctx::
+ Specifies the radius of history context in days to show in
+ linkgit:gitk[1] for the selected commit, when the `Show History
+ Context` menu item is invoked from 'git gui blame'. If this
+ variable is set to zero, the whole history is shown.
info "The branch '$1' is new..."
else
# updating -- make sure it is a fast-forward
- mb=$(git-merge-base "$2" "$3")
+ mb=$(git merge-base "$2" "$3")
case "$mb,$2" in
"$2,$mb") info "Update is fast-forward" ;;
*) noff=y; info "This is not a fast-forward update.";;
Any other value is treated as a custom merge tool and requires
that a corresponding mergetool.<tool>.cmd variable is defined.
+merge.guitool::
+ Controls which merge tool is used by linkgit:git-mergetool[1] when the
+ -g/--gui flag is specified. The list below shows the valid built-in values.
+ Any other value is treated as a custom merge tool and requires that a
+ corresponding mergetool.<guitool>.cmd variable is defined.
+
include::mergetools-merge.txt[]
merge.verbosity::
--- /dev/null
+pull.ff::
+ By default, Git does not create an extra merge commit when merging
+ a commit that is a descendant of the current commit. Instead, the
+ tip of the current branch is fast-forwarded. When set to `false`,
+ this variable tells Git to create an extra merge commit in such
+ a case (equivalent to giving the `--no-ff` option from the command
+ line). When set to `only`, only such fast-forward merges are
+ allowed (equivalent to giving the `--ff-only` option from the
+ command line). This setting overrides `merge.ff` when pulling.
+
+pull.rebase::
+ When true, rebase branches on top of the fetched branch, instead
+ of merging the default branch from the default remote when "git
+ pull" is run. See "branch.<name>.rebase" for setting this on a
+ per-branch basis.
++
+When `merges`, pass the `--rebase-merges` option to 'git rebase'
+so that the local merge commits are included in the rebase (see
+linkgit:git-rebase[1] for details).
++
+When preserve, also pass `--preserve-merges` along to 'git rebase'
+so that locally committed merge commits will not be flattened
+by running 'git pull'.
++
+When the value is `interactive`, the rebase is run in interactive mode.
++
+*NOTE*: this is a possibly dangerous operation; do *not* use
+it unless you understand the implications (see linkgit:git-rebase[1]
+for details).
+
+pull.octopus::
+ The default merge strategy to use when pulling multiple branches
+ at once.
+
+pull.twohead::
+ The default merge strategy to use when pulling a single branch.
it requests fetching everything up to the given tag.
+
The remote ref that matches <src>
-is fetched, and if <dst> is not an empty string, the local
-ref that matches it is fast-forwarded using <src>.
-If the optional plus `+` is used, the local ref
-is updated even if it does not result in a fast-forward
-update.
+is fetched, and if <dst> is not an empty string, an attempt
+is made to update the local ref that matches it.
++
+Whether that update is allowed without `--force` depends on the ref
+namespace it's being fetched to, the type of object being fetched, and
+whether the update is considered to be a fast-forward. Generally, the
+same rules apply for fetching as when pushing, see the `<refspec>...`
+section of linkgit:git-push[1] for what those are. Exceptions to those
+rules particular to 'git fetch' are noted below.
++
+Until Git version 2.20, and unlike when pushing with
+linkgit:git-push[1], any updates to `refs/tags/*` would be accepted
+without `+` in the refspec (or `--force`). When fetching, we promiscuously
+considered all tag updates from a remote to be forced fetches. Since
+Git version 2.20, fetching to update `refs/tags/*` works the same way
+as when pushing. I.e. any updates will be rejected without `+` in the
+refspec (or `--force`).
++
+Unlike when pushing with linkgit:git-push[1], any updates outside of
+`refs/{tags,heads}/*` will be accepted without `+` in the refspec (or
+`--force`), whether that's swapping e.g. a tree object for a blob, or
+a commit for another commit that's doesn't have the previous commit as
+an ancestor etc.
++
+Unlike when pushing with linkgit:git-push[1], there is no
+configuration which'll amend these rules, and nothing like a
+`pre-fetch` hook analogous to the `pre-receive` hook.
++
+As with pushing with linkgit:git-push[1], all of the rules described
+above about what's not allowed as an update can be overridden by
+adding an the optional leading `+` to a refspec (or using `--force`
+command line option). The only exception to this is that no amount of
+forcing will make the `refs/heads/*` namespace accept a non-commit
+object.
+
[NOTE]
When the remote branch you want to fetch is known to
--- /dev/null
+push.default::
+ Defines the action `git push` should take if no refspec is
+ explicitly given. Different values are well-suited for
+ specific workflows; for instance, in a purely central workflow
+ (i.e. the fetch source is equal to the push destination),
+ `upstream` is probably what you want. Possible values are:
++
+--
+
+* `nothing` - do not push anything (error out) unless a refspec is
+ explicitly given. This is primarily meant for people who want to
+ avoid mistakes by always being explicit.
+
+* `current` - push the current branch to update a branch with the same
+ name on the receiving end. Works in both central and non-central
+ workflows.
+
+* `upstream` - push the current branch back to the branch whose
+ changes are usually integrated into the current branch (which is
+ called `@{upstream}`). This mode only makes sense if you are
+ pushing to the same repository you would normally pull from
+ (i.e. central workflow).
+
+* `tracking` - This is a deprecated synonym for `upstream`.
+
+* `simple` - in centralized workflow, work like `upstream` with an
+ added safety to refuse to push if the upstream branch's name is
+ different from the local one.
++
+When pushing to a remote that is different from the remote you normally
+pull from, work as `current`. This is the safest option and is suited
+for beginners.
++
+This mode has become the default in Git 2.0.
+
+* `matching` - push all branches having the same name on both ends.
+ This makes the repository you are pushing to remember the set of
+ branches that will be pushed out (e.g. if you always push 'maint'
+ and 'master' there and no other branches, the repository you push
+ to will have these two branches, and your local 'maint' and
+ 'master' will be pushed there).
++
+To use this mode effectively, you have to make sure _all_ the
+branches you would push out are ready to be pushed out before
+running 'git push', as the whole point of this mode is to allow you
+to push all of the branches in one go. If you usually finish work
+on only one branch and push out the result, while other branches are
+unfinished, this mode is not for you. Also this mode is not
+suitable for pushing into a shared central repository, as other
+people may add new branches there, or update the tip of existing
+branches outside your control.
++
+This used to be the default, but not since Git 2.0 (`simple` is the
+new default).
+
+--
+
+push.followTags::
+ If set to true enable `--follow-tags` option by default. You
+ may override this configuration at time of push by specifying
+ `--no-follow-tags`.
+
+push.gpgSign::
+ May be set to a boolean value, or the string 'if-asked'. A true
+ value causes all pushes to be GPG signed, as if `--signed` is
+ passed to linkgit:git-push[1]. The string 'if-asked' causes
+ pushes to be signed if the server supports it, as if
+ `--signed=if-asked` is passed to 'git push'. A false value may
+ override a value from a lower-priority config file. An explicit
+ command-line flag always overrides this config option.
+
+push.pushOption::
+ When no `--push-option=<option>` argument is given from the
+ command line, `git push` behaves as if each <value> of
+ this variable is given as `--push-option=<value>`.
++
+This is a multi-valued variable, and an empty value can be used in a
+higher priority configuration file (e.g. `.git/config` in a
+repository) to clear the values inherited from a lower priority
+configuration files (e.g. `$HOME/.gitconfig`).
++
+--
+
+Example:
+
+/etc/gitconfig
+ push.pushoption = a
+ push.pushoption = b
+
+~/.gitconfig
+ push.pushoption = c
+
+repo/.git/config
+ push.pushoption =
+ push.pushoption = b
+
+This will result in only b (a and c are cleared).
+
+--
+
+push.recurseSubmodules::
+ Make sure all submodule commits used by the revisions to be pushed
+ are available on a remote-tracking branch. If the value is 'check'
+ then Git will verify that all submodule commits that changed in the
+ revisions to be pushed are available on at least one remote of the
+ submodule. If any commits are missing, the push will be aborted and
+ exit with non-zero status. If the value is 'on-demand' then all
+ submodules that changed in the revisions to be pushed will be
+ pushed. If on-demand was not able to push all necessary revisions
+ it will also be aborted and exit with non-zero status. If the value
+ is 'no' then default behavior of ignoring submodules when pushing
+ is retained. You may override this configuration at time of push by
+ specifying '--recurse-submodules=check|on-demand|no'.
--- /dev/null
+receive.advertiseAtomic::
+ By default, git-receive-pack will advertise the atomic push
+ capability to its clients. If you don't want to advertise this
+ capability, set this variable to false.
+
+receive.advertisePushOptions::
+ When set to true, git-receive-pack will advertise the push options
+ capability to its clients. False by default.
+
+receive.autogc::
+ By default, git-receive-pack will run "git-gc --auto" after
+ receiving data from git-push and updating refs. You can stop
+ it by setting this variable to false.
+
+receive.certNonceSeed::
+ By setting this variable to a string, `git receive-pack`
+ will accept a `git push --signed` and verifies it by using
+ a "nonce" protected by HMAC using this string as a secret
+ key.
+
+receive.certNonceSlop::
+ When a `git push --signed` sent a push certificate with a
+ "nonce" that was issued by a receive-pack serving the same
+ repository within this many seconds, export the "nonce"
+ found in the certificate to `GIT_PUSH_CERT_NONCE` to the
+ hooks (instead of what the receive-pack asked the sending
+ side to include). This may allow writing checks in
+ `pre-receive` and `post-receive` a bit easier. Instead of
+ checking `GIT_PUSH_CERT_NONCE_SLOP` environment variable
+ that records by how many seconds the nonce is stale to
+ decide if they want to accept the certificate, they only
+ can check `GIT_PUSH_CERT_NONCE_STATUS` is `OK`.
+
+receive.fsckObjects::
+ If it is set to true, git-receive-pack will check all received
+ objects. See `transfer.fsckObjects` for what's checked.
+ Defaults to false. If not set, the value of
+ `transfer.fsckObjects` is used instead.
+
+receive.fsck.<msg-id>::
+ Acts like `fsck.<msg-id>`, but is used by
+ linkgit:git-receive-pack[1] instead of
+ linkgit:git-fsck[1]. See the `fsck.<msg-id>` documentation for
+ details.
+
+receive.fsck.skipList::
+ Acts like `fsck.skipList`, but is used by
+ linkgit:git-receive-pack[1] instead of
+ linkgit:git-fsck[1]. See the `fsck.skipList` documentation for
+ details.
+
+receive.keepAlive::
+ After receiving the pack from the client, `receive-pack` may
+ produce no output (if `--quiet` was specified) while processing
+ the pack, causing some networks to drop the TCP connection.
+ With this option set, if `receive-pack` does not transmit
+ any data in this phase for `receive.keepAlive` seconds, it will
+ send a short keepalive packet. The default is 5 seconds; set
+ to 0 to disable keepalives entirely.
+
+receive.unpackLimit::
+ If the number of objects received in a push is below this
+ limit then the objects will be unpacked into loose object
+ files. However if the number of received objects equals or
+ exceeds this limit then the received pack will be stored as
+ a pack, after adding any missing delta bases. Storing the
+ pack from a push can make the push operation complete faster,
+ especially on slow filesystems. If not set, the value of
+ `transfer.unpackLimit` is used instead.
+
+receive.maxInputSize::
+ If the size of the incoming pack stream is larger than this
+ limit, then git-receive-pack will error out, instead of
+ accepting the pack file. If not set or set to 0, then the size
+ is unlimited.
+
+receive.denyDeletes::
+ If set to true, git-receive-pack will deny a ref update that deletes
+ the ref. Use this to prevent such a ref deletion via a push.
+
+receive.denyDeleteCurrent::
+ If set to true, git-receive-pack will deny a ref update that
+ deletes the currently checked out branch of a non-bare repository.
+
+receive.denyCurrentBranch::
+ If set to true or "refuse", git-receive-pack will deny a ref update
+ to the currently checked out branch of a non-bare repository.
+ Such a push is potentially dangerous because it brings the HEAD
+ out of sync with the index and working tree. If set to "warn",
+ print a warning of such a push to stderr, but allow the push to
+ proceed. If set to false or "ignore", allow such pushes with no
+ message. Defaults to "refuse".
++
+Another option is "updateInstead" which will update the working
+tree if pushing into the current branch. This option is
+intended for synchronizing working directories when one side is not easily
+accessible via interactive ssh (e.g. a live web site, hence the requirement
+that the working directory be clean). This mode also comes in handy when
+developing inside a VM to test and fix code on different Operating Systems.
++
+By default, "updateInstead" will refuse the push if the working tree or
+the index have any difference from the HEAD, but the `push-to-checkout`
+hook can be used to customize this. See linkgit:githooks[5].
+
+receive.denyNonFastForwards::
+ If set to true, git-receive-pack will deny a ref update which is
+ not a fast-forward. Use this to prevent such an update via a push,
+ even if that push is forced. This configuration variable is
+ set when initializing a shared repository.
+
+receive.hideRefs::
+ This variable is the same as `transfer.hideRefs`, but applies
+ only to `receive-pack` (and so affects pushes, but not fetches).
+ An attempt to update or delete a hidden ref by `git push` is
+ rejected.
+
+receive.updateServerInfo::
+ If set to true, git-receive-pack will run git-update-server-info
+ after receiving data from git-push and updating refs.
+
+receive.shallowUpdate::
+ If set to true, .git/shallow can be updated when new refs
+ require new shallow roots. Otherwise those refs are rejected.
+
The form '--filter=sparse:path=<path>' similarly uses a sparse-checkout
specification contained in <path>.
++
+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.
--no-filter::
Turn off any previous `--filter=` argument.
--- /dev/null
+sendemail.identity::
+ A configuration identity. When given, causes values in the
+ 'sendemail.<identity>' subsection to take precedence over
+ values in the 'sendemail' section. The default identity is
+ the value of `sendemail.identity`.
+
+sendemail.smtpEncryption::
+ See linkgit:git-send-email[1] for description. Note that this
+ setting is not subject to the 'identity' mechanism.
+
+sendemail.smtpssl (deprecated)::
+ Deprecated alias for 'sendemail.smtpEncryption = ssl'.
+
+sendemail.smtpsslcertpath::
+ Path to ca-certificates (either a directory or a single file).
+ Set it to an empty string to disable certificate verification.
+
+sendemail.<identity>.*::
+ Identity-specific versions of the 'sendemail.*' parameters
+ found below, taking precedence over those when this
+ identity is selected, through either the command-line or
+ `sendemail.identity`.
+
+sendemail.aliasesFile::
+sendemail.aliasFileType::
+sendemail.annotate::
+sendemail.bcc::
+sendemail.cc::
+sendemail.ccCmd::
+sendemail.chainReplyTo::
+sendemail.confirm::
+sendemail.envelopeSender::
+sendemail.from::
+sendemail.multiEdit::
+sendemail.signedoffbycc::
+sendemail.smtpPass::
+sendemail.suppresscc::
+sendemail.suppressFrom::
+sendemail.to::
+sendemail.tocmd::
+sendemail.smtpDomain::
+sendemail.smtpServer::
+sendemail.smtpServerPort::
+sendemail.smtpServerOption::
+sendemail.smtpUser::
+sendemail.thread::
+sendemail.transferEncoding::
+sendemail.validate::
+sendemail.xmailer::
+ See linkgit:git-send-email[1] for description.
+
+sendemail.signedoffcc (deprecated)::
+ Deprecated alias for `sendemail.signedoffbycc`.
+
+sendemail.smtpBatchSize::
+ Number of messages to be sent per connection, after that a relogin
+ will happen. If the value is 0 or undefined, send all messages in
+ one connection.
+ See also the `--batch-size` option of linkgit:git-send-email[1].
+
+sendemail.smtpReloginDelay::
+ Seconds wait before reconnecting to smtp server.
+ See also the `--relogin-delay` option of linkgit:git-send-email[1].
--- /dev/null
+submodule.<name>.url::
+ The URL for a submodule. This variable is copied from the .gitmodules
+ file to the git config via 'git submodule init'. The user can change
+ the configured URL before obtaining the submodule via 'git submodule
+ update'. If neither submodule.<name>.active or submodule.active are
+ set, the presence of this variable is used as a fallback to indicate
+ whether the submodule is of interest to git commands.
+ See linkgit:git-submodule[1] and linkgit:gitmodules[5] for details.
+
+submodule.<name>.update::
+ The method by which a submodule is updated by 'git submodule update',
+ which is the only affected command, others such as
+ 'git checkout --recurse-submodules' are unaffected. It exists for
+ historical reasons, when 'git submodule' was the only command to
+ interact with submodules; settings like `submodule.active`
+ and `pull.rebase` are more specific. It is populated by
+ `git submodule init` from the linkgit:gitmodules[5] file.
+ See description of 'update' command in linkgit:git-submodule[1].
+
+submodule.<name>.branch::
+ The remote branch name for a submodule, used by `git submodule
+ update --remote`. Set this option to override the value found in
+ the `.gitmodules` file. See linkgit:git-submodule[1] and
+ linkgit:gitmodules[5] for details.
+
+submodule.<name>.fetchRecurseSubmodules::
+ This option can be used to control recursive fetching of this
+ submodule. It can be overridden by using the --[no-]recurse-submodules
+ command-line option to "git fetch" and "git pull".
+ This setting will override that from in the linkgit:gitmodules[5]
+ file.
+
+submodule.<name>.ignore::
+ Defines under what circumstances "git status" and the diff family show
+ a submodule as modified. When set to "all", it will never be considered
+ modified (but it will nonetheless show up in the output of status and
+ commit when it has been staged), "dirty" will ignore all changes
+ to the submodules work tree and
+ takes only differences between the HEAD of the submodule and the commit
+ recorded in the superproject into account. "untracked" will additionally
+ let submodules with modified tracked files in their work tree show up.
+ Using "none" (the default when this option is not set) also shows
+ submodules that have untracked files in their work tree as changed.
+ This setting overrides any setting made in .gitmodules for this submodule,
+ both settings can be overridden on the command line by using the
+ "--ignore-submodules" option. The 'git submodule' commands are not
+ affected by this setting.
+
+submodule.<name>.active::
+ Boolean value indicating if the submodule is of interest to git
+ commands. This config option takes precedence over the
+ submodule.active config option. See linkgit:gitsubmodules[7] for
+ details.
+
+submodule.active::
+ A repeated field which contains a pathspec used to match against a
+ submodule's path to determine if the submodule is of interest to git
+ commands. See linkgit:gitsubmodules[7] for details.
+
+submodule.recurse::
+ Specifies if commands recurse into submodules by default. This
+ applies to all commands that have a `--recurse-submodules` option,
+ except `clone`.
+ Defaults to false.
+
+submodule.fetchJobs::
+ Specifies how many submodules are fetched/cloned at the same time.
+ A positive integer allows up to that number of submodules fetched
+ in parallel. A value of 0 will give some reasonable default.
+ If unset, it defaults to 1.
+
+submodule.alternateLocation::
+ Specifies how the submodules obtain alternates when submodules are
+ cloned. Possible values are `no`, `superproject`.
+ By default `no` is assumed, which doesn't add references. When the
+ value is set to `superproject` the submodule to be cloned computes
+ its alternates location relative to the superprojects alternate.
+
+submodule.alternateErrorStrategy::
+ Specifies how to treat errors with the alternates for a submodule
+ as computed via `submodule.alternateLocation`. Possible values are
+ `ignore`, `info`, `die`. Default is `die`.
----------------
* Prepare `struct diff_options` to record the set of diff options, and
- then call `diff_setup()` to initialize this structure. This sets up
- the vanilla default.
+ then call `repo_diff_setup()` to initialize this structure. This
+ sets up the vanilla default.
* Fill in the options structure to specify desired output format, rename
detection, etc. `diff_opt_parse()` can be used to parse options given
it is invoked.
* For each commit, call `graph_next_line()` repeatedly, until
- `graph_is_commit_finished()` returns non-zero. Each call go
+ `graph_is_commit_finished()` returns non-zero. Each call to
`graph_next_line()` will output a single line of the graph. The resulting
lines will not contain any newlines. `graph_next_line()` returns 1 if the
resulting line contains the current commit, or 0 if this is merely a line
struct git_graph *graph = graph_init(opts);
while ((commit = get_revision(opts)) != NULL) {
- graph_update(graph, commit);
while (!graph_is_commit_finished(graph))
{
struct strbuf sb;
Functions
---------
-`init_revisions`::
+`repo_init_revisions`::
- Initialize a rev_info structure with default values. The second
+ Initialize a rev_info structure with default values. The third
parameter may be NULL or can be prefix path, and then the `.prefix`
variable will be set to it. This is typically the first function you
want to call when you want to deal with a revision list. After calling
1. Decompressing and parsing commits.
2. Walking the entire graph to satisfy topological order constraints.
-The commit graph file is a supplemental data structure that accelerates
+The commit-graph file is a supplemental data structure that accelerates
commit graph walks. If a user downgrades or disables the 'core.commitGraph'
config setting, then the existing ODB is sufficient. The file is stored
as "commit-graph" either in the .git/objects/info directory or in the info
directory of an alternate.
-The commit graph file stores the commit graph structure along with some
+The commit-graph file stores the commit graph structure along with some
extra metadata to speed up graph walks. By listing commit OIDs in lexi-
cographic order, we can identify an integer position for each commit and
refer to the parents of a commit using those integer positions. We use
Design Details
--------------
-- The commit graph file is stored in a file named 'commit-graph' in the
+- The commit-graph file is stored in a file named 'commit-graph' in the
.git/objects/info directory. This could be stored in the info directory
of an alternate.
- The file format includes parameters for the object ID hash function,
so a future change of hash algorithm does not require a change in format.
+- Commit grafts and replace objects can change the shape of the commit
+ history. The latter can also be enabled/disabled on the fly using
+ `--no-replace-objects`. This leads to difficultly storing both possible
+ interpretations of a commit id, especially when computing generation
+ numbers. The commit-graph will not be read or written when
+ replace-objects or grafts are present.
+
+- Shallow clones create grafts of commits by dropping their parents. This
+ leads the commit-graph to think those commits have generation number 1.
+ If and when those commits are made unshallow, those generation numbers
+ become invalid. Since shallow clones are intended to restrict the commit
+ history to a very small set of commits, the commit-graph feature is less
+ helpful for these clones, anyway. The commit-graph will not be read or
+ written when shallow commits are present.
+
Future Work
-----------
-- The commit graph feature currently does not honor commit grafts. This can
- be remedied by duplicating or refactoring the current graft logic.
-
- After computing and storing generation numbers, we must make graph
walks aware of generation numbers to gain the performance benefits they
enable. This will mostly be accomplished by swapping a commit-date-ordered
- 'log --topo-order'
- 'tag --merged'
-- A server could provide a commit graph file as part of the network protocol
+- A server could provide a commit-graph file as part of the network protocol
to avoid extra calculations by clients. This feature is only of benefit if
the user is willing to trust the file, because verifying the file is correct
is as hard as computing it from scratch.
- An ewah bitmap, the n-th bit indicates whether the n-th index entry
is not CE_FSMONITOR_VALID.
+
+== End of Index Entry
+
+ The End of Index Entry (EOIE) is used to locate the end of the variable
+ length index entries and the begining of the extensions. Code can take
+ advantage of this to quickly locate the index extensions without having
+ to parse through all of the index entries.
+
+ Because it must be able to be loaded before the variable length cache
+ entries and other index extensions, this extension must be written last.
+ The signature for this extension is { 'E', 'O', 'I', 'E' }.
+
+ The extension consists of:
+
+ - 32-bit offset to the end of the index entries
+
+ - 160-bit SHA-1 over the extension types and their sizes (but not
+ their contents). E.g. if we have "TREE" extension that is N-bytes
+ long, "REUC" extension that is M-bytes long, followed by "EOIE",
+ then the hash would be:
+
+ SHA-1("TREE" + <binary representation of N> +
+ "REUC" + <binary representation of M>)
+
+== Index Entry Offset Table
+
+ The Index Entry Offset Table (IEOT) is used to help address the CPU
+ cost of loading the index by enabling multi-threading the process of
+ converting cache entries from the on-disk format to the in-memory format.
+ The signature for this extension is { 'I', 'E', 'O', 'T' }.
+
+ The extension consists of:
+
+ - 32-bit version (currently 1)
+
+ - A number of index offset entries each consisting of:
+
+ - 32-bit offset from the begining of the file to the first cache entry
+ in this block of entries.
+
+ - 32-bit count of cache entries in this block
--- /dev/null
+Multi-Pack-Index (MIDX) Design Notes
+====================================
+
+The Git object directory contains a 'pack' directory containing
+packfiles (with suffix ".pack") and pack-indexes (with suffix
+".idx"). The pack-indexes provide a way to lookup objects and
+navigate to their offset within the pack, but these must come
+in pairs with the packfiles. This pairing depends on the file
+names, as the pack-index differs only in suffix with its pack-
+file. While the pack-indexes provide fast lookup per packfile,
+this performance degrades as the number of packfiles increases,
+because abbreviations need to inspect every packfile and we are
+more likely to have a miss on our most-recently-used packfile.
+For some large repositories, repacking into a single packfile
+is not feasible due to storage space or excessive repack times.
+
+The multi-pack-index (MIDX for short) stores a list of objects
+and their offsets into multiple packfiles. It contains:
+
+- A list of packfile names.
+- A sorted list of object IDs.
+- A list of metadata for the ith object ID including:
+ - A value j referring to the jth packfile.
+ - An offset within the jth packfile for the object.
+- If large offsets are required, we use another list of large
+ offsets similar to version 2 pack-indexes.
+
+Thus, we can provide O(log N) lookup time for any number
+of packfiles.
+
+Design Details
+--------------
+
+- The MIDX is stored in a file named 'multi-pack-index' in the
+ .git/objects/pack directory. This could be stored in the pack
+ directory of an alternate. It refers only to packfiles in that
+ same directory.
+
+- The pack.multiIndex config setting must be on to consume MIDX files.
+
+- The file format includes parameters for the object ID hash
+ function, so a future change of hash algorithm does not require
+ a change in format.
+
+- The MIDX keeps only one record per object ID. If an object appears
+ in multiple packfiles, then the MIDX selects the copy in the most-
+ recently modified packfile.
+
+- If there exist packfiles in the pack directory not registered in
+ the MIDX, then those packfiles are loaded into the `packed_git`
+ list and `packed_git_mru` cache.
+
+- The pack-indexes (.idx files) remain in the pack directory so we
+ can delete the MIDX file, set core.midx to false, or downgrade
+ without any loss of information.
+
+- The MIDX file format uses a chunk-based approach (similar to the
+ commit-graph file) that allows optional data to be added.
+
+Future Work
+-----------
+
+- Add a 'verify' subcommand to the 'git midx' builtin to verify the
+ contents of the multi-pack-index file match the offsets listed in
+ the corresponding pack-indexes.
+
+- The multi-pack-index allows many packfiles, especially in a context
+ where repacking is expensive (such as a very large repo), or
+ unexpected maintenance time is unacceptable (such as a high-demand
+ build machine). However, the multi-pack-index needs to be rewritten
+ in full every time. We can extend the format to be incremental, so
+ writes are fast. By storing a small "tip" multi-pack-index that
+ points to large "base" MIDX files, we can keep writes fast while
+ still reducing the number of binary searches required for object
+ lookups.
+
+- The reachability bitmap is currently paired directly with a single
+ packfile, using the pack-order as the object order to hopefully
+ compress the bitmaps well using run-length encoding. This could be
+ extended to pair a reachability bitmap with a multi-pack-index. If
+ the multi-pack-index is extended to store a "stable object order"
+ (a function Order(hash) = integer that is constant for a given hash,
+ even as the multi-pack-index is updated) then a reachability bitmap
+ could point to a multi-pack-index and be updated independently.
+
+- Packfiles can be marked as "special" using empty files that share
+ the initial name but replace ".pack" with ".keep" or ".promisor".
+ We can add an optional chunk of data to the multi-pack-index that
+ records flags of information about the packfiles. This allows new
+ states, such as 'repacked' or 'redeltified', that can help with
+ pack maintenance in a multi-pack environment. It may also be
+ helpful to organize packfiles by object type (commit, tree, blob,
+ etc.) and use this metadata to help that maintenance.
+
+- The partial clone feature records special "promisor" packs that
+ may point to objects that are not stored locally, but available
+ on request to a server. The multi-pack-index does not currently
+ track these promisor packs.
+
+Related Links
+-------------
+[0] https://bugs.chromium.org/p/git/issues/detail?id=6
+ Chromium work item for: Multi-Pack Index (MIDX)
+
+[1] https://public-inbox.org/git/20180107181459.222909-1-dstolee@microsoft.com/
+ An earlier RFC for the multi-pack-index feature
+
+[2] https://public-inbox.org/git/alpine.DEB.2.20.1803091557510.23109@alexmv-linux/
+ Git Merge 2018 Contributor's summit notes (includes discussion of MIDX)
corresponding packfile.
20-byte SHA-1-checksum of all of the above.
+
+== multi-pack-index (MIDX) files have the following format:
+
+The multi-pack-index files refer to multiple pack-files and loose objects.
+
+In order to allow extensions that add extra data to the MIDX, we organize
+the body into "chunks" and provide a lookup table at the beginning of the
+body. The header includes certain length values, such as the number of packs,
+the number of base MIDX files, hash lengths and types.
+
+All 4-byte numbers are in network order.
+
+HEADER:
+
+ 4-byte signature:
+ The signature is: {'M', 'I', 'D', 'X'}
+
+ 1-byte version number:
+ Git only writes or recognizes version 1.
+
+ 1-byte Object Id Version
+ Git only writes or recognizes version 1 (SHA1).
+
+ 1-byte number of "chunks"
+
+ 1-byte number of base multi-pack-index files:
+ This value is currently always zero.
+
+ 4-byte number of pack files
+
+CHUNK LOOKUP:
+
+ (C + 1) * 12 bytes providing the chunk offsets:
+ First 4 bytes describe chunk id. Value 0 is a terminating label.
+ Other 8 bytes provide offset in current file for chunk to start.
+ (Chunks are provided in file-order, so you can infer the length
+ using the next chunk position if necessary.)
+
+ The remaining data in the body is described one chunk at a time, and
+ these chunks may be given in any order. Chunks are required unless
+ otherwise specified.
+
+CHUNK DATA:
+
+ Packfile Names (ID: {'P', 'N', 'A', 'M'})
+ Stores the packfile names as concatenated, null-terminated strings.
+ Packfiles must be listed in lexicographic order for fast lookups by
+ name. This is the only chunk not guaranteed to be a multiple of four
+ bytes in length, so should be the last chunk for alignment reasons.
+
+ OID Fanout (ID: {'O', 'I', 'D', 'F'})
+ The ith entry, F[i], stores the number of OIDs with first
+ byte at most i. Thus F[255] stores the total
+ number of objects.
+
+ OID Lookup (ID: {'O', 'I', 'D', 'L'})
+ The OIDs for all objects in the MIDX are stored in lexicographic
+ order in this chunk.
+
+ Object Offsets (ID: {'O', 'O', 'F', 'F'})
+ Stores two 4-byte values for every object.
+ 1: The pack-int-id for the pack storing this object.
+ 2: The offset within the pack.
+ If all offsets are less than 2^31, then the large offset chunk
+ will not exist and offsets are stored as in IDX v1.
+ If there is at least one offset value larger than 2^32-1, then
+ the large offset chunk must exist. If the large offset chunk
+ exists and the 31st bit is on, then removing that bit reveals
+ the row in the large offsets containing the 8-byte offset of
+ this object.
+
+ [Optional] Object Large Offsets (ID: {'L', 'O', 'F', 'F'})
+ 8-byte offsets into large packfiles.
+
+TRAILER:
+
+ 20-byte SHA1-checksum of the above contents.
--- /dev/null
+Rerere
+======
+
+This document describes the rerere logic.
+
+Conflict normalization
+----------------------
+
+To ensure recorded conflict resolutions can be looked up in the rerere
+database, even when branches are merged in a different order,
+different branches are merged that result in the same conflict, or
+when different conflict style settings are used, rerere normalizes the
+conflicts before writing them to the rerere database.
+
+Different conflict styles and branch names are normalized by stripping
+the labels from the conflict markers, and removing the common ancestor
+version from the `diff3` conflict style. Branches that are merged
+in different order are normalized by sorting the conflict hunks. More
+on each of those steps in the following sections.
+
+Once these two normalization operations are applied, a conflict ID is
+calculated based on the normalized conflict, which is later used by
+rerere to look up the conflict in the rerere database.
+
+Removing the common ancestor version
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Say we have three branches AB, AC and AC2. The common ancestor of
+these branches has a file with a line containing the string "A" (for
+brevity this is called "line A" in the rest of the document). In
+branch AB this line is changed to "B", in AC, this line is changed to
+"C", and branch AC2 is forked off of AC, after the line was changed to
+"C".
+
+Forking a branch ABAC off of branch AB and then merging AC into it, we
+get a conflict like the following:
+
+ <<<<<<< HEAD
+ B
+ =======
+ C
+ >>>>>>> AC
+
+Doing the analogous with AC2 (forking a branch ABAC2 off of branch AB
+and then merging branch AC2 into it), using the diff3 conflict style,
+we get a conflict like the following:
+
+ <<<<<<< HEAD
+ B
+ ||||||| merged common ancestors
+ A
+ =======
+ C
+ >>>>>>> AC2
+
+By resolving this conflict, to leave line D, the user declares:
+
+ After examining what branches AB and AC did, I believe that making
+ line A into line D is the best thing to do that is compatible with
+ what AB and AC wanted to do.
+
+As branch AC2 refers to the same commit as AC, the above implies that
+this is also compatible what AB and AC2 wanted to do.
+
+By extension, this means that rerere should recognize that the above
+conflicts are the same. To do this, the labels on the conflict
+markers are stripped, and the common ancestor version is removed. The above
+examples would both result in the following normalized conflict:
+
+ <<<<<<<
+ B
+ =======
+ C
+ >>>>>>>
+
+Sorting hunks
+~~~~~~~~~~~~~
+
+As before, lets imagine that a common ancestor had a file with line A
+its early part, and line X in its late part. And then four branches
+are forked that do these things:
+
+ - AB: changes A to B
+ - AC: changes A to C
+ - XY: changes X to Y
+ - XZ: changes X to Z
+
+Now, forking a branch ABAC off of branch AB and then merging AC into
+it, and forking a branch ACAB off of branch AC and then merging AB
+into it, would yield the conflict in a different order. The former
+would say "A became B or C, what now?" while the latter would say "A
+became C or B, what now?"
+
+As a reminder, the act of merging AC into ABAC and resolving the
+conflict to leave line D means that the user declares:
+
+ After examining what branches AB and AC did, I believe that
+ making line A into line D is the best thing to do that is
+ compatible with what AB and AC wanted to do.
+
+So the conflict we would see when merging AB into ACAB should be
+resolved the same way---it is the resolution that is in line with that
+declaration.
+
+Imagine that similarly previously a branch XYXZ was forked from XY,
+and XZ was merged into it, and resolved "X became Y or Z" into "X
+became W".
+
+Now, if a branch ABXY was forked from AB and then merged XY, then ABXY
+would have line B in its early part and line Y in its later part.
+Such a merge would be quite clean. We can construct 4 combinations
+using these four branches ((AB, AC) x (XY, XZ)).
+
+Merging ABXY and ACXZ would make "an early A became B or C, a late X
+became Y or Z" conflict, while merging ACXY and ABXZ would make "an
+early A became C or B, a late X became Y or Z". We can see there are
+4 combinations of ("B or C", "C or B") x ("X or Y", "Y or X").
+
+By sorting, the conflict is given its canonical name, namely, "an
+early part became B or C, a late part becames X or Y", and whenever
+any of these four patterns appear, and we can get to the same conflict
+and resolution that we saw earlier.
+
+Without the sorting, we'd have to somehow find a previous resolution
+from combinatorial explosion.
+
+Conflict ID calculation
+~~~~~~~~~~~~~~~~~~~~~~~
+
+Once the conflict normalization is done, the conflict ID is calculated
+as the sha1 hash of the conflict hunks appended to each other,
+separated by <NUL> characters. The conflict markers are stripped out
+before the sha1 is calculated. So in the example above, where we
+merge branch AC which changes line A to line C, into branch AB, which
+changes line A to line C, the conflict ID would be
+SHA1('B<NUL>C<NUL>').
+
+If there are multiple conflicts in one file, the sha1 is calculated
+the same way with all hunks appended to each other, in the order in
+which they appear in the file, separated by a <NUL> character.
+
+Nested conflicts
+~~~~~~~~~~~~~~~~
+
+Nested conflicts are handled very similarly to "simple" conflicts.
+Similar to simple conflicts, the conflict is first normalized by
+stripping the labels from conflict markers, stripping the common ancestor
+version, and the sorting the conflict hunks, both for the outer and the
+inner conflict. This is done recursively, so any number of nested
+conflicts can be handled.
+
+Note that this only works for conflict markers that "cleanly nest". If
+there are any unmatched conflict markers, rerere will fail to handle
+the conflict and record a conflict resolution.
+
+The only difference is in how the conflict ID is calculated. For the
+inner conflict, the conflict markers themselves are not stripped out
+before calculating the sha1.
+
+Say we have the following conflict for example:
+
+ <<<<<<< HEAD
+ 1
+ =======
+ <<<<<<< HEAD
+ 3
+ =======
+ 2
+ >>>>>>> branch-2
+ >>>>>>> branch-3~
+
+After stripping out the labels of the conflict markers, and sorting
+the hunks, the conflict would look as follows:
+
+ <<<<<<<
+ 1
+ =======
+ <<<<<<<
+ 2
+ =======
+ 3
+ >>>>>>>
+ >>>>>>>
+
+and finally the conflict ID would be calculated as:
+`sha1('1<NUL><<<<<<<\n3\n=======\n2\n>>>>>>><NUL>')`
#!/bin/sh
GVF=GIT-VERSION-FILE
-DEF_VER=v2.19.1
+DEF_VER=v2.19.GIT
LF='
'
# (defaults to "man") if you want to have a different default when
# "git help" is called without a parameter specifying the format.
#
-# Define TEST_GIT_INDEX_VERSION to 2, 3 or 4 to run the test suite
+# Define GIT_TEST_INDEX_VERSION to 2, 3 or 4 to run the test suite
# with a different indexfile format version. If it isn't set the index
# file format used is index-v[23].
#
VCSSVN_OBJS =
GENERATED_H =
EXTRA_CPPFLAGS =
+FUZZ_OBJS =
+FUZZ_PROGRAMS =
LIB_OBJS =
PROGRAM_OBJS =
PROGRAMS =
ETAGS_TARGET = TAGS
+FUZZ_OBJS += fuzz-pack-headers.o
+FUZZ_OBJS += fuzz-pack-idx.o
+
+# Always build fuzz objects even if not testing, to prevent bit-rot.
+all:: $(FUZZ_OBJS)
+
+FUZZ_PROGRAMS += $(patsubst %.o,%,$(FUZZ_OBJS))
+
# Empty...
EXTRA_PROGRAMS =
TEST_BUILTINS_OBJS += test-delta.o
TEST_BUILTINS_OBJS += test-drop-caches.o
TEST_BUILTINS_OBJS += test-dump-cache-tree.o
+TEST_BUILTINS_OBJS += test-dump-fsmonitor.o
TEST_BUILTINS_OBJS += test-dump-split-index.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-hashmap.o
TEST_BUILTINS_OBJS += test-mergesort.o
TEST_BUILTINS_OBJS += test-mktemp.o
TEST_BUILTINS_OBJS += test-online-cpus.o
+TEST_BUILTINS_OBJS += test-parse-options.o
TEST_BUILTINS_OBJS += test-path-utils.o
+TEST_BUILTINS_OBJS += test-pkt-line.o
TEST_BUILTINS_OBJS += test-prio-queue.o
+TEST_BUILTINS_OBJS += test-reach.o
TEST_BUILTINS_OBJS += test-read-cache.o
+TEST_BUILTINS_OBJS += test-read-midx.o
TEST_BUILTINS_OBJS += test-ref-store.o
TEST_BUILTINS_OBJS += test-regex.o
TEST_BUILTINS_OBJS += test-repository.o
TEST_BUILTINS_OBJS += test-revision-walking.o
TEST_BUILTINS_OBJS += test-run-command.o
TEST_BUILTINS_OBJS += test-scrap-cache-tree.o
-TEST_BUILTINS_OBJS += test-sha1-array.o
TEST_BUILTINS_OBJS += test-sha1.o
+TEST_BUILTINS_OBJS += test-sha1-array.o
TEST_BUILTINS_OBJS += test-sigchain.o
TEST_BUILTINS_OBJS += test-strcmp-offset.o
TEST_BUILTINS_OBJS += test-string-list.o
TEST_BUILTINS_OBJS += test-subprocess.o
TEST_BUILTINS_OBJS += test-urlmatch-normalization.o
TEST_BUILTINS_OBJS += test-wildmatch.o
+TEST_BUILTINS_OBJS += test-windows-named-pipe.o
TEST_BUILTINS_OBJS += test-write-cache.o
-TEST_PROGRAMS_NEED_X += test-dump-fsmonitor
-TEST_PROGRAMS_NEED_X += test-dump-untracked-cache
+# Do not add more tests here unless they have extra dependencies. Add
+# them in TEST_BUILTINS_OBJS above.
TEST_PROGRAMS_NEED_X += test-fake-ssh
TEST_PROGRAMS_NEED_X += test-line-buffer
-TEST_PROGRAMS_NEED_X += test-parse-options
-TEST_PROGRAMS_NEED_X += test-pkt-line
TEST_PROGRAMS_NEED_X += test-svn-fe
TEST_PROGRAMS_NEED_X += test-tool
LIB_OBJS += combine-diff.o
LIB_OBJS += commit.o
LIB_OBJS += commit-graph.o
+LIB_OBJS += commit-reach.o
LIB_OBJS += compat/obstack.o
LIB_OBJS += compat/terminal.o
LIB_OBJS += config.o
LIB_OBJS += ctype.o
LIB_OBJS += date.o
LIB_OBJS += decorate.o
+LIB_OBJS += delta-islands.o
LIB_OBJS += diffcore-break.o
LIB_OBJS += diffcore-delta.o
LIB_OBJS += diffcore-order.o
LIB_OBJS += help.o
LIB_OBJS += hex.o
LIB_OBJS += ident.o
+LIB_OBJS += interdiff.o
LIB_OBJS += json-writer.o
LIB_OBJS += kwset.o
LIB_OBJS += levenshtein.o
LIB_OBJS += merge-blobs.o
LIB_OBJS += merge-recursive.o
LIB_OBJS += mergesort.o
+LIB_OBJS += midx.o
LIB_OBJS += name-hash.o
LIB_OBJS += negotiator/default.o
LIB_OBJS += negotiator/skipping.o
BUILTIN_OBJS += builtin/merge-tree.o
BUILTIN_OBJS += builtin/mktag.o
BUILTIN_OBJS += builtin/mktree.o
+BUILTIN_OBJS += builtin/multi-pack-index.o
BUILTIN_OBJS += builtin/mv.o
BUILTIN_OBJS += builtin/name-rev.o
BUILTIN_OBJS += builtin/notes.o
QUIET_MSGFMT = @echo ' ' MSGFMT $@;
QUIET_GCOV = @echo ' ' GCOV $@;
QUIET_SP = @echo ' ' SP $<;
+ QUIET_HDR = @echo ' ' HDR $<;
QUIET_RC = @echo ' ' RC $@;
QUIET_SUBDIR0 = +@subdir=
QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \
OBJECTS := $(LIB_OBJS) $(BUILTIN_OBJS) $(PROGRAM_OBJS) $(TEST_OBJS) \
$(XDIFF_OBJS) \
$(VCSSVN_OBJS) \
+ $(FUZZ_OBJS) \
common-main.o \
git.o
ifndef NO_CURL
ifdef GIT_INTEROP_MAKE_OPTS
@echo GIT_INTEROP_MAKE_OPTS=\''$(subst ','\'',$(subst ','\'',$(GIT_INTEROP_MAKE_OPTS)))'\' >>$@+
endif
-ifdef TEST_GIT_INDEX_VERSION
- @echo TEST_GIT_INDEX_VERSION=\''$(subst ','\'',$(subst ','\'',$(TEST_GIT_INDEX_VERSION)))'\' >>$@+
+ifdef GIT_TEST_INDEX_VERSION
+ @echo GIT_TEST_INDEX_VERSION=\''$(subst ','\'',$(subst ','\'',$(GIT_TEST_INDEX_VERSION)))'\' >>$@+
endif
@if cmp $@+ $@ >/dev/null 2>&1; then $(RM) $@+; else mv $@+ $@; fi
.PHONY: sparse $(SP_OBJ)
sparse: $(SP_OBJ)
+GEN_HDRS := command-list.h unicode-width.h
+EXCEPT_HDRS := $(GEN_HDRS) compat% xdiff%
+CHK_HDRS = $(filter-out $(EXCEPT_HDRS),$(patsubst ./%,%,$(LIB_H)))
+HCO = $(patsubst %.h,%.hco,$(CHK_HDRS))
+
+$(HCO): %.hco: %.h FORCE
+ $(QUIET_HDR)$(CC) -include git-compat-util.h -I. -o /dev/null -c -xc $<
+
+.PHONY: hdr-check $(HCO)
+hdr-check: $(HCO)
+
.PHONY: style
style:
git clang-format --style file --diff --extensions c,h
$(RM) $(LIB_FILE) $(XDIFF_LIB) $(VCSSVN_LIB)
$(RM) $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) git$X
$(RM) $(TEST_PROGRAMS) $(NO_INSTALL)
+ $(RM) $(FUZZ_PROGRAMS)
$(RM) -r bin-wrappers $(dep_dirs)
$(RM) -r po/build/
$(RM) *.pyc *.pyo */*.pyc */*.pyo command-list.h $(ETAGS_TARGET) tags cscope*
cover_db_html: cover_db
cover -report html -outputdir cover_db_html cover_db
+
+### Fuzz testing
+#
+# Building fuzz targets generally requires a special set of compiler flags that
+# are not necessarily appropriate for general builds, and that vary greatly
+# depending on the compiler version used.
+#
+# An example command to build against libFuzzer from LLVM 4.0.0:
+#
+# make CC=clang CXX=clang++ \
+# CFLAGS="-fsanitize-coverage=trace-pc-guard -fsanitize=address" \
+# LIB_FUZZING_ENGINE=/usr/lib/llvm-4.0/lib/libFuzzer.a \
+# fuzz-all
+#
+.PHONY: fuzz-all
+
+$(FUZZ_PROGRAMS): all
+ $(QUIET_LINK)$(CXX) $(CFLAGS) $(LIB_OBJS) $(BUILTIN_OBJS) \
+ $(XDIFF_OBJS) $(EXTLIBS) git.o $@.o $(LIB_FUZZING_ENGINE) -o $@
+
+fuzz-all: $(FUZZ_PROGRAMS)
-Documentation/RelNotes/2.19.1.txt
\ No newline at end of file
+Documentation/RelNotes/2.20.0.txt
\ No newline at end of file
-#ifndef __ALIAS_H__
-#define __ALIAS_H__
+#ifndef ALIAS_H
+#define ALIAS_H
struct string_list;
struct fragment *fragments;
char *result;
size_t resultsize;
- char old_sha1_prefix[41];
- char new_sha1_prefix[41];
+ char old_oid_prefix[GIT_MAX_HEXSZ + 1];
+ char new_oid_prefix[GIT_MAX_HEXSZ + 1];
struct patch *next;
/* three-way fallback result */
*/
const char *ptr, *eol;
int len;
+ const unsigned hexsz = the_hash_algo->hexsz;
ptr = strchr(line, '.');
- if (!ptr || ptr[1] != '.' || 40 < ptr - line)
+ if (!ptr || ptr[1] != '.' || hexsz < ptr - line)
return 0;
len = ptr - line;
- memcpy(patch->old_sha1_prefix, line, len);
- patch->old_sha1_prefix[len] = 0;
+ memcpy(patch->old_oid_prefix, line, len);
+ patch->old_oid_prefix[len] = 0;
line = ptr + 2;
ptr = strchr(line, ' ');
ptr = eol;
len = ptr - line;
- if (40 < len)
+ if (hexsz < len)
return 0;
- memcpy(patch->new_sha1_prefix, line, len);
- patch->new_sha1_prefix[len] = 0;
+ memcpy(patch->new_oid_prefix, line, len);
+ patch->new_oid_prefix[len] = 0;
if (*ptr == ' ')
return gitdiff_oldmode(state, ptr + 1, patch);
return 0;
if (!use_patch(state, patch))
patch->ws_rule = 0;
+ else if (patch->new_name)
+ patch->ws_rule = whitespace_rule(state->repo->index,
+ patch->new_name);
else
- patch->ws_rule = whitespace_rule(patch->new_name
- ? patch->new_name
- : patch->old_name);
+ patch->ws_rule = whitespace_rule(state->repo->index,
+ patch->old_name);
patchsize = parse_single_patch(state,
buffer + offset + hdrsize,
SWAP(p->new_mode, p->old_mode);
SWAP(p->is_new, p->is_delete);
SWAP(p->lines_added, p->lines_deleted);
- SWAP(p->old_sha1_prefix, p->new_sha1_prefix);
+ SWAP(p->old_oid_prefix, p->new_oid_prefix);
for (; frag; frag = frag->next) {
SWAP(frag->newpos, frag->oldpos);
{
const char *name = patch->old_name ? patch->old_name : patch->new_name;
struct object_id oid;
+ const unsigned hexsz = the_hash_algo->hexsz;
/*
* For safety, we require patch index line to contain
- * full 40-byte textual SHA1 for old and new, at least for now.
+ * full hex textual object ID for old and new, at least for now.
*/
- if (strlen(patch->old_sha1_prefix) != 40 ||
- strlen(patch->new_sha1_prefix) != 40 ||
- get_oid_hex(patch->old_sha1_prefix, &oid) ||
- get_oid_hex(patch->new_sha1_prefix, &oid))
+ if (strlen(patch->old_oid_prefix) != hexsz ||
+ strlen(patch->new_oid_prefix) != hexsz ||
+ get_oid_hex(patch->old_oid_prefix, &oid) ||
+ get_oid_hex(patch->new_oid_prefix, &oid))
return error(_("cannot apply binary patch to '%s' "
"without full index line"), name);
* applies to.
*/
hash_object_file(img->buf, img->len, blob_type, &oid);
- if (strcmp(oid_to_hex(&oid), patch->old_sha1_prefix))
+ if (strcmp(oid_to_hex(&oid), patch->old_oid_prefix))
return error(_("the patch applies to '%s' (%s), "
"which does not match the "
"current contents."),
"'%s' but it is not empty"), name);
}
- get_oid_hex(patch->new_sha1_prefix, &oid);
+ get_oid_hex(patch->new_oid_prefix, &oid);
if (is_null_oid(&oid)) {
clear_image(img);
return 0; /* deletion patch */
if (!result)
return error(_("the necessary postimage %s for "
"'%s' cannot be read"),
- patch->new_sha1_prefix, name);
+ patch->new_oid_prefix, name);
clear_image(img);
img->buf = result;
img->len = size;
/* verify that the result matches */
hash_object_file(img->buf, img->len, blob_type, &oid);
- if (strcmp(oid_to_hex(&oid), patch->new_sha1_prefix))
+ if (strcmp(oid_to_hex(&oid), patch->new_oid_prefix))
return error(_("binary patch to '%s' creates incorrect result (expecting %s, got %s)"),
- name, patch->new_sha1_prefix, oid_to_hex(&oid));
+ name, patch->new_oid_prefix, oid_to_hex(&oid));
}
return 0;
return 0;
}
-static int three_way_merge(struct image *image,
+static int three_way_merge(struct apply_state *state,
+ struct image *image,
char *path,
const struct object_id *base,
const struct object_id *ours,
status = ll_merge(&result, path,
&base_file, "base",
&our_file, "ours",
- &their_file, "theirs", NULL);
+ &their_file, "theirs",
+ state->repo->index,
+ NULL);
free(base_file.ptr);
free(our_file.ptr);
free(their_file.ptr);
/* Preimage the patch was prepared for */
if (patch->is_new)
write_object_file("", 0, blob_type, &pre_oid);
- else if (get_oid(patch->old_sha1_prefix, &pre_oid) ||
+ else if (get_oid(patch->old_oid_prefix, &pre_oid) ||
read_blob_object(&buf, &pre_oid, patch->old_mode))
return error(_("repository lacks the necessary blob to fall back on 3-way merge."));
clear_image(&tmp_image);
/* in-core three-way merge between post and our using pre as base */
- status = three_way_merge(image, patch->new_name,
+ status = three_way_merge(state, image, patch->new_name,
&pre_oid, &our_oid, &post_oid);
if (status < 0) {
if (state->apply_verbosity > verbosity_silent)
starts_with(++preimage, heading) &&
/* does it record full SHA-1? */
!get_oid_hex(preimage + sizeof(heading) - 1, oid) &&
- preimage[sizeof(heading) + GIT_SHA1_HEXSZ - 1] == '\n' &&
+ preimage[sizeof(heading) + the_hash_algo->hexsz - 1] == '\n' &&
/* does the abbreviated name on the index line agree with it? */
- starts_with(preimage + sizeof(heading) - 1, p->old_sha1_prefix))
+ starts_with(preimage + sizeof(heading) - 1, p->old_oid_prefix))
return 0; /* it all looks fine */
/* we may have full object name on the index line */
- return get_oid_hex(p->old_sha1_prefix, oid);
+ return get_oid_hex(p->old_oid_prefix, oid);
}
/* Build an index that contains just the files needed for a 3way merge */
else
return error(_("sha1 information is lacking or "
"useless for submodule %s"), name);
- } else if (!get_oid_blob(patch->old_sha1_prefix, &oid)) {
+ } else if (!get_oid_blob(patch->old_oid_prefix, &oid)) {
; /* ok */
} else if (!patch->lines_added && !patch->lines_deleted) {
/* mode-only change: update the current */
}
string_list_clear(&cpath, 0);
- rerere(0);
+ repo_rerere(state->repo, 0);
}
return errs;
}
}
-static int entry_is_binary(const char *path, const void *buffer, size_t size)
+static int entry_is_binary(struct index_state *istate, const char *path,
+ const void *buffer, size_t size)
{
- struct userdiff_driver *driver = userdiff_find_by_path(path);
+ struct userdiff_driver *driver = userdiff_find_by_path(istate, path);
if (!driver)
driver = userdiff_find_by_name("default");
if (driver->binary != -1)
return error(_("cannot read %s"),
oid_to_hex(oid));
crc = crc32(crc, buffer, size);
- is_binary = entry_is_binary(path_without_prefix,
+ is_binary = entry_is_binary(args->repo->index,
+ path_without_prefix,
buffer, size);
out = buffer;
}
break;
crc = crc32(crc, buf, readlen);
if (is_binary == -1)
- is_binary = entry_is_binary(path_without_prefix,
+ is_binary = entry_is_binary(args->repo->index,
+ path_without_prefix,
buf, readlen);
write_or_die(1, buf, readlen);
}
break;
crc = crc32(crc, buf, readlen);
if (is_binary == -1)
- is_binary = entry_is_binary(path_without_prefix,
+ is_binary = entry_is_binary(args->repo->index,
+ path_without_prefix,
buf, readlen);
zstream.next_in = buf;
static struct attr_check *check;
if (!check)
check = attr_check_initl("export-ignore", "export-subst", NULL);
- return git_check_attr(istate, path, check) ? NULL : check;
+ git_check_attr(istate, path, check);
+ return check;
}
static int check_attr_export_ignore(const struct attr_check *check)
if (get_oid(name, &oid))
die("Not a valid object name");
- commit = lookup_commit_reference_gently(the_repository, &oid, 1);
+ commit = lookup_commit_reference_gently(ar_args->repo, &oid, 1);
if (commit) {
commit_sha1 = commit->object.oid.hash;
archive_time = commit->date;
fill(path, pathlen, basename_offset, check->stack, check->all_attrs, rem);
}
-int git_check_attr(const struct index_state *istate,
- const char *path,
- struct attr_check *check)
+void git_check_attr(const struct index_state *istate,
+ const char *path,
+ struct attr_check *check)
{
int i;
value = ATTR__UNSET;
check->items[i].value = value;
}
-
- return 0;
}
void git_all_attrs(const struct index_state *istate,
*/
const char *git_attr_name(const struct git_attr *);
-int git_check_attr(const struct index_state *istate,
- const char *path, struct attr_check *check);
+void git_check_attr(const struct index_state *istate,
+ const char *path, struct attr_check *check);
/*
* Retrieve all attributes that apply to the specified path.
#include "sha1-array.h"
#include "argv-array.h"
#include "commit-slab.h"
+#include "commit-reach.h"
+#include "object-store.h"
static struct oid_array good_revs;
static struct oid_array skipped_revs;
}
}
-#if !DEBUG_BISECT
-#define show_list(a,b,c,d) do { ; } while (0)
-#else
static void show_list(const char *debug, int counted, int nr,
struct commit_list *list)
{
struct commit_list *p;
+ if (!DEBUG_BISECT)
+ return;
+
fprintf(stderr, "%s (%d/%d)\n", debug, counted, nr);
for (p = list; p; p = p->next) {
(flags & TREESAME) ? ' ' : 'T',
(flags & UNINTERESTING) ? 'U' : ' ',
(flags & COUNTED) ? 'C' : ' ');
- if (commit->util)
+ if (*commit_weight_at(&commit_weight, p->item))
fprintf(stderr, "%3d", weight(p));
else
fprintf(stderr, "---");
fprintf(stderr, "\n");
}
}
-#endif /* DEBUG_BISECT */
static struct commit_list *best_bisection(struct commit_list *list, int nr)
{
for (i = 0; cur; cur = cur->next, i++) {
if (i == index) {
- if (oidcmp(&cur->item->object.oid, current_bad_oid))
+ if (!oideq(&cur->item->object.oid, current_bad_oid))
return cur;
if (previous)
return previous;
struct argv_array rev_argv = ARGV_ARRAY_INIT;
int i;
- init_revisions(revs, prefix);
+ repo_init_revisions(the_repository, revs, prefix);
revs->abbrev = 0;
revs->commit_format = CMIT_FMT_UNSPECIFIED;
for (; result; result = result->next) {
const struct object_id *mb = &result->item->object.oid;
- if (!oidcmp(mb, current_bad_oid)) {
+ if (oideq(mb, current_bad_oid)) {
handle_bad_merge_base();
} else if (0 <= oid_array_lookup(&good_revs, mb)) {
continue;
struct rev_info opt;
/* diff-tree init */
- init_revisions(&opt, prefix);
+ repo_init_revisions(the_repository, &opt, prefix);
git_config(git_diff_basic_config, NULL); /* no "diff" UI options */
opt.abbrev = 0;
opt.diff = 1;
bisect_rev = &revs.commits->item->object.oid;
- if (!oidcmp(bisect_rev, current_bad_oid)) {
+ if (oideq(bisect_rev, current_bad_oid)) {
exit_if_skipped_commits(tried, current_bad_oid);
printf("%s is the first %s commit\n", oid_to_hex(bisect_rev),
term_bad);
-static void verify_working_tree_path(struct repository *repo,
+static void verify_working_tree_path(struct repository *r,
struct commit *work_tree, const char *path)
{
struct commit_list *parents;
unsigned mode;
if (!get_tree_entry(commit_oid, path, &blob_oid, &mode) &&
- oid_object_info(repo, &blob_oid, NULL) == OBJ_BLOB)
+ oid_object_info(r, &blob_oid, NULL) == OBJ_BLOB)
return;
}
- pos = index_name_pos(repo->index, path, strlen(path));
+ pos = index_name_pos(r->index, path, strlen(path));
if (pos >= 0)
; /* path is in the index */
- else if (-1 - pos < repo->index->cache_nr &&
- !strcmp(repo->index->cache[-1 - pos]->name, path))
+ else if (-1 - pos < r->index->cache_nr &&
+ !strcmp(r->index->cache[-1 - pos]->name, path))
; /* path is in the index, unmerged */
else
die("no such path '%s' in HEAD", path);
* Prepare a dummy commit that represents the work tree (or staged) item.
* Note that annotating work tree item never works in the reverse.
*/
-static struct commit *fake_working_tree_commit(struct repository *repo,
+static struct commit *fake_working_tree_commit(struct repository *r,
struct diff_options *opt,
const char *path,
const char *contents_from)
unsigned mode;
struct strbuf msg = STRBUF_INIT;
- read_index(repo->index);
+ read_index(r->index);
time(&now);
commit = alloc_commit_node(the_repository);
commit->object.parsed = 1;
parent_tail = append_parent(parent_tail, &head_oid);
append_merge_parents(parent_tail);
- verify_working_tree_path(repo, commit, path);
+ verify_working_tree_path(r, commit, path);
origin = make_origin(commit, path);
switch (st.st_mode & S_IFMT) {
case S_IFREG:
if (opt->flags.allow_textconv &&
- textconv_object(read_from, mode, &null_oid, 0, &buf_ptr, &buf_len))
+ textconv_object(r, read_from, mode, &null_oid, 0, &buf_ptr, &buf_len))
strbuf_attach(&buf, buf_ptr, buf_len, buf_len + 1);
else if (strbuf_read_file(&buf, read_from, st.st_size) != st.st_size)
die_errno("cannot open or read '%s'", read_from);
if (strbuf_read(&buf, 0, 0) < 0)
die_errno("failed to read from stdin");
}
- convert_to_git(repo->index, path, buf.buf, buf.len, &buf, 0);
+ convert_to_git(r->index, path, buf.buf, buf.len, &buf, 0);
origin->file.ptr = buf.buf;
origin->file.size = buf.len;
pretend_object_file(buf.buf, buf.len, OBJ_BLOB, &origin->blob_oid);
* bits; we are not going to write this index out -- we just
* want to run "diff-index --cached".
*/
- discard_index(repo->index);
- read_index(repo->index);
+ discard_index(r->index);
+ read_index(r->index);
len = strlen(path);
if (!mode) {
- int pos = index_name_pos(repo->index, path, len);
+ int pos = index_name_pos(r->index, path, len);
if (0 <= pos)
- mode = repo->index->cache[pos]->ce_mode;
+ mode = r->index->cache[pos]->ce_mode;
else
/* Let's not bother reading from HEAD tree */
mode = S_IFREG | 0644;
}
- ce = make_empty_cache_entry(repo->index, len);
+ ce = make_empty_cache_entry(r->index, len);
oidcpy(&ce->oid, &origin->blob_oid);
memcpy(ce->name, path, len);
ce->ce_flags = create_ce_flags(0);
ce->ce_namelen = len;
ce->ce_mode = create_ce_mode(mode);
- add_index_entry(repo->index, ce,
+ add_index_entry(r->index, ce,
ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE);
- cache_tree_invalidate_path(repo->index, path);
+ cache_tree_invalidate_path(r->index, path);
return commit;
}
(*num_read_blob)++;
if (opt->flags.allow_textconv &&
- textconv_object(o->path, o->mode, &o->blob_oid, 1, &file->ptr, &file_size))
+ textconv_object(opt->repo, o->path, o->mode,
+ &o->blob_oid, 1, &file->ptr, &file_size))
;
else
file->ptr = read_object_file(&o->blob_oid, &type,
*
* This also fills origin->mode for corresponding tree path.
*/
-static int fill_blob_sha1_and_mode(struct repository *repo,
+static int fill_blob_sha1_and_mode(struct repository *r,
struct blame_origin *origin)
{
if (!is_null_oid(&origin->blob_oid))
return 0;
if (get_tree_entry(&origin->commit->object.oid, origin->path, &origin->blob_oid, &origin->mode))
goto error_out;
- if (oid_object_info(repo, &origin->blob_oid, NULL) != OBJ_BLOB)
+ if (oid_object_info(r, &origin->blob_oid, NULL) != OBJ_BLOB)
goto error_out;
return 0;
error_out:
* We have an origin -- check if the same path exists in the
* parent and return an origin structure to represent it.
*/
-static struct blame_origin *find_origin(struct commit *parent,
- struct blame_origin *origin)
+static struct blame_origin *find_origin(struct repository *r,
+ struct commit *parent,
+ struct blame_origin *origin)
{
struct blame_origin *porigin;
struct diff_options diff_opts;
* and origin first. Most of the time they are the
* same and diff-tree is fairly efficient about this.
*/
- diff_setup(&diff_opts);
+ repo_diff_setup(r, &diff_opts);
diff_opts.flags.recursive = 1;
diff_opts.detect_rename = 0;
diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
* We have an origin -- find the path that corresponds to it in its
* parent and return an origin structure to represent it.
*/
-static struct blame_origin *find_rename(struct commit *parent,
- struct blame_origin *origin)
+static struct blame_origin *find_rename(struct repository *r,
+ struct commit *parent,
+ struct blame_origin *origin)
{
struct blame_origin *porigin = NULL;
struct diff_options diff_opts;
int i;
- diff_setup(&diff_opts);
+ repo_diff_setup(r, &diff_opts);
diff_opts.flags.recursive = 1;
diff_opts.detect_rename = DIFF_DETECT_RENAME;
diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
if (!unblamed)
return; /* nothing remains for this target */
- diff_setup(&diff_opts);
+ repo_diff_setup(sb->repo, &diff_opts);
diff_opts.flags.recursive = 1;
diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
* common cases, then we look for renames in the second pass.
*/
for (pass = 0; pass < 2 - sb->no_whole_file_rename; pass++) {
- struct blame_origin *(*find)(struct commit *, struct blame_origin *);
+ struct blame_origin *(*find)(struct repository *, struct commit *, struct blame_origin *);
find = pass ? find_rename : find_origin;
for (i = 0, sg = first_scapegoat(revs, commit, sb->reverse);
continue;
if (parse_commit(p))
continue;
- porigin = find(p, origin);
+ porigin = find(sb->repo, p, origin);
if (!porigin)
continue;
- if (!oidcmp(&porigin->blob_oid, &origin->blob_oid)) {
+ if (oideq(&porigin->blob_oid, &origin->blob_oid)) {
pass_whole_blame(sb, origin, porigin);
blame_origin_decref(porigin);
goto finish;
}
for (j = same = 0; j < i; j++)
if (sg_origin[j] &&
- !oidcmp(&sg_origin[j]->blob_oid, &porigin->blob_oid)) {
+ oideq(&sg_origin[j]->blob_oid, &porigin->blob_oid)) {
same = 1;
break;
}
sb->revs->children.name = "children";
while (c->parents &&
- oidcmp(&c->object.oid, &sb->final->object.oid)) {
+ !oideq(&c->object.oid, &sb->final->object.oid)) {
struct commit_list *l = xcalloc(1, sizeof(*l));
l->item = c;
c = c->parents->item;
}
- if (oidcmp(&c->object.oid, &sb->final->object.oid))
+ if (!oideq(&c->object.oid, &sb->final->object.oid))
die(_("--reverse --first-parent together require range along first-parent chain"));
}
die(_("no such path %s in %s"), path, final_commit_name);
if (sb->revs->diffopt.flags.allow_textconv &&
- textconv_object(path, o->mode, &o->blob_oid, 1, (char **) &sb->final_buf,
+ textconv_object(sb->repo, path, o->mode, &o->blob_oid, 1, (char **) &sb->final_buf,
&sb->final_buf_size))
;
else
extern int cmd_merge_tree(int argc, const char **argv, const char *prefix);
extern int cmd_mktag(int argc, const char **argv, const char *prefix);
extern int cmd_mktree(int argc, const char **argv, const char *prefix);
+extern int cmd_multi_pack_index(int argc, const char **argv, const char *prefix);
extern int cmd_mv(int argc, const char **argv, const char *prefix);
extern int cmd_name_rev(int argc, const char **argv, const char *prefix);
extern int cmd_notes(int argc, const char **argv, const char *prefix);
memset(&data, 0, sizeof(data));
data.flags = flags;
- init_revisions(&rev, prefix);
+ repo_init_revisions(the_repository, &rev, prefix);
setup_revisions(0, NULL, &rev, NULL);
if (pathspec)
copy_pathspec(&rev.prune_data, pathspec);
if (read_cache() < 0)
die(_("Could not read the index"));
- init_revisions(&rev, prefix);
+ repo_init_revisions(the_repository, &rev, prefix);
rev.diffopt.context = 7;
argc = setup_revisions(argc, argv, &rev, NULL);
* Check the "pathspec '%s' did not match any files" block
* below before enabling new magic.
*/
- parse_pathspec(&pathspec, 0,
+ parse_pathspec(&pathspec, PATHSPEC_ATTR,
PATHSPEC_PREFER_FULL |
PATHSPEC_SYMLINK_LEADING_PATH,
prefix, argv);
fclose(mi.input);
fclose(mi.output);
+ if (mi.format_flowed)
+ warning(_("Patch sent with format=flowed; "
+ "space at the end of lines might be lost."));
+
/* Extract message and author information */
fp = xfopen(am_path(state, "info"), "r");
while (!strbuf_getline_lf(&sb, fp)) {
FILE *fp;
fp = xfopen(am_path(state, "patch"), "w");
- init_revisions(&rev_info, NULL);
+ repo_init_revisions(the_repository, &rev_info, NULL);
rev_info.diff = 1;
rev_info.abbrev = 0;
rev_info.disable_stdin = 1;
the_repository->hash_algo->empty_tree);
fp = xfopen(am_path(state, "patch"), "w");
- init_revisions(&rev_info, NULL);
+ repo_init_revisions(the_repository, &rev_info, NULL);
rev_info.diff = 1;
rev_info.disable_stdin = 1;
rev_info.no_commit_id = 1;
struct rev_info rev_info;
const char *diff_filter_str = "--diff-filter=AM";
- init_revisions(&rev_info, NULL);
+ repo_init_revisions(the_repository, &rev_info, NULL);
rev_info.diffopt.output_format = DIFF_FORMAT_NAME_STATUS;
diff_opt_parse(&rev_info.diffopt, &diff_filter_str, 1, rev_info.prefix);
add_pending_oid(&rev_info, "HEAD", &our_tree, 0);
o.verbosity = 0;
if (merge_recursive_generic(&o, &our_tree, &their_tree, 1, bases, &result)) {
- rerere(state->allow_rerere_autoupdate);
+ repo_rerere(the_repository, state->allow_rerere_autoupdate);
free(their_tree_name);
return error(_("Failed to merge in the changes."));
}
goto next;
}
- rerere(0);
+ repo_rerere(the_repository, 0);
do_commit(state);
if (get_oid("HEAD", &head))
oidclr(&head);
- if (!oidcmp(&head, &abort_safety))
+ if (oideq(&head, &abort_safety))
return 1;
warning(_("You seem to have moved HEAD since the last 'am' failure.\n"
/* Ensure a valid committer ident can be constructed */
git_committer_info(IDENT_STRICT);
- if (read_index_preload(&the_index, NULL) < 0)
+ if (read_index_preload(&the_index, NULL, 0) < 0)
die(_("failed to read the index"));
if (in_progress) {
setup_default_color_by_age();
git_config(git_blame_config, &output_option);
- init_revisions(&revs, NULL);
+ repo_init_revisions(the_repository, &revs, NULL);
revs.date_mode = blame_date_mode;
revs.diffopt.flags.allow_textconv = 1;
revs.diffopt.flags.follow_renames = 1;
long bottom, top;
if (parse_range_arg(range_list.items[range_i].string,
nth_line_cb, &sb, lno, anchor,
- &bottom, &top, sb.path))
+ &bottom, &top, sb.path, &the_index))
usage(blame_usage);
if ((!lno && (top || bottom)) || lno < bottom)
die(Q_("file %s has only %lu line",
#include "ref-filter.h"
#include "worktree.h"
#include "help.h"
+#include "commit-reach.h"
static const char * const builtin_branch_usage[] = {
N_("git branch [<options>] [-r | -a] [--merged | --no-merged]"),
static const char *head;
static struct object_id head_oid;
-static int used_deprecated_reflog_option;
static int branch_use_color = -1;
static char branch_colors[][COLOR_MAXLEN] = {
return 0;
}
-static int deprecated_reflog_option_cb(const struct option *opt,
- const char *arg, int unset)
-{
- used_deprecated_reflog_option = 1;
- *(int *)opt->value = !unset;
- return 0;
-}
-
int cmd_branch(int argc, const char **argv, const char *prefix)
{
int delete = 0, rename = 0, copy = 0, force = 0, list = 0;
OPT_BIT('M', NULL, &rename, N_("move/rename a branch, even if target exists"), 2),
OPT_BIT('c', "copy", ©, N_("copy a branch and its reflog"), 1),
OPT_BIT('C', NULL, ©, N_("copy a branch, even if target exists"), 2),
- OPT_BOOL(0, "list", &list, N_("list branch names")),
+ OPT_BOOL('l', "list", &list, N_("list branch names")),
OPT_BOOL(0, "create-reflog", &reflog, N_("create the branch's reflog")),
- {
- OPTION_CALLBACK, 'l', NULL, &reflog, NULL,
- N_("deprecated synonym for --create-reflog"),
- PARSE_OPT_NOARG | PARSE_OPT_HIDDEN,
- deprecated_reflog_option_cb
- },
OPT_BOOL(0, "edit-description", &edit_description,
N_("edit the description for the branch")),
OPT__FORCE(&force, N_("force creation, move/rename, deletion"), PARSE_OPT_NOCOMPLETE),
if (list)
setup_auto_pager("branch", 1);
- if (used_deprecated_reflog_option && !list) {
- warning("the '-l' alias for '--create-reflog' is deprecated;");
- warning("it will be removed in a future version of Git");
- }
-
if (delete) {
if (!argc)
die(_("branch name required"));
print_columns(&output, colopts, NULL);
string_list_clear(&output, 0);
return 0;
- }
- else if (edit_description) {
+ } else if (edit_description) {
const char *branch_name;
struct strbuf branch_ref = STRBUF_INIT;
git_config_set_multivar(buf.buf, NULL, NULL, 1);
strbuf_release(&buf);
} else if (argc > 0 && argc <= 2) {
- struct branch *branch = branch_get(argv[0]);
-
- if (!branch)
- die(_("no such branch '%s'"), argv[0]);
-
if (filter.kind != FILTER_REFS_BRANCHES)
die(_("-a and -r options to 'git branch' do not make sense with a branch name"));
die("git cat-file --textconv %s: <object> must be <sha1:path>",
obj_name);
- if (textconv_object(path, obj_context.mode, &oid, 1, &buf, &size))
+ if (textconv_object(the_repository, path, obj_context.mode,
+ &oid, 1, &buf, &size))
break;
/* else fallthrough */
oid_to_hex(oid), data->rest);
} else if (opt->cmdmode == 'c') {
enum object_type type;
- if (!textconv_object(data->rest, 0100644, oid,
+ if (!textconv_object(the_repository,
+ data->rest, 0100644, oid,
1, &contents, &size))
contents = read_object_file(oid,
&type,
if (collect_all) {
git_all_attrs(&the_index, full_path, check);
} else {
- if (git_check_attr(&the_index, full_path, check))
- die("git_check_attr died");
+ git_check_attr(&the_index, full_path, check);
}
output_attr(check, file);
#include "submodule.h"
#include "advice.h"
+static int checkout_optimize_new_branch;
+
static const char * const checkout_usage[] = {
N_("git checkout [<options>] <branch>"),
N_("git checkout [<options>] [<branch>] -- <file>..."),
int ignore_skipworktree;
int ignore_other_worktrees;
int show_progress;
+ /*
+ * If new checkout options are added, skip_merge_working_tree
+ * should be updated accordingly.
+ */
const char *new_branch;
const char *new_branch_force;
if (pos >= 0) {
struct cache_entry *old = active_cache[pos];
if (ce->ce_mode == old->ce_mode &&
- !oidcmp(&ce->oid, &old->oid)) {
+ oideq(&ce->oid, &old->oid)) {
old->ce_flags |= CE_UPDATE;
discard_cache_entry(ce);
return 0;
* merge.renormalize set, too
*/
status = ll_merge(&result_buf, path, &ancestor, "base",
- &ours, "ours", &theirs, "theirs", NULL);
+ &ours, "ours", &theirs, "theirs",
+ state->istate, NULL);
free(ancestor.ptr);
free(ours.ptr);
free(theirs.ptr);
{
struct rev_info rev;
/* I think we want full paths, even if we're in a subdirectory. */
- init_revisions(&rev, NULL);
+ repo_init_revisions(the_repository, &rev, NULL);
rev.diffopt.flags = opts->flags;
rev.diffopt.output_format |= DIFF_FORMAT_NAME_STATUS;
diff_setup_done(&rev.diffopt);
branch->path = strbuf_detach(&buf, NULL);
}
+/*
+ * Skip merging the trees, updating the index and working directory if and
+ * only if we are creating a new branch via "git checkout -b <new_branch>."
+ */
+static int skip_merge_working_tree(const struct checkout_opts *opts,
+ const struct branch_info *old_branch_info,
+ const struct branch_info *new_branch_info)
+{
+ /*
+ * Do the merge if sparse checkout is on and the user has not opted in
+ * to the optimized behavior
+ */
+ if (core_apply_sparse_checkout && !checkout_optimize_new_branch)
+ return 0;
+
+ /*
+ * We must do the merge if we are actually moving to a new commit.
+ */
+ if (!old_branch_info->commit || !new_branch_info->commit ||
+ !oideq(&old_branch_info->commit->object.oid,
+ &new_branch_info->commit->object.oid))
+ return 0;
+
+ /*
+ * opts->patch_mode cannot be used with switching branches so is
+ * not tested here
+ */
+
+ /*
+ * opts->quiet only impacts output so doesn't require a merge
+ */
+
+ /*
+ * Honor the explicit request for a three-way merge or to throw away
+ * local changes
+ */
+ if (opts->merge || opts->force)
+ return 0;
+
+ /*
+ * --detach is documented as "updating the index and the files in the
+ * working tree" but this optimization skips those steps so fall through
+ * to the regular code path.
+ */
+ if (opts->force_detach)
+ return 0;
+
+ /*
+ * opts->writeout_stage cannot be used with switching branches so is
+ * not tested here
+ */
+
+ /*
+ * Honor the explicit ignore requests
+ */
+ if (!opts->overwrite_ignore || opts->ignore_skipworktree ||
+ opts->ignore_other_worktrees)
+ return 0;
+
+ /*
+ * opts->show_progress only impacts output so doesn't require a merge
+ */
+
+ /*
+ * If we aren't creating a new branch any changes or updates will
+ * happen in the existing branch. Since that could only be updating
+ * the index and working directory, we don't want to skip those steps
+ * or we've defeated any purpose in running the command.
+ */
+ if (!opts->new_branch)
+ return 0;
+
+ /*
+ * new_branch_force is defined to "create/reset and checkout a branch"
+ * so needs to go through the merge to do the reset
+ */
+ if (opts->new_branch_force)
+ return 0;
+
+ /*
+ * A new orphaned branch requrires the index and the working tree to be
+ * adjusted to <start_point>
+ */
+ if (opts->new_orphan_branch)
+ return 0;
+
+ /*
+ * Remaining variables are not checkout options but used to track state
+ */
+
+ return 1;
+}
+
static int merge_working_tree(const struct checkout_opts *opts,
struct branch_info *old_branch_info,
struct branch_info *new_branch_info,
struct rev_info revs;
struct object *object = &old_commit->object;
- init_revisions(&revs, NULL);
+ repo_init_revisions(the_repository, &revs, NULL);
setup_revisions(0, NULL, &revs, NULL);
object->flags &= ~UNINTERESTING;
parse_commit_or_die(new_branch_info->commit);
}
- ret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);
- if (ret) {
- free(path_to_free);
- return ret;
+ /* optimize the "checkout -b <new_branch> path */
+ if (skip_merge_working_tree(opts, &old_branch_info, new_branch_info)) {
+ if (!checkout_optimize_new_branch && !opts->quiet) {
+ if (read_cache_preload(NULL) < 0)
+ return error(_("index file corrupt"));
+ show_local_changes(&new_branch_info->commit->object, &opts->diff_options);
+ }
+ } else {
+ ret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);
+ if (ret) {
+ free(path_to_free);
+ return ret;
+ }
}
if (!opts->quiet && !old_branch_info.path && old_branch_info.commit && new_branch_info->commit != old_branch_info.commit)
static int git_checkout_config(const char *var, const char *value, void *cb)
{
+ if (!strcmp(var, "checkout.optimizenewbranch")) {
+ checkout_optimize_new_branch = git_config_bool(var, value);
+ return 0;
+ }
+
if (!strcmp(var, "diff.ignoresubmodules")) {
struct checkout_opts *opts = cb;
handle_ignore_submodules_arg(&opts->diff_options, value);
memset(&opts, 0, sizeof opts);
opts.update = 1;
opts.merge = 1;
+ opts.clone = 1;
opts.fn = oneway_merge;
opts.verbose_update = (option_verbosity >= 0);
opts.src_index = &the_index;
if (!graph)
return 0;
+ UNLEAK(graph);
return verify_commit_graph(the_repository, graph);
}
graph_name = get_commit_graph_filename(opts.obj_dir);
graph = load_commit_graph_one(graph_name);
- if (!graph) {
- UNLEAK(graph_name);
+ if (!graph)
die("graph file %s does not exist", graph_name);
- }
FREE_AND_NULL(graph_name);
printf(" large_edges");
printf("\n");
- free_commit_graph(graph);
+ UNLEAK(graph);
return 0;
}
+extern int read_replace_refs;
+
static int graph_write(int argc, const char **argv)
{
struct string_list *pack_indexes = NULL;
if (!opts.obj_dir)
opts.obj_dir = get_object_directory();
+ read_replace_refs = 0;
+
if (opts.reachable) {
- write_commit_graph_reachable(opts.obj_dir, opts.append);
+ write_commit_graph_reachable(opts.obj_dir, opts.append, 1);
return 0;
}
pack_indexes = &lines;
if (opts.stdin_commits)
commit_hex = &lines;
+
+ UNLEAK(buf);
}
write_commit_graph(opts.obj_dir,
pack_indexes,
commit_hex,
- opts.append);
+ opts.append,
+ 1);
- string_list_clear(&lines, 0);
+ UNLEAK(lines);
return 0;
}
#include "sequencer.h"
#include "mailmap.h"
#include "help.h"
+#include "commit-reach.h"
+#include "commit-graph.h"
static const char * const builtin_commit_usage[] = {
N_("git commit [<options>] [--] <pathspec>..."),
wt_status_collect(s);
wt_status_print(s);
+ wt_status_collect_free_buffers(s);
- return s->commitable;
+ return s->committable;
}
static int is_a_merge(const struct commit *current_head)
{
struct stat statbuf;
struct strbuf committer_ident = STRBUF_INIT;
- int commitable;
+ int committable;
struct strbuf sb = STRBUF_INIT;
const char *hook_arg1 = NULL;
const char *hook_arg2 = NULL;
saved_color_setting = s->use_color;
s->use_color = 0;
- commitable = run_status(s->fp, index_file, prefix, 1, s);
+ committable = run_status(s->fp, index_file, prefix, 1, s);
s->use_color = saved_color_setting;
+ string_list_clear(&s->change, 1);
} else {
struct object_id oid;
const char *parent = "HEAD";
for (i = 0; i < active_nr; i++)
if (ce_intent_to_add(active_cache[i]))
ita_nr++;
- commitable = active_nr - ita_nr > 0;
+ committable = active_nr - ita_nr > 0;
} else {
/*
* Unless the user did explicitly request a submodule
if (ignore_submodule_arg &&
!strcmp(ignore_submodule_arg, "all"))
flags.ignore_submodules = 1;
- commitable = index_differs_from(parent, &flags, 1);
+ committable = index_differs_from(parent, &flags, 1);
}
}
strbuf_release(&committer_ident);
* explicit --allow-empty. In the cherry-pick case, it may be
* empty due to conflict resolution, which the user should okay.
*/
- if (!commitable && whence != FROM_MERGE && !allow_empty &&
+ if (!committable && whence != FROM_MERGE && !allow_empty &&
!(amend && is_a_merge(current_head))) {
s->display_comment_prefix = old_display_comment_prefix;
run_status(stdout, index_file, prefix, 0, s);
const char *av[20];
int ac = 0;
- init_revisions(&revs, NULL);
+ repo_init_revisions(the_repository, &revs, NULL);
strbuf_addf(&buf, "--author=%s", name);
av[++ac] = "--all";
av[++ac] = "-i";
static int dry_run_commit(int argc, const char **argv, const char *prefix,
const struct commit *current_head, struct wt_status *s)
{
- int commitable;
+ int committable;
const char *index_file;
index_file = prepare_index(argc, argv, prefix, current_head, 1);
- commitable = run_status(stdout, index_file, prefix, 0, s);
+ committable = run_status(stdout, index_file, prefix, 0, s);
rollback_index_files();
- return commitable ? 0 : 1;
+ return committable ? 0 : 1;
}
define_list_config_array_extra(color_status_slots, {"added"});
static int no_renames = -1;
static const char *rename_score_arg = (const char *)-1;
static struct wt_status s;
+ unsigned int progress_flag = 0;
int fd;
struct object_id oid;
static struct option builtin_status_options[] = {
PATHSPEC_PREFER_FULL,
prefix, argv);
- read_cache_preload(&s.pathspec);
- refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, &s.pathspec, NULL, NULL);
+ if (status_format != STATUS_FORMAT_PORCELAIN &&
+ status_format != STATUS_FORMAT_PORCELAIN_V2)
+ progress_flag = REFRESH_PROGRESS;
+ read_index_preload(&the_index, &s.pathspec, progress_flag);
+ refresh_index(&the_index,
+ REFRESH_QUIET|REFRESH_UNMERGED|progress_flag,
+ &s.pathspec, NULL, NULL);
if (use_optional_locks())
fd = hold_locked_index(&index_lock, 0);
s.prefix = prefix;
wt_status_print(&s);
+ wt_status_collect_free_buffers(&s);
+
return 0;
}
"new_index file. Check that disk is not full and quota is\n"
"not exceeded, and then \"git reset HEAD\" to recover."));
- rerere(0);
+ if (git_env_bool(GIT_TEST_COMMIT_GRAPH, 0))
+ write_commit_graph_reachable(get_object_directory(), 0, 0);
+
+ repo_rerere(the_repository, 0);
run_command_v_opt(argv_gc_auto, RUN_GIT_CMD);
run_commit_hook(use_editor, get_index_file(), "post-commit", NULL);
if (amend && !no_post_rewrite) {
struct strbuf pack_buf = STRBUF_INIT;
struct strbuf garbage_buf = STRBUF_INIT;
- for (p = get_packed_git(the_repository); p; p = p->next) {
+ for (p = get_all_packs(the_repository); p; p = p->next) {
if (!p->pack_local)
continue;
if (open_pack_index(p))
N_("head"), N_("lightweight"), N_("annotated"),
};
-static int commit_name_cmp(const void *unused_cmp_data,
+static int commit_name_neq(const void *unused_cmp_data,
const void *entry,
const void *entry_or_key,
const void *peeled)
const struct commit_name *cn1 = entry;
const struct commit_name *cn2 = entry_or_key;
- return oidcmp(&cn1->peeled, peeled ? peeled : &cn2->peeled);
+ return !oideq(&cn1->peeled, peeled ? peeled : &cn2->peeled);
}
static inline struct commit_name *find_commit_name(const struct object_id *peeled)
/* Is it annotated? */
if (!peel_ref(path, &peeled)) {
- is_annotated = !!oidcmp(oid, &peeled);
+ is_annotated = !oideq(oid, &peeled);
} else {
oidcpy(&peeled, oid);
is_annotated = 0;
{
struct process_commit_data *pcd = data;
- if (!oidcmp(&pcd->looking_for, &obj->oid) && !pcd->dst->len) {
+ if (oideq(&pcd->looking_for, &obj->oid) && !pcd->dst->len) {
reset_revision_walk();
describe_commit(&pcd->current_commit, pcd->dst);
strbuf_addf(pcd->dst, ":%s", path);
"--objects", "--in-commit-order", "--reverse", "HEAD",
NULL);
- init_revisions(&revs, NULL);
+ repo_init_revisions(the_repository, &revs, NULL);
if (setup_revisions(args.argc, args.argv, &revs, NULL) > 1)
BUG("setup_revisions could not handle all args?");
return cmd_name_rev(args.argc, args.argv, prefix);
}
- hashmap_init(&names, commit_name_cmp, NULL, 0);
+ hashmap_init(&names, commit_name_neq, NULL, 0);
for_each_rawref(get_name, NULL);
if (!hashmap_get_size(&names) && !always)
die(_("No names found, cannot describe anything."));
if (0 <= fd)
update_index_if_able(&the_index, &index_lock);
- init_revisions(&revs, prefix);
+ repo_init_revisions(the_repository, &revs, prefix);
argv_array_pushv(&args, diff_index_args);
if (setup_revisions(args.argc, args.argv, &revs, NULL) != 1)
BUG("malformed internal diff-index command line");
usage(diff_files_usage);
git_config(git_diff_basic_config, NULL); /* no "diff" UI options */
- init_revisions(&rev, prefix);
+ repo_init_revisions(the_repository, &rev, prefix);
rev.abbrev = 0;
precompose_argv(argc, argv);
usage(diff_cache_usage);
git_config(git_diff_basic_config, NULL); /* no "diff" UI options */
- init_revisions(&rev, prefix);
+ repo_init_revisions(the_repository, &rev, prefix);
rev.abbrev = 0;
precompose_argv(argc, argv);
usage(diff_tree_usage);
git_config(git_diff_basic_config, NULL); /* no "diff" UI options */
- init_revisions(opt, prefix);
+ repo_init_revisions(the_repository, opt, prefix);
if (read_cache() < 0)
die(_("index file corrupt"));
opt->abbrev = 0;
struct diff_filespec *one, *two;
if (!is_null_oid(old_oid) && !is_null_oid(new_oid) &&
- !oidcmp(old_oid, new_oid) && (old_mode == new_mode))
+ oideq(old_oid, new_oid) && (old_mode == new_mode))
return;
if (opt->flags.reverse_diff) {
git_config(git_diff_ui_config, NULL);
precompose_argv(argc, argv);
- init_revisions(&rev, prefix);
+ repo_init_revisions(the_repository, &rev, prefix);
if (no_index && argc != i + 2) {
if (no_index == DIFF_NO_INDEX_IMPLICIT) {
}
if (no_index)
/* If this is a no-index diff, just run it and exit there. */
- diff_no_index(&rev, argc, argv);
+ diff_no_index(the_repository, &rev, argc, argv);
/* Otherwise, we are doing the usual "git" diff */
rev.diffopt.skip_stat_unmatch = !!diff_auto_refresh_index;
int fd = open(buf.buf, O_RDONLY);
if (fd >= 0 &&
- !index_fd(&wt_oid, fd, &st, OBJ_BLOB, name, 0)) {
+ !index_fd(&the_index, &wt_oid, fd, &st, OBJ_BLOB, name, 0)) {
if (is_null_oid(oid)) {
oidcpy(oid, &wt_oid);
use = 1;
- } else if (!oidcmp(oid, &wt_oid))
+ } else if (oideq(oid, &wt_oid))
use = 1;
}
}
strbuf_reset(&buf);
strbuf_addf(&buf, "Subproject commit %s",
oid_to_hex(&roid));
- if (!oidcmp(&loid, &roid))
+ if (oideq(&loid, &roid))
strbuf_addstr(&buf, "-dirty");
add_left_or_right(&submodules, dst_path, buf.buf, 1);
continue;
string_list_insert(changed, spec->path);
putchar('\n');
- if (!oidcmp(&ospec->oid, &spec->oid) &&
+ if (oideq(&ospec->oid, &spec->oid) &&
ospec->mode == spec->mode)
break;
}
/* we handle encodings */
git_config(git_default_config, NULL);
- init_revisions(&revs, prefix);
+ repo_init_revisions(the_repository, &revs, prefix);
init_revision_sources(&revision_sources);
revs.topo_order = 1;
revs.sources = &revision_sources;
{
struct ref *ref;
struct object_id oid;
+ const char *p;
- if (!get_oid_hex(name, &oid)) {
- if (name[GIT_SHA1_HEXSZ] == ' ') {
- /* <sha1> <ref>, find refname */
- name += GIT_SHA1_HEXSZ + 1;
- } else if (name[GIT_SHA1_HEXSZ] == '\0') {
- ; /* <sha1>, leave sha1 as name */
+ if (!parse_oid_hex(name, &oid, &p)) {
+ if (*p == ' ') {
+ /* <oid> <ref>, find refname */
+ name = p + 1;
+ } else if (*p == '\0') {
+ ; /* <oid>, leave oid as name */
} else {
/* <ref>, clear cruft from oid */
oidclr(&oid);
#include "utf8.h"
#include "packfile.h"
#include "list-objects-filter-options.h"
+#include "commit-reach.h"
static const char * const builtin_fetch_usage[] = {
N_("git fetch [<options>] [<repository> [<refspec>...]]"),
N_("append to .git/FETCH_HEAD instead of overwriting")),
OPT_STRING(0, "upload-pack", &upload_pack, N_("path"),
N_("path to upload pack on remote end")),
- OPT__FORCE(&force, N_("force overwrite of local branch"), 0),
+ OPT__FORCE(&force, N_("force overwrite of local reference"), 0),
OPT_BOOL('m', "multiple", &multiple,
N_("fetch from multiple remotes")),
OPT_SET_INT('t', "tags", &tags,
{
struct ref *rm = *head;
while (rm) {
- if (!hashcmp(rm->old_oid.hash, sha1))
+ if (hasheq(rm->old_oid.hash, sha1))
return 1;
rm = rm->next;
}
int max, rlen, llen, len;
/* uptodate lines are only shown on high verbosity level */
- if (!verbosity && !oidcmp(&ref->peer_ref->old_oid, &ref->old_oid))
+ if (!verbosity && oideq(&ref->peer_ref->old_oid, &ref->old_oid))
return;
max = term_columns();
if (type < 0)
die(_("object %s not found"), oid_to_hex(&ref->new_oid));
- if (!oidcmp(&ref->old_oid, &ref->new_oid)) {
+ if (oideq(&ref->old_oid, &ref->new_oid)) {
if (verbosity > 0)
format_display(display, '=', _("[up to date]"), NULL,
remote, pretty_ref, summary_width);
if (!is_null_oid(&ref->old_oid) &&
starts_with(ref->name, "refs/tags/")) {
- int r;
- r = s_update_ref("updating tag", ref, 0);
- format_display(display, r ? '!' : 't', _("[tag update]"),
- r ? _("unable to update local ref") : NULL,
- remote, pretty_ref, summary_width);
- return r;
+ if (force || ref->force) {
+ int r;
+ r = s_update_ref("updating tag", ref, 0);
+ format_display(display, r ? '!' : 't', _("[tag update]"),
+ r ? _("unable to update local ref") : NULL,
+ remote, pretty_ref, summary_width);
+ return r;
+ } else {
+ format_display(display, '!', _("[rejected]"), _("would clobber existing tag"),
+ remote, pretty_ref, summary_width);
+ return 1;
+ }
}
current = lookup_commit_reference_gently(the_repository,
* everything we are going to fetch already exists and is connected
* locally.
*/
-static int quickfetch(struct ref *ref_map)
+static int check_exist_and_connected(struct ref *ref_map)
{
struct ref *rm = ref_map;
struct check_connected_options opt = CHECK_CONNECTED_INIT;
+ struct ref *r;
/*
* If we are deepening a shallow clone we already have these
*/
if (deepen)
return -1;
+
+ /*
+ * check_connected() allows objects to merely be promised, but
+ * we need all direct targets to exist.
+ */
+ for (r = rm; r; r = r->next) {
+ if (!has_object_file(&r->old_oid))
+ return -1;
+ }
+
opt.quiet = 1;
return check_connected(iterate_ref_map, &rm, &opt);
}
static int fetch_refs(struct transport *transport, struct ref *ref_map)
{
- int ret = quickfetch(ref_map);
+ int ret = check_exist_and_connected(ref_map);
if (ret)
ret = transport_fetch_refs(transport, ref_map);
if (!ret)
int retcode = 0;
const struct ref *remote_refs;
struct argv_array ref_prefixes = ARGV_ARRAY_INIT;
+ int must_list_refs = 1;
if (tags == TAGS_DEFAULT) {
if (transport->remote->fetch_tags == 2)
goto cleanup;
}
- if (rs->nr)
+ if (rs->nr) {
+ int i;
+
refspec_ref_prefixes(rs, &ref_prefixes);
- else if (transport->remote && transport->remote->fetch.nr)
+
+ /*
+ * We can avoid listing refs if all of them are exact
+ * OIDs
+ */
+ must_list_refs = 0;
+ for (i = 0; i < rs->nr; i++) {
+ if (!rs->items[i].exact_sha1) {
+ must_list_refs = 1;
+ break;
+ }
+ }
+ } else if (transport->remote && transport->remote->fetch.nr)
refspec_ref_prefixes(&transport->remote->fetch, &ref_prefixes);
- if (ref_prefixes.argc &&
- (tags == TAGS_SET || (tags == TAGS_DEFAULT))) {
- argv_array_push(&ref_prefixes, "refs/tags/");
+ if (tags == TAGS_SET || tags == TAGS_DEFAULT) {
+ must_list_refs = 1;
+ if (ref_prefixes.argc)
+ argv_array_push(&ref_prefixes, "refs/tags/");
}
- remote_refs = transport_get_remote_refs(transport, &ref_prefixes);
+ if (must_list_refs)
+ remote_refs = transport_get_remote_refs(transport, &ref_prefixes);
+ else
+ remote_refs = NULL;
+
argv_array_clear(&ref_prefixes);
ref_map = get_ref_map(transport->remote, remote_refs, rs,
#include "fmt-merge-msg.h"
#include "gpg-interface.h"
#include "repository.h"
+#include "commit-reach.h"
static const char * const fmt_merge_msg_usage[] = {
N_("git fmt-merge-msg [-m <message>] [--log[=<n>] | --no-log] [--file <file>]"),
{
int i;
for (i = 0; i < table->nr; i++) {
- if (given && oidcmp(&table->item[i].given, given))
+ if (given && !oideq(&table->item[i].given, given))
continue;
- if (commit && oidcmp(&table->item[i].commit, commit))
+ if (commit && !oideq(&table->item[i].commit, commit))
continue;
return &table->item[i];
}
while (parents) {
struct commit *cmit = pop_commit(&parents);
for (i = 0; i < result->nr; i++)
- if (!oidcmp(&result->item[i].commit, &cmit->object.oid))
+ if (oideq(&result->item[i].commit, &cmit->object.oid))
result->item[i].used = 1;
}
struct rev_info rev;
head = lookup_commit_or_die(&head_oid, "HEAD");
- init_revisions(&rev, NULL);
+ repo_init_revisions(the_repository, &rev, NULL);
rev.commit_format = CMIT_FMT_ONELINE;
rev.ignore_merges = 1;
rev.limited = 1;
struct progress *progress = NULL;
if (show_progress) {
- for (p = get_packed_git(the_repository); p;
+ for (p = get_all_packs(the_repository); p;
p = p->next) {
if (open_pack_index(p))
continue;
progress = start_progress(_("Checking objects"), total);
}
- for (p = get_packed_git(the_repository); 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 (!git_config_get_bool("core.multipackindex", &i) && i) {
+ 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) {
+ midx_argv[2] = "--object-dir";
+ midx_argv[3] = alt->path;
+ if (run_command(&midx_verify))
+ errors_found |= ERROR_COMMIT_GRAPH;
+ }
+ }
+
return errors_found;
}
{
struct packed_git *p, *base = NULL;
- for (p = get_packed_git(the_repository); p; p = p->next) {
+ for (p = get_all_packs(the_repository); p; p = p->next) {
if (!p->pack_local)
continue;
if (limit) {
if (gc_auto_pack_limit <= 0)
return 0;
- for (cnt = 0, p = get_packed_git(the_repository); p; p = p->next) {
+ for (cnt = 0, p = get_all_packs(the_repository); p; p = p->next) {
if (!p->pack_local)
continue;
if (p->pack_keep)
return NULL;
}
+/*
+ * Returns 0 if there was no previous error and gc can proceed, 1 if
+ * gc should not proceed due to an error in the last run. Prints a
+ * message and returns -1 if an error occured while reading gc.log
+ */
static int report_last_gc_error(void)
{
struct strbuf sb = STRBUF_INIT;
int ret = 0;
+ ssize_t len;
struct stat st;
char *gc_log_path = git_pathdup("gc.log");
if (errno == ENOENT)
goto done;
- ret = error_errno(_("Can't stat %s"), gc_log_path);
+ ret = error_errno(_("cannot stat '%s'"), gc_log_path);
goto done;
}
if (st.st_mtime < gc_log_expire_time)
goto done;
- ret = strbuf_read_file(&sb, gc_log_path, 0);
- if (ret > 0)
- ret = error(_("The last gc run reported the following. "
+ len = strbuf_read_file(&sb, gc_log_path, 0);
+ if (len < 0)
+ ret = error_errno(_("cannot read '%s'"), gc_log_path);
+ else if (len > 0) {
+ /*
+ * A previous gc failed. Report the error, and don't
+ * bother with an automatic gc run since it is likely
+ * to fail in the same way.
+ */
+ warning(_("The last gc run reported the following. "
"Please correct the root cause\n"
"and remove %s.\n"
"Automatic cleanup will not be performed "
"until the file is removed.\n\n"
"%s"),
gc_log_path, sb.buf);
+ ret = 1;
+ }
strbuf_release(&sb);
done:
free(gc_log_path);
return ret;
}
-static int gc_before_repack(void)
+static void gc_before_repack(void)
{
if (pack_refs && run_command_v_opt(pack_refs_cmd.argv, RUN_GIT_CMD))
- return error(FAILED_RUN, pack_refs_cmd.argv[0]);
+ die(FAILED_RUN, pack_refs_cmd.argv[0]);
if (prune_reflogs && run_command_v_opt(reflog.argv, RUN_GIT_CMD))
- return error(FAILED_RUN, reflog.argv[0]);
+ die(FAILED_RUN, reflog.argv[0]);
pack_refs = 0;
prune_reflogs = 0;
- return 0;
}
int cmd_gc(int argc, const char **argv, const char *prefix)
fprintf(stderr, _("See \"git help gc\" for manual housekeeping.\n"));
}
if (detach_auto) {
- if (report_last_gc_error())
- return -1;
+ int ret = report_last_gc_error();
+ if (ret < 0)
+ /* an I/O error occured, already reported */
+ exit(128);
+ if (ret == 1)
+ /* Last gc --auto failed. Skip this one. */
+ return 0;
if (lock_repo_for_gc(force, &pid))
return 0;
- if (gc_before_repack())
- return -1;
+ gc_before_repack(); /* dies on failure */
delete_tempfile(&pidfile);
/*
atexit(process_log_file_at_exit);
}
- if (gc_before_repack())
- return -1;
+ gc_before_repack();
if (!repository_format_precious_objects) {
close_all_packs(the_repository->objects);
if (run_command_v_opt(repack.argv, RUN_GIT_CMD))
- return error(FAILED_RUN, repack.argv[0]);
+ die(FAILED_RUN, repack.argv[0]);
if (prune_expire) {
argv_array_push(&prune, prune_expire);
argv_array_push(&prune,
"--exclude-promisor-objects");
if (run_command_v_opt(prune.argv, RUN_GIT_CMD))
- return error(FAILED_RUN, prune.argv[0]);
+ die(FAILED_RUN, prune.argv[0]);
}
}
if (prune_worktrees_expire) {
argv_array_push(&prune_worktrees, prune_worktrees_expire);
if (run_command_v_opt(prune_worktrees.argv, RUN_GIT_CMD))
- return error(FAILED_RUN, prune_worktrees.argv[0]);
+ die(FAILED_RUN, prune_worktrees.argv[0]);
}
if (run_command_v_opt(rerere.argv, RUN_GIT_CMD))
- return error(FAILED_RUN, rerere.argv[0]);
+ die(FAILED_RUN, rerere.argv[0]);
report_garbage = report_pack_garbage;
reprepare_packed_git(the_repository);
clean_pack_garbage();
if (gc_write_commit_graph)
- write_commit_graph_reachable(get_object_directory(), 0);
+ write_commit_graph_reachable(get_object_directory(), 0,
+ !quiet && !daemonized);
if (auto_gc && too_many_loose_objects())
warning(_("There are too many unreachable loose objects; "
todo[todo_end].source = *gs;
if (opt->binary != GREP_BINARY_TEXT)
- grep_source_load_driver(&todo[todo_end].source);
+ grep_source_load_driver(&todo[todo_end].source,
+ opt->repo->index);
todo[todo_end].done = 0;
strbuf_reset(&todo[todo_end].out);
todo_end = (todo_end + 1) % ARRAY_SIZE(todo);
GREP_BINARY_NOMATCH),
OPT_BOOL(0, "textconv", &opt.allow_textconv,
N_("process binary files with textconv filters")),
+ OPT_SET_INT('r', "recursive", &opt.max_depth,
+ N_("search in subdirectories (default)"), -1),
{ OPTION_INTEGER, 0, "max-depth", &opt.max_depth, N_("depth"),
N_("descend at most <depth> levels"), PARSE_OPT_NONEG,
NULL, 1 },
OPT_END()
};
- init_grep_defaults();
+ init_grep_defaults(the_repository);
git_config(grep_cmd_config, NULL);
- grep_init(&opt, prefix);
+ grep_init(&opt, the_repository, prefix);
/*
* If there is no -- then the paths must exist in the working
if (fstat(fd, &st) < 0 ||
(literally
? hash_literally(&oid, fd, type, flags)
- : index_fd(&oid, fd, &st, type_from_string(type), path, flags)))
+ : index_fd(&the_index, &oid, fd, &st, type_from_string(type), path, flags)))
die((flags & HASH_WRITE_OBJECT)
? "Unable to add %s to database"
: "Unable to hash %s", path);
static int show_all = 0;
static int show_guides = 0;
static int show_config;
-static int verbose;
+static int verbose = 1;
static unsigned int colopts;
static enum help_format help_format = HELP_FORMAT_NONE;
static int exclude_guides;
alias = alias_lookup(cmd);
if (alias) {
- printf_ln(_("'%s' is aliased to '%s'"), cmd, alias);
- free(alias);
- exit(0);
+ const char **argv;
+ int count;
+
+ /*
+ * handle_builtin() in git.c rewrites "git cmd --help"
+ * to "git help --exclude-guides cmd", so we can use
+ * exclude_guides to distinguish "git cmd --help" from
+ * "git help cmd". In the latter case, or if cmd is an
+ * alias for a shell command, just print the alias
+ * definition.
+ */
+ if (!exclude_guides || alias[0] == '!') {
+ printf_ln(_("'%s' is aliased to '%s'"), cmd, alias);
+ free(alias);
+ exit(0);
+ }
+ /*
+ * Otherwise, we pretend that the command was "git
+ * word0 --help". We use split_cmdline() to get the
+ * first word of the alias, to ensure that we use the
+ * same rules as when the alias is actually
+ * used. split_cmdline() modifies alias in-place.
+ */
+ fprintf_ln(stderr, _("'%s' is aliased to '%s'"), cmd, alias);
+ count = split_cmdline(alias, &argv);
+ if (count < 0)
+ die(_("bad alias.%s string: %s"), cmd,
+ split_cmdline_strerror(count));
+ free(argv);
+ UNLEAK(alias);
+ return alias;
}
if (exclude_guides)
*last_index = -1;
return;
}
- while (first > 0 && !oidcmp(&ref_deltas[first - 1].oid, oid))
+ while (first > 0 && oideq(&ref_deltas[first - 1].oid, oid))
--first;
- while (last < end && !oidcmp(&ref_deltas[last + 1].oid, oid))
+ while (last < end && oideq(&ref_deltas[last + 1].oid, oid))
++last;
*first_index = first;
*last_index = last;
/* Check pack integrity */
flush();
the_hash_algo->final_fn(hash, &input_ctx);
- if (hashcmp(fill(the_hash_algo->rawsz), hash))
+ if (!hasheq(fill(the_hash_algo->rawsz), hash))
die(_("pack is corrupted (SHA1 mismatch)"));
use(the_hash_algo->rawsz);
fixup_pack_header_footer(output_fd, pack_hash,
curr_pack, nr_objects,
read_hash, consumed_bytes-the_hash_algo->rawsz);
- if (hashcmp(read_hash, tail_hash) != 0)
+ if (!hasheq(read_hash, tail_hash))
die(_("Unexpected tail checksum for %s "
"(disk corruption?)"), curr_pack);
}
OPT_BOOL(0, "unfold", &opts.unfold, N_("join whitespace-continued values")),
{ OPTION_CALLBACK, 0, "parse", &opts, NULL, N_("set parsing options"),
PARSE_OPT_NOARG | PARSE_OPT_NONEG, parse_opt_parse },
+ OPT_BOOL(0, "no-divider", &opts.no_divider, N_("do not treat --- specially")),
OPT_CALLBACK(0, "trailer", &trailers, N_("trailer"),
N_("trailer(s) to add"), option_parse_trailer),
OPT_END()
#include "progress.h"
#include "commit-slab.h"
#include "repository.h"
+#include "commit-reach.h"
+#include "interdiff.h"
+#include "range-diff.h"
#define MAIL_DEFAULT_WRAP 72
static void init_log_defaults(void)
{
- init_grep_defaults();
+ init_grep_defaults(the_repository);
init_diff_ui_defaults();
decoration_style = auto_decoration_style();
init_log_defaults();
git_config(git_log_config, NULL);
- init_revisions(&rev, prefix);
+ repo_init_revisions(the_repository, &rev, prefix);
rev.diff = 1;
rev.simplify_history = 0;
memset(&opt, 0, sizeof(opt));
&oidc, &obj_context))
die(_("Not a valid object name %s"), obj_name);
if (!obj_context.path ||
- !textconv_object(obj_context.path, obj_context.mode, &oidc, 1, &buf, &size)) {
+ !textconv_object(the_repository, obj_context.path,
+ obj_context.mode, &oidc, 1, &buf, &size)) {
free(obj_context.path);
return stream_blob_to_fd(1, oid, NULL, 0);
}
git_config(git_log_config, NULL);
memset(&match_all, 0, sizeof(match_all));
- init_revisions(&rev, prefix);
+ repo_init_revisions(the_repository, &rev, prefix);
rev.diff = 1;
rev.always_show_header = 1;
rev.no_walk = REVISION_WALK_NO_WALK_SORTED;
init_log_defaults();
git_config(git_log_config, NULL);
- init_revisions(&rev, prefix);
+ repo_init_revisions(the_repository, &rev, prefix);
init_reflog_walk(&rev.reflog_info);
rev.verbose_header = 1;
memset(&opt, 0, sizeof(opt));
init_log_defaults();
git_config(git_log_config, NULL);
- init_revisions(&rev, prefix);
+ repo_init_revisions(the_repository, &rev, prefix);
rev.always_show_header = 1;
memset(&opt, 0, sizeof(opt));
opt.def = "HEAD";
if ((flags1 & UNINTERESTING) == (flags2 & UNINTERESTING))
die(_("Not a range."));
- init_patch_ids(ids);
+ init_patch_ids(the_repository, ids);
/* given a range a..b get all patch ids for b..a */
- init_revisions(&check_rev, rev->prefix);
+ repo_init_revisions(the_repository, &check_rev, rev->prefix);
check_rev.max_parents = 1;
o1->flags ^= UNINTERESTING;
o2->flags ^= UNINTERESTING;
tip_oid = &rev->cmdline.rev[positive].item->oid;
if (dwim_ref(ref, strlen(ref), &branch_oid, &full_ref) &&
skip_prefix(full_ref, "refs/heads/", &v) &&
- !oidcmp(tip_oid, &branch_oid))
+ oideq(tip_oid, &branch_oid))
branch = xstrdup(v);
free(full_ref);
return branch;
}
+static void show_diffstat(struct rev_info *rev,
+ struct commit *origin, struct commit *head)
+{
+ struct diff_options opts;
+
+ memcpy(&opts, &rev->diffopt, sizeof(opts));
+ opts.output_format = DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT;
+ opts.stat_width = MAIL_DEFAULT_WRAP;
+
+ diff_setup_done(&opts);
+
+ diff_tree_oid(get_commit_tree_oid(origin),
+ get_commit_tree_oid(head),
+ "", &opts);
+ diffcore_std(&opts);
+ diff_flush(&opts);
+
+ fprintf(rev->diffopt.file, "\n");
+}
+
static void make_cover_letter(struct rev_info *rev, int use_stdout,
struct commit *origin,
int nr, struct commit **list,
struct strbuf sb = STRBUF_INIT;
int i;
const char *encoding = "UTF-8";
- struct diff_options opts;
int need_8bit_cte = 0;
struct pretty_print_context pp = {0};
struct commit *head = list[0];
shortlog_output(&log);
- /*
- * We can only do diffstat with a unique reference point
- */
- if (!origin)
- return;
-
- memcpy(&opts, &rev->diffopt, sizeof(opts));
- opts.output_format = DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT;
- opts.stat_width = MAIL_DEFAULT_WRAP;
+ /* We can only do diffstat with a unique reference point */
+ if (origin)
+ show_diffstat(rev, origin, head);
- diff_setup_done(&opts);
-
- diff_tree_oid(get_commit_tree_oid(origin),
- get_commit_tree_oid(head),
- "", &opts);
- diffcore_std(&opts);
- diff_flush(&opts);
+ if (rev->idiff_oid1) {
+ fprintf_ln(rev->diffopt.file, "%s", rev->idiff_title);
+ show_interdiff(rev, 0);
+ }
- fprintf(rev->diffopt.file, "\n");
+ if (rev->rdiff1) {
+ fprintf_ln(rev->diffopt.file, "%s", rev->rdiff_title);
+ show_range_diff(rev->rdiff1, rev->rdiff2,
+ rev->creation_factor, 1, &rev->diffopt);
+ }
}
static const char *clean_message_id(const char *msg_id)
return;
init_commit_base(&commit_base);
- diff_setup(&diffopt);
+ repo_diff_setup(the_repository, &diffopt);
diffopt.flags.recursive = 1;
diff_setup_done(&diffopt);
oidcpy(&bases->base_commit, &base->object.oid);
- init_revisions(&revs, NULL);
+ repo_init_revisions(the_repository, &revs, NULL);
revs.max_parents = 1;
revs.topo_order = 1;
for (i = 0; i < total; i++) {
oidclr(&bases->base_commit);
}
+static const char *diff_title(struct strbuf *sb, int reroll_count,
+ const char *generic, const char *rerolled)
+{
+ if (reroll_count <= 0)
+ strbuf_addstr(sb, generic);
+ else /* RFC may be v0, so allow -v1 to diff against v0 */
+ strbuf_addf(sb, rerolled, reroll_count - 1);
+ return sb->buf;
+}
+
+static void infer_range_diff_ranges(struct strbuf *r1,
+ struct strbuf *r2,
+ const char *prev,
+ struct commit *origin,
+ struct commit *head)
+{
+ const char *head_oid = oid_to_hex(&head->object.oid);
+
+ if (!strstr(prev, "..")) {
+ strbuf_addf(r1, "%s..%s", head_oid, prev);
+ strbuf_addf(r2, "%s..%s", prev, head_oid);
+ } else if (!origin) {
+ die(_("failed to infer range-diff ranges"));
+ } else {
+ strbuf_addstr(r1, prev);
+ strbuf_addf(r2, "%s..%s",
+ oid_to_hex(&origin->object.oid), head_oid);
+ }
+}
+
int cmd_format_patch(int argc, const char **argv, const char *prefix)
{
struct commit *commit;
struct base_tree_info bases;
int show_progress = 0;
struct progress *progress = NULL;
+ struct oid_array idiff_prev = OID_ARRAY_INIT;
+ struct strbuf idiff_title = STRBUF_INIT;
+ const char *rdiff_prev = NULL;
+ struct strbuf rdiff1 = STRBUF_INIT;
+ struct strbuf rdiff2 = STRBUF_INIT;
+ struct strbuf rdiff_title = STRBUF_INIT;
+ int creation_factor = -1;
const struct option builtin_format_patch_options[] = {
{ OPTION_CALLBACK, 'n', "numbered", &numbered, NULL,
OPT__QUIET(&quiet, N_("don't print the patch filenames")),
OPT_BOOL(0, "progress", &show_progress,
N_("show progress while generating patches")),
+ OPT_CALLBACK(0, "interdiff", &idiff_prev, N_("rev"),
+ N_("show changes against <rev> in cover letter or single patch"),
+ parse_opt_object_name),
+ OPT_STRING(0, "range-diff", &rdiff_prev, N_("refspec"),
+ N_("show changes against <refspec> in cover letter or single patch")),
+ OPT_INTEGER(0, "creation-factor", &creation_factor,
+ N_("percentage by which creation is weighted")),
OPT_END()
};
extra_cc.strdup_strings = 1;
init_log_defaults();
git_config(git_format_config, NULL);
- init_revisions(&rev, prefix);
+ repo_init_revisions(the_repository, &rev, prefix);
rev.commit_format = CMIT_FMT_EMAIL;
rev.expand_tabs_in_log_default = 0;
rev.verbose_header = 1;
/* Don't say anything if head and upstream are the same. */
if (rev.pending.nr == 2) {
struct object_array_entry *o = rev.pending.objects;
- if (oidcmp(&o[0].item->oid, &o[1].item->oid) == 0)
- return 0;
+ if (oideq(&o[0].item->oid, &o[1].item->oid))
+ goto done;
}
get_patch_ids(&rev, &ids);
}
}
if (nr == 0)
/* nothing to do */
- return 0;
+ goto done;
total = nr;
if (cover_letter == -1) {
if (config_cover_letter == COVER_AUTO)
if (numbered)
rev.total = total + start_number - 1;
+ if (idiff_prev.nr) {
+ if (!cover_letter && total != 1)
+ die(_("--interdiff requires --cover-letter or single patch"));
+ rev.idiff_oid1 = &idiff_prev.oid[idiff_prev.nr - 1];
+ rev.idiff_oid2 = get_commit_tree_oid(list[0]);
+ rev.idiff_title = diff_title(&idiff_title, reroll_count,
+ _("Interdiff:"),
+ _("Interdiff against v%d:"));
+ }
+
+ if (creation_factor < 0)
+ creation_factor = RANGE_DIFF_CREATION_FACTOR_DEFAULT;
+ else if (!rdiff_prev)
+ die(_("--creation-factor requires --range-diff"));
+
+ if (rdiff_prev) {
+ if (!cover_letter && total != 1)
+ die(_("--range-diff requires --cover-letter or single patch"));
+
+ infer_range_diff_ranges(&rdiff1, &rdiff2, rdiff_prev,
+ origin, list[0]);
+ rev.rdiff1 = rdiff1.buf;
+ rev.rdiff2 = rdiff2.buf;
+ rev.creation_factor = creation_factor;
+ rev.rdiff_title = diff_title(&rdiff_title, reroll_count,
+ _("Range-diff:"),
+ _("Range-diff against v%d:"));
+ }
+
if (!signature) {
; /* --no-signature inhibits all signatures */
} else if (signature && signature != git_version_string) {
print_signature(rev.diffopt.file);
total++;
start_number--;
+ /* interdiff/range-diff in cover-letter; omit from patches */
+ rev.idiff_oid1 = NULL;
+ rev.rdiff1 = NULL;
}
rev.add_signoff = do_signoff;
string_list_clear(&extra_hdr, 0);
if (ignore_if_in_upstream)
free_patch_ids(&ids);
+
+done:
+ oid_array_clear(&idiff_prev);
+ strbuf_release(&idiff_title);
+ strbuf_release(&rdiff1);
+ strbuf_release(&rdiff2);
+ strbuf_release(&rdiff_title);
return 0;
}
}
}
- init_revisions(&revs, prefix);
+ repo_init_revisions(the_repository, &revs, prefix);
revs.max_parents = 1;
if (add_pending_commit(head, &revs, 0))
/* Don't say anything if head and upstream are the same. */
if (revs.pending.nr == 2) {
struct object_array_entry *o = revs.pending.objects;
- if (oidcmp(&o[0].item->oid, &o[1].item->oid) == 0)
+ if (oideq(&o[0].item->oid, &o[1].item->oid))
return 0;
}
}
UNLEAK(sorting);
- UNLEAK(ref_array);
+ ref_array_clear(&ref_array);
return status;
}
#include "revision.h"
#include "parse-options.h"
#include "repository.h"
+#include "commit-reach.h"
static int show_merge_base(struct commit **rev, int rev_nr, int show_all)
{
their = NULL;
if (entry)
their = entry->blob;
- return merge_blobs(path, base, our, their, size);
+ return merge_blobs(&the_index, path, base, our, their, size);
}
static void *origin(struct merge_list *entry, unsigned long *size)
{
return a->oid &&
b->oid &&
- !oidcmp(a->oid, b->oid) &&
+ oideq(a->oid, b->oid) &&
a->mode == b->mode;
}
#include "packfile.h"
#include "tag.h"
#include "alias.h"
+#include "commit-reach.h"
#define DEFAULT_TWOHEAD (1<<0)
#define DEFAULT_OCTOPUS (1<<1)
printf(_("Squash commit -- not updating HEAD\n"));
- init_revisions(&rev, NULL);
+ repo_init_revisions(the_repository, &rev, NULL);
rev.ignore_merges = 1;
rev.commit_format = CMIT_FMT_MEDIUM;
}
if (new_head && show_diffstat) {
struct diff_options opts;
- diff_setup(&opts);
+ repo_diff_setup(the_repository, &opts);
opts.stat_width = -1; /* use full terminal width */
opts.stat_graph_width = -1; /* respect statGraphWidth config */
opts.output_format |=
die(_("unable to write %s"), get_index_file());
return clean ? 0 : 1;
} else {
- return try_merge_command(strategy, xopts_nr, xopts,
- common, head_arg, remoteheads);
+ return try_merge_command(the_repository,
+ strategy, xopts_nr, xopts,
+ common, head_arg, remoteheads);
}
}
fputs(msgbuf.buf, fp);
strbuf_release(&msgbuf);
fclose(fp);
- rerere(allow_rerere_auto);
+ repo_rerere(the_repository, allow_rerere_auto);
printf(_("Automatic merge failed; "
"fix conflicts and then commit the result.\n"));
return 1;
struct rev_info rev;
/* Check how many files differ. */
- init_revisions(&rev, "");
+ repo_init_revisions(the_repository, &rev, "");
setup_revisions(0, NULL, &rev, NULL);
rev.diffopt.output_format |=
DIFF_FORMAT_CALLBACK;
tag_ref = xstrfmt("refs/tags/%s",
((struct tag *)merge_remote_util(commit)->obj)->tag);
if (!read_ref(tag_ref, &oid) &&
- !oidcmp(&oid, &merge_remote_util(commit)->obj->oid))
+ oideq(&oid, &merge_remote_util(commit)->obj->oid))
is_throwaway_tag = 0;
else
is_throwaway_tag = 1;
goto done;
} else if (fast_forward != FF_NO && !remoteheads->next &&
!common->next &&
- !oidcmp(&common->item->object.oid, &head_commit->object.oid)) {
+ oideq(&common->item->object.oid, &head_commit->object.oid)) {
/* Again the most common case of merging one remote. */
struct strbuf msg = STRBUF_INIT;
struct commit *commit;
goto done;
}
- if (checkout_fast_forward(&head_commit->object.oid,
+ if (checkout_fast_forward(the_repository,
+ &head_commit->object.oid,
&commit->object.oid,
overwrite_ignore)) {
ret = 1;
* HEAD^^" would be missed.
*/
common_one = get_merge_bases(head_commit, j->item);
- if (oidcmp(&common_one->item->object.oid, &j->item->object.oid)) {
+ if (!oideq(&common_one->item->object.oid, &j->item->object.oid)) {
up_to_date = 0;
break;
}
*ntr++ = 0; /* now at the beginning of SHA1 */
- path = ntr + 41; /* at the beginning of name */
+ path = (char *)p + 1; /* at the beginning of name */
if (!nul_term_line && path[0] == '"') {
struct strbuf p_uq = STRBUF_INIT;
if (unquote_c_style(&p_uq, path, NULL))
--- /dev/null
+#include "builtin.h"
+#include "cache.h"
+#include "config.h"
+#include "parse-options.h"
+#include "midx.h"
+
+static char const * const builtin_multi_pack_index_usage[] = {
+ N_("git multi-pack-index [--object-dir=<dir>] (write|verify)"),
+ NULL
+};
+
+static struct opts_multi_pack_index {
+ const char *object_dir;
+} opts;
+
+int cmd_multi_pack_index(int argc, const char **argv,
+ const char *prefix)
+{
+ static struct option builtin_multi_pack_index_options[] = {
+ OPT_FILENAME(0, "object-dir", &opts.object_dir,
+ N_("object directory containing set of packfile and pack-index pairs")),
+ OPT_END(),
+ };
+
+ git_config(git_default_config, NULL);
+
+ argc = parse_options(argc, argv, prefix,
+ builtin_multi_pack_index_options,
+ builtin_multi_pack_index_usage, 0);
+
+ if (!opts.object_dir)
+ opts.object_dir = get_object_directory();
+
+ if (argc == 0)
+ usage_with_options(builtin_multi_pack_index_usage,
+ builtin_multi_pack_index_options);
+
+ if (argc > 1) {
+ die(_("too many arguments"));
+ return 1;
+ }
+
+ if (!strcmp(argv[0], "write"))
+ return write_midx_file(opts.object_dir);
+ if (!strcmp(argv[0], "verify"))
+ return verify_midx_file(opts.object_dir);
+
+ die(_("unrecognized verb: %s"), argv[0]);
+}
#include "streaming.h"
#include "thread-utils.h"
#include "pack-bitmap.h"
+#include "delta-islands.h"
#include "reachable.h"
#include "sha1-array.h"
#include "argv-array.h"
#include "packfile.h"
#include "object-store.h"
#include "dir.h"
+#include "midx.h"
#define IN_PACK(obj) oe_in_pack(&to_pack, obj)
#define SIZE(obj) oe_size(&to_pack, obj)
#define DELTA_CHILD(obj) oe_delta_child(&to_pack, obj)
#define DELTA_SIBLING(obj) oe_delta_sibling(&to_pack, obj)
#define SET_DELTA(obj, val) oe_set_delta(&to_pack, obj, val)
+#define SET_DELTA_EXT(obj, oid) oe_set_delta_ext(&to_pack, obj, oid)
#define SET_DELTA_SIZE(obj, val) oe_set_delta_size(&to_pack, obj, val)
#define SET_DELTA_CHILD(obj, val) oe_set_delta_child(&to_pack, obj, val)
#define SET_DELTA_SIBLING(obj, val) oe_set_delta_sibling(&to_pack, obj, val)
static struct pack_idx_entry **written_list;
static uint32_t nr_result, nr_written, nr_seen;
+static struct bitmap_index *bitmap_git;
+static uint32_t write_layer;
static int non_empty;
static int reuse_delta = 1, reuse_object = 1;
static int depth = 50;
static int delta_search_threads;
static int pack_to_stdout;
+static int thin;
static int num_preferred_base;
static struct progress *progress_state;
static int exclude_promisor_objects;
+static int use_delta_islands;
+
static unsigned long delta_cache_size = 0;
static unsigned long max_delta_cache_size = DEFAULT_DELTA_CACHE_SIZE;
static unsigned long cache_max_small_delta_size = 1000;
unsigned int *endp,
struct object_entry *e)
{
- if (e->filled)
+ if (e->filled || oe_layer(&to_pack, e) != write_layer)
return;
wo[(*endp)++] = e;
e->filled = 1;
add_descendants_to_write_order(wo, endp, root);
}
-static struct object_entry **compute_write_order(void)
+static void compute_layer_order(struct object_entry **wo, unsigned int *wo_end)
{
- unsigned int i, wo_end, last_untagged;
-
- struct object_entry **wo;
+ unsigned int i, last_untagged;
struct object_entry *objects = to_pack.objects;
for (i = 0; i < to_pack.nr_objects; i++) {
- objects[i].tagged = 0;
- objects[i].filled = 0;
- SET_DELTA_CHILD(&objects[i], NULL);
- SET_DELTA_SIBLING(&objects[i], NULL);
- }
-
- /*
- * Fully connect delta_child/delta_sibling network.
- * Make sure delta_sibling is sorted in the original
- * recency order.
- */
- for (i = to_pack.nr_objects; i > 0;) {
- struct object_entry *e = &objects[--i];
- if (!DELTA(e))
- continue;
- /* Mark me as the first child */
- e->delta_sibling_idx = DELTA(e)->delta_child_idx;
- SET_DELTA_CHILD(DELTA(e), e);
- }
-
- /*
- * Mark objects that are at the tip of tags.
- */
- for_each_tag_ref(mark_tagged, NULL);
-
- /*
- * Give the objects in the original recency order until
- * we see a tagged tip.
- */
- ALLOC_ARRAY(wo, to_pack.nr_objects);
- for (i = wo_end = 0; i < to_pack.nr_objects; i++) {
if (objects[i].tagged)
break;
- add_to_write_order(wo, &wo_end, &objects[i]);
+ add_to_write_order(wo, wo_end, &objects[i]);
}
last_untagged = i;
*/
for (; i < to_pack.nr_objects; i++) {
if (objects[i].tagged)
- add_to_write_order(wo, &wo_end, &objects[i]);
+ add_to_write_order(wo, wo_end, &objects[i]);
}
/*
if (oe_type(&objects[i]) != OBJ_COMMIT &&
oe_type(&objects[i]) != OBJ_TAG)
continue;
- add_to_write_order(wo, &wo_end, &objects[i]);
+ add_to_write_order(wo, wo_end, &objects[i]);
}
/*
for (i = last_untagged; i < to_pack.nr_objects; i++) {
if (oe_type(&objects[i]) != OBJ_TREE)
continue;
- add_to_write_order(wo, &wo_end, &objects[i]);
+ add_to_write_order(wo, wo_end, &objects[i]);
}
/*
* Finally all the rest in really tight order
*/
for (i = last_untagged; i < to_pack.nr_objects; i++) {
- if (!objects[i].filled)
- add_family_to_write_order(wo, &wo_end, &objects[i]);
+ if (!objects[i].filled && oe_layer(&to_pack, &objects[i]) == write_layer)
+ add_family_to_write_order(wo, wo_end, &objects[i]);
+ }
+}
+
+static struct object_entry **compute_write_order(void)
+{
+ uint32_t max_layers = 1;
+ unsigned int i, wo_end;
+
+ struct object_entry **wo;
+ struct object_entry *objects = to_pack.objects;
+
+ for (i = 0; i < to_pack.nr_objects; i++) {
+ objects[i].tagged = 0;
+ objects[i].filled = 0;
+ SET_DELTA_CHILD(&objects[i], NULL);
+ SET_DELTA_SIBLING(&objects[i], NULL);
+ }
+
+ /*
+ * Fully connect delta_child/delta_sibling network.
+ * Make sure delta_sibling is sorted in the original
+ * recency order.
+ */
+ for (i = to_pack.nr_objects; i > 0;) {
+ struct object_entry *e = &objects[--i];
+ if (!DELTA(e))
+ continue;
+ /* Mark me as the first child */
+ e->delta_sibling_idx = DELTA(e)->delta_child_idx;
+ SET_DELTA_CHILD(DELTA(e), e);
}
+ /*
+ * Mark objects that are at the tip of tags.
+ */
+ for_each_tag_ref(mark_tagged, NULL);
+
+ if (use_delta_islands)
+ max_layers = compute_pack_layers(&to_pack);
+
+ ALLOC_ARRAY(wo, to_pack.nr_objects);
+ wo_end = 0;
+
+ for (; write_layer < max_layers; ++write_layer)
+ compute_layer_order(wo, &wo_end);
+
if (wo_end != to_pack.nr_objects)
die(_("ordered %u objects, expected %"PRIu32),
wo_end, to_pack.nr_objects);
if (!check)
check = attr_check_initl("delta", NULL);
- if (git_check_attr(&the_index, path, check))
- return 0;
+ git_check_attr(&the_index, path, check);
if (ATTR_FALSE(check->items[0].value))
return 1;
return 0;
{
int want;
struct list_head *pos;
+ struct multi_pack_index *m;
if (!exclude && local && has_loose_object_nonlocal(oid))
return 0;
if (want != -1)
return want;
}
+
+ for (m = get_multi_pack_index(the_repository); m; m = m->next) {
+ struct pack_entry e;
+ if (fill_midx_entry(oid, &e, m)) {
+ struct packed_git *p = e.p;
+ off_t offset;
+
+ if (p == *found_pack)
+ offset = *found_offset;
+ else
+ offset = find_pack_entry_one(oid->hash, p);
+
+ if (offset) {
+ if (!*found_pack) {
+ if (!is_pack_valid(p))
+ continue;
+ *found_offset = offset;
+ *found_pack = p;
+ }
+ want = want_found_object(exclude, p);
+ if (want != -1)
+ return want;
+ }
+ }
+ }
+
list_for_each(pos, get_packed_git_mru(the_repository)) {
struct packed_git *p = list_entry(pos, struct packed_git, mru);
off_t offset;
*/
for (neigh = 0; neigh < 8; neigh++) {
ent = pbase_tree_cache[my_ix];
- if (ent && !oidcmp(&ent->oid, oid)) {
+ if (ent && oideq(&ent->oid, oid)) {
ent->ref++;
return ent;
}
return;
for (it = pbase_tree; it; it = it->next) {
- if (!oidcmp(&it->pcache.oid, &tree_oid)) {
+ if (oideq(&it->pcache.oid, &tree_oid)) {
free(data);
return;
}
done_pbase_paths_num = done_pbase_paths_alloc = 0;
}
+/*
+ * Return 1 iff the object specified by "delta" can be sent
+ * literally as a delta against the base in "base_sha1". If
+ * so, then *base_out will point to the entry in our packing
+ * list, or NULL if we must use the external-base list.
+ *
+ * Depth value does not matter - find_deltas() will
+ * never consider reused delta as the base object to
+ * deltify other objects against, in order to avoid
+ * circular deltas.
+ */
+static int can_reuse_delta(const unsigned char *base_sha1,
+ struct object_entry *delta,
+ struct object_entry **base_out)
+{
+ struct object_entry *base;
+
+ if (!base_sha1)
+ return 0;
+
+ /*
+ * First see if we're already sending the base (or it's explicitly in
+ * our "excluded" list).
+ */
+ base = packlist_find(&to_pack, base_sha1, NULL);
+ if (base) {
+ if (!in_same_island(&delta->idx.oid, &base->idx.oid))
+ return 0;
+ *base_out = base;
+ return 1;
+ }
+
+ /*
+ * Otherwise, reachability bitmaps may tell us if the receiver has it,
+ * even if it was buried too deep in history to make it into the
+ * packing list.
+ */
+ if (thin && bitmap_has_sha1_in_uninteresting(bitmap_git, base_sha1)) {
+ if (use_delta_islands) {
+ struct object_id base_oid;
+ hashcpy(base_oid.hash, base_sha1);
+ if (!in_same_island(&delta->idx.oid, &base_oid))
+ return 0;
+ }
+ *base_out = NULL;
+ return 1;
+ }
+
+ return 0;
+}
+
static void check_object(struct object_entry *entry)
{
unsigned long canonical_size;
break;
}
- if (base_ref && (base_entry = packlist_find(&to_pack, base_ref, NULL))) {
- /*
- * If base_ref was set above that means we wish to
- * reuse delta data, and we even found that base
- * in the list of objects we want to pack. Goodie!
- *
- * Depth value does not matter - find_deltas() will
- * never consider reused delta as the base object to
- * deltify other objects against, in order to avoid
- * circular deltas.
- */
+ if (can_reuse_delta(base_ref, entry, &base_entry)) {
oe_set_type(entry, entry->in_pack_type);
SET_SIZE(entry, in_pack_size); /* delta size */
- SET_DELTA(entry, base_entry);
SET_DELTA_SIZE(entry, in_pack_size);
- entry->delta_sibling_idx = base_entry->delta_child_idx;
- SET_DELTA_CHILD(base_entry, entry);
+
+ if (base_entry) {
+ SET_DELTA(entry, base_entry);
+ entry->delta_sibling_idx = base_entry->delta_child_idx;
+ SET_DELTA_CHILD(base_entry, entry);
+ } else {
+ SET_DELTA_EXT(entry, base_ref);
+ }
+
unuse_pack(&w_curs);
return;
}
return -1;
if (a->preferred_base < b->preferred_base)
return 1;
+ if (use_delta_islands) {
+ int island_cmp = island_delta_cmp(&a->idx.oid, &b->idx.oid);
+ if (island_cmp)
+ return island_cmp;
+ }
if (a_size > b_size)
return -1;
if (a_size < b_size)
if (trg_size < src_size / 32)
return 0;
+ if (!in_same_island(&trg->entry->idx.oid, &src->entry->idx.oid))
+ return 0;
+
/* Load data if not already done */
if (!trg->data) {
read_lock();
pthread_mutex_init(&cache_mutex, NULL);
pthread_mutex_init(&progress_mutex, NULL);
pthread_cond_init(&progress_cond, NULL);
- pthread_mutex_init(&to_pack.lock, NULL);
old_try_to_free_routine = set_try_to_free_routine(try_to_free_from_threads);
}
uint32_t i, nr_deltas;
unsigned n;
+ if (use_delta_islands)
+ resolve_tree_islands(progress, &to_pack);
+
get_object_details();
/*
if (write_bitmap_index)
index_commit_for_bitmap(commit);
+
+ if (use_delta_islands)
+ propagate_island_marks(commit);
}
static void show_object(struct object *obj, const char *name, void *data)
add_preferred_base_object(name);
add_object_entry(&obj->oid, obj->type, name, 0);
obj->flags |= OBJECT_ADDED;
+
+ if (use_delta_islands) {
+ const char *p;
+ unsigned depth = 0;
+ struct object_entry *ent;
+
+ for (p = strchr(name, '/'); p; p = strchr(p + 1, '/'))
+ depth++;
+
+ ent = packlist_find(&to_pack, obj->oid.hash, NULL);
+ if (ent && depth > oe_tree_depth(&to_pack, ent))
+ oe_set_tree_depth(&to_pack, ent, depth);
+ }
}
static void show_object__ma_allow_any(struct object *obj, const char *name, void *data)
memset(&in_pack, 0, sizeof(in_pack));
- for (p = get_packed_git(the_repository); p; p = p->next) {
+ for (p = get_all_packs(the_repository); p; p = p->next) {
struct object_id oid;
struct object *o;
struct packed_git *p;
p = (last_found != (void *)1) ? last_found :
- get_packed_git(the_repository);
+ get_all_packs(the_repository);
while (p) {
if ((!p->pack_local || p->pack_keep ||
return 1;
}
if (p == last_found)
- p = get_packed_git(the_repository);
+ p = get_all_packs(the_repository);
else
p = p->next;
if (p == last_found)
uint32_t i;
struct object_id oid;
- for (p = get_packed_git(the_repository); p; p = p->next) {
+ for (p = get_all_packs(the_repository); p; p = p->next) {
if (!p->pack_local || p->pack_keep || p->pack_keep_in_core)
continue;
static int get_object_list_from_bitmap(struct rev_info *revs)
{
- struct bitmap_index *bitmap_git;
if (!(bitmap_git = prepare_bitmap_walk(revs)))
return -1;
}
traverse_bitmap_commit_list(bitmap_git, &add_object_entry_from_bitmap);
- free_bitmap_index(bitmap_git);
return 0;
}
char line[1000];
int flags = 0;
- init_revisions(&revs, NULL);
+ repo_init_revisions(the_repository, &revs, NULL);
save_commit_buffer = 0;
setup_revisions(ac, av, &revs, NULL);
if (use_bitmap_index && !get_object_list_from_bitmap(&revs))
return;
+ if (use_delta_islands)
+ load_delta_islands();
+
if (prepare_revision_walk(&revs))
die(_("revision walk setup failed"));
mark_edges_uninteresting(&revs, show_edge);
if (!names->nr)
return;
- for (p = get_packed_git(the_repository); p; p = p->next) {
+ for (p = get_all_packs(the_repository); p; p = p->next) {
const char *name = basename(p->pack_name);
int i;
int cmd_pack_objects(int argc, const char **argv, const char *prefix)
{
int use_internal_rev_list = 0;
- int thin = 0;
int shallow = 0;
int all_progress_implied = 0;
struct argv_array rp = ARGV_ARRAY_INIT;
option_parse_missing_action },
OPT_BOOL(0, "exclude-promisor-objects", &exclude_promisor_objects,
N_("do not pack objects in promisor packfiles")),
+ OPT_BOOL(0, "delta-islands", &use_delta_islands,
+ N_("respect islands during delta compression")),
OPT_END(),
};
if (pack_to_stdout || !rev_list_all)
write_bitmap_index = 0;
+ if (use_delta_islands)
+ argv_array_push(&rp, "--topo-order");
+
if (progress && all_progress_implied)
progress = 2;
add_extra_kept_packs(&keep_pack_list);
if (ignore_packed_keep_on_disk) {
struct packed_git *p;
- for (p = get_packed_git(the_repository); p; p = p->next)
+ for (p = get_all_packs(the_repository); p; p = p->next)
if (p->pack_local && p->pack_keep)
break;
if (!p) /* no keep-able packs found */
* it also covers non-local objects
*/
struct packed_git *p;
- for (p = get_packed_git(the_repository); p; p = p->next) {
+ for (p = get_all_packs(the_repository); p; p = p->next) {
if (!p->pack_local) {
have_non_local_packs = 1;
break;
static struct pack_list * add_pack_file(const char *filename)
{
- struct packed_git *p = get_packed_git(the_repository);
+ struct packed_git *p = get_all_packs(the_repository);
if (strlen(filename) < 40)
die("Bad pack filename: %s", filename);
static void load_all(void)
{
- struct packed_git *p = get_packed_git(the_repository);
+ struct packed_git *p = get_all_packs(the_repository);
while (p) {
add_pack(p);
save_commit_buffer = 0;
read_replace_refs = 0;
ref_paranoia = 1;
- init_revisions(&revs, prefix);
+ repo_init_revisions(the_repository, &revs, prefix);
argc = parse_options(argc, argv, prefix, options, prune_usage, 0);
#include "tempfile.h"
#include "lockfile.h"
#include "wt-status.h"
+#include "commit-reach.h"
enum rebase_type {
REBASE_INVALID = -1,
* index/worktree changes that the user already made on the unborn
* branch.
*/
- if (checkout_fast_forward(the_hash_algo->empty_tree, merge_head, 0))
+ if (checkout_fast_forward(the_repository,
+ the_hash_algo->empty_tree,
+ merge_head, 0))
return 1;
if (update_ref("initial pull", "HEAD", merge_head, curr_head, 0, UPDATE_REFS_DIE_ON_ERR))
struct argv_array args = ARGV_ARRAY_INIT;
if (!get_octopus_merge_base(&oct_merge_base, curr_head, merge_head, fork_point))
- if (!is_null_oid(fork_point) && !oidcmp(&oct_merge_base, fork_point))
+ if (!is_null_oid(fork_point) && oideq(&oct_merge_base, fork_point))
fork_point = NULL;
argv_array_push(&args, "rebase");
oidclr(&curr_head);
if (!is_null_oid(&orig_head) && !is_null_oid(&curr_head) &&
- oidcmp(&orig_head, &curr_head)) {
+ !oideq(&orig_head, &curr_head)) {
/*
* The fetch involved updating the current branch.
*
"fast-forwarding your working tree from\n"
"commit %s."), oid_to_hex(&orig_head));
- if (checkout_fast_forward(&orig_head, &curr_head, 0))
+ if (checkout_fast_forward(the_repository, &orig_head,
+ &curr_head, 0))
die(_("Cannot fast-forward your working tree.\n"
"After making sure that you saved anything precious from\n"
"$ git diff %s\n"
int ret = 0;
if ((recurse_submodules == RECURSE_SUBMODULES_ON ||
recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND) &&
- submodule_touches_in_range(&rebase_fork_point, &curr_head))
+ submodule_touches_in_range(&the_index, &rebase_fork_point, &curr_head))
die(_("cannot rebase with locally recorded submodule modifications"));
if (!autostash) {
struct commit_list *list = NULL;
NULL
};
-static struct strbuf *output_prefix_cb(struct diff_options *opt, void *data)
-{
- return data;
-}
-
int cmd_range_diff(int argc, const char **argv, const char *prefix)
{
- int creation_factor = 60;
+ int creation_factor = RANGE_DIFF_CREATION_FACTOR_DEFAULT;
struct diff_options diffopt = { NULL };
int simple_color = -1;
struct option options[] = {
OPT_END()
};
int i, j, res = 0;
- struct strbuf four_spaces = STRBUF_INIT;
struct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;
git_config(git_diff_ui_config, NULL);
- diff_setup(&diffopt);
- diffopt.output_format = DIFF_FORMAT_PATCH;
- diffopt.flags.suppress_diff_headers = 1;
- diffopt.output_prefix = output_prefix_cb;
- strbuf_addstr(&four_spaces, " ");
- diffopt.output_prefix_data = &four_spaces;
+ repo_diff_setup(the_repository, &diffopt);
argc = parse_options(argc, argv, NULL, options,
builtin_range_diff_usage, PARSE_OPT_KEEP_UNKNOWN |
options + ARRAY_SIZE(options) - 1, /* OPT_END */
builtin_range_diff_usage, 0);
- if (simple_color < 1) {
- if (!simple_color)
- /* force color when --dual-color was used */
- diffopt.use_color = 1;
- diffopt.flags.dual_color_diffed_diffs = 1;
- }
+ /* force color when --dual-color was used */
+ if (!simple_color)
+ diffopt.use_color = 1;
if (argc == 2) {
if (!strstr(argv[0], ".."))
}
res = show_range_diff(range1.buf, range2.buf, creation_factor,
- &diffopt);
+ simple_color < 1, &diffopt);
strbuf_release(&range1);
strbuf_release(&range2);
- strbuf_release(&four_spaces);
return res;
}
#include "packfile.h"
#include "object-store.h"
#include "protocol.h"
+#include "commit-reach.h"
static const char * const receive_pack_usage[] = {
N_("git receive-pack <git-dir>"),
return 0;
}
-static void show_one_alternate_ref(const char *refname,
- const struct object_id *oid,
+static void show_one_alternate_ref(const struct object_id *oid,
void *data)
{
struct oidset *seen = data;
unsigned char sha1[GIT_SHA1_RAWSZ];
strbuf_addf(&buf, "%s:%"PRItime, path, stamp);
- hmac_sha1(sha1, buf.buf, buf.len, cert_nonce_seed, strlen(cert_nonce_seed));;
+ hmac_sha1(sha1, buf.buf, buf.len, cert_nonce_seed, strlen(cert_nonce_seed));
strbuf_release(&buf);
/* RFC 2104 5. HMAC-SHA1-80 */
const char *ret;
struct object_id *old_oid = &cmd->old_oid;
struct object_id *new_oid = &cmd->new_oid;
+ int do_update_worktree = 0;
/* only refs/... are allowed */
if (!starts_with(name, "refs/") || check_refname_format(name + 5, 0)) {
refuse_unconfigured_deny();
return "branch is currently checked out";
case DENY_UPDATE_INSTEAD:
- ret = update_worktree(new_oid->hash);
- if (ret)
- return ret;
+ /* pass -- let other checks intervene first */
+ do_update_worktree = 1;
break;
}
}
return "hook declined";
}
+ if (do_update_worktree) {
+ ret = update_worktree(new_oid->hash);
+ if (ret)
+ return ret;
+ }
+
if (is_null_oid(new_oid)) {
struct strbuf err = STRBUF_INIT;
if (!parse_object(the_repository, old_oid)) {
dst_cmd = (struct command *) item->util;
- if (!oidcmp(&cmd->old_oid, &dst_cmd->old_oid) &&
- !oidcmp(&cmd->new_oid, &dst_cmd->new_oid))
+ if (oideq(&cmd->old_oid, &dst_cmd->old_oid) &&
+ oideq(&cmd->new_oid, &dst_cmd->new_oid))
return;
dst_cmd->skip_update = 1;
/*
* keep hooks happy by forcing a temporary shallow file via
* env variable because we can't add --shallow-file to every
- * command. check_everything_connected() will be done with
+ * command. check_connected() will be done with
* true .git/shallow though.
*/
setenv(GIT_SHALLOW_FILE_ENVIRONMENT, alt_shallow_file, 1);
* from reflog if the repository was pruned with older git.
*/
if (cb.cmd.stalefix) {
- init_revisions(&cb.cmd.revs, prefix);
+ repo_init_revisions(the_repository, &cb.cmd.revs, prefix);
if (flags & EXPIRE_REFLOGS_VERBOSE)
printf("Marking reachable objects...");
mark_reachable_objects(&cb.cmd.revs, 0, 0, NULL);
#include "refspec.h"
#include "object-store.h"
#include "argv-array.h"
+#include "commit-reach.h"
static const char * const builtin_remote_usage[] = {
N_("git remote [-v | --verbose]"),
if (is_null_oid(&ref->new_oid)) {
info->status = PUSH_STATUS_DELETE;
- } else if (!oidcmp(&ref->old_oid, &ref->new_oid))
+ } else if (oideq(&ref->old_oid, &ref->new_oid))
info->status = PUSH_STATUS_UPTODATE;
else if (is_null_oid(&ref->old_oid))
info->status = PUSH_STATUS_CREATE;
oldremote = remote_get(rename.old_name);
if (!remote_is_configured(oldremote, 1))
- die(_("No such remote: %s"), rename.old_name);
+ die(_("No such remote: '%s'"), rename.old_name);
if (!strcmp(rename.old_name, rename.new_name) && oldremote->origin != REMOTE_CONFIG)
return migrate_file(oldremote);
remote = remote_get(argv[1]);
if (!remote_is_configured(remote, 1))
- die(_("No such remote: %s"), argv[1]);
+ die(_("No such remote: '%s'"), argv[1]);
known_remotes.to_delete = remote;
for_each_remote(add_known_remote, &known_remotes);
states->remote = remote_get(name);
if (!states->remote)
- return error(_("No such remote: %s"), name);
+ return error(_("No such remote: '%s'"), name);
read_branches();
#include "strbuf.h"
#include "string-list.h"
#include "argv-array.h"
+#include "midx.h"
#include "packfile.h"
#include "object-store.h"
static int delta_base_offset = 1;
static int pack_kept_objects = -1;
static int write_bitmaps;
+static int use_delta_islands;
static char *packdir, *packtmp;
static const char *const git_repack_usage[] = {
write_bitmaps = git_config_bool(var, value);
return 0;
}
+ if (!strcmp(var, "repack.usedeltaislands")) {
+ use_delta_islands = git_config_bool(var, value);
+ return 0;
+ }
return git_default_config(var, value, cb);
}
while (strbuf_getline_lf(&line, out) != EOF) {
char *promisor_name;
int fd;
- if (line.len != 40)
- die("repack: Expecting 40 character sha1 lines only from pack-objects.");
+ if (line.len != the_hash_algo->hexsz)
+ die("repack: Expecting full hex object ID lines only from pack-objects.");
string_list_append(names, line.buf);
/*
int keep_unreachable = 0;
struct string_list keep_pack_list = STRING_LIST_INIT_NODUP;
int no_update_server_info = 0;
+ int midx_cleared = 0;
struct pack_objects_args po_args = {NULL};
struct option builtin_repack_options[] = {
N_("pass --local to git-pack-objects")),
OPT_BOOL('b', "write-bitmap-index", &write_bitmaps,
N_("write bitmap index")),
+ OPT_BOOL('i', "delta-islands", &use_delta_islands,
+ N_("pass --delta-islands to git-pack-objects")),
OPT_STRING(0, "unpack-unreachable", &unpack_unreachable, N_("approxidate"),
N_("with -A, do not loosen objects older than this")),
OPT_BOOL('k', "keep-unreachable", &keep_unreachable,
argv_array_push(&cmd.args, "--exclude-promisor-objects");
if (write_bitmaps)
argv_array_push(&cmd.args, "--write-bitmap-index");
+ if (use_delta_islands)
+ argv_array_push(&cmd.args, "--delta-islands");
if (pack_everything & ALL_INTO_ONE) {
get_non_kept_pack_filenames(&existing_packs, &keep_pack_list);
out = xfdopen(cmd.out, "r");
while (strbuf_getline_lf(&line, out) != EOF) {
- if (line.len != 40)
- die("repack: Expecting 40 character sha1 lines only from pack-objects.");
+ if (line.len != the_hash_algo->hexsz)
+ die("repack: Expecting full hex object ID lines only from pack-objects.");
string_list_append(&names, line.buf);
}
fclose(out);
for_each_string_list_item(item, &names) {
for (ext = 0; ext < ARRAY_SIZE(exts); ext++) {
char *fname, *fname_old;
+
+ if (!midx_cleared) {
+ /* if we move a packfile, it will invalidated the midx */
+ clear_midx_file(get_object_directory());
+ midx_cleared = 1;
+ }
+
fname = mkpathdup("%s/pack-%s%s", packdir,
item->string, exts[ext].name);
if (!file_exists(fname)) {
reprepare_packed_git(the_repository);
if (delete_redundant) {
+ const int hexsz = the_hash_algo->hexsz;
int opts = 0;
string_list_sort(&names);
for_each_string_list_item(item, &existing_packs) {
char *sha1;
size_t len = strlen(item->string);
- if (len < 40)
+ if (len < hexsz)
continue;
- sha1 = item->string + len - 40;
+ sha1 = item->string + len - hexsz;
if (!string_list_has_string(&names, sha1))
remove_redundant_pack(packdir, item->string);
}
enum replace_format format;
};
-static int show_reference(const char *refname, const struct object_id *oid,
+static int show_reference(struct repository *r, const char *refname,
+ const struct object_id *oid,
int flag, void *cb_data)
{
struct show_data *data = cb_data;
if (get_oid(refname, &object))
return error(_("failed to resolve '%s' as a valid ref"), refname);
- obj_type = oid_object_info(the_repository, &object,
- NULL);
- repl_type = oid_object_info(the_repository, oid, NULL);
+ obj_type = oid_object_info(r, &object, NULL);
+ repl_type = oid_object_info(r, oid, NULL);
printf("%s (%s) -> %s (%s)\n", refname, type_name(obj_type),
oid_to_hex(oid), type_name(repl_type));
close(fd);
return -1;
}
- if (index_fd(oid, fd, &st, type, NULL, flags) < 0)
+ if (index_fd(&the_index, oid, fd, &st, type, NULL, flags) < 0)
return error(_("unable to write object to database"));
/* index_fd close()s fd for us */
}
}
free(tmpfile);
- if (!oidcmp(&old_oid, &new_oid))
+ if (oideq(&old_oid, &new_oid))
return error(_("new object is the same as the old one: '%s'"), oid_to_hex(&old_oid));
return replace_object_oid(object_ref, &old_oid, "replacement", &new_oid, force);
if (get_oid(mergetag_data->argv[i], &oid) < 0)
return error(_("not a valid object name: '%s'"),
mergetag_data->argv[i]);
- if (!oidcmp(&tag->tagged->oid, &oid))
+ if (oideq(&tag->tagged->oid, &oid))
return 0; /* found */
}
strbuf_release(&buf);
- if (!oidcmp(&old_oid, &new_oid)) {
+ if (oideq(&old_oid, &new_oid)) {
if (gentle) {
warning(_("graft for '%s' unnecessary"), oid_to_hex(&old_oid));
return 0;
flags = RERERE_NOAUTOUPDATE;
if (argc < 1)
- return rerere(flags);
+ return repo_rerere(the_repository, flags);
if (!strcmp(argv[0], "forget")) {
struct pathspec pathspec;
if (argc < 2)
- warning("'git rerere forget' without paths is deprecated");
+ warning(_("'git rerere forget' without paths is deprecated"));
parse_pathspec(&pathspec, 0, PATHSPEC_PREFER_CWD,
prefix, argv + 1);
- return rerere_forget(&pathspec);
+ return rerere_forget(the_repository, &pathspec);
}
if (!strcmp(argv[0], "clear")) {
for (i = 0; i < merge_rr.nr; i++)
printf("%s\n", merge_rr.items[i].string);
} else if (!strcmp(argv[0], "remaining")) {
- rerere_remaining(&merge_rr);
+ rerere_remaining(the_repository, &merge_rr);
for (i = 0; i < merge_rr.nr; i++) {
if (merge_rr.items[i].util != RERERE_RESOLVED)
printf("%s\n", merge_rr.items[i].string);
const char *path = merge_rr.items[i].string;
const struct rerere_id *id = merge_rr.items[i].util;
if (diff_two(rerere_path(id, "preimage"), path, path, path))
- die("unable to generate diff for %s", rerere_path(id, NULL));
+ die(_("unable to generate diff for '%s'"), rerere_path(id, NULL));
}
} else
usage_with_options(rerere_usage, options);
opt.format_callback = update_index_from_diff;
opt.format_callback_data = &intent_to_add;
opt.flags.override_submodule_config = 1;
+ opt.repo = the_repository;
if (do_diff_cache(tree_oid, &opt))
return 1;
#include "list-objects.h"
#include "list-objects-filter.h"
#include "list-objects-filter-options.h"
+#include "object.h"
#include "object-store.h"
#include "pack.h"
#include "pack-bitmap.h"
*/
switch (arg_missing_action) {
case MA_ERROR:
- die("missing blob object '%s'", oid_to_hex(&obj->oid));
+ die("missing %s object '%s'",
+ type_name(obj->type), oid_to_hex(&obj->oid));
return;
case MA_ALLOW_ANY:
case MA_ALLOW_PROMISOR:
if (is_promisor_object(&obj->oid))
return;
- die("unexpected missing blob object '%s'",
- oid_to_hex(&obj->oid));
+ die("unexpected missing %s object '%s'",
+ type_name(obj->type), oid_to_hex(&obj->oid));
return;
default:
static int finish_object(struct object *obj, const char *name, void *cb_data)
{
struct rev_list_info *info = cb_data;
- if (obj->type == OBJ_BLOB && !has_object_file(&obj->oid)) {
+ if (!has_object_file(&obj->oid)) {
finish_object__ma(obj);
return 1;
}
usage(rev_list_usage);
git_config(git_default_config, NULL);
- init_revisions(&revs, prefix);
+ repo_init_revisions(the_repository, &revs, prefix);
revs.abbrev = DEFAULT_ABBREV;
revs.commit_format = CMIT_FMT_UNSPECIFIED;
+ revs.do_not_die_on_missing_tree = 1;
/*
* Scan the argument list before invoking setup_revisions(), so that we
if ((!revs.commits && reflog_walk_empty(revs.reflog_info) &&
(!(revs.tag_objects || revs.tree_objects || revs.blob_objects) &&
!revs.pending.nr) &&
- !revs.rev_input_given) ||
+ !revs.rev_input_given && !revs.read_from_stdin) ||
revs.diff)
usage(rev_list_usage);
#include "revision.h"
#include "split-index.h"
#include "submodule.h"
+#include "commit-reach.h"
#define DO_REVS 1
#define DO_NOREV 2
} else {
struct setup_revision_opt s_r_opt;
opts->revs = xmalloc(sizeof(*opts->revs));
- init_revisions(opts->revs, NULL);
+ repo_init_revisions(the_repository, opts->revs, NULL);
opts->revs->no_walk = REVISION_WALK_NO_WALK_UNSORTED;
if (argc < 2)
usage_with_options(usage_str, options);
if (no_head
|| get_tree_entry(head, name, &oid, &mode)
|| ce->ce_mode != create_ce_mode(mode)
- || oidcmp(&ce->oid, &oid))
+ || !oideq(&ce->oid, &oid))
staged_changes = 1;
/*
git_config(git_default_config, NULL);
shortlog_init(&log);
- init_revisions(&rev, prefix);
+ repo_init_revisions(the_repository, &rev, prefix);
parse_options_start(&ctx, argc, argv, prefix, options,
PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_ARGV0);
/* If both heads/foo and tags/foo exists, get_sha1 would
* get confused.
*/
- if (get_oid(refname + ofs, &tmp) || oidcmp(&tmp, oid))
+ if (get_oid(refname + ofs, &tmp) || !oideq(&tmp, oid))
ofs = 5;
return append_ref(refname + ofs, oid, 0);
}
/* If both heads/foo and tags/foo exists, get_sha1 would
* get confused.
*/
- if (get_oid(refname + ofs, &tmp) || oidcmp(&tmp, oid))
+ if (get_oid(refname + ofs, &tmp) || !oideq(&tmp, oid))
ofs = 5;
return append_ref(refname + ofs, oid, 0);
}
static int rev_is_head(const char *head, const char *name,
unsigned char *head_sha1, unsigned char *sha1)
{
- if (!head || (head_sha1 && sha1 && hashcmp(head_sha1, sha1)))
+ if (!head || (head_sha1 && sha1 && !hasheq(head_sha1, sha1)))
return 0;
skip_prefix(head, "refs/heads/", &head);
if (!skip_prefix(name, "refs/heads/", &name))
path, NULL);
git_config(git_diff_basic_config, NULL);
- init_revisions(&rev, prefix);
+ repo_init_revisions(the_repository, &rev, prefix);
rev.abbrev = 0;
diff_files_args.argc = setup_revisions(diff_files_args.argc,
diff_files_args.argv,
return 0;
}
+static void determine_submodule_update_strategy(struct repository *r,
+ int just_cloned,
+ const char *path,
+ const char *update,
+ struct submodule_update_strategy *out)
+{
+ const struct submodule *sub = submodule_from_path(r, &null_oid, path);
+ char *key;
+ const char *val;
+
+ key = xstrfmt("submodule.%s.update", sub->name);
+
+ if (update) {
+ if (parse_submodule_update_strategy(update, out) < 0)
+ die(_("Invalid update mode '%s' for submodule path '%s'"),
+ update, path);
+ } else if (!repo_config_get_string_const(r, key, &val)) {
+ if (parse_submodule_update_strategy(val, out) < 0)
+ die(_("Invalid update mode '%s' configured for submodule path '%s'"),
+ val, path);
+ } else if (sub->update_strategy.type != SM_UPDATE_UNSPECIFIED) {
+ out->type = sub->update_strategy.type;
+ out->command = sub->update_strategy.command;
+ } else
+ out->type = SM_UPDATE_CHECKOUT;
+
+ if (just_cloned &&
+ (out->type == SM_UPDATE_MERGE ||
+ out->type == SM_UPDATE_REBASE ||
+ out->type == SM_UPDATE_NONE))
+ out->type = SM_UPDATE_CHECKOUT;
+
+ free(key);
+}
+
+static int module_update_module_mode(int argc, const char **argv, const char *prefix)
+{
+ const char *path, *update = NULL;
+ int just_cloned;
+ struct submodule_update_strategy update_strategy = { .type = SM_UPDATE_CHECKOUT };
+
+ if (argc < 3 || argc > 4)
+ die("submodule--helper update-module-clone expects <just-cloned> <path> [<update>]");
+
+ just_cloned = git_config_int("just_cloned", argv[1]);
+ path = argv[2];
+
+ if (argc == 4)
+ update = argv[3];
+
+ determine_submodule_update_strategy(the_repository,
+ just_cloned, path, update,
+ &update_strategy);
+ fputs(submodule_strategy_to_string(&update_strategy), stdout);
+
+ return 0;
+}
+
+struct update_clone_data {
+ const struct submodule *sub;
+ struct object_id oid;
+ unsigned just_cloned;
+};
+
struct submodule_update_clone {
/* index into 'list', the list of submodules to look into for cloning */
int current;
const char *recursive_prefix;
const char *prefix;
- /* Machine-readable status lines to be consumed by git-submodule.sh */
- struct string_list projectlines;
+ /* to be consumed by git-submodule.sh */
+ struct update_clone_data *update_clone;
+ int update_clone_nr; int update_clone_alloc;
/* If we want to stop as fast as possible and return an error */
unsigned quickstop : 1;
/* failed clones to be retried again */
const struct cache_entry **failed_clones;
int failed_clones_nr, failed_clones_alloc;
+
+ int max_jobs;
};
#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, \
- STRING_LIST_INIT_DUP, 0, NULL, 0, 0}
+ NULL, 0, 0, 0, NULL, 0, 0, 0}
static void next_submodule_warn_missing(struct submodule_update_clone *suc,
strbuf_addf(&sb, "%s/.git", ce->name);
needs_cloning = !file_exists(sb.buf);
- strbuf_reset(&sb);
- strbuf_addf(&sb, "%06o %s %d %d\t%s\n", ce->ce_mode,
- oid_to_hex(&ce->oid), ce_stage(ce),
- needs_cloning, ce->name);
- string_list_append(&suc->projectlines, sb.buf);
+ ALLOC_GROW(suc->update_clone, suc->update_clone_nr + 1,
+ suc->update_clone_alloc);
+ oidcpy(&suc->update_clone[suc->update_clone_nr].oid, &ce->oid);
+ suc->update_clone[suc->update_clone_nr].just_cloned = needs_cloning;
+ suc->update_clone[suc->update_clone_nr].sub = sub;
+ suc->update_clone_nr++;
if (!needs_cloning)
goto cleanup;
return 0;
}
+static void update_submodule(struct update_clone_data *ucd)
+{
+ fprintf(stdout, "dummy %s %d\t%s\n",
+ oid_to_hex(&ucd->oid),
+ ucd->just_cloned,
+ ucd->sub->path);
+}
+
+static int update_submodules(struct submodule_update_clone *suc)
+{
+ int i;
+
+ run_processes_parallel(suc->max_jobs,
+ update_clone_get_next_task,
+ update_clone_start_failure,
+ update_clone_task_finished,
+ suc);
+
+ /*
+ * We saved the output and put it out all at once now.
+ * That means:
+ * - the listener does not have to interleave their (checkout)
+ * work with our fetching. The writes involved in a
+ * checkout involve more straightforward sequential I/O.
+ * - the listener can avoid doing any work if fetching failed.
+ */
+ if (suc->quickstop)
+ return 1;
+
+ for (i = 0; i < suc->update_clone_nr; i++)
+ update_submodule(&suc->update_clone[i]);
+
+ return 0;
+}
+
static int update_clone(int argc, const char **argv, const char *prefix)
{
const char *update = NULL;
- int max_jobs = 1;
- struct string_list_item *item;
struct pathspec pathspec;
struct submodule_update_clone suc = SUBMODULE_UPDATE_CLONE_INIT;
OPT_STRING(0, "depth", &suc.depth, "<depth>",
N_("Create a shallow clone truncated to the "
"specified number of revisions")),
- OPT_INTEGER('j', "jobs", &max_jobs,
+ OPT_INTEGER('j', "jobs", &suc.max_jobs,
N_("parallel jobs")),
OPT_BOOL(0, "recommend-shallow", &suc.recommend_shallow,
N_("whether the initial clone should follow the shallow recommendation")),
};
suc.prefix = prefix;
- update_clone_config_from_gitmodules(&max_jobs);
- git_config(git_update_clone_config, &max_jobs);
+ update_clone_config_from_gitmodules(&suc.max_jobs);
+ git_config(git_update_clone_config, &suc.max_jobs);
argc = parse_options(argc, argv, prefix, module_update_clone_options,
git_submodule_helper_usage, 0);
if (pathspec.nr)
suc.warn_if_uninitialized = 1;
- run_processes_parallel(max_jobs,
- update_clone_get_next_task,
- update_clone_start_failure,
- update_clone_task_finished,
- &suc);
-
- /*
- * We saved the output and put it out all at once now.
- * That means:
- * - the listener does not have to interleave their (checkout)
- * work with our fetching. The writes involved in a
- * checkout involve more straightforward sequential I/O.
- * - the listener can avoid doing any work if fetching failed.
- */
- if (suc.quickstop)
- return 1;
-
- for_each_string_list_item(item, &suc.projectlines)
- fprintf(stdout, "%s", item->string);
-
- return 0;
+ return update_submodules(&suc);
}
static int resolve_relative_path(int argc, const char **argv, const char *prefix)
return 0;
}
+static int ensure_core_worktree(int argc, const char **argv, const char *prefix)
+{
+ const struct submodule *sub;
+ const char *path;
+ char *cw;
+ struct repository subrepo;
+
+ if (argc != 2)
+ BUG("submodule--helper connect-gitdir-workingtree <name> <path>");
+
+ path = argv[1];
+
+ sub = submodule_from_path(the_repository, &null_oid, path);
+ if (!sub)
+ BUG("We could get the submodule handle before?");
+
+ if (repo_submodule_init(&subrepo, the_repository, path))
+ die(_("could not get a repository handle for submodule '%s'"), path);
+
+ if (!repo_config_get_string(&subrepo, "core.worktree", &cw)) {
+ char *cfg_file, *abs_path;
+ const char *rel_path;
+ struct strbuf sb = STRBUF_INIT;
+
+ cfg_file = repo_git_path(&subrepo, "config");
+
+ abs_path = absolute_pathdup(path);
+ rel_path = relative_path(abs_path, subrepo.gitdir, &sb);
+
+ git_config_set_in_file(cfg_file, "core.worktree", rel_path);
+
+ free(cfg_file);
+ free(abs_path);
+ strbuf_release(&sb);
+ }
+
+ return 0;
+}
+
static int absorb_git_dirs(int argc, const char **argv, const char *prefix)
{
int i;
{"list", module_list, 0},
{"name", module_name, 0},
{"clone", module_clone, 0},
+ {"update-module-mode", module_update_module_mode, 0},
{"update-clone", update_clone, 0},
+ {"ensure-core-worktree", ensure_core_worktree, 0},
{"relative-path", resolve_relative_path, 0},
{"resolve-relative-url", resolve_relative_url, 0},
{"resolve-relative-url-test", resolve_relative_url_test, 0},
ref_transaction_commit(transaction, &err))
die("%s", err.buf);
ref_transaction_free(transaction);
- if (force && !is_null_oid(&prev) && oidcmp(&prev, &object))
+ if (force && !is_null_oid(&prev) && !oideq(&prev, &object))
printf(_("Updated tag '%s' (was %s)\n"), tag,
find_unique_abbrev(&prev, DEFAULT_ABBREV));
struct delta_info *info;
while ((info = *p) != NULL) {
- if (!oidcmp(&info->base_oid, &obj_list[nr].oid) ||
+ if (oideq(&info->base_oid, &obj_list[nr].oid) ||
info->base_offset == obj_list[nr].offset) {
*p = info->next;
p = &delta_list;
if (fsck_finish(&fsck_options))
die(_("fsck error in pack objects"));
}
- if (hashcmp(fill(the_hash_algo->rawsz), oid.hash))
+ if (!hasheq(fill(the_hash_algo->rawsz), oid.hash))
die("final sha1 did not match");
use(the_hash_algo->rawsz);
fill_stat_cache_info(ce, st);
ce->ce_mode = ce_mode_from_stat(old, st->st_mode);
- if (index_path(&ce->oid, path, st,
+ if (index_path(&the_index, &ce->oid, path, st,
info_only ? 0 : HASH_WRITE_OBJECT)) {
discard_cache_entry(ce);
return -1;
ret = -1;
goto free_return;
}
- if (!oidcmp(&ce_2->oid, &ce_3->oid) &&
+ if (oideq(&ce_2->oid, &ce_3->oid) &&
ce_2->ce_mode == ce_3->ce_mode) {
fprintf(stderr, "%s: identical in both, skipping.\n",
path);
old = read_one_ent(NULL, &head_oid,
ce->name, ce_namelen(ce), 0);
if (old && ce->ce_mode == old->ce_mode &&
- !oidcmp(&ce->oid, &old->oid)) {
+ oideq(&ce->oid, &old->oid)) {
discard_cache_entry(old);
continue; /* unchanged */
}
};
static char line_termination = '\n';
-static int update_flags;
+static unsigned int update_flags;
+static unsigned int default_flags;
static unsigned create_reflog_flag;
static const char *msg;
msg, &err))
die("%s", err.buf);
- update_flags = 0;
+ update_flags = default_flags;
free(refname);
strbuf_release(&err);
msg, &err))
die("%s", err.buf);
- update_flags = 0;
+ update_flags = default_flags;
free(refname);
strbuf_release(&err);
update_flags, msg, &err))
die("%s", err.buf);
- update_flags = 0;
+ update_flags = default_flags;
free(refname);
strbuf_release(&err);
update_flags, &err))
die("%s", err.buf);
- update_flags = 0;
+ update_flags = default_flags;
free(refname);
strbuf_release(&err);
const char *refname, *oldval;
struct object_id oid, oldoid;
int delete = 0, no_deref = 0, read_stdin = 0, end_null = 0;
- unsigned int flags = 0;
int create_reflog = 0;
struct option options[] = {
OPT_STRING( 'm', NULL, &msg, N_("reason"), N_("reason of the update")),
create_reflog_flag = create_reflog ? REF_FORCE_CREATE_REFLOG : 0;
+ if (no_deref) {
+ default_flags = REF_NO_DEREF;
+ update_flags = default_flags;
+ }
+
if (read_stdin) {
struct strbuf err = STRBUF_INIT;
struct ref_transaction *transaction;
transaction = ref_transaction_begin(&err);
if (!transaction)
die("%s", err.buf);
- if (delete || no_deref || argc > 0)
+ if (delete || argc > 0)
usage_with_options(git_update_ref_usage, options);
if (end_null)
line_termination = '\0';
die("%s: not a valid old SHA1", oldval);
}
- if (no_deref)
- flags = REF_NO_DEREF;
if (delete)
/*
* For purposes of backwards compatibility, we treat
*/
return delete_ref(msg, refname,
(oldval && !is_null_oid(&oldoid)) ? &oldoid : NULL,
- flags);
+ default_flags);
else
return update_ref(msg, refname, &oid, oldval ? &oldoid : NULL,
- flags | create_reflog_flag,
+ default_flags | create_reflog_flag,
UPDATE_REFS_DIE_ON_ERR);
}
return git_default_config(var, value, cb);
}
+static int delete_git_dir(const char *id)
+{
+ struct strbuf sb = STRBUF_INIT;
+ int ret;
+
+ strbuf_addstr(&sb, git_common_path("worktrees/%s", id));
+ ret = remove_dir_recursively(&sb, 0);
+ if (ret < 0 && errno == ENOTDIR)
+ ret = unlink(sb.buf);
+ if (ret)
+ error_errno(_("failed to delete '%s'"), sb.buf);
+ strbuf_release(&sb);
+ return ret;
+}
+
+static void delete_worktrees_dir_if_empty(void)
+{
+ rmdir(git_path("worktrees")); /* ignore failed removal */
+}
+
static int prune_worktree(const char *id, struct strbuf *reason)
{
struct stat st;
static void prune_worktrees(void)
{
struct strbuf reason = STRBUF_INIT;
- struct strbuf path = STRBUF_INIT;
DIR *dir = opendir(git_path("worktrees"));
struct dirent *d;
- int ret;
if (!dir)
return;
while ((d = readdir(dir)) != NULL) {
printf("%s\n", reason.buf);
if (show_only)
continue;
- git_path_buf(&path, "worktrees/%s", d->d_name);
- ret = remove_dir_recursively(&path, 0);
- if (ret < 0 && errno == ENOTDIR)
- ret = unlink(path.buf);
- if (ret)
- error_errno(_("failed to remove '%s'"), path.buf);
+ delete_git_dir(d->d_name);
}
closedir(dir);
if (!show_only)
- rmdir(git_path("worktrees"));
+ delete_worktrees_dir_if_empty();
strbuf_release(&reason);
- strbuf_release(&path);
}
static int prune(int ac, const char **av, const char *prefix)
return name;
}
+static void validate_worktree_add(const char *path, const struct add_opts *opts)
+{
+ struct worktree **worktrees;
+ struct worktree *wt;
+ int locked;
+
+ if (file_exists(path) && !is_empty_dir(path))
+ die(_("'%s' already exists"), path);
+
+ worktrees = get_worktrees(0);
+ /*
+ * find_worktree()'s suffix matching may undesirably find the main
+ * rather than a linked worktree (for instance, when the basenames
+ * of the main worktree and the one being created are the same).
+ * We're only interested in linked worktrees, so skip the main
+ * worktree with +1.
+ */
+ wt = find_worktree(worktrees + 1, NULL, path);
+ if (!wt)
+ goto done;
+
+ locked = !!is_worktree_locked(wt);
+ if ((!locked && opts->force) || (locked && opts->force > 1)) {
+ if (delete_git_dir(wt->id))
+ die(_("unable to re-add worktree '%s'"), path);
+ goto done;
+ }
+
+ if (locked)
+ die(_("'%s' is a missing but locked worktree;\nuse 'add -f -f' to override, or 'unlock' and 'prune' or 'remove' to clear"), path);
+ else
+ die(_("'%s' is a missing but already registered worktree;\nuse 'add -f' to override, or 'prune' or 'remove' to clear"), path);
+
+done:
+ free_worktrees(worktrees);
+}
+
static int add_worktree(const char *path, const char *refname,
const struct add_opts *opts)
{
struct commit *commit = NULL;
int is_branch = 0;
- if (file_exists(path) && !is_empty_dir(path))
- die(_("'%s' already exists"), path);
+ validate_worktree_add(path, opts);
/* is 'refname' a branch or commit? */
if (!opts->detach && !strbuf_check_branch_ref(&symref, refname) &&
static int move_worktree(int ac, const char **av, const char *prefix)
{
+ int force = 0;
struct option options[] = {
+ OPT__FORCE(&force,
+ N_("force move even if worktree is dirty or locked"),
+ PARSE_OPT_NOCOMPLETE),
OPT_END()
};
struct worktree **worktrees, *wt;
struct strbuf dst = STRBUF_INIT;
struct strbuf errmsg = STRBUF_INIT;
- const char *reason;
+ const char *reason = NULL;
char *path;
ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
validate_no_submodules(wt);
- reason = is_worktree_locked(wt);
+ if (force < 2)
+ reason = is_worktree_locked(wt);
if (reason) {
if (*reason)
- die(_("cannot move a locked working tree, lock reason: %s"),
+ die(_("cannot move a locked working tree, lock reason: %s\nuse 'move -f -f' to override or unlock first"),
reason);
- die(_("cannot move a locked working tree"));
+ die(_("cannot move a locked working tree;\nuse 'move -f -f' to override or unlock first"));
}
if (validate_worktree(wt, &errmsg, 0))
die(_("validation failed, cannot move working tree: %s"),
return ret;
}
-static int delete_git_dir(struct worktree *wt)
-{
- struct strbuf sb = STRBUF_INIT;
- int ret = 0;
-
- strbuf_addstr(&sb, git_common_path("worktrees/%s", wt->id));
- if (remove_dir_recursively(&sb, 0)) {
- error_errno(_("failed to delete '%s'"), sb.buf);
- ret = -1;
- }
- strbuf_release(&sb);
- return ret;
-}
-
static int remove_worktree(int ac, const char **av, const char *prefix)
{
int force = 0;
struct option options[] = {
OPT__FORCE(&force,
- N_("force removing even if the worktree is dirty"),
+ N_("force removal even if worktree is dirty or locked"),
PARSE_OPT_NOCOMPLETE),
OPT_END()
};
struct worktree **worktrees, *wt;
struct strbuf errmsg = STRBUF_INIT;
- const char *reason;
+ const char *reason = NULL;
int ret = 0;
ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
die(_("'%s' is not a working tree"), av[0]);
if (is_main_worktree(wt))
die(_("'%s' is a main working tree"), av[0]);
- reason = is_worktree_locked(wt);
+ if (force < 2)
+ reason = is_worktree_locked(wt);
if (reason) {
if (*reason)
- die(_("cannot remove a locked working tree, lock reason: %s"),
+ die(_("cannot remove a locked working tree, lock reason: %s\nuse 'remove -f -f' to override or unlock first"),
reason);
- die(_("cannot remove a locked working tree"));
+ die(_("cannot remove a locked working tree;\nuse 'remove -f -f' to override or unlock first"));
}
if (validate_worktree(wt, &errmsg, WT_VALIDATE_WORKTREE_MISSING_OK))
die(_("validation failed, cannot remove working tree: %s"),
* continue on even if ret is non-zero, there's no going back
* from here.
*/
- ret |= delete_git_dir(wt);
+ ret |= delete_git_dir(wt->id);
+ delete_worktrees_dir_if_empty();
free_worktrees(worktrees);
return ret;
/* Might want to keep the list sorted */
for (i = 0; i < state->nr_written; i++)
- if (!oidcmp(&state->written[i]->oid, oid))
+ if (oideq(&state->written[i]->oid, oid))
return 1;
/* This is a new object we need to keep */
int i, ret = 0, req_nr;
const char *message = _("Repository lacks these prerequisite commits:");
- init_revisions(&revs, NULL);
+ repo_init_revisions(the_repository, &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);
* commit that is referenced by the tag, and not the tag
* itself.
*/
- if (oidcmp(&oid, &e->item->oid)) {
+ if (!oideq(&oid, &e->item->oid)) {
/*
* Is this the positive end of a range expressed
* in terms of a tag (e.g. v2.0 from the range
/* init revs to list objects for pack-objects later */
save_commit_buffer = 0;
- init_revisions(&revs, NULL);
+ repo_init_revisions(the_repository, &revs, NULL);
/* write prerequisites */
if (compute_and_write_prerequisites(bundle_fd, &revs, argc, argv))
#include "tree-walk.h"
#include "cache-tree.h"
#include "object-store.h"
+#include "replace-object.h"
#ifndef DEBUG
#define DEBUG 0
unsigned mode;
int expected_missing = 0;
int contains_ita = 0;
+ int ce_missing_ok;
path = ce->name;
pathlen = ce_namelen(ce);
i++;
}
+ ce_missing_ok = mode == S_IFGITLINK || missing_ok ||
+ (repository_format_partial_clone &&
+ ce_skip_worktree(ce));
if (is_null_oid(oid) ||
- (mode != S_IFGITLINK && !missing_ok && !has_object_file(oid))) {
+ (!ce_missing_ok && !has_object_file(oid))) {
strbuf_release(&buffer);
if (expected_missing)
return -1;
if (i)
return i;
+ trace_performance_enter();
i = update_one(it, cache, entries, "", 0, &skip, flags);
+ trace_performance_leave("cache_tree_update");
if (i < 0)
return i;
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 && !oidcmp(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,
+ struct cache_tree *it,
+ struct strbuf *path)
+{
+ int i, pos, len = path->len;
+ struct strbuf tree_buf = STRBUF_INIT;
+ struct object_id new_oid;
+
+ 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);
+ 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)
+ return;
+
+ if (path->len) {
+ pos = index_name_pos(istate, path->buf, path->len);
+ pos = -pos - 1;
+ } else {
+ pos = 0;
+ }
+
+ i = 0;
+ while (i < it->entry_count) {
+ struct cache_entry *ce = istate->cache[pos + i];
+ const char *slash;
+ struct cache_tree_sub *sub = NULL;
+ const struct object_id *oid;
+ const char *name;
+ unsigned mode;
+ int entlen;
+
+ if (ce->ce_flags & (CE_STAGEMASK | CE_INTENT_TO_ADD | CE_REMOVE))
+ BUG("%s with flags 0x%x should not be in cache-tree",
+ ce->name, ce->ce_flags);
+ name = ce->name + path->len;
+ slash = strchr(name, '/');
+ if (slash) {
+ entlen = slash - name;
+ sub = find_subtree(it, ce->name + path->len, entlen, 0);
+ if (!sub || sub->cache_tree->entry_count < 0)
+ BUG("bad subtree '%.*s'", entlen, name);
+ oid = &sub->cache_tree->oid;
+ mode = S_IFDIR;
+ i += sub->cache_tree->entry_count;
+ } else {
+ oid = &ce->oid;
+ mode = ce->ce_mode;
+ entlen = ce_namelen(ce) - path->len;
+ i++;
+ }
+ strbuf_addf(&tree_buf, "%o %.*s%c", mode, entlen, name, '\0');
+ strbuf_add(&tree_buf, oid->hash, the_hash_algo->rawsz);
+ }
+ hash_object_file(tree_buf.buf, tree_buf.len, tree_type, &new_oid);
+ if (!oideq(&new_oid, &it->oid))
+ BUG("cache-tree for path %.*s does not match. "
+ "Expected %s got %s", len, path->buf,
+ oid_to_hex(&new_oid), oid_to_hex(&it->oid));
+ strbuf_setlen(path, len);
+ strbuf_release(&tree_buf);
+}
+
+void cache_tree_verify(struct index_state *istate)
+{
+ struct strbuf path = STRBUF_INIT;
+
+ if (!istate->cache_tree)
+ return;
+ verify_one(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 *);
/* bitmasks to write_cache_as_tree flags */
#define WRITE_TREE_MISSING_OK 1
#define read_cache() read_index(&the_index)
#define read_cache_from(path) read_index_from(&the_index, (path), (get_git_dir()))
-#define read_cache_preload(pathspec) read_index_preload(&the_index, (pathspec))
+#define read_cache_preload(pathspec) read_index_preload(&the_index, (pathspec), 0)
#define is_cache_unborn() is_index_unborn(&the_index)
#define read_cache_unmerged() read_index_unmerged(&the_index)
#define discard_cache() discard_index(&the_index)
/* Initialize and use the cache information */
struct lock_file;
extern int read_index(struct index_state *);
-extern int read_index_preload(struct index_state *, const struct pathspec *pathspec);
+extern int read_index_preload(struct index_state *,
+ const struct pathspec *pathspec,
+ unsigned int refresh_flags);
extern int do_read_index(struct index_state *istate, const char *path,
int must_exist); /* for testting only! */
extern int read_index_from(struct index_state *, const char *path,
* provided, the space-separated list of files that differ will be appended
* to it.
*/
-extern int index_has_changes(const struct index_state *istate,
+extern int index_has_changes(struct index_state *istate,
struct tree *tree,
struct strbuf *sb);
#define CE_MATCH_REFRESH 0x10
/* don't refresh_fsmonitor state or do stat comparison even if CE_FSMONITOR_VALID is true */
#define CE_MATCH_IGNORE_FSMONITOR 0X20
+extern int is_racy_timestamp(const struct index_state *istate,
+ const struct cache_entry *ce);
extern int ie_match_stat(struct index_state *, const struct cache_entry *, struct stat *, unsigned int);
extern int ie_modified(struct index_state *, const struct cache_entry *, struct stat *, unsigned int);
#define HASH_WRITE_OBJECT 1
#define HASH_FORMAT_CHECK 2
#define HASH_RENORMALIZE 4
-extern int index_fd(struct object_id *oid, int fd, struct stat *st, enum object_type type, const char *path, unsigned flags);
-extern int index_path(struct object_id *oid, const char *path, struct stat *st, unsigned flags);
+extern int index_fd(struct index_state *istate, struct object_id *oid, int fd, struct stat *st, enum object_type type, const char *path, unsigned flags);
+extern int index_path(struct index_state *istate, struct object_id *oid, const char *path, struct stat *st, unsigned flags);
/*
* Record to sd the data from st that we use to check whether a file
#define REFRESH_IGNORE_MISSING 0x0008 /* ignore non-existent */
#define REFRESH_IGNORE_SUBMODULES 0x0010 /* ignore submodules */
#define REFRESH_IN_PORCELAIN 0x0020 /* user friendly output, not "needs update" */
+#define REFRESH_PROGRESS 0x0040 /* show progress bar if stderr is tty */
extern int refresh_index(struct index_state *, unsigned int flags, const struct pathspec *pathspec, char *seen, const char *header_msg);
extern struct cache_entry *refresh_cache_entry(struct index_state *, struct cache_entry *, unsigned int);
return hashcmp(oid1->hash, oid2->hash);
}
+static inline int hasheq(const unsigned char *sha1, const unsigned char *sha2)
+{
+ return !hashcmp(sha1, sha2);
+}
+
+static inline int oideq(const struct object_id *oid1, const struct object_id *oid2)
+{
+ return hasheq(oid1->hash, oid2->hash);
+}
+
static inline int is_null_sha1(const unsigned char *sha1)
{
- return !hashcmp(sha1, null_sha1);
+ return hasheq(sha1, null_sha1);
}
static inline int is_null_oid(const struct object_id *oid)
{
- return !hashcmp(oid->hash, null_sha1);
+ return hasheq(oid->hash, null_sha1);
}
static inline void hashcpy(unsigned char *sha_dst, const unsigned char *sha_src)
static inline int is_empty_blob_sha1(const unsigned char *sha1)
{
- return !hashcmp(sha1, the_hash_algo->empty_blob->hash);
+ return hasheq(sha1, the_hash_algo->empty_blob->hash);
}
static inline int is_empty_blob_oid(const struct object_id *oid)
{
- return !oidcmp(oid, the_hash_algo->empty_blob);
+ return oideq(oid, the_hash_algo->empty_blob);
}
static inline int is_empty_tree_sha1(const unsigned char *sha1)
{
- return !hashcmp(sha1, the_hash_algo->empty_tree->hash);
+ return hasheq(sha1, the_hash_algo->empty_tree->hash);
}
static inline int is_empty_tree_oid(const struct object_id *oid)
{
- return !oidcmp(oid, the_hash_algo->empty_tree);
+ return oideq(oid, the_hash_algo->empty_tree);
}
const char *empty_tree_oid_hex(void);
unsigned force:1,
quiet:1,
not_new:1,
+ clone:1,
refresh_cache:1;
};
#define CHECKOUT_INIT { NULL, "" }
/* All WS_* -- when extended, adapt diff.c emit_symbol */
#define WS_RULE_MASK 07777
extern unsigned whitespace_rule_cfg;
-extern unsigned whitespace_rule(const char *);
+extern unsigned whitespace_rule(struct index_state *, const char *);
extern unsigned parse_whitespace_rule(const char *);
extern unsigned ws_check(const char *line, int len, unsigned ws_rule);
extern void ws_check_emit(const char *line, int len, unsigned ws_rule, FILE *stream, const char *set, const char *reset, const char *ws);
/* merge.c */
struct commit_list;
-int try_merge_command(const char *strategy, size_t xopts_nr,
+int try_merge_command(struct repository *r,
+ const char *strategy, size_t xopts_nr,
const char **xopts, struct commit_list *common,
const char *head_arg, struct commit_list *remotes);
-int checkout_fast_forward(const struct object_id *from,
+int checkout_fast_forward(struct repository *r,
+ const struct object_id *from,
const struct object_id *to,
int overwrite_ignore);
export GIT_TEST_FULL_IN_PACK_ARRAY=true
export GIT_TEST_OE_SIZE=10
export GIT_TEST_OE_DELTA_SIZE=5
+ export GIT_TEST_COMMIT_GRAPH=1
+ export GIT_TEST_MULTI_PACK_INDEX=1
make --quiet test
fi
return base;
}
-static char *grab_blob(const struct object_id *oid, unsigned int mode,
+static char *grab_blob(struct repository *r,
+ const struct object_id *oid, unsigned int mode,
unsigned long *size, struct userdiff_driver *textconv,
const char *path)
{
} else if (textconv) {
struct diff_filespec *df = alloc_filespec(path);
fill_filespec(df, oid, 1, mode);
- *size = fill_textconv(textconv, df, &blob);
+ *size = fill_textconv(r, textconv, df, &blob);
free_filespec(df);
} else {
blob = read_object_file(oid, &type, size);
}
}
-static void combine_diff(const struct object_id *parent, unsigned int mode,
+static void combine_diff(struct repository *r,
+ const struct object_id *parent, unsigned int mode,
mmfile_t *result_file,
struct sline *sline, unsigned int cnt, int n,
int num_parent, int result_deleted,
if (result_deleted)
return; /* result deleted */
- parent_file.ptr = grab_blob(parent, mode, &sz, textconv, path);
+ parent_file.ptr = grab_blob(r, parent, mode, &sz, textconv, path);
parent_file.size = sz;
memset(&xpp, 0, sizeof(xpp));
xpp.flags = flags;
const char *line_prefix = diff_line_prefix(opt);
context = opt->context;
- userdiff = userdiff_find_by_path(elem->path);
+ userdiff = userdiff_find_by_path(opt->repo->index, elem->path);
if (!userdiff)
userdiff = userdiff_find_by_name("default");
if (opt->flags.allow_textconv)
/* Read the result of merge first */
if (!working_tree_file)
- result = grab_blob(&elem->oid, elem->mode, &result_size,
+ result = grab_blob(opt->repo, &elem->oid, elem->mode, &result_size,
textconv, elem->path);
else {
/* Used by diff-tree to read from the working tree */
} else if (S_ISDIR(st.st_mode)) {
struct object_id oid;
if (resolve_gitlink_ref(elem->path, "HEAD", &oid) < 0)
- result = grab_blob(&elem->oid, elem->mode,
- &result_size, NULL, NULL);
+ result = grab_blob(opt->repo, &elem->oid,
+ elem->mode, &result_size,
+ NULL, NULL);
else
- result = grab_blob(&oid, elem->mode,
+ result = grab_blob(opt->repo, &oid, elem->mode,
&result_size, NULL, NULL);
} else if (textconv) {
struct diff_filespec *df = alloc_filespec(elem->path);
fill_filespec(df, &null_oid, 0, st.st_mode);
- result_size = fill_textconv(textconv, df, &result);
+ result_size = fill_textconv(opt->repo, textconv, df, &result);
free_filespec(df);
} else if (0 <= (fd = open(elem->path, O_RDONLY))) {
size_t len = xsize_t(st.st_size);
if (is_file) {
struct strbuf buf = STRBUF_INIT;
- if (convert_to_git(&the_index, elem->path, result, len, &buf, global_conv_flags_eol)) {
+ if (convert_to_git(rev->diffopt.repo->index,
+ elem->path, result, len, &buf, global_conv_flags_eol)) {
free(result);
result = strbuf_detach(&buf, &len);
result_size = len;
for (i = 0; !is_binary && i < num_parent; i++) {
char *buf;
unsigned long size;
- buf = grab_blob(&elem->parent[i].oid,
+ buf = grab_blob(opt->repo,
+ &elem->parent[i].oid,
elem->parent[i].mode,
&size, NULL, NULL);
if (buffer_is_binary(buf, size))
for (i = 0; i < num_parent; i++) {
int j;
for (j = 0; j < i; j++) {
- if (!oidcmp(&elem->parent[i].oid,
- &elem->parent[j].oid)) {
+ if (oideq(&elem->parent[i].oid,
+ &elem->parent[j].oid)) {
reuse_combine_diff(sline, cnt, i, j);
break;
}
}
if (i <= j)
- combine_diff(&elem->parent[i].oid,
+ combine_diff(opt->repo,
+ &elem->parent[i].oid,
elem->parent[i].mode,
&result_file, sline,
cnt, i, num_parent, result_deleted,
git-checkout mainporcelain history
git-checkout-index plumbingmanipulators
git-check-ref-format purehelpers
-git-cherry ancillaryinterrogators complete
+git-cherry plumbinginterrogators complete
git-cherry-pick mainporcelain
git-citool mainporcelain
git-clean mainporcelain
git-format-patch mainporcelain
git-fsck ancillaryinterrogators complete
git-gc mainporcelain
-git-get-tar-commit-id ancillaryinterrogators
+git-get-tar-commit-id plumbinginterrogators
git-grep mainporcelain info
git-gui mainporcelain
git-hash-object plumbingmanipulators
git-merge-one-file purehelpers
git-mergetool ancillarymanipulators complete
git-merge-tree ancillaryinterrogators
+git-multi-pack-index plumbingmanipulators
git-mktag plumbingmanipulators
git-mktree plumbingmanipulators
git-mv mainporcelain worktree
git-reset mainporcelain worktree
git-revert mainporcelain
git-rev-list plumbinginterrogators
-git-rev-parse ancillaryinterrogators
+git-rev-parse plumbinginterrogators
git-rm mainporcelain worktree
git-send-email foreignscminterface complete
git-send-pack synchingrepositories
#include "commit-graph.h"
#include "object-store.h"
#include "alloc.h"
+#include "hashmap.h"
+#include "replace-object.h"
+#include "progress.h"
#define GRAPH_SIGNATURE 0x43475048 /* "CGPH" */
#define GRAPH_CHUNKID_OIDFANOUT 0x4f494446 /* "OIDF" */
return g;
}
+extern int read_replace_refs;
+
+static int commit_graph_compatible(struct repository *r)
+{
+ if (!r->gitdir)
+ return 0;
+
+ if (read_replace_refs) {
+ prepare_replace_object(r);
+ if (hashmap_get_size(&r->objects->replace_map->map))
+ return 0;
+ }
+
+ prepare_commit_graft(r);
+ if (r->parsed_objects && r->parsed_objects->grafts_nr)
+ return 0;
+ if (is_repository_shallow(r))
+ return 0;
+
+ return 1;
+}
+
struct commit_graph *load_commit_graph_one(const char *graph_file)
{
void *graph_map;
return !!r->objects->commit_graph;
r->objects->commit_graph_attempted = 1;
- if (repo_config_get_bool(r, "core.commitgraph", &config_value) ||
- !config_value)
+ if (!git_env_bool(GIT_TEST_COMMIT_GRAPH, 0) &&
+ (repo_config_get_bool(r, "core.commitgraph", &config_value) ||
+ !config_value))
/*
* This repository is not configured to use commit graphs, so
* do not load one. (But report commit_graph_attempted anyway
*/
return 0;
+ if (!commit_graph_compatible(r))
+ return 0;
+
obj_dir = r->objects->objectdir;
prepare_commit_graph_one(r, obj_dir);
prepare_alt_odb(r);
return !!r->objects->commit_graph;
}
-static void close_commit_graph(void)
+int generation_numbers_enabled(struct repository *r)
{
- free_commit_graph(the_repository->objects->commit_graph);
- the_repository->objects->commit_graph = NULL;
+ uint32_t first_generation;
+ struct commit_graph *g;
+ if (!prepare_commit_graph(r))
+ return 0;
+
+ g = r->objects->commit_graph;
+
+ if (!g->num_commits)
+ return 0;
+
+ first_generation = get_be32(g->chunk_commit_data +
+ g->hash_len + 8) >> 2;
+
+ return !!first_generation;
+}
+
+void close_commit_graph(struct repository *r)
+{
+ free_commit_graph(r->objects->commit_graph);
+ r->objects->commit_graph = NULL;
}
static int bsearch_graph(struct commit_graph *g, struct object_id *oid, uint32_t *pos)
struct object_id *list;
int nr;
int alloc;
+ struct progress *progress;
+ int progress_done;
};
static int add_packed_commits(const struct object_id *oid,
off_t offset = nth_packed_object_offset(pack, pos);
struct object_info oi = OBJECT_INFO_INIT;
+ if (list->progress)
+ display_progress(list->progress, ++list->progress_done);
+
oi.typep = &type;
if (packed_object_info(the_repository, pack, offset, &oi) < 0)
die(_("unable to get type of object %s"), oid_to_hex(oid));
}
}
-static void close_reachable(struct packed_oid_list *oids)
+static void close_reachable(struct packed_oid_list *oids, int report_progress)
{
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);
for (i = 0; i < oids->nr; i++) {
+ display_progress(progress, ++j);
commit = lookup_commit(the_repository, &oids->list[i]);
if (commit)
commit->object.flags |= UNINTERESTING;
* closure.
*/
for (i = 0; i < oids->nr; i++) {
+ display_progress(progress, ++j);
commit = lookup_commit(the_repository, &oids->list[i]);
if (commit && !parse_commit(commit))
}
for (i = 0; i < oids->nr; i++) {
+ display_progress(progress, ++j);
commit = lookup_commit(the_repository, &oids->list[i]);
if (commit)
commit->object.flags &= ~UNINTERESTING;
}
+ stop_progress(&progress);
}
-static void compute_generation_numbers(struct packed_commit_list* commits)
+static void compute_generation_numbers(struct packed_commit_list* commits,
+ int report_progress)
{
int i;
struct commit_list *list = NULL;
+ struct progress *progress = NULL;
+ if (report_progress)
+ progress = start_progress(
+ _("Computing commit graph generation numbers"),
+ commits->nr);
for (i = 0; i < commits->nr; i++) {
+ display_progress(progress, i + 1);
if (commits->list[i]->generation != GENERATION_NUMBER_INFINITY &&
commits->list[i]->generation != GENERATION_NUMBER_ZERO)
continue;
}
}
}
+ stop_progress(&progress);
}
static int add_ref_to_list(const char *refname,
return 0;
}
-void write_commit_graph_reachable(const char *obj_dir, int append)
+void write_commit_graph_reachable(const char *obj_dir, int append,
+ int report_progress)
{
- struct string_list list;
+ struct string_list list = STRING_LIST_INIT_DUP;
- string_list_init(&list, 1);
for_each_ref(add_ref_to_list, &list);
- write_commit_graph(obj_dir, NULL, &list, append);
+ write_commit_graph(obj_dir, NULL, &list, append, report_progress);
+
+ string_list_clear(&list, 0);
}
void write_commit_graph(const char *obj_dir,
struct string_list *pack_indexes,
struct string_list *commit_hex,
- int append)
+ int append, int report_progress)
{
struct packed_oid_list oids;
struct packed_commit_list commits;
int num_chunks;
int num_extra_edges;
struct commit_list *parent;
+ struct progress *progress = NULL;
+
+ if (!commit_graph_compatible(the_repository))
+ return;
oids.nr = 0;
- oids.alloc = approximate_object_count() / 4;
+ oids.alloc = approximate_object_count() / 32;
+ oids.progress = NULL;
+ oids.progress_done = 0;
if (append) {
prepare_commit_graph_one(the_repository, obj_dir);
int dirlen;
strbuf_addf(&packname, "%s/pack/", obj_dir);
dirlen = packname.len;
+ if (report_progress) {
+ oids.progress = start_delayed_progress(
+ _("Finding commits for commit graph"), 0);
+ oids.progress_done = 0;
+ }
for (i = 0; i < pack_indexes->nr; i++) {
struct packed_git *p;
strbuf_setlen(&packname, dirlen);
die(_("error opening index for %s"), packname.buf);
for_each_object_in_pack(p, add_packed_commits, &oids, 0);
close_pack(p);
+ free(p);
}
+ stop_progress(&oids.progress);
strbuf_release(&packname);
}
if (commit_hex) {
+ if (report_progress)
+ progress = start_delayed_progress(
+ _("Finding commits for commit graph"),
+ commit_hex->nr);
for (i = 0; i < commit_hex->nr; i++) {
const char *end;
struct object_id oid;
struct commit *result;
+ display_progress(progress, i + 1);
if (commit_hex->items[i].string &&
parse_oid_hex(commit_hex->items[i].string, &oid, &end))
continue;
oids.nr++;
}
}
+ stop_progress(&progress);
}
- if (!pack_indexes && !commit_hex)
+ 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);
+ stop_progress(&oids.progress);
+ }
- close_reachable(&oids);
+ close_reachable(&oids, report_progress);
QSORT(oids.list, oids.nr, commit_compare);
count_distinct = 1;
for (i = 1; i < oids.nr; i++) {
- if (oidcmp(&oids.list[i-1], &oids.list[i]))
+ if (!oideq(&oids.list[i - 1], &oids.list[i]))
count_distinct++;
}
num_extra_edges = 0;
for (i = 0; i < oids.nr; i++) {
int num_parents = 0;
- if (i > 0 && !oidcmp(&oids.list[i-1], &oids.list[i]))
+ if (i > 0 && oideq(&oids.list[i - 1], &oids.list[i]))
continue;
commits.list[commits.nr] = lookup_commit(the_repository, &oids.list[i]);
if (commits.nr >= GRAPH_PARENT_MISSING)
die(_("too many commits to write graph"));
- compute_generation_numbers(&commits);
+ compute_generation_numbers(&commits, report_progress);
graph_name = get_commit_graph_filename(obj_dir);
- if (safe_create_leading_directories(graph_name))
+ if (safe_create_leading_directories(graph_name)) {
+ UNLEAK(graph_name);
die_errno(_("unable to create leading directories of %s"),
graph_name);
+ }
hold_lock_file_for_update(&lk, graph_name, LOCK_DIE_ON_ERROR);
f = hashfd(lk.tempfile->fd, lk.tempfile->filename.buf);
write_graph_chunk_data(f, GRAPH_OID_LEN, commits.list, commits.nr);
write_graph_chunk_large_edges(f, commits.list, commits.nr);
- close_commit_graph();
+ close_commit_graph(the_repository);
finalize_hashfile(f, NULL, CSUM_HASH_IN_STREAM | CSUM_FSYNC);
commit_lock_file(&lk);
+ free(graph_name);
+ free(commits.list);
free(oids.list);
- oids.alloc = 0;
- oids.nr = 0;
}
#define VERIFY_COMMIT_GRAPH_ERROR_HASH 2
int generation_zero = 0;
struct hashfile *f;
int devnull;
+ struct progress *progress = NULL;
if (!g) {
graph_report("no commit-graph file loaded");
f = hashfd(devnull, NULL);
hashwrite(f, g->data, g->data_len - g->hash_len);
finalize_hashfile(f, checksum.hash, CSUM_CLOSE);
- if (hashcmp(checksum.hash, g->data + g->data_len - g->hash_len)) {
+ if (!hasheq(checksum.hash, g->data + g->data_len - g->hash_len)) {
graph_report(_("the commit-graph file has incorrect checksum and is likely corrupt"));
verify_commit_graph_error = VERIFY_COMMIT_GRAPH_ERROR_HASH;
}
if (verify_commit_graph_error & ~VERIFY_COMMIT_GRAPH_ERROR_HASH)
return verify_commit_graph_error;
+ progress = start_progress(_("Verifying commits in commit graph"),
+ g->num_commits);
for (i = 0; i < g->num_commits; i++) {
struct commit *graph_commit, *odb_commit;
struct commit_list *graph_parents, *odb_parents;
uint32_t max_generation = 0;
+ display_progress(progress, i + 1);
hashcpy(cur_oid.hash, g->chunk_oid_lookup + g->hash_len * i);
graph_commit = lookup_commit(r, &cur_oid);
continue;
}
- if (oidcmp(&get_commit_tree_in_graph_one(g, graph_commit)->object.oid,
+ if (!oideq(&get_commit_tree_in_graph_one(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),
break;
}
- if (oidcmp(&graph_parents->item->object.oid, &odb_parents->item->object.oid))
+ if (!oideq(&graph_parents->item->object.oid, &odb_parents->item->object.oid))
graph_report("commit-graph parent for %s is %s != %s",
oid_to_hex(&cur_oid),
oid_to_hex(&graph_parents->item->object.oid),
graph_commit->date,
odb_commit->date);
}
+ stop_progress(&progress);
return verify_commit_graph_error;
}
#include "string-list.h"
#include "cache.h"
+#define GIT_TEST_COMMIT_GRAPH "GIT_TEST_COMMIT_GRAPH"
+
struct commit;
char *get_commit_graph_filename(const char *obj_dir);
struct commit_graph *load_commit_graph_one(const char *graph_file);
-void write_commit_graph_reachable(const char *obj_dir, int append);
+/*
+ * Return 1 if and only if the repository has a commit-graph
+ * file and generation numbers are computed in that file.
+ */
+int generation_numbers_enabled(struct repository *r);
+
+void write_commit_graph_reachable(const char *obj_dir, int append,
+ int report_progress);
void write_commit_graph(const char *obj_dir,
struct string_list *pack_indexes,
struct string_list *commit_hex,
- int append);
+ int append, int report_progress);
int verify_commit_graph(struct repository *r, struct commit_graph *g);
+void close_commit_graph(struct repository *);
void free_commit_graph(struct commit_graph *);
#endif
--- /dev/null
+#include "cache.h"
+#include "commit.h"
+#include "commit-graph.h"
+#include "decorate.h"
+#include "prio-queue.h"
+#include "tree.h"
+#include "ref-filter.h"
+#include "revision.h"
+#include "tag.h"
+#include "commit-reach.h"
+
+/* Remember to update object flag allocation in object.h */
+#define REACHABLE (1u<<15)
+#define PARENT1 (1u<<16)
+#define PARENT2 (1u<<17)
+#define STALE (1u<<18)
+#define RESULT (1u<<19)
+
+static const unsigned all_flags = (PARENT1 | PARENT2 | STALE | RESULT);
+
+static int queue_has_nonstale(struct prio_queue *queue)
+{
+ int i;
+ for (i = 0; i < queue->nr; i++) {
+ struct commit *commit = queue->array[i].data;
+ if (!(commit->object.flags & STALE))
+ return 1;
+ }
+ return 0;
+}
+
+/* all input commits in one and twos[] must have been parsed! */
+static struct commit_list *paint_down_to_common(struct commit *one, int n,
+ struct commit **twos,
+ int min_generation)
+{
+ struct prio_queue queue = { compare_commits_by_gen_then_commit_date };
+ struct commit_list *result = NULL;
+ int i;
+ uint32_t last_gen = GENERATION_NUMBER_INFINITY;
+
+ if (!min_generation)
+ queue.compare = compare_commits_by_commit_date;
+
+ one->object.flags |= PARENT1;
+ if (!n) {
+ commit_list_append(one, &result);
+ return result;
+ }
+ prio_queue_put(&queue, one);
+
+ for (i = 0; i < n; i++) {
+ twos[i]->object.flags |= PARENT2;
+ prio_queue_put(&queue, twos[i]);
+ }
+
+ while (queue_has_nonstale(&queue)) {
+ struct commit *commit = prio_queue_get(&queue);
+ struct commit_list *parents;
+ int flags;
+
+ if (min_generation && commit->generation > last_gen)
+ BUG("bad generation skip %8x > %8x at %s",
+ commit->generation, last_gen,
+ oid_to_hex(&commit->object.oid));
+ last_gen = commit->generation;
+
+ if (commit->generation < min_generation)
+ break;
+
+ flags = commit->object.flags & (PARENT1 | PARENT2 | STALE);
+ if (flags == (PARENT1 | PARENT2)) {
+ if (!(commit->object.flags & RESULT)) {
+ commit->object.flags |= RESULT;
+ commit_list_insert_by_date(commit, &result);
+ }
+ /* Mark parents of a found merge stale */
+ flags |= STALE;
+ }
+ parents = commit->parents;
+ while (parents) {
+ struct commit *p = parents->item;
+ parents = parents->next;
+ if ((p->object.flags & flags) == flags)
+ continue;
+ if (parse_commit(p))
+ return NULL;
+ p->object.flags |= flags;
+ prio_queue_put(&queue, p);
+ }
+ }
+
+ clear_prio_queue(&queue);
+ return result;
+}
+
+static struct commit_list *merge_bases_many(struct commit *one, int n, struct commit **twos)
+{
+ struct commit_list *list = NULL;
+ struct commit_list *result = NULL;
+ int i;
+
+ for (i = 0; i < n; i++) {
+ if (one == twos[i])
+ /*
+ * We do not mark this even with RESULT so we do not
+ * have to clean it up.
+ */
+ return commit_list_insert(one, &result);
+ }
+
+ if (parse_commit(one))
+ return NULL;
+ for (i = 0; i < n; i++) {
+ if (parse_commit(twos[i]))
+ return NULL;
+ }
+
+ list = paint_down_to_common(one, n, twos, 0);
+
+ while (list) {
+ struct commit *commit = pop_commit(&list);
+ if (!(commit->object.flags & STALE))
+ commit_list_insert_by_date(commit, &result);
+ }
+ return result;
+}
+
+struct commit_list *get_octopus_merge_bases(struct commit_list *in)
+{
+ struct commit_list *i, *j, *k, *ret = NULL;
+
+ if (!in)
+ return ret;
+
+ commit_list_insert(in->item, &ret);
+
+ for (i = in->next; i; i = i->next) {
+ struct commit_list *new_commits = NULL, *end = NULL;
+
+ for (j = ret; j; j = j->next) {
+ struct commit_list *bases;
+ bases = get_merge_bases(i->item, j->item);
+ if (!new_commits)
+ new_commits = bases;
+ else
+ end->next = bases;
+ for (k = bases; k; k = k->next)
+ end = k;
+ }
+ ret = new_commits;
+ }
+ return ret;
+}
+
+static int remove_redundant(struct commit **array, int cnt)
+{
+ /*
+ * Some commit in the array may be an ancestor of
+ * another commit. Move such commit to the end of
+ * the array, and return the number of commits that
+ * are independent from each other.
+ */
+ struct commit **work;
+ unsigned char *redundant;
+ int *filled_index;
+ int i, j, filled;
+
+ work = xcalloc(cnt, sizeof(*work));
+ redundant = xcalloc(cnt, 1);
+ ALLOC_ARRAY(filled_index, cnt - 1);
+
+ for (i = 0; i < cnt; i++)
+ parse_commit(array[i]);
+ for (i = 0; i < cnt; i++) {
+ struct commit_list *common;
+ uint32_t min_generation = array[i]->generation;
+
+ if (redundant[i])
+ continue;
+ for (j = filled = 0; j < cnt; j++) {
+ if (i == j || redundant[j])
+ continue;
+ filled_index[filled] = j;
+ work[filled++] = array[j];
+
+ if (array[j]->generation < min_generation)
+ min_generation = array[j]->generation;
+ }
+ common = paint_down_to_common(array[i], filled, work,
+ min_generation);
+ if (array[i]->object.flags & PARENT2)
+ redundant[i] = 1;
+ for (j = 0; j < filled; j++)
+ if (work[j]->object.flags & PARENT1)
+ redundant[filled_index[j]] = 1;
+ clear_commit_marks(array[i], all_flags);
+ clear_commit_marks_many(filled, work, all_flags);
+ free_commit_list(common);
+ }
+
+ /* Now collect the result */
+ COPY_ARRAY(work, array, cnt);
+ for (i = filled = 0; i < cnt; i++)
+ if (!redundant[i])
+ array[filled++] = work[i];
+ for (j = filled, i = 0; i < cnt; i++)
+ if (redundant[i])
+ array[j++] = work[i];
+ free(work);
+ free(redundant);
+ free(filled_index);
+ return filled;
+}
+
+static struct commit_list *get_merge_bases_many_0(struct commit *one,
+ int n,
+ struct commit **twos,
+ int cleanup)
+{
+ struct commit_list *list;
+ struct commit **rslt;
+ struct commit_list *result;
+ int cnt, i;
+
+ result = merge_bases_many(one, n, twos);
+ for (i = 0; i < n; i++) {
+ if (one == twos[i])
+ return result;
+ }
+ if (!result || !result->next) {
+ if (cleanup) {
+ clear_commit_marks(one, all_flags);
+ clear_commit_marks_many(n, twos, all_flags);
+ }
+ return result;
+ }
+
+ /* There are more than one */
+ cnt = commit_list_count(result);
+ rslt = xcalloc(cnt, sizeof(*rslt));
+ for (list = result, i = 0; list; list = list->next)
+ rslt[i++] = list->item;
+ free_commit_list(result);
+
+ clear_commit_marks(one, all_flags);
+ clear_commit_marks_many(n, twos, all_flags);
+
+ cnt = remove_redundant(rslt, cnt);
+ result = NULL;
+ for (i = 0; i < cnt; i++)
+ commit_list_insert_by_date(rslt[i], &result);
+ free(rslt);
+ return result;
+}
+
+struct commit_list *get_merge_bases_many(struct commit *one,
+ int n,
+ struct commit **twos)
+{
+ return get_merge_bases_many_0(one, n, twos, 1);
+}
+
+struct commit_list *get_merge_bases_many_dirty(struct commit *one,
+ int n,
+ struct commit **twos)
+{
+ return get_merge_bases_many_0(one, n, twos, 0);
+}
+
+struct commit_list *get_merge_bases(struct commit *one, struct commit *two)
+{
+ return get_merge_bases_many_0(one, 1, &two, 1);
+}
+
+/*
+ * Is "commit" a descendant of one of the elements on the "with_commit" list?
+ */
+int is_descendant_of(struct commit *commit, struct commit_list *with_commit)
+{
+ if (!with_commit)
+ return 1;
+
+ if (generation_numbers_enabled(the_repository)) {
+ struct commit_list *from_list = NULL;
+ int result;
+ commit_list_insert(commit, &from_list);
+ result = can_all_from_reach(from_list, with_commit, 0);
+ free_commit_list(from_list);
+ return result;
+ } else {
+ while (with_commit) {
+ struct commit *other;
+
+ other = with_commit->item;
+ with_commit = with_commit->next;
+ if (in_merge_bases(other, commit))
+ return 1;
+ }
+ return 0;
+ }
+}
+
+/*
+ * Is "commit" an ancestor of one of the "references"?
+ */
+int in_merge_bases_many(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))
+ return ret;
+ for (i = 0; i < nr_reference; i++) {
+ if (parse_commit(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);
+ if (commit->object.flags & PARENT2)
+ ret = 1;
+ clear_commit_marks(commit, all_flags);
+ clear_commit_marks_many(nr_reference, reference, all_flags);
+ free_commit_list(bases);
+ return ret;
+}
+
+/*
+ * Is "commit" an ancestor of (i.e. reachable from) the "reference"?
+ */
+int in_merge_bases(struct commit *commit, struct commit *reference)
+{
+ return in_merge_bases_many(commit, 1, &reference);
+}
+
+struct commit_list *reduce_heads(struct commit_list *heads)
+{
+ struct commit_list *p;
+ struct commit_list *result = NULL, **tail = &result;
+ struct commit **array;
+ int num_head, i;
+
+ if (!heads)
+ return NULL;
+
+ /* Uniquify */
+ for (p = heads; p; p = p->next)
+ p->item->object.flags &= ~STALE;
+ for (p = heads, num_head = 0; p; p = p->next) {
+ if (p->item->object.flags & STALE)
+ continue;
+ p->item->object.flags |= STALE;
+ num_head++;
+ }
+ array = xcalloc(num_head, sizeof(*array));
+ for (p = heads, i = 0; p; p = p->next) {
+ if (p->item->object.flags & STALE) {
+ array[i++] = p->item;
+ p->item->object.flags &= ~STALE;
+ }
+ }
+ num_head = remove_redundant(array, num_head);
+ for (i = 0; i < num_head; i++)
+ tail = &commit_list_insert(array[i], tail)->next;
+ free(array);
+ return result;
+}
+
+void reduce_heads_replace(struct commit_list **heads)
+{
+ struct commit_list *result = reduce_heads(*heads);
+ free_commit_list(*heads);
+ *heads = result;
+}
+
+int ref_newer(const struct object_id *new_oid, const struct object_id *old_oid)
+{
+ struct object *o;
+ struct commit *old_commit, *new_commit;
+ struct commit_list *old_commit_list = NULL;
+
+ /*
+ * Both new_commit and old_commit must be commit-ish and new_commit is descendant of
+ * old_commit. Otherwise we require --force.
+ */
+ o = deref_tag(the_repository, parse_object(the_repository, old_oid),
+ NULL, 0);
+ if (!o || o->type != OBJ_COMMIT)
+ return 0;
+ old_commit = (struct commit *) o;
+
+ o = deref_tag(the_repository, parse_object(the_repository, new_oid),
+ NULL, 0);
+ if (!o || o->type != OBJ_COMMIT)
+ return 0;
+ new_commit = (struct commit *) o;
+
+ if (parse_commit(new_commit) < 0)
+ return 0;
+
+ commit_list_insert(old_commit, &old_commit_list);
+ return is_descendant_of(new_commit, old_commit_list);
+}
+
+/*
+ * Mimicking the real stack, this stack lives on the heap, avoiding stack
+ * overflows.
+ *
+ * At each recursion step, the stack items points to the commits whose
+ * ancestors are to be inspected.
+ */
+struct contains_stack {
+ int nr, alloc;
+ struct contains_stack_entry {
+ struct commit *commit;
+ struct commit_list *parents;
+ } *contains_stack;
+};
+
+static int in_commit_list(const struct commit_list *want, struct commit *c)
+{
+ for (; want; want = want->next)
+ if (oideq(&want->item->object.oid, &c->object.oid))
+ return 1;
+ return 0;
+}
+
+/*
+ * Test whether the candidate is contained in the list.
+ * Do not recurse to find out, though, but return -1 if inconclusive.
+ */
+static enum contains_result contains_test(struct commit *candidate,
+ const struct commit_list *want,
+ struct contains_cache *cache,
+ uint32_t cutoff)
+{
+ enum contains_result *cached = contains_cache_at(cache, candidate);
+
+ /* If we already have the answer cached, return that. */
+ if (*cached)
+ return *cached;
+
+ /* or are we it? */
+ if (in_commit_list(want, candidate)) {
+ *cached = CONTAINS_YES;
+ return CONTAINS_YES;
+ }
+
+ /* Otherwise, we don't know; prepare to recurse */
+ parse_commit_or_die(candidate);
+
+ if (candidate->generation < cutoff)
+ return CONTAINS_NO;
+
+ return CONTAINS_UNKNOWN;
+}
+
+static void push_to_contains_stack(struct commit *candidate, struct contains_stack *contains_stack)
+{
+ ALLOC_GROW(contains_stack->contains_stack, contains_stack->nr + 1, contains_stack->alloc);
+ contains_stack->contains_stack[contains_stack->nr].commit = candidate;
+ contains_stack->contains_stack[contains_stack->nr++].parents = candidate->parents;
+}
+
+static enum contains_result contains_tag_algo(struct commit *candidate,
+ const struct commit_list *want,
+ struct contains_cache *cache)
+{
+ struct contains_stack contains_stack = { 0, 0, NULL };
+ enum contains_result result;
+ uint32_t cutoff = GENERATION_NUMBER_INFINITY;
+ const struct commit_list *p;
+
+ for (p = want; p; p = p->next) {
+ struct commit *c = p->item;
+ load_commit_graph_info(the_repository, c);
+ if (c->generation < cutoff)
+ cutoff = c->generation;
+ }
+
+ result = contains_test(candidate, want, cache, cutoff);
+ if (result != CONTAINS_UNKNOWN)
+ return result;
+
+ push_to_contains_stack(candidate, &contains_stack);
+ while (contains_stack.nr) {
+ struct contains_stack_entry *entry = &contains_stack.contains_stack[contains_stack.nr - 1];
+ struct commit *commit = entry->commit;
+ struct commit_list *parents = entry->parents;
+
+ if (!parents) {
+ *contains_cache_at(cache, commit) = CONTAINS_NO;
+ contains_stack.nr--;
+ }
+ /*
+ * If we just popped the stack, parents->item has been marked,
+ * therefore contains_test will return a meaningful yes/no.
+ */
+ else switch (contains_test(parents->item, want, cache, cutoff)) {
+ case CONTAINS_YES:
+ *contains_cache_at(cache, commit) = CONTAINS_YES;
+ contains_stack.nr--;
+ break;
+ case CONTAINS_NO:
+ entry->parents = parents->next;
+ break;
+ case CONTAINS_UNKNOWN:
+ push_to_contains_stack(parents->item, &contains_stack);
+ break;
+ }
+ }
+ free(contains_stack.contains_stack);
+ return contains_test(candidate, want, cache, cutoff);
+}
+
+int commit_contains(struct ref_filter *filter, struct commit *commit,
+ struct commit_list *list, struct contains_cache *cache)
+{
+ if (filter->with_commit_tag_algo)
+ return contains_tag_algo(commit, list, cache) == CONTAINS_YES;
+ return is_descendant_of(commit, list);
+}
+
+static int compare_commits_by_gen(const void *_a, const void *_b)
+{
+ const struct commit *a = *(const struct commit * const *)_a;
+ const struct commit *b = *(const struct commit * const *)_b;
+
+ if (a->generation < b->generation)
+ return -1;
+ if (a->generation > b->generation)
+ return 1;
+ return 0;
+}
+
+int can_all_from_reach_with_flag(struct object_array *from,
+ unsigned int with_flag,
+ unsigned int assign_flag,
+ time_t min_commit_date,
+ uint32_t min_generation)
+{
+ struct commit **list = NULL;
+ int i;
+ int nr_commits;
+ int result = 1;
+
+ ALLOC_ARRAY(list, from->nr);
+ nr_commits = 0;
+ for (i = 0; i < from->nr; i++) {
+ struct object *from_one = from->objects[i].item;
+
+ if (!from_one || from_one->flags & assign_flag)
+ continue;
+
+ from_one = deref_tag(the_repository, from_one,
+ "a from object", 0);
+ if (!from_one || from_one->type != OBJ_COMMIT) {
+ /*
+ * no way to tell if this is reachable by
+ * looking at the ancestry chain alone, so
+ * leave a note to ourselves not to worry about
+ * this object anymore.
+ */
+ from->objects[i].item->flags |= assign_flag;
+ continue;
+ }
+
+ list[nr_commits] = (struct commit *)from_one;
+ if (parse_commit(list[nr_commits]) ||
+ list[nr_commits]->generation < min_generation) {
+ result = 0;
+ goto cleanup;
+ }
+
+ nr_commits++;
+ }
+
+ QSORT(list, nr_commits, compare_commits_by_gen);
+
+ for (i = 0; i < nr_commits; i++) {
+ /* DFS from list[i] */
+ struct commit_list *stack = NULL;
+
+ list[i]->object.flags |= assign_flag;
+ commit_list_insert(list[i], &stack);
+
+ while (stack) {
+ struct commit_list *parent;
+
+ if (stack->item->object.flags & (with_flag | RESULT)) {
+ pop_commit(&stack);
+ if (stack)
+ stack->item->object.flags |= RESULT;
+ continue;
+ }
+
+ for (parent = stack->item->parents; parent; parent = parent->next) {
+ if (parent->item->object.flags & (with_flag | RESULT))
+ stack->item->object.flags |= RESULT;
+
+ if (!(parent->item->object.flags & assign_flag)) {
+ parent->item->object.flags |= assign_flag;
+
+ if (parse_commit(parent->item) ||
+ parent->item->date < min_commit_date ||
+ parent->item->generation < min_generation)
+ continue;
+
+ commit_list_insert(parent->item, &stack);
+ break;
+ }
+ }
+
+ if (!parent)
+ pop_commit(&stack);
+ }
+
+ if (!(list[i]->object.flags & (with_flag | RESULT))) {
+ result = 0;
+ goto cleanup;
+ }
+ }
+
+cleanup:
+ clear_commit_marks_many(nr_commits, list, RESULT | assign_flag);
+ free(list);
+
+ for (i = 0; i < from->nr; i++)
+ from->objects[i].item->flags &= ~assign_flag;
+
+ return result;
+}
+
+int can_all_from_reach(struct commit_list *from, struct commit_list *to,
+ int cutoff_by_min_date)
+{
+ struct object_array from_objs = OBJECT_ARRAY_INIT;
+ time_t min_commit_date = cutoff_by_min_date ? from->item->date : 0;
+ struct commit_list *from_iter = from, *to_iter = to;
+ int result;
+ uint32_t min_generation = GENERATION_NUMBER_INFINITY;
+
+ while (from_iter) {
+ add_object_array(&from_iter->item->object, NULL, &from_objs);
+
+ if (!parse_commit(from_iter->item)) {
+ if (from_iter->item->date < min_commit_date)
+ min_commit_date = from_iter->item->date;
+
+ if (from_iter->item->generation < min_generation)
+ min_generation = from_iter->item->generation;
+ }
+
+ from_iter = from_iter->next;
+ }
+
+ while (to_iter) {
+ if (!parse_commit(to_iter->item)) {
+ if (to_iter->item->date < min_commit_date)
+ min_commit_date = to_iter->item->date;
+
+ if (to_iter->item->generation < min_generation)
+ min_generation = to_iter->item->generation;
+ }
+
+ to_iter->item->object.flags |= PARENT2;
+
+ to_iter = to_iter->next;
+ }
+
+ result = can_all_from_reach_with_flag(&from_objs, PARENT2, PARENT1,
+ min_commit_date, min_generation);
+
+ while (from) {
+ clear_commit_marks(from->item, PARENT1);
+ from = from->next;
+ }
+
+ while (to) {
+ clear_commit_marks(to->item, PARENT2);
+ to = to->next;
+ }
+
+ object_array_clear(&from_objs);
+ return result;
+}
--- /dev/null
+#ifndef COMMIT_REACH_H
+#define COMMIT_REACH_H
+
+#include "commit-slab.h"
+
+struct commit;
+struct commit_list;
+struct contains_cache;
+struct ref_filter;
+
+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);
+
+/* 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);
+
+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);
+
+/*
+ * Takes a list of commits and returns a new list where those
+ * have been removed that can be reached from other commits in
+ * the list. It is useful for, e.g., reducing the commits
+ * randomly thrown at the git-merge command and removing
+ * redundant commits that the user shouldn't have given to it.
+ *
+ * This function destroys the STALE bit of the commit objects'
+ * flags.
+ */
+struct commit_list *reduce_heads(struct commit_list *heads);
+
+/*
+ * Like `reduce_heads()`, except it replaces the list. Use this
+ * instead of `foo = reduce_heads(foo);` to avoid memory leaks.
+ */
+void reduce_heads_replace(struct commit_list **heads);
+
+int ref_newer(const struct object_id *new_oid, const struct object_id *old_oid);
+
+/*
+ * Unknown has to be "0" here, because that's the default value for
+ * contains_cache slab entries that have not yet been assigned.
+ */
+enum contains_result {
+ CONTAINS_UNKNOWN = 0,
+ CONTAINS_NO,
+ CONTAINS_YES
+};
+
+define_commit_slab(contains_cache, enum contains_result);
+
+int commit_contains(struct ref_filter *filter, struct commit *commit,
+ struct commit_list *list, struct contains_cache *cache);
+
+/*
+ * Determine if every commit in 'from' can reach at least one commit
+ * that is marked with 'with_flag'. As we traverse, use 'assign_flag'
+ * as a marker for commits that are already visited. Do not walk
+ * commits with date below 'min_commit_date' or generation below
+ * 'min_generation'.
+ */
+int can_all_from_reach_with_flag(struct object_array *from,
+ unsigned int with_flag,
+ unsigned int assign_flag,
+ time_t min_commit_date,
+ uint32_t min_generation);
+int can_all_from_reach(struct commit_list *from, struct commit_list *to,
+ int commit_date_cutoff);
+
+#endif
#ifndef COMMIT_SLAB_IMPL_H
#define COMMIT_SLAB_IMPL_H
-#define MAYBE_UNUSED __attribute__((__unused__))
+#include "git-compat-util.h"
#define implement_static_commit_slab(slabname, elemtype) \
- implement_commit_slab(slabname, elemtype, static MAYBE_UNUSED)
+ implement_commit_slab(slabname, elemtype, MAYBE_UNUSED static)
#define implement_shared_commit_slab(slabname, elemtype) \
implement_commit_slab(slabname, elemtype, )
struct commit *c = lookup_commit_reference(the_repository, oid);
if (!c)
die(_("could not parse %s"), ref_name);
- if (oidcmp(oid, &c->object.oid)) {
+ if (!oideq(oid, &c->object.oid)) {
warning(_("%s %s is not a commit!"),
ref_name, oid_to_hex(oid));
}
return 0;
}
-static void prepare_commit_graft(struct repository *r)
+void prepare_commit_graft(struct repository *r)
{
char *graft_file;
clear_author_date_slab(&author_date);
}
-/* merge-base stuff */
-
-/* Remember to update object flag allocation in object.h */
-#define PARENT1 (1u<<16)
-#define PARENT2 (1u<<17)
-#define STALE (1u<<18)
-#define RESULT (1u<<19)
-
-static const unsigned all_flags = (PARENT1 | PARENT2 | STALE | RESULT);
-
-static int queue_has_nonstale(struct prio_queue *queue)
-{
- int i;
- for (i = 0; i < queue->nr; i++) {
- struct commit *commit = queue->array[i].data;
- if (!(commit->object.flags & STALE))
- return 1;
- }
- return 0;
-}
-
-/* all input commits in one and twos[] must have been parsed! */
-static struct commit_list *paint_down_to_common(struct commit *one, int n,
- struct commit **twos,
- int min_generation)
-{
- struct prio_queue queue = { compare_commits_by_gen_then_commit_date };
- struct commit_list *result = NULL;
- int i;
- uint32_t last_gen = GENERATION_NUMBER_INFINITY;
-
- if (!min_generation)
- queue.compare = compare_commits_by_commit_date;
-
- one->object.flags |= PARENT1;
- if (!n) {
- commit_list_append(one, &result);
- return result;
- }
- prio_queue_put(&queue, one);
-
- for (i = 0; i < n; i++) {
- twos[i]->object.flags |= PARENT2;
- prio_queue_put(&queue, twos[i]);
- }
-
- while (queue_has_nonstale(&queue)) {
- struct commit *commit = prio_queue_get(&queue);
- struct commit_list *parents;
- int flags;
-
- if (min_generation && commit->generation > last_gen)
- BUG("bad generation skip %8x > %8x at %s",
- commit->generation, last_gen,
- oid_to_hex(&commit->object.oid));
- last_gen = commit->generation;
-
- if (commit->generation < min_generation)
- break;
-
- flags = commit->object.flags & (PARENT1 | PARENT2 | STALE);
- if (flags == (PARENT1 | PARENT2)) {
- if (!(commit->object.flags & RESULT)) {
- commit->object.flags |= RESULT;
- commit_list_insert_by_date(commit, &result);
- }
- /* Mark parents of a found merge stale */
- flags |= STALE;
- }
- parents = commit->parents;
- while (parents) {
- struct commit *p = parents->item;
- parents = parents->next;
- if ((p->object.flags & flags) == flags)
- continue;
- if (parse_commit(p))
- return NULL;
- p->object.flags |= flags;
- prio_queue_put(&queue, p);
- }
- }
-
- clear_prio_queue(&queue);
- return result;
-}
-
-static struct commit_list *merge_bases_many(struct commit *one, int n, struct commit **twos)
-{
- struct commit_list *list = NULL;
- struct commit_list *result = NULL;
- int i;
-
- for (i = 0; i < n; i++) {
- if (one == twos[i])
- /*
- * We do not mark this even with RESULT so we do not
- * have to clean it up.
- */
- return commit_list_insert(one, &result);
- }
-
- if (parse_commit(one))
- return NULL;
- for (i = 0; i < n; i++) {
- if (parse_commit(twos[i]))
- return NULL;
- }
-
- list = paint_down_to_common(one, n, twos, 0);
-
- while (list) {
- struct commit *commit = pop_commit(&list);
- if (!(commit->object.flags & STALE))
- commit_list_insert_by_date(commit, &result);
- }
- return result;
-}
-
-struct commit_list *get_octopus_merge_bases(struct commit_list *in)
-{
- struct commit_list *i, *j, *k, *ret = NULL;
-
- if (!in)
- return ret;
-
- commit_list_insert(in->item, &ret);
-
- for (i = in->next; i; i = i->next) {
- struct commit_list *new_commits = NULL, *end = NULL;
-
- for (j = ret; j; j = j->next) {
- struct commit_list *bases;
- bases = get_merge_bases(i->item, j->item);
- if (!new_commits)
- new_commits = bases;
- else
- end->next = bases;
- for (k = bases; k; k = k->next)
- end = k;
- }
- ret = new_commits;
- }
- return ret;
-}
-
-static int remove_redundant(struct commit **array, int cnt)
-{
- /*
- * Some commit in the array may be an ancestor of
- * another commit. Move such commit to the end of
- * the array, and return the number of commits that
- * are independent from each other.
- */
- struct commit **work;
- unsigned char *redundant;
- int *filled_index;
- int i, j, filled;
-
- work = xcalloc(cnt, sizeof(*work));
- redundant = xcalloc(cnt, 1);
- ALLOC_ARRAY(filled_index, cnt - 1);
-
- for (i = 0; i < cnt; i++)
- parse_commit(array[i]);
- for (i = 0; i < cnt; i++) {
- struct commit_list *common;
- uint32_t min_generation = array[i]->generation;
-
- if (redundant[i])
- continue;
- for (j = filled = 0; j < cnt; j++) {
- if (i == j || redundant[j])
- continue;
- filled_index[filled] = j;
- work[filled++] = array[j];
-
- if (array[j]->generation < min_generation)
- min_generation = array[j]->generation;
- }
- common = paint_down_to_common(array[i], filled, work,
- min_generation);
- if (array[i]->object.flags & PARENT2)
- redundant[i] = 1;
- for (j = 0; j < filled; j++)
- if (work[j]->object.flags & PARENT1)
- redundant[filled_index[j]] = 1;
- clear_commit_marks(array[i], all_flags);
- clear_commit_marks_many(filled, work, all_flags);
- free_commit_list(common);
- }
-
- /* Now collect the result */
- COPY_ARRAY(work, array, cnt);
- for (i = filled = 0; i < cnt; i++)
- if (!redundant[i])
- array[filled++] = work[i];
- for (j = filled, i = 0; i < cnt; i++)
- if (redundant[i])
- array[j++] = work[i];
- free(work);
- free(redundant);
- free(filled_index);
- return filled;
-}
-
-static struct commit_list *get_merge_bases_many_0(struct commit *one,
- int n,
- struct commit **twos,
- int cleanup)
-{
- struct commit_list *list;
- struct commit **rslt;
- struct commit_list *result;
- int cnt, i;
-
- result = merge_bases_many(one, n, twos);
- for (i = 0; i < n; i++) {
- if (one == twos[i])
- return result;
- }
- if (!result || !result->next) {
- if (cleanup) {
- clear_commit_marks(one, all_flags);
- clear_commit_marks_many(n, twos, all_flags);
- }
- return result;
- }
-
- /* There are more than one */
- cnt = commit_list_count(result);
- rslt = xcalloc(cnt, sizeof(*rslt));
- for (list = result, i = 0; list; list = list->next)
- rslt[i++] = list->item;
- free_commit_list(result);
-
- clear_commit_marks(one, all_flags);
- clear_commit_marks_many(n, twos, all_flags);
-
- cnt = remove_redundant(rslt, cnt);
- result = NULL;
- for (i = 0; i < cnt; i++)
- commit_list_insert_by_date(rslt[i], &result);
- free(rslt);
- return result;
-}
-
-struct commit_list *get_merge_bases_many(struct commit *one,
- int n,
- struct commit **twos)
-{
- return get_merge_bases_many_0(one, n, twos, 1);
-}
-
-struct commit_list *get_merge_bases_many_dirty(struct commit *one,
- int n,
- struct commit **twos)
-{
- return get_merge_bases_many_0(one, n, twos, 0);
-}
-
-struct commit_list *get_merge_bases(struct commit *one, struct commit *two)
-{
- return get_merge_bases_many_0(one, 1, &two, 1);
-}
-
-/*
- * Is "commit" a descendant of one of the elements on the "with_commit" list?
- */
-int is_descendant_of(struct commit *commit, struct commit_list *with_commit)
-{
- if (!with_commit)
- return 1;
- while (with_commit) {
- struct commit *other;
-
- other = with_commit->item;
- with_commit = with_commit->next;
- if (in_merge_bases(other, commit))
- return 1;
- }
- return 0;
-}
-
-/*
- * Is "commit" an ancestor of one of the "references"?
- */
-int in_merge_bases_many(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))
- return ret;
- for (i = 0; i < nr_reference; i++) {
- if (parse_commit(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);
- if (commit->object.flags & PARENT2)
- ret = 1;
- clear_commit_marks(commit, all_flags);
- clear_commit_marks_many(nr_reference, reference, all_flags);
- free_commit_list(bases);
- return ret;
-}
-
-/*
- * Is "commit" an ancestor of (i.e. reachable from) the "reference"?
- */
-int in_merge_bases(struct commit *commit, struct commit *reference)
-{
- return in_merge_bases_many(commit, 1, &reference);
-}
-
-struct commit_list *reduce_heads(struct commit_list *heads)
-{
- struct commit_list *p;
- struct commit_list *result = NULL, **tail = &result;
- struct commit **array;
- int num_head, i;
-
- if (!heads)
- return NULL;
-
- /* Uniquify */
- for (p = heads; p; p = p->next)
- p->item->object.flags &= ~STALE;
- for (p = heads, num_head = 0; p; p = p->next) {
- if (p->item->object.flags & STALE)
- continue;
- p->item->object.flags |= STALE;
- num_head++;
- }
- array = xcalloc(num_head, sizeof(*array));
- for (p = heads, i = 0; p; p = p->next) {
- if (p->item->object.flags & STALE) {
- array[i++] = p->item;
- p->item->object.flags &= ~STALE;
- }
- }
- num_head = remove_redundant(array, num_head);
- for (i = 0; i < num_head; i++)
- tail = &commit_list_insert(array[i], tail)->next;
- free(array);
- return result;
-}
-
-void reduce_heads_replace(struct commit_list **heads)
-{
- struct commit_list *result = reduce_heads(*heads);
- free_commit_list(*heads);
- *heads = result;
-}
-
static const char gpg_sig_header[] = "gpgsig";
static const int gpg_sig_header_len = sizeof(gpg_sig_header) - 1;
* Returns the number of bytes from the tail to ignore, to be fed as
* the second parameter to append_signoff().
*/
-int ignore_non_trailer(const char *buf, size_t len)
+size_t ignore_non_trailer(const char *buf, size_t len)
{
- int boc = 0;
- int bol = 0;
+ size_t boc = 0;
+ size_t bol = 0;
int in_old_conflicts_block = 0;
size_t cutoff = wt_status_locate_end(buf, len);
struct commit_graft *read_graft_line(struct strbuf *line);
int register_commit_graft(struct repository *r, struct commit_graft *, int);
+void prepare_commit_graft(struct repository *r);
struct commit_graft *lookup_commit_graft(struct repository *r, const struct object_id *oid);
-extern struct commit_list *get_merge_bases(struct commit *rev1, struct commit *rev2);
-extern struct commit_list *get_merge_bases_many(struct commit *one, int n, struct commit **twos);
-extern struct commit_list *get_octopus_merge_bases(struct commit_list *in);
-
-/* To be used only when object flags after this call no longer matter */
-extern struct commit_list *get_merge_bases_many_dirty(struct commit *one, int n, struct commit **twos);
-
/* largest positive number a signed 32-bit integer can contain */
#define INFINITE_DEPTH 0x7fffffff
extern void prune_shallow(int show_only);
extern struct trace_key trace_shallow;
-int is_descendant_of(struct commit *, struct commit_list *);
-int in_merge_bases(struct commit *, struct commit *);
-int in_merge_bases_many(struct commit *, int, struct commit **);
-
extern int interactive_add(int argc, const char **argv, const char *prefix, int patch);
extern int run_add_interactive(const char *revision, const char *patch_mode,
const struct pathspec *pathspec);
-/*
- * Takes a list of commits and returns a new list where those
- * have been removed that can be reached from other commits in
- * the list. It is useful for, e.g., reducing the commits
- * randomly thrown at the git-merge command and removing
- * redundant commits that the user shouldn't have given to it.
- *
- * This function destroys the STALE bit of the commit objects'
- * flags.
- */
-extern struct commit_list *reduce_heads(struct commit_list *heads);
-
-/*
- * Like `reduce_heads()`, except it replaces the list. Use this
- * instead of `foo = reduce_heads(foo);` to avoid memory leaks.
- */
-extern void reduce_heads_replace(struct commit_list **heads);
-
struct commit_extra_header {
struct commit_extra_header *next;
char *key;
size_t *out_len);
/* Find the end of the log message, the right place for a new trailer. */
-extern int ignore_non_trailer(const char *buf, size_t len);
+extern size_t ignore_non_trailer(const char *buf, size_t len);
typedef int (*each_mergetag_fn)(struct commit *commit, struct commit_extra_header *extra,
void *cb_data);
#include "../strbuf.h"
#include "../run-command.h"
#include "../cache.h"
+#include "win32/lazyload.h"
#define HCAST(type, handle) ((type)(intptr_t)handle)
}
}
+/* Normalizes NT paths as returned by some low-level APIs. */
+static wchar_t *normalize_ntpath(wchar_t *wbuf)
+{
+ int i;
+ /* fix absolute path prefixes */
+ if (wbuf[0] == '\\') {
+ /* strip NT namespace prefixes */
+ if (!wcsncmp(wbuf, L"\\??\\", 4) ||
+ !wcsncmp(wbuf, L"\\\\?\\", 4))
+ wbuf += 4;
+ else if (!wcsnicmp(wbuf, L"\\DosDevices\\", 12))
+ wbuf += 12;
+ /* replace remaining '...UNC\' with '\\' */
+ if (!wcsnicmp(wbuf, L"UNC\\", 4)) {
+ wbuf += 2;
+ *wbuf = '\\';
+ }
+ }
+ /* convert backslashes to slashes */
+ for (i = 0; wbuf[i]; i++)
+ if (wbuf[i] == '\\')
+ wbuf[i] = '/';
+ return wbuf;
+}
+
int mingw_unlink(const char *pathname)
{
int ret, tries = 0;
return ret;
}
+/*
+ * Calling CreateFile() using FILE_APPEND_DATA and without FILE_WRITE_DATA
+ * is documented in [1] as opening a writable file handle in append mode.
+ * (It is believed that) this is atomic since it is maintained by the
+ * kernel unlike the O_APPEND flag which is racily maintained by the CRT.
+ *
+ * [1] https://docs.microsoft.com/en-us/windows/desktop/fileio/file-access-rights-constants
+ *
+ * This trick does not appear to work for named pipes. Instead it creates
+ * a named pipe client handle that cannot be written to. Callers should
+ * just use the regular _wopen() for them. (And since client handle gets
+ * bound to a unique server handle, it isn't really an issue.)
+ */
static int mingw_open_append(wchar_t const *wfilename, int oflags, ...)
{
HANDLE handle;
NULL, create, FILE_ATTRIBUTE_NORMAL, NULL);
if (handle == INVALID_HANDLE_VALUE)
return errno = err_win_to_posix(GetLastError()), -1;
+
/*
* No O_APPEND here, because the CRT uses it only to reset the
- * file pointer to EOF on write(); but that is not necessary
- * for a file created with FILE_APPEND_DATA.
+ * file pointer to EOF before each write(); but that is not
+ * necessary (and may lead to races) for a file created with
+ * FILE_APPEND_DATA.
*/
fd = _open_osfhandle((intptr_t)handle, O_BINARY);
if (fd < 0)
return fd;
}
+/*
+ * Does the pathname map to the local named pipe filesystem?
+ * That is, does it have a "//./pipe/" prefix?
+ */
+static int is_local_named_pipe_path(const char *filename)
+{
+ return (is_dir_sep(filename[0]) &&
+ is_dir_sep(filename[1]) &&
+ filename[2] == '.' &&
+ is_dir_sep(filename[3]) &&
+ !strncasecmp(filename+4, "pipe", 4) &&
+ is_dir_sep(filename[8]) &&
+ filename[9]);
+}
+
int mingw_open (const char *filename, int oflags, ...)
{
typedef int (*open_fn_t)(wchar_t const *wfilename, int oflags, ...);
if (filename && !strcmp(filename, "/dev/null"))
filename = "nul";
- if (oflags & O_APPEND)
+ if ((oflags & O_APPEND) && !is_local_named_pipe_path(filename))
open_fn = mingw_open_append;
else
open_fn = _wopen;
char *mingw_getcwd(char *pointer, int len)
{
- wchar_t wpointer[MAX_PATH];
- if (!_wgetcwd(wpointer, ARRAY_SIZE(wpointer)))
+ wchar_t cwd[MAX_PATH], wpointer[MAX_PATH];
+ DWORD ret = GetCurrentDirectoryW(ARRAY_SIZE(cwd), cwd);
+
+ if (!ret || ret >= ARRAY_SIZE(cwd)) {
+ errno = ret ? ENAMETOOLONG : err_win_to_posix(GetLastError());
+ return NULL;
+ }
+ ret = GetLongPathNameW(cwd, wpointer, ARRAY_SIZE(wpointer));
+ if (!ret && GetLastError() == ERROR_ACCESS_DENIED) {
+ HANDLE hnd = CreateFileW(cwd, 0,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
+ OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
+ if (hnd == INVALID_HANDLE_VALUE)
+ return NULL;
+ ret = GetFinalPathNameByHandleW(hnd, wpointer, ARRAY_SIZE(wpointer), 0);
+ CloseHandle(hnd);
+ if (!ret || ret >= ARRAY_SIZE(wpointer))
+ return NULL;
+ if (xwcstoutf(pointer, normalize_ntpath(wpointer), len) < 0)
+ return NULL;
+ return pointer;
+ }
+ if (!ret || ret >= ARRAY_SIZE(wpointer))
return NULL;
if (xwcstoutf(pointer, wpointer, len) < 0)
return NULL;
WSAGetLastError());
for (name = libraries; *name; name++) {
- ipv6_dll = LoadLibrary(*name);
+ ipv6_dll = LoadLibraryExA(*name, NULL,
+ LOAD_LIBRARY_SEARCH_SYSTEM32);
if (!ipv6_dll)
continue;
return si.dwAllocationGranularity;
}
+/* See https://msdn.microsoft.com/en-us/library/windows/desktop/ms724435.aspx */
+enum EXTENDED_NAME_FORMAT {
+ NameDisplay = 3,
+ NameUserPrincipal = 8
+};
+
+static char *get_extended_user_info(enum EXTENDED_NAME_FORMAT type)
+{
+ DECLARE_PROC_ADDR(secur32.dll, BOOL, GetUserNameExW,
+ enum EXTENDED_NAME_FORMAT, LPCWSTR, PULONG);
+ static wchar_t wbuffer[1024];
+ DWORD len;
+
+ if (!INIT_PROC_ADDR(GetUserNameExW))
+ return NULL;
+
+ len = ARRAY_SIZE(wbuffer);
+ if (GetUserNameExW(type, wbuffer, &len)) {
+ char *converted = xmalloc((len *= 3));
+ if (xwcstoutf(converted, wbuffer, len) >= 0)
+ return converted;
+ free(converted);
+ }
+
+ return NULL;
+}
+
+char *mingw_query_user_email(void)
+{
+ return get_extended_user_info(NameUserPrincipal);
+}
+
struct passwd *getpwuid(int uid)
{
+ static unsigned initialized;
static char user_name[100];
- static struct passwd p;
+ static struct passwd *p;
+ DWORD len;
+
+ if (initialized)
+ return p;
- DWORD len = sizeof(user_name);
- if (!GetUserName(user_name, &len))
+ len = sizeof(user_name);
+ if (!GetUserName(user_name, &len)) {
+ initialized = 1;
return NULL;
- p.pw_name = user_name;
- p.pw_gecos = "unknown";
- p.pw_dir = NULL;
- return &p;
+ }
+
+ p = xmalloc(sizeof(*p));
+ p->pw_name = user_name;
+ p->pw_gecos = get_extended_user_info(NameDisplay);
+ if (!p->pw_gecos)
+ p->pw_gecos = "unknown";
+ p->pw_dir = NULL;
+
+ initialized = 1;
+ return p;
}
static HANDLE timer_event;
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
#if !defined(__MINGW64_VERSION_MAJOR) && (!defined(_MSC_VER) || _MSC_VER < 1800)
#define PRIuMAX "I64u"
#define PRId64 "I64d"
{
size_t n = 0;
- if (start != NULL || !(flags & MAP_PRIVATE))
+ if (start != NULL || flags != MAP_PRIVATE || prot != PROT_READ)
die("Invalid usage of mmap when built with NO_MMAP");
start = xmalloc(length);
#include <sys/types.h>
-/* Specification. */
-#include <poll.h>
-
#include <errno.h>
#include <limits.h>
#include <assert.h>
# include <unistd.h>
#endif
+/* Specification. */
+#include "poll.h"
+
#ifdef HAVE_SYS_IOCTL_H
# include <sys/ioctl.h>
#endif
#ifndef _GL_POLL_H
#define _GL_POLL_H
+#if defined(_WIN32_WINNT) && _WIN32_WINNT >= 0x600
+/* Vista has its own, socket-only poll() */
+#undef POLLIN
+#undef POLLPRI
+#undef POLLOUT
+#undef POLLERR
+#undef POLLHUP
+#undef POLLNVAL
+#undef POLLRDNORM
+#undef POLLRDBAND
+#undef POLLWRNORM
+#undef POLLWRBAND
+#define pollfd compat_pollfd
+#endif
+
/* fake a poll(2) environment */
#define POLLIN 0x0001 /* any readable data available */
#define POLLPRI 0x0002 /* OOB/Urgent readable data */
int git_config_get_fsmonitor(void)
{
if (git_config_get_pathname("core.fsmonitor", &core_fsmonitor))
- core_fsmonitor = getenv("GIT_FSMONITOR_TEST");
+ core_fsmonitor = getenv("GIT_TEST_FSMONITOR");
if (core_fsmonitor && !*core_fsmonitor)
core_fsmonitor = NULL;
return 0;
}
+int git_config_get_index_threads(void)
+{
+ int is_bool, val = 0;
+
+ val = git_env_ulong("GIT_TEST_INDEX_THREADS", 0);
+ if (val)
+ return val;
+
+ if (!git_config_get_bool_or_int("index.threads", &is_bool, &val)) {
+ if (is_bool)
+ return val ? 0 : 1;
+ else
+ return val;
+ }
+
+ return 0; /* auto */
+}
+
NORETURN
void git_die_config_linenr(const char *key, const char *filename, int linenr)
{
extern int git_config_get_split_index(void);
extern int git_config_get_max_percent_split_change(void);
extern int git_config_get_fsmonitor(void);
+extern int git_config_get_index_threads(void);
/* This dies if the configured or default date is in the future */
extern int git_config_get_expiry(const char *key, const char **output);
CFLAGS += -DUSE_PARENS_AROUND_GETTEXT_N=0
endif
CFLAGS += -Wdeclaration-after-statement
+CFLAGS += -Wformat-security
CFLAGS += -Wno-format-zero-length
CFLAGS += -Wold-style-definition
CFLAGS += -Woverflow
CFLAGS += -Wno-empty-body
CFLAGS += -Wno-missing-field-initializers
CFLAGS += -Wno-sign-compare
-CFLAGS += -Wno-unused-function
CFLAGS += -Wno-unused-parameter
endif
endif
NO_PYTHON = YesPlease
BLK_SHA1 = YesPlease
ETAGS_TARGET = ETAGS
- NO_INET_PTON = YesPlease
- NO_INET_NTOP = YesPlease
NO_POSIX_GOODIES = UnfortunatelyYes
NATIVE_CRLF = YesPlease
DEFAULT_HELP_FORMAT = html
NO_REGEX = YesPlease
NO_PYTHON = YesPlease
ETAGS_TARGET = ETAGS
- NO_INET_PTON = YesPlease
- NO_INET_NTOP = YesPlease
NO_POSIX_GOODIES = UnfortunatelyYes
DEFAULT_HELP_FORMAT = html
COMPAT_CFLAGS += -DNOGDI -Icompat -Icompat/win32
return 0;
name++;
- return !oidcmp(&null_oid, &oid) && !strcmp(name, "capabilities^{}");
+ return oideq(&null_oid, &oid) && !strcmp(name, "capabilities^{}");
}
static void check_no_capabilities(const char *line, int len)
#define CHECK_CONNECTED_INIT { 0 }
/*
- * Make sure that our object store has all the commits necessary to
- * connect the ancestry chain to some of our existing refs, and all
- * the trees and blobs that these commits use.
+ * Make sure that all given objects and all objects reachable from them
+ * either exist in our object store or (if the repository is a partial
+ * clone) are promised to be available.
*
* Return 0 if Ok, non zero otherwise (i.e. some missing objects)
*
identifier f !~ "^(get_commit_tree|get_commit_tree_in_graph_one|load_tree_for_commit)$";
expression c;
@@
- f(...) {...
+ f(...) {<...
- c->maybe_tree
+ get_commit_tree(c)
- ...}
+ ...>}
@@
expression c;
@@
-expression E1;
+struct object_id OID;
@@
-- is_null_sha1(E1.hash)
-+ is_null_oid(&E1)
+- is_null_sha1(OID.hash)
++ is_null_oid(&OID)
@@
-expression E1;
+struct object_id *OIDPTR;
@@
-- is_null_sha1(E1->hash)
-+ is_null_oid(E1)
+- is_null_sha1(OIDPTR->hash)
++ is_null_oid(OIDPTR)
@@
-expression E1;
+struct object_id OID;
@@
-- sha1_to_hex(E1.hash)
-+ oid_to_hex(&E1)
+- sha1_to_hex(OID.hash)
++ oid_to_hex(&OID)
@@
identifier f != oid_to_hex;
-expression E1;
+struct object_id *OIDPTR;
@@
- f(...) {...
-- sha1_to_hex(E1->hash)
-+ oid_to_hex(E1)
- ...}
+ f(...) {<...
+- sha1_to_hex(OIDPTR->hash)
++ oid_to_hex(OIDPTR)
+ ...>}
@@
-expression E1, E2;
+expression E;
+struct object_id OID;
@@
-- sha1_to_hex_r(E1, E2.hash)
-+ oid_to_hex_r(E1, &E2)
+- sha1_to_hex_r(E, OID.hash)
++ oid_to_hex_r(E, &OID)
@@
identifier f != oid_to_hex_r;
-expression E1, E2;
+expression E;
+struct object_id *OIDPTR;
@@
- f(...) {...
-- sha1_to_hex_r(E1, E2->hash)
-+ oid_to_hex_r(E1, E2)
- ...}
+ f(...) {<...
+- sha1_to_hex_r(E, OIDPTR->hash)
++ oid_to_hex_r(E, OIDPTR)
+ ...>}
@@
-expression E1;
+struct object_id OID;
@@
-- hashclr(E1.hash)
-+ oidclr(&E1)
+- hashclr(OID.hash)
++ oidclr(&OID)
@@
identifier f != oidclr;
-expression E1;
+struct object_id *OIDPTR;
@@
- f(...) {...
-- hashclr(E1->hash)
-+ oidclr(E1)
- ...}
+ f(...) {<...
+- hashclr(OIDPTR->hash)
++ oidclr(OIDPTR)
+ ...>}
@@
-expression E1, E2;
+struct object_id OID1, OID2;
@@
-- hashcmp(E1.hash, E2.hash)
-+ oidcmp(&E1, &E2)
+- hashcmp(OID1.hash, OID2.hash)
++ oidcmp(&OID1, &OID2)
@@
identifier f != oidcmp;
-expression E1, E2;
+struct object_id *OIDPTR1, OIDPTR2;
@@
- f(...) {...
-- hashcmp(E1->hash, E2->hash)
-+ oidcmp(E1, E2)
- ...}
+ f(...) {<...
+- hashcmp(OIDPTR1->hash, OIDPTR2->hash)
++ oidcmp(OIDPTR1, OIDPTR2)
+ ...>}
@@
-expression E1, E2;
+struct object_id *OIDPTR;
+struct object_id OID;
@@
-- hashcmp(E1->hash, E2.hash)
-+ oidcmp(E1, &E2)
+- hashcmp(OIDPTR->hash, OID.hash)
++ oidcmp(OIDPTR, &OID)
@@
-expression E1, E2;
+struct object_id *OIDPTR;
+struct object_id OID;
@@
-- hashcmp(E1.hash, E2->hash)
-+ oidcmp(&E1, E2)
+- hashcmp(OID.hash, OIDPTR->hash)
++ oidcmp(&OID, OIDPTR)
@@
-expression E1, E2;
+struct object_id OID1, OID2;
@@
-- hashcpy(E1.hash, E2.hash)
-+ oidcpy(&E1, &E2)
+- hashcpy(OID1.hash, OID2.hash)
++ oidcpy(&OID1, &OID2)
@@
identifier f != oidcpy;
-expression E1, E2;
+struct object_id *OIDPTR1;
+struct object_id *OIDPTR2;
+@@
+ f(...) {<...
+- hashcpy(OIDPTR1->hash, OIDPTR2->hash)
++ oidcpy(OIDPTR1, OIDPTR2)
+ ...>}
+
@@
- f(...) {...
-- hashcpy(E1->hash, E2->hash)
-+ oidcpy(E1, E2)
- ...}
+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;
+@@
+- oidcmp(OIDPTR1, OIDPTR2) == 0
++ oideq(OIDPTR1, OIDPTR2)
@@
+identifier f != hasheq;
expression E1, E2;
@@
-- hashcpy(E1->hash, E2.hash)
-+ oidcpy(E1, &E2)
+ f(...) {<...
+- hashcmp(E1, E2) == 0
++ hasheq(E1, E2)
+ ...>}
+
+@@
+struct object_id *OIDPTR1;
+struct object_id *OIDPTR2;
+@@
+- oidcmp(OIDPTR1, OIDPTR2) != 0
++ !oideq(OIDPTR1, OIDPTR2)
@@
+identifier f != hasheq;
expression E1, E2;
@@
-- hashcpy(E1.hash, E2->hash)
-+ oidcpy(&E1, E2)
+ f(...) {<...
+- hashcmp(E1, E2) != 0
++ !hasheq(E1, E2)
+ ...>}
--- /dev/null
+@ preincrement @
+identifier i;
+@@
+- ++i > 1
++ i++
*) ;;
esac
;;
+ --multiple) no_complete_refspec=1; break ;;
-*) ;;
*) remote="$i"; break ;;
esac
esac
}
-_git_cherry ()
-{
- case "$cur" in
- --*)
- __gitcomp_builtin cherry
- return
- esac
-
- __git_complete_refs
-}
-
__git_cherry_pick_inprogress_options="--continue --quit --abort"
_git_cherry_pick ()
return
;;
--*)
- __gitcomp "--tool= --prompt --no-prompt"
+ __gitcomp "--tool= --prompt --no-prompt --gui --no-gui"
return
;;
esac
drop,--*)
__gitcomp "--quiet"
;;
+ list,--*)
+ __gitcomp "--name-status --oneline --patch-with-stat"
+ ;;
show,--*|branch,--*)
;;
branch,*)
--- /dev/null
+#!/bin/sh
+
+# Usage: Run 'contrib/coverage-diff.sh <version1> <version2>' from source-root
+# after running
+#
+# make coverage-test
+# make coverage-report
+#
+# while checked out at <version2>. This script combines the *.gcov files
+# generated by the 'make' commands above with 'git diff <version1> <version2>'
+# to report new lines that are not covered by the test suite.
+
+V1=$1
+V2=$2
+
+diff_lines () {
+ perl -e '
+ my $line_num;
+ while (<>) {
+ # Hunk header? Grab the beginning in postimage.
+ if (/^@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@/) {
+ $line_num = $1;
+ next;
+ }
+
+ # Have we seen a hunk? Ignore "diff --git" etc.
+ next unless defined $line_num;
+
+ # Deleted line? Ignore.
+ if (/^-/) {
+ next;
+ }
+
+ # Show only the line number of added lines.
+ if (/^\+/) {
+ print "$line_num\n";
+ }
+ # Either common context or added line appear in
+ # the postimage. Count it.
+ $line_num++;
+ }
+ '
+}
+
+files=$(git diff --name-only "$V1" "$V2" -- \*.c)
+
+# create empty file
+>coverage-data.txt
+
+for file in $files
+do
+ git diff "$V1" "$V2" -- "$file" |
+ diff_lines |
+ sort >new_lines.txt
+
+ if ! test -s new_lines.txt
+ then
+ continue
+ fi
+
+ hash_file=$(echo $file | sed "s/\//\#/")
+
+ if ! test -s "$hash_file.gcov"
+ then
+ continue
+ fi
+
+ sed -ne '/#####:/{
+ s/ #####://
+ s/:.*//
+ s/ //g
+ p
+ }' "$hash_file.gcov" |
+ sort >uncovered_lines.txt
+
+ comm -12 uncovered_lines.txt new_lines.txt |
+ sed -e 's/$/\)/' |
+ sed -e 's/^/ /' >uncovered_new_lines.txt
+
+ grep -q '[^[:space:]]' <uncovered_new_lines.txt &&
+ echo $file >>coverage-data.txt &&
+ git blame -s "$V2" -- "$file" |
+ sed 's/\t//g' |
+ grep -f uncovered_new_lines.txt >>coverage-data.txt &&
+ echo >>coverage-data.txt
+
+ rm -f new_lines.txt uncovered_lines.txt uncovered_new_lines.txt
+done
+
+cat coverage-data.txt
+
+echo "Commits introducing uncovered code:"
+
+commit_list=$(cat coverage-data.txt |
+ grep -E '^[0-9a-f]{7,} ' |
+ awk '{print $1;}' |
+ sort |
+ uniq)
+
+(
+ for commit in $commit_list
+ do
+ git log --no-decorate --pretty=format:'%an %h: %s' -1 $commit
+ echo
+ done
+) | sort
+
+rm coverage-data.txt
static void load_cred_funcs(void)
{
/* load DLLs */
- advapi = LoadLibrary("advapi32.dll");
+ advapi = LoadLibraryExA("advapi32.dll", NULL,
+ LOAD_LIBRARY_SEARCH_SYSTEM32);
if (!advapi)
die("failed to load advapi32.dll");
doc: $(GIT_SUBTREE_DOC) $(GIT_SUBTREE_HTML)
+man: $(GIT_SUBTREE_DOC)
+
+html: $(GIT_SUBTREE_HTML)
+
install: $(GIT_SUBTREE)
$(INSTALL) -d -m 755 $(DESTDIR)$(gitexecdir)
$(INSTALL) -m 755 $(GIT_SUBTREE) $(DESTDIR)$(gitexecdir)
}
check_parents () {
- missed=$(cache_miss "$@")
+ missed=$(cache_miss "$1")
+ local indent=$(($2 + 1))
for miss in $missed
do
if ! test -r "$cachedir/notree/$miss"
then
debug " incorrect order: $miss"
+ process_split_commit "$miss" "" "$indent"
fi
done
}
revs="$2"
main=
sub=
- git log --grep="^git-subtree-dir: $dir/*\$" \
+ local grep_format="^git-subtree-dir: $dir/*\$"
+ if test -n "$ignore_joins"
+ then
+ grep_format="^Add '$dir/' from commit '"
+ fi
+ git log --grep="$grep_format" \
--no-show-signature --pretty=format:'START %H%n%s%n%n%b%nEND%n' $revs |
while read a b junk
do
nonidentical=
p=
gotparents=
+ copycommit=
for parent in $newparents
do
ptree=$(toptree_for_commit $parent) || exit $?
if test "$ptree" = "$tree"
then
# an identical parent could be used in place of this rev.
- identical="$parent"
+ if test -n "$identical"
+ then
+ # if a previous identical parent was found, check whether
+ # one is already an ancestor of the other
+ mergebase=$(git merge-base $identical $parent)
+ if test "$identical" = "$mergebase"
+ then
+ # current identical commit is an ancestor of parent
+ identical="$parent"
+ elif test "$parent" != "$mergebase"
+ then
+ # no common history; commit must be copied
+ copycommit=1
+ fi
+ else
+ # first identical parent detected
+ identical="$parent"
+ fi
else
nonidentical="$parent"
fi
fi
done
- copycommit=
if test -n "$identical" && test -n "$nonidentical"
then
extras=$(git rev-list --count $identical..$nonidentical)
die "'$1' does not look like a ref"
}
+process_split_commit () {
+ local rev="$1"
+ local parents="$2"
+ local indent=$3
+
+ if test $indent -eq 0
+ then
+ revcount=$(($revcount + 1))
+ else
+ # processing commit without normal parent information;
+ # fetch from repo
+ parents=$(git rev-parse "$rev^@")
+ extracount=$(($extracount + 1))
+ fi
+
+ progress "$revcount/$revmax ($createcount) [$extracount]"
+
+ debug "Processing commit: $rev"
+ exists=$(cache_get "$rev")
+ if test -n "$exists"
+ then
+ debug " prior: $exists"
+ return
+ fi
+ createcount=$(($createcount + 1))
+ debug " parents: $parents"
+ check_parents "$parents" "$indent"
+ newparents=$(cache_get $parents)
+ debug " newparents: $newparents"
+
+ tree=$(subtree_for_commit "$rev" "$dir")
+ debug " tree is: $tree"
+
+ # ugly. is there no better way to tell if this is a subtree
+ # vs. a mainline commit? Does it matter?
+ if test -z "$tree"
+ then
+ set_notree "$rev"
+ if test -n "$newparents"
+ then
+ cache_set "$rev" "$rev"
+ fi
+ return
+ fi
+
+ newrev=$(copy_or_skip "$rev" "$tree" "$newparents") || exit $?
+ debug " newrev is: $newrev"
+ cache_set "$rev" "$newrev"
+ cache_set latest_new "$newrev"
+ cache_set latest_old "$rev"
+}
+
cmd_add () {
if test -e "$dir"
then
done
fi
- if test -n "$ignore_joins"
- then
- unrevs=
- else
- unrevs="$(find_existing_splits "$dir" "$revs")"
- fi
+ unrevs="$(find_existing_splits "$dir" "$revs")"
# We can't restrict rev-list to only $dir here, because some of our
# parents have the $dir contents the root, and those won't match.
revmax=$(eval "$grl" | wc -l)
revcount=0
createcount=0
+ extracount=0
eval "$grl" |
while read rev parents
do
- revcount=$(($revcount + 1))
- progress "$revcount/$revmax ($createcount)"
- debug "Processing commit: $rev"
- exists=$(cache_get "$rev")
- if test -n "$exists"
- then
- debug " prior: $exists"
- continue
- fi
- createcount=$(($createcount + 1))
- debug " parents: $parents"
- newparents=$(cache_get $parents)
- debug " newparents: $newparents"
-
- tree=$(subtree_for_commit "$rev" "$dir")
- debug " tree is: $tree"
-
- check_parents $parents
-
- # ugly. is there no better way to tell if this is a subtree
- # vs. a mainline commit? Does it matter?
- if test -z "$tree"
- then
- set_notree "$rev"
- if test -n "$newparents"
- then
- cache_set "$rev" "$rev"
- fi
- continue
- fi
-
- newrev=$(copy_or_skip "$rev" "$tree" "$newparents") || exit $?
- debug " newrev is: $newrev"
- cache_set "$rev" "$newrev"
- cache_set latest_new "$newrev"
- cache_set latest_old "$rev"
+ process_split_commit "$rev" "$parents" 0
done || exit $?
latest_new=$(cache_get latest_new)
struct conv_attrs *ca, const char *path)
{
static struct attr_check *check;
+ struct attr_check_item *ccheck = NULL;
if (!check) {
check = attr_check_initl("crlf", "ident", "filter",
git_config(read_convert_config, NULL);
}
- if (!git_check_attr(istate, path, check)) {
- struct attr_check_item *ccheck = check->items;
- ca->crlf_action = git_path_check_crlf(ccheck + 4);
- if (ca->crlf_action == CRLF_UNDEFINED)
- ca->crlf_action = git_path_check_crlf(ccheck + 0);
- ca->ident = git_path_check_ident(ccheck + 1);
- ca->drv = git_path_check_convert(ccheck + 2);
- if (ca->crlf_action != CRLF_BINARY) {
- enum eol eol_attr = git_path_check_eol(ccheck + 3);
- if (ca->crlf_action == CRLF_AUTO && eol_attr == EOL_LF)
- ca->crlf_action = CRLF_AUTO_INPUT;
- else if (ca->crlf_action == CRLF_AUTO && eol_attr == EOL_CRLF)
- ca->crlf_action = CRLF_AUTO_CRLF;
- else if (eol_attr == EOL_LF)
- ca->crlf_action = CRLF_TEXT_INPUT;
- else if (eol_attr == EOL_CRLF)
- ca->crlf_action = CRLF_TEXT_CRLF;
- }
- ca->working_tree_encoding = git_path_check_encoding(ccheck + 5);
- } else {
- ca->drv = NULL;
- ca->crlf_action = CRLF_UNDEFINED;
- ca->ident = 0;
+ git_check_attr(istate, path, check);
+ ccheck = check->items;
+ ca->crlf_action = git_path_check_crlf(ccheck + 4);
+ if (ca->crlf_action == CRLF_UNDEFINED)
+ ca->crlf_action = git_path_check_crlf(ccheck + 0);
+ ca->ident = git_path_check_ident(ccheck + 1);
+ ca->drv = git_path_check_convert(ccheck + 2);
+ if (ca->crlf_action != CRLF_BINARY) {
+ enum eol eol_attr = git_path_check_eol(ccheck + 3);
+ if (ca->crlf_action == CRLF_AUTO && eol_attr == EOL_LF)
+ ca->crlf_action = CRLF_AUTO_INPUT;
+ else if (ca->crlf_action == CRLF_AUTO && eol_attr == EOL_CRLF)
+ ca->crlf_action = CRLF_AUTO_CRLF;
+ else if (eol_attr == EOL_LF)
+ ca->crlf_action = CRLF_TEXT_INPUT;
+ else if (eol_attr == EOL_CRLF)
+ ca->crlf_action = CRLF_TEXT_CRLF;
}
+ ca->working_tree_encoding = git_path_check_encoding(ccheck + 5);
/* Save attr and make a decision for action */
ca->attr_action = ca->crlf_action;
--- /dev/null
+#include "cache.h"
+#include "attr.h"
+#include "object.h"
+#include "blob.h"
+#include "commit.h"
+#include "tag.h"
+#include "tree.h"
+#include "delta.h"
+#include "pack.h"
+#include "tree-walk.h"
+#include "diff.h"
+#include "revision.h"
+#include "list-objects.h"
+#include "progress.h"
+#include "refs.h"
+#include "khash.h"
+#include "pack-bitmap.h"
+#include "pack-objects.h"
+#include "delta-islands.h"
+#include "sha1-array.h"
+#include "config.h"
+
+KHASH_INIT(str, const char *, void *, 1, kh_str_hash_func, kh_str_hash_equal)
+
+static khash_sha1 *island_marks;
+static unsigned island_counter;
+static unsigned island_counter_core;
+
+static kh_str_t *remote_islands;
+
+struct remote_island {
+ uint64_t hash;
+ struct oid_array oids;
+};
+
+struct island_bitmap {
+ uint32_t refcount;
+ uint32_t bits[FLEX_ARRAY];
+};
+
+static uint32_t island_bitmap_size;
+
+/*
+ * Allocate a new bitmap; if "old" is not NULL, the new bitmap will be a copy
+ * of "old". Otherwise, the new bitmap is empty.
+ */
+static struct island_bitmap *island_bitmap_new(const struct island_bitmap *old)
+{
+ size_t size = sizeof(struct island_bitmap) + (island_bitmap_size * 4);
+ struct island_bitmap *b = xcalloc(1, size);
+
+ if (old)
+ memcpy(b, old, size);
+
+ b->refcount = 1;
+ return b;
+}
+
+static void island_bitmap_or(struct island_bitmap *a, const struct island_bitmap *b)
+{
+ uint32_t i;
+
+ for (i = 0; i < island_bitmap_size; ++i)
+ a->bits[i] |= b->bits[i];
+}
+
+static int island_bitmap_is_subset(struct island_bitmap *self,
+ struct island_bitmap *super)
+{
+ uint32_t i;
+
+ if (self == super)
+ return 1;
+
+ for (i = 0; i < island_bitmap_size; ++i) {
+ if ((self->bits[i] & super->bits[i]) != self->bits[i])
+ return 0;
+ }
+
+ return 1;
+}
+
+#define ISLAND_BITMAP_BLOCK(x) (x / 32)
+#define ISLAND_BITMAP_MASK(x) (1 << (x % 32))
+
+static void island_bitmap_set(struct island_bitmap *self, uint32_t i)
+{
+ self->bits[ISLAND_BITMAP_BLOCK(i)] |= ISLAND_BITMAP_MASK(i);
+}
+
+static int island_bitmap_get(struct island_bitmap *self, uint32_t i)
+{
+ return (self->bits[ISLAND_BITMAP_BLOCK(i)] & ISLAND_BITMAP_MASK(i)) != 0;
+}
+
+int in_same_island(const struct object_id *trg_oid, const struct object_id *src_oid)
+{
+ khiter_t trg_pos, src_pos;
+
+ /* If we aren't using islands, assume everything goes together. */
+ if (!island_marks)
+ return 1;
+
+ /*
+ * If we don't have a bitmap for the target, we can delta it
+ * against anything -- it's not an important object
+ */
+ trg_pos = kh_get_sha1(island_marks, trg_oid->hash);
+ if (trg_pos >= kh_end(island_marks))
+ return 1;
+
+ /*
+ * if the source (our delta base) doesn't have a bitmap,
+ * we don't want to base any deltas on it!
+ */
+ src_pos = kh_get_sha1(island_marks, src_oid->hash);
+ if (src_pos >= kh_end(island_marks))
+ return 0;
+
+ return island_bitmap_is_subset(kh_value(island_marks, trg_pos),
+ kh_value(island_marks, src_pos));
+}
+
+int island_delta_cmp(const struct object_id *a, const struct object_id *b)
+{
+ khiter_t a_pos, b_pos;
+ struct island_bitmap *a_bitmap = NULL, *b_bitmap = NULL;
+
+ if (!island_marks)
+ return 0;
+
+ a_pos = kh_get_sha1(island_marks, a->hash);
+ if (a_pos < kh_end(island_marks))
+ a_bitmap = kh_value(island_marks, a_pos);
+
+ b_pos = kh_get_sha1(island_marks, b->hash);
+ if (b_pos < kh_end(island_marks))
+ b_bitmap = kh_value(island_marks, b_pos);
+
+ if (a_bitmap) {
+ if (!b_bitmap || !island_bitmap_is_subset(a_bitmap, b_bitmap))
+ return -1;
+ }
+ if (b_bitmap) {
+ if (!a_bitmap || !island_bitmap_is_subset(b_bitmap, a_bitmap))
+ return 1;
+ }
+
+ return 0;
+}
+
+static struct island_bitmap *create_or_get_island_marks(struct object *obj)
+{
+ khiter_t pos;
+ int hash_ret;
+
+ pos = kh_put_sha1(island_marks, obj->oid.hash, &hash_ret);
+ if (hash_ret)
+ kh_value(island_marks, pos) = island_bitmap_new(NULL);
+
+ return kh_value(island_marks, pos);
+}
+
+static void set_island_marks(struct object *obj, struct island_bitmap *marks)
+{
+ struct island_bitmap *b;
+ khiter_t pos;
+ int hash_ret;
+
+ pos = kh_put_sha1(island_marks, obj->oid.hash, &hash_ret);
+ if (hash_ret) {
+ /*
+ * We don't have one yet; make a copy-on-write of the
+ * parent.
+ */
+ marks->refcount++;
+ kh_value(island_marks, pos) = marks;
+ return;
+ }
+
+ /*
+ * We do have it. Make sure we split any copy-on-write before
+ * updating.
+ */
+ b = kh_value(island_marks, pos);
+ if (b->refcount > 1) {
+ b->refcount--;
+ b = kh_value(island_marks, pos) = island_bitmap_new(b);
+ }
+ island_bitmap_or(b, marks);
+}
+
+static void mark_remote_island_1(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]);
+
+ if (!obj)
+ continue;
+
+ marks = create_or_get_island_marks(obj);
+ island_bitmap_set(marks, island_counter);
+
+ if (is_core_island && obj->type == OBJ_COMMIT)
+ obj->flags |= NEEDS_BITMAP;
+
+ /* If it was a tag, also make sure we hit the underlying object. */
+ while (obj && obj->type == OBJ_TAG) {
+ obj = ((struct tag *)obj)->tagged;
+ if (obj) {
+ parse_object(the_repository, &obj->oid);
+ marks = create_or_get_island_marks(obj);
+ island_bitmap_set(marks, island_counter);
+ }
+ }
+ }
+
+ if (is_core_island)
+ island_counter_core = island_counter;
+
+ island_counter++;
+}
+
+struct tree_islands_todo {
+ struct object_entry *entry;
+ unsigned int depth;
+};
+
+static int tree_depth_compare(const void *a, const void *b)
+{
+ const struct tree_islands_todo *todo_a = a;
+ const struct tree_islands_todo *todo_b = b;
+
+ return todo_a->depth - todo_b->depth;
+}
+
+void resolve_tree_islands(int progress, struct packing_data *to_pack)
+{
+ struct progress *progress_state = NULL;
+ struct tree_islands_todo *todo;
+ int nr = 0;
+ int i;
+
+ if (!island_marks)
+ return;
+
+ /*
+ * We process only trees, as commits and tags have already been handled
+ * (and passed their marks on to root trees, as well. We must make sure
+ * to process them in descending tree-depth order so that marks
+ * propagate down the tree properly, even if a sub-tree is found in
+ * multiple parent trees.
+ */
+ ALLOC_ARRAY(todo, to_pack->nr_objects);
+ for (i = 0; i < to_pack->nr_objects; i++) {
+ if (oe_type(&to_pack->objects[i]) == OBJ_TREE) {
+ todo[nr].entry = &to_pack->objects[i];
+ todo[nr].depth = oe_tree_depth(to_pack, &to_pack->objects[i]);
+ nr++;
+ }
+ }
+ QSORT(todo, nr, tree_depth_compare);
+
+ if (progress)
+ progress_state = start_progress(_("Propagating island marks"), nr);
+
+ for (i = 0; i < nr; i++) {
+ struct object_entry *ent = todo[i].entry;
+ struct island_bitmap *root_marks;
+ struct tree *tree;
+ struct tree_desc desc;
+ struct name_entry entry;
+ khiter_t pos;
+
+ pos = kh_get_sha1(island_marks, ent->idx.oid.hash);
+ if (pos >= kh_end(island_marks))
+ continue;
+
+ root_marks = kh_value(island_marks, pos);
+
+ tree = lookup_tree(the_repository, &ent->idx.oid);
+ if (!tree || parse_tree(tree) < 0)
+ die(_("bad tree object %s"), oid_to_hex(&ent->idx.oid));
+
+ init_tree_desc(&desc, tree->buffer, tree->size);
+ while (tree_entry(&desc, &entry)) {
+ struct object *obj;
+
+ if (S_ISGITLINK(entry.mode))
+ continue;
+
+ obj = lookup_object(the_repository, entry.oid->hash);
+ if (!obj)
+ continue;
+
+ set_island_marks(obj, root_marks);
+ }
+
+ free_tree_buffer(tree);
+
+ display_progress(progress_state, i+1);
+ }
+
+ stop_progress(&progress_state);
+ free(todo);
+}
+
+static regex_t *island_regexes;
+static unsigned int island_regexes_alloc, island_regexes_nr;
+static const char *core_island_name;
+
+static int island_config_callback(const char *k, const char *v, void *cb)
+{
+ if (!strcmp(k, "pack.island")) {
+ struct strbuf re = STRBUF_INIT;
+
+ if (!v)
+ return config_error_nonbool(k);
+
+ ALLOC_GROW(island_regexes, island_regexes_nr + 1, island_regexes_alloc);
+
+ if (*v != '^')
+ strbuf_addch(&re, '^');
+ strbuf_addstr(&re, v);
+
+ if (regcomp(&island_regexes[island_regexes_nr], re.buf, REG_EXTENDED))
+ die(_("failed to load island regex for '%s': %s"), k, re.buf);
+
+ strbuf_release(&re);
+ island_regexes_nr++;
+ return 0;
+ }
+
+ if (!strcmp(k, "pack.islandcore"))
+ return git_config_string(&core_island_name, k, v);
+
+ return 0;
+}
+
+static void add_ref_to_island(const char *island_name, const struct object_id *oid)
+{
+ uint64_t sha_core;
+ struct remote_island *rl = NULL;
+
+ int hash_ret;
+ khiter_t pos = kh_put_str(remote_islands, island_name, &hash_ret);
+
+ if (hash_ret) {
+ kh_key(remote_islands, pos) = xstrdup(island_name);
+ kh_value(remote_islands, pos) = xcalloc(1, sizeof(struct remote_island));
+ }
+
+ rl = kh_value(remote_islands, pos);
+ oid_array_append(&rl->oids, oid);
+
+ memcpy(&sha_core, oid->hash, sizeof(uint64_t));
+ rl->hash += sha_core;
+}
+
+static int find_island_for_ref(const char *refname, const struct object_id *oid,
+ int flags, void *data)
+{
+ /*
+ * We should advertise 'ARRAY_SIZE(matches) - 2' as the max,
+ * so we can diagnose below a config with more capture groups
+ * than we support.
+ */
+ regmatch_t matches[16];
+ int i, m;
+ struct strbuf island_name = STRBUF_INIT;
+
+ /* walk backwards to get last-one-wins ordering */
+ for (i = island_regexes_nr - 1; i >= 0; i--) {
+ if (!regexec(&island_regexes[i], refname,
+ ARRAY_SIZE(matches), matches, 0))
+ break;
+ }
+
+ if (i < 0)
+ return 0;
+
+ if (matches[ARRAY_SIZE(matches) - 1].rm_so != -1)
+ warning(_("island regex from config has "
+ "too many capture groups (max=%d)"),
+ (int)ARRAY_SIZE(matches) - 2);
+
+ for (m = 1; m < ARRAY_SIZE(matches); m++) {
+ regmatch_t *match = &matches[m];
+
+ if (match->rm_so == -1)
+ continue;
+
+ if (island_name.len)
+ strbuf_addch(&island_name, '-');
+
+ strbuf_add(&island_name, refname + match->rm_so, match->rm_eo - match->rm_so);
+ }
+
+ add_ref_to_island(island_name.buf, oid);
+ strbuf_release(&island_name);
+ return 0;
+}
+
+static struct remote_island *get_core_island(void)
+{
+ if (core_island_name) {
+ khiter_t pos = kh_get_str(remote_islands, core_island_name);
+ if (pos < kh_end(remote_islands))
+ return kh_value(remote_islands, pos);
+ }
+
+ return NULL;
+}
+
+static void deduplicate_islands(void)
+{
+ struct remote_island *island, *core = NULL, **list;
+ unsigned int island_count, dst, src, ref, i = 0;
+
+ island_count = kh_size(remote_islands);
+ ALLOC_ARRAY(list, island_count);
+
+ kh_foreach_value(remote_islands, island, {
+ list[i++] = island;
+ });
+
+ for (ref = 0; ref + 1 < island_count; ref++) {
+ for (src = ref + 1, dst = src; src < island_count; src++) {
+ if (list[ref]->hash == list[src]->hash)
+ continue;
+
+ if (src != dst)
+ list[dst] = list[src];
+
+ dst++;
+ }
+ island_count = dst;
+ }
+
+ island_bitmap_size = (island_count / 32) + 1;
+ core = get_core_island();
+
+ for (i = 0; i < island_count; ++i) {
+ mark_remote_island_1(list[i], core && list[i]->hash == core->hash);
+ }
+
+ free(list);
+}
+
+void load_delta_islands(void)
+{
+ 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();
+
+ fprintf(stderr, _("Marked %d islands, done.\n"), island_counter);
+}
+
+void propagate_island_marks(struct commit *commit)
+{
+ khiter_t pos = kh_get_sha1(island_marks, commit->object.oid.hash);
+
+ if (pos < kh_end(island_marks)) {
+ struct commit_list *p;
+ struct island_bitmap *root_marks = kh_value(island_marks, pos);
+
+ parse_commit(commit);
+ set_island_marks(&get_commit_tree(commit)->object, root_marks);
+ for (p = commit->parents; p; p = p->next)
+ set_island_marks(&p->item->object, root_marks);
+ }
+}
+
+int compute_pack_layers(struct packing_data *to_pack)
+{
+ uint32_t i;
+
+ if (!core_island_name || !island_marks)
+ return 1;
+
+ for (i = 0; i < to_pack->nr_objects; ++i) {
+ struct object_entry *entry = &to_pack->objects[i];
+ khiter_t pos = kh_get_sha1(island_marks, entry->idx.oid.hash);
+
+ oe_set_layer(to_pack, entry, 1);
+
+ if (pos < kh_end(island_marks)) {
+ struct island_bitmap *bitmap = kh_value(island_marks, pos);
+
+ if (island_bitmap_get(bitmap, island_counter_core))
+ oe_set_layer(to_pack, entry, 0);
+ }
+ }
+
+ return 2;
+}
--- /dev/null
+#ifndef DELTA_ISLANDS_H
+#define DELTA_ISLANDS_H
+
+struct object_id;
+struct packing_data;
+struct commit;
+
+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 propagate_island_marks(struct commit *commit);
+int compute_pack_layers(struct packing_data *to_pack);
+
+#endif /* DELTA_ISLANDS_H */
struct stat *st, unsigned ce_option,
unsigned *dirty_submodule)
{
- int changed = ce_match_stat(ce, st, ce_option);
+ int changed = ie_match_stat(diffopt->repo->index, ce, st, ce_option);
if (S_ISGITLINK(ce->ce_mode)) {
struct diff_flags orig_flags = diffopt->flags;
if (!diffopt->flags.override_submodule_config)
unsigned ce_option = ((option & DIFF_RACY_IS_MODIFIED)
? CE_MATCH_RACY_IS_DIRTY : 0);
uint64_t start = getnanotime();
+ struct index_state *istate = revs->diffopt.repo->index;
diff_set_mnemonic_prefix(&revs->diffopt, "i/", "w/");
if (diff_unmerged_stage < 0)
diff_unmerged_stage = 2;
- entries = active_nr;
+ entries = istate->cache_nr;
for (i = 0; i < entries; i++) {
unsigned int oldmode, newmode;
- struct cache_entry *ce = active_cache[i];
+ struct cache_entry *ce = istate->cache[i];
int changed;
unsigned dirty_submodule = 0;
const struct object_id *old_oid, *new_oid;
if (diff_can_quit_early(&revs->diffopt))
break;
- if (!ce_path_match(&the_index, ce, &revs->prune_data, NULL))
+ if (!ce_path_match(istate, ce, &revs->prune_data, NULL))
continue;
if (ce_stage(ce)) {
dpath->mode = wt_mode;
while (i < entries) {
- struct cache_entry *nce = active_cache[i];
+ struct cache_entry *nce = istate->cache[i];
int stage;
if (strcmp(ce->name, nce->name))
}
if (revs->combine_merges && !cached &&
- (oidcmp(oid, &old_entry->oid) || oidcmp(&old_entry->oid, &new_entry->oid))) {
+ (!oideq(oid, &old_entry->oid) || !oideq(&old_entry->oid, &new_entry->oid))) {
struct combine_diff_path *p;
int pathlen = ce_namelen(new_entry);
}
oldmode = old_entry->ce_mode;
- if (mode == oldmode && !oidcmp(oid, &old_entry->oid) && !dirty_submodule &&
+ if (mode == oldmode && oideq(oid, &old_entry->oid) && !dirty_submodule &&
!revs->diffopt.flags.find_copies_harder)
return 0;
if (tree == o->df_conflict_entry)
tree = NULL;
- if (ce_path_match(&the_index, idx ? idx : tree, &revs->prune_data, NULL)) {
+ if (ce_path_match(revs->diffopt.repo->index,
+ idx ? idx : tree,
+ &revs->prune_data, NULL)) {
do_oneway_diff(o, idx, tree);
if (diff_can_quit_early(&revs->diffopt)) {
o->exiting_early = 1;
opts.merge = 1;
opts.fn = oneway_diff;
opts.unpack_data = revs;
- opts.src_index = &the_index;
+ opts.src_index = revs->diffopt.repo->index;
opts.dst_index = NULL;
opts.pathspec = &revs->diffopt.pathspec;
opts.pathspec->recursive = 1;
int run_diff_index(struct rev_info *revs, int cached)
{
struct object_array_entry *ent;
- uint64_t start = getnanotime();
if (revs->pending.nr != 1)
BUG("run_diff_index must be passed exactly one tree");
+ trace_performance_enter();
ent = revs->pending.objects;
if (diff_cache(revs, &ent->item->oid, ent->name, cached))
exit(128);
diffcore_fix_diff_index(&revs->diffopt);
diffcore_std(&revs->diffopt);
diff_flush(&revs->diffopt);
- trace_performance_since(start, "diff-index");
+ trace_performance_leave("diff-index");
return 0;
}
{
struct rev_info revs;
- init_revisions(&revs, NULL);
+ repo_init_revisions(the_repository, &revs, NULL);
copy_pathspec(&revs.prune_data, &opt->pathspec);
revs.diffopt = *opt;
struct rev_info rev;
struct setup_revision_opt opt;
- init_revisions(&rev, NULL);
+ repo_init_revisions(the_repository, &rev, NULL);
memset(&opt, 0, sizeof(opt));
opt.def = def;
setup_revisions(0, NULL, &rev, &opt);
}
}
-void diff_no_index(struct rev_info *revs,
+void diff_no_index(struct repository *r,
+ struct rev_info *revs,
int argc, const char **argv)
{
int i;
struct strbuf replacement = STRBUF_INIT;
const char *prefix = revs->prefix;
- diff_setup(&revs->diffopt);
+ /*
+ * FIXME: --no-index should not look at index and we should be
+ * able to pass NULL repo. Maybe later.
+ */
+ repo_diff_setup(r, &revs->diffopt);
for (i = 1; i < argc - 2; ) {
int j;
if (!strcmp(argv[i], "--no-index"))
return count;
}
-static int fill_mmfile(mmfile_t *mf, struct diff_filespec *one)
+static int fill_mmfile(struct repository *r, mmfile_t *mf,
+ struct diff_filespec *one)
{
if (!DIFF_FILE_VALID(one)) {
mf->ptr = (char *)""; /* does not matter */
mf->size = 0;
return 0;
}
- else if (diff_populate_filespec(one, 0))
+ else if (diff_populate_filespec(r, one, 0))
return -1;
mf->ptr = one->data;
}
/* like fill_mmfile, but only for size, so we can avoid retrieving blob */
-static unsigned long diff_filespec_size(struct diff_filespec *one)
+static unsigned long diff_filespec_size(struct repository *r,
+ struct diff_filespec *one)
{
if (!DIFF_FILE_VALID(one))
return 0;
- diff_populate_filespec(one, CHECK_SIZE_ONLY);
+ diff_populate_filespec(r, one, CHECK_SIZE_ONLY);
return one->size;
}
}
static void emit_line_0(struct diff_options *o,
- const char *set, unsigned reverse, const char *reset,
+ const char *set_sign, const char *set, unsigned reverse, const char *reset,
int first, const char *line, int len)
{
int has_trailing_newline, has_trailing_carriage_return;
- int nofirst;
+ int needs_reset = 0; /* at the end of the line */
FILE *file = o->file;
- if (first)
- fputs(diff_line_prefix(o), file);
- else if (!len)
- return;
+ fputs(diff_line_prefix(o), file);
- if (len == 0) {
- has_trailing_newline = (first == '\n');
- has_trailing_carriage_return = (!has_trailing_newline &&
- (first == '\r'));
- nofirst = has_trailing_newline || has_trailing_carriage_return;
- } else {
- has_trailing_newline = (len > 0 && line[len-1] == '\n');
- if (has_trailing_newline)
- len--;
- has_trailing_carriage_return = (len > 0 && line[len-1] == '\r');
- if (has_trailing_carriage_return)
- len--;
- nofirst = 0;
+ has_trailing_newline = (len > 0 && line[len-1] == '\n');
+ if (has_trailing_newline)
+ len--;
+
+ has_trailing_carriage_return = (len > 0 && line[len-1] == '\r');
+ if (has_trailing_carriage_return)
+ len--;
+
+ if (!len && !first)
+ goto end_of_line;
+
+ if (reverse && want_color(o->use_color)) {
+ fputs(GIT_COLOR_REVERSE, file);
+ needs_reset = 1;
}
- if (len || !nofirst) {
- if (reverse && want_color(o->use_color))
- fputs(GIT_COLOR_REVERSE, file);
+ if (set_sign) {
+ fputs(set_sign, file);
+ needs_reset = 1;
+ }
+
+ if (first)
+ fputc(first, file);
+
+ if (!len)
+ goto end_of_line;
+
+ if (set) {
+ if (set_sign && set != set_sign)
+ fputs(reset, file);
fputs(set, file);
- if (first && !nofirst)
- fputc(first, file);
- fwrite(line, len, 1, file);
- fputs(reset, file);
+ needs_reset = 1;
}
+ fwrite(line, len, 1, file);
+ needs_reset = 1; /* 'line' may contain color codes. */
+
+end_of_line:
+ if (needs_reset)
+ fputs(reset, file);
if (has_trailing_carriage_return)
fputc('\r', file);
if (has_trailing_newline)
static void emit_line(struct diff_options *o, const char *set, const char *reset,
const char *line, int len)
{
- emit_line_0(o, set, 0, reset, line[0], line+1, len-1);
+ emit_line_0(o, set, NULL, 0, reset, 0, line, len);
}
enum diff_symbol {
struct hashmap_entry ent;
const struct emitted_diff_symbol *es;
struct moved_entry *next_line;
- struct ws_delta *wsd;
};
/**
};
#define WS_DELTA_INIT { NULL, 0 }
+struct moved_block {
+ struct moved_entry *match;
+ struct ws_delta wsd;
+};
+
+static void moved_block_clear(struct moved_block *b)
+{
+ FREE_AND_NULL(b->wsd.string);
+ b->match = NULL;
+}
+
static int compute_ws_delta(const struct emitted_diff_symbol *a,
const struct emitted_diff_symbol *b,
struct ws_delta *out)
const struct emitted_diff_symbol *shorter = a->len > b->len ? b : a;
int d = longer->len - shorter->len;
+ if (strncmp(longer->line + d, shorter->line, shorter->len))
+ return 0;
+
out->string = xmemdupz(longer->line, d);
out->current_longer = (a == longer);
- return !strncmp(longer->line + d, shorter->line, shorter->len);
+ return 1;
}
static int cmp_in_block_with_wsd(const struct diff_options *o,
const struct moved_entry *cur,
const struct moved_entry *match,
- struct moved_entry *pmb,
+ struct moved_block *pmb,
int n)
{
struct emitted_diff_symbol *l = &o->emitted_symbols->buf[n];
if (strcmp(a, b))
return 1;
- if (!pmb->wsd)
+ if (!pmb->wsd.string)
/*
- * No white space delta was carried forward? This can happen
- * when we exit early in this function and do not carry
- * forward ws.
+ * The white space delta is not active? This can happen
+ * when we exit early in this function.
*/
return 1;
/*
- * The indent changes of the block are known and carried forward in
+ * 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.
*
* one of them for the white spaces, depending which was longer.
*/
- wslen = strlen(pmb->wsd->string);
- if (pmb->wsd->current_longer) {
+ wslen = strlen(pmb->wsd.string);
+ if (pmb->wsd.current_longer) {
c += wslen;
cl -= wslen;
} else {
al -= wslen;
}
- if (strcmp(a, c))
+ if (al != cl || memcmp(a, c, al))
return 1;
return 0;
ret->ent.hash = xdiff_hash_string(l->line, l->len, flags);
ret->es = l;
ret->next_line = NULL;
- ret->wsd = NULL;
return ret;
}
static void pmb_advance_or_null(struct diff_options *o,
struct moved_entry *match,
struct hashmap *hm,
- struct moved_entry **pmb,
+ struct moved_block *pmb,
int pmb_nr)
{
int i;
for (i = 0; i < pmb_nr; i++) {
- struct moved_entry *prev = pmb[i];
+ struct moved_entry *prev = pmb[i].match;
struct moved_entry *cur = (prev && prev->next_line) ?
prev->next_line : NULL;
if (cur && !hm->cmpfn(o, cur, match, NULL)) {
- pmb[i] = cur;
+ pmb[i].match = cur;
} else {
- pmb[i] = NULL;
+ pmb[i].match = NULL;
}
}
}
static void pmb_advance_or_null_multi_match(struct diff_options *o,
struct moved_entry *match,
struct hashmap *hm,
- struct moved_entry **pmb,
+ struct moved_block *pmb,
int pmb_nr, int n)
{
int i;
for (; match; match = hashmap_get_next(hm, match)) {
for (i = 0; i < pmb_nr; i++) {
- struct moved_entry *prev = pmb[i];
+ struct moved_entry *prev = pmb[i].match;
struct moved_entry *cur = (prev && prev->next_line) ?
prev->next_line : NULL;
if (!cur)
continue;
- if (!cmp_in_block_with_wsd(o, cur, match, pmb[i], n))
+ if (!cmp_in_block_with_wsd(o, cur, match, &pmb[i], n))
got_match[i] |= 1;
}
}
for (i = 0; i < pmb_nr; i++) {
if (got_match[i]) {
- /* Carry the white space delta forward */
- pmb[i]->next_line->wsd = pmb[i]->wsd;
- pmb[i] = pmb[i]->next_line;
- } else
- pmb[i] = NULL;
+ /* Advance to the next line */
+ pmb[i].match = pmb[i].match->next_line;
+ } else {
+ moved_block_clear(&pmb[i]);
+ }
}
+
+ free(got_match);
}
-static int shrink_potential_moved_blocks(struct moved_entry **pmb,
+static int shrink_potential_moved_blocks(struct moved_block *pmb,
int pmb_nr)
{
int lp, rp;
/* Shrink the set of potential block to the remaining running */
for (lp = 0, rp = pmb_nr - 1; lp <= rp;) {
- while (lp < pmb_nr && pmb[lp])
+ while (lp < pmb_nr && pmb[lp].match)
lp++;
/* lp points at the first NULL now */
- while (rp > -1 && !pmb[rp])
+ while (rp > -1 && !pmb[rp].match)
rp--;
/* rp points at the last non-NULL */
if (lp < pmb_nr && rp > -1 && lp < rp) {
pmb[lp] = pmb[rp];
- if (pmb[rp]->wsd) {
- free(pmb[rp]->wsd->string);
- FREE_AND_NULL(pmb[rp]->wsd);
- }
- pmb[rp] = NULL;
+ pmb[rp].match = NULL;
+ pmb[rp].wsd.string = NULL;
rp--;
lp++;
}
struct hashmap *add_lines,
struct hashmap *del_lines)
{
- struct moved_entry **pmb = NULL; /* potentially moved blocks */
+ struct moved_block *pmb = NULL; /* potentially moved blocks */
int pmb_nr = 0, pmb_alloc = 0;
int n, flipped_block = 1, block_length = 0;
}
if (!match) {
+ int i;
+
adjust_last_block(o, n, block_length);
+ for(i = 0; i < pmb_nr; i++)
+ moved_block_clear(&pmb[i]);
pmb_nr = 0;
block_length = 0;
continue;
ALLOC_GROW(pmb, pmb_nr + 1, pmb_alloc);
if (o->color_moved_ws_handling &
COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE) {
- struct ws_delta *wsd = xmalloc(sizeof(*match->wsd));
- if (compute_ws_delta(l, match->es, wsd)) {
- match->wsd = wsd;
- pmb[pmb_nr++] = match;
- } else
- free(wsd);
+ if (compute_ws_delta(l, match->es,
+ &pmb[pmb_nr].wsd))
+ pmb[pmb_nr++].match = match;
} else {
- pmb[pmb_nr++] = match;
+ pmb[pmb_nr].wsd.string = NULL;
+ pmb[pmb_nr++].match = match;
}
}
}
adjust_last_block(o, n, block_length);
+ for(n = 0; n < pmb_nr; n++)
+ moved_block_clear(&pmb[n]);
free(pmb);
}
}
static void emit_line_ws_markup(struct diff_options *o,
- const char *set, const char *reset,
- const char *line, int len,
- const char *set_sign, char sign,
+ const char *set_sign, const char *set,
+ const char *reset,
+ int sign_index, const char *line, int len,
unsigned ws_rule, int blank_at_eof)
{
const char *ws = NULL;
+ int sign = o->output_indicators[sign_index];
if (o->ws_error_highlight & ws_rule) {
ws = diff_get_color_opt(o, DIFF_WHITESPACE);
}
if (!ws && !set_sign)
- emit_line_0(o, set, 0, reset, sign, line, len);
+ emit_line_0(o, set, NULL, 0, reset, sign, line, len);
else if (!ws) {
- /* Emit just the prefix, then the rest. */
- emit_line_0(o, set_sign ? set_sign : set, !!set_sign, reset,
- sign, "", 0);
- emit_line_0(o, set, 0, reset, 0, line, len);
+ emit_line_0(o, set_sign, set, !!set_sign, reset, sign, line, len);
} else if (blank_at_eof)
/* Blank line at EOF - paint '+' as well */
- emit_line_0(o, ws, 0, reset, sign, line, len);
+ emit_line_0(o, ws, NULL, 0, reset, sign, line, len);
else {
/* Emit just the prefix, then the rest. */
- emit_line_0(o, set_sign ? set_sign : set, !!set_sign, reset,
+ emit_line_0(o, set_sign ? set_sign : set, NULL, !!set_sign, reset,
sign, "", 0);
ws_check_emit(line, len, ws_rule,
o->file, set, reset, ws);
context = diff_get_color_opt(o, DIFF_CONTEXT);
reset = diff_get_color_opt(o, DIFF_RESET);
putc('\n', o->file);
- emit_line_0(o, context, 0, reset, '\\',
+ emit_line_0(o, context, NULL, 0, reset, '\\',
nneof, strlen(nneof));
break;
case DIFF_SYMBOL_SUBMODULE_HEADER:
else if (c == '-')
set = diff_get_color_opt(o, DIFF_FILE_OLD);
}
- emit_line_ws_markup(o, set, reset, line, len, set_sign, ' ',
+ emit_line_ws_markup(o, set_sign, set, reset,
+ OUTPUT_INDICATOR_CONTEXT, line, len,
flags & (DIFF_SYMBOL_CONTENT_WS_MASK), 0);
break;
case DIFF_SYMBOL_PLUS:
set = diff_get_color_opt(o, DIFF_CONTEXT_BOLD);
flags &= ~DIFF_SYMBOL_CONTENT_WS_MASK;
}
- emit_line_ws_markup(o, set, reset, line, len, set_sign, '+',
+ emit_line_ws_markup(o, set_sign, set, reset,
+ OUTPUT_INDICATOR_NEW, line, len,
flags & DIFF_SYMBOL_CONTENT_WS_MASK,
flags & DIFF_SYMBOL_CONTENT_BLANK_LINE_EOF);
break;
else
set = diff_get_color_opt(o, DIFF_CONTEXT_DIM);
}
- emit_line_ws_markup(o, set, reset, line, len, set_sign, '-',
+ emit_line_ws_markup(o, set_sign, set, reset,
+ OUTPUT_INDICATOR_OLD, line, len,
flags & DIFF_SYMBOL_CONTENT_WS_MASK, 0);
break;
case DIFF_SYMBOL_WORDS_PORCELAIN:
quote_two_c_style(&a_name, a_prefix, name_a, 0);
quote_two_c_style(&b_name, b_prefix, name_b, 0);
- size_one = fill_textconv(textconv_one, one, &data_one);
- size_two = fill_textconv(textconv_two, two, &data_two);
+ size_one = fill_textconv(o->repo, textconv_one, one, &data_one);
+ size_two = fill_textconv(o->repo, textconv_two, two, &data_two);
memset(&ecbdata, 0, sizeof(ecbdata));
ecbdata.color_diff = want_color(o->use_color);
- ecbdata.ws_rule = whitespace_rule(name_b);
+ ecbdata.ws_rule = whitespace_rule(o->repo->index, name_b);
ecbdata.opt = o;
if (ecbdata.ws_rule & WS_BLANK_AT_EOF) {
mmfile_t mf1, mf2;
}
}
-static void diff_filespec_load_driver(struct diff_filespec *one)
+static void diff_filespec_load_driver(struct diff_filespec *one,
+ struct index_state *istate)
{
/* Use already-loaded driver */
if (one->driver)
return;
if (S_ISREG(one->mode))
- one->driver = userdiff_find_by_path(one->path);
+ one->driver = userdiff_find_by_path(istate, one->path);
/* Fallback to default settings */
if (!one->driver)
one->driver = userdiff_find_by_name("default");
}
-static const char *userdiff_word_regex(struct diff_filespec *one)
+static const char *userdiff_word_regex(struct diff_filespec *one,
+ struct index_state *istate)
{
- diff_filespec_load_driver(one);
+ diff_filespec_load_driver(one, istate);
return one->driver->word_regex;
}
xcalloc(1, sizeof(struct emitted_diff_symbols));
if (!o->word_regex)
- o->word_regex = userdiff_word_regex(one);
+ o->word_regex = userdiff_word_regex(one, o->repo->index);
if (!o->word_regex)
- o->word_regex = userdiff_word_regex(two);
+ o->word_regex = userdiff_word_regex(two, o->repo->index);
if (!o->word_regex)
o->word_regex = diff_word_regex_cfg;
if (o->word_regex) {
struct diff_filepair *p = q->queue[i];
const char *name;
unsigned long copied, added, damage;
- int content_changed;
name = p->two->path ? p->two->path : p->one->path;
- if (p->one->oid_valid && p->two->oid_valid)
- content_changed = oidcmp(&p->one->oid, &p->two->oid);
- else
- content_changed = 1;
-
- if (!content_changed) {
+ if (p->one->oid_valid && p->two->oid_valid &&
+ oideq(&p->one->oid, &p->two->oid)) {
/*
* The SHA1 has not changed, so pre-/post-content is
* identical. We can therefore skip looking at the
}
if (DIFF_FILE_VALID(p->one) && DIFF_FILE_VALID(p->two)) {
- diff_populate_filespec(p->one, 0);
- diff_populate_filespec(p->two, 0);
- diffcore_count_changes(p->one, p->two, NULL, NULL,
+ diff_populate_filespec(options->repo, p->one, 0);
+ diff_populate_filespec(options->repo, p->two, 0);
+ diffcore_count_changes(options->repo,
+ p->one, p->two, NULL, NULL,
&copied, &added);
diff_free_filespec_data(p->one);
diff_free_filespec_data(p->two);
} else if (DIFF_FILE_VALID(p->one)) {
- diff_populate_filespec(p->one, CHECK_SIZE_ONLY);
+ diff_populate_filespec(options->repo, p->one, CHECK_SIZE_ONLY);
copied = added = 0;
diff_free_filespec_data(p->one);
} else if (DIFF_FILE_VALID(p->two)) {
- diff_populate_filespec(p->two, CHECK_SIZE_ONLY);
+ diff_populate_filespec(options->repo, p->two, CHECK_SIZE_ONLY);
copied = 0;
added = p->two->size;
diff_free_filespec_data(p->two);
* made to the preimage.
* If the resulting damage is zero, we know that
* diffcore_count_changes() considers the two entries to
- * be identical, but since content_changed is true, we
+ * be identical, but since the oid changed, we
* know that there must have been _some_ kind of change,
* so we force all entries to have damage > 0.
*/
emit_binary_diff_body(o, two, one);
}
-int diff_filespec_is_binary(struct diff_filespec *one)
+int diff_filespec_is_binary(struct repository *r,
+ struct diff_filespec *one)
{
if (one->is_binary == -1) {
- diff_filespec_load_driver(one);
+ diff_filespec_load_driver(one, r->index);
if (one->driver->binary != -1)
one->is_binary = one->driver->binary;
else {
if (!one->data && DIFF_FILE_VALID(one))
- diff_populate_filespec(one, CHECK_BINARY);
+ diff_populate_filespec(r, one, CHECK_BINARY);
if (one->is_binary == -1 && one->data)
one->is_binary = buffer_is_binary(one->data,
one->size);
return one->is_binary;
}
-static const struct userdiff_funcname *diff_funcname_pattern(struct diff_filespec *one)
+static const struct userdiff_funcname *
+diff_funcname_pattern(struct diff_options *o, struct diff_filespec *one)
{
- diff_filespec_load_driver(one);
+ diff_filespec_load_driver(one, o->repo->index);
return one->driver->funcname.pattern ? &one->driver->funcname : NULL;
}
options->b_prefix = b;
}
-struct userdiff_driver *get_textconv(struct diff_filespec *one)
+struct userdiff_driver *get_textconv(struct index_state *istate,
+ struct diff_filespec *one)
{
if (!DIFF_FILE_VALID(one))
return NULL;
- diff_filespec_load_driver(one);
+ diff_filespec_load_driver(one, istate);
return userdiff_get_textconv(one->driver);
}
}
if (o->flags.allow_textconv) {
- textconv_one = get_textconv(one);
- textconv_two = get_textconv(two);
+ textconv_one = get_textconv(o->repo->index, one);
+ textconv_two = get_textconv(o->repo->index, two);
}
/* Never use a non-valid filename anywhere if at all possible */
if ((one->mode ^ two->mode) & S_IFMT)
goto free_ab_and_return;
if (complete_rewrite &&
- (textconv_one || !diff_filespec_is_binary(one)) &&
- (textconv_two || !diff_filespec_is_binary(two))) {
+ (textconv_one || !diff_filespec_is_binary(o->repo, one)) &&
+ (textconv_two || !diff_filespec_is_binary(o->repo, two))) {
emit_diff_symbol(o, DIFF_SYMBOL_HEADER,
header.buf, header.len, 0);
strbuf_reset(&header);
emit_rewrite_diff(name_a, name_b, one, two,
- textconv_one, textconv_two, o);
+ textconv_one, textconv_two, o);
o->found_changes = 1;
goto free_ab_and_return;
}
strbuf_reset(&header);
goto free_ab_and_return;
} else if (!o->flags.text &&
- ( (!textconv_one && diff_filespec_is_binary(one)) ||
- (!textconv_two && diff_filespec_is_binary(two)) )) {
+ ( (!textconv_one && diff_filespec_is_binary(o->repo, one)) ||
+ (!textconv_two && diff_filespec_is_binary(o->repo, two)) )) {
struct strbuf sb = STRBUF_INIT;
if (!one->data && !two->data &&
S_ISREG(one->mode) && S_ISREG(two->mode) &&
!o->flags.binary) {
- if (!oidcmp(&one->oid, &two->oid)) {
+ if (oideq(&one->oid, &two->oid)) {
if (must_show_header)
emit_diff_symbol(o, DIFF_SYMBOL_HEADER,
header.buf, header.len,
strbuf_release(&sb);
goto free_ab_and_return;
}
- if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
+ if (fill_mmfile(o->repo, &mf1, one) < 0 ||
+ fill_mmfile(o->repo, &mf2, two) < 0)
die("unable to read files to diff");
/* Quite common confusing case */
if (mf1.size == mf2.size &&
strbuf_reset(&header);
}
- mf1.size = fill_textconv(textconv_one, one, &mf1.ptr);
- mf2.size = fill_textconv(textconv_two, two, &mf2.ptr);
+ mf1.size = fill_textconv(o->repo, textconv_one, one, &mf1.ptr);
+ mf2.size = fill_textconv(o->repo, textconv_two, two, &mf2.ptr);
- pe = diff_funcname_pattern(one);
+ pe = diff_funcname_pattern(o, one);
if (!pe)
- pe = diff_funcname_pattern(two);
+ pe = diff_funcname_pattern(o, two);
memset(&xpp, 0, sizeof(xpp));
memset(&xecfg, 0, sizeof(xecfg));
lbl[0] = NULL;
ecbdata.label_path = lbl;
ecbdata.color_diff = want_color(o->use_color);
- ecbdata.ws_rule = whitespace_rule(name_b);
+ ecbdata.ws_rule = whitespace_rule(o->repo->index, name_b);
if (ecbdata.ws_rule & WS_BLANK_AT_EOF)
check_blank_at_eof(&mf1, &mf2, &ecbdata);
ecbdata.opt = o;
return;
}
- same_contents = !oidcmp(&one->oid, &two->oid);
+ same_contents = oideq(&one->oid, &two->oid);
- if (diff_filespec_is_binary(one) || diff_filespec_is_binary(two)) {
+ if (diff_filespec_is_binary(o->repo, one) ||
+ diff_filespec_is_binary(o->repo, two)) {
data->is_binary = 1;
if (same_contents) {
data->added = 0;
data->deleted = 0;
} else {
- data->added = diff_filespec_size(two);
- data->deleted = diff_filespec_size(one);
+ data->added = diff_filespec_size(o->repo, two);
+ data->deleted = diff_filespec_size(o->repo, one);
}
}
else if (complete_rewrite) {
- diff_populate_filespec(one, 0);
- diff_populate_filespec(two, 0);
+ diff_populate_filespec(o->repo, one, 0);
+ diff_populate_filespec(o->repo, two, 0);
data->deleted = count_lines(one->data, one->size);
data->added = count_lines(two->data, two->size);
}
xpparam_t xpp;
xdemitconf_t xecfg;
- if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
+ if (fill_mmfile(o->repo, &mf1, one) < 0 ||
+ fill_mmfile(o->repo, &mf2, two) < 0)
die("unable to read files to diff");
memset(&xpp, 0, sizeof(xpp));
data.filename = name_b ? name_b : name_a;
data.lineno = 0;
data.o = o;
- data.ws_rule = whitespace_rule(attr_path);
- data.conflict_marker_size = ll_merge_marker_size(attr_path);
+ data.ws_rule = whitespace_rule(o->repo->index, attr_path);
+ data.conflict_marker_size = ll_merge_marker_size(o->repo->index, attr_path);
- if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
+ if (fill_mmfile(o->repo, &mf1, one) < 0 ||
+ fill_mmfile(o->repo, &mf2, two) < 0)
die("unable to read files to diff");
/*
* introduced changes, and as long as the "new" side is text, we
* can and should check what it introduces.
*/
- if (diff_filespec_is_binary(two))
+ if (diff_filespec_is_binary(o->repo, two))
goto free_and_return;
else {
/* Crazy xdl interfaces.. */
* the work tree has that object contents, return true, so that
* prepare_temp_file() does not have to inflate and extract.
*/
-static int reuse_worktree_file(const char *name, const struct object_id *oid, int want_file)
+static int reuse_worktree_file(struct index_state *istate,
+ const char *name,
+ const struct object_id *oid,
+ int want_file)
{
const struct cache_entry *ce;
struct stat st;
* by diff-cache --cached, which does read the cache before
* calling us.
*/
- if (!active_cache)
+ if (!istate->cache)
return 0;
/* We want to avoid the working directory if our caller
* Similarly, if we'd have to convert the file contents anyway, that
* makes the optimization not worthwhile.
*/
- if (!want_file && would_convert_to_git(&the_index, name))
+ if (!want_file && would_convert_to_git(istate, name))
return 0;
len = strlen(name);
- pos = cache_name_pos(name, len);
+ pos = index_name_pos(istate, name, len);
if (pos < 0)
return 0;
- ce = active_cache[pos];
+ ce = istate->cache[pos];
/*
* This is not the sha1 we are looking for, or
* unreusable because it is not a regular file.
*/
- if (oidcmp(oid, &ce->oid) || !S_ISREG(ce->ce_mode))
+ if (!oideq(oid, &ce->oid) || !S_ISREG(ce->ce_mode))
return 0;
/*
* If ce matches the file in the work tree, we can reuse it.
*/
if (ce_uptodate(ce) ||
- (!lstat(name, &st) && !ce_match_stat(ce, &st, 0)))
+ (!lstat(name, &st) && !ie_match_stat(istate, ce, &st, 0)))
return 1;
return 0;
* grab the data for the blob (or file) for our own in-core comparison.
* diff_filespec has data and size fields for this purpose.
*/
-int diff_populate_filespec(struct diff_filespec *s, unsigned int flags)
+int diff_populate_filespec(struct repository *r,
+ struct diff_filespec *s,
+ unsigned int flags)
{
int size_only = flags & CHECK_SIZE_ONLY;
int err = 0;
return diff_populate_gitlink(s, size_only);
if (!s->oid_valid ||
- reuse_worktree_file(s->path, &s->oid, 0)) {
+ reuse_worktree_file(r->index, s->path, &s->oid, 0)) {
struct strbuf buf = STRBUF_INIT;
struct stat st;
int fd;
* point if the path requires us to run the content
* conversion.
*/
- if (size_only && !would_convert_to_git(&the_index, s->path))
+ if (size_only && !would_convert_to_git(r->index, s->path))
return 0;
/*
/*
* Convert from working tree format to canonical git format
*/
- if (convert_to_git(&the_index, s->path, s->data, s->size, &buf, conv_flags)) {
+ if (convert_to_git(r->index, s->path, s->data, s->size, &buf, conv_flags)) {
size_t size = 0;
munmap(s->data, s->size);
s->should_munmap = 0;
else {
enum object_type type;
if (size_only || (flags & CHECK_BINARY)) {
- type = oid_object_info(the_repository, &s->oid,
- &s->size);
+ type = oid_object_info(r, &s->oid, &s->size);
if (type < 0)
die("unable to read %s",
oid_to_hex(&s->oid));
FREE_AND_NULL(s->cnt_data);
}
-static void prep_temp_blob(const char *path, struct diff_tempfile *temp,
+static void prep_temp_blob(struct index_state *istate,
+ const char *path, struct diff_tempfile *temp,
void *blob,
unsigned long size,
const struct object_id *oid,
temp->tempfile = mks_tempfile_ts(tempfile.buf, strlen(base) + 1);
if (!temp->tempfile)
die_errno("unable to create temp-file");
- if (convert_to_working_tree(&the_index, path,
+ if (convert_to_working_tree(istate, path,
(const char *)blob, (size_t)size, &buf)) {
blob = buf.buf;
size = buf.len;
free(path_dup);
}
-static struct diff_tempfile *prepare_temp_file(const char *name,
- struct diff_filespec *one)
+static struct diff_tempfile *prepare_temp_file(struct repository *r,
+ const char *name,
+ struct diff_filespec *one)
{
struct diff_tempfile *temp = claim_diff_tempfile();
if (!S_ISGITLINK(one->mode) &&
(!one->oid_valid ||
- reuse_worktree_file(name, &one->oid, 1))) {
+ reuse_worktree_file(r->index, name, &one->oid, 1))) {
struct stat st;
if (lstat(name, &st) < 0) {
if (errno == ENOENT)
struct strbuf sb = STRBUF_INIT;
if (strbuf_readlink(&sb, name, st.st_size) < 0)
die_errno("readlink(%s)", name);
- prep_temp_blob(name, temp, sb.buf, sb.len,
+ prep_temp_blob(r->index, name, temp, sb.buf, sb.len,
(one->oid_valid ?
&one->oid : &null_oid),
(one->oid_valid ?
return temp;
}
else {
- if (diff_populate_filespec(one, 0))
+ if (diff_populate_filespec(r, one, 0))
die("cannot read data blob for %s", one->path);
- prep_temp_blob(name, temp, one->data, one->size,
+ prep_temp_blob(r->index, name, temp,
+ one->data, one->size,
&one->oid, one->mode);
}
return temp;
}
-static void add_external_diff_name(struct argv_array *argv,
+static void add_external_diff_name(struct repository *r,
+ struct argv_array *argv,
const char *name,
struct diff_filespec *df)
{
- struct diff_tempfile *temp = prepare_temp_file(name, df);
+ struct diff_tempfile *temp = prepare_temp_file(r, name, df);
argv_array_push(argv, temp->name);
argv_array_push(argv, temp->hex);
argv_array_push(argv, temp->mode);
argv_array_push(&argv, name);
if (one && two) {
- add_external_diff_name(&argv, name, one);
+ add_external_diff_name(o->repo, &argv, name, one);
if (!other)
- add_external_diff_name(&argv, name, two);
+ add_external_diff_name(o->repo, &argv, name, two);
else {
- add_external_diff_name(&argv, other, two);
+ add_external_diff_name(o->repo, &argv, other, two);
argv_array_push(&argv, other);
argv_array_push(&argv, xfrm_msg);
}
default:
*must_show_header = 0;
}
- if (one && two && oidcmp(&one->oid, &two->oid)) {
+ if (one && two && !oideq(&one->oid, &two->oid)) {
const unsigned hexsz = the_hash_algo->hexsz;
int abbrev = o->flags.full_index ? hexsz : DEFAULT_ABBREV;
if (o->flags.binary) {
mmfile_t mf;
- if ((!fill_mmfile(&mf, one) && diff_filespec_is_binary(one)) ||
- (!fill_mmfile(&mf, two) && diff_filespec_is_binary(two)))
+ if ((!fill_mmfile(o->repo, &mf, one) &&
+ diff_filespec_is_binary(o->repo, one)) ||
+ (!fill_mmfile(o->repo, &mf, two) &&
+ diff_filespec_is_binary(o->repo, two)))
abbrev = hexsz;
}
strbuf_addf(msg, "%s%sindex %s..%s", line_prefix, set,
if (o->flags.allow_external) {
- struct userdiff_driver *drv = userdiff_find_by_path(attr_path);
+ struct userdiff_driver *drv;
+
+ drv = userdiff_find_by_path(o->repo->index, attr_path);
if (drv && drv->external)
pgm = drv->external;
}
fprintf(o->file, "* Unmerged path %s\n", name);
}
-static void diff_fill_oid_info(struct diff_filespec *one)
+static void diff_fill_oid_info(struct diff_filespec *one, struct index_state *istate)
{
if (DIFF_FILE_VALID(one)) {
if (!one->oid_valid) {
}
if (lstat(one->path, &st) < 0)
die_errno("stat '%s'", one->path);
- if (index_path(&one->oid, one->path, &st, 0))
+ if (index_path(istate, &one->oid, one->path, &st, 0))
die("cannot hash %s", one->path);
}
}
static void strip_prefix(int prefix_length, const char **namep, const char **otherp)
{
/* Strip the prefix but do not molest /dev/null and absolute paths */
- if (*namep && **namep != '/') {
+ if (*namep && !is_absolute_path(*namep)) {
*namep += prefix_length;
if (**namep == '/')
++*namep;
}
- if (*otherp && **otherp != '/') {
+ if (*otherp && !is_absolute_path(*otherp)) {
*otherp += prefix_length;
if (**otherp == '/')
++*otherp;
return;
}
- diff_fill_oid_info(one);
- diff_fill_oid_info(two);
+ diff_fill_oid_info(one, o->repo->index);
+ diff_fill_oid_info(two, o->repo->index);
if (!pgm &&
DIFF_FILE_VALID(one) && DIFF_FILE_VALID(two) &&
*/
struct diff_filespec *null = alloc_filespec(two->path);
run_diff_cmd(NULL, name, other, attr_path,
- one, null, &msg, o, p);
+ one, null, &msg,
+ o, p);
free(null);
strbuf_release(&msg);
if (DIFF_PAIR_UNMERGED(p)) {
/* unmerged */
- builtin_diffstat(p->one->path, NULL, NULL, NULL, diffstat, o, p);
+ builtin_diffstat(p->one->path, NULL, NULL, NULL,
+ diffstat, o, p);
return;
}
if (o->prefix_length)
strip_prefix(o->prefix_length, &name, &other);
- diff_fill_oid_info(p->one);
- diff_fill_oid_info(p->two);
+ diff_fill_oid_info(p->one, o->repo->index);
+ diff_fill_oid_info(p->two, o->repo->index);
- builtin_diffstat(name, other, p->one, p->two, diffstat, o, p);
+ builtin_diffstat(name, other, p->one, p->two,
+ diffstat, o, p);
}
static void run_checkdiff(struct diff_filepair *p, struct diff_options *o)
if (o->prefix_length)
strip_prefix(o->prefix_length, &name, &other);
- diff_fill_oid_info(p->one);
- diff_fill_oid_info(p->two);
+ diff_fill_oid_info(p->one, o->repo->index);
+ diff_fill_oid_info(p->two, o->repo->index);
builtin_checkdiff(name, other, attr_path, p->one, p->two, o);
}
-void diff_setup(struct diff_options *options)
+void repo_diff_setup(struct repository *r, struct diff_options *options)
{
memcpy(options, &default_diff_options, sizeof(*options));
options->file = stdout;
+ options->repo = r;
+ options->output_indicators[OUTPUT_INDICATOR_NEW] = '+';
+ options->output_indicators[OUTPUT_INDICATOR_OLD] = '-';
+ options->output_indicators[OUTPUT_INDICATOR_CONTEXT] = ' ';
options->abbrev = DEFAULT_ABBREV;
options->line_termination = '\n';
options->break_opt = -1;
options->output_format |= DIFF_FORMAT_DIFFSTAT;
} else if (!strcmp(arg, "--no-compact-summary"))
options->flags.stat_with_summary = 0;
+ else if (skip_prefix(arg, "--output-indicator-new=", &arg))
+ options->output_indicators[OUTPUT_INDICATOR_NEW] = arg[0];
+ else if (skip_prefix(arg, "--output-indicator-old=", &arg))
+ options->output_indicators[OUTPUT_INDICATOR_OLD] = arg[0];
+ else if (skip_prefix(arg, "--output-indicator-context=", &arg))
+ options->output_indicators[OUTPUT_INDICATOR_CONTEXT] = arg[0];
/* renames options */
else if (starts_with(arg, "-B") ||
* dealing with a change.
*/
if (one->oid_valid && two->oid_valid &&
- !oidcmp(&one->oid, &two->oid) &&
+ oideq(&one->oid, &two->oid) &&
!one->dirty_submodule && !two->dirty_submodule)
return 1; /* no change */
if (!one->oid_valid && !two->oid_valid)
else
p->status = DIFF_STATUS_RENAMED;
}
- else if (oidcmp(&p->one->oid, &p->two->oid) ||
+ else if (!oideq(&p->one->oid, &p->two->oid) ||
p->one->mode != p->two->mode ||
p->one->dirty_submodule ||
p->two->dirty_submodule ||
if (DIFF_PAIR_UNMERGED(p))
continue;
- diff_fill_oid_info(p->one);
- diff_fill_oid_info(p->two);
+ diff_fill_oid_info(p->one, options->repo->index);
+ diff_fill_oid_info(p->two, options->repo->index);
len1 = remove_space(p->one->path, strlen(p->one->path));
len2 = remove_space(p->two->path, strlen(p->two->path));
if (diff_header_only)
continue;
- if (fill_mmfile(&mf1, p->one) < 0 ||
- fill_mmfile(&mf2, p->two) < 0)
+ if (fill_mmfile(options->repo, &mf1, p->one) < 0 ||
+ fill_mmfile(options->repo, &mf2, p->two) < 0)
return error("unable to read files to diff");
- if (diff_filespec_is_binary(p->one) ||
- diff_filespec_is_binary(p->two)) {
+ if (diff_filespec_is_binary(options->repo, p->one) ||
+ diff_filespec_is_binary(options->repo, p->two)) {
git_SHA1_Update(&ctx, oid_to_hex(&p->one->oid),
GIT_SHA1_HEXSZ);
git_SHA1_Update(&ctx, oid_to_hex(&p->two->oid),
if (o->color_moved == COLOR_MOVED_ZEBRA_DIM)
dim_moved_lines(o);
- hashmap_free(&add_lines, 0);
- hashmap_free(&del_lines, 0);
+ hashmap_free(&add_lines, 1);
+ hashmap_free(&del_lines, 1);
}
for (i = 0; i < esm.nr; i++)
}
/* Check whether two filespecs with the same mode and size are identical */
-static int diff_filespec_is_identical(struct diff_filespec *one,
+static int diff_filespec_is_identical(struct repository *r,
+ struct diff_filespec *one,
struct diff_filespec *two)
{
if (S_ISGITLINK(one->mode))
return 0;
- if (diff_populate_filespec(one, 0))
+ if (diff_populate_filespec(r, one, 0))
return 0;
- if (diff_populate_filespec(two, 0))
+ if (diff_populate_filespec(r, two, 0))
return 0;
return !memcmp(one->data, two->data, one->size);
}
-static int diff_filespec_check_stat_unmatch(struct diff_filepair *p)
+static int diff_filespec_check_stat_unmatch(struct repository *r,
+ struct diff_filepair *p)
{
if (p->done_skip_stat_unmatch)
return p->skip_stat_unmatch_result;
!DIFF_FILE_VALID(p->two) ||
(p->one->oid_valid && p->two->oid_valid) ||
(p->one->mode != p->two->mode) ||
- diff_populate_filespec(p->one, CHECK_SIZE_ONLY) ||
- diff_populate_filespec(p->two, CHECK_SIZE_ONLY) ||
+ diff_populate_filespec(r, p->one, CHECK_SIZE_ONLY) ||
+ diff_populate_filespec(r, p->two, CHECK_SIZE_ONLY) ||
(p->one->size != p->two->size) ||
- !diff_filespec_is_identical(p->one, p->two)) /* (2) */
+ !diff_filespec_is_identical(r, p->one, p->two)) /* (2) */
p->skip_stat_unmatch_result = 1;
return p->skip_stat_unmatch_result;
}
for (i = 0; i < q->nr; i++) {
struct diff_filepair *p = q->queue[i];
- if (diff_filespec_check_stat_unmatch(p))
+ if (diff_filespec_check_stat_unmatch(diffopt->repo, p))
diff_q(&outq, p);
else {
/*
if (!options->found_follow) {
/* See try_to_follow_renames() in tree-diff.c */
if (options->break_opt != -1)
- diffcore_break(options->break_opt);
+ diffcore_break(options->repo,
+ options->break_opt);
if (options->detect_rename)
diffcore_rename(options);
if (options->break_opt != -1)
return;
if (options->flags.quick && options->skip_stat_unmatch &&
- !diff_filespec_check_stat_unmatch(p))
+ !diff_filespec_check_stat_unmatch(options->repo, p))
return;
options->flags.has_changes = 1;
return pair;
}
-static char *run_textconv(const char *pgm, struct diff_filespec *spec,
- size_t *outsize)
+static char *run_textconv(struct repository *r,
+ const char *pgm,
+ struct diff_filespec *spec,
+ size_t *outsize)
{
struct diff_tempfile *temp;
const char *argv[3];
struct strbuf buf = STRBUF_INIT;
int err = 0;
- temp = prepare_temp_file(spec->path, spec);
+ temp = prepare_temp_file(r, spec->path, spec);
*arg++ = pgm;
*arg++ = temp->name;
*arg = NULL;
return strbuf_detach(&buf, outsize);
}
-size_t fill_textconv(struct userdiff_driver *driver,
+size_t fill_textconv(struct repository *r,
+ struct userdiff_driver *driver,
struct diff_filespec *df,
char **outbuf)
{
*outbuf = "";
return 0;
}
- if (diff_populate_filespec(df, 0))
+ if (diff_populate_filespec(r, df, 0))
die("unable to read files to diff");
*outbuf = df->data;
return df->size;
return size;
}
- *outbuf = run_textconv(driver->textconv, df, &size);
+ *outbuf = run_textconv(r, driver->textconv, df, &size);
if (!*outbuf)
die("unable to read files to diff");
return size;
}
-int textconv_object(const char *path,
+int textconv_object(struct repository *r,
+ const char *path,
unsigned mode,
const struct object_id *oid,
int oid_valid,
df = alloc_filespec(path);
fill_filespec(df, oid, oid_valid, mode);
- textconv = get_textconv(df);
+ textconv = get_textconv(r->index, df);
if (!textconv) {
free_filespec(df);
return 0;
}
- *buf_size = fill_textconv(textconv, df, buf);
+ *buf_size = fill_textconv(r, textconv, df, buf);
free_filespec(df);
return 1;
}
struct oid_array;
struct commit;
struct combine_diff_path;
+struct repository;
typedef int (*pathchange_fn_t)(struct diff_options *options,
struct combine_diff_path *path);
FILE *file;
int close_file;
+#define OUTPUT_INDICATOR_NEW 0
+#define OUTPUT_INDICATOR_OLD 1
+#define OUTPUT_INDICATOR_CONTEXT 2
+ char output_indicators[3];
+
struct pathspec pathspec;
pathchange_fn_t pathchange;
change_fn_t change;
/* 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;
+
+ struct repository *repo;
};
void diff_emit_submodule_del(struct diff_options *o, const char *line);
int git_diff_heuristic_config(const char *var, const char *value, void *cb);
void init_diff_ui_defaults(void);
int git_diff_ui_config(const char *var, const char *value, void *cb);
-void diff_setup(struct diff_options *);
+#ifndef NO_THE_REPOSITORY_COMPATIBILITY_MACROS
+#define diff_setup(diffopts) repo_diff_setup(the_repository, diffopts)
+#endif
+void repo_diff_setup(struct repository *, struct diff_options *);
int diff_opt_parse(struct diff_options *, const char **, int, const char *);
void diff_setup_done(struct diff_options *);
int git_config_rename(const char *var, const char *value);
int diff_result_code(struct diff_options *, int);
-void diff_no_index(struct rev_info *, int, const char **);
+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 ita_invisible_in_index);
* struct. If it is non-NULL, then "outbuf" points to a newly allocated buffer
* that should be freed by the caller.
*/
-size_t fill_textconv(struct userdiff_driver *driver,
+size_t fill_textconv(struct repository *r,
+ struct userdiff_driver *driver,
struct diff_filespec *df,
char **outbuf);
* and only if it has textconv enabled (otherwise return NULL). The result
* can be passed to fill_textconv().
*/
-struct userdiff_driver *get_textconv(struct diff_filespec *one);
+struct userdiff_driver *get_textconv(struct index_state *istate,
+ struct diff_filespec *one);
/*
* Prepare diff_filespec and convert it using diff textconv API
* if the textconv driver exists.
* Return 1 if the conversion succeeds, 0 otherwise.
*/
-int textconv_object(const char *path, unsigned mode, const struct object_id *oid, int oid_valid, char **buf, unsigned long *buf_size);
+int textconv_object(struct repository *repo,
+ const char *path,
+ unsigned mode,
+ const struct object_id *oid, int oid_valid,
+ char **buf, unsigned long *buf_size);
int parse_rename_score(const char **cp_p);
#include "diff.h"
#include "diffcore.h"
-static int should_break(struct diff_filespec *src,
+static int should_break(struct repository *r,
+ struct diff_filespec *src,
struct diff_filespec *dst,
int break_score,
int *merge_score_p)
}
if (src->oid_valid && dst->oid_valid &&
- !oidcmp(&src->oid, &dst->oid))
+ oideq(&src->oid, &dst->oid))
return 0; /* they are the same */
- if (diff_populate_filespec(src, 0) || diff_populate_filespec(dst, 0))
+ if (diff_populate_filespec(r, src, 0) ||
+ diff_populate_filespec(r, dst, 0))
return 0; /* error but caught downstream */
max_size = ((src->size > dst->size) ? src->size : dst->size);
if (!src->size)
return 0; /* we do not let empty files get renamed */
- if (diffcore_count_changes(src, dst,
+ if (diffcore_count_changes(r, src, dst,
&src->cnt_data, &dst->cnt_data,
&src_copied, &literal_added))
return 0;
return 1;
}
-void diffcore_break(int break_score)
+void diffcore_break(struct repository *r, int break_score)
{
struct diff_queue_struct *q = &diff_queued_diff;
struct diff_queue_struct outq;
object_type(p->one->mode) == OBJ_BLOB &&
object_type(p->two->mode) == OBJ_BLOB &&
!strcmp(p->one->path, p->two->path)) {
- if (should_break(p->one, p->two,
+ if (should_break(r, p->one, p->two,
break_score, &score)) {
/* Split this into delete and create */
struct diff_filespec *null_one, *null_two;
a->hashval > b->hashval ? 1 : 0;
}
-static struct spanhash_top *hash_chars(struct diff_filespec *one)
+static struct spanhash_top *hash_chars(struct repository *r,
+ struct diff_filespec *one)
{
int i, n;
unsigned int accum1, accum2, hashval;
struct spanhash_top *hash;
unsigned char *buf = one->data;
unsigned int sz = one->size;
- int is_text = !diff_filespec_is_binary(one);
+ int is_text = !diff_filespec_is_binary(r, one);
i = INITIAL_HASH_SIZE;
hash = xmalloc(st_add(sizeof(*hash),
return hash;
}
-int diffcore_count_changes(struct diff_filespec *src,
+int diffcore_count_changes(struct repository *r,
+ struct diff_filespec *src,
struct diff_filespec *dst,
void **src_count_p,
void **dst_count_p,
if (src_count_p)
src_count = *src_count_p;
if (!src_count) {
- src_count = hash_chars(src);
+ src_count = hash_chars(r, src);
if (src_count_p)
*src_count_p = src_count;
}
if (dst_count_p)
dst_count = *dst_count_p;
if (!dst_count) {
- dst_count = hash_chars(dst);
+ dst_count = hash_chars(r, dst);
if (dst_count_p)
*dst_count_p = dst_count;
}
return 0;
if (o->flags.allow_textconv) {
- textconv_one = get_textconv(p->one);
- textconv_two = get_textconv(p->two);
+ textconv_one = get_textconv(o->repo->index, p->one);
+ textconv_two = get_textconv(o->repo->index, p->two);
}
/*
if (textconv_one == textconv_two && diff_unmodified_pair(p))
return 0;
- mf1.size = fill_textconv(textconv_one, p->one, &mf1.ptr);
- mf2.size = fill_textconv(textconv_two, p->two, &mf2.ptr);
+ mf1.size = fill_textconv(o->repo, textconv_one, p->one, &mf1.ptr);
+ mf2.size = fill_textconv(o->repo, textconv_two, p->two, &mf2.ptr);
ret = fn(DIFF_FILE_VALID(p->one) ? &mf1 : NULL,
DIFF_FILE_VALID(p->two) ? &mf2 : NULL,
short name_score;
};
-static int estimate_similarity(struct diff_filespec *src,
+static int estimate_similarity(struct repository *r,
+ struct diff_filespec *src,
struct diff_filespec *dst,
int minimum_score)
{
* say whether the size is valid or not!)
*/
if (!src->cnt_data &&
- diff_populate_filespec(src, CHECK_SIZE_ONLY))
+ diff_populate_filespec(r, src, CHECK_SIZE_ONLY))
return 0;
if (!dst->cnt_data &&
- diff_populate_filespec(dst, CHECK_SIZE_ONLY))
+ diff_populate_filespec(r, dst, CHECK_SIZE_ONLY))
return 0;
max_size = ((src->size > dst->size) ? src->size : dst->size);
if (max_size * (MAX_SCORE-minimum_score) < delta_size * MAX_SCORE)
return 0;
- if (!src->cnt_data && diff_populate_filespec(src, 0))
+ if (!src->cnt_data && diff_populate_filespec(r, src, 0))
return 0;
- if (!dst->cnt_data && diff_populate_filespec(dst, 0))
+ if (!dst->cnt_data && diff_populate_filespec(r, dst, 0))
return 0;
- if (diffcore_count_changes(src, dst,
+ if (diffcore_count_changes(r, src, dst,
&src->cnt_data, &dst->cnt_data,
&src_copied, &literal_added))
return 0;
struct diff_filespec *filespec;
};
-static unsigned int hash_filespec(struct diff_filespec *filespec)
+static unsigned int hash_filespec(struct repository *r,
+ struct diff_filespec *filespec)
{
if (!filespec->oid_valid) {
- if (diff_populate_filespec(filespec, 0))
+ if (diff_populate_filespec(r, filespec, 0))
return 0;
hash_object_file(filespec->data, filespec->size, "blob",
&filespec->oid);
/*
* Find the best source match for specified destination.
*/
- p = hashmap_get_from_hash(srcs, hash_filespec(target), NULL);
+ p = hashmap_get_from_hash(srcs,
+ hash_filespec(options->repo, target),
+ NULL);
for (; p; p = hashmap_get_next(srcs, p)) {
int score;
struct diff_filespec *source = p->filespec;
/* False hash collision? */
- if (oidcmp(&source->oid, &target->oid))
+ if (!oideq(&source->oid, &target->oid))
continue;
/* Non-regular files? If so, the modes must match! */
if (!S_ISREG(source->mode) || !S_ISREG(target->mode)) {
return renames;
}
-static void insert_file_table(struct hashmap *table, int index, struct diff_filespec *filespec)
+static void insert_file_table(struct repository *r,
+ struct hashmap *table, int index,
+ struct diff_filespec *filespec)
{
struct file_similarity *entry = xmalloc(sizeof(*entry));
entry->index = index;
entry->filespec = filespec;
- hashmap_entry_init(entry, hash_filespec(filespec));
+ hashmap_entry_init(entry, hash_filespec(r, filespec));
hashmap_add(table, entry);
}
*/
hashmap_init(&file_table, NULL, NULL, rename_src_nr);
for (i = rename_src_nr-1; i >= 0; i--)
- insert_file_table(&file_table, i, rename_src[i].p->one);
+ insert_file_table(options->repo,
+ &file_table, i,
+ rename_src[i].p->one);
/* Walk the destinations and find best source match */
for (i = 0; i < rename_dst_nr; i++)
diff_unmodified_pair(rename_src[j].p))
continue;
- this_src.score = estimate_similarity(one, two,
+ this_src.score = estimate_similarity(options->repo,
+ one, two,
minimum_score);
this_src.name_score = basename_same(one, two);
this_src.dst = i;
#include "cache.h"
struct diff_options;
+struct repository;
+struct userdiff_driver;
/* This header file is internal between diff.c and its diff transformers
* (e.g. diffcore-rename, diffcore-pickaxe). Never include this header
#define MINIMUM_BREAK_SIZE 400 /* do not break a file smaller than this */
-struct userdiff_driver;
-
struct diff_filespec {
struct object_id oid;
char *path;
#define CHECK_SIZE_ONLY 1
#define CHECK_BINARY 2
-int diff_populate_filespec(struct diff_filespec *, unsigned int);
+int diff_populate_filespec(struct repository *, struct diff_filespec *, unsigned int);
void diff_free_filespec_data(struct diff_filespec *);
void diff_free_filespec_blob(struct diff_filespec *);
-int diff_filespec_is_binary(struct diff_filespec *);
+int diff_filespec_is_binary(struct repository *, struct diff_filespec *);
struct diff_filepair {
struct diff_filespec *one;
struct diff_filespec *);
void diff_q(struct diff_queue_struct *, struct diff_filepair *);
-void diffcore_break(int);
+void diffcore_break(struct repository *, int);
void diffcore_rename(struct diff_options *);
void diffcore_merge_broken(void);
void diffcore_pickaxe(struct diff_options *);
#define diff_debug_queue(a,b) do { /* nothing */ } while (0)
#endif
-int diffcore_count_changes(struct diff_filespec *src,
+int diffcore_count_changes(struct repository *r,
+ struct diff_filespec *src,
struct diff_filespec *dst,
void **src_count_p,
void **dst_count_p,
* order, though, if you do that.
*/
if (untracked &&
- oidcmp(&oid_stat.oid, &untracked->exclude_oid)) {
+ !oideq(&oid_stat.oid, &untracked->exclude_oid)) {
invalidate_gitignore(dir->untracked, untracked);
oidcpy(&untracked->exclude_oid, &oid_stat.oid);
}
/* Validate $GIT_DIR/info/exclude and core.excludesfile */
root = dir->untracked->root;
- if (oidcmp(&dir->ss_info_exclude.oid,
+ if (!oideq(&dir->ss_info_exclude.oid,
&dir->untracked->ss_info_exclude.oid)) {
invalidate_gitignore(dir->untracked, root);
dir->untracked->ss_info_exclude = dir->ss_info_exclude;
}
- if (oidcmp(&dir->ss_excludes_file.oid,
+ if (!oideq(&dir->ss_excludes_file.oid,
&dir->untracked->ss_excludes_file.oid)) {
invalidate_gitignore(dir->untracked, root);
dir->untracked->ss_excludes_file = dir->ss_excludes_file;
const char *path, int len, const struct pathspec *pathspec)
{
struct untracked_cache_dir *untracked;
- uint64_t start = getnanotime();
- if (has_symlink_leading_path(path, len))
+ trace_performance_enter();
+
+ if (has_symlink_leading_path(path, len)) {
+ trace_performance_leave("read directory %.*s", len, path);
return dir->nr;
+ }
untracked = validate_untracked_cache(dir, len, pathspec);
if (!untracked)
dir->nr = i;
}
- trace_performance_since(start, "read directory %.*s", len, path);
+ trace_performance_leave("read directory %.*s", len, path);
if (dir->untracked) {
static int force_untracked_cache = -1;
static struct trace_key trace_untracked_stats = TRACE_KEY_INIT(UNTRACKED_STATS);
return lstat(path, st);
}
+static void mark_colliding_entries(const struct checkout *state,
+ struct cache_entry *ce, struct stat *st)
+{
+ int i, trust_ino = check_stat;
+
+#if defined(GIT_WINDOWS_NATIVE)
+ trust_ino = 0;
+#endif
+
+ ce->ce_flags |= CE_MATCHED;
+
+ for (i = 0; i < state->istate->cache_nr; i++) {
+ struct cache_entry *dup = state->istate->cache[i];
+
+ if (dup == ce)
+ break;
+
+ if (dup->ce_flags & (CE_MATCHED | CE_VALID | CE_SKIP_WORKTREE))
+ continue;
+
+ if ((trust_ino && dup->ce_stat_data.sd_ino == st->st_ino) ||
+ (!trust_ino && !fspathcmp(ce->name, dup->name))) {
+ dup->ce_flags |= CE_MATCHED;
+ break;
+ }
+ }
+}
+
/*
* Write the contents from ce out to the working tree.
*
return -1;
}
+ if (state->clone)
+ mark_colliding_entries(state, ce, &st);
+
/*
* We unlink the old file, to get the new one with the
* right permissions (including umask, which is nasty
#ifndef __EWOK_RLW_H__
#define __EWOK_RLW_H__
+#include "ewok.h"
+
#define RLW_RUNNING_BITS (sizeof(eword_t) * 4)
#define RLW_LITERAL_BITS (sizeof(eword_t) * 8 - 1 - RLW_RUNNING_BITS)
#include "packfile.h"
#include "object-store.h"
#include "mem-pool.h"
+#include "commit-reach.h"
#define PACK_ID_BITS 16
#define MAX_PACK_ID ((1<<PACK_ID_BITS)-1)
unsigned int h = oid->hash[0] << 8 | oid->hash[1];
struct object_entry *e;
for (e = object_table[h]; e; e = e->next)
- if (!oidcmp(oid, &e->idx.oid))
+ if (oideq(oid, &e->idx.oid))
return e;
return NULL;
}
struct object_entry *e = object_table[h];
while (e) {
- if (!oidcmp(oid, &e->idx.oid))
+ if (oideq(oid, &e->idx.oid))
return e;
e = e->next;
}
duplicate_count_by_type[type]++;
return 1;
} else if (find_sha1_pack(oid.hash,
- get_packed_git(the_repository))) {
+ get_all_packs(the_repository))) {
e->type = type;
e->pack_id = MAX_PACK_ID;
e->idx.offset = 1; /* just not zero! */
truncate_pack(&checkpoint);
} else if (find_sha1_pack(oid.hash,
- get_packed_git(the_repository))) {
+ get_all_packs(the_repository))) {
e->type = OBJ_BLOB;
e->pack_id = MAX_PACK_ID;
e->idx.offset = 1; /* just not zero! */
if (!*slash1) {
if (!S_ISDIR(mode)
&& e->versions[1].mode == mode
- && !oidcmp(&e->versions[1].oid, oid))
+ && oideq(&e->versions[1].oid, oid))
return 0;
e->versions[1].mode = mode;
oidcpy(&e->versions[1].oid, oid);
struct object_entry *oe = find_mark(idnum);
if (oe->type != OBJ_COMMIT)
die("Mark :%" PRIuMAX " not a commit", idnum);
- if (oidcmp(&b->oid, &oe->idx.oid)) {
+ if (!oideq(&b->oid, &oe->idx.oid)) {
oidcpy(&b->oid, &oe->idx.oid);
if (oe->pack_id != MAX_PACK_ID) {
unsigned long size;
else
die("Invalid ref name or SHA1 expression: %s", from);
- if (b->branch_tree.tree && oidcmp(&oid, &b->branch_tree.versions[1].oid)) {
+ if (b->branch_tree.tree && !oideq(&oid, &b->branch_tree.versions[1].oid)) {
release_tree_content_recursive(b->branch_tree.tree);
b->branch_tree.tree = NULL;
}
-#ifndef FETCH_NEGOTIATOR
-#define FETCH_NEGOTIATOR
+#ifndef FETCH_NEGOTIATOR_H
+#define FETCH_NEGOTIATOR_H
struct commit;
fetch_if_missing = original_fetch_if_missing;
}
-void fetch_object(const char *remote_name, const unsigned char *sha1)
-{
- struct ref *ref = alloc_ref(sha1_to_hex(sha1));
- hashcpy(ref->old_oid.hash, sha1);
- fetch_refs(remote_name, ref);
-}
-
-void fetch_objects(const char *remote_name, const struct oid_array *to_fetch)
+void fetch_objects(const char *remote_name, const struct object_id *oids,
+ int oid_nr)
{
struct ref *ref = NULL;
int i;
- for (i = 0; i < to_fetch->nr; i++) {
- struct ref *new_ref = alloc_ref(oid_to_hex(&to_fetch->oid[i]));
- oidcpy(&new_ref->old_oid, &to_fetch->oid[i]);
+ for (i = 0; i < oid_nr; i++) {
+ struct ref *new_ref = alloc_ref(oid_to_hex(&oids[i]));
+ oidcpy(&new_ref->old_oid, &oids[i]);
+ new_ref->exact_oid = 1;
new_ref->next = ref;
ref = new_ref;
}
#ifndef FETCH_OBJECT_H
#define FETCH_OBJECT_H
-#include "sha1-array.h"
-
-extern void fetch_object(const char *remote_name, const unsigned char *sha1);
-
-extern void fetch_objects(const char *remote_name,
- const struct oid_array *to_fetch);
+void fetch_objects(const char *remote_name, const struct object_id *oids,
+ int oid_nr);
#endif
size_t nr, alloc;
};
-static void cache_one_alternate(const char *refname,
- const struct object_id *oid,
+static void cache_one_alternate(const struct object_id *oid,
void *vcache)
{
struct alternate_object_cache *cache = vcache;
if (args->stateless_rpc && multi_ack == 1)
die(_("--stateless-rpc requires multi_ack_detailed"));
- mark_tips(negotiator, args->negotiation_tips);
- for_each_cached_alternate(negotiator, insert_one_alternate_object);
+ if (!args->no_dependents) {
+ mark_tips(negotiator, args->negotiation_tips);
+ for_each_cached_alternate(negotiator, insert_one_alternate_object);
+ }
fetching = 0;
for ( ; refs ; refs = refs->next) {
* We use lookup_object here because we are only
* interested in the case we *know* the object is
* reachable and we have already scanned it.
+ *
+ * Do this only if args->no_dependents is false (if it is true,
+ * we cannot trust the object flags).
*/
- if (((o = lookup_object(the_repository, remote->hash)) != NULL) &&
+ if (!args->no_dependents &&
+ ((o = lookup_object(the_repository, remote->hash)) != NULL) &&
(o->flags & COMPLETE)) {
continue;
}
oidset_insert(oids, &refs->old_oid);
}
-static int tip_oids_contain(struct oidset *tip_oids,
- struct ref *unmatched, struct ref *newlist,
- const struct object_id *id)
+static int is_unmatched_ref(const struct ref *ref)
{
- /*
- * Note that this only looks at the ref lists the first time it's
- * called. This works out in filter_refs() because even though it may
- * add to "newlist" between calls, the additions will always be for
- * oids that are already in the set.
- */
- if (!tip_oids->map.map.tablesize) {
- add_refs_to_oidset(tip_oids, unmatched);
- add_refs_to_oidset(tip_oids, newlist);
- }
- return oidset_contains(tip_oids, id);
+ struct object_id oid;
+ const char *p;
+ return ref->match_status == REF_NOT_MATCHED &&
+ !parse_oid_hex(ref->name, &oid, &p) &&
+ *p == '\0' &&
+ oideq(&oid, &ref->old_oid);
}
static void filter_refs(struct fetch_pack_args *args,
struct ref *ref, *next;
struct oidset tip_oids = OIDSET_INIT;
int i;
+ int strict = !(allow_unadvertised_object_request &
+ (ALLOW_TIP_SHA1 | ALLOW_REACHABLE_SHA1));
i = 0;
for (ref = *refs; ref; ref = next) {
}
}
+ if (strict) {
+ for (i = 0; i < nr_sought; i++) {
+ ref = sought[i];
+ if (!is_unmatched_ref(ref))
+ continue;
+
+ add_refs_to_oidset(&tip_oids, unmatched);
+ add_refs_to_oidset(&tip_oids, newlist);
+ break;
+ }
+ }
+
/* Append unmatched requests to the list */
for (i = 0; i < nr_sought; i++) {
- struct object_id oid;
- const char *p;
-
ref = sought[i];
- if (ref->match_status != REF_NOT_MATCHED)
- continue;
- if (parse_oid_hex(ref->name, &oid, &p) ||
- *p != '\0' ||
- oidcmp(&oid, &ref->old_oid))
+ if (!is_unmatched_ref(ref))
continue;
- if ((allow_unadvertised_object_request &
- (ALLOW_TIP_SHA1 | ALLOW_REACHABLE_SHA1)) ||
- tip_oids_contain(&tip_oids, unmatched, newlist,
- &ref->old_oid)) {
+ if (!strict || oidset_contains(&tip_oids, &ref->old_oid)) {
ref->match_status = REF_MATCHED;
*newtail = copy_ref(ref);
newtail = &(*newtail)->next;
oidset_clear(&loose_oid_set);
- if (!args->no_dependents) {
- if (!args->deepen) {
- for_each_ref(mark_complete_oid, NULL);
- for_each_cached_alternate(NULL, mark_alternate_complete);
- commit_list_sort_by_date(&complete);
- if (cutoff)
- mark_recent_complete_commits(args, cutoff);
- }
+ if (!args->deepen) {
+ for_each_ref(mark_complete_oid, NULL);
+ for_each_cached_alternate(NULL, mark_alternate_complete);
+ commit_list_sort_by_date(&complete);
+ if (cutoff)
+ mark_recent_complete_commits(args, cutoff);
+ }
- /*
- * Mark all complete remote refs as common refs.
- * Don't mark them common yet; the server has to be told so first.
- */
- for (ref = *refs; ref; ref = ref->next) {
- struct object *o = deref_tag(the_repository,
- lookup_object(the_repository,
- ref->old_oid.hash),
- NULL, 0);
+ /*
+ * Mark all complete remote refs as common refs.
+ * Don't mark them common yet; the server has to be told so first.
+ */
+ for (ref = *refs; ref; ref = ref->next) {
+ struct object *o = deref_tag(the_repository,
+ lookup_object(the_repository,
+ ref->old_oid.hash),
+ NULL, 0);
- if (!o || o->type != OBJ_COMMIT || !(o->flags & COMPLETE))
- continue;
+ if (!o || o->type != OBJ_COMMIT || !(o->flags & COMPLETE))
+ continue;
- negotiator->known_common(negotiator,
- (struct commit *)o);
- }
+ negotiator->known_common(negotiator,
+ (struct commit *)o);
}
save_commit_buffer = old_save_commit_buffer;
if (!server_supports("deepen-relative") && args->deepen_relative)
die(_("Server does not support --deepen"));
- mark_complete_and_common_ref(&negotiator, args, &ref);
- filter_refs(args, &ref, sought, nr_sought);
- if (everything_local(args, &ref)) {
- packet_flush(fd[1]);
- goto all_done;
+ if (!args->no_dependents) {
+ mark_complete_and_common_ref(&negotiator, args, &ref);
+ filter_refs(args, &ref, sought, nr_sought);
+ if (everything_local(args, &ref)) {
+ packet_flush(fd[1]);
+ goto all_done;
+ }
+ } else {
+ filter_refs(args, &ref, sought, nr_sought);
}
if (find_common(&negotiator, args, fd, &oid, ref) < 0)
if (!args->keep_pack)
}
}
-static void add_wants(const struct ref *wants, struct strbuf *req_buf)
+static void add_wants(int no_dependents, const struct ref *wants, struct strbuf *req_buf)
{
int use_ref_in_want = server_supports_feature("fetch", "ref-in-want", 0);
* We use lookup_object here because we are only
* interested in the case we *know* the object is
* reachable and we have already scanned it.
+ *
+ * Do this only if args->no_dependents is false (if it is true,
+ * we cannot trust the object flags).
*/
- if (((o = lookup_object(the_repository, remote->hash)) != NULL) &&
+ if (!no_dependents &&
+ ((o = lookup_object(the_repository, remote->hash)) != NULL) &&
(o->flags & COMPLETE)) {
continue;
}
}
/* add wants */
- add_wants(wants, &req_buf);
+ add_wants(args->no_dependents, wants, &req_buf);
if (args->no_dependents) {
packet_buf_write(&req_buf, "done");
args->deepen = 1;
/* Filter 'ref' by 'sought' and those that aren't local */
- mark_complete_and_common_ref(&negotiator, args, &ref);
- filter_refs(args, &ref, sought, nr_sought);
- if (everything_local(args, &ref))
- state = FETCH_DONE;
- else
+ if (!args->no_dependents) {
+ mark_complete_and_common_ref(&negotiator, args, &ref);
+ filter_refs(args, &ref, sought, nr_sought);
+ if (everything_local(args, &ref))
+ state = FETCH_DONE;
+ else
+ state = FETCH_SEND_REQUEST;
+
+ mark_tips(&negotiator, args->negotiation_tips);
+ for_each_cached_alternate(&negotiator,
+ insert_one_alternate_object);
+ } else {
+ filter_refs(args, &ref, sought, nr_sought);
state = FETCH_SEND_REQUEST;
-
- mark_tips(&negotiator, args->negotiation_tips);
- for_each_cached_alternate(&negotiator,
- insert_one_alternate_object);
+ }
break;
case FETCH_SEND_REQUEST:
if (send_fetch_request(&negotiator, fd[1], args, ref,
if (nr_sought)
nr_sought = remove_duplicates_in_refs(sought, nr_sought);
- if (!ref) {
+ if (args->no_dependents && !args->filter_options.choice) {
+ /*
+ * The protocol does not support requesting that only the
+ * wanted objects be sent, so approximate this by setting a
+ * "blob:none" filter if no filter is already set. This works
+ * for all object types: note that wanted blobs will still be
+ * sent because they are directly specified as a "want".
+ *
+ * NEEDSWORK: Add an option in the protocol to request that
+ * only the wanted objects be sent, and implement it.
+ */
+ parse_list_objects_filter(&args->filter_options, "blob:none");
+ }
+
+ if (version != protocol_v2 && !ref) {
packet_flush(fd[1]);
die(_("no matching remote head"));
}
unsigned from_promisor:1;
/*
+ * Attempt to fetch only the wanted objects, and not any objects
+ * referred to by them. Due to protocol limitations, extraneous
+ * objects may still be included. (When fetching non-blob
+ * objects, only blobs are excluded; when fetching a blob, the
+ * blob itself will still be sent. The client does not need to
+ * know whether a wanted object is a blob or not.)
+ *
* If 1, fetch_pack() will also not modify any object flags.
* This allows fetch_pack() to safely be called by any function,
* regardless of which object flags it uses (if any).
#include "fsck.h"
#include "refs.h"
#include "utf8.h"
-#include "sha1-array.h"
#include "decorate.h"
#include "oidset.h"
#include "packfile.h"
static void init_skiplist(struct fsck_options *options, const char *path)
{
- static struct oid_array skiplist = OID_ARRAY_INIT;
- int sorted, fd;
- char buffer[GIT_MAX_HEXSZ + 1];
+ FILE *fp;
+ struct strbuf sb = STRBUF_INIT;
struct object_id oid;
- if (options->skiplist)
- sorted = options->skiplist->sorted;
- else {
- sorted = 1;
- options->skiplist = &skiplist;
- }
-
- fd = open(path, O_RDONLY);
- if (fd < 0)
+ fp = fopen(path, "r");
+ if (!fp)
die("Could not open skip list: %s", path);
- for (;;) {
+ while (!strbuf_getline(&sb, fp)) {
const char *p;
- int result = read_in_full(fd, buffer, sizeof(buffer));
- if (result < 0)
- die_errno("Could not read '%s'", path);
- if (!result)
- break;
- if (parse_oid_hex(buffer, &oid, &p) || *p != '\n')
- die("Invalid SHA-1: %s", buffer);
- oid_array_append(&skiplist, &oid);
- if (sorted && skiplist.nr > 1 &&
- oidcmp(&skiplist.oid[skiplist.nr - 2],
- &oid) > 0)
- sorted = 0;
- }
- close(fd);
+ const char *hash;
- if (sorted)
- skiplist.sorted = 1;
+ /*
+ * Allow trailing comments, leading whitespace
+ * (including before commits), and empty or whitespace
+ * only lines.
+ */
+ hash = strchr(sb.buf, '#');
+ if (hash)
+ strbuf_setlen(&sb, hash - sb.buf);
+ strbuf_trim(&sb);
+ if (!sb.len)
+ continue;
+
+ if (parse_oid_hex(sb.buf, &oid, &p) || *p != '\0')
+ die("Invalid SHA-1: %s", sb.buf);
+ oidset_insert(&options->skiplist, &oid);
+ }
+ if (ferror(fp))
+ die_errno("Could not read '%s'", path);
+ fclose(fp);
+ strbuf_release(&sb);
}
static int parse_msg_type(const char *str)
static int object_on_skiplist(struct fsck_options *opts, struct object *obj)
{
- if (opts && opts->skiplist && obj)
- return oid_array_lookup(opts->skiplist, &obj->oid) >= 0;
- return 0;
+ return opts && obj && oidset_contains(&opts->skiplist, &obj->oid);
}
__attribute__((format (printf, 4, 5)))
if (name) {
struct object *obj = &parents->item->object;
- if (++counter > 1)
+ if (counter++)
put_object_name(options, obj, "%s^%d",
name, counter);
else if (generation > 0)
#ifndef GIT_FSCK_H
#define GIT_FSCK_H
+#include "oidset.h"
+
#define FSCK_ERROR 1
#define FSCK_WARN 2
#define FSCK_IGNORE 3
fsck_error error_func;
unsigned strict:1;
int *msg_type;
- struct oid_array *skiplist;
+ struct oidset skiplist;
struct decoration *object_names;
};
-#define FSCK_OPTIONS_DEFAULT { NULL, fsck_error_function, 0, NULL }
-#define FSCK_OPTIONS_STRICT { NULL, fsck_error_function, 1, NULL }
+#define FSCK_OPTIONS_DEFAULT { NULL, fsck_error_function, 0, NULL, OIDSET_INIT }
+#define FSCK_OPTIONS_STRICT { NULL, fsck_error_function, 1, NULL, OIDSET_INIT }
/* descend in all linked child objects
* the return value is:
--- /dev/null
+#include "packfile.h"
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+{
+ enum object_type type;
+ unsigned long len;
+
+ unpack_object_header_buffer((const unsigned char *)data,
+ (unsigned long)size, &type, &len);
+
+ return 0;
+}
--- /dev/null
+#include "object-store.h"
+#include "packfile.h"
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+{
+ struct packed_git p;
+
+ load_idx("fuzz-input", GIT_SHA1_RAWSZ, (void *)data, size, &p);
+
+ return 0;
+}
#define _SGI_SOURCE 1
#if defined(WIN32) && !defined(__CYGWIN__) /* Both MinGW and MSVC */
-# if defined (_MSC_VER) && !defined(_WIN32_WINNT)
-# define _WIN32_WINNT 0x0502
+# if !defined(_WIN32_WINNT)
+# define _WIN32_WINNT 0x0600
# endif
#define WIN32_LEAN_AND_MEAN /* stops windows.h including winsock.h */
#include <winsock2.h>
#define find_last_dir_sep git_find_last_dir_sep
#endif
+#ifndef query_user_email
+#define query_user_email() NULL
+#endif
+
#if defined(__HP_cc) && (__HP_cc >= 61000)
#define NORETURN __attribute__((noreturn))
#define NORETURN_PTR
#define LAST_ARG_MUST_BE_NULL
#endif
+#define MAYBE_UNUSED __attribute__((__unused__))
+
#include "compat/bswap.h"
#include "wildmatch.h"
}
get_configured_merge_tool () {
- # Diff mode first tries diff.tool and falls back to merge.tool.
- # Merge mode only checks merge.tool
+ # If first argument is true, find the guitool instead
+ if test "$1" = true
+ then
+ gui_prefix=gui
+ fi
+
+ # Diff mode first tries diff.(gui)tool and falls back to merge.(gui)tool.
+ # Merge mode only checks merge.(gui)tool
if diff_mode
then
- merge_tool=$(git config diff.tool || git config merge.tool)
+ merge_tool=$(git config diff.${gui_prefix}tool || git config merge.${gui_prefix}tool)
else
- merge_tool=$(git config merge.tool)
+ merge_tool=$(git config merge.${gui_prefix}tool)
fi
if test -n "$merge_tool" && ! valid_tool "$merge_tool"
then
- echo >&2 "git config option $TOOL_MODE.tool set to unknown tool: $merge_tool"
+ echo >&2 "git config option $TOOL_MODE.${gui_prefix}tool set to unknown tool: $merge_tool"
echo >&2 "Resetting to default..."
return 1
fi
# at the discretion of Junio C Hamano.
#
-USAGE='[--tool=tool] [--tool-help] [-y|--no-prompt|--prompt] [-O<orderfile>] [file to merge] ...'
+USAGE='[--tool=tool] [--tool-help] [-y|--no-prompt|--prompt] [-g|--gui|--no-gui] [-O<orderfile>] [file to merge] ...'
SUBDIRECTORY_OK=Yes
NONGIT_OK=Yes
OPTIONS_SPEC=
main () {
prompt=$(git config --bool mergetool.prompt)
+ gui_tool=false
guessed_merge_tool=false
orderfile=
shift ;;
esac
;;
+ --no-gui)
+ gui_tool=false
+ ;;
+ -g|--gui)
+ gui_tool=true
+ ;;
-y|--no-prompt)
prompt=false
;;
if test -z "$merge_tool"
then
# Check if a merge tool has been configured
- merge_tool=$(get_configured_merge_tool)
+ merge_tool=$(get_configured_merge_tool $gui_tool)
# Try to guess an appropriate merge tool if no tool has been set.
if test -z "$merge_tool"
then
return LargeFileSystem.processContent(self, git_mode, relPath, contents)
class Command:
+ delete_actions = ( "delete", "move/delete", "purge" )
+ add_actions = ( "add", "move/add" )
+
def __init__(self):
self.usage = "usage: %prog [options]"
self.needsGit = True
return ""
class P4Sync(Command, P4UserMap):
- delete_actions = ( "delete", "move/delete", "purge" )
def __init__(self):
Command.__init__(self)
if self.verbose:
print("checkpoint finished: " + out)
- def cmp_shelved(self, path, filerev, revision):
- """ Determine if a path at revision #filerev is the same as the file
- at revision @revision for a shelved changelist. If they don't match,
- unshelving won't be safe (we will get other changes mixed in).
-
- This is comparing the revision that the shelved changelist is *based* on, not
- the shelved changelist itself.
- """
- ret = p4Cmd(["diff2", "{0}#{1}".format(path, filerev), "{0}@{1}".format(path, revision)])
- if verbose:
- print("p4 diff2 path %s filerev %s revision %s => %s" % (path, filerev, revision, ret))
- return ret["status"] == "identical"
-
- def extractFilesFromCommit(self, commit, shelved=False, shelved_cl = 0, origin_revision = 0):
+ def extractFilesFromCommit(self, commit, shelved=False, shelved_cl = 0):
self.cloneExclude = [re.sub(r"\.\.\.$", "", path)
for path in self.cloneExclude]
files = []
file["type"] = commit["type%s" % fnum]
if shelved:
file["shelved_cl"] = int(shelved_cl)
-
- # For shelved changelists, check that the revision of each file that the
- # shelve was based on matches the revision that we are using for the
- # starting point for git-fast-import (self.initialParent). Otherwise
- # the resulting diff will contain deltas from multiple commits.
-
- if file["action"] != "add" and \
- not self.cmp_shelved(path, file["rev"], origin_revision):
- sys.exit("change {0} not based on {1} for {2}, cannot unshelve".format(
- commit["change"], self.initialParent, path))
-
files.append(file)
fnum = fnum + 1
return files
relPath = self.stripRepoPath(file['depotFile'], self.branchPrefixes)
relPath = self.encodeWithUTF8(relPath)
if verbose:
- size = int(self.stream_file['fileSize'])
+ if 'fileSize' in self.stream_file:
+ size = int(self.stream_file['fileSize'])
+ else:
+ size = 0 # deleted files don't get a fileSize apparently
sys.stdout.write('\r%s --> %s (%i MB)\n' % (file['depotFile'], relPath, size/1024/1024))
sys.stdout.flush()
print('Ignoring file outside of prefix: {0}'.format(path))
return hasPrefix
- def commit(self, details, files, branch, parent = ""):
+ def commit(self, details, files, branch, parent = "", allow_empty=False):
epoch = details["time"]
author = details["user"]
jobs = self.extractJobsFromCommit(details)
files = [f for f in files
if self.inClientSpec(f['path']) and self.hasBranchPrefix(f['path'])]
- if not files and not gitConfigBool('git-p4.keepEmptyCommits'):
+ if gitConfigBool('git-p4.keepEmptyCommits'):
+ allow_empty = True
+
+ if not files and not allow_empty:
print('Ignoring revision {0} as it would produce an empty commit.'
.format(details['change']))
return
else:
return None
- def importChanges(self, changes, shelved=False, origin_revision=0):
+ def importChanges(self, changes, origin_revision=0):
cnt = 1
for change in changes:
- description = p4_describe(change, shelved)
+ description = p4_describe(change)
self.updateOptionDict(description)
if not self.silent:
print("Parent of %s not found. Committing into head of %s" % (branch, parent))
self.commit(description, filesForCommit, branch, parent)
else:
- files = self.extractFilesFromCommit(description, shelved, change, origin_revision)
+ files = self.extractFilesFromCommit(description)
self.commit(description, files, self.branch,
self.initialParent)
# only needed once, to connect to the previous commit
]
self.verbose = False
self.noCommit = False
- self.destbranch = "refs/remotes/p4/unshelved"
+ self.destbranch = "refs/remotes/p4-unshelved"
def renameBranch(self, branch_name):
""" Rename the existing branch to branch_name.N
sys.exit("could not find git-p4 commits in {0}".format(self.origin))
+ def createShelveParent(self, change, branch_name, sync, origin):
+ """ Create a commit matching the parent of the shelved changelist 'change'
+ """
+ parent_description = p4_describe(change, shelved=True)
+ parent_description['desc'] = 'parent for shelved changelist {}\n'.format(change)
+ files = sync.extractFilesFromCommit(parent_description, shelved=False, shelved_cl=change)
+
+ parent_files = []
+ for f in files:
+ # if it was added in the shelved changelist, it won't exist in the parent
+ if f['action'] in self.add_actions:
+ continue
+
+ # if it was deleted in the shelved changelist it must not be deleted
+ # in the parent - we might even need to create it if the origin branch
+ # does not have it
+ if f['action'] in self.delete_actions:
+ f['action'] = 'add'
+
+ parent_files.append(f)
+
+ sync.commit(parent_description, parent_files, branch_name,
+ parent=origin, allow_empty=True)
+ print("created parent commit for {0} based on {1} in {2}".format(
+ change, self.origin, branch_name))
+
def run(self, args):
if len(args) != 1:
return False
sync = P4Sync()
changes = args
- sync.initialParent = self.origin
- # use the first change in the list to construct the branch to unshelve into
+ # only one change at a time
change = changes[0]
# if the target branch already exists, rename it
sync.suppress_meta_comment = True
settings = self.findLastP4Revision(self.origin)
- origin_revision = settings['change']
sync.depotPaths = settings['depot-paths']
sync.branchPrefixes = sync.depotPaths
sync.openStreams()
sync.loadUserMapFromCache()
sync.silent = True
- sync.importChanges(changes, shelved=True, origin_revision=origin_revision)
+
+ # create a commit for the parent of the shelved changelist
+ self.createShelveParent(change, branch_name, sync, self.origin)
+
+ # create the commit for the shelved changelist itself
+ description = p4_describe(change, True)
+ files = sync.extractFilesFromCommit(description, True, change)
+
+ sync.commit(description, files, branch_name, "")
sync.closeStreams()
print("unshelved changelist {0} into {1}".format(change, branch_name))
# Note: incompatibility with --interactive is just a strong warning;
# git-rebase.txt caveats with "unless you know what you are doing"
test -n "$rebase_merges" &&
- die "$(gettext "error: cannot combine '--preserve_merges' with '--rebase-merges'")"
+ die "$(gettext "error: cannot combine '--preserve-merges' with '--rebase-merges'")"
fi
if test -n "$rebase_merges"
then
test -n "$strategy_opts" &&
- die "$(gettext "error: cannot combine '--rebase_merges' with '--strategy-option'")"
+ die "$(gettext "error: cannot combine '--rebase-merges' with '--strategy-option'")"
test -n "$strategy" &&
- die "$(gettext "error: cannot combine '--rebase_merges' with '--strategy'")"
+ die "$(gettext "error: cannot combine '--rebase-merges' with '--strategy'")"
fi
if test -z "$rebase_root"
--identity <str> * Use the sendemail.<id> options.
--to-cmd <str> * Email To: via `<str> \$patch_path`
--cc-cmd <str> * Email Cc: via `<str> \$patch_path`
- --suppress-cc <str> * author, self, sob, cc, cccmd, body, bodycc, all.
+ --suppress-cc <str> * author, self, sob, cc, cccmd, body, bodycc, misc-by, all.
--[no-]cc-cover * Email Cc: addresses in the cover letter.
--[no-]to-cover * Email To: addresses in the cover letter.
--[no-]signed-off-by-cc * Send to Signed-off-by: addresses. Default on.
if (@suppress_cc) {
foreach my $entry (@suppress_cc) {
die sprintf(__("Unknown --suppress-cc field: '%s'\n"), $entry)
- unless $entry =~ /^(?:all|cccmd|cc|author|self|sob|body|bodycc)$/;
+ unless $entry =~ /^(?:all|cccmd|cc|author|self|sob|body|bodycc|misc-by)$/;
$suppress_cc{$entry} = 1;
}
}
if ($suppress_cc{'all'}) {
- foreach my $entry (qw (cccmd cc author self sob body bodycc)) {
+ foreach my $entry (qw (cccmd cc author self sob body bodycc misc-by)) {
$suppress_cc{$entry} = 1;
}
delete $suppress_cc{'all'};
$suppress_cc{'sob'} = !$signed_off_by_cc if defined $signed_off_by_cc;
if ($suppress_cc{'body'}) {
- foreach my $entry (qw (sob bodycc)) {
+ foreach my $entry (qw (sob bodycc misc-by)) {
$suppress_cc{$entry} = 1;
}
delete $suppress_cc{'body'};
# Now parse the message body
while(<$fh>) {
$message .= $_;
- if (/^(Signed-off-by|Cc): (.*)/i) {
+ if (/^([a-z-]*-by|Cc): (.*)/i) {
chomp;
my ($what, $c) = ($1, $2);
# strip garbage for the address we'll use:
if ($sc eq $sender) {
next if ($suppress_cc{'self'});
} else {
- next if $suppress_cc{'sob'} and $what =~ /Signed-off-by/i;
- next if $suppress_cc{'bodycc'} and $what =~ /Cc/i;
+ if ($what =~ /^Signed-off-by$/i) {
+ next if $suppress_cc{'sob'};
+ } elsif ($what =~ /-by$/i) {
+ next if $suppress_cc{'misc-by'};
+ } elsif ($what =~ /Cc/i) {
+ next if $suppress_cc{'bodycc'};
+ }
+ }
+ if ($c !~ /.+@.+|<.+>/) {
+ printf("(body) Ignoring %s from line '%s'\n",
+ $what, $_) unless $quiet;
+ next;
}
push @cc, $c;
printf(__("(body) Adding cc: %s from line '%s'\n"),
n=$(($1 + 0)) 2>/dev/null && test "$n" = "$1"
}
+# Given a full hex object ID, is this the zero OID?
+is_zero_oid () {
+ echo "$1" | sane_egrep '^0+$' >/dev/null 2>&1
+}
+
# Sanitize the local git environment for use within a submodule. We
# can't simply use clear_local_git_env since we want to preserve some
# of the settings from GIT_CONFIG_PARAMETERS.
"$@" || echo "#unmatched" $?
} | {
err=
- while read -r mode sha1 stage just_cloned sm_path
+ while read -r quickabort sha1 just_cloned sm_path
do
- die_if_unmatched "$mode" "$sha1"
+ die_if_unmatched "$quickabort" "$sha1"
- name=$(git submodule--helper name "$sm_path") || exit
- if ! test -z "$update"
- then
- update_module=$update
- else
- update_module=$(git config submodule."$name".update)
- if test -z "$update_module"
- then
- update_module="checkout"
- fi
- fi
+ git submodule--helper ensure-core-worktree "$sm_path"
+
+ update_module=$(git submodule--helper update-module-mode $just_cloned "$sm_path" $update)
displaypath=$(git submodule--helper relative-path "$prefix$sm_path" "$wt_prefix")
if test $just_cloned -eq 1
then
subsha1=
- case "$update_module" in
- merge | rebase | none)
- update_module=checkout ;;
- esac
else
subsha1=$(sanitize_submodule_env; cd "$sm_path" &&
git rev-parse --verify HEAD) ||
must_die_on_failure=yes
;;
*)
- die "$(eval_gettext "Invalid update mode '$update_module' for submodule '$name'")"
+ die "$(eval_gettext "Invalid update mode '$update_module' for submodule path '$path'")"
esac
if (sanitize_submodule_env; cd "$sm_path" && $command "$sha1")
while read -r mod_src mod_dst sha1_src sha1_dst status name
do
if test -z "$cached" &&
- test $sha1_dst = 0000000000000000000000000000000000000000
+ is_zero_oid $sha1_dst
then
case "$mod_dst" in
160000)
alias_command = (*argv)[0];
alias_string = alias_lookup(alias_command);
if (alias_string) {
+ if (*argcp > 1 && !strcmp((*argv)[1], "-h"))
+ fprintf_ln(stderr, _("'%s' is aliased to '%s'"),
+ alias_command, alias_string);
if (alias_string[0] == '!') {
struct child_process child = CHILD_PROCESS_INIT;
int nongit_ok;
{ "merge-tree", cmd_merge_tree, RUN_SETUP | NO_PARSEOPT },
{ "mktag", cmd_mktag, RUN_SETUP | NO_PARSEOPT },
{ "mktree", cmd_mktree, RUN_SETUP },
+ { "multi-pack-index", cmd_multi_pack_index, RUN_SETUP_GENTLY },
{ "mv", cmd_mv, RUN_SETUP | NEED_WORK_TREE },
{ "name-rev", cmd_name_rev, RUN_SETUP },
{ "notes", cmd_notes, RUN_SETUP },
static int run_argv(int *argcp, const char ***argv)
{
int done_alias = 0;
+ struct string_list cmd_list = STRING_LIST_INIT_NODUP;
+ struct string_list_item *seen;
while (1) {
/*
/* .. then try the external ones */
execv_dashed_external(*argv);
- /* It could be an alias -- this works around the insanity
+ seen = unsorted_string_list_lookup(&cmd_list, *argv[0]);
+ if (seen) {
+ int i;
+ struct strbuf sb = STRBUF_INIT;
+ for (i = 0; i < cmd_list.nr; i++) {
+ struct string_list_item *item = &cmd_list.items[i];
+
+ strbuf_addf(&sb, "\n %s", item->string);
+ if (item == seen)
+ strbuf_addstr(&sb, " <==");
+ else if (i == cmd_list.nr - 1)
+ strbuf_addstr(&sb, " ==>");
+ }
+ die(_("alias loop detected: expansion of '%s' does"
+ " not terminate:%s"), cmd_list.items[0].string, sb.buf);
+ }
+
+ string_list_append(&cmd_list, *argv[0]);
+
+ /*
+ * It could be an alias -- this works around the insanity
* of overriding "git log" with "git show" by having
* alias.log = show
*/
- if (done_alias)
- break;
if (!handle_alias(argcp, argv))
break;
done_alias = 1;
}
+ string_list_clear(&cmd_list, 0);
+
return done_alias;
}
}
/*
- * Draw an octopus merge and return the number of characters written.
+ * Draw the horizontal dashes of an octopus merge and return the number of
+ * characters written.
*/
static int graph_draw_octopus_merge(struct git_graph *graph,
struct strbuf *sb)
{
/*
- * Here dashless_commits represents the number of parents
- * which don't need to have dashes (because their edges fit
- * neatly under the commit).
- */
- const int dashless_commits = 2;
- int col_num, i;
- int num_dashes =
- ((graph->num_parents - dashless_commits) * 2) - 1;
- for (i = 0; i < num_dashes; i++) {
- col_num = (i / 2) + dashless_commits + graph->commit_index;
- strbuf_write_column(sb, &graph->new_columns[col_num], '-');
+ * Here dashless_parents represents the number of parents which don't
+ * need to have dashes (the edges labeled "0" and "1"). And
+ * dashful_parents are the remaining ones.
+ *
+ * | *---.
+ * | |\ \ \
+ * | | | | |
+ * x 0 1 2 3
+ *
+ */
+ const int dashless_parents = 2;
+ int dashful_parents = graph->num_parents - dashless_parents;
+
+ /*
+ * Usually, we add one new column for each parent (like the diagram
+ * above) but sometimes the first parent goes into an existing column,
+ * like this:
+ *
+ * | *---.
+ * | |\ \ \
+ * |/ / / /
+ * x 0 1 2
+ *
+ * In which case the number of parents will be one greater than the
+ * number of added columns.
+ */
+ int added_cols = (graph->num_new_columns - graph->num_columns);
+ int parent_in_old_cols = graph->num_parents - added_cols;
+
+ /*
+ * In both cases, commit_index corresponds to the edge labeled "0".
+ */
+ int first_col = graph->commit_index + dashless_parents
+ - parent_in_old_cols;
+
+ int i;
+ for (i = 0; i < dashful_parents; i++) {
+ strbuf_write_column(sb, &graph->new_columns[i+first_col], '-');
+ strbuf_write_column(sb, &graph->new_columns[i+first_col],
+ i == dashful_parents-1 ? '.' : '-');
}
- col_num = (i / 2) + dashless_commits + graph->commit_index;
- strbuf_write_column(sb, &graph->new_columns[col_num], '.');
- return num_dashes + 1;
+ return 2 * dashful_parents;
}
static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)
#include "help.h"
static int grep_source_load(struct grep_source *gs);
-static int grep_source_is_binary(struct grep_source *gs);
+static int grep_source_is_binary(struct grep_source *gs,
+ struct index_state *istate);
static struct grep_opt grep_defaults;
* We could let the compiler do this, but without C99 initializers
* the code gets unwieldy and unreadable, so...
*/
-void init_grep_defaults(void)
+void init_grep_defaults(struct repository *repo)
{
struct grep_opt *opt = &grep_defaults;
static int run_once;
run_once++;
memset(opt, 0, sizeof(*opt));
+ opt->repo = repo;
opt->relative = 1;
opt->pathname = 1;
opt->max_depth = -1;
* default values from the template we read the configuration
* information in an earlier call to git_config(grep_config).
*/
-void grep_init(struct grep_opt *opt, const char *prefix)
+void grep_init(struct grep_opt *opt, struct repository *repo, const char *prefix)
{
struct grep_opt *def = &grep_defaults;
int i;
memset(opt, 0, sizeof(*opt));
+ opt->repo = repo;
opt->prefix = prefix;
opt->prefix_length = (prefix && *prefix) ? strlen(prefix) : 0;
opt->pattern_tail = &opt->pattern_list;
{
xdemitconf_t *xecfg = opt->priv;
if (xecfg && !xecfg->find_func) {
- grep_source_load_driver(gs);
+ grep_source_load_driver(gs, opt->repo->index);
if (gs->driver->funcname.pattern) {
const struct userdiff_funcname *pe = &gs->driver->funcname;
xdiff_set_find_func(xecfg, pe->pattern, pe->cflags);
return 0;
}
-static int fill_textconv_grep(struct userdiff_driver *driver,
+static int fill_textconv_grep(struct repository *r,
+ struct userdiff_driver *driver,
struct grep_source *gs)
{
struct diff_filespec *df;
* structure.
*/
grep_read_lock();
- size = fill_textconv(driver, df, &buf);
+ size = fill_textconv(r, driver, df, &buf);
grep_read_unlock();
free_filespec(df);
opt->last_shown = 0;
if (opt->allow_textconv) {
- grep_source_load_driver(gs);
+ grep_source_load_driver(gs, opt->repo->index);
/*
* We might set up the shared textconv cache data here, which
* is not thread-safe.
if (!textconv) {
switch (opt->binary) {
case GREP_BINARY_DEFAULT:
- if (grep_source_is_binary(gs))
+ if (grep_source_is_binary(gs, opt->repo->index))
binary_match_only = 1;
break;
case GREP_BINARY_NOMATCH:
- if (grep_source_is_binary(gs))
+ if (grep_source_is_binary(gs, opt->repo->index))
return 0; /* Assume unmatch */
break;
case GREP_BINARY_TEXT:
try_lookahead = should_lookahead(opt);
- if (fill_textconv_grep(textconv, gs) < 0)
+ if (fill_textconv_grep(opt->repo, textconv, gs) < 0)
return 0;
bol = gs->buf;
BUG("invalid grep_source type to load");
}
-void grep_source_load_driver(struct grep_source *gs)
+void grep_source_load_driver(struct grep_source *gs,
+ struct index_state *istate)
{
if (gs->driver)
return;
grep_attr_lock();
if (gs->path)
- gs->driver = userdiff_find_by_path(gs->path);
+ gs->driver = userdiff_find_by_path(istate, gs->path);
if (!gs->driver)
gs->driver = userdiff_find_by_name("default");
grep_attr_unlock();
}
-static int grep_source_is_binary(struct grep_source *gs)
+static int grep_source_is_binary(struct grep_source *gs,
+ struct index_state *istate)
{
- grep_source_load_driver(gs);
+ grep_source_load_driver(gs, istate);
if (gs->driver->binary != -1)
return gs->driver->binary;
#include "thread-utils.h"
#include "userdiff.h"
+struct repository;
+
enum grep_pat_token {
GREP_PATTERN,
GREP_PATTERN_HEAD,
struct grep_pat *header_list;
struct grep_pat **header_tail;
struct grep_expr *pattern_expression;
+ struct repository *repo;
const char *prefix;
int prefix_length;
regex_t regexp;
void *output_priv;
};
-extern void init_grep_defaults(void);
+extern void init_grep_defaults(struct repository *);
extern int grep_config(const char *var, const char *value, void *);
-extern void grep_init(struct grep_opt *, const char *prefix);
+extern void grep_init(struct grep_opt *, struct repository *repo, const char *prefix);
void grep_commit_pattern_type(enum grep_pattern_type, struct grep_opt *opt);
extern void append_grep_pat(struct grep_opt *opt, const char *pat, size_t patlen, const char *origin, int no, enum grep_pat_token t);
const void *identifier);
void grep_source_clear_data(struct grep_source *gs);
void grep_source_clear(struct grep_source *gs);
-void grep_source_load_driver(struct grep_source *gs);
+void grep_source_load_driver(struct grep_source *gs,
+ struct index_state *istate);
int grep_source(struct grep_opt *opt, struct grep_source *gs);
return strcmp(e1->name, e2->name);
}
-static void print_cmd_by_category(const struct category_description *catdesc)
+static void print_cmd_by_category(const struct category_description *catdesc,
+ int *longest_p)
{
struct cmdname_help *cmds;
int longest = 0;
print_command_list(cmds, mask, longest);
}
free(cmds);
+ if (longest_p)
+ *longest_p = longest;
}
void add_cmdname(struct cmdnames *cmds, const char *name, int len)
void list_common_cmds_help(void)
{
puts(_("These are common Git commands used in various situations:"));
- print_cmd_by_category(common_categories);
+ print_cmd_by_category(common_categories, NULL);
}
void list_all_main_cmds(struct string_list *list)
{ CAT_guide, N_("The common Git guides are:") },
{ 0, NULL }
};
- print_cmd_by_category(catdesc);
+ print_cmd_by_category(catdesc, NULL);
putchar('\n');
}
string_list_clear(&keys, 0);
}
+static int get_alias(const char *var, const char *value, void *data)
+{
+ struct string_list *list = data;
+
+ if (skip_prefix(var, "alias.", &var))
+ string_list_append(list, var)->util = xstrdup(value);
+
+ return 0;
+}
+
void list_all_cmds_help(void)
{
- print_cmd_by_category(main_categories);
+ struct string_list others = STRING_LIST_INIT_DUP;
+ struct string_list alias_list = STRING_LIST_INIT_DUP;
+ struct cmdname_help *aliases;
+ int i, longest;
+
+ printf_ln(_("See 'git help <command>' to read about a specific subcommand"));
+ print_cmd_by_category(main_categories, &longest);
+
+ list_all_other_cmds(&others);
+ if (others.nr)
+ printf("\n%s\n", _("External commands"));
+ for (i = 0; i < others.nr; i++)
+ printf(" %s\n", others.items[i].string);
+ string_list_clear(&others, 0);
+
+ git_config(get_alias, &alias_list);
+ string_list_sort(&alias_list);
+ if (alias_list.nr) {
+ printf("\n%s\n", _("Command aliases"));
+ ALLOC_ARRAY(aliases, alias_list.nr + 1);
+ for (i = 0; i < alias_list.nr; i++) {
+ aliases[i].name = alias_list.items[i].string;
+ aliases[i].help = alias_list.items[i].util;
+ aliases[i].category = 1;
+ }
+ aliases[alias_list.nr].name = NULL;
+ print_command_list(aliases, 1, longest);
+ free(aliases);
+ }
+ string_list_clear(&alias_list, 1);
}
int is_in_cmdlist(struct cmdnames *c, const char *s)
size_t cnt = 0;
select_getanyfile(hdr);
- for (p = get_packed_git(the_repository); p; p = p->next) {
+ for (p = get_all_packs(the_repository); p; p = p->next) {
if (p->pack_local)
cnt++;
}
strbuf_grow(&buf, cnt * 53 + 2);
- for (p = get_packed_git(the_repository); p; p = p->next) {
+ for (p = get_all_packs(the_repository); p; p = p->next) {
if (p->pack_local)
strbuf_addf(&buf, "P %s\n", p->pack_name + objdirlen + 6);
}
#include "argv-array.h"
#include "packfile.h"
#include "object-store.h"
-
+#include "commit-reach.h"
#ifdef EXPAT_NEEDS_XMLPARSE_H
#include <xmlparse.h>
continue;
}
- if (!oidcmp(&ref->old_oid, &ref->peer_ref->new_oid)) {
+ if (oideq(&ref->old_oid, &ref->peer_ref->new_oid)) {
if (push_verbosely)
fprintf(stderr, "'%s': up-to-date\n", ref->name);
if (helper_status)
if (!push_all && !is_null_oid(&ref->old_oid))
argv_array_pushf(&commit_argv, "^%s",
oid_to_hex(&ref->old_oid));
- init_revisions(&revs, setup_git_directory());
+ repo_init_revisions(the_repository, &revs, setup_git_directory());
setup_revisions(commit_argv.argc, commit_argv.argv, &revs, NULL);
revs.edge_hint = 0; /* just in case */
list_for_each(pos, head) {
obj_req = list_entry(pos, struct object_request, node);
- if (!hashcmp(obj_req->oid.hash, sha1))
+ if (hasheq(obj_req->oid.hash, sha1))
break;
}
if (obj_req == NULL)
} else if (req->zret != Z_STREAM_END) {
walker->corrupt_object_found++;
ret = error("File %s (%s) corrupt", hex, req->url);
- } else if (hashcmp(obj_req->oid.hash, req->real_sha1)) {
+ } else if (!hasheq(obj_req->oid.hash, req->real_sha1)) {
ret = error("File %s has bad hash", hex);
} else if (req->rename < 0) {
struct strbuf buf = STRBUF_INIT;
unlink_or_warn(freq->tmpfile.buf);
return -1;
}
- if (hashcmp(freq->sha1, freq->real_sha1)) {
+ if (!hasheq(freq->sha1, freq->real_sha1)) {
unlink_or_warn(freq->tmpfile.buf);
return -1;
}
strbuf_addstr(&git_default_email, email);
committer_ident_explicitly_given |= IDENT_MAIL_GIVEN;
author_ident_explicitly_given |= IDENT_MAIL_GIVEN;
+ } else if ((email = query_user_email()) && email[0]) {
+ strbuf_addstr(&git_default_email, email);
+ free((char *)email);
} else
copy_email(xgetpwuid_self(&default_email_is_bogus),
&git_default_email, &default_email_is_bogus);
--- /dev/null
+#include "cache.h"
+#include "commit.h"
+#include "revision.h"
+#include "interdiff.h"
+
+static struct strbuf *idiff_prefix_cb(struct diff_options *opt, void *data)
+{
+ return data;
+}
+
+void show_interdiff(struct rev_info *rev, int indent)
+{
+ struct diff_options opts;
+ struct strbuf prefix = STRBUF_INIT;
+
+ memcpy(&opts, &rev->diffopt, sizeof(opts));
+ opts.output_format = DIFF_FORMAT_PATCH;
+ opts.output_prefix = idiff_prefix_cb;
+ strbuf_addchars(&prefix, ' ', indent);
+ opts.output_prefix_data = &prefix;
+ diff_setup_done(&opts);
+
+ diff_tree_oid(rev->idiff_oid1, rev->idiff_oid2, "", &opts);
+ diffcore_std(&opts);
+ diff_flush(&opts);
+
+ strbuf_release(&prefix);
+}
--- /dev/null
+#ifndef INTERDIFF_H
+#define INTERDIFF_H
+
+struct rev_info;
+
+void show_interdiff(struct rev_info *, int indent);
+
+#endif
* of the given strings.
*/
+#include "strbuf.h"
+
struct json_writer
{
/*
SCOPE kh_##name##_t *kh_init_##name(void) { \
return (kh_##name##_t*)xcalloc(1, sizeof(kh_##name##_t)); \
} \
+ SCOPE void kh_release_##name(kh_##name##_t *h) \
+ { \
+ free(h->flags); \
+ free((void *)h->keys); \
+ free((void *)h->vals); \
+ } \
SCOPE void kh_destroy_##name(kh_##name##_t *h) \
{ \
if (h) { \
- free((void *)h->keys); free(h->flags); \
- free((void *)h->vals); \
+ kh_release_##name(h); \
free(h); \
} \
} \
__KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal)
#define KHASH_INIT(name, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \
- KHASH_INIT2(name, static inline, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal)
+ KHASH_INIT2(name, MAYBE_UNUSED static inline, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal)
/* Other convenient macros... */
return;
}
-static void fill_line_ends(struct diff_filespec *spec, long *lines,
+static void fill_line_ends(struct repository *r,
+ struct diff_filespec *spec,
+ long *lines,
unsigned long **line_ends)
{
int num = 0, size = 50;
unsigned long *ends = NULL;
char *data = NULL;
- if (diff_populate_filespec(spec, 0))
+ if (diff_populate_filespec(r, spec, 0))
die("Cannot read blob %s", oid_to_hex(&spec->oid));
ALLOC_ARRAY(ends, size);
}
static struct line_log_data *
-parse_lines(struct commit *commit, const char *prefix, struct string_list *args)
+parse_lines(struct repository *r, struct commit *commit,
+ const char *prefix, struct string_list *args)
{
long lines = 0;
unsigned long *ends = NULL;
long begin = 0, end = 0;
long anchor;
- name_part = skip_range_arg(item->string);
+ name_part = skip_range_arg(item->string, r->index);
if (!name_part || *name_part != ':' || !name_part[1])
die("-L argument not 'start,end:file' or ':funcname:file': %s",
item->string);
spec = alloc_filespec(full_name);
fill_blob_sha1(commit, spec);
- fill_line_ends(spec, &lines, &ends);
+ fill_line_ends(r, spec, &lines, &ends);
cb_data.spec = spec;
cb_data.lines = lines;
cb_data.line_ends = ends;
if (parse_range_arg(range_part, nth_line, &cb_data,
lines, anchor, &begin, &end,
- full_name))
+ full_name, r->index))
die("malformed -L argument '%s'", range_part);
if ((!lines && (begin || end)) || lines < begin)
die("file %s has only %lu lines", name_part, lines);
struct line_log_data *range;
commit = check_single_commit(rev);
- range = parse_lines(commit, prefix, args);
+ range = parse_lines(rev->diffopt.repo, commit, prefix, args);
add_line_range(rev, commit, range);
if (!rev->diffopt.detect_rename) {
return;
if (pair->one->oid_valid)
- fill_line_ends(pair->one, &p_lines, &p_ends);
- fill_line_ends(pair->two, &t_lines, &t_ends);
+ fill_line_ends(rev->diffopt.repo, pair->one, &p_lines, &p_ends);
+ fill_line_ends(rev->diffopt.repo, pair->two, &t_lines, &t_ends);
fprintf(opt->file, "%s%sdiff --git a/%s b/%s%s\n", prefix, c_meta, pair->one->path, pair->two->path, c_reset);
fprintf(opt->file, "%s%s--- %s%s%s\n", prefix, c_meta,
return 0;
assert(pair->two->oid_valid);
- diff_populate_filespec(pair->two, 0);
+ diff_populate_filespec(rev->diffopt.repo, pair->two, 0);
file_target.ptr = pair->two->data;
file_target.size = pair->two->size;
if (pair->one->oid_valid) {
- diff_populate_filespec(pair->one, 0);
+ diff_populate_filespec(rev->diffopt.repo, pair->one, 0);
file_parent.ptr = pair->one->data;
file_parent.size = pair->one->size;
} else {
}
}
-static const char *parse_range_funcname(const char *arg, nth_line_fn_t nth_line_cb,
- void *cb_data, long lines, long anchor, long *begin, long *end,
- const char *path)
+static const char *parse_range_funcname(
+ const char *arg, nth_line_fn_t nth_line_cb,
+ void *cb_data, long lines, long anchor, long *begin, long *end,
+ const char *path, struct index_state *istate)
{
char *pattern;
const char *term;
anchor--; /* input is in human terms */
start = nth_line_cb(cb_data, anchor);
- drv = userdiff_find_by_path(path);
+ drv = userdiff_find_by_path(istate, path);
if (drv && drv->funcname.pattern) {
const struct userdiff_funcname *pe = &drv->funcname;
xecfg = xcalloc(1, sizeof(*xecfg));
int parse_range_arg(const char *arg, nth_line_fn_t nth_line_cb,
void *cb_data, long lines, long anchor,
- long *begin, long *end, const char *path)
+ long *begin, long *end,
+ const char *path, struct index_state *istate)
{
*begin = *end = 0;
anchor = lines + 1;
if (*arg == ':' || (*arg == '^' && *(arg + 1) == ':')) {
- arg = parse_range_funcname(arg, nth_line_cb, cb_data, lines, anchor, begin, end, path);
+ arg = parse_range_funcname(arg, nth_line_cb, cb_data,
+ lines, anchor, begin, end,
+ path, istate);
if (!arg || *arg)
return -1;
return 0;
return 0;
}
-const char *skip_range_arg(const char *arg)
+const char *skip_range_arg(const char *arg, struct index_state *istate)
{
if (*arg == ':' || (*arg == '^' && *(arg + 1) == ':'))
- return parse_range_funcname(arg, NULL, NULL, 0, 0, NULL, NULL, NULL);
+ return parse_range_funcname(arg, NULL, NULL,
+ 0, 0, NULL, NULL,
+ NULL, istate);
arg = parse_loc(arg, NULL, NULL, 0, -1, NULL);
#ifndef LINE_RANGE_H
#define LINE_RANGE_H
+struct index_state;
+
/*
* Parse one item in an -L begin,end option w.r.t. the notional file
* object 'cb_data' consisting of 'lines' lines.
nth_line_fn_t nth_line_cb,
void *cb_data, long lines, long anchor,
long *begin, long *end,
- const char *path);
+ const char *path, struct index_state *istate);
/*
* Scan past a range argument that could be parsed by
* NULL in case the argument is obviously malformed.
*/
-const char *skip_range_arg(const char *arg);
+const char *skip_range_arg(const char *arg, struct index_state *istate);
#endif /* LINE_RANGE_H */
int *free_row, free_count = 0, saved_free_count, *pred, *col;
int i, j, phase;
+ if (column_count < 2) {
+ memset(column2row, 0, sizeof(int) * column_count);
+ memset(row2column, 0, sizeof(int) * row_count);
+ return;
+ }
+
memset(column2row, -1, sizeof(int) * column_count);
memset(row2column, -1, sizeof(int) * row_count);
ALLOC_ARRAY(v, column_count);
if (filter_options->choice) {
if (errbuf) {
- strbuf_init(errbuf, 0);
strbuf_addstr(
errbuf,
_("multiple filter-specs cannot be combined"));
return 0;
}
+ } else if (skip_prefix(arg, "tree:", &v0)) {
+ unsigned long depth;
+ if (!git_parse_ulong(v0, &depth) || depth != 0) {
+ if (errbuf) {
+ strbuf_addstr(
+ errbuf,
+ _("only 'tree:0' is supported"));
+ }
+ return 1;
+ }
+ filter_options->choice = LOFC_TREE_NONE;
+ return 0;
+
} else if (skip_prefix(arg, "sparse:oid=", &v0)) {
struct object_context oc;
struct object_id sparse_oid;
return 0;
}
- if (errbuf) {
- strbuf_init(errbuf, 0);
+ if (errbuf)
strbuf_addf(errbuf, "invalid filter-spec '%s'", arg);
- }
+
memset(filter_options, 0, sizeof(*filter_options));
return 1;
}
LOFC_DISABLED = 0,
LOFC_BLOB_NONE,
LOFC_BLOB_LIMIT,
+ LOFC_TREE_NONE,
LOFC_SPARSE_OID,
LOFC_SPARSE_PATH,
LOFC__COUNT /* must be last */
switch (filter_situation) {
default:
- die("unknown filter_situation");
- return LOFR_ZERO;
+ BUG("unknown filter_situation: %d", filter_situation);
case LOFS_BEGIN_TREE:
assert(obj->type == OBJ_TREE);
return d;
}
+/*
+ * 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 oidset *omits;
+};
+
+static enum list_objects_filter_result filter_trees_none(
+ 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_;
+
+ switch (filter_situation) {
+ default:
+ BUG("unknown filter_situation: %d", filter_situation);
+
+ case LOFS_BEGIN_TREE:
+ 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;
+ } else {
+ /*
+ * Not collecting omits so no need to to traverse tree.
+ */
+ return LOFR_SKIP_TREE | LOFR_MARK_SEEN;
+ }
+
+ case LOFS_END_TREE:
+ assert(obj->type == OBJ_TREE);
+ return LOFR_ZERO;
+
+ }
+}
+
+static void* filter_trees_none__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));
+ d->omits = omitted;
+
+ *filter_fn = filter_trees_none;
+ *filter_free_fn = free;
+ return d;
+}
+
/*
* A filter for list-objects to omit large blobs.
* And to OPTIONALLY collect a list of the omitted OIDs.
switch (filter_situation) {
default:
- die("unknown filter_situation");
- return LOFR_ZERO;
+ BUG("unknown filter_situation: %d", filter_situation);
case LOFS_BEGIN_TREE:
assert(obj->type == OBJ_TREE);
switch (filter_situation) {
default:
- die("unknown filter_situation");
- return LOFR_ZERO;
+ BUG("unknown filter_situation: %d", filter_situation);
case LOFS_BEGIN_TREE:
assert(obj->type == OBJ_TREE);
NULL,
filter_blobs_none__init,
filter_blobs_limit__init,
+ filter_trees_none__init,
filter_sparse_oid__init,
filter_sparse_path__init,
};
assert((sizeof(s_filters) / sizeof(s_filters[0])) == LOFC__COUNT);
if (filter_options->choice >= LOFC__COUNT)
- die("invalid list-objects filter choice: %d",
+ BUG("invalid list-objects filter choice: %d",
filter_options->choice);
init_fn = s_filters[filter_options->choice];
* In general, objects should only be shown once, but
* this result DOES NOT imply that we mark it SEEN.
*
+ * _SKIP_TREE : Used in LOFS_BEGIN_TREE situation - indicates that
+ * the tree's children should not be iterated over. This
+ * is used as an optimization when all children will
+ * definitely be ignored.
+ *
* Most of the time, you want the combination (_MARK_SEEN | _DO_SHOW)
* but they can be used independently, such as when sparse-checkout
* pattern matching is being applied.
LOFR_ZERO = 0,
LOFR_MARK_SEEN = 1<<0,
LOFR_DO_SHOW = 1<<1,
+ LOFR_SKIP_TREE = 1<<2,
};
enum list_objects_filter_situation {
#include "list-objects-filter-options.h"
#include "packfile.h"
#include "object-store.h"
+#include "trace.h"
-static void process_blob(struct rev_info *revs,
+struct traversal_context {
+ struct rev_info *revs;
+ show_object_fn show_object;
+ show_commit_fn show_commit;
+ void *show_data;
+ filter_object_fn filter_fn;
+ void *filter_data;
+};
+
+static void process_blob(struct traversal_context *ctx,
struct blob *blob,
- show_object_fn show,
struct strbuf *path,
- const char *name,
- void *cb_data,
- filter_object_fn filter_fn,
- void *filter_data)
+ const char *name)
{
struct object *obj = &blob->object;
size_t pathlen;
enum list_objects_filter_result r = LOFR_MARK_SEEN | LOFR_DO_SHOW;
- if (!revs->blob_objects)
+ if (!ctx->revs->blob_objects)
return;
if (!obj)
die("bad blob object");
* may cause the actual filter to report an incomplete list
* of missing objects.
*/
- if (revs->exclude_promisor_objects &&
+ if (ctx->revs->exclude_promisor_objects &&
!has_object_file(&obj->oid) &&
is_promisor_object(&obj->oid))
return;
pathlen = path->len;
strbuf_addstr(path, name);
- if (!(obj->flags & USER_GIVEN) && filter_fn)
- r = filter_fn(LOFS_BLOB, obj,
- path->buf, &path->buf[pathlen],
- filter_data);
+ if ((obj->flags & NOT_USER_GIVEN) && ctx->filter_fn)
+ r = ctx->filter_fn(LOFS_BLOB, obj,
+ path->buf, &path->buf[pathlen],
+ ctx->filter_data);
if (r & LOFR_MARK_SEEN)
obj->flags |= SEEN;
if (r & LOFR_DO_SHOW)
- show(obj, path->buf, cb_data);
+ ctx->show_object(obj, path->buf, ctx->show_data);
strbuf_setlen(path, pathlen);
}
* the link, and how to do it. Whether it necessarily makes
* any sense what-so-ever to ever do that is another issue.
*/
-static void process_gitlink(struct rev_info *revs,
+static void process_gitlink(struct traversal_context *ctx,
const unsigned char *sha1,
- show_object_fn show,
struct strbuf *path,
- const char *name,
- void *cb_data)
+ const char *name)
{
/* Nothing to do */
}
-static void process_tree(struct rev_info *revs,
+static void process_tree(struct traversal_context *ctx,
struct tree *tree,
- show_object_fn show,
struct strbuf *base,
- const char *name,
- void *cb_data,
- filter_object_fn filter_fn,
- void *filter_data)
+ const char *name);
+
+static void process_tree_contents(struct traversal_context *ctx,
+ struct tree *tree,
+ struct strbuf *base)
{
- struct object *obj = &tree->object;
struct tree_desc desc;
struct name_entry entry;
- enum interesting match = revs->diffopt.pathspec.nr == 0 ?
- all_entries_interesting: entry_not_interesting;
+ enum interesting match = ctx->revs->diffopt.pathspec.nr == 0 ?
+ all_entries_interesting : entry_not_interesting;
+
+ init_tree_desc(&desc, tree->buffer, tree->size);
+
+ while (tree_entry(&desc, &entry)) {
+ if (match != all_entries_interesting) {
+ match = tree_entry_interesting(&entry, base, 0,
+ &ctx->revs->diffopt.pathspec);
+ if (match == all_entries_not_interesting)
+ break;
+ if (match == entry_not_interesting)
+ continue;
+ }
+
+ if (S_ISDIR(entry.mode)) {
+ struct tree *t = lookup_tree(the_repository, 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,
+ base, entry.path);
+ else {
+ struct blob *b = lookup_blob(the_repository, entry.oid);
+ b->object.flags |= NOT_USER_GIVEN;
+ process_blob(ctx, b, base, entry.path);
+ }
+ }
+}
+
+static void process_tree(struct traversal_context *ctx,
+ struct tree *tree,
+ struct strbuf *base,
+ const char *name)
+{
+ struct object *obj = &tree->object;
+ struct rev_info *revs = ctx->revs;
int baselen = base->len;
enum list_objects_filter_result r = LOFR_MARK_SEEN | LOFR_DO_SHOW;
- int gently = revs->ignore_missing_links ||
- revs->exclude_promisor_objects;
+ int failed_parse;
if (!revs->tree_objects)
return;
die("bad tree object");
if (obj->flags & (UNINTERESTING | SEEN))
return;
- if (parse_tree_gently(tree, gently) < 0) {
+
+ failed_parse = parse_tree_gently(tree, 1);
+ if (failed_parse) {
if (revs->ignore_missing_links)
return;
is_promisor_object(&obj->oid))
return;
- die("bad tree object %s", oid_to_hex(&obj->oid));
+ if (!revs->do_not_die_on_missing_tree)
+ die("bad tree object %s", oid_to_hex(&obj->oid));
}
strbuf_addstr(base, name);
- if (!(obj->flags & USER_GIVEN) && filter_fn)
- r = filter_fn(LOFS_BEGIN_TREE, obj,
- base->buf, &base->buf[baselen],
- filter_data);
+ if ((obj->flags & NOT_USER_GIVEN) && ctx->filter_fn)
+ r = ctx->filter_fn(LOFS_BEGIN_TREE, obj,
+ base->buf, &base->buf[baselen],
+ ctx->filter_data);
if (r & LOFR_MARK_SEEN)
obj->flags |= SEEN;
if (r & LOFR_DO_SHOW)
- show(obj, base->buf, cb_data);
+ ctx->show_object(obj, base->buf, ctx->show_data);
if (base->len)
strbuf_addch(base, '/');
- init_tree_desc(&desc, tree->buffer, tree->size);
+ if (r & LOFR_SKIP_TREE)
+ trace_printf("Skipping contents of tree %s...\n", base->buf);
+ else if (!failed_parse)
+ process_tree_contents(ctx, tree, base);
- while (tree_entry(&desc, &entry)) {
- if (match != all_entries_interesting) {
- match = tree_entry_interesting(&entry, base, 0,
- &revs->diffopt.pathspec);
- if (match == all_entries_not_interesting)
- break;
- if (match == entry_not_interesting)
- continue;
- }
-
- if (S_ISDIR(entry.mode))
- process_tree(revs,
- lookup_tree(the_repository, entry.oid),
- show, base, entry.path,
- cb_data, filter_fn, filter_data);
- else if (S_ISGITLINK(entry.mode))
- process_gitlink(revs, entry.oid->hash,
- show, base, entry.path,
- cb_data);
- else
- process_blob(revs,
- lookup_blob(the_repository, entry.oid),
- show, base, entry.path,
- cb_data, filter_fn, filter_data);
- }
-
- if (!(obj->flags & USER_GIVEN) && filter_fn) {
- r = filter_fn(LOFS_END_TREE, obj,
- base->buf, &base->buf[baselen],
- filter_data);
+ if ((obj->flags & NOT_USER_GIVEN) && ctx->filter_fn) {
+ r = ctx->filter_fn(LOFS_END_TREE, obj,
+ base->buf, &base->buf[baselen],
+ ctx->filter_data);
if (r & LOFR_MARK_SEEN)
obj->flags |= SEEN;
if (r & LOFR_DO_SHOW)
- show(obj, base->buf, cb_data);
+ ctx->show_object(obj, base->buf, ctx->show_data);
}
strbuf_setlen(base, baselen);
struct commit *parent = parents->item;
if (!(parent->object.flags & UNINTERESTING))
continue;
- mark_tree_uninteresting(get_commit_tree(parent));
+ mark_tree_uninteresting(revs->repo, get_commit_tree(parent));
if (revs->edge_hint && !(parent->object.flags & SHOWN)) {
parent->object.flags |= SHOWN;
show_edge(parent);
struct commit *commit = list->item;
if (commit->object.flags & UNINTERESTING) {
- mark_tree_uninteresting(get_commit_tree(commit));
+ mark_tree_uninteresting(revs->repo,
+ get_commit_tree(commit));
if (revs->edge_hint_aggressive && !(commit->object.flags & SHOWN)) {
commit->object.flags |= SHOWN;
show_edge(commit);
struct commit *commit = (struct commit *)obj;
if (obj->type != OBJ_COMMIT || !(obj->flags & UNINTERESTING))
continue;
- mark_tree_uninteresting(get_commit_tree(commit));
+ mark_tree_uninteresting(revs->repo,
+ get_commit_tree(commit));
if (!(obj->flags & SHOWN)) {
obj->flags |= SHOWN;
show_edge(commit);
add_pending_object(revs, &tree->object, "");
}
-static void traverse_trees_and_blobs(struct rev_info *revs,
- struct strbuf *base,
- show_object_fn show_object,
- void *show_data,
- filter_object_fn filter_fn,
- void *filter_data)
+static void traverse_trees_and_blobs(struct traversal_context *ctx,
+ struct strbuf *base)
{
int i;
assert(base->len == 0);
- for (i = 0; i < revs->pending.nr; i++) {
- struct object_array_entry *pending = revs->pending.objects + i;
+ for (i = 0; i < ctx->revs->pending.nr; i++) {
+ struct object_array_entry *pending = ctx->revs->pending.objects + i;
struct object *obj = pending->item;
const char *name = pending->name;
const char *path = pending->path;
continue;
if (obj->type == OBJ_TAG) {
obj->flags |= SEEN;
- show_object(obj, name, show_data);
+ ctx->show_object(obj, name, ctx->show_data);
continue;
}
if (!path)
path = "";
if (obj->type == OBJ_TREE) {
- process_tree(revs, (struct tree *)obj, show_object,
- base, path, show_data,
- filter_fn, filter_data);
+ process_tree(ctx, (struct tree *)obj, base, path);
continue;
}
if (obj->type == OBJ_BLOB) {
- process_blob(revs, (struct blob *)obj, show_object,
- base, path, show_data,
- filter_fn, filter_data);
+ process_blob(ctx, (struct blob *)obj, base, path);
continue;
}
die("unknown pending object %s (%s)",
oid_to_hex(&obj->oid), name);
}
- object_array_clear(&revs->pending);
+ object_array_clear(&ctx->revs->pending);
}
-static void do_traverse(struct rev_info *revs,
- show_commit_fn show_commit,
- show_object_fn show_object,
- void *show_data,
- filter_object_fn filter_fn,
- void *filter_data)
+static void do_traverse(struct traversal_context *ctx)
{
struct commit *commit;
struct strbuf csp; /* callee's scratch pad */
strbuf_init(&csp, PATH_MAX);
- while ((commit = get_revision(revs)) != NULL) {
+ while ((commit = get_revision(ctx->revs)) != NULL) {
/*
* an uninteresting boundary commit may not have its tree
* parsed yet, but we are not going to show them anyway
*/
- if (get_commit_tree(commit))
- add_pending_tree(revs, get_commit_tree(commit));
- show_commit(commit, show_data);
+ if (get_commit_tree(commit)) {
+ struct tree *tree = get_commit_tree(commit);
+ tree->object.flags |= NOT_USER_GIVEN;
+ add_pending_tree(ctx->revs, tree);
+ }
+ ctx->show_commit(commit, ctx->show_data);
- if (revs->tree_blobs_in_commit_order)
+ if (ctx->revs->tree_blobs_in_commit_order)
/*
* NEEDSWORK: Adding the tree and then flushing it here
* needs a reallocation for each commit. Can we pass the
* tree directory without allocation churn?
*/
- traverse_trees_and_blobs(revs, &csp,
- show_object, show_data,
- filter_fn, filter_data);
+ traverse_trees_and_blobs(ctx, &csp);
}
- traverse_trees_and_blobs(revs, &csp,
- show_object, show_data,
- filter_fn, filter_data);
+ traverse_trees_and_blobs(ctx, &csp);
strbuf_release(&csp);
}
show_object_fn show_object,
void *show_data)
{
- do_traverse(revs, show_commit, show_object, show_data, NULL, NULL);
+ struct traversal_context ctx;
+ ctx.revs = revs;
+ ctx.show_commit = show_commit;
+ ctx.show_object = show_object;
+ ctx.show_data = show_data;
+ ctx.filter_fn = NULL;
+ ctx.filter_data = NULL;
+ do_traverse(&ctx);
}
void traverse_commit_list_filtered(
void *show_data,
struct oidset *omitted)
{
- filter_object_fn filter_fn = NULL;
+ struct traversal_context ctx;
filter_free_fn filter_free_fn = NULL;
- void *filter_data = NULL;
-
- filter_data = list_objects_filter__init(omitted, filter_options,
- &filter_fn, &filter_free_fn);
- do_traverse(revs, show_commit, show_object, show_data,
- filter_fn, filter_data);
- if (filter_data && filter_free_fn)
- filter_free_fn(filter_data);
+
+ ctx.revs = revs;
+ ctx.show_object = show_object;
+ ctx.show_commit = show_commit;
+ ctx.show_data = show_data;
+ ctx.filter_fn = NULL;
+
+ ctx.filter_data = list_objects_filter__init(omitted, filter_options,
+ &ctx.filter_fn, &filter_free_fn);
+ do_traverse(&ctx);
+ if (ctx.filter_data && filter_free_fn)
+ filter_free_fn(ctx.filter_data);
}
return &ll_merge_drv[LL_TEXT_MERGE];
}
-static void normalize_file(mmfile_t *mm, const char *path)
+static void normalize_file(mmfile_t *mm, const char *path, struct index_state *istate)
{
struct strbuf strbuf = STRBUF_INIT;
- if (renormalize_buffer(&the_index, path, mm->ptr, mm->size, &strbuf)) {
+ if (renormalize_buffer(istate, path, mm->ptr, mm->size, &strbuf)) {
free(mm->ptr);
mm->size = strbuf.len;
mm->ptr = strbuf_detach(&strbuf, NULL);
mmfile_t *ancestor, const char *ancestor_label,
mmfile_t *ours, const char *our_label,
mmfile_t *theirs, const char *their_label,
+ struct index_state *istate,
const struct ll_merge_options *opts)
{
static struct attr_check *check;
opts = &default_opts;
if (opts->renormalize) {
- normalize_file(ancestor, path);
- normalize_file(ours, path);
- normalize_file(theirs, path);
+ normalize_file(ancestor, path, istate);
+ normalize_file(ours, path, istate);
+ normalize_file(theirs, path, istate);
}
if (!check)
check = attr_check_initl("merge", "conflict-marker-size", NULL);
- if (!git_check_attr(&the_index, path, check)) {
- ll_driver_name = check->items[0].value;
- if (check->items[1].value) {
- marker_size = atoi(check->items[1].value);
- if (marker_size <= 0)
- marker_size = DEFAULT_CONFLICT_MARKER_SIZE;
- }
+ git_check_attr(istate, path, check);
+ ll_driver_name = check->items[0].value;
+ if (check->items[1].value) {
+ marker_size = atoi(check->items[1].value);
+ if (marker_size <= 0)
+ marker_size = DEFAULT_CONFLICT_MARKER_SIZE;
}
driver = find_ll_merge_driver(ll_driver_name);
opts, marker_size);
}
-int ll_merge_marker_size(const char *path)
+int ll_merge_marker_size(struct index_state *istate, const char *path)
{
static struct attr_check *check;
int marker_size = DEFAULT_CONFLICT_MARKER_SIZE;
if (!check)
check = attr_check_initl("conflict-marker-size", NULL);
- if (!git_check_attr(&the_index, path, check) && check->items[0].value) {
+ git_check_attr(istate, path, check);
+ if (check->items[0].value) {
marker_size = atoi(check->items[0].value);
if (marker_size <= 0)
marker_size = DEFAULT_CONFLICT_MARKER_SIZE;
#include "xdiff/xdiff.h"
+struct index_state;
+
struct ll_merge_options {
unsigned virtual_ancestor : 1;
unsigned variant : 2; /* favor ours, favor theirs, or union merge */
mmfile_t *ancestor, const char *ancestor_label,
mmfile_t *ours, const char *our_label,
mmfile_t *theirs, const char *their_label,
+ struct index_state *istate,
const struct ll_merge_options *opts);
-int ll_merge_marker_size(const char *path);
+int ll_merge_marker_size(struct index_state *istate, const char *path);
#endif
* nobody else) to inspect the contents you wrote, while still
* holding the lock yourself.
*
- * * `reopen_lock_file()` to reopen the lockfile. Make further updates
- * to the contents.
+ * * `reopen_lock_file()` to reopen the lockfile, truncating the existing
+ * contents. Write out the new contents.
*
* * `commit_lock_file()` to make the final version permanent.
*/
#include "sequencer.h"
#include "line-log.h"
#include "help.h"
+#include "interdiff.h"
+#include "range-diff.h"
static struct decoration name_decoration = { "object names" };
static int decoration_loaded;
const struct commit_list *parent;
for (nth = 0, parent = commit->parents; parent; parent = parent->next) {
- if (!oidcmp(&parent->item->object.oid, oid))
+ if (oideq(&parent->item->object.oid, oid))
return nth;
nth++;
}
if (parse_tag_buffer(the_repository, tag, extra->value, extra->len))
strbuf_addstr(&verify_message, "malformed mergetag\n");
else if (is_common_merge(commit) &&
- !oidcmp(&tag->tagged->oid,
- &commit->parents->next->item->object.oid))
+ oideq(&tag->tagged->oid,
+ &commit->parents->next->item->object.oid))
strbuf_addf(&verify_message,
"merged tag '%s'\n", tag->tag);
else if ((nth = which_parent(&tag->tagged->oid, commit)) < 0)
return for_each_mergetag(show_one_mergetag, commit, opt);
}
+static void next_commentary_block(struct rev_info *opt, struct strbuf *sb)
+{
+ const char *x = opt->shown_dashes ? "\n" : "---\n";
+ if (sb)
+ strbuf_addstr(sb, x);
+ else
+ fputs(x, opt->diffopt.file);
+ opt->shown_dashes = 1;
+}
+
void show_log(struct rev_info *opt)
{
struct strbuf msgbuf = STRBUF_INIT;
if ((ctx.fmt != CMIT_FMT_USERFORMAT) &&
ctx.notes_message && *ctx.notes_message) {
- if (cmit_fmt_is_mail(ctx.fmt)) {
- strbuf_addstr(&msgbuf, "---\n");
- opt->shown_dashes = 1;
- }
+ if (cmit_fmt_is_mail(ctx.fmt))
+ next_commentary_block(opt, &msgbuf);
strbuf_addstr(&msgbuf, ctx.notes_message);
}
strbuf_release(&msgbuf);
free(ctx.notes_message);
+
+ if (cmit_fmt_is_mail(ctx.fmt) && opt->idiff_oid1) {
+ struct diff_queue_struct dq;
+
+ memcpy(&dq, &diff_queued_diff, sizeof(diff_queued_diff));
+ DIFF_QUEUE_CLEAR(&diff_queued_diff);
+
+ next_commentary_block(opt, NULL);
+ fprintf_ln(opt->diffopt.file, "%s", opt->idiff_title);
+ show_interdiff(opt, 2);
+
+ memcpy(&diff_queued_diff, &dq, sizeof(diff_queued_diff));
+ }
+
+ if (cmit_fmt_is_mail(ctx.fmt) && opt->rdiff1) {
+ struct diff_queue_struct dq;
+
+ memcpy(&dq, &diff_queued_diff, sizeof(diff_queued_diff));
+ DIFF_QUEUE_CLEAR(&diff_queued_diff);
+
+ next_commentary_block(opt, NULL);
+ fprintf_ln(opt->diffopt.file, "%s", opt->rdiff_title);
+ show_range_diff(opt->rdiff1, opt->rdiff2,
+ opt->creation_factor, 1, &opt->diffopt);
+
+ memcpy(&diff_queued_diff, &dq, sizeof(diff_queued_diff));
+ }
}
int log_tree_diff_flush(struct rev_info *opt)
/*
* We may have shown three-dashes line early
- * between notes and the log message, in which
- * case we only want a blank line after the
- * notes without (an extra) three-dashes line.
+ * between generated commentary (notes, etc.)
+ * and the log message, in which case we only
+ * want a blank line after the commentary
+ * without (an extra) three-dashes line.
* Otherwise, we show the three-dashes line if
* we are showing the patch with diffstat, but
* in that case, there is no extra blank line
return 1;
}
+static int has_attr_value(const char *line, const char *name, const char *value)
+{
+ struct strbuf sb = STRBUF_INIT;
+ int rc = slurp_attr(line, name, &sb) && !strcasecmp(sb.buf, value);
+ strbuf_release(&sb);
+ return rc;
+}
+
static void handle_content_type(struct mailinfo *mi, struct strbuf *line)
{
struct strbuf *boundary = xmalloc(sizeof(struct strbuf));
strbuf_init(boundary, line->len);
+ mi->format_flowed = has_attr_value(line->buf, "format=", "flowed");
+ mi->delsp = has_attr_value(line->buf, "delsp=", "yes");
+
if (slurp_attr(line->buf, "boundary=", boundary)) {
strbuf_insert(boundary, 0, "--", 2);
if (++mi->content_top >= &mi->content[MAX_BOUNDARIES]) {
return 1;
}
+static void handle_filter_flowed(struct mailinfo *mi, struct strbuf *line,
+ struct strbuf *prev)
+{
+ size_t len = line->len;
+ const char *rest;
+
+ if (!mi->format_flowed) {
+ handle_filter(mi, line);
+ return;
+ }
+
+ if (line->buf[len - 1] == '\n') {
+ len--;
+ if (len && line->buf[len - 1] == '\r')
+ len--;
+ }
+
+ /* Keep signature separator as-is. */
+ if (skip_prefix(line->buf, "-- ", &rest) && rest - line->buf == len) {
+ if (prev->len) {
+ handle_filter(mi, prev);
+ strbuf_reset(prev);
+ }
+ handle_filter(mi, line);
+ return;
+ }
+
+ /* Unstuff space-stuffed line. */
+ if (len && line->buf[0] == ' ') {
+ strbuf_remove(line, 0, 1);
+ len--;
+ }
+
+ /* Save flowed line for later, but without the soft line break. */
+ if (len && line->buf[len - 1] == ' ') {
+ strbuf_add(prev, line->buf, len - !!mi->delsp);
+ return;
+ }
+
+ /* Prepend any previous partial lines */
+ strbuf_insert(line, 0, prev->buf, prev->len);
+ strbuf_reset(prev);
+
+ handle_filter(mi, line);
+}
+
static void handle_body(struct mailinfo *mi, struct strbuf *line)
{
struct strbuf prev = STRBUF_INIT;
strbuf_addbuf(&prev, sb);
break;
}
- handle_filter(mi, sb);
+ handle_filter_flowed(mi, sb, &prev);
}
/*
* The partial chunk is saved in "prev" and will be
break;
}
default:
- handle_filter(mi, line);
+ handle_filter_flowed(mi, line, &prev);
}
if (mi->input_error)
break;
} while (!strbuf_getwholeline(line, mi->input, '\n'));
+ if (prev.len)
+ handle_filter(mi, &prev);
+
flush_inbody_header_accum(mi);
handle_body_out:
struct strbuf *content[MAX_BOUNDARIES];
struct strbuf **content_top;
struct strbuf charset;
+ unsigned int format_flowed:1;
+ unsigned int delsp:1;
char *message_id;
enum {
TE_DONTCARE, TE_QP, TE_BASE64
update_tree_entry(&two);
} else {
/* path appears in both */
- if (oidcmp(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,
free(f->ptr);
}
-static void *three_way_filemerge(const char *path, mmfile_t *base, mmfile_t *our, mmfile_t *their, unsigned long *size)
+static void *three_way_filemerge(struct index_state *istate,
+ const char *path,
+ mmfile_t *base,
+ mmfile_t *our,
+ mmfile_t *their,
+ unsigned long *size)
{
int merge_status;
mmbuffer_t res;
* common ancestor.
*/
merge_status = ll_merge(&res, path, base, NULL,
- our, ".our", their, ".their", NULL);
+ our, ".our", their, ".their",
+ istate, NULL);
if (merge_status < 0)
return NULL;
return res.ptr;
}
-void *merge_blobs(const char *path, struct blob *base, struct blob *our, struct blob *their, unsigned long *size)
+void *merge_blobs(struct index_state *istate, const char *path,
+ struct blob *base, struct blob *our,
+ struct blob *their, unsigned long *size)
{
void *res = NULL;
mmfile_t f1, f2, common;
common.ptr = xstrdup("");
common.size = 0;
}
- res = three_way_filemerge(path, &common, &f1, &f2, size);
+ res = three_way_filemerge(istate, path, &common, &f1, &f2, size);
free_mmfile(&common);
out_free_f2_f1:
free_mmfile(&f2);
#ifndef MERGE_BLOBS_H
#define MERGE_BLOBS_H
-#include "blob.h"
+struct blob;
+struct index_state;
-extern void *merge_blobs(const char *, struct blob *, struct blob *, struct blob *, unsigned long *);
+extern void *merge_blobs(struct index_state *, const char *,
+ struct blob *, struct blob *,
+ struct blob *, unsigned long *);
#endif /* MERGE_BLOBS_H */
#include "dir.h"
#include "submodule.h"
#include "revision.h"
+#include "commit-reach.h"
struct path_hashmap_entry {
struct hashmap_entry e;
shift_tree_by(&one->object.oid, &two->object.oid, &shifted,
subtree_shift);
}
- if (!oidcmp(&two->object.oid, &shifted))
+ if (oideq(&two->object.oid, &shifted))
return two;
return lookup_tree(the_repository, &shifted);
}
{
if (!a && !b)
return 2;
- return a && b && oidcmp(a, b) == 0;
+ return a && b && oideq(a, b);
}
enum rename_type {
read_mmblob(&src2, &b->oid);
merge_status = ll_merge(result_buf, a->path, &orig, base_name,
- &src1, name1, &src2, name2, &ll_opts);
+ &src1, name1, &src2, name2,
+ &the_index, &ll_opts);
free(base_name);
free(name1);
/* get all revisions that merge commit a */
xsnprintf(merged_revision, sizeof(merged_revision), "^%s",
oid_to_hex(&a->object.oid));
- init_revisions(&revs, NULL);
+ repo_init_revisions(the_repository, &revs, NULL);
rev_opts.submodule = path;
/* FIXME: can't handle linked worktrees in submodules yet */
revs.single_worktree = path != NULL;
return 0;
}
-static int merge_file_1(struct merge_options *o,
- const struct diff_filespec *one,
- const struct diff_filespec *a,
- const struct diff_filespec *b,
- const char *filename,
- const char *branch1,
- const char *branch2,
- struct merge_file_info *result)
+static int merge_mode_and_contents(struct merge_options *o,
+ const struct diff_filespec *one,
+ const struct diff_filespec *a,
+ const struct diff_filespec *b,
+ const char *filename,
+ const char *branch1,
+ const char *branch2,
+ struct merge_file_info *result)
{
result->merge = 0;
result->clean = 1;
return 0;
}
-static int merge_file_special_markers(struct merge_options *o,
- const struct diff_filespec *one,
- const struct diff_filespec *a,
- const struct diff_filespec *b,
- const char *target_filename,
- const char *branch1,
- const char *filename1,
- const char *branch2,
- const char *filename2,
- struct merge_file_info *mfi)
-{
- char *side1 = NULL;
- char *side2 = NULL;
- int ret;
-
- if (filename1)
- side1 = xstrfmt("%s:%s", branch1, filename1);
- if (filename2)
- side2 = xstrfmt("%s:%s", branch2, filename2);
-
- ret = merge_file_1(o, one, a, b, target_filename,
- side1 ? side1 : branch1,
- side2 ? side2 : branch2, mfi);
-
- free(side1);
- free(side2);
- return ret;
-}
-
-static int merge_file_one(struct merge_options *o,
- const char *path,
- const struct object_id *o_oid, int o_mode,
- const struct object_id *a_oid, int a_mode,
- const struct object_id *b_oid, int b_mode,
- const char *branch1,
- const char *branch2,
- struct merge_file_info *mfi)
-{
- struct diff_filespec one, a, b;
-
- one.path = a.path = b.path = (char *)path;
- oidcpy(&one.oid, o_oid);
- one.mode = o_mode;
- oidcpy(&a.oid, a_oid);
- a.mode = a_mode;
- oidcpy(&b.oid, b_oid);
- b.mode = b_mode;
- return merge_file_1(o, &one, &a, &b, path, branch1, branch2, mfi);
-}
-
static int handle_rename_via_dir(struct merge_options *o,
struct diff_filepair *pair,
const char *rename_branch,
struct merge_file_info mfi;
struct diff_filespec other;
struct diff_filespec *add;
- if (merge_file_one(o, one->path,
- &one->oid, one->mode,
- &a->oid, a->mode,
- &b->oid, b->mode,
- ci->branch1, ci->branch2, &mfi))
+ if (merge_mode_and_contents(o, one, a, b, one->path,
+ ci->branch1, ci->branch2, &mfi))
return -1;
/*
path_side_1_desc = xstrfmt("%s (was %s)", path, a->path);
path_side_2_desc = xstrfmt("%s (was %s)", path, b->path);
- if (merge_file_special_markers(o, a, c1, &ci->ren1_other,
- path_side_1_desc,
- o->branch1, c1->path,
- o->branch2, ci->ren1_other.path, &mfi_c1) ||
- merge_file_special_markers(o, b, &ci->ren2_other, c2,
- path_side_2_desc,
- o->branch1, ci->ren2_other.path,
- o->branch2, c2->path, &mfi_c2))
+ if (merge_mode_and_contents(o, a, c1, &ci->ren1_other, path_side_1_desc,
+ o->branch1, o->branch2, &mfi_c1) ||
+ merge_mode_and_contents(o, b, &ci->ren2_other, c2, path_side_2_desc,
+ o->branch1, o->branch2, &mfi_c2))
return -1;
free(path_side_1_desc);
free(path_side_2_desc);
struct diff_queue_struct *ret;
struct diff_options opts;
- diff_setup(&opts);
+ repo_diff_setup(the_repository, &opts);
opts.flags.recursive = 1;
opts.flags.rename_empty = 0;
opts.detect_rename = merge_detect_rename(o);
{
char *temp = xstrdup(path);
char *end;
- struct dir_rename_entry *entry = NULL;;
+ struct dir_rename_entry *entry = NULL;
while ((end = strrchr(temp, '/'))) {
*end = '\0';
ren1_dst, branch2);
if (o->call_depth) {
struct merge_file_info mfi;
- if (merge_file_one(o, ren1_dst, &null_oid, 0,
- &ren1->pair->two->oid,
- ren1->pair->two->mode,
- &dst_other.oid,
- dst_other.mode,
- branch1, branch2, &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;
}
_("modify"), _("modified"));
}
-static int merge_content(struct merge_options *o,
- const char *path,
- int is_dirty,
- struct object_id *o_oid, int o_mode,
- struct object_id *a_oid, int a_mode,
- struct object_id *b_oid, int b_mode,
- struct rename_conflict_info *rename_conflict_info)
+static int handle_content_merge(struct merge_options *o,
+ const char *path,
+ int is_dirty,
+ struct object_id *o_oid, int o_mode,
+ struct object_id *a_oid, int a_mode,
+ struct object_id *b_oid, int b_mode,
+ struct rename_conflict_info *rename_conflict_info)
{
const char *reason = _("content");
const char *path1 = NULL, *path2 = NULL;
path2 = (rename_conflict_info->pair2 ||
o->branch2 == rename_conflict_info->branch1) ?
pair1->two->path : pair1->one->path;
+ one.path = pair1->one->path;
+ a.path = (char *)path1;
+ b.path = (char *)path2;
if (dir_in_way(path, !o->call_depth,
S_ISGITLINK(pair1->two->mode)))
df_conflict_remains = 1;
}
- if (merge_file_special_markers(o, &one, &a, &b, path,
- o->branch1, path1,
- o->branch2, path2, &mfi))
+ if (merge_mode_and_contents(o, &one, &a, &b, path,
+ o->branch1, o->branch2, &mfi))
return -1;
/*
struct rename_conflict_info *ci)
{
/* Merge the content and write it out */
- return merge_content(o, path, was_dirty(o, path),
- o_oid, o_mode, a_oid, a_mode, b_oid, b_mode,
- ci);
+ return handle_content_merge(o, path, was_dirty(o, path),
+ o_oid, o_mode, a_oid, a_mode, b_oid, b_mode,
+ ci);
}
/* Per entry merge function */
/* 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 = merge_content(o, path, is_dirty,
- o_oid, o_mode, a_oid, a_mode, b_oid, b_mode,
- NULL);
+ 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
return oid_to_hex(commit ? &commit->object.oid : the_hash_algo->empty_tree);
}
-int try_merge_command(const char *strategy, size_t xopts_nr,
+int try_merge_command(struct repository *r,
+ const char *strategy, size_t xopts_nr,
const char **xopts, struct commit_list *common,
const char *head_arg, struct commit_list *remotes)
{
ret = run_command_v_opt(args.argv, RUN_GIT_CMD);
argv_array_clear(&args);
- discard_cache();
- if (read_cache() < 0)
+ discard_index(r->index);
+ if (read_index(r->index) < 0)
die(_("failed to read the cache"));
- resolve_undo_clear();
+ resolve_undo_clear_index(r->index);
return ret;
}
-int checkout_fast_forward(const struct object_id *head,
+int checkout_fast_forward(struct repository *r,
+ const struct object_id *head,
const struct object_id *remote,
int overwrite_ignore)
{
struct dir_struct dir;
struct lock_file lock_file = LOCK_INIT;
- refresh_cache(REFRESH_QUIET);
+ refresh_index(r->index, REFRESH_QUIET, NULL, NULL, NULL);
if (hold_locked_index(&lock_file, LOCK_REPORT_ON_ERROR) < 0)
return -1;
}
opts.head_idx = 1;
- opts.src_index = &the_index;
- opts.dst_index = &the_index;
+ opts.src_index = r->index;
+ opts.dst_index = r->index;
opts.update = 1;
opts.verbose_update = 1;
opts.merge = 1;
}
clear_unpack_trees_porcelain(&opts);
- if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
+ if (write_locked_index(r->index, &lock_file, COMMIT_LOCK))
return error(_("unable to write new index file"));
return 0;
}
--- /dev/null
+#include "cache.h"
+#include "config.h"
+#include "csum-file.h"
+#include "dir.h"
+#include "lockfile.h"
+#include "packfile.h"
+#include "object-store.h"
+#include "sha1-lookup.h"
+#include "midx.h"
+#include "progress.h"
+
+#define MIDX_SIGNATURE 0x4d494458 /* "MIDX" */
+#define MIDX_VERSION 1
+#define MIDX_BYTE_FILE_VERSION 4
+#define MIDX_BYTE_HASH_VERSION 5
+#define MIDX_BYTE_NUM_CHUNKS 6
+#define MIDX_BYTE_NUM_PACKS 8
+#define MIDX_HASH_VERSION 1
+#define MIDX_HEADER_SIZE 12
+#define MIDX_HASH_LEN 20
+#define MIDX_MIN_SIZE (MIDX_HEADER_SIZE + MIDX_HASH_LEN)
+
+#define MIDX_MAX_CHUNKS 5
+#define MIDX_CHUNK_ALIGNMENT 4
+#define MIDX_CHUNKID_PACKNAMES 0x504e414d /* "PNAM" */
+#define MIDX_CHUNKID_OIDFANOUT 0x4f494446 /* "OIDF" */
+#define MIDX_CHUNKID_OIDLOOKUP 0x4f49444c /* "OIDL" */
+#define MIDX_CHUNKID_OBJECTOFFSETS 0x4f4f4646 /* "OOFF" */
+#define MIDX_CHUNKID_LARGEOFFSETS 0x4c4f4646 /* "LOFF" */
+#define MIDX_CHUNKLOOKUP_WIDTH (sizeof(uint32_t) + sizeof(uint64_t))
+#define MIDX_CHUNK_FANOUT_SIZE (sizeof(uint32_t) * 256)
+#define MIDX_CHUNK_OFFSET_WIDTH (2 * sizeof(uint32_t))
+#define MIDX_CHUNK_LARGE_OFFSET_WIDTH (sizeof(uint64_t))
+#define MIDX_LARGE_OFFSET_NEEDED 0x80000000
+
+static char *get_midx_filename(const char *object_dir)
+{
+ return xstrfmt("%s/pack/multi-pack-index", object_dir);
+}
+
+struct multi_pack_index *load_multi_pack_index(const char *object_dir, int local)
+{
+ struct multi_pack_index *m = NULL;
+ int fd;
+ struct stat st;
+ size_t midx_size;
+ void *midx_map = NULL;
+ uint32_t hash_version;
+ char *midx_name = get_midx_filename(object_dir);
+ uint32_t i;
+ const char *cur_pack_name;
+
+ fd = git_open(midx_name);
+
+ if (fd < 0)
+ goto cleanup_fail;
+ if (fstat(fd, &st)) {
+ error_errno(_("failed to read %s"), midx_name);
+ goto cleanup_fail;
+ }
+
+ midx_size = xsize_t(st.st_size);
+
+ if (midx_size < MIDX_MIN_SIZE) {
+ error(_("multi-pack-index file %s is too small"), midx_name);
+ goto cleanup_fail;
+ }
+
+ FREE_AND_NULL(midx_name);
+
+ midx_map = xmmap(NULL, midx_size, PROT_READ, MAP_PRIVATE, fd, 0);
+
+ FLEX_ALLOC_MEM(m, object_dir, object_dir, strlen(object_dir));
+ m->fd = fd;
+ m->data = midx_map;
+ m->data_len = midx_size;
+ m->local = local;
+
+ m->signature = get_be32(m->data);
+ if (m->signature != MIDX_SIGNATURE)
+ die(_("multi-pack-index signature 0x%08x does not match signature 0x%08x"),
+ m->signature, MIDX_SIGNATURE);
+
+ m->version = m->data[MIDX_BYTE_FILE_VERSION];
+ if (m->version != MIDX_VERSION)
+ die(_("multi-pack-index version %d not recognized"),
+ m->version);
+
+ hash_version = m->data[MIDX_BYTE_HASH_VERSION];
+ if (hash_version != MIDX_HASH_VERSION)
+ die(_("hash version %u does not match"), hash_version);
+ m->hash_len = MIDX_HASH_LEN;
+
+ m->num_chunks = m->data[MIDX_BYTE_NUM_CHUNKS];
+
+ m->num_packs = get_be32(m->data + MIDX_BYTE_NUM_PACKS);
+
+ for (i = 0; i < m->num_chunks; i++) {
+ uint32_t chunk_id = get_be32(m->data + MIDX_HEADER_SIZE +
+ MIDX_CHUNKLOOKUP_WIDTH * i);
+ uint64_t chunk_offset = get_be64(m->data + MIDX_HEADER_SIZE + 4 +
+ MIDX_CHUNKLOOKUP_WIDTH * i);
+
+ if (chunk_offset >= m->data_len)
+ die(_("invalid chunk offset (too large)"));
+
+ switch (chunk_id) {
+ case MIDX_CHUNKID_PACKNAMES:
+ m->chunk_pack_names = m->data + chunk_offset;
+ break;
+
+ case MIDX_CHUNKID_OIDFANOUT:
+ m->chunk_oid_fanout = (uint32_t *)(m->data + chunk_offset);
+ break;
+
+ case MIDX_CHUNKID_OIDLOOKUP:
+ m->chunk_oid_lookup = m->data + chunk_offset;
+ break;
+
+ case MIDX_CHUNKID_OBJECTOFFSETS:
+ m->chunk_object_offsets = m->data + chunk_offset;
+ break;
+
+ case MIDX_CHUNKID_LARGEOFFSETS:
+ m->chunk_large_offsets = m->data + chunk_offset;
+ break;
+
+ case 0:
+ die(_("terminating multi-pack-index chunk id appears earlier than expected"));
+ break;
+
+ default:
+ /*
+ * Do nothing on unrecognized chunks, allowing future
+ * extensions to add optional chunks.
+ */
+ break;
+ }
+ }
+
+ if (!m->chunk_pack_names)
+ die(_("multi-pack-index missing required pack-name chunk"));
+ if (!m->chunk_oid_fanout)
+ die(_("multi-pack-index missing required OID fanout chunk"));
+ if (!m->chunk_oid_lookup)
+ die(_("multi-pack-index missing required OID lookup chunk"));
+ if (!m->chunk_object_offsets)
+ die(_("multi-pack-index missing required object offsets chunk"));
+
+ m->num_objects = ntohl(m->chunk_oid_fanout[255]);
+
+ m->pack_names = xcalloc(m->num_packs, sizeof(*m->pack_names));
+ m->packs = xcalloc(m->num_packs, sizeof(*m->packs));
+
+ cur_pack_name = (const char *)m->chunk_pack_names;
+ for (i = 0; i < m->num_packs; i++) {
+ m->pack_names[i] = cur_pack_name;
+
+ cur_pack_name += strlen(cur_pack_name) + 1;
+
+ if (i && strcmp(m->pack_names[i], m->pack_names[i - 1]) <= 0)
+ die(_("multi-pack-index pack names out of order: '%s' before '%s'"),
+ m->pack_names[i - 1],
+ m->pack_names[i]);
+ }
+
+ return m;
+
+cleanup_fail:
+ free(m);
+ free(midx_name);
+ if (midx_map)
+ munmap(midx_map, midx_size);
+ if (0 <= fd)
+ close(fd);
+ return NULL;
+}
+
+static void close_midx(struct multi_pack_index *m)
+{
+ uint32_t i;
+ munmap((unsigned char *)m->data, m->data_len);
+ close(m->fd);
+ m->fd = -1;
+
+ for (i = 0; i < m->num_packs; i++) {
+ if (m->packs[i]) {
+ close_pack(m->packs[i]);
+ free(m->packs);
+ }
+ }
+ FREE_AND_NULL(m->packs);
+ FREE_AND_NULL(m->pack_names);
+}
+
+int prepare_midx_pack(struct multi_pack_index *m, uint32_t pack_int_id)
+{
+ struct strbuf pack_name = STRBUF_INIT;
+
+ if (pack_int_id >= m->num_packs)
+ die(_("bad pack-int-id: %u (%u total packs"),
+ pack_int_id, m->num_packs);
+
+ if (m->packs[pack_int_id])
+ return 0;
+
+ strbuf_addf(&pack_name, "%s/pack/%s", m->object_dir,
+ m->pack_names[pack_int_id]);
+
+ m->packs[pack_int_id] = add_packed_git(pack_name.buf, pack_name.len, m->local);
+ strbuf_release(&pack_name);
+ return !m->packs[pack_int_id];
+}
+
+int bsearch_midx(const struct object_id *oid, struct multi_pack_index *m, uint32_t *result)
+{
+ return bsearch_hash(oid->hash, m->chunk_oid_fanout, m->chunk_oid_lookup,
+ MIDX_HASH_LEN, result);
+}
+
+struct object_id *nth_midxed_object_oid(struct object_id *oid,
+ struct multi_pack_index *m,
+ uint32_t n)
+{
+ if (n >= m->num_objects)
+ return NULL;
+
+ hashcpy(oid->hash, m->chunk_oid_lookup + m->hash_len * n);
+ return oid;
+}
+
+static off_t nth_midxed_offset(struct multi_pack_index *m, uint32_t pos)
+{
+ const unsigned char *offset_data;
+ uint32_t offset32;
+
+ offset_data = m->chunk_object_offsets + pos * MIDX_CHUNK_OFFSET_WIDTH;
+ offset32 = get_be32(offset_data + sizeof(uint32_t));
+
+ if (m->chunk_large_offsets && offset32 & MIDX_LARGE_OFFSET_NEEDED) {
+ if (sizeof(off_t) < sizeof(uint64_t))
+ die(_("multi-pack-index stores a 64-bit offset, but off_t is too small"));
+
+ offset32 ^= MIDX_LARGE_OFFSET_NEEDED;
+ return get_be64(m->chunk_large_offsets + sizeof(uint64_t) * offset32);
+ }
+
+ return offset32;
+}
+
+static uint32_t nth_midxed_pack_int_id(struct multi_pack_index *m, uint32_t pos)
+{
+ return get_be32(m->chunk_object_offsets + pos * MIDX_CHUNK_OFFSET_WIDTH);
+}
+
+static int nth_midxed_pack_entry(struct multi_pack_index *m, struct pack_entry *e, uint32_t pos)
+{
+ uint32_t pack_int_id;
+ struct packed_git *p;
+
+ if (pos >= m->num_objects)
+ return 0;
+
+ pack_int_id = nth_midxed_pack_int_id(m, pos);
+
+ if (prepare_midx_pack(m, pack_int_id))
+ die(_("error preparing packfile from multi-pack-index"));
+ p = m->packs[pack_int_id];
+
+ /*
+ * We are about to tell the caller where they can locate the
+ * requested object. We better make sure the packfile is
+ * still here and can be accessed before supplying that
+ * answer, as it may have been deleted since the MIDX was
+ * loaded!
+ */
+ if (!is_pack_valid(p))
+ return 0;
+
+ if (p->num_bad_objects) {
+ uint32_t i;
+ struct object_id oid;
+ nth_midxed_object_oid(&oid, m, pos);
+ for (i = 0; i < p->num_bad_objects; i++)
+ if (hasheq(oid.hash,
+ p->bad_object_sha1 + the_hash_algo->rawsz * i))
+ return 0;
+ }
+
+ e->offset = nth_midxed_offset(m, pos);
+ e->p = p;
+
+ return 1;
+}
+
+int fill_midx_entry(const struct object_id *oid, struct pack_entry *e, struct multi_pack_index *m)
+{
+ uint32_t pos;
+
+ if (!bsearch_midx(oid, m, &pos))
+ return 0;
+
+ return nth_midxed_pack_entry(m, e, pos);
+}
+
+int midx_contains_pack(struct multi_pack_index *m, const char *idx_name)
+{
+ uint32_t first = 0, last = m->num_packs;
+
+ while (first < last) {
+ uint32_t mid = first + (last - first) / 2;
+ const char *current;
+ int cmp;
+
+ current = m->pack_names[mid];
+ cmp = strcmp(idx_name, current);
+ if (!cmp)
+ return 1;
+ if (cmp > 0) {
+ first = mid + 1;
+ continue;
+ }
+ last = mid;
+ }
+
+ return 0;
+}
+
+int prepare_multi_pack_index_one(struct repository *r, const char *object_dir, int local)
+{
+ struct multi_pack_index *m;
+ struct multi_pack_index *m_search;
+ int config_value;
+
+ if (repo_config_get_bool(r, "core.multipackindex", &config_value) ||
+ !config_value)
+ return 0;
+
+ for (m_search = r->objects->multi_pack_index; m_search; m_search = m_search->next)
+ if (!strcmp(object_dir, m_search->object_dir))
+ return 1;
+
+ m = load_multi_pack_index(object_dir, local);
+
+ if (m) {
+ m->next = r->objects->multi_pack_index;
+ r->objects->multi_pack_index = m;
+ return 1;
+ }
+
+ return 0;
+}
+
+static size_t write_midx_header(struct hashfile *f,
+ unsigned char num_chunks,
+ uint32_t num_packs)
+{
+ unsigned char byte_values[4];
+
+ hashwrite_be32(f, MIDX_SIGNATURE);
+ byte_values[0] = MIDX_VERSION;
+ byte_values[1] = MIDX_HASH_VERSION;
+ byte_values[2] = num_chunks;
+ byte_values[3] = 0; /* unused */
+ hashwrite(f, byte_values, sizeof(byte_values));
+ hashwrite_be32(f, num_packs);
+
+ return MIDX_HEADER_SIZE;
+}
+
+struct pack_list {
+ struct packed_git **list;
+ char **names;
+ uint32_t nr;
+ uint32_t alloc_list;
+ uint32_t alloc_names;
+ size_t pack_name_concat_len;
+ struct multi_pack_index *m;
+};
+
+static void add_pack_to_midx(const char *full_path, size_t full_path_len,
+ const char *file_name, void *data)
+{
+ struct pack_list *packs = (struct pack_list *)data;
+
+ if (ends_with(file_name, ".idx")) {
+ if (packs->m && midx_contains_pack(packs->m, file_name))
+ return;
+
+ ALLOC_GROW(packs->list, packs->nr + 1, packs->alloc_list);
+ ALLOC_GROW(packs->names, packs->nr + 1, packs->alloc_names);
+
+ packs->list[packs->nr] = add_packed_git(full_path,
+ full_path_len,
+ 0);
+
+ if (!packs->list[packs->nr]) {
+ warning(_("failed to add packfile '%s'"),
+ full_path);
+ return;
+ }
+
+ if (open_pack_index(packs->list[packs->nr])) {
+ warning(_("failed to open pack-index '%s'"),
+ full_path);
+ close_pack(packs->list[packs->nr]);
+ FREE_AND_NULL(packs->list[packs->nr]);
+ return;
+ }
+
+ packs->names[packs->nr] = xstrdup(file_name);
+ packs->pack_name_concat_len += strlen(file_name) + 1;
+ packs->nr++;
+ }
+}
+
+struct pack_pair {
+ uint32_t pack_int_id;
+ char *pack_name;
+};
+
+static int pack_pair_compare(const void *_a, const void *_b)
+{
+ struct pack_pair *a = (struct pack_pair *)_a;
+ struct pack_pair *b = (struct pack_pair *)_b;
+ return strcmp(a->pack_name, b->pack_name);
+}
+
+static void sort_packs_by_name(char **pack_names, uint32_t nr_packs, uint32_t *perm)
+{
+ uint32_t i;
+ struct pack_pair *pairs;
+
+ ALLOC_ARRAY(pairs, nr_packs);
+
+ for (i = 0; i < nr_packs; i++) {
+ pairs[i].pack_int_id = i;
+ pairs[i].pack_name = pack_names[i];
+ }
+
+ QSORT(pairs, nr_packs, pack_pair_compare);
+
+ for (i = 0; i < nr_packs; i++) {
+ pack_names[i] = pairs[i].pack_name;
+ perm[pairs[i].pack_int_id] = i;
+ }
+
+ free(pairs);
+}
+
+struct pack_midx_entry {
+ struct object_id oid;
+ uint32_t pack_int_id;
+ time_t pack_mtime;
+ uint64_t offset;
+};
+
+static int midx_oid_compare(const void *_a, const void *_b)
+{
+ const struct pack_midx_entry *a = (const struct pack_midx_entry *)_a;
+ const struct pack_midx_entry *b = (const struct pack_midx_entry *)_b;
+ int cmp = oidcmp(&a->oid, &b->oid);
+
+ if (cmp)
+ return cmp;
+
+ if (a->pack_mtime > b->pack_mtime)
+ return -1;
+ else if (a->pack_mtime < b->pack_mtime)
+ return 1;
+
+ return a->pack_int_id - b->pack_int_id;
+}
+
+static int nth_midxed_pack_midx_entry(struct multi_pack_index *m,
+ uint32_t *pack_perm,
+ struct pack_midx_entry *e,
+ uint32_t pos)
+{
+ if (pos >= m->num_objects)
+ return 1;
+
+ nth_midxed_object_oid(&e->oid, m, pos);
+ e->pack_int_id = pack_perm[nth_midxed_pack_int_id(m, pos)];
+ e->offset = nth_midxed_offset(m, pos);
+
+ /* consider objects in midx to be from "old" packs */
+ e->pack_mtime = 0;
+ return 0;
+}
+
+static void fill_pack_entry(uint32_t pack_int_id,
+ struct packed_git *p,
+ uint32_t cur_object,
+ struct pack_midx_entry *entry)
+{
+ if (!nth_packed_object_oid(&entry->oid, p, cur_object))
+ die(_("failed to locate object %d in packfile"), cur_object);
+
+ entry->pack_int_id = pack_int_id;
+ entry->pack_mtime = p->mtime;
+
+ entry->offset = nth_packed_object_offset(p, cur_object);
+}
+
+/*
+ * It is possible to artificially get into a state where there are many
+ * duplicate copies of objects. That can create high memory pressure if
+ * we are to create a list of all objects before de-duplication. To reduce
+ * this memory pressure without a significant performance drop, automatically
+ * group objects by the first byte of their object id. Use the IDX fanout
+ * tables to group the data, copy to a local array, then sort.
+ *
+ * Copy only the de-duplicated entries (selected by most-recent modified time
+ * of a packfile containing the object).
+ */
+static struct pack_midx_entry *get_sorted_entries(struct multi_pack_index *m,
+ struct packed_git **p,
+ uint32_t *perm,
+ uint32_t nr_packs,
+ uint32_t *nr_objects)
+{
+ uint32_t cur_fanout, cur_pack, cur_object;
+ uint32_t alloc_fanout, alloc_objects, total_objects = 0;
+ struct pack_midx_entry *entries_by_fanout = NULL;
+ struct pack_midx_entry *deduplicated_entries = NULL;
+ uint32_t start_pack = m ? m->num_packs : 0;
+
+ for (cur_pack = start_pack; cur_pack < nr_packs; cur_pack++)
+ total_objects += p[cur_pack]->num_objects;
+
+ /*
+ * As we de-duplicate by fanout value, we expect the fanout
+ * slices to be evenly distributed, with some noise. Hence,
+ * allocate slightly more than one 256th.
+ */
+ alloc_objects = alloc_fanout = total_objects > 3200 ? total_objects / 200 : 16;
+
+ ALLOC_ARRAY(entries_by_fanout, alloc_fanout);
+ ALLOC_ARRAY(deduplicated_entries, alloc_objects);
+ *nr_objects = 0;
+
+ for (cur_fanout = 0; cur_fanout < 256; cur_fanout++) {
+ uint32_t nr_fanout = 0;
+
+ if (m) {
+ uint32_t start = 0, end;
+
+ if (cur_fanout)
+ start = ntohl(m->chunk_oid_fanout[cur_fanout - 1]);
+ end = ntohl(m->chunk_oid_fanout[cur_fanout]);
+
+ for (cur_object = start; cur_object < end; cur_object++) {
+ ALLOC_GROW(entries_by_fanout, nr_fanout + 1, alloc_fanout);
+ nth_midxed_pack_midx_entry(m, perm,
+ &entries_by_fanout[nr_fanout],
+ cur_object);
+ nr_fanout++;
+ }
+ }
+
+ for (cur_pack = start_pack; cur_pack < nr_packs; cur_pack++) {
+ uint32_t start = 0, end;
+
+ if (cur_fanout)
+ start = get_pack_fanout(p[cur_pack], cur_fanout - 1);
+ end = get_pack_fanout(p[cur_pack], cur_fanout);
+
+ for (cur_object = start; cur_object < end; cur_object++) {
+ ALLOC_GROW(entries_by_fanout, nr_fanout + 1, alloc_fanout);
+ fill_pack_entry(perm[cur_pack], p[cur_pack], cur_object, &entries_by_fanout[nr_fanout]);
+ nr_fanout++;
+ }
+ }
+
+ QSORT(entries_by_fanout, nr_fanout, midx_oid_compare);
+
+ /*
+ * The batch is now sorted by OID and then mtime (descending).
+ * Take only the first duplicate.
+ */
+ for (cur_object = 0; cur_object < nr_fanout; cur_object++) {
+ if (cur_object && oideq(&entries_by_fanout[cur_object - 1].oid,
+ &entries_by_fanout[cur_object].oid))
+ continue;
+
+ ALLOC_GROW(deduplicated_entries, *nr_objects + 1, alloc_objects);
+ memcpy(&deduplicated_entries[*nr_objects],
+ &entries_by_fanout[cur_object],
+ sizeof(struct pack_midx_entry));
+ (*nr_objects)++;
+ }
+ }
+
+ free(entries_by_fanout);
+ return deduplicated_entries;
+}
+
+static size_t write_midx_pack_names(struct hashfile *f,
+ char **pack_names,
+ uint32_t num_packs)
+{
+ uint32_t i;
+ unsigned char padding[MIDX_CHUNK_ALIGNMENT];
+ size_t written = 0;
+
+ for (i = 0; i < num_packs; i++) {
+ size_t writelen = strlen(pack_names[i]) + 1;
+
+ if (i && strcmp(pack_names[i], pack_names[i - 1]) <= 0)
+ BUG("incorrect pack-file order: %s before %s",
+ pack_names[i - 1],
+ pack_names[i]);
+
+ hashwrite(f, pack_names[i], writelen);
+ written += writelen;
+ }
+
+ /* add padding to be aligned */
+ i = MIDX_CHUNK_ALIGNMENT - (written % MIDX_CHUNK_ALIGNMENT);
+ if (i < MIDX_CHUNK_ALIGNMENT) {
+ memset(padding, 0, sizeof(padding));
+ hashwrite(f, padding, i);
+ written += i;
+ }
+
+ return written;
+}
+
+static size_t write_midx_oid_fanout(struct hashfile *f,
+ struct pack_midx_entry *objects,
+ uint32_t nr_objects)
+{
+ struct pack_midx_entry *list = objects;
+ struct pack_midx_entry *last = objects + nr_objects;
+ uint32_t count = 0;
+ uint32_t i;
+
+ /*
+ * Write the first-level table (the list is sorted,
+ * but we use a 256-entry lookup to be able to avoid
+ * having to do eight extra binary search iterations).
+ */
+ for (i = 0; i < 256; i++) {
+ struct pack_midx_entry *next = list;
+
+ while (next < last && next->oid.hash[0] == i) {
+ count++;
+ next++;
+ }
+
+ hashwrite_be32(f, count);
+ list = next;
+ }
+
+ return MIDX_CHUNK_FANOUT_SIZE;
+}
+
+static size_t write_midx_oid_lookup(struct hashfile *f, unsigned char hash_len,
+ struct pack_midx_entry *objects,
+ uint32_t nr_objects)
+{
+ struct pack_midx_entry *list = objects;
+ uint32_t i;
+ size_t written = 0;
+
+ for (i = 0; i < nr_objects; i++) {
+ struct pack_midx_entry *obj = list++;
+
+ if (i < nr_objects - 1) {
+ struct pack_midx_entry *next = list;
+ if (oidcmp(&obj->oid, &next->oid) >= 0)
+ BUG("OIDs not in order: %s >= %s",
+ oid_to_hex(&obj->oid),
+ oid_to_hex(&next->oid));
+ }
+
+ hashwrite(f, obj->oid.hash, (int)hash_len);
+ written += hash_len;
+ }
+
+ return written;
+}
+
+static size_t write_midx_object_offsets(struct hashfile *f, int large_offset_needed,
+ struct pack_midx_entry *objects, uint32_t nr_objects)
+{
+ struct pack_midx_entry *list = objects;
+ uint32_t i, nr_large_offset = 0;
+ size_t written = 0;
+
+ for (i = 0; i < nr_objects; i++) {
+ struct pack_midx_entry *obj = list++;
+
+ hashwrite_be32(f, obj->pack_int_id);
+
+ if (large_offset_needed && obj->offset >> 31)
+ hashwrite_be32(f, MIDX_LARGE_OFFSET_NEEDED | nr_large_offset++);
+ else if (!large_offset_needed && obj->offset >> 32)
+ BUG("object %s requires a large offset (%"PRIx64") but the MIDX is not writing large offsets!",
+ oid_to_hex(&obj->oid),
+ obj->offset);
+ else
+ hashwrite_be32(f, (uint32_t)obj->offset);
+
+ written += MIDX_CHUNK_OFFSET_WIDTH;
+ }
+
+ return written;
+}
+
+static size_t write_midx_large_offsets(struct hashfile *f, uint32_t nr_large_offset,
+ struct pack_midx_entry *objects, uint32_t nr_objects)
+{
+ struct pack_midx_entry *list = objects;
+ size_t written = 0;
+
+ while (nr_large_offset) {
+ struct pack_midx_entry *obj = list++;
+ uint64_t offset = obj->offset;
+
+ if (!(offset >> 31))
+ continue;
+
+ hashwrite_be32(f, offset >> 32);
+ hashwrite_be32(f, offset & 0xffffffffUL);
+ written += 2 * sizeof(uint32_t);
+
+ nr_large_offset--;
+ }
+
+ return written;
+}
+
+int write_midx_file(const char *object_dir)
+{
+ unsigned char cur_chunk, num_chunks = 0;
+ char *midx_name;
+ uint32_t i;
+ struct hashfile *f = NULL;
+ struct lock_file lk;
+ struct pack_list packs;
+ uint32_t *pack_perm = NULL;
+ uint64_t written = 0;
+ uint32_t chunk_ids[MIDX_MAX_CHUNKS + 1];
+ uint64_t chunk_offsets[MIDX_MAX_CHUNKS + 1];
+ uint32_t nr_entries, num_large_offsets = 0;
+ struct pack_midx_entry *entries = NULL;
+ int large_offsets_needed = 0;
+
+ midx_name = get_midx_filename(object_dir);
+ if (safe_create_leading_directories(midx_name)) {
+ UNLEAK(midx_name);
+ die_errno(_("unable to create leading directories of %s"),
+ midx_name);
+ }
+
+ packs.m = load_multi_pack_index(object_dir, 1);
+
+ packs.nr = 0;
+ packs.alloc_list = packs.m ? packs.m->num_packs : 16;
+ packs.alloc_names = packs.alloc_list;
+ packs.list = NULL;
+ packs.names = NULL;
+ packs.pack_name_concat_len = 0;
+ ALLOC_ARRAY(packs.list, packs.alloc_list);
+ ALLOC_ARRAY(packs.names, packs.alloc_names);
+
+ if (packs.m) {
+ for (i = 0; i < packs.m->num_packs; i++) {
+ ALLOC_GROW(packs.list, packs.nr + 1, packs.alloc_list);
+ ALLOC_GROW(packs.names, packs.nr + 1, packs.alloc_names);
+
+ packs.list[packs.nr] = NULL;
+ packs.names[packs.nr] = xstrdup(packs.m->pack_names[i]);
+ packs.pack_name_concat_len += strlen(packs.names[packs.nr]) + 1;
+ packs.nr++;
+ }
+ }
+
+ for_each_file_in_pack_dir(object_dir, add_pack_to_midx, &packs);
+
+ if (packs.m && packs.nr == packs.m->num_packs)
+ goto cleanup;
+
+ if (packs.pack_name_concat_len % MIDX_CHUNK_ALIGNMENT)
+ packs.pack_name_concat_len += MIDX_CHUNK_ALIGNMENT -
+ (packs.pack_name_concat_len % MIDX_CHUNK_ALIGNMENT);
+
+ ALLOC_ARRAY(pack_perm, packs.nr);
+ sort_packs_by_name(packs.names, packs.nr, pack_perm);
+
+ entries = get_sorted_entries(packs.m, packs.list, pack_perm, packs.nr, &nr_entries);
+
+ for (i = 0; i < nr_entries; i++) {
+ if (entries[i].offset > 0x7fffffff)
+ num_large_offsets++;
+ if (entries[i].offset > 0xffffffff)
+ large_offsets_needed = 1;
+ }
+
+ hold_lock_file_for_update(&lk, midx_name, LOCK_DIE_ON_ERROR);
+ f = hashfd(lk.tempfile->fd, lk.tempfile->filename.buf);
+ FREE_AND_NULL(midx_name);
+
+ if (packs.m)
+ close_midx(packs.m);
+
+ cur_chunk = 0;
+ num_chunks = large_offsets_needed ? 5 : 4;
+
+ written = write_midx_header(f, num_chunks, packs.nr);
+
+ chunk_ids[cur_chunk] = MIDX_CHUNKID_PACKNAMES;
+ chunk_offsets[cur_chunk] = written + (num_chunks + 1) * MIDX_CHUNKLOOKUP_WIDTH;
+
+ cur_chunk++;
+ chunk_ids[cur_chunk] = MIDX_CHUNKID_OIDFANOUT;
+ chunk_offsets[cur_chunk] = chunk_offsets[cur_chunk - 1] + packs.pack_name_concat_len;
+
+ cur_chunk++;
+ chunk_ids[cur_chunk] = MIDX_CHUNKID_OIDLOOKUP;
+ chunk_offsets[cur_chunk] = chunk_offsets[cur_chunk - 1] + MIDX_CHUNK_FANOUT_SIZE;
+
+ cur_chunk++;
+ chunk_ids[cur_chunk] = MIDX_CHUNKID_OBJECTOFFSETS;
+ chunk_offsets[cur_chunk] = chunk_offsets[cur_chunk - 1] + nr_entries * MIDX_HASH_LEN;
+
+ cur_chunk++;
+ chunk_offsets[cur_chunk] = chunk_offsets[cur_chunk - 1] + nr_entries * MIDX_CHUNK_OFFSET_WIDTH;
+ if (large_offsets_needed) {
+ chunk_ids[cur_chunk] = MIDX_CHUNKID_LARGEOFFSETS;
+
+ cur_chunk++;
+ chunk_offsets[cur_chunk] = chunk_offsets[cur_chunk - 1] +
+ num_large_offsets * MIDX_CHUNK_LARGE_OFFSET_WIDTH;
+ }
+
+ chunk_ids[cur_chunk] = 0;
+
+ for (i = 0; i <= num_chunks; i++) {
+ if (i && chunk_offsets[i] < chunk_offsets[i - 1])
+ BUG("incorrect chunk offsets: %"PRIu64" before %"PRIu64,
+ chunk_offsets[i - 1],
+ chunk_offsets[i]);
+
+ if (chunk_offsets[i] % MIDX_CHUNK_ALIGNMENT)
+ BUG("chunk offset %"PRIu64" is not properly aligned",
+ chunk_offsets[i]);
+
+ hashwrite_be32(f, chunk_ids[i]);
+ hashwrite_be32(f, chunk_offsets[i] >> 32);
+ hashwrite_be32(f, chunk_offsets[i]);
+
+ written += MIDX_CHUNKLOOKUP_WIDTH;
+ }
+
+ for (i = 0; i < num_chunks; i++) {
+ if (written != chunk_offsets[i])
+ BUG("incorrect chunk offset (%"PRIu64" != %"PRIu64") for chunk id %"PRIx32,
+ chunk_offsets[i],
+ written,
+ chunk_ids[i]);
+
+ switch (chunk_ids[i]) {
+ case MIDX_CHUNKID_PACKNAMES:
+ written += write_midx_pack_names(f, packs.names, packs.nr);
+ break;
+
+ case MIDX_CHUNKID_OIDFANOUT:
+ written += write_midx_oid_fanout(f, entries, nr_entries);
+ break;
+
+ case MIDX_CHUNKID_OIDLOOKUP:
+ written += write_midx_oid_lookup(f, MIDX_HASH_LEN, entries, nr_entries);
+ break;
+
+ case MIDX_CHUNKID_OBJECTOFFSETS:
+ written += write_midx_object_offsets(f, large_offsets_needed, entries, nr_entries);
+ break;
+
+ case MIDX_CHUNKID_LARGEOFFSETS:
+ written += write_midx_large_offsets(f, num_large_offsets, entries, nr_entries);
+ break;
+
+ default:
+ BUG("trying to write unknown chunk id %"PRIx32,
+ chunk_ids[i]);
+ }
+ }
+
+ if (written != chunk_offsets[num_chunks])
+ BUG("incorrect final offset %"PRIu64" != %"PRIu64,
+ written,
+ chunk_offsets[num_chunks]);
+
+ finalize_hashfile(f, NULL, CSUM_FSYNC | CSUM_HASH_IN_STREAM);
+ commit_lock_file(&lk);
+
+cleanup:
+ for (i = 0; i < packs.nr; i++) {
+ if (packs.list[i]) {
+ close_pack(packs.list[i]);
+ free(packs.list[i]);
+ }
+ free(packs.names[i]);
+ }
+
+ free(packs.list);
+ free(packs.names);
+ free(entries);
+ free(pack_perm);
+ free(midx_name);
+ return 0;
+}
+
+void clear_midx_file(const char *object_dir)
+{
+ char *midx = get_midx_filename(object_dir);
+
+ if (remove_path(midx)) {
+ UNLEAK(midx);
+ die(_("failed to clear multi-pack-index at %s"), midx);
+ }
+
+ free(midx);
+}
+
+static int verify_midx_error;
+
+static void midx_report(const char *fmt, ...)
+{
+ va_list ap;
+ verify_midx_error = 1;
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ fprintf(stderr, "\n");
+ va_end(ap);
+}
+
+int verify_midx_file(const char *object_dir)
+{
+ uint32_t i;
+ struct progress *progress;
+ struct multi_pack_index *m = load_multi_pack_index(object_dir, 1);
+ verify_midx_error = 0;
+
+ if (!m)
+ return 0;
+
+ for (i = 0; i < m->num_packs; i++) {
+ if (prepare_midx_pack(m, i))
+ midx_report("failed to load pack in position %d", i);
+ }
+
+ for (i = 0; i < 255; i++) {
+ uint32_t oid_fanout1 = ntohl(m->chunk_oid_fanout[i]);
+ uint32_t oid_fanout2 = ntohl(m->chunk_oid_fanout[i + 1]);
+
+ if (oid_fanout1 > oid_fanout2)
+ midx_report(_("oid fanout out of order: fanout[%d] = %"PRIx32" > %"PRIx32" = fanout[%d]"),
+ i, oid_fanout1, oid_fanout2, i + 1);
+ }
+
+ for (i = 0; i < m->num_objects - 1; i++) {
+ struct object_id oid1, oid2;
+
+ nth_midxed_object_oid(&oid1, m, i);
+ nth_midxed_object_oid(&oid2, m, i + 1);
+
+ if (oidcmp(&oid1, &oid2) >= 0)
+ midx_report(_("oid lookup out of order: oid[%d] = %s >= %s = oid[%d]"),
+ i, oid_to_hex(&oid1), oid_to_hex(&oid2), i + 1);
+ }
+
+ progress = start_progress(_("Verifying object offsets"), m->num_objects);
+ for (i = 0; i < m->num_objects; i++) {
+ struct object_id oid;
+ struct pack_entry e;
+ off_t m_offset, p_offset;
+
+ nth_midxed_object_oid(&oid, m, i);
+ if (!fill_midx_entry(&oid, &e, m)) {
+ midx_report(_("failed to load pack entry for oid[%d] = %s"),
+ i, oid_to_hex(&oid));
+ continue;
+ }
+
+ if (open_pack_index(e.p)) {
+ midx_report(_("failed to load pack-index for packfile %s"),
+ e.p->pack_name);
+ break;
+ }
+
+ m_offset = e.offset;
+ p_offset = find_pack_entry_one(oid.hash, e.p);
+
+ if (m_offset != p_offset)
+ midx_report(_("incorrect object offset for oid[%d] = %s: %"PRIx64" != %"PRIx64),
+ i, oid_to_hex(&oid), m_offset, p_offset);
+
+ display_progress(progress, i + 1);
+ }
+ stop_progress(&progress);
+
+ return verify_midx_error;
+}
--- /dev/null
+#ifndef MIDX_H
+#define MIDX_H
+
+#include "repository.h"
+
+struct object_id;
+struct pack_entry;
+
+struct multi_pack_index {
+ struct multi_pack_index *next;
+
+ int fd;
+
+ const unsigned char *data;
+ size_t data_len;
+
+ uint32_t signature;
+ unsigned char version;
+ unsigned char hash_len;
+ unsigned char num_chunks;
+ uint32_t num_packs;
+ uint32_t num_objects;
+
+ int local;
+
+ const unsigned char *chunk_pack_names;
+ const uint32_t *chunk_oid_fanout;
+ const unsigned char *chunk_oid_lookup;
+ const unsigned char *chunk_object_offsets;
+ const unsigned char *chunk_large_offsets;
+
+ const char **pack_names;
+ struct packed_git **packs;
+ char object_dir[FLEX_ARRAY];
+};
+
+struct multi_pack_index *load_multi_pack_index(const char *object_dir, int local);
+int prepare_midx_pack(struct multi_pack_index *m, uint32_t pack_int_id);
+int bsearch_midx(const struct object_id *oid, struct multi_pack_index *m, uint32_t *result);
+struct object_id *nth_midxed_object_oid(struct object_id *oid,
+ struct multi_pack_index *m,
+ uint32_t n);
+int fill_midx_entry(const struct object_id *oid, struct pack_entry *e, struct multi_pack_index *m);
+int midx_contains_pack(struct multi_pack_index *m, const char *idx_name);
+int prepare_multi_pack_index_one(struct repository *r, const char *object_dir, int local);
+
+int write_midx_file(const char *object_dir);
+void clear_midx_file(const char *object_dir);
+int verify_midx_file(const char *object_dir);
+
+#endif
static void lazy_init_name_hash(struct index_state *istate)
{
- uint64_t start = getnanotime();
if (istate->name_hash_initialized)
return;
+ trace_performance_enter();
hashmap_init(&istate->name_hash, cache_entry_cmp, NULL, istate->cache_nr);
hashmap_init(&istate->dir_hash, dir_entry_cmp, NULL, istate->cache_nr);
}
istate->name_hash_initialized = 1;
- trace_performance_since(start, "initialize name hash");
+ trace_performance_leave("initialize name hash");
}
/*
#include "notes-merge.h"
#include "strbuf.h"
#include "notes-utils.h"
+#include "commit-reach.h"
struct notes_merge_pair {
struct object_id obj, base, local, remote;
trace_printf("\tdiff_tree_remote(base = %.7s, remote = %.7s)\n",
oid_to_hex(base), oid_to_hex(remote));
- diff_setup(&opt);
+ repo_diff_setup(the_repository, &opt);
opt.flags.recursive = 1;
opt.output_format = DIFF_FORMAT_NO_OUTPUT;
diff_setup_done(&opt);
mp = find_notes_merge_pair_pos(changes, len, &obj, 1, &occupied);
if (occupied) {
/* We've found an addition/deletion pair */
- assert(!oidcmp(&mp->obj, &obj));
+ assert(oideq(&mp->obj, &obj));
if (is_null_oid(&p->one->oid)) { /* addition */
assert(is_null_oid(&mp->remote));
oidcpy(&mp->remote, &p->two->oid);
trace_printf("\tdiff_tree_local(len = %i, base = %.7s, local = %.7s)\n",
len, oid_to_hex(base), oid_to_hex(local));
- diff_setup(&opt);
+ repo_diff_setup(the_repository, &opt);
opt.flags.recursive = 1;
opt.output_format = DIFF_FORMAT_NO_OUTPUT;
diff_setup_done(&opt);
continue;
}
- assert(!oidcmp(&mp->obj, &obj));
+ assert(oideq(&mp->obj, &obj));
if (is_null_oid(&p->two->oid)) { /* deletion */
/*
* Either this is a true deletion (1), or it is part
* (3) mp->local is uninitialized; set it to null_sha1
* (will be overwritten by following addition)
*/
- if (!oidcmp(&mp->local, &uninitialized))
+ if (oideq(&mp->local, &uninitialized))
oidclr(&mp->local);
} else if (is_null_oid(&p->one->oid)) { /* addition */
/*
* (3) mp->local is null_sha1; set to p->two->sha1
*/
assert(is_null_oid(&mp->local) ||
- !oidcmp(&mp->local, &uninitialized));
+ oideq(&mp->local, &uninitialized));
oidcpy(&mp->local, &p->two->oid);
} else { /* modification */
/*
* match mp->base, and mp->local shall be uninitialized.
* Set mp->local to p->two->sha1.
*/
- assert(!oidcmp(&p->one->oid, &mp->base));
- assert(!oidcmp(&mp->local, &uninitialized));
+ assert(oideq(&p->one->oid, &mp->base));
+ assert(oideq(&mp->local, &uninitialized));
oidcpy(&mp->local, &p->two->oid);
}
trace_printf("\t\tStored local change for %s: %.7s -> %.7s\n",
read_mmblob(&remote, &p->remote);
status = ll_merge(&result_buf, oid_to_hex(&p->obj), &base, NULL,
- &local, o->local_ref, &remote, o->remote_ref, NULL);
+ &local, o->local_ref, &remote, o->remote_ref,
+ &the_index, NULL);
free(base.ptr);
free(local.ptr);
oid_to_hex(&p->local),
oid_to_hex(&p->remote));
- if (!oidcmp(&p->base, &p->remote)) {
+ if (oideq(&p->base, &p->remote)) {
/* no remote change; nothing to do */
trace_printf("\t\t\tskipping (no remote change)\n");
- } else if (!oidcmp(&p->local, &p->remote)) {
+ } else if (oideq(&p->local, &p->remote)) {
/* same change in local and remote; nothing to do */
trace_printf("\t\t\tskipping (local == remote)\n");
- } else if (!oidcmp(&p->local, &uninitialized) ||
- !oidcmp(&p->local, &p->base)) {
+ } else if (oideq(&p->local, &uninitialized) ||
+ oideq(&p->local, &p->base)) {
/* no local change; adopt remote change */
trace_printf("\t\t\tno local change, adopted remote\n");
if (add_note(t, &p->obj, &p->remote,
oid_to_hex(&local->object.oid),
oid_to_hex(base_oid));
- if (!oidcmp(&remote->object.oid, base_oid)) {
+ if (oideq(&remote->object.oid, base_oid)) {
/* Already merged; result == local commit */
if (o->verbosity >= 2)
printf("Already up to date!\n");
oidcpy(result_oid, &local->object.oid);
goto found_result;
}
- if (!oidcmp(&local->object.oid, base_oid)) {
+ if (oideq(&local->object.oid, base_oid)) {
/* Fast-forward; result == remote commit */
if (o->verbosity >= 2)
printf("Fast-forward\n");
/* 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(&blob_oid, path.buf, &st, HASH_WRITE_OBJECT))
+ if (index_path(&the_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",
void **p = note_tree_search(t, &tree, &n, key_sha1);
if (GET_PTR_TYPE(*p) == PTR_TYPE_NOTE) {
struct leaf_node *l = (struct leaf_node *) CLR_PTR_TYPE(*p);
- if (!hashcmp(key_sha1, l->key_oid.hash))
+ if (hasheq(key_sha1, l->key_oid.hash))
return l;
}
return NULL;
if (GET_PTR_TYPE(*p) != PTR_TYPE_NOTE)
return; /* type mismatch, nothing to remove */
l = (struct leaf_node *) CLR_PTR_TYPE(*p);
- if (oidcmp(&l->key_oid, &entry->key_oid))
+ if (!oideq(&l->key_oid, &entry->key_oid))
return; /* key mismatch, nothing to remove */
/* we have found a matching entry */
case PTR_TYPE_NOTE:
switch (type) {
case PTR_TYPE_NOTE:
- if (!oidcmp(&l->key_oid, &entry->key_oid)) {
+ if (oideq(&l->key_oid, &entry->key_oid)) {
/* skip concatenation if l == entry */
- if (!oidcmp(&l->val_oid, &entry->val_oid))
+ if (oideq(&l->val_oid, &entry->val_oid))
return 0;
ret = combine_notes(&l->val_oid,
char pack_name[FLEX_ARRAY]; /* more */
};
+struct multi_pack_index;
+
struct raw_object_store {
/*
* Path to the repository's object store.
struct commit_graph *commit_graph;
unsigned commit_graph_attempted : 1; /* if loading has been attempted */
+ /*
+ * private data
+ *
+ * should only be accessed directly by packfile.c and midx.c
+ */
+ struct multi_pack_index *multi_pack_index;
+
/*
* private data
*
/* A most-recently-used ordered version of the packed_git list. */
struct list_head packed_git_mru;
+ /*
+ * A linked list containing all packfiles, starting with those
+ * contained in the multi_pack_index.
+ */
+ struct packed_git *all_packs;
+
/*
* A fast, rough count of the number of objects in the repository.
* These two fields are not meant for direct access. Use
first = i = hash_obj(sha1, r->parsed_objects->obj_hash_size);
while ((obj = r->parsed_objects->obj_hash[i]) != NULL) {
- if (!hashcmp(sha1, obj->oid.hash))
+ if (hasheq(sha1, obj->oid.hash))
break;
i++;
if (i == r->parsed_objects->obj_hash_size)
* fetch-pack.c: 01
* negotiator/default.c: 2--5
* walker.c: 0-2
- * upload-pack.c: 4 11----------------19
+ * upload-pack.c: 4 11-----14 16-----19
* builtin/blame.c: 12-13
* bisect.c: 16
* bundle.c: 16
* http-push.c: 16-----19
- * commit.c: 16-----19
+ * commit-reach.c: 15-------19
* sha1-name.c: 20
* list-objects-filter.c: 21
* builtin/fsck.c: 0--3
#include "cache.h"
#include "oidmap.h"
-static int cmpfn(const void *hashmap_cmp_fn_data,
- const void *entry, const void *entry_or_key,
- const void *keydata)
+static int oidmap_neq(const void *hashmap_cmp_fn_data,
+ const void *entry, const void *entry_or_key,
+ const void *keydata)
{
const struct oidmap_entry *entry_ = entry;
if (keydata)
- return oidcmp(&entry_->oid, (const struct object_id *) keydata);
- return oidcmp(&entry_->oid,
+ return !oideq(&entry_->oid, (const struct object_id *) keydata);
+ return !oideq(&entry_->oid,
&((const struct oidmap_entry *) entry_or_key)->oid);
}
void oidmap_init(struct oidmap *map, size_t initial_size)
{
- hashmap_init(&map->map, cmpfn, NULL, initial_size);
+ hashmap_init(&map->map, oidmap_neq, NULL, initial_size);
}
void oidmap_free(struct oidmap *map, int free_entries)
#include "cache.h"
#include "oidset.h"
+void oidset_init(struct oidset *set, size_t initial_size)
+{
+ memset(&set->set, 0, sizeof(set->set));
+ if (initial_size)
+ kh_resize_oid(&set->set, initial_size);
+}
+
int oidset_contains(const struct oidset *set, const struct object_id *oid)
{
- if (!set->map.map.tablesize)
- return 0;
- return !!oidmap_get(&set->map, oid);
+ khiter_t pos = kh_get_oid(&set->set, *oid);
+ return pos != kh_end(&set->set);
}
int oidset_insert(struct oidset *set, const struct object_id *oid)
{
- struct oidmap_entry *entry;
-
- if (!set->map.map.tablesize)
- oidmap_init(&set->map, 0);
- else if (oidset_contains(set, oid))
- return 1;
-
- entry = xmalloc(sizeof(*entry));
- oidcpy(&entry->oid, oid);
-
- oidmap_put(&set->map, entry);
- return 0;
+ int added;
+ kh_put_oid(&set->set, *oid, &added);
+ return !added;
}
int oidset_remove(struct oidset *set, const struct object_id *oid)
{
- struct oidmap_entry *entry;
-
- entry = oidmap_remove(&set->map, oid);
- free(entry);
-
- return (entry != NULL);
+ khiter_t pos = kh_get_oid(&set->set, *oid);
+ if (pos == kh_end(&set->set))
+ return 0;
+ kh_del_oid(&set->set, pos);
+ return 1;
}
void oidset_clear(struct oidset *set)
{
- oidmap_free(&set->map, 1);
+ kh_release_oid(&set->set);
+ oidset_init(set, 0);
}
#ifndef OIDSET_H
#define OIDSET_H
-#include "oidmap.h"
+#include "hashmap.h"
+#include "khash.h"
/**
* This API is similar to sha1-array, in that it maintains a set of object ids
* table overhead.
*/
+static inline unsigned int oid_hash(struct object_id oid)
+{
+ return sha1hash(oid.hash);
+}
+
+static inline int oid_equal(struct object_id a, struct object_id b)
+{
+ return oideq(&a, &b);
+}
+
+KHASH_INIT(oid, struct object_id, int, 0, oid_hash, oid_equal)
+
/**
* A single oidset; should be zero-initialized (or use OIDSET_INIT).
*/
struct oidset {
- struct oidmap map;
+ kh_oid_t set;
};
-#define OIDSET_INIT { OIDMAP_INIT }
+#define OIDSET_INIT { { 0 } }
-static inline void oidset_init(struct oidset *set, size_t initial_size)
-{
- oidmap_init(&set->map, initial_size);
-}
+/**
+ * Initialize the oidset structure `set`.
+ *
+ * If `initial_size` is bigger than 0 then preallocate to allow inserting
+ * the specified number of elements without further allocations.
+ */
+void oidset_init(struct oidset *set, size_t initial_size);
/**
* Returns true iff `set` contains `oid`.
void oidset_clear(struct oidset *set);
struct oidset_iter {
- struct oidmap_iter m_iter;
+ kh_oid_t *set;
+ khiter_t iter;
};
static inline void oidset_iter_init(struct oidset *set,
struct oidset_iter *iter)
{
- oidmap_iter_init(&set->map, &iter->m_iter);
+ iter->set = &set->set;
+ iter->iter = kh_begin(iter->set);
}
static inline struct object_id *oidset_iter_next(struct oidset_iter *iter)
{
- struct oidmap_entry *e = oidmap_iter_next(&iter->m_iter);
- return e ? &e->oid : NULL;
+ for (; iter->iter != kh_end(iter->set); iter->iter++) {
+ if (kh_exist(iter->set, iter->iter))
+ return &kh_key(iter->set, iter->iter++);
+ }
+ return NULL;
}
static inline struct object_id *oidset_iter_first(struct oidset *set,
#include "pack-bitmap.h"
#include "sha1-lookup.h"
#include "pack-objects.h"
+#include "commit-reach.h"
struct bitmapped_commit {
struct commit *commit;
struct progress *progress;
int show_progress;
- unsigned char pack_checksum[20];
+ unsigned char pack_checksum[GIT_MAX_RAWSZ];
};
static struct bitmap_writer writer;
if (writer.show_progress)
writer.progress = start_progress("Building bitmaps", writer.selected_nr);
- init_revisions(&revs, NULL);
+ repo_init_revisions(the_repository, &revs, NULL);
revs.tag_objects = 1;
revs.tree_objects = 1;
revs.blob_objects = 1;
/* Bitmap result of the last performed walk */
struct bitmap *result;
+ /* "have" bitmap from the last performed walk */
+ struct bitmap *haves;
+
/* Version of the bitmap index */
unsigned int version;
-
- unsigned loaded : 1;
};
static struct ewah_bitmap *lookup_stored_bitmap(struct stored_bitmap *st)
static int load_pack_bitmap(struct bitmap_index *bitmap_git)
{
- assert(bitmap_git->map && !bitmap_git->loaded);
+ assert(bitmap_git->map);
bitmap_git->bitmaps = kh_init_sha1();
bitmap_git->ext_index.positions = kh_init_sha1_pos();
if (load_bitmap_entries_v1(bitmap_git) < 0)
goto failed;
- bitmap_git->loaded = 1;
return 0;
failed:
struct packed_git *p;
int ret = -1;
- assert(!bitmap_git->map && !bitmap_git->loaded);
+ assert(!bitmap_git->map);
- for (p = get_packed_git(the_repository); p; p = p->next) {
+ for (p = get_all_packs(the_repository); p; p = p->next) {
if (open_pack_bitmap_1(bitmap_git, p) == 0)
ret = 0;
}
* from disk. this is the point of no return; after this the rev_list
* becomes invalidated and we must perform the revwalk through bitmaps
*/
- if (!bitmap_git->loaded && load_pack_bitmap(bitmap_git) < 0)
+ if (load_pack_bitmap(bitmap_git) < 0)
goto cleanup;
object_array_clear(&revs->pending);
bitmap_and_not(wants_bitmap, haves_bitmap);
bitmap_git->result = wants_bitmap;
+ bitmap_git->haves = haves_bitmap;
- bitmap_free(haves_bitmap);
return bitmap_git;
cleanup:
OBJ_TAG, show_reachable);
show_extended_objects(bitmap_git, show_reachable);
-
- bitmap_free(bitmap_git->result);
- bitmap_git->result = NULL;
}
static uint32_t count_object_type(struct bitmap_index *bitmap_git,
free(b->ext_index.objects);
free(b->ext_index.hashes);
bitmap_free(b->result);
+ bitmap_free(b->haves);
free(b);
}
+
+int bitmap_has_sha1_in_uninteresting(struct bitmap_index *bitmap_git,
+ const unsigned char *sha1)
+{
+ int pos;
+
+ if (!bitmap_git)
+ return 0; /* no bitmap loaded */
+ if (!bitmap_git->haves)
+ return 0; /* walk had no "haves" */
+
+ pos = bitmap_position_packfile(bitmap_git, sha1);
+ if (pos < 0)
+ return 0;
+
+ return bitmap_get(bitmap_git->haves, pos);
+}
khash_sha1 *reused_bitmaps, int show_progress);
void free_bitmap_index(struct bitmap_index *);
+/*
+ * After a traversal has been performed by prepare_bitmap_walk(), this can be
+ * queried to see if a particular object was reachable from any of the
+ * objects flagged as UNINTERESTING.
+ */
+int bitmap_has_sha1_in_uninteresting(struct bitmap_index *, const unsigned char *sha1);
+
void bitmap_writer_show_progress(int show);
void bitmap_writer_set_checksum(unsigned char *sha1);
void bitmap_writer_build_type_index(struct packing_data *to_pack,
} while (offset < pack_sig_ofs);
the_hash_algo->final_fn(hash, &ctx);
pack_sig = use_pack(p, w_curs, pack_sig_ofs, NULL);
- if (hashcmp(hash, pack_sig))
+ if (!hasheq(hash, pack_sig))
err = error("%s pack checksum mismatch",
p->pack_name);
- if (hashcmp(index_base + index_size - the_hash_algo->hexsz, pack_sig))
+ if (!hasheq(index_base + index_size - the_hash_algo->hexsz, pack_sig))
err = error("%s pack checksum does not match its index",
p->pack_name);
unuse_pack(w_curs);
the_hash_algo->init_fn(&ctx);
the_hash_algo->update_fn(&ctx, index_base, (unsigned int)(index_size - the_hash_algo->rawsz));
the_hash_algo->final_fn(hash, &ctx);
- if (hashcmp(hash, index_base + index_size - the_hash_algo->rawsz))
+ if (!hasheq(hash, index_base + index_size - the_hash_algo->rawsz))
err = error("Packfile index for %s hash mismatch",
p->pack_name);
return err;
while (pdata->index[i] > 0) {
uint32_t pos = pdata->index[i] - 1;
- if (!hashcmp(sha1, pdata->objects[pos].idx.oid.hash)) {
+ if (hasheq(sha1, pdata->objects[pos].idx.oid.hash)) {
*found = 1;
return i;
}
* (i.e. in_pack_idx also zero) should return NULL.
*/
mapping[cnt++] = NULL;
- for (p = get_packed_git(the_repository); p; p = p->next, cnt++) {
+ for (p = get_all_packs(the_repository); p; p = p->next, cnt++) {
if (cnt == nr) {
free(mapping);
return;
1U << OE_SIZE_BITS);
pdata->oe_delta_size_limit = git_env_ulong("GIT_TEST_OE_DELTA_SIZE",
1UL << OE_DELTA_SIZE_BITS);
+#ifndef NO_PTHREADS
+ pthread_mutex_init(&pdata->lock, NULL);
+#endif
}
struct object_entry *packlist_alloc(struct packing_data *pdata,
REALLOC_ARRAY(pdata->in_pack, pdata->nr_alloc);
if (pdata->delta_size)
REALLOC_ARRAY(pdata->delta_size, pdata->nr_alloc);
+
+ if (pdata->tree_depth)
+ REALLOC_ARRAY(pdata->tree_depth, pdata->nr_alloc);
+
+ if (pdata->layer)
+ REALLOC_ARRAY(pdata->layer, pdata->nr_alloc);
}
new_entry = pdata->objects + pdata->nr_objects++;
if (pdata->in_pack)
pdata->in_pack[pdata->nr_objects - 1] = NULL;
+ if (pdata->tree_depth)
+ pdata->tree_depth[pdata->nr_objects - 1] = 0;
+
+ if (pdata->layer)
+ pdata->layer[pdata->nr_objects - 1] = 0;
+
return new_entry;
}
+
+void oe_set_delta_ext(struct packing_data *pdata,
+ struct object_entry *delta,
+ const unsigned char *sha1)
+{
+ struct object_entry *base;
+
+ ALLOC_GROW(pdata->ext_bases, pdata->nr_ext + 1, pdata->alloc_ext);
+ base = &pdata->ext_bases[pdata->nr_ext++];
+ memset(base, 0, sizeof(*base));
+ hashcpy(base->idx.oid.hash, sha1);
+
+ /* These flags mark that we are not part of the actual pack output. */
+ base->preferred_base = 1;
+ base->filled = 1;
+
+ delta->ext_base = 1;
+ delta->delta_idx = base - pdata->ext_bases + 1;
+}
unsigned no_try_delta:1;
unsigned type_:TYPE_BITS;
unsigned in_pack_type:TYPE_BITS; /* could be delta */
+
unsigned preferred_base:1; /*
* we do not pack this, but is available
* to be used as the base object to delta
unsigned filled:1; /* assigned write-order */
unsigned dfs_state:OE_DFS_STATE_BITS;
unsigned depth:OE_DEPTH_BITS;
+ unsigned ext_base:1; /* delta_idx points outside packlist */
/*
* pahole results on 64-bit linux (gcc and clang)
pthread_mutex_t lock;
#endif
+ /*
+ * This list contains entries for bases which we know the other side
+ * has (e.g., via reachability bitmaps), but which aren't in our
+ * "objects" list.
+ */
+ struct object_entry *ext_bases;
+ uint32_t nr_ext, alloc_ext;
+
uintmax_t oe_size_limit;
uintmax_t oe_delta_size_limit;
+
+ /* delta islands */
+ unsigned int *tree_depth;
+ unsigned char *layer;
};
void prepare_packing_data(struct packing_data *pdata);
const struct packing_data *pack,
const struct object_entry *e)
{
- if (e->delta_idx)
+ if (!e->delta_idx)
+ return NULL;
+ if (e->ext_base)
+ return &pack->ext_bases[e->delta_idx - 1];
+ else
return &pack->objects[e->delta_idx - 1];
- return NULL;
}
static inline void oe_set_delta(struct packing_data *pack,
e->delta_idx = 0;
}
+void oe_set_delta_ext(struct packing_data *pack,
+ struct object_entry *e,
+ const unsigned char *sha1);
+
static inline struct object_entry *oe_delta_child(
const struct packing_data *pack,
const struct object_entry *e)
return e->delta_size_;
/*
- * pack->detla_size[] can't be NULL because oe_set_delta_size()
+ * pack->delta_size[] can't be NULL because oe_set_delta_size()
* must have been called when a new delta is saved with
* oe_set_delta().
* If oe_delta() returns NULL (i.e. default state, which means
}
}
+static inline unsigned int oe_tree_depth(struct packing_data *pack,
+ struct object_entry *e)
+{
+ if (!pack->tree_depth)
+ return 0;
+ return pack->tree_depth[e - pack->objects];
+}
+
+static inline void oe_set_tree_depth(struct packing_data *pack,
+ struct object_entry *e,
+ unsigned int tree_depth)
+{
+ if (!pack->tree_depth)
+ ALLOC_ARRAY(pack->tree_depth, pack->nr_objects);
+ pack->tree_depth[e - pack->objects] = tree_depth;
+}
+
+static inline unsigned char oe_layer(struct packing_data *pack,
+ struct object_entry *e)
+{
+ if (!pack->layer)
+ return 0;
+ return pack->layer[e - pack->objects];
+}
+
+static inline void oe_set_layer(struct packing_data *pack,
+ struct object_entry *e,
+ unsigned char layer)
+{
+ if (!pack->layer)
+ ALLOC_ARRAY(pack->layer, pack->nr_objects);
+ pack->layer[e - pack->objects] = layer;
+}
+
#endif
unsigned num_ent = p->num_objects;
unsigned i;
const char *index = p->index_data;
+ const unsigned hashsz = the_hash_algo->rawsz;
ALLOC_ARRAY(p->revindex, num_ent + 1);
index += 4 * 256;
if (p->index_version > 1) {
const uint32_t *off_32 =
- (uint32_t *)(index + 8 + p->num_objects * (20 + 4));
+ (uint32_t *)(index + 8 + p->num_objects * (hashsz + 4));
const uint32_t *off_64 = off_32 + p->num_objects;
for (i = 0; i < num_ent; i++) {
uint32_t off = ntohl(*off_32++);
}
} else {
for (i = 0; i < num_ent; i++) {
- uint32_t hl = *((uint32_t *)(index + 24 * i));
+ uint32_t hl = *((uint32_t *)(index + (hashsz + 4) * i));
p->revindex[i].offset = ntohl(hl);
p->revindex[i].nr = i;
}
}
- /* This knows the pack format -- the 20-byte trailer
+ /*
+ * This knows the pack format -- the hash trailer
* follows immediately after the last object data.
*/
- p->revindex[num_ent].offset = p->pack_size - 20;
+ p->revindex[num_ent].offset = p->pack_size - hashsz;
p->revindex[num_ent].nr = -1;
sort_revindex(p->revindex, num_ent, p->pack_size);
}
}
hashwrite(f, obj->oid.hash, the_hash_algo->rawsz);
if ((opts->flags & WRITE_IDX_STRICT) &&
- (i && !oidcmp(&list[-2]->oid, &obj->oid)))
+ (i && oideq(&list[-2]->oid, &obj->oid)))
die("The same object %s appears twice in the pack",
oid_to_hex(&obj->oid));
}
if (partial_pack_offset == 0) {
unsigned char hash[GIT_MAX_RAWSZ];
the_hash_algo->final_fn(hash, &old_hash_ctx);
- if (hashcmp(hash, partial_pack_hash) != 0)
+ if (!hasheq(hash, partial_pack_hash))
die("Unexpected checksum for %s "
"(disk corruption?)", pack_name);
/*
#include "tree-walk.h"
#include "tree.h"
#include "object-store.h"
+#include "midx.h"
char *odb_pack_name(struct strbuf *buf,
const unsigned char *sha1,
static int check_packed_git_idx(const char *path, struct packed_git *p)
{
void *idx_map;
- struct pack_idx_header *hdr;
size_t idx_size;
- uint32_t version, nr, i, *index;
- int fd = git_open(path);
+ int fd = git_open(path), ret;
struct stat st;
const unsigned int hashsz = the_hash_algo->rawsz;
idx_map = xmmap(NULL, idx_size, PROT_READ, MAP_PRIVATE, fd, 0);
close(fd);
- hdr = idx_map;
+ ret = load_idx(path, hashsz, idx_map, idx_size, p);
+
+ if (ret)
+ munmap(idx_map, idx_size);
+
+ return ret;
+}
+
+int load_idx(const char *path, const unsigned int hashsz, void *idx_map,
+ size_t idx_size, struct packed_git *p)
+{
+ struct pack_idx_header *hdr = idx_map;
+ uint32_t version, nr, i, *index;
+
+ if (idx_size < 4 * 256 + hashsz + hashsz)
+ return error("index file %s is too small", path);
+ if (idx_map == NULL)
+ return error("empty data");
+
if (hdr->idx_signature == htonl(PACK_IDX_SIGNATURE)) {
version = ntohl(hdr->idx_version);
- if (version < 2 || version > 2) {
- munmap(idx_map, idx_size);
+ if (version < 2 || version > 2)
return error("index file %s is version %"PRIu32
" and is not supported by this binary"
" (try upgrading GIT to a newer version)",
path, version);
- }
} else
version = 1;
index += 2; /* skip index header */
for (i = 0; i < 256; i++) {
uint32_t n = ntohl(index[i]);
- if (n < nr) {
- munmap(idx_map, idx_size);
+ if (n < nr)
return error("non-monotonic index %s", path);
- }
nr = n;
}
* - hash of the packfile
* - file checksum
*/
- if (idx_size != 4*256 + nr * (hashsz + 4) + hashsz + hashsz) {
- munmap(idx_map, idx_size);
+ if (idx_size != 4 * 256 + nr * (hashsz + 4) + hashsz + hashsz)
return error("wrong index v1 file size in %s", path);
- }
} else if (version == 2) {
/*
* Minimum size:
unsigned long max_size = min_size;
if (nr)
max_size += (nr - 1)*8;
- if (idx_size < min_size || idx_size > max_size) {
- munmap(idx_map, idx_size);
+ if (idx_size < min_size || idx_size > max_size)
return error("wrong index v2 file size in %s", path);
- }
if (idx_size != min_size &&
/*
* make sure we can deal with large pack offsets.
* 31-bit signed offset won't be enough, neither
* 32-bit unsigned one will be.
*/
- (sizeof(off_t) <= 4)) {
- munmap(idx_map, idx_size);
+ (sizeof(off_t) <= 4))
return error("pack too large for current definition of off_t in %s", path);
- }
}
p->index_version = version;
return ret;
}
+uint32_t get_pack_fanout(struct packed_git *p, uint32_t value)
+{
+ const uint32_t *level1_ofs = p->index_data;
+
+ if (!level1_ofs) {
+ if (open_pack_index(p))
+ return 0;
+ level1_ofs = p->index_data;
+ }
+
+ if (p->index_version > 1) {
+ level1_ofs += 2;
+ }
+
+ return ntohl(level1_ofs[value]);
+}
+
static struct packed_git *alloc_packed_git(int extra)
{
struct packed_git *p = xmalloc(st_add(sizeof(*p), extra));
ssize_t read_result;
const unsigned hashsz = the_hash_algo->rawsz;
- if (!p->index_data && open_pack_index(p))
- return error("packfile %s index unavailable", p->pack_name);
+ if (!p->index_data) {
+ struct multi_pack_index *m;
+ const char *pack_name = strrchr(p->pack_name, '/');
+
+ for (m = the_repository->objects->multi_pack_index;
+ m; m = m->next) {
+ if (midx_contains_pack(m, pack_name))
+ break;
+ }
+
+ if (!m && open_pack_index(p))
+ return error("packfile %s index unavailable", p->pack_name);
+ }
if (!pack_max_fds) {
unsigned int max_fds = get_max_fd_limit();
" supported (try upgrading GIT to a newer version)",
p->pack_name, ntohl(hdr.hdr_version));
+ /* Skip index checking if in multi-pack-index */
+ if (!p->index_data)
+ return 0;
+
/* Verify the pack matches its index. */
if (p->num_objects != ntohl(hdr.hdr_entries))
return error("packfile %s claims to have %"PRIu32" objects"
if (read_result != hashsz)
return error("packfile %s signature is unavailable", p->pack_name);
idx_hash = ((unsigned char *)p->index_data) + p->index_size - hashsz * 2;
- if (hashcmp(hash, idx_hash))
+ if (!hasheq(hash, idx_hash))
return error("packfile %s does not match index", p->pack_name);
return 0;
}
report_helper(list, seen_bits, first, list->nr);
}
-static void prepare_packed_git_one(struct repository *r, char *objdir, int local)
+void for_each_file_in_pack_dir(const char *objdir,
+ each_file_in_pack_dir_fn fn,
+ void *data)
{
struct strbuf path = STRBUF_INIT;
size_t dirnamelen;
DIR *dir;
struct dirent *de;
- struct string_list garbage = STRING_LIST_INIT_DUP;
strbuf_addstr(&path, objdir);
strbuf_addstr(&path, "/pack");
strbuf_addch(&path, '/');
dirnamelen = path.len;
while ((de = readdir(dir)) != NULL) {
- struct packed_git *p;
- size_t base_len;
-
if (is_dot_or_dotdot(de->d_name))
continue;
strbuf_setlen(&path, dirnamelen);
strbuf_addstr(&path, de->d_name);
- base_len = path.len;
- if (strip_suffix_mem(path.buf, &base_len, ".idx")) {
- /* Don't reopen a pack we already have. */
- for (p = r->objects->packed_git; p;
- p = p->next) {
- size_t len;
- if (strip_suffix(p->pack_name, ".pack", &len) &&
- len == base_len &&
- !memcmp(p->pack_name, path.buf, len))
- break;
- }
- if (p == NULL &&
- /*
- * See if it really is a valid .idx file with
- * corresponding .pack file that we can map.
- */
- (p = add_packed_git(path.buf, path.len, local)) != NULL)
- install_packed_git(r, p);
- }
-
- if (!report_garbage)
- continue;
-
- if (ends_with(de->d_name, ".idx") ||
- ends_with(de->d_name, ".pack") ||
- ends_with(de->d_name, ".bitmap") ||
- ends_with(de->d_name, ".keep") ||
- ends_with(de->d_name, ".promisor"))
- string_list_append(&garbage, path.buf);
- else
- report_garbage(PACKDIR_FILE_GARBAGE, path.buf);
+ fn(path.buf, path.len, de->d_name, data);
}
+
closedir(dir);
- report_pack_garbage(&garbage);
- string_list_clear(&garbage, 0);
strbuf_release(&path);
}
+struct prepare_pack_data {
+ struct repository *r;
+ struct string_list *garbage;
+ int local;
+ struct multi_pack_index *m;
+};
+
+static void prepare_pack(const char *full_name, size_t full_name_len,
+ const char *file_name, void *_data)
+{
+ struct prepare_pack_data *data = (struct prepare_pack_data *)_data;
+ struct packed_git *p;
+ size_t base_len = full_name_len;
+
+ if (strip_suffix_mem(full_name, &base_len, ".idx") &&
+ !(data->m && midx_contains_pack(data->m, file_name))) {
+ /* Don't reopen a pack we already have. */
+ for (p = data->r->objects->packed_git; p; p = p->next) {
+ size_t len;
+ if (strip_suffix(p->pack_name, ".pack", &len) &&
+ len == base_len &&
+ !memcmp(p->pack_name, full_name, len))
+ break;
+ }
+
+ if (!p) {
+ p = add_packed_git(full_name, full_name_len, data->local);
+ if (p)
+ install_packed_git(data->r, p);
+ }
+ }
+
+ if (!report_garbage)
+ return;
+
+ if (!strcmp(file_name, "multi-pack-index"))
+ return;
+ if (ends_with(file_name, ".idx") ||
+ ends_with(file_name, ".pack") ||
+ ends_with(file_name, ".bitmap") ||
+ ends_with(file_name, ".keep") ||
+ ends_with(file_name, ".promisor"))
+ string_list_append(data->garbage, full_name);
+ else
+ report_garbage(PACKDIR_FILE_GARBAGE, full_name);
+}
+
+static void prepare_packed_git_one(struct repository *r, char *objdir, int local)
+{
+ struct prepare_pack_data data;
+ struct string_list garbage = STRING_LIST_INIT_DUP;
+
+ data.m = r->objects->multi_pack_index;
+
+ /* look for the multi-pack-index for this object directory */
+ while (data.m && strcmp(data.m->object_dir, objdir))
+ data.m = data.m->next;
+
+ data.r = r;
+ data.garbage = &garbage;
+ data.local = local;
+
+ for_each_file_in_pack_dir(objdir, prepare_pack, &data);
+
+ report_pack_garbage(data.garbage);
+ string_list_clear(data.garbage, 0);
+}
+
static void prepare_packed_git(struct repository *r);
/*
* Give a fast, rough count of the number of objects in the repository. This
{
if (!the_repository->objects->approximate_object_count_valid) {
unsigned long count;
+ struct multi_pack_index *m;
struct packed_git *p;
prepare_packed_git(the_repository);
count = 0;
+ for (m = get_multi_pack_index(the_repository); m; m = m->next)
+ count += m->num_objects;
for (p = the_repository->objects->packed_git; p; p = p->next) {
if (open_pack_index(p))
continue;
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)
+ 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);
+ }
rearrange_packed_git(r);
+
+ r->objects->all_packs = NULL;
+
prepare_packed_git_mru(r);
r->objects->packed_git_initialized = 1;
}
return r->objects->packed_git;
}
+struct multi_pack_index *get_multi_pack_index(struct repository *r)
+{
+ prepare_packed_git(r);
+ return r->objects->multi_pack_index;
+}
+
+struct packed_git *get_all_packs(struct repository *r)
+{
+ prepare_packed_git(r);
+
+ if (!r->objects->all_packs) {
+ struct packed_git *p = r->objects->packed_git;
+ struct multi_pack_index *m;
+
+ for (m = r->objects->multi_pack_index; m; m = m->next) {
+ uint32_t i;
+ for (i = 0; i < m->num_packs; i++) {
+ if (!prepare_midx_pack(m, i)) {
+ m->packs[i]->next = p;
+ p = m->packs[i];
+ }
+ }
+ }
+
+ r->objects->all_packs = p;
+ }
+
+ return r->objects->all_packs;
+}
+
struct list_head *get_packed_git_mru(struct repository *r)
{
prepare_packed_git(r);
void mark_bad_packed_object(struct packed_git *p, const unsigned char *sha1)
{
unsigned i;
+ const unsigned hashsz = the_hash_algo->rawsz;
for (i = 0; i < p->num_bad_objects; i++)
- if (!hashcmp(sha1, p->bad_object_sha1 + GIT_SHA1_RAWSZ * i))
+ if (hasheq(sha1, p->bad_object_sha1 + hashsz * i))
return;
p->bad_object_sha1 = xrealloc(p->bad_object_sha1,
st_mult(GIT_MAX_RAWSZ,
st_add(p->num_bad_objects, 1)));
- hashcpy(p->bad_object_sha1 + GIT_SHA1_RAWSZ * p->num_bad_objects, sha1);
+ hashcpy(p->bad_object_sha1 + hashsz * p->num_bad_objects, sha1);
p->num_bad_objects++;
}
for (p = the_repository->objects->packed_git; p; p = p->next)
for (i = 0; i < p->num_bad_objects; i++)
- if (!hashcmp(sha1,
- p->bad_object_sha1 + the_hash_algo->rawsz * i))
+ if (hasheq(sha1,
+ p->bad_object_sha1 + the_hash_algo->rawsz * i))
return p;
return NULL;
}
if (p->num_bad_objects) {
unsigned i;
for (i = 0; i < p->num_bad_objects; i++)
- if (!hashcmp(oid->hash,
- p->bad_object_sha1 + the_hash_algo->rawsz * i))
+ if (hasheq(oid->hash,
+ p->bad_object_sha1 + the_hash_algo->rawsz * i))
return 0;
}
int find_pack_entry(struct repository *r, const struct object_id *oid, struct pack_entry *e)
{
struct list_head *pos;
+ struct multi_pack_index *m;
prepare_packed_git(r);
- if (!r->objects->packed_git)
+ if (!r->objects->packed_git && !r->objects->multi_pack_index)
return 0;
+ for (m = r->objects->multi_pack_index; m; m = m->next) {
+ if (fill_midx_entry(oid, e, m))
+ return 1;
+ }
+
list_for_each(pos, &r->objects->packed_git_mru) {
struct packed_git *p = list_entry(pos, struct packed_git, mru);
if (fill_pack_entry(oid, e, p)) {
int pack_errors = 0;
prepare_packed_git(the_repository);
- for (p = the_repository->objects->packed_git; p; p = p->next) {
+ for (p = get_all_packs(the_repository); p; p = p->next) {
if ((flags & FOR_EACH_OBJECT_LOCAL_ONLY) && !p->pack_local)
continue;
if ((flags & FOR_EACH_OBJECT_PROMISOR_ONLY) &&
extern struct packed_git *parse_pack_index(unsigned char *sha1, const char *idx_path);
+typedef void each_file_in_pack_dir_fn(const char *full_path, size_t full_path_len,
+ const char *file_pach, void *data);
+void for_each_file_in_pack_dir(const char *objdir,
+ each_file_in_pack_dir_fn fn,
+ void *data);
+
/* A hook to report invalid files in pack directory */
#define PACKDIR_FILE_PACK 1
#define PACKDIR_FILE_IDX 2
struct packed_git *get_packed_git(struct repository *r);
struct list_head *get_packed_git_mru(struct repository *r);
+struct multi_pack_index *get_multi_pack_index(struct repository *r);
+struct packed_git *get_all_packs(struct repository *r);
/*
* Give a rough count of objects in the repository. This sacrifices accuracy
*/
extern void close_pack_index(struct packed_git *);
+extern uint32_t get_pack_fanout(struct packed_git *p, uint32_t value);
+
extern unsigned char *use_pack(struct packed_git *, struct pack_window **, off_t, unsigned long *);
extern void close_pack_windows(struct packed_git *);
extern void close_pack(struct packed_git *);
*/
extern int is_promisor_object(const struct object_id *oid);
+/*
+ * Expose a function for fuzz testing.
+ *
+ * load_idx() parses a block of memory as a packfile index and puts the results
+ * into a struct packed_git.
+ *
+ * This function should not be used directly. It is exposed here only so that we
+ * have a convenient entry-point for fuzz testing. For real uses, you should
+ * probably use open_pack_index() or parse_pack_index() instead.
+ */
+extern int load_idx(const char *path, const unsigned int hashsz, void *idx_map,
+ size_t idx_size, struct packed_git *p);
+
#endif
cmd = *data++;
if (cmd & 0x80) {
unsigned long cp_off = 0, cp_size = 0;
- if (cmd & 0x01) cp_off = *data++;
- if (cmd & 0x02) cp_off |= (*data++ << 8);
- if (cmd & 0x04) cp_off |= (*data++ << 16);
- if (cmd & 0x08) cp_off |= ((unsigned) *data++ << 24);
- if (cmd & 0x10) cp_size = *data++;
- if (cmd & 0x20) cp_size |= (*data++ << 8);
- if (cmd & 0x40) cp_size |= (*data++ << 16);
+#define PARSE_CP_PARAM(bit, var, shift) do { \
+ if (cmd & (bit)) { \
+ if (data >= top) \
+ goto bad_length; \
+ var |= ((unsigned) *data++ << (shift)); \
+ } } while (0)
+ PARSE_CP_PARAM(0x01, cp_off, 0);
+ PARSE_CP_PARAM(0x02, cp_off, 8);
+ PARSE_CP_PARAM(0x04, cp_off, 16);
+ PARSE_CP_PARAM(0x08, cp_off, 24);
+ PARSE_CP_PARAM(0x10, cp_size, 0);
+ PARSE_CP_PARAM(0x20, cp_size, 8);
+ PARSE_CP_PARAM(0x40, cp_size, 16);
+#undef PARSE_CP_PARAM
if (cp_size == 0) cp_size = 0x10000;
if (unsigned_add_overflows(cp_off, cp_size) ||
cp_off + cp_size > src_size ||
cp_size > size)
- break;
+ goto bad_length;
memcpy(out, (char *) src_buf + cp_off, cp_size);
out += cp_size;
size -= cp_size;
} else if (cmd) {
- if (cmd > size)
- break;
+ if (cmd > size || cmd > top - data)
+ goto bad_length;
memcpy(out, data, cmd);
out += cmd;
data += cmd;
/* sanity check */
if (data != top || size != 0) {
+ bad_length:
error("delta replay has gone wild");
bad:
free(dst_buf);
/*
* When we cannot load the full patch-id for both commits for whatever
* reason, the function returns -1 (i.e. return error(...)). Despite
- * the "cmp" in the name of this function, the caller only cares about
+ * the "neq" in the name of this function, the caller only cares about
* the return value being zero (a and b are equivalent) or non-zero (a
* and b are different), and returning non-zero would keep both in the
* result, even if they actually were equivalent, in order to err on
* the side of safety. The actual value being negative does not have
* any significance; only that it is non-zero matters.
*/
-static int patch_id_cmp(const void *cmpfn_data,
+static int patch_id_neq(const void *cmpfn_data,
const void *entry,
const void *entry_or_key,
const void *unused_keydata)
commit_patch_id(b->commit, opt, &b->patch_id, 0))
return error("Could not get patch ID for %s",
oid_to_hex(&b->commit->object.oid));
- return oidcmp(&a->patch_id, &b->patch_id);
+ return !oideq(&a->patch_id, &b->patch_id);
}
-int init_patch_ids(struct patch_ids *ids)
+int init_patch_ids(struct repository *r, struct patch_ids *ids)
{
memset(ids, 0, sizeof(*ids));
- diff_setup(&ids->diffopts);
+ repo_diff_setup(r, &ids->diffopts);
ids->diffopts.detect_rename = 0;
ids->diffopts.flags.recursive = 1;
diff_setup_done(&ids->diffopts);
- hashmap_init(&ids->patches, patch_id_cmp, &ids->diffopts, 256);
+ hashmap_init(&ids->patches, patch_id_neq, &ids->diffopts, 256);
return 0;
}
struct commit;
struct object_id;
+struct repository;
struct patch_id {
struct hashmap_entry ent;
int commit_patch_id(struct commit *commit, struct diff_options *options,
struct object_id *oid, int);
-int init_patch_ids(struct patch_ids *);
+int init_patch_ids(struct repository *, struct patch_ids *);
int free_patch_ids(struct patch_ids *);
struct patch_id *add_commit_patch_id(struct commit *, struct patch_ids *);
struct patch_id *has_commit_patch_id(struct commit *, struct patch_ids *);
#include "pathspec.h"
#include "dir.h"
#include "fsmonitor.h"
+#include "config.h"
+#include "progress.h"
#ifdef NO_PTHREADS
static void preload_index(struct index_state *index,
- const struct pathspec *pathspec)
+ const struct pathspec *pathspec,
+ unsigned int refresh_flags)
{
; /* nothing */
}
#define MAX_PARALLEL (20)
#define THREAD_COST (500)
+struct progress_data {
+ unsigned long n;
+ struct progress *progress;
+ pthread_mutex_t mutex;
+};
+
struct thread_data {
pthread_t pthread;
struct index_state *index;
struct pathspec pathspec;
+ struct progress_data *progress;
int offset, nr;
};
static void *preload_thread(void *_data)
{
- int nr;
+ int nr, last_nr;
struct thread_data *p = _data;
struct index_state *index = p->index;
struct cache_entry **cep = index->cache + p->offset;
nr = p->nr;
if (nr + p->offset > index->cache_nr)
nr = index->cache_nr - p->offset;
+ last_nr = nr;
do {
struct cache_entry *ce = *cep++;
continue;
if (ce->ce_flags & CE_FSMONITOR_VALID)
continue;
+ if (p->progress && !(nr & 31)) {
+ struct progress_data *pd = p->progress;
+
+ pthread_mutex_lock(&pd->mutex);
+ pd->n += last_nr - nr;
+ display_progress(pd->progress, pd->n);
+ pthread_mutex_unlock(&pd->mutex);
+ last_nr = nr;
+ }
if (!ce_path_match(index, ce, &p->pathspec, NULL))
continue;
if (threaded_has_symlink_leading_path(&cache, ce->name, ce_namelen(ce)))
ce_mark_uptodate(ce);
mark_fsmonitor_valid(ce);
} while (--nr > 0);
+ if (p->progress) {
+ struct progress_data *pd = p->progress;
+
+ pthread_mutex_lock(&pd->mutex);
+ display_progress(pd->progress, pd->n + last_nr);
+ pthread_mutex_unlock(&pd->mutex);
+ }
cache_def_clear(&cache);
return NULL;
}
static void preload_index(struct index_state *index,
- const struct pathspec *pathspec)
+ const struct pathspec *pathspec,
+ unsigned int refresh_flags)
{
int threads, i, work, offset;
struct thread_data data[MAX_PARALLEL];
- uint64_t start = getnanotime();
+ struct progress_data pd;
if (!core_preload_index)
return;
threads = index->cache_nr / THREAD_COST;
- if ((index->cache_nr > 1) && (threads < 2) && getenv("GIT_FORCE_PRELOAD_TEST"))
+ if ((index->cache_nr > 1) && (threads < 2) && git_env_bool("GIT_TEST_PRELOAD_INDEX", 0))
threads = 2;
if (threads < 2)
return;
+ trace_performance_enter();
if (threads > MAX_PARALLEL)
threads = MAX_PARALLEL;
offset = 0;
work = DIV_ROUND_UP(index->cache_nr, threads);
memset(&data, 0, sizeof(data));
+
+ memset(&pd, 0, sizeof(pd));
+ if (refresh_flags & REFRESH_PROGRESS && isatty(2)) {
+ pd.progress = start_delayed_progress(_("Refreshing index"), index->cache_nr);
+ pthread_mutex_init(&pd.mutex, NULL);
+ }
+
for (i = 0; i < threads; i++) {
struct thread_data *p = data+i;
p->index = index;
copy_pathspec(&p->pathspec, pathspec);
p->offset = offset;
p->nr = work;
+ if (pd.progress)
+ p->progress = &pd;
offset += work;
if (pthread_create(&p->pthread, NULL, preload_thread, p))
die("unable to create threaded lstat");
if (pthread_join(p->pthread, NULL))
die("unable to join threaded lstat");
}
- trace_performance_since(start, "preload index");
+ stop_progress(&pd.progress);
+
+ trace_performance_leave("preload index");
}
#endif
int read_index_preload(struct index_state *index,
- const struct pathspec *pathspec)
+ const struct pathspec *pathspec,
+ unsigned int refresh_flags)
{
int retval = read_index(index);
- preload_index(index, pathspec);
+ preload_index(index, pathspec, refresh_flags);
return retval;
}
if (skip_prefix(placeholder, "(trailers", &arg)) {
struct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT;
+
+ opts.no_divider = 1;
+
if (*arg == ':') {
arg++;
for (;;) {
argv_array_pushl(&cp.args, "log", "--no-color", "-p", "--no-merges",
"--reverse", "--date-order", "--decorate=no",
+ /*
+ * Choose indicators that are not used anywhere
+ * else in diffs, but still look reasonable
+ * (e.g. will not be confusing when debugging)
+ */
+ "--output-indicator-new=>",
+ "--output-indicator-old=<",
+ "--output-indicator-context=#",
"--no-abbrev-commit", range,
NULL);
cp.out = -1;
strbuf_addch(&buf, '\n');
if (!util->diff_offset)
util->diff_offset = buf.len;
+ strbuf_addch(&buf, ' ');
strbuf_addbuf(&buf, &line);
} else if (in_header) {
if (starts_with(line.buf, "Author: ")) {
* we are not interested.
*/
continue;
- else
+ else if (line.buf[0] == '>') {
+ strbuf_addch(&buf, '+');
+ strbuf_add(&buf, line.buf + 1, line.len - 1);
+ } else if (line.buf[0] == '<') {
+ strbuf_addch(&buf, '-');
+ strbuf_add(&buf, line.buf + 1, line.len - 1);
+ } else if (line.buf[0] == '#') {
+ strbuf_addch(&buf, ' ');
+ strbuf_add(&buf, line.buf + 1, line.len - 1);
+ } else {
+ strbuf_addch(&buf, ' ');
strbuf_addbuf(&buf, &line);
+ }
strbuf_addch(&buf, '\n');
util->diffsize++;
}
strbuf_addf(buf, "%s\n", color_reset);
- fwrite(buf->buf, buf->len, 1, stdout);
+ fwrite(buf->buf, buf->len, 1, diffopt->file);
}
static struct userdiff_driver no_func_name = {
{
struct diff_filespec *spec = alloc_filespec(name);
- fill_filespec(spec, &null_oid, 0, 0644);
+ fill_filespec(spec, &null_oid, 0, 0100644);
spec->data = (char *)p;
spec->size = strlen(p);
spec->should_munmap = 0;
strbuf_release(&dashes);
}
+static struct strbuf *output_prefix_cb(struct diff_options *opt, void *data)
+{
+ return data;
+}
+
int show_range_diff(const char *range1, const char *range2,
- int creation_factor, struct diff_options *diffopt)
+ int creation_factor, int dual_color,
+ struct diff_options *diffopt)
{
int res = 0;
res = error(_("could not parse log for '%s'"), range2);
if (!res) {
+ struct diff_options opts;
+ struct strbuf indent = STRBUF_INIT;
+
+ memcpy(&opts, diffopt, sizeof(opts));
+ opts.output_format = DIFF_FORMAT_PATCH;
+ opts.flags.suppress_diff_headers = 1;
+ opts.flags.dual_color_diffed_diffs = dual_color;
+ opts.output_prefix = output_prefix_cb;
+ strbuf_addstr(&indent, " ");
+ opts.output_prefix_data = &indent;
+ diff_setup_done(&opts);
+
find_exact_matches(&branch1, &branch2);
get_correspondences(&branch1, &branch2, creation_factor);
- output(&branch1, &branch2, diffopt);
+ output(&branch1, &branch2, &opts);
+
+ strbuf_release(&indent);
}
string_list_clear(&branch1, 1);
#include "diff.h"
+#define RANGE_DIFF_CREATION_FACTOR_DEFAULT 60
+
int show_range_diff(const char *range1, const char *range2,
- int creation_factor, struct diff_options *diffopt);
+ int creation_factor, int dual_color,
+ struct diff_options *diffopt);
#endif
#include "split-index.h"
#include "utf8.h"
#include "fsmonitor.h"
+#include "thread-utils.h"
+#include "progress.h"
/* Mask for the name length in ce_flags in the on-disk index */
#define CACHE_EXT_LINK 0x6c696e6b /* "link" */
#define CACHE_EXT_UNTRACKED 0x554E5452 /* "UNTR" */
#define CACHE_EXT_FSMONITOR 0x46534D4E /* "FSMN" */
+#define CACHE_EXT_ENDOFINDEXENTRIES 0x454F4945 /* "EOIE" */
+#define CACHE_EXT_INDEXENTRYOFFSETTABLE 0x49454F54 /* "IEOT" */
/* changes that can be kept in $GIT_DIR/index (basically all extensions) */
#define EXTMASK (RESOLVE_UNDO_CHANGED | CACHE_TREE_CHANGED | \
}
}
-static int ce_compare_data(const struct cache_entry *ce, struct stat *st)
+static int ce_compare_data(struct index_state *istate,
+ const struct cache_entry *ce,
+ struct stat *st)
{
int match = -1;
int fd = git_open_cloexec(ce->name, O_RDONLY);
if (fd >= 0) {
struct object_id oid;
- if (!index_fd(&oid, fd, st, OBJ_BLOB, ce->name, 0))
- match = oidcmp(&oid, &ce->oid);
+ if (!index_fd(istate, &oid, fd, st, OBJ_BLOB, ce->name, 0))
+ match = !oideq(&oid, &ce->oid);
/* index_fd() closed the file descriptor already */
}
return match;
*/
if (resolve_gitlink_ref(ce->name, "HEAD", &oid) < 0)
return 0;
- return oidcmp(&oid, &ce->oid);
+ return !oideq(&oid, &ce->oid);
}
-static int ce_modified_check_fs(const struct cache_entry *ce, struct stat *st)
+static int ce_modified_check_fs(struct index_state *istate,
+ const struct cache_entry *ce,
+ struct stat *st)
{
switch (st->st_mode & S_IFMT) {
case S_IFREG:
- if (ce_compare_data(ce, st))
+ if (ce_compare_data(istate, ce, st))
return DATA_CHANGED;
break;
case S_IFLNK:
);
}
-static int is_racy_timestamp(const struct index_state *istate,
+int is_racy_timestamp(const struct index_state *istate,
const struct cache_entry *ce)
{
return (!S_ISGITLINK(ce->ce_mode) &&
if (assume_racy_is_modified)
changed |= DATA_CHANGED;
else
- changed |= ce_modified_check_fs(ce, st);
+ changed |= ce_modified_check_fs(istate, ce, st);
}
return changed;
(S_ISGITLINK(ce->ce_mode) || ce->ce_stat_data.sd_size != 0))
return changed;
- changed_fs = ce_modified_check_fs(ce, st);
+ changed_fs = ce_modified_check_fs(istate, ce, st);
if (changed_fs)
return changed | changed_fs;
return 0;
}
}
if (!intent_only) {
- if (index_path(&ce->oid, path, st, newflags)) {
+ if (index_path(istate, &ce->oid, path, st, newflags)) {
discard_cache_entry(ce);
return error("unable to index file %s", path);
}
/* It was suspected to be racily clean, but it turns out to be Ok */
was_same = (alias &&
!ce_stage(alias) &&
- !oidcmp(&alias->oid, &ce->oid) &&
+ oideq(&alias->oid, &ce->oid) &&
ce->ce_mode == alias->ce_mode);
if (pretend)
ce->ce_namelen = len;
ce->ce_mode = create_ce_mode(mode);
- ret = refresh_cache_entry(&the_index, ce, refresh_options);
+ ret = refresh_cache_entry(istate, ce, refresh_options);
if (ret != ce)
discard_cache_entry(ce);
return ret;
const char *typechange_fmt;
const char *added_fmt;
const char *unmerged_fmt;
- uint64_t start = getnanotime();
+ struct progress *progress = NULL;
+
+ if (flags & REFRESH_PROGRESS && isatty(2))
+ progress = start_delayed_progress(_("Refresh index"),
+ 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");
if (ignore_submodules && S_ISGITLINK(ce->ce_mode))
continue;
- if (pathspec && !ce_path_match(&the_index, ce, pathspec, seen))
+ if (pathspec && !ce_path_match(istate, ce, pathspec, seen))
filtered = 1;
if (ce_stage(ce)) {
new_entry = refresh_cache_ent(istate, ce, options, &cache_errno, &changed);
if (new_entry == ce)
continue;
+ if (progress)
+ display_progress(progress, i);
if (!new_entry) {
const char *fmt;
replace_index_entry(istate, i, new_entry);
}
- trace_performance_since(start, "refresh index");
+ if (progress) {
+ display_progress(progress, istate->cache_nr);
+ stop_progress(&progress);
+ }
+ trace_performance_leave("refresh index");
return has_errors;
}
/* Allow fsck to force verification of the cache entry order. */
int verify_ce_order;
-static int verify_hdr(struct cache_header *hdr, unsigned long size)
+static int verify_hdr(const struct cache_header *hdr, unsigned long size)
{
git_hash_ctx c;
unsigned char hash[GIT_MAX_RAWSZ];
the_hash_algo->init_fn(&c);
the_hash_algo->update_fn(&c, hdr, size - the_hash_algo->rawsz);
the_hash_algo->final_fn(hash, &c);
- if (hashcmp(hash, (unsigned char *)hdr + size - the_hash_algo->rawsz))
+ if (!hasheq(hash, (unsigned char *)hdr + size - the_hash_algo->rawsz))
return error("bad index file sha1 signature");
return 0;
}
static int read_index_extension(struct index_state *istate,
- const char *ext, void *data, unsigned long sz)
+ const char *ext, const char *data, unsigned long sz)
{
switch (CACHE_EXT(ext)) {
case CACHE_EXT_TREE:
case CACHE_EXT_FSMONITOR:
read_fsmonitor_extension(istate, data, sz);
break;
+ case CACHE_EXT_ENDOFINDEXENTRIES:
+ case CACHE_EXT_INDEXENTRYOFFSETTABLE:
+ /* already handled in do_read_index() */
+ break;
default:
if (*ext < 'A' || 'Z' < *ext)
return error("index uses %.4s extension, which we do not understand",
return read_index_from(istate, get_index_file(), get_git_dir());
}
-static struct cache_entry *cache_entry_from_ondisk(struct mem_pool *mem_pool,
- struct ondisk_cache_entry *ondisk,
- unsigned int flags,
- const char *name,
- size_t len)
-{
- struct cache_entry *ce = mem_pool__ce_alloc(mem_pool, len);
-
- ce->ce_stat_data.sd_ctime.sec = get_be32(&ondisk->ctime.sec);
- ce->ce_stat_data.sd_mtime.sec = get_be32(&ondisk->mtime.sec);
- ce->ce_stat_data.sd_ctime.nsec = get_be32(&ondisk->ctime.nsec);
- ce->ce_stat_data.sd_mtime.nsec = get_be32(&ondisk->mtime.nsec);
- ce->ce_stat_data.sd_dev = get_be32(&ondisk->dev);
- ce->ce_stat_data.sd_ino = get_be32(&ondisk->ino);
- ce->ce_mode = get_be32(&ondisk->mode);
- ce->ce_stat_data.sd_uid = get_be32(&ondisk->uid);
- ce->ce_stat_data.sd_gid = get_be32(&ondisk->gid);
- ce->ce_stat_data.sd_size = get_be32(&ondisk->size);
- ce->ce_flags = flags & ~CE_NAMEMASK;
- ce->ce_namelen = len;
- ce->index = 0;
- hashcpy(ce->oid.hash, ondisk->sha1);
- memcpy(ce->name, name, len);
- ce->name[len] = '\0';
- return ce;
-}
-
-/*
- * Adjacent cache entries tend to share the leading paths, so it makes
- * sense to only store the differences in later entries. In the v4
- * on-disk format of the index, each on-disk cache entry stores the
- * number of bytes to be stripped from the end of the previous name,
- * and the bytes to append to the result, to come up with its name.
- */
-static unsigned long expand_name_field(struct strbuf *name, const char *cp_)
-{
- const unsigned char *ep, *cp = (const unsigned char *)cp_;
- size_t len = decode_varint(&cp);
-
- if (name->len < len)
- die("malformed name field in the index");
- strbuf_remove(name, name->len - len, len);
- for (ep = cp; *ep; ep++)
- ; /* find the end */
- strbuf_add(name, cp, ep - cp);
- return (const char *)ep + 1 - cp_;
-}
-
-static struct cache_entry *create_from_disk(struct mem_pool *mem_pool,
+static struct cache_entry *create_from_disk(struct mem_pool *ce_mem_pool,
+ unsigned int version,
struct ondisk_cache_entry *ondisk,
unsigned long *ent_size,
- struct strbuf *previous_name)
+ const struct cache_entry *previous_ce)
{
struct cache_entry *ce;
size_t len;
const char *name;
unsigned int flags;
+ size_t copy_len;
+ /*
+ * Adjacent cache entries tend to share the leading paths, so it makes
+ * sense to only store the differences in later entries. In the v4
+ * on-disk format of the index, each on-disk cache entry stores the
+ * number of bytes to be stripped from the end of the previous name,
+ * and the bytes to append to the result, to come up with its name.
+ */
+ int expand_name_field = version == 4;
/* On-disk flags are just 16 bits */
flags = get_be16(&ondisk->flags);
else
name = ondisk->name;
- if (!previous_name) {
- /* v3 and earlier */
- if (len == CE_NAMEMASK)
- len = strlen(name);
- ce = cache_entry_from_ondisk(mem_pool, ondisk, flags, name, len);
+ if (expand_name_field) {
+ const unsigned char *cp = (const unsigned char *)name;
+ size_t strip_len, previous_len;
+
+ /* If we're at the begining of a block, ignore the previous name */
+ strip_len = decode_varint(&cp);
+ if (previous_ce) {
+ previous_len = previous_ce->ce_namelen;
+ if (previous_len < strip_len)
+ die(_("malformed name field in the index, near path '%s'"),
+ previous_ce->name);
+ copy_len = previous_len - strip_len;
+ } else {
+ copy_len = 0;
+ }
+ name = (const char *)cp;
+ }
- *ent_size = ondisk_ce_size(ce);
- } else {
- unsigned long consumed;
- consumed = expand_name_field(previous_name, name);
- ce = cache_entry_from_ondisk(mem_pool, ondisk, flags,
- previous_name->buf,
- previous_name->len);
+ if (len == CE_NAMEMASK) {
+ len = strlen(name);
+ if (expand_name_field)
+ len += copy_len;
+ }
- *ent_size = (name - ((char *)ondisk)) + consumed;
+ ce = mem_pool__ce_alloc(ce_mem_pool, len);
+
+ ce->ce_stat_data.sd_ctime.sec = get_be32(&ondisk->ctime.sec);
+ ce->ce_stat_data.sd_mtime.sec = get_be32(&ondisk->mtime.sec);
+ ce->ce_stat_data.sd_ctime.nsec = get_be32(&ondisk->ctime.nsec);
+ ce->ce_stat_data.sd_mtime.nsec = get_be32(&ondisk->mtime.nsec);
+ ce->ce_stat_data.sd_dev = get_be32(&ondisk->dev);
+ ce->ce_stat_data.sd_ino = get_be32(&ondisk->ino);
+ ce->ce_mode = get_be32(&ondisk->mode);
+ ce->ce_stat_data.sd_uid = get_be32(&ondisk->uid);
+ ce->ce_stat_data.sd_gid = get_be32(&ondisk->gid);
+ ce->ce_stat_data.sd_size = get_be32(&ondisk->size);
+ ce->ce_flags = flags & ~CE_NAMEMASK;
+ ce->ce_namelen = len;
+ ce->index = 0;
+ hashcpy(ce->oid.hash, ondisk->sha1);
+
+ if (expand_name_field) {
+ if (copy_len)
+ memcpy(ce->name, previous_ce->name, copy_len);
+ memcpy(ce->name + copy_len, name, len + 1 - copy_len);
+ *ent_size = (name - ((char *)ondisk)) + len + 1 - copy_len;
+ } else {
+ memcpy(ce->name, name, len + 1);
+ *ent_size = ondisk_ce_size(ce);
}
return ce;
}
return ondisk_size + entries * per_entry;
}
+struct index_entry_offset
+{
+ /* starting byte offset into index file, count of index entries in this block */
+ int offset, nr;
+};
+
+struct index_entry_offset_table
+{
+ int nr;
+ struct index_entry_offset entries[FLEX_ARRAY];
+};
+
+#ifndef NO_PTHREADS
+static struct index_entry_offset_table *read_ieot_extension(const char *mmap, size_t mmap_size, size_t offset);
+static void write_ieot_extension(struct strbuf *sb, struct index_entry_offset_table *ieot);
+#endif
+
+static size_t read_eoie_extension(const char *mmap, size_t mmap_size);
+static void write_eoie_extension(struct strbuf *sb, git_hash_ctx *eoie_context, size_t offset);
+
+struct load_index_extensions
+{
+#ifndef NO_PTHREADS
+ pthread_t pthread;
+#endif
+ struct index_state *istate;
+ const char *mmap;
+ size_t mmap_size;
+ unsigned long src_offset;
+};
+
+static void *load_index_extensions(void *_data)
+{
+ struct load_index_extensions *p = _data;
+ unsigned long src_offset = p->src_offset;
+
+ while (src_offset <= p->mmap_size - the_hash_algo->rawsz - 8) {
+ /* After an array of active_nr index entries,
+ * there can be arbitrary number of extended
+ * sections, each of which is prefixed with
+ * extension name (4-byte) and section length
+ * in 4-byte network byte order.
+ */
+ uint32_t extsize = get_be32(p->mmap + src_offset + 4);
+ if (read_index_extension(p->istate,
+ p->mmap + src_offset,
+ p->mmap + src_offset + 8,
+ extsize) < 0) {
+ munmap((void *)p->mmap, p->mmap_size);
+ die(_("index file corrupt"));
+ }
+ src_offset += 8;
+ src_offset += extsize;
+ }
+
+ return NULL;
+}
+
+/*
+ * A helper function that will load the specified range of cache entries
+ * from the memory mapped file and add them to the given index.
+ */
+static unsigned long load_cache_entry_block(struct index_state *istate,
+ struct mem_pool *ce_mem_pool, int offset, int nr, const char *mmap,
+ unsigned long start_offset, const struct cache_entry *previous_ce)
+{
+ int i;
+ unsigned long src_offset = start_offset;
+
+ for (i = offset; i < offset + nr; i++) {
+ struct ondisk_cache_entry *disk_ce;
+ struct cache_entry *ce;
+ unsigned long consumed;
+
+ disk_ce = (struct ondisk_cache_entry *)(mmap + src_offset);
+ ce = create_from_disk(ce_mem_pool, istate->version, disk_ce, &consumed, previous_ce);
+ set_index_entry(istate, i, ce);
+
+ src_offset += consumed;
+ previous_ce = ce;
+ }
+ return src_offset - start_offset;
+}
+
+static unsigned long load_all_cache_entries(struct index_state *istate,
+ const char *mmap, size_t mmap_size, unsigned long src_offset)
+{
+ unsigned long consumed;
+
+ if (istate->version == 4) {
+ mem_pool_init(&istate->ce_mem_pool,
+ estimate_cache_size_from_compressed(istate->cache_nr));
+ } else {
+ mem_pool_init(&istate->ce_mem_pool,
+ estimate_cache_size(mmap_size, istate->cache_nr));
+ }
+
+ consumed = load_cache_entry_block(istate, istate->ce_mem_pool,
+ 0, istate->cache_nr, mmap, src_offset, NULL);
+ return consumed;
+}
+
+#ifndef NO_PTHREADS
+
+/*
+ * Mostly randomly chosen maximum thread counts: we
+ * cap the parallelism to online_cpus() threads, and we want
+ * to have at least 10000 cache entries per thread for it to
+ * be worth starting a thread.
+ */
+
+#define THREAD_COST (10000)
+
+struct load_cache_entries_thread_data
+{
+ pthread_t pthread;
+ struct index_state *istate;
+ struct mem_pool *ce_mem_pool;
+ int offset;
+ const char *mmap;
+ struct index_entry_offset_table *ieot;
+ int ieot_start; /* starting index into the ieot array */
+ int ieot_blocks; /* count of ieot entries to process */
+ unsigned long consumed; /* return # of bytes in index file processed */
+};
+
+/*
+ * A thread proc to run the load_cache_entries() computation
+ * across multiple background threads.
+ */
+static void *load_cache_entries_thread(void *_data)
+{
+ struct load_cache_entries_thread_data *p = _data;
+ int i;
+
+ /* iterate across all ieot blocks assigned to this thread */
+ for (i = p->ieot_start; i < p->ieot_start + p->ieot_blocks; i++) {
+ p->consumed += load_cache_entry_block(p->istate, p->ce_mem_pool,
+ p->offset, p->ieot->entries[i].nr, p->mmap, p->ieot->entries[i].offset, NULL);
+ p->offset += p->ieot->entries[i].nr;
+ }
+ return NULL;
+}
+
+static unsigned long load_cache_entries_threaded(struct index_state *istate, const char *mmap, size_t mmap_size,
+ unsigned long src_offset, int nr_threads, struct index_entry_offset_table *ieot)
+{
+ int i, offset, ieot_blocks, ieot_start, err;
+ struct load_cache_entries_thread_data *data;
+ unsigned long consumed = 0;
+
+ /* a little sanity checking */
+ if (istate->name_hash_initialized)
+ BUG("the name hash isn't thread safe");
+
+ mem_pool_init(&istate->ce_mem_pool, 0);
+
+ /* ensure we have no more threads than we have blocks to process */
+ if (nr_threads > ieot->nr)
+ nr_threads = ieot->nr;
+ data = xcalloc(nr_threads, sizeof(*data));
+
+ offset = ieot_start = 0;
+ ieot_blocks = DIV_ROUND_UP(ieot->nr, nr_threads);
+ for (i = 0; i < nr_threads; i++) {
+ struct load_cache_entries_thread_data *p = &data[i];
+ int nr, j;
+
+ if (ieot_start + ieot_blocks > ieot->nr)
+ ieot_blocks = ieot->nr - ieot_start;
+
+ p->istate = istate;
+ p->offset = offset;
+ p->mmap = mmap;
+ p->ieot = ieot;
+ p->ieot_start = ieot_start;
+ p->ieot_blocks = ieot_blocks;
+
+ /* create a mem_pool for each thread */
+ nr = 0;
+ for (j = p->ieot_start; j < p->ieot_start + p->ieot_blocks; j++)
+ nr += p->ieot->entries[j].nr;
+ if (istate->version == 4) {
+ mem_pool_init(&p->ce_mem_pool,
+ estimate_cache_size_from_compressed(nr));
+ } else {
+ mem_pool_init(&p->ce_mem_pool,
+ estimate_cache_size(mmap_size, nr));
+ }
+
+ err = pthread_create(&p->pthread, NULL, load_cache_entries_thread, p);
+ if (err)
+ die(_("unable to create load_cache_entries thread: %s"), strerror(err));
+
+ /* increment by the number of cache entries in the ieot block being processed */
+ for (j = 0; j < ieot_blocks; j++)
+ offset += ieot->entries[ieot_start + j].nr;
+ ieot_start += ieot_blocks;
+ }
+
+ for (i = 0; i < nr_threads; i++) {
+ struct load_cache_entries_thread_data *p = &data[i];
+
+ err = pthread_join(p->pthread, NULL);
+ if (err)
+ die(_("unable to join load_cache_entries thread: %s"), strerror(err));
+ mem_pool_combine(istate->ce_mem_pool, p->ce_mem_pool);
+ consumed += p->consumed;
+ }
+
+ free(data);
+
+ return consumed;
+}
+#endif
+
/* remember to discard_cache() before reading a different cache! */
int do_read_index(struct index_state *istate, const char *path, int must_exist)
{
- int fd, i;
+ int fd;
struct stat st;
unsigned long src_offset;
- struct cache_header *hdr;
- void *mmap;
+ const struct cache_header *hdr;
+ const char *mmap;
size_t mmap_size;
- struct strbuf previous_name_buf = STRBUF_INIT, *previous_name;
+ struct load_index_extensions p;
+ size_t extension_offset = 0;
+#ifndef NO_PTHREADS
+ int nr_threads, cpus;
+ struct index_entry_offset_table *ieot = NULL;
+#endif
if (istate->initialized)
return istate->cache_nr;
die_errno("unable to map index file");
close(fd);
- hdr = mmap;
+ hdr = (const struct cache_header *)mmap;
if (verify_hdr(hdr, mmap_size) < 0)
goto unmap;
istate->cache = xcalloc(istate->cache_alloc, sizeof(*istate->cache));
istate->initialized = 1;
- if (istate->version == 4) {
- previous_name = &previous_name_buf;
- mem_pool_init(&istate->ce_mem_pool,
- estimate_cache_size_from_compressed(istate->cache_nr));
- } else {
- previous_name = NULL;
- mem_pool_init(&istate->ce_mem_pool,
- estimate_cache_size(mmap_size, istate->cache_nr));
- }
+ p.istate = istate;
+ p.mmap = mmap;
+ p.mmap_size = mmap_size;
src_offset = sizeof(*hdr);
- for (i = 0; i < istate->cache_nr; i++) {
- struct ondisk_cache_entry *disk_ce;
- struct cache_entry *ce;
- unsigned long consumed;
- disk_ce = (struct ondisk_cache_entry *)((char *)mmap + src_offset);
- ce = create_from_disk(istate->ce_mem_pool, disk_ce, &consumed, previous_name);
- set_index_entry(istate, i, ce);
+#ifndef NO_PTHREADS
+ nr_threads = git_config_get_index_threads();
- src_offset += consumed;
+ /* TODO: does creating more threads than cores help? */
+ if (!nr_threads) {
+ nr_threads = istate->cache_nr / THREAD_COST;
+ cpus = online_cpus();
+ if (nr_threads > cpus)
+ nr_threads = cpus;
}
- strbuf_release(&previous_name_buf);
+
+ if (nr_threads > 1) {
+ extension_offset = read_eoie_extension(mmap, mmap_size);
+ if (extension_offset) {
+ int err;
+
+ p.src_offset = extension_offset;
+ err = pthread_create(&p.pthread, NULL, load_index_extensions, &p);
+ if (err)
+ die(_("unable to create load_index_extensions thread: %s"), strerror(err));
+
+ nr_threads--;
+ }
+ }
+
+ /*
+ * Locate and read the index entry offset table so that we can use it
+ * to multi-thread the reading of the cache entries.
+ */
+ if (extension_offset && nr_threads > 1)
+ ieot = read_ieot_extension(mmap, mmap_size, extension_offset);
+
+ if (ieot) {
+ src_offset += load_cache_entries_threaded(istate, mmap, mmap_size, src_offset, nr_threads, ieot);
+ free(ieot);
+ } else {
+ src_offset += load_all_cache_entries(istate, mmap, mmap_size, src_offset);
+ }
+#else
+ src_offset += load_all_cache_entries(istate, mmap, mmap_size, src_offset);
+#endif
+
istate->timestamp.sec = st.st_mtime;
istate->timestamp.nsec = ST_MTIME_NSEC(st);
- while (src_offset <= mmap_size - the_hash_algo->rawsz - 8) {
- /* After an array of active_nr index entries,
- * there can be arbitrary number of extended
- * sections, each of which is prefixed with
- * extension name (4-byte) and section length
- * in 4-byte network byte order.
- */
- uint32_t extsize;
- memcpy(&extsize, (char *)mmap + src_offset + 4, 4);
- extsize = ntohl(extsize);
- if (read_index_extension(istate,
- (const char *) mmap + src_offset,
- (char *) mmap + src_offset + 8,
- extsize) < 0)
- goto unmap;
- src_offset += 8;
- src_offset += extsize;
+ /* if we created a thread, join it otherwise load the extensions on the primary thread */
+#ifndef NO_PTHREADS
+ if (extension_offset) {
+ int ret = pthread_join(p.pthread, NULL);
+ if (ret)
+ die(_("unable to join load_index_extensions thread: %s"), strerror(ret));
+ }
+#endif
+ if (!extension_offset) {
+ p.src_offset = src_offset;
+ load_index_extensions(&p);
}
- munmap(mmap, mmap_size);
+ munmap((void *)mmap, mmap_size);
return istate->cache_nr;
unmap:
- munmap(mmap, mmap_size);
+ munmap((void *)mmap, mmap_size);
die("index file corrupt");
}
int read_index_from(struct index_state *istate, const char *path,
const char *gitdir)
{
- uint64_t start = getnanotime();
struct split_index *split_index;
int ret;
char *base_oid_hex;
if (istate->initialized)
return istate->cache_nr;
+ trace_performance_enter();
ret = do_read_index(istate, path, 0);
- trace_performance_since(start, "read cache %s", path);
+ trace_performance_leave("read cache %s", path);
split_index = istate->split_index;
if (!split_index || is_null_oid(&split_index->base_oid)) {
return ret;
}
+ trace_performance_enter();
if (split_index->base)
discard_index(split_index->base);
else
base_oid_hex = oid_to_hex(&split_index->base_oid);
base_path = xstrfmt("%s/sharedindex.%s", gitdir, base_oid_hex);
ret = do_read_index(split_index->base, base_path, 1);
- if (oidcmp(&split_index->base_oid, &split_index->base->oid))
+ if (!oideq(&split_index->base_oid, &split_index->base->oid))
die("broken index, expect %s in %s, got %s",
base_oid_hex, base_path,
oid_to_hex(&split_index->base->oid));
freshen_shared_index(base_path, 0);
merge_base_index(istate);
post_read_index_from(istate);
- trace_performance_since(start, "read cache %s", base_path);
free(base_path);
+ trace_performance_leave("read cache %s", base_path);
return ret;
}
return 0;
}
-int index_has_changes(const struct index_state *istate,
+int index_has_changes(struct index_state *istate,
struct tree *tree,
struct strbuf *sb)
{
if (tree || !get_oid_tree("HEAD", &cmp)) {
struct diff_options opt;
- diff_setup(&opt);
+ repo_diff_setup(the_repository, &opt);
opt.flags.exit_with_status = 1;
if (!sb)
opt.flags.quick = 1;
return 0;
}
-static int write_index_ext_header(git_hash_ctx *context, int fd,
- unsigned int ext, unsigned int sz)
+static int write_index_ext_header(git_hash_ctx *context, git_hash_ctx *eoie_context,
+ int fd, unsigned int ext, unsigned int sz)
{
ext = htonl(ext);
sz = htonl(sz);
+ if (eoie_context) {
+ the_hash_algo->update_fn(eoie_context, &ext, 4);
+ the_hash_algo->update_fn(eoie_context, &sz, 4);
+ }
return ((ce_write(context, fd, &ext, 4) < 0) ||
(ce_write(context, fd, &sz, 4) < 0)) ? -1 : 0;
}
return (write_in_full(fd, write_buffer, left) < 0) ? -1 : 0;
}
-static void ce_smudge_racily_clean_entry(struct cache_entry *ce)
+static void ce_smudge_racily_clean_entry(struct index_state *istate,
+ struct cache_entry *ce)
{
/*
* The only thing we care about in this function is to smudge the
return;
if (ce_match_stat_basic(ce, &st))
return;
- if (ce_modified_check_fs(ce, &st)) {
+ if (ce_modified_check_fs(istate, ce, &st)) {
/* This is "racily clean"; smudge it. Note that this
* is a tricky code. At first glance, it may appear
* that it can break with this sequence:
if (n != the_hash_algo->rawsz)
goto out;
- if (hashcmp(istate->oid.hash, hash))
+ if (!hasheq(istate->oid.hash, hash))
goto out;
close(fd);
{
uint64_t start = getnanotime();
int newfd = tempfile->fd;
- git_hash_ctx c;
+ git_hash_ctx c, eoie_c;
struct cache_header hdr;
int i, err = 0, removed, extended, hdr_version;
struct cache_entry **cache = istate->cache;
struct ondisk_cache_entry_extended ondisk;
struct strbuf previous_name_buf = STRBUF_INIT, *previous_name;
int drop_cache_tree = istate->drop_cache_tree;
+ off_t offset;
+ int ieot_entries = 1;
+ struct index_entry_offset_table *ieot = NULL;
+ int nr, nr_threads;
for (i = removed = extended = 0; i < entries; i++) {
if (cache[i]->ce_flags & CE_REMOVE)
if (ce_write(&c, newfd, &hdr, sizeof(hdr)) < 0)
return -1;
+#ifndef NO_PTHREADS
+ nr_threads = git_config_get_index_threads();
+ if (nr_threads != 1) {
+ int ieot_blocks, cpus;
+
+ /*
+ * ensure default number of ieot blocks maps evenly to the
+ * default number of threads that will process them leaving
+ * room for the thread to load the index extensions.
+ */
+ if (!nr_threads) {
+ ieot_blocks = istate->cache_nr / THREAD_COST;
+ cpus = online_cpus();
+ if (ieot_blocks > cpus - 1)
+ ieot_blocks = cpus - 1;
+ } else {
+ ieot_blocks = nr_threads;
+ if (ieot_blocks > istate->cache_nr)
+ ieot_blocks = istate->cache_nr;
+ }
+
+ /*
+ * no reason to write out the IEOT extension if we don't
+ * have enough blocks to utilize multi-threading
+ */
+ if (ieot_blocks > 1) {
+ ieot = xcalloc(1, sizeof(struct index_entry_offset_table)
+ + (ieot_blocks * sizeof(struct index_entry_offset)));
+ ieot_entries = DIV_ROUND_UP(entries, ieot_blocks);
+ }
+ }
+#endif
+
+ offset = lseek(newfd, 0, SEEK_CUR);
+ if (offset < 0) {
+ free(ieot);
+ return -1;
+ }
+ offset += write_buffer_len;
+ nr = 0;
previous_name = (hdr_version == 4) ? &previous_name_buf : NULL;
for (i = 0; i < entries; i++) {
if (ce->ce_flags & CE_REMOVE)
continue;
if (!ce_uptodate(ce) && is_racy_timestamp(istate, ce))
- ce_smudge_racily_clean_entry(ce);
+ ce_smudge_racily_clean_entry(istate, ce);
if (is_null_oid(&ce->oid)) {
static const char msg[] = "cache entry has null sha1: %s";
static int allow = -1;
drop_cache_tree = 1;
}
+ if (ieot && i && (i % ieot_entries == 0)) {
+ ieot->entries[ieot->nr].nr = nr;
+ ieot->entries[ieot->nr].offset = offset;
+ ieot->nr++;
+ /*
+ * If we have a V4 index, set the first byte to an invalid
+ * character to ensure there is nothing common with the previous
+ * entry
+ */
+ if (previous_name)
+ previous_name->buf[0] = 0;
+ nr = 0;
+ offset = lseek(newfd, 0, SEEK_CUR);
+ if (offset < 0) {
+ free(ieot);
+ return -1;
+ }
+ offset += write_buffer_len;
+ }
if (ce_write_entry(&c, newfd, ce, previous_name, (struct ondisk_cache_entry *)&ondisk) < 0)
err = -1;
if (err)
break;
+ nr++;
+ }
+ if (ieot && nr) {
+ ieot->entries[ieot->nr].nr = nr;
+ ieot->entries[ieot->nr].offset = offset;
+ ieot->nr++;
}
strbuf_release(&previous_name_buf);
- if (err)
+ if (err) {
+ free(ieot);
return err;
+ }
/* Write extension data here */
+ offset = lseek(newfd, 0, SEEK_CUR);
+ if (offset < 0) {
+ free(ieot);
+ return -1;
+ }
+ offset += write_buffer_len;
+ the_hash_algo->init_fn(&eoie_c);
+
+ /*
+ * Lets write out CACHE_EXT_INDEXENTRYOFFSETTABLE first so that we
+ * can minimize the number of extensions we have to scan through to
+ * find it during load. Write it out regardless of the
+ * strip_extensions parameter as we need it when loading the shared
+ * index.
+ */
+#ifndef NO_PTHREADS
+ if (ieot) {
+ struct strbuf sb = STRBUF_INIT;
+
+ write_ieot_extension(&sb, ieot);
+ err = write_index_ext_header(&c, &eoie_c, newfd, CACHE_EXT_INDEXENTRYOFFSETTABLE, sb.len) < 0
+ || ce_write(&c, newfd, sb.buf, sb.len) < 0;
+ strbuf_release(&sb);
+ free(ieot);
+ if (err)
+ return -1;
+ }
+#endif
+
if (!strip_extensions && istate->split_index) {
struct strbuf sb = STRBUF_INIT;
err = write_link_extension(&sb, istate) < 0 ||
- write_index_ext_header(&c, newfd, CACHE_EXT_LINK,
+ write_index_ext_header(&c, &eoie_c, newfd, CACHE_EXT_LINK,
sb.len) < 0 ||
ce_write(&c, newfd, sb.buf, sb.len) < 0;
strbuf_release(&sb);
struct strbuf sb = STRBUF_INIT;
cache_tree_write(&sb, istate->cache_tree);
- err = write_index_ext_header(&c, newfd, CACHE_EXT_TREE, sb.len) < 0
+ err = write_index_ext_header(&c, &eoie_c, newfd, CACHE_EXT_TREE, sb.len) < 0
|| ce_write(&c, newfd, sb.buf, sb.len) < 0;
strbuf_release(&sb);
if (err)
struct strbuf sb = STRBUF_INIT;
resolve_undo_write(&sb, istate->resolve_undo);
- err = write_index_ext_header(&c, newfd, CACHE_EXT_RESOLVE_UNDO,
+ err = write_index_ext_header(&c, &eoie_c, newfd, CACHE_EXT_RESOLVE_UNDO,
sb.len) < 0
|| ce_write(&c, newfd, sb.buf, sb.len) < 0;
strbuf_release(&sb);
struct strbuf sb = STRBUF_INIT;
write_untracked_extension(&sb, istate->untracked);
- err = write_index_ext_header(&c, newfd, CACHE_EXT_UNTRACKED,
+ err = write_index_ext_header(&c, &eoie_c, newfd, CACHE_EXT_UNTRACKED,
sb.len) < 0 ||
ce_write(&c, newfd, sb.buf, sb.len) < 0;
strbuf_release(&sb);
struct strbuf sb = STRBUF_INIT;
write_fsmonitor_extension(&sb, istate);
- err = write_index_ext_header(&c, newfd, CACHE_EXT_FSMONITOR, sb.len) < 0
+ err = write_index_ext_header(&c, &eoie_c, newfd, CACHE_EXT_FSMONITOR, sb.len) < 0
+ || ce_write(&c, newfd, sb.buf, sb.len) < 0;
+ strbuf_release(&sb);
+ if (err)
+ return -1;
+ }
+
+ /*
+ * CACHE_EXT_ENDOFINDEXENTRIES must be written as the last entry before the SHA1
+ * so that it can be found and processed before all the index entries are
+ * read. Write it out regardless of the strip_extensions parameter as we need it
+ * when loading the shared index.
+ */
+ if (offset) {
+ struct strbuf sb = STRBUF_INIT;
+
+ write_eoie_extension(&sb, &eoie_c, offset);
+ err = write_index_ext_header(&c, NULL, newfd, CACHE_EXT_ENDOFINDEXENTRIES, sb.len) < 0
|| ce_write(&c, newfd, sb.buf, sb.len) < 0;
strbuf_release(&sb);
if (err)
int new_shared_index, ret;
struct split_index *si = istate->split_index;
+ if (git_env_bool("GIT_TEST_CHECK_CACHE_TREE", 0))
+ cache_tree_verify(istate);
+
if ((flags & SKIP_IF_UNCHANGED) && !istate->cache_changed) {
if (flags & COMMIT_LOCK)
rollback_lock_file(lock);
{
dst->untracked = src->untracked;
src->untracked = NULL;
+ dst->cache_tree = src->cache_tree;
+ src->cache_tree = NULL;
}
struct cache_entry *dup_cache_entry(const struct cache_entry *ce,
return validate_index_cache_entries;
}
+
+#define EOIE_SIZE (4 + GIT_SHA1_RAWSZ) /* <4-byte offset> + <20-byte hash> */
+#define EOIE_SIZE_WITH_HEADER (4 + 4 + EOIE_SIZE) /* <4-byte signature> + <4-byte length> + EOIE_SIZE */
+
+static size_t read_eoie_extension(const char *mmap, size_t mmap_size)
+{
+ /*
+ * The end of index entries (EOIE) extension is guaranteed to be last
+ * so that it can be found by scanning backwards from the EOF.
+ *
+ * "EOIE"
+ * <4-byte length>
+ * <4-byte offset>
+ * <20-byte hash>
+ */
+ const char *index, *eoie;
+ uint32_t extsize;
+ size_t offset, src_offset;
+ unsigned char hash[GIT_MAX_RAWSZ];
+ git_hash_ctx c;
+
+ /* ensure we have an index big enough to contain an EOIE extension */
+ if (mmap_size < sizeof(struct cache_header) + EOIE_SIZE_WITH_HEADER + the_hash_algo->rawsz)
+ return 0;
+
+ /* validate the extension signature */
+ index = eoie = mmap + mmap_size - EOIE_SIZE_WITH_HEADER - the_hash_algo->rawsz;
+ if (CACHE_EXT(index) != CACHE_EXT_ENDOFINDEXENTRIES)
+ return 0;
+ index += sizeof(uint32_t);
+
+ /* validate the extension size */
+ extsize = get_be32(index);
+ if (extsize != EOIE_SIZE)
+ return 0;
+ index += sizeof(uint32_t);
+
+ /*
+ * Validate the offset we're going to look for the first extension
+ * signature is after the index header and before the eoie extension.
+ */
+ offset = get_be32(index);
+ if (mmap + offset < mmap + sizeof(struct cache_header))
+ return 0;
+ if (mmap + offset >= eoie)
+ return 0;
+ index += sizeof(uint32_t);
+
+ /*
+ * The hash is computed over extension types and their sizes (but not
+ * their contents). E.g. if we have "TREE" extension that is N-bytes
+ * long, "REUC" extension that is M-bytes long, followed by "EOIE",
+ * then the hash would be:
+ *
+ * SHA-1("TREE" + <binary representation of N> +
+ * "REUC" + <binary representation of M>)
+ */
+ src_offset = offset;
+ the_hash_algo->init_fn(&c);
+ while (src_offset < mmap_size - the_hash_algo->rawsz - EOIE_SIZE_WITH_HEADER) {
+ /* After an array of active_nr index entries,
+ * there can be arbitrary number of extended
+ * sections, each of which is prefixed with
+ * extension name (4-byte) and section length
+ * in 4-byte network byte order.
+ */
+ uint32_t extsize;
+ memcpy(&extsize, mmap + src_offset + 4, 4);
+ extsize = ntohl(extsize);
+
+ /* verify the extension size isn't so large it will wrap around */
+ if (src_offset + 8 + extsize < src_offset)
+ return 0;
+
+ the_hash_algo->update_fn(&c, mmap + src_offset, 8);
+
+ src_offset += 8;
+ src_offset += extsize;
+ }
+ the_hash_algo->final_fn(hash, &c);
+ if (!hasheq(hash, (const unsigned char *)index))
+ return 0;
+
+ /* Validate that the extension offsets returned us back to the eoie extension. */
+ if (src_offset != mmap_size - the_hash_algo->rawsz - EOIE_SIZE_WITH_HEADER)
+ return 0;
+
+ return offset;
+}
+
+static void write_eoie_extension(struct strbuf *sb, git_hash_ctx *eoie_context, size_t offset)
+{
+ uint32_t buffer;
+ unsigned char hash[GIT_MAX_RAWSZ];
+
+ /* offset */
+ put_be32(&buffer, offset);
+ strbuf_add(sb, &buffer, sizeof(uint32_t));
+
+ /* hash */
+ the_hash_algo->final_fn(hash, eoie_context);
+ strbuf_add(sb, hash, the_hash_algo->rawsz);
+}
+
+#ifndef NO_PTHREADS
+#define IEOT_VERSION (1)
+
+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;
+}
+
+static void write_ieot_extension(struct strbuf *sb, struct index_entry_offset_table *ieot)
+{
+ uint32_t buffer;
+ int i;
+
+ /* version */
+ put_be32(&buffer, IEOT_VERSION);
+ strbuf_add(sb, &buffer, sizeof(uint32_t));
+
+ /* ieot */
+ for (i = 0; i < ieot->nr; i++) {
+
+ /* 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));
+ }
+}
+#endif
#include "wt-status.h"
#include "commit-slab.h"
#include "commit-graph.h"
+#include "commit-reach.h"
static struct ref_msg {
const char *gone;
struct string_list params = STRING_LIST_INIT_DUP;
int i;
+ atom->u.contents.trailer_opts.no_divider = 1;
+
if (arg) {
string_list_split(¶ms, arg, ',', -1);
for (i = 0; i < params.nr; i++) {
if (deref)
name++;
if (!strcmp(name, "objecttype"))
- v->s = type_name(oi->type);
+ v->s = xstrdup(type_name(oi->type));
else if (!strcmp(name, "objectsize")) {
v->value = oi->size;
v->s = xstrfmt("%lu", oi->size);
if (deref)
name++;
if (!strcmp(name, "tag"))
- v->s = tag->tag;
+ v->s = xstrdup(tag->tag);
else if (!strcmp(name, "type") && tag->tagged)
- v->s = type_name(tag->tagged->type);
+ v->s = xstrdup(type_name(tag->tagged->type));
else if (!strcmp(name, "object") && tag->tagged)
v->s = xstrdup(oid_to_hex(&tag->tagged->oid));
}
v->value = timestamp;
return;
bad:
- v->s = "";
+ v->s = xstrdup("");
v->value = 0;
}
for (i = 0; i < used_atom_cnt; i++) {
struct atom_value *v = &val[i];
if (v->s == NULL)
- v->s = "";
+ v->s = xstrdup("");
}
}
static const char *lstrip_ref_components(const char *refname, int len)
{
long remaining = len;
- const char *start = refname;
+ const char *start = xstrdup(refname);
+ const char *to_free = start;
if (len < 0) {
int i;
while (remaining > 0) {
switch (*start++) {
case '\0':
- return "";
+ free((char *)to_free);
+ return xstrdup("");
case '/':
remaining--;
break;
}
}
+ start = xstrdup(start);
+ free((char *)to_free);
return start;
}
static const char *rstrip_ref_components(const char *refname, int len)
{
long remaining = len;
- char *start = xstrdup(refname);
+ const char *start = xstrdup(refname);
+ const char *to_free = start;
if (len < 0) {
int i;
while (remaining-- > 0) {
char *p = strrchr(start, '/');
- if (p == NULL)
- return "";
- else
+ if (p == NULL) {
+ free((char *)to_free);
+ return xstrdup("");
+ } else
p[0] = '\0';
}
return start;
else if (atom->option == R_RSTRIP)
return rstrip_ref_components(refname, atom->rstrip);
else
- return refname;
+ return xstrdup(refname);
}
static void fill_remote_ref_details(struct used_atom *atom, const char *refname,
NULL, AHEAD_BEHIND_FULL) < 0) {
*s = xstrdup(msgs.gone);
} else if (!num_ours && !num_theirs)
- *s = "";
+ *s = xstrdup("");
else if (!num_ours)
*s = xstrfmt(msgs.behind, num_theirs);
else if (!num_theirs)
}
} else if (atom->u.remote_ref.option == RR_TRACKSHORT) {
if (stat_tracking_info(branch, &num_ours, &num_theirs,
- NULL, AHEAD_BEHIND_FULL) < 0)
+ NULL, AHEAD_BEHIND_FULL) < 0) {
+ *s = xstrdup("");
return;
-
+ }
if (!num_ours && !num_theirs)
- *s = "=";
+ *s = xstrdup("=");
else if (!num_ours)
- *s = "<";
+ *s = xstrdup("<");
else if (!num_theirs)
- *s = ">";
+ *s = xstrdup(">");
else
- *s = "<>";
+ *s = xstrdup("<>");
} else if (atom->u.remote_ref.option == RR_REMOTE_NAME) {
int explicit;
const char *remote = atom->u.remote_ref.push ?
pushremote_for_branch(branch, &explicit) :
remote_for_branch(branch, &explicit);
- if (explicit)
- *s = xstrdup(remote);
- else
- *s = "";
+ *s = xstrdup(explicit ? remote : "");
} else if (atom->u.remote_ref.option == RR_REMOTE_REF) {
int explicit;
const char *merge;
merge = remote_ref_for_branch(branch, atom->u.remote_ref.push,
&explicit);
- if (explicit)
- *s = xstrdup(merge);
- else
- *s = "";
+ *s = xstrdup(explicit ? merge : "");
} else
BUG("unhandled RR_* enum");
}
static const char *get_symref(struct used_atom *atom, struct ref_array_item *ref)
{
if (!ref->symref)
- return "";
+ return xstrdup("");
else
return show_ref(&atom->u.refname, ref->symref);
}
ref->symref = resolve_refdup(ref->refname, RESOLVE_REF_READING,
NULL, NULL);
if (!ref->symref)
- ref->symref = "";
+ ref->symref = xstrdup("");
}
/* Fill in specials first */
refname = get_symref(atom, ref);
else if (starts_with(name, "upstream")) {
const char *branch_name;
- v->s = "";
/* only local branches may have an upstream */
if (!skip_prefix(ref->refname, "refs/heads/",
- &branch_name))
+ &branch_name)) {
+ v->s = xstrdup("");
continue;
+ }
branch = branch_get(branch_name);
refname = branch_get_upstream(branch, NULL);
if (refname)
fill_remote_ref_details(atom, refname, branch, &v->s);
+ else
+ v->s = xstrdup("");
continue;
} else if (atom->u.remote_ref.push) {
const char *branch_name;
- v->s = "";
+ v->s = xstrdup("");
if (!skip_prefix(ref->refname, "refs/heads/",
&branch_name))
continue;
if (!refname)
continue;
}
+ /* We will definitely re-init v->s on the next line. */
+ free((char *)v->s);
fill_remote_ref_details(atom, refname, branch, &v->s);
continue;
} else if (starts_with(name, "color:")) {
- v->s = atom->u.color;
+ v->s = xstrdup(atom->u.color);
continue;
} else if (!strcmp(name, "flag")) {
char buf[256], *cp = buf;
if (ref->flag & REF_ISPACKED)
cp = copy_advance(cp, ",packed");
if (cp == buf)
- v->s = "";
+ v->s = xstrdup("");
else {
*cp = '\0';
v->s = xstrdup(buf + 1);
continue;
} else if (!strcmp(name, "HEAD")) {
if (atom->u.head && !strcmp(ref->refname, atom->u.head))
- v->s = "*";
+ v->s = xstrdup("*");
else
- v->s = " ";
+ v->s = xstrdup(" ");
continue;
} else if (starts_with(name, "align")) {
v->handler = align_atom_handler;
- v->s = "";
+ v->s = xstrdup("");
continue;
} else if (!strcmp(name, "end")) {
v->handler = end_atom_handler;
- v->s = "";
+ v->s = xstrdup("");
continue;
} else if (starts_with(name, "if")) {
const char *s;
- v->s = "";
if (skip_prefix(name, "if:", &s))
v->s = xstrdup(s);
+ else
+ v->s = xstrdup("");
v->handler = if_atom_handler;
continue;
} else if (!strcmp(name, "then")) {
v->handler = then_atom_handler;
- v->s = "";
+ v->s = xstrdup("");
continue;
} else if (!strcmp(name, "else")) {
v->handler = else_atom_handler;
- v->s = "";
+ v->s = xstrdup("");
continue;
} else
continue;
if (!deref)
- v->s = refname;
+ v->s = xstrdup(refname);
else
v->s = xstrfmt("%s^{}", refname);
+ free((char *)refname);
}
for (i = 0; i < used_atom_cnt; i++) {
return 0;
}
-/*
- * Unknown has to be "0" here, because that's the default value for
- * contains_cache slab entries that have not yet been assigned.
- */
-enum contains_result {
- CONTAINS_UNKNOWN = 0,
- CONTAINS_NO,
- CONTAINS_YES
-};
-
-define_commit_slab(contains_cache, enum contains_result);
-
-struct ref_filter_cbdata {
- struct ref_array *array;
- struct ref_filter *filter;
- struct contains_cache contains_cache;
- struct contains_cache no_contains_cache;
-};
-
-/*
- * Mimicking the real stack, this stack lives on the heap, avoiding stack
- * overflows.
- *
- * At each recursion step, the stack items points to the commits whose
- * ancestors are to be inspected.
- */
-struct contains_stack {
- int nr, alloc;
- struct contains_stack_entry {
- struct commit *commit;
- struct commit_list *parents;
- } *contains_stack;
-};
-
-static int in_commit_list(const struct commit_list *want, struct commit *c)
-{
- for (; want; want = want->next)
- if (!oidcmp(&want->item->object.oid, &c->object.oid))
- return 1;
- return 0;
-}
-
-/*
- * Test whether the candidate is contained in the list.
- * Do not recurse to find out, though, but return -1 if inconclusive.
- */
-static enum contains_result contains_test(struct commit *candidate,
- const struct commit_list *want,
- struct contains_cache *cache,
- uint32_t cutoff)
-{
- enum contains_result *cached = contains_cache_at(cache, candidate);
-
- /* If we already have the answer cached, return that. */
- if (*cached)
- return *cached;
-
- /* or are we it? */
- if (in_commit_list(want, candidate)) {
- *cached = CONTAINS_YES;
- return CONTAINS_YES;
- }
-
- /* Otherwise, we don't know; prepare to recurse */
- parse_commit_or_die(candidate);
-
- if (candidate->generation < cutoff)
- return CONTAINS_NO;
-
- return CONTAINS_UNKNOWN;
-}
-
-static void push_to_contains_stack(struct commit *candidate, struct contains_stack *contains_stack)
-{
- ALLOC_GROW(contains_stack->contains_stack, contains_stack->nr + 1, contains_stack->alloc);
- contains_stack->contains_stack[contains_stack->nr].commit = candidate;
- contains_stack->contains_stack[contains_stack->nr++].parents = candidate->parents;
-}
-
-static enum contains_result contains_tag_algo(struct commit *candidate,
- const struct commit_list *want,
- struct contains_cache *cache)
-{
- struct contains_stack contains_stack = { 0, 0, NULL };
- enum contains_result result;
- uint32_t cutoff = GENERATION_NUMBER_INFINITY;
- const struct commit_list *p;
-
- for (p = want; p; p = p->next) {
- struct commit *c = p->item;
- load_commit_graph_info(the_repository, c);
- if (c->generation < cutoff)
- cutoff = c->generation;
- }
-
- result = contains_test(candidate, want, cache, cutoff);
- if (result != CONTAINS_UNKNOWN)
- return result;
-
- push_to_contains_stack(candidate, &contains_stack);
- while (contains_stack.nr) {
- struct contains_stack_entry *entry = &contains_stack.contains_stack[contains_stack.nr - 1];
- struct commit *commit = entry->commit;
- struct commit_list *parents = entry->parents;
-
- if (!parents) {
- *contains_cache_at(cache, commit) = CONTAINS_NO;
- contains_stack.nr--;
- }
- /*
- * If we just popped the stack, parents->item has been marked,
- * therefore contains_test will return a meaningful yes/no.
- */
- else switch (contains_test(parents->item, want, cache, cutoff)) {
- case CONTAINS_YES:
- *contains_cache_at(cache, commit) = CONTAINS_YES;
- contains_stack.nr--;
- break;
- case CONTAINS_NO:
- entry->parents = parents->next;
- break;
- case CONTAINS_UNKNOWN:
- push_to_contains_stack(parents->item, &contains_stack);
- break;
- }
- }
- free(contains_stack.contains_stack);
- return contains_test(candidate, want, cache, cutoff);
-}
-
-static int commit_contains(struct ref_filter *filter, struct commit *commit,
- struct commit_list *list, struct contains_cache *cache)
-{
- if (filter->with_commit_tag_algo)
- return contains_tag_algo(commit, list, cache) == CONTAINS_YES;
- return is_descendant_of(commit, list);
-}
-
/*
* Return 1 if the refname matches one of the patterns, otherwise 0.
* A pattern can be a literal prefix (e.g. a refname "refs/heads/master"
return ref_kind_from_refname(refname);
}
+struct ref_filter_cbdata {
+ struct ref_array *array;
+ struct ref_filter *filter;
+ struct contains_cache contains_cache;
+ struct contains_cache no_contains_cache;
+};
+
/*
* A call-back given to for_each_ref(). Filter refs and keep them for
* later object processing.
static void free_array_item(struct ref_array_item *item)
{
free((char *)item->symref);
+ if (item->value) {
+ free((char *)item->value->s);
+ free(item->value);
+ }
free(item);
}
{
int i;
+ for (i = 0; i < used_atom_cnt; i++)
+ free((char *)used_atom[i].name);
+ FREE_AND_NULL(used_atom);
+ used_atom_cnt = 0;
for (i = 0; i < array->nr; i++)
free_array_item(array->items[i]);
FREE_AND_NULL(array->items);
struct ref_array *array = ref_cbdata->array;
struct commit **to_clear = xcalloc(sizeof(struct commit *), array->nr);
- init_revisions(&revs, NULL);
+ repo_init_revisions(the_repository, &revs, NULL);
for (i = 0; i < array->nr; i++) {
struct ref_array_item *item = array->items[i];
pseudoref);
rollback_lock_file(&lock);
goto done;
- } else if (oidcmp(&actual_old_oid, old_oid)) {
+ } else if (!oideq(&actual_old_oid, old_oid)) {
strbuf_addf(err, _("unexpected object ID when writing '%s'"),
pseudoref);
rollback_lock_file(&lock);
}
if (read_ref(pseudoref, &actual_old_oid))
die(_("could not read ref '%s'"), pseudoref);
- if (oidcmp(&actual_old_oid, old_oid)) {
+ if (!oideq(&actual_old_oid, old_oid)) {
error(_("unexpected object ID when deleting '%s'"),
pseudoref);
rollback_lock_file(&lock);
*/
if (!is_null_oid(&cb->ooid)) {
oidcpy(cb->oid, noid);
- if (oidcmp(&cb->ooid, noid))
+ if (!oideq(&cb->ooid, noid))
warning(_("log for ref %s has gap after %s"),
cb->refname, show_date(cb->date, cb->tz, DATE_MODE(RFC2822)));
}
else if (cb->date == cb->at_time)
oidcpy(cb->oid, noid);
- else if (oidcmp(noid, cb->oid))
+ else if (!oideq(noid, cb->oid))
warning(_("log for ref %s unexpectedly ended on %s"),
cb->refname, show_date(cb->date, cb->tz,
DATE_MODE(RFC2822)));
* non-zero value, stop the iteration and return that value;
* otherwise, return 0.
*/
+static int do_for_each_repo_ref(struct repository *r, const char *prefix,
+ each_repo_ref_fn fn, int trim, int flags,
+ void *cb_data)
+{
+ struct ref_iterator *iter;
+ struct ref_store *refs = get_main_ref_store(r);
+
+ if (!refs)
+ return 0;
+
+ iter = refs_ref_iterator_begin(refs, prefix, trim, flags);
+
+ return do_for_each_repo_ref_iterator(r, iter, fn, cb_data);
+}
+
+struct do_for_each_ref_help {
+ each_ref_fn *fn;
+ void *cb_data;
+};
+
+static int do_for_each_ref_helper(struct repository *r,
+ const char *refname,
+ const struct object_id *oid,
+ int flags,
+ void *cb_data)
+{
+ struct do_for_each_ref_help *hp = cb_data;
+
+ return hp->fn(refname, oid, flags, hp->cb_data);
+}
+
static int do_for_each_ref(struct ref_store *refs, const char *prefix,
each_ref_fn fn, int trim, int flags, void *cb_data)
{
struct ref_iterator *iter;
+ struct do_for_each_ref_help hp = { fn, cb_data };
if (!refs)
return 0;
iter = refs_ref_iterator_begin(refs, prefix, trim, flags);
- return do_for_each_ref_iterator(iter, fn, cb_data);
+ return do_for_each_repo_ref_iterator(the_repository, iter,
+ do_for_each_ref_helper, &hp);
}
int refs_for_each_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
return do_for_each_ref(refs, prefix, fn, 0, flag, cb_data);
}
-int for_each_replace_ref(struct repository *r, each_ref_fn fn, void *cb_data)
+int for_each_replace_ref(struct repository *r, each_repo_ref_fn fn, void *cb_data)
{
- return do_for_each_ref(get_main_ref_store(r),
- git_replace_ref_base, fn,
- strlen(git_replace_ref_base),
- DO_FOR_EACH_INCLUDE_BROKEN, cb_data);
+ return do_for_each_repo_ref(r, git_replace_ref_base, fn,
+ strlen(git_replace_ref_base),
+ DO_FOR_EACH_INCLUDE_BROKEN, cb_data);
}
int for_each_namespaced_ref(each_ref_fn fn, void *cb_data)
int refs_for_each_reflog(struct ref_store *refs, each_ref_fn fn, void *cb_data)
{
struct ref_iterator *iter;
+ struct do_for_each_ref_help hp = { fn, cb_data };
iter = refs->be->reflog_iterator_begin(refs);
- return do_for_each_ref_iterator(iter, fn, cb_data);
+ return do_for_each_repo_ref_iterator(the_repository, iter,
+ do_for_each_ref_helper, &hp);
}
int for_each_reflog(each_ref_fn fn, void *cb_data)
typedef int each_ref_fn(const char *refname,
const struct object_id *oid, int flags, void *cb_data);
+/*
+ * The same as each_ref_fn, but also with a repository argument that
+ * contains the repository associated with the callback.
+ */
+typedef int each_repo_ref_fn(struct repository *r,
+ const char *refname,
+ const struct object_id *oid,
+ int flags,
+ void *cb_data);
+
/*
* The following functions invoke the specified callback function for
* each reference indicated. If the function ever returns a nonzero
int for_each_tag_ref(each_ref_fn fn, void *cb_data);
int for_each_branch_ref(each_ref_fn fn, void *cb_data);
int for_each_remote_ref(each_ref_fn fn, void *cb_data);
-int for_each_replace_ref(struct repository *r, each_ref_fn fn, void *cb_data);
+int for_each_replace_ref(struct repository *r, each_repo_ref_fn fn, void *cb_data);
int for_each_glob_ref(each_ref_fn fn, const char *pattern, void *cb_data);
int for_each_glob_ref_in(each_ref_fn fn, const char *pattern,
const char *prefix, void *cb_data);
return 0;
}
}
- if (old_oid && oidcmp(&lock->old_oid, old_oid)) {
+ if (old_oid && !oideq(&lock->old_oid, old_oid)) {
strbuf_addf(err, "ref '%s' is at %s but expected %s",
lock->ref_name,
oid_to_hex(&lock->old_oid),
struct strbuf *err)
{
if (!(update->flags & REF_HAVE_OLD) ||
- !oidcmp(oid, &update->old_oid))
+ oideq(oid, &update->old_oid))
return 0;
if (is_null_oid(&update->old_oid))
!(update->flags & REF_DELETING) &&
!(update->flags & REF_LOG_ONLY)) {
if (!(update->type & REF_ISSYMREF) &&
- !oidcmp(&lock->old_oid, &update->new_oid)) {
+ oideq(&lock->old_oid, &update->new_oid)) {
/*
* The reference already has the desired
* value, so we don't need to write it.
struct ref_iterator *current_ref_iter = NULL;
-int do_for_each_ref_iterator(struct ref_iterator *iter,
- each_ref_fn fn, void *cb_data)
+int do_for_each_repo_ref_iterator(struct repository *r, struct ref_iterator *iter,
+ each_repo_ref_fn fn, void *cb_data)
{
int retval = 0, ok;
struct ref_iterator *old_ref_iter = current_ref_iter;
current_ref_iter = iter;
while ((ok = ref_iterator_advance(iter)) == ITER_OK) {
- retval = fn(iter->refname, iter->oid, iter->flags, cb_data);
+ retval = fn(r, iter->refname, iter->oid, iter->flags, cb_data);
if (retval) {
/*
* If ref_iterator_abort() returns ITER_ERROR,
static int cmp_packed_ref_records(const void *v1, const void *v2)
{
const struct snapshot_record *e1 = v1, *e2 = v2;
- const char *r1 = e1->start + GIT_SHA1_HEXSZ + 1;
- const char *r2 = e2->start + GIT_SHA1_HEXSZ + 1;
+ const char *r1 = e1->start + the_hash_algo->hexsz + 1;
+ const char *r2 = e2->start + the_hash_algo->hexsz + 1;
while (1) {
if (*r1 == '\n')
*/
static int cmp_record_to_refname(const char *rec, const char *refname)
{
- const char *r1 = rec + GIT_SHA1_HEXSZ + 1;
+ const char *r1 = rec + the_hash_algo->hexsz + 1;
const char *r2 = refname;
while (1) {
if (!eol)
/* The safety check should prevent this. */
BUG("unterminated line found in packed-refs");
- if (eol - pos < GIT_SHA1_HEXSZ + 2)
+ if (eol - pos < the_hash_algo->hexsz + 2)
die_invalid_line(snapshot->refs->path,
pos, eof - pos);
eol++;
return;
last_line = find_start_of_record(start, eof - 1);
- if (*(eof - 1) != '\n' || eof - last_line < GIT_SHA1_HEXSZ + 2)
+ if (*(eof - 1) != '\n' || eof - last_line < the_hash_algo->hexsz + 2)
die_invalid_line(snapshot->refs->path,
last_line, eof - last_line);
}
iter->base.flags = REF_ISPACKED;
- if (iter->eof - p < GIT_SHA1_HEXSZ + 2 ||
+ if (iter->eof - p < the_hash_algo->hexsz + 2 ||
parse_oid_hex(p, &iter->oid, &p) ||
!isspace(*p++))
die_invalid_line(iter->snapshot->refs->path,
if (iter->pos < iter->eof && *iter->pos == '^') {
p = iter->pos + 1;
- if (iter->eof - p < GIT_SHA1_HEXSZ + 1 ||
+ if (iter->eof - p < the_hash_algo->hexsz + 1 ||
parse_oid_hex(p, &iter->peeled, &p) ||
*p++ != '\n')
die_invalid_line(iter->snapshot->refs->path,
"reference already exists",
update->refname);
goto error;
- } else if (oidcmp(&update->old_oid, iter->oid)) {
+ } else if (!oideq(&update->old_oid, iter->oid)) {
strbuf_addf(err, "cannot update ref '%s': "
"is at %s but expected %s",
update->refname,
#ifndef REFS_PACKED_BACKEND_H
#define REFS_PACKED_BACKEND_H
+struct ref_transaction;
+
/*
* Support for storing references in a `packed-refs` file.
*
/* This is impossible by construction */
die("Reference directory conflict: %s", ref1->name);
- if (oidcmp(&ref1->u.value.oid, &ref2->u.value.oid))
+ if (!oideq(&ref1->u.value.oid, &ref2->u.value.oid))
die("Duplicated ref, and SHA1s don't match: %s", ref1->name);
warning("Duplicated ref: %s", ref1->name);
#ifndef REFS_REF_CACHE_H
#define REFS_REF_CACHE_H
+#include "cache.h"
+
struct ref_dir;
+struct ref_store;
/*
* If this ref_cache is filled lazily, this function is used to load
#ifndef REFS_REFS_INTERNAL_H
#define REFS_REFS_INTERNAL_H
+#include "cache.h"
+#include "refs.h"
#include "iterator.h"
+struct ref_transaction;
+
/*
* Data structures and functions for the internal use of the refs
* module. Code outside of the refs module should use only the public
*
* // Access information about the current reference:
* if (!(iter->flags & REF_ISSYMREF))
- * printf("%s is %s\n", iter->refname, oid_to_hex(&iter->oid));
+ * printf("%s is %s\n", iter->refname, oid_to_hex(iter->oid));
*
* // If you need to peel the reference:
* ref_iterator_peel(iter, &oid);
* adapter between the callback style of reference iteration and the
* iterator style.
*/
-int do_for_each_ref_iterator(struct ref_iterator *iter,
- each_ref_fn fn, void *cb_data);
+int do_for_each_repo_ref_iterator(struct repository *r,
+ struct ref_iterator *iter,
+ each_repo_ref_fn fn, void *cb_data);
/*
* Only include per-worktree refs in a do_for_each_ref*() iteration.
options.no_dependents = 1;
return 0;
} else if (!strcmp(name, "filter")) {
- options.filter = xstrdup(value);;
+ options.filter = xstrdup(value);
return 0;
} else {
return 1 /* unsupported */;
#include "string-list.h"
#include "mergesort.h"
#include "argv-array.h"
+#include "commit-reach.h"
enum map_direction { FROM_SRC, FROM_DST };
ref->deletion = is_null_oid(&ref->new_oid);
if (!ref->deletion &&
- !oidcmp(&ref->old_oid, &ref->new_oid)) {
+ oideq(&ref->old_oid, &ref->new_oid)) {
ref->status = REF_STATUS_UPTODATE;
continue;
}
* branch.
*/
if (ref->expect_old_sha1) {
- if (oidcmp(&ref->old_oid, &ref->old_oid_expect))
+ if (!oideq(&ref->old_oid, &ref->old_oid_expect))
reject_reason = REF_STATUS_REJECT_STALE;
else
/* If the ref isn't stale then force the update. */
return 1;
}
-static void unmark_and_free(struct commit_list *list, unsigned int mark)
-{
- while (list) {
- struct commit *commit = pop_commit(&list);
- commit->object.flags &= ~mark;
- }
-}
-
-int ref_newer(const struct object_id *new_oid, const struct object_id *old_oid)
-{
- struct object *o;
- struct commit *old_commit, *new_commit;
- struct commit_list *list, *used;
- int found = 0;
-
- /*
- * Both new_commit and old_commit must be commit-ish and new_commit is descendant of
- * old_commit. Otherwise we require --force.
- */
- o = deref_tag(the_repository, parse_object(the_repository, old_oid),
- NULL, 0);
- if (!o || o->type != OBJ_COMMIT)
- return 0;
- old_commit = (struct commit *) o;
-
- o = deref_tag(the_repository, parse_object(the_repository, new_oid),
- NULL, 0);
- if (!o || o->type != OBJ_COMMIT)
- return 0;
- new_commit = (struct commit *) o;
-
- if (parse_commit(new_commit) < 0)
- return 0;
-
- used = list = NULL;
- commit_list_insert(new_commit, &list);
- while (list) {
- new_commit = pop_most_recent_commit(&list, TMP_MARK);
- commit_list_insert(new_commit, &used);
- if (new_commit == old_commit) {
- found = 1;
- break;
- }
- }
- unmark_and_free(list, TMP_MARK);
- unmark_and_free(used, TMP_MARK);
- return found;
-}
-
/*
* Lookup the upstream branch for the given branch and if present, optionally
* compute the commit ahead/behind values for the pair.
oid_to_hex(&theirs->object.oid));
argv_array_push(&argv, "--");
- init_revisions(&revs, NULL);
+ 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");
/* If refs/heads/master could be right, it is. */
if (!all) {
r = find_ref_by_name(refs, "refs/heads/master");
- if (r && !oidcmp(&r->old_oid, &head->old_oid))
+ if (r && oideq(&r->old_oid, &head->old_oid))
return copy_ref(r);
}
for (r = refs; r; r = r->next) {
if (r != head &&
starts_with(r->name, "refs/heads/") &&
- !oidcmp(&r->old_oid, &head->old_oid)) {
+ oideq(&r->old_oid, &head->old_oid)) {
*tail = copy_ref(r);
tail = &((*tail)->next);
if (!all)
const struct string_list *server_options);
int resolve_remote_symref(struct ref *ref, struct ref *list);
-int ref_newer(const struct object_id *new_oid, const struct object_id *old_oid);
/*
* Remove and free all but the first of any entries in the input list
#include "repository.h"
#include "commit.h"
-static int register_replace_ref(const char *refname,
+static int register_replace_ref(struct repository *r,
+ const char *refname,
const struct object_id *oid,
int flag, void *cb_data)
{
oidcpy(&repl_obj->replacement, oid);
/* Register new object */
- if (oidmap_put(the_repository->objects->replace_map, repl_obj))
+ if (oidmap_put(r->objects->replace_map, repl_obj))
die(_("duplicate replace ref: %s"), refname);
return 0;
}
-static void prepare_replace_object(struct repository *r)
+void prepare_replace_object(struct repository *r)
{
if (r->objects->replace_map)
return;
struct object_id replacement;
};
+void prepare_replace_object(struct repository *r);
+
/*
* This internal function is only declared here for the benefit of
* lookup_replace_object(). Please do not call it directly.
#define RR_HAS_POSTIMAGE 1
#define RR_HAS_PREIMAGE 2
static struct rerere_dir {
- unsigned char sha1[20];
+ unsigned char hash[GIT_MAX_HEXSZ];
int status_alloc, status_nr;
unsigned char *status;
} **rerere_dir;
static const char *rerere_id_hex(const struct rerere_id *id)
{
- return sha1_to_hex(id->collection->sha1);
+ return sha1_to_hex(id->collection->hash);
}
static void fit_variant(struct rerere_dir *rr_dir, int variant)
static void scan_rerere_dir(struct rerere_dir *rr_dir)
{
struct dirent *de;
- DIR *dir = opendir(git_path("rr-cache/%s", sha1_to_hex(rr_dir->sha1)));
+ DIR *dir = opendir(git_path("rr-cache/%s", sha1_to_hex(rr_dir->hash)));
if (!dir)
return;
closedir(dir);
}
-static const unsigned char *rerere_dir_sha1(size_t i, void *table)
+static const unsigned char *rerere_dir_hash(size_t i, void *table)
{
struct rerere_dir **rr_dir = table;
- return rr_dir[i]->sha1;
+ return rr_dir[i]->hash;
}
static struct rerere_dir *find_rerere_dir(const char *hex)
{
- unsigned char sha1[20];
+ unsigned char hash[GIT_MAX_RAWSZ];
struct rerere_dir *rr_dir;
int pos;
- if (get_sha1_hex(hex, sha1))
+ if (get_sha1_hex(hex, hash))
return NULL; /* BUG */
- pos = sha1_pos(sha1, rerere_dir, rerere_dir_nr, rerere_dir_sha1);
+ pos = sha1_pos(hash, rerere_dir, rerere_dir_nr, rerere_dir_hash);
if (pos < 0) {
rr_dir = xmalloc(sizeof(*rr_dir));
- hashcpy(rr_dir->sha1, sha1);
+ hashcpy(rr_dir->hash, hash);
rr_dir->status = NULL;
rr_dir->status_nr = 0;
rr_dir->status_alloc = 0;
return;
while (!strbuf_getwholeline(&buf, in, '\0')) {
char *path;
- unsigned char sha1[20];
+ unsigned char hash[GIT_MAX_RAWSZ];
struct rerere_id *id;
int variant;
+ const unsigned hexsz = the_hash_algo->hexsz;
/* There has to be the hash, tab, path and then NUL */
- if (buf.len < 42 || get_sha1_hex(buf.buf, sha1))
- die("corrupt MERGE_RR");
+ if (buf.len < hexsz + 2 || get_sha1_hex(buf.buf, hash))
+ die(_("corrupt MERGE_RR"));
- if (buf.buf[40] != '.') {
+ if (buf.buf[hexsz] != '.') {
variant = 0;
- path = buf.buf + 40;
+ path = buf.buf + hexsz;
} else {
errno = 0;
- variant = strtol(buf.buf + 41, &path, 10);
+ variant = strtol(buf.buf + hexsz + 1, &path, 10);
if (errno)
- die("corrupt MERGE_RR");
+ die(_("corrupt MERGE_RR"));
}
if (*(path++) != '\t')
- die("corrupt MERGE_RR");
- buf.buf[40] = '\0';
+ die(_("corrupt MERGE_RR"));
+ buf.buf[hexsz] = '\0';
id = new_rerere_id_hex(buf.buf);
id->variant = variant;
string_list_insert(rr, path)->util = id;
rr->items[i].string, 0);
if (write_in_full(out_fd, buf.buf, buf.len) < 0)
- die("unable to write rerere record");
+ die(_("unable to write rerere record"));
strbuf_release(&buf);
}
if (commit_lock_file(&write_lock) != 0)
- die("unable to write rerere record");
+ die(_("unable to write rerere record"));
return 0;
}
ferr_puts(str, io->output, &io->wrerror);
}
-/*
- * Write a conflict marker to io->output (if defined).
- */
-static void rerere_io_putconflict(int ch, int size, struct rerere_io *io)
-{
- char buf[64];
-
- while (size) {
- if (size <= sizeof(buf) - 2) {
- memset(buf, ch, size);
- buf[size] = '\n';
- buf[size + 1] = '\0';
- size = 0;
- } else {
- int sz = sizeof(buf) - 1;
-
- /*
- * Make sure we will not write everything out
- * in this round by leaving at least 1 byte
- * for the next round, giving the next round
- * a chance to add the terminating LF. Yuck.
- */
- if (size <= sz)
- sz -= (sz - size) + 1;
- memset(buf, ch, sz);
- buf[sz] = '\0';
- size -= sz;
- }
- rerere_io_putstr(buf, io);
- }
-}
-
static void rerere_io_putmem(const char *mem, size_t sz, struct rerere_io *io)
{
if (io->output)
return isspace(*buf);
}
-/*
- * Read contents a file with conflicts, normalize the conflicts
- * by (1) discarding the common ancestor version in diff3-style,
- * (2) reordering our side and their side so that whichever sorts
- * alphabetically earlier comes before the other one, while
- * computing the "conflict ID", which is just an SHA-1 hash of
- * one side of the conflict, NUL, the other side of the conflict,
- * and NUL concatenated together.
- *
- * Return the number of conflict hunks found.
- *
- * NEEDSWORK: the logic and theory of operation behind this conflict
- * normalization may deserve to be documented somewhere, perhaps in
- * Documentation/technical/rerere.txt.
- */
-static int handle_path(unsigned char *sha1, struct rerere_io *io, int marker_size)
+static void rerere_strbuf_putconflict(struct strbuf *buf, int ch, size_t size)
+{
+ strbuf_addchars(buf, ch, size);
+ strbuf_addch(buf, '\n');
+}
+
+static int handle_conflict(struct strbuf *out, struct rerere_io *io,
+ int marker_size, git_hash_ctx *ctx)
{
- git_SHA_CTX ctx;
- int hunk_no = 0;
enum {
- RR_CONTEXT = 0, RR_SIDE_1, RR_SIDE_2, RR_ORIGINAL
- } hunk = RR_CONTEXT;
+ RR_SIDE_1 = 0, RR_SIDE_2, RR_ORIGINAL
+ } hunk = RR_SIDE_1;
struct strbuf one = STRBUF_INIT, two = STRBUF_INIT;
- struct strbuf buf = STRBUF_INIT;
-
- if (sha1)
- git_SHA1_Init(&ctx);
+ struct strbuf buf = STRBUF_INIT, conflict = STRBUF_INIT;
+ int has_conflicts = -1;
while (!io->getline(&buf, io)) {
if (is_cmarker(buf.buf, '<', marker_size)) {
- if (hunk != RR_CONTEXT)
- goto bad;
- hunk = RR_SIDE_1;
+ if (handle_conflict(&conflict, io, marker_size, NULL) < 0)
+ break;
+ if (hunk == RR_SIDE_1)
+ strbuf_addbuf(&one, &conflict);
+ else
+ strbuf_addbuf(&two, &conflict);
+ strbuf_release(&conflict);
} else if (is_cmarker(buf.buf, '|', marker_size)) {
if (hunk != RR_SIDE_1)
- goto bad;
+ break;
hunk = RR_ORIGINAL;
} else if (is_cmarker(buf.buf, '=', marker_size)) {
if (hunk != RR_SIDE_1 && hunk != RR_ORIGINAL)
- goto bad;
+ break;
hunk = RR_SIDE_2;
} else if (is_cmarker(buf.buf, '>', marker_size)) {
if (hunk != RR_SIDE_2)
- goto bad;
+ break;
if (strbuf_cmp(&one, &two) > 0)
strbuf_swap(&one, &two);
- hunk_no++;
- hunk = RR_CONTEXT;
- rerere_io_putconflict('<', marker_size, io);
- rerere_io_putmem(one.buf, one.len, io);
- rerere_io_putconflict('=', marker_size, io);
- rerere_io_putmem(two.buf, two.len, io);
- rerere_io_putconflict('>', marker_size, io);
- if (sha1) {
- git_SHA1_Update(&ctx, one.buf ? one.buf : "",
- one.len + 1);
- git_SHA1_Update(&ctx, two.buf ? two.buf : "",
- two.len + 1);
+ has_conflicts = 1;
+ rerere_strbuf_putconflict(out, '<', marker_size);
+ strbuf_addbuf(out, &one);
+ rerere_strbuf_putconflict(out, '=', marker_size);
+ strbuf_addbuf(out, &two);
+ rerere_strbuf_putconflict(out, '>', marker_size);
+ if (ctx) {
+ the_hash_algo->update_fn(ctx, one.buf ?
+ one.buf : "",
+ one.len + 1);
+ the_hash_algo->update_fn(ctx, two.buf ?
+ two.buf : "",
+ two.len + 1);
}
- strbuf_reset(&one);
- strbuf_reset(&two);
+ break;
} else if (hunk == RR_SIDE_1)
strbuf_addbuf(&one, &buf);
else if (hunk == RR_ORIGINAL)
; /* discard */
else if (hunk == RR_SIDE_2)
strbuf_addbuf(&two, &buf);
- else
- rerere_io_putstr(buf.buf, io);
- continue;
- bad:
- hunk = 99; /* force error exit */
- break;
}
strbuf_release(&one);
strbuf_release(&two);
strbuf_release(&buf);
- if (sha1)
- git_SHA1_Final(sha1, &ctx);
- if (hunk != RR_CONTEXT)
- return -1;
- return hunk_no;
+ return has_conflicts;
+}
+
+/*
+ * Read contents a file with conflicts, normalize the conflicts
+ * by (1) discarding the common ancestor version in diff3-style,
+ * (2) reordering our side and their side so that whichever sorts
+ * alphabetically earlier comes before the other one, while
+ * computing the "conflict ID", which is just an SHA-1 hash of
+ * one side of the conflict, NUL, the other side of the conflict,
+ * and NUL concatenated together.
+ *
+ * Return 1 if conflict hunks are found, 0 if there are no conflict
+ * hunks and -1 if an error occured.
+ */
+static int handle_path(unsigned char *hash, struct rerere_io *io, int marker_size)
+{
+ git_hash_ctx ctx;
+ struct strbuf buf = STRBUF_INIT, out = STRBUF_INIT;
+ int has_conflicts = 0;
+ if (hash)
+ the_hash_algo->init_fn(&ctx);
+
+ while (!io->getline(&buf, io)) {
+ if (is_cmarker(buf.buf, '<', marker_size)) {
+ has_conflicts = handle_conflict(&out, io, marker_size,
+ hash ? &ctx : NULL);
+ if (has_conflicts < 0)
+ break;
+ rerere_io_putmem(out.buf, out.len, io);
+ strbuf_reset(&out);
+ } else
+ rerere_io_putstr(buf.buf, io);
+ }
+ strbuf_release(&buf);
+ strbuf_release(&out);
+
+ if (hash)
+ the_hash_algo->final_fn(hash, &ctx);
+
+ return has_conflicts;
}
/*
* Scan the path for conflicts, do the "handle_path()" thing above, and
* return the number of conflict hunks found.
*/
-static int handle_file(const char *path, unsigned char *sha1, const char *output)
+static int handle_file(struct index_state *istate,
+ const char *path, unsigned char *hash, const char *output)
{
- int hunk_no = 0;
+ int has_conflicts = 0;
struct rerere_io_file io;
- int marker_size = ll_merge_marker_size(path);
+ int marker_size = ll_merge_marker_size(istate, path);
memset(&io, 0, sizeof(io));
io.io.getline = rerere_file_getline;
io.input = fopen(path, "r");
io.io.wrerror = 0;
if (!io.input)
- return error_errno("Could not open %s", path);
+ return error_errno(_("could not open '%s'"), path);
if (output) {
io.io.output = fopen(output, "w");
if (!io.io.output) {
- error_errno("Could not write %s", output);
+ error_errno(_("could not write '%s'"), output);
fclose(io.input);
return -1;
}
}
- hunk_no = handle_path(sha1, (struct rerere_io *)&io, marker_size);
+ has_conflicts = handle_path(hash, (struct rerere_io *)&io, marker_size);
fclose(io.input);
if (io.io.wrerror)
- error("There were errors while writing %s (%s)",
+ error(_("there were errors while writing '%s' (%s)"),
path, strerror(io.io.wrerror));
if (io.io.output && fclose(io.io.output))
- io.io.wrerror = error_errno("Failed to flush %s", path);
+ io.io.wrerror = error_errno(_("failed to flush '%s'"), path);
- if (hunk_no < 0) {
+ if (has_conflicts < 0) {
if (output)
unlink_or_warn(output);
- return error("Could not parse conflict hunks in %s", path);
+ return error(_("could not parse conflict hunks in '%s'"), path);
}
if (io.io.wrerror)
return -1;
- return hunk_no;
+ return has_conflicts;
}
/*
* stages we have already looked at in this invocation of this
* function.
*/
-static int check_one_conflict(int i, int *type)
+static int check_one_conflict(struct index_state *istate, int i, int *type)
{
- const struct cache_entry *e = active_cache[i];
+ const struct cache_entry *e = istate->cache[i];
if (!ce_stage(e)) {
*type = RESOLVED;
}
*type = PUNTED;
- while (ce_stage(active_cache[i]) == 1)
+ while (i < istate->cache_nr && ce_stage(istate->cache[i]) == 1)
i++;
/* Only handle regular files with both stages #2 and #3 */
- if (i + 1 < active_nr) {
- const struct cache_entry *e2 = active_cache[i];
- const struct cache_entry *e3 = active_cache[i + 1];
+ if (i + 1 < istate->cache_nr) {
+ const struct cache_entry *e2 = istate->cache[i];
+ const struct cache_entry *e3 = istate->cache[i + 1];
if (ce_stage(e2) == 2 &&
ce_stage(e3) == 3 &&
ce_same_name(e, e3) &&
}
/* Skip the entries with the same name */
- while (i < active_nr && ce_same_name(e, active_cache[i]))
+ while (i < istate->cache_nr && ce_same_name(e, istate->cache[i]))
i++;
return i;
}
* are identical to the previous round, might want to be handled,
* though.
*/
-static int find_conflict(struct string_list *conflict)
+static int find_conflict(struct repository *r, struct string_list *conflict)
{
int i;
- if (read_cache() < 0)
- return error("Could not read index");
- for (i = 0; i < active_nr;) {
+ if (read_index(r->index) < 0)
+ return error(_("index file corrupt"));
+
+ for (i = 0; i < r->index->cache_nr;) {
int conflict_type;
- const struct cache_entry *e = active_cache[i];
- i = check_one_conflict(i, &conflict_type);
+ const struct cache_entry *e = r->index->cache[i];
+ i = check_one_conflict(r->index, i, &conflict_type);
if (conflict_type == THREE_STAGED)
string_list_insert(conflict, (const char *)e->name);
}
* NEEDSWORK: we may want to fix the caller that implements "rerere
* remaining" to do this without abusing merge_rr.
*/
-int rerere_remaining(struct string_list *merge_rr)
+int rerere_remaining(struct repository *r, struct string_list *merge_rr)
{
int i;
+
if (setup_rerere(merge_rr, RERERE_READONLY))
return 0;
- if (read_cache() < 0)
- return error("Could not read index");
+ if (read_index(r->index) < 0)
+ return error(_("index file corrupt"));
- for (i = 0; i < active_nr;) {
+ for (i = 0; i < r->index->cache_nr;) {
int conflict_type;
- const struct cache_entry *e = active_cache[i];
- i = check_one_conflict(i, &conflict_type);
+ const struct cache_entry *e = r->index->cache[i];
+ i = check_one_conflict(r->index, i, &conflict_type);
if (conflict_type == PUNTED)
string_list_insert(merge_rr, (const char *)e->name);
else if (conflict_type == RESOLVED) {
* if that recorded conflict resolves cleanly what we
* got in the "cur".
*/
-static int try_merge(const struct rerere_id *id, const char *path,
+static int try_merge(struct index_state *istate,
+ const struct rerere_id *id, const char *path,
mmfile_t *cur, mmbuffer_t *result)
{
int ret;
* A three-way merge. Note that this honors user-customizable
* low-level merge driver settings.
*/
- ret = ll_merge(result, path, &base, NULL, cur, "", &other, "", NULL);
+ ret = ll_merge(result, path, &base, NULL, cur, "", &other, "",
+ istate, NULL);
free(base.ptr);
free(other.ptr);
* Returns 0 for successful replay of recorded resolution, or non-zero
* for failure.
*/
-static int merge(const struct rerere_id *id, const char *path)
+static int merge(struct index_state *istate, const struct rerere_id *id, const char *path)
{
FILE *f;
int ret;
* Normalize the conflicts in path and write it out to
* "thisimage" temporary file.
*/
- if ((handle_file(path, NULL, rerere_path(id, "thisimage")) < 0) ||
+ if ((handle_file(istate, path, NULL, rerere_path(id, "thisimage")) < 0) ||
read_mmfile(&cur, rerere_path(id, "thisimage"))) {
ret = 1;
goto out;
}
- ret = try_merge(id, path, &cur, &result);
+ ret = try_merge(istate, id, path, &cur, &result);
if (ret)
goto out;
* Mark that "postimage" was used to help gc.
*/
if (utime(rerere_path(id, "postimage"), NULL) < 0)
- warning_errno("failed utime() on %s",
+ warning_errno(_("failed utime() on '%s'"),
rerere_path(id, "postimage"));
/* Update "path" with the resolution */
f = fopen(path, "w");
if (!f)
- return error_errno("Could not open %s", path);
+ return error_errno(_("could not open '%s'"), path);
if (fwrite(result.ptr, result.size, 1, f) != 1)
- error_errno("Could not write %s", path);
+ error_errno(_("could not write '%s'"), path);
if (fclose(f))
- return error_errno("Writing %s failed", path);
+ return error_errno(_("writing '%s' failed"), path);
out:
free(cur.ptr);
return ret;
}
-static void update_paths(struct string_list *update)
+static void update_paths(struct repository *r, struct string_list *update)
{
struct lock_file index_lock = LOCK_INIT;
int i;
for (i = 0; i < update->nr; i++) {
struct string_list_item *item = &update->items[i];
- if (add_file_to_cache(item->string, 0))
+ if (add_file_to_index(r->index, item->string, 0))
exit(128);
- fprintf(stderr, "Staged '%s' using previous resolution.\n",
+ fprintf_ln(stderr, _("Staged '%s' using previous resolution."),
item->string);
}
- if (write_locked_index(&the_index, &index_lock,
+ if (write_locked_index(r->index, &index_lock,
COMMIT_LOCK | SKIP_IF_UNCHANGED))
- die("Unable to write new index file");
+ die(_("unable to write new index file"));
}
static void remove_variant(struct rerere_id *id)
* only have the preimage for that conflict, in which case the result
* needs to be recorded as a resolution in a postimage file.
*/
-static void do_rerere_one_path(struct string_list_item *rr_item,
+static void do_rerere_one_path(struct index_state *istate,
+ struct string_list_item *rr_item,
struct string_list *update)
{
const char *path = rr_item->string;
/* Has the user resolved it already? */
if (variant >= 0) {
- if (!handle_file(path, NULL, NULL)) {
+ if (!handle_file(istate, path, NULL, NULL)) {
copy_file(rerere_path(id, "postimage"), path, 0666);
id->collection->status[variant] |= RR_HAS_POSTIMAGE;
- fprintf(stderr, "Recorded resolution for '%s'.\n", path);
+ fprintf_ln(stderr, _("Recorded resolution for '%s'."), path);
free_rerere_id(rr_item);
rr_item->util = NULL;
return;
continue;
vid.variant = variant;
- if (merge(&vid, path))
+ if (merge(istate, &vid, path))
continue; /* failed to replay */
/*
if (rerere_autoupdate)
string_list_insert(update, path);
else
- fprintf(stderr,
- "Resolved '%s' using previous resolution.\n",
- path);
+ fprintf_ln(stderr,
+ _("Resolved '%s' using previous resolution."),
+ path);
free_rerere_id(rr_item);
rr_item->util = NULL;
return;
assign_variant(id);
variant = id->variant;
- handle_file(path, NULL, rerere_path(id, "preimage"));
+ handle_file(istate, path, NULL, rerere_path(id, "preimage"));
if (id->collection->status[variant] & RR_HAS_POSTIMAGE) {
const char *path = rerere_path(id, "postimage");
if (unlink(path))
- die_errno("cannot unlink stray '%s'", path);
+ die_errno(_("cannot unlink stray '%s'"), path);
id->collection->status[variant] &= ~RR_HAS_POSTIMAGE;
}
id->collection->status[variant] |= RR_HAS_PREIMAGE;
- fprintf(stderr, "Recorded preimage for '%s'\n", path);
+ fprintf_ln(stderr, _("Recorded preimage for '%s'"), path);
}
-static int do_plain_rerere(struct string_list *rr, int fd)
+static int do_plain_rerere(struct repository *r,
+ struct string_list *rr, int fd)
{
struct string_list conflict = STRING_LIST_INIT_DUP;
struct string_list update = STRING_LIST_INIT_DUP;
int i;
- find_conflict(&conflict);
+ find_conflict(r, &conflict);
/*
* MERGE_RR records paths with conflicts immediately after
*/
for (i = 0; i < conflict.nr; i++) {
struct rerere_id *id;
- unsigned char sha1[20];
+ unsigned char hash[GIT_MAX_RAWSZ];
const char *path = conflict.items[i].string;
int ret;
- if (string_list_has_string(rr, path))
- continue;
-
/*
* Ask handle_file() to scan and assign a
* conflict ID. No need to write anything out
* yet.
*/
- ret = handle_file(path, sha1, NULL);
+ ret = handle_file(r->index, path, hash, NULL);
+ if (ret != 0 && string_list_has_string(rr, path)) {
+ remove_variant(string_list_lookup(rr, path)->util);
+ string_list_remove(rr, path, 1);
+ }
if (ret < 1)
continue;
- id = new_rerere_id(sha1);
+ id = new_rerere_id(hash);
string_list_insert(rr, path)->util = id;
/* Ensure that the directory exists. */
}
for (i = 0; i < rr->nr; i++)
- do_rerere_one_path(&rr->items[i], &update);
+ do_rerere_one_path(r->index, &rr->items[i], &update);
if (update.nr)
- update_paths(&update);
+ update_paths(r, &update);
return write_rr(rr, fd);
}
return rr_cache_exists;
if (!rr_cache_exists && mkdir_in_gitdir(git_path_rr_cache()))
- die("Could not create directory %s", git_path_rr_cache());
+ die(_("could not create directory '%s'"), git_path_rr_cache());
return 1;
}
* perform mergy operations, possibly leaving conflicted index entries
* and working tree files.
*/
-int rerere(int flags)
+int repo_rerere(struct repository *r, int flags)
{
struct string_list merge_rr = STRING_LIST_INIT_DUP;
int fd, status;
fd = setup_rerere(&merge_rr, flags);
if (fd < 0)
return 0;
- status = do_plain_rerere(&merge_rr, fd);
+ status = do_plain_rerere(r, &merge_rr, fd);
free_rerere_dirs();
return status;
}
return 0;
}
-static int handle_cache(const char *path, unsigned char *sha1, const char *output)
+static int handle_cache(struct index_state *istate,
+ const char *path, unsigned char *hash, const char *output)
{
mmfile_t mmfile[3] = {{NULL}};
mmbuffer_t result = {NULL, 0};
const struct cache_entry *ce;
- int pos, len, i, hunk_no;
+ int pos, len, i, has_conflicts;
struct rerere_io_mem io;
- int marker_size = ll_merge_marker_size(path);
+ int marker_size = ll_merge_marker_size(istate, path);
/*
* Reproduce the conflicted merge in-core
*/
len = strlen(path);
- pos = cache_name_pos(path, len);
+ pos = index_name_pos(istate, path, len);
if (0 <= pos)
return -1;
pos = -pos - 1;
- while (pos < active_nr) {
+ while (pos < istate->cache_nr) {
enum object_type type;
unsigned long size;
- ce = active_cache[pos++];
+ ce = istate->cache[pos++];
if (ce_namelen(ce) != len || memcmp(ce->name, path, len))
break;
i = ce_stage(ce) - 1;
*/
ll_merge(&result, path, &mmfile[0], NULL,
&mmfile[1], "ours",
- &mmfile[2], "theirs", NULL);
+ &mmfile[2], "theirs",
+ istate, NULL);
for (i = 0; i < 3; i++)
free(mmfile[i].ptr);
* Grab the conflict ID and optionally write the original
* contents with conflict markers out.
*/
- hunk_no = handle_path(sha1, (struct rerere_io *)&io, marker_size);
+ has_conflicts = handle_path(hash, (struct rerere_io *)&io, marker_size);
strbuf_release(&io.input);
if (io.io.output)
fclose(io.io.output);
- return hunk_no;
+ return has_conflicts;
}
-static int rerere_forget_one_path(const char *path, struct string_list *rr)
+static int rerere_forget_one_path(struct index_state *istate,
+ const char *path,
+ struct string_list *rr)
{
const char *filename;
struct rerere_id *id;
- unsigned char sha1[20];
+ unsigned char hash[GIT_MAX_RAWSZ];
int ret;
struct string_list_item *item;
* Recreate the original conflict from the stages in the
* index and compute the conflict ID
*/
- ret = handle_cache(path, sha1, NULL);
+ ret = handle_cache(istate, path, hash, NULL);
if (ret < 1)
- return error("Could not parse conflict hunks in '%s'", path);
+ return error(_("could not parse conflict hunks in '%s'"), path);
/* Nuke the recorded resolution for the conflict */
- id = new_rerere_id(sha1);
+ id = new_rerere_id(hash);
for (id->variant = 0;
id->variant < id->collection->status_nr;
if (!has_rerere_resolution(id))
continue;
- handle_cache(path, sha1, rerere_path(id, "thisimage"));
+ handle_cache(istate, path, hash, rerere_path(id, "thisimage"));
if (read_mmfile(&cur, rerere_path(id, "thisimage"))) {
free(cur.ptr);
- error("Failed to update conflicted state in '%s'", path);
+ error(_("failed to update conflicted state in '%s'"), path);
goto fail_exit;
}
- cleanly_resolved = !try_merge(id, path, &cur, &result);
+ cleanly_resolved = !try_merge(istate, id, path, &cur, &result);
free(result.ptr);
free(cur.ptr);
if (cleanly_resolved)
}
if (id->collection->status_nr <= id->variant) {
- error("no remembered resolution for '%s'", path);
+ error(_("no remembered resolution for '%s'"), path);
goto fail_exit;
}
filename = rerere_path(id, "postimage");
if (unlink(filename)) {
if (errno == ENOENT)
- error("no remembered resolution for %s", path);
+ error(_("no remembered resolution for '%s'"), path);
else
- error_errno("cannot unlink %s", filename);
+ error_errno(_("cannot unlink '%s'"), filename);
goto fail_exit;
}
* conflict in the working tree, run us again to record
* the postimage.
*/
- handle_cache(path, sha1, rerere_path(id, "preimage"));
- fprintf(stderr, "Updated preimage for '%s'\n", path);
+ handle_cache(istate, path, hash, rerere_path(id, "preimage"));
+ fprintf_ln(stderr, _("Updated preimage for '%s'"), path);
/*
* And remember that we can record resolution for this
item = string_list_insert(rr, path);
free_rerere_id(item);
item->util = id;
- fprintf(stderr, "Forgot resolution for %s\n", path);
+ fprintf(stderr, _("Forgot resolution for '%s'\n"), path);
return 0;
fail_exit:
return -1;
}
-int rerere_forget(struct pathspec *pathspec)
+int rerere_forget(struct repository *r, struct pathspec *pathspec)
{
int i, fd;
struct string_list conflict = STRING_LIST_INIT_DUP;
struct string_list merge_rr = STRING_LIST_INIT_DUP;
- if (read_cache() < 0)
- return error("Could not read index");
+ if (read_index(r->index) < 0)
+ return error(_("index file corrupt"));
fd = setup_rerere(&merge_rr, RERERE_NOAUTOUPDATE);
if (fd < 0)
* recover the original conflicted state and then
* find the conflicted paths.
*/
- unmerge_cache(pathspec);
- find_conflict(&conflict);
+ unmerge_index(r->index, pathspec);
+ find_conflict(r, &conflict);
for (i = 0; i < conflict.nr; i++) {
struct string_list_item *it = &conflict.items[i];
- if (!match_pathspec(&the_index, pathspec, it->string,
+ if (!match_pathspec(r->index, pathspec, it->string,
strlen(it->string), 0, NULL, 0))
continue;
- rerere_forget_one_path(it->string, &merge_rr);
+ rerere_forget_one_path(r->index, it->string, &merge_rr);
}
return write_rr(&merge_rr, fd);
}
git_config(git_default_config, NULL);
dir = opendir(git_path("rr-cache"));
if (!dir)
- die_errno("unable to open rr-cache directory");
+ die_errno(_("unable to open rr-cache directory"));
/* Collect stale conflict IDs ... */
while ((e = readdir(dir))) {
struct rerere_dir *rr_dir;
#include "string-list.h"
struct pathspec;
+struct repository;
#define RERERE_AUTOUPDATE 01
#define RERERE_NOAUTOUPDATE 02
};
int setup_rerere(struct string_list *, int);
-int rerere(int);
+#ifndef NO_THE_REPOSITORY_COMPATIBILITY_MACROS
+#define rerere(flags) repo_rerere(the_repository, flags)
+#endif
+int repo_rerere(struct repository *, int);
/*
* Given the conflict ID and the name of a "file" used for replaying
* the recorded resolution (e.g. "preimage", "postimage"), return the
* return the path to the directory that houses these files.
*/
const char *rerere_path(const struct rerere_id *, const char *file);
-int rerere_forget(struct pathspec *);
-int rerere_remaining(struct string_list *);
+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 *);
#include "packfile.h"
#include "worktree.h"
#include "argv-array.h"
+#include "commit-reach.h"
volatile show_early_output_fn_t show_early_output;
blob->object.flags |= UNINTERESTING;
}
-static void mark_tree_contents_uninteresting(struct tree *tree)
+static void mark_tree_contents_uninteresting(struct repository *r,
+ struct tree *tree)
{
struct tree_desc desc;
struct name_entry entry;
while (tree_entry(&desc, &entry)) {
switch (object_type(entry.mode)) {
case OBJ_TREE:
- mark_tree_uninteresting(lookup_tree(the_repository, entry.oid));
+ mark_tree_uninteresting(r, lookup_tree(r, entry.oid));
break;
case OBJ_BLOB:
- mark_blob_uninteresting(lookup_blob(the_repository, entry.oid));
+ mark_blob_uninteresting(lookup_blob(r, entry.oid));
break;
default:
/* Subproject commit - not in this repository */
free_tree_buffer(tree);
}
-void mark_tree_uninteresting(struct tree *tree)
+void mark_tree_uninteresting(struct repository *r, struct tree *tree)
{
struct object *obj;
if (obj->flags & UNINTERESTING)
return;
obj->flags |= UNINTERESTING;
- mark_tree_contents_uninteresting(tree);
+ mark_tree_contents_uninteresting(r, tree);
}
struct commit_stack {
strbuf_release(&buf);
return; /* do not add the commit itself */
}
- obj->flags |= USER_GIVEN;
add_object_array_with_path(obj, name, &revs->pending, mode, path);
}
struct object *obj;
if (get_oid("HEAD", &oid))
return;
- obj = parse_object(the_repository, &oid);
+ obj = parse_object(revs->repo, &oid);
if (!obj)
return;
add_pending_object(revs, obj, "HEAD");
{
struct object *object;
- object = parse_object(the_repository, oid);
+ object = parse_object(revs->repo, oid);
if (!object) {
if (revs->ignore_missing)
return object;
add_pending_object(revs, object, tag->tag);
if (!tag->tagged)
die("bad tag");
- object = parse_object(the_repository, &tag->tagged->oid);
+ object = parse_object(revs->repo, &tag->tagged->oid);
if (!object) {
if (revs->ignore_missing_links || (flags & UNINTERESTING))
return NULL;
if (!revs->tree_objects)
return NULL;
if (flags & UNINTERESTING) {
- mark_tree_contents_uninteresting(tree);
+ mark_tree_contents_uninteresting(revs->repo, tree);
return NULL;
}
add_pending_object_with_path(revs, object, name, mode, path);
return;
left_first = left_count < right_count;
- init_patch_ids(&ids);
+ init_patch_ids(revs->repo, &ids);
ids.diffopts.pathspec = revs->diffopt.pathspec;
/* Compute patch-ids for one side */
{
struct all_refs_cb *cb = cb_data;
if (!is_null_oid(oid)) {
- struct object *o = parse_object(the_repository, oid);
+ struct object *o = parse_object(cb->all_revs->repo, oid);
if (o) {
o->flags |= cb->all_flags;
/* ??? CMDLINEFLAGS ??? */
cb.all_revs = revs;
cb.all_flags = flags;
- cb.refs = get_main_ref_store(the_repository);
+ cb.refs = get_main_ref_store(revs->repo);
for_each_reflog(handle_one_reflog, &cb);
if (!revs->single_worktree)
int i;
if (it->entry_count >= 0) {
- struct tree *tree = lookup_tree(the_repository, &it->oid);
+ struct tree *tree = lookup_tree(revs->repo, &it->oid);
add_pending_object_with_path(revs, &tree->object, "",
040000, path->buf);
}
if (S_ISGITLINK(ce->ce_mode))
continue;
- blob = lookup_blob(the_repository, &ce->oid);
+ blob = lookup_blob(revs->repo, &ce->oid);
if (!blob)
die("unable to add index blob to traversal");
add_pending_object_with_path(revs, &blob->object, "",
{
struct worktree **worktrees, **p;
- read_cache();
- do_add_index_objects_to_pending(revs, &the_index);
+ read_index(revs->repo->index);
+ do_add_index_objects_to_pending(revs, revs->repo->index);
if (revs->single_worktree)
return;
return 1;
}
-void init_revisions(struct rev_info *revs, const char *prefix)
+void repo_init_revisions(struct repository *r,
+ struct rev_info *revs,
+ const char *prefix)
{
memset(revs, 0, sizeof(*revs));
+ revs->repo = r;
revs->abbrev = DEFAULT_ABBREV;
revs->ignore_merges = 1;
revs->simplify_history = 1;
revs->commit_format = CMIT_FMT_DEFAULT;
revs->expand_tabs_in_log_default = 8;
- init_grep_defaults();
- grep_init(&revs->grep_filter, prefix);
+ init_grep_defaults(revs->repo);
+ grep_init(&revs->grep_filter, revs->repo, prefix);
revs->grep_filter.status_only = 1;
- diff_setup(&revs->diffopt);
+ repo_diff_setup(revs->repo, &revs->diffopt);
if (prefix && !revs->diffopt.prefix) {
revs->diffopt.prefix = prefix;
revs->diffopt.prefix_length = strlen(prefix);
struct object_id oid;
const char **prune = NULL;
int i, prune_num = 1; /* counting terminating NULL */
+ struct index_state *istate = revs->repo->index;
if (get_oid("HEAD", &oid))
die("--merge without HEAD?");
free_commit_list(bases);
head->object.flags |= SYMMETRIC_LEFT;
- if (!active_nr)
- read_cache();
- for (i = 0; i < active_nr; i++) {
- const struct cache_entry *ce = active_cache[i];
+ if (!istate->cache_nr)
+ read_index(istate);
+ for (i = 0; i < istate->cache_nr; i++) {
+ const struct cache_entry *ce = istate->cache[i];
if (!ce_stage(ce))
continue;
- if (ce_path_match(&the_index, ce, &revs->prune_data, NULL)) {
+ if (ce_path_match(istate, ce, &revs->prune_data, NULL)) {
prune_num++;
REALLOC_ARRAY(prune, prune_num);
prune[prune_num-2] = ce->name;
prune[prune_num-1] = NULL;
}
- while ((i+1 < active_nr) &&
- ce_same_name(ce, active_cache[i+1]))
+ while ((i+1 < istate->cache_nr) &&
+ ce_same_name(ce, istate->cache[i+1]))
i++;
}
clear_pathspec(&revs->prune_data);
*dotdot = '\0';
}
- a_obj = parse_object(the_repository, &a_oid);
- b_obj = parse_object(the_repository, &b_oid);
+ a_obj = parse_object(revs->repo, &a_oid);
+ b_obj = parse_object(revs->repo, &b_oid);
if (!a_obj || !b_obj)
return dotdot_missing(arg, dotdot, revs, symmetric);
struct commit *a, *b;
struct commit_list *exclude;
- a = lookup_commit_reference(the_repository, &a_obj->oid);
- b = lookup_commit_reference(the_repository, &b_obj->oid);
+ a = lookup_commit_reference(revs->repo, &a_obj->oid);
+ b = lookup_commit_reference(revs->repo, &b_obj->oid);
if (!a || !b)
return dotdot_missing(arg, dotdot, revs, symmetric);
BUG("--single-worktree cannot be used together with submodule");
refs = get_submodule_ref_store(submodule);
} else
- refs = get_main_ref_store(the_repository);
+ refs = get_main_ref_store(revs->repo);
/*
* NOTE!
*/
int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct setup_revision_opt *opt)
{
- int i, flags, left, seen_dashdash, read_from_stdin, got_rev_arg = 0, revarg_opt;
+ int i, flags, left, seen_dashdash, got_rev_arg = 0, revarg_opt;
struct argv_array prune_data = ARGV_ARRAY_INIT;
const char *submodule = NULL;
revarg_opt = opt ? opt->revarg_opt : 0;
if (seen_dashdash)
revarg_opt |= REVARG_CANNOT_BE_FILENAME;
- read_from_stdin = 0;
for (left = i = 1; i < argc; i++) {
const char *arg = argv[i];
if (*arg == '-') {
argv[left++] = arg;
continue;
}
- if (read_from_stdin++)
+ if (revs->read_from_stdin++)
die("--stdin given twice?");
read_revisions_from_stdin(revs, &prune_data);
continue;
static int mark_uninteresting(const struct object_id *oid,
struct packed_git *pack,
uint32_t pos,
- void *unused)
+ void *cb)
{
- struct object *o = parse_object(the_repository, oid);
+ struct rev_info *revs = cb;
+ struct object *o = parse_object(revs->repo, oid);
o->flags |= UNINTERESTING | SEEN;
return 0;
}
revs->treesame.name = "treesame";
if (revs->exclude_promisor_objects) {
- for_each_packed_object(mark_uninteresting, NULL,
+ for_each_packed_object(mark_uninteresting, revs,
FOR_EACH_OBJECT_PROMISOR_ONLY);
}
struct commit_list *p;
for (p = revs->previous_parents; p; p = p->next)
if (p->item == NULL || /* first commit */
- !oidcmp(&p->item->object.oid, &commit->object.oid))
+ oideq(&p->item->object.oid, &commit->object.oid))
break;
revs->linear = p != NULL;
}
#define SYMMETRIC_LEFT (1u<<8)
#define PATCHSAME (1u<<9)
#define BOTTOM (1u<<10)
-#define USER_GIVEN (1u<<25) /* given directly by the user */
+/*
+ * Indicates object was reached by traversal. i.e. not given by user on
+ * command-line or stdin.
+ * NEEDSWORK: NOT_USER_GIVEN doesn't apply to commits because we only support
+ * filtering trees and blobs, but it may be useful to support filtering commits
+ * in the future.
+ */
+#define NOT_USER_GIVEN (1u<<25)
#define TRACK_LINEAR (1u<<26)
-#define ALL_REV_FLAGS (((1u<<11)-1) | USER_GIVEN | TRACK_LINEAR)
+#define ALL_REV_FLAGS (((1u<<11)-1) | NOT_USER_GIVEN | TRACK_LINEAR)
#define DECORATE_SHORT_REFS 1
#define DECORATE_FULL_REFS 2
-struct rev_info;
struct log_info;
+struct repository;
+struct rev_info;
struct string_list;
struct saved_parents;
define_shared_commit_slab(revision_sources, char *);
/* Starting list */
struct commit_list *commits;
struct object_array pending;
+ struct repository *repo;
/* Parents of shown commits */
struct object_array boundary_commits;
*/
int rev_input_given;
+ /*
+ * Whether we read from stdin due to the --stdin option.
+ */
+ int read_from_stdin;
+
/* topo-sort */
enum rev_sort_order sort_order;
line_level_traverse:1,
tree_blobs_in_commit_order:1,
+ /*
+ * Blobs are shown without regard for their existence.
+ * But not so for trees: unless exclude_promisor_objects
+ * is set and the tree in question is a promisor object;
+ * OR ignore_missing_links is set, the revision walker
+ * dies with a "bad tree object HASH" message when
+ * encountering a missing tree. For callers that can
+ * handle missing trees and want them to be filterable
+ * and showable, set this to true. The revision walker
+ * will filter and show such a missing tree as usual,
+ * but will not attempt to recurse into this tree
+ * object.
+ */
+ do_not_die_on_missing_tree:1,
+
/* for internal use only */
exclude_promisor_objects:1;
/* notes-specific options: which refs to show */
struct display_notes_opt notes_opt;
+ /* interdiff */
+ const struct object_id *idiff_oid1;
+ const struct object_id *idiff_oid2;
+ const char *idiff_title;
+
+ /* range-diff */
+ const char *rdiff1;
+ const char *rdiff2;
+ int creation_factor;
+ const char *rdiff_title;
+
/* commit counts */
int count_left;
int count_right;
struct setup_revision_opt {
const char *def;
void (*tweak)(struct rev_info *, struct setup_revision_opt *);
- const char *submodule;
+ const char *submodule; /* TODO: drop this and use rev_info->repo */
int assume_dashdash;
unsigned revarg_opt;
};
-void init_revisions(struct rev_info *revs, const char *prefix);
+#ifndef NO_THE_REPOSITORY_COMPATIBILITY_MACROS
+#define init_revisions(revs, prefix) repo_init_revisions(the_repository, revs, prefix)
+#endif
+void repo_init_revisions(struct repository *r,
+ struct rev_info *revs,
+ const char *prefix);
int setup_revisions(int argc, const char **argv, struct rev_info *revs,
struct setup_revision_opt *);
void parse_revision_opt(struct rev_info *revs, struct parse_opt_ctx_t *ctx,
const struct commit *commit);
void mark_parents_uninteresting(struct commit *commit);
-void mark_tree_uninteresting(struct tree *tree);
+void mark_tree_uninteresting(struct repository *r, struct tree *tree);
void show_object_with_name(FILE *, struct object *, const char *);
set_error_routine(old_errfn);
}
-static void prepare_cmd(struct argv_array *out, const struct child_process *cmd)
+static int prepare_cmd(struct argv_array *out, const struct child_process *cmd)
{
if (!cmd->argv[0])
BUG("command is empty");
/*
* If there are no '/' characters in the command then perform a path
* lookup and use the resolved path as the command to exec. If there
- * are no '/' characters or if the command wasn't found in the path,
- * have exec attempt to invoke the command directly.
+ * are '/' characters, we have exec attempt to invoke the command
+ * directly.
*/
if (!strchr(out->argv[1], '/')) {
char *program = locate_in_PATH(out->argv[1]);
if (program) {
free((char *)out->argv[1]);
out->argv[1] = program;
+ } else {
+ argv_array_clear(out);
+ errno = ENOENT;
+ return -1;
}
}
+
+ return 0;
}
static char **prep_childenv(const char *const *deltaenv)
struct child_err cerr;
struct atfork_state as;
+ if (prepare_cmd(&argv, cmd) < 0) {
+ failed_errno = errno;
+ cmd->pid = -1;
+ goto end_of_spawn;
+ }
+
if (pipe(notify_pipe))
notify_pipe[0] = notify_pipe[1] = -1;
set_cloexec(null_fd);
}
- prepare_cmd(&argv, cmd);
childenv = prep_childenv(cmd->env);
atfork_prepare(&as);
argv_array_clear(&argv);
free(childenv);
}
+end_of_spawn:
+
#else
{
int fhin = 0, fhout = 1, fherr = 2;
#include "oidset.h"
#include "commit-slab.h"
#include "alias.h"
+#include "commit-reach.h"
#define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
* Returns 3 when sob exists within conforming footer as last entry
*/
static int has_conforming_footer(struct strbuf *sb, struct strbuf *sob,
- int ignore_footer)
+ size_t ignore_footer)
{
+ struct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT;
struct trailer_info info;
- int i;
+ size_t i;
int found_sob = 0, found_sob_last = 0;
- trailer_info_get(&info, sb->buf);
+ opts.no_divider = 1;
+
+ trailer_info_get(&info, sb->buf, &opts);
if (info.trailer_start == info.trailer_end)
return 0;
struct strbuf sb = STRBUF_INIT;
struct strbuf err = STRBUF_INIT;
- read_cache();
- if (checkout_fast_forward(from, to, 1))
+ read_index(&the_index);
+ if (checkout_fast_forward(the_repository, from, to, 1))
return -1; /* the callee should have complained already */
strbuf_addf(&sb, _("%s: fast-forward"), _(action_name(opts)));
if (!(cache_tree_oid = get_cache_tree_oid()))
return -1;
- return !oidcmp(cache_tree_oid, get_commit_tree_oid(head_commit));
+ return oideq(cache_tree_oid, get_commit_tree_oid(head_commit));
}
static int write_author_script(const char *message)
if ((flags & ALLOW_EMPTY))
argv_array_push(&cmd.args, "--allow-empty");
- if (opts->allow_empty_message)
+ if (!(flags & EDIT_MSG))
argv_array_push(&cmd.args, "--allow-empty-message");
if (cmd.err == -1) {
strbuf_release(&author_ident);
strbuf_release(&committer_ident);
- init_revisions(&rev, prefix);
+ repo_init_revisions(the_repository, &rev, prefix);
setup_revisions(0, NULL, &rev, NULL);
rev.diff = 1;
current_head = lookup_commit_reference(the_repository, &oid);
if (!current_head)
return error(_("could not parse HEAD"));
- if (oidcmp(&oid, ¤t_head->object.oid)) {
+ if (!oideq(&oid, ¤t_head->object.oid)) {
warning(_("HEAD %s is not a commit!"),
oid_to_hex(&oid));
}
goto out;
}
- if (!(flags & ALLOW_EMPTY) && !oidcmp(current_head ?
- get_commit_tree_oid(current_head) :
- the_hash_algo->empty_tree, &tree)) {
+ if (!(flags & ALLOW_EMPTY) && oideq(current_head ?
+ get_commit_tree_oid(current_head) :
+ the_hash_algo->empty_tree, &tree)) {
res = 1; /* run 'git commit' to display error message */
goto out;
}
if (cleanup != COMMIT_MSG_CLEANUP_NONE)
strbuf_stripspace(msg, cleanup == COMMIT_MSG_CLEANUP_ALL);
- if (!opts->allow_empty_message && message_is_empty(msg, cleanup)) {
+ if ((flags & EDIT_MSG) && message_is_empty(msg, cleanup)) {
res = 1; /* run 'git commit' to display error message */
goto out;
}
ptree_oid = the_hash_algo->empty_tree; /* commit is root */
}
- return !oidcmp(ptree_oid, get_commit_tree_oid(commit));
+ return oideq(ptree_oid, get_commit_tree_oid(commit));
}
/*
unborn = get_oid("HEAD", &head);
/* Do we want to generate a root commit? */
if (is_pick_or_similar(command) && opts->have_squash_onto &&
- !oidcmp(&head, &opts->squash_onto)) {
+ oideq(&head, &opts->squash_onto)) {
if (is_fixup(command))
return error(_("cannot fixup root commit"));
flags |= CREATE_ROOT_COMMIT;
oid_to_hex(&commit->object.oid));
if (opts->allow_ff && !is_fixup(command) &&
- ((parent && !oidcmp(&parent->object.oid, &head)) ||
+ ((parent && oideq(&parent->object.oid, &head)) ||
(!parent && unborn))) {
if (is_rebase_i(opts))
write_author_script(msg.message);
commit_list_insert(base, &common);
commit_list_insert(next, &remotes);
- res |= try_merge_command(opts->strategy,
+ res |= try_merge_command(the_repository, opts->strategy,
opts->xopts_nr, (const char **)opts->xopts,
common, oid_to_hex(&head), remotes);
free_commit_list(common);
: _("could not apply %s... %s"),
short_commit_name(commit), msg.subject);
print_advice(res == 1, opts);
- rerere(opts->allow_rerere_auto);
+ repo_rerere(the_repository, opts->allow_rerere_auto);
goto leave;
}
{
struct lock_file index_lock = LOCK_INIT;
int index_fd = hold_locked_index(&index_lock, 0);
- if (read_index_preload(&the_index, NULL) < 0) {
+ if (read_index_preload(&the_index, NULL, 0) < 0) {
rollback_lock_file(&index_lock);
return error(_("git %s: failed to read the index"),
_(action_name(opts)));
if (get_oid("HEAD", &actual_head))
oidclr(&actual_head);
- return !oidcmp(&actual_head, &expected_head);
+ return oideq(&actual_head, &expected_head);
}
static int reset_for_rollback(const struct object_id *oid)
strbuf_addf(&buf, "%s/patch", get_dir(opts));
memset(&log_tree_opt, 0, sizeof(log_tree_opt));
- init_revisions(&log_tree_opt, NULL);
+ repo_init_revisions(the_repository, &log_tree_opt, NULL);
log_tree_opt.abbrev = 0;
log_tree_opt.diff = 1;
log_tree_opt.diffopt.output_format = DIFF_FORMAT_PATCH;
}
if (opts->have_squash_onto &&
- !oidcmp(&head_commit->object.oid, &opts->squash_onto)) {
+ oideq(&head_commit->object.oid, &opts->squash_onto)) {
/*
* When the user tells us to "merge" something into a
* "[new root]", let's simply fast-forward to the merge head.
* commit, we cannot fast-forward.
*/
can_fast_forward = opts->allow_ff && commit && commit->parents &&
- !oidcmp(&commit->parents->item->object.oid,
- &head_commit->object.oid);
+ oideq(&commit->parents->item->object.oid,
+ &head_commit->object.oid);
/*
* If any merge head is different from the original one, we cannot
struct commit_list *p = commit->parents->next;
for (j = to_merge; j && p; j = j->next, p = p->next)
- if (oidcmp(&j->item->object.oid,
+ if (!oideq(&j->item->object.oid,
&p->item->object.oid)) {
can_fast_forward = 0;
break;
write_message("no-ff", 5, git_path_merge_mode(the_repository), 0);
bases = get_merge_bases(head_commit, merge_commit);
- if (bases && !oidcmp(&merge_commit->object.oid,
- &bases->item->object.oid)) {
+ if (bases && oideq(&merge_commit->object.oid,
+ &bases->item->object.oid)) {
ret = 0;
/* skip merging an ancestor of HEAD */
goto leave_merge;
rollback_lock_file(&lock);
if (ret)
- rerere(opts->allow_rerere_auto);
+ repo_rerere(the_repository, opts->allow_rerere_auto);
else
/*
* In case of problems, we now want to return a positive
*/
if (item->command == TODO_REWORD &&
!get_oid("HEAD", &oid) &&
- (!oidcmp(&item->commit->object.oid, &oid) ||
+ (oideq(&item->commit->object.oid, &oid) ||
(opts->have_squash_onto &&
- !oidcmp(&opts->squash_onto, &oid))))
+ oideq(&opts->squash_onto, &oid))))
to_amend = 1;
return res | error_with_patch(item->commit,
struct object_id orig, head;
memset(&log_tree_opt, 0, sizeof(log_tree_opt));
- init_revisions(&log_tree_opt, NULL);
+ repo_init_revisions(the_repository, &log_tree_opt, NULL);
log_tree_opt.diff = 1;
log_tree_opt.diffopt.output_format =
DIFF_FORMAT_DIFFSTAT;
if (get_oid_hex(rev.buf, &to_amend))
return error(_("invalid contents: '%s'"),
rebase_path_amend());
- if (!is_clean && oidcmp(&head, &to_amend))
+ if (!is_clean && !oideq(&head, &to_amend))
return error(_("\nYou have uncommitted changes in your "
"working tree. Please, commit them\n"
"first and then run 'git rebase "
* the commit message and if there was a squash, let the user
* edit it.
*/
- if (is_clean && !oidcmp(&head, &to_amend) &&
- opts->current_fixup_count > 0 &&
- file_exists(rebase_path_stopped_sha())) {
+ if (!is_clean || !opts->current_fixup_count)
+ ; /* this is not the final fixup */
+ else if (!oideq(&head, &to_amend) ||
+ !file_exists(rebase_path_stopped_sha())) {
+ /* was a final fixup or squash done manually? */
+ if (!is_fixup(peek_command(todo_list, 0))) {
+ unlink(rebase_path_fixup_msg());
+ unlink(rebase_path_squash_msg());
+ unlink(rebase_path_current_fixups());
+ strbuf_reset(&opts->current_fixups);
+ opts->current_fixup_count = 0;
+ }
+ } else {
+ /* we are in a fixup/squash chain */
const char *p = opts->current_fixups.buf;
int len = opts->current_fixups.len;
return res;
}
-void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag)
+void append_signoff(struct strbuf *msgbuf, size_t ignore_footer, unsigned flag)
{
unsigned no_dup_sob = flag & APPEND_SIGNOFF_DEDUP;
struct strbuf sob = STRBUF_INIT;
struct object_id *oid = &parent->item->object.oid;
if (!oidset_contains(&interesting, oid))
continue;
- if (!oidset_contains(&child_seen, oid))
- oidset_insert(&child_seen, oid);
- else
+ if (oidset_insert(&child_seen, oid))
label_oid(oid, "branch-point", &state);
}
const char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? "p" : "pick";
int rebase_merges = flags & TODO_LIST_REBASE_MERGES;
- init_revisions(&revs, NULL);
+ repo_init_revisions(the_repository, &revs, NULL);
revs.verbose_header = 1;
if (!rebase_merges)
revs.max_parents = 1;
if (item->commit->parents->next)
break; /* merge commit */
parent_oid = &item->commit->parents->item->object.oid;
- if (hashcmp(parent_oid->hash, oid->hash))
+ if (!oideq(parent_oid, oid))
break;
oid = &item->commit->object.oid;
}
extern const char sign_off_header[];
-void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag);
+/*
+ * Append a signoff to the commit message in "msgbuf". The ignore_footer
+ * parameter specifies the number of bytes at the end of msgbuf that should
+ * not be considered at all. I.e., they are not checked for existing trailers,
+ * and the new signoff will be spliced into the buffer before those bytes.
+ */
+void append_signoff(struct strbuf *msgbuf, size_t ignore_footer, unsigned flag);
+
void append_conflicts_hint(struct strbuf *msgbuf);
int message_is_empty(const struct strbuf *sb,
enum commit_msg_cleanup_mode cleanup_mode);
objdir = get_object_directory();
objdirlen = strlen(objdir);
- for (p = get_packed_git(the_repository); p; p = p->next) {
+ for (p = get_all_packs(the_repository); p; p = p->next) {
/* we ignore things on alternate path since they are
* not available to the pullers in general.
*/
}
num_pack = i;
info = xcalloc(num_pack, sizeof(struct pack_info *));
- for (i = 0, p = get_packed_git(the_repository); p; p = p->next) {
+ for (i = 0, p = get_all_packs(the_repository); p; p = p->next) {
if (!p->pack_local)
continue;
info[i] = xcalloc(1, sizeof(struct pack_info));
for (i = 0; i < array->nr; i++) {
int ret;
- if (i > 0 && !oidcmp(array->oid + i, array->oid + i - 1))
+ if (i > 0 && oideq(array->oid + i, array->oid + i - 1))
continue;
ret = fn(array->oid + i, data);
if (ret)
struct cached_object *co = cached_objects;
for (i = 0; i < cached_object_nr; i++, co++) {
- if (!oidcmp(&co->oid, oid))
+ if (oideq(&co->oid, oid))
return co;
}
- if (!oidcmp(oid, the_hash_algo->empty_tree))
+ if (oideq(oid, the_hash_algo->empty_tree))
return &empty_tree;
return NULL;
}
if (map) {
hash_object_file(map, size, type, &real_oid);
- return oidcmp(oid, &real_oid) ? -1 : 0;
+ return !oideq(oid, &real_oid) ? -1 : 0;
}
st = open_istream(oid, &obj_type, &size, NULL);
}
the_hash_algo->final_fn(real_oid.hash, &c);
close_istream(st);
- return oidcmp(oid, &real_oid) ? -1 : 0;
+ return !oideq(oid, &real_oid) ? -1 : 0;
}
int git_open_cloexec(const char *name, int flags)
* TODO Pass a repository struct through fetch_object,
* such that arbitrary repositories work.
*/
- fetch_object(repository_format_partial_clone, real->hash);
+ fetch_objects(repository_format_partial_clone, real, 1);
already_retried = 1;
continue;
}
die(_("deflateEnd on object %s failed (%d)"), oid_to_hex(oid),
ret);
the_hash_algo->final_fn(parano_oid.hash, &c);
- if (oidcmp(oid, ¶no_oid) != 0)
+ if (!oideq(oid, ¶no_oid))
die(_("confused by unstable object source data for %s"),
oid_to_hex(oid));
die(_("corrupt tag"));
}
-static int index_mem(struct object_id *oid, void *buf, size_t size,
+static int index_mem(struct index_state *istate,
+ struct object_id *oid, void *buf, size_t size,
enum object_type type,
const char *path, unsigned flags)
{
*/
if ((type == OBJ_BLOB) && path) {
struct strbuf nbuf = STRBUF_INIT;
- if (convert_to_git(&the_index, path, buf, size, &nbuf,
+ if (convert_to_git(istate, path, buf, size, &nbuf,
get_conv_flags(flags))) {
buf = strbuf_detach(&nbuf, &size);
re_allocated = 1;
return ret;
}
-static int index_stream_convert_blob(struct object_id *oid, int fd,
- const char *path, unsigned flags)
+static int index_stream_convert_blob(struct index_state *istate,
+ struct object_id *oid,
+ int fd,
+ const char *path,
+ unsigned flags)
{
int ret;
const int write_object = flags & HASH_WRITE_OBJECT;
struct strbuf sbuf = STRBUF_INIT;
assert(path);
- assert(would_convert_to_git_filter_fd(&the_index, path));
+ assert(would_convert_to_git_filter_fd(istate, path));
- convert_to_git_filter_fd(&the_index, path, fd, &sbuf,
+ convert_to_git_filter_fd(istate, path, fd, &sbuf,
get_conv_flags(flags));
if (write_object)
return ret;
}
-static int index_pipe(struct object_id *oid, int fd, enum object_type type,
+static int index_pipe(struct index_state *istate, struct object_id *oid,
+ int fd, enum object_type type,
const char *path, unsigned flags)
{
struct strbuf sbuf = STRBUF_INIT;
int ret;
if (strbuf_read(&sbuf, fd, 4096) >= 0)
- ret = index_mem(oid, sbuf.buf, sbuf.len, type, path, flags);
+ ret = index_mem(istate, oid, sbuf.buf, sbuf.len, type, path, flags);
else
ret = -1;
strbuf_release(&sbuf);
#define SMALL_FILE_SIZE (32*1024)
-static int index_core(struct object_id *oid, int fd, size_t size,
+static int index_core(struct index_state *istate,
+ struct object_id *oid, int fd, size_t size,
enum object_type type, const char *path,
unsigned flags)
{
int ret;
if (!size) {
- ret = index_mem(oid, "", size, type, path, flags);
+ ret = index_mem(istate, oid, "", size, type, path, flags);
} else if (size <= SMALL_FILE_SIZE) {
char *buf = xmalloc(size);
ssize_t read_result = read_in_full(fd, buf, size);
ret = error(_("short read while indexing %s"),
path ? path : "<unknown>");
else
- ret = index_mem(oid, buf, size, type, path, flags);
+ ret = index_mem(istate, oid, buf, size, type, path, flags);
free(buf);
} else {
void *buf = xmmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
- ret = index_mem(oid, buf, size, type, path, flags);
+ ret = index_mem(istate, oid, buf, size, type, path, flags);
munmap(buf, size);
}
return ret;
return index_bulk_checkin(oid, fd, size, type, path, flags);
}
-int index_fd(struct object_id *oid, int fd, struct stat *st,
+int index_fd(struct index_state *istate, struct object_id *oid,
+ int fd, struct stat *st,
enum object_type type, const char *path, unsigned flags)
{
int ret;
* Call xsize_t() only when needed to avoid potentially unnecessary
* die() for large files.
*/
- if (type == OBJ_BLOB && path && would_convert_to_git_filter_fd(&the_index, path))
- ret = index_stream_convert_blob(oid, fd, path, flags);
+ if (type == OBJ_BLOB && path && would_convert_to_git_filter_fd(istate, path))
+ ret = index_stream_convert_blob(istate, oid, fd, path, flags);
else if (!S_ISREG(st->st_mode))
- ret = index_pipe(oid, fd, type, path, flags);
+ ret = index_pipe(istate, oid, fd, type, path, flags);
else if (st->st_size <= big_file_threshold || type != OBJ_BLOB ||
- (path && would_convert_to_git(&the_index, path)))
- ret = index_core(oid, fd, xsize_t(st->st_size), type, path,
- flags);
+ (path && would_convert_to_git(istate, path)))
+ ret = index_core(istate, oid, fd, xsize_t(st->st_size),
+ type, path, flags);
else
ret = index_stream(oid, fd, xsize_t(st->st_size), type, path,
flags);
return ret;
}
-int index_path(struct object_id *oid, const char *path, struct stat *st, unsigned flags)
+int index_path(struct index_state *istate, struct object_id *oid,
+ const char *path, struct stat *st, unsigned flags)
{
int fd;
struct strbuf sb = STRBUF_INIT;
fd = open(path, O_RDONLY);
if (fd < 0)
return error_errno("open(\"%s\")", path);
- if (index_fd(oid, fd, st, OBJ_BLOB, path, flags) < 0)
+ if (index_fd(istate, oid, fd, st, OBJ_BLOB, path, flags) < 0)
return error(_("%s: failed to insert into database"),
path);
break;
}
the_hash_algo->final_fn(real_sha1, &c);
- if (hashcmp(expected_sha1, real_sha1)) {
+ if (!hasheq(expected_sha1, real_sha1)) {
error(_("sha1 mismatch for %s (expected %s)"), path,
sha1_to_hex(expected_sha1));
return -1;
#include "packfile.h"
#include "object-store.h"
#include "repository.h"
+#include "midx.h"
+#include "commit-reach.h"
static int get_oid_oneline(const char *, struct object_id *, struct commit_list *);
oidcpy(&ds->candidate, current);
ds->candidate_exists = 1;
return;
- } else if (!oidcmp(&ds->candidate, current)) {
+ } else if (oideq(&ds->candidate, current)) {
/* the same as what we already have seen */
return;
}
return 1;
}
+static void unique_in_midx(struct multi_pack_index *m,
+ struct disambiguate_state *ds)
+{
+ uint32_t num, i, first = 0;
+ const struct object_id *current = NULL;
+ num = m->num_objects;
+
+ if (!num)
+ return;
+
+ bsearch_midx(&ds->bin_pfx, m, &first);
+
+ /*
+ * At this point, "first" is the location of the lowest object
+ * with an object name that could match "bin_pfx". See if we have
+ * 0, 1 or more objects that actually match(es).
+ */
+ for (i = first; i < num && !ds->ambiguous; i++) {
+ struct object_id oid;
+ current = nth_midxed_object_oid(&oid, m, i);
+ if (!match_sha(ds->len, ds->bin_pfx.hash, current->hash))
+ break;
+ update_candidates(ds, current);
+ }
+}
+
static void unique_in_pack(struct packed_git *p,
struct disambiguate_state *ds)
{
static void find_short_packed_object(struct disambiguate_state *ds)
{
+ struct multi_pack_index *m;
struct packed_git *p;
+ for (m = get_multi_pack_index(the_repository); m && !ds->ambiguous;
+ m = m->next)
+ unique_in_midx(m, ds);
for (p = get_packed_git(the_repository); p && !ds->ambiguous;
p = p->next)
unique_in_pack(p, ds);
return 0;
}
+static void find_abbrev_len_for_midx(struct multi_pack_index *m,
+ struct min_abbrev_data *mad)
+{
+ int match = 0;
+ uint32_t num, first = 0;
+ struct object_id oid;
+ const struct object_id *mad_oid;
+
+ if (!m->num_objects)
+ return;
+
+ num = m->num_objects;
+ mad_oid = mad->oid;
+ match = bsearch_midx(mad_oid, m, &first);
+
+ /*
+ * first is now the position in the packfile where we would insert
+ * mad->hash if it does not exist (or the position of mad->hash if
+ * it does exist). Hence, we consider a maximum of two objects
+ * nearby for the abbreviation length.
+ */
+ mad->init_len = 0;
+ if (!match) {
+ if (nth_midxed_object_oid(&oid, m, first))
+ extend_abbrev_len(&oid, mad);
+ } else if (first < num - 1) {
+ if (nth_midxed_object_oid(&oid, m, first + 1))
+ extend_abbrev_len(&oid, mad);
+ }
+ if (first > 0) {
+ if (nth_midxed_object_oid(&oid, m, first - 1))
+ extend_abbrev_len(&oid, mad);
+ }
+ mad->init_len = mad->cur_len;
+}
+
static void find_abbrev_len_for_pack(struct packed_git *p,
struct min_abbrev_data *mad)
{
static void find_abbrev_len_packed(struct min_abbrev_data *mad)
{
+ struct multi_pack_index *m;
struct packed_git *p;
+ for (m = get_multi_pack_index(the_repository); m; m = m->next)
+ find_abbrev_len_for_midx(m, mad);
for (p = get_packed_git(the_repository); p; p = p->next)
find_abbrev_len_for_pack(p, mad);
}
#include "list-objects.h"
#include "commit-slab.h"
#include "repository.h"
+#include "commit-reach.h"
void set_alternate_shallow_file(struct repository *r, const char *path, int override)
{
is_repository_shallow(the_repository); /* make sure shallows are read */
- init_revisions(&revs, NULL);
+ repo_init_revisions(the_repository, &revs, NULL);
save_commit_buffer = 0;
setup_revisions(ac, av, &revs, NULL);
die("position for delete %d exceeds base index size %d",
(int)pos, istate->cache_nr);
istate->cache[pos]->ce_flags |= CE_REMOVE;
- istate->split_index->nr_deletions = 1;
+ istate->split_index->nr_deletions++;
}
static void replace_entry(size_t pos, void *data)
si->saved_cache_nr = 0;
}
+/*
+ * Compare most of the fields in two cache entries, i.e. all except the
+ * hashmap_entry and the name.
+ */
+static int compare_ce_content(struct cache_entry *a, struct cache_entry *b)
+{
+ const unsigned int ondisk_flags = CE_STAGEMASK | CE_VALID |
+ CE_EXTENDED_FLAGS;
+ unsigned int ce_flags = a->ce_flags;
+ unsigned int base_flags = b->ce_flags;
+ int ret;
+
+ /* only on-disk flags matter */
+ a->ce_flags &= ondisk_flags;
+ b->ce_flags &= ondisk_flags;
+ ret = memcmp(&a->ce_stat_data, &b->ce_stat_data,
+ offsetof(struct cache_entry, name) -
+ offsetof(struct cache_entry, ce_stat_data));
+ a->ce_flags = ce_flags;
+ b->ce_flags = base_flags;
+
+ return ret;
+}
+
void prepare_to_write_split_index(struct index_state *istate)
{
struct split_index *si = init_split_index(istate);
*/
for (i = 0; i < istate->cache_nr; i++) {
struct cache_entry *base;
- /* namelen is checked separately */
- const unsigned int ondisk_flags =
- CE_STAGEMASK | CE_VALID | CE_EXTENDED_FLAGS;
- unsigned int ce_flags, base_flags, ret;
ce = istate->cache[i];
- if (!ce->index)
+ if (!ce->index) {
+ /*
+ * During simple update index operations this
+ * is a cache entry that is not present in
+ * the shared index. It will be added to the
+ * split index.
+ *
+ * However, it might also represent a file
+ * that already has a cache entry in the
+ * shared index, but a new index has just
+ * been constructed by unpack_trees(), and
+ * this entry now refers to different content
+ * than what was recorded in the original
+ * index, e.g. during 'read-tree -m HEAD^' or
+ * 'checkout HEAD^'. In this case the
+ * original entry in the shared index will be
+ * marked as deleted, and this entry will be
+ * added to the split index.
+ */
continue;
+ }
if (ce->index > si->base->cache_nr) {
- ce->index = 0;
- continue;
+ BUG("ce refers to a shared ce at %d, which is beyond the shared index size %d",
+ ce->index, si->base->cache_nr);
}
ce->ce_flags |= CE_MATCHED; /* or "shared" */
base = si->base->cache[ce->index - 1];
- if (ce == base)
+ if (ce == base) {
+ /* The entry is present in the shared index. */
+ if (ce->ce_flags & CE_UPDATE_IN_BASE) {
+ /*
+ * Already marked for inclusion in
+ * the split index, either because
+ * the corresponding file was
+ * modified and the cached stat data
+ * was refreshed, or because there
+ * is already a replacement entry in
+ * the split index.
+ * Nothing more to do here.
+ */
+ } else if (!ce_uptodate(ce) &&
+ is_racy_timestamp(istate, ce)) {
+ /*
+ * A racily clean cache entry stored
+ * only in the shared index: it must
+ * be added to the split index, so
+ * the subsequent do_write_index()
+ * can smudge its stat data.
+ */
+ ce->ce_flags |= CE_UPDATE_IN_BASE;
+ } else {
+ /*
+ * The entry is only present in the
+ * shared index and it was not
+ * refreshed.
+ * Just leave it there.
+ */
+ }
continue;
+ }
if (ce->ce_namelen != base->ce_namelen ||
strcmp(ce->name, base->name)) {
ce->index = 0;
continue;
}
- ce_flags = ce->ce_flags;
- base_flags = base->ce_flags;
- /* only on-disk flags matter */
- ce->ce_flags &= ondisk_flags;
- base->ce_flags &= ondisk_flags;
- ret = memcmp(&ce->ce_stat_data, &base->ce_stat_data,
- offsetof(struct cache_entry, name) -
- offsetof(struct cache_entry, ce_stat_data));
- ce->ce_flags = ce_flags;
- base->ce_flags = base_flags;
- if (ret)
+ /*
+ * This is the copy of a cache entry that is present
+ * in the shared index, created by unpack_trees()
+ * while it constructed a new index.
+ */
+ if (ce->ce_flags & CE_UPDATE_IN_BASE) {
+ /*
+ * Already marked for inclusion in the split
+ * index, either because the corresponding
+ * file was modified and the cached stat data
+ * was refreshed, or because the original
+ * entry already had a replacement entry in
+ * the split index.
+ * Nothing to do.
+ */
+ } else if (!ce_uptodate(ce) &&
+ is_racy_timestamp(istate, ce)) {
+ /*
+ * A copy of a racily clean cache entry from
+ * the shared index. It must be added to
+ * the split index, so the subsequent
+ * do_write_index() can smudge its stat data.
+ */
ce->ce_flags |= CE_UPDATE_IN_BASE;
+ } else {
+ /*
+ * Thoroughly compare the cached data to see
+ * whether it should be marked for inclusion
+ * in the split index.
+ *
+ * This comparison might be unnecessary, as
+ * code paths modifying the cached data do
+ * set CE_UPDATE_IN_BASE as well.
+ */
+ if (compare_ce_content(ce, base))
+ ce->ce_flags |= CE_UPDATE_IN_BASE;
+ }
discard_cache_entry(base);
si->base->cache[ce->index - 1] = ce;
}
* Initialize the structure. The second parameter can be zero or a bigger
* number to allocate memory, in case you want to prevent further reallocs.
*/
-extern void strbuf_init(struct strbuf *, size_t);
+void strbuf_init(struct strbuf *sb, size_t alloc);
/**
* Release a string buffer and the memory it used. After this call, the
* To clear a strbuf in preparation for further use without the overhead
* of free()ing and malloc()ing again, use strbuf_reset() instead.
*/
-extern void strbuf_release(struct strbuf *);
+void strbuf_release(struct strbuf *sb);
/**
* Detach the string from the strbuf and returns it; you now own the
* The strbuf that previously held the string is reset to `STRBUF_INIT` so
* it can be reused after calling this function.
*/
-extern char *strbuf_detach(struct strbuf *, size_t *);
+char *strbuf_detach(struct strbuf *sb, size_t *sz);
/**
* Attach a string to a buffer. You should specify the string to attach,
* malloc()ed, and after attaching, the pointer cannot be relied upon
* anymore, and neither be free()d directly.
*/
-extern void strbuf_attach(struct strbuf *, void *, size_t, size_t);
+void strbuf_attach(struct strbuf *sb, void *str, size_t len, size_t mem);
/**
* Swap the contents of two string buffers.
* This is never a needed operation, but can be critical for performance in
* some cases.
*/
-extern void strbuf_grow(struct strbuf *, size_t);
+void strbuf_grow(struct strbuf *sb, size_t amount);
/**
* Set the length of the buffer to a given value. This function does *not*
* Strip whitespace from the beginning (`ltrim`), end (`rtrim`), or both side
* (`trim`) of a string.
*/
-extern void strbuf_trim(struct strbuf *);
-extern void strbuf_rtrim(struct strbuf *);
-extern void strbuf_ltrim(struct strbuf *);
+void strbuf_trim(struct strbuf *sb);
+void strbuf_rtrim(struct strbuf *sb);
+void strbuf_ltrim(struct strbuf *sb);
/* Strip trailing directory separators */
-extern void strbuf_trim_trailing_dir_sep(struct strbuf *);
+void strbuf_trim_trailing_dir_sep(struct strbuf *sb);
/**
* Replace the contents of the strbuf with a reencoded form. Returns -1
* on error, 0 on success.
*/
-extern int strbuf_reencode(struct strbuf *sb, const char *from, const char *to);
+int strbuf_reencode(struct strbuf *sb, const char *from, const char *to);
/**
* Lowercase each character in the buffer using `tolower`.
*/
-extern void strbuf_tolower(struct strbuf *sb);
+void strbuf_tolower(struct strbuf *sb);
/**
* Compare two buffers. Returns an integer less than, equal to, or greater
* than zero if the first buffer is found, respectively, to be less than,
* to match, or be greater than the second buffer.
*/
-extern int strbuf_cmp(const struct strbuf *, const struct strbuf *);
+int strbuf_cmp(const struct strbuf *first, const struct strbuf *second);
/**
/**
* Add a character the specified number of times to the buffer.
*/
-extern void strbuf_addchars(struct strbuf *sb, int c, size_t n);
+void strbuf_addchars(struct strbuf *sb, int c, size_t n);
/**
* Insert data to the given position of the buffer. The remaining contents
* will be shifted, not overwritten.
*/
-extern void strbuf_insert(struct strbuf *, size_t pos, const void *, size_t);
+void strbuf_insert(struct strbuf *sb, size_t pos, const void *, size_t);
/**
* Remove given amount of data from a given position of the buffer.
*/
-extern void strbuf_remove(struct strbuf *, size_t pos, size_t len);
+void strbuf_remove(struct strbuf *sb, size_t pos, size_t len);
/**
* Remove the bytes between `pos..pos+len` and replace it with the given
* data.
*/
-extern void strbuf_splice(struct strbuf *, size_t pos, size_t len,
- const void *, size_t);
+void strbuf_splice(struct strbuf *sb, size_t pos, size_t len,
+ const void *data, size_t data_len);
/**
* Add a NUL-terminated string to the buffer. Each line will be prepended
* by a comment character and a blank.
*/
-extern void strbuf_add_commented_lines(struct strbuf *out, const char *buf, size_t size);
+void strbuf_add_commented_lines(struct strbuf *out,
+ const char *buf, size_t size);
/**
* Add data of given length to the buffer.
*/
-extern void strbuf_add(struct strbuf *, const void *, size_t);
+void strbuf_add(struct strbuf *sb, const void *data, size_t len);
/**
* Add a NUL-terminated string to the buffer.
/**
* Copy the contents of another buffer at the end of the current one.
*/
-extern void strbuf_addbuf(struct strbuf *sb, const struct strbuf *sb2);
+void strbuf_addbuf(struct strbuf *sb, const struct strbuf *sb2);
/**
* This function can be used to expand a format string containing
* parameters to the callback, `strbuf_expand()` passes a context pointer,
* which can be used by the programmer of the callback as she sees fit.
*/
-typedef size_t (*expand_fn_t) (struct strbuf *sb, const char *placeholder, void *context);
-extern void strbuf_expand(struct strbuf *sb, const char *format, expand_fn_t fn, void *context);
+typedef size_t (*expand_fn_t) (struct strbuf *sb,
+ const char *placeholder,
+ void *context);
+void strbuf_expand(struct strbuf *sb,
+ const char *format,
+ expand_fn_t fn,
+ void *context);
/**
* Used as callback for `strbuf_expand()`, expects an array of
const char *placeholder;
const char *value;
};
-extern size_t strbuf_expand_dict_cb(struct strbuf *sb, const char *placeholder, void *context);
+size_t strbuf_expand_dict_cb(struct strbuf *sb,
+ const char *placeholder,
+ void *context);
/**
* Append the contents of one strbuf to another, quoting any
* destination. This is useful for literal data to be fed to either
* strbuf_expand or to the *printf family of functions.
*/
-extern void strbuf_addbuf_percentquote(struct strbuf *dst, const struct strbuf *src);
+void strbuf_addbuf_percentquote(struct strbuf *dst, const struct strbuf *src);
/**
* Append the given byte size as a human-readable string (i.e. 12.23 KiB,
* 3.50 MiB).
*/
-extern void strbuf_humanise_bytes(struct strbuf *buf, off_t bytes);
+void strbuf_humanise_bytes(struct strbuf *buf, off_t bytes);
/**
* Add a formatted string to the buffer.
*/
__attribute__((format (printf,2,3)))
-extern void strbuf_addf(struct strbuf *sb, const char *fmt, ...);
+void strbuf_addf(struct strbuf *sb, const char *fmt, ...);
/**
* Add a formatted string prepended by a comment character and a
* blank to the buffer.
*/
__attribute__((format (printf, 2, 3)))
-extern void strbuf_commented_addf(struct strbuf *sb, const char *fmt, ...);
+void strbuf_commented_addf(struct strbuf *sb, const char *fmt, ...);
__attribute__((format (printf,2,0)))
-extern void strbuf_vaddf(struct strbuf *sb, const char *fmt, va_list ap);
+void strbuf_vaddf(struct strbuf *sb, const char *fmt, va_list ap);
/**
* Add the time specified by `tm`, as formatted by `strftime`.
* `suppress_tz_name`, when set, expands %Z internally to the empty
* string rather than passing it to `strftime`.
*/
-extern void strbuf_addftime(struct strbuf *sb, const char *fmt,
- const struct tm *tm, int tz_offset,
- int suppress_tz_name);
+void strbuf_addftime(struct strbuf *sb, const char *fmt,
+ const struct tm *tm, int tz_offset,
+ int suppress_tz_name);
/**
* Read a given size of data from a FILE* pointer to the buffer.
* `strbuf_read()`, `strbuf_read_file()` and `strbuf_getline_*()`
* family of functions have the same behaviour as well.
*/
-extern size_t strbuf_fread(struct strbuf *, size_t, FILE *);
+size_t strbuf_fread(struct strbuf *sb, size_t size, FILE *file);
/**
* Read the contents of a given file descriptor. The third argument can be
* used to give a hint about the file size, to avoid reallocs. If read fails,
* any partial read is undone.
*/
-extern ssize_t strbuf_read(struct strbuf *, int fd, size_t hint);
+ssize_t strbuf_read(struct strbuf *sb, int fd, size_t hint);
/**
* Read the contents of a given file descriptor partially by using only one
* file size, to avoid reallocs. Returns the number of new bytes appended to
* the sb.
*/
-extern ssize_t strbuf_read_once(struct strbuf *, int fd, size_t hint);
+ssize_t strbuf_read_once(struct strbuf *sb, int fd, size_t hint);
/**
* Read the contents of a file, specified by its path. The third argument
* Return the number of bytes read or a negative value if some error
* occurred while opening or reading the file.
*/
-extern ssize_t strbuf_read_file(struct strbuf *sb, const char *path, size_t hint);
+ssize_t strbuf_read_file(struct strbuf *sb, const char *path, size_t hint);
/**
* Read the target of a symbolic link, specified by its path. The third
* argument can be used to give a hint about the size, to avoid reallocs.
*/
-extern int strbuf_readlink(struct strbuf *sb, const char *path, size_t hint);
+int strbuf_readlink(struct strbuf *sb, const char *path, size_t hint);
/**
* Write the whole content of the strbuf to the stream not stopping at
* NUL bytes.
*/
-extern ssize_t strbuf_write(struct strbuf *sb, FILE *stream);
+ssize_t strbuf_write(struct strbuf *sb, FILE *stream);
/**
* Read a line from a FILE *, overwriting the existing contents of
typedef int (*strbuf_getline_fn)(struct strbuf *, FILE *);
/* Uses LF as the line terminator */
-extern int strbuf_getline_lf(struct strbuf *sb, FILE *fp);
+int strbuf_getline_lf(struct strbuf *sb, FILE *fp);
/* Uses NUL as the line terminator */
-extern int strbuf_getline_nul(struct strbuf *sb, FILE *fp);
+int strbuf_getline_nul(struct strbuf *sb, FILE *fp);
/*
* Similar to strbuf_getline_lf(), but additionally treats a CR that
* that can come from platforms whose native text format is CRLF
* terminated.
*/
-extern int strbuf_getline(struct strbuf *, FILE *);
+int strbuf_getline(struct strbuf *sb, FILE *file);
/**
* Like `strbuf_getline`, but keeps the trailing terminator (if
* any) in the buffer.
*/
-extern int strbuf_getwholeline(struct strbuf *, FILE *, int);
+int strbuf_getwholeline(struct strbuf *sb, FILE *file, int term);
/**
* Like `strbuf_getwholeline`, but operates on a file descriptor.
* use it unless you need the correct position in the file
* descriptor.
*/
-extern int strbuf_getwholeline_fd(struct strbuf *, int, int);
+int strbuf_getwholeline_fd(struct strbuf *sb, int fd, int term);
/**
* Set the buffer to the path of the current working directory.
*/
-extern int strbuf_getcwd(struct strbuf *sb);
+int strbuf_getcwd(struct strbuf *sb);
/**
* Add a path to a buffer, converting a relative path to an
* absolute one in the process. Symbolic links are not
* resolved.
*/
-extern void strbuf_add_absolute_path(struct strbuf *sb, const char *path);
+void strbuf_add_absolute_path(struct strbuf *sb, const char *path);
/**
* Canonize `path` (make it absolute, resolve symlinks, remove extra
* Callers that don't mind links should use the more lightweight
* strbuf_add_absolute_path() instead.
*/
-extern void strbuf_add_real_path(struct strbuf *sb, const char *path);
+void strbuf_add_real_path(struct strbuf *sb, const char *path);
/**
* normalize_path_copy() for details. If an error occurs, the contents of "sb"
* are left untouched, and -1 is returned.
*/
-extern int strbuf_normalize_path(struct strbuf *sb);
+int strbuf_normalize_path(struct strbuf *sb);
/**
* Strip whitespace from a buffer. The second parameter controls if
* comments are considered contents to be removed or not.
*/
-extern void strbuf_stripspace(struct strbuf *buf, int skip_comments);
+void strbuf_stripspace(struct strbuf *buf, int skip_comments);
static inline int strbuf_strip_suffix(struct strbuf *sb, const char *suffix)
{
* For lighter-weight alternatives, see string_list_split() and
* string_list_split_in_place().
*/
-extern struct strbuf **strbuf_split_buf(const char *, size_t,
- int terminator, int max);
+struct strbuf **strbuf_split_buf(const char *str, size_t len,
+ int terminator, int max);
static inline struct strbuf **strbuf_split_str(const char *str,
int terminator, int max)
}
static inline struct strbuf **strbuf_split_max(const struct strbuf *sb,
- int terminator, int max)
+ int terminator, int max)
{
return strbuf_split_buf(sb->buf, sb->len, terminator, max);
}
* 'element1, element2, ..., elementN'
* to str. If only one element, just write "element1" to str.
*/
-extern void strbuf_add_separated_string_list(struct strbuf *str,
- const char *sep,
- struct string_list *slist);
+void strbuf_add_separated_string_list(struct strbuf *str,
+ const char *sep,
+ struct string_list *slist);
/**
* Free a NULL-terminated list of strbufs (for example, the return
* values of the strbuf_split*() functions).
*/
-extern void strbuf_list_free(struct strbuf **);
+void strbuf_list_free(struct strbuf **list);
/**
* Add the abbreviation, as generated by find_unique_abbrev, of `sha1` to
* the strbuf `sb`.
*/
-extern void strbuf_add_unique_abbrev(struct strbuf *sb,
- const struct object_id *oid,
- int abbrev_len);
+void strbuf_add_unique_abbrev(struct strbuf *sb,
+ const struct object_id *oid,
+ int abbrev_len);
/**
* Launch the user preferred editor to edit a file and fill the buffer
* run in. If the buffer is NULL the editor is launched as usual but the
* file's contents are not read into the buffer upon completion.
*/
-extern int launch_editor(const char *path, struct strbuf *buffer, const char *const *env);
+int launch_editor(const char *path,
+ struct strbuf *buffer,
+ const char *const *env);
-extern void strbuf_add_lines(struct strbuf *sb, const char *prefix, const char *buf, size_t size);
+void strbuf_add_lines(struct strbuf *sb,
+ const char *prefix,
+ const char *buf,
+ size_t size);
/**
* Append s to sb, with the characters '<', '>', '&' and '"' converted
* into XML entities.
*/
-extern void strbuf_addstr_xml_quoted(struct strbuf *sb, const char *s);
+void strbuf_addstr_xml_quoted(struct strbuf *sb,
+ const char *s);
/**
* "Complete" the contents of `sb` by ensuring that either it ends with the
* If "allowed" is non-zero, restrict the set of allowed expansions. See
* interpret_branch_name() for details.
*/
-extern void strbuf_branchname(struct strbuf *sb, const char *name,
- unsigned allowed);
+void strbuf_branchname(struct strbuf *sb, const char *name,
+ unsigned allowed);
/*
* Like strbuf_branchname() above, but confirm that the result is
*
* The return value is "0" if the result is valid, and "-1" otherwise.
*/
-extern int strbuf_check_branch_ref(struct strbuf *sb, const char *name);
+int strbuf_check_branch_ref(struct strbuf *sb, const char *name);
-extern void strbuf_addstr_urlencode(struct strbuf *, const char *,
- int reserved);
+void strbuf_addstr_urlencode(struct strbuf *sb, const char *name,
+ int reserved);
__attribute__((format (printf,1,2)))
-extern int printf_ln(const char *fmt, ...);
+int printf_ln(const char *fmt, ...);
__attribute__((format (printf,2,3)))
-extern int fprintf_ln(FILE *fp, const char *fmt, ...);
+int fprintf_ln(FILE *fp, const char *fmt, ...);
char *xstrdup_tolower(const char *);
char *xstrdup_toupper(const char *);
list->nr = list->alloc = 0;
}
-
-void print_string_list(const struct string_list *p, const char *text)
-{
- int i;
- if ( text )
- printf("%s\n", text);
- for (i = 0; i < p->nr; i++)
- printf("%s:%p\n", p->items[i].string, p->items[i].util);
-}
-
struct string_list_item *string_list_append_nodup(struct string_list *list,
char *string)
{
void filter_string_list(struct string_list *list, int free_util,
string_list_each_func_t want, void *cb_data);
-/**
- * Dump a string_list to stdout, useful mainly for debugging
- * purposes. It can take an optional header argument and it writes out
- * the string-pointer pairs of the string_list, each one in its own
- * line.
- */
-void print_string_list(const struct string_list *p, const char *text);
-
/**
* Free a string_list. The `string` pointer of the items will be freed
* in case the `strdup_strings` member of the string_list is set. The
const struct submodule_entry *b = entry_or_key;
return strcmp(a->config->path, b->config->path) ||
- oidcmp(&a->config->gitmodules_oid, &b->config->gitmodules_oid);
+ !oideq(&a->config->gitmodules_oid, &b->config->gitmodules_oid);
}
static int config_name_cmp(const void *unused_cmp_data,
const struct submodule_entry *b = entry_or_key;
return strcmp(a->config->name, b->config->name) ||
- oidcmp(&a->config->gitmodules_oid, &b->config->gitmodules_oid);
+ !oideq(&a->config->gitmodules_oid, &b->config->gitmodules_oid);
}
static struct submodule_cache *submodule_cache_alloc(void)
#include "worktree.h"
#include "parse-options.h"
#include "object-store.h"
+#include "commit-reach.h"
static int config_update_recurse_submodules = RECURSE_SUBMODULES_OFF;
static struct string_list changed_submodule_names = STRING_LIST_INIT_DUP;
if ((pos >= 0) && (pos < istate->cache_nr)) {
struct stat st;
if (lstat(GITMODULES_FILE, &st) == 0 &&
- ie_match_stat(istate, istate->cache[pos], &st,
- CE_MATCH_IGNORE_FSMONITOR) & DATA_CHANGED)
+ ie_match_stat(istate, istate->cache[pos], &st, 0) & DATA_CHANGED)
return 0;
}
{
struct commit_list *list;
- init_revisions(rev, NULL);
+ repo_init_revisions(the_repository, rev, NULL);
setup_revisions(0, NULL, rev, NULL);
rev->left_right = 1;
rev->first_parent_only = 1;
fast_backward = 1;
}
- if (!oidcmp(one, two)) {
+ if (oideq(one, two)) {
strbuf_release(&sb);
return;
}
* have a corresponding 'struct oid_array' (in the 'util' field) which lists
* what the submodule pointers were updated to during the change.
*/
-static void collect_changed_submodules(struct string_list *changed,
+static void collect_changed_submodules(struct index_state *istate,
+ struct string_list *changed,
struct argv_array *argv)
{
struct rev_info rev;
const struct commit *commit;
- init_revisions(&rev, NULL);
+ repo_init_revisions(the_repository, &rev, NULL);
setup_revisions(argv->argc, argv->argv, &rev, NULL);
if (prepare_revision_walk(&rev))
die("revision walk setup failed");
data.changed = changed;
data.commit_oid = &commit->object.oid;
- init_revisions(&diff_rev, NULL);
+ repo_init_revisions(the_repository, &diff_rev, NULL);
diff_rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
diff_rev.diffopt.format_callback = collect_changed_submodules_cb;
diff_rev.diffopt.format_callback_data = &data;
return 0;
}
-int find_unpushed_submodules(struct oid_array *commits,
- const char *remotes_name, struct string_list *needs_pushing)
+int find_unpushed_submodules(struct index_state *istate,
+ struct oid_array *commits,
+ const char *remotes_name,
+ struct string_list *needs_pushing)
{
struct string_list submodules = STRING_LIST_INIT_DUP;
struct string_list_item *name;
argv_array_push(&argv, "--not");
argv_array_pushf(&argv, "--remotes=%s", remotes_name);
- collect_changed_submodules(&submodules, &argv);
+ collect_changed_submodules(istate, &submodules, &argv);
for_each_string_list_item(name, &submodules) {
struct oid_array *commits = name->util;
die("process for submodule '%s' failed", path);
}
-int push_unpushed_submodules(struct oid_array *commits,
+int push_unpushed_submodules(struct index_state *istate,
+ struct oid_array *commits,
const struct remote *remote,
const struct refspec *rs,
const struct string_list *push_options,
int i, ret = 1;
struct string_list needs_pushing = STRING_LIST_INIT_DUP;
- if (!find_unpushed_submodules(commits, remote->name, &needs_pushing))
+ if (!find_unpushed_submodules(istate, commits,
+ remote->name, &needs_pushing))
return 1;
/*
oid_array_append(&ref_tips_after_fetch, oid);
}
-static void calculate_changed_submodule_paths(void)
+static void calculate_changed_submodule_paths(struct index_state *istate)
{
struct argv_array argv = ARGV_ARRAY_INIT;
struct string_list changed_submodules = STRING_LIST_INIT_DUP;
* Collect all submodules (whether checked out or not) for which new
* commits have been recorded upstream in "changed_submodule_names".
*/
- collect_changed_submodules(&changed_submodules, &argv);
+ collect_changed_submodules(istate, &changed_submodules, &argv);
for_each_string_list_item(name, &changed_submodules) {
struct oid_array *commits = name->util;
initialized_fetch_ref_tips = 0;
}
-int submodule_touches_in_range(struct object_id *excl_oid,
+int submodule_touches_in_range(struct index_state *istate,
+ struct object_id *excl_oid,
struct object_id *incl_oid)
{
struct string_list subs = STRING_LIST_INIT_DUP;
argv_array_push(&args, oid_to_hex(excl_oid));
}
- collect_changed_submodules(&subs, &args);
+ collect_changed_submodules(istate, &subs, &args);
ret = subs.nr;
argv_array_clear(&args);
argv_array_push(&spf.args, "--recurse-submodules-default");
/* default value, "--submodule-prefix" and its value are added later */
- calculate_changed_submodule_paths();
+ calculate_changed_submodule_paths(r->index);
run_processes_parallel(max_parallel_jobs,
get_next_submodule,
fetch_start_failure,
* We're only interested in the name after the tab.
*/
super_sub = strchr(sb.buf, '\t') + 1;
- super_sub_len = sb.buf + sb.len - super_sub - 1;
+ super_sub_len = strlen(super_sub);
if (super_sub_len > cwd_len ||
strcmp(&cwd[cwd_len - super_sub_len], super_sub))
* Checks if there are submodule changes in a..b. If a is the null OID,
* checks b and all its ancestors instead.
*/
-int submodule_touches_in_range(struct object_id *a,
+int submodule_touches_in_range(struct index_state *istate,
+ struct object_id *a,
struct object_id *b);
-int find_unpushed_submodules(struct oid_array *commits,
+int find_unpushed_submodules(struct index_state *istate,
+ struct oid_array *commits,
const char *remotes_name,
struct string_list *needs_pushing);
struct refspec;
-int push_unpushed_submodules(struct oid_array *commits,
+int push_unpushed_submodules(struct index_state *istate,
+ struct oid_array *commits,
const struct remote *remote,
const struct refspec *rs,
const struct string_list *push_options,
over 2GB. This variable forces the code path on any object larger than
<n> bytes.
-GIT_TEST_OE_DELTA_SIZE=<n> exercises the uncomon pack-objects code
+GIT_TEST_OE_DELTA_SIZE=<n> exercises the uncommon pack-objects code
path where deltas larger than this limit require extra memory
allocation for bookkeeping.
+GIT_TEST_VALIDATE_INDEX_CACHE_ENTRIES=<boolean> checks that cache-tree
+records are valid when the index is written out or after a merge. This
+is mostly to catch missing invalidation. Default is true.
+
+GIT_TEST_COMMIT_GRAPH=<boolean>, when true, forces the commit-graph to
+be written after every 'git commit' command, and overrides the
+'core.commitGraph' setting to true.
+
+GIT_TEST_FSMONITOR=$PWD/t7519/fsmonitor-all exercises the fsmonitor
+code path for utilizing a file system monitor to speed up detecting
+new or changed files.
+
+GIT_TEST_INDEX_VERSION=<n> exercises the index read/write code path
+for the index version specified. Can be set to any valid version
+(currently 2, 3, or 4).
+
+GIT_TEST_PRELOAD_INDEX=<boolean> exercises the preload-index code path
+by overriding the minimum number of cache entries required per thread.
+
+GIT_TEST_INDEX_THREADS=<n> enables exercising the multi-threaded loading
+of the index for the whole test suite by bypassing the default number of
+cache entries and thread minimums. Setting this to 1 will make the
+index loading single threaded.
+
Naming Tests
------------
consistently when command line arguments --verbose (or -v),
--debug (or -d), and --immediate (or -i) is given.
-Do's, don'ts & things to keep in mind
--------------------------------------
+Do's & don'ts
+-------------
Here are a few examples of things you probably should and shouldn't do
when writing tests.
-Do:
+Here are the "do's:"
- Put all code inside test_expect_success and other assertions.
Windows, where the shell (MSYS bash) mangles absolute path names.
For details, see the commit message of 4114156ae9.
-Don't:
+ - Remember that inside the <script> part, the standard output and
+ standard error streams are discarded, and the test harness only
+ reports "ok" or "not ok" to the end user running the tests. Under
+ --verbose, they are shown to help debug the tests.
- - exit() within a <script> part.
+And here are the "don'ts:"
+
+ - Don't exit() within a <script> part.
The harness will catch this as a programming error of the test.
Use test_done instead if you need to stop the tests early (see
"Skipping tests" below).
- - use '! git cmd' when you want to make sure the git command exits
- with failure in a controlled way by calling "die()". Instead,
+ - Don't use '! git cmd' when you want to make sure the git command
+ exits with failure in a controlled way by calling "die()". Instead,
use 'test_must_fail git cmd'. This will signal a failure if git
dies in an unexpected way (e.g. segfault).
platform commands; just use '! cmd'. We are not in the business
of verifying that the world given to us sanely works.
- - use perl without spelling it as "$PERL_PATH". This is to help our
- friends on Windows where the platform Perl often adds CR before
+ - Don't feed the output of a git command to a pipe, as in:
+
+ git -C repo ls-files |
+ xargs -n 1 basename |
+ grep foo
+
+ which will discard git's exit code and may mask a crash. In the
+ above example, all exit codes are ignored except grep's.
+
+ Instead, write the output of that command to a temporary
+ file with ">" or assign it to a variable with "x=$(git ...)" rather
+ than pipe it.
+
+ - Don't use command substitution in a way that discards git's exit
+ code. When assigning to a variable, the exit code is not discarded,
+ e.g.:
+
+ x=$(git cat-file -p $sha) &&
+ ...
+
+ is OK because a crash in "git cat-file" will cause the "&&" chain
+ to fail, but:
+
+ test "refs/heads/foo" = "$(git symbolic-ref HEAD)"
+
+ is not OK and a crash in git could go undetected.
+
+ - Don't use perl without spelling it as "$PERL_PATH". This is to help
+ our friends on Windows where the platform Perl often adds CR before
the end of line, and they bundle Git with a version of Perl that
does not do so, whose path is specified with $PERL_PATH. Note that we
provide a "perl" function which uses $PERL_PATH under the hood, so
(but you do, for example, on a shebang line or in a sub script
created via "write_script").
- - use sh without spelling it as "$SHELL_PATH", when the script can
- be misinterpreted by broken platform shell (e.g. Solaris).
+ - Don't use sh without spelling it as "$SHELL_PATH", when the script
+ can be misinterpreted by broken platform shell (e.g. Solaris).
- - chdir around in tests. It is not sufficient to chdir to
+ - Don't chdir around in tests. It is not sufficient to chdir to
somewhere and then chdir back to the original location later in
the test, as any intermediate step can fail and abort the test,
causing the next test to start in an unexpected directory. Do so
inside a subshell if necessary.
- - save and verify the standard error of compound commands, i.e. group
- commands, subshells, and shell functions (except test helper
+ - Don't save and verify the standard error of compound commands, i.e.
+ group commands, subshells, and shell functions (except test helper
functions like 'test_must_fail') like this:
( cd dir && git cmd ) 2>error &&
( cd dir && git cmd 2>../error ) &&
test_cmp expect error
- - Break the TAP output
+ - Don't break the TAP output
The raw output from your test may be interpreted by a TAP harness. TAP
harnesses will ignore everything they don't know about, but don't step
but the best indication is to just run the tests with prove(1),
it'll complain if anything is amiss.
-Keep in mind:
-
- - Inside the <script> part, the standard output and standard error
- streams are discarded, and the test harness only reports "ok" or
- "not ok" to the end user running the tests. Under --verbose, they
- are shown to help debugging the tests.
-
Skipping tests
--------------
the symbolic link in the file system and a part that does; then only
the latter part need be protected by a SYMLINKS prerequisite (see below).
+ - test_oid_init
+
+ This function loads facts and useful object IDs related to the hash
+ algorithm(s) in use from the files in t/oid-info.
+
+ - test_oid_cache
+
+ This function reads per-hash algorithm information from standard
+ input (usually a heredoc) in the format described in
+ t/oid-info/README. This is useful for test-specific values, such as
+ object IDs, which must vary based on the hash algorithm.
+
+ Certain fixed values, such as hash sizes and common placeholder
+ object IDs, can be loaded with test_oid_init (described above).
+
+ - test_oid <key>
+
+ This function looks up a value for the hash algorithm in use, based
+ on the key given. The value must have been loaded using
+ test_oid_init or test_oid_cache. Providing an unknown key is an
+ error.
+
Prerequisites
-------------
return 1;
}
from_size = st.st_size;
- from_buf = mmap(NULL, from_size, PROT_READ, MAP_PRIVATE, fd, 0);
- if (from_buf == MAP_FAILED) {
+ from_buf = xmalloc(from_size);
+ if (read_in_full(fd, from_buf, from_size) < 0) {
perror(argv[2]);
close(fd);
return 1;
return 1;
}
data_size = st.st_size;
- data_buf = mmap(NULL, data_size, PROT_READ, MAP_PRIVATE, fd, 0);
- if (data_buf == MAP_FAILED) {
+ data_buf = xmalloc(data_size);
+ if (read_in_full(fd, data_buf, data_size) < 0) {
perror(argv[3]);
close(fd);
return 1;
}
else {
dump_one(it, pfx, "");
- if (oidcmp(&it->oid, &ref->oid) ||
+ if (!oideq(&it->oid, &ref->oid) ||
ref->entry_count != it->entry_count ||
ref->subtree_nr != it->subtree_nr) {
/* claims to be valid but is lying */
+#include "test-tool.h"
#include "cache.h"
-int cmd_main(int ac, const char **av)
+int cmd__dump_fsmonitor(int ac, const char **av)
{
struct index_state *istate = &the_index;
int i;
+#include "test-tool.h"
#include "cache.h"
#include "dir.h"
strbuf_setlen(base, len);
}
-int cmd_main(int ac, const char **av)
+int cmd__dump_untracked_cache(int ac, const char **av)
{
struct untracked_cache *uc;
struct strbuf base = STRBUF_INIT;
+#include "test-tool.h"
#include "cache.h"
#include "parse-options.h"
#include "string-list.h"
strbuf_release(&buf);
}
-int cmd_main(int argc, const char **argv)
+int cmd__parse_options(int argc, const char **argv)
{
const char *prefix = "prefix/";
const char *usage[] = {
- "test-parse-options <options>",
+ "test-tool parse-options <options>",
"",
"A helper function for the parse-options API.",
NULL
#include "cache.h"
+#include "test-tool.h"
#include "pkt-line.h"
static void pack_line(const char *line)
}
}
-int cmd_main(int argc, const char **argv)
+int cmd__pkt_line(int argc, const char **argv)
{
if (argc < 2)
die("too few arguments");
--- /dev/null
+#include "test-tool.h"
+#include "cache.h"
+#include "commit.h"
+#include "commit-reach.h"
+#include "config.h"
+#include "parse-options.h"
+#include "ref-filter.h"
+#include "string-list.h"
+#include "tag.h"
+
+static void print_sorted_commit_ids(struct commit_list *list)
+{
+ int i;
+ struct string_list s = STRING_LIST_INIT_DUP;
+
+ while (list) {
+ string_list_append(&s, oid_to_hex(&list->item->object.oid));
+ list = list->next;
+ }
+
+ string_list_sort(&s);
+
+ for (i = 0; i < s.nr; i++)
+ printf("%s\n", s.items[i].string);
+
+ string_list_clear(&s, 0);
+}
+
+int cmd__reach(int ac, const char **av)
+{
+ struct object_id oid_A, oid_B;
+ struct commit *A, *B;
+ struct commit_list *X, *Y;
+ struct object_array X_obj = OBJECT_ARRAY_INIT;
+ struct commit **X_array;
+ int X_nr, X_alloc;
+ struct strbuf buf = STRBUF_INIT;
+ struct repository *r = the_repository;
+
+ setup_git_directory();
+
+ if (ac < 2)
+ exit(1);
+
+ A = B = NULL;
+ X = Y = NULL;
+ X_nr = 0;
+ X_alloc = 16;
+ ALLOC_ARRAY(X_array, X_alloc);
+
+ while (strbuf_getline(&buf, stdin) != EOF) {
+ struct object_id oid;
+ struct object *orig;
+ struct object *peeled;
+ struct commit *c;
+ if (buf.len < 3)
+ continue;
+
+ if (get_oid_committish(buf.buf + 2, &oid))
+ die("failed to resolve %s", buf.buf + 2);
+
+ orig = parse_object(r, &oid);
+ peeled = deref_tag_noverify(orig);
+
+ if (!peeled)
+ die("failed to load commit for input %s resulting in oid %s\n",
+ buf.buf, oid_to_hex(&oid));
+
+ c = object_as_type(r, peeled, OBJ_COMMIT, 0);
+
+ if (!c)
+ die("failed to load commit for input %s resulting in oid %s\n",
+ buf.buf, oid_to_hex(&oid));
+
+ switch (buf.buf[0]) {
+ case 'A':
+ oidcpy(&oid_A, &oid);
+ A = c;
+ break;
+
+ case 'B':
+ oidcpy(&oid_B, &oid);
+ B = c;
+ break;
+
+ case 'X':
+ commit_list_insert(c, &X);
+ ALLOC_GROW(X_array, X_nr + 1, X_alloc);
+ X_array[X_nr++] = c;
+ add_object_array(orig, NULL, &X_obj);
+ break;
+
+ case 'Y':
+ commit_list_insert(c, &Y);
+ break;
+
+ default:
+ die("unexpected start of line: %c", buf.buf[0]);
+ }
+ }
+ strbuf_release(&buf);
+
+ if (!strcmp(av[1], "ref_newer"))
+ printf("%s(A,B):%d\n", av[1], ref_newer(&oid_A, &oid_B));
+ else if (!strcmp(av[1], "in_merge_bases"))
+ printf("%s(A,B):%d\n", av[1], in_merge_bases(A, B));
+ else if (!strcmp(av[1], "is_descendant_of"))
+ printf("%s(A,X):%d\n", av[1], is_descendant_of(A, X));
+ else if (!strcmp(av[1], "get_merge_bases_many")) {
+ struct commit_list *list = get_merge_bases_many(A, X_nr, X_array);
+ printf("%s(A,X):\n", av[1]);
+ print_sorted_commit_ids(list);
+ } else if (!strcmp(av[1], "reduce_heads")) {
+ struct commit_list *list = reduce_heads(X);
+ printf("%s(X):\n", av[1]);
+ print_sorted_commit_ids(list);
+ } else if (!strcmp(av[1], "can_all_from_reach")) {
+ printf("%s(X,Y):%d\n", av[1], can_all_from_reach(X, Y, 1));
+ } else if (!strcmp(av[1], "can_all_from_reach_with_flag")) {
+ struct commit_list *iter = Y;
+
+ while (iter) {
+ iter->item->object.flags |= 2;
+ iter = iter->next;
+ }
+
+ printf("%s(X,_,_,0,0):%d\n", av[1], can_all_from_reach_with_flag(&X_obj, 2, 4, 0, 0));
+ } else if (!strcmp(av[1], "commit_contains")) {
+ struct ref_filter filter;
+ struct contains_cache cache;
+ init_contains_cache(&cache);
+
+ if (ac > 2 && !strcmp(av[2], "--tag"))
+ filter.with_commit_tag_algo = 1;
+ else
+ filter.with_commit_tag_algo = 0;
+
+ printf("%s(_,A,X,_):%d\n", av[1], commit_contains(&filter, A, X, &cache));
+ }
+
+ exit(0);
+}
--- /dev/null
+#include "test-tool.h"
+#include "cache.h"
+#include "midx.h"
+#include "repository.h"
+#include "object-store.h"
+
+static int read_midx_file(const char *object_dir)
+{
+ uint32_t i;
+ struct multi_pack_index *m = load_multi_pack_index(object_dir, 1);
+
+ if (!m)
+ return 1;
+
+ printf("header: %08x %d %d %d\n",
+ m->signature,
+ m->version,
+ m->num_chunks,
+ m->num_packs);
+
+ printf("chunks:");
+
+ if (m->chunk_pack_names)
+ printf(" pack-names");
+ if (m->chunk_oid_fanout)
+ printf(" oid-fanout");
+ if (m->chunk_oid_lookup)
+ printf(" oid-lookup");
+ if (m->chunk_object_offsets)
+ printf(" object-offsets");
+ if (m->chunk_large_offsets)
+ printf(" large-offsets");
+
+ printf("\nnum_objects: %d\n", m->num_objects);
+
+ printf("packs:\n");
+ for (i = 0; i < m->num_packs; i++)
+ printf("%s\n", m->pack_names[i]);
+
+ printf("object-dir: %s\n", m->object_dir);
+
+ return 0;
+}
+
+int cmd__read_midx(int argc, const char **argv)
+{
+ if (argc != 2)
+ usage("read-midx <object-dir>");
+
+ return read_midx_file(argv[1]);
+}
struct commit *c;
struct commit_list *parent;
- repo_init(&r, gitdir, worktree);
+ setup_git_env(gitdir);
+
+ if (repo_init(&r, gitdir, worktree))
+ die("Couldn't init repo");
c = lookup_commit(&r, commit_oid);
struct commit *c;
struct tree *tree;
- repo_init(&r, gitdir, worktree);
+ setup_git_env(gitdir);
+
+ if (repo_init(&r, gitdir, worktree))
+ die("Couldn't init repo");
c = lookup_commit(&r, commit_oid);
int argc = ARRAY_SIZE(argv) - 1;
int got_revision = 0;
- init_revisions(&rev, NULL);
+ repo_init_revisions(the_repository, &rev, NULL);
setup_revisions(argc, argv, &rev, NULL);
if (prepare_revision_walk(&rev))
die("revision walk setup failed");
{ "delta", cmd__delta },
{ "drop-caches", cmd__drop_caches },
{ "dump-cache-tree", cmd__dump_cache_tree },
+ { "dump-fsmonitor", cmd__dump_fsmonitor },
{ "dump-split-index", cmd__dump_split_index },
+ { "dump-untracked-cache", cmd__dump_untracked_cache },
{ "example-decorate", cmd__example_decorate },
{ "genrandom", cmd__genrandom },
{ "hashmap", cmd__hashmap },
{ "mergesort", cmd__mergesort },
{ "mktemp", cmd__mktemp },
{ "online-cpus", cmd__online_cpus },
+ { "parse-options", cmd__parse_options },
{ "path-utils", cmd__path_utils },
+ { "pkt-line", cmd__pkt_line },
{ "prio-queue", cmd__prio_queue },
+ { "reach", cmd__reach },
{ "read-cache", cmd__read_cache },
+ { "read-midx", cmd__read_midx },
{ "ref-store", cmd__ref_store },
{ "regex", cmd__regex },
{ "repository", cmd__repository },
{ "revision-walking", cmd__revision_walking },
{ "run-command", cmd__run_command },
{ "scrap-cache-tree", cmd__scrap_cache_tree },
- { "sha1-array", cmd__sha1_array },
{ "sha1", cmd__sha1 },
+ { "sha1-array", cmd__sha1_array },
{ "sigchain", cmd__sigchain },
{ "strcmp-offset", cmd__strcmp_offset },
{ "string-list", cmd__string_list },
{ "subprocess", cmd__subprocess },
{ "urlmatch-normalization", cmd__urlmatch_normalization },
{ "wildmatch", cmd__wildmatch },
+#ifdef GIT_WINDOWS_NATIVE
+ { "windows-named-pipe", cmd__windows_named_pipe },
+#endif
{ "write-cache", cmd__write_cache },
};
+static NORETURN void die_usage(void)
+{
+ size_t i;
+
+ fprintf(stderr, "usage: test-tool <toolname> [args]\n");
+ for (i = 0; i < ARRAY_SIZE(cmds); i++)
+ fprintf(stderr, " %s\n", cmds[i].name);
+ exit(128);
+}
+
int cmd_main(int argc, const char **argv)
{
int i;
BUG_exit_code = 99;
if (argc < 2)
- die("I need a test name!");
+ die_usage();
for (i = 0; i < ARRAY_SIZE(cmds); i++) {
if (!strcmp(cmds[i].name, argv[1])) {
return cmds[i].fn(argc, argv);
}
}
- die("There is no test named '%s'", argv[1]);
+ error("there is no tool named '%s'", argv[1]);
+ die_usage();
}
-#ifndef __TEST_TOOL_H__
-#define __TEST_TOOL_H__
+#ifndef TEST_TOOL_H
+#define TEST_TOOL_H
#include "git-compat-util.h"
int cmd__delta(int argc, const char **argv);
int cmd__drop_caches(int argc, const char **argv);
int cmd__dump_cache_tree(int argc, const char **argv);
+int cmd__dump_fsmonitor(int argc, const char **argv);
int cmd__dump_split_index(int argc, const char **argv);
+int cmd__dump_untracked_cache(int argc, const char **argv);
int cmd__example_decorate(int argc, const char **argv);
int cmd__genrandom(int argc, const char **argv);
int cmd__hashmap(int argc, const char **argv);
int cmd__mergesort(int argc, const char **argv);
int cmd__mktemp(int argc, const char **argv);
int cmd__online_cpus(int argc, const char **argv);
+int cmd__parse_options(int argc, const char **argv);
int cmd__path_utils(int argc, const char **argv);
+int cmd__pkt_line(int argc, const char **argv);
int cmd__prio_queue(int argc, const char **argv);
+int cmd__reach(int argc, const char **argv);
int cmd__read_cache(int argc, const char **argv);
+int cmd__read_midx(int argc, const char **argv);
int cmd__ref_store(int argc, const char **argv);
int cmd__regex(int argc, const char **argv);
int cmd__repository(int argc, const char **argv);
int cmd__revision_walking(int argc, const char **argv);
int cmd__run_command(int argc, const char **argv);
int cmd__scrap_cache_tree(int argc, const char **argv);
-int cmd__sha1_array(int argc, const char **argv);
int cmd__sha1(int argc, const char **argv);
+int cmd__sha1_array(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);
int cmd__subprocess(int argc, const char **argv);
int cmd__urlmatch_normalization(int argc, const char **argv);
int cmd__wildmatch(int argc, const char **argv);
+#ifdef GIT_WINDOWS_NATIVE
+int cmd__windows_named_pipe(int argc, const char **argv);
+#endif
int cmd__write_cache(int argc, const char **argv);
#endif
--- /dev/null
+#include "test-tool.h"
+#include "git-compat-util.h"
+#include "strbuf.h"
+
+#ifdef GIT_WINDOWS_NATIVE
+static const char *usage_string = "<pipe-filename>";
+
+#define TEST_BUFSIZE (4096)
+
+int cmd__windows_named_pipe(int argc, const char **argv)
+{
+ const char *filename;
+ struct strbuf pathname = STRBUF_INIT;
+ int err;
+ HANDLE h;
+ BOOL connected;
+ char buf[TEST_BUFSIZE + 1];
+
+ if (argc < 2)
+ goto print_usage;
+ filename = argv[1];
+ if (strchr(filename, '/') || strchr(filename, '\\'))
+ goto print_usage;
+ strbuf_addf(&pathname, "//./pipe/%s", filename);
+
+ /*
+ * Create a single instance of the server side of the named pipe.
+ * This will allow exactly one client instance to connect to it.
+ */
+ h = CreateNamedPipeA(
+ pathname.buf,
+ PIPE_ACCESS_INBOUND | FILE_FLAG_FIRST_PIPE_INSTANCE,
+ PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
+ PIPE_UNLIMITED_INSTANCES,
+ TEST_BUFSIZE, TEST_BUFSIZE, 0, NULL);
+ if (h == INVALID_HANDLE_VALUE) {
+ err = err_win_to_posix(GetLastError());
+ fprintf(stderr, "CreateNamedPipe failed: %s\n",
+ strerror(err));
+ return err;
+ }
+
+ connected = ConnectNamedPipe(h, NULL)
+ ? TRUE
+ : (GetLastError() == ERROR_PIPE_CONNECTED);
+ if (!connected) {
+ err = err_win_to_posix(GetLastError());
+ fprintf(stderr, "ConnectNamedPipe failed: %s\n",
+ strerror(err));
+ CloseHandle(h);
+ return err;
+ }
+
+ while (1) {
+ DWORD nbr;
+ BOOL success = ReadFile(h, buf, TEST_BUFSIZE, &nbr, NULL);
+ if (!success || nbr == 0)
+ break;
+ buf[nbr] = 0;
+
+ write(1, buf, nbr);
+ }
+
+ DisconnectNamedPipe(h);
+ CloseHandle(h);
+ return 0;
+
+print_usage:
+ fprintf(stderr, "usage: %s %s\n", argv[0], usage_string);
+ return 1;
+}
+#endif
echo | gpgsm --homedir "${GNUPGHOME}" 2>/dev/null \
--passphrase-fd 0 --pinentry-mode loopback \
--import "$TEST_DIRECTORY"/lib-gpg/gpgsm_cert.p12 &&
- gpgsm --homedir "${GNUPGHOME}" 2>/dev/null -K \
- | grep fingerprint: | cut -d" " -f4 | tr -d '\n' > \
- ${GNUPGHOME}/trustlist.txt &&
+
+ gpgsm --homedir "${GNUPGHOME}" 2>/dev/null -K |
+ grep fingerprint: |
+ cut -d" " -f4 |
+ tr -d '\n' >"${GNUPGHOME}/trustlist.txt" &&
+
echo " S relax" >> ${GNUPGHOME}/trustlist.txt &&
(gpgconf --kill gpg-agent >/dev/null 2>&1 || : ) &&
echo hello | gpgsm --homedir "${GNUPGHOME}" >/dev/null \
--- /dev/null
+This directory contains various per-hash values that are used in the testsuite.
+
+Each file contains lines containing a key-value pair; blank lines and lines
+starting with `#` are ignored. The key and value are separated by whitespace
+(specifically, those whitespace in the default `$IFS`). The key consists only
+of shell identifier characters, and the value consists of a hash algorithm,
+colon, and value. The hash algorithm also consists only of shell identifier
+characters; it should match the value in sha1-file.c.
+
+For example, the following lines map the key "rawsz" to "20" if SHA-1 is in use
+and to "32" if SHA-256 is in use:
+
+----
+rawsz sha1:20
+rawsz sha256:32
+----
+
+The keys and values used here are loaded by `test_oid_init` (see the README file
+in the "t" directory) and are used by calling `test_oid`.
--- /dev/null
+rawsz sha1:20
+rawsz sha256:32
+
+hexsz sha1:40
+hexsz sha256:64
+
+zero sha1:0000000000000000000000000000000000000000
+zero sha256:0000000000000000000000000000000000000000000000000000000000000000
--- /dev/null
+# These are some common invalid and partial object IDs used in tests.
+001 sha1:0000000000000000000000000000000000000001
+001 sha256:0000000000000000000000000000000000000000000000000000000000000001
+002 sha1:0000000000000000000000000000000000000002
+002 sha256:0000000000000000000000000000000000000000000000000000000000000002
+003 sha1:0000000000000000000000000000000000000003
+003 sha256:0000000000000000000000000000000000000000000000000000000000000003
+004 sha1:0000000000000000000000000000000000000004
+004 sha256:0000000000000000000000000000000000000000000000000000000000000004
+005 sha1:0000000000000000000000000000000000000005
+005 sha256:0000000000000000000000000000000000000000000000000000000000000005
+006 sha1:0000000000000000000000000000000000000006
+006 sha256:0000000000000000000000000000000000000000000000000000000000000006
+007 sha1:0000000000000000000000000000000000000007
+007 sha256:0000000000000000000000000000000000000000000000000000000000000007
+# All zeros or Fs missing one or two hex segments.
+zero_1 sha1:000000000000000000000000000000000000000
+zero_1 sha256:000000000000000000000000000000000000000000000000000000000000000
+zero_2 sha1:00000000000000000000000000000000000000
+zero_2 sha256:00000000000000000000000000000000000000000000000000000000000000
+ff_1 sha1:fffffffffffffffffffffffffffffffffffffff
+ff_1 sha256:fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ff_2 sha1:ffffffffffffffffffffffffffffffffffffff
+ff_2 sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+# More various invalid OIDs.
+numeric sha1:0123456789012345678901234567890123456789
+numeric sha256:0123456789012345678901234567890123456789012345678901234567890123
+deadbeef sha1:deadbeefdeadbeefdeadbeefdeadbeefdeadbeef
+deadbeef sha256:deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef
While we have tried to make sure that it can cope with embedded
whitespace and other special characters, it will not work with
multi-line data.
+
+Rather than tracking the performance by run-time as `test_perf` does, you
+may also track output size by using `test_size`. The stdout of the
+function should be a single numeric value, which will be captured and
+shown in the aggregated output. For example:
+
+ test_perf 'time foo' '
+ ./foo >foo.out
+ '
+
+ test_size 'output size'
+ wc -c <foo.out
+ '
+
+might produce output like:
+
+ Test origin HEAD
+ -------------------------------------------------------------
+ 1234.1 time foo 0.37(0.79+0.02) 0.26(0.51+0.02) -29.7%
+ 1234.2 output size 4.3M 3.6M -14.7%
+
+The item being measured (and its units) is up to the test; the context
+and the test title should make it clear to the user whether bigger or
+smaller numbers are better. Unlike test_perf, the test code will only be
+run once, since output sizes tend to be more deterministic than timings.
my $line = <$fh>;
return undef if not defined $line;
close $fh or die "cannot close $name: $!";
- $line =~ /^(?:(\d+):)?(\d+):(\d+(?:\.\d+)?) (\d+(?:\.\d+)?) (\d+(?:\.\d+)?)$/
- or die "bad input line: $line";
- my $rt = ((defined $1 ? $1 : 0.0)*60+$2)*60+$3;
- return ($rt, $4, $5);
+ # times
+ if ($line =~ /^(?:(\d+):)?(\d+):(\d+(?:\.\d+)?) (\d+(?:\.\d+)?) (\d+(?:\.\d+)?)$/) {
+ my $rt = ((defined $1 ? $1 : 0.0)*60+$2)*60+$3;
+ return ($rt, $4, $5);
+ # size
+ } elsif ($line =~ /^\d+$/) {
+ return $&;
+ } else {
+ die "bad input line: $line";
+ }
+}
+
+sub relative_change {
+ my ($r, $firstr) = @_;
+ if ($firstr > 0) {
+ return sprintf "%+.1f%%", 100.0*($r-$firstr)/$firstr;
+ } elsif ($r == 0) {
+ return "=";
+ } else {
+ return "+inf";
+ }
}
sub format_times {
my ($r, $u, $s, $firstr) = @_;
+ # no value means we did not finish the test
if (!defined $r) {
return "<missing>";
}
- my $out = sprintf "%.2f(%.2f+%.2f)", $r, $u, $s;
- if (defined $firstr) {
- if ($firstr > 0) {
- $out .= sprintf " %+.1f%%", 100.0*($r-$firstr)/$firstr;
- } elsif ($r == 0) {
- $out .= " =";
- } else {
- $out .= " +inf";
- }
+ # a single value means we have a size, not times
+ if (!defined $u) {
+ return format_size($r, $firstr);
}
+ # otherwise, we have real/user/system times
+ my $out = sprintf "%.2f(%.2f+%.2f)", $r, $u, $s;
+ $out .= ' ' . relative_change($r, $firstr) if defined $firstr;
return $out;
}
exit(1);
}
+sub human_size {
+ my $n = shift;
+ my @units = ('', qw(K M G));
+ while ($n > 900 && @units > 1) {
+ $n /= 1000;
+ shift @units;
+ }
+ return $n unless length $units[0];
+ return sprintf '%.1f%s', $n, $units[0];
+}
+
+sub format_size {
+ my ($size, $first) = @_;
+ # match the width of a time: 0.00(0.00+0.00)
+ my $out = sprintf '%15s', human_size($size);
+ $out .= ' ' . relative_change($size, $first) if defined $first;
+ return $out;
+}
+
my (@dirs, %dirnames, %dirabbrevs, %prefixes, @tests,
$codespeed, $sortby, $subsection, $reponame);
my $firstr;
for my $i (0..$#dirs) {
my $d = $dirs[$i];
- $times{$prefixes{$d}.$t} = [get_times("$resultsdir/$prefixes{$d}$t.times")];
+ my $base = "$resultsdir/$prefixes{$d}$t";
+ $times{$prefixes{$d}.$t} = [];
+ foreach my $type (qw(times size)) {
+ if (-e "$base.$type") {
+ $times{$prefixes{$d}.$t} = [get_times("$base.$type")];
+ last;
+ }
+ }
my ($r,$u,$s) = @{$times{$prefixes{$d}.$t}};
my $w = length format_times($r,$u,$s,$firstr);
$colwidth[$i] = $w if $w > $colwidth[$i];
--- /dev/null
+#!/bin/sh
+
+test_description='Test fsck performance'
+
+. ./perf-lib.sh
+
+test_perf_large_repo
+
+test_perf 'fsck' '
+ git fsck
+'
+
+test_done
--- /dev/null
+#!/bin/sh
+
+test_description='Test fsck skipList performance'
+
+. ./perf-lib.sh
+
+test_perf_fresh_repo
+
+n=1000000
+
+test_expect_success "setup $n bad commits" '
+ for i in $(test_seq 1 $n)
+ do
+ echo "commit refs/heads/master" &&
+ echo "committer C <c@example.com> 1234567890 +0000" &&
+ echo "data <<EOF" &&
+ echo "$i.Q." &&
+ echo "EOF"
+ done | q_to_nul | git fast-import
+'
+
+skip=0
+while test $skip -le $n
+do
+ test_expect_success "create skipList for $skip bad commits" '
+ git log --format=%H --max-count=$skip |
+ sort >skiplist
+ '
+
+ test_perf "fsck with $skip skipped bad commits" '
+ git -c fsck.skipList=skiplist fsck
+ '
+
+ case $skip in
+ 0) skip=1 ;;
+ *) skip=${skip}0 ;;
+ esac
+done
+
+test_done
--- /dev/null
+#!/bin/sh
+
+test_description='performance of fetches from bitmapped packs'
+. ./perf-lib.sh
+
+test_perf_default_repo
+
+test_expect_success 'create bitmapped server repo' '
+ git config pack.writebitmaps true &&
+ git config pack.writebitmaphashcache true &&
+ git repack -ad
+'
+
+# simulate a fetch from a repository that last fetched N days ago, for
+# various values of N. We do so by following the first-parent chain,
+# and assume the first entry in the chain that is N days older than the current
+# HEAD is where the HEAD would have been then.
+for days in 1 2 4 8 16 32 64 128; do
+ title=$(printf '%10s' "($days days)")
+ test_expect_success "setup revs from $days days ago" '
+ now=$(git log -1 --format=%ct HEAD) &&
+ then=$(($now - ($days * 86400))) &&
+ tip=$(git rev-list -1 --first-parent --until=$then HEAD) &&
+ {
+ echo HEAD &&
+ echo ^$tip
+ } >revs
+ '
+
+ test_perf "server $title" '
+ git pack-objects --stdout --revs \
+ --thin --delta-base-offset \
+ <revs >tmp.pack
+ '
+
+ test_size "size $title" '
+ wc -c <tmp.pack
+ '
+
+ test_perf "client $title" '
+ git index-pack --stdin --fix-thin <tmp.pack
+ '
+done
+
+test_done
return "$eval_ret"
}
-
-test_perf () {
+test_wrapper_ () {
+ test_wrapper_func_=$1; shift
test_start_
test "$#" = 3 && { test_prereq=$1; shift; } || test_prereq=
test "$#" = 2 ||
base=$(basename "$0" .sh)
echo "$test_count" >>"$perf_results_dir"/$base.subtests
echo "$1" >"$perf_results_dir"/$base.$test_count.descr
- if test -z "$verbose"; then
- printf "%s" "perf $test_count - $1:"
- else
- echo "perf $test_count - $1:"
- fi
- for i in $(test_seq 1 $GIT_PERF_REPEAT_COUNT); do
- say >&3 "running: $2"
- if test_run_perf_ "$2"
- then
- if test -z "$verbose"; then
- printf " %s" "$i"
- else
- echo "* timing run $i/$GIT_PERF_REPEAT_COUNT:"
- fi
+ base="$perf_results_dir"/"$perf_results_prefix$(basename "$0" .sh)"."$test_count"
+ "$test_wrapper_func_" "$@"
+ fi
+
+ test_finish_
+}
+
+test_perf_ () {
+ if test -z "$verbose"; then
+ printf "%s" "perf $test_count - $1:"
+ else
+ echo "perf $test_count - $1:"
+ fi
+ for i in $(test_seq 1 $GIT_PERF_REPEAT_COUNT); do
+ say >&3 "running: $2"
+ if test_run_perf_ "$2"
+ then
+ if test -z "$verbose"; then
+ printf " %s" "$i"
else
- test -z "$verbose" && echo
- test_failure_ "$@"
- break
+ echo "* timing run $i/$GIT_PERF_REPEAT_COUNT:"
fi
- done
- if test -z "$verbose"; then
- echo " ok"
else
- test_ok_ "$1"
+ test -z "$verbose" && echo
+ test_failure_ "$@"
+ break
fi
- base="$perf_results_dir"/"$perf_results_prefix$(basename "$0" .sh)"."$test_count"
- "$TEST_DIRECTORY"/perf/min_time.perl test_time.* >"$base".times
+ done
+ if test -z "$verbose"; then
+ echo " ok"
+ else
+ test_ok_ "$1"
fi
- test_finish_
+ "$TEST_DIRECTORY"/perf/min_time.perl test_time.* >"$base".times
+}
+
+test_perf () {
+ test_wrapper_ test_perf_ "$@"
+}
+
+test_size_ () {
+ say >&3 "running: $2"
+ if test_eval_ "$2" 3>"$base".size; then
+ test_ok_ "$1"
+ else
+ test_failure_ "$@"
+ fi
+}
+
+test_size () {
+ test_wrapper_ test_size_ "$@"
}
# We extend test_done to print timings at the end (./run disables this
passing metrics
'
+ # Tell the framework that we are self-testing to make sure
+ # it yields a stable result.
+ GIT_TEST_FRAMEWORK_SELFTEST=t &&
+
# Point to the t/test-lib.sh, which isn't in ../ as usual
. "\$TEST_DIRECTORY"/test-lib.sh
EOF
EOF
"
+test_expect_success 'test_oid setup' '
+ test_oid_init
+'
+
+test_expect_success 'test_oid provides sane info by default' '
+ test_oid zero >actual &&
+ grep "^00*\$" actual &&
+ rawsz="$(test_oid rawsz)" &&
+ hexsz="$(test_oid hexsz)" &&
+ test "$hexsz" -eq $(wc -c <actual) &&
+ test $(( $rawsz * 2)) -eq "$hexsz"
+'
+
+test_expect_success 'test_oid can look up data for SHA-1' '
+ test_when_finished "test_detect_hash" &&
+ test_set_hash sha1 &&
+ test_oid zero >actual &&
+ grep "^00*\$" actual &&
+ rawsz="$(test_oid rawsz)" &&
+ hexsz="$(test_oid hexsz)" &&
+ test $(wc -c <actual) -eq 40 &&
+ test "$rawsz" -eq 20 &&
+ test "$hexsz" -eq 40
+'
+
+test_expect_success 'test_oid can look up data for SHA-256' '
+ test_when_finished "test_detect_hash" &&
+ test_set_hash sha256 &&
+ test_oid zero >actual &&
+ grep "^00*\$" actual &&
+ rawsz="$(test_oid rawsz)" &&
+ hexsz="$(test_oid hexsz)" &&
+ test $(wc -c <actual) -eq 64 &&
+ test "$rawsz" -eq 32 &&
+ test "$hexsz" -eq 64
+'
+
################################################################
# Basics of the basics
+test_oid_cache <<\EOF
+path0f sha1:f87290f8eb2cbbea7857214459a0739927eab154
+path0f sha256:638106af7c38be056f3212cbd7ac65bc1bac74f420ca5a436ff006a9d025d17d
+
+path0s sha1:15a98433ae33114b085f3eb3bb03b832b3180a01
+path0s sha256:3a24cc53cf68edddac490bbf94a418a52932130541361f685df685e41dd6c363
+
+path2f sha1:3feff949ed00a62d9f7af97c15cd8a30595e7ac7
+path2f sha256:2a7f36571c6fdbaf0e3f62751a0b25a3f4c54d2d1137b3f4af9cb794bb498e5f
+
+path2s sha1:d8ce161addc5173867a3c3c730924388daedbc38
+path2s sha256:18fd611b787c2e938ddcc248fabe4d66a150f9364763e9ec133dd01d5bb7c65a
+
+path2d sha1:58a09c23e2ca152193f2786e06986b7b6712bdbe
+path2d sha256:00e4b32b96e7e3d65d79112dcbea53238a22715f896933a62b811377e2650c17
+
+path3f sha1:0aa34cae68d0878578ad119c86ca2b5ed5b28376
+path3f sha256:09f58616b951bd571b8cb9dc76d372fbb09ab99db2393f5ab3189d26c45099ad
+
+path3s sha1:8599103969b43aff7e430efea79ca4636466794f
+path3s sha256:fce1aed087c053306f3f74c32c1a838c662bbc4551a7ac2420f5d6eb061374d0
+
+path3d sha1:21ae8269cacbe57ae09138dcc3a2887f904d02b3
+path3d sha256:9b60497be959cb830bf3f0dc82bcc9ad9e925a24e480837ade46b2295e47efe1
+
+subp3f sha1:00fb5908cb97c2564a9783c0c64087333b3b464f
+subp3f sha256:a1a9e16998c988453f18313d10375ee1d0ddefe757e710dcae0d66aa1e0c58b3
+
+subp3s sha1:6649a1ebe9e9f1c553b66f5a6e74136a07ccc57c
+subp3s sha256:81759d9f5e93c6546ecfcadb560c1ff057314b09f93fe8ec06e2d8610d34ef10
+
+subp3d sha1:3c5e5399f3a333eddecce7a9b9465b63f65f51e2
+subp3d sha256:76b4ef482d4fa1c754390344cf3851c7f883b27cf9bc999c6547928c46aeafb7
+
+root sha1:087704a96baf1c2d1c869a8b084481e121c88b5b
+root sha256:9481b52abab1b2ffeedbf9de63ce422b929f179c1b98ff7bee5f8f1bc0710751
+
+simpletree sha1:7bb943559a305bdd6bdee2cef6e5df2413c3d30a
+simpletree sha256:1710c07a6c86f9a3c7376364df04c47ee39e5a5e221fcdd84b743bc9bb7e2bc5
+EOF
+
# updating a new file without --add should fail.
test_expect_success 'git update-index without --add should fail adding' '
test_must_fail git update-index should-be-empty
'
# we know the shape and contents of the tree and know the object ID for it.
-test_expect_success SHA1 'validate object ID of a known tree' '
- test "$tree" = 7bb943559a305bdd6bdee2cef6e5df2413c3d30a
+test_expect_success 'validate object ID of a known tree' '
+ test "$tree" = "$(test_oid simpletree)"
'
# Removing paths.
git ls-files --stage >current
'
-test_expect_success SHA1 'validate git ls-files output for a known tree' '
- cat >expected <<-\EOF &&
- 100644 f87290f8eb2cbbea7857214459a0739927eab154 0 path0
- 120000 15a98433ae33114b085f3eb3bb03b832b3180a01 0 path0sym
- 100644 3feff949ed00a62d9f7af97c15cd8a30595e7ac7 0 path2/file2
- 120000 d8ce161addc5173867a3c3c730924388daedbc38 0 path2/file2sym
- 100644 0aa34cae68d0878578ad119c86ca2b5ed5b28376 0 path3/file3
- 120000 8599103969b43aff7e430efea79ca4636466794f 0 path3/file3sym
- 100644 00fb5908cb97c2564a9783c0c64087333b3b464f 0 path3/subp3/file3
- 120000 6649a1ebe9e9f1c553b66f5a6e74136a07ccc57c 0 path3/subp3/file3sym
+test_expect_success 'validate git ls-files output for a known tree' '
+ cat >expected <<-EOF &&
+ 100644 $(test_oid path0f) 0 path0
+ 120000 $(test_oid path0s) 0 path0sym
+ 100644 $(test_oid path2f) 0 path2/file2
+ 120000 $(test_oid path2s) 0 path2/file2sym
+ 100644 $(test_oid path3f) 0 path3/file3
+ 120000 $(test_oid path3s) 0 path3/file3sym
+ 100644 $(test_oid subp3f) 0 path3/subp3/file3
+ 120000 $(test_oid subp3s) 0 path3/subp3/file3sym
EOF
test_cmp expected current
'
tree=$(git write-tree)
'
-test_expect_success SHA1 'validate object ID for a known tree' '
- test "$tree" = 087704a96baf1c2d1c869a8b084481e121c88b5b
+test_expect_success 'validate object ID for a known tree' '
+ test "$tree" = "$(test_oid root)"
'
test_expect_success 'showing tree with git ls-tree' '
git ls-tree $tree >current
'
-test_expect_success SHA1 'git ls-tree output for a known tree' '
- cat >expected <<-\EOF &&
- 100644 blob f87290f8eb2cbbea7857214459a0739927eab154 path0
- 120000 blob 15a98433ae33114b085f3eb3bb03b832b3180a01 path0sym
- 040000 tree 58a09c23e2ca152193f2786e06986b7b6712bdbe path2
- 040000 tree 21ae8269cacbe57ae09138dcc3a2887f904d02b3 path3
+test_expect_success 'git ls-tree output for a known tree' '
+ cat >expected <<-EOF &&
+ 100644 blob $(test_oid path0f) path0
+ 120000 blob $(test_oid path0s) path0sym
+ 040000 tree $(test_oid path2d) path2
+ 040000 tree $(test_oid path3d) path3
EOF
test_cmp expected current
'
git ls-tree -r $tree >current
'
-test_expect_success SHA1 'git ls-tree -r output for a known tree' '
- cat >expected <<-\EOF &&
- 100644 blob f87290f8eb2cbbea7857214459a0739927eab154 path0
- 120000 blob 15a98433ae33114b085f3eb3bb03b832b3180a01 path0sym
- 100644 blob 3feff949ed00a62d9f7af97c15cd8a30595e7ac7 path2/file2
- 120000 blob d8ce161addc5173867a3c3c730924388daedbc38 path2/file2sym
- 100644 blob 0aa34cae68d0878578ad119c86ca2b5ed5b28376 path3/file3
- 120000 blob 8599103969b43aff7e430efea79ca4636466794f path3/file3sym
- 100644 blob 00fb5908cb97c2564a9783c0c64087333b3b464f path3/subp3/file3
- 120000 blob 6649a1ebe9e9f1c553b66f5a6e74136a07ccc57c path3/subp3/file3sym
+test_expect_success 'git ls-tree -r output for a known tree' '
+ cat >expected <<-EOF &&
+ 100644 blob $(test_oid path0f) path0
+ 120000 blob $(test_oid path0s) path0sym
+ 100644 blob $(test_oid path2f) path2/file2
+ 120000 blob $(test_oid path2s) path2/file2sym
+ 100644 blob $(test_oid path3f) path3/file3
+ 120000 blob $(test_oid path3s) path3/file3sym
+ 100644 blob $(test_oid subp3f) path3/subp3/file3
+ 120000 blob $(test_oid subp3s) path3/subp3/file3sym
EOF
test_cmp expected current
'
git ls-tree -r -t $tree >current
'
-test_expect_success SHA1 'git ls-tree -r output for a known tree' '
- cat >expected <<-\EOF &&
- 100644 blob f87290f8eb2cbbea7857214459a0739927eab154 path0
- 120000 blob 15a98433ae33114b085f3eb3bb03b832b3180a01 path0sym
- 040000 tree 58a09c23e2ca152193f2786e06986b7b6712bdbe path2
- 100644 blob 3feff949ed00a62d9f7af97c15cd8a30595e7ac7 path2/file2
- 120000 blob d8ce161addc5173867a3c3c730924388daedbc38 path2/file2sym
- 040000 tree 21ae8269cacbe57ae09138dcc3a2887f904d02b3 path3
- 100644 blob 0aa34cae68d0878578ad119c86ca2b5ed5b28376 path3/file3
- 120000 blob 8599103969b43aff7e430efea79ca4636466794f path3/file3sym
- 040000 tree 3c5e5399f3a333eddecce7a9b9465b63f65f51e2 path3/subp3
- 100644 blob 00fb5908cb97c2564a9783c0c64087333b3b464f path3/subp3/file3
- 120000 blob 6649a1ebe9e9f1c553b66f5a6e74136a07ccc57c path3/subp3/file3sym
+test_expect_success 'git ls-tree -r output for a known tree' '
+ cat >expected <<-EOF &&
+ 100644 blob $(test_oid path0f) path0
+ 120000 blob $(test_oid path0s) path0sym
+ 040000 tree $(test_oid path2d) path2
+ 100644 blob $(test_oid path2f) path2/file2
+ 120000 blob $(test_oid path2s) path2/file2sym
+ 040000 tree $(test_oid path3d) path3
+ 100644 blob $(test_oid path3f) path3/file3
+ 120000 blob $(test_oid path3s) path3/file3sym
+ 040000 tree $(test_oid subp3d) path3/subp3
+ 100644 blob $(test_oid subp3f) path3/subp3/file3
+ 120000 blob $(test_oid subp3s) path3/subp3/file3sym
EOF
test_cmp expected current
'
ptree=$(git write-tree --prefix=path3)
'
-test_expect_success SHA1 'validate object ID for a known tree' '
- test "$ptree" = 21ae8269cacbe57ae09138dcc3a2887f904d02b3
+test_expect_success 'validate object ID for a known tree' '
+ test "$ptree" = $(test_oid path3d)
'
test_expect_success 'writing partial tree out with git write-tree --prefix' '
ptree=$(git write-tree --prefix=path3/subp3)
'
-test_expect_success SHA1 'validate object ID for a known tree' '
- test "$ptree" = 3c5e5399f3a333eddecce7a9b9465b63f65f51e2
+test_expect_success 'validate object ID for a known tree' '
+ test "$ptree" = $(test_oid subp3d)
'
test_expect_success 'put invalid objects into the index' '
rm -f .git/index &&
- cat >badobjects <<-\EOF &&
- 100644 blob 1000000000000000000000000000000000000000 dir/file1
- 100644 blob 2000000000000000000000000000000000000000 dir/file2
- 100644 blob 3000000000000000000000000000000000000000 dir/file3
- 100644 blob 4000000000000000000000000000000000000000 dir/file4
- 100644 blob 5000000000000000000000000000000000000000 dir/file5
+ suffix=$(echo $ZERO_OID | sed -e "s/^.//") &&
+ cat >badobjects <<-EOF &&
+ 100644 blob $(test_oid 001) dir/file1
+ 100644 blob $(test_oid 002) dir/file2
+ 100644 blob $(test_oid 003) dir/file3
+ 100644 blob $(test_oid 004) dir/file4
+ 100644 blob $(test_oid 005) dir/file5
EOF
git update-index --index-info <badobjects
'
test "$newtree" = "$tree"
'
-test_expect_success SHA1 'validate git diff-files output for a know cache/work tree state' '
- cat >expected <<\EOF &&
-:100644 100644 f87290f8eb2cbbea7857214459a0739927eab154 0000000000000000000000000000000000000000 M path0
-:120000 120000 15a98433ae33114b085f3eb3bb03b832b3180a01 0000000000000000000000000000000000000000 M path0sym
-:100644 100644 3feff949ed00a62d9f7af97c15cd8a30595e7ac7 0000000000000000000000000000000000000000 M path2/file2
-:120000 120000 d8ce161addc5173867a3c3c730924388daedbc38 0000000000000000000000000000000000000000 M path2/file2sym
-:100644 100644 0aa34cae68d0878578ad119c86ca2b5ed5b28376 0000000000000000000000000000000000000000 M path3/file3
-:120000 120000 8599103969b43aff7e430efea79ca4636466794f 0000000000000000000000000000000000000000 M path3/file3sym
-:100644 100644 00fb5908cb97c2564a9783c0c64087333b3b464f 0000000000000000000000000000000000000000 M path3/subp3/file3
-:120000 120000 6649a1ebe9e9f1c553b66f5a6e74136a07ccc57c 0000000000000000000000000000000000000000 M path3/subp3/file3sym
+test_expect_success 'validate git diff-files output for a know cache/work tree state' '
+ cat >expected <<EOF &&
+:100644 100644 $(test_oid path0f) $ZERO_OID M path0
+:120000 120000 $(test_oid path0s) $ZERO_OID M path0sym
+:100644 100644 $(test_oid path2f) $ZERO_OID M path2/file2
+:120000 120000 $(test_oid path2s) $ZERO_OID M path2/file2sym
+:100644 100644 $(test_oid path3f) $ZERO_OID M path3/file3
+:120000 120000 $(test_oid path3s) $ZERO_OID M path3/file3sym
+:100644 100644 $(test_oid subp3f) $ZERO_OID M path3/subp3/file3
+:120000 120000 $(test_oid subp3s) $ZERO_OID M path3/subp3/file3sym
EOF
git diff-files >current &&
- test_cmp current expected
+ test_cmp expected current
'
test_expect_success 'git update-index --refresh should succeed' '
'
################################################################
-P=087704a96baf1c2d1c869a8b084481e121c88b5b
+P=$(test_oid root)
-test_expect_success SHA1 'git commit-tree records the correct tree in a commit' '
+test_expect_success 'git commit-tree records the correct tree in a commit' '
commit0=$(echo NO | git commit-tree $P) &&
tree=$(git show --pretty=raw $commit0 |
sed -n -e "s/^tree //p" -e "/^author /q") &&
test "z$tree" = "z$P"
'
-test_expect_success SHA1 'git commit-tree records the correct parent in a commit' '
+test_expect_success 'git commit-tree records the correct parent in a commit' '
commit1=$(echo NO | git commit-tree $P -p $commit0) &&
parent=$(git show --pretty=raw $commit1 |
sed -n -e "s/^parent //p" -e "/^author /q") &&
test "z$commit0" = "z$parent"
'
-test_expect_success SHA1 'git commit-tree omits duplicated parent in a commit' '
+test_expect_success 'git commit-tree omits duplicated parent in a commit' '
commit2=$(echo NO | git commit-tree $P -p $commit0 -p $commit0) &&
parent=$(git show --pretty=raw $commit2 |
sed -n -e "s/^parent //p" -e "/^author /q" |
mv .git .realgit &&
echo "gitdir: .realgit" >.git
) &&
+ head=$(git -C enter_repo rev-parse HEAD) &&
git ls-remote enter_repo >actual &&
- cat >expected <<-\EOF &&
- 946e985ab20de757ca5b872b16d64e92ff3803a9 HEAD
- 946e985ab20de757ca5b872b16d64e92ff3803a9 refs/heads/master
- 946e985ab20de757ca5b872b16d64e92ff3803a9 refs/tags/foo
+ cat >expected <<-EOF &&
+ $head HEAD
+ $head refs/heads/master
+ $head refs/tags/foo
EOF
test_cmp expected actual
'
cd enter_repo &&
git worktree add ../foo refs/tags/foo
) &&
+ head=$(git -C enter_repo rev-parse HEAD) &&
git ls-remote foo >actual &&
- cat >expected <<-\EOF &&
- 946e985ab20de757ca5b872b16d64e92ff3803a9 HEAD
- 946e985ab20de757ca5b872b16d64e92ff3803a9 refs/heads/master
- 946e985ab20de757ca5b872b16d64e92ff3803a9 refs/tags/foo
+ cat >expected <<-EOF &&
+ $head HEAD
+ $head refs/heads/master
+ $head refs/tags/foo
EOF
test_cmp expected actual
'
test_expect_success 'enter_repo strict mode' '
+ head=$(git -C enter_repo rev-parse HEAD) &&
git ls-remote --upload-pack="git upload-pack --strict" foo/.git >actual &&
- cat >expected <<-\EOF &&
- 946e985ab20de757ca5b872b16d64e92ff3803a9 HEAD
- 946e985ab20de757ca5b872b16d64e92ff3803a9 refs/heads/master
- 946e985ab20de757ca5b872b16d64e92ff3803a9 refs/tags/foo
+ cat >expected <<-EOF &&
+ $head HEAD
+ $head refs/heads/master
+ $head refs/tags/foo
EOF
test_cmp expected actual
'
# to verify
test_expect_success 'basic help commands' '
git help >/dev/null &&
- git help -a >/dev/null &&
+ git help -a --no-verbose >/dev/null &&
git help -g >/dev/null &&
- git help -av >/dev/null
+ git help -a >/dev/null
'
test_expect_success "works for commands and guides by default" '
--- /dev/null
+#!/bin/sh
+
+test_description='git command aliasing'
+
+. ./test-lib.sh
+
+test_expect_success 'nested aliases - internal execution' '
+ git config alias.nested-internal-1 nested-internal-2 &&
+ git config alias.nested-internal-2 status &&
+ git nested-internal-1 >output &&
+ test_i18ngrep "^On branch " output
+'
+
+test_expect_success 'nested aliases - mixed execution' '
+ git config alias.nested-external-1 nested-external-2 &&
+ git config alias.nested-external-2 "!git nested-external-3" &&
+ git config alias.nested-external-3 status &&
+ git nested-external-1 >output &&
+ test_i18ngrep "^On branch " output
+'
+
+test_expect_success 'looping aliases - internal execution' '
+ git config alias.loop-internal-1 loop-internal-2 &&
+ git config alias.loop-internal-2 loop-internal-3 &&
+ git config alias.loop-internal-3 loop-internal-2 &&
+ test_must_fail git loop-internal-1 2>output &&
+ test_i18ngrep "^fatal: alias loop detected: expansion of" output
+'
+
+# This test is disabled until external loops are fixed, because would block
+# the test suite for a full minute.
+#
+#test_expect_failure 'looping aliases - mixed execution' '
+# git config alias.loop-mixed-1 loop-mixed-2 &&
+# git config alias.loop-mixed-2 "!git loop-mixed-1" &&
+# test_must_fail git loop-mixed-1 2>output &&
+# test_i18ngrep "^fatal: alias loop detected: expansion of" output
+#'
+
+test_done
rm -f expanded-keywords expanded-keywords-crlf &&
git checkout -- expanded-keywords &&
- test_cmp expanded-keywords expected-output &&
+ test_cmp expected-output expanded-keywords &&
git checkout -- expanded-keywords-crlf &&
- test_cmp expanded-keywords-crlf expected-output-crlf
+ test_cmp expected-output-crlf expanded-keywords-crlf
'
# The use of %f in a filter definition is expanded to the path to
. ./test-lib.sh
cat >expect <<\EOF
-usage: test-parse-options <options>
+usage: test-tool parse-options <options>
A helper function for the parse-options API.
EOF
test_expect_success 'test help' '
- test_must_fail test-parse-options -h >output 2>output.err &&
+ test_must_fail test-tool parse-options -h >output 2>output.err &&
test_must_be_empty output.err &&
test_i18ncmp expect output
'
shift &&
expect="$1" &&
shift &&
- test-parse-options --expect="$what $expect" "$@"
+ test-tool parse-options --expect="$what $expect" "$@"
}
check_unknown_i18n() {
echo error: unknown switch \`${1#-}\' >expect ;;
esac &&
cat expect.err >>expect &&
- test_must_fail test-parse-options $* >output 2>output.err &&
+ test_must_fail test-tool parse-options $* >output 2>output.err &&
test_must_be_empty output &&
test_i18ncmp expect output.err
}
EOF
test_expect_success 'short options' '
- test-parse-options -s123 -b -i 1729 -m 16k -b -vv -n -F my.file \
+ test-tool parse-options -s123 -b -i 1729 -m 16k -b -vv -n -F my.file \
>output 2>output.err &&
test_cmp expect output &&
test_must_be_empty output.err
EOF
test_expect_success 'long options' '
- test-parse-options --boolean --integer 1729 --magnitude 16k \
+ test-tool parse-options --boolean --integer 1729 --magnitude 16k \
--boolean --string2=321 --verbose --verbose --no-dry-run \
--abbrev=10 --file fi.le --obsolete \
>output 2>output.err &&
'
test_expect_success 'missing required value' '
- test_expect_code 129 test-parse-options -s &&
- test_expect_code 129 test-parse-options --string &&
- test_expect_code 129 test-parse-options --file
+ test_expect_code 129 test-tool parse-options -s &&
+ test_expect_code 129 test-tool parse-options --string &&
+ test_expect_code 129 test-tool parse-options --file
'
cat >expect <<\EOF
EOF
test_expect_success 'intermingled arguments' '
- test-parse-options a1 --string 123 b1 --boolean -j 13 -- --boolean \
+ test-tool parse-options a1 --string 123 b1 --boolean -j 13 -- --boolean \
>output 2>output.err &&
test_must_be_empty output.err &&
test_cmp expect output
EOF
test_expect_success 'unambiguously abbreviated option' '
- test-parse-options --int 2 --boolean --no-bo >output 2>output.err &&
+ test-tool parse-options --int 2 --boolean --no-bo >output 2>output.err &&
test_must_be_empty output.err &&
test_cmp expect output
'
test_expect_success 'unambiguously abbreviated option with "="' '
- test-parse-options --expect="integer: 2" --int=2
+ test-tool parse-options --expect="integer: 2" --int=2
'
test_expect_success 'ambiguously abbreviated option' '
- test_expect_code 129 test-parse-options --strin 123
+ test_expect_code 129 test-tool parse-options --strin 123
'
test_expect_success 'non ambiguous option (after two options it abbreviates)' '
- test-parse-options --expect="string: 123" --st 123
+ test-tool parse-options --expect="string: 123" --st 123
'
cat >typo.err <<\EOF
EOF
test_expect_success 'detect possible typos' '
- test_must_fail test-parse-options -boolean >output 2>output.err &&
+ test_must_fail test-tool parse-options -boolean >output 2>output.err &&
test_must_be_empty output &&
test_cmp typo.err output.err
'
EOF
test_expect_success 'detect possible typos' '
- test_must_fail test-parse-options -ambiguous >output 2>output.err &&
+ test_must_fail test-tool parse-options -ambiguous >output 2>output.err &&
test_must_be_empty output &&
test_cmp typo.err output.err
'
test_expect_success 'keep some options as arguments' '
- test-parse-options --expect="arg 00: --quux" --quux
+ test-tool parse-options --expect="arg 00: --quux" --quux
'
cat >expect <<\EOF
EOF
test_expect_success 'OPT_DATE() works' '
- test-parse-options -t "1970-01-01 00:00:01 +0000" \
+ test-tool parse-options -t "1970-01-01 00:00:01 +0000" \
foo -q >output 2>output.err &&
test_must_be_empty output.err &&
test_cmp expect output
EOF
test_expect_success 'OPT_CALLBACK() and OPT_BIT() work' '
- test-parse-options --length=four -b -4 >output 2>output.err &&
+ test-tool parse-options --length=four -b -4 >output 2>output.err &&
test_must_be_empty output.err &&
test_cmp expect output
'
test_expect_success 'OPT_CALLBACK() and callback errors work' '
- test_must_fail test-parse-options --no-length >output 2>output.err &&
+ test_must_fail test-tool parse-options --no-length >output 2>output.err &&
test_must_be_empty output &&
test_must_be_empty output.err
'
EOF
test_expect_success 'OPT_BIT() and OPT_SET_INT() work' '
- test-parse-options --set23 -bbbbb --no-or4 >output 2>output.err &&
+ test-tool parse-options --set23 -bbbbb --no-or4 >output 2>output.err &&
test_must_be_empty output.err &&
test_cmp expect output
'
test_expect_success 'OPT_NEGBIT() and OPT_SET_INT() work' '
- test-parse-options --set23 -bbbbb --neg-or4 >output 2>output.err &&
+ test-tool parse-options --set23 -bbbbb --neg-or4 >output 2>output.err &&
test_must_be_empty output.err &&
test_cmp expect output
'
test_expect_success 'OPT_BIT() works' '
- test-parse-options --expect="boolean: 6" -bb --or4
+ test-tool parse-options --expect="boolean: 6" -bb --or4
'
test_expect_success 'OPT_NEGBIT() works' '
- test-parse-options --expect="boolean: 6" -bb --no-neg-or4
+ test-tool parse-options --expect="boolean: 6" -bb --no-neg-or4
'
test_expect_success 'OPT_COUNTUP() with PARSE_OPT_NODASH works' '
- test-parse-options --expect="boolean: 6" + + + + + +
+ test-tool parse-options --expect="boolean: 6" + + + + + +
'
test_expect_success 'OPT_NUMBER_CALLBACK() works' '
- test-parse-options --expect="integer: 12345" -12345
+ test-tool parse-options --expect="integer: 12345" -12345
'
cat >expect <<\EOF
EOF
test_expect_success 'negation of OPT_NONEG flags is not ambiguous' '
- test-parse-options --no-ambig >output 2>output.err &&
+ test-tool parse-options --no-ambig >output 2>output.err &&
test_must_be_empty output.err &&
test_cmp expect output
'
list: baz
EOF
test_expect_success '--list keeps list of strings' '
- test-parse-options --list foo --list=bar --list=baz >output &&
+ test-tool parse-options --list foo --list=bar --list=baz >output &&
test_cmp expect output
'
test_expect_success '--no-list resets list' '
- test-parse-options --list=other --list=irrelevant --list=options \
+ test-tool parse-options --list=other --list=irrelevant --list=options \
--no-list --list=foo --list=bar --list=baz >output &&
test_cmp expect output
'
test_expect_success 'multiple quiet levels' '
- test-parse-options --expect="quiet: 3" -q -q -q
+ test-tool parse-options --expect="quiet: 3" -q -q -q
'
test_expect_success 'multiple verbose levels' '
- test-parse-options --expect="verbose: 3" -v -v -v
+ test-tool parse-options --expect="verbose: 3" -v -v -v
'
test_expect_success '--no-quiet sets --quiet to 0' '
- test-parse-options --expect="quiet: 0" --no-quiet
+ test-tool parse-options --expect="quiet: 0" --no-quiet
'
test_expect_success '--no-quiet resets multiple -q to 0' '
- test-parse-options --expect="quiet: 0" -q -q -q --no-quiet
+ test-tool parse-options --expect="quiet: 0" -q -q -q --no-quiet
'
test_expect_success '--no-verbose sets verbose to 0' '
- test-parse-options --expect="verbose: 0" --no-verbose
+ test-tool parse-options --expect="verbose: 0" --no-verbose
'
test_expect_success '--no-verbose resets multiple verbose to 0' '
- test-parse-options --expect="verbose: 0" -v -v -v --no-verbose
+ test-tool parse-options --expect="verbose: 0" -v -v -v --no-verbose
'
test_done
--- /dev/null
+#!/bin/sh
+
+test_description='Windows named pipes'
+
+. ./test-lib.sh
+
+test_expect_success MINGW 'o_append write to named pipe' '
+ GIT_TRACE="$(pwd)/expect" git status >/dev/null 2>&1 &&
+ { test-tool windows-named-pipe t0051 >actual 2>&1 & } &&
+ pid=$! &&
+ sleep 1 &&
+ GIT_TRACE=//./pipe/t0051 git status >/dev/null 2>warning &&
+ wait $pid &&
+ test_cmp expect actual
+'
+
+test_done
cat hello-script
EOF
-test_expect_success 'start_command reports ENOENT' '
+test_expect_success 'start_command reports ENOENT (slash)' '
test-tool run-command start-command-ENOENT ./does-not-exist
'
+test_expect_success 'start_command reports ENOENT (no slash)' '
+ test-tool run-command start-command-ENOENT does-not-exist
+'
+
test_expect_success 'run_command can run a command' '
cat hello-script >hello.sh &&
chmod +x hello.sh &&
test_must_be_empty err
'
+test_expect_success 'run_command is restricted to PATH' '
+ write_script should-not-run <<-\EOF &&
+ echo yikes
+ EOF
+ test_must_fail test-tool run-command run-command should-not-run
+'
+
test_expect_success !MINGW 'run_command can run a script without a #! line' '
cat >hello <<-\EOF &&
cat hello-script
test_description='basic tests for the SHA1 array implementation'
. ./test-lib.sh
-echo20 () {
+echoid () {
prefix="${1:+$1 }"
shift
while test $# -gt 0
do
- echo "$prefix$1$1$1$1$1$1$1$1$1$1$1$1$1$1$1$1$1$1$1$1"
+ echo "$prefix$ZERO_OID" | sed -e "s/00/$1/g"
shift
done
}
test_expect_success 'ordered enumeration' '
- echo20 "" 44 55 88 aa >expect &&
+ echoid "" 44 55 88 aa >expect &&
{
- echo20 append 88 44 aa 55 &&
+ echoid append 88 44 aa 55 &&
echo for_each_unique
} | test-tool sha1-array >actual &&
test_cmp expect actual
'
test_expect_success 'ordered enumeration with duplicate suppression' '
- echo20 "" 44 55 88 aa >expect &&
+ echoid "" 44 55 88 aa >expect &&
{
- echo20 append 88 44 aa 55 &&
- echo20 append 88 44 aa 55 &&
+ echoid append 88 44 aa 55 &&
+ echoid append 88 44 aa 55 &&
echo for_each_unique
} | test-tool sha1-array >actual &&
test_cmp expect actual
test_expect_success 'lookup' '
{
- echo20 append 88 44 aa 55 &&
- echo20 lookup 55
+ echoid append 88 44 aa 55 &&
+ echoid lookup 55
} | test-tool sha1-array >actual &&
n=$(cat actual) &&
test "$n" -eq 1
test_expect_success 'lookup non-existing entry' '
{
- echo20 append 88 44 aa 55 &&
- echo20 lookup 33
+ echoid append 88 44 aa 55 &&
+ echoid lookup 33
} | test-tool sha1-array >actual &&
n=$(cat actual) &&
test "$n" -lt 0
test_expect_success 'lookup with duplicates' '
{
- echo20 append 88 44 aa 55 &&
- echo20 append 88 44 aa 55 &&
- echo20 lookup 55
+ echoid append 88 44 aa 55 &&
+ echoid append 88 44 aa 55 &&
+ echoid lookup 55
} | test-tool sha1-array >actual &&
n=$(cat actual) &&
test "$n" -ge 2 &&
test_expect_success 'lookup non-existing entry with duplicates' '
{
- echo20 append 88 44 aa 55 &&
- echo20 append 88 44 aa 55 &&
- echo20 lookup 66
+ echoid append 88 44 aa 55 &&
+ echoid append 88 44 aa 55 &&
+ echoid lookup 66
} | test-tool sha1-array >actual &&
n=$(cat actual) &&
test "$n" -lt 0
'
test_expect_success 'lookup with almost duplicate values' '
+ # n-1 5s
+ root=$(echoid "" 55) &&
+ root=${root%5} &&
{
- echo "append 5555555555555555555555555555555555555555" &&
- echo "append 555555555555555555555555555555555555555f" &&
- echo20 lookup 55
+ id1="${root}5" &&
+ id2="${root}f" &&
+ echo "append $id1" &&
+ echo "append $id2" &&
+ echoid lookup 55
} | test-tool sha1-array >actual &&
n=$(cat actual) &&
test "$n" -eq 0
test_expect_success 'lookup with single duplicate value' '
{
- echo20 append 55 55 &&
- echo20 lookup 55
+ echoid append 55 55 &&
+ echoid lookup 55
} | test-tool sha1-array >actual &&
n=$(cat actual) &&
test "$n" -ge 0 &&
test_cache_tree
'
+test_expect_success PERL 'commit -p with shrinking cache-tree' '
+ mkdir -p deep/subdir &&
+ echo content >deep/subdir/file &&
+ git add deep &&
+ git commit -m add &&
+ git rm -r deep &&
+
+ before=$(wc -c <.git/index) &&
+ git commit -m delete -p &&
+ after=$(wc -c <.git/index) &&
+
+ # double check that the index shrank
+ test $before -gt $after &&
+
+ # and that our index was not corrupted
+ git fsck
+'
+
test_expect_success 'commit in child dir has cache-tree' '
mkdir dir &&
>dir/child.t &&
'
test_expect_success 'switching trees does not invalidate shared index' '
- git update-index --split-index &&
- >split &&
- git add split &&
- test-tool dump-split-index .git/index | grep -v ^own >before &&
- git commit -m "as-is" &&
- test-tool dump-split-index .git/index | grep -v ^own >after &&
- test_cmp before after
+ (
+ sane_unset GIT_TEST_SPLIT_INDEX &&
+ git update-index --split-index &&
+ >split &&
+ git add split &&
+ test-tool dump-split-index .git/index | grep -v ^own >before &&
+ git commit -m "as-is" &&
+ test-tool dump-split-index .git/index | grep -v ^own >after &&
+ test_cmp before after
+ )
'
test_done
git verify-pack --verbose "$IDX" | grep "$HASH"
'
+test_expect_success 'fetching of missing objects works with ref-in-want enabled' '
+ # ref-in-want requires protocol version 2
+ git -C server config protocol.version 2 &&
+ git -C server config uploadpack.allowrefinwant 1 &&
+ git -C repo config protocol.version 2 &&
+
+ rm -rf repo/.git/objects/* &&
+ rm -f trace &&
+ GIT_TRACE_PACKET="$(pwd)/trace" git -C repo cat-file -p "$HASH" &&
+ grep "git< fetch=.*ref-in-want" trace
+'
+
+test_expect_success 'fetching of missing blobs works' '
+ rm -rf server repo &&
+ test_create_repo server &&
+ test_commit -C server foo &&
+ git -C server repack -a -d --write-bitmap-index &&
+
+ git clone "file://$(pwd)/server" repo &&
+ git hash-object repo/foo.t >blobhash &&
+ rm -rf repo/.git/objects/* &&
+
+ git -C server config uploadpack.allowanysha1inwant 1 &&
+ git -C server config uploadpack.allowfilter 1 &&
+ git -C repo config core.repositoryformatversion 1 &&
+ git -C repo config extensions.partialclone "origin" &&
+
+ git -C repo cat-file -p $(cat blobhash)
+'
+
+test_expect_success 'fetching of missing trees does not fetch blobs' '
+ rm -rf server repo &&
+ test_create_repo server &&
+ test_commit -C server foo &&
+ git -C server repack -a -d --write-bitmap-index &&
+
+ git clone "file://$(pwd)/server" repo &&
+ git -C repo rev-parse foo^{tree} >treehash &&
+ git hash-object repo/foo.t >blobhash &&
+ rm -rf repo/.git/objects/* &&
+
+ git -C server config uploadpack.allowanysha1inwant 1 &&
+ git -C server config uploadpack.allowfilter 1 &&
+ git -C repo config core.repositoryformatversion 1 &&
+ git -C repo config extensions.partialclone "origin" &&
+ git -C repo cat-file -p $(cat treehash) &&
+
+ # Ensure that the tree, but not the blob, is fetched
+ git -C repo rev-list --objects --missing=print $(cat treehash) >objects &&
+ grep "^$(cat treehash)" objects &&
+ grep "^[?]$(cat blobhash)" objects
+'
+
test_expect_success 'rev-list stops traversal at missing and promised commit' '
rm -rf repo &&
test_create_repo repo &&
git -C repo config core.repositoryformatversion 1 &&
git -C repo config extensions.partialclone "arbitrary string" &&
- git -C repo rev-list --exclude-promisor-objects --objects bar >out &&
+ GIT_TEST_COMMIT_GRAPH=0 git -C repo rev-list --exclude-promisor-objects --objects bar >out &&
grep $(git -C repo rev-parse bar) out &&
! grep $FOO out
'
+test_expect_success 'missing tree objects with --missing=allow-promisor and --exclude-promisor-objects' '
+ rm -rf repo &&
+ test_create_repo repo &&
+ test_commit -C repo foo &&
+ test_commit -C repo bar &&
+ test_commit -C repo baz &&
+
+ promise_and_delete $(git -C repo rev-parse bar^{tree}) &&
+ promise_and_delete $(git -C repo rev-parse foo^{tree}) &&
+
+ git -C repo config core.repositoryformatversion 1 &&
+ git -C repo config extensions.partialclone "arbitrary string" &&
+
+ git -C repo rev-list --missing=allow-promisor --objects HEAD >objs 2>rev_list_err &&
+ test_must_be_empty rev_list_err &&
+ # 3 commits, 3 blobs, and 1 tree
+ test_line_count = 7 objs &&
+
+ # Do the same for --exclude-promisor-objects, but with all trees gone.
+ promise_and_delete $(git -C repo rev-parse baz^{tree}) &&
+ git -C repo rev-list --exclude-promisor-objects --objects HEAD >objs 2>rev_list_err &&
+ test_must_be_empty rev_list_err &&
+ # 3 commits, no blobs or trees
+ test_line_count = 3 objs
+'
+
+test_expect_success 'missing non-root tree object and rev-list' '
+ rm -rf repo &&
+ test_create_repo repo &&
+ mkdir repo/dir &&
+ echo foo >repo/dir/foo &&
+ git -C repo add dir/foo &&
+ git -C repo commit -m "commit dir/foo" &&
+
+ promise_and_delete $(git -C repo rev-parse HEAD:dir) &&
+
+ git -C repo config core.repositoryformatversion 1 &&
+ git -C repo config extensions.partialclone "arbitrary string" &&
+
+ git -C repo rev-list --missing=allow-any --objects HEAD >objs 2>rev_list_err &&
+ test_must_be_empty rev_list_err &&
+ # 1 commit and 1 tree
+ test_line_count = 2 objs
+'
+
test_expect_success 'rev-list stops traversal at missing and promised tree' '
rm -rf repo &&
test_create_repo repo &&
test_cmp expect actual
'
+test_oid_init
+
tree_sha1=$(git write-tree)
-tree_size=33
+tree_size=$(($(test_oid rawsz) + 13))
tree_pretty_content="100644 blob $hello_sha1 hello"
run_tests 'tree' $tree_sha1 $tree_size "" "$tree_pretty_content"
commit_message="Initial commit"
commit_sha1=$(echo_without_newline "$commit_message" | git commit-tree $tree_sha1)
-commit_size=177
+commit_size=$(($(test_oid hexsz) + 137))
commit_content="tree $tree_sha1
author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> 0000000000 +0000
committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 0000000000 +0000
test "0000000000000000000000000000000000000042 missing
0000000000000000000000000000000000000084 missing" = \
"$( ( echo 0000000000000000000000000000000000000042;
- echo_without_newline 0000000000000000000000000000000000000084; ) \
- | git cat-file --batch-check)"
+ echo_without_newline 0000000000000000000000000000000000000084; ) |
+ git cat-file --batch-check)"
'
test_expect_success "--batch for an existent and a non-existent hash" '
$tag_content
0000000000000000000000000000000000000000 missing" = \
"$( ( echo $tag_sha1;
- echo_without_newline 0000000000000000000000000000000000000000; ) \
- | git cat-file --batch)"
+ echo_without_newline 0000000000000000000000000000000000000000; ) |
+ git cat-file --batch)"
'
test_expect_success "--batch-check for an empty line" '
test_path_is_file c
'
+test_expect_success 'checkout -b checkout.optimizeNewBranch interaction' '
+ cp .git/info/sparse-checkout .git/info/sparse-checkout.bak &&
+ test_when_finished "
+ mv -f .git/info/sparse-checkout.bak .git/info/sparse-checkout
+ git checkout master
+ " &&
+ echo "/b" >>.git/info/sparse-checkout &&
+ test "$(git ls-files -t b)" = "S b" &&
+ git -c checkout.optimizeNewBranch=true checkout -b fast &&
+ test "$(git ls-files -t b)" = "S b" &&
+ git checkout -b slow &&
+ test "$(git ls-files -t b)" = "H b"
+'
+
test_expect_success 'merge feature branch into sparse checkout of master' '
git merge feature &&
test_path_is_file a &&
test "$(cat b)" = "modified"
'
+test_expect_success 'in partial clone, sparse checkout only fetches needed blobs' '
+ test_create_repo server &&
+ git clone "file://$(pwd)/server" client &&
+
+ test_config -C server uploadpack.allowfilter 1 &&
+ test_config -C server uploadpack.allowanysha1inwant 1 &&
+ echo a >server/a &&
+ echo bb >server/b &&
+ mkdir server/c &&
+ echo ccc >server/c/c &&
+ git -C server add a b c/c &&
+ git -C server commit -m message &&
+
+ test_config -C client core.sparsecheckout 1 &&
+ test_config -C client extensions.partialclone origin &&
+ echo "!/*" >client/.git/info/sparse-checkout &&
+ echo "/a" >>client/.git/info/sparse-checkout &&
+ git -C client fetch --filter=blob:none origin &&
+ git -C client checkout FETCH_HEAD &&
+
+ git -C client rev-list HEAD \
+ --quiet --objects --missing=print >unsorted_actual &&
+ (
+ printf "?" &&
+ git hash-object server/b &&
+ printf "?" &&
+ git hash-object server/c/c
+ ) >unsorted_expect &&
+ sort unsorted_actual >actual &&
+ sort unsorted_expect >expect &&
+ test_cmp expect actual
+'
+
test_done
test_expect_success 'value continued on next line' '
git config --list > result &&
- test_cmp result expect
+ test_cmp expect result
'
cat > .git/config <<\EOF
cat >expect <<-EOF &&
file:$INCLUDE_DIR/stdin.include include
EOF
- echo "[include]path=\"$INCLUDE_DIR\"/stdin.include" \
- | git config --show-origin --includes --file - user.stdin >output &&
+ echo "[include]path=\"$INCLUDE_DIR\"/stdin.include" |
+ git config --show-origin --includes --file - user.stdin >output &&
+
test_cmp expect output
'
Qkey = b
EOF
git config --replace-all abc.key b &&
- test_cmp .git/config expect
+ test_cmp expect .git/config
'
test_done
check() {
echo "$2" >expected
git config --get "$1" >actual 2>&1
- test_cmp actual expected
+ test_cmp expected actual
}
# 'check section.key regex value' verifies that the entry for
check_regex() {
echo "$3" >expected
git config --get "$1" "$2" >actual 2>&1
- test_cmp actual expected
+ test_cmp expected actual
}
test_expect_success 'modify same key' '
git update-ref $m $D
cat >.git/logs/$m <<EOF
-0000000000000000000000000000000000000000 $C $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150320 -0500
+$Z $C $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150320 -0500
$C $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150350 -0500
$A $B $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150380 -0500
$F $Z $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150680 -0500
test_when_finished "rm -f o e" &&
git rev-parse --verify master@{2005-05-25} >o 2>e &&
test $C = $(cat o) &&
- echo test "warning: Log for '\''master'\'' only goes back to $ed." = "$(cat e)"
+ test "warning: Log for '\''master'\'' only goes back to $ed." = "$(cat e)"
'
test_expect_success 'Query "master@{May 26 2005 23:31:59}" (1 second before history)' '
test_when_finished "rm -f o e" &&
test_cmp expect actual
'
+test_expect_success 'stdin update symref works flag --no-deref' '
+ git symbolic-ref TESTSYMREFONE $b &&
+ git symbolic-ref TESTSYMREFTWO $b &&
+ cat >stdin <<-EOF &&
+ update TESTSYMREFONE $a $b
+ update TESTSYMREFTWO $a $b
+ EOF
+ git update-ref --no-deref --stdin <stdin &&
+ git rev-parse TESTSYMREFONE TESTSYMREFTWO >expect &&
+ git rev-parse $a $a >actual &&
+ test_cmp expect actual &&
+ git rev-parse $m~1 >expect &&
+ git rev-parse $b >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'stdin delete symref works flag --no-deref' '
+ git symbolic-ref TESTSYMREFONE $b &&
+ git symbolic-ref TESTSYMREFTWO $b &&
+ cat >stdin <<-EOF &&
+ delete TESTSYMREFONE $b
+ delete TESTSYMREFTWO $b
+ EOF
+ git update-ref --no-deref --stdin <stdin &&
+ test_must_fail git rev-parse --verify -q TESTSYMREFONE &&
+ test_must_fail git rev-parse --verify -q TESTSYMREFTWO &&
+ git rev-parse $m~1 >expect &&
+ git rev-parse $b >actual &&
+ test_cmp expect actual
+'
+
test_expect_success 'stdin delete ref works with right old value' '
echo "delete $b $m~1" >stdin &&
git update-ref --stdin <stdin &&
{
# Note: the following command is intentionally run in the
# background. We increase the timeout so that `update-ref`
- # attempts to acquire the `packed-refs` lock for longer than
- # it takes for us to do the check then delete it:
- git -c core.packedrefstimeout=3000 update-ref -d $prefix/foo &
+ # attempts to acquire the `packed-refs` lock for much longer
+ # than it takes for us to do the check then delete it:
+ git -c core.packedrefstimeout=30000 update-ref -d $prefix/foo &
} &&
pid2=$! &&
# Give update-ref plenty of time to get to the point where it tries
'
test_expect_success 'for_each_ref() is sorted' '
- $RUN for-each-ref refs/heads/ | cut -c 42- >actual &&
+ $RUN for-each-ref refs/heads/ | cut -d" " -f 2- >actual &&
sort actual > expected &&
test_cmp expected actual
'
'
test_expect_success 'for_each_reflog()' '
- $RUN for-each-reflog | sort -k2 | cut -c 42- >actual &&
+ $RUN for-each-reflog | sort -k2 | cut -d" " -f 2- >actual &&
cat >expected <<-\EOF &&
HEAD 0x1
refs/heads/master 0x0
'
test_expect_success 'for_each_ref(refs/heads/)' '
- $RUN for-each-ref refs/heads/ | cut -c 42- >actual &&
+ $RUN for-each-ref refs/heads/ | cut -d" " -f 2- >actual &&
cat >expected <<-\EOF &&
master 0x0
new-master 0x0
'
test_expect_success 'for_each_ref() is sorted' '
- $RUN for-each-ref refs/heads/ | cut -c 42- >actual &&
+ $RUN for-each-ref refs/heads/ | cut -d" " -f 2- >actual &&
sort actual > expected &&
test_cmp expected actual
'
'
test_expect_success 'for_each_reflog()' '
- $RUN for-each-reflog | sort | cut -c 42- >actual &&
+ $RUN for-each-reflog | sort | cut -d" " -f 2- >actual &&
cat >expected <<-\EOF &&
HEAD 0x1
refs/heads/master 0x0
mkdir -p .git/worktrees/wt/logs/refs/bisect &&
echo $ZERO_OID > .git/worktrees/wt/logs/refs/bisect/wt-random &&
- $RWT for-each-reflog | cut -c 42- | sort >actual &&
+ $RWT for-each-reflog | cut -d" " -f 2- | sort >actual &&
cat >expected <<-\EOF &&
HEAD 0x1
PSEUDO-WT 0x0
EOF
test_cmp expected actual &&
- $RMAIN for-each-reflog | cut -c 42- | sort >actual &&
+ $RMAIN for-each-reflog | cut -d" " -f 2- | sort >actual &&
cat >expected <<-\EOF &&
HEAD 0x1
PSEUDO-MAIN 0x0
test_commit -C sub test_commit &&
git -C super submodule add ../sub dir/sub &&
echo $(pwd)/super >expect &&
+ git -C super/dir/sub rev-parse --show-superproject-working-tree >out &&
+ test_cmp expect out &&
+
+ test_commit -C super submodule_add &&
+ git -C super checkout -b branch1 &&
+ git -C super/dir/sub checkout -b branch1 &&
+ test_commit -C super/dir/sub branch1_commit &&
+ git -C super add dir/sub &&
+ test_commit -C super branch1_commit &&
+ git -C super checkout -b branch2 master &&
+ git -C super/dir/sub checkout -b branch2 master &&
+ test_commit -C super/dir/sub branch2_commit &&
+ git -C super add dir/sub &&
+ test_commit -C super branch2_commit &&
+ test_must_fail git -C super merge branch1 &&
+
git -C super/dir/sub rev-parse --show-superproject-working-tree >out &&
test_cmp expect out
'
# We need total control of index splitting here
sane_unset GIT_TEST_SPLIT_INDEX
-sane_unset GIT_FSMONITOR_TEST
+
+# Testing a hard coded SHA against an index with an extension
+# that can vary from run to run is problematic so we disable
+# those extensions.
+sane_unset GIT_TEST_FSMONITOR
+sane_unset GIT_TEST_INDEX_THREADS
+
+# Create a file named as $1 with content read from stdin.
+# Set the file's mtime to a few seconds in the past to avoid racy situations.
+create_non_racy_file () {
+ cat >"$1" &&
+ test-tool chmtime =-5 "$1"
+}
test_expect_success 'enable split index' '
git config splitIndex.maxPercentChange 100 &&
indexversion=$(test-tool index-version <.git/index) &&
if test "$indexversion" = "4"
then
- own=432ef4b63f32193984f339431fd50ca796493569
- base=508851a7f0dfa8691e9f69c7f055865389012491
+ own=3527df833c6c100d3d1d921a9a782d62a8be4b58
+ base=746f7ab2ed44fb839efdfbffcf399d0b113fb4cb
else
- own=8299b0bcd1ac364e5f1d7768efb62fa2da79a339
- base=39d890139ee5356c7ef572216cebcd27aa41f9df
+ own=5e9b60117ece18da410ddecc8b8d43766a0e4204
+ base=4370042739b31cd17a5c5cd6043a77c9a00df113
fi &&
cat >expect <<-EOF &&
own $own
'
test_expect_success 'add one file' '
- : >one &&
+ create_non_racy_file one &&
git update-index --add one &&
git ls-files --stage >ls-files.actual &&
cat >ls-files.expect <<-EOF &&
EOF
test_cmp ls-files.expect ls-files.actual &&
- BASE=$(test-tool dump-split-index .git/index | grep "^own" | sed "s/own/base/") &&
+ BASE=$(test-tool dump-split-index .git/index | sed -n "s/^own/base/p") &&
test-tool dump-split-index .git/index | sed "/^own/d" >actual &&
cat >expect <<-EOF &&
not a split index
'
test_expect_success 'modify original file, base index untouched' '
- echo modified >one &&
+ echo modified | create_non_racy_file one &&
git update-index one &&
git ls-files --stage >ls-files.actual &&
cat >ls-files.expect <<-EOF &&
'
test_expect_success 'add another file, which stays index' '
- : >two &&
+ create_non_racy_file two &&
git update-index --add two &&
git ls-files --stage >ls-files.actual &&
cat >ls-files.expect <<-EOF &&
'
test_expect_success 'add original file back' '
- : >one &&
+ create_non_racy_file one &&
git update-index --add one &&
git ls-files --stage >ls-files.actual &&
cat >ls-files.expect <<-EOF &&
'
test_expect_success 'add new file' '
- : >two &&
+ create_non_racy_file two &&
git update-index --add two &&
git ls-files --stage >actual &&
cat >expect <<-EOF &&
test_expect_success 'set core.splitIndex config variable to true' '
git config core.splitIndex true &&
- : >three &&
+ create_non_racy_file three &&
git update-index --add three &&
git ls-files --stage >ls-files.actual &&
cat >ls-files.expect <<-EOF &&
test_cmp expect actual
'
-test_expect_success 'set core.splitIndex config variable to true' '
+test_expect_success 'set core.splitIndex config variable back to true' '
git config core.splitIndex true &&
- : >three &&
+ create_non_racy_file three &&
git update-index --add three &&
BASE=$(test-tool dump-split-index .git/index | grep "^base") &&
test-tool dump-split-index .git/index | sed "/^own/d" >actual &&
deletions:
EOF
test_cmp expect actual &&
- : >four &&
+ create_non_racy_file four &&
git update-index --add four &&
test-tool dump-split-index .git/index | sed "/^own/d" >actual &&
cat >expect <<-EOF &&
test_expect_success 'check behavior with splitIndex.maxPercentChange unset' '
git config --unset splitIndex.maxPercentChange &&
- : >five &&
+ create_non_racy_file five &&
git update-index --add five &&
BASE=$(test-tool dump-split-index .git/index | grep "^base") &&
test-tool dump-split-index .git/index | sed "/^own/d" >actual &&
deletions:
EOF
test_cmp expect actual &&
- : >six &&
+ create_non_racy_file six &&
git update-index --add six &&
test-tool dump-split-index .git/index | sed "/^own/d" >actual &&
cat >expect <<-EOF &&
test_expect_success 'check splitIndex.maxPercentChange set to 0' '
git config splitIndex.maxPercentChange 0 &&
- : >seven &&
+ create_non_racy_file seven &&
git update-index --add seven &&
BASE=$(test-tool dump-split-index .git/index | grep "^base") &&
test-tool dump-split-index .git/index | sed "/^own/d" >actual &&
deletions:
EOF
test_cmp expect actual &&
- : >eight &&
+ create_non_racy_file eight &&
git update-index --add eight &&
BASE=$(test-tool dump-split-index .git/index | grep "^base") &&
test-tool dump-split-index .git/index | sed "/^own/d" >actual &&
'
test_expect_success 'shared index files expire after 2 weeks by default' '
- : >ten &&
+ create_non_racy_file ten &&
git update-index --add ten &&
test $(ls .git/sharedindex.* | wc -l) -gt 2 &&
just_under_2_weeks_ago=$((5-14*86400)) &&
test-tool chmtime =$just_under_2_weeks_ago .git/sharedindex.* &&
- : >eleven &&
+ create_non_racy_file eleven &&
git update-index --add eleven &&
test $(ls .git/sharedindex.* | wc -l) -gt 2 &&
just_over_2_weeks_ago=$((-1-14*86400)) &&
test-tool chmtime =$just_over_2_weeks_ago .git/sharedindex.* &&
- : >twelve &&
+ create_non_racy_file twelve &&
git update-index --add twelve &&
test $(ls .git/sharedindex.* | wc -l) -le 2
'
test_expect_success 'check splitIndex.sharedIndexExpire set to 16 days' '
git config splitIndex.sharedIndexExpire "16.days.ago" &&
test-tool chmtime =$just_over_2_weeks_ago .git/sharedindex.* &&
- : >thirteen &&
+ create_non_racy_file thirteen &&
git update-index --add thirteen &&
test $(ls .git/sharedindex.* | wc -l) -gt 2 &&
just_over_16_days_ago=$((-1-16*86400)) &&
test-tool chmtime =$just_over_16_days_ago .git/sharedindex.* &&
- : >fourteen &&
+ create_non_racy_file fourteen &&
git update-index --add fourteen &&
test $(ls .git/sharedindex.* | wc -l) -le 2
'
git config splitIndex.sharedIndexExpire never &&
just_10_years_ago=$((-365*10*86400)) &&
test-tool chmtime =$just_10_years_ago .git/sharedindex.* &&
- : >fifteen &&
+ create_non_racy_file fifteen &&
git update-index --add fifteen &&
test $(ls .git/sharedindex.* | wc -l) -gt 2 &&
git config splitIndex.sharedIndexExpire now &&
just_1_second_ago=-1 &&
test-tool chmtime =$just_1_second_ago .git/sharedindex.* &&
- : >sixteen &&
+ create_non_racy_file sixteen &&
git update-index --add sixteen &&
test $(ls .git/sharedindex.* | wc -l) -le 2
'
# Create one new shared index file
git config core.sharedrepository "$mode" &&
git config core.splitIndex true &&
- : >one &&
+ create_non_racy_file one &&
git update-index --add one &&
echo "$modebits" >expect &&
test_modebits .git/index >actual &&
--- /dev/null
+#!/bin/sh
+
+# This test can give false success if your machine is sufficiently
+# slow or all trials happened to happen on second boundaries.
+
+test_description='racy split index'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+ # Only split the index when the test explicitly says so.
+ sane_unset GIT_TEST_SPLIT_INDEX &&
+ git config splitIndex.maxPercentChange 100 &&
+
+ echo "cached content" >racy-file &&
+ git add racy-file &&
+ git commit -m initial &&
+
+ echo something >other-file &&
+ # No raciness with this file.
+ test-tool chmtime =-20 other-file &&
+
+ echo "+cached content" >expect
+'
+
+check_cached_diff () {
+ git diff-index --patch --cached $EMPTY_TREE racy-file >diff &&
+ tail -1 diff >actual &&
+ test_cmp expect actual
+}
+
+trials="0 1 2 3 4"
+for trial in $trials
+do
+ test_expect_success "split the index while adding a racily clean file #$trial" '
+ rm -f .git/index .git/sharedindex.* &&
+
+ # The next three commands must be run within the same
+ # second (so both writes to racy-file result in the same
+ # mtime) to create the interesting racy situation.
+ echo "cached content" >racy-file &&
+
+ # Update and split the index. The cache entry of
+ # racy-file will be stored only in the shared index.
+ git update-index --split-index --add racy-file &&
+
+ # File size must stay the same.
+ echo "dirty worktree" >racy-file &&
+
+ # Subsequent git commands should notice that racy-file
+ # and the split index have the same mtime, and check
+ # the content of the file to see if it is actually
+ # clean.
+ check_cached_diff
+ '
+done
+
+for trial in $trials
+do
+ test_expect_success "add a racily clean file to an already split index #$trial" '
+ rm -f .git/index .git/sharedindex.* &&
+
+ git update-index --split-index &&
+
+ # The next three commands must be run within the same
+ # second.
+ echo "cached content" >racy-file &&
+
+ # Update the split index. The cache entry of racy-file
+ # will be stored only in the split index.
+ git update-index --add racy-file &&
+
+ # File size must stay the same.
+ echo "dirty worktree" >racy-file &&
+
+ # Subsequent git commands should notice that racy-file
+ # and the split index have the same mtime, and check
+ # the content of the file to see if it is actually
+ # clean.
+ check_cached_diff
+ '
+done
+
+for trial in $trials
+do
+ test_expect_success "split the index when the index contains a racily clean cache entry #$trial" '
+ rm -f .git/index .git/sharedindex.* &&
+
+ # The next three commands must be run within the same
+ # second.
+ echo "cached content" >racy-file &&
+
+ git update-index --add racy-file &&
+
+ # File size must stay the same.
+ echo "dirty worktree" >racy-file &&
+
+ # Now wait a bit to ensure that the split index written
+ # below will get a more recent mtime than racy-file.
+ sleep 1 &&
+
+ # Update and split the index when the index contains
+ # the racily clean cache entry of racy-file.
+ # A corresponding replacement cache entry with smudged
+ # stat data should be added to the new split index.
+ git update-index --split-index --add other-file &&
+
+ # Subsequent git commands should notice the smudged
+ # stat data in the replacement cache entry and that it
+ # doesnt match with the file the worktree.
+ check_cached_diff
+ '
+done
+
+for trial in $trials
+do
+ test_expect_success "update the split index when it contains a new racily clean cache entry #$trial" '
+ rm -f .git/index .git/sharedindex.* &&
+
+ git update-index --split-index &&
+
+ # The next three commands must be run within the same
+ # second.
+ echo "cached content" >racy-file &&
+
+ # Update the split index. The cache entry of racy-file
+ # will be stored only in the split index.
+ git update-index --add racy-file &&
+
+ # File size must stay the same.
+ echo "dirty worktree" >racy-file &&
+
+ # Now wait a bit to ensure that the split index written
+ # below will get a more recent mtime than racy-file.
+ sleep 1 &&
+
+ # Update the split index when the racily clean cache
+ # entry of racy-file is only stored in the split index.
+ # An updated cache entry with smudged stat data should
+ # be added to the new split index.
+ git update-index --add other-file &&
+
+ # Subsequent git commands should notice the smudged
+ # stat data.
+ check_cached_diff
+ '
+done
+
+for trial in $trials
+do
+ test_expect_success "update the split index when a racily clean cache entry is stored only in the shared index #$trial" '
+ rm -f .git/index .git/sharedindex.* &&
+
+ # The next three commands must be run within the same
+ # second.
+ echo "cached content" >racy-file &&
+
+ # Update and split the index. The cache entry of
+ # racy-file will be stored only in the shared index.
+ git update-index --split-index --add racy-file &&
+
+ # File size must stay the same.
+ echo "dirty worktree" >racy-file &&
+
+ # Now wait a bit to ensure that the split index written
+ # below will get a more recent mtime than racy-file.
+ sleep 1 &&
+
+ # Update the split index when the racily clean cache
+ # entry of racy-file is only stored in the shared index.
+ # A corresponding replacement cache entry with smudged
+ # stat data should be added to the new split index.
+ git update-index --add other-file &&
+
+ # Subsequent git commands should notice the smudged
+ # stat data.
+ check_cached_diff
+ '
+done
+
+for trial in $trials
+do
+ test_expect_success "update the split index after unpack trees() copied a racily clean cache entry from the shared index #$trial" '
+ rm -f .git/index .git/sharedindex.* &&
+
+ # The next three commands must be run within the same
+ # second.
+ echo "cached content" >racy-file &&
+
+ # Update and split the index. The cache entry of
+ # racy-file will be stored only in the shared index.
+ git update-index --split-index --add racy-file &&
+
+ # File size must stay the same.
+ echo "dirty worktree" >racy-file &&
+
+ # Now wait a bit to ensure that the split index written
+ # below will get a more recent mtime than racy-file.
+ sleep 1 &&
+
+ # Update the split index after unpack_trees() copied the
+ # racily clean cache entry of racy-file from the shared
+ # index. A corresponding replacement cache entry
+ # with smudged stat data should be added to the new
+ # split index.
+ git read-tree -m HEAD &&
+
+ # Subsequent git commands should notice the smudged
+ # stat data.
+ check_cached_diff
+ '
+done
+
+test_done
+++ /dev/null
-#!/bin/sh
-#
-# Copyright (c) 2005 Junio C Hamano
-#
-
-test_description='git checkout-index test.
-
-This test registers the following filesystem structure in the
-cache:
-
- path0 - a file
- path1/file1 - a file in a directory
-
-And then tries to checkout in a work tree that has the following:
-
- path0/file0 - a file in a directory
- path1 - a file
-
-The git checkout-index command should fail when attempting to checkout
-path0, finding it is occupied by a directory, and path1/file1, finding
-path1 is occupied by a non-directory. With "-f" flag, it should remove
-the conflicting paths and succeed.
-'
-. ./test-lib.sh
-
-date >path0
-mkdir path1
-date >path1/file1
-
-test_expect_success \
- 'git update-index --add various paths.' \
- 'git update-index --add path0 path1/file1'
-
-rm -fr path0 path1
-mkdir path0
-date >path0/file0
-date >path1
-
-test_expect_success \
- 'git checkout-index without -f should fail on conflicting work tree.' \
- 'test_must_fail git checkout-index -a'
-
-test_expect_success \
- 'git checkout-index with -f should succeed.' \
- 'git checkout-index -f -a'
-
-test_expect_success \
- 'git checkout-index conflicting paths.' \
- 'test -f path0 && test -d path1 && test -f path1/file1'
-
-test_expect_success SYMLINKS 'checkout-index -f twice with --prefix' '
- mkdir -p tar/get &&
- ln -s tar/get there &&
- echo first &&
- git checkout-index -a -f --prefix=there/ &&
- echo second &&
- git checkout-index -a -f --prefix=there/
-'
-
-test_done
--- /dev/null
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='git conflicts when checking files out test.'
+
+# The first test registers the following filesystem structure in the
+# cache:
+#
+# path0 - a file
+# path1/file1 - a file in a directory
+#
+# And then tries to checkout in a work tree that has the following:
+#
+# path0/file0 - a file in a directory
+# path1 - a file
+#
+# The git checkout-index command should fail when attempting to checkout
+# path0, finding it is occupied by a directory, and path1/file1, finding
+# path1 is occupied by a non-directory. With "-f" flag, it should remove
+# the conflicting paths and succeed.
+
+. ./test-lib.sh
+
+show_files() {
+ # show filesystem files, just [-dl] for type and name
+ find path? -ls |
+ sed -e 's/^[0-9]* * [0-9]* * \([-bcdl]\)[^ ]* *[0-9]* *[^ ]* *[^ ]* *[0-9]* [A-Z][a-z][a-z] [0-9][0-9] [^ ]* /fs: \1 /'
+ # what's in the cache, just mode and name
+ git ls-files --stage |
+ sed -e 's/^\([0-9]*\) [0-9a-f]* [0-3] /ca: \1 /'
+ # what's in the tree, just mode and name.
+ git ls-tree -r "$1" |
+ sed -e 's/^\([0-9]*\) [^ ]* [0-9a-f]* /tr: \1 /'
+}
+
+date >path0
+mkdir path1
+date >path1/file1
+
+test_expect_success \
+ 'git update-index --add various paths.' \
+ 'git update-index --add path0 path1/file1'
+
+rm -fr path0 path1
+mkdir path0
+date >path0/file0
+date >path1
+
+test_expect_success \
+ 'git checkout-index without -f should fail on conflicting work tree.' \
+ 'test_must_fail git checkout-index -a'
+
+test_expect_success \
+ 'git checkout-index with -f should succeed.' \
+ 'git checkout-index -f -a'
+
+test_expect_success \
+ 'git checkout-index conflicting paths.' \
+ 'test -f path0 && test -d path1 && test -f path1/file1'
+
+test_expect_success SYMLINKS 'checkout-index -f twice with --prefix' '
+ mkdir -p tar/get &&
+ ln -s tar/get there &&
+ echo first &&
+ git checkout-index -a -f --prefix=there/ &&
+ echo second &&
+ git checkout-index -a -f --prefix=there/
+'
+
+# The second test registers the following filesystem structure in the cache:
+#
+# path2/file0 - a file in a directory
+# path3/file1 - a file in a directory
+#
+# and attempts to check it out when the work tree has:
+#
+# path2/file0 - a file in a directory
+# path3 - a symlink pointing at "path2"
+#
+# Checkout cache should fail to extract path3/file1 because the leading
+# path path3 is occupied by a non-directory. With "-f" it should remove
+# the symlink path3 and create directory path3 and file path3/file1.
+
+mkdir path2
+date >path2/file0
+test_expect_success \
+ 'git update-index --add path2/file0' \
+ 'git update-index --add path2/file0'
+test_expect_success \
+ 'writing tree out with git write-tree' \
+ 'tree1=$(git write-tree)'
+test_debug 'show_files $tree1'
+
+mkdir path3
+date >path3/file1
+test_expect_success \
+ 'git update-index --add path3/file1' \
+ 'git update-index --add path3/file1'
+test_expect_success \
+ 'writing tree out with git write-tree' \
+ 'tree2=$(git write-tree)'
+test_debug 'show_files $tree2'
+
+rm -fr path3
+test_expect_success \
+ 'read previously written tree and checkout.' \
+ 'git read-tree -m $tree1 && git checkout-index -f -a'
+test_debug 'show_files $tree1'
+
+test_expect_success \
+ 'add a symlink' \
+ 'test_ln_s_add path2 path3'
+test_expect_success \
+ 'writing tree out with git write-tree' \
+ 'tree3=$(git write-tree)'
+test_debug 'show_files $tree3'
+
+# Morten says "Got that?" here.
+# Test begins.
+
+test_expect_success \
+ 'read previously written tree and checkout.' \
+ 'git read-tree $tree2 && git checkout-index -f -a'
+test_debug 'show_files $tree2'
+
+test_expect_success \
+ 'checking out conflicting path with -f' \
+ 'test ! -h path2 && test -d path2 &&
+ test ! -h path3 && test -d path3 &&
+ test ! -h path2/file0 && test -f path2/file0 &&
+ test ! -h path3/file1 && test -f path3/file1'
+
+test_done
+++ /dev/null
-#!/bin/sh
-#
-# Copyright (c) 2005 Junio C Hamano
-#
-
-test_description='git checkout-index test.
-
-This test registers the following filesystem structure in the cache:
-
- path0/file0 - a file in a directory
- path1/file1 - a file in a directory
-
-and attempts to check it out when the work tree has:
-
- path0/file0 - a file in a directory
- path1 - a symlink pointing at "path0"
-
-Checkout cache should fail to extract path1/file1 because the leading
-path path1 is occupied by a non-directory. With "-f" it should remove
-the symlink path1 and create directory path1 and file path1/file1.
-'
-. ./test-lib.sh
-
-show_files() {
- # show filesystem files, just [-dl] for type and name
- find path? -ls |
- sed -e 's/^[0-9]* * [0-9]* * \([-bcdl]\)[^ ]* *[0-9]* *[^ ]* *[^ ]* *[0-9]* [A-Z][a-z][a-z] [0-9][0-9] [^ ]* /fs: \1 /'
- # what's in the cache, just mode and name
- git ls-files --stage |
- sed -e 's/^\([0-9]*\) [0-9a-f]* [0-3] /ca: \1 /'
- # what's in the tree, just mode and name.
- git ls-tree -r "$1" |
- sed -e 's/^\([0-9]*\) [^ ]* [0-9a-f]* /tr: \1 /'
-}
-
-mkdir path0
-date >path0/file0
-test_expect_success \
- 'git update-index --add path0/file0' \
- 'git update-index --add path0/file0'
-test_expect_success \
- 'writing tree out with git write-tree' \
- 'tree1=$(git write-tree)'
-test_debug 'show_files $tree1'
-
-mkdir path1
-date >path1/file1
-test_expect_success \
- 'git update-index --add path1/file1' \
- 'git update-index --add path1/file1'
-test_expect_success \
- 'writing tree out with git write-tree' \
- 'tree2=$(git write-tree)'
-test_debug 'show_files $tree2'
-
-rm -fr path1
-test_expect_success \
- 'read previously written tree and checkout.' \
- 'git read-tree -m $tree1 && git checkout-index -f -a'
-test_debug 'show_files $tree1'
-
-test_expect_success \
- 'add a symlink' \
- 'test_ln_s_add path0 path1'
-test_expect_success \
- 'writing tree out with git write-tree' \
- 'tree3=$(git write-tree)'
-test_debug 'show_files $tree3'
-
-# Morten says "Got that?" here.
-# Test begins.
-
-test_expect_success \
- 'read previously written tree and checkout.' \
- 'git read-tree $tree2 && git checkout-index -f -a'
-test_debug 'show_files $tree2'
-
-test_expect_success \
- 'checking out conflicting path with -f' \
- 'test ! -h path0 && test -d path0 &&
- test ! -h path1 && test -d path1 &&
- test ! -h path0/file0 && test -f path0/file0 &&
- test ! -h path1/file1 && test -f path1/file1'
-
-test_done
test_cmp hook.expect goozy/hook.actual
'
+test_expect_success '"add" an existing but missing worktree' '
+ git worktree add --detach pneu &&
+ test_must_fail git worktree add --detach pneu &&
+ rm -fr pneu &&
+ test_must_fail git worktree add --detach pneu &&
+ git worktree add --force --detach pneu
+'
+
+test_expect_success '"add" an existing locked but missing worktree' '
+ git worktree add --detach gnoo &&
+ git worktree lock gnoo &&
+ test_when_finished "git worktree unlock gnoo || :" &&
+ rm -fr gnoo &&
+ test_must_fail git worktree add --detach gnoo &&
+ test_must_fail git worktree add --force --detach gnoo &&
+ git worktree add --force --force --detach gnoo
+'
+
test_done
test_cmp expected2 actual2
'
+test_expect_success 'move locked worktree (force)' '
+ test_when_finished "
+ git worktree unlock flump || :
+ git worktree remove flump || :
+ git worktree unlock ploof || :
+ git worktree remove ploof || :
+ " &&
+ git worktree add --detach flump &&
+ git worktree lock flump &&
+ test_must_fail git worktree move flump ploof" &&
+ test_must_fail git worktree move --force flump ploof" &&
+ git worktree move --force --force flump ploof
+'
+
test_expect_success 'remove main worktree' '
test_must_fail git worktree remove .
'
test_path_is_dir .git/worktrees/gone-but-locked
'
+test_expect_success 'proper error when worktree not found' '
+ for i in noodle noodle/bork
+ do
+ test_must_fail git worktree lock $i 2>err &&
+ test_i18ngrep "not a working tree" err || return 1
+ done
+'
+
+test_expect_success 'remove locked worktree (force)' '
+ git worktree add --detach gumby &&
+ test_when_finished "git worktree remove gumby || :" &&
+ git worktree lock gumby &&
+ test_when_finished "git worktree unlock gumby || :" &&
+ test_must_fail git worktree remove gumby &&
+ test_must_fail git worktree remove --force gumby &&
+ git worktree remove --force --force gumby
+'
+
+test_expect_success 'remove cleans up .git/worktrees when empty' '
+ git init moog &&
+ (
+ cd moog &&
+ test_commit bim &&
+ git worktree add --detach goom &&
+ test_path_exists .git/worktrees &&
+ git worktree remove goom &&
+ test_path_is_missing .git/worktrees
+ )
+'
+
test_done
100644 $(git hash-object dir1/file3) 0 dir1/file3
100644 $file2 0 file2
EOF
- test_cmp current expected
+ test_cmp expected current
'
test_expect_success 'update-index --update with pathspec' '
EOF
EDITOR=./editor git branch --edit-description &&
echo "New contents" >expect &&
- test_cmp EDITOR_OUTPUT expect
+ test_cmp expect EDITOR_OUTPUT
'
test_expect_success 'detect typo in branch name when using --edit-description' '
test_cmp expected actual
'
+test_expect_success 'changed commit with sm config' '
+ git range-diff --no-color --submodule=log topic...changed >actual &&
+ cat >expected <<-EOF &&
+ 1: 4de457d = 1: a4b3333 s/5/A/
+ 2: fccce22 = 2: f51d370 s/4/A/
+ 3: 147e64e ! 3: 0559556 s/11/B/
+ @@ -10,7 +10,7 @@
+ 9
+ 10
+ -11
+ -+B
+ ++BB
+ 12
+ 13
+ 14
+ 4: a63e992 ! 4: d966c5c s/12/B/
+ @@ -8,7 +8,7 @@
+ @@
+ 9
+ 10
+ - B
+ + BB
+ -12
+ +B
+ 13
+ EOF
+ test_cmp expected actual
+'
+
+test_expect_success 'no commits on one side' '
+ git commit --amend -m "new message" &&
+ git range-diff master HEAD@{1} HEAD
+'
+
test_expect_success 'changed message' '
git range-diff --no-color topic...changed-message >actual &&
sed s/Z/\ /g >expected <<-EOF &&
Z
+ Also a silly comment here!
+
- Zdiff --git a/file b/file
- Z--- a/file
- Z+++ b/file
+ Z diff --git a/file b/file
+ Z --- a/file
+ Z +++ b/file
3: 147e64e = 3: b9cb956 s/11/B/
4: a63e992 = 4: 8add5f1 s/12/B/
EOF
test_cmp expected actual
'
+test_expect_success 'dual-coloring' '
+ sed -e "s|^:||" >expect <<-\EOF &&
+ :<YELLOW>1: a4b3333 = 1: f686024 s/5/A/<RESET>
+ :<RED>2: f51d370 <RESET><YELLOW>!<RESET><GREEN> 2: 4ab067d<RESET><YELLOW> s/4/A/<RESET>
+ : <REVERSE><CYAN>@@ -2,6 +2,8 @@<RESET>
+ : <RESET>
+ : s/4/A/<RESET>
+ : <RESET>
+ : <REVERSE><GREEN>+<RESET><BOLD> Also a silly comment here!<RESET>
+ : <REVERSE><GREEN>+<RESET>
+ : diff --git a/file b/file<RESET>
+ : --- a/file<RESET>
+ : +++ b/file<RESET>
+ :<RED>3: 0559556 <RESET><YELLOW>!<RESET><GREEN> 3: b9cb956<RESET><YELLOW> s/11/B/<RESET>
+ : <REVERSE><CYAN>@@ -10,7 +10,7 @@<RESET>
+ : 9<RESET>
+ : 10<RESET>
+ : <RED> -11<RESET>
+ : <REVERSE><RED>-<RESET><FAINT;GREEN>+BB<RESET>
+ : <REVERSE><GREEN>+<RESET><BOLD;GREEN>+B<RESET>
+ : 12<RESET>
+ : 13<RESET>
+ : 14<RESET>
+ :<RED>4: d966c5c <RESET><YELLOW>!<RESET><GREEN> 4: 8add5f1<RESET><YELLOW> s/12/B/<RESET>
+ : <REVERSE><CYAN>@@ -8,7 +8,7 @@<RESET>
+ : <CYAN> @@<RESET>
+ : 9<RESET>
+ : 10<RESET>
+ : <REVERSE><RED>-<RESET><FAINT> BB<RESET>
+ : <REVERSE><GREEN>+<RESET><BOLD> B<RESET>
+ : <RED> -12<RESET>
+ : <GREEN> +B<RESET>
+ : 13<RESET>
+ EOF
+ git range-diff changed...changed-message --color --dual-color >actual.raw &&
+ test_decode_color >actual <actual.raw &&
+ test_cmp expect actual
+'
+
+for prev in topic master..topic
+do
+ test_expect_success "format-patch --range-diff=$prev" '
+ git format-patch --stdout --cover-letter --range-diff=$prev \
+ master..unmodified >actual &&
+ grep "= 1: .* s/5/A" actual &&
+ grep "= 2: .* s/4/A" actual &&
+ grep "= 3: .* s/11/B" actual &&
+ grep "= 4: .* s/12/B" actual
+ '
+done
+
+test_expect_success 'format-patch --range-diff as commentary' '
+ git format-patch --stdout --range-diff=HEAD~1 HEAD~1 >actual &&
+ test_i18ngrep "^Range-diff:$" actual
+'
+
test_done
git config core.notesRef refs/notes/y &&
test_must_fail git notes merge z &&
echo "ref: refs/notes/y" >expect &&
- test_cmp .git/NOTES_MERGE_REF expect
+ test_cmp expect .git/NOTES_MERGE_REF
'
test_expect_success 'merge z into y while mid-merge in another workdir fails' '
grep -v "A notes merge into refs/notes/x is already in-progress in" out
) &&
echo "ref: refs/notes/x" >expect &&
- test_cmp .git/worktrees/worktree2/NOTES_MERGE_REF expect
+ test_cmp expect .git/worktrees/worktree2/NOTES_MERGE_REF
'
test_done
test_commit final_B B "Final B" &&
git rebase &&
echo Amended >expect &&
- test_cmp A expect &&
+ test_cmp expect A &&
echo "Final B" >expect &&
- test_cmp B expect &&
+ test_cmp expect B &&
echo C >expect &&
- test_cmp C expect &&
+ test_cmp expect C &&
echo D >expect &&
- test_cmp D expect
+ test_cmp expect D
'
test_expect_success 'rebase -q is quiet' '
'
test_expect_success 'aborted --continue does not squash commits after "edit"' '
- test_when_finished "git rebase --abort" &&
old=$(git rev-parse HEAD) &&
test_tick &&
set_fake_editor &&
FAKE_LINES="edit 1" git rebase -i HEAD^ &&
echo "edited again" > file7 &&
git add file7 &&
- echo all the things >>conflict &&
- test_must_fail git rebase --continue &&
- test $old = $(git rev-parse HEAD)
+ test_must_fail env FAKE_COMMIT_MESSAGE=" " git rebase --continue &&
+ test $old = $(git rev-parse HEAD) &&
+ git rebase --abort
'
test_expect_success 'auto-amend only edited commits after "edit"' '
test_expect_success 'rebase -i commit with empty message' '
git checkout diff-in-message &&
set_fake_editor &&
- env FAKE_COMMIT_MESSAGE=" " FAKE_LINES="reword 1" \
+ test_must_fail env FAKE_COMMIT_MESSAGE=" " FAKE_LINES="reword 1" \
git rebase -i HEAD^
'
test $base = $parent
'
+test_expect_success 'abort last squash' '
+ test_when_finished "test_might_fail git rebase --abort" &&
+ test_when_finished "git checkout master" &&
+
+ git checkout -b some-squashes &&
+ git commit --allow-empty -m first &&
+ git commit --allow-empty --squash HEAD &&
+ git commit --allow-empty -m second &&
+ git commit --allow-empty --squash HEAD &&
+
+ test_must_fail git -c core.editor="grep -q ^pick" \
+ rebase -ki --autosquash HEAD~4 &&
+ : do not finish the squash, but resolve it manually &&
+ git commit --allow-empty --amend -m edited-first &&
+ git rebase --skip &&
+ git show >actual &&
+ ! grep first actual
+'
+
test_done
git add file && git commit -m second &&
git rebase --whitespace=fix HEAD^^ &&
git diff --exit-code HEAD^:file expect-first &&
- test_cmp file expect-second
+ test_cmp expect-second file
'
# prepare third revision of "file"
cp third file && git add file && git commit -m third &&
git rebase --whitespace=fix HEAD^^ &&
git diff --exit-code HEAD^:file expect-second &&
- test_cmp file expect-third
+ test_cmp expect-third file
'
test_expect_success 'same, but do not remove trailing spaces' '
done >> file &&
git commit -m more file &&
git rebase --whitespace=fix HEAD^^ &&
- test_cmp file expect-beginning
+ test_cmp expect-beginning file
'
test_done
test_tick &&
git commit -m "first" &&
- git checkout -b empty-branch &&
- test_tick &&
- git commit --allow-empty -m "empty" &&
-
+ git checkout -b empty-message-branch &&
echo third >> file1 &&
git add file1 &&
test_tick &&
git commit --allow-empty-message -m "" &&
git checkout master &&
- git checkout -b empty-branch2 &&
+ git checkout -b empty-change-branch &&
test_tick &&
git commit --allow-empty -m "empty"
test_expect_success 'cherry-pick an empty commit' '
git checkout master &&
- test_expect_code 1 git cherry-pick empty-branch^
+ test_expect_code 1 git cherry-pick empty-change-branch
'
test_expect_success 'index lockfile was removed' '
'
test_expect_success 'cherry-pick a commit with an empty message' '
+ test_when_finished "git reset --hard empty-message-branch~1" &&
git checkout master &&
- test_expect_code 1 git cherry-pick empty-branch
+ git cherry-pick empty-message-branch
'
test_expect_success 'index lockfile was removed' '
test_expect_success 'cherry-pick a commit with an empty message with --allow-empty-message' '
git checkout -f master &&
- git cherry-pick --allow-empty-message empty-branch
+ git cherry-pick --allow-empty-message empty-message-branch
'
test_expect_success 'cherry pick an empty non-ff commit without --allow-empty' '
echo fourth >>file2 &&
git add file2 &&
git commit -m "fourth" &&
- test_must_fail git cherry-pick empty-branch2
+ test_must_fail git cherry-pick empty-change-branch
'
test_expect_success 'cherry pick an empty non-ff commit with --allow-empty' '
git checkout master &&
- git cherry-pick --allow-empty empty-branch2
+ git cherry-pick --allow-empty empty-change-branch
'
test_expect_success 'cherry pick with --keep-redundant-commits' '
# update it, but we want to be sure that our "." pathspec
# was not expanded into the argument list of any command.
# So look only for "not-changed".
- ! grep not-changed trace.out
+ ! grep -E "^trace: (built-in|exec|run_command): .*not-changed" trace.out
'
test_expect_success 'hunk-editing handles custom comment char' '
cp second-part file &&
git add -e &&
test_cmp second-part file &&
- test_cmp orig-patch expected-patch &&
+ test_cmp expected-patch orig-patch &&
git diff --cached >actual &&
grep -v index actual >out &&
- test_cmp out expected
+ test_cmp expected out
'
test_expect_success 'parents of stash' '
test $(git rev-parse stash^) = $(git rev-parse HEAD) &&
git diff stash^2..stash > output &&
- test_cmp output expect
+ test_cmp expect output
'
test_expect_success 'applying bogus stash does nothing' '
test refs/heads/stashbranch = $(git symbolic-ref HEAD) &&
test $(git rev-parse HEAD) = $(git rev-parse master^) &&
git diff --cached > output &&
- test_cmp output expect &&
+ test_cmp expect output &&
git diff > output &&
- test_cmp output expect1 &&
+ test_cmp expect1 output &&
git add file &&
git commit -m alternate\ second &&
git diff master..stashbranch > output &&
git diff-index --cached --quiet HEAD &&
test "$(git rev-parse stash^)" = "$(git rev-parse HEAD)" &&
git diff stash^..stash > output &&
- test_cmp output expect
+ test_cmp expect output
'
test_expect_success 'store called with invalid commit' '
rm -f file &&
git stash save --include-untracked &&
echo 1 > expect &&
- test_cmp file expect
+ test_cmp expect file
'
rm -f expect
test_cmp expect actual
'
+test_expect_success 'interdiff: setup' '
+ git checkout -b boop master &&
+ test_commit fnorp blorp &&
+ test_commit fleep blorp
+'
+
+test_expect_success 'interdiff: cover-letter' '
+ sed "y/q/ /" >expect <<-\EOF &&
+ +fleep
+ --q
+ EOF
+ git format-patch --cover-letter --interdiff=boop~2 -1 boop &&
+ test_i18ngrep "^Interdiff:$" 0000-cover-letter.patch &&
+ test_i18ngrep ! "^Interdiff:$" 0001-fleep.patch &&
+ sed "1,/^@@ /d; /^-- $/q" <0000-cover-letter.patch >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'interdiff: reroll-count' '
+ git format-patch --cover-letter --interdiff=boop~2 -v2 -1 boop &&
+ test_i18ngrep "^Interdiff ..* v1:$" v2-0000-cover-letter.patch
+'
+
+test_expect_success 'interdiff: solo-patch' '
+ cat >expect <<-\EOF &&
+ +fleep
+
+ EOF
+ git format-patch --interdiff=boop~2 -1 boop &&
+ test_i18ngrep "^Interdiff:$" 0001-fleep.patch &&
+ sed "1,/^ @@ /d; /^$/q" <0001-fleep.patch >actual &&
+ test_cmp expect actual
+'
+
test_done
echo " A $N$N$N$N$N$N$N$N$N2" &&
echo " L $N$N$N$N$N$N$N$N$N1"
) >expected &&
- test_cmp actual expected
+ test_cmp expected actual
'
test_cmp expect actual.head
'
+test_expect_success 'diff --no-index from repo subdir with absolute paths' '
+ cat <<-EOF >expect &&
+ 1 1 $(pwd)/non/git/{a => b}
+ EOF
+ test_expect_code 1 \
+ git -C repo/sub diff --numstat \
+ "$(pwd)/non/git/a" "$(pwd)/non/git/b" >actual &&
+ test_cmp expect actual
+'
+
test_done
rm -f file1.rej file2.rej &&
test_must_fail git apply --reject patch.1 &&
- test_cmp file1 expected &&
+ test_cmp expected file1 &&
cat file1.rej &&
test_path_is_missing file2.rej
test_must_fail git apply --reject patch.2 >rejects &&
test_path_is_missing file1 &&
- test_cmp file2 expected &&
+ test_cmp expected file2 &&
cat file2.rej &&
test_path_is_missing file1.rej
test_must_fail git apply --reject --verbose patch.2 >rejects &&
test_path_is_missing file1 &&
- test_cmp file2 expected &&
+ test_cmp expected file2 &&
cat file2.rej &&
test_path_is_missing file1.rej
{ echo a; echo; } >one &&
cp one expect &&
test_must_fail git apply --whitespace=fix patch &&
- test_cmp one expect &&
+ test_cmp expect one &&
test_must_fail git apply --ignore-space-change --whitespace=fix patch &&
- test_cmp one expect
+ test_cmp expect one
'
test_expect_success 'tons of blanks at EOF should not apply' '
cp one saved-one &&
test_must_fail git apply patch &&
git apply --whitespace=fix patch &&
- test_cmp one expect &&
+ test_cmp expect one &&
mv saved-one one &&
git apply --ignore-space-change --whitespace=fix patch &&
- test_cmp one expect
+ test_cmp expect one
'
test_expect_success 'two missing blank lines at end with --whitespace=fix' '
cp no-blank-lines one &&
test_must_fail git apply patch &&
git apply --whitespace=fix patch &&
- test_cmp one expect &&
+ test_cmp expect one &&
mv no-blank-lines one &&
test_must_fail git apply patch &&
git apply --ignore-space-change --whitespace=fix patch &&
- test_cmp one expect
+ test_cmp expect one
'
test_expect_success 'missing blank line at end, insert before end, --whitespace=fix' '
echo a >one &&
test_must_fail git apply patch &&
git apply --whitespace=fix patch &&
- test_cmp one expect
+ test_cmp expect one
'
test_expect_success 'shrink file with tons of missing blanks at end of file' '
cp no-blank-lines one &&
test_must_fail git apply patch &&
git apply --whitespace=fix patch &&
- test_cmp one expect &&
+ test_cmp expect one &&
mv no-blank-lines one &&
git apply --ignore-space-change --whitespace=fix patch &&
- test_cmp one expect
+ test_cmp expect one
'
test_expect_success 'missing blanks at EOF must only match blank lines' '
git add one &&
git apply --whitespace=fix patch &&
- test_cmp one expect
+ test_cmp expect one
'
sed -e's/Z//' >one <<EOF
git checkout-index -f one &&
git apply --ignore-space-change --whitespace=fix patch &&
- test_cmp one expect
+ test_cmp expect one
'
test_expect_success 'same, but with CR-LF line endings && cr-at-eol set' '
mv save-one one &&
git apply --ignore-space-change --whitespace=fix patch &&
- test_cmp one expect
+ test_cmp expect one
'
test_expect_success 'CR-LF line endings && add line && text=auto' '
mv save-one one &&
echo "one text=auto" >.gitattributes &&
git apply patch &&
- test_cmp one expect
+ test_cmp expect one
'
test_expect_success 'CR-LF line endings && change line && text=auto' '
mv save-one one &&
echo "one text=auto" >.gitattributes &&
git apply patch &&
- test_cmp one expect
+ test_cmp expect one
'
test_expect_success 'LF in repo, CRLF in worktree && change line && text=auto' '
echo "one text=auto" >.gitattributes &&
git -c core.eol=CRLF apply patch &&
printf "b\r\n" >expect &&
- test_cmp one expect
+ test_cmp expect one
'
test_expect_success 'whitespace=fix to expand' '
do
test_expect_success 'apply with ws expansion (t=$t)' '
git apply patch$t.patch &&
- test_cmp test-$t expect-$t
+ test_cmp expect-$t test-$t
'
done
count_pre_post 0 0
'
+test_expect_success 'rerere with unexpected conflict markers does not crash' '
+ git reset --hard &&
+
+ git checkout -b branch-1 master &&
+ echo "bar" >test &&
+ git add test &&
+ git commit -q -m two &&
+
+ git reset --hard &&
+ git checkout -b branch-2 master &&
+ echo "foo" >test &&
+ git add test &&
+ git commit -q -a -m one &&
+
+ test_must_fail git merge branch-1 &&
+ echo "<<<<<<< a" >test &&
+ git rerere &&
+
+ git rerere clear
+'
+
+test_expect_success 'rerere with inner conflict markers' '
+ git reset --hard &&
+
+ git checkout -b A master &&
+ echo "bar" >test &&
+ git add test &&
+ git commit -q -m two &&
+ echo "baz" >test &&
+ git add test &&
+ git commit -q -m three &&
+
+ git reset --hard &&
+ git checkout -b B master &&
+ echo "foo" >test &&
+ git add test &&
+ git commit -q -a -m one &&
+
+ test_must_fail git merge A~ &&
+ git add test &&
+ git commit -q -m "will solve conflicts later" &&
+ test_must_fail git merge A &&
+
+ echo "resolved" >test &&
+ git add test &&
+ git commit -q -m "solved conflict" &&
+
+ echo "resolved" >expect &&
+
+ git reset --hard HEAD~~ &&
+ test_must_fail git merge A~ &&
+ git add test &&
+ git commit -q -m "will solve conflicts later" &&
+ test_must_fail git merge A &&
+ cat test >actual &&
+ test_cmp expect actual &&
+
+ git add test &&
+ git commit -m "rerere solved conflict" &&
+ git reset --hard HEAD~ &&
+ test_must_fail git merge A &&
+ cat test >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'setup simple stage 1 handling' '
+ test_create_repo stage_1_handling &&
+ (
+ cd stage_1_handling &&
+
+ test_seq 1 10 >original &&
+ git add original &&
+ git commit -m original &&
+
+ git checkout -b A master &&
+ git mv original A &&
+ git commit -m "rename to A" &&
+
+ git checkout -b B master &&
+ git mv original B &&
+ git commit -m "rename to B"
+ )
+'
+
+test_expect_success 'test simple stage 1 handling' '
+ (
+ cd stage_1_handling &&
+
+ git config rerere.enabled true &&
+ git checkout A^0 &&
+ test_must_fail git merge B^0
+ )
+'
+
test_done
test_cmp expect actual
'
+test_expect_success 'trailer parsing not fooled by --- line' '
+ git commit --allow-empty -F - <<-\EOF &&
+ this is the subject
+
+ This is the body. The message has a "---" line which would confuse a
+ message+patch parser. But here we know we have only a commit message,
+ so we get it right.
+
+ trailer: wrong
+ ---
+ This is more body.
+
+ trailer: right
+ EOF
+
+ {
+ echo "trailer: right" &&
+ echo
+ } >expect &&
+ git log --no-walk --format="%(trailers)" >actual &&
+ test_cmp expect actual
+'
+
test_done
--- /dev/null
+#!/bin/sh
+
+test_description='git log --graph of skewed left octopus merge.'
+
+. ./test-lib.sh
+
+test_expect_success 'set up merge history' '
+ cat >expect.uncolored <<-\EOF &&
+ * left
+ | *---. octopus-merge
+ | |\ \ \
+ |/ / / /
+ | | | * 4
+ | | * | 3
+ | | |/
+ | * | 2
+ | |/
+ * | 1
+ |/
+ * initial
+ EOF
+ cat >expect.colors <<-\EOF &&
+ * left
+ <RED>|<RESET> *<BLUE>-<RESET><BLUE>-<RESET><MAGENTA>-<RESET><MAGENTA>.<RESET> octopus-merge
+ <RED>|<RESET> <RED>|<RESET><YELLOW>\<RESET> <BLUE>\<RESET> <MAGENTA>\<RESET>
+ <RED>|<RESET><RED>/<RESET> <YELLOW>/<RESET> <BLUE>/<RESET> <MAGENTA>/<RESET>
+ <RED>|<RESET> <YELLOW>|<RESET> <BLUE>|<RESET> * 4
+ <RED>|<RESET> <YELLOW>|<RESET> * <MAGENTA>|<RESET> 3
+ <RED>|<RESET> <YELLOW>|<RESET> <MAGENTA>|<RESET><MAGENTA>/<RESET>
+ <RED>|<RESET> * <MAGENTA>|<RESET> 2
+ <RED>|<RESET> <MAGENTA>|<RESET><MAGENTA>/<RESET>
+ * <MAGENTA>|<RESET> 1
+ <MAGENTA>|<RESET><MAGENTA>/<RESET>
+ * initial
+ EOF
+ test_commit initial &&
+ for i in 1 2 3 4 ; do
+ git checkout master -b $i || return $?
+ # Make tag name different from branch name, to avoid
+ # ambiguity error when calling checkout.
+ test_commit $i $i $i tag$i || return $?
+ done &&
+ git checkout 1 -b merge &&
+ test_tick &&
+ git merge -m octopus-merge 1 2 3 4 &&
+ git checkout 1 -b L &&
+ test_commit left
+'
+
+test_expect_success 'log --graph with tricky octopus merge with colors' '
+ test_config log.graphColors red,green,yellow,blue,magenta,cyan &&
+ git log --color=always --graph --date-order --pretty=tformat:%s --all >actual.colors.raw &&
+ test_decode_color <actual.colors.raw | sed "s/ *\$//" >actual.colors &&
+ test_cmp expect.colors actual.colors
+'
+
+test_expect_success 'log --graph with tricky octopus merge, no color' '
+ git log --color=never --graph --date-order --pretty=tformat:%s --all >actual.raw &&
+ sed "s/ *\$//" actual.raw >actual &&
+ test_cmp expect.uncolored actual
+'
+
+# Repeat the previous two tests with "normal" octopus merge (i.e.,
+# without the first parent skewing to the "left" branch column).
+
+test_expect_success 'log --graph with normal octopus merge, no color' '
+ cat >expect.uncolored <<-\EOF &&
+ *---. octopus-merge
+ |\ \ \
+ | | | * 4
+ | | * | 3
+ | | |/
+ | * | 2
+ | |/
+ * | 1
+ |/
+ * initial
+ EOF
+ git log --color=never --graph --date-order --pretty=tformat:%s merge >actual.raw &&
+ sed "s/ *\$//" actual.raw >actual &&
+ test_cmp expect.uncolored actual
+'
+
+test_expect_success 'log --graph with normal octopus merge with colors' '
+ cat >expect.colors <<-\EOF &&
+ *<YELLOW>-<RESET><YELLOW>-<RESET><BLUE>-<RESET><BLUE>.<RESET> octopus-merge
+ <RED>|<RESET><GREEN>\<RESET> <YELLOW>\<RESET> <BLUE>\<RESET>
+ <RED>|<RESET> <GREEN>|<RESET> <YELLOW>|<RESET> * 4
+ <RED>|<RESET> <GREEN>|<RESET> * <BLUE>|<RESET> 3
+ <RED>|<RESET> <GREEN>|<RESET> <BLUE>|<RESET><BLUE>/<RESET>
+ <RED>|<RESET> * <BLUE>|<RESET> 2
+ <RED>|<RESET> <BLUE>|<RESET><BLUE>/<RESET>
+ * <BLUE>|<RESET> 1
+ <BLUE>|<RESET><BLUE>/<RESET>
+ * initial
+ EOF
+ test_config log.graphColors red,green,yellow,blue,magenta,cyan &&
+ git log --color=always --graph --date-order --pretty=tformat:%s merge >actual.colors.raw &&
+ test_decode_color <actual.colors.raw | sed "s/ *\$//" >actual.colors &&
+ test_cmp expect.colors actual.colors
+'
+test_done
--- /dev/null
+#!/bin/sh
+
+test_description='test format=flowed support of git am'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+ cp "$TEST_DIRECTORY/t4256/1/mailinfo.c.orig" mailinfo.c &&
+ git add mailinfo.c &&
+ git commit -m initial
+'
+
+test_expect_success 'am with format=flowed' '
+ git am <"$TEST_DIRECTORY/t4256/1/patch" >stdout 2>stderr &&
+ test_i18ngrep "warning: Patch sent with format=flowed" stderr &&
+ test_cmp "$TEST_DIRECTORY/t4256/1/mailinfo.c" mailinfo.c
+'
+
+test_done
--- /dev/null
+#include "cache.h"
+#include "config.h"
+#include "utf8.h"
+#include "strbuf.h"
+#include "mailinfo.h"
+
+static void cleanup_space(struct strbuf *sb)
+{
+ size_t pos, cnt;
+ for (pos = 0; pos < sb->len; pos++) {
+ if (isspace(sb->buf[pos])) {
+ sb->buf[pos] = ' ';
+ for (cnt = 0; isspace(sb->buf[pos + cnt + 1]); cnt++);
+ strbuf_remove(sb, pos + 1, cnt);
+ }
+ }
+}
+
+static void get_sane_name(struct strbuf *out, struct strbuf *name, struct strbuf *email)
+{
+ struct strbuf *src = name;
+ if (name->len < 3 || 60 < name->len || strchr(name->buf, '@') ||
+ strchr(name->buf, '<') || strchr(name->buf, '>'))
+ src = email;
+ else if (name == out)
+ return;
+ strbuf_reset(out);
+ strbuf_addbuf(out, src);
+}
+
+static void parse_bogus_from(struct mailinfo *mi, const struct strbuf *line)
+{
+ /* John Doe <johndoe> */
+
+ char *bra, *ket;
+ /* This is fallback, so do not bother if we already have an
+ * e-mail address.
+ */
+ if (mi->email.len)
+ return;
+
+ bra = strchr(line->buf, '<');
+ if (!bra)
+ return;
+ ket = strchr(bra, '>');
+ if (!ket)
+ return;
+
+ strbuf_reset(&mi->email);
+ strbuf_add(&mi->email, bra + 1, ket - bra - 1);
+
+ strbuf_reset(&mi->name);
+ strbuf_add(&mi->name, line->buf, bra - line->buf);
+ strbuf_trim(&mi->name);
+ get_sane_name(&mi->name, &mi->name, &mi->email);
+}
+
+static const char *unquote_comment(struct strbuf *outbuf, const char *in)
+{
+ int c;
+ int take_next_literally = 0;
+
+ strbuf_addch(outbuf, '(');
+
+ while ((c = *in++) != 0) {
+ if (take_next_literally == 1) {
+ take_next_literally = 0;
+ } else {
+ switch (c) {
+ case '\\':
+ take_next_literally = 1;
+ continue;
+ case '(':
+ in = unquote_comment(outbuf, in);
+ continue;
+ case ')':
+ strbuf_addch(outbuf, ')');
+ return in;
+ }
+ }
+
+ strbuf_addch(outbuf, c);
+ }
+
+ return in;
+}
+
+static const char *unquote_quoted_string(struct strbuf *outbuf, const char *in)
+{
+ int c;
+ int take_next_literally = 0;
+
+ while ((c = *in++) != 0) {
+ if (take_next_literally == 1) {
+ take_next_literally = 0;
+ } else {
+ switch (c) {
+ case '\\':
+ take_next_literally = 1;
+ continue;
+ case '"':
+ return in;
+ }
+ }
+
+ strbuf_addch(outbuf, c);
+ }
+
+ return in;
+}
+
+static void unquote_quoted_pair(struct strbuf *line)
+{
+ struct strbuf outbuf;
+ const char *in = line->buf;
+ int c;
+
+ strbuf_init(&outbuf, line->len);
+
+ while ((c = *in++) != 0) {
+ switch (c) {
+ case '"':
+ in = unquote_quoted_string(&outbuf, in);
+ continue;
+ case '(':
+ in = unquote_comment(&outbuf, in);
+ continue;
+ }
+
+ strbuf_addch(&outbuf, c);
+ }
+
+ strbuf_swap(&outbuf, line);
+ strbuf_release(&outbuf);
+
+}
+
+static void handle_from(struct mailinfo *mi, const struct strbuf *from)
+{
+ char *at;
+ size_t el;
+ struct strbuf f;
+
+ strbuf_init(&f, from->len);
+ strbuf_addbuf(&f, from);
+
+ unquote_quoted_pair(&f);
+
+ at = strchr(f.buf, '@');
+ if (!at) {
+ parse_bogus_from(mi, from);
+ goto out;
+ }
+
+ /*
+ * If we already have one email, don't take any confusing lines
+ */
+ if (mi->email.len && strchr(at + 1, '@'))
+ goto out;
+
+ /* Pick up the string around '@', possibly delimited with <>
+ * pair; that is the email part.
+ */
+ while (at > f.buf) {
+ char c = at[-1];
+ if (isspace(c))
+ break;
+ if (c == '<') {
+ at[-1] = ' ';
+ break;
+ }
+ at--;
+ }
+ el = strcspn(at, " \n\t\r\v\f>");
+ strbuf_reset(&mi->email);
+ strbuf_add(&mi->email, at, el);
+ strbuf_remove(&f, at - f.buf, el + (at[el] ? 1 : 0));
+
+ /* The remainder is name. It could be
+ *
+ * - "John Doe <john.doe@xz>" (a), or
+ * - "john.doe@xz (John Doe)" (b), or
+ * - "John (zzz) Doe <john.doe@xz> (Comment)" (c)
+ *
+ * but we have removed the email part, so
+ *
+ * - remove extra spaces which could stay after email (case 'c'), and
+ * - trim from both ends, possibly removing the () pair at the end
+ * (cases 'a' and 'b').
+ */
+ cleanup_space(&f);
+ strbuf_trim(&f);
+ if (f.buf[0] == '(' && f.len && f.buf[f.len - 1] == ')') {
+ strbuf_remove(&f, 0, 1);
+ strbuf_setlen(&f, f.len - 1);
+ }
+
+ get_sane_name(&mi->name, &f, &mi->email);
+out:
+ strbuf_release(&f);
+}
+
+static void handle_header(struct strbuf **out, const struct strbuf *line)
+{
+ if (!*out) {
+ *out = xmalloc(sizeof(struct strbuf));
+ strbuf_init(*out, line->len);
+ } else
+ strbuf_reset(*out);
+
+ strbuf_addbuf(*out, line);
+}
+
+/* NOTE NOTE NOTE. We do not claim we do full MIME. We just attempt
+ * to have enough heuristics to grok MIME encoded patches often found
+ * on our mailing lists. For example, we do not even treat header lines
+ * case insensitively.
+ */
+
+static int slurp_attr(const char *line, const char *name, struct strbuf *attr)
+{
+ const char *ends, *ap = strcasestr(line, name);
+ size_t sz;
+
+ strbuf_setlen(attr, 0);
+ if (!ap)
+ return 0;
+ ap += strlen(name);
+ if (*ap == '"') {
+ ap++;
+ ends = "\"";
+ }
+ else
+ ends = "; \t";
+ sz = strcspn(ap, ends);
+ strbuf_add(attr, ap, sz);
+ return 1;
+}
+
+static int has_attr_value(const char *line, const char *name, const char *value)
+{
+ struct strbuf sb = STRBUF_INIT;
+ int rc = slurp_attr(line, name, &sb) && !strcasecmp(sb.buf, value);
+ strbuf_release(&sb);
+ return rc;
+}
+
+static void handle_content_type(struct mailinfo *mi, struct strbuf *line)
+{
+ struct strbuf *boundary = xmalloc(sizeof(struct strbuf));
+ strbuf_init(boundary, line->len);
+
+ mi->format_flowed = has_attr_value(line->buf, "format=", "flowed");
+ mi->delsp = has_attr_value(line->buf, "delsp=", "yes");
+
+ if (slurp_attr(line->buf, "boundary=", boundary)) {
+ strbuf_insert(boundary, 0, "--", 2);
+ if (++mi->content_top >= &mi->content[MAX_BOUNDARIES]) {
+ error("Too many boundaries to handle");
+ mi->input_error = -1;
+ mi->content_top = &mi->content[MAX_BOUNDARIES] - 1;
+ return;
+ }
+ *(mi->content_top) = boundary;
+ boundary = NULL;
+ }
+ slurp_attr(line->buf, "charset=", &mi->charset);
+
+ if (boundary) {
+ strbuf_release(boundary);
+ free(boundary);
+ }
+}
+
+static void handle_content_transfer_encoding(struct mailinfo *mi,
+ const struct strbuf *line)
+{
+ if (strcasestr(line->buf, "base64"))
+ mi->transfer_encoding = TE_BASE64;
+ else if (strcasestr(line->buf, "quoted-printable"))
+ mi->transfer_encoding = TE_QP;
+ else
+ mi->transfer_encoding = TE_DONTCARE;
+}
+
+static int is_multipart_boundary(struct mailinfo *mi, const struct strbuf *line)
+{
+ struct strbuf *content_top = *(mi->content_top);
+
+ return ((content_top->len <= line->len) &&
+ !memcmp(line->buf, content_top->buf, content_top->len));
+}
+
+static void cleanup_subject(struct mailinfo *mi, struct strbuf *subject)
+{
+ size_t at = 0;
+
+ while (at < subject->len) {
+ char *pos;
+ size_t remove;
+
+ switch (subject->buf[at]) {
+ case 'r': case 'R':
+ if (subject->len <= at + 3)
+ break;
+ if ((subject->buf[at + 1] == 'e' ||
+ subject->buf[at + 1] == 'E') &&
+ subject->buf[at + 2] == ':') {
+ strbuf_remove(subject, at, 3);
+ continue;
+ }
+ at++;
+ break;
+ case ' ': case '\t': case ':':
+ strbuf_remove(subject, at, 1);
+ continue;
+ case '[':
+ pos = strchr(subject->buf + at, ']');
+ if (!pos)
+ break;
+ remove = pos - subject->buf + at + 1;
+ if (!mi->keep_non_patch_brackets_in_subject ||
+ (7 <= remove &&
+ memmem(subject->buf + at, remove, "PATCH", 5)))
+ strbuf_remove(subject, at, remove);
+ else {
+ at += remove;
+ /*
+ * If the input had a space after the ], keep
+ * it. We don't bother with finding the end of
+ * the space, since we later normalize it
+ * anyway.
+ */
+ if (isspace(subject->buf[at]))
+ at += 1;
+ }
+ continue;
+ }
+ break;
+ }
+ strbuf_trim(subject);
+}
+
+#define MAX_HDR_PARSED 10
+static const char *header[MAX_HDR_PARSED] = {
+ "From","Subject","Date",
+};
+
+static inline int cmp_header(const struct strbuf *line, const char *hdr)
+{
+ int len = strlen(hdr);
+ return !strncasecmp(line->buf, hdr, len) && line->len > len &&
+ line->buf[len] == ':' && isspace(line->buf[len + 1]);
+}
+
+static int is_format_patch_separator(const char *line, int len)
+{
+ static const char SAMPLE[] =
+ "From e6807f3efca28b30decfecb1732a56c7db1137ee Mon Sep 17 00:00:00 2001\n";
+ const char *cp;
+
+ if (len != strlen(SAMPLE))
+ return 0;
+ if (!skip_prefix(line, "From ", &cp))
+ return 0;
+ if (strspn(cp, "0123456789abcdef") != 40)
+ return 0;
+ cp += 40;
+ return !memcmp(SAMPLE + (cp - line), cp, strlen(SAMPLE) - (cp - line));
+}
+
+static struct strbuf *decode_q_segment(const struct strbuf *q_seg, int rfc2047)
+{
+ const char *in = q_seg->buf;
+ int c;
+ struct strbuf *out = xmalloc(sizeof(struct strbuf));
+ strbuf_init(out, q_seg->len);
+
+ while ((c = *in++) != 0) {
+ if (c == '=') {
+ int ch, d = *in;
+ if (d == '\n' || !d)
+ break; /* drop trailing newline */
+ ch = hex2chr(in);
+ if (ch >= 0) {
+ strbuf_addch(out, ch);
+ in += 2;
+ continue;
+ }
+ /* garbage -- fall through */
+ }
+ if (rfc2047 && c == '_') /* rfc2047 4.2 (2) */
+ c = 0x20;
+ strbuf_addch(out, c);
+ }
+ return out;
+}
+
+static struct strbuf *decode_b_segment(const struct strbuf *b_seg)
+{
+ /* Decode in..ep, possibly in-place to ot */
+ int c, pos = 0, acc = 0;
+ const char *in = b_seg->buf;
+ struct strbuf *out = xmalloc(sizeof(struct strbuf));
+ strbuf_init(out, b_seg->len);
+
+ while ((c = *in++) != 0) {
+ if (c == '+')
+ c = 62;
+ else if (c == '/')
+ c = 63;
+ else if ('A' <= c && c <= 'Z')
+ c -= 'A';
+ else if ('a' <= c && c <= 'z')
+ c -= 'a' - 26;
+ else if ('0' <= c && c <= '9')
+ c -= '0' - 52;
+ else
+ continue; /* garbage */
+ switch (pos++) {
+ case 0:
+ acc = (c << 2);
+ break;
+ case 1:
+ strbuf_addch(out, (acc | (c >> 4)));
+ acc = (c & 15) << 4;
+ break;
+ case 2:
+ strbuf_addch(out, (acc | (c >> 2)));
+ acc = (c & 3) << 6;
+ break;
+ case 3:
+ strbuf_addch(out, (acc | c));
+ acc = pos = 0;
+ break;
+ }
+ }
+ return out;
+}
+
+static int convert_to_utf8(struct mailinfo *mi,
+ struct strbuf *line, const char *charset)
+{
+ char *out;
+
+ if (!mi->metainfo_charset || !charset || !*charset)
+ return 0;
+
+ if (same_encoding(mi->metainfo_charset, charset))
+ return 0;
+ out = reencode_string(line->buf, mi->metainfo_charset, charset);
+ if (!out) {
+ mi->input_error = -1;
+ return error("cannot convert from %s to %s",
+ charset, mi->metainfo_charset);
+ }
+ strbuf_attach(line, out, strlen(out), strlen(out));
+ return 0;
+}
+
+static void decode_header(struct mailinfo *mi, struct strbuf *it)
+{
+ char *in, *ep, *cp;
+ struct strbuf outbuf = STRBUF_INIT, *dec;
+ struct strbuf charset_q = STRBUF_INIT, piecebuf = STRBUF_INIT;
+ int found_error = 1; /* pessimism */
+
+ in = it->buf;
+ while (in - it->buf <= it->len && (ep = strstr(in, "=?")) != NULL) {
+ int encoding;
+ strbuf_reset(&charset_q);
+ strbuf_reset(&piecebuf);
+
+ if (in != ep) {
+ /*
+ * We are about to process an encoded-word
+ * that begins at ep, but there is something
+ * before the encoded word.
+ */
+ char *scan;
+ for (scan = in; scan < ep; scan++)
+ if (!isspace(*scan))
+ break;
+
+ if (scan != ep || in == it->buf) {
+ /*
+ * We should not lose that "something",
+ * unless we have just processed an
+ * encoded-word, and there is only LWS
+ * before the one we are about to process.
+ */
+ strbuf_add(&outbuf, in, ep - in);
+ }
+ }
+ /* E.g.
+ * ep : "=?iso-2022-jp?B?GyR...?= foo"
+ * ep : "=?ISO-8859-1?Q?Foo=FCbar?= baz"
+ */
+ ep += 2;
+
+ if (ep - it->buf >= it->len || !(cp = strchr(ep, '?')))
+ goto release_return;
+
+ if (cp + 3 - it->buf > it->len)
+ goto release_return;
+ strbuf_add(&charset_q, ep, cp - ep);
+
+ encoding = cp[1];
+ if (!encoding || cp[2] != '?')
+ goto release_return;
+ ep = strstr(cp + 3, "?=");
+ if (!ep)
+ goto release_return;
+ strbuf_add(&piecebuf, cp + 3, ep - cp - 3);
+ switch (tolower(encoding)) {
+ default:
+ goto release_return;
+ case 'b':
+ dec = decode_b_segment(&piecebuf);
+ break;
+ case 'q':
+ dec = decode_q_segment(&piecebuf, 1);
+ break;
+ }
+ if (convert_to_utf8(mi, dec, charset_q.buf))
+ goto release_return;
+
+ strbuf_addbuf(&outbuf, dec);
+ strbuf_release(dec);
+ free(dec);
+ in = ep + 2;
+ }
+ strbuf_addstr(&outbuf, in);
+ strbuf_reset(it);
+ strbuf_addbuf(it, &outbuf);
+ found_error = 0;
+release_return:
+ strbuf_release(&outbuf);
+ strbuf_release(&charset_q);
+ strbuf_release(&piecebuf);
+
+ if (found_error)
+ mi->input_error = -1;
+}
+
+static int check_header(struct mailinfo *mi,
+ const struct strbuf *line,
+ struct strbuf *hdr_data[], int overwrite)
+{
+ int i, ret = 0, len;
+ struct strbuf sb = STRBUF_INIT;
+
+ /* search for the interesting parts */
+ for (i = 0; header[i]; i++) {
+ int len = strlen(header[i]);
+ if ((!hdr_data[i] || overwrite) && cmp_header(line, header[i])) {
+ /* Unwrap inline B and Q encoding, and optionally
+ * normalize the meta information to utf8.
+ */
+ strbuf_add(&sb, line->buf + len + 2, line->len - len - 2);
+ decode_header(mi, &sb);
+ handle_header(&hdr_data[i], &sb);
+ ret = 1;
+ goto check_header_out;
+ }
+ }
+
+ /* Content stuff */
+ if (cmp_header(line, "Content-Type")) {
+ len = strlen("Content-Type: ");
+ strbuf_add(&sb, line->buf + len, line->len - len);
+ decode_header(mi, &sb);
+ strbuf_insert(&sb, 0, "Content-Type: ", len);
+ handle_content_type(mi, &sb);
+ ret = 1;
+ goto check_header_out;
+ }
+ if (cmp_header(line, "Content-Transfer-Encoding")) {
+ len = strlen("Content-Transfer-Encoding: ");
+ strbuf_add(&sb, line->buf + len, line->len - len);
+ decode_header(mi, &sb);
+ handle_content_transfer_encoding(mi, &sb);
+ ret = 1;
+ goto check_header_out;
+ }
+ if (cmp_header(line, "Message-Id")) {
+ len = strlen("Message-Id: ");
+ strbuf_add(&sb, line->buf + len, line->len - len);
+ decode_header(mi, &sb);
+ if (mi->add_message_id)
+ mi->message_id = strbuf_detach(&sb, NULL);
+ ret = 1;
+ goto check_header_out;
+ }
+
+check_header_out:
+ strbuf_release(&sb);
+ return ret;
+}
+
+/*
+ * Returns 1 if the given line or any line beginning with the given line is an
+ * in-body header (that is, check_header will succeed when passed
+ * mi->s_hdr_data).
+ */
+static int is_inbody_header(const struct mailinfo *mi,
+ const struct strbuf *line)
+{
+ int i;
+ for (i = 0; header[i]; i++)
+ if (!mi->s_hdr_data[i] && cmp_header(line, header[i]))
+ return 1;
+ return 0;
+}
+
+static void decode_transfer_encoding(struct mailinfo *mi, struct strbuf *line)
+{
+ struct strbuf *ret;
+
+ switch (mi->transfer_encoding) {
+ case TE_QP:
+ ret = decode_q_segment(line, 0);
+ break;
+ case TE_BASE64:
+ ret = decode_b_segment(line);
+ break;
+ case TE_DONTCARE:
+ default:
+ return;
+ }
+ strbuf_reset(line);
+ strbuf_addbuf(line, ret);
+ strbuf_release(ret);
+ free(ret);
+}
+
+static inline int patchbreak(const struct strbuf *line)
+{
+ size_t i;
+
+ /* Beginning of a "diff -" header? */
+ if (starts_with(line->buf, "diff -"))
+ return 1;
+
+ /* CVS "Index: " line? */
+ if (starts_with(line->buf, "Index: "))
+ return 1;
+
+ /*
+ * "--- <filename>" starts patches without headers
+ * "---<sp>*" is a manual separator
+ */
+ if (line->len < 4)
+ return 0;
+
+ if (starts_with(line->buf, "---")) {
+ /* space followed by a filename? */
+ if (line->buf[3] == ' ' && !isspace(line->buf[4]))
+ return 1;
+ /* Just whitespace? */
+ for (i = 3; i < line->len; i++) {
+ unsigned char c = line->buf[i];
+ if (c == '\n')
+ return 1;
+ if (!isspace(c))
+ break;
+ }
+ return 0;
+ }
+ return 0;
+}
+
+static int is_scissors_line(const char *line)
+{
+ const char *c;
+ int scissors = 0, gap = 0;
+ const char *first_nonblank = NULL, *last_nonblank = NULL;
+ int visible, perforation = 0, in_perforation = 0;
+
+ for (c = line; *c; c++) {
+ if (isspace(*c)) {
+ if (in_perforation) {
+ perforation++;
+ gap++;
+ }
+ continue;
+ }
+ last_nonblank = c;
+ if (first_nonblank == NULL)
+ first_nonblank = c;
+ if (*c == '-') {
+ in_perforation = 1;
+ perforation++;
+ continue;
+ }
+ if ((!memcmp(c, ">8", 2) || !memcmp(c, "8<", 2) ||
+ !memcmp(c, ">%", 2) || !memcmp(c, "%<", 2))) {
+ in_perforation = 1;
+ perforation += 2;
+ scissors += 2;
+ c++;
+ continue;
+ }
+ in_perforation = 0;
+ }
+
+ /*
+ * The mark must be at least 8 bytes long (e.g. "-- >8 --").
+ * Even though there can be arbitrary cruft on the same line
+ * (e.g. "cut here"), in order to avoid misidentification, the
+ * perforation must occupy more than a third of the visible
+ * width of the line, and dashes and scissors must occupy more
+ * than half of the perforation.
+ */
+
+ if (first_nonblank && last_nonblank)
+ visible = last_nonblank - first_nonblank + 1;
+ else
+ visible = 0;
+ return (scissors && 8 <= visible &&
+ visible < perforation * 3 &&
+ gap * 2 < perforation);
+}
+
+static void flush_inbody_header_accum(struct mailinfo *mi)
+{
+ if (!mi->inbody_header_accum.len)
+ return;
+ if (!check_header(mi, &mi->inbody_header_accum, mi->s_hdr_data, 0))
+ BUG("inbody_header_accum, if not empty, must always contain a valid in-body header");
+ strbuf_reset(&mi->inbody_header_accum);
+}
+
+static int check_inbody_header(struct mailinfo *mi, const struct strbuf *line)
+{
+ if (mi->inbody_header_accum.len &&
+ (line->buf[0] == ' ' || line->buf[0] == '\t')) {
+ if (mi->use_scissors && is_scissors_line(line->buf)) {
+ /*
+ * This is a scissors line; do not consider this line
+ * as a header continuation line.
+ */
+ flush_inbody_header_accum(mi);
+ return 0;
+ }
+ strbuf_strip_suffix(&mi->inbody_header_accum, "\n");
+ strbuf_addbuf(&mi->inbody_header_accum, line);
+ return 1;
+ }
+
+ flush_inbody_header_accum(mi);
+
+ if (starts_with(line->buf, ">From") && isspace(line->buf[5]))
+ return is_format_patch_separator(line->buf + 1, line->len - 1);
+ if (starts_with(line->buf, "[PATCH]") && isspace(line->buf[7])) {
+ int i;
+ for (i = 0; header[i]; i++)
+ if (!strcmp("Subject", header[i])) {
+ handle_header(&mi->s_hdr_data[i], line);
+ return 1;
+ }
+ return 0;
+ }
+ if (is_inbody_header(mi, line)) {
+ strbuf_addbuf(&mi->inbody_header_accum, line);
+ return 1;
+ }
+ return 0;
+}
+
+static int handle_commit_msg(struct mailinfo *mi, struct strbuf *line)
+{
+ assert(!mi->filter_stage);
+
+ if (mi->header_stage) {
+ if (!line->len || (line->len == 1 && line->buf[0] == '\n')) {
+ if (mi->inbody_header_accum.len) {
+ flush_inbody_header_accum(mi);
+ mi->header_stage = 0;
+ }
+ return 0;
+ }
+ }
+
+ if (mi->use_inbody_headers && mi->header_stage) {
+ mi->header_stage = check_inbody_header(mi, line);
+ if (mi->header_stage)
+ return 0;
+ } else
+ /* Only trim the first (blank) line of the commit message
+ * when ignoring in-body headers.
+ */
+ mi->header_stage = 0;
+
+ /* normalize the log message to UTF-8. */
+ if (convert_to_utf8(mi, line, mi->charset.buf))
+ return 0; /* mi->input_error already set */
+
+ if (mi->use_scissors && is_scissors_line(line->buf)) {
+ int i;
+
+ strbuf_setlen(&mi->log_message, 0);
+ mi->header_stage = 1;
+
+ /*
+ * We may have already read "secondary headers"; purge
+ * them to give ourselves a clean restart.
+ */
+ for (i = 0; header[i]; i++) {
+ if (mi->s_hdr_data[i])
+ strbuf_release(mi->s_hdr_data[i]);
+ mi->s_hdr_data[i] = NULL;
+ }
+ return 0;
+ }
+
+ if (patchbreak(line)) {
+ if (mi->message_id)
+ strbuf_addf(&mi->log_message,
+ "Message-Id: %s\n", mi->message_id);
+ return 1;
+ }
+
+ strbuf_addbuf(&mi->log_message, line);
+ return 0;
+}
+
+static void handle_patch(struct mailinfo *mi, const struct strbuf *line)
+{
+ fwrite(line->buf, 1, line->len, mi->patchfile);
+ mi->patch_lines++;
+}
+
+static void handle_filter(struct mailinfo *mi, struct strbuf *line)
+{
+ switch (mi->filter_stage) {
+ case 0:
+ if (!handle_commit_msg(mi, line))
+ break;
+ mi->filter_stage++;
+ /* fallthrough */
+ case 1:
+ handle_patch(mi, line);
+ break;
+ }
+}
+
+static int is_rfc2822_header(const struct strbuf *line)
+{
+ /*
+ * The section that defines the loosest possible
+ * field name is "3.6.8 Optional fields".
+ *
+ * optional-field = field-name ":" unstructured CRLF
+ * field-name = 1*ftext
+ * ftext = %d33-57 / %59-126
+ */
+ int ch;
+ char *cp = line->buf;
+
+ /* Count mbox From headers as headers */
+ if (starts_with(cp, "From ") || starts_with(cp, ">From "))
+ return 1;
+
+ while ((ch = *cp++)) {
+ if (ch == ':')
+ return 1;
+ if ((33 <= ch && ch <= 57) ||
+ (59 <= ch && ch <= 126))
+ continue;
+ break;
+ }
+ return 0;
+}
+
+static int read_one_header_line(struct strbuf *line, FILE *in)
+{
+ struct strbuf continuation = STRBUF_INIT;
+
+ /* Get the first part of the line. */
+ if (strbuf_getline_lf(line, in))
+ return 0;
+
+ /*
+ * Is it an empty line or not a valid rfc2822 header?
+ * If so, stop here, and return false ("not a header")
+ */
+ strbuf_rtrim(line);
+ if (!line->len || !is_rfc2822_header(line)) {
+ /* Re-add the newline */
+ strbuf_addch(line, '\n');
+ return 0;
+ }
+
+ /*
+ * Now we need to eat all the continuation lines..
+ * Yuck, 2822 header "folding"
+ */
+ for (;;) {
+ int peek;
+
+ peek = fgetc(in);
+ if (peek == EOF)
+ break;
+ ungetc(peek, in);
+ if (peek != ' ' && peek != '\t')
+ break;
+ if (strbuf_getline_lf(&continuation, in))
+ break;
+ continuation.buf[0] = ' ';
+ strbuf_rtrim(&continuation);
+ strbuf_addbuf(line, &continuation);
+ }
+ strbuf_release(&continuation);
+
+ return 1;
+}
+
+static int find_boundary(struct mailinfo *mi, struct strbuf *line)
+{
+ while (!strbuf_getline_lf(line, mi->input)) {
+ if (*(mi->content_top) && is_multipart_boundary(mi, line))
+ return 1;
+ }
+ return 0;
+}
+
+static int handle_boundary(struct mailinfo *mi, struct strbuf *line)
+{
+ struct strbuf newline = STRBUF_INIT;
+
+ strbuf_addch(&newline, '\n');
+again:
+ if (line->len >= (*(mi->content_top))->len + 2 &&
+ !memcmp(line->buf + (*(mi->content_top))->len, "--", 2)) {
+ /* we hit an end boundary */
+ /* pop the current boundary off the stack */
+ strbuf_release(*(mi->content_top));
+ FREE_AND_NULL(*(mi->content_top));
+
+ /* technically won't happen as is_multipart_boundary()
+ will fail first. But just in case..
+ */
+ if (--mi->content_top < mi->content) {
+ error("Detected mismatched boundaries, can't recover");
+ mi->input_error = -1;
+ mi->content_top = mi->content;
+ strbuf_release(&newline);
+ return 0;
+ }
+ handle_filter(mi, &newline);
+ strbuf_release(&newline);
+ if (mi->input_error)
+ return 0;
+
+ /* skip to the next boundary */
+ if (!find_boundary(mi, line))
+ return 0;
+ goto again;
+ }
+
+ /* set some defaults */
+ mi->transfer_encoding = TE_DONTCARE;
+ strbuf_reset(&mi->charset);
+
+ /* slurp in this section's info */
+ while (read_one_header_line(line, mi->input))
+ check_header(mi, line, mi->p_hdr_data, 0);
+
+ strbuf_release(&newline);
+ /* replenish line */
+ if (strbuf_getline_lf(line, mi->input))
+ return 0;
+ strbuf_addch(line, '\n');
+ return 1;
+}
+
+static void handle_filter_flowed(struct mailinfo *mi, struct strbuf *line,
+ struct strbuf *prev)
+{
+ size_t len = line->len;
+ const char *rest;
+
+ if (!mi->format_flowed) {
+ handle_filter(mi, line);
+ return;
+ }
+
+ if (line->buf[len - 1] == '\n') {
+ len--;
+ if (len && line->buf[len - 1] == '\r')
+ len--;
+ }
+
+ /* Keep signature separator as-is. */
+ if (skip_prefix(line->buf, "-- ", &rest) && rest - line->buf == len) {
+ if (prev->len) {
+ handle_filter(mi, prev);
+ strbuf_reset(prev);
+ }
+ handle_filter(mi, line);
+ return;
+ }
+
+ /* Unstuff space-stuffed line. */
+ if (len && line->buf[0] == ' ') {
+ strbuf_remove(line, 0, 1);
+ len--;
+ }
+
+ /* Save flowed line for later, but without the soft line break. */
+ if (len && line->buf[len - 1] == ' ') {
+ strbuf_add(prev, line->buf, len - !!mi->delsp);
+ return;
+ }
+
+ /* Prepend any previous partial lines */
+ strbuf_insert(line, 0, prev->buf, prev->len);
+ strbuf_reset(prev);
+
+ handle_filter(mi, line);
+}
+
+static void handle_body(struct mailinfo *mi, struct strbuf *line)
+{
+ struct strbuf prev = STRBUF_INIT;
+
+ /* Skip up to the first boundary */
+ if (*(mi->content_top)) {
+ if (!find_boundary(mi, line))
+ goto handle_body_out;
+ }
+
+ do {
+ /* process any boundary lines */
+ if (*(mi->content_top) && is_multipart_boundary(mi, line)) {
+ /* flush any leftover */
+ if (prev.len) {
+ handle_filter(mi, &prev);
+ strbuf_reset(&prev);
+ }
+ if (!handle_boundary(mi, line))
+ goto handle_body_out;
+ }
+
+ /* Unwrap transfer encoding */
+ decode_transfer_encoding(mi, line);
+
+ switch (mi->transfer_encoding) {
+ case TE_BASE64:
+ case TE_QP:
+ {
+ struct strbuf **lines, **it, *sb;
+
+ /* Prepend any previous partial lines */
+ strbuf_insert(line, 0, prev.buf, prev.len);
+ strbuf_reset(&prev);
+
+ /*
+ * This is a decoded line that may contain
+ * multiple new lines. Pass only one chunk
+ * at a time to handle_filter()
+ */
+ lines = strbuf_split(line, '\n');
+ for (it = lines; (sb = *it); it++) {
+ if (*(it + 1) == NULL) /* The last line */
+ if (sb->buf[sb->len - 1] != '\n') {
+ /* Partial line, save it for later. */
+ strbuf_addbuf(&prev, sb);
+ break;
+ }
+ handle_filter_flowed(mi, sb, &prev);
+ }
+ /*
+ * The partial chunk is saved in "prev" and will be
+ * appended by the next iteration of read_line_with_nul().
+ */
+ strbuf_list_free(lines);
+ break;
+ }
+ default:
+ handle_filter_flowed(mi, line, &prev);
+ }
+
+ if (mi->input_error)
+ break;
+ } while (!strbuf_getwholeline(line, mi->input, '\n'));
+
+ if (prev.len)
+ handle_filter(mi, &prev);
+
+ flush_inbody_header_accum(mi);
+
+handle_body_out:
+ strbuf_release(&prev);
+}
+
+static void output_header_lines(FILE *fout, const char *hdr, const struct strbuf *data)
+{
+ const char *sp = data->buf;
+ while (1) {
+ char *ep = strchr(sp, '\n');
+ int len;
+ if (!ep)
+ len = strlen(sp);
+ else
+ len = ep - sp;
+ fprintf(fout, "%s: %.*s\n", hdr, len, sp);
+ if (!ep)
+ break;
+ sp = ep + 1;
+ }
+}
+
+static void handle_info(struct mailinfo *mi)
+{
+ struct strbuf *hdr;
+ int i;
+
+ for (i = 0; header[i]; i++) {
+ /* only print inbody headers if we output a patch file */
+ if (mi->patch_lines && mi->s_hdr_data[i])
+ hdr = mi->s_hdr_data[i];
+ else if (mi->p_hdr_data[i])
+ hdr = mi->p_hdr_data[i];
+ else
+ continue;
+
+ if (!strcmp(header[i], "Subject")) {
+ if (!mi->keep_subject) {
+ cleanup_subject(mi, hdr);
+ cleanup_space(hdr);
+ }
+ output_header_lines(mi->output, "Subject", hdr);
+ } else if (!strcmp(header[i], "From")) {
+ cleanup_space(hdr);
+ handle_from(mi, hdr);
+ fprintf(mi->output, "Author: %s\n", mi->name.buf);
+ fprintf(mi->output, "Email: %s\n", mi->email.buf);
+ } else {
+ cleanup_space(hdr);
+ fprintf(mi->output, "%s: %s\n", header[i], hdr->buf);
+ }
+ }
+ fprintf(mi->output, "\n");
+}
+
+int mailinfo(struct mailinfo *mi, const char *msg, const char *patch)
+{
+ FILE *cmitmsg;
+ int peek;
+ struct strbuf line = STRBUF_INIT;
+
+ cmitmsg = fopen(msg, "w");
+ if (!cmitmsg) {
+ perror(msg);
+ return -1;
+ }
+ mi->patchfile = fopen(patch, "w");
+ if (!mi->patchfile) {
+ perror(patch);
+ fclose(cmitmsg);
+ return -1;
+ }
+
+ mi->p_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(*(mi->p_hdr_data)));
+ mi->s_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(*(mi->s_hdr_data)));
+
+ do {
+ peek = fgetc(mi->input);
+ if (peek == EOF) {
+ fclose(cmitmsg);
+ return error("empty patch: '%s'", patch);
+ }
+ } while (isspace(peek));
+ ungetc(peek, mi->input);
+
+ /* process the email header */
+ while (read_one_header_line(&line, mi->input))
+ check_header(mi, &line, mi->p_hdr_data, 1);
+
+ handle_body(mi, &line);
+ fwrite(mi->log_message.buf, 1, mi->log_message.len, cmitmsg);
+ fclose(cmitmsg);
+ fclose(mi->patchfile);
+
+ handle_info(mi);
+ strbuf_release(&line);
+ return mi->input_error;
+}
+
+static int git_mailinfo_config(const char *var, const char *value, void *mi_)
+{
+ struct mailinfo *mi = mi_;
+
+ if (!starts_with(var, "mailinfo."))
+ return git_default_config(var, value, NULL);
+ if (!strcmp(var, "mailinfo.scissors")) {
+ mi->use_scissors = git_config_bool(var, value);
+ return 0;
+ }
+ /* perhaps others here */
+ return 0;
+}
+
+void setup_mailinfo(struct mailinfo *mi)
+{
+ memset(mi, 0, sizeof(*mi));
+ strbuf_init(&mi->name, 0);
+ strbuf_init(&mi->email, 0);
+ strbuf_init(&mi->charset, 0);
+ strbuf_init(&mi->log_message, 0);
+ strbuf_init(&mi->inbody_header_accum, 0);
+ mi->header_stage = 1;
+ mi->use_inbody_headers = 1;
+ mi->content_top = mi->content;
+ git_config(git_mailinfo_config, mi);
+}
+
+void clear_mailinfo(struct mailinfo *mi)
+{
+ int i;
+
+ strbuf_release(&mi->name);
+ strbuf_release(&mi->email);
+ strbuf_release(&mi->charset);
+ strbuf_release(&mi->inbody_header_accum);
+ free(mi->message_id);
+
+ if (mi->p_hdr_data)
+ for (i = 0; mi->p_hdr_data[i]; i++)
+ strbuf_release(mi->p_hdr_data[i]);
+ free(mi->p_hdr_data);
+ if (mi->s_hdr_data)
+ for (i = 0; mi->s_hdr_data[i]; i++)
+ strbuf_release(mi->s_hdr_data[i]);
+ free(mi->s_hdr_data);
+
+ while (mi->content < mi->content_top) {
+ free(*(mi->content_top));
+ mi->content_top--;
+ }
+
+ strbuf_release(&mi->log_message);
+}
--- /dev/null
+#include "cache.h"
+#include "config.h"
+#include "utf8.h"
+#include "strbuf.h"
+#include "mailinfo.h"
+
+static void cleanup_space(struct strbuf *sb)
+{
+ size_t pos, cnt;
+ for (pos = 0; pos < sb->len; pos++) {
+ if (isspace(sb->buf[pos])) {
+ sb->buf[pos] = ' ';
+ for (cnt = 0; isspace(sb->buf[pos + cnt + 1]); cnt++);
+ strbuf_remove(sb, pos + 1, cnt);
+ }
+ }
+}
+
+static void get_sane_name(struct strbuf *out, struct strbuf *name, struct strbuf *email)
+{
+ struct strbuf *src = name;
+ if (name->len < 3 || 60 < name->len || strchr(name->buf, '@') ||
+ strchr(name->buf, '<') || strchr(name->buf, '>'))
+ src = email;
+ else if (name == out)
+ return;
+ strbuf_reset(out);
+ strbuf_addbuf(out, src);
+}
+
+static void parse_bogus_from(struct mailinfo *mi, const struct strbuf *line)
+{
+ /* John Doe <johndoe> */
+
+ char *bra, *ket;
+ /* This is fallback, so do not bother if we already have an
+ * e-mail address.
+ */
+ if (mi->email.len)
+ return;
+
+ bra = strchr(line->buf, '<');
+ if (!bra)
+ return;
+ ket = strchr(bra, '>');
+ if (!ket)
+ return;
+
+ strbuf_reset(&mi->email);
+ strbuf_add(&mi->email, bra + 1, ket - bra - 1);
+
+ strbuf_reset(&mi->name);
+ strbuf_add(&mi->name, line->buf, bra - line->buf);
+ strbuf_trim(&mi->name);
+ get_sane_name(&mi->name, &mi->name, &mi->email);
+}
+
+static const char *unquote_comment(struct strbuf *outbuf, const char *in)
+{
+ int c;
+ int take_next_literally = 0;
+
+ strbuf_addch(outbuf, '(');
+
+ while ((c = *in++) != 0) {
+ if (take_next_literally == 1) {
+ take_next_literally = 0;
+ } else {
+ switch (c) {
+ case '\\':
+ take_next_literally = 1;
+ continue;
+ case '(':
+ in = unquote_comment(outbuf, in);
+ continue;
+ case ')':
+ strbuf_addch(outbuf, ')');
+ return in;
+ }
+ }
+
+ strbuf_addch(outbuf, c);
+ }
+
+ return in;
+}
+
+static const char *unquote_quoted_string(struct strbuf *outbuf, const char *in)
+{
+ int c;
+ int take_next_literally = 0;
+
+ while ((c = *in++) != 0) {
+ if (take_next_literally == 1) {
+ take_next_literally = 0;
+ } else {
+ switch (c) {
+ case '\\':
+ take_next_literally = 1;
+ continue;
+ case '"':
+ return in;
+ }
+ }
+
+ strbuf_addch(outbuf, c);
+ }
+
+ return in;
+}
+
+static void unquote_quoted_pair(struct strbuf *line)
+{
+ struct strbuf outbuf;
+ const char *in = line->buf;
+ int c;
+
+ strbuf_init(&outbuf, line->len);
+
+ while ((c = *in++) != 0) {
+ switch (c) {
+ case '"':
+ in = unquote_quoted_string(&outbuf, in);
+ continue;
+ case '(':
+ in = unquote_comment(&outbuf, in);
+ continue;
+ }
+
+ strbuf_addch(&outbuf, c);
+ }
+
+ strbuf_swap(&outbuf, line);
+ strbuf_release(&outbuf);
+
+}
+
+static void handle_from(struct mailinfo *mi, const struct strbuf *from)
+{
+ char *at;
+ size_t el;
+ struct strbuf f;
+
+ strbuf_init(&f, from->len);
+ strbuf_addbuf(&f, from);
+
+ unquote_quoted_pair(&f);
+
+ at = strchr(f.buf, '@');
+ if (!at) {
+ parse_bogus_from(mi, from);
+ goto out;
+ }
+
+ /*
+ * If we already have one email, don't take any confusing lines
+ */
+ if (mi->email.len && strchr(at + 1, '@'))
+ goto out;
+
+ /* Pick up the string around '@', possibly delimited with <>
+ * pair; that is the email part.
+ */
+ while (at > f.buf) {
+ char c = at[-1];
+ if (isspace(c))
+ break;
+ if (c == '<') {
+ at[-1] = ' ';
+ break;
+ }
+ at--;
+ }
+ el = strcspn(at, " \n\t\r\v\f>");
+ strbuf_reset(&mi->email);
+ strbuf_add(&mi->email, at, el);
+ strbuf_remove(&f, at - f.buf, el + (at[el] ? 1 : 0));
+
+ /* The remainder is name. It could be
+ *
+ * - "John Doe <john.doe@xz>" (a), or
+ * - "john.doe@xz (John Doe)" (b), or
+ * - "John (zzz) Doe <john.doe@xz> (Comment)" (c)
+ *
+ * but we have removed the email part, so
+ *
+ * - remove extra spaces which could stay after email (case 'c'), and
+ * - trim from both ends, possibly removing the () pair at the end
+ * (cases 'a' and 'b').
+ */
+ cleanup_space(&f);
+ strbuf_trim(&f);
+ if (f.buf[0] == '(' && f.len && f.buf[f.len - 1] == ')') {
+ strbuf_remove(&f, 0, 1);
+ strbuf_setlen(&f, f.len - 1);
+ }
+
+ get_sane_name(&mi->name, &f, &mi->email);
+out:
+ strbuf_release(&f);
+}
+
+static void handle_header(struct strbuf **out, const struct strbuf *line)
+{
+ if (!*out) {
+ *out = xmalloc(sizeof(struct strbuf));
+ strbuf_init(*out, line->len);
+ } else
+ strbuf_reset(*out);
+
+ strbuf_addbuf(*out, line);
+}
+
+/* NOTE NOTE NOTE. We do not claim we do full MIME. We just attempt
+ * to have enough heuristics to grok MIME encoded patches often found
+ * on our mailing lists. For example, we do not even treat header lines
+ * case insensitively.
+ */
+
+static int slurp_attr(const char *line, const char *name, struct strbuf *attr)
+{
+ const char *ends, *ap = strcasestr(line, name);
+ size_t sz;
+
+ strbuf_setlen(attr, 0);
+ if (!ap)
+ return 0;
+ ap += strlen(name);
+ if (*ap == '"') {
+ ap++;
+ ends = "\"";
+ }
+ else
+ ends = "; \t";
+ sz = strcspn(ap, ends);
+ strbuf_add(attr, ap, sz);
+ return 1;
+}
+
+static void handle_content_type(struct mailinfo *mi, struct strbuf *line)
+{
+ struct strbuf *boundary = xmalloc(sizeof(struct strbuf));
+ strbuf_init(boundary, line->len);
+
+ if (slurp_attr(line->buf, "boundary=", boundary)) {
+ strbuf_insert(boundary, 0, "--", 2);
+ if (++mi->content_top >= &mi->content[MAX_BOUNDARIES]) {
+ error("Too many boundaries to handle");
+ mi->input_error = -1;
+ mi->content_top = &mi->content[MAX_BOUNDARIES] - 1;
+ return;
+ }
+ *(mi->content_top) = boundary;
+ boundary = NULL;
+ }
+ slurp_attr(line->buf, "charset=", &mi->charset);
+
+ if (boundary) {
+ strbuf_release(boundary);
+ free(boundary);
+ }
+}
+
+static void handle_content_transfer_encoding(struct mailinfo *mi,
+ const struct strbuf *line)
+{
+ if (strcasestr(line->buf, "base64"))
+ mi->transfer_encoding = TE_BASE64;
+ else if (strcasestr(line->buf, "quoted-printable"))
+ mi->transfer_encoding = TE_QP;
+ else
+ mi->transfer_encoding = TE_DONTCARE;
+}
+
+static int is_multipart_boundary(struct mailinfo *mi, const struct strbuf *line)
+{
+ struct strbuf *content_top = *(mi->content_top);
+
+ return ((content_top->len <= line->len) &&
+ !memcmp(line->buf, content_top->buf, content_top->len));
+}
+
+static void cleanup_subject(struct mailinfo *mi, struct strbuf *subject)
+{
+ size_t at = 0;
+
+ while (at < subject->len) {
+ char *pos;
+ size_t remove;
+
+ switch (subject->buf[at]) {
+ case 'r': case 'R':
+ if (subject->len <= at + 3)
+ break;
+ if ((subject->buf[at + 1] == 'e' ||
+ subject->buf[at + 1] == 'E') &&
+ subject->buf[at + 2] == ':') {
+ strbuf_remove(subject, at, 3);
+ continue;
+ }
+ at++;
+ break;
+ case ' ': case '\t': case ':':
+ strbuf_remove(subject, at, 1);
+ continue;
+ case '[':
+ pos = strchr(subject->buf + at, ']');
+ if (!pos)
+ break;
+ remove = pos - subject->buf + at + 1;
+ if (!mi->keep_non_patch_brackets_in_subject ||
+ (7 <= remove &&
+ memmem(subject->buf + at, remove, "PATCH", 5)))
+ strbuf_remove(subject, at, remove);
+ else {
+ at += remove;
+ /*
+ * If the input had a space after the ], keep
+ * it. We don't bother with finding the end of
+ * the space, since we later normalize it
+ * anyway.
+ */
+ if (isspace(subject->buf[at]))
+ at += 1;
+ }
+ continue;
+ }
+ break;
+ }
+ strbuf_trim(subject);
+}
+
+#define MAX_HDR_PARSED 10
+static const char *header[MAX_HDR_PARSED] = {
+ "From","Subject","Date",
+};
+
+static inline int cmp_header(const struct strbuf *line, const char *hdr)
+{
+ int len = strlen(hdr);
+ return !strncasecmp(line->buf, hdr, len) && line->len > len &&
+ line->buf[len] == ':' && isspace(line->buf[len + 1]);
+}
+
+static int is_format_patch_separator(const char *line, int len)
+{
+ static const char SAMPLE[] =
+ "From e6807f3efca28b30decfecb1732a56c7db1137ee Mon Sep 17 00:00:00 2001\n";
+ const char *cp;
+
+ if (len != strlen(SAMPLE))
+ return 0;
+ if (!skip_prefix(line, "From ", &cp))
+ return 0;
+ if (strspn(cp, "0123456789abcdef") != 40)
+ return 0;
+ cp += 40;
+ return !memcmp(SAMPLE + (cp - line), cp, strlen(SAMPLE) - (cp - line));
+}
+
+static struct strbuf *decode_q_segment(const struct strbuf *q_seg, int rfc2047)
+{
+ const char *in = q_seg->buf;
+ int c;
+ struct strbuf *out = xmalloc(sizeof(struct strbuf));
+ strbuf_init(out, q_seg->len);
+
+ while ((c = *in++) != 0) {
+ if (c == '=') {
+ int ch, d = *in;
+ if (d == '\n' || !d)
+ break; /* drop trailing newline */
+ ch = hex2chr(in);
+ if (ch >= 0) {
+ strbuf_addch(out, ch);
+ in += 2;
+ continue;
+ }
+ /* garbage -- fall through */
+ }
+ if (rfc2047 && c == '_') /* rfc2047 4.2 (2) */
+ c = 0x20;
+ strbuf_addch(out, c);
+ }
+ return out;
+}
+
+static struct strbuf *decode_b_segment(const struct strbuf *b_seg)
+{
+ /* Decode in..ep, possibly in-place to ot */
+ int c, pos = 0, acc = 0;
+ const char *in = b_seg->buf;
+ struct strbuf *out = xmalloc(sizeof(struct strbuf));
+ strbuf_init(out, b_seg->len);
+
+ while ((c = *in++) != 0) {
+ if (c == '+')
+ c = 62;
+ else if (c == '/')
+ c = 63;
+ else if ('A' <= c && c <= 'Z')
+ c -= 'A';
+ else if ('a' <= c && c <= 'z')
+ c -= 'a' - 26;
+ else if ('0' <= c && c <= '9')
+ c -= '0' - 52;
+ else
+ continue; /* garbage */
+ switch (pos++) {
+ case 0:
+ acc = (c << 2);
+ break;
+ case 1:
+ strbuf_addch(out, (acc | (c >> 4)));
+ acc = (c & 15) << 4;
+ break;
+ case 2:
+ strbuf_addch(out, (acc | (c >> 2)));
+ acc = (c & 3) << 6;
+ break;
+ case 3:
+ strbuf_addch(out, (acc | c));
+ acc = pos = 0;
+ break;
+ }
+ }
+ return out;
+}
+
+static int convert_to_utf8(struct mailinfo *mi,
+ struct strbuf *line, const char *charset)
+{
+ char *out;
+
+ if (!mi->metainfo_charset || !charset || !*charset)
+ return 0;
+
+ if (same_encoding(mi->metainfo_charset, charset))
+ return 0;
+ out = reencode_string(line->buf, mi->metainfo_charset, charset);
+ if (!out) {
+ mi->input_error = -1;
+ return error("cannot convert from %s to %s",
+ charset, mi->metainfo_charset);
+ }
+ strbuf_attach(line, out, strlen(out), strlen(out));
+ return 0;
+}
+
+static void decode_header(struct mailinfo *mi, struct strbuf *it)
+{
+ char *in, *ep, *cp;
+ struct strbuf outbuf = STRBUF_INIT, *dec;
+ struct strbuf charset_q = STRBUF_INIT, piecebuf = STRBUF_INIT;
+ int found_error = 1; /* pessimism */
+
+ in = it->buf;
+ while (in - it->buf <= it->len && (ep = strstr(in, "=?")) != NULL) {
+ int encoding;
+ strbuf_reset(&charset_q);
+ strbuf_reset(&piecebuf);
+
+ if (in != ep) {
+ /*
+ * We are about to process an encoded-word
+ * that begins at ep, but there is something
+ * before the encoded word.
+ */
+ char *scan;
+ for (scan = in; scan < ep; scan++)
+ if (!isspace(*scan))
+ break;
+
+ if (scan != ep || in == it->buf) {
+ /*
+ * We should not lose that "something",
+ * unless we have just processed an
+ * encoded-word, and there is only LWS
+ * before the one we are about to process.
+ */
+ strbuf_add(&outbuf, in, ep - in);
+ }
+ }
+ /* E.g.
+ * ep : "=?iso-2022-jp?B?GyR...?= foo"
+ * ep : "=?ISO-8859-1?Q?Foo=FCbar?= baz"
+ */
+ ep += 2;
+
+ if (ep - it->buf >= it->len || !(cp = strchr(ep, '?')))
+ goto release_return;
+
+ if (cp + 3 - it->buf > it->len)
+ goto release_return;
+ strbuf_add(&charset_q, ep, cp - ep);
+
+ encoding = cp[1];
+ if (!encoding || cp[2] != '?')
+ goto release_return;
+ ep = strstr(cp + 3, "?=");
+ if (!ep)
+ goto release_return;
+ strbuf_add(&piecebuf, cp + 3, ep - cp - 3);
+ switch (tolower(encoding)) {
+ default:
+ goto release_return;
+ case 'b':
+ dec = decode_b_segment(&piecebuf);
+ break;
+ case 'q':
+ dec = decode_q_segment(&piecebuf, 1);
+ break;
+ }
+ if (convert_to_utf8(mi, dec, charset_q.buf))
+ goto release_return;
+
+ strbuf_addbuf(&outbuf, dec);
+ strbuf_release(dec);
+ free(dec);
+ in = ep + 2;
+ }
+ strbuf_addstr(&outbuf, in);
+ strbuf_reset(it);
+ strbuf_addbuf(it, &outbuf);
+ found_error = 0;
+release_return:
+ strbuf_release(&outbuf);
+ strbuf_release(&charset_q);
+ strbuf_release(&piecebuf);
+
+ if (found_error)
+ mi->input_error = -1;
+}
+
+static int check_header(struct mailinfo *mi,
+ const struct strbuf *line,
+ struct strbuf *hdr_data[], int overwrite)
+{
+ int i, ret = 0, len;
+ struct strbuf sb = STRBUF_INIT;
+
+ /* search for the interesting parts */
+ for (i = 0; header[i]; i++) {
+ int len = strlen(header[i]);
+ if ((!hdr_data[i] || overwrite) && cmp_header(line, header[i])) {
+ /* Unwrap inline B and Q encoding, and optionally
+ * normalize the meta information to utf8.
+ */
+ strbuf_add(&sb, line->buf + len + 2, line->len - len - 2);
+ decode_header(mi, &sb);
+ handle_header(&hdr_data[i], &sb);
+ ret = 1;
+ goto check_header_out;
+ }
+ }
+
+ /* Content stuff */
+ if (cmp_header(line, "Content-Type")) {
+ len = strlen("Content-Type: ");
+ strbuf_add(&sb, line->buf + len, line->len - len);
+ decode_header(mi, &sb);
+ strbuf_insert(&sb, 0, "Content-Type: ", len);
+ handle_content_type(mi, &sb);
+ ret = 1;
+ goto check_header_out;
+ }
+ if (cmp_header(line, "Content-Transfer-Encoding")) {
+ len = strlen("Content-Transfer-Encoding: ");
+ strbuf_add(&sb, line->buf + len, line->len - len);
+ decode_header(mi, &sb);
+ handle_content_transfer_encoding(mi, &sb);
+ ret = 1;
+ goto check_header_out;
+ }
+ if (cmp_header(line, "Message-Id")) {
+ len = strlen("Message-Id: ");
+ strbuf_add(&sb, line->buf + len, line->len - len);
+ decode_header(mi, &sb);
+ if (mi->add_message_id)
+ mi->message_id = strbuf_detach(&sb, NULL);
+ ret = 1;
+ goto check_header_out;
+ }
+
+check_header_out:
+ strbuf_release(&sb);
+ return ret;
+}
+
+/*
+ * Returns 1 if the given line or any line beginning with the given line is an
+ * in-body header (that is, check_header will succeed when passed
+ * mi->s_hdr_data).
+ */
+static int is_inbody_header(const struct mailinfo *mi,
+ const struct strbuf *line)
+{
+ int i;
+ for (i = 0; header[i]; i++)
+ if (!mi->s_hdr_data[i] && cmp_header(line, header[i]))
+ return 1;
+ return 0;
+}
+
+static void decode_transfer_encoding(struct mailinfo *mi, struct strbuf *line)
+{
+ struct strbuf *ret;
+
+ switch (mi->transfer_encoding) {
+ case TE_QP:
+ ret = decode_q_segment(line, 0);
+ break;
+ case TE_BASE64:
+ ret = decode_b_segment(line);
+ break;
+ case TE_DONTCARE:
+ default:
+ return;
+ }
+ strbuf_reset(line);
+ strbuf_addbuf(line, ret);
+ strbuf_release(ret);
+ free(ret);
+}
+
+static inline int patchbreak(const struct strbuf *line)
+{
+ size_t i;
+
+ /* Beginning of a "diff -" header? */
+ if (starts_with(line->buf, "diff -"))
+ return 1;
+
+ /* CVS "Index: " line? */
+ if (starts_with(line->buf, "Index: "))
+ return 1;
+
+ /*
+ * "--- <filename>" starts patches without headers
+ * "---<sp>*" is a manual separator
+ */
+ if (line->len < 4)
+ return 0;
+
+ if (starts_with(line->buf, "---")) {
+ /* space followed by a filename? */
+ if (line->buf[3] == ' ' && !isspace(line->buf[4]))
+ return 1;
+ /* Just whitespace? */
+ for (i = 3; i < line->len; i++) {
+ unsigned char c = line->buf[i];
+ if (c == '\n')
+ return 1;
+ if (!isspace(c))
+ break;
+ }
+ return 0;
+ }
+ return 0;
+}
+
+static int is_scissors_line(const char *line)
+{
+ const char *c;
+ int scissors = 0, gap = 0;
+ const char *first_nonblank = NULL, *last_nonblank = NULL;
+ int visible, perforation = 0, in_perforation = 0;
+
+ for (c = line; *c; c++) {
+ if (isspace(*c)) {
+ if (in_perforation) {
+ perforation++;
+ gap++;
+ }
+ continue;
+ }
+ last_nonblank = c;
+ if (first_nonblank == NULL)
+ first_nonblank = c;
+ if (*c == '-') {
+ in_perforation = 1;
+ perforation++;
+ continue;
+ }
+ if ((!memcmp(c, ">8", 2) || !memcmp(c, "8<", 2) ||
+ !memcmp(c, ">%", 2) || !memcmp(c, "%<", 2))) {
+ in_perforation = 1;
+ perforation += 2;
+ scissors += 2;
+ c++;
+ continue;
+ }
+ in_perforation = 0;
+ }
+
+ /*
+ * The mark must be at least 8 bytes long (e.g. "-- >8 --").
+ * Even though there can be arbitrary cruft on the same line
+ * (e.g. "cut here"), in order to avoid misidentification, the
+ * perforation must occupy more than a third of the visible
+ * width of the line, and dashes and scissors must occupy more
+ * than half of the perforation.
+ */
+
+ if (first_nonblank && last_nonblank)
+ visible = last_nonblank - first_nonblank + 1;
+ else
+ visible = 0;
+ return (scissors && 8 <= visible &&
+ visible < perforation * 3 &&
+ gap * 2 < perforation);
+}
+
+static void flush_inbody_header_accum(struct mailinfo *mi)
+{
+ if (!mi->inbody_header_accum.len)
+ return;
+ if (!check_header(mi, &mi->inbody_header_accum, mi->s_hdr_data, 0))
+ BUG("inbody_header_accum, if not empty, must always contain a valid in-body header");
+ strbuf_reset(&mi->inbody_header_accum);
+}
+
+static int check_inbody_header(struct mailinfo *mi, const struct strbuf *line)
+{
+ if (mi->inbody_header_accum.len &&
+ (line->buf[0] == ' ' || line->buf[0] == '\t')) {
+ if (mi->use_scissors && is_scissors_line(line->buf)) {
+ /*
+ * This is a scissors line; do not consider this line
+ * as a header continuation line.
+ */
+ flush_inbody_header_accum(mi);
+ return 0;
+ }
+ strbuf_strip_suffix(&mi->inbody_header_accum, "\n");
+ strbuf_addbuf(&mi->inbody_header_accum, line);
+ return 1;
+ }
+
+ flush_inbody_header_accum(mi);
+
+ if (starts_with(line->buf, ">From") && isspace(line->buf[5]))
+ return is_format_patch_separator(line->buf + 1, line->len - 1);
+ if (starts_with(line->buf, "[PATCH]") && isspace(line->buf[7])) {
+ int i;
+ for (i = 0; header[i]; i++)
+ if (!strcmp("Subject", header[i])) {
+ handle_header(&mi->s_hdr_data[i], line);
+ return 1;
+ }
+ return 0;
+ }
+ if (is_inbody_header(mi, line)) {
+ strbuf_addbuf(&mi->inbody_header_accum, line);
+ return 1;
+ }
+ return 0;
+}
+
+static int handle_commit_msg(struct mailinfo *mi, struct strbuf *line)
+{
+ assert(!mi->filter_stage);
+
+ if (mi->header_stage) {
+ if (!line->len || (line->len == 1 && line->buf[0] == '\n')) {
+ if (mi->inbody_header_accum.len) {
+ flush_inbody_header_accum(mi);
+ mi->header_stage = 0;
+ }
+ return 0;
+ }
+ }
+
+ if (mi->use_inbody_headers && mi->header_stage) {
+ mi->header_stage = check_inbody_header(mi, line);
+ if (mi->header_stage)
+ return 0;
+ } else
+ /* Only trim the first (blank) line of the commit message
+ * when ignoring in-body headers.
+ */
+ mi->header_stage = 0;
+
+ /* normalize the log message to UTF-8. */
+ if (convert_to_utf8(mi, line, mi->charset.buf))
+ return 0; /* mi->input_error already set */
+
+ if (mi->use_scissors && is_scissors_line(line->buf)) {
+ int i;
+
+ strbuf_setlen(&mi->log_message, 0);
+ mi->header_stage = 1;
+
+ /*
+ * We may have already read "secondary headers"; purge
+ * them to give ourselves a clean restart.
+ */
+ for (i = 0; header[i]; i++) {
+ if (mi->s_hdr_data[i])
+ strbuf_release(mi->s_hdr_data[i]);
+ mi->s_hdr_data[i] = NULL;
+ }
+ return 0;
+ }
+
+ if (patchbreak(line)) {
+ if (mi->message_id)
+ strbuf_addf(&mi->log_message,
+ "Message-Id: %s\n", mi->message_id);
+ return 1;
+ }
+
+ strbuf_addbuf(&mi->log_message, line);
+ return 0;
+}
+
+static void handle_patch(struct mailinfo *mi, const struct strbuf *line)
+{
+ fwrite(line->buf, 1, line->len, mi->patchfile);
+ mi->patch_lines++;
+}
+
+static void handle_filter(struct mailinfo *mi, struct strbuf *line)
+{
+ switch (mi->filter_stage) {
+ case 0:
+ if (!handle_commit_msg(mi, line))
+ break;
+ mi->filter_stage++;
+ /* fallthrough */
+ case 1:
+ handle_patch(mi, line);
+ break;
+ }
+}
+
+static int is_rfc2822_header(const struct strbuf *line)
+{
+ /*
+ * The section that defines the loosest possible
+ * field name is "3.6.8 Optional fields".
+ *
+ * optional-field = field-name ":" unstructured CRLF
+ * field-name = 1*ftext
+ * ftext = %d33-57 / %59-126
+ */
+ int ch;
+ char *cp = line->buf;
+
+ /* Count mbox From headers as headers */
+ if (starts_with(cp, "From ") || starts_with(cp, ">From "))
+ return 1;
+
+ while ((ch = *cp++)) {
+ if (ch == ':')
+ return 1;
+ if ((33 <= ch && ch <= 57) ||
+ (59 <= ch && ch <= 126))
+ continue;
+ break;
+ }
+ return 0;
+}
+
+static int read_one_header_line(struct strbuf *line, FILE *in)
+{
+ struct strbuf continuation = STRBUF_INIT;
+
+ /* Get the first part of the line. */
+ if (strbuf_getline_lf(line, in))
+ return 0;
+
+ /*
+ * Is it an empty line or not a valid rfc2822 header?
+ * If so, stop here, and return false ("not a header")
+ */
+ strbuf_rtrim(line);
+ if (!line->len || !is_rfc2822_header(line)) {
+ /* Re-add the newline */
+ strbuf_addch(line, '\n');
+ return 0;
+ }
+
+ /*
+ * Now we need to eat all the continuation lines..
+ * Yuck, 2822 header "folding"
+ */
+ for (;;) {
+ int peek;
+
+ peek = fgetc(in);
+ if (peek == EOF)
+ break;
+ ungetc(peek, in);
+ if (peek != ' ' && peek != '\t')
+ break;
+ if (strbuf_getline_lf(&continuation, in))
+ break;
+ continuation.buf[0] = ' ';
+ strbuf_rtrim(&continuation);
+ strbuf_addbuf(line, &continuation);
+ }
+ strbuf_release(&continuation);
+
+ return 1;
+}
+
+static int find_boundary(struct mailinfo *mi, struct strbuf *line)
+{
+ while (!strbuf_getline_lf(line, mi->input)) {
+ if (*(mi->content_top) && is_multipart_boundary(mi, line))
+ return 1;
+ }
+ return 0;
+}
+
+static int handle_boundary(struct mailinfo *mi, struct strbuf *line)
+{
+ struct strbuf newline = STRBUF_INIT;
+
+ strbuf_addch(&newline, '\n');
+again:
+ if (line->len >= (*(mi->content_top))->len + 2 &&
+ !memcmp(line->buf + (*(mi->content_top))->len, "--", 2)) {
+ /* we hit an end boundary */
+ /* pop the current boundary off the stack */
+ strbuf_release(*(mi->content_top));
+ FREE_AND_NULL(*(mi->content_top));
+
+ /* technically won't happen as is_multipart_boundary()
+ will fail first. But just in case..
+ */
+ if (--mi->content_top < mi->content) {
+ error("Detected mismatched boundaries, can't recover");
+ mi->input_error = -1;
+ mi->content_top = mi->content;
+ strbuf_release(&newline);
+ return 0;
+ }
+ handle_filter(mi, &newline);
+ strbuf_release(&newline);
+ if (mi->input_error)
+ return 0;
+
+ /* skip to the next boundary */
+ if (!find_boundary(mi, line))
+ return 0;
+ goto again;
+ }
+
+ /* set some defaults */
+ mi->transfer_encoding = TE_DONTCARE;
+ strbuf_reset(&mi->charset);
+
+ /* slurp in this section's info */
+ while (read_one_header_line(line, mi->input))
+ check_header(mi, line, mi->p_hdr_data, 0);
+
+ strbuf_release(&newline);
+ /* replenish line */
+ if (strbuf_getline_lf(line, mi->input))
+ return 0;
+ strbuf_addch(line, '\n');
+ return 1;
+}
+
+static void handle_body(struct mailinfo *mi, struct strbuf *line)
+{
+ struct strbuf prev = STRBUF_INIT;
+
+ /* Skip up to the first boundary */
+ if (*(mi->content_top)) {
+ if (!find_boundary(mi, line))
+ goto handle_body_out;
+ }
+
+ do {
+ /* process any boundary lines */
+ if (*(mi->content_top) && is_multipart_boundary(mi, line)) {
+ /* flush any leftover */
+ if (prev.len) {
+ handle_filter(mi, &prev);
+ strbuf_reset(&prev);
+ }
+ if (!handle_boundary(mi, line))
+ goto handle_body_out;
+ }
+
+ /* Unwrap transfer encoding */
+ decode_transfer_encoding(mi, line);
+
+ switch (mi->transfer_encoding) {
+ case TE_BASE64:
+ case TE_QP:
+ {
+ struct strbuf **lines, **it, *sb;
+
+ /* Prepend any previous partial lines */
+ strbuf_insert(line, 0, prev.buf, prev.len);
+ strbuf_reset(&prev);
+
+ /*
+ * This is a decoded line that may contain
+ * multiple new lines. Pass only one chunk
+ * at a time to handle_filter()
+ */
+ lines = strbuf_split(line, '\n');
+ for (it = lines; (sb = *it); it++) {
+ if (*(it + 1) == NULL) /* The last line */
+ if (sb->buf[sb->len - 1] != '\n') {
+ /* Partial line, save it for later. */
+ strbuf_addbuf(&prev, sb);
+ break;
+ }
+ handle_filter(mi, sb);
+ }
+ /*
+ * The partial chunk is saved in "prev" and will be
+ * appended by the next iteration of read_line_with_nul().
+ */
+ strbuf_list_free(lines);
+ break;
+ }
+ default:
+ handle_filter(mi, line);
+ }
+
+ if (mi->input_error)
+ break;
+ } while (!strbuf_getwholeline(line, mi->input, '\n'));
+
+ flush_inbody_header_accum(mi);
+
+handle_body_out:
+ strbuf_release(&prev);
+}
+
+static void output_header_lines(FILE *fout, const char *hdr, const struct strbuf *data)
+{
+ const char *sp = data->buf;
+ while (1) {
+ char *ep = strchr(sp, '\n');
+ int len;
+ if (!ep)
+ len = strlen(sp);
+ else
+ len = ep - sp;
+ fprintf(fout, "%s: %.*s\n", hdr, len, sp);
+ if (!ep)
+ break;
+ sp = ep + 1;
+ }
+}
+
+static void handle_info(struct mailinfo *mi)
+{
+ struct strbuf *hdr;
+ int i;
+
+ for (i = 0; header[i]; i++) {
+ /* only print inbody headers if we output a patch file */
+ if (mi->patch_lines && mi->s_hdr_data[i])
+ hdr = mi->s_hdr_data[i];
+ else if (mi->p_hdr_data[i])
+ hdr = mi->p_hdr_data[i];
+ else
+ continue;
+
+ if (!strcmp(header[i], "Subject")) {
+ if (!mi->keep_subject) {
+ cleanup_subject(mi, hdr);
+ cleanup_space(hdr);
+ }
+ output_header_lines(mi->output, "Subject", hdr);
+ } else if (!strcmp(header[i], "From")) {
+ cleanup_space(hdr);
+ handle_from(mi, hdr);
+ fprintf(mi->output, "Author: %s\n", mi->name.buf);
+ fprintf(mi->output, "Email: %s\n", mi->email.buf);
+ } else {
+ cleanup_space(hdr);
+ fprintf(mi->output, "%s: %s\n", header[i], hdr->buf);
+ }
+ }
+ fprintf(mi->output, "\n");
+}
+
+int mailinfo(struct mailinfo *mi, const char *msg, const char *patch)
+{
+ FILE *cmitmsg;
+ int peek;
+ struct strbuf line = STRBUF_INIT;
+
+ cmitmsg = fopen(msg, "w");
+ if (!cmitmsg) {
+ perror(msg);
+ return -1;
+ }
+ mi->patchfile = fopen(patch, "w");
+ if (!mi->patchfile) {
+ perror(patch);
+ fclose(cmitmsg);
+ return -1;
+ }
+
+ mi->p_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(*(mi->p_hdr_data)));
+ mi->s_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(*(mi->s_hdr_data)));
+
+ do {
+ peek = fgetc(mi->input);
+ if (peek == EOF) {
+ fclose(cmitmsg);
+ return error("empty patch: '%s'", patch);
+ }
+ } while (isspace(peek));
+ ungetc(peek, mi->input);
+
+ /* process the email header */
+ while (read_one_header_line(&line, mi->input))
+ check_header(mi, &line, mi->p_hdr_data, 1);
+
+ handle_body(mi, &line);
+ fwrite(mi->log_message.buf, 1, mi->log_message.len, cmitmsg);
+ fclose(cmitmsg);
+ fclose(mi->patchfile);
+
+ handle_info(mi);
+ strbuf_release(&line);
+ return mi->input_error;
+}
+
+static int git_mailinfo_config(const char *var, const char *value, void *mi_)
+{
+ struct mailinfo *mi = mi_;
+
+ if (!starts_with(var, "mailinfo."))
+ return git_default_config(var, value, NULL);
+ if (!strcmp(var, "mailinfo.scissors")) {
+ mi->use_scissors = git_config_bool(var, value);
+ return 0;
+ }
+ /* perhaps others here */
+ return 0;
+}
+
+void setup_mailinfo(struct mailinfo *mi)
+{
+ memset(mi, 0, sizeof(*mi));
+ strbuf_init(&mi->name, 0);
+ strbuf_init(&mi->email, 0);
+ strbuf_init(&mi->charset, 0);
+ strbuf_init(&mi->log_message, 0);
+ strbuf_init(&mi->inbody_header_accum, 0);
+ mi->header_stage = 1;
+ mi->use_inbody_headers = 1;
+ mi->content_top = mi->content;
+ git_config(git_mailinfo_config, mi);
+}
+
+void clear_mailinfo(struct mailinfo *mi)
+{
+ int i;
+
+ strbuf_release(&mi->name);
+ strbuf_release(&mi->email);
+ strbuf_release(&mi->charset);
+ strbuf_release(&mi->inbody_header_accum);
+ free(mi->message_id);
+
+ if (mi->p_hdr_data)
+ for (i = 0; mi->p_hdr_data[i]; i++)
+ strbuf_release(mi->p_hdr_data[i]);
+ free(mi->p_hdr_data);
+ if (mi->s_hdr_data)
+ for (i = 0; mi->s_hdr_data[i]; i++)
+ strbuf_release(mi->s_hdr_data[i]);
+ free(mi->s_hdr_data);
+
+ while (mi->content < mi->content_top) {
+ free(*(mi->content_top));
+ mi->content_top--;
+ }
+
+ strbuf_release(&mi->log_message);
+}
--- /dev/null
+From: A <author@example.com>
+Subject: [PATCH] mailinfo: support format=flowed
+Message-ID: <aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa@example.com>
+Date: Sat, 25 Aug 2018 22:04:50 +0200
+User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:60.0) Gecko/20100101
+ Thunderbird/60.0
+MIME-Version: 1.0
+Content-Type: text/plain; charset=utf-8; format=flowed
+Content-Language: en-US
+Content-Transfer-Encoding: 7bit
+
+---
+ mailinfo.c | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++--
+ 1 file changed, 62 insertions(+), 2 deletions(-)
+
+diff --git a/mailinfo.c b/mailinfo.c
+index 3281a37d51..b395adbdf2 100644
+--- a/mailinfo.c
++++ b/mailinfo.c
+@@ -237,11 +237,22 @@ static int slurp_attr(const char *line, const char
+*name, struct strbuf *attr)
+ return 1;
+ }
+
++static int has_attr_value(const char *line, const char *name, const
+char *value)
++{
++ struct strbuf sb = STRBUF_INIT;
++ int rc = slurp_attr(line, name, &sb) && !strcasecmp(sb.buf, value);
++ strbuf_release(&sb);
++ return rc;
++}
++
+ static void handle_content_type(struct mailinfo *mi, struct strbuf *line)
+ {
+ struct strbuf *boundary = xmalloc(sizeof(struct strbuf));
+ strbuf_init(boundary, line->len);
+
++ mi->format_flowed = has_attr_value(line->buf, "format=", "flowed");
++ mi->delsp = has_attr_value(line->buf, "delsp=", "yes");
++
+ if (slurp_attr(line->buf, "boundary=", boundary)) {
+ strbuf_insert(boundary, 0, "--", 2);
+ if (++mi->content_top >= &mi->content[MAX_BOUNDARIES]) {
+@@ -964,6 +975,52 @@ static int handle_boundary(struct mailinfo *mi,
+struct strbuf *line)
+ return 1;
+ }
+
++static void handle_filter_flowed(struct mailinfo *mi, struct strbuf *line,
++ struct strbuf *prev)
++{
++ size_t len = line->len;
++ const char *rest;
++
++ if (!mi->format_flowed) {
++ handle_filter(mi, line);
++ return;
++ }
++
++ if (line->buf[len - 1] == '\n') {
++ len--;
++ if (len && line->buf[len - 1] == '\r')
++ len--;
++ }
++
++ /* Keep signature separator as-is. */
++ if (skip_prefix(line->buf, "-- ", &rest) && rest - line->buf == len) {
++ if (prev->len) {
++ handle_filter(mi, prev);
++ strbuf_reset(prev);
++ }
++ handle_filter(mi, line);
++ return;
++ }
++
++ /* Unstuff space-stuffed line. */
++ if (len && line->buf[0] == ' ') {
++ strbuf_remove(line, 0, 1);
++ len--;
++ }
++
++ /* Save flowed line for later, but without the soft line break. */
++ if (len && line->buf[len - 1] == ' ') {
++ strbuf_add(prev, line->buf, len - !!mi->delsp);
++ return;
++ }
++
++ /* Prepend any previous partial lines */
++ strbuf_insert(line, 0, prev->buf, prev->len);
++ strbuf_reset(prev);
++
++ handle_filter(mi, line);
++}
++
+ static void handle_body(struct mailinfo *mi, struct strbuf *line)
+ {
+ struct strbuf prev = STRBUF_INIT;
+@@ -1012,7 +1069,7 @@ static void handle_body(struct mailinfo *mi,
+struct strbuf *line)
+ strbuf_addbuf(&prev, sb);
+ break;
+ }
+- handle_filter(mi, sb);
++ handle_filter_flowed(mi, sb, &prev);
+ }
+ /*
+ * The partial chunk is saved in "prev" and will be
+@@ -1022,13 +1079,16 @@ static void handle_body(struct mailinfo *mi,
+struct strbuf *line)
+ break;
+ }
+ default:
+- handle_filter(mi, line);
++ handle_filter_flowed(mi, line, &prev);
+ }
+
+ if (mi->input_error)
+ break;
+ } while (!strbuf_getwholeline(line, mi->input, '\n'));
+
++ if (prev.len)
++ handle_filter(mi, &prev);
++
+ flush_inbody_header_accum(mi);
+
+ handle_body_out:
+--
+2.18.0
test_must_fail git cat-file blob $blob_2 > /dev/null &&
test_must_fail git cat-file blob $blob_3 > /dev/null'
+# \0 - empty base
+# \1 - one byte in result
+# \1 - one literal byte (X)
+test_expect_success \
+ 'apply good minimal delta' \
+ 'printf "\0\1\1X" > minimal_delta &&
+ test-tool delta -p /dev/null minimal_delta /dev/null'
+
+# \0 - empty base
+# \1 - 1 byte in result
+# \2 - two literal bytes (one too many)
+test_expect_success \
+ 'apply delta with too many literal bytes' \
+ 'printf "\0\1\2XX" > too_big_literal &&
+ test_must_fail test-tool delta -p /dev/null too_big_literal /dev/null'
+
+# \4 - four bytes in base
+# \1 - one byte in result
+# \221 - copy, one byte offset, one byte size
+# \0 - copy from offset 0
+# \2 - copy two bytes (one too many)
+test_expect_success \
+ 'apply delta with too many copied bytes' \
+ 'printf "\4\1\221\0\2" > too_big_copy &&
+ printf base >base &&
+ test_must_fail test-tool delta -p base too_big_copy /dev/null'
+
+# \0 - empty base
+# \2 - two bytes in result
+# \2 - two literal bytes (we are short one)
+test_expect_success \
+ 'apply delta with too few literal bytes' \
+ 'printf "\0\2\2X" > truncated_delta &&
+ test_must_fail test-tool delta -p /dev/null truncated_delta /dev/null'
+
+# \0 - empty base
+# \1 - one byte in result
+# \221 - copy, one byte offset, one byte size
+# \0 - copy from offset 0
+# \1 - copy one byte (we are short one)
+test_expect_success \
+ 'apply delta with too few bytes in base' \
+ 'printf "\0\1\221\0\1" > truncated_base &&
+ test_must_fail test-tool delta -p /dev/null truncated_base /dev/null'
+
+# \4 - four bytes in base
+# \2 - two bytes in result
+# \1 - one literal byte (X)
+# \221 - copy, one byte offset, one byte size
+# (offset/size missing)
+#
+# Note that the literal byte is necessary to get past the uninteresting minimum
+# delta size check.
+test_expect_success \
+ 'apply delta with truncated copy parameters' \
+ 'printf "\4\2\1X\221" > truncated_copy_delta &&
+ printf base >base &&
+ test_must_fail test-tool delta -p base truncated_copy_delta /dev/null'
+
+# \0 - empty base
+# \1 - one byte in result
+# \1 - one literal byte (X)
+# \1 - trailing garbage command
+test_expect_success \
+ 'apply delta with trailing garbage literal' \
+ 'printf "\0\1\1X\1" > tail_garbage_literal &&
+ test_must_fail test-tool delta -p /dev/null tail_garbage_literal /dev/null'
+
+# \4 - four bytes in base
+# \1 - one byte in result
+# \1 - one literal byte (X)
+# \221 - copy, one byte offset, one byte size
+# \0 - copy from offset 0
+# \1 - copy 1 byte
+test_expect_success \
+ 'apply delta with trailing garbage copy' \
+ 'printf "\4\1\1X\221\0\1" > tail_garbage_copy &&
+ printf base >base &&
+ test_must_fail test-tool delta -p /dev/null tail_garbage_copy /dev/null'
+
+# \0 - empty base
+# \1 - one byte in result
+# \1 - one literal byte (X)
+# \0 - bogus opcode
+test_expect_success \
+ 'apply delta with trailing garbage opcode' \
+ 'printf "\0\1\1X\0" > tail_garbage_opcode &&
+ test_must_fail test-tool delta -p /dev/null tail_garbage_opcode /dev/null'
+
test_done
'
test_expect_success 'rev-list notices corruption (1)' '
- test_must_fail git rev-list HEAD
+ test_must_fail env GIT_TEST_COMMIT_GRAPH=0 git rev-list HEAD
'
test_expect_success 'rev-list notices corruption (2)' '
- test_must_fail git rev-list --objects HEAD
+ test_must_fail env GIT_TEST_COMMIT_GRAPH=0 git rev-list --objects HEAD
'
test_expect_success 'pack-objects notices corruption' '
test_i18ngrep corrupt stderr
'
+# have_delta <obj> <expected_base>
+#
+# Note that because this relies on cat-file, it might find _any_ copy of an
+# object in the repository. The caller is responsible for making sure
+# there's only one (e.g., via "repack -ad", or having just fetched a copy).
+have_delta () {
+ echo $2 >expect &&
+ echo $1 | git cat-file --batch-check="%(deltabase)" >actual &&
+ test_cmp expect actual
+}
+
+# Create a state of history with these properties:
+#
+# - refs that allow a client to fetch some new history, while sharing some old
+# history with the server; we use branches delta-reuse-old and
+# delta-reuse-new here
+#
+# - the new history contains an object that is stored on the server as a delta
+# against a base that is in the old history
+#
+# - the base object is not immediately reachable from the tip of the old
+# history; finding it would involve digging down through history we know the
+# other side has
+#
+# This should result in a state where fetching from old->new would not
+# traditionally reuse the on-disk delta (because we'd have to dig to realize
+# that the client has it), but we will do so if bitmaps can tell us cheaply
+# that the other side has it.
+test_expect_success 'set up thin delta-reuse parent' '
+ # This first commit contains the buried base object.
+ test-tool genrandom delta 16384 >file &&
+ git add file &&
+ git commit -m "delta base" &&
+ base=$(git rev-parse --verify HEAD:file) &&
+
+ # These intermediate commits bury the base back in history.
+ # This becomes the "old" state.
+ for i in 1 2 3 4 5
+ do
+ echo $i >file &&
+ git commit -am "intermediate $i" || return 1
+ done &&
+ git branch delta-reuse-old &&
+
+ # And now our new history has a delta against the buried base. Note
+ # that this must be smaller than the original file, since pack-objects
+ # prefers to create deltas from smaller objects to larger.
+ test-tool genrandom delta 16300 >file &&
+ git commit -am "delta result" &&
+ delta=$(git rev-parse --verify HEAD:file) &&
+ git branch delta-reuse-new &&
+
+ # Repack with bitmaps and double check that we have the expected delta
+ # relationship.
+ git repack -adb &&
+ have_delta $delta $base
+'
+
+# Now we can sanity-check the non-bitmap behavior (that the server is not able
+# to reuse the delta). This isn't strictly something we care about, so this
+# test could be scrapped in the future. But it makes sure that the next test is
+# actually triggering the feature we want.
+#
+# Note that our tools for working with on-the-wire "thin" packs are limited. So
+# we actually perform the fetch, retain the resulting pack, and inspect the
+# result.
+test_expect_success 'fetch without bitmaps ignores delta against old base' '
+ test_config pack.usebitmaps false &&
+ test_when_finished "rm -rf client.git" &&
+ git init --bare client.git &&
+ (
+ cd client.git &&
+ git config transfer.unpackLimit 1 &&
+ git fetch .. delta-reuse-old:delta-reuse-old &&
+ git fetch .. delta-reuse-new:delta-reuse-new &&
+ have_delta $delta $ZERO_OID
+ )
+'
+
+# And do the same for the bitmap case, where we do expect to find the delta.
+test_expect_success 'fetch with bitmaps can reuse old base' '
+ test_config pack.usebitmaps true &&
+ test_when_finished "rm -rf client.git" &&
+ git init --bare client.git &&
+ (
+ cd client.git &&
+ git config transfer.unpackLimit 1 &&
+ git fetch .. delta-reuse-old:delta-reuse-old &&
+ git fetch .. delta-reuse-new:delta-reuse-new &&
+ have_delta $delta $base
+ )
+'
+
test_done
test_expect_success 'verify blob count in normal packfile' '
git -C r1 ls-files -s file.1 file.2 file.3 file.4 file.5 \
- | awk -f print_2.awk \
- | sort >expected &&
+ >ls_files_result &&
+ awk -f print_2.awk ls_files_result |
+ sort >expected &&
+
git -C r1 pack-objects --rev --stdout >all.pack <<-EOF &&
HEAD
EOF
git -C r1 index-pack ../all.pack &&
- git -C r1 verify-pack -v ../all.pack \
- | grep blob \
- | awk -f print_1.awk \
- | sort >observed &&
- test_cmp observed expected
+
+ git -C r1 verify-pack -v ../all.pack >verify_result &&
+ grep blob verify_result |
+ awk -f print_1.awk |
+ sort >observed &&
+
+ test_cmp expected observed
'
test_expect_success 'verify blob:none packfile has no blobs' '
HEAD
EOF
git -C r1 index-pack ../filter.pack &&
- git -C r1 verify-pack -v ../filter.pack \
- | grep blob \
- | awk -f print_1.awk \
- | sort >observed &&
+
+ git -C r1 verify-pack -v ../filter.pack >verify_result &&
+ grep blob verify_result |
+ awk -f print_1.awk |
+ sort >observed &&
+
nr=$(wc -l <observed) &&
test 0 -eq $nr
'
test_expect_success 'verify normal and blob:none packfiles have same commits/trees' '
- git -C r1 verify-pack -v ../all.pack \
- | grep -E "commit|tree" \
- | awk -f print_1.awk \
- | sort >expected &&
- git -C r1 verify-pack -v ../filter.pack \
- | grep -E "commit|tree" \
- | awk -f print_1.awk \
- | sort >observed &&
- test_cmp observed expected
+ git -C r1 verify-pack -v ../all.pack >verify_result &&
+ grep -E "commit|tree" verify_result |
+ awk -f print_1.awk |
+ sort >expected &&
+
+ git -C r1 verify-pack -v ../filter.pack >verify_result &&
+ grep -E "commit|tree" verify_result |
+ awk -f print_1.awk |
+ sort >observed &&
+
+ test_cmp expected observed
+'
+
+test_expect_success 'get an error for missing tree object' '
+ git init r5 &&
+ echo foo >r5/foo &&
+ git -C r5 add foo &&
+ git -C r5 commit -m "foo" &&
+ del=$(git -C r5 rev-parse HEAD^{tree} | sed "s|..|&/|") &&
+ rm r5/.git/objects/$del &&
+ test_must_fail git -C r5 pack-objects --rev --stdout 2>bad_tree <<-EOF &&
+ HEAD
+ EOF
+ grep "bad tree object" bad_tree
+'
+
+test_expect_success 'setup for tests of tree:0' '
+ mkdir r1/subtree &&
+ echo "This is a file in a subtree" >r1/subtree/file &&
+ git -C r1 add subtree/file &&
+ git -C r1 commit -m subtree
+'
+
+test_expect_success 'verify tree:0 packfile has no blobs or trees' '
+ git -C r1 pack-objects --rev --stdout --filter=tree:0 >commitsonly.pack <<-EOF &&
+ HEAD
+ EOF
+ git -C r1 index-pack ../commitsonly.pack &&
+ git -C r1 verify-pack -v ../commitsonly.pack >objs &&
+ ! grep -E "tree|blob" objs
+'
+
+test_expect_success 'grab tree directly when using tree:0' '
+ # We should get the tree specified directly but not its blobs or subtrees.
+ git -C r1 pack-objects --rev --stdout --filter=tree:0 >commitsonly.pack <<-EOF &&
+ HEAD:
+ EOF
+ git -C r1 index-pack ../commitsonly.pack &&
+ git -C r1 verify-pack -v ../commitsonly.pack >objs &&
+ awk "/tree|blob/{print \$1}" objs >trees_and_blobs &&
+ git -C r1 rev-parse HEAD: >expected &&
+ test_cmp expected trees_and_blobs
'
# Test blob:limit=<n>[kmg] filter.
'
test_expect_success 'verify blob count in normal packfile' '
- git -C r2 ls-files -s large.1000 large.10000 \
- | awk -f print_2.awk \
- | sort >expected &&
+ git -C r2 ls-files -s large.1000 large.10000 >ls_files_result &&
+ awk -f print_2.awk ls_files_result |
+ sort >expected &&
+
git -C r2 pack-objects --rev --stdout >all.pack <<-EOF &&
HEAD
EOF
git -C r2 index-pack ../all.pack &&
- git -C r2 verify-pack -v ../all.pack \
- | grep blob \
- | awk -f print_1.awk \
- | sort >observed &&
- test_cmp observed expected
+
+ git -C r2 verify-pack -v ../all.pack >verify_result &&
+ grep blob verify_result |
+ awk -f print_1.awk |
+ sort >observed &&
+
+ test_cmp expected observed
'
test_expect_success 'verify blob:limit=500 omits all blobs' '
HEAD
EOF
git -C r2 index-pack ../filter.pack &&
- git -C r2 verify-pack -v ../filter.pack \
- | grep blob \
- | awk -f print_1.awk \
- | sort >observed &&
+
+ git -C r2 verify-pack -v ../filter.pack >verify_result &&
+ grep blob verify_result |
+ awk -f print_1.awk |
+ sort >observed &&
+
nr=$(wc -l <observed) &&
test 0 -eq $nr
'
HEAD
EOF
git -C r2 index-pack ../filter.pack &&
- git -C r2 verify-pack -v ../filter.pack \
- | grep blob \
- | awk -f print_1.awk \
- | sort >observed &&
+
+ git -C r2 verify-pack -v ../filter.pack >verify_result &&
+ grep blob verify_result |
+ awk -f print_1.awk |
+ sort >observed &&
+
nr=$(wc -l <observed) &&
test 0 -eq $nr
'
test_expect_success 'verify blob:limit=1001' '
- git -C r2 ls-files -s large.1000 \
- | awk -f print_2.awk \
- | sort >expected &&
+ git -C r2 ls-files -s large.1000 >ls_files_result &&
+ awk -f print_2.awk ls_files_result |
+ sort >expected &&
+
git -C r2 pack-objects --rev --stdout --filter=blob:limit=1001 >filter.pack <<-EOF &&
HEAD
EOF
git -C r2 index-pack ../filter.pack &&
- git -C r2 verify-pack -v ../filter.pack \
- | grep blob \
- | awk -f print_1.awk \
- | sort >observed &&
- test_cmp observed expected
+
+ git -C r2 verify-pack -v ../filter.pack >verify_result &&
+ grep blob verify_result |
+ awk -f print_1.awk |
+ sort >observed &&
+
+ test_cmp expected observed
'
test_expect_success 'verify blob:limit=10001' '
- git -C r2 ls-files -s large.1000 large.10000 \
- | awk -f print_2.awk \
- | sort >expected &&
+ git -C r2 ls-files -s large.1000 large.10000 >ls_files_result &&
+ awk -f print_2.awk ls_files_result |
+ sort >expected &&
+
git -C r2 pack-objects --rev --stdout --filter=blob:limit=10001 >filter.pack <<-EOF &&
HEAD
EOF
git -C r2 index-pack ../filter.pack &&
- git -C r2 verify-pack -v ../filter.pack \
- | grep blob \
- | awk -f print_1.awk \
- | sort >observed &&
- test_cmp observed expected
+
+ git -C r2 verify-pack -v ../filter.pack >verify_result &&
+ grep blob verify_result |
+ awk -f print_1.awk |
+ sort >observed &&
+
+ test_cmp expected observed
'
test_expect_success 'verify blob:limit=1k' '
- git -C r2 ls-files -s large.1000 \
- | awk -f print_2.awk \
- | sort >expected &&
+ git -C r2 ls-files -s large.1000 >ls_files_result &&
+ awk -f print_2.awk ls_files_result |
+ sort >expected &&
+
git -C r2 pack-objects --rev --stdout --filter=blob:limit=1k >filter.pack <<-EOF &&
HEAD
EOF
git -C r2 index-pack ../filter.pack &&
- git -C r2 verify-pack -v ../filter.pack \
- | grep blob \
- | awk -f print_1.awk \
- | sort >observed &&
- test_cmp observed expected
+
+ git -C r2 verify-pack -v ../filter.pack >verify_result &&
+ grep blob verify_result |
+ awk -f print_1.awk |
+ sort >observed &&
+
+ test_cmp expected observed
'
test_expect_success 'verify explicitly specifying oversized blob in input' '
- git -C r2 ls-files -s large.1000 large.10000 \
- | awk -f print_2.awk \
- | sort >expected &&
+ git -C r2 ls-files -s large.1000 large.10000 >ls_files_result &&
+ awk -f print_2.awk ls_files_result |
+ sort >expected &&
+
git -C r2 pack-objects --rev --stdout --filter=blob:limit=1k >filter.pack <<-EOF &&
HEAD
$(git -C r2 rev-parse HEAD:large.10000)
EOF
git -C r2 index-pack ../filter.pack &&
- git -C r2 verify-pack -v ../filter.pack \
- | grep blob \
- | awk -f print_1.awk \
- | sort >observed &&
- test_cmp observed expected
+
+ git -C r2 verify-pack -v ../filter.pack >verify_result &&
+ grep blob verify_result |
+ awk -f print_1.awk |
+ sort >observed &&
+
+ test_cmp expected observed
'
test_expect_success 'verify blob:limit=1m' '
- git -C r2 ls-files -s large.1000 large.10000 \
- | awk -f print_2.awk \
- | sort >expected &&
+ git -C r2 ls-files -s large.1000 large.10000 >ls_files_result &&
+ awk -f print_2.awk ls_files_result |
+ sort >expected &&
+
git -C r2 pack-objects --rev --stdout --filter=blob:limit=1m >filter.pack <<-EOF &&
HEAD
EOF
git -C r2 index-pack ../filter.pack &&
- git -C r2 verify-pack -v ../filter.pack \
- | grep blob \
- | awk -f print_1.awk \
- | sort >observed &&
- test_cmp observed expected
+
+ git -C r2 verify-pack -v ../filter.pack >verify_result &&
+ grep blob verify_result |
+ awk -f print_1.awk |
+ sort >observed &&
+
+ test_cmp expected observed
'
test_expect_success 'verify normal and blob:limit packfiles have same commits/trees' '
- git -C r2 verify-pack -v ../all.pack \
- | grep -E "commit|tree" \
- | awk -f print_1.awk \
- | sort >expected &&
- git -C r2 verify-pack -v ../filter.pack \
- | grep -E "commit|tree" \
- | awk -f print_1.awk \
- | sort >observed &&
- test_cmp observed expected
+ git -C r2 verify-pack -v ../all.pack >verify_result &&
+ grep -E "commit|tree" verify_result |
+ awk -f print_1.awk |
+ sort >expected &&
+
+ git -C r2 verify-pack -v ../filter.pack >verify_result &&
+ grep -E "commit|tree" verify_result |
+ awk -f print_1.awk |
+ sort >observed &&
+
+ test_cmp expected observed
'
# Test sparse:path=<path> filter.
test_expect_success 'verify blob count in normal packfile' '
git -C r3 ls-files -s sparse1 sparse2 dir1/sparse1 dir1/sparse2 \
- | awk -f print_2.awk \
- | sort >expected &&
+ >ls_files_result &&
+ awk -f print_2.awk ls_files_result |
+ sort >expected &&
+
git -C r3 pack-objects --rev --stdout >all.pack <<-EOF &&
HEAD
EOF
git -C r3 index-pack ../all.pack &&
- git -C r3 verify-pack -v ../all.pack \
- | grep blob \
- | awk -f print_1.awk \
- | sort >observed &&
- test_cmp observed expected
+
+ git -C r3 verify-pack -v ../all.pack >verify_result &&
+ grep blob verify_result |
+ awk -f print_1.awk |
+ sort >observed &&
+
+ test_cmp expected observed
'
test_expect_success 'verify sparse:path=pattern1' '
- git -C r3 ls-files -s dir1/sparse1 dir1/sparse2 \
- | awk -f print_2.awk \
- | sort >expected &&
+ git -C r3 ls-files -s dir1/sparse1 dir1/sparse2 >ls_files_result &&
+ awk -f print_2.awk ls_files_result |
+ sort >expected &&
+
git -C r3 pack-objects --rev --stdout --filter=sparse:path=../pattern1 >filter.pack <<-EOF &&
HEAD
EOF
git -C r3 index-pack ../filter.pack &&
- git -C r3 verify-pack -v ../filter.pack \
- | grep blob \
- | awk -f print_1.awk \
- | sort >observed &&
- test_cmp observed expected
+
+ git -C r3 verify-pack -v ../filter.pack >verify_result &&
+ grep blob verify_result |
+ awk -f print_1.awk |
+ sort >observed &&
+
+ test_cmp expected observed
'
test_expect_success 'verify normal and sparse:path=pattern1 packfiles have same commits/trees' '
- git -C r3 verify-pack -v ../all.pack \
- | grep -E "commit|tree" \
- | awk -f print_1.awk \
- | sort >expected &&
- git -C r3 verify-pack -v ../filter.pack \
- | grep -E "commit|tree" \
- | awk -f print_1.awk \
- | sort >observed &&
- test_cmp observed expected
+ git -C r3 verify-pack -v ../all.pack >verify_result &&
+ grep -E "commit|tree" verify_result |
+ awk -f print_1.awk |
+ sort >expected &&
+
+ git -C r3 verify-pack -v ../filter.pack >verify_result &&
+ grep -E "commit|tree" verify_result |
+ awk -f print_1.awk |
+ sort >observed &&
+
+ test_cmp expected observed
'
test_expect_success 'verify sparse:path=pattern2' '
- git -C r3 ls-files -s sparse1 dir1/sparse1 \
- | awk -f print_2.awk \
- | sort >expected &&
+ git -C r3 ls-files -s sparse1 dir1/sparse1 >ls_files_result &&
+ awk -f print_2.awk ls_files_result |
+ sort >expected &&
+
git -C r3 pack-objects --rev --stdout --filter=sparse:path=../pattern2 >filter.pack <<-EOF &&
HEAD
EOF
git -C r3 index-pack ../filter.pack &&
- git -C r3 verify-pack -v ../filter.pack \
- | grep blob \
- | awk -f print_1.awk \
- | sort >observed &&
- test_cmp observed expected
+
+ git -C r3 verify-pack -v ../filter.pack >verify_result &&
+ grep blob verify_result |
+ awk -f print_1.awk |
+ sort >observed &&
+
+ test_cmp expected observed
'
test_expect_success 'verify normal and sparse:path=pattern2 packfiles have same commits/trees' '
- git -C r3 verify-pack -v ../all.pack \
- | grep -E "commit|tree" \
- | awk -f print_1.awk \
- | sort >expected &&
- git -C r3 verify-pack -v ../filter.pack \
- | grep -E "commit|tree" \
- | awk -f print_1.awk \
- | sort >observed &&
- test_cmp observed expected
+ git -C r3 verify-pack -v ../all.pack >verify_result &&
+ grep -E "commit|tree" verify_result |
+ awk -f print_1.awk |
+ sort >expected &&
+
+ git -C r3 verify-pack -v ../filter.pack >verify_result &&
+ grep -E "commit|tree" verify_result |
+ awk -f print_1.awk |
+ sort >observed &&
+
+ test_cmp expected observed
'
# Test sparse:oid=<oid-ish> filter.
test_expect_success 'verify blob count in normal packfile' '
git -C r4 ls-files -s pattern sparse1 sparse2 dir1/sparse1 dir1/sparse2 \
- | awk -f print_2.awk \
- | sort >expected &&
+ >ls_files_result &&
+ awk -f print_2.awk ls_files_result |
+ sort >expected &&
+
git -C r4 pack-objects --rev --stdout >all.pack <<-EOF &&
HEAD
EOF
git -C r4 index-pack ../all.pack &&
- git -C r4 verify-pack -v ../all.pack \
- | grep blob \
- | awk -f print_1.awk \
- | sort >observed &&
- test_cmp observed expected
+
+ git -C r4 verify-pack -v ../all.pack >verify_result &&
+ grep blob verify_result |
+ awk -f print_1.awk |
+ sort >observed &&
+
+ test_cmp expected observed
'
test_expect_success 'verify sparse:oid=OID' '
- git -C r4 ls-files -s dir1/sparse1 dir1/sparse2 \
- | awk -f print_2.awk \
- | sort >expected &&
+ git -C r4 ls-files -s dir1/sparse1 dir1/sparse2 >ls_files_result &&
+ awk -f print_2.awk ls_files_result |
+ sort >expected &&
+
oid=$(git -C r4 ls-files -s pattern | awk -f print_2.awk) &&
git -C r4 pack-objects --rev --stdout --filter=sparse:oid=$oid >filter.pack <<-EOF &&
HEAD
EOF
git -C r4 index-pack ../filter.pack &&
- git -C r4 verify-pack -v ../filter.pack \
- | grep blob \
- | awk -f print_1.awk \
- | sort >observed &&
- test_cmp observed expected
+
+ git -C r4 verify-pack -v ../filter.pack >verify_result &&
+ grep blob verify_result |
+ awk -f print_1.awk |
+ sort >observed &&
+
+ test_cmp expected observed
'
test_expect_success 'verify sparse:oid=oid-ish' '
- git -C r4 ls-files -s dir1/sparse1 dir1/sparse2 \
- | awk -f print_2.awk \
- | sort >expected &&
+ git -C r4 ls-files -s dir1/sparse1 dir1/sparse2 >ls_files_result &&
+ awk -f print_2.awk ls_files_result |
+ sort >expected &&
+
git -C r4 pack-objects --rev --stdout --filter=sparse:oid=master:pattern >filter.pack <<-EOF &&
HEAD
EOF
git -C r4 index-pack ../filter.pack &&
- git -C r4 verify-pack -v ../filter.pack \
- | grep blob \
- | awk -f print_1.awk \
- | sort >observed &&
- test_cmp observed expected
+
+ git -C r4 verify-pack -v ../filter.pack >verify_result &&
+ grep blob verify_result |
+ awk -f print_1.awk |
+ sort >observed &&
+
+ test_cmp expected observed
'
# Delete some loose objects and use pack-objects, but WITHOUT any filtering.
test_expect_success 'setup r1 - delete loose blobs' '
git -C r1 ls-files -s file.1 file.2 file.3 file.4 file.5 \
- | awk -f print_2.awk \
- | sort >expected &&
+ >ls_files_result &&
+ awk -f print_2.awk ls_files_result |
+ sort >expected &&
+
for id in `cat expected | sed "s|..|&/|"`
do
rm r1/.git/objects/$id
cd "$TRASH_DIRECTORY/full" &&
git init &&
git config core.commitGraph true &&
- objdir=".git/objects"
+ objdir=".git/objects" &&
+ test_oid_init
'
test_expect_success 'verify graph with no graph file' '
graph_git_two_modes() {
git -c core.commitGraph=true $1 >output
git -c core.commitGraph=false $1 >expect
- test_cmp output expect
+ test_cmp expect output
}
graph_git_behavior() {
test_cmp_bin commit-graph-after-gc $objdir/info/commit-graph
'
+test_expect_success 'replace-objects invalidates commit-graph' '
+ cd "$TRASH_DIRECTORY" &&
+ test_when_finished rm -rf replace &&
+ git clone full replace &&
+ (
+ cd replace &&
+ git commit-graph write --reachable &&
+ test_path_is_file .git/objects/info/commit-graph &&
+ git replace HEAD~1 HEAD~2 &&
+ git -c core.commitGraph=false log >expect &&
+ git -c core.commitGraph=true log >actual &&
+ test_cmp expect actual &&
+ git commit-graph write --reachable &&
+ git -c core.commitGraph=false --no-replace-objects log >expect &&
+ git -c core.commitGraph=true --no-replace-objects log >actual &&
+ test_cmp expect actual &&
+ rm -rf .git/objects/info/commit-graph &&
+ git commit-graph write --reachable &&
+ test_path_is_file .git/objects/info/commit-graph
+ )
+'
+
+test_expect_success 'commit grafts invalidate commit-graph' '
+ cd "$TRASH_DIRECTORY" &&
+ test_when_finished rm -rf graft &&
+ git clone full graft &&
+ (
+ cd graft &&
+ git commit-graph write --reachable &&
+ test_path_is_file .git/objects/info/commit-graph &&
+ H1=$(git rev-parse --verify HEAD~1) &&
+ H3=$(git rev-parse --verify HEAD~3) &&
+ echo "$H1 $H3" >.git/info/grafts &&
+ git -c core.commitGraph=false log >expect &&
+ git -c core.commitGraph=true log >actual &&
+ test_cmp expect actual &&
+ git commit-graph write --reachable &&
+ git -c core.commitGraph=false --no-replace-objects log >expect &&
+ git -c core.commitGraph=true --no-replace-objects log >actual &&
+ test_cmp expect actual &&
+ rm -rf .git/objects/info/commit-graph &&
+ git commit-graph write --reachable &&
+ test_path_is_missing .git/objects/info/commit-graph
+ )
+'
+
+test_expect_success 'replace-objects invalidates commit-graph' '
+ cd "$TRASH_DIRECTORY" &&
+ test_when_finished rm -rf shallow &&
+ git clone --depth 2 "file://$TRASH_DIRECTORY/full" shallow &&
+ (
+ cd shallow &&
+ git commit-graph write --reachable &&
+ test_path_is_missing .git/objects/info/commit-graph &&
+ git fetch origin --unshallow &&
+ git commit-graph write --reachable &&
+ test_path_is_file .git/objects/info/commit-graph
+ )
+'
+
# the verify tests below expect the commit-graph to contain
# exactly the commits reachable from the commits/8 branch.
# If the file changes the set of commits in the list, then the
NUM_COMMITS=9
NUM_OCTOPUS_EDGES=2
-HASH_LEN=20
+HASH_LEN="$(test_oid rawsz)"
GRAPH_BYTE_VERSION=4
GRAPH_BYTE_HASH=5
GRAPH_BYTE_CHUNK_COUNT=6
--- /dev/null
+#!/bin/sh
+
+test_description='multi-pack-indexes'
+. ./test-lib.sh
+
+objdir=.git/objects
+
+midx_read_expect () {
+ NUM_PACKS=$1
+ NUM_OBJECTS=$2
+ NUM_CHUNKS=$3
+ OBJECT_DIR=$4
+ EXTRA_CHUNKS="$5"
+ {
+ cat <<-EOF &&
+ header: 4d494458 1 $NUM_CHUNKS $NUM_PACKS
+ chunks: pack-names oid-fanout oid-lookup object-offsets$EXTRA_CHUNKS
+ num_objects: $NUM_OBJECTS
+ packs:
+ EOF
+ if test $NUM_PACKS -ge 1
+ then
+ ls $OBJECT_DIR/pack/ | grep idx | sort
+ fi &&
+ printf "object-dir: $OBJECT_DIR\n"
+ } >expect &&
+ test-tool read-midx $OBJECT_DIR >actual &&
+ test_cmp expect actual
+}
+
+test_expect_success 'write midx with no packs' '
+ test_when_finished rm -f pack/multi-pack-index &&
+ git multi-pack-index --object-dir=. write &&
+ midx_read_expect 0 0 4 .
+'
+
+generate_objects () {
+ i=$1
+ iii=$(printf '%03i' $i)
+ {
+ test-tool genrandom "bar" 200 &&
+ test-tool genrandom "baz $iii" 50
+ } >wide_delta_$iii &&
+ {
+ test-tool genrandom "foo"$i 100 &&
+ test-tool genrandom "foo"$(( $i + 1 )) 100 &&
+ test-tool genrandom "foo"$(( $i + 2 )) 100
+ } >deep_delta_$iii &&
+ {
+ echo $iii &&
+ test-tool genrandom "$iii" 8192
+ } >file_$iii &&
+ git update-index --add file_$iii deep_delta_$iii wide_delta_$iii
+}
+
+commit_and_list_objects () {
+ {
+ echo 101 &&
+ test-tool genrandom 100 8192;
+ } >file_101 &&
+ git update-index --add file_101 &&
+ tree=$(git write-tree) &&
+ commit=$(git commit-tree $tree -p HEAD</dev/null) &&
+ {
+ echo $tree &&
+ git ls-tree $tree | sed -e "s/.* \\([0-9a-f]*\\) .*/\\1/"
+ } >obj-list &&
+ git reset --hard $commit
+}
+
+test_expect_success 'create objects' '
+ test_commit initial &&
+ for i in $(test_seq 1 5)
+ do
+ generate_objects $i
+ done &&
+ commit_and_list_objects
+'
+
+test_expect_success 'write midx with one v1 pack' '
+ pack=$(git pack-objects --index-version=1 $objdir/pack/test <obj-list) &&
+ test_when_finished rm $objdir/pack/test-$pack.pack \
+ $objdir/pack/test-$pack.idx $objdir/pack/multi-pack-index &&
+ git multi-pack-index --object-dir=$objdir write &&
+ midx_read_expect 1 18 4 $objdir
+'
+
+midx_git_two_modes () {
+ if [ "$2" = "sorted" ]
+ then
+ git -c core.multiPackIndex=false $1 | sort >expect &&
+ git -c core.multiPackIndex=true $1 | sort >actual
+ else
+ git -c core.multiPackIndex=false $1 >expect &&
+ git -c core.multiPackIndex=true $1 >actual
+ fi &&
+ test_cmp expect actual
+}
+
+compare_results_with_midx () {
+ MSG=$1
+ test_expect_success "check normal git operations: $MSG" '
+ midx_git_two_modes "rev-list --objects --all" &&
+ midx_git_two_modes "log --raw" &&
+ midx_git_two_modes "count-objects --verbose" &&
+ midx_git_two_modes "cat-file --batch-all-objects --buffer --batch-check" &&
+ midx_git_two_modes "cat-file --batch-all-objects --buffer --batch-check --unsorted" sorted
+ '
+}
+
+test_expect_success 'write midx with one v2 pack' '
+ git pack-objects --index-version=2,0x40 $objdir/pack/test <obj-list &&
+ git multi-pack-index --object-dir=$objdir write &&
+ midx_read_expect 1 18 4 $objdir
+'
+
+compare_results_with_midx "one v2 pack"
+
+test_expect_success 'add more objects' '
+ for i in $(test_seq 6 10)
+ do
+ generate_objects $i
+ done &&
+ commit_and_list_objects
+'
+
+test_expect_success 'write midx with two packs' '
+ git pack-objects --index-version=1 $objdir/pack/test-2 <obj-list &&
+ git multi-pack-index --object-dir=$objdir write &&
+ midx_read_expect 2 34 4 $objdir
+'
+
+compare_results_with_midx "two packs"
+
+test_expect_success 'add more packs' '
+ for j in $(test_seq 11 20)
+ do
+ generate_objects $j &&
+ commit_and_list_objects &&
+ git pack-objects --index-version=2 $objdir/pack/test-pack <obj-list
+ done
+'
+
+compare_results_with_midx "mixed mode (two packs + extra)"
+
+test_expect_success 'write midx with twelve packs' '
+ git multi-pack-index --object-dir=$objdir write &&
+ midx_read_expect 12 74 4 $objdir
+'
+
+compare_results_with_midx "twelve packs"
+
+test_expect_success 'verify multi-pack-index success' '
+ git multi-pack-index verify --object-dir=$objdir
+'
+
+# usage: corrupt_midx_and_verify <pos> <data> <objdir> <string>
+corrupt_midx_and_verify() {
+ POS=$1 &&
+ DATA="${2:-\0}" &&
+ OBJDIR=$3 &&
+ GREPSTR="$4" &&
+ COMMAND="$5" &&
+ if test -z "$COMMAND"
+ then
+ COMMAND="git multi-pack-index verify --object-dir=$OBJDIR"
+ fi &&
+ FILE=$OBJDIR/pack/multi-pack-index &&
+ chmod a+w $FILE &&
+ test_when_finished mv midx-backup $FILE &&
+ cp $FILE midx-backup &&
+ printf "$DATA" | dd of="$FILE" bs=1 seek="$POS" conv=notrunc &&
+ test_must_fail $COMMAND 2>test_err &&
+ grep -v "^+" test_err >err &&
+ test_i18ngrep "$GREPSTR" err
+}
+
+test_expect_success 'verify bad signature' '
+ corrupt_midx_and_verify 0 "\00" $objdir \
+ "multi-pack-index signature"
+'
+
+HASH_LEN=20
+NUM_OBJECTS=74
+MIDX_BYTE_VERSION=4
+MIDX_BYTE_OID_VERSION=5
+MIDX_BYTE_CHUNK_COUNT=6
+MIDX_HEADER_SIZE=12
+MIDX_BYTE_CHUNK_ID=$MIDX_HEADER_SIZE
+MIDX_BYTE_CHUNK_OFFSET=$(($MIDX_HEADER_SIZE + 4))
+MIDX_NUM_CHUNKS=5
+MIDX_CHUNK_LOOKUP_WIDTH=12
+MIDX_OFFSET_PACKNAMES=$(($MIDX_HEADER_SIZE + \
+ $MIDX_NUM_CHUNKS * $MIDX_CHUNK_LOOKUP_WIDTH))
+MIDX_BYTE_PACKNAME_ORDER=$(($MIDX_OFFSET_PACKNAMES + 2))
+MIDX_OFFSET_OID_FANOUT=$(($MIDX_OFFSET_PACKNAMES + 652))
+MIDX_OID_FANOUT_WIDTH=4
+MIDX_BYTE_OID_FANOUT_ORDER=$((MIDX_OFFSET_OID_FANOUT + 250 * $MIDX_OID_FANOUT_WIDTH + 1))
+MIDX_OFFSET_OID_LOOKUP=$(($MIDX_OFFSET_OID_FANOUT + 256 * $MIDX_OID_FANOUT_WIDTH))
+MIDX_BYTE_OID_LOOKUP=$(($MIDX_OFFSET_OID_LOOKUP + 16 * $HASH_LEN))
+MIDX_OFFSET_OBJECT_OFFSETS=$(($MIDX_OFFSET_OID_LOOKUP + $NUM_OBJECTS * $HASH_LEN))
+MIDX_OFFSET_WIDTH=8
+MIDX_BYTE_PACK_INT_ID=$(($MIDX_OFFSET_OBJECT_OFFSETS + 16 * $MIDX_OFFSET_WIDTH + 2))
+MIDX_BYTE_OFFSET=$(($MIDX_OFFSET_OBJECT_OFFSETS + 16 * $MIDX_OFFSET_WIDTH + 6))
+
+test_expect_success 'verify bad version' '
+ corrupt_midx_and_verify $MIDX_BYTE_VERSION "\00" $objdir \
+ "multi-pack-index version"
+'
+
+test_expect_success 'verify bad OID version' '
+ corrupt_midx_and_verify $MIDX_BYTE_OID_VERSION "\02" $objdir \
+ "hash version"
+'
+
+test_expect_success 'verify truncated chunk count' '
+ corrupt_midx_and_verify $MIDX_BYTE_CHUNK_COUNT "\01" $objdir \
+ "missing required"
+'
+
+test_expect_success 'verify extended chunk count' '
+ corrupt_midx_and_verify $MIDX_BYTE_CHUNK_COUNT "\07" $objdir \
+ "terminating multi-pack-index chunk id appears earlier than expected"
+'
+
+test_expect_success 'verify missing required chunk' '
+ corrupt_midx_and_verify $MIDX_BYTE_CHUNK_ID "\01" $objdir \
+ "missing required"
+'
+
+test_expect_success 'verify invalid chunk offset' '
+ corrupt_midx_and_verify $MIDX_BYTE_CHUNK_OFFSET "\01" $objdir \
+ "invalid chunk offset (too large)"
+'
+
+test_expect_success 'verify packnames out of order' '
+ corrupt_midx_and_verify $MIDX_BYTE_PACKNAME_ORDER "z" $objdir \
+ "pack names out of order"
+'
+
+test_expect_success 'verify packnames out of order' '
+ corrupt_midx_and_verify $MIDX_BYTE_PACKNAME_ORDER "a" $objdir \
+ "failed to load pack"
+'
+
+test_expect_success 'verify oid fanout out of order' '
+ corrupt_midx_and_verify $MIDX_BYTE_OID_FANOUT_ORDER "\01" $objdir \
+ "oid fanout out of order"
+'
+
+test_expect_success 'verify oid lookup out of order' '
+ corrupt_midx_and_verify $MIDX_BYTE_OID_LOOKUP "\00" $objdir \
+ "oid lookup out of order"
+'
+
+test_expect_success 'verify incorrect pack-int-id' '
+ corrupt_midx_and_verify $MIDX_BYTE_PACK_INT_ID "\07" $objdir \
+ "bad pack-int-id"
+'
+
+test_expect_success 'verify incorrect offset' '
+ corrupt_midx_and_verify $MIDX_BYTE_OFFSET "\07" $objdir \
+ "incorrect object offset"
+'
+
+test_expect_success 'git-fsck incorrect offset' '
+ corrupt_midx_and_verify $MIDX_BYTE_OFFSET "\07" $objdir \
+ "incorrect object offset" \
+ "git -c core.multipackindex=true fsck"
+'
+
+test_expect_success 'repack removes multi-pack-index' '
+ test_path_is_file $objdir/pack/multi-pack-index &&
+ git repack -adf &&
+ test_path_is_missing $objdir/pack/multi-pack-index
+'
+
+compare_results_with_midx "after repack"
+
+test_expect_success 'multi-pack-index and pack-bitmap' '
+ git -c repack.writeBitmaps=true repack -ad &&
+ git multi-pack-index write &&
+ git rev-list --test-bitmap HEAD
+'
+
+test_expect_success 'multi-pack-index and alternates' '
+ git init --bare alt.git &&
+ echo $(pwd)/alt.git/objects >.git/objects/info/alternates &&
+ echo content1 >file1 &&
+ altblob=$(GIT_DIR=alt.git git hash-object -w file1) &&
+ git cat-file blob $altblob &&
+ git rev-list --all
+'
+
+compare_results_with_midx "with alternate (local midx)"
+
+test_expect_success 'multi-pack-index in an alternate' '
+ mv .git/objects/pack/* alt.git/objects/pack &&
+ test_commit add_local_objects &&
+ git repack --local &&
+ git multi-pack-index write &&
+ midx_read_expect 1 3 4 $objdir &&
+ git reset --hard HEAD~1 &&
+ rm -f .git/objects/pack/*
+'
+
+compare_results_with_midx "with alternate (remote midx)"
+
+# usage: corrupt_data <file> <pos> [<data>]
+corrupt_data () {
+ file=$1
+ pos=$2
+ data="${3:-\0}"
+ printf "$data" | dd of="$file" bs=1 seek="$pos" conv=notrunc
+}
+
+# Force 64-bit offsets by manipulating the idx file.
+# This makes the IDX file _incorrect_ so be careful to clean up after!
+test_expect_success 'force some 64-bit offsets with pack-objects' '
+ mkdir objects64 &&
+ mkdir objects64/pack &&
+ for i in $(test_seq 1 11)
+ do
+ generate_objects 11
+ done &&
+ commit_and_list_objects &&
+ pack64=$(git pack-objects --index-version=2,0x40 objects64/pack/test-64 <obj-list) &&
+ idx64=objects64/pack/test-64-$pack64.idx &&
+ chmod u+w $idx64 &&
+ corrupt_data $idx64 2999 "\02" &&
+ midx64=$(git multi-pack-index --object-dir=objects64 write) &&
+ midx_read_expect 1 63 5 objects64 " large-offsets"
+'
+
+test_expect_success 'verify multi-pack-index with 64-bit offsets' '
+ git multi-pack-index verify --object-dir=objects64
+'
+
+NUM_OBJECTS=63
+MIDX_OFFSET_OID_FANOUT=$((MIDX_OFFSET_PACKNAMES + 54))
+MIDX_OFFSET_OID_LOOKUP=$((MIDX_OFFSET_OID_FANOUT + 256 * $MIDX_OID_FANOUT_WIDTH))
+MIDX_OFFSET_OBJECT_OFFSETS=$(($MIDX_OFFSET_OID_LOOKUP + $NUM_OBJECTS * $HASH_LEN))
+MIDX_OFFSET_LARGE_OFFSETS=$(($MIDX_OFFSET_OBJECT_OFFSETS + $NUM_OBJECTS * $MIDX_OFFSET_WIDTH))
+MIDX_BYTE_LARGE_OFFSET=$(($MIDX_OFFSET_LARGE_OFFSETS + 3))
+
+test_expect_success 'verify incorrect 64-bit offset' '
+ corrupt_midx_and_verify $MIDX_BYTE_LARGE_OFFSET "\07" objects64 \
+ "incorrect object offset"
+'
+
+test_done
--- /dev/null
+#!/bin/sh
+
+test_description='exercise delta islands'
+. ./test-lib.sh
+
+# returns true iff $1 is a delta based on $2
+is_delta_base () {
+ delta_base=$(echo "$1" | git cat-file --batch-check='%(deltabase)') &&
+ echo >&2 "$1 has base $delta_base" &&
+ test "$delta_base" = "$2"
+}
+
+# generate a commit on branch $1 with a single file, "file", whose
+# content is mostly based on the seed $2, but with a unique bit
+# of content $3 appended. This should allow us to see whether
+# blobs of different refs delta against each other.
+commit() {
+ blob=$({ test-tool genrandom "$2" 10240 && echo "$3"; } |
+ git hash-object -w --stdin) &&
+ tree=$(printf '100644 blob %s\tfile\n' "$blob" | git mktree) &&
+ commit=$(echo "$2-$3" | git commit-tree "$tree" ${4:+-p "$4"}) &&
+ git update-ref "refs/heads/$1" "$commit" &&
+ eval "$1"'=$(git rev-parse $1:file)' &&
+ eval "echo >&2 $1=\$$1"
+}
+
+test_expect_success 'setup commits' '
+ commit one seed 1 &&
+ commit two seed 12
+'
+
+# Note: This is heavily dependent on the "prefer larger objects as base"
+# heuristic.
+test_expect_success 'vanilla repack deltas one against two' '
+ git repack -adf &&
+ is_delta_base $one $two
+'
+
+test_expect_success 'island repack with no island definition is vanilla' '
+ git repack -adfi &&
+ is_delta_base $one $two
+'
+
+test_expect_success 'island repack with no matches is vanilla' '
+ git -c "pack.island=refs/foo" repack -adfi &&
+ is_delta_base $one $two
+'
+
+test_expect_success 'separate islands disallows delta' '
+ git -c "pack.island=refs/heads/(.*)" repack -adfi &&
+ ! is_delta_base $one $two &&
+ ! is_delta_base $two $one
+'
+
+test_expect_success 'same island allows delta' '
+ git -c "pack.island=refs/heads" repack -adfi &&
+ is_delta_base $one $two
+'
+
+test_expect_success 'coalesce same-named islands' '
+ git \
+ -c "pack.island=refs/(.*)/one" \
+ -c "pack.island=refs/(.*)/two" \
+ repack -adfi &&
+ is_delta_base $one $two
+'
+
+test_expect_success 'island restrictions drop reused deltas' '
+ git repack -adfi &&
+ is_delta_base $one $two &&
+ git -c "pack.island=refs/heads/(.*)" repack -adi &&
+ ! is_delta_base $one $two &&
+ ! is_delta_base $two $one
+'
+
+test_expect_success 'island regexes are left-anchored' '
+ git -c "pack.island=heads/(.*)" repack -adfi &&
+ is_delta_base $one $two
+'
+
+test_expect_success 'island regexes follow last-one-wins scheme' '
+ git \
+ -c "pack.island=refs/heads/(.*)" \
+ -c "pack.island=refs/heads/" \
+ repack -adfi &&
+ is_delta_base $one $two
+'
+
+test_expect_success 'setup shared history' '
+ commit root shared root &&
+ commit one shared 1 root &&
+ commit two shared 12-long root
+'
+
+# We know that $two will be preferred as a base from $one,
+# because we can transform it with a pure deletion.
+#
+# We also expect $root as a delta against $two by the "longest is base" rule.
+test_expect_success 'vanilla delta goes between branches' '
+ git repack -adf &&
+ is_delta_base $one $two &&
+ is_delta_base $root $two
+'
+
+# Here we should allow $one to base itself on $root; even though
+# they are in different islands, the objects in $root are in a superset
+# of islands compared to those in $one.
+#
+# Similarly, $two can delta against $root by our rules. And unlike $one,
+# in which we are just allowing it, the island rules actually put $root
+# as a possible base for $two, which it would not otherwise be (due to the size
+# sorting).
+test_expect_success 'deltas allowed against superset islands' '
+ git -c "pack.island=refs/heads/(.*)" repack -adfi &&
+ is_delta_base $one $root &&
+ is_delta_base $two $root
+'
+
+# We are going to test the packfile order here, so we again have to make some
+# assumptions. We assume that "$root", as part of our core "one", must come
+# before "$two". This should be guaranteed by the island code. However, for
+# this test to fail without islands, we are also assuming that it would not
+# otherwise do so. This is true by the current write order, which will put
+# commits (and their contents) before their parents.
+test_expect_success 'island core places core objects first' '
+ cat >expect <<-EOF &&
+ $root
+ $two
+ EOF
+ git -c "pack.island=refs/heads/(.*)" \
+ -c "pack.islandcore=one" \
+ repack -adfi &&
+ git verify-pack -v .git/objects/pack/*.pack |
+ cut -d" " -f1 |
+ egrep "$root|$two" >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'unmatched island core is not fatal' '
+ git -c "pack.islandcore=one" repack -adfi
+'
+
+test_done
--- /dev/null
+#!/bin/sh
+#
+# Copyright (c) 2018 Johannes Schindelin
+#
+
+test_description='git pack-object with "large" deltas
+
+'
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-pack.sh
+
+# Two similar-ish objects that we have computed deltas between.
+A=01d7713666f4de822776c7622c10f1b07de280dc
+B=e68fe8129b546b101aee9510c5328e7f21ca1d18
+
+test_expect_success 'setup' '
+ clear_packs &&
+ {
+ pack_header 2 &&
+ pack_obj $A $B &&
+ pack_obj $B
+ } >ab.pack &&
+ pack_trailer ab.pack &&
+ git index-pack --stdin <ab.pack
+'
+
+test_expect_success 'repack large deltas' '
+ printf "%s\\n" $A $B |
+ GIT_TEST_OE_DELTA_SIZE=2 git pack-objects tmp-pack
+'
+
+test_done
--- /dev/null
+#!/bin/sh
+
+test_description='git receive-pack with alternate ref filtering'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+ test_commit base &&
+ git clone -s --bare . fork &&
+ git checkout -b public/branch master &&
+ test_commit public &&
+ git checkout -b private/branch master &&
+ test_commit private
+'
+
+extract_haves () {
+ depacketize | perl -lne '/^(\S+) \.have/ and print $1'
+}
+
+test_expect_success 'with core.alternateRefsCommand' '
+ write_script fork/alternate-refs <<-\EOF &&
+ git --git-dir="$1" for-each-ref \
+ --format="%(objectname)" \
+ refs/heads/public/
+ EOF
+ test_config -C fork core.alternateRefsCommand ./alternate-refs &&
+ git rev-parse public/branch >expect &&
+ printf "0000" | git receive-pack fork >actual &&
+ extract_haves <actual >actual.haves &&
+ test_cmp expect actual.haves
+'
+
+test_expect_success 'with core.alternateRefsPrefixes' '
+ test_config -C fork core.alternateRefsPrefixes "refs/heads/private" &&
+ git rev-parse private/branch >expect &&
+ printf "0000" | git receive-pack fork >actual &&
+ extract_haves <actual >actual.haves &&
+ test_cmp expect actual.haves
+'
+
+test_done
case "$heads" in *B*)
git update-ref refs/heads/B "$BTIP";;
esac &&
- git symbolic-ref HEAD refs/heads/$(echo $heads \
- | sed -e "s/^\(.\).*$/\1/") &&
+
+ git symbolic-ref HEAD refs/heads/$(
+ echo $heads |
+ sed -e "s/^\(.\).*$/\1/"
+ ) &&
git fsck --full &&
This commit object intentionally broken
EOF
+test_expect_success 'setup bogus commit' '
+ commit="$(git hash-object -t commit -w --stdin <bogus-commit)"
+'
+
+test_expect_success 'fsck with no skipList input' '
+ test_must_fail git fsck 2>err &&
+ test_i18ngrep "missingEmail" err
+'
+
+test_expect_success 'setup sorted and unsorted skipLists' '
+ cat >SKIP.unsorted <<-EOF &&
+ 0000000000000000000000000000000000000004
+ 0000000000000000000000000000000000000002
+ $commit
+ 0000000000000000000000000000000000000001
+ 0000000000000000000000000000000000000003
+ EOF
+ sort SKIP.unsorted >SKIP.sorted
+'
+
+test_expect_success 'fsck with sorted skipList' '
+ git -c fsck.skipList=SKIP.sorted fsck
+'
+
+test_expect_success 'fsck with unsorted skipList' '
+ git -c fsck.skipList=SKIP.unsorted fsck
+'
+
test_expect_success 'fsck with invalid or bogus skipList input' '
git -c fsck.skipList=/dev/null -c fsck.missingEmail=ignore fsck &&
test_must_fail git -c fsck.skipList=does-not-exist -c fsck.missingEmail=ignore fsck 2>err &&
test_i18ngrep "Invalid SHA-1: \[core\]" err
'
+test_expect_success 'fsck with other accepted skipList input (comments & empty lines)' '
+ cat >SKIP.with-comment <<-EOF &&
+ # Some bad commit
+ 0000000000000000000000000000000000000001
+ EOF
+ test_must_fail git -c fsck.skipList=SKIP.with-comment fsck 2>err-with-comment &&
+ test_i18ngrep "missingEmail" err-with-comment &&
+ cat >SKIP.with-empty-line <<-EOF &&
+ 0000000000000000000000000000000000000001
+
+ 0000000000000000000000000000000000000002
+ EOF
+ test_must_fail git -c fsck.skipList=SKIP.with-empty-line fsck 2>err-with-empty-line &&
+ test_i18ngrep "missingEmail" err-with-empty-line
+'
+
+test_expect_success 'fsck no garbage output from comments & empty lines errors' '
+ test_line_count = 1 err-with-comment &&
+ test_line_count = 1 err-with-empty-line
+'
+
+test_expect_success 'fsck with invalid abbreviated skipList input' '
+ echo $commit | test_copy_bytes 20 >SKIP.abbreviated &&
+ test_must_fail git -c fsck.skipList=SKIP.abbreviated fsck 2>err-abbreviated &&
+ test_i18ngrep "^fatal: Invalid SHA-1: " err-abbreviated
+'
+
+test_expect_success 'fsck with exhaustive accepted skipList input (various types of comments etc.)' '
+ >SKIP.exhaustive &&
+ echo "# A commented line" >>SKIP.exhaustive &&
+ echo "" >>SKIP.exhaustive &&
+ echo " " >>SKIP.exhaustive &&
+ echo " # Comment after whitespace" >>SKIP.exhaustive &&
+ echo "$commit # Our bad commit (with leading whitespace and trailing comment)" >>SKIP.exhaustive &&
+ echo "# Some bad commit (leading whitespace)" >>SKIP.exhaustive &&
+ echo " 0000000000000000000000000000000000000001" >>SKIP.exhaustive &&
+ git -c fsck.skipList=SKIP.exhaustive fsck 2>err &&
+ test_must_be_empty err
+'
+
test_expect_success 'push with receive.fsck.skipList' '
- commit="$(git hash-object -t commit -w --stdin <bogus-commit)" &&
git push . $commit:refs/heads/bogus &&
rm -rf dst &&
git init dst &&
'
test_expect_success 'fetch with fetch.fsck.skipList' '
- commit="$(git hash-object -t commit -w --stdin <bogus-commit)" &&
refspec=refs/heads/bogus:refs/heads/bogus &&
git push . $commit:refs/heads/bogus &&
rm -rf dst &&
'
test_expect_success 'push with receive.fsck.missingEmail=warn' '
- commit="$(git hash-object -t commit -w --stdin <bogus-commit)" &&
git push . $commit:refs/heads/bogus &&
rm -rf dst &&
git init dst &&
'
test_expect_success 'fetch with fetch.fsck.missingEmail=warn' '
- commit="$(git hash-object -t commit -w --stdin <bogus-commit)" &&
refspec=refs/heads/bogus:refs/heads/bogus &&
git push . $commit:refs/heads/bogus &&
rm -rf dst &&
test_expect_success 'remove errors out early when deleting non-existent branch' '
(
cd test &&
- echo "fatal: No such remote: foo" >expect &&
+ echo "fatal: No such remote: '\''foo'\''" >expect &&
test_must_fail git remote rm foo 2>actual &&
test_i18ncmp expect actual
)
test_expect_success 'rename errors out early when deleting non-existent branch' '
(
cd test &&
- echo "fatal: No such remote: foo" >expect &&
+ echo "fatal: No such remote: '\''foo'\''" >expect &&
test_must_fail git remote rename foo bar 2>actual &&
test_i18ncmp expect actual
)
tag_type_description=$1
tag_args=$2
- test_expect_success 'force pushing required to update lightweight tag' "
+ test_expect_success "force pushing required to update $tag_type_description" "
mk_test testrepo heads/master &&
mk_child testrepo child1 &&
mk_child testrepo child2 &&
}
test_force_push_tag "lightweight tag" "-f"
-test_force_push_tag "annotated tag" "-f -a -m'msg'"
+test_force_push_tag "annotated tag" "-f -a -m'tag message'"
+
+test_force_fetch_tag () {
+ tag_type_description=$1
+ tag_args=$2
+
+ test_expect_success "fetch will not clobber an existing $tag_type_description without --force" "
+ mk_test testrepo heads/master &&
+ mk_child testrepo child1 &&
+ mk_child testrepo child2 &&
+ (
+ cd testrepo &&
+ git tag testTag &&
+ git -C ../child1 fetch origin tag testTag &&
+ >file1 &&
+ git add file1 &&
+ git commit -m 'file1' &&
+ git tag $tag_args testTag &&
+ test_must_fail git -C ../child1 fetch origin tag testTag &&
+ git -C ../child1 fetch origin '+refs/tags/*:refs/tags/*'
+ )
+ "
+}
+
+test_force_fetch_tag "lightweight tag" "-f"
+test_force_fetch_tag "annotated tag" "-f -a -m'tag message'"
test_expect_success 'push --porcelain' '
mk_empty testrepo &&
test $(git -C .. rev-parse master) = $(git rev-parse HEAD) &&
git diff --quiet &&
git diff --cached --quiet
- )
+ ) &&
+
+ # (6) updateInstead intervened by fast-forward check
+ test_must_fail git push void master^:master &&
+ test $(git -C void rev-parse HEAD) = $(git rev-parse master) &&
+ git -C void diff --quiet &&
+ git -C void diff --cached --quiet
'
test_expect_success 'updateInstead with push-to-checkout hook' '
setup_askpass_helper
-cat >exp <<EOF
-> GET /smart/repo.git/info/refs?service=git-upload-pack HTTP/1.1
-> Accept: */*
-> Accept-Encoding: ENCODINGS
-> Pragma: no-cache
-< HTTP/1.1 200 OK
-< Pragma: no-cache
-< Cache-Control: no-cache, max-age=0, must-revalidate
-< Content-Type: application/x-git-upload-pack-advertisement
-> POST /smart/repo.git/git-upload-pack HTTP/1.1
-> Accept-Encoding: ENCODINGS
-> Content-Type: application/x-git-upload-pack-request
-> Accept: application/x-git-upload-pack-result
-> Content-Length: xxx
-< HTTP/1.1 200 OK
-< Pragma: no-cache
-< Cache-Control: no-cache, max-age=0, must-revalidate
-< Content-Type: application/x-git-upload-pack-result
-EOF
test_expect_success 'clone http repository' '
+ cat >exp <<-\EOF &&
+ > GET /smart/repo.git/info/refs?service=git-upload-pack HTTP/1.1
+ > Accept: */*
+ > Accept-Encoding: ENCODINGS
+ > Pragma: no-cache
+ < HTTP/1.1 200 OK
+ < Pragma: no-cache
+ < Cache-Control: no-cache, max-age=0, must-revalidate
+ < Content-Type: application/x-git-upload-pack-advertisement
+ > POST /smart/repo.git/git-upload-pack HTTP/1.1
+ > Accept-Encoding: ENCODINGS
+ > Content-Type: application/x-git-upload-pack-request
+ > Accept: application/x-git-upload-pack-result
+ > Content-Length: xxx
+ < HTTP/1.1 200 OK
+ < Pragma: no-cache
+ < Cache-Control: no-cache, max-age=0, must-revalidate
+ < Content-Type: application/x-git-upload-pack-result
+ EOF
GIT_TRACE_CURL=true git clone --quiet $HTTPD_URL/smart/repo.git clone 2>err &&
test_cmp file clone/file &&
tr '\''\015'\'' Q <err |
test_cmp file clone/file
'
-cat >exp <<EOF
-GET /smart/repo.git/info/refs?service=git-upload-pack HTTP/1.1 200
-POST /smart/repo.git/git-upload-pack HTTP/1.1 200
-GET /smart/repo.git/info/refs?service=git-upload-pack HTTP/1.1 200
-POST /smart/repo.git/git-upload-pack HTTP/1.1 200
-EOF
test_expect_success 'used upload-pack service' '
+ cat >exp <<-\EOF &&
+ GET /smart/repo.git/info/refs?service=git-upload-pack HTTP/1.1 200
+ POST /smart/repo.git/git-upload-pack HTTP/1.1 200
+ GET /smart/repo.git/info/refs?service=git-upload-pack HTTP/1.1 200
+ POST /smart/repo.git/git-upload-pack HTTP/1.1 200
+ EOF
check_access_log exp
'
test_cmp expect actual
'
-cat >cookies.txt <<EOF
-127.0.0.1 FALSE /smart_cookies/ FALSE 0 othername othervalue
-EOF
-cat >expect_cookies.txt <<EOF
-
-127.0.0.1 FALSE /smart_cookies/ FALSE 0 othername othervalue
-127.0.0.1 FALSE /smart_cookies/repo.git/info/ FALSE 0 name value
-EOF
test_expect_success 'cookies stored in http.cookiefile when http.savecookies set' '
+ cat >cookies.txt <<-\EOF &&
+ 127.0.0.1 FALSE /smart_cookies/ FALSE 0 othername othervalue
+ EOF
+ sort >expect_cookies.txt <<-\EOF &&
+
+ 127.0.0.1 FALSE /smart_cookies/ FALSE 0 othername othervalue
+ 127.0.0.1 FALSE /smart_cookies/repo.git/info/ FALSE 0 name value
+ EOF
git config http.cookiefile cookies.txt &&
git config http.savecookies true &&
git ls-remote $HTTPD_URL/smart_cookies/repo.git master &&
- tail -3 cookies.txt >cookies_tail.txt &&
+ tail -3 cookies.txt | sort >cookies_tail.txt &&
test_cmp expect_cookies.txt cookies_tail.txt
'
test_cmp expect actual
'
+test_expect_success 'fetch by SHA-1 without tag following' '
+ SERVER="$HTTPD_DOCUMENT_ROOT_PATH/server" &&
+ rm -rf "$SERVER" client &&
+
+ git init "$SERVER" &&
+ test_commit -C "$SERVER" foo &&
+
+ git clone $HTTPD_URL/smart/server client &&
+
+ test_commit -C "$SERVER" bar &&
+ git -C "$SERVER" rev-parse bar >bar_hash &&
+ git -C client -c protocol.version=0 fetch \
+ --no-tags origin $(cat bar_hash)
+'
+
test_expect_success 'GIT_REDACT_COOKIES redacts cookies' '
rm -rf clone &&
echo "Set-Cookie: Foo=1" >cookies &&
test_expect_success 'empty CONTENT_LENGTH' '
env \
- QUERY_STRING=/repo.git/HEAD \
- PATH_TRANSLATED="$PWD"/.git/HEAD \
+ QUERY_STRING="service=git-receive-pack" \
+ PATH_TRANSLATED="$PWD"/.git/info/refs \
GIT_HTTP_EXPORT_ALL=TRUE \
REQUEST_METHOD=GET \
CONTENT_LENGTH="" \
git hash-object -w -t tree --stdin) &&
c=$(git commit-tree -m bogus $t) &&
git update-ref refs/heads/bogus $c &&
- git clone -b bogus . bogus
+ git clone -b bogus . bogus 2>warning
)
'
+test_expect_success !MINGW,!CYGWIN,CASE_INSENSITIVE_FS 'colliding file detection' '
+ grep X icasefs/warning &&
+ grep x icasefs/warning &&
+ test_i18ngrep "the following paths have collided" icasefs/warning
+'
+
partial_clone () {
SERVER="$1" &&
URL="$2" &&
test_expect_success '--single-branch while HEAD pointing at master' '
(
cd dir_master &&
- git fetch &&
+ git fetch --force &&
git for-each-ref refs/remotes/origin |
sed -e "/HEAD$/d" \
-e "s|/remotes/origin/|/heads/|" >../actual
test_cmp expect actual &&
(
cd dir_master &&
- git fetch --tags &&
+ git fetch --tags --force &&
git for-each-ref refs/tags >../actual
) &&
git for-each-ref refs/tags >expect &&
# confirm partial clone was registered in the local config.
test_expect_success 'do partial clone 1' '
git clone --no-checkout --filter=blob:none "file://$(pwd)/srv.bare" pc1 &&
- git -C pc1 rev-list HEAD --quiet --objects --missing=print \
- | awk -f print_1.awk \
- | sed "s/?//" \
- | sort >observed.oids &&
+
+ git -C pc1 rev-list --quiet --objects --missing=print HEAD >revs &&
+ awk -f print_1.awk revs |
+ sed "s/?//" |
+ sort >observed.oids &&
+
test_cmp expect_1.oids observed.oids &&
test "$(git -C pc1 config --local core.repositoryformatversion)" = "1" &&
test "$(git -C pc1 config --local extensions.partialclone)" = "origin" &&
# checkout master to force dynamic object fetch of blobs at HEAD.
test_expect_success 'verify checkout with dynamic object fetch' '
- git -C pc1 rev-list HEAD --quiet --objects --missing=print >observed &&
+ git -C pc1 rev-list --quiet --objects --missing=print HEAD >observed &&
test_line_count = 4 observed &&
git -C pc1 checkout master &&
- git -C pc1 rev-list HEAD --quiet --objects --missing=print >observed &&
+ git -C pc1 rev-list --quiet --objects --missing=print HEAD >observed &&
test_line_count = 0 observed
'
# have the new blobs.
test_expect_success 'partial fetch inherits filter settings' '
git -C pc1 fetch origin &&
- git -C pc1 rev-list master..origin/master --quiet --objects --missing=print >observed &&
+ git -C pc1 rev-list --quiet --objects --missing=print \
+ master..origin/master >observed &&
test_line_count = 5 observed
'
# we should only get 1 new blob (for the file in origin/master).
test_expect_success 'verify diff causes dynamic object fetch' '
git -C pc1 diff master..origin/master -- file.1.txt &&
- git -C pc1 rev-list master..origin/master --quiet --objects --missing=print >observed &&
+ git -C pc1 rev-list --quiet --objects --missing=print \
+ master..origin/master >observed &&
test_line_count = 4 observed
'
test_expect_success 'verify blame causes dynamic object fetch' '
git -C pc1 blame origin/master -- file.1.txt >observed.blame &&
test_cmp expect.blame observed.blame &&
- git -C pc1 rev-list master..origin/master --quiet --objects --missing=print >observed &&
+ git -C pc1 rev-list --quiet --objects --missing=print \
+ master..origin/master >observed &&
test_line_count = 0 observed
'
# Verify we have all the new blobs.
test_expect_success 'override inherited filter-spec using --no-filter' '
git -C pc1 fetch --no-filter origin &&
- git -C pc1 rev-list master..origin/master --quiet --objects --missing=print >observed &&
+ git -C pc1 rev-list --quiet --objects --missing=print \
+ master..origin/master >observed &&
test_line_count = 0 observed
'
# perhaps combined with a command in dry-run mode.
test_expect_success 'manual prefetch of missing objects' '
git -C pc1 fetch --filter=blob:none origin &&
- git -C pc1 rev-list master..origin/master --quiet --objects --missing=print \
- | awk -f print_1.awk \
- | sed "s/?//" \
- | sort >observed.oids &&
+
+ git -C pc1 rev-list --quiet --objects --missing=print \
+ master..origin/master >revs &&
+ awk -f print_1.awk revs |
+ sed "s/?//" |
+ sort >observed.oids &&
+
test_line_count = 6 observed.oids &&
git -C pc1 fetch-pack --stdin "file://$(pwd)/srv.bare" <observed.oids &&
- git -C pc1 rev-list master..origin/master --quiet --objects --missing=print \
- | awk -f print_1.awk \
- | sed "s/?//" \
- | sort >observed.oids &&
+
+ git -C pc1 rev-list --quiet --objects --missing=print \
+ master..origin/master >revs &&
+ awk -f print_1.awk revs |
+ sed "s/?//" |
+ sort >observed.oids &&
+
test_line_count = 0 observed.oids
'
grep "git index-pack.*--fsck-objects" trace
'
+test_expect_success 'use fsck before and after manually fetching a missing subtree' '
+ # push new commit so server has a subtree
+ mkdir src/dir &&
+ echo "in dir" >src/dir/file.txt &&
+ git -C src add dir/file.txt &&
+ git -C src commit -m "file in dir" &&
+ git -C src push -u srv master &&
+ SUBTREE=$(git -C src rev-parse HEAD:dir) &&
+
+ rm -rf dst &&
+ git clone --no-checkout --filter=tree:0 "file://$(pwd)/srv.bare" dst &&
+ git -C dst fsck &&
+
+ # Make sure we only have commits, and all trees and blobs are missing.
+ git -C dst rev-list --missing=allow-any --objects master \
+ >fetched_objects &&
+ awk -f print_1.awk fetched_objects |
+ xargs -n1 git -C dst cat-file -t >fetched_types &&
+
+ sort -u fetched_types >unique_types.observed &&
+ echo commit >unique_types.expected &&
+ test_cmp unique_types.expected unique_types.observed &&
+
+ # Auto-fetch a tree with cat-file.
+ git -C dst cat-file -p $SUBTREE >tree_contents &&
+ grep file.txt tree_contents &&
+
+ # fsck still works after an auto-fetch of a tree.
+ git -C dst fsck &&
+
+ # Auto-fetch all remaining trees and blobs with --missing=error
+ git -C dst rev-list --missing=error --objects master >fetched_objects &&
+ test_line_count = 70 fetched_objects &&
+
+ awk -f print_1.awk fetched_objects |
+ xargs -n1 git -C dst cat-file -t >fetched_types &&
+
+ sort -u fetched_types >unique_types.observed &&
+ test_write_lines blob commit tree >unique_types.expected &&
+ test_cmp unique_types.expected unique_types.observed
+'
+
test_expect_success 'partial clone fetches blobs pointed to by refs even if normally filtered out' '
rm -rf src dst &&
git init src &&
git -C dst fsck
'
+test_expect_success 'fetch what is specified on CLI even if already promised' '
+ rm -rf src dst.git &&
+ git init src &&
+ test_commit -C src foo &&
+ test_config -C src uploadpack.allowfilter 1 &&
+ test_config -C src uploadpack.allowanysha1inwant 1 &&
+
+ git hash-object --stdin <src/foo.t >blob &&
+
+ git clone --bare --filter=blob:none "file://$(pwd)/src" dst.git &&
+ git -C dst.git rev-list --objects --quiet --missing=print HEAD >missing_before &&
+ grep "?$(cat blob)" missing_before &&
+ git -C dst.git fetch origin $(cat blob) &&
+ git -C dst.git rev-list --objects --quiet --missing=print HEAD >missing_after &&
+ ! grep "?$(cat blob)" missing_after
+'
+
. "$TEST_DIRECTORY"/lib-httpd.sh
start_httpd
# Craft a packfile not including that blob.
git -C "$SERVER" rev-parse HEAD |
- git -C "$SERVER" pack-objects --stdout >incomplete.pack &&
+ git -C "$SERVER" pack-objects --stdout >incomplete.pack &&
# Replace the existing packfile with the crafted one. The protocol
# requires that the packfile be sent in sideband 1, hence the extra
EOF
git serve --advertise-capabilities >out &&
- test-pkt-line unpack <out >actual &&
- test_cmp actual expect
+ test-tool pkt-line unpack <out >actual &&
+ test_cmp expect actual
'
test_expect_success 'stateless-rpc flag does not list capabilities' '
# Empty request
- test-pkt-line pack >in <<-EOF &&
+ test-tool pkt-line pack >in <<-EOF &&
0000
EOF
git serve --stateless-rpc >out <in &&
'
test_expect_success 'request invalid capability' '
- test-pkt-line pack >in <<-EOF &&
+ test-tool pkt-line pack >in <<-EOF &&
foobar
0000
EOF
'
test_expect_success 'request with no command' '
- test-pkt-line pack >in <<-EOF &&
+ test-tool pkt-line pack >in <<-EOF &&
agent=git/test
0000
EOF
'
test_expect_success 'request invalid command' '
- test-pkt-line pack >in <<-EOF &&
+ test-tool pkt-line pack >in <<-EOF &&
command=foo
agent=git/test
0000
'
test_expect_success 'basics of ls-refs' '
- test-pkt-line pack >in <<-EOF &&
+ test-tool pkt-line pack >in <<-EOF &&
command=ls-refs
0000
EOF
EOF
git serve --stateless-rpc <in >out &&
- test-pkt-line unpack <out >actual &&
- test_cmp actual expect
+ test-tool pkt-line unpack <out >actual &&
+ test_cmp expect actual
'
test_expect_success 'basic ref-prefixes' '
- test-pkt-line pack >in <<-EOF &&
+ test-tool pkt-line pack >in <<-EOF &&
command=ls-refs
0001
ref-prefix refs/heads/master
EOF
git serve --stateless-rpc <in >out &&
- test-pkt-line unpack <out >actual &&
- test_cmp actual expect
+ test-tool pkt-line unpack <out >actual &&
+ test_cmp expect actual
'
test_expect_success 'refs/heads prefix' '
- test-pkt-line pack >in <<-EOF &&
+ test-tool pkt-line pack >in <<-EOF &&
command=ls-refs
0001
ref-prefix refs/heads/
EOF
git serve --stateless-rpc <in >out &&
- test-pkt-line unpack <out >actual &&
- test_cmp actual expect
+ test-tool pkt-line unpack <out >actual &&
+ test_cmp expect actual
'
test_expect_success 'peel parameter' '
- test-pkt-line pack >in <<-EOF &&
+ test-tool pkt-line pack >in <<-EOF &&
command=ls-refs
0001
peel
EOF
git serve --stateless-rpc <in >out &&
- test-pkt-line unpack <out >actual &&
- test_cmp actual expect
+ test-tool pkt-line unpack <out >actual &&
+ test_cmp expect actual
'
test_expect_success 'symrefs parameter' '
- test-pkt-line pack >in <<-EOF &&
+ test-tool pkt-line pack >in <<-EOF &&
command=ls-refs
0001
symrefs
EOF
git serve --stateless-rpc <in >out &&
- test-pkt-line unpack <out >actual &&
- test_cmp actual expect
+ test-tool pkt-line unpack <out >actual &&
+ test_cmp expect actual
'
test_expect_success 'sending server-options' '
- test-pkt-line pack >in <<-EOF &&
+ test-tool pkt-line pack >in <<-EOF &&
command=ls-refs
server-option=hello
server-option=world
EOF
git serve --stateless-rpc <in >out &&
- test-pkt-line unpack <out >actual &&
- test_cmp actual expect
+ test-tool pkt-line unpack <out >actual &&
+ test_cmp expect actual
'
test_expect_success 'unexpected lines are not allowed in fetch request' '
git init server &&
- test-pkt-line pack >in <<-EOF &&
+ test-tool pkt-line pack >in <<-EOF &&
command=fetch
0001
this-is-not-a-command
grep "git< version 2" log &&
git ls-remote --symref "$GIT_DAEMON_URL/parent" >expect &&
- test_cmp actual expect
+ test_cmp expect actual
'
test_expect_success 'ref advertisment is filtered with ls-remote using protocol v2' '
$(git -C "$daemon_parent" rev-parse refs/heads/master)$(printf "\t")refs/heads/master
EOF
- test_cmp actual expect
+ test_cmp expect actual
'
test_expect_success 'clone with git:// using protocol v2' '
grep "fetch< version 2" log
'
+test_expect_success 'fetch by hash without tag following with protocol v2 does not list refs' '
+ test_when_finished "rm -f log" &&
+
+ test_commit -C "$daemon_parent" two_a &&
+ git -C "$daemon_parent" rev-parse two_a >two_a_hash &&
+
+ GIT_TRACE_PACKET="$(pwd)/log" git -C daemon_child -c protocol.version=2 \
+ fetch --no-tags origin $(cat two_a_hash) &&
+
+ grep "fetch< version 2" log &&
+ ! grep "fetch> command=ls-refs" log
+'
+
test_expect_success 'pull with git:// using protocol v2' '
test_when_finished "rm -f log" &&
grep "git< version 2" log &&
git ls-remote --symref "file://$(pwd)/file_parent" >expect &&
- test_cmp actual expect
+ test_cmp expect actual
'
test_expect_success 'ref advertisment is filtered with ls-remote using protocol v2' '
$(git -C file_parent rev-parse refs/heads/master)$(printf "\t")refs/heads/master
EOF
- test_cmp actual expect
+ test_cmp expect actual
'
test_expect_success 'server-options are sent when using ls-remote' '
$(git -C file_parent rev-parse refs/heads/master)$(printf "\t")refs/heads/master
EOF
- test_cmp actual expect &&
+ test_cmp expect actual &&
grep "server-option=hello" log &&
grep "server-option=world" log
'
grep "version 2" trace &&
# Ensure that the old version of the file is missing
- git -C client rev-list master --quiet --objects --missing=print \
+ git -C client rev-list --quiet --objects --missing=print master \
>observed.oids &&
grep "$(git -C server rev-parse message1:a.txt)" observed.oids &&
grep "version 2" trace
'
+test_expect_success 'when dynamically fetching missing object, do not list refs' '
+ ! grep "git> command=ls-refs" trace
+'
+
test_expect_success 'partial fetch' '
rm -rf client "$(pwd)/trace" &&
git init client &&
grep "version 2" trace &&
# Ensure that the old version of the file is missing
- git -C client rev-list other --quiet --objects --missing=print \
+ git -C client rev-list --quiet --objects --missing=print other \
>observed.oids &&
grep "$(git -C server rev-parse message1:a.txt)" observed.oids &&
git -C server config uploadpack.allowfilter 0 &&
# Custom request that tries to filter even though it is not advertised.
- test-pkt-line pack >in <<-EOF &&
+ test-tool pkt-line pack >in <<-EOF &&
command=fetch
0001
want $(git -C server rev-parse master)
/wanted-refs/d
/0001/d
p
- }' <out | test-pkt-line unpack >actual_refs
+ }' <out | test-tool pkt-line unpack >actual_refs
}
get_actual_commits () {
sed -n -e '/packfile/,/0000/{
/packfile/d
p
- }' <out | test-pkt-line unpack-sideband >o.pack &&
+ }' <out | test-tool pkt-line unpack-sideband >o.pack &&
git index-pack o.pack &&
git verify-pack -v o.idx | grep commit | cut -c-40 | sort >actual_commits
}
'
test_expect_success 'invalid want-ref line' '
- test-pkt-line pack >in <<-EOF &&
+ test-tool pkt-line pack >in <<-EOF &&
command=fetch
0001
no-progress
EOF
git rev-parse f | sort >expected_commits &&
- test-pkt-line pack >in <<-EOF &&
+ test-tool pkt-line pack >in <<-EOF &&
command=fetch
0001
no-progress
EOF
git rev-parse c d | sort >expected_commits &&
- test-pkt-line pack >in <<-EOF &&
+ test-tool pkt-line pack >in <<-EOF &&
command=fetch
0001
no-progress
EOF
git rev-parse e f | sort >expected_commits &&
- test-pkt-line pack >in <<-EOF &&
+ test-tool pkt-line pack >in <<-EOF &&
command=fetch
0001
no-progress
EOF
>expected_commits &&
- test-pkt-line pack >in <<-EOF &&
+ test-tool pkt-line pack >in <<-EOF &&
command=fetch
0001
no-progress
test_must_fail git fsck --full
'
-test_expect_success 'rev-list should fail' \
- '
- test_must_fail git rev-list --all > /dev/null
- '
+test_expect_success 'rev-list should fail' '
+ test_must_fail env GIT_TEST_COMMIT_GRAPH=0 git rev-list --all > /dev/null
+'
test_expect_success 'git repack _MUST_ fail' \
'
compare rev-list "--exclude=refs/remotes/* --exclude=refs/tags/* --all" --branches
'
-test_expect_failure 'rev-list should succeed with empty output on empty stdin' '
+test_expect_success 'rev-list should succeed with empty output on empty stdin' '
git rev-list --stdin </dev/null >actual &&
test_must_be_empty actual
'
printf "propter nomen suum." >> expect.txt
test_expect_success "merge does not add LF away of change" \
- "test_cmp test3.txt expect.txt"
+ "test_cmp expect.txt test3.txt"
cp test.txt backup.txt
test_expect_success "merge with conflicts" \
virga tua et baculus tuus ipsa me consolata sunt.
EOF
-test_expect_success "expected conflict markers" "test_cmp test.txt expect.txt"
+test_expect_success "expected conflict markers" "test_cmp expect.txt test.txt"
cp backup.txt test.txt
virga tua et baculus tuus ipsa me consolata sunt.
EOF
test_expect_success "merge conflicting with --ours" \
- "git merge-file --ours test.txt orig.txt new3.txt && test_cmp test.txt expect.txt"
+ "git merge-file --ours test.txt orig.txt new3.txt && test_cmp expect.txt test.txt"
cp backup.txt test.txt
cat > expect.txt << EOF
virga tua et baculus tuus ipsa me consolata sunt.
EOF
test_expect_success "merge conflicting with --theirs" \
- "git merge-file --theirs test.txt orig.txt new3.txt && test_cmp test.txt expect.txt"
+ "git merge-file --theirs test.txt orig.txt new3.txt && test_cmp expect.txt test.txt"
cp backup.txt test.txt
cat > expect.txt << EOF
virga tua et baculus tuus ipsa me consolata sunt.
EOF
test_expect_success "merge conflicting with --union" \
- "git merge-file --union test.txt orig.txt new3.txt && test_cmp test.txt expect.txt"
+ "git merge-file --union test.txt orig.txt new3.txt && test_cmp expect.txt test.txt"
cp backup.txt test.txt
test_expect_success "merge with conflicts, using -L" \
EOF
test_expect_success "expected conflict markers, with -L" \
- "test_cmp test.txt expect.txt"
+ "test_cmp expect.txt test.txt"
sed "s/ tu / TU /" < new1.txt > new5.txt
test_expect_success "conflict in removed tail" \
GIT_AUTHOR_DATE="2006-12-12 23:00:08" git commit -m F
'
-test_expect_success "combined merge conflicts" "
- test_must_fail git merge -m final G
-"
+test_expect_success 'combined merge conflicts' '
+ test_must_fail env GIT_TEST_COMMIT_GRAPH=0 git merge -m final G
+'
cat > expect << EOF
<<<<<<< HEAD
false
else
git ls-files -s >current
- test_cmp current expect
+ test_cmp expect current
fi
'
false
else
git ls-files -s >current
- test_cmp current expect
+ test_cmp expect current
fi
'
git checkout -f a2 &&
test_must_fail git merge -s $strategy b2 &&
git ls-files -u >actual &&
- test_cmp actual expect &&
+ test_cmp expect actual &&
git ls-files -s file2 | grep ^100755
'
test_expect_success 'verify blob:none omits all 5 blobs' '
git -C r1 ls-files -s file.1 file.2 file.3 file.4 file.5 \
- | awk -f print_2.awk \
- | sort >expected &&
- git -C r1 rev-list HEAD --quiet --objects --filter-print-omitted --filter=blob:none \
- | awk -f print_1.awk \
- | sed "s/~//" \
- | sort >observed &&
- test_cmp observed expected
+ >ls_files_result &&
+ awk -f print_2.awk ls_files_result |
+ sort >expected &&
+
+ git -C r1 rev-list --quiet --objects --filter-print-omitted \
+ --filter=blob:none HEAD >revs &&
+ awk -f print_1.awk revs |
+ sed "s/~//" |
+ sort >observed &&
+
+ test_cmp expected observed
+'
+
+test_expect_success 'specify blob explicitly prevents filtering' '
+ file_3=$(git -C r1 ls-files -s file.3 |
+ awk -f print_2.awk) &&
+
+ file_4=$(git -C r1 ls-files -s file.4 |
+ awk -f print_2.awk) &&
+
+ git -C r1 rev-list --objects --filter=blob:none HEAD $file_3 >observed &&
+ grep "$file_3" observed &&
+ ! grep "$file_4" observed
'
test_expect_success 'verify emitted+omitted == all' '
- git -C r1 rev-list HEAD --objects \
- | awk -f print_1.awk \
- | sort >expected &&
- git -C r1 rev-list HEAD --objects --filter-print-omitted --filter=blob:none \
- | awk -f print_1.awk \
- | sed "s/~//" \
- | sort >observed &&
- test_cmp observed expected
+ git -C r1 rev-list --objects HEAD >revs &&
+ awk -f print_1.awk revs |
+ sort >expected &&
+
+ git -C r1 rev-list --objects --filter-print-omitted --filter=blob:none \
+ HEAD >revs &&
+ awk -f print_1.awk revs |
+ sed "s/~//" |
+ sort >observed &&
+
+ test_cmp expected observed
'
'
test_expect_success 'verify blob:limit=500 omits all blobs' '
- git -C r2 ls-files -s large.1000 large.10000 \
- | awk -f print_2.awk \
- | sort >expected &&
- git -C r2 rev-list HEAD --quiet --objects --filter-print-omitted --filter=blob:limit=500 \
- | awk -f print_1.awk \
- | sed "s/~//" \
- | sort >observed &&
- test_cmp observed expected
+ git -C r2 ls-files -s large.1000 large.10000 >ls_files_result &&
+ awk -f print_2.awk ls_files_result |
+ sort >expected &&
+
+ git -C r2 rev-list --quiet --objects --filter-print-omitted \
+ --filter=blob:limit=500 HEAD >revs &&
+ awk -f print_1.awk revs |
+ sed "s/~//" |
+ sort >observed &&
+
+ test_cmp expected observed
'
test_expect_success 'verify emitted+omitted == all' '
- git -C r2 rev-list HEAD --objects \
- | awk -f print_1.awk \
- | sort >expected &&
- git -C r2 rev-list HEAD --objects --filter-print-omitted --filter=blob:limit=500 \
- | awk -f print_1.awk \
- | sed "s/~//" \
- | sort >observed &&
- test_cmp observed expected
+ git -C r2 rev-list --objects HEAD >revs &&
+ awk -f print_1.awk revs |
+ sort >expected &&
+
+ git -C r2 rev-list --objects --filter-print-omitted \
+ --filter=blob:limit=500 HEAD >revs &&
+ awk -f print_1.awk revs |
+ sed "s/~//" |
+ sort >observed &&
+
+ test_cmp expected observed
'
test_expect_success 'verify blob:limit=1000' '
- git -C r2 ls-files -s large.1000 large.10000 \
- | awk -f print_2.awk \
- | sort >expected &&
- git -C r2 rev-list HEAD --quiet --objects --filter-print-omitted --filter=blob:limit=1000 \
- | awk -f print_1.awk \
- | sed "s/~//" \
- | sort >observed &&
- test_cmp observed expected
+ git -C r2 ls-files -s large.1000 large.10000 >ls_files_result &&
+ awk -f print_2.awk ls_files_result |
+ sort >expected &&
+
+ git -C r2 rev-list --quiet --objects --filter-print-omitted \
+ --filter=blob:limit=1000 HEAD >revs &&
+ awk -f print_1.awk revs |
+ sed "s/~//" |
+ sort >observed &&
+
+ test_cmp expected observed
'
test_expect_success 'verify blob:limit=1001' '
- git -C r2 ls-files -s large.10000 \
- | awk -f print_2.awk \
- | sort >expected &&
- git -C r2 rev-list HEAD --quiet --objects --filter-print-omitted --filter=blob:limit=1001 \
- | awk -f print_1.awk \
- | sed "s/~//" \
- | sort >observed &&
- test_cmp observed expected
+ git -C r2 ls-files -s large.10000 >ls_files_result &&
+ awk -f print_2.awk ls_files_result |
+ sort >expected &&
+
+ git -C r2 rev-list --quiet --objects --filter-print-omitted \
+ --filter=blob:limit=1001 HEAD >revs &&
+ awk -f print_1.awk revs |
+ sed "s/~//" |
+ sort >observed &&
+
+ test_cmp expected observed
'
test_expect_success 'verify blob:limit=1k' '
- git -C r2 ls-files -s large.10000 \
- | awk -f print_2.awk \
- | sort >expected &&
- git -C r2 rev-list HEAD --quiet --objects --filter-print-omitted --filter=blob:limit=1k \
- | awk -f print_1.awk \
- | sed "s/~//" \
- | sort >observed &&
- test_cmp observed expected
+ git -C r2 ls-files -s large.10000 >ls_files_result &&
+ awk -f print_2.awk ls_files_result |
+ sort >expected &&
+
+ git -C r2 rev-list --quiet --objects --filter-print-omitted \
+ --filter=blob:limit=1k HEAD >revs &&
+ awk -f print_1.awk revs |
+ sed "s/~//" |
+ sort >observed &&
+
+ test_cmp expected observed
'
test_expect_success 'verify blob:limit=1m' '
- git -C r2 rev-list HEAD --quiet --objects --filter-print-omitted --filter=blob:limit=1m \
- | awk -f print_1.awk \
- | sed "s/~//" \
- | sort >observed &&
+ git -C r2 rev-list --quiet --objects --filter-print-omitted \
+ --filter=blob:limit=1m HEAD >revs &&
+ awk -f print_1.awk revs |
+ sed "s/~//" |
+ sort >observed &&
+
test_must_be_empty observed
'
'
test_expect_success 'verify sparse:path=pattern1 omits top-level files' '
- git -C r3 ls-files -s sparse1 sparse2 \
- | awk -f print_2.awk \
- | sort >expected &&
- git -C r3 rev-list HEAD --quiet --objects --filter-print-omitted --filter=sparse:path=../pattern1 \
- | awk -f print_1.awk \
- | sed "s/~//" \
- | sort >observed &&
- test_cmp observed expected
+ git -C r3 ls-files -s sparse1 sparse2 >ls_files_result &&
+ awk -f print_2.awk ls_files_result |
+ sort >expected &&
+
+ git -C r3 rev-list --quiet --objects --filter-print-omitted \
+ --filter=sparse:path=../pattern1 HEAD >revs &&
+ awk -f print_1.awk revs |
+ sed "s/~//" |
+ sort >observed &&
+
+ test_cmp expected observed
'
test_expect_success 'verify sparse:path=pattern2 omits both sparse2 files' '
- git -C r3 ls-files -s sparse2 dir1/sparse2 \
- | awk -f print_2.awk \
- | sort >expected &&
- git -C r3 rev-list HEAD --quiet --objects --filter-print-omitted --filter=sparse:path=../pattern2 \
- | awk -f print_1.awk \
- | sed "s/~//" \
- | sort >observed &&
- test_cmp observed expected
+ git -C r3 ls-files -s sparse2 dir1/sparse2 >ls_files_result &&
+ awk -f print_2.awk ls_files_result |
+ sort >expected &&
+
+ git -C r3 rev-list --quiet --objects --filter-print-omitted \
+ --filter=sparse:path=../pattern2 HEAD >revs &&
+ awk -f print_1.awk revs |
+ sed "s/~//" |
+ sort >observed &&
+
+ test_cmp expected observed
'
# Test sparse:oid=<oid-ish> filter.
'
test_expect_success 'verify sparse:oid=OID omits top-level files' '
- git -C r3 ls-files -s pattern sparse1 sparse2 \
- | awk -f print_2.awk \
- | sort >expected &&
+ git -C r3 ls-files -s pattern sparse1 sparse2 >ls_files_result &&
+ awk -f print_2.awk ls_files_result |
+ sort >expected &&
+
oid=$(git -C r3 ls-files -s pattern | awk -f print_2.awk) &&
- git -C r3 rev-list HEAD --quiet --objects --filter-print-omitted --filter=sparse:oid=$oid \
- | awk -f print_1.awk \
- | sed "s/~//" \
- | sort >observed &&
- test_cmp observed expected
+
+ git -C r3 rev-list --quiet --objects --filter-print-omitted \
+ --filter=sparse:oid=$oid HEAD >revs &&
+ awk -f print_1.awk revs |
+ sed "s/~//" |
+ sort >observed &&
+
+ test_cmp expected observed
'
test_expect_success 'verify sparse:oid=oid-ish omits top-level files' '
- git -C r3 ls-files -s pattern sparse1 sparse2 \
- | awk -f print_2.awk \
- | sort >expected &&
- git -C r3 rev-list HEAD --quiet --objects --filter-print-omitted --filter=sparse:oid=master:pattern \
- | awk -f print_1.awk \
- | sed "s/~//" \
- | sort >observed &&
- test_cmp observed expected
+ git -C r3 ls-files -s pattern sparse1 sparse2 >ls_files_result &&
+ awk -f print_2.awk ls_files_result |
+ sort >expected &&
+
+ git -C r3 rev-list --quiet --objects --filter-print-omitted \
+ --filter=sparse:oid=master:pattern HEAD >revs &&
+ awk -f print_1.awk revs |
+ sed "s/~//" |
+ sort >observed &&
+
+ test_cmp expected observed
+'
+
+test_expect_success 'rev-list W/ --missing=print and --missing=allow-any for trees' '
+ TREE=$(git -C r3 rev-parse HEAD:dir1) &&
+
+ # Create a spare repo because we will be deleting objects from this one.
+ git clone r3 r3.b &&
+
+ rm r3.b/.git/objects/$(echo $TREE | sed "s|^..|&/|") &&
+
+ git -C r3.b rev-list --quiet --missing=print --objects HEAD \
+ >missing_objs 2>rev_list_err &&
+ echo "?$TREE" >expected &&
+ test_cmp expected missing_objs &&
+
+ # do not complain when a missing tree cannot be parsed
+ test_must_be_empty rev_list_err &&
+
+ git -C r3.b rev-list --missing=allow-any --objects HEAD \
+ >objs 2>rev_list_err &&
+ ! grep $TREE objs &&
+ test_must_be_empty rev_list_err
+'
+
+# Test tree:0 filter.
+
+test_expect_success 'verify tree:0 includes trees in "filtered" output' '
+ git -C r3 rev-list --quiet --objects --filter-print-omitted \
+ --filter=tree:0 HEAD >revs &&
+
+ awk -f print_1.awk revs |
+ sed s/~// |
+ xargs -n1 git -C r3 cat-file -t >unsorted_filtered_types &&
+
+ sort -u unsorted_filtered_types >filtered_types &&
+ test_write_lines blob tree >expected &&
+ test_cmp expected filtered_types
+'
+
+# Make sure tree:0 does not iterate through any trees.
+
+test_expect_success 'filter a GIANT tree through tree:0' '
+ GIT_TRACE=1 git -C r3 rev-list \
+ --objects --filter=tree:0 HEAD 2>filter_trace &&
+ grep "Skipping contents of tree [.][.][.]" filter_trace >actual &&
+ # One line for each commit traversed.
+ test_line_count = 2 actual &&
+
+ # Make sure no other trees were considered besides the root.
+ ! grep "Skipping contents of tree [^.]" filter_trace
'
# Delete some loose objects and use rev-list, but WITHOUT any filtering.
test_expect_success 'rev-list W/ --missing=print' '
git -C r1 ls-files -s file.1 file.2 file.3 file.4 file.5 \
- | awk -f print_2.awk \
- | sort >expected &&
+ >ls_files_result &&
+ awk -f print_2.awk ls_files_result |
+ sort >expected &&
+
for id in `cat expected | sed "s|..|&/|"`
do
rm r1/.git/objects/$id
done &&
- git -C r1 rev-list --quiet HEAD --missing=print --objects \
- | awk -f print_1.awk \
- | sed "s/?//" \
- | sort >observed &&
- test_cmp observed expected
+
+ git -C r1 rev-list --quiet --missing=print --objects HEAD >revs &&
+ awk -f print_1.awk revs |
+ sed "s/?//" |
+ sort >observed &&
+
+ test_cmp expected observed
'
test_expect_success 'rev-list W/O --missing fails' '
# though, but git-add is convenient as it has its own internal pathspec
# parsing.
test_must_fail git add ":(attr:labelB)" 2>actual &&
- test_i18ngrep "unsupported magic" actual
+ test_i18ngrep "magic not supported" actual
'
test_expect_success 'abort on giving invalid label on the command line' '
test_cmp expect actual.clean
'
+test_expect_success 'trailer parsing not fooled by --- line' '
+ git commit --allow-empty -F - <<-\EOF &&
+ this is the subject
+
+ This is the body. The message has a "---" line which would confuse a
+ message+patch parser. But here we know we have only a commit message,
+ so we get it right.
+
+ trailer: wrong
+ ---
+ This is more body.
+
+ trailer: right
+ EOF
+
+ {
+ echo "trailer: right" &&
+ echo
+ } >expect &&
+ git for-each-ref --format="%(trailers)" refs/heads/master >actual &&
+ test_cmp expect actual
+'
+
test_expect_success 'Add symbolic ref for the following tests' '
git symbolic-ref refs/heads/sym refs/heads/master
'
'
. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-terminal.sh
test_expect_success 'setup' '
# do not let the amount of physical memory affects gc
test_line_count = 2 new # There is one new pack and its .idx
'
+test_expect_success 'gc --no-quiet' '
+ git -c gc.writeCommitGraph=true gc --no-quiet >stdout 2>stderr &&
+ test_must_be_empty stdout &&
+ test_line_count = 1 stderr &&
+ test_i18ngrep "Computing commit graph generation numbers" stderr
+'
+
+test_expect_success TTY 'with TTY: gc --no-quiet' '
+ test_terminal git -c gc.writeCommitGraph=true gc --no-quiet >stdout 2>stderr &&
+ test_must_be_empty stdout &&
+ test_i18ngrep "Enumerating objects" stderr &&
+ test_i18ngrep "Computing commit graph generation numbers" stderr
+'
+
+test_expect_success 'gc --quiet' '
+ git -c gc.writeCommitGraph=true gc --quiet >stdout 2>stderr &&
+ test_must_be_empty stdout &&
+ test_must_be_empty stderr
+'
+
run_and_wait_for_auto_gc () {
# We read stdout from gc for the side effect of waiting until the
# background gc process exits, closing its fd 9. Furthermore, the
test_config gc.autopacklimit 1 &&
test_config gc.autodetach true &&
echo fleem >.git/gc.log &&
- test_must_fail git gc --auto 2>err &&
- test_i18ngrep "^error:" err &&
+ git gc --auto 2>err &&
+ test_i18ngrep "^warning:" err &&
test_config gc.logexpiry 5.days &&
test-tool chmtime =-345600 .git/gc.log &&
- test_must_fail git gc --auto &&
+ git gc --auto &&
test_config gc.logexpiry 2.days &&
run_and_wait_for_auto_gc &&
ls .git/objects/pack/pack-*.pack >packs &&
--- /dev/null
+#!/bin/sh
+
+test_description='basic commit reachability tests'
+
+. ./test-lib.sh
+
+# Construct a grid-like commit graph with points (x,y)
+# with 1 <= x <= 10, 1 <= y <= 10, where (x,y) has
+# parents (x-1, y) and (x, y-1), keeping in mind that
+# we drop a parent if a coordinate is nonpositive.
+#
+# (10,10)
+# / \
+# (10,9) (9,10)
+# / \ / \
+# (10,8) (9,9) (8,10)
+# / \ / \ / \
+# ( continued...)
+# \ / \ / \ /
+# (3,1) (2,2) (1,3)
+# \ / \ /
+# (2,1) (2,1)
+# \ /
+# (1,1)
+#
+# We use branch 'commit-x-y' to refer to (x,y).
+# This grid allows interesting reachability and
+# non-reachability queries: (x,y) can reach (x',y')
+# if and only if x' <= x and y' <= y.
+test_expect_success 'setup' '
+ for i in $(test_seq 1 10)
+ do
+ test_commit "1-$i" &&
+ git branch -f commit-1-$i &&
+ git tag -a -m "1-$i" tag-1-$i commit-1-$i
+ done &&
+ for j in $(test_seq 1 9)
+ do
+ git reset --hard commit-$j-1 &&
+ x=$(($j + 1)) &&
+ test_commit "$x-1" &&
+ git branch -f commit-$x-1 &&
+ git tag -a -m "$x-1" tag-$x-1 commit-$x-1 &&
+
+ for i in $(test_seq 2 10)
+ do
+ git merge commit-$j-$i -m "$x-$i" &&
+ git branch -f commit-$x-$i &&
+ git tag -a -m "$x-$i" tag-$x-$i commit-$x-$i
+ done
+ done &&
+ git commit-graph write --reachable &&
+ mv .git/objects/info/commit-graph commit-graph-full &&
+ git show-ref -s commit-5-5 | git commit-graph write --stdin-commits &&
+ mv .git/objects/info/commit-graph commit-graph-half &&
+ git config core.commitGraph true
+'
+
+test_three_modes () {
+ test_when_finished rm -rf .git/objects/info/commit-graph &&
+ test-tool reach $1 <input >actual &&
+ test_cmp expect actual &&
+ cp commit-graph-full .git/objects/info/commit-graph &&
+ test-tool reach $1 <input >actual &&
+ test_cmp expect actual &&
+ cp commit-graph-half .git/objects/info/commit-graph &&
+ test-tool reach $1 <input >actual &&
+ test_cmp expect actual
+}
+
+test_expect_success 'ref_newer:miss' '
+ cat >input <<-\EOF &&
+ A:commit-5-7
+ B:commit-4-9
+ EOF
+ echo "ref_newer(A,B):0" >expect &&
+ test_three_modes ref_newer
+'
+
+test_expect_success 'ref_newer:hit' '
+ cat >input <<-\EOF &&
+ A:commit-5-7
+ B:commit-2-3
+ EOF
+ echo "ref_newer(A,B):1" >expect &&
+ test_three_modes ref_newer
+'
+
+test_expect_success 'in_merge_bases:hit' '
+ cat >input <<-\EOF &&
+ A:commit-5-7
+ B:commit-8-8
+ EOF
+ echo "in_merge_bases(A,B):1" >expect &&
+ test_three_modes in_merge_bases
+'
+
+test_expect_success 'in_merge_bases:miss' '
+ cat >input <<-\EOF &&
+ A:commit-6-8
+ B:commit-5-9
+ EOF
+ echo "in_merge_bases(A,B):0" >expect &&
+ test_three_modes in_merge_bases
+'
+
+test_expect_success 'is_descendant_of:hit' '
+ cat >input <<-\EOF &&
+ A:commit-5-7
+ X:commit-4-8
+ X:commit-6-6
+ X:commit-1-1
+ EOF
+ echo "is_descendant_of(A,X):1" >expect &&
+ test_three_modes is_descendant_of
+'
+
+test_expect_success 'is_descendant_of:miss' '
+ cat >input <<-\EOF &&
+ A:commit-6-8
+ X:commit-5-9
+ X:commit-4-10
+ X:commit-7-6
+ EOF
+ echo "is_descendant_of(A,X):0" >expect &&
+ test_three_modes is_descendant_of
+'
+
+test_expect_success 'get_merge_bases_many' '
+ cat >input <<-\EOF &&
+ A:commit-5-7
+ X:commit-4-8
+ X:commit-6-6
+ X:commit-8-3
+ EOF
+ {
+ echo "get_merge_bases_many(A,X):" &&
+ git rev-parse commit-5-6 \
+ commit-4-7 | sort
+ } >expect &&
+ test_three_modes get_merge_bases_many
+'
+
+test_expect_success 'reduce_heads' '
+ cat >input <<-\EOF &&
+ X:commit-1-10
+ X:commit-2-8
+ X:commit-3-6
+ X:commit-4-4
+ X:commit-1-7
+ X:commit-2-5
+ X:commit-3-3
+ X:commit-5-1
+ EOF
+ {
+ echo "reduce_heads(X):" &&
+ git rev-parse commit-5-1 \
+ commit-4-4 \
+ commit-3-6 \
+ commit-2-8 \
+ commit-1-10 | sort
+ } >expect &&
+ test_three_modes reduce_heads
+'
+
+test_expect_success 'can_all_from_reach:hit' '
+ cat >input <<-\EOF &&
+ X:commit-2-10
+ X:commit-3-9
+ X:commit-4-8
+ X:commit-5-7
+ X:commit-6-6
+ X:commit-7-5
+ X:commit-8-4
+ X:commit-9-3
+ Y:commit-1-9
+ Y:commit-2-8
+ Y:commit-3-7
+ Y:commit-4-6
+ Y:commit-5-5
+ Y:commit-6-4
+ Y:commit-7-3
+ Y:commit-8-1
+ EOF
+ echo "can_all_from_reach(X,Y):1" >expect &&
+ test_three_modes can_all_from_reach
+'
+
+test_expect_success 'can_all_from_reach:miss' '
+ cat >input <<-\EOF &&
+ X:commit-2-10
+ X:commit-3-9
+ X:commit-4-8
+ X:commit-5-7
+ X:commit-6-6
+ X:commit-7-5
+ X:commit-8-4
+ X:commit-9-3
+ Y:commit-1-9
+ Y:commit-2-8
+ Y:commit-3-7
+ Y:commit-4-6
+ Y:commit-5-5
+ Y:commit-6-4
+ Y:commit-8-5
+ EOF
+ echo "can_all_from_reach(X,Y):0" >expect &&
+ test_three_modes can_all_from_reach
+'
+
+test_expect_success 'can_all_from_reach_with_flag: tags case' '
+ cat >input <<-\EOF &&
+ X:tag-2-10
+ X:tag-3-9
+ X:tag-4-8
+ X:commit-5-7
+ X:commit-6-6
+ X:commit-7-5
+ X:commit-8-4
+ X:commit-9-3
+ Y:tag-1-9
+ Y:tag-2-8
+ Y:tag-3-7
+ Y:commit-4-6
+ Y:commit-5-5
+ Y:commit-6-4
+ Y:commit-7-3
+ Y:commit-8-1
+ EOF
+ echo "can_all_from_reach_with_flag(X,_,_,0,0):1" >expect &&
+ test_three_modes can_all_from_reach_with_flag
+'
+
+test_expect_success 'commit_contains:hit' '
+ cat >input <<-\EOF &&
+ A:commit-7-7
+ X:commit-2-10
+ X:commit-3-9
+ X:commit-4-8
+ X:commit-5-7
+ X:commit-6-6
+ X:commit-7-5
+ X:commit-8-4
+ X:commit-9-3
+ EOF
+ echo "commit_contains(_,A,X,_):1" >expect &&
+ test_three_modes commit_contains &&
+ test_three_modes commit_contains --tag
+'
+
+test_expect_success 'commit_contains:miss' '
+ cat >input <<-\EOF &&
+ A:commit-6-5
+ X:commit-2-10
+ X:commit-3-9
+ X:commit-4-8
+ X:commit-5-7
+ X:commit-6-6
+ X:commit-7-5
+ X:commit-8-4
+ X:commit-9-3
+ EOF
+ echo "commit_contains(_,A,X,_):0" >expect &&
+ test_three_modes commit_contains &&
+ test_three_modes commit_contains --tag
+'
+
+test_done
done
test_expect_success 'editor with a space' '
- echo "echo space >\$1" >"e space.sh" &&
+ echo "echo space >\"\$1\"" >"e space.sh" &&
chmod a+x "e space.sh" &&
GIT_EDITOR="./e\ space.sh" git commit --amend &&
test space = "$(git show -s --pretty=format:%s)"
'
test_expect_success 'untracked cache is empty' '
- test-dump-untracked-cache >../actual &&
+ test-tool dump-untracked-cache >../actual &&
cat >../expect-empty <<EOF &&
info/exclude 0000000000000000000000000000000000000000
core.excludesfile 0000000000000000000000000000000000000000
'
test_expect_success 'untracked cache after first status' '
- test-dump-untracked-cache >../actual &&
+ test-tool dump-untracked-cache >../actual &&
test_cmp ../dump.expect ../actual
'
'
test_expect_success 'untracked cache after second status' '
- test-dump-untracked-cache >../actual &&
+ test-tool dump-untracked-cache >../actual &&
test_cmp ../dump.expect ../actual
'
'
test_expect_success 'verify untracked cache dump' '
- test-dump-untracked-cache >../actual &&
+ test-tool dump-untracked-cache >../actual &&
cat >../expect <<EOF &&
info/exclude $EMPTY_BLOB
core.excludesfile 0000000000000000000000000000000000000000
'
test_expect_success 'verify untracked cache dump' '
- test-dump-untracked-cache >../actual &&
+ test-tool dump-untracked-cache >../actual &&
cat >../expect <<EOF &&
info/exclude $EMPTY_BLOB
core.excludesfile 0000000000000000000000000000000000000000
'
test_expect_success 'verify untracked cache dump' '
- test-dump-untracked-cache >../actual &&
+ test-tool dump-untracked-cache >../actual &&
cat >../expect <<EOF &&
info/exclude 13263c0978fb9fad16b2d580fb800b6d811c3ff0
core.excludesfile 0000000000000000000000000000000000000000
test_expect_success 'move two from tracked to untracked' '
git rm --cached two &&
- test-dump-untracked-cache >../actual &&
+ test-tool dump-untracked-cache >../actual &&
cat >../expect <<EOF &&
info/exclude 13263c0978fb9fad16b2d580fb800b6d811c3ff0
core.excludesfile 0000000000000000000000000000000000000000
'
test_expect_success 'verify untracked cache dump' '
- test-dump-untracked-cache >../actual &&
+ test-tool dump-untracked-cache >../actual &&
cat >../expect <<EOF &&
info/exclude 13263c0978fb9fad16b2d580fb800b6d811c3ff0
core.excludesfile 0000000000000000000000000000000000000000
test_expect_success 'move two from untracked to tracked' '
git add two &&
- test-dump-untracked-cache >../actual &&
+ test-tool dump-untracked-cache >../actual &&
cat >../expect <<EOF &&
info/exclude 13263c0978fb9fad16b2d580fb800b6d811c3ff0
core.excludesfile 0000000000000000000000000000000000000000
'
test_expect_success 'verify untracked cache dump' '
- test-dump-untracked-cache >../actual &&
+ test-tool dump-untracked-cache >../actual &&
cat >../expect <<EOF &&
info/exclude 13263c0978fb9fad16b2d580fb800b6d811c3ff0
core.excludesfile 0000000000000000000000000000000000000000
'
test_expect_success 'untracked cache correct after commit' '
- test-dump-untracked-cache >../actual &&
+ test-tool dump-untracked-cache >../actual &&
cat >../expect <<EOF &&
info/exclude 13263c0978fb9fad16b2d580fb800b6d811c3ff0
core.excludesfile 0000000000000000000000000000000000000000
'
test_expect_success 'untracked cache correct after status' '
- test-dump-untracked-cache >../actual &&
+ test-tool dump-untracked-cache >../actual &&
cat >../expect <<EOF &&
info/exclude 13263c0978fb9fad16b2d580fb800b6d811c3ff0
core.excludesfile 0000000000000000000000000000000000000000
'
test_expect_success 'verify untracked cache dump (sparse/subdirs)' '
- test-dump-untracked-cache >../actual &&
+ test-tool dump-untracked-cache >../actual &&
cat >../expect-from-test-dump <<EOF &&
info/exclude 13263c0978fb9fad16b2d580fb800b6d811c3ff0
core.excludesfile 0000000000000000000000000000000000000000
test_expect_success '--no-untracked-cache removes the cache' '
git update-index --no-untracked-cache &&
- test-dump-untracked-cache >../actual &&
+ test-tool dump-untracked-cache >../actual &&
echo "no untracked cache" >../expect-no-uc &&
test_cmp ../expect-no-uc ../actual
'
test_expect_success 'git status does not change anything' '
git status &&
- test-dump-untracked-cache >../actual &&
+ test-tool dump-untracked-cache >../actual &&
test_cmp ../expect-no-uc ../actual
'
test_expect_success 'setting core.untrackedCache to true and using git status creates the cache' '
git config core.untrackedCache true &&
- test-dump-untracked-cache >../actual &&
+ test-tool dump-untracked-cache >../actual &&
test_cmp ../expect-no-uc ../actual &&
git status &&
- test-dump-untracked-cache >../actual &&
+ test-tool dump-untracked-cache >../actual &&
test_cmp ../expect-from-test-dump ../actual
'
test_expect_success 'using --no-untracked-cache does not fail when core.untrackedCache is true' '
git update-index --no-untracked-cache &&
- test-dump-untracked-cache >../actual &&
+ test-tool dump-untracked-cache >../actual &&
test_cmp ../expect-no-uc ../actual &&
git update-index --untracked-cache &&
- test-dump-untracked-cache >../actual &&
+ test-tool dump-untracked-cache >../actual &&
test_cmp ../expect-empty ../actual
'
test_expect_success 'setting core.untrackedCache to false and using git status removes the cache' '
git config core.untrackedCache false &&
- test-dump-untracked-cache >../actual &&
+ test-tool dump-untracked-cache >../actual &&
test_cmp ../expect-empty ../actual &&
git status &&
- test-dump-untracked-cache >../actual &&
+ test-tool dump-untracked-cache >../actual &&
test_cmp ../expect-no-uc ../actual
'
test_expect_success 'using --untracked-cache does not fail when core.untrackedCache is false' '
git update-index --untracked-cache &&
- test-dump-untracked-cache >../actual &&
+ test-tool dump-untracked-cache >../actual &&
test_cmp ../expect-empty ../actual
'
test_expect_success 'setting core.untrackedCache to keep' '
git config core.untrackedCache keep &&
git update-index --untracked-cache &&
- test-dump-untracked-cache >../actual &&
+ test-tool dump-untracked-cache >../actual &&
test_cmp ../expect-empty ../actual &&
git status &&
- test-dump-untracked-cache >../actual &&
+ test-tool dump-untracked-cache >../actual &&
test_cmp ../expect-from-test-dump ../actual &&
git update-index --no-untracked-cache &&
- test-dump-untracked-cache >../actual &&
+ test-tool dump-untracked-cache >../actual &&
test_cmp ../expect-no-uc ../actual &&
git update-index --force-untracked-cache &&
- test-dump-untracked-cache >../actual &&
+ test-tool dump-untracked-cache >../actual &&
test_cmp ../expect-empty ../actual &&
git status &&
- test-dump-untracked-cache >../actual &&
+ test-tool dump-untracked-cache >../actual &&
test_cmp ../expect-from-test-dump ../actual
'
test_expect_success 'untracked cache survives a checkout' '
git commit --allow-empty -m empty &&
- test-dump-untracked-cache >../before &&
+ test-tool dump-untracked-cache >../before &&
test_when_finished "git checkout master" &&
git checkout -b other_branch &&
- test-dump-untracked-cache >../after &&
+ test-tool dump-untracked-cache >../after &&
test_cmp ../before ../after &&
test_commit test &&
- test-dump-untracked-cache >../before &&
+ test-tool dump-untracked-cache >../before &&
git checkout master &&
- test-dump-untracked-cache >../after &&
+ test-tool dump-untracked-cache >../after &&
test_cmp ../before ../after
'
test_expect_success 'untracked cache survives a commit' '
- test-dump-untracked-cache >../before &&
+ test-tool dump-untracked-cache >../before &&
git add done/two &&
git commit -m commit &&
- test-dump-untracked-cache >../after &&
+ test-tool dump-untracked-cache >../after &&
test_cmp ../before ../after
'
git checkout master &&
avoid_racy &&
status_is_clean &&
- test-dump-untracked-cache >../actual &&
+ test-tool dump-untracked-cache >../actual &&
grep -F "recurse valid" ../actual >../actual.grep &&
cat >../expect.grep <<EOF &&
/ 0000000000000000000000000000000000000000 recurse valid
git diff master:one :3:uno |
sed -e "1,/^@@/d" -e "/^ /d" -e "s/^-/d/" -e "s/^+/a/" >current &&
fill d2 aT d7 aS >expect &&
- test_cmp current expect &&
+ test_cmp expect current &&
git diff --cached two >current &&
test_must_be_empty current
'
git ls-files >current &&
fill same two two two >expect &&
- test_cmp current expect &&
+ test_cmp expect current &&
cat <<-EOF >expect &&
<<<<<<< simple
(cd .git/modules/deeper/submodule &&
git log > ../../../../actual
) &&
- test_cmp actual expected
+ test_cmp expected actual
)
'
(cd .git/modules/deeper/submodule &&
git log > ../../../../actual
) &&
- test_cmp actual expected
+ test_cmp expected actual
)
'
git add deeper/submodule &&
git commit -m "update submodule" &&
git push origin : &&
- test_cmp actual expected
+ test_cmp expected actual
)
'
(cd .git/modules/submodule/modules/subsubmodule &&
git log > ../../../../../actual
) &&
- test_cmp actual expected
+ test_cmp expected actual
)
'
--- /dev/null
+#!/bin/sh
+#
+# Copyright (c) 2007 Steven Grimm
+#
+
+test_description='git commit
+
+Tests for template, signoff, squash and -F functions.'
+
+. ./test-lib.sh
+
+commit_msg_is () {
+ expect=commit_msg_is.expect
+ actual=commit_msg_is.actual
+
+ printf "%s" "$(git log --pretty=format:%s%b -1)" >"$actual" &&
+ printf "%s" "$1" >"$expect" &&
+ test_i18ncmp "$expect" "$actual"
+}
+
+# A sanity check to see if commit is working at all.
+test_expect_success 'a basic commit in an empty tree should succeed' '
+ echo content > foo &&
+ git add foo &&
+ git commit -m "initial commit"
+'
+
+test_expect_success 'nonexistent template file should return error' '
+ echo changes >> foo &&
+ git add foo &&
+ (
+ GIT_EDITOR="echo hello >\"\$1\"" &&
+ export GIT_EDITOR &&
+ test_must_fail git commit --template "$PWD"/notexist
+ )
+'
+
+test_expect_success 'nonexistent template file in config should return error' '
+ test_config commit.template "$PWD"/notexist &&
+ (
+ GIT_EDITOR="echo hello >\"\$1\"" &&
+ export GIT_EDITOR &&
+ test_must_fail git commit
+ )
+'
+
+# From now on we'll use a template file that exists.
+TEMPLATE="$PWD"/template
+
+test_expect_success 'unedited template should not commit' '
+ echo "template line" > "$TEMPLATE" &&
+ test_must_fail git commit --template "$TEMPLATE"
+'
+
+test_expect_success 'unedited template with comments should not commit' '
+ echo "# comment in template" >> "$TEMPLATE" &&
+ test_must_fail git commit --template "$TEMPLATE"
+'
+
+test_expect_success 'a Signed-off-by line by itself should not commit' '
+ (
+ test_set_editor "$TEST_DIRECTORY"/t7500/add-signed-off &&
+ test_must_fail git commit --template "$TEMPLATE"
+ )
+'
+
+test_expect_success 'adding comments to a template should not commit' '
+ (
+ test_set_editor "$TEST_DIRECTORY"/t7500/add-comments &&
+ test_must_fail git commit --template "$TEMPLATE"
+ )
+'
+
+test_expect_success 'adding real content to a template should commit' '
+ (
+ test_set_editor "$TEST_DIRECTORY"/t7500/add-content &&
+ git commit --template "$TEMPLATE"
+ ) &&
+ commit_msg_is "template linecommit message"
+'
+
+test_expect_success '-t option should be short for --template' '
+ echo "short template" > "$TEMPLATE" &&
+ echo "new content" >> foo &&
+ git add foo &&
+ (
+ test_set_editor "$TEST_DIRECTORY"/t7500/add-content &&
+ git commit -t "$TEMPLATE"
+ ) &&
+ commit_msg_is "short templatecommit message"
+'
+
+test_expect_success 'config-specified template should commit' '
+ echo "new template" > "$TEMPLATE" &&
+ test_config commit.template "$TEMPLATE" &&
+ echo "more content" >> foo &&
+ git add foo &&
+ (
+ test_set_editor "$TEST_DIRECTORY"/t7500/add-content &&
+ git commit
+ ) &&
+ commit_msg_is "new templatecommit message"
+'
+
+test_expect_success 'explicit commit message should override template' '
+ echo "still more content" >> foo &&
+ git add foo &&
+ GIT_EDITOR="$TEST_DIRECTORY"/t7500/add-content git commit --template "$TEMPLATE" \
+ -m "command line msg" &&
+ commit_msg_is "command line msg"
+'
+
+test_expect_success 'commit message from file should override template' '
+ echo "content galore" >> foo &&
+ git add foo &&
+ echo "standard input msg" |
+ (
+ test_set_editor "$TEST_DIRECTORY"/t7500/add-content &&
+ git commit --template "$TEMPLATE" --file -
+ ) &&
+ commit_msg_is "standard input msg"
+'
+
+cat >"$TEMPLATE" <<\EOF
+
+
+### template
+
+EOF
+test_expect_success 'commit message from template with whitespace issue' '
+ echo "content galore" >>foo &&
+ git add foo &&
+ GIT_EDITOR=\""$TEST_DIRECTORY"\"/t7500/add-whitespaced-content \
+ git commit --template "$TEMPLATE" &&
+ commit_msg_is "commit message"
+'
+
+test_expect_success 'using alternate GIT_INDEX_FILE (1)' '
+
+ cp .git/index saved-index &&
+ (
+ echo some new content >file &&
+ GIT_INDEX_FILE=.git/another_index &&
+ export GIT_INDEX_FILE &&
+ git add file &&
+ git commit -m "commit using another index" &&
+ git diff-index --exit-code HEAD &&
+ git diff-files --exit-code
+ ) &&
+ cmp .git/index saved-index >/dev/null
+
+'
+
+test_expect_success 'using alternate GIT_INDEX_FILE (2)' '
+
+ cp .git/index saved-index &&
+ (
+ rm -f .git/no-such-index &&
+ GIT_INDEX_FILE=.git/no-such-index &&
+ export GIT_INDEX_FILE &&
+ git commit -m "commit using nonexistent index" &&
+ test -z "$(git ls-files)" &&
+ test -z "$(git ls-tree HEAD)"
+
+ ) &&
+ cmp .git/index saved-index >/dev/null
+'
+
+cat > expect << EOF
+zort
+
+Signed-off-by: C O Mitter <committer@example.com>
+EOF
+
+test_expect_success '--signoff' '
+ echo "yet another content *narf*" >> foo &&
+ echo "zort" | git commit -s -F - foo &&
+ git cat-file commit HEAD | sed "1,/^\$/d" > output &&
+ test_cmp expect output
+'
+
+test_expect_success 'commit message from file (1)' '
+ mkdir subdir &&
+ echo "Log in top directory" >log &&
+ echo "Log in sub directory" >subdir/log &&
+ (
+ cd subdir &&
+ git commit --allow-empty -F log
+ ) &&
+ commit_msg_is "Log in sub directory"
+'
+
+test_expect_success 'commit message from file (2)' '
+ rm -f log &&
+ echo "Log in sub directory" >subdir/log &&
+ (
+ cd subdir &&
+ git commit --allow-empty -F log
+ ) &&
+ commit_msg_is "Log in sub directory"
+'
+
+test_expect_success 'commit message from stdin' '
+ (
+ cd subdir &&
+ echo "Log with foo word" | git commit --allow-empty -F -
+ ) &&
+ commit_msg_is "Log with foo word"
+'
+
+test_expect_success 'commit -F overrides -t' '
+ (
+ cd subdir &&
+ echo "-F log" > f.log &&
+ echo "-t template" > t.template &&
+ git commit --allow-empty -F f.log -t t.template
+ ) &&
+ commit_msg_is "-F log"
+'
+
+test_expect_success 'Commit without message is allowed with --allow-empty-message' '
+ echo "more content" >>foo &&
+ git add foo &&
+ >empty &&
+ git commit --allow-empty-message <empty &&
+ commit_msg_is "" &&
+ git tag empty-message-commit
+'
+
+test_expect_success 'Commit without message is no-no without --allow-empty-message' '
+ echo "more content" >>foo &&
+ git add foo &&
+ >empty &&
+ test_must_fail git commit <empty
+'
+
+test_expect_success 'Commit a message with --allow-empty-message' '
+ echo "even more content" >>foo &&
+ git add foo &&
+ git commit --allow-empty-message -m"hello there" &&
+ commit_msg_is "hello there"
+'
+
+test_expect_success 'commit -C empty respects --allow-empty-message' '
+ echo more >>foo &&
+ git add foo &&
+ test_must_fail git commit -C empty-message-commit &&
+ git commit -C empty-message-commit --allow-empty-message &&
+ commit_msg_is ""
+'
+
+commit_for_rebase_autosquash_setup () {
+ echo "first content line" >>foo &&
+ git add foo &&
+ cat >log <<EOF &&
+target message subject line
+
+target message body line 1
+target message body line 2
+EOF
+ git commit -F log &&
+ echo "second content line" >>foo &&
+ git add foo &&
+ git commit -m "intermediate commit" &&
+ echo "third content line" >>foo &&
+ git add foo
+}
+
+test_expect_success 'commit --fixup provides correct one-line commit message' '
+ commit_for_rebase_autosquash_setup &&
+ git commit --fixup HEAD~1 &&
+ commit_msg_is "fixup! target message subject line"
+'
+
+test_expect_success 'commit --fixup -m"something" -m"extra"' '
+ commit_for_rebase_autosquash_setup &&
+ git commit --fixup HEAD~1 -m"something" -m"extra" &&
+ commit_msg_is "fixup! target message subject linesomething
+
+extra"
+'
+
+test_expect_success 'commit --squash works with -F' '
+ commit_for_rebase_autosquash_setup &&
+ echo "log message from file" >msgfile &&
+ git commit --squash HEAD~1 -F msgfile &&
+ commit_msg_is "squash! target message subject linelog message from file"
+'
+
+test_expect_success 'commit --squash works with -m' '
+ commit_for_rebase_autosquash_setup &&
+ git commit --squash HEAD~1 -m "foo bar\nbaz" &&
+ commit_msg_is "squash! target message subject linefoo bar\nbaz"
+'
+
+test_expect_success 'commit --squash works with -C' '
+ commit_for_rebase_autosquash_setup &&
+ git commit --squash HEAD~1 -C HEAD &&
+ commit_msg_is "squash! target message subject lineintermediate commit"
+'
+
+test_expect_success 'commit --squash works with -c' '
+ commit_for_rebase_autosquash_setup &&
+ test_set_editor "$TEST_DIRECTORY"/t7500/edit-content &&
+ git commit --squash HEAD~1 -c HEAD &&
+ commit_msg_is "squash! target message subject lineedited commit"
+'
+
+test_expect_success 'commit --squash works with -C for same commit' '
+ commit_for_rebase_autosquash_setup &&
+ git commit --squash HEAD -C HEAD &&
+ commit_msg_is "squash! intermediate commit"
+'
+
+test_expect_success 'commit --squash works with -c for same commit' '
+ commit_for_rebase_autosquash_setup &&
+ test_set_editor "$TEST_DIRECTORY"/t7500/edit-content &&
+ git commit --squash HEAD -c HEAD &&
+ commit_msg_is "squash! edited commit"
+'
+
+test_expect_success 'commit --squash works with editor' '
+ commit_for_rebase_autosquash_setup &&
+ test_set_editor "$TEST_DIRECTORY"/t7500/add-content &&
+ git commit --squash HEAD~1 &&
+ commit_msg_is "squash! target message subject linecommit message"
+'
+
+test_expect_success 'invalid message options when using --fixup' '
+ echo changes >>foo &&
+ echo "message" >log &&
+ git add foo &&
+ test_must_fail git commit --fixup HEAD~1 --squash HEAD~2 &&
+ test_must_fail git commit --fixup HEAD~1 -C HEAD~2 &&
+ test_must_fail git commit --fixup HEAD~1 -c HEAD~2 &&
+ test_must_fail git commit --fixup HEAD~1 -F log
+'
+
+cat >expected-template <<EOF
+
+# Please enter the commit message for your changes. Lines starting
+# with '#' will be ignored, and an empty message aborts the commit.
+#
+# Author: A U Thor <author@example.com>
+#
+# On branch commit-template-check
+# Changes to be committed:
+# new file: commit-template-check
+#
+# Untracked files not listed
+EOF
+
+test_expect_success 'new line found before status message in commit template' '
+ git checkout -b commit-template-check &&
+ git reset --hard HEAD &&
+ touch commit-template-check &&
+ git add commit-template-check &&
+ GIT_EDITOR="cat >editor-input" git commit --untracked-files=no --allow-empty-message &&
+ test_i18ncmp expected-template editor-input
+'
+
+test_expect_success 'setup empty commit with unstaged rename and copy' '
+ test_create_repo unstaged_rename_and_copy &&
+ (
+ cd unstaged_rename_and_copy &&
+
+ echo content >orig &&
+ git add orig &&
+ test_commit orig &&
+
+ cp orig new_copy &&
+ mv orig new_rename &&
+ git add -N new_copy new_rename
+ )
+'
+
+test_expect_success 'check commit with unstaged rename and copy' '
+ (
+ cd unstaged_rename_and_copy &&
+
+ test_must_fail git -c diff.renames=copy commit
+ )
+'
+
+test_done
+++ /dev/null
-#!/bin/sh
-#
-# Copyright (c) 2007 Steven Grimm
-#
-
-test_description='git commit
-
-Tests for selected commit options.'
-
-. ./test-lib.sh
-
-commit_msg_is () {
- expect=commit_msg_is.expect
- actual=commit_msg_is.actual
-
- printf "%s" "$(git log --pretty=format:%s%b -1)" >"$actual" &&
- printf "%s" "$1" >"$expect" &&
- test_i18ncmp "$expect" "$actual"
-}
-
-# A sanity check to see if commit is working at all.
-test_expect_success 'a basic commit in an empty tree should succeed' '
- echo content > foo &&
- git add foo &&
- git commit -m "initial commit"
-'
-
-test_expect_success 'nonexistent template file should return error' '
- echo changes >> foo &&
- git add foo &&
- (
- GIT_EDITOR="echo hello >\"\$1\"" &&
- export GIT_EDITOR &&
- test_must_fail git commit --template "$PWD"/notexist
- )
-'
-
-test_expect_success 'nonexistent template file in config should return error' '
- test_config commit.template "$PWD"/notexist &&
- (
- GIT_EDITOR="echo hello >\"\$1\"" &&
- export GIT_EDITOR &&
- test_must_fail git commit
- )
-'
-
-# From now on we'll use a template file that exists.
-TEMPLATE="$PWD"/template
-
-test_expect_success 'unedited template should not commit' '
- echo "template line" > "$TEMPLATE" &&
- test_must_fail git commit --template "$TEMPLATE"
-'
-
-test_expect_success 'unedited template with comments should not commit' '
- echo "# comment in template" >> "$TEMPLATE" &&
- test_must_fail git commit --template "$TEMPLATE"
-'
-
-test_expect_success 'a Signed-off-by line by itself should not commit' '
- (
- test_set_editor "$TEST_DIRECTORY"/t7500/add-signed-off &&
- test_must_fail git commit --template "$TEMPLATE"
- )
-'
-
-test_expect_success 'adding comments to a template should not commit' '
- (
- test_set_editor "$TEST_DIRECTORY"/t7500/add-comments &&
- test_must_fail git commit --template "$TEMPLATE"
- )
-'
-
-test_expect_success 'adding real content to a template should commit' '
- (
- test_set_editor "$TEST_DIRECTORY"/t7500/add-content &&
- git commit --template "$TEMPLATE"
- ) &&
- commit_msg_is "template linecommit message"
-'
-
-test_expect_success '-t option should be short for --template' '
- echo "short template" > "$TEMPLATE" &&
- echo "new content" >> foo &&
- git add foo &&
- (
- test_set_editor "$TEST_DIRECTORY"/t7500/add-content &&
- git commit -t "$TEMPLATE"
- ) &&
- commit_msg_is "short templatecommit message"
-'
-
-test_expect_success 'config-specified template should commit' '
- echo "new template" > "$TEMPLATE" &&
- test_config commit.template "$TEMPLATE" &&
- echo "more content" >> foo &&
- git add foo &&
- (
- test_set_editor "$TEST_DIRECTORY"/t7500/add-content &&
- git commit
- ) &&
- commit_msg_is "new templatecommit message"
-'
-
-test_expect_success 'explicit commit message should override template' '
- echo "still more content" >> foo &&
- git add foo &&
- GIT_EDITOR="$TEST_DIRECTORY"/t7500/add-content git commit --template "$TEMPLATE" \
- -m "command line msg" &&
- commit_msg_is "command line msg"
-'
-
-test_expect_success 'commit message from file should override template' '
- echo "content galore" >> foo &&
- git add foo &&
- echo "standard input msg" |
- (
- test_set_editor "$TEST_DIRECTORY"/t7500/add-content &&
- git commit --template "$TEMPLATE" --file -
- ) &&
- commit_msg_is "standard input msg"
-'
-
-cat >"$TEMPLATE" <<\EOF
-
-
-### template
-
-EOF
-test_expect_success 'commit message from template with whitespace issue' '
- echo "content galore" >>foo &&
- git add foo &&
- GIT_EDITOR=\""$TEST_DIRECTORY"\"/t7500/add-whitespaced-content \
- git commit --template "$TEMPLATE" &&
- commit_msg_is "commit message"
-'
-
-test_expect_success 'using alternate GIT_INDEX_FILE (1)' '
-
- cp .git/index saved-index &&
- (
- echo some new content >file &&
- GIT_INDEX_FILE=.git/another_index &&
- export GIT_INDEX_FILE &&
- git add file &&
- git commit -m "commit using another index" &&
- git diff-index --exit-code HEAD &&
- git diff-files --exit-code
- ) &&
- cmp .git/index saved-index >/dev/null
-
-'
-
-test_expect_success 'using alternate GIT_INDEX_FILE (2)' '
-
- cp .git/index saved-index &&
- (
- rm -f .git/no-such-index &&
- GIT_INDEX_FILE=.git/no-such-index &&
- export GIT_INDEX_FILE &&
- git commit -m "commit using nonexistent index" &&
- test -z "$(git ls-files)" &&
- test -z "$(git ls-tree HEAD)"
-
- ) &&
- cmp .git/index saved-index >/dev/null
-'
-
-cat > expect << EOF
-zort
-
-Signed-off-by: C O Mitter <committer@example.com>
-EOF
-
-test_expect_success '--signoff' '
- echo "yet another content *narf*" >> foo &&
- echo "zort" | git commit -s -F - foo &&
- git cat-file commit HEAD | sed "1,/^\$/d" > output &&
- test_cmp expect output
-'
-
-test_expect_success 'commit message from file (1)' '
- mkdir subdir &&
- echo "Log in top directory" >log &&
- echo "Log in sub directory" >subdir/log &&
- (
- cd subdir &&
- git commit --allow-empty -F log
- ) &&
- commit_msg_is "Log in sub directory"
-'
-
-test_expect_success 'commit message from file (2)' '
- rm -f log &&
- echo "Log in sub directory" >subdir/log &&
- (
- cd subdir &&
- git commit --allow-empty -F log
- ) &&
- commit_msg_is "Log in sub directory"
-'
-
-test_expect_success 'commit message from stdin' '
- (
- cd subdir &&
- echo "Log with foo word" | git commit --allow-empty -F -
- ) &&
- commit_msg_is "Log with foo word"
-'
-
-test_expect_success 'commit -F overrides -t' '
- (
- cd subdir &&
- echo "-F log" > f.log &&
- echo "-t template" > t.template &&
- git commit --allow-empty -F f.log -t t.template
- ) &&
- commit_msg_is "-F log"
-'
-
-test_expect_success 'Commit without message is allowed with --allow-empty-message' '
- echo "more content" >>foo &&
- git add foo &&
- >empty &&
- git commit --allow-empty-message <empty &&
- commit_msg_is "" &&
- git tag empty-message-commit
-'
-
-test_expect_success 'Commit without message is no-no without --allow-empty-message' '
- echo "more content" >>foo &&
- git add foo &&
- >empty &&
- test_must_fail git commit <empty
-'
-
-test_expect_success 'Commit a message with --allow-empty-message' '
- echo "even more content" >>foo &&
- git add foo &&
- git commit --allow-empty-message -m"hello there" &&
- commit_msg_is "hello there"
-'
-
-test_expect_success 'commit -C empty respects --allow-empty-message' '
- echo more >>foo &&
- git add foo &&
- test_must_fail git commit -C empty-message-commit &&
- git commit -C empty-message-commit --allow-empty-message &&
- commit_msg_is ""
-'
-
-commit_for_rebase_autosquash_setup () {
- echo "first content line" >>foo &&
- git add foo &&
- cat >log <<EOF &&
-target message subject line
-
-target message body line 1
-target message body line 2
-EOF
- git commit -F log &&
- echo "second content line" >>foo &&
- git add foo &&
- git commit -m "intermediate commit" &&
- echo "third content line" >>foo &&
- git add foo
-}
-
-test_expect_success 'commit --fixup provides correct one-line commit message' '
- commit_for_rebase_autosquash_setup &&
- git commit --fixup HEAD~1 &&
- commit_msg_is "fixup! target message subject line"
-'
-
-test_expect_success 'commit --fixup -m"something" -m"extra"' '
- commit_for_rebase_autosquash_setup &&
- git commit --fixup HEAD~1 -m"something" -m"extra" &&
- commit_msg_is "fixup! target message subject linesomething
-
-extra"
-'
-
-test_expect_success 'commit --squash works with -F' '
- commit_for_rebase_autosquash_setup &&
- echo "log message from file" >msgfile &&
- git commit --squash HEAD~1 -F msgfile &&
- commit_msg_is "squash! target message subject linelog message from file"
-'
-
-test_expect_success 'commit --squash works with -m' '
- commit_for_rebase_autosquash_setup &&
- git commit --squash HEAD~1 -m "foo bar\nbaz" &&
- commit_msg_is "squash! target message subject linefoo bar\nbaz"
-'
-
-test_expect_success 'commit --squash works with -C' '
- commit_for_rebase_autosquash_setup &&
- git commit --squash HEAD~1 -C HEAD &&
- commit_msg_is "squash! target message subject lineintermediate commit"
-'
-
-test_expect_success 'commit --squash works with -c' '
- commit_for_rebase_autosquash_setup &&
- test_set_editor "$TEST_DIRECTORY"/t7500/edit-content &&
- git commit --squash HEAD~1 -c HEAD &&
- commit_msg_is "squash! target message subject lineedited commit"
-'
-
-test_expect_success 'commit --squash works with -C for same commit' '
- commit_for_rebase_autosquash_setup &&
- git commit --squash HEAD -C HEAD &&
- commit_msg_is "squash! intermediate commit"
-'
-
-test_expect_success 'commit --squash works with -c for same commit' '
- commit_for_rebase_autosquash_setup &&
- test_set_editor "$TEST_DIRECTORY"/t7500/edit-content &&
- git commit --squash HEAD -c HEAD &&
- commit_msg_is "squash! edited commit"
-'
-
-test_expect_success 'commit --squash works with editor' '
- commit_for_rebase_autosquash_setup &&
- test_set_editor "$TEST_DIRECTORY"/t7500/add-content &&
- git commit --squash HEAD~1 &&
- commit_msg_is "squash! target message subject linecommit message"
-'
-
-test_expect_success 'invalid message options when using --fixup' '
- echo changes >>foo &&
- echo "message" >log &&
- git add foo &&
- test_must_fail git commit --fixup HEAD~1 --squash HEAD~2 &&
- test_must_fail git commit --fixup HEAD~1 -C HEAD~2 &&
- test_must_fail git commit --fixup HEAD~1 -c HEAD~2 &&
- test_must_fail git commit --fixup HEAD~1 -F log
-'
-
-cat >expected-template <<EOF
-
-# Please enter the commit message for your changes. Lines starting
-# with '#' will be ignored, and an empty message aborts the commit.
-#
-# Author: A U Thor <author@example.com>
-#
-# On branch commit-template-check
-# Changes to be committed:
-# new file: commit-template-check
-#
-# Untracked files not listed
-EOF
-
-test_expect_success 'new line found before status message in commit template' '
- git checkout -b commit-template-check &&
- git reset --hard HEAD &&
- touch commit-template-check &&
- git add commit-template-check &&
- GIT_EDITOR="cat >editor-input" git commit --untracked-files=no --allow-empty-message &&
- test_i18ncmp expected-template editor-input
-'
-
-test_done
--- /dev/null
+#!/bin/sh
+#
+# Copyright (c) 2007 Kristian Høgsberg <krh@redhat.com>
+#
+
+# FIXME: Test the various index usages, -i and -o, test reflog,
+# signoff
+
+test_description='git commit'
+. ./test-lib.sh
+. "$TEST_DIRECTORY/diff-lib.sh"
+
+author='The Real Author <someguy@his.email.org>'
+
+test_tick
+
+test_expect_success 'initial status' '
+ echo bongo bongo >file &&
+ git add file &&
+ git status >actual &&
+ test_i18ngrep "No commits yet" actual
+'
+
+test_expect_success 'fail initial amend' '
+ test_must_fail git commit --amend
+'
+
+test_expect_success 'setup: initial commit' '
+ git commit -m initial
+'
+
+test_expect_success '-m and -F do not mix' '
+ git checkout HEAD file && echo >>file && git add file &&
+ test_must_fail git commit -m foo -m bar -F file
+'
+
+test_expect_success '-m and -C do not mix' '
+ git checkout HEAD file && echo >>file && git add file &&
+ test_must_fail git commit -C HEAD -m illegal
+'
+
+test_expect_success 'paths and -a do not mix' '
+ echo King of the bongo >file &&
+ test_must_fail git commit -m foo -a file
+'
+
+test_expect_success PERL 'can use paths with --interactive' '
+ echo bong-o-bong >file &&
+ # 2: update, 1:st path, that is all, 7: quit
+ test_write_lines 2 1 "" 7 |
+ git commit -m foo --interactive file &&
+ git reset --hard HEAD^
+'
+
+test_expect_success 'removed files and relative paths' '
+ test_when_finished "rm -rf foo" &&
+ git init foo &&
+ >foo/foo.txt &&
+ git -C foo add foo.txt &&
+ git -C foo commit -m first &&
+ git -C foo rm foo.txt &&
+
+ mkdir -p foo/bar &&
+ git -C foo/bar commit -m second ../foo.txt
+'
+
+test_expect_success 'using invalid commit with -C' '
+ test_must_fail git commit --allow-empty -C bogus
+'
+
+test_expect_success 'nothing to commit' '
+ git reset --hard &&
+ test_must_fail git commit -m initial
+'
+
+test_expect_success '--dry-run fails with nothing to commit' '
+ test_must_fail git commit -m initial --dry-run
+'
+
+test_expect_success '--short fails with nothing to commit' '
+ test_must_fail git commit -m initial --short
+'
+
+test_expect_success '--porcelain fails with nothing to commit' '
+ test_must_fail git commit -m initial --porcelain
+'
+
+test_expect_success '--long fails with nothing to commit' '
+ test_must_fail git commit -m initial --long
+'
+
+test_expect_success 'setup: non-initial commit' '
+ echo bongo bongo bongo >file &&
+ git commit -m next -a
+'
+
+test_expect_success '--dry-run with stuff to commit returns ok' '
+ echo bongo bongo bongo >>file &&
+ git commit -m next -a --dry-run
+'
+
+test_expect_success '--short with stuff to commit returns ok' '
+ echo bongo bongo bongo >>file &&
+ git commit -m next -a --short
+'
+
+test_expect_success '--porcelain with stuff to commit returns ok' '
+ echo bongo bongo bongo >>file &&
+ git commit -m next -a --porcelain
+'
+
+test_expect_success '--long with stuff to commit returns ok' '
+ echo bongo bongo bongo >>file &&
+ git commit -m next -a --long
+'
+
+test_expect_success 'commit message from non-existing file' '
+ echo more bongo: bongo bongo bongo bongo >file &&
+ test_must_fail git commit -F gah -a
+'
+
+test_expect_success 'empty commit message' '
+ # Empty except stray tabs and spaces on a few lines.
+ sed -e "s/@//g" >msg <<-\EOF &&
+ @ @
+ @@
+ @ @
+ @Signed-off-by: hula@
+ EOF
+ test_must_fail git commit -F msg -a
+'
+
+test_expect_success 'template "emptyness" check does not kick in with -F' '
+ git checkout HEAD file && echo >>file && git add file &&
+ git commit -t file -F file
+'
+
+test_expect_success 'template "emptyness" check' '
+ git checkout HEAD file && echo >>file && git add file &&
+ test_must_fail git commit -t file 2>err &&
+ test_i18ngrep "did not edit" err
+'
+
+test_expect_success 'setup: commit message from file' '
+ git checkout HEAD file && echo >>file && git add file &&
+ echo this is the commit message, coming from a file >msg &&
+ git commit -F msg -a
+'
+
+test_expect_success 'amend commit' '
+ cat >editor <<-\EOF &&
+ #!/bin/sh
+ sed -e "s/a file/an amend commit/g" < "$1" > "$1-"
+ mv "$1-" "$1"
+ EOF
+ chmod 755 editor &&
+ EDITOR=./editor git commit --amend
+'
+
+test_expect_success 'amend --only ignores staged contents' '
+ cp file file.expect &&
+ echo changed >file &&
+ git add file &&
+ git commit --no-edit --amend --only &&
+ git cat-file blob HEAD:file >file.actual &&
+ test_cmp file.expect file.actual &&
+ git diff --exit-code
+'
+
+test_expect_success 'allow-empty --only ignores staged contents' '
+ echo changed-again >file &&
+ git add file &&
+ git commit --allow-empty --only -m "empty" &&
+ git cat-file blob HEAD:file >file.actual &&
+ test_cmp file.expect file.actual &&
+ git diff --exit-code
+'
+
+test_expect_success 'set up editor' '
+ cat >editor <<-\EOF &&
+ #!/bin/sh
+ sed -e "s/unamended/amended/g" <"$1" >"$1-"
+ mv "$1-" "$1"
+ EOF
+ chmod 755 editor
+'
+
+test_expect_success 'amend without launching editor' '
+ echo unamended >expect &&
+ git commit --allow-empty -m "unamended" &&
+ echo needs more bongo >file &&
+ git add file &&
+ EDITOR=./editor git commit --no-edit --amend &&
+ git diff --exit-code HEAD -- file &&
+ git diff-tree -s --format=%s HEAD >msg &&
+ test_cmp expect msg
+'
+
+test_expect_success '--amend --edit' '
+ echo amended >expect &&
+ git commit --allow-empty -m "unamended" &&
+ echo bongo again >file &&
+ git add file &&
+ EDITOR=./editor git commit --edit --amend &&
+ git diff-tree -s --format=%s HEAD >msg &&
+ test_cmp expect msg
+'
+
+test_expect_success '--amend --edit of empty message' '
+ cat >replace <<-\EOF &&
+ #!/bin/sh
+ echo "amended" >"$1"
+ EOF
+ chmod 755 replace &&
+ git commit --allow-empty --allow-empty-message -m "" &&
+ echo more bongo >file &&
+ git add file &&
+ EDITOR=./replace git commit --edit --amend &&
+ git diff-tree -s --format=%s HEAD >msg &&
+ ./replace expect &&
+ test_cmp expect msg
+'
+
+test_expect_success '--amend to set message to empty' '
+ echo bata >file &&
+ git add file &&
+ git commit -m "unamended" &&
+ git commit --amend --allow-empty-message -m "" &&
+ git diff-tree -s --format=%s HEAD >msg &&
+ echo "" >expect &&
+ test_cmp expect msg
+'
+
+test_expect_success '--amend to set empty message needs --allow-empty-message' '
+ echo conga >file &&
+ git add file &&
+ git commit -m "unamended" &&
+ test_must_fail git commit --amend -m "" &&
+ git diff-tree -s --format=%s HEAD >msg &&
+ echo "unamended" >expect &&
+ test_cmp expect msg
+'
+
+test_expect_success '-m --edit' '
+ echo amended >expect &&
+ git commit --allow-empty -m buffer &&
+ echo bongo bongo >file &&
+ git add file &&
+ EDITOR=./editor git commit -m unamended --edit &&
+ git diff-tree -s --format=%s HEAD >msg &&
+ test_cmp expect msg
+'
+
+test_expect_success '-m and -F do not mix' '
+ echo enough with the bongos >file &&
+ test_must_fail git commit -F msg -m amending .
+'
+
+test_expect_success 'using message from other commit' '
+ git commit -C HEAD^ .
+'
+
+test_expect_success 'editing message from other commit' '
+ cat >editor <<-\EOF &&
+ #!/bin/sh
+ sed -e "s/amend/older/g" < "$1" > "$1-"
+ mv "$1-" "$1"
+ EOF
+ chmod 755 editor &&
+ echo hula hula >file &&
+ EDITOR=./editor git commit -c HEAD^ -a
+'
+
+test_expect_success 'message from stdin' '
+ echo silly new contents >file &&
+ echo commit message from stdin |
+ git commit -F - -a
+'
+
+test_expect_success 'overriding author from command line' '
+ echo gak >file &&
+ git commit -m author \
+ --author "Rubber Duck <rduck@convoy.org>" -a >output 2>&1 &&
+ grep Rubber.Duck output
+'
+
+test_expect_success PERL 'interactive add' '
+ echo 7 |
+ git commit --interactive |
+ grep "What now"
+'
+
+test_expect_success PERL "commit --interactive doesn't change index if editor aborts" '
+ echo zoo >file &&
+ test_must_fail git diff --exit-code >diff1 &&
+ test_write_lines u "*" q |
+ (
+ EDITOR=: &&
+ export EDITOR &&
+ test_must_fail git commit --interactive
+ ) &&
+ git diff >diff2 &&
+ compare_diff_patch diff1 diff2
+'
+
+test_expect_success 'editor not invoked if -F is given' '
+ cat >editor <<-\EOF &&
+ #!/bin/sh
+ sed -e s/good/bad/g <"$1" >"$1-"
+ mv "$1-" "$1"
+ EOF
+ chmod 755 editor &&
+
+ echo A good commit message. >msg &&
+ echo moo >file &&
+
+ EDITOR=./editor git commit -a -F msg &&
+ git show -s --pretty=format:%s >subject &&
+ grep -q good subject &&
+
+ echo quack >file &&
+ echo Another good message. |
+ EDITOR=./editor git commit -a -F - &&
+ git show -s --pretty=format:%s >subject &&
+ grep -q good subject
+'
+
+test_expect_success 'partial commit that involves removal (1)' '
+
+ git rm --cached file &&
+ mv file elif &&
+ git add elif &&
+ git commit -m "Partial: add elif" elif &&
+ git diff-tree --name-status HEAD^ HEAD >current &&
+ echo "A elif" >expected &&
+ test_cmp expected current
+
+'
+
+test_expect_success 'partial commit that involves removal (2)' '
+
+ git commit -m "Partial: remove file" file &&
+ git diff-tree --name-status HEAD^ HEAD >current &&
+ echo "D file" >expected &&
+ test_cmp expected current
+
+'
+
+test_expect_success 'partial commit that involves removal (3)' '
+
+ git rm --cached elif &&
+ echo elif >elif &&
+ git commit -m "Partial: modify elif" elif &&
+ git diff-tree --name-status HEAD^ HEAD >current &&
+ echo "M elif" >expected &&
+ test_cmp expected current
+
+'
+
+test_expect_success 'amend commit to fix author' '
+
+ oldtick=$GIT_AUTHOR_DATE &&
+ test_tick &&
+ git reset --hard &&
+ git cat-file -p HEAD |
+ sed -e "s/author.*/author $author $oldtick/" \
+ -e "s/^\(committer.*> \).*$/\1$GIT_COMMITTER_DATE/" > \
+ expected &&
+ git commit --amend --author="$author" &&
+ git cat-file -p HEAD > current &&
+ test_cmp expected current
+
+'
+
+test_expect_success 'amend commit to fix date' '
+
+ test_tick &&
+ newtick=$GIT_AUTHOR_DATE &&
+ git reset --hard &&
+ git cat-file -p HEAD |
+ sed -e "s/author.*/author $author $newtick/" \
+ -e "s/^\(committer.*> \).*$/\1$GIT_COMMITTER_DATE/" > \
+ expected &&
+ git commit --amend --date="$newtick" &&
+ git cat-file -p HEAD > current &&
+ test_cmp expected current
+
+'
+
+test_expect_success 'commit mentions forced date in output' '
+ git commit --amend --date=2010-01-02T03:04:05 >output &&
+ grep "Date: *Sat Jan 2 03:04:05 2010" output
+'
+
+test_expect_success 'commit complains about completely bogus dates' '
+ test_must_fail git commit --amend --date=seventeen
+'
+
+test_expect_success 'commit --date allows approxidate' '
+ git commit --amend \
+ --date="midnight the 12th of october, anno domini 1979" &&
+ echo "Fri Oct 12 00:00:00 1979 +0000" >expect &&
+ git log -1 --format=%ad >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'sign off (1)' '
+
+ echo 1 >positive &&
+ git add positive &&
+ git commit -s -m "thank you" &&
+ git cat-file commit HEAD | sed -e "1,/^\$/d" >actual &&
+ (
+ echo thank you &&
+ echo &&
+ git var GIT_COMMITTER_IDENT |
+ sed -e "s/>.*/>/" -e "s/^/Signed-off-by: /"
+ ) >expected &&
+ test_cmp expected actual
+
+'
+
+test_expect_success 'sign off (2)' '
+
+ echo 2 >positive &&
+ git add positive &&
+ existing="Signed-off-by: Watch This <watchthis@example.com>" &&
+ git commit -s -m "thank you
+
+$existing" &&
+ git cat-file commit HEAD | sed -e "1,/^\$/d" >actual &&
+ (
+ echo thank you &&
+ echo &&
+ echo $existing &&
+ git var GIT_COMMITTER_IDENT |
+ sed -e "s/>.*/>/" -e "s/^/Signed-off-by: /"
+ ) >expected &&
+ test_cmp expected actual
+
+'
+
+test_expect_success 'signoff gap' '
+
+ echo 3 >positive &&
+ git add positive &&
+ alt="Alt-RFC-822-Header: Value" &&
+ git commit -s -m "welcome
+
+$alt" &&
+ git cat-file commit HEAD | sed -e "1,/^\$/d" > actual &&
+ (
+ echo welcome &&
+ echo &&
+ echo $alt &&
+ git var GIT_COMMITTER_IDENT |
+ sed -e "s/>.*/>/" -e "s/^/Signed-off-by: /"
+ ) >expected &&
+ test_cmp expected actual
+'
+
+test_expect_success 'signoff gap 2' '
+
+ echo 4 >positive &&
+ git add positive &&
+ alt="fixed: 34" &&
+ git commit -s -m "welcome
+
+We have now
+$alt" &&
+ git cat-file commit HEAD | sed -e "1,/^\$/d" > actual &&
+ (
+ echo welcome &&
+ echo &&
+ echo We have now &&
+ echo $alt &&
+ echo &&
+ git var GIT_COMMITTER_IDENT |
+ sed -e "s/>.*/>/" -e "s/^/Signed-off-by: /"
+ ) >expected &&
+ test_cmp expected actual
+'
+
+test_expect_success 'signoff respects trailer config' '
+
+ echo 5 >positive &&
+ git add positive &&
+ git commit -s -m "subject
+
+non-trailer line
+Myfooter: x" &&
+ git cat-file commit HEAD | sed -e "1,/^\$/d" > actual &&
+ (
+ echo subject &&
+ echo &&
+ echo non-trailer line &&
+ echo Myfooter: x &&
+ echo &&
+ echo "Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>"
+ ) >expected &&
+ test_cmp expected actual &&
+
+ echo 6 >positive &&
+ git add positive &&
+ git -c "trailer.Myfooter.ifexists=add" commit -s -m "subject
+
+non-trailer line
+Myfooter: x" &&
+ git cat-file commit HEAD | sed -e "1,/^\$/d" > actual &&
+ (
+ echo subject &&
+ echo &&
+ echo non-trailer line &&
+ echo Myfooter: x &&
+ echo "Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>"
+ ) >expected &&
+ test_cmp expected actual
+'
+
+test_expect_success 'signoff not confused by ---' '
+ cat >expected <<-EOF &&
+ subject
+
+ body
+ ---
+ these dashes confuse the parser!
+
+ Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>
+ EOF
+ # should be a noop, since we already signed
+ git commit --allow-empty --signoff -F expected &&
+ git log -1 --pretty=format:%B >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'multiple -m' '
+
+ >negative &&
+ git add negative &&
+ git commit -m "one" -m "two" -m "three" &&
+ git cat-file commit HEAD | sed -e "1,/^\$/d" >actual &&
+ (
+ echo one &&
+ echo &&
+ echo two &&
+ echo &&
+ echo three
+ ) >expected &&
+ test_cmp expected actual
+
+'
+
+test_expect_success 'amend commit to fix author' '
+
+ oldtick=$GIT_AUTHOR_DATE &&
+ test_tick &&
+ git reset --hard &&
+ git cat-file -p HEAD |
+ sed -e "s/author.*/author $author $oldtick/" \
+ -e "s/^\(committer.*> \).*$/\1$GIT_COMMITTER_DATE/" > \
+ expected &&
+ git commit --amend --author="$author" &&
+ git cat-file -p HEAD > current &&
+ test_cmp expected current
+
+'
+
+test_expect_success 'git commit <file> with dirty index' '
+ echo tacocat > elif &&
+ echo tehlulz > chz &&
+ git add chz &&
+ git commit elif -m "tacocat is a palindrome" &&
+ git show --stat | grep elif &&
+ git diff --cached | grep chz
+'
+
+test_expect_success 'same tree (single parent)' '
+
+ git reset --hard &&
+ test_must_fail git commit -m empty
+
+'
+
+test_expect_success 'same tree (single parent) --allow-empty' '
+
+ git commit --allow-empty -m "forced empty" &&
+ git cat-file commit HEAD | grep forced
+
+'
+
+test_expect_success 'same tree (merge and amend merge)' '
+
+ git checkout -b side HEAD^ &&
+ echo zero >zero &&
+ git add zero &&
+ git commit -m "add zero" &&
+ git checkout master &&
+
+ git merge -s ours side -m "empty ok" &&
+ git diff HEAD^ HEAD >actual &&
+ test_must_be_empty actual &&
+
+ git commit --amend -m "empty really ok" &&
+ git diff HEAD^ HEAD >actual &&
+ test_must_be_empty actual
+
+'
+
+test_expect_success 'amend using the message from another commit' '
+
+ git reset --hard &&
+ test_tick &&
+ git commit --allow-empty -m "old commit" &&
+ old=$(git rev-parse --verify HEAD) &&
+ test_tick &&
+ git commit --allow-empty -m "new commit" &&
+ new=$(git rev-parse --verify HEAD) &&
+ test_tick &&
+ git commit --allow-empty --amend -C "$old" &&
+ git show --pretty="format:%ad %s" "$old" >expected &&
+ git show --pretty="format:%ad %s" HEAD >actual &&
+ test_cmp expected actual
+
+'
+
+test_expect_success 'amend using the message from a commit named with tag' '
+
+ git reset --hard &&
+ test_tick &&
+ git commit --allow-empty -m "old commit" &&
+ old=$(git rev-parse --verify HEAD) &&
+ git tag -a -m "tag on old" tagged-old HEAD &&
+ test_tick &&
+ git commit --allow-empty -m "new commit" &&
+ new=$(git rev-parse --verify HEAD) &&
+ test_tick &&
+ git commit --allow-empty --amend -C tagged-old &&
+ git show --pretty="format:%ad %s" "$old" >expected &&
+ git show --pretty="format:%ad %s" HEAD >actual &&
+ test_cmp expected actual
+
+'
+
+test_expect_success 'amend can copy notes' '
+
+ git config notes.rewrite.amend true &&
+ git config notes.rewriteRef "refs/notes/*" &&
+ test_commit foo &&
+ git notes add -m"a note" &&
+ test_tick &&
+ git commit --amend -m"new foo" &&
+ test "$(git notes show)" = "a note"
+
+'
+
+test_expect_success 'commit a file whose name is a dash' '
+ git reset --hard &&
+ for i in 1 2 3 4 5
+ do
+ echo $i
+ done >./- &&
+ git add ./- &&
+ test_tick &&
+ git commit -m "add dash" >output </dev/null &&
+ test_i18ngrep " changed, 5 insertions" output
+'
+
+test_expect_success '--only works on to-be-born branch' '
+ # This test relies on having something in the index, as it
+ # would not otherwise actually prove much. So check this.
+ test -n "$(git ls-files)" &&
+ git checkout --orphan orphan &&
+ echo foo >newfile &&
+ git add newfile &&
+ git commit --only newfile -m"--only on unborn branch" &&
+ echo newfile >expected &&
+ git ls-tree -r --name-only HEAD >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success '--dry-run with conflicts fixed from a merge' '
+ # setup two branches with conflicting information
+ # in the same file, resolve the conflict,
+ # call commit with --dry-run
+ echo "Initial contents, unimportant" >test-file &&
+ git add test-file &&
+ git commit -m "Initial commit" &&
+ echo "commit-1-state" >test-file &&
+ git commit -m "commit 1" -i test-file &&
+ git tag commit-1 &&
+ git checkout -b branch-2 HEAD^1 &&
+ echo "commit-2-state" >test-file &&
+ git commit -m "commit 2" -i test-file &&
+ test_must_fail git merge --no-commit commit-1 &&
+ echo "commit-2-state" >test-file &&
+ git add test-file &&
+ git commit --dry-run &&
+ git commit -m "conflicts fixed from merge."
+'
+
+test_expect_success '--dry-run --short' '
+ >test-file &&
+ git add test-file &&
+ git commit --dry-run --short
+'
+
+test_done
+++ /dev/null
-#!/bin/sh
-#
-# Copyright (c) 2007 Kristian Høgsberg <krh@redhat.com>
-#
-
-# FIXME: Test the various index usages, -i and -o, test reflog,
-# signoff
-
-test_description='git commit'
-. ./test-lib.sh
-. "$TEST_DIRECTORY/diff-lib.sh"
-
-author='The Real Author <someguy@his.email.org>'
-
-test_tick
-
-test_expect_success 'initial status' '
- echo bongo bongo >file &&
- git add file &&
- git status >actual &&
- test_i18ngrep "No commits yet" actual
-'
-
-test_expect_success 'fail initial amend' '
- test_must_fail git commit --amend
-'
-
-test_expect_success 'setup: initial commit' '
- git commit -m initial
-'
-
-test_expect_success '-m and -F do not mix' '
- git checkout HEAD file && echo >>file && git add file &&
- test_must_fail git commit -m foo -m bar -F file
-'
-
-test_expect_success '-m and -C do not mix' '
- git checkout HEAD file && echo >>file && git add file &&
- test_must_fail git commit -C HEAD -m illegal
-'
-
-test_expect_success 'paths and -a do not mix' '
- echo King of the bongo >file &&
- test_must_fail git commit -m foo -a file
-'
-
-test_expect_success PERL 'can use paths with --interactive' '
- echo bong-o-bong >file &&
- # 2: update, 1:st path, that is all, 7: quit
- test_write_lines 2 1 "" 7 |
- git commit -m foo --interactive file &&
- git reset --hard HEAD^
-'
-
-test_expect_success 'removed files and relative paths' '
- test_when_finished "rm -rf foo" &&
- git init foo &&
- >foo/foo.txt &&
- git -C foo add foo.txt &&
- git -C foo commit -m first &&
- git -C foo rm foo.txt &&
-
- mkdir -p foo/bar &&
- git -C foo/bar commit -m second ../foo.txt
-'
-
-test_expect_success 'using invalid commit with -C' '
- test_must_fail git commit --allow-empty -C bogus
-'
-
-test_expect_success 'nothing to commit' '
- git reset --hard &&
- test_must_fail git commit -m initial
-'
-
-test_expect_success '--dry-run fails with nothing to commit' '
- test_must_fail git commit -m initial --dry-run
-'
-
-test_expect_success '--short fails with nothing to commit' '
- test_must_fail git commit -m initial --short
-'
-
-test_expect_success '--porcelain fails with nothing to commit' '
- test_must_fail git commit -m initial --porcelain
-'
-
-test_expect_success '--long fails with nothing to commit' '
- test_must_fail git commit -m initial --long
-'
-
-test_expect_success 'setup: non-initial commit' '
- echo bongo bongo bongo >file &&
- git commit -m next -a
-'
-
-test_expect_success '--dry-run with stuff to commit returns ok' '
- echo bongo bongo bongo >>file &&
- git commit -m next -a --dry-run
-'
-
-test_expect_failure '--short with stuff to commit returns ok' '
- echo bongo bongo bongo >>file &&
- git commit -m next -a --short
-'
-
-test_expect_failure '--porcelain with stuff to commit returns ok' '
- echo bongo bongo bongo >>file &&
- git commit -m next -a --porcelain
-'
-
-test_expect_success '--long with stuff to commit returns ok' '
- echo bongo bongo bongo >>file &&
- git commit -m next -a --long
-'
-
-test_expect_success 'commit message from non-existing file' '
- echo more bongo: bongo bongo bongo bongo >file &&
- test_must_fail git commit -F gah -a
-'
-
-test_expect_success 'empty commit message' '
- # Empty except stray tabs and spaces on a few lines.
- sed -e "s/@//g" >msg <<-\EOF &&
- @ @
- @@
- @ @
- @Signed-off-by: hula@
- EOF
- test_must_fail git commit -F msg -a
-'
-
-test_expect_success 'template "emptyness" check does not kick in with -F' '
- git checkout HEAD file && echo >>file && git add file &&
- git commit -t file -F file
-'
-
-test_expect_success 'template "emptyness" check' '
- git checkout HEAD file && echo >>file && git add file &&
- test_must_fail git commit -t file 2>err &&
- test_i18ngrep "did not edit" err
-'
-
-test_expect_success 'setup: commit message from file' '
- git checkout HEAD file && echo >>file && git add file &&
- echo this is the commit message, coming from a file >msg &&
- git commit -F msg -a
-'
-
-test_expect_success 'amend commit' '
- cat >editor <<-\EOF &&
- #!/bin/sh
- sed -e "s/a file/an amend commit/g" < "$1" > "$1-"
- mv "$1-" "$1"
- EOF
- chmod 755 editor &&
- EDITOR=./editor git commit --amend
-'
-
-test_expect_success 'amend --only ignores staged contents' '
- cp file file.expect &&
- echo changed >file &&
- git add file &&
- git commit --no-edit --amend --only &&
- git cat-file blob HEAD:file >file.actual &&
- test_cmp file.expect file.actual &&
- git diff --exit-code
-'
-
-test_expect_success 'allow-empty --only ignores staged contents' '
- echo changed-again >file &&
- git add file &&
- git commit --allow-empty --only -m "empty" &&
- git cat-file blob HEAD:file >file.actual &&
- test_cmp file.expect file.actual &&
- git diff --exit-code
-'
-
-test_expect_success 'set up editor' '
- cat >editor <<-\EOF &&
- #!/bin/sh
- sed -e "s/unamended/amended/g" <"$1" >"$1-"
- mv "$1-" "$1"
- EOF
- chmod 755 editor
-'
-
-test_expect_success 'amend without launching editor' '
- echo unamended >expect &&
- git commit --allow-empty -m "unamended" &&
- echo needs more bongo >file &&
- git add file &&
- EDITOR=./editor git commit --no-edit --amend &&
- git diff --exit-code HEAD -- file &&
- git diff-tree -s --format=%s HEAD >msg &&
- test_cmp expect msg
-'
-
-test_expect_success '--amend --edit' '
- echo amended >expect &&
- git commit --allow-empty -m "unamended" &&
- echo bongo again >file &&
- git add file &&
- EDITOR=./editor git commit --edit --amend &&
- git diff-tree -s --format=%s HEAD >msg &&
- test_cmp expect msg
-'
-
-test_expect_success '--amend --edit of empty message' '
- cat >replace <<-\EOF &&
- #!/bin/sh
- echo "amended" >"$1"
- EOF
- chmod 755 replace &&
- git commit --allow-empty --allow-empty-message -m "" &&
- echo more bongo >file &&
- git add file &&
- EDITOR=./replace git commit --edit --amend &&
- git diff-tree -s --format=%s HEAD >msg &&
- ./replace expect &&
- test_cmp expect msg
-'
-
-test_expect_success '--amend to set message to empty' '
- echo bata >file &&
- git add file &&
- git commit -m "unamended" &&
- git commit --amend --allow-empty-message -m "" &&
- git diff-tree -s --format=%s HEAD >msg &&
- echo "" >expect &&
- test_cmp expect msg
-'
-
-test_expect_success '--amend to set empty message needs --allow-empty-message' '
- echo conga >file &&
- git add file &&
- git commit -m "unamended" &&
- test_must_fail git commit --amend -m "" &&
- git diff-tree -s --format=%s HEAD >msg &&
- echo "unamended" >expect &&
- test_cmp expect msg
-'
-
-test_expect_success '-m --edit' '
- echo amended >expect &&
- git commit --allow-empty -m buffer &&
- echo bongo bongo >file &&
- git add file &&
- EDITOR=./editor git commit -m unamended --edit &&
- git diff-tree -s --format=%s HEAD >msg &&
- test_cmp expect msg
-'
-
-test_expect_success '-m and -F do not mix' '
- echo enough with the bongos >file &&
- test_must_fail git commit -F msg -m amending .
-'
-
-test_expect_success 'using message from other commit' '
- git commit -C HEAD^ .
-'
-
-test_expect_success 'editing message from other commit' '
- cat >editor <<-\EOF &&
- #!/bin/sh
- sed -e "s/amend/older/g" < "$1" > "$1-"
- mv "$1-" "$1"
- EOF
- chmod 755 editor &&
- echo hula hula >file &&
- EDITOR=./editor git commit -c HEAD^ -a
-'
-
-test_expect_success 'message from stdin' '
- echo silly new contents >file &&
- echo commit message from stdin |
- git commit -F - -a
-'
-
-test_expect_success 'overriding author from command line' '
- echo gak >file &&
- git commit -m author \
- --author "Rubber Duck <rduck@convoy.org>" -a >output 2>&1 &&
- grep Rubber.Duck output
-'
-
-test_expect_success PERL 'interactive add' '
- echo 7 |
- git commit --interactive |
- grep "What now"
-'
-
-test_expect_success PERL "commit --interactive doesn't change index if editor aborts" '
- echo zoo >file &&
- test_must_fail git diff --exit-code >diff1 &&
- test_write_lines u "*" q |
- (
- EDITOR=: &&
- export EDITOR &&
- test_must_fail git commit --interactive
- ) &&
- git diff >diff2 &&
- compare_diff_patch diff1 diff2
-'
-
-test_expect_success 'editor not invoked if -F is given' '
- cat >editor <<-\EOF &&
- #!/bin/sh
- sed -e s/good/bad/g <"$1" >"$1-"
- mv "$1-" "$1"
- EOF
- chmod 755 editor &&
-
- echo A good commit message. >msg &&
- echo moo >file &&
-
- EDITOR=./editor git commit -a -F msg &&
- git show -s --pretty=format:%s >subject &&
- grep -q good subject &&
-
- echo quack >file &&
- echo Another good message. |
- EDITOR=./editor git commit -a -F - &&
- git show -s --pretty=format:%s >subject &&
- grep -q good subject
-'
-
-test_expect_success 'partial commit that involves removal (1)' '
-
- git rm --cached file &&
- mv file elif &&
- git add elif &&
- git commit -m "Partial: add elif" elif &&
- git diff-tree --name-status HEAD^ HEAD >current &&
- echo "A elif" >expected &&
- test_cmp expected current
-
-'
-
-test_expect_success 'partial commit that involves removal (2)' '
-
- git commit -m "Partial: remove file" file &&
- git diff-tree --name-status HEAD^ HEAD >current &&
- echo "D file" >expected &&
- test_cmp expected current
-
-'
-
-test_expect_success 'partial commit that involves removal (3)' '
-
- git rm --cached elif &&
- echo elif >elif &&
- git commit -m "Partial: modify elif" elif &&
- git diff-tree --name-status HEAD^ HEAD >current &&
- echo "M elif" >expected &&
- test_cmp expected current
-
-'
-
-test_expect_success 'amend commit to fix author' '
-
- oldtick=$GIT_AUTHOR_DATE &&
- test_tick &&
- git reset --hard &&
- git cat-file -p HEAD |
- sed -e "s/author.*/author $author $oldtick/" \
- -e "s/^\(committer.*> \).*$/\1$GIT_COMMITTER_DATE/" > \
- expected &&
- git commit --amend --author="$author" &&
- git cat-file -p HEAD > current &&
- test_cmp expected current
-
-'
-
-test_expect_success 'amend commit to fix date' '
-
- test_tick &&
- newtick=$GIT_AUTHOR_DATE &&
- git reset --hard &&
- git cat-file -p HEAD |
- sed -e "s/author.*/author $author $newtick/" \
- -e "s/^\(committer.*> \).*$/\1$GIT_COMMITTER_DATE/" > \
- expected &&
- git commit --amend --date="$newtick" &&
- git cat-file -p HEAD > current &&
- test_cmp expected current
-
-'
-
-test_expect_success 'commit mentions forced date in output' '
- git commit --amend --date=2010-01-02T03:04:05 >output &&
- grep "Date: *Sat Jan 2 03:04:05 2010" output
-'
-
-test_expect_success 'commit complains about completely bogus dates' '
- test_must_fail git commit --amend --date=seventeen
-'
-
-test_expect_success 'commit --date allows approxidate' '
- git commit --amend \
- --date="midnight the 12th of october, anno domini 1979" &&
- echo "Fri Oct 12 00:00:00 1979 +0000" >expect &&
- git log -1 --format=%ad >actual &&
- test_cmp expect actual
-'
-
-test_expect_success 'sign off (1)' '
-
- echo 1 >positive &&
- git add positive &&
- git commit -s -m "thank you" &&
- git cat-file commit HEAD | sed -e "1,/^\$/d" >actual &&
- (
- echo thank you &&
- echo &&
- git var GIT_COMMITTER_IDENT |
- sed -e "s/>.*/>/" -e "s/^/Signed-off-by: /"
- ) >expected &&
- test_cmp expected actual
-
-'
-
-test_expect_success 'sign off (2)' '
-
- echo 2 >positive &&
- git add positive &&
- existing="Signed-off-by: Watch This <watchthis@example.com>" &&
- git commit -s -m "thank you
-
-$existing" &&
- git cat-file commit HEAD | sed -e "1,/^\$/d" >actual &&
- (
- echo thank you &&
- echo &&
- echo $existing &&
- git var GIT_COMMITTER_IDENT |
- sed -e "s/>.*/>/" -e "s/^/Signed-off-by: /"
- ) >expected &&
- test_cmp expected actual
-
-'
-
-test_expect_success 'signoff gap' '
-
- echo 3 >positive &&
- git add positive &&
- alt="Alt-RFC-822-Header: Value" &&
- git commit -s -m "welcome
-
-$alt" &&
- git cat-file commit HEAD | sed -e "1,/^\$/d" > actual &&
- (
- echo welcome &&
- echo &&
- echo $alt &&
- git var GIT_COMMITTER_IDENT |
- sed -e "s/>.*/>/" -e "s/^/Signed-off-by: /"
- ) >expected &&
- test_cmp expected actual
-'
-
-test_expect_success 'signoff gap 2' '
-
- echo 4 >positive &&
- git add positive &&
- alt="fixed: 34" &&
- git commit -s -m "welcome
-
-We have now
-$alt" &&
- git cat-file commit HEAD | sed -e "1,/^\$/d" > actual &&
- (
- echo welcome &&
- echo &&
- echo We have now &&
- echo $alt &&
- echo &&
- git var GIT_COMMITTER_IDENT |
- sed -e "s/>.*/>/" -e "s/^/Signed-off-by: /"
- ) >expected &&
- test_cmp expected actual
-'
-
-test_expect_success 'signoff respects trailer config' '
-
- echo 5 >positive &&
- git add positive &&
- git commit -s -m "subject
-
-non-trailer line
-Myfooter: x" &&
- git cat-file commit HEAD | sed -e "1,/^\$/d" > actual &&
- (
- echo subject &&
- echo &&
- echo non-trailer line &&
- echo Myfooter: x &&
- echo &&
- echo "Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>"
- ) >expected &&
- test_cmp expected actual &&
-
- echo 6 >positive &&
- git add positive &&
- git -c "trailer.Myfooter.ifexists=add" commit -s -m "subject
-
-non-trailer line
-Myfooter: x" &&
- git cat-file commit HEAD | sed -e "1,/^\$/d" > actual &&
- (
- echo subject &&
- echo &&
- echo non-trailer line &&
- echo Myfooter: x &&
- echo "Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>"
- ) >expected &&
- test_cmp expected actual
-'
-
-test_expect_success 'multiple -m' '
-
- >negative &&
- git add negative &&
- git commit -m "one" -m "two" -m "three" &&
- git cat-file commit HEAD | sed -e "1,/^\$/d" >actual &&
- (
- echo one &&
- echo &&
- echo two &&
- echo &&
- echo three
- ) >expected &&
- test_cmp expected actual
-
-'
-
-test_expect_success 'amend commit to fix author' '
-
- oldtick=$GIT_AUTHOR_DATE &&
- test_tick &&
- git reset --hard &&
- git cat-file -p HEAD |
- sed -e "s/author.*/author $author $oldtick/" \
- -e "s/^\(committer.*> \).*$/\1$GIT_COMMITTER_DATE/" > \
- expected &&
- git commit --amend --author="$author" &&
- git cat-file -p HEAD > current &&
- test_cmp expected current
-
-'
-
-test_expect_success 'git commit <file> with dirty index' '
- echo tacocat > elif &&
- echo tehlulz > chz &&
- git add chz &&
- git commit elif -m "tacocat is a palindrome" &&
- git show --stat | grep elif &&
- git diff --cached | grep chz
-'
-
-test_expect_success 'same tree (single parent)' '
-
- git reset --hard &&
- test_must_fail git commit -m empty
-
-'
-
-test_expect_success 'same tree (single parent) --allow-empty' '
-
- git commit --allow-empty -m "forced empty" &&
- git cat-file commit HEAD | grep forced
-
-'
-
-test_expect_success 'same tree (merge and amend merge)' '
-
- git checkout -b side HEAD^ &&
- echo zero >zero &&
- git add zero &&
- git commit -m "add zero" &&
- git checkout master &&
-
- git merge -s ours side -m "empty ok" &&
- git diff HEAD^ HEAD >actual &&
- test_must_be_empty actual &&
-
- git commit --amend -m "empty really ok" &&
- git diff HEAD^ HEAD >actual &&
- test_must_be_empty actual
-
-'
-
-test_expect_success 'amend using the message from another commit' '
-
- git reset --hard &&
- test_tick &&
- git commit --allow-empty -m "old commit" &&
- old=$(git rev-parse --verify HEAD) &&
- test_tick &&
- git commit --allow-empty -m "new commit" &&
- new=$(git rev-parse --verify HEAD) &&
- test_tick &&
- git commit --allow-empty --amend -C "$old" &&
- git show --pretty="format:%ad %s" "$old" >expected &&
- git show --pretty="format:%ad %s" HEAD >actual &&
- test_cmp expected actual
-
-'
-
-test_expect_success 'amend using the message from a commit named with tag' '
-
- git reset --hard &&
- test_tick &&
- git commit --allow-empty -m "old commit" &&
- old=$(git rev-parse --verify HEAD) &&
- git tag -a -m "tag on old" tagged-old HEAD &&
- test_tick &&
- git commit --allow-empty -m "new commit" &&
- new=$(git rev-parse --verify HEAD) &&
- test_tick &&
- git commit --allow-empty --amend -C tagged-old &&
- git show --pretty="format:%ad %s" "$old" >expected &&
- git show --pretty="format:%ad %s" HEAD >actual &&
- test_cmp expected actual
-
-'
-
-test_expect_success 'amend can copy notes' '
-
- git config notes.rewrite.amend true &&
- git config notes.rewriteRef "refs/notes/*" &&
- test_commit foo &&
- git notes add -m"a note" &&
- test_tick &&
- git commit --amend -m"new foo" &&
- test "$(git notes show)" = "a note"
-
-'
-
-test_expect_success 'commit a file whose name is a dash' '
- git reset --hard &&
- for i in 1 2 3 4 5
- do
- echo $i
- done >./- &&
- git add ./- &&
- test_tick &&
- git commit -m "add dash" >output </dev/null &&
- test_i18ngrep " changed, 5 insertions" output
-'
-
-test_expect_success '--only works on to-be-born branch' '
- # This test relies on having something in the index, as it
- # would not otherwise actually prove much. So check this.
- test -n "$(git ls-files)" &&
- git checkout --orphan orphan &&
- echo foo >newfile &&
- git add newfile &&
- git commit --only newfile -m"--only on unborn branch" &&
- echo newfile >expected &&
- git ls-tree -r --name-only HEAD >actual &&
- test_cmp expected actual
-'
-
-test_expect_success '--dry-run with conflicts fixed from a merge' '
- # setup two branches with conflicting information
- # in the same file, resolve the conflict,
- # call commit with --dry-run
- echo "Initial contents, unimportant" >test-file &&
- git add test-file &&
- git commit -m "Initial commit" &&
- echo "commit-1-state" >test-file &&
- git commit -m "commit 1" -i test-file &&
- git tag commit-1 &&
- git checkout -b branch-2 HEAD^1 &&
- echo "commit-2-state" >test-file &&
- git commit -m "commit 2" -i test-file &&
- test_must_fail git merge --no-commit commit-1 &&
- echo "commit-2-state" >test-file &&
- git add test-file &&
- git commit --dry-run &&
- git commit -m "conflicts fixed from merge."
-'
-
-test_done
--- /dev/null
+#!/bin/sh
+
+test_description='git commit porcelain-ish'
+
+. ./test-lib.sh
+
+commit_msg_is () {
+ expect=commit_msg_is.expect
+ actual=commit_msg_is.actual
+
+ printf "%s" "$(git log --pretty=format:%s%b -1)" >$actual &&
+ printf "%s" "$1" >$expect &&
+ test_i18ncmp $expect $actual
+}
+
+# Arguments: [<prefix] [<commit message>] [<commit options>]
+check_summary_oneline() {
+ test_tick &&
+ git commit ${3+"$3"} -m "$2" | head -1 > act &&
+
+ # branch name
+ SUMMARY_PREFIX="$(git name-rev --name-only HEAD)" &&
+
+ # append the "special" prefix, like "root-commit", "detached HEAD"
+ if test -n "$1"
+ then
+ SUMMARY_PREFIX="$SUMMARY_PREFIX ($1)"
+ fi
+
+ # abbrev SHA-1
+ SUMMARY_POSTFIX="$(git log -1 --pretty='format:%h')"
+ echo "[$SUMMARY_PREFIX $SUMMARY_POSTFIX] $2" >exp &&
+
+ test_i18ncmp exp act
+}
+
+test_expect_success 'output summary format' '
+
+ echo new >file1 &&
+ git add file1 &&
+ check_summary_oneline "root-commit" "initial" &&
+
+ echo change >>file1 &&
+ git add file1
+'
+
+test_expect_success 'output summary format: root-commit' '
+ check_summary_oneline "" "a change"
+'
+
+test_expect_success 'output summary format for commit with an empty diff' '
+
+ check_summary_oneline "" "empty" "--allow-empty"
+'
+
+test_expect_success 'output summary format for merges' '
+
+ git checkout -b recursive-base &&
+ test_commit base file1 &&
+
+ git checkout -b recursive-a recursive-base &&
+ test_commit commit-a file1 &&
+
+ git checkout -b recursive-b recursive-base &&
+ test_commit commit-b file1 &&
+
+ # conflict
+ git checkout recursive-a &&
+ test_must_fail git merge recursive-b &&
+ # resolve the conflict
+ echo commit-a > file1 &&
+ git add file1 &&
+ check_summary_oneline "" "Merge"
+'
+
+output_tests_cleanup() {
+ # this is needed for "do not fire editor in the presence of conflicts"
+ git checkout master &&
+
+ # this is needed for the "partial removal" test to pass
+ git rm file1 &&
+ git commit -m "cleanup"
+}
+
+test_expect_success 'the basics' '
+
+ output_tests_cleanup &&
+
+ echo doing partial >"commit is" &&
+ mkdir not &&
+ echo very much encouraged but we should >not/forbid &&
+ git add "commit is" not &&
+ echo update added "commit is" file >"commit is" &&
+ echo also update another >not/forbid &&
+ test_tick &&
+ git commit -a -m "initial with -a" &&
+
+ git cat-file blob HEAD:"commit is" >current.1 &&
+ git cat-file blob HEAD:not/forbid >current.2 &&
+
+ cmp current.1 "commit is" &&
+ cmp current.2 not/forbid
+
+'
+
+test_expect_success 'partial' '
+
+ echo another >"commit is" &&
+ echo another >not/forbid &&
+ test_tick &&
+ git commit -m "partial commit to handle a file" "commit is" &&
+
+ changed=$(git diff-tree --name-only HEAD^ HEAD) &&
+ test "$changed" = "commit is"
+
+'
+
+test_expect_success 'partial modification in a subdirectory' '
+
+ test_tick &&
+ git commit -m "partial commit to subdirectory" not &&
+
+ changed=$(git diff-tree -r --name-only HEAD^ HEAD) &&
+ test "$changed" = "not/forbid"
+
+'
+
+test_expect_success 'partial removal' '
+
+ git rm not/forbid &&
+ git commit -m "partial commit to remove not/forbid" not &&
+
+ changed=$(git diff-tree -r --name-only HEAD^ HEAD) &&
+ test "$changed" = "not/forbid" &&
+ remain=$(git ls-tree -r --name-only HEAD) &&
+ test "$remain" = "commit is"
+
+'
+
+test_expect_success 'sign off' '
+
+ >positive &&
+ git add positive &&
+ git commit -s -m "thank you" &&
+ actual=$(git cat-file commit HEAD | sed -ne "s/Signed-off-by: //p") &&
+ expected=$(git var GIT_COMMITTER_IDENT | sed -e "s/>.*/>/") &&
+ test "z$actual" = "z$expected"
+
+'
+
+test_expect_success 'multiple -m' '
+
+ >negative &&
+ git add negative &&
+ git commit -m "one" -m "two" -m "three" &&
+ actual=$(git cat-file commit HEAD | sed -e "1,/^\$/d") &&
+ expected=$(echo one; echo; echo two; echo; echo three) &&
+ test "z$actual" = "z$expected"
+
+'
+
+test_expect_success 'verbose' '
+
+ echo minus >negative &&
+ git add negative &&
+ git status -v | sed -ne "/^diff --git /p" >actual &&
+ echo "diff --git a/negative b/negative" >expect &&
+ test_cmp expect actual
+
+'
+
+test_expect_success 'verbose respects diff config' '
+
+ test_config diff.noprefix true &&
+ git status -v >actual &&
+ grep "diff --git negative negative" actual
+'
+
+mesg_with_comment_and_newlines='
+# text
+
+'
+
+test_expect_success 'prepare file with comment line and trailing newlines' '
+ printf "%s" "$mesg_with_comment_and_newlines" >expect
+'
+
+test_expect_success 'cleanup commit messages (verbatim option,-t)' '
+
+ echo >>negative &&
+ git commit --cleanup=verbatim --no-status -t expect -a &&
+ git cat-file -p HEAD |sed -e "1,/^\$/d" >actual &&
+ test_cmp expect actual
+
+'
+
+test_expect_success 'cleanup commit messages (verbatim option,-F)' '
+
+ echo >>negative &&
+ git commit --cleanup=verbatim -F expect -a &&
+ git cat-file -p HEAD |sed -e "1,/^\$/d">actual &&
+ test_cmp expect actual
+
+'
+
+test_expect_success 'cleanup commit messages (verbatim option,-m)' '
+
+ echo >>negative &&
+ git commit --cleanup=verbatim -m "$mesg_with_comment_and_newlines" -a &&
+ git cat-file -p HEAD |sed -e "1,/^\$/d">actual &&
+ test_cmp expect actual
+
+'
+
+test_expect_success 'cleanup commit messages (whitespace option,-F)' '
+
+ echo >>negative &&
+ { echo;echo "# text";echo; } >text &&
+ echo "# text" >expect &&
+ git commit --cleanup=whitespace -F text -a &&
+ git cat-file -p HEAD |sed -e "1,/^\$/d">actual &&
+ test_cmp expect actual
+
+'
+
+test_expect_success 'cleanup commit messages (scissors option,-F,-e)' '
+
+ echo >>negative &&
+ cat >text <<EOF &&
+
+# to be kept
+
+ # ------------------------ >8 ------------------------
+# to be kept, too
+# ------------------------ >8 ------------------------
+to be removed
+# ------------------------ >8 ------------------------
+to be removed, too
+EOF
+
+ cat >expect <<EOF &&
+# to be kept
+
+ # ------------------------ >8 ------------------------
+# to be kept, too
+EOF
+ git commit --cleanup=scissors -e -F text -a &&
+ git cat-file -p HEAD |sed -e "1,/^\$/d">actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'cleanup commit messages (scissors option,-F,-e, scissors on first line)' '
+
+ echo >>negative &&
+ cat >text <<EOF &&
+# ------------------------ >8 ------------------------
+to be removed
+EOF
+ git commit --cleanup=scissors -e -F text -a --allow-empty-message &&
+ git cat-file -p HEAD |sed -e "1,/^\$/d">actual &&
+ test_must_be_empty actual
+'
+
+test_expect_success 'cleanup commit messages (strip option,-F)' '
+
+ echo >>negative &&
+ { echo;echo "# text";echo sample;echo; } >text &&
+ echo sample >expect &&
+ git commit --cleanup=strip -F text -a &&
+ git cat-file -p HEAD |sed -e "1,/^\$/d">actual &&
+ test_cmp expect actual
+
+'
+
+test_expect_success 'cleanup commit messages (strip option,-F,-e)' '
+
+ echo >>negative &&
+ { echo;echo sample;echo; } >text &&
+ git commit -e -F text -a &&
+ head -n 4 .git/COMMIT_EDITMSG >actual
+'
+
+echo "sample
+
+# Please enter the commit message for your changes. Lines starting
+# with '#' will be ignored, and an empty message aborts the commit." >expect
+
+test_expect_success 'cleanup commit messages (strip option,-F,-e): output' '
+ test_i18ncmp expect actual
+'
+
+test_expect_success 'cleanup commit message (fail on invalid cleanup mode option)' '
+ test_must_fail git commit --cleanup=non-existent
+'
+
+test_expect_success 'cleanup commit message (fail on invalid cleanup mode configuration)' '
+ test_must_fail git -c commit.cleanup=non-existent commit
+'
+
+test_expect_success 'cleanup commit message (no config and no option uses default)' '
+ echo content >>file &&
+ git add file &&
+ (
+ test_set_editor "$TEST_DIRECTORY"/t7500/add-content-and-comment &&
+ git commit --no-status
+ ) &&
+ commit_msg_is "commit message"
+'
+
+test_expect_success 'cleanup commit message (option overrides default)' '
+ echo content >>file &&
+ git add file &&
+ (
+ test_set_editor "$TEST_DIRECTORY"/t7500/add-content-and-comment &&
+ git commit --cleanup=whitespace --no-status
+ ) &&
+ commit_msg_is "commit message # comment"
+'
+
+test_expect_success 'cleanup commit message (config overrides default)' '
+ echo content >>file &&
+ git add file &&
+ (
+ test_set_editor "$TEST_DIRECTORY"/t7500/add-content-and-comment &&
+ git -c commit.cleanup=whitespace commit --no-status
+ ) &&
+ commit_msg_is "commit message # comment"
+'
+
+test_expect_success 'cleanup commit message (option overrides config)' '
+ echo content >>file &&
+ git add file &&
+ (
+ test_set_editor "$TEST_DIRECTORY"/t7500/add-content-and-comment &&
+ git -c commit.cleanup=whitespace commit --cleanup=default
+ ) &&
+ commit_msg_is "commit message"
+'
+
+test_expect_success 'cleanup commit message (default, -m)' '
+ echo content >>file &&
+ git add file &&
+ git commit -m "message #comment " &&
+ commit_msg_is "message #comment"
+'
+
+test_expect_success 'cleanup commit message (whitespace option, -m)' '
+ echo content >>file &&
+ git add file &&
+ git commit --cleanup=whitespace --no-status -m "message #comment " &&
+ commit_msg_is "message #comment"
+'
+
+test_expect_success 'cleanup commit message (whitespace config, -m)' '
+ echo content >>file &&
+ git add file &&
+ git -c commit.cleanup=whitespace commit --no-status -m "message #comment " &&
+ commit_msg_is "message #comment"
+'
+
+test_expect_success 'message shows author when it is not equal to committer' '
+ echo >>negative &&
+ git commit -e -m "sample" -a &&
+ test_i18ngrep \
+ "^# Author: *A U Thor <author@example.com>\$" \
+ .git/COMMIT_EDITMSG
+'
+
+test_expect_success 'message shows date when it is explicitly set' '
+ git commit --allow-empty -e -m foo --date="2010-01-02T03:04:05" &&
+ test_i18ngrep \
+ "^# Date: *Sat Jan 2 03:04:05 2010 +0000" \
+ .git/COMMIT_EDITMSG
+'
+
+test_expect_success AUTOIDENT 'message shows committer when it is automatic' '
+
+ echo >>negative &&
+ (
+ sane_unset GIT_COMMITTER_EMAIL &&
+ sane_unset GIT_COMMITTER_NAME &&
+ git commit -e -m "sample" -a
+ ) &&
+ # the ident is calculated from the system, so we cannot
+ # check the actual value, only that it is there
+ test_i18ngrep "^# Committer: " .git/COMMIT_EDITMSG
+'
+
+write_script .git/FAKE_EDITOR <<EOF
+echo editor started > "$(pwd)/.git/result"
+exit 0
+EOF
+
+test_expect_success !AUTOIDENT 'do not fire editor when committer is bogus' '
+ >.git/result &&
+
+ echo >>negative &&
+ (
+ sane_unset GIT_COMMITTER_EMAIL &&
+ sane_unset GIT_COMMITTER_NAME &&
+ GIT_EDITOR="\"$(pwd)/.git/FAKE_EDITOR\"" &&
+ export GIT_EDITOR &&
+ test_must_fail git commit -e -m sample -a
+ ) &&
+ test_must_be_empty .git/result
+'
+
+test_expect_success 'do not fire editor if -m <msg> was given' '
+ echo tick >file &&
+ git add file &&
+ echo "editor not started" >.git/result &&
+ (GIT_EDITOR="\"$(pwd)/.git/FAKE_EDITOR\"" git commit -m tick) &&
+ test "$(cat .git/result)" = "editor not started"
+'
+
+test_expect_success 'do not fire editor if -m "" was given' '
+ echo tock >file &&
+ git add file &&
+ echo "editor not started" >.git/result &&
+ (GIT_EDITOR="\"$(pwd)/.git/FAKE_EDITOR\"" \
+ git commit -m "" --allow-empty-message) &&
+ test "$(cat .git/result)" = "editor not started"
+'
+
+test_expect_success 'do not fire editor in the presence of conflicts' '
+
+ git clean -f &&
+ echo f >g &&
+ git add g &&
+ git commit -m "add g" &&
+ git branch second &&
+ echo master >g &&
+ echo g >h &&
+ git add g h &&
+ git commit -m "modify g and add h" &&
+ git checkout second &&
+ echo second >g &&
+ git add g &&
+ git commit -m second &&
+ # Must fail due to conflict
+ test_must_fail git cherry-pick -n master &&
+ echo "editor not started" >.git/result &&
+ (
+ GIT_EDITOR="\"$(pwd)/.git/FAKE_EDITOR\"" &&
+ export GIT_EDITOR &&
+ test_must_fail git commit
+ ) &&
+ test "$(cat .git/result)" = "editor not started"
+'
+
+write_script .git/FAKE_EDITOR <<EOF
+# kill -TERM command added below.
+EOF
+
+test_expect_success EXECKEEPSPID 'a SIGTERM should break locks' '
+ echo >>negative &&
+ ! "$SHELL_PATH" -c '\''
+ echo kill -TERM $$ >> .git/FAKE_EDITOR
+ GIT_EDITOR=.git/FAKE_EDITOR
+ export GIT_EDITOR
+ exec git commit -a'\'' &&
+ test ! -f .git/index.lock
+'
+
+rm -f .git/MERGE_MSG .git/COMMIT_EDITMSG
+git reset -q --hard
+
+test_expect_success 'Hand committing of a redundant merge removes dups' '
+
+ git rev-parse second master >expect &&
+ test_must_fail git merge second master &&
+ git checkout master g &&
+ EDITOR=: git commit -a &&
+ git cat-file commit HEAD | sed -n -e "s/^parent //p" -e "/^$/q" >actual &&
+ test_cmp expect actual
+
+'
+
+test_expect_success 'A single-liner subject with a token plus colon is not a footer' '
+
+ git reset --hard &&
+ git commit -s -m "hello: kitty" --allow-empty &&
+ git cat-file commit HEAD | sed -e "1,/^$/d" >actual &&
+ test_line_count = 3 actual
+
+'
+
+test_expect_success 'commit -s places sob on third line after two empty lines' '
+ git commit -s --allow-empty --allow-empty-message &&
+ cat <<-EOF >expect &&
+
+
+ Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>
+
+ EOF
+ sed -e "/^#/d" -e "s/^:.*//" .git/COMMIT_EDITMSG >actual &&
+ test_cmp expect actual
+'
+
+write_script .git/FAKE_EDITOR <<\EOF
+mv "$1" "$1.orig"
+(
+ echo message
+ cat "$1.orig"
+) >"$1"
+EOF
+
+echo '## Custom template' >template
+
+try_commit () {
+ git reset --hard &&
+ echo >>negative &&
+ GIT_EDITOR=.git/FAKE_EDITOR git commit -a $* $use_template &&
+ case "$use_template" in
+ '')
+ test_i18ngrep ! "^## Custom template" .git/COMMIT_EDITMSG ;;
+ *)
+ test_i18ngrep "^## Custom template" .git/COMMIT_EDITMSG ;;
+ esac
+}
+
+try_commit_status_combo () {
+
+ test_expect_success 'commit' '
+ try_commit "" &&
+ test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG
+ '
+
+ test_expect_success 'commit --status' '
+ try_commit --status &&
+ test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG
+ '
+
+ test_expect_success 'commit --no-status' '
+ try_commit --no-status &&
+ test_i18ngrep ! "^# Changes to be committed:" .git/COMMIT_EDITMSG
+ '
+
+ test_expect_success 'commit with commit.status = yes' '
+ test_config commit.status yes &&
+ try_commit "" &&
+ test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG
+ '
+
+ test_expect_success 'commit with commit.status = no' '
+ test_config commit.status no &&
+ try_commit "" &&
+ test_i18ngrep ! "^# Changes to be committed:" .git/COMMIT_EDITMSG
+ '
+
+ test_expect_success 'commit --status with commit.status = yes' '
+ test_config commit.status yes &&
+ try_commit --status &&
+ test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG
+ '
+
+ test_expect_success 'commit --no-status with commit.status = yes' '
+ test_config commit.status yes &&
+ try_commit --no-status &&
+ test_i18ngrep ! "^# Changes to be committed:" .git/COMMIT_EDITMSG
+ '
+
+ test_expect_success 'commit --status with commit.status = no' '
+ test_config commit.status no &&
+ try_commit --status &&
+ test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG
+ '
+
+ test_expect_success 'commit --no-status with commit.status = no' '
+ test_config commit.status no &&
+ try_commit --no-status &&
+ test_i18ngrep ! "^# Changes to be committed:" .git/COMMIT_EDITMSG
+ '
+
+}
+
+try_commit_status_combo
+
+use_template="-t template"
+
+try_commit_status_combo
+
+test_expect_success 'commit --status with custom comment character' '
+ test_config core.commentchar ";" &&
+ try_commit --status &&
+ test_i18ngrep "^; Changes to be committed:" .git/COMMIT_EDITMSG
+'
+
+test_expect_success 'switch core.commentchar' '
+ test_commit "#foo" foo &&
+ GIT_EDITOR=.git/FAKE_EDITOR git -c core.commentChar=auto commit --amend &&
+ test_i18ngrep "^; Changes to be committed:" .git/COMMIT_EDITMSG
+'
+
+test_expect_success 'switch core.commentchar but out of options' '
+ cat >text <<\EOF &&
+# 1
+; 2
+@ 3
+! 4
+$ 5
+% 6
+^ 7
+& 8
+| 9
+: 10
+EOF
+ git commit --amend -F text &&
+ (
+ test_set_editor .git/FAKE_EDITOR &&
+ test_must_fail git -c core.commentChar=auto commit --amend
+ )
+'
+
+test_done
+++ /dev/null
-#!/bin/sh
-
-test_description='git commit porcelain-ish'
-
-. ./test-lib.sh
-
-commit_msg_is () {
- expect=commit_msg_is.expect
- actual=commit_msg_is.actual
-
- printf "%s" "$(git log --pretty=format:%s%b -1)" >$actual &&
- printf "%s" "$1" >$expect &&
- test_i18ncmp $expect $actual
-}
-
-# Arguments: [<prefix] [<commit message>] [<commit options>]
-check_summary_oneline() {
- test_tick &&
- git commit ${3+"$3"} -m "$2" | head -1 > act &&
-
- # branch name
- SUMMARY_PREFIX="$(git name-rev --name-only HEAD)" &&
-
- # append the "special" prefix, like "root-commit", "detached HEAD"
- if test -n "$1"
- then
- SUMMARY_PREFIX="$SUMMARY_PREFIX ($1)"
- fi
-
- # abbrev SHA-1
- SUMMARY_POSTFIX="$(git log -1 --pretty='format:%h')"
- echo "[$SUMMARY_PREFIX $SUMMARY_POSTFIX] $2" >exp &&
-
- test_i18ncmp exp act
-}
-
-test_expect_success 'output summary format' '
-
- echo new >file1 &&
- git add file1 &&
- check_summary_oneline "root-commit" "initial" &&
-
- echo change >>file1 &&
- git add file1
-'
-
-test_expect_success 'output summary format: root-commit' '
- check_summary_oneline "" "a change"
-'
-
-test_expect_success 'output summary format for commit with an empty diff' '
-
- check_summary_oneline "" "empty" "--allow-empty"
-'
-
-test_expect_success 'output summary format for merges' '
-
- git checkout -b recursive-base &&
- test_commit base file1 &&
-
- git checkout -b recursive-a recursive-base &&
- test_commit commit-a file1 &&
-
- git checkout -b recursive-b recursive-base &&
- test_commit commit-b file1 &&
-
- # conflict
- git checkout recursive-a &&
- test_must_fail git merge recursive-b &&
- # resolve the conflict
- echo commit-a > file1 &&
- git add file1 &&
- check_summary_oneline "" "Merge"
-'
-
-output_tests_cleanup() {
- # this is needed for "do not fire editor in the presence of conflicts"
- git checkout master &&
-
- # this is needed for the "partial removal" test to pass
- git rm file1 &&
- git commit -m "cleanup"
-}
-
-test_expect_success 'the basics' '
-
- output_tests_cleanup &&
-
- echo doing partial >"commit is" &&
- mkdir not &&
- echo very much encouraged but we should >not/forbid &&
- git add "commit is" not &&
- echo update added "commit is" file >"commit is" &&
- echo also update another >not/forbid &&
- test_tick &&
- git commit -a -m "initial with -a" &&
-
- git cat-file blob HEAD:"commit is" >current.1 &&
- git cat-file blob HEAD:not/forbid >current.2 &&
-
- cmp current.1 "commit is" &&
- cmp current.2 not/forbid
-
-'
-
-test_expect_success 'partial' '
-
- echo another >"commit is" &&
- echo another >not/forbid &&
- test_tick &&
- git commit -m "partial commit to handle a file" "commit is" &&
-
- changed=$(git diff-tree --name-only HEAD^ HEAD) &&
- test "$changed" = "commit is"
-
-'
-
-test_expect_success 'partial modification in a subdirectory' '
-
- test_tick &&
- git commit -m "partial commit to subdirectory" not &&
-
- changed=$(git diff-tree -r --name-only HEAD^ HEAD) &&
- test "$changed" = "not/forbid"
-
-'
-
-test_expect_success 'partial removal' '
-
- git rm not/forbid &&
- git commit -m "partial commit to remove not/forbid" not &&
-
- changed=$(git diff-tree -r --name-only HEAD^ HEAD) &&
- test "$changed" = "not/forbid" &&
- remain=$(git ls-tree -r --name-only HEAD) &&
- test "$remain" = "commit is"
-
-'
-
-test_expect_success 'sign off' '
-
- >positive &&
- git add positive &&
- git commit -s -m "thank you" &&
- actual=$(git cat-file commit HEAD | sed -ne "s/Signed-off-by: //p") &&
- expected=$(git var GIT_COMMITTER_IDENT | sed -e "s/>.*/>/") &&
- test "z$actual" = "z$expected"
-
-'
-
-test_expect_success 'multiple -m' '
-
- >negative &&
- git add negative &&
- git commit -m "one" -m "two" -m "three" &&
- actual=$(git cat-file commit HEAD | sed -e "1,/^\$/d") &&
- expected=$(echo one; echo; echo two; echo; echo three) &&
- test "z$actual" = "z$expected"
-
-'
-
-test_expect_success 'verbose' '
-
- echo minus >negative &&
- git add negative &&
- git status -v | sed -ne "/^diff --git /p" >actual &&
- echo "diff --git a/negative b/negative" >expect &&
- test_cmp expect actual
-
-'
-
-test_expect_success 'verbose respects diff config' '
-
- test_config diff.noprefix true &&
- git status -v >actual &&
- grep "diff --git negative negative" actual
-'
-
-mesg_with_comment_and_newlines='
-# text
-
-'
-
-test_expect_success 'prepare file with comment line and trailing newlines' '
- printf "%s" "$mesg_with_comment_and_newlines" >expect
-'
-
-test_expect_success 'cleanup commit messages (verbatim option,-t)' '
-
- echo >>negative &&
- git commit --cleanup=verbatim --no-status -t expect -a &&
- git cat-file -p HEAD |sed -e "1,/^\$/d" >actual &&
- test_cmp expect actual
-
-'
-
-test_expect_success 'cleanup commit messages (verbatim option,-F)' '
-
- echo >>negative &&
- git commit --cleanup=verbatim -F expect -a &&
- git cat-file -p HEAD |sed -e "1,/^\$/d">actual &&
- test_cmp expect actual
-
-'
-
-test_expect_success 'cleanup commit messages (verbatim option,-m)' '
-
- echo >>negative &&
- git commit --cleanup=verbatim -m "$mesg_with_comment_and_newlines" -a &&
- git cat-file -p HEAD |sed -e "1,/^\$/d">actual &&
- test_cmp expect actual
-
-'
-
-test_expect_success 'cleanup commit messages (whitespace option,-F)' '
-
- echo >>negative &&
- { echo;echo "# text";echo; } >text &&
- echo "# text" >expect &&
- git commit --cleanup=whitespace -F text -a &&
- git cat-file -p HEAD |sed -e "1,/^\$/d">actual &&
- test_cmp expect actual
-
-'
-
-test_expect_success 'cleanup commit messages (scissors option,-F,-e)' '
-
- echo >>negative &&
- cat >text <<EOF &&
-
-# to be kept
-
- # ------------------------ >8 ------------------------
-# to be kept, too
-# ------------------------ >8 ------------------------
-to be removed
-# ------------------------ >8 ------------------------
-to be removed, too
-EOF
-
- cat >expect <<EOF &&
-# to be kept
-
- # ------------------------ >8 ------------------------
-# to be kept, too
-EOF
- git commit --cleanup=scissors -e -F text -a &&
- git cat-file -p HEAD |sed -e "1,/^\$/d">actual &&
- test_cmp expect actual
-'
-
-test_expect_success 'cleanup commit messages (scissors option,-F,-e, scissors on first line)' '
-
- echo >>negative &&
- cat >text <<EOF &&
-# ------------------------ >8 ------------------------
-to be removed
-EOF
- git commit --cleanup=scissors -e -F text -a --allow-empty-message &&
- git cat-file -p HEAD |sed -e "1,/^\$/d">actual &&
- test_must_be_empty actual
-'
-
-test_expect_success 'cleanup commit messages (strip option,-F)' '
-
- echo >>negative &&
- { echo;echo "# text";echo sample;echo; } >text &&
- echo sample >expect &&
- git commit --cleanup=strip -F text -a &&
- git cat-file -p HEAD |sed -e "1,/^\$/d">actual &&
- test_cmp expect actual
-
-'
-
-test_expect_success 'cleanup commit messages (strip option,-F,-e)' '
-
- echo >>negative &&
- { echo;echo sample;echo; } >text &&
- git commit -e -F text -a &&
- head -n 4 .git/COMMIT_EDITMSG >actual
-'
-
-echo "sample
-
-# Please enter the commit message for your changes. Lines starting
-# with '#' will be ignored, and an empty message aborts the commit." >expect
-
-test_expect_success 'cleanup commit messages (strip option,-F,-e): output' '
- test_i18ncmp expect actual
-'
-
-test_expect_success 'cleanup commit message (fail on invalid cleanup mode option)' '
- test_must_fail git commit --cleanup=non-existent
-'
-
-test_expect_success 'cleanup commit message (fail on invalid cleanup mode configuration)' '
- test_must_fail git -c commit.cleanup=non-existent commit
-'
-
-test_expect_success 'cleanup commit message (no config and no option uses default)' '
- echo content >>file &&
- git add file &&
- (
- test_set_editor "$TEST_DIRECTORY"/t7500/add-content-and-comment &&
- git commit --no-status
- ) &&
- commit_msg_is "commit message"
-'
-
-test_expect_success 'cleanup commit message (option overrides default)' '
- echo content >>file &&
- git add file &&
- (
- test_set_editor "$TEST_DIRECTORY"/t7500/add-content-and-comment &&
- git commit --cleanup=whitespace --no-status
- ) &&
- commit_msg_is "commit message # comment"
-'
-
-test_expect_success 'cleanup commit message (config overrides default)' '
- echo content >>file &&
- git add file &&
- (
- test_set_editor "$TEST_DIRECTORY"/t7500/add-content-and-comment &&
- git -c commit.cleanup=whitespace commit --no-status
- ) &&
- commit_msg_is "commit message # comment"
-'
-
-test_expect_success 'cleanup commit message (option overrides config)' '
- echo content >>file &&
- git add file &&
- (
- test_set_editor "$TEST_DIRECTORY"/t7500/add-content-and-comment &&
- git -c commit.cleanup=whitespace commit --cleanup=default
- ) &&
- commit_msg_is "commit message"
-'
-
-test_expect_success 'cleanup commit message (default, -m)' '
- echo content >>file &&
- git add file &&
- git commit -m "message #comment " &&
- commit_msg_is "message #comment"
-'
-
-test_expect_success 'cleanup commit message (whitespace option, -m)' '
- echo content >>file &&
- git add file &&
- git commit --cleanup=whitespace --no-status -m "message #comment " &&
- commit_msg_is "message #comment"
-'
-
-test_expect_success 'cleanup commit message (whitespace config, -m)' '
- echo content >>file &&
- git add file &&
- git -c commit.cleanup=whitespace commit --no-status -m "message #comment " &&
- commit_msg_is "message #comment"
-'
-
-test_expect_success 'message shows author when it is not equal to committer' '
- echo >>negative &&
- git commit -e -m "sample" -a &&
- test_i18ngrep \
- "^# Author: *A U Thor <author@example.com>\$" \
- .git/COMMIT_EDITMSG
-'
-
-test_expect_success 'message shows date when it is explicitly set' '
- git commit --allow-empty -e -m foo --date="2010-01-02T03:04:05" &&
- test_i18ngrep \
- "^# Date: *Sat Jan 2 03:04:05 2010 +0000" \
- .git/COMMIT_EDITMSG
-'
-
-test_expect_success AUTOIDENT 'message shows committer when it is automatic' '
-
- echo >>negative &&
- (
- sane_unset GIT_COMMITTER_EMAIL &&
- sane_unset GIT_COMMITTER_NAME &&
- git commit -e -m "sample" -a
- ) &&
- # the ident is calculated from the system, so we cannot
- # check the actual value, only that it is there
- test_i18ngrep "^# Committer: " .git/COMMIT_EDITMSG
-'
-
-write_script .git/FAKE_EDITOR <<EOF
-echo editor started > "$(pwd)/.git/result"
-exit 0
-EOF
-
-test_expect_success !AUTOIDENT 'do not fire editor when committer is bogus' '
- >.git/result &&
-
- echo >>negative &&
- (
- sane_unset GIT_COMMITTER_EMAIL &&
- sane_unset GIT_COMMITTER_NAME &&
- GIT_EDITOR="\"$(pwd)/.git/FAKE_EDITOR\"" &&
- export GIT_EDITOR &&
- test_must_fail git commit -e -m sample -a
- ) &&
- test_must_be_empty .git/result
-'
-
-test_expect_success 'do not fire editor if -m <msg> was given' '
- echo tick >file &&
- git add file &&
- echo "editor not started" >.git/result &&
- (GIT_EDITOR="\"$(pwd)/.git/FAKE_EDITOR\"" git commit -m tick) &&
- test "$(cat .git/result)" = "editor not started"
-'
-
-test_expect_success 'do not fire editor if -m "" was given' '
- echo tock >file &&
- git add file &&
- echo "editor not started" >.git/result &&
- (GIT_EDITOR="\"$(pwd)/.git/FAKE_EDITOR\"" \
- git commit -m "" --allow-empty-message) &&
- test "$(cat .git/result)" = "editor not started"
-'
-
-test_expect_success 'do not fire editor in the presence of conflicts' '
-
- git clean -f &&
- echo f >g &&
- git add g &&
- git commit -m "add g" &&
- git branch second &&
- echo master >g &&
- echo g >h &&
- git add g h &&
- git commit -m "modify g and add h" &&
- git checkout second &&
- echo second >g &&
- git add g &&
- git commit -m second &&
- # Must fail due to conflict
- test_must_fail git cherry-pick -n master &&
- echo "editor not started" >.git/result &&
- (
- GIT_EDITOR="\"$(pwd)/.git/FAKE_EDITOR\"" &&
- export GIT_EDITOR &&
- test_must_fail git commit
- ) &&
- test "$(cat .git/result)" = "editor not started"
-'
-
-write_script .git/FAKE_EDITOR <<EOF
-# kill -TERM command added below.
-EOF
-
-test_expect_success EXECKEEPSPID 'a SIGTERM should break locks' '
- echo >>negative &&
- ! "$SHELL_PATH" -c '\''
- echo kill -TERM $$ >> .git/FAKE_EDITOR
- GIT_EDITOR=.git/FAKE_EDITOR
- export GIT_EDITOR
- exec git commit -a'\'' &&
- test ! -f .git/index.lock
-'
-
-rm -f .git/MERGE_MSG .git/COMMIT_EDITMSG
-git reset -q --hard
-
-test_expect_success 'Hand committing of a redundant merge removes dups' '
-
- git rev-parse second master >expect &&
- test_must_fail git merge second master &&
- git checkout master g &&
- EDITOR=: git commit -a &&
- git cat-file commit HEAD | sed -n -e "s/^parent //p" -e "/^$/q" >actual &&
- test_cmp expect actual
-
-'
-
-test_expect_success 'A single-liner subject with a token plus colon is not a footer' '
-
- git reset --hard &&
- git commit -s -m "hello: kitty" --allow-empty &&
- git cat-file commit HEAD | sed -e "1,/^$/d" >actual &&
- test_line_count = 3 actual
-
-'
-
-test_expect_success 'commit -s places sob on third line after two empty lines' '
- git commit -s --allow-empty --allow-empty-message &&
- cat <<-EOF >expect &&
-
-
- Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>
-
- EOF
- sed -e "/^#/d" -e "s/^:.*//" .git/COMMIT_EDITMSG >actual &&
- test_cmp expect actual
-'
-
-write_script .git/FAKE_EDITOR <<\EOF
-mv "$1" "$1.orig"
-(
- echo message
- cat "$1.orig"
-) >"$1"
-EOF
-
-echo '## Custom template' >template
-
-try_commit () {
- git reset --hard &&
- echo >>negative &&
- GIT_EDITOR=.git/FAKE_EDITOR git commit -a $* $use_template &&
- case "$use_template" in
- '')
- test_i18ngrep ! "^## Custom template" .git/COMMIT_EDITMSG ;;
- *)
- test_i18ngrep "^## Custom template" .git/COMMIT_EDITMSG ;;
- esac
-}
-
-try_commit_status_combo () {
-
- test_expect_success 'commit' '
- try_commit "" &&
- test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG
- '
-
- test_expect_success 'commit --status' '
- try_commit --status &&
- test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG
- '
-
- test_expect_success 'commit --no-status' '
- try_commit --no-status &&
- test_i18ngrep ! "^# Changes to be committed:" .git/COMMIT_EDITMSG
- '
-
- test_expect_success 'commit with commit.status = yes' '
- test_config commit.status yes &&
- try_commit "" &&
- test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG
- '
-
- test_expect_success 'commit with commit.status = no' '
- test_config commit.status no &&
- try_commit "" &&
- test_i18ngrep ! "^# Changes to be committed:" .git/COMMIT_EDITMSG
- '
-
- test_expect_success 'commit --status with commit.status = yes' '
- test_config commit.status yes &&
- try_commit --status &&
- test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG
- '
-
- test_expect_success 'commit --no-status with commit.status = yes' '
- test_config commit.status yes &&
- try_commit --no-status &&
- test_i18ngrep ! "^# Changes to be committed:" .git/COMMIT_EDITMSG
- '
-
- test_expect_success 'commit --status with commit.status = no' '
- test_config commit.status no &&
- try_commit --status &&
- test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG
- '
-
- test_expect_success 'commit --no-status with commit.status = no' '
- test_config commit.status no &&
- try_commit --no-status &&
- test_i18ngrep ! "^# Changes to be committed:" .git/COMMIT_EDITMSG
- '
-
-}
-
-try_commit_status_combo
-
-use_template="-t template"
-
-try_commit_status_combo
-
-test_expect_success 'commit --status with custom comment character' '
- test_config core.commentchar ";" &&
- try_commit --status &&
- test_i18ngrep "^; Changes to be committed:" .git/COMMIT_EDITMSG
-'
-
-test_expect_success 'switch core.commentchar' '
- test_commit "#foo" foo &&
- GIT_EDITOR=.git/FAKE_EDITOR git -c core.commentChar=auto commit --amend &&
- test_i18ngrep "^; Changes to be committed:" .git/COMMIT_EDITMSG
-'
-
-test_expect_success 'switch core.commentchar but out of options' '
- cat >text <<\EOF &&
-# 1
-; 2
-@ 3
-! 4
-$ 5
-% 6
-^ 7
-& 8
-| 9
-: 10
-EOF
- git commit --amend -F text &&
- (
- test_set_editor .git/FAKE_EDITOR &&
- test_must_fail git -c core.commentChar=auto commit --amend
- )
-'
-
-test_done
--- /dev/null
+#!/bin/sh
+#
+# Copyright (c) 2009 Erick Mattos
+#
+
+test_description='commit tests of various authorhip options. '
+
+. ./test-lib.sh
+
+author_header () {
+ git cat-file commit "$1" |
+ sed -n -e '/^$/q' -e '/^author /p'
+}
+
+message_body () {
+ git cat-file commit "$1" |
+ sed -e '1,/^$/d'
+}
+
+test_expect_success '-C option copies authorship and message' '
+ echo "Initial" >foo &&
+ git add foo &&
+ test_tick &&
+ git commit -m "Initial Commit" --author Frigate\ \<flying@over.world\> &&
+ git tag Initial &&
+ echo "Test 1" >>foo &&
+ test_tick &&
+ git commit -a -C Initial &&
+ author_header Initial >expect &&
+ author_header HEAD >actual &&
+ test_cmp expect actual &&
+
+ message_body Initial >expect &&
+ message_body HEAD >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success '-C option copies only the message with --reset-author' '
+ echo "Test 2" >>foo &&
+ test_tick &&
+ git commit -a -C Initial --reset-author &&
+ echo "author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE" >expect &&
+ author_header HEAD >actual &&
+ test_cmp expect actual &&
+
+ message_body Initial >expect &&
+ message_body HEAD >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success '-c option copies authorship and message' '
+ echo "Test 3" >>foo &&
+ test_tick &&
+ EDITOR=: VISUAL=: git commit -a -c Initial &&
+ author_header Initial >expect &&
+ author_header HEAD >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success '-c option copies only the message with --reset-author' '
+ echo "Test 4" >>foo &&
+ test_tick &&
+ EDITOR=: VISUAL=: git commit -a -c Initial --reset-author &&
+ echo "author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE" >expect &&
+ author_header HEAD >actual &&
+ test_cmp expect actual &&
+
+ message_body Initial >expect &&
+ message_body HEAD >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success '--amend option copies authorship' '
+ git checkout Initial &&
+ echo "Test 5" >>foo &&
+ test_tick &&
+ git commit -a --amend -m "amend test" &&
+ author_header Initial >expect &&
+ author_header HEAD >actual &&
+ test_cmp expect actual &&
+
+ echo "amend test" >expect &&
+ message_body HEAD >actual &&
+ test_cmp expect actual
+'
+
+sha1_file() {
+ echo "$*" | sed "s#..#.git/objects/&/#"
+}
+remove_object() {
+ rm -f $(sha1_file "$*")
+}
+
+test_expect_success '--amend option with empty author' '
+ git cat-file commit Initial >tmp &&
+ sed "s/author [^<]* </author </" tmp >empty-author &&
+ sha=$(git hash-object -t commit -w empty-author) &&
+ test_when_finished "remove_object $sha" &&
+ git checkout $sha &&
+ test_when_finished "git checkout Initial" &&
+ echo "Empty author test" >>foo &&
+ test_tick &&
+ test_must_fail git commit -a -m "empty author" --amend 2>err &&
+ test_i18ngrep "empty ident" err
+'
+
+test_expect_success '--amend option with missing author' '
+ git cat-file commit Initial >tmp &&
+ sed "s/author [^<]* </author </" tmp >malformed &&
+ sha=$(git hash-object -t commit -w malformed) &&
+ test_when_finished "remove_object $sha" &&
+ git checkout $sha &&
+ test_when_finished "git checkout Initial" &&
+ echo "Missing author test" >>foo &&
+ test_tick &&
+ test_must_fail git commit -a -m "malformed author" --amend 2>err &&
+ test_i18ngrep "empty ident" err
+'
+
+test_expect_success '--reset-author makes the commit ours even with --amend option' '
+ git checkout Initial &&
+ echo "Test 6" >>foo &&
+ test_tick &&
+ git commit -a --reset-author -m "Changed again" --amend &&
+ echo "author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE" >expect &&
+ author_header HEAD >actual &&
+ test_cmp expect actual &&
+
+ echo "Changed again" >expect &&
+ message_body HEAD >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success '--reset-author and --author are mutually exclusive' '
+ git checkout Initial &&
+ echo "Test 7" >>foo &&
+ test_tick &&
+ test_must_fail git commit -a --reset-author --author="Xyzzy <frotz@nitfol.xz>"
+'
+
+test_expect_success '--reset-author should be rejected without -c/-C/--amend' '
+ git checkout Initial &&
+ echo "Test 7" >>foo &&
+ test_tick &&
+ test_must_fail git commit -a --reset-author -m done
+'
+
+test_expect_success 'commit respects CHERRY_PICK_HEAD and MERGE_MSG' '
+ echo "cherry-pick 1a" >>foo &&
+ test_tick &&
+ git commit -am "cherry-pick 1" --author="Cherry <cherry@pick.er>" &&
+ git tag cherry-pick-head &&
+ git rev-parse cherry-pick-head >.git/CHERRY_PICK_HEAD &&
+ echo "This is a MERGE_MSG" >.git/MERGE_MSG &&
+ echo "cherry-pick 1b" >>foo &&
+ test_tick &&
+ git commit -a &&
+ author_header cherry-pick-head >expect &&
+ author_header HEAD >actual &&
+ test_cmp expect actual &&
+
+ echo "This is a MERGE_MSG" >expect &&
+ message_body HEAD >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success '--reset-author with CHERRY_PICK_HEAD' '
+ git rev-parse cherry-pick-head >.git/CHERRY_PICK_HEAD &&
+ echo "cherry-pick 2" >>foo &&
+ test_tick &&
+ git commit -am "cherry-pick 2" --reset-author &&
+ echo "author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE" >expect &&
+ author_header HEAD >actual &&
+ test_cmp expect actual
+'
+
+test_done
+++ /dev/null
-#!/bin/sh
-#
-# Copyright (c) 2009 Erick Mattos
-#
-
-test_description='git commit --reset-author'
-
-. ./test-lib.sh
-
-author_header () {
- git cat-file commit "$1" |
- sed -n -e '/^$/q' -e '/^author /p'
-}
-
-message_body () {
- git cat-file commit "$1" |
- sed -e '1,/^$/d'
-}
-
-test_expect_success '-C option copies authorship and message' '
- echo "Initial" >foo &&
- git add foo &&
- test_tick &&
- git commit -m "Initial Commit" --author Frigate\ \<flying@over.world\> &&
- git tag Initial &&
- echo "Test 1" >>foo &&
- test_tick &&
- git commit -a -C Initial &&
- author_header Initial >expect &&
- author_header HEAD >actual &&
- test_cmp expect actual &&
-
- message_body Initial >expect &&
- message_body HEAD >actual &&
- test_cmp expect actual
-'
-
-test_expect_success '-C option copies only the message with --reset-author' '
- echo "Test 2" >>foo &&
- test_tick &&
- git commit -a -C Initial --reset-author &&
- echo "author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE" >expect &&
- author_header HEAD >actual &&
- test_cmp expect actual &&
-
- message_body Initial >expect &&
- message_body HEAD >actual &&
- test_cmp expect actual
-'
-
-test_expect_success '-c option copies authorship and message' '
- echo "Test 3" >>foo &&
- test_tick &&
- EDITOR=: VISUAL=: git commit -a -c Initial &&
- author_header Initial >expect &&
- author_header HEAD >actual &&
- test_cmp expect actual
-'
-
-test_expect_success '-c option copies only the message with --reset-author' '
- echo "Test 4" >>foo &&
- test_tick &&
- EDITOR=: VISUAL=: git commit -a -c Initial --reset-author &&
- echo "author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE" >expect &&
- author_header HEAD >actual &&
- test_cmp expect actual &&
-
- message_body Initial >expect &&
- message_body HEAD >actual &&
- test_cmp expect actual
-'
-
-test_expect_success '--amend option copies authorship' '
- git checkout Initial &&
- echo "Test 5" >>foo &&
- test_tick &&
- git commit -a --amend -m "amend test" &&
- author_header Initial >expect &&
- author_header HEAD >actual &&
- test_cmp expect actual &&
-
- echo "amend test" >expect &&
- message_body HEAD >actual &&
- test_cmp expect actual
-'
-
-sha1_file() {
- echo "$*" | sed "s#..#.git/objects/&/#"
-}
-remove_object() {
- rm -f $(sha1_file "$*")
-}
-
-test_expect_success '--amend option with empty author' '
- git cat-file commit Initial >tmp &&
- sed "s/author [^<]* </author </" tmp >empty-author &&
- sha=$(git hash-object -t commit -w empty-author) &&
- test_when_finished "remove_object $sha" &&
- git checkout $sha &&
- test_when_finished "git checkout Initial" &&
- echo "Empty author test" >>foo &&
- test_tick &&
- test_must_fail git commit -a -m "empty author" --amend 2>err &&
- test_i18ngrep "empty ident" err
-'
-
-test_expect_success '--amend option with missing author' '
- git cat-file commit Initial >tmp &&
- sed "s/author [^<]* </author </" tmp >malformed &&
- sha=$(git hash-object -t commit -w malformed) &&
- test_when_finished "remove_object $sha" &&
- git checkout $sha &&
- test_when_finished "git checkout Initial" &&
- echo "Missing author test" >>foo &&
- test_tick &&
- test_must_fail git commit -a -m "malformed author" --amend 2>err &&
- test_i18ngrep "empty ident" err
-'
-
-test_expect_success '--reset-author makes the commit ours even with --amend option' '
- git checkout Initial &&
- echo "Test 6" >>foo &&
- test_tick &&
- git commit -a --reset-author -m "Changed again" --amend &&
- echo "author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE" >expect &&
- author_header HEAD >actual &&
- test_cmp expect actual &&
-
- echo "Changed again" >expect &&
- message_body HEAD >actual &&
- test_cmp expect actual
-'
-
-test_expect_success '--reset-author and --author are mutually exclusive' '
- git checkout Initial &&
- echo "Test 7" >>foo &&
- test_tick &&
- test_must_fail git commit -a --reset-author --author="Xyzzy <frotz@nitfol.xz>"
-'
-
-test_expect_success '--reset-author should be rejected without -c/-C/--amend' '
- git checkout Initial &&
- echo "Test 7" >>foo &&
- test_tick &&
- test_must_fail git commit -a --reset-author -m done
-'
-
-test_expect_success 'commit respects CHERRY_PICK_HEAD and MERGE_MSG' '
- echo "cherry-pick 1a" >>foo &&
- test_tick &&
- git commit -am "cherry-pick 1" --author="Cherry <cherry@pick.er>" &&
- git tag cherry-pick-head &&
- git rev-parse cherry-pick-head >.git/CHERRY_PICK_HEAD &&
- echo "This is a MERGE_MSG" >.git/MERGE_MSG &&
- echo "cherry-pick 1b" >>foo &&
- test_tick &&
- git commit -a &&
- author_header cherry-pick-head >expect &&
- author_header HEAD >actual &&
- test_cmp expect actual &&
-
- echo "This is a MERGE_MSG" >expect &&
- message_body HEAD >actual &&
- test_cmp expect actual
-'
-
-test_expect_success '--reset-author with CHERRY_PICK_HEAD' '
- git rev-parse cherry-pick-head >.git/CHERRY_PICK_HEAD &&
- echo "cherry-pick 2" >>foo &&
- test_tick &&
- git commit -am "cherry-pick 2" --reset-author &&
- echo "author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE" >expect &&
- author_header HEAD >actual &&
- test_cmp expect actual
-'
-
-test_done
test_cmp expected actual
'
+test_expect_success 'handling of --- lines in input' '
+ echo "real-trailer: just right" >expected &&
+
+ git interpret-trailers --parse >actual <<-\EOF &&
+ subject
+
+ body
+
+ not-a-trailer: too soon
+ ------ this is just a line in the commit message with a bunch of
+ ------ dashes; it does not have any syntactic meaning.
+
+ real-trailer: just right
+ ---
+ below the dashed line may be a patch, etc.
+
+ not-a-trailer: too late
+ EOF
+
+ test_cmp expected actual
+'
+
+test_expect_success 'suppress --- handling' '
+ echo "real-trailer: just right" >expected &&
+
+ git interpret-trailers --parse --no-divider >actual <<-\EOF &&
+ subject
+
+ This commit message has a "---" in it, but because we tell
+ interpret-trailers not to respect that, it has no effect.
+
+ not-a-trailer: too soon
+ ---
+
+ This is still the commit message body.
+
+ real-trailer: just right
+ EOF
+
+ test_cmp expected actual
+'
+
test_done
. ./test-lib.sh
-#
-# To run the entire git test suite using fsmonitor:
-#
-# copy t/t7519/fsmonitor-all to a location in your path and then set
-# GIT_FSMONITOR_TEST=fsmonitor-all and run your tests.
-#
-
# Note, after "git reset --hard HEAD" no extensions exist other than 'TREE'
# "git update-index --fsmonitor" can be used to get the extension written
# before testing the results.
# test that the fsmonitor extension is off by default
test_expect_success 'fsmonitor extension is off by default' '
- test-dump-fsmonitor >actual &&
+ test-tool dump-fsmonitor >actual &&
grep "^no fsmonitor" actual
'
# test that "update-index --fsmonitor" adds the fsmonitor extension
test_expect_success 'update-index --fsmonitor" adds the fsmonitor extension' '
git update-index --fsmonitor &&
- test-dump-fsmonitor >actual &&
+ test-tool dump-fsmonitor >actual &&
grep "^fsmonitor last update" actual
'
# test that "update-index --no-fsmonitor" removes the fsmonitor extension
test_expect_success 'update-index --no-fsmonitor" removes the fsmonitor extension' '
git update-index --no-fsmonitor &&
- test-dump-fsmonitor >actual &&
+ test-tool dump-fsmonitor >actual &&
grep "^no fsmonitor" actual
'
git config core.preloadIndex $preload_val &&
if test $preload_val = true
then
- GIT_FORCE_PRELOAD_TEST=$preload_val; export GIT_FORCE_PRELOAD_TEST
+ GIT_TEST_PRELOAD_INDEX=$preload_val; export GIT_TEST_PRELOAD_INDEX
else
- unset GIT_FORCE_PRELOAD_TEST
+ sane_unset GIT_TEST_PRELOAD_INDEX
fi
'
dirty_repo &&
git update-index --fsmonitor &&
git ls-files -f >expect &&
- test-dump-fsmonitor >&2 && echo &&
+ test-tool dump-fsmonitor >&2 && echo &&
git update-index --fsmonitor --split-index &&
- test-dump-fsmonitor >&2 && echo &&
+ test-tool dump-fsmonitor >&2 && echo &&
git ls-files -f >actual &&
test_cmp expect actual
'
git update-index --fsmonitor &&
GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace-before" \
git status &&
- test-dump-untracked-cache >../before
+ test-tool dump-untracked-cache >../before
) &&
cat >>dot-git/.git/hooks/fsmonitor-test <<-\EOF &&
printf ".git\0"
cd dot-git &&
GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace-after" \
git status &&
- test-dump-untracked-cache >../after
+ test-tool dump-untracked-cache >../after
) &&
grep "directory invalidation" trace-before >>before &&
grep "directory invalidation" trace-after >>after &&
EOF
git difftool --dir-diff --symlink \
--extcmd "./.git/CHECK_SYMLINKS" branch HEAD &&
- test_cmp actual expect
+ test_cmp expect actual
'
write_script modify-right-file <<\EOF
echo ${HC}v:1:vvv
} >expected &&
git grep --max-depth -1 -n -e vvv $H >actual &&
+ test_cmp expected actual &&
+ git grep --recursive -n -e vvv $H >actual &&
test_cmp expected actual
'
echo ${HC}v:1:vvv
} >expected &&
git grep --max-depth 0 -n -e vvv $H >actual &&
+ test_cmp expected actual &&
+ git grep --no-recursive -n -e vvv $H >actual &&
test_cmp expected actual
'
echo ${HC}v:1:vvv
} >expected &&
git grep --max-depth 0 -n -e vvv $H -- "*" >actual &&
+ test_cmp expected actual &&
+ git grep --no-recursive -n -e vvv $H -- "*" >actual &&
test_cmp expected actual
'
echo ${HC}t/v:1:vvv
} >expected &&
git grep --max-depth 0 -n -e vvv $H -- t >actual &&
+ test_cmp expected actual &&
+ git grep --no-recursive -n -e vvv $H -- t >actual &&
test_cmp expected actual
'
echo ${HC}v:1:vvv
} >expected &&
git grep --max-depth 0 -n -e vvv $H -- . t >actual &&
+ test_cmp expected actual &&
+ git grep --no-recursive -n -e vvv $H -- . t >actual &&
test_cmp expected actual
'
echo ${HC}v:1:vvv
} >expected &&
git grep --max-depth 0 -n -e vvv $H -- t . >actual &&
+ test_cmp expected actual &&
+ git grep --no-recursive -n -e vvv $H -- t . >actual &&
test_cmp expected actual
'
test_expect_success "grep $L with grep.extendedRegexp=false" '
tree 8f51f74cf0163afc9ad68a4b1537288c4558b5a4
EOF
-test_expect_success POSIXPERM,SYMLINKS "$name" "test_cmp a expected"
+test_expect_success POSIXPERM,SYMLINKS "$name" "test_cmp expected a"
test_expect_success 'exit if remote refs are ambigious' '
git config --add svn-remote.svn.fetch \
cmp ./deeply/.gitignore create-ignore.expect &&
cmp ./deeply/nested/.gitignore create-ignore.expect &&
cmp ./deeply/nested/directory/.gitignore create-ignore.expect &&
- git ls-files -s | grep gitignore | cmp - create-ignore-index.expect
+ git ls-files -s >ls_files_result &&
+ grep gitignore ls_files_result | cmp - create-ignore-index.expect
"
cat >prop.expect <<\EOF
# This test can be improved: since all the svn:ignore contain the same
# pattern, it can pass even though the propget did not execute on the
# right directory.
-test_expect_success 'test propget' "
- git svn propget svn:ignore . | cmp - prop.expect &&
+test_expect_success 'test propget' '
+ test_propget () {
+ git svn propget $1 $2 >actual &&
+ cmp $3 actual
+ } &&
+ test_propget svn:ignore . prop.expect &&
cd deeply &&
- git svn propget svn:ignore . | cmp - ../prop.expect &&
- git svn propget svn:entry:committed-rev nested/directory/.keep \
- | cmp - ../prop2.expect &&
- git svn propget svn:ignore .. | cmp - ../prop.expect &&
- git svn propget svn:ignore nested/ | cmp - ../prop.expect &&
- git svn propget svn:ignore ./nested | cmp - ../prop.expect &&
- git svn propget svn:ignore .././deeply/nested | cmp - ../prop.expect
- "
+ test_propget svn:ignore . ../prop.expect &&
+ test_propget svn:entry:committed-rev nested/directory/.keep \
+ ../prop2.expect &&
+ test_propget svn:ignore .. ../prop.expect &&
+ test_propget svn:ignore nested/ ../prop.expect &&
+ test_propget svn:ignore ./nested ../prop.expect &&
+ test_propget svn:ignore .././deeply/nested ../prop.expect
+ '
cat >prop.expect <<\EOF
Properties on '.':
EOF
test_expect_success 'test proplist' "
- git svn proplist . | cmp - prop.expect &&
- git svn proplist nested/directory/.keep | cmp - prop2.expect
+ git svn proplist . >actual &&
+ cmp prop.expect actual &&
+
+ git svn proplist nested/directory/.keep >actual &&
+ cmp prop2.expect actual
"
test_done
git svn rebase &&
echo a > expect &&
echo b >> expect &&
- test_cmp a expect &&
+ test_cmp expect a &&
rm expect
)
'
git svn rebase &&
echo a > expect &&
echo b >> expect &&
- test_cmp a expect &&
+ test_cmp expect a &&
rm expect
)
'
echo a > expect &&
echo b >> expect &&
echo c >> expect &&
- test_cmp a expect &&
+ test_cmp expect a &&
rm expect
)
'
git cvsimport -a -z0 &&
echo 1 >expect &&
git log -1 --pretty=format:%s%n >actual &&
- test_cmp actual expect
+ test_cmp expect actual
)
'
Rev 3
Rev 2
Rev 1" > expect-master &&
- test_cmp actual-master expect-master &&
+ test_cmp expect-master actual-master &&
echo "Rev 5 Branch A Wed Mar 11 19:09:10 2009 +0000
Rev 4 Branch A Wed Mar 11 19:03:52 2009 +0000" > expect-A &&
- test_cmp actual-A expect-A
+ test_cmp expect-A actual-A
'
test_done
Rev 2 2005-02-01 00:00:00 +0000
Rev 1 2005-01-01 00:00:00 +0000
EOF
- test_cmp actual-1 expect-1
+ test_cmp expect-1 actual-1
'
test_expect_success PERL 'check timestamps with author-specific timezones' '
Rev 2 2005-01-31 18:00:00 -0600 User Two
Rev 1 2005-01-01 00:00:00 +0000 User One
EOF
- test_cmp actual-2 expect-2
+ test_cmp expect-2 actual-2
'
test_done
p4 add file1 &&
p4 submit -d "change 1" &&
: >file_to_delete &&
+ : >file_to_move &&
p4 add file_to_delete &&
- p4 submit -d "file to delete"
+ p4 add file_to_move &&
+ p4 submit -d "add files to delete"
)
'
echo "new file" >file2 &&
p4 add file2 &&
p4 delete file_to_delete &&
+ p4 edit file_to_move &&
+ p4 move file_to_move moved_file &&
p4 opened &&
p4 shelve -i <<EOF
Change: new
//depot/file1
//depot/file2
//depot/file_to_delete
+ //depot/file_to_move
+ //depot/moved_file
EOF
) &&
cd "$git" &&
change=$(last_shelved_change) &&
git p4 unshelve $change &&
- git show refs/remotes/p4/unshelved/$change | grep -q "Further description" &&
- git cherry-pick refs/remotes/p4/unshelved/$change &&
+ git show refs/remotes/p4-unshelved/$change | grep -q "Further description" &&
+ git cherry-pick refs/remotes/p4-unshelved/$change &&
test_path_is_file file2 &&
test_cmp file1 "$cli"/file1 &&
test_cmp file2 "$cli"/file2 &&
- test_path_is_missing file_to_delete
+ test_path_is_missing file_to_delete &&
+ test_path_is_missing file_to_move &&
+ test_path_is_file moved_file
)
'
cd "$git" &&
change=$(last_shelved_change) &&
git p4 unshelve $change &&
- git diff refs/remotes/p4/unshelved/$change.0 refs/remotes/p4/unshelved/$change | grep -q file3
+ git diff refs/remotes/p4-unshelved/$change.0 refs/remotes/p4-unshelved/$change | grep -q file3
)
'
+shelve_one_file () {
+ description="Change to be unshelved" &&
+ file="$1" &&
+ p4 shelve -i <<EOF
+Change: new
+Description:
+ $description
+Files:
+ $file
+EOF
+}
+
# This is the tricky case where the shelved changelist base revision doesn't
# match git-p4's idea of the base revision
#
p4 submit -d "change:foo" &&
p4 edit file1 &&
echo "bar" >>file1 &&
- p4 shelve -i <<EOF &&
-Change: new
-Description:
- Change to be unshelved
-Files:
- //depot/file1
-EOF
+ shelve_one_file //depot/file1 &&
change=$(last_shelved_change) &&
- p4 describe -S $change | grep -q "Change to be unshelved"
+ p4 describe -S $change >out.txt &&
+ grep -q "Change to be unshelved" out.txt
)
'
-# Now try to unshelve it. git-p4 should refuse to do so.
+# Now try to unshelve it.
test_expect_success 'try to unshelve the change' '
test_when_finished cleanup_git &&
(
change=$(last_shelved_change) &&
cd "$git" &&
- test_must_fail git p4 unshelve $change 2>out.txt &&
- grep -q "cannot unshelve" out.txt
+ git p4 unshelve $change >out.txt &&
+ grep -q "unshelved changelist $change" out.txt
)
'
+# Specify the origin. Create 2 unrelated files, and check that
+# we only get the one in HEAD~, not the one in HEAD.
+
+test_expect_success 'unshelve specifying the origin' '
+ (
+ cd "$cli" &&
+ : >unrelated_file0 &&
+ p4 add unrelated_file0 &&
+ p4 submit -d "unrelated" &&
+ : >unrelated_file1 &&
+ p4 add unrelated_file1 &&
+ p4 submit -d "unrelated" &&
+ : >file_to_shelve &&
+ p4 add file_to_shelve &&
+ shelve_one_file //depot/file_to_shelve
+ ) &&
+ test_when_finished cleanup_git &&
+ git p4 clone --dest="$git" //depot/@all &&
+ (
+ cd "$git" &&
+ change=$(last_shelved_change) &&
+ git p4 unshelve --origin HEAD~ $change &&
+ git checkout refs/remotes/p4-unshelved/$change &&
+ test_path_is_file unrelated_file0 &&
+ test_path_is_missing unrelated_file1 &&
+ test_path_is_file file_to_shelve
+ )
+'
test_expect_success 'kill p4d' '
kill_p4d
'
function name(n) {
if (n == 0) return "RESET";
if (n == 1) return "BOLD";
+ if (n == 2) return "FAINT";
+ if (n == 3) return "ITALIC";
if (n == 7) return "REVERSE";
if (n == 30) return "BLACK";
if (n == 31) return "RED";
}
'
}
+
+# Set the hash algorithm in use to $1. Only useful when testing the testsuite.
+test_set_hash () {
+ test_hash_algo="$1"
+}
+
+# Detect the hash algorithm in use.
+test_detect_hash () {
+ # Currently we only support SHA-1, but in the future this function will
+ # actually detect the algorithm in use.
+ test_hash_algo='sha1'
+}
+
+# Load common hash metadata and common placeholder object IDs for use with
+# test_oid.
+test_oid_init () {
+ test -n "$test_hash_algo" || test_detect_hash &&
+ test_oid_cache <"$TEST_DIRECTORY/oid-info/hash-info" &&
+ test_oid_cache <"$TEST_DIRECTORY/oid-info/oid"
+}
+
+# Load key-value pairs from stdin suitable for use with test_oid. Blank lines
+# and lines starting with "#" are ignored. Keys must be shell identifier
+# characters.
+#
+# Examples:
+# rawsz sha1:20
+# rawsz sha256:32
+test_oid_cache () {
+ local tag rest k v &&
+
+ { test -n "$test_hash_algo" || test_detect_hash; } &&
+ while read tag rest
+ do
+ case $tag in
+ \#*)
+ continue;;
+ ?*)
+ # non-empty
+ ;;
+ *)
+ # blank line
+ continue;;
+ esac &&
+
+ k="${rest%:*}" &&
+ v="${rest#*:}" &&
+
+ if ! expr "$k" : '[a-z0-9][a-z0-9]*$' >/dev/null
+ then
+ error 'bug in the test script: bad hash algorithm'
+ fi &&
+ eval "test_oid_${k}_$tag=\"\$v\""
+ done
+}
+
+# Look up a per-hash value based on a key ($1). The value must have been loaded
+# by test_oid_init or test_oid_cache.
+test_oid () {
+ local var="test_oid_${test_hash_algo}_$1" &&
+
+ # If the variable is unset, we must be missing an entry for this
+ # key-hash pair, so exit with an error.
+ if eval "test -z \"\${$var+set}\""
+ then
+ error "bug in the test script: undefined key '$1'" >&2
+ fi &&
+ eval "printf '%s' \"\${$var}\""
+}
GIT_TRACE_BARE=1
export GIT_TRACE_BARE
-if test -n "${TEST_GIT_INDEX_VERSION:+isset}"
+check_var_migration () {
+ # the warnings and hints given from this helper depends
+ # on end-user settings, which will disrupt the self-test
+ # done on the test framework itself.
+ case "$GIT_TEST_FRAMEWORK_SELFTEST" in
+ t) return ;;
+ esac
+
+ old_name=$1 new_name=$2
+ eval "old_isset=\${${old_name}:+isset}"
+ eval "new_isset=\${${new_name}:+isset}"
+
+ case "$old_isset,$new_isset" in
+ isset,)
+ echo >&2 "warning: $old_name is now $new_name"
+ echo >&2 "hint: set $new_name too during the transition period"
+ eval "$new_name=\$$old_name"
+ ;;
+ isset,isset)
+ # do this later
+ # echo >&2 "warning: $old_name is now $new_name"
+ # echo >&2 "hint: remove $old_name"
+ ;;
+ esac
+}
+
+check_var_migration GIT_FSMONITOR_TEST GIT_TEST_FSMONITOR
+check_var_migration TEST_GIT_INDEX_VERSION GIT_TEST_INDEX_VERSION
+check_var_migration GIT_FORCE_PRELOAD_TEST GIT_TEST_PRELOAD_INDEX
+
+# Use specific version of the index file format
+if test -n "${GIT_TEST_INDEX_VERSION:+isset}"
then
- GIT_INDEX_VERSION="$TEST_GIT_INDEX_VERSION"
+ GIT_INDEX_VERSION="$GIT_TEST_INDEX_VERSION"
export GIT_INDEX_VERSION
fi
test_set_prereq C_LOCALE_OUTPUT
fi
+if test -z "$GIT_TEST_CHECK_CACHE_TREE"
+then
+ GIT_TEST_CHECK_CACHE_TREE=true
+ export GIT_TEST_CHECK_CACHE_TREE
+fi
+
test_lazy_prereq PIPE '
# test whether the filesystem supports FIFOs
test_have_prereq !MINGW,!CYGWIN &&
return 0;
item->object.parsed = 1;
- if (size < GIT_SHA1_HEXSZ + 24)
+ if (size < the_hash_algo->hexsz + 24)
return -1;
if (memcmp("object ", bufptr, 7) || parse_oid_hex(bufptr + 7, &oid, &bufptr) || *bufptr++ != '\n')
return -1;
BUG("reopen_tempfile called for an inactive object");
if (0 <= tempfile->fd)
BUG("reopen_tempfile called for an open object");
- tempfile->fd = open(tempfile->filename.buf, O_WRONLY);
+ tempfile->fd = open(tempfile->filename.buf, O_WRONLY|O_TRUNC);
return tempfile->fd;
}
* it (and nobody else) to inspect or even modify the file's
* contents.
*
- * * `reopen_tempfile()` to reopen the temporary file. Make further
- * updates to the contents.
+ * * `reopen_tempfile()` to reopen the temporary file, truncating the existing
+ * contents. Write out the new contents.
*
* * `rename_tempfile()` to move the file to its permanent location.
*/
strbuf_release(&buf);
}
+static uint64_t perf_start_times[10];
+static int perf_indent;
+
+uint64_t trace_performance_enter(void)
+{
+ uint64_t now;
+
+ if (!trace_want(&trace_perf_key))
+ return 0;
+
+ now = getnanotime();
+ perf_start_times[perf_indent] = now;
+ if (perf_indent + 1 < ARRAY_SIZE(perf_start_times))
+ perf_indent++;
+ else
+ BUG("Too deep indentation");
+ return now;
+}
+
static void trace_performance_vprintf_fl(const char *file, int line,
uint64_t nanos, const char *format,
va_list ap)
{
+ static const char space[] = " ";
struct strbuf buf = STRBUF_INIT;
if (!prepare_trace_line(file, line, &trace_perf_key, &buf))
strbuf_addf(&buf, "performance: %.9f s", (double) nanos / 1000000000);
if (format && *format) {
- strbuf_addstr(&buf, ": ");
+ if (perf_indent >= strlen(space))
+ BUG("Too deep indentation");
+
+ strbuf_addf(&buf, ":%.*s ", perf_indent, space);
strbuf_vaddf(&buf, format, ap);
}
va_end(ap);
}
+void trace_performance_leave(const char *format, ...)
+{
+ va_list ap;
+ uint64_t since;
+
+ if (perf_indent)
+ perf_indent--;
+
+ if (!format) /* Allow callers to leave without tracing anything */
+ return;
+
+ since = perf_start_times[perf_indent];
+ va_start(ap, format);
+ trace_performance_vprintf_fl(NULL, 0, getnanotime() - since,
+ format, ap);
+ va_end(ap);
+}
+
#else
void trace_printf_key_fl(const char *file, int line, struct trace_key *key,
va_end(ap);
}
+void trace_performance_leave_fl(const char *file, int line,
+ uint64_t nanos, const char *format, ...)
+{
+ va_list ap;
+ uint64_t since;
+
+ if (perf_indent)
+ perf_indent--;
+
+ if (!format) /* Allow callers to leave without tracing anything */
+ return;
+
+ since = perf_start_times[perf_indent];
+ va_start(ap, format);
+ trace_performance_vprintf_fl(file, line, nanos - since, format, ap);
+ va_end(ap);
+}
+
#endif /* HAVE_VARIADIC_MACROS */
}
}
-static uint64_t command_start_time;
static struct strbuf command_line = STRBUF_INIT;
static void print_command_performance_atexit(void)
{
- trace_performance_since(command_start_time, "git command:%s",
- command_line.buf);
+ trace_performance_leave("git command:%s", command_line.buf);
}
void trace_command_performance(const char **argv)
if (!trace_want(&trace_perf_key))
return;
- if (!command_start_time)
+ if (!command_line.len)
atexit(print_command_performance_atexit);
strbuf_reset(&command_line);
sq_quote_argv_pretty(&command_line, argv);
- command_start_time = getnanotime();
+ trace_performance_enter();
}
extern uint64_t getnanotime(void);
extern void trace_command_performance(const char **argv);
extern void trace_verbatim(struct trace_key *key, const void *buf, unsigned len);
+uint64_t trace_performance_enter(void);
#ifndef HAVE_VARIADIC_MACROS
__attribute__((format (printf, 2, 3)))
extern void trace_performance_since(uint64_t start, const char *format, ...);
+__attribute__((format (printf, 1, 2)))
+void trace_performance_leave(const char *format, ...);
+
#else
/*
__VA_ARGS__); \
} while (0)
+#define trace_performance_leave(...) \
+ do { \
+ if (trace_pass_fl(&trace_perf_key)) \
+ trace_performance_leave_fl(TRACE_CONTEXT, __LINE__, \
+ getnanotime(), \
+ __VA_ARGS__); \
+ } while (0)
+
/* backend functions, use non-*fl macros instead */
__attribute__((format (printf, 4, 5)))
extern void trace_printf_key_fl(const char *file, int line, struct trace_key *key,
__attribute__((format (printf, 4, 5)))
extern void trace_performance_fl(const char *file, int line,
uint64_t nanos, const char *fmt, ...);
+__attribute__((format (printf, 4, 5)))
+extern void trace_performance_leave_fl(const char *file, int line,
+ uint64_t nanos, const char *fmt, ...);
static inline int trace_pass_fl(struct trace_key *key)
{
return key->fd || !key->initialized;
return item->conf.name;
}
-static int token_matches_item(const char *tok, struct arg_item *item, int tok_len)
+static int token_matches_item(const char *tok, struct arg_item *item, size_t tok_len)
{
if (!strncasecmp(tok, item->conf.name, tok_len))
return 1;
* distinguished from the non-well-formed-line case (in which this function
* returns -1) because some callers of this function need such a distinction.
*/
-static int find_separator(const char *line, const char *separators)
+static ssize_t find_separator(const char *line, const char *separators)
{
int whitespace_found = 0;
const char *c;
*/
static void parse_trailer(struct strbuf *tok, struct strbuf *val,
const struct conf_info **conf, const char *trailer,
- int separator_pos)
+ ssize_t separator_pos)
{
struct arg_item *item;
- int tok_len;
+ size_t tok_len;
struct list_head *pos;
if (separator_pos != -1) {
list_for_each(pos, new_trailer_head) {
struct new_trailer_item *tr =
list_entry(pos, struct new_trailer_item, list);
- int separator_pos = find_separator(tr->text, cl_separators);
+ ssize_t separator_pos = find_separator(tr->text, cl_separators);
if (separator_pos == 0) {
struct strbuf sb = STRBUF_INIT;
/*
* Return the position of the start of the last line. If len is 0, return -1.
*/
-static int last_line(const char *buf, size_t len)
+static ssize_t last_line(const char *buf, size_t len)
{
- int i;
+ ssize_t i;
if (len == 0)
return -1;
if (len == 1)
* Return the position of the start of the patch or the length of str if there
* is no patch in the message.
*/
-static int find_patch_start(const char *str)
+static size_t find_patch_start(const char *str)
{
const char *s;
for (s = str; *s; s = next_line(s)) {
- if (starts_with(s, "---"))
+ const char *v;
+
+ if (skip_prefix(s, "---", &v) && isspace(*v))
return s - str;
}
* Return the position of the first trailer line or len if there are no
* trailers.
*/
-static int find_trailer_start(const char *buf, size_t len)
+static size_t find_trailer_start(const char *buf, size_t len)
{
const char *s;
- int end_of_title, l, only_spaces = 1;
+ ssize_t end_of_title, l;
+ int only_spaces = 1;
int recognized_prefix = 0, trailer_lines = 0, non_trailer_lines = 0;
/*
* Number of possible continuation lines encountered. This will be
l = last_line(buf, l)) {
const char *bol = buf + l;
const char **p;
- int separator_pos;
+ ssize_t separator_pos;
if (bol[0] == comment_line_char) {
non_trailer_lines += possible_continuation_lines;
}
/* Return the position of the end of the trailers. */
-static int find_trailer_end(const char *buf, size_t len)
+static size_t find_trailer_end(const char *buf, size_t len)
{
return len - ignore_non_trailer(buf, len);
}
static int ends_with_blank_line(const char *buf, size_t len)
{
- int ll = last_line(buf, len);
+ ssize_t ll = last_line(buf, len);
if (ll < 0)
return 0;
return is_blank_line(buf + ll);
strbuf_release(&out);
}
-static int process_input_file(FILE *outfile,
- const char *str,
- struct list_head *head,
- const struct process_trailer_options *opts)
+static size_t process_input_file(FILE *outfile,
+ const char *str,
+ struct list_head *head,
+ const struct process_trailer_options *opts)
{
struct trailer_info info;
struct strbuf tok = STRBUF_INIT;
struct strbuf val = STRBUF_INIT;
- int i;
+ size_t i;
- trailer_info_get(&info, str);
+ trailer_info_get(&info, str, opts);
/* Print lines before the trailers as is */
if (!opts->only_trailers)
{
LIST_HEAD(head);
struct strbuf sb = STRBUF_INIT;
- int trailer_end;
+ size_t trailer_end;
FILE *outfile = stdout;
ensure_configured();
strbuf_release(&sb);
}
-void trailer_info_get(struct trailer_info *info, const char *str)
+void trailer_info_get(struct trailer_info *info, const char *str,
+ const struct process_trailer_options *opts)
{
int patch_start, trailer_end, trailer_start;
struct strbuf **trailer_lines, **ptr;
ensure_configured();
- patch_start = find_patch_start(str);
+ if (opts->no_divider)
+ patch_start = strlen(str);
+ else
+ patch_start = find_patch_start(str);
+
trailer_end = find_trailer_end(str, patch_start);
trailer_start = find_trailer_start(str, trailer_end);
void trailer_info_release(struct trailer_info *info)
{
- int i;
+ size_t i;
for (i = 0; i < info->trailer_nr; i++)
free(info->trailers[i]);
free(info->trailers);
const struct trailer_info *info,
const struct process_trailer_options *opts)
{
- int i;
+ size_t i;
/* If we want the whole block untouched, we can take the fast path. */
if (!opts->only_trailers && !opts->unfold) {
for (i = 0; i < info->trailer_nr; i++) {
char *trailer = info->trailers[i];
- int separator_pos = find_separator(trailer, separators);
+ ssize_t separator_pos = find_separator(trailer, separators);
if (separator_pos >= 1) {
struct strbuf tok = STRBUF_INIT;
{
struct trailer_info info;
- trailer_info_get(&info, msg);
+ trailer_info_get(&info, msg, opts);
format_trailer_info(out, &info, opts);
trailer_info_release(&info);
}
int only_trailers;
int only_input;
int unfold;
+ int no_divider;
};
#define PROCESS_TRAILER_OPTIONS_INIT {0}
const struct process_trailer_options *opts,
struct list_head *new_trailer_head);
-void trailer_info_get(struct trailer_info *info, const char *str);
+void trailer_info_get(struct trailer_info *info, const char *str,
+ const struct process_trailer_options *opts);
void trailer_info_release(struct trailer_info *info);
}
static struct transport_vtable vtable = {
+ 0,
set_helper_option,
get_refs_list,
fetch,
struct argv_array;
struct transport_vtable {
+ /**
+ * This transport supports the fetch() function being called
+ * without get_refs_list() first being called.
+ */
+ unsigned fetch_without_list : 1;
+
/**
* Returns 0 if successful, positive if the option is not
* recognized or is inapplicable, and negative if the option
return 0;
}
-static struct ref *get_refs_via_connect(struct transport *transport, int for_push,
- const struct argv_array *ref_prefixes)
+/*
+ * Obtains the protocol version from the transport and writes it to
+ * transport->data->version, first connecting if not already connected.
+ *
+ * If the protocol version is one that allows skipping the listing of remote
+ * refs, and must_list_refs is 0, the listing of remote refs is skipped and
+ * this function returns NULL. Otherwise, this function returns the list of
+ * remote refs.
+ */
+static struct ref *handshake(struct transport *transport, int for_push,
+ const struct argv_array *ref_prefixes,
+ int must_list_refs)
{
struct git_transport_data *data = transport->data;
struct ref *refs = NULL;
data->version = discover_version(&reader);
switch (data->version) {
case protocol_v2:
- get_remote_refs(data->fd[1], &reader, &refs, for_push,
- ref_prefixes, transport->server_options);
+ if (must_list_refs)
+ get_remote_refs(data->fd[1], &reader, &refs, for_push,
+ ref_prefixes,
+ transport->server_options);
break;
case protocol_v1:
case protocol_v0:
}
data->got_remote_heads = 1;
+ if (reader.line_peeked)
+ BUG("buffer must be empty at the end of handshake()");
+
return refs;
}
+static struct ref *get_refs_via_connect(struct transport *transport, int for_push,
+ const struct argv_array *ref_prefixes)
+{
+ return handshake(transport, for_push, ref_prefixes, 1);
+}
+
static int fetch_refs_via_pack(struct transport *transport,
int nr_heads, struct ref **to_fetch)
{
args.server_options = transport->server_options;
args.negotiation_tips = data->options.negotiation_tips;
- if (!data->got_remote_heads)
- refs_tmp = get_refs_via_connect(transport, 0, NULL);
+ if (!data->got_remote_heads) {
+ int i;
+ int must_list_refs = 0;
+ for (i = 0; i < nr_heads; i++) {
+ if (!to_fetch[i]->exact_oid) {
+ must_list_refs = 1;
+ break;
+ }
+ }
+ refs_tmp = handshake(transport, 0, NULL, must_list_refs);
+ }
switch (data->version) {
case protocol_v2:
}
static struct transport_vtable taken_over_vtable = {
+ 1,
NULL,
get_refs_via_connect,
fetch_refs_via_pack,
}
static struct transport_vtable bundle_vtable = {
+ 0,
NULL,
get_refs_from_bundle,
fetch_refs_from_bundle,
};
static struct transport_vtable builtin_smart_vtable = {
+ 1,
NULL,
get_refs_via_connect,
fetch_refs_via_pack,
oid_array_append(&commits,
&ref->new_oid);
- if (!push_unpushed_submodules(&commits,
+ if (!push_unpushed_submodules(&the_index,
+ &commits,
transport->remote,
rs,
transport->push_options,
oid_array_append(&commits,
&ref->new_oid);
- if (find_unpushed_submodules(&commits, transport->remote->name,
- &needs_pushing)) {
+ if (find_unpushed_submodules(&the_index,
+ &commits,
+ transport->remote->name,
+ &needs_pushing)) {
oid_array_clear(&commits);
die_with_unpushed_submodules(&needs_pushing);
}
struct ref **heads = NULL;
struct ref *rm;
+ if (!transport->vtable->fetch_without_list)
+ /*
+ * Some transports (e.g. the built-in bundle transport and the
+ * transport helper interface) do not work when fetching is
+ * done immediately after transport creation. List the remote
+ * refs anyway (if not already listed) as a workaround.
+ */
+ transport_get_remote_refs(transport, NULL);
+
for (rm = refs; rm; rm = rm->next) {
nr_refs++;
if (rm->peer_ref &&
!is_null_oid(&rm->old_oid) &&
- !oidcmp(&rm->peer_ref->old_oid, &rm->old_oid))
+ oideq(&rm->peer_ref->old_oid, &rm->old_oid))
continue;
ALLOC_GROW(heads, nr_heads + 1, nr_alloc);
heads[nr_heads++] = rm;
return xstrdup(url);
}
+static void fill_alternate_refs_command(struct child_process *cmd,
+ const char *repo_path)
+{
+ const char *value;
+
+ if (!git_config_get_value("core.alternateRefsCommand", &value)) {
+ cmd->use_shell = 1;
+
+ argv_array_push(&cmd->args, value);
+ argv_array_push(&cmd->args, repo_path);
+ } else {
+ cmd->git_cmd = 1;
+
+ argv_array_pushf(&cmd->args, "--git-dir=%s", repo_path);
+ argv_array_push(&cmd->args, "for-each-ref");
+ argv_array_push(&cmd->args, "--format=%(objectname)");
+
+ if (!git_config_get_value("core.alternateRefsPrefixes", &value)) {
+ argv_array_push(&cmd->args, "--");
+ argv_array_split(&cmd->args, value);
+ }
+ }
+
+ cmd->env = local_repo_env;
+ cmd->out = -1;
+}
+
static void read_alternate_refs(const char *path,
alternate_ref_fn *cb,
void *data)
struct strbuf line = STRBUF_INIT;
FILE *fh;
- cmd.git_cmd = 1;
- argv_array_pushf(&cmd.args, "--git-dir=%s", path);
- argv_array_push(&cmd.args, "for-each-ref");
- argv_array_push(&cmd.args, "--format=%(objectname) %(refname)");
- cmd.env = local_repo_env;
- cmd.out = -1;
+ fill_alternate_refs_command(&cmd, path);
if (start_command(&cmd))
return;
fh = xfdopen(cmd.out, "r");
while (strbuf_getline_lf(&line, fh) != EOF) {
struct object_id oid;
+ const char *p;
- if (get_oid_hex(line.buf, &oid) ||
- line.buf[GIT_SHA1_HEXSZ] != ' ') {
+ if (parse_oid_hex(line.buf, &oid, &p) || *p) {
warning(_("invalid line while parsing alternate refs: %s"),
line.buf);
break;
}
- cb(line.buf + GIT_SHA1_HEXSZ + 1, &oid, data);
+ cb(&oid, data);
}
fclose(fh);
void transport_print_push_status(const char *dest, struct ref *refs,
int verbose, int porcelain, unsigned int *reject_reasons);
-typedef void alternate_ref_fn(const char *refname, const struct object_id *oid, void *);
+typedef void alternate_ref_fn(const struct object_id *oid, void *);
extern void for_each_alternate_ref(alternate_ref_fn, void *);
#endif
continue;
/* diff(t,pi) != ø */
- if (oidcmp(t.entry.oid, tp[i].entry.oid) ||
+ if (!oideq(t.entry.oid, tp[i].entry.oid) ||
(t.entry.mode != tp[i].entry.mode))
continue;
choice = q->queue[0];
q->nr = 0;
- diff_setup(&diff_opts);
+ repo_diff_setup(opt->repo, &diff_opts);
diff_opts.flags.recursive = 1;
diff_opts.flags.find_copies_harder = 1;
diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
return start_delayed_progress(_("Checking out files"), total);
}
+static void setup_collided_checkout_detection(struct checkout *state,
+ struct index_state *index)
+{
+ int i;
+
+ state->clone = 1;
+ for (i = 0; i < index->cache_nr; i++)
+ index->cache[i]->ce_flags &= ~CE_MATCHED;
+}
+
+static void report_collided_checkout(struct index_state *index)
+{
+ struct string_list list = STRING_LIST_INIT_NODUP;
+ int i;
+
+ for (i = 0; i < index->cache_nr; i++) {
+ struct cache_entry *ce = index->cache[i];
+
+ if (!(ce->ce_flags & CE_MATCHED))
+ continue;
+
+ string_list_append(&list, ce->name);
+ ce->ce_flags &= ~CE_MATCHED;
+ }
+
+ list.cmp = fspathcmp;
+ string_list_sort(&list);
+
+ if (list.nr) {
+ warning(_("the following paths have collided (e.g. case-sensitive paths\n"
+ "on a case-insensitive filesystem) and only one from the same\n"
+ "colliding group is in the working tree:\n"));
+
+ for (i = 0; i < list.nr; i++)
+ fprintf(stderr, " '%s'\n", list.items[i].string);
+ }
+
+ string_list_clear(&list, 0);
+}
+
static int check_updates(struct unpack_trees_options *o)
{
unsigned cnt = 0;
int errs = 0;
- struct progress *progress = NULL;
+ struct progress *progress;
struct index_state *index = &o->result;
struct checkout state = CHECKOUT_INIT;
int i;
+ trace_performance_enter();
state.force = 1;
state.quiet = 1;
state.refresh_cache = 1;
state.istate = index;
+ if (o->clone)
+ setup_collided_checkout_detection(&state, index);
+
progress = get_progress(o);
if (o->update)
}
if (to_fetch.nr)
fetch_objects(repository_format_partial_clone,
- &to_fetch);
+ to_fetch.oid, to_fetch.nr);
fetch_if_missing = fetch_if_missing_store;
oid_array_clear(&to_fetch);
}
errs |= finish_delayed_checkout(&state);
if (o->update)
git_attr_set_direction(GIT_ATTR_CHECKIN);
+
+ if (o->clone)
+ report_collided_checkout(index);
+
+ trace_performance_leave("check_updates");
return errs != 0;
}
static inline int are_same_oid(struct name_entry *name_j, struct name_entry *name_k)
{
- return name_j->oid && name_k->oid && !oidcmp(name_j->oid, name_k->oid);
+ return name_j->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;
+
+ if (!o->merge || dirmask != ((1 << n) - 1))
+ return 0;
+
+ for (i = 1; i < n; i++)
+ if (!are_same_oid(names, names + i))
+ return 0;
+
+ return cache_tree_matches_traversal(o->src_index->cache_tree, names, info);
+}
+
+static int index_pos_by_traverse_info(struct name_entry *names,
+ struct traverse_info *info)
+{
+ struct unpack_trees_options *o = info->data;
+ int len = traverse_path_len(info, names);
+ char *name = xmalloc(len + 1 /* slash */ + 1 /* NUL */);
+ int pos;
+
+ make_traverse_path(name, info, names);
+ name[len++] = '/';
+ name[len] = '\0';
+ pos = index_name_pos(o->src_index, name, len);
+ if (pos >= 0)
+ BUG("This is a directory and should not exist in index");
+ pos = -pos - 1;
+ if (!starts_with(o->src_index->cache[pos]->name, name) ||
+ (pos > 0 && starts_with(o->src_index->cache[pos-1]->name, name)))
+ BUG("pos must point at the first entry in this directory");
+ free(name);
+ return pos;
+}
+
+/*
+ * Fast path if we detect that all trees are the same as cache-tree at this
+ * path. We'll walk these trees in an iterative loop using cache-tree/index
+ * instead of ODB since we already know what these trees contain.
+ */
+static int traverse_by_cache_tree(int pos, int nr_entries, int nr_names,
+ struct name_entry *names,
+ struct traverse_info *info)
+{
+ struct cache_entry *src[MAX_UNPACK_TREES + 1] = { NULL, };
+ struct unpack_trees_options *o = info->data;
+ struct cache_entry *tree_ce = NULL;
+ int ce_len = 0;
+ int i, d;
+
+ if (!o->merge)
+ BUG("We need cache-tree to do this optimization");
+
+ /*
+ * Do what unpack_callback() and unpack_nondirectories() normally
+ * do. But we walk all paths in an iterative loop instead.
+ *
+ * D/F conflicts and higher stage entries are not a concern
+ * because cache-tree would be invalidated and we would never
+ * get here in the first place.
+ */
+ for (i = 0; i < nr_entries; i++) {
+ int new_ce_len, len, rc;
+
+ src[0] = o->src_index->cache[pos + i];
+
+ len = ce_namelen(src[0]);
+ new_ce_len = cache_entry_size(len);
+
+ if (new_ce_len > ce_len) {
+ new_ce_len <<= 1;
+ tree_ce = xrealloc(tree_ce, new_ce_len);
+ memset(tree_ce, 0, new_ce_len);
+ ce_len = new_ce_len;
+
+ tree_ce->ce_flags = create_ce_flags(0);
+
+ for (d = 1; d <= nr_names; d++)
+ src[d] = tree_ce;
+ }
+
+ tree_ce->ce_mode = src[0]->ce_mode;
+ tree_ce->ce_namelen = len;
+ oidcpy(&tree_ce->oid, &src[0]->oid);
+ memcpy(tree_ce->name, src[0]->name, len + 1);
+
+ rc = call_unpack_fn((const struct cache_entry * const *)src, o);
+ if (rc < 0) {
+ free(tree_ce);
+ return rc;
+ }
+
+ mark_ce_used(src[0], o);
+ }
+ free(tree_ce);
+ if (o->debug_unpack)
+ printf("Unpacked %d entries from %s to %s using cache-tree\n",
+ nr_entries,
+ o->src_index->cache[pos]->name,
+ o->src_index->cache[pos + nr_entries - 1]->name);
+ return 0;
}
static int traverse_trees_recursive(int n, unsigned long dirmask,
void *buf[MAX_UNPACK_TREES];
struct traverse_info newinfo;
struct name_entry *p;
+ int nr_entries;
+
+ 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)
+ BUG("Wrong condition to get here buddy");
+
+ /*
+ * All entries up to 'pos' must have been processed
+ * (i.e. marked CE_UNPACKED) at this point. But to be safe,
+ * save and restore cache_bottom anyway to not miss
+ * unprocessed entries before 'pos'.
+ */
+ bottom = o->cache_bottom;
+ ret = traverse_by_cache_tree(pos, nr_entries, n, names, info);
+ o->cache_bottom = bottom;
+ return ret;
+ }
p = names;
while (!p->mode)
return ce;
}
+/*
+ * Note that traverse_by_cache_tree() duplicates some logic in this function
+ * without actually calling it. If you change the logic here you may need to
+ * check and change there as well.
+ */
static int unpack_nondirectories(int n, unsigned long mask,
unsigned long dirmask,
struct cache_entry **src,
debug_name_entry(i, names + i);
}
+/*
+ * Note that traverse_by_cache_tree() duplicates some logic in this function
+ * without actually calling it. If you change the logic here you may need to
+ * check and change there as well.
+ */
static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, struct name_entry *names, struct traverse_info *info)
{
struct cache_entry *src[MAX_UNPACK_TREES + 1] = { NULL, };
if (len > MAX_UNPACK_TREES)
die("unpack_trees takes at most %d trees", MAX_UNPACK_TREES);
+ trace_performance_enter();
memset(&el, 0, sizeof(el));
if (!core_apply_sparse_checkout || !o->update)
o->skip_sparse_checkout = 1;
}
}
- if (traverse_trees(len, t, &info) < 0)
+ trace_performance_enter();
+ ret = traverse_trees(len, t, &info);
+ trace_performance_leave("traverse_trees");
+ if (ret < 0)
goto return_failed;
}
ret = check_updates(o) ? (-2) : 0;
if (o->dst_index) {
+ 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);
if (!o->result.cache_tree)
o->result.cache_tree = cache_tree();
if (!cache_tree_fully_valid(o->result.cache_tree))
WRITE_TREE_SILENT |
WRITE_TREE_REPAIR);
}
- move_index_extensions(&o->result, o->src_index);
discard_index(o->dst_index);
*o->dst_index = o->result;
} else {
o->src_index = NULL;
done:
+ trace_performance_leave("unpack_trees");
clear_exclude_list(&el);
return ret;
if ((a->ce_flags | b->ce_flags) & CE_CONFLICTED)
return 0;
return a->ce_mode == b->ce_mode &&
- !oidcmp(&a->oid, &b->oid);
+ oideq(&a->oid, &b->oid);
}
* If we are not going to update the submodule, then
* we don't care.
*/
- if (!sub_head && !oidcmp(&oid, &ce->oid))
+ if (!sub_head && oideq(&oid, &ce->oid))
return 0;
return verify_clean_submodule(sub_head ? NULL : oid_to_hex(&oid),
ce, error_type, o);
if (verify_uptodate(ce2, o))
return -1;
add_entry(o, ce2, CE_REMOVE, 0);
+ invalidate_ce_path(ce, o);
mark_ce_used(ce2, o);
}
cnt++;
struct unpack_trees_options *o)
{
add_entry(o, ce, 0, 0);
+ if (ce_stage(ce))
+ invalidate_ce_path(ce, o);
return 1;
}
unsigned int reset,
merge,
update,
+ clone,
index_only,
nontrivial_merge,
trivial_merges_only,
#include "quote.h"
#include "upload-pack.h"
#include "serve.h"
+#include "commit-graph.h"
+#include "commit-reach.h"
/* Remember to update object flag allocation in object.h */
#define THEY_HAVE (1u << 11)
#define OUR_REF (1u << 12)
#define WANTED (1u << 13)
#define COMMON_KNOWN (1u << 14)
-#define REACHABLE (1u << 15)
#define SHALLOW (1u << 16)
#define NOT_SHALLOW (1u << 17)
return 0;
}
-static int reachable(struct commit *want)
-{
- struct prio_queue work = { compare_commits_by_commit_date };
-
- prio_queue_put(&work, want);
- while (work.nr) {
- struct commit_list *list;
- struct commit *commit = prio_queue_get(&work);
-
- if (commit->object.flags & THEY_HAVE) {
- want->object.flags |= COMMON_KNOWN;
- break;
- }
- if (!commit->object.parsed)
- parse_object(the_repository, &commit->object.oid);
- if (commit->object.flags & REACHABLE)
- continue;
- commit->object.flags |= REACHABLE;
- if (commit->date < oldest_have)
- continue;
- for (list = commit->parents; list; list = list->next) {
- struct commit *parent = list->item;
- if (!(parent->object.flags & REACHABLE))
- prio_queue_put(&work, parent);
- }
- }
- want->object.flags |= REACHABLE;
- clear_commit_marks(want, REACHABLE);
- clear_prio_queue(&work);
- return (want->object.flags & COMMON_KNOWN);
-}
-
static int ok_to_give_up(void)
{
- int i;
+ uint32_t min_generation = GENERATION_NUMBER_ZERO;
if (!have_obj.nr)
return 0;
- for (i = 0; i < want_obj.nr; i++) {
- struct object *want = want_obj.objects[i].item;
-
- if (want->flags & COMMON_KNOWN)
- continue;
- want = deref_tag(the_repository, want, "a want line", 0);
- if (!want || want->type != OBJ_COMMIT) {
- /* no way to tell if this is reachable by
- * looking at the ancestry chain alone, so
- * leave a note to ourselves not to worry about
- * this object anymore.
- */
- want_obj.objects[i].item->flags |= COMMON_KNOWN;
- continue;
- }
- if (!reachable((struct commit *)want))
- return 0;
- }
- return 1;
+ return can_all_from_reach_with_flag(&want_obj, THEY_HAVE,
+ COMMON_KNOWN, oldest_have,
+ min_generation);
}
static int get_common_commits(void)
struct object *o;
char namebuf[GIT_MAX_HEXSZ + 2]; /* ^ + hash + LF */
int i;
+ const unsigned hexsz = the_hash_algo->hexsz;
cmd->argv = argv;
cmd->git_cmd = 1;
goto error;
namebuf[0] = '^';
- namebuf[GIT_SHA1_HEXSZ + 1] = '\n';
+ namebuf[hexsz + 1] = '\n';
for (i = get_max_object_index(); 0 < i; ) {
o = get_indexed_object(--i);
if (!o)
o->flags &= ~TMP_MARK;
if (!is_our_ref(o))
continue;
- memcpy(namebuf + 1, oid_to_hex(&o->oid), GIT_SHA1_HEXSZ);
- if (write_in_full(cmd->in, namebuf, GIT_SHA1_HEXSZ + 2) < 0)
+ memcpy(namebuf + 1, oid_to_hex(&o->oid), hexsz);
+ if (write_in_full(cmd->in, namebuf, hexsz + 2) < 0)
goto error;
}
- namebuf[GIT_SHA1_HEXSZ] = '\n';
+ namebuf[hexsz] = '\n';
for (i = 0; i < src->nr; i++) {
o = src->objects[i].item;
if (is_our_ref(o)) {
}
if (reachable && o->type == OBJ_COMMIT)
o->flags |= TMP_MARK;
- memcpy(namebuf, oid_to_hex(&o->oid), GIT_SHA1_HEXSZ);
- if (write_in_full(cmd->in, namebuf, GIT_SHA1_HEXSZ + 1) < 0)
+ memcpy(namebuf, oid_to_hex(&o->oid), hexsz);
+ if (write_in_full(cmd->in, namebuf, hexsz + 1) < 0)
goto error;
}
close(cmd->in);
{
struct commit_list *result;
+ close_commit_graph(the_repository);
result = get_shallow_commits_by_rev_list(ac, av, SHALLOW, NOT_SHALLOW);
send_shallow(result);
free_commit_list(result);
return userdiff_find_by_namelen(name, len);
}
-struct userdiff_driver *userdiff_find_by_path(const char *path)
+struct userdiff_driver *userdiff_find_by_path(struct index_state *istate,
+ const char *path)
{
static struct attr_check *check;
check = attr_check_initl("diff", NULL);
if (!path)
return NULL;
- if (git_check_attr(&the_index, path, check))
- return NULL;
+ git_check_attr(istate, path, check);
if (ATTR_TRUE(check->items[0].value))
return &driver_true;
#include "notes-cache.h"
+struct index_state;
+
struct userdiff_funcname {
const char *pattern;
int cflags;
int userdiff_config(const char *k, const char *v);
struct userdiff_driver *userdiff_find_by_name(const char *name);
-struct userdiff_driver *userdiff_find_by_path(const char *path);
+struct userdiff_driver *userdiff_find_by_path(struct index_state *istate,
+ const char *path);
/*
* Initialize any textconv-related fields in the driver and return it, or NULL
-#ifndef FAST_EXPORT_H_
-#define FAST_EXPORT_H_
+#ifndef FAST_EXPORT_H
+#define FAST_EXPORT_H
struct strbuf;
struct line_buffer;
-#ifndef LINE_BUFFER_H_
-#define LINE_BUFFER_H_
+#ifndef LINE_BUFFER_H
+#define LINE_BUFFER_H
#include "strbuf.h"
-#ifndef SLIDING_WINDOW_H_
-#define SLIDING_WINDOW_H_
+#ifndef SLIDING_WINDOW_H
+#define SLIDING_WINDOW_H
#include "strbuf.h"
-#ifndef SVNDIFF_H_
-#define SVNDIFF_H_
+#ifndef SVNDIFF_H
+#define SVNDIFF_H
struct line_buffer;
struct sliding_view;
-#ifndef SVNDUMP_H_
-#define SVNDUMP_H_
+#ifndef SVNDUMP_H
+#define SVNDUMP_H
int svndump_init(const char *filename);
int svndump_init_fd(int in_fd, int back_fd);
if (prefix)
arg = to_free = prefix_filename(prefix, arg);
- path = real_pathdup(arg, 1);
+ path = real_pathdup(arg, 0);
+ if (!path) {
+ free(to_free);
+ return NULL;
+ }
for (; *list; list++)
if (!fspathcmp(path, real_path((*list)->path)))
break;
*
* Copyright (c) 2007 Junio C Hamano
*/
-
#include "cache.h"
#include "attr.h"
return rule;
}
-unsigned whitespace_rule(const char *pathname)
+unsigned whitespace_rule(struct index_state *istate, const char *pathname)
{
static struct attr_check *attr_whitespace_rule;
+ const char *value;
if (!attr_whitespace_rule)
attr_whitespace_rule = attr_check_initl("whitespace", NULL);
- if (!git_check_attr(&the_index, pathname, attr_whitespace_rule)) {
- const char *value;
-
- value = attr_whitespace_rule->items[0].value;
- if (ATTR_TRUE(value)) {
- /* true (whitespace) */
- unsigned all_rule = ws_tab_width(whitespace_rule_cfg);
- int i;
- for (i = 0; i < ARRAY_SIZE(whitespace_rule_names); i++)
- if (!whitespace_rule_names[i].loosens_error &&
- !whitespace_rule_names[i].exclude_default)
- all_rule |= whitespace_rule_names[i].rule_bits;
- return all_rule;
- } else if (ATTR_FALSE(value)) {
- /* false (-whitespace) */
- return ws_tab_width(whitespace_rule_cfg);
- } else if (ATTR_UNSET(value)) {
- /* reset to default (!whitespace) */
- return whitespace_rule_cfg;
- } else {
- /* string */
- return parse_whitespace_rule(value);
- }
- } else {
+ git_check_attr(istate, pathname, attr_whitespace_rule);
+ value = attr_whitespace_rule->items[0].value;
+ if (ATTR_TRUE(value)) {
+ /* true (whitespace) */
+ unsigned all_rule = ws_tab_width(whitespace_rule_cfg);
+ int i;
+ for (i = 0; i < ARRAY_SIZE(whitespace_rule_names); i++)
+ if (!whitespace_rule_names[i].loosens_error &&
+ !whitespace_rule_names[i].exclude_default)
+ all_rule |= whitespace_rule_names[i].rule_bits;
+ return all_rule;
+ } else if (ATTR_FALSE(value)) {
+ /* false (-whitespace) */
+ return ws_tab_width(whitespace_rule_cfg);
+ } else if (ATTR_UNSET(value)) {
+ /* reset to default (!whitespace) */
return whitespace_rule_cfg;
+ } else {
+ /* string */
+ return parse_whitespace_rule(value);
}
}
d->worktree_status = p->status;
if (S_ISGITLINK(p->two->mode)) {
d->dirty_submodule = p->two->dirty_submodule;
- d->new_submodule_commits = !!oidcmp(&p->one->oid,
- &p->two->oid);
+ d->new_submodule_commits = !oideq(&p->one->oid,
+ &p->two->oid);
if (s->status_format == STATUS_FORMAT_SHORT)
d->worktree_status = short_submodule_status(d);
}
/* Leave {mode,oid}_head zero for an add. */
d->mode_index = p->two->mode;
oidcpy(&d->oid_index, &p->two->oid);
+ s->committable = 1;
break;
case DIFF_STATUS_DELETED:
d->mode_head = p->one->mode;
oidcpy(&d->oid_head, &p->one->oid);
+ s->committable = 1;
/* Leave {mode,oid}_index zero for a delete. */
break;
d->mode_index = p->two->mode;
oidcpy(&d->oid_head, &p->one->oid);
oidcpy(&d->oid_index, &p->two->oid);
+ s->committable = 1;
break;
case DIFF_STATUS_UNMERGED:
d->stagemask = unmerged_mask(p->two->path);
{
struct rev_info rev;
- init_revisions(&rev, NULL);
+ repo_init_revisions(the_repository, &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;
- init_revisions(&rev, NULL);
+ repo_init_revisions(the_repository, &rev, NULL);
memset(&opt, 0, sizeof(opt));
opt.def = s->is_initial ? empty_tree_oid_hex() : s->reference;
setup_revisions(0, NULL, &rev, &opt);
* code will output the stage values directly and not use the
* values in these fields.
*/
+ s->committable = 1;
} else {
d->index_status = DIFF_STATUS_ADDED;
/* Leave {mode,oid}_head zero for adds. */
d->mode_index = ce->ce_mode;
oidcpy(&d->oid_index, &ce->oid);
+ s->committable = 1;
}
}
}
s->untracked_in_ms = (getnanotime() - t_begin) / 1000000;
}
+static int has_unmerged(struct wt_status *s)
+{
+ int i;
+
+ for (i = 0; i < s->change.nr; i++) {
+ struct wt_status_change_data *d;
+ d = s->change.items[i].util;
+ if (d->stagemask)
+ return 1;
+ }
+ return 0;
+}
+
void wt_status_collect(struct wt_status *s)
{
wt_status_collect_changes_worktree(s);
-
if (s->is_initial)
wt_status_collect_changes_initial(s);
else
wt_status_collect_changes_index(s);
wt_status_collect_untracked(s);
+
+ wt_status_get_state(&s->state, s->branch && !strcmp(s->branch, "HEAD"));
+ if (s->state.merge_in_progress && !has_unmerged(s))
+ s->committable = 1;
+}
+
+void wt_status_collect_free_buffers(struct wt_status *s)
+{
+ free(s->state.branch);
+ free(s->state.onto);
+ free(s->state.detached_from);
}
static void wt_longstatus_print_unmerged(struct wt_status *s)
continue;
if (!shown_header) {
wt_longstatus_print_cached_header(s);
- s->commitable = 1;
shown_header = 1;
}
wt_longstatus_print_change_data(s, WT_STATUS_UPDATED, it);
int dirty_submodules;
const char *c = color(WT_STATUS_HEADER, s);
- init_revisions(&rev, NULL);
+ repo_init_revisions(the_repository, &rev, NULL);
rev.diffopt.flags.allow_textconv = 1;
rev.diffopt.ita_invisible_in_index = 1;
rev.diffopt.use_color = 0;
wt_status_add_cut_line(s->fp);
}
- if (s->verbose > 1 && s->commitable) {
+ if (s->verbose > 1 && s->committable) {
/* print_updated() printed a header, so do we */
if (s->fp != stdout)
wt_longstatus_print_trailer(s);
strbuf_release(&sb);
}
-static int has_unmerged(struct wt_status *s)
-{
- int i;
-
- for (i = 0; i < s->change.nr; i++) {
- struct wt_status_change_data *d;
- d = s->change.items[i].util;
- if (d->stagemask)
- return 1;
- }
- return 0;
-}
-
static void show_merge_in_progress(struct wt_status *s,
- struct wt_status_state *state,
- const char *color)
+ const char *color)
{
if (has_unmerged(s)) {
status_printf_ln(s, color, _("You have unmerged paths."));
_(" (use \"git merge --abort\" to abort the merge)"));
}
} else {
- s-> commitable = 1;
status_printf_ln(s, color,
_("All conflicts fixed but you are still merging."));
if (s->hints)
}
static void show_am_in_progress(struct wt_status *s,
- struct wt_status_state *state,
const char *color)
{
status_printf_ln(s, color,
_("You are in the middle of an am session."));
- if (state->am_empty_patch)
+ if (s->state.am_empty_patch)
status_printf_ln(s, color,
_("The current patch is empty."));
if (s->hints) {
- if (!state->am_empty_patch)
+ if (!s->state.am_empty_patch)
status_printf_ln(s, color,
_(" (fix conflicts and then run \"git am --continue\")"));
status_printf_ln(s, color,
}
static void show_rebase_information(struct wt_status *s,
- struct wt_status_state *state,
- const char *color)
+ const char *color)
{
- if (state->rebase_interactive_in_progress) {
+ if (s->state.rebase_interactive_in_progress) {
int i;
int nr_lines_to_show = 2;
}
static void print_rebase_state(struct wt_status *s,
- struct wt_status_state *state,
- const char *color)
+ const char *color)
{
- if (state->branch)
+ if (s->state.branch)
status_printf_ln(s, color,
_("You are currently rebasing branch '%s' on '%s'."),
- state->branch,
- state->onto);
+ s->state.branch,
+ s->state.onto);
else
status_printf_ln(s, color,
_("You are currently rebasing."));
}
static void show_rebase_in_progress(struct wt_status *s,
- struct wt_status_state *state,
- const char *color)
+ const char *color)
{
struct stat st;
- show_rebase_information(s, state, color);
+ show_rebase_information(s, color);
if (has_unmerged(s)) {
- print_rebase_state(s, state, color);
+ print_rebase_state(s, color);
if (s->hints) {
status_printf_ln(s, color,
_(" (fix conflicts and then run \"git rebase --continue\")"));
status_printf_ln(s, color,
_(" (use \"git rebase --abort\" to check out the original branch)"));
}
- } else if (state->rebase_in_progress || !stat(git_path_merge_msg(the_repository), &st)) {
- print_rebase_state(s, state, color);
+ } else if (s->state.rebase_in_progress ||
+ !stat(git_path_merge_msg(the_repository), &st)) {
+ print_rebase_state(s, color);
if (s->hints)
status_printf_ln(s, color,
_(" (all conflicts fixed: run \"git rebase --continue\")"));
} else if (split_commit_in_progress(s)) {
- if (state->branch)
+ if (s->state.branch)
status_printf_ln(s, color,
_("You are currently splitting a commit while rebasing branch '%s' on '%s'."),
- state->branch,
- state->onto);
+ s->state.branch,
+ s->state.onto);
else
status_printf_ln(s, color,
_("You are currently splitting a commit during a rebase."));
status_printf_ln(s, color,
_(" (Once your working directory is clean, run \"git rebase --continue\")"));
} else {
- if (state->branch)
+ if (s->state.branch)
status_printf_ln(s, color,
_("You are currently editing a commit while rebasing branch '%s' on '%s'."),
- state->branch,
- state->onto);
+ s->state.branch,
+ s->state.onto);
else
status_printf_ln(s, color,
_("You are currently editing a commit during a rebase."));
}
static void show_cherry_pick_in_progress(struct wt_status *s,
- struct wt_status_state *state,
- const char *color)
+ const char *color)
{
status_printf_ln(s, color, _("You are currently cherry-picking commit %s."),
- find_unique_abbrev(&state->cherry_pick_head_oid, DEFAULT_ABBREV));
+ find_unique_abbrev(&s->state.cherry_pick_head_oid, DEFAULT_ABBREV));
if (s->hints) {
if (has_unmerged(s))
status_printf_ln(s, color,
}
static void show_revert_in_progress(struct wt_status *s,
- struct wt_status_state *state,
- const char *color)
+ const char *color)
{
status_printf_ln(s, color, _("You are currently reverting commit %s."),
- find_unique_abbrev(&state->revert_head_oid, DEFAULT_ABBREV));
+ find_unique_abbrev(&s->state.revert_head_oid, DEFAULT_ABBREV));
if (s->hints) {
if (has_unmerged(s))
status_printf_ln(s, color,
}
static void show_bisect_in_progress(struct wt_status *s,
- struct wt_status_state *state,
- const char *color)
+ const char *color)
{
- if (state->branch)
+ if (s->state.branch)
status_printf_ln(s, color,
_("You are currently bisecting, started from branch '%s'."),
- state->branch);
+ s->state.branch);
else
status_printf_ln(s, color,
_("You are currently bisecting."));
if (dwim_ref(cb.buf.buf, cb.buf.len, &oid, &ref) == 1 &&
/* sha1 is a commit? match without further lookup */
- (!oidcmp(&cb.noid, &oid) ||
+ (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 &&
- !oidcmp(&cb.noid, &commit->object.oid)))) {
+ oideq(&cb.noid, &commit->object.oid)))) {
const char *from = ref;
if (!skip_prefix(from, "refs/tags/", &from))
skip_prefix(from, "refs/remotes/", &from);
xstrdup(find_unique_abbrev(&cb.noid, DEFAULT_ABBREV));
oidcpy(&state->detached_oid, &cb.noid);
state->detached_at = !get_oid("HEAD", &oid) &&
- !oidcmp(&oid, &state->detached_oid);
+ oideq(&oid, &state->detached_oid);
free(ref);
strbuf_release(&cb.buf);
wt_status_get_detached_from(state);
}
-static void wt_longstatus_print_state(struct wt_status *s,
- struct wt_status_state *state)
+static void wt_longstatus_print_state(struct wt_status *s)
{
const char *state_color = color(WT_STATUS_HEADER, s);
+ struct wt_status_state *state = &s->state;
+
if (state->merge_in_progress)
- show_merge_in_progress(s, state, state_color);
+ show_merge_in_progress(s, state_color);
else if (state->am_in_progress)
- show_am_in_progress(s, state, state_color);
+ show_am_in_progress(s, state_color);
else if (state->rebase_in_progress || state->rebase_interactive_in_progress)
- show_rebase_in_progress(s, state, state_color);
+ show_rebase_in_progress(s, state_color);
else if (state->cherry_pick_in_progress)
- show_cherry_pick_in_progress(s, state, state_color);
+ show_cherry_pick_in_progress(s, state_color);
else if (state->revert_in_progress)
- show_revert_in_progress(s, state, state_color);
+ show_revert_in_progress(s, state_color);
if (state->bisect_in_progress)
- show_bisect_in_progress(s, state, state_color);
+ show_bisect_in_progress(s, state_color);
}
static void wt_longstatus_print(struct wt_status *s)
{
const char *branch_color = color(WT_STATUS_ONBRANCH, s);
const char *branch_status_color = color(WT_STATUS_HEADER, s);
- struct wt_status_state state;
-
- memset(&state, 0, sizeof(state));
- wt_status_get_state(&state,
- s->branch && !strcmp(s->branch, "HEAD"));
if (s->branch) {
const char *on_what = _("On branch ");
const char *branch_name = s->branch;
if (!strcmp(branch_name, "HEAD")) {
branch_status_color = color(WT_STATUS_NOBRANCH, s);
- if (state.rebase_in_progress || state.rebase_interactive_in_progress) {
- if (state.rebase_interactive_in_progress)
+ if (s->state.rebase_in_progress ||
+ s->state.rebase_interactive_in_progress) {
+ if (s->state.rebase_interactive_in_progress)
on_what = _("interactive rebase in progress; onto ");
else
on_what = _("rebase in progress; onto ");
- branch_name = state.onto;
- } else if (state.detached_from) {
- branch_name = state.detached_from;
- if (state.detached_at)
+ branch_name = s->state.onto;
+ } else if (s->state.detached_from) {
+ branch_name = s->state.detached_from;
+ if (s->state.detached_at)
on_what = _("HEAD detached at ");
else
on_what = _("HEAD detached from ");
wt_longstatus_print_tracking(s);
}
- wt_longstatus_print_state(s, &state);
- free(state.branch);
- free(state.onto);
- free(state.detached_from);
+ wt_longstatus_print_state(s);
if (s->is_initial) {
status_printf_ln(s, color(WT_STATUS_HEADER, s), "%s", "");
"new files yourself (see 'git help status')."),
s->untracked_in_ms / 1000.0);
}
- } else if (s->commitable)
+ } else if (s->committable)
status_printf_ln(s, GIT_COLOR_NORMAL, _("Untracked files not listed%s"),
s->hints
? _(" (use -u option to show untracked files)") : "");
if (s->verbose)
wt_longstatus_print_verbose(s);
- if (!s->commitable) {
+ if (!s->committable) {
if (s->amend)
status_printf_ln(s, GIT_COLOR_NORMAL, _("No changes"));
else if (s->nowarn)
struct branch *branch;
const char *base;
const char *branch_name;
- struct wt_status_state state;
int ab_info, nr_ahead, nr_behind;
char eol = s->null_termination ? '\0' : '\n';
- memset(&state, 0, sizeof(state));
- wt_status_get_state(&state, s->branch && !strcmp(s->branch, "HEAD"));
-
fprintf(s->fp, "# branch.oid %s%c",
(s->is_initial ? "(initial)" : sha1_to_hex(s->sha1_commit)),
eol);
if (!strcmp(s->branch, "HEAD")) {
fprintf(s->fp, "# branch.head %s%c", "(detached)", eol);
- if (state.rebase_in_progress || state.rebase_interactive_in_progress)
- branch_name = state.onto;
- else if (state.detached_from)
- branch_name = state.detached_from;
+ if (s->state.rebase_in_progress ||
+ s->state.rebase_interactive_in_progress)
+ branch_name = s->state.onto;
+ else if (s->state.detached_from)
+ branch_name = s->state.detached_from;
else
branch_name = "";
} else {
}
}
}
-
- free(state.branch);
- free(state.onto);
- free(state.detached_from);
}
/*
struct rev_info rev_info;
int result;
- init_revisions(&rev_info, NULL);
+ repo_init_revisions(the_repository, &rev_info, NULL);
if (ignore_submodules) {
rev_info.diffopt.flags.ignore_submodules = 1;
rev_info.diffopt.flags.override_submodule_config = 1;
if (is_cache_unborn())
return 0;
- init_revisions(&rev_info, NULL);
+ repo_init_revisions(the_repository, &rev_info, NULL);
if (ignore_submodules)
rev_info.diffopt.flags.ignore_submodules = 1;
rev_info.diffopt.flags.quick = 1;
STATUS_FORMAT_UNSPECIFIED
};
+struct wt_status_state {
+ int merge_in_progress;
+ int am_in_progress;
+ int am_empty_patch;
+ int rebase_in_progress;
+ int rebase_interactive_in_progress;
+ int cherry_pick_in_progress;
+ int bisect_in_progress;
+ int revert_in_progress;
+ int detached_at;
+ char *branch;
+ char *onto;
+ char *detached_from;
+ struct object_id detached_oid;
+ struct object_id revert_head_oid;
+ struct object_id cherry_pick_head_oid;
+};
+
struct wt_status {
int is_initial;
char *branch;
int rename_score;
int rename_limit;
enum wt_status_format status_format;
+ struct wt_status_state state;
unsigned char sha1_commit[GIT_MAX_RAWSZ]; /* when not Initial */
/* These are computed during processing of the individual sections */
- int commitable;
+ int committable;
int workdir_dirty;
const char *index_file;
FILE *fp;
uint32_t untracked_in_ms;
};
-struct wt_status_state {
- int merge_in_progress;
- int am_in_progress;
- int am_empty_patch;
- int rebase_in_progress;
- int rebase_interactive_in_progress;
- int cherry_pick_in_progress;
- int bisect_in_progress;
- int revert_in_progress;
- int detached_at;
- char *branch;
- char *onto;
- char *detached_from;
- struct object_id detached_oid;
- struct object_id revert_head_oid;
- struct object_id cherry_pick_head_oid;
-};
-
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_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);
int wt_status_check_rebase(const struct worktree *wt,
struct wt_status_state *state);
unsigned long size;
enum object_type type;
- if (!oidcmp(oid, &null_oid)) {
+ if (oideq(oid, &null_oid)) {
ptr->ptr = xstrdup("");
ptr->size = 0;
return;