/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.2 Release Notes
+========================
+
+Fixes since v2.4.1
+------------------
+
+ * "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.
+
+ * "hash-object --literally" introduced in v2.2 was not prepared to
+ take a really long object type name.
+
+ * "git rebase --quiet" was not quite quiet when there is nothing to
+ do.
+
+ * The completion for "log --decorate=" parameter value was incorrect.
+
+ * "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".
+
+ * "git daemon" fails to build from the source under NO_IPV6
+ configuration (regression in 2.4).
+
+ * "git stash pop/apply" forgot to make sure that not just the working
+ tree is clean but also the index is clean. The latter is important
+ as a stash application can conflict and the index will be used for
+ conflict resolution.
+
+ * 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).
+
+Also contains typofixes, documentation updates and trivial code
+clean-ups.
--- /dev/null
+Git v2.4.3 Release Notes
+========================
+
+Fixes since v2.4.3
+------------------
+
+ * Error messages from "git branch" called remote-tracking branches as
+ "remote branches".
+
+ * "git rerere forget" in a repository without rerere enabled gave a
+ cryptic error message; it should be a silent no-op instead.
+
+ * "git pull --log" and "git pull --no-log" worked as expected, but
+ "git pull --log=20" did not.
+
+ * The pull.ff configuration was supposed to override the merge.ff
+ configuration, but it didn't.
+
+ * 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.
+
+ * Various documentation mark-up fixes to make the output more
+ consistent in general and also make AsciiDoctor (an alternative
+ formatter) happier.
+
+ * "git bundle verify" did not diagnose extra parameters on the
+ command line.
+
+ * Multi-ref transaction support we merged a few releases ago
+ unnecessarily kept many file descriptors open, risking to fail with
+ resource exhaustion.
+
+ * 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.
+
+ * 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.
+
+ * 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.
+
+ * 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.
+
+ * 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").
+
+ * "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.
+
+ * "git add -e" did not allow the user to abort the operation by
+ killing the editor.
+
+ * Git 2.4 broke setting verbosity and progress levels on "git clone"
+ with native transports.
+
+ * 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 portabile across
+ platforms while terminating lines in their working tree files with
+ CRLF for their platform.
+
+ * Code clean-up for xdg configuration path support.
+
+Also contains typofixes, documentation updates and trivial code
+clean-ups.
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.
chunks from Perforce, instead of making one call to "p4 changes"
that may trigger "too many rows scanned" error from Perforce.
+ * 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.
* The Git subcommand completion (in contrib/) listed credential
helpers among candidates, which is not something the end user would
- invoke interatively.
+ 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.
Performance, Internal Implementation, Development Support etc.
- * "unsigned char [20]" used thoughout the code to represent object
+ * "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
the semantics of the option changed back in Git 1.9 days.
(merge 19d122b pt/pull-tags-error-diag later to maint).
+ * for_each_ref() callback functions were taught to name the objects
+ not with "unsigned char sha1[20]" but with "struct object_id".
+
+ * Error reporting mechanism used in "refs" API has been made more
+ consistent.
+
+ * "git pull" has more test coverage now.
+
Also contains various documentation updates and code clean-ups.
* 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 portabile across
- platforms while terminating lines in their working tree files with
- CRLF for their platform.
+ 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
(merge 27547e5 cn/bom-in-gitignore later to maint).
* a few helper scripts in the test suite did not report errors
- correcty.
+ correctly.
(merge de248e9 ep/fix-test-lib-functions-report later to maint).
* The default $HOME/.gitconfig file created upon "git config --global"
(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).
+
* 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 f86a374 sb/test-bitmap-free-at-end 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 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).
}
}
+while (<>) {
+ last if /^### command list/;
+}
+
my %cmds = ();
for (sort <>) {
next if /^#/;
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).
a case (equivalent to giving the `--no-ff` option from the command
line). When set to `only`, only such fast-forward merges are
allowed (equivalent to giving the `--ff-only` option from the
- command line).
+ command line). This setting overrides `merge.ff` when pulling.
pull.rebase::
When true, rebase branches on top of the fetched branch, instead
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
ifndef::git-format-patch[]
--raw::
- Generate the raw format.
+ifndef::git-log[]
+ Generate the diff in raw format.
ifdef::git-diff-core[]
This is the default.
endif::git-diff-core[]
+endif::git-log[]
+ifdef::git-log[]
+ For each commit, show a summary of changes using the raw diff
+ format. See the "RAW OUTPUT FORMAT" section of
+ linkgit:git-diff[1]. This is different from showing the log
+ itself in raw format, which you can achieve with
+ `--format=raw`.
+endif::git-log[]
endif::git-format-patch[]
ifndef::git-format-patch[]
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::
--------
[verse]
'git cat-file' (-t [--allow-unknown-type]| -s [--allow-unknown-type]| -e | -p | <type> | --textconv ) <object>
-'git cat-file' (--batch | --batch-check) < <list-of-objects>
+'git cat-file' (--batch | --batch-check) [--follow-symlinks] < <list-of-objects>
DESCRIPTION
-----------
--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
-------
--reset-author::
When used with -C/-c/--amend options, or when committing after a
a conflicting cherry-pick, declare that the authorship of the
- resulting commit now belongs of the committer. This also renews
+ resulting commit now belongs to the committer. This also renews
the author timestamp.
--short::
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.
'git-http-backend' to bypass the check for the "git-daemon-export-ok"
file in each repository before allowing export of that repository.
+The `GIT_HTTP_MAX_REQUEST_BUFFER` environment variable (or the
+`http.maxRequestBuffer` config variable) may be set to change the
+largest ref negotiation request that git will handle during a fetch; any
+fetch requiring a larger buffer will not succeed. This value should not
+normally need to be changed, but may be helpful if you are fetching from
+a repository with an extremely large number of refs. The value can be
+specified with a unit (e.g., `100M` for 100 megabytes). The default is
+10 megabytes.
+
The backend process sets GIT_COMMITTER_NAME to '$REMOTE_USER' and
GIT_COMMITTER_EMAIL to '$\{REMOTE_USER}@http.$\{REMOTE_ADDR\}',
ensuring that any reflogs created by 'git-receive-pack' contain some
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
DESCRIPTION
-----------
-Submodules allow foreign repositories to be embedded within
-a dedicated subdirectory of the source tree, always pointed
-at a particular commit.
+Inspects, updates and manages submodules.
-They are not to be confused with remotes, which are meant mainly
-for branches of the same project; submodules are meant for
-different projects you would like to make part of your source tree,
-while the history of the two projects still stays completely
-independent and you cannot modify the contents of the submodule
-from within the main project.
-If you want to merge the project histories and want to treat the
-aggregated whole as a single project from then on, you may want to
-add a remote for the other project and use the 'subtree' merge strategy,
-instead of treating the other project as a submodule. Directories
-that come from both projects can be cloned and checked out as a whole
-if you choose to go that route.
+A submodule allows you to keep another Git repository in a subdirectory
+of your repository. The other repository has its own history, which does not
+interfere with the history of the current repository. This can be used to
+have external dependencies such as third party libraries for example.
+
+When cloning or pulling a repository containing submodules however,
+these will not be checked out by default; the 'init' and 'update'
+subcommands will maintain submodules checked out and at
+appropriate revision in your working tree.
Submodules are composed from a so-called `gitlink` tree entry
in the main repository that refers to a particular commit object
The logical name can be used for overriding this URL within your
local repository configuration (see 'submodule init').
-This command will manage the tree entries and contents of the
-gitmodules file for you, as well as inspect the status of your
-submodules and update them.
-When adding a new submodule to the tree, the 'add' subcommand
-is to be used. However, when pulling a tree containing submodules,
-these will not be checked out by default;
-the 'init' and 'update' subcommands will maintain submodules
-checked out and at appropriate revision in your working tree.
-You can briefly inspect the up-to-date status of your submodules
-using the 'status' subcommand and get a detailed overview of the
-difference between the index and checkouts using the 'summary'
-subcommand.
-
+Submodules are not to be confused with remotes, which are other
+repositories of the same project; submodules are meant for
+different projects you would like to make part of your source tree,
+while the history of the two projects still stays completely
+independent and you cannot modify the contents of the submodule
+from within the main project.
+If you want to merge the project histories and want to treat the
+aggregated whole as a single project from then on, you may want to
+add a remote for the other project and use the 'subtree' merge strategy,
+instead of treating the other project as a submodule. Directories
+that come from both projects can be cloned and checked out as a whole
+if you choose to go that route.
COMMANDS
--------
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.1/git.html[documentation for release 2.4.1]
+* link:v2.4.3/git.html[documentation for release 2.4.3]
* release notes for
+ link:RelNotes/2.4.3.txt[2.4.3],
+ link:RelNotes/2.4.2.txt[2.4.2],
link:RelNotes/2.4.1.txt[2.4.1],
link:RelNotes/2.4.0.txt[2.4].
<<def_push,push>> to describe the mapping between remote
<<def_ref,ref>> and local ref.
+[[def_remote]]remote repository::
+ A <<def_repository,repository>> which is used to track the same
+ project but resides somewhere else. To communicate with remotes,
+ see <<def_fetch,fetch>> or <<def_push,push>>.
+
[[def_remote_tracking_branch]]remote-tracking branch::
A <<def_ref,ref>> that is used to follow changes from another
<<def_repository,repository>>. It typically looks like
is created by giving the `--depth` option to linkgit:git-clone[1], and
its history can be later deepened with linkgit:git-fetch[1].
+[[def_submodule]]submodule::
+ A <<def_repository,repository>> that holds the history of a
+ separate project inside another repository (the latter of
+ which is called <<def_superproject, superproject>>).
+
+[[def_superproject]]superproject::
+ A <<def_repository,repository>> that references repositories
+ of other projects in its working tree as <<def_submodule,submodules>>.
+ The superproject knows about the names of (but does not hold
+ copies of) commit objects of the contained submodules.
+
[[def_symref]]symref::
Symbolic reference: instead of containing the <<def_SHA1,SHA-1>>
id itself, it is of the format 'ref: refs/some/thing' and when
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
displayed in full, regardless of whether --abbrev or
--no-abbrev are used, and 'parents' information show the
true parent commits, without taking grafts or history
-simplification into account.
+simplification into account. Note that this format affects the way
+commits are displayed, but not the way the diff is shown e.g. with
+`git log --raw`. To get full object names in a raw diff format,
+use `--no-abbrev`.
* 'format:<string>'
+
`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.
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>
-----------------
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
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):\
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 | \
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_oid = xmalloc(sizeof(*current_bad_oid));
- hashcpy(current_bad_oid->hash, sha1);
+ 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;
#include "userdiff.h"
#include "line-range.h"
#include "line-log.h"
+#include "dir.h"
-static char blame_usage[] = N_("git blame [<options>] [<rev-opts>] [<rev>] [--] file");
+static char blame_usage[] = N_("git blame [<options>] [<rev-opts>] [<rev>] [--] <file>");
static const char *blame_opt_usage[] = {
blame_usage,
}
}
-/*
- * Used for the command line parsing; check if the path exists
- * in the working tree.
- */
-static int has_string_in_work_tree(const char *path)
-{
- struct stat st;
- return !lstat(path, &st);
-}
-
static unsigned parse_score(const char *arg)
{
char *end;
if (argc < 2)
usage_with_options(blame_opt_usage, options);
path = add_prefix(prefix, argv[argc - 1]);
- if (argc == 3 && !has_string_in_work_tree(path)) { /* (2b) */
+ if (argc == 3 && !file_exists(path)) { /* (2b) */
path = add_prefix(prefix, argv[1]);
argv[1] = argv[2];
}
argv[argc - 1] = "--";
setup_work_tree();
- if (!has_string_in_work_tree(path))
+ if (!file_exists(path))
die_errno("cannot stat path '%s'", path);
}
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),
#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,
int unknown_type)
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 [--allow-unknown-type]|-s [--allow-unknown-type]|-e|-p|<type>|--textconv) <object>"),
- N_("git cat-file (--batch | --batch-check) < <list-of-objects>"),
+ 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;
{ 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()
};
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);
}
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;
}
{
struct string_list menu_list = STRING_LIST_INIT_DUP;
struct strbuf menu = STRBUF_INIT;
- struct strbuf buf = STRBUF_INIT;
struct menu_item *menu_item;
struct string_list_item *string_list_item;
int i;
pretty_print_menus(&menu_list);
strbuf_release(&menu);
- strbuf_release(&buf);
string_list_clear(&menu_list, 0);
}
if (!cache_name_is_other(ent->name, ent->len))
continue;
- if (lstat(ent->name, &st))
- die_errno("Cannot lstat '%s'", ent->name);
-
if (pathspec.nr)
matches = dir_path_match(ent, &pathspec, 0, NULL);
if (pathspec.nr && !matches)
continue;
+ if (lstat(ent->name, &st))
+ die_errno("Cannot lstat '%s'", ent->name);
+
if (S_ISDIR(st.st_mode) && !remove_directories &&
matches != MATCHED_EXACTLY)
continue;
static struct string_list option_reference;
static int option_dissociate;
-static int opt_parse_reference(const struct option *opt, const char *arg, int unset)
-{
- struct string_list *option_reference = opt->value;
- if (!arg)
- return -1;
- string_list_append(option_reference, arg);
- return 0;
-}
-
static struct option builtin_clone_options[] = {
OPT__VERBOSITY(&option_verbosity),
OPT_BOOL(0, "progress", &option_progress,
N_("initialize submodules in the clone")),
OPT_STRING(0, "template", &option_template, N_("template-directory"),
N_("directory from which templates will be used")),
- OPT_CALLBACK(0 , "reference", &option_reference, N_("repo"),
- N_("reference repository"), &opt_parse_reference),
+ OPT_STRING_LIST(0, "reference", &option_reference, N_("repo"),
+ N_("reference repository")),
+ OPT_BOOL(0, "dissociate", &option_dissociate,
+ N_("use --reference only while cloning")),
OPT_STRING('o', "origin", &option_origin, N_("name"),
N_("use <name> instead of 'origin' to track upstream")),
OPT_STRING('b', "branch", &option_branch, N_("branch"),
N_("create a shallow clone of that depth")),
OPT_BOOL(0, "single-branch", &option_single_branch,
N_("clone only one branch, HEAD or --branch")),
- OPT_BOOL(0, "dissociate", &option_dissociate,
- N_("use --reference only while cloning")),
OPT_STRING(0, "separate-git-dir", &real_git_dir, N_("gitdir"),
N_("separate git dir from working tree")),
OPT_STRING_LIST('c', "config", &option_config, N_("key=value"),
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;
}
}
-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 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;
*/
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;
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);
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 const char *fmt_pretty;
static const char * const builtin_log_usage[] = {
- N_("git log [<options>] [<revision range>] [[--] <path>...]"),
+ N_("git log [<options>] [<revision-range>] [[--] <path>...]"),
N_("git show [<options>] <object>..."),
NULL
};
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 (!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."));
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;
}
}
}
-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);
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
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;
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;
}
const char *name = list.entry[i].name;
int pos;
const struct cache_entry *ce;
- struct stat st;
pos = cache_name_pos(name, strlen(name));
if (pos < 0) {
ce = active_cache[pos];
if (!S_ISGITLINK(ce->ce_mode) ||
- (lstat(ce->name, &st) < 0) ||
+ !file_exists(ce->name) ||
is_empty_dir(name))
continue;
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;
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);
}
}
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);
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()
};
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) {
#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;
* 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);
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);
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);
-# List of known git commands.
+# 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 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-fetch mainporcelain remote
git-fetch-pack synchingrepositories
git-filter-branch ancillarymanipulators
git-fmt-merge-msg purehelpers
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-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
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 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);
/*
/* 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
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
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:
_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"
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"))
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,
#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);
/*
* Make sure all pathspec matched; otherwise it is an error.
*/
- struct strbuf sb = STRBUF_INIT;
int num, errors = 0;
for (num = 0; num < pathspec->nr; num++) {
int other, found_dup;
pathspec->items[num].original);
errors++;
}
- strbuf_release(&sb);
return errors;
}
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
*/
#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)
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);
#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)
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 "};"
#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);
: ${MERGE_TOOLS_DIR=$(git --exec-path)/mergetools}
+IFS='
+'
+
mode_ok () {
if diff_mode
then
printf "Merging:\n"
printf "%s\n" "$files"
-IFS='
-'
rc=0
for i in $files
do
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.
# 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 ]
fi
# Setup default fast-forward options via `pull.ff`
-pull_ff=$(git config pull.ff)
+pull_ff=$(bool_or_string_config pull.ff)
case "$pull_ff" in
+true)
+ no_ff=--ff
+ ;;
false)
no_ff=--no-ff
;;
diffstat=--no-stat ;;
--stat|--summary)
diffstat=--stat ;;
- --log|--no-log)
- log_arg=$1 ;;
+ --log|--log=*|--no-log)
+ log_arg="$1" ;;
--no-c|--no-co|--no-com|--no-comm|--no-commi|--no-commit)
no_commit=--no-commit ;;
--c|--co|--com|--comm|--commi|--commit)
}
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.
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
-a|--all)
untracked=all
;;
+ --help)
+ show_help
+ ;;
--)
shift
break
}
show_stash () {
+ ALLOW_UNKNOWN_FLAGS=t
assert_stash_like "$@"
git diff ${FLAGS:---stat} $b_commit $w_commit
}
+show_help () {
+ exec git help stash
+ exit 1
+}
+
#
# Parses the remaining options looking for flags and
# at most one revision defaulting to ${ref_stash}@{0}
#
# GIT_QUIET is set to t if -q is specified
# INDEX_OPTION is set to --index if --index is specified.
-# FLAGS is set to the remaining flags
+# FLAGS is set to the remaining flags (if allowed)
#
# dies if:
# * too many revisions specified
# * no revision is specified and there is no stash stack
# * a revision is specified which cannot be resolve to a SHA1
# * a non-existent stash reference is specified
+# * unknown flags were set and ALLOW_UNKNOWN_FLAGS is not "t"
#
parse_flags_and_rev()
--index)
INDEX_OPTION=--index
;;
+ --help)
+ show_help
+ ;;
-*)
+ test "$ALLOW_UNKNOWN_FLAGS" = t ||
+ die "$(eval_gettext "unknown option: \$opt")"
FLAGS="${FLAGS}${FLAGS:+ }$opt"
;;
esac
}
}
+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));
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);
static const char content_length[] = "Content-Length";
static const char last_modified[] = "Last-Modified";
static int getanyfile = 1;
+static unsigned long max_request_buffer = 10 * 1024 * 1024;
static struct string_list *query_params;
struct rpc_service {
const char *name;
const char *config_name;
+ unsigned buffer_input : 1;
signed enabled : 2;
};
static struct rpc_service rpc_service[] = {
- { "upload-pack", "uploadpack", 1 },
- { "receive-pack", "receivepack", -1 },
+ { "upload-pack", "uploadpack", 1, 1 },
+ { "receive-pack", "receivepack", 0, -1 },
};
static struct string_list *get_parameters(void)
struct strbuf var = STRBUF_INIT;
git_config_get_bool("http.getanyfile", &getanyfile);
+ git_config_get_ulong("http.maxrequestbuffer", &max_request_buffer);
for (i = 0; i < ARRAY_SIZE(rpc_service); i++) {
struct rpc_service *svc = &rpc_service[i];
return svc;
}
-static void inflate_request(const char *prog_name, int out)
+/*
+ * This is basically strbuf_read(), except that if we
+ * hit max_request_buffer we die (we'd rather reject a
+ * maliciously large request than chew up infinite memory).
+ */
+static ssize_t read_request(int fd, unsigned char **out)
+{
+ size_t len = 0, alloc = 8192;
+ unsigned char *buf = xmalloc(alloc);
+
+ if (max_request_buffer < alloc)
+ max_request_buffer = alloc;
+
+ while (1) {
+ ssize_t cnt;
+
+ cnt = read_in_full(fd, buf + len, alloc - len);
+ if (cnt < 0) {
+ free(buf);
+ return -1;
+ }
+
+ /* partial read from read_in_full means we hit EOF */
+ len += cnt;
+ if (len < alloc) {
+ *out = buf;
+ return len;
+ }
+
+ /* otherwise, grow and try again (if we can) */
+ if (alloc == max_request_buffer)
+ die("request was larger than our maximum size (%lu);"
+ " try setting GIT_HTTP_MAX_REQUEST_BUFFER",
+ max_request_buffer);
+
+ alloc = alloc_nr(alloc);
+ if (alloc > max_request_buffer)
+ alloc = max_request_buffer;
+ REALLOC_ARRAY(buf, alloc);
+ }
+}
+
+static void inflate_request(const char *prog_name, int out, int buffer_input)
{
git_zstream stream;
+ unsigned char *full_request = NULL;
unsigned char in_buf[8192];
unsigned char out_buf[8192];
unsigned long cnt = 0;
git_inflate_init_gzip_only(&stream);
while (1) {
- ssize_t n = xread(0, in_buf, sizeof(in_buf));
+ ssize_t n;
+
+ if (buffer_input) {
+ if (full_request)
+ n = 0; /* nothing left to read */
+ else
+ n = read_request(0, &full_request);
+ stream.next_in = full_request;
+ } else {
+ n = xread(0, in_buf, sizeof(in_buf));
+ stream.next_in = in_buf;
+ }
+
if (n <= 0)
die("request ended in the middle of the gzip stream");
-
- stream.next_in = in_buf;
stream.avail_in = n;
while (0 < stream.avail_in) {
done:
git_inflate_end(&stream);
close(out);
+ free(full_request);
+}
+
+static void copy_request(const char *prog_name, int out)
+{
+ unsigned char *buf;
+ ssize_t n = read_request(0, &buf);
+ if (n < 0)
+ die_errno("error reading request body");
+ if (write_in_full(out, buf, n) != n)
+ die("%s aborted reading request", prog_name);
+ close(out);
+ free(buf);
}
-static void run_service(const char **argv)
+static void run_service(const char **argv, int buffer_input)
{
const char *encoding = getenv("HTTP_CONTENT_ENCODING");
const char *user = getenv("REMOTE_USER");
"GIT_COMMITTER_EMAIL=%s@http.%s", user, host);
cld.argv = argv;
- if (gzipped_request)
+ if (buffer_input || gzipped_request)
cld.in = -1;
cld.git_cmd = 1;
if (start_command(&cld))
close(1);
if (gzipped_request)
- inflate_request(argv[0], cld.in);
+ inflate_request(argv[0], cld.in, buffer_input);
+ else if (buffer_input)
+ copy_request(argv[0], cld.in);
else
close(0);
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)
packet_flush(1);
argv[0] = svc->name;
- run_service(argv);
+ run_service(argv, 0);
} else {
select_getanyfile();
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;
end_headers();
argv[0] = svc->name;
- run_service(argv);
+ run_service(argv, svc->buffer_input);
strbuf_release(&buf);
}
+static int dead;
static NORETURN void die_webcgi(const char *err, va_list params)
{
- static int dead;
+ if (dead <= 1) {
+ vreportf("fatal: ", err, params);
- if (!dead) {
- dead = 1;
http_status(500, "Internal Server Error");
hdr_nocache();
end_headers();
-
- vreportf("fatal: ", err, params);
}
exit(0); /* we successfully reported a failure ;-) */
}
+static int die_webcgi_recursing(void)
+{
+ return dead++ > 1;
+}
+
static char* getdir(void)
{
struct strbuf buf = STRBUF_INIT;
git_extract_argv0_path(argv[0]);
set_die_routine(die_webcgi);
+ set_die_is_recursing_routine(die_webcgi_recursing);
if (!method)
die("No REQUEST_METHOD from server");
not_found("Repository not exported: '%s'", dir);
http_config();
+ max_request_buffer = git_env_ulong("GIT_HTTP_MAX_REQUEST_BUFFER",
+ max_request_buffer);
+
cmd->imp(cmd_arg);
return 0;
}
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));
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;
void load_ref_decorations(int flags)
{
if (!decoration_loaded) {
+
decoration_loaded = 1;
decoration_flags = flags;
for_each_ref(add_ref_decoration, NULL);
{
struct strbuf newpath = STRBUF_INIT;
int suffix = 0;
- struct stat st;
size_t base_len;
strbuf_addf(&newpath, "%s~", path);
base_len = newpath.len;
while (string_list_has_string(&o->current_file_set, newpath.buf) ||
string_list_has_string(&o->current_directory_set, newpath.buf) ||
- lstat(newpath.buf, &st) == 0) {
+ file_exists(newpath.buf)) {
strbuf_setlen(&newpath, base_len);
strbuf_addf(&newpath, "_%d", suffix++);
}
--- /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
+}
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;
return buffer[(*pos)++];
}
+#define MAX_XOR_OFFSET 160
+
static int load_bitmap_entries_v1(struct bitmap_index *index)
{
- static const size_t MAX_XOR_OFFSET = 160;
-
uint32_t i;
- struct stored_bitmap **recent_bitmaps;
-
- recent_bitmaps = xcalloc(MAX_XOR_OFFSET, sizeof(struct stored_bitmap));
+ struct stored_bitmap *recent_bitmaps[MAX_XOR_OFFSET] = { NULL };
for (i = 0; i < index->entry_count; ++i) {
int xor_offset, flags;
else
fprintf(stderr, "Mismatch!\n");
- free(result);
+ bitmap_free(result);
}
static int rebuild_bitmap(uint32_t *reposition,
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;
+ 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];
+ 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;
die("Reference has invalid format: '%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;
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*
if (ref == NULL)
return -1;
- hashcpy(sha1, ref->u.value.sha1);
+ hashcpy(sha1, ref->u.value.oid.hash);
return 0;
}
*/
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;
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)
}
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;
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;
}
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;
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)) {
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;
* 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
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))
return error("Could not read index");
fd = setup_rerere(&merge_rr, RERERE_NOAUTOUPDATE);
+ if (fd < 0)
+ return 0;
unmerge_cache(pathspec);
find_conflict(&conflict);
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);
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) {
(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) {
int ret;
if (!size) {
- ret = index_mem(sha1, NULL, size, type, path, flags);
+ ret = index_mem(sha1, "", size, type, path, flags);
} else if (size <= SMALL_FILE_SIZE) {
char *buf = xmalloc(size);
if (size == read_in_full(fd, buf, size))
#include "tree-walk.h"
#include "refs.h"
#include "remote.h"
+#include "dir.h"
static int get_sha1_oneline(const char *, unsigned char *, struct commit_list *);
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;
}
const char *object_name,
int object_name_len)
{
- struct stat st;
unsigned char sha1[20];
unsigned mode;
if (!prefix)
prefix = "";
- if (!lstat(filename, &st))
+ if (file_exists(filename))
die("Path '%s' exists on disk, but not in '%.*s'.",
filename, object_name_len, object_name);
if (errno == ENOENT || errno == ENOTDIR) {
const char *prefix,
const char *filename)
{
- struct stat st;
const struct cache_entry *ce;
int pos;
unsigned namelen = strlen(filename);
ce_stage(ce), filename);
}
- if (!lstat(filename, &st))
+ if (file_exists(filename))
die("Path '%s' exists on disk, but not in the index.", filename);
if (errno == ENOENT || errno == ENOTDIR)
die("Path '%s' does not exist (neither on disk nor in the index).",
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);
}
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;
}
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;
}
int ok_to_remove_submodule(const char *path)
{
- struct stat st;
ssize_t len;
struct child_process cp = CHILD_PROCESS_INIT;
const char *argv[] = {
struct strbuf buf = STRBUF_INIT;
int ok_to_remove = 1;
- if ((lstat(path, &st) < 0) || is_empty_dir(path))
+ if (!file_exists(path) || is_empty_dir(path))
return 1;
if (!submodule_uses_gitfile(path))
! test -s err
'
+test_expect_success "filter: clean empty file" '
+ git config filter.in-repo-header.clean "echo cleaned && cat" &&
+ git config filter.in-repo-header.smudge "sed 1d" &&
+
+ echo "empty-in-worktree filter=in-repo-header" >>.gitattributes &&
+ >empty-in-worktree &&
+
+ echo cleaned >expected &&
+ git add empty-in-worktree &&
+ git show :empty-in-worktree >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success "filter: smudge empty file" '
+ git config filter.empty-in-repo.clean "cat >/dev/null" &&
+ git config filter.empty-in-repo.smudge "echo smudged && cat" &&
+
+ echo "empty-in-repo filter=empty-in-repo" >>.gitattributes &&
+ echo dead data walking >empty-in-repo &&
+ git add empty-in-repo &&
+
+ echo smudged >expected &&
+ git checkout-index --prefix=filtered- empty-in-repo &&
+ test_cmp expected filtered-empty-in-repo
+'
+
test_done
'
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" = \
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_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
--- /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_expect_success 'stash drop complains of extra options' '
+ test_must_fail git stash drop --foo
+'
+
test_expect_success 'drop top stash' '
git reset --hard &&
git stash list > stashlist1 &&
. ./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
'
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 &&
'
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 '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)"
'
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_done
git commit -m "add bfile"
) &&
test_tick && test_tick &&
+ echo "second" >afile &&
+ git add afile &&
+ git commit -m "second commit" &&
echo "original $dollar" >afile &&
git add afile &&
git commit -m "do not clobber $dollar signs"
)
'
+test_expect_success '--log=1 limits shortlog length' '
+(
+ cd cloned &&
+ git reset --hard HEAD^ &&
+ test "$(cat afile)" = original &&
+ test "$(cat bfile)" = added &&
+ git pull --log=1 &&
+ git log -3 &&
+ git cat-file commit HEAD >result &&
+ grep Dollar result &&
+ ! grep "second commit" result
+)
+'
+
test_done
git -C hidden.git rev-parse --verify b
'
-test_expect_success 'create 2,000 tags in the repo' '
- (
- cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
- for i in $(test_seq 2000)
+# create an arbitrary number of tags, numbered from tag-$1 to tag-$2
+create_tags () {
+ rm -f marks &&
+ for i in $(test_seq "$1" "$2")
do
- echo "commit refs/heads/too-many-refs"
- echo "mark :$i"
- echo "committer git <git@example.com> $i +0000"
- echo "data 0"
- echo "M 644 inline bla.txt"
- echo "data 4"
- echo "bla"
+ # don't use here-doc, because it requires a process
+ # per loop iteration
+ echo "commit refs/heads/too-many-refs-$1" &&
+ echo "mark :$i" &&
+ echo "committer git <git@example.com> $i +0000" &&
+ echo "data 0" &&
+ echo "M 644 inline bla.txt" &&
+ echo "data 4" &&
+ echo "bla" &&
# make every commit dangling by always
# rewinding the branch after each commit
- echo "reset refs/heads/too-many-refs"
- echo "from :1"
+ echo "reset refs/heads/too-many-refs-$1" &&
+ echo "from :$1"
done | git fast-import --export-marks=marks &&
# now assign tags to all the dangling commits we created above
tag=$(perl -e "print \"bla\" x 30") &&
sed -e "s|^:\([^ ]*\) \(.*\)$|\2 refs/tags/$tag-\1|" <marks >>packed-refs
+}
+
+test_expect_success 'create 2,000 tags in the repo' '
+ (
+ cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
+ create_tags 1 2000
)
'
test_line_count = 2 posts
'
+test_expect_success EXPENSIVE 'http can handle enormous ref negotiation' '
+ (
+ cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
+ create_tags 2001 50000
+ ) &&
+ git -C too-many-refs fetch -q --tags &&
+ (
+ cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
+ create_tags 50001 100000
+ ) &&
+ git -C too-many-refs fetch -q --tags &&
+ git -C too-many-refs for-each-ref refs/tags >tags &&
+ test_line_count = 100000 tags
+'
+
stop_httpd
test_done
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
'
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
test "$(git rev-parse HEAD)" = "$(git rev-parse c1)"
'
+test_expect_success 'pull.ff=true overrides merge.ff=false' '
+ git reset --hard c0 &&
+ test_config merge.ff false &&
+ test_config pull.ff true &&
+ git pull . c1 &&
+ test "$(git rev-parse HEAD)" = "$(git rev-parse c1)"
+'
+
test_expect_success 'fast-forward pull creates merge with "false" in pull.ff' '
git reset --hard c0 &&
test_config pull.ff false &&
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 &&
--- /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
--- /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;
+}
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;
strbuf_addstr(buf, name);
if (safe_create_leading_directories(buf->buf) ||
- write_file(buf->buf, 0, "%s\n", sha1_to_hex(sha1)))
+ 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);
}
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;
}
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;
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;
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);
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);
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);