Doc update.
* tb/checkout-doc:
git-checkout.txt: document "git checkout <pathspec>" better
/test-delta
/test-dump-cache-tree
/test-dump-split-index
+/test-dump-untracked-cache
/test-scrap-cache-tree
/test-genrandom
/test-hashmap
--- /dev/null
+Git v2.4.5 Release Notes
+========================
+
+Fixes since v2.4.4
+------------------
+
+ * The setup code used to die when core.bare and core.worktree are set
+ inconsistently, even for commands that do not need working tree.
+
+ * There was a dead code that used to handle "git pull --tags" and
+ show special-cased error message, which was made irrelevant when
+ the semantics of the option changed back in Git 1.9 days.
+
+ * "color.diff.plain" was a misnomer; give it 'color.diff.context' as
+ a more logical synonym.
+
+ * The configuration reader/writer uses mmap(2) interface to access
+ the files; when we find a directory, it barfed with "Out of memory?".
+
+ * Recent "git prune" traverses young unreachable objects to safekeep
+ old objects in the reachability chain from them, which sometimes
+ showed unnecessary error messages that are alarming.
+
+ * "git rebase -i" fired post-rewrite hook when it shouldn't (namely,
+ when it was told to stop sequencing with 'exec' insn).
+
+Also contains typofixes, documentation updates and trivial code
+clean-ups.
--- /dev/null
+Git 2.5 Release Notes
+=====================
+
+Updates since v2.4
+------------------
+
+UI, Workflows & Features
+
+ * The bash completion script (in contrib/) learned a few options that
+ "git revert" takes.
+
+ * Whitespace breakages in deleted and context lines can also be
+ painted in the output of "git diff" and friends with the new
+ --ws-error-highlight option.
+
+ * List of commands shown by "git help" are grouped along the workflow
+ elements to help early learners.
+
+ * "git p4" now detects the filetype (e.g. binary) correctly even when
+ the files are opened exclusively.
+
+ * git p4 attempts to better handle branches in Perforce.
+
+ * "git p4" learned "--changes-block-size <n>" to read the changes in
+ chunks from Perforce, instead of making one call to "p4 changes"
+ that may trigger "too many rows scanned" error from Perforce.
+
+ * More workaround for Perforce's row number limit in "git p4".
+
+ * Unlike "$EDITOR" and "$GIT_EDITOR" that can hold the path to the
+ command and initial options (e.g. "/path/to/emacs -nw"), 'git p4'
+ did not let the shell interpolate the contents of the environment
+ variable that name the editor "$P4EDITOR" (and "$EDITOR", too).
+ This release makes it in line with the rest of Git, as well as with
+ Perforce.
+
+ * A new short-hand <branch>@{push} denotes the remote-tracking branch
+ that tracks the branch at the remote the <branch> would be pushed
+ to.
+
+ * "git show-branch --topics HEAD" (with no other arguments) did not
+ do anything interesting. Instead, contrast the given revision
+ against all the local branches by default.
+
+ * A replacement for contrib/workdir/git-new-workdir that does not
+ rely on symbolic links and make sharing of objects and refs safer
+ by making the borrowee and borrowers aware of each other.
+
+ Consider this as still an experimental feature; the UI will likely
+ to change.
+
+ * Tweak the sample "store" backend of the credential helper to honor
+ XDG configuration file locations when specified.
+
+ * A heuristic we use to catch mistyped paths on the command line
+ "git <cmd> <revs> <pathspec>" is to make sure that all the non-rev
+ parameters in the later part of the command line are names of the
+ files in the working tree, but that means "git grep $str -- \*.c"
+ must always be disambiguated with "--", because nobody sane will
+ create a file whose name literally is asterisk-dot-see. Loosen the
+ heuristic to declare that with a wildcard string the user likely
+ meant to give us a pathspec.
+
+ * "git merge FETCH_HEAD" learned that the previous "git fetch" could
+ be to create an Octopus merge, i.e. recording multiple branches
+ that are not marked as "not-for-merge"; this allows us to lose an
+ old style invocation "git merge <msg> HEAD $commits..." in the
+ implementation of "git pull" script; the old style syntax can now
+ be deprecated (but not removed yet).
+
+ * Filter scripts were run with SIGPIPE disabled on the Git side,
+ expecting that they may not read what Git feeds them to filter.
+ We however treated a filter that does not read its input fully
+ before exiting as an error. We no longer do and ignore EPIPE
+ when writing to feed the filter scripts.
+
+ This changes semantics, but arguably in a good way. If a filter
+ can produce its output without fully consuming its input using
+ whatever magic, we now let it do so, instead of diagnosing it
+ as a programming error.
+
+ * Instead of dying immediately upon failing to obtain a lock, the
+ locking (of refs etc) retries after a short while with backoff.
+
+ * Introduce http.<url>.SSLCipherList configuration variable to tweak
+ the list of cipher suite to be used with libcURL when talking with
+ https:// sites.
+
+ * "git subtree" script (in contrib/) used "echo -n" to produce
+ progress messages in a non-portable way.
+
+ * "git subtree" script (in contrib/) does not have --squash option
+ when pushing, but the documentation and help text pretended as if
+ it did.
+
+ * The Git subcommand completion (in contrib/) no longer lists credential
+ helpers among candidates; they are not something the end user would
+ invoke interactively.
+
+ * The index file can be taught with "update-index --untracked-cache"
+ to optionally remember already seen untracked files, in order to
+ speed up "git status" in a working tree with tons of cruft.
+
+ * "git mergetool" learned to drive WinMerge as a backend.
+
+ * "git upload-pack" that serves "git fetch" can be told to serve
+ commits that are not at the tip of any ref, as long as they are
+ reachable from a ref, with uploadpack.allowReachableSHA1InWant
+ configuration variable.
+
+ * "git cat-file --batch(-check)" learned the "--follow-symlinks"
+ option that follows an in-tree symbolic link when asked about an
+ object via extended SHA-1 syntax, e.g. HEAD:RelNotes that points at
+ Documentation/RelNotes/2.5.0.txt. With the new option, the command
+ behaves as if HEAD:Documentation/RelNotes/2.5.0.txt was given as
+ input instead.
+
+ Consider this as still an experimental and incomplete feature:
+
+ - We may want to do the same for in-index objects, e.g.
+ asking for :RelNotes with this option should give
+ :Documentation/RelNotes/2.5.0.txt, too
+
+ - "git cat-file --follow-symlinks blob HEAD:RelNotes"
+ may also be something we want to allow in the future.
+
+ * "git send-email" learned the alias file format used by the sendmail
+ program (in a simplified form; we obviously do not feed pipes).
+
+ * "git am" learned am.threeWay configuration variable.
+
+ * Traditionally, external low-level 3-way merge drivers are expected
+ to produce their results based solely on the contents of the three
+ variants given in temporary files named by %O, %A and %B on their
+ command line. Additionally allow them to look at the final path
+ (given by %P).
+
+ * "git blame" learned blame.showEmail configuration variable.
+
+ * "git apply" cannot diagnose a patch corruption when the breakage is
+ to mark the length of the hunk shorter than it really is on the
+ hunk header line "@@ -l,k +m,n @@"; one special case it could is
+ when the hunk becomes no-op (e.g. k == n == 2 for two-line context
+ patch output), and it learned to do so in this special case.
+
+ * Add the "--allow-unknown-type" option to "cat-file" to allow
+ inspecting loose objects of an experimental or a broken type.
+
+ * Many long-running operations show progress eye-candy, even when
+ they are later backgrounded. Hide the eye-candy when the process
+ is sent to the background instead.
+ (merge a4fb76c lm/squelch-bg-progress later to maint).
+
+
+Performance, Internal Implementation, Development Support etc.
+
+ * "unsigned char [20]" used throughout the code to represent object
+ names are being converted into a semi-opaque "struct object_id".
+ This effort is expected to interfere with other topics in flight,
+ but hopefully will give us one extra level of abstraction in the
+ end, when completed.
+
+ * for_each_ref() callback functions were taught to name the objects
+ not with "unsigned char sha1[20]" but with "struct object_id".
+
+ * Catch a programmer mistake to feed a pointer not an array to
+ ARRAY_SIZE() macro, by using a couple of GCC extensions.
+
+ * Some error messages in "git config" were emitted without calling
+ the usual error() facility.
+
+ * When "add--interactive" splits a hunk into two overlapping hunks
+ and then let the user choose only one, it sometimes feeds an
+ incorrect patch text to "git apply". Add tests to demonstrate
+ this.
+
+ I have a slight suspicion that this may be $gmane/87202 coming back
+ and biting us (I seem to have said "let's run with this and see
+ what happens" back then).
+
+ * More line-ending tests.
+
+ * An earlier rewrite to use strbuf_getwholeline() instead of fgets(3)
+ to read packed-refs file revealed that the former is unacceptably
+ inefficient. It has been optimized by using getdelim(3) when
+ available.
+
+ * The refs API uses ref_lock struct which had its own "int fd", even
+ though the same file descriptor was in the lock struct it contains.
+ Clean-up the code to lose this redundant field.
+
+ * There was a dead code that used to handle "git pull --tags" and
+ show special-cased error message, which was made irrelevant when
+ the semantics of the option changed back in Git 1.9 days.
+ (merge 19d122b pt/pull-tags-error-diag later to maint).
+
+ * Help us to find broken test script that splits the body part of the
+ test by mistaken use of wrong kind of quotes.
+ (merge d93d5d5 jc/test-prereq-validate later to maint).
+
+ * Developer support to automatically detect broken &&-chain in the
+ test scripts is now turned on by default.
+ (merge 92b269f jk/test-chain-lint later to maint).
+
+ * Error reporting mechanism used in "refs" API has been made more
+ consistent.
+
+ * "git pull" has more test coverage now.
+
+ * "git pull" has become more aware of the options meant for
+ underlying "git fetch" and then learned to use parse-options
+ parser.
+
+Also contains various documentation updates and code clean-ups.
+
+
+Fixes since v2.4
+----------------
+
+Unless otherwise noted, all the fixes since v2.4 in the maintenance
+track are contained in this release (see the maintenance releases'
+notes for details).
+
+ * Git 2.4 broke setting verbosity and progress levels on "git clone"
+ with native transports.
+ (merge 822f0c4 mh/clone-verbosity-fix later to maint).
+
+ * "git add -e" did not allow the user to abort the operation by
+ killing the editor.
+ (merge cb64800 jk/add-e-kill-editor later to maint).
+
+ * Memory usage of "git index-pack" has been trimmed by tens of
+ per-cent.
+ (merge f0e7f11 nd/slim-index-pack-memory-usage later to maint).
+
+ * "git rev-list --objects $old --not --all" to see if everything that
+ is reachable from $old is already connected to the existing refs
+ was very inefficient.
+ (merge b6e8a3b jk/still-interesting later to maint).
+
+ * "hash-object --literally" introduced in v2.2 was not prepared to
+ take a really long object type name.
+ (merge 1427a7f jc/hash-object later to maint).
+
+ * "git rebase --quiet" was not quite quiet when there is nothing to
+ do.
+ (merge 22946a9 jk/rebase-quiet-noop later to maint).
+
+ * The completion for "log --decorate=" parameter value was incorrect.
+ (merge af16bda sg/complete-decorate-full-not-long later to maint).
+
+ * "filter-branch" corrupted commit log message that ends with an
+ incomplete line on platforms with some "sed" implementations that
+ munge such a line. Work it around by avoiding to use "sed".
+ (merge df06201 jk/filter-branch-use-of-sed-on-incomplete-line later to maint).
+
+ * "git daemon" fails to build from the source under NO_IPV6
+ configuration (regression in 2.4).
+ (merge d358f77 jc/daemon-no-ipv6-for-2.4.1 later to maint).
+
+ * Some time ago, "git blame" (incorrectly) lost the convert_to_git()
+ call when synthesizing a fake "tip" commit that represents the
+ state in the working tree, which broke folks who record the history
+ with LF line ending to make their project portable across platforms
+ while terminating lines in their working tree files with CRLF for
+ their platform.
+ (merge 4bf256d tb/blame-resurrect-convert-to-git later to maint).
+
+ * We avoid setting core.worktree when the repository location is the
+ ".git" directory directly at the top level of the working tree, but
+ the code misdetected the case in which the working tree is at the
+ root level of the filesystem (which arguably is a silly thing to
+ do, but still valid).
+ (merge 84ccad8 jk/init-core-worktree-at-root later to maint).
+
+ * "git commit --date=now" or anything that relies on approxidate lost
+ the daylight-saving-time offset.
+ (merge f6e6362 jc/epochtime-wo-tz later to maint).
+
+ * Access to objects in repositories that borrow from another one on a
+ slow NFS server unnecessarily got more expensive due to recent code
+ becoming more cautious in a naive way not to lose objects to pruning.
+ (merge ee1c6c3 jk/prune-mtime later to maint).
+
+ * The codepaths that read .gitignore and .gitattributes files have been
+ taught that these files encoded in UTF-8 may have UTF-8 BOM marker at
+ the beginning; this makes it in line with what we do for configuration
+ files already.
+ (merge 27547e5 cn/bom-in-gitignore later to maint).
+
+ * a few helper scripts in the test suite did not report errors
+ correctly.
+ (merge de248e9 ep/fix-test-lib-functions-report later to maint).
+
+ * The default $HOME/.gitconfig file created upon "git config --global"
+ that edits it had incorrectly spelled user.name and user.email
+ entries in it.
+ (merge 7e11052 oh/fix-config-default-user-name-section later to maint).
+
+ * "git cat-file bl $blob" failed to barf even though there is no
+ object type that is "bl".
+ (merge b7994af jk/type-from-string-gently later to maint).
+
+ * The usual "git diff" when seeing a file turning into a directory
+ showed a patchset to remove the file and create all files in the
+ directory, but "git diff --no-index" simply refused to work. Also,
+ when asked to compare a file and a directory, imitate POSIX "diff"
+ and compare the file with the file with the same name in the
+ directory, instead of refusing to run.
+ (merge 0615173 jc/diff-no-index-d-f later to maint).
+
+ * "git rebase -i" moved the "current" command from "todo" to "done" a
+ bit too prematurely, losing a step when a "pick" did not even start.
+ (merge 8cbc57c ph/rebase-i-redo later to maint).
+
+ * The connection initiation code for "ssh" transport tried to absorb
+ differences between the stock "ssh" and Putty-supplied "plink" and
+ its derivatives, but the logic to tell that we are using "plink"
+ variants were too loose and falsely triggered when "plink" appeared
+ anywhere in the path (e.g. "/home/me/bin/uplink/ssh").
+ (merge baaf233 bc/connect-plink later to maint).
+
+ * We have prepended $GIT_EXEC_PATH and the path "git" is installed in
+ (typically "/usr/bin") to $PATH when invoking subprograms and hooks
+ for almost eternity, but the original use case the latter tried to
+ support was semi-bogus (i.e. install git to /opt/foo/git and run it
+ without having /opt/foo on $PATH), and more importantly it has
+ become less and less relevant as Git grew more mainstream (i.e. the
+ users would _want_ to have it on their $PATH). Stop prepending the
+ path in which "git" is installed to users' $PATH, as that would
+ interfere the command search order people depend on (e.g. they may
+ not like versions of programs that are unrelated to Git in /usr/bin
+ and want to override them by having different ones in /usr/local/bin
+ and have the latter directory earlier in their $PATH).
+ (merge a0b4507 jk/git-no-more-argv0-path-munging later to maint).
+
+ * core.excludesfile (defaulting to $XDG_HOME/git/ignore) is supposed
+ to be overridden by repository-specific .git/info/exclude file, but
+ the order was swapped from the beginning. This belatedly fixes it.
+ (merge 099d2d8 jc/gitignore-precedence later to maint).
+
+ * There was a commented-out (instead of being marked to expect
+ failure) test that documented a breakage that was fixed since the
+ test was written; turn it into a proper test.
+ (merge 66d2e04 sb/t1020-cleanup later to maint).
+
+ * The "log --decorate" enhancement in Git 2.4 that shows the commit
+ at the tip of the current branch e.g. "HEAD -> master", did not
+ work with --decorate=full.
+ (merge 429ad20 mg/log-decorate-HEAD later to maint).
+
+ * The ref API did not handle cases where 'refs/heads/xyzzy/frotz' is
+ removed at the same time as 'refs/heads/xyzzy' is added (or vice
+ versa) very well.
+ (merge c628edf mh/ref-directory-file later to maint).
+
+ * Multi-ref transaction support we merged a few releases ago
+ unnecessarily kept many file descriptors open, risking to fail with
+ resource exhaustion. This is for 2.4.x track.
+ (merge 185ce3a mh/write-refs-sooner-2.4 later to maint).
+
+ * "git bundle verify" did not diagnose extra parameters on the
+ command line.
+ (merge 7886cfa ps/bundle-verify-arg later to maint).
+
+ * Various documentation mark-up fixes to make the output more
+ consistent in general and also make AsciiDoctor (an alternative
+ formatter) happier.
+ (merge d0258b9 jk/asciidoc-markup-fix later to maint).
+ (merge ad3967a jk/stripspace-asciidoctor-fix later to maint).
+ (merge 975e382 ja/tutorial-asciidoctor-fix later to maint).
+
+ * The code to read pack-bitmap wanted to allocate a few hundred
+ pointers to a structure, but by mistake allocated and leaked memory
+ enough to hold that many actual structures. Correct the allocation
+ size and also have it on stack, as it is small enough.
+ (merge 599dc76 rs/plug-leak-in-pack-bitmaps later to maint).
+
+ * The pull.ff configuration was supposed to override the merge.ff
+ configuration, but it didn't.
+ (merge db9bb28 pt/pull-ff-vs-merge-ff later to maint).
+
+ * "git pull --log" and "git pull --no-log" worked as expected, but
+ "git pull --log=20" did not.
+ (merge 5061a44 pt/pull-log-n later to maint).
+
+ * "git rerere forget" in a repository without rerere enabled gave a
+ cryptic error message; it should be a silent no-op instead.
+ (merge 0544574 jk/rerere-forget-check-enabled later to maint).
+
+ * "git rebase -i" fired post-rewrite hook when it shouldn't (namely,
+ when it was told to stop sequencing with 'exec' insn).
+ (merge 141ff8f mm/rebase-i-post-rewrite-exec later to maint).
+
+ * Clarify that "log --raw" and "log --format=raw" are unrelated
+ concepts.
+ (merge 92de921 mm/log-format-raw-doc later to maint).
+
+ * Make "git stash something --help" error out, so that users can
+ safely say "git stash drop --help".
+ (merge 5ba2831 jk/stash-options later to maint).
+
+ * The clean/smudge interface did not work well when filtering an
+ empty contents (failed and then passed the empty input through).
+ It can be argued that a filter that produces anything but empty for
+ an empty input is nonsense, but if the user wants to do strange
+ things, then why not?
+ (merge f6a1e1e jh/filter-empty-contents later to maint).
+
+ * Communication between the HTTP server and http_backend process can
+ lead to a dead-lock when relaying a large ref negotiation request.
+ Diagnose the situation better, and mitigate it by reading such a
+ request first into core (to a reasonable limit).
+ (merge 636614f jk/http-backend-deadlock later to maint).
+
+ * "git clean pathspec..." tried to lstat(2) and complain even for
+ paths outside the given pathspec.
+ (merge 838d6a9 dt/clean-pathspec-filter-then-lstat later to maint).
+
+ * Recent "git prune" traverses young unreachable objects to safekeep
+ old objects in the reachability chain from them, which sometimes
+ caused error messages that are unnecessarily alarming.
+ (merge ce4e7b2 jk/squelch-missing-link-warning-for-unreachable later to maint).
+
+ * The configuration reader/writer uses mmap(2) interface to access
+ the files; when we find a directory, it barfed with "Out of memory?".
+ (merge 9ca0aaf jk/diagnose-config-mmap-failure later to maint).
+
+ * "color.diff.plain" was a misnomer; give it 'color.diff.context' as
+ a more logical synonym.
+ (merge 8dbf3eb jk/color-diff-plain-is-context later to maint).
+
+ * The setup code used to die when core.bare and core.worktree are set
+ inconsistently, even for commands that do not need working tree.
+ (merge fada767 jk/die-on-bogus-worktree-late later to maint).
+
+ * Recent Mac OS X updates breaks the logic to detect that the machine
+ is on the AC power in the sample pre-auto-gc script.
+ (merge c54c7b3 pa/auto-gc-mac-osx later to maint).
+
+ * "git commit --cleanup=scissors" was not careful enough to protect
+ against getting fooled by a line that looked like scissors.
+ (merge fbfa097 sg/commit-cleanup-scissors later to maint).
+
+ * "Have we lost a race with competing repack?" check was too
+ expensive, especially while receiving a huge object transfer
+ that runs index-pack (e.g. "clone" or "fetch").
+ (merge 0eeb077 jk/index-pack-reduce-recheck later to maint).
+
+ * The tcsh completion writes a bash scriptlet but that would have
+ failed for users with noclobber set.
+ (merge 0b1f688 af/tcsh-completion-noclobber later to maint).
+
+ * "git for-each-ref" reported "missing object" for 0{40} when it
+ encounters a broken ref. The lack of object whose name is 0{40} is
+ not the problem; the ref being broken is.
+ (merge 501cf47 mh/reporting-broken-refs-from-for-each-ref later to maint).
+
+ * Various fixes around "git am" that applies a patch to a history
+ that is not there yet.
+ (merge 6ea3b67 pt/am-abort-fix later to maint).
+
+ * "git fsck" used to ignore missing or invalid objects recorded in reflog.
+ (merge 19bf6c9 mh/fsck-reflog-entries later to maint).
+
+ * "git format-patch --ignore-if-upstream A..B" did not like to be fed
+ tags as boundary commits.
+ (merge 9b7a61d jc/do-not-feed-tags-to-clear-commit-marks later to maint).
+
+ * "git fetch --depth=<depth>" and "git clone --depth=<depth>" issued
+ a shallow transfer request even to an upload-pack that does not
+ support the capability.
+ (merge eb86a50 me/fetch-into-shallow-safety later to maint).
+
+ * Code cleanups and documentation updates.
+ (merge 0269f96 mm/usage-log-l-can-take-regex later to maint).
+ (merge 64f2589 nd/t1509-chroot-test later to maint).
+ (merge d201a1e sb/test-bitmap-free-at-end later to maint).
+ (merge 05bfc7d sb/line-log-plug-pairdiff-leak later to maint).
+ (merge 846e5df pt/xdg-config-path later to maint).
+ (merge 1154aa4 jc/plug-fmt-merge-msg-leak later to maint).
+ (merge 319b678 jk/sha1-file-reduce-useless-warnings later to maint).
+ (merge 9a35c14 fg/document-commit-message-stripping later to maint).
+ (merge bbf431c ps/doc-packfile-vs-pack-file later to maint).
+ (merge 309a9e3 jk/skip-http-tests-under-no-curl later to maint).
+ (merge ccd593c dl/branch-error-message later to maint).
+ (merge 22570b6 rs/janitorial later to maint).
+ (merge 5c2a581 mc/commit-doc-grammofix later to maint).
+ (merge ce41720 ah/usage-strings later to maint).
+ (merge e6a268c sb/glossary-submodule later to maint).
+ (merge ec48a76 sb/submodule-doc-intro later to maint).
+ (merge 14f8b9b jk/clone-dissociate later to maint).
+ (merge 055c7e9 sb/pack-protocol-mention-smart-http later to maint).
+ (merge 7c37a5d jk/make-fix-dependencies later to maint).
+ (merge fc0aa39 sg/merge-summary-config later to maint).
+ (merge 329af6c pt/t0302-needs-sanity later to maint).
+ (merge d614f07 fk/doc-format-patch-vn later to maint).
+ (merge 72dbb36 sg/completion-commit-cleanup later to maint).
+ (merge e654eb2 es/utf8-stupid-compiler-workaround later to maint).
+ (merge 34b935c es/osx-header-pollutes-mask-macro later to maint).
+ (merge ab7fade jc/prompt-document-ps1-state-separator later to maint).
+ (merge 25f600e mm/describe-doc later to maint).
}
}
+while (<>) {
+ last if /^### command list/;
+}
+
my %cmds = ();
for (sort <>) {
next if /^#/;
core.worktree::
Set the path to the root of the working tree.
+ If GIT_COMMON_DIR environment variable is set, core.worktree
+ is ignored and not used for determining the root of working tree.
This can be overridden by the GIT_WORK_TREE environment
variable and the '--work-tree' command-line option.
The value can be an absolute path or relative to the path to
If set to "auto", `git-commit` would select a character that is not
the beginning character of any line in existing commit messages.
+core.packedRefsTimeout::
+ The length of time, in milliseconds, to retry when trying to
+ lock the `packed-refs` file. Value 0 means not to retry at
+ 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.
by giving '--no-keep-cr' from the command line.
See linkgit:git-am[1], linkgit:git-mailsplit[1].
+am.threeWay::
+ By default, `git am` will fail if the patch does not apply cleanly. When
+ set to true, this setting tells `git am` to fall back on 3-way merge if
+ the patch records the identity of blobs it is supposed to apply to and
+ we have those blobs available locally (equivalent to giving the `--3way`
+ option from the command line). Defaults to `false`.
+ See linkgit:git-am[1].
+
apply.ignoreWhitespace::
When set to 'change', tells 'git apply' to ignore changes in
whitespace, in the same way as the '--ignore-space-change'
color.diff.<slot>::
Use customized color for diff colorization. `<slot>` specifies
which part of the patch to use the specified color, and is one
- of `plain` (context text), `meta` (metainformation), `frag`
+ of `context` (context text - `plain` is a historical synonym),
+ `meta` (metainformation), `frag`
(hunk header), 'func' (function in hunk header), `old` (removed lines),
`new` (added lines), `commit` (commit headers), or `whitespace`
(highlighting whitespace errors).
"now" may be used to disable this grace period and always prune
unreachable objects immediately.
+gc.pruneWorktreesExpire::
+ When 'git gc' is run, it will call
+ 'prune --worktrees --expire 3.months.ago'.
+ Override the grace period with this config variable. The value
+ "now" may be used to disable the grace period and prune
+ $GIT_DIR/worktrees immediately.
+
gc.reflogExpire::
gc.<pattern>.reflogExpire::
'git reflog expire' removes reflog entries older than
If set, store cookies received during requests to the file specified by
http.cookieFile. Has no effect if http.cookieFile is unset.
+http.sslCipherList::
+ A list of SSL ciphers to use when negotiating an SSL connection.
+ The available ciphers depend on whether libcurl was built against
+ NSS or OpenSSL and the particular configuration of the crypto
+ library in use. Internally this sets the 'CURLOPT_SSL_CIPHER_LIST'
+ option; see the libcurl documentation for more details on the format
+ of this list.
++
+Can be overridden by the 'GIT_SSL_CIPHER_LIST' environment variable.
+To force git to use libcurl's default cipher list and ignore any
+explicit http.sslCipherList option, set 'GIT_SSL_CIPHER_LIST' to the
+empty string.
+
http.sslVerify::
Whether to verify the SSL certificate when fetching or pushing
over HTTPS. Can be overridden by the 'GIT_SSL_NO_VERIFY' environment
are under the hierarchies listed on the value of this
variable is excluded, and is hidden from `git ls-remote`,
`git fetch`, etc. An attempt to fetch a hidden ref by `git
- fetch` will fail. See also `uploadpack.allowtipsha1inwant`.
+ fetch` will fail. See also `uploadpack.allowTipSHA1InWant`.
-uploadpack.allowtipsha1inwant::
+uploadpack.allowTipSHA1InWant::
When `uploadpack.hideRefs` is in effect, allow `upload-pack`
to accept a fetch request that asks for an object at the tip
of a hidden ref (by default, such a request is rejected).
see also `uploadpack.hideRefs`.
+uploadpack.allowReachableSHA1InWant::
+ Allow `upload-pack` to accept a fetch request that asks for an
+ object that is reachable from any ref tip. However, note that
+ calculating object reachability is computationally expensive.
+ Defaults to `false`.
+
uploadpack.keepAlive::
When `upload-pack` has started `pack-objects`, there may be a
quiet period while `pack-objects` prepares the pack. Normally
initial indent of the line are considered whitespace errors.
Exits with non-zero status if problems are found. Not compatible
with --exit-code.
+
+--ws-error-highlight=<kind>::
+ Highlight whitespace errors on lines specified by <kind>
+ in the color specified by `color.diff.whitespace`. <kind>
+ is a comma separated list of `old`, `new`, `context`. When
+ this option is not given, only whitespace errors in `new`
+ lines are highlighted. E.g. `--ws-error-highlight=new,old`
+ highlights whitespace errors on both deleted and added lines.
+ `all` can be used as a short-hand for `old,new,context`.
+
endif::git-format-patch[]
--full-index::
--- /dev/null
+merge.branchdesc::
+ In addition to branch names, populate the log message with
+ the branch description text associated with them. Defaults
+ to false.
+
+merge.log::
+ In addition to branch names, populate the log message with at
+ most the specified number of one-line descriptions from the
+ actual commits that are being merged. Defaults to false, and
+ true is a synonym for 20.
--------
[verse]
'git am' [--signoff] [--keep] [--[no-]keep-cr] [--[no-]utf8]
- [--3way] [--interactive] [--committer-date-is-author-date]
+ [--[no-]3way] [--interactive] [--committer-date-is-author-date]
[--ignore-date] [--ignore-space-change | --ignore-whitespace]
[--whitespace=<option>] [-C<n>] [-p<n>] [--directory=<dir>]
[--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet]
-3::
--3way::
+--no-3way::
When the patch does not apply cleanly, fall back on
3-way merge if the patch records the identity of blobs
it is supposed to apply to and we have those blobs
- available locally.
+ available locally. `--no-3way` can be used to override
+ am.threeWay configuration variable. For more information,
+ see am.threeWay in linkgit:git-config[1].
--ignore-space-change::
--ignore-whitespace::
-e::
--show-email::
Show the author email instead of author name (Default: off).
+ This can also be controlled via the `blame.showEmail` config
+ option.
-w::
Ignore whitespace when comparing the parent's version and
SYNOPSIS
--------
[verse]
-'git cat-file' (-t | -s | -e | -p | <type> | --textconv ) <object>
-'git cat-file' (--batch | --batch-check) < <list-of-objects>
+'git cat-file' (-t [--allow-unknown-type]| -s [--allow-unknown-type]| -e | -p | <type> | --textconv ) <object>
+'git cat-file' (--batch | --batch-check) [--follow-symlinks] < <list-of-objects>
DESCRIPTION
-----------
not be combined with any other options or arguments. See the
section `BATCH OUTPUT` below for details.
+--allow-unknown-type::
+ Allow -s or -t to query broken/corrupt objects of unknown type.
+
+--follow-symlinks::
+ With --batch or --batch-check, follow symlinks inside the
+ repository when requesting objects with extended SHA-1
+ expressions of the form tree-ish:path-in-tree. Instead of
+ providing output about the link itself, provide output about
+ the linked-to object. If a symlink points outside the
+ tree-ish (e.g. a link to /foo or a root-level link to ../foo),
+ the portion of the link which is outside the tree will be
+ printed.
++
+This option does not (currently) work correctly when an object in the
+index is specified (e.g. `:link` instead of `HEAD:link`) rather than
+one in the tree.
++
+This option cannot (currently) be used unless `--batch` or
+`--batch-check` is used.
++
+For example, consider a git repository containing:
++
+--
+ f: a file containing "hello\n"
+ link: a symlink to f
+ dir/link: a symlink to ../f
+ plink: a symlink to ../f
+ alink: a symlink to /etc/passwd
+--
++
+For a regular file `f`, `echo HEAD:f | git cat-file --batch` would print
++
+--
+ ce013625030ba8dba906f756967f9e9ca394464a blob 6
+--
++
+And `echo HEAD:link | git cat-file --batch --follow-symlinks` would
+print the same thing, as would `HEAD:dir/link`, as they both point at
+`HEAD:f`.
++
+Without `--follow-symlinks`, these would print data about the symlink
+itself. In the case of `HEAD:link`, you would see
++
+--
+ 4d1ae35ba2c8ec712fa2a379db44ad639ca277bd blob 1
+--
++
+Both `plink` and `alink` point outside the tree, so they would
+respectively print:
++
+--
+ symlink 4
+ ../f
+
+ symlink 11
+ /etc/passwd
+--
+
+
OUTPUT
------
If '-t' is specified, one of the <type>.
<object> SP missing LF
------------
+If --follow-symlinks is used, and a symlink in the repository points
+outside the repository, then `cat-file` will ignore any custom format
+and print:
+
+------------
+symlink SP <size> LF
+<symlink> LF
+------------
+
+The symlink will either be absolute (beginning with a /), or relative
+to the tree root. For instance, if dir/link points to ../../foo, then
+<symlink> will be ../foo. <size> is the size of the symlink in bytes.
+
+If --follow-symlinks is used, the following error messages will be
+displayed:
+
+------------
+<object> SP missing LF
+------------
+is printed when the initial symlink requested does not exist.
+
+------------
+dangling SP <size> LF
+<object> LF
+------------
+is printed when the initial symlink exists, but something that
+it (transitive-of) points to does not.
+
+------------
+loop SP <size> LF
+<object> LF
+------------
+is printed for symlink loops (or any symlinks that
+require more than 40 link resolutions to resolve).
+
+------------
+notdir SP <size> LF
+<object> LF
+------------
+is printed when, during symlink resolution, a file is used as a
+directory name.
CAVEATS
-------
edits from your current working tree. See the ``Interactive Mode''
section of linkgit:git-add[1] to learn how to operate the `--patch` mode.
+--to=<path>::
+ Check out a branch in a separate working directory at
+ `<path>`. A new working directory is linked to the current
+ repository, sharing everything except working directory
+ specific files such as HEAD, index... See "MULTIPLE WORKING
+ TREES" section for more information.
+
+--ignore-other-worktrees::
+ `git checkout` refuses when the wanted ref is already checked
+ out by another worktree. This option makes it check the ref
+ out anyway. In other words, the ref can be held by more than one
+ worktree.
+
<branch>::
Branch to checkout; if it refers to a branch (i.e., a name that,
when prepended with "refs/heads/", is a valid ref), then that
$ git log -g -2 HEAD
------------
+MULTIPLE WORKING TREES
+----------------------
+
+A git repository can support multiple working trees, allowing you to check
+out more than one branch at a time. With `git checkout --to` a new working
+tree is associated with the repository. This new working tree is called a
+"linked working tree" as opposed to the "main working tree" prepared by "git
+init" or "git clone". A repository has one main working tree (if it's not a
+bare repository) and zero or more linked working trees.
+
+Each linked working tree has a private sub-directory in the repository's
+$GIT_DIR/worktrees directory. The private sub-directory's name is usually
+the base name of the linked working tree's path, possibly appended with a
+number to make it unique. For example, when `$GIT_DIR=/path/main/.git` the
+command `git checkout --to /path/other/test-next next` creates the linked
+working tree in `/path/other/test-next` and also creates a
+`$GIT_DIR/worktrees/test-next` directory (or `$GIT_DIR/worktrees/test-next1`
+if `test-next` is already taken).
+
+Within a linked working tree, $GIT_DIR is set to point to this private
+directory (e.g. `/path/main/.git/worktrees/test-next` in the example) and
+$GIT_COMMON_DIR is set to point back to the main working tree's $GIT_DIR
+(e.g. `/path/main/.git`). These settings are made in a `.git` file located at
+the top directory of the linked working tree.
+
+Path resolution via `git rev-parse --git-path` uses either
+$GIT_DIR or $GIT_COMMON_DIR depending on the path. For example, in the
+linked working tree `git rev-parse --git-path HEAD` returns
+`/path/main/.git/worktrees/test-next/HEAD` (not
+`/path/other/test-next/.git/HEAD` or `/path/main/.git/HEAD`) while `git
+rev-parse --git-path refs/heads/master` uses
+$GIT_COMMON_DIR and returns `/path/main/.git/refs/heads/master`,
+since refs are shared across all working trees.
+
+See linkgit:gitrepository-layout[5] for more information. The rule of
+thumb is do not make any assumption about whether a path belongs to
+$GIT_DIR or $GIT_COMMON_DIR when you need to directly access something
+inside $GIT_DIR. Use `git rev-parse --git-path` to get the final path.
+
+When you are done with a linked working tree you can simply delete it.
+The working tree's entry in the repository's $GIT_DIR/worktrees
+directory will eventually be removed automatically (see
+`gc.pruneworktreesexpire` in linkgit::git-config[1]), or you can run
+`git prune --worktrees` in the main or any linked working tree to
+clean up any stale entries in $GIT_DIR/worktrees.
+
+If you move a linked working directory to another file system, or
+within a file system that does not support hard links, you need to run
+at least one git command inside the linked working directory
+(e.g. `git status`) in order to update its entry in $GIT_DIR/worktrees
+so that it does not get automatically removed.
+
+To prevent a $GIT_DIR/worktrees entry from from being pruned (which
+can be useful in some situations, such as when the
+entry's working tree is stored on a portable device), add a file named
+'locked' to the entry's directory. The file contains the reason in
+plain text. For example, if a linked working tree's `.git` file points
+to `/path/main/.git/worktrees/test-next` then a file named
+`/path/main/.git/worktrees/test-next/locked` will prevent the
+`test-next` entry from being pruned. See
+linkgit:gitrepository-layout[5] for details.
+
+Multiple checkout support for submodules is incomplete. It is NOT
+recommended to make multiple checkouts of a superproject.
+
EXAMPLES
--------
NAME
----
-git-describe - Show the most recent tag that is reachable from a commit
+git-describe - Describe a commit using the most recent tag reachable from it
SYNOPSIS
CONFIGURATION
-------------
-
-merge.branchdesc::
- In addition to branch names, populate the log message with
- the branch description text associated with them. Defaults
- to false.
-
-merge.log::
- In addition to branch names, populate the log message with at
- most the specified number of one-line descriptions from the
- actual commits that are being merged. Defaults to false, and
- true is a synonym for 20.
+include::fmt-merge-msg-config.txt[]
merge.summary::
Synonym to `merge.log`; this is deprecated and will be removed in
or "=" (in sync). Has no effect if the ref does not have
tracking information associated with it.
+push::
+ The name of a local ref which represents the `@{push}` location
+ for the displayed ref. Respects `:short`, `:track`, and
+ `:trackshort` options as `upstream` does. Produces an empty
+ string if no `@{push}` ref is configured.
+
HEAD::
'*' if HEAD matches current ref (the checked out branch), ' '
otherwise.
-v <n>::
--reroll-count=<n>::
Mark the series as the <n>-th iteration of the topic. The
- output filenames have `v<n>` pretended to them, and the
+ output filenames have `v<n>` prepended to them, and the
subject prefix ("PATCH" by default, but configurable via the
`--subject-prefix` option) has ` v<n>` appended to it. E.g.
`--reroll-count=4` may produce `v4-0001-add-makefile.patch`
If no commit is given from the command line, merge the remote-tracking
branches that the current branch is configured to use as its upstream.
See also the configuration section of this manual page.
++
+When `FETCH_HEAD` (and no other commit) is specified, the branches
+recorded in the `.git/FETCH_HEAD` file by the previous invocation
+of `git fetch` for merging are merged to the current branch.
PRE-MERGE CHECKS
they can find the p4 branches in refs/heads.
--max-changes <n>::
- Limit the number of imported changes to 'n'. Useful to
- limit the amount of history when using the '@all' p4 revision
- specifier.
+ Import at most 'n' changes, rather than the entire range of
+ changes included in the given revision specifier. A typical
+ usage would be use '@all' as the revision specifier, but then
+ to use '--max-changes 1000' to import only the last 1000
+ revisions rather than the entire revision history.
+
+--changes-block-size <n>::
+ The internal block size to use when converting a revision
+ specifier such as '@all' into a list of specific change
+ numbers. Instead of using a single call to 'p4 changes' to
+ find the full list of changes for the conversion, there are a
+ sequence of calls to 'p4 changes -m', each of which requests
+ one block of changes of the given size. The default block size
+ is 500, which should usually be suitable.
--keep-path::
The mapping of file names from the p4 depot path to Git, by
--expire <time>::
Only expire loose objects older than <time>.
+--worktrees::
+ Prune dead working tree information in $GIT_DIR/worktrees.
+
<head>...::
In addition to objects
reachable from any of our references, keep objects
OPTIONS
-------
-Options meant for 'git pull' itself and the underlying 'git merge'
-must be given before the options meant for 'git fetch'.
-
-q::
--quiet::
This is passed to both underlying git-fetch to squelch reporting of
is not detected to lie in a Git repository or work tree
print a message to stderr and exit with nonzero status.
+--git-common-dir::
+ Show `$GIT_COMMON_DIR` if defined, else `$GIT_DIR`.
+
--is-inside-git-dir::
When the current working directory is below the repository
directory print "true", otherwise "false".
repository. If <path> is a gitfile then the resolved path
to the real repository is printed.
+--git-path <path>::
+ Resolve "$GIT_DIR/<path>" and takes other path relocation
+ variables such as $GIT_OBJECT_DIRECTORY,
+ $GIT_INDEX_FILE... into account. For example, if
+ $GIT_OBJECT_DIRECTORY is set to /foo/bar then "git rev-parse
+ --git-path objects/abc" returns /foo/bar/abc.
+
--show-cdup::
When the command is invoked from a subdirectory, show the
path of the top-level directory relative to the current
sendemail.aliasFileType::
Format of the file(s) specified in sendemail.aliasesFile. Must be
- one of 'mutt', 'mailrc', 'pine', 'elm', or 'gnus'.
+ one of 'mutt', 'mailrc', 'pine', 'elm', or 'gnus', or 'sendmail'.
++
+What an alias file in each format looks like can be found in
+the documentation of the email program of the same name. The
+differences and limitations from the standard formats are
+described below:
++
+--
+sendmail;;
+* Quoted aliases and quoted addresses are not supported: lines that
+ contain a `"` symbol are ignored.
+* Redirection to a file (`/path/name`) or pipe (`|command`) is not
+ supported.
+* File inclusion (`:include: /path/name`) is not supported.
+* Warnings are printed on the standard error output for any
+ explicitly unsupported constructs, and any other lines that are not
+ recognized by the parser.
+--
sendemail.multiEdit::
If true (default), a single editor instance will be spawned to edit
shown (i.e. the same as specifying `normal`), to help you avoid
forgetting to add newly created files. Because it takes extra work
to find untracked files in the filesystem, this mode may take some
-time in a large working tree. You can use `no` to have `git status`
+time in a large working tree.
+Consider enabling untracked cache and split index if supported (see
+`git update-index --untracked-cache` and `git update-index
+--split-index`), Otherwise you can use `no` to have `git status`
return more quickly without showing untracked files.
+
The default can be changed using the status.showUntrackedFiles
the shared index file. This mode is designed for very large
indexes that take a significant amount of time to read or write.
+--untracked-cache::
+--no-untracked-cache::
+ Enable or disable untracked cache extension. This could speed
+ up for commands that involve determining untracked files such
+ as `git status`. The underlying operating system and file
+ system must change `st_mtime` field of a directory if files
+ are added or deleted in that directory.
+
+--force-untracked-cache::
+ For safety, `--untracked-cache` performs tests on the working
+ directory to make sure untracked cache can be used. These
+ tests can take a few seconds. `--force-untracked-cache` can be
+ used to skip the tests.
+
\--::
Do not interpret any more arguments as options.
branch of the `git.git` repository.
Documentation for older releases are available here:
-* link:v2.4.4/git.html[documentation for release 2.4.4]
+* link:v2.4.5/git.html[documentation for release 2.4.5]
* release notes for
+ link:RelNotes/2.4.5.txt[2.4.5],
link:RelNotes/2.4.4.txt[2.4.4],
link:RelNotes/2.4.3.txt[2.4.3],
link:RelNotes/2.4.2.txt[2.4.2],
an explicit repository directory set via 'GIT_DIR' or on the
command line.
+'GIT_COMMON_DIR'::
+ If this variable is set to a path, non-worktree files that are
+ normally in $GIT_DIR will be taken from this path
+ instead. Worktree-specific files such as HEAD or index are
+ taken from $GIT_DIR. See linkgit:gitrepository-layout[5] and
+ the section 'MULTIPLE CHECKOUT MODE' in linkgit:checkout[1]
+ details. This variable has lower precedence than other path
+ variables such as GIT_INDEX_FILE, GIT_OBJECT_DIRECTORY...
+
Git Commits
~~~~~~~~~~~
'GIT_AUTHOR_NAME'::
----------------------------------------------------------------
[merge "filfre"]
name = feel-free merge driver
- driver = filfre %O %A %B
+ driver = filfre %O %A %B %L %P
recursive = binary
----------------------------------------------------------------
When left unspecified, the driver itself is used for both
internal merge and the final merge.
+The merge driver can learn the pathname in which the merged result
+will be stored via placeholder `%P`.
+
`conflict-marker-size`
^^^^^^^^^^^^^^^^^^^^^^
use with dumb transports but otherwise is OK as long as
`objects/info/alternates` points at the object stores it
borrows from.
++
+This directory is ignored if $GIT_COMMON_DIR is set and
+"$GIT_COMMON_DIR/objects" will be used instead.
objects/[0-9a-f][0-9a-f]::
A newly created object is stored in its own file.
References are stored in subdirectories of this
directory. The 'git prune' command knows to preserve
objects reachable from refs found in this directory and
- its subdirectories.
+ its subdirectories. This directory is ignored if $GIT_COMMON_DIR
+ is set and "$GIT_COMMON_DIR/refs" will be used instead.
refs/heads/`name`::
records tip-of-the-tree commit objects of branch `name`
packed-refs::
records the same information as refs/heads/, refs/tags/,
and friends record in a more efficient way. See
- linkgit:git-pack-refs[1].
+ linkgit:git-pack-refs[1]. This file is ignored if $GIT_COMMON_DIR
+ is set and "$GIT_COMMON_DIR/packed-refs" will be used instead.
HEAD::
A symref (see glossary) to the `refs/heads/` namespace
is often called 'detached HEAD.' See linkgit:git-checkout[1]
for details.
+config::
+ Repository specific configuration file. This file is ignored
+ if $GIT_COMMON_DIR is set and "$GIT_COMMON_DIR/config" will be
+ used instead.
+
branches::
A slightly deprecated way to store shorthands to be used
to specify a URL to 'git fetch', 'git pull' and 'git push'.
'name' can be given to these commands in place of
'repository' argument. See the REMOTES section in
linkgit:git-fetch[1] for details. This mechanism is legacy
- and not likely to be found in modern repositories.
+ and not likely to be found in modern repositories. This
+ directory is ignored if $GIT_COMMON_DIR is set and
+ "$GIT_COMMON_DIR/branches" will be used instead.
+
hooks::
Hooks are customization scripts used by various Git
default. To enable, the `.sample` suffix has to be
removed from the filename by renaming.
Read linkgit:githooks[5] for more details about
- each hook.
+ each hook. This directory is ignored if $GIT_COMMON_DIR is set
+ and "$GIT_COMMON_DIR/hooks" will be used instead.
+
index::
The current index file for the repository. It is
info::
Additional information about the repository is recorded
- in this directory.
+ in this directory. This directory is ignored if $GIT_COMMON_DIR
+ is set and "$GIT_COMMON_DIR/index" will be used instead.
info/refs::
This file helps dumb transports discover what refs are
when interacting with remote repositories via 'git fetch',
'git pull' and 'git push' commands. See the REMOTES section
in linkgit:git-fetch[1] for details. This mechanism is legacy
- and not likely to be found in modern repositories.
+ and not likely to be found in modern repositories. This
+ directory is ignored if $GIT_COMMON_DIR is set and
+ "$GIT_COMMON_DIR/remotes" will be used instead.
logs::
- Records of changes made to refs are stored in this
- directory. See linkgit:git-update-ref[1]
- for more information.
+ Records of changes made to refs are stored in this directory.
+ See linkgit:git-update-ref[1] for more information. This
+ directory is ignored if $GIT_COMMON_DIR is set and
+ "$GIT_COMMON_DIR/logs" will be used instead.
logs/refs/heads/`name`::
Records all changes made to the branch tip named `name`.
shallow::
This is similar to `info/grafts` but is internally used
and maintained by shallow clone mechanism. See `--depth`
- option to linkgit:git-clone[1] and linkgit:git-fetch[1].
+ option to linkgit:git-clone[1] and linkgit:git-fetch[1]. This
+ file is ignored if $GIT_COMMON_DIR is set and
+ "$GIT_COMMON_DIR/shallow" will be used instead.
+
+commondir::
+ If this file exists, $GIT_COMMON_DIR (see linkgit:git[1]) will
+ be set to the path specified in this file if it is not
+ explicitly set. If the specified path is relative, it is
+ relative to $GIT_DIR. The repository with commondir is
+ incomplete without the repository pointed by "commondir".
modules::
Contains the git-repositories of the submodules.
+worktrees::
+ Contains worktree specific information of linked
+ checkouts. Each subdirectory contains the worktree-related
+ part of a linked checkout. This directory is ignored if
+ $GIT_COMMON_DIR is set and "$GIT_COMMON_DIR/worktrees" will be
+ used instead.
+
+worktrees/<id>/gitdir::
+ A text file containing the absolute path back to the .git file
+ that points to here. This is used to check if the linked
+ repository has been manually removed and there is no need to
+ keep this directory any more. mtime of this file should be
+ updated every time the linked repository is accessed.
+
+worktrees/<id>/locked::
+ If this file exists, the linked repository may be on a
+ portable device and not available. It does not mean that the
+ linked repository is gone and `worktrees/<id>` could be
+ removed. The file's content contains a reason string on why
+ the repository is locked.
+
+worktrees/<id>/link::
+ If this file exists, it is a hard link to the linked .git
+ file. It is used to detect if the linked repository is
+ manually removed.
+
SEE ALSO
--------
linkgit:git-init[1],
that categorizes commands by type, so they can be listed in appropriate
subsections in the documentation's summary command list. Add an entry
for yours. To understand the categories, look at git-commands.txt
-in the main directory.
+in the main directory. If the new command is part of the typical Git
+workflow and you believe it common enough to be mentioned in 'git help',
+map this command to a common group in the column [common].
7. Give the maintainer one paragraph to include in the RelNotes file
to describe the new feature; a good place to do so is in the cover
allowed (equivalent to giving the `--ff-only` option from the
command line).
-merge.log::
- In addition to branch names, populate the log message with at
- most the specified number of one-line descriptions from the
- actual commits that are being merged. Defaults to false, and
- true is a synonym for 20.
+include::fmt-merge-msg-config.txt[]
merge.renameLimit::
The number of files to consider when performing rename detection
in their encoding header; this option can be used to tell the
command to re-code the commit log message in the encoding
preferred by the user. For non plumbing commands this
- defaults to UTF-8.
+ defaults to UTF-8. Note that if an object claims to be encoded
+ in `X` and we are outputting in `X`, we will output the object
+ verbatim; this means that invalid sequences in the original
+ commit may be copied to the output.
--notes[=<ref>]::
Show the notes (see linkgit:git-notes[1]) that annotate the
`branch.<name>.merge`). A missing branchname defaults to the
current one.
+'<branchname>@\{push\}', e.g. 'master@\{push\}', '@\{push\}'::
+ The suffix '@\{push}' reports the branch "where we would push to" if
+ `git push` were run while `branchname` was checked out (or the current
+ 'HEAD' if no branchname is specified). Since our push destination is
+ in a remote repository, of course, we report the local tracking branch
+ that corresponds to that branch (i.e., something in 'refs/remotes/').
++
+Here's an example to make it more clear:
++
+------------------------------
+$ git config push.default current
+$ git config remote.pushdefault myfork
+$ git checkout -b mybranch origin/master
+
+$ git rev-parse --symbolic-full-name @{upstream}
+refs/remotes/origin/master
+
+$ git rev-parse --symbolic-full-name @{push}
+refs/remotes/myfork/mybranch
+------------------------------
++
+Note in the example that we set up a triangular workflow, where we pull
+from one location and push to another. In a non-triangular workflow,
+'@\{push}' is the same as '@\{upstream}', and there is no need for it.
+
'<rev>{caret}', e.g. 'HEAD{caret}, v1.5.1{caret}0'::
A suffix '{caret}' to a revision parameter means the first parent of
that commit object. '{caret}<n>' means the <n>th parent (i.e.
callback function for every ref. The callback function has this
signature:
- int handle_one_ref(const char *refname, const unsigned char *sha1,
+ int handle_one_ref(const char *refname, const struct object_id *oid,
int flags, void *cb_data);
There are different kinds of iterate functions which all take a
The name of the remote listed in the configuration.
-`remote`::
-
- The struct remote for that remote.
-
`merge_name`::
An array of the "merge" lines in the configuration.
Clients MUST send at least one "want" command in the request body.
Clients MUST NOT reference an id in a "want" command which did not
appear in the response obtained through ref discovery unless the
-server advertises capability `allow-tip-sha1-in-want`.
+server advertises capability `allow-tip-sha1-in-want` or
+`allow-reachable-sha1-in-want`.
compute_request = want_list
have_list
The remaining index entries after replaced ones will be added to the
final index. These added entries are also sorted by entry name then
stage.
+
+== Untracked cache
+
+ Untracked cache saves the untracked file list and necessary data to
+ verify the cache. The signature for this extension is { 'U', 'N',
+ 'T', 'R' }.
+
+ The extension starts with
+
+ - A sequence of NUL-terminated strings, preceded by the size of the
+ sequence in variable width encoding. Each string describes the
+ environment where the cache can be used.
+
+ - Stat data of $GIT_DIR/info/exclude. See "Index entry" section from
+ ctime field until "file size".
+
+ - Stat data of core.excludesfile
+
+ - 32-bit dir_flags (see struct dir_struct)
+
+ - 160-bit SHA-1 of $GIT_DIR/info/exclude. Null SHA-1 means the file
+ does not exist.
+
+ - 160-bit SHA-1 of core.excludesfile. Null SHA-1 means the file does
+ not exist.
+
+ - NUL-terminated string of per-dir exclude file name. This usually
+ is ".gitignore".
+
+ - The number of following directory blocks, variable width
+ encoding. If this number is zero, the extension ends here with a
+ following NUL.
+
+ - A number of directory blocks in depth-first-search order, each
+ consists of
+
+ - The number of untracked entries, variable width encoding.
+
+ - The number of sub-directory blocks, variable width encoding.
+
+ - The directory name terminated by NUL.
+
+ - A number of untrached file/dir names terminated by NUL.
+
+The remaining data of each directory block is grouped by type:
+
+ - An ewah bitmap, the n-th bit marks whether the n-th directory has
+ valid untracked cache entries.
+
+ - An ewah bitmap, the n-th bit records "check-only" bit of
+ read_directory_recursive() for the n-th directory.
+
+ - An ewah bitmap, the n-th bit indicates whether SHA-1 and stat data
+ is valid for the n-th directory and exists in the next data.
+
+ - An array of stat data. The n-th data corresponds with the n-th
+ "one" bit in the previous ewah bitmap.
+
+ - An array of SHA-1. The n-th SHA-1 corresponds with the n-th "one" bit
+ in the previous ewah bitmap.
+
+ - One NUL.
Packfile transfer protocols
===========================
-Git supports transferring data in packfiles over the ssh://, git:// and
+Git supports transferring data in packfiles over the ssh://, git://, http:// and
file:// transports. There exist two sets of protocols, one for pushing
data from a client to a server and another for fetching data from a
-server to a client. All three transports (ssh, git, file) use the same
-protocol to transfer data.
+server to a client. The three transports (ssh, git, file) use the same
+protocol to transfer data. http is documented in http-protocol.txt.
The processes invoked in the canonical Git implementation are 'upload-pack'
on the server side and 'fetch-pack' on the client side for fetching data;
send "want" lines with SHA-1s that exist at the server but are not
advertised by upload-pack.
+allow-reachable-sha1-in-want
+----------------------------
+
+If the upload-pack server advertises this capability, fetch-pack may
+send "want" lines with SHA-1s that exist at the server but are not
+advertised by upload-pack.
+
push-cert=<nonce>
-----------------
#!/bin/sh
GVF=GIT-VERSION-FILE
-DEF_VER=v2.4.4
+DEF_VER=v2.5.0-rc1
LF='
'
# compiler is detected to support it.
#
# Define HAVE_BSD_SYSCTL if your platform has a BSD-compatible sysctl function.
+#
+# Define HAVE_GETDELIM if your system has the getdelim() function.
GIT-VERSION-FILE: FORCE
@$(SHELL_PATH) ./GIT-VERSION-GEN
TEST_PROGRAMS_NEED_X += test-delta
TEST_PROGRAMS_NEED_X += test-dump-cache-tree
TEST_PROGRAMS_NEED_X += test-dump-split-index
+TEST_PROGRAMS_NEED_X += test-dump-untracked-cache
TEST_PROGRAMS_NEED_X += test-genrandom
TEST_PROGRAMS_NEED_X += test-hashmap
TEST_PROGRAMS_NEED_X += test-index-version
BASIC_CFLAGS += -DHAVE_BSD_SYSCTL
endif
+ifdef HAVE_GETDELIM
+ BASIC_CFLAGS += -DHAVE_GETDELIM
+endif
+
ifeq ($(TCLTK_PATH),)
NO_TCLTK = NoThanks
endif
ln -s $< $@ 2>/dev/null || \
cp $< $@
-common-cmds.h: ./generate-cmdlist.sh command-list.txt
+common-cmds.h: generate-cmdlist.perl command-list.txt
common-cmds.h: $(wildcard Documentation/git-*.txt)
- $(QUIET_GEN)./generate-cmdlist.sh > $@+ && mv $@+ $@
+ $(QUIET_GEN)$(PERL_PATH) generate-cmdlist.perl command-list.txt > $@+ && mv $@+ $@
SCRIPT_DEFINES = $(SHELL_PATH_SQ):$(DIFF_SQ):$(GIT_VERSION):\
$(localedir_SQ):$(NO_CURL):$(USE_GETTEXT_SCHEME):$(SANE_TOOL_PATH_SQ):\
perl/perl.mak: perl/PM.stamp
perl/PM.stamp: FORCE
- $(QUIET_GEN)$(FIND) perl -type f -name '*.pm' | sort >$@+ && \
+ @$(FIND) perl -type f -name '*.pm' | sort >$@+ && \
{ cmp $@+ $@ >/dev/null 2>/dev/null || mv $@+ $@; } && \
$(RM) $@+
gitweb:
$(QUIET_SUBDIR0)gitweb $(QUIET_SUBDIR1) all
-git-instaweb: git-instaweb.sh gitweb GIT-SCRIPT-DEFINES
+git-instaweb: git-instaweb.sh GIT-SCRIPT-DEFINES
$(QUIET_GEN)$(cmd_munge_script) && \
chmod +x $@+ && \
mv $@+ $@
# that runs GIT-BUILD-OPTIONS, and then again to protect it
# and the first level quoting from the shell that runs "echo".
GIT-BUILD-OPTIONS: FORCE
- @echo SHELL_PATH=\''$(subst ','\'',$(SHELL_PATH_SQ))'\' >$@
- @echo PERL_PATH=\''$(subst ','\'',$(PERL_PATH_SQ))'\' >>$@
- @echo DIFF=\''$(subst ','\'',$(subst ','\'',$(DIFF)))'\' >>$@
- @echo PYTHON_PATH=\''$(subst ','\'',$(PYTHON_PATH_SQ))'\' >>$@
- @echo TAR=\''$(subst ','\'',$(subst ','\'',$(TAR)))'\' >>$@
- @echo NO_CURL=\''$(subst ','\'',$(subst ','\'',$(NO_CURL)))'\' >>$@
- @echo NO_EXPAT=\''$(subst ','\'',$(subst ','\'',$(NO_EXPAT)))'\' >>$@
- @echo USE_LIBPCRE=\''$(subst ','\'',$(subst ','\'',$(USE_LIBPCRE)))'\' >>$@
- @echo NO_PERL=\''$(subst ','\'',$(subst ','\'',$(NO_PERL)))'\' >>$@
- @echo NO_PYTHON=\''$(subst ','\'',$(subst ','\'',$(NO_PYTHON)))'\' >>$@
- @echo NO_UNIX_SOCKETS=\''$(subst ','\'',$(subst ','\'',$(NO_UNIX_SOCKETS)))'\' >>$@
+ @echo SHELL_PATH=\''$(subst ','\'',$(SHELL_PATH_SQ))'\' >$@+
+ @echo PERL_PATH=\''$(subst ','\'',$(PERL_PATH_SQ))'\' >>$@+
+ @echo DIFF=\''$(subst ','\'',$(subst ','\'',$(DIFF)))'\' >>$@+
+ @echo PYTHON_PATH=\''$(subst ','\'',$(PYTHON_PATH_SQ))'\' >>$@+
+ @echo TAR=\''$(subst ','\'',$(subst ','\'',$(TAR)))'\' >>$@+
+ @echo NO_CURL=\''$(subst ','\'',$(subst ','\'',$(NO_CURL)))'\' >>$@+
+ @echo NO_EXPAT=\''$(subst ','\'',$(subst ','\'',$(NO_EXPAT)))'\' >>$@+
+ @echo USE_LIBPCRE=\''$(subst ','\'',$(subst ','\'',$(USE_LIBPCRE)))'\' >>$@+
+ @echo NO_PERL=\''$(subst ','\'',$(subst ','\'',$(NO_PERL)))'\' >>$@+
+ @echo NO_PYTHON=\''$(subst ','\'',$(subst ','\'',$(NO_PYTHON)))'\' >>$@+
+ @echo NO_UNIX_SOCKETS=\''$(subst ','\'',$(subst ','\'',$(NO_UNIX_SOCKETS)))'\' >>$@+
ifdef TEST_OUTPUT_DIRECTORY
- @echo TEST_OUTPUT_DIRECTORY=\''$(subst ','\'',$(subst ','\'',$(TEST_OUTPUT_DIRECTORY)))'\' >>$@
+ @echo TEST_OUTPUT_DIRECTORY=\''$(subst ','\'',$(subst ','\'',$(TEST_OUTPUT_DIRECTORY)))'\' >>$@+
endif
ifdef GIT_TEST_OPTS
- @echo GIT_TEST_OPTS=\''$(subst ','\'',$(subst ','\'',$(GIT_TEST_OPTS)))'\' >>$@
+ @echo GIT_TEST_OPTS=\''$(subst ','\'',$(subst ','\'',$(GIT_TEST_OPTS)))'\' >>$@+
endif
ifdef GIT_TEST_CMP
- @echo GIT_TEST_CMP=\''$(subst ','\'',$(subst ','\'',$(GIT_TEST_CMP)))'\' >>$@
+ @echo GIT_TEST_CMP=\''$(subst ','\'',$(subst ','\'',$(GIT_TEST_CMP)))'\' >>$@+
endif
ifdef GIT_TEST_CMP_USE_COPIED_CONTEXT
- @echo GIT_TEST_CMP_USE_COPIED_CONTEXT=YesPlease >>$@
+ @echo GIT_TEST_CMP_USE_COPIED_CONTEXT=YesPlease >>$@+
endif
- @echo NO_GETTEXT=\''$(subst ','\'',$(subst ','\'',$(NO_GETTEXT)))'\' >>$@
- @echo GETTEXT_POISON=\''$(subst ','\'',$(subst ','\'',$(GETTEXT_POISON)))'\' >>$@
+ @echo NO_GETTEXT=\''$(subst ','\'',$(subst ','\'',$(NO_GETTEXT)))'\' >>$@+
+ @echo GETTEXT_POISON=\''$(subst ','\'',$(subst ','\'',$(GETTEXT_POISON)))'\' >>$@+
ifdef GIT_PERF_REPEAT_COUNT
- @echo GIT_PERF_REPEAT_COUNT=\''$(subst ','\'',$(subst ','\'',$(GIT_PERF_REPEAT_COUNT)))'\' >>$@
+ @echo GIT_PERF_REPEAT_COUNT=\''$(subst ','\'',$(subst ','\'',$(GIT_PERF_REPEAT_COUNT)))'\' >>$@+
endif
ifdef GIT_PERF_REPO
- @echo GIT_PERF_REPO=\''$(subst ','\'',$(subst ','\'',$(GIT_PERF_REPO)))'\' >>$@
+ @echo GIT_PERF_REPO=\''$(subst ','\'',$(subst ','\'',$(GIT_PERF_REPO)))'\' >>$@+
endif
ifdef GIT_PERF_LARGE_REPO
- @echo GIT_PERF_LARGE_REPO=\''$(subst ','\'',$(subst ','\'',$(GIT_PERF_LARGE_REPO)))'\' >>$@
+ @echo GIT_PERF_LARGE_REPO=\''$(subst ','\'',$(subst ','\'',$(GIT_PERF_LARGE_REPO)))'\' >>$@+
endif
ifdef GIT_PERF_MAKE_OPTS
- @echo GIT_PERF_MAKE_OPTS=\''$(subst ','\'',$(subst ','\'',$(GIT_PERF_MAKE_OPTS)))'\' >>$@
+ @echo GIT_PERF_MAKE_OPTS=\''$(subst ','\'',$(subst ','\'',$(GIT_PERF_MAKE_OPTS)))'\' >>$@+
endif
ifdef TEST_GIT_INDEX_VERSION
- @echo TEST_GIT_INDEX_VERSION=\''$(subst ','\'',$(subst ','\'',$(TEST_GIT_INDEX_VERSION)))'\' >>$@
+ @echo TEST_GIT_INDEX_VERSION=\''$(subst ','\'',$(subst ','\'',$(TEST_GIT_INDEX_VERSION)))'\' >>$@+
endif
+ @if cmp $@+ $@ >/dev/null 2>&1; then $(RM) $@+; else mv $@+ $@; fi
### Detect Python interpreter path changes
ifndef NO_PYTHON
esac ; \
test -f "Documentation/$$v.txt" || \
echo "no doc: $$v"; \
- sed -e '/^#/d' command-list.txt | \
+ sed -e '1,/^### command list/d' -e '/^#/d' command-list.txt | \
grep -q "^$$v[ ]" || \
case "$$v" in \
git) ;; \
esac ; \
done; \
( \
- sed -e '/^#/d' \
+ sed -e '1,/^### command list/d' \
+ -e '/^#/d' \
-e 's/[ ].*//' \
-e 's/^/listed /' command-list.txt; \
$(MAKE) -C Documentation print-man1 | \
-Documentation/RelNotes/2.4.4.txt
\ No newline at end of file
+Documentation/RelNotes/2.5.0.txt
\ No newline at end of file
copy_le16(trailer.entries, zip_dir_entries);
copy_le32(trailer.size, zip_dir_offset);
copy_le32(trailer.offset, zip_offset);
- copy_le16(trailer.comment_length, sha1 ? 40 : 0);
+ copy_le16(trailer.comment_length, sha1 ? GIT_SHA1_HEXSZ : 0);
write_or_die(1, zip_dir, zip_dir_offset);
write_or_die(1, &trailer, ZIP_DIR_TRAILER_SIZE);
if (sha1)
- write_or_die(1, sha1_to_hex(sha1), 40);
+ write_or_die(1, sha1_to_hex(sha1), GIT_SHA1_HEXSZ);
}
static void dos_time(time_t *time, int *dos_date, int *dos_time)
struct directory {
struct directory *up;
- unsigned char sha1[20];
+ struct object_id oid;
int baselen, len;
unsigned mode;
int stage;
d->stage = stage;
c->bottom = d;
d->len = sprintf(d->path, "%.*s%s/", (int)base->len, base->buf, filename);
- hashcpy(d->sha1, sha1);
+ hashcpy(d->oid.hash, sha1);
}
static int write_directory(struct archiver_context *c)
d->path[d->len - 1] = '\0'; /* no trailing slash */
ret =
write_directory(c) ||
- write_archive_entry(d->sha1, d->path, d->baselen,
+ write_archive_entry(d->oid.hash, d->path, d->baselen,
d->path + d->baselen, d->mode,
d->stage, c) != READ_TREE_RECURSIVE;
free(d);
time_t archive_time;
struct tree *tree;
const struct commit *commit;
- unsigned char sha1[20];
+ struct object_id oid;
/* Remotes are only allowed to fetch actual refs */
if (remote && !remote_allow_unreachable) {
const char *colon = strchrnul(name, ':');
int refnamelen = colon - name;
- if (!dwim_ref(name, refnamelen, sha1, &ref))
+ if (!dwim_ref(name, refnamelen, oid.hash, &ref))
die("no such ref: %.*s", refnamelen, name);
free(ref);
}
- if (get_sha1(name, sha1))
+ if (get_sha1(name, oid.hash))
die("Not a valid object name");
- commit = lookup_commit_reference_gently(sha1, 1);
+ commit = lookup_commit_reference_gently(oid.hash, 1);
if (commit) {
commit_sha1 = commit->object.sha1;
archive_time = commit->date;
archive_time = time(NULL);
}
- tree = parse_tree_indirect(sha1);
+ tree = parse_tree_indirect(oid.hash);
if (tree == NULL)
die("not a tree object");
if (prefix) {
- unsigned char tree_sha1[20];
+ struct object_id tree_oid;
unsigned int mode;
int err;
err = get_tree_entry(tree->object.sha1, prefix,
- tree_sha1, &mode);
+ tree_oid.hash, &mode);
if (err || !S_ISDIR(mode))
die("current working directory is untracked");
- tree = parse_tree_indirect(tree_sha1);
+ tree = parse_tree_indirect(tree_oid.hash);
}
ar_args->tree = tree;
ar_args->commit_sha1 = commit_sha1;
static struct sha1_array good_revs;
static struct sha1_array skipped_revs;
-static unsigned char *current_bad_sha1;
+static struct object_id *current_bad_oid;
static const char *argv_checkout[] = {"checkout", "-q", NULL, "--", NULL};
static const char *argv_show_branch[] = {"show-branch", NULL, NULL};
return best;
}
-static int register_ref(const char *refname, const unsigned char *sha1,
+static int register_ref(const char *refname, const struct object_id *oid,
int flags, void *cb_data)
{
if (!strcmp(refname, "bad")) {
- current_bad_sha1 = xmalloc(20);
- hashcpy(current_bad_sha1, sha1);
+ current_bad_oid = xmalloc(sizeof(*current_bad_oid));
+ oidcpy(current_bad_oid, oid);
} else if (starts_with(refname, "good-")) {
- sha1_array_append(&good_revs, sha1);
+ sha1_array_append(&good_revs, oid->hash);
} else if (starts_with(refname, "skip-")) {
- sha1_array_append(&skipped_revs, sha1);
+ sha1_array_append(&skipped_revs, oid->hash);
}
return 0;
for (i = 0; cur; cur = cur->next, i++) {
if (i == index) {
- if (hashcmp(cur->item->object.sha1, current_bad_sha1))
+ if (hashcmp(cur->item->object.sha1, current_bad_oid->hash))
return cur;
if (previous)
return previous;
/* rev_argv.argv[0] will be ignored by setup_revisions */
argv_array_push(&rev_argv, "bisect_rev_setup");
- argv_array_pushf(&rev_argv, bad_format, sha1_to_hex(current_bad_sha1));
+ argv_array_pushf(&rev_argv, bad_format, oid_to_hex(current_bad_oid));
for (i = 0; i < good_revs.nr; i++)
argv_array_pushf(&rev_argv, good_format,
sha1_to_hex(good_revs.sha1[i]));
}
static void exit_if_skipped_commits(struct commit_list *tried,
- const unsigned char *bad)
+ const struct object_id *bad)
{
if (!tried)
return;
"The first bad commit could be any of:\n");
print_commit_list(tried, "%s\n", "%s\n");
if (bad)
- printf("%s\n", sha1_to_hex(bad));
+ printf("%s\n", oid_to_hex(bad));
printf("We cannot bisect more!\n");
exit(2);
}
-static int is_expected_rev(const unsigned char *sha1)
+static int is_expected_rev(const struct object_id *oid)
{
const char *filename = git_path("BISECT_EXPECTED_REV");
struct stat st;
return 0;
if (strbuf_getline(&str, fp, '\n') != EOF)
- res = !strcmp(str.buf, sha1_to_hex(sha1));
+ res = !strcmp(str.buf, oid_to_hex(oid));
strbuf_release(&str);
fclose(fp);
struct commit **rev = xmalloc(len * sizeof(*rev));
int i, n = 0;
- rev[n++] = get_commit_reference(current_bad_sha1);
+ rev[n++] = get_commit_reference(current_bad_oid->hash);
for (i = 0; i < good_revs.nr; i++)
rev[n++] = get_commit_reference(good_revs.sha1[i]);
*rev_nr = n;
static void handle_bad_merge_base(void)
{
- if (is_expected_rev(current_bad_sha1)) {
- char *bad_hex = sha1_to_hex(current_bad_sha1);
+ if (is_expected_rev(current_bad_oid)) {
+ char *bad_hex = oid_to_hex(current_bad_oid);
char *good_hex = join_sha1_array_hex(&good_revs, ' ');
fprintf(stderr, "The merge base %s is bad.\n"
static void handle_skipped_merge_base(const unsigned char *mb)
{
char *mb_hex = sha1_to_hex(mb);
- char *bad_hex = sha1_to_hex(current_bad_sha1);
+ char *bad_hex = sha1_to_hex(current_bad_oid->hash);
char *good_hex = join_sha1_array_hex(&good_revs, ' ');
warning("the merge base between %s and [%s] "
for (; result; result = result->next) {
const unsigned char *mb = result->item->object.sha1;
- if (!hashcmp(mb, current_bad_sha1)) {
+ if (!hashcmp(mb, current_bad_oid->hash)) {
handle_bad_merge_base();
} else if (0 <= sha1_array_lookup(&good_revs, mb)) {
continue;
struct stat st;
int fd;
- if (!current_bad_sha1)
+ if (!current_bad_oid)
die("a bad revision is needed");
/* Check if file BISECT_ANCESTORS_OK exists. */
struct commit_list *tried;
int reaches = 0, all = 0, nr, steps;
const unsigned char *bisect_rev;
- char bisect_rev_hex[41];
+ char bisect_rev_hex[GIT_SHA1_HEXSZ + 1];
if (read_bisect_refs())
die("reading bisect refs failed");
exit_if_skipped_commits(tried, NULL);
printf("%s was both good and bad\n",
- sha1_to_hex(current_bad_sha1));
+ oid_to_hex(current_bad_oid));
exit(1);
}
}
bisect_rev = revs.commits->item->object.sha1;
- memcpy(bisect_rev_hex, sha1_to_hex(bisect_rev), 41);
+ memcpy(bisect_rev_hex, sha1_to_hex(bisect_rev), GIT_SHA1_HEXSZ + 1);
- if (!hashcmp(bisect_rev, current_bad_sha1)) {
- exit_if_skipped_commits(tried, current_bad_sha1);
+ if (!hashcmp(bisect_rev, current_bad_oid->hash)) {
+ exit_if_skipped_commits(tried, current_bad_oid);
printf("%s is the first bad commit\n", bisect_rev_hex);
show_diff_tree(prefix, revs.commits->item);
/* This means the bisection process succeeded. */
struct patch *next;
/* three-way fallback result */
- unsigned char threeway_stage[3][20];
+ struct object_id threeway_stage[3];
};
static void free_fragment_list(struct fragment *list)
}
if (oldlines || newlines)
return -1;
+ if (!deleted && !added)
+ return -1;
+
fragment->leading = leading;
fragment->trailing = trailing;
if (status) {
patch->conflicted_threeway = 1;
if (patch->is_new)
- hashclr(patch->threeway_stage[0]);
+ oidclr(&patch->threeway_stage[0]);
else
- hashcpy(patch->threeway_stage[0], pre_sha1);
- hashcpy(patch->threeway_stage[1], our_sha1);
- hashcpy(patch->threeway_stage[2], post_sha1);
+ hashcpy(patch->threeway_stage[0].hash, pre_sha1);
+ hashcpy(patch->threeway_stage[1].hash, our_sha1);
+ hashcpy(patch->threeway_stage[2].hash, post_sha1);
fprintf(stderr, "Applied patch to '%s' with conflicts.\n", patch->new_name);
} else {
fprintf(stderr, "Applied patch to '%s' cleanly.\n", patch->new_name);
remove_file_from_cache(patch->new_name);
for (stage = 1; stage < 4; stage++) {
- if (is_null_sha1(patch->threeway_stage[stage - 1]))
+ if (is_null_oid(&patch->threeway_stage[stage - 1]))
continue;
ce = xcalloc(1, ce_size);
memcpy(ce->name, patch->new_name, namelen);
ce->ce_mode = create_ce_mode(mode);
ce->ce_flags = create_ce_flags(stage);
ce->ce_namelen = namelen;
- hashcpy(ce->sha1, patch->threeway_stage[stage - 1]);
+ hashcpy(ce->sha1, patch->threeway_stage[stage - 1].hash);
if (add_cache_entry(ce, ADD_CACHE_OK_TO_ADD) < 0)
die(_("unable to add cache entry for %s"), patch->new_name);
}
blank_boundary = git_config_bool(var, value);
return 0;
}
+ if (!strcmp(var, "blame.showemail")) {
+ int *output_option = cb;
+ if (git_config_bool(var, value))
+ *output_option |= OUTPUT_SHOW_EMAIL;
+ else
+ *output_option &= ~OUTPUT_SHOW_EMAIL;
+ return 0;
+ }
if (!strcmp(var, "blame.date")) {
if (!value)
return config_error_nonbool(var);
unsigned int range_i;
long anchor;
- git_config(git_blame_config, NULL);
+ git_config(git_blame_config, &output_option);
init_revisions(&revs, NULL);
revs.date_mode = blame_date_mode;
DIFF_OPT_SET(&revs.diffopt, ALLOW_TEXTCONV);
if (kind == REF_LOCAL_BRANCH) {
struct branch *branch = branch_get(name);
+ const char *upstream = branch_get_upstream(branch, NULL);
unsigned char sha1[20];
- if (branch &&
- branch->merge &&
- branch->merge[0] &&
- branch->merge[0]->dst &&
+ if (upstream &&
(reference_name = reference_name_to_free =
- resolve_refdup(branch->merge[0]->dst, RESOLVE_REF_READING,
+ resolve_refdup(upstream, RESOLVE_REF_READING,
sha1, NULL)) != NULL)
reference_rev = lookup_commit_reference(sha1);
}
return 0;
}
-static int append_ref(const char *refname, const unsigned char *sha1, int flags, void *cb_data)
+static int append_ref(const char *refname, const struct object_id *oid, int flags, void *cb_data)
{
struct append_ref_cb *cb = (struct append_ref_cb *)(cb_data);
struct ref_list *ref_list = cb->ref_list;
commit = NULL;
if (ref_list->verbose || ref_list->with_commit || merge_filter != NO_FILTER) {
- commit = lookup_commit_reference_gently(sha1, 1);
+ commit = lookup_commit_reference_gently(oid->hash, 1);
if (!commit) {
cb->ret = error(_("branch '%s' does not point at a commit"), refname);
return 0;
int ours, theirs;
char *ref = NULL;
struct branch *branch = branch_get(branch_name);
+ const char *upstream;
struct strbuf fancy = STRBUF_INIT;
int upstream_is_gone = 0;
int added_decoration = 1;
- switch (stat_tracking_info(branch, &ours, &theirs)) {
- case 0:
- /* no base */
- return;
- case -1:
- /* with "gone" base */
+ if (stat_tracking_info(branch, &ours, &theirs, &upstream) < 0) {
+ if (!upstream)
+ return;
upstream_is_gone = 1;
- break;
- default:
- /* with base */
- break;
}
if (show_upstream_ref) {
- ref = shorten_unambiguous_ref(branch->merge[0]->dst, 0);
+ ref = shorten_unambiguous_ref(upstream, 0);
if (want_color(branch_use_color))
strbuf_addf(&fancy, "%s%s%s",
branch_get_color(BRANCH_COLOR_UPSTREAM),
static int edit_branch_description(const char *branch_name)
{
- FILE *fp;
int status;
struct strbuf buf = STRBUF_INIT;
struct strbuf name = STRBUF_INIT;
" %s\n"
"Lines starting with '%c' will be stripped.\n",
branch_name, comment_line_char);
- fp = fopen(git_path(edit_description), "w");
- if ((fwrite(buf.buf, 1, buf.len, fp) < buf.len) || fclose(fp)) {
+ if (write_file(git_path(edit_description), 0, "%s", buf.buf)) {
strbuf_release(&buf);
return error(_("could not write branch description template: %s"),
strerror(errno));
#include "parse-options.h"
#include "userdiff.h"
#include "streaming.h"
+#include "tree-walk.h"
-static int cat_one_file(int opt, const char *exp_type, const char *obj_name)
+static int cat_one_file(int opt, const char *exp_type, const char *obj_name,
+ int unknown_type)
{
unsigned char sha1[20];
enum object_type type;
char *buf;
unsigned long size;
struct object_context obj_context;
+ struct object_info oi = {NULL};
+ struct strbuf sb = STRBUF_INIT;
+ unsigned flags = LOOKUP_REPLACE_OBJECT;
+
+ if (unknown_type)
+ flags |= LOOKUP_UNKNOWN_OBJECT;
if (get_sha1_with_context(obj_name, 0, sha1, &obj_context))
die("Not a valid object name %s", obj_name);
buf = NULL;
switch (opt) {
case 't':
- type = sha1_object_info(sha1, NULL);
- if (type > 0) {
- printf("%s\n", typename(type));
+ oi.typename = &sb;
+ if (sha1_object_info_extended(sha1, &oi, flags) < 0)
+ die("git cat-file: could not get object info");
+ if (sb.len) {
+ printf("%s\n", sb.buf);
+ strbuf_release(&sb);
return 0;
}
break;
case 's':
- type = sha1_object_info(sha1, &size);
- if (type > 0) {
- printf("%lu\n", size);
- return 0;
- }
- break;
+ oi.sizep = &size;
+ if (sha1_object_info_extended(sha1, &oi, flags) < 0)
+ die("git cat-file: could not get object info");
+ printf("%lu\n", size);
+ return 0;
case 'e':
return !has_sha1_file(sha1);
struct batch_options {
int enabled;
+ int follow_symlinks;
int print_contents;
const char *format;
};
struct expand_data *data)
{
struct strbuf buf = STRBUF_INIT;
+ struct object_context ctx;
+ int flags = opt->follow_symlinks ? GET_SHA1_FOLLOW_SYMLINKS : 0;
+ enum follow_symlinks_result result;
if (!obj_name)
return 1;
- if (get_sha1(obj_name, data->sha1)) {
- printf("%s missing\n", obj_name);
+ result = get_sha1_with_context(obj_name, flags, data->sha1, &ctx);
+ if (result != FOUND) {
+ switch (result) {
+ case MISSING_OBJECT:
+ printf("%s missing\n", obj_name);
+ break;
+ case DANGLING_SYMLINK:
+ printf("dangling %"PRIuMAX"\n%s\n",
+ (uintmax_t)strlen(obj_name), obj_name);
+ break;
+ case SYMLINK_LOOP:
+ printf("loop %"PRIuMAX"\n%s\n",
+ (uintmax_t)strlen(obj_name), obj_name);
+ break;
+ case NOT_DIR:
+ printf("notdir %"PRIuMAX"\n%s\n",
+ (uintmax_t)strlen(obj_name), obj_name);
+ break;
+ default:
+ die("BUG: unknown get_sha1_with_context result %d\n",
+ result);
+ break;
+ }
+ fflush(stdout);
+ return 0;
+ }
+
+ if (ctx.mode == 0) {
+ printf("symlink %"PRIuMAX"\n%s\n",
+ (uintmax_t)ctx.symlink_path.len,
+ ctx.symlink_path.buf);
fflush(stdout);
return 0;
}
}
static const char * const cat_file_usage[] = {
- N_("git cat-file (-t | -s | -e | -p | <type> | --textconv) <object>"),
- N_("git cat-file (--batch | --batch-check) < <list-of-objects>"),
+ N_("git cat-file (-t [--allow-unknown-type]|-s [--allow-unknown-type]|-e|-p|<type>|--textconv) <object>"),
+ N_("git cat-file (--batch | --batch-check) [--follow-symlinks] < <list-of-objects>"),
NULL
};
{
struct batch_options *bo = opt->value;
- if (unset) {
- memset(bo, 0, sizeof(*bo));
- return 0;
+ if (bo->enabled) {
+ return 1;
}
bo->enabled = 1;
int opt = 0;
const char *exp_type = NULL, *obj_name = NULL;
struct batch_options batch = {0};
+ int unknown_type = 0;
const struct option options[] = {
OPT_GROUP(N_("<type> can be one of: blob, tree, commit, tag")),
- OPT_SET_INT('t', NULL, &opt, N_("show object type"), 't'),
- OPT_SET_INT('s', NULL, &opt, N_("show object size"), 's'),
- OPT_SET_INT('e', NULL, &opt,
+ OPT_CMDMODE('t', NULL, &opt, N_("show object type"), 't'),
+ OPT_CMDMODE('s', NULL, &opt, N_("show object size"), 's'),
+ OPT_CMDMODE('e', NULL, &opt,
N_("exit with zero when there's no error"), 'e'),
- OPT_SET_INT('p', NULL, &opt, N_("pretty-print object's content"), 'p'),
- OPT_SET_INT(0, "textconv", &opt,
+ OPT_CMDMODE('p', NULL, &opt, N_("pretty-print object's content"), 'p'),
+ OPT_CMDMODE(0, "textconv", &opt,
N_("for blob objects, run textconv on object's content"), 'c'),
+ OPT_BOOL( 0, "allow-unknown-type", &unknown_type,
+ N_("allow -s and -t to work with broken/corrupt objects")),
{ OPTION_CALLBACK, 0, "batch", &batch, "format",
N_("show info and content of objects fed from the standard input"),
PARSE_OPT_OPTARG, batch_option_callback },
{ OPTION_CALLBACK, 0, "batch-check", &batch, "format",
N_("show info about objects fed from the standard input"),
PARSE_OPT_OPTARG, batch_option_callback },
+ OPT_BOOL(0, "follow-symlinks", &batch.follow_symlinks,
+ N_("follow in-tree symlinks (used with --batch or --batch-check)")),
OPT_END()
};
git_config(git_cat_file_config, NULL);
- if (argc != 3 && argc != 2)
- usage_with_options(cat_file_usage, options);
-
argc = parse_options(argc, argv, prefix, options, cat_file_usage, 0);
if (opt) {
usage_with_options(cat_file_usage, options);
}
+ if (batch.follow_symlinks && !batch.enabled) {
+ usage_with_options(cat_file_usage, options);
+ }
+
if (batch.enabled)
return batch_objects(&batch);
- return cat_one_file(opt, exp_type, obj_name);
+ if (unknown_type && opt != 't' && opt != 's')
+ die("git cat-file --allow-unknown-type: use with -s or -t");
+ return cat_one_file(opt, exp_type, obj_name, unknown_type);
}
/* Check out named files first */
for (i = 0; i < argc; i++) {
const char *arg = argv[i];
- const char *p;
+ char *p;
if (all)
die("git checkout-index: don't mix '--all' and explicit filenames");
die("git checkout-index: don't mix '--stdin' and explicit filenames");
p = prefix_path(prefix, prefix_length, arg);
checkout_file(p, prefix);
- if (p < arg || p > arg + strlen(arg))
- free((char *)p);
+ free(p);
}
if (read_from_stdin) {
die("git checkout-index: don't mix '--all' and '--stdin'");
while (strbuf_getline(&buf, stdin, line_termination) != EOF) {
- const char *p;
+ char *p;
if (line_termination && buf.buf[0] == '"') {
strbuf_reset(&nbuf);
if (unquote_c_style(&nbuf, buf.buf, NULL))
}
p = prefix_path(prefix, prefix_length, buf.buf);
checkout_file(p, prefix);
- if (p < buf.buf || p > buf.buf + buf.len)
- free((char *)p);
+ free(p);
}
strbuf_release(&nbuf);
strbuf_release(&buf);
#include "resolve-undo.h"
#include "submodule.h"
#include "argv-array.h"
+#include "sigchain.h"
static const char * const checkout_usage[] = {
N_("git checkout [<options>] <branch>"),
int writeout_stage;
int overwrite_ignore;
int ignore_skipworktree;
+ int ignore_other_worktrees;
const char *new_branch;
const char *new_branch_force;
const char *prefix;
struct pathspec pathspec;
struct tree *source_tree;
+
+ const char *new_worktree;
+ const char **saved_argv;
+ int new_worktree_mode;
};
static int post_checkout_hook(struct commit *old, struct commit *new,
die(_("Cannot update paths and switch to branch '%s' at the same time."),
opts->new_branch);
+ if (opts->new_worktree)
+ die(_("'%s' cannot be used with updating paths"), "--to");
+
if (opts->patch_mode)
return run_add_interactive(revision, "--patch=checkout",
&opts->pathspec);
const char *name; /* The short name used */
const char *path; /* The full name of a real branch */
struct commit *commit; /* The named commit */
+ /*
+ * if not null the branch is detached because it's already
+ * checked out in this checkout
+ */
+ char *checkout;
};
static void setup_branch_path(struct branch_info *branch)
topts.dir->flags |= DIR_SHOW_IGNORED;
setup_standard_excludes(topts.dir);
}
- tree = parse_tree_indirect(old->commit ?
+ tree = parse_tree_indirect(old->commit && !opts->new_worktree_mode ?
old->commit->object.sha1 :
EMPTY_TREE_SHA1_BIN);
init_tree_desc(&trees[0], tree->buffer, tree->size);
if (opts->new_orphan_branch) {
if (opts->new_branch_log && !log_all_ref_updates) {
int temp;
- char log_file[PATH_MAX];
- char *ref_name = mkpath("refs/heads/%s", opts->new_orphan_branch);
+ struct strbuf log_file = STRBUF_INIT;
+ int ret;
+ const char *ref_name;
+ ref_name = mkpath("refs/heads/%s", opts->new_orphan_branch);
temp = log_all_ref_updates;
log_all_ref_updates = 1;
- if (log_ref_setup(ref_name, log_file, sizeof(log_file))) {
+ ret = log_ref_setup(ref_name, &log_file);
+ log_all_ref_updates = temp;
+ strbuf_release(&log_file);
+ if (ret) {
fprintf(stderr, _("Can not do reflog for '%s'\n"),
opts->new_orphan_branch);
- log_all_ref_updates = temp;
return;
}
- log_all_ref_updates = temp;
}
}
else
}
static int add_pending_uninteresting_ref(const char *refname,
- const unsigned char *sha1,
+ const struct object_id *oid,
int flags, void *cb_data)
{
- add_pending_sha1(cb_data, refname, sha1, UNINTERESTING);
+ add_pending_sha1(cb_data, refname, oid->hash, UNINTERESTING);
return 0;
}
if (advice_detached_head)
fprintf(stderr,
- _(
+ Q_(
+ /* The singular version */
+ "If you want to keep it by creating a new branch, "
+ "this may be a good time\nto do so with:\n\n"
+ " git branch <new-branch-name> %s\n\n",
+ /* The plural version */
"If you want to keep them by creating a new branch, "
"this may be a good time\nto do so with:\n\n"
- " git branch <new-branch-name> %s\n\n"),
+ " git branch <new-branch-name> %s\n\n",
+ /* Give ngettext() the count */
+ lost),
find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV));
}
return ret;
}
- if (!opts->quiet && !old.path && old.commit && new->commit != old.commit)
+ if (!opts->quiet && !old.path && old.commit &&
+ new->commit != old.commit && !opts->new_worktree_mode)
orphaned_commit_warning(old.commit, new->commit);
update_refs_for_switch(opts, &old, new);
return ret || writeout_error;
}
+static char *junk_work_tree;
+static char *junk_git_dir;
+static int is_junk;
+static pid_t junk_pid;
+
+static void remove_junk(void)
+{
+ struct strbuf sb = STRBUF_INIT;
+ if (!is_junk || getpid() != junk_pid)
+ return;
+ if (junk_git_dir) {
+ strbuf_addstr(&sb, junk_git_dir);
+ remove_dir_recursively(&sb, 0);
+ strbuf_reset(&sb);
+ }
+ if (junk_work_tree) {
+ strbuf_addstr(&sb, junk_work_tree);
+ remove_dir_recursively(&sb, 0);
+ }
+ strbuf_release(&sb);
+}
+
+static void remove_junk_on_signal(int signo)
+{
+ remove_junk();
+ sigchain_pop(signo);
+ raise(signo);
+}
+
+static int prepare_linked_checkout(const struct checkout_opts *opts,
+ struct branch_info *new)
+{
+ struct strbuf sb_git = STRBUF_INIT, sb_repo = STRBUF_INIT;
+ struct strbuf sb = STRBUF_INIT;
+ const char *path = opts->new_worktree, *name;
+ struct stat st;
+ struct child_process cp;
+ int counter = 0, len, ret;
+
+ if (!new->commit)
+ die(_("no branch specified"));
+ if (file_exists(path) && !is_empty_dir(path))
+ die(_("'%s' already exists"), path);
+
+ len = strlen(path);
+ while (len && is_dir_sep(path[len - 1]))
+ len--;
+
+ for (name = path + len - 1; name > path; name--)
+ if (is_dir_sep(*name)) {
+ name++;
+ break;
+ }
+ strbuf_addstr(&sb_repo,
+ git_path("worktrees/%.*s", (int)(path + len - name), name));
+ len = sb_repo.len;
+ if (safe_create_leading_directories_const(sb_repo.buf))
+ die_errno(_("could not create leading directories of '%s'"),
+ sb_repo.buf);
+ while (!stat(sb_repo.buf, &st)) {
+ counter++;
+ strbuf_setlen(&sb_repo, len);
+ strbuf_addf(&sb_repo, "%d", counter);
+ }
+ name = strrchr(sb_repo.buf, '/') + 1;
+
+ junk_pid = getpid();
+ atexit(remove_junk);
+ sigchain_push_common(remove_junk_on_signal);
+
+ if (mkdir(sb_repo.buf, 0777))
+ die_errno(_("could not create directory of '%s'"), sb_repo.buf);
+ junk_git_dir = xstrdup(sb_repo.buf);
+ is_junk = 1;
+
+ /*
+ * lock the incomplete repo so prune won't delete it, unlock
+ * after the preparation is over.
+ */
+ strbuf_addf(&sb, "%s/locked", sb_repo.buf);
+ write_file(sb.buf, 1, "initializing\n");
+
+ strbuf_addf(&sb_git, "%s/.git", path);
+ if (safe_create_leading_directories_const(sb_git.buf))
+ die_errno(_("could not create leading directories of '%s'"),
+ sb_git.buf);
+ junk_work_tree = xstrdup(path);
+
+ strbuf_reset(&sb);
+ strbuf_addf(&sb, "%s/gitdir", sb_repo.buf);
+ write_file(sb.buf, 1, "%s\n", real_path(sb_git.buf));
+ write_file(sb_git.buf, 1, "gitdir: %s/worktrees/%s\n",
+ real_path(get_git_common_dir()), name);
+ /*
+ * This is to keep resolve_ref() happy. We need a valid HEAD
+ * or is_git_directory() will reject the directory. Any valid
+ * value would do because this value will be ignored and
+ * replaced at the next (real) checkout.
+ */
+ strbuf_reset(&sb);
+ strbuf_addf(&sb, "%s/HEAD", sb_repo.buf);
+ write_file(sb.buf, 1, "%s\n", sha1_to_hex(new->commit->object.sha1));
+ strbuf_reset(&sb);
+ strbuf_addf(&sb, "%s/commondir", sb_repo.buf);
+ write_file(sb.buf, 1, "../..\n");
+
+ if (!opts->quiet)
+ fprintf_ln(stderr, _("Enter %s (identifier %s)"), path, name);
+
+ setenv("GIT_CHECKOUT_NEW_WORKTREE", "1", 1);
+ setenv(GIT_DIR_ENVIRONMENT, sb_git.buf, 1);
+ setenv(GIT_WORK_TREE_ENVIRONMENT, path, 1);
+ memset(&cp, 0, sizeof(cp));
+ cp.git_cmd = 1;
+ cp.argv = opts->saved_argv;
+ ret = run_command(&cp);
+ if (!ret) {
+ is_junk = 0;
+ free(junk_work_tree);
+ free(junk_git_dir);
+ junk_work_tree = NULL;
+ junk_git_dir = NULL;
+ }
+ strbuf_reset(&sb);
+ strbuf_addf(&sb, "%s/locked", sb_repo.buf);
+ unlink_or_warn(sb.buf);
+ strbuf_release(&sb);
+ strbuf_release(&sb_repo);
+ strbuf_release(&sb_git);
+ return ret;
+}
+
static int git_checkout_config(const char *var, const char *value, void *cb)
{
if (!strcmp(var, "diff.ignoresubmodules")) {
return NULL;
}
+static void check_linked_checkout(struct branch_info *new, const char *id)
+{
+ struct strbuf sb = STRBUF_INIT;
+ struct strbuf path = STRBUF_INIT;
+ struct strbuf gitdir = STRBUF_INIT;
+ const char *start, *end;
+
+ if (id)
+ strbuf_addf(&path, "%s/worktrees/%s/HEAD", get_git_common_dir(), id);
+ else
+ strbuf_addf(&path, "%s/HEAD", get_git_common_dir());
+
+ if (strbuf_read_file(&sb, path.buf, 0) < 0 ||
+ !skip_prefix(sb.buf, "ref:", &start))
+ goto done;
+ while (isspace(*start))
+ start++;
+ end = start;
+ while (*end && !isspace(*end))
+ end++;
+ if (strncmp(start, new->path, end - start) || new->path[end - start] != '\0')
+ goto done;
+ if (id) {
+ strbuf_reset(&path);
+ strbuf_addf(&path, "%s/worktrees/%s/gitdir", get_git_common_dir(), id);
+ if (strbuf_read_file(&gitdir, path.buf, 0) <= 0)
+ goto done;
+ strbuf_rtrim(&gitdir);
+ } else
+ strbuf_addstr(&gitdir, get_git_common_dir());
+ die(_("'%s' is already checked out at '%s'"), new->name, gitdir.buf);
+done:
+ strbuf_release(&path);
+ strbuf_release(&sb);
+ strbuf_release(&gitdir);
+}
+
+static void check_linked_checkouts(struct branch_info *new)
+{
+ struct strbuf path = STRBUF_INIT;
+ DIR *dir;
+ struct dirent *d;
+
+ strbuf_addf(&path, "%s/worktrees", get_git_common_dir());
+ if ((dir = opendir(path.buf)) == NULL) {
+ strbuf_release(&path);
+ return;
+ }
+
+ /*
+ * $GIT_COMMON_DIR/HEAD is practically outside
+ * $GIT_DIR so resolve_ref_unsafe() won't work (it
+ * uses git_path). Parse the ref ourselves.
+ */
+ check_linked_checkout(new, NULL);
+
+ while ((d = readdir(dir)) != NULL) {
+ if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
+ continue;
+ check_linked_checkout(new, d->d_name);
+ }
+ strbuf_release(&path);
+ closedir(dir);
+}
+
static int parse_branchname_arg(int argc, const char **argv,
int dwim_new_local_branch_ok,
struct branch_info *new,
- struct tree **source_tree,
- unsigned char rev[20],
- const char **new_branch)
+ struct checkout_opts *opts,
+ unsigned char rev[20])
{
+ struct tree **source_tree = &opts->source_tree;
+ const char **new_branch = &opts->new_branch;
+ int force_detach = opts->force_detach;
int argcount = 0;
unsigned char branch_rev[20];
const char *arg;
else
new->path = NULL; /* not an existing branch */
+ if (new->path && !force_detach && !*new_branch) {
+ unsigned char sha1[20];
+ int flag;
+ char *head_ref = resolve_refdup("HEAD", 0, sha1, &flag);
+ if (head_ref &&
+ (!(flag & REF_ISSYMREF) || strcmp(head_ref, new->path)) &&
+ !opts->ignore_other_worktrees)
+ check_linked_checkouts(new);
+ free(head_ref);
+ }
+
new->commit = lookup_commit_reference_gently(rev, 1);
if (!new->commit) {
/* not a commit */
die(_("Cannot switch branch to a non-commit '%s'"),
new->name);
+ if (opts->new_worktree)
+ return prepare_linked_checkout(opts, new);
+
if (!new->commit && opts->new_branch) {
unsigned char rev[20];
int flag;
N_("do not limit pathspecs to sparse entries only")),
OPT_HIDDEN_BOOL(0, "guess", &dwim_new_local_branch,
N_("second guess 'git checkout <no-such-branch>'")),
+ OPT_FILENAME(0, "to", &opts.new_worktree,
+ N_("check a branch out in a separate working directory")),
+ OPT_BOOL(0, "ignore-other-worktrees", &opts.ignore_other_worktrees,
+ N_("do not check if another worktree is holding the given ref")),
OPT_END(),
};
opts.overwrite_ignore = 1;
opts.prefix = prefix;
+ opts.saved_argv = xmalloc(sizeof(const char *) * (argc + 2));
+ memcpy(opts.saved_argv, argv, sizeof(const char *) * (argc + 1));
+
gitmodules_config();
git_config(git_checkout_config, &opts);
argc = parse_options(argc, argv, prefix, options, checkout_usage,
PARSE_OPT_KEEP_DASHDASH);
+ /* recursive execution from checkout_new_worktree() */
+ opts.new_worktree_mode = getenv("GIT_CHECKOUT_NEW_WORKTREE") != NULL;
+ if (opts.new_worktree_mode)
+ opts.new_worktree = NULL;
+
+ if (!opts.new_worktree)
+ setup_work_tree();
+
if (conflict_style) {
opts.merge = 1; /* implied */
git_xmerge_config("merge.conflictstyle", conflict_style, NULL);
opts.track == BRANCH_TRACK_UNSPECIFIED &&
!opts.new_branch;
int n = parse_branchname_arg(argc, argv, dwim_ok,
- &new, &opts.source_tree,
- rev, &opts.new_branch);
+ &new, &opts, rev);
argv += n;
argc -= n;
}
struct strbuf line = STRBUF_INIT;
while (strbuf_getline(&line, in, '\n') != EOF) {
- char *abs_path, abs_buf[PATH_MAX];
+ char *abs_path;
if (!line.len || line.buf[0] == '#')
continue;
if (is_absolute_path(line.buf)) {
add_to_alternates_file(line.buf);
continue;
}
- abs_path = mkpath("%s/objects/%s", src_repo, line.buf);
- normalize_path_copy(abs_buf, abs_path);
- add_to_alternates_file(abs_buf);
+ abs_path = mkpathdup("%s/objects/%s", src_repo, line.buf);
+ normalize_path_copy(abs_path, abs_path);
+ add_to_alternates_file(abs_path);
+ free(abs_path);
}
strbuf_release(&line);
fclose(in);
whence = FROM_MERGE;
else if (file_exists(git_path("CHERRY_PICK_HEAD"))) {
whence = FROM_CHERRY_PICK;
- if (file_exists(git_path("sequencer")))
+ if (file_exists(git_path(SEQ_DIR)))
sequencer_in_use = 1;
}
else
refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, &s.pathspec, NULL, NULL);
fd = hold_locked_index(&index_lock, 0);
- if (0 <= fd)
- update_index_if_able(&the_index, &index_lock);
s.is_initial = get_sha1(s.reference, sha1) ? 1 : 0;
s.ignore_submodule_arg = ignore_submodule_arg;
wt_status_collect(&s);
+ if (0 <= fd)
+ update_index_if_able(&the_index, &index_lock);
+
if (s.relative_paths)
s.prefix = prefix;
key_regexp = (regex_t*)xmalloc(sizeof(regex_t));
if (regcomp(key_regexp, key, REG_EXTENDED)) {
- fprintf(stderr, "Invalid key pattern: %s\n", key_);
+ error("invalid key pattern: %s", key_);
free(key_regexp);
key_regexp = NULL;
ret = CONFIG_INVALID_PATTERN;
regexp = (regex_t*)xmalloc(sizeof(regex_t));
if (regcomp(regexp, regex_, REG_EXTENDED)) {
- fprintf(stderr, "Invalid pattern: %s\n", regex_);
+ error("invalid pattern: %s", regex_);
free(regexp);
regexp = NULL;
ret = CONFIG_INVALID_PATTERN;
/* we do not take arguments other than flags for now */
if (argc)
usage_with_options(count_objects_usage, opts);
- if (verbose)
+ if (verbose) {
report_garbage = real_report_garbage;
+ report_linked_checkout_garbage();
+ }
for_each_loose_file_in_objdir(get_object_directory(),
count_loose, count_cruft, NULL, NULL);
}
}
-static int get_name(const char *path, const unsigned char *sha1, int flag, void *cb_data)
+static int get_name(const char *path, const struct object_id *oid, int flag, void *cb_data)
{
int is_tag = starts_with(path, "refs/tags/");
- unsigned char peeled[20];
+ struct object_id peeled;
int is_annotated, prio;
/* Reject anything outside refs/tags/ unless --all */
return 0;
/* Is it annotated? */
- if (!peel_ref(path, peeled)) {
- is_annotated = !!hashcmp(sha1, peeled);
+ if (!peel_ref(path, peeled.hash)) {
+ is_annotated = !!oidcmp(oid, &peeled);
} else {
- hashcpy(peeled, sha1);
+ oidcpy(&peeled, oid);
is_annotated = 0;
}
else
prio = 0;
- add_to_known_names(all ? path + 5 : path + 10, peeled, prio, sha1);
+ add_to_known_names(all ? path + 5 : path + 10, peeled.hash, prio, oid->hash);
return 0;
}
}
}
-static int add_existing(const char *refname, const unsigned char *sha1,
+static int add_existing(const char *refname, const struct object_id *oid,
int flag, void *cbdata)
{
struct string_list *list = (struct string_list *)cbdata;
struct string_list_item *item = string_list_insert(list, refname);
- item->util = xmalloc(20);
- hashcpy(item->util, sha1);
+ struct object_id *old_oid = xmalloc(sizeof(*old_oid));
+
+ oidcpy(old_oid, oid);
+ item->util = old_oid;
return 0;
}
struct strbuf note = STRBUF_INIT;
const char *what, *kind;
struct ref *rm;
- char *url, *filename = dry_run ? "/dev/null" : git_path("FETCH_HEAD");
+ char *url;
+ const char *filename = dry_run ? "/dev/null" : git_path("FETCH_HEAD");
int want_status;
fp = fopen(filename, "a");
static int truncate_fetch_head(void)
{
- char *filename = git_path("FETCH_HEAD");
+ const char *filename = git_path("FETCH_HEAD");
FILE *fp = fopen(filename, "w");
if (!fp)
struct string_list_item *peer_item =
string_list_lookup(&existing_refs,
rm->peer_ref->name);
- if (peer_item)
- hashcpy(rm->peer_ref->old_sha1,
- peer_item->util);
+ if (peer_item) {
+ struct object_id *old_oid = peer_item->util;
+ hashcpy(rm->peer_ref->old_sha1, old_oid->hash);
+ }
}
}
{ "contents:body" },
{ "contents:signature" },
{ "upstream" },
+ { "push" },
{ "symref" },
{ "flag" },
{ "HEAD" },
else if (starts_with(name, "symref"))
refname = ref->symref ? ref->symref : "";
else if (starts_with(name, "upstream")) {
+ const char *branch_name;
/* only local branches may have an upstream */
- if (!starts_with(ref->refname, "refs/heads/"))
+ if (!skip_prefix(ref->refname, "refs/heads/",
+ &branch_name))
continue;
- branch = branch_get(ref->refname + 11);
+ branch = branch_get(branch_name);
- if (!branch || !branch->merge || !branch->merge[0] ||
- !branch->merge[0]->dst)
+ refname = branch_get_upstream(branch, NULL);
+ if (!refname)
+ continue;
+ } else if (starts_with(name, "push")) {
+ const char *branch_name;
+ if (!skip_prefix(ref->refname, "refs/heads/",
+ &branch_name))
+ continue;
+ branch = branch_get(branch_name);
+
+ refname = branch_get_push(branch, NULL);
+ if (!refname)
continue;
- refname = branch->merge[0]->dst;
} else if (starts_with(name, "color:")) {
char color[COLOR_MAXLEN] = "";
refname = shorten_unambiguous_ref(refname,
warn_ambiguous_refs);
else if (!strcmp(formatp, "track") &&
- starts_with(name, "upstream")) {
+ (starts_with(name, "upstream") ||
+ starts_with(name, "push"))) {
char buf[40];
if (stat_tracking_info(branch, &num_ours,
- &num_theirs) != 1)
+ &num_theirs, NULL))
continue;
if (!num_ours && !num_theirs)
}
continue;
} else if (!strcmp(formatp, "trackshort") &&
- starts_with(name, "upstream")) {
+ (starts_with(name, "upstream") ||
+ starts_with(name, "push"))) {
assert(branch);
if (stat_tracking_info(branch, &num_ours,
- &num_theirs) != 1)
+ &num_theirs, NULL))
continue;
if (!num_ours && !num_theirs)
* A call-back given to for_each_ref(). Filter refs and keep them for
* later object processing.
*/
-static int grab_single_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+static int grab_single_ref(const char *refname, const struct object_id *oid,
+ int flag, void *cb_data)
{
struct grab_ref_cbdata *cb = cb_data;
struct refinfo *ref;
return 0;
}
+ if (flag & REF_ISBROKEN) {
+ warning("ignoring broken ref %s", refname);
+ return 0;
+ }
+
if (*cb->grab_pattern) {
const char **pattern;
int namelen = strlen(refname);
*/
ref = xcalloc(1, sizeof(*ref));
ref->refname = xstrdup(refname);
- hashcpy(ref->objectname, sha1);
+ hashcpy(ref->objectname, oid->hash);
ref->flag = flag;
cnt = cb->grab_cnt;
static int check_full = 1;
static int check_strict;
static int keep_cache_objects;
-static unsigned char head_sha1[20];
+static struct object_id head_oid;
static const char *head_points_at;
static int errors_found;
static int write_lost_and_found;
printf("dangling %s %s\n", typename(obj->type),
sha1_to_hex(obj->sha1));
if (write_lost_and_found) {
- char *filename = git_path("lost-found/%s/%s",
+ const char *filename = git_path("lost-found/%s/%s",
obj->type == OBJ_COMMIT ? "commit" : "other",
sha1_to_hex(obj->sha1));
FILE *f;
- if (safe_create_leading_directories(filename)) {
+ if (safe_create_leading_directories_const(filename)) {
error("Could not create lost-found");
return;
}
static int default_refs;
+static void fsck_handle_reflog_sha1(const char *refname, unsigned char *sha1)
+{
+ struct object *obj;
+
+ if (!is_null_sha1(sha1)) {
+ obj = lookup_object(sha1);
+ if (obj) {
+ obj->used = 1;
+ mark_object_reachable(obj);
+ } else {
+ error("%s: invalid reflog entry %s", refname, sha1_to_hex(sha1));
+ errors_found |= ERROR_REACHABLE;
+ }
+ }
+}
+
static int fsck_handle_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
const char *email, unsigned long timestamp, int tz,
const char *message, void *cb_data)
{
- struct object *obj;
+ const char *refname = cb_data;
if (verbose)
fprintf(stderr, "Checking reflog %s->%s\n",
sha1_to_hex(osha1), sha1_to_hex(nsha1));
- if (!is_null_sha1(osha1)) {
- obj = lookup_object(osha1);
- if (obj) {
- obj->used = 1;
- mark_object_reachable(obj);
- }
- }
- obj = lookup_object(nsha1);
- if (obj) {
- obj->used = 1;
- mark_object_reachable(obj);
- }
+ fsck_handle_reflog_sha1(refname, osha1);
+ fsck_handle_reflog_sha1(refname, nsha1);
return 0;
}
-static int fsck_handle_reflog(const char *logname, const unsigned char *sha1, int flag, void *cb_data)
+static int fsck_handle_reflog(const char *logname, const struct object_id *oid,
+ int flag, void *cb_data)
{
- for_each_reflog_ent(logname, fsck_handle_reflog_ent, NULL);
+ for_each_reflog_ent(logname, fsck_handle_reflog_ent, (void *)logname);
return 0;
}
-static int fsck_handle_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+static int fsck_handle_ref(const char *refname, const struct object_id *oid,
+ int flag, void *cb_data)
{
struct object *obj;
- obj = parse_object(sha1);
+ obj = parse_object(oid->hash);
if (!obj) {
- error("%s: invalid sha1 pointer %s", refname, sha1_to_hex(sha1));
+ error("%s: invalid sha1 pointer %s", refname, oid_to_hex(oid));
errors_found |= ERROR_REACHABLE;
/* We'll continue with the rest despite the error.. */
return 0;
static void get_default_heads(void)
{
- if (head_points_at && !is_null_sha1(head_sha1))
- fsck_handle_ref("HEAD", head_sha1, 0, NULL);
+ if (head_points_at && !is_null_oid(&head_oid))
+ fsck_handle_ref("HEAD", &head_oid, 0, NULL);
for_each_rawref(fsck_handle_ref, NULL);
if (include_reflogs)
for_each_reflog(fsck_handle_reflog, NULL);
if (verbose)
fprintf(stderr, "Checking HEAD link\n");
- head_points_at = resolve_ref_unsafe("HEAD", 0, head_sha1, &flag);
+ head_points_at = resolve_ref_unsafe("HEAD", 0, head_oid.hash, &flag);
if (!head_points_at)
return error("Invalid HEAD");
if (!strcmp(head_points_at, "HEAD"))
else if (!starts_with(head_points_at, "refs/heads/"))
return error("HEAD points to something strange (%s)",
head_points_at);
- if (is_null_sha1(head_sha1)) {
+ if (is_null_oid(&head_oid)) {
if (null_is_error)
return error("HEAD: detached HEAD points at nothing");
fprintf(stderr, "notice: HEAD points to an unborn branch (%s)\n",
static int gc_auto_pack_limit = 50;
static int detach_auto = 1;
static const char *prune_expire = "2.weeks.ago";
+static const char *prune_worktrees_expire = "3.months.ago";
static struct argv_array pack_refs_cmd = ARGV_ARRAY_INIT;
static struct argv_array reflog = ARGV_ARRAY_INIT;
static struct argv_array repack = ARGV_ARRAY_INIT;
static struct argv_array prune = ARGV_ARRAY_INIT;
+static struct argv_array prune_worktrees = ARGV_ARRAY_INIT;
static struct argv_array rerere = ARGV_ARRAY_INIT;
static char *pidfile;
raise(signo);
}
+static void git_config_date_string(const char *key, const char **output)
+{
+ if (git_config_get_string_const(key, output))
+ return;
+ if (strcmp(*output, "now")) {
+ unsigned long now = approxidate("now");
+ if (approxidate(*output) >= now)
+ git_die_config(key, _("Invalid %s: '%s'"), key, *output);
+ }
+}
+
static void gc_config(void)
{
const char *value;
git_config_get_int("gc.auto", &gc_auto_threshold);
git_config_get_int("gc.autopacklimit", &gc_auto_pack_limit);
git_config_get_bool("gc.autodetach", &detach_auto);
-
- if (!git_config_get_string_const("gc.pruneexpire", &prune_expire)) {
- if (strcmp(prune_expire, "now")) {
- unsigned long now = approxidate("now");
- if (approxidate(prune_expire) >= now) {
- git_die_config("gc.pruneexpire", _("Invalid gc.pruneexpire: '%s'"),
- prune_expire);
- }
- }
- }
+ git_config_date_string("gc.pruneexpire", &prune_expire);
+ git_config_date_string("gc.pruneworktreesexpire", &prune_worktrees_expire);
git_config(git_default_config, NULL);
}
argv_array_pushl(&pack_refs_cmd, "pack-refs", "--all", "--prune", NULL);
argv_array_pushl(&reflog, "reflog", "expire", "--all", NULL);
argv_array_pushl(&repack, "repack", "-d", "-l", NULL);
- argv_array_pushl(&prune, "prune", "--expire", NULL );
+ argv_array_pushl(&prune, "prune", "--expire", NULL);
+ argv_array_pushl(&prune_worktrees, "prune", "--worktrees", "--expire", NULL);
argv_array_pushl(&rerere, "rerere", "gc", NULL);
gc_config();
return error(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]);
+ }
+
if (run_command_v_opt(rerere.argv, RUN_GIT_CMD))
return error(FAILED_RUN, rerere.argv[0]);
struct object_entry {
struct pack_idx_entry idx;
unsigned long size;
- unsigned int hdr_size;
- enum object_type type;
- enum object_type real_type;
- unsigned delta_depth;
- int base_object_no;
+ unsigned char hdr_size;
+ signed char type;
+ signed char real_type;
};
-union delta_base {
- unsigned char sha1[20];
- off_t offset;
+struct object_stat {
+ unsigned delta_depth;
+ int base_object_no;
};
struct base_data {
int pack_fd;
};
-/*
- * Even if sizeof(union delta_base) == 24 on 64-bit archs, we really want
- * to memcmp() only the first 20 bytes.
- */
-#define UNION_BASE_SZ 20
-
#define FLAG_LINK (1u<<20)
#define FLAG_CHECKED (1u<<21)
-struct delta_entry {
- union delta_base base;
+struct ofs_delta_entry {
+ off_t offset;
+ int obj_no;
+};
+
+struct ref_delta_entry {
+ unsigned char sha1[20];
int obj_no;
};
static struct object_entry *objects;
-static struct delta_entry *deltas;
+static struct object_stat *obj_stat;
+static struct ofs_delta_entry *ofs_deltas;
+static struct ref_delta_entry *ref_deltas;
static struct thread_local nothread_data;
static int nr_objects;
-static int nr_deltas;
+static int nr_ofs_deltas;
+static int nr_ref_deltas;
+static int ref_deltas_alloc;
static int nr_resolved_deltas;
static int nr_threads;
}
static void *unpack_raw_entry(struct object_entry *obj,
- union delta_base *delta_base,
+ off_t *ofs_offset,
+ unsigned char *ref_sha1,
unsigned char *sha1)
{
unsigned char *p;
switch (obj->type) {
case OBJ_REF_DELTA:
- hashcpy(delta_base->sha1, fill(20));
+ hashcpy(ref_sha1, fill(20));
use(20);
break;
case OBJ_OFS_DELTA:
- memset(delta_base, 0, sizeof(*delta_base));
p = fill(1);
c = *p;
use(1);
use(1);
base_offset = (base_offset << 7) + (c & 127);
}
- delta_base->offset = obj->idx.offset - base_offset;
- if (delta_base->offset <= 0 || delta_base->offset >= obj->idx.offset)
+ *ofs_offset = obj->idx.offset - base_offset;
+ if (*ofs_offset <= 0 || *ofs_offset >= obj->idx.offset)
bad_object(obj->idx.offset, _("delta base offset is out of bound"));
break;
case OBJ_COMMIT:
return unpack_data(obj, NULL, NULL);
}
-static int compare_delta_bases(const union delta_base *base1,
- const union delta_base *base2,
- enum object_type type1,
- enum object_type type2)
+static int compare_ofs_delta_bases(off_t offset1, off_t offset2,
+ enum object_type type1,
+ enum object_type type2)
+{
+ int cmp = type1 - type2;
+ if (cmp)
+ return cmp;
+ return offset1 < offset2 ? -1 :
+ offset1 > offset2 ? 1 :
+ 0;
+}
+
+static int find_ofs_delta(const off_t offset, enum object_type type)
+{
+ int first = 0, last = nr_ofs_deltas;
+
+ while (first < last) {
+ int next = (first + last) / 2;
+ struct ofs_delta_entry *delta = &ofs_deltas[next];
+ int cmp;
+
+ cmp = compare_ofs_delta_bases(offset, delta->offset,
+ type, objects[delta->obj_no].type);
+ if (!cmp)
+ return next;
+ if (cmp < 0) {
+ last = next;
+ continue;
+ }
+ first = next+1;
+ }
+ return -first-1;
+}
+
+static void find_ofs_delta_children(off_t offset,
+ int *first_index, int *last_index,
+ enum object_type type)
+{
+ int first = find_ofs_delta(offset, type);
+ int last = first;
+ int end = nr_ofs_deltas - 1;
+
+ if (first < 0) {
+ *first_index = 0;
+ *last_index = -1;
+ return;
+ }
+ while (first > 0 && ofs_deltas[first - 1].offset == offset)
+ --first;
+ while (last < end && ofs_deltas[last + 1].offset == offset)
+ ++last;
+ *first_index = first;
+ *last_index = last;
+}
+
+static int compare_ref_delta_bases(const unsigned char *sha1,
+ const unsigned char *sha2,
+ enum object_type type1,
+ enum object_type type2)
{
int cmp = type1 - type2;
if (cmp)
return cmp;
- return memcmp(base1, base2, UNION_BASE_SZ);
+ return hashcmp(sha1, sha2);
}
-static int find_delta(const union delta_base *base, enum object_type type)
+static int find_ref_delta(const unsigned char *sha1, enum object_type type)
{
- int first = 0, last = nr_deltas;
-
- while (first < last) {
- int next = (first + last) / 2;
- struct delta_entry *delta = &deltas[next];
- int cmp;
-
- cmp = compare_delta_bases(base, &delta->base,
- type, objects[delta->obj_no].type);
- if (!cmp)
- return next;
- if (cmp < 0) {
- last = next;
- continue;
- }
- first = next+1;
- }
- return -first-1;
+ int first = 0, last = nr_ref_deltas;
+
+ while (first < last) {
+ int next = (first + last) / 2;
+ struct ref_delta_entry *delta = &ref_deltas[next];
+ int cmp;
+
+ cmp = compare_ref_delta_bases(sha1, delta->sha1,
+ type, objects[delta->obj_no].type);
+ if (!cmp)
+ return next;
+ if (cmp < 0) {
+ last = next;
+ continue;
+ }
+ first = next+1;
+ }
+ return -first-1;
}
-static void find_delta_children(const union delta_base *base,
- int *first_index, int *last_index,
- enum object_type type)
+static void find_ref_delta_children(const unsigned char *sha1,
+ int *first_index, int *last_index,
+ enum object_type type)
{
- int first = find_delta(base, type);
+ int first = find_ref_delta(sha1, type);
int last = first;
- int end = nr_deltas - 1;
+ int end = nr_ref_deltas - 1;
if (first < 0) {
*first_index = 0;
*last_index = -1;
return;
}
- while (first > 0 && !memcmp(&deltas[first - 1].base, base, UNION_BASE_SZ))
+ while (first > 0 && !hashcmp(ref_deltas[first - 1].sha1, sha1))
--first;
- while (last < end && !memcmp(&deltas[last + 1].base, base, UNION_BASE_SZ))
+ while (last < end && !hashcmp(ref_deltas[last + 1].sha1, sha1))
++last;
*first_index = first;
*last_index = last;
assert(data || obj_entry);
read_lock();
- collision_test_needed = has_sha1_file(sha1);
+ collision_test_needed = has_sha1_file_with_flags(sha1, HAS_SHA1_QUICK);
read_unlock();
if (collision_test_needed && !data) {
void *base_data, *delta_data;
if (show_stat) {
- delta_obj->delta_depth = base->obj->delta_depth + 1;
+ int i = delta_obj - objects;
+ int j = base->obj - objects;
+ obj_stat[i].delta_depth = obj_stat[j].delta_depth + 1;
deepest_delta_lock();
- if (deepest_delta < delta_obj->delta_depth)
- deepest_delta = delta_obj->delta_depth;
+ if (deepest_delta < obj_stat[i].delta_depth)
+ deepest_delta = obj_stat[i].delta_depth;
deepest_delta_unlock();
+ obj_stat[i].base_object_no = j;
}
- delta_obj->base_object_no = base->obj - objects;
delta_data = get_data_from_pack(delta_obj);
base_data = get_base_data(base);
result->obj = delta_obj;
* "want"; if so, swap in "set" and return true. Otherwise, leave it untouched
* and return false.
*/
-static int compare_and_swap_type(enum object_type *type,
+static int compare_and_swap_type(signed char *type,
enum object_type want,
enum object_type set)
{
struct base_data *prev_base)
{
if (base->ref_last == -1 && base->ofs_last == -1) {
- union delta_base base_spec;
-
- hashcpy(base_spec.sha1, base->obj->idx.sha1);
- find_delta_children(&base_spec,
- &base->ref_first, &base->ref_last, OBJ_REF_DELTA);
+ find_ref_delta_children(base->obj->idx.sha1,
+ &base->ref_first, &base->ref_last,
+ OBJ_REF_DELTA);
- memset(&base_spec, 0, sizeof(base_spec));
- base_spec.offset = base->obj->idx.offset;
- find_delta_children(&base_spec,
- &base->ofs_first, &base->ofs_last, OBJ_OFS_DELTA);
+ find_ofs_delta_children(base->obj->idx.offset,
+ &base->ofs_first, &base->ofs_last,
+ OBJ_OFS_DELTA);
if (base->ref_last == -1 && base->ofs_last == -1) {
free(base->data);
}
if (base->ref_first <= base->ref_last) {
- struct object_entry *child = objects + deltas[base->ref_first].obj_no;
+ struct object_entry *child = objects + ref_deltas[base->ref_first].obj_no;
struct base_data *result = alloc_base_data();
if (!compare_and_swap_type(&child->real_type, OBJ_REF_DELTA,
}
if (base->ofs_first <= base->ofs_last) {
- struct object_entry *child = objects + deltas[base->ofs_first].obj_no;
+ struct object_entry *child = objects + ofs_deltas[base->ofs_first].obj_no;
struct base_data *result = alloc_base_data();
assert(child->real_type == OBJ_OFS_DELTA);
}
}
-static int compare_delta_entry(const void *a, const void *b)
+static int compare_ofs_delta_entry(const void *a, const void *b)
{
- const struct delta_entry *delta_a = a;
- const struct delta_entry *delta_b = b;
+ const struct ofs_delta_entry *delta_a = a;
+ const struct ofs_delta_entry *delta_b = b;
- /* group by type (ref vs ofs) and then by value (sha-1 or offset) */
- return compare_delta_bases(&delta_a->base, &delta_b->base,
- objects[delta_a->obj_no].type,
- objects[delta_b->obj_no].type);
+ return delta_a->offset < delta_b->offset ? -1 :
+ delta_a->offset > delta_b->offset ? 1 :
+ 0;
+}
+
+static int compare_ref_delta_entry(const void *a, const void *b)
+{
+ const struct ref_delta_entry *delta_a = a;
+ const struct ref_delta_entry *delta_b = b;
+
+ return hashcmp(delta_a->sha1, delta_b->sha1);
}
static void resolve_base(struct object_entry *obj)
static void parse_pack_objects(unsigned char *sha1)
{
int i, nr_delays = 0;
- struct delta_entry *delta = deltas;
+ struct ofs_delta_entry *ofs_delta = ofs_deltas;
+ unsigned char ref_delta_sha1[20];
struct stat st;
if (verbose)
nr_objects);
for (i = 0; i < nr_objects; i++) {
struct object_entry *obj = &objects[i];
- void *data = unpack_raw_entry(obj, &delta->base, obj->idx.sha1);
+ void *data = unpack_raw_entry(obj, &ofs_delta->offset,
+ ref_delta_sha1, obj->idx.sha1);
obj->real_type = obj->type;
- if (is_delta_type(obj->type)) {
- nr_deltas++;
- delta->obj_no = i;
- delta++;
+ if (obj->type == OBJ_OFS_DELTA) {
+ nr_ofs_deltas++;
+ ofs_delta->obj_no = i;
+ ofs_delta++;
+ } else if (obj->type == OBJ_REF_DELTA) {
+ ALLOC_GROW(ref_deltas, nr_ref_deltas + 1, ref_deltas_alloc);
+ hashcpy(ref_deltas[nr_ref_deltas].sha1, ref_delta_sha1);
+ ref_deltas[nr_ref_deltas].obj_no = i;
+ nr_ref_deltas++;
} else if (!data) {
/* large blobs, check later */
obj->real_type = OBJ_BAD;
{
int i;
- if (!nr_deltas)
+ if (!nr_ofs_deltas && !nr_ref_deltas)
return;
/* Sort deltas by base SHA1/offset for fast searching */
- qsort(deltas, nr_deltas, sizeof(struct delta_entry),
- compare_delta_entry);
+ qsort(ofs_deltas, nr_ofs_deltas, sizeof(struct ofs_delta_entry),
+ compare_ofs_delta_entry);
+ qsort(ref_deltas, nr_ref_deltas, sizeof(struct ref_delta_entry),
+ compare_ref_delta_entry);
if (verbose)
- progress = start_progress(_("Resolving deltas"), nr_deltas);
+ progress = start_progress(_("Resolving deltas"),
+ nr_ref_deltas + nr_ofs_deltas);
#ifndef NO_PTHREADS
nr_dispatched = 0;
* - append objects to convert thin pack to full pack if required
* - write the final 20-byte SHA-1
*/
-static void fix_unresolved_deltas(struct sha1file *f, int nr_unresolved);
+static void fix_unresolved_deltas(struct sha1file *f);
static void conclude_pack(int fix_thin_pack, const char *curr_pack, unsigned char *pack_sha1)
{
- if (nr_deltas == nr_resolved_deltas) {
+ if (nr_ref_deltas + nr_ofs_deltas == nr_resolved_deltas) {
stop_progress(&progress);
/* Flush remaining pack final 20-byte SHA1. */
flush();
struct sha1file *f;
unsigned char read_sha1[20], tail_sha1[20];
struct strbuf msg = STRBUF_INIT;
- int nr_unresolved = nr_deltas - nr_resolved_deltas;
+ int nr_unresolved = nr_ofs_deltas + nr_ref_deltas - nr_resolved_deltas;
int nr_objects_initial = nr_objects;
if (nr_unresolved <= 0)
die(_("confusion beyond insanity"));
memset(objects + nr_objects + 1, 0,
nr_unresolved * sizeof(*objects));
f = sha1fd(output_fd, curr_pack);
- fix_unresolved_deltas(f, nr_unresolved);
+ fix_unresolved_deltas(f);
strbuf_addf(&msg, _("completed with %d local objects"),
nr_objects - nr_objects_initial);
stop_progress_msg(&progress, msg.buf);
die(_("Unexpected tail checksum for %s "
"(disk corruption?)"), curr_pack);
}
- if (nr_deltas != nr_resolved_deltas)
+ if (nr_ofs_deltas + nr_ref_deltas != nr_resolved_deltas)
die(Q_("pack has %d unresolved delta",
"pack has %d unresolved deltas",
- nr_deltas - nr_resolved_deltas),
- nr_deltas - nr_resolved_deltas);
+ nr_ofs_deltas + nr_ref_deltas - nr_resolved_deltas),
+ nr_ofs_deltas + nr_ref_deltas - nr_resolved_deltas);
}
static int write_compressed(struct sha1file *f, void *in, unsigned int size)
static int delta_pos_compare(const void *_a, const void *_b)
{
- struct delta_entry *a = *(struct delta_entry **)_a;
- struct delta_entry *b = *(struct delta_entry **)_b;
+ struct ref_delta_entry *a = *(struct ref_delta_entry **)_a;
+ struct ref_delta_entry *b = *(struct ref_delta_entry **)_b;
return a->obj_no - b->obj_no;
}
-static void fix_unresolved_deltas(struct sha1file *f, int nr_unresolved)
+static void fix_unresolved_deltas(struct sha1file *f)
{
- struct delta_entry **sorted_by_pos;
- int i, n = 0;
+ struct ref_delta_entry **sorted_by_pos;
+ int i;
/*
* Since many unresolved deltas may well be themselves base objects
* before deltas depending on them, a good heuristic is to start
* resolving deltas in the same order as their position in the pack.
*/
- sorted_by_pos = xmalloc(nr_unresolved * sizeof(*sorted_by_pos));
- for (i = 0; i < nr_deltas; i++) {
- if (objects[deltas[i].obj_no].real_type != OBJ_REF_DELTA)
- continue;
- sorted_by_pos[n++] = &deltas[i];
- }
- qsort(sorted_by_pos, n, sizeof(*sorted_by_pos), delta_pos_compare);
+ sorted_by_pos = xmalloc(nr_ref_deltas * sizeof(*sorted_by_pos));
+ for (i = 0; i < nr_ref_deltas; i++)
+ sorted_by_pos[i] = &ref_deltas[i];
+ qsort(sorted_by_pos, nr_ref_deltas, sizeof(*sorted_by_pos), delta_pos_compare);
- for (i = 0; i < n; i++) {
- struct delta_entry *d = sorted_by_pos[i];
+ for (i = 0; i < nr_ref_deltas; i++) {
+ struct ref_delta_entry *d = sorted_by_pos[i];
enum object_type type;
struct base_data *base_obj = alloc_base_data();
if (objects[d->obj_no].real_type != OBJ_REF_DELTA)
continue;
- base_obj->data = read_sha1_file(d->base.sha1, &type, &base_obj->size);
+ base_obj->data = read_sha1_file(d->sha1, &type, &base_obj->size);
if (!base_obj->data)
continue;
- if (check_sha1_signature(d->base.sha1, base_obj->data,
+ if (check_sha1_signature(d->sha1, base_obj->data,
base_obj->size, typename(type)))
- die(_("local object %s is corrupt"), sha1_to_hex(d->base.sha1));
- base_obj->obj = append_obj_to_pack(f, d->base.sha1,
+ die(_("local object %s is corrupt"), sha1_to_hex(d->sha1));
+ base_obj->obj = append_obj_to_pack(f, d->sha1,
base_obj->data, base_obj->size, type);
find_unresolved_deltas(base_obj);
display_progress(progress, nr_resolved_deltas);
static void show_pack_info(int stat_only)
{
- int i, baseobjects = nr_objects - nr_deltas;
+ int i, baseobjects = nr_objects - nr_ref_deltas - nr_ofs_deltas;
unsigned long *chain_histogram = NULL;
if (deepest_delta)
struct object_entry *obj = &objects[i];
if (is_delta_type(obj->type))
- chain_histogram[obj->delta_depth - 1]++;
+ chain_histogram[obj_stat[i].delta_depth - 1]++;
if (stat_only)
continue;
printf("%s %-6s %lu %lu %"PRIuMAX,
(unsigned long)(obj[1].idx.offset - obj->idx.offset),
(uintmax_t)obj->idx.offset);
if (is_delta_type(obj->type)) {
- struct object_entry *bobj = &objects[obj->base_object_no];
- printf(" %u %s", obj->delta_depth, sha1_to_hex(bobj->idx.sha1));
+ struct object_entry *bobj = &objects[obj_stat[i].base_object_no];
+ printf(" %u %s", obj_stat[i].delta_depth, sha1_to_hex(bobj->idx.sha1));
}
putchar('\n');
}
curr_pack = open_pack_file(pack_name);
parse_pack_header();
objects = xcalloc(nr_objects + 1, sizeof(struct object_entry));
- deltas = xcalloc(nr_objects, sizeof(struct delta_entry));
+ if (show_stat)
+ obj_stat = xcalloc(nr_objects + 1, sizeof(struct object_stat));
+ ofs_deltas = xcalloc(nr_objects, sizeof(struct ofs_delta_entry));
parse_pack_objects(pack_sha1);
resolve_deltas();
conclude_pack(fix_thin_pack, curr_pack, pack_sha1);
- free(deltas);
+ free(ofs_deltas);
+ free(ref_deltas);
if (strict)
foreign_nr = check_objects();
static void separate_git_dir(const char *git_dir)
{
struct stat st;
- FILE *fp;
if (!stat(git_link, &st)) {
const char *src;
die_errno(_("unable to move %s to %s"), src, git_dir);
}
- fp = fopen(git_link, "w");
- if (!fp)
- die(_("Could not create git link %s"), git_link);
- fprintf(fp, "gitdir: %s\n", git_dir);
- fclose(fp);
+ write_file(git_link, 1, "gitdir: %s\n", git_dir);
}
int init_db(const char *template_dir, unsigned int flags)
static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids)
{
struct rev_info check_rev;
- struct commit *commit;
+ struct commit *commit, *c1, *c2;
struct object *o1, *o2;
unsigned flags1, flags2;
die(_("Need exactly one range."));
o1 = rev->pending.objects[0].item;
- flags1 = o1->flags;
o2 = rev->pending.objects[1].item;
+ flags1 = o1->flags;
flags2 = o2->flags;
+ c1 = lookup_commit_reference(o1->sha1);
+ c2 = lookup_commit_reference(o2->sha1);
if ((flags1 & UNINTERESTING) == (flags2 & UNINTERESTING))
die(_("Not a range."));
}
/* reset for next revision walk */
- clear_commit_marks((struct commit *)o1,
- SEEN | UNINTERESTING | SHOWN | ADDED);
- clear_commit_marks((struct commit *)o2,
- SEEN | UNINTERESTING | SHOWN | ADDED);
+ clear_commit_marks(c1, SEEN | UNINTERESTING | SHOWN | ADDED);
+ clear_commit_marks(c2, SEEN | UNINTERESTING | SHOWN | ADDED);
o1->flags = flags1;
o2->flags = flags2;
}
break;
default:
current_branch = branch_get(NULL);
- if (!current_branch || !current_branch->merge
- || !current_branch->merge[0]
- || !current_branch->merge[0]->dst) {
+ upstream = branch_get_upstream(current_branch, NULL);
+ if (!upstream) {
fprintf(stderr, _("Could not find a tracked"
" remote branch, please"
" specify <upstream> manually.\n"));
usage_with_options(cherry_usage, options);
}
-
- upstream = current_branch->merge[0]->dst;
}
init_revisions(&revs, prefix);
}
if (len) {
struct strbuf truname = STRBUF_INIT;
- strbuf_addstr(&truname, "refs/heads/");
- strbuf_addstr(&truname, remote);
+ strbuf_addf(&truname, "refs/heads/%s", remote);
strbuf_setlen(&truname, truname.len - len);
if (ref_exists(truname.buf)) {
strbuf_addf(msg,
strbuf_release(&truname);
goto cleanup;
}
- }
-
- if (!strcmp(remote, "FETCH_HEAD") &&
- !access(git_path("FETCH_HEAD"), R_OK)) {
- const char *filename;
- FILE *fp;
- struct strbuf line = STRBUF_INIT;
- char *ptr;
-
- filename = git_path("FETCH_HEAD");
- fp = fopen(filename, "r");
- if (!fp)
- die_errno(_("could not open '%s' for reading"),
- filename);
- strbuf_getline(&line, fp, '\n');
- fclose(fp);
- ptr = strstr(line.buf, "\tnot-for-merge\t");
- if (ptr)
- strbuf_remove(&line, ptr-line.buf+1, 13);
- strbuf_addbuf(msg, &line);
- strbuf_release(&line);
- goto cleanup;
+ strbuf_release(&truname);
}
if (remote_head->util) {
if (!branch)
die(_("No current branch."));
- if (!branch->remote)
+ if (!branch->remote_name)
die(_("No remote for the current branch."));
if (!branch->merge_nr)
die(_("No default upstream defined for the current branch."));
st_stdin.st_mode == st_stdout.st_mode);
}
-static struct commit_list *collect_parents(struct commit *head_commit,
- int *head_subsumed,
- int argc, const char **argv)
+static struct commit_list *reduce_parents(struct commit *head_commit,
+ int *head_subsumed,
+ struct commit_list *remoteheads)
{
- int i;
- struct commit_list *remoteheads = NULL, *parents, *next;
- struct commit_list **remotes = &remoteheads;
+ struct commit_list *parents, *next, **remotes = &remoteheads;
- if (head_commit)
- remotes = &commit_list_insert(head_commit, remotes)->next;
- for (i = 0; i < argc; i++) {
- struct commit *commit = get_merge_parent(argv[i]);
- if (!commit)
- help_unknown_ref(argv[i], "merge",
- "not something we can merge");
- remotes = &commit_list_insert(commit, remotes)->next;
- }
- *remotes = NULL;
+ /*
+ * Is the current HEAD reachable from another commit being
+ * merged? If so we do not want to record it as a parent of
+ * the resulting merge, unless --no-ff is given. We will flip
+ * this variable to 0 when we find HEAD among the independent
+ * tips being merged.
+ */
+ *head_subsumed = 1;
+ /* Find what parents to record by checking independent ones. */
parents = reduce_heads(remoteheads);
- *head_subsumed = 1; /* we will flip this to 0 when we find it */
for (remoteheads = NULL, remotes = &remoteheads;
parents;
parents = next) {
*head_subsumed = 0;
else
remotes = &commit_list_insert(commit, remotes)->next;
+ free(parents);
+ }
+ return remoteheads;
+}
+
+static void prepare_merge_message(struct strbuf *merge_names, struct strbuf *merge_msg)
+{
+ struct fmt_merge_msg_opts opts;
+
+ memset(&opts, 0, sizeof(opts));
+ opts.add_title = !have_message;
+ opts.shortlog_len = shortlog_len;
+ opts.credit_people = (0 < option_edit);
+
+ fmt_merge_msg(merge_names, merge_msg, &opts);
+ if (merge_msg->len)
+ strbuf_setlen(merge_msg, merge_msg->len - 1);
+}
+
+static void handle_fetch_head(struct commit_list **remotes, struct strbuf *merge_names)
+{
+ const char *filename;
+ int fd, pos, npos;
+ struct strbuf fetch_head_file = STRBUF_INIT;
+
+ if (!merge_names)
+ merge_names = &fetch_head_file;
+
+ filename = git_path("FETCH_HEAD");
+ fd = open(filename, O_RDONLY);
+ if (fd < 0)
+ die_errno(_("could not open '%s' for reading"), filename);
+
+ if (strbuf_read(merge_names, fd, 0) < 0)
+ die_errno(_("could not read '%s'"), filename);
+ if (close(fd) < 0)
+ die_errno(_("could not close '%s'"), filename);
+
+ for (pos = 0; pos < merge_names->len; pos = npos) {
+ unsigned char sha1[20];
+ char *ptr;
+ struct commit *commit;
+
+ ptr = strchr(merge_names->buf + pos, '\n');
+ if (ptr)
+ npos = ptr - merge_names->buf + 1;
+ else
+ npos = merge_names->len;
+
+ if (npos - pos < 40 + 2 ||
+ get_sha1_hex(merge_names->buf + pos, sha1))
+ commit = NULL; /* bad */
+ else if (memcmp(merge_names->buf + pos + 40, "\t\t", 2))
+ continue; /* not-for-merge */
+ else {
+ char saved = merge_names->buf[pos + 40];
+ merge_names->buf[pos + 40] = '\0';
+ commit = get_merge_parent(merge_names->buf + pos);
+ merge_names->buf[pos + 40] = saved;
+ }
+ if (!commit) {
+ if (ptr)
+ *ptr = '\0';
+ die("not something we can merge in %s: %s",
+ filename, merge_names->buf + pos);
+ }
+ remotes = &commit_list_insert(commit, remotes)->next;
+ }
+
+ if (merge_names == &fetch_head_file)
+ strbuf_release(&fetch_head_file);
+}
+
+static struct commit_list *collect_parents(struct commit *head_commit,
+ int *head_subsumed,
+ int argc, const char **argv,
+ struct strbuf *merge_msg)
+{
+ int i;
+ struct commit_list *remoteheads = NULL;
+ struct commit_list **remotes = &remoteheads;
+ struct strbuf merge_names = STRBUF_INIT, *autogen = NULL;
+
+ if (merge_msg && (!have_message || shortlog_len))
+ autogen = &merge_names;
+
+ if (head_commit)
+ remotes = &commit_list_insert(head_commit, remotes)->next;
+
+ if (argc == 1 && !strcmp(argv[0], "FETCH_HEAD")) {
+ handle_fetch_head(remotes, autogen);
+ remoteheads = reduce_parents(head_commit, head_subsumed, remoteheads);
+ } else {
+ for (i = 0; i < argc; i++) {
+ struct commit *commit = get_merge_parent(argv[i]);
+ if (!commit)
+ help_unknown_ref(argv[i], "merge",
+ "not something we can merge");
+ remotes = &commit_list_insert(commit, remotes)->next;
+ }
+ remoteheads = reduce_parents(head_commit, head_subsumed, remoteheads);
+ if (autogen) {
+ struct commit_list *p;
+ for (p = remoteheads; p; p = p->next)
+ merge_name(merge_remote_util(p->item)->name, autogen);
+ }
}
+
+ if (autogen) {
+ prepare_merge_message(autogen, merge_msg);
+ strbuf_release(autogen);
+ }
+
return remoteheads;
}
option_commit = 0;
}
- if (!abort_current_merge) {
- if (!argc) {
- if (default_to_upstream)
- argc = setup_with_upstream(&argv);
- else
- die(_("No commit specified and merge.defaultToUpstream not set."));
- } else if (argc == 1 && !strcmp(argv[0], "-"))
- argv[0] = "@{-1}";
+ if (!argc) {
+ if (default_to_upstream)
+ argc = setup_with_upstream(&argv);
+ else
+ die(_("No commit specified and merge.defaultToUpstream not set."));
+ } else if (argc == 1 && !strcmp(argv[0], "-")) {
+ argv[0] = "@{-1}";
}
+
if (!argc)
usage_with_options(builtin_merge_usage,
builtin_merge_options);
- /*
- * This could be traditional "merge <msg> HEAD <commit>..." and
- * the way we can tell it is to see if the second token is HEAD,
- * but some people might have misused the interface and used a
- * commit-ish that is the same as HEAD there instead.
- * Traditional format never would have "-m" so it is an
- * additional safety measure to check for it.
- */
-
- if (!have_message && head_commit &&
- is_old_style_invocation(argc, argv, head_commit->object.sha1)) {
- strbuf_addstr(&merge_msg, argv[0]);
- head_arg = argv[1];
- argv += 2;
- argc -= 2;
- remoteheads = collect_parents(head_commit, &head_subsumed, argc, argv);
- } else if (!head_commit) {
+ if (!head_commit) {
struct commit *remote_head;
/*
* If the merged head is a valid one there is no reason
* to forbid "git merge" into a branch yet to be born.
* We do the same for "git pull".
*/
- if (argc != 1)
- die(_("Can merge only exactly one commit into "
- "empty head"));
if (squash)
die(_("Squash commit into empty head not supported yet"));
if (fast_forward == FF_NO)
die(_("Non-fast-forward commit does not make sense into "
"an empty head"));
- remoteheads = collect_parents(head_commit, &head_subsumed, argc, argv);
+ remoteheads = collect_parents(head_commit, &head_subsumed,
+ argc, argv, NULL);
remote_head = remoteheads->item;
if (!remote_head)
die(_("%s - not something we can merge"), argv[0]);
+ if (remoteheads->next)
+ die(_("Can merge only exactly one commit into empty head"));
read_empty(remote_head->object.sha1, 0);
update_ref("initial pull", "HEAD", remote_head->object.sha1,
NULL, 0, UPDATE_REFS_DIE_ON_ERR);
goto done;
- } else {
- struct strbuf merge_names = STRBUF_INIT;
+ }
+ /*
+ * This could be traditional "merge <msg> HEAD <commit>..." and
+ * the way we can tell it is to see if the second token is HEAD,
+ * but some people might have misused the interface and used a
+ * commit-ish that is the same as HEAD there instead.
+ * Traditional format never would have "-m" so it is an
+ * additional safety measure to check for it.
+ */
+ if (!have_message &&
+ is_old_style_invocation(argc, argv, head_commit->object.sha1)) {
+ warning("old-style 'git merge <msg> HEAD <commit>' is deprecated.");
+ strbuf_addstr(&merge_msg, argv[0]);
+ head_arg = argv[1];
+ argv += 2;
+ argc -= 2;
+ remoteheads = collect_parents(head_commit, &head_subsumed,
+ argc, argv, NULL);
+ } else {
/* We are invoked directly as the first-class UI. */
head_arg = "HEAD";
* the standard merge summary message to be appended
* to the given message.
*/
- remoteheads = collect_parents(head_commit, &head_subsumed, argc, argv);
- for (p = remoteheads; p; p = p->next)
- merge_name(merge_remote_util(p->item)->name, &merge_names);
-
- if (!have_message || shortlog_len) {
- struct fmt_merge_msg_opts opts;
- memset(&opts, 0, sizeof(opts));
- opts.add_title = !have_message;
- opts.shortlog_len = shortlog_len;
- opts.credit_people = (0 < option_edit);
-
- fmt_merge_msg(&merge_names, &merge_msg, &opts);
- if (merge_msg.len)
- strbuf_setlen(&merge_msg, merge_msg.len - 1);
- }
+ remoteheads = collect_parents(head_commit, &head_subsumed,
+ argc, argv, &merge_msg);
}
if (!head_commit || !argc)
return hashcmp(a->sha1, b->sha1);
}
-static int name_ref(const char *path, const unsigned char *sha1, int flags, void *cb_data)
+static int name_ref(const char *path, const struct object_id *oid, int flags, void *cb_data)
{
- struct object *o = parse_object(sha1);
+ struct object *o = parse_object(oid->hash);
struct name_ref_data *data = cb_data;
int can_abbreviate_output = data->tags_only && data->name_only;
int deref = 0;
}
}
- add_to_tip_table(sha1, path, can_abbreviate_output);
+ add_to_tip_table(oid->hash, path, can_abbreviate_output);
while (o && o->type == OBJ_TAG) {
struct tag *t = (struct tag *) o;
return WRITE_ONE_WRITTEN;
}
-static int mark_tagged(const char *path, const unsigned char *sha1, int flag,
+static int mark_tagged(const char *path, const struct object_id *oid, int flag,
void *cb_data)
{
unsigned char peeled[20];
- struct object_entry *entry = packlist_find(&to_pack, sha1, NULL);
+ struct object_entry *entry = packlist_find(&to_pack, oid->hash, NULL);
if (entry)
entry->tagged = 1;
#define ll_find_deltas(l, s, w, d, p) find_deltas(l, &s, w, d, p)
#endif
-static int add_ref_tag(const char *path, const unsigned char *sha1, int flag, void *cb_data)
+static int add_ref_tag(const char *path, const struct object_id *oid, int flag, void *cb_data)
{
- unsigned char peeled[20];
+ struct object_id peeled;
if (starts_with(path, "refs/tags/") && /* is a tag? */
- !peel_ref(path, peeled) && /* peelable? */
- packlist_find(&to_pack, peeled, NULL)) /* object packed? */
- add_object_entry(sha1, OBJ_TAG, NULL, 0);
+ !peel_ref(path, peeled.hash) && /* peelable? */
+ packlist_find(&to_pack, peeled.hash, NULL)) /* object packed? */
+ add_object_entry(oid->hash, OBJ_TAG, NULL, 0);
return 0;
}
#include "builtin.h"
-static void flush_current_id(int patchlen, unsigned char *id, unsigned char *result)
+static void flush_current_id(int patchlen, struct object_id *id, struct object_id *result)
{
char name[50];
if (!patchlen)
return;
- memcpy(name, sha1_to_hex(id), 41);
- printf("%s %s\n", sha1_to_hex(result), name);
+ memcpy(name, oid_to_hex(id), GIT_SHA1_HEXSZ + 1);
+ printf("%s %s\n", oid_to_hex(result), name);
}
static int remove_space(char *line)
return 1;
}
-static void flush_one_hunk(unsigned char *result, git_SHA_CTX *ctx)
+static void flush_one_hunk(struct object_id *result, git_SHA_CTX *ctx)
{
- unsigned char hash[20];
+ unsigned char hash[GIT_SHA1_RAWSZ];
unsigned short carry = 0;
int i;
git_SHA1_Final(hash, ctx);
git_SHA1_Init(ctx);
/* 20-byte sum, with carry */
- for (i = 0; i < 20; ++i) {
- carry += result[i] + hash[i];
- result[i] = carry;
+ for (i = 0; i < GIT_SHA1_RAWSZ; ++i) {
+ carry += result->hash[i] + hash[i];
+ result->hash[i] = carry;
carry >>= 8;
}
}
-static int get_one_patchid(unsigned char *next_sha1, unsigned char *result,
+static int get_one_patchid(struct object_id *next_oid, struct object_id *result,
struct strbuf *line_buf, int stable)
{
int patchlen = 0, found_next = 0;
git_SHA_CTX ctx;
git_SHA1_Init(&ctx);
- hashclr(result);
+ oidclr(result);
while (strbuf_getwholeline(line_buf, stdin, '\n') != EOF) {
char *line = line_buf->buf;
else if (!memcmp(line, "\\ ", 2) && 12 < strlen(line))
continue;
- if (!get_sha1_hex(p, next_sha1)) {
+ if (!get_oid_hex(p, next_oid)) {
found_next = 1;
break;
}
}
if (!found_next)
- hashclr(next_sha1);
+ oidclr(next_oid);
flush_one_hunk(result, &ctx);
static void generate_id_list(int stable)
{
- unsigned char sha1[20], n[20], result[20];
+ struct object_id oid, n, result;
int patchlen;
struct strbuf line_buf = STRBUF_INIT;
- hashclr(sha1);
+ oidclr(&oid);
while (!feof(stdin)) {
- patchlen = get_one_patchid(n, result, &line_buf, stable);
- flush_current_id(patchlen, sha1, result);
- hashcpy(sha1, n);
+ patchlen = get_one_patchid(&n, &result, &line_buf, stable);
+ flush_current_id(patchlen, &oid, &result);
+ oidcpy(&oid, &n);
}
strbuf_release(&line_buf);
}
return 0;
}
+static int prune_worktree(const char *id, struct strbuf *reason)
+{
+ struct stat st;
+ char *path;
+ int fd, len;
+
+ if (!is_directory(git_path("worktrees/%s", id))) {
+ strbuf_addf(reason, _("Removing worktrees/%s: not a valid directory"), id);
+ return 1;
+ }
+ if (file_exists(git_path("worktrees/%s/locked", id)))
+ return 0;
+ if (stat(git_path("worktrees/%s/gitdir", id), &st)) {
+ strbuf_addf(reason, _("Removing worktrees/%s: gitdir file does not exist"), id);
+ return 1;
+ }
+ fd = open(git_path("worktrees/%s/gitdir", id), O_RDONLY);
+ if (fd < 0) {
+ strbuf_addf(reason, _("Removing worktrees/%s: unable to read gitdir file (%s)"),
+ id, strerror(errno));
+ return 1;
+ }
+ len = st.st_size;
+ path = xmalloc(len + 1);
+ read_in_full(fd, path, len);
+ close(fd);
+ while (len && (path[len - 1] == '\n' || path[len - 1] == '\r'))
+ len--;
+ if (!len) {
+ strbuf_addf(reason, _("Removing worktrees/%s: invalid gitdir file"), id);
+ free(path);
+ return 1;
+ }
+ path[len] = '\0';
+ if (!file_exists(path)) {
+ struct stat st_link;
+ free(path);
+ /*
+ * the repo is moved manually and has not been
+ * accessed since?
+ */
+ if (!stat(git_path("worktrees/%s/link", id), &st_link) &&
+ st_link.st_nlink > 1)
+ return 0;
+ if (st.st_mtime <= expire) {
+ strbuf_addf(reason, _("Removing worktrees/%s: gitdir file points to non-existent location"), id);
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+ free(path);
+ return 0;
+}
+
+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) {
+ if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
+ continue;
+ strbuf_reset(&reason);
+ if (!prune_worktree(d->d_name, &reason))
+ continue;
+ if (show_only || verbose)
+ printf("%s\n", reason.buf);
+ if (show_only)
+ continue;
+ strbuf_reset(&path);
+ strbuf_addstr(&path, git_path("worktrees/%s", d->d_name));
+ ret = remove_dir_recursively(&path, 0);
+ if (ret < 0 && errno == ENOTDIR)
+ ret = unlink(path.buf);
+ if (ret)
+ error(_("failed to remove: %s"), strerror(errno));
+ }
+ closedir(dir);
+ if (!show_only)
+ rmdir(git_path("worktrees"));
+ strbuf_release(&reason);
+ strbuf_release(&path);
+}
+
/*
* Write errors (particularly out of space) can result in
* failed temporary packs (and more rarely indexes and other
{
struct rev_info revs;
struct progress *progress = NULL;
+ int do_prune_worktrees = 0;
const struct option options[] = {
OPT__DRY_RUN(&show_only, N_("do not remove, show only")),
OPT__VERBOSE(&verbose, N_("report pruned objects")),
OPT_BOOL(0, "progress", &show_progress, N_("show progress")),
+ OPT_BOOL(0, "worktrees", &do_prune_worktrees, N_("prune .git/worktrees")),
OPT_EXPIRY_DATE(0, "expire", &expire,
N_("expire objects older than <time>")),
OPT_END()
init_revisions(&revs, prefix);
argc = parse_options(argc, argv, prefix, options, prune_usage, 0);
+
+ if (do_prune_worktrees) {
+ if (argc)
+ die(_("--worktrees does not take extra arguments"));
+ prune_worktrees();
+ return 0;
+ }
+
while (argc--) {
unsigned char sha1[20];
const char *name = *argv++;
}
}
-static int show_ref_cb(const char *path, const unsigned char *sha1, int flag, void *unused)
+static int show_ref_cb(const char *path, const struct object_id *oid, int flag, void *unused)
{
path = strip_namespace(path);
/*
*/
if (!path)
path = ".have";
- show_ref(path, sha1);
+ show_ref(path, oid->hash);
return 0;
}
static void write_head_info(void)
{
struct sha1_array sa = SHA1_ARRAY_INIT;
+
for_each_alternate_ref(collect_one_alternate_ref, &sa);
sha1_array_for_each_unique(&sa, show_one_alternate_sha1, NULL);
sha1_array_clear(&sa);
int argc;
const char **argv;
struct child_process proc = CHILD_PROCESS_INIT;
- char *hook;
+ const char *hook;
hook = find_hook("post-update");
for (argc = 0, cmd = commands; cmd; cmd = cmd->next) {
return 0;
}
-static int push_tip_to_list(const char *refname, const unsigned char *sha1,
+static int push_tip_to_list(const char *refname, const struct object_id *oid,
int flags, void *cb_data)
{
struct commit_list **list = cb_data;
struct commit *tip_commit;
if (flags & REF_ISSYMREF)
return 0;
- tip_commit = lookup_commit_reference_gently(sha1, 1);
+ tip_commit = lookup_commit_reference_gently(oid->hash, 1);
if (!tip_commit)
return 0;
commit_list_insert(tip_commit, list);
if (cb->unreachable_expire_kind != UE_ALWAYS) {
if (cb->unreachable_expire_kind == UE_HEAD) {
struct commit_list *elem;
+
for_each_ref(push_tip_to_list, &cb->tips);
for (elem = cb->tips; elem; elem = elem->next)
commit_list_insert(elem->item, &cb->mark_list);
}
}
-static int collect_reflog(const char *ref, const unsigned char *sha1, int unused, void *cb_data)
+static int collect_reflog(const char *ref, const struct object_id *oid, int unused, void *cb_data)
{
struct collected_reflog *e;
struct collect_reflog_cb *cb = cb_data;
size_t namelen = strlen(ref);
e = xmalloc(sizeof(*e) + namelen + 1);
- hashcpy(e->sha1, sha1);
+ hashcpy(e->sha1, oid->hash);
memcpy(e->reflog, ref, namelen + 1);
ALLOC_GROW(cb->e, cb->nr + 1, cb->alloc);
cb->e[cb->nr++] = e;
};
static int add_branch_for_removal(const char *refname,
- const unsigned char *sha1, int flags, void *cb_data)
+ const struct object_id *oid, int flags, void *cb_data)
{
struct branches_for_remote *branches = cb_data;
struct refspec refspec;
- struct string_list_item *item;
struct known_remote *kr;
memset(&refspec, 0, sizeof(refspec));
if (flags & REF_ISSYMREF)
return unlink(git_path("%s", refname));
- item = string_list_append(branches->branches, refname);
- item->util = xmalloc(20);
- hashcpy(item->util, sha1);
+ string_list_append(branches->branches, refname);
return 0;
}
};
static int read_remote_branches(const char *refname,
- const unsigned char *sha1, int flags, void *cb_data)
+ const struct object_id *oid, int flags, void *cb_data)
{
struct rename_info *rename = cb_data;
struct strbuf buf = STRBUF_INIT;
struct string_list_item *item;
int flag;
- unsigned char orig_sha1[20];
+ struct object_id orig_oid;
const char *symref;
strbuf_addf(&buf, "refs/remotes/%s/", rename->old);
if (starts_with(refname, buf.buf)) {
item = string_list_append(rename->remote_branches, xstrdup(refname));
symref = resolve_ref_unsafe(refname, RESOLVE_REF_READING,
- orig_sha1, &flag);
+ orig_oid.hash, &flag);
if (flag & REF_ISSYMREF)
item->util = xstrdup(symref);
else
{
struct strbuf buf = STRBUF_INIT;
int i;
- char *path = NULL;
+ const char *path = NULL;
strbuf_addf(&buf, "remote.%s.url", remote->name);
for (i = 0; i < remote->url_nr; i++)
for (i = 0; i < remote_branches.nr; i++) {
struct string_list_item *item = remote_branches.items + i;
int flag = 0;
- unsigned char sha1[20];
+ struct object_id oid;
- read_ref_full(item->string, RESOLVE_REF_READING, sha1, &flag);
+ read_ref_full(item->string, RESOLVE_REF_READING, oid.hash, &flag);
if (!(flag & REF_ISSYMREF))
continue;
if (delete_ref(item->string, NULL, REF_NODEREF))
if (!result)
result = remove_branches(&branches);
- string_list_clear(&branches, 1);
+ string_list_clear(&branches, 0);
if (skipped.nr) {
fprintf_ln(stderr,
}
static int append_ref_to_tracked_list(const char *refname,
- const unsigned char *sha1, int flags, void *cb_data)
+ const struct object_id *oid, int flags, void *cb_data)
{
struct ref_states *states = cb_data;
struct refspec refspec;
failed = 0;
for_each_string_list_item(item, &names) {
for (ext = 0; ext < ARRAY_SIZE(exts); ext++) {
- char *fname, *fname_old;
+ const char *fname_old;
+ char *fname;
fname = mkpathdup("%s/pack-%s%s", packdir,
item->string, exts[ext].name);
if (!file_exists(fname)) {
if (failed) {
struct string_list rollback_failure = STRING_LIST_INIT_DUP;
for_each_string_list_item(item, &rollback) {
- char *fname, *fname_old;
+ const char *fname_old;
+ char *fname;
fname = mkpathdup("%s/%s", packdir, item->string);
fname_old = mkpath("%s/old-%s", packdir, item->string);
if (rename(fname_old, fname))
/* Remove the "old-" files */
for_each_string_list_item(item, &names) {
for (ext = 0; ext < ARRAY_SIZE(exts); ext++) {
- char *fname;
+ const char *fname;
fname = mkpath("%s/old-%s%s",
packdir,
item->string,
enum replace_format format;
};
-static int show_reference(const char *refname, const unsigned char *sha1,
+static int show_reference(const char *refname, const struct object_id *oid,
int flag, void *cb_data)
{
struct show_data *data = cb_data;
if (data->format == REPLACE_FORMAT_SHORT)
printf("%s\n", refname);
else if (data->format == REPLACE_FORMAT_MEDIUM)
- printf("%s -> %s\n", refname, sha1_to_hex(sha1));
+ printf("%s -> %s\n", refname, oid_to_hex(oid));
else { /* data->format == REPLACE_FORMAT_LONG */
- unsigned char object[20];
+ struct object_id object;
enum object_type obj_type, repl_type;
- if (get_sha1(refname, object))
+ if (get_sha1(refname, object.hash))
return error("Failed to resolve '%s' as a valid ref.", refname);
- obj_type = sha1_object_info(object, NULL);
- repl_type = sha1_object_info(sha1, NULL);
+ obj_type = sha1_object_info(object.hash, NULL);
+ repl_type = sha1_object_info(oid->hash, NULL);
printf("%s (%s) -> %s (%s)\n", refname, typename(obj_type),
- sha1_to_hex(sha1), typename(repl_type));
+ oid_to_hex(oid), typename(repl_type));
}
}
"valid formats are 'short', 'medium' and 'long'\n",
format);
- for_each_replace_ref(show_reference, (void *) &data);
+ for_each_replace_ref(show_reference, (void *)&data);
return 0;
}
return 0;
}
-static int show_reference(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+static int show_reference(const char *refname, const struct object_id *oid, int flag, void *cb_data)
{
if (ref_excluded(ref_excludes, refname))
return 0;
- show_rev(NORMAL, sha1, refname);
+ show_rev(NORMAL, oid->hash, refname);
return 0;
}
-static int anti_reference(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+static int anti_reference(const char *refname, const struct object_id *oid, int flag, void *cb_data)
{
- show_rev(REVERSED, sha1, refname);
+ show_rev(REVERSED, oid->hash, refname);
return 0;
}
for (i = 1; i < argc; i++) {
const char *arg = argv[i];
+ if (!strcmp(arg, "--git-path")) {
+ if (!argv[i + 1])
+ die("--git-path requires an argument");
+ puts(git_path("%s", argv[i + 1]));
+ i++;
+ continue;
+ }
if (as_is) {
if (show_file(arg, output_prefix) && as_is < 2)
verify_filename(prefix, arg, 0);
free(cwd);
continue;
}
+ if (!strcmp(arg, "--git-common-dir")) {
+ puts(get_git_common_dir());
+ continue;
+ }
if (!strcmp(arg, "--resolve-git-dir")) {
const char *gitdir = argv[++i];
if (!gitdir)
compare_ref_name);
}
-static int append_ref(const char *refname, const unsigned char *sha1,
+static int append_ref(const char *refname, const struct object_id *oid,
int allow_dups)
{
- struct commit *commit = lookup_commit_reference_gently(sha1, 1);
+ struct commit *commit = lookup_commit_reference_gently(oid->hash, 1);
int i;
if (!commit)
return 0;
}
-static int append_head_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+static int append_head_ref(const char *refname, const struct object_id *oid,
+ int flag, void *cb_data)
{
- unsigned char tmp[20];
+ struct object_id tmp;
int ofs = 11;
if (!starts_with(refname, "refs/heads/"))
return 0;
/* If both heads/foo and tags/foo exists, get_sha1 would
* get confused.
*/
- if (get_sha1(refname + ofs, tmp) || hashcmp(tmp, sha1))
+ if (get_sha1(refname + ofs, tmp.hash) || oidcmp(&tmp, oid))
ofs = 5;
- return append_ref(refname + ofs, sha1, 0);
+ return append_ref(refname + ofs, oid, 0);
}
-static int append_remote_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+static int append_remote_ref(const char *refname, const struct object_id *oid,
+ int flag, void *cb_data)
{
- unsigned char tmp[20];
+ struct object_id tmp;
int ofs = 13;
if (!starts_with(refname, "refs/remotes/"))
return 0;
/* If both heads/foo and tags/foo exists, get_sha1 would
* get confused.
*/
- if (get_sha1(refname + ofs, tmp) || hashcmp(tmp, sha1))
+ if (get_sha1(refname + ofs, tmp.hash) || oidcmp(&tmp, oid))
ofs = 5;
- return append_ref(refname + ofs, sha1, 0);
+ return append_ref(refname + ofs, oid, 0);
}
-static int append_tag_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+static int append_tag_ref(const char *refname, const struct object_id *oid,
+ int flag, void *cb_data)
{
if (!starts_with(refname, "refs/tags/"))
return 0;
- return append_ref(refname + 5, sha1, 0);
+ return append_ref(refname + 5, oid, 0);
}
static const char *match_ref_pattern = NULL;
return cnt;
}
-static int append_matching_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+static int append_matching_ref(const char *refname, const struct object_id *oid,
+ int flag, void *cb_data)
{
/* we want to allow pattern hold/<asterisk> to show all
* branches under refs/heads/hold/, and v0.99.9? to show
if (wildmatch(match_ref_pattern, tail, 0, NULL))
return 0;
if (starts_with(refname, "refs/heads/"))
- return append_head_ref(refname, sha1, flag, cb_data);
+ return append_head_ref(refname, oid, flag, cb_data);
if (starts_with(refname, "refs/tags/"))
- return append_tag_ref(refname, sha1, flag, cb_data);
- return append_ref(refname, sha1, 0);
+ return append_tag_ref(refname, oid, flag, cb_data);
+ return append_ref(refname, oid, 0);
}
static void snarf_refs(int head, int remotes)
{
if (head) {
int orig_cnt = ref_name_cnt;
+
for_each_ref(append_head_ref, NULL);
sort_ref_range(orig_cnt, ref_name_cnt);
}
if (remotes) {
int orig_cnt = ref_name_cnt;
+
for_each_ref(append_remote_ref, NULL);
sort_ref_range(orig_cnt, ref_name_cnt);
}
static void append_one_rev(const char *av)
{
- unsigned char revkey[20];
- if (!get_sha1(av, revkey)) {
- append_ref(av, revkey, 0);
+ struct object_id revkey;
+ if (!get_sha1(av, revkey.hash)) {
+ append_ref(av, &revkey, 0);
return;
}
if (strchr(av, '*') || strchr(av, '?') || strchr(av, '[')) {
/* glob style match */
int saved_matches = ref_name_cnt;
+
match_ref_pattern = av;
match_ref_slash = count_slash(av);
for_each_ref(append_matching_ref, NULL);
char head[128];
const char *head_p;
int head_len;
- unsigned char head_sha1[20];
+ struct object_id head_oid;
int merge_base = 0;
int independent = 0;
int no_name = 0;
}
/* If nothing is specified, show all branches by default */
- if (ac + all_heads + all_remotes == 0)
+ if (ac <= topics && all_heads + all_remotes == 0)
all_heads = 1;
if (reflog) {
- unsigned char sha1[20];
+ struct object_id oid;
char nth_desc[256];
char *ref;
int base = 0;
fake_av[0] = resolve_refdup("HEAD",
RESOLVE_REF_READING,
- sha1, NULL);
+ oid.hash, NULL);
fake_av[1] = NULL;
av = fake_av;
ac = 1;
if (MAX_REVS < reflog)
die("Only %d entries can be shown at one time.",
MAX_REVS);
- if (!dwim_ref(*av, strlen(*av), sha1, &ref))
+ if (!dwim_ref(*av, strlen(*av), oid.hash, &ref))
die("No such ref %s", *av);
/* Has the base been specified? */
/* Ah, that is a date spec... */
unsigned long at;
at = approxidate(reflog_base);
- read_ref_at(ref, flags, at, -1, sha1, NULL,
+ read_ref_at(ref, flags, at, -1, oid.hash, NULL,
NULL, NULL, &base);
}
}
unsigned long timestamp;
int tz;
- if (read_ref_at(ref, flags, 0, base+i, sha1, &logmsg,
+ if (read_ref_at(ref, flags, 0, base+i, oid.hash, &logmsg,
×tamp, &tz, NULL)) {
reflog = i;
break;
msg);
free(logmsg);
sprintf(nth_desc, "%s@{%d}", *av, base+i);
- append_ref(nth_desc, sha1, 1);
+ append_ref(nth_desc, &oid, 1);
}
free(ref);
}
- else if (all_heads + all_remotes)
- snarf_refs(all_heads, all_remotes);
else {
while (0 < ac) {
append_one_rev(*av);
ac--; av++;
}
+ if (all_heads + all_remotes)
+ snarf_refs(all_heads, all_remotes);
}
head_p = resolve_ref_unsafe("HEAD", RESOLVE_REF_READING,
- head_sha1, NULL);
+ head_oid.hash, NULL);
if (head_p) {
head_len = strlen(head_p);
memcpy(head, head_p, head_len + 1);
if (rev_is_head(head,
head_len,
ref_name[i],
- head_sha1, NULL))
+ head_oid.hash, NULL))
has_head++;
}
if (!has_head) {
}
for (num_rev = 0; ref_name[num_rev]; num_rev++) {
- unsigned char revkey[20];
+ struct object_id revkey;
unsigned int flag = 1u << (num_rev + REV_SHIFT);
if (MAX_REVS <= num_rev)
die("cannot handle more than %d revs.", MAX_REVS);
- if (get_sha1(ref_name[num_rev], revkey))
+ if (get_sha1(ref_name[num_rev], revkey.hash))
die("'%s' is not a valid ref.", ref_name[num_rev]);
- commit = lookup_commit_reference(revkey);
+ commit = lookup_commit_reference(revkey.hash);
if (!commit)
die("cannot find commit %s (%s)",
- ref_name[num_rev], revkey);
+ ref_name[num_rev], oid_to_hex(&revkey));
parse_commit(commit);
mark_seen(commit, &seen);
int is_head = rev_is_head(head,
head_len,
ref_name[i],
- head_sha1,
+ head_oid.hash,
rev[i]->object.sha1);
if (extra < 0)
printf("%c [%s] ",
static const char **pattern;
static const char *exclude_existing_arg;
-static void show_one(const char *refname, const unsigned char *sha1)
+static void show_one(const char *refname, const struct object_id *oid)
{
- const char *hex = find_unique_abbrev(sha1, abbrev);
+ const char *hex = find_unique_abbrev(oid->hash, abbrev);
if (hash_only)
printf("%s\n", hex);
else
printf("%s %s\n", hex, refname);
}
-static int show_ref(const char *refname, const unsigned char *sha1, int flag, void *cbdata)
+static int show_ref(const char *refname, const struct object_id *oid,
+ int flag, void *cbdata)
{
const char *hex;
- unsigned char peeled[20];
+ struct object_id peeled;
if (show_head && !strcmp(refname, "HEAD"))
goto match;
* detect and return error if the repository is corrupt and
* ref points at a nonexistent object.
*/
- if (!has_sha1_file(sha1))
+ if (!has_sha1_file(oid->hash))
die("git show-ref: bad ref %s (%s)", refname,
- sha1_to_hex(sha1));
+ oid_to_hex(oid));
if (quiet)
return 0;
- show_one(refname, sha1);
+ show_one(refname, oid);
if (!deref_tags)
return 0;
- if (!peel_ref(refname, peeled)) {
- hex = find_unique_abbrev(peeled, abbrev);
+ if (!peel_ref(refname, peeled.hash)) {
+ hex = find_unique_abbrev(peeled.hash, abbrev);
printf("%s %s^{}\n", hex, refname);
}
return 0;
}
-static int add_existing(const char *refname, const unsigned char *sha1, int flag, void *cbdata)
+static int add_existing(const char *refname, const struct object_id *oid,
+ int flag, void *cbdata)
{
struct string_list *list = (struct string_list *)cbdata;
string_list_insert(list, refname);
if (!pattern)
die("--verify requires a reference");
while (*pattern) {
- unsigned char sha1[20];
+ struct object_id oid;
if (starts_with(*pattern, "refs/") &&
- !read_ref(*pattern, sha1)) {
+ !read_ref(*pattern, oid.hash)) {
if (!quiet)
- show_one(*pattern, sha1);
+ show_one(*pattern, &oid);
}
else if (!quiet)
die("'%s' - not a valid ref", *pattern);
return contains_test(candidate, want);
}
-static void show_tag_lines(const unsigned char *sha1, int lines)
+static void show_tag_lines(const struct object_id *oid, int lines)
{
int i;
unsigned long size;
char *buf, *sp, *eol;
size_t len;
- buf = read_sha1_file(sha1, &type, &size);
+ buf = read_sha1_file(oid->hash, &type, &size);
if (!buf)
- die_errno("unable to read object %s", sha1_to_hex(sha1));
+ die_errno("unable to read object %s", oid_to_hex(oid));
if (type != OBJ_COMMIT && type != OBJ_TAG)
goto free_return;
if (!size)
die("an empty %s object %s?",
- typename(type), sha1_to_hex(sha1));
+ typename(type), oid_to_hex(oid));
/* skip header */
sp = strstr(buf, "\n\n");
free(buf);
}
-static int show_reference(const char *refname, const unsigned char *sha1,
+static int show_reference(const char *refname, const struct object_id *oid,
int flag, void *cb_data)
{
struct tag_filter *filter = cb_data;
if (filter->with_commit) {
struct commit *commit;
- commit = lookup_commit_reference_gently(sha1, 1);
+ commit = lookup_commit_reference_gently(oid->hash, 1);
if (!commit)
return 0;
if (!contains(commit, filter->with_commit))
return 0;
}
- if (points_at.nr && !match_points_at(refname, sha1))
+ if (points_at.nr && !match_points_at(refname, oid->hash))
return 0;
if (!filter->lines) {
return 0;
}
printf("%-15s ", refname);
- show_tag_lines(sha1, filter->lines);
+ show_tag_lines(oid, filter->lines);
putchar('\n');
}
memset(&filter.tags, 0, sizeof(filter.tags));
filter.tags.strdup_strings = 1;
- for_each_tag_ref(show_reference, (void *) &filter);
+ for_each_tag_ref(show_reference, (void *)&filter);
if (sort) {
int i;
if ((sort & SORT_MASK) == VERCMP_SORT)
static int mark_skip_worktree_only;
#define MARK_FLAG 1
#define UNMARK_FLAG 2
+static struct strbuf mtime_dir = STRBUF_INIT;
__attribute__((format (printf, 1, 2)))
static void report(const char *fmt, ...)
va_end(vp);
}
+static void remove_test_directory(void)
+{
+ if (mtime_dir.len)
+ remove_dir_recursively(&mtime_dir, 0);
+}
+
+static const char *get_mtime_path(const char *path)
+{
+ static struct strbuf sb = STRBUF_INIT;
+ strbuf_reset(&sb);
+ strbuf_addf(&sb, "%s/%s", mtime_dir.buf, path);
+ return sb.buf;
+}
+
+static void xmkdir(const char *path)
+{
+ path = get_mtime_path(path);
+ if (mkdir(path, 0700))
+ die_errno(_("failed to create directory %s"), path);
+}
+
+static int xstat_mtime_dir(struct stat *st)
+{
+ if (stat(mtime_dir.buf, st))
+ die_errno(_("failed to stat %s"), mtime_dir.buf);
+ return 0;
+}
+
+static int create_file(const char *path)
+{
+ int fd;
+ path = get_mtime_path(path);
+ fd = open(path, O_CREAT | O_RDWR, 0644);
+ if (fd < 0)
+ die_errno(_("failed to create file %s"), path);
+ return fd;
+}
+
+static void xunlink(const char *path)
+{
+ path = get_mtime_path(path);
+ if (unlink(path))
+ die_errno(_("failed to delete file %s"), path);
+}
+
+static void xrmdir(const char *path)
+{
+ path = get_mtime_path(path);
+ if (rmdir(path))
+ die_errno(_("failed to delete directory %s"), path);
+}
+
+static void avoid_racy(void)
+{
+ /*
+ * not use if we could usleep(10) if USE_NSEC is defined. The
+ * field nsec could be there, but the OS could choose to
+ * ignore it?
+ */
+ sleep(1);
+}
+
+static int test_if_untracked_cache_is_supported(void)
+{
+ struct stat st;
+ struct stat_data base;
+ int fd, ret = 0;
+
+ strbuf_addstr(&mtime_dir, "mtime-test-XXXXXX");
+ if (!mkdtemp(mtime_dir.buf))
+ die_errno("Could not make temporary directory");
+
+ fprintf(stderr, _("Testing "));
+ atexit(remove_test_directory);
+ xstat_mtime_dir(&st);
+ fill_stat_data(&base, &st);
+ fputc('.', stderr);
+
+ avoid_racy();
+ fd = create_file("newfile");
+ xstat_mtime_dir(&st);
+ if (!match_stat_data(&base, &st)) {
+ close(fd);
+ fputc('\n', stderr);
+ fprintf_ln(stderr,_("directory stat info does not "
+ "change after adding a new file"));
+ goto done;
+ }
+ fill_stat_data(&base, &st);
+ fputc('.', stderr);
+
+ avoid_racy();
+ xmkdir("new-dir");
+ xstat_mtime_dir(&st);
+ if (!match_stat_data(&base, &st)) {
+ close(fd);
+ fputc('\n', stderr);
+ fprintf_ln(stderr, _("directory stat info does not change "
+ "after adding a new directory"));
+ goto done;
+ }
+ fill_stat_data(&base, &st);
+ fputc('.', stderr);
+
+ avoid_racy();
+ write_or_die(fd, "data", 4);
+ close(fd);
+ xstat_mtime_dir(&st);
+ if (match_stat_data(&base, &st)) {
+ fputc('\n', stderr);
+ fprintf_ln(stderr, _("directory stat info changes "
+ "after updating a file"));
+ goto done;
+ }
+ fputc('.', stderr);
+
+ avoid_racy();
+ close(create_file("new-dir/new"));
+ xstat_mtime_dir(&st);
+ if (match_stat_data(&base, &st)) {
+ fputc('\n', stderr);
+ fprintf_ln(stderr, _("directory stat info changes after "
+ "adding a file inside subdirectory"));
+ goto done;
+ }
+ fputc('.', stderr);
+
+ avoid_racy();
+ xunlink("newfile");
+ xstat_mtime_dir(&st);
+ if (!match_stat_data(&base, &st)) {
+ fputc('\n', stderr);
+ fprintf_ln(stderr, _("directory stat info does not "
+ "change after deleting a file"));
+ goto done;
+ }
+ fill_stat_data(&base, &st);
+ fputc('.', stderr);
+
+ avoid_racy();
+ xunlink("new-dir/new");
+ xrmdir("new-dir");
+ xstat_mtime_dir(&st);
+ if (!match_stat_data(&base, &st)) {
+ fputc('\n', stderr);
+ fprintf_ln(stderr, _("directory stat info does not "
+ "change after deleting a directory"));
+ goto done;
+ }
+
+ if (rmdir(mtime_dir.buf))
+ die_errno(_("failed to delete directory %s"), mtime_dir.buf);
+ fprintf_ln(stderr, _(" OK"));
+ ret = 1;
+
+done:
+ strbuf_release(&mtime_dir);
+ return ret;
+}
+
static int mark_ce_flags(const char *path, int flag, int mark)
{
int namelen = strlen(path);
for (i = 1; i < ac; i++) {
const char *arg = av[i];
- const char *p = prefix_path(prefix, prefix_length, arg);
+ char *p = prefix_path(prefix, prefix_length, arg);
err |= unresolve_one(p);
- if (p < arg || p > arg + strlen(arg))
- free((char *)p);
+ free(p);
}
return err;
}
int cmd_update_index(int argc, const char **argv, const char *prefix)
{
int newfd, entries, has_errors = 0, line_termination = '\n';
+ int untracked_cache = -1;
int read_from_stdin = 0;
int prefix_length = prefix ? strlen(prefix) : 0;
int preferred_index_format = 0;
N_("write index in this format")),
OPT_BOOL(0, "split-index", &split_index,
N_("enable or disable split index")),
+ OPT_BOOL(0, "untracked-cache", &untracked_cache,
+ N_("enable/disable untracked cache")),
+ OPT_SET_INT(0, "force-untracked-cache", &untracked_cache,
+ N_("enable untracked cache without testing the filesystem"), 2),
OPT_END()
};
case PARSE_OPT_DONE:
{
const char *path = ctx.argv[0];
- const char *p;
+ char *p;
setup_work_tree();
p = prefix_path(prefix, prefix_length, path);
update_one(p);
if (set_executable_bit)
chmod_path(set_executable_bit, p);
- free((char *)p);
+ free(p);
ctx.argc--;
ctx.argv++;
break;
setup_work_tree();
while (strbuf_getline(&buf, stdin, line_termination) != EOF) {
- const char *p;
+ char *p;
if (line_termination && buf.buf[0] == '"') {
strbuf_reset(&nbuf);
if (unquote_c_style(&nbuf, buf.buf, NULL))
update_one(p);
if (set_executable_bit)
chmod_path(set_executable_bit, p);
- free((char *)p);
+ free(p);
}
strbuf_release(&nbuf);
strbuf_release(&buf);
the_index.split_index = NULL;
the_index.cache_changed |= SOMETHING_CHANGED;
}
+ if (untracked_cache > 0) {
+ struct untracked_cache *uc;
+
+ if (untracked_cache < 2) {
+ setup_work_tree();
+ if (!test_if_untracked_cache_is_supported())
+ return 1;
+ }
+ if (!the_index.untracked) {
+ uc = xcalloc(1, sizeof(*uc));
+ strbuf_init(&uc->ident, 100);
+ uc->exclude_per_dir = ".gitignore";
+ /* should be the same flags used by git-status */
+ uc->dir_flags = DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES;
+ the_index.untracked = uc;
+ }
+ add_untracked_ident(the_index.untracked);
+ the_index.cache_changed |= UNTRACKED_CHANGED;
+ } else if (!untracked_cache && the_index.untracked) {
+ the_index.untracked = NULL;
+ the_index.cache_changed |= UNTRACKED_CHANGED;
+ }
if (active_cache_changed) {
if (newfd < 0) {
static void finish_bulk_checkin(struct bulk_checkin_state *state)
{
- unsigned char sha1[20];
+ struct object_id oid;
struct strbuf packname = STRBUF_INIT;
int i;
unlink(state->pack_tmp_name);
goto clear_exit;
} else if (state->nr_written == 1) {
- sha1close(state->f, sha1, CSUM_FSYNC);
+ sha1close(state->f, oid.hash, CSUM_FSYNC);
} else {
- int fd = sha1close(state->f, sha1, 0);
- fixup_pack_header_footer(fd, sha1, state->pack_tmp_name,
- state->nr_written, sha1,
+ int fd = sha1close(state->f, oid.hash, 0);
+ fixup_pack_header_footer(fd, oid.hash, state->pack_tmp_name,
+ state->nr_written, oid.hash,
state->offset);
close(fd);
}
strbuf_addf(&packname, "%s/pack/pack-", get_object_directory());
finish_tmp_packfile(&packname, state->pack_tmp_name,
state->written, state->nr_written,
- &state->pack_idx_opts, sha1);
+ &state->pack_idx_opts, oid.hash);
for (i = 0; i < state->nr_written; i++)
free(state->written[i]);
int git_deflate(git_zstream *, int flush);
unsigned long git_deflate_bound(git_zstream *, unsigned long);
+/* The length in bytes and in hex digits of an object name (SHA-1 value). */
+#define GIT_SHA1_RAWSZ 20
+#define GIT_SHA1_HEXSZ (2 * GIT_SHA1_RAWSZ)
+
+struct object_id {
+ unsigned char hash[GIT_SHA1_RAWSZ];
+};
+
#if defined(DT_UNKNOWN) && !defined(NO_D_TYPE_IN_DIRENT)
#define DTYPE(de) ((de)->d_type)
#else
#define RESOLVE_UNDO_CHANGED (1 << 4)
#define CACHE_TREE_CHANGED (1 << 5)
#define SPLIT_INDEX_ORDERED (1 << 6)
+#define UNTRACKED_CHANGED (1 << 7)
struct split_index;
+struct untracked_cache;
+
struct index_state {
struct cache_entry **cache;
unsigned int version;
struct hashmap name_hash;
struct hashmap dir_hash;
unsigned char sha1[20];
+ struct untracked_cache *untracked;
};
extern struct index_state the_index;
/* Double-check local_repo_env below if you add to this list. */
#define GIT_DIR_ENVIRONMENT "GIT_DIR"
+#define GIT_COMMON_DIR_ENVIRONMENT "GIT_COMMON_DIR"
#define GIT_NAMESPACE_ENVIRONMENT "GIT_NAMESPACE"
#define GIT_WORK_TREE_ENVIRONMENT "GIT_WORK_TREE"
#define GIT_PREFIX_ENVIRONMENT "GIT_PREFIX"
extern char *git_work_tree_cfg;
extern int is_inside_work_tree(void);
extern const char *get_git_dir(void);
+extern const char *get_git_common_dir(void);
extern int is_git_directory(const char *path);
extern char *get_object_directory(void);
extern char *get_index_file(void);
extern char *get_graft_file(void);
extern int set_git_dir(const char *path);
+extern int get_common_dir(struct strbuf *sb, const char *gitdir);
extern const char *get_git_namespace(void);
extern const char *strip_namespace(const char *namespaced_ref);
extern const char *get_git_work_tree(void);
* INODE_CHANGED, and DATA_CHANGED.
*/
extern int match_stat_data(const struct stat_data *sd, struct stat *st);
+extern int match_stat_data_racy(const struct index_state *istate,
+ const struct stat_data *sd, struct stat *st);
extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st);
extern int precomposed_unicode;
extern int protect_hfs;
extern int protect_ntfs;
+extern int git_db_env, git_index_env, git_graft_env, git_common_dir_env;
/*
* Include broken refs in all ref iterations, which will
extern char *mksnpath(char *buf, size_t n, const char *fmt, ...)
__attribute__((format (printf, 3, 4)));
-extern char *git_snpath(char *buf, size_t n, const char *fmt, ...)
- __attribute__((format (printf, 3, 4)));
+extern void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)
+ __attribute__((format (printf, 2, 3)));
extern char *git_pathdup(const char *fmt, ...)
__attribute__((format (printf, 1, 2)));
extern char *mkpathdup(const char *fmt, ...)
__attribute__((format (printf, 1, 2)));
/* Return a statically allocated filename matching the sha1 signature */
-extern char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
-extern char *git_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
-extern char *git_path_submodule(const char *path, const char *fmt, ...)
+extern const char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
+extern const char *git_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
+extern const char *git_path_submodule(const char *path, const char *fmt, ...)
__attribute__((format (printf, 2, 3)));
+extern void report_linked_checkout_garbage(void);
/*
* Return the name of the file in the local object database that would
extern char *sha1_pack_index_name(const unsigned char *sha1);
extern const char *find_unique_abbrev(const unsigned char *sha1, int);
-extern const unsigned char null_sha1[20];
+extern const unsigned char null_sha1[GIT_SHA1_RAWSZ];
static inline int hashcmp(const unsigned char *sha1, const unsigned char *sha2)
{
int i;
- for (i = 0; i < 20; i++, sha1++, sha2++) {
+ for (i = 0; i < GIT_SHA1_RAWSZ; i++, sha1++, sha2++) {
if (*sha1 != *sha2)
return *sha1 - *sha2;
}
return 0;
}
+static inline int oidcmp(const struct object_id *oid1, const struct object_id *oid2)
+{
+ return hashcmp(oid1->hash, oid2->hash);
+}
+
static inline int is_null_sha1(const unsigned char *sha1)
{
return !hashcmp(sha1, null_sha1);
}
+static inline int is_null_oid(const struct object_id *oid)
+{
+ return !hashcmp(oid->hash, null_sha1);
+}
+
static inline void hashcpy(unsigned char *sha_dst, const unsigned char *sha_src)
{
- memcpy(sha_dst, sha_src, 20);
+ memcpy(sha_dst, sha_src, GIT_SHA1_RAWSZ);
}
+
+static inline void oidcpy(struct object_id *dst, const struct object_id *src)
+{
+ hashcpy(dst->hash, src->hash);
+}
+
static inline void hashclr(unsigned char *hash)
{
- memset(hash, 0, 20);
+ memset(hash, 0, GIT_SHA1_RAWSZ);
+}
+
+static inline void oidclr(struct object_id *oid)
+{
+ hashclr(oid->hash);
}
+
#define EMPTY_TREE_SHA1_HEX \
"4b825dc642cb6eb9a060e54bf8d69288fbee4904"
#define EMPTY_TREE_SHA1_BIN_LITERAL \
/* object replacement */
#define LOOKUP_REPLACE_OBJECT 1
+#define LOOKUP_UNKNOWN_OBJECT 2
extern void *read_sha1_file_extended(const unsigned char *sha1, enum object_type *type, unsigned long *size, unsigned flag);
static inline void *read_sha1_file(const unsigned char *sha1, enum object_type *type, unsigned long *size)
{
* Return true iff we have an object named sha1, whether local or in
* an alternate object database, and whether packed or loose. This
* function does not respect replace references.
+ *
+ * If the QUICK flag is set, do not re-check the pack directory
+ * when we cannot find the object (this means we may give a false
+ * negative answer if another process is simultaneously repacking).
*/
-extern int has_sha1_file(const unsigned char *sha1);
+#define HAS_SHA1_QUICK 0x1
+extern int has_sha1_file_with_flags(const unsigned char *sha1, int flags);
+static inline int has_sha1_file(const unsigned char *sha1)
+{
+ return has_sha1_file_with_flags(sha1, 0);
+}
/*
* Return true iff an alternate object database has a loose object
unsigned char tree[20];
char path[PATH_MAX];
unsigned mode;
+ /*
+ * symlink_path is only used by get_tree_entry_follow_symlinks,
+ * and only for symlinks that point outside the repository.
+ */
+ struct strbuf symlink_path;
};
-#define GET_SHA1_QUIETLY 01
-#define GET_SHA1_COMMIT 02
-#define GET_SHA1_COMMITTISH 04
-#define GET_SHA1_TREE 010
-#define GET_SHA1_TREEISH 020
-#define GET_SHA1_BLOB 040
-#define GET_SHA1_ONLY_TO_DIE 04000
+#define GET_SHA1_QUIETLY 01
+#define GET_SHA1_COMMIT 02
+#define GET_SHA1_COMMITTISH 04
+#define GET_SHA1_TREE 010
+#define GET_SHA1_TREEISH 020
+#define GET_SHA1_BLOB 040
+#define GET_SHA1_FOLLOW_SYMLINKS 0100
+#define GET_SHA1_ONLY_TO_DIE 04000
extern int get_sha1(const char *str, unsigned char *sha1);
extern int get_sha1_commit(const char *str, unsigned char *sha1);
* null-terminated string.
*/
extern int get_sha1_hex(const char *hex, unsigned char *sha1);
+extern int get_oid_hex(const char *hex, struct object_id *sha1);
extern char *sha1_to_hex(const unsigned char *sha1); /* static buffer result! */
+extern char *oid_to_hex(const struct object_id *oid); /* same static buffer as sha1_to_hex */
extern int read_ref_full(const char *refname, int resolve_flags,
unsigned char *sha1, int *flags);
extern int read_ref(const char *refname, unsigned char *sha1);
unsigned long *sizep;
unsigned long *disk_sizep;
unsigned char *delta_base_sha1;
+ struct strbuf *typename;
/* Response */
enum {
extern void maybe_flush_or_die(FILE *, const char *);
__attribute__((format (printf, 2, 3)))
extern void fprintf_or_die(FILE *, const char *fmt, ...);
+
+#define COPY_READ_ERROR (-2)
+#define COPY_WRITE_ERROR (-3)
extern int copy_fd(int ifd, int ofd);
extern int copy_file(const char *dst, const char *src, int mode);
extern int copy_file_with_time(const char *dst, const char *src, int mode);
+
extern void write_or_die(int fd, const void *buf, size_t count);
extern int write_or_whine(int fd, const void *buf, size_t count, const char *msg);
extern int write_or_whine_pipe(int fd, const void *buf, size_t count, const char *msg);
{
return write_in_full(fd, str, strlen(str));
}
+__attribute__((format (printf, 3, 4)))
+extern int write_file(const char *path, int fatal, const char *fmt, ...);
/* pager.c */
extern void setup_pager(void);
void stat_validity_update(struct stat_validity *sv, int fd);
int versioncmp(const char *s1, const char *s2);
+void sleep_millisec(int millisec);
#endif /* CACHE_H */
memset(p->parent, 0,
sizeof(p->parent[0]) * num_parent);
- hashcpy(p->sha1, q->queue[i]->two->sha1);
+ hashcpy(p->oid.hash, q->queue[i]->two->sha1);
p->mode = q->queue[i]->two->mode;
- hashcpy(p->parent[n].sha1, q->queue[i]->one->sha1);
+ hashcpy(p->parent[n].oid.hash, q->queue[i]->one->sha1);
p->parent[n].mode = q->queue[i]->one->mode;
p->parent[n].status = q->queue[i]->status;
*tail = p;
continue;
}
- hashcpy(p->parent[n].sha1, q->queue[i]->one->sha1);
+ hashcpy(p->parent[n].oid.hash, q->queue[i]->one->sha1);
p->parent[n].mode = q->queue[i]->one->mode;
p->parent[n].status = q->queue[i]->status;
return base;
}
-static char *grab_blob(const unsigned char *sha1, unsigned int mode,
+static char *grab_blob(const struct object_id *oid, unsigned int mode,
unsigned long *size, struct userdiff_driver *textconv,
const char *path)
{
if (S_ISGITLINK(mode)) {
blob = xmalloc(100);
*size = snprintf(blob, 100,
- "Subproject commit %s\n", sha1_to_hex(sha1));
- } else if (is_null_sha1(sha1)) {
+ "Subproject commit %s\n", oid_to_hex(oid));
+ } else if (is_null_oid(oid)) {
/* deleted blob */
*size = 0;
return xcalloc(1, 1);
} else if (textconv) {
struct diff_filespec *df = alloc_filespec(path);
- fill_filespec(df, sha1, 1, mode);
+ fill_filespec(df, oid->hash, 1, mode);
*size = fill_textconv(textconv, df, &blob);
free_filespec(df);
} else {
- blob = read_sha1_file(sha1, &type, size);
+ blob = read_sha1_file(oid->hash, &type, size);
if (type != OBJ_BLOB)
- die("object '%s' is not a blob!", sha1_to_hex(sha1));
+ die("object '%s' is not a blob!", oid_to_hex(oid));
}
return blob;
}
}
}
-static void combine_diff(const unsigned char *parent, unsigned int mode,
+static void combine_diff(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,
const char *c_func = diff_get_color(use_color, DIFF_FUNCINFO);
const char *c_new = diff_get_color(use_color, DIFF_FILE_NEW);
const char *c_old = diff_get_color(use_color, DIFF_FILE_OLD);
- const char *c_plain = diff_get_color(use_color, DIFF_PLAIN);
+ const char *c_context = diff_get_color(use_color, DIFF_CONTEXT);
const char *c_reset = diff_get_color(use_color, DIFF_RESET);
if (result_deleted)
}
if (comment_end)
printf("%s%s %s%s", c_reset,
- c_plain, c_reset,
+ c_context, c_reset,
c_func);
for (i = 0; i < comment_end; i++)
putchar(hunk_comment[i]);
*/
if (!context)
continue;
- fputs(c_plain, stdout);
+ fputs(c_context, stdout);
}
else
fputs(c_new, stdout);
int show_file_header)
{
struct diff_options *opt = &rev->diffopt;
- int abbrev = DIFF_OPT_TST(opt, FULL_INDEX) ? 40 : DEFAULT_ABBREV;
+ int abbrev = DIFF_OPT_TST(opt, FULL_INDEX) ? GIT_SHA1_HEXSZ : DEFAULT_ABBREV;
const char *a_prefix = opt->a_prefix ? opt->a_prefix : "a/";
const char *b_prefix = opt->b_prefix ? opt->b_prefix : "b/";
const char *c_meta = diff_get_color_opt(opt, DIFF_METAINFO);
"", elem->path, line_prefix, c_meta, c_reset);
printf("%s%sindex ", line_prefix, c_meta);
for (i = 0; i < num_parent; i++) {
- abb = find_unique_abbrev(elem->parent[i].sha1,
+ abb = find_unique_abbrev(elem->parent[i].oid.hash,
abbrev);
printf("%s%s", i ? "," : "", abb);
}
- abb = find_unique_abbrev(elem->sha1, abbrev);
+ abb = find_unique_abbrev(elem->oid.hash, abbrev);
printf("..%s%s\n", abb, c_reset);
if (mode_differs) {
/* Read the result of merge first */
if (!working_tree_file)
- result = grab_blob(elem->sha1, elem->mode, &result_size,
+ result = grab_blob(&elem->oid, elem->mode, &result_size,
textconv, elem->path);
else {
/* Used by diff-tree to read from the working tree */
result = strbuf_detach(&buf, NULL);
elem->mode = canon_mode(st.st_mode);
} else if (S_ISDIR(st.st_mode)) {
- unsigned char sha1[20];
- if (resolve_gitlink_ref(elem->path, "HEAD", sha1) < 0)
- result = grab_blob(elem->sha1, elem->mode,
+ struct object_id oid;
+ if (resolve_gitlink_ref(elem->path, "HEAD", oid.hash) < 0)
+ result = grab_blob(&elem->oid, elem->mode,
&result_size, NULL, NULL);
else
- result = grab_blob(sha1, elem->mode,
+ result = grab_blob(&oid, elem->mode,
&result_size, NULL, NULL);
} else if (textconv) {
struct diff_filespec *df = alloc_filespec(elem->path);
for (i = 0; !is_binary && i < num_parent; i++) {
char *buf;
unsigned long size;
- buf = grab_blob(elem->parent[i].sha1,
+ buf = grab_blob(&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 (!hashcmp(elem->parent[i].sha1,
- elem->parent[j].sha1)) {
+ if (!oidcmp(&elem->parent[i].oid,
+ &elem->parent[j].oid)) {
reuse_combine_diff(sline, cnt, i, j);
break;
}
}
if (i <= j)
- combine_diff(elem->parent[i].sha1,
+ combine_diff(&elem->parent[i].oid,
elem->parent[i].mode,
&result_file, sline,
cnt, i, num_parent, result_deleted,
/* Show sha1's */
for (i = 0; i < num_parent; i++)
- printf(" %s", diff_unique_abbrev(p->parent[i].sha1,
+ printf(" %s", diff_unique_abbrev(p->parent[i].oid.hash,
opt->abbrev));
- printf(" %s ", diff_unique_abbrev(p->sha1, opt->abbrev));
+ printf(" %s ", diff_unique_abbrev(p->oid.hash, opt->abbrev));
}
if (opt->output_format & (DIFF_FORMAT_RAW | DIFF_FORMAT_NAME_STATUS)) {
for (i = 0; i < num_parent; i++) {
pair->one[i].path = p->path;
pair->one[i].mode = p->parent[i].mode;
- hashcpy(pair->one[i].sha1, p->parent[i].sha1);
- pair->one[i].sha1_valid = !is_null_sha1(p->parent[i].sha1);
+ hashcpy(pair->one[i].sha1, p->parent[i].oid.hash);
+ pair->one[i].sha1_valid = !is_null_oid(&p->parent[i].oid);
pair->one[i].has_more_entries = 1;
}
pair->one[num_parent - 1].has_more_entries = 0;
pair->two->path = p->path;
pair->two->mode = p->mode;
- hashcpy(pair->two->sha1, p->sha1);
- pair->two->sha1_valid = !is_null_sha1(p->sha1);
+ hashcpy(pair->two->sha1, p->oid.hash);
+ pair->two->sha1_valid = !is_null_oid(&p->oid);
return pair;
}
-# List of known git commands.
-# command name category [deprecated] [common]
-git-add mainporcelain common
+# common commands are grouped by themes
+# these groups are output by 'git help' in the order declared here.
+# map each common command in the command list to one of these groups.
+### common groups (do not change this line)
+init start a working area (see also: git help tutorial)
+worktree work on the current change (see also: git help everyday)
+info examine the history and state (see also: git help revisions)
+history grow, mark and tweak your common history
+remote collaborate (see also: git help workflows)
+
+### command list (do not change this line)
+# command name category [deprecated] [common]
+git-add mainporcelain worktree
git-am mainporcelain
git-annotate ancillaryinterrogators
git-apply plumbingmanipulators
git-archimport foreignscminterface
git-archive mainporcelain
-git-bisect mainporcelain common
+git-bisect mainporcelain info
git-blame ancillaryinterrogators
-git-branch mainporcelain common
+git-branch mainporcelain history
git-bundle mainporcelain
git-cat-file plumbinginterrogators
git-check-attr purehelpers
git-check-ignore purehelpers
git-check-mailmap purehelpers
-git-checkout mainporcelain common
+git-checkout mainporcelain history
git-checkout-index plumbingmanipulators
git-check-ref-format purehelpers
git-cherry ancillaryinterrogators
git-cherry-pick mainporcelain
git-citool mainporcelain
git-clean mainporcelain
-git-clone mainporcelain common
+git-clone mainporcelain init
git-column purehelpers
-git-commit mainporcelain common
+git-commit mainporcelain history
git-commit-tree plumbingmanipulators
git-config ancillarymanipulators
git-count-objects ancillaryinterrogators
git-cvsserver foreignscminterface
git-daemon synchingrepositories
git-describe mainporcelain
-git-diff mainporcelain common
+git-diff mainporcelain history
git-diff-files plumbinginterrogators
git-diff-index plumbinginterrogators
git-diff-tree plumbinginterrogators
git-difftool ancillaryinterrogators
-git-fast-export ancillarymanipulators
-git-fast-import ancillarymanipulators
-git-fetch mainporcelain common
+git-fast-export ancillarymanipulators
+git-fast-import ancillarymanipulators
+git-fetch mainporcelain remote
git-fetch-pack synchingrepositories
git-filter-branch ancillarymanipulators
git-fmt-merge-msg purehelpers
git-for-each-ref plumbinginterrogators
git-format-patch mainporcelain
-git-fsck ancillaryinterrogators
+git-fsck ancillaryinterrogators
git-gc mainporcelain
git-get-tar-commit-id ancillaryinterrogators
-git-grep mainporcelain common
+git-grep mainporcelain info
git-gui mainporcelain
git-hash-object plumbingmanipulators
-git-help ancillaryinterrogators
+git-help ancillaryinterrogators
git-http-backend synchingrepositories
git-http-fetch synchelpers
git-http-push synchelpers
git-imap-send foreignscminterface
git-index-pack plumbingmanipulators
-git-init mainporcelain common
+git-init mainporcelain init
git-instaweb ancillaryinterrogators
git-interpret-trailers purehelpers
gitk mainporcelain
-git-log mainporcelain common
+git-log mainporcelain info
git-ls-files plumbinginterrogators
git-ls-remote plumbinginterrogators
git-ls-tree plumbinginterrogators
git-mailinfo purehelpers
git-mailsplit purehelpers
-git-merge mainporcelain common
+git-merge mainporcelain history
git-merge-base plumbinginterrogators
git-merge-file plumbingmanipulators
git-merge-index plumbingmanipulators
git-merge-tree ancillaryinterrogators
git-mktag plumbingmanipulators
git-mktree plumbingmanipulators
-git-mv mainporcelain common
+git-mv mainporcelain worktree
git-name-rev plumbinginterrogators
git-notes mainporcelain
git-p4 foreignscminterface
git-patch-id purehelpers
git-prune ancillarymanipulators
git-prune-packed plumbingmanipulators
-git-pull mainporcelain common
-git-push mainporcelain common
+git-pull mainporcelain remote
+git-push mainporcelain remote
git-quiltimport foreignscminterface
git-read-tree plumbingmanipulators
-git-rebase mainporcelain common
+git-rebase mainporcelain history
git-receive-pack synchelpers
git-reflog ancillarymanipulators
git-relink ancillarymanipulators
git-replace ancillarymanipulators
git-request-pull foreignscminterface
git-rerere ancillaryinterrogators
-git-reset mainporcelain common
+git-reset mainporcelain worktree
git-revert mainporcelain
git-rev-list plumbinginterrogators
git-rev-parse ancillaryinterrogators
-git-rm mainporcelain common
+git-rm mainporcelain worktree
git-send-email foreignscminterface
git-send-pack synchingrepositories
git-shell synchelpers
git-shortlog mainporcelain
-git-show mainporcelain common
+git-show mainporcelain info
git-show-branch ancillaryinterrogators
git-show-index plumbinginterrogators
git-show-ref plumbinginterrogators
git-sh-i18n purehelpers
git-sh-setup purehelpers
git-stash mainporcelain
-git-status mainporcelain common
+git-status mainporcelain info
git-stripspace purehelpers
git-submodule mainporcelain
git-svn foreignscminterface
git-symbolic-ref plumbingmanipulators
-git-tag mainporcelain common
+git-tag mainporcelain history
git-unpack-file plumbinginterrogators
git-unpack-objects plumbingmanipulators
git-update-index plumbingmanipulators
struct commit *lookup_commit_reference_by_name(const char *name)
{
- unsigned char sha1[20];
+ struct object_id oid;
struct commit *commit;
- if (get_sha1_committish(name, sha1))
+ if (get_sha1_committish(name, oid.hash))
return NULL;
- commit = lookup_commit_reference(sha1);
+ commit = lookup_commit_reference(oid.hash);
if (parse_commit(commit))
return NULL;
return commit;
static const unsigned char *commit_graft_sha1_access(size_t index, void *table)
{
struct commit_graft **commit_graft_table = table;
- return commit_graft_table[index]->sha1;
+ return commit_graft_table[index]->oid.hash;
}
static int commit_graft_pos(const unsigned char *sha1)
int register_commit_graft(struct commit_graft *graft, int ignore_dups)
{
- int pos = commit_graft_pos(graft->sha1);
+ int pos = commit_graft_pos(graft->oid.hash);
if (0 <= pos) {
if (ignore_dups)
/* The format is just "Commit Parent1 Parent2 ...\n" */
int i;
struct commit_graft *graft = NULL;
+ const int entry_size = GIT_SHA1_HEXSZ + 1;
while (len && isspace(buf[len-1]))
buf[--len] = '\0';
if (buf[0] == '#' || buf[0] == '\0')
return NULL;
- if ((len + 1) % 41)
+ if ((len + 1) % entry_size)
goto bad_graft_data;
- i = (len + 1) / 41 - 1;
- graft = xmalloc(sizeof(*graft) + 20 * i);
+ i = (len + 1) / entry_size - 1;
+ graft = xmalloc(sizeof(*graft) + GIT_SHA1_RAWSZ * i);
graft->nr_parent = i;
- if (get_sha1_hex(buf, graft->sha1))
+ if (get_oid_hex(buf, &graft->oid))
goto bad_graft_data;
- for (i = 40; i < len; i += 41) {
+ for (i = GIT_SHA1_HEXSZ; i < len; i += entry_size) {
if (buf[i] != ' ')
goto bad_graft_data;
- if (get_sha1_hex(buf + i + 1, graft->parent[i/41]))
+ if (get_sha1_hex(buf + i + 1, graft->parent[i/entry_size].hash))
goto bad_graft_data;
}
return graft;
{
const char *tail = buffer;
const char *bufptr = buffer;
- unsigned char parent[20];
+ struct object_id parent;
struct commit_list **pptr;
struct commit_graft *graft;
+ const int tree_entry_len = GIT_SHA1_HEXSZ + 5;
+ const int parent_entry_len = GIT_SHA1_HEXSZ + 7;
if (item->object.parsed)
return 0;
item->object.parsed = 1;
tail += size;
- if (tail <= bufptr + 46 || memcmp(bufptr, "tree ", 5) || bufptr[45] != '\n')
+ if (tail <= bufptr + tree_entry_len + 1 || memcmp(bufptr, "tree ", 5) ||
+ bufptr[tree_entry_len] != '\n')
return error("bogus commit object %s", sha1_to_hex(item->object.sha1));
- if (get_sha1_hex(bufptr + 5, parent) < 0)
+ if (get_sha1_hex(bufptr + 5, parent.hash) < 0)
return error("bad tree pointer in commit %s",
sha1_to_hex(item->object.sha1));
- item->tree = lookup_tree(parent);
- bufptr += 46; /* "tree " + "hex sha1" + "\n" */
+ item->tree = lookup_tree(parent.hash);
+ bufptr += tree_entry_len + 1; /* "tree " + "hex sha1" + "\n" */
pptr = &item->parents;
graft = lookup_commit_graft(item->object.sha1);
- while (bufptr + 48 < tail && !memcmp(bufptr, "parent ", 7)) {
+ while (bufptr + parent_entry_len < tail && !memcmp(bufptr, "parent ", 7)) {
struct commit *new_parent;
- if (tail <= bufptr + 48 ||
- get_sha1_hex(bufptr + 7, parent) ||
- bufptr[47] != '\n')
+ if (tail <= bufptr + parent_entry_len + 1 ||
+ get_sha1_hex(bufptr + 7, parent.hash) ||
+ bufptr[parent_entry_len] != '\n')
return error("bad parents in commit %s", sha1_to_hex(item->object.sha1));
- bufptr += 48;
+ bufptr += parent_entry_len + 1;
/*
* The clone is shallow if nr_parent < 0, and we must
* not traverse its real parents even when we unhide them.
*/
if (graft && (graft->nr_parent < 0 || grafts_replace_parents))
continue;
- new_parent = lookup_commit(parent);
+ new_parent = lookup_commit(parent.hash);
if (new_parent)
pptr = &commit_list_insert(new_parent, pptr)->next;
}
int i;
struct commit *new_parent;
for (i = 0; i < graft->nr_parent; i++) {
- new_parent = lookup_commit(graft->parent[i]);
+ new_parent = lookup_commit(graft->parent[i].hash);
if (!new_parent)
continue;
pptr = &commit_list_insert(new_parent, pptr)->next;
return 0;
}
-int parse_commit(struct commit *item)
+int parse_commit_gently(struct commit *item, int quiet_on_missing)
{
enum object_type type;
void *buffer;
return 0;
buffer = read_sha1_file(item->object.sha1, &type, &size);
if (!buffer)
- return error("Could not read %s",
+ return quiet_on_missing ? -1 :
+ error("Could not read %s",
sha1_to_hex(item->object.sha1));
if (type != OBJ_COMMIT) {
free(buffer);
{
struct object *obj;
struct commit *commit;
- unsigned char sha1[20];
- if (get_sha1(name, sha1))
+ struct object_id oid;
+ if (get_sha1(name, oid.hash))
return NULL;
- obj = parse_object(sha1);
+ obj = parse_object(oid.hash);
commit = (struct commit *)peel_to_type(name, 0, obj, OBJ_COMMIT);
if (commit && !commit->util) {
struct merge_remote_desc *desc;
struct commit *lookup_commit_or_die(const unsigned char *sha1, const char *ref_name);
int parse_commit_buffer(struct commit *item, const void *buffer, unsigned long size);
-int parse_commit(struct commit *item);
+int parse_commit_gently(struct commit *item, int quiet_on_missing);
+static inline int parse_commit(struct commit *item)
+{
+ return parse_commit_gently(item, 0);
+}
void parse_commit_or_die(struct commit *item);
/*
void sort_in_topological_order(struct commit_list **, enum rev_sort_order);
struct commit_graft {
- unsigned char sha1[20];
+ struct object_id oid;
int nr_parent; /* < 0 if shallow commit */
- unsigned char parent[FLEX_ARRAY][20]; /* more */
+ struct object_id parent[FLEX_ARRAY]; /* more */
};
typedef int (*each_commit_graft_fn)(const struct commit_graft *, void *);
/* initialize Unicode console */
winansi_init();
}
+
+int uname(struct utsname *buf)
+{
+ DWORD v = GetVersion();
+ memset(buf, 0, sizeof(*buf));
+ strcpy(buf->sysname, "Windows");
+ sprintf(buf->release, "%u.%u", v & 0xff, (v >> 8) & 0xff);
+ /* assuming NT variants only.. */
+ sprintf(buf->version, "%u", (v >> 16) & 0x7fff);
+ return 0;
+}
};
#define ITIMER_REAL 0
+struct utsname {
+ char sysname[16];
+ char nodename[1];
+ char release[16];
+ char version[16];
+ char machine[1];
+};
+
/*
* sanitize preprocessor namespace polluted by Windows headers defining
* macros which collide with git local versions
{ return 0; }
static inline int fsync(int fd)
{ return _commit(fd); }
-static inline pid_t getppid(void)
-{ return 1; }
static inline void sync(void)
{}
static inline uid_t getuid(void)
#define SIG_UNBLOCK 0
static inline int sigprocmask(int how, const sigset_t *set, sigset_t *oldset)
{ return 0; }
+static inline pid_t getppid(void)
+{ return 1; }
+static inline pid_t getpgid(pid_t pid)
+{ return pid == 0 ? getpid() : pid; }
+static inline pid_t tcgetpgrp(int fd)
+{ return getpid(); }
/*
* simple adaptors
int setitimer(int type, struct itimerval *in, struct itimerval *out);
int sigaction(int sig, struct sigaction *in, struct sigaction *out);
int link(const char *oldpath, const char *newpath);
+int uname(struct utsname *buf);
/*
* replacements of existing functions
static int config_file_fgetc(struct config_source *conf)
{
- return fgetc(conf->u.file);
+ return getc_unlocked(conf->u.file);
}
static int config_file_ungetc(int c, struct config_source *conf)
f = fopen(filename, "r");
if (f) {
+ flockfile(f);
ret = do_config_from_file(fn, filename, filename, f, data);
+ funlockfile(f);
fclose(f);
}
return ret;
int ret;
struct lock_file *lock = NULL;
char *filename_buf = NULL;
+ char *contents = NULL;
+ size_t contents_sz;
/* parse-key returns negative; flip the sign to feed exit(3) */
ret = 0 - git_config_parse_key(key, &store.key, &store.baselen);
goto write_err_out;
} else {
struct stat st;
- char *contents;
- size_t contents_sz, copy_begin, copy_end;
+ size_t copy_begin, copy_end;
int i, new_line = 0;
if (value_regex == NULL)
fstat(in_fd, &st);
contents_sz = xsize_t(st.st_size);
- contents = xmmap(NULL, contents_sz, PROT_READ,
- MAP_PRIVATE, in_fd, 0);
+ contents = xmmap_gently(NULL, contents_sz, PROT_READ,
+ MAP_PRIVATE, in_fd, 0);
+ if (contents == MAP_FAILED) {
+ if (errno == ENODEV && S_ISDIR(st.st_mode))
+ errno = EISDIR;
+ error("unable to mmap '%s': %s",
+ config_filename, strerror(errno));
+ ret = CONFIG_INVALID_FILE;
+ contents = NULL;
+ goto out_free;
+ }
close(in_fd);
if (chmod(lock->filename.buf, st.st_mode & 07777) < 0) {
contents_sz - copy_begin) <
contents_sz - copy_begin)
goto write_err_out;
-
- munmap(contents, contents_sz);
}
if (commit_lock_file(lock) < 0) {
if (lock)
rollback_lock_file(lock);
free(filename_buf);
+ if (contents)
+ munmap(contents, contents_sz);
return ret;
write_err_out:
HAVE_DEV_TTY = YesPlease
HAVE_CLOCK_GETTIME = YesPlease
HAVE_CLOCK_MONOTONIC = YesPlease
+ HAVE_GETDELIM = YesPlease
endif
ifeq ($(uname_S),GNU/kFreeBSD)
HAVE_ALLOCA_H = YesPlease
ifeq ($(shell expr "$(uname_R)" : '[15]\.'),2)
NO_STRLCPY = YesPlease
endif
+ ifeq ($(shell test "`expr "$(uname_R)" : '\([0-9][0-9]*\)\.'`" -ge 11 && echo 1),1)
+ HAVE_GETDELIM = YesPlease
+ endif
NO_MEMMEM = YesPlease
USE_ST_TIMESPEC = YesPlease
HAVE_DEV_TTY = YesPlease
[NO_INITGROUPS=YesPlease])
GIT_CONF_SUBST([NO_INITGROUPS])
#
+# Define HAVE_GETDELIM if you have getdelim in the C library.
+GIT_CHECK_FUNC(getdelim,
+[HAVE_GETDELIM=YesPlease],
+[HAVE_GETDELIM=])
+GIT_CONF_SUBST([HAVE_GETDELIM])
+#
#
# Define NO_MMAP if you want to avoid mmap.
#
checkout-index) : plumbing;;
commit-tree) : plumbing;;
count-objects) : infrequent;;
- credential-cache) : credentials helper;;
- credential-store) : credentials helper;;
+ credential) : credentials;;
+ credential-*) : credentials helper;;
cvsexportcommit) : export;;
cvsimport) : import;;
cvsserver) : daemon;;
__git_porcelain_commands=
__git_compute_porcelain_commands ()
{
- __git_compute_all_commands
test -n "$__git_porcelain_commands" ||
__git_porcelain_commands=$(__git_list_porcelain_commands)
}
-__git_pretty_aliases ()
+# Lists all set config variables starting with the given section prefix,
+# with the prefix removed.
+__git_get_config_variables ()
{
- local i IFS=$'\n'
- for i in $(git --git-dir="$(__gitdir)" config --get-regexp "pretty\..*" 2>/dev/null); do
- case "$i" in
- pretty.*)
- i="${i#pretty.}"
- echo "${i/ */}"
- ;;
- esac
+ local section="$1" i IFS=$'\n'
+ for i in $(git --git-dir="$(__gitdir)" config --get-regexp "^$section\..*" 2>/dev/null); do
+ i="${i#$section.}"
+ echo "${i/ */}"
done
}
+__git_pretty_aliases ()
+{
+ __git_get_config_variables "pretty"
+}
+
__git_aliases ()
{
- local i IFS=$'\n'
- for i in $(git --git-dir="$(__gitdir)" config --get-regexp "alias\..*" 2>/dev/null); do
- case "$i" in
- alias.*)
- i="${i#alias.}"
- echo "${i/ */}"
- ;;
- esac
- done
+ __git_get_config_variables "alias"
}
# __git_aliased_command requires 1 argument
case "$cur" in
--cleanup=*)
- __gitcomp "default strip verbatim whitespace
+ __gitcomp "default scissors strip verbatim whitespace
" "" "${cur##--cleanup=}"
return
;;
http.noEPSV
http.postBuffer
http.proxy
+ http.sslCipherList
http.sslCAInfo
http.sslCAPath
http.sslCert
__git_complete_remote_or_refspec
;;
update)
- local i c='' IFS=$'\n'
- for i in $(git --git-dir="$(__gitdir)" config --get-regexp "remotes\..*" 2>/dev/null); do
- i="${i#remotes.}"
- c="$c ${i/ */}"
- done
- __gitcomp "$c"
+ __gitcomp "$(__git_get_config_variables "remotes")"
;;
*)
;;
_git_revert ()
{
+ local dir="$(__gitdir)"
+ if [ -f "$dir"/REVERT_HEAD ]; then
+ __gitcomp "--continue --quit --abort"
+ return
+ fi
case "$cur" in
--*)
__gitcomp "--edit --mainline --no-edit --no-commit --signoff"
exit
endif
-cat << EOF > ${__git_tcsh_completion_script}
+cat << EOF >! ${__git_tcsh_completion_script}
#!bash
#
# This script is GENERATED and will be overwritten automatically.
# git always compare HEAD to @{upstream}
# svn always compare HEAD to your SVN upstream
#
+# You can change the separator between the branch name and the above
+# state symbols by setting GIT_PS1_STATESEPARATOR. The default separator
+# is SP.
+#
# By default, __git_ps1 will compare HEAD to your SVN upstream if it can
# find one, or @{upstream} otherwise. Once you have set
# GIT_PS1_SHOWUPSTREAM, you can override it on a per-repository basis by
+Release 1.1.1 (bugfix-only release)
+===================================
+
+* The SMTP mailer was not working with Python 2.4.
+
+Release 1.1.0
+=============
+
+* When a single commit is pushed, omit the reference changed email.
+ Set multimailhook.combineWhenSingleCommit to false to disable this
+ new feature.
+
+* In gitolite environments, the pusher's email address can be used as
+ the From address by creating a specially formatted comment block in
+ gitolite.conf (see multimailhook.from in README).
+
+* Support for SMTP authentication and SSL/TLS encryption was added,
+ see smtpUser, smtpPass, smtpEncryption in README.
+
+* A new option scanCommitForCc was added to allow git-multimail to
+ search the commit message for 'Cc: ...' lines, and add the
+ corresponding emails in Cc.
+
+* If $USER is not set, use the variable $USERNAME. This is needed on
+ Windows platform to recognize the pusher.
+
+* The emailPrefix variable can now be set to an empty string to remove
+ the prefix.
+
+* A short tutorial was added in doc/gitolite.rst to set up
+ git-multimail with gitolite.
+
+* The post-receive file was renamed to post-receive.example. It has
+ always been an example (the standard way to call git-multimail is to
+ call git_multimail.py), but it was unclear to many users.
+
+* A new refchangeShowGraph option was added to make it possible to
+ include both a graph and a log in the summary emails. The options
+ to control the graph formatting can be set via the new graphOpts
+ option.
+
+* New option --force-send was added to disable new commit detection
+ for update hook. One use-case is to run git_multimail.py after
+ running "git fetch" to send emails about commits that have just been
+ fetched (the detection of new commits was unreliable in this mode).
+
+* The testing infrastructure was considerably improved (continuous
+ integration with travis-ci, automatic check of PEP8 and RST syntax,
+ many improvements to the test scripts).
+
+This version has been tested with Python 2.4 to 2.7, and Git 1.7.1 to
+2.4.
+
Release 1.0.0
=============
- git-multimail
- =============
+git-multimail Version 1.1.1
+===========================
+
+.. image:: https://travis-ci.org/git-multimail/git-multimail.svg?branch=master
+ :target: https://travis-ci.org/git-multimail/git-multimail
git-multimail is a tool for sending notification emails on pushes to a
Git repository. It includes a Python module called git_multimail.py,
list) makes it easy to scan through the emails, jump to patches
that need further attention, and write comments about specific
commits. Commits are handled in reverse topological order (i.e.,
- parents shown before children). For example,
-
- [git] branch master updated
- + [git] 01/08: doc: fix xref link from api docs to manual pages
- + [git] 02/08: api-credentials.txt: show the big picture first
- + [git] 03/08: api-credentials.txt: mention credential.helper explicitly
- + [git] 04/08: api-credentials.txt: add "see also" section
- + [git] 05/08: t3510 (cherry-pick-sequence): add missing '&&'
- + [git] 06/08: Merge branch 'rr/maint-t3510-cascade-fix'
- + [git] 07/08: Merge branch 'mm/api-credentials-doc'
- + [git] 08/08: Git 1.7.11-rc2
+ parents shown before children). For example::
+
+ [git] branch master updated
+ + [git] 01/08: doc: fix xref link from api docs to manual pages
+ + [git] 02/08: api-credentials.txt: show the big picture first
+ + [git] 03/08: api-credentials.txt: mention credential.helper explicitly
+ + [git] 04/08: api-credentials.txt: add "see also" section
+ + [git] 05/08: t3510 (cherry-pick-sequence): add missing '&&'
+ + [git] 06/08: Merge branch 'rr/maint-t3510-cascade-fix'
+ + [git] 07/08: Merge branch 'mm/api-credentials-doc'
+ + [git] 08/08: Git 1.7.11-rc2
Each commit appears in exactly one commit email, the first time
that it is pushed to the repository. If a commit is later merged
3.x.
The example scripts invoke Python using the following shebang line
- (following PEP 394 [1]):
+ (following PEP 394 [1]_)::
#! /usr/bin/env python2
If your system's Python2 interpreter is not in your PATH or is not
- called "python2", you can change the lines accordingly. Or you can
+ called ``python2``, you can change the lines accordingly. Or you can
invoke the Python interpreter explicitly, for example via a tiny
- shell script like
+ shell script like::
#! /bin/sh
/usr/local/bin/python /path/to/git_multimail.py "$@"
-* The "git" command must be in your PATH. git-multimail is known to
+* The ``git`` command must be in your PATH. git-multimail is known to
work with Git versions back to 1.7.1. (Earlier versions have not
been tested; if you do so, please report your results.)
Invocation
----------
-git_multimail.py is designed to be used as a "post-receive" hook in a
+git_multimail.py is designed to be used as a ``post-receive`` hook in a
Git repository (see githooks(5)). Link or copy it to
$GIT_DIR/hooks/post-receive within the repository for which email
notifications are desired. Usually it should be installed on the
pushed.
For use on pre-v1.5.1 Git servers, git_multimail.py can also work as
-an "update" hook, taking its arguments on the command line. To use
+an ``update`` hook, taking its arguments on the command line. To use
this script in this manner, link or copy it to $GIT_DIR/hooks/update.
Please note that the script is not completely reliable in this mode
-[2].
+[2]_.
Alternatively, git_multimail.py can be imported as a Python module
into your own Python post-receive script. This method is a bit more
only about changes affecting particular files or subdirectories)
Or you can change how emails are sent by writing your own Mailer
-class. The "post-receive" script in this directory demonstrates how
+class. The ``post-receive`` script in this directory demonstrates how
to use git_multimail.py as a Python module. (If you make interesting
changes of this type, please consider sharing them with the
community.)
-------------
By default, git-multimail mostly takes its configuration from the
-following "git config" settings:
+following ``git config`` settings:
multimailhook.environment
This describes the general environment of the repository.
Currently supported values:
- "generic" -- the username of the pusher is read from $USER and the
- repository name is derived from the repository's path.
+ * generic
+
+ the username of the pusher is read from $USER or $USERNAME and
+ the repository name is derived from the repository's path.
+
+ * gitolite
- "gitolite" -- the username of the pusher is read from $GL_USER and
- the repository name from $GL_REPO.
+ the username of the pusher is read from $GL_USER, the repository
+ name is read from $GL_REPO, and the From: header value is
+ optionally read from gitolite.conf (see multimailhook.from).
+
+ For more information about gitolite and git-multimail, read
+ doc/gitolite.rst
If neither of these environments is suitable for your setup, then
you can implement a Python class that inherits from Environment
The environment value can be specified on the command line using
the --environment option. If it is not specified on the command
line or by multimailhook.environment, then it defaults to
- "gitolite" if the environment contains variables $GL_USER and
- $GL_REPO; otherwise "generic".
+ ``gitolite`` if the environment contains variables $GL_USER and
+ $GL_REPO; otherwise ``generic``.
multimailhook.repoName
not so straightforward, then the shortlog might be confusing
rather than useful. Default is false.
+multimailhook.refchangeShowGraph
+
+ If this option is set to true, then summary emails about reference
+ changes will additionally include:
+
+ * a graph of the added commits (if any)
+
+ * a graph of the discarded commits (if any)
+
+ The log is generated by running ``git log --graph`` with the options
+ specified in graphOpts. The default is false.
+
multimailhook.refchangeShowLog
If this option is set to true, then summary emails about reference
changes will include a detailed log of the added commits in
addition to the one line summary. The log is generated by running
- "git log" with the options specified in multimailhook.logOpts.
+ ``git log`` with the options specified in multimailhook.logOpts.
Default is false.
multimailhook.mailer
This option changes the way emails are sent. Accepted values are:
- - sendmail (the default): use the command /usr/sbin/sendmail or
- /usr/lib/sendmail (or sendmailCommand, if configured). This
+ - sendmail (the default): use the command ``/usr/sbin/sendmail`` or
+ ``/usr/lib/sendmail`` (or sendmailCommand, if configured). This
mode can be further customized via the following options:
- multimailhook.sendmailCommand
+ * multimailhook.sendmailCommand
- The command used by mailer "sendmail" to send emails. Shell
- quoting is allowed in the value of this setting, but remember that
- Git requires double-quotes to be escaped; e.g.,
+ The command used by mailer ``sendmail`` to send emails. Shell
+ quoting is allowed in the value of this setting, but remember that
+ Git requires double-quotes to be escaped; e.g.::
git config multimailhook.sendmailcommand '/usr/sbin/sendmail -oi -t -F \"Git Repo\"'
- Default is '/usr/sbin/sendmail -oi -t' or
- '/usr/lib/sendmail -oi -t' (depending on which file is
- present and executable).
+ Default is '/usr/sbin/sendmail -oi -t' or
+ '/usr/lib/sendmail -oi -t' (depending on which file is
+ present and executable).
- multimailhook.envelopeSender
+ * multimailhook.envelopeSender
- If set then pass this value to sendmail via the -f option to set
- the envelope sender address.
+ If set then pass this value to sendmail via the -f option to set
+ the envelope sender address.
- smtp: use Python's smtplib. This is useful when the sendmail
command is not available on the system. This mode can be
further customized via the following options:
- multimailhook.smtpServer
+ * multimailhook.smtpServer
+
+ The name of the SMTP server to connect to. The value can
+ also include a colon and a port number; e.g.,
+ ``mail.example.com:25``. Default is 'localhost' using port 25.
+
+ * multimailhook.smtpUser
+ * multimailhook.smtpPass
+
+ Server username and password. Required if smtpEncryption is 'ssl'.
+ Note that the username and password currently need to be
+ set cleartext in the configuration file, which is not
+ recommended. If you need to use this option, be sure your
+ configuration file is read-only.
+
+ * multimailhook.envelopeSender
+
+ The sender address to be passed to the SMTP server. If
+ unset, then the value of multimailhook.from is used.
+
+ * multimailhook.smtpServerTimeout
+
+ Timeout in seconds.
- The name of the SMTP server to connect to. The value can
- also include a colon and a port number; e.g.,
- "mail.example.com:25". Default is 'localhost' using port
- 25.
+ * multimailhook.smtpEncryption
- multimailhook.envelopeSender
+ Set the security type. Allowed values: none, ssl.
+ Default=none.
- The sender address to be passed to the SMTP server. If
- unset, then the value of multimailhook.from is used.
+ * multimailhook.smtpServerDebugLevel
+
+ Integer number. Set to greater than 0 to activate debugging.
multimailhook.from
- If set then use this value in the From: field of generated emails.
- If unset, then use the repository's user configuration (user.name
- and user.email). If user.email is also unset, then use
- multimailhook.envelopeSender.
+ If set, use this value in the From: field of generated emails. If
+ unset, the value of the From: header is determined as follows:
+
+ 1. (gitolite environment only) Parse gitolite.conf, looking for a
+ block of comments that looks like this::
+
+ # BEGIN USER EMAILS
+ # username Firstname Lastname <email@example.com>
+ # END USER EMAILS
+
+ If that block exists, and there is a line between the BEGIN
+ USER EMAILS and END USER EMAILS lines where the first field
+ matches the gitolite username ($GL_USER), use the rest of the
+ line for the From: header.
+
+ 2. If the user.email configuration setting is set, use its value
+ (and the value of user.name, if set).
+
+ 3. Use the value of multimailhook.envelopeSender.
multimailhook.administrator
All emails have this string prepended to their subjects, to aid
email filtering (though filtering based on the X-Git-* email
headers is probably more robust). Default is the short name of
- the repository in square brackets; e.g., "[myrepo]".
+ the repository in square brackets; e.g., ``[myrepo]``. Set this
+ value to the empty string to suppress the email prefix.
multimailhook.emailMaxLines
multimailhook.emailMaxLineLength
The maximum length of a line in the email body. Lines longer than
- this limit are truncated to this length with a trailing " [...]"
+ this limit are truncated to this length with a trailing `` [...]``
added to indicate the missing text. The default is 500, because
(a) diffs with longer lines are probably from binary files, for
which a diff is useless, and (b) even if a text file has such long
multimailhook.emailStrictUTF8
- If this boolean option is set to "true", then the main part of the
+ If this boolean option is set to `true`, then the main part of the
email body is forced to be valid UTF-8. Any characters that are
not valid UTF-8 are converted to the Unicode replacement
- character, U+FFFD. The default is "true".
+ character, U+FFFD. The default is `true`.
multimailhook.diffOpts
- Options passed to "git diff-tree" when generating the summary
- information for ReferenceChange emails. Default is "--stat
- --summary --find-copies-harder". Add -p to those options to
+ Options passed to ``git diff-tree`` when generating the summary
+ information for ReferenceChange emails. Default is ``--stat
+ --summary --find-copies-harder``. Add -p to those options to
include a unified diff of changes in addition to the usual summary
output. Shell quoting is allowed; see multimailhook.logOpts for
details.
+multimailhook.graphOpts
+
+ Options passed to ``git log --graph`` when generating graphs for the
+ reference change summary emails (used only if refchangeShowGraph
+ is true). The default is '--oneline --decorate'.
+
+ Shell quoting is allowed; see logOpts for details.
+
multimailhook.logOpts
- Options passed to "git log" to generate additional info for
+ Options passed to ``git log`` to generate additional info for
reference change emails (used only if refchangeShowLog is set).
- For example, adding --graph will show the graph of revisions, -p
- will show the complete diff, etc. The default is empty.
+ For example, adding -p will show each commit's complete diff. The
+ default is empty.
Shell quoting is allowed; for example, a log format that contains
- spaces can be specified using something like:
+ spaces can be specified using something like::
git config multimailhook.logopts '--pretty=format:"%h %aN <%aE>%n%s%n%n%b%n"'
If you want to set this by editing your configuration file
directly, remember that Git requires double-quotes to be escaped
- (see git-config(1) for more information):
+ (see git-config(1) for more information)::
[multimailhook]
logopts = --pretty=format:\"%h %aN <%aE>%n%s%n%n%b%n\"
multimailhook.commitLogOpts
- Options passed to "git log" to generate additional info for
+ Options passed to ``git log`` to generate additional info for
revision change emails. For example, adding --ignore-all-spaces
- will suppress whitespace changes. The default options are "-C
- --stat -p --cc". Shell quoting is allowed; see
+ will suppress whitespace changes. The default options are ``-C
+ --stat -p --cc``. Shell quoting is allowed; see
multimailhook.logOpts for details.
multimailhook.emailDomain
Domain name appended to the username of the person doing the push
- to convert it into an email address (via "%s@%s" % (username,
- emaildomain)). More complicated schemes can be implemented by
- overriding Environment and overriding its get_pusher_email()
- method.
+ to convert it into an email address
+ (via ``"%s@%s" % (username, emaildomain)``). More complicated
+ schemes can be implemented by overriding Environment and
+ overriding its get_pusher_email() method.
multimailhook.replyTo
multimailhook.replyToCommit
- An email address, which will be used directly.
- - The value "pusher", in which case the pusher's address (if
+ - The value `pusher`, in which case the pusher's address (if
available) will be used. This is the default for refchange
emails.
- - The value "author" (meaningful only for replyToCommit), in which
+ - The value `author` (meaningful only for replyToCommit), in which
case the commit author's address will be used. This is the
default for commit emails.
- - The value "none", in which case the Reply-To: field will be
+ - The value `none`, in which case the Reply-To: field will be
omitted.
+multimailhook.quiet
+
+ Do not output the list of email recipients from the hook
+
+multimailhook.stdout
+
+ For debugging, send emails to stdout rather than to the
+ mailer. Equivalent to the --stdout command line option
+
+multimailhook.scanCommitForCc
+
+ If this option is set to true, than recipients from lines in commit body
+ that starts with ``CC:`` will be added to CC list.
+ Default: false
+
+multimailhook.combineWhenSingleCommit
+
+ If this option is set to true and a single new commit is pushed to
+ a branch, combine the summary and commit email messages into a
+ single email.
+ Default: true
+
Email filtering aids
--------------------
All emails include extra headers to enable fine tuned filtering and
give information for debugging. All emails include the headers
-"X-Git-Host", "X-Git-Repo", "X-Git-Refname", and "X-Git-Reftype".
-ReferenceChange emails also include headers "X-Git-Oldrev" and "X-Git-Newrev";
-Revision emails also include header "X-Git-Rev".
+``X-Git-Host``, ``X-Git-Repo``, ``X-Git-Refname``, and ``X-Git-Reftype``.
+ReferenceChange emails also include headers ``X-Git-Oldrev`` and ``X-Git-Newrev``;
+Revision emails also include header ``X-Git-Rev``.
Customizing email contents
* GenericEnvironment: a stand-alone Git repository.
* GitoliteEnvironment: a Git repository that is managed by gitolite
- [3]. For such repositories, the identity of the pusher is read from
- environment variable $GL_USER, and the name of the repository is
- read from $GL_REPO (if it is not overridden by
- multimailhook.reponame).
+ [3]_. For such repositories, the identity of the pusher is read from
+ environment variable $GL_USER, the name of the repository is read
+ from $GL_REPO (if it is not overridden by multimailhook.reponame),
+ and the From: header value is optionally read from gitolite.conf
+ (see multimailhook.from).
By default, git-multimail assumes GitoliteEnvironment if $GL_USER and
$GL_REPO are set, and otherwise assumes GenericEnvironment.
Alternatively, you can choose one of these two environments explicitly
-by setting a "multimailhook.environment" config setting (which can
-have the value "generic" or "gitolite") or by passing an --environment
+by setting a ``multimailhook.environment`` config setting (which can
+have the value `generic` or `gitolite`) or by passing an --environment
option to the script.
If you need to customize the script in ways that are not supported by
post-receive script. Then implement your environment class; it should
usually inherit from one of the existing Environment classes and
possibly one or more of the EnvironmentMixin classes. Then set the
-"environment" variable to an instance of your own environment class
-and pass it to run_as_post_receive_hook().
+``environment`` variable to an instance of your own environment class
+and pass it to ``run_as_post_receive_hook()``.
The standard environment classes, GenericEnvironment and
GitoliteEnvironment, are in fact themselves put together out of a
Footnotes
---------
-[1] http://www.python.org/dev/peps/pep-0394/
+.. [1] http://www.python.org/dev/peps/pep-0394/
-[2] Because of the way information is passed to update hooks, the
- script's method of determining whether a commit has already been
- seen does not work when it is used as an "update" script. In
- particular, no notification email will be generated for a new
- commit that is added to multiple references in the same push.
+.. [2] Because of the way information is passed to update hooks, the
+ script's method of determining whether a commit has already
+ been seen does not work when it is used as an ``update`` script.
+ In particular, no notification email will be generated for a
+ new commit that is added to multiple references in the same
+ push. A workaround is to use --force-send to force sending the
+ emails.
-[3] https://github.com/sitaramc/gitolite
+.. [3] https://github.com/sitaramc/gitolite
https://github.com/git-multimail/git-multimail
The version in this directory was obtained from the upstream project
-on 2015-04-27 and consists of the "git-multimail" subdirectory from
+on July 03 2015 and consists of the "git-multimail" subdirectory from
revision
- 8c3aaafa873bf10de8dddf1d202c449b3eff3b42 refs/tags/1.0.2
+ 6d6c9eb62a054143322cfaecde3949189c065b46 refs/tags/1.1.1
Please see the README file in this directory for information about how
to report bugs or contribute to git-multimail.
#! /usr/bin/env python2
+# Copyright (c) 2015 Matthieu Moy and others
# Copyright (c) 2012-2014 Michael Haggerty and others
# Derived from contrib/hooks/post-receive-email, which is
# Copyright (c) 2007 Andy Parkins
' (was %(oldrev_short)s)'
)
+COMBINED_REFCHANGE_REVISION_SUBJECT_TEMPLATE = (
+ '%(emailprefix)s%(refname_type)s %(short_refname)s updated: %(oneline)s'
+ )
+
REFCHANGE_HEADER_TEMPLATE = """\
Date: %(send_date)s
To: %(recipients)s
REVISION_HEADER_TEMPLATE = """\
Date: %(send_date)s
To: %(recipients)s
+Cc: %(cc_recipients)s
Subject: %(emailprefix)s%(num)02d/%(tot)02d: %(oneline)s
MIME-Version: 1.0
Content-Type: text/plain; charset=%(charset)s
REVISION_FOOTER_TEMPLATE = FOOTER_TEMPLATE
+# Combined, meaning refchange+revision email (for single-commit additions)
+COMBINED_HEADER_TEMPLATE = """\
+Date: %(send_date)s
+To: %(recipients)s
+Subject: %(subject)s
+MIME-Version: 1.0
+Content-Type: text/plain; charset=%(charset)s
+Content-Transfer-Encoding: 8bit
+Message-ID: %(msgid)s
+From: %(fromaddr)s
+Reply-To: %(reply_to)s
+X-Git-Host: %(fqdn)s
+X-Git-Repo: %(repo_shortname)s
+X-Git-Refname: %(refname)s
+X-Git-Reftype: %(refname_type)s
+X-Git-Oldrev: %(oldrev)s
+X-Git-Newrev: %(newrev)s
+X-Git-Rev: %(rev)s
+Auto-Submitted: auto-generated
+"""
+
+COMBINED_INTRO_TEMPLATE = """\
+This is an automated email from the git hooks/post-receive script.
+
+%(pusher)s pushed a commit to %(refname_type)s %(short_refname)s
+in repository %(repo_shortname)s.
+
+"""
+
+COMBINED_FOOTER_TEMPLATE = FOOTER_TEMPLATE
+
+
class CommandError(Exception):
def __init__(self, cmd, retcode):
self.cmd = cmd
return read_git_output(args, keepends=True, **kw).splitlines(keepends)
+def git_rev_list_ish(cmd, spec, args=None, **kw):
+ """Common functionality for invoking a 'git rev-list'-like command.
+
+ Parameters:
+ * cmd is the Git command to run, e.g., 'rev-list' or 'log'.
+ * spec is a list of revision arguments to pass to the named
+ command. If None, this function returns an empty list.
+ * args is a list of extra arguments passed to the named command.
+ * All other keyword arguments (if any) are passed to the
+ underlying read_git_lines() function.
+
+ Return the output of the Git command in the form of a list, one
+ entry per output line.
+ """
+ if spec is None:
+ return []
+ if args is None:
+ args = []
+ args = [cmd, '--stdin'] + args
+ spec_stdin = ''.join(s + '\n' for s in spec)
+ return read_git_lines(args, input=spec_stdin, **kw)
+
+
+def git_rev_list(spec, **kw):
+ """Run 'git rev-list' with the given list of revision arguments.
+
+ See git_rev_list_ish() for parameter and return value
+ documentation.
+ """
+ return git_rev_list_ish('rev-list', spec, **kw)
+
+
+def git_log(spec, **kw):
+ """Run 'git log' with the given list of revision arguments.
+
+ See git_rev_list_ish() for parameter and return value
+ documentation.
+ """
+ return git_rev_list_ish('log', spec, **kw)
+
+
def header_encode(text, header_name=None):
"""Encode and line-wrap the value of an email header field."""
def get(self, name, default=None):
try:
values = self._split(read_git_output(
- ['config', '--get', '--null', '%s.%s' % (self.section, name)],
- env=self.env, keepends=True,
- ))
+ ['config', '--get', '--null', '%s.%s' % (self.section, name)],
+ env=self.env, keepends=True,
+ ))
assert len(values) == 1
return values[0]
except CommandError:
env=self.env,
)
- def has_key(self, name):
+ def __contains__(self, name):
return self.get_all(name, default=None) is not None
+ # We don't use this method anymore internally, but keep it here in
+ # case somebody is calling it from their own code:
+ def has_key(self, name):
+ return name in self
+
def unset_all(self, name):
try:
read_git_output(
self._values = None
def _compute_values(self):
- """Return a dictionary {keyword : expansion} for this Change.
+ """Return a dictionary {keyword: expansion} for this Change.
Derived classes overload this method to add more entries to
the return value. This method is used internally by
return self.environment.get_values()
def get_values(self, **extra_values):
- """Return a dictionary {keyword : expansion} for this Change.
+ """Return a dictionary {keyword: expansion} for this Change.
Return a dictionary mapping keywords to the values that they
should be expanded to for this Change (used when interpolating
value = value % values
except KeyError, e:
if DEBUG:
- sys.stderr.write(
+ self.environment.log_warning(
'Warning: unknown variable %r in the following line; line skipped:\n'
' %s\n'
% (e.args[0], line,)
class Revision(Change):
"""A Change consisting of a single git commit."""
+ CC_RE = re.compile(r'^\s*C[Cc]:\s*(?P<to>[^#]+@[^\s#]*)\s*(#.*)?$')
+
def __init__(self, reference_change, rev, num, tot):
Change.__init__(self, reference_change.environment)
self.reference_change = reference_change
self.author = read_git_output(['log', '--no-walk', '--format=%aN <%aE>', self.rev.sha1])
self.recipients = self.environment.get_revision_recipients(self)
+ self.cc_recipients = ''
+ if self.environment.get_scancommitforcc():
+ self.cc_recipients = ', '.join(to.strip() for to in self._cc_recipients())
+ if self.cc_recipients:
+ self.environment.log_msg(
+ 'Add %s to CC for %s\n' % (self.cc_recipients, self.rev.sha1))
+
+ def _cc_recipients(self):
+ cc_recipients = []
+ message = read_git_output(['log', '--no-walk', '--format=%b', self.rev.sha1])
+ lines = message.strip().split('\n')
+ for line in lines:
+ m = re.match(self.CC_RE, line)
+ if m:
+ cc_recipients.append(m.group('to'))
+
+ return cc_recipients
+
def _compute_values(self):
values = Change._compute_values(self)
values['num'] = self.num
values['tot'] = self.tot
values['recipients'] = self.recipients
+ if self.cc_recipients:
+ values['cc_recipients'] = self.cc_recipients
values['oneline'] = oneline
values['author'] = self.author
def generate_email_header(self, **extra_values):
for line in self.expand_header_lines(
- REVISION_HEADER_TEMPLATE, **extra_values
- ):
+ REVISION_HEADER_TEMPLATE, **extra_values
+ ):
yield line
def generate_email_intro(self):
klass = BranchChange
elif area == 'remotes':
# Tracking branch:
- sys.stderr.write(
+ environment.log_warning(
'*** Push-update of tracking branch %r\n'
'*** - incomplete email generated.\n'
- % (refname,)
+ % (refname,)
)
klass = OtherReferenceChange
else:
# Some other reference namespace:
- sys.stderr.write(
+ environment.log_warning(
'*** Push-update of strange reference %r\n'
'*** - incomplete email generated.\n'
- % (refname,)
+ % (refname,)
)
klass = OtherReferenceChange
else:
# Anything else (is there anything else?)
- sys.stderr.write(
+ environment.log_warning(
'*** Unknown type of update to %r (%s)\n'
'*** - incomplete email generated.\n'
- % (refname, rev.type,)
+ % (refname, rev.type,)
)
klass = OtherReferenceChange
def __init__(self, environment, refname, short_refname, old, new, rev):
Change.__init__(self, environment)
self.change_type = {
- (False, True) : 'create',
- (True, True) : 'update',
- (True, False) : 'delete',
+ (False, True): 'create',
+ (True, True): 'update',
+ (True, False): 'delete',
}[bool(old), bool(new)]
self.refname = refname
self.short_refname = short_refname
self.rev = rev
self.msgid = make_msgid()
self.diffopts = environment.diffopts
+ self.graphopts = environment.graphopts
self.logopts = environment.logopts
self.commitlogopts = environment.commitlogopts
+ self.showgraph = environment.refchange_showgraph
self.showlog = environment.refchange_showlog
+ self.header_template = REFCHANGE_HEADER_TEMPLATE
+ self.intro_template = REFCHANGE_INTRO_TEMPLATE
+ self.footer_template = FOOTER_TEMPLATE
+
def _compute_values(self):
values = Change._compute_values(self)
return values
+ def send_single_combined_email(self, known_added_sha1s):
+ """Determine if a combined refchange/revision email should be sent
+
+ If there is only a single new (non-merge) commit added by a
+ change, it is useful to combine the ReferenceChange and
+ Revision emails into one. In such a case, return the single
+ revision; otherwise, return None.
+
+ This method is overridden in BranchChange."""
+
+ return None
+
+ def generate_combined_email(self, push, revision, body_filter=None, extra_header_values={}):
+ """Generate an email describing this change AND specified revision.
+
+ Iterate over the lines (including the header lines) of an
+ email describing this change. If body_filter is not None,
+ then use it to filter the lines that are intended for the
+ email body.
+
+ The extra_header_values field is received as a dict and not as
+ **kwargs, to allow passing other keyword arguments in the
+ future (e.g. passing extra values to generate_email_intro()
+
+ This method is overridden in BranchChange."""
+
+ raise NotImplementedError
+
def get_subject(self):
template = {
- 'create' : REF_CREATED_SUBJECT_TEMPLATE,
- 'update' : REF_UPDATED_SUBJECT_TEMPLATE,
- 'delete' : REF_DELETED_SUBJECT_TEMPLATE,
+ 'create': REF_CREATED_SUBJECT_TEMPLATE,
+ 'update': REF_UPDATED_SUBJECT_TEMPLATE,
+ 'delete': REF_DELETED_SUBJECT_TEMPLATE,
}[self.change_type]
return self.expand(template)
extra_values['subject'] = self.get_subject()
for line in self.expand_header_lines(
- REFCHANGE_HEADER_TEMPLATE, **extra_values
- ):
+ self.header_template, **extra_values
+ ):
yield line
def generate_email_intro(self):
- for line in self.expand_lines(REFCHANGE_INTRO_TEMPLATE):
+ for line in self.expand_lines(self.intro_template):
yield line
def generate_email_body(self, push):
generate_update_summary() / generate_delete_summary()."""
change_summary = {
- 'create' : self.generate_create_summary,
- 'delete' : self.generate_delete_summary,
- 'update' : self.generate_update_summary,
+ 'create': self.generate_create_summary,
+ 'delete': self.generate_delete_summary,
+ 'update': self.generate_update_summary,
}[self.change_type](push)
for line in change_summary:
yield line
yield line
def generate_email_footer(self):
- return self.expand_lines(FOOTER_TEMPLATE)
+ return self.expand_lines(self.footer_template)
+
+ def generate_revision_change_graph(self, push):
+ if self.showgraph:
+ args = ['--graph'] + self.graphopts
+ for newold in ('new', 'old'):
+ has_newold = False
+ spec = push.get_commits_spec(newold, self)
+ for line in git_log(spec, args=args, keepends=True):
+ if not has_newold:
+ has_newold = True
+ yield '\n'
+ yield 'Graph of %s commits:\n\n' % (
+ {'new': 'new', 'old': 'discarded'}[newold],)
+ yield ' ' + line
+ if has_newold:
+ yield '\n'
def generate_revision_change_log(self, new_commits_list):
if self.showlog:
+ new_commits_list
+ ['--'],
keepends=True,
- ):
+ ):
yield line
+ def generate_new_revision_summary(self, tot, new_commits_list, push):
+ for line in self.expand_lines(NEW_REVISIONS_TEMPLATE, tot=tot):
+ yield line
+ for line in self.generate_revision_change_graph(push):
+ yield line
+ for line in self.generate_revision_change_log(new_commits_list):
+ yield line
+
def generate_revision_change_summary(self, push):
"""Generate a summary of the revisions added/removed by this change."""
sha1s.reverse()
tot = len(sha1s)
new_revisions = [
- Revision(self, GitObject(sha1), num=i+1, tot=tot)
+ Revision(self, GitObject(sha1), num=i + 1, tot=tot)
for (i, sha1) in enumerate(sha1s)
]
BRIEF_SUMMARY_TEMPLATE, action='new', text=subject,
)
yield '\n'
- for line in self.expand_lines(NEW_REVISIONS_TEMPLATE, tot=tot):
- yield line
- for line in self.generate_revision_change_log([r.rev.sha1 for r in new_revisions]):
+ for line in self.generate_new_revision_summary(
+ tot, [r.rev.sha1 for r in new_revisions], push):
yield line
else:
for line in self.expand_lines(NO_NEW_REVISIONS_TEMPLATE):
# revisions in the summary even though we will not send
# new notification emails for them.
adds = list(generate_summaries(
- '--topo-order', '--reverse', '%s..%s'
- % (self.old.commit_sha1, self.new.commit_sha1,)
- ))
+ '--topo-order', '--reverse', '%s..%s'
+ % (self.old.commit_sha1, self.new.commit_sha1,)
+ ))
# List of the revisions that were removed from the branch
# by this update. This will be empty except for
# non-fast-forward updates.
discards = list(generate_summaries(
- '%s..%s' % (self.new.commit_sha1, self.old.commit_sha1,)
- ))
+ '%s..%s' % (self.new.commit_sha1, self.old.commit_sha1,)
+ ))
if adds:
new_commits_list = push.get_new_commits(self)
yield '\n'
if new_commits:
- for line in self.expand_lines(NEW_REVISIONS_TEMPLATE, tot=len(new_commits)):
- yield line
- for line in self.generate_revision_change_log(new_commits_list):
+ for line in self.generate_new_revision_summary(
+ len(new_commits), new_commits_list, push):
yield line
else:
for line in self.expand_lines(NO_NEW_REVISIONS_TEMPLATE):
yield line
+ for line in self.generate_revision_change_graph(push):
+ yield line
# The diffstat is shown from the old revision to the new
# revision. This is to show the truth of what happened in
yield '\n'
yield 'Summary of changes:\n'
for line in read_git_lines(
- ['diff-tree']
- + self.diffopts
- + ['%s..%s' % (self.old.commit_sha1, self.new.commit_sha1,)],
- keepends=True,
- ):
+ ['diff-tree']
+ + self.diffopts
+ + ['%s..%s' % (self.old.commit_sha1, self.new.commit_sha1,)],
+ keepends=True,
+ ):
yield line
elif self.old.commit_sha1 and not self.new.commit_sha1:
sha1s = list(push.get_discarded_commits(self))
tot = len(sha1s)
discarded_revisions = [
- Revision(self, GitObject(sha1), num=i+1, tot=tot)
+ Revision(self, GitObject(sha1), num=i + 1, tot=tot)
for (i, sha1) in enumerate(sha1s)
]
yield r.expand(
BRIEF_SUMMARY_TEMPLATE, action='discards', text=subject,
)
+ for line in self.generate_revision_change_graph(push):
+ yield line
else:
for line in self.expand_lines(NO_DISCARDED_REVISIONS_TEMPLATE):
yield line
old=old, new=new, rev=rev,
)
self.recipients = environment.get_refchange_recipients(self)
+ self._single_revision = None
+
+ def send_single_combined_email(self, known_added_sha1s):
+ if not self.environment.combine_when_single_commit:
+ return None
+
+ # In the sadly-all-too-frequent usecase of people pushing only
+ # one of their commits at a time to a repository, users feel
+ # the reference change summary emails are noise rather than
+ # important signal. This is because, in this particular
+ # usecase, there is a reference change summary email for each
+ # new commit, and all these summaries do is point out that
+ # there is one new commit (which can readily be inferred by
+ # the existence of the individual revision email that is also
+ # sent). In such cases, our users prefer there to be a combined
+ # reference change summary/new revision email.
+ #
+ # So, if the change is an update and it doesn't discard any
+ # commits, and it adds exactly one non-merge commit (gerrit
+ # forces a workflow where every commit is individually merged
+ # and the git-multimail hook fired off for just this one
+ # change), then we send a combined refchange/revision email.
+ try:
+ # If this change is a reference update that doesn't discard
+ # any commits...
+ if self.change_type != 'update':
+ return None
+
+ if read_git_lines(
+ ['merge-base', self.old.sha1, self.new.sha1]
+ ) != [self.old.sha1]:
+ return None
+
+ # Check if this update introduced exactly one non-merge
+ # commit:
+
+ def split_line(line):
+ """Split line into (sha1, [parent,...])."""
+
+ words = line.split()
+ return (words[0], words[1:])
+
+ # Get the new commits introduced by the push as a list of
+ # (sha1, [parent,...])
+ new_commits = [
+ split_line(line)
+ for line in read_git_lines(
+ [
+ 'log', '-3', '--format=%H %P',
+ '%s..%s' % (self.old.sha1, self.new.sha1),
+ ]
+ )
+ ]
+
+ if not new_commits:
+ return None
+
+ # If the newest commit is a merge, save it for a later check
+ # but otherwise ignore it
+ merge = None
+ tot = len(new_commits)
+ if len(new_commits[0][1]) > 1:
+ merge = new_commits[0][0]
+ del new_commits[0]
+
+ # Our primary check: we can't combine if more than one commit
+ # is introduced. We also currently only combine if the new
+ # commit is a non-merge commit, though it may make sense to
+ # combine if it is a merge as well.
+ if not (
+ len(new_commits) == 1
+ and len(new_commits[0][1]) == 1
+ and new_commits[0][0] in known_added_sha1s
+ ):
+ return None
+
+ # We do not want to combine revision and refchange emails if
+ # those go to separate locations.
+ rev = Revision(self, GitObject(new_commits[0][0]), 1, tot)
+ if rev.recipients != self.recipients:
+ return None
+
+ # We ignored the newest commit if it was just a merge of the one
+ # commit being introduced. But we don't want to ignore that
+ # merge commit it it involved conflict resolutions. Check that.
+ if merge and merge != read_git_output(['diff-tree', '--cc', merge]):
+ return None
+
+ # We can combine the refchange and one new revision emails
+ # into one. Return the Revision that a combined email should
+ # be sent about.
+ return rev
+ except CommandError:
+ # Cannot determine number of commits in old..new or new..old;
+ # don't combine reference/revision emails:
+ return None
+
+ def generate_combined_email(self, push, revision, body_filter=None, extra_header_values={}):
+ values = revision.get_values()
+ if extra_header_values:
+ values.update(extra_header_values)
+ if 'subject' not in extra_header_values:
+ values['subject'] = self.expand(COMBINED_REFCHANGE_REVISION_SUBJECT_TEMPLATE, **values)
+
+ self._single_revision = revision
+ self.header_template = COMBINED_HEADER_TEMPLATE
+ self.intro_template = COMBINED_INTRO_TEMPLATE
+ self.footer_template = COMBINED_FOOTER_TEMPLATE
+ for line in self.generate_email(push, body_filter, values):
+ yield line
+
+ def generate_email_body(self, push):
+ '''Call the appropriate body generation routine.
+
+ If this is a combined refchange/revision email, the special logic
+ for handling this combined email comes from this function. For
+ other cases, we just use the normal handling.'''
+
+ # If self._single_revision isn't set; don't override
+ if not self._single_revision:
+ for line in super(BranchChange, self).generate_email_body(push):
+ yield line
+ return
+
+ # This is a combined refchange/revision email; we first provide
+ # some info from the refchange portion, and then call the revision
+ # generate_email_body function to handle the revision portion.
+ adds = list(generate_summaries(
+ '--topo-order', '--reverse', '%s..%s'
+ % (self.old.commit_sha1, self.new.commit_sha1,)
+ ))
+
+ yield self.expand("The following commit(s) were added to %(refname)s by this push:\n")
+ for (sha1, subject) in adds:
+ yield self.expand(
+ BRIEF_SUMMARY_TEMPLATE, action='new',
+ rev_short=sha1, text=subject,
+ )
+
+ yield self._single_revision.rev.short + " is described below\n"
+ yield '\n'
+
+ for line in self._single_revision.generate_email_body(push):
+ yield line
class AnnotatedTagChange(ReferenceChange):
sys.exit(1)
try:
p.stdin.writelines(lines)
- except:
+ except Exception, e:
sys.stderr.write(
'*** Error while generating commit email\n'
'*** - mail sending aborted.\n'
)
- p.terminate()
- raise
+ try:
+ # subprocess.terminate() is not available in Python 2.4
+ p.terminate()
+ except AttributeError:
+ pass
+ raise e
else:
p.stdin.close()
retcode = p.wait()
class SMTPMailer(Mailer):
"""Send emails using Python's smtplib."""
- def __init__(self, envelopesender, smtpserver):
+ def __init__(self, envelopesender, smtpserver,
+ smtpservertimeout=10.0, smtpserverdebuglevel=0,
+ smtpencryption='none',
+ smtpuser='', smtppass='',
+ ):
if not envelopesender:
sys.stderr.write(
'fatal: git_multimail: cannot use SMTPMailer without a sender address.\n'
'please set either multimailhook.envelopeSender or user.email\n'
)
sys.exit(1)
+ if smtpencryption == 'ssl' and not (smtpuser and smtppass):
+ raise ConfigurationException(
+ 'Cannot use SMTPMailer with security option ssl '
+ 'without options username and password.'
+ )
self.envelopesender = envelopesender
self.smtpserver = smtpserver
+ self.smtpservertimeout = smtpservertimeout
+ self.smtpserverdebuglevel = smtpserverdebuglevel
+ self.security = smtpencryption
+ self.username = smtpuser
+ self.password = smtppass
try:
- self.smtp = smtplib.SMTP(self.smtpserver)
+ def call(klass, server, timeout):
+ try:
+ return klass(server, timeout=timeout)
+ except TypeError:
+ # Old Python versions do not have timeout= argument.
+ return klass(server)
+ if self.security == 'none':
+ self.smtp = call(smtplib.SMTP, self.smtpserver, timeout=self.smtpservertimeout)
+ elif self.security == 'ssl':
+ self.smtp = call(smtplib.SMTP_SSL, self.smtpserver, timeout=self.smtpservertimeout)
+ elif self.security == 'tls':
+ if ':' not in self.smtpserver:
+ self.smtpserver += ':587' # default port for TLS
+ self.smtp = call(smtplib.SMTP, self.smtpserver, timeout=self.smtpservertimeout)
+ self.smtp.ehlo()
+ self.smtp.starttls()
+ self.smtp.ehlo()
+ else:
+ sys.stdout.write('*** Error: Control reached an invalid option. ***')
+ sys.exit(1)
+ if self.smtpserverdebuglevel > 0:
+ sys.stdout.write(
+ "*** Setting debug on for SMTP server connection (%s) ***\n"
+ % self.smtpserverdebuglevel)
+ self.smtp.set_debuglevel(self.smtpserverdebuglevel)
except Exception, e:
- sys.stderr.write('*** Error establishing SMTP connection to %s***\n' % self.smtpserver)
+ sys.stderr.write(
+ '*** Error establishing SMTP connection to %s ***\n'
+ % self.smtpserver)
sys.stderr.write('*** %s\n' % str(e))
sys.exit(1)
def __del__(self):
- self.smtp.quit()
+ if hasattr(self, 'smtp'):
+ self.smtp.quit()
def send(self, lines, to_addrs):
try:
+ if self.username or self.password:
+ sys.stderr.write("*** Authenticating as %s ***\n" % self.username)
+ self.smtp.login(self.username, self.password)
msg = ''.join(lines)
# turn comma-separated list into Python list if needed.
if isinstance(to_addrs, basestring):
to_addrs = [email for (name, email) in getaddresses([to_addrs])]
self.smtp.sendmail(self.envelopesender, to_addrs, msg)
except Exception, e:
- sys.stderr.write('*** Error sending email***\n')
+ sys.stderr.write('*** Error sending email ***\n')
sys.stderr.write('*** %s\n' % str(e))
self.smtp.quit()
sys.exit(1)
True iff announce emails should include a shortlog.
+ refchange_showgraph (bool)
+
+ True iff refchanges emails should include a detailed graph.
+
refchange_showlog (bool)
True iff refchanges emails should include a detailed log.
summary email. The value should be a list of strings
representing words to be passed to the command.
+ graphopts (list of strings)
+
+ Analogous to diffopts, but contains options passed to
+ 'git log --graph' when generating the detailed graph for
+ a set of commits (see refchange_showgraph)
+
logopts (list of strings)
Analogous to diffopts, but contains options passed to
commit mail. The value should be a list of strings
representing words to be passed to the command.
+ quiet (bool)
+ On success do not write to stderr
+
+ stdout (bool)
+ Write email to stdout rather than emailing. Useful for debugging
+
+ combine_when_single_commit (bool)
+
+ True if a combined email should be produced when a single
+ new commit is pushed to a branch, False otherwise.
+
"""
REPO_NAME_RE = re.compile(r'^(?P<name>.+?)(?:\.git)$')
self.announce_show_shortlog = False
self.maxcommitemails = 500
self.diffopts = ['--stat', '--summary', '--find-copies-harder']
+ self.graphopts = ['--oneline', '--decorate']
self.logopts = []
+ self.refchange_showgraph = False
self.refchange_showlog = False
self.commitlogopts = ['-C', '--stat', '-p', '--cc']
+ self.quiet = False
+ self.stdout = False
+ self.combine_when_single_commit = True
self.COMPUTED_KEYS = [
'administrator',
def get_pusher_email(self):
return None
+ def get_fromaddr(self):
+ config = Config('user')
+ fromname = config.get('name', default='')
+ fromemail = config.get('email', default='')
+ if fromemail:
+ return formataddr([fromname, fromemail])
+ return self.get_sender()
+
def get_administrator(self):
return 'the administrator of this repository'
return CHARSET
def get_values(self):
- """Return a dictionary {keyword : expansion} for this Environment.
+ """Return a dictionary {keyword: expansion} for this Environment.
This method is called by Change._compute_values(). The keys
in the returned dictionary are available to be used in any of
return lines
+ def log_msg(self, msg):
+ """Write the string msg on a log file or on stderr.
+
+ Sends the text to stderr by default, override to change the behavior."""
+ sys.stderr.write(msg)
+
+ def log_warning(self, msg):
+ """Write the string msg on a log file or on stderr.
+
+ Sends the text to stderr by default, override to change the behavior."""
+ sys.stderr.write(msg)
+
+ def log_error(self, msg):
+ """Write the string msg on a log file or on stderr.
+
+ Sends the text to stderr by default, override to change the behavior."""
+ sys.stderr.write(msg)
+
class ConfigEnvironmentMixin(Environment):
"""A mixin that sets self.config to its constructor's config argument.
config=config, **kw
)
- self.announce_show_shortlog = config.get_bool(
- 'announceshortlog', default=self.announce_show_shortlog
- )
-
- self.refchange_showlog = config.get_bool(
- 'refchangeshowlog', default=self.refchange_showlog
- )
+ for var, cfg in (
+ ('announce_show_shortlog', 'announceshortlog'),
+ ('refchange_showgraph', 'refchangeShowGraph'),
+ ('refchange_showlog', 'refchangeshowlog'),
+ ('quiet', 'quiet'),
+ ('stdout', 'stdout'),
+ ):
+ val = config.get_bool(cfg)
+ if val is not None:
+ setattr(self, var, val)
maxcommitemails = config.get('maxcommitemails')
if maxcommitemails is not None:
try:
self.maxcommitemails = int(maxcommitemails)
except ValueError:
- sys.stderr.write(
+ self.log_warning(
'*** Malformed value for multimailhook.maxCommitEmails: %s\n' % maxcommitemails
+ '*** Expected a number. Ignoring.\n'
)
if diffopts is not None:
self.diffopts = shlex.split(diffopts)
+ graphopts = config.get('graphOpts')
+ if graphopts is not None:
+ self.graphopts = shlex.split(graphopts)
+
logopts = config.get('logopts')
if logopts is not None:
self.logopts = shlex.split(logopts)
reply_to = config.get('replyTo')
self.__reply_to_refchange = config.get('replyToRefchange', default=reply_to)
if (
- self.__reply_to_refchange is not None
- and self.__reply_to_refchange.lower() == 'author'
- ):
+ self.__reply_to_refchange is not None
+ and self.__reply_to_refchange.lower() == 'author'
+ ):
raise ConfigurationException(
'"author" is not an allowed setting for replyToRefchange'
)
self.__reply_to_commit = config.get('replyToCommit', default=reply_to)
+ combine = config.get_bool('combineWhenSingleCommit')
+ if combine is not None:
+ self.combine_when_single_commit = combine
+
def get_administrator(self):
return (
self.config.get('administrator')
def get_emailprefix(self):
emailprefix = self.config.get('emailprefix')
- if emailprefix and emailprefix.strip():
- return emailprefix.strip() + ' '
+ if emailprefix is not None:
+ emailprefix = emailprefix.strip()
+ if emailprefix:
+ return emailprefix + ' '
+ else:
+ return ''
else:
return '[%s] ' % (self.get_repo_shortname(),)
fromaddr = self.config.get('from')
if fromaddr:
return fromaddr
- else:
- config = Config('user')
- fromname = config.get('name', default='')
- fromemail = config.get('email', default='')
- if fromemail:
- return formataddr([fromname, fromemail])
- else:
- return self.get_sender()
+ return super(ConfigOptionsEnvironmentMixin, self).get_fromaddr()
def get_reply_to_refchange(self, refchange):
if self.__reply_to_refchange is None:
if self.__reply_to_commit is None:
return super(ConfigOptionsEnvironmentMixin, self).get_reply_to_commit(revision)
elif self.__reply_to_commit.lower() == 'author':
- return revision.get_author()
+ return revision.author
elif self.__reply_to_commit.lower() == 'pusher':
return self.get_pusher_email()
elif self.__reply_to_commit.lower() == 'none':
else:
return self.__reply_to_commit
+ def get_scancommitforcc(self):
+ return self.config.get('scancommitforcc')
+
class FilterLinesEnvironmentMixin(Environment):
"""Handle encoding and maximum line length of body lines.
class ConfigFilterLinesEnvironmentMixin(
- ConfigEnvironmentMixin,
- FilterLinesEnvironmentMixin,
- ):
+ ConfigEnvironmentMixin,
+ FilterLinesEnvironmentMixin,
+ ):
"""Handle encoding and maximum line length based on config."""
def __init__(self, config, **kw):
class ConfigMaxlinesEnvironmentMixin(
- ConfigEnvironmentMixin,
- MaxlinesEnvironmentMixin,
- ):
+ ConfigEnvironmentMixin,
+ MaxlinesEnvironmentMixin,
+ ):
"""Limit the email body to the number of lines specified in config."""
def __init__(self, config, **kw):
class ConfigFQDNEnvironmentMixin(
- ConfigEnvironmentMixin,
- FQDNEnvironmentMixin,
- ):
+ ConfigEnvironmentMixin,
+ FQDNEnvironmentMixin,
+ ):
"""Read the FQDN from the config."""
def __init__(self, config, **kw):
"""Set recipients statically based on constructor parameters."""
def __init__(
- self,
- refchange_recipients, announce_recipients, revision_recipients,
- **kw
- ):
+ self,
+ refchange_recipients, announce_recipients, revision_recipients, scancommitforcc,
+ **kw
+ ):
super(StaticRecipientsEnvironmentMixin, self).__init__(**kw)
# The recipients for various types of notification emails, as
# compute them once and for all:
if not (refchange_recipients
or announce_recipients
- or revision_recipients):
+ or revision_recipients
+ or scancommitforcc):
raise ConfigurationException('No email recipients configured!')
self.__refchange_recipients = refchange_recipients
self.__announce_recipients = announce_recipients
class ConfigRecipientsEnvironmentMixin(
- ConfigEnvironmentMixin,
- StaticRecipientsEnvironmentMixin
- ):
+ ConfigEnvironmentMixin,
+ StaticRecipientsEnvironmentMixin
+ ):
"""Determine recipients statically based on config."""
def __init__(self, config, **kw):
revision_recipients=self._get_recipients(
config, 'commitlist', 'mailinglist',
),
+ scancommitforcc=config.get('scancommitforcc'),
**kw
)
class GenericEnvironmentMixin(Environment):
def get_pusher(self):
- return self.osenv.get('USER', 'unknown user')
+ return self.osenv.get('USER', self.osenv.get('USERNAME', 'unknown user'))
class GenericEnvironment(
- ProjectdescEnvironmentMixin,
- ConfigMaxlinesEnvironmentMixin,
- ComputeFQDNEnvironmentMixin,
- ConfigFilterLinesEnvironmentMixin,
- ConfigRecipientsEnvironmentMixin,
- PusherDomainEnvironmentMixin,
- ConfigOptionsEnvironmentMixin,
- GenericEnvironmentMixin,
- Environment,
- ):
+ ProjectdescEnvironmentMixin,
+ ConfigMaxlinesEnvironmentMixin,
+ ComputeFQDNEnvironmentMixin,
+ ConfigFilterLinesEnvironmentMixin,
+ ConfigRecipientsEnvironmentMixin,
+ PusherDomainEnvironmentMixin,
+ ConfigOptionsEnvironmentMixin,
+ GenericEnvironmentMixin,
+ Environment,
+ ):
pass
def get_pusher(self):
return self.osenv.get('GL_USER', 'unknown user')
+ def get_fromaddr(self):
+ GL_USER = self.osenv.get('GL_USER')
+ if GL_USER is not None:
+ # Find the path to gitolite.conf. Note that gitolite v3
+ # did away with the GL_ADMINDIR and GL_CONF environment
+ # variables (they are now hard-coded).
+ GL_ADMINDIR = self.osenv.get(
+ 'GL_ADMINDIR',
+ os.path.expanduser(os.path.join('~', '.gitolite')))
+ GL_CONF = self.osenv.get(
+ 'GL_CONF',
+ os.path.join(GL_ADMINDIR, 'conf', 'gitolite.conf'))
+ if os.path.isfile(GL_CONF):
+ f = open(GL_CONF, 'rU')
+ try:
+ in_user_emails_section = False
+ re_template = r'^\s*#\s*{}\s*$'
+ re_begin, re_user, re_end = (
+ re.compile(re_template.format(x))
+ for x in (
+ r'BEGIN\s+USER\s+EMAILS',
+ re.escape(GL_USER) + r'\s+(.*)',
+ r'END\s+USER\s+EMAILS',
+ ))
+ for l in f:
+ l = l.rstrip('\n')
+ if not in_user_emails_section:
+ if re_begin.match(l):
+ in_user_emails_section = True
+ continue
+ if re_end.match(l):
+ break
+ m = re_user.match(l)
+ if m:
+ return m.group(1)
+ finally:
+ f.close()
+ return super(GitoliteEnvironmentMixin, self).get_fromaddr()
+
class IncrementalDateTime(object):
"""Simple wrapper to give incremental date/times.
class GitoliteEnvironment(
- ProjectdescEnvironmentMixin,
- ConfigMaxlinesEnvironmentMixin,
- ComputeFQDNEnvironmentMixin,
- ConfigFilterLinesEnvironmentMixin,
- ConfigRecipientsEnvironmentMixin,
- PusherDomainEnvironmentMixin,
- ConfigOptionsEnvironmentMixin,
- GitoliteEnvironmentMixin,
- Environment,
- ):
+ ProjectdescEnvironmentMixin,
+ ConfigMaxlinesEnvironmentMixin,
+ ComputeFQDNEnvironmentMixin,
+ ConfigFilterLinesEnvironmentMixin,
+ ConfigRecipientsEnvironmentMixin,
+ PusherDomainEnvironmentMixin,
+ ConfigOptionsEnvironmentMixin,
+ GitoliteEnvironmentMixin,
+ Environment,
+ ):
pass
references.
The first step is to determine the "other" references--those
- unaffected by the current push. They are computed by
- Push._compute_other_ref_sha1s() by listing all references then
- removing any affected by this push.
+ unaffected by the current push. They are computed by listing all
+ references then removing any affected by this push. The results
+ are stored in Push._other_ref_sha1s.
The commits contained in the repository before this push were
possible and working with SHA1s thereafter (because SHA1s are
immutable)."""
- # A map {(changeclass, changetype) : integer} specifying the order
+ # A map {(changeclass, changetype): integer} specifying the order
# that reference changes will be processed if multiple reference
# changes are included in a single push. The order is significant
# mostly because new commit notifications are threaded together
])
)
- def __init__(self, changes):
+ def __init__(self, changes, ignore_other_refs=False):
self.changes = sorted(changes, key=self._sort_key)
+ self.__other_ref_sha1s = None
+ self.__cached_commits_spec = {}
- # The SHA-1s of commits referred to by references unaffected
- # by this push:
- other_ref_sha1s = self._compute_other_ref_sha1s()
+ if ignore_other_refs:
+ self.__other_ref_sha1s = set()
- self._old_rev_exclusion_spec = self._compute_rev_exclusion_spec(
- other_ref_sha1s.union(
- change.old.sha1
+ @classmethod
+ def _sort_key(klass, change):
+ return (klass.SORT_ORDER[change.__class__, change.change_type], change.refname,)
+
+ @property
+ def _other_ref_sha1s(self):
+ """The GitObjects referred to by references unaffected by this push.
+ """
+ if self.__other_ref_sha1s is None:
+ # The refnames being changed by this push:
+ updated_refs = set(
+ change.refname
for change in self.changes
- if change.old.type in ['commit', 'tag']
)
- )
- self._new_rev_exclusion_spec = self._compute_rev_exclusion_spec(
- other_ref_sha1s.union(
- change.new.sha1
- for change in self.changes
- if change.new.type in ['commit', 'tag']
+
+ # The SHA-1s of commits referred to by all references in this
+ # repository *except* updated_refs:
+ sha1s = set()
+ fmt = (
+ '%(objectname) %(objecttype) %(refname)\n'
+ '%(*objectname) %(*objecttype) %(refname)'
)
- )
+ for line in read_git_lines(
+ ['for-each-ref', '--format=%s' % (fmt,)]):
+ (sha1, type, name) = line.split(' ', 2)
+ if sha1 and type == 'commit' and name not in updated_refs:
+ sha1s.add(sha1)
- @classmethod
- def _sort_key(klass, change):
- return (klass.SORT_ORDER[change.__class__, change.change_type], change.refname,)
+ self.__other_ref_sha1s = sha1s
+
+ return self.__other_ref_sha1s
+
+ def _get_commits_spec_incl(self, new_or_old, reference_change=None):
+ """Get new or old SHA-1 from one or each of the changed refs.
- def _compute_other_ref_sha1s(self):
- """Return the GitObjects referred to by references unaffected by this push."""
+ Return a list of SHA-1 commit identifier strings suitable as
+ arguments to 'git rev-list' (or 'git log' or ...). The
+ returned identifiers are either the old or new values from one
+ or all of the changed references, depending on the values of
+ new_or_old and reference_change.
- # The refnames being changed by this push:
- updated_refs = set(
- change.refname
+ new_or_old is either the string 'new' or the string 'old'. If
+ 'new', the returned SHA-1 identifiers are the new values from
+ each changed reference. If 'old', the SHA-1 identifiers are
+ the old values from each changed reference.
+
+ If reference_change is specified and not None, only the new or
+ old reference from the specified reference is included in the
+ return value.
+
+ This function returns None if there are no matching revisions
+ (e.g., because a branch was deleted and new_or_old is 'new').
+ """
+
+ if not reference_change:
+ incl_spec = sorted(
+ getattr(change, new_or_old).sha1
+ for change in self.changes
+ if getattr(change, new_or_old)
+ )
+ if not incl_spec:
+ incl_spec = None
+ elif not getattr(reference_change, new_or_old).commit_sha1:
+ incl_spec = None
+ else:
+ incl_spec = [getattr(reference_change, new_or_old).commit_sha1]
+ return incl_spec
+
+ def _get_commits_spec_excl(self, new_or_old):
+ """Get exclusion revisions for determining new or discarded commits.
+
+ Return a list of strings suitable as arguments to 'git
+ rev-list' (or 'git log' or ...) that will exclude all
+ commits that, depending on the value of new_or_old, were
+ either previously in the repository (useful for determining
+ which commits are new to the repository) or currently in the
+ repository (useful for determining which commits were
+ discarded from the repository).
+
+ new_or_old is either the string 'new' or the string 'old'. If
+ 'new', the commits to be excluded are those that were in the
+ repository before the push. If 'old', the commits to be
+ excluded are those that are currently in the repository. """
+
+ old_or_new = {'old': 'new', 'new': 'old'}[new_or_old]
+ excl_revs = self._other_ref_sha1s.union(
+ getattr(change, old_or_new).sha1
for change in self.changes
+ if getattr(change, old_or_new).type in ['commit', 'tag']
)
+ return ['^' + sha1 for sha1 in sorted(excl_revs)]
- # The SHA-1s of commits referred to by all references in this
- # repository *except* updated_refs:
- sha1s = set()
- fmt = (
- '%(objectname) %(objecttype) %(refname)\n'
- '%(*objectname) %(*objecttype) %(refname)'
- )
- for line in read_git_lines(['for-each-ref', '--format=%s' % (fmt,)]):
- (sha1, type, name) = line.split(' ', 2)
- if sha1 and type == 'commit' and name not in updated_refs:
- sha1s.add(sha1)
+ def get_commits_spec(self, new_or_old, reference_change=None):
+ """Get rev-list arguments for added or discarded commits.
- return sha1s
+ Return a list of strings suitable as arguments to 'git
+ rev-list' (or 'git log' or ...) that select those commits
+ that, depending on the value of new_or_old, are either new to
+ the repository or were discarded from the repository.
- def _compute_rev_exclusion_spec(self, sha1s):
- """Return an exclusion specification for 'git rev-list'.
+ new_or_old is either the string 'new' or the string 'old'. If
+ 'new', the returned list is used to select commits that are
+ new to the repository. If 'old', the returned value is used
+ to select the commits that have been discarded from the
+ repository.
- git_objects is an iterable over GitObject instances. Return a
- string that can be passed to the standard input of 'git
- rev-list --stdin' to exclude all of the commits referred to by
- git_objects."""
+ If reference_change is specified and not None, the new or
+ discarded commits are limited to those that are reachable from
+ the new or old value of the specified reference.
- return ''.join(
- ['^%s\n' % (sha1,) for sha1 in sorted(sha1s)]
- )
+ This function returns None if there are no added (or discarded)
+ revisions.
+ """
+ key = (new_or_old, reference_change)
+ if key not in self.__cached_commits_spec:
+ ret = self._get_commits_spec_incl(new_or_old, reference_change)
+ if ret is not None:
+ ret.extend(self._get_commits_spec_excl(new_or_old))
+ self.__cached_commits_spec[key] = ret
+ return self.__cached_commits_spec[key]
def get_new_commits(self, reference_change=None):
"""Return a list of commits added by this push.
reference_change is None, then return a list of *all* commits
added by this push."""
- if not reference_change:
- new_revs = sorted(
- change.new.sha1
- for change in self.changes
- if change.new
- )
- elif not reference_change.new.commit_sha1:
- return []
- else:
- new_revs = [reference_change.new.commit_sha1]
-
- cmd = ['rev-list', '--stdin'] + new_revs
- return read_git_lines(cmd, input=self._old_rev_exclusion_spec)
+ spec = self.get_commits_spec('new', reference_change)
+ return git_rev_list(spec)
def get_discarded_commits(self, reference_change):
"""Return a list of commits discarded by this push.
entirely discarded from the repository by the part of this
push represented by reference_change."""
- if not reference_change.old.commit_sha1:
- return []
- else:
- old_revs = [reference_change.old.commit_sha1]
-
- cmd = ['rev-list', '--stdin'] + old_revs
- return read_git_lines(cmd, input=self._new_rev_exclusion_spec)
+ spec = self.get_commits_spec('old', reference_change)
+ return git_rev_list(spec)
def send_emails(self, mailer, body_filter=None):
"""Use send all of the notification emails needed for this push.
unhandled_sha1s = set(self.get_new_commits())
send_date = IncrementalDateTime()
for change in self.changes:
+ sha1s = []
+ for sha1 in reversed(list(self.get_new_commits(change))):
+ if sha1 in unhandled_sha1s:
+ sha1s.append(sha1)
+ unhandled_sha1s.remove(sha1)
+
# Check if we've got anyone to send to
if not change.recipients:
- sys.stderr.write(
+ change.environment.log_warning(
'*** no recipients configured so no email will be sent\n'
'*** for %r update %s->%s\n'
% (change.refname, change.old.sha1, change.new.sha1,)
)
else:
- sys.stderr.write('Sending notification emails to: %s\n' % (change.recipients,))
- extra_values = {'send_date' : send_date.next()}
- mailer.send(
- change.generate_email(self, body_filter, extra_values),
- change.recipients,
- )
+ if not change.environment.quiet:
+ change.environment.log_msg(
+ 'Sending notification emails to: %s\n' % (change.recipients,))
+ extra_values = {'send_date': send_date.next()}
- sha1s = []
- for sha1 in reversed(list(self.get_new_commits(change))):
- if sha1 in unhandled_sha1s:
- sha1s.append(sha1)
- unhandled_sha1s.remove(sha1)
+ rev = change.send_single_combined_email(sha1s)
+ if rev:
+ mailer.send(
+ change.generate_combined_email(self, rev, body_filter, extra_values),
+ rev.recipients,
+ )
+ # This change is now fully handled; no need to handle
+ # individual revisions any further.
+ continue
+ else:
+ mailer.send(
+ change.generate_email(self, body_filter, extra_values),
+ change.recipients,
+ )
max_emails = change.environment.maxcommitemails
if max_emails and len(sha1s) > max_emails:
- sys.stderr.write(
+ change.environment.log_warning(
'*** Too many new commits (%d), not sending commit emails.\n' % len(sha1s)
+ '*** Try setting multimailhook.maxCommitEmails to a greater value\n'
+ '*** Currently, multimailhook.maxCommitEmails=%d\n' % max_emails
return
for (num, sha1) in enumerate(sha1s):
- rev = Revision(change, GitObject(sha1), num=num+1, tot=len(sha1s))
+ rev = Revision(change, GitObject(sha1), num=num + 1, tot=len(sha1s))
+ if not rev.recipients and rev.cc_recipients:
+ change.environment.log_msg('*** Replacing Cc: with To:\n')
+ rev.recipients = rev.cc_recipients
+ rev.cc_recipients = None
if rev.recipients:
- extra_values = {'send_date' : send_date.next()}
+ extra_values = {'send_date': send_date.next()}
mailer.send(
rev.generate_email(self, body_filter, extra_values),
rev.recipients,
# Consistency check:
if unhandled_sha1s:
- sys.stderr.write(
+ change.environment.log_error(
'ERROR: No emails were sent for the following new commits:\n'
' %s\n'
% ('\n '.join(sorted(unhandled_sha1s)),)
push.send_emails(mailer, body_filter=environment.filter_body)
-def run_as_update_hook(environment, mailer, refname, oldrev, newrev):
+def run_as_update_hook(environment, mailer, refname, oldrev, newrev, force_send=False):
changes = [
ReferenceChange.create(
environment,
refname,
),
]
- push = Push(changes)
+ push = Push(changes, force_send)
push.send_emails(mailer, body_filter=environment.filter_body)
if mailer == 'smtp':
smtpserver = config.get('smtpserver', default='localhost')
+ smtpservertimeout = float(config.get('smtpservertimeout', default=10.0))
+ smtpserverdebuglevel = int(config.get('smtpserverdebuglevel', default=0))
+ smtpencryption = config.get('smtpencryption', default='none')
+ smtpuser = config.get('smtpuser', default='')
+ smtppass = config.get('smtppass', default='')
mailer = SMTPMailer(
envelopesender=(environment.get_sender() or environment.get_fromaddr()),
- smtpserver=smtpserver,
+ smtpserver=smtpserver, smtpservertimeout=smtpservertimeout,
+ smtpserverdebuglevel=smtpserverdebuglevel,
+ smtpencryption=smtpencryption,
+ smtpuser=smtpuser,
+ smtppass=smtppass,
)
elif mailer == 'sendmail':
command = config.get('sendmailcommand')
command = shlex.split(command)
mailer = SendMailer(command=command, envelopesender=environment.get_sender())
else:
- sys.stderr.write(
+ environment.log_error(
'fatal: multimailhook.mailer is set to an incorrect value: "%s"\n' % mailer
+ 'please use one of "smtp" or "sendmail".\n'
)
KNOWN_ENVIRONMENTS = {
- 'generic' : GenericEnvironmentMixin,
- 'gitolite' : GitoliteEnvironmentMixin,
+ 'generic': GenericEnvironmentMixin,
+ 'gitolite': GitoliteEnvironmentMixin,
}
ConfigOptionsEnvironmentMixin,
]
environment_kw = {
- 'osenv' : osenv,
- 'config' : config,
+ 'osenv': osenv,
+ 'config': config,
}
if not env:
environment_kw['refchange_recipients'] = recipients
environment_kw['announce_recipients'] = recipients
environment_kw['revision_recipients'] = recipients
+ environment_kw['scancommitforcc'] = config.get('scancommitforcc')
else:
environment_mixins.insert(0, ConfigRecipientsEnvironmentMixin)
'(intended for debugging purposes).'
),
)
+ parser.add_option(
+ '--force-send', action='store_true', default=False,
+ help=(
+ 'Force sending refchange email when using as an update hook. '
+ 'This is useful to work around the unreliable new commits '
+ 'detection in this mode.'
+ ),
+ )
(options, args) = parser.parse_args(args)
if options.show_env:
sys.stderr.write('Environment values:\n')
- for (k,v) in sorted(environment.get_values().items()):
- sys.stderr.write(' %s : %r\n' % (k,v))
+ for (k, v) in sorted(environment.get_values().items()):
+ sys.stderr.write(' %s : %r\n' % (k, v))
sys.stderr.write('\n')
- if options.stdout:
+ if options.stdout or environment.stdout:
mailer = OutputMailer(sys.stdout)
else:
mailer = choose_mailer(config, environment)
if len(args) != 3:
parser.error('Need zero or three non-option arguments')
(refname, oldrev, newrev) = args
- run_as_update_hook(environment, mailer, refname, oldrev, newrev)
+ run_as_update_hook(environment, mailer, refname, oldrev, newrev, options.force_send)
else:
run_as_post_receive_hook(environment, mailer)
except ConfigurationException, e:
'showrev',
'emailmaxlines',
'diffopts',
+ 'scancommitforcc',
]
NEW_NAMES = [
'emailmaxlines',
'diffopts',
'emaildomain',
+ 'scancommitforcc',
]
"""Check that at least one old configuration value is set."""
for name in OLD_NAMES:
- if old.has_key(name):
+ if name in old:
return True
return False
retval = True
for name in NEW_NAMES:
- if new.has_key(name):
+ if name in new:
if retval:
sys.stderr.write('INFO: The following configuration values already exist:\n\n')
sys.stderr.write(' "%s.%s"\n' % (new.section, name))
def erase_values(config, names):
for name in names:
- if config.has_key(name):
+ if name in config:
try:
sys.stderr.write('...unsetting "%s.%s"\n' % (config.section, name))
config.unset_all(name)
)
name = 'showrev'
- if old.has_key(name):
+ if name in old:
msg = 'git-multimail does not support "%s.%s"' % (old.section, name,)
if strict:
sys.exit(
sys.stderr.write('\nWARNING: %s (ignoring).\n\n' % (msg,))
for name in ['mailinglist', 'announcelist']:
- if old.has_key(name):
+ if name in old:
sys.stderr.write(
'...copying "%s.%s" to "%s.%s"\n' % (old.section, name, new.section, name)
)
)
new.set('announceshortlog', 'true')
- for name in ['envelopesender', 'emailmaxlines', 'diffopts']:
- if old.has_key(name):
+ for name in ['envelopesender', 'emailmaxlines', 'diffopts', 'scancommitforcc']:
+ if name in old:
sys.stderr.write(
'...copying "%s.%s" to "%s.%s"\n' % (old.section, name, new.section, name)
)
new.set(name, old.get(name))
name = 'emailprefix'
- if old.has_key(name):
+ if name in old:
sys.stderr.write(
'...copying "%s.%s" to "%s.%s"\n' % (old.section, name, new.section, name)
)
+++ /dev/null
-#! /usr/bin/env python2
-
-"""Example post-receive hook based on git-multimail.
-
-This script is a simple example of a post-receive hook implemented
-using git_multimail.py as a Python module. It is intended to be
-customized before use; see the comments in the script to help you get
-started.
-
-It is possible to use git_multimail.py itself as a post-receive or
-update hook, configured via git config settings and/or command-line
-parameters. But for more flexibility, it can also be imported as a
-Python module by a custom post-receive script as done here. The
-latter has the following advantages:
-
-* The tool's behavior can be customized using arbitrary Python code,
- without having to edit git_multimail.py.
-
-* Configuration settings can be read from other sources; for example,
- user names and email addresses could be read from LDAP or from a
- database. Or the settings can even be hardcoded in the importing
- Python script, if this is preferred.
-
-This script is a very basic example of how to use git_multimail.py as
-a module. The comments below explain some of the points at which the
-script's behavior could be changed or customized.
-
-"""
-
-import sys
-import os
-
-# If necessary, add the path to the directory containing
-# git_multimail.py to the Python path as follows. (This is not
-# necessary if git_multimail.py is in the same directory as this
-# script):
-
-#LIBDIR = 'path/to/directory/containing/module'
-#sys.path.insert(0, LIBDIR)
-
-import git_multimail
-
-
-# It is possible to modify the output templates here; e.g.:
-
-#git_multimail.FOOTER_TEMPLATE = """\
-#
-#-- \n\
-#This email was generated by the wonderful git-multimail tool.
-#"""
-
-
-# Specify which "git config" section contains the configuration for
-# git-multimail:
-config = git_multimail.Config('multimailhook')
-
-
-# Select the type of environment:
-environment = git_multimail.GenericEnvironment(config=config)
-#environment = git_multimail.GitoliteEnvironment(config=config)
-
-
-# Choose the method of sending emails based on the git config:
-mailer = git_multimail.choose_mailer(config, environment)
-
-# Alternatively, you may hardcode the mailer using code like one of
-# the following:
-
-# Use "/usr/sbin/sendmail -oi -t" to send emails. The envelopesender
-# argument is optional:
-#mailer = git_multimail.SendMailer(
-# command=['/usr/sbin/sendmail', '-oi', '-t'],
-# envelopesender='git-repo@example.com',
-# )
-
-# Use Python's smtplib to send emails. Both arguments are required.
-#mailer = git_multimail.SMTPMailer(
-# envelopesender='git-repo@example.com',
-# # The smtpserver argument can also include a port number; e.g.,
-# # smtpserver='mail.example.com:25'
-# smtpserver='mail.example.com',
-# )
-
-# OutputMailer is intended only for testing; it writes the emails to
-# the specified file stream.
-#mailer = git_multimail.OutputMailer(sys.stdout)
-
-
-# Read changes from stdin and send notification emails:
-git_multimail.run_as_post_receive_hook(environment, mailer)
--- /dev/null
+#! /usr/bin/env python2
+
+"""Example post-receive hook based on git-multimail.
+
+The simplest way to use git-multimail is to use the script
+git_multimail.py directly as a post-receive hook, and to configure it
+using Git's configuration files and command-line parameters. You can
+also write your own Python wrapper for more advanced configurability,
+using git_multimail.py as a Python module.
+
+This script is a simple example of such a post-receive hook. It is
+intended to be customized before use; see the comments in the script
+to help you get started.
+
+Using git-multimail as a Python module as done here provides more
+flexibility. It has the following advantages:
+
+* The tool's behavior can be customized using arbitrary Python code,
+ without having to edit git_multimail.py.
+
+* Configuration settings can be read from other sources; for example,
+ user names and email addresses could be read from LDAP or from a
+ database. Or the settings can even be hardcoded in the importing
+ Python script, if this is preferred.
+
+This script is a very basic example of how to use git_multimail.py as
+a module. The comments below explain some of the points at which the
+script's behavior could be changed or customized.
+
+"""
+
+import sys
+import os
+
+# If necessary, add the path to the directory containing
+# git_multimail.py to the Python path as follows. (This is not
+# necessary if git_multimail.py is in the same directory as this
+# script):
+
+#LIBDIR = 'path/to/directory/containing/module'
+#sys.path.insert(0, LIBDIR)
+
+import git_multimail
+
+
+# It is possible to modify the output templates here; e.g.:
+
+#git_multimail.FOOTER_TEMPLATE = """\
+#
+#-- \n\
+#This email was generated by the wonderful git-multimail tool.
+#"""
+
+
+# Specify which "git config" section contains the configuration for
+# git-multimail:
+config = git_multimail.Config('multimailhook')
+
+
+# Select the type of environment:
+try:
+ environment = git_multimail.GenericEnvironment(config=config)
+ #environment = git_multimail.GitoliteEnvironment(config=config)
+except git_multimail.ConfigurationException, e:
+ sys.exit(str(e))
+
+
+# Choose the method of sending emails based on the git config:
+mailer = git_multimail.choose_mailer(config, environment)
+
+# Alternatively, you may hardcode the mailer using code like one of
+# the following:
+
+# Use "/usr/sbin/sendmail -oi -t" to send emails. The envelopesender
+# argument is optional:
+#mailer = git_multimail.SendMailer(
+# command=['/usr/sbin/sendmail', '-oi', '-t'],
+# envelopesender='git-repo@example.com',
+# )
+
+# Use Python's smtplib to send emails. Both arguments are required.
+#mailer = git_multimail.SMTPMailer(
+# envelopesender='git-repo@example.com',
+# # The smtpserver argument can also include a port number; e.g.,
+# # smtpserver='mail.example.com:25'
+# smtpserver='mail.example.com',
+# )
+
+# OutputMailer is intended only for testing; it writes the emails to
+# the specified file stream.
+#mailer = git_multimail.OutputMailer(sys.stdout)
+
+
+# Read changes from stdin and send notification emails:
+git_multimail.run_as_post_receive_hook(environment, mailer)
then
exit 0
elif test -x /usr/bin/pmset && /usr/bin/pmset -g batt |
- grep -q "Currently drawing from 'AC Power'"
+ grep -q "drawing from 'AC Power'"
then
exit 0
fi
ignore-joins ignore prior --rejoin commits
onto= try connecting new tree to an existing one
rejoin merge the new branch back into HEAD
- options for 'add', 'merge', 'pull' and 'push'
+ options for 'add', 'merge', and 'pull'
squash merge subtree changes as a single commit
"
eval "$(echo "$OPTS_SPEC" | git rev-parse --parseopt -- "$@" || echo exit $?)"
debug()
{
if [ -n "$debug" ]; then
- echo "$@" >&2
+ printf "%s\n" "$*" >&2
fi
}
say()
{
if [ -z "$quiet" ]; then
- echo "$@" >&2
+ printf "%s\n" "$*" >&2
+ fi
+}
+
+progress()
+{
+ if [ -z "$quiet" ]; then
+ printf "%s\r" "$*" >&2
fi
}
eval "$grl" |
while read rev parents; do
revcount=$(($revcount + 1))
- say -n "$revcount/$revmax ($createcount)\r"
+ progress "$revcount/$revmax ($createcount)"
debug "Processing commit: $rev"
exists=$(cache_get $rev)
if [ -n "$exists" ]; then
OPTIONS FOR add, merge, push, pull
----------------------------------
--squash::
- This option is only valid for add, merge, push and pull
+ This option is only valid for add, merge, and pull
commands.
+
Instead of merging the entire history from the subtree project, produce
sigchain_push(SIGPIPE, SIG_IGN);
if (params->src) {
- write_err = (write_in_full(child_process.in, params->src, params->size) < 0);
+ write_err = (write_in_full(child_process.in,
+ params->src, params->size) < 0);
+ if (errno == EPIPE)
+ write_err = 0;
} else {
write_err = copy_fd(params->fd, child_process.in);
+ if (write_err == COPY_WRITE_ERROR && errno == EPIPE)
+ write_err = 0;
}
if (close(child_process.in))
ssize_t len = xread(ifd, buffer, sizeof(buffer));
if (!len)
break;
- if (len < 0) {
- return error("copy-fd: read returned %s",
- strerror(errno));
- }
+ if (len < 0)
+ return COPY_READ_ERROR;
if (write_in_full(ofd, buffer, len) < 0)
- return error("copy-fd: write returned %s",
- strerror(errno));
+ return COPY_WRITE_ERROR;
}
return 0;
}
return fdo;
}
status = copy_fd(fdi, fdo);
+ switch (status) {
+ case COPY_READ_ERROR:
+ error("copy-fd: read returned %s", strerror(errno));
+ break;
+ case COPY_WRITE_ERROR:
+ error("copy-fd: write returned %s", strerror(errno));
+ break;
+ }
close(fdi);
if (close(fdo) != 0)
return error("%s: close error: %s", dst, strerror(errno));
}
#endif
-static void store_pid(const char *path)
-{
- FILE *f = fopen(path, "w");
- if (!f)
- die_errno("cannot open pid file '%s'", path);
- if (fprintf(f, "%"PRIuMAX"\n", (uintmax_t) getpid()) < 0 || fclose(f) != 0)
- die_errno("failed to write pid file '%s'", path);
-}
-
static int serve(struct string_list *listen_addr, int listen_port,
struct credentials *cred)
{
sanitize_stdfds();
if (pid_file)
- store_pid(pid_file);
+ write_file(pid_file, 1, "%"PRIuMAX"\n", (uintmax_t) getpid());
/* prepare argv for serving-processes */
cld_argv = xmalloc(sizeof (char *) * (argc + 2));
dpath->next = NULL;
memcpy(dpath->path, ce->name, path_len);
dpath->path[path_len] = '\0';
- hashclr(dpath->sha1);
+ oidclr(&dpath->oid);
memset(&(dpath->parent[0]), 0,
sizeof(struct combine_diff_parent)*5);
if (2 <= stage) {
int mode = nce->ce_mode;
num_compare_stages++;
- hashcpy(dpath->parent[stage-2].sha1, nce->sha1);
+ hashcpy(dpath->parent[stage-2].oid.hash, nce->sha1);
dpath->parent[stage-2].mode = ce_mode_from_stat(nce, mode);
dpath->parent[stage-2].status =
DIFF_STATUS_MODIFIED;
memcpy(p->path, new->name, pathlen);
p->path[pathlen] = 0;
p->mode = mode;
- hashclr(p->sha1);
+ oidclr(&p->oid);
memset(p->parent, 0, 2 * sizeof(struct combine_diff_parent));
p->parent[0].status = DIFF_STATUS_MODIFIED;
p->parent[0].mode = new->ce_mode;
- hashcpy(p->parent[0].sha1, new->sha1);
+ hashcpy(p->parent[0].oid.hash, new->sha1);
p->parent[1].status = DIFF_STATUS_MODIFIED;
p->parent[1].mode = old->ce_mode;
- hashcpy(p->parent[1].sha1, old->sha1);
+ hashcpy(p->parent[1].oid.hash, old->sha1);
show_combined_diff(p, 2, revs->dense_combined_merges, revs);
free(p);
return 0;
static char diff_colors[][COLOR_MAXLEN] = {
GIT_COLOR_RESET,
- GIT_COLOR_NORMAL, /* PLAIN */
+ GIT_COLOR_NORMAL, /* CONTEXT */
GIT_COLOR_BOLD, /* METAINFO */
GIT_COLOR_CYAN, /* FRAGINFO */
GIT_COLOR_RED, /* OLD */
static int parse_diff_color_slot(const char *var)
{
- if (!strcasecmp(var, "plain"))
- return DIFF_PLAIN;
+ if (!strcasecmp(var, "context") || !strcasecmp(var, "plain"))
+ return DIFF_CONTEXT;
if (!strcasecmp(var, "meta"))
return DIFF_METAINFO;
if (!strcasecmp(var, "frag"))
return ws_blank_line(line, len, ecbdata->ws_rule);
}
-static void emit_add_line(const char *reset,
- struct emit_callback *ecbdata,
- const char *line, int len)
+static void emit_line_checked(const char *reset,
+ struct emit_callback *ecbdata,
+ const char *line, int len,
+ enum color_diff color,
+ unsigned ws_error_highlight,
+ char sign)
{
- const char *ws = diff_get_color(ecbdata->color_diff, DIFF_WHITESPACE);
- const char *set = diff_get_color(ecbdata->color_diff, DIFF_FILE_NEW);
+ const char *set = diff_get_color(ecbdata->color_diff, color);
+ const char *ws = NULL;
- if (!*ws)
- emit_line_0(ecbdata->opt, set, reset, '+', line, len);
- else if (new_blank_line_at_eof(ecbdata, line, len))
+ if (ecbdata->opt->ws_error_highlight & ws_error_highlight) {
+ ws = diff_get_color(ecbdata->color_diff, DIFF_WHITESPACE);
+ if (!*ws)
+ ws = NULL;
+ }
+
+ if (!ws)
+ emit_line_0(ecbdata->opt, set, reset, sign, line, len);
+ else if (sign == '+' && new_blank_line_at_eof(ecbdata, line, len))
/* Blank line at EOF - paint '+' as well */
- emit_line_0(ecbdata->opt, ws, reset, '+', line, len);
+ emit_line_0(ecbdata->opt, ws, reset, sign, line, len);
else {
/* Emit just the prefix, then the rest. */
- emit_line_0(ecbdata->opt, set, reset, '+', "", 0);
+ emit_line_0(ecbdata->opt, set, reset, sign, "", 0);
ws_check_emit(line, len, ecbdata->ws_rule,
ecbdata->opt->file, set, reset, ws);
}
}
+static void emit_add_line(const char *reset,
+ struct emit_callback *ecbdata,
+ const char *line, int len)
+{
+ emit_line_checked(reset, ecbdata, line, len,
+ DIFF_FILE_NEW, WSEH_NEW, '+');
+}
+
+static void emit_del_line(const char *reset,
+ struct emit_callback *ecbdata,
+ const char *line, int len)
+{
+ emit_line_checked(reset, ecbdata, line, len,
+ DIFF_FILE_OLD, WSEH_OLD, '-');
+}
+
+static void emit_context_line(const char *reset,
+ struct emit_callback *ecbdata,
+ const char *line, int len)
+{
+ emit_line_checked(reset, ecbdata, line, len,
+ DIFF_CONTEXT, WSEH_CONTEXT, ' ');
+}
+
static void emit_hunk_header(struct emit_callback *ecbdata,
const char *line, int len)
{
- const char *plain = diff_get_color(ecbdata->color_diff, DIFF_PLAIN);
+ const char *context = diff_get_color(ecbdata->color_diff, DIFF_CONTEXT);
const char *frag = diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO);
const char *func = diff_get_color(ecbdata->color_diff, DIFF_FUNCINFO);
const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET);
if (len < 10 ||
memcmp(line, atat, 2) ||
!(ep = memmem(line + 2, len - 2, atat, 2))) {
- emit_line(ecbdata->opt, plain, reset, line, len);
+ emit_line(ecbdata->opt, context, reset, line, len);
return;
}
ep += 2; /* skip over @@ */
if (*ep != ' ' && *ep != '\t')
break;
if (ep != cp) {
- strbuf_addstr(&msgbuf, plain);
+ strbuf_addstr(&msgbuf, context);
strbuf_add(&msgbuf, cp, ep - cp);
strbuf_addstr(&msgbuf, reset);
}
{
const char *endp = NULL;
static const char *nneof = " No newline at end of file\n";
- const char *old = diff_get_color(ecb->color_diff, DIFF_FILE_OLD);
const char *reset = diff_get_color(ecb->color_diff, DIFF_RESET);
while (0 < size) {
len = endp ? (endp - data + 1) : size;
if (prefix != '+') {
ecb->lno_in_preimage++;
- emit_line_0(ecb->opt, old, reset, '-',
- data, len);
+ emit_del_line(reset, ecb, data, len);
} else {
ecb->lno_in_postimage++;
emit_add_line(reset, ecb, data, len);
data += len;
}
if (!endp) {
- const char *plain = diff_get_color(ecb->color_diff,
- DIFF_PLAIN);
+ const char *context = diff_get_color(ecb->color_diff,
+ DIFF_CONTEXT);
putc('\n', ecb->opt->file);
- emit_line_0(ecb->opt, plain, reset, '\\',
+ emit_line_0(ecb->opt, context, reset, '\\',
nneof, strlen(nneof));
}
}
struct diff_words_style *st = ecbdata->diff_words->style;
st->old.color = diff_get_color_opt(o, DIFF_FILE_OLD);
st->new.color = diff_get_color_opt(o, DIFF_FILE_NEW);
- st->ctx.color = diff_get_color_opt(o, DIFF_PLAIN);
+ st->ctx.color = diff_get_color_opt(o, DIFF_CONTEXT);
}
}
{
struct emit_callback *ecbdata = priv;
const char *meta = diff_get_color(ecbdata->color_diff, DIFF_METAINFO);
- const char *plain = diff_get_color(ecbdata->color_diff, DIFF_PLAIN);
+ const char *context = diff_get_color(ecbdata->color_diff, DIFF_CONTEXT);
const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET);
struct diff_options *o = ecbdata->opt;
const char *line_prefix = diff_line_prefix(o);
}
diff_words_flush(ecbdata);
if (ecbdata->diff_words->type == DIFF_WORDS_PORCELAIN) {
- emit_line(ecbdata->opt, plain, reset, line, len);
+ emit_line(ecbdata->opt, context, reset, line, len);
fputs("~\n", ecbdata->opt->file);
} else {
/*
line++;
len--;
}
- emit_line(ecbdata->opt, plain, reset, line, len);
+ emit_line(ecbdata->opt, context, reset, line, len);
}
return;
}
- if (line[0] != '+') {
- const char *color =
- diff_get_color(ecbdata->color_diff,
- line[0] == '-' ? DIFF_FILE_OLD : DIFF_PLAIN);
- ecbdata->lno_in_preimage++;
- if (line[0] == ' ')
- ecbdata->lno_in_postimage++;
- emit_line(ecbdata->opt, color, reset, line, len);
- } else {
+ switch (line[0]) {
+ case '+':
ecbdata->lno_in_postimage++;
emit_add_line(reset, ecbdata, line + 1, len - 1);
+ break;
+ case '-':
+ ecbdata->lno_in_preimage++;
+ emit_del_line(reset, ecbdata, line + 1, len - 1);
+ break;
+ case ' ':
+ ecbdata->lno_in_postimage++;
+ ecbdata->lno_in_preimage++;
+ emit_context_line(reset, ecbdata, line + 1, len - 1);
+ break;
+ default:
+ /* incomplete line at the end */
+ ecbdata->lno_in_preimage++;
+ emit_line(ecbdata->opt,
+ diff_get_color(ecbdata->color_diff, DIFF_CONTEXT),
+ reset, line, len);
+ break;
}
}
options->rename_limit = -1;
options->dirstat_permille = diff_dirstat_permille_default;
options->context = diff_context_default;
+ options->ws_error_highlight = WSEH_NEW;
DIFF_OPT_SET(options, RENAME_EMPTY);
/* pathchange left =NULL by default */
*fmt |= DIFF_FORMAT_PATCH;
}
+static int parse_one_token(const char **arg, const char *token)
+{
+ return skip_prefix(*arg, token, arg) && (!**arg || **arg == ',');
+}
+
+static int parse_ws_error_highlight(struct diff_options *opt, const char *arg)
+{
+ const char *orig_arg = arg;
+ unsigned val = 0;
+ while (*arg) {
+ if (parse_one_token(&arg, "none"))
+ val = 0;
+ else if (parse_one_token(&arg, "default"))
+ val = WSEH_NEW;
+ else if (parse_one_token(&arg, "all"))
+ val = WSEH_NEW | WSEH_OLD | WSEH_CONTEXT;
+ else if (parse_one_token(&arg, "new"))
+ val |= WSEH_NEW;
+ else if (parse_one_token(&arg, "old"))
+ val |= WSEH_OLD;
+ else if (parse_one_token(&arg, "context"))
+ val |= WSEH_CONTEXT;
+ else {
+ error("unknown value after ws-error-highlight=%.*s",
+ (int)(arg - orig_arg), orig_arg);
+ return 0;
+ }
+ if (*arg)
+ arg++;
+ }
+ opt->ws_error_highlight = val;
+ return 1;
+}
+
int diff_opt_parse(struct diff_options *options, const char **av, int ac)
{
const char *arg = av[0];
DIFF_OPT_SET(options, SUBMODULE_LOG);
else if (skip_prefix(arg, "--submodule=", &arg))
return parse_submodule_opt(options, arg);
+ else if (skip_prefix(arg, "--ws-error-highlight=", &arg))
+ return parse_ws_error_highlight(options, arg);
/* misc options */
else if (!strcmp(arg, "-z"))
#include "tree-walk.h"
#include "pathspec.h"
+#include "object.h"
struct rev_info;
struct diff_options;
int dirstat_permille;
int setup;
int abbrev;
+/* white-space error highlighting */
+#define WSEH_NEW 1
+#define WSEH_CONTEXT 2
+#define WSEH_OLD 4
+ unsigned ws_error_highlight;
const char *prefix;
int prefix_length;
const char *stat_sep;
enum color_diff {
DIFF_RESET = 0,
- DIFF_PLAIN = 1,
+ DIFF_CONTEXT = 1,
DIFF_METAINFO = 2,
DIFF_FRAGINFO = 3,
DIFF_FILE_OLD = 4,
struct combine_diff_path *next;
char *path;
unsigned int mode;
- unsigned char sha1[20];
+ struct object_id oid;
struct combine_diff_parent {
char status;
unsigned int mode;
- unsigned char sha1[20];
+ struct object_id oid;
} parent[FLEX_ARRAY];
};
#define combine_diff_path_size(n, l) \
#include "wildmatch.h"
#include "pathspec.h"
#include "utf8.h"
+#include "varint.h"
+#include "ewah/ewok.h"
struct path_simplify {
int len;
path_untracked
};
+/*
+ * Support data structure for our opendir/readdir/closedir wrappers
+ */
+struct cached_dir {
+ DIR *fdir;
+ struct untracked_cache_dir *untracked;
+ int nr_files;
+ int nr_dirs;
+
+ struct dirent *de;
+ const char *file;
+ struct untracked_cache_dir *ucd;
+};
+
static enum path_treatment read_directory_recursive(struct dir_struct *dir,
- const char *path, int len,
+ const char *path, int len, struct untracked_cache_dir *untracked,
int check_only, const struct path_simplify *simplify);
static int get_dtype(struct dirent *de, const char *path, int len);
x->el = el;
}
-static void *read_skip_worktree_file_from_index(const char *path, size_t *size)
+static void *read_skip_worktree_file_from_index(const char *path, size_t *size,
+ struct sha1_stat *sha1_stat)
{
int pos, len;
unsigned long sz;
return NULL;
}
*size = xsize_t(sz);
+ if (sha1_stat) {
+ memset(&sha1_stat->stat, 0, sizeof(sha1_stat->stat));
+ hashcpy(sha1_stat->sha1, active_cache[pos]->sha1);
+ }
return data;
}
*last_space = '\0';
}
-int add_excludes_from_file_to_list(const char *fname,
- const char *base,
- int baselen,
- struct exclude_list *el,
- int check_index)
+/*
+ * Given a subdirectory name and "dir" of the current directory,
+ * search the subdir in "dir" and return it, or create a new one if it
+ * does not exist in "dir".
+ *
+ * If "name" has the trailing slash, it'll be excluded in the search.
+ */
+static struct untracked_cache_dir *lookup_untracked(struct untracked_cache *uc,
+ struct untracked_cache_dir *dir,
+ const char *name, int len)
+{
+ int first, last;
+ struct untracked_cache_dir *d;
+ if (!dir)
+ return NULL;
+ if (len && name[len - 1] == '/')
+ len--;
+ first = 0;
+ last = dir->dirs_nr;
+ while (last > first) {
+ int cmp, next = (last + first) >> 1;
+ d = dir->dirs[next];
+ cmp = strncmp(name, d->name, len);
+ if (!cmp && strlen(d->name) > len)
+ cmp = -1;
+ if (!cmp)
+ return d;
+ if (cmp < 0) {
+ last = next;
+ continue;
+ }
+ first = next+1;
+ }
+
+ uc->dir_created++;
+ d = xmalloc(sizeof(*d) + len + 1);
+ memset(d, 0, sizeof(*d));
+ memcpy(d->name, name, len);
+ d->name[len] = '\0';
+
+ ALLOC_GROW(dir->dirs, dir->dirs_nr + 1, dir->dirs_alloc);
+ memmove(dir->dirs + first + 1, dir->dirs + first,
+ (dir->dirs_nr - first) * sizeof(*dir->dirs));
+ dir->dirs_nr++;
+ dir->dirs[first] = d;
+ return d;
+}
+
+static void do_invalidate_gitignore(struct untracked_cache_dir *dir)
+{
+ int i;
+ dir->valid = 0;
+ dir->untracked_nr = 0;
+ for (i = 0; i < dir->dirs_nr; i++)
+ do_invalidate_gitignore(dir->dirs[i]);
+}
+
+static void invalidate_gitignore(struct untracked_cache *uc,
+ struct untracked_cache_dir *dir)
+{
+ uc->gitignore_invalidated++;
+ do_invalidate_gitignore(dir);
+}
+
+static void invalidate_directory(struct untracked_cache *uc,
+ struct untracked_cache_dir *dir)
+{
+ int i;
+ uc->dir_invalidated++;
+ dir->valid = 0;
+ dir->untracked_nr = 0;
+ for (i = 0; i < dir->dirs_nr; i++)
+ dir->dirs[i]->recurse = 0;
+}
+
+/*
+ * Given a file with name "fname", read it (either from disk, or from
+ * the index if "check_index" is non-zero), parse it and store the
+ * exclude rules in "el".
+ *
+ * If "ss" is not NULL, compute SHA-1 of the exclude file and fill
+ * stat data from disk (only valid if add_excludes returns zero). If
+ * ss_valid is non-zero, "ss" must contain good value as input.
+ */
+static int add_excludes(const char *fname, const char *base, int baselen,
+ struct exclude_list *el, int check_index,
+ struct sha1_stat *sha1_stat)
{
struct stat st;
int fd, i, lineno = 1;
if (0 <= fd)
close(fd);
if (!check_index ||
- (buf = read_skip_worktree_file_from_index(fname, &size)) == NULL)
+ (buf = read_skip_worktree_file_from_index(fname, &size, sha1_stat)) == NULL)
return -1;
if (size == 0) {
free(buf);
} else {
size = xsize_t(st.st_size);
if (size == 0) {
+ if (sha1_stat) {
+ fill_stat_data(&sha1_stat->stat, &st);
+ hashcpy(sha1_stat->sha1, EMPTY_BLOB_SHA1_BIN);
+ sha1_stat->valid = 1;
+ }
close(fd);
return 0;
}
}
buf[size++] = '\n';
close(fd);
+ if (sha1_stat) {
+ int pos;
+ if (sha1_stat->valid &&
+ !match_stat_data_racy(&the_index, &sha1_stat->stat, &st))
+ ; /* no content change, ss->sha1 still good */
+ else if (check_index &&
+ (pos = cache_name_pos(fname, strlen(fname))) >= 0 &&
+ !ce_stage(active_cache[pos]) &&
+ ce_uptodate(active_cache[pos]) &&
+ !would_convert_to_git(fname))
+ hashcpy(sha1_stat->sha1, active_cache[pos]->sha1);
+ else
+ hash_sha1_file(buf, size, "blob", sha1_stat->sha1);
+ fill_stat_data(&sha1_stat->stat, &st);
+ sha1_stat->valid = 1;
+ }
}
el->filebuf = buf;
return 0;
}
+int add_excludes_from_file_to_list(const char *fname, const char *base,
+ int baselen, struct exclude_list *el,
+ int check_index)
+{
+ return add_excludes(fname, base, baselen, el, check_index, NULL);
+}
+
struct exclude_list *add_exclude_list(struct dir_struct *dir,
int group_type, const char *src)
{
/*
* Used to set up core.excludesfile and .git/info/exclude lists.
*/
-void add_excludes_from_file(struct dir_struct *dir, const char *fname)
+static void add_excludes_from_file_1(struct dir_struct *dir, const char *fname,
+ struct sha1_stat *sha1_stat)
{
struct exclude_list *el;
+ /*
+ * catch setup_standard_excludes() that's called before
+ * dir->untracked is assigned. That function behaves
+ * differently when dir->untracked is non-NULL.
+ */
+ if (!dir->untracked)
+ dir->unmanaged_exclude_files++;
el = add_exclude_list(dir, EXC_FILE, fname);
- if (add_excludes_from_file_to_list(fname, "", 0, el, 0) < 0)
+ if (add_excludes(fname, "", 0, el, 0, sha1_stat) < 0)
die("cannot use %s as an exclude file", fname);
}
+void add_excludes_from_file(struct dir_struct *dir, const char *fname)
+{
+ dir->unmanaged_exclude_files++; /* see validate_untracked_cache() */
+ add_excludes_from_file_1(dir, fname, NULL);
+}
+
int match_basename(const char *basename, int basenamelen,
const char *pattern, int prefix, int patternlen,
int flags)
struct exclude_list_group *group;
struct exclude_list *el;
struct exclude_stack *stk = NULL;
+ struct untracked_cache_dir *untracked;
int current;
group = &dir->exclude_list_group[EXC_DIRS];
/* Read from the parent directories and push them down. */
current = stk ? stk->baselen : -1;
strbuf_setlen(&dir->basebuf, current < 0 ? 0 : current);
+ if (dir->untracked)
+ untracked = stk ? stk->ucd : dir->untracked->root;
+ else
+ untracked = NULL;
+
while (current < baselen) {
const char *cp;
+ struct sha1_stat sha1_stat;
stk = xcalloc(1, sizeof(*stk));
if (current < 0) {
if (!cp)
die("oops in prep_exclude");
cp++;
+ untracked =
+ lookup_untracked(dir->untracked, untracked,
+ base + current,
+ cp - base - current);
}
stk->prev = dir->exclude_stack;
stk->baselen = cp - base;
stk->exclude_ix = group->nr;
+ stk->ucd = untracked;
el = add_exclude_list(dir, EXC_DIRS, NULL);
strbuf_add(&dir->basebuf, base + current, stk->baselen - current);
assert(stk->baselen == dir->basebuf.len);
}
/* Try to read per-directory file */
- if (dir->exclude_per_dir) {
+ hashclr(sha1_stat.sha1);
+ sha1_stat.valid = 0;
+ if (dir->exclude_per_dir &&
+ /*
+ * If we know that no files have been added in
+ * this directory (i.e. valid_cached_dir() has
+ * been executed and set untracked->valid) ..
+ */
+ (!untracked || !untracked->valid ||
+ /*
+ * .. and .gitignore does not exist before
+ * (i.e. null exclude_sha1 and skip_worktree is
+ * not set). Then we can skip loading .gitignore,
+ * which would result in ENOENT anyway.
+ * skip_worktree is taken care in read_directory()
+ */
+ !is_null_sha1(untracked->exclude_sha1))) {
/*
* dir->basebuf gets reused by the traversal, but we
* need fname to remain unchanged to ensure the src
strbuf_addbuf(&sb, &dir->basebuf);
strbuf_addstr(&sb, dir->exclude_per_dir);
el->src = strbuf_detach(&sb, NULL);
- add_excludes_from_file_to_list(el->src, el->src,
- stk->baselen, el, 1);
+ add_excludes(el->src, el->src, stk->baselen, el, 1,
+ untracked ? &sha1_stat : NULL);
+ }
+ /*
+ * NEEDSWORK: when untracked cache is enabled, prep_exclude()
+ * will first be called in valid_cached_dir() then maybe many
+ * times more in last_exclude_matching(). When the cache is
+ * used, last_exclude_matching() will not be called and
+ * reading .gitignore content will be a waste.
+ *
+ * So when it's called by valid_cached_dir() and we can get
+ * .gitignore SHA-1 from the index (i.e. .gitignore is not
+ * modified on work tree), we could delay reading the
+ * .gitignore content until we absolutely need it in
+ * last_exclude_matching(). Be careful about ignore rule
+ * order, though, if you do that.
+ */
+ if (untracked &&
+ hashcmp(sha1_stat.sha1, untracked->exclude_sha1)) {
+ invalidate_gitignore(dir->untracked, untracked);
+ hashcpy(untracked->exclude_sha1, sha1_stat.sha1);
}
dir->exclude_stack = stk;
current = stk->baselen;
* (c) otherwise, we recurse into it.
*/
static enum path_treatment treat_directory(struct dir_struct *dir,
+ struct untracked_cache_dir *untracked,
const char *dirname, int len, int exclude,
const struct path_simplify *simplify)
{
if (!(dir->flags & DIR_HIDE_EMPTY_DIRECTORIES))
return exclude ? path_excluded : path_untracked;
- return read_directory_recursive(dir, dirname, len, 1, simplify);
+ untracked = lookup_untracked(dir->untracked, untracked, dirname, len);
+ return read_directory_recursive(dir, dirname, len,
+ untracked, 1, simplify);
}
/*
}
static enum path_treatment treat_one_path(struct dir_struct *dir,
+ struct untracked_cache_dir *untracked,
struct strbuf *path,
const struct path_simplify *simplify,
int dtype, struct dirent *de)
return path_none;
case DT_DIR:
strbuf_addch(path, '/');
- return treat_directory(dir, path->buf, path->len, exclude,
+ return treat_directory(dir, untracked, path->buf, path->len, exclude,
simplify);
case DT_REG:
case DT_LNK:
}
}
+static enum path_treatment treat_path_fast(struct dir_struct *dir,
+ struct untracked_cache_dir *untracked,
+ struct cached_dir *cdir,
+ struct strbuf *path,
+ int baselen,
+ const struct path_simplify *simplify)
+{
+ strbuf_setlen(path, baselen);
+ if (!cdir->ucd) {
+ strbuf_addstr(path, cdir->file);
+ return path_untracked;
+ }
+ strbuf_addstr(path, cdir->ucd->name);
+ /* treat_one_path() does this before it calls treat_directory() */
+ if (path->buf[path->len - 1] != '/')
+ strbuf_addch(path, '/');
+ if (cdir->ucd->check_only)
+ /*
+ * check_only is set as a result of treat_directory() getting
+ * to its bottom. Verify again the same set of directories
+ * with check_only set.
+ */
+ return read_directory_recursive(dir, path->buf, path->len,
+ cdir->ucd, 1, simplify);
+ /*
+ * We get path_recurse in the first run when
+ * directory_exists_in_index() returns index_nonexistent. We
+ * are sure that new changes in the index does not impact the
+ * outcome. Return now.
+ */
+ return path_recurse;
+}
+
static enum path_treatment treat_path(struct dir_struct *dir,
- struct dirent *de,
+ struct untracked_cache_dir *untracked,
+ struct cached_dir *cdir,
struct strbuf *path,
int baselen,
const struct path_simplify *simplify)
{
int dtype;
+ struct dirent *de = cdir->de;
+ if (!de)
+ return treat_path_fast(dir, untracked, cdir, path,
+ baselen, simplify);
if (is_dot_or_dotdot(de->d_name) || !strcmp(de->d_name, ".git"))
return path_none;
strbuf_setlen(path, baselen);
return path_none;
dtype = DTYPE(de);
- return treat_one_path(dir, path, simplify, dtype, de);
+ return treat_one_path(dir, untracked, path, simplify, dtype, de);
+}
+
+static void add_untracked(struct untracked_cache_dir *dir, const char *name)
+{
+ if (!dir)
+ return;
+ ALLOC_GROW(dir->untracked, dir->untracked_nr + 1,
+ dir->untracked_alloc);
+ dir->untracked[dir->untracked_nr++] = xstrdup(name);
+}
+
+static int valid_cached_dir(struct dir_struct *dir,
+ struct untracked_cache_dir *untracked,
+ struct strbuf *path,
+ int check_only)
+{
+ struct stat st;
+
+ if (!untracked)
+ return 0;
+
+ if (stat(path->len ? path->buf : ".", &st)) {
+ invalidate_directory(dir->untracked, untracked);
+ memset(&untracked->stat_data, 0, sizeof(untracked->stat_data));
+ return 0;
+ }
+ if (!untracked->valid ||
+ match_stat_data_racy(&the_index, &untracked->stat_data, &st)) {
+ if (untracked->valid)
+ invalidate_directory(dir->untracked, untracked);
+ fill_stat_data(&untracked->stat_data, &st);
+ return 0;
+ }
+
+ if (untracked->check_only != !!check_only) {
+ invalidate_directory(dir->untracked, untracked);
+ return 0;
+ }
+
+ /*
+ * prep_exclude will be called eventually on this directory,
+ * but it's called much later in last_exclude_matching(). We
+ * need it now to determine the validity of the cache for this
+ * path. The next calls will be nearly no-op, the way
+ * prep_exclude() is designed.
+ */
+ if (path->len && path->buf[path->len - 1] != '/') {
+ strbuf_addch(path, '/');
+ prep_exclude(dir, path->buf, path->len);
+ strbuf_setlen(path, path->len - 1);
+ } else
+ prep_exclude(dir, path->buf, path->len);
+
+ /* hopefully prep_exclude() haven't invalidated this entry... */
+ return untracked->valid;
+}
+
+static int open_cached_dir(struct cached_dir *cdir,
+ struct dir_struct *dir,
+ struct untracked_cache_dir *untracked,
+ struct strbuf *path,
+ int check_only)
+{
+ memset(cdir, 0, sizeof(*cdir));
+ cdir->untracked = untracked;
+ if (valid_cached_dir(dir, untracked, path, check_only))
+ return 0;
+ cdir->fdir = opendir(path->len ? path->buf : ".");
+ if (dir->untracked)
+ dir->untracked->dir_opened++;
+ if (!cdir->fdir)
+ return -1;
+ return 0;
+}
+
+static int read_cached_dir(struct cached_dir *cdir)
+{
+ if (cdir->fdir) {
+ cdir->de = readdir(cdir->fdir);
+ if (!cdir->de)
+ return -1;
+ return 0;
+ }
+ while (cdir->nr_dirs < cdir->untracked->dirs_nr) {
+ struct untracked_cache_dir *d = cdir->untracked->dirs[cdir->nr_dirs];
+ if (!d->recurse) {
+ cdir->nr_dirs++;
+ continue;
+ }
+ cdir->ucd = d;
+ cdir->nr_dirs++;
+ return 0;
+ }
+ cdir->ucd = NULL;
+ if (cdir->nr_files < cdir->untracked->untracked_nr) {
+ struct untracked_cache_dir *d = cdir->untracked;
+ cdir->file = d->untracked[cdir->nr_files++];
+ return 0;
+ }
+ return -1;
+}
+
+static void close_cached_dir(struct cached_dir *cdir)
+{
+ if (cdir->fdir)
+ closedir(cdir->fdir);
+ /*
+ * We have gone through this directory and found no untracked
+ * entries. Mark it valid.
+ */
+ if (cdir->untracked) {
+ cdir->untracked->valid = 1;
+ cdir->untracked->recurse = 1;
+ }
}
/*
*/
static enum path_treatment read_directory_recursive(struct dir_struct *dir,
const char *base, int baselen,
- int check_only,
+ struct untracked_cache_dir *untracked, int check_only,
const struct path_simplify *simplify)
{
- DIR *fdir;
+ struct cached_dir cdir;
enum path_treatment state, subdir_state, dir_state = path_none;
- struct dirent *de;
struct strbuf path = STRBUF_INIT;
strbuf_add(&path, base, baselen);
- fdir = opendir(path.len ? path.buf : ".");
- if (!fdir)
+ if (open_cached_dir(&cdir, dir, untracked, &path, check_only))
goto out;
- while ((de = readdir(fdir)) != NULL) {
+ if (untracked)
+ untracked->check_only = !!check_only;
+
+ while (!read_cached_dir(&cdir)) {
/* check how the file or directory should be treated */
- state = treat_path(dir, de, &path, baselen, simplify);
+ state = treat_path(dir, untracked, &cdir, &path, baselen, simplify);
+
if (state > dir_state)
dir_state = state;
/* recurse into subdir if instructed by treat_path */
if (state == path_recurse) {
- subdir_state = read_directory_recursive(dir, path.buf,
- path.len, check_only, simplify);
+ struct untracked_cache_dir *ud;
+ ud = lookup_untracked(dir->untracked, untracked,
+ path.buf + baselen,
+ path.len - baselen);
+ subdir_state =
+ read_directory_recursive(dir, path.buf, path.len,
+ ud, check_only, simplify);
if (subdir_state > dir_state)
dir_state = subdir_state;
}
if (check_only) {
/* abort early if maximum state has been reached */
- if (dir_state == path_untracked)
+ if (dir_state == path_untracked) {
+ if (cdir.fdir)
+ add_untracked(untracked, path.buf + baselen);
break;
+ }
/* skip the dir_add_* part */
continue;
}
break;
case path_untracked:
- if (!(dir->flags & DIR_SHOW_IGNORED))
- dir_add_name(dir, path.buf, path.len);
+ if (dir->flags & DIR_SHOW_IGNORED)
+ break;
+ dir_add_name(dir, path.buf, path.len);
+ if (cdir.fdir)
+ add_untracked(untracked, path.buf + baselen);
break;
default:
break;
}
}
- closedir(fdir);
+ close_cached_dir(&cdir);
out:
strbuf_release(&path);
break;
if (simplify_away(sb.buf, sb.len, simplify))
break;
- if (treat_one_path(dir, &sb, simplify,
+ if (treat_one_path(dir, NULL, &sb, simplify,
DT_DIR, NULL) == path_none)
break; /* do not recurse into it */
if (len <= baselen) {
return rc;
}
+static const char *get_ident_string(void)
+{
+ static struct strbuf sb = STRBUF_INIT;
+ struct utsname uts;
+
+ if (sb.len)
+ return sb.buf;
+ if (uname(&uts))
+ die_errno(_("failed to get kernel name and information"));
+ strbuf_addf(&sb, "Location %s, system %s %s %s", get_git_work_tree(),
+ uts.sysname, uts.release, uts.version);
+ return sb.buf;
+}
+
+static int ident_in_untracked(const struct untracked_cache *uc)
+{
+ const char *end = uc->ident.buf + uc->ident.len;
+ const char *p = uc->ident.buf;
+
+ for (p = uc->ident.buf; p < end; p += strlen(p) + 1)
+ if (!strcmp(p, get_ident_string()))
+ return 1;
+ return 0;
+}
+
+void add_untracked_ident(struct untracked_cache *uc)
+{
+ if (ident_in_untracked(uc))
+ return;
+ strbuf_addstr(&uc->ident, get_ident_string());
+ /* this strbuf contains a list of strings, save NUL too */
+ strbuf_addch(&uc->ident, 0);
+}
+
+static struct untracked_cache_dir *validate_untracked_cache(struct dir_struct *dir,
+ int base_len,
+ const struct pathspec *pathspec)
+{
+ struct untracked_cache_dir *root;
+ int i;
+
+ if (!dir->untracked || getenv("GIT_DISABLE_UNTRACKED_CACHE"))
+ return NULL;
+
+ /*
+ * We only support $GIT_DIR/info/exclude and core.excludesfile
+ * as the global ignore rule files. Any other additions
+ * (e.g. from command line) invalidate the cache. This
+ * condition also catches running setup_standard_excludes()
+ * before setting dir->untracked!
+ */
+ if (dir->unmanaged_exclude_files)
+ return NULL;
+
+ /*
+ * Optimize for the main use case only: whole-tree git
+ * status. More work involved in treat_leading_path() if we
+ * use cache on just a subset of the worktree. pathspec
+ * support could make the matter even worse.
+ */
+ if (base_len || (pathspec && pathspec->nr))
+ return NULL;
+
+ /* Different set of flags may produce different results */
+ if (dir->flags != dir->untracked->dir_flags ||
+ /*
+ * See treat_directory(), case index_nonexistent. Without
+ * this flag, we may need to also cache .git file content
+ * for the resolve_gitlink_ref() call, which we don't.
+ */
+ !(dir->flags & DIR_SHOW_OTHER_DIRECTORIES) ||
+ /* We don't support collecting ignore files */
+ (dir->flags & (DIR_SHOW_IGNORED | DIR_SHOW_IGNORED_TOO |
+ DIR_COLLECT_IGNORED)))
+ return NULL;
+
+ /*
+ * If we use .gitignore in the cache and now you change it to
+ * .gitexclude, everything will go wrong.
+ */
+ if (dir->exclude_per_dir != dir->untracked->exclude_per_dir &&
+ strcmp(dir->exclude_per_dir, dir->untracked->exclude_per_dir))
+ return NULL;
+
+ /*
+ * EXC_CMDL is not considered in the cache. If people set it,
+ * skip the cache.
+ */
+ if (dir->exclude_list_group[EXC_CMDL].nr)
+ return NULL;
+
+ /*
+ * An optimization in prep_exclude() does not play well with
+ * CE_SKIP_WORKTREE. It's a rare case anyway, if a single
+ * entry has that bit set, disable the whole untracked cache.
+ */
+ for (i = 0; i < active_nr; i++)
+ if (ce_skip_worktree(active_cache[i]))
+ return NULL;
+
+ if (!ident_in_untracked(dir->untracked)) {
+ warning(_("Untracked cache is disabled on this system."));
+ return NULL;
+ }
+
+ if (!dir->untracked->root) {
+ const int len = sizeof(*dir->untracked->root);
+ dir->untracked->root = xmalloc(len);
+ memset(dir->untracked->root, 0, len);
+ }
+
+ /* Validate $GIT_DIR/info/exclude and core.excludesfile */
+ root = dir->untracked->root;
+ if (hashcmp(dir->ss_info_exclude.sha1,
+ dir->untracked->ss_info_exclude.sha1)) {
+ invalidate_gitignore(dir->untracked, root);
+ dir->untracked->ss_info_exclude = dir->ss_info_exclude;
+ }
+ if (hashcmp(dir->ss_excludes_file.sha1,
+ dir->untracked->ss_excludes_file.sha1)) {
+ invalidate_gitignore(dir->untracked, root);
+ dir->untracked->ss_excludes_file = dir->ss_excludes_file;
+ }
+
+ /* Make sure this directory is not dropped out at saving phase */
+ root->recurse = 1;
+ return root;
+}
+
int read_directory(struct dir_struct *dir, const char *path, int len, const struct pathspec *pathspec)
{
struct path_simplify *simplify;
+ struct untracked_cache_dir *untracked;
/*
* Check out create_simplify()
* create_simplify().
*/
simplify = create_simplify(pathspec ? pathspec->_raw : NULL);
+ untracked = validate_untracked_cache(dir, len, pathspec);
+ if (!untracked)
+ /*
+ * make sure untracked cache code path is disabled,
+ * e.g. prep_exclude()
+ */
+ dir->untracked = NULL;
if (!len || treat_leading_path(dir, path, len, simplify))
- read_directory_recursive(dir, path, len, 0, simplify);
+ read_directory_recursive(dir, path, len, untracked, 0, simplify);
free_simplify(simplify);
qsort(dir->entries, dir->nr, sizeof(struct dir_entry *), cmp_name);
qsort(dir->ignored, dir->ignored_nr, sizeof(struct dir_entry *), cmp_name);
+ if (dir->untracked) {
+ static struct trace_key trace_untracked_stats = TRACE_KEY_INIT(UNTRACKED_STATS);
+ trace_printf_key(&trace_untracked_stats,
+ "node creation: %u\n"
+ "gitignore invalidation: %u\n"
+ "directory invalidation: %u\n"
+ "opendir: %u\n",
+ dir->untracked->dir_created,
+ dir->untracked->gitignore_invalidated,
+ dir->untracked->dir_invalidated,
+ dir->untracked->dir_opened);
+ if (dir->untracked == the_index.untracked &&
+ (dir->untracked->dir_opened ||
+ dir->untracked->gitignore_invalidated ||
+ dir->untracked->dir_invalidated))
+ the_index.cache_changed |= UNTRACKED_CHANGED;
+ if (dir->untracked != the_index.untracked) {
+ free(dir->untracked);
+ dir->untracked = NULL;
+ }
+ }
return dir->nr;
}
if (!excludes_file)
excludes_file = xdg_config_home("ignore");
if (excludes_file && !access_or_warn(excludes_file, R_OK, 0))
- add_excludes_from_file(dir, excludes_file);
+ add_excludes_from_file_1(dir, excludes_file,
+ dir->untracked ? &dir->ss_excludes_file : NULL);
/* per repository user preference */
path = git_path("info/exclude");
if (!access_or_warn(path, R_OK, 0))
- add_excludes_from_file(dir, path);
+ add_excludes_from_file_1(dir, path,
+ dir->untracked ? &dir->ss_info_exclude : NULL);
}
int remove_path(const char *name)
}
strbuf_release(&dir->basebuf);
}
+
+struct ondisk_untracked_cache {
+ struct stat_data info_exclude_stat;
+ struct stat_data excludes_file_stat;
+ uint32_t dir_flags;
+ unsigned char info_exclude_sha1[20];
+ unsigned char excludes_file_sha1[20];
+ char exclude_per_dir[FLEX_ARRAY];
+};
+
+#define ouc_size(len) (offsetof(struct ondisk_untracked_cache, exclude_per_dir) + len + 1)
+
+struct write_data {
+ int index; /* number of written untracked_cache_dir */
+ struct ewah_bitmap *check_only; /* from untracked_cache_dir */
+ struct ewah_bitmap *valid; /* from untracked_cache_dir */
+ struct ewah_bitmap *sha1_valid; /* set if exclude_sha1 is not null */
+ struct strbuf out;
+ struct strbuf sb_stat;
+ struct strbuf sb_sha1;
+};
+
+static void stat_data_to_disk(struct stat_data *to, const struct stat_data *from)
+{
+ to->sd_ctime.sec = htonl(from->sd_ctime.sec);
+ to->sd_ctime.nsec = htonl(from->sd_ctime.nsec);
+ to->sd_mtime.sec = htonl(from->sd_mtime.sec);
+ to->sd_mtime.nsec = htonl(from->sd_mtime.nsec);
+ to->sd_dev = htonl(from->sd_dev);
+ to->sd_ino = htonl(from->sd_ino);
+ to->sd_uid = htonl(from->sd_uid);
+ to->sd_gid = htonl(from->sd_gid);
+ to->sd_size = htonl(from->sd_size);
+}
+
+static void write_one_dir(struct untracked_cache_dir *untracked,
+ struct write_data *wd)
+{
+ struct stat_data stat_data;
+ struct strbuf *out = &wd->out;
+ unsigned char intbuf[16];
+ unsigned int intlen, value;
+ int i = wd->index++;
+
+ /*
+ * untracked_nr should be reset whenever valid is clear, but
+ * for safety..
+ */
+ if (!untracked->valid) {
+ untracked->untracked_nr = 0;
+ untracked->check_only = 0;
+ }
+
+ if (untracked->check_only)
+ ewah_set(wd->check_only, i);
+ if (untracked->valid) {
+ ewah_set(wd->valid, i);
+ stat_data_to_disk(&stat_data, &untracked->stat_data);
+ strbuf_add(&wd->sb_stat, &stat_data, sizeof(stat_data));
+ }
+ if (!is_null_sha1(untracked->exclude_sha1)) {
+ ewah_set(wd->sha1_valid, i);
+ strbuf_add(&wd->sb_sha1, untracked->exclude_sha1, 20);
+ }
+
+ intlen = encode_varint(untracked->untracked_nr, intbuf);
+ strbuf_add(out, intbuf, intlen);
+
+ /* skip non-recurse directories */
+ for (i = 0, value = 0; i < untracked->dirs_nr; i++)
+ if (untracked->dirs[i]->recurse)
+ value++;
+ intlen = encode_varint(value, intbuf);
+ strbuf_add(out, intbuf, intlen);
+
+ strbuf_add(out, untracked->name, strlen(untracked->name) + 1);
+
+ for (i = 0; i < untracked->untracked_nr; i++)
+ strbuf_add(out, untracked->untracked[i],
+ strlen(untracked->untracked[i]) + 1);
+
+ for (i = 0; i < untracked->dirs_nr; i++)
+ if (untracked->dirs[i]->recurse)
+ write_one_dir(untracked->dirs[i], wd);
+}
+
+void write_untracked_extension(struct strbuf *out, struct untracked_cache *untracked)
+{
+ struct ondisk_untracked_cache *ouc;
+ struct write_data wd;
+ unsigned char varbuf[16];
+ int len = 0, varint_len;
+ if (untracked->exclude_per_dir)
+ len = strlen(untracked->exclude_per_dir);
+ ouc = xmalloc(sizeof(*ouc) + len + 1);
+ stat_data_to_disk(&ouc->info_exclude_stat, &untracked->ss_info_exclude.stat);
+ stat_data_to_disk(&ouc->excludes_file_stat, &untracked->ss_excludes_file.stat);
+ hashcpy(ouc->info_exclude_sha1, untracked->ss_info_exclude.sha1);
+ hashcpy(ouc->excludes_file_sha1, untracked->ss_excludes_file.sha1);
+ ouc->dir_flags = htonl(untracked->dir_flags);
+ memcpy(ouc->exclude_per_dir, untracked->exclude_per_dir, len + 1);
+
+ varint_len = encode_varint(untracked->ident.len, varbuf);
+ strbuf_add(out, varbuf, varint_len);
+ strbuf_add(out, untracked->ident.buf, untracked->ident.len);
+
+ strbuf_add(out, ouc, ouc_size(len));
+ free(ouc);
+ ouc = NULL;
+
+ if (!untracked->root) {
+ varint_len = encode_varint(0, varbuf);
+ strbuf_add(out, varbuf, varint_len);
+ return;
+ }
+
+ wd.index = 0;
+ wd.check_only = ewah_new();
+ wd.valid = ewah_new();
+ wd.sha1_valid = ewah_new();
+ strbuf_init(&wd.out, 1024);
+ strbuf_init(&wd.sb_stat, 1024);
+ strbuf_init(&wd.sb_sha1, 1024);
+ write_one_dir(untracked->root, &wd);
+
+ varint_len = encode_varint(wd.index, varbuf);
+ strbuf_add(out, varbuf, varint_len);
+ strbuf_addbuf(out, &wd.out);
+ ewah_serialize_strbuf(wd.valid, out);
+ ewah_serialize_strbuf(wd.check_only, out);
+ ewah_serialize_strbuf(wd.sha1_valid, out);
+ strbuf_addbuf(out, &wd.sb_stat);
+ strbuf_addbuf(out, &wd.sb_sha1);
+ strbuf_addch(out, '\0'); /* safe guard for string lists */
+
+ ewah_free(wd.valid);
+ ewah_free(wd.check_only);
+ ewah_free(wd.sha1_valid);
+ strbuf_release(&wd.out);
+ strbuf_release(&wd.sb_stat);
+ strbuf_release(&wd.sb_sha1);
+}
+
+static void free_untracked(struct untracked_cache_dir *ucd)
+{
+ int i;
+ if (!ucd)
+ return;
+ for (i = 0; i < ucd->dirs_nr; i++)
+ free_untracked(ucd->dirs[i]);
+ for (i = 0; i < ucd->untracked_nr; i++)
+ free(ucd->untracked[i]);
+ free(ucd->untracked);
+ free(ucd->dirs);
+ free(ucd);
+}
+
+void free_untracked_cache(struct untracked_cache *uc)
+{
+ if (uc)
+ free_untracked(uc->root);
+ free(uc);
+}
+
+struct read_data {
+ int index;
+ struct untracked_cache_dir **ucd;
+ struct ewah_bitmap *check_only;
+ struct ewah_bitmap *valid;
+ struct ewah_bitmap *sha1_valid;
+ const unsigned char *data;
+ const unsigned char *end;
+};
+
+static void stat_data_from_disk(struct stat_data *to, const struct stat_data *from)
+{
+ to->sd_ctime.sec = get_be32(&from->sd_ctime.sec);
+ to->sd_ctime.nsec = get_be32(&from->sd_ctime.nsec);
+ to->sd_mtime.sec = get_be32(&from->sd_mtime.sec);
+ to->sd_mtime.nsec = get_be32(&from->sd_mtime.nsec);
+ to->sd_dev = get_be32(&from->sd_dev);
+ to->sd_ino = get_be32(&from->sd_ino);
+ to->sd_uid = get_be32(&from->sd_uid);
+ to->sd_gid = get_be32(&from->sd_gid);
+ to->sd_size = get_be32(&from->sd_size);
+}
+
+static int read_one_dir(struct untracked_cache_dir **untracked_,
+ struct read_data *rd)
+{
+ struct untracked_cache_dir ud, *untracked;
+ const unsigned char *next, *data = rd->data, *end = rd->end;
+ unsigned int value;
+ int i, len;
+
+ memset(&ud, 0, sizeof(ud));
+
+ next = data;
+ value = decode_varint(&next);
+ if (next > end)
+ return -1;
+ ud.recurse = 1;
+ ud.untracked_alloc = value;
+ ud.untracked_nr = value;
+ if (ud.untracked_nr)
+ ud.untracked = xmalloc(sizeof(*ud.untracked) * ud.untracked_nr);
+ data = next;
+
+ next = data;
+ ud.dirs_alloc = ud.dirs_nr = decode_varint(&next);
+ if (next > end)
+ return -1;
+ ud.dirs = xmalloc(sizeof(*ud.dirs) * ud.dirs_nr);
+ data = next;
+
+ len = strlen((const char *)data);
+ next = data + len + 1;
+ if (next > rd->end)
+ return -1;
+ *untracked_ = untracked = xmalloc(sizeof(*untracked) + len);
+ memcpy(untracked, &ud, sizeof(ud));
+ memcpy(untracked->name, data, len + 1);
+ data = next;
+
+ for (i = 0; i < untracked->untracked_nr; i++) {
+ len = strlen((const char *)data);
+ next = data + len + 1;
+ if (next > rd->end)
+ return -1;
+ untracked->untracked[i] = xstrdup((const char*)data);
+ data = next;
+ }
+
+ rd->ucd[rd->index++] = untracked;
+ rd->data = data;
+
+ for (i = 0; i < untracked->dirs_nr; i++) {
+ len = read_one_dir(untracked->dirs + i, rd);
+ if (len < 0)
+ return -1;
+ }
+ return 0;
+}
+
+static void set_check_only(size_t pos, void *cb)
+{
+ struct read_data *rd = cb;
+ struct untracked_cache_dir *ud = rd->ucd[pos];
+ ud->check_only = 1;
+}
+
+static void read_stat(size_t pos, void *cb)
+{
+ struct read_data *rd = cb;
+ struct untracked_cache_dir *ud = rd->ucd[pos];
+ if (rd->data + sizeof(struct stat_data) > rd->end) {
+ rd->data = rd->end + 1;
+ return;
+ }
+ stat_data_from_disk(&ud->stat_data, (struct stat_data *)rd->data);
+ rd->data += sizeof(struct stat_data);
+ ud->valid = 1;
+}
+
+static void read_sha1(size_t pos, void *cb)
+{
+ struct read_data *rd = cb;
+ struct untracked_cache_dir *ud = rd->ucd[pos];
+ if (rd->data + 20 > rd->end) {
+ rd->data = rd->end + 1;
+ return;
+ }
+ hashcpy(ud->exclude_sha1, rd->data);
+ rd->data += 20;
+}
+
+static void load_sha1_stat(struct sha1_stat *sha1_stat,
+ const struct stat_data *stat,
+ const unsigned char *sha1)
+{
+ stat_data_from_disk(&sha1_stat->stat, stat);
+ hashcpy(sha1_stat->sha1, sha1);
+ sha1_stat->valid = 1;
+}
+
+struct untracked_cache *read_untracked_extension(const void *data, unsigned long sz)
+{
+ const struct ondisk_untracked_cache *ouc;
+ struct untracked_cache *uc;
+ struct read_data rd;
+ const unsigned char *next = data, *end = (const unsigned char *)data + sz;
+ const char *ident;
+ int ident_len, len;
+
+ if (sz <= 1 || end[-1] != '\0')
+ return NULL;
+ end--;
+
+ ident_len = decode_varint(&next);
+ if (next + ident_len > end)
+ return NULL;
+ ident = (const char *)next;
+ next += ident_len;
+
+ ouc = (const struct ondisk_untracked_cache *)next;
+ if (next + ouc_size(0) > end)
+ return NULL;
+
+ uc = xcalloc(1, sizeof(*uc));
+ strbuf_init(&uc->ident, ident_len);
+ strbuf_add(&uc->ident, ident, ident_len);
+ load_sha1_stat(&uc->ss_info_exclude, &ouc->info_exclude_stat,
+ ouc->info_exclude_sha1);
+ load_sha1_stat(&uc->ss_excludes_file, &ouc->excludes_file_stat,
+ ouc->excludes_file_sha1);
+ uc->dir_flags = get_be32(&ouc->dir_flags);
+ uc->exclude_per_dir = xstrdup(ouc->exclude_per_dir);
+ /* NUL after exclude_per_dir is covered by sizeof(*ouc) */
+ next += ouc_size(strlen(ouc->exclude_per_dir));
+ if (next >= end)
+ goto done2;
+
+ len = decode_varint(&next);
+ if (next > end || len == 0)
+ goto done2;
+
+ rd.valid = ewah_new();
+ rd.check_only = ewah_new();
+ rd.sha1_valid = ewah_new();
+ rd.data = next;
+ rd.end = end;
+ rd.index = 0;
+ rd.ucd = xmalloc(sizeof(*rd.ucd) * len);
+
+ if (read_one_dir(&uc->root, &rd) || rd.index != len)
+ goto done;
+
+ next = rd.data;
+ len = ewah_read_mmap(rd.valid, next, end - next);
+ if (len < 0)
+ goto done;
+
+ next += len;
+ len = ewah_read_mmap(rd.check_only, next, end - next);
+ if (len < 0)
+ goto done;
+
+ next += len;
+ len = ewah_read_mmap(rd.sha1_valid, next, end - next);
+ if (len < 0)
+ goto done;
+
+ ewah_each_bit(rd.check_only, set_check_only, &rd);
+ rd.data = next + len;
+ ewah_each_bit(rd.valid, read_stat, &rd);
+ ewah_each_bit(rd.sha1_valid, read_sha1, &rd);
+ next = rd.data;
+
+done:
+ free(rd.ucd);
+ ewah_free(rd.valid);
+ ewah_free(rd.check_only);
+ ewah_free(rd.sha1_valid);
+done2:
+ if (next != end) {
+ free_untracked_cache(uc);
+ uc = NULL;
+ }
+ return uc;
+}
+
+void untracked_cache_invalidate_path(struct index_state *istate,
+ const char *path)
+{
+ const char *sep;
+ struct untracked_cache_dir *d;
+ if (!istate->untracked || !istate->untracked->root)
+ return;
+ sep = strrchr(path, '/');
+ if (sep)
+ d = lookup_untracked(istate->untracked,
+ istate->untracked->root,
+ path, sep - path);
+ else
+ d = istate->untracked->root;
+ istate->untracked->dir_invalidated++;
+ d->valid = 0;
+ d->untracked_nr = 0;
+}
+
+void untracked_cache_remove_from_index(struct index_state *istate,
+ const char *path)
+{
+ untracked_cache_invalidate_path(istate, path);
+}
+
+void untracked_cache_add_to_index(struct index_state *istate,
+ const char *path)
+{
+ untracked_cache_invalidate_path(istate, path);
+}
struct exclude_stack *prev; /* the struct exclude_stack for the parent directory */
int baselen;
int exclude_ix; /* index of exclude_list within EXC_DIRS exclude_list_group */
+ struct untracked_cache_dir *ucd;
};
struct exclude_list_group {
struct exclude_list *el;
};
+struct sha1_stat {
+ struct stat_data stat;
+ unsigned char sha1[20];
+ int valid;
+};
+
+/*
+ * Untracked cache
+ *
+ * The following inputs are sufficient to determine what files in a
+ * directory are excluded:
+ *
+ * - The list of files and directories of the directory in question
+ * - The $GIT_DIR/index
+ * - dir_struct flags
+ * - The content of $GIT_DIR/info/exclude
+ * - The content of core.excludesfile
+ * - The content (or the lack) of .gitignore of all parent directories
+ * from $GIT_WORK_TREE
+ * - The check_only flag in read_directory_recursive (for
+ * DIR_HIDE_EMPTY_DIRECTORIES)
+ *
+ * The first input can be checked using directory mtime. In many
+ * filesystems, directory mtime (stat_data field) is updated when its
+ * files or direct subdirs are added or removed.
+ *
+ * The second one can be hooked from cache_tree_invalidate_path().
+ * Whenever a file (or a submodule) is added or removed from a
+ * directory, we invalidate that directory.
+ *
+ * The remaining inputs are easy, their SHA-1 could be used to verify
+ * their contents (exclude_sha1[], info_exclude_sha1[] and
+ * excludes_file_sha1[])
+ */
+struct untracked_cache_dir {
+ struct untracked_cache_dir **dirs;
+ char **untracked;
+ struct stat_data stat_data;
+ unsigned int untracked_alloc, dirs_nr, dirs_alloc;
+ unsigned int untracked_nr;
+ unsigned int check_only : 1;
+ /* all data except 'dirs' in this struct are good */
+ unsigned int valid : 1;
+ unsigned int recurse : 1;
+ /* null SHA-1 means this directory does not have .gitignore */
+ unsigned char exclude_sha1[20];
+ char name[FLEX_ARRAY];
+};
+
+struct untracked_cache {
+ struct sha1_stat ss_info_exclude;
+ struct sha1_stat ss_excludes_file;
+ const char *exclude_per_dir;
+ struct strbuf ident;
+ /*
+ * dir_struct#flags must match dir_flags or the untracked
+ * cache is ignored.
+ */
+ unsigned dir_flags;
+ struct untracked_cache_dir *root;
+ /* Statistics */
+ int dir_created;
+ int gitignore_invalidated;
+ int dir_invalidated;
+ int dir_opened;
+};
+
struct dir_struct {
int nr, alloc;
int ignored_nr, ignored_alloc;
struct exclude_stack *exclude_stack;
struct exclude *exclude;
struct strbuf basebuf;
+
+ /* Enable untracked file cache if set */
+ struct untracked_cache *untracked;
+ struct sha1_stat ss_info_exclude;
+ struct sha1_stat ss_excludes_file;
+ unsigned unmanaged_exclude_files;
};
/*
has_trailing_dir);
}
+void untracked_cache_invalidate_path(struct index_state *, const char *);
+void untracked_cache_remove_from_index(struct index_state *, const char *);
+void untracked_cache_add_to_index(struct index_state *, const char *);
+
+void free_untracked_cache(struct untracked_cache *);
+struct untracked_cache *read_untracked_extension(const void *data, unsigned long sz);
+void write_untracked_extension(struct strbuf *out, struct untracked_cache *untracked);
+void add_untracked_ident(struct untracked_cache *);
#endif
static const char *namespace;
static size_t namespace_len;
-static const char *git_dir;
+static const char *git_dir, *git_common_dir;
static char *git_object_dir, *git_index_file, *git_graft_file;
+int git_db_env, git_index_env, git_graft_env, git_common_dir_env;
/*
* Repository-local GIT_* environment variables; see cache.h for details.
NO_REPLACE_OBJECTS_ENVIRONMENT,
GIT_PREFIX_ENVIRONMENT,
GIT_SHALLOW_FILE_ENVIRONMENT,
+ GIT_COMMON_DIR_ENVIRONMENT,
NULL
};
return strbuf_detach(&buf, NULL);
}
-static char *git_path_from_env(const char *envvar, const char *path)
+static char *git_path_from_env(const char *envvar, const char *git_dir,
+ const char *path, int *fromenv)
{
const char *value = getenv(envvar);
- return value ? xstrdup(value) : git_pathdup("%s", path);
+ if (!value) {
+ char *buf = xmalloc(strlen(git_dir) + strlen(path) + 2);
+ sprintf(buf, "%s/%s", git_dir, path);
+ return buf;
+ }
+ if (fromenv)
+ *fromenv = 1;
+ return xstrdup(value);
}
static void setup_git_env(void)
{
+ struct strbuf sb = STRBUF_INIT;
const char *gitfile;
const char *shallow_file;
git_dir = DEFAULT_GIT_DIR_ENVIRONMENT;
gitfile = read_gitfile(git_dir);
git_dir = xstrdup(gitfile ? gitfile : git_dir);
- git_object_dir = git_path_from_env(DB_ENVIRONMENT, "objects");
- git_index_file = git_path_from_env(INDEX_ENVIRONMENT, "index");
- git_graft_file = git_path_from_env(GRAFT_ENVIRONMENT, "info/grafts");
+ if (get_common_dir(&sb, git_dir))
+ git_common_dir_env = 1;
+ git_common_dir = strbuf_detach(&sb, NULL);
+ git_object_dir = git_path_from_env(DB_ENVIRONMENT, git_common_dir,
+ "objects", &git_db_env);
+ git_index_file = git_path_from_env(INDEX_ENVIRONMENT, git_dir,
+ "index", &git_index_env);
+ git_graft_file = git_path_from_env(GRAFT_ENVIRONMENT, git_common_dir,
+ "info/grafts", &git_graft_env);
if (getenv(NO_REPLACE_OBJECTS_ENVIRONMENT))
check_replace_refs = 0;
namespace = expand_namespace(getenv(GIT_NAMESPACE_ENVIRONMENT));
return git_dir;
}
+const char *get_git_common_dir(void)
+{
+ return git_common_dir;
+}
+
const char *get_git_namespace(void)
{
if (!namespace)
#include "git-compat-util.h"
#include "ewok.h"
-#define MASK(x) ((eword_t)1 << (x % BITS_IN_WORD))
-#define BLOCK(x) (x / BITS_IN_WORD)
+#define EWAH_MASK(x) ((eword_t)1 << (x % BITS_IN_EWORD))
+#define EWAH_BLOCK(x) (x / BITS_IN_EWORD)
struct bitmap *bitmap_new(void)
{
void bitmap_set(struct bitmap *self, size_t pos)
{
- size_t block = BLOCK(pos);
+ size_t block = EWAH_BLOCK(pos);
if (block >= self->word_alloc) {
size_t old_size = self->word_alloc;
(self->word_alloc - old_size) * sizeof(eword_t));
}
- self->words[block] |= MASK(pos);
+ self->words[block] |= EWAH_MASK(pos);
}
void bitmap_clear(struct bitmap *self, size_t pos)
{
- size_t block = BLOCK(pos);
+ size_t block = EWAH_BLOCK(pos);
if (block < self->word_alloc)
- self->words[block] &= ~MASK(pos);
+ self->words[block] &= ~EWAH_MASK(pos);
}
int bitmap_get(struct bitmap *self, size_t pos)
{
- size_t block = BLOCK(pos);
+ size_t block = EWAH_BLOCK(pos);
return block < self->word_alloc &&
- (self->words[block] & MASK(pos)) != 0;
+ (self->words[block] & EWAH_MASK(pos)) != 0;
}
struct ewah_bitmap *bitmap_to_ewah(struct bitmap *bitmap)
void bitmap_or_ewah(struct bitmap *self, struct ewah_bitmap *other)
{
size_t original_size = self->word_alloc;
- size_t other_final = (other->bit_size / BITS_IN_WORD) + 1;
+ size_t other_final = (other->bit_size / BITS_IN_EWORD) + 1;
size_t i = 0;
struct ewah_iterator it;
eword_t word;
uint32_t offset;
if (word == (eword_t)~0) {
- for (offset = 0; offset < BITS_IN_WORD; ++offset)
+ for (offset = 0; offset < BITS_IN_EWORD; ++offset)
callback(pos++, data);
} else {
- for (offset = 0; offset < BITS_IN_WORD; ++offset) {
+ for (offset = 0; offset < BITS_IN_EWORD; ++offset) {
if ((word >> offset) == 0)
break;
offset += ewah_bit_ctz64(word >> offset);
callback(pos + offset, data);
}
- pos += BITS_IN_WORD;
+ pos += BITS_IN_EWORD;
}
}
}
if (number == 0)
return 0;
- self->bit_size += number * BITS_IN_WORD;
+ self->bit_size += number * BITS_IN_EWORD;
return add_empty_words(self, v, number);
}
self->buffer_size += can_add;
}
- self->bit_size += can_add * BITS_IN_WORD;
+ self->bit_size += can_add * BITS_IN_EWORD;
if (number - can_add == 0)
break;
size_t ewah_add(struct ewah_bitmap *self, eword_t word)
{
- self->bit_size += BITS_IN_WORD;
+ self->bit_size += BITS_IN_EWORD;
if (word == 0)
return add_empty_word(self, 0);
void ewah_set(struct ewah_bitmap *self, size_t i)
{
const size_t dist =
- (i + BITS_IN_WORD) / BITS_IN_WORD -
- (self->bit_size + BITS_IN_WORD - 1) / BITS_IN_WORD;
+ (i + BITS_IN_EWORD) / BITS_IN_EWORD -
+ (self->bit_size + BITS_IN_EWORD - 1) / BITS_IN_EWORD;
assert(i >= self->bit_size);
if (dist > 1)
add_empty_words(self, 0, dist - 1);
- add_literal(self, (eword_t)1 << (i % BITS_IN_WORD));
+ add_literal(self, (eword_t)1 << (i % BITS_IN_EWORD));
return;
}
if (rlw_get_literal_words(self->rlw) == 0) {
rlw_set_running_len(self->rlw,
rlw_get_running_len(self->rlw) - 1);
- add_literal(self, (eword_t)1 << (i % BITS_IN_WORD));
+ add_literal(self, (eword_t)1 << (i % BITS_IN_EWORD));
return;
}
self->buffer[self->buffer_size - 1] |=
- ((eword_t)1 << (i % BITS_IN_WORD));
+ ((eword_t)1 << (i % BITS_IN_EWORD));
/* check if we just completed a stream of 1s */
if (self->buffer[self->buffer_size - 1] == (eword_t)(~0)) {
eword_t *word = &self->buffer[pointer];
if (rlw_get_run_bit(word)) {
- size_t len = rlw_get_running_len(word) * BITS_IN_WORD;
+ size_t len = rlw_get_running_len(word) * BITS_IN_EWORD;
for (k = 0; k < len; ++k, ++pos)
callback(pos, payload);
} else {
- pos += rlw_get_running_len(word) * BITS_IN_WORD;
+ pos += rlw_get_running_len(word) * BITS_IN_EWORD;
}
++pointer;
int c;
/* todo: zero count optimization */
- for (c = 0; c < BITS_IN_WORD; ++c, ++pos) {
+ for (c = 0; c < BITS_IN_EWORD; ++c, ++pos) {
if ((self->buffer[pointer] & ((eword_t)1 << c)) != 0)
callback(pos, payload);
}
*/
#include "git-compat-util.h"
#include "ewok.h"
+#include "strbuf.h"
int ewah_serialize_native(struct ewah_bitmap *self, int fd)
{
return ewah_serialize_to(self, write_helper, (void *)(intptr_t)fd);
}
+static int write_strbuf(void *user_data, const void *data, size_t len)
+{
+ struct strbuf *sb = user_data;
+ strbuf_add(sb, data, len);
+ return len;
+}
+
+int ewah_serialize_strbuf(struct ewah_bitmap *self, struct strbuf *sb)
+{
+ return ewah_serialize_to(self, write_strbuf, sb);
+}
+
int ewah_read_mmap(struct ewah_bitmap *self, const void *map, size_t len)
{
const uint8_t *ptr = map;
# define ewah_calloc xcalloc
#endif
+struct strbuf;
typedef uint64_t eword_t;
-#define BITS_IN_WORD (sizeof(eword_t) * 8)
+#define BITS_IN_EWORD (sizeof(eword_t) * 8)
/**
* Do not use __builtin_popcountll. The GCC implementation
void *out);
int ewah_serialize(struct ewah_bitmap *self, int fd);
int ewah_serialize_native(struct ewah_bitmap *self, int fd);
+int ewah_serialize_strbuf(struct ewah_bitmap *self, struct strbuf *);
int ewah_deserialize(struct ewah_bitmap *self, int fd);
int ewah_read_mmap(struct ewah_bitmap *self, const void *map, size_t len);
static void write_crash_report(const char *err)
{
- char *loc = git_path("fast_import_crash_%"PRIuMAX, (uintmax_t) getpid());
+ const char *loc = git_path("fast_import_crash_%"PRIuMAX, (uintmax_t) getpid());
FILE *rpt = fopen(loc, "w");
struct branch *b;
unsigned long lu;
static char* make_fast_import_path(const char *path)
{
- struct strbuf abs_path = STRBUF_INIT;
-
if (!relative_marks_paths || is_absolute_path(path))
return xstrdup(path);
- strbuf_addf(&abs_path, "%s/info/fast-import/%s", get_git_dir(), path);
- return strbuf_detach(&abs_path, NULL);
+ return xstrdup(git_path("info/fast-import/%s", path));
}
static void option_import_marks(const char *marks,
#define MAX_IN_VAIN 256
static struct prio_queue rev_list = { compare_commits_by_commit_date };
-static int non_common_revs, multi_ack, use_sideband, allow_tip_sha1_in_want;
+static int non_common_revs, multi_ack, use_sideband;
+/* Allow specifying sha1 if it is a ref tip. */
+#define ALLOW_TIP_SHA1 01
+/* Allow request of a sha1 if it is reachable from a ref (possibly hidden ref). */
+#define ALLOW_REACHABLE_SHA1 02
+static unsigned int allow_unadvertised_object_request;
static void rev_list_push(struct commit *commit, int mark)
{
}
}
-static int rev_list_insert_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+static int rev_list_insert_ref(const char *refname, const unsigned char *sha1)
{
struct object *o = deref_tag(parse_object(sha1), refname, 0);
return 0;
}
-static int clear_marks(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+static int rev_list_insert_ref_oid(const char *refname, const struct object_id *oid,
+ int flag, void *cb_data)
{
- struct object *o = deref_tag(parse_object(sha1), refname, 0);
+ return rev_list_insert_ref(refname, oid->hash);
+}
+
+static int clear_marks(const char *refname, const struct object_id *oid,
+ int flag, void *cb_data)
+{
+ struct object *o = deref_tag(parse_object(oid->hash), refname, 0);
if (o && o->type == OBJ_COMMIT)
clear_commit_marks((struct commit *)o,
static void insert_one_alternate_ref(const struct ref *ref, void *unused)
{
- rev_list_insert_ref(NULL, ref->old_sha1, 0, NULL);
+ rev_list_insert_ref(NULL, ref->old_sha1);
}
#define INITIAL_FLUSH 16
for_each_ref(clear_marks, NULL);
marked = 1;
- for_each_ref(rev_list_insert_ref, NULL);
+ for_each_ref(rev_list_insert_ref_oid, NULL);
for_each_alternate_ref(insert_one_alternate_ref, NULL);
fetching = 0;
static struct commit_list *complete;
-static int mark_complete(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+static int mark_complete(const unsigned char *sha1)
{
struct object *o = parse_object(sha1);
return 0;
}
+static int mark_complete_oid(const char *refname, const struct object_id *oid,
+ int flag, void *cb_data)
+{
+ return mark_complete(oid->hash);
+}
+
static void mark_recent_complete_commits(struct fetch_pack_args *args,
unsigned long cutoff)
{
}
/* Append unmatched requests to the list */
- if (allow_tip_sha1_in_want) {
+ if ((allow_unadvertised_object_request &
+ (ALLOW_TIP_SHA1 | ALLOW_REACHABLE_SHA1))) {
for (i = 0; i < nr_sought; i++) {
unsigned char sha1[20];
static void mark_alternate_complete(const struct ref *ref, void *unused)
{
- mark_complete(NULL, ref->old_sha1, 0, NULL);
+ mark_complete(ref->old_sha1);
}
static int everything_local(struct fetch_pack_args *args,
}
if (!args->depth) {
- for_each_ref(mark_complete, NULL);
+ for_each_ref(mark_complete_oid, NULL);
for_each_alternate_ref(mark_alternate_complete, NULL);
commit_list_sort_by_date(&complete);
if (cutoff)
sort_ref_list(&ref, ref_compare_name);
qsort(sought, nr_sought, sizeof(*sought), cmp_ref_by_name);
- if (is_repository_shallow() && !server_supports("shallow"))
+ if ((args->depth > 0 || is_repository_shallow()) && !server_supports("shallow"))
die("Server does not support shallow clients");
if (server_supports("multi_ack_detailed")) {
if (args->verbose)
if (server_supports("allow-tip-sha1-in-want")) {
if (args->verbose)
fprintf(stderr, "Server supports allow-tip-sha1-in-want\n");
- allow_tip_sha1_in_want = 1;
+ allow_unadvertised_object_request |= ALLOW_TIP_SHA1;
+ }
+ if (server_supports("allow-reachable-sha1-in-want")) {
+ if (args->verbose)
+ fprintf(stderr, "Server supports allow-reachable-sha1-in-want\n");
+ allow_unadvertised_object_request |= ALLOW_REACHABLE_SHA1;
}
if (!server_supports("thin-pack"))
args->use_thin_pack = 0;
--- /dev/null
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+print <<"EOT";
+/* Automatically generated by $0 */
+
+struct cmdname_help {
+ char name[16];
+ char help[80];
+ unsigned char group;
+};
+
+static char *common_cmd_groups[] = {
+EOT
+
+my $n = 0;
+my %grp;
+while (<>) {
+ last if /^### command list/;
+ next if (1../^### common groups/) || /^#/ || /^\s*$/;
+ chop;
+ my ($k, $v) = split ' ', $_, 2;
+ $grp{$k} = $n++;
+ print "\tN_(\"$v\"),\n";
+}
+
+print "};\n\nstatic struct cmdname_help common_cmds[] = {\n";
+
+while (<>) {
+ next if /^#/ || /^\s*$/;
+ my @tags = split;
+ my $cmd = shift @tags;
+ for my $t (@tags) {
+ if (exists $grp{$t}) {
+ my $s;
+ open my $f, '<', "Documentation/$cmd.txt" or die;
+ while (<$f>) {
+ ($s) = /^$cmd - (.+)$/;
+ last if $s;
+ }
+ close $f;
+ $cmd =~ s/^git-//;
+ print "\t{\"$cmd\", N_(\"$s\"), $grp{$t}},\n";
+ last;
+ }
+ }
+}
+
+print "};\n";
+++ /dev/null
-#!/bin/sh
-
-echo "/* Automatically generated by $0 */
-struct cmdname_help {
- char name[16];
- char help[80];
-};
-
-static struct cmdname_help common_cmds[] = {"
-
-sed -n -e 's/^git-\([^ ]*\)[ ].* common.*/\1/p' command-list.txt |
-sort |
-while read cmd
-do
- sed -n '
- /^NAME/,/git-'"$cmd"'/H
- ${
- x
- s/.*git-'"$cmd"' - \(.*\)/ {"'"$cmd"'", N_("\1")},/
- p
- }' "Documentation/git-$cmd.txt"
-done
-echo "};"
cmdline="$cmdline -3"
fi
+empty_tree=4b825dc642cb6eb9a060e54bf8d69288fbee4904
+
sq () {
git rev-parse --sq-quote "$@"
}
return 1
fi
- if ! test -s "$dotest/abort-safety"
+ if ! test -f "$dotest/abort-safety"
then
return 0
fi
then
GIT_MERGE_VERBOSITY=0 && export GIT_MERGE_VERBOSITY
fi
- git-merge-recursive $orig_tree -- HEAD $his_tree || {
+ our_tree=$(git rev-parse --verify -q HEAD || echo $empty_tree)
+ git-merge-recursive $orig_tree -- $our_tree $his_tree || {
git rerere $allow_rerere_autoupdate
die "$(gettext "Failed to merge in the changes.")"
}
ignore_date=
allow_rerere_autoupdate=
gpg_sign_opt=
+threeway=
if test "$(git config --bool --get am.messageid)" = true
then
keepcr=t
fi
+if test "$(git config --bool --get am.threeWay)" = true
+then
+ threeway=t
+fi
+
while test $# != 0
do
case "$1" in
;;
-3|--3way)
threeway=t ;;
+ --no-3way)
+ threeway=f ;;
-s|--signoff)
sign=t ;;
-u|--utf8)
;;
t,)
git rerere clear
- git read-tree --reset -u HEAD HEAD
- orig_head=$(cat "$GIT_DIR/ORIG_HEAD")
- git reset HEAD
- git update-ref ORIG_HEAD $orig_head
+ head_tree=$(git rev-parse --verify -q HEAD || echo $empty_tree) &&
+ git read-tree --reset -u $head_tree $head_tree &&
+ index_tree=$(git write-tree) &&
+ git read-tree -m -u $index_tree $head_tree
+ git read-tree $head_tree
;;
,t)
if test -f "$dotest/rebasing"
git rerere clear
if safe_to_abort
then
- git read-tree --reset -u HEAD ORIG_HEAD
- git reset ORIG_HEAD
+ head_tree=$(git rev-parse --verify -q HEAD || echo $empty_tree) &&
+ git read-tree --reset -u $head_tree $head_tree &&
+ index_tree=$(git write-tree) &&
+ orig_head=$(git rev-parse --verify -q ORIG_HEAD || echo $empty_tree) &&
+ git read-tree -m -u $index_tree $orig_head
+ if git rev-parse --verify -q ORIG_HEAD >/dev/null 2>&1
+ then
+ git reset ORIG_HEAD
+ else
+ git read-tree $empty_tree
+ curr_branch=$(git symbolic-ref HEAD 2>/dev/null) &&
+ git update-ref -d $curr_branch
+ fi
fi
rm -fr "$dotest"
exit ;;
if test "$(cat "$dotest/threeway")" = t
then
threeway=t
+else
+ threeway=f
fi
git_apply_opt=$(cat "$dotest/apply-opt")
if test "$(cat "$dotest/sign")" = t
continue
fi
- if test -x "$GIT_DIR"/hooks/applypatch-msg
+ hook="$(git rev-parse --git-path hooks/applypatch-msg)"
+ if test -x "$hook"
then
- "$GIT_DIR"/hooks/applypatch-msg "$dotest/final-commit" ||
- stop_here $this
+ "$hook" "$dotest/final-commit" || stop_here $this
fi
if test -f "$dotest/final-commit"
stop_here_user_resolve $this
fi
- if test -x "$GIT_DIR"/hooks/pre-applypatch
+ hook="$(git rev-parse --git-path hooks/pre-applypatch)"
+ if test -x "$hook"
then
- "$GIT_DIR"/hooks/pre-applypatch || stop_here $this
+ "$hook" || stop_here $this
fi
tree=$(git write-tree) &&
echo "$(cat "$dotest/original-commit") $commit" >> "$dotest/rewritten"
fi
- if test -x "$GIT_DIR"/hooks/post-applypatch
- then
- "$GIT_DIR"/hooks/post-applypatch
- fi
+ hook="$(git rev-parse --git-path hooks/post-applypatch)"
+ test -x "$hook" && "$hook"
go_next
done
if test -s "$dotest"/rewritten; then
git notes copy --for-rewrite=rebase < "$dotest"/rewritten
- if test -x "$GIT_DIR"/hooks/post-rewrite; then
- "$GIT_DIR"/hooks/post-rewrite rebase < "$dotest"/rewritten
+ hook="$(git rev-parse --git-path hooks/post-rewrite)"
+ if test -x "$hook"; then
+ "$hook" rebase < "$dotest"/rewritten
fi
fi
#define BUILD_ASSERT_OR_ZERO(cond) \
(sizeof(char [1 - 2*!(cond)]) - 1)
-#if defined(__GNUC__) && (__GNUC__ >= 3)
-# if GIT_GNUC_PREREQ(3, 1)
+#if GIT_GNUC_PREREQ(3, 1)
/* &arr[0] degrades to a pointer: a different type from an array */
# define BARF_UNLESS_AN_ARRAY(arr) \
BUILD_ASSERT_OR_ZERO(!__builtin_types_compatible_p(__typeof__(arr), \
__typeof__(&(arr)[0])))
-# else
-# define BARF_UNLESS_AN_ARRAY(arr) 0
-# endif
+#else
+# define BARF_UNLESS_AN_ARRAY(arr) 0
#endif
/*
* ARRAY_SIZE - get the number of elements in a visible array
#elif defined(_MSC_VER)
#include "compat/msvc.h"
#else
+#include <sys/utsname.h>
#include <sys/wait.h>
#include <sys/resource.h>
#include <sys/socket.h>
extern void *xrealloc(void *ptr, size_t size);
extern void *xcalloc(size_t nmemb, size_t size);
extern void *xmmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
+extern void *xmmap_gently(void *start, size_t length, int prot, int flags, int fd, off_t offset);
extern ssize_t xread(int fd, void *buf, size_t len);
extern ssize_t xwrite(int fd, const void *buf, size_t len);
extern ssize_t xpread(int fd, void *buf, size_t len, off_t offset);
# define SHELL_PATH "/bin/sh"
#endif
+#ifndef _POSIX_THREAD_SAFE_FUNCTIONS
+#define flockfile(fh)
+#define funlockfile(fh)
+#define getc_unlocked(fh) getc(fh)
+#endif
+
#endif
# (See check_refname_component in refs.c.)
1 while $xtag =~ s/
(?: \.\. # Tag cannot contain '..'.
- | \@{ # Tag cannot contain '@{'.
+ | \@\{ # Tag cannot contain '@{'.
| ^ - # Tag cannot begin with '-'.
| \.lock $ # Tag cannot end with '.lock'.
| ^ \. # Tag cannot begin...
die "Fast-forward update failed: $?\n" if $?;
}
else {
- system(qw(git merge cvsimport HEAD), "$remote/$opt_o");
+ system(qw(git merge -m cvsimport), "$remote/$opt_o");
die "Could not merge $opt_o into the current branch.\n" if $?;
}
} else {
: ${MERGE_TOOLS_DIR=$(git --exec-path)/mergetools}
+IFS='
+'
+
mode_ok () {
if diff_mode
then
EOF
# Loop over each candidate and stop when a valid merge tool is found.
+ IFS=' '
for tool in $tools
do
is_available "$tool" && echo "$tool" && return 0
printf "Merging:\n"
printf "%s\n" "$files"
-IFS='
-'
rc=0
for i in $files
do
# Only labels/tags matching this will be imported/exported
defaultLabelRegexp = r'[a-zA-Z0-9_\-.]+$'
+# Grab changes in blocks of this many revisions, unless otherwise requested
+defaultBlockSize = 512
+
def p4_build_cmd(cmd):
"""Build a suitable p4 command line.
def p4_move(src, dest):
p4_system(["move", "-k", wildcard_encode(src), wildcard_encode(dest)])
+def p4_last_change():
+ results = p4CmdList(["changes", "-m", "1"])
+ return int(results[0]['change'])
+
def p4_describe(change):
"""Make sure it returns a valid result by checking for
the presence of field "time". Return a dict of the
# Returns the perforce file type for the given file.
result = p4_read_pipe(["opened", wildcard_encode(file)])
- match = re.match(".*\((.+)\)\r?$", result)
+ match = re.match(".*\((.+)\)( \*exclusive\*)?\r?$", result)
if match:
return match.group(1)
else:
def p4Where(depotPath):
if not depotPath.endswith("/"):
depotPath += "/"
- depotPath = depotPath + "..."
- outputList = p4CmdList(["where", depotPath])
+ depotPathLong = depotPath + "..."
+ outputList = p4CmdList(["where", depotPathLong])
output = None
for entry in outputList:
if "depotFile" in entry:
- if entry["depotFile"] == depotPath:
+ # Search for the base client side depot path, as long as it starts with the branch's P4 path.
+ # The base path always ends with "/...".
+ if entry["depotFile"].find(depotPath) == 0 and entry["depotFile"][-4:] == "/...":
output = entry
break
elif "data" in entry:
def originP4BranchesExist():
return gitBranchExists("origin") or gitBranchExists("origin/p4") or gitBranchExists("origin/p4/master")
-def p4ChangesForPaths(depotPaths, changeRange):
+
+def p4ParseNumericChangeRange(parts):
+ changeStart = int(parts[0][1:])
+ if parts[1] == '#head':
+ changeEnd = p4_last_change()
+ else:
+ changeEnd = int(parts[1])
+
+ return (changeStart, changeEnd)
+
+def chooseBlockSize(blockSize):
+ if blockSize:
+ return blockSize
+ else:
+ return defaultBlockSize
+
+def p4ChangesForPaths(depotPaths, changeRange, requestedBlockSize):
assert depotPaths
- cmd = ['changes']
- for p in depotPaths:
- cmd += ["%s...%s" % (p, changeRange)]
- output = p4_read_pipe_lines(cmd)
+ # Parse the change range into start and end. Try to find integer
+ # revision ranges as these can be broken up into blocks to avoid
+ # hitting server-side limits (maxrows, maxscanresults). But if
+ # that doesn't work, fall back to using the raw revision specifier
+ # strings, without using block mode.
+
+ if changeRange is None or changeRange == '':
+ changeStart = 1
+ changeEnd = p4_last_change()
+ block_size = chooseBlockSize(requestedBlockSize)
+ else:
+ parts = changeRange.split(',')
+ assert len(parts) == 2
+ try:
+ (changeStart, changeEnd) = p4ParseNumericChangeRange(parts)
+ block_size = chooseBlockSize(requestedBlockSize)
+ except:
+ changeStart = parts[0][1:]
+ changeEnd = parts[1]
+ if requestedBlockSize:
+ die("cannot use --changes-block-size with non-numeric revisions")
+ block_size = None
+
+ # Accumulate change numbers in a dictionary to avoid duplicates
changes = {}
- for line in output:
- changeNum = int(line.split(" ")[1])
- changes[changeNum] = True
+
+ for p in depotPaths:
+ # Retrieve changes a block at a time, to prevent running
+ # into a MaxResults/MaxScanRows error from the server.
+
+ while True:
+ cmd = ['changes']
+
+ if block_size:
+ end = min(changeEnd, changeStart + block_size)
+ revisionRange = "%d,%d" % (changeStart, end)
+ else:
+ revisionRange = "%s,%s" % (changeStart, changeEnd)
+
+ cmd += ["%s...@%s" % (p, revisionRange)]
+
+ for line in p4_read_pipe_lines(cmd):
+ changeNum = int(line.split(" ")[1])
+ changes[changeNum] = True
+
+ if not block_size:
+ break
+
+ if end >= changeEnd:
+ break
+
+ changeStart = end + 1
changelist = changes.keys()
changelist.sort()
editor = os.environ.get("P4EDITOR")
else:
editor = read_pipe("git var GIT_EDITOR").strip()
- system([editor, template_file])
+ system(["sh", "-c", ('%s "$@"' % editor), editor, template_file])
# If the file was not saved, prompt to see if this patch should
# be skipped. But skip this verification step if configured so.
if self.useClientSpec:
self.clientSpecDirs = getClientSpec()
- if self.useClientSpec:
+ # Check for the existance of P4 branches
+ branchesDetected = (len(p4BranchesInGit().keys()) > 1)
+
+ if self.useClientSpec and not branchesDetected:
# all files are relative to the client spec
self.clientPath = getClientRoot()
else:
optparse.make_option("--import-labels", dest="importLabels", action="store_true"),
optparse.make_option("--import-local", dest="importIntoRemotes", action="store_false",
help="Import into refs/heads/ , not refs/remotes"),
- optparse.make_option("--max-changes", dest="maxChanges"),
+ optparse.make_option("--max-changes", dest="maxChanges",
+ help="Maximum number of changes to import"),
+ optparse.make_option("--changes-block-size", dest="changes_block_size", type="int",
+ help="Internal block size to use when iteratively calling p4 changes"),
optparse.make_option("--keep-path", dest="keepRepoPath", action='store_true',
help="Keep entire BRANCH/DIR/SUBDIR prefix during import"),
optparse.make_option("--use-client-spec", dest="useClientSpec", action='store_true',
self.syncWithOrigin = True
self.importIntoRemotes = True
self.maxChanges = ""
+ self.changes_block_size = None
self.keepRepoPath = False
self.depotPaths = None
self.p4BranchesInGit = []
# them back too. This is not needed to the cygwin windows version,
# just the native "NT" type.
#
- text = p4_read_pipe(['print', '-q', '-o', '-', file['depotFile']])
+ text = p4_read_pipe(['print', '-q', '-o', '-', "%s@%s" % (file['depotFile'], file['change']) ])
if p4_version_string().find("/NT") >= 0:
text = text.replace("\r\n", "\n")
contents = [ text ]
branchPrefix = self.depotPaths[0] + branch + "/"
range = "@1,%s" % maxChange
#print "prefix" + branchPrefix
- changes = p4ChangesForPaths([branchPrefix], range)
+ changes = p4ChangesForPaths([branchPrefix], range, self.changes_block_size)
if len(changes) <= 0:
return False
firstChange = changes[0]
if self.verbose:
print "Getting p4 changes for %s...%s" % (', '.join(self.depotPaths),
self.changeRange)
- changes = p4ChangesForPaths(self.depotPaths, self.changeRange)
+ changes = p4ChangesForPaths(self.depotPaths, self.changeRange, self.changes_block_size)
if len(self.maxChanges) > 0:
changes = changes[:min(int(self.maxChanges), len(changes))]
#
# Fetch one or more remote refs and merge it/them into the current HEAD.
-USAGE='[-n | --no-stat] [--[no-]commit] [--[no-]squash] [--[no-]ff|--ff-only] [--[no-]rebase|--rebase=preserve] [-s strategy]... [<fetch-options>] <repo> <head>...'
-LONG_USAGE='Fetch one or more remote refs and integrate it/them with the current HEAD.'
SUBDIRECTORY_OK=Yes
-OPTIONS_SPEC=
+OPTIONS_KEEPDASHDASH=
+OPTIONS_STUCKLONG=Yes
+OPTIONS_SPEC="\
+git pull [options] [<repository> [<refspec>...]]
+
+Fetch one or more remote refs and integrate it/them with the current HEAD.
+--
+v,verbose be more verbose
+q,quiet be more quiet
+progress force progress reporting
+
+ Options related to merging
+r,rebase?false|true|preserve incorporate changes by rebasing rather than merging
+n! do not show a diffstat at the end of the merge
+stat show a diffstat at the end of the merge
+summary (synonym to --stat)
+log?n add (at most <n>) entries from shortlog to merge commit message
+squash create a single commit instead of doing a merge
+commit perform a commit if the merge succeeds (default)
+e,edit edit message before committing
+ff allow fast-forward
+ff-only! abort if fast-forward is not possible
+verify-signatures verify that the named commit has a valid GPG signature
+s,strategy=strategy merge strategy to use
+X,strategy-option=option option for selected merge strategy
+S,gpg-sign?key-id GPG sign commit
+
+ Options related to fetching
+all fetch from all remotes
+a,append append to .git/FETCH_HEAD instead of overwriting
+upload-pack=path path to upload pack on remote end
+f,force force overwrite of local branch
+t,tags fetch all tags and associated objects
+p,prune prune remote-tracking branches no longer on remote
+recurse-submodules?on-demand control recursive fetching of submodules
+dry-run dry run
+k,keep keep downloaded pack
+depth=depth deepen history of shallow clone
+unshallow convert to a complete repository
+update-shallow accept refs that update .git/shallow
+refmap=refmap specify fetch refmap
+"
+test $# -gt 0 && args="$*"
. git-sh-setup
. git-sh-i18n
-set_reflog_action "pull${1+ $*}"
+set_reflog_action "pull${args+ $args}"
require_work_tree_exists
cd_to_toplevel
strategy_args= diffstat= no_commit= squash= no_ff= ff_only=
log_arg= verbosity= progress= recurse_submodules= verify_signatures=
-merge_args= edit= rebase_args=
+merge_args= edit= rebase_args= all= append= upload_pack= force= tags= prune=
+keep= depth= unshallow= update_shallow= refmap=
curr_branch=$(git symbolic-ref -q HEAD)
curr_branch_short="${curr_branch#refs/heads/}"
rebase=$(bool_or_string_config branch.$curr_branch_short.rebase)
diffstat=--stat ;;
--log|--log=*|--no-log)
log_arg="$1" ;;
- --no-c|--no-co|--no-com|--no-comm|--no-commi|--no-commit)
+ --no-commit)
no_commit=--no-commit ;;
- --c|--co|--com|--comm|--commi|--commit)
+ --commit)
no_commit=--commit ;;
-e|--edit)
edit=--edit ;;
--no-edit)
edit=--no-edit ;;
- --sq|--squ|--squa|--squas|--squash)
+ --squash)
squash=--squash ;;
- --no-sq|--no-squ|--no-squa|--no-squas|--no-squash)
+ --no-squash)
squash=--no-squash ;;
--ff)
no_ff=--ff ;;
no_ff=--no-ff ;;
--ff-only)
ff_only=--ff-only ;;
- -s=*|--s=*|--st=*|--str=*|--stra=*|--strat=*|--strate=*|\
- --strateg=*|--strategy=*|\
- -s|--s|--st|--str|--stra|--strat|--strate|--strateg|--strategy)
- case "$#,$1" in
- *,*=*)
- strategy=$(expr "z$1" : 'z-[^=]*=\(.*\)') ;;
- 1,*)
- usage ;;
- *)
- strategy="$2"
- shift ;;
- esac
- strategy_args="${strategy_args}-s $strategy "
+ -s*|--strategy=*)
+ strategy_args="$strategy_args $1"
;;
- -X*)
- case "$#,$1" in
- 1,-X)
- usage ;;
- *,-X)
- xx="-X $(git rev-parse --sq-quote "$2")"
- shift ;;
- *,*)
- xx=$(git rev-parse --sq-quote "$1") ;;
- esac
- merge_args="$merge_args$xx "
+ -X*|--strategy-option=*)
+ merge_args="$merge_args $(git rev-parse --sq-quote "$1")"
;;
- -r=*|--r=*|--re=*|--reb=*|--reba=*|--rebas=*|--rebase=*)
+ -r*|--rebase=*)
rebase="${1#*=}"
;;
- -r|--r|--re|--reb|--reba|--rebas|--rebase)
+ --rebase)
rebase=true
;;
- --no-r|--no-re|--no-reb|--no-reba|--no-rebas|--no-rebase)
+ --no-rebase)
rebase=false
;;
--recurse-submodules)
-S*)
gpg_sign_args=$(git rev-parse --sq-quote "$1")
;;
- --d|--dr|--dry|--dry-|--dry-r|--dry-ru|--dry-run)
+ --dry-run)
dry_run=--dry-run
;;
+ --all|--no-all)
+ all=$1 ;;
+ -a|--append|--no-append)
+ append=$1 ;;
+ --upload-pack=*|--no-upload-pack)
+ upload_pack=$1 ;;
+ -f|--force|--no-force)
+ force="$force $1" ;;
+ -t|--tags|--no-tags)
+ tags=$1 ;;
+ -p|--prune|--no-prune)
+ prune=$1 ;;
+ -k|--keep|--no-keep)
+ keep=$1 ;;
+ --depth=*|--no-depth)
+ depth=$1 ;;
+ --unshallow|--no-unshallow)
+ unshallow=$1 ;;
+ --update-shallow|--no-update-shallow)
+ update_shallow=$1 ;;
+ --refmap=*|--no-refmap)
+ refmap=$1 ;;
-h|--help-all)
usage
;;
- *)
- # Pass thru anything that may be meant for fetch.
+ --)
+ shift
break
;;
+ *)
+ usage
+ ;;
esac
shift
done
error_on_no_merge_candidates () {
exec >&2
- for opt
- do
- case "$opt" in
- -t|--t|--ta|--tag|--tags)
- echo "It doesn't make sense to pull all tags; you probably meant:"
- echo " git fetch --tags"
- exit 1
- esac
- done
if test true = "$rebase"
then
if ! git rev-parse -q --verify HEAD >/dev/null
then
# On an unborn branch
- if test -f "$GIT_DIR/index"
+ if test -f "$(git rev-parse --git-path index)"
then
die "$(gettext "updating an unborn branch with changes added to the index")"
fi
oldremoteref=$(git merge-base --fork-point "$remoteref" $curr_branch 2>/dev/null)
}
orig_head=$(git rev-parse -q --verify HEAD)
-git fetch $verbosity $progress $dry_run $recurse_submodules --update-head-ok "$@" || exit 1
+git fetch $verbosity $progress $dry_run $recurse_submodules $all $append \
+$upload_pack $force $tags $prune $keep $depth $unshallow $update_shallow \
+$refmap --update-head-ok "$@" || exit 1
test -z "$dry_run" || exit 0
curr_head=$(git rev-parse -q --verify HEAD)
fi
fi
-merge_name=$(git fmt-merge-msg $log_arg <"$GIT_DIR/FETCH_HEAD") || exit
case "$rebase" in
true)
eval="git-rebase $diffstat $strategy_args $merge_args $rebase_args $verbosity"
eval="git-merge $diffstat $no_commit $verify_signatures $edit $squash $no_ff $ff_only"
eval="$eval $log_arg $strategy_args $merge_args $verbosity $progress"
eval="$eval $gpg_sign_args"
- eval="$eval \"\$merge_name\" HEAD $merge_head"
+ eval="$eval FETCH_HEAD"
;;
esac
eval "exec $eval"
}
do_next () {
- rm -f "$msg" "$author_script" "$amend" || exit
+ rm -f "$msg" "$author_script" "$amend" "$state_dir"/stopped-sha || exit
read -r command sha1 rest < "$todo"
case "$command" in
"$comment_char"*|''|noop)
read -r command rest < "$todo"
mark_action_done
printf 'Executing: %s\n' "$rest"
- # "exec" command doesn't take a sha1 in the todo-list.
- # => can't just use $sha1 here.
- git rev-parse --verify HEAD > "$state_dir"/stopped-sha
${SHELL:-@SHELL_PATH@} -c "$rest" # Actual execution
status=$?
# Run in subshell because require_clean_work_tree can die.
git notes copy --for-rewrite=rebase < "$rewritten_list" ||
true # we don't care if this copying failed
} &&
- if test -x "$GIT_DIR"/hooks/post-rewrite &&
- test -s "$rewritten_list"; then
- "$GIT_DIR"/hooks/post-rewrite rebase < "$rewritten_list"
+ hook="$(git rev-parse --git-path hooks/post-rewrite)"
+ if test -x "$hook" && test -s "$rewritten_list"; then
+ "$hook" rebase < "$rewritten_list"
true # we don't care if this hook failed
fi &&
warn "Successfully rebased and updated $head_name."
fi
fi
- record_in_rewritten "$(cat "$state_dir"/stopped-sha)"
+ if test -r "$state_dir"/stopped-sha
+ then
+ record_in_rewritten "$(cat "$state_dir"/stopped-sha)"
+ fi
require_clean_work_tree "rebase"
do_rest
if test -s "$state_dir"/rewritten
then
git notes copy --for-rewrite=rebase <"$state_dir"/rewritten
- if test -x "$GIT_DIR"/hooks/post-rewrite
- then
- "$GIT_DIR"/hooks/post-rewrite rebase <"$state_dir"/rewritten
- fi
+ hook="$(git rev-parse --git-path hooks/post-rewrite)"
+ test -x "$hook" && "$hook" rebase <"$state_dir"/rewritten
fi
say All done.
}
run_pre_rebase_hook () {
if test -z "$ok_to_skip_pre_rebase" &&
- test -x "$GIT_DIR/hooks/pre-rebase"
+ test -x "$(git rev-parse --git-path hooks/pre-rebase)"
then
- "$GIT_DIR/hooks/pre-rebase" ${1+"$@"} ||
+ "$(git rev-parse --git-path hooks/pre-rebase)" ${1+"$@"} ||
die "$(gettext "The pre-rebase hook refused to rebase.")"
fi
}
}
my %aliases;
+
+sub parse_sendmail_alias {
+ local $_ = shift;
+ if (/"/) {
+ print STDERR "warning: sendmail alias with quotes is not supported: $_\n";
+ } elsif (/:include:/) {
+ print STDERR "warning: `:include:` not supported: $_\n";
+ } elsif (/[\/|]/) {
+ print STDERR "warning: `/file` or `|pipe` redirection not supported: $_\n";
+ } elsif (/^(\S+?)\s*:\s*(.+)$/) {
+ my ($alias, $addr) = ($1, $2);
+ $aliases{$alias} = [ split_addrs($addr) ];
+ } else {
+ print STDERR "warning: sendmail line is not recognized: $_\n";
+ }
+}
+
+sub parse_sendmail_aliases {
+ my $fh = shift;
+ my $s = '';
+ while (<$fh>) {
+ chomp;
+ next if /^\s*$/ || /^\s*#/;
+ $s .= $_, next if $s =~ s/\\$// || s/^\s+//;
+ parse_sendmail_alias($s) if $s;
+ $s = $_;
+ }
+ $s =~ s/\\$//; # silently tolerate stray '\' on last line
+ parse_sendmail_alias($s) if $s;
+}
+
my %parse_alias = (
# multiline formats can be supported in the future
mutt => sub { my $fh = shift; while (<$fh>) {
$aliases{$alias} = [ split_addrs($addr) ];
}
} },
-
+ sendmail => \&parse_sendmail_aliases,
gnus => sub { my $fh = shift; while (<$fh>) {
if (/\(define-mail-alias\s+"(\S+?)"\s+"(\S+?)"\)/) {
$aliases{$1} = [ $2 ];
echo >&2 "Unable to determine absolute path of git directory"
exit 1
}
- : ${GIT_OBJECT_DIRECTORY="$GIT_DIR/objects"}
+ : ${GIT_OBJECT_DIRECTORY="$(git rev-parse --git-path objects)"}
}
if test -z "$NONGIT_OK"
cd_to_toplevel
TMP="$GIT_DIR/.git-stash.$$"
-TMPindex=${GIT_INDEX_FILE-"$GIT_DIR/index"}.stash.$$
+TMPindex=${GIT_INDEX_FILE-"$(git rev-parse --git-path index)"}.stash.$$
trap 'rm -f "$TMP-"* "$TMPindex"' 0
ref_stash=refs/stash
fi
# Make sure the reflog for stash is kept.
- : >>"$GIT_DIR/logs/$ref_stash"
+ : >>"$(git rev-parse --git-path logs/$ref_stash)"
git update-ref -m "$stash_msg" $ref_stash $w_commit
ret=$?
test $ret != 0 && test -z $quiet &&
say "$(gettext "No local changes to save")"
exit 0
fi
- test -f "$GIT_DIR/logs/$ref_stash" ||
+ test -f "$(git rev-parse --git-path logs/$ref_stash)" ||
clear_stash || die "$(gettext "Cannot initialize stash")"
create_stash "$stash_msg" $untracked
assert_stash_like "$@"
git update-index -q --refresh || die "$(gettext "unable to refresh index")"
- git diff-index --cached --quiet --ignore-submodules HEAD -- ||
- die "$(gettext "Cannot apply stash: Your index contains uncommitted changes.")"
# current index state
c_tree=$(git write-tree) ||
{ "check-ignore", cmd_check_ignore, RUN_SETUP | NEED_WORK_TREE },
{ "check-mailmap", cmd_check_mailmap, RUN_SETUP },
{ "check-ref-format", cmd_check_ref_format },
- { "checkout", cmd_checkout, RUN_SETUP | NEED_WORK_TREE },
+ { "checkout", cmd_checkout, RUN_SETUP },
{ "checkout-index", cmd_checkout_index,
RUN_SETUP | NEED_WORK_TREE},
{ "cherry", cmd_cherry, RUN_SETUP },
}
}
+static int cmd_group_cmp(const void *elem1, const void *elem2)
+{
+ const struct cmdname_help *e1 = elem1;
+ const struct cmdname_help *e2 = elem2;
+
+ if (e1->group < e2->group)
+ return -1;
+ if (e1->group > e2->group)
+ return 1;
+ return strcmp(e1->name, e2->name);
+}
+
void list_common_cmds_help(void)
{
int i, longest = 0;
+ int current_grp = -1;
for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
if (longest < strlen(common_cmds[i].name))
longest = strlen(common_cmds[i].name);
}
- puts(_("The most commonly used git commands are:"));
+ qsort(common_cmds, ARRAY_SIZE(common_cmds),
+ sizeof(common_cmds[0]), cmd_group_cmp);
+
+ puts(_("These are common Git commands used in various situations:"));
+
for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
+ if (common_cmds[i].group != current_grp) {
+ printf("\n%s\n", _(common_cmd_groups[common_cmds[i].group]));
+ current_grp = common_cmds[i].group;
+ }
+
printf(" %s ", common_cmds[i].name);
mput_char(' ', longest - strlen(common_cmds[i].name));
puts(_(common_cmds[i].help));
if (autocorrect > 0) {
fprintf_ln(stderr, _("in %0.1f seconds automatically..."),
(float)autocorrect/10.0);
- poll(NULL, 0, autocorrect * 100);
+ sleep_millisec(autocorrect * 100);
}
return assumed;
}
struct string_list *similar_refs;
};
-static int append_similar_ref(const char *refname, const unsigned char *sha1,
+static int append_similar_ref(const char *refname, const struct object_id *oid,
int flags, void *cb_data)
{
struct similar_ref_cb *cb = (struct similar_ref_cb *)(cb_data);
int get_sha1_hex(const char *hex, unsigned char *sha1)
{
int i;
- for (i = 0; i < 20; i++) {
+ for (i = 0; i < GIT_SHA1_RAWSZ; i++) {
unsigned int val;
/*
* hex[1]=='\0' is caught when val is checked below,
return 0;
}
+int get_oid_hex(const char *hex, struct object_id *oid)
+{
+ return get_sha1_hex(hex, oid->hash);
+}
+
char *sha1_to_hex(const unsigned char *sha1)
{
static int bufno;
- static char hexbuffer[4][41];
+ static char hexbuffer[4][GIT_SHA1_HEXSZ + 1];
static const char hex[] = "0123456789abcdef";
char *buffer = hexbuffer[3 & ++bufno], *buf = buffer;
int i;
- for (i = 0; i < 20; i++) {
+ for (i = 0; i < GIT_SHA1_RAWSZ; i++) {
unsigned int val = *sha1++;
*buf++ = hex[val >> 4];
*buf++ = hex[val & 0xf];
return buffer;
}
+
+char *oid_to_hex(const struct object_id *oid)
+{
+ return sha1_to_hex(oid->hash);
+}
exit(1);
}
-static int show_text_ref(const char *name, const unsigned char *sha1,
- int flag, void *cb_data)
+static int show_text_ref(const char *name, const struct object_id *oid,
+ int flag, void *cb_data)
{
const char *name_nons = strip_namespace(name);
struct strbuf *buf = cb_data;
- struct object *o = parse_object(sha1);
+ struct object *o = parse_object(oid->hash);
if (!o)
return 0;
- strbuf_addf(buf, "%s\t%s\n", sha1_to_hex(sha1), name_nons);
+ strbuf_addf(buf, "%s\t%s\n", oid_to_hex(oid), name_nons);
if (o->type == OBJ_TAG) {
o = deref_tag(o, name, 0);
if (!o)
strbuf_release(&buf);
}
-static int show_head_ref(const char *refname, const unsigned char *sha1,
- int flag, void *cb_data)
+static int show_head_ref(const char *refname, const struct object_id *oid,
+ int flag, void *cb_data)
{
struct strbuf *buf = cb_data;
if (flag & REF_ISSYMREF) {
- unsigned char unused[20];
+ struct object_id unused;
const char *target = resolve_ref_unsafe(refname,
RESOLVE_REF_READING,
- unused, NULL);
+ unused.hash, NULL);
const char *target_nons = strip_namespace(target);
strbuf_addf(buf, "ref: %s\n", target_nons);
} else {
- strbuf_addf(buf, "%s\n", sha1_to_hex(sha1));
+ strbuf_addf(buf, "%s\n", oid_to_hex(oid));
}
return 0;
static int curl_ssl_verify = -1;
static int curl_ssl_try;
static const char *ssl_cert;
+static const char *ssl_cipherlist;
#if LIBCURL_VERSION_NUM >= 0x070903
static const char *ssl_key;
#endif
curl_ssl_verify = git_config_bool(var, value);
return 0;
}
+ if (!strcmp("http.sslcipherlist", var))
+ return git_config_string(&ssl_cipherlist, var, value);
if (!strcmp("http.sslcert", var))
return git_config_string(&ssl_cert, var, value);
#if LIBCURL_VERSION_NUM >= 0x070903
if (http_proactive_auth)
init_curl_http_auth(result);
+ if (getenv("GIT_SSL_CIPHER_LIST"))
+ ssl_cipherlist = getenv("GIT_SSL_CIPHER_LIST");
+
+ if (ssl_cipherlist != NULL && *ssl_cipherlist)
+ curl_easy_setopt(result, CURLOPT_SSL_CIPHER_LIST,
+ ssl_cipherlist);
+
if (ssl_cert != NULL)
curl_easy_setopt(result, CURLOPT_SSLCERT, ssl_cert);
if (has_cert_password())
const char *c_meta = diff_get_color(opt->use_color, DIFF_METAINFO);
const char *c_old = diff_get_color(opt->use_color, DIFF_FILE_OLD);
const char *c_new = diff_get_color(opt->use_color, DIFF_FILE_NEW);
- const char *c_plain = diff_get_color(opt->use_color, DIFF_PLAIN);
+ const char *c_context = diff_get_color(opt->use_color, DIFF_CONTEXT);
if (!pair || !diff)
return;
int k;
for (; t_cur < diff->target.ranges[j].start; t_cur++)
print_line(prefix, ' ', t_cur, t_ends, pair->two->data,
- c_plain, c_reset);
+ c_context, c_reset);
for (k = diff->parent.ranges[j].start; k < diff->parent.ranges[j].end; k++)
print_line(prefix, '-', k, p_ends, pair->one->data,
c_old, c_reset);
}
for (; t_cur < t_end; t_cur++)
print_line(prefix, ' ', t_cur, t_ends, pair->two->data,
- c_plain, c_reset);
+ c_context, c_reset);
}
free(p_ends);
die("bad tree object");
if (obj->flags & (UNINTERESTING | SEEN))
return;
- if (parse_tree(tree) < 0) {
+ if (parse_tree_gently(tree, revs->ignore_missing_links) < 0) {
if (revs->ignore_missing_links)
return;
die("bad tree object %s", sha1_to_hex(obj->sha1));
#include "xdiff-interface.h"
#include "run-command.h"
#include "ll-merge.h"
+#include "quote.h"
struct ll_merge_driver;
{
char temp[4][50];
struct strbuf cmd = STRBUF_INIT;
- struct strbuf_expand_dict_entry dict[5];
+ struct strbuf_expand_dict_entry dict[6];
+ struct strbuf path_sq = STRBUF_INIT;
const char *args[] = { NULL, NULL };
int status, fd, i;
struct stat st;
assert(opts);
+ sq_quote_buf(&path_sq, path);
dict[0].placeholder = "O"; dict[0].value = temp[0];
dict[1].placeholder = "A"; dict[1].value = temp[1];
dict[2].placeholder = "B"; dict[2].value = temp[2];
dict[3].placeholder = "L"; dict[3].value = temp[3];
- dict[4].placeholder = NULL; dict[4].value = NULL;
+ dict[4].placeholder = "P"; dict[4].value = path_sq.buf;
+ dict[5].placeholder = NULL; dict[5].value = NULL;
if (fn->cmdline == NULL)
die("custom merge driver %s lacks command line.", fn->name);
for (i = 0; i < 3; i++)
unlink_or_warn(temp[i]);
strbuf_release(&cmd);
+ strbuf_release(&path_sq);
return status;
}
* %A - temporary file name for our version.
* %B - temporary file name for the other branches' version.
* %L - conflict marker length
+ * %P - the original path (safely quoted for the shell)
*
* The external merge driver should write the results in the
* file named by %A, and signal that it has done with zero exit
return lk->fd;
}
+/*
+ * Constants defining the gaps between attempts to lock a file. The
+ * first backoff period is approximately INITIAL_BACKOFF_MS
+ * milliseconds. The longest backoff period is approximately
+ * (BACKOFF_MAX_MULTIPLIER * INITIAL_BACKOFF_MS) milliseconds.
+ */
+#define INITIAL_BACKOFF_MS 1L
+#define BACKOFF_MAX_MULTIPLIER 1000
+
+/*
+ * Try locking path, retrying with quadratic backoff for at least
+ * timeout_ms milliseconds. If timeout_ms is 0, try locking the file
+ * exactly once. If timeout_ms is -1, try indefinitely.
+ */
+static int lock_file_timeout(struct lock_file *lk, const char *path,
+ int flags, long timeout_ms)
+{
+ int n = 1;
+ int multiplier = 1;
+ long remaining_ms = 0;
+ static int random_initialized = 0;
+
+ if (timeout_ms == 0)
+ return lock_file(lk, path, flags);
+
+ if (!random_initialized) {
+ srand((unsigned int)getpid());
+ random_initialized = 1;
+ }
+
+ if (timeout_ms > 0)
+ remaining_ms = timeout_ms;
+
+ while (1) {
+ long backoff_ms, wait_ms;
+ int fd;
+
+ fd = lock_file(lk, path, flags);
+
+ if (fd >= 0)
+ return fd; /* success */
+ else if (errno != EEXIST)
+ return -1; /* failure other than lock held */
+ else if (timeout_ms > 0 && remaining_ms <= 0)
+ return -1; /* failure due to timeout */
+
+ backoff_ms = multiplier * INITIAL_BACKOFF_MS;
+ /* back off for between 0.75*backoff_ms and 1.25*backoff_ms */
+ wait_ms = (750 + rand() % 500) * backoff_ms / 1000;
+ sleep_millisec(wait_ms);
+ remaining_ms -= wait_ms;
+
+ /* Recursion: (n+1)^2 = n^2 + 2n + 1 */
+ multiplier += 2*n + 1;
+ if (multiplier > BACKOFF_MAX_MULTIPLIER)
+ multiplier = BACKOFF_MAX_MULTIPLIER;
+ else
+ n++;
+ }
+}
+
void unable_to_lock_message(const char *path, int err, struct strbuf *buf)
{
if (err == EEXIST) {
}
/* This should return a meaningful errno on failure */
-int hold_lock_file_for_update(struct lock_file *lk, const char *path, int flags)
+int hold_lock_file_for_update_timeout(struct lock_file *lk, const char *path,
+ int flags, long timeout_ms)
{
- int fd = lock_file(lk, path, flags);
+ int fd = lock_file_timeout(lk, path, flags, timeout_ms);
if (fd < 0 && (flags & LOCK_DIE_ON_ERROR))
unable_to_lock_die(path, errno);
return fd;
int save_errno = errno;
if (flags & LOCK_DIE_ON_ERROR)
- exit(128);
+ die("failed to prepare '%s' for appending", path);
close(orig_fd);
rollback_lock_file(lk);
errno = save_errno;
extern void unable_to_lock_message(const char *path, int err,
struct strbuf *buf);
extern NORETURN void unable_to_lock_die(const char *path, int err);
-extern int hold_lock_file_for_update(struct lock_file *, const char *path, int);
-extern int hold_lock_file_for_append(struct lock_file *, const char *path, int);
+extern int hold_lock_file_for_update_timeout(
+ struct lock_file *lk, const char *path,
+ int flags, long timeout_ms);
+
+static inline int hold_lock_file_for_update(
+ struct lock_file *lk, const char *path,
+ int flags)
+{
+ return hold_lock_file_for_update_timeout(lk, path, flags, 0);
+}
+
+extern int hold_lock_file_for_append(struct lock_file *lk, const char *path,
+ int flags);
+
extern FILE *fdopen_lock_file(struct lock_file *, const char *mode);
extern char *get_locked_file_path(struct lock_file *);
extern int commit_lock_file_to(struct lock_file *, const char *path);
return lookup_decoration(&name_decoration, obj);
}
-static int add_ref_decoration(const char *refname, const unsigned char *sha1, int flags, void *cb_data)
+static int add_ref_decoration(const char *refname, const struct object_id *oid,
+ int flags, void *cb_data)
{
struct object *obj;
enum decoration_type type = DECORATION_NONE;
assert(cb_data == NULL);
if (starts_with(refname, "refs/replace/")) {
- unsigned char original_sha1[20];
+ struct object_id original_oid;
if (!check_replace_refs)
return 0;
- if (get_sha1_hex(refname + 13, original_sha1)) {
+ if (get_oid_hex(refname + 13, &original_oid)) {
warning("invalid replace ref %s", refname);
return 0;
}
- obj = parse_object(original_sha1);
+ obj = parse_object(original_oid.hash);
if (obj)
add_name_decoration(DECORATION_GRAFTED, "replaced", obj);
return 0;
}
- obj = parse_object(sha1);
+ obj = parse_object(oid->hash);
if (!obj)
return 0;
static int add_graft_decoration(const struct commit_graft *graft, void *cb_data)
{
- struct commit *commit = lookup_commit(graft->sha1);
+ struct commit *commit = lookup_commit(graft->oid.hash);
if (!commit)
return 0;
add_name_decoration(DECORATION_GRAFTED, "grafted", &commit->object);
void load_ref_decorations(int flags)
{
if (!decoration_loaded) {
+
decoration_loaded = 1;
decoration_flags = flags;
for_each_ref(add_ref_decoration, NULL);
--- /dev/null
+diff_cmd () {
+ "$merge_tool_path" -u -e "$LOCAL" "$REMOTE"
+ return 0
+}
+
+merge_cmd () {
+ # mergetool.winmerge.trustExitCode is implicitly false.
+ # touch $BACKUP so that we can check_unchanged.
+ touch "$BACKUP"
+ "$merge_tool_path" -u -e -dl Local -dr Remote \
+ "$LOCAL" "$REMOTE" "$MERGED"
+ check_unchanged
+}
+
+translate_merge_tool_path() {
+ # Use WinMergeU.exe if it exists in $PATH
+ if type -p WinMergeU.exe >/dev/null 2>&1
+ then
+ printf WinMergeU.exe
+ return
+ fi
+
+ # Look for WinMergeU.exe in the typical locations
+ winmerge_exe="WinMerge/WinMergeU.exe"
+ for directory in $(env | grep -Ei '^PROGRAM(FILES(\(X86\))?|W6432)=' |
+ cut -d '=' -f 2- | sort -u)
+ do
+ if test -n "$directory" && test -x "$directory/$winmerge_exe"
+ then
+ printf '%s' "$directory/$winmerge_exe"
+ return
+ fi
+ done
+
+ printf WinMergeU.exe
+}
"(%s exists).", git_path("NOTES_MERGE_*"));
}
- if (safe_create_leading_directories(git_path(
+ if (safe_create_leading_directories_const(git_path(
NOTES_MERGE_WORKTREE "/.test")))
die_errno("unable to create directory %s",
git_path(NOTES_MERGE_WORKTREE));
const char *buf, unsigned long size)
{
int fd;
- char *path = git_path(NOTES_MERGE_WORKTREE "/%s", sha1_to_hex(obj));
- if (safe_create_leading_directories(path))
+ const char *path = git_path(NOTES_MERGE_WORKTREE "/%s", sha1_to_hex(obj));
+ if (safe_create_leading_directories_const(path))
die_errno("unable to create directory for '%s'", path);
if (file_exists(path))
die("found existing file at '%s'", path);
return ret;
}
-static int string_list_add_one_ref(const char *refname, const unsigned char *sha1,
+static int string_list_add_one_ref(const char *refname, const struct object_id *oid,
int flag, void *cb)
{
struct string_list *refs = cb;
while (i < objects->word_alloc && ewah_iterator_next(&filter, &it)) {
eword_t word = objects->words[i] & filter;
- for (offset = 0; offset < BITS_IN_WORD; ++offset) {
+ for (offset = 0; offset < BITS_IN_EWORD; ++offset) {
const unsigned char *sha1;
struct revindex_entry *entry;
uint32_t hash = 0;
show_reach(sha1, object_type, 0, hash, bitmap_git.pack, entry->offset);
}
- pos += BITS_IN_WORD;
+ pos += BITS_IN_EWORD;
i++;
}
}
break;
}
- reuse_objects += BITS_IN_WORD;
+ reuse_objects += BITS_IN_EWORD;
}
#ifdef GIT_BITMAP_DEBUG
while (ewah_iterator_next(&word, &it)) {
uint32_t offset, bit_pos;
- for (offset = 0; offset < BITS_IN_WORD; ++offset) {
+ for (offset = 0; offset < BITS_IN_EWORD; ++offset) {
if ((word >> offset) == 0)
break;
return -1;
}
- pos += BITS_IN_WORD;
+ pos += BITS_IN_EWORD;
}
return 0;
}
#include "cache.h"
#include "strbuf.h"
#include "string-list.h"
+#include "dir.h"
static int get_st_mode_bits(const char *path, int *mode)
{
static char bad_path[] = "/bad-path/";
-static char *get_pathname(void)
+static struct strbuf *get_pathname(void)
{
- static char pathname_array[4][PATH_MAX];
+ static struct strbuf pathname_array[4] = {
+ STRBUF_INIT, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
+ };
static int index;
- return pathname_array[3 & ++index];
+ struct strbuf *sb = &pathname_array[3 & ++index];
+ strbuf_reset(sb);
+ return sb;
}
static char *cleanup_path(char *path)
return path;
}
+static void strbuf_cleanup_path(struct strbuf *sb)
+{
+ char *path = cleanup_path(sb->buf);
+ if (path > sb->buf)
+ strbuf_remove(sb, 0, path - sb->buf);
+}
+
char *mksnpath(char *buf, size_t n, const char *fmt, ...)
{
va_list args;
return cleanup_path(buf);
}
-static char *vsnpath(char *buf, size_t n, const char *fmt, va_list args)
+static int dir_prefix(const char *buf, const char *dir)
{
- const char *git_dir = get_git_dir();
- size_t len;
+ int len = strlen(dir);
+ return !strncmp(buf, dir, len) &&
+ (is_dir_sep(buf[len]) || buf[len] == '\0');
+}
- len = strlen(git_dir);
- if (n < len + 1)
- goto bad;
- memcpy(buf, git_dir, len);
- if (len && !is_dir_sep(git_dir[len-1]))
- buf[len++] = '/';
- len += vsnprintf(buf + len, n - len, fmt, args);
- if (len >= n)
- goto bad;
- return cleanup_path(buf);
-bad:
- strlcpy(buf, bad_path, n);
- return buf;
+/* $buf =~ m|$dir/+$file| but without regex */
+static int is_dir_file(const char *buf, const char *dir, const char *file)
+{
+ int len = strlen(dir);
+ if (strncmp(buf, dir, len) || !is_dir_sep(buf[len]))
+ return 0;
+ while (is_dir_sep(buf[len]))
+ len++;
+ return !strcmp(buf + len, file);
+}
+
+static void replace_dir(struct strbuf *buf, int len, const char *newdir)
+{
+ int newlen = strlen(newdir);
+ int need_sep = (buf->buf[len] && !is_dir_sep(buf->buf[len])) &&
+ !is_dir_sep(newdir[newlen - 1]);
+ if (need_sep)
+ len--; /* keep one char, to be replaced with '/' */
+ strbuf_splice(buf, 0, len, newdir, newlen);
+ if (need_sep)
+ buf->buf[newlen] = '/';
+}
+
+static const char *common_list[] = {
+ "/branches", "/hooks", "/info", "!/logs", "/lost-found",
+ "/objects", "/refs", "/remotes", "/worktrees", "/rr-cache", "/svn",
+ "config", "!gc.pid", "packed-refs", "shallow",
+ NULL
+};
+
+static void update_common_dir(struct strbuf *buf, int git_dir_len)
+{
+ char *base = buf->buf + git_dir_len;
+ const char **p;
+
+ if (is_dir_file(base, "logs", "HEAD") ||
+ is_dir_file(base, "info", "sparse-checkout"))
+ return; /* keep this in $GIT_DIR */
+ for (p = common_list; *p; p++) {
+ const char *path = *p;
+ int is_dir = 0;
+ if (*path == '!')
+ path++;
+ if (*path == '/') {
+ path++;
+ is_dir = 1;
+ }
+ if (is_dir && dir_prefix(base, path)) {
+ replace_dir(buf, git_dir_len, get_git_common_dir());
+ return;
+ }
+ if (!is_dir && !strcmp(base, path)) {
+ replace_dir(buf, git_dir_len, get_git_common_dir());
+ return;
+ }
+ }
+}
+
+void report_linked_checkout_garbage(void)
+{
+ struct strbuf sb = STRBUF_INIT;
+ const char **p;
+ int len;
+
+ if (!git_common_dir_env)
+ return;
+ strbuf_addf(&sb, "%s/", get_git_dir());
+ len = sb.len;
+ for (p = common_list; *p; p++) {
+ const char *path = *p;
+ if (*path == '!')
+ continue;
+ strbuf_setlen(&sb, len);
+ strbuf_addstr(&sb, path);
+ if (file_exists(sb.buf))
+ report_garbage("unused in linked checkout", sb.buf);
+ }
+ strbuf_release(&sb);
+}
+
+static void adjust_git_path(struct strbuf *buf, int git_dir_len)
+{
+ const char *base = buf->buf + git_dir_len;
+ if (git_graft_env && is_dir_file(base, "info", "grafts"))
+ strbuf_splice(buf, 0, buf->len,
+ get_graft_file(), strlen(get_graft_file()));
+ else if (git_index_env && !strcmp(base, "index"))
+ strbuf_splice(buf, 0, buf->len,
+ get_index_file(), strlen(get_index_file()));
+ else if (git_db_env && dir_prefix(base, "objects"))
+ replace_dir(buf, git_dir_len + 7, get_object_directory());
+ else if (git_common_dir_env)
+ update_common_dir(buf, git_dir_len);
+}
+
+static void do_git_path(struct strbuf *buf, const char *fmt, va_list args)
+{
+ int gitdir_len;
+ strbuf_addstr(buf, get_git_dir());
+ if (buf->len && !is_dir_sep(buf->buf[buf->len - 1]))
+ strbuf_addch(buf, '/');
+ gitdir_len = buf->len;
+ strbuf_vaddf(buf, fmt, args);
+ adjust_git_path(buf, gitdir_len);
+ strbuf_cleanup_path(buf);
}
-char *git_snpath(char *buf, size_t n, const char *fmt, ...)
+void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)
{
- char *ret;
va_list args;
va_start(args, fmt);
- ret = vsnpath(buf, n, fmt, args);
+ do_git_path(sb, fmt, args);
va_end(args);
- return ret;
}
-char *git_pathdup(const char *fmt, ...)
+const char *git_path(const char *fmt, ...)
{
- char path[PATH_MAX], *ret;
+ struct strbuf *pathname = get_pathname();
va_list args;
va_start(args, fmt);
- ret = vsnpath(path, sizeof(path), fmt, args);
+ do_git_path(pathname, fmt, args);
va_end(args);
- return xstrdup(ret);
+ return pathname->buf;
}
-char *mkpathdup(const char *fmt, ...)
+char *git_pathdup(const char *fmt, ...)
{
- char *path;
- struct strbuf sb = STRBUF_INIT;
+ struct strbuf path = STRBUF_INIT;
va_list args;
-
va_start(args, fmt);
- strbuf_vaddf(&sb, fmt, args);
+ do_git_path(&path, fmt, args);
va_end(args);
- path = xstrdup(cleanup_path(sb.buf));
-
- strbuf_release(&sb);
- return path;
+ return strbuf_detach(&path, NULL);
}
-char *mkpath(const char *fmt, ...)
+char *mkpathdup(const char *fmt, ...)
{
+ struct strbuf sb = STRBUF_INIT;
va_list args;
- unsigned len;
- char *pathname = get_pathname();
-
va_start(args, fmt);
- len = vsnprintf(pathname, PATH_MAX, fmt, args);
+ strbuf_vaddf(&sb, fmt, args);
va_end(args);
- if (len >= PATH_MAX)
- return bad_path;
- return cleanup_path(pathname);
+ strbuf_cleanup_path(&sb);
+ return strbuf_detach(&sb, NULL);
}
-char *git_path(const char *fmt, ...)
+const char *mkpath(const char *fmt, ...)
{
- char *pathname = get_pathname();
va_list args;
- char *ret;
-
+ struct strbuf *pathname = get_pathname();
va_start(args, fmt);
- ret = vsnpath(pathname, PATH_MAX, fmt, args);
+ strbuf_vaddf(pathname, fmt, args);
va_end(args);
- return ret;
+ return cleanup_path(pathname->buf);
}
-char *git_path_submodule(const char *path, const char *fmt, ...)
+const char *git_path_submodule(const char *path, const char *fmt, ...)
{
- char *pathname = get_pathname();
- struct strbuf buf = STRBUF_INIT;
+ struct strbuf *buf = get_pathname();
const char *git_dir;
va_list args;
- unsigned len;
- len = strlen(path);
- if (len > PATH_MAX-100)
- return bad_path;
+ strbuf_addstr(buf, path);
+ if (buf->len && buf->buf[buf->len - 1] != '/')
+ strbuf_addch(buf, '/');
+ strbuf_addstr(buf, ".git");
- strbuf_addstr(&buf, path);
- if (len && path[len-1] != '/')
- strbuf_addch(&buf, '/');
- strbuf_addstr(&buf, ".git");
-
- git_dir = read_gitfile(buf.buf);
+ git_dir = read_gitfile(buf->buf);
if (git_dir) {
- strbuf_reset(&buf);
- strbuf_addstr(&buf, git_dir);
+ strbuf_reset(buf);
+ strbuf_addstr(buf, git_dir);
}
- strbuf_addch(&buf, '/');
-
- if (buf.len >= PATH_MAX)
- return bad_path;
- memcpy(pathname, buf.buf, buf.len + 1);
-
- strbuf_release(&buf);
- len = strlen(pathname);
+ strbuf_addch(buf, '/');
va_start(args, fmt);
- len += vsnprintf(pathname + len, PATH_MAX - len, fmt, args);
+ strbuf_vaddf(buf, fmt, args);
va_end(args);
- if (len >= PATH_MAX)
- return bad_path;
- return cleanup_path(pathname);
+ strbuf_cleanup_path(buf);
+ return buf->buf;
}
int validate_headref(const char *path)
progress_update = 0;
}
+static int is_foreground_fd(int fd)
+{
+ int tpgrp = tcgetpgrp(fd);
+ return tpgrp < 0 || tpgrp == getpgid(0);
+}
+
static int display(struct progress *progress, unsigned n, const char *done)
{
const char *eol, *tp;
unsigned percent = n * 100 / progress->total;
if (percent != progress->last_percent || progress_update) {
progress->last_percent = percent;
- fprintf(stderr, "%s: %3u%% (%u/%u)%s%s",
- progress->title, percent, n,
- progress->total, tp, eol);
- fflush(stderr);
+ if (is_foreground_fd(fileno(stderr)) || done) {
+ fprintf(stderr, "%s: %3u%% (%u/%u)%s%s",
+ progress->title, percent, n,
+ progress->total, tp, eol);
+ fflush(stderr);
+ }
progress_update = 0;
return 1;
}
} else if (progress_update) {
- fprintf(stderr, "%s: %u%s%s", progress->title, n, tp, eol);
- fflush(stderr);
+ if (is_foreground_fd(fileno(stderr)) || done) {
+ fprintf(stderr, "%s: %u%s%s",
+ progress->title, n, tp, eol);
+ fflush(stderr);
+ }
progress_update = 0;
return 1;
}
display_progress(cp->progress, cp->count);
}
-static int add_one_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data)
+static int add_one_ref(const char *path, const struct object_id *oid,
+ int flag, void *cb_data)
{
- struct object *object = parse_object_or_die(sha1, path);
+ struct object *object = parse_object_or_die(oid->hash, path);
struct rev_info *revs = (struct rev_info *)cb_data;
add_pending_object(revs, object, "");
#define CACHE_EXT_TREE 0x54524545 /* "TREE" */
#define CACHE_EXT_RESOLVE_UNDO 0x52455543 /* "REUC" */
#define CACHE_EXT_LINK 0x6c696e6b /* "link" */
+#define CACHE_EXT_UNTRACKED 0x554E5452 /* "UNTR" */
/* changes that can be kept in $GIT_DIR/index (basically all extensions) */
#define EXTMASK (RESOLVE_UNDO_CHANGED | CACHE_TREE_CHANGED | \
CE_ENTRY_ADDED | CE_ENTRY_REMOVED | CE_ENTRY_CHANGED | \
- SPLIT_INDEX_ORDERED)
+ SPLIT_INDEX_ORDERED | UNTRACKED_CHANGED)
struct index_state the_index;
static const char *alternate_index_output;
memcpy(new->name, new_name, namelen + 1);
cache_tree_invalidate_path(istate, old->name);
+ untracked_cache_remove_from_index(istate, old->name);
remove_index_entry_at(istate, nr);
add_index_entry(istate, new, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE);
}
return changed;
}
-static int is_racy_timestamp(const struct index_state *istate,
- const struct cache_entry *ce)
+static int is_racy_stat(const struct index_state *istate,
+ const struct stat_data *sd)
{
- return (!S_ISGITLINK(ce->ce_mode) &&
- istate->timestamp.sec &&
+ return (istate->timestamp.sec &&
#ifdef USE_NSEC
/* nanosecond timestamped files can also be racy! */
- (istate->timestamp.sec < ce->ce_stat_data.sd_mtime.sec ||
- (istate->timestamp.sec == ce->ce_stat_data.sd_mtime.sec &&
- istate->timestamp.nsec <= ce->ce_stat_data.sd_mtime.nsec))
+ (istate->timestamp.sec < sd->sd_mtime.sec ||
+ (istate->timestamp.sec == sd->sd_mtime.sec &&
+ istate->timestamp.nsec <= sd->sd_mtime.nsec))
#else
- istate->timestamp.sec <= ce->ce_stat_data.sd_mtime.sec
+ istate->timestamp.sec <= sd->sd_mtime.sec
#endif
- );
+ );
+}
+
+static int is_racy_timestamp(const struct index_state *istate,
+ const struct cache_entry *ce)
+{
+ return (!S_ISGITLINK(ce->ce_mode) &&
+ is_racy_stat(istate, &ce->ce_stat_data));
+}
+
+int match_stat_data_racy(const struct index_state *istate,
+ const struct stat_data *sd, struct stat *st)
+{
+ if (is_racy_stat(istate, sd))
+ return MTIME_CHANGED;
+ return match_stat_data(sd, st);
}
int ie_match_stat(const struct index_state *istate,
if (pos < 0)
pos = -pos-1;
cache_tree_invalidate_path(istate, path);
+ untracked_cache_remove_from_index(istate, path);
while (pos < istate->cache_nr && !strcmp(istate->cache[pos]->name, path))
remove_index_entry_at(istate, pos);
return 0;
}
pos = -pos-1;
+ if (!(option & ADD_CACHE_KEEP_CACHE_TREE))
+ untracked_cache_add_to_index(istate, ce->name);
+
/*
* Inserting a merged entry ("stage 0") into the index
* will always replace all non-merged entries..
if (read_link_extension(istate, data, sz))
return -1;
break;
+ case CACHE_EXT_UNTRACKED:
+ istate->untracked = read_untracked_extension(data, sz);
+ break;
default:
if (*ext < 'A' || 'Z' < *ext)
return error("index uses %.4s extension, which we do not understand",
if (mmap_size < sizeof(struct cache_header) + 20)
die("index file smaller than expected");
- mmap = xmmap(NULL, mmap_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
+ mmap = xmmap(NULL, mmap_size, PROT_READ, MAP_PRIVATE, fd, 0);
if (mmap == MAP_FAILED)
die_errno("unable to map index file");
close(fd);
istate->cache = NULL;
istate->cache_alloc = 0;
discard_split_index(istate);
+ free_untracked_cache(istate->untracked);
+ istate->untracked = NULL;
return 0;
}
if (err)
return -1;
}
+ if (!strip_extensions && istate->untracked) {
+ struct strbuf sb = STRBUF_INIT;
+
+ write_untracked_extension(&sb, istate->untracked);
+ err = write_index_ext_header(&c, newfd, CACHE_EXT_UNTRACKED,
+ sb.len) < 0 ||
+ ce_write(&c, newfd, sb.buf, sb.len) < 0;
+ strbuf_release(&sb);
+ if (err)
+ return -1;
+ }
if (ce_flush(&c, newfd, istate->sha1) || fstat(newfd, &st))
return -1;
char *ref_name;
char *orig_ref_name;
struct lock_file *lk;
- unsigned char old_sha1[20];
- int lock_fd;
+ struct object_id old_oid;
};
/*
* null. If REF_ISSYMREF, then this is the name of the object
* referred to by the last reference in the symlink chain.
*/
- unsigned char sha1[20];
+ struct object_id oid;
/*
* If REF_KNOWS_PEELED, then this field holds the peeled value
* be peelable. See the documentation for peel_ref() for an
* exact definition of "peelable".
*/
- unsigned char peeled[20];
+ struct object_id peeled;
};
struct ref_cache;
if (check_name &&
check_refname_format(refname, REFNAME_ALLOW_ONELEVEL))
die("Reference has invalid format: '%s'", refname);
- if (!check_name && !refname_is_safe(refname))
- die("Reference has invalid name: '%s'", refname);
len = strlen(refname) + 1;
ref = xmalloc(sizeof(struct ref_entry) + len);
- hashcpy(ref->u.value.sha1, sha1);
- hashclr(ref->u.value.peeled);
+ hashcpy(ref->u.value.oid.hash, sha1);
+ oidclr(&ref->u.value.peeled);
memcpy(ref->name, refname, len);
ref->flag = flag;
return ref;
/* This is impossible by construction */
die("Reference directory conflict: %s", ref1->name);
- if (hashcmp(ref1->u.value.sha1, ref2->u.value.sha1))
+ if (oidcmp(&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);
{
if (entry->flag & REF_ISBROKEN)
return 0;
- if (!has_sha1_file(entry->u.value.sha1)) {
+ if (!has_sha1_file(entry->u.value.oid.hash)) {
error("%s does not point to a valid object!", entry->name);
return 0;
}
/* Store the old value, in case this is a recursive call: */
old_current_ref = current_ref;
current_ref = entry;
- retval = data->fn(entry->name + data->trim, entry->u.value.sha1,
+ retval = data->fn(entry->name + data->trim, &entry->u.value.oid,
entry->flag, data->cb_data);
current_ref = old_current_ref;
return retval;
int flag = REF_ISPACKED;
if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {
+ if (!refname_is_safe(refname))
+ die("packed refname is dangerous: %s", refname);
hashclr(sha1);
flag |= REF_BAD_NAME | REF_ISBROKEN;
}
line.len == PEELED_LINE_LENGTH &&
line.buf[PEELED_LINE_LENGTH - 1] == '\n' &&
!get_sha1_hex(line.buf + 1, sha1)) {
- hashcpy(last->u.value.peeled, sha1);
+ hashcpy(last->u.value.peeled.hash, sha1);
/*
* Regardless of what the file header said,
* we definitely know the value of *this*
create_dir_entry(refs, refname.buf,
refname.len, 1));
} else {
+ int read_ok;
+
if (*refs->name) {
hashclr(sha1);
flag = 0;
- if (resolve_gitlink_ref(refs->name, refname.buf, sha1) < 0) {
- hashclr(sha1);
- flag |= REF_ISBROKEN;
- }
- } else if (read_ref_full(refname.buf,
- RESOLVE_REF_READING,
- sha1, &flag)) {
+ read_ok = !resolve_gitlink_ref(refs->name,
+ refname.buf, sha1);
+ } else {
+ read_ok = !read_ref_full(refname.buf,
+ RESOLVE_REF_READING,
+ sha1, &flag);
+ }
+
+ if (!read_ok) {
hashclr(sha1);
flag |= REF_ISBROKEN;
+ } else if (is_null_sha1(sha1)) {
+ /*
+ * It is so astronomically unlikely
+ * that NULL_SHA1 is the SHA-1 of an
+ * actual object that we consider its
+ * appearance in a loose reference
+ * file to be repo corruption
+ * (probably due to a software bug).
+ */
+ flag |= REF_ISBROKEN;
}
+
if (check_refname_format(refname.buf,
REFNAME_ALLOW_ONELEVEL)) {
+ if (!refname_is_safe(refname.buf))
+ die("loose refname is dangerous: %s", refname.buf);
hashclr(sha1);
flag |= REF_BAD_NAME | REF_ISBROKEN;
}
if (ref == NULL)
return -1;
- hashcpy(sha1, ref->u.value.sha1);
+ hashcpy(sha1, ref->u.value.oid.hash);
return 0;
}
{
int fd, len;
char buffer[128], *p;
- char *path;
+ const char *path;
if (recursion > MAXDEPTH || strlen(refname) > MAXREFLEN)
return -1;
*/
entry = get_packed_ref(refname);
if (entry) {
- hashcpy(sha1, entry->u.value.sha1);
+ hashcpy(sha1, entry->u.value.oid.hash);
if (flags)
*flags |= REF_ISPACKED;
return 0;
}
/* This function needs to return a meaningful errno on failure */
-const char *resolve_ref_unsafe(const char *refname, int resolve_flags, unsigned char *sha1, int *flags)
+static const char *resolve_ref_unsafe_1(const char *refname,
+ int resolve_flags,
+ unsigned char *sha1,
+ int *flags,
+ struct strbuf *sb_path)
{
int depth = MAXDEPTH;
ssize_t len;
bad_name = 1;
}
for (;;) {
- char path[PATH_MAX];
+ const char *path;
struct stat st;
char *buf;
int fd;
return NULL;
}
- git_snpath(path, sizeof(path), "%s", refname);
+ strbuf_reset(sb_path);
+ strbuf_git_path(sb_path, "%s", refname);
+ path = sb_path->buf;
/*
* We might have to loop back here to avoid a race
}
}
+const char *resolve_ref_unsafe(const char *refname, int resolve_flags,
+ unsigned char *sha1, int *flags)
+{
+ struct strbuf sb_path = STRBUF_INIT;
+ const char *ret = resolve_ref_unsafe_1(refname, resolve_flags,
+ sha1, flags, &sb_path);
+ strbuf_release(&sb_path);
+ return ret;
+}
+
char *resolve_refdup(const char *ref, int resolve_flags, unsigned char *sha1, int *flags)
{
return xstrdup_or_null(resolve_ref_unsafe(ref, resolve_flags, sha1, flags));
return !!resolve_ref_unsafe(refname, RESOLVE_REF_READING, sha1, NULL);
}
-static int filter_refs(const char *refname, const unsigned char *sha1, int flags,
- void *data)
+static int filter_refs(const char *refname, const struct object_id *oid,
+ int flags, void *data)
{
struct ref_filter *filter = (struct ref_filter *)data;
+
if (wildmatch(filter->pattern, refname, 0, NULL))
return 0;
- return filter->fn(refname, sha1, flags, filter->cb_data);
+ return filter->fn(refname, oid, flags, filter->cb_data);
}
enum peel_status {
if (entry->flag & REF_KNOWS_PEELED) {
if (repeel) {
entry->flag &= ~REF_KNOWS_PEELED;
- hashclr(entry->u.value.peeled);
+ oidclr(&entry->u.value.peeled);
} else {
- return is_null_sha1(entry->u.value.peeled) ?
+ return is_null_oid(&entry->u.value.peeled) ?
PEEL_NON_TAG : PEEL_PEELED;
}
}
if (entry->flag & REF_ISSYMREF)
return PEEL_IS_SYMREF;
- status = peel_object(entry->u.value.sha1, entry->u.value.peeled);
+ status = peel_object(entry->u.value.oid.hash, entry->u.value.peeled.hash);
if (status == PEEL_PEELED || status == PEEL_NON_TAG)
entry->flag |= REF_KNOWS_PEELED;
return status;
|| !strcmp(current_ref->name, refname))) {
if (peel_entry(current_ref, 0))
return -1;
- hashcpy(sha1, current_ref->u.value.peeled);
+ hashcpy(sha1, current_ref->u.value.peeled.hash);
return 0;
}
if (r) {
if (peel_entry(r, 0))
return -1;
- hashcpy(sha1, r->u.value.peeled);
+ hashcpy(sha1, r->u.value.peeled.hash);
return 0;
}
}
const char *msg_fmt;
};
-static int warn_if_dangling_symref(const char *refname, const unsigned char *sha1,
+static int warn_if_dangling_symref(const char *refname, const struct object_id *oid,
int flags, void *cb_data)
{
struct warn_if_dangling_data *d = cb_data;
const char *resolves_to;
- unsigned char junk[20];
+ struct object_id junk;
if (!(flags & REF_ISSYMREF))
return 0;
- resolves_to = resolve_ref_unsafe(refname, 0, junk, NULL);
+ resolves_to = resolve_ref_unsafe(refname, 0, junk.hash, NULL);
if (!resolves_to
|| (d->refname
? strcmp(resolves_to, d->refname)
static int do_head_ref(const char *submodule, each_ref_fn fn, void *cb_data)
{
- unsigned char sha1[20];
+ struct object_id oid;
int flag;
if (submodule) {
- if (resolve_gitlink_ref(submodule, "HEAD", sha1) == 0)
- return fn("HEAD", sha1, 0, cb_data);
+ if (resolve_gitlink_ref(submodule, "HEAD", oid.hash) == 0)
+ return fn("HEAD", &oid, 0, cb_data);
return 0;
}
- if (!read_ref_full("HEAD", RESOLVE_REF_READING, sha1, &flag))
- return fn("HEAD", sha1, flag, cb_data);
+ if (!read_ref_full("HEAD", RESOLVE_REF_READING, oid.hash, &flag))
+ return fn("HEAD", &oid, flag, cb_data);
return 0;
}
{
struct strbuf buf = STRBUF_INIT;
int ret = 0;
- unsigned char sha1[20];
+ struct object_id oid;
int flag;
strbuf_addf(&buf, "%sHEAD", get_git_namespace());
- if (!read_ref_full(buf.buf, RESOLVE_REF_READING, sha1, &flag))
- ret = fn(buf.buf, sha1, flag, cb_data);
+ if (!read_ref_full(buf.buf, RESOLVE_REF_READING, oid.hash, &flag))
+ ret = fn(buf.buf, &oid, flag, cb_data);
strbuf_release(&buf);
return ret;
free(lock);
}
-/* This function should make sure errno is meaningful on error */
-static struct ref_lock *verify_lock(struct ref_lock *lock,
- const unsigned char *old_sha1, int mustexist)
+/*
+ * Verify that the reference locked by lock has the value old_sha1.
+ * Fail if the reference doesn't exist and mustexist is set. Return 0
+ * on success. On error, write an error message to err, set errno, and
+ * return a negative value.
+ */
+static int verify_lock(struct ref_lock *lock,
+ const unsigned char *old_sha1, int mustexist,
+ struct strbuf *err)
{
+ assert(err);
+
if (read_ref_full(lock->ref_name,
mustexist ? RESOLVE_REF_READING : 0,
- lock->old_sha1, NULL)) {
+ lock->old_oid.hash, NULL)) {
int save_errno = errno;
- error("Can't verify ref %s", lock->ref_name);
- unlock_ref(lock);
+ strbuf_addf(err, "can't verify ref %s", lock->ref_name);
errno = save_errno;
- return NULL;
+ return -1;
}
- if (hashcmp(lock->old_sha1, old_sha1)) {
- error("Ref %s is at %s but expected %s", lock->ref_name,
- sha1_to_hex(lock->old_sha1), sha1_to_hex(old_sha1));
- unlock_ref(lock);
+ if (hashcmp(lock->old_oid.hash, old_sha1)) {
+ strbuf_addf(err, "ref %s is at %s but expected %s",
+ lock->ref_name,
+ sha1_to_hex(lock->old_oid.hash),
+ sha1_to_hex(old_sha1));
errno = EBUSY;
- return NULL;
+ return -1;
}
- return lock;
+ return 0;
}
static int remove_empty_directories(const char *file)
unsigned int flags, int *type_p,
struct strbuf *err)
{
- char *ref_file;
+ const char *ref_file;
const char *orig_refname = refname;
struct ref_lock *lock;
int last_errno = 0;
assert(err);
lock = xcalloc(1, sizeof(struct ref_lock));
- lock->lock_fd = -1;
if (mustexist)
resolve_flags |= RESOLVE_REF_READING;
}
refname = resolve_ref_unsafe(refname, resolve_flags,
- lock->old_sha1, &type);
+ lock->old_oid.hash, &type);
if (!refname && errno == EISDIR) {
/* we are trying to lock foo but we used to
* have foo/bar which now does not exist;
goto error_return;
}
refname = resolve_ref_unsafe(orig_refname, resolve_flags,
- lock->old_sha1, &type);
+ lock->old_oid.hash, &type);
}
if (type_p)
*type_p = type;
* refname, nor a packed ref whose name is a proper prefix of
* our refname.
*/
- if (is_null_sha1(lock->old_sha1) &&
+ if (is_null_oid(&lock->old_oid) &&
verify_refname_available(refname, extras, skip,
get_packed_refs(&ref_cache), err)) {
last_errno = ENOTDIR;
ref_file = git_path("%s", refname);
retry:
- switch (safe_create_leading_directories(ref_file)) {
+ switch (safe_create_leading_directories_const(ref_file)) {
case SCLD_OK:
break; /* success */
case SCLD_VANISHED:
goto error_return;
}
- lock->lock_fd = hold_lock_file_for_update(lock->lk, ref_file, lflags);
- if (lock->lock_fd < 0) {
+ if (hold_lock_file_for_update(lock->lk, ref_file, lflags) < 0) {
last_errno = errno;
if (errno == ENOENT && --attempts_remaining > 0)
/*
goto error_return;
}
}
- return old_sha1 ? verify_lock(lock, old_sha1, mustexist) : lock;
+ if (old_sha1 && verify_lock(lock, old_sha1, mustexist, err)) {
+ last_errno = errno;
+ goto error_return;
+ }
+ return lock;
error_return:
unlock_ref(lock);
if (peel_status != PEEL_PEELED && peel_status != PEEL_NON_TAG)
error("internal error: %s is not a valid packed reference!",
entry->name);
- write_packed_entry(cb_data, entry->name, entry->u.value.sha1,
+ write_packed_entry(cb_data, entry->name, entry->u.value.oid.hash,
peel_status == PEEL_PEELED ?
- entry->u.value.peeled : NULL);
+ entry->u.value.peeled.hash : NULL);
return 0;
}
/* This should return a meaningful errno on failure */
int lock_packed_refs(int flags)
{
+ static int timeout_configured = 0;
+ static int timeout_value = 1000;
+
struct packed_ref_cache *packed_ref_cache;
- if (hold_lock_file_for_update(&packlock, git_path("packed-refs"), flags) < 0)
+ if (!timeout_configured) {
+ git_config_get_int("core.packedrefstimeout", &timeout_value);
+ timeout_configured = 1;
+ }
+
+ if (hold_lock_file_for_update_timeout(
+ &packlock, git_path("packed-refs"),
+ flags, timeout_value) < 0)
return -1;
/*
* Get the current packed-refs while holding the lock. If the
peel_status = peel_entry(entry, 1);
if (peel_status != PEEL_PEELED && peel_status != PEEL_NON_TAG)
die("internal error peeling reference %s (%s)",
- entry->name, sha1_to_hex(entry->u.value.sha1));
+ entry->name, oid_to_hex(&entry->u.value.oid));
packed_entry = find_ref(cb->packed_refs, entry->name);
if (packed_entry) {
/* Overwrite existing packed entry with info from loose entry */
packed_entry->flag = REF_ISPACKED | REF_KNOWS_PEELED;
- hashcpy(packed_entry->u.value.sha1, entry->u.value.sha1);
+ oidcpy(&packed_entry->u.value.oid, &entry->u.value.oid);
} else {
- packed_entry = create_ref_entry(entry->name, entry->u.value.sha1,
+ packed_entry = create_ref_entry(entry->name, entry->u.value.oid.hash,
REF_ISPACKED | REF_KNOWS_PEELED, 0);
add_ref(cb->packed_refs, packed_entry);
}
- hashcpy(packed_entry->u.value.peeled, entry->u.value.peeled);
+ oidcpy(&packed_entry->u.value.peeled, &entry->u.value.peeled);
/* Schedule the loose reference for pruning if requested. */
if ((cb->flags & PACK_REFS_PRUNE)) {
int namelen = strlen(entry->name) + 1;
struct ref_to_prune *n = xcalloc(1, sizeof(*n) + namelen);
- hashcpy(n->sha1, entry->u.value.sha1);
+ hashcpy(n->sha1, entry->u.value.oid.hash);
strcpy(n->name, entry->name);
n->next = cb->ref_to_prune;
cb->ref_to_prune = n;
int attempts_remaining = 4;
retry:
- switch (safe_create_leading_directories(git_path("logs/%s", newrefname))) {
+ switch (safe_create_leading_directories_const(git_path("logs/%s", newrefname))) {
case SCLD_OK:
break; /* success */
case SCLD_VANISHED:
strbuf_release(&err);
goto rollback;
}
- hashcpy(lock->old_sha1, orig_sha1);
+ hashcpy(lock->old_oid.hash, orig_sha1);
if (write_ref_to_lockfile(lock, orig_sha1) ||
commit_ref_update(lock, orig_sha1, logmsg)) {
{
if (close_lock_file(lock->lk))
return -1;
- lock->lock_fd = -1;
return 0;
}
{
if (commit_lock_file(lock->lk))
return -1;
- lock->lock_fd = -1;
return 0;
}
}
/* This function must set a meaningful errno on failure */
-int log_ref_setup(const char *refname, char *logfile, int bufsize)
+int log_ref_setup(const char *refname, struct strbuf *sb_logfile)
{
int logfd, oflags = O_APPEND | O_WRONLY;
+ char *logfile;
- git_snpath(logfile, bufsize, "logs/%s", refname);
+ strbuf_git_path(sb_logfile, "logs/%s", refname);
+ logfile = sb_logfile->buf;
+ /* make sure the rest of the function can't change "logfile" */
+ sb_logfile = NULL;
if (log_all_ref_updates &&
(starts_with(refname, "refs/heads/") ||
starts_with(refname, "refs/remotes/") ||
return 0;
}
-static int log_ref_write(const char *refname, const unsigned char *old_sha1,
- const unsigned char *new_sha1, const char *msg)
+static int log_ref_write_1(const char *refname, const unsigned char *old_sha1,
+ const unsigned char *new_sha1, const char *msg,
+ struct strbuf *sb_log_file)
{
int logfd, result, oflags = O_APPEND | O_WRONLY;
- char log_file[PATH_MAX];
+ char *log_file;
if (log_all_ref_updates < 0)
log_all_ref_updates = !is_bare_repository();
- result = log_ref_setup(refname, log_file, sizeof(log_file));
+ result = log_ref_setup(refname, sb_log_file);
if (result)
return result;
+ log_file = sb_log_file->buf;
+ /* make sure the rest of the function can't change "log_file" */
+ sb_log_file = NULL;
logfd = open(log_file, oflags);
if (logfd < 0)
return 0;
}
+static int log_ref_write(const char *refname, const unsigned char *old_sha1,
+ const unsigned char *new_sha1, const char *msg)
+{
+ struct strbuf sb = STRBUF_INIT;
+ int ret = log_ref_write_1(refname, old_sha1, new_sha1, msg, &sb);
+ strbuf_release(&sb);
+ return ret;
+}
+
int is_branch(const char *refname)
{
return !strcmp(refname, "HEAD") || starts_with(refname, "refs/heads/");
errno = EINVAL;
return -1;
}
- if (write_in_full(lock->lock_fd, sha1_to_hex(sha1), 40) != 40 ||
- write_in_full(lock->lock_fd, &term, 1) != 1 ||
+ if (write_in_full(lock->lk->fd, sha1_to_hex(sha1), 40) != 40 ||
+ write_in_full(lock->lk->fd, &term, 1) != 1 ||
close_ref(lock) < 0) {
int save_errno = errno;
error("Couldn't write %s", lock->lk->filename.buf);
const unsigned char *sha1, const char *logmsg)
{
clear_loose_ref_cache(&ref_cache);
- if (log_ref_write(lock->ref_name, lock->old_sha1, sha1, logmsg) < 0 ||
+ if (log_ref_write(lock->ref_name, lock->old_oid.hash, sha1, logmsg) < 0 ||
(strcmp(lock->ref_name, lock->orig_ref_name) &&
- log_ref_write(lock->orig_ref_name, lock->old_sha1, sha1, logmsg) < 0)) {
+ log_ref_write(lock->orig_ref_name, lock->old_oid.hash, sha1, logmsg) < 0)) {
unlock_ref(lock);
return -1;
}
head_sha1, &head_flag);
if (head_ref && (head_flag & REF_ISSYMREF) &&
!strcmp(head_ref, lock->ref_name))
- log_ref_write("HEAD", lock->old_sha1, sha1, logmsg);
+ log_ref_write("HEAD", lock->old_oid.hash, sha1, logmsg);
}
if (commit_ref(lock)) {
error("Couldn't set %s", lock->ref_name);
strbuf_addch(name, '/');
retval = do_for_each_reflog(name, fn, cb_data);
} else {
- unsigned char sha1[20];
- if (read_ref_full(name->buf, 0, sha1, NULL))
+ struct object_id oid;
+
+ if (read_ref_full(name->buf, 0, oid.hash, NULL))
retval = error("bad ref for %s", name->buf);
else
- retval = fn(name->buf, sha1, 0, cb_data);
+ retval = fn(name->buf, &oid, 0, cb_data);
}
if (retval)
break;
? TRANSACTION_NAME_CONFLICT
: TRANSACTION_GENERIC_ERROR;
reason = strbuf_detach(err, NULL);
- strbuf_addf(err, "Cannot lock ref '%s': %s",
+ strbuf_addf(err, "cannot lock ref '%s': %s",
update->refname, reason);
free(reason);
goto cleanup;
(update->flags & REF_NODEREF));
if (!overwriting_symref &&
- !hashcmp(update->lock->old_sha1, update->new_sha1)) {
+ !hashcmp(update->lock->old_oid.hash, update->new_sha1)) {
/*
* The reference already has the desired
* value, so we don't need to write it.
* write_ref_to_lockfile():
*/
update->lock = NULL;
- strbuf_addf(err, "Cannot update the ref '%s'.",
+ strbuf_addf(err, "cannot update the ref '%s'.",
update->refname);
ret = TRANSACTION_GENERIC_ERROR;
goto cleanup;
status |= error("couldn't write %s: %s", log_file,
strerror(errno));
} else if (update &&
- (write_in_full(lock->lock_fd,
+ (write_in_full(lock->lk->fd,
sha1_to_hex(cb.last_kept_sha1), 40) != 40 ||
- write_str_in_full(lock->lock_fd, "\n") != 1 ||
+ write_str_in_full(lock->lk->fd, "\n") != 1 ||
close_ref(lock) < 0)) {
status |= error("couldn't write %s",
lock->lk->filename.buf);
* single callback invocation.
*/
typedef int each_ref_fn(const char *refname,
- const unsigned char *sha1, int flags, void *cb_data);
+ const struct object_id *oid, int flags, void *cb_data);
/*
* The following functions invoke the specified callback function for
/*
* Setup reflog before using. Set errno to something meaningful on failure.
*/
-int log_ref_setup(const char *refname, char *logfile, int bufsize);
+int log_ref_setup(const char *refname, struct strbuf *logfile);
/** Reads log for the value of ref during at_time. **/
extern int read_ref_at(const char *refname, unsigned int flags,
static int branches_nr;
static struct branch *current_branch;
-static const char *default_remote_name;
-static const char *branch_pushremote_name;
static const char *pushremote_name;
-static int explicit_default_remote_name;
static struct rewrites rewrites;
static struct rewrites rewrites_push;
return 0;
branch = make_branch(name, subkey - name);
if (!strcmp(subkey, ".remote")) {
- if (git_config_string(&branch->remote_name, key, value))
- return -1;
- if (branch == current_branch) {
- default_remote_name = branch->remote_name;
- explicit_default_remote_name = 1;
- }
+ return git_config_string(&branch->remote_name, key, value);
} else if (!strcmp(subkey, ".pushremote")) {
- if (branch == current_branch)
- if (git_config_string(&branch_pushremote_name, key, value))
- return -1;
+ return git_config_string(&branch->pushremote_name, key, value);
} else if (!strcmp(subkey, ".merge")) {
if (!value)
return config_error_nonbool(key);
static void read_config(void)
{
+ static int loaded;
unsigned char sha1[20];
const char *head_ref;
int flag;
- if (default_remote_name) /* did this already */
+
+ if (loaded)
return;
- default_remote_name = "origin";
+ loaded = 1;
+
current_branch = NULL;
head_ref = resolve_ref_unsafe("HEAD", 0, sha1, &flag);
if (head_ref && (flag & REF_ISSYMREF) &&
current_branch = make_branch(head_ref, 0);
}
git_config(handle_config, NULL);
- if (branch_pushremote_name) {
- free((char *)pushremote_name);
- pushremote_name = branch_pushremote_name;
- }
alias_all_urls();
}
return !strchr(name, '/'); /* no slash */
}
-static struct remote *remote_get_1(const char *name, const char *pushremote_name)
+const char *remote_for_branch(struct branch *branch, int *explicit)
+{
+ if (branch && branch->remote_name) {
+ if (explicit)
+ *explicit = 1;
+ return branch->remote_name;
+ }
+ if (explicit)
+ *explicit = 0;
+ return "origin";
+}
+
+const char *pushremote_for_branch(struct branch *branch, int *explicit)
+{
+ if (branch && branch->pushremote_name) {
+ if (explicit)
+ *explicit = 1;
+ return branch->pushremote_name;
+ }
+ if (pushremote_name) {
+ if (explicit)
+ *explicit = 1;
+ return pushremote_name;
+ }
+ return remote_for_branch(branch, explicit);
+}
+
+static struct remote *remote_get_1(const char *name,
+ const char *(*get_default)(struct branch *, int *))
{
struct remote *ret;
int name_given = 0;
+ read_config();
+
if (name)
name_given = 1;
- else {
- if (pushremote_name) {
- name = pushremote_name;
- name_given = 1;
- } else {
- name = default_remote_name;
- name_given = explicit_default_remote_name;
- }
- }
+ else
+ name = get_default(current_branch, &name_given);
ret = make_remote(name, 0);
if (valid_remote_nick(name)) {
struct remote *remote_get(const char *name)
{
- read_config();
- return remote_get_1(name, NULL);
+ return remote_get_1(name, remote_for_branch);
}
struct remote *pushremote_get(const char *name)
{
- read_config();
- return remote_get_1(name, pushremote_name);
+ return remote_get_1(name, pushremote_for_branch);
}
int remote_is_configured(const char *name)
static void set_merge(struct branch *ret)
{
+ struct remote *remote;
char *ref;
unsigned char sha1[20];
int i;
+ if (!ret)
+ return; /* no branch */
+ if (ret->merge)
+ return; /* already run */
+ if (!ret->remote_name || !ret->merge_nr) {
+ /*
+ * no merge config; let's make sure we don't confuse callers
+ * with a non-zero merge_nr but a NULL merge
+ */
+ ret->merge_nr = 0;
+ return;
+ }
+
+ remote = remote_get(ret->remote_name);
+
ret->merge = xcalloc(ret->merge_nr, sizeof(*ret->merge));
for (i = 0; i < ret->merge_nr; i++) {
ret->merge[i] = xcalloc(1, sizeof(**ret->merge));
ret->merge[i]->src = xstrdup(ret->merge_name[i]);
- if (!remote_find_tracking(ret->remote, ret->merge[i]) ||
+ if (!remote_find_tracking(remote, ret->merge[i]) ||
strcmp(ret->remote_name, "."))
continue;
if (dwim_ref(ret->merge_name[i], strlen(ret->merge_name[i]),
ret = current_branch;
else
ret = make_branch(name, 0);
- if (ret && ret->remote_name) {
- ret->remote = remote_get(ret->remote_name);
- if (ret->merge_nr)
- set_merge(ret);
- }
+ set_merge(ret);
return ret;
}
return refname_match(branch->merge[i]->src, refname);
}
+__attribute((format (printf,2,3)))
+static const char *error_buf(struct strbuf *err, const char *fmt, ...)
+{
+ if (err) {
+ va_list ap;
+ va_start(ap, fmt);
+ strbuf_vaddf(err, fmt, ap);
+ va_end(ap);
+ }
+ return NULL;
+}
+
+const char *branch_get_upstream(struct branch *branch, struct strbuf *err)
+{
+ if (!branch)
+ return error_buf(err, _("HEAD does not point to a branch"));
+
+ if (!branch->merge || !branch->merge[0]) {
+ /*
+ * no merge config; is it because the user didn't define any,
+ * or because it is not a real branch, and get_branch
+ * auto-vivified it?
+ */
+ if (!ref_exists(branch->refname))
+ return error_buf(err, _("no such branch: '%s'"),
+ branch->name);
+ return error_buf(err,
+ _("no upstream configured for branch '%s'"),
+ branch->name);
+ }
+
+ if (!branch->merge[0]->dst)
+ return error_buf(err,
+ _("upstream branch '%s' not stored as a remote-tracking branch"),
+ branch->merge[0]->src);
+
+ return branch->merge[0]->dst;
+}
+
+static const char *tracking_for_push_dest(struct remote *remote,
+ const char *refname,
+ struct strbuf *err)
+{
+ char *ret;
+
+ ret = apply_refspecs(remote->fetch, remote->fetch_refspec_nr, refname);
+ if (!ret)
+ return error_buf(err,
+ _("push destination '%s' on remote '%s' has no local tracking branch"),
+ refname, remote->name);
+ return ret;
+}
+
+static const char *branch_get_push_1(struct branch *branch, struct strbuf *err)
+{
+ struct remote *remote;
+
+ if (!branch)
+ return error_buf(err, _("HEAD does not point to a branch"));
+
+ remote = remote_get(pushremote_for_branch(branch, NULL));
+ if (!remote)
+ return error_buf(err,
+ _("branch '%s' has no remote for pushing"),
+ branch->name);
+
+ if (remote->push_refspec_nr) {
+ char *dst;
+ const char *ret;
+
+ dst = apply_refspecs(remote->push, remote->push_refspec_nr,
+ branch->refname);
+ if (!dst)
+ return error_buf(err,
+ _("push refspecs for '%s' do not include '%s'"),
+ remote->name, branch->name);
+
+ ret = tracking_for_push_dest(remote, dst, err);
+ free(dst);
+ return ret;
+ }
+
+ if (remote->mirror)
+ return tracking_for_push_dest(remote, branch->refname, err);
+
+ switch (push_default) {
+ case PUSH_DEFAULT_NOTHING:
+ return error_buf(err, _("push has no destination (push.default is 'nothing')"));
+
+ case PUSH_DEFAULT_MATCHING:
+ case PUSH_DEFAULT_CURRENT:
+ return tracking_for_push_dest(remote, branch->refname, err);
+
+ case PUSH_DEFAULT_UPSTREAM:
+ return branch_get_upstream(branch, err);
+
+ case PUSH_DEFAULT_UNSPECIFIED:
+ case PUSH_DEFAULT_SIMPLE:
+ {
+ const char *up, *cur;
+
+ up = branch_get_upstream(branch, err);
+ if (!up)
+ return NULL;
+ cur = tracking_for_push_dest(remote, branch->refname, err);
+ if (!cur)
+ return NULL;
+ if (strcmp(cur, up))
+ return error_buf(err,
+ _("cannot resolve 'simple' push to a single destination"));
+ return cur;
+ }
+ }
+
+ die("BUG: unhandled push situation");
+}
+
+const char *branch_get_push(struct branch *branch, struct strbuf *err)
+{
+ if (!branch->push_tracking_ref)
+ branch->push_tracking_ref = branch_get_push_1(branch, err);
+ return branch->push_tracking_ref;
+}
+
static int ignore_symref_update(const char *refname)
{
unsigned char sha1[20];
/*
* Compare a branch with its upstream, and save their differences (number
- * of commits) in *num_ours and *num_theirs.
+ * of commits) in *num_ours and *num_theirs. The name of the upstream branch
+ * (or NULL if no upstream is defined) is returned via *upstream_name, if it
+ * is not itself NULL.
*
- * Return 0 if branch has no upstream (no base), -1 if upstream is missing
- * (with "gone" base), otherwise 1 (with base).
+ * Returns -1 if num_ours and num_theirs could not be filled in (e.g., no
+ * upstream defined, or ref does not exist), 0 otherwise.
*/
-int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs)
+int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
+ const char **upstream_name)
{
unsigned char sha1[20];
struct commit *ours, *theirs;
int rev_argc;
/* Cannot stat unless we are marked to build on top of somebody else. */
- if (!branch ||
- !branch->merge || !branch->merge[0] || !branch->merge[0]->dst)
- return 0;
+ base = branch_get_upstream(branch, NULL);
+ if (upstream_name)
+ *upstream_name = base;
+ if (!base)
+ return -1;
/* Cannot stat if what we used to build on no longer exists */
- base = branch->merge[0]->dst;
if (read_ref(base, sha1))
return -1;
theirs = lookup_commit_reference(sha1);
/* are we the same? */
if (theirs == ours) {
*num_theirs = *num_ours = 0;
- return 1;
+ return 0;
}
/* Run "rev-list --left-right ours...theirs" internally... */
/* clear object flags smudged by the above traversal */
clear_commit_marks(ours, ALL_REV_FLAGS);
clear_commit_marks(theirs, ALL_REV_FLAGS);
- return 1;
+ return 0;
}
/*
int format_tracking_info(struct branch *branch, struct strbuf *sb)
{
int ours, theirs;
+ const char *full_base;
char *base;
int upstream_is_gone = 0;
- switch (stat_tracking_info(branch, &ours, &theirs)) {
- case 0:
- /* no base */
- return 0;
- case -1:
- /* with "gone" base */
+ if (stat_tracking_info(branch, &ours, &theirs, &full_base) < 0) {
+ if (!full_base)
+ return 0;
upstream_is_gone = 1;
- break;
- default:
- /* with base */
- break;
}
- base = shorten_unambiguous_ref(branch->merge[0]->dst, 0);
+ base = shorten_unambiguous_ref(full_base, 0);
if (upstream_is_gone) {
strbuf_addf(sb,
_("Your branch is based on '%s', but the upstream is gone.\n"),
return 1;
}
-static int one_local_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+static int one_local_ref(const char *refname, const struct object_id *oid,
+ int flag, void *cb_data)
{
struct ref ***local_tail = cb_data;
struct ref *ref;
len = strlen(refname) + 1;
ref = xcalloc(1, sizeof(*ref) + len);
- hashcpy(ref->new_sha1, sha1);
+ hashcpy(ref->new_sha1, oid->hash);
memcpy(ref->name, refname, len);
**local_tail = ref;
*local_tail = &ref->next;
struct ref *get_local_heads(void)
{
struct ref *local_refs = NULL, **local_tail = &local_refs;
+
for_each_ref(one_local_ref, &local_tail);
return local_refs;
}
int ref_count;
};
-static int get_stale_heads_cb(const char *refname,
- const unsigned char *sha1, int flags, void *cb_data)
+static int get_stale_heads_cb(const char *refname, const struct object_id *oid,
+ int flags, void *cb_data)
{
struct stale_heads_info *info = cb_data;
struct string_list matches = STRING_LIST_INIT_DUP;
if (stale) {
struct ref *ref = make_linked_ref(refname, &info->stale_refs_tail);
- hashcpy(ref->new_sha1, sha1);
+ hashcpy(ref->new_sha1, oid->hash);
}
clean_exit:
struct ref *ref, *stale_refs = NULL;
struct string_list ref_names = STRING_LIST_INIT_NODUP;
struct stale_heads_info info;
+
info.ref_names = &ref_names;
info.stale_refs_tail = &stale_refs;
info.refs = refs;
const char *refname;
const char *remote_name;
- struct remote *remote;
+ const char *pushremote_name;
const char **merge_name;
struct refspec **merge;
int merge_nr;
int merge_alloc;
+
+ const char *push_tracking_ref;
};
struct branch *branch_get(const char *name);
+const char *remote_for_branch(struct branch *branch, int *explicit);
+const char *pushremote_for_branch(struct branch *branch, int *explicit);
int branch_has_merge_config(struct branch *branch);
int branch_merge_matches(struct branch *, int n, const char *);
+/**
+ * Return the fully-qualified refname of the tracking branch for `branch`.
+ * I.e., what "branch@{upstream}" would give you. Returns NULL if no
+ * upstream is defined.
+ *
+ * If `err` is not NULL and no upstream is defined, a more specific error
+ * message is recorded there (if the function does not return NULL, then
+ * `err` is not touched).
+ */
+const char *branch_get_upstream(struct branch *branch, struct strbuf *err);
+
+/**
+ * Return the tracking branch that corresponds to the ref we would push to
+ * given a bare `git push` while `branch` is checked out.
+ *
+ * The return value and `err` conventions match those of `branch_get_upstream`.
+ */
+const char *branch_get_push(struct branch *branch, struct strbuf *err);
+
/* Flags to match_refs. */
enum match_refs_flags {
MATCH_REFS_NONE = 0,
};
/* Reporting of tracking info */
-int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs);
+int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
+ const char **upstream_name);
int format_tracking_info(struct branch *branch, struct strbuf *sb);
struct ref *get_local_heads(void);
}
static int register_replace_ref(const char *refname,
- const unsigned char *sha1,
+ const struct object_id *oid,
int flag, void *cb_data)
{
/* Get sha1 from refname */
}
/* Copy sha1 from the read ref */
- hashcpy(repl_obj->replacement, sha1);
+ hashcpy(repl_obj->replacement, oid->hash);
/* Register new object */
if (register_replace_object(repl_obj, 1))
parent = parent->next;
if (p)
p->object.flags |= UNINTERESTING;
- if (parse_commit(p) < 0)
+ if (parse_commit_gently(p, 1) < 0)
continue;
if (p->parents)
mark_parents_uninteresting(p);
for (parent = commit->parents; parent; parent = parent->next) {
struct commit *p = parent->item;
- if (parse_commit(p) < 0)
+ if (parse_commit_gently(p, revs->ignore_missing_links) < 0)
return -1;
if (revs->show_source && !p->util)
p->util = commit->util;
return 0;
}
-static int handle_one_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data)
+static int handle_one_ref(const char *path, const struct object_id *oid,
+ int flag, void *cb_data)
{
struct all_refs_cb *cb = cb_data;
struct object *object;
if (ref_excluded(cb->all_revs->ref_excludes, path))
return 0;
- object = get_reference(cb->all_revs, path, sha1, cb->all_flags);
+ object = get_reference(cb->all_revs, path, oid->hash, cb->all_flags);
add_rev_cmdline(cb->all_revs, object, path, REV_CMD_REF, cb->all_flags);
- add_pending_sha1(cb->all_revs, path, sha1, cb->all_flags);
+ add_pending_sha1(cb->all_revs, path, oid->hash, cb->all_flags);
return 0;
}
return 0;
}
-static int handle_one_reflog(const char *path, const unsigned char *sha1, int flag, void *cb_data)
+static int handle_one_reflog(const char *path, const struct object_id *oid,
+ int flag, void *cb_data)
{
struct all_refs_cb *cb = cb_data;
cb->warned_bad_reflog = 0;
void add_reflogs_to_pending(struct rev_info *revs, unsigned flags)
{
struct all_refs_cb cb;
+
cb.all_revs = revs;
cb.all_flags = flags;
for_each_reflog(handle_one_reflog, &cb);
#endif
}
-char *find_hook(const char *name)
+const char *find_hook(const char *name)
{
- char *path = git_path("hooks/%s", name);
+ const char *path = git_path("hooks/%s", name);
if (access(path, X_OK) < 0)
path = NULL;
int finish_command(struct child_process *);
int run_command(struct child_process *);
-extern char *find_hook(const char *name);
+extern const char *find_hook(const char *name);
LAST_ARG_MUST_BE_NULL
extern int run_hook_le(const char *const *env, const char *name, ...);
extern int run_hook_ve(const char *const *env, const char *name, va_list args);
{
struct strbuf *sb = cb;
if (graft->nr_parent == -1)
- packet_buf_write(sb, "shallow %s\n", sha1_to_hex(graft->sha1));
+ packet_buf_write(sb, "shallow %s\n", oid_to_hex(&graft->oid));
return 0;
}
return ret;
}
-static int add_info_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data)
+static int add_info_ref(const char *path, const struct object_id *oid,
+ int flag, void *cb_data)
{
FILE *fp = cb_data;
- struct object *o = parse_object(sha1);
+ struct object *o = parse_object(oid->hash);
if (!o)
return -1;
- if (fprintf(fp, "%s %s\n", sha1_to_hex(sha1), path) < 0)
+ if (fprintf(fp, "%s %s\n", oid_to_hex(oid), path) < 0)
return -1;
if (o->type == OBJ_TAG) {
static int inside_git_dir = -1;
static int inside_work_tree = -1;
+static int work_tree_config_is_bogus;
/*
* The input parameter must contain an absolute path, and it must already be
if (arg[2] == '\0') /* ":/" is root dir, always exists */
return 1;
name = arg + 2;
- } else if (prefix)
+ } else if (!no_wildcard(arg))
+ return 1;
+ else if (prefix)
name = prefix_filename(prefix, strlen(prefix), arg);
else
name = arg;
"'git <command> [<revision>...] -- [<file>...]'", arg);
}
+int get_common_dir(struct strbuf *sb, const char *gitdir)
+{
+ struct strbuf data = STRBUF_INIT;
+ struct strbuf path = STRBUF_INIT;
+ const char *git_common_dir = getenv(GIT_COMMON_DIR_ENVIRONMENT);
+ int ret = 0;
+ if (git_common_dir) {
+ strbuf_addstr(sb, git_common_dir);
+ return 1;
+ }
+ strbuf_addf(&path, "%s/commondir", gitdir);
+ if (file_exists(path.buf)) {
+ if (strbuf_read_file(&data, path.buf, 0) <= 0)
+ die_errno(_("failed to read %s"), path.buf);
+ while (data.len && (data.buf[data.len - 1] == '\n' ||
+ data.buf[data.len - 1] == '\r'))
+ data.len--;
+ data.buf[data.len] = '\0';
+ strbuf_reset(&path);
+ if (!is_absolute_path(data.buf))
+ strbuf_addf(&path, "%s/", gitdir);
+ strbuf_addbuf(&path, &data);
+ strbuf_addstr(sb, real_path(path.buf));
+ ret = 1;
+ } else
+ strbuf_addstr(sb, gitdir);
+ strbuf_release(&data);
+ strbuf_release(&path);
+ return ret;
+}
/*
* Test if it looks like we're at a git directory.
*/
int is_git_directory(const char *suspect)
{
- char path[PATH_MAX];
- size_t len = strlen(suspect);
+ struct strbuf path = STRBUF_INIT;
+ int ret = 0;
+ size_t len;
+
+ /* Check worktree-related signatures */
+ strbuf_addf(&path, "%s/HEAD", suspect);
+ if (validate_headref(path.buf))
+ goto done;
+
+ strbuf_reset(&path);
+ get_common_dir(&path, suspect);
+ len = path.len;
- if (PATH_MAX <= len + strlen("/objects"))
- die("Too long path: %.*s", 60, suspect);
- strcpy(path, suspect);
+ /* Check non-worktree-related signatures */
if (getenv(DB_ENVIRONMENT)) {
if (access(getenv(DB_ENVIRONMENT), X_OK))
- return 0;
+ goto done;
}
else {
- strcpy(path + len, "/objects");
- if (access(path, X_OK))
- return 0;
+ strbuf_setlen(&path, len);
+ strbuf_addstr(&path, "/objects");
+ if (access(path.buf, X_OK))
+ goto done;
}
- strcpy(path + len, "/refs");
- if (access(path, X_OK))
- return 0;
-
- strcpy(path + len, "/HEAD");
- if (validate_headref(path))
- return 0;
+ strbuf_setlen(&path, len);
+ strbuf_addstr(&path, "/refs");
+ if (access(path.buf, X_OK))
+ goto done;
- return 1;
+ ret = 1;
+done:
+ strbuf_release(&path);
+ return ret;
}
int is_inside_git_dir(void)
if (initialized)
return;
+
+ if (work_tree_config_is_bogus)
+ die("unable to set up work tree using invalid config");
+
work_tree = get_git_work_tree();
git_dir = get_git_dir();
if (!is_absolute_path(git_dir))
initialized = 1;
}
+static int check_repo_format(const char *var, const char *value, void *cb)
+{
+ if (strcmp(var, "core.repositoryformatversion") == 0)
+ repository_format_version = git_config_int(var, value);
+ else if (strcmp(var, "core.sharedrepository") == 0)
+ shared_repository = git_config_perm(var, value);
+ return 0;
+}
+
static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
{
- char repo_config[PATH_MAX+1];
+ struct strbuf sb = STRBUF_INIT;
+ const char *repo_config;
+ config_fn_t fn;
+ int ret = 0;
+
+ if (get_common_dir(&sb, gitdir))
+ fn = check_repo_format;
+ else
+ fn = check_repository_format_version;
+ strbuf_addstr(&sb, "/config");
+ repo_config = sb.buf;
/*
* git_config() can't be used here because it calls git_pathdup()
* Use a gentler version of git_config() to check if this repo
* is a good one.
*/
- snprintf(repo_config, PATH_MAX, "%s/config", gitdir);
- git_config_early(check_repository_format_version, NULL, repo_config);
+ git_config_early(fn, NULL, repo_config);
if (GIT_REPO_VERSION < repository_format_version) {
if (!nongit_ok)
die ("Expected git repo version <= %d, found %d",
GIT_REPO_VERSION, repository_format_version);
warning("Please upgrade Git");
*nongit_ok = -1;
- return -1;
+ ret = -1;
}
- return 0;
+ strbuf_release(&sb);
+ return ret;
+}
+
+static void update_linked_gitdir(const char *gitfile, const char *gitdir)
+{
+ struct strbuf path = STRBUF_INIT;
+ struct stat st;
+
+ strbuf_addf(&path, "%s/gitfile", gitdir);
+ if (stat(path.buf, &st) || st.st_mtime + 24 * 3600 < time(NULL))
+ write_file(path.buf, 0, "%s\n", gitfile);
+ strbuf_release(&path);
}
/*
if (!is_git_directory(dir))
die("Not a git repository: %s", dir);
+
+ update_linked_gitdir(path, dir);
path = real_path(dir);
free(buf);
if (work_tree_env)
set_git_work_tree(work_tree_env);
else if (is_bare_repository_cfg > 0) {
- if (git_work_tree_cfg) /* #22.2, #30 */
- die("core.bare and core.worktree do not make sense");
+ if (git_work_tree_cfg) {
+ /* #22.2, #30 */
+ warning("core.bare and core.worktree do not make sense");
+ work_tree_config_is_bogus = 1;
+ }
/* #18, #26 */
set_git_dir(gitdirenv);
int check_repository_format_version(const char *var, const char *value, void *cb)
{
- if (strcmp(var, "core.repositoryformatversion") == 0)
- repository_format_version = git_config_int(var, value);
- else if (strcmp(var, "core.sharedrepository") == 0)
- shared_repository = git_config_perm(var, value);
- else if (strcmp(var, "core.bare") == 0) {
+ int ret = check_repo_format(var, value, cb);
+ if (ret)
+ return ret;
+ if (strcmp(var, "core.bare") == 0) {
is_bare_repository_cfg = git_config_bool(var, value);
if (is_bare_repository_cfg == 1)
inside_work_tree = -1;
{
struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
int fd = hold_lock_file_for_append(lock, git_path("objects/info/alternates"), LOCK_DIE_ON_ERROR);
- char *alt = mkpath("%s\n", reference);
+ const char *alt = mkpath("%s\n", reference);
write_or_die(fd, alt, strlen(alt));
if (commit_lock_file(lock))
die("could not close alternates file");
(uintmax_t)length, (uintmax_t)limit);
}
-void *xmmap(void *start, size_t length,
- int prot, int flags, int fd, off_t offset)
+void *xmmap_gently(void *start, size_t length,
+ int prot, int flags, int fd, off_t offset)
{
void *ret;
return NULL;
release_pack_memory(length);
ret = mmap(start, length, prot, flags, fd, offset);
- if (ret == MAP_FAILED)
- die_errno("Out of memory? mmap failed");
}
return ret;
}
+void *xmmap(void *start, size_t length,
+ int prot, int flags, int fd, off_t offset)
+{
+ void *ret = xmmap_gently(start, length, prot, flags, fd, offset);
+ if (ret == MAP_FAILED)
+ die_errno("mmap failed");
+ return ret;
+}
+
void close_pack_windows(struct packed_git *p)
{
while (p->windows) {
return git_inflate(stream, 0);
}
+static int unpack_sha1_header_to_strbuf(git_zstream *stream, unsigned char *map,
+ unsigned long mapsize, void *buffer,
+ unsigned long bufsiz, struct strbuf *header)
+{
+ int status;
+
+ status = unpack_sha1_header(stream, map, mapsize, buffer, bufsiz);
+
+ /*
+ * Check if entire header is unpacked in the first iteration.
+ */
+ if (memchr(buffer, '\0', stream->next_out - (unsigned char *)buffer))
+ return 0;
+
+ /*
+ * buffer[0..bufsiz] was not large enough. Copy the partial
+ * result out to header, and then append the result of further
+ * reading the stream.
+ */
+ strbuf_add(header, buffer, stream->next_out - (unsigned char *)buffer);
+ stream->next_out = buffer;
+ stream->avail_out = bufsiz;
+
+ do {
+ status = git_inflate(stream, 0);
+ strbuf_add(header, buffer, stream->next_out - (unsigned char *)buffer);
+ if (memchr(buffer, '\0', stream->next_out - (unsigned char *)buffer))
+ return 0;
+ stream->next_out = buffer;
+ stream->avail_out = bufsiz;
+ } while (status != Z_STREAM_END);
+ return -1;
+}
+
static void *unpack_sha1_rest(git_zstream *stream, void *buffer, unsigned long size, const unsigned char *sha1)
{
int bytes = strlen(buffer) + 1;
* too permissive for what we want to check. So do an anal
* object header parse by hand.
*/
-int parse_sha1_header(const char *hdr, unsigned long *sizep)
+static int parse_sha1_header_extended(const char *hdr, struct object_info *oi,
+ unsigned int flags)
{
- char type[10];
- int i;
+ const char *type_buf = hdr;
unsigned long size;
+ int type, type_len = 0;
/*
- * The type can be at most ten bytes (including the
- * terminating '\0' that we add), and is followed by
+ * The type can be of any size but is followed by
* a space.
*/
- i = 0;
for (;;) {
char c = *hdr++;
if (c == ' ')
break;
- type[i++] = c;
- if (i >= sizeof(type))
- return -1;
+ type_len++;
}
- type[i] = 0;
+
+ type = type_from_string_gently(type_buf, type_len, 1);
+ if (oi->typename)
+ strbuf_add(oi->typename, type_buf, type_len);
+ /*
+ * Set type to 0 if its an unknown object and
+ * we're obtaining the type using '--allow-unkown-type'
+ * option.
+ */
+ if ((flags & LOOKUP_UNKNOWN_OBJECT) && (type < 0))
+ type = 0;
+ else if (type < 0)
+ die("invalid object type");
+ if (oi->typep)
+ *oi->typep = type;
/*
* The length must follow immediately, and be in canonical
size = size * 10 + c;
}
}
- *sizep = size;
+
+ if (oi->sizep)
+ *oi->sizep = size;
/*
* The length must be followed by a zero byte
*/
- return *hdr ? -1 : type_from_string(type);
+ return *hdr ? -1 : type;
+}
+
+int parse_sha1_header(const char *hdr, unsigned long *sizep)
+{
+ struct object_info oi;
+
+ oi.sizep = sizep;
+ oi.typename = NULL;
+ oi.typep = NULL;
+ return parse_sha1_header_extended(hdr, &oi, LOOKUP_REPLACE_OBJECT);
}
static void *unpack_sha1_file(void *map, unsigned long mapsize, enum object_type *type, unsigned long *size, const unsigned char *sha1)
}
static int sha1_loose_object_info(const unsigned char *sha1,
- struct object_info *oi)
+ struct object_info *oi,
+ int flags)
{
- int status;
- unsigned long mapsize, size;
+ int status = 0;
+ unsigned long mapsize;
void *map;
git_zstream stream;
char hdr[32];
+ struct strbuf hdrbuf = STRBUF_INIT;
if (oi->delta_base_sha1)
hashclr(oi->delta_base_sha1);
* return value implicitly indicates whether the
* object even exists.
*/
- if (!oi->typep && !oi->sizep) {
+ if (!oi->typep && !oi->typename && !oi->sizep) {
struct stat st;
if (stat_sha1_file(sha1, &st) < 0)
return -1;
return -1;
if (oi->disk_sizep)
*oi->disk_sizep = mapsize;
- if (unpack_sha1_header(&stream, map, mapsize, hdr, sizeof(hdr)) < 0)
+ if ((flags & LOOKUP_UNKNOWN_OBJECT)) {
+ if (unpack_sha1_header_to_strbuf(&stream, map, mapsize, hdr, sizeof(hdr), &hdrbuf) < 0)
+ status = error("unable to unpack %s header with --allow-unknown-type",
+ sha1_to_hex(sha1));
+ } else if (unpack_sha1_header(&stream, map, mapsize, hdr, sizeof(hdr)) < 0)
status = error("unable to unpack %s header",
sha1_to_hex(sha1));
- else if ((status = parse_sha1_header(hdr, &size)) < 0)
+ if (status < 0)
+ ; /* Do nothing */
+ else if (hdrbuf.len) {
+ if ((status = parse_sha1_header_extended(hdrbuf.buf, oi, flags)) < 0)
+ status = error("unable to parse %s header with --allow-unknown-type",
+ sha1_to_hex(sha1));
+ } else if ((status = parse_sha1_header_extended(hdr, oi, flags)) < 0)
status = error("unable to parse %s header", sha1_to_hex(sha1));
- else if (oi->sizep)
- *oi->sizep = size;
git_inflate_end(&stream);
munmap(map, mapsize);
- if (oi->typep)
+ if (status && oi->typep)
*oi->typep = status;
+ strbuf_release(&hdrbuf);
return 0;
}
struct cached_object *co;
struct pack_entry e;
int rtype;
+ enum object_type real_type;
const unsigned char *real = lookup_replace_object_extended(sha1, flags);
co = find_cached_object(real);
*(oi->disk_sizep) = 0;
if (oi->delta_base_sha1)
hashclr(oi->delta_base_sha1);
+ if (oi->typename)
+ strbuf_addstr(oi->typename, typename(co->type));
oi->whence = OI_CACHED;
return 0;
}
if (!find_pack_entry(real, &e)) {
/* Most likely it's a loose object. */
- if (!sha1_loose_object_info(real, oi)) {
+ if (!sha1_loose_object_info(real, oi, flags)) {
oi->whence = OI_LOOSE;
return 0;
}
return -1;
}
+ /*
+ * packed_object_info() does not follow the delta chain to
+ * find out the real type, unless it is given oi->typep.
+ */
+ if (oi->typename && !oi->typep)
+ oi->typep = &real_type;
+
rtype = packed_object_info(e.p, e.offset, oi);
if (rtype < 0) {
mark_bad_packed_object(e.p, real);
+ if (oi->typep == &real_type)
+ oi->typep = NULL;
return sha1_object_info_extended(real, oi, 0);
} else if (in_delta_base_cache(e.p, e.offset)) {
oi->whence = OI_DBCACHED;
oi->u.packed.is_delta = (rtype == OBJ_REF_DELTA ||
rtype == OBJ_OFS_DELTA);
}
+ if (oi->typename)
+ strbuf_addstr(oi->typename, typename(*oi->typep));
+ if (oi->typep == &real_type)
+ oi->typep = NULL;
return 0;
}
return find_pack_entry(sha1, &e);
}
-int has_sha1_file(const unsigned char *sha1)
+int has_sha1_file_with_flags(const unsigned char *sha1, int flags)
{
struct pack_entry e;
return 1;
if (has_loose_object(sha1))
return 1;
+ if (flags & HAS_SHA1_QUICK)
+ return 0;
reprepare_packed_git();
return find_pack_entry(sha1, &e);
}
{
struct packed_git *p;
int r = 0;
+ int pack_errors = 0;
prepare_packed_git();
for (p = packed_git; p; p = p->next) {
if ((flags & FOR_EACH_OBJECT_LOCAL_ONLY) && !p->pack_local)
continue;
+ if (open_pack_index(p)) {
+ pack_errors = 1;
+ continue;
+ }
r = for_each_object_in_pack(p, cb, data);
if (r)
break;
}
- return r;
+ return r ? r : pack_errors;
}
return slash;
}
-static inline int upstream_mark(const char *string, int len)
+static inline int at_mark(const char *string, int len,
+ const char **suffix, int nr)
{
- const char *suffix[] = { "@{upstream}", "@{u}" };
int i;
- for (i = 0; i < ARRAY_SIZE(suffix); i++) {
+ for (i = 0; i < nr; i++) {
int suffix_len = strlen(suffix[i]);
if (suffix_len <= len
&& !memcmp(string, suffix[i], suffix_len))
return 0;
}
+static inline int upstream_mark(const char *string, int len)
+{
+ const char *suffix[] = { "@{upstream}", "@{u}" };
+ return at_mark(string, len, suffix, ARRAY_SIZE(suffix));
+}
+
+static inline int push_mark(const char *string, int len)
+{
+ const char *suffix[] = { "@{push}" };
+ return at_mark(string, len, suffix, ARRAY_SIZE(suffix));
+}
+
static int get_sha1_1(const char *name, int len, unsigned char *sha1, unsigned lookup_flags);
static int interpret_nth_prior_checkout(const char *name, int namelen, struct strbuf *buf);
nth_prior = 1;
continue;
}
- if (!upstream_mark(str + at, len - at)) {
+ if (!upstream_mark(str + at, len - at) &&
+ !push_mark(str + at, len - at)) {
reflog_len = (len-1) - (at+2);
len = at;
}
/* Remember to update object flag allocation in object.h */
#define ONELINE_SEEN (1u<<20)
-static int handle_one_ref(const char *path,
- const unsigned char *sha1, int flag, void *cb_data)
+static int handle_one_ref(const char *path, const struct object_id *oid,
+ int flag, void *cb_data)
{
struct commit_list **list = cb_data;
- struct object *object = parse_object(sha1);
+ struct object *object = parse_object(oid->hash);
if (!object)
return 0;
if (object->type == OBJ_TAG) {
free(s);
}
-static const char *get_upstream_branch(const char *branch_buf, int len)
-{
- char *branch = xstrndup(branch_buf, len);
- struct branch *upstream = branch_get(*branch ? branch : NULL);
-
- /*
- * Upstream can be NULL only if branch refers to HEAD and HEAD
- * points to something different than a branch.
- */
- if (!upstream)
- die(_("HEAD does not point to a branch"));
- if (!upstream->merge || !upstream->merge[0]->dst) {
- if (!ref_exists(upstream->refname))
- die(_("No such branch: '%s'"), branch);
- if (!upstream->merge) {
- die(_("No upstream configured for branch '%s'"),
- upstream->name);
- }
- die(
- _("Upstream branch '%s' not stored as a remote-tracking branch"),
- upstream->merge[0]->src);
- }
- free(branch);
-
- return upstream->merge[0]->dst;
-}
-
-static int interpret_upstream_mark(const char *name, int namelen,
- int at, struct strbuf *buf)
+static int interpret_branch_mark(const char *name, int namelen,
+ int at, struct strbuf *buf,
+ int (*get_mark)(const char *, int),
+ const char *(*get_data)(struct branch *,
+ struct strbuf *))
{
int len;
+ struct branch *branch;
+ struct strbuf err = STRBUF_INIT;
+ const char *value;
- len = upstream_mark(name + at, namelen - at);
+ len = get_mark(name + at, namelen - at);
if (!len)
return -1;
if (memchr(name, ':', at))
return -1;
- set_shortened_ref(buf, get_upstream_branch(name, at));
+ if (at) {
+ char *name_str = xmemdupz(name, at);
+ branch = branch_get(name_str);
+ free(name_str);
+ } else
+ branch = branch_get(NULL);
+
+ value = get_data(branch, &err);
+ if (!value)
+ die("%s", err.buf);
+
+ set_shortened_ref(buf, value);
return len + at;
}
if (len > 0)
return reinterpret(name, namelen, len, buf);
- len = interpret_upstream_mark(name, namelen, at - name, buf);
+ len = interpret_branch_mark(name, namelen, at - name, buf,
+ upstream_mark, branch_get_upstream);
+ if (len > 0)
+ return len;
+
+ len = interpret_branch_mark(name, namelen, at - name, buf,
+ push_mark, branch_get_push);
if (len > 0)
return len;
}
int pos;
if (!only_to_die && namelen > 2 && name[1] == '/') {
struct commit_list *list = NULL;
+
for_each_ref(handle_one_ref, &list);
commit_list_sort_by_date(&list);
return get_sha1_oneline(name + 2, sha1, list);
new_filename = resolve_relative_path(filename);
if (new_filename)
filename = new_filename;
- ret = get_tree_entry(tree_sha1, filename, sha1, &oc->mode);
- if (ret && only_to_die) {
- diagnose_invalid_sha1_path(prefix, filename,
- tree_sha1,
- name, len);
+ if (flags & GET_SHA1_FOLLOW_SYMLINKS) {
+ ret = get_tree_entry_follow_symlinks(tree_sha1,
+ filename, sha1, &oc->symlink_path,
+ &oc->mode);
+ } else {
+ ret = get_tree_entry(tree_sha1, filename,
+ sha1, &oc->mode);
+ if (ret && only_to_die) {
+ diagnose_invalid_sha1_path(prefix,
+ filename,
+ tree_sha1,
+ name, len);
+ }
}
hashcpy(oc->tree, tree_sha1);
strlcpy(oc->path, filename, sizeof(oc->path));
int get_sha1_with_context(const char *str, unsigned flags, unsigned char *sha1, struct object_context *orc)
{
+ if (flags & GET_SHA1_FOLLOW_SYMLINKS && flags & GET_SHA1_ONLY_TO_DIE)
+ die("BUG: incompatible flags for get_sha1_with_context");
return get_sha1_with_context_1(str, flags, NULL, sha1, orc);
}
xmalloc(sizeof(struct commit_graft));
struct commit *commit = lookup_commit(sha1);
- hashcpy(graft->sha1, sha1);
+ hashcpy(graft->oid.hash, sha1);
graft->nr_parent = -1;
if (commit && commit->object.parsed)
commit->parents = NULL;
static int write_one_shallow(const struct commit_graft *graft, void *cb_data)
{
struct write_shallow_data *data = cb_data;
- const char *hex = sha1_to_hex(graft->sha1);
+ const char *hex = oid_to_hex(&graft->oid);
if (graft->nr_parent != -1)
return 0;
if (data->flags & SEEN_ONLY) {
- struct commit *c = lookup_commit(graft->sha1);
+ struct commit *c = lookup_commit(graft->oid.hash);
if (!c || !(c->object.flags & SEEN)) {
if (data->flags & VERBOSE)
printf("Removing %s from .git/shallow\n",
{
int fd = *(int *)cb;
if (graft->nr_parent == -1)
- packet_write(fd, "shallow %s\n", sha1_to_hex(graft->sha1));
+ packet_write(fd, "shallow %s\n", oid_to_hex(&graft->oid));
return 0;
}
free(tmp);
}
-static int mark_uninteresting(const char *refname,
- const unsigned char *sha1,
+static int mark_uninteresting(const char *refname, const struct object_id *oid,
int flags, void *cb_data)
{
- struct commit *commit = lookup_commit_reference_gently(sha1, 1);
+ struct commit *commit = lookup_commit_reference_gently(oid->hash, 1);
if (!commit)
return 0;
commit->object.flags |= UNINTERESTING;
int nr, alloc;
};
-static int add_ref(const char *refname,
- const unsigned char *sha1, int flags, void *cb_data)
+static int add_ref(const char *refname, const struct object_id *oid,
+ int flags, void *cb_data)
{
struct commit_array *ca = cb_data;
ALLOC_GROW(ca->commits, ca->nr + 1, ca->alloc);
- ca->commits[ca->nr] = lookup_commit_reference_gently(sha1, 1);
+ ca->commits[ca->nr] = lookup_commit_reference_gently(oid->hash, 1);
if (ca->commits[ca->nr])
ca->nr++;
return 0;
if (!si->commits) {
struct commit_array ca;
+
memset(&ca, 0, sizeof(ca));
head_ref(add_ref, &ca);
for_each_ref(add_ref, &ca);
return 0;
}
-static int write_strbuf(void *user_data, const void *data, size_t len)
-{
- struct strbuf *sb = user_data;
- strbuf_add(sb, data, len);
- return len;
-}
-
int write_link_extension(struct strbuf *sb,
struct index_state *istate)
{
strbuf_add(sb, si->base_sha1, 20);
if (!si->delete_bitmap && !si->replace_bitmap)
return 0;
- ewah_serialize_to(si->delete_bitmap, write_strbuf, sb);
- ewah_serialize_to(si->replace_bitmap, write_strbuf, sb);
+ ewah_serialize_strbuf(si->delete_bitmap, sb);
+ ewah_serialize_strbuf(si->replace_bitmap, sb);
return 0;
}
return -1;
}
+#ifdef HAVE_GETDELIM
+int strbuf_getwholeline(struct strbuf *sb, FILE *fp, int term)
+{
+ ssize_t r;
+
+ if (feof(fp))
+ return EOF;
+
+ strbuf_reset(sb);
+
+ /* Translate slopbuf to NULL, as we cannot call realloc on it */
+ if (!sb->alloc)
+ sb->buf = NULL;
+ r = getdelim(&sb->buf, &sb->alloc, term, fp);
+
+ if (r > 0) {
+ sb->len = r;
+ return 0;
+ }
+ assert(r == -1);
+
+ /*
+ * Normally we would have called xrealloc, which will try to free
+ * memory and recover. But we have no way to tell getdelim() to do so.
+ * Worse, we cannot try to recover ENOMEM ourselves, because we have
+ * no idea how many bytes were read by getdelim.
+ *
+ * Dying here is reasonable. It mirrors what xrealloc would do on
+ * catastrophic memory failure. We skip the opportunity to free pack
+ * memory and retry, but that's unlikely to help for a malloc small
+ * enough to hold a single line of input, anyway.
+ */
+ if (errno == ENOMEM)
+ die("Out of memory, getdelim failed");
+
+ /* Restore slopbuf that we moved out of the way before */
+ if (!sb->buf)
+ strbuf_init(sb, 0);
+ return EOF;
+}
+#else
int strbuf_getwholeline(struct strbuf *sb, FILE *fp, int term)
{
int ch;
return EOF;
strbuf_reset(sb);
- while ((ch = fgetc(fp)) != EOF) {
- strbuf_grow(sb, 1);
+ flockfile(fp);
+ while ((ch = getc_unlocked(fp)) != EOF) {
+ if (!strbuf_avail(sb))
+ strbuf_grow(sb, 1);
sb->buf[sb->len++] = ch;
if (ch == term)
break;
}
+ funlockfile(fp);
if (ch == EOF && sb->len == 0)
return EOF;
sb->buf[sb->len] = '\0';
return 0;
}
+#endif
int strbuf_getline(struct strbuf *sb, FILE *fp, int term)
{
*/
static inline void strbuf_addch(struct strbuf *sb, int c)
{
- strbuf_grow(sb, 1);
+ if (!strbuf_avail(sb))
+ strbuf_grow(sb, 1);
sb->buf[sb->len++] = c;
sb->buf[sb->len] = '\0';
}
config_fetch_recurse_submodules = value;
}
-static int has_remote(const char *refname, const unsigned char *sha1, int flags, void *cb_data)
+static int has_remote(const char *refname, const struct object_id *oid,
+ int flags, void *cb_data)
{
return 1;
}
}
}
-static int add_sha1_to_array(const char *ref, const unsigned char *sha1,
+static int add_sha1_to_array(const char *ref, const struct object_id *oid,
int flags, void *data)
{
- sha1_array_append(data, sha1);
+ sha1_array_append(data, oid->hash);
return 0;
}
struct strbuf file_name = STRBUF_INIT;
struct strbuf rel_path = STRBUF_INIT;
const char *real_work_tree = xstrdup(real_path(work_tree));
- FILE *fp;
/* Update gitfile */
strbuf_addf(&file_name, "%s/.git", work_tree);
- fp = fopen(file_name.buf, "w");
- if (!fp)
- die(_("Could not create git link %s"), file_name.buf);
- fprintf(fp, "gitdir: %s\n", relative_path(git_dir, real_work_tree,
- &rel_path));
- fclose(fp);
+ write_file(file_name.buf, 1, "gitdir: %s\n",
+ relative_path(git_dir, real_work_tree, &rel_path));
/* Update core.worktree setting */
strbuf_reset(&file_name);
(
cd "$db" &&
{
- p4d -q -p $P4DPORT &
+ p4d -q -p $P4DPORT "$@" &
echo $! >"$pidfile"
}
) &&
GIT_MMAP_LIMIT=1m GIT_ALLOC_LIMIT=1m git add 30MB
'
+test_expect_success 'filter that does not read is fine' '
+ test-genrandom foo $((128 * 1024 + 1)) >big &&
+ echo "big filter=epipe" >.gitattributes &&
+ git config filter.epipe.clean "echo xyzzy" &&
+ git add big &&
+ git cat-file blob :big >actual &&
+ echo xyzzy >expect &&
+ test_cmp expect actual
+'
+
test_expect_success EXPENSIVE 'filter large file' '
git config filter.largefile.smudge cat &&
git config filter.largefile.clean cat &&
check_warning () {
case "$1" in
- LF_CRLF) grep "LF will be replaced by CRLF" $2;;
- CRLF_LF) grep "CRLF will be replaced by LF" $2;;
- '')
- >expect
- grep "will be replaced by" $2 >actual
- test_cmp expect actual
- ;;
- *) false ;;
+ LF_CRLF) echo "warning: LF will be replaced by CRLF" >"$2".expect ;;
+ CRLF_LF) echo "warning: CRLF will be replaced by LF" >"$2".expect ;;
+ '') >"$2".expect ;;
+ *) echo >&2 "Illegal 1": "$1" ; return false ;;
esac
+ grep "will be replaced by" "$2" | sed -e "s/\(.*\) in [^ ]*$/\1/" >"$2".actual
+ test_cmp "$2".expect "$2".actual
}
-create_file_in_repo () {
+commit_check_warn () {
crlf=$1
attr=$2
lfname=$3
crlfname=$4
- lfmixcrlf=$5
- lfmixcr=$6
- crlfnul=$7
- create_gitattributes "$attr" &&
+ repoMIX=$5
+ lfmixcrlf=$6
+ lfmixcr=$7
+ crlfnul=$8
pfx=crlf_${crlf}_attr_${attr}
- for f in LF CRLF LF_mix_CR CRLF_mix_LF CRLF_nul
+ # Special handling for repoMIX: It should already be in the repo
+ # with CRLF
+ f=repoMIX
+ fname=${pfx}_$f.txt
+ echo >.gitattributes &&
+ cp $f $fname &&
+ git -c core.autocrlf=false add $fname 2>"${pfx}_$f.err" &&
+ git commit -m "repoMIX" &&
+ create_gitattributes "$attr" &&
+ for f in LF CRLF repoMIX LF_mix_CR CRLF_mix_LF LF_nul CRLF_nul
do
fname=${pfx}_$f.txt &&
cp $f $fname &&
}
-check_files_in_ws () {
+checkout_files () {
eol=$1
crlf=$2
attr=$3
git config core.autocrlf $crlf &&
pfx=eol_${eol}_crlf_${crlf}_attr_${attr}_ &&
src=crlf_false_attr__ &&
- for f in LF CRLF LF_mix_CR CRLF_mix_LF CRLF_nul
+ for f in LF CRLF LF_mix_CR CRLF_mix_LF LF_nul
do
rm $src$f.txt &&
if test -z "$eol"; then
test_expect_success "checkout core.eol=$eol core.autocrlf=$crlf gitattributes=$attr file=LF_mix_CR" "
compare_ws_file $pfx $lfmixcr ${src}LF_mix_CR.txt
"
- test_expect_success "checkout core.eol=$eol core.autocrlf=$crlf gitattributes=$attr file=CRLF_nul" "
- compare_ws_file $pfx $crlfnul ${src}CRLF_nul.txt
+ test_expect_success "checkout core.eol=$eol core.autocrlf=$crlf gitattributes=$attr file=LF_nul" "
+ compare_ws_file $pfx $crlfnul ${src}LF_nul.txt
"
}
git commit -m "add .gitattributes" "" &&
printf "line1\nline2\nline3" >LF &&
printf "line1\r\nline2\r\nline3" >CRLF &&
+ printf "line1\r\nline2\nline3" >repoMIX &&
printf "line1\r\nline2\nline3" >CRLF_mix_LF &&
printf "line1\nline2\rline3" >LF_mix_CR &&
printf "line1\r\nline2\rline3" >CRLF_mix_CR &&
warn_LF_CRLF="LF will be replaced by CRLF"
warn_CRLF_LF="CRLF will be replaced by LF"
-test_expect_success 'add files empty attr' '
- create_file_in_repo false "" "" "" "" "" "" &&
- create_file_in_repo true "" "LF_CRLF" "" "LF_CRLF" "" "" &&
- create_file_in_repo input "" "" "CRLF_LF" "CRLF_LF" "" ""
+# WILC stands for "Warn if (this OS) converts LF into CRLF".
+# WICL: Warn if CRLF becomes LF
+# WAMIX: Mixed line endings: either CRLF->LF or LF->CRLF
+if test_have_prereq NATIVE_CRLF
+then
+ WILC=LF_CRLF
+ WICL=
+ WAMIX=LF_CRLF
+else
+ WILC=
+ WICL=CRLF_LF
+ WAMIX=CRLF_LF
+fi
+
+# attr LF CRLF repoMIX CRLFmixLF LFmixCR CRLFNUL
+test_expect_success 'commit files empty attr' '
+ commit_check_warn false "" "" "" "" "" "" "" &&
+ commit_check_warn true "" "LF_CRLF" "" "LF_CRLF" "LF_CRLF" "" "" &&
+ commit_check_warn input "" "" "CRLF_LF" "CRLF_LF" "CRLF_LF" "" ""
'
-test_expect_success 'add files attr=auto' '
- create_file_in_repo false "auto" "" "CRLF_LF" "CRLF_LF" "" "" &&
- create_file_in_repo true "auto" "LF_CRLF" "" "LF_CRLF" "" "" &&
- create_file_in_repo input "auto" "" "CRLF_LF" "CRLF_LF" "" ""
+test_expect_success 'commit files attr=auto' '
+ commit_check_warn false "auto" "$WILC" "$WICL" "$WAMIX" "$WAMIX" "" "" &&
+ commit_check_warn true "auto" "LF_CRLF" "" "LF_CRLF" "LF_CRLF" "" "" &&
+ commit_check_warn input "auto" "" "CRLF_LF" "CRLF_LF" "CRLF_LF" "" ""
'
-test_expect_success 'add files attr=text' '
- create_file_in_repo false "text" "" "CRLF_LF" "CRLF_LF" "" "CRLF_LF" &&
- create_file_in_repo true "text" "LF_CRLF" "" "LF_CRLF" "LF_CRLF" "" &&
- create_file_in_repo input "text" "" "CRLF_LF" "CRLF_LF" "" "CRLF_LF"
+test_expect_success 'commit files attr=text' '
+ commit_check_warn false "text" "$WILC" "$WICL" "$WAMIX" "$WAMIX" "$WILC" "$WICL" &&
+ commit_check_warn true "text" "LF_CRLF" "" "LF_CRLF" "LF_CRLF" "LF_CRLF" "" &&
+ commit_check_warn input "text" "" "CRLF_LF" "CRLF_LF" "CRLF_LF" "" "CRLF_LF"
'
-test_expect_success 'add files attr=-text' '
- create_file_in_repo false "-text" "" "" "" "" "" &&
- create_file_in_repo true "-text" "" "" "" "" "" &&
- create_file_in_repo input "-text" "" "" "" "" ""
+test_expect_success 'commit files attr=-text' '
+ commit_check_warn false "-text" "" "" "" "" "" "" &&
+ commit_check_warn true "-text" "" "" "" "" "" "" &&
+ commit_check_warn input "-text" "" "" "" "" "" ""
'
-test_expect_success 'add files attr=lf' '
- create_file_in_repo false "lf" "" "CRLF_LF" "CRLF_LF" "" "CRLF_LF" &&
- create_file_in_repo true "lf" "" "CRLF_LF" "CRLF_LF" "" "CRLF_LF" &&
- create_file_in_repo input "lf" "" "CRLF_LF" "CRLF_LF" "" "CRLF_LF"
+test_expect_success 'commit files attr=lf' '
+ commit_check_warn false "lf" "" "CRLF_LF" "CRLF_LF" "CRLF_LF" "" "CRLF_LF" &&
+ commit_check_warn true "lf" "" "CRLF_LF" "CRLF_LF" "CRLF_LF" "" "CRLF_LF" &&
+ commit_check_warn input "lf" "" "CRLF_LF" "CRLF_LF" "CRLF_LF" "" "CRLF_LF"
'
-test_expect_success 'add files attr=crlf' '
- create_file_in_repo false "crlf" "LF_CRLF" "" "LF_CRLF" "LF_CRLF" "" &&
- create_file_in_repo true "crlf" "LF_CRLF" "" "LF_CRLF" "LF_CRLF" "" &&
- create_file_in_repo input "crlf" "LF_CRLF" "" "LF_CRLF" "LF_CRLF" ""
+test_expect_success 'commit files attr=crlf' '
+ commit_check_warn false "crlf" "LF_CRLF" "" "LF_CRLF" "LF_CRLF" "LF_CRLF" "" &&
+ commit_check_warn true "crlf" "LF_CRLF" "" "LF_CRLF" "LF_CRLF" "LF_CRLF" "" &&
+ commit_check_warn input "crlf" "LF_CRLF" "" "LF_CRLF" "LF_CRLF" "LF_CRLF" ""
'
test_expect_success 'create files cleanup' '
################################################################################
# Check how files in the repo are changed when they are checked out
# How to read the table below:
-# - check_files_in_ws will check multiple files with a combination of settings
+# - checkout_files will check multiple files with a combination of settings
# and attributes (core.autocrlf=input is forbidden with core.eol=crlf)
# - parameter $1 : core.eol lf | crlf
# - parameter $2 : core.autocrlf false | true | input
# - parameter $8 : reference for a file with CRLF and a NUL (should be handled as binary when auto)
# What we have in the repo:
-# ----------------- EOL in repo ----------------
-# LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
+# ----------------- EOL in repo ----------------
+# LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
# settings with checkout:
# core. core. .gitattr
# eol acrlf
# ----------------------------------------------
# What we want to have in the working tree:
-if test_have_prereq MINGW
+if test_have_prereq NATIVE_CRLF
then
MIX_CRLF_LF=CRLF
MIX_LF_CR=CRLF_mix_CR
NL=CRLF
+LFNUL=CRLF_nul
else
MIX_CRLF_LF=CRLF_mix_LF
MIX_LF_CR=LF_mix_CR
NL=LF
+LFNUL=LF_nul
fi
export CRLF_MIX_LF_CR MIX NL
-check_files_in_ws lf false "" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws lf true "" CRLF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws lf input "" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws lf false "auto" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws lf true "auto" CRLF CRLF CRLF LF_mix_CR CRLF_nul
-check_files_in_ws lf input "auto" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws lf false "text" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws lf true "text" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
-check_files_in_ws lf input "text" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws lf false "-text" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws lf true "-text" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws lf input "-text" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws lf false "lf" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws lf true "lf" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws lf input "lf" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws lf false "crlf" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
-check_files_in_ws lf true "crlf" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
-check_files_in_ws lf input "crlf" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
-
-check_files_in_ws crlf false "" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws crlf true "" CRLF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws crlf false "auto" CRLF CRLF CRLF LF_mix_CR CRLF_nul
-check_files_in_ws crlf true "auto" CRLF CRLF CRLF LF_mix_CR CRLF_nul
-check_files_in_ws crlf false "text" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
-check_files_in_ws crlf true "text" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
-check_files_in_ws crlf false "-text" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws crlf true "-text" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws crlf false "lf" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws crlf true "lf" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws crlf false "crlf" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
-check_files_in_ws crlf true "crlf" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
-
-check_files_in_ws "" false "" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws "" true "" CRLF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws "" input "" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws "" false "auto" $NL CRLF $MIX_CRLF_LF LF_mix_CR CRLF_nul
-check_files_in_ws "" true "auto" CRLF CRLF CRLF LF_mix_CR CRLF_nul
-check_files_in_ws "" input "auto" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws "" false "text" $NL CRLF $MIX_CRLF_LF $MIX_LF_CR CRLF_nul
-check_files_in_ws "" true "text" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
-check_files_in_ws "" input "text" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws "" false "-text" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws "" true "-text" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws "" input "-text" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws "" false "lf" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws "" true "lf" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws "" input "lf" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws "" false "crlf" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
-check_files_in_ws "" true "crlf" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
-check_files_in_ws "" input "crlf" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
-
-check_files_in_ws native false "" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws native true "" CRLF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws native false "auto" $NL CRLF $MIX_CRLF_LF LF_mix_CR CRLF_nul
-check_files_in_ws native true "auto" CRLF CRLF CRLF LF_mix_CR CRLF_nul
-check_files_in_ws native false "text" $NL CRLF $MIX_CRLF_LF $MIX_LF_CR CRLF_nul
-check_files_in_ws native true "text" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
-check_files_in_ws native false "-text" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws native true "-text" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws native false "lf" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws native true "lf" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
-check_files_in_ws native false "crlf" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
-check_files_in_ws native true "crlf" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
+checkout_files lf false "" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files lf true "" CRLF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files lf input "" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files lf false "auto" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files lf true "auto" CRLF CRLF CRLF LF_mix_CR LF_nul
+checkout_files lf input "auto" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files lf false "text" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files lf true "text" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
+checkout_files lf input "text" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files lf false "-text" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files lf true "-text" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files lf input "-text" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files lf false "lf" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files lf true "lf" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files lf input "lf" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files lf false "crlf" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
+checkout_files lf true "crlf" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
+checkout_files lf input "crlf" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
+
+checkout_files crlf false "" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files crlf true "" CRLF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files crlf false "auto" CRLF CRLF CRLF LF_mix_CR LF_nul
+checkout_files crlf true "auto" CRLF CRLF CRLF LF_mix_CR LF_nul
+checkout_files crlf false "text" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
+checkout_files crlf true "text" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
+checkout_files crlf false "-text" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files crlf true "-text" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files crlf false "lf" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files crlf true "lf" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files crlf false "crlf" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
+checkout_files crlf true "crlf" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
+
+checkout_files "" false "" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files "" true "" CRLF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files "" input "" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files "" false "auto" $NL CRLF $MIX_CRLF_LF LF_mix_CR LF_nul
+checkout_files "" true "auto" CRLF CRLF CRLF LF_mix_CR LF_nul
+checkout_files "" input "auto" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files "" false "text" $NL CRLF $MIX_CRLF_LF $MIX_LF_CR $LFNUL
+checkout_files "" true "text" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
+checkout_files "" input "text" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files "" false "-text" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files "" true "-text" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files "" input "-text" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files "" false "lf" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files "" true "lf" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files "" input "lf" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files "" false "crlf" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
+checkout_files "" true "crlf" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
+checkout_files "" input "crlf" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
+
+checkout_files native false "" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files native true "" CRLF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files native false "auto" $NL CRLF $MIX_CRLF_LF LF_mix_CR LF_nul
+checkout_files native true "auto" CRLF CRLF CRLF LF_mix_CR LF_nul
+checkout_files native false "text" $NL CRLF $MIX_CRLF_LF $MIX_LF_CR $LFNUL
+checkout_files native true "text" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
+checkout_files native false "-text" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files native true "-text" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files native false "lf" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files native true "lf" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+checkout_files native false "crlf" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
+checkout_files native true "crlf" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
test_done
"test \"\$(test-path-utils relative_path '$1' '$2')\" = '$expected'"
}
+test_git_path() {
+ test_expect_success "git-path $1 $2 => $3" "
+ $1 git rev-parse --git-path $2 >actual &&
+ echo $3 >expect &&
+ test_cmp expect actual
+ "
+}
+
# On Windows, we are using MSYS's bash, which mangles the paths.
# Absolute paths are anchored at the MSYS installation directory,
# which means that the path / accounts for this many characters:
relative_path "<null>" "<null>" ./
relative_path "<null>" /foo/a/b ./
+test_git_path A=B info/grafts .git/info/grafts
+test_git_path GIT_GRAFT_FILE=foo info/grafts foo
+test_git_path GIT_GRAFT_FILE=foo info/////grafts foo
+test_git_path GIT_INDEX_FILE=foo index foo
+test_git_path GIT_INDEX_FILE=foo index/foo .git/index/foo
+test_git_path GIT_INDEX_FILE=foo index2 .git/index2
+test_expect_success 'setup fake objects directory foo' 'mkdir foo'
+test_git_path GIT_OBJECT_DIRECTORY=foo objects foo
+test_git_path GIT_OBJECT_DIRECTORY=foo objects/foo foo/foo
+test_git_path GIT_OBJECT_DIRECTORY=foo objects2 .git/objects2
+test_expect_success 'setup common repository' 'git --git-dir=bar init'
+test_git_path GIT_COMMON_DIR=bar index .git/index
+test_git_path GIT_COMMON_DIR=bar HEAD .git/HEAD
+test_git_path GIT_COMMON_DIR=bar logs/HEAD .git/logs/HEAD
+test_git_path GIT_COMMON_DIR=bar objects bar/objects
+test_git_path GIT_COMMON_DIR=bar objects/bar bar/objects/bar
+test_git_path GIT_COMMON_DIR=bar info/exclude bar/info/exclude
+test_git_path GIT_COMMON_DIR=bar info/grafts bar/info/grafts
+test_git_path GIT_COMMON_DIR=bar info/sparse-checkout .git/info/sparse-checkout
+test_git_path GIT_COMMON_DIR=bar remotes/bar bar/remotes/bar
+test_git_path GIT_COMMON_DIR=bar branches/bar bar/branches/bar
+test_git_path GIT_COMMON_DIR=bar logs/refs/heads/master bar/logs/refs/heads/master
+test_git_path GIT_COMMON_DIR=bar refs/heads/master bar/refs/heads/master
+test_git_path GIT_COMMON_DIR=bar hooks/me bar/hooks/me
+test_git_path GIT_COMMON_DIR=bar config bar/config
+test_git_path GIT_COMMON_DIR=bar packed-refs bar/packed-refs
+test_git_path GIT_COMMON_DIR=bar shallow bar/shallow
+
test_done
EOF
'
-test_expect_success POSIXPERM 'get: use xdg file if home file is unreadable' '
+test_expect_success POSIXPERM,SANITY 'get: use xdg file if home file is unreadable' '
echo "https://home-user:home-pass@example.com" >"$HOME/.git-credentials" &&
chmod -r "$HOME/.git-credentials" &&
mkdir -p "$HOME/.config/git" &&
test_cmp expect actual
'
+ test_expect_success "Type of $type is correct using --allow-unknown-type" '
+ echo $type >expect &&
+ git cat-file -t --allow-unknown-type $sha1 >actual &&
+ test_cmp expect actual
+ '
+
+ test_expect_success "Size of $type is correct using --allow-unknown-type" '
+ echo $size >expect &&
+ git cat-file -s --allow-unknown-type $sha1 >actual &&
+ test_cmp expect actual
+ '
+
test -z "$content" ||
test_expect_success "Content of $type is correct" '
maybe_remove_timestamp "$content" $no_ts >expect &&
'
done
+for opt in t s e p
+do
+ test_expect_success "Passing -$opt with --follow-symlinks fails" '
+ test_must_fail git cat-file --follow-symlinks -$opt $hello_sha1
+ '
+done
+
test_expect_success "--batch-check for a non-existent named object" '
test "foobar42 missing
foobar84 missing" = \
}
'
+bogus_type="bogus"
+bogus_content="bogus"
+bogus_size=$(strlen "$bogus_content")
+bogus_sha1=$(echo_without_newline "$bogus_content" | git hash-object -t $bogus_type --literally -w --stdin)
+
+test_expect_success "Type of broken object is correct" '
+ echo $bogus_type >expect &&
+ git cat-file -t --allow-unknown-type $bogus_sha1 >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success "Size of broken object is correct" '
+ echo $bogus_size >expect &&
+ git cat-file -s --allow-unknown-type $bogus_sha1 >actual &&
+ test_cmp expect actual
+'
+bogus_type="abcdefghijklmnopqrstuvwxyz1234679"
+bogus_content="bogus"
+bogus_size=$(strlen "$bogus_content")
+bogus_sha1=$(echo_without_newline "$bogus_content" | git hash-object -t $bogus_type --literally -w --stdin)
+
+test_expect_success "Type of broken object is correct when type is large" '
+ echo $bogus_type >expect &&
+ git cat-file -t --allow-unknown-type $bogus_sha1 >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success "Size of large broken object is correct when type is large" '
+ echo $bogus_size >expect &&
+ git cat-file -s --allow-unknown-type $bogus_sha1 >actual &&
+ test_cmp expect actual
+'
+
+# Tests for git cat-file --follow-symlinks
+test_expect_success 'prep for symlink tests' '
+ echo_without_newline "$hello_content" >morx &&
+ test_ln_s_add morx same-dir-link &&
+ test_ln_s_add dir link-to-dir &&
+ test_ln_s_add ../fleem out-of-repo-link &&
+ test_ln_s_add .. out-of-repo-link-dir &&
+ test_ln_s_add same-dir-link link-to-link &&
+ test_ln_s_add nope broken-same-dir-link &&
+ mkdir dir &&
+ test_ln_s_add ../morx dir/parent-dir-link &&
+ test_ln_s_add .. dir/link-dir &&
+ test_ln_s_add ../../escape dir/out-of-repo-link &&
+ test_ln_s_add ../.. dir/out-of-repo-link-dir &&
+ test_ln_s_add nope dir/broken-link-in-dir &&
+ mkdir dir/subdir &&
+ test_ln_s_add ../../morx dir/subdir/grandparent-dir-link &&
+ test_ln_s_add ../../../great-escape dir/subdir/out-of-repo-link &&
+ test_ln_s_add ../../.. dir/subdir/out-of-repo-link-dir &&
+ test_ln_s_add ../../../ dir/subdir/out-of-repo-link-dir-trailing &&
+ test_ln_s_add ../parent-dir-link dir/subdir/parent-dir-link-to-link &&
+ echo_without_newline "$hello_content" >dir/subdir/ind2 &&
+ echo_without_newline "$hello_content" >dir/ind1 &&
+ test_ln_s_add dir dirlink &&
+ test_ln_s_add dir/subdir subdirlink &&
+ test_ln_s_add subdir/ind2 dir/link-to-child &&
+ test_ln_s_add dir/link-to-child link-to-down-link &&
+ test_ln_s_add dir/.. up-down &&
+ test_ln_s_add dir/../ up-down-trailing &&
+ test_ln_s_add dir/../morx up-down-file &&
+ test_ln_s_add dir/../../morx up-up-down-file &&
+ test_ln_s_add subdirlink/../../morx up-two-down-file &&
+ test_ln_s_add loop1 loop2 &&
+ test_ln_s_add loop2 loop1 &&
+ git add morx dir/subdir/ind2 dir/ind1 &&
+ git commit -am "test" &&
+ echo $hello_sha1 blob $hello_size >found
+'
+
+test_expect_success 'git cat-file --batch-check --follow-symlinks works for non-links' '
+ echo HEAD:morx | git cat-file --batch-check --follow-symlinks >actual &&
+ test_cmp found actual &&
+ echo HEAD:nope missing >expect &&
+ echo HEAD:nope | git cat-file --batch-check --follow-symlinks >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'git cat-file --batch-check --follow-symlinks works for in-repo, same-dir links' '
+ echo HEAD:same-dir-link | git cat-file --batch-check --follow-symlinks >actual &&
+ test_cmp found actual
+'
+
+test_expect_success 'git cat-file --batch-check --follow-symlinks works for in-repo, links to dirs' '
+ echo HEAD:link-to-dir/ind1 | git cat-file --batch-check --follow-symlinks >actual &&
+ test_cmp found actual
+'
+
+
+test_expect_success 'git cat-file --batch-check --follow-symlinks works for broken in-repo, same-dir links' '
+ echo dangling 25 >expect &&
+ echo HEAD:broken-same-dir-link >>expect &&
+ echo HEAD:broken-same-dir-link | git cat-file --batch-check --follow-symlinks >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'git cat-file --batch-check --follow-symlinks works for same-dir links-to-links' '
+ echo HEAD:link-to-link | git cat-file --batch-check --follow-symlinks >actual &&
+ test_cmp found actual
+'
+
+test_expect_success 'git cat-file --batch-check --follow-symlinks works for parent-dir links' '
+ echo HEAD:dir/parent-dir-link | git cat-file --batch-check --follow-symlinks >actual &&
+ test_cmp found actual &&
+ echo notdir 29 >expect &&
+ echo HEAD:dir/parent-dir-link/nope >>expect &&
+ echo HEAD:dir/parent-dir-link/nope | git cat-file --batch-check --follow-symlinks >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'git cat-file --batch-check --follow-symlinks works for .. links' '
+ echo dangling 22 >expect &&
+ echo HEAD:dir/link-dir/nope >>expect &&
+ echo HEAD:dir/link-dir/nope | git cat-file --batch-check --follow-symlinks >actual &&
+ test_cmp expect actual &&
+ echo HEAD:dir/link-dir/morx | git cat-file --batch-check --follow-symlinks >actual &&
+ test_cmp found actual &&
+ echo dangling 27 >expect &&
+ echo HEAD:dir/broken-link-in-dir >>expect &&
+ echo HEAD:dir/broken-link-in-dir | git cat-file --batch-check --follow-symlinks >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'git cat-file --batch-check --follow-symlinks works for ../.. links' '
+ echo notdir 41 >expect &&
+ echo HEAD:dir/subdir/grandparent-dir-link/nope >>expect &&
+ echo HEAD:dir/subdir/grandparent-dir-link/nope | git cat-file --batch-check --follow-symlinks >actual &&
+ test_cmp expect actual &&
+ echo HEAD:dir/subdir/grandparent-dir-link | git cat-file --batch-check --follow-symlinks >actual &&
+ test_cmp found actual &&
+ echo HEAD:dir/subdir/parent-dir-link-to-link | git cat-file --batch-check --follow-symlinks >actual &&
+ test_cmp found actual
+'
+
+test_expect_success 'git cat-file --batch-check --follow-symlinks works for dir/ links' '
+ echo dangling 17 >expect &&
+ echo HEAD:dirlink/morx >>expect &&
+ echo HEAD:dirlink/morx | git cat-file --batch-check --follow-symlinks >actual &&
+ test_cmp expect actual &&
+ echo $hello_sha1 blob $hello_size >expect &&
+ echo HEAD:dirlink/ind1 | git cat-file --batch-check --follow-symlinks >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'git cat-file --batch-check --follow-symlinks works for dir/subdir links' '
+ echo dangling 20 >expect &&
+ echo HEAD:subdirlink/morx >>expect &&
+ echo HEAD:subdirlink/morx | git cat-file --batch-check --follow-symlinks >actual &&
+ test_cmp expect actual &&
+ echo HEAD:subdirlink/ind2 | git cat-file --batch-check --follow-symlinks >actual &&
+ test_cmp found actual
+'
+
+test_expect_success 'git cat-file --batch-check --follow-symlinks works for dir ->subdir links' '
+ echo notdir 27 >expect &&
+ echo HEAD:dir/link-to-child/morx >>expect &&
+ echo HEAD:dir/link-to-child/morx | git cat-file --batch-check --follow-symlinks >actual &&
+ test_cmp expect actual &&
+ echo HEAD:dir/link-to-child | git cat-file --batch-check --follow-symlinks >actual &&
+ test_cmp found actual &&
+ echo HEAD:link-to-down-link | git cat-file --batch-check --follow-symlinks >actual &&
+ test_cmp found actual
+'
+
+test_expect_success 'git cat-file --batch-check --follow-symlinks works for out-of-repo symlinks' '
+ echo symlink 8 >expect &&
+ echo ../fleem >>expect &&
+ echo HEAD:out-of-repo-link | git cat-file --batch-check --follow-symlinks >actual &&
+ test_cmp expect actual &&
+ echo symlink 2 >expect &&
+ echo .. >>expect &&
+ echo HEAD:out-of-repo-link-dir | git cat-file --batch-check --follow-symlinks >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'git cat-file --batch-check --follow-symlinks works for out-of-repo symlinks in dirs' '
+ echo symlink 9 >expect &&
+ echo ../escape >>expect &&
+ echo HEAD:dir/out-of-repo-link | git cat-file --batch-check --follow-symlinks >actual &&
+ test_cmp expect actual &&
+ echo symlink 2 >expect &&
+ echo .. >>expect &&
+ echo HEAD:dir/out-of-repo-link-dir | git cat-file --batch-check --follow-symlinks >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'git cat-file --batch-check --follow-symlinks works for out-of-repo symlinks in subdirs' '
+ echo symlink 15 >expect &&
+ echo ../great-escape >>expect &&
+ echo HEAD:dir/subdir/out-of-repo-link | git cat-file --batch-check --follow-symlinks >actual &&
+ test_cmp expect actual &&
+ echo symlink 2 >expect &&
+ echo .. >>expect &&
+ echo HEAD:dir/subdir/out-of-repo-link-dir | git cat-file --batch-check --follow-symlinks >actual &&
+ test_cmp expect actual &&
+ echo symlink 3 >expect &&
+ echo ../ >>expect &&
+ echo HEAD:dir/subdir/out-of-repo-link-dir-trailing | git cat-file --batch-check --follow-symlinks >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'git cat-file --batch-check --follow-symlinks works for symlinks with internal ..' '
+ echo HEAD: | git cat-file --batch-check >expect &&
+ echo HEAD:up-down | git cat-file --batch-check --follow-symlinks >actual &&
+ test_cmp expect actual &&
+ echo HEAD:up-down-trailing | git cat-file --batch-check --follow-symlinks >actual &&
+ test_cmp expect actual &&
+ echo HEAD:up-down-file | git cat-file --batch-check --follow-symlinks >actual &&
+ test_cmp found actual &&
+ echo symlink 7 >expect &&
+ echo ../morx >>expect &&
+ echo HEAD:up-up-down-file | git cat-file --batch-check --follow-symlinks >actual &&
+ test_cmp expect actual &&
+ echo HEAD:up-two-down-file | git cat-file --batch-check --follow-symlinks >actual &&
+ test_cmp found actual
+'
+
+test_expect_success 'git cat-file --batch-check --follow-symlink breaks loops' '
+ echo loop 10 >expect &&
+ echo HEAD:loop1 >>expect &&
+ echo HEAD:loop1 | git cat-file --batch-check --follow-symlinks >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'git cat-file --batch --follow-symlink returns correct sha and mode' '
+ echo HEAD:morx | git cat-file --batch >expect &&
+ echo HEAD:morx | git cat-file --batch --follow-symlinks >actual &&
+ test_cmp expect actual
+'
+
test_done
test_expect_success 'stdin update ref fails with wrong old value' '
echo "update $c $m $m~1" >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err &&
- grep "fatal: Cannot lock ref '"'"'$c'"'"'" err &&
+ grep "fatal: cannot lock ref '"'"'$c'"'"'" err &&
test_must_fail git rev-parse --verify -q $c
'
test_expect_success 'stdin delete ref fails with wrong old value' '
echo "delete $a $m~1" >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err &&
- grep "fatal: Cannot lock ref '"'"'$a'"'"'" err &&
+ grep "fatal: cannot lock ref '"'"'$a'"'"'" err &&
git rev-parse $m >expect &&
git rev-parse $a >actual &&
test_cmp expect actual
update $c ''
EOF
test_must_fail git update-ref --stdin <stdin 2>err &&
- grep "fatal: Cannot lock ref '"'"'$c'"'"'" err &&
+ grep "fatal: cannot lock ref '"'"'$c'"'"'" err &&
git rev-parse $m >expect &&
git rev-parse $a >actual &&
test_cmp expect actual &&
test_expect_success 'stdin -z update ref fails with wrong old value' '
printf $F "update $c" "$m" "$m~1" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
- grep "fatal: Cannot lock ref '"'"'$c'"'"'" err &&
+ grep "fatal: cannot lock ref '"'"'$c'"'"'" err &&
test_must_fail git rev-parse --verify -q $c
'
git rev-parse "$c" >expect &&
printf $F "create $c" "$m~1" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
- grep "fatal: Cannot lock ref '"'"'$c'"'"'" err &&
+ grep "fatal: cannot lock ref '"'"'$c'"'"'" err &&
git rev-parse "$c" >actual &&
test_cmp expect actual
'
test_expect_success 'stdin -z delete ref fails with wrong old value' '
printf $F "delete $a" "$m~1" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
- grep "fatal: Cannot lock ref '"'"'$a'"'"'" err &&
+ grep "fatal: cannot lock ref '"'"'$a'"'"'" err &&
git rev-parse $m >expect &&
git rev-parse $a >actual &&
test_cmp expect actual
git update-ref $c $m &&
printf $F "update $a" "$m" "$m" "update $b" "$m" "$m" "update $c" "$m" "$Z" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
- grep "fatal: Cannot lock ref '"'"'$c'"'"'" err &&
+ grep "fatal: cannot lock ref '"'"'$c'"'"'" err &&
git rev-parse $m >expect &&
git rev-parse $a >actual &&
test_cmp expect actual &&
test_cmp expect .git/my-private-file
'
+test_expect_success 'branch -D cannot delete ref in .git dir' '
+ git rev-parse HEAD >.git/my-private-file &&
+ git rev-parse HEAD >expect &&
+ git branch foo/legit &&
+ test_must_fail git branch -D foo////./././../../../my-private-file &&
+ test_cmp expect .git/my-private-file
+'
+
test_expect_success 'branch -D cannot delete absolute path' '
git branch -f extra &&
test_must_fail git branch -D "$(pwd)/.git/refs/heads/extra" &&
test_cmp expected actual
'
+test_expect_success 'Multi-worktree setup' '
+ mkdir work &&
+ mkdir -p repo.git/repos/foo &&
+ cp repo.git/HEAD repo.git/index repo.git/repos/foo &&
+ test_might_fail cp repo.git/sharedindex.* repo.git/repos/foo &&
+ sane_unset GIT_DIR GIT_CONFIG GIT_WORK_TREE
+'
+
+test_expect_success 'GIT_DIR set (1)' '
+ echo "gitdir: repo.git/repos/foo" >gitfile &&
+ echo ../.. >repo.git/repos/foo/commondir &&
+ (
+ cd work &&
+ GIT_DIR=../gitfile git rev-parse --git-common-dir >actual &&
+ test-path-utils real_path "$TRASH_DIRECTORY/repo.git" >expect &&
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'GIT_DIR set (2)' '
+ echo "gitdir: repo.git/repos/foo" >gitfile &&
+ echo "$(pwd)/repo.git" >repo.git/repos/foo/commondir &&
+ (
+ cd work &&
+ GIT_DIR=../gitfile git rev-parse --git-common-dir >actual &&
+ test-path-utils real_path "$TRASH_DIRECTORY/repo.git" >expect &&
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'Auto discovery' '
+ echo "gitdir: repo.git/repos/foo" >.git &&
+ echo ../.. >repo.git/repos/foo/commondir &&
+ (
+ cd work &&
+ git rev-parse --git-common-dir >actual &&
+ test-path-utils real_path "$TRASH_DIRECTORY/repo.git" >expect &&
+ test_cmp expect actual &&
+ echo haha >data1 &&
+ git add data1 &&
+ git ls-files --full-name :/ | grep data1 >actual &&
+ echo work/data1 >expect &&
+ test_cmp expect actual
+ )
+'
+
+test_expect_success '$GIT_DIR/common overrides core.worktree' '
+ mkdir elsewhere &&
+ git --git-dir=repo.git config core.worktree "$TRASH_DIRECTORY/elsewhere" &&
+ echo "gitdir: repo.git/repos/foo" >.git &&
+ echo ../.. >repo.git/repos/foo/commondir &&
+ (
+ cd work &&
+ git rev-parse --git-common-dir >actual &&
+ test-path-utils real_path "$TRASH_DIRECTORY/repo.git" >expect &&
+ test_cmp expect actual &&
+ echo haha >data2 &&
+ git add data2 &&
+ git ls-files --full-name :/ | grep data2 >actual &&
+ echo work/data2 >expect &&
+ test_cmp expect actual
+ )
+'
+
+test_expect_success '$GIT_WORK_TREE overrides $GIT_DIR/common' '
+ echo "gitdir: repo.git/repos/foo" >.git &&
+ echo ../.. >repo.git/repos/foo/commondir &&
+ (
+ cd work &&
+ echo haha >data3 &&
+ git --git-dir=../.git --work-tree=. add data3 &&
+ git ls-files --full-name -- :/ | grep data3 >actual &&
+ echo data3 >expect &&
+ test_cmp expect actual
+ )
+'
+
test_done
test_expect_success 'branch@{u} error message when no upstream' '
cat >expect <<-EOF &&
- fatal: No upstream configured for branch ${sq}non-tracking${sq}
+ fatal: no upstream configured for branch ${sq}non-tracking${sq}
EOF
error_message non-tracking@{u} 2>actual &&
test_i18ncmp expect actual
test_expect_success '@{u} error message when no upstream' '
cat >expect <<-EOF &&
- fatal: No upstream configured for branch ${sq}master${sq}
+ fatal: no upstream configured for branch ${sq}master${sq}
EOF
test_must_fail git rev-parse --verify @{u} 2>actual &&
test_i18ncmp expect actual
test_expect_success 'branch@{u} error message with misspelt branch' '
cat >expect <<-EOF &&
- fatal: No such branch: ${sq}no-such-branch${sq}
+ fatal: no such branch: ${sq}no-such-branch${sq}
EOF
error_message no-such-branch@{u} 2>actual &&
test_i18ncmp expect actual
test_expect_success 'branch@{u} error message if upstream branch not fetched' '
cat >expect <<-EOF &&
- fatal: Upstream branch ${sq}refs/heads/side${sq} not stored as a remote-tracking branch
+ fatal: upstream branch ${sq}refs/heads/side${sq} not stored as a remote-tracking branch
EOF
error_message bad-upstream@{u} 2>actual &&
test_i18ncmp expect actual
expect () {
cat >"$1/expected" <<-EOF
setup: git_dir: $2
+ setup: git_common_dir: $2
setup: worktree: $3
setup: cwd: $4
setup: prefix: $5
mkdir -p 20b/.git/wt/sub &&
(
cd 20b/.git &&
- test_must_fail git symbolic-ref HEAD >/dev/null
+ test_must_fail git status >/dev/null
) 2>message &&
grep "core.bare and core.worktree" message
'
+test_expect_success '#20d: core.worktree and core.bare OK when working tree not needed' '
+ setup_repo 20d non-existent "" true &&
+ mkdir -p 20d/.git/wt/sub &&
+ (
+ cd 20d/.git &&
+ git config foo.bar value
+ )
+'
+
# Case #21: core.worktree/GIT_WORK_TREE overrides core.bare' '
test_expect_success '#21: setup, core.worktree warns before overriding core.bare' '
setup_repo 21 non-existent "" unset &&
cd 21/.git &&
GIT_WORK_TREE="$here/21" &&
export GIT_WORK_TREE &&
- git symbolic-ref HEAD >/dev/null
+ git status >/dev/null
) 2>message &&
! test -s message
cd 22/.git &&
GIT_DIR=. &&
export GIT_DIR &&
- test_must_fail git symbolic-ref HEAD 2>result
+ test_must_fail git status 2>result
) &&
(
cd 22 &&
GIT_DIR=.git &&
export GIT_DIR &&
- test_must_fail git symbolic-ref HEAD 2>result
+ test_must_fail git status 2>result
) &&
grep "core.bare and core.worktree" 22/.git/result &&
grep "core.bare and core.worktree" 22/result
setup_repo 28 "$here/28" gitfile true &&
(
cd 28 &&
- test_must_fail git symbolic-ref HEAD
+ test_must_fail git status
) 2>message &&
- ! grep "^warning:" message &&
grep "core.bare and core.worktree" message
'
cd 29 &&
GIT_WORK_TREE="$here/29" &&
export GIT_WORK_TREE &&
- git symbolic-ref HEAD >/dev/null
+ git status
) 2>message &&
! test -s message
'
setup_repo 30 "$here/30" gitfile true &&
(
cd 30 &&
- test_must_fail env GIT_DIR=.git git symbolic-ref HEAD 2>result
+ test_must_fail env GIT_DIR=.git git status 2>result
) &&
grep "core.bare and core.worktree" 30/result
'
--- /dev/null
+#!/bin/sh
+
+test_description='test <branch>@{push} syntax'
+. ./test-lib.sh
+
+resolve () {
+ echo "$2" >expect &&
+ git rev-parse --symbolic-full-name "$1" >actual &&
+ test_cmp expect actual
+}
+
+test_expect_success 'setup' '
+ git init --bare parent.git &&
+ git init --bare other.git &&
+ git remote add origin parent.git &&
+ git remote add other other.git &&
+ test_commit base &&
+ git push origin HEAD &&
+ git branch --set-upstream-to=origin/master master &&
+ git branch --track topic origin/master &&
+ git push origin topic &&
+ git push other topic
+'
+
+test_expect_success '@{push} with default=nothing' '
+ test_config push.default nothing &&
+ test_must_fail git rev-parse master@{push}
+'
+
+test_expect_success '@{push} with default=simple' '
+ test_config push.default simple &&
+ resolve master@{push} refs/remotes/origin/master
+'
+
+test_expect_success 'triangular @{push} fails with default=simple' '
+ test_config push.default simple &&
+ test_must_fail git rev-parse topic@{push}
+'
+
+test_expect_success '@{push} with default=current' '
+ test_config push.default current &&
+ resolve topic@{push} refs/remotes/origin/topic
+'
+
+test_expect_success '@{push} with default=matching' '
+ test_config push.default matching &&
+ resolve topic@{push} refs/remotes/origin/topic
+'
+
+test_expect_success '@{push} with pushremote defined' '
+ test_config push.default current &&
+ test_config branch.topic.pushremote other &&
+ resolve topic@{push} refs/remotes/other/topic
+'
+
+test_expect_success '@{push} with push refspecs' '
+ test_config push.default nothing &&
+ test_config remote.origin.push refs/heads/*:refs/heads/magic/* &&
+ git push &&
+ resolve topic@{push} refs/remotes/origin/magic/topic
+'
+
+test_done
test_i18ngrep ! "^HEAD is now at" stderr
'
+test_expect_success 'wildcard ambiguation, paths win' '
+ git init ambi &&
+ (
+ cd ambi &&
+ echo a >a.c &&
+ git add a.c &&
+ echo b >a.c &&
+ git checkout "*.c" &&
+ echo a >expect &&
+ test_cmp expect a.c
+ )
+'
+
+test_expect_success 'wildcard ambiguation, refs lose' '
+ git init ambi2 &&
+ (
+ cd ambi2 &&
+ echo a >"*.c" &&
+ git add . &&
+ test_must_fail git show :"*.c" &&
+ git show :"*.c" -- >actual &&
+ echo a >expect &&
+ test_cmp expect actual
+ )
+'
+
test_done
--- /dev/null
+#!/bin/sh
+
+test_description='test git checkout --to'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+ test_commit init
+'
+
+test_expect_success 'checkout --to not updating paths' '
+ test_must_fail git checkout --to -- init.t
+'
+
+test_expect_success 'checkout --to an existing worktree' '
+ mkdir -p existing/subtree &&
+ test_must_fail git checkout --detach --to existing master
+'
+
+test_expect_success 'checkout --to an existing empty worktree' '
+ mkdir existing_empty &&
+ git checkout --detach --to existing_empty master
+'
+
+test_expect_success 'checkout --to refuses to checkout locked branch' '
+ test_must_fail git checkout --to zere master &&
+ ! test -d zere &&
+ ! test -d .git/worktrees/zere
+'
+
+test_expect_success 'checkout --to a new worktree' '
+ git rev-parse HEAD >expect &&
+ git checkout --detach --to here master &&
+ (
+ cd here &&
+ test_cmp ../init.t init.t &&
+ test_must_fail git symbolic-ref HEAD &&
+ git rev-parse HEAD >actual &&
+ test_cmp ../expect actual &&
+ git fsck
+ )
+'
+
+test_expect_success 'checkout --to a new worktree from a subdir' '
+ (
+ mkdir sub &&
+ cd sub &&
+ git checkout --detach --to here master &&
+ cd here &&
+ test_cmp ../../init.t init.t
+ )
+'
+
+test_expect_success 'checkout --to from a linked checkout' '
+ (
+ cd here &&
+ git checkout --detach --to nested-here master &&
+ cd nested-here &&
+ git fsck
+ )
+'
+
+test_expect_success 'checkout --to a new worktree creating new branch' '
+ git checkout --to there -b newmaster master &&
+ (
+ cd there &&
+ test_cmp ../init.t init.t &&
+ git symbolic-ref HEAD >actual &&
+ echo refs/heads/newmaster >expect &&
+ test_cmp expect actual &&
+ git fsck
+ )
+'
+
+test_expect_success 'die the same branch is already checked out' '
+ (
+ cd here &&
+ test_must_fail git checkout newmaster
+ )
+'
+
+test_expect_success 'not die the same branch is already checked out' '
+ (
+ cd here &&
+ git checkout --ignore-other-worktrees --to anothernewmaster newmaster
+ )
+'
+
+test_expect_success 'not die on re-checking out current branch' '
+ (
+ cd there &&
+ git checkout newmaster
+ )
+'
+
+test_expect_success 'checkout --to from a bare repo' '
+ (
+ git clone --bare . bare &&
+ cd bare &&
+ git checkout --to ../there2 -b bare-master master
+ )
+'
+
+test_expect_success 'checkout from a bare repo without --to' '
+ (
+ cd bare &&
+ test_must_fail git checkout master
+ )
+'
+
+test_expect_success 'checkout with grafts' '
+ test_when_finished rm .git/info/grafts &&
+ test_commit abc &&
+ SHA1=`git rev-parse HEAD` &&
+ test_commit def &&
+ test_commit xyz &&
+ echo "`git rev-parse HEAD` $SHA1" >.git/info/grafts &&
+ cat >expected <<-\EOF &&
+ xyz
+ abc
+ EOF
+ git log --format=%s -2 >actual &&
+ test_cmp expected actual &&
+ git checkout --detach --to grafted master &&
+ git --git-dir=grafted/.git log --format=%s -2 >actual &&
+ test_cmp expected actual
+'
+
+test_done
--- /dev/null
+#!/bin/sh
+
+test_description='prune $GIT_DIR/worktrees'
+
+. ./test-lib.sh
+
+test_expect_success initialize '
+ git commit --allow-empty -m init
+'
+
+test_expect_success 'prune --worktrees on normal repo' '
+ git prune --worktrees &&
+ test_must_fail git prune --worktrees abc
+'
+
+test_expect_success 'prune files inside $GIT_DIR/worktrees' '
+ mkdir .git/worktrees &&
+ : >.git/worktrees/abc &&
+ git prune --worktrees --verbose >actual &&
+ cat >expect <<EOF &&
+Removing worktrees/abc: not a valid directory
+EOF
+ test_i18ncmp expect actual &&
+ ! test -f .git/worktrees/abc &&
+ ! test -d .git/worktrees
+'
+
+test_expect_success 'prune directories without gitdir' '
+ mkdir -p .git/worktrees/def/abc &&
+ : >.git/worktrees/def/def &&
+ cat >expect <<EOF &&
+Removing worktrees/def: gitdir file does not exist
+EOF
+ git prune --worktrees --verbose >actual &&
+ test_i18ncmp expect actual &&
+ ! test -d .git/worktrees/def &&
+ ! test -d .git/worktrees
+'
+
+test_expect_success SANITY 'prune directories with unreadable gitdir' '
+ mkdir -p .git/worktrees/def/abc &&
+ : >.git/worktrees/def/def &&
+ : >.git/worktrees/def/gitdir &&
+ chmod u-r .git/worktrees/def/gitdir &&
+ git prune --worktrees --verbose >actual &&
+ test_i18ngrep "Removing worktrees/def: unable to read gitdir file" actual &&
+ ! test -d .git/worktrees/def &&
+ ! test -d .git/worktrees
+'
+
+test_expect_success 'prune directories with invalid gitdir' '
+ mkdir -p .git/worktrees/def/abc &&
+ : >.git/worktrees/def/def &&
+ : >.git/worktrees/def/gitdir &&
+ git prune --worktrees --verbose >actual &&
+ test_i18ngrep "Removing worktrees/def: invalid gitdir file" actual &&
+ ! test -d .git/worktrees/def &&
+ ! test -d .git/worktrees
+'
+
+test_expect_success 'prune directories with gitdir pointing to nowhere' '
+ mkdir -p .git/worktrees/def/abc &&
+ : >.git/worktrees/def/def &&
+ echo "$(pwd)"/nowhere >.git/worktrees/def/gitdir &&
+ git prune --worktrees --verbose >actual &&
+ test_i18ngrep "Removing worktrees/def: gitdir file points to non-existent location" actual &&
+ ! test -d .git/worktrees/def &&
+ ! test -d .git/worktrees
+'
+
+test_expect_success 'not prune locked checkout' '
+ test_when_finished rm -r .git/worktrees &&
+ mkdir -p .git/worktrees/ghi &&
+ : >.git/worktrees/ghi/locked &&
+ git prune --worktrees &&
+ test -d .git/worktrees/ghi
+'
+
+test_expect_success 'not prune recent checkouts' '
+ test_when_finished rm -r .git/worktrees &&
+ mkdir zz &&
+ mkdir -p .git/worktrees/jlm &&
+ echo "$(pwd)"/zz >.git/worktrees/jlm/gitdir &&
+ rmdir zz &&
+ git prune --worktrees --verbose --expire=2.days.ago &&
+ test -d .git/worktrees/jlm
+'
+
+test_expect_success 'not prune proper checkouts' '
+ test_when_finished rm -r .git/worktrees &&
+ git checkout "--to=$PWD/nop" --detach master &&
+ git prune --worktrees &&
+ test -d .git/worktrees/nop
+'
+
+test_done
--- /dev/null
+#!/bin/sh
+
+test_description='"git merge" top-level frontend'
+
+. ./test-lib.sh
+
+t3033_reset () {
+ git checkout -B master two &&
+ git branch -f left three &&
+ git branch -f right four
+}
+
+test_expect_success setup '
+ test_commit one &&
+ git branch left &&
+ git branch right &&
+ test_commit two &&
+ git checkout left &&
+ test_commit three &&
+ git checkout right &&
+ test_commit four &&
+ git checkout master
+'
+
+# Local branches
+
+test_expect_success 'merge an octopus into void' '
+ t3033_reset &&
+ git checkout --orphan test &&
+ git rm -fr . &&
+ test_must_fail git merge left right &&
+ test_must_fail git rev-parse --verify HEAD &&
+ git diff --quiet &&
+ test_must_fail git rev-parse HEAD
+'
+
+test_expect_success 'merge an octopus, fast-forward (ff)' '
+ t3033_reset &&
+ git reset --hard one &&
+ git merge left right &&
+ # one is ancestor of three (left) and four (right)
+ test_must_fail git rev-parse --verify HEAD^3 &&
+ git rev-parse HEAD^1 HEAD^2 | sort >actual &&
+ git rev-parse three four | sort >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'merge octopus, non-fast-forward (ff)' '
+ t3033_reset &&
+ git reset --hard one &&
+ git merge --no-ff left right &&
+ # one is ancestor of three (left) and four (right)
+ test_must_fail git rev-parse --verify HEAD^4 &&
+ git rev-parse HEAD^1 HEAD^2 HEAD^3 | sort >actual &&
+ git rev-parse one three four | sort >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'merge octopus, fast-forward (does not ff)' '
+ t3033_reset &&
+ git merge left right &&
+ # two (master) is not an ancestor of three (left) and four (right)
+ test_must_fail git rev-parse --verify HEAD^4 &&
+ git rev-parse HEAD^1 HEAD^2 HEAD^3 | sort >actual &&
+ git rev-parse two three four | sort >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'merge octopus, non-fast-forward' '
+ t3033_reset &&
+ git merge --no-ff left right &&
+ test_must_fail git rev-parse --verify HEAD^4 &&
+ git rev-parse HEAD^1 HEAD^2 HEAD^3 | sort >actual &&
+ git rev-parse two three four | sort >expect &&
+ test_cmp expect actual
+'
+
+# The same set with FETCH_HEAD
+
+test_expect_success 'merge FETCH_HEAD octopus into void' '
+ t3033_reset &&
+ git checkout --orphan test &&
+ git rm -fr . &&
+ git fetch . left right &&
+ test_must_fail git merge FETCH_HEAD &&
+ test_must_fail git rev-parse --verify HEAD &&
+ git diff --quiet &&
+ test_must_fail git rev-parse HEAD
+'
+
+test_expect_success 'merge FETCH_HEAD octopus fast-forward (ff)' '
+ t3033_reset &&
+ git reset --hard one &&
+ git fetch . left right &&
+ git merge FETCH_HEAD &&
+ # one is ancestor of three (left) and four (right)
+ test_must_fail git rev-parse --verify HEAD^3 &&
+ git rev-parse HEAD^1 HEAD^2 | sort >actual &&
+ git rev-parse three four | sort >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'merge FETCH_HEAD octopus non-fast-forward (ff)' '
+ t3033_reset &&
+ git reset --hard one &&
+ git fetch . left right &&
+ git merge --no-ff FETCH_HEAD &&
+ # one is ancestor of three (left) and four (right)
+ test_must_fail git rev-parse --verify HEAD^4 &&
+ git rev-parse HEAD^1 HEAD^2 HEAD^3 | sort >actual &&
+ git rev-parse one three four | sort >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'merge FETCH_HEAD octopus fast-forward (does not ff)' '
+ t3033_reset &&
+ git fetch . left right &&
+ git merge FETCH_HEAD &&
+ # two (master) is not an ancestor of three (left) and four (right)
+ test_must_fail git rev-parse --verify HEAD^4 &&
+ git rev-parse HEAD^1 HEAD^2 HEAD^3 | sort >actual &&
+ git rev-parse two three four | sort >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'merge FETCH_HEAD octopus non-fast-forward' '
+ t3033_reset &&
+ git fetch . left right &&
+ git merge --no-ff FETCH_HEAD &&
+ test_must_fail git rev-parse --verify HEAD^4 &&
+ git rev-parse HEAD^1 HEAD^2 HEAD^3 | sort >actual &&
+ git rev-parse two three four | sort >expect &&
+ test_cmp expect actual
+'
+
+test_done
test_must_fail git branch foo/bar/baz/lots/of/extra/components
'
+test_expect_success 'timeout if packed-refs.lock exists' '
+ LOCK=.git/packed-refs.lock &&
+ >"$LOCK" &&
+ test_when_finished "rm -f $LOCK" &&
+ test_must_fail git pack-refs --all --prune
+'
+
+test_expect_success 'retry acquiring packed-refs.lock' '
+ LOCK=.git/packed-refs.lock &&
+ >"$LOCK" &&
+ test_when_finished "wait; rm -f $LOCK" &&
+ {
+ ( sleep 1 ; rm -f $LOCK ) &
+ } &&
+ git -c core.packedrefstimeout=3000 pack-refs --all --prune
+'
+
test_done
'
test_expect_success 'reference merge' '
- git merge -s recursive "reference merge" HEAD master
+ git merge -s recursive -m "reference merge" master
'
PRE_REBASE=$(git rev-parse test-rebase)
# 2. Correct version applies the (not)edited version, and asks
# about the next hunk, against which we say q and program
# exits.
- for a in s e q n q q
- do
- echo $a
- done |
+ printf "%s\n" s e q n q q |
EDITOR=: git add -p &&
git diff >actual &&
! grep "^+15" actual
'
+test_expect_failure 'split hunk "add -p (no, yes, edit)"' '
+ cat >test <<-\EOF &&
+ 5
+ 10
+ 20
+ 21
+ 30
+ 31
+ 40
+ 50
+ 60
+ EOF
+ git reset &&
+ # test sequence is s(plit), n(o), y(es), e(dit)
+ # q n q q is there to make sure we exit at the end.
+ printf "%s\n" s n y e q n q q |
+ EDITOR=: git add -p 2>error &&
+ test_must_be_empty error &&
+ git diff >actual &&
+ ! grep "^+31" actual
+'
+
test_expect_success 'patch mode ignores unmerged entries' '
git reset --hard &&
test_commit conflict &&
test_cmp expect file
'
-test_expect_success 'apply requires a clean index' '
- test_when_finished "git reset --hard" &&
- echo changed >other-file &&
- git add other-file &&
- test_must_fail git stash apply
-'
-
test_expect_success 'apply does not need clean working directory' '
echo 4 >other-file &&
git stash apply &&
#!/bin/sh
-test_description='git checkout --patch'
+test_description='stash -p'
. ./lib-patch-mode.sh
-test_expect_success PERL 'setup' '
+if ! test_have_prereq PERL
+then
+ skip_all='skipping stash -p tests, perl not available'
+ test_done
+fi
+
+test_expect_success 'setup' '
mkdir dir &&
echo parent > dir/foo &&
echo dummy > bar &&
# note: order of files with unstaged changes: HEAD bar dir/foo
-test_expect_success PERL 'saying "n" does nothing' '
+test_expect_success 'saying "n" does nothing' '
set_state HEAD HEADfile_work HEADfile_index &&
set_state dir/foo work index &&
(echo n; echo n; echo n) | test_must_fail git stash save -p &&
verify_state dir/foo work index
'
-test_expect_success PERL 'git stash -p' '
+test_expect_success 'git stash -p' '
(echo y; echo n; echo y) | git stash save -p &&
verify_state HEAD committed HEADfile_index &&
verify_saved_state bar &&
verify_state dir/foo work head
'
-test_expect_success PERL 'git stash -p --no-keep-index' '
+test_expect_success 'git stash -p --no-keep-index' '
set_state HEAD HEADfile_work HEADfile_index &&
set_state bar bar_work bar_index &&
set_state dir/foo work index &&
verify_state dir/foo work index
'
-test_expect_success PERL 'git stash --no-keep-index -p' '
+test_expect_success 'git stash --no-keep-index -p' '
set_state HEAD HEADfile_work HEADfile_index &&
set_state bar bar_work bar_index &&
set_state dir/foo work index &&
verify_state dir/foo work index
'
-test_expect_success PERL 'none of this moved HEAD' '
+test_expect_success 'none of this moved HEAD' '
verify_saved_head
'
+test_expect_failure 'stash -p with split hunk' '
+ git reset --hard &&
+ cat >test <<-\EOF &&
+ aaa
+ bbb
+ ccc
+ EOF
+ git add test &&
+ git commit -m "initial" &&
+ cat >test <<-\EOF &&
+ aaa
+ added line 1
+ bbb
+ added line 2
+ ccc
+ EOF
+ printf "%s\n" s n y q |
+ test_might_fail git stash -p 2>error &&
+ ! test_must_be_empty error &&
+ grep "added line 1" test &&
+ ! grep "added line 2" test
+'
+
test_done
'
+test_expect_success "format-patch --ignore-if-in-upstream handles tags" '
+ git tag -a v1 -m tag side &&
+ git tag -a v2 -m tag master &&
+ git format-patch --stdout --ignore-if-in-upstream v2..v1 >patch1 &&
+ cnt=$(grep "^From " patch1 | wc -l) &&
+ test $cnt = 2
+'
+
test_expect_success "format-patch doesn't consider merge commits" '
git checkout -b slave master &&
. ./test-lib.sh
. "$TEST_DIRECTORY"/diff-lib.sh
-# Ray Lehtiniemi's example
+test_expect_success "Ray Lehtiniemi's example" '
+ cat <<-\EOF >x &&
+ do {
+ nothing;
+ } while (0);
+ EOF
+ git update-index --add x &&
-cat << EOF > x
-do {
- nothing;
-} while (0);
-EOF
+ cat <<-\EOF >x &&
+ do
+ {
+ nothing;
+ }
+ while (0);
+ EOF
+
+ cat <<-\EOF >expect &&
+ diff --git a/x b/x
+ index adf3937..6edc172 100644
+ --- a/x
+ +++ b/x
+ @@ -1,3 +1,5 @@
+ -do {
+ +do
+ +{
+ nothing;
+ -} while (0);
+ +}
+ +while (0);
+ EOF
-git update-index --add x
+ git diff >out &&
+ test_cmp expect out &&
-cat << EOF > x
-do
-{
- nothing;
-}
-while (0);
-EOF
+ git diff -w >out &&
+ test_cmp expect out &&
-cat << EOF > expect
-diff --git a/x b/x
-index adf3937..6edc172 100644
---- a/x
-+++ b/x
-@@ -1,3 +1,5 @@
--do {
-+do
-+{
- nothing;
--} while (0);
-+}
-+while (0);
-EOF
+ git diff -b >out &&
+ test_cmp expect out
+'
-git diff > out
-test_expect_success "Ray's example without options" 'test_cmp expect out'
+test_expect_success 'another test, without options' '
+ tr Q "\015" <<-\EOF >x &&
+ whitespace at beginning
+ whitespace change
+ whitespace in the middle
+ whitespace at end
+ unchanged line
+ CR at endQ
+ EOF
-git diff -w > out
-test_expect_success "Ray's example with -w" 'test_cmp expect out'
+ git update-index x &&
-git diff -b > out
-test_expect_success "Ray's example with -b" 'test_cmp expect out'
+ tr "_" " " <<-\EOF >x &&
+ _ whitespace at beginning
+ whitespace change
+ white space in the middle
+ whitespace at end__
+ unchanged line
+ CR at end
+ EOF
-tr 'Q' '\015' << EOF > x
-whitespace at beginning
-whitespace change
-whitespace in the middle
-whitespace at end
-unchanged line
-CR at endQ
-EOF
+ tr "Q_" "\015 " <<-\EOF >expect &&
+ diff --git a/x b/x
+ index d99af23..22d9f73 100644
+ --- a/x
+ +++ b/x
+ @@ -1,6 +1,6 @@
+ -whitespace at beginning
+ -whitespace change
+ -whitespace in the middle
+ -whitespace at end
+ + whitespace at beginning
+ +whitespace change
+ +white space in the middle
+ +whitespace at end__
+ unchanged line
+ -CR at endQ
+ +CR at end
+ EOF
-git update-index x
+ git diff >out &&
+ test_cmp expect out &&
-tr '_' ' ' << EOF > x
- whitespace at beginning
-whitespace change
-white space in the middle
-whitespace at end__
-unchanged line
-CR at end
-EOF
+ >expect &&
+ git diff -w >out &&
+ test_cmp expect out &&
-tr 'Q_' '\015 ' << EOF > expect
-diff --git a/x b/x
-index d99af23..8b32fb5 100644
---- a/x
-+++ b/x
-@@ -1,6 +1,6 @@
--whitespace at beginning
--whitespace change
--whitespace in the middle
--whitespace at end
-+ whitespace at beginning
-+whitespace change
-+white space in the middle
-+whitespace at end__
- unchanged line
--CR at endQ
-+CR at end
-EOF
-git diff > out
-test_expect_success 'another test, without options' 'test_cmp expect out'
+ git diff -w -b >out &&
+ test_cmp expect out &&
-cat << EOF > expect
-EOF
-git diff -w > out
-test_expect_success 'another test, with -w' 'test_cmp expect out'
-git diff -w -b > out
-test_expect_success 'another test, with -w -b' 'test_cmp expect out'
-git diff -w --ignore-space-at-eol > out
-test_expect_success 'another test, with -w --ignore-space-at-eol' 'test_cmp expect out'
-git diff -w -b --ignore-space-at-eol > out
-test_expect_success 'another test, with -w -b --ignore-space-at-eol' 'test_cmp expect out'
-
-tr 'Q_' '\015 ' << EOF > expect
-diff --git a/x b/x
-index d99af23..8b32fb5 100644
---- a/x
-+++ b/x
-@@ -1,6 +1,6 @@
--whitespace at beginning
-+ whitespace at beginning
- whitespace change
--whitespace in the middle
-+white space in the middle
- whitespace at end__
- unchanged line
- CR at end
-EOF
-git diff -b > out
-test_expect_success 'another test, with -b' 'test_cmp expect out'
-git diff -b --ignore-space-at-eol > out
-test_expect_success 'another test, with -b --ignore-space-at-eol' 'test_cmp expect out'
-
-tr 'Q_' '\015 ' << EOF > expect
-diff --git a/x b/x
-index d99af23..8b32fb5 100644
---- a/x
-+++ b/x
-@@ -1,6 +1,6 @@
--whitespace at beginning
--whitespace change
--whitespace in the middle
-+ whitespace at beginning
-+whitespace change
-+white space in the middle
- whitespace at end__
- unchanged line
- CR at end
-EOF
-git diff --ignore-space-at-eol > out
-test_expect_success 'another test, with --ignore-space-at-eol' 'test_cmp expect out'
+ git diff -w --ignore-space-at-eol >out &&
+ test_cmp expect out &&
+
+ git diff -w -b --ignore-space-at-eol >out &&
+ test_cmp expect out &&
+
+
+ tr "Q_" "\015 " <<-\EOF >expect &&
+ diff --git a/x b/x
+ index d99af23..22d9f73 100644
+ --- a/x
+ +++ b/x
+ @@ -1,6 +1,6 @@
+ -whitespace at beginning
+ +_ whitespace at beginning
+ whitespace change
+ -whitespace in the middle
+ +white space in the middle
+ whitespace at end__
+ unchanged line
+ CR at end
+ EOF
+ git diff -b >out &&
+ test_cmp expect out &&
+
+ git diff -b --ignore-space-at-eol >out &&
+ test_cmp expect out &&
+
+ tr "Q_" "\015 " <<-\EOF >expect &&
+ diff --git a/x b/x
+ index d99af23..22d9f73 100644
+ --- a/x
+ +++ b/x
+ @@ -1,6 +1,6 @@
+ -whitespace at beginning
+ -whitespace change
+ -whitespace in the middle
+ +_ whitespace at beginning
+ +whitespace change
+ +white space in the middle
+ whitespace at end__
+ unchanged line
+ CR at end
+ EOF
+ git diff --ignore-space-at-eol >out &&
+ test_cmp expect out
+'
test_expect_success 'ignore-blank-lines: only new lines' '
test_seq 5 >x &&
'
test_expect_success 'check mixed spaces and tabs in indent' '
-
# This is indented with SP HT SP.
- echo " foo();" > x &&
+ echo " foo();" >x &&
git diff --check | grep "space before tab in indent"
-
'
test_expect_success 'check mixed tabs and spaces in indent' '
-
# This is indented with HT SP HT.
- echo " foo();" > x &&
+ echo " foo();" >x &&
git diff --check | grep "space before tab in indent"
-
'
test_expect_success 'check with no whitespace errors' '
-
git commit -m "snapshot" &&
- echo "foo();" > x &&
+ echo "foo();" >x &&
git diff --check
-
'
test_expect_success 'check with trailing whitespace' '
-
- echo "foo(); " > x &&
+ echo "foo(); " >x &&
test_must_fail git diff --check
-
'
test_expect_success 'check with space before tab in indent' '
-
# indent has space followed by hard tab
- echo " foo();" > x &&
+ echo " foo();" >x &&
test_must_fail git diff --check
-
'
test_expect_success '--check and --exit-code are not exclusive' '
-
git checkout x &&
git diff --check --exit-code
-
'
test_expect_success '--check and --quiet are not exclusive' '
-
git diff --check --quiet
-
'
test_expect_success 'check staged with no whitespace errors' '
-
- echo "foo();" > x &&
+ echo "foo();" >x &&
git add x &&
git diff --cached --check
-
'
test_expect_success 'check staged with trailing whitespace' '
-
- echo "foo(); " > x &&
+ echo "foo(); " >x &&
git add x &&
test_must_fail git diff --cached --check
-
'
test_expect_success 'check staged with space before tab in indent' '
-
# indent has space followed by hard tab
- echo " foo();" > x &&
+ echo " foo();" >x &&
git add x &&
test_must_fail git diff --cached --check
-
'
test_expect_success 'check with no whitespace errors (diff-index)' '
-
- echo "foo();" > x &&
+ echo "foo();" >x &&
git add x &&
git diff-index --check HEAD
-
'
test_expect_success 'check with trailing whitespace (diff-index)' '
-
- echo "foo(); " > x &&
+ echo "foo(); " >x &&
git add x &&
test_must_fail git diff-index --check HEAD
-
'
test_expect_success 'check with space before tab in indent (diff-index)' '
-
# indent has space followed by hard tab
- echo " foo();" > x &&
+ echo " foo();" >x &&
git add x &&
test_must_fail git diff-index --check HEAD
-
'
test_expect_success 'check staged with no whitespace errors (diff-index)' '
-
- echo "foo();" > x &&
+ echo "foo();" >x &&
git add x &&
git diff-index --cached --check HEAD
-
'
test_expect_success 'check staged with trailing whitespace (diff-index)' '
-
- echo "foo(); " > x &&
+ echo "foo(); " >x &&
git add x &&
test_must_fail git diff-index --cached --check HEAD
-
'
test_expect_success 'check staged with space before tab in indent (diff-index)' '
-
# indent has space followed by hard tab
- echo " foo();" > x &&
+ echo " foo();" >x &&
git add x &&
test_must_fail git diff-index --cached --check HEAD
-
'
test_expect_success 'check with no whitespace errors (diff-tree)' '
-
- echo "foo();" > x &&
+ echo "foo();" >x &&
git commit -m "new commit" x &&
git diff-tree --check HEAD^ HEAD
-
'
test_expect_success 'check with trailing whitespace (diff-tree)' '
-
- echo "foo(); " > x &&
+ echo "foo(); " >x &&
git commit -m "another commit" x &&
test_must_fail git diff-tree --check HEAD^ HEAD
-
'
test_expect_success 'check with space before tab in indent (diff-tree)' '
-
# indent has space followed by hard tab
- echo " foo();" > x &&
+ echo " foo();" >x &&
git commit -m "yet another" x &&
test_must_fail git diff-tree --check HEAD^ HEAD
-
'
test_expect_success 'check trailing whitespace (trailing-space: off)' '
-
git config core.whitespace "-trailing-space" &&
- echo "foo (); " > x &&
+ echo "foo (); " >x &&
git diff --check
-
'
test_expect_success 'check trailing whitespace (trailing-space: on)' '
-
git config core.whitespace "trailing-space" &&
- echo "foo (); " > x &&
+ echo "foo (); " >x &&
test_must_fail git diff --check
-
'
test_expect_success 'check space before tab in indent (space-before-tab: off)' '
-
# indent contains space followed by HT
git config core.whitespace "-space-before-tab" &&
- echo " foo ();" > x &&
+ echo " foo ();" >x &&
git diff --check
-
'
test_expect_success 'check space before tab in indent (space-before-tab: on)' '
-
# indent contains space followed by HT
git config core.whitespace "space-before-tab" &&
- echo " foo (); " > x &&
+ echo " foo (); " >x &&
test_must_fail git diff --check
-
'
test_expect_success 'check spaces as indentation (indent-with-non-tab: off)' '
-
git config core.whitespace "-indent-with-non-tab" &&
- echo " foo ();" > x &&
+ echo " foo ();" >x &&
git diff --check
-
'
test_expect_success 'check spaces as indentation (indent-with-non-tab: on)' '
-
git config core.whitespace "indent-with-non-tab" &&
- echo " foo ();" > x &&
+ echo " foo ();" >x &&
test_must_fail git diff --check
-
'
test_expect_success 'ditto, but tabwidth=9' '
-
git config core.whitespace "indent-with-non-tab,tabwidth=9" &&
git diff --check
-
'
test_expect_success 'check tabs and spaces as indentation (indent-with-non-tab: on)' '
-
git config core.whitespace "indent-with-non-tab" &&
- echo " foo ();" > x &&
+ echo " foo ();" >x &&
test_must_fail git diff --check
-
'
test_expect_success 'ditto, but tabwidth=10' '
-
git config core.whitespace "indent-with-non-tab,tabwidth=10" &&
test_must_fail git diff --check
-
'
test_expect_success 'ditto, but tabwidth=20' '
-
git config core.whitespace "indent-with-non-tab,tabwidth=20" &&
git diff --check
-
'
test_expect_success 'check tabs as indentation (tab-in-indent: off)' '
-
git config core.whitespace "-tab-in-indent" &&
- echo " foo ();" > x &&
+ echo " foo ();" >x &&
git diff --check
-
'
test_expect_success 'check tabs as indentation (tab-in-indent: on)' '
-
git config core.whitespace "tab-in-indent" &&
- echo " foo ();" > x &&
+ echo " foo ();" >x &&
test_must_fail git diff --check
-
'
test_expect_success 'check tabs and spaces as indentation (tab-in-indent: on)' '
-
git config core.whitespace "tab-in-indent" &&
- echo " foo ();" > x &&
+ echo " foo ();" >x &&
test_must_fail git diff --check
-
'
test_expect_success 'ditto, but tabwidth=1 (must be irrelevant)' '
-
git config core.whitespace "tab-in-indent,tabwidth=1" &&
test_must_fail git diff --check
-
'
test_expect_success 'check tab-in-indent and indent-with-non-tab conflict' '
-
git config core.whitespace "tab-in-indent,indent-with-non-tab" &&
- echo "foo ();" > x &&
+ echo "foo ();" >x &&
test_must_fail git diff --check
-
'
test_expect_success 'check tab-in-indent excluded from wildcard whitespace attribute' '
-
git config --unset core.whitespace &&
- echo "x whitespace" > .gitattributes &&
- echo " foo ();" > x &&
+ echo "x whitespace" >.gitattributes &&
+ echo " foo ();" >x &&
git diff --check &&
rm -f .gitattributes
-
'
test_expect_success 'line numbers in --check output are correct' '
-
- echo "" > x &&
- echo "foo(); " >> x &&
+ echo "" >x &&
+ echo "foo(); " >>x &&
git diff --check | grep "x:2:"
-
'
test_expect_success 'checkdiff detects new trailing blank lines (1)' '
git config color.diff.old red &&
git config color.diff.new green &&
git config color.diff.commit yellow &&
- git config color.diff.whitespace "normal red" &&
+ git config color.diff.whitespace blue &&
git config core.autocrlf false
'
-cat >expected <<\EOF
-<BOLD>diff --git a/x b/x<RESET>
-<BOLD>index 9daeafb..2874b91 100644<RESET>
-<BOLD>--- a/x<RESET>
-<BOLD>+++ b/x<RESET>
-<CYAN>@@ -1 +1,4 @@<RESET>
- test<RESET>
-<GREEN>+<RESET><GREEN>{<RESET>
-<GREEN>+<RESET><BRED> <RESET>
-<GREEN>+<RESET><GREEN>}<RESET>
-EOF
test_expect_success 'diff that introduces a line with only tabs' '
git config core.whitespace blank-at-eol &&
git reset --hard &&
- echo "test" > x &&
+ echo "test" >x &&
git commit -m "initial" x &&
- echo "{NTN}" | tr "NT" "\n\t" >> x &&
+ echo "{NTN}" | tr "NT" "\n\t" >>x &&
git -c color.diff=always diff | test_decode_color >current &&
+
+ cat >expected <<-\EOF &&
+ <BOLD>diff --git a/x b/x<RESET>
+ <BOLD>index 9daeafb..2874b91 100644<RESET>
+ <BOLD>--- a/x<RESET>
+ <BOLD>+++ b/x<RESET>
+ <CYAN>@@ -1 +1,4 @@<RESET>
+ test<RESET>
+ <GREEN>+<RESET><GREEN>{<RESET>
+ <GREEN>+<RESET><BLUE> <RESET>
+ <GREEN>+<RESET><GREEN>}<RESET>
+ EOF
+
+ test_cmp expected current
+'
+
+test_expect_success 'diff that introduces and removes ws breakages' '
+ git reset --hard &&
+ {
+ echo "0. blank-at-eol " &&
+ echo "1. blank-at-eol "
+ } >x &&
+ git commit -a --allow-empty -m preimage &&
+ {
+ echo "0. blank-at-eol " &&
+ echo "1. still-blank-at-eol " &&
+ echo "2. and a new line "
+ } >x &&
+
+ git -c color.diff=always diff |
+ test_decode_color >current &&
+
+ cat >expected <<-\EOF &&
+ <BOLD>diff --git a/x b/x<RESET>
+ <BOLD>index d0233a2..700886e 100644<RESET>
+ <BOLD>--- a/x<RESET>
+ <BOLD>+++ b/x<RESET>
+ <CYAN>@@ -1,2 +1,3 @@<RESET>
+ 0. blank-at-eol <RESET>
+ <RED>-1. blank-at-eol <RESET>
+ <GREEN>+<RESET><GREEN>1. still-blank-at-eol<RESET><BLUE> <RESET>
+ <GREEN>+<RESET><GREEN>2. and a new line<RESET><BLUE> <RESET>
+ EOF
+
+ test_cmp expected current
+'
+
+test_expect_success 'the same with --ws-error-highlight' '
+ git reset --hard &&
+ {
+ echo "0. blank-at-eol " &&
+ echo "1. blank-at-eol "
+ } >x &&
+ git commit -a --allow-empty -m preimage &&
+ {
+ echo "0. blank-at-eol " &&
+ echo "1. still-blank-at-eol " &&
+ echo "2. and a new line "
+ } >x &&
+
+ git -c color.diff=always diff --ws-error-highlight=default,old |
+ test_decode_color >current &&
+
+ cat >expected <<-\EOF &&
+ <BOLD>diff --git a/x b/x<RESET>
+ <BOLD>index d0233a2..700886e 100644<RESET>
+ <BOLD>--- a/x<RESET>
+ <BOLD>+++ b/x<RESET>
+ <CYAN>@@ -1,2 +1,3 @@<RESET>
+ 0. blank-at-eol <RESET>
+ <RED>-<RESET><RED>1. blank-at-eol<RESET><BLUE> <RESET>
+ <GREEN>+<RESET><GREEN>1. still-blank-at-eol<RESET><BLUE> <RESET>
+ <GREEN>+<RESET><GREEN>2. and a new line<RESET><BLUE> <RESET>
+ EOF
+
+ test_cmp expected current &&
+
+ git -c color.diff=always diff --ws-error-highlight=all |
+ test_decode_color >current &&
+
+ cat >expected <<-\EOF &&
+ <BOLD>diff --git a/x b/x<RESET>
+ <BOLD>index d0233a2..700886e 100644<RESET>
+ <BOLD>--- a/x<RESET>
+ <BOLD>+++ b/x<RESET>
+ <CYAN>@@ -1,2 +1,3 @@<RESET>
+ <RESET>0. blank-at-eol<RESET><BLUE> <RESET>
+ <RED>-<RESET><RED>1. blank-at-eol<RESET><BLUE> <RESET>
+ <GREEN>+<RESET><GREEN>1. still-blank-at-eol<RESET><BLUE> <RESET>
+ <GREEN>+<RESET><GREEN>2. and a new line<RESET><BLUE> <RESET>
+ EOF
+
+ test_cmp expected current &&
+
+ git -c color.diff=always diff --ws-error-highlight=none |
+ test_decode_color >current &&
+
+ cat >expected <<-\EOF &&
+ <BOLD>diff --git a/x b/x<RESET>
+ <BOLD>index d0233a2..700886e 100644<RESET>
+ <BOLD>--- a/x<RESET>
+ <BOLD>+++ b/x<RESET>
+ <CYAN>@@ -1,2 +1,3 @@<RESET>
+ 0. blank-at-eol <RESET>
+ <RED>-1. blank-at-eol <RESET>
+ <GREEN>+1. still-blank-at-eol <RESET>
+ <GREEN>+2. and a new line <RESET>
+ EOF
+
test_cmp expected current
'
EOF
'
+test_expect_success 'apply exits non-zero with no-op patch' '
+ cat >input <<-\EOF &&
+ diff --get a/1 b/1
+ index 6696ea4..606eddd 100644
+ --- a/1
+ +++ b/1
+ @@ -1,1 +1,1 @@
+ 1
+ EOF
+ test_must_fail git apply --stat input &&
+ test_must_fail git apply --check input
+'
+
test_done
grep "^\[foo\] third" actual
'
-test_expect_success 'am -3 falls back to 3-way merge' '
+test_expect_success 'setup am -3' '
rm -fr .git/rebase-apply &&
git reset --hard &&
- git checkout -b lorem2 master2 &&
+ git checkout -b base3way master2 &&
sed -n -e "3,\$p" msg >file &&
head -n 9 msg >>file &&
git add file &&
test_tick &&
- git commit -m "copied stuff" &&
+ git commit -m "copied stuff"
+'
+
+test_expect_success 'am -3 falls back to 3-way merge' '
+ rm -fr .git/rebase-apply &&
+ git reset --hard &&
+ git checkout -b lorem2 base3way &&
git am -3 lorem-move.patch &&
test_path_is_missing .git/rebase-apply &&
git diff --exit-code lorem
test_expect_success 'am -3 -p0 can read --no-prefix patch' '
rm -fr .git/rebase-apply &&
git reset --hard &&
- git checkout -b lorem3 master2 &&
- sed -n -e "3,\$p" msg >file &&
- head -n 9 msg >>file &&
- git add file &&
- test_tick &&
- git commit -m "copied stuff" &&
+ git checkout -b lorem3 base3way &&
git am -3 -p0 lorem-zero.patch &&
test_path_is_missing .git/rebase-apply &&
git diff --exit-code lorem
'
+test_expect_success 'am with config am.threeWay falls back to 3-way merge' '
+ rm -fr .git/rebase-apply &&
+ git reset --hard &&
+ git checkout -b lorem4 base3way &&
+ test_config am.threeWay 1 &&
+ git am lorem-move.patch &&
+ test_path_is_missing .git/rebase-apply &&
+ git diff --exit-code lorem
+'
+
+test_expect_success 'am with config am.threeWay overridden by --no-3way' '
+ rm -fr .git/rebase-apply &&
+ git reset --hard &&
+ git checkout -b lorem5 base3way &&
+ test_config am.threeWay 1 &&
+ test_must_fail git am --no-3way lorem-move.patch &&
+ test_path_is_dir .git/rebase-apply
+'
+
test_expect_success 'am can rename a file' '
grep "^rename from" rename.patch &&
rm -fr .git/rebase-apply &&
test_expect_success 'am -3 -q is quiet' '
rm -fr .git/rebase-apply &&
git checkout -f lorem2 &&
- git reset master2 --hard &&
- sed -n -e "3,\$p" msg >file &&
- head -n 9 msg >>file &&
- git add file &&
- test_tick &&
- git commit -m "copied stuff" &&
+ git reset base3way --hard &&
git am -3 -q lorem-move.patch >output.out 2>&1 &&
! test -s output.out
'
git add file-1 file-2 &&
git commit -m initial &&
git tag initial &&
+ git format-patch --stdout --root initial >initial.patch &&
for i in 2 3 4 5 6
do
echo $i >>file-1 &&
done
+test_expect_success 'am -3 --skip removes otherfile-4' '
+ git reset --hard initial &&
+ test_must_fail git am -3 0003-*.patch &&
+ test 3 -eq $(git ls-files -u | wc -l) &&
+ test 4 = "$(cat otherfile-4)" &&
+ git am --skip &&
+ test_cmp_rev initial HEAD &&
+ test -z "$(git ls-files -u)" &&
+ test_path_is_missing otherfile-4
+'
+
+test_expect_success 'am -3 --abort removes otherfile-4' '
+ git reset --hard initial &&
+ test_must_fail git am -3 0003-*.patch &&
+ test 3 -eq $(git ls-files -u | wc -l) &&
+ test 4 = "$(cat otherfile-4)" &&
+ git am --abort &&
+ test_cmp_rev initial HEAD &&
+ test -z $(git ls-files -u) &&
+ test_path_is_missing otherfile-4
+'
+
test_expect_success 'am --abort will keep the local commits intact' '
test_must_fail git am 0004-*.patch &&
test_commit unrelated &&
test_cmp expect actual
'
+test_expect_success 'am -3 stops on conflict on unborn branch' '
+ git checkout -f --orphan orphan &&
+ git reset &&
+ rm -f otherfile-4 &&
+ test_must_fail git am -3 0003-*.patch &&
+ test 2 -eq $(git ls-files -u | wc -l) &&
+ test 4 = "$(cat otherfile-4)"
+'
+
+test_expect_success 'am -3 --skip clears index on unborn branch' '
+ test_path_is_dir .git/rebase-apply &&
+ echo tmpfile >tmpfile &&
+ git add tmpfile &&
+ git am --skip &&
+ test -z "$(git ls-files)" &&
+ test_path_is_missing otherfile-4 &&
+ test_path_is_missing tmpfile
+'
+
+test_expect_success 'am -3 --abort removes otherfile-4 on unborn branch' '
+ git checkout -f --orphan orphan &&
+ git reset &&
+ rm -f otherfile-4 file-1 &&
+ test_must_fail git am -3 0003-*.patch &&
+ test 2 -eq $(git ls-files -u | wc -l) &&
+ test 4 = "$(cat otherfile-4)" &&
+ git am --abort &&
+ test -z "$(git ls-files -u)" &&
+ test_path_is_missing otherfile-4
+'
+
+test_expect_success 'am -3 --abort on unborn branch removes applied commits' '
+ git checkout -f --orphan orphan &&
+ git reset &&
+ rm -f otherfile-4 otherfile-2 file-1 file-2 &&
+ test_must_fail git am -3 initial.patch 0003-*.patch &&
+ test 3 -eq $(git ls-files -u | wc -l) &&
+ test 4 = "$(cat otherfile-4)" &&
+ git am --abort &&
+ test -z "$(git ls-files -u)" &&
+ test_path_is_missing otherfile-4 &&
+ test_path_is_missing file-1 &&
+ test_path_is_missing file-2 &&
+ test 0 -eq $(git log --oneline 2>/dev/null | wc -l) &&
+ test refs/heads/orphan = "$(git symbolic-ref HEAD)"
+'
+
+test_expect_success 'am --abort on unborn branch will keep local commits intact' '
+ git checkout -f --orphan orphan &&
+ git reset &&
+ test_must_fail git am 0004-*.patch &&
+ test_commit unrelated2 &&
+ git rev-parse HEAD >expect &&
+ git am --abort &&
+ git rev-parse HEAD >actual &&
+ test_cmp expect actual
+'
+
test_done
'
test_expect_success 'garbage report in count-objects -v' '
+ test_when_finished "rm -f .git/objects/pack/fake*" &&
: >.git/objects/pack/foo &&
: >.git/objects/pack/foo.bar &&
: >.git/objects/pack/foo.keep &&
git add foo &&
git rebase --continue &&
echo rebase >expected.args &&
- cat >expected.data <<EOF &&
-$(git rev-parse C) $(git rev-parse HEAD^)
-$(git rev-parse D) $(git rev-parse HEAD)
-EOF
+ cat >expected.data <<-EOF &&
+ $(git rev-parse C) $(git rev-parse HEAD^)
+ $(git rev-parse D) $(git rev-parse HEAD)
+ EOF
verify_hook_input
'
git add foo &&
git rebase --continue &&
echo rebase >expected.args &&
- cat >expected.data <<EOF &&
-$(git rev-parse D) $(git rev-parse HEAD)
-EOF
+ cat >expected.data <<-EOF &&
+ $(git rev-parse D) $(git rev-parse HEAD)
+ EOF
verify_hook_input
'
test_must_fail git rebase --onto D A &&
git rebase --skip &&
echo rebase >expected.args &&
- cat >expected.data <<EOF &&
-$(git rev-parse E) $(git rev-parse HEAD)
-EOF
+ cat >expected.data <<-EOF &&
+ $(git rev-parse E) $(git rev-parse HEAD)
+ EOF
verify_hook_input
'
git add foo &&
git rebase --continue &&
echo rebase >expected.args &&
- cat >expected.data <<EOF &&
-$(git rev-parse C) $(git rev-parse HEAD^)
-$(git rev-parse D) $(git rev-parse HEAD)
-EOF
+ cat >expected.data <<-EOF &&
+ $(git rev-parse C) $(git rev-parse HEAD^)
+ $(git rev-parse D) $(git rev-parse HEAD)
+ EOF
verify_hook_input
'
git add foo &&
git rebase --continue &&
echo rebase >expected.args &&
- cat >expected.data <<EOF &&
-$(git rev-parse D) $(git rev-parse HEAD)
-EOF
+ cat >expected.data <<-EOF &&
+ $(git rev-parse D) $(git rev-parse HEAD)
+ EOF
verify_hook_input
'
git add foo &&
git rebase --continue &&
echo rebase >expected.args &&
- cat >expected.data <<EOF &&
-$(git rev-parse C) $(git rev-parse HEAD^)
-$(git rev-parse D) $(git rev-parse HEAD)
-EOF
+ cat >expected.data <<-EOF &&
+ $(git rev-parse C) $(git rev-parse HEAD^)
+ $(git rev-parse D) $(git rev-parse HEAD)
+ EOF
verify_hook_input
'
git add foo &&
git rebase --continue &&
echo rebase >expected.args &&
- cat >expected.data <<EOF &&
-$(git rev-parse D) $(git rev-parse HEAD)
-EOF
+ cat >expected.data <<-EOF &&
+ $(git rev-parse D) $(git rev-parse HEAD)
+ EOF
verify_hook_input
'
git add foo &&
git rebase --continue &&
echo rebase >expected.args &&
- cat >expected.data <<EOF &&
-$(git rev-parse C) $(git rev-parse HEAD)
-$(git rev-parse D) $(git rev-parse HEAD)
-EOF
+ cat >expected.data <<-EOF &&
+ $(git rev-parse C) $(git rev-parse HEAD)
+ $(git rev-parse D) $(git rev-parse HEAD)
+ EOF
verify_hook_input
'
clear_hook_input &&
FAKE_LINES="1 fixup 2" git rebase -i B &&
echo rebase >expected.args &&
- cat >expected.data <<EOF &&
-$(git rev-parse C) $(git rev-parse HEAD)
-$(git rev-parse D) $(git rev-parse HEAD)
-EOF
+ cat >expected.data <<-EOF &&
+ $(git rev-parse C) $(git rev-parse HEAD)
+ $(git rev-parse D) $(git rev-parse HEAD)
+ EOF
verify_hook_input
'
git add foo &&
git rebase --continue &&
echo rebase >expected.args &&
- cat >expected.data <<EOF &&
-$(git rev-parse C) $(git rev-parse HEAD^)
-$(git rev-parse D) $(git rev-parse HEAD)
-EOF
+ cat >expected.data <<-EOF &&
+ $(git rev-parse C) $(git rev-parse HEAD^)
+ $(git rev-parse D) $(git rev-parse HEAD)
+ EOF
+ verify_hook_input
+'
+
+test_expect_success 'git rebase -i (exec)' '
+ git reset --hard D &&
+ clear_hook_input &&
+ FAKE_LINES="edit 1 exec_false 2" git rebase -i B &&
+ echo something >bar &&
+ git add bar &&
+ # Fails because of exec false
+ test_must_fail git rebase --continue &&
+ git rebase --continue &&
+ echo rebase >expected.args &&
+ cat >expected.data <<-EOF &&
+ $(git rev-parse C) $(git rev-parse HEAD^)
+ $(git rev-parse D) $(git rev-parse HEAD)
+ EOF
verify_hook_input
'
)
'
+for configallowtipsha1inwant in true false
+do
+ test_expect_success "shallow fetch reachable SHA1 (but not a ref), allowtipsha1inwant=$configallowtipsha1inwant" '
+ mk_empty testrepo &&
+ (
+ cd testrepo &&
+ git config uploadpack.allowtipsha1inwant $configallowtipsha1inwant &&
+ git commit --allow-empty -m foo &&
+ git commit --allow-empty -m bar
+ ) &&
+ SHA1=$(git --git-dir=testrepo/.git rev-parse HEAD^) &&
+ mk_empty shallow &&
+ (
+ cd shallow &&
+ test_must_fail git fetch --depth=1 ../testrepo/.git $SHA1 &&
+ git --git-dir=../testrepo/.git config uploadpack.allowreachablesha1inwant true &&
+ git fetch --depth=1 ../testrepo/.git $SHA1 &&
+ git cat-file commit $SHA1
+ )
+ '
+
+ test_expect_success "deny fetch unreachable SHA1, allowtipsha1inwant=$configallowtipsha1inwant" '
+ mk_empty testrepo &&
+ (
+ cd testrepo &&
+ git config uploadpack.allowtipsha1inwant $configallowtipsha1inwant &&
+ git commit --allow-empty -m foo &&
+ git commit --allow-empty -m bar &&
+ git commit --allow-empty -m xyz
+ ) &&
+ SHA1_1=$(git --git-dir=testrepo/.git rev-parse HEAD^^) &&
+ SHA1_2=$(git --git-dir=testrepo/.git rev-parse HEAD^) &&
+ SHA1_3=$(git --git-dir=testrepo/.git rev-parse HEAD) &&
+ (
+ cd testrepo &&
+ git reset --hard $SHA1_2 &&
+ git cat-file commit $SHA1_1 &&
+ git cat-file commit $SHA1_3
+ ) &&
+ mk_empty shallow &&
+ (
+ cd shallow &&
+ test_must_fail git fetch ../testrepo/.git $SHA1_3 &&
+ test_must_fail git fetch ../testrepo/.git $SHA1_1 &&
+ git --git-dir=../testrepo/.git config uploadpack.allowreachablesha1inwant true &&
+ git fetch ../testrepo/.git $SHA1_1 &&
+ git cat-file commit $SHA1_1 &&
+ test_must_fail git cat-file commit $SHA1_2 &&
+ git fetch ../testrepo/.git $SHA1_2 &&
+ git cat-file commit $SHA1_2 &&
+ test_must_fail git fetch ../testrepo/.git $SHA1_3
+ )
+ '
+done
+
test_expect_success 'fetch follows tags by default' '
mk_test testrepo heads/master &&
rm -fr src dst &&
mv "$2.x" "$2"
}
-D=`pwd`
-
test_expect_success setup '
-
echo file >file &&
git add file &&
git commit -a -m original
-
'
test_expect_success 'pulling into void' '
- mkdir cloned &&
- cd cloned &&
- git init &&
- git pull ..
-'
-
-cd "$D"
-
-test_expect_success 'checking the results' '
+ git init cloned &&
+ (
+ cd cloned &&
+ git pull ..
+ ) &&
test -f file &&
test -f cloned/file &&
test_cmp file cloned/file
'
test_expect_success 'pulling into void using master:master' '
- mkdir cloned-uho &&
+ git init cloned-uho &&
(
cd cloned-uho &&
- git init &&
git pull .. master:master
) &&
test -f file &&
)
'
-
test_expect_success 'pulling into void does not remove new staged files' '
git init cloned-staged-new &&
(
)
'
-test_expect_success 'test . as a remote' '
+test_expect_success 'pulling into void must not create an octopus' '
+ git init cloned-octopus &&
+ (
+ cd cloned-octopus &&
+ test_must_fail git pull .. master master &&
+ ! test -f file
+ )
+'
+test_expect_success 'test . as a remote' '
git branch copy master &&
git config branch.copy.remote . &&
git config branch.copy.merge refs/heads/master &&
echo updated >file &&
git commit -a -m updated &&
git checkout copy &&
- test `cat file` = file &&
+ test "$(cat file)" = file &&
git pull &&
- test `cat file` = updated
+ test "$(cat file)" = updated &&
+ git reflog -1 >reflog.actual &&
+ sed "s/^[0-9a-f][0-9a-f]*/OBJID/" reflog.actual >reflog.fuzzy &&
+ echo "OBJID HEAD@{0}: pull: Fast-forward" >reflog.expected &&
+ test_cmp reflog.expected reflog.fuzzy
'
test_expect_success 'the default remote . should not break explicit pull' '
git commit -a -m modified &&
git checkout copy &&
git reset --hard HEAD^ &&
- test `cat file` = file &&
+ test "$(cat file)" = file &&
git pull . second &&
- test `cat file` = modified
+ test "$(cat file)" = modified &&
+ git reflog -1 >reflog.actual &&
+ sed "s/^[0-9a-f][0-9a-f]*/OBJID/" reflog.actual >reflog.fuzzy &&
+ echo "OBJID HEAD@{0}: pull . second: Fast-forward" >reflog.expected &&
+ test_cmp reflog.expected reflog.fuzzy
+'
+
+test_expect_success 'fail if wildcard spec does not match any refs' '
+ git checkout -b test copy^ &&
+ test_when_finished "git checkout -f copy && git branch -D test" &&
+ test "$(cat file)" = file &&
+ test_must_fail git pull . "refs/nonexisting1/*:refs/nonexisting2/*" 2>err &&
+ test_i18ngrep "no candidates for merging" err &&
+ test "$(cat file)" = file
+'
+
+test_expect_success 'fail if no branches specified with non-default remote' '
+ git remote add test_remote . &&
+ test_when_finished "git remote remove test_remote" &&
+ git checkout -b test copy^ &&
+ test_when_finished "git checkout -f copy && git branch -D test" &&
+ test "$(cat file)" = file &&
+ test_config branch.test.remote origin &&
+ test_must_fail git pull test_remote 2>err &&
+ test_i18ngrep "specify a branch on the command line" err &&
+ test "$(cat file)" = file
+'
+
+test_expect_success 'fail if not on a branch' '
+ git remote add origin . &&
+ test_when_finished "git remote remove origin" &&
+ git checkout HEAD^ &&
+ test_when_finished "git checkout -f copy" &&
+ test "$(cat file)" = file &&
+ test_must_fail git pull 2>err &&
+ test_i18ngrep "not currently on a branch" err &&
+ test "$(cat file)" = file
+'
+
+test_expect_success 'fail if no configuration for current branch' '
+ git remote add test_remote . &&
+ test_when_finished "git remote remove test_remote" &&
+ git checkout -b test copy^ &&
+ test_when_finished "git checkout -f copy && git branch -D test" &&
+ test_config branch.test.remote test_remote &&
+ test "$(cat file)" = file &&
+ test_must_fail git pull 2>err &&
+ test_i18ngrep "no tracking information" err &&
+ test "$(cat file)" = file
+'
+
+test_expect_success 'pull --all: fail if no configuration for current branch' '
+ git remote add test_remote . &&
+ test_when_finished "git remote remove test_remote" &&
+ git checkout -b test copy^ &&
+ test_when_finished "git checkout -f copy && git branch -D test" &&
+ test_config branch.test.remote test_remote &&
+ test "$(cat file)" = file &&
+ test_must_fail git pull --all 2>err &&
+ test_i18ngrep "There is no tracking information" err &&
+ test "$(cat file)" = file
+'
+
+test_expect_success 'fail if upstream branch does not exist' '
+ git checkout -b test copy^ &&
+ test_when_finished "git checkout -f copy && git branch -D test" &&
+ test_config branch.test.remote . &&
+ test_config branch.test.merge refs/heads/nonexisting &&
+ test "$(cat file)" = file &&
+ test_must_fail git pull 2>err &&
+ test_i18ngrep "no such ref was fetched" err &&
+ test "$(cat file)" = file
+'
+
+test_expect_success 'fail if the index has unresolved entries' '
+ git checkout -b third second^ &&
+ test_when_finished "git checkout -f copy && git branch -D third" &&
+ test "$(cat file)" = file &&
+ test_commit modified2 file &&
+ test -z "$(git ls-files -u)" &&
+ test_must_fail git pull . second &&
+ test -n "$(git ls-files -u)" &&
+ cp file expected &&
+ test_must_fail git pull . second 2>err &&
+ test_i18ngrep "Pull is not possible because you have unmerged files" err &&
+ test_cmp expected file &&
+ git add file &&
+ test -z "$(git ls-files -u)" &&
+ test_must_fail git pull . second 2>err &&
+ test_i18ngrep "You have not concluded your merge" err &&
+ test_cmp expected file
+'
+
+test_expect_success 'fast-forwards working tree if branch head is updated' '
+ git checkout -b third second^ &&
+ test_when_finished "git checkout -f copy && git branch -D third" &&
+ test "$(cat file)" = file &&
+ git pull . second:third 2>err &&
+ test_i18ngrep "fetch updated the current branch head" err &&
+ test "$(cat file)" = modified &&
+ test "$(git rev-parse third)" = "$(git rev-parse second)"
+'
+
+test_expect_success 'fast-forward fails with conflicting work tree' '
+ git checkout -b third second^ &&
+ test_when_finished "git checkout -f copy && git branch -D third" &&
+ test "$(cat file)" = file &&
+ echo conflict >file &&
+ test_must_fail git pull . second:third 2>err &&
+ test_i18ngrep "Cannot fast-forward your working tree" err &&
+ test "$(cat file)" = conflict &&
+ test "$(git rev-parse third)" = "$(git rev-parse second)"
'
test_expect_success '--rebase' '
git commit -m "new file" &&
git tag before-rebase &&
git pull --rebase . copy &&
- test $(git rev-parse HEAD^) = $(git rev-parse copy) &&
- test new = $(git show HEAD:file2)
+ test "$(git rev-parse HEAD^)" = "$(git rev-parse copy)" &&
+ test new = "$(git show HEAD:file2)"
+'
+
+test_expect_success '--rebase fails with multiple branches' '
+ git reset --hard before-rebase &&
+ test_must_fail git pull --rebase . copy master 2>err &&
+ test "$(git rev-parse HEAD)" = "$(git rev-parse before-rebase)" &&
+ test_i18ngrep "Cannot rebase onto multiple branches" err &&
+ test modified = "$(git show HEAD:file)"
'
+
test_expect_success 'pull.rebase' '
git reset --hard before-rebase &&
test_config pull.rebase true &&
git pull . copy &&
- test $(git rev-parse HEAD^) = $(git rev-parse copy) &&
- test new = $(git show HEAD:file2)
+ test "$(git rev-parse HEAD^)" = "$(git rev-parse copy)" &&
+ test new = "$(git show HEAD:file2)"
'
test_expect_success 'branch.to-rebase.rebase' '
git reset --hard before-rebase &&
test_config branch.to-rebase.rebase true &&
git pull . copy &&
- test $(git rev-parse HEAD^) = $(git rev-parse copy) &&
- test new = $(git show HEAD:file2)
+ test "$(git rev-parse HEAD^)" = "$(git rev-parse copy)" &&
+ test new = "$(git show HEAD:file2)"
'
test_expect_success 'branch.to-rebase.rebase should override pull.rebase' '
test_config pull.rebase true &&
test_config branch.to-rebase.rebase false &&
git pull . copy &&
- test $(git rev-parse HEAD^) != $(git rev-parse copy) &&
- test new = $(git show HEAD:file2)
+ test "$(git rev-parse HEAD^)" != "$(git rev-parse copy)" &&
+ test new = "$(git show HEAD:file2)"
'
# add a feature branch, keep-merge, that is merged into master, so the
git reset --hard before-preserve-rebase &&
test_config pull.rebase false &&
git pull . copy &&
- test $(git rev-parse HEAD^1) = $(git rev-parse before-preserve-rebase) &&
- test $(git rev-parse HEAD^2) = $(git rev-parse copy) &&
- test file3 = $(git show HEAD:file3.t)
+ test "$(git rev-parse HEAD^1)" = "$(git rev-parse before-preserve-rebase)" &&
+ test "$(git rev-parse HEAD^2)" = "$(git rev-parse copy)" &&
+ test file3 = "$(git show HEAD:file3.t)"
'
test_expect_success 'pull.rebase=true flattens keep-merge' '
git reset --hard before-preserve-rebase &&
test_config pull.rebase true &&
git pull . copy &&
- test $(git rev-parse HEAD^^) = $(git rev-parse copy) &&
- test file3 = $(git show HEAD:file3.t)
+ test "$(git rev-parse HEAD^^)" = "$(git rev-parse copy)" &&
+ test file3 = "$(git show HEAD:file3.t)"
'
test_expect_success 'pull.rebase=1 is treated as true and flattens keep-merge' '
git reset --hard before-preserve-rebase &&
test_config pull.rebase 1 &&
git pull . copy &&
- test $(git rev-parse HEAD^^) = $(git rev-parse copy) &&
- test file3 = $(git show HEAD:file3.t)
+ test "$(git rev-parse HEAD^^)" = "$(git rev-parse copy)" &&
+ test file3 = "$(git show HEAD:file3.t)"
'
test_expect_success 'pull.rebase=preserve rebases and merges keep-merge' '
git reset --hard before-preserve-rebase &&
test_config pull.rebase preserve &&
git pull . copy &&
- test $(git rev-parse HEAD^^) = $(git rev-parse copy) &&
- test $(git rev-parse HEAD^2) = $(git rev-parse keep-merge)
+ test "$(git rev-parse HEAD^^)" = "$(git rev-parse copy)" &&
+ test "$(git rev-parse HEAD^2)" = "$(git rev-parse keep-merge)"
'
test_expect_success 'pull.rebase=invalid fails' '
git reset --hard before-preserve-rebase &&
test_config pull.rebase true &&
git pull --rebase=false . copy &&
- test $(git rev-parse HEAD^1) = $(git rev-parse before-preserve-rebase) &&
- test $(git rev-parse HEAD^2) = $(git rev-parse copy) &&
- test file3 = $(git show HEAD:file3.t)
+ test "$(git rev-parse HEAD^1)" = "$(git rev-parse before-preserve-rebase)" &&
+ test "$(git rev-parse HEAD^2)" = "$(git rev-parse copy)" &&
+ test file3 = "$(git show HEAD:file3.t)"
'
test_expect_success '--rebase=true rebases and flattens keep-merge' '
git reset --hard before-preserve-rebase &&
test_config pull.rebase preserve &&
git pull --rebase=true . copy &&
- test $(git rev-parse HEAD^^) = $(git rev-parse copy) &&
- test file3 = $(git show HEAD:file3.t)
+ test "$(git rev-parse HEAD^^)" = "$(git rev-parse copy)" &&
+ test file3 = "$(git show HEAD:file3.t)"
'
test_expect_success '--rebase=preserve rebases and merges keep-merge' '
git reset --hard before-preserve-rebase &&
test_config pull.rebase true &&
git pull --rebase=preserve . copy &&
- test $(git rev-parse HEAD^^) = $(git rev-parse copy) &&
- test $(git rev-parse HEAD^2) = $(git rev-parse keep-merge)
+ test "$(git rev-parse HEAD^^)" = "$(git rev-parse copy)" &&
+ test "$(git rev-parse HEAD^2)" = "$(git rev-parse keep-merge)"
'
test_expect_success '--rebase=invalid fails' '
git reset --hard before-preserve-rebase &&
test_config pull.rebase preserve &&
git pull --rebase . copy &&
- test $(git rev-parse HEAD^^) = $(git rev-parse copy) &&
- test file3 = $(git show HEAD:file3.t)
+ test "$(git rev-parse HEAD^^)" = "$(git rev-parse copy)" &&
+ test file3 = "$(git show HEAD:file3.t)"
'
test_expect_success '--rebase with rebased upstream' '
git tag to-rebase-orig &&
git pull --rebase me copy &&
test "conflicting modification" = "$(cat file)" &&
- test file = $(cat file2)
+ test file = "$(cat file2)"
'
+test_expect_success '--rebase -f with rebased upstream' '
+ test_when_finished "test_might_fail git rebase --abort" &&
+ git reset --hard to-rebase-orig &&
+ git pull --rebase -f me copy &&
+ test "conflicting modification" = "$(cat file)" &&
+ test file = "$(cat file2)"
+'
+
test_expect_success '--rebase with rebased default upstream' '
git update-ref refs/remotes/me/copy copy-orig &&
git reset --hard to-rebase-orig &&
git pull --rebase &&
test "conflicting modification" = "$(cat file)" &&
- test file = $(cat file2)
+ test file = "$(cat file2)"
'
git checkout to-rebase &&
git update-ref refs/remotes/me/copy copy^ &&
- COPY=$(git rev-parse --verify me/copy) &&
+ COPY="$(git rev-parse --verify me/copy)" &&
git rebase --onto $COPY copy &&
test_config branch.to-rebase.remote me &&
test_config branch.to-rebase.merge refs/heads/copy &&
echo dirty >> file &&
git add file &&
test_must_fail git pull &&
- test $COPY = $(git rev-parse --verify me/copy) &&
+ test "$COPY" = "$(git rev-parse --verify me/copy)" &&
git checkout HEAD -- file &&
git pull &&
- test $COPY != $(git rev-parse --verify me/copy)
+ test "$COPY" != "$(git rev-parse --verify me/copy)"
'
test_cmp expect actual
'
+test_expect_success 'pull --rebase fails on unborn branch with staged changes' '
+ test_when_finished "rm -rf empty_repo2" &&
+ git init empty_repo2 &&
+ (
+ cd empty_repo2 &&
+ echo staged-file >staged-file &&
+ git add staged-file &&
+ test "$(git ls-files)" = staged-file &&
+ test_must_fail git pull --rebase .. master 2>err &&
+ test "$(git ls-files)" = staged-file &&
+ test "$(git show :staged-file)" = staged-file &&
+ test_i18ngrep "unborn branch with changes added to the index" err
+ )
+'
+
test_expect_success 'setup for detecting upstreamed changes' '
mkdir src &&
(cd src &&
)
'
+test_expect_success 'git pull --dry-run' '
+ test_when_finished "rm -rf clonedry" &&
+ git init clonedry &&
+ (
+ cd clonedry &&
+ git pull --dry-run ../parent &&
+ test_path_is_missing .git/FETCH_HEAD &&
+ test_path_is_missing .git/refs/heads/master &&
+ test_path_is_missing .git/index &&
+ test_path_is_missing file
+ )
+'
+
+test_expect_success 'git pull --all --dry-run' '
+ test_when_finished "rm -rf cloneddry" &&
+ git init clonedry &&
+ (
+ cd clonedry &&
+ git remote add origin ../parent &&
+ git pull --all --dry-run &&
+ test_path_is_missing .git/FETCH_HEAD &&
+ test_path_is_missing .git/refs/remotes/origin/master &&
+ test_path_is_missing .git/index &&
+ test_path_is_missing file
+ )
+'
+
test_done
'
test_expect_success 'Merge with d/f conflicts' '
- test_expect_code 1 git merge "merge msg" B master
+ test_expect_code 1 git merge -m "merge msg" master
'
test_expect_success 'F/D conflict' '
" > file &&
git commit -m "C3" file &&
git branch C3 &&
-git merge "pre E3 merge" B A &&
+git merge -m "pre E3 merge" A &&
echo "1
2
3 changed in E3, branch B. New file size
" > file &&
git commit -m "E3" file &&
git checkout A &&
-git merge "pre D8 merge" A C3 &&
+git merge -m "pre D8 merge" C3 &&
echo "1
2
3 changed in C3, branch B
9" > file &&
git commit -m D8 file'
-test_expect_success 'Criss-cross merge' 'git merge "final merge" A B'
+test_expect_success 'Criss-cross merge' 'git merge -m "final merge" B'
cat > file-expect <<EOF
1
cat >./custom-merge <<\EOF
#!/bin/sh
-orig="$1" ours="$2" theirs="$3" exit="$4"
+orig="$1" ours="$2" theirs="$3" exit="$4" path=$5
(
echo "orig is $orig"
echo "ours is $ours"
echo "theirs is $theirs"
+ echo "path is $path"
echo "=== orig ==="
cat "$orig"
echo "=== ours ==="
git reset --hard anchor &&
git config --replace-all \
- merge.custom.driver "./custom-merge %O %A %B 0" &&
+ merge.custom.driver "./custom-merge %O %A %B 0 %P" &&
git config --replace-all \
merge.custom.name "custom merge driver for testing" &&
o=$(git unpack-file master^:text) &&
a=$(git unpack-file side^:text) &&
b=$(git unpack-file master:text) &&
- sh -c "./custom-merge $o $a $b 0" &&
+ sh -c "./custom-merge $o $a $b 0 'text'" &&
sed -e 1,3d $a >check-2 &&
cmp check-1 check-2 &&
rm -f $o $a $b
git reset --hard anchor &&
git config --replace-all \
- merge.custom.driver "./custom-merge %O %A %B 1" &&
+ merge.custom.driver "./custom-merge %O %A %B 1 %P" &&
git config --replace-all \
merge.custom.name "custom merge driver for testing" &&
o=$(git unpack-file master^:text) &&
a=$(git unpack-file anchor:text) &&
b=$(git unpack-file master:text) &&
- sh -c "./custom-merge $o $a $b 0" &&
+ sh -c "./custom-merge $o $a $b 0 'text'" &&
sed -e 1,3d $a >check-2 &&
cmp check-1 check-2 &&
+ sed -e 1,3d -e 4q $a >check-3 &&
+ echo "path is text" >expect &&
+ cmp expect check-3 &&
rm -f $o $a $b
'
git update-ref refs/remotes/origin/master master &&
git remote add origin nowhere &&
git config branch.master.remote origin &&
- git config branch.master.merge refs/heads/master
+ git config branch.master.merge refs/heads/master &&
+ git remote add myfork elsewhere &&
+ git config remote.pushdefault myfork &&
+ git config push.default current
'
test_atom() {
test_atom head refname refs/heads/master
test_atom head upstream refs/remotes/origin/master
+test_atom head push refs/remotes/myfork/master
test_atom head objecttype commit
test_atom head objectsize 171
test_atom head objectname $(git rev-parse refs/heads/master)
test_atom tag refname refs/tags/testtag
test_atom tag upstream ''
+test_atom tag push ''
test_atom tag objecttype tag
test_atom tag objectsize 154
test_atom tag objectname $(git rev-parse refs/tags/testtag)
test_cmp expected actual
'
+test_expect_success '%(push) supports tracking specifiers, too' '
+ echo "[ahead 1]" >expected &&
+ git for-each-ref --format="%(push:track)" refs/heads >actual &&
+ test_cmp expected actual
+'
+
cat >expected <<EOF
$(git rev-parse --short HEAD)
EOF
--- /dev/null
+#!/bin/sh
+
+test_description='for-each-ref errors for broken refs'
+
+. ./test-lib.sh
+
+ZEROS=$_z40
+MISSING=abababababababababababababababababababab
+
+test_expect_success setup '
+ git commit --allow-empty -m "Initial" &&
+ git tag testtag &&
+ git for-each-ref >full-list &&
+ git for-each-ref --format="%(objectname) %(refname)" >brief-list
+'
+
+test_expect_success 'Broken refs are reported correctly' '
+ r=refs/heads/bogus &&
+ : >.git/$r &&
+ test_when_finished "rm -f .git/$r" &&
+ echo "warning: ignoring broken ref $r" >broken-err &&
+ git for-each-ref >out 2>err &&
+ test_cmp full-list out &&
+ test_cmp broken-err err
+'
+
+test_expect_success 'NULL_SHA1 refs are reported correctly' '
+ r=refs/heads/zeros &&
+ echo $ZEROS >.git/$r &&
+ test_when_finished "rm -f .git/$r" &&
+ echo "warning: ignoring broken ref $r" >zeros-err &&
+ git for-each-ref >out 2>err &&
+ test_cmp full-list out &&
+ test_cmp zeros-err err &&
+ git for-each-ref --format="%(objectname) %(refname)" >brief-out 2>brief-err &&
+ test_cmp brief-list brief-out &&
+ test_cmp zeros-err brief-err
+'
+
+test_expect_success 'Missing objects are reported correctly' '
+ r=refs/heads/missing &&
+ echo $MISSING >.git/$r &&
+ test_when_finished "rm -f .git/$r" &&
+ echo "fatal: missing object $MISSING for $r" >missing-err &&
+ test_must_fail git for-each-ref 2>err &&
+ test_cmp missing-err err &&
+ (
+ cat brief-list &&
+ echo "$MISSING $r"
+ ) | sort -k 2 >missing-brief-expected &&
+ git for-each-ref --format="%(objectname) %(refname)" >brief-out 2>brief-err &&
+ test_cmp missing-brief-expected brief-out &&
+ test_must_be_empty brief-err
+'
+
+test_done
'
done
+test_expect_success 'do not complain about existing broken links' '
+ cat >broken-commit <<-\EOF &&
+ tree 0000000000000000000000000000000000000001
+ parent 0000000000000000000000000000000000000002
+ author whatever <whatever@example.com> 1234 -0000
+ committer whatever <whatever@example.com> 1234 -0000
+
+ some message
+ EOF
+ commit=$(git hash-object -t commit -w broken-commit) &&
+ git gc 2>stderr &&
+ verbose git cat-file -e $commit &&
+ test_must_be_empty stderr
+'
+
test_done
--- /dev/null
+#!/bin/sh
+
+test_description='test untracked cache'
+
+. ./test-lib.sh
+
+avoid_racy() {
+ sleep 1
+}
+
+# It's fine if git update-index returns an error code other than one,
+# it'll be caught in the first test.
+test_lazy_prereq UNTRACKED_CACHE '
+ { git update-index --untracked-cache; ret=$?; } &&
+ test $ret -ne 1
+'
+
+if ! test_have_prereq UNTRACKED_CACHE; then
+ skip_all='This system does not support untracked cache'
+ test_done
+fi
+
+test_expect_success 'setup' '
+ git init worktree &&
+ cd worktree &&
+ mkdir done dtwo dthree &&
+ touch one two three done/one dtwo/two dthree/three &&
+ git add one two done/one &&
+ : >.git/info/exclude &&
+ git update-index --untracked-cache
+'
+
+test_expect_success 'untracked cache is empty' '
+ test-dump-untracked-cache >../actual &&
+ cat >../expect <<EOF &&
+info/exclude 0000000000000000000000000000000000000000
+core.excludesfile 0000000000000000000000000000000000000000
+exclude_per_dir .gitignore
+flags 00000006
+EOF
+ test_cmp ../expect ../actual
+'
+
+cat >../status.expect <<EOF &&
+A done/one
+A one
+A two
+?? dthree/
+?? dtwo/
+?? three
+EOF
+
+cat >../dump.expect <<EOF &&
+info/exclude e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
+core.excludesfile 0000000000000000000000000000000000000000
+exclude_per_dir .gitignore
+flags 00000006
+/ 0000000000000000000000000000000000000000 recurse valid
+dthree/
+dtwo/
+three
+/done/ 0000000000000000000000000000000000000000 recurse valid
+/dthree/ 0000000000000000000000000000000000000000 recurse check_only valid
+three
+/dtwo/ 0000000000000000000000000000000000000000 recurse check_only valid
+two
+EOF
+
+test_expect_success 'status first time (empty cache)' '
+ avoid_racy &&
+ : >../trace &&
+ GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
+ git status --porcelain >../actual &&
+ test_cmp ../status.expect ../actual &&
+ cat >../trace.expect <<EOF &&
+node creation: 3
+gitignore invalidation: 1
+directory invalidation: 0
+opendir: 4
+EOF
+ test_cmp ../trace.expect ../trace
+'
+
+test_expect_success 'untracked cache after first status' '
+ test-dump-untracked-cache >../actual &&
+ test_cmp ../dump.expect ../actual
+'
+
+test_expect_success 'status second time (fully populated cache)' '
+ avoid_racy &&
+ : >../trace &&
+ GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
+ git status --porcelain >../actual &&
+ test_cmp ../status.expect ../actual &&
+ cat >../trace.expect <<EOF &&
+node creation: 0
+gitignore invalidation: 0
+directory invalidation: 0
+opendir: 0
+EOF
+ test_cmp ../trace.expect ../trace
+'
+
+test_expect_success 'untracked cache after second status' '
+ test-dump-untracked-cache >../actual &&
+ test_cmp ../dump.expect ../actual
+'
+
+test_expect_success 'modify in root directory, one dir invalidation' '
+ avoid_racy &&
+ : >four &&
+ : >../trace &&
+ GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
+ git status --porcelain >../actual &&
+ cat >../status.expect <<EOF &&
+A done/one
+A one
+A two
+?? dthree/
+?? dtwo/
+?? four
+?? three
+EOF
+ test_cmp ../status.expect ../actual &&
+ cat >../trace.expect <<EOF &&
+node creation: 0
+gitignore invalidation: 0
+directory invalidation: 1
+opendir: 1
+EOF
+ test_cmp ../trace.expect ../trace
+
+'
+
+test_expect_success 'verify untracked cache dump' '
+ test-dump-untracked-cache >../actual &&
+ cat >../expect <<EOF &&
+info/exclude e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
+core.excludesfile 0000000000000000000000000000000000000000
+exclude_per_dir .gitignore
+flags 00000006
+/ 0000000000000000000000000000000000000000 recurse valid
+dthree/
+dtwo/
+four
+three
+/done/ 0000000000000000000000000000000000000000 recurse valid
+/dthree/ 0000000000000000000000000000000000000000 recurse check_only valid
+three
+/dtwo/ 0000000000000000000000000000000000000000 recurse check_only valid
+two
+EOF
+ test_cmp ../expect ../actual
+'
+
+test_expect_success 'new .gitignore invalidates recursively' '
+ avoid_racy &&
+ echo four >.gitignore &&
+ : >../trace &&
+ GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
+ git status --porcelain >../actual &&
+ cat >../status.expect <<EOF &&
+A done/one
+A one
+A two
+?? .gitignore
+?? dthree/
+?? dtwo/
+?? three
+EOF
+ test_cmp ../status.expect ../actual &&
+ cat >../trace.expect <<EOF &&
+node creation: 0
+gitignore invalidation: 1
+directory invalidation: 1
+opendir: 4
+EOF
+ test_cmp ../trace.expect ../trace
+
+'
+
+test_expect_success 'verify untracked cache dump' '
+ test-dump-untracked-cache >../actual &&
+ cat >../expect <<EOF &&
+info/exclude e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
+core.excludesfile 0000000000000000000000000000000000000000
+exclude_per_dir .gitignore
+flags 00000006
+/ e6fcc8f2ee31bae321d66afd183fcb7237afae6e recurse valid
+.gitignore
+dthree/
+dtwo/
+three
+/done/ 0000000000000000000000000000000000000000 recurse valid
+/dthree/ 0000000000000000000000000000000000000000 recurse check_only valid
+three
+/dtwo/ 0000000000000000000000000000000000000000 recurse check_only valid
+two
+EOF
+ test_cmp ../expect ../actual
+'
+
+test_expect_success 'new info/exclude invalidates everything' '
+ avoid_racy &&
+ echo three >>.git/info/exclude &&
+ : >../trace &&
+ GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
+ git status --porcelain >../actual &&
+ cat >../status.expect <<EOF &&
+A done/one
+A one
+A two
+?? .gitignore
+?? dtwo/
+EOF
+ test_cmp ../status.expect ../actual &&
+ cat >../trace.expect <<EOF &&
+node creation: 0
+gitignore invalidation: 1
+directory invalidation: 0
+opendir: 4
+EOF
+ test_cmp ../trace.expect ../trace
+'
+
+test_expect_success 'verify untracked cache dump' '
+ test-dump-untracked-cache >../actual &&
+ cat >../expect <<EOF &&
+info/exclude 13263c0978fb9fad16b2d580fb800b6d811c3ff0
+core.excludesfile 0000000000000000000000000000000000000000
+exclude_per_dir .gitignore
+flags 00000006
+/ e6fcc8f2ee31bae321d66afd183fcb7237afae6e recurse valid
+.gitignore
+dtwo/
+/done/ 0000000000000000000000000000000000000000 recurse valid
+/dthree/ 0000000000000000000000000000000000000000 recurse check_only valid
+/dtwo/ 0000000000000000000000000000000000000000 recurse check_only valid
+two
+EOF
+ test_cmp ../expect ../actual
+'
+
+test_expect_success 'move two from tracked to untracked' '
+ git rm --cached two &&
+ test-dump-untracked-cache >../actual &&
+ cat >../expect <<EOF &&
+info/exclude 13263c0978fb9fad16b2d580fb800b6d811c3ff0
+core.excludesfile 0000000000000000000000000000000000000000
+exclude_per_dir .gitignore
+flags 00000006
+/ e6fcc8f2ee31bae321d66afd183fcb7237afae6e recurse
+/done/ 0000000000000000000000000000000000000000 recurse valid
+/dthree/ 0000000000000000000000000000000000000000 recurse check_only valid
+/dtwo/ 0000000000000000000000000000000000000000 recurse check_only valid
+two
+EOF
+ test_cmp ../expect ../actual
+'
+
+test_expect_success 'status after the move' '
+ : >../trace &&
+ GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
+ git status --porcelain >../actual &&
+ cat >../status.expect <<EOF &&
+A done/one
+A one
+?? .gitignore
+?? dtwo/
+?? two
+EOF
+ test_cmp ../status.expect ../actual &&
+ cat >../trace.expect <<EOF &&
+node creation: 0
+gitignore invalidation: 0
+directory invalidation: 0
+opendir: 1
+EOF
+ test_cmp ../trace.expect ../trace
+'
+
+test_expect_success 'verify untracked cache dump' '
+ test-dump-untracked-cache >../actual &&
+ cat >../expect <<EOF &&
+info/exclude 13263c0978fb9fad16b2d580fb800b6d811c3ff0
+core.excludesfile 0000000000000000000000000000000000000000
+exclude_per_dir .gitignore
+flags 00000006
+/ e6fcc8f2ee31bae321d66afd183fcb7237afae6e recurse valid
+.gitignore
+dtwo/
+two
+/done/ 0000000000000000000000000000000000000000 recurse valid
+/dthree/ 0000000000000000000000000000000000000000 recurse check_only valid
+/dtwo/ 0000000000000000000000000000000000000000 recurse check_only valid
+two
+EOF
+ test_cmp ../expect ../actual
+'
+
+test_expect_success 'move two from untracked to tracked' '
+ git add two &&
+ test-dump-untracked-cache >../actual &&
+ cat >../expect <<EOF &&
+info/exclude 13263c0978fb9fad16b2d580fb800b6d811c3ff0
+core.excludesfile 0000000000000000000000000000000000000000
+exclude_per_dir .gitignore
+flags 00000006
+/ e6fcc8f2ee31bae321d66afd183fcb7237afae6e recurse
+/done/ 0000000000000000000000000000000000000000 recurse valid
+/dthree/ 0000000000000000000000000000000000000000 recurse check_only valid
+/dtwo/ 0000000000000000000000000000000000000000 recurse check_only valid
+two
+EOF
+ test_cmp ../expect ../actual
+'
+
+test_expect_success 'status after the move' '
+ : >../trace &&
+ GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
+ git status --porcelain >../actual &&
+ cat >../status.expect <<EOF &&
+A done/one
+A one
+A two
+?? .gitignore
+?? dtwo/
+EOF
+ test_cmp ../status.expect ../actual &&
+ cat >../trace.expect <<EOF &&
+node creation: 0
+gitignore invalidation: 0
+directory invalidation: 0
+opendir: 1
+EOF
+ test_cmp ../trace.expect ../trace
+'
+
+test_expect_success 'verify untracked cache dump' '
+ test-dump-untracked-cache >../actual &&
+ cat >../expect <<EOF &&
+info/exclude 13263c0978fb9fad16b2d580fb800b6d811c3ff0
+core.excludesfile 0000000000000000000000000000000000000000
+exclude_per_dir .gitignore
+flags 00000006
+/ e6fcc8f2ee31bae321d66afd183fcb7237afae6e recurse valid
+.gitignore
+dtwo/
+/done/ 0000000000000000000000000000000000000000 recurse valid
+/dthree/ 0000000000000000000000000000000000000000 recurse check_only valid
+/dtwo/ 0000000000000000000000000000000000000000 recurse check_only valid
+two
+EOF
+ test_cmp ../expect ../actual
+'
+
+test_done
--- /dev/null
+#!/bin/sh
+
+test_description='Combination of submodules and multiple workdirs'
+
+. ./test-lib.sh
+
+base_path=$(pwd -P)
+
+test_expect_success 'setup: make origin' \
+ 'mkdir -p origin/sub && ( cd origin/sub && git init &&
+ echo file1 >file1 &&
+ git add file1 &&
+ git commit -m file1 ) &&
+ mkdir -p origin/main && ( cd origin/main && git init &&
+ git submodule add ../sub &&
+ git commit -m "add sub" ) &&
+ ( cd origin/sub &&
+ echo file1updated >file1 &&
+ git add file1 &&
+ git commit -m "file1 updated" ) &&
+ ( cd origin/main/sub && git pull ) &&
+ ( cd origin/main &&
+ git add sub &&
+ git commit -m "sub updated" )'
+
+test_expect_success 'setup: clone' \
+ 'mkdir clone && ( cd clone &&
+ git clone --recursive "$base_path/origin/main")'
+
+rev1_hash_main=$(git --git-dir=origin/main/.git show --pretty=format:%h -q "HEAD~1")
+rev1_hash_sub=$(git --git-dir=origin/sub/.git show --pretty=format:%h -q "HEAD~1")
+
+test_expect_success 'checkout main' \
+ 'mkdir default_checkout &&
+ (cd clone/main &&
+ git checkout --to "$base_path/default_checkout/main" "$rev1_hash_main")'
+
+test_expect_failure 'can see submodule diffs just after checkout' \
+ '(cd default_checkout/main && git diff --submodule master"^!" | grep "file1 updated")'
+
+test_expect_success 'checkout main and initialize independed clones' \
+ 'mkdir fully_cloned_submodule &&
+ (cd clone/main &&
+ git checkout --to "$base_path/fully_cloned_submodule/main" "$rev1_hash_main") &&
+ (cd fully_cloned_submodule/main && git submodule update)'
+
+test_expect_success 'can see submodule diffs after independed cloning' \
+ '(cd fully_cloned_submodule/main && git diff --submodule master"^!" | grep "file1 updated")'
+
+test_done
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
- echo "# to be kept" >expect &&
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)' '
EOF
test_expect_success !AUTOIDENT 'do not fire editor when committer is bogus' '
- >.git/result
+ >.git/result &&
>expect &&
echo >>negative &&
"<E at test dot git>" 1
'
+test_expect_success 'setup showEmail tests' '
+ echo "bin: test number 1" >one &&
+ git add one &&
+ GIT_AUTHOR_NAME=name1 \
+ GIT_AUTHOR_EMAIL=email1@test.git \
+ git commit -m First --date="2010-01-01 01:00:00" &&
+ cat >expected_n <<-\EOF &&
+ (name1 2010-01-01 01:00:00 +0000 1) bin: test number 1
+ EOF
+ cat >expected_e <<-\EOF
+ (<email1@test.git> 2010-01-01 01:00:00 +0000 1) bin: test number 1
+ EOF
+'
+
+find_blame () {
+ sed -e 's/^[^(]*//'
+}
+
+test_expect_success 'blame with no options and no config' '
+ git blame one >blame &&
+ find_blame <blame >result &&
+ test_cmp expected_n result
+'
+
+test_expect_success 'blame with showemail options' '
+ git blame --show-email one >blame1 &&
+ find_blame <blame1 >result &&
+ test_cmp expected_e result &&
+ git blame -e one >blame2 &&
+ find_blame <blame2 >result &&
+ test_cmp expected_e result &&
+ git blame --no-show-email one >blame3 &&
+ find_blame <blame3 >result &&
+ test_cmp expected_n result
+'
+
+test_expect_success 'blame with showEmail config false' '
+ git config blame.showEmail false &&
+ git blame one >blame1 &&
+ find_blame <blame1 >result &&
+ test_cmp expected_n result &&
+ git blame --show-email one >blame2 &&
+ find_blame <blame2 >result &&
+ test_cmp expected_e result &&
+ git blame -e one >blame3 &&
+ find_blame <blame3 >result &&
+ test_cmp expected_e result &&
+ git blame --no-show-email one >blame4 &&
+ find_blame <blame4 >result &&
+ test_cmp expected_n result
+'
+
+test_expect_success 'blame with showEmail config true' '
+ git config blame.showEmail true &&
+ git blame one >blame1 &&
+ find_blame <blame1 >result &&
+ test_cmp expected_e result &&
+ git blame --no-show-email one >blame2 &&
+ find_blame <blame2 >result &&
+ test_cmp expected_n result
+'
+
test_done
test_expect_success $PREREQ 'sendemail.aliasfile=~/.mailrc' '
clean_fake_sendmail &&
- echo "alias sbd someone@example.org" >~/.mailrc &&
+ echo "alias sbd someone@example.org" >"$HOME/.mailrc" &&
git config --replace-all sendemail.aliasesfile "~/.mailrc" &&
git config sendemail.aliasfiletype mailrc &&
git send-email \
grep "^!someone@example\.org!$" commandline1
'
+test_sendmail_aliases () {
+ msg="$1" && shift &&
+ expect="$@" &&
+ cat >.tmp-email-aliases &&
+
+ test_expect_success $PREREQ "$msg" '
+ clean_fake_sendmail && rm -fr outdir &&
+ git format-patch -1 -o outdir &&
+ git config --replace-all sendemail.aliasesfile \
+ "$(pwd)/.tmp-email-aliases" &&
+ git config sendemail.aliasfiletype sendmail &&
+ git send-email \
+ --from="Example <nobody@example.com>" \
+ --to=alice --to=bcgrp \
+ --smtp-server="$(pwd)/fake.sendmail" \
+ outdir/0001-*.patch \
+ 2>errors >out &&
+ for i in $expect
+ do
+ grep "^!$i!$" commandline1 || return 1
+ done
+ '
+}
+
+test_sendmail_aliases 'sendemail.aliasfiletype=sendmail' \
+ 'awol@example\.com' \
+ 'bob@example\.com' \
+ 'chloe@example\.com' \
+ 'o@example\.com' <<-\EOF
+ alice: Alice W Land <awol@example.com>
+ bob: Robert Bobbyton <bob@example.com>
+ # this is a comment
+ # this is also a comment
+ chloe: chloe@example.com
+ abgroup: alice, bob
+ bcgrp: bob, chloe, Other <o@example.com>
+ EOF
+
+test_sendmail_aliases 'sendmail aliases line folding' \
+ alice1 \
+ bob1 bob2 \
+ chuck1 chuck2 \
+ darla1 darla2 darla3 \
+ elton1 elton2 elton3 \
+ fred1 fred2 \
+ greg1 <<-\EOF
+ alice: alice1
+ bob: bob1,\
+ bob2
+ chuck: chuck1,
+ chuck2
+ darla: darla1,\
+ darla2,
+ darla3
+ elton: elton1,
+ elton2,\
+ elton3
+ fred: fred1,\
+ fred2
+ greg: greg1
+ bcgrp: bob, chuck, darla, elton, fred, greg
+ EOF
+
+test_sendmail_aliases 'sendmail aliases tolerate bogus line folding' \
+ alice1 bob1 <<-\EOF
+ alice: alice1
+ bcgrp: bob1\
+ EOF
+
+test_sendmail_aliases 'sendmail aliases empty' alice bcgrp <<-\EOF
+ EOF
+
do_xmailer_test () {
expected=$1 params=$2 &&
git format-patch -1 &&
'
test_expect_success 'merge early [cvswork3] b3 with b1' '
- ( cd gitwork3 && git merge "message" HEAD b1 ) &&
+ ( cd gitwork3 && git merge -m "message" b1 ) &&
git fetch gitwork3 b3:b3 &&
git tag v3merged b3 &&
git push --tags gitcvs.git b3:b3
)
'
+revision_ranges="2000/01/01,#head \
+ 1,2080/01/01 \
+ 2000/01/01,2080/01/01 \
+ 2000/01/01,1000 \
+ 1,1000"
+
+test_expect_success 'clone using non-numeric revision ranges' '
+ test_when_finished cleanup_git &&
+ for r in $revision_ranges
+ do
+ rm -fr "$git" &&
+ test ! -d "$git" &&
+ git p4 clone --dest="$git" //depot@$r &&
+ (
+ cd "$git" &&
+ git ls-files >lines &&
+ test_line_count = 6 lines
+ )
+ done
+'
+
+test_expect_success 'clone with date range, excluding some changes' '
+ test_when_finished cleanup_git &&
+ before=$(date +%Y/%m/%d:%H:%M:%S) &&
+ sleep 2 &&
+ (
+ cd "$cli" &&
+ :>date_range_test &&
+ p4 add date_range_test &&
+ p4 submit -d "Adding file"
+ ) &&
+ git p4 clone --dest="$git" //depot@1,$before &&
+ (
+ cd "$git" &&
+ test_path_is_missing date_range_test
+ )
+'
+
test_expect_success 'exit when p4 fails to produce marshaled output' '
mkdir badp4dir &&
test_when_finished "rm badp4dir/p4 && rmdir badp4dir" &&
)
'
+test_expect_success 'restart p4d' '
+ kill_p4d &&
+ start_p4d
+'
+
+#
+# 1: //depot/branch1/base/file1
+# //depot/branch1/base/file2
+# //depot/branch1/base/dir/sub_file1
+# 2: integrate //depot/branch1/base/... -> //depot/branch2/base/...
+# 3: //depot/branch1/base/file3
+# 4: //depot/branch1/base/file2 (edit)
+# 5: integrate //depot/branch1/base/... -> //depot/branch3/base/...
+#
+# Note: the client view removes the "base" folder from the workspace
+# and moves sub_file1 one level up.
+test_expect_success 'add simple p4 branches with common base folder on each branch' '
+ (
+ cd "$cli" &&
+ client_view "//depot/branch1/base/... //client/branch1/..." \
+ "//depot/branch1/base/dir/sub_file1 //client/branch1/sub_file1" \
+ "//depot/branch2/base/... //client/branch2/..." \
+ "//depot/branch3/base/... //client/branch3/..." &&
+ mkdir -p branch1 &&
+ cd branch1 &&
+ echo file1 >file1 &&
+ echo file2 >file2 &&
+ mkdir dir &&
+ echo sub_file1 >sub_file1 &&
+ p4 add file1 file2 sub_file1 &&
+ p4 submit -d "Create branch1" &&
+ p4 integrate //depot/branch1/base/... //depot/branch2/base/... &&
+ p4 submit -d "Integrate branch2 from branch1" &&
+ echo file3 >file3 &&
+ p4 add file3 &&
+ p4 submit -d "add file3 in branch1" &&
+ p4 open file2 &&
+ echo update >>file2 &&
+ p4 submit -d "update file2 in branch1" &&
+ p4 integrate //depot/branch1/base/... //depot/branch3/base/... &&
+ p4 submit -d "Integrate branch3 from branch1"
+ )
+'
+
+# Configure branches through git-config and clone them.
+# All files are tested to make sure branches were cloned correctly.
+# Finally, make an update to branch1 on P4 side to check if it is imported
+# correctly by git p4.
+# git p4 is expected to use the client view to also not include the common
+# "base" folder in the imported directory structure.
+test_expect_success 'git p4 clone simple branches with base folder on server side' '
+ test_create_repo "$git" &&
+ (
+ cd "$git" &&
+ git config git-p4.branchList branch1:branch2 &&
+ git config --add git-p4.branchList branch1:branch3 &&
+ git p4 clone --dest=. --use-client-spec --detect-branches //depot@all &&
+ git log --all --graph --decorate --stat &&
+ git reset --hard p4/depot/branch1 &&
+ test -f file1 &&
+ test -f file2 &&
+ test -f file3 &&
+ test -f sub_file1 &&
+ grep update file2 &&
+ git reset --hard p4/depot/branch2 &&
+ test -f file1 &&
+ test -f file2 &&
+ test ! -f file3 &&
+ test -f sub_file1 &&
+ ! grep update file2 &&
+ git reset --hard p4/depot/branch3 &&
+ test -f file1 &&
+ test -f file2 &&
+ test -f file3 &&
+ test -f sub_file1 &&
+ grep update file2 &&
+ cd "$cli" &&
+ cd branch1 &&
+ p4 edit file2 &&
+ echo file2_ >>file2 &&
+ p4 submit -d "update file2 in branch1" &&
+ cd "$git" &&
+ git reset --hard p4/depot/branch1 &&
+ git p4 rebase &&
+ grep file2_ file2
+ )
+'
+
+# Now update a file in one of the branches in git and submit to P4
+test_expect_success 'Update a file in git side and submit to P4 using client view' '
+ test_when_finished cleanup_git &&
+ (
+ cd "$git" &&
+ git reset --hard p4/depot/branch1 &&
+ echo "client spec" >> file1 &&
+ git add -u . &&
+ git commit -m "update file1 in branch1" &&
+ git config git-p4.skipSubmitEdit true &&
+ git p4 submit --verbose &&
+ cd "$cli" &&
+ p4 sync ... &&
+ cd branch1 &&
+ grep "client spec" file1
+ )
+'
+
test_expect_success 'kill p4d' '
kill_p4d
'
echo f2 >"file with spaces" &&
git add "file with spaces" &&
git commit -m "add files" &&
- P4EDITOR=touch git p4 submit
+ P4EDITOR="test-chmtime +5" git p4 submit
) &&
(
cd "$cli" &&
git rm foo\$bar &&
git rm file\ with\ spaces &&
git commit -m "remove files" &&
- P4EDITOR=touch git p4 submit
+ P4EDITOR="test-chmtime +5" git p4 submit
) &&
(
cd "$cli" &&
cd "$git" &&
echo line >>file1 &&
git commit -a -m "change 5" &&
- P4EDITOR="$TRASH_DIRECTORY/ed.sh" &&
+ P4EDITOR="\"$TRASH_DIRECTORY/ed.sh\"" &&
export P4EDITOR &&
git p4 submit &&
p4 changes //depot/... >wc &&
git commit --author "Alice <alice@example.com>" -m "a change by alice" file1 &&
git commit --author "Bob <bob@example.com>" -m "a change by bob" file2 &&
git config git-p4.skipSubmitEditCheck true &&
- P4EDITOR=touch P4USER=alice P4PASSWD=secret git p4 commit --preserve-user &&
+ P4EDITOR="test-chmtime +5" P4USER=alice P4PASSWD=secret &&
+ export P4EDITOR P4USER P4PASSWD &&
+ git p4 commit --preserve-user &&
p4_check_commit_author file1 alice &&
p4_check_commit_author file2 bob
)
git config git-p4.skipSubmitEditCheck true &&
echo "username-noperms: a change by alice" >>file1 &&
git commit --author "Alice <alice@example.com>" -m "perms: a change by alice" file1 &&
- P4EDITOR=touch P4USER=bob P4PASSWD=secret &&
+ P4EDITOR="test-chmtime +5" P4USER=bob P4PASSWD=secret &&
export P4EDITOR P4USER P4PASSWD &&
test_must_fail git p4 commit --preserve-user &&
! git diff --exit-code HEAD..p4/master
git commit --author "Bob <bob@example.com>" -m "preserve: a change by bob" file1 &&
echo "username-unknown: a change by charlie" >>file1 &&
git commit --author "Charlie <charlie@example.com>" -m "preserve: a change by charlie" file1 &&
- P4EDITOR=touch P4USER=alice P4PASSWD=secret &&
+ P4EDITOR="test-chmtime +5" P4USER=alice P4PASSWD=secret &&
export P4EDITOR P4USER P4PASSWD &&
test_must_fail git p4 commit --preserve-user &&
! git diff --exit-code HEAD..p4/master &&
# See if configurables can be set, and in particular if the run.move.allow
# variable exists, which allows admins to disable the "p4 move" command.
-test_expect_success 'p4 configure command and run.move.allow are available' '
- p4 configure show run.move.allow >out ; retval=$? &&
- test $retval = 0 &&
- {
- egrep ^run.move.allow: out &&
- test_set_prereq P4D_HAVE_CONFIGURABLE_RUN_MOVE_ALLOW ||
- true
- } || true
+test_lazy_prereq P4D_HAVE_CONFIGURABLE_RUN_MOVE_ALLOW '
+ p4 configure show run.move.allow >out &&
+ egrep ^run.move.allow: out
'
# If move can be disabled, turn it off and test p4 move handling
)
'
-test_expect_failure 'add with lock not taken' '
+test_expect_success 'add with lock not taken' '
test_when_finished cleanup_git &&
git p4 clone --dest="$git" //depot &&
(
cd "$git" &&
echo line1 >>add-lock-not-taken &&
- git add file2 &&
+ git add add-lock-not-taken &&
git commit -m "add add-lock-not-taken" &&
git config git-p4.skipSubmitEdit true &&
git p4 submit --verbose
)
'
-test_expect_failure 'copy with lock taken' '
+test_expect_success 'copy with lock taken' '
lock_in_another_client &&
test_when_finished cleanup_git &&
test_when_finished "cd \"$cli\" && p4 revert file2 && rm -f file2" &&
git p4 clone --dest="$git" //depot &&
(
cd "$git" &&
- git mv file1 file2 &&
- git commit -m "mv file1 to file2" &&
+ git mv file1 file3 &&
+ git commit -m "mv file1 to file3" &&
git config git-p4.skipSubmitEdit true &&
git config git-p4.detectRenames true &&
git p4 submit --verbose
--- /dev/null
+#!/bin/sh
+
+test_description='git p4 fetching changes in multiple blocks'
+
+. ./lib-git-p4.sh
+
+test_expect_success 'start p4d' '
+ start_p4d
+'
+
+create_restricted_group() {
+ p4 group -i <<-EOF
+ Group: restricted
+ MaxResults: 7
+ MaxScanRows: 40
+ Users: author
+ EOF
+}
+
+test_expect_success 'Create group with limited maxrows' '
+ create_restricted_group
+'
+
+test_expect_success 'Create a repo with many changes' '
+ (
+ client_view "//depot/included/... //client/included/..." \
+ "//depot/excluded/... //client/excluded/..." &&
+ mkdir -p "$cli/included" "$cli/excluded" &&
+ cd "$cli/included" &&
+ >file.txt &&
+ p4 add file.txt &&
+ p4 submit -d "Add file.txt" &&
+ for i in $(test_seq 0 5)
+ do
+ >outer$i.txt &&
+ p4 add outer$i.txt &&
+ p4 submit -d "Adding outer$i.txt" &&
+ for j in $(test_seq 0 5)
+ do
+ p4 edit file.txt &&
+ echo $i$j >file.txt &&
+ p4 submit -d "Commit $i$j" || exit
+ done || exit
+ done
+ )
+'
+
+test_expect_success 'Default user cannot fetch changes' '
+ ! p4 changes -m 1 //depot/...
+'
+
+test_expect_success 'Clone the repo' '
+ git p4 clone --dest="$git" --changes-block-size=7 --verbose //depot/included@all
+'
+
+test_expect_success 'All files are present' '
+ echo file.txt >expected &&
+ test_write_lines outer0.txt outer1.txt outer2.txt outer3.txt outer4.txt >>expected &&
+ test_write_lines outer5.txt >>expected &&
+ ls "$git" >current &&
+ test_cmp expected current
+'
+
+test_expect_success 'file.txt is correct' '
+ echo 55 >expected &&
+ test_cmp expected "$git/file.txt"
+'
+
+test_expect_success 'Correct number of commits' '
+ (cd "$git" && git log --oneline) >log &&
+ wc -l log &&
+ test_line_count = 43 log
+'
+
+test_expect_success 'Previous version of file.txt is correct' '
+ (cd "$git" && git checkout HEAD^^) &&
+ echo 53 >expected &&
+ test_cmp expected "$git/file.txt"
+'
+
+# Test git-p4 sync, with some files outside the client specification.
+
+p4_add_file() {
+ (cd "$cli" &&
+ >$1 &&
+ p4 add $1 &&
+ p4 submit -d "Added a file" $1
+ )
+}
+
+test_expect_success 'Add some more files' '
+ for i in $(test_seq 0 10)
+ do
+ p4_add_file "included/x$i" &&
+ p4_add_file "excluded/x$i"
+ done &&
+ for i in $(test_seq 0 10)
+ do
+ p4_add_file "excluded/y$i"
+ done
+'
+
+# This should pick up the 10 new files in "included", but not be confused
+# by the additional files in "excluded"
+test_expect_success 'Syncing files' '
+ (
+ cd "$git" &&
+ git p4 sync --changes-block-size=7 &&
+ git checkout p4/master &&
+ ls -l x* > log &&
+ test_line_count = 11 log
+ )
+'
+
+test_expect_success 'kill p4d' '
+ kill_p4d
+'
+
+test_done
--- /dev/null
+#!/bin/sh
+
+test_description='interaction with P4 case-folding'
+
+. ./lib-git-p4.sh
+
+test_expect_success 'start p4d with case folding enabled' '
+ start_p4d -C1
+'
+
+test_expect_success 'Create a repo, name is lowercase' '
+ (
+ client_view "//depot/... //client/..." &&
+ cd "$cli" &&
+ mkdir -p lc UC &&
+ >lc/file.txt && >UC/file.txt &&
+ p4 add lc/file.txt UC/file.txt &&
+ p4 submit -d "Add initial lc and UC repos"
+ )
+'
+
+test_expect_success 'Check p4 is in case-folding mode' '
+ (
+ cd "$cli" &&
+ >lc/FILE.TXT &&
+ p4 add lc/FILE.TXT &&
+ test_must_fail p4 submit -d "Cannot add file differing only in case" lc/FILE.TXT
+ )
+'
+
+# Check we created the repo properly
+test_expect_success 'Clone lc repo using lc name' '
+ git p4 clone //depot/lc/... &&
+ test_path_is_file lc/file.txt &&
+ git p4 clone //depot/UC/... &&
+ test_path_is_file UC/file.txt
+'
+
+# The clone should fail, since there is no repo called LC, but because
+# we have case-insensitive p4d enabled, it appears to go ahead and work,
+# but leaves an empty git repo in place.
+test_expect_failure 'Clone lc repo using uc name' '
+ test_must_fail git p4 clone //depot/LC/...
+'
+
+test_expect_failure 'Clone UC repo with lc name' '
+ test_must_fail git p4 clone //depot/uc/...
+'
+
+test_expect_success 'kill p4d' '
+ kill_p4d
+'
+
+test_done
--- /dev/null
+#!/bin/sh
+
+test_description='git p4 handling of EDITOR'
+
+. ./lib-git-p4.sh
+
+test_expect_success 'start p4d' '
+ start_p4d
+'
+
+test_expect_success 'init depot' '
+ (
+ cd "$cli" &&
+ echo file1 >file1 &&
+ p4 add file1 &&
+ p4 submit -d "file1"
+ )
+'
+
+# Check that the P4EDITOR argument can be given command-line
+# options, which git-p4 will then pass through to the shell.
+test_expect_success 'EDITOR with options' '
+ git p4 clone --dest="$git" //depot &&
+ test_when_finished cleanup_git &&
+ (
+ cd "$git" &&
+ echo change >file1 &&
+ git commit -m "change" file1 &&
+ P4EDITOR=": >\"$git/touched\" && test-chmtime +5" git p4 submit &&
+ test_path_is_file "$git/touched"
+ )
+'
+
+test_expect_success 'kill p4d' '
+ kill_p4d
+'
+
+test_done
test_cmp expect actual
'
+test_expect_success '__git_get_config_variables' '
+ cat >expect <<-EOF &&
+ name-1
+ name-2
+ EOF
+ test_config interesting.name-1 good &&
+ test_config interesting.name-2 good &&
+ test_config subsection.interesting.name-3 bad &&
+ __git_get_config_variables interesting >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success '__git_pretty_aliases' '
+ cat >expect <<-EOF &&
+ author
+ hash
+ EOF
+ test_config pretty.author "%an %ae" &&
+ test_config pretty.hash %H &&
+ __git_pretty_aliases >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success '__git_aliases' '
+ cat >expect <<-EOF &&
+ ci
+ co
+ EOF
+ test_config alias.ci commit &&
+ test_config alias.co checkout &&
+ __git_aliases >actual &&
+ test_cmp expect actual
+'
+
test_expect_success 'basic' '
run_completion "git " &&
# built-in
return 1
}
+test_verify_prereq () {
+ test -z "$test_prereq" ||
+ expr >/dev/null "$test_prereq" : '[A-Z0-9_,!]*$' ||
+ error "bug in the test script: '$test_prereq' does not look like a prereq"
+}
+
test_expect_failure () {
test_start_
test "$#" = 3 && { test_prereq=$1; shift; } || test_prereq=
test "$#" = 2 ||
error "bug in the test script: not 2 or 3 parameters to test-expect-failure"
+ test_verify_prereq
export test_prereq
if ! test_skip "$@"
then
test "$#" = 3 && { test_prereq=$1; shift; } || test_prereq=
test "$#" = 2 ||
error "bug in the test script: not 2 or 3 parameters to test-expect-success"
+ test_verify_prereq
export test_prereq
if ! test_skip "$@"
then
error >&5 "bug in the test script: not 3 or 4 parameters to test_external"
descr="$1"
shift
+ test_verify_prereq
export test_prereq
if ! test_skip "$descr" "$@"
then
test_cleanup=:
expecting_failure=$2
- if test "${GIT_TEST_CHAIN_LINT:-0}" != 0; then
+ if test "${GIT_TEST_CHAIN_LINT:-1}" != 0; then
# 117 is magic because it is unlikely to match the exit
# code of other programs
test_eval_ "(exit 117) && $1"
# To enable this hook, rename this file to "applypatch-msg".
. git-sh-setup
-test -x "$GIT_DIR/hooks/commit-msg" &&
- exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"}
+commitmsg="$(git rev-parse --git-path hooks/commit-msg)"
+test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"}
:
# To enable this hook, rename this file to "pre-applypatch".
. git-sh-setup
-test -x "$GIT_DIR/hooks/pre-commit" &&
- exec "$GIT_DIR/hooks/pre-commit" ${1+"$@"}
+precommit="$(git rev-parse --git-path hooks/pre-commit)"
+test -x "$precommit" && exec "$precommit" ${1+"$@"}
:
--- /dev/null
+#include "cache.h"
+#include "dir.h"
+
+static int compare_untracked(const void *a_, const void *b_)
+{
+ const char *const *a = a_;
+ const char *const *b = b_;
+ return strcmp(*a, *b);
+}
+
+static int compare_dir(const void *a_, const void *b_)
+{
+ const struct untracked_cache_dir *const *a = a_;
+ const struct untracked_cache_dir *const *b = b_;
+ return strcmp((*a)->name, (*b)->name);
+}
+
+static void dump(struct untracked_cache_dir *ucd, struct strbuf *base)
+{
+ int i, len;
+ qsort(ucd->untracked, ucd->untracked_nr, sizeof(*ucd->untracked),
+ compare_untracked);
+ qsort(ucd->dirs, ucd->dirs_nr, sizeof(*ucd->dirs),
+ compare_dir);
+ len = base->len;
+ strbuf_addf(base, "%s/", ucd->name);
+ printf("%s %s", base->buf,
+ sha1_to_hex(ucd->exclude_sha1));
+ if (ucd->recurse)
+ fputs(" recurse", stdout);
+ if (ucd->check_only)
+ fputs(" check_only", stdout);
+ if (ucd->valid)
+ fputs(" valid", stdout);
+ printf("\n");
+ for (i = 0; i < ucd->untracked_nr; i++)
+ printf("%s\n", ucd->untracked[i]);
+ for (i = 0; i < ucd->dirs_nr; i++)
+ dump(ucd->dirs[i], base);
+ strbuf_setlen(base, len);
+}
+
+int main(int ac, char **av)
+{
+ struct untracked_cache *uc;
+ struct strbuf base = STRBUF_INIT;
+ setup_git_directory();
+ if (read_cache() < 0)
+ die("unable to read index file");
+ uc = the_index.untracked;
+ if (!uc) {
+ printf("no untracked cache\n");
+ return 0;
+ }
+ printf("info/exclude %s\n", sha1_to_hex(uc->ss_info_exclude.sha1));
+ printf("core.excludesfile %s\n", sha1_to_hex(uc->ss_excludes_file.sha1));
+ printf("exclude_per_dir %s\n", uc->exclude_per_dir);
+ printf("flags %08x\n", uc->dir_flags);
+ if (uc->root)
+ dump(uc->root, &base);
+ return 0;
+}
prefix = "(null)";
trace_printf_key(&key, "setup: git_dir: %s\n", quote_crnl(get_git_dir()));
+ trace_printf_key(&key, "setup: git_common_dir: %s\n", quote_crnl(get_git_common_dir()));
trace_printf_key(&key, "setup: worktree: %s\n", quote_crnl(git_work_tree));
trace_printf_key(&key, "setup: cwd: %s\n", quote_crnl(cwd));
trace_printf_key(&key, "setup: prefix: %s\n", quote_crnl(prefix));
return run_command(&rsync);
}
-static int write_one_ref(const char *name, const unsigned char *sha1,
- int flags, void *data)
+static int write_one_ref(const char *name, const struct object_id *oid,
+ int flags, void *data)
{
struct strbuf *buf = data;
int len = buf->len;
- FILE *f;
/* when called via for_each_ref(), flags is non-zero */
if (flags && !starts_with(name, "refs/heads/") &&
strbuf_addstr(buf, name);
if (safe_create_leading_directories(buf->buf) ||
- !(f = fopen(buf->buf, "w")) ||
- fprintf(f, "%s\n", sha1_to_hex(sha1)) < 0 ||
- fclose(f))
- return error("problems writing temporary file %s", buf->buf);
+ write_file(buf->buf, 0, "%s\n", oid_to_hex(oid)))
+ return error("problems writing temporary file %s: %s",
+ buf->buf, strerror(errno));
strbuf_setlen(buf, len);
return 0;
}
static int write_refs_to_temp_dir(struct strbuf *temp_dir,
- int refspec_nr, const char **refspec)
+ int refspec_nr, const char **refspec)
{
int i;
for (i = 0; i < refspec_nr; i++) {
- unsigned char sha1[20];
+ struct object_id oid;
char *ref;
- if (dwim_ref(refspec[i], strlen(refspec[i]), sha1, &ref) != 1)
+ if (dwim_ref(refspec[i], strlen(refspec[i]), oid.hash, &ref) != 1)
return error("Could not get ref %s", refspec[i]);
- if (write_one_ref(ref, sha1, 0, temp_dir)) {
+ if (write_one_ref(ref, &oid, 0, temp_dir)) {
free(ref);
return -1;
}
{
struct combine_diff_parent *p0 = &p->parent[0];
if (p->mode && p0->mode) {
- opt->change(opt, p0->mode, p->mode, p0->sha1, p->sha1,
+ opt->change(opt, p0->mode, p->mode, p0->oid.hash, p->oid.hash,
1, 1, p->path, 0, 0);
}
else {
if (p->mode) {
addremove = '+';
- sha1 = p->sha1;
+ sha1 = p->oid.hash;
mode = p->mode;
} else {
addremove = '-';
- sha1 = p0->sha1;
+ sha1 = p0->oid.hash;
mode = p0->mode;
}
memcpy(p->path + base->len, path, pathlen);
p->path[len] = 0;
p->mode = mode;
- hashcpy(p->sha1, sha1 ? sha1 : null_sha1);
+ hashcpy(p->oid.hash, sha1 ? sha1 : null_sha1);
return p;
}
}
p->parent[i].mode = mode_i;
- hashcpy(p->parent[i].sha1, sha1_i ? sha1_i : null_sha1);
+ hashcpy(p->parent[i].oid.hash, sha1_i ? sha1_i : null_sha1);
}
keep = 1;
return error;
}
+struct dir_state {
+ void *tree;
+ unsigned long size;
+ unsigned char sha1[20];
+};
+
static int find_tree_entry(struct tree_desc *t, const char *name, unsigned char *result, unsigned *mode)
{
int namelen = strlen(name);
return retval;
}
+/*
+ * This is Linux's built-in max for the number of symlinks to follow.
+ * That limit, of course, does not affect git, but it's a reasonable
+ * choice.
+ */
+#define GET_TREE_ENTRY_FOLLOW_SYMLINKS_MAX_LINKS 40
+
+/**
+ * Find a tree entry by following symlinks in tree_sha (which is
+ * assumed to be the root of the repository). In the event that a
+ * symlink points outside the repository (e.g. a link to /foo or a
+ * root-level link to ../foo), the portion of the link which is
+ * outside the repository will be returned in result_path, and *mode
+ * will be set to 0. It is assumed that result_path is uninitialized.
+ * If there are no symlinks, or the end result of the symlink chain
+ * points to an object inside the repository, result will be filled in
+ * with the sha1 of the found object, and *mode will hold the mode of
+ * the object.
+ *
+ * See the code for enum follow_symlink_result for a description of
+ * the return values.
+ */
+enum follow_symlinks_result get_tree_entry_follow_symlinks(unsigned char *tree_sha1, const char *name, unsigned char *result, struct strbuf *result_path, unsigned *mode)
+{
+ int retval = MISSING_OBJECT;
+ struct dir_state *parents = NULL;
+ size_t parents_alloc = 0;
+ ssize_t parents_nr = 0;
+ unsigned char current_tree_sha1[20];
+ struct strbuf namebuf = STRBUF_INIT;
+ struct tree_desc t;
+ int follows_remaining = GET_TREE_ENTRY_FOLLOW_SYMLINKS_MAX_LINKS;
+ int i;
+
+ init_tree_desc(&t, NULL, 0UL);
+ strbuf_init(result_path, 0);
+ strbuf_addstr(&namebuf, name);
+ hashcpy(current_tree_sha1, tree_sha1);
+
+ while (1) {
+ int find_result;
+ char *first_slash;
+ char *remainder = NULL;
+
+ if (!t.buffer) {
+ void *tree;
+ unsigned char root[20];
+ unsigned long size;
+ tree = read_object_with_reference(current_tree_sha1,
+ tree_type, &size,
+ root);
+ if (!tree)
+ goto done;
+
+ ALLOC_GROW(parents, parents_nr + 1, parents_alloc);
+ parents[parents_nr].tree = tree;
+ parents[parents_nr].size = size;
+ hashcpy(parents[parents_nr].sha1, root);
+ parents_nr++;
+
+ if (namebuf.buf[0] == '\0') {
+ hashcpy(result, root);
+ retval = FOUND;
+ goto done;
+ }
+
+ if (!size)
+ goto done;
+
+ /* descend */
+ init_tree_desc(&t, tree, size);
+ }
+
+ /* Handle symlinks to e.g. a//b by removing leading slashes */
+ while (namebuf.buf[0] == '/') {
+ strbuf_remove(&namebuf, 0, 1);
+ }
+
+ /* Split namebuf into a first component and a remainder */
+ if ((first_slash = strchr(namebuf.buf, '/'))) {
+ *first_slash = 0;
+ remainder = first_slash + 1;
+ }
+
+ if (!strcmp(namebuf.buf, "..")) {
+ struct dir_state *parent;
+ /*
+ * We could end up with .. in the namebuf if it
+ * appears in a symlink.
+ */
+
+ if (parents_nr == 1) {
+ if (remainder)
+ *first_slash = '/';
+ strbuf_add(result_path, namebuf.buf,
+ namebuf.len);
+ *mode = 0;
+ retval = FOUND;
+ goto done;
+ }
+ parent = &parents[parents_nr - 1];
+ free(parent->tree);
+ parents_nr--;
+ parent = &parents[parents_nr - 1];
+ init_tree_desc(&t, parent->tree, parent->size);
+ strbuf_remove(&namebuf, 0, remainder ? 3 : 2);
+ continue;
+ }
+
+ /* We could end up here via a symlink to dir/.. */
+ if (namebuf.buf[0] == '\0') {
+ hashcpy(result, parents[parents_nr - 1].sha1);
+ retval = FOUND;
+ goto done;
+ }
+
+ /* Look up the first (or only) path component in the tree. */
+ find_result = find_tree_entry(&t, namebuf.buf,
+ current_tree_sha1, mode);
+ if (find_result) {
+ goto done;
+ }
+
+ if (S_ISDIR(*mode)) {
+ if (!remainder) {
+ hashcpy(result, current_tree_sha1);
+ retval = FOUND;
+ goto done;
+ }
+ /* Descend the tree */
+ t.buffer = NULL;
+ strbuf_remove(&namebuf, 0,
+ 1 + first_slash - namebuf.buf);
+ } else if (S_ISREG(*mode)) {
+ if (!remainder) {
+ hashcpy(result, current_tree_sha1);
+ retval = FOUND;
+ } else {
+ retval = NOT_DIR;
+ }
+ goto done;
+ } else if (S_ISLNK(*mode)) {
+ /* Follow a symlink */
+ unsigned long link_len;
+ size_t len;
+ char *contents, *contents_start;
+ struct dir_state *parent;
+ enum object_type type;
+
+ if (follows_remaining-- == 0) {
+ /* Too many symlinks followed */
+ retval = SYMLINK_LOOP;
+ goto done;
+ }
+
+ /*
+ * At this point, we have followed at a least
+ * one symlink, so on error we need to report this.
+ */
+ retval = DANGLING_SYMLINK;
+
+ contents = read_sha1_file(current_tree_sha1, &type,
+ &link_len);
+
+ if (!contents)
+ goto done;
+
+ if (contents[0] == '/') {
+ strbuf_addstr(result_path, contents);
+ free(contents);
+ *mode = 0;
+ retval = FOUND;
+ goto done;
+ }
+
+ if (remainder)
+ len = first_slash - namebuf.buf;
+ else
+ len = namebuf.len;
+
+ contents_start = contents;
+
+ parent = &parents[parents_nr - 1];
+ init_tree_desc(&t, parent->tree, parent->size);
+ strbuf_splice(&namebuf, 0, len,
+ contents_start, link_len);
+ if (remainder)
+ namebuf.buf[link_len] = '/';
+ free(contents);
+ }
+ }
+done:
+ for (i = 0; i < parents_nr; i++)
+ free(parents[i].tree);
+ free(parents);
+
+ strbuf_release(&namebuf);
+ return retval;
+}
+
static int match_entry(const struct pathspec_item *item,
const struct name_entry *entry, int pathlen,
const char *match, int matchlen,
typedef int (*traverse_callback_t)(int n, unsigned long mask, unsigned long dirmask, struct name_entry *entry, struct traverse_info *);
int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info);
+enum follow_symlinks_result {
+ FOUND = 0, /* This includes out-of-tree links */
+ MISSING_OBJECT = -1, /* The initial symlink is missing */
+ DANGLING_SYMLINK = -2, /*
+ * The initial symlink is there, but
+ * (transitively) points to a missing
+ * in-tree file
+ */
+ SYMLINK_LOOP = -3,
+ NOT_DIR = -4, /*
+ * Somewhere along the symlink chain, a path is
+ * requested which contains a file as a
+ * non-final element.
+ */
+};
+
+enum follow_symlinks_result get_tree_entry_follow_symlinks(unsigned char *tree_sha1, const char *name, unsigned char *result, struct strbuf *result_path, unsigned *mode);
+
struct traverse_info {
struct traverse_info *prev;
struct name_entry name;
return 0;
}
-int parse_tree(struct tree *item)
+int parse_tree_gently(struct tree *item, int quiet_on_missing)
{
enum object_type type;
void *buffer;
return 0;
buffer = read_sha1_file(item->object.sha1, &type, &size);
if (!buffer)
- return error("Could not read %s",
+ return quiet_on_missing ? -1 :
+ error("Could not read %s",
sha1_to_hex(item->object.sha1));
if (type != OBJ_TREE) {
free(buffer);
int parse_tree_buffer(struct tree *item, void *buffer, unsigned long size);
-int parse_tree(struct tree *tree);
+int parse_tree_gently(struct tree *tree, int quiet_on_missing);
+static inline int parse_tree(struct tree *tree)
+{
+ return parse_tree_gently(tree, 0);
+}
void free_tree_buffer(struct tree *tree);
/* Parses and returns the tree in the given ent, chasing tags and commits. */
#include "refs.h"
#include "attr.h"
#include "split-index.h"
+#include "dir.h"
/*
* Error messages expected by scripts out of plumbing commands such as
static void invalidate_ce_path(const struct cache_entry *ce,
struct unpack_trees_options *o)
{
- if (ce)
- cache_tree_invalidate_path(o->src_index, ce->name);
+ if (!ce)
+ return;
+ cache_tree_invalidate_path(o->src_index, ce->name);
+ untracked_cache_invalidate_path(o->src_index, ce->name);
}
/*
static int no_done;
static int use_thin_pack, use_ofs_delta, use_include_tag;
static int no_progress, daemon_mode;
-static int allow_tip_sha1_in_want;
+/* Allow specifying sha1 if it is a ref tip. */
+#define ALLOW_TIP_SHA1 01
+/* Allow request of a sha1 if it is reachable from a ref (possibly hidden ref). */
+#define ALLOW_REACHABLE_SHA1 02
+static unsigned int allow_unadvertised_object_request;
static int shallow_nr;
static struct object_array have_obj;
static struct object_array want_obj;
{
FILE *fp = cb_data;
if (graft->nr_parent == -1)
- fprintf(fp, "--shallow %s\n", sha1_to_hex(graft->sha1));
+ fprintf(fp, "--shallow %s\n", oid_to_hex(&graft->oid));
return 0;
}
static int is_our_ref(struct object *o)
{
- return o->flags &
- ((allow_tip_sha1_in_want ? HIDDEN_REF : 0) | OUR_REF);
+ int allow_hidden_ref = (allow_unadvertised_object_request &
+ (ALLOW_TIP_SHA1 | ALLOW_REACHABLE_SHA1));
+ return o->flags & ((allow_hidden_ref ? HIDDEN_REF : 0) | OUR_REF);
}
static void check_non_tip(void)
char namebuf[42]; /* ^ + SHA-1 + LF */
int i;
- /* In the normal in-process case non-tip request can never happen */
- if (!stateless_rpc)
+ /*
+ * In the normal in-process case without
+ * uploadpack.allowReachableSHA1InWant,
+ * non-tip requests can never happen.
+ */
+ if (!stateless_rpc && !(allow_unadvertised_object_request & ALLOW_REACHABLE_SHA1))
goto error;
cmd.argv = argv;
}
/* return non-zero if the ref is hidden, otherwise 0 */
-static int mark_our_ref(const char *refname, const unsigned char *sha1)
+static int mark_our_ref(const char *refname, const struct object_id *oid)
{
- struct object *o = lookup_unknown_object(sha1);
+ struct object *o = lookup_unknown_object(oid->hash);
if (ref_is_hidden(refname)) {
o->flags |= HIDDEN_REF;
return 0;
}
-static int check_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+static int check_ref(const char *refname, const struct object_id *oid,
+ int flag, void *cb_data)
{
- mark_our_ref(refname, sha1);
+ mark_our_ref(refname, oid);
return 0;
}
strbuf_addf(buf, " symref=%s:%s", item->string, (char *)item->util);
}
-static int send_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+static int send_ref(const char *refname, const struct object_id *oid,
+ int flag, void *cb_data)
{
static const char *capabilities = "multi_ack thin-pack side-band"
" side-band-64k ofs-delta shallow no-progress"
" include-tag multi_ack_detailed";
const char *refname_nons = strip_namespace(refname);
- unsigned char peeled[20];
+ struct object_id peeled;
- if (mark_our_ref(refname, sha1))
+ if (mark_our_ref(refname, oid))
return 0;
if (capabilities) {
struct strbuf symref_info = STRBUF_INIT;
format_symref_info(&symref_info, cb_data);
- packet_write(1, "%s %s%c%s%s%s%s agent=%s\n",
- sha1_to_hex(sha1), refname_nons,
+ packet_write(1, "%s %s%c%s%s%s%s%s agent=%s\n",
+ oid_to_hex(oid), refname_nons,
0, capabilities,
- allow_tip_sha1_in_want ? " allow-tip-sha1-in-want" : "",
+ (allow_unadvertised_object_request & ALLOW_TIP_SHA1) ?
+ " allow-tip-sha1-in-want" : "",
+ (allow_unadvertised_object_request & ALLOW_REACHABLE_SHA1) ?
+ " allow-reachable-sha1-in-want" : "",
stateless_rpc ? " no-done" : "",
symref_info.buf,
git_user_agent_sanitized());
strbuf_release(&symref_info);
} else {
- packet_write(1, "%s %s\n", sha1_to_hex(sha1), refname_nons);
+ packet_write(1, "%s %s\n", oid_to_hex(oid), refname_nons);
}
capabilities = NULL;
- if (!peel_ref(refname, peeled))
- packet_write(1, "%s %s^{}\n", sha1_to_hex(peeled), refname_nons);
+ if (!peel_ref(refname, peeled.hash))
+ packet_write(1, "%s %s^{}\n", oid_to_hex(&peeled), refname_nons);
return 0;
}
-static int find_symref(const char *refname, const unsigned char *sha1, int flag,
- void *cb_data)
+static int find_symref(const char *refname, const struct object_id *oid,
+ int flag, void *cb_data)
{
const char *symref_target;
struct string_list_item *item;
- unsigned char unused[20];
+ struct object_id unused;
if ((flag & REF_ISSYMREF) == 0)
return 0;
- symref_target = resolve_ref_unsafe(refname, 0, unused, &flag);
+ symref_target = resolve_ref_unsafe(refname, 0, unused.hash, &flag);
if (!symref_target || (flag & REF_ISSYMREF) == 0)
die("'%s' is a symref but it is not?", refname);
item = string_list_append(cb_data, refname);
static int upload_pack_config(const char *var, const char *value, void *unused)
{
- if (!strcmp("uploadpack.allowtipsha1inwant", var))
- allow_tip_sha1_in_want = git_config_bool(var, value);
- else if (!strcmp("uploadpack.keepalive", var)) {
+ if (!strcmp("uploadpack.allowtipsha1inwant", var)) {
+ if (git_config_bool(var, value))
+ allow_unadvertised_object_request |= ALLOW_TIP_SHA1;
+ else
+ allow_unadvertised_object_request &= ~ALLOW_TIP_SHA1;
+ } else if (!strcmp("uploadpack.allowreachablesha1inwant", var)) {
+ if (git_config_bool(var, value))
+ allow_unadvertised_object_request |= ALLOW_REACHABLE_SHA1;
+ else
+ allow_unadvertised_object_request &= ~ALLOW_REACHABLE_SHA1;
+ } else if (!strcmp("uploadpack.keepalive", var)) {
keepalive = git_config_int(var, value);
if (!keepalive)
keepalive = -1;
const char *in_encoding,
int *outsz);
#else
-#define reencode_string_len(a,b,c,d,e) NULL
+static inline char *reencode_string_len(const char *a, int b,
+ const char *c, const char *d, int *e)
+{ if (e) *e = 0; return NULL; }
#endif
static inline char *reencode_string(const char *in,
return -1;
}
-static int mark_complete(const char *path, const unsigned char *sha1, int flag, void *cb_data)
+static int mark_complete(const char *path, const struct object_id *oid,
+ int flag, void *cb_data)
{
- struct commit *commit = lookup_commit_reference_gently(sha1, 1);
+ struct commit *commit = lookup_commit_reference_gently(oid->hash, 1);
+
if (commit) {
commit->object.flags |= COMPLETE;
commit_list_insert(commit, &complete);
die_errno(_("unable to get current working directory"));
return strbuf_detach(&sb, NULL);
}
+
+int write_file(const char *path, int fatal, const char *fmt, ...)
+{
+ struct strbuf sb = STRBUF_INIT;
+ va_list params;
+ int fd = open(path, O_RDWR | O_CREAT | O_TRUNC, 0666);
+ if (fd < 0) {
+ if (fatal)
+ die_errno(_("could not open %s for writing"), path);
+ return -1;
+ }
+ va_start(params, fmt);
+ strbuf_vaddf(&sb, fmt, params);
+ va_end(params);
+ if (write_in_full(fd, sb.buf, sb.len) != sb.len) {
+ int err = errno;
+ close(fd);
+ strbuf_release(&sb);
+ errno = err;
+ if (fatal)
+ die_errno(_("could not write to %s"), path);
+ return -1;
+ }
+ strbuf_release(&sb);
+ if (close(fd)) {
+ if (fatal)
+ die_errno(_("could not close %s"), path);
+ return -1;
+ }
+ return 0;
+}
+
+void sleep_millisec(int millisec)
+{
+ poll(NULL, 0, millisec);
+}
DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES;
if (s->show_ignored_files)
dir.flags |= DIR_SHOW_IGNORED_TOO;
+ else
+ dir.untracked = the_index.untracked;
setup_standard_excludes(&dir);
fill_directory(&dir, &s->pathspec);
const char *p;
struct strbuf pattern = STRBUF_INIT;
- strbuf_addf(&pattern, "%c %s", comment_line_char, cut_line);
- p = strstr(buf->buf, pattern.buf);
- if (p && (p == buf->buf || p[-1] == '\n'))
- strbuf_setlen(buf, p - buf->buf);
+ strbuf_addf(&pattern, "\n%c %s", comment_line_char, cut_line);
+ if (starts_with(buf->buf, pattern.buf + 1))
+ strbuf_setlen(buf, 0);
+ else if ((p = strstr(buf->buf, pattern.buf)))
+ strbuf_setlen(buf, p - buf->buf + 1);
strbuf_release(&pattern);
}
color_fprintf(s->fp, branch_color_local, "%s", branch_name);
- switch (stat_tracking_info(branch, &num_ours, &num_theirs)) {
- case 0:
- /* no base */
- fputc(s->null_termination ? '\0' : '\n', s->fp);
- return;
- case -1:
- /* with "gone" base */
+ if (stat_tracking_info(branch, &num_ours, &num_theirs, &base) < 0) {
+ if (!base) {
+ fputc(s->null_termination ? '\0' : '\n', s->fp);
+ return;
+ }
+
upstream_is_gone = 1;
- break;
- default:
- /* with base */
- break;
}
- base = branch->merge[0]->dst;
base = shorten_unambiguous_ref(base, 0);
color_fprintf(s->fp, header_color, "...");
color_fprintf(s->fp, branch_color_remote, "%s", base);