author | Junio C Hamano <gitster@pobox.com> | |
Fri, 4 Mar 2011 23:02:27 +0000 (15:02 -0800) | ||
committer | Junio C Hamano <gitster@pobox.com> | |
Fri, 4 Mar 2011 23:02:27 +0000 (15:02 -0800) |
* jn/maint-commit-missing-template:
commit: error out for missing commit message template
commit: error out for missing commit message template
533 files changed:
diff --git a/.gitignore b/.gitignore
index 20560b810b0471c3b7d012bf0f14d7885e2b83ec..c460c66766ab590239e4ae9107bb55b9fd30fac6 100644 (file)
--- a/.gitignore
+++ b/.gitignore
/git-remote-https
/git-remote-ftp
/git-remote-ftps
+/git-remote-fd
+/git-remote-ext
/git-remote-testgit
/git-repack
/git-replace
/test-index-version
/test-line-buffer
/test-match-trees
+/test-mktemp
/test-obj-pool
/test-parse-options
/test-path-utils
/test-sha1
/test-sigchain
/test-string-pool
+/test-subprocess
/test-svn-fe
/test-treap
/common-cmds.h
diff --git a/.mailmap b/.mailmap
index a8091eb5dfa430bf1b0537da47a31e7cf88d8622..19c87262322cf5acc9d4fd5fa06f96eb3e988049 100644 (file)
--- a/.mailmap
+++ b/.mailmap
Lars Doelle <lars.doelle@on-line.de>
Li Hong <leehong@pku.edu.cn>
Lukas Sandström <lukass@etek.chalmers.se>
-Martin Langhoff <martin@catalyst.net.nz>
+Martin Langhoff <martin@laptop.org>
Michael Coleman <tutufan@gmail.com>
Michael J Gruber <git@drmicha.warpmail.net> <michaeljgruber+gmane@fastmail.fm>
Michael W. Olson <mwolson@gnu.org>
index 46f8a3fab13f71623ef6dbc4239067e8daa9dc59..fe1c1e5bc26e683540cb9fe5f43320192be9185d 100644 (file)
- When we pass <string, length> pair to functions, we should try to
pass them in that order.
+
+Writing Documentation:
+
+ Every user-visible change should be reflected in the documentation.
+ The same general rule as for code applies -- imitate the existing
+ conventions. A few commented examples follow to provide reference
+ when writing or modifying command usage strings and synopsis sections
+ in the manual pages:
+
+ Placeholders are spelled in lowercase and enclosed in angle brackets:
+ <file>
+ --sort=<key>
+ --abbrev[=<n>]
+
+ Possibility of multiple occurrences is indicated by three dots:
+ <file>...
+ (One or more of <file>.)
+
+ Optional parts are enclosed in square brackets:
+ [<extra>]
+ (Zero or one <extra>.)
+
+ --exec-path[=<path>]
+ (Option with an optional argument. Note that the "=" is inside the
+ brackets.)
+
+ [<patch>...]
+ (Zero or more of <patch>. Note that the dots are inside, not
+ outside the brackets.)
+
+ Multiple alternatives are indicated with vertical bar:
+ [-q | --quiet]
+ [--utf8 | --no-utf8]
+
+ Parentheses are used for grouping:
+ [(<rev>|<range>)...]
+ (Any number of either <rev> or <range>. Parens are needed to make
+ it clear that "..." pertains to both <rev> and <range>.)
+
+ [(-p <parent>)...]
+ (Any number of option -p, each with one <parent> argument.)
+
+ git remote set-head <name> (-a | -d | <branch>)
+ (One and only one of "-a", "-d" or "<branch>" _must_ (no square
+ brackets) be provided.)
+
+ And a somewhat more contrived example:
+ --diff-filter=[(A|C|D|M|R|T|U|X|B)...[*]]
+ Here "=" is outside the brackets, because "--diff-filter=" is a
+ valid usage. "*" has its own pair of brackets, because it can
+ (optionally) be specified only when one or more of the letters is
+ also provided.
diff --git a/Documentation/Makefile b/Documentation/Makefile
index e117bc4315f6e288e89cf83fb80e876bee7a7dc2..36989b7f6541cb8444385b64d2c955ae2a1f6d1b 100644 (file)
--- a/Documentation/Makefile
+++ b/Documentation/Makefile
#
# For asciidoc ...
-# -7.1.2, no extra settings are needed.
-# 8.0-, set ASCIIDOC8.
+# -7.1.2, set ASCIIDOC7
+# 8.0-, no extra settings are needed
#
#
# For docbook-xsl ...
-# -1.68.1, set ASCIIDOC_NO_ROFF? (based on changelog from 1.73.0)
-# 1.69.0, no extra settings are needed?
+# -1.68.1, no extra settings are needed?
+# 1.69.0, set ASCIIDOC_ROFF?
# 1.69.1-1.71.0, set DOCBOOK_SUPPRESS_SP?
-# 1.71.1, no extra settings are needed?
+# 1.71.1, set ASCIIDOC_ROFF?
# 1.72.0, set DOCBOOK_XSL_172.
-# 1.73.0-, set ASCIIDOC_NO_ROFF
+# 1.73.0-, no extra settings are needed
#
-#
-# If you had been using DOCBOOK_XSL_172 in an attempt to get rid
-# of 'the ".ft C" problem' in your generated manpages, and you
-# instead ended up with weird characters around callouts, try
-# using ASCIIDOC_NO_ROFF instead (it works fine with ASCIIDOC8).
-#
-
-ifdef ASCIIDOC8
+ifndef ASCIIDOC7
ASCIIDOC_EXTRA += -a asciidoc7compatible -a no-inline-literal
endif
ifdef DOCBOOK_XSL_172
ASCIIDOC_EXTRA += -a git-asciidoc-no-roff
MANPAGE_XSL = manpage-1.72.xsl
else
- ifdef ASCIIDOC_NO_ROFF
+ ifndef ASCIIDOC_ROFF
# docbook-xsl after 1.72 needs the regular XSL, but will not
# pass-thru raw roff codes from asciidoc.conf, so turn them off.
ASCIIDOC_EXTRA += -a git-asciidoc-no-roff
diff --git a/Documentation/RelNotes/1.7.4.1.txt b/Documentation/RelNotes/1.7.4.1.txt
--- /dev/null
@@ -0,0 +1,27 @@
+Git v1.7.4.1 Release Notes
+==========================
+
+Fixes since v1.7.4
+------------------
+
+ * On Windows platform, the codepath to spawn a new child process forgot
+ to first flush the output buffer.
+
+ * "git bundle" did not use OFS_DELTA encoding, making its output a few
+ per-cent larger than necessarily.
+
+ * The option to tell "git clone" to recurse into the submodules was
+ misspelled with an underscore "--recurse_submodules".
+
+ * "git diff --cached HEAD" before the first commit does what an end user
+ would expect (namely, show what would be committed without further "git
+ add").
+
+ * "git fast-import" didn't accept the command to ask for "notes" feature
+ to be present in its input stream, even though it was capable of the
+ feature.
+
+ * "git fsck" gave up scanning loose object files in directories with
+ garbage files.
+
+And other minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.4.txt b/Documentation/RelNotes/1.7.4.txt
--- /dev/null
@@ -0,0 +1,156 @@
+Git v1.7.4 Release Notes
+========================
+
+Updates since v1.7.3
+--------------------
+
+ * The documentation Makefile now assumes by default asciidoc 8 and
+ docbook-xsl >= 1.73. If you have older versions, you can set
+ ASCIIDOC7 and ASCIIDOC_ROFF, respectively.
+
+ * The option parsers of various commands that create new branches (or
+ rename existing ones to a new name) were too loose and users were
+ allowed to give a branch a name that begins with a dash by creative
+ abuse of their command line options, which only led to burning
+ themselves. The name of a branch cannot begin with a dash now.
+
+ * System-wide fallback default attributes can be stored in
+ /etc/gitattributes; the core.attributesfile configuration variable can
+ be used to customize the path to this file.
+
+ * The thread structure generated by "git send-email" has changed
+ slightly. Setting the cover letter of the latest series as a reply
+ to the cover letter of the previous series with --in-reply-to used
+ to make the new cover letter and all the patches replies to the
+ cover letter of the previous series; this has been changed to make
+ the patches in the new series replies to the new cover letter.
+
+ * The Bash completion script in contrib/ has been adjusted to be usable with
+ Bash 4 (options with '=value' didn't complete). It has been also made
+ usable with zsh.
+
+ * Different pagers can be chosen depending on which subcommand is
+ being run under the pager, using the "pager.<subcommand>" variable.
+
+ * The hardcoded tab-width of 8 that is used in whitespace breakage checks is now
+ configurable via the attributes mechanism.
+
+ * Support of case insensitive filesystems (i.e. "core.ignorecase") has
+ been improved. For example, the gitignore mechanism didn't pay attention
+ to case insensitivity.
+
+ * The <tree>:<path> syntax for naming a blob in a tree, and the :<path>
+ syntax for naming a blob in the index (e.g. "master:Makefile",
+ ":hello.c") have been extended. You can start <path> with "./" to
+ implicitly have the (sub)directory you are in prefixed to the
+ lookup. Similarly, ":../Makefile" from a subdirectory would mean
+ "the Makefile of the parent directory in the index".
+
+ * "git blame" learned the --show-email option to display the e-mail
+ addresses instead of the names of authors.
+
+ * "git commit" learned the --fixup and --squash options to help later invocation
+ of interactive rebase.
+
+ * Command line options to "git cvsimport" whose names are in capital
+ letters (-A, -M, -R and -S) can now be specified as the default in
+ the .git/config file by their longer names (cvsimport.authorsFile,
+ cvsimport.mergeRegex, cvsimport.trackRevisions, cvsimport.ignorePaths).
+
+ * "git daemon" can be built in the MinGW environment.
+
+ * "git daemon" can take more than one --listen option to listen to
+ multiple addresses.
+
+ * "git describe --exact-match" was optimized not to read commit
+ objects unnecessarily.
+
+ * "git diff" and "git grep" learned what functions and subroutines
+ in Fortran, Pascal and Perl look like.
+
+ * "git fetch" learned the "--recurse-submodules" option.
+
+ * "git mergetool" tells vim/gvim to show a three-way diff by default
+ (use vimdiff2/gvimdiff2 as the tool name for old behavior).
+
+ * "git log -G<pattern>" limits the output to commits whose change has
+ added or deleted lines that match the given pattern.
+
+ * "git read-tree" with no argument as a way to empty the index is
+ deprecated; we might want to remove it in the future. Users can
+ use the new --empty option to be more explicit instead.
+
+ * "git repack -f" does not spend cycles to recompress objects in the
+ non-delta representation anymore (use -F if you really mean it
+ e.g. after you changed the core.compression variable setting).
+
+ * "git merge --log" used to limit the resulting merge log to 20
+ entries; this is now customizable by giving e.g. "--log=47".
+
+ * "git merge" may work better when all files were moved out of a
+ directory in one branch while a new file is created in place of that
+ directory in the other branch.
+
+ * "git merge" learned the "--abort" option, synonymous to
+ "git reset --merge" when a merge is in progress.
+
+ * "git notes" learned the "merge" subcommand to merge notes refs.
+ In addition to the default manual conflict resolution, there are
+ also several notes merge strategies for automatically resolving
+ notes merge conflicts.
+
+ * "git rebase --autosquash" can use SHA-1 object names to name the
+ commit which is to be fixed up (e.g. "fixup! e83c5163").
+
+ * The default "recursive" merge strategy learned the --rename-threshold
+ option to influence the rename detection, similar to the -M option
+ of "git diff". From the "git merge" frontend, the "-X<strategy option>"
+ interface, e.g. "git merge -Xrename-threshold=50% ...", can be used
+ to trigger this.
+
+ * The "recursive" strategy also learned to ignore various whitespace
+ changes; the most notable is -Xignore-space-at-eol.
+
+ * "git send-email" learned "--to-cmd", similar to "--cc-cmd", to read
+ the recipient list from a command output.
+
+ * "git send-email" learned to read and use "To:" from its input files.
+
+ * you can extend "git shell", which is often used on boxes that allow
+ git-only login over ssh as login shell, with a custom set of
+ commands.
+
+ * The current branch name in "git status" output can be colored differently
+ from the generic header color by setting the "color.status.branch" variable.
+
+ * "git submodule sync" updates metainformation for all submodules,
+ not just the ones that have been checked out.
+
+ * gitweb can use a custom 'highlight' command with its configuration file.
+
+ * other gitweb updates.
+
+
+Also contains various documentation updates.
+
+
+Fixes since v1.7.3
+------------------
+
+All of the fixes in the v1.7.3.X maintenance series are included in this
+release, unless otherwise noted.
+
+ * "git log --author=me --author=her" did not find commits written by
+ me or by her; instead it looked for commits written by me and by
+ her, which is impossible.
+
+ * "git push --progress" shows progress indicators now.
+
+ * "git rebase -i" showed a confusing error message when given a
+ branch name that does not exist.
+
+ * "git repack" places its temporary packs under $GIT_OBJECT_DIRECTORY/pack
+ instead of $GIT_OBJECT_DIRECTORY/ to avoid cross directory renames.
+
+ * "git submodule update --recursive --other-flags" passes flags down
+ to its subinvocations.
diff --git a/Documentation/RelNotes/1.7.5.txt b/Documentation/RelNotes/1.7.5.txt
--- /dev/null
@@ -0,0 +1,56 @@
+Git v1.7.5 Release Notes (draft)
+========================
+
+Updates since v1.7.4
+--------------------
+
+ * Various MinGW portability fixes.
+
+ * Various git-p4 enhancements (in contrib).
+
+ * "git config" used to be also known as "git repo-config", but the old
+ name is now officially deprecated.
+
+ * "git checkout --detach <commit>" is a more user friendly synonym for
+ "git checkout <commit>^0".
+
+ * "git cherry-pick" and "git revert" can be told to use custom merge
+ strategy, similar to "git rebase".
+
+ * "rev-list --objects $revs -- $pathspec" would limit the objects listed
+ in its output properly with the pathspec, in preparation for narrow
+ clones.
+
+ * "git log" family of commands now understand globbing pathspecs. You
+ can say "git log -- '*.txt'" for example.
+
+ * "git rerere" learned a new subcommand "remaining", that is similar to
+ "status" that lists the paths that had conflicts that are known to
+ rerere, but excludes the paths that have already been marked as
+ resolved in the index from its output. "git mergetool" has been
+ updated to use this facility.
+
+ * A possible value to the "push.default" configuration variable,
+ 'tracking', gained a synonym that more naturally describes what it
+ does, 'upstream'.
+
+Also contains various documentation updates.
+
+
+Fixes since v1.7.4
+------------------
+
+All of the fixes in the v1.7.4.X maintenance series are included in this
+release, unless otherwise noted.
+
+ * "git merge" triggers prepare-commit-msg hook. Earlier, only "git
+ commit" to conclude an interrupted merge triggered the hook, leading to
+ an inconsistent overall user experience (js/maint-merge-use-prepare-commit-msg-hook).
+
+
+---
+exec >/var/tmp/1
+O=v1.7.4
+O=v1.7.4.1-140-g8978166
+echo O=$(git describe 'master')
+git shortlog --no-merges ^maint ^$O master
index 4f1e9799321380739afe96dd7ec841b768ae988b..c995a1a47b3cfb99a2bebbadf4f7a5360cbe3d1a 100644 (file)
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
= true).
core.worktree::
- Set the path to the root of the work tree.
+ Set the path to the root of the working tree.
This can be overridden by the GIT_WORK_TREE environment
- variable and the '--work-tree' command line option. It can be
- an absolute path or a relative path to the .git directory,
- either specified by --git-dir or GIT_DIR, or automatically
- discovered.
- If --git-dir or GIT_DIR are specified but none of
+ variable and the '--work-tree' command line option.
+ The value can an absolute path or relative to the path to
+ the .git directory, which is either specified by --git-dir
+ or GIT_DIR, or automatically discovered.
+ If --git-dir or GIT_DIR is specified but none of
--work-tree, GIT_WORK_TREE and core.worktree is specified,
- the current working directory is regarded as the root of the
- work tree.
+ the current working directory is regarded as the top level
+ of your working tree.
+
Note that this variable is honored even when set in a configuration
-file in a ".git" subdirectory of a directory, and its value differs
+file in a ".git" subdirectory of a directory and its value differs
from the latter directory (e.g. "/path/to/.git/config" has
core.worktree set to "/different/path"), which is most likely a
-misconfiguration. Running git commands in "/path/to" directory will
+misconfiguration. Running git commands in the "/path/to" directory will
still use "/different/path" as the root of the work tree and can cause
-great confusion to the users.
+confusion unless you know what you are doing (e.g. you are creating a
+read-only snapshot of the same index to a location different from the
+repository's usual working tree).
core.logAllRefUpdates::
Enable the reflog. Updates to a ref <ref> is logged to the file
If true, git will warn you if the ref name you passed it is ambiguous
and might match multiple refs in the .git/refs/ tree. True by default.
+core.abbrevguard::
+ Even though git makes sure that it uses enough hexdigits to show
+ an abbreviated object name unambiguously, as more objects are
+ added to the repository over time, a short name that used to be
+ unique will stop being unique. Git uses this many extra hexdigits
+ that are more than necessary to make the object name currently
+ unique, in the hope that its output will stay unique a bit longer.
+ Defaults to 0.
+
core.compression::
An integer -1..9, indicating a default compression level.
-1 is the zlib default. 0 means no compression,
prompt. The external program shall be given a suitable prompt as
command line argument and write the password on its STDOUT.
+core.attributesfile::
+ In addition to '.gitattributes' (per-directory) and
+ '.git/info/attributes', git looks into this file for attributes
+ (see linkgit:gitattributes[5]). Path expansions are made the same
+ way as for `core.excludesfile`.
+
core.editor::
Commands such as `commit` and `tag` that lets you edit
messages by launching an editor uses the value of this
part of the line terminator, i.e. with it, `trailing-space`
does not trigger if the character before such a carriage-return
is not a whitespace (not enabled by default).
+* `tabwidth=<n>` tells how many character positions a tab occupies; this
+ is relevant for `indent-with-non-tab` and when git fixes `tab-in-indent`
+ errors. The default tab width is 8. Allowed values are 1 to 63.
core.fsyncobjectfiles::
This boolean will enable 'fsync()' when writing object files.
this behavior can be chosen per-branch using the `--track`
and `--no-track` options. The valid settings are: `false` -- no
automatic setup is done; `true` -- automatic setup is done when the
- starting point is a remote branch; `always` -- automatic setup is
- done when the starting point is either a local branch or remote
+ starting point is a remote-tracking branch; `always` --
+ automatic setup is done when the starting point is either a
+ local branch or remote-tracking
branch. This option defaults to true.
branch.autosetuprebase::
When `local`, rebase is set to true for tracked branches of
other local branches.
When `remote`, rebase is set to true for tracked branches of
- remote branches.
+ remote-tracking branches.
When `always`, rebase will be set to true for all tracking
branches.
See "branch.autosetupmerge" for details on how to set up a
color.branch.<slot>::
Use customized color for branch coloration. `<slot>` is one of
`current` (the current branch), `local` (a local branch),
- `remote` (a tracking branch in refs/remotes/), `plain` (other
+ `remote` (a remote-tracking branch in refs/remotes/), `plain` (other
refs).
+
The value for these configuration variables is a list of colors (at most
color.decorate.<slot>::
Use customized color for 'git log --decorate' output. `<slot>` is one
of `branch`, `remoteBranch`, `tag`, `stash` or `HEAD` for local
- branches, remote tracking branches, tags, stash and HEAD, respectively.
+ branches, remote-tracking branches, tags, stash and HEAD, respectively.
color.grep::
When set to `always`, always highlight matches. When `false` (or
one of `header` (the header text of the status message),
`added` or `updated` (files which are added but not committed),
`changed` (files which are changed but not added in the index),
- `untracked` (files which are not tracked by git), or
+ `untracked` (files which are not tracked by git),
+ `branch` (the current branch), or
`nobranch` (the color the 'no branch' warning is shown in, defaulting
to red). The values of these variables may be specified as in
color.branch.<slot>.
sequences that match the regular expression are "words", all other
characters are *ignorable* whitespace.
+fetch.recurseSubmodules::
+ A boolean value which changes the behavior for fetch and pull, the
+ default is to not recursively fetch populated submodules unless
+ configured otherwise.
+
fetch.unpackLimit::
If the number of objects fetched over the git native
transfer is below this
linkgit:git-gui[1].
gui.pruneduringfetch::
- "true" if linkgit:git-gui[1] should prune tracking branches when
+ "true" if linkgit:git-gui[1] should prune remote-tracking branches when
performing a fetch. The default value is "false".
gui.trustmtime::
supported.
pager.<cmd>::
- Allows turning on or off pagination of the output of a
- particular git subcommand when writing to a tty. If
- `\--paginate` or `\--no-pager` is specified on the command line,
- it takes precedence over this option. To disable pagination for
- all commands, set `core.pager` or `GIT_PAGER` to `cat`.
+ If the value is boolean, turns on or off pagination of the
+ output of a particular git subcommand when writing to a tty.
+ Otherwise, turns on pagination for the subcommand using the
+ pager specified by the value of `pager.<cmd>`. If `\--paginate`
+ or `\--no-pager` is specified on the command line, it takes
+ precedence over this option. To disable pagination for all
+ commands, set `core.pager` or `GIT_PAGER` to `cat`.
pretty.<name>::
Alias for a --pretty= format string, as specified in
* `matching` - push all matching branches.
All branches having the same name in both ends are considered to be
matching. This is the default.
-* `tracking` - push the current branch to its upstream branch.
+* `upstream` - push the current branch to its upstream branch.
+* `tracking` - deprecated synonym for `upstream`.
* `current` - push the current branch to a branch of the same name.
rebase.stat::
sendemail.smtpdomain::
sendemail.smtpserver::
sendemail.smtpserverport::
+sendemail.smtpserveroption::
sendemail.smtpuser::
sendemail.thread::
sendemail.validate::
URL and other values found in the `.gitmodules` file. See
linkgit:git-submodule[1] and linkgit:gitmodules[5] for details.
+submodule.<name>.fetchRecurseSubmodules::
+ This option can be used to enable/disable recursive fetching of this
+ submodule. It can be overridden by using the --[no-]recurse-submodules
+ command line option to "git fetch" and "git pull".
+ This setting will override that from in the linkgit:gitmodules[5]
+ file.
+
submodule.<name>.ignore::
Defines under what circumstances "git status" and the diff family show
a submodule as modified. When set to "all", it will never be considered
index 5495344e612dece0eb6cb6311864463c004f1181..c93124be79809e36d9a29fd69ce77cd7f22413e1 100644 (file)
digits can be specified with `--abbrev=<n>`.
-B[<n>][/<m>]::
+--break-rewrites[=[<n>][/<m>]]::
Break complete rewrite changes into pairs of delete and
create. This serves two purposes:
+
another file.
-M[<n>]::
+--find-renames[=<n>]::
ifndef::git-log[]
Detect renames.
endif::git-log[]
hasn't changed.
-C[<n>]::
+--find-copies[=<n>]::
Detect copies as well as renames. See also `--find-copies-harder`.
If `n` is specified, it has the same meaning as for `-M<n>`.
appearing in diff output; see the 'pickaxe' entry in
linkgit:gitdiffcore[7] for more details.
+-G<regex>::
+ Look for differences whose added or removed line matches
+ the given <regex>.
+
--pickaxe-all::
- When `-S` finds a change, show all the changes in that
+ When `-S` or `-G` finds a change, show all the changes in that
changeset, not just the files that contain the change
in <string>.
index e0ba8cc07549af375c89496c57c016a41b8cc699..ae413e52a52618503aa8d569f72ae688ec95a8fa 100644 (file)
machine.
<2> clone sets these configuration variables by default.
It arranges `git pull` to fetch and store the branches of mothership
-machine to local `remotes/origin/*` tracking branches.
+machine to local `remotes/origin/*` remote-tracking branches.
<3> arrange `git push` to push local `master` branch to
`remotes/satellite/master` branch of the mothership machine.
<4> push will stash our work away on `remotes/satellite/master`
-tracking branch on the mothership machine. You could use this as
-a back-up method.
+remote-tracking branch on the mothership machine. You could use this
+as a back-up method.
<5> on mothership machine, merge the work done on the satellite
machine into the master branch.
index 5ce1e727458bfa5e73ad98c7a852bd7c0f034c6b..f37276e5add75583fd363af2cd0b7ba051771a97 100644 (file)
-p::
--prune::
- After fetching, remove any remote tracking branches which
+ After fetching, remove any remote-tracking branches which
no longer exist on the remote.
endif::git-pull[]
downloaded. The default behavior for a remote may be
specified with the remote.<name>.tagopt setting. See
linkgit:git-config[1].
+
+--[no-]recurse-submodules::
+ This option controls if new commits of all populated submodules should
+ be fetched too (see linkgit:git-config[1] and linkgit:gitmodules[5]).
+
+--submodule-prefix=<path>::
+ Prepend <path> to paths printed in informative messages
+ such as "Fetching submodule foo". This option is used
+ internally when recursing over submodules.
endif::git-pull[]
-u::
index 54aaaeb41b969be4c79a2a8072bc6daca469ca8d..a03448f9231f78340fc492bffa009b96648ee6ac 100644 (file)
There are also more complex operations that can be performed. But beware
that because the patch is applied only to the index and not the working
tree, the working tree will appear to "undo" the change in the index.
-For example, introducing a a new line into the index that is in neither
+For example, introducing a new line into the index that is in neither
the HEAD nor the working tree will stage the new line for commit, but
the line will appear to be reverted in the working tree.
index 4f358c8d6c3320c5ef0a78d6c4d155535433f3d0..2411ce5bfe05a14ed45c1546c3d1a6d92b39d8eb 100644 (file)
Author
------
-Written by Martin Langhoff <martin@catalyst.net.nz>.
+Written by Martin Langhoff <martin@laptop.org>.
Documentation
--------------
index a27f43950fdfe8f6a0e8860d79ee31f0bc60750d..c71671b4f99e0be3670fc6d7e4872927a86ed02e 100644 (file)
SYNOPSIS
--------
[verse]
-'git blame' [-c] [-b] [-l] [--root] [-t] [-f] [-n] [-s] [-p] [-w] [--incremental] [-L n,m]
+'git blame' [-c] [-b] [-l] [--root] [-t] [-f] [-n] [-s] [-e] [-p] [-w] [--incremental] [-L n,m]
[-S <revs-file>] [-M] [-C] [-C] [-C] [--since=<date>]
[<rev> | --contents <file> | --reverse <rev>] [--] <file>
-s::
Suppress the author name and timestamp from the output.
+-e::
+--show-email::
+ Show the author email instead of author name (Default: off).
+
-w::
Ignore whitespace when comparing the parent's version and
the child's to find where the lines came from.
index 1940256930d92c0679b95bb0cd41f24847828bd4..9106d38e406003cd53fd690ca66ee8e98630e4e3 100644 (file)
working tree to it; use "git checkout <newbranch>" to switch to the
new branch.
-When a local branch is started off a remote branch, git sets up the
+When a local branch is started off a remote-tracking branch, git sets up the
branch so that 'git pull' will appropriately merge from
-the remote branch. This behavior may be changed via the global
+the remote-tracking branch. This behavior may be changed via the global
`branch.autosetupmerge` configuration flag. That setting can be
-overridden by using the `--track` and `--no-track` options.
+overridden by using the `--track` and `--no-track` options, and
+changed later using `git branch --set-upstream`.
With a '-m' or '-M' option, <oldbranch> will be renamed to <newbranch>.
If <oldbranch> had a corresponding reflog, it is renamed to match
Move/rename a branch even if the new branch name already exists.
--color[=<when>]::
- Color branches to highlight current, local, and remote branches.
+ Color branches to highlight current, local, and
+ remote-tracking branches.
The value must be always (the default), never, or auto.
--no-color::
it directs `git pull` without arguments to pull from the
upstream when the new branch is checked out.
+
-This behavior is the default when the start point is a remote branch.
+This behavior is the default when the start point is a remote-tracking branch.
Set the branch.autosetupmerge configuration variable to `false` if you
want `git checkout` and `git branch` to always behave as if '--no-track'
were given. Set it to `always` if you want this behavior when the
-start-point is either a local or remote branch.
+start-point is either a local or remote-tracking branch.
--no-track::
Do not set up "upstream" configuration, even if the
index 6266a3a6020db72c8629ad521dfedccc9d790a03..299007b206cb5d810d19eed9b7831c3ea6368043 100644 (file)
<git-rev-list-args>::
A list of arguments, acceptable to 'git rev-parse' and
- 'git rev-list' (and containg a named ref, see SPECIFYING REFERENCES
+ 'git rev-list' (and containing a named ref, see SPECIFYING REFERENCES
below), that specifies the specific objects and references
to transport. For example, `master{tilde}10..master` causes the
current master reference to be packaged along with all objects
index 22d36114df68963811a80a6c311a98b1a26d88d3..396f4cc15bff745e72e137239dbd49e6584e23d2 100644 (file)
--------
[verse]
'git checkout' [-q] [-f] [-m] [<branch>]
+'git checkout' [-q] [-f] [-m] [--detach] [<commit>]
'git checkout' [-q] [-f] [-m] [[-b|-B|--orphan] <new_branch>] [<start_point>]
'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <paths>...
'git checkout' --patch [<tree-ish>] [--] [<paths>...]
'git checkout' [<branch>]::
'git checkout' -b|-B <new_branch> [<start point>]::
+'git checkout' [--detach] [<commit>]::
This form switches branches by updating the index, working
- tree, and HEAD to reflect the specified branch.
+ tree, and HEAD to reflect the specified branch or commit.
+
If `-b` is given, a new branch is created as if linkgit:git-branch[1]
were called and then checked out; in this case you can
"--track" in linkgit:git-branch[1] for details.
+
If no '-b' option is given, the name of the new branch will be
-derived from the remote branch. If "remotes/" or "refs/remotes/"
+derived from the remote-tracking branch. If "remotes/" or "refs/remotes/"
is prefixed it is stripped away, and then the part up to the
next slash (which would be the nickname of the remote) is removed.
This would tell us to use "hack" as the local branch when branching
Create the new branch's reflog; see linkgit:git-branch[1] for
details.
+--detach::
+ Rather than checking out a branch to work on it, check out a
+ commit for inspection and discardable experiments.
+ This is the default behavior of "git checkout <commit>" when
+ <commit> is not a branch name. See the "DETACHED HEAD" section
+ below for details.
+
--orphan::
Create a new 'orphan' branch, named <new_branch>, started from
<start_point> and switch to it. The first commit made on this
-Detached HEAD
+DETACHED HEAD
-------------
+HEAD normally refers to a named branch (e.g. 'master'). Meanwhile, each
+branch refers to a specific commit. Let's look at a repo with three
+commits, one of them tagged, and with branch 'master' checked out:
+
+------------
+ HEAD (refers to branch 'master')
+ |
+ v
+a---b---c branch 'master' (refers to commit 'c')
+ ^
+ |
+ tag 'v2.0' (refers to commit 'b')
+------------
-It is sometimes useful to be able to 'checkout' a commit that is
-not at the tip of one of your branches. The most obvious
-example is to check out the commit at a tagged official release
-point, like this:
+When a commit is created in this state, the branch is updated to refer to
+the new commit. Specifically, 'git commit' creates a new commit 'd', whose
+parent is commit 'c', and then updates branch 'master' to refer to new
+commit 'd'. HEAD still refers to branch 'master' and so indirectly now refers
+to commit 'd':
------------
-$ git checkout v2.6.18
+$ edit; git add; git commit
+
+ HEAD (refers to branch 'master')
+ |
+ v
+a---b---c---d branch 'master' (refers to commit 'd')
+ ^
+ |
+ tag 'v2.0' (refers to commit 'b')
------------
-Earlier versions of git did not allow this and asked you to
-create a temporary branch using the `-b` option, but starting from
-version 1.5.0, the above command 'detaches' your HEAD from the
-current branch and directly points at the commit named by the tag
-(`v2.6.18` in the example above).
+It is sometimes useful to be able to checkout a commit that is not at
+the tip of any named branch, or even to create a new commit that is not
+referenced by a named branch. Let's look at what happens when we
+checkout commit 'b' (here we show two ways this may be done):
-You can use all git commands while in this state. You can use
-`git reset --hard $othercommit` to further move around, for
-example. You can make changes and create a new commit on top of
-a detached HEAD. You can even create a merge by using `git
-merge $othercommit`.
+------------
+$ git checkout v2.0 # or
+$ git checkout master^^
+
+ HEAD (refers to commit 'b')
+ |
+ v
+a---b---c---d branch 'master' (refers to commit 'd')
+ ^
+ |
+ tag 'v2.0' (refers to commit 'b')
+------------
-The state you are in while your HEAD is detached is not recorded
-by any branch (which is natural --- you are not on any branch).
-What this means is that you can discard your temporary commits
-and merges by switching back to an existing branch (e.g. `git
-checkout master`), and a later `git prune` or `git gc` would
-garbage-collect them. If you did this by mistake, you can ask
-the reflog for HEAD where you were, e.g.
+Notice that regardless of which checkout command we use, HEAD now refers
+directly to commit 'b'. This is known as being in detached HEAD state.
+It means simply that HEAD refers to a specific commit, as opposed to
+referring to a named branch. Let's see what happens when we create a commit:
------------
-$ git log -g -2 HEAD
+$ edit; git add; git commit
+
+ HEAD (refers to commit 'e')
+ |
+ v
+ e
+ /
+a---b---c---d branch 'master' (refers to commit 'd')
+ ^
+ |
+ tag 'v2.0' (refers to commit 'b')
+------------
+
+There is now a new commit 'e', but it is referenced only by HEAD. We can
+of course add yet another commit in this state:
+
+------------
+$ edit; git add; git commit
+
+ HEAD (refers to commit 'f')
+ |
+ v
+ e---f
+ /
+a---b---c---d branch 'master' (refers to commit 'd')
+ ^
+ |
+ tag 'v2.0' (refers to commit 'b')
+------------
+
+In fact, we can perform all the normal git operations. But, let's look
+at what happens when we then checkout master:
+
+------------
+$ git checkout master
+
+ HEAD (refers to branch 'master')
+ e---f |
+ / v
+a---b---c---d branch 'master' (refers to commit 'd')
+ ^
+ |
+ tag 'v2.0' (refers to commit 'b')
------------
+It is important to realize that at this point nothing refers to commit
+'f'. Eventually commit 'f' (and by extension commit 'e') will be deleted
+by the routine git garbage collection process, unless we create a reference
+before that happens. If we have not yet moved away from commit 'f',
+any of these will create a reference to it:
+
+------------
+$ git checkout -b foo <1>
+$ git branch foo <2>
+$ git tag foo <3>
+------------
+
+<1> creates a new branch 'foo', which refers to commit 'f', and then
+updates HEAD to refer to branch 'foo'. In other words, we'll no longer
+be in detached HEAD state after this command.
+
+<2> similarly creates a new branch 'foo', which refers to commit 'f',
+but leaves HEAD detached.
+
+<3> creates a new tag 'foo', which refers to commit 'f',
+leaving HEAD detached.
+
+If we have moved away from commit 'f', then we must first recover its object
+name (typically by using git reflog), and then we can create a reference to
+it. For example, to see the last two commits to which HEAD referred, we
+can use either of these commands:
+
+------------
+$ git reflog -2 HEAD # or
+$ git log -g -2 HEAD
+------------
EXAMPLES
--------
index 73008705ebe304c8d2a2d089e9328be369c50fc6..749d68a72bf720d82260fe175be0824f7ad069eb 100644 (file)
cherry-pick'ed commit, then a fast forward to this commit will
be performed.
+--strategy=<strategy>::
+ Use the given merge strategy. Should only be used once.
+ See the MERGE STRATEGIES section in linkgit:git-merge[1]
+ for details.
+
+-X<option>::
+--strategy-option=<option>::
+ Pass the merge strategy-specific option through to the
+ merge strategy. See linkgit:git-merge[1] for details.
+
EXAMPLES
--------
git cherry-pick master::
so the result can be inspected and made into a single new
commit if suitable.
+The following sequence attempts to backport a patch, bails out because
+the code the patch applies to has changed too much, and then tries
+again, this time exercising more care about matching up context lines.
+
+------------
+$ git cherry-pick topic^ <1>
+$ git diff <2>
+$ git reset --merge ORIG_HEAD <3>
+$ git cherry-pick -Xpatience topic^ <4>
+------------
+<1> apply the change that would be shown by `git show topic^`.
+In this example, the patch does not apply cleanly, so
+information about the conflict is written to the index and
+working tree and no new commit results.
+<2> summarize changes to be reconciled
+<3> cancel the cherry-pick. In other words, return to the
+pre-cherry-pick state, preserving any local modifications you had in
+the working tree.
+<4> try to apply the change introduced by `topic^` again,
+spending extra time to avoid mistakes based on incorrectly matching
+context lines.
+
Author
------
Written by Junio C Hamano <gitster@pobox.com>
index ab7293351d283e7ee3a8275c534c3667d6b4d696..42e7021215563f146a66d14e1b642d4dbe55bfaf 100644 (file)
'git clone' [--template=<template_directory>]
[-l] [-s] [--no-hardlinks] [-q] [-n] [--bare] [--mirror]
[-o <name>] [-b <name>] [-u <upload-pack>] [--reference <repository>]
- [--depth <depth>] [--recursive] [--] <repository> [<directory>]
+ [--depth <depth>] [--recursive|--recurse-submodules] [--] <repository>
+ [<directory>]
DESCRIPTION
-----------
Set up a mirror of the source repository. This implies `--bare`.
Compared to `--bare`, `--mirror` not only maps local branches of the
source to local branches of the target, it maps all refs (including
- remote branches, notes etc.) and sets up a refspec configuration such
+ remote-tracking branches, notes etc.) and sets up a refspec configuration such
that all these refs are overwritten by a `git remote update` in the
target repository.
as patches.
--recursive::
+--recurse-submodules::
After the clone is created, initialize all submodules within,
using their default settings. This is equivalent to running
`git submodule update --init --recursive` immediately after
index ec7b577b85ae5a320b1006380f5abd03946ba44b..8f89f6f08c57335ea6606dd1eaddde98477e9c9e 100644 (file)
--------
[verse]
'git commit' [-a | --interactive] [-s] [-v] [-u<mode>] [--amend] [--dry-run]
- [(-c | -C) <commit>] [-F <file> | -m <msg>] [--reset-author]
- [--allow-empty] [--allow-empty-message] [--no-verify] [-e] [--author=<author>]
- [--date=<date>] [--cleanup=<mode>] [--status | --no-status]
- [-i | -o] [--] [<file>...]
+ [(-c | -C | --fixup | --squash) <commit>] [-F <file> | -m <msg>]
+ [--reset-author] [--allow-empty] [--allow-empty-message] [--no-verify]
+ [-e] [--author=<author>] [--date=<date>] [--cleanup=<mode>]
+ [--status | --no-status] [-i | -o] [--] [<file>...]
DESCRIPTION
-----------
Like '-C', but with '-c' the editor is invoked, so that
the user can further edit the commit message.
+--fixup=<commit>::
+ Construct a commit message for use with `rebase --autosquash`.
+ The commit message will be the subject line from the specified
+ commit with a prefix of "fixup! ". See linkgit:git-rebase[1]
+ for details.
+
+--squash=<commit>::
+ Construct a commit message for use with `rebase --autosquash`.
+ The commit message subject line is taken from the specified
+ commit with a prefix of "squash! ". Can be used with additional
+ commit message options (`-m`/`-c`/`-C`/`-F`). See
+ linkgit:git-rebase[1] for details.
+
--reset-author::
When used with -C/-c/--amend options, declare that the
authorship of the resulting commit now belongs of the committer.
-u[<mode>]::
--untracked-files[=<mode>]::
- Show untracked files (Default: 'all').
+ Show untracked files.
+
-The mode parameter is optional, and is used to specify
-the handling of untracked files.
+The mode parameter is optional (defaults to 'all'), and is used to
+specify the handling of untracked files; when -u is not used, the
+default is 'normal', i.e. show untracked files and directories.
+
The possible options are:
+
- 'normal' - Shows untracked files and directories
- 'all' - Also shows individual files in untracked directories.
+
-See linkgit:git-config[1] for configuration variable
-used to change the default for when the option is not
-specified.
+The default can be changed using the status.showUntrackedFiles
+configuration variable documented in linkgit:git-config[1].
-v::
--verbose::
index b2696efae95b20b693251a9d9785405218285084..d25661eb215c8bd2002933a48dd6ad5ea360459a 100644 (file)
@@ -114,11 +114,11 @@ $ git cherry cvshead myhead | sed -n 's/^+ //p' | xargs -l1 git cvsexportcommit
Author
------
-Written by Martin Langhoff <martin@catalyst.net.nz> and others.
+Written by Martin Langhoff <martin@laptop.org> and others.
Documentation
--------------
-Documentation by Martin Langhoff <martin@catalyst.net.nz> and others.
+Documentation by Martin Langhoff <martin@laptop.org> and others.
GIT
---
index f4472c61dbb297d61053ef2e8503ad7c56d06148..70cbb2cae7b60a5c6514d0990da91ed5b23e0827 100644 (file)
Authors:
- Martyn Smith <martyn@catalyst.net.nz>
-- Martin Langhoff <martin@catalyst.net.nz>
+- Martin Langhoff <martin@laptop.org>
with ideas and patches from participants of the git-list <git@vger.kernel.org>.
Documentation
--------------
-Documentation by Martyn Smith <martyn@catalyst.net.nz>, Martin Langhoff <martin@catalyst.net.nz>, and Matthias Urlichs <smurf@smurf.noris.de>.
+Documentation by Martyn Smith <martyn@catalyst.net.nz>, Martin Langhoff <martin@laptop.org>, and Matthias Urlichs <smurf@smurf.noris.de>.
GIT
---
index 2f0ddf6fe817ab7b59fb5f5276ba688447661ee3..d15cb6a845645d7fe76e7782ce580b3678bd25e4 100644 (file)
--inetd::
Have the server run as an inetd service. Implies --syslog.
- Incompatible with --port, --listen, --user and --group options.
+ Incompatible with --detach, --port, --listen, --user and --group
+ options.
--listen=<host_or_ipaddr>::
Listen on a specific IP address or hostname. IP addresses can
be either an IPv4 address or an IPv6 address if supported. If IPv6
is not supported, then --listen=hostname is also not supported and
--listen must be given an IPv4 address.
+ Can be given more than once.
Incompatible with '--inetd' option.
--port=<n>::
index 7ef9d51577594ae6a27f71bae251d6ee2f52befa..02e015ad9c41e760b3ab37d2c5a9e4ef9ef0c4f5 100644 (file)
--all::
Instead of using only the annotated tags, use any ref
found in `.git/refs/`. This option enables matching
- any known branch, remote branch, or lightweight tag.
+ any known branch, remote-tracking branch, or lightweight tag.
--tags::
Instead of using only the annotated tags, use any tag
index dd1fb32786f6f467a1c46f2643fbc54d0390f2ec..49105102dbf899c62833572fdda51cefaed53476 100644 (file)
SYNOPSIS
--------
-'git diff' [<common diff options>] <commit>{0,2} [--] [<path>...]
+[verse]
+'git diff' [options] [<commit>] [--] [<path>...]
+'git diff' [options] --cached [<commit>] [--] [<path>...]
+'git diff' [options] <commit> <commit> [--] [<path>...]
+'git diff' [options] [--no-index] [--] <path> <path>
DESCRIPTION
-----------
-Show changes between two trees, a tree and the working tree, a
-tree and the index file, or the index file and the working tree.
+Show changes between the working tree and the index or a tree, changes
+between the index and a tree, changes between two trees, or changes
+between two files on disk.
'git diff' [--options] [--] [<path>...]::
commit relative to the named <commit>. Typically you
would want comparison with the latest commit, so if you
do not give <commit>, it defaults to HEAD.
+ If HEAD does not exist (e.g. unborned branches) and
+ <commit> is not given, it shows all staged changes.
--staged is a synonym of --cached.
'git diff' [--options] <commit> [--] [<path>...]::
index 573843211112a6efb7b296bbc5b11894c01dd8cd..db87f1d42382e32d7e3ee94e5c3d0a032b7430e2 100644 (file)
SYNOPSIS
--------
-'git difftool' [<options>] <commit>{0,2} [--] [<path>...]
+'git difftool' [<options>] [<commit> [<commit>]] [--] [<path>...]
DESCRIPTION
-----------
'git difftool' is a git command that allows you to compare and edit files
between revisions using common diff tools. 'git difftool' is a frontend
-to 'git diff' and accepts the same options and arguments.
+to 'git diff' and accepts the same options and arguments. See
+linkgit:git-diff[1].
OPTIONS
-------
index 4349d6a9a648fd9694ca29dc3608facb3d67f449..e1b7a0f9ec88deab1711d99293e23d4396a7bc0c 100644 (file)
set of marks. If a mark is defined to different values,
the last file wins.
+--import-marks-if-exists=<file>::
+ Like --import-marks but instead of erroring out, silently
+ skips the file if it does not exist.
+
--relative-marks::
After specifying --relative-marks= the paths specified
with --import-marks= and --export-marks= are relative
--(no-)-relative-marks= with the --(import|export)-marks=
options.
+--cat-blob-fd=<fd>::
+ Specify the file descriptor that will be written to
+ when the `cat-blob` command is encountered in the stream.
+ The default behaviour is to write to `stdout`.
+
--export-pack-edges=<file>::
After creating a packfile, print a line of data to
<file> listing the filename of the packfile and the last
Ruby is being used.
fast-import is very strict about its input. Where we say SP below we mean
-*exactly* one space. Likewise LF means one (and only one) linefeed.
+*exactly* one space. Likewise LF means one (and only one) linefeed
+and HT one (and only one) horizontal tab.
Supplying additional whitespace characters will cause unexpected
results, such as branch names or file names with leading or trailing
spaces in their name, or early termination of fast-import when it encounters
standard output. This command is optional and is not needed
to perform an import.
+`cat-blob`::
+ Causes fast-import to print a blob in 'cat-file --batch'
+ format to the file descriptor set with `--cat-blob-fd` or
+ `stdout` if unspecified.
+
+`ls`::
+ Causes fast-import to print a line describing a directory
+ entry in 'ls-tree' format to the file descriptor set with
+ `--cat-blob-fd` or `stdout` if unspecified.
+
`feature`::
Require that fast-import supports the specified feature, or
abort if it does not.
* contain the special component `.` or `..` (e.g. `foo/./bar` and
`foo/../bar` are invalid).
+The root of the tree can be represented by an empty string as `<path>`.
+
It is recommended that `<path>` always be encoded using UTF-8.
`filedelete`
inform the reader when the `checkpoint` has been completed and it
can safely access the refs that fast-import updated.
+`cat-blob`
+~~~~~~~~~~
+Causes fast-import to print a blob to a file descriptor previously
+arranged with the `--cat-blob-fd` argument. The command otherwise
+has no impact on the current import; its main purpose is to
+retrieve blobs that may be in fast-import's memory but not
+accessible from the target repository.
+
+....
+ 'cat-blob' SP <dataref> LF
+....
+
+The `<dataref>` can be either a mark reference (`:<idnum>`)
+set previously or a full 40-byte SHA-1 of a Git blob, preexisting or
+ready to be written.
+
+Output uses the same format as `git cat-file --batch`:
+
+====
+ <sha1> SP 'blob' SP <size> LF
+ <contents> LF
+====
+
+This command can be used anywhere in the stream that comments are
+accepted. In particular, the `cat-blob` command can be used in the
+middle of a commit but not in the middle of a `data` command.
+
+`ls`
+~~~~
+Prints information about the object at a path to a file descriptor
+previously arranged with the `--cat-blob-fd` argument. This allows
+printing a blob from the active commit (with `cat-blob`) or copying a
+blob or tree from a previous commit for use in the current one (with
+`filemodify`).
+
+The `ls` command can be used anywhere in the stream that comments are
+accepted, including the middle of a commit.
+
+Reading from the active commit::
+ This form can only be used in the middle of a `commit`.
+ The path names a directory entry within fast-import's
+ active commit. The path must be quoted in this case.
++
+....
+ 'ls' SP <path> LF
+....
+
+Reading from a named tree::
+ The `<dataref>` can be a mark reference (`:<idnum>`) or the
+ full 40-byte SHA-1 of a Git tag, commit, or tree object,
+ preexisting or waiting to be written.
+ The path is relative to the top level of the tree
+ named by `<dataref>`.
++
+....
+ 'ls' SP <dataref> SP <path> LF
+....
+
+See `filemodify` above for a detailed description of `<path>`.
+
+Output uses the same format as `git ls-tree <tree> {litdd} <path>`:
+
+====
+ <mode> SP ('blob' | 'tree' | 'commit') SP <dataref> HT <path> LF
+====
+
+The <dataref> represents the blob, tree, or commit object at <path>
+and can be used in later 'cat-blob', 'filemodify', or 'ls' commands.
+
+If there is no file or subtree at that path, 'git fast-import' will
+instead report
+
+====
+ missing SP <path> LF
+====
+
`feature`
~~~~~~~~~
Require that fast-import supports the specified feature, or abort if
second, an --import-marks= command-line option overrides
any "feature import-marks" command in the stream.
+cat-blob::
+ls::
+ Require that the backend support the 'cat-blob' or 'ls' command.
+ Versions of fast-import not supporting the specified command
+ will exit with a message indicating so.
+ This lets the import error out early with a clear message,
+ rather than wasting time on the early part of an import
+ before the unsupported command is detected.
+
notes::
Require that the backend support the 'notemodify' (N)
subcommand to the 'commit' command.
Versions of fast-import not supporting notes will exit
with a message indicating so.
+
`option`
~~~~~~~~
Processes the specified option so that git fast-import behaves in a
* date-format
* import-marks
* export-marks
+* cat-blob-fd
* force
Crash Reports
projects with 2,000+ branches and 45,114+ files in a very limited
memory footprint (less than 2.7 MiB per active branch).
+Signals
+-------
+Sending *SIGUSR1* to the 'git fast-import' process ends the current
+packfile early, simulating a `checkpoint` command. The impatient
+operator can use this facility to peek at the objects and refs from an
+import in progress, at the cost of some added running time and worse
+compression.
Author
------
index d159e88292cbab575be8153b381fffd1f4c85b6b..c76e313923f6a6655ad69209070e8333ca360678 100644 (file)
in `.git/FETCH_HEAD`. This information is left for a later merge
operation done by 'git merge'.
-When <refspec> stores the fetched result in tracking branches,
+When <refspec> stores the fetched result in remote-tracking branches,
the tags that point at these branches are automatically
followed. This is done by first fetching from the remote using
the given <refspec>s, and if the repository has objects that are
index 302f56b88941cfaa002a8b75c078335bf8d86276..75adf7a502e18aa11e7a8424acf217b4bf6be64c 100644 (file)
SYNOPSIS
--------
[verse]
-'git fmt-merge-msg' [-m <message>] [--log | --no-log] <$GIT_DIR/FETCH_HEAD
-'git fmt-merge-msg' [-m <message>] [--log | --no-log] -F <file>
+'git fmt-merge-msg' [-m <message>] [--log[=<n>] | --no-log] <$GIT_DIR/FETCH_HEAD
+'git fmt-merge-msg' [-m <message>] [--log[=<n>] | --no-log] -F <file>
DESCRIPTION
-----------
OPTIONS
-------
---log::
+--log[=<n>]::
In addition to branch names, populate the log message with
one-line descriptions from the actual commits that are being
- merged.
+ merged. At most <n> commits from each merge parent will be
+ used (20 if <n> is omitted). This overrides the `merge.log`
+ configuration variable.
--no-log::
Do not list one-line descriptions from the actual commits being
-------------
merge.log::
- Whether to include summaries of merged commits in newly
- merge commit messages. False by default.
+ In addition to branch names, populate the log message with at
+ most the specified number of one-line descriptions from the
+ actual commits that are being merged. Defaults to false, and
+ true is a synonym for 20.
merge.summary::
Synonym to `merge.log`; this is deprecated and will be removed in
index a00b783fe5dd7a074fc70f9aed85e6e45189ac8f..9dcafc6d4442b6db66df642e92336537395b9919 100644 (file)
include::diff-options.txt[]
-<n>::
- Limits the number of patches to prepare.
+ Prepare patches from the topmost <n> commits.
-o <dir>::
--output-directory <dir>::
index a01eef6763d8c0383121019e9c33c869f0300acf..26632414b2646ba80a17fa861d422a9c8c957810 100644 (file)
--- a/Documentation/git-gc.txt
+++ b/Documentation/git-gc.txt
them sooner. This option defaults to '30 days'.
The above two configuration variables can be given to a pattern. For
-example, this sets non-default expiry values only to remote tracking
+example, this sets non-default expiry values only to remote-tracking
branches:
------------
'git gc' tries very hard to be safe about the garbage it collects. In
particular, it will keep not only objects referenced by your current set
-of branches and tags, but also objects referenced by the index, remote
-tracking branches, refs saved by 'git filter-branch' in
+of branches and tags, but also objects referenced by the index,
+remote-tracking branches, refs saved by 'git filter-branch' in
refs/original/, or reflogs (which may reference commits in branches
that were later amended or rewound).
index 6d40f0011b0fd297e5676c8479e46989d7f47696..ff41784c60b04a276931fc90ab54a22a67024a1e 100644 (file)
git log --branches --not --remotes=origin::
Shows all commits that are in any of local branches but not in
- any of remote tracking branches for 'origin' (what you have that
+ any of remote-tracking branches for 'origin' (what you have that
origin doesn't).
git log master --not --remotes=*/master::
index d43416d299a7c028a88e8ddce89536c2c64e65c9..c1efaaa5c52cd93e90a1a7427125e470f771146a 100644 (file)
[-s <strategy>] [-X <strategy-option>]
[--[no-]rerere-autoupdate] [-m <msg>] <commit>...
'git merge' <msg> HEAD <commit>...
+'git merge' --abort
DESCRIPTION
-----------
historical reasons. Do not use it from the command line or in
new scripts. It is the same as `git merge -m <msg> <commit>...`.
+The third syntax ("`git merge --abort`") can only be run after the
+merge has resulted in conflicts. 'git merge --abort' will abort the
+merge process and try to reconstruct the pre-merge state. However,
+if there were uncommitted changes when the merge started (and
+especially if those changes were further modified after the merge
+was started), 'git merge --abort' will in some cases be unable to
+reconstruct the original (pre-merge) changes. Therefore:
+
*Warning*: Running 'git merge' with uncommitted changes is
discouraged: while possible, it leaves you in a state that is hard to
back out of in the case of a conflict.
Allow the rerere mechanism to update the index with the
result of auto-conflict resolution if possible.
+--abort::
+ Abort the current conflict resolution process, and
+ try to reconstruct the pre-merge state.
++
+If there were uncommitted worktree changes present when the merge
+started, 'git merge --abort' will in some cases be unable to
+reconstruct these changes. It is therefore recommended to always
+commit or stash your changes before running 'git merge'.
++
+'git merge --abort' is equivalent to 'git reset --merge' when
+`MERGE_HEAD` is present.
+
<commit>...::
Commits, usually other branch heads, to merge into our branch.
You need at least one <commit>. Specifying more than one
i.e. matching `HEAD`.
If you tried a merge which resulted in complex conflicts and
-want to start over, you can recover with `git reset --merge`.
+want to start over, you can recover with `git merge --abort`.
HOW CONFLICTS ARE PRESENTED
---------------------------
* Decide not to merge. The only clean-ups you need are to reset
the index file to the `HEAD` commit to reverse 2. and to clean
- up working tree changes made by 2. and 3.; `git-reset --hard` can
- be used for this.
+ up working tree changes made by 2. and 3.; `git merge --abort`
+ can be used for this.
* Resolve the conflicts. Git will mark the conflicts in
the working tree. Edit the files into shape and
index 2981d8c5efd4e7e0f65d051cfe3b28d3430e3213..296f314eae5af684e68965f4b04bd9acbd4c35a1 100644 (file)
'git notes' append [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
'git notes' edit [<object>]
'git notes' show [<object>]
+'git notes' merge [-v | -q] [-s <strategy> ] <notes_ref>
+'git notes' merge --commit [-v | -q]
+'git notes' merge --abort [-v | -q]
'git notes' remove [<object>]
'git notes' prune [-n | -v]
+'git notes' get-ref
DESCRIPTION
show::
Show the notes for a given object (defaults to HEAD).
+merge::
+ Merge the given notes ref into the current notes ref.
+ This will try to merge the changes made by the given
+ notes ref (called "remote") since the merge-base (if
+ any) into the current notes ref (called "local").
++
+If conflicts arise and a strategy for automatically resolving
+conflicting notes (see the -s/--strategy option) is not given,
+the "manual" resolver is used. This resolver checks out the
+conflicting notes in a special worktree (`.git/NOTES_MERGE_WORKTREE`),
+and instructs the user to manually resolve the conflicts there.
+When done, the user can either finalize the merge with
+'git notes merge --commit', or abort the merge with
+'git notes merge --abort'.
+
remove::
Remove the notes for a given object (defaults to HEAD).
This is equivalent to specifying an empty note message to
prune::
Remove all notes for non-existing/unreachable objects.
+get-ref::
+ Print the current notes ref. This provides an easy way to
+ retrieve the current notes ref (e.g. from scripts).
+
OPTIONS
-------
-f::
Do not remove anything; just report the object names whose notes
would be removed.
+-s <strategy>::
+--strategy=<strategy>::
+ When merging notes, resolve notes conflicts using the given
+ strategy. The following strategies are recognized: "manual"
+ (default), "ours", "theirs", "union" and "cat_sort_uniq".
+ See the "NOTES MERGE STRATEGIES" section below for more
+ information on each notes merge strategy.
+
+--commit::
+ Finalize an in-progress 'git notes merge'. Use this option
+ when you have resolved the conflicts that 'git notes merge'
+ stored in .git/NOTES_MERGE_WORKTREE. This amends the partial
+ merge commit created by 'git notes merge' (stored in
+ .git/NOTES_MERGE_PARTIAL) by adding the notes in
+ .git/NOTES_MERGE_WORKTREE. The notes ref stored in the
+ .git/NOTES_MERGE_REF symref is updated to the resulting commit.
+
+--abort::
+ Abort/reset a in-progress 'git notes merge', i.e. a notes merge
+ with conflicts. This simply removes all files related to the
+ notes merge.
+
+-q::
+--quiet::
+ When merging notes, operate quietly.
+
-v::
--verbose::
- Report all object names whose notes are removed.
+ When merging notes, be more verbose.
+ When pruning notes, report all object names whose notes are
+ removed.
DISCUSSION
`git log -p -g <refname>`.
+NOTES MERGE STRATEGIES
+----------------------
+
+The default notes merge strategy is "manual", which checks out
+conflicting notes in a special work tree for resolving notes conflicts
+(`.git/NOTES_MERGE_WORKTREE`), and instructs the user to resolve the
+conflicts in that work tree.
+When done, the user can either finalize the merge with
+'git notes merge --commit', or abort the merge with
+'git notes merge --abort'.
+
+"ours" automatically resolves conflicting notes in favor of the local
+version (i.e. the current notes ref).
+
+"theirs" automatically resolves notes conflicts in favor of the remote
+version (i.e. the given notes ref being merged into the current notes
+ref).
+
+"union" automatically resolves notes conflicts by concatenating the
+local and remote versions.
+
+"cat_sort_uniq" is similar to "union", but in addition to concatenating
+the local and remote versions, this strategy also sorts the resulting
+lines, and removes duplicate lines from the result. This is equivalent
+to applying the "cat | sort | uniq" shell pipeline to the local and
+remote versions. This strategy is useful if the notes follow a line-based
+format where one wants to avoid duplicated lines in the merge result.
+Note that if either the local or remote version contain duplicate lines
+prior to the merge, these will also be removed by this notes merge
+strategy.
+
+
EXAMPLES
--------
index 64009dee31e22de815c99d641fb90b49901d9ede..b33e6be872508778faa59f478229cd6e16bddad7 100644 (file)
<repository> should be the name of a remote repository as
passed to linkgit:git-fetch[1]. <refspec> can name an
arbitrary remote ref (for example, the name of a tag) or even
-a collection of refs with corresponding remote tracking branches
+a collection of refs with corresponding remote-tracking branches
(e.g., refs/heads/{asterisk}:refs/remotes/origin/{asterisk}),
but usually it is the name of a branch in the remote repository.
--verbose::
Pass --verbose to git-fetch and git-merge.
+--[no-]recurse-submodules::
+ This option controls if new commits of all populated submodules should
+ be fetched too (see linkgit:git-config[1] and linkgit:gitmodules[5]).
+ That might be necessary to get the data needed for merging submodule
+ commits, a feature git learned in 1.7.3. Notice that the result of a
+ merge will not be checked out in the submodule, "git submodule update"
+ has to be called afterwards to bring the work tree up to date with the
+ merge result.
+
Options related to merging
~~~~~~~~~~~~~~~~~~~~~~~~~~
in `$GIT_DIR/remotes/<origin>` file is used.
In order to determine what remote branches to fetch (and
-optionally store in the tracking branches) when the command is
+optionally store in the remote-tracking branches) when the command is
run without any refspec parameters on the command line, values
of the configuration variable `remote.<origin>.fetch` are
consulted, and if there aren't any, `$GIT_DIR/remotes/<origin>`
------------
A globbing refspec must have a non-empty RHS (i.e. must store
-what were fetched in tracking branches), and its LHS and RHS
+what were fetched in remote-tracking branches), and its LHS and RHS
must end with `/*`. The above specifies that all remote
-branches are tracked using tracking branches in
+branches are tracked using remote-tracking branches in
`refs/remotes/origin/` hierarchy under the same name.
The rule to determine which remote branch to merge after
index 2e78da448f30ad260cb7683a5610f424d0c5ffea..634423a69ee6a4f2e9c7dd3941fd5160fbf6c99f 100644 (file)
'git read-tree' [[-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>]
[-u [--exclude-per-directory=<gitignore>] | -i]]
[--index-output=<file>] [--no-sparse-checkout]
- <tree-ish1> [<tree-ish2> [<tree-ish3>]]
+ (--empty | <tree-ish1> [<tree-ish2> [<tree-ish3>]])
DESCRIPTION
Disable sparse checkout support even if `core.sparseCheckout`
is true.
+--empty::
+ Instead of reading tree object(s) into the index, just empty
+ it.
+
<tree-ish#>::
The id of the tree object(s) to be read/merged.
support.
-BUGS
-----
-In order to match a directory with $GIT_DIR/info/sparse-checkout,
-trailing slash must be used. The form without trailing slash, while
-works with .gitignore, does not work with sparse checkout.
-
-
SEE ALSO
--------
linkgit:git-write-tree[1]; linkgit:git-ls-files[1];
index 30e5c0eb14470a5f72174b1071eac79c55fb0c41..96680c84561c434c4cf3a00cfe958b21f18a3d8d 100644 (file)
--no-verify::
This option bypasses the pre-rebase hook. See also linkgit:githooks[5].
+--verify::
+ Allows the pre-rebase hook to run, which is the default. This option can
+ be used to override --no-verify. See also linkgit:githooks[5].
+
-C<n>::
Ensure at least <n> lines of surrounding context match before
and after each change. When fewer lines of surrounding
diff --git a/Documentation/git-remote-ext.txt b/Documentation/git-remote-ext.txt
--- /dev/null
@@ -0,0 +1,125 @@
+git-remote-ext(1)
+=================
+
+NAME
+----
+git-remote-ext - Bridge smart transport to external command.
+
+SYNOPSIS
+--------
+git remote add <nick> "ext::<command>[ <arguments>...]"
+
+DESCRIPTION
+-----------
+This remote helper uses the specified '<command>' to connect
+to a remote git server.
+
+Data written to stdin of the specified '<command>' is assumed
+to be sent to a git:// server, git-upload-pack, git-receive-pack
+or git-upload-archive (depending on situation), and data read
+from stdout of <command> is assumed to be received from
+the same service.
+
+Command and arguments are separated by an unescaped space.
+
+The following sequences have a special meaning:
+
+'% '::
+ Literal space in command or argument.
+
+'%%'::
+ Literal percent sign.
+
+'%s'::
+ Replaced with name (receive-pack, upload-pack, or
+ upload-archive) of the service git wants to invoke.
+
+'%S'::
+ Replaced with long name (git-receive-pack,
+ git-upload-pack, or git-upload-archive) of the service
+ git wants to invoke.
+
+'%G' (must be the first characters in an argument)::
+ This argument will not be passed to '<command>'. Instead, it
+ will cause the helper to start by sending git:// service requests to
+ the remote side with the service field set to an appropriate value and
+ the repository field set to rest of the argument. Default is not to send
+ such a request.
++
+This is useful if remote side is git:// server accessed over
+some tunnel.
+
+'%V' (must be first characters in argument)::
+ This argument will not be passed to '<command>'. Instead it sets
+ the vhost field in the git:// service request (to rest of the argument).
+ Default is not to send vhost in such request (if sent).
+
+ENVIRONMENT VARIABLES:
+----------------------
+
+GIT_TRANSLOOP_DEBUG::
+ If set, prints debugging information about various reads/writes.
+
+ENVIRONMENT VARIABLES PASSED TO COMMAND:
+----------------------------------------
+
+GIT_EXT_SERVICE::
+ Set to long name (git-upload-pack, etc...) of service helper needs
+ to invoke.
+
+GIT_EXT_SERVICE_NOPREFIX::
+ Set to long name (upload-pack, etc...) of service helper needs
+ to invoke.
+
+
+EXAMPLES:
+---------
+This remote helper is transparently used by git when
+you use commands such as "git fetch <URL>", "git clone <URL>",
+, "git push <URL>" or "git remote add <nick> <URL>", where <URL>
+begins with `ext::`. Examples:
+
+"ext::ssh -i /home/foo/.ssh/somekey user@host.example %S 'foo/repo'"::
+ Like host.example:foo/repo, but use /home/foo/.ssh/somekey as
+ keypair and user as user on remote side. This avoids needing to
+ edit .ssh/config.
+
+"ext::socat -t3600 - ABSTRACT-CONNECT:/git-server %G/somerepo"::
+ Represents repository with path /somerepo accessable over
+ git protocol at abstract namespace address /git-server.
+
+"ext::git-server-alias foo %G/repo"::
+ Represents a repository with path /repo accessed using the
+ helper program "git-server-alias foo". The path to the
+ repository and type of request are not passed on the command
+ line but as part of the protocol stream, as usual with git://
+ protocol.
+
+"ext::git-server-alias foo %G/repo %Vfoo"::
+ Represents a repository with path /repo accessed using the
+ helper program "git-server-alias foo". The hostname for the
+ remote server passed in the protocol stream will be "foo"
+ (this allows multiple virtual git servers to share a
+ link-level address).
+
+"ext::git-server-alias foo %G/repo% with% spaces %Vfoo"::
+ Represents a repository with path '/repo with spaces' accessed
+ using the helper program "git-server-alias foo". The hostname for
+ the remote server passed in the protocol stream will be "foo"
+ (this allows multiple virtual git servers to share a
+ link-level address).
+
+"ext::git-ssl foo.example /bar"::
+ Represents a repository accessed using the helper program
+ "git-ssl foo.example /bar". The type of request can be
+ determined by the helper using environment variables (see
+ above).
+
+Documentation
+--------------
+Documentation by Ilari Liusvaara, Jonathan Nieder and the git list
+<git@vger.kernel.org>
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Documentation/git-remote-fd.txt b/Documentation/git-remote-fd.txt
--- /dev/null
@@ -0,0 +1,59 @@
+git-remote-fd(1)
+================
+
+NAME
+----
+git-remote-fd - Reflect smart transport stream back to caller
+
+SYNOPSIS
+--------
+"fd::<infd>[,<outfd>][/<anything>]" (as URL)
+
+DESCRIPTION
+-----------
+This helper uses specified file descriptors to connect to a remote git server.
+This is not meant for end users but for programs and scripts calling git
+fetch, push or archive.
+
+If only <infd> is given, it is assumed to be a bidirectional socket connected
+to remote git server (git-upload-pack, git-receive-pack or
+git-upload-achive). If both <infd> and <outfd> are given, they are assumed
+to be pipes connected to a remote git server (<infd> being the inbound pipe
+and <outfd> being the outbound pipe.
+
+It is assumed that any handshaking procedures have already been completed
+(such as sending service request for git://) before this helper is started.
+
+<anything> can be any string. It is ignored. It is meant for providing
+information to user in the URL in case that URL is displayed in some
+context.
+
+ENVIRONMENT VARIABLES
+---------------------
+GIT_TRANSLOOP_DEBUG::
+ If set, prints debugging information about various reads/writes.
+
+EXAMPLES
+--------
+git fetch fd::17 master::
+ Fetch master, using file descriptor #17 to communicate with
+ git-upload-pack.
+
+git fetch fd::17/foo master::
+ Same as above.
+
+git push fd::7,8 master (as URL)::
+ Push master, using file descriptor #7 to read data from
+ git-receive-pack and file descriptor #8 to write data to
+ same service.
+
+git push fd::7,8/bar master::
+ Same as above.
+
+Documentation
+--------------
+Documentation by Ilari Liusvaara and the git list <git@vger.kernel.org>
+
+GIT
+---
+Part of the linkgit:git[1] suite
index 3a23477ce72763b562258a25c3777dd57ac1a962..51de895822d6d79f970012c835d68b2cd8c8e136 100644 (file)
OPTIONS
-------
-'option verbosity' <N>::
+'option verbosity' <n>::
Changes the verbosity of messages displayed by the helper.
- A value of 0 for N means that processes operate
+ A value of 0 for <n> means that processes operate
quietly, and the helper produces only error output.
1 is the default level of verbosity, and higher values
- of N correspond to the number of -v flags passed on the
+ of <n> correspond to the number of -v flags passed on the
command line.
'option progress' \{'true'|'false'\}::
index 0d28febe1b44fff289b772a0e0fb6e74a010b9eb..c258ea48dbb8889a48f3075ab171ebddfd7fda3b 100644 (file)
'rename'::
-Rename the remote named <old> to <new>. All remote tracking branches and
+Rename the remote named <old> to <new>. All remote-tracking branches and
configuration settings for the remote are updated.
+
In case <old> and <new> are the same, and <old> is a file under
'rm'::
-Remove the remote named <name>. All remote tracking branches and
+Remove the remote named <name>. All remote-tracking branches and
configuration settings for the remote are removed.
'set-head'::
'prune'::
-Deletes all stale tracking branches under <name>.
+Deletes all stale remote-tracking branches under <name>.
These stale branches have already been removed from the remote repository
referenced by <name>, but are still locally available in
"remotes/<name>".
index fd72976371cee0fcd25e3419138eb13382dc6ab8..927ecee2f26cdf0bd30457d5016b654c01a23a14 100644 (file)
but carries forward unmerged index entries.
--keep::
- Resets the index, updates files in the working tree that are
- different between <commit> and HEAD, but keeps those
- which are different between HEAD and the working tree (i.e.
- which have local changes).
+ Resets index entries and updates files in the working tree that are
+ different between <commit> and HEAD.
If a file that is different between <commit> and HEAD has local changes,
reset is aborted.
-+
-In other words, --keep does a 2-way merge between <commit> and HEAD followed by
-'git reset --mixed <commit>'.
--
If you want to undo a commit other than the latest on a branch,
index 4a27643c1ea0e908a0a54b84f668d5a80f6d7100..ff23cb0219d602803a83da273195fcc4ca71b5f5 100644 (file)
directory (typically a sequence of "../", or an empty string).
--git-dir::
- Show `$GIT_DIR` if defined else show the path to the .git directory.
+ Show `$GIT_DIR` if defined. Otherwise show the path to
+ the .git directory, relative to the current directory.
++
+If `$GIT_DIR` is not defined and the current directory
+is not detected to lie in a git repository or work tree
+print a message to stderr and exit with nonzero status.
--is-inside-git-dir::
When the current working directory is below the repository
index 752fc88e768e4b8e1afddad4bdd7833cf20978c7..45be851750c2ce81500765f6dfbd23578c68abda 100644 (file)
--signoff::
Add Signed-off-by line at the end of the commit message.
+--strategy=<strategy>::
+ Use the given merge strategy. Should only be used once.
+ See the MERGE STRATEGIES section in linkgit:git-merge[1]
+ for details.
+
+-X<option>::
+--strategy-option=<option>::
+ Pass the merge strategy-specific option through to the
+ merge strategy. See linkgit:git-merge[1] for details.
+
EXAMPLES
--------
git revert HEAD~3::
index adbca12b1ec173dc8e69c4c354b3df4e7682767a..7ec9dabe6815f2b8ff03048043e1cfd81cd19fcc 100644 (file)
set, as returned by "git var -l".
--in-reply-to=<identifier>::
- Specify the contents of the first In-Reply-To header.
- Subsequent emails will refer to the previous email
- instead of this if --chain-reply-to is set.
- Only necessary if --compose is also set. If --compose
- is not set, this will be prompted for.
+ Make the first mail (or all the mails with `--no-thread`) appear as a
+ reply to the given Message-Id, which avoids breaking threads to
+ provide a new patch series.
+ The second and subsequent emails will be sent as replies according to
+ the `--[no]-chain-reply-to` setting.
++
+So for example when `--thread` and `--no-chain-reply-to` are specified, the
+second and subsequent patches will be replies to the first one like in the
+illustration below where `[PATCH v2 0/3]` is in reply to `[PATCH 0/2]`:
++
+ [PATCH 0/2] Here is what I did...
+ [PATCH 1/2] Clean up and tests
+ [PATCH 2/2] Implementation
+ [PATCH v2 0/3] Here is a reroll
+ [PATCH v2 1/3] Clean up
+ [PATCH v2 2/3] New tests
+ [PATCH v2 3/3] Implementation
++
+Only necessary if --compose is also set. If --compose
+is not set, this will be prompted for.
--subject=<string>::
Specify the initial subject of the email thread.
Specify the primary recipient of the emails generated. Generally, this
will be the upstream maintainer of the project involved. Default is the
value of the 'sendemail.to' configuration value; if that is unspecified,
- this will be prompted for.
+ and --to-cmd is not specified, this will be prompted for.
+
The --to option must be repeated for each user you want on the to list.
are also accepted. The port can also be set with the
'sendemail.smtpserverport' configuration variable.
+--smtp-server-option=<option>::
+ If set, specifies the outgoing SMTP server option to use.
+ Default value can be specified by the 'sendemail.smtpserveroption'
+ configuration option.
++
+The --smtp-server-option option must be repeated for each option you want
+to pass to the server. Likewise, different lines in the configuration files
+must be used for each option.
+
--smtp-ssl::
Legacy alias for '--smtp-encryption ssl'.
Automating
~~~~~~~~~~
+--to-cmd=<command>::
+ Specify a command to execute once per patch file which
+ should generate patch file specific "To:" entries.
+ Output of this command must be single email address per line.
+ Default is the value of 'sendemail.tocmd' configuration value.
+
--cc-cmd=<command>::
Specify a command to execute once per patch file which
should generate patch file specific "Cc:" entries.
index 0f3ad811cfa41e65a3d807a5eb766ce2a66a7831..6403126a029bf43acaa219296353f7ab1f2040a0 100644 (file)
NAME
----
-git-shell - Restricted login shell for GIT-only SSH access
+git-shell - Restricted login shell for Git-only SSH access
SYNOPSIS
--------
-'$(git --exec-path)/git-shell' -c <command> <argument>
+'git shell' [-c <command> <argument>]
DESCRIPTION
-----------
-This is meant to be used as a login shell for SSH accounts you want
-to restrict to GIT pull/push access only. It permits execution only
-of server-side GIT commands implementing the pull/push functionality.
-The commands can be executed only by the '-c' option; the shell is not
-interactive.
-
-Currently, only four commands are permitted to be called, 'git-receive-pack'
-'git-upload-pack' and 'git-upload-archive' with a single required argument, or
-'cvs server' (to invoke 'git-cvsserver').
+
+A login shell for SSH accounts to provide restricted Git access. When
+'-c' is given, the program executes <command> non-interactively;
+<command> can be one of 'git receive-pack', 'git upload-pack', 'git
+upload-archive', 'cvs server', or a command in COMMAND_DIR. The shell
+is started in interactive mode when no arguments are given; in this
+case, COMMAND_DIR must exist, and any of the executables in it can be
+invoked.
+
+'cvs server' is a special command which executes git-cvsserver.
+
+COMMAND_DIR is the path "$HOME/git-shell-commands". The user must have
+read and execute permissions to the directory in order to execute the
+programs in it. The programs are executed with a cwd of $HOME, and
+<argument> is parsed as a command-line string.
Author
------
index dae190a5f21d3835024ce263bf4bd9b08048cfa6..5102a23f8ee30fcff2a6e67d098495329a2fca1d 100644 (file)
-u[<mode>]::
--untracked-files[=<mode>]::
- Show untracked files (Default: 'all').
+ Show untracked files.
+
-The mode parameter is optional, and is used to specify
-the handling of untracked files. The possible options are:
+The mode parameter is optional (defaults to 'all'), and is used to
+specify the handling of untracked files; when -u is not used, the
+default is 'normal', i.e. show untracked files and directories.
++
+The possible options are:
+
---
- 'no' - Show no untracked files
- 'normal' - Shows untracked files and directories
- 'all' - Also shows individual files in untracked directories.
---
+
-See linkgit:git-config[1] for configuration variable
-used to change the default for when the option is not
-specified.
+The default can be changed using the status.showUntrackedFiles
+configuration variable documented in linkgit:git-config[1].
--ignore-submodules[=<when>]::
Ignore changes to submodules when looking for changes. <when> can be
index 139d314ba54c4e7897ffcf257a1fd847eb7939fa..e161a40a73cb57557435e94c9444935da0828f1c 100644 (file)
Set the 'rewriteRoot' option in the [svn-remote] config.
--rewrite-uuid=<UUID>;;
Set the 'rewriteUUID' option in the [svn-remote] config.
---username=<USER>;;
+--username=<user>;;
For transports that SVN handles authentication for (http,
https, and plain svn), specify the username. For other
transports (eg svn+ssh://), you must include the username in
Only used with the 'init' command.
These are passed directly to 'git init'.
--r <ARG>::
---revision <ARG>::
+-r <arg>::
+--revision <arg>::
Used with the 'fetch' command.
+
This allows revision ranges for partial/cauterized history
reports and archives. If you plan to eventually migrate from SVN to git
and are certain about dropping SVN history, consider
linkgit:git-filter-branch[1] instead. filter-branch also allows
-reformating of metadata for ease-of-reading and rewriting authorship
+reformatting of metadata for ease-of-reading and rewriting authorship
info for non-"svn.authorsFile" users.
svn.useSvmProps::
cd project
git init
git remote add origin server:/pub/project
- git config --add remote.origin.fetch '+refs/remotes/*:refs/remotes/*'
+ git config --replace-all remote.origin.fetch '+refs/remotes/*:refs/remotes/*'
git fetch
+# Prevent fetch/pull from remote git server in the future,
+# we only want to use git svn for future updates
+ git config --remove-section remote.origin
# Create a local branch from one of the branches just fetched
git checkout -b master FETCH_HEAD
# Initialize 'git svn' locally (be sure to use the same URL and -T/-b/-t options as were used on server)
index 31c78a81e094ffc8a66639119eadefbd1e760e35..65f76c544086458ec84322c73319d8b071288a0b 100644 (file)
DESCRIPTION
-----------
-Adds a tag reference in `.git/refs/tags/`.
+Add a tag reference in `.git/refs/tags/`, unless `-d/-l/-v` is given
+to delete, list or verify tags.
-Unless `-f` is given, the tag must not yet exist in
+Unless `-f` is given, the tag to be created must not yet exist in the
`.git/refs/tags/` directory.
If one of `-a`, `-s`, or `-u <key-id>` is passed, the command
-creates a 'tag' object, and requires the tag message. Unless
+creates a 'tag' object, and requires a tag message. Unless
`-m <msg>` or `-F <file>` is given, an editor is started for the user to type
in the tag message.
If `-m <msg>` or `-F <file>` is given and `-a`, `-s`, and `-u <key-id>`
are absent, `-a` is implied.
-Otherwise just the SHA1 object name of the commit object is
-written (i.e. a lightweight tag).
+Otherwise just a tag reference for the SHA1 object name of the commit object is
+created (i.e. a lightweight tag).
A GnuPG signed tag object will be created when `-s` or `-u
<key-id>` is used. When `-u <key-id>` is not used, the
~~~~~~~~~~~~~~~~~~~~~~
If you are following somebody else's tree, you are most likely
-using tracking branches (`refs/heads/origin` in traditional
+using remote-tracking branches (`refs/heads/origin` in traditional
layout, or `refs/remotes/origin/master` in the separate-remote
layout). You usually want the tags from the other end.
It may well be that among networking people, they may want to
exchange the tags internal to their group, but in that workflow
they are most likely tracking with each other's progress by
-having tracking branches. Again, the heuristic to automatically
+having remote-tracking branches. Again, the heuristic to automatically
follow such tags is a good thing.
index 916a38aa99020efe281907670932517a66d075e1..0f848de8b29dd32aebf0ab9b21ddddce0b06a5b2 100644 (file)
SYNOPSIS
--------
-'git verify-pack' [-v|--verbose] [--] <pack>.idx ...
+'git verify-pack' [-v|--verbose] [-s|--stat-only] [--] <pack>.idx ...
DESCRIPTION
index dada21242c915148543dda8485b3322894aeb4c5..711219749cac76ff1363ce5f27e28da9a7ecba57 100644 (file)
OPTIONS
-------
+-v::
+--verbose::
+ Print the contents of the tag object before validating it.
+
<tag>...::
SHA1 identifiers of git tag objects.
index 51e8e0af1e7ff37ce4abfe53b6d52e9935b536a8..c0416e5e1a5243ef228173a3a45bb47bf1f3a840 100644 (file)
* firefox (this is the default under X Window when not using KDE)
* iceweasel
+* seamonkey
+* iceape
+* chromium (also supported as chromium-browser)
+* google-chrome (also supported as chrome)
* konqueror (this is the default under KDE, see 'Note about konqueror' below)
+* opera
* w3m (this is the default outside graphical environments)
+* elinks
* links
* lynx
* dillo
diff --git a/Documentation/git.txt b/Documentation/git.txt
index 0c897df6a71ddb5e7f4e960300c2b336ede5aab2..0c32d45248c99e9e4b51f168ba03c829ad96423f 100644 (file)
--- a/Documentation/git.txt
+++ b/Documentation/git.txt
branch of the `git.git` repository.
Documentation for older releases are available here:
-* link:v1.7.3.2/git.html[documentation for release 1.7.3.2]
+* link:v1.7.4.1/git.html[documentation for release 1.7.4.1]
* release notes for
+ link:RelNotes/1.7.4.1.txt[1.7.4.1],
+ link:RelNotes/1.7.4.txt[1.7.4].
+
+* link:v1.7.3.5/git.html[documentation for release 1.7.3.5]
+
+* release notes for
+ link:RelNotes/1.7.3.5.txt[1.7.3.5],
+ link:RelNotes/1.7.3.4.txt[1.7.3.4],
+ link:RelNotes/1.7.3.3.txt[1.7.3.3],
link:RelNotes/1.7.3.2.txt[1.7.3.2],
link:RelNotes/1.7.3.1.txt[1.7.3.1],
link:RelNotes/1.7.3.txt[1.7.3].
-* link:v1.7.2.3/git.html[documentation for release 1.7.2.3]
+* link:v1.7.2.5/git.html[documentation for release 1.7.2.5]
* release notes for
+ link:RelNotes/1.7.2.5.txt[1.7.2.5],
+ link:RelNotes/1.7.2.4.txt[1.7.2.4],
link:RelNotes/1.7.2.3.txt[1.7.2.3],
link:RelNotes/1.7.2.2.txt[1.7.2.2],
link:RelNotes/1.7.2.1.txt[1.7.2.1],
link:RelNotes/1.7.2.txt[1.7.2].
-* link:v1.7.1.2/git.html[documentation for release 1.7.1.2]
+* link:v1.7.1.4/git.html[documentation for release 1.7.1.4]
* release notes for
+ link:RelNotes/1.7.1.4.txt[1.7.1.4],
+ link:RelNotes/1.7.1.3.txt[1.7.1.3],
link:RelNotes/1.7.1.2.txt[1.7.1.2],
link:RelNotes/1.7.1.1.txt[1.7.1.1],
link:RelNotes/1.7.1.txt[1.7.1].
-* link:v1.7.0.7/git.html[documentation for release 1.7.0.7]
+* link:v1.7.0.9/git.html[documentation for release 1.7.0.9]
* release notes for
+ link:RelNotes/1.7.0.9.txt[1.7.0.9],
+ link:RelNotes/1.7.0.8.txt[1.7.0.8],
link:RelNotes/1.7.0.7.txt[1.7.0.7],
link:RelNotes/1.7.0.6.txt[1.7.0.6],
link:RelNotes/1.7.0.5.txt[1.7.0.5],
link:RelNotes/1.7.0.1.txt[1.7.0.1],
link:RelNotes/1.7.0.txt[1.7.0].
-* link:v1.6.6.2/git.html[documentation for release 1.6.6.2]
+* link:v1.6.6.3/git.html[documentation for release 1.6.6.3]
* release notes for
+ link:RelNotes/1.6.6.3.txt[1.6.6.3],
link:RelNotes/1.6.6.2.txt[1.6.6.2],
link:RelNotes/1.6.6.1.txt[1.6.6.1],
link:RelNotes/1.6.6.txt[1.6.6].
-* link:v1.6.5.8/git.html[documentation for release 1.6.5.8]
+* link:v1.6.5.9/git.html[documentation for release 1.6.5.9]
* release notes for
+ link:RelNotes/1.6.5.9.txt[1.6.5.9],
link:RelNotes/1.6.5.8.txt[1.6.5.8],
link:RelNotes/1.6.5.7.txt[1.6.5.7],
link:RelNotes/1.6.5.6.txt[1.6.5.6],
link:RelNotes/1.6.5.1.txt[1.6.5.1],
link:RelNotes/1.6.5.txt[1.6.5].
-* link:v1.6.4.4/git.html[documentation for release 1.6.4.4]
+* link:v1.6.4.5/git.html[documentation for release 1.6.4.5]
* release notes for
+ link:RelNotes/1.6.4.5.txt[1.6.4.5],
link:RelNotes/1.6.4.4.txt[1.6.4.4],
link:RelNotes/1.6.4.3.txt[1.6.4.3],
link:RelNotes/1.6.4.2.txt[1.6.4.2],
path or relative path to current working directory.
--work-tree=<path>::
- Set the path to the working tree. The value will not be
- used in combination with repositories found automatically in
- a .git directory (i.e. $GIT_DIR is not set).
+ Set the path to the working tree. It can be an absolute path
+ or a path relative to the current working directory.
This can also be controlled by setting the GIT_WORK_TREE
environment variable and the core.worktree configuration
- variable. It can be an absolute path or relative path to
- the directory specified by --git-dir or GIT_DIR.
- Note: If --git-dir or GIT_DIR are specified but none of
- --work-tree, GIT_WORK_TREE and core.worktree is specified,
- the current working directory is regarded as the top directory
- of your working tree.
+ variable (see core.worktree in linkgit:git-config[1] for a
+ more detailed discussion).
--bare::
Treat the repository as a bare repository. If GIT_DIR
index 4641d49f76b0d0498cc5a66ae2458c6bc89ed424..7e7e12168eb69d43f6d7eea9cde2176e06fe73d5 100644 (file)
precedence), `.gitattributes` file in the same directory as the
path in question, and its parent directories up to the toplevel of the
work tree (the further the directory that contains `.gitattributes`
-is from the path in question, the lower its precedence).
+is from the path in question, the lower its precedence). Finally
+global and system-wide files are considered (they have the lowest
+precedence).
If you wish to affect only a single repository (i.e., to assign
-attributes to files that are particular to one user's workflow), then
+attributes to files that are particular to
+one user's workflow for that repository), then
attributes should be placed in the `$GIT_DIR/info/attributes` file.
Attributes which should be version-controlled and distributed to other
repositories (i.e., attributes of interest to all users) should go into
-`.gitattributes` files.
+`.gitattributes` files. Attributes that should affect all repositories
+for a single user should be placed in a file specified by the
+`core.attributesfile` configuration option (see linkgit:git-config[1]).
+Attributes for all users on a system should be placed in the
+`$(prefix)/etc/gitattributes` file.
Sometimes you would need to override an setting of an attribute
for a path to `unspecified` state. This can be done by listing
smudge filter means that the clean filter _must_ accept its own output
without modifying it.
+Sequence "%f" on the filter command line is replaced with the name of
+the file the filter is working on. A filter might use this in keyword
+substitution. For example:
+
+------------------------
+[filter "p4"]
+ clean = git-p4-filter --clean %f
+ smudge = git-p4-filter --smudge %f
+------------------------
+
Interaction between checkin/checkout attributes
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- `pascal` suitable for source code in the Pascal/Delphi language.
+- `perl` suitable for source code in the Perl language.
+
- `php` suitable for source code in the PHP language.
- `python` suitable for source code in the Python language.
Set::
Notice all types of potential whitespace errors known to git.
+ The tab width is taken from the value of the `core.whitespace`
+ configuration variable.
Unset::
Unspecified::
- Use the value of `core.whitespace` configuration variable to
+ Use the value of the `core.whitespace` configuration variable to
decide what to notice as error.
String::
Specify a comma separate list of common whitespace problems to
- notice in the same format as `core.whitespace` configuration
+ notice in the same format as the `core.whitespace` configuration
variable.
index 5d91a7e5b3a40cbf41f03a81799c162775c36607..6af29a4603de57e0040c4bcd819b831dd6649f92 100644 (file)
commands.
When diffcore-pickaxe is in use, it checks if there are
-filepairs whose "result" side has the specified string and
-whose "origin" side does not. Such a filepair represents "the
-string appeared in this changeset". It also checks for the
+filepairs whose "result" side and whose "origin" side have
+different number of specified string. Such a filepair represents
+"the string appeared in this changeset". It also checks for the
opposite case that loses the specified string.
When `\--pickaxe-all` is not in effect, diffcore-pickaxe leaves
index bcffd95ada574a8fb3c6e62e539e736a17170414..68977943e7bba092f5e55e6ad1b9ede83f4c2b67 100644 (file)
This config option is overridden if 'git submodule update' is given
the '--merge' or '--rebase' options.
+submodule.<name>.fetchRecurseSubmodules::
+ This option can be used to enable/disable recursive fetching of this
+ submodule. If this option is also present in the submodules entry in
+ .git/config of the superproject, the setting there will override the
+ one found in .gitmodules.
+ Both settings can be overridden on the command line by using the
+ "--[no-]recurse-submodules" option to "git fetch" and "git pull"..
+
submodule.<name>.ignore::
Defines under what circumstances "git status" and the diff family show
a submodule as modified. When set to "all", it will never be considered
index ecab0c09d01fadd423b8d6bc4ee4bf3dd9e669de..7fe5848d1f7ef77748448c03c4b2b2b33d82f03c 100644 (file)
#
# new file: closing.txt
#
-# Changed but not updated:
+# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
#
# modified: file.txt
index 1c1606696e3771f1b881c18689b049b53a8a3539..0982f74ef6212577afb74d86079472662e2bffba 100644 (file)
Unlike the longhand form, when Alice fetches from Bob using a
remote repository shorthand set up with 'git remote', what was
-fetched is stored in a remote tracking branch, in this case
+fetched is stored in a remote-tracking branch, in this case
`bob/master`. So after this:
-------------------------------------
alice$ git merge bob/master
-------------------------------------
-This `merge` can also be done by 'pulling from her own remote
-tracking branch', like this:
+This `merge` can also be done by 'pulling from her own remote-tracking
+branch', like this:
-------------------------------------
alice$ git pull . remotes/bob/master
index 1f029f8aa080c4de6323e8b4905a81fa7e8e2046..33716a31d0c60093c14f894ef07a87bee73fafc9 100644 (file)
you have. In such these cases, you do not make a new <<def_merge,merge>>
<<def_commit,commit>> but instead just update to his
revision. This will happen frequently on a
- <<def_tracking_branch,tracking branch>> of a remote
+ <<def_remote_tracking_branch,remote-tracking branch>> of a remote
<<def_repository,repository>>.
[[def_fetch]]fetch::
The default upstream <<def_repository,repository>>. Most projects have
at least one upstream project which they track. By default
'origin' is used for that purpose. New upstream updates
- will be fetched into remote <<def_tracking_branch,tracking branches>> named
+ will be fetched into remote <<def_remote_tracking_branch,remote-tracking branches>> named
origin/name-of-upstream-branch, which you can see using
`git branch -r`.
<<def_pack,pack>>, to assist in efficiently accessing the contents of a
pack.
+[[def_pathspec]]pathspec::
+ Pattern used to specify paths.
++
+Pathspecs are used on the command line of "git ls-files", "git
+ls-tree", "git grep", "git checkout", and many other commands to
+limit the scope of operations to some subset of the tree or
+worktree. See the documentation of each command for whether
+paths are relative to the current directory or toplevel. The
+pathspec syntax is as follows:
+
+* any path matches itself
+* the pathspec up to the last slash represents a
+ directory prefix. The scope of that pathspec is
+ limited to that subtree.
+* the rest of the pathspec is a pattern for the remainder
+ of the pathname. Paths relative to the directory
+ prefix will be matched against that pattern using fnmatch(3);
+ in particular, '*' and '?' _can_ match directory separators.
++
+For example, Documentation/*.jpg will match all .jpg files
+in the Documentation subtree,
+including Documentation/chapter_1/figure_1.jpg.
+
[[def_parent]]parent::
A <<def_commit_object,commit object>> contains a (possibly empty) list
of the logical predecessor(s) in the line of development, i.e. its
master branch head as to-upstream branch at $URL". See also
linkgit:git-push[1].
+[[def_remote_tracking_branch]]remote-tracking branch::
+ A regular git <<def_branch,branch>> that is used to follow changes from
+ another <<def_repository,repository>>. A remote-tracking
+ branch should not contain direct modifications or have local commits
+ made to it. A remote-tracking branch can usually be
+ identified as the right-hand-side <<def_ref,ref>> in a Pull:
+ <<def_refspec,refspec>>.
+
[[def_repository]]repository::
A collection of <<def_ref,refs>> together with an
<<def_object_database,object database>> containing all objects
that each contain very well defined concepts or small incremental yet
related changes.
-[[def_tracking_branch]]tracking branch::
- A regular git <<def_branch,branch>> that is used to follow changes from
- another <<def_repository,repository>>. A tracking
- branch should not contain direct modifications or have local commits
- made to it. A tracking branch can usually be
- identified as the right-hand-side <<def_ref,ref>> in a Pull:
- <<def_refspec,refspec>>.
-
[[def_tree]]tree::
Either a <<def_working_tree,working tree>>, or a <<def_tree_object,tree
object>> together with the dependent <<def_blob_object,blob>> and tree objects
diff --git a/Documentation/howto/using-merge-subtree.txt b/Documentation/howto/using-merge-subtree.txt
index 0953a50b693307976977c81a4c0b611fd5dfb490..2933056120bf0a0d6249c6951ab1e964ae40674b 100644 (file)
relevant parts of your tree.
- Please note that if the other project merges from you, then it will
- connects its history to yours, which can be something they don't want
+ connect its history to yours, which can be something they don't want
to.
index b72f5339704e89087992b7d6060e88f43b9d661b..1e5c22c5e5c19dab5d5c35ec2401870237830a5d 100644 (file)
marker and the original text before the `=======` marker.
merge.log::
- Whether to include summaries of merged commits in newly created
- merge commit messages. False by default.
+ In addition to branch names, populate the log message with at
+ most the specified number of one-line descriptions from the
+ actual commits that are being merged. Defaults to false, and
+ true is a synonym for 20.
merge.renameLimit::
The number of files to consider when performing rename detection
index 722d704ff2de1abd3d77a18ca396ba96d5cab6bc..e33e0f8e110879a0cd6fe81d97ee6044290e5cfa 100644 (file)
With --no-ff Generate a merge commit even if the merge
resolved as a fast-forward.
---log::
+--log[=<n>]::
--no-log::
In addition to branch names, populate the log message with
- one-line descriptions from the actual commits that are being
- merged.
+ one-line descriptions from at most <n> actual commits that are being
+ merged. See also linkgit:git-fmt-merge-msg[1].
+
With --no-log do not list one-line descriptions from the
actual commits being merged.
index 8676e26ca20311196d069ab28c08c913271c7631..595a3cf1a7118ba29a1d57d7fc17d233d89cd3d0 100644 (file)
theirs;;
This is opposite of 'ours'.
+patience;;
+ With this option, 'merge-recursive' spends a little extra time
+ to avoid mismerges that sometimes occur due to unimportant
+ matching lines (e.g., braces from distinct functions). Use
+ this when the branches to be merged have diverged wildly.
+ See also linkgit:git-diff[1] `--patience`.
+
+ignore-space-change;;
+ignore-all-space;;
+ignore-space-at-eol;;
+ Treats lines with the indicated type of whitespace change as
+ unchanged for the sake of a three-way merge. Whitespace
+ changes mixed with other changes to a line are not ignored.
+ See also linkgit:git-diff[1] `-b`, `-w`, and
+ `--ignore-space-at-eol`.
++
+* If 'their' version only introduces whitespace changes to a line,
+ 'our' version is used;
+* If 'our' version introduces whitespace changes but 'their'
+ version includes a substantial change, 'their' version is used;
+* Otherwise, the merge proceeds in the usual way.
+
renormalize;;
This runs a virtual check-out and check-in of all three stages
of a file when resolving a three-way merge. This option is
Disables the `renormalize` option. This overrides the
`merge.renormalize` configuration variable.
+rename-threshold=<n>;;
+ Controls the similarity threshold used for rename detection.
+ See also linkgit:git-diff[1] `-M`.
+
subtree[=<path>];;
This option is a more advanced form of 'subtree' strategy, where
the strategy makes a guess on how two trees must be shifted to
index 42ca059908b6af5726d958d92912705c96235d29..44a2ef1de15bdce5d67a151a1cb6150031e57c7e 100644 (file)
Pretend as if all the refs in `refs/remotes` are listed
on the command line as '<commit>'. If '<pattern>' is given, limit
- remote tracking branches to ones matching given shell glob.
+ remote-tracking branches to ones matching given shell glob.
If pattern lacks '?', '*', or '[', '/*' at the end is implied.
--glob=<glob-pattern>::
index 3d4b79c480e2c84150bee96392ee2dd2a4b7d3ea..9e92734bc1b308c47e5f5726c704a3ddffa5beb9 100644 (file)
and dereference the tag recursively until a non-tag object is
found.
+* A suffix '{caret}' to a revision parameter followed by a brace
+ pair that contains a text led by a slash (e.g. `HEAD^{/fix nasty bug}`):
+ this is the same as `:/fix nasty bug` syntax below except that
+ it returns the youngest matching commit which is reachable from
+ the ref before '{caret}'.
+
* A colon, followed by a slash, followed by a text (e.g. `:/fix nasty bug`): this names
a commit whose commit message matches the specified regular expression.
This name returns the youngest matching commit which is
':path' (with an empty part before the colon, e.g. `:README`)
is a special case of the syntax described next: content
recorded in the index at the given path.
+ A path starting with './' or '../' is relative to current working directory.
+ The given path will be converted to be relative to working tree's root directory.
+ This is most useful to address a blob or tree from a commit or tree that has
+ the same tree structure with the working tree.
* A colon, optionally followed by a stage number (0 to 3) and a
colon, followed by a path (e.g. `:0:README`); this names a blob object in the
index a7e050bb7acb59764c851b09cd488b6620c0a2dd..9dc1bed768a473317dd77c4d1ed93f65b2c4d483 100644 (file)
path-specific merge drivers (specified in `.gitattributes`)
into account.
+Data structures
+---------------
+
+* `mmbuffer_t`, `mmfile_t`
+
+These store data usable for use by the xdiff backend, for writing and
+for reading, respectively. See `xdiff/xdiff.h` for the definitions
+and `diff.c` for examples.
+
+* `struct ll_merge_options`
+
+This describes the set of options the calling program wants to affect
+the operation of a low-level (single file) merge. Some options:
+
+`virtual_ancestor`::
+ Behave as though this were part of a merge between common
+ ancestors in a recursive merge.
+ If a helper program is specified by the
+ `[merge "<driver>"] recursive` configuration, it will
+ be used (see linkgit:gitattributes[5]).
+
+`variant`::
+ Resolve local conflicts automatically in favor
+ of one side or the other (as in 'git merge-file'
+ `--ours`/`--theirs`/`--union`). Can be `0`,
+ `XDL_MERGE_FAVOR_OURS`, `XDL_MERGE_FAVOR_THEIRS`, or
+ `XDL_MERGE_FAVOR_UNION`.
+
+`renormalize`::
+ Resmudge and clean the "base", "theirs" and "ours" files
+ before merging. Use this when the merge is likely to have
+ overlapped with a change in smudge/clean or end-of-line
+ normalization rules.
+
Low-level (single file) merge
-----------------------------
`.git/info/attributes` into account. Returns 0 for a
clean merge.
-The caller:
+Calling sequence:
-1. allocates an mmbuffer_t variable for the result;
-2. allocates and fills variables with the file's original content
- and two modified versions (using `read_mmfile`, for example);
-3. calls ll_merge();
-4. reads the output from result_buf.ptr and result_buf.size;
-5. releases buffers when finished (free(ancestor.ptr); free(ours.ptr);
- free(theirs.ptr); free(result_buf.ptr);).
+* Prepare a `struct ll_merge_options` to record options.
+ If you have no special requests, skip this and pass `NULL`
+ as the `opts` parameter to use the default options.
+
+* Allocate an mmbuffer_t variable for the result.
+
+* Allocate and fill variables with the file's original content
+ and two modified versions (using `read_mmfile`, for example).
+
+* Call `ll_merge()`.
+
+* Read the merged content from `result_buf.ptr` and `result_buf.size`.
+
+* Release buffers when finished. A simple
+ `free(ancestor.ptr); free(ours.ptr); free(theirs.ptr);
+ free(result_buf.ptr);` will do.
If the modifications do not merge cleanly, `ll_merge` will return a
nonzero value and `result_buf` will generally include a description of
used to label the different sides of a conflict if the merge driver
supports this.
-The `flag` parameter is a bitfield:
-
- - The `LL_OPT_VIRTUAL_ANCESTOR` bit indicates whether this is an
- internal merge to consolidate ancestors for a recursive merge.
-
- - The `LL_OPT_FAVOR_MASK` bits allow local conflicts to be automatically
- resolved in favor of one side or the other (as in 'git merge-file'
- `--ours`/`--theirs`/`--union`).
- They can be populated by `create_ll_flag`, whose argument can be
- `XDL_MERGE_FAVOR_OURS`, `XDL_MERGE_FAVOR_THEIRS`, or
- `XDL_MERGE_FAVOR_UNION`.
-
Everything else
---------------
diff --git a/Documentation/technical/api-parse-options.txt b/Documentation/technical/api-parse-options.txt
index c5d141cd63d15e97dd4b4777c30dccb57d430782..f6a4a361bd56e1cc0f4645e09898e6852bfb8a8b 100644 (file)
`OPT__COLOR(&int_var, description)`::
Add `\--color[=<when>]` and `--no-color`.
-`OPT__DRY_RUN(&int_var)`::
+`OPT__DRY_RUN(&int_var, description)`::
Add `-n, \--dry-run`.
-`OPT__QUIET(&int_var)`::
+`OPT__FORCE(&int_var, description)`::
+ Add `-f, \--force`.
+
+`OPT__QUIET(&int_var, description)`::
Add `-q, \--quiet`.
-`OPT__VERBOSE(&int_var)`::
+`OPT__VERBOSE(&int_var, description)`::
Add `-v, \--verbose`.
`OPT_GROUP(description)`::
index 535cdff164f1f1cdf829f18fa192783efb9f0086..9e1189ef01dff889447c1bf92802c9792f267b9b 100644 (file)
}
------------------------------------------
-Handlers are given the typdef of sigchain_fun. This is the same type
+Handlers are given the typedef of sigchain_fun. This is the same type
that is given to signal() or sigaction(). It is perfectly reasonable to
push SIG_DFL or SIG_IGN onto the stack.
index fc56da677cb0ed3d9243bfb928686b60df213243..f13a84613198936ab238e356ec30091db4844544 100644 (file)
The "master" branch that was created at the time you cloned is a copy
of the HEAD in the repository that you cloned from. That repository
may also have had other branches, though, and your local repository
-keeps branches which track each of those remote branches, which you
+keeps branches which track each of those remote branches, called
+remote-tracking branches, which you
can view using the "-r" option to linkgit:git-branch[1]:
------------------------------------------------
origin/todo
------------------------------------------------
-You cannot check out these remote-tracking branches, but you can
-examine them on a branch of your own, just as you would a tag:
+In this example, "origin" is called a remote repository, or "remote"
+for short. The branches of this repository are called "remote
+branches" from our point of view. The remote-tracking branches listed
+above were created based on the remote branches at clone time and will
+be updated by "git fetch" (hence "git pull") and "git push". See
+<<Updating-a-repository-With-git-fetch>> for details.
+
+You might want to build on one of these remote-tracking branches
+on a branch of your own, just as you would for a tag:
------------------------------------------------
$ git checkout -b my-todo-copy origin/todo
------------------------------------------------
+You can also check out "origin/todo" directly to examine it or
+write a one-off patch. See <<detached-head,detached head>>.
+
Note that the name "origin" is just the name that git uses by default
to refer to the repository that you cloned from.
origin/master
-------------------------------------------------
-If you run "git fetch <remote>" later, the tracking branches for the
+If you run "git fetch <remote>" later, the remote-tracking branches for the
named <remote> will be updated.
If you examine the file .git/config, you will see that git has added
into your own work.
We have already seen <<Updating-a-repository-With-git-fetch,how to
-keep remote tracking branches up to date>> with linkgit:git-fetch[1],
+keep remote-tracking branches up to date>> with linkgit:git-fetch[1],
and how to merge two branches. So you can merge in changes from the
original repository's master branch with:
$ git pull origin master
-------------------------------------------------
-In fact, if you have "master" checked out, then by default "git pull"
-merges from the HEAD branch of the origin repository. So often you can
+In fact, if you have "master" checked out, then this branch has been
+configured by "git clone" to get changes from the HEAD branch of the
+origin repository. So often you can
accomplish the above with just a simple
-------------------------------------------------
$ git pull
-------------------------------------------------
-More generally, a branch that is created from a remote branch will pull
+This command will fetch changes from the remote branches to your
+remote-tracking branches `origin/*`, and merge the default branch into
+the current branch.
+
+More generally, a branch that is created from a remote-tracking branch
+will pull
by default from that branch. See the descriptions of the
branch.<name>.remote and branch.<name>.merge options in
linkgit:git-config[1], and the discussion of the `--track` option in
@@ -2106,7 +2123,7 @@ $ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git
$ cd work
-------------------------------------------------
-Linus's tree will be stored in the remote branch named origin/master,
+Linus's tree will be stored in the remote-tracking branch named origin/master,
and can be updated using linkgit:git-fetch[1]; you can track other
public trees using linkgit:git-remote[1] to set up a "remote" and
linkgit:git-fetch[1] to keep them up-to-date; see
may be lost, as we saw in the previous section.
[[remote-branch-configuration]]
-Configuring remote branches
----------------------------
+Configuring remote-tracking branches
+------------------------------------
We saw above that "origin" is just a shortcut to refer to the
repository that you originally cloned from. This information is
diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN
index d810ec288015de8113fa388730579e4fdedbfa20..92fe7a59dbf81b0a055280f9351811641ce7044a 100755 (executable)
--- a/GIT-VERSION-GEN
+++ b/GIT-VERSION-GEN
#!/bin/sh
GVF=GIT-VERSION-FILE
-DEF_VER=v1.7.3.5
+DEF_VER=v1.7.4
LF='
'
index 10a1cba643cc9e6cc9a441708b1d43f468fb8383..16e45f114f18d5d6964ab9fa39eee617dfa59bfc 100644 (file)
--- a/INSTALL
+++ b/INSTALL
Building and installing the pdf file additionally requires
dblatex. Version 0.2.7 with asciidoc >= 8.2.7 is known to work.
- The documentation is written for AsciiDoc 7, but "make
- ASCIIDOC8=YesPlease doc" will let you format with AsciiDoc 8.
+ The documentation is written for AsciiDoc 7, but by default
+ uses some compatibility wrappers to work on AsciiDoc 8. If you have
+ AsciiDoc 7, try "make ASCIIDOC7=YesPlease".
Alternatively, pre-formatted documentation is available in
"html" and "man" branches of the git repository itself. For
diff --git a/Makefile b/Makefile
index ecb4e64435b674d97d643bf04d896b26a2b505b2..ade79232f4c3c080fa7d35c4b55870c416e49da6 100644 (file)
--- a/Makefile
+++ b/Makefile
#
# Define NO_STRTOK_R if you don't have strtok_r in the C library.
#
+# Define NO_FNMATCH if you don't have fnmatch in the C library.
+#
+# Define NO_FNMATCH_CASEFOLD if your fnmatch function doesn't have the
+# FNM_CASEFOLD GNU extension.
+#
# Define NO_LIBGEN_H if you don't have libgen.h.
#
# Define NEEDS_LIBGEN if your libgen needs -lgen when linking
# Define NO_ST_BLOCKS_IN_STRUCT_STAT if your platform does not have st_blocks
# field that counts the on-disk footprint in 512-byte blocks.
#
-# Define ASCIIDOC8 if you want to format documentation with AsciiDoc 8
+# Define ASCIIDOC7 if you want to format documentation with AsciiDoc 7
#
# Define DOCBOOK_XSL_172 if you want to format man pages with DocBook XSL v1.72
# (not v1.73 or v1.71).
#
-# Define ASCIIDOC_NO_ROFF if your DocBook XSL escapes raw roff directives
-# (versions 1.72 and later and 1.68.1 and earlier).
+# Define ASCIIDOC_ROFF if your DocBook XSL does not escape raw roff directives
+# (versions 1.68.1 through v1.72).
#
# Define GNU_ROFF if your target system uses GNU groff. This forces
# apostrophes to be ASCII so that cut&pasting examples to the shell
# infodir
# htmldir
# ETC_GITCONFIG (but not sysconfdir)
+# ETC_GITATTRIBUTES
# can be specified as a relative path some/where/else;
# this is interpreted as relative to $(prefix) and "git" at
# runtime figures out where they are based on the path to the executable.
ifeq ($(prefix),/usr)
sysconfdir = /etc
ETC_GITCONFIG = $(sysconfdir)/gitconfig
+ETC_GITATTRIBUTES = $(sysconfdir)/gitattributes
else
sysconfdir = $(prefix)/etc
ETC_GITCONFIG = etc/gitconfig
+ETC_GITATTRIBUTES = etc/gitattributes
endif
lib = lib
# DESTDIR=
# ... and all the rest that could be moved out of bindir to gitexecdir
PROGRAMS += $(EXTRA_PROGRAMS)
+PROGRAM_OBJS += daemon.o
PROGRAM_OBJS += fast-import.o
PROGRAM_OBJS += imap-send.o
PROGRAM_OBJS += shell.o
TEST_PROGRAMS_NEED_X += test-sha1
TEST_PROGRAMS_NEED_X += test-sigchain
TEST_PROGRAMS_NEED_X += test-string-pool
+TEST_PROGRAMS_NEED_X += test-subprocess
TEST_PROGRAMS_NEED_X += test-svn-fe
TEST_PROGRAMS_NEED_X += test-treap
TEST_PROGRAMS_NEED_X += test-index-version
+TEST_PROGRAMS_NEED_X += test-mktemp
TEST_PROGRAMS = $(patsubst %,%$X,$(TEST_PROGRAMS_NEED_X))
LIB_H += compat/cygwin.h
LIB_H += compat/mingw.h
LIB_H += compat/win32/pthread.h
+LIB_H += compat/win32/syslog.h
+LIB_H += compat/win32/sys/poll.h
+LIB_H += compat/win32/dirent.h
LIB_H += csum-file.h
LIB_H += decorate.h
LIB_H += delta.h
LIB_H += merge-recursive.h
LIB_H += notes.h
LIB_H += notes-cache.h
+LIB_H += notes-merge.h
LIB_H += object.h
LIB_H += pack.h
LIB_H += pack-refs.h
LIB_OBJS += name-hash.o
LIB_OBJS += notes.o
LIB_OBJS += notes-cache.o
+LIB_OBJS += notes-merge.o
LIB_OBJS += object.o
LIB_OBJS += pack-check.o
LIB_OBJS += pack-refs.o
LIB_OBJS += ws.o
LIB_OBJS += wt-status.o
LIB_OBJS += xdiff-interface.o
+LIB_OBJS += zlib.o
BUILTIN_OBJS += builtin/add.o
BUILTIN_OBJS += builtin/annotate.o
BUILTIN_OBJS += builtin/receive-pack.o
BUILTIN_OBJS += builtin/reflog.o
BUILTIN_OBJS += builtin/remote.o
+BUILTIN_OBJS += builtin/remote-ext.o
+BUILTIN_OBJS += builtin/remote-fd.o
BUILTIN_OBJS += builtin/replace.o
BUILTIN_OBJS += builtin/rerere.o
BUILTIN_OBJS += builtin/reset.o
NO_MKDTEMP = YesPlease
NO_MKSTEMPS = YesPlease
NO_REGEX = YesPlease
+ NO_FNMATCH_CASEFOLD = YesPlease
ifeq ($(uname_R),5.6)
SOCKLEN_T = int
NO_HSTRERROR = YesPlease
# issue, comment out the NO_MMAP statement.
NO_MMAP = YesPlease
NO_REGEX = YesPlease
+ NO_FNMATCH_CASEFOLD = YesPlease
SNPRINTF_RETURNS_BOGUS = YesPlease
SHELL_PATH = /usr/gnu/bin/bash
NEEDS_LIBGEN = YesPlease
# issue, comment out the NO_MMAP statement.
NO_MMAP = YesPlease
NO_REGEX = YesPlease
+ NO_FNMATCH_CASEFOLD = YesPlease
SNPRINTF_RETURNS_BOGUS = YesPlease
SHELL_PATH=/usr/gnu/bin/bash
NEEDS_LIBGEN = YesPlease
NO_STRCASESTR = YesPlease
NO_STRLCPY = YesPlease
NO_STRTOK_R = YesPlease
+ NO_FNMATCH = YesPlease
NO_MEMMEM = YesPlease
# NEEDS_LIBICONV = YesPlease
NO_ICONV = YesPlease
NO_SVN_TESTS = YesPlease
NO_PERL_MAKEMAKER = YesPlease
RUNTIME_PREFIX = YesPlease
- NO_POSIX_ONLY_PROGRAMS = YesPlease
NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease
NO_NSEC = YesPlease
USE_WIN32_MMAP = YesPlease
NO_CURL = YesPlease
NO_PYTHON = YesPlease
BLK_SHA1 = YesPlease
+ NO_POSIX_GOODIES = UnfortunatelyYes
NATIVE_CRLF = YesPlease
CC = compat/vcbuild/scripts/clink.pl
AR = compat/vcbuild/scripts/lib.pl
CFLAGS =
BASIC_CFLAGS = -nologo -I. -I../zlib -Icompat/vcbuild -Icompat/vcbuild/include -DWIN32 -D_CONSOLE -DHAVE_STRING_H -D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE
- COMPAT_OBJS = compat/msvc.o compat/fnmatch/fnmatch.o compat/winansi.o compat/win32/pthread.o
- COMPAT_CFLAGS = -D__USE_MINGW_ACCESS -DNOGDI -DHAVE_STRING_H -DHAVE_ALLOCA_H -Icompat -Icompat/fnmatch -Icompat/regex -Icompat/fnmatch -Icompat/win32 -DSTRIP_EXTENSION=\".exe\"
+ COMPAT_OBJS = compat/msvc.o compat/winansi.o \
+ compat/win32/pthread.o compat/win32/syslog.o \
+ compat/win32/sys/poll.o compat/win32/dirent.o
+ COMPAT_CFLAGS = -D__USE_MINGW_ACCESS -DNOGDI -DHAVE_STRING_H -DHAVE_ALLOCA_H -Icompat -Icompat/regex -Icompat/win32 -DSTRIP_EXTENSION=\".exe\"
BASIC_LDFLAGS = -IGNORE:4217 -IGNORE:4049 -NOLOGO -SUBSYSTEM:CONSOLE -NODEFAULTLIB:MSVCRT.lib
- EXTLIBS = advapi32.lib shell32.lib wininet.lib ws2_32.lib
+ EXTLIBS = user32.lib advapi32.lib shell32.lib wininet.lib ws2_32.lib
PTHREAD_LIBS =
lib =
ifndef DEBUG
endif
X = .exe
endif
+ifeq ($(uname_S),Interix)
+ NO_SYS_POLL_H = YesPlease
+ NO_INTTYPES_H = YesPlease
+ NO_INITGROUPS = YesPlease
+ NO_IPV6 = YesPlease
+ NO_MEMMEM = YesPlease
+ NO_MKDTEMP = YesPlease
+ NO_STRTOUMAX = YesPlease
+ NO_NSEC = YesPlease
+ NO_MKSTEMPS = YesPlease
+ ifeq ($(uname_R),3.5)
+ NO_INET_NTOP = YesPlease
+ NO_INET_PTON = YesPlease
+ endif
+ ifeq ($(uname_R),5.2)
+ NO_INET_NTOP = YesPlease
+ NO_INET_PTON = YesPlease
+ endif
+endif
ifneq (,$(findstring MINGW,$(uname_S)))
pathsep = ;
NO_PREAD = YesPlease
NO_STRCASESTR = YesPlease
NO_STRLCPY = YesPlease
NO_STRTOK_R = YesPlease
+ NO_FNMATCH = YesPlease
NO_MEMMEM = YesPlease
NEEDS_LIBICONV = YesPlease
OLD_ICONV = YesPlease
NO_SVN_TESTS = YesPlease
NO_PERL_MAKEMAKER = YesPlease
RUNTIME_PREFIX = YesPlease
- NO_POSIX_ONLY_PROGRAMS = YesPlease
NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease
NO_NSEC = YesPlease
USE_WIN32_MMAP = YesPlease
NO_PYTHON = YesPlease
BLK_SHA1 = YesPlease
ETAGS_TARGET = ETAGS
- COMPAT_CFLAGS += -D__USE_MINGW_ACCESS -DNOGDI -Icompat -Icompat/fnmatch -Icompat/win32
+ NO_INET_PTON = YesPlease
+ NO_INET_NTOP = YesPlease
+ NO_POSIX_GOODIES = UnfortunatelyYes
+ COMPAT_CFLAGS += -D__USE_MINGW_ACCESS -DNOGDI -Icompat -Icompat/win32
COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\"
- COMPAT_OBJS += compat/mingw.o compat/fnmatch/fnmatch.o compat/winansi.o \
- compat/win32/pthread.o
+ COMPAT_OBJS += compat/mingw.o compat/winansi.o \
+ compat/win32/pthread.o compat/win32/syslog.o \
+ compat/win32/sys/poll.o compat/win32/dirent.o
EXTLIBS += -lws2_32
PTHREAD_LIBS =
X = .exe
endif
EXTLIBS += -lz
-ifndef NO_POSIX_ONLY_PROGRAMS
- PROGRAM_OBJS += daemon.o
-endif
ifndef NO_OPENSSL
OPENSSL_LIBSSL = -lssl
ifdef OPENSSLDIR
COMPAT_CFLAGS += -DNO_STRTOK_R
COMPAT_OBJS += compat/strtok_r.o
endif
+ifdef NO_FNMATCH
+ COMPAT_CFLAGS += -Icompat/fnmatch
+ COMPAT_CFLAGS += -DNO_FNMATCH
+ COMPAT_OBJS += compat/fnmatch/fnmatch.o
+else
+ifdef NO_FNMATCH_CASEFOLD
+ COMPAT_CFLAGS += -Icompat/fnmatch
+ COMPAT_CFLAGS += -DNO_FNMATCH_CASEFOLD
+ COMPAT_OBJS += compat/fnmatch/fnmatch.o
+endif
+endif
ifdef NO_SETENV
COMPAT_CFLAGS += -DNO_SETENV
COMPAT_OBJS += compat/setenv.o
ifdef NO_SYS_SELECT_H
BASIC_CFLAGS += -DNO_SYS_SELECT_H
endif
+ifdef NO_SYS_POLL_H
+ BASIC_CFLAGS += -DNO_SYS_POLL_H
+endif
+ifdef NO_INTTYPES_H
+ BASIC_CFLAGS += -DNO_INTTYPES_H
+endif
+ifdef NO_INITGROUPS
+ BASIC_CFLAGS += -DNO_INITGROUPS
+endif
ifdef NO_MMAP
COMPAT_CFLAGS += -DNO_MMAP
COMPAT_OBJS += compat/mmap.o
endif
ifdef NO_INET_NTOP
LIB_OBJS += compat/inet_ntop.o
+ BASIC_CFLAGS += -DNO_INET_NTOP
endif
ifdef NO_INET_PTON
LIB_OBJS += compat/inet_pton.o
+ BASIC_CFLAGS += -DNO_INET_PTON
endif
ifdef NO_ICONV
BASIC_CFLAGS += -DNO_DEFLATE_BOUND
endif
+ifdef NO_POSIX_GOODIES
+ BASIC_CFLAGS += -DNO_POSIX_GOODIES
+endif
+
ifdef BLK_SHA1
SHA1_HEADER = "block-sha1/sha1.h"
LIB_OBJS += block-sha1/sha1.o
endif
endif
-ifdef ASCIIDOC8
- export ASCIIDOC8
+ifdef ASCIIDOC7
+ export ASCIIDOC7
endif
# Shell quote (do not use $(call) to accommodate ancient setups);
SHA1_HEADER_SQ = $(subst ','\'',$(SHA1_HEADER))
ETC_GITCONFIG_SQ = $(subst ','\'',$(ETC_GITCONFIG))
+ETC_GITATTRIBUTES_SQ = $(subst ','\'',$(ETC_GITATTRIBUTES))
DESTDIR_SQ = $(subst ','\'',$(DESTDIR))
bindir_SQ = $(subst ','\'',$(bindir))
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ git.o \
$(BUILTIN_OBJS) $(ALL_LDFLAGS) $(LIBS)
+help.o: common-cmds.h
+
builtin/help.o: common-cmds.h
builtin/help.s builtin/help.o: EXTRA_CPPFLAGS = \
'-DGIT_HTML_PATH="$(htmldir_SQ)"' \
xdiff/xmerge.o xdiff/xpatience.o
VCSSVN_OBJS = vcs-svn/string_pool.o vcs-svn/line_buffer.o \
vcs-svn/repo_tree.o vcs-svn/fast_export.o vcs-svn/svndump.o
+VCSSVN_TEST_OBJS = test-obj-pool.o test-string-pool.o \
+ test-line-buffer.o test-treap.o
OBJECTS := $(GIT_OBJS) $(XDIFF_OBJS) $(VCSSVN_OBJS)
dep_files := $(foreach f,$(OBJECTS),$(dir $f).depend/$(notdir $f).d)
@@ -1874,13 +1950,12 @@ builtin/branch.o builtin/checkout.o builtin/clone.o builtin/reset.o branch.o tra
builtin/bundle.o bundle.o transport.o: bundle.h
builtin/bisect--helper.o builtin/rev-list.o bisect.o: bisect.h
builtin/clone.o builtin/fetch-pack.o transport.o: fetch-pack.h
-builtin/grep.o: thread-utils.h
+builtin/grep.o builtin/pack-objects.o transport-helper.o: thread-utils.h
builtin/send-pack.o transport.o: send-pack.h
builtin/log.o builtin/shortlog.o: shortlog.h
builtin/prune.o builtin/reflog.o reachable.o: reachable.h
builtin/commit.o builtin/revert.o wt-status.o: wt-status.h
builtin/tar-tree.o archive-tar.o: tar.h
-builtin/pack-objects.o: thread-utils.h
connect.o transport.o http-backend.o: url.h
http-fetch.o http-walker.o remote-curl.o transport.o walker.o: walker.h
http.o http-walker.o http-push.o http-fetch.o remote-curl.o: http.h url.h
xdiff/xinclude.h xdiff/xmacros.h xdiff/xdiff.h xdiff/xtypes.h \
xdiff/xutils.h xdiff/xprepare.h xdiff/xdiffi.h xdiff/xemit.h
-$(VCSSVN_OBJS): \
+$(VCSSVN_OBJS) $(VCSSVN_TEST_OBJS): $(LIB_H) \
vcs-svn/obj_pool.h vcs-svn/trp.h vcs-svn/string_pool.h \
vcs-svn/line_buffer.h vcs-svn/repo_tree.h vcs-svn/fast_export.h \
vcs-svn/svndump.h
+
+test-svn-fe.o: vcs-svn/svndump.h
endif
exec_cmd.s exec_cmd.o: EXTRA_CPPFLAGS = \
config.s config.o: EXTRA_CPPFLAGS = -DETC_GITCONFIG='"$(ETC_GITCONFIG_SQ)"'
+attr.s attr.o: EXTRA_CPPFLAGS = -DETC_GITATTRIBUTES='"$(ETC_GITATTRIBUTES_SQ)"'
+
http.s http.o: EXTRA_CPPFLAGS = -DGIT_HTTP_USER_AGENT='"git/$(GIT_VERSION)"'
ifdef NO_EXPAT
diff --git a/RelNotes b/RelNotes
index 39dc075ee01342db1cd11c47f6de0395df605976..540b756d2f64d8c8ebf927a707b9b2cc0a4c6151 120000 (symlink)
--- a/RelNotes
+++ b/RelNotes
-Documentation/RelNotes/1.7.3.5.txt
\ No newline at end of file
+Documentation/RelNotes/1.7.5.txt
\ No newline at end of file
diff --git a/abspath.c b/abspath.c
index c91a29cb298a3ad792ff8745f3e8e0eb28d71678..91ca00f05f7d648fa801a36b78c749f9d691ba43 100644 (file)
--- a/abspath.c
+++ b/abspath.c
if (strlcpy(buf, path, PATH_MAX) >= PATH_MAX)
die("Too long path: %.*s", 60, path);
} else {
+ size_t len;
+ const char *fmt;
const char *cwd = get_pwd_cwd();
if (!cwd)
die_errno("Cannot determine the current working directory");
- if (snprintf(buf, PATH_MAX, "%s/%s", cwd, path) >= PATH_MAX)
+ len = strlen(cwd);
+ fmt = (len > 0 && is_dir_sep(cwd[len-1])) ? "%s%s" : "%s/%s";
+ if (snprintf(buf, PATH_MAX, fmt, cwd, path) >= PATH_MAX)
die("Too long path: %.*s", 60, path);
}
return buf;
diff --git a/aclocal.m4 b/aclocal.m4
index d399de2671bff39ee34b55017c179242010d643f..f11bc7ed7df2f6b058f947bfe850abb33590bfa7 100644 (file)
--- a/aclocal.m4
+++ b/aclocal.m4
git_cv_socklen_t_equiv=
for arg2 in "struct sockaddr" void; do
for t in int size_t unsigned long "unsigned long"; do
- AC_TRY_COMPILE([
+ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([
#include <sys/types.h>
#include <sys/socket.h>
],[
$t len;
getpeername(0,0,&len);
- ],[
+ ])],[
git_cv_socklen_t_equiv="$t"
break 2
])
diff --git a/archive.c b/archive.c
index f59afda6ffda02920a50cabe1eb3e764221199ba..1944ed4e4dc36addaf6a8b408703a7ff418bd26c 100644 (file)
--- a/archive.c
+++ b/archive.c
"write the archive to this file"),
OPT_BOOLEAN(0, "worktree-attributes", &worktree_attributes,
"read .gitattributes in working directory"),
- OPT__VERBOSE(&verbose),
+ OPT__VERBOSE(&verbose, "report archived files on stderr"),
OPT__COMPR('0', &compression_level, "store only", 0),
OPT__COMPR('1', &compression_level, "compress faster", 1),
OPT__COMPR_HIDDEN('2', &compression_level, 2),
index 8ba606c933088e27ac08aabb546b764745f8187e..6aff6951d38d7f672aeb9851eca88c6e5a26b950 100644 (file)
--- a/attr.c
+++ b/attr.c
#define NO_THE_INDEX_COMPATIBILITY_MACROS
#include "cache.h"
+#include "exec_cmd.h"
#include "attr.h"
const char git_attr__true[] = "(builtin)true";
#define ATTR__UNSET NULL
#define ATTR__UNKNOWN git_attr__unknown
+static const char *attributes_file;
+
/*
* The basic design decision here is that we are not going to have
* insanely large number of attributes.
}
}
+const char *git_etc_gitattributes(void)
+{
+ static const char *system_wide;
+ if (!system_wide)
+ system_wide = system_path(ETC_GITATTRIBUTES);
+ return system_wide;
+}
+
+int git_attr_system(void)
+{
+ return !git_env_bool("GIT_ATTR_NOSYSTEM", 0);
+}
+
+int git_attr_global(void)
+{
+ return !git_env_bool("GIT_ATTR_NOGLOBAL", 0);
+}
+
+static int git_attr_config(const char *var, const char *value, void *dummy)
+{
+ if (!strcmp(var, "core.attributesfile"))
+ return git_config_pathname(&attributes_file, var, value);
+
+ return 0;
+}
+
static void bootstrap_attr_stack(void)
{
if (!attr_stack) {
elem->prev = attr_stack;
attr_stack = elem;
+ if (git_attr_system()) {
+ elem = read_attr_from_file(git_etc_gitattributes(), 1);
+ if (elem) {
+ elem->origin = NULL;
+ elem->prev = attr_stack;
+ attr_stack = elem;
+ }
+ }
+
+ git_config(git_attr_config, NULL);
+ if (git_attr_global() && attributes_file) {
+ elem = read_attr_from_file(attributes_file, 1);
+ if (elem) {
+ elem->origin = NULL;
+ elem->prev = attr_stack;
+ attr_stack = elem;
+ }
+ }
+
if (!is_bare_repository() || direction == GIT_ATTR_INDEX) {
elem = read_attr(GITATTRIBUTES_FILE, 1);
elem->origin = strdup("");
/*
* At the bottom of the attribute stack is the built-in
- * set of attribute definitions. Then, contents from
+ * set of attribute definitions, followed by the contents
+ * of $(prefix)/etc/gitattributes and a file specified by
+ * core.attributesfile. Then, contents from
* .gitattribute files from directories closer to the
* root to the ones in deeper directories are pushed
* to the stack. Finally, at the very top of the stack
diff --git a/branch.h b/branch.h
index eed817a64c7620bfe67f395e39b4eef2f85a4ab3..4026e3832b265c4cef6e5bc151976771867b3da9 100644 (file)
--- a/branch.h
+++ b/branch.h
void remove_branch_state(void);
/*
- * Configure local branch "local" to merge remote branch "remote"
- * taken from origin "origin".
+ * Configure local branch "local" as downstream to branch "remote"
+ * from remote "origin". Used by git branch --set-upstream.
*/
#define BRANCH_CONFIG_VERBOSE 01
extern void install_branch_config(int flag, const char *local, const char *origin, const char *remote);
diff --git a/builtin.h b/builtin.h
index 9bf69eee0cb99fd4a8c66ee2f2bdecafe02b3b0b..0e9da9083491eb3c18464b730f481cf74ee82430 100644 (file)
--- a/builtin.h
+++ b/builtin.h
#include "commit.h"
#include "notes.h"
+#define DEFAULT_MERGE_LOG_LEN 20
+
extern const char git_version_string[];
extern const char git_usage_string[];
extern const char git_more_info_string[];
extern void prune_packed_objects(int);
-extern int fmt_merge_msg(int merge_summary, struct strbuf *in,
- struct strbuf *out);
-extern int fmt_merge_msg_shortlog(struct strbuf *in, struct strbuf *out);
-extern int commit_notes(struct notes_tree *t, const char *msg);
+extern int fmt_merge_msg(struct strbuf *in, struct strbuf *out,
+ int merge_title, int shortlog_len);
+extern void commit_notes(struct notes_tree *t, const char *msg);
struct notes_rewrite_cfg {
struct notes_tree **trees;
extern int cmd_clean(int argc, const char **argv, const char *prefix);
extern int cmd_commit(int argc, const char **argv, const char *prefix);
extern int cmd_commit_tree(int argc, const char **argv, const char *prefix);
+extern int cmd_config(int argc, const char **argv, const char *prefix);
extern int cmd_count_objects(int argc, const char **argv, const char *prefix);
extern int cmd_describe(int argc, const char **argv, const char *prefix);
extern int cmd_diff_files(int argc, const char **argv, const char *prefix);
extern int cmd_receive_pack(int argc, const char **argv, const char *prefix);
extern int cmd_reflog(int argc, const char **argv, const char *prefix);
extern int cmd_remote(int argc, const char **argv, const char *prefix);
-extern int cmd_config(int argc, const char **argv, const char *prefix);
+extern int cmd_remote_ext(int argc, const char **argv, const char *prefix);
+extern int cmd_remote_fd(int argc, const char **argv, const char *prefix);
+extern int cmd_repo_config(int argc, const char **argv, const char *prefix);
extern int cmd_rerere(int argc, const char **argv, const char *prefix);
extern int cmd_reset(int argc, const char **argv, const char *prefix);
extern int cmd_rev_list(int argc, const char **argv, const char *prefix);
diff --git a/builtin/add.c b/builtin/add.c
index 3a5fca51590db53747f8712f733aa01e5840d8fb..f7a17e43f6816622ab4ff836a169221289756505 100644 (file)
--- a/builtin/add.c
+++ b/builtin/add.c
struct rev_info rev;
init_revisions(&rev, prefix);
setup_revisions(0, NULL, &rev, NULL);
- rev.prune_data = pathspec;
+ init_pathspec(&rev.prune_data, pathspec);
rev.diffopt.output_format = DIFF_FORMAT_CALLBACK;
rev.diffopt.format_callback = update_callback;
data.flags = flags;
static int ignore_add_errors, addremove, intent_to_add, ignore_missing = 0;
static struct option builtin_add_options[] = {
- OPT__DRY_RUN(&show_only),
- OPT__VERBOSE(&verbose),
+ OPT__DRY_RUN(&show_only, "dry run"),
+ OPT__VERBOSE(&verbose, "be verbose"),
OPT_GROUP(""),
OPT_BOOLEAN('i', "interactive", &add_interactive, "interactive picking"),
- OPT_BOOLEAN('p', "patch", &patch_interactive, "interactive patching"),
+ OPT_BOOLEAN('p', "patch", &patch_interactive, "select hunks interactively"),
OPT_BOOLEAN('e', "edit", &edit_interactive, "edit current diff and apply"),
- OPT_BOOLEAN('f', "force", &ignored_too, "allow adding otherwise ignored files"),
+ OPT__FORCE(&ignored_too, "allow adding otherwise ignored files"),
OPT_BOOLEAN('u', "update", &take_worktree_changes, "update tracked files"),
OPT_BOOLEAN('N', "intent-to-add", &intent_to_add, "record only the fact that the path will be added later"),
- OPT_BOOLEAN('A', "all", &addremove, "add all, noticing removal of tracked files"),
+ OPT_BOOLEAN('A', "all", &addremove, "add changes from all tracked and untracked files"),
OPT_BOOLEAN( 0 , "refresh", &refresh_only, "don't add, only refresh the index"),
OPT_BOOLEAN( 0 , "ignore-errors", &ignore_add_errors, "just skip files which cannot be added because of errors"),
OPT_BOOLEAN( 0 , "ignore-missing", &ignore_missing, "check if - even missing - files are ignored in dry run"),
if (!seen[i] && pathspec[i][0]
&& !file_exists(pathspec[i])) {
if (ignore_missing) {
- if (excluded(&dir, pathspec[i], DT_UNKNOWN))
+ int dtype = DT_UNKNOWN;
+ if (excluded(&dir, pathspec[i], &dtype))
dir_add_ignored(&dir, pathspec[i], strlen(pathspec[i]));
} else
die("pathspec '%s' did not match any files",
diff --git a/builtin/apply.c b/builtin/apply.c
index b719f41482ee3be39d91a30f93eeabaf9d96be55..14951daedffa9e8a8a913eee5e8423220e86e291 100644 (file)
--- a/builtin/apply.c
+++ b/builtin/apply.c
"don't expect at least one line of context"),
OPT_BOOLEAN(0, "reject", &apply_with_reject,
"leave the rejected hunks in corresponding *.rej files"),
- OPT__VERBOSE(&apply_verbosely),
+ OPT__VERBOSE(&apply_verbosely, "be verbose"),
OPT_BIT(0, "inaccurate-eof", &options,
"tolerate incorrectly detected missing new-line at the end of file",
INACCURATE_EOF),
diff --git a/builtin/blame.c b/builtin/blame.c
index f5fccc1f6767d7386e37f7273c560d0d4491ee91..aa30ec52692558b0d8ed3dc3b6d671185682026e 100644 (file)
--- a/builtin/blame.c
+++ b/builtin/blame.c
#define OUTPUT_SHOW_NUMBER 040
#define OUTPUT_SHOW_SCORE 0100
#define OUTPUT_NO_AUTHOR 0200
+#define OUTPUT_SHOW_EMAIL 0400
static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent)
{
@@ -1682,12 +1683,17 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt)
}
printf("%.*s", length, hex);
- if (opt & OUTPUT_ANNOTATE_COMPAT)
- printf("\t(%10s\t%10s\t%d)", ci.author,
+ if (opt & OUTPUT_ANNOTATE_COMPAT) {
+ const char *name;
+ if (opt & OUTPUT_SHOW_EMAIL)
+ name = ci.author_mail;
+ else
+ name = ci.author;
+ printf("\t(%10s\t%10s\t%d)", name,
format_time(ci.author_time, ci.author_tz,
show_raw_time),
ent->lno + 1 + cnt);
- else {
+ } else {
if (opt & OUTPUT_SHOW_SCORE)
printf(" %*d %02d",
max_score_digits, ent->score,
@@ -1700,9 +1706,15 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt)
ent->s_lno + 1 + cnt);
if (!(opt & OUTPUT_NO_AUTHOR)) {
- int pad = longest_author - utf8_strwidth(ci.author);
+ const char *name;
+ int pad;
+ if (opt & OUTPUT_SHOW_EMAIL)
+ name = ci.author_mail;
+ else
+ name = ci.author;
+ pad = longest_author - utf8_strwidth(name);
printf(" (%s%*s %10s",
- ci.author, pad, "",
+ name, pad, "",
format_time(ci.author_time,
ci.author_tz,
show_raw_time));
if (!(suspect->commit->object.flags & METAINFO_SHOWN)) {
suspect->commit->object.flags |= METAINFO_SHOWN;
get_commit_info(suspect->commit, &ci, 1);
- num = utf8_strwidth(ci.author);
+ if (*option & OUTPUT_SHOW_EMAIL)
+ num = utf8_strwidth(ci.author_mail);
+ else
+ num = utf8_strwidth(ci.author);
if (longest_author < num)
longest_author = num;
}
OPT_BIT('t', NULL, &output_option, "Show raw timestamp (Default: off)", OUTPUT_RAW_TIMESTAMP),
OPT_BIT('l', NULL, &output_option, "Show long commit SHA1 (Default: off)", OUTPUT_LONG_OBJECT_NAME),
OPT_BIT('s', NULL, &output_option, "Suppress author name and timestamp (Default: off)", OUTPUT_NO_AUTHOR),
+ OPT_BIT('e', "show-email", &output_option, "Show author email instead of name (Default: off)", OUTPUT_SHOW_EMAIL),
OPT_BIT('w', NULL, &xdl_opts, "Ignore whitespace differences", XDF_IGNORE_WHITESPACE),
OPT_STRING('S', NULL, &revs_file, "file", "Use revisions from <file> instead of calling git-rev-list"),
OPT_STRING(0, "contents", &contents_from, "file", "Use <file>'s contents as the final image"),
save_commit_buffer = 0;
dashdash_pos = 0;
- parse_options_start(&ctx, argc, argv, prefix, PARSE_OPT_KEEP_DASHDASH |
- PARSE_OPT_KEEP_ARGV0);
+ parse_options_start(&ctx, argc, argv, prefix, options,
+ PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_ARGV0);
for (;;) {
switch (parse_options_step(&ctx, options, blame_opt_usage)) {
case PARSE_OPT_HELP:
diff --git a/builtin/branch.c b/builtin/branch.c
index 87976f0921d87f2a8564eb54aa3b81d30ee7f214..fe8f2fcd524cd690c8601d9370079f9d9ea21df3 100644 (file)
--- a/builtin/branch.c
+++ b/builtin/branch.c
in_merge_bases(rev, &head_rev, 1) != merged) {
if (merged)
warning("deleting branch '%s' that has been merged to\n"
- " '%s', but it is not yet merged to HEAD.",
+ " '%s', but not yet been merged to HEAD.",
name, reference_name);
else
warning("not deleting branch '%s' that is not yet merged to\n"
@@ -313,12 +313,7 @@ static int append_ref(const char *refname, const unsigned char *sha1, int flags,
(struct object *)commit, refname);
}
- /* Resize buffer */
- if (ref_list->index >= ref_list->alloc) {
- ref_list->alloc = alloc_nr(ref_list->alloc);
- ref_list->list = xrealloc(ref_list->list,
- ref_list->alloc * sizeof(struct ref_item));
- }
+ ALLOC_GROW(ref_list->list, ref_list->index + 1, ref_list->alloc);
/* Record the new item */
newitem = &(ref_list->list[ref_list->index++]);
struct option options[] = {
OPT_GROUP("Generic options"),
- OPT__VERBOSE(&verbose),
+ OPT__VERBOSE(&verbose,
+ "show hash and subject, give twice for upstream branch"),
OPT_SET_INT('t', "track", &track, "set up tracking mode (see git-pull(1))",
BRANCH_TRACK_EXPLICIT),
OPT_SET_INT( 0, "set-upstream", &track, "change upstream info",
OPT_BIT('m', NULL, &rename, "move/rename a branch and its reflog", 1),
OPT_BIT('M', NULL, &rename, "move/rename a branch, even if target exists", 2),
OPT_BOOLEAN('l', NULL, &reflog, "create the branch's reflog"),
- OPT_BOOLEAN('f', "force", &force_create, "force creation (when already exists)"),
+ OPT__FORCE(&force_create, "force creation (when already exists)"),
{
OPTION_CALLBACK, 0, "no-merged", &merge_filter_ref,
"commit", "print only not merged branches",
OPT_END(),
};
+ if (argc == 2 && !strcmp(argv[1], "-h"))
+ usage_with_options(builtin_branch_usage, options);
+
git_config(git_branch_config, NULL);
if (branch_use_color == -1)
index 65cbee0552b8e2d5ed7cfd57781031d72c1cdd98..f1fec24745a854e77555a1b4bea3bc8d7ab04367 100644 (file)
--- a/builtin/checkout-index.c
+++ b/builtin/checkout-index.c
struct option builtin_checkout_index_options[] = {
OPT_BOOLEAN('a', "all", &all,
"checks out all files in the index"),
- OPT_BOOLEAN('f', "force", &force,
- "forces overwrite of existing files"),
- OPT__QUIET(&quiet),
+ OPT__FORCE(&force, "forces overwrite of existing files"),
+ OPT__QUIET(&quiet,
+ "no warning for existing files and files not in index"),
OPT_BOOLEAN('n', "no-create", ¬_new,
"don't checkout new files"),
{ OPTION_CALLBACK, 'u', "index", &newfd, NULL,
OPT_END()
};
+ if (argc == 2 && !strcmp(argv[1], "-h"))
+ usage_with_options(builtin_checkout_index_usage,
+ builtin_checkout_index_options);
git_config(git_default_config, NULL);
state.base_dir = "";
prefix_length = prefix ? strlen(prefix) : 0;
diff --git a/builtin/checkout.c b/builtin/checkout.c
index a54583b3a4936b341820cf5c639e5647ce456a5a..cc97dbc30f1b064a44157897d9e325d934111cee 100644 (file)
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
int quiet;
int merge;
int force;
+ int force_detach;
int writeout_stage;
int writeout_error;
* merge.renormalize set, too
*/
status = ll_merge(&result_buf, path, &ancestor, "base",
- &ours, "ours", &theirs, "theirs", 0);
+ &ours, "ours", &theirs, "theirs", NULL);
free(ancestor.ptr);
free(ours.ptr);
free(theirs.ptr);
run_diff_index(&rev, 0);
}
-static void describe_detached_head(char *msg, struct commit *commit)
+static void describe_detached_head(const char *msg, struct commit *commit)
{
struct strbuf sb = STRBUF_INIT;
struct pretty_print_context ctx = {0};
topts.dir->exclude_per_dir = ".gitignore";
tree = parse_tree_indirect(old->commit ?
old->commit->object.sha1 :
- (unsigned char *)EMPTY_TREE_SHA1_BIN);
+ EMPTY_TREE_SHA1_BIN);
init_tree_desc(&trees[0], tree->buffer, tree->size);
tree = parse_tree_indirect(new->commit->object.sha1);
init_tree_desc(&trees[1], tree->buffer, tree->size);
strbuf_addf(&msg, "checkout: moving from %s to %s",
old_desc ? old_desc : "(invalid)", new->name);
- if (new->path) {
+ if (!strcmp(new->name, "HEAD") && !new->path && !opts->force_detach) {
+ /* Nothing to do. */
+ } else if (opts->force_detach || !new->path) { /* No longer on any branch. */
+ update_ref(msg.buf, "HEAD", new->commit->object.sha1, NULL,
+ REF_NODEREF, DIE_ON_ERR);
+ if (!opts->quiet) {
+ if (old->path && advice_detached_head)
+ detach_advice(old->path, new->name);
+ describe_detached_head("HEAD is now at", new->commit);
+ }
+ } else if (new->path) { /* Switch branches. */
create_symref("HEAD", new->path, msg.buf);
if (!opts->quiet) {
if (old->path && !strcmp(new->path, old->path))
if (!file_exists(ref_file) && file_exists(log_file))
remove_path(log_file);
}
- } else if (strcmp(new->name, "HEAD")) {
- update_ref(msg.buf, "HEAD", new->commit->object.sha1, NULL,
- REF_NODEREF, DIE_ON_ERR);
- if (!opts->quiet) {
- if (old->path && advice_detached_head)
- detach_advice(old->path, new->name);
- describe_detached_head("HEAD is now at", new->commit);
- }
}
remove_branch_state();
strbuf_release(&msg);
- if (!opts->quiet && (new->path || !strcmp(new->name, "HEAD")))
+ if (!opts->quiet &&
+ (new->path || (!opts->force_detach && !strcmp(new->name, "HEAD"))))
report_tracking(new);
}
return NULL;
}
+static int parse_branchname_arg(int argc, const char **argv,
+ int dwim_new_local_branch_ok,
+ struct branch_info *new,
+ struct tree **source_tree,
+ unsigned char rev[20],
+ const char **new_branch)
+{
+ int argcount = 0;
+ unsigned char branch_rev[20];
+ const char *arg;
+ int has_dash_dash;
+
+ /*
+ * case 1: git checkout <ref> -- [<paths>]
+ *
+ * <ref> must be a valid tree, everything after the '--' must be
+ * a path.
+ *
+ * case 2: git checkout -- [<paths>]
+ *
+ * everything after the '--' must be paths.
+ *
+ * case 3: git checkout <something> [<paths>]
+ *
+ * With no paths, if <something> is a commit, that is to
+ * switch to the branch or detach HEAD at it. As a special case,
+ * if <something> is A...B (missing A or B means HEAD but you can
+ * omit at most one side), and if there is a unique merge base
+ * between A and B, A...B names that merge base.
+ *
+ * With no paths, if <something> is _not_ a commit, no -t nor -b
+ * was given, and there is a tracking branch whose name is
+ * <something> in one and only one remote, then this is a short-hand
+ * to fork local <something> from that remote-tracking branch.
+ *
+ * Otherwise <something> shall not be ambiguous.
+ * - If it's *only* a reference, treat it like case (1).
+ * - If it's only a path, treat it like case (2).
+ * - else: fail.
+ *
+ */
+ if (!argc)
+ return 0;
+
+ if (!strcmp(argv[0], "--")) /* case (2) */
+ return 1;
+
+ arg = argv[0];
+ has_dash_dash = (argc > 1) && !strcmp(argv[1], "--");
+
+ if (!strcmp(arg, "-"))
+ arg = "@{-1}";
+
+ if (get_sha1_mb(arg, rev)) {
+ if (has_dash_dash) /* case (1) */
+ die("invalid reference: %s", arg);
+ if (dwim_new_local_branch_ok &&
+ !check_filename(NULL, arg) &&
+ argc == 1) {
+ const char *remote = unique_tracking_name(arg);
+ if (!remote || get_sha1(remote, rev))
+ return argcount;
+ *new_branch = arg;
+ arg = remote;
+ /* DWIMmed to create local branch */
+ } else {
+ return argcount;
+ }
+ }
+
+ /* we can't end up being in (2) anymore, eat the argument */
+ argcount++;
+ argv++;
+ argc--;
+
+ new->name = arg;
+ setup_branch_path(new);
+
+ if (check_ref_format(new->path) == CHECK_REF_FORMAT_OK &&
+ resolve_ref(new->path, branch_rev, 1, NULL))
+ hashcpy(rev, branch_rev);
+ else
+ new->path = NULL; /* not an existing branch */
+
+ new->commit = lookup_commit_reference_gently(rev, 1);
+ if (!new->commit) {
+ /* not a commit */
+ *source_tree = parse_tree_indirect(rev);
+ } else {
+ parse_commit(new->commit);
+ *source_tree = new->commit->tree;
+ }
+
+ if (!*source_tree) /* case (1): want a tree */
+ die("reference is not a tree: %s", arg);
+ if (!has_dash_dash) {/* case (3 -> 1) */
+ /*
+ * Do not complain the most common case
+ * git checkout branch
+ * even if there happen to be a file called 'branch';
+ * it would be extremely annoying.
+ */
+ if (argc)
+ verify_non_filename(NULL, arg);
+ } else {
+ argcount++;
+ argv++;
+ argc--;
+ }
+
+ return argcount;
+}
+
int cmd_checkout(int argc, const char **argv, const char *prefix)
{
struct checkout_opts opts;
unsigned char rev[20];
- const char *arg;
struct branch_info new;
struct tree *source_tree = NULL;
char *conflict_style = NULL;
int patch_mode = 0;
int dwim_new_local_branch = 1;
struct option options[] = {
- OPT__QUIET(&opts.quiet),
+ OPT__QUIET(&opts.quiet, "suppress progress reporting"),
OPT_STRING('b', NULL, &opts.new_branch, "branch",
"create and checkout a new branch"),
OPT_STRING('B', NULL, &opts.new_branch_force, "branch",
"create/reset and checkout a branch"),
OPT_BOOLEAN('l', NULL, &opts.new_branch_log, "create reflog for new branch"),
+ OPT_BOOLEAN(0, "detach", &opts.force_detach, "detach the HEAD at named commit"),
OPT_SET_INT('t', "track", &opts.track, "set upstream info for new branch",
BRANCH_TRACK_EXPLICIT),
OPT_STRING(0, "orphan", &opts.new_orphan_branch, "new branch", "new unparented branch"),
2),
OPT_SET_INT('3', "theirs", &opts.writeout_stage, "checkout their version for unmerged files",
3),
- OPT_BOOLEAN('f', "force", &opts.force, "force checkout (throw away local modifications)"),
+ OPT__FORCE(&opts.force, "force checkout (throw away local modifications)"),
OPT_BOOLEAN('m', "merge", &opts.merge, "perform a 3-way merge with the new branch"),
OPT_STRING(0, "conflict", &conflict_style, "style",
"conflict style (merge or diff3)"),
PARSE_OPT_NOARG | PARSE_OPT_HIDDEN },
OPT_END(),
};
- int has_dash_dash;
memset(&opts, 0, sizeof(opts));
memset(&new, 0, sizeof(new));
opts.new_branch = opts.new_branch_force;
if (patch_mode && (opts.track > 0 || opts.new_branch
- || opts.new_branch_log || opts.merge || opts.force))
+ || opts.new_branch_log || opts.merge || opts.force
+ || opts.force_detach))
die ("--patch is incompatible with all other options");
+ if (opts.force_detach && (opts.new_branch || opts.new_orphan_branch))
+ die("--detach cannot be used with -b/-B/--orphan");
+ if (opts.force_detach && 0 < opts.track)
+ die("--detach cannot be used with -t");
+
/* --track without -b should DWIM */
if (0 < opts.track && !opts.new_branch) {
const char *argv0 = argv[0];
die("git checkout: -f and -m are incompatible");
/*
- * case 1: git checkout <ref> -- [<paths>]
- *
- * <ref> must be a valid tree, everything after the '--' must be
- * a path.
- *
- * case 2: git checkout -- [<paths>]
- *
- * everything after the '--' must be paths.
- *
- * case 3: git checkout <something> [<paths>]
- *
- * With no paths, if <something> is a commit, that is to
- * switch to the branch or detach HEAD at it. As a special case,
- * if <something> is A...B (missing A or B means HEAD but you can
- * omit at most one side), and if there is a unique merge base
- * between A and B, A...B names that merge base.
+ * Extract branch name from command line arguments, so
+ * all that is left is pathspecs.
*
- * With no paths, if <something> is _not_ a commit, no -t nor -b
- * was given, and there is a tracking branch whose name is
- * <something> in one and only one remote, then this is a short-hand
- * to fork local <something> from that remote tracking branch.
+ * Handle
*
- * Otherwise <something> shall not be ambiguous.
- * - If it's *only* a reference, treat it like case (1).
- * - If it's only a path, treat it like case (2).
- * - else: fail.
+ * 1) git checkout <tree> -- [<paths>]
+ * 2) git checkout -- [<paths>]
+ * 3) git checkout <something> [<paths>]
*
+ * including "last branch" syntax and DWIM-ery for names of
+ * remote branches, erroring out for invalid or ambiguous cases.
*/
if (argc) {
- if (!strcmp(argv[0], "--")) { /* case (2) */
- argv++;
- argc--;
- goto no_reference;
- }
-
- arg = argv[0];
- has_dash_dash = (argc > 1) && !strcmp(argv[1], "--");
-
- if (!strcmp(arg, "-"))
- arg = "@{-1}";
-
- if (get_sha1_mb(arg, rev)) {
- if (has_dash_dash) /* case (1) */
- die("invalid reference: %s", arg);
- if (!patch_mode &&
- dwim_new_local_branch &&
- opts.track == BRANCH_TRACK_UNSPECIFIED &&
- !opts.new_branch &&
- !check_filename(NULL, arg) &&
- argc == 1) {
- const char *remote = unique_tracking_name(arg);
- if (!remote || get_sha1(remote, rev))
- goto no_reference;
- opts.new_branch = arg;
- arg = remote;
- /* DWIMmed to create local branch */
- }
- else
- goto no_reference;
- }
-
- /* we can't end up being in (2) anymore, eat the argument */
- argv++;
- argc--;
-
- new.name = arg;
- if ((new.commit = lookup_commit_reference_gently(rev, 1))) {
- setup_branch_path(&new);
-
- if ((check_ref_format(new.path) == CHECK_REF_FORMAT_OK) &&
- resolve_ref(new.path, rev, 1, NULL))
- ;
- else
- new.path = NULL;
- parse_commit(new.commit);
- source_tree = new.commit->tree;
- } else
- source_tree = parse_tree_indirect(rev);
-
- if (!source_tree) /* case (1): want a tree */
- die("reference is not a tree: %s", arg);
- if (!has_dash_dash) {/* case (3 -> 1) */
- /*
- * Do not complain the most common case
- * git checkout branch
- * even if there happen to be a file called 'branch';
- * it would be extremely annoying.
- */
- if (argc)
- verify_non_filename(NULL, arg);
- }
- else {
- argv++;
- argc--;
- }
+ int dwim_ok =
+ !patch_mode &&
+ dwim_new_local_branch &&
+ opts.track == BRANCH_TRACK_UNSPECIFIED &&
+ !opts.new_branch;
+ int n = parse_branchname_arg(argc, argv, dwim_ok,
+ &new, &source_tree, rev, &opts.new_branch);
+ argv += n;
+ argc -= n;
}
-no_reference:
-
if (opts.track == BRANCH_TRACK_UNSPECIFIED)
opts.track = git_branch_track;
}
}
+ if (opts.force_detach)
+ die("git checkout: --detach does not take a path argument");
+
if (1 < !!opts.writeout_stage + !!opts.force + !!opts.merge)
die("git checkout: --ours/--theirs, --force and --merge are incompatible when\nchecking out of the index.");
diff --git a/builtin/clean.c b/builtin/clean.c
index fb24030751ec5e755009a97439ffa08ff55f67d5..4a312abc6b3ecef5f174b2786ad31385ba058b44 100644 (file)
--- a/builtin/clean.c
+++ b/builtin/clean.c
const char *qname;
char *seen = NULL;
struct option options[] = {
- OPT__QUIET(&quiet),
- OPT__DRY_RUN(&show_only),
- OPT_BOOLEAN('f', "force", &force, "force"),
+ OPT__QUIET(&quiet, "do not print names of files removed"),
+ OPT__DRY_RUN(&show_only, "dry run"),
+ OPT__FORCE(&force, "force"),
OPT_BOOLEAN('d', NULL, &remove_directories,
"remove whole directories"),
{ OPTION_CALLBACK, 'e', "exclude", &exclude_list, "pattern",
diff --git a/builtin/clone.c b/builtin/clone.c
index 19ed64041d0575ee1ad49071dfe1e671114b8ae3..2ee1fa984620de4fad79ce94d4f993f5e83a053d 100644 (file)
--- a/builtin/clone.c
+++ b/builtin/clone.c
"setup as shared repository"),
OPT_BOOLEAN(0, "recursive", &option_recursive,
"initialize submodules in the clone"),
- OPT_STRING(0, "template", &option_template, "path",
- "path the template repository"),
+ OPT_BOOLEAN(0, "recurse-submodules", &option_recursive,
+ "initialize submodules in the clone"),
+ OPT_STRING(0, "template", &option_template, "template-directory",
+ "directory from which templates will be used"),
OPT_STRING(0, "reference", &option_reference, "repo",
"reference repository"),
OPT_STRING('o', "origin", &option_origin, "branch",
if (path)
repo = xstrdup(make_nonrelative_path(repo_name));
else if (!strchr(repo_name, ':'))
- repo = xstrdup(make_absolute_path(repo_name));
+ die("repository '%s' does not exist", repo_name);
else
repo = repo_name;
is_local = path && !is_bundle;
diff --git a/builtin/commit-tree.c b/builtin/commit-tree.c
index e06573920f461229717b885e291c76831f87e428..d083795e26e7893c6b7d466f9bcf2be1311cfeb2 100644 (file)
--- a/builtin/commit-tree.c
+++ b/builtin/commit-tree.c
if (strbuf_read(&buffer, 0, 0) < 0)
die_errno("git commit-tree: failed to read");
- if (!commit_tree(buffer.buf, tree_sha1, parents, commit_sha1, NULL)) {
- printf("%s\n", sha1_to_hex(commit_sha1));
- return 0;
- }
- else
+ if (commit_tree(buffer.buf, tree_sha1, parents, commit_sha1, NULL)) {
+ strbuf_release(&buffer);
return 1;
+ }
+
+ printf("%s\n", sha1_to_hex(commit_sha1));
+ strbuf_release(&buffer);
+ return 0;
}
diff --git a/builtin/commit.c b/builtin/commit.c
index cc16e3f292403814e137078fd46927eeaecf0d36..d71e1e0c9c27c4d03cd02b4deaacec67af33917a 100644 (file)
--- a/builtin/commit.c
+++ b/builtin/commit.c
static const char *logfile, *force_author;
static const char *template_file;
static char *edit_message, *use_message;
+static char *fixup_message, *squash_message;
static int all, edit_flag, also, interactive, only, amend, signoff;
static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
static int no_post_rewrite, allow_empty_message;
}
static struct option builtin_commit_options[] = {
- OPT__QUIET(&quiet),
- OPT__VERBOSE(&verbose),
+ OPT__QUIET(&quiet, "suppress summary after successful commit"),
+ OPT__VERBOSE(&verbose, "show diff in commit message template"),
OPT_GROUP("Commit message options"),
- OPT_FILENAME('F', "file", &logfile, "read log from file"),
- OPT_STRING(0, "author", &force_author, "AUTHOR", "override author for commit"),
- OPT_STRING(0, "date", &force_date, "DATE", "override date for commit"),
- OPT_CALLBACK('m', "message", &message, "MESSAGE", "specify commit message", opt_parse_m),
- OPT_STRING('c', "reedit-message", &edit_message, "COMMIT", "reuse and edit message from specified commit"),
- OPT_STRING('C', "reuse-message", &use_message, "COMMIT", "reuse message from specified commit"),
+ OPT_FILENAME('F', "file", &logfile, "read message from file"),
+ OPT_STRING(0, "author", &force_author, "author", "override author for commit"),
+ OPT_STRING(0, "date", &force_date, "date", "override date for commit"),
+ OPT_CALLBACK('m', "message", &message, "message", "commit message", opt_parse_m),
+ OPT_STRING('c', "reedit-message", &edit_message, "commit", "reuse and edit message from specified commit"),
+ OPT_STRING('C', "reuse-message", &use_message, "commit", "reuse message from specified commit"),
+ OPT_STRING(0, "fixup", &fixup_message, "commit", "use autosquash formatted message to fixup specified commit"),
+ OPT_STRING(0, "squash", &squash_message, "commit", "use autosquash formatted message to squash specified commit"),
OPT_BOOLEAN(0, "reset-author", &renew_authorship, "the commit is authored by me now (used with -C-c/--amend)"),
OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"),
OPT_FILENAME('t', "template", &template_file, "use specified template file"),
STATUS_FORMAT_SHORT),
OPT_BOOLEAN(0, "branch", &status_show_branch, "show branch information"),
OPT_SET_INT(0, "porcelain", &status_format,
- "show porcelain output format", STATUS_FORMAT_PORCELAIN),
+ "machine-readable output", STATUS_FORMAT_PORCELAIN),
OPT_BOOLEAN('z', "null", &null_termination,
"terminate entries with NUL"),
OPT_BOOLEAN(0, "amend", &amend, "amend previous commit"),
OPT_BOOLEAN(0, "no-post-rewrite", &no_post_rewrite, "bypass post-rewrite hook"),
- { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, "mode", "show untracked files, optional modes: all, normal, no (Default: all)", PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
+ { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, "mode", "show untracked files, optional modes: all, normal, no. (Default: all)", PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
/* end commit contents options */
{ OPTION_BOOLEAN, 0, "allow-empty", &allow_empty, NULL,
if (!no_verify && run_hook(index_file, "pre-commit", NULL))
return 0;
+ if (squash_message) {
+ /*
+ * Insert the proper subject line before other commit
+ * message options add their content.
+ */
+ if (use_message && !strcmp(use_message, squash_message))
+ strbuf_addstr(&sb, "squash! ");
+ else {
+ struct pretty_print_context ctx = {0};
+ struct commit *c;
+ c = lookup_commit_reference_by_name(squash_message);
+ if (!c)
+ die("could not lookup commit %s", squash_message);
+ ctx.output_encoding = get_commit_output_encoding();
+ format_commit_message(c, "squash! %s\n\n", &sb,
+ &ctx);
+ }
+ }
+
if (message.len) {
strbuf_addbuf(&sb, &message);
hook_arg1 = "message";
strbuf_add(&sb, buffer + 2, strlen(buffer + 2));
hook_arg1 = "commit";
hook_arg2 = use_message;
+ } else if (fixup_message) {
+ struct pretty_print_context ctx = {0};
+ struct commit *commit;
+ commit = lookup_commit_reference_by_name(fixup_message);
+ if (!commit)
+ die("could not lookup commit %s", fixup_message);
+ ctx.output_encoding = get_commit_output_encoding();
+ format_commit_message(commit, "fixup! %s\n\n",
+ &sb, &ctx);
+ hook_arg1 = "message";
} else if (!stat(git_path("MERGE_MSG"), &statbuf)) {
if (strbuf_read_file(&sb, git_path("MERGE_MSG"), 0) < 0)
die_errno("could not read MERGE_MSG");
else if (in_merge)
hook_arg1 = "merge";
+ if (squash_message) {
+ /*
+ * If squash_commit was used for the commit subject,
+ * then we're possibly hijacking other commit log options.
+ * Reset the hook args to tell the real story.
+ */
+ hook_arg1 = "message";
+ hook_arg2 = "";
+ }
+
fp = fopen(git_path(commit_editmsg), "w");
if (fp == NULL)
die_errno("could not open '%s'", git_path(commit_editmsg));
if (force_author && renew_authorship)
die("Using both --reset-author and --author does not make sense");
- if (logfile || message.len || use_message)
+ if (logfile || message.len || use_message || fixup_message)
use_editor = 0;
if (edit_flag)
use_editor = 1;
die("You have nothing to amend.");
if (amend && in_merge)
die("You are in the middle of a merge -- cannot amend.");
-
+ if (fixup_message && squash_message)
+ die("Options --squash and --fixup cannot be used together");
if (use_message)
f++;
if (edit_message)
f++;
+ if (fixup_message)
+ f++;
if (logfile)
f++;
if (f > 1)
- die("Only one of -c/-C/-F can be used.");
+ die("Only one of -c/-C/-F/--fixup can be used.");
if (message.len && f > 0)
- die("Option -m cannot be combined with -c/-C/-F.");
+ die("Option -m cannot be combined with -c/-C/-F/--fixup.");
if (edit_message)
use_message = edit_message;
- if (amend && !use_message)
+ if (amend && !use_message && !fixup_message)
use_message = "HEAD";
if (!use_message && renew_authorship)
die("--reset-author can be used only with -C, -c or --amend.");
if (use_message) {
- unsigned char sha1[20];
- static char utf8[] = "UTF-8";
const char *out_enc;
- char *enc, *end;
struct commit *commit;
- if (get_sha1(use_message, sha1))
+ commit = lookup_commit_reference_by_name(use_message);
+ if (!commit)
die("could not lookup commit %s", use_message);
- commit = lookup_commit_reference(sha1);
- if (!commit || parse_commit(commit))
- die("could not parse commit %s", use_message);
-
- enc = strstr(commit->buffer, "\nencoding");
- if (enc) {
- end = strchr(enc + 10, '\n');
- enc = xstrndup(enc + 10, end - (enc + 10));
- } else {
- enc = utf8;
- }
- out_enc = git_commit_encoding ? git_commit_encoding : utf8;
-
- if (strcmp(out_enc, enc))
- use_message_buffer =
- reencode_string(commit->buffer, out_enc, enc);
+ out_enc = get_commit_output_encoding();
+ use_message_buffer = logmsg_reencode(commit, out_enc);
/*
* If we failed to reencode the buffer, just copy it
*/
if (use_message_buffer == NULL)
use_message_buffer = xstrdup(commit->buffer);
- if (enc != utf8)
- free(enc);
}
if (!!also + !!only + !!all + !!interactive > 1)
{
if (!strcasecmp(var+offset, "header"))
return WT_STATUS_HEADER;
+ if (!strcasecmp(var+offset, "branch"))
+ return WT_STATUS_ONBRANCH;
if (!strcasecmp(var+offset, "updated")
|| !strcasecmp(var+offset, "added"))
return WT_STATUS_UPDATED;
int fd;
unsigned char sha1[20];
static struct option builtin_status_options[] = {
- OPT__VERBOSE(&verbose),
+ OPT__VERBOSE(&verbose, "be verbose"),
OPT_SET_INT('s', "short", &status_format,
"show status concisely", STATUS_FORMAT_SHORT),
OPT_BOOLEAN('b', "branch", &status_show_branch,
"show branch information"),
OPT_SET_INT(0, "porcelain", &status_format,
- "show porcelain output format",
+ "machine-readable output",
STATUS_FORMAT_PORCELAIN),
OPT_BOOLEAN('z', "null", &null_termination,
"terminate entries with NUL"),
OPT_END(),
};
+ if (argc == 2 && !strcmp(argv[1], "-h"))
+ usage_with_options(builtin_status_usage, builtin_status_options);
+
if (null_termination && status_format == STATUS_FORMAT_LONG)
status_format = STATUS_FORMAT_PORCELAIN;
int allow_fast_forward = 1;
struct wt_status s;
+ if (argc == 2 && !strcmp(argv[1], "-h"))
+ usage_with_options(builtin_commit_usage, builtin_commit_options);
+
wt_status_prepare(&s);
git_config(git_commit_config, &s);
in_merge = file_exists(git_path("MERGE_HEAD"));
diff --git a/builtin/config.c b/builtin/config.c
index ca4a0db4a79241d7ded005483b4693967eea636f..76be0b786fd52f181f91b5ad3bdef891068946f1 100644 (file)
--- a/builtin/config.c
+++ b/builtin/config.c
OPT_BOOLEAN(0, "global", &use_global_config, "use global config file"),
OPT_BOOLEAN(0, "system", &use_system_config, "use system config file"),
OPT_BOOLEAN(0, "local", &use_local_config, "use repository config file"),
- OPT_STRING('f', "file", &given_config_file, "FILE", "use given config file"),
+ OPT_STRING('f', "file", &given_config_file, "file", "use given config file"),
OPT_GROUP("Action"),
OPT_BIT(0, "get", &actions, "get value: name [value-regex]", ACTION_GET),
OPT_BIT(0, "get-all", &actions, "get all values: key [value-regex]", ACTION_GET_ALL),
static int get_value(const char *key_, const char *regex_)
{
int ret = -1;
- char *tl;
char *global = NULL, *repo_config = NULL;
const char *system_wide = NULL, *local;
system_wide = git_etc_gitconfig();
}
- key = xstrdup(key_);
- for (tl=key+strlen(key)-1; tl >= key && *tl != '.'; --tl)
- *tl = tolower(*tl);
- for (tl=key; *tl && *tl != '.'; ++tl)
- *tl = tolower(*tl);
-
if (use_key_regexp) {
+ char *tl;
+
+ /*
+ * NEEDSWORK: this naive pattern lowercasing obviously does not
+ * work for more complex patterns like "^[^.]*Foo.*bar".
+ * Perhaps we should deprecate this altogether someday.
+ */
+
+ key = xstrdup(key_);
+ for (tl = key + strlen(key) - 1;
+ tl >= key && *tl != '.';
+ tl--)
+ *tl = tolower(*tl);
+ for (tl = key; *tl && *tl != '.'; tl++)
+ *tl = tolower(*tl);
+
key_regexp = (regex_t*)xmalloc(sizeof(regex_t));
if (regcomp(key_regexp, key, REG_EXTENDED)) {
fprintf(stderr, "Invalid key pattern: %s\n", key_);
+ free(key);
goto free_strings;
}
+ } else {
+ if (git_config_parse_key(key_, &key, NULL))
+ goto free_strings;
}
if (regex_) {
return 0;
}
+
+int cmd_repo_config(int argc, const char **argv, const char *prefix)
+{
+ fprintf(stderr, "WARNING: git repo-config is deprecated in favor of git config.\n");
+ return cmd_config(argc, argv, prefix);
+}
index 2bdd8ebde1002e852055385396ef053f26c91ae8..c37cb98c31ddfaa90d38a0355d32cf3d6c404bff 100644 (file)
--- a/builtin/count-objects.c
+++ b/builtin/count-objects.c
unsigned long loose = 0, packed = 0, packed_loose = 0, garbage = 0;
off_t loose_size = 0;
struct option opts[] = {
- OPT__VERBOSE(&verbose),
+ OPT__VERBOSE(&verbose, "be verbose"),
OPT_END(),
};
diff --git a/builtin/describe.c b/builtin/describe.c
index a0f52c1b72b2ffd95e781abffb78d7b66ec583c7..3ba26dc8192d0a75d7e330d2f73d0d9841a6216c 100644 (file)
--- a/builtin/describe.c
+++ b/builtin/describe.c
return n;
}
-static int set_util(void *chain)
+static int set_util(void *chain, void *data)
{
struct commit_name *n;
for (n = chain; n; n = n->next) {
struct commit *p = parents->item;
parse_commit(p);
if (!(p->object.flags & SEEN))
- insert_by_date(p, list);
+ commit_list_insert_by_date(p, list);
p->object.flags |= c->object.flags;
parents = parents->next;
}
fprintf(stderr, "searching to describe %s\n", arg);
if (!have_util) {
- for_each_hash(&names, set_util);
+ for_each_hash(&names, set_util, NULL);
have_util = 1;
}
struct commit *p = parents->item;
parse_commit(p);
if (!(p->object.flags & SEEN))
- insert_by_date(p, &list);
+ commit_list_insert_by_date(p, &list);
p->object.flags |= c->object.flags;
parents = parents->next;
}
qsort(all_matches, match_cnt, sizeof(all_matches[0]), compare_pt);
if (gave_up_on) {
- insert_by_date(gave_up_on, &list);
+ commit_list_insert_by_date(gave_up_on, &list);
seen_commits--;
}
seen_commits += finish_depth_computation(&list, &all_matches[0]);
diff --git a/builtin/diff-files.c b/builtin/diff-files.c
index 951c7c8994704543fc1784c87a17a1aa47ede257..46085f862f937b005493319cea25b93bcb10c999 100644 (file)
--- a/builtin/diff-files.c
+++ b/builtin/diff-files.c
(rev.diffopt.output_format & DIFF_FORMAT_PATCH))
rev.combine_merges = rev.dense_combined_merges = 1;
- if (read_cache_preload(rev.diffopt.paths) < 0) {
+ if (read_cache_preload(rev.diffopt.pathspec.raw) < 0) {
perror("read_cache_preload");
return -1;
}
diff --git a/builtin/diff.c b/builtin/diff.c
index a43d3263638daf083c52f37f3bd0872327d7459b..4c9deb28ec15d0c2adae795cc2c177b24256e585 100644 (file)
--- a/builtin/diff.c
+++ b/builtin/diff.c
};
static const char builtin_diff_usage[] =
-"git diff <options> <rev>{0,2} -- <path>*";
+"git diff [<options>] [<commit> [<commit>]] [--] [<path>...]";
static void stuff_change(struct diff_options *opt,
unsigned old_mode, unsigned new_mode,
revs->max_count != -1 || revs->min_age != -1 ||
revs->max_age != -1)
usage(builtin_diff_usage);
- if (read_cache_preload(revs->diffopt.paths) < 0) {
+ if (read_cache_preload(revs->diffopt.pathspec.raw) < 0) {
perror("read_cache_preload");
return -1;
}
@@ -237,7 +237,7 @@ static int builtin_diff_files(struct rev_info *revs, int argc, const char **argv
revs->combine_merges = revs->dense_combined_merges = 1;
setup_work_tree();
- if (read_cache_preload(revs->diffopt.paths) < 0) {
+ if (read_cache_preload(revs->diffopt.pathspec.raw) < 0) {
perror("read_cache_preload");
return -1;
}
else if (!strcmp(arg, "--cached") ||
!strcmp(arg, "--staged")) {
add_head_to_pending(&rev);
- if (!rev.pending.nr)
- die("No HEAD commit to compare with (yet)");
+ if (!rev.pending.nr) {
+ struct tree *tree;
+ tree = lookup_tree((const unsigned char*)EMPTY_TREE_SHA1_BIN);
+ add_pending_object(&rev, &tree->object, "HEAD");
+ }
break;
}
}
}
die("unhandled object '%s' given.", name);
}
- if (rev.prune_data) {
- const char **pathspec = rev.prune_data;
- while (*pathspec) {
- if (!path)
- path = *pathspec;
- paths++;
- pathspec++;
- }
+ if (rev.prune_data.nr) {
+ if (!path)
+ path = rev.prune_data.items[0].match;
+ paths += rev.prune_data.nr;
}
/*
diff --git a/builtin/fast-export.c b/builtin/fast-export.c
index c8fd46b872780b27b09ad70316fa164d855f3220..daf19451ba7df541d1314c880679749232ccce35 100644 (file)
--- a/builtin/fast-export.c
+++ b/builtin/fast-export.c
OPT_CALLBACK(0, "tag-of-filtered-object", &tag_of_filtered_mode, "mode",
"select handling of tags that tag filtered objects",
parse_opt_tag_of_filtered_mode),
- OPT_STRING(0, "export-marks", &export_filename, "FILE",
+ OPT_STRING(0, "export-marks", &export_filename, "file",
"Dump marks to this file"),
- OPT_STRING(0, "import-marks", &import_filename, "FILE",
+ OPT_STRING(0, "import-marks", &import_filename, "file",
"Import marks from this file"),
OPT_BOOLEAN(0, "fake-missing-tagger", &fake_missing_tagger,
"Fake a tagger when tags lack one"),
if (import_filename)
import_marks(import_filename);
- if (import_filename && revs.prune_data)
+ if (import_filename && revs.prune_data.nr)
full_tree = 1;
get_tags_and_duplicates(&revs.pending, &extra_refs);
diff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c
index dbd8b7bcc8b5ddea9bec3a0c774346007cb0d31c..b9994139345834a58b08a5ce57cf59c124e21760 100644 (file)
--- a/builtin/fetch-pack.c
+++ b/builtin/fetch-pack.c
if (parse_commit(commit))
return;
- insert_by_date(commit, &rev_list);
+ commit_list_insert_by_date(commit, &rev_list);
if (!(commit->object.flags & COMMON))
non_common_revs++;
if (o && o->type == OBJ_COMMIT) {
struct commit *commit = (struct commit *)o;
commit->object.flags |= COMPLETE;
- insert_by_date(commit, &complete);
+ commit_list_insert_by_date(commit, &complete);
}
return 0;
}
diff --git a/builtin/fetch.c b/builtin/fetch.c
index d35f000c034964ed600b1d8d45e2ef9260abcfb6..7efecfe1cf6b14a53d619b4bcc4760c81a492747 100644 (file)
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
#include "parse-options.h"
#include "sigchain.h"
#include "transport.h"
+#include "submodule.h"
static const char * const builtin_fetch_usage[] = {
"git fetch [<options>] [<repository> [<refspec>...]]",
TAGS_SET = 2
};
+enum {
+ RECURSE_SUBMODULES_OFF = 0,
+ RECURSE_SUBMODULES_DEFAULT = 1,
+ RECURSE_SUBMODULES_ON = 2
+};
+
static int all, append, dry_run, force, keep, multiple, prune, update_head_ok, verbosity;
-static int progress;
+static int progress, recurse_submodules = RECURSE_SUBMODULES_DEFAULT;
static int tags = TAGS_DEFAULT;
static const char *depth;
static const char *upload_pack;
static struct strbuf default_rla = STRBUF_INIT;
static struct transport *transport;
+static const char *submodule_prefix = "";
static struct option builtin_fetch_options[] = {
OPT__VERBOSITY(&verbosity),
"fetch from all remotes"),
OPT_BOOLEAN('a', "append", &append,
"append to .git/FETCH_HEAD instead of overwriting"),
- OPT_STRING(0, "upload-pack", &upload_pack, "PATH",
+ OPT_STRING(0, "upload-pack", &upload_pack, "path",
"path to upload pack on remote end"),
- OPT_BOOLEAN('f', "force", &force,
- "force overwrite of local branch"),
+ OPT__FORCE(&force, "force overwrite of local branch"),
OPT_BOOLEAN('m', "multiple", &multiple,
"fetch from multiple remotes"),
OPT_SET_INT('t', "tags", &tags,
OPT_SET_INT('n', NULL, &tags,
"do not fetch all tags (--no-tags)", TAGS_UNSET),
OPT_BOOLEAN('p', "prune", &prune,
- "prune tracking branches no longer on remote"),
+ "prune remote-tracking branches no longer on remote"),
+ OPT_SET_INT(0, "recurse-submodules", &recurse_submodules,
+ "control recursive fetching of submodules",
+ RECURSE_SUBMODULES_ON),
OPT_BOOLEAN(0, "dry-run", &dry_run,
"dry run"),
OPT_BOOLEAN('k', "keep", &keep, "keep downloaded pack"),
OPT_BOOLEAN('u', "update-head-ok", &update_head_ok,
"allow updating of HEAD ref"),
OPT_BOOLEAN(0, "progress", &progress, "force progress reporting"),
- OPT_STRING(0, "depth", &depth, "DEPTH",
+ OPT_STRING(0, "depth", &depth, "depth",
"deepen history of shallow clone"),
+ { OPTION_STRING, 0, "submodule-prefix", &submodule_prefix, "dir",
+ "prepend this to submodule path output", PARSE_OPT_HIDDEN },
OPT_END()
};
continue;
/*
- * Not fetched to a tracking branch? We need to fetch
+ * Not fetched to a remote-tracking branch? We need to fetch
* it anyway to allow this branch's "branch.$name.merge"
* to be honored by 'git pull', but we do not have to
* fail if branch.$name.merge is misconfigured to point
what = rm->name + 10;
}
else if (!prefixcmp(rm->name, "refs/remotes/")) {
- kind = "remote branch";
+ kind = "remote-tracking branch";
what = rm->name + 13;
}
else {
return 1;
}
-static int fetch_multiple(struct string_list *list)
+static void add_options_to_argv(int *argc, const char **argv)
{
- int i, result = 0;
- const char *argv[11] = { "fetch", "--append" };
- int argc = 2;
-
if (dry_run)
- argv[argc++] = "--dry-run";
+ argv[(*argc)++] = "--dry-run";
if (prune)
- argv[argc++] = "--prune";
+ argv[(*argc)++] = "--prune";
if (update_head_ok)
- argv[argc++] = "--update-head-ok";
+ argv[(*argc)++] = "--update-head-ok";
if (force)
- argv[argc++] = "--force";
+ argv[(*argc)++] = "--force";
if (keep)
- argv[argc++] = "--keep";
+ argv[(*argc)++] = "--keep";
+ if (recurse_submodules == RECURSE_SUBMODULES_ON)
+ argv[(*argc)++] = "--recurse-submodules";
if (verbosity >= 2)
- argv[argc++] = "-v";
+ argv[(*argc)++] = "-v";
if (verbosity >= 1)
- argv[argc++] = "-v";
+ argv[(*argc)++] = "-v";
else if (verbosity < 0)
- argv[argc++] = "-q";
+ argv[(*argc)++] = "-q";
+
+}
+
+static int fetch_multiple(struct string_list *list)
+{
+ int i, result = 0;
+ const char *argv[12] = { "fetch", "--append" };
+ int argc = 2;
+
+ add_options_to_argv(&argc, argv);
if (!append && !dry_run) {
int errcode = truncate_fetch_head();
}
}
+ if (!result && (recurse_submodules != RECURSE_SUBMODULES_OFF)) {
+ const char *options[10];
+ int num_options = 0;
+ /* Set recursion as default when we already are recursing */
+ if (submodule_prefix[0])
+ set_config_fetch_recurse_submodules(1);
+ gitmodules_config();
+ git_config(submodule_config, NULL);
+ add_options_to_argv(&num_options, options);
+ result = fetch_populated_submodules(num_options, options,
+ submodule_prefix,
+ recurse_submodules == RECURSE_SUBMODULES_ON,
+ verbosity < 0);
+ }
+
/* All names were strdup()ed or strndup()ed */
list.strdup_strings = 1;
string_list_clear(&list, 0);
index e7e12eea250691a2c0d70964d1fffea0923dd292..5189b16c9e59c20b449435c413c34da9f7bf9baa 100644 (file)
--- a/builtin/fmt-merge-msg.c
+++ b/builtin/fmt-merge-msg.c
#include "string-list.h"
static const char * const fmt_merge_msg_usage[] = {
- "git fmt-merge-msg [-m <message>] [--log|--no-log] [--file <file>]",
+ "git fmt-merge-msg [-m <message>] [--log[=<n>]|--no-log] [--file <file>]",
NULL
};
-static int merge_summary;
+static int shortlog_len;
static int fmt_merge_msg_config(const char *key, const char *value, void *cb)
{
- static int found_merge_log = 0;
- if (!strcmp("merge.log", key)) {
- found_merge_log = 1;
- merge_summary = git_config_bool(key, value);
+ if (!strcmp(key, "merge.log") || !strcmp(key, "merge.summary")) {
+ int is_bool;
+ shortlog_len = git_config_bool_or_int(key, value, &is_bool);
+ if (!is_bool && shortlog_len < 0)
+ return error("%s: negative length %s", key, value);
+ if (is_bool && shortlog_len)
+ shortlog_len = DEFAULT_MERGE_LOG_LEN;
}
- if (!found_merge_log && !strcmp("merge.summary", key))
- merge_summary = git_config_bool(key, value);
return 0;
}
origin = line;
string_list_append(&src_data->tag, origin + 4);
src_data->head_status |= 2;
- } else if (!prefixcmp(line, "remote branch ")) {
- origin = line + 14;
+ } else if (!prefixcmp(line, "remote-tracking branch ")) {
+ origin = line + strlen("remote-tracking branch ");
string_list_append(&src_data->r_branch, origin);
src_data->head_status |= 2;
} else {
if (src_data->r_branch.nr) {
strbuf_addstr(out, subsep);
subsep = ", ";
- print_joined("remote branch ", "remote branches ",
+ print_joined("remote-tracking branch ", "remote-tracking branches ",
&src_data->r_branch, out);
}
if (src_data->tag.nr) {
strbuf_addf(out, " into %s\n", current_branch);
}
-static int do_fmt_merge_msg(int merge_title, int merge_summary,
- struct strbuf *in, struct strbuf *out) {
- int limit = 20, i = 0, pos = 0;
+static int do_fmt_merge_msg(int merge_title, struct strbuf *in,
+ struct strbuf *out, int shortlog_len) {
+ int i = 0, pos = 0;
unsigned char head_sha1[20];
const char *current_branch;
if (merge_title)
do_fmt_merge_msg_title(out, current_branch);
- if (merge_summary) {
+ if (shortlog_len) {
struct commit *head;
struct rev_info rev;
for (i = 0; i < origins.nr; i++)
shortlog(origins.items[i].string, origins.items[i].util,
- head, &rev, limit, out);
+ head, &rev, shortlog_len, out);
}
return 0;
}
-int fmt_merge_msg(int merge_summary, struct strbuf *in, struct strbuf *out) {
- return do_fmt_merge_msg(1, merge_summary, in, out);
-}
-
-int fmt_merge_msg_shortlog(struct strbuf *in, struct strbuf *out) {
- return do_fmt_merge_msg(0, 1, in, out);
+int fmt_merge_msg(struct strbuf *in, struct strbuf *out,
+ int merge_title, int shortlog_len) {
+ return do_fmt_merge_msg(merge_title, in, out, shortlog_len);
}
int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix)
const char *inpath = NULL;
const char *message = NULL;
struct option options[] = {
- OPT_BOOLEAN(0, "log", &merge_summary, "populate log with the shortlog"),
- { OPTION_BOOLEAN, 0, "summary", &merge_summary, NULL,
+ { OPTION_INTEGER, 0, "log", &shortlog_len, "n",
+ "populate log with at most <n> entries from shortlog",
+ PARSE_OPT_OPTARG, NULL, DEFAULT_MERGE_LOG_LEN },
+ { OPTION_INTEGER, 0, "summary", &shortlog_len, "n",
"alias for --log (deprecated)",
- PARSE_OPT_NOARG | PARSE_OPT_HIDDEN },
+ PARSE_OPT_OPTARG | PARSE_OPT_HIDDEN, NULL,
+ DEFAULT_MERGE_LOG_LEN },
OPT_STRING('m', "message", &message, "text",
"use <text> as start of message"),
OPT_FILENAME('F', "file", &inpath, "file to read from"),
0);
if (argc > 0)
usage_with_options(fmt_merge_msg_usage, options);
- if (message && !merge_summary) {
+ if (message && !shortlog_len) {
char nl = '\n';
write_in_full(STDOUT_FILENO, message, strlen(message));
write_in_full(STDOUT_FILENO, &nl, 1);
return 0;
}
+ if (shortlog_len < 0)
+ die("Negative --log=%d", shortlog_len);
if (inpath && strcmp(inpath, "-")) {
in = fopen(inpath, "r");
if (strbuf_read(&input, fileno(in), 0) < 0)
die_errno("could not read input file");
- if (message) {
+
+ if (message)
strbuf_addstr(&output, message);
- ret = fmt_merge_msg_shortlog(&input, &output);
- } else {
- ret = fmt_merge_msg(merge_summary, &input, &output);
- }
+ ret = fmt_merge_msg(&input, &output,
+ message ? 0 : 1,
+ shortlog_len);
+
if (ret)
return ret;
write_in_full(STDOUT_FILENO, output.buf, output.len);
diff --git a/builtin/fsck.c b/builtin/fsck.c
index 0929c7f245cbe85a4a68374850871a0ffbcdeec3..795aba087fe9c6ce0e644526d662a561b7f13f31 100644 (file)
--- a/builtin/fsck.c
+++ b/builtin/fsck.c
{
struct object *parent = data;
+ /*
+ * The only case data is NULL or type is OBJ_ANY is when
+ * mark_object_reachable() calls us. All the callers of
+ * that function has non-NULL obj hence ...
+ */
if (!obj) {
+ /* ... these references to parent->fld are safe here */
printf("broken link from %7s %s\n",
typename(parent->type), sha1_to_hex(parent->sha1));
printf("broken link from %7s %s\n",
}
if (type != OBJ_ANY && obj->type != type)
+ /* ... and the reference to parent is safe here */
objerror(parent, "wrong object type in link");
if (obj->flags & REACHABLE)
mark_object(obj, OBJ_ANY, NULL);
}
-static int traverse_one_object(struct object *obj, struct object *parent)
+static int traverse_one_object(struct object *obj)
{
int result;
struct tree *tree = NULL;
entry = pending.objects + --pending.nr;
obj = entry->item;
parent = (struct object *) entry->name;
- result |= traverse_one_object(obj, parent);
+ result |= traverse_one_object(obj);
}
return !!result;
}
sha1_list.nr = ++nr;
}
+static inline int is_loose_object_file(struct dirent *de,
+ char *name, unsigned char *sha1)
+{
+ if (strlen(de->d_name) != 38)
+ return 0;
+ memcpy(name + 2, de->d_name, 39);
+ return !get_sha1_hex(name, sha1);
+}
+
static void fsck_dir(int i, char *path)
{
DIR *dir = opendir(path);
struct dirent *de;
+ char name[100];
if (!dir)
return;
if (verbose)
fprintf(stderr, "Checking directory %s\n", path);
+ sprintf(name, "%02x", i);
while ((de = readdir(dir)) != NULL) {
- char name[100];
unsigned char sha1[20];
if (is_dot_or_dotdot(de->d_name))
continue;
- if (strlen(de->d_name) == 38) {
- sprintf(name, "%02x", i);
- memcpy(name+2, de->d_name, 39);
- if (get_sha1_hex(name, sha1) < 0)
- break;
+ if (is_loose_object_file(de, name, sha1)) {
add_sha1_list(sha1, DIRENT_SORT_HINT(de));
continue;
}
sha1_to_hex(it->sha1));
return 1;
}
- mark_object_reachable(obj);
obj->used = 1;
+ mark_object_reachable(obj);
if (obj->type != OBJ_TREE)
err |= objerror(obj, "non-tree in cache-tree");
}
};
static struct option fsck_opts[] = {
- OPT__VERBOSE(&verbose),
+ OPT__VERBOSE(&verbose, "be verbose"),
OPT_BOOLEAN(0, "unreachable", &show_unreachable, "show unreachable objects"),
OPT_BOOLEAN(0, "tags", &show_tags, "report tags"),
OPT_BOOLEAN(0, "root", &show_root, "report root nodes"),
diff --git a/builtin/gc.c b/builtin/gc.c
index c304638b7845601f184149d3c48cbc1ea1e195f5..1a80702b3d1c86b55af24be3b3396f17e9d4a21c 100644 (file)
--- a/builtin/gc.c
+++ b/builtin/gc.c
char buf[80];
struct option builtin_gc_options[] = {
- OPT__QUIET(&quiet),
+ OPT__QUIET(&quiet, "suppress progress reporting"),
{ OPTION_STRING, 0, "prune", &prune_expire, "date",
"prune unreferenced objects",
PARSE_OPT_OPTARG, NULL, (intptr_t)prune_expire },
OPT_END()
};
+ if (argc == 2 && !strcmp(argv[1], "-h"))
+ usage_with_options(builtin_gc_usage, builtin_gc_options);
+
git_config(gc_config, NULL);
if (pack_refs < 0)
diff --git a/builtin/grep.c b/builtin/grep.c
index 3d5f6ace97b806fd7dba9f8c5d62abcdbd903681..5afee2f3a42c76c7e414a667e04c10dd87af1a6a 100644 (file)
--- a/builtin/grep.c
+++ b/builtin/grep.c
#include "grep.h"
#include "quote.h"
#include "dir.h"
-
-#ifndef NO_PTHREADS
-#include <pthread.h>
#include "thread-utils.h"
-#endif
static char const * const grep_usage[] = {
"git grep [options] [-e] <pattern> [<rev>...] [[--] <path>...]",
return 0;
}
-/*
- * Return non-zero if max_depth is negative or path has no more then max_depth
- * slashes.
- */
-static int accept_subdir(const char *path, int max_depth)
-{
- if (max_depth < 0)
- return 1;
-
- while ((path = strchr(path, '/')) != NULL) {
- max_depth--;
- if (max_depth < 0)
- return 0;
- path++;
- }
- return 1;
-}
-
-/*
- * Return non-zero if name is a subdirectory of match and is not too deep.
- */
-static int is_subdir(const char *name, int namelen,
- const char *match, int matchlen, int max_depth)
-{
- if (matchlen > namelen || strncmp(name, match, matchlen))
- return 0;
-
- if (name[matchlen] == '\0') /* exact match */
- return 1;
-
- if (!matchlen || match[matchlen-1] == '/' || name[matchlen] == '/')
- return accept_subdir(name + matchlen + 1, max_depth);
-
- return 0;
-}
-
-/*
- * git grep pathspecs are somewhat different from diff-tree pathspecs;
- * pathname wildcards are allowed.
- */
-static int pathspec_matches(const char **paths, const char *name, int max_depth)
-{
- int namelen, i;
- if (!paths || !*paths)
- return accept_subdir(name, max_depth);
- namelen = strlen(name);
- for (i = 0; paths[i]; i++) {
- const char *match = paths[i];
- int matchlen = strlen(match);
- const char *cp, *meta;
-
- if (is_subdir(name, namelen, match, matchlen, max_depth))
- return 1;
- if (!fnmatch(match, name, 0))
- return 1;
- if (name[namelen-1] != '/')
- continue;
-
- /* We are being asked if the directory ("name") is worth
- * descending into.
- *
- * Find the longest leading directory name that does
- * not have metacharacter in the pathspec; the name
- * we are looking at must overlap with that directory.
- */
- for (cp = match, meta = NULL; cp - match < matchlen; cp++) {
- char ch = *cp;
- if (ch == '*' || ch == '[' || ch == '?') {
- meta = cp;
- break;
- }
- }
- if (!meta)
- meta = cp; /* fully literal */
-
- if (namelen <= meta - match) {
- /* Looking at "Documentation/" and
- * the pattern says "Documentation/howto/", or
- * "Documentation/diff*.txt". The name we
- * have should match prefix.
- */
- if (!memcmp(match, name, namelen))
- return 1;
- continue;
- }
-
- if (meta - match < namelen) {
- /* Looking at "Documentation/howto/" and
- * the pattern says "Documentation/h*";
- * match up to "Do.../h"; this avoids descending
- * into "Documentation/technical/".
- */
- if (!memcmp(match, name, meta - match))
- return 1;
- continue;
- }
- }
- return 0;
-}
-
static void *lock_and_read_sha1_file(const unsigned char *sha1, enum object_type *type, unsigned long *size)
{
void *data;
free(argv);
}
-static int grep_cache(struct grep_opt *opt, const char **paths, int cached)
+static int grep_cache(struct grep_opt *opt, const struct pathspec *pathspec, int cached)
{
int hit = 0;
int nr;
struct cache_entry *ce = active_cache[nr];
if (!S_ISREG(ce->ce_mode))
continue;
- if (!pathspec_matches(paths, ce->name, opt->max_depth))
+ if (!match_pathspec_depth(pathspec, ce->name, ce_namelen(ce), 0, NULL))
continue;
/*
* If CE_VALID is on, we assume worktree file and its cache entry
return hit;
}
-static int grep_tree(struct grep_opt *opt, const char **paths,
- struct tree_desc *tree,
- const char *tree_name, const char *base)
+static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec,
+ struct tree_desc *tree, struct strbuf *base, int tn_len)
{
- int len;
- int hit = 0;
+ int hit = 0, matched = 0;
struct name_entry entry;
- char *down;
- int tn_len = strlen(tree_name);
- struct strbuf pathbuf;
-
- strbuf_init(&pathbuf, PATH_MAX + tn_len);
-
- if (tn_len) {
- strbuf_add(&pathbuf, tree_name, tn_len);
- strbuf_addch(&pathbuf, ':');
- tn_len = pathbuf.len;
- }
- strbuf_addstr(&pathbuf, base);
- len = pathbuf.len;
+ int old_baselen = base->len;
while (tree_entry(tree, &entry)) {
int te_len = tree_entry_len(entry.path, entry.sha1);
- pathbuf.len = len;
- strbuf_add(&pathbuf, entry.path, te_len);
-
- if (S_ISDIR(entry.mode))
- /* Match "abc/" against pathspec to
- * decide if we want to descend into "abc"
- * directory.
- */
- strbuf_addch(&pathbuf, '/');
-
- down = pathbuf.buf + tn_len;
- if (!pathspec_matches(paths, down, opt->max_depth))
- ;
- else if (S_ISREG(entry.mode))
- hit |= grep_sha1(opt, entry.sha1, pathbuf.buf, tn_len);
+
+ if (matched != 2) {
+ matched = tree_entry_interesting(&entry, base, tn_len, pathspec);
+ if (matched == -1)
+ break; /* no more matches */
+ if (!matched)
+ continue;
+ }
+
+ strbuf_add(base, entry.path, te_len);
+
+ if (S_ISREG(entry.mode)) {
+ hit |= grep_sha1(opt, entry.sha1, base->buf, tn_len);
+ }
else if (S_ISDIR(entry.mode)) {
enum object_type type;
struct tree_desc sub;
if (!data)
die("unable to read tree (%s)",
sha1_to_hex(entry.sha1));
+
+ strbuf_addch(base, '/');
init_tree_desc(&sub, data, size);
- hit |= grep_tree(opt, paths, &sub, tree_name, down);
+ hit |= grep_tree(opt, pathspec, &sub, base, tn_len);
free(data);
}
+ strbuf_setlen(base, old_baselen);
+
if (hit && opt->status_only)
break;
}
- strbuf_release(&pathbuf);
return hit;
}
-static int grep_object(struct grep_opt *opt, const char **paths,
+static int grep_object(struct grep_opt *opt, const struct pathspec *pathspec,
struct object *obj, const char *name)
{
if (obj->type == OBJ_BLOB)
struct tree_desc tree;
void *data;
unsigned long size;
- int hit;
+ struct strbuf base;
+ int hit, len;
+
data = read_object_with_reference(obj->sha1, tree_type,
&size, NULL);
if (!data)
die("unable to read tree (%s)", sha1_to_hex(obj->sha1));
+
+ len = name ? strlen(name) : 0;
+ strbuf_init(&base, PATH_MAX + len + 1);
+ if (len) {
+ strbuf_add(&base, name, len);
+ strbuf_addch(&base, ':');
+ }
init_tree_desc(&tree, data, size);
- hit = grep_tree(opt, paths, &tree, name, "");
+ hit = grep_tree(opt, pathspec, &tree, &base, base.len);
+ strbuf_release(&base);
free(data);
return hit;
}
die("unable to grep from object of type %s", typename(obj->type));
}
-static int grep_objects(struct grep_opt *opt, const char **paths,
+static int grep_objects(struct grep_opt *opt, const struct pathspec *pathspec,
const struct object_array *list)
{
unsigned int i;
for (i = 0; i < nr; i++) {
struct object *real_obj;
real_obj = deref_tag(list->objects[i].item, NULL, 0);
- if (grep_object(opt, paths, real_obj, list->objects[i].name)) {
+ if (grep_object(opt, pathspec, real_obj, list->objects[i].name)) {
hit = 1;
if (opt->status_only)
break;
return hit;
}
-static int grep_directory(struct grep_opt *opt, const char **paths)
+static int grep_directory(struct grep_opt *opt, const struct pathspec *pathspec)
{
struct dir_struct dir;
int i, hit = 0;
memset(&dir, 0, sizeof(dir));
setup_standard_excludes(&dir);
- fill_directory(&dir, paths);
+ fill_directory(&dir, pathspec->raw);
for (i = 0; i < dir.nr; i++) {
+ const char *name = dir.entries[i]->name;
+ int namelen = strlen(name);
+ if (!match_pathspec_depth(pathspec, name, namelen, 0, NULL))
+ continue;
hit |= grep_file(opt, dir.entries[i]->name);
if (hit && opt->status_only)
break;
struct grep_opt opt;
struct object_array list = OBJECT_ARRAY_INIT;
const char **paths = NULL;
+ struct pathspec pathspec;
struct string_list path_list = STRING_LIST_INIT_NODUP;
int i;
int dummy;
{ OPTION_CALLBACK, ')', NULL, &opt, NULL, "",
PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_NODASH,
close_callback },
- OPT_BOOLEAN('q', "quiet", &opt.status_only,
- "indicate hit with exit status without output"),
+ OPT__QUIET(&opt.status_only,
+ "indicate hit with exit status without output"),
OPT_BOOLEAN(0, "all-match", &opt.all_match,
"show only matches from files that match all patterns"),
OPT_GROUP(""),
paths[0] = prefix;
paths[1] = NULL;
}
+ init_pathspec(&pathspec, paths);
+ pathspec.max_depth = opt.max_depth;
+ pathspec.recursive = 1;
if (show_in_pager && (cached || list.nr))
die("--open-files-in-pager only works on the worktree");
die("--cached cannot be used with --no-index.");
if (list.nr)
die("--no-index cannot be used with revs.");
- hit = grep_directory(&opt, paths);
+ hit = grep_directory(&opt, &pathspec);
} else if (!list.nr) {
if (!cached)
setup_work_tree();
- hit = grep_cache(&opt, paths, cached);
+ hit = grep_cache(&opt, &pathspec, cached);
} else {
if (cached)
die("both --cached and trees are given.");
- hit = grep_objects(&opt, paths, &list);
+ hit = grep_objects(&opt, &pathspec, &list);
}
if (use_threads)
diff --git a/builtin/hash-object.c b/builtin/hash-object.c
index 080af1a01b8155680faf6c04101217b60ae7b919..c90acddcb2c32ce5170a220c9b1af96b44552a41 100644 (file)
--- a/builtin/hash-object.c
+++ b/builtin/hash-object.c
struct stat st;
unsigned char sha1[20];
if (fstat(fd, &st) < 0 ||
- index_fd(sha1, fd, &st, write_object, type_from_string(type), path))
+ index_fd(sha1, fd, &st, write_object, type_from_string(type), path, 1))
die(write_object
? "Unable to add %s to database"
: "Unable to hash %s", path);
diff --git a/builtin/init-db.c b/builtin/init-db.c
index 9d4886c71675f2bc50671ea284875ebae9a34735..4f5348eec670faaa62b24fff30b477ddd42acbc6 100644 (file)
--- a/builtin/init-db.c
+++ b/builtin/init-db.c
int cmd_init_db(int argc, const char **argv, const char *prefix)
{
const char *git_dir;
+ const char *work_tree;
const char *template_dir = NULL;
unsigned int flags = 0;
const struct option init_db_options[] = {
OPT_STRING(0, "template", &template_dir, "template-directory",
- "provide the directory from which templates will be used"),
+ "directory from which templates will be used"),
OPT_SET_INT(0, "bare", &is_bare_repository_cfg,
"create a bare repository", 1),
{ OPTION_CALLBACK, 0, "shared", &init_shared_repository,
* without --bare. Catch the error early.
*/
git_dir = getenv(GIT_DIR_ENVIRONMENT);
- if ((!git_dir || is_bare_repository_cfg == 1)
- && getenv(GIT_WORK_TREE_ENVIRONMENT))
+ work_tree = getenv(GIT_WORK_TREE_ENVIRONMENT);
+ if ((!git_dir || is_bare_repository_cfg == 1) && work_tree)
die("%s (or --work-tree=<directory>) not allowed without "
"specifying %s (or --git-dir=<directory>)",
GIT_WORK_TREE_ENVIRONMENT,
if (!getcwd(git_work_tree_cfg, PATH_MAX))
die_errno ("Cannot access current working directory");
}
+ if (work_tree)
+ set_git_work_tree(make_absolute_path(work_tree));
+ else
+ set_git_work_tree(git_work_tree_cfg);
if (access(get_git_work_tree(), X_OK))
die_errno ("Cannot access work tree '%s'",
get_git_work_tree());
}
+ else {
+ if (work_tree)
+ set_git_work_tree(make_absolute_path(work_tree));
+ }
set_git_dir(make_absolute_path(git_dir));
diff --git a/builtin/log.c b/builtin/log.c
index eaa1ee0fa71a344af747dec038c6a5907c8273a5..f5ed690c435423662808fe0ff7f1e256374586db 100644 (file)
--- a/builtin/log.c
+++ b/builtin/log.c
rev->always_show_header = 0;
if (DIFF_OPT_TST(&rev->diffopt, FOLLOW_RENAMES)) {
rev->always_show_header = 0;
- if (rev->diffopt.nr_paths != 1)
+ if (rev->diffopt.pathspec.nr != 1)
usage("git logs can only follow renames on one pathname at a time");
}
for (i = 1; i < argc; i++) {
struct strbuf out = STRBUF_INIT;
pp_user_info("Tagger", rev->commit_format, &out, buf, rev->date_mode,
- git_log_output_encoding ?
- git_log_output_encoding: git_commit_encoding);
+ get_log_output_encoding());
printf("%s", out.buf);
strbuf_release(&out);
}
rev.commit_format = CMIT_FMT_EMAIL;
rev.verbose_header = 1;
rev.diff = 1;
- rev.combine_merges = 0;
- rev.ignore_merges = 1;
+ rev.no_merges = 1;
DIFF_OPT_SET(&rev.diffopt, RECURSIVE);
rev.subject_prefix = fmt_patch_subject_prefix;
memset(&s_r_opt, 0, sizeof(s_r_opt));
if (!use_stdout)
output_directory = set_outdir(prefix, output_directory);
+ else
+ setup_pager();
if (output_directory) {
if (use_stdout)
continue;
}
- /* ignore merges */
- if (commit->parents && commit->parents->next)
- continue;
-
if (ignore_if_in_upstream &&
has_commit_patch_id(commit, &ids))
continue;
struct option options[] = {
OPT__ABBREV(&abbrev),
- OPT__VERBOSE(&verbose),
+ OPT__VERBOSE(&verbose, "be verbose"),
OPT_END()
};
diff --git a/builtin/ls-files.c b/builtin/ls-files.c
index 6a307ab784a25c9f8d201392f79c7146a4a8eece..fb2d5f4b1fb0ce9ef2fb0c4099b5fea6d07b6838 100644 (file)
--- a/builtin/ls-files.c
+++ b/builtin/ls-files.c
OPT_END()
};
+ if (argc == 2 && !strcmp(argv[1], "-h"))
+ usage_with_options(ls_files_usage, builtin_ls_files_options);
+
memset(&dir, 0, sizeof(dir));
prefix = cmd_prefix;
if (prefix)
diff --git a/builtin/mailinfo.c b/builtin/mailinfo.c
index 2320d981ceab220e287e42c21f3c9f3be5b3722f..71e6262a87d883a4a82953db5f742bccf99e5c77 100644 (file)
--- a/builtin/mailinfo.c
+++ b/builtin/mailinfo.c
*/
git_config(git_mailinfo_config, NULL);
- def_charset = (git_commit_encoding ? git_commit_encoding : "UTF-8");
+ def_charset = get_commit_output_encoding();
metainfo_charset = def_charset;
while (1 < argc && argv[1][0] == '-') {
diff --git a/builtin/merge-file.c b/builtin/merge-file.c
index 6c4afb5a38bc954be836c441b9b09b22ef82b132..237abd3c0b27b601aca140bfb67a302666ff9cf2 100644 (file)
--- a/builtin/merge-file.c
+++ b/builtin/merge-file.c
XDL_MERGE_FAVOR_UNION),
OPT_INTEGER(0, "marker-size", &xmp.marker_size,
"for conflicts, use this marker size"),
- OPT__QUIET(&quiet),
+ OPT__QUIET(&quiet, "do not warn about conflicts"),
OPT_CALLBACK('L', NULL, names, "name",
"set labels for file1/orig_file/file2", &label_cb),
OPT_END(),
index 78b9db76a0819529e7f6cab44319acd2978c9e9b..c33091b3ed52bc8539ff82f039ec8c7718f3dcc2 100644 (file)
#include "commit.h"
#include "tag.h"
#include "merge-recursive.h"
+#include "xdiff-interface.h"
static const char builtin_merge_recursive_usage[] =
"git %s <base>... -- <head> <remote> ...";
if (!prefixcmp(arg, "--")) {
if (!arg[2])
break;
- if (!strcmp(arg+2, "ours"))
- o.recursive_variant = MERGE_RECURSIVE_OURS;
- else if (!strcmp(arg+2, "theirs"))
- o.recursive_variant = MERGE_RECURSIVE_THEIRS;
- else if (!strcmp(arg+2, "subtree"))
- o.subtree_shift = "";
- else if (!prefixcmp(arg+2, "subtree="))
- o.subtree_shift = arg + 10;
- else if (!strcmp(arg+2, "renormalize"))
- o.renormalize = 1;
- else if (!strcmp(arg+2, "no-renormalize"))
- o.renormalize = 0;
- else
+ if (parse_merge_opt(&o, arg + 2))
die("Unknown option %s", arg);
continue;
}
diff --git a/builtin/merge.c b/builtin/merge.c
index d0bd651725648c01e2c4250f2533aed2c8174164..a89ddbb65578182d1d0cbd9b0104889a6dc05ac8 100644 (file)
--- a/builtin/merge.c
+++ b/builtin/merge.c
NULL
};
-static int show_diffstat = 1, option_log, squash;
+static int show_diffstat = 1, shortlog_len, squash;
static int option_commit = 1, allow_fast_forward = 1;
static int fast_forward_only;
static int allow_trivial = 1, have_message;
static int option_renormalize;
static int verbosity;
static int allow_rerere_auto;
+static int abort_current_merge;
static struct strategy all_strategy[] = {
{ "recursive", DEFAULT_TWOHEAD | NO_TRIVIAL },
OPT_BOOLEAN(0, "stat", &show_diffstat,
"show a diffstat at the end of the merge"),
OPT_BOOLEAN(0, "summary", &show_diffstat, "(synonym to --stat)"),
- OPT_BOOLEAN(0, "log", &option_log,
- "add list of one-line log to merge commit message"),
+ { OPTION_INTEGER, 0, "log", &shortlog_len, "n",
+ "add (at most <n>) entries from shortlog to merge commit message",
+ PARSE_OPT_OPTARG, NULL, DEFAULT_MERGE_LOG_LEN },
OPT_BOOLEAN(0, "squash", &squash,
"create a single commit instead of doing a merge"),
OPT_BOOLEAN(0, "commit", &option_commit,
OPT_CALLBACK('X', "strategy-option", &xopts, "option=value",
"option for selected merge strategy", option_parse_x),
OPT_CALLBACK('m', "message", &merge_msg, "message",
- "message to be used for the merge commit (if any)",
+ "merge commit message (for a non-fast-forward merge)",
option_parse_message),
OPT__VERBOSITY(&verbosity),
+ OPT_BOOLEAN(0, "abort", &abort_current_merge,
+ "abort the current in-progress merge"),
OPT_END()
};
goto cleanup;
}
if (!prefixcmp(found_ref, "refs/remotes/")) {
- strbuf_addf(msg, "%s\t\tremote branch '%s' of .\n",
+ strbuf_addf(msg, "%s\t\tremote-tracking branch '%s' of .\n",
sha1_to_hex(branch_head), remote);
goto cleanup;
}
return git_config_string(&pull_twohead, k, v);
else if (!strcmp(k, "pull.octopus"))
return git_config_string(&pull_octopus, k, v);
- else if (!strcmp(k, "merge.log") || !strcmp(k, "merge.summary"))
- option_log = git_config_bool(k, v);
else if (!strcmp(k, "merge.renormalize"))
option_renormalize = git_config_bool(k, v);
+ else if (!strcmp(k, "merge.log") || !strcmp(k, "merge.summary")) {
+ int is_bool;
+ shortlog_len = git_config_bool_or_int(k, v, &is_bool);
+ if (!is_bool && shortlog_len < 0)
+ return error("%s: negative length %s", k, v);
+ if (is_bool && shortlog_len)
+ shortlog_len = DEFAULT_MERGE_LOG_LEN;
+ return 0;
+ }
return git_diff_ui_config(k, v, cb);
}
die("git write-tree failed to write a tree");
}
-int try_merge_command(const char *strategy, struct commit_list *common,
+int try_merge_command(const char *strategy, size_t xopts_nr,
+ const char **xopts, struct commit_list *common,
const char *head_arg, struct commit_list *remotes)
{
const char **args;
@@ -649,25 +661,9 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,
o.renormalize = option_renormalize;
- /*
- * NEEDSWORK: merge with table in builtin/merge-recursive
- */
- for (x = 0; x < xopts_nr; x++) {
- if (!strcmp(xopts[x], "ours"))
- o.recursive_variant = MERGE_RECURSIVE_OURS;
- else if (!strcmp(xopts[x], "theirs"))
- o.recursive_variant = MERGE_RECURSIVE_THEIRS;
- else if (!strcmp(xopts[x], "subtree"))
- o.subtree_shift = "";
- else if (!prefixcmp(xopts[x], "subtree="))
- o.subtree_shift = xopts[x]+8;
- else if (!strcmp(xopts[x], "renormalize"))
- o.renormalize = 1;
- else if (!strcmp(xopts[x], "no-renormalize"))
- o.renormalize = 0;
- else
+ for (x = 0; x < xopts_nr; x++)
+ if (parse_merge_opt(&o, xopts[x]))
die("Unknown option for merge-recursive: -X%s", xopts[x]);
- }
o.branch1 = head_arg;
o.branch2 = remoteheads->item->util;
rollback_lock_file(lock);
return clean ? 0 : 1;
} else {
- return try_merge_command(strategy, common, head_arg, remoteheads);
+ return try_merge_command(strategy, xopts_nr, xopts,
+ common, head_arg, remoteheads);
}
}
}
+static void write_merge_msg(void)
+{
+ int fd = open(git_path("MERGE_MSG"), O_WRONLY | O_CREAT, 0666);
+ if (fd < 0)
+ die_errno("Could not open '%s' for writing",
+ git_path("MERGE_MSG"));
+ if (write_in_full(fd, merge_msg.buf, merge_msg.len) != merge_msg.len)
+ die_errno("Could not write to '%s'", git_path("MERGE_MSG"));
+ close(fd);
+}
+
+static void read_merge_msg(void)
+{
+ strbuf_reset(&merge_msg);
+ if (strbuf_read_file(&merge_msg, git_path("MERGE_MSG"), 0) < 0)
+ die_errno("Could not read from '%s'", git_path("MERGE_MSG"));
+}
+
+static void run_prepare_commit_msg(void)
+{
+ write_merge_msg();
+ run_hook(get_index_file(), "prepare-commit-msg",
+ git_path("MERGE_MSG"), "merge", NULL, NULL);
+ read_merge_msg();
+}
+
static int merge_trivial(void)
{
unsigned char result_tree[20], result_commit[20];
parent->next = xmalloc(sizeof(*parent->next));
parent->next->item = remoteheads->item;
parent->next->next = NULL;
+ run_prepare_commit_msg();
commit_tree(merge_msg.buf, result_tree, parent, result_commit, NULL);
finish(result_commit, "In-index merge");
drop_save();
}
free_commit_list(remoteheads);
strbuf_addch(&merge_msg, '\n');
+ run_prepare_commit_msg();
commit_tree(merge_msg.buf, result_tree, parents, result_commit, NULL);
strbuf_addf(&buf, "Merge made by %s.", wt_strategy);
finish(result_commit, buf.buf);
const char *best_strategy = NULL, *wt_strategy = NULL;
struct commit_list **remotes = &remoteheads;
- if (read_cache_unmerged()) {
- die_resolve_conflict("merge");
- }
- if (file_exists(git_path("MERGE_HEAD"))) {
- /*
- * There is no unmerged entry, don't advise 'git
- * add/rm <file>', just 'git commit'.
- */
- if (advice_resolve_conflict)
- die("You have not concluded your merge (MERGE_HEAD exists).\n"
- "Please, commit your changes before you can merge.");
- else
- die("You have not concluded your merge (MERGE_HEAD exists).");
- }
+ if (argc == 2 && !strcmp(argv[1], "-h"))
+ usage_with_options(builtin_merge_usage, builtin_merge_options);
- resolve_undo_clear();
/*
* Check if we are _not_ on a detached HEAD, i.e. if there is a
* current branch.
argc = parse_options(argc, argv, prefix, builtin_merge_options,
builtin_merge_usage, 0);
+
+ if (abort_current_merge) {
+ int nargc = 2;
+ const char *nargv[] = {"reset", "--merge", NULL};
+
+ if (!file_exists(git_path("MERGE_HEAD")))
+ die("There is no merge to abort (MERGE_HEAD missing).");
+
+ /* Invoke 'git reset --merge' */
+ return cmd_reset(nargc, nargv, prefix);
+ }
+
+ if (read_cache_unmerged())
+ die_resolve_conflict("merge");
+
+ if (file_exists(git_path("MERGE_HEAD"))) {
+ /*
+ * There is no unmerged entry, don't advise 'git
+ * add/rm <file>', just 'git commit'.
+ */
+ if (advice_resolve_conflict)
+ die("You have not concluded your merge (MERGE_HEAD exists).\n"
+ "Please, commit your changes before you can merge.");
+ else
+ die("You have not concluded your merge (MERGE_HEAD exists).");
+ }
+ resolve_undo_clear();
+
if (verbosity < 0)
show_diffstat = 0;
for (i = 0; i < argc; i++)
merge_name(argv[i], &merge_names);
- if (have_message && option_log)
- fmt_merge_msg_shortlog(&merge_names, &merge_msg);
- else if (!have_message)
- fmt_merge_msg(option_log, &merge_names, &merge_msg);
-
-
- if (!(have_message && !option_log) && merge_msg.len)
- strbuf_setlen(&merge_msg, merge_msg.len-1);
+ if (!have_message || shortlog_len) {
+ fmt_merge_msg(&merge_names, &merge_msg, !have_message,
+ shortlog_len);
+ if (merge_msg.len)
+ strbuf_setlen(&merge_msg, merge_msg.len - 1);
+ }
}
if (head_invalid || !argc)
die_errno("Could not write to '%s'", git_path("MERGE_HEAD"));
close(fd);
strbuf_addch(&merge_msg, '\n');
- fd = open(git_path("MERGE_MSG"), O_WRONLY | O_CREAT, 0666);
- if (fd < 0)
- die_errno("Could not open '%s' for writing",
- git_path("MERGE_MSG"));
- if (write_in_full(fd, merge_msg.buf, merge_msg.len) !=
- merge_msg.len)
- die_errno("Could not write to '%s'", git_path("MERGE_MSG"));
- close(fd);
+ write_merge_msg();
fd = open(git_path("MERGE_MODE"), O_WRONLY | O_CREAT | O_TRUNC, 0666);
if (fd < 0)
die_errno("Could not open '%s' for writing",
diff --git a/builtin/mv.c b/builtin/mv.c
index cdbb09473c0c45efff23a63e78b7623cad523350..93e8995d9e704faa041f97f92ba72dabe7839d81 100644 (file)
--- a/builtin/mv.c
+++ b/builtin/mv.c
int i, newfd;
int verbose = 0, show_only = 0, force = 0, ignore_errors = 0;
struct option builtin_mv_options[] = {
- OPT__DRY_RUN(&show_only),
- OPT_BOOLEAN('f', "force", &force, "force move/rename even if target exists"),
+ OPT__DRY_RUN(&show_only, "dry run"),
+ OPT__FORCE(&force, "force move/rename even if target exists"),
OPT_BOOLEAN('k', NULL, &ignore_errors, "skip move/rename errors"),
OPT_END(),
};
diff --git a/builtin/notes.c b/builtin/notes.c
index 6d07aac80cc2bfd042eee905abb5086a29e1d90a..0aab150c52c839512b82b5efc6e978b7a80653e3 100644 (file)
--- a/builtin/notes.c
+++ b/builtin/notes.c
#include "run-command.h"
#include "parse-options.h"
#include "string-list.h"
+#include "notes-merge.h"
static const char * const git_notes_usage[] = {
"git notes [--ref <notes_ref>] [list [<object>]]",
"git notes [--ref <notes_ref>] append [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]",
"git notes [--ref <notes_ref>] edit [<object>]",
"git notes [--ref <notes_ref>] show [<object>]",
+ "git notes [--ref <notes_ref>] merge [-v | -q] [-s <strategy> ] <notes_ref>",
+ "git notes merge --commit [-v | -q]",
+ "git notes merge --abort [-v | -q]",
"git notes [--ref <notes_ref>] remove [<object>]",
"git notes [--ref <notes_ref>] prune [-n | -v]",
+ "git notes [--ref <notes_ref>] get-ref",
NULL
};
NULL
};
+static const char * const git_notes_merge_usage[] = {
+ "git notes merge [<options>] <notes_ref>",
+ "git notes merge --commit [<options>]",
+ "git notes merge --abort [<options>]",
+ NULL
+};
+
static const char * const git_notes_remove_usage[] = {
"git notes remove [<object>]",
NULL
NULL
};
+static const char * const git_notes_get_ref_usage[] = {
+ "git notes get-ref",
+ NULL
+};
+
static const char note_template[] =
"\n"
"#\n"
struct strbuf buf;
};
+static void expand_notes_ref(struct strbuf *sb)
+{
+ if (!prefixcmp(sb->buf, "refs/notes/"))
+ return; /* we're happy */
+ else if (!prefixcmp(sb->buf, "notes/"))
+ strbuf_insert(sb, 0, "refs/", 5);
+ else
+ strbuf_insert(sb, 0, "refs/notes/", 11);
+}
+
static int list_each_note(const unsigned char *object_sha1,
const unsigned char *note_sha1, char *note_path,
void *cb_data)
@@ -271,18 +298,17 @@ static int parse_reedit_arg(const struct option *opt, const char *arg, int unset
return parse_reuse_arg(opt, arg, unset);
}
-int commit_notes(struct notes_tree *t, const char *msg)
+void commit_notes(struct notes_tree *t, const char *msg)
{
- struct commit_list *parent;
- unsigned char tree_sha1[20], prev_commit[20], new_commit[20];
struct strbuf buf = STRBUF_INIT;
+ unsigned char commit_sha1[20];
if (!t)
t = &default_notes_tree;
if (!t->initialized || !t->ref || !*t->ref)
die("Cannot commit uninitialized/unreferenced notes tree");
if (!t->dirty)
- return 0; /* don't have to commit an unchanged tree */
+ return; /* don't have to commit an unchanged tree */
/* Prepare commit message and reflog message */
strbuf_addstr(&buf, "notes: "); /* commit message starts at index 7 */
if (buf.buf[buf.len - 1] != '\n')
strbuf_addch(&buf, '\n'); /* Make sure msg ends with newline */
- /* Convert notes tree to tree object */
- if (write_notes_tree(t, tree_sha1))
- die("Failed to write current notes tree to database");
-
- /* Create new commit for the tree object */
- if (!read_ref(t->ref, prev_commit)) { /* retrieve parent commit */
- parent = xmalloc(sizeof(*parent));
- parent->item = lookup_commit(prev_commit);
- parent->next = NULL;
- } else {
- hashclr(prev_commit);
- parent = NULL;
- }
- if (commit_tree(buf.buf + 7, tree_sha1, parent, new_commit, NULL))
- die("Failed to commit notes tree to database");
-
- /* Update notes ref with new commit */
- update_ref(buf.buf, t->ref, new_commit, prev_commit, 0, DIE_ON_ERR);
+ create_notes_commit(t, NULL, buf.buf + 7, commit_sha1);
+ update_ref(buf.buf, t->ref, commit_sha1, NULL, 0, DIE_ON_ERR);
strbuf_release(&buf);
- return 0;
}
combine_notes_fn parse_combine_notes_fn(const char *v)
return combine_notes_ignore;
else if (!strcasecmp(v, "concatenate"))
return combine_notes_concatenate;
+ else if (!strcasecmp(v, "cat_sort_uniq"))
+ return combine_notes_cat_sort_uniq;
else
return NULL;
}
const unsigned char *note;
struct msg_arg msg = { 0, 0, STRBUF_INIT };
struct option options[] = {
- { OPTION_CALLBACK, 'm', "message", &msg, "MSG",
+ { OPTION_CALLBACK, 'm', "message", &msg, "msg",
"note contents as a string", PARSE_OPT_NONEG,
parse_msg_arg},
- { OPTION_CALLBACK, 'F', "file", &msg, "FILE",
+ { OPTION_CALLBACK, 'F', "file", &msg, "file",
"note contents in a file", PARSE_OPT_NONEG,
parse_file_arg},
- { OPTION_CALLBACK, 'c', "reedit-message", &msg, "OBJECT",
+ { OPTION_CALLBACK, 'c', "reedit-message", &msg, "object",
"reuse and edit specified note object", PARSE_OPT_NONEG,
parse_reedit_arg},
- { OPTION_CALLBACK, 'C', "reuse-message", &msg, "OBJECT",
+ { OPTION_CALLBACK, 'C', "reuse-message", &msg, "object",
"reuse specified note object", PARSE_OPT_NONEG,
parse_reuse_arg},
- OPT_BOOLEAN('f', "force", &force, "replace existing notes"),
+ OPT__FORCE(&force, "replace existing notes"),
OPT_END()
};
if (is_null_sha1(new_note))
remove_note(t, object);
- else
- add_note(t, object, new_note, combine_notes_overwrite);
+ else if (add_note(t, object, new_note, combine_notes_overwrite))
+ die("BUG: combine_notes_overwrite failed");
snprintf(logmsg, sizeof(logmsg), "Notes %s by 'git notes %s'",
is_null_sha1(new_note) ? "removed" : "added", "add");
struct notes_tree *t;
const char *rewrite_cmd = NULL;
struct option options[] = {
- OPT_BOOLEAN('f', "force", &force, "replace existing notes"),
+ OPT__FORCE(&force, "replace existing notes"),
OPT_BOOLEAN(0, "stdin", &from_stdin, "read objects from stdin"),
OPT_STRING(0, "for-rewrite", &rewrite_cmd, "command",
"load rewriting config for <command> (implies "
goto out;
}
- add_note(t, object, from_note, combine_notes_overwrite);
+ if (add_note(t, object, from_note, combine_notes_overwrite))
+ die("BUG: combine_notes_overwrite failed");
commit_notes(t, "Notes added by 'git notes copy'");
out:
free_notes(t);
const char * const *usage;
struct msg_arg msg = { 0, 0, STRBUF_INIT };
struct option options[] = {
- { OPTION_CALLBACK, 'm', "message", &msg, "MSG",
+ { OPTION_CALLBACK, 'm', "message", &msg, "msg",
"note contents as a string", PARSE_OPT_NONEG,
parse_msg_arg},
- { OPTION_CALLBACK, 'F', "file", &msg, "FILE",
+ { OPTION_CALLBACK, 'F', "file", &msg, "file",
"note contents in a file", PARSE_OPT_NONEG,
parse_file_arg},
- { OPTION_CALLBACK, 'c', "reedit-message", &msg, "OBJECT",
+ { OPTION_CALLBACK, 'c', "reedit-message", &msg, "object",
"reuse and edit specified note object", PARSE_OPT_NONEG,
parse_reedit_arg},
- { OPTION_CALLBACK, 'C', "reuse-message", &msg, "OBJECT",
+ { OPTION_CALLBACK, 'C', "reuse-message", &msg, "object",
"reuse specified note object", PARSE_OPT_NONEG,
parse_reuse_arg},
OPT_END()
if (is_null_sha1(new_note))
remove_note(t, object);
- else
- add_note(t, object, new_note, combine_notes_overwrite);
+ else if (add_note(t, object, new_note, combine_notes_overwrite))
+ die("BUG: combine_notes_overwrite failed");
snprintf(logmsg, sizeof(logmsg), "Notes %s by 'git notes %s'",
is_null_sha1(new_note) ? "removed" : "added", argv[0]);
return retval;
}
+static int merge_abort(struct notes_merge_options *o)
+{
+ int ret = 0;
+
+ /*
+ * Remove .git/NOTES_MERGE_PARTIAL and .git/NOTES_MERGE_REF, and call
+ * notes_merge_abort() to remove .git/NOTES_MERGE_WORKTREE.
+ */
+
+ if (delete_ref("NOTES_MERGE_PARTIAL", NULL, 0))
+ ret += error("Failed to delete ref NOTES_MERGE_PARTIAL");
+ if (delete_ref("NOTES_MERGE_REF", NULL, REF_NODEREF))
+ ret += error("Failed to delete ref NOTES_MERGE_REF");
+ if (notes_merge_abort(o))
+ ret += error("Failed to remove 'git notes merge' worktree");
+ return ret;
+}
+
+static int merge_commit(struct notes_merge_options *o)
+{
+ struct strbuf msg = STRBUF_INIT;
+ unsigned char sha1[20], parent_sha1[20];
+ struct notes_tree *t;
+ struct commit *partial;
+ struct pretty_print_context pretty_ctx;
+
+ /*
+ * Read partial merge result from .git/NOTES_MERGE_PARTIAL,
+ * and target notes ref from .git/NOTES_MERGE_REF.
+ */
+
+ if (get_sha1("NOTES_MERGE_PARTIAL", sha1))
+ die("Failed to read ref NOTES_MERGE_PARTIAL");
+ else if (!(partial = lookup_commit_reference(sha1)))
+ die("Could not find commit from NOTES_MERGE_PARTIAL.");
+ else if (parse_commit(partial))
+ die("Could not parse commit from NOTES_MERGE_PARTIAL.");
+
+ if (partial->parents)
+ hashcpy(parent_sha1, partial->parents->item->object.sha1);
+ else
+ hashclr(parent_sha1);
+
+ t = xcalloc(1, sizeof(struct notes_tree));
+ init_notes(t, "NOTES_MERGE_PARTIAL", combine_notes_overwrite, 0);
+
+ o->local_ref = resolve_ref("NOTES_MERGE_REF", sha1, 0, 0);
+ if (!o->local_ref)
+ die("Failed to resolve NOTES_MERGE_REF");
+
+ if (notes_merge_commit(o, t, partial, sha1))
+ die("Failed to finalize notes merge");
+
+ /* Reuse existing commit message in reflog message */
+ memset(&pretty_ctx, 0, sizeof(pretty_ctx));
+ format_commit_message(partial, "%s", &msg, &pretty_ctx);
+ strbuf_trim(&msg);
+ strbuf_insert(&msg, 0, "notes: ", 7);
+ update_ref(msg.buf, o->local_ref, sha1,
+ is_null_sha1(parent_sha1) ? NULL : parent_sha1,
+ 0, DIE_ON_ERR);
+
+ free_notes(t);
+ strbuf_release(&msg);
+ return merge_abort(o);
+}
+
+static int merge(int argc, const char **argv, const char *prefix)
+{
+ struct strbuf remote_ref = STRBUF_INIT, msg = STRBUF_INIT;
+ unsigned char result_sha1[20];
+ struct notes_tree *t;
+ struct notes_merge_options o;
+ int do_merge = 0, do_commit = 0, do_abort = 0;
+ int verbosity = 0, result;
+ const char *strategy = NULL;
+ struct option options[] = {
+ OPT_GROUP("General options"),
+ OPT__VERBOSITY(&verbosity),
+ OPT_GROUP("Merge options"),
+ OPT_STRING('s', "strategy", &strategy, "strategy",
+ "resolve notes conflicts using the given strategy "
+ "(manual/ours/theirs/union/cat_sort_uniq)"),
+ OPT_GROUP("Committing unmerged notes"),
+ { OPTION_BOOLEAN, 0, "commit", &do_commit, NULL,
+ "finalize notes merge by committing unmerged notes",
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG },
+ OPT_GROUP("Aborting notes merge resolution"),
+ { OPTION_BOOLEAN, 0, "abort", &do_abort, NULL,
+ "abort notes merge",
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG },
+ OPT_END()
+ };
+
+ argc = parse_options(argc, argv, prefix, options,
+ git_notes_merge_usage, 0);
+
+ if (strategy || do_commit + do_abort == 0)
+ do_merge = 1;
+ if (do_merge + do_commit + do_abort != 1) {
+ error("cannot mix --commit, --abort or -s/--strategy");
+ usage_with_options(git_notes_merge_usage, options);
+ }
+
+ if (do_merge && argc != 1) {
+ error("Must specify a notes ref to merge");
+ usage_with_options(git_notes_merge_usage, options);
+ } else if (!do_merge && argc) {
+ error("too many parameters");
+ usage_with_options(git_notes_merge_usage, options);
+ }
+
+ init_notes_merge_options(&o);
+ o.verbosity = verbosity + NOTES_MERGE_VERBOSITY_DEFAULT;
+
+ if (do_abort)
+ return merge_abort(&o);
+ if (do_commit)
+ return merge_commit(&o);
+
+ o.local_ref = default_notes_ref();
+ strbuf_addstr(&remote_ref, argv[0]);
+ expand_notes_ref(&remote_ref);
+ o.remote_ref = remote_ref.buf;
+
+ if (strategy) {
+ if (!strcmp(strategy, "manual"))
+ o.strategy = NOTES_MERGE_RESOLVE_MANUAL;
+ else if (!strcmp(strategy, "ours"))
+ o.strategy = NOTES_MERGE_RESOLVE_OURS;
+ else if (!strcmp(strategy, "theirs"))
+ o.strategy = NOTES_MERGE_RESOLVE_THEIRS;
+ else if (!strcmp(strategy, "union"))
+ o.strategy = NOTES_MERGE_RESOLVE_UNION;
+ else if (!strcmp(strategy, "cat_sort_uniq"))
+ o.strategy = NOTES_MERGE_RESOLVE_CAT_SORT_UNIQ;
+ else {
+ error("Unknown -s/--strategy: %s", strategy);
+ usage_with_options(git_notes_merge_usage, options);
+ }
+ }
+
+ t = init_notes_check("merge");
+
+ strbuf_addf(&msg, "notes: Merged notes from %s into %s",
+ remote_ref.buf, default_notes_ref());
+ strbuf_add(&(o.commit_msg), msg.buf + 7, msg.len - 7); /* skip "notes: " */
+
+ result = notes_merge(&o, t, result_sha1);
+
+ if (result >= 0) /* Merge resulted (trivially) in result_sha1 */
+ /* Update default notes ref with new commit */
+ update_ref(msg.buf, default_notes_ref(), result_sha1, NULL,
+ 0, DIE_ON_ERR);
+ else { /* Merge has unresolved conflicts */
+ /* Update .git/NOTES_MERGE_PARTIAL with partial merge result */
+ update_ref(msg.buf, "NOTES_MERGE_PARTIAL", result_sha1, NULL,
+ 0, DIE_ON_ERR);
+ /* Store ref-to-be-updated into .git/NOTES_MERGE_REF */
+ if (create_symref("NOTES_MERGE_REF", default_notes_ref(), NULL))
+ die("Failed to store link to current notes ref (%s)",
+ default_notes_ref());
+ printf("Automatic notes merge failed. Fix conflicts in %s and "
+ "commit the result with 'git notes merge --commit', or "
+ "abort the merge with 'git notes merge --abort'.\n",
+ git_path(NOTES_MERGE_WORKTREE));
+ }
+
+ free_notes(t);
+ strbuf_release(&remote_ref);
+ strbuf_release(&msg);
+ return result < 0; /* return non-zero on conflicts */
+}
+
static int remove_cmd(int argc, const char **argv, const char *prefix)
{
struct option options[] = {
struct notes_tree *t;
int show_only = 0, verbose = 0;
struct option options[] = {
- OPT_BOOLEAN('n', "dry-run", &show_only,
- "do not remove, show only"),
- OPT_BOOLEAN('v', "verbose", &verbose, "report pruned notes"),
+ OPT__DRY_RUN(&show_only, "do not remove, show only"),
+ OPT__VERBOSE(&verbose, "report pruned notes"),
OPT_END()
};
return 0;
}
+static int get_ref(int argc, const char **argv, const char *prefix)
+{
+ struct option options[] = { OPT_END() };
+ argc = parse_options(argc, argv, prefix, options,
+ git_notes_get_ref_usage, 0);
+
+ if (argc) {
+ error("too many parameters");
+ usage_with_options(git_notes_get_ref_usage, options);
+ }
+
+ puts(default_notes_ref());
+ return 0;
+}
+
int cmd_notes(int argc, const char **argv, const char *prefix)
{
int result;
if (override_notes_ref) {
struct strbuf sb = STRBUF_INIT;
- if (!prefixcmp(override_notes_ref, "refs/notes/"))
- /* we're happy */;
- else if (!prefixcmp(override_notes_ref, "notes/"))
- strbuf_addstr(&sb, "refs/");
- else
- strbuf_addstr(&sb, "refs/notes/");
strbuf_addstr(&sb, override_notes_ref);
+ expand_notes_ref(&sb);
setenv("GIT_NOTES_REF", sb.buf, 1);
strbuf_release(&sb);
}
result = append_edit(argc, argv, prefix);
else if (!strcmp(argv[0], "show"))
result = show(argc, argv, prefix);
+ else if (!strcmp(argv[0], "merge"))
+ result = merge(argc, argv, prefix);
else if (!strcmp(argv[0], "remove"))
result = remove_cmd(argc, argv, prefix);
else if (!strcmp(argv[0], "prune"))
result = prune(argc, argv, prefix);
+ else if (!strcmp(argv[0], "get-ref"))
+ result = get_ref(argc, argv, prefix);
else {
result = error("Unknown subcommand: %s", argv[0]);
usage_with_options(git_notes_usage, options);
diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c
index 3cbeb299d172c51b8ab6940a836efdf8a1a8e4ce..b0503b202afb4356caaca794518fb4e21082a48f 100644 (file)
--- a/builtin/pack-objects.c
+++ b/builtin/pack-objects.c
#include "list-objects.h"
#include "progress.h"
#include "refs.h"
-
-#ifndef NO_PTHREADS
-#include <pthread.h>
#include "thread-utils.h"
-#endif
static const char pack_usage[] =
"git pack-objects [ -q | --progress | --all-progress ]\n"
read_unlock();
}
-try_to_free_t old_try_to_free_routine;
+static try_to_free_t old_try_to_free_routine;
/*
* The main thread waits on the condition that (at least) one of the workers
diff --git a/builtin/patch-id.c b/builtin/patch-id.c
index 512530022edac398f8541ee6c400c7312659e730..49a0472a9bd28274c4be1352996e700a1db4b94a 100644 (file)
--- a/builtin/patch-id.c
+++ b/builtin/patch-id.c
p += 7;
else if (!memcmp(line, "From ", 5))
p += 5;
+ else if (!memcmp(line, "\\ ", 2) && 12 < strlen(line))
+ continue;
if (!get_sha1_hex(p, next_sha1)) {
found_next = 1;
diff --git a/builtin/prune.c b/builtin/prune.c
index 99218ba49e2c6ea7ff7185e6397bb73bca213a66..e65690ba370511072dfa1e64838eef5e5686aac9 100644 (file)
--- a/builtin/prune.c
+++ b/builtin/prune.c
{
struct rev_info revs;
const struct option options[] = {
- OPT_BOOLEAN('n', "dry-run", &show_only,
- "do not remove, show only"),
- OPT_BOOLEAN('v', "verbose", &verbose, "report pruned objects"),
+ OPT__DRY_RUN(&show_only, "do not remove, show only"),
+ OPT__VERBOSE(&verbose, "report pruned objects"),
OPT_DATE(0, "expire", &expire,
"expire objects older than <time>"),
OPT_END()
diff --git a/builtin/push.c b/builtin/push.c
index e655eb7695faba13c4d9e8f25b9649ffec7195be..31da418cf4a9e4732edb8201c80b07f7ddadbac1 100644 (file)
--- a/builtin/push.c
+++ b/builtin/push.c
}
}
-static void setup_push_tracking(void)
+static void setup_push_upstream(void)
{
struct strbuf refspec = STRBUF_INIT;
struct branch *branch = branch_get(NULL);
if (!branch)
die("You are not currently on a branch.");
if (!branch->merge_nr || !branch->merge)
- die("The current branch %s is not tracking anything.",
+ die("The current branch %s has no upstream branch.",
branch->name);
if (branch->merge_nr != 1)
- die("The current branch %s is tracking multiple branches, "
+ die("The current branch %s has multiple upstream branches, "
"refusing to push.", branch->name);
strbuf_addf(&refspec, "%s:%s", branch->name, branch->merge[0]->src);
add_refspec(refspec.buf);
add_refspec(":");
break;
- case PUSH_DEFAULT_TRACKING:
- setup_push_tracking();
+ case PUSH_DEFAULT_UPSTREAM:
+ setup_push_upstream();
break;
case PUSH_DEFAULT_CURRENT:
diff --git a/builtin/read-tree.c b/builtin/read-tree.c
index 9ad1e66916545dd2aeeb110661e56edfa5921019..93c92814cf28855aee87cabf4a3b906e11a36125 100644 (file)
--- a/builtin/read-tree.c
+++ b/builtin/read-tree.c
#include "resolve-undo.h"
static int nr_trees;
+static int read_empty;
static struct tree *trees[MAX_UNPACK_TREES];
static int list_tree(unsigned char *sha1)
}
static const char * const read_tree_usage[] = {
- "git read-tree [[-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>] [-u [--exclude-per-directory=<gitignore>] | -i]] [--no-sparse-checkout] [--index-output=<file>] <tree-ish1> [<tree-ish2> [<tree-ish3>]]",
+ "git read-tree [[-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>] [-u [--exclude-per-directory=<gitignore>] | -i]] [--no-sparse-checkout] [--index-output=<file>] (--empty | <tree-ish1> [<tree-ish2> [<tree-ish3>]])",
NULL
};
struct unpack_trees_options opts;
int prefix_set = 0;
const struct option read_tree_options[] = {
- { OPTION_CALLBACK, 0, "index-output", NULL, "FILE",
- "write resulting index to <FILE>",
+ { OPTION_CALLBACK, 0, "index-output", NULL, "file",
+ "write resulting index to <file>",
PARSE_OPT_NONEG, index_output_cb },
- OPT__VERBOSE(&opts.verbose_update),
+ OPT_SET_INT(0, "empty", &read_empty,
+ "only empty the index", 1),
+ OPT__VERBOSE(&opts.verbose_update, "be verbose"),
OPT_GROUP("Merging"),
OPT_SET_INT('m', NULL, &opts.merge,
"perform a merge in addition to a read", 1),
die("failed to unpack tree object %s", arg);
stage++;
}
+ if (nr_trees == 0 && !read_empty)
+ warning("read-tree: emptying the index with no arguments is deprecated; use --empty");
+ else if (nr_trees > 0 && read_empty)
+ die("passing trees as arguments contradicts --empty");
+
if (1 < opts.index_only + opts.update)
die("-u and -i at the same time makes no sense");
if ((opts.update||opts.index_only) && !opts.merge)
diff --git a/builtin/remote-ext.c b/builtin/remote-ext.c
--- /dev/null
+++ b/builtin/remote-ext.c
@@ -0,0 +1,246 @@
+#include "git-compat-util.h"
+#include "transport.h"
+#include "run-command.h"
+
+/*
+ * URL syntax:
+ * 'command [arg1 [arg2 [...]]]' Invoke command with given arguments.
+ * Special characters:
+ * '% ': Literal space in argument.
+ * '%%': Literal percent sign.
+ * '%S': Name of service (git-upload-pack/git-upload-archive/
+ * git-receive-pack.
+ * '%s': Same as \s, but with possible git- prefix stripped.
+ * '%G': Only allowed as first 'character' of argument. Do not pass this
+ * Argument to command, instead send this as name of repository
+ * in in-line git://-style request (also activates sending this
+ * style of request).
+ * '%V': Only allowed as first 'character' of argument. Used in
+ * conjunction with '%G': Do not pass this argument to command,
+ * instead send this as vhost in git://-style request (note: does
+ * not activate sending git:// style request).
+ */
+
+static char *git_req;
+static char *git_req_vhost;
+
+static char *strip_escapes(const char *str, const char *service,
+ const char **next)
+{
+ size_t rpos = 0;
+ int escape = 0;
+ char special = 0;
+ size_t pslen = 0;
+ size_t pSlen = 0;
+ size_t psoff = 0;
+ struct strbuf ret = STRBUF_INIT;
+
+ /* Calculate prefix length for \s and lengths for \s and \S */
+ if (!strncmp(service, "git-", 4))
+ psoff = 4;
+ pSlen = strlen(service);
+ pslen = pSlen - psoff;
+
+ /* Pass the service to command. */
+ setenv("GIT_EXT_SERVICE", service, 1);
+ setenv("GIT_EXT_SERVICE_NOPREFIX", service + psoff, 1);
+
+ /* Scan the length of argument. */
+ while (str[rpos] && (escape || str[rpos] != ' ')) {
+ if (escape) {
+ switch (str[rpos]) {
+ case ' ':
+ case '%':
+ case 's':
+ case 'S':
+ break;
+ case 'G':
+ case 'V':
+ special = str[rpos];
+ if (rpos == 1)
+ break;
+ /* Fall-through to error. */
+ default:
+ die("Bad remote-ext placeholder '%%%c'.",
+ str[rpos]);
+ }
+ escape = 0;
+ } else
+ escape = (str[rpos] == '%');
+ rpos++;
+ }
+ if (escape && !str[rpos])
+ die("remote-ext command has incomplete placeholder");
+ *next = str + rpos;
+ if (**next == ' ')
+ ++*next; /* Skip over space */
+
+ /*
+ * Do the actual placeholder substitution. The string will be short
+ * enough not to overflow integers.
+ */
+ rpos = special ? 2 : 0; /* Skip first 2 bytes in specials. */
+ escape = 0;
+ while (str[rpos] && (escape || str[rpos] != ' ')) {
+ if (escape) {
+ switch (str[rpos]) {
+ case ' ':
+ case '%':
+ strbuf_addch(&ret, str[rpos]);
+ break;
+ case 's':
+ strbuf_addstr(&ret, service + psoff);
+ break;
+ case 'S':
+ strbuf_addstr(&ret, service);
+ break;
+ }
+ escape = 0;
+ } else
+ switch (str[rpos]) {
+ case '%':
+ escape = 1;
+ break;
+ default:
+ strbuf_addch(&ret, str[rpos]);
+ break;
+ }
+ rpos++;
+ }
+ switch (special) {
+ case 'G':
+ git_req = strbuf_detach(&ret, NULL);
+ return NULL;
+ case 'V':
+ git_req_vhost = strbuf_detach(&ret, NULL);
+ return NULL;
+ default:
+ return strbuf_detach(&ret, NULL);
+ }
+}
+
+/* Should be enough... */
+#define MAXARGUMENTS 256
+
+static const char **parse_argv(const char *arg, const char *service)
+{
+ int arguments = 0;
+ int i;
+ const char **ret;
+ char *temparray[MAXARGUMENTS + 1];
+
+ while (*arg) {
+ char *expanded;
+ if (arguments == MAXARGUMENTS)
+ die("remote-ext command has too many arguments");
+ expanded = strip_escapes(arg, service, &arg);
+ if (expanded)
+ temparray[arguments++] = expanded;
+ }
+
+ ret = xmalloc((arguments + 1) * sizeof(char *));
+ for (i = 0; i < arguments; i++)
+ ret[i] = temparray[i];
+ ret[arguments] = NULL;
+ return ret;
+}
+
+static void send_git_request(int stdin_fd, const char *serv, const char *repo,
+ const char *vhost)
+{
+ size_t bufferspace;
+ size_t wpos = 0;
+ char *buffer;
+
+ /*
+ * Request needs 12 bytes extra if there is vhost (xxxx \0host=\0) and
+ * 6 bytes extra (xxxx \0) if there is no vhost.
+ */
+ if (vhost)
+ bufferspace = strlen(serv) + strlen(repo) + strlen(vhost) + 12;
+ else
+ bufferspace = strlen(serv) + strlen(repo) + 6;
+
+ if (bufferspace > 0xFFFF)
+ die("Request too large to send");
+ buffer = xmalloc(bufferspace);
+
+ /* Make the packet. */
+ wpos = sprintf(buffer, "%04x%s %s%c", (unsigned)bufferspace,
+ serv, repo, 0);
+
+ /* Add vhost if any. */
+ if (vhost)
+ sprintf(buffer + wpos, "host=%s%c", vhost, 0);
+
+ /* Send the request */
+ if (write_in_full(stdin_fd, buffer, bufferspace) < 0)
+ die_errno("Failed to send request");
+
+ free(buffer);
+}
+
+static int run_child(const char *arg, const char *service)
+{
+ int r;
+ struct child_process child;
+
+ memset(&child, 0, sizeof(child));
+ child.in = -1;
+ child.out = -1;
+ child.err = 0;
+ child.argv = parse_argv(arg, service);
+
+ if (start_command(&child) < 0)
+ die("Can't run specified command");
+
+ if (git_req)
+ send_git_request(child.in, service, git_req, git_req_vhost);
+
+ r = bidirectional_transfer_loop(child.out, child.in);
+ if (!r)
+ r = finish_command(&child);
+ else
+ finish_command(&child);
+ return r;
+}
+
+#define MAXCOMMAND 4096
+
+static int command_loop(const char *child)
+{
+ char buffer[MAXCOMMAND];
+
+ while (1) {
+ size_t i;
+ if (!fgets(buffer, MAXCOMMAND - 1, stdin)) {
+ if (ferror(stdin))
+ die("Comammand input error");
+ exit(0);
+ }
+ /* Strip end of line characters. */
+ i = strlen(buffer);
+ while (i > 0 && isspace(buffer[i - 1]))
+ buffer[--i] = 0;
+
+ if (!strcmp(buffer, "capabilities")) {
+ printf("*connect\n\n");
+ fflush(stdout);
+ } else if (!strncmp(buffer, "connect ", 8)) {
+ printf("\n");
+ fflush(stdout);
+ return run_child(child, buffer + 8);
+ } else {
+ fprintf(stderr, "Bad command");
+ return 1;
+ }
+ }
+}
+
+int cmd_remote_ext(int argc, const char **argv, const char *prefix)
+{
+ if (argc != 3)
+ die("Expected two arguments");
+
+ return command_loop(argv[2]);
+}
diff --git a/builtin/remote-fd.c b/builtin/remote-fd.c
--- /dev/null
+++ b/builtin/remote-fd.c
@@ -0,0 +1,79 @@
+#include "git-compat-util.h"
+#include "transport.h"
+
+/*
+ * URL syntax:
+ * 'fd::<inoutfd>[/<anything>]' Read/write socket pair
+ * <inoutfd>.
+ * 'fd::<infd>,<outfd>[/<anything>]' Read pipe <infd> and write
+ * pipe <outfd>.
+ * [foo] indicates 'foo' is optional. <anything> is any string.
+ *
+ * The data output to <outfd>/<inoutfd> should be passed unmolested to
+ * git-receive-pack/git-upload-pack/git-upload-archive and output of
+ * git-receive-pack/git-upload-pack/git-upload-archive should be passed
+ * unmolested to <infd>/<inoutfd>.
+ *
+ */
+
+#define MAXCOMMAND 4096
+
+static void command_loop(int input_fd, int output_fd)
+{
+ char buffer[MAXCOMMAND];
+
+ while (1) {
+ size_t i;
+ if (!fgets(buffer, MAXCOMMAND - 1, stdin)) {
+ if (ferror(stdin))
+ die("Input error");
+ return;
+ }
+ /* Strip end of line characters. */
+ i = strlen(buffer);
+ while (i > 0 && isspace(buffer[i - 1]))
+ buffer[--i] = 0;
+
+ if (!strcmp(buffer, "capabilities")) {
+ printf("*connect\n\n");
+ fflush(stdout);
+ } else if (!strncmp(buffer, "connect ", 8)) {
+ printf("\n");
+ fflush(stdout);
+ if (bidirectional_transfer_loop(input_fd,
+ output_fd))
+ die("Copying data between file descriptors failed");
+ return;
+ } else {
+ die("Bad command: %s", buffer);
+ }
+ }
+}
+
+int cmd_remote_fd(int argc, const char **argv, const char *prefix)
+{
+ int input_fd = -1;
+ int output_fd = -1;
+ char *end;
+
+ if (argc != 3)
+ die("Expected two arguments");
+
+ input_fd = (int)strtoul(argv[2], &end, 10);
+
+ if ((end == argv[2]) || (*end != ',' && *end != '/' && *end))
+ die("Bad URL syntax");
+
+ if (*end == '/' || !*end) {
+ output_fd = input_fd;
+ } else {
+ char *end2;
+ output_fd = (int)strtoul(end + 1, &end2, 10);
+
+ if ((end2 == end + 1) || (*end2 != '/' && *end2))
+ die("Bad URL syntax");
+ }
+
+ command_loop(input_fd, output_fd);
+ return 0;
+}
diff --git a/builtin/remote.c b/builtin/remote.c
index e9a6e09257f445b1c26833dd9fae89518369daa7..cb26080956077f8c9ae02c91ffdc341293a4f9f1 100644 (file)
--- a/builtin/remote.c
+++ b/builtin/remote.c
return 0;
}
- /* don't delete non-remote refs */
+ /* don't delete non-remote-tracking refs */
if (prefixcmp(refname, "refs/remotes")) {
/* advise user how to delete local branches */
if (!prefixcmp(refname, "refs/heads/"))
if (skipped.nr) {
fprintf(stderr, skipped.nr == 1 ?
- "Note: A non-remote branch was not removed; "
+ "Note: A branch outside the refs/remotes/ hierarchy was not removed;\n"
"to delete it, use:\n" :
- "Note: Non-remote branches were not removed; "
+ "Note: Some branches outside the refs/remotes/ hierarchy were not removed;\n"
"to delete them, use:\n");
for (i = 0; i < skipped.nr; i++)
fprintf(stderr, " git branch -d %s\n",
{
int dry_run = 0, result = 0;
struct option options[] = {
- OPT__DRY_RUN(&dry_run),
+ OPT__DRY_RUN(&dry_run, "dry run"),
OPT_END()
};
int cmd_remote(int argc, const char **argv, const char *prefix)
{
struct option options[] = {
- OPT_BOOLEAN('v', "verbose", &verbose, "be verbose; must be placed before a subcommand"),
+ OPT__VERBOSE(&verbose, "be verbose; must be placed before a subcommand"),
OPT_END()
};
int result;
diff --git a/builtin/rerere.c b/builtin/rerere.c
index 642bf35587ed994948d3eeac0a189ae29e39bf11..67cbfeb152386a1c918a103a2073d83138bfdff0 100644 (file)
--- a/builtin/rerere.c
+++ b/builtin/rerere.c
#include "xdiff-interface.h"
static const char * const rerere_usage[] = {
- "git rerere [clear | status | diff | gc]",
+ "git rerere [clear | status | remaining | diff | gc]",
NULL,
};
else if (!strcmp(argv[0], "status"))
for (i = 0; i < merge_rr.nr; i++)
printf("%s\n", merge_rr.items[i].string);
- else if (!strcmp(argv[0], "diff"))
+ else if (!strcmp(argv[0], "remaining")) {
+ rerere_remaining(&merge_rr);
+ for (i = 0; i < merge_rr.nr; i++) {
+ if (merge_rr.items[i].util != RERERE_RESOLVED)
+ printf("%s\n", merge_rr.items[i].string);
+ else
+ /* prepare for later call to
+ * string_list_clear() */
+ merge_rr.items[i].util = NULL;
+ }
+ } else if (!strcmp(argv[0], "diff"))
for (i = 0; i < merge_rr.nr; i++) {
const char *path = merge_rr.items[i].string;
const char *name = (const char *)merge_rr.items[i].util;
diff --git a/builtin/reset.c b/builtin/reset.c
index 0037be4693bbd875e644df13ce4a9fae31e7378c..5de2bceeec8c1d243ed6da70464d0c4dd60d7352 100644 (file)
--- a/builtin/reset.c
+++ b/builtin/reset.c
struct commit *commit;
char *reflog_action, msg[1024];
const struct option options[] = {
- OPT__QUIET(&quiet),
+ OPT__QUIET(&quiet, "be quiet, only report errors"),
OPT_SET_INT(0, "mixed", &reset_type,
"reset HEAD and index", MIXED),
OPT_SET_INT(0, "soft", &reset_type, "reset only HEAD", SOFT),
diff --git a/builtin/revert.c b/builtin/revert.c
index bb6e9e83b756b47dae449064ca7762fe3558fc0e..dc1b702edc6b4bd95ebf41c12bf1da6fe9d6850d 100644 (file)
--- a/builtin/revert.c
+++ b/builtin/revert.c
static int allow_rerere_auto;
static const char *me;
+
+/* Merge strategy. */
static const char *strategy;
+static const char **xopts;
+static size_t xopts_nr, xopts_alloc;
#define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
return action == REVERT ? revert_usage : cherry_pick_usage;
}
+static int option_parse_x(const struct option *opt,
+ const char *arg, int unset)
+{
+ if (unset)
+ return 0;
+
+ ALLOC_GROW(xopts, xopts_nr + 1, xopts_alloc);
+ xopts[xopts_nr++] = xstrdup(arg);
+ return 0;
+}
+
static void parse_args(int argc, const char **argv)
{
const char * const * usage_str = revert_or_cherry_pick_usage();
OPT_INTEGER('m', "mainline", &mainline, "parent number"),
OPT_RERERE_AUTOUPDATE(&allow_rerere_auto),
OPT_STRING(0, "strategy", &strategy, "strategy", "merge strategy"),
+ OPT_CALLBACK('X', "strategy-option", &xopts, "option",
+ "option for merge strategy", option_parse_x),
OPT_END(),
OPT_END(),
OPT_END(),
struct merge_options o;
struct tree *result, *next_tree, *base_tree, *head_tree;
int clean, index_fd;
+ const char **xopt;
static struct lock_file index_lock;
index_fd = hold_locked_index(&index_lock, 1);
read_cache();
- /*
- * NEEDSWORK: cherry-picking between branches with
- * different end-of-line normalization is a pain;
- * plumb in an option to set o.renormalize?
- * (or better: arbitrary -X options)
- */
init_merge_options(&o);
o.ancestor = base ? base_label : "(empty tree)";
o.branch1 = "HEAD";
next_tree = next ? next->tree : empty_tree();
base_tree = base ? base->tree : empty_tree();
+ for (xopt = xopts; xopt != xopts + xopts_nr; xopt++)
+ parse_merge_opt(&o, *xopt);
+
clean = merge_trees(&o,
head_tree,
next_tree, base_tree, &result);
commit_list_insert(base, &common);
commit_list_insert(next, &remotes);
- res = try_merge_command(strategy, common,
+ res = try_merge_command(strategy, xopts_nr, xopts, common,
sha1_to_hex(head), remotes);
free_commit_list(common);
free_commit_list(remotes);
diff --git a/builtin/rm.c b/builtin/rm.c
index f3772c84de0a1f1a18123e55ae6e6983739f590b..ff491d77612ffb3c906c5e1ffa232f4410e4b661 100644 (file)
--- a/builtin/rm.c
+++ b/builtin/rm.c
const char **name;
} list;
-static void add_list(const char *name)
-{
- if (list.nr >= list.alloc) {
- list.alloc = alloc_nr(list.alloc);
- list.name = xrealloc(list.name, list.alloc * sizeof(const char *));
- }
- list.name[list.nr++] = name;
-}
-
static int check_local_mod(unsigned char *head, int index_only)
{
/*
@@ -139,10 +130,10 @@ static int show_only = 0, force = 0, index_only = 0, recursive = 0, quiet = 0;
static int ignore_unmatch = 0;
static struct option builtin_rm_options[] = {
- OPT__DRY_RUN(&show_only),
- OPT__QUIET(&quiet),
+ OPT__DRY_RUN(&show_only, "dry run"),
+ OPT__QUIET(&quiet, "do not list removed files"),
OPT_BOOLEAN( 0 , "cached", &index_only, "only remove from the index"),
- OPT_BOOLEAN('f', "force", &force, "override the up-to-date check"),
+ OPT__FORCE(&force, "override the up-to-date check"),
OPT_BOOLEAN('r', NULL, &recursive, "allow recursive removal"),
OPT_BOOLEAN( 0 , "ignore-unmatch", &ignore_unmatch,
"exit with a zero status even if nothing matched"),
struct cache_entry *ce = active_cache[i];
if (!match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, seen))
continue;
- add_list(ce->name);
+ ALLOC_GROW(list.name, list.nr + 1, list.alloc);
+ list.name[list.nr++] = ce->name;
}
if (pathspec) {
diff --git a/builtin/send-pack.c b/builtin/send-pack.c
index 8aa303158b2461c9272c8ca5b094149a54cc35d2..2cd1c40b70890732905ad9433e452aa9a9548533 100644 (file)
--- a/builtin/send-pack.c
+++ b/builtin/send-pack.c
NULL,
NULL,
NULL,
+ NULL,
};
struct child_process po;
int i;
argv[i++] = "--delta-base-offset";
if (args->quiet)
argv[i++] = "-q";
+ if (args->progress)
+ argv[i++] = "--progress";
memset(&po, 0, sizeof(po));
po.argv = argv;
po.in = -1;
diff --git a/builtin/shortlog.c b/builtin/shortlog.c
index 2135b0dde11faa0c501682ed074e0ba96bf28b42..1a21e4b0538565f7a64488ed7b3a5c317e559699 100644 (file)
--- a/builtin/shortlog.c
+++ b/builtin/shortlog.c
git_config(git_default_config, NULL);
shortlog_init(&log);
init_revisions(&rev, prefix);
- parse_options_start(&ctx, argc, argv, prefix, PARSE_OPT_KEEP_DASHDASH |
- PARSE_OPT_KEEP_ARGV0);
+ parse_options_start(&ctx, argc, argv, prefix, options,
+ PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_ARGV0);
for (;;) {
switch (parse_options_step(&ctx, options, shortlog_usage)) {
diff --git a/builtin/show-branch.c b/builtin/show-branch.c
index 8663ccaa99299447a42da64f9f2c77f772996902..da695815e26c281cbacfbf865dfa72da44df9f19 100644 (file)
--- a/builtin/show-branch.c
+++ b/builtin/show-branch.c
if (mark_seen(p, seen_p) && !still_interesting)
extra--;
p->object.flags |= flags;
- insert_by_date(p, list_p);
+ commit_list_insert_by_date(p, list_p);
}
}
*/
commit->object.flags |= flag;
if (commit->object.flags == flag)
- insert_by_date(commit, &list);
+ commit_list_insert_by_date(commit, &list);
rev[num_rev] = commit;
}
for (i = 0; i < num_rev; i++)
if (0 <= extra)
join_revs(&list, &seen, num_rev, extra);
- sort_by_date(&seen);
+ commit_list_sort_by_date(&seen);
if (merge_base)
return show_merge_base(seen, num_rev);
diff --git a/builtin/show-ref.c b/builtin/show-ref.c
index be9b512eebc72984e6ff4cdd4859ab7951539e19..45f0340c3ea004822477ed70fdd14e05dfcd9bb5 100644 (file)
--- a/builtin/show-ref.c
+++ b/builtin/show-ref.c
"only show SHA1 hash using <n> digits",
PARSE_OPT_OPTARG, &hash_callback },
OPT__ABBREV(&abbrev),
- OPT__QUIET(&quiet),
+ OPT__QUIET(&quiet,
+ "do not print results to stdout (useful with --verify)"),
{ OPTION_CALLBACK, 0, "exclude-existing", &exclude_existing_arg,
"pattern", "show refs from stdin that aren't in local repository",
PARSE_OPT_OPTARG | PARSE_OPT_NONEG, exclude_existing_callback },
diff --git a/builtin/symbolic-ref.c b/builtin/symbolic-ref.c
index ca855a5eb239f4dadccd53369e38db4e78b1d13f..dea849c3c5ec0c0a7ee0ac98e1ae62a6dacf5806 100644 (file)
--- a/builtin/symbolic-ref.c
+++ b/builtin/symbolic-ref.c
int quiet = 0;
const char *msg = NULL;
struct option options[] = {
- OPT__QUIET(&quiet),
+ OPT__QUIET(&quiet,
+ "suppress error message for non-symbolic (detached) refs"),
OPT_STRING('m', NULL, &msg, "reason", "reason of the update"),
OPT_END(),
};
diff --git a/builtin/tag.c b/builtin/tag.c
index 617a58f0583049716c7b73577ac6df0e132bebea..7cf48abca857f049243dfab7c95fea70d216ea93 100644 (file)
--- a/builtin/tag.c
+++ b/builtin/tag.c
OPT_GROUP("Tag creation options"),
OPT_BOOLEAN('a', NULL, &annotate,
"annotated tag, needs a message"),
- OPT_CALLBACK('m', NULL, &msg, "msg",
- "message for the tag", parse_msg_arg),
- OPT_FILENAME('F', NULL, &msgfile, "message in a file"),
+ OPT_CALLBACK('m', NULL, &msg, "message",
+ "tag message", parse_msg_arg),
+ OPT_FILENAME('F', NULL, &msgfile, "read message from file"),
OPT_BOOLEAN('s', NULL, &sign, "annotated and GPG-signed tag"),
OPT_STRING('u', NULL, &keyid, "key-id",
"use another key to sign the tag"),
- OPT_BOOLEAN('f', "force", &force, "replace the tag if exists"),
+ OPT__FORCE(&force, "replace the tag if exists"),
OPT_GROUP("Tag listing options"),
{
diff --git a/builtin/update-index.c b/builtin/update-index.c
index 62d9f3f0fa358058fcf54e4fcbd4c3101b1279db..d7850c6309aec0c1a4f640999fb757d7b32ba1b8 100644 (file)
--- a/builtin/update-index.c
+++ b/builtin/update-index.c
#include "builtin.h"
#include "refs.h"
#include "resolve-undo.h"
+#include "parse-options.h"
/*
* Default to not allowing changes to the list of files. The
strbuf_release(&uq);
}
-static const char update_index_usage[] =
-"git update-index [-q] [--add] [--replace] [--remove] [--unmerged] [--refresh] [--really-refresh] [--cacheinfo] [--chmod=(+|-)x] [--assume-unchanged] [--skip-worktree|--no-skip-worktree] [--info-only] [--force-remove] [--stdin] [--index-info] [--unresolve] [--again | -g] [--ignore-missing] [-z] [--verbose] [--] [<file>...]";
+static const char * const update_index_usage[] = {
+ "git update-index [options] [--] [<file>...]",
+ NULL
+};
static unsigned char head_sha1[20];
static unsigned char merge_head_sha1[20];
*/
int pos;
int has_head = 1;
- const char **pathspec = get_pathspec(prefix, av + 1);
+ const char **paths = get_pathspec(prefix, av + 1);
+ struct pathspec pathspec;
+
+ init_pathspec(&pathspec, paths);
if (read_ref("HEAD", head_sha1))
/* If there is no HEAD, that means it is an initial
struct cache_entry *old = NULL;
int save_nr;
- if (ce_stage(ce) || !ce_path_match(ce, pathspec))
+ if (ce_stage(ce) || !ce_path_match(ce, &pathspec))
continue;
if (has_head)
old = read_one_ent(NULL, head_sha1,
if (save_nr != active_nr)
goto redo;
}
+ free_pathspec(&pathspec);
+ return 0;
+}
+
+struct refresh_params {
+ unsigned int flags;
+ int *has_errors;
+};
+
+static int refresh(struct refresh_params *o, unsigned int flag)
+{
+ setup_work_tree();
+ *o->has_errors |= refresh_cache(o->flags | flag);
+ return 0;
+}
+
+static int refresh_callback(const struct option *opt,
+ const char *arg, int unset)
+{
+ return refresh(opt->value, 0);
+}
+
+static int really_refresh_callback(const struct option *opt,
+ const char *arg, int unset)
+{
+ return refresh(opt->value, REFRESH_REALLY);
+}
+
+static int chmod_callback(const struct option *opt,
+ const char *arg, int unset)
+{
+ char *flip = opt->value;
+ if ((arg[0] != '-' && arg[0] != '+') || arg[1] != 'x' || arg[2])
+ return error("option 'chmod' expects \"+x\" or \"-x\"");
+ *flip = arg[0];
+ return 0;
+}
+
+static int resolve_undo_clear_callback(const struct option *opt,
+ const char *arg, int unset)
+{
+ resolve_undo_clear();
+ return 0;
+}
+
+static int cacheinfo_callback(struct parse_opt_ctx_t *ctx,
+ const struct option *opt, int unset)
+{
+ unsigned char sha1[20];
+ unsigned int mode;
+
+ if (ctx->argc <= 3)
+ return error("option 'cacheinfo' expects three arguments");
+ if (strtoul_ui(*++ctx->argv, 8, &mode) ||
+ get_sha1_hex(*++ctx->argv, sha1) ||
+ add_cacheinfo(mode, sha1, *++ctx->argv, 0))
+ die("git update-index: --cacheinfo cannot add %s", *ctx->argv);
+ ctx->argc -= 3;
+ return 0;
+}
+
+static int stdin_cacheinfo_callback(struct parse_opt_ctx_t *ctx,
+ const struct option *opt, int unset)
+{
+ int *line_termination = opt->value;
+
+ if (ctx->argc != 1)
+ return error("option '%s' must be the last argument", opt->long_name);
+ allow_add = allow_replace = allow_remove = 1;
+ read_index_info(*line_termination);
+ return 0;
+}
+
+static int stdin_callback(struct parse_opt_ctx_t *ctx,
+ const struct option *opt, int unset)
+{
+ int *read_from_stdin = opt->value;
+
+ if (ctx->argc != 1)
+ return error("option '%s' must be the last argument", opt->long_name);
+ *read_from_stdin = 1;
+ return 0;
+}
+
+static int unresolve_callback(struct parse_opt_ctx_t *ctx,
+ const struct option *opt, int flags)
+{
+ int *has_errors = opt->value;
+ const char *prefix = startup_info->prefix;
+
+ /* consume remaining arguments. */
+ *has_errors = do_unresolve(ctx->argc, ctx->argv,
+ prefix, prefix ? strlen(prefix) : 0);
+ if (*has_errors)
+ active_cache_changed = 0;
+
+ ctx->argv += ctx->argc - 1;
+ ctx->argc = 1;
+ return 0;
+}
+
+static int reupdate_callback(struct parse_opt_ctx_t *ctx,
+ const struct option *opt, int flags)
+{
+ int *has_errors = opt->value;
+ const char *prefix = startup_info->prefix;
+
+ /* consume remaining arguments. */
+ setup_work_tree();
+ *has_errors = do_reupdate(ctx->argc, ctx->argv,
+ prefix, prefix ? strlen(prefix) : 0);
+ if (*has_errors)
+ active_cache_changed = 0;
+
+ ctx->argv += ctx->argc - 1;
+ ctx->argc = 1;
return 0;
}
int cmd_update_index(int argc, const char **argv, const char *prefix)
{
- int i, newfd, entries, has_errors = 0, line_termination = '\n';
- int allow_options = 1;
+ int newfd, entries, has_errors = 0, line_termination = '\n';
int read_from_stdin = 0;
int prefix_length = prefix ? strlen(prefix) : 0;
char set_executable_bit = 0;
- unsigned int refresh_flags = 0;
+ struct refresh_params refresh_args = {0, &has_errors};
int lock_error = 0;
struct lock_file *lock_file;
+ struct parse_opt_ctx_t ctx;
+ int parseopt_state = PARSE_OPT_UNKNOWN;
+ struct option options[] = {
+ OPT_BIT('q', NULL, &refresh_args.flags,
+ "continue refresh even when index needs update",
+ REFRESH_QUIET),
+ OPT_BIT(0, "ignore-submodules", &refresh_args.flags,
+ "refresh: ignore submodules",
+ REFRESH_IGNORE_SUBMODULES),
+ OPT_SET_INT(0, "add", &allow_add,
+ "do not ignore new files", 1),
+ OPT_SET_INT(0, "replace", &allow_replace,
+ "let files replace directories and vice-versa", 1),
+ OPT_SET_INT(0, "remove", &allow_remove,
+ "notice files missing from worktree", 1),
+ OPT_BIT(0, "unmerged", &refresh_args.flags,
+ "refresh even if index contains unmerged entries",
+ REFRESH_UNMERGED),
+ {OPTION_CALLBACK, 0, "refresh", &refresh_args, NULL,
+ "refresh stat information",
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG,
+ refresh_callback},
+ {OPTION_CALLBACK, 0, "really-refresh", &refresh_args, NULL,
+ "like --refresh, but ignore assume-unchanged setting",
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG,
+ really_refresh_callback},
+ {OPTION_LOWLEVEL_CALLBACK, 0, "cacheinfo", NULL,
+ "<mode> <object> <path>",
+ "add the specified entry to the index",
+ PARSE_OPT_NOARG | /* disallow --cacheinfo=<mode> form */
+ PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP,
+ (parse_opt_cb *) cacheinfo_callback},
+ {OPTION_CALLBACK, 0, "chmod", &set_executable_bit, "(+/-)x",
+ "override the executable bit of the listed files",
+ PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP,
+ chmod_callback},
+ {OPTION_SET_INT, 0, "assume-unchanged", &mark_valid_only, NULL,
+ "mark files as \"not changing\"",
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, MARK_FLAG},
+ {OPTION_SET_INT, 0, "no-assume-unchanged", &mark_valid_only, NULL,
+ "clear assumed-unchanged bit",
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, UNMARK_FLAG},
+ {OPTION_SET_INT, 0, "skip-worktree", &mark_skip_worktree_only, NULL,
+ "mark files as \"index-only\"",
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, MARK_FLAG},
+ {OPTION_SET_INT, 0, "no-skip-worktree", &mark_skip_worktree_only, NULL,
+ "clear skip-worktree bit",
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, UNMARK_FLAG},
+ OPT_SET_INT(0, "info-only", &info_only,
+ "add to index only; do not add content to object database", 1),
+ OPT_SET_INT(0, "force-remove", &force_remove,
+ "remove named paths even if present in worktree", 1),
+ OPT_SET_INT('z', NULL, &line_termination,
+ "with --stdin: input lines are terminated by null bytes", '\0'),
+ {OPTION_LOWLEVEL_CALLBACK, 0, "stdin", &read_from_stdin, NULL,
+ "read list of paths to be updated from standard input",
+ PARSE_OPT_NONEG | PARSE_OPT_NOARG,
+ (parse_opt_cb *) stdin_callback},
+ {OPTION_LOWLEVEL_CALLBACK, 0, "index-info", &line_termination, NULL,
+ "add entries from standard input to the index",
+ PARSE_OPT_NONEG | PARSE_OPT_NOARG,
+ (parse_opt_cb *) stdin_cacheinfo_callback},
+ {OPTION_LOWLEVEL_CALLBACK, 0, "unresolve", &has_errors, NULL,
+ "repopulate stages #2 and #3 for the listed paths",
+ PARSE_OPT_NONEG | PARSE_OPT_NOARG,
+ (parse_opt_cb *) unresolve_callback},
+ {OPTION_LOWLEVEL_CALLBACK, 'g', "again", &has_errors, NULL,
+ "only update entries that differ from HEAD",
+ PARSE_OPT_NONEG | PARSE_OPT_NOARG,
+ (parse_opt_cb *) reupdate_callback},
+ OPT_BIT(0, "ignore-missing", &refresh_args.flags,
+ "ignore files missing from worktree",
+ REFRESH_IGNORE_MISSING),
+ OPT_SET_INT(0, "verbose", &verbose,
+ "report actions to standard output", 1),
+ {OPTION_CALLBACK, 0, "clear-resolve-undo", NULL, NULL,
+ "(for porcelains) forget saved unresolved conflicts",
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG,
+ resolve_undo_clear_callback},
+ OPT_END()
+ };
+
+ if (argc == 2 && !strcmp(argv[1], "-h"))
+ usage(update_index_usage[0]);
git_config(git_default_config, NULL);
if (entries < 0)
die("cache corrupted");
- for (i = 1 ; i < argc; i++) {
- const char *path = argv[i];
- const char *p;
+ /*
+ * Custom copy of parse_options() because we want to handle
+ * filename arguments as they come.
+ */
+ parse_options_start(&ctx, argc, argv, prefix,
+ options, PARSE_OPT_STOP_AT_NON_OPTION);
+ while (ctx.argc) {
+ if (parseopt_state != PARSE_OPT_DONE)
+ parseopt_state = parse_options_step(&ctx, options,
+ update_index_usage);
+ if (!ctx.argc)
+ break;
+ switch (parseopt_state) {
+ case PARSE_OPT_HELP:
+ exit(129);
+ case PARSE_OPT_NON_OPTION:
+ case PARSE_OPT_DONE:
+ {
+ const char *path = ctx.argv[0];
+ const char *p;
- if (allow_options && *path == '-') {
- if (!strcmp(path, "--")) {
- allow_options = 0;
- continue;
- }
- if (!strcmp(path, "-q")) {
- refresh_flags |= REFRESH_QUIET;
- continue;
- }
- if (!strcmp(path, "--ignore-submodules")) {
- refresh_flags |= REFRESH_IGNORE_SUBMODULES;
- continue;
- }
- if (!strcmp(path, "--add")) {
- allow_add = 1;
- continue;
- }
- if (!strcmp(path, "--replace")) {
- allow_replace = 1;
- continue;
- }
- if (!strcmp(path, "--remove")) {
- allow_remove = 1;
- continue;
- }
- if (!strcmp(path, "--unmerged")) {
- refresh_flags |= REFRESH_UNMERGED;
- continue;
- }
- if (!strcmp(path, "--refresh")) {
- setup_work_tree();
- has_errors |= refresh_cache(refresh_flags);
- continue;
- }
- if (!strcmp(path, "--really-refresh")) {
- setup_work_tree();
- has_errors |= refresh_cache(REFRESH_REALLY | refresh_flags);
- continue;
- }
- if (!strcmp(path, "--cacheinfo")) {
- unsigned char sha1[20];
- unsigned int mode;
-
- if (i+3 >= argc)
- die("git update-index: --cacheinfo <mode> <sha1> <path>");
-
- if (strtoul_ui(argv[i+1], 8, &mode) ||
- get_sha1_hex(argv[i+2], sha1) ||
- add_cacheinfo(mode, sha1, argv[i+3], 0))
- die("git update-index: --cacheinfo"
- " cannot add %s", argv[i+3]);
- i += 3;
- continue;
- }
- if (!strcmp(path, "--chmod=-x") ||
- !strcmp(path, "--chmod=+x")) {
- if (argc <= i+1)
- die("git update-index: %s <path>", path);
- set_executable_bit = path[8];
- continue;
- }
- if (!strcmp(path, "--assume-unchanged")) {
- mark_valid_only = MARK_FLAG;
- continue;
- }
- if (!strcmp(path, "--no-assume-unchanged")) {
- mark_valid_only = UNMARK_FLAG;
- continue;
- }
- if (!strcmp(path, "--no-skip-worktree")) {
- mark_skip_worktree_only = UNMARK_FLAG;
- continue;
- }
- if (!strcmp(path, "--skip-worktree")) {
- mark_skip_worktree_only = MARK_FLAG;
- continue;
- }
- if (!strcmp(path, "--info-only")) {
- info_only = 1;
- continue;
- }
- if (!strcmp(path, "--force-remove")) {
- force_remove = 1;
- continue;
- }
- if (!strcmp(path, "-z")) {
- line_termination = 0;
- continue;
- }
- if (!strcmp(path, "--stdin")) {
- if (i != argc - 1)
- die("--stdin must be at the end");
- read_from_stdin = 1;
- break;
- }
- if (!strcmp(path, "--index-info")) {
- if (i != argc - 1)
- die("--index-info must be at the end");
- allow_add = allow_replace = allow_remove = 1;
- read_index_info(line_termination);
- break;
- }
- if (!strcmp(path, "--unresolve")) {
- has_errors = do_unresolve(argc - i, argv + i,
- prefix, prefix_length);
- if (has_errors)
- active_cache_changed = 0;
- goto finish;
- }
- if (!strcmp(path, "--again") || !strcmp(path, "-g")) {
- setup_work_tree();
- has_errors = do_reupdate(argc - i, argv + i,
- prefix, prefix_length);
- if (has_errors)
- active_cache_changed = 0;
- goto finish;
- }
- if (!strcmp(path, "--ignore-missing")) {
- refresh_flags |= REFRESH_IGNORE_MISSING;
- continue;
- }
- if (!strcmp(path, "--verbose")) {
- verbose = 1;
- continue;
- }
- if (!strcmp(path, "--clear-resolve-undo")) {
- resolve_undo_clear();
- continue;
- }
- if (!strcmp(path, "-h") || !strcmp(path, "--help"))
- usage(update_index_usage);
- die("unknown option %s", path);
+ setup_work_tree();
+ p = prefix_path(prefix, prefix_length, path);
+ update_one(p, NULL, 0);
+ if (set_executable_bit)
+ chmod_path(set_executable_bit, p);
+ if (p < path || p > path + strlen(path))
+ free((char *)p);
+ ctx.argc--;
+ ctx.argv++;
+ break;
+ }
+ case PARSE_OPT_UNKNOWN:
+ if (ctx.argv[0][1] == '-')
+ error("unknown option '%s'", ctx.argv[0] + 2);
+ else
+ error("unknown switch '%c'", *ctx.opt);
+ usage_with_options(update_index_usage, options);
}
- setup_work_tree();
- p = prefix_path(prefix, prefix_length, path);
- update_one(p, NULL, 0);
- if (set_executable_bit)
- chmod_path(set_executable_bit, p);
- if (p < path || p > path + strlen(path))
- free((char *)p);
}
+ argc = parse_options_end(&ctx);
+
if (read_from_stdin) {
struct strbuf buf = STRBUF_INIT, nbuf = STRBUF_INIT;
strbuf_release(&buf);
}
- finish:
if (active_cache_changed) {
if (newfd < 0) {
- if (refresh_flags & REFRESH_QUIET)
+ if (refresh_args.flags & REFRESH_QUIET)
exit(128);
unable_to_lock_index_die(get_index_file(), lock_error);
}
index 2b3fddcc69087d856b6a9c9f4383c5577a759c77..b90dce6358153b274a1e26afde9cc89aad473d14 100644 (file)
{
int force = 0;
struct option options[] = {
- OPT_BOOLEAN('f', "force", &force,
- "update the info files from scratch"),
+ OPT__FORCE(&force, "update the info files from scratch"),
OPT_END()
};
diff --git a/builtin/verify-tag.c b/builtin/verify-tag.c
index 86cac6d715dbc29b3586e84ea28f4083ddb2a1b8..313476604967bb2962350c82d45ffad04eab09e1 100644 (file)
--- a/builtin/verify-tag.c
+++ b/builtin/verify-tag.c
{
int i = 1, verbose = 0, had_error = 0;
const struct option verify_tag_options[] = {
- OPT__VERBOSE(&verbose),
+ OPT__VERBOSE(&verbose, "print tag contents"),
OPT_END()
};
diff --git a/bundle.c b/bundle.c
index 65ea26bdb8c2cef671030c3a62d87d84612e8144..f48fd7d4c1c706b68e879e4dfabdd3fb8597a328 100644 (file)
--- a/bundle.c
+++ b/bundle.c
int bundle_fd = -1;
int bundle_to_stdout;
const char **argv_boundary = xmalloc((argc + 4) * sizeof(const char *));
- const char **argv_pack = xmalloc(5 * sizeof(const char *));
+ const char **argv_pack = xmalloc(6 * sizeof(const char *));
int i, ref_count = 0;
char buffer[1024];
struct rev_info revs;
argv_pack[1] = "--all-progress-implied";
argv_pack[2] = "--stdout";
argv_pack[3] = "--thin";
- argv_pack[4] = NULL;
+ argv_pack[4] = "--delta-base-offset";
+ argv_pack[5] = NULL;
memset(&rls, 0, sizeof(rls));
rls.argv = argv_pack;
rls.in = -1;
index d85ce86f7fd72ee90553241316c1cf6d84a2898d..08a902210c6374c65f94a1238d895a65c7cde43a 100644 (file)
--- a/cache.h
+++ b/cache.h
*
* In-memory only flags
*/
-#define CE_UPDATE (0x10000)
-#define CE_REMOVE (0x20000)
-#define CE_UPTODATE (0x40000)
-#define CE_ADDED (0x80000)
+#define CE_UPDATE (1 << 16)
+#define CE_REMOVE (1 << 17)
+#define CE_UPTODATE (1 << 18)
+#define CE_ADDED (1 << 19)
-#define CE_HASHED (0x100000)
-#define CE_UNHASHED (0x200000)
-#define CE_CONFLICTED (0x800000)
+#define CE_HASHED (1 << 20)
+#define CE_UNHASHED (1 << 21)
+#define CE_WT_REMOVE (1 << 22) /* remove in work directory */
+#define CE_CONFLICTED (1 << 23)
-#define CE_WT_REMOVE (0x400000) /* remove in work directory */
-
-#define CE_UNPACKED (0x1000000)
+#define CE_UNPACKED (1 << 24)
+#define CE_NEW_SKIP_WORKTREE (1 << 25)
/*
* Extended on-disk flags
*/
-#define CE_INTENT_TO_ADD 0x20000000
-#define CE_SKIP_WORKTREE 0x40000000
+#define CE_INTENT_TO_ADD (1 << 29)
+#define CE_SKIP_WORKTREE (1 << 30)
/* CE_EXTENDED2 is for future extension */
-#define CE_EXTENDED2 0x80000000
+#define CE_EXTENDED2 (1 << 31)
#define CE_EXTENDED_FLAGS (CE_INTENT_TO_ADD | CE_SKIP_WORKTREE)
extern void setup_work_tree(void);
extern const char *setup_git_directory_gently(int *);
extern const char *setup_git_directory(void);
-extern const char *prefix_path(const char *prefix, int len, const char *path);
+extern char *prefix_path(const char *prefix, int len, const char *path);
extern const char *prefix_filename(const char *prefix, int len, const char *path);
extern int check_filename(const char *prefix, const char *name);
extern void verify_filename(const char *prefix, const char *name);
extern int ie_match_stat(const struct index_state *, struct cache_entry *, struct stat *, unsigned int);
extern int ie_modified(const struct index_state *, struct cache_entry *, struct stat *, unsigned int);
-extern int ce_path_match(const struct cache_entry *ce, const char **pathspec);
-extern int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, enum object_type type, const char *path);
+struct pathspec {
+ const char **raw; /* get_pathspec() result, not freed by free_pathspec() */
+ int nr;
+ int has_wildcard:1;
+ int recursive:1;
+ int max_depth;
+ struct pathspec_item {
+ const char *match;
+ int len;
+ int has_wildcard:1;
+ } *items;
+};
+
+extern int init_pathspec(struct pathspec *, const char **);
+extern void free_pathspec(struct pathspec *);
+extern int ce_path_match(const struct cache_entry *ce, const struct pathspec *pathspec);
+extern int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, enum object_type type, const char *path, int format_check);
extern int index_path(unsigned char *sha1, const char *path, struct stat *st, int write_object);
extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st);
#define REFRESH_IGNORE_MISSING 0x0008 /* ignore non-existent */
#define REFRESH_IGNORE_SUBMODULES 0x0010 /* ignore submodules */
#define REFRESH_IN_PORCELAIN 0x0020 /* user friendly output, not "needs update" */
-extern int refresh_index(struct index_state *, unsigned int flags, const char **pathspec, char *seen, char *header_msg);
+extern int refresh_index(struct index_state *, unsigned int flags, const char **pathspec, char *seen, const char *header_msg);
struct lock_file {
struct lock_file *next;
extern int prefer_symlink_refs;
extern int log_all_ref_updates;
extern int warn_ambiguous_refs;
+extern int unique_abbrev_extra_length;
extern int shared_repository;
extern const char *apply_default_whitespace;
extern const char *apply_default_ignorewhitespace;
enum push_default_type {
PUSH_DEFAULT_NOTHING = 0,
PUSH_DEFAULT_MATCHING,
- PUSH_DEFAULT_TRACKING,
+ PUSH_DEFAULT_UPSTREAM,
PUSH_DEFAULT_CURRENT
};
#define EMPTY_TREE_SHA1_HEX \
"4b825dc642cb6eb9a060e54bf8d69288fbee4904"
-#define EMPTY_TREE_SHA1_BIN \
+#define EMPTY_TREE_SHA1_BIN_LITERAL \
"\x4b\x82\x5d\xc6\x42\xcb\x6e\xb9\xa0\x60" \
"\xe5\x4b\xf8\xd6\x92\x88\xfb\xee\x49\x04"
+#define EMPTY_TREE_SHA1_BIN \
+ ((const unsigned char *) EMPTY_TREE_SHA1_BIN_LITERAL)
int git_mkstemp(char *path, size_t n, const char *template);
extern int git_config_parse_environment(void);
extern int git_config_from_parameters(config_fn_t fn, void *data);
extern int git_config(config_fn_t fn, void *);
+extern int git_config_early(config_fn_t fn, void *, const char *repo_config);
extern int git_parse_ulong(const char *, unsigned long *);
extern int git_config_int(const char *, const char *);
extern unsigned long git_config_ulong(const char *, const char *);
extern int git_config_string(const char **, const char *, const char *);
extern int git_config_pathname(const char **, const char *, const char *);
extern int git_config_set(const char *, const char *);
+extern int git_config_parse_key(const char *, char **, int *);
extern int git_config_set_multivar(const char *, const char *, const char *, int);
extern int git_config_rename_section(const char *, const char *);
extern const char *git_etc_gitconfig(void);
extern int git_config_system(void);
extern int git_config_global(void);
extern int config_error_nonbool(const char *);
+extern const char *get_log_output_encoding(void);
+extern const char *get_commit_output_encoding(void);
+
extern const char *config_exclusive_filename;
#define MAX_GITNAME (1000)
extern void trace_printf(const char *format, ...);
__attribute__((format (printf, 2, 3)))
extern void trace_argv_printf(const char **argv, const char *format, ...);
+extern void trace_repo_setup(const char *prefix);
/* convert.c */
/* returns 1 if *dst was used */
@@ -1087,15 +1111,17 @@ void shift_tree_by(const unsigned char *, const unsigned char *, unsigned char *
/*
* whitespace rules.
* used by both diff and apply
+ * last two digits are tab width
*/
-#define WS_BLANK_AT_EOL 01
-#define WS_SPACE_BEFORE_TAB 02
-#define WS_INDENT_WITH_NON_TAB 04
-#define WS_CR_AT_EOL 010
-#define WS_BLANK_AT_EOF 020
-#define WS_TAB_IN_INDENT 040
+#define WS_BLANK_AT_EOL 0100
+#define WS_SPACE_BEFORE_TAB 0200
+#define WS_INDENT_WITH_NON_TAB 0400
+#define WS_CR_AT_EOL 01000
+#define WS_BLANK_AT_EOF 02000
+#define WS_TAB_IN_INDENT 04000
#define WS_TRAILING_SPACE (WS_BLANK_AT_EOL|WS_BLANK_AT_EOF)
-#define WS_DEFAULT_RULE (WS_TRAILING_SPACE|WS_SPACE_BEFORE_TAB)
+#define WS_DEFAULT_RULE (WS_TRAILING_SPACE|WS_SPACE_BEFORE_TAB|8)
+#define WS_TAB_WIDTH_MASK 077
extern unsigned whitespace_rule_cfg;
extern unsigned whitespace_rule(const char *);
extern unsigned parse_whitespace_rule(const char *);
@@ -1104,6 +1130,7 @@ extern void ws_check_emit(const char *line, int len, unsigned ws_rule, FILE *str
extern char *whitespace_error_string(unsigned ws);
extern void ws_fix_copy(struct strbuf *, const char *, int, unsigned, int *);
extern int ws_blank_line(const char *line, int len, unsigned ws_rule);
+#define ws_tab_width(rule) ((rule) & WS_TAB_WIDTH_MASK)
/* ls-files */
int report_path_error(const char *ps_matched, const char **pathspec, int prefix_offset);
/* git.c */
struct startup_info {
int have_repository;
+ const char *prefix;
};
extern struct startup_info *startup_info;
index 1b00554dd50d2960b4e66f229b0d2d0da8eb8c28..6a5a54ec668e08ddef0d2f27359f3ebb3272243b 100644 (file)
--- a/color.c
+++ b/color.c
va_end(args);
return r;
}
+
+int color_is_nil(const char *c)
+{
+ return !strcmp(c, "NIL");
+}
index 03ca0647485e8044d4131f2c61c37b4e9408cd2a..170ff4074d220e7e62264c4c63d1419f4a59d664 100644 (file)
--- a/color.h
+++ b/color.h
#define GIT_COLOR_BG_MAGENTA "\033[45m"
#define GIT_COLOR_BG_CYAN "\033[46m"
+/* A special value meaning "no color selected" */
+#define GIT_COLOR_NIL "NIL"
+
/*
* This variable stores the value of color.ui
*/
__attribute__((format (printf, 3, 4)))
int color_fprintf_ln(FILE *fp, const char *color, const char *fmt, ...);
+int color_is_nil(const char *color);
+
#endif /* COLOR_H */
diff --git a/commit.c b/commit.c
index 0094ec1c9278332f736d2814c847b272788a7dd3..ac337c7d7dc1724fa918f9340816d3102edb10bd 100644 (file)
--- a/commit.c
+++ b/commit.c
return check_commit(obj, sha1, 0);
}
+struct commit *lookup_commit_reference_by_name(const char *name)
+{
+ unsigned char sha1[20];
+ struct commit *commit;
+
+ if (get_sha1(name, sha1))
+ return NULL;
+ commit = lookup_commit_reference(sha1);
+ if (!commit || parse_commit(commit))
+ return NULL;
+ return commit;
+}
+
static unsigned long parse_commit_date(const char *buf, const char *tail)
{
const char *dateptr;
buf[--len] = '\0';
if (buf[0] == '#' || buf[0] == '\0')
return NULL;
- if ((len + 1) % 41) {
- bad_graft_data:
- error("bad graft data: %s", buf);
- free(graft);
- return NULL;
- }
+ if ((len + 1) % 41)
+ goto bad_graft_data;
i = (len + 1) / 41 - 1;
graft = xmalloc(sizeof(*graft) + 20 * i);
graft->nr_parent = i;
goto bad_graft_data;
}
return graft;
+
+bad_graft_data:
+ error("bad graft data: %s", buf);
+ free(graft);
+ return NULL;
}
static int read_graft_file(const char *graft_file)
return 0;
}
-int parse_commit_buffer(struct commit *item, void *buffer, unsigned long size)
+int parse_commit_buffer(struct commit *item, const void *buffer, unsigned long size)
{
- char *tail = buffer;
- char *bufptr = buffer;
+ const char *tail = buffer;
+ const char *bufptr = buffer;
unsigned char parent[20];
struct commit_list **pptr;
struct commit_graft *graft;
}
}
-struct commit_list * insert_by_date(struct commit *item, struct commit_list **list)
+struct commit_list * commit_list_insert_by_date(struct commit *item, struct commit_list **list)
{
struct commit_list **pp = list;
struct commit_list *p;
@@ -374,11 +388,11 @@ struct commit_list * insert_by_date(struct commit *item, struct commit_list **li
}
-void sort_by_date(struct commit_list **list)
+void commit_list_sort_by_date(struct commit_list **list)
{
struct commit_list *ret = NULL;
while (*list) {
- insert_by_date((*list)->item, &ret);
+ commit_list_insert_by_date((*list)->item, &ret);
*list = (*list)->next;
}
*list = ret;
struct commit *commit = parents->item;
if (!parse_commit(commit) && !(commit->object.flags & mark)) {
commit->object.flags |= mark;
- insert_by_date(commit, list);
+ commit_list_insert_by_date(commit, list);
}
parents = parents->next;
}
/* process the list in topological order */
if (!lifo)
- sort_by_date(&work);
+ commit_list_sort_by_date(&work);
pptr = list;
*list = NULL;
*/
if (--parent->indegree == 1) {
if (!lifo)
- insert_by_date(parent, &work);
+ commit_list_insert_by_date(parent, &work);
else
commit_list_insert(parent, &work);
}
@@ -573,10 +587,10 @@ static struct commit_list *merge_bases_many(struct commit *one, int n, struct co
}
one->object.flags |= PARENT1;
- insert_by_date(one, &list);
+ commit_list_insert_by_date(one, &list);
for (i = 0; i < n; i++) {
twos[i]->object.flags |= PARENT2;
- insert_by_date(twos[i], &list);
+ commit_list_insert_by_date(twos[i], &list);
}
while (interesting(list)) {
@@ -594,7 +608,7 @@ static struct commit_list *merge_bases_many(struct commit *one, int n, struct co
if (flags == (PARENT1 | PARENT2)) {
if (!(commit->object.flags & RESULT)) {
commit->object.flags |= RESULT;
- insert_by_date(commit, &result);
+ commit_list_insert_by_date(commit, &result);
}
/* Mark parents of a found merge stale */
flags |= STALE;
@@ -608,7 +622,7 @@ static struct commit_list *merge_bases_many(struct commit *one, int n, struct co
if (parse_commit(p))
return NULL;
p->object.flags |= flags;
- insert_by_date(p, &list);
+ commit_list_insert_by_date(p, &list);
}
}
@@ -618,7 +632,7 @@ static struct commit_list *merge_bases_many(struct commit *one, int n, struct co
while (list) {
struct commit_list *next = list->next;
if (!(list->item->object.flags & STALE))
- insert_by_date(list->item, &result);
+ commit_list_insert_by_date(list->item, &result);
free(list);
list = next;
}
result = NULL;
for (i = 0; i < cnt; i++) {
if (rslt[i])
- insert_by_date(rslt[i], &result);
+ commit_list_insert_by_date(rslt[i], &result);
}
free(rslt);
return result;
diff --git a/commit.h b/commit.h
index 9113bbe4889d71e824348edcb920110598db18d2..659c87c3ee90c23064f617e3bcf3ace12e32ea04 100644 (file)
--- a/commit.h
+++ b/commit.h
struct commit *lookup_commit_reference(const unsigned char *sha1);
struct commit *lookup_commit_reference_gently(const unsigned char *sha1,
int quiet);
+struct commit *lookup_commit_reference_by_name(const char *name);
-int parse_commit_buffer(struct commit *item, void *buffer, unsigned long size);
-
+int parse_commit_buffer(struct commit *item, const void *buffer, unsigned long size);
int parse_commit(struct commit *item);
/* Find beginning and length of commit subject. */
int find_commit_subject(const char *commit_buffer, const char **subject);
-struct commit_list * commit_list_insert(struct commit *item, struct commit_list **list_p);
+struct commit_list *commit_list_insert(struct commit *item,
+ struct commit_list **list);
unsigned commit_list_count(const struct commit_list *l);
-struct commit_list * insert_by_date(struct commit *item, struct commit_list **list);
+struct commit_list *commit_list_insert_by_date(struct commit *item,
+ struct commit_list **list);
+void commit_list_sort_by_date(struct commit_list **list);
void free_commit_list(struct commit_list *list);
-void sort_by_date(struct commit_list **list);
-
/* Commit formats */
enum cmit_fmt {
CMIT_FMT_RAW,
int need_8bit_cte;
int show_notes;
struct reflog_walk_info *reflog_info;
+ const char *output_encoding;
};
struct userformat_want {
extern int has_non_ascii(const char *text);
struct rev_info; /* in revision.h, it circularly uses enum cmit_fmt */
+extern char *logmsg_reencode(const struct commit *commit,
+ const char *output_encoding);
extern char *reencode_commit_message(const struct commit *commit,
const char **encoding_p);
extern void get_commit_format(const char *arg, struct rev_info *);
diff --git a/compat/inet_ntop.c b/compat/inet_ntop.c
index f44498258d4c2a0ebd1379ed818d9d04b56f0761..ea249c6ac6423fd4ef865c1a9d0149ac0ba0cc46 100644 (file)
--- a/compat/inet_ntop.c
+++ b/compat/inet_ntop.c
#include <errno.h>
#include <sys/types.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
+
+#include "../git-compat-util.h"
+
#include <stdio.h>
#include <string.h>
* Paul Vixie, 1996.
*/
static const char *
-inet_ntop4(src, dst, size)
- const u_char *src;
- char *dst;
- size_t size;
+inet_ntop4(const u_char *src, char *dst, size_t size)
{
static const char fmt[] = "%u.%u.%u.%u";
char tmp[sizeof "255.255.255.255"];
* Paul Vixie, 1996.
*/
static const char *
-inet_ntop6(src, dst, size)
- const u_char *src;
- char *dst;
- size_t size;
+inet_ntop6(const u_char *src, char *dst, size_t size)
{
/*
* Note that int32_t and int16_t need only be "at least" large enough
* Paul Vixie, 1996.
*/
const char *
-inet_ntop(af, src, dst, size)
- int af;
- const void *src;
- char *dst;
- size_t size;
+inet_ntop(int af, const void *src, char *dst, size_t size)
{
switch (af) {
case AF_INET:
diff --git a/compat/inet_pton.c b/compat/inet_pton.c
index 4078fc0877ca99c82152acdd6b7a9eef70a9f8a4..2ec995e63d0cdef6dcd8b2fd6ea7aa544f4d1b33 100644 (file)
--- a/compat/inet_pton.c
+++ b/compat/inet_pton.c
#include <errno.h>
#include <sys/types.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
+
+#include "../git-compat-util.h"
+
#include <stdio.h>
#include <string.h>
*/
static int inet_pton4(const char *src, unsigned char *dst);
+#ifndef NO_IPV6
static int inet_pton6(const char *src, unsigned char *dst);
+#endif
/* int
* inet_pton4(src, dst)
diff --git a/compat/mingw.c b/compat/mingw.c
index b98e6000062134f01a0611fb17a3fd43250f7989..878b1de97c6c437857e7a29e1b9afc677610f1fe 100644 (file)
--- a/compat/mingw.c
+++ b/compat/mingw.c
#include "win32.h"
#include <conio.h>
#include "../strbuf.h"
+#include "../run-command.h"
+
+static const int delay[] = { 0, 1, 10, 20, 40 };
int err_win_to_posix(DWORD winerr)
{
return error;
}
+static inline int is_file_in_use_error(DWORD errcode)
+{
+ switch (errcode) {
+ case ERROR_SHARING_VIOLATION:
+ case ERROR_ACCESS_DENIED:
+ return 1;
+ }
+
+ return 0;
+}
+
+static int read_yes_no_answer(void)
+{
+ char answer[1024];
+
+ if (fgets(answer, sizeof(answer), stdin)) {
+ size_t answer_len = strlen(answer);
+ int got_full_line = 0, c;
+
+ /* remove the newline */
+ if (answer_len >= 2 && answer[answer_len-2] == '\r') {
+ answer[answer_len-2] = '\0';
+ got_full_line = 1;
+ } else if (answer_len >= 1 && answer[answer_len-1] == '\n') {
+ answer[answer_len-1] = '\0';
+ got_full_line = 1;
+ }
+ /* flush the buffer in case we did not get the full line */
+ if (!got_full_line)
+ while ((c = getchar()) != EOF && c != '\n')
+ ;
+ } else
+ /* we could not read, return the
+ * default answer which is no */
+ return 0;
+
+ if (tolower(answer[0]) == 'y' && !answer[1])
+ return 1;
+ if (!strncasecmp(answer, "yes", sizeof(answer)))
+ return 1;
+ if (tolower(answer[0]) == 'n' && !answer[1])
+ return 0;
+ if (!strncasecmp(answer, "no", sizeof(answer)))
+ return 0;
+
+ /* did not find an answer we understand */
+ return -1;
+}
+
+static int ask_yes_no_if_possible(const char *format, ...)
+{
+ char question[4096];
+ const char *retry_hook[] = { NULL, NULL, NULL };
+ va_list args;
+
+ va_start(args, format);
+ vsnprintf(question, sizeof(question), format, args);
+ va_end(args);
+
+ if ((retry_hook[0] = getenv("GIT_ASK_YESNO"))) {
+ retry_hook[1] = question;
+ return !run_command_v_opt(retry_hook, 0);
+ }
+
+ if (!isatty(_fileno(stdin)) || !isatty(_fileno(stderr)))
+ return 0;
+
+ while (1) {
+ int answer;
+ fprintf(stderr, "%s (y/n) ", question);
+
+ if ((answer = read_yes_no_answer()) >= 0)
+ return answer;
+
+ fprintf(stderr, "Sorry, I did not understand your answer. "
+ "Please type 'y' or 'n'\n");
+ }
+}
+
+#undef unlink
+int mingw_unlink(const char *pathname)
+{
+ int ret, tries = 0;
+
+ /* read-only files cannot be removed */
+ chmod(pathname, 0666);
+ while ((ret = unlink(pathname)) == -1 && tries < ARRAY_SIZE(delay)) {
+ if (!is_file_in_use_error(GetLastError()))
+ break;
+ /*
+ * We assume that some other process had the source or
+ * destination file open at the wrong moment and retry.
+ * In order to give the other process a higher chance to
+ * complete its operation, we give up our time slice now.
+ * If we have to retry again, we do sleep a bit.
+ */
+ Sleep(delay[tries]);
+ tries++;
+ }
+ while (ret == -1 && is_file_in_use_error(GetLastError()) &&
+ ask_yes_no_if_possible("Unlink of file '%s' failed. "
+ "Should I try again?", pathname))
+ ret = unlink(pathname);
+ return ret;
+}
+
+static int is_dir_empty(const char *path)
+{
+ struct strbuf buf = STRBUF_INIT;
+ WIN32_FIND_DATAA findbuf;
+ HANDLE handle;
+
+ strbuf_addf(&buf, "%s\\*", path);
+ handle = FindFirstFileA(buf.buf, &findbuf);
+ if (handle == INVALID_HANDLE_VALUE) {
+ strbuf_release(&buf);
+ return GetLastError() == ERROR_NO_MORE_FILES;
+ }
+
+ while (!strcmp(findbuf.cFileName, ".") ||
+ !strcmp(findbuf.cFileName, ".."))
+ if (!FindNextFile(handle, &findbuf)) {
+ strbuf_release(&buf);
+ return GetLastError() == ERROR_NO_MORE_FILES;
+ }
+ FindClose(handle);
+ strbuf_release(&buf);
+ return 0;
+}
+
+#undef rmdir
+int mingw_rmdir(const char *pathname)
+{
+ int ret, tries = 0;
+
+ while ((ret = rmdir(pathname)) == -1 && tries < ARRAY_SIZE(delay)) {
+ if (!is_file_in_use_error(GetLastError()))
+ break;
+ if (!is_dir_empty(pathname)) {
+ errno = ENOTEMPTY;
+ break;
+ }
+ /*
+ * We assume that some other process had the source or
+ * destination file open at the wrong moment and retry.
+ * In order to give the other process a higher chance to
+ * complete its operation, we give up our time slice now.
+ * If we have to retry again, we do sleep a bit.
+ */
+ Sleep(delay[tries]);
+ tries++;
+ }
+ while (ret == -1 && is_file_in_use_error(GetLastError()) &&
+ ask_yes_no_if_possible("Deletion of directory '%s' failed. "
+ "Should I try again?", pathname))
+ ret = rmdir(pathname);
+ return ret;
+}
+
#undef open
int mingw_open (const char *filename, int oflags, ...)
{
mode = va_arg(args, int);
va_end(args);
- if (!strcmp(filename, "/dev/null"))
+ if (filename && !strcmp(filename, "/dev/null"))
filename = "nul";
fd = open(filename, oflags, mode);
#undef fopen
FILE *mingw_fopen (const char *filename, const char *otype)
{
- if (!strcmp(filename, "/dev/null"))
+ if (filename && !strcmp(filename, "/dev/null"))
filename = "nul";
return fopen(filename, otype);
}
/* We keep the do_lstat code in a separate function to avoid recursion.
* When a path ends with a slash, the stat will fail with ENOENT. In
* this case, we strip the trailing slashes and stat again.
+ *
+ * If follow is true then act like stat() and report on the link
+ * target. Otherwise report on the link itself.
*/
-static int do_lstat(const char *file_name, struct stat *buf)
+static int do_lstat(int follow, const char *file_name, struct stat *buf)
{
int err;
WIN32_FILE_ATTRIBUTE_DATA fdata;
buf->st_atime = filetime_to_time_t(&(fdata.ftLastAccessTime));
buf->st_mtime = filetime_to_time_t(&(fdata.ftLastWriteTime));
buf->st_ctime = filetime_to_time_t(&(fdata.ftCreationTime));
+ if (fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
+ WIN32_FIND_DATAA findbuf;
+ HANDLE handle = FindFirstFileA(file_name, &findbuf);
+ if (handle != INVALID_HANDLE_VALUE) {
+ if ((findbuf.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) &&
+ (findbuf.dwReserved0 == IO_REPARSE_TAG_SYMLINK)) {
+ if (follow) {
+ char buffer[MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
+ buf->st_size = readlink(file_name, buffer, MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
+ } else {
+ buf->st_mode = S_IFLNK;
+ }
+ buf->st_mode |= S_IREAD;
+ if (!(findbuf.dwFileAttributes & FILE_ATTRIBUTE_READONLY))
+ buf->st_mode |= S_IWRITE;
+ }
+ FindClose(handle);
+ }
+ }
return 0;
}
errno = err;
* complete. Note that Git stat()s are redirected to mingw_lstat()
* too, since Windows doesn't really handle symlinks that well.
*/
-int mingw_lstat(const char *file_name, struct stat *buf)
+static int do_stat_internal(int follow, const char *file_name, struct stat *buf)
{
int namelen;
static char alt_name[PATH_MAX];
- if (!do_lstat(file_name, buf))
+ if (!do_lstat(follow, file_name, buf))
return 0;
/* if file_name ended in a '/', Windows returned ENOENT;
memcpy(alt_name, file_name, namelen);
alt_name[namelen] = 0;
- return do_lstat(alt_name, buf);
+ return do_lstat(follow, alt_name, buf);
+}
+
+int mingw_lstat(const char *file_name, struct stat *buf)
+{
+ return do_stat_internal(0, file_name, buf);
+}
+int mingw_stat(const char *file_name, struct stat *buf)
+{
+ return do_stat_internal(1, file_name, buf);
}
#undef fstat
return 0;
}
-int poll(struct pollfd *ufds, unsigned int nfds, int timeout)
-{
- int i, pending;
-
- if (timeout >= 0) {
- if (nfds == 0) {
- Sleep(timeout);
- return 0;
- }
- return errno = EINVAL, error("poll timeout not supported");
- }
-
- /* When there is only one fd to wait for, then we pretend that
- * input is available and let the actual wait happen when the
- * caller invokes read().
- */
- if (nfds == 1) {
- if (!(ufds[0].events & POLLIN))
- return errno = EINVAL, error("POLLIN not set");
- ufds[0].revents = POLLIN;
- return 0;
- }
-
-repeat:
- pending = 0;
- for (i = 0; i < nfds; i++) {
- DWORD avail = 0;
- HANDLE h = (HANDLE) _get_osfhandle(ufds[i].fd);
- if (h == INVALID_HANDLE_VALUE)
- return -1; /* errno was set */
-
- if (!(ufds[i].events & POLLIN))
- return errno = EINVAL, error("POLLIN not set");
-
- /* this emulation works only for pipes */
- if (!PeekNamedPipe(h, NULL, 0, NULL, &avail, NULL)) {
- int err = GetLastError();
- if (err == ERROR_BROKEN_PIPE) {
- ufds[i].revents = POLLHUP;
- pending++;
- } else {
- errno = EINVAL;
- return error("PeekNamedPipe failed,"
- " GetLastError: %u", err);
- }
- } else if (avail) {
- ufds[i].revents = POLLIN;
- pending++;
- } else
- ufds[i].revents = 0;
- }
- if (!pending) {
- /* The only times that we spin here is when the process
- * that is connected through the pipes is waiting for
- * its own input data to become available. But since
- * the process (pack-objects) is itself CPU intensive,
- * it will happily pick up the time slice that we are
- * relinquishing here.
- */
- Sleep(0);
- goto repeat;
- }
- return 0;
-}
-
struct tm *gmtime_r(const time_t *timep, struct tm *result)
{
/* gmtime() in MSVCRT.DLL is thread-safe, but not reentrant */
return strcasecmp(*ea, *eb);
}
+struct pinfo_t {
+ struct pinfo_t *next;
+ pid_t pid;
+ HANDLE proc;
+} pinfo_t;
+struct pinfo_t *pinfo = NULL;
+CRITICAL_SECTION pinfo_cs;
+
static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **env,
const char *dir,
int prepend_cmd, int fhin, int fhout, int fherr)
return -1;
}
CloseHandle(pi.hThread);
- return (pid_t)pi.hProcess;
+
+ /*
+ * The process ID is the human-readable identifier of the process
+ * that we want to present in log and error messages. The handle
+ * is not useful for this purpose. But we cannot close it, either,
+ * because it is not possible to turn a process ID into a process
+ * handle after the process terminated.
+ * Keep the handle in a list for waitpid.
+ */
+ EnterCriticalSection(&pinfo_cs);
+ {
+ struct pinfo_t *info = xmalloc(sizeof(struct pinfo_t));
+ info->pid = pi.dwProcessId;
+ info->proc = pi.hProcess;
+ info->next = pinfo;
+ pinfo = info;
+ }
+ LeaveCriticalSection(&pinfo_cs);
+
+ return (pid_t)pi.dwProcessId;
}
static pid_t mingw_spawnve(const char *cmd, const char **argv, char **env,
free_path_split(path);
}
+void mingw_execv(const char *cmd, char *const *argv)
+{
+ mingw_execve(cmd, argv, environ);
+}
+
+int mingw_kill(pid_t pid, int sig)
+{
+ if (pid > 0 && sig == SIGTERM) {
+ HANDLE h = OpenProcess(PROCESS_TERMINATE, FALSE, pid);
+
+ if (TerminateProcess(h, -1)) {
+ CloseHandle(h);
+ return 0;
+ }
+
+ errno = err_win_to_posix(GetLastError());
+ CloseHandle(h);
+ return -1;
+ }
+
+ errno = EINVAL;
+ return -1;
+}
+
static char **copy_environ(void)
{
char **env;
const struct addrinfo *hints,
struct addrinfo **res)
{
- struct hostent *h = gethostbyname(node);
+ struct hostent *h = NULL;
struct addrinfo *ai;
struct sockaddr_in *sin;
- if (!h)
- return WSAGetLastError();
+ if (node) {
+ h = gethostbyname(node);
+ if (!h)
+ return WSAGetLastError();
+ }
ai = xmalloc(sizeof(struct addrinfo));
*res = ai;
ai->ai_flags = 0;
ai->ai_family = AF_INET;
- ai->ai_socktype = hints->ai_socktype;
- switch (hints->ai_socktype) {
+ ai->ai_socktype = hints ? hints->ai_socktype : 0;
+ switch (ai->ai_socktype) {
case SOCK_STREAM:
ai->ai_protocol = IPPROTO_TCP;
break;
break;
}
ai->ai_addrlen = sizeof(struct sockaddr_in);
- ai->ai_canonname = strdup(h->h_name);
+ if (hints && (hints->ai_flags & AI_CANONNAME))
+ ai->ai_canonname = h ? strdup(h->h_name) : NULL;
+ else
+ ai->ai_canonname = NULL;
sin = xmalloc(ai->ai_addrlen);
memset(sin, 0, ai->ai_addrlen);
sin->sin_family = AF_INET;
+ /* Note: getaddrinfo is supposed to allow service to be a string,
+ * which should be looked up using getservbyname. This is
+ * currently not implemented */
if (service)
sin->sin_port = htons(atoi(service));
- sin->sin_addr = *(struct in_addr *)h->h_addr;
+ if (h)
+ sin->sin_addr = *(struct in_addr *)h->h_addr;
+ else if (hints && (hints->ai_flags & AI_PASSIVE))
+ sin->sin_addr.s_addr = INADDR_ANY;
+ else
+ sin->sin_addr.s_addr = INADDR_LOOPBACK;
ai->ai_addr = (struct sockaddr *)sin;
ai->ai_next = 0;
return 0;
int mingw_socket(int domain, int type, int protocol)
{
int sockfd;
- SOCKET s = WSASocket(domain, type, protocol, NULL, 0, 0);
+ SOCKET s;
+
+ ensure_socket_initialization();
+ s = WSASocket(domain, type, protocol, NULL, 0, 0);
if (s == INVALID_SOCKET) {
/*
* WSAGetLastError() values are regular BSD error codes
return connect(s, sa, sz);
}
+#undef bind
+int mingw_bind(int sockfd, struct sockaddr *sa, size_t sz)
+{
+ SOCKET s = (SOCKET)_get_osfhandle(sockfd);
+ return bind(s, sa, sz);
+}
+
+#undef setsockopt
+int mingw_setsockopt(int sockfd, int lvl, int optname, void *optval, int optlen)
+{
+ SOCKET s = (SOCKET)_get_osfhandle(sockfd);
+ return setsockopt(s, lvl, optname, (const char*)optval, optlen);
+}
+
+#undef listen
+int mingw_listen(int sockfd, int backlog)
+{
+ SOCKET s = (SOCKET)_get_osfhandle(sockfd);
+ return listen(s, backlog);
+}
+
+#undef accept
+int mingw_accept(int sockfd1, struct sockaddr *sa, socklen_t *sz)
+{
+ int sockfd2;
+
+ SOCKET s1 = (SOCKET)_get_osfhandle(sockfd1);
+ SOCKET s2 = accept(s1, sa, sz);
+
+ /* convert into a file descriptor */
+ if ((sockfd2 = _open_osfhandle(s2, O_RDWR|O_BINARY)) < 0) {
+ int err = errno;
+ closesocket(s2);
+ return error("unable to make a socket file descriptor: %s",
+ strerror(err));
+ }
+ return sockfd2;
+}
+
#undef rename
int mingw_rename(const char *pold, const char *pnew)
{
DWORD attrs, gle;
int tries = 0;
- static const int delay[] = { 0, 1, 10, 20, 40 };
/*
* Try native rename() first to get errno right.
tries++;
goto repeat;
}
+ if (gle == ERROR_ACCESS_DENIED &&
+ ask_yes_no_if_possible("Rename from '%s' to '%s' failed. "
+ "Should I try again?", pold, pnew))
+ goto repeat;
+
errno = EACCES;
return -1;
}
const char *, const char *, const char *, INT);
T ShellExecute;
HMODULE shell32;
+ int r;
shell32 = LoadLibrary("shell32.dll");
if (!shell32)
die("cannot run browser");
printf("Launching default browser to display HTML ...\n");
- ShellExecute(NULL, "open", htmlpath, NULL, "\\", 0);
-
+ r = (int)ShellExecute(NULL, "open", htmlpath, NULL, "\\", SW_SHOWNORMAL);
FreeLibrary(shell32);
+ /* see the MSDN documentation referring to the result codes here */
+ if (r <= 32) {
+ die("failed to launch browser for %.*s", MAX_PATH, unixpath);
+ }
}
int link(const char *oldpath, const char *newpath)
return strbuf_detach(&buf, NULL);
}
-#ifndef NO_MINGW_REPLACE_READDIR
-/* MinGW readdir implementation to avoid extra lstats for Git */
-struct mingw_DIR
-{
- struct _finddata_t dd_dta; /* disk transfer area for this dir */
- struct mingw_dirent dd_dir; /* Our own implementation, including d_type */
- long dd_handle; /* _findnext handle */
- int dd_stat; /* 0 = next entry to read is first entry, -1 = off the end, positive = 0 based index of next entry */
- char dd_name[1]; /* given path for dir with search pattern (struct is extended) */
-};
-
-struct dirent *mingw_readdir(DIR *dir)
+pid_t waitpid(pid_t pid, int *status, unsigned options)
{
- WIN32_FIND_DATAA buf;
- HANDLE handle;
- struct mingw_DIR *mdir = (struct mingw_DIR*)dir;
-
- if (!dir->dd_handle) {
- errno = EBADF; /* No set_errno for mingw */
- return NULL;
+ HANDLE h = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION,
+ FALSE, pid);
+ if (!h) {
+ errno = ECHILD;
+ return -1;
}
- if (dir->dd_handle == (long)INVALID_HANDLE_VALUE && dir->dd_stat == 0)
- {
- DWORD lasterr;
- handle = FindFirstFileA(dir->dd_name, &buf);
- lasterr = GetLastError();
- dir->dd_handle = (long)handle;
- if (handle == INVALID_HANDLE_VALUE && (lasterr != ERROR_NO_MORE_FILES)) {
- errno = err_win_to_posix(lasterr);
- return NULL;
+ if (pid > 0 && options & WNOHANG) {
+ if (WAIT_OBJECT_0 != WaitForSingleObject(h, 0)) {
+ CloseHandle(h);
+ return 0;
}
- } else if (dir->dd_handle == (long)INVALID_HANDLE_VALUE) {
- return NULL;
- } else if (!FindNextFileA((HANDLE)dir->dd_handle, &buf)) {
- DWORD lasterr = GetLastError();
- FindClose((HANDLE)dir->dd_handle);
- dir->dd_handle = (long)INVALID_HANDLE_VALUE;
- /* POSIX says you shouldn't set errno when readdir can't
- find any more files; so, if another error we leave it set. */
- if (lasterr != ERROR_NO_MORE_FILES)
- errno = err_win_to_posix(lasterr);
- return NULL;
+ options &= ~WNOHANG;
}
- /* We get here if `buf' contains valid data. */
- strcpy(dir->dd_dir.d_name, buf.cFileName);
- ++dir->dd_stat;
+ if (options == 0) {
+ struct pinfo_t **ppinfo;
+ if (WaitForSingleObject(h, INFINITE) != WAIT_OBJECT_0) {
+ CloseHandle(h);
+ return 0;
+ }
- /* Set file type, based on WIN32_FIND_DATA */
- mdir->dd_dir.d_type = 0;
- if (buf.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
- mdir->dd_dir.d_type |= DT_DIR;
- else
- mdir->dd_dir.d_type |= DT_REG;
+ if (status)
+ GetExitCodeProcess(h, (LPDWORD)status);
- return (struct dirent*)&dir->dd_dir;
+ EnterCriticalSection(&pinfo_cs);
+
+ ppinfo = &pinfo;
+ while (*ppinfo) {
+ struct pinfo_t *info = *ppinfo;
+ if (info->pid == pid) {
+ CloseHandle(info->proc);
+ *ppinfo = info->next;
+ free(info);
+ break;
+ }
+ ppinfo = &info->next;
+ }
+
+ LeaveCriticalSection(&pinfo_cs);
+
+ CloseHandle(h);
+ return pid;
+ }
+ CloseHandle(h);
+
+ errno = EINVAL;
+ return -1;
}
-#endif // !NO_MINGW_REPLACE_READDIR
diff --git a/compat/mingw.h b/compat/mingw.h
index 3b2477be5f658be665f19a12b48cc47fa07d1c6b..fe6ba340437b67237afd8cfdb4cf2a20074d27f7 100644 (file)
--- a/compat/mingw.h
+++ b/compat/mingw.h
*/
typedef int pid_t;
+typedef int uid_t;
+typedef int socklen_t;
#define hstrerror strerror
#define S_IFLNK 0120000 /* Symbolic link */
#define S_ISLNK(x) (((x) & S_IFMT) == S_IFLNK)
#define S_ISSOCK(x) 0
+
#define S_IRGRP 0
#define S_IWGRP 0
#define S_IXGRP 0
-#define S_ISGID 0
+#define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
#define S_IROTH 0
+#define S_IWOTH 0
#define S_IXOTH 0
+#define S_IRWXO (S_IROTH | S_IWOTH | S_IXOTH)
+#define S_ISUID 0
+#define S_ISGID 0
+#define S_ISVTX 0
#define WIFEXITED(x) 1
#define WIFSIGNALED(x) 0
#define WEXITSTATUS(x) ((x) & 0xff)
#define WTERMSIG(x) SIGTERM
+#define EWOULDBLOCK EAGAIN
+#define SHUT_WR SD_SEND
+
#define SIGHUP 1
#define SIGQUIT 3
#define SIGKILL 9
#define F_SETFD 2
#define FD_CLOEXEC 0x1
+#define EAFNOSUPPORT WSAEAFNOSUPPORT
+#define ECONNABORTED WSAECONNABORTED
+
struct passwd {
char *pw_name;
char *pw_gecos;
extern char *getpass(const char *prompt);
-#ifndef POLLIN
-struct pollfd {
- int fd; /* file descriptor */
- short events; /* requested events */
- short revents; /* returned events */
-};
-#define POLLIN 1
-#define POLLHUP 2
-#endif
-
typedef void (__cdecl *sig_handler_t)(int);
struct sigaction {
sig_handler_t sa_handler;
};
#define ITIMER_REAL 0
+/*
+ * sanitize preprocessor namespace polluted by Windows headers defining
+ * macros which collide with git local versions
+ */
+#undef HELP_COMMAND /* from winuser.h */
+
/*
* trivial stubs
*/
{ errno = ENOSYS; return -1; }
static inline int fchmod(int fildes, mode_t mode)
{ errno = ENOSYS; return -1; }
-static inline int fork(void)
+static inline pid_t fork(void)
{ errno = ENOSYS; return -1; }
static inline unsigned int alarm(unsigned int seconds)
{ return 0; }
static inline int fsync(int fd)
{ return _commit(fd); }
-static inline int getppid(void)
+static inline pid_t getppid(void)
{ return 1; }
static inline void sync(void)
{}
-static inline int getuid()
+static inline uid_t getuid(void)
{ return 1; }
static inline struct passwd *getpwnam(const char *name)
{ return NULL; }
}
#define mkdir mingw_mkdir
-static inline int mingw_unlink(const char *pathname)
-{
- /* read-only files cannot be removed */
- chmod(pathname, 0666);
- return unlink(pathname);
-}
-#define unlink mingw_unlink
+#define WNOHANG 1
+pid_t waitpid(pid_t pid, int *status, unsigned options);
-static inline int waitpid(pid_t pid, int *status, unsigned options)
-{
- if (options == 0)
- return _cwait(status, pid, 0);
- errno = EINVAL;
- return -1;
-}
+#define kill mingw_kill
+int mingw_kill(pid_t pid, int sig);
#ifndef NO_OPENSSL
#include <openssl/ssl.h>
unsigned int sleep (unsigned int seconds);
int mkstemp(char *template);
int gettimeofday(struct timeval *tv, void *tz);
-int poll(struct pollfd *ufds, unsigned int nfds, int timeout);
struct tm *gmtime_r(const time_t *timep, struct tm *result);
struct tm *localtime_r(const time_t *timep, struct tm *result);
int getpagesize(void); /* defined in MinGW's libgcc.a */
-struct passwd *getpwuid(int uid);
+struct passwd *getpwuid(uid_t uid);
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);
* replacements of existing functions
*/
+int mingw_unlink(const char *pathname);
+#define unlink mingw_unlink
+
+int mingw_rmdir(const char *path);
+#define rmdir mingw_rmdir
+
int mingw_open (const char *filename, int oflags, ...);
#define open mingw_open
int mingw_connect(int sockfd, struct sockaddr *sa, size_t sz);
#define connect mingw_connect
+int mingw_bind(int sockfd, struct sockaddr *sa, size_t sz);
+#define bind mingw_bind
+
+int mingw_setsockopt(int sockfd, int lvl, int optname, void *optval, int optlen);
+#define setsockopt mingw_setsockopt
+
+int mingw_listen(int sockfd, int backlog);
+#define listen mingw_listen
+
+int mingw_accept(int sockfd, struct sockaddr *sa, socklen_t *sz);
+#define accept mingw_accept
+
int mingw_rename(const char*, const char*);
#define rename mingw_rename
#ifndef ALREADY_DECLARED_STAT_FUNCS
#define stat _stati64
int mingw_lstat(const char *file_name, struct stat *buf);
+int mingw_stat(const char *file_name, struct stat *buf);
int mingw_fstat(int fd, struct stat *buf);
#define fstat mingw_fstat
#define lstat mingw_lstat
-#define _stati64(x,y) mingw_lstat(x,y)
+#define _stati64(x,y) mingw_stat(x,y)
#endif
int mingw_utime(const char *file_name, const struct utimbuf *times);
int fhin, int fhout, int fherr);
void mingw_execvp(const char *cmd, char *const *argv);
#define execvp mingw_execvp
+void mingw_execv(const char *cmd, char *const *argv);
+#define execv mingw_execv
static inline unsigned int git_ntohl(unsigned int x)
{ return (unsigned int)ntohl(x); }
static int mingw_main(); \
int main(int argc, const char **argv) \
{ \
+ extern CRITICAL_SECTION pinfo_cs; \
_fmode = _O_BINARY; \
_setmode(_fileno(stdin), _O_BINARY); \
_setmode(_fileno(stdout), _O_BINARY); \
_setmode(_fileno(stderr), _O_BINARY); \
argv[0] = xstrdup(_pgmptr); \
+ InitializeCriticalSection(&pinfo_cs); \
return mingw_main(argc, argv); \
} \
static int mingw_main(c,v)
-#ifndef NO_MINGW_REPLACE_READDIR
-/*
- * A replacement of readdir, to ensure that it reads the file type at
- * the same time. This avoid extra unneeded lstats in git on MinGW
- */
-#undef DT_UNKNOWN
-#undef DT_DIR
-#undef DT_REG
-#undef DT_LNK
-#define DT_UNKNOWN 0
-#define DT_DIR 1
-#define DT_REG 2
-#define DT_LNK 3
-
-struct mingw_dirent
-{
- long d_ino; /* Always zero. */
- union {
- unsigned short d_reclen; /* Always zero. */
- unsigned char d_type; /* Reimplementation adds this */
- };
- unsigned short d_namlen; /* Length of name in d_name. */
- char d_name[FILENAME_MAX]; /* File name. */
-};
-#define dirent mingw_dirent
-#define readdir(x) mingw_readdir(x)
-struct dirent *mingw_readdir(DIR *dir);
-#endif // !NO_MINGW_REPLACE_READDIR
-
/*
* Used by Pthread API implementation for Windows
*/
diff --git a/compat/msvc.c b/compat/msvc.c
index ac04a4ccbdd8319349aa2647374d78fd6639f425..71843d7eefafcea2ddf71b9fbff519790f661481 100644 (file)
--- a/compat/msvc.c
+++ b/compat/msvc.c
#include <conio.h>
#include "../strbuf.h"
-DIR *opendir(const char *name)
-{
- int len;
- DIR *p;
- p = (DIR*)malloc(sizeof(DIR));
- memset(p, 0, sizeof(DIR));
- strncpy(p->dd_name, name, PATH_MAX);
- len = strlen(p->dd_name);
- p->dd_name[len] = '/';
- p->dd_name[len+1] = '*';
-
- if (p == NULL)
- return NULL;
-
- p->dd_handle = _findfirst(p->dd_name, &p->dd_dta);
-
- if (p->dd_handle == -1) {
- free(p);
- return NULL;
- }
- return p;
-}
-int closedir(DIR *dir)
-{
- _findclose(dir->dd_handle);
- free(dir);
- return 0;
-}
-
#include "mingw.c"
diff --git a/compat/vcbuild/include/dirent.h b/compat/vcbuild/include/dirent.h
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * DIRENT.H (formerly DIRLIB.H)
- * This file has no copyright assigned and is placed in the Public Domain.
- * This file is a part of the mingw-runtime package.
- *
- * The mingw-runtime package and its code is distributed in the hope that it
- * will be useful but WITHOUT ANY WARRANTY. ALL WARRANTIES, EXPRESSED OR
- * IMPLIED ARE HEREBY DISCLAIMED. This includes but is not limited to
- * warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- *
- * You are free to use this package and its code without limitation.
- */
-#ifndef _DIRENT_H_
-#define _DIRENT_H_
-#include <io.h>
-
-#define PATH_MAX 512
-
-#define __MINGW_NOTHROW
-
-#ifndef RC_INVOKED
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-struct dirent
-{
- long d_ino; /* Always zero. */
- unsigned short d_reclen; /* Always zero. */
- unsigned short d_namlen; /* Length of name in d_name. */
- char d_name[FILENAME_MAX]; /* File name. */
-};
-
-/*
- * This is an internal data structure. Good programmers will not use it
- * except as an argument to one of the functions below.
- * dd_stat field is now int (was short in older versions).
- */
-typedef struct
-{
- /* disk transfer area for this dir */
- struct _finddata_t dd_dta;
-
- /* dirent struct to return from dir (NOTE: this makes this thread
- * safe as long as only one thread uses a particular DIR struct at
- * a time) */
- struct dirent dd_dir;
-
- /* _findnext handle */
- long dd_handle;
-
- /*
- * Status of search:
- * 0 = not started yet (next entry to read is first entry)
- * -1 = off the end
- * positive = 0 based index of next entry
- */
- int dd_stat;
-
- /* given path for dir with search pattern (struct is extended) */
- char dd_name[PATH_MAX+3];
-} DIR;
-
-DIR* __cdecl __MINGW_NOTHROW opendir (const char*);
-struct dirent* __cdecl __MINGW_NOTHROW readdir (DIR*);
-int __cdecl __MINGW_NOTHROW closedir (DIR*);
-void __cdecl __MINGW_NOTHROW rewinddir (DIR*);
-long __cdecl __MINGW_NOTHROW telldir (DIR*);
-void __cdecl __MINGW_NOTHROW seekdir (DIR*, long);
-
-
-/* wide char versions */
-
-struct _wdirent
-{
- long d_ino; /* Always zero. */
- unsigned short d_reclen; /* Always zero. */
- unsigned short d_namlen; /* Length of name in d_name. */
- wchar_t d_name[FILENAME_MAX]; /* File name. */
-};
-
-/*
- * This is an internal data structure. Good programmers will not use it
- * except as an argument to one of the functions below.
- */
-typedef struct
-{
- /* disk transfer area for this dir */
- //struct _wfinddata_t dd_dta;
-
- /* dirent struct to return from dir (NOTE: this makes this thread
- * safe as long as only one thread uses a particular DIR struct at
- * a time) */
- struct _wdirent dd_dir;
-
- /* _findnext handle */
- long dd_handle;
-
- /*
- * Status of search:
- * 0 = not started yet (next entry to read is first entry)
- * -1 = off the end
- * positive = 0 based index of next entry
- */
- int dd_stat;
-
- /* given path for dir with search pattern (struct is extended) */
- wchar_t dd_name[1];
-} _WDIR;
-
-
-
-_WDIR* __cdecl __MINGW_NOTHROW _wopendir (const wchar_t*);
-struct _wdirent* __cdecl __MINGW_NOTHROW _wreaddir (_WDIR*);
-int __cdecl __MINGW_NOTHROW _wclosedir (_WDIR*);
-void __cdecl __MINGW_NOTHROW _wrewinddir (_WDIR*);
-long __cdecl __MINGW_NOTHROW _wtelldir (_WDIR*);
-void __cdecl __MINGW_NOTHROW _wseekdir (_WDIR*, long);
-
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif /* Not RC_INVOKED */
-
-#endif /* Not _DIRENT_H_ */
index 2a4f276869c507c49c3f1d066fa648c6ea78890d..b14fcf94da405c6f3267437e59936d598168e00f 100644 (file)
typedef int64_t off64_t;
+#define INTMAX_MIN _I64_MIN
+#define INTMAX_MAX _I64_MAX
+#define UINTMAX_MAX _UI64_MAX
+
#define STDOUT_FILENO 1
#define STDERR_FILENO 2
diff --git a/compat/win32/dirent.c b/compat/win32/dirent.c
--- /dev/null
+++ b/compat/win32/dirent.c
@@ -0,0 +1,108 @@
+#include "../git-compat-util.h"
+#include "dirent.h"
+
+struct DIR {
+ struct dirent dd_dir; /* includes d_type */
+ HANDLE dd_handle; /* FindFirstFile handle */
+ int dd_stat; /* 0-based index */
+ char dd_name[1]; /* extend struct */
+};
+
+DIR *opendir(const char *name)
+{
+ DWORD attrs = GetFileAttributesA(name);
+ int len;
+ DIR *p;
+
+ /* check for valid path */
+ if (attrs == INVALID_FILE_ATTRIBUTES) {
+ errno = ENOENT;
+ return NULL;
+ }
+
+ /* check if it's a directory */
+ if (!(attrs & FILE_ATTRIBUTE_DIRECTORY)) {
+ errno = ENOTDIR;
+ return NULL;
+ }
+
+ /* check that the pattern won't be too long for FindFirstFileA */
+ len = strlen(name);
+ if (is_dir_sep(name[len - 1]))
+ len--;
+ if (len + 2 >= MAX_PATH) {
+ errno = ENAMETOOLONG;
+ return NULL;
+ }
+
+ p = malloc(sizeof(DIR) + len + 2);
+ if (!p)
+ return NULL;
+
+ memset(p, 0, sizeof(DIR) + len + 2);
+ strcpy(p->dd_name, name);
+ p->dd_name[len] = '/';
+ p->dd_name[len+1] = '*';
+
+ p->dd_handle = INVALID_HANDLE_VALUE;
+ return p;
+}
+
+struct dirent *readdir(DIR *dir)
+{
+ WIN32_FIND_DATAA buf;
+ HANDLE handle;
+
+ if (!dir || !dir->dd_handle) {
+ errno = EBADF; /* No set_errno for mingw */
+ return NULL;
+ }
+
+ if (dir->dd_handle == INVALID_HANDLE_VALUE && dir->dd_stat == 0) {
+ DWORD lasterr;
+ handle = FindFirstFileA(dir->dd_name, &buf);
+ lasterr = GetLastError();
+ dir->dd_handle = handle;
+ if (handle == INVALID_HANDLE_VALUE && (lasterr != ERROR_NO_MORE_FILES)) {
+ errno = err_win_to_posix(lasterr);
+ return NULL;
+ }
+ } else if (dir->dd_handle == INVALID_HANDLE_VALUE) {
+ return NULL;
+ } else if (!FindNextFileA(dir->dd_handle, &buf)) {
+ DWORD lasterr = GetLastError();
+ FindClose(dir->dd_handle);
+ dir->dd_handle = INVALID_HANDLE_VALUE;
+ /* POSIX says you shouldn't set errno when readdir can't
+ find any more files; so, if another error we leave it set. */
+ if (lasterr != ERROR_NO_MORE_FILES)
+ errno = err_win_to_posix(lasterr);
+ return NULL;
+ }
+
+ /* We get here if `buf' contains valid data. */
+ strcpy(dir->dd_dir.d_name, buf.cFileName);
+ ++dir->dd_stat;
+
+ /* Set file type, based on WIN32_FIND_DATA */
+ dir->dd_dir.d_type = 0;
+ if (buf.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+ dir->dd_dir.d_type |= DT_DIR;
+ else
+ dir->dd_dir.d_type |= DT_REG;
+
+ return &dir->dd_dir;
+}
+
+int closedir(DIR *dir)
+{
+ if (!dir) {
+ errno = EBADF;
+ return -1;
+ }
+
+ if (dir->dd_handle != INVALID_HANDLE_VALUE)
+ FindClose(dir->dd_handle);
+ free(dir);
+ return 0;
+}
diff --git a/compat/win32/dirent.h b/compat/win32/dirent.h
--- /dev/null
+++ b/compat/win32/dirent.h
@@ -0,0 +1,24 @@
+#ifndef DIRENT_H
+#define DIRENT_H
+
+typedef struct DIR DIR;
+
+#define DT_UNKNOWN 0
+#define DT_DIR 1
+#define DT_REG 2
+#define DT_LNK 3
+
+struct dirent {
+ long d_ino; /* Always zero. */
+ char d_name[FILENAME_MAX]; /* File name. */
+ union {
+ unsigned short d_reclen; /* Always zero. */
+ unsigned char d_type; /* Reimplementation adds this */
+ };
+};
+
+DIR *opendir(const char *dirname);
+struct dirent *readdir(DIR *dir);
+int closedir(DIR *dir);
+
+#endif /* DIRENT_H */
diff --git a/compat/win32/sys/poll.c b/compat/win32/sys/poll.c
--- /dev/null
+++ b/compat/win32/sys/poll.c
@@ -0,0 +1,599 @@
+/* Emulation for poll(2)
+ Contributed by Paolo Bonzini.
+
+ Copyright 2001-2003, 2006-2010 Free Software Foundation, Inc.
+
+ This file is part of gnulib.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
+
+/* Tell gcc not to warn about the (nfd < 0) tests, below. */
+#if (__GNUC__ == 4 && 3 <= __GNUC_MINOR__) || 4 < __GNUC__
+# pragma GCC diagnostic ignored "-Wtype-limits"
+#endif
+
+#include <malloc.h>
+
+#include <sys/types.h>
+#include "poll.h"
+#include <errno.h>
+#include <limits.h>
+#include <assert.h>
+
+#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
+# define WIN32_NATIVE
+# if defined (_MSC_VER)
+# define _WIN32_WINNT 0x0502
+# endif
+# include <winsock2.h>
+# include <windows.h>
+# include <io.h>
+# include <stdio.h>
+# include <conio.h>
+#else
+# include <sys/time.h>
+# include <sys/socket.h>
+# include <sys/select.h>
+# include <unistd.h>
+#endif
+
+#ifdef HAVE_SYS_IOCTL_H
+# include <sys/ioctl.h>
+#endif
+#ifdef HAVE_SYS_FILIO_H
+# include <sys/filio.h>
+#endif
+
+#include <time.h>
+
+#ifndef INFTIM
+# define INFTIM (-1)
+#endif
+
+/* BeOS does not have MSG_PEEK. */
+#ifndef MSG_PEEK
+# define MSG_PEEK 0
+#endif
+
+#ifdef WIN32_NATIVE
+
+#define IsConsoleHandle(h) (((long) (h) & 3) == 3)
+
+static BOOL
+IsSocketHandle (HANDLE h)
+{
+ WSANETWORKEVENTS ev;
+
+ if (IsConsoleHandle (h))
+ return FALSE;
+
+ /* Under Wine, it seems that getsockopt returns 0 for pipes too.
+ WSAEnumNetworkEvents instead distinguishes the two correctly. */
+ ev.lNetworkEvents = 0xDEADBEEF;
+ WSAEnumNetworkEvents ((SOCKET) h, NULL, &ev);
+ return ev.lNetworkEvents != 0xDEADBEEF;
+}
+
+/* Declare data structures for ntdll functions. */
+typedef struct _FILE_PIPE_LOCAL_INFORMATION {
+ ULONG NamedPipeType;
+ ULONG NamedPipeConfiguration;
+ ULONG MaximumInstances;
+ ULONG CurrentInstances;
+ ULONG InboundQuota;
+ ULONG ReadDataAvailable;
+ ULONG OutboundQuota;
+ ULONG WriteQuotaAvailable;
+ ULONG NamedPipeState;
+ ULONG NamedPipeEnd;
+} FILE_PIPE_LOCAL_INFORMATION, *PFILE_PIPE_LOCAL_INFORMATION;
+
+typedef struct _IO_STATUS_BLOCK
+{
+ union {
+ DWORD Status;
+ PVOID Pointer;
+ } u;
+ ULONG_PTR Information;
+} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;
+
+typedef enum _FILE_INFORMATION_CLASS {
+ FilePipeLocalInformation = 24
+} FILE_INFORMATION_CLASS, *PFILE_INFORMATION_CLASS;
+
+typedef DWORD (WINAPI *PNtQueryInformationFile)
+ (HANDLE, IO_STATUS_BLOCK *, VOID *, ULONG, FILE_INFORMATION_CLASS);
+
+# ifndef PIPE_BUF
+# define PIPE_BUF 512
+# endif
+
+/* Compute revents values for file handle H. If some events cannot happen
+ for the handle, eliminate them from *P_SOUGHT. */
+
+static int
+win32_compute_revents (HANDLE h, int *p_sought)
+{
+ int i, ret, happened;
+ INPUT_RECORD *irbuffer;
+ DWORD avail, nbuffer;
+ BOOL bRet;
+ IO_STATUS_BLOCK iosb;
+ FILE_PIPE_LOCAL_INFORMATION fpli;
+ static PNtQueryInformationFile NtQueryInformationFile;
+ static BOOL once_only;
+
+ switch (GetFileType (h))
+ {
+ case FILE_TYPE_PIPE:
+ if (!once_only)
+ {
+ NtQueryInformationFile = (PNtQueryInformationFile)
+ GetProcAddress (GetModuleHandle ("ntdll.dll"),
+ "NtQueryInformationFile");
+ once_only = TRUE;
+ }
+
+ happened = 0;
+ if (PeekNamedPipe (h, NULL, 0, NULL, &avail, NULL) != 0)
+ {
+ if (avail)
+ happened |= *p_sought & (POLLIN | POLLRDNORM);
+ }
+ else if (GetLastError () == ERROR_BROKEN_PIPE)
+ happened |= POLLHUP;
+
+ else
+ {
+ /* It was the write-end of the pipe. Check if it is writable.
+ If NtQueryInformationFile fails, optimistically assume the pipe is
+ writable. This could happen on Win9x, where NtQueryInformationFile
+ is not available, or if we inherit a pipe that doesn't permit
+ FILE_READ_ATTRIBUTES access on the write end (I think this should
+ not happen since WinXP SP2; WINE seems fine too). Otherwise,
+ ensure that enough space is available for atomic writes. */
+ memset (&iosb, 0, sizeof (iosb));
+ memset (&fpli, 0, sizeof (fpli));
+
+ if (!NtQueryInformationFile
+ || NtQueryInformationFile (h, &iosb, &fpli, sizeof (fpli),
+ FilePipeLocalInformation)
+ || fpli.WriteQuotaAvailable >= PIPE_BUF
+ || (fpli.OutboundQuota < PIPE_BUF &&
+ fpli.WriteQuotaAvailable == fpli.OutboundQuota))
+ happened |= *p_sought & (POLLOUT | POLLWRNORM | POLLWRBAND);
+ }
+ return happened;
+
+ case FILE_TYPE_CHAR:
+ ret = WaitForSingleObject (h, 0);
+ if (!IsConsoleHandle (h))
+ return ret == WAIT_OBJECT_0 ? *p_sought & ~(POLLPRI | POLLRDBAND) : 0;
+
+ nbuffer = avail = 0;
+ bRet = GetNumberOfConsoleInputEvents (h, &nbuffer);
+ if (bRet)
+ {
+ /* Input buffer. */
+ *p_sought &= POLLIN | POLLRDNORM;
+ if (nbuffer == 0)
+ return POLLHUP;
+ if (!*p_sought)
+ return 0;
+
+ irbuffer = (INPUT_RECORD *) alloca (nbuffer * sizeof (INPUT_RECORD));
+ bRet = PeekConsoleInput (h, irbuffer, nbuffer, &avail);
+ if (!bRet || avail == 0)
+ return POLLHUP;
+
+ for (i = 0; i < avail; i++)
+ if (irbuffer[i].EventType == KEY_EVENT)
+ return *p_sought;
+ return 0;
+ }
+ else
+ {
+ /* Screen buffer. */
+ *p_sought &= POLLOUT | POLLWRNORM | POLLWRBAND;
+ return *p_sought;
+ }
+
+ default:
+ ret = WaitForSingleObject (h, 0);
+ if (ret == WAIT_OBJECT_0)
+ return *p_sought & ~(POLLPRI | POLLRDBAND);
+
+ return *p_sought & (POLLOUT | POLLWRNORM | POLLWRBAND);
+ }
+}
+
+/* Convert fd_sets returned by select into revents values. */
+
+static int
+win32_compute_revents_socket (SOCKET h, int sought, long lNetworkEvents)
+{
+ int happened = 0;
+
+ if ((lNetworkEvents & (FD_READ | FD_ACCEPT | FD_CLOSE)) == FD_ACCEPT)
+ happened |= (POLLIN | POLLRDNORM) & sought;
+
+ else if (lNetworkEvents & (FD_READ | FD_ACCEPT | FD_CLOSE))
+ {
+ int r, error;
+
+ char data[64];
+ WSASetLastError (0);
+ r = recv (h, data, sizeof (data), MSG_PEEK);
+ error = WSAGetLastError ();
+ WSASetLastError (0);
+
+ if (r > 0 || error == WSAENOTCONN)
+ happened |= (POLLIN | POLLRDNORM) & sought;
+
+ /* Distinguish hung-up sockets from other errors. */
+ else if (r == 0 || error == WSAESHUTDOWN || error == WSAECONNRESET
+ || error == WSAECONNABORTED || error == WSAENETRESET)
+ happened |= POLLHUP;
+
+ else
+ happened |= POLLERR;
+ }
+
+ if (lNetworkEvents & (FD_WRITE | FD_CONNECT))
+ happened |= (POLLOUT | POLLWRNORM | POLLWRBAND) & sought;
+
+ if (lNetworkEvents & FD_OOB)
+ happened |= (POLLPRI | POLLRDBAND) & sought;
+
+ return happened;
+}
+
+#else /* !MinGW */
+
+/* Convert select(2) returned fd_sets into poll(2) revents values. */
+static int
+compute_revents (int fd, int sought, fd_set *rfds, fd_set *wfds, fd_set *efds)
+{
+ int happened = 0;
+ if (FD_ISSET (fd, rfds))
+ {
+ int r;
+ int socket_errno;
+
+# if defined __MACH__ && defined __APPLE__
+ /* There is a bug in Mac OS X that causes it to ignore MSG_PEEK
+ for some kinds of descriptors. Detect if this descriptor is a
+ connected socket, a server socket, or something else using a
+ 0-byte recv, and use ioctl(2) to detect POLLHUP. */
+ r = recv (fd, NULL, 0, MSG_PEEK);
+ socket_errno = (r < 0) ? errno : 0;
+ if (r == 0 || socket_errno == ENOTSOCK)
+ ioctl (fd, FIONREAD, &r);
+# else
+ char data[64];
+ r = recv (fd, data, sizeof (data), MSG_PEEK);
+ socket_errno = (r < 0) ? errno : 0;
+# endif
+ if (r == 0)
+ happened |= POLLHUP;
+
+ /* If the event happened on an unconnected server socket,
+ that's fine. */
+ else if (r > 0 || ( /* (r == -1) && */ socket_errno == ENOTCONN))
+ happened |= (POLLIN | POLLRDNORM) & sought;
+
+ /* Distinguish hung-up sockets from other errors. */
+ else if (socket_errno == ESHUTDOWN || socket_errno == ECONNRESET
+ || socket_errno == ECONNABORTED || socket_errno == ENETRESET)
+ happened |= POLLHUP;
+
+ else
+ happened |= POLLERR;
+ }
+
+ if (FD_ISSET (fd, wfds))
+ happened |= (POLLOUT | POLLWRNORM | POLLWRBAND) & sought;
+
+ if (FD_ISSET (fd, efds))
+ happened |= (POLLPRI | POLLRDBAND) & sought;
+
+ return happened;
+}
+#endif /* !MinGW */
+
+int
+poll (pfd, nfd, timeout)
+ struct pollfd *pfd;
+ nfds_t nfd;
+ int timeout;
+{
+#ifndef WIN32_NATIVE
+ fd_set rfds, wfds, efds;
+ struct timeval tv;
+ struct timeval *ptv;
+ int maxfd, rc;
+ nfds_t i;
+
+# ifdef _SC_OPEN_MAX
+ static int sc_open_max = -1;
+
+ if (nfd < 0
+ || (nfd > sc_open_max
+ && (sc_open_max != -1
+ || nfd > (sc_open_max = sysconf (_SC_OPEN_MAX)))))
+ {
+ errno = EINVAL;
+ return -1;
+ }
+# else /* !_SC_OPEN_MAX */
+# ifdef OPEN_MAX
+ if (nfd < 0 || nfd > OPEN_MAX)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+# endif /* OPEN_MAX -- else, no check is needed */
+# endif /* !_SC_OPEN_MAX */
+
+ /* EFAULT is not necessary to implement, but let's do it in the
+ simplest case. */
+ if (!pfd)
+ {
+ errno = EFAULT;
+ return -1;
+ }
+
+ /* convert timeout number into a timeval structure */
+ if (timeout == 0)
+ {
+ ptv = &tv;
+ ptv->tv_sec = 0;
+ ptv->tv_usec = 0;
+ }
+ else if (timeout > 0)
+ {
+ ptv = &tv;
+ ptv->tv_sec = timeout / 1000;
+ ptv->tv_usec = (timeout % 1000) * 1000;
+ }
+ else if (timeout == INFTIM)
+ /* wait forever */
+ ptv = NULL;
+ else
+ {
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* create fd sets and determine max fd */
+ maxfd = -1;
+ FD_ZERO (&rfds);
+ FD_ZERO (&wfds);
+ FD_ZERO (&efds);
+ for (i = 0; i < nfd; i++)
+ {
+ if (pfd[i].fd < 0)
+ continue;
+
+ if (pfd[i].events & (POLLIN | POLLRDNORM))
+ FD_SET (pfd[i].fd, &rfds);
+
+ /* see select(2): "the only exceptional condition detectable
+ is out-of-band data received on a socket", hence we push
+ POLLWRBAND events onto wfds instead of efds. */
+ if (pfd[i].events & (POLLOUT | POLLWRNORM | POLLWRBAND))
+ FD_SET (pfd[i].fd, &wfds);
+ if (pfd[i].events & (POLLPRI | POLLRDBAND))
+ FD_SET (pfd[i].fd, &efds);
+ if (pfd[i].fd >= maxfd
+ && (pfd[i].events & (POLLIN | POLLOUT | POLLPRI
+ | POLLRDNORM | POLLRDBAND
+ | POLLWRNORM | POLLWRBAND)))
+ {
+ maxfd = pfd[i].fd;
+ if (maxfd > FD_SETSIZE)
+ {
+ errno = EOVERFLOW;
+ return -1;
+ }
+ }
+ }
+
+ /* examine fd sets */
+ rc = select (maxfd + 1, &rfds, &wfds, &efds, ptv);
+ if (rc < 0)
+ return rc;
+
+ /* establish results */
+ rc = 0;
+ for (i = 0; i < nfd; i++)
+ if (pfd[i].fd < 0)
+ pfd[i].revents = 0;
+ else
+ {
+ int happened = compute_revents (pfd[i].fd, pfd[i].events,
+ &rfds, &wfds, &efds);
+ if (happened)
+ {
+ pfd[i].revents = happened;
+ rc++;
+ }
+ }
+
+ return rc;
+#else
+ static struct timeval tv0;
+ static HANDLE hEvent;
+ WSANETWORKEVENTS ev;
+ HANDLE h, handle_array[FD_SETSIZE + 2];
+ DWORD ret, wait_timeout, nhandles;
+ fd_set rfds, wfds, xfds;
+ BOOL poll_again;
+ MSG msg;
+ int rc = 0;
+ nfds_t i;
+
+ if (nfd < 0 || timeout < -1)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (!hEvent)
+ hEvent = CreateEvent (NULL, FALSE, FALSE, NULL);
+
+ handle_array[0] = hEvent;
+ nhandles = 1;
+ FD_ZERO (&rfds);
+ FD_ZERO (&wfds);
+ FD_ZERO (&xfds);
+
+ /* Classify socket handles and create fd sets. */
+ for (i = 0; i < nfd; i++)
+ {
+ int sought = pfd[i].events;
+ pfd[i].revents = 0;
+ if (pfd[i].fd < 0)
+ continue;
+ if (!(sought & (POLLIN | POLLRDNORM | POLLOUT | POLLWRNORM | POLLWRBAND
+ | POLLPRI | POLLRDBAND)))
+ continue;
+
+ h = (HANDLE) _get_osfhandle (pfd[i].fd);
+ assert (h != NULL);
+ if (IsSocketHandle (h))
+ {
+ int requested = FD_CLOSE;
+
+ /* see above; socket handles are mapped onto select. */
+ if (sought & (POLLIN | POLLRDNORM))
+ {
+ requested |= FD_READ | FD_ACCEPT;
+ FD_SET ((SOCKET) h, &rfds);
+ }
+ if (sought & (POLLOUT | POLLWRNORM | POLLWRBAND))
+ {
+ requested |= FD_WRITE | FD_CONNECT;
+ FD_SET ((SOCKET) h, &wfds);
+ }
+ if (sought & (POLLPRI | POLLRDBAND))
+ {
+ requested |= FD_OOB;
+ FD_SET ((SOCKET) h, &xfds);
+ }
+
+ if (requested)
+ WSAEventSelect ((SOCKET) h, hEvent, requested);
+ }
+ else
+ {
+ /* Poll now. If we get an event, do not poll again. Also,
+ screen buffer handles are waitable, and they'll block until
+ a character is available. win32_compute_revents eliminates
+ bits for the "wrong" direction. */
+ pfd[i].revents = win32_compute_revents (h, &sought);
+ if (sought)
+ handle_array[nhandles++] = h;
+ if (pfd[i].revents)
+ timeout = 0;
+ }
+ }
+
+ if (select (0, &rfds, &wfds, &xfds, &tv0) > 0)
+ {
+ /* Do MsgWaitForMultipleObjects anyway to dispatch messages, but
+ no need to call select again. */
+ poll_again = FALSE;
+ wait_timeout = 0;
+ }
+ else
+ {
+ poll_again = TRUE;
+ if (timeout == INFTIM)
+ wait_timeout = INFINITE;
+ else
+ wait_timeout = timeout;
+ }
+
+ for (;;)
+ {
+ ret = MsgWaitForMultipleObjects (nhandles, handle_array, FALSE,
+ wait_timeout, QS_ALLINPUT);
+
+ if (ret == WAIT_OBJECT_0 + nhandles)
+ {
+ /* new input of some other kind */
+ BOOL bRet;
+ while ((bRet = PeekMessage (&msg, NULL, 0, 0, PM_REMOVE)) != 0)
+ {
+ TranslateMessage (&msg);
+ DispatchMessage (&msg);
+ }
+ }
+ else
+ break;
+ }
+
+ if (poll_again)
+ select (0, &rfds, &wfds, &xfds, &tv0);
+
+ /* Place a sentinel at the end of the array. */
+ handle_array[nhandles] = NULL;
+ nhandles = 1;
+ for (i = 0; i < nfd; i++)
+ {
+ int happened;
+
+ if (pfd[i].fd < 0)
+ continue;
+ if (!(pfd[i].events & (POLLIN | POLLRDNORM |
+ POLLOUT | POLLWRNORM | POLLWRBAND)))
+ continue;
+
+ h = (HANDLE) _get_osfhandle (pfd[i].fd);
+ if (h != handle_array[nhandles])
+ {
+ /* It's a socket. */
+ WSAEnumNetworkEvents ((SOCKET) h, NULL, &ev);
+ WSAEventSelect ((SOCKET) h, 0, 0);
+
+ /* If we're lucky, WSAEnumNetworkEvents already provided a way
+ to distinguish FD_READ and FD_ACCEPT; this saves a recv later. */
+ if (FD_ISSET ((SOCKET) h, &rfds)
+ && !(ev.lNetworkEvents & (FD_READ | FD_ACCEPT)))
+ ev.lNetworkEvents |= FD_READ | FD_ACCEPT;
+ if (FD_ISSET ((SOCKET) h, &wfds))
+ ev.lNetworkEvents |= FD_WRITE | FD_CONNECT;
+ if (FD_ISSET ((SOCKET) h, &xfds))
+ ev.lNetworkEvents |= FD_OOB;
+
+ happened = win32_compute_revents_socket ((SOCKET) h, pfd[i].events,
+ ev.lNetworkEvents);
+ }
+ else
+ {
+ /* Not a socket. */
+ int sought = pfd[i].events;
+ happened = win32_compute_revents (h, &sought);
+ nhandles++;
+ }
+
+ if ((pfd[i].revents |= happened) != 0)
+ rc++;
+ }
+
+ return rc;
+#endif
+}
diff --git a/compat/win32/sys/poll.h b/compat/win32/sys/poll.h
--- /dev/null
+++ b/compat/win32/sys/poll.h
@@ -0,0 +1,53 @@
+/* Header for poll(2) emulation
+ Contributed by Paolo Bonzini.
+
+ Copyright 2001, 2002, 2003, 2007, 2009, 2010 Free Software Foundation, Inc.
+
+ This file is part of gnulib.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
+
+#ifndef _GL_POLL_H
+#define _GL_POLL_H
+
+/* fake a poll(2) environment */
+#define POLLIN 0x0001 /* any readable data available */
+#define POLLPRI 0x0002 /* OOB/Urgent readable data */
+#define POLLOUT 0x0004 /* file descriptor is writeable */
+#define POLLERR 0x0008 /* some poll error occurred */
+#define POLLHUP 0x0010 /* file descriptor was "hung up" */
+#define POLLNVAL 0x0020 /* requested events "invalid" */
+#define POLLRDNORM 0x0040
+#define POLLRDBAND 0x0080
+#define POLLWRNORM 0x0100
+#define POLLWRBAND 0x0200
+
+struct pollfd
+{
+ int fd; /* which file descriptor to poll */
+ short events; /* events we are interested in */
+ short revents; /* events found on return */
+};
+
+typedef unsigned long nfds_t;
+
+extern int poll (struct pollfd *pfd, nfds_t nfd, int timeout);
+
+/* Define INFTIM only if doing so conforms to POSIX. */
+#if !defined (_POSIX_C_SOURCE) && !defined (_XOPEN_SOURCE)
+#define INFTIM (-1)
+#endif
+
+#endif /* _GL_POLL_H */
diff --git a/compat/win32/syslog.c b/compat/win32/syslog.c
--- /dev/null
+++ b/compat/win32/syslog.c
@@ -0,0 +1,72 @@
+#include "../../git-compat-util.h"
+#include "../../strbuf.h"
+
+static HANDLE ms_eventlog;
+
+void openlog(const char *ident, int logopt, int facility)
+{
+ if (ms_eventlog)
+ return;
+
+ ms_eventlog = RegisterEventSourceA(NULL, ident);
+
+ if (!ms_eventlog)
+ warning("RegisterEventSource() failed: %lu", GetLastError());
+}
+
+void syslog(int priority, const char *fmt, ...)
+{
+ struct strbuf sb = STRBUF_INIT;
+ struct strbuf_expand_dict_entry dict[] = {
+ {"1", "% 1"},
+ {NULL, NULL}
+ };
+ WORD logtype;
+ char *str;
+ int str_len;
+ va_list ap;
+
+ if (!ms_eventlog)
+ return;
+
+ va_start(ap, fmt);
+ str_len = vsnprintf(NULL, 0, fmt, ap);
+ va_end(ap);
+
+ if (str_len < 0) {
+ warning("vsnprintf failed: '%s'", strerror(errno));
+ return;
+ }
+
+ str = malloc(str_len + 1);
+ va_start(ap, fmt);
+ vsnprintf(str, str_len + 1, fmt, ap);
+ va_end(ap);
+ strbuf_expand(&sb, str, strbuf_expand_dict_cb, &dict);
+ free(str);
+
+ switch (priority) {
+ case LOG_EMERG:
+ case LOG_ALERT:
+ case LOG_CRIT:
+ case LOG_ERR:
+ logtype = EVENTLOG_ERROR_TYPE;
+ break;
+
+ case LOG_WARNING:
+ logtype = EVENTLOG_WARNING_TYPE;
+ break;
+
+ case LOG_NOTICE:
+ case LOG_INFO:
+ case LOG_DEBUG:
+ default:
+ logtype = EVENTLOG_INFORMATION_TYPE;
+ break;
+ }
+
+ ReportEventA(ms_eventlog, logtype, 0, 0, NULL, 1, 0,
+ (const char **)&sb.buf, NULL);
+
+ strbuf_release(&sb);
+}
diff --git a/compat/win32/syslog.h b/compat/win32/syslog.h
--- /dev/null
+++ b/compat/win32/syslog.h
@@ -0,0 +1,20 @@
+#ifndef SYSLOG_H
+#define SYSLOG_H
+
+#define LOG_PID 0x01
+
+#define LOG_EMERG 0
+#define LOG_ALERT 1
+#define LOG_CRIT 2
+#define LOG_ERR 3
+#define LOG_WARNING 4
+#define LOG_NOTICE 5
+#define LOG_INFO 6
+#define LOG_DEBUG 7
+
+#define LOG_DAEMON (3<<3)
+
+void openlog(const char *ident, int logopt, int facility);
+void syslog(int priority, const char *fmt, ...);
+
+#endif /* SYSLOG_H */
diff --git a/config.c b/config.c
index 9918b9351d8b8b9b33fea479bd4f4997a5d6528a..31f778b5e8999bd898a18d8ea121c23a01fed352 100644 (file)
--- a/config.c
+++ b/config.c
int git_config_maybe_bool(const char *name, const char *value)
{
- int v = git_config_maybe_bool_text(name, value);
+ long v = git_config_maybe_bool_text(name, value);
if (0 <= v)
return v;
- if (!strcmp(value, "0"))
- return 0;
- if (!strcmp(value, "1"))
- return 1;
+ if (git_parse_long(value, &v))
+ return !!v;
return -1;
}
return 0;
}
+ if (!strcmp(var, "core.abbrevguard")) {
+ unique_abbrev_extra_length = git_config_int(var, value);
+ if (unique_abbrev_extra_length < 0)
+ unique_abbrev_extra_length = 0;
+ return 0;
+ }
+
if (!strcmp(var, "core.bare")) {
is_bare_repository_cfg = git_config_bool(var, value);
return 0;
push_default = PUSH_DEFAULT_NOTHING;
else if (!strcmp(value, "matching"))
push_default = PUSH_DEFAULT_MATCHING;
- else if (!strcmp(value, "tracking"))
- push_default = PUSH_DEFAULT_TRACKING;
+ else if (!strcmp(value, "upstream"))
+ push_default = PUSH_DEFAULT_UPSTREAM;
+ else if (!strcmp(value, "tracking")) /* deprecated */
+ push_default = PUSH_DEFAULT_UPSTREAM;
else if (!strcmp(value, "current"))
push_default = PUSH_DEFAULT_CURRENT;
else {
return 0;
}
-int git_config(config_fn_t fn, void *data)
+int git_config_early(config_fn_t fn, void *data, const char *repo_config)
{
int ret = 0, found = 0;
- char *repo_config = NULL;
const char *home = NULL;
/* Setting $GIT_CONFIG makes git read _only_ the given config file. */
free(user_config);
}
- repo_config = git_pathdup("config");
- if (!access(repo_config, R_OK)) {
+ if (repo_config && !access(repo_config, R_OK)) {
ret += git_config_from_file(fn, repo_config, data);
found += 1;
}
- free(repo_config);
ret += git_config_from_parameters(fn, data);
if (config_parameters)
found += 1;
- if (found == 0)
- return -1;
+ return ret == 0 ? found : ret;
+}
+
+int git_config(config_fn_t fn, void *data)
+{
+ char *repo_config = NULL;
+ int ret;
+
+ repo_config = git_pathdup("config");
+ ret = git_config_early(fn, data, repo_config);
+ if (repo_config)
+ free(repo_config);
return ret;
}
return git_config_set_multivar(key, value, NULL, 0);
}
+/*
+ * Auxiliary function to sanity-check and split the key into the section
+ * identifier and variable name.
+ *
+ * Returns 0 on success, -1 when there is an invalid character in the key and
+ * -2 if there is no section name in the key.
+ *
+ * store_key - pointer to char* which will hold a copy of the key with
+ * lowercase section and variable name
+ * baselen - pointer to int which will hold the length of the
+ * section + subsection part, can be NULL
+ */
+int git_config_parse_key(const char *key, char **store_key, int *baselen_)
+{
+ int i, dot, baselen;
+ const char *last_dot = strrchr(key, '.');
+
+ /*
+ * Since "key" actually contains the section name and the real
+ * key name separated by a dot, we have to know where the dot is.
+ */
+
+ if (last_dot == NULL || last_dot == key) {
+ error("key does not contain a section: %s", key);
+ return -2;
+ }
+
+ if (!last_dot[1]) {
+ error("key does not contain variable name: %s", key);
+ return -2;
+ }
+
+ baselen = last_dot - key;
+ if (baselen_)
+ *baselen_ = baselen;
+
+ /*
+ * Validate the key and while at it, lower case it for matching.
+ */
+ *store_key = xmalloc(strlen(key) + 1);
+
+ dot = 0;
+ for (i = 0; key[i]; i++) {
+ unsigned char c = key[i];
+ if (c == '.')
+ dot = 1;
+ /* Leave the extended basename untouched.. */
+ if (!dot || i > baselen) {
+ if (!iskeychar(c) ||
+ (i == baselen + 1 && !isalpha(c))) {
+ error("invalid key: %s", key);
+ goto out_free_ret_1;
+ }
+ c = tolower(c);
+ } else if (c == '\n') {
+ error("invalid key (newline): %s", key);
+ goto out_free_ret_1;
+ }
+ (*store_key)[i] = c;
+ }
+ (*store_key)[i] = 0;
+
+ return 0;
+
+out_free_ret_1:
+ free(*store_key);
+ return -1;
+}
+
/*
* If value==NULL, unset in (remove from) config,
* if value_regex!=NULL, disregard key/value pairs where value does not match.
int git_config_set_multivar(const char *key, const char *value,
const char *value_regex, int multi_replace)
{
- int i, dot;
int fd = -1, in_fd;
int ret;
char *config_filename;
struct lock_file *lock = NULL;
- const char *last_dot = strrchr(key, '.');
if (config_exclusive_filename)
config_filename = xstrdup(config_exclusive_filename);
else
config_filename = git_pathdup("config");
- /*
- * Since "key" actually contains the section name and the real
- * key name separated by a dot, we have to know where the dot is.
- */
-
- if (last_dot == NULL) {
- error("key does not contain a section: %s", key);
- ret = 2;
+ /* parse-key returns negative; flip the sign to feed exit(3) */
+ ret = 0 - git_config_parse_key(key, &store.key, &store.baselen);
+ if (ret)
goto out_free;
- }
- store.baselen = last_dot - key;
store.multi_replace = multi_replace;
- /*
- * Validate the key and while at it, lower case it for matching.
- */
- store.key = xmalloc(strlen(key) + 1);
- dot = 0;
- for (i = 0; key[i]; i++) {
- unsigned char c = key[i];
- if (c == '.')
- dot = 1;
- /* Leave the extended basename untouched.. */
- if (!dot || i > store.baselen) {
- if (!iskeychar(c) || (i == store.baselen+1 && !isalpha(c))) {
- error("invalid key: %s", key);
- free(store.key);
- ret = 1;
- goto out_free;
- }
- c = tolower(c);
- } else if (c == '\n') {
- error("invalid key (newline): %s", key);
- free(store.key);
- ret = 1;
- goto out_free;
- }
- store.key[i] = c;
- }
- store.key[i] = 0;
/*
* The lock serves a purpose in addition to locking: the new
diff --git a/config.mak.in b/config.mak.in
index a0c34eec15773f0f3e4d2b4e12f0c76e722ede95..961497305777fcbd67001fdae1335fcfe2910e88 100644 (file)
--- a/config.mak.in
+++ b/config.mak.in
export exec_prefix mandir
export srcdir VPATH
-ASCIIDOC8=@ASCIIDOC8@
+ASCIIDOC7=@ASCIIDOC7@
NEEDS_SSL_WITH_CRYPTO=@NEEDS_SSL_WITH_CRYPTO@
NO_OPENSSL=@NO_OPENSSL@
NO_CURL=@NO_CURL@
NO_HSTRERROR=@NO_HSTRERROR@
NO_STRCASESTR=@NO_STRCASESTR@
NO_STRTOK_R=@NO_STRTOK_R@
+NO_FNMATCH=@NO_FNMATCH@
+NO_FNMATCH_CASEFOLD=@NO_FNMATCH_CASEFOLD@
NO_MEMMEM=@NO_MEMMEM@
NO_STRLCPY=@NO_STRLCPY@
NO_UINTMAX_T=@NO_UINTMAX_T@
diff --git a/configure.ac b/configure.ac
index 56731c35c9d1b5e2d2c82cdff51ab496dcbac7f5..20039c546f1cc91575771acbe6f52de6778bea89 100644 (file)
--- a/configure.ac
+++ b/configure.ac
GIT_PARSE_WITH_SET_MAKE_VAR(gitconfig, ETC_GITCONFIG,
Use VALUE instead of /etc/gitconfig as the
global git configuration file.
- If VALUE is not fully qualified it will be interpretted
+ If VALUE is not fully qualified it will be interpreted
+ as a path relative to the computed prefix at runtime.)
+
+#
+# Allow user to set ETC_GITATTRIBUTES variable
+GIT_PARSE_WITH_SET_MAKE_VAR(gitattributes, ETC_GITATTRIBUTES,
+ Use VALUE instead of /etc/gitattributes as the
+ global git attributes file.
+ If VALUE is not fully qualified it will be interpreted
as a path relative to the computed prefix at runtime.)
#
AC_CACHE_CHECK([if linker supports -R], git_cv_ld_dashr, [
SAVE_LDFLAGS="${LDFLAGS}"
LDFLAGS="${SAVE_LDFLAGS} -R /"
- AC_LINK_IFELSE(AC_LANG_PROGRAM([], []), [git_cv_ld_dashr=yes], [git_cv_ld_dashr=no])
+ AC_LINK_IFELSE([AC_LANG_PROGRAM([], [])], [git_cv_ld_dashr=yes], [git_cv_ld_dashr=no])
LDFLAGS="${SAVE_LDFLAGS}"
])
if test "$git_cv_ld_dashr" = "yes"; then
AC_CACHE_CHECK([if linker supports -Wl,-rpath,], git_cv_ld_wl_rpath, [
SAVE_LDFLAGS="${LDFLAGS}"
LDFLAGS="${SAVE_LDFLAGS} -Wl,-rpath,/"
- AC_LINK_IFELSE(AC_LANG_PROGRAM([], []), [git_cv_ld_wl_rpath=yes], [git_cv_ld_wl_rpath=no])
+ AC_LINK_IFELSE([AC_LANG_PROGRAM([], [])], [git_cv_ld_wl_rpath=yes], [git_cv_ld_wl_rpath=no])
LDFLAGS="${SAVE_LDFLAGS}"
])
if test "$git_cv_ld_wl_rpath" = "yes"; then
AC_CACHE_CHECK([if linker supports -rpath], git_cv_ld_rpath, [
SAVE_LDFLAGS="${LDFLAGS}"
LDFLAGS="${SAVE_LDFLAGS} -rpath /"
- AC_LINK_IFELSE(AC_LANG_PROGRAM([], []), [git_cv_ld_rpath=yes], [git_cv_ld_rpath=no])
+ AC_LINK_IFELSE([AC_LANG_PROGRAM([], [])], [git_cv_ld_rpath=yes], [git_cv_ld_rpath=no])
LDFLAGS="${SAVE_LDFLAGS}"
])
if test "$git_cv_ld_rpath" = "yes"; then
AC_MSG_CHECKING([for asciidoc version])
asciidoc_version=`$ASCIIDOC --version 2>/dev/null`
case "${asciidoc_version}" in
- asciidoc' '8*)
- ASCIIDOC8=YesPlease
+ asciidoc' '7*)
+ ASCIIDOC7=YesPlease
AC_MSG_RESULT([${asciidoc_version} > 7])
;;
- asciidoc' '7*)
- ASCIIDOC8=
+ asciidoc' '8*)
+ ASCIIDOC7=
AC_MSG_RESULT([${asciidoc_version}])
;;
*)
- ASCIIDOC8=
+ ASCIIDOC7=
AC_MSG_RESULT([${asciidoc_version} (unknown)])
;;
esac
fi
-AC_SUBST(ASCIIDOC8)
+AC_SUBST(ASCIIDOC7)
## Checks for libraries.
GIT_STASH_FLAGS($ICONVDIR)
-AC_DEFUN([ICONVTEST_SRC], [
-#include <iconv.h>
-
-int main(void)
-{
- iconv_open("", "");
- return 0;
-}
-])
+AC_DEFUN([ICONVTEST_SRC],
+[AC_LANG_PROGRAM([#include <iconv.h>],
+ [iconv_open("", "");])])
if test -n "$ICONVDIR"; then
lib_order="-liconv -lc"
old_LIBS="$LIBS"
LIBS="$LIBS $l"
AC_MSG_CHECKING([for iconv in $l])
- AC_LINK_IFELSE(ICONVTEST_SRC,
+ AC_LINK_IFELSE([ICONVTEST_SRC],
[AC_MSG_RESULT([yes])
NO_ICONV=
break],
GIT_STASH_FLAGS($ZLIB_PATH)
AC_DEFUN([ZLIBTEST_SRC], [
-#include <zlib.h>
-
-int main(void)
-{
- deflateBound(0, 0);
- return 0;
-}
-])
+AC_LANG_PROGRAM([#include <zlib.h>],
+ [deflateBound(0, 0);])])
AC_MSG_CHECKING([for deflateBound in -lz])
old_LIBS="$LIBS"
LIBS="$LIBS -lz"
-AC_LINK_IFELSE(ZLIBTEST_SRC,
+AC_LINK_IFELSE([ZLIBTEST_SRC],
[AC_MSG_RESULT([yes])],
[AC_MSG_RESULT([no])
NO_DEFLATE_BOUND=yes])
[NO_SYS_SELECT_H=UnfortunatelyYes])
AC_SUBST(NO_SYS_SELECT_H)
#
+# Define NO_SYS_POLL_H if you don't have sys/poll.h
+AC_CHECK_HEADER([sys/poll.h],
+[NO_SYS_POLL_H=],
+[NO_SYS_POLL_H=UnfortunatelyYes])
+AC_SUBST(NO_SYS_POLL_H)
+#
+# Define NO_INTTYPES_H if you don't have inttypes.h
+AC_CHECK_HEADER([inttypes.h],
+[NO_INTTYPES_H=],
+[NO_INTTYPES_H=UnfortunatelyYes])
+AC_SUBST(NO_INTTYPES_H)
+#
# Define OLD_ICONV if your library has an old iconv(), where the second
# (input buffer pointer) parameter is declared with type (const char **).
-AC_DEFUN([OLDICONVTEST_SRC], [[
+AC_DEFUN([OLDICONVTEST_SRC], [
+AC_LANG_PROGRAM([[
#include <iconv.h>
extern size_t iconv(iconv_t cd,
char **inbuf, size_t *inbytesleft,
char **outbuf, size_t *outbytesleft);
-
-int main(void)
-{
- return 0;
-}
-]])
+]], [])])
GIT_STASH_FLAGS($ICONVDIR)
AC_MSG_CHECKING([for old iconv()])
-AC_COMPILE_IFELSE(OLDICONVTEST_SRC,
+AC_COMPILE_IFELSE([OLDICONVTEST_SRC],
[AC_MSG_RESULT([no])],
[AC_MSG_RESULT([yes])
OLD_ICONV=UnfortunatelyYes])
[NO_STRTOK_R=YesPlease])
AC_SUBST(NO_STRTOK_R)
#
+# Define NO_FNMATCH if you don't have fnmatch
+GIT_CHECK_FUNC(fnmatch,
+[NO_FNMATCH=],
+[NO_FNMATCH=YesPlease])
+AC_SUBST(NO_FNMATCH)
+#
+# Define NO_FNMATCH_CASEFOLD if your fnmatch function doesn't have the
+# FNM_CASEFOLD GNU extension.
+AC_CACHE_CHECK([whether the fnmatch function supports the FNMATCH_CASEFOLD GNU extension],
+ [ac_cv_c_excellent_fnmatch], [
+AC_EGREP_CPP(yippeeyeswehaveit,
+ AC_LANG_PROGRAM([
+#include <fnmatch.h>
+],
+[#ifdef FNM_CASEFOLD
+yippeeyeswehaveit
+#endif
+]),
+ [ac_cv_c_excellent_fnmatch=yes],
+ [ac_cv_c_excellent_fnmatch=no])
+])
+if test $ac_cv_c_excellent_fnmatch = yes; then
+ NO_FNMATCH_CASEFOLD=
+else
+ NO_FNMATCH_CASEFOLD=YesPlease
+fi
+AC_SUBST(NO_FNMATCH_CASEFOLD)
+#
# Define NO_MEMMEM if you don't have memmem.
GIT_CHECK_FUNC(memmem,
[NO_MEMMEM=],
[NO_MKSTEMPS=YesPlease])
AC_SUBST(NO_MKSTEMPS)
#
+# Define NO_INITGROUPS if you don't have initgroups in the C library.
+GIT_CHECK_FUNC(initgroups,
+[NO_INITGROUPS=],
+[NO_INITGROUPS=YesPlease])
+AC_SUBST(NO_INITGROUPS)
+#
#
# Define NO_MMAP if you want to avoid mmap.
#
#
# Define PTHREAD_LIBS to the linker flag used for Pthread support.
AC_DEFUN([PTHREADTEST_SRC], [
+AC_LANG_PROGRAM([[
#include <pthread.h>
-
-int main(void)
-{
+]], [[
pthread_mutex_t test_mutex;
int retcode = 0;
retcode |= pthread_mutex_init(&test_mutex,(void *)0);
retcode |= pthread_mutex_lock(&test_mutex);
retcode |= pthread_mutex_unlock(&test_mutex);
return retcode;
-}
-])
+]])])
dnl AC_LANG_CONFTEST([AC_LANG_PROGRAM(
dnl [[#include <pthread.h>]],
old_CFLAGS="$CFLAGS"
CFLAGS="$opt $CFLAGS"
AC_MSG_CHECKING([Checking for POSIX Threads with '$opt'])
- AC_LINK_IFELSE(PTHREADTEST_SRC,
+ AC_LINK_IFELSE([PTHREADTEST_SRC],
[AC_MSG_RESULT([yes])
NO_PTHREADS=
PTHREAD_LIBS="$opt"
old_CFLAGS="$CFLAGS"
CFLAGS="$PTHREAD_CFLAGS $CFLAGS"
AC_MSG_CHECKING([Checking for POSIX Threads with '$PTHREAD_CFLAGS'])
- AC_LINK_IFELSE(PTHREADTEST_SRC,
+ AC_LINK_IFELSE([PTHREADTEST_SRC],
[AC_MSG_RESULT([yes])
NO_PTHREADS=
PTHREAD_LIBS="$PTHREAD_CFLAGS"
index d0627e0852cc47df3ffd29754254ccb99c3b2ad0..9775dffb5d49521a7078643104762cd136edffcc 100755 (executable)
--- a/contrib/ciabot/ciabot.py
+++ b/contrib/ciabot/ciabot.py
branch = os.path.basename(refname)
# Compute a shortnane for the revision
- rev = do("git describe ${merged} 2>/dev/null") or merged[:12]
+ rev = do("git describe '"+ merged +"' 2>/dev/null") or merged[:12]
# Extract the neta-information for the commit
rawcommit = do("git cat-file commit " + merged)
index d3037fc94eb027e5067b0371da2b33081fd89da5..893b7716cafa4811d237480a980140d308aa20dc 100755 (executable)
# 2) Added the following line to your .bashrc:
# source ~/.git-completion.sh
#
+# Or, add the following lines to your .zshrc:
+# autoload bashcompinit
+# bashcompinit
+# source ~/.git-completion.sh
+#
# 3) Consider changing your PS1 to also show the current branch:
# PS1='[\u@\h \W$(__git_ps1 " (%s)")]\$ '
#
# get the upstream from the "git-svn-id: ..." in a commit message
# (git-svn uses essentially the same procedure internally)
local svn_upstream=($(git log --first-parent -1 \
- --grep="^git-svn-id: \(${svn_url_pattern:2}\)" 2>/dev/null))
+ --grep="^git-svn-id: \(${svn_url_pattern#??}\)" 2>/dev/null))
if [[ 0 -ne ${#svn_upstream[@]} ]]; then
svn_upstream=${svn_upstream[ ${#svn_upstream[@]} - 2 ]}
svn_upstream=${svn_upstream%@*}
- for ((n=1; "$n" <= "${#svn_remote[@]}"; ++n)); do
+ local n_stop="${#svn_remote[@]}"
+ for ((n=1; n <= n_stop; ++n)); do
svn_upstream=${svn_upstream#${svn_remote[$n]}}
done
done
}
+# The following function is based on code from:
+#
+# bash_completion - programmable completion functions for bash 3.2+
+#
+# Copyright © 2006-2008, Ian Macdonald <ian@caliban.org>
+# © 2009-2010, Bash Completion Maintainers
+# <bash-completion-devel@lists.alioth.debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+# The latest version of this software can be obtained here:
+#
+# http://bash-completion.alioth.debian.org/
+#
+# RELEASE: 2.x
+
+# This function can be used to access a tokenized list of words
+# on the command line:
+#
+# __git_reassemble_comp_words_by_ref '=:'
+# if test "${words_[cword_-1]}" = -w
+# then
+# ...
+# fi
+#
+# The argument should be a collection of characters from the list of
+# word completion separators (COMP_WORDBREAKS) to treat as ordinary
+# characters.
+#
+# This is roughly equivalent to going back in time and setting
+# COMP_WORDBREAKS to exclude those characters. The intent is to
+# make option types like --date=<type> and <rev>:<path> easy to
+# recognize by treating each shell word as a single token.
+#
+# It is best not to set COMP_WORDBREAKS directly because the value is
+# shared with other completion scripts. By the time the completion
+# function gets called, COMP_WORDS has already been populated so local
+# changes to COMP_WORDBREAKS have no effect.
+#
+# Output: words_, cword_, cur_.
+
+__git_reassemble_comp_words_by_ref()
+{
+ local exclude i j first
+ # Which word separators to exclude?
+ exclude="${1//[^$COMP_WORDBREAKS]}"
+ cword_=$COMP_CWORD
+ if [ -z "$exclude" ]; then
+ words_=("${COMP_WORDS[@]}")
+ return
+ fi
+ # List of word completion separators has shrunk;
+ # re-assemble words to complete.
+ for ((i=0, j=0; i < ${#COMP_WORDS[@]}; i++, j++)); do
+ # Append each nonempty word consisting of just
+ # word separator characters to the current word.
+ first=t
+ while
+ [ $i -gt 0 ] &&
+ [ -n "${COMP_WORDS[$i]}" ] &&
+ # word consists of excluded word separators
+ [ "${COMP_WORDS[$i]//[^$exclude]}" = "${COMP_WORDS[$i]}" ]
+ do
+ # Attach to the previous token,
+ # unless the previous token is the command name.
+ if [ $j -ge 2 ] && [ -n "$first" ]; then
+ ((j--))
+ fi
+ first=
+ words_[$j]=${words_[j]}${COMP_WORDS[i]}
+ if [ $i = $COMP_CWORD ]; then
+ cword_=$j
+ fi
+ if (($i < ${#COMP_WORDS[@]} - 1)); then
+ ((i++))
+ else
+ # Done.
+ return
+ fi
+ done
+ words_[$j]=${words_[j]}${COMP_WORDS[i]}
+ if [ $i = $COMP_CWORD ]; then
+ cword_=$j
+ fi
+ done
+}
+
+if ! type _get_comp_words_by_ref >/dev/null 2>&1; then
+if [[ -z ${ZSH_VERSION:+set} ]]; then
+_get_comp_words_by_ref ()
+{
+ local exclude cur_ words_ cword_
+ if [ "$1" = "-n" ]; then
+ exclude=$2
+ shift 2
+ fi
+ __git_reassemble_comp_words_by_ref "$exclude"
+ cur_=${words_[cword_]}
+ while [ $# -gt 0 ]; do
+ case "$1" in
+ cur)
+ cur=$cur_
+ ;;
+ prev)
+ prev=${words_[$cword_-1]}
+ ;;
+ words)
+ words=("${words_[@]}")
+ ;;
+ cword)
+ cword=$cword_
+ ;;
+ esac
+ shift
+ done
+}
+else
+_get_comp_words_by_ref ()
+{
+ while [ $# -gt 0 ]; do
+ case "$1" in
+ cur)
+ cur=${COMP_WORDS[COMP_CWORD]}
+ ;;
+ prev)
+ prev=${COMP_WORDS[COMP_CWORD-1]}
+ ;;
+ words)
+ words=("${COMP_WORDS[@]}")
+ ;;
+ cword)
+ cword=$COMP_CWORD
+ ;;
+ -n)
+ # assume COMP_WORDBREAKS is already set sanely
+ shift
+ ;;
+ esac
+ shift
+ done
+}
+fi
+fi
+
# __gitcomp accepts 1, 2, 3, or 4 arguments
# generates completion reply with compgen
__gitcomp ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}"
+ local cur
+ _get_comp_words_by_ref -n =: cur
if [ $# -gt 2 ]; then
cur="$3"
fi
done
}
-# __git_refs accepts 0 or 1 arguments (to pass to __gitdir)
+# __git_refs accepts 0, 1 (to pass to __gitdir), or 2 arguments
+# presence of 2nd argument means use the guess heuristic employed
+# by checkout for tracking branches
__git_refs ()
{
- local i is_hash=y dir="$(__gitdir "${1-}")"
- local cur="${COMP_WORDS[COMP_CWORD]}" format refs
+ local i is_hash=y dir="$(__gitdir "${1-}")" track="${2-}"
+ local cur format refs
+ _get_comp_words_by_ref -n =: cur
if [ -d "$dir" ]; then
case "$cur" in
refs|refs/*)
format="refname"
refs="${cur%/*}"
+ track=""
;;
*)
for i in HEAD FETCH_HEAD ORIG_HEAD MERGE_HEAD; do
esac
git --git-dir="$dir" for-each-ref --format="%($format)" \
$refs
+ if [ -n "$track" ]; then
+ # employ the heuristic used by git checkout
+ # Try to find a remote branch that matches the completion word
+ # but only output if the branch name is unique
+ local ref entry
+ git --git-dir="$dir" for-each-ref --shell --format="ref=%(refname:short)" \
+ "refs/remotes/" | \
+ while read entry; do
+ eval "$entry"
+ ref="${ref#*/}"
+ if [[ "$ref" == "$cur"* ]]; then
+ echo "$ref"
+ fi
+ done | uniq -u
+ fi
return
fi
for i in $(git ls-remote "$dir" 2>/dev/null); do
__git_complete_file ()
{
- local pfx ls ref cur="${COMP_WORDS[COMP_CWORD]}"
+ local pfx ls ref cur
+ _get_comp_words_by_ref -n =: cur
case "$cur" in
?*:*)
ref="${cur%%:*}"
__git_complete_revlist ()
{
- local pfx cur="${COMP_WORDS[COMP_CWORD]}"
+ local pfx cur
+ _get_comp_words_by_ref -n =: cur
case "$cur" in
*...*)
pfx="${cur%...*}..."
__git_complete_remote_or_refspec ()
{
- local cmd="${COMP_WORDS[1]}"
- local cur="${COMP_WORDS[COMP_CWORD]}"
+ local cur words cword
+ _get_comp_words_by_ref -n =: cur words cword
+ local cmd="${words[1]}"
local i c=2 remote="" pfx="" lhs=1 no_complete_refspec=0
- while [ $c -lt $COMP_CWORD ]; do
- i="${COMP_WORDS[c]}"
+ while [ $c -lt $cword ]; do
+ i="${words[c]}"
case "$i" in
--mirror) [ "$cmd" = "push" ] && no_complete_refspec=1 ;;
--all)
__git_complete_strategy ()
{
+ local cur prev
+ _get_comp_words_by_ref -n =: cur prev
__git_compute_merge_strategies
- case "${COMP_WORDS[COMP_CWORD-1]}" in
+ case "$prev" in
-s|--strategy)
__gitcomp "$__git_merge_strategies"
return 0
esac
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--strategy=*)
__gitcomp "$__git_merge_strategies" "" "${cur##--strategy=}"
quiltimport) : import;;
read-tree) : plumbing;;
receive-pack) : plumbing;;
- reflog) : plumbing;;
remote-*) : transport;;
repo-config) : deprecated;;
rerere) : plumbing;;
: ${__git_porcelain_commands:=$(__git_list_porcelain_commands)}
}
+__git_pretty_aliases ()
+{
+ local i IFS=$'\n'
+ for i in $(git --git-dir="$(__gitdir)" config --get-regexp "pretty\..*" 2>/dev/null); do
+ case "$i" in
+ pretty.*)
+ i="${i#pretty.}"
+ echo "${i/ */}"
+ ;;
+ esac
+ done
+}
+
__git_aliases ()
{
local i IFS=$'\n'
# __git_find_on_cmdline requires 1 argument
__git_find_on_cmdline ()
{
- local word subcommand c=1
-
- while [ $c -lt $COMP_CWORD ]; do
- word="${COMP_WORDS[c]}"
+ local word subcommand c=1 words cword
+ _get_comp_words_by_ref -n =: words cword
+ while [ $c -lt $cword ]; do
+ word="${words[c]}"
for subcommand in $1; do
if [ "$subcommand" = "$word" ]; then
echo "$subcommand"
__git_has_doubledash ()
{
- local c=1
- while [ $c -lt $COMP_CWORD ]; do
- if [ "--" = "${COMP_WORDS[c]}" ]; then
+ local c=1 words cword
+ _get_comp_words_by_ref -n =: words cword
+ while [ $c -lt $cword ]; do
+ if [ "--" = "${words[c]}" ]; then
return 0
fi
c=$((++c))
_git_am ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}" dir="$(__gitdir)"
+ local cur dir="$(__gitdir)"
+ _get_comp_words_by_ref -n =: cur
if [ -d "$dir"/rebase-apply ]; then
__gitcomp "--skip --continue --resolved --abort"
return
_git_apply ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}"
+ local cur
+ _get_comp_words_by_ref -n =: cur
case "$cur" in
--whitespace=*)
__gitcomp "$__git_whitespacelist" "" "${cur##--whitespace=}"
{
__git_has_doubledash && return
- local cur="${COMP_WORDS[COMP_CWORD]}"
+ local cur
+ _get_comp_words_by_ref -n =: cur
case "$cur" in
--*)
__gitcomp "
_git_archive ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}"
+ local cur
+ _get_comp_words_by_ref -n =: cur
case "$cur" in
--format=*)
__gitcomp "$(git archive --list)" "" "${cur##--format=}"
local subcommands="start bad good skip reset visualize replay log run"
local subcommand="$(__git_find_on_cmdline "$subcommands")"
if [ -z "$subcommand" ]; then
- __gitcomp "$subcommands"
+ if [ -f "$(__gitdir)"/BISECT_START ]; then
+ __gitcomp "$subcommands"
+ else
+ __gitcomp "replay start"
+ fi
return
fi
case "$subcommand" in
- bad|good|reset|skip)
+ bad|good|reset|skip|start)
__gitcomp "$(__git_refs)"
;;
*)
_git_branch ()
{
- local i c=1 only_local_ref="n" has_r="n"
+ local i c=1 only_local_ref="n" has_r="n" cur words cword
- while [ $c -lt $COMP_CWORD ]; do
- i="${COMP_WORDS[c]}"
+ _get_comp_words_by_ref -n =: cur words cword
+ while [ $c -lt $cword ]; do
+ i="${words[c]}"
case "$i" in
-d|-m) only_local_ref="y" ;;
-r) has_r="y" ;;
c=$((++c))
done
- case "${COMP_WORDS[COMP_CWORD]}" in
+ case "$cur" in
--*)
__gitcomp "
--color --no-color --verbose --abbrev= --no-abbrev
_git_bundle ()
{
- local cmd="${COMP_WORDS[2]}"
- case "$COMP_CWORD" in
+ local words cword
+ _get_comp_words_by_ref -n =: words cword
+ local cmd="${words[2]}"
+ case "$cword" in
2)
__gitcomp "create list-heads verify unbundle"
;;
{
__git_has_doubledash && return
- local cur="${COMP_WORDS[COMP_CWORD]}"
+ local cur
+ _get_comp_words_by_ref -n =: cur
case "$cur" in
--conflict=*)
__gitcomp "diff3 merge" "" "${cur##--conflict=}"
"
;;
*)
- __gitcomp "$(__git_refs)"
+ # check if --track, --no-track, or --no-guess was specified
+ # if so, disable DWIM mode
+ local flags="--track --no-track --no-guess" track=1
+ if [ -n "$(__git_find_on_cmdline "$flags")" ]; then
+ track=''
+ fi
+ __gitcomp "$(__git_refs '' $track)"
;;
esac
}
_git_cherry_pick ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}"
+ local cur
+ _get_comp_words_by_ref -n =: cur
case "$cur" in
--*)
__gitcomp "--edit --no-commit"
{
__git_has_doubledash && return
- local cur="${COMP_WORDS[COMP_CWORD]}"
+ local cur
+ _get_comp_words_by_ref -n =: cur
case "$cur" in
--*)
__gitcomp "--dry-run --quiet"
_git_clone ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}"
+ local cur
+ _get_comp_words_by_ref -n =: cur
case "$cur" in
--*)
__gitcomp "
{
__git_has_doubledash && return
- local cur="${COMP_WORDS[COMP_CWORD]}"
+ local cur
+ _get_comp_words_by_ref -n =: cur
case "$cur" in
--cleanup=*)
__gitcomp "default strip verbatim whitespace
_git_describe ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}"
+ local cur
+ _get_comp_words_by_ref -n =: cur
case "$cur" in
--*)
__gitcomp "
{
__git_has_doubledash && return
- local cur="${COMP_WORDS[COMP_CWORD]}"
+ local cur
+ _get_comp_words_by_ref -n =: cur
case "$cur" in
--*)
__gitcomp "--cached --staged --pickaxe-all --pickaxe-regex
{
__git_has_doubledash && return
- local cur="${COMP_WORDS[COMP_CWORD]}"
+ local cur
+ _get_comp_words_by_ref -n =: cur
case "$cur" in
--tool=*)
__gitcomp "$__git_mergetools_common kompare" "" "${cur##--tool=}"
_git_fetch ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}"
+ local cur
+ _get_comp_words_by_ref -n =: cur
case "$cur" in
--*)
__gitcomp "$__git_fetch_options"
_git_format_patch ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}"
+ local cur
+ _get_comp_words_by_ref -n =: cur
case "$cur" in
--thread=*)
__gitcomp "
_git_fsck ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}"
+ local cur
+ _get_comp_words_by_ref -n =: cur
case "$cur" in
--*)
__gitcomp "
_git_gc ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}"
+ local cur
+ _get_comp_words_by_ref -n =: cur
case "$cur" in
--*)
__gitcomp "--prune --aggressive"
{
__git_has_doubledash && return
- local cur="${COMP_WORDS[COMP_CWORD]}"
+ local cur
+ _get_comp_words_by_ref -n =: cur
case "$cur" in
--*)
__gitcomp "
_git_help ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}"
+ local cur
+ _get_comp_words_by_ref -n =: cur
case "$cur" in
--*)
__gitcomp "--all --info --man --web"
_git_init ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}"
+ local cur
+ _get_comp_words_by_ref -n =: cur
case "$cur" in
--shared=*)
__gitcomp "
{
__git_has_doubledash && return
- local cur="${COMP_WORDS[COMP_CWORD]}"
+ local cur
+ _get_comp_words_by_ref -n =: cur
case "$cur" in
--*)
__gitcomp "--cached --deleted --modified --others --ignored
{
__git_has_doubledash && return
- local cur="${COMP_WORDS[COMP_CWORD]}"
local g="$(git rev-parse --git-dir 2>/dev/null)"
local merge=""
if [ -f "$g/MERGE_HEAD" ]; then
merge="--merge"
fi
+ local cur
+ _get_comp_words_by_ref -n =: cur
case "$cur" in
--pretty=*)
- __gitcomp "$__git_log_pretty_formats
+ __gitcomp "$__git_log_pretty_formats $(__git_pretty_aliases)
" "" "${cur##--pretty=}"
return
;;
--format=*)
- __gitcomp "$__git_log_pretty_formats
+ __gitcomp "$__git_log_pretty_formats $(__git_pretty_aliases)
" "" "${cur##--format=}"
return
;;
{
__git_complete_strategy && return
- local cur="${COMP_WORDS[COMP_CWORD]}"
+ local cur
+ _get_comp_words_by_ref -n =: cur
case "$cur" in
--*)
__gitcomp "$__git_merge_options"
_git_mergetool ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}"
+ local cur
+ _get_comp_words_by_ref -n =: cur
case "$cur" in
--tool=*)
__gitcomp "$__git_mergetools_common tortoisemerge" "" "${cur##--tool=}"
_git_mv ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}"
+ local cur
+ _get_comp_words_by_ref -n =: cur
case "$cur" in
--*)
__gitcomp "--dry-run"
_git_notes ()
{
- local subcommands="edit show"
- if [ -z "$(__git_find_on_cmdline "$subcommands")" ]; then
- __gitcomp "$subcommands"
- return
- fi
+ local subcommands='add append copy edit list prune remove show'
+ local subcommand="$(__git_find_on_cmdline "$subcommands")"
+ local cur words cword
+ _get_comp_words_by_ref -n =: cur words cword
- case "${COMP_WORDS[COMP_CWORD-1]}" in
- -m|-F)
- COMPREPLY=()
+ case "$subcommand,$cur" in
+ ,--*)
+ __gitcomp '--ref'
+ ;;
+ ,*)
+ case "${words[cword-1]}" in
+ --ref)
+ __gitcomp "$(__git_refs)"
+ ;;
+ *)
+ __gitcomp "$subcommands --ref"
+ ;;
+ esac
+ ;;
+ add,--reuse-message=*|append,--reuse-message=*)
+ __gitcomp "$(__git_refs)" "" "${cur##--reuse-message=}"
+ ;;
+ add,--reedit-message=*|append,--reedit-message=*)
+ __gitcomp "$(__git_refs)" "" "${cur##--reedit-message=}"
+ ;;
+ add,--*|append,--*)
+ __gitcomp '--file= --message= --reedit-message=
+ --reuse-message='
+ ;;
+ copy,--*)
+ __gitcomp '--stdin'
+ ;;
+ prune,--*)
+ __gitcomp '--dry-run --verbose'
+ ;;
+ prune,*)
;;
*)
- __gitcomp "$(__git_refs)"
+ case "${words[cword-1]}" in
+ -m|-F)
+ ;;
+ *)
+ __gitcomp "$(__git_refs)"
+ ;;
+ esac
;;
esac
}
{
__git_complete_strategy && return
- local cur="${COMP_WORDS[COMP_CWORD]}"
+ local cur
+ _get_comp_words_by_ref -n =: cur
case "$cur" in
--*)
__gitcomp "
_git_push ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}"
- case "${COMP_WORDS[COMP_CWORD-1]}" in
+ local cur prev
+ _get_comp_words_by_ref -n =: cur prev
+ case "$prev" in
--repo)
__gitcomp "$(__git_remotes)"
return
_git_rebase ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}" dir="$(__gitdir)"
+ local dir="$(__gitdir)"
+ local cur
+ _get_comp_words_by_ref -n =: cur
if [ -d "$dir"/rebase-apply ] || [ -d "$dir"/rebase-merge ]; then
__gitcomp "--continue --skip --abort"
return
__gitcomp "$(__git_refs)"
}
+_git_reflog ()
+{
+ local subcommands="show delete expire"
+ local subcommand="$(__git_find_on_cmdline "$subcommands")"
+
+ if [ -z "$subcommand" ]; then
+ __gitcomp "$subcommands"
+ else
+ __gitcomp "$(__git_refs)"
+ fi
+}
+
__git_send_email_confirm_options="always never auto cc compose"
__git_send_email_suppresscc_options="author self cc bodycc sob cccmd body all"
_git_send_email ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}"
+ local cur
+ _get_comp_words_by_ref -n =: cur
case "$cur" in
--confirm=*)
__gitcomp "
__git_config_get_set_variables ()
{
- local prevword word config_file= c=$COMP_CWORD
+ local words cword
+ _get_comp_words_by_ref -n =: words cword
+ local prevword word config_file= c=$cword
while [ $c -gt 1 ]; do
- word="${COMP_WORDS[c]}"
+ word="${words[c]}"
case "$word" in
--global|--system|--file=*)
config_file="$word"
_git_config ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}"
- local prv="${COMP_WORDS[COMP_CWORD-1]}"
- case "$prv" in
+ local cur prev
+ _get_comp_words_by_ref -n =: cur prev
+ case "$prev" in
branch.*.remote)
__gitcomp "$(__git_remotes)"
return
return
;;
remote.*.fetch)
- local remote="${prv#remote.}"
+ local remote="${prev#remote.}"
remote="${remote%.fetch}"
__gitcomp "$(__git_refs_remotes "$remote")"
return
;;
remote.*.push)
- local remote="${prv#remote.}"
+ local remote="${prev#remote.}"
remote="${remote%.push}"
__gitcomp "$(git --git-dir="$(__gitdir)" \
for-each-ref --format='%(refname):%(refname)' \
;;
esac
__gitcomp "
- add.ignore-errors
+ add.ignoreErrors
+ advice.commitBeforeMerge
+ advice.detachedHead
+ advice.implicitIdentity
+ advice.pushNonFastForward
+ advice.resolveConflict
+ advice.statusHints
alias.
+ am.keepcr
apply.ignorewhitespace
apply.whitespace
branch.autosetupmerge
branch.autosetuprebase
+ browser.
clean.requireForce
color.branch
color.branch.current
color.branch.local
color.branch.plain
color.branch.remote
+ color.decorate.HEAD
+ color.decorate.branch
+ color.decorate.remoteBranch
+ color.decorate.stash
+ color.decorate.tag
color.diff
color.diff.commit
color.diff.frag
+ color.diff.func
color.diff.meta
color.diff.new
color.diff.old
color.diff.plain
color.diff.whitespace
color.grep
- color.grep.external
+ color.grep.context
+ color.grep.filename
+ color.grep.function
+ color.grep.linenumber
color.grep.match
+ color.grep.selected
+ color.grep.separator
color.interactive
+ color.interactive.error
color.interactive.header
color.interactive.help
color.interactive.prompt
color.status.untracked
color.status.updated
color.ui
+ commit.status
commit.template
+ core.abbrevguard
+ core.askpass
+ core.attributesfile
core.autocrlf
core.bare
+ core.bigFileThreshold
core.compression
core.createObject
core.deltaBaseCacheLimit
core.editor
+ core.eol
core.excludesfile
core.fileMode
core.fsyncobjectfiles
core.gitProxy
core.ignoreCygwinFSTricks
core.ignoreStat
+ core.ignorecase
core.logAllRefUpdates
core.loosecompression
+ core.notesRef
core.packedGitLimit
core.packedGitWindowSize
core.pager
core.repositoryFormatVersion
core.safecrlf
core.sharedRepository
+ core.sparseCheckout
core.symlinks
core.trustctime
core.warnAmbiguousRefs
core.worktree
diff.autorefreshindex
diff.external
+ diff.ignoreSubmodules
diff.mnemonicprefix
+ diff.noprefix
diff.renameLimit
- diff.renameLimit.
diff.renames
diff.suppressBlankEmpty
diff.tool
diff.wordRegex
difftool.
difftool.prompt
+ fetch.recurseSubmodules
fetch.unpackLimit
format.attach
format.cc
format.subjectprefix
format.suffix
format.thread
+ format.to
+ gc.
gc.aggressiveWindow
gc.auto
gc.autopacklimit
http.lowSpeedLimit
http.lowSpeedTime
http.maxRequests
+ http.minSessions
http.noEPSV
+ http.postBuffer
http.proxy
http.sslCAInfo
http.sslCAPath
http.sslCert
+ http.sslCertPasswordProtected
http.sslKey
http.sslVerify
+ http.useragent
i18n.commitEncoding
i18n.logOutputEncoding
+ imap.authMethod
imap.folder
imap.host
imap.pass
imap.sslverify
imap.tunnel
imap.user
+ init.templatedir
instaweb.browser
instaweb.httpd
instaweb.local
instaweb.port
interactive.singlekey
log.date
+ log.decorate
log.showroot
mailmap.file
man.
man.viewer
+ merge.
merge.conflictstyle
merge.log
merge.renameLimit
+ merge.renormalize
merge.stat
merge.tool
merge.verbosity
mergetool.
mergetool.keepBackup
+ mergetool.keepTemporaries
mergetool.prompt
+ notes.displayRef
+ notes.rewrite.
+ notes.rewrite.amend
+ notes.rewrite.rebase
+ notes.rewriteMode
+ notes.rewriteRef
pack.compression
pack.deltaCacheLimit
pack.deltaCacheSize
pack.window
pack.windowMemory
pager.
+ pretty.
pull.octopus
pull.twohead
push.default
+ rebase.autosquash
rebase.stat
+ receive.autogc
receive.denyCurrentBranch
+ receive.denyDeleteCurrent
receive.denyDeletes
receive.denyNonFastForwards
receive.fsckObjects
receive.unpackLimit
+ receive.updateserverinfo
+ remotes.
repack.usedeltabaseoffset
rerere.autoupdate
rerere.enabled
+ sendemail.
sendemail.aliasesfile
- sendemail.aliasesfiletype
+ sendemail.aliasfiletype
sendemail.bcc
sendemail.cc
sendemail.cccmd
sendemail.chainreplyto
sendemail.confirm
sendemail.envelopesender
+ sendemail.from
+ sendemail.identity
sendemail.multiedit
sendemail.signedoffbycc
+ sendemail.smtpdomain
sendemail.smtpencryption
sendemail.smtppass
sendemail.smtpserver
+ sendemail.smtpserveroption
sendemail.smtpserverport
sendemail.smtpuser
sendemail.suppresscc
showbranch.default
status.relativePaths
status.showUntrackedFiles
+ status.submodulesummary
+ submodule.
tar.umask
transfer.unpackLimit
url.
{
__git_has_doubledash && return
- local cur="${COMP_WORDS[COMP_CWORD]}"
+ local cur
+ _get_comp_words_by_ref -n =: cur
case "$cur" in
--*)
__gitcomp "--merge --mixed --hard --soft --patch"
_git_revert ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}"
+ local cur
+ _get_comp_words_by_ref -n =: cur
case "$cur" in
--*)
__gitcomp "--edit --mainline --no-edit --no-commit --signoff"
{
__git_has_doubledash && return
- local cur="${COMP_WORDS[COMP_CWORD]}"
+ local cur
+ _get_comp_words_by_ref -n =: cur
case "$cur" in
--*)
__gitcomp "--cached --dry-run --ignore-unmatch --quiet"
{
__git_has_doubledash && return
- local cur="${COMP_WORDS[COMP_CWORD]}"
+ local cur
+ _get_comp_words_by_ref -n =: cur
case "$cur" in
--*)
__gitcomp "
{
__git_has_doubledash && return
- local cur="${COMP_WORDS[COMP_CWORD]}"
+ local cur
+ _get_comp_words_by_ref -n =: cur
case "$cur" in
--pretty=*)
- __gitcomp "$__git_log_pretty_formats
+ __gitcomp "$__git_log_pretty_formats $(__git_pretty_aliases)
" "" "${cur##--pretty=}"
return
;;
--format=*)
- __gitcomp "$__git_log_pretty_formats
+ __gitcomp "$__git_log_pretty_formats $(__git_pretty_aliases)
" "" "${cur##--format=}"
return
;;
_git_show_branch ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}"
+ local cur
+ _get_comp_words_by_ref -n =: cur
case "$cur" in
--*)
__gitcomp "
_git_stash ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}"
+ local cur
+ _get_comp_words_by_ref -n =: cur
local save_opts='--keep-index --no-keep-index --quiet --patch'
local subcommands='save list show apply clear drop pop create branch'
local subcommand="$(__git_find_on_cmdline "$subcommands")"
local subcommands="add status init update summary foreach sync"
if [ -z "$(__git_find_on_cmdline "$subcommands")" ]; then
- local cur="${COMP_WORDS[COMP_CWORD]}"
+ local cur
+ _get_comp_words_by_ref -n =: cur
case "$cur" in
--*)
__gitcomp "--quiet --cached"
--edit --rmdir --find-copies-harder --copy-similarity=
"
- local cur="${COMP_WORDS[COMP_CWORD]}"
+ local cur
+ _get_comp_words_by_ref -n =: cur
case "$subcommand,$cur" in
fetch,--*)
__gitcomp "--revision= --fetch-all $fc_opts"
_git_tag ()
{
local i c=1 f=0
- while [ $c -lt $COMP_CWORD ]; do
- i="${COMP_WORDS[c]}"
+ local words cword prev
+ _get_comp_words_by_ref -n =: words cword prev
+ while [ $c -lt $cword ]; do
+ i="${words[c]}"
case "$i" in
-d|-v)
__gitcomp "$(__git_tags)"
c=$((++c))
done
- case "${COMP_WORDS[COMP_CWORD-1]}" in
+ case "$prev" in
-m|-F)
COMPREPLY=()
;;
{
local i c=1 command __git_dir
- while [ $c -lt $COMP_CWORD ]; do
- i="${COMP_WORDS[c]}"
+ if [[ -n ${ZSH_VERSION-} ]]; then
+ emulate -L bash
+ setopt KSH_TYPESET
+ fi
+
+ local cur words cword
+ _get_comp_words_by_ref -n =: cur words cword
+ while [ $c -lt $cword ]; do
+ i="${words[c]}"
case "$i" in
--git-dir=*) __git_dir="${i#--git-dir=}" ;;
--bare) __git_dir="." ;;
done
if [ -z "$command" ]; then
- case "${COMP_WORDS[COMP_CWORD]}" in
+ case "$cur" in
--*) __gitcomp "
--paginate
--no-pager
fi
local completion_func="_git_${command//-/_}"
- declare -F $completion_func >/dev/null && $completion_func && return
+ declare -f $completion_func >/dev/null && $completion_func && return
local expansion=$(__git_aliased_command "$command")
if [ -n "$expansion" ]; then
completion_func="_git_${expansion//-/_}"
- declare -F $completion_func >/dev/null && $completion_func
+ declare -f $completion_func >/dev/null && $completion_func
fi
}
_gitk ()
{
+ if [[ -n ${ZSH_VERSION-} ]]; then
+ emulate -L bash
+ setopt KSH_TYPESET
+ fi
+
__git_has_doubledash && return
- local cur="${COMP_WORDS[COMP_CWORD]}"
+ local cur
local g="$(__gitdir)"
local merge=""
if [ -f "$g/MERGE_HEAD" ]; then
merge="--merge"
fi
+ _get_comp_words_by_ref -n =: cur
case "$cur" in
--*)
__gitcomp "
complete -o bashdefault -o default -o nospace -F _git git.exe 2>/dev/null \
|| complete -o default -o nospace -F _git git.exe
fi
+
+if [[ -n ${ZSH_VERSION-} ]]; then
+ shopt () {
+ local option
+ if [ $# -ne 2 ]; then
+ echo "USAGE: $0 (-q|-s|-u) <option>" >&2
+ return 1
+ fi
+ case "$2" in
+ nullglob)
+ option="$2"
+ ;;
+ *)
+ echo "$0: invalid option: $2" >&2
+ return 1
+ esac
+ case "$1" in
+ -q) setopt | grep -q "$option" ;;
+ -u) unsetopt "$option" ;;
+ -s) setopt "$option" ;;
+ *)
+ echo "$0: invalid flag: $1" >&2
+ return 1
+ esac
+ }
+fi
index cd10dbcbc90ee155e05cffb49e88defcf35c5a59..3140e405fa130a0fa20b2f6ee4171da70935de66 100644 (file)
what = remote_name + 10;
}
else if (!strncmp(remote_name, "refs/remotes/", 13)) {
- kind = "remote branch";
+ kind = "remote-tracking branch";
what = remote_name + 13;
}
else {
index 60a05a8b978345224c0313edb8060f5a7c2ccb54..6bf155cbdb61498bdaf84712a2dea6fb05361ce2 100755 (executable)
cd_to_toplevel
no_commit=
+xopt=
while case "$#" in 0) break ;; esac
do
case "$1" in
-x|--i-really-want-to-expose-my-private-commit-object-name)
replay=
;;
+ -X?*)
+ xopt="$xopt$(git rev-parse --sq-quote "--${1#-X}")"
+ ;;
+ --strategy-option=*)
+ xopt="$xopt$(git rev-parse --sq-quote "--${1#--strategy-option=}")"
+ ;;
+ -X|--strategy-option)
+ shift
+ xopt="$xopt$(git rev-parse --sq-quote "--$1")"
+ ;;
-*)
usage
;;
# and $prev on top of us (when reverting), or the change between
# $prev and $commit on top of us (when cherry-picking or replaying).
-git-merge-recursive $base -- $head $next &&
+eval "git merge-recursive $xopt $base -- $head $next" &&
result=$(git-write-tree 2>/dev/null) || {
mv -f .msg "$GIT_DIR/MERGE_MSG"
{
index ead4c04d3f7ca64ed55ff605d7ee8087189a2226..b09ff8f12f7e5b5b6faeaf857d7c61973de8590e 100755 (executable)
unless(-d $git_dir) {
system("git init");
die "Cannot init the GIT db at $git_tree: $?\n" if $?;
- system("git read-tree");
+ system("git read-tree --empty");
die "Cannot init an empty tree: $?\n" if $?;
$last_branch = $opt_o;
index 04ce7e3b020f489d59fe103970fc989d7b351127..a4f440d11696caa6ee4756108bec78a85d4b04f7 100755 (executable)
self.options = [
optparse.make_option("--verbose", dest="verbose", action="store_true"),
optparse.make_option("--origin", dest="origin"),
- optparse.make_option("-M", dest="detectRename", action="store_true"),
+ optparse.make_option("-M", dest="detectRenames", action="store_true"),
]
self.description = "Submit changes from git to the perforce depot."
self.usage += " [name of git branch to submit into perforce depot]"
self.interactive = True
self.origin = ""
- self.detectRename = False
+ self.detectRenames = False
self.verbose = False
self.isWindows = (platform.system() == "Windows")
def applyCommit(self, id):
print "Applying %s" % (read_pipe("git log --max-count=1 --pretty=oneline %s" % id))
- diffOpts = ("", "-M")[self.detectRename]
+
+ if not self.detectRenames:
+ # If not explicitly set check the config variable
+ self.detectRenames = gitConfig("git-p4.detectRenames").lower() == "true"
+
+ if self.detectRenames:
+ diffOpts = "-M"
+ else:
+ diffOpts = ""
+
+ if gitConfig("git-p4.detectCopies").lower() == "true":
+ diffOpts += " -C"
+
+ if gitConfig("git-p4.detectCopiesHarder").lower() == "true":
+ diffOpts += " --find-copies-harder"
+
diff = read_pipe_lines("git diff-tree -r %s \"%s^\" \"%s\"" % (diffOpts, id, id))
filesToAdd = set()
filesToDelete = set()
filesToDelete.add(path)
if path in filesToAdd:
filesToAdd.remove(path)
+ elif modifier == "C":
+ src, dest = diff['src'], diff['dst']
+ p4_system("integrate -Dt \"%s\" \"%s\"" % (src, dest))
+ if diff['src_sha1'] != diff['dst_sha1']:
+ p4_system("edit \"%s\"" % (dest))
+ if isModeExecChanged(diff['src_mode'], diff['dst_mode']):
+ p4_system("edit \"%s\"" % (dest))
+ filesToChangeExecBit[dest] = diff['dst_mode']
+ os.unlink(dest)
+ editedFiles.add(dest)
elif modifier == "R":
src, dest = diff['src'], diff['dst']
p4_system("integrate -Dt \"%s\" \"%s\"" % (src, dest))
- p4_system("edit \"%s\"" % (dest))
+ if diff['src_sha1'] != diff['dst_sha1']:
+ p4_system("edit \"%s\"" % (dest))
if isModeExecChanged(diff['src_mode'], diff['dst_mode']):
+ p4_system("edit \"%s\"" % (dest))
filesToChangeExecBit[dest] = diff['dst_mode']
os.unlink(dest)
editedFiles.add(dest)
return True
class P4Sync(Command):
+ delete_actions = ( "delete", "move/delete", "purge" )
+
def __init__(self):
Command.__init__(self)
self.options = [
if gitConfig("git-p4.syncFromOrigin") == "false":
self.syncWithOrigin = False
+ #
+ # P4 wildcards are not allowed in filenames. P4 complains
+ # if you simply add them, but you can force it with "-f", in
+ # which case it translates them into %xx encoding internally.
+ # Search for and fix just these four characters. Do % last so
+ # that fixing it does not inadvertently create new %-escapes.
+ #
+ def wildcard_decode(self, path):
+ # Cannot have * in a filename in windows; untested as to
+ # what p4 would do in such a case.
+ if not self.isWindows:
+ path = path.replace("%2A", "*")
+ path = path.replace("%23", "#") \
+ .replace("%40", "@") \
+ .replace("%25", "%")
+ return path
+
def extractFilesFromCommit(self, commit):
self.cloneExclude = [re.sub(r"\.\.\.$", "", path)
for path in self.cloneExclude]
return files
def stripRepoPath(self, path, prefixes):
+ if self.useClientSpec:
+
+ # if using the client spec, we use the output directory
+ # specified in the client. For example, a view
+ # //depot/foo/branch/... //client/branch/foo/...
+ # will end up putting all foo/branch files into
+ # branch/foo/
+ for val in self.clientSpecDirs:
+ if path.startswith(val[0]):
+ # replace the depot path with the client path
+ path = path.replace(val[0], val[1][1])
+ # now strip out the client (//client/...)
+ path = re.sub("^(//[^/]+/)", '', path)
+ # the rest is all path
+ return path
+
if self.keepRepoPath:
prefixes = [re.sub("^(//[^/]+/).*", r'\1', prefixes[0])]
return
relPath = self.stripRepoPath(file['depotFile'], self.branchPrefixes)
+ relPath = self.wildcard_decode(relPath)
if verbose:
sys.stderr.write("%s\n" % relPath)
includeFile = True
for val in self.clientSpecDirs:
if f['path'].startswith(val[0]):
- if val[1] <= 0:
+ if val[1][0] <= 0:
includeFile = False
break
if includeFile:
filesForCommit.append(f)
- if f['action'] not in ('delete', 'move/delete', 'purge'):
- filesToRead.append(f)
- else:
+ if f['action'] in self.delete_actions:
filesToDelete.append(f)
+ else:
+ filesToRead.append(f)
# deleted files...
for f in filesToDelete:
cleanedFiles = {}
for info in files:
- if info["action"] in ("delete", "purge"):
+ if info["action"] in self.delete_actions:
continue
cleanedFiles[info["depotFile"]] = info["rev"]
print "Doing initial import of %s from revision %s into %s" % (' '.join(self.depotPaths), revision, self.branch)
details = { "user" : "git perforce import user", "time" : int(time.time()) }
- details["desc"] = ("Initial import of %s from the state at revision %s"
+ details["desc"] = ("Initial import of %s from the state at revision %s\n"
% (' '.join(self.depotPaths), revision))
details["change"] = revision
newestRevision = 0
% (p, revision)
for p in self.depotPaths])):
- if info['code'] == 'error':
+ if 'code' in info and info['code'] == 'error':
sys.stderr.write("p4 returned an error: %s\n"
% info['data'])
+ if info['data'].find("must refer to client") >= 0:
+ sys.stderr.write("This particular p4 error is misleading.\n")
+ sys.stderr.write("Perhaps the depot path was misspelled.\n");
+ sys.stderr.write("Depot path: %s\n" % " ".join(self.depotPaths))
+ sys.exit(1)
+ if 'p4ExitCode' in info:
+ sys.stderr.write("p4 exitcode: %s\n" % info['p4ExitCode'])
sys.exit(1)
if change > newestRevision:
newestRevision = change
- if info["action"] in ("delete", "purge"):
+ if info["action"] in self.delete_actions:
# don't increase the file cnt, otherwise details["depotFile123"] will have gaps!
#fileCnt = fileCnt + 1
continue
for entry in specList:
for k,v in entry.iteritems():
if k.startswith("View"):
+
+ # p4 has these %%1 to %%9 arguments in specs to
+ # reorder paths; which we can't handle (yet :)
+ if re.match('%%\d', v) != None:
+ print "Sorry, can't handle %%n arguments in client specs"
+ sys.exit(1)
+
if v.startswith('"'):
start = 1
else:
start = 0
index = v.find("...")
+
+ # save the "client view"; i.e the RHS of the view
+ # line that tells the client where to put the
+ # files for this view.
+ cv = v[index+3:].strip() # +3 to remove previous '...'
+
+ # if the client view doesn't end with a
+ # ... wildcard, then we're going to mess up the
+ # output directory, so fail gracefully.
+ if not cv.endswith('...'):
+ print 'Sorry, client view in "%s" needs to end with wildcard' % (k)
+ sys.exit(1)
+ cv=cv[:-3]
+
+ # now save the view; +index means included, -index
+ # means it should be filtered out.
v = v[start:index]
if v.startswith("-"):
v = v[1:]
- temp[v] = -len(v)
+ include = -len(v)
else:
- temp[v] = len(v)
+ include = len(v)
+
+ temp[v] = (include, cv)
+
self.clientSpecDirs = temp.items()
- self.clientSpecDirs.sort( lambda x, y: abs( y[1] ) - abs( x[1] ) )
+ self.clientSpecDirs.sort( lambda x, y: abs( y[1][0] ) - abs( x[1][0] ) )
def run(self, args):
self.depotPaths = []
changes.sort()
else:
+ if not self.p4BranchesInGit:
+ die("No remote p4 branches. Perhaps you never did \"git p4 clone\" in here.");
if self.verbose:
print "Getting p4 changes for %s...%s" % (', '.join(self.depotPaths),
self.changeRange)
help="where to leave result of the clone"),
optparse.make_option("-/", dest="cloneExclude",
action="append", type="string",
- help="exclude depot path")
+ help="exclude depot path"),
+ optparse.make_option("--bare", dest="cloneBare",
+ action="store_true", default=False),
]
self.cloneDestination = None
self.needsGit = False
+ self.cloneBare = False
# This is required for the "append" cloneExclude action
def ensure_value(self, attr, value):
self.cloneDestination = self.defaultDestination(args)
print "Importing from %s into %s" % (', '.join(depotPaths), self.cloneDestination)
+
if not os.path.exists(self.cloneDestination):
os.makedirs(self.cloneDestination)
chdir(self.cloneDestination)
- system("git init")
- self.gitdir = os.getcwd() + "/.git"
+
+ init_cmd = [ "git", "init" ]
+ if self.cloneBare:
+ init_cmd.append("--bare")
+ subprocess.check_call(init_cmd)
+
if not P4Sync.run(self, depotPaths):
return False
if self.branch != "master":
masterbranch = "refs/heads/p4/master"
if gitBranchExists(masterbranch):
system("git branch master %s" % masterbranch)
- system("git checkout -f")
+ if not self.cloneBare:
+ system("git checkout -f")
else:
print "Could not detect main branch. No checkout/master branch created."
index 49b335921a3871d82a2c0110170a6e66d71561ee..e09da445b692db2a1cbdba7ee49456de6e2571e9 100644 (file)
git config [--global] git-p4.useclientspec false
+The P4CLIENT environment variable should be correctly set for p4 to be
+able to find the relevant client. This client spec will be used to
+both filter the files cloned by git and set the directory layout as
+specified in the client (this implies --keep-path style semantics).
+
Implementation Details...
=========================
diff --git a/contrib/git-shell-commands/README b/contrib/git-shell-commands/README
--- /dev/null
@@ -0,0 +1,18 @@
+Sample programs callable through git-shell. Place a directory named
+'git-shell-commands' in the home directory of a user whose shell is
+git-shell. Then anyone logging in as that user will be able to run
+executables in the 'git-shell-commands' directory.
+
+Provided commands:
+
+help: Prints out the names of available commands. When run
+interactively, git-shell will automatically run 'help' on startup,
+provided it exists.
+
+list: Displays any bare repository whose name ends with ".git" under
+user's home directory. No other git repositories are visible,
+although they might be clonable through git-shell. 'list' is designed
+to minimize the number of calls to git that must be made in finding
+available repositories; if your setup has additional repositories that
+should be user-discoverable, you may wish to modify 'list'
+accordingly.
diff --git a/contrib/git-shell-commands/help b/contrib/git-shell-commands/help
--- /dev/null
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+if tty -s
+then
+ echo "Run 'help' for help, or 'exit' to leave. Available commands:"
+else
+ echo "Run 'help' for help. Available commands:"
+fi
+
+cd "$(dirname "$0")"
+
+for cmd in *
+do
+ case "$cmd" in
+ help) ;;
+ *) [ -f "$cmd" ] && [ -x "$cmd" ] && echo "$cmd" ;;
+ esac
+done
diff --git a/contrib/git-shell-commands/list b/contrib/git-shell-commands/list
--- /dev/null
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+print_if_bare_repo='
+ if "$(git --git-dir="$1" rev-parse --is-bare-repository)" = true
+ then
+ printf "%s\n" "${1#./}"
+ fi
+'
+
+find -type d -name "*.git" -exec sh -c "$print_if_bare_repo" -- \{} \; -prune 2>/dev/null
index f99ea9585014face001849aeeefa5f20e79bc1e2..21989fc6ab84c0503cdc9418673d9d0bf06f9d49 100755 (executable)
exit 1
fi
-projectdesc=$(sed -ne '1p' "$GIT_DIR/description")
+projectdesc=$(sed -ne '1p' "$GIT_DIR/description" 2>/dev/null)
# Check if the description is unchanged from it's default, and shorten it to
# a more manageable length if it is
if expr "$projectdesc" : "Unnamed repository.*$" >/dev/null
index a2677b03e0ff8347d13a5d56f4fa2e1aba18824a..35db24f5eae600a973391915ceb61c4635941aec 100644 (file)
--- a/contrib/svn-fe/svn-fe.c
+++ b/contrib/svn-fe/svn-fe.c
int main(int argc, char **argv)
{
- svndump_init(NULL);
+ if (svndump_init(NULL))
+ return 1;
svndump_read((argc > 1) ? argv[1] : NULL);
svndump_deinit();
svndump_reset();
index 35f84bd9e7577b58bc9905c8b46806bfb37963ff..cd075b96c54419fdaa25c56b24133dd080f440ca 100644 (file)
repositories can be mirrored on local disk using the `svnsync`
command.
+Note: this tool is very young. The details of its commandline
+interface may change in backward incompatible ways.
+
INPUT FORMAT
------------
Subversion's repository dump format is documented in full in
diff --git a/convert.c b/convert.c
index 01de9a84c21b31a0120065a32a386f27321cdf7b..d5aebed48df3387e966a23b6ee460dc2244f728e 100644 (file)
--- a/convert.c
+++ b/convert.c
#include "cache.h"
#include "attr.h"
#include "run-command.h"
+#include "quote.h"
/*
* convert.c - convert a file when checking it out and checking it in.
const char *src;
unsigned long size;
const char *cmd;
+ const char *path;
};
static int filter_buffer(int in, int out, void *data)
int write_err, status;
const char *argv[] = { NULL, NULL };
- argv[0] = params->cmd;
+ /* apply % substitution to cmd */
+ struct strbuf cmd = STRBUF_INIT;
+ struct strbuf path = STRBUF_INIT;
+ struct strbuf_expand_dict_entry dict[] = {
+ { "f", NULL, },
+ { NULL, NULL, },
+ };
+
+ /* quote the path to preserve spaces, etc. */
+ sq_quote_buf(&path, params->path);
+ dict[0].value = path.buf;
+
+ /* expand all %f with the quoted path */
+ strbuf_expand(&cmd, params->cmd, strbuf_expand_dict_cb, &dict);
+ strbuf_release(&path);
+
+ argv[0] = cmd.buf;
memset(&child_process, 0, sizeof(child_process));
child_process.argv = argv;
status = finish_command(&child_process);
if (status)
error("external filter %s failed %d", params->cmd, status);
+
+ strbuf_release(&cmd);
return (write_err || status);
}
params.src = src;
params.size = len;
params.cmd = cmd;
+ params.path = path;
fflush(NULL);
if (start_async(&async))
diff --git a/daemon.c b/daemon.c
index 9326d3a1fa05f4878a21b58a623d07428f7230fe..347fd0c52b4cd797f5dafffb6885324f7c7f0274 100644 (file)
--- a/daemon.c
+++ b/daemon.c
#include "exec_cmd.h"
#include "run-command.h"
#include "strbuf.h"
-
-#include <syslog.h>
+#include "string-list.h"
#ifndef HOST_NAME_MAX
#define HOST_NAME_MAX 256
#define NI_MAXSERV 32
#endif
+#ifdef NO_INITGROUPS
+#define initgroups(x, y) (0) /* nothing */
+#endif
+
static int log_syslog;
static int verbose;
static int reuseaddr;
" [--strict-paths] [--base-path=<path>] [--base-path-relaxed]\n"
" [--user-path | --user-path=<path>]\n"
" [--interpolated-path=<path>]\n"
-" [--reuseaddr] [--detach] [--pid-file=<file>]\n"
+" [--reuseaddr] [--pid-file=<file>]\n"
" [--(enable|disable|allow-override|forbid-override)=<service>]\n"
" [--inetd | [--listen=<host_or_ipaddr>] [--port=<n>]\n"
-" [--user=<user> [--group=<group>]]\n"
+" [--detach] [--user=<user> [--group=<group>]]\n"
" [<directory>...]";
/* List of acceptable pathname prefixes */
syslog(priority, "%s", buf);
} else {
/*
- * Since stderr is set to linebuffered mode, the
+ * Since stderr is set to buffered mode, the
* logging of different processes will not overlap
+ * unless they overflow the (rather big) buffers.
*/
fprintf(stderr, "[%"PRIuMAX"] ", (uintmax_t)getpid());
vfprintf(stderr, err, params);
fputc('\n', stderr);
+ fflush(stderr);
}
}
}
-static int execute(struct sockaddr *addr)
+static int execute(void)
{
static char line[1000];
int pktlen, len, i;
+ char *addr = getenv("REMOTE_ADDR"), *port = getenv("REMOTE_PORT");
- if (addr) {
- char addrbuf[256] = "";
- int port = -1;
-
- if (addr->sa_family == AF_INET) {
- struct sockaddr_in *sin_addr = (void *) addr;
- inet_ntop(addr->sa_family, &sin_addr->sin_addr, addrbuf, sizeof(addrbuf));
- port = ntohs(sin_addr->sin_port);
-#ifndef NO_IPV6
- } else if (addr && addr->sa_family == AF_INET6) {
- struct sockaddr_in6 *sin6_addr = (void *) addr;
-
- char *buf = addrbuf;
- *buf++ = '['; *buf = '\0'; /* stpcpy() is cool */
- inet_ntop(AF_INET6, &sin6_addr->sin6_addr, buf, sizeof(addrbuf) - 1);
- strcat(buf, "]");
-
- port = ntohs(sin6_addr->sin6_port);
-#endif
- }
- loginfo("Connection from %s:%d", addrbuf, port);
- setenv("REMOTE_ADDR", addrbuf, 1);
- }
- else {
- unsetenv("REMOTE_ADDR");
- }
+ if (addr)
+ loginfo("Connection from %s:%s", addr, port);
alarm(init_timeout ? init_timeout : timeout);
pktlen = packet_read_line(0, line, sizeof(line));
static struct child {
struct child *next;
- pid_t pid;
+ struct child_process cld;
struct sockaddr_storage address;
} *firstborn;
-static void add_child(pid_t pid, struct sockaddr *addr, int addrlen)
+static void add_child(struct child_process *cld, struct sockaddr *addr, socklen_t addrlen)
{
struct child *newborn, **cradle;
newborn = xcalloc(1, sizeof(*newborn));
live_children++;
- newborn->pid = pid;
+ memcpy(&newborn->cld, cld, sizeof(*cld));
memcpy(&newborn->address, addr, addrlen);
for (cradle = &firstborn; *cradle; cradle = &(*cradle)->next)
if (!addrcmp(&(*cradle)->address, &newborn->address))
*cradle = newborn;
}
-static void remove_child(pid_t pid)
-{
- struct child **cradle, *blanket;
-
- for (cradle = &firstborn; (blanket = *cradle); cradle = &blanket->next)
- if (blanket->pid == pid) {
- *cradle = blanket->next;
- live_children--;
- free(blanket);
- break;
- }
-}
-
/*
* This gets called if the number of connections grows
* past "max_connections".
for (; (next = blanket->next); blanket = next)
if (!addrcmp(&blanket->address, &next->address)) {
- kill(blanket->pid, SIGTERM);
+ kill(blanket->cld.pid, SIGTERM);
break;
}
}
int status;
pid_t pid;
- while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
- const char *dead = "";
- remove_child(pid);
- if (!WIFEXITED(status) || (WEXITSTATUS(status) > 0))
- dead = " (with error)";
- loginfo("[%"PRIuMAX"] Disconnected%s", (uintmax_t)pid, dead);
- }
+ struct child **cradle, *blanket;
+ for (cradle = &firstborn; (blanket = *cradle);)
+ if ((pid = waitpid(blanket->cld.pid, &status, WNOHANG)) > 1) {
+ const char *dead = "";
+ if (status)
+ dead = " (with error)";
+ loginfo("[%"PRIuMAX"] Disconnected%s", (uintmax_t)pid, dead);
+
+ /* remove the child */
+ *cradle = blanket->next;
+ live_children--;
+ free(blanket);
+ } else
+ cradle = &blanket->next;
}
-static void handle(int incoming, struct sockaddr *addr, int addrlen)
+static char **cld_argv;
+static void handle(int incoming, struct sockaddr *addr, socklen_t addrlen)
{
- pid_t pid;
+ struct child_process cld = { 0 };
+ char addrbuf[300] = "REMOTE_ADDR=", portbuf[300];
+ char *env[] = { addrbuf, portbuf, NULL };
if (max_connections && live_children >= max_connections) {
kill_some_child();
}
}
- if ((pid = fork())) {
- close(incoming);
- if (pid < 0) {
- logerror("Couldn't fork %s", strerror(errno));
- return;
- }
+ if (addr->sa_family == AF_INET) {
+ struct sockaddr_in *sin_addr = (void *) addr;
+ inet_ntop(addr->sa_family, &sin_addr->sin_addr, addrbuf + 12,
+ sizeof(addrbuf) - 12);
+ snprintf(portbuf, sizeof(portbuf), "REMOTE_PORT=%d",
+ ntohs(sin_addr->sin_port));
+#ifndef NO_IPV6
+ } else if (addr && addr->sa_family == AF_INET6) {
+ struct sockaddr_in6 *sin6_addr = (void *) addr;
- add_child(pid, addr, addrlen);
- return;
+ char *buf = addrbuf + 12;
+ *buf++ = '['; *buf = '\0'; /* stpcpy() is cool */
+ inet_ntop(AF_INET6, &sin6_addr->sin6_addr, buf,
+ sizeof(addrbuf) - 13);
+ strcat(buf, "]");
+
+ snprintf(portbuf, sizeof(portbuf), "REMOTE_PORT=%d",
+ ntohs(sin6_addr->sin6_port));
+#endif
}
- dup2(incoming, 0);
- dup2(incoming, 1);
- close(incoming);
+ cld.env = (const char **)env;
+ cld.argv = (const char **)cld_argv;
+ cld.in = incoming;
+ cld.out = dup(incoming);
- exit(execute(addr));
+ if (start_command(&cld))
+ logerror("unable to fork");
+ else
+ add_child(&cld, addr, addrlen);
+ close(incoming);
}
static void child_handler(int signo)
&on, sizeof(on));
}
+struct socketlist {
+ int *list;
+ size_t nr;
+ size_t alloc;
+};
+
#ifndef NO_IPV6
-static int socksetup(char *listen_addr, int listen_port, int **socklist_p)
+static int setup_named_sock(char *listen_addr, int listen_port, struct socketlist *socklist)
{
- int socknum = 0, *socklist = NULL;
+ int socknum = 0;
int maxfd = -1;
char pbuf[NI_MAXSERV];
struct addrinfo hints, *ai0, *ai;
hints.ai_flags = AI_PASSIVE;
gai = getaddrinfo(listen_addr, pbuf, &hints, &ai0);
- if (gai)
- die("getaddrinfo() failed: %s", gai_strerror(gai));
+ if (gai) {
+ logerror("getaddrinfo() for %s failed: %s", listen_addr, gai_strerror(gai));
+ return 0;
+ }
for (ai = ai0; ai; ai = ai->ai_next) {
int sockfd;
if (flags >= 0)
fcntl(sockfd, F_SETFD, flags | FD_CLOEXEC);
- socklist = xrealloc(socklist, sizeof(int) * (socknum + 1));
- socklist[socknum++] = sockfd;
+ ALLOC_GROW(socklist->list, socklist->nr + 1, socklist->alloc);
+ socklist->list[socklist->nr++] = sockfd;
+ socknum++;
if (maxfd < sockfd)
maxfd = sockfd;
freeaddrinfo(ai0);
- *socklist_p = socklist;
return socknum;
}
#else /* NO_IPV6 */
-static int socksetup(char *listen_addr, int listen_port, int **socklist_p)
+static int setup_named_sock(char *listen_addr, int listen_port, struct socketlist *socklist)
{
struct sockaddr_in sin;
int sockfd;
if (flags >= 0)
fcntl(sockfd, F_SETFD, flags | FD_CLOEXEC);
- *socklist_p = xmalloc(sizeof(int));
- **socklist_p = sockfd;
+ ALLOC_GROW(socklist->list, socklist->nr + 1, socklist->alloc);
+ socklist->list[socklist->nr++] = sockfd;
return 1;
}
#endif
-static int service_loop(int socknum, int *socklist)
+static void socksetup(struct string_list *listen_addr, int listen_port, struct socketlist *socklist)
+{
+ if (!listen_addr->nr)
+ setup_named_sock(NULL, listen_port, socklist);
+ else {
+ int i, socknum;
+ for (i = 0; i < listen_addr->nr; i++) {
+ socknum = setup_named_sock(listen_addr->items[i].string,
+ listen_port, socklist);
+
+ if (socknum == 0)
+ logerror("unable to allocate any listen sockets for host %s on port %u",
+ listen_addr->items[i].string, listen_port);
+ }
+ }
+}
+
+static int service_loop(struct socketlist *socklist)
{
struct pollfd *pfd;
int i;
- pfd = xcalloc(socknum, sizeof(struct pollfd));
+ pfd = xcalloc(socklist->nr, sizeof(struct pollfd));
- for (i = 0; i < socknum; i++) {
- pfd[i].fd = socklist[i];
+ for (i = 0; i < socklist->nr; i++) {
+ pfd[i].fd = socklist->list[i];
pfd[i].events = POLLIN;
}
check_dead_children();
- if (poll(pfd, socknum, -1) < 0) {
+ if (poll(pfd, socklist->nr, -1) < 0) {
if (errno != EINTR) {
logerror("Poll failed, resuming: %s",
strerror(errno));
continue;
}
- for (i = 0; i < socknum; i++) {
+ for (i = 0; i < socklist->nr; i++) {
if (pfd[i].revents & POLLIN) {
- struct sockaddr_storage ss;
- unsigned int sslen = sizeof(ss);
- int incoming = accept(pfd[i].fd, (struct sockaddr *)&ss, &sslen);
+ union {
+ struct sockaddr sa;
+ struct sockaddr_in sai;
+#ifndef NO_IPV6
+ struct sockaddr_in6 sai6;
+#endif
+ } ss;
+ socklen_t sslen = sizeof(ss);
+ int incoming = accept(pfd[i].fd, &ss.sa, &sslen);
if (incoming < 0) {
switch (errno) {
case EAGAIN:
die_errno("accept returned");
}
}
- handle(incoming, (struct sockaddr *)&ss, sslen);
+ handle(incoming, &ss.sa, sslen);
}
}
}
close(fd);
}
+#ifdef NO_POSIX_GOODIES
+
+struct credentials;
+
+static void drop_privileges(struct credentials *cred)
+{
+ /* nothing */
+}
+
+static void daemonize(void)
+{
+ die("--detach not supported on this platform");
+}
+
+static struct credentials *prepare_credentials(const char *user_name,
+ const char *group_name)
+{
+ die("--user not supported on this platform");
+}
+
+#else
+
+struct credentials {
+ struct passwd *pass;
+ gid_t gid;
+};
+
+static void drop_privileges(struct credentials *cred)
+{
+ if (cred && (initgroups(cred->pass->pw_name, cred->gid) ||
+ setgid (cred->gid) || setuid(cred->pass->pw_uid)))
+ die("cannot drop privileges");
+}
+
+static struct credentials *prepare_credentials(const char *user_name,
+ const char *group_name)
+{
+ static struct credentials c;
+
+ c.pass = getpwnam(user_name);
+ if (!c.pass)
+ die("user not found - %s", user_name);
+
+ if (!group_name)
+ c.gid = c.pass->pw_gid;
+ else {
+ struct group *group = getgrnam(group_name);
+ if (!group)
+ die("group not found - %s", group_name);
+
+ c.gid = group->gr_gid;
+ }
+
+ return &c;
+}
+
static void daemonize(void)
{
switch (fork()) {
close(2);
sanitize_stdfds();
}
+#endif
static void store_pid(const char *path)
{
die_errno("failed to write pid file '%s'", path);
}
-static int serve(char *listen_addr, int listen_port, struct passwd *pass, gid_t gid)
+static int serve(struct string_list *listen_addr, int listen_port,
+ struct credentials *cred)
{
- int socknum, *socklist;
+ struct socketlist socklist = { NULL, 0, 0 };
- socknum = socksetup(listen_addr, listen_port, &socklist);
- if (socknum == 0)
- die("unable to allocate any listen sockets on host %s port %u",
- listen_addr, listen_port);
+ socksetup(listen_addr, listen_port, &socklist);
+ if (socklist.nr == 0)
+ die("unable to allocate any listen sockets on port %u",
+ listen_port);
- if (pass && gid &&
- (initgroups(pass->pw_name, gid) || setgid (gid) ||
- setuid(pass->pw_uid)))
- die("cannot drop privileges");
+ drop_privileges(cred);
- return service_loop(socknum, socklist);
+ return service_loop(&socklist);
}
int main(int argc, char **argv)
{
int listen_port = 0;
- char *listen_addr = NULL;
- int inetd_mode = 0;
+ struct string_list listen_addr = STRING_LIST_INIT_NODUP;
+ int serve_mode = 0, inetd_mode = 0;
const char *pid_file = NULL, *user_name = NULL, *group_name = NULL;
int detach = 0;
- struct passwd *pass = NULL;
- struct group *group;
- gid_t gid = 0;
+ struct credentials *cred = NULL;
int i;
git_extract_argv0_path(argv[0]);
char *arg = argv[i];
if (!prefixcmp(arg, "--listen=")) {
- listen_addr = xstrdup_tolower(arg + 9);
+ string_list_append(&listen_addr, xstrdup_tolower(arg + 9));
continue;
}
if (!prefixcmp(arg, "--port=")) {
continue;
}
}
+ if (!strcmp(arg, "--serve")) {
+ serve_mode = 1;
+ continue;
+ }
if (!strcmp(arg, "--inetd")) {
inetd_mode = 1;
log_syslog = 1;
set_die_routine(daemon_die);
} else
/* avoid splitting a message in the middle */
- setvbuf(stderr, NULL, _IOLBF, 0);
+ setvbuf(stderr, NULL, _IOFBF, 4096);
- if (inetd_mode && (group_name || user_name))
- die("--user and --group are incompatible with --inetd");
+ if (inetd_mode && (detach || group_name || user_name))
+ die("--detach, --user and --group are incompatible with --inetd");
- if (inetd_mode && (listen_port || listen_addr))
+ if (inetd_mode && (listen_port || (listen_addr.nr > 0)))
die("--listen= and --port= are incompatible with --inetd");
else if (listen_port == 0)
listen_port = DEFAULT_GIT_PORT;
if (group_name && !user_name)
die("--group supplied without --user");
- if (user_name) {
- pass = getpwnam(user_name);
- if (!pass)
- die("user not found - %s", user_name);
-
- if (!group_name)
- gid = pass->pw_gid;
- else {
- group = getgrnam(group_name);
- if (!group)
- die("group not found - %s", group_name);
-
- gid = group->gr_gid;
- }
- }
+ if (user_name)
+ cred = prepare_credentials(user_name, group_name);
if (strict_paths && (!ok_paths || !*ok_paths))
die("option --strict-paths requires a whitelist");
base_path);
if (inetd_mode) {
- struct sockaddr_storage ss;
- struct sockaddr *peer = (struct sockaddr *)&ss;
- socklen_t slen = sizeof(ss);
-
if (!freopen("/dev/null", "w", stderr))
die_errno("failed to redirect stderr to /dev/null");
-
- if (getpeername(0, peer, &slen))
- peer = NULL;
-
- return execute(peer);
}
+ if (inetd_mode || serve_mode)
+ return execute();
+
if (detach) {
daemonize();
loginfo("Ready to rumble");
if (pid_file)
store_pid(pid_file);
- return serve(listen_addr, listen_port, pass, gid);
+ /* prepare argv for serving-processes */
+ cld_argv = xmalloc(sizeof (char *) * (argc + 2));
+ cld_argv[0] = argv[0]; /* git-daemon */
+ cld_argv[1] = "--serve";
+ for (i = 1; i < argc; ++i)
+ cld_argv[i+1] = argv[i];
+ cld_argv[argc+1] = NULL;
+
+ return serve(&listen_addr, listen_port, cred);
}
diff --git a/diff-lib.c b/diff-lib.c
index 392ce2bef05746cea7922d39da67bf25d1d3d192..1e22992cb10420b9dd6def16f80efc5f196ffbbb 100644 (file)
--- a/diff-lib.c
+++ b/diff-lib.c
DIFF_OPT_TST(&revs->diffopt, HAS_CHANGES))
break;
- if (!ce_path_match(ce, revs->prune_data))
+ if (!ce_path_match(ce, &revs->prune_data))
continue;
if (ce_stage(ce)) {
@@ -427,7 +427,7 @@ static int oneway_diff(struct cache_entry **src, struct unpack_trees_options *o)
if (tree == o->df_conflict_entry)
tree = NULL;
- if (ce_path_match(idx ? idx : tree, revs->prune_data))
+ if (ce_path_match(idx ? idx : tree, &revs->prune_data))
do_oneway_diff(o, idx, tree);
return 0;
active_nr = dst - active_cache;
init_revisions(&revs, NULL);
- revs.prune_data = opt->paths;
+ init_pathspec(&revs.prune_data, opt->pathspec.raw);
tree = parse_tree_indirect(tree_sha1);
if (!tree)
die("bad tree object %s", sha1_to_hex(tree_sha1));
diff --git a/diff-no-index.c b/diff-no-index.c
index ce9e783407437bb1e0efc6d5bc2392af26da5a41..3a36144687ae2f5bf7bb3afc914ddbada8d5ff93 100644 (file)
--- a/diff-no-index.c
+++ b/diff-no-index.c
if (prefix) {
int len = strlen(prefix);
+ const char *paths[3];
+ memset(paths, 0, sizeof(paths));
- revs->diffopt.paths = xcalloc(2, sizeof(char *));
for (i = 0; i < 2; i++) {
const char *p = argv[argc - 2 + i];
/*
p = (strcmp(p, "-")
? xstrdup(prefix_filename(prefix, len, p))
: p);
- revs->diffopt.paths[i] = p;
+ paths[i] = p;
}
+ diff_tree_setup_paths(paths, &revs->diffopt);
}
else
- revs->diffopt.paths = argv + argc - 2;
- revs->diffopt.nr_paths = 2;
+ diff_tree_setup_paths(argv + argc - 2, &revs->diffopt);
revs->diffopt.skip_stat_unmatch = 1;
if (!revs->diffopt.output_format)
revs->diffopt.output_format = DIFF_FORMAT_PATCH;
if (diff_setup_done(&revs->diffopt) < 0)
die("diff_setup_done failed");
- if (queue_diff(&revs->diffopt, revs->diffopt.paths[0],
- revs->diffopt.paths[1]))
+ if (queue_diff(&revs->diffopt, revs->diffopt.pathspec.raw[0],
+ revs->diffopt.pathspec.raw[1]))
exit(1);
diff_set_mnemonic_prefix(&revs->diffopt, "1/", "2/");
diffcore_std(&revs->diffopt);
index fa3b29d92e39cd35b30c1572acd06e2ba2708edd..66408571abf91d6b90cd69d04164eafd633e407a 100644 (file)
--- a/diff.c
+++ b/diff.c
return 0;
}
+/* like fill_mmfile, but only for size, so we can avoid retrieving blob */
+static unsigned long diff_filespec_size(struct diff_filespec *one)
+{
+ if (!DIFF_FILE_VALID(one))
+ return 0;
+ diff_populate_filespec(one, 1);
+ return one->size;
+}
+
static int count_trailing_blank(mmfile_t *mf, unsigned ws_rule)
{
char *ptr = mf->ptr;
data->is_unmerged = 1;
return;
}
- if (complete_rewrite) {
+
+ if (diff_filespec_is_binary(one) || diff_filespec_is_binary(two)) {
+ data->is_binary = 1;
+ data->added = diff_filespec_size(two);
+ data->deleted = diff_filespec_size(one);
+ }
+
+ else if (complete_rewrite) {
diff_populate_filespec(one, 0);
diff_populate_filespec(two, 0);
data->deleted = count_lines(one->data, one->size);
data->added = count_lines(two->data, two->size);
- goto free_and_return;
}
- if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
- die("unable to read files to diff");
- if (diff_filespec_is_binary(one) || diff_filespec_is_binary(two)) {
- data->is_binary = 1;
- data->added = mf2.size;
- data->deleted = mf1.size;
- } else {
+ else {
/* Crazy xdl interfaces.. */
xpparam_t xpp;
xdemitconf_t xecfg;
+ if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
+ die("unable to read files to diff");
+
memset(&xpp, 0, sizeof(xpp));
memset(&xecfg, 0, sizeof(xecfg));
xpp.flags = o->xdl_opts;
&xpp, &xecfg);
}
- free_and_return:
diff_free_filespec_data(one);
diff_free_filespec_data(two);
}
return stat_opt(options, av);
/* renames options */
- else if (!prefixcmp(arg, "-B")) {
+ else if (!prefixcmp(arg, "-B") || !prefixcmp(arg, "--break-rewrites=") ||
+ !strcmp(arg, "--break-rewrites")) {
if ((options->break_opt = diff_scoreopt_parse(arg)) == -1)
- return -1;
+ return error("invalid argument to -B: %s", arg+2);
}
- else if (!prefixcmp(arg, "-M")) {
+ else if (!prefixcmp(arg, "-M") || !prefixcmp(arg, "--find-renames=") ||
+ !strcmp(arg, "--find-renames")) {
if ((options->rename_score = diff_scoreopt_parse(arg)) == -1)
- return -1;
+ return error("invalid argument to -M: %s", arg+2);
options->detect_rename = DIFF_DETECT_RENAME;
}
- else if (!prefixcmp(arg, "-C")) {
+ else if (!prefixcmp(arg, "-C") || !prefixcmp(arg, "--find-copies=") ||
+ !strcmp(arg, "--find-copies")) {
if (options->detect_rename == DIFF_DETECT_COPY)
DIFF_OPT_SET(options, FIND_COPIES_HARDER);
if ((options->rename_score = diff_scoreopt_parse(arg)) == -1)
- return -1;
+ return error("invalid argument to -C: %s", arg+2);
options->detect_rename = DIFF_DETECT_COPY;
}
else if (!strcmp(arg, "--no-renames"))
}
else if ((argcount = short_opt('S', av, &optarg))) {
options->pickaxe = optarg;
+ options->pickaxe_opts |= DIFF_PICKAXE_KIND_S;
+ return argcount;
+ } else if ((argcount = short_opt('G', av, &optarg))) {
+ options->pickaxe = optarg;
+ options->pickaxe_opts |= DIFF_PICKAXE_KIND_G;
return argcount;
}
else if (!strcmp(arg, "--pickaxe-all"))
- options->pickaxe_opts = DIFF_PICKAXE_ALL;
+ options->pickaxe_opts |= DIFF_PICKAXE_ALL;
else if (!strcmp(arg, "--pickaxe-regex"))
- options->pickaxe_opts = DIFF_PICKAXE_REGEX;
+ options->pickaxe_opts |= DIFF_PICKAXE_REGEX;
else if ((argcount = short_opt('O', av, &optarg))) {
options->orderfile = optarg;
return argcount;
return 1;
}
-static int parse_num(const char **cp_p)
+int parse_rename_score(const char **cp_p)
{
unsigned long num, scale;
int ch, dot;
if (*opt++ != '-')
return -1;
cmd = *opt++;
+ if (cmd == '-') {
+ /* convert the long-form arguments into short-form versions */
+ if (!prefixcmp(opt, "break-rewrites")) {
+ opt += strlen("break-rewrites");
+ if (*opt == 0 || *opt++ == '=')
+ cmd = 'B';
+ } else if (!prefixcmp(opt, "find-copies")) {
+ opt += strlen("find-copies");
+ if (*opt == 0 || *opt++ == '=')
+ cmd = 'C';
+ } else if (!prefixcmp(opt, "find-renames")) {
+ opt += strlen("find-renames");
+ if (*opt == 0 || *opt++ == '=')
+ cmd = 'M';
+ }
+ }
if (cmd != 'M' && cmd != 'C' && cmd != 'B')
return -1; /* that is not a -M, -C nor -B option */
- opt1 = parse_num(&opt);
+ opt1 = parse_rename_score(&opt);
if (cmd != 'B')
opt2 = 0;
else {
return -1; /* we expect -B80/99 or -B80 */
else {
opt++;
- opt2 = parse_num(&opt);
+ opt2 = parse_rename_score(&opt);
}
}
if (*opt != 0)
diffcore_merge_broken();
}
if (options->pickaxe)
- diffcore_pickaxe(options->pickaxe, options->pickaxe_opts);
+ diffcore_pickaxe(options);
if (options->orderfile)
diffcore_order(options->orderfile);
if (!options->found_follow)
index bf2f44d840735684c89f654ee6f3b3a935af2c55..310bd6b2832ce7f874aff735bb19b4813f117d4d 100644 (file)
--- a/diff.h
+++ b/diff.h
FILE *file;
int close_file;
- int nr_paths;
- const char **paths;
- int *pathlens;
+ struct pathspec pathspec;
change_fn_t change;
add_remove_fn_t add_remove;
diff_format_fn_t format_callback;
#define DIFF_PICKAXE_ALL 1
#define DIFF_PICKAXE_REGEX 2
+#define DIFF_PICKAXE_KIND_S 4 /* traditional plumbing counter */
+#define DIFF_PICKAXE_KIND_G 8 /* grep in the patch */
+
extern void diffcore_std(struct diff_options *);
extern void diffcore_fix_diff_index(struct diff_options *);
extern struct userdiff_driver *get_textconv(struct diff_filespec *one);
+extern int parse_rename_score(const char **cp_p);
+
#endif /* DIFF_H */
diff --git a/diffcore-pickaxe.c b/diffcore-pickaxe.c
index 9c6544daacb6d0c7aeb2cc188d089ee5aeb06c5d..ea03b9107eb2e85dd4a4c5ad93ac08dfe7e1c9f0 100644 (file)
--- a/diffcore-pickaxe.c
+++ b/diffcore-pickaxe.c
/*
* Copyright (C) 2005 Junio C Hamano
+ * Copyright (C) 2010 Google Inc.
*/
#include "cache.h"
#include "diff.h"
#include "diffcore.h"
+#include "xdiff-interface.h"
+
+struct diffgrep_cb {
+ regex_t *regexp;
+ int hit;
+};
+
+static void diffgrep_consume(void *priv, char *line, unsigned long len)
+{
+ struct diffgrep_cb *data = priv;
+ regmatch_t regmatch;
+ int hold;
+
+ if (line[0] != '+' && line[0] != '-')
+ return;
+ if (data->hit)
+ /*
+ * NEEDSWORK: we should have a way to terminate the
+ * caller early.
+ */
+ return;
+ /* Yuck -- line ought to be "const char *"! */
+ hold = line[len];
+ line[len] = '\0';
+ data->hit = !regexec(data->regexp, line + 1, 1, ®match, 0);
+ line[len] = hold;
+}
+
+static void fill_one(struct diff_filespec *one,
+ mmfile_t *mf, struct userdiff_driver **textconv)
+{
+ if (DIFF_FILE_VALID(one)) {
+ *textconv = get_textconv(one);
+ mf->size = fill_textconv(*textconv, one, &mf->ptr);
+ } else {
+ memset(mf, 0, sizeof(*mf));
+ }
+}
+
+static int diff_grep(struct diff_filepair *p, regex_t *regexp, struct diff_options *o)
+{
+ regmatch_t regmatch;
+ struct userdiff_driver *textconv_one = NULL;
+ struct userdiff_driver *textconv_two = NULL;
+ mmfile_t mf1, mf2;
+ int hit;
+
+ if (diff_unmodified_pair(p))
+ return 0;
+
+ fill_one(p->one, &mf1, &textconv_one);
+ fill_one(p->two, &mf2, &textconv_two);
+
+ if (!mf1.ptr) {
+ if (!mf2.ptr)
+ return 0; /* ignore unmerged */
+ /* created "two" -- does it have what we are looking for? */
+ hit = !regexec(regexp, p->two->data, 1, ®match, 0);
+ } else if (!mf2.ptr) {
+ /* removed "one" -- did it have what we are looking for? */
+ hit = !regexec(regexp, p->one->data, 1, ®match, 0);
+ } else {
+ /*
+ * We have both sides; need to run textual diff and see if
+ * the pattern appears on added/deleted lines.
+ */
+ struct diffgrep_cb ecbdata;
+ xpparam_t xpp;
+ xdemitconf_t xecfg;
+
+ memset(&xpp, 0, sizeof(xpp));
+ memset(&xecfg, 0, sizeof(xecfg));
+ ecbdata.regexp = regexp;
+ ecbdata.hit = 0;
+ xecfg.ctxlen = o->context;
+ xecfg.interhunkctxlen = o->interhunkcontext;
+ xdi_diff_outf(&mf1, &mf2, diffgrep_consume, &ecbdata,
+ &xpp, &xecfg);
+ hit = ecbdata.hit;
+ }
+ if (textconv_one)
+ free(mf1.ptr);
+ if (textconv_two)
+ free(mf2.ptr);
+ return hit;
+}
+
+static void diffcore_pickaxe_grep(struct diff_options *o)
+{
+ struct diff_queue_struct *q = &diff_queued_diff;
+ int i, has_changes, err;
+ regex_t regex;
+ struct diff_queue_struct outq;
+ outq.queue = NULL;
+ outq.nr = outq.alloc = 0;
+
+ err = regcomp(®ex, o->pickaxe, REG_EXTENDED | REG_NEWLINE);
+ if (err) {
+ char errbuf[1024];
+ regerror(err, ®ex, errbuf, 1024);
+ regfree(®ex);
+ die("invalid log-grep regex: %s", errbuf);
+ }
+
+ if (o->pickaxe_opts & DIFF_PICKAXE_ALL) {
+ /* Showing the whole changeset if needle exists */
+ for (i = has_changes = 0; !has_changes && i < q->nr; i++) {
+ struct diff_filepair *p = q->queue[i];
+ if (diff_grep(p, ®ex, o))
+ has_changes++;
+ }
+ if (has_changes)
+ return; /* do not munge the queue */
+
+ /*
+ * Otherwise we will clear the whole queue by copying
+ * the empty outq at the end of this function, but
+ * first clear the current entries in the queue.
+ */
+ for (i = 0; i < q->nr; i++)
+ diff_free_filepair(q->queue[i]);
+ } else {
+ /* Showing only the filepairs that has the needle */
+ for (i = 0; i < q->nr; i++) {
+ struct diff_filepair *p = q->queue[i];
+ if (diff_grep(p, ®ex, o))
+ diff_q(&outq, p);
+ else
+ diff_free_filepair(p);
+ }
+ }
+
+ regfree(®ex);
+
+ free(q->queue);
+ *q = outq;
+ return;
+}
static unsigned int contains(struct diff_filespec *one,
const char *needle, unsigned long len,
return cnt;
}
-void diffcore_pickaxe(const char *needle, int opts)
+static void diffcore_pickaxe_count(struct diff_options *o)
{
+ const char *needle = o->pickaxe;
+ int opts = o->pickaxe_opts;
struct diff_queue_struct *q = &diff_queued_diff;
unsigned long len = strlen(needle);
int i, has_changes;
*q = outq;
return;
}
+
+void diffcore_pickaxe(struct diff_options *o)
+{
+ /* Might want to warn when both S and G are on; I don't care... */
+ if (o->pickaxe_opts & DIFF_PICKAXE_KIND_G)
+ diffcore_pickaxe_grep(o);
+ else
+ diffcore_pickaxe_count(o);
+}
diff --git a/diffcore-rename.c b/diffcore-rename.c
index df41be56deab60d4d39a45920a1e62b05d0474f6..0cd4c1305ba82b39bb8455dd6779fec2cd692e02 100644 (file)
--- a/diffcore-rename.c
+++ b/diffcore-rename.c
* and the final score computation below would not have a
* divide-by-zero issue.
*/
- if (base_size * (MAX_SCORE-minimum_score) < delta_size * MAX_SCORE)
+ if (max_size * (MAX_SCORE-minimum_score) < delta_size * MAX_SCORE)
return 0;
if (!src->cnt_data && diff_populate_filespec(src, 0))
};
static int find_identical_files(struct file_similarity *src,
- struct file_similarity *dst)
+ struct file_similarity *dst,
+ struct diff_options *options)
{
int renames = 0;
}
/* Give higher scores to sources that haven't been used already */
score = !source->rename_used;
+ if (source->rename_used && options->detect_rename != DIFF_DETECT_COPY)
+ continue;
score += basename_same(source, target);
if (score > best_score) {
best = p;
}
}
-static int find_same_files(void *ptr)
+static int find_same_files(void *ptr, void *data)
{
int ret;
struct file_similarity *p = ptr;
struct file_similarity *src = NULL, *dst = NULL;
+ struct diff_options *options = data;
/* Split the hash list up into sources and destinations */
do {
* If we have both sources *and* destinations, see if
* we can match them up
*/
- ret = (src && dst) ? find_identical_files(src, dst) : 0;
+ ret = (src && dst) ? find_identical_files(src, dst, options) : 0;
/* Free the hashes and return the number of renames found */
free_similarity_list(src);
* and then during the second round we try to match
* cache-dirty entries as well.
*/
-static int find_exact_renames(void)
+static int find_exact_renames(struct diff_options *options)
{
int i;
struct hash_table file_table;
insert_file_table(&file_table, 1, i, rename_dst[i].two);
/* Find the renames */
- i = for_each_hash(&file_table, find_same_files);
+ i = for_each_hash(&file_table, find_same_files, options);
/* .. and free the hash data structure */
free_hash(&file_table);
m[worst] = *o;
}
+static int find_renames(struct diff_score *mx, int dst_cnt, int minimum_score, int copies)
+{
+ int count = 0, i;
+
+ for (i = 0; i < dst_cnt * NUM_CANDIDATE_PER_DST; i++) {
+ struct diff_rename_dst *dst;
+
+ if ((mx[i].dst < 0) ||
+ (mx[i].score < minimum_score))
+ break; /* there is no more usable pair. */
+ dst = &rename_dst[mx[i].dst];
+ if (dst->pair)
+ continue; /* already done, either exact or fuzzy. */
+ if (!copies && rename_src[mx[i].src].one->rename_used)
+ continue;
+ record_rename_pair(mx[i].dst, mx[i].src, mx[i].score);
+ count++;
+ }
+ return count;
+}
+
void diffcore_rename(struct diff_options *options)
{
int detect_rename = options->detect_rename;
* We really want to cull the candidates list early
* with cheap tests in order to avoid doing deltas.
*/
- rename_count = find_exact_renames();
+ rename_count = find_exact_renames(options);
/* Did we only want exact renames? */
if (minimum_score == MAX_SCORE)
/* cost matrix sorted by most to least similar pair */
qsort(mx, dst_cnt * NUM_CANDIDATE_PER_DST, sizeof(*mx), score_compare);
- for (i = 0; i < dst_cnt * NUM_CANDIDATE_PER_DST; i++) {
- struct diff_rename_dst *dst;
-
- if ((mx[i].dst < 0) ||
- (mx[i].score < minimum_score))
- break; /* there is no more usable pair. */
- dst = &rename_dst[mx[i].dst];
- if (dst->pair)
- continue; /* already done, either exact or fuzzy. */
- if (rename_src[mx[i].src].one->rename_used)
- continue;
- record_rename_pair(mx[i].dst, mx[i].src, mx[i].score);
- rename_count++;
- }
-
- for (i = 0; i < dst_cnt * NUM_CANDIDATE_PER_DST; i++) {
- struct diff_rename_dst *dst;
-
- if ((mx[i].dst < 0) ||
- (mx[i].score < minimum_score))
- break; /* there is no more usable pair. */
- dst = &rename_dst[mx[i].dst];
- if (dst->pair)
- continue; /* already done, either exact or fuzzy. */
- record_rename_pair(mx[i].dst, mx[i].src, mx[i].score);
- rename_count++;
- }
+ rename_count += find_renames(mx, dst_cnt, minimum_score, 0);
+ if (detect_rename == DIFF_DETECT_COPY)
+ rename_count += find_renames(mx, dst_cnt, minimum_score, 1);
free(mx);
cleanup:
diff --git a/diffcore.h b/diffcore.h
index 8b3241ad137f5934e32336cd1caf8d99ca11d1f5..b8f1fdecf4d9e5e8f8e834eb55a374f1af8352fd 100644 (file)
--- a/diffcore.h
+++ b/diffcore.h
extern void diffcore_break(int);
extern void diffcore_rename(struct diff_options *);
extern void diffcore_merge_broken(void);
-extern void diffcore_pickaxe(const char *needle, int opts);
+extern void diffcore_pickaxe(struct diff_options *);
extern void diffcore_order(const char *orderfile);
#define DIFF_DEBUG 0
index b68750450cdec89df9ea7595ab9a7022619e6e58..168dad615230d77d7719101b76b50b4f6fe02777 100644 (file)
--- a/dir.c
+++ b/dir.c
int check_only, const struct path_simplify *simplify);
static int get_dtype(struct dirent *de, const char *path, int len);
+/* helper string functions with support for the ignore_case flag */
+int strcmp_icase(const char *a, const char *b)
+{
+ return ignore_case ? strcasecmp(a, b) : strcmp(a, b);
+}
+
+int strncmp_icase(const char *a, const char *b, size_t count)
+{
+ return ignore_case ? strncasecmp(a, b, count) : strncmp(a, b, count);
+}
+
+int fnmatch_icase(const char *pattern, const char *string, int flags)
+{
+ return fnmatch(pattern, string, flags | (ignore_case ? FNM_CASEFOLD : 0));
+}
+
static int common_prefix(const char **pathspec)
{
const char *path, *slash, *next;
return len;
}
+int within_depth(const char *name, int namelen,
+ int depth, int max_depth)
+{
+ const char *cp = name, *cpe = name + namelen;
+
+ while (cp < cpe) {
+ if (*cp++ != '/')
+ continue;
+ depth++;
+ if (depth > max_depth)
+ return 0;
+ }
+ return 1;
+}
+
/*
* Does 'match' match the given name?
* A match is found if
if (!*match)
return MATCHED_RECURSIVELY;
- for (;;) {
- unsigned char c1 = *match;
- unsigned char c2 = *name;
- if (c1 == '\0' || is_glob_special(c1))
- break;
- if (c1 != c2)
- return 0;
- match++;
- name++;
- namelen--;
+ if (ignore_case) {
+ for (;;) {
+ unsigned char c1 = tolower(*match);
+ unsigned char c2 = tolower(*name);
+ if (c1 == '\0' || is_glob_special(c1))
+ break;
+ if (c1 != c2)
+ return 0;
+ match++;
+ name++;
+ namelen--;
+ }
+ } else {
+ for (;;) {
+ unsigned char c1 = *match;
+ unsigned char c2 = *name;
+ if (c1 == '\0' || is_glob_special(c1))
+ break;
+ if (c1 != c2)
+ return 0;
+ match++;
+ name++;
+ namelen--;
+ }
}
* we need to match by fnmatch
*/
matchlen = strlen(match);
- if (strncmp(match, name, matchlen))
- return !fnmatch(match, name, 0) ? MATCHED_FNMATCH : 0;
+ if (strncmp_icase(match, name, matchlen))
+ return !fnmatch_icase(match, name, 0) ? MATCHED_FNMATCH : 0;
if (namelen == matchlen)
return MATCHED_EXACTLY;
return retval;
}
+/*
+ * Does 'match' match the given name?
+ * A match is found if
+ *
+ * (1) the 'match' string is leading directory of 'name', or
+ * (2) the 'match' string is a wildcard and matches 'name', or
+ * (3) the 'match' string is exactly the same as 'name'.
+ *
+ * and the return value tells which case it was.
+ *
+ * It returns 0 when there is no match.
+ */
+static int match_pathspec_item(const struct pathspec_item *item, int prefix,
+ const char *name, int namelen)
+{
+ /* name/namelen has prefix cut off by caller */
+ const char *match = item->match + prefix;
+ int matchlen = item->len - prefix;
+
+ /* If the match was just the prefix, we matched */
+ if (!*match)
+ return MATCHED_RECURSIVELY;
+
+ if (matchlen <= namelen && !strncmp(match, name, matchlen)) {
+ if (matchlen == namelen)
+ return MATCHED_EXACTLY;
+
+ if (match[matchlen-1] == '/' || name[matchlen] == '/')
+ return MATCHED_RECURSIVELY;
+ }
+
+ if (item->has_wildcard && !fnmatch(match, name, 0))
+ return MATCHED_FNMATCH;
+
+ return 0;
+}
+
+/*
+ * Given a name and a list of pathspecs, see if the name matches
+ * any of the pathspecs. The caller is also interested in seeing
+ * all pathspec matches some names it calls this function with
+ * (otherwise the user could have mistyped the unmatched pathspec),
+ * and a mark is left in seen[] array for pathspec element that
+ * actually matched anything.
+ */
+int match_pathspec_depth(const struct pathspec *ps,
+ const char *name, int namelen,
+ int prefix, char *seen)
+{
+ int i, retval = 0;
+
+ if (!ps->nr) {
+ if (!ps->recursive || ps->max_depth == -1)
+ return MATCHED_RECURSIVELY;
+
+ if (within_depth(name, namelen, 0, ps->max_depth))
+ return MATCHED_EXACTLY;
+ else
+ return 0;
+ }
+
+ name += prefix;
+ namelen -= prefix;
+
+ for (i = ps->nr - 1; i >= 0; i--) {
+ int how;
+ if (seen && seen[i] == MATCHED_EXACTLY)
+ continue;
+ how = match_pathspec_item(ps->items+i, prefix, name, namelen);
+ if (ps->recursive && ps->max_depth != -1 &&
+ how && how != MATCHED_FNMATCH) {
+ int len = ps->items[i].len;
+ if (name[len] == '/')
+ len++;
+ if (within_depth(name+len, namelen-len, 0, ps->max_depth))
+ how = MATCHED_EXACTLY;
+ else
+ how = 0;
+ }
+ if (how) {
+ if (retval < how)
+ retval = how;
+ if (seen && seen[i] < how)
+ seen[i] = how;
+ }
+ }
+ return retval;
+}
+
static int no_wildcard(const char *string)
{
return string[strcspn(string, "*?[{\\")] == '\0';
@@ -223,6 +357,18 @@ static void *read_skip_worktree_file_from_index(const char *path, size_t *size)
return data;
}
+void free_excludes(struct exclude_list *el)
+{
+ int i;
+
+ for (i = 0; i < el->nr; i++)
+ free(el->excludes[i]);
+ free(el->excludes);
+
+ el->nr = 0;
+ el->excludes = NULL;
+}
+
int add_excludes_from_file_to_list(const char *fname,
const char *base,
int baselen,
int to_exclude = x->to_exclude;
if (x->flags & EXC_FLAG_MUSTBEDIR) {
- if (!dtype) {
- if (!prefixcmp(pathname, exclude) &&
- pathname[x->patternlen] == '/')
- return to_exclude;
- else
- continue;
- }
if (*dtype == DT_UNKNOWN)
*dtype = get_dtype(NULL, pathname, pathlen);
if (*dtype != DT_DIR)
if (x->flags & EXC_FLAG_NODIR) {
/* match basename */
if (x->flags & EXC_FLAG_NOWILDCARD) {
- if (!strcmp(exclude, basename))
+ if (!strcmp_icase(exclude, basename))
return to_exclude;
} else if (x->flags & EXC_FLAG_ENDSWITH) {
if (x->patternlen - 1 <= pathlen &&
- !strcmp(exclude + 1, pathname + pathlen - x->patternlen + 1))
+ !strcmp_icase(exclude + 1, pathname + pathlen - x->patternlen + 1))
return to_exclude;
} else {
- if (fnmatch(exclude, basename, 0) == 0)
+ if (fnmatch_icase(exclude, basename, 0) == 0)
return to_exclude;
}
}
if (pathlen < baselen ||
(baselen && pathname[baselen-1] != '/') ||
- strncmp(pathname, x->base, baselen))
+ strncmp_icase(pathname, x->base, baselen))
continue;
if (x->flags & EXC_FLAG_NOWILDCARD) {
- if (!strcmp(exclude, pathname + baselen))
+ if (!strcmp_icase(exclude, pathname + baselen))
return to_exclude;
} else {
- if (fnmatch(exclude, pathname+baselen,
+ if (fnmatch_icase(exclude, pathname+baselen,
FNM_PATHNAME) == 0)
return to_exclude;
}
index_gitdir
};
+/*
+ * Do not use the alphabetically stored index to look up
+ * the directory name; instead, use the case insensitive
+ * name hash.
+ */
+static enum exist_status directory_exists_in_index_icase(const char *dirname, int len)
+{
+ struct cache_entry *ce = index_name_exists(&the_index, dirname, len + 1, ignore_case);
+ unsigned char endchar;
+
+ if (!ce)
+ return index_nonexistent;
+ endchar = ce->name[len];
+
+ /*
+ * The cache_entry structure returned will contain this dirname
+ * and possibly additional path components.
+ */
+ if (endchar == '/')
+ return index_directory;
+
+ /*
+ * If there are no additional path components, then this cache_entry
+ * represents a submodule. Submodules, despite being directories,
+ * are stored in the cache without a closing slash.
+ */
+ if (!endchar && S_ISGITLINK(ce->ce_mode))
+ return index_gitdir;
+
+ /* This should never be hit, but it exists just in case. */
+ return index_nonexistent;
+}
+
/*
* The index sorts alphabetically by entry name, which
* means that a gitlink sorts as '\0' at the end, while
*/
static enum exist_status directory_exists_in_index(const char *dirname, int len)
{
- int pos = cache_name_pos(dirname, len);
+ int pos;
+
+ if (ignore_case)
+ return directory_exists_in_index_icase(dirname, len);
+
+ pos = cache_name_pos(dirname, len);
if (pos < 0)
pos = -pos-1;
while (pos < active_nr) {
return 0;
}
+static int pathspec_item_cmp(const void *a_, const void *b_)
+{
+ struct pathspec_item *a, *b;
+
+ a = (struct pathspec_item *)a_;
+ b = (struct pathspec_item *)b_;
+ return strcmp(a->match, b->match);
+}
+
+int init_pathspec(struct pathspec *pathspec, const char **paths)
+{
+ const char **p = paths;
+ int i;
+
+ memset(pathspec, 0, sizeof(*pathspec));
+ if (!p)
+ return 0;
+ while (*p)
+ p++;
+ pathspec->raw = paths;
+ pathspec->nr = p - paths;
+ if (!pathspec->nr)
+ return 0;
+
+ pathspec->items = xmalloc(sizeof(struct pathspec_item)*pathspec->nr);
+ for (i = 0; i < pathspec->nr; i++) {
+ struct pathspec_item *item = pathspec->items+i;
+ const char *path = paths[i];
+
+ item->match = path;
+ item->len = strlen(path);
+ item->has_wildcard = !no_wildcard(path);
+ if (item->has_wildcard)
+ pathspec->has_wildcard = 1;
+ }
+
+ qsort(pathspec->items, pathspec->nr,
+ sizeof(struct pathspec_item), pathspec_item_cmp);
+
+ return 0;
+}
+
+void free_pathspec(struct pathspec *pathspec)
+{
+ free(pathspec->items);
+ pathspec->items = NULL;
+}
index 278d84cdf7df01c33a45e6dc9c20592cecff9d85..aa511da77b0ec54cbdbd3786faad0c1285df7182 100644 (file)
--- a/dir.h
+++ b/dir.h
#define MATCHED_FNMATCH 2
#define MATCHED_EXACTLY 3
extern int match_pathspec(const char **pathspec, const char *name, int namelen, int prefix, char *seen);
+extern int match_pathspec_depth(const struct pathspec *pathspec,
+ const char *name, int namelen,
+ int prefix, char *seen);
+extern int within_depth(const char *name, int namelen, int depth, int max_depth);
extern int fill_directory(struct dir_struct *dir, const char **pathspec);
extern int read_directory(struct dir_struct *, const char *path, int len, const char **pathspec);
extern void add_excludes_from_file(struct dir_struct *, const char *fname);
extern void add_exclude(const char *string, const char *base,
int baselen, struct exclude_list *which);
+extern void free_excludes(struct exclude_list *el);
extern int file_exists(const char *);
extern char *get_relative_cwd(char *buffer, int size, const char *dir);
/* tries to remove the path with empty directories along it, ignores ENOENT */
extern int remove_path(const char *path);
+extern int strcmp_icase(const char *a, const char *b);
+extern int strncmp_icase(const char *a, const char *b, size_t count);
+extern int fnmatch_icase(const char *pattern, const char *string, int flags);
+
#endif
diff --git a/environment.c b/environment.c
index 18aded6dc433ebc19c8b34e8ebcf59979a1985f6..9564475f429312a467a020106f7443112367d5da 100644 (file)
--- a/environment.c
+++ b/environment.c
int is_bare_repository_cfg = -1; /* unspecified */
int log_all_ref_updates = -1; /* unspecified */
int warn_ambiguous_refs = 1;
+int unique_abbrev_extra_length;
int repository_format_version;
const char *git_commit_encoding;
const char *git_log_output_encoding;
*/
void set_git_work_tree(const char *new_work_tree)
{
- if (is_bare_repository_cfg >= 0)
- die("cannot set work tree after initialization");
+ if (git_work_tree_initialized) {
+ new_work_tree = make_absolute_path(new_work_tree);
+ if (strcmp(new_work_tree, work_tree))
+ die("internal error: work tree has already been set\n"
+ "Current worktree: %s\nNew worktree: %s",
+ work_tree, new_work_tree);
+ return;
+ }
git_work_tree_initialized = 1;
- free(work_tree);
work_tree = xstrdup(make_absolute_path(new_work_tree));
- is_bare_repository_cfg = 0;
}
const char *get_git_work_tree(void)
{
- if (!git_work_tree_initialized) {
- work_tree = getenv(GIT_WORK_TREE_ENVIRONMENT);
- /* core.bare = true overrides implicit and config work tree */
- if (!work_tree && is_bare_repository_cfg < 1) {
- work_tree = git_work_tree_cfg;
- /* make_absolute_path also normalizes the path */
- if (work_tree && !is_absolute_path(work_tree))
- work_tree = xstrdup(make_absolute_path(git_path("%s", work_tree)));
- } else if (work_tree)
- work_tree = xstrdup(make_absolute_path(work_tree));
- git_work_tree_initialized = 1;
- if (work_tree)
- is_bare_repository_cfg = 0;
- }
return work_tree;
}
return git_object_dir;
}
+int odb_mkstemp(char *template, size_t limit, const char *pattern)
+{
+ int fd;
+ /*
+ * we let the umask do its job, don't try to be more
+ * restrictive except to remove write permission.
+ */
+ int mode = 0444;
+ snprintf(template, limit, "%s/%s",
+ get_object_directory(), pattern);
+ fd = git_mkstemp_mode(template, mode);
+ if (0 <= fd)
+ return fd;
+
+ /* slow path */
+ /* some mkstemp implementations erase template on failure */
+ snprintf(template, limit, "%s/%s",
+ get_object_directory(), pattern);
+ safe_create_leading_directories(template);
+ return xmkstemp_mode(template, mode);
+}
+
+int odb_pack_keep(char *name, size_t namesz, unsigned char *sha1)
+{
+ int fd;
+
+ snprintf(name, namesz, "%s/pack/pack-%s.keep",
+ get_object_directory(), sha1_to_hex(sha1));
+ fd = open(name, O_RDWR|O_CREAT|O_EXCL, 0600);
+ if (0 <= fd)
+ return fd;
+
+ /* slow path */
+ safe_create_leading_directories(name);
+ return open(name, O_RDWR|O_CREAT|O_EXCL, 0600);
+}
+
char *get_index_file(void)
{
if (!git_index_file)
setup_git_env();
return 0;
}
+
+const char *get_log_output_encoding(void)
+{
+ return git_log_output_encoding ? git_log_output_encoding
+ : get_commit_output_encoding();
+}
+
+const char *get_commit_output_encoding(void)
+{
+ return git_commit_encoding ? git_commit_encoding : "UTF-8";
+}
diff --git a/fast-import.c b/fast-import.c
index 8be8a5b73298e2f4a3c7e68a1c824fb0b6cd92e8..e1268b8cb4ae2ac482928b23e6a3a04bf78c7fcb 100644 (file)
--- a/fast-import.c
+++ b/fast-import.c
commit_msg
('from' sp committish lf)?
('merge' sp committish lf)*
- file_change*
+ (file_change | ls)*
lf?;
commit_msg ::= data;
+ ls ::= 'ls' sp '"' quoted(path) '"' lf;
+
file_change ::= file_clr
| file_del
| file_rnm
ts ::= # time since the epoch in seconds, ascii base10 notation;
tz ::= # GIT style timezone;
- # note: comments may appear anywhere in the input, except
- # within a data command. Any form of the data command
- # always escapes the related input from comment processing.
+ # note: comments, ls and cat requests may appear anywhere
+ # in the input, except within a data command. Any form
+ # of the data command always escapes the related input
+ # from comment processing.
#
# In case it is not clear, the '#' that starts the comment
# must be the first character on that line (an lf
# preceded it).
#
+
+ cat_blob ::= 'cat-blob' sp (hexsha1 | idnum) lf;
+ ls_tree ::= 'ls' sp (hexsha1 | idnum) sp path_str lf;
+
comment ::= '#' not_lf* lf;
not_lf ::= # Any byte that is not ASCII newline (LF);
*/
#include "csum-file.h"
#include "quote.h"
#include "exec_cmd.h"
+#include "dir.h"
#define PACK_ID_BITS 16
#define MAX_PACK_ID ((1<<PACK_ID_BITS)-1)
static const char *export_marks_file;
static const char *import_marks_file;
static int import_marks_file_from_stream;
+static int import_marks_file_ignore_missing;
static int relative_marks_paths;
/* Our last blob */
static struct strbuf new_data = STRBUF_INIT;
static int seen_data_command;
+/* Signal handling */
+static volatile sig_atomic_t checkpoint_requested;
+
+/* Where to write output of cat-blob commands */
+static int cat_blob_fd = STDOUT_FILENO;
+
static void parse_argv(void);
+static void parse_cat_blob(void);
+static void parse_ls(struct branch *b);
static void write_branch_report(FILE *rpt, struct branch *b)
{
exit(128);
}
+#ifndef SIGUSR1 /* Windows, for example */
+
+static void set_checkpoint_signal(void)
+{
+}
+
+#else
+
+static void checkpoint_signal(int signo)
+{
+ checkpoint_requested = 1;
+}
+
+static void set_checkpoint_signal(void)
+{
+ struct sigaction sa;
+
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = checkpoint_signal;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_RESTART;
+ sigaction(SIGUSR1, &sa, NULL);
+}
+
+#endif
+
static void alloc_objects(unsigned int cnt)
{
struct object_entry_pool *b;
t->entry_count -= del;
}
+static void tree_content_replace(
+ struct tree_entry *root,
+ const unsigned char *sha1,
+ const uint16_t mode,
+ struct tree_content *newtree)
+{
+ if (!S_ISDIR(mode))
+ die("Root cannot be a non-directory");
+ hashcpy(root->versions[1].sha1, sha1);
+ if (root->tree)
+ release_tree_content_recursive(root->tree);
+ root->tree = newtree;
+}
+
static int tree_content_set(
struct tree_entry *root,
const char *p,
const uint16_t mode,
struct tree_content *subtree)
{
- struct tree_content *t = root->tree;
+ struct tree_content *t;
const char *slash1;
unsigned int i, n;
struct tree_entry *e;
if (!slash1 && !S_ISDIR(mode) && subtree)
die("Non-directories cannot have subtrees");
+ if (!root->tree)
+ load_tree(root);
+ t = root->tree;
for (i = 0; i < t->entry_count; i++) {
e = t->entries[i];
- if (e->name->str_len == n && !strncmp(p, e->name->str_dat, n)) {
+ if (e->name->str_len == n && !strncmp_icase(p, e->name->str_dat, n)) {
if (!slash1) {
if (!S_ISDIR(mode)
&& e->versions[1].mode == mode
const char *p,
struct tree_entry *backup_leaf)
{
- struct tree_content *t = root->tree;
+ struct tree_content *t;
const char *slash1;
unsigned int i, n;
struct tree_entry *e;
else
n = strlen(p);
+ if (!root->tree)
+ load_tree(root);
+ t = root->tree;
for (i = 0; i < t->entry_count; i++) {
e = t->entries[i];
- if (e->name->str_len == n && !strncmp(p, e->name->str_dat, n)) {
+ if (e->name->str_len == n && !strncmp_icase(p, e->name->str_dat, n)) {
if (slash1 && !S_ISDIR(e->versions[1].mode))
/*
* If p names a file in some subdirectory, and a
const char *p,
struct tree_entry *leaf)
{
- struct tree_content *t = root->tree;
+ struct tree_content *t;
const char *slash1;
unsigned int i, n;
struct tree_entry *e;
else
n = strlen(p);
+ if (!root->tree)
+ load_tree(root);
+ t = root->tree;
for (i = 0; i < t->entry_count; i++) {
e = t->entries[i];
- if (e->name->str_len == n && !strncmp(p, e->name->str_dat, n)) {
+ if (e->name->str_len == n && !strncmp_icase(p, e->name->str_dat, n)) {
if (!slash1) {
memcpy(leaf, e, sizeof(*leaf));
if (e->tree && is_null_sha1(e->versions[1].sha1))
{
char line[512];
FILE *f = fopen(import_marks_file, "r");
- if (!f)
+ if (f)
+ ;
+ else if (import_marks_file_ignore_missing && errno == ENOENT)
+ return; /* Marks file does not exist */
+ else
die_errno("cannot read '%s'", import_marks_file);
while (fgets(line, sizeof(line), f)) {
uintmax_t mark;
return EOF;
}
- do {
+ for (;;) {
if (unread_command_buf) {
unread_command_buf = 0;
} else {
rc->prev->next = rc;
cmd_tail = rc;
}
- } while (command_buf.buf[0] == '#');
-
- return 0;
+ if (!prefixcmp(command_buf.buf, "cat-blob ")) {
+ parse_cat_blob();
+ continue;
+ }
+ if (command_buf.buf[0] == '#')
+ continue;
+ return 0;
+ }
}
static void skip_optional_lf(void)
command_buf.buf);
}
+ if (!*p) {
+ tree_content_replace(&b->branch_tree, sha1, mode, NULL);
+ return;
+ }
tree_content_set(&b->branch_tree, p, sha1, mode, NULL);
}
tree_content_get(&b->branch_tree, s, &leaf);
if (!leaf.versions[1].mode)
die("Path %s not in branch", s);
+ if (!*d) { /* C "path/to/subdir" "" */
+ tree_content_replace(&b->branch_tree,
+ leaf.versions[1].sha1,
+ leaf.versions[1].mode,
+ leaf.tree);
+ return;
+ }
tree_content_set(&b->branch_tree, d,
leaf.versions[1].sha1,
leaf.versions[1].mode,
note_change_n(b, prev_fanout);
else if (!strcmp("deleteall", command_buf.buf))
file_change_deleteall(b);
+ else if (!prefixcmp(command_buf.buf, "ls "))
+ parse_ls(b);
else {
unread_command_buf = 1;
break;
unread_command_buf = 1;
}
-static void parse_checkpoint(void)
+static void cat_blob_write(const char *buf, unsigned long size)
+{
+ if (write_in_full(cat_blob_fd, buf, size) != size)
+ die_errno("Write to frontend failed");
+}
+
+static void cat_blob(struct object_entry *oe, unsigned char sha1[20])
+{
+ struct strbuf line = STRBUF_INIT;
+ unsigned long size;
+ enum object_type type = 0;
+ char *buf;
+
+ if (!oe || oe->pack_id == MAX_PACK_ID) {
+ buf = read_sha1_file(sha1, &type, &size);
+ } else {
+ type = oe->type;
+ buf = gfi_unpack_entry(oe, &size);
+ }
+
+ /*
+ * Output based on batch_one_object() from cat-file.c.
+ */
+ if (type <= 0) {
+ strbuf_reset(&line);
+ strbuf_addf(&line, "%s missing\n", sha1_to_hex(sha1));
+ cat_blob_write(line.buf, line.len);
+ strbuf_release(&line);
+ free(buf);
+ return;
+ }
+ if (!buf)
+ die("Can't read object %s", sha1_to_hex(sha1));
+ if (type != OBJ_BLOB)
+ die("Object %s is a %s but a blob was expected.",
+ sha1_to_hex(sha1), typename(type));
+ strbuf_reset(&line);
+ strbuf_addf(&line, "%s %s %lu\n", sha1_to_hex(sha1),
+ typename(type), size);
+ cat_blob_write(line.buf, line.len);
+ strbuf_release(&line);
+ cat_blob_write(buf, size);
+ cat_blob_write("\n", 1);
+ free(buf);
+}
+
+static void parse_cat_blob(void)
+{
+ const char *p;
+ struct object_entry *oe = oe;
+ unsigned char sha1[20];
+
+ /* cat-blob SP <object> LF */
+ p = command_buf.buf + strlen("cat-blob ");
+ if (*p == ':') {
+ char *x;
+ oe = find_mark(strtoumax(p + 1, &x, 10));
+ if (x == p + 1)
+ die("Invalid mark: %s", command_buf.buf);
+ if (!oe)
+ die("Unknown mark: %s", command_buf.buf);
+ if (*x)
+ die("Garbage after mark: %s", command_buf.buf);
+ hashcpy(sha1, oe->idx.sha1);
+ } else {
+ if (get_sha1_hex(p, sha1))
+ die("Invalid SHA1: %s", command_buf.buf);
+ if (p[40])
+ die("Garbage after SHA1: %s", command_buf.buf);
+ oe = find_object(sha1);
+ }
+
+ cat_blob(oe, sha1);
+}
+
+static struct object_entry *dereference(struct object_entry *oe,
+ unsigned char sha1[20])
{
+ unsigned long size;
+ char *buf = NULL;
+ if (!oe) {
+ enum object_type type = sha1_object_info(sha1, NULL);
+ if (type < 0)
+ die("object not found: %s", sha1_to_hex(sha1));
+ /* cache it! */
+ oe = insert_object(sha1);
+ oe->type = type;
+ oe->pack_id = MAX_PACK_ID;
+ oe->idx.offset = 1;
+ }
+ switch (oe->type) {
+ case OBJ_TREE: /* easy case. */
+ return oe;
+ case OBJ_COMMIT:
+ case OBJ_TAG:
+ break;
+ default:
+ die("Not a treeish: %s", command_buf.buf);
+ }
+
+ if (oe->pack_id != MAX_PACK_ID) { /* in a pack being written */
+ buf = gfi_unpack_entry(oe, &size);
+ } else {
+ enum object_type unused;
+ buf = read_sha1_file(sha1, &unused, &size);
+ }
+ if (!buf)
+ die("Can't load object %s", sha1_to_hex(sha1));
+
+ /* Peel one layer. */
+ switch (oe->type) {
+ case OBJ_TAG:
+ if (size < 40 + strlen("object ") ||
+ get_sha1_hex(buf + strlen("object "), sha1))
+ die("Invalid SHA1 in tag: %s", command_buf.buf);
+ break;
+ case OBJ_COMMIT:
+ if (size < 40 + strlen("tree ") ||
+ get_sha1_hex(buf + strlen("tree "), sha1))
+ die("Invalid SHA1 in commit: %s", command_buf.buf);
+ }
+
+ free(buf);
+ return find_object(sha1);
+}
+
+static struct object_entry *parse_treeish_dataref(const char **p)
+{
+ unsigned char sha1[20];
+ struct object_entry *e;
+
+ if (**p == ':') { /* <mark> */
+ char *endptr;
+ e = find_mark(strtoumax(*p + 1, &endptr, 10));
+ if (endptr == *p + 1)
+ die("Invalid mark: %s", command_buf.buf);
+ if (!e)
+ die("Unknown mark: %s", command_buf.buf);
+ *p = endptr;
+ hashcpy(sha1, e->idx.sha1);
+ } else { /* <sha1> */
+ if (get_sha1_hex(*p, sha1))
+ die("Invalid SHA1: %s", command_buf.buf);
+ e = find_object(sha1);
+ *p += 40;
+ }
+
+ while (!e || e->type != OBJ_TREE)
+ e = dereference(e, sha1);
+ return e;
+}
+
+static void print_ls(int mode, const unsigned char *sha1, const char *path)
+{
+ static struct strbuf line = STRBUF_INIT;
+
+ /* See show_tree(). */
+ const char *type =
+ S_ISGITLINK(mode) ? commit_type :
+ S_ISDIR(mode) ? tree_type :
+ blob_type;
+
+ if (!mode) {
+ /* missing SP path LF */
+ strbuf_reset(&line);
+ strbuf_addstr(&line, "missing ");
+ quote_c_style(path, &line, NULL, 0);
+ strbuf_addch(&line, '\n');
+ } else {
+ /* mode SP type SP object_name TAB path LF */
+ strbuf_reset(&line);
+ strbuf_addf(&line, "%06o %s %s\t",
+ mode, type, sha1_to_hex(sha1));
+ quote_c_style(path, &line, NULL, 0);
+ strbuf_addch(&line, '\n');
+ }
+ cat_blob_write(line.buf, line.len);
+}
+
+static void parse_ls(struct branch *b)
+{
+ const char *p;
+ struct tree_entry *root = NULL;
+ struct tree_entry leaf = {0};
+
+ /* ls SP (<treeish> SP)? <path> */
+ p = command_buf.buf + strlen("ls ");
+ if (*p == '"') {
+ if (!b)
+ die("Not in a commit: %s", command_buf.buf);
+ root = &b->branch_tree;
+ } else {
+ struct object_entry *e = parse_treeish_dataref(&p);
+ root = new_tree_entry();
+ hashcpy(root->versions[1].sha1, e->idx.sha1);
+ load_tree(root);
+ if (*p++ != ' ')
+ die("Missing space after tree-ish: %s", command_buf.buf);
+ }
+ if (*p == '"') {
+ static struct strbuf uq = STRBUF_INIT;
+ const char *endp;
+ strbuf_reset(&uq);
+ if (unquote_c_style(&uq, p, &endp))
+ die("Invalid path: %s", command_buf.buf);
+ if (*endp)
+ die("Garbage after path in: %s", command_buf.buf);
+ p = uq.buf;
+ }
+ tree_content_get(root, p, &leaf);
+ /*
+ * A directory in preparation would have a sha1 of zero
+ * until it is saved. Save, for simplicity.
+ */
+ if (S_ISDIR(leaf.versions[1].mode))
+ store_tree(&leaf);
+
+ print_ls(leaf.versions[1].mode, leaf.versions[1].sha1, p);
+ if (!b || root != &b->branch_tree)
+ release_tree_entry(root);
+}
+
+static void checkpoint(void)
+{
+ checkpoint_requested = 0;
if (object_count) {
cycle_packfile();
dump_branches();
dump_tags();
dump_marks();
}
+}
+
+static void parse_checkpoint(void)
+{
+ checkpoint_requested = 1;
skip_optional_lf();
}
return strbuf_detach(&abs_path, NULL);
}
-static void option_import_marks(const char *marks, int from_stream)
+static void option_import_marks(const char *marks,
+ int from_stream, int ignore_missing)
{
if (import_marks_file) {
if (from_stream)
import_marks_file = make_fast_import_path(marks);
safe_create_leading_directories_const(import_marks_file);
import_marks_file_from_stream = from_stream;
+ import_marks_file_ignore_missing = ignore_missing;
}
static void option_date_format(const char *fmt)
die("unknown --date-format argument %s", fmt);
}
+static unsigned long ulong_arg(const char *option, const char *arg)
+{
+ char *endptr;
+ unsigned long rv = strtoul(arg, &endptr, 0);
+ if (strchr(arg, '-') || endptr == arg || *endptr)
+ die("%s: argument must be a non-negative integer", option);
+ return rv;
+}
+
static void option_depth(const char *depth)
{
- max_depth = strtoul(depth, NULL, 0);
+ max_depth = ulong_arg("--depth", depth);
if (max_depth > MAX_DEPTH)
die("--depth cannot exceed %u", MAX_DEPTH);
}
static void option_active_branches(const char *branches)
{
- max_active_branches = strtoul(branches, NULL, 0);
+ max_active_branches = ulong_arg("--active-branches", branches);
}
static void option_export_marks(const char *marks)
safe_create_leading_directories_const(export_marks_file);
}
+static void option_cat_blob_fd(const char *fd)
+{
+ unsigned long n = ulong_arg("--cat-blob-fd", fd);
+ if (n > (unsigned long) INT_MAX)
+ die("--cat-blob-fd cannot exceed %d", INT_MAX);
+ cat_blob_fd = (int) n;
+}
+
static void option_export_pack_edges(const char *edges)
{
if (pack_edges)
if (!prefixcmp(feature, "date-format=")) {
option_date_format(feature + 12);
} else if (!prefixcmp(feature, "import-marks=")) {
- option_import_marks(feature + 13, from_stream);
+ option_import_marks(feature + 13, from_stream, 0);
+ } else if (!prefixcmp(feature, "import-marks-if-exists=")) {
+ option_import_marks(feature + strlen("import-marks-if-exists="),
+ from_stream, 1);
} else if (!prefixcmp(feature, "export-marks=")) {
option_export_marks(feature + 13);
+ } else if (!strcmp(feature, "cat-blob")) {
+ ; /* Don't die - this feature is supported */
} else if (!prefixcmp(feature, "relative-marks")) {
relative_marks_paths = 1;
} else if (!prefixcmp(feature, "no-relative-marks")) {
relative_marks_paths = 0;
} else if (!prefixcmp(feature, "force")) {
force_update = 1;
- } else if (!strcmp(feature, "notes")) {
+ } else if (!strcmp(feature, "notes") || !strcmp(feature, "ls")) {
; /* do nothing; we have the feature */
} else {
return 0;
if (parse_one_feature(a + 2, 0))
continue;
+ if (!prefixcmp(a + 2, "cat-blob-fd=")) {
+ option_cat_blob_fd(a + 2 + strlen("cat-blob-fd="));
+ continue;
+ }
+
die("unknown option %s", a);
}
if (i != global_argc)
prepare_packed_git();
start_packfile();
set_die_routine(die_nicely);
+ set_checkpoint_signal();
while (read_next_command() != EOF) {
if (!strcmp("blob", command_buf.buf))
parse_new_blob();
+ else if (!prefixcmp(command_buf.buf, "ls "))
+ parse_ls(NULL);
else if (!prefixcmp(command_buf.buf, "commit "))
parse_new_commit();
else if (!prefixcmp(command_buf.buf, "tag "))
/* ignore non-git options*/;
else
die("Unsupported command: %s", command_buf.buf);
+
+ if (checkpoint_requested)
+ checkpoint();
}
/* argv hasn't been parsed yet, do so */
diff --git a/git-am.sh b/git-am.sh
index f4db17d93424c18c1db53e9bf84cdeee8219fa10..6cdd5910db50c96df3d149fba172750cb10c09cb 100755 (executable)
--- a/git-am.sh
+++ b/git-am.sh
set x
first=
}
- case "$arg" in
- /*)
- set "$@" "$arg" ;;
- *)
- set "$@" "$prefix$arg" ;;
- esac
+ if is_absolute_path "$arg"
+ then
+ set "$@" "$arg"
+ else
+ set "$@" "$prefix$arg"
+ fi
done
shift
fi
diff --git a/git-bisect.sh b/git-bisect.sh
index 6e2acb8ef29e5003945bed17014a68b141ada454..c21e33c8d133af0e9e0ae3edaffd0a5593ff4ac3 100755 (executable)
--- a/git-bisect.sh
+++ b/git-bisect.sh
*)
usage ;;
esac
- git checkout "$branch" -- && bisect_clean_state
+ if git checkout "$branch" -- ; then
+ bisect_clean_state
+ else
+ die "Could not check out original HEAD '$branch'." \
+ "Try 'git bisect reset <commit>'."
+ fi
}
bisect_clean_state() {
}
bisect_replay () {
+ test "$#" -eq 1 || die "No logfile given"
test -r "$1" || die "cannot read $1 for replaying"
bisect_reset
while read git bisect command rev
done
}
+bisect_log () {
+ test -s "$GIT_DIR/BISECT_LOG" || die "We are not bisecting."
+ cat "$GIT_DIR/BISECT_LOG"
+}
case "$#" in
0)
replay)
bisect_replay "$@" ;;
log)
- cat "$GIT_DIR/BISECT_LOG" ;;
+ bisect_log ;;
run)
bisect_run "$@" ;;
*)
diff --git a/git-compat-util.h b/git-compat-util.h
index 2af8d3edbe35dce35e940c5cccb91f9e06ebeff8..9c23622ed5ee9b60d40921ae74dd1149e1d68b8c 100644 (file)
--- a/git-compat-util.h
+++ b/git-compat-util.h
#define maximum_signed_value_of_type(a) \
(INTMAX_MAX >> (bitsizeof(intmax_t) - bitsizeof(a)))
+#define maximum_unsigned_value_of_type(a) \
+ (UINTMAX_MAX >> (bitsizeof(uintmax_t) - bitsizeof(a)))
+
/*
* Signed integer overflow is undefined in C, so here's a helper macro
* to detect if the sum of two integers will overflow.
#define signed_add_overflows(a, b) \
((b) > maximum_signed_value_of_type(a) - (a))
+#define unsigned_add_overflows(a, b) \
+ ((b) > maximum_unsigned_value_of_type(a) - (a))
+
#ifdef __GNUC__
#define TYPEOF(x) (__typeof__(x))
#else
#include <assert.h>
#include <regex.h>
#include <utime.h>
+#include <syslog.h>
+#ifndef NO_SYS_POLL_H
+#include <sys/poll.h>
+#else
+#include <poll.h>
+#endif
#ifndef __MINGW32__
#include <sys/wait.h>
-#include <sys/poll.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <termios.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <pwd.h>
+#ifndef NO_INTTYPES_H
#include <inttypes.h>
+#else
+#include <stdint.h>
+#endif
#if defined(__CYGWIN__)
#undef _XOPEN_SOURCE
#include <grp.h>
}
#endif
+#ifdef NO_INET_PTON
+int inet_pton(int af, const char *src, void *dst);
+#endif
+
+#ifdef NO_INET_NTOP
+const char *inet_ntop(int af, const void *src, char *dst, size_t size);
+#endif
+
extern void release_pack_memory(size_t, int);
typedef void (*try_to_free_t)(size_t);
extern int xdup(int fd);
extern FILE *xfdopen(int fd, const char *mode);
extern int xmkstemp(char *template);
+extern int xmkstemp_mode(char *template, int mode);
extern int odb_mkstemp(char *template, size_t limit, const char *pattern);
extern int odb_pack_keep(char *name, size_t namesz, unsigned char *sha1);
diff --git a/git-cvsimport.perl b/git-cvsimport.perl
index 249aeaf17557c4b8f05fdd5f1df5c16d65f0f84c..8e683e54783ab6e916bdb8ade56832ac81470648 100755 (executable)
--- a/git-cvsimport.perl
+++ b/git-cvsimport.perl
}
# convert getopts specs for use by git config
+my %longmap = (
+ 'A:' => 'authors-file',
+ 'M:' => 'merge-regex',
+ 'P:' => undef,
+ 'R' => 'track-revisions',
+ 'S:' => 'ignore-paths',
+);
+
sub read_repo_config {
- # Split the string between characters, unless there is a ':'
- # So "abc:de" becomes ["a", "b", "c:", "d", "e"]
+ # Split the string between characters, unless there is a ':'
+ # So "abc:de" becomes ["a", "b", "c:", "d", "e"]
my @opts = split(/ *(?!:)/, shift);
foreach my $o (@opts) {
my $key = $o;
$key =~ s/://g;
my $arg = 'git config';
$arg .= ' --bool' if ($o !~ /:$/);
-
- chomp(my $tmp = `$arg --get cvsimport.$key`);
+ my $ckey = $key;
+
+ if (exists $longmap{$o}) {
+ # An uppercase option like -R cannot be
+ # expressed in the configuration, as the
+ # variable names are downcased.
+ $ckey = $longmap{$o};
+ next if (! defined $ckey);
+ $ckey =~ s/-//g;
+ }
+ chomp(my $tmp = `$arg --get cvsimport.$ckey`);
if ($tmp && !($arg =~ /--bool/ && $tmp eq 'false')) {
- no strict 'refs';
- my $opt_name = "opt_" . $key;
- if (!$$opt_name) {
- $$opt_name = $tmp;
- }
+ no strict 'refs';
+ my $opt_name = "opt_" . $key;
+ if (!$$opt_name) {
+ $$opt_name = $tmp;
+ }
}
}
}
unless (-d $git_dir) {
system(qw(git init));
die "Cannot init the GIT db at $git_tree: $?\n" if $?;
- system(qw(git read-tree));
+ system(qw(git read-tree --empty));
die "Cannot init an empty tree: $?\n" if $?;
$last_branch = $opt_o;
diff --git a/git-cvsserver.perl b/git-cvsserver.perl
index 2822bed1fdf6977cd415461bc48630a4996f7084..1b8bff2cac163a6588df397168f57214c30b4784 100755 (executable)
--- a/git-cvsserver.perl
+++ b/git-cvsserver.perl
#### Copyright The Open University UK - 2006.
####
#### Authors: Martyn Smith <martyn@catalyst.net.nz>
-#### Martin Langhoff <martin@catalyst.net.nz>
+#### Martin Langhoff <martin@laptop.org>
####
####
#### Released under the GNU Public License, version 2.
#### Copyright The Open University UK - 2006.
####
#### Authors: Martyn Smith <martyn@catalyst.net.nz>
-#### Martin Langhoff <martin@catalyst.net.nz>
+#### Martin Langhoff <martin@laptop.org>
####
####
#### Copyright The Open University UK - 2006.
####
#### Authors: Martyn Smith <martyn@catalyst.net.nz>
-#### Martin Langhoff <martin@catalyst.net.nz>
+#### Martin Langhoff <martin@laptop.org>
####
####
diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh
index 4617f29c26726c2cd438f160be21a8422e66b352..d3acf0d2134b40b8ffb3d0e8c3229f71fa6db663 100755 (executable)
--- a/git-gui/git-gui.sh
+++ b/git-gui/git-gui.sh
puts stderr "source $name"
uplevel 1 real__source $name
}
+ if {[tk windowingsystem] eq "win32"} { console show }
}
######################################################################
set _nice [_which nice]
if {[catch {exec $_nice git version}]} {
set _nice {}
+ } elseif {[is_Windows] && [file dirname $_nice] ne [file dirname $::_git]} {
+ set _nice {}
}
}
if {$_nice ne {}} {
if {[is_Windows]} {
wm iconbitmap . -default $oguilib/git-gui.ico
set ::tk::AlwaysShowSelection 1
+ bind . <Control-F2> {console show}
# Spoof an X11 display for SSH
if {![info exists env(DISPLAY)]} {
exit 1
}
+proc get_trimmed_version {s} {
+ set r {}
+ foreach x [split $s -._] {
+ if {[string is integer -strict $x]} {
+ lappend r $x
+ } else {
+ break
+ }
+ }
+ return [join $r .]
+}
set _real_git_version $_git_version
-regsub -- {[\-\.]dirty$} $_git_version {} _git_version
-regsub {\.[0-9]+\.g[0-9a-f]+$} $_git_version {} _git_version
-regsub {\.[a-zA-Z]+\.?[0-9]+$} $_git_version {} _git_version
-regsub {\.GIT$} $_git_version {} _git_version
-regsub {\.[a-zA-Z]+\.?[0-9]+$} $_git_version {} _git_version
+set _git_version [get_trimmed_version $_git_version]
if {![regexp {^[1-9]+(\.[0-9]+)+$} $_git_version]} {
catch {wm withdraw .}
# _gitdir exists, so try loading the config
load_config 0
apply_config
-# try to set work tree from environment, falling back to core.worktree
-if {[catch { set _gitworktree $env(GIT_WORK_TREE) }]} {
- set _gitworktree [get_config core.worktree]
- if {$_gitworktree eq ""} {
- set _gitworktree [file dirname [file normalize $_gitdir]]
+
+# v1.7.0 introduced --show-toplevel to return the canonical work-tree
+if {[package vsatisfies $_git_version 1.7.0]} {
+ set _gitworktree [git rev-parse --show-toplevel]
+} else {
+ # try to set work tree from environment, core.worktree or use
+ # cdup to obtain a relative path to the top of the worktree. If
+ # run from the top, the ./ prefix ensures normalize expands pwd.
+ if {[catch { set _gitworktree $env(GIT_WORK_TREE) }]} {
+ set _gitworktree [get_config core.worktree]
+ if {$_gitworktree eq ""} {
+ set _gitworktree [file normalize ./[git rev-parse --show-cdup]]
+ }
}
}
+
if {$_prefix ne {}} {
if {$_gitworktree eq {}} {
regsub -all {[^/]+/} $_prefix ../ cdup
set s "usage: $::argv0 $::subcommand $::subcommand_args"
if {[tk windowingsystem] eq "win32"} {
wm withdraw .
- tk_messageBox -icon info -title "Usage" -message $s
+ tk_messageBox -icon info -message $s \
+ -title [mc "Usage"]
} else {
puts stderr $s
}
if {[catch {
set head [git rev-parse --verify $head]
} err]} {
- puts stderr $err
+ if {[tk windowingsystem] eq "win32"} {
+ tk_messageBox -icon error -title [mc Error] -message $err
+ } else {
+ puts stderr $err
+ }
exit 1
}
}
citool -
gui {
if {[llength $argv] != 0} {
- puts -nonewline stderr "usage: $argv0"
- if {$subcommand ne {gui}
- && [file tail $argv0] ne "git-$subcommand"} {
- puts -nonewline stderr " $subcommand"
- }
- puts stderr {}
- exit 1
+ usage
}
# fall through to setup UI for commits
}
default {
- puts stderr "usage: $argv0 \[{blame|browser|citool}\]"
+ set err "usage: $argv0 \[{blame|browser|citool}\]"
+ if {[tk windowingsystem] eq "win32"} {
+ wm withdraw .
+ tk_messageBox -icon error -message $err \
+ -title [mc "Usage"]
+ } else {
+ puts stderr $err
+ }
exit 1
}
}
-xscrollcommand {.vpane.lower.diff.body.sbx set} \
-yscrollcommand {.vpane.lower.diff.body.sby set} \
-state disabled
+catch {$ui_diff configure -tabstyle wordprocessor}
${NS}::scrollbar .vpane.lower.diff.body.sbx -orient horizontal \
-command [list $ui_diff xview]
${NS}::scrollbar .vpane.lower.diff.body.sby -orient vertical \
pack .vpane.lower.diff.header -side top -fill x
pack .vpane.lower.diff.body -side bottom -fill both -expand 1
+foreach {n c} {0 black 1 red4 2 green4 3 yellow4 4 blue4 5 magenta4 6 cyan4 7 grey60} {
+ $ui_diff tag configure clr4$n -background $c
+ $ui_diff tag configure clri4$n -foreground $c
+ $ui_diff tag configure clr3$n -foreground $c
+ $ui_diff tag configure clri3$n -background $c
+}
+$ui_diff tag configure clr1 -font font_diffbold
+
$ui_diff tag conf d_cr -elide true
-$ui_diff tag conf d_@ -foreground blue -font font_diffbold
+$ui_diff tag conf d_@ -font font_diffbold
$ui_diff tag conf d_+ -foreground {#00a000}
$ui_diff tag conf d_- -foreground red
index 63988773ba0d02af3dc91474b4a1669ecda27c5c..6e510ec2e39305e6d89de61ab2c925bcbc821a65 100644 (file)
return 1
}
- grid $w.rename.oldname_l $w.rename.oldname_m -sticky w -padx {0 5}
+ grid $w.rename.oldname_l $w.rename.oldname_m -sticky we -padx {0 5}
grid $w.rename.newname_l $w.rename.newname_t -sticky we -padx {0 5}
grid columnconfigure $w.rename 1 -weight 1
pack $w.rename -anchor nw -fill x -pady 5 -padx 5
diff --git a/git-gui/lib/diff.tcl b/git-gui/lib/diff.tcl
index c628750276303fca620c9292463c4c59690c764f..dcf0711be0119a8f5ec42b48d97980330f7113b3 100644 (file)
--- a/git-gui/lib/diff.tcl
+++ b/git-gui/lib/diff.tcl
}
lappend cmd -p
- lappend cmd --no-color
+ lappend cmd --color
if {$repo_config(gui.diffcontext) >= 1} {
lappend cmd "-U$repo_config(gui.diffcontext)"
}
fileevent $fd readable [list read_diff $fd $cont_info]
}
+proc parse_color_line {line} {
+ set start 0
+ set result ""
+ set markup [list]
+ set regexp {\033\[((?:\d+;)*\d+)?m}
+ while {[regexp -indices -start $start $regexp $line match code]} {
+ foreach {begin end} $match break
+ append result [string range $line $start [expr {$begin - 1}]]
+ lappend markup [string length $result] \
+ [eval [linsert $code 0 string range $line]]
+ set start [incr end]
+ }
+ append result [string range $line $start end]
+ if {[llength $markup] < 4} {set markup {}}
+ return [list $result $markup]
+}
+
proc read_diff {fd cont_info} {
global ui_diff diff_active is_submodule_diff
global is_3way_diff is_conflict_diff current_diff_header
$ui_diff conf -state normal
while {[gets $fd line] >= 0} {
+ foreach {line markup} [parse_color_line $line] break
+ set line [string map {\033 ^} $line]
+
# -- Cleanup uninteresting diff header lines.
#
if {$::current_diff_inheader} {
}
}
}
+ set mark [$ui_diff index "end - 1 line linestart"]
$ui_diff insert end $line $tags
if {[string index $line end] eq "\r"} {
$ui_diff tag add d_cr {end - 2c}
}
$ui_diff insert end "\n" $tags
+
+ foreach {posbegin colbegin posend colend} $markup {
+ set prefix clr
+ foreach style [split $colbegin ";"] {
+ if {$style eq "7"} {append prefix i; continue}
+ if {$style < 30 || $style > 47} {continue}
+ set a "$mark linestart + $posbegin chars"
+ set b "$mark linestart + $posend chars"
+ catch {$ui_diff tag add $prefix$style $a $b}
+ }
+ }
}
$ui_diff conf -state disabled
diff --git a/git-instaweb.sh b/git-instaweb.sh
index e6f6ecda177c66a79b18359f7bf8cad9c4744c8c..10fcebb119ce2af81527aa3c24a9b7c3ab3b3e7f 100755 (executable)
--- a/git-instaweb.sh
+++ b/git-instaweb.sh
our \$projectroot = "$(dirname "$fqgitdir")";
our \$git_temp = "$fqgitdir/gitweb/tmp";
our \$projects_list = \$projectroot;
+
+\$feature{'remote_heads'}{'default'} = [1];
EOF
}
diff --git a/git-merge-octopus.sh b/git-merge-octopus.sh
index 615753c83c38f4a97752d5ca3400beeef4acd5f1..8643f74cb09f278c37851c418e839c7d160f36ca 100755 (executable)
--- a/git-merge-octopus.sh
+++ b/git-merge-octopus.sh
esac
eval pretty_name=\${GITHEAD_$SHA1:-$SHA1}
+ if test "$SHA1" = "$pretty_name"
+ then
+ SHA1_UP="$(echo "$SHA1" | tr a-z A-Z)"
+ eval pretty_name=\${GITHEAD_$SHA1_UP:-$pretty_name}
+ fi
common=$(git merge-base --all $SHA1 $MRC) ||
die "Unable to find common commit with $pretty_name"
diff --git a/git-mergetool--lib.sh b/git-mergetool--lib.sh
index b5e1943b1d166b543998c80579c65e82de36516a..77d4aee20ee5b8b01f9a0e3d1f8dbc9f7fdf3eb9 100644 (file)
--- a/git-mergetool--lib.sh
+++ b/git-mergetool--lib.sh
translate_merge_tool_path () {
case "$1" in
- vimdiff)
+ vimdiff|vimdiff2)
echo vim
;;
- gvimdiff)
+ gvimdiff|gvimdiff2)
echo gvim
;;
emerge)
valid_tool () {
case "$1" in
kdiff3 | tkdiff | xxdiff | meld | opendiff | \
- emerge | vimdiff | gvimdiff | ecmerge | diffuse | araxis | p4merge)
+ vimdiff | gvimdiff | vimdiff2 | gvimdiff2 | \
+ emerge | ecmerge | diffuse | araxis | p4merge)
;; # happy
tortoisemerge)
if ! merge_mode; then
"$merge_tool_path" "$LOCAL" "$REMOTE" | cat
fi
;;
- vimdiff)
+ vimdiff|gvimdiff)
if merge_mode; then
touch "$BACKUP"
- "$merge_tool_path" -d -c "wincmd l" \
- "$LOCAL" "$MERGED" "$REMOTE"
+ if $base_present; then
+ "$merge_tool_path" -f -d -c "wincmd J" \
+ "$MERGED" "$LOCAL" "$BASE" "$REMOTE"
+ else
+ "$merge_tool_path" -f -d -c "wincmd l" \
+ "$LOCAL" "$MERGED" "$REMOTE"
+ fi
check_unchanged
else
- "$merge_tool_path" -d -c "wincmd l" \
+ "$merge_tool_path" -f -d -c "wincmd l" \
"$LOCAL" "$REMOTE"
fi
;;
- gvimdiff)
+ vimdiff2|gvimdiff2)
if merge_mode; then
touch "$BACKUP"
- "$merge_tool_path" -d -c "wincmd l" -f \
+ "$merge_tool_path" -f -d -c "wincmd l" \
"$LOCAL" "$MERGED" "$REMOTE"
check_unchanged
else
- "$merge_tool_path" -d -c "wincmd l" -f \
+ "$merge_tool_path" -f -d -c "wincmd l" \
"$LOCAL" "$REMOTE"
fi
;;
diff --git a/git-mergetool.sh b/git-mergetool.sh
index 2f8dc441c6d575323f2fad920ecdb7b20e1445e6..bacbda2bb75854235b912903c7e45fc94d38e719 100755 (executable)
--- a/git-mergetool.sh
+++ b/git-mergetool.sh
files_to_merge() {
if test "$rerere" = true
then
- git rerere status
+ git rerere remaining
else
git ls-files -u | sed -e 's/^[^ ]* //' | sort -u
fi
diff --git a/git-parse-remote.sh b/git-parse-remote.sh
index 5f47b18141a0758c3d0dae2cfe764495c92139c5..1cc2ba6e09614fa55ad205145a3bdacfb78bf283 100644 (file)
--- a/git-parse-remote.sh
+++ b/git-parse-remote.sh
origin="$1"
default=$(get_default_remote)
test -z "$origin" && origin=$default
- curr_branch=$(git symbolic-ref -q HEAD)
+ curr_branch=$(git symbolic-ref -q HEAD) &&
[ "$origin" = "$default" ] &&
echo $(git for-each-ref --format='%(upstream)' $curr_branch)
;;
refs/heads/*) remote=${remote#refs/heads/} ;;
refs/* | tags/* | remotes/* ) remote=
esac
-
- [ -n "$remote" ] && echo "refs/remotes/$repo/$remote"
+ [ -n "$remote" ] && case "$repo" in
+ .)
+ echo "refs/heads/$remote"
+ ;;
+ *)
+ echo "refs/remotes/$repo/$remote"
+ ;;
+ esac
esac
}
diff --git a/git-pull.sh b/git-pull.sh
index 8eb74d45debe7ab2bd2cc9b5ed57b1ba49d94bdb..f6b7b8404896c15487044bcfaa64f758faeeef06 100755 (executable)
--- a/git-pull.sh
+++ b/git-pull.sh
test -f "$GIT_DIR/MERGE_HEAD" && die_merge
strategy_args= diffstat= no_commit= squash= no_ff= ff_only=
-log_arg= verbosity= progress=
+log_arg= verbosity= progress= recurse_submodules=
merge_args=
curr_branch=$(git symbolic-ref -q HEAD)
curr_branch_short="${curr_branch#refs/heads/}"
--no-r|--no-re|--no-reb|--no-reba|--no-rebas|--no-rebase)
rebase=false
;;
+ --recurse-submodules)
+ recurse_submodules=--recurse-submodules
+ ;;
+ --no-recurse-submodules)
+ recurse_submodules=--no-recurse-submodules
+ ;;
--d|--dr|--dry|--dry-|--dry-r|--dry-ru|--dry-run)
dry_run=--dry-run
;;
- -h|--h|--he|--hel|--help)
+ -h|--h|--he|--hel|--help|--help-|--help-a|--help-al|--help-all)
usage
;;
*)
die "updating an unborn branch with changes added to the index"
fi
else
- git update-index --ignore-submodules --refresh &&
- git diff-files --ignore-submodules --quiet &&
- git diff-index --ignore-submodules --cached --quiet HEAD -- ||
- die "refusing to pull with rebase: your working tree is not up-to-date"
+ require_clean_work_tree "pull with rebase" "Please commit or stash them."
fi
oldremoteref= &&
. git-parse-remote &&
done
}
orig_head=$(git rev-parse -q --verify HEAD)
-git fetch $verbosity $progress $dry_run --update-head-ok "$@" || exit 1
+git fetch $verbosity $progress $dry_run $recurse_submodules --update-head-ok "$@" || exit 1
test -z "$dry_run" || exit 0
curr_head=$(git rev-parse -q --verify HEAD)
index 9f017750ca493fadf2ab1b51fc46f549926fee97..5873ba4bc3b2a94cd7754ae572072fd745dc059d 100755 (executable)
abort abort rebasing process and restore original branch
skip skip current patch and continue rebasing process
no-verify override pre-rebase hook from stopping the operation
+verify allow pre-rebase hook to run
root rebase all reachable commmits up to the root(s)
autosquash move commits that begin with squash!/fixup! under -i
"
fi
}
-require_clean_work_tree () {
- # test if working tree is dirty
- git rev-parse --verify HEAD > /dev/null &&
- git update-index --ignore-submodules --refresh &&
- git diff-files --quiet --ignore-submodules &&
- git diff-index --cached --quiet HEAD --ignore-submodules -- ||
- die "Working tree is dirty"
-}
ORIG_REFLOG_ACTION="$GIT_REFLOG_ACTION"
exit "$status"
fi
# Run in subshell because require_clean_work_tree can die.
- if ! (require_clean_work_tree)
+ if ! (require_clean_work_tree "rebase")
then
warn "Commit or stash your changes, and then run"
warn
# comes immediately after the former, and change "pick" to
# "fixup"/"squash".
rearrange_squash () {
- sed -n -e 's/^pick \([0-9a-f]*\) \(squash\)! /\1 \2 /p' \
- -e 's/^pick \([0-9a-f]*\) \(fixup\)! /\1 \2 /p' \
- "$1" >"$1.sq"
+ # extract fixup!/squash! lines and resolve any referenced sha1's
+ while read -r pick sha1 message
+ do
+ case "$message" in
+ "squash! "*|"fixup! "*)
+ action="${message%%!*}"
+ rest="${message#*! }"
+ echo "$sha1 $action $rest"
+ # if it's a single word, try to resolve to a full sha1 and
+ # emit a second copy. This allows us to match on both message
+ # and on sha1 prefix
+ if test "${rest#* }" = "$rest"; then
+ fullsha="$(git rev-parse -q --verify "$rest" 2>/dev/null)"
+ if test -n "$fullsha"; then
+ # prefix the action to uniquely identify this line as
+ # intended for full sha1 match
+ echo "$sha1 +$action $fullsha"
+ fi
+ fi
+ esac
+ done >"$1.sq" <"$1"
test -s "$1.sq" || return
used=
*" $sha1 "*) continue ;;
esac
printf '%s\n' "$pick $sha1 $message"
+ used="$used$sha1 "
while read -r squash action msg
do
- case "$message" in
- "$msg"*)
+ case " $used" in
+ *" $squash "*) continue ;;
+ esac
+ emit=0
+ case "$action" in
+ +*)
+ action="${action#+}"
+ # full sha1 prefix test
+ case "$msg" in "$sha1"*) emit=1;; esac ;;
+ *)
+ # message prefix test
+ case "$message" in "$msg"*) emit=1;; esac ;;
+ esac
+ if test $emit = 1; then
printf '%s\n' "$action $squash $action! $msg"
used="$used$squash "
- ;;
- esac
+ fi
done <"$1.sq"
done >"$1.rearranged" <"$1"
cat "$1.rearranged" >"$1"
OK_TO_SKIP_PRE_REBASE=yes
;;
--verify)
+ OK_TO_SKIP_PRE_REBASE=
;;
--continue)
is_standalone "$@" || usage
record_in_rewritten "$(cat "$DOTEST"/stopped-sha)"
- require_clean_work_tree
+ require_clean_work_tree "rebase"
do_rest
;;
--abort)
comment_for_reflog start
- require_clean_work_tree
+ require_clean_work_tree "rebase" "Please commit or stash them."
if test ! -z "$1"
then
diff --git a/git-rebase.sh b/git-rebase.sh
index 26d5197a0fb627eb6991910c229eacbf9b190638..cbb0ea90ed410d5af68ce814b7fd93a90829f3ee 100755 (executable)
--- a/git-rebase.sh
+++ b/git-rebase.sh
--no-verify)
OK_TO_SKIP_PRE_REBASE=yes
;;
+ --verify)
+ OK_TO_SKIP_PRE_REBASE=
+ ;;
--continue)
test -d "$dotest" -o -d "$GIT_DIR"/rebase-apply ||
die "No rebase in progress?"
fi
fi
-# The tree must be really really clean.
-if ! git update-index --ignore-submodules --refresh > /dev/null; then
- echo >&2 "cannot rebase: you have unstaged changes"
- git diff-files --name-status -r --ignore-submodules -- >&2
- exit 1
-fi
-diff=$(git diff-index --cached --name-status -r --ignore-submodules HEAD --)
-case "$diff" in
-?*) echo >&2 "cannot rebase: your index contains uncommitted changes"
- echo >&2 "$diff"
- exit 1
- ;;
-esac
+require_clean_work_tree "rebase" "Please commit or stash them."
if test -z "$rebase_root"
then
diff --git a/git-send-email.perl b/git-send-email.perl
index c1d8edbdd324fc1eca7bc475cbf31bd913036682..76565de2ee517f48001ffacca32e3c08320cfe38 100755 (executable)
--- a/git-send-email.perl
+++ b/git-send-email.perl
use Data::Dumper;
use Term::ANSIColor;
use File::Temp qw/ tempdir tempfile /;
+use File::Spec::Functions qw(catfile);
use Error qw(:try);
use Git;
--envelope-sender <str> * Email envelope sender.
--smtp-server <str:int> * Outgoing SMTP server to use. The port
is optional. Default 'localhost'.
+ --smtp-server-option <str> * Outgoing SMTP server option to use.
--smtp-server-port <int> * Outgoing SMTP server port.
--smtp-user <str> * Username for SMTP-AUTH.
--smtp-pass <str> * Password for SMTP-AUTH; not necessary.
Automating:
--identity <str> * Use the sendemail.<id> options.
+ --to-cmd <str> * Email To: via `<str> \$patch_path`
--cc-cmd <str> * Email Cc: via `<str> \$patch_path`
--suppress-cc <str> * author, self, sob, cc, cccmd, body, bodycc, all.
--[no-]signed-off-by-cc * Send to Signed-off-by: addresses. Default on.
my $smtp;
my $auth;
-sub unique_email_list(@);
-sub cleanup_compose_files();
-
# Variables we fill in automatically, or via prompting:
-my (@to,$no_to,@cc,$no_cc,@initial_cc,@bcclist,$no_bcc,@xh,
+my (@to,$no_to,@initial_to,@cc,$no_cc,@initial_cc,@bcclist,$no_bcc,@xh,
$initial_reply_to,$initial_subject,@files,
$author,$sender,$smtp_authpass,$annotate,$compose,$time);
}
# Variables with corresponding config settings
-my ($thread, $chain_reply_to, $suppress_from, $signed_off_by_cc, $cc_cmd);
-my ($smtp_server, $smtp_server_port, $smtp_authuser, $smtp_encryption);
-my ($identity, $aliasfiletype, @alias_files, @smtp_host_parts, $smtp_domain);
+my ($thread, $chain_reply_to, $suppress_from, $signed_off_by_cc);
+my ($to_cmd, $cc_cmd);
+my ($smtp_server, $smtp_server_port, @smtp_server_options);
+my ($smtp_authuser, $smtp_encryption);
+my ($identity, $aliasfiletype, @alias_files, $smtp_domain);
my ($validate, $confirm);
my (@suppress_cc);
my ($auto_8bit_encoding);
my %config_settings = (
"smtpserver" => \$smtp_server,
"smtpserverport" => \$smtp_server_port,
+ "smtpserveroption" => \@smtp_server_options,
"smtpuser" => \$smtp_authuser,
"smtppass" => \$smtp_authpass,
- "smtpdomain" => \$smtp_domain,
- "to" => \@to,
+ "smtpdomain" => \$smtp_domain,
+ "to" => \@initial_to,
+ "tocmd" => \$to_cmd,
"cc" => \@initial_cc,
"cccmd" => \$cc_cmd,
"aliasfiletype" => \$aliasfiletype,
my $rc = GetOptions("sender|from=s" => \$sender,
"in-reply-to=s" => \$initial_reply_to,
"subject=s" => \$initial_subject,
- "to=s" => \@to,
+ "to=s" => \@initial_to,
+ "to-cmd=s" => \$to_cmd,
"no-to" => \$no_to,
"cc=s" => \@initial_cc,
"no-cc" => \$no_cc,
"no-bcc" => \$no_bcc,
"chain-reply-to!" => \$chain_reply_to,
"smtp-server=s" => \$smtp_server,
+ "smtp-server-option=s" => \@smtp_server_options,
"smtp-server-port=s" => \$smtp_server_port,
"smtp-user=s" => \$smtp_authuser,
"smtp-pass:s" => \$smtp_authpass,
if (@suppress_cc) {
foreach my $entry (@suppress_cc) {
die "Unknown --suppress-cc field: '$entry'\n"
- unless $entry =~ /^(all|cccmd|cc|author|self|sob|body|bodycc)$/;
+ unless $entry =~ /^(?:all|cccmd|cc|author|self|sob|body|bodycc)$/;
$suppress_cc{$entry} = 1;
}
}
# Verify the user input
-foreach my $entry (@to) {
+foreach my $entry (@initial_to) {
die "Comma in --to entry: $entry'\n" unless $entry !~ m/,/;
}
push @rev_list_opts, "--", @ARGV;
@ARGV = ();
} elsif (-d $f and !check_file_rev_conflict($f)) {
- opendir(DH,$f)
+ opendir my $dh, $f
or die "Failed to opendir $f: $!";
- push @files, grep { -f $_ } map { +$f . "/" . $_ }
- sort readdir(DH);
- closedir(DH);
+ push @files, grep { -f $_ } map { catfile($f, $_) }
+ sort readdir $dh;
+ closedir $dh;
} elsif ((-f $f or -p $f) and !check_file_rev_conflict($f)) {
push @files, $f;
} else {
usage();
}
-sub get_patch_subject($) {
+sub get_patch_subject {
my $fn = shift;
open (my $fh, '<', $fn);
while (my $line = <$fh>) {
$compose_filename = ($repo ?
tempfile(".gitsendemail.msg.XXXXXX", DIR => $repo->repo_path()) :
tempfile(".gitsendemail.msg.XXXXXX", DIR => "."))[1];
- open(C,">",$compose_filename)
+ open my $c, ">", $compose_filename
or die "Failed to open for writing $compose_filename: $!";
my $tpl_subject = $initial_subject || '';
my $tpl_reply_to = $initial_reply_to || '';
- print C <<EOT;
+ print $c <<EOT;
From $tpl_sender # This line is ignored.
GIT: Lines beginning in "GIT:" will be removed.
GIT: Consider including an overall diffstat or table of contents
EOT
for my $f (@files) {
- print C get_patch_subject($f);
+ print $c get_patch_subject($f);
}
- close(C);
+ close $c;
if ($annotate) {
do_edit($compose_filename, @files);
do_edit($compose_filename);
}
- open(C2,">",$compose_filename . ".final")
+ open my $c2, ">", $compose_filename . ".final"
or die "Failed to open $compose_filename.final : " . $!;
- open(C,"<",$compose_filename)
+ open $c, "<", $compose_filename
or die "Failed to open $compose_filename : " . $!;
my $need_8bit_cte = file_has_nonascii($compose_filename);
my $in_body = 0;
my $summary_empty = 1;
- while(<C>) {
+ while(<$c>) {
next if m/^GIT:/;
if ($in_body) {
$summary_empty = 0 unless (/^\n$/);
} elsif (/^\n$/) {
$in_body = 1;
if ($need_8bit_cte) {
- print C2 "MIME-Version: 1.0\n",
+ print $c2 "MIME-Version: 1.0\n",
"Content-Type: text/plain; ",
"charset=UTF-8\n",
"Content-Transfer-Encoding: 8bit\n";
print "To/Cc/Bcc fields are not interpreted yet, they have been ignored\n";
next;
}
- print C2 $_;
+ print $c2 $_;
}
- close(C);
- close(C2);
+ close $c;
+ close $c2;
if ($summary_empty) {
print "Summary email is empty, skipping it\n";
my %broken_encoding;
-sub file_declares_8bit_cte($) {
+sub file_declares_8bit_cte {
my $fn = shift;
open (my $fh, '<', $fn);
while (my $line = <$fh>) {
if (!$force) {
for my $f (@files) {
- if (get_patch_subject($f) =~ /\*\*\* SUBJECT HERE \*\*\*/) {
+ if (get_patch_subject($f) =~ /\Q*** SUBJECT HERE ***\E/) {
die "Refusing to send because the patch\n\t$f\n"
. "has the template subject '*** SUBJECT HERE ***'. "
. "Pass --force if you really want to send.\n";
$prompting++;
}
-if (!@to) {
+if (!@initial_to && !defined $to_cmd) {
my $to = ask("Who should the emails be sent to? ");
- push @to, parse_address_line($to) if defined $to; # sanitized/validated later
+ push @initial_to, parse_address_line($to) if defined $to; # sanitized/validated later
$prompting++;
}
return $aliases{$alias} ? expand_aliases(@{$aliases{$alias}}) : $alias;
}
-@to = expand_aliases(@to);
-@to = (map { sanitize_address($_) } @to);
+@initial_to = expand_aliases(@initial_to);
+@initial_to = (map { sanitize_address($_) } @initial_to);
@initial_cc = expand_aliases(@initial_cc);
@bcclist = expand_aliases(@bcclist);
sub extract_valid_address {
my $address = shift;
- my $local_part_regexp = '[^<>"\s@]+';
- my $domain_regexp = '[^.<>"\s@]+(?:\.[^.<>"\s@]+)+';
+ my $local_part_regexp = qr/[^<>"\s@]+/;
+ my $domain_regexp = qr/[^.<>"\s@]+(?:\.[^.<>"\s@]+)+/;
# check for a local address:
return $address if ($address =~ /^($local_part_regexp)$/);
last if (defined $du_part and $du_part ne '');
}
if (not defined $du_part or $du_part eq '') {
- use Sys::Hostname qw();
+ require Sys::Hostname;
$du_part = 'user@' . Sys::Hostname::hostname();
}
my $message_id_template = "<%s-git-send-email-%s>";
sub is_rfc2047_quoted {
my $s = shift;
- my $token = '[^][()<>@,;:"\/?.= \000-\037\177-\377]+';
- my $encoded_text = '[!->@-~]+';
+ my $token = qr/[^][()<>@,;:"\/?.= \000-\037\177-\377]+/;
+ my $encoded_text = qr/[!->@-~]+/;
length($s) <= 75 &&
$s =~ m/^(?:"[[:ascii:]]*"|=\?$token\?$token\?$encoded_text\?=)$/o;
}
my ($recipient_name, $recipient_addr) = ($recipient =~ /^(.*?)\s*(<.*)/);
if (not $recipient_name) {
- return "$recipient";
+ return $recipient;
}
# if recipient_name is already quoted, do nothing
# double quotes are needed if specials or CTLs are included
elsif ($recipient_name =~ /[][()<>@,;:\\".\000-\037\177]/) {
$recipient_name =~ s/(["\\\r])/\\$1/g;
- $recipient_name = "\"$recipient_name\"";
+ $recipient_name = qq["$recipient_name"];
}
return "$recipient_name $recipient_addr";
}
}
+ unshift (@sendmail_parameters, @smtp_server_options);
+
if ($dry_run) {
# We don't want to send the email.
} elsif ($smtp_server =~ m#^/#) {
exec($smtp_server, @sendmail_parameters) or die $!;
}
print $sm "$header\n$message";
- close $sm or die $?;
+ close $sm or die $!;
} else {
if (!defined $smtp_server) {
$message_num = 0;
foreach my $t (@files) {
- open(F,"<",$t) or die "can't open file $t";
+ open my $fh, "<", $t or die "can't open file $t";
my $author = undef;
my $author_encoding;
my $has_content_type;
my $body_encoding;
+ @to = ();
@cc = ();
@xh = ();
my $input_format = undef;
$message = "";
$message_num++;
# First unfold multiline header fields
- while(<F>) {
+ while(<$fh>) {
last if /^\s*$/;
if (/^\s+\S/ and @header) {
chomp($header[$#header]);
$1, $_) unless $quiet;
push @cc, $1;
}
+ elsif (/^To:\s+(.*)$/) {
+ foreach my $addr (parse_address_line($1)) {
+ printf("(mbox) Adding to: %s from line '%s'\n",
+ $addr, $_) unless $quiet;
+ push @to, sanitize_address($addr);
+ }
+ }
elsif (/^Cc:\s+(.*)$/) {
foreach my $addr (parse_address_line($1)) {
if (unquote_rfc2047($addr) eq $sender) {
}
}
# Now parse the message body
- while(<F>) {
+ while(<$fh>) {
$message .= $_;
if (/^(Signed-off-by|Cc): (.*)$/i) {
chomp;
$c, $_) unless $quiet;
}
}
- close F;
-
- if (defined $cc_cmd && !$suppress_cc{'cccmd'}) {
- open(F, "$cc_cmd \Q$t\E |")
- or die "(cc-cmd) Could not execute '$cc_cmd'";
- while(<F>) {
- my $c = $_;
- $c =~ s/^\s*//g;
- $c =~ s/\n$//g;
- next if ($c eq $sender and $suppress_from);
- push @cc, $c;
- printf("(cc-cmd) Adding cc: %s from: '%s'\n",
- $c, $cc_cmd) unless $quiet;
- }
- close F
- or die "(cc-cmd) failed to close pipe to '$cc_cmd'";
- }
+ close $fh;
+
+ push @to, recipients_cmd("to-cmd", "to", $to_cmd, $t)
+ if defined $to_cmd;
+ push @cc, recipients_cmd("cc-cmd", "cc", $cc_cmd, $t)
+ if defined $cc_cmd && !$suppress_cc{'cccmd'};
if ($broken_encoding{$t} && !$has_content_type) {
$has_content_type = 1;
($confirm =~ /^(?:auto|compose)$/ && $compose && $message_num == 1));
$needs_confirm = "inform" if ($needs_confirm && $confirm_unconfigured && @cc);
+ @to = (@initial_to, @to);
@cc = (@initial_cc, @cc);
my $message_was_sent = send_message();
# set up for the next message
if ($thread && $message_was_sent &&
- (chain_reply_to() || !defined $reply_to || length($reply_to) == 0)) {
+ (chain_reply_to() || !defined $reply_to || length($reply_to) == 0 ||
+ $message_num == 1)) {
$reply_to = $message_id;
if (length $references > 0) {
$references .= "\n $message_id";
$message_id = undef;
}
+# Execute a command (e.g. $to_cmd) to get a list of email addresses
+# and return a results array
+sub recipients_cmd {
+ my ($prefix, $what, $cmd, $file) = @_;
+
+ my $sanitized_sender = sanitize_address($sender);
+ my @addresses = ();
+ open my $fh, "$cmd \Q$file\E |"
+ or die "($prefix) Could not execute '$cmd'";
+ while (my $address = <$fh>) {
+ $address =~ s/^\s*//g;
+ $address =~ s/\s*$//g;
+ $address = sanitize_address($address);
+ next if ($address eq $sanitized_sender and $suppress_from);
+ push @addresses, $address;
+ printf("($prefix) Adding %s: %s from: '%s'\n",
+ $what, $address, $cmd) unless $quiet;
+ }
+ close $fh
+ or die "($prefix) failed to close pipe to '$cmd'";
+ return @addresses;
+}
+
cleanup_compose_files();
-sub cleanup_compose_files() {
+sub cleanup_compose_files {
unlink($compose_filename, $compose_filename . ".final") if $compose;
}
$smtp->quit if $smtp;
-sub unique_email_list(@) {
+sub unique_email_list {
my %seen;
my @emails;
diff --git a/git-sh-setup.sh b/git-sh-setup.sh
index 8d54b73d3208064829613499580bad541586c725..aa16b8356507e4e669ee5c4cb4b5a667942d559e 100644 (file)
--- a/git-sh-setup.sh
+++ b/git-sh-setup.sh
die "fatal: $0 cannot be used without a working tree."
}
+require_clean_work_tree () {
+ git rev-parse --verify HEAD >/dev/null || exit 1
+ git update-index -q --ignore-submodules --refresh
+ err=0
+
+ if ! git diff-files --quiet --ignore-submodules
+ then
+ echo >&2 "Cannot $1: You have unstaged changes."
+ err=1
+ fi
+
+ if ! git diff-index --cached --quiet --ignore-submodules HEAD --
+ then
+ if [ $err = 0 ]
+ then
+ echo >&2 "Cannot $1: Your index contains uncommitted changes."
+ else
+ echo >&2 "Additionally, your index contains uncommitted changes."
+ fi
+ err=1
+ fi
+
+ if [ $err = 1 ]
+ then
+ test -n "$2" && echo >&2 "$2"
+ exit 1
+ fi
+}
+
get_author_ident_from_commit () {
pick_author_script='
/^author /{
find () {
/usr/bin/find "$@"
}
+ is_absolute_path () {
+ case "$1" in
+ [/\\]* | [A-Za-z]:*)
+ return 0 ;;
+ esac
+ return 1
+ }
;;
+*)
+ is_absolute_path () {
+ case "$1" in
+ /*)
+ return 0 ;;
+ esac
+ return 1
+ }
esac
diff --git a/git-submodule.sh b/git-submodule.sh
index 9e752904b3d492ef899f172291160c88bf2fa51b..8b9058971767dbb4d94e996876f6ba7ed178ddd6 100755 (executable)
--- a/git-submodule.sh
+++ b/git-submodule.sh
cmd_update()
{
# parse $args after "submodule ... update".
- orig_args="$@"
+ orig_flags=
while test $# -ne 0
do
case "$1" in
-q|--quiet)
- shift
GIT_QUIET=1
;;
-i|--init)
init=1
- shift
;;
-N|--no-fetch)
- shift
nofetch=1
;;
-r|--rebase)
- shift
update="rebase"
;;
--reference)
case "$2" in '') usage ;; esac
reference="--reference=$2"
- shift 2
+ orig_flags="$orig_flags $(git rev-parse --sq-quote "$1")"
+ shift
;;
--reference=*)
reference="$1"
- shift
;;
-m|--merge)
- shift
update="merge"
;;
--recursive)
- shift
recursive=1
;;
--)
break
;;
esac
+ orig_flags="$orig_flags $(git rev-parse --sq-quote "$1")"
+ shift
done
if test -n "$init"
if test -n "$recursive"
then
- (clear_local_git_env; cd "$path" && cmd_update $orig_args) ||
+ (clear_local_git_env; cd "$path" && eval cmd_update "$orig_flags") ||
die "Failed to recurse into submodule path '$path'"
fi
done
cmd_status()
{
# parse $args after "submodule ... status".
- orig_args="$@"
+ orig_flags=
while test $# -ne 0
do
case "$1" in
break
;;
esac
+ orig_flags="$orig_flags $(git rev-parse --sq-quote "$1")"
shift
done
prefix="$displaypath/"
clear_local_git_env
cd "$path" &&
- cmd_status $orig_args
+ eval cmd_status "$orig_args"
) ||
die "Failed to recurse into submodule path '$path'"
fi
diff --git a/git-svn.perl b/git-svn.perl
index 757de82161e05b9d12c489efeff05c7fec341fe4..177dd259cd53cde017d73db5ccbecc7ed7dfa573 100755 (executable)
--- a/git-svn.perl
+++ b/git-svn.perl
$_version, $_fetch_all, $_no_rebase, $_fetch_parent,
$_merge, $_strategy, $_dry_run, $_local,
$_prefix, $_no_checkout, $_url, $_verbose,
- $_git_format, $_commit_url, $_tag);
+ $_git_format, $_commit_url, $_tag, $_merge_info);
$Git::SVN::_follow_parent = 1;
$_q ||= 0;
my %remote_opts = ( 'username=s' => \$Git::SVN::Prompt::_username,
'commit-url=s' => \$_commit_url,
'revision|r=i' => \$_revision,
'no-rebase' => \$_no_rebase,
+ 'mergeinfo=s' => \$_merge_info,
%cmt_opts, %fc_opts } ],
branch => [ \&cmd_branch,
'Create a branch in the SVN repository',
print "Committed r$_[0]\n";
$cmt_rev = $_[0];
},
+ mergeinfo => $_merge_info,
svn_path => '');
if (!SVN::Git::Editor->new(\%ed_opts)->apply_diff) {
print "No changes\n$d~1 == $d\n";
$self->{path_prefix} = length $self->{svn_path} ?
"$self->{svn_path}/" : '';
$self->{config} = $opts->{config};
+ $self->{mergeinfo} = $opts->{mergeinfo};
return $self;
}
$self->SUPER::change_file_prop($fbat, $pname, $pval, $self->{pool});
}
+sub change_dir_prop {
+ my ($self, $pbat, $pname, $pval) = @_;
+ $self->SUPER::change_dir_prop($pbat, $pname, $pval, $self->{pool});
+}
+
sub _chg_file_get_blob ($$$$) {
my ($self, $fbat, $m, $which) = @_;
my $fh = $::_repository->temp_acquire("git_blob_$which");
fatal("Invalid change type: $f");
}
}
+
+ if (defined($self->{mergeinfo})) {
+ $self->change_dir_prop($self->{bat}{''}, "svn:mergeinfo",
+ $self->{mergeinfo});
+ }
$self->rmdirs if $_rmdir;
if (@$mods == 0) {
$self->abort_edit;
diff --git a/git-web--browse.sh b/git-web--browse.sh
index 3fc4166b25714911b6b1294c7439da1e237e4918..e9de241dd004a9f5d6d6d8e6a300a1fa5949a46d 100755 (executable)
--- a/git-web--browse.sh
+++ b/git-web--browse.sh
valid_tool() {
case "$1" in
- firefox | iceweasel | chrome | google-chrome | chromium | konqueror | w3m | links | lynx | dillo | open | start)
- ;; # happy
- *)
- valid_custom_tool "$1" || return 1
- ;;
+ firefox | iceweasel | seamonkey | iceape | \
+ chrome | google-chrome | chromium | chromium-browser |\
+ konqueror | opera | w3m | elinks | links | lynx | dillo | open | start)
+ ;; # happy
+ *)
+ valid_custom_tool "$1" || return 1
+ ;;
esac
}
init_browser_path() {
browser_path=$(git config "browser.$1.path")
- test -z "$browser_path" && browser_path="$1"
+ if test -z "$browser_path" &&
+ test "$1" = chromium &&
+ type chromium-browser >/dev/null 2>&1
+ then
+ browser_path=chromium-browser
+ fi
+ : ${browser_path:="$1"}
}
while test $# != 0
do
- case "$1" in
+ case "$1" in
-b|--browser*|-t|--tool*)
- case "$#,$1" in
+ case "$#,$1" in
*,*=*)
- browser=`expr "z$1" : 'z-[^=]*=\(.*\)'`
- ;;
+ browser=`expr "z$1" : 'z-[^=]*=\(.*\)'`
+ ;;
1,*)
- usage ;;
+ usage ;;
*)
- browser="$2"
- shift ;;
- esac
- ;;
+ browser="$2"
+ shift ;;
+ esac
+ ;;
-c|--config*)
- case "$#,$1" in
+ case "$#,$1" in
*,*=*)
- conf=`expr "z$1" : 'z-[^=]*=\(.*\)'`
- ;;
+ conf=`expr "z$1" : 'z-[^=]*=\(.*\)'`
+ ;;
1,*)
- usage ;;
+ usage ;;
*)
- conf="$2"
- shift ;;
- esac
- ;;
+ conf="$2"
+ shift ;;
+ esac
+ ;;
--)
- break
- ;;
+ break
+ ;;
-*)
- usage
- ;;
+ usage
+ ;;
*)
- break
- ;;
- esac
- shift
+ break
+ ;;
+ esac
+ shift
done
test $# = 0 && usage
if test -z "$browser"
then
- for opt in "$conf" "web.browser"
- do
- test -z "$opt" && continue
- browser="`git config $opt`"
- test -z "$browser" || break
- done
- if test -n "$browser" && ! valid_tool "$browser"; then
- echo >&2 "git config option $opt set to unknown browser: $browser"
- echo >&2 "Resetting to default..."
- unset browser
- fi
+ for opt in "$conf" "web.browser"
+ do
+ test -z "$opt" && continue
+ browser="`git config $opt`"
+ test -z "$browser" || break
+ done
+ if test -n "$browser" && ! valid_tool "$browser"; then
+ echo >&2 "git config option $opt set to unknown browser: $browser"
+ echo >&2 "Resetting to default..."
+ unset browser
+ fi
fi
if test -z "$browser" ; then
- if test -n "$DISPLAY"; then
- browser_candidates="firefox iceweasel google-chrome chrome chromium konqueror w3m links lynx dillo"
- if test "$KDE_FULL_SESSION" = "true"; then
- browser_candidates="konqueror $browser_candidates"
+ if test -n "$DISPLAY"; then
+ browser_candidates="firefox iceweasel google-chrome chrome chromium chromium-browser konqueror opera seamonkey iceape w3m elinks links lynx dillo"
+ if test "$KDE_FULL_SESSION" = "true"; then
+ browser_candidates="konqueror $browser_candidates"
+ fi
+ else
+ browser_candidates="w3m elinks links lynx"
fi
- else
- browser_candidates="w3m links lynx"
- fi
- # SECURITYSESSIONID indicates an OS X GUI login session
- if test -n "$SECURITYSESSIONID" \
- -o "$TERM_PROGRAM" = "Apple_Terminal" ; then
- browser_candidates="open $browser_candidates"
- fi
- # /bin/start indicates MinGW
- if test -x /bin/start; then
- browser_candidates="start $browser_candidates"
- fi
-
- for i in $browser_candidates; do
- init_browser_path $i
- if type "$browser_path" > /dev/null 2>&1; then
- browser=$i
- break
+ # SECURITYSESSIONID indicates an OS X GUI login session
+ if test -n "$SECURITYSESSIONID" \
+ -o "$TERM_PROGRAM" = "Apple_Terminal" ; then
+ browser_candidates="open $browser_candidates"
fi
- done
- test -z "$browser" && die "No known browser available."
+ # /bin/start indicates MinGW
+ if test -x /bin/start; then
+ browser_candidates="start $browser_candidates"
+ fi
+
+ for i in $browser_candidates; do
+ init_browser_path $i
+ if type "$browser_path" > /dev/null 2>&1; then
+ browser=$i
+ break
+ fi
+ done
+ test -z "$browser" && die "No known browser available."
else
- valid_tool "$browser" || die "Unknown browser '$browser'."
+ valid_tool "$browser" || die "Unknown browser '$browser'."
- init_browser_path "$browser"
+ init_browser_path "$browser"
- if test -z "$browser_cmd" && ! type "$browser_path" > /dev/null 2>&1; then
- die "The browser $browser is not available as '$browser_path'."
- fi
+ if test -z "$browser_cmd" && ! type "$browser_path" > /dev/null 2>&1; then
+ die "The browser $browser is not available as '$browser_path'."
+ fi
fi
case "$browser" in
- firefox|iceweasel)
+firefox|iceweasel|seamonkey|iceape)
# Check version because firefox < 2.0 does not support "-new-tab".
vers=$(expr "$($browser_path -version)" : '.* \([0-9][0-9]*\)\..*')
NEWTAB='-new-tab'
test "$vers" -lt 2 && NEWTAB=''
"$browser_path" $NEWTAB "$@" &
;;
- google-chrome|chrome|chromium)
- # Actual command for chromium is chromium-browser.
+google-chrome|chrome|chromium|chromium-browser)
# No need to specify newTab. It's default in chromium
eval "$browser_path" "$@" &
;;
- konqueror)
+konqueror)
case "$(basename "$browser_path")" in
- konqueror)
+ konqueror)
# It's simpler to use kfmclient to open a new tab in konqueror.
browser_path="$(echo "$browser_path" | sed -e 's/konqueror$/kfmclient/')"
type "$browser_path" > /dev/null 2>&1 || die "No '$browser_path' found."
eval "$browser_path" newTab "$@"
;;
- kfmclient)
+ kfmclient)
eval "$browser_path" newTab "$@"
;;
- *)
+ *)
"$browser_path" "$@" &
;;
esac
;;
- w3m|links|lynx|open)
+w3m|elinks|links|lynx|open)
eval "$browser_path" "$@"
;;
- start)
- exec "$browser_path" '"web-browse"' "$@"
- ;;
- dillo)
+start)
+ exec "$browser_path" '"web-browse"' "$@"
+ ;;
+opera|dillo)
"$browser_path" "$@" &
;;
- *)
+*)
if test -n "$browser_cmd"; then
- ( eval $browser_cmd "$@" )
+ ( eval $browser_cmd "$@" )
fi
;;
esac
index 0409ac9fd3f1ea36680189e07116e58b2630ccad..ef598c3e7053b8dd2859f4d582ce2917a804fe42 100644 (file)
--- a/git.c
+++ b/git.c
static int use_pager = -1;
struct pager_config {
const char *cmd;
- int val;
+ int want;
+ char *value;
};
static int pager_command_config(const char *var, const char *value, void *data)
{
struct pager_config *c = data;
- if (!prefixcmp(var, "pager.") && !strcmp(var + 6, c->cmd))
- c->val = git_config_bool(var, value);
+ if (!prefixcmp(var, "pager.") && !strcmp(var + 6, c->cmd)) {
+ int b = git_config_maybe_bool(var, value);
+ if (b >= 0)
+ c->want = b;
+ else {
+ c->want = 1;
+ c->value = xstrdup(value);
+ }
+ }
return 0;
}
{
struct pager_config c;
c.cmd = cmd;
- c.val = -1;
+ c.want = -1;
+ c.value = NULL;
git_config(pager_command_config, &c);
- return c.val;
+ if (c.value)
+ pager_program = c.value;
+ return c.want;
}
static void commit_pager_choice(void) {
alias_string = alias_lookup(alias_command);
if (alias_string) {
if (alias_string[0] == '!') {
+ const char **alias_argv;
+ int argc = *argcp, i;
+
commit_pager_choice();
- if (*argcp > 1) {
- struct strbuf buf;
-
- strbuf_init(&buf, PATH_MAX);
- strbuf_addstr(&buf, alias_string);
- sq_quote_argv(&buf, (*argv) + 1, PATH_MAX);
- free(alias_string);
- alias_string = buf.buf;
- }
- trace_printf("trace: alias to shell cmd: %s => %s\n",
- alias_command, alias_string + 1);
- ret = system(alias_string + 1);
- if (ret >= 0 && WIFEXITED(ret) &&
- WEXITSTATUS(ret) != 127)
- exit(WEXITSTATUS(ret));
- die("Failed to run '%s' when expanding alias '%s'",
- alias_string + 1, alias_command);
+
+ /* build alias_argv */
+ alias_argv = xmalloc(sizeof(*alias_argv) * (argc + 1));
+ alias_argv[0] = alias_string + 1;
+ for (i = 1; i < argc; ++i)
+ alias_argv[i] = (*argv)[i];
+ alias_argv[argc] = NULL;
+
+ ret = run_command_v_opt(alias_argv, RUN_USING_SHELL);
+ if (ret >= 0) /* normal exit */
+ exit(ret);
+
+ die_errno("While expanding alias '%s': '%s'",
+ alias_command, alias_string + 1);
}
count = split_cmdline(alias_string, &new_argv);
if (count < 0)
use_pager = check_pager_config(p->cmd);
if (use_pager == -1 && p->option & USE_PAGER)
use_pager = 1;
+
+ if ((p->option & (RUN_SETUP | RUN_SETUP_GENTLY)) &&
+ startup_info->have_repository) /* get_git_dir() may set up repo, avoid that */
+ trace_repo_setup(prefix);
}
commit_pager_choice();
const char *cmd = argv[0];
static struct cmd_struct commands[] = {
{ "add", cmd_add, RUN_SETUP | NEED_WORK_TREE },
- { "stage", cmd_add, RUN_SETUP | NEED_WORK_TREE },
{ "annotate", cmd_annotate, RUN_SETUP },
{ "apply", cmd_apply, RUN_SETUP_GENTLY },
{ "archive", cmd_archive },
{ "branch", cmd_branch, RUN_SETUP },
{ "bundle", cmd_bundle, RUN_SETUP_GENTLY },
{ "cat-file", cmd_cat_file, RUN_SETUP },
+ { "check-attr", cmd_check_attr, RUN_SETUP },
+ { "check-ref-format", cmd_check_ref_format },
{ "checkout", cmd_checkout, RUN_SETUP | NEED_WORK_TREE },
{ "checkout-index", cmd_checkout_index,
RUN_SETUP | NEED_WORK_TREE},
- { "check-ref-format", cmd_check_ref_format },
- { "check-attr", cmd_check_attr, RUN_SETUP },
{ "cherry", cmd_cherry, RUN_SETUP },
{ "cherry-pick", cmd_cherry_pick, RUN_SETUP | NEED_WORK_TREE },
- { "clone", cmd_clone },
{ "clean", cmd_clean, RUN_SETUP | NEED_WORK_TREE },
+ { "clone", cmd_clone },
{ "commit", cmd_commit, RUN_SETUP | NEED_WORK_TREE },
{ "commit-tree", cmd_commit_tree, RUN_SETUP },
{ "config", cmd_config, RUN_SETUP_GENTLY },
{ "init-db", cmd_init_db },
{ "log", cmd_log, RUN_SETUP },
{ "ls-files", cmd_ls_files, RUN_SETUP },
- { "ls-tree", cmd_ls_tree, RUN_SETUP },
{ "ls-remote", cmd_ls_remote, RUN_SETUP_GENTLY },
+ { "ls-tree", cmd_ls_tree, RUN_SETUP },
{ "mailinfo", cmd_mailinfo },
{ "mailsplit", cmd_mailsplit },
{ "merge", cmd_merge, RUN_SETUP | NEED_WORK_TREE },
{ "notes", cmd_notes, RUN_SETUP },
{ "pack-objects", cmd_pack_objects, RUN_SETUP },
{ "pack-redundant", cmd_pack_redundant, RUN_SETUP },
+ { "pack-refs", cmd_pack_refs, RUN_SETUP },
{ "patch-id", cmd_patch_id },
{ "peek-remote", cmd_ls_remote, RUN_SETUP_GENTLY },
{ "pickaxe", cmd_blame, RUN_SETUP },
{ "receive-pack", cmd_receive_pack },
{ "reflog", cmd_reflog, RUN_SETUP },
{ "remote", cmd_remote, RUN_SETUP },
+ { "remote-ext", cmd_remote_ext },
+ { "remote-fd", cmd_remote_fd },
{ "replace", cmd_replace, RUN_SETUP },
- { "repo-config", cmd_config, RUN_SETUP_GENTLY },
+ { "repo-config", cmd_repo_config, RUN_SETUP_GENTLY },
{ "rerere", cmd_rerere, RUN_SETUP },
{ "reset", cmd_reset, RUN_SETUP },
{ "rev-list", cmd_rev_list, RUN_SETUP },
{ "rm", cmd_rm, RUN_SETUP },
{ "send-pack", cmd_send_pack, RUN_SETUP },
{ "shortlog", cmd_shortlog, RUN_SETUP_GENTLY | USE_PAGER },
- { "show-branch", cmd_show_branch, RUN_SETUP },
{ "show", cmd_show, RUN_SETUP },
+ { "show-branch", cmd_show_branch, RUN_SETUP },
+ { "show-ref", cmd_show_ref, RUN_SETUP },
+ { "stage", cmd_add, RUN_SETUP | NEED_WORK_TREE },
{ "status", cmd_status, RUN_SETUP | NEED_WORK_TREE },
{ "stripspace", cmd_stripspace },
{ "symbolic-ref", cmd_symbolic_ref, RUN_SETUP },
{ "update-server-info", cmd_update_server_info, RUN_SETUP },
{ "upload-archive", cmd_upload_archive },
{ "var", cmd_var, RUN_SETUP_GENTLY },
+ { "verify-pack", cmd_verify_pack },
{ "verify-tag", cmd_verify_tag, RUN_SETUP },
{ "version", cmd_version },
{ "whatchanged", cmd_whatchanged, RUN_SETUP },
{ "write-tree", cmd_write_tree, RUN_SETUP },
- { "verify-pack", cmd_verify_pack },
- { "show-ref", cmd_show_ref, RUN_SETUP },
- { "pack-refs", cmd_pack_refs, RUN_SETUP },
};
int i;
static const char ext[] = STRIP_EXTENSION;
diff --git a/gitk-git/gitk b/gitk-git/gitk
index 1b0e09a561e01c51bdc6dbced052e0c753924618..e82c6bfedea5fb9a3d768c0fa1759346c6e5e603 100755 (executable)
--- a/gitk-git/gitk
+++ b/gitk-git/gitk
proc parseviewargs {n arglist} {
global vdatemode vmergeonly vflags vdflags vrevs vfiltered vorigargs env
+ global worddiff git_version
set vdatemode($n) 0
set vmergeonly($n) 0
lappend diffargs $arg
}
"--raw" - "--patch-with-raw" - "--patch-with-stat" -
- "--name-only" - "--name-status" - "--color" - "--color-words" -
+ "--name-only" - "--name-status" - "--color" -
"--log-size" - "--pretty=*" - "--decorate" - "--abbrev-commit" -
"--cc" - "-z" - "--header" - "--parents" - "--boundary" -
"--no-color" - "-g" - "--walk-reflogs" - "--no-walk" -
# These cause our parsing of git log's output to fail, or else
# they're options we want to set ourselves, so ignore them.
}
+ "--color-words*" - "--word-diff=color" {
+ # These trigger a word diff in the console interface,
+ # so help the user by enabling our own support
+ if {[package vcompare $git_version "1.7.2"] >= 0} {
+ set worddiff [mc "Color words"]
+ }
+ }
+ "--word-diff*" {
+ if {[package vcompare $git_version "1.7.2"] >= 0} {
+ set worddiff [mc "Markup words"]
+ }
+ }
"--stat=*" - "--numstat" - "--shortstat" - "--summary" -
"--check" - "--exit-code" - "--quiet" - "--topo-order" -
"--full-history" - "--dense" - "--sparse" -
global viewactive viewinstances vmergeonly
global mainheadid viewmainheadid viewmainheadid_orig
global vcanopt vflags vrevs vorigargs
+ global show_notes
set startmsecs [clock clicks -milliseconds]
set commitidx($view) 0
}
if {[catch {
- set fd [open [concat | git log --no-color -z --pretty=raw --parents \
- --boundary $args "--" $files] r]
+ set fd [open [concat | git log --no-color -z --pretty=raw $show_notes \
+ --parents --boundary $args "--" $files] r]
} err]} {
error_popup "[mc "Error executing git log:"] $err"
return 0
global mainheadid viewmainheadid viewmainheadid_orig pending_select
global isworktree
global varcid vposids vnegids vflags vrevs
+ global show_notes
set isworktree [expr {[exec git rev-parse --is-inside-work-tree] == "true"}]
rereadrefs
set args $vorigargs($view)
}
if {[catch {
- set fd [open [concat | git log --no-color -z --pretty=raw --parents \
- --boundary $args "--" $vfilelimit($view)] r]
+ set fd [open [concat | git log --no-color -z --pretty=raw $show_notes \
+ --parents --boundary $args "--" $vfilelimit($view)] r]
} err]} {
error_popup "[mc "Error executing git log:"] $err"
return
global fprogitem fprogcoord lastprogupdate progupdatepending
global rprogitem rprogcoord rownumsel numcommits
global have_tk85 use_ttk NS
+ global git_version
+ global worddiff
# The "mc" arguments here are purely so that xgettext
# sees the following string as needing to be translated
${NS}::checkbutton .bleft.mid.ignspace -text [mc "Ignore space change"] \
-command changeignorespace -variable ignorespace
pack .bleft.mid.ignspace -side left -padx 5
+
+ set worddiff [mc "Line diff"]
+ if {[package vcompare $git_version "1.7.2"] >= 0} {
+ makedroplist .bleft.mid.worddiff worddiff [mc "Line diff"] \
+ [mc "Markup words"] [mc "Color words"]
+ trace add variable worddiff write changeworddiff
+ pack .bleft.mid.worddiff -side left -padx 5
+ }
+
set ctext .bleft.bottom.ctext
text $ctext -background $bgcolor -foreground $fgcolor \
-state disabled -font textfont \
global ctxbut
bind $cflist $ctxbut {pop_flist_menu %W %X %Y %x %y}
bind $ctext $ctxbut {pop_diff_menu %W %X %Y %x %y}
+ bind $ctext <Button-1> {focus %W}
set maincursor [. cget -cursor]
set textcursor [$ctext cget -cursor]
[lindex [split $commentend .] 0]}]
mark_ctext_line $lnum
}
+ $ctext config -state disabled
return 0
}
$ctext config -state disabled
reselectline
}
+proc changeworddiff {name ix op} {
+ reselectline
+}
+
proc getblobdiffs {ids} {
global blobdifffd diffids env
global diffinhdr treediffs
global diffcontext
global ignorespace
+ global worddiff
global limitdiffs vfilelimit curview
global diffencoding targetline diffnparents
global git_version currdiffsubmod
if {$ignorespace} {
append cmd " -w"
}
+ if {$worddiff ne [mc "Line diff"]} {
+ append cmd " --word-diff=porcelain"
+ }
if {$limitdiffs && $vfilelimit($curview) ne {}} {
set cmd [concat $cmd -- $vfilelimit($curview)]
}
global ctext_file_names ctext_file_lines
global diffinhdr treediffs mergemax diffnparents
global diffencoding jump_to_here targetline diffline currdiffsubmod
+ global worddiff
set nr 0
$ctext conf -state normal
# parse the prefix - one ' ', '-' or '+' for each parent
set prefix [string range $line 0 [expr {$diffnparents - 1}]]
set tag [expr {$diffnparents > 1? "m": "d"}]
+ set dowords [expr {$worddiff ne [mc "Line diff"] && $diffnparents == 1}]
+ set words_pre_markup ""
+ set words_post_markup ""
if {[string trim $prefix " -+"] eq {}} {
# prefix only has " ", "-" and "+" in it: normal diff line
set num [string first "-" $prefix]
+ if {$dowords} {
+ set line [string range $line 1 end]
+ }
if {$num >= 0} {
# removed line, first parent with line is $num
if {$num >= $mergemax} {
set num "max"
}
- $ctext insert end "$line\n" $tag$num
+ if {$dowords && $worddiff eq [mc "Markup words"]} {
+ $ctext insert end "\[-$line-\]" $tag$num
+ } else {
+ $ctext insert end "$line" $tag$num
+ }
+ if {!$dowords} {
+ $ctext insert end "\n" $tag$num
+ }
} else {
set tags {}
if {[string first "+" $prefix] >= 0} {
lappend tags m$num
}
}
+ set words_pre_markup "{+"
+ set words_post_markup "+}"
}
if {$targetline ne {}} {
if {$diffline == $targetline} {
incr diffline
}
}
- $ctext insert end "$line\n" $tags
+ if {$dowords && $worddiff eq [mc "Markup words"]} {
+ $ctext insert end "$words_pre_markup$line$words_post_markup" $tags
+ } else {
+ $ctext insert end "$line" $tags
+ }
+ if {!$dowords} {
+ $ctext insert end "\n" $tags
+ }
}
+ } elseif {$dowords && $prefix eq "~"} {
+ $ctext insert end "\n" {}
} else {
# "\ No newline at end of file",
# or something else we don't recognize
set diffcolors {red "#00a000" blue}
set diffcontext 3
set ignorespace 0
+set worddiff ""
set markbgcolor "#e0e0ff"
set circlecolors {white blue gray blue blue}
set git_version [join [lrange [split [lindex [exec git version] end] .] 0 2] .]
+set show_notes {}
+if {[package vcompare $git_version "1.6.6.2"] >= 0} {
+ set show_notes "--show-notes"
+}
+
set runq {}
set history {}
set historyindex 0
diff --git a/gitk-git/po/pt_br.po b/gitk-git/po/pt_br.po
--- /dev/null
+++ b/gitk-git/po/pt_br.po
@@ -0,0 +1,1277 @@
+# Translation of gitk to Brazilian Portuguese.
+# Copyright (C) 2007 Paul Mackerras, et al.
+# This file is distributed under the same license as the gitk package.
+#
+# Alexandre Erwin Ittner <alexandre@ittner.com.br>, 2010.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: gitk\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2010-01-26 15:47-0800\n"
+"PO-Revision-Date: 2010-12-06 23:39-0200\n"
+"Last-Translator: Alexandre Erwin Ittner <alexandre@ittner.com.br>\n"
+"Language-Team: Brazilian Portuguese <>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: gitk:115
+msgid "Couldn't get list of unmerged files:"
+msgstr "Não foi possível obter a lista dos arquivos não mesclados:"
+
+#: gitk:274
+msgid "Error parsing revisions:"
+msgstr "Erro ao interpretar revisões:"
+
+#: gitk:330
+msgid "Error executing --argscmd command:"
+msgstr "Erro ao executar o comando--argscmd:"
+
+#: gitk:343
+msgid "No files selected: --merge specified but no files are unmerged."
+msgstr ""
+"Nenhum arquivo foi selecionado: --merge especificado mas não há arquivos não-"
+"mesclados."
+
+#: gitk:346
+msgid ""
+"No files selected: --merge specified but no unmerged files are within file "
+"limit."
+msgstr ""
+"Nenhum arquivo foi selecionado: --merge especificado mas não há arquivos não-"
+"mesclados dentro dos limites."
+
+#: gitk:368 gitk:516
+msgid "Error executing git log:"
+msgstr "Erro ao executar git log:"
+
+#: gitk:386 gitk:532
+msgid "Reading"
+msgstr "Lendo"
+
+#: gitk:446 gitk:4271
+msgid "Reading commits..."
+msgstr "Lendo revisões..."
+
+#: gitk:449 gitk:1580 gitk:4274
+msgid "No commits selected"
+msgstr "Nenhuma revisão foi selecionada"
+
+#: gitk:1456
+msgid "Can't parse git log output:"
+msgstr "Não foi possível interpretar a saída do \"git log\":"
+
+#: gitk:1676
+msgid "No commit information available"
+msgstr "Não há informações disponíveis sobre a revisão"
+
+#: gitk:1818
+msgid "mc"
+msgstr "mc"
+
+#: gitk:1853 gitk:4064 gitk:9067 gitk:10607 gitk:10817
+msgid "OK"
+msgstr "Ok"
+
+#: gitk:1855 gitk:4066 gitk:8657 gitk:8736 gitk:8851 gitk:8900 gitk:9069
+#: gitk:10608 gitk:10818
+msgid "Cancel"
+msgstr "Cancelar"
+
+#: gitk:1980
+msgid "Update"
+msgstr "Atualizar"
+
+#: gitk:1981
+msgid "Reload"
+msgstr "Recarregar"
+
+#: gitk:1982
+msgid "Reread references"
+msgstr "Ler as referências novamente"
+
+#: gitk:1983
+msgid "List references"
+msgstr "Listar referências"
+
+#: gitk:1985
+msgid "Start git gui"
+msgstr "Iniciar Git GUI"
+
+#: gitk:1987
+msgid "Quit"
+msgstr "Sair"
+
+#: gitk:1979
+msgid "File"
+msgstr "Arquivo"
+
+#: gitk:1991
+msgid "Preferences"
+msgstr "Preferências"
+
+#: gitk:1990
+msgid "Edit"
+msgstr "Editar"
+
+#: gitk:1995
+msgid "New view..."
+msgstr "Nova vista..."
+
+#: gitk:1996
+msgid "Edit view..."
+msgstr "Editar vista..."
+
+#: gitk:1997
+msgid "Delete view"
+msgstr "Apagar vista"
+
+#: gitk:1999
+msgid "All files"
+msgstr "Todos os arquivos"
+
+#: gitk:1994 gitk:3817
+msgid "View"
+msgstr "Exibir"
+
+#: gitk:2004 gitk:2014 gitk:2787
+msgid "About gitk"
+msgstr "Sobre o gitk"
+
+#: gitk:2005 gitk:2019
+msgid "Key bindings"
+msgstr "Atalhos de teclado"
+
+#: gitk:2003 gitk:2018
+msgid "Help"
+msgstr "Ajuda"
+
+#: gitk:2096 gitk:8132
+msgid "SHA1 ID:"
+msgstr "SHA1 ID:"
+
+#: gitk:2127
+msgid "Row"
+msgstr "Linha"
+
+#: gitk:2165
+msgid "Find"
+msgstr "Encontrar"
+
+#: gitk:2166
+msgid "next"
+msgstr "Próximo"
+
+#: gitk:2167
+msgid "prev"
+msgstr "Anterior"
+
+#: gitk:2168
+msgid "commit"
+msgstr "Revisão"
+
+#: gitk:2171 gitk:2173 gitk:4432 gitk:4455 gitk:4479 gitk:6420 gitk:6492
+#: gitk:6576
+msgid "containing:"
+msgstr "contendo:"
+
+#: gitk:2174 gitk:3298 gitk:3303 gitk:4507
+msgid "touching paths:"
+msgstr "envolvendo os caminhos:"
+
+#: gitk:2175 gitk:4512
+msgid "adding/removing string:"
+msgstr "Adicionando/removendo texto:"
+
+#: gitk:2184 gitk:2186
+msgid "Exact"
+msgstr "Exatamente"
+
+#: gitk:2186 gitk:4587 gitk:6388
+msgid "IgnCase"
+msgstr "Ignorar maiúsculas/minúsculas"
+
+#: gitk:2186 gitk:4481 gitk:4585 gitk:6384
+msgid "Regexp"
+msgstr "Expressão regular"
+
+#: gitk:2188 gitk:2189 gitk:4606 gitk:4636 gitk:4643 gitk:6512 gitk:6580
+msgid "All fields"
+msgstr "Todos os campos"
+
+#: gitk:2189 gitk:4604 gitk:4636 gitk:6451
+msgid "Headline"
+msgstr "Assunto"
+
+#: gitk:2190 gitk:4604 gitk:6451 gitk:6580 gitk:7013
+msgid "Comments"
+msgstr "Descrição da revisão"
+
+#: gitk:2190 gitk:4604 gitk:4608 gitk:4643 gitk:6451 gitk:6948 gitk:8307
+#: gitk:8322
+msgid "Author"
+msgstr "Autor"
+
+#: gitk:2190 gitk:4604 gitk:6451 gitk:6950
+msgid "Committer"
+msgstr "Revisor"
+
+#: gitk:2221
+msgid "Search"
+msgstr "Buscar"
+
+#: gitk:2229
+msgid "Diff"
+msgstr "Diferenças"
+
+#: gitk:2231
+msgid "Old version"
+msgstr "Versão antiga"
+
+#: gitk:2233
+msgid "New version"
+msgstr "Versão nova"
+
+#: gitk:2235
+msgid "Lines of context"
+msgstr "Número de linhas de contexto"
+
+#: gitk:2245
+msgid "Ignore space change"
+msgstr "Ignorar mudanças de caixa"
+
+#: gitk:2304
+msgid "Patch"
+msgstr "Diferenças"
+
+#: gitk:2306
+msgid "Tree"
+msgstr "Árvore"
+
+#: gitk:2463 gitk:2480
+msgid "Diff this -> selected"
+msgstr "Comparar esta revisão com a selecionada"
+
+#: gitk:2464 gitk:2481
+msgid "Diff selected -> this"
+msgstr "Comparar a revisão selecionada com esta"
+
+#: gitk:2465 gitk:2482
+msgid "Make patch"
+msgstr "Criar patch"
+
+#: gitk:2466 gitk:8715
+msgid "Create tag"
+msgstr "Criar etiqueta"
+
+#: gitk:2467 gitk:8831
+msgid "Write commit to file"
+msgstr "Salvar revisão para um arquivo"
+
+#: gitk:2468 gitk:8888
+msgid "Create new branch"
+msgstr "Criar novo ramo"
+
+#: gitk:2469
+msgid "Cherry-pick this commit"
+msgstr "Fazer cherry-pick desta revisão"
+
+#: gitk:2470
+msgid "Reset HEAD branch to here"
+msgstr "Redefinir HEAD para cá"
+
+#: gitk:2471
+msgid "Mark this commit"
+msgstr "Marcar esta revisão"
+
+#: gitk:2472
+msgid "Return to mark"
+msgstr "Voltar à marca"
+
+#: gitk:2473
+msgid "Find descendant of this and mark"
+msgstr "Encontrar descendente e marcar"
+
+#: gitk:2474
+msgid "Compare with marked commit"
+msgstr "Comparar com a revisão marcada"
+
+#: gitk:2488
+msgid "Check out this branch"
+msgstr "Efetuar checkout deste ramo"
+
+#: gitk:2489
+msgid "Remove this branch"
+msgstr "Excluir este ramo"
+
+#: gitk:2496
+msgid "Highlight this too"
+msgstr "Marcar este também"
+
+#: gitk:2497
+msgid "Highlight this only"
+msgstr "Marcar apenas este"
+
+#: gitk:2498
+msgid "External diff"
+msgstr "Diff externo"
+
+#: gitk:2499
+msgid "Blame parent commit"
+msgstr "Anotar revisão anterior"
+
+#: gitk:2506
+msgid "Show origin of this line"
+msgstr "Exibir origem desta linha"
+
+#: gitk:2507
+msgid "Run git gui blame on this line"
+msgstr "Executar 'git blame' nesta linha"
+
+#: gitk:2789
+msgid "\n"
+"Gitk - a commit viewer for git\n"
+"\n"
+"Copyright ©9 2005-2010 Paul Mackerras\n"
+"\n"
+"Use and redistribute under the terms of the GNU General Public License"
+msgstr "\n"
+"Gitk - um visualizador de revisões para o git \n"
+"\n"
+"Copyright ©9 2005-2010 Paul Mackerras\n"
+"\n"
+"Uso e distribuição segundo os termos da Licença Pública Geral GNU"
+
+#: gitk:2797 gitk:2862 gitk:9253
+msgid "Close"
+msgstr "Fechar"
+
+#: gitk:2818
+msgid "Gitk key bindings"
+msgstr "Atalhos de teclado"
+
+#: gitk:2821
+msgid "Gitk key bindings:"
+msgstr "Atalhos de teclado:"
+
+#: gitk:2823
+#, tcl-format
+msgid "<%s-Q>\t\tQuit"
+msgstr "<%s-Q>\t\tSair"
+
+#: gitk:2824
+#, tcl-format
+msgid "<%s-W>\t\tClose window"
+msgstr "<%s-W>\t\tFechar janela"
+
+#: gitk:2825
+msgid "<Home>\t\tMove to first commit"
+msgstr "<Home>\t\tIr para a primeira revisão"
+
+#: gitk:2826
+msgid "<End>\t\tMove to last commit"
+msgstr "<End>\t\tIr para a última revisão"
+
+#: gitk:2827
+msgid "<Up>, p, i\tMove up one commit"
+msgstr "<Up>, p, i\tIr para uma revisão acima"
+
+#: gitk:2828
+msgid "<Down>, n, k\tMove down one commit"
+msgstr "<Down>, n, k\tIr para uma revisão abaixo"
+
+#: gitk:2829
+msgid "<Left>, z, j\tGo back in history list"
+msgstr "<Left>, z, j\tVoltar no histórico"
+
+#: gitk:2830
+msgid "<Right>, x, l\tGo forward in history list"
+msgstr "<Right>, x, l\tAvançar no histórico"
+
+#: gitk:2831
+msgid "<PageUp>\tMove up one page in commit list"
+msgstr "<PageUp>\tSubir uma página na lista de revisões"
+
+#: gitk:2832
+msgid "<PageDown>\tMove down one page in commit list"
+msgstr "<PageDown>\tDescer uma página na lista de revisões"
+
+#: gitk:2833
+#, tcl-format
+msgid "<%s-Home>\tScroll to top of commit list"
+msgstr "<%s-Home>\tRolar para o início da lista de revisões"
+
+#: gitk:2834
+#, tcl-format
+msgid "<%s-End>\tScroll to bottom of commit list"
+msgstr "<%s-End>\tRolar para o final da lista de revisões"
+
+#: gitk:2835
+#, tcl-format
+msgid "<%s-Up>\tScroll commit list up one line"
+msgstr "<%s-Up>\tRolar uma linha acima na lista de revisões"
+
+#: gitk:2836
+#, tcl-format
+msgid "<%s-Down>\tScroll commit list down one line"
+msgstr "<%s-Down>\tRolar uma linha abaixo na lista de revisões"
+
+#: gitk:2837
+#, tcl-format
+msgid "<%s-PageUp>\tScroll commit list up one page"
+msgstr "<%s-PageUp>\tRolar uma página acima na lista de revisões"
+
+#: gitk:2838
+#, tcl-format
+msgid "<%s-PageDown>\tScroll commit list down one page"
+msgstr "<%s-PageDown>\tRolar uma página abaixo na lista de revisões"
+
+#: gitk:2839
+msgid "<Shift-Up>\tFind backwards (upwards, later commits)"
+msgstr "<Shift-Up>\tProcurar próxima (revisões mas recentes)"
+
+#: gitk:2840
+msgid "<Shift-Down>\tFind forwards (downwards, earlier commits)"
+msgstr "<Shift-Down>\tProcurar anterior (revisões mais antigas)"
+
+#: gitk:2841
+msgid "<Delete>, b\tScroll diff view up one page"
+msgstr "<Delete>, b\tRola alterações uma página acima"
+
+#: gitk:2842
+msgid "<Backspace>\tScroll diff view up one page"
+msgstr "<Backspace>\tRolar alterações uma página abaixo"
+
+#: gitk:2843
+msgid "<Space>\t\tScroll diff view down one page"
+msgstr "<Space>\t\tRolar alterações uma página abaixo"
+
+#: gitk:2844
+msgid "u\t\tScroll diff view up 18 lines"
+msgstr "u\t\tRolar alterações 18 linhas acima"
+
+#: gitk:2845
+msgid "d\t\tScroll diff view down 18 lines"
+msgstr "d\t\tRolar alterações 18 linhas abaixo"
+
+#: gitk:2846
+#, tcl-format
+msgid "<%s-F>\t\tFind"
+msgstr "<%s-F>\t\tProcurar"
+
+#: gitk:2847
+#, tcl-format
+msgid "<%s-G>\t\tMove to next find hit"
+msgstr "<%s-G>\t\tIr para a próxima ocorrência"
+
+#: gitk:2848
+msgid "<Return>\tMove to next find hit"
+msgstr "<Return>\tIr para a próxima ocorrência"
+
+#: gitk:2849
+msgid "/\t\tFocus the search box"
+msgstr "/\t\tPor foco na caixa de busca"
+
+#: gitk:2850
+msgid "?\t\tMove to previous find hit"
+msgstr "?\t\tIr para a ocorrência anterior"
+
+#: gitk:2851
+msgid "f\t\tScroll diff view to next file"
+msgstr "f\t\tRolar alterações para o próximo arquivo"
+
+#: gitk:2852
+#, tcl-format
+msgid "<%s-S>\t\tSearch for next hit in diff view"
+msgstr "<%s-S>\t\tProcurar a próxima ocorrência na lista de alterações"
+
+#: gitk:2853
+#, tcl-format
+msgid "<%s-R>\t\tSearch for previous hit in diff view"
+msgstr "<%s-R>\t\tProcurar ocorrência anterior na lista de alterações"
+
+#: gitk:2854
+#, tcl-format
+msgid "<%s-KP+>\tIncrease font size"
+msgstr "<%s-KP+>\tAumentar tamanho da fonte"
+
+#: gitk:2855
+#, tcl-format
+msgid "<%s-plus>\tIncrease font size"
+msgstr "<%s-plus>\tAumentar tamanho da fonte"
+
+#: gitk:2856
+#, tcl-format
+msgid "<%s-KP->\tDecrease font size"
+msgstr "<%s-KP->\tReduzir tamanho da fonte"
+
+#: gitk:2857
+#, tcl-format
+msgid "<%s-minus>\tDecrease font size"
+msgstr "<%s-minus>\tReduzir tamanho da fonte"
+
+#: gitk:2858
+msgid "<F5>\t\tUpdate"
+msgstr "<F5>\t\tAtualizar"
+
+#: gitk:3313 gitk:3322
+#, tcl-format
+msgid "Error creating temporary directory %s:"
+msgstr "Erro ao criar o diretório temporário %s:"
+
+#: gitk:3335
+#, tcl-format
+msgid "Error getting \"%s\" from %s:"
+msgstr "Erro ao ler \"%s\" de %s:"
+
+#: gitk:3398
+msgid "command failed:"
+msgstr "O comando falhou:"
+
+#: gitk:3547
+msgid "No such commit"
+msgstr "Revisão não encontrada"
+
+#: gitk:3561
+msgid "git gui blame: command failed:"
+msgstr "Comando 'git gui blame' falhou:"
+
+#: gitk:3592
+#, tcl-format
+msgid "Couldn't read merge head: %s"
+msgstr "Impossível ler merge head: %s"
+
+#: gitk:3600
+#, tcl-format
+msgid "Error reading index: %s"
+msgstr "Erro ao ler o índice: %s"
+
+#: gitk:3625
+#, tcl-format
+msgid "Couldn't start git blame: %s"
+msgstr "Não foi possível inciar o 'git blame': %s"
+
+#: gitk:3628 gitk:6419
+msgid "Searching"
+msgstr "Procurando"
+
+#: gitk:3660
+#, tcl-format
+msgid "Error running git blame: %s"
+msgstr "Erro ao executar 'git blame': %s"
+
+#: gitk:3688
+#, tcl-format
+msgid "That line comes from commit %s, which is not in this view"
+msgstr "Esta linha vem da revisão %s, que não está nesta vista"
+
+#: gitk:3702
+msgid "External diff viewer failed:"
+msgstr "Erro do visualizador de alterações externo:"
+
+#: gitk:3820
+msgid "Gitk view definition"
+msgstr "Definir vista"
+
+#: gitk:3824
+msgid "Remember this view"
+msgstr "Lembrar esta vista"
+
+#: gitk:3825
+msgid "References (space separated list):"
+msgstr "Referências (separar a lista com um espaço):"
+
+#: gitk:3826
+msgid "Branches & tags:"
+msgstr "Ramos & etiquetas:"
+
+#: gitk:3827
+msgid "All refs"
+msgstr "Todas as referências"
+
+#: gitk:3828
+msgid "All (local) branches"
+msgstr "Todos os ramos locais"
+
+#: gitk:3829
+msgid "All tags"
+msgstr "Todas as etiquetas"
+
+#: gitk:3830
+msgid "All remote-tracking branches"
+msgstr "Todos os ramos de rastreio"
+
+#: gitk:3831
+msgid "Commit Info (regular expressions):"
+msgstr "Informações da revisão (expressões regulares):"
+
+#: gitk:3832
+msgid "Author:"
+msgstr "Autor:"
+
+#: gitk:3833
+msgid "Committer:"
+msgstr "Revisor:"
+
+#: gitk:3834
+msgid "Commit Message:"
+msgstr "Descrição da revisão:"
+
+#: gitk:3835
+msgid "Matches all Commit Info criteria"
+msgstr "Coincidir todos os critérios de informações da revisão"
+
+#: gitk:3836
+msgid "Changes to Files:"
+msgstr "Mudanças para os arquivos:"
+
+#: gitk:3837
+msgid "Fixed String"
+msgstr "Texto fixo"
+
+#: gitk:3838
+msgid "Regular Expression"
+msgstr "Expressão regular"
+
+#: gitk:3839
+msgid "Search string:"
+msgstr "Texto de busca"
+
+#: gitk:3840
+msgid ""
+"Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 "
+"15:27:38\"):"
+msgstr ""
+"Datas de revisão (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 "
+"15:27:38\"):"
+
+#: gitk:3841
+msgid "Since:"
+msgstr "Desde:"
+
+#: gitk:3842
+msgid "Until:"
+msgstr "Até:"
+
+#: gitk:3843
+msgid "Limit and/or skip a number of revisions (positive integer):"
+msgstr "Limitar e/ou ignorar um número de revisões (inteiro positivo):"
+
+#: gitk:3844
+msgid "Number to show:"
+msgstr "Número para mostrar:"
+
+#: gitk:3845
+msgid "Number to skip:"
+msgstr "Número para ignorar:"
+
+#: gitk:3846
+msgid "Miscellaneous options:"
+msgstr "Opções diversas:"
+
+#: gitk:3847
+msgid "Strictly sort by date"
+msgstr "Ordenar estritamente pela data"
+
+#: gitk:3848
+msgid "Mark branch sides"
+msgstr "Marcar os dois lados do ramo"
+
+#: gitk:3849
+msgid "Limit to first parent"
+msgstr "Limitar ao primeiro antecessor"
+
+#: gitk:3850
+msgid "Simple history"
+msgstr "Histórico simplificado"
+
+#: gitk:3851
+msgid "Additional arguments to git log:"
+msgstr "Argumentos adicionais para o 'git log':"
+
+#: gitk:3852
+msgid "Enter files and directories to include, one per line:"
+msgstr "Arquivos e diretórios para incluir, um por linha"
+
+#: gitk:3853
+msgid "Command to generate more commits to include:"
+msgstr "Comando para gerar mais revisões para incluir:"
+
+#: gitk:3977
+msgid "Gitk: edit view"
+msgstr "Gitk: editar vista"
+
+#: gitk:3985
+msgid "-- criteria for selecting revisions"
+msgstr "-- critérios para selecionar revisões"
+
+#: gitk:3990
+msgid "View Name"
+msgstr "Nome da vista"
+
+#: gitk:4065
+msgid "Apply (F5)"
+msgstr "Aplicar (F5)"
+
+#: gitk:4103
+msgid "Error in commit selection arguments:"
+msgstr "Erro nos argumentos de seleção de revisões:"
+
+#: gitk:4156 gitk:4208 gitk:4656 gitk:4670 gitk:5931 gitk:11551 gitk:11552
+msgid "None"
+msgstr "Nenhum"
+
+#: gitk:4604 gitk:6451 gitk:8309 gitk:8324
+msgid "Date"
+msgstr "Data"
+
+#: gitk:4604 gitk:6451
+msgid "CDate"
+msgstr "DataR"
+
+#: gitk:4753 gitk:4758
+msgid "Descendant"
+msgstr "Descendente de"
+
+#: gitk:4754
+msgid "Not descendant"
+msgstr "Não descendente de"
+
+#: gitk:4761 gitk:4766
+msgid "Ancestor"
+msgstr "Antecessor de"
+
+#: gitk:4762
+msgid "Not ancestor"
+msgstr "Não antecessor de"
+
+#: gitk:5052
+msgid "Local changes checked in to index but not committed"
+msgstr "Mudanças locais marcadas, porém não salvas"
+
+#: gitk:5088
+msgid "Local uncommitted changes, not checked in to index"
+msgstr "Mudanças locais não marcadas"
+
+#: gitk:6769
+msgid "many"
+msgstr "muitas"
+
+#: gitk:6952
+msgid "Tags:"
+msgstr "Etiquetas:"
+
+#: gitk:6969 gitk:6975 gitk:8302
+msgid "Parent"
+msgstr "Antecessor"
+
+#: gitk:6980
+msgid "Child"
+msgstr "Descendente"
+
+#: gitk:6989
+msgid "Branch"
+msgstr "Ramo"
+
+#: gitk:6992
+msgid "Follows"
+msgstr "Segue"
+
+#: gitk:6995
+msgid "Precedes"
+msgstr "Precede"
+
+#: gitk:7532
+#, tcl-format
+msgid "Error getting diffs: %s"
+msgstr "Erro ao obter diferenças: %s"
+
+#: gitk:8130
+msgid "Goto:"
+msgstr "Ir para:"
+
+#: gitk:8151
+#, tcl-format
+msgid "Short SHA1 id %s is ambiguous"
+msgstr "O id SHA1 %s é ambíguo"
+
+#: gitk:8158
+#, tcl-format
+msgid "Revision %s is not known"
+msgstr "Revisão %s desconhecida"
+
+#: gitk:8168
+#, tcl-format
+msgid "SHA1 id %s is not known"
+msgstr "Id SHA1 %s desconhecido"
+
+#: gitk:8170
+#, tcl-format
+msgid "Revision %s is not in the current view"
+msgstr "A revisão %s não está na vista atual"
+
+#: gitk:8312
+msgid "Children"
+msgstr "Descendentes"
+
+#: gitk:8370
+#, tcl-format
+msgid "Reset %s branch to here"
+msgstr "Redefinir ramo %s para este ponto"
+
+#: gitk:8372
+msgid "Detached head: can't reset"
+msgstr "Detached head: impossível redefinir"
+
+#: gitk:8481 gitk:8487
+msgid "Skipping merge commit "
+msgstr "Saltando revisão de mesclagem"
+
+#: gitk:8496 gitk:8501
+msgid "Error getting patch ID for "
+msgstr "Erro ao obter patch ID para"
+
+#: gitk:8497 gitk:8502
+msgid " - stopping\n"
+msgstr "- parando\n"
+
+#: gitk:8507 gitk:8510 gitk:8518 gitk:8532 gitk:8541
+msgid "Commit "
+msgstr "Revisão"
+
+#: gitk:8511
+msgid ""
+" is the same patch as\n"
+" "
+msgstr ""
+"é o mesmo patch que\n"
+" "
+
+#: gitk:8519
+msgid ""
+" differs from\n"
+" "
+msgstr "difere de"
+
+#: gitk:8521
+msgid ""
+"Diff of commits:\n"
+"\n"
+msgstr ""
+"Diferença de revisões:\n"
+"\n"
+
+#: gitk:8533 gitk:8542
+#, tcl-format
+msgid " has %s children - stopping\n"
+msgstr "possui %s descendentes - parando\n"
+
+#: gitk:8561
+#, tcl-format
+msgid "Error writing commit to file: %s"
+msgstr "Erro ao salvar revisão para o arquivo: %s"
+
+#: gitk:8567
+#, tcl-format
+msgid "Error diffing commits: %s"
+msgstr "Erro ao comparar revisões: %s"
+
+#: gitk:8598
+msgid "Top"
+msgstr "Início"
+
+#: gitk:8599
+msgid "From"
+msgstr "De"
+
+#: gitk:8604
+msgid "To"
+msgstr "Para"
+
+#: gitk:8628
+msgid "Generate patch"
+msgstr "Gerar patch"
+
+#: gitk:8630
+msgid "From:"
+msgstr "De:"
+
+#: gitk:8639
+msgid "To:"
+msgstr "Para:"
+
+#: gitk:8648
+msgid "Reverse"
+msgstr "Inverter"
+
+#: gitk:8650 gitk:8845
+msgid "Output file:"
+msgstr "Arquivo de saída:"
+
+#: gitk:8656
+msgid "Generate"
+msgstr "Gerar"
+
+#: gitk:8694
+msgid "Error creating patch:"
+msgstr "Erro ao criar patch:"
+
+#: gitk:8717 gitk:8833 gitk:8890
+msgid "ID:"
+msgstr "ID:"
+
+#: gitk:8726
+msgid "Tag name:"
+msgstr "Nome da etiqueta:"
+
+#: gitk:8729
+msgid "Tag message is optional"
+msgstr "A descrição da etiqueta é opcional"
+
+#: gitk:8731
+msgid "Tag message:"
+msgstr "Descrição da etiqueta"
+
+#: gitk:8735 gitk:8899
+msgid "Create"
+msgstr "Criar"
+
+#: gitk:8753
+msgid "No tag name specified"
+msgstr "Nome da etiqueta não indicado"
+
+#: gitk:8757
+#, tcl-format
+msgid "Tag \"%s\" already exists"
+msgstr "Etiqueta \"%s\" já existe"
+
+#: gitk:8767
+msgid "Error creating tag:"
+msgstr "Erro ao criar etiqueta:"
+
+#: gitk:8842
+msgid "Command:"
+msgstr "Comando:"
+
+#: gitk:8850
+msgid "Write"
+msgstr "Exportar"
+
+#: gitk:8868
+msgid "Error writing commit:"
+msgstr "Erro ao exportar revisão"
+
+#: gitk:8895
+msgid "Name:"
+msgstr "Nome:"
+
+#: gitk:8918
+msgid "Please specify a name for the new branch"
+msgstr "Indique um nome para o novo ramo"
+
+#: gitk:8923
+#, tcl-format
+msgid "Branch '%s' already exists. Overwrite?"
+msgstr "O ramo \"%s\" já existe. Sobrescrever?"
+
+#: gitk:8989
+#, tcl-format
+msgid "Commit %s is already included in branch %s -- really re-apply it?"
+msgstr "Revisão %s já inclusa no ramo %s -- você realmente deseja reaplicá-la?"
+
+#: gitk:8994
+msgid "Cherry-picking"
+msgstr "Cherry-picking"
+
+#: gitk:9003
+#, tcl-format
+msgid ""
+"Cherry-pick failed because of local changes to file '%s'.\n"
+"Please commit, reset or stash your changes and try again."
+msgstr ""
+"O cherry-pick falhou porque o arquivo \"%s\" possui mudanças locais.\n"
+"Salve a uma revisão, redefina ou armazene (stash) suas mudanças e tente "
+"novamente."
+
+#: gitk:9009
+msgid ""
+"Cherry-pick failed because of merge conflict.\n"
+"Do you wish to run git citool to resolve it?"
+msgstr ""
+"O cherry-pick falhou porque houve um conflito na mesclagem.\n"
+"Executar o 'git citool' para resolvê-lo?"
+
+#: gitk:9025
+msgid "No changes committed"
+msgstr "Nenhuma revisão foi salva"
+
+#: gitk:9051
+msgid "Confirm reset"
+msgstr "Confirmar redefinição"
+
+#: gitk:9053
+#, tcl-format
+msgid "Reset branch %s to %s?"
+msgstr "Você realmente deseja redefinir o ramo %s para %s?"
+
+#: gitk:9055
+msgid "Reset type:"
+msgstr "Tipo de redefinição"
+
+#: gitk:9058
+msgid "Soft: Leave working tree and index untouched"
+msgstr "Soft: deixa a árvore de trabalho e o índice intocados"
+
+#: gitk:9061
+msgid "Mixed: Leave working tree untouched, reset index"
+msgstr "Misto: Deixa a árvore de trabalho intocada, redefine o índice"
+
+#: gitk:9064
+msgid ""
+"Hard: Reset working tree and index\n"
+"(discard ALL local changes)"
+msgstr ""
+"Hard: Redefine a árvore de trabalho e o índice\n"
+"(descarta TODAS as mudanças locais)"
+
+#: gitk:9081
+msgid "Resetting"
+msgstr "Redefinindo"
+
+#: gitk:9141
+msgid "Checking out"
+msgstr "Abrindo"
+
+#: gitk:9194
+msgid "Cannot delete the currently checked-out branch"
+msgstr "Impossível excluir o ramo atualmente aberto"
+
+#: gitk:9200
+#, tcl-format
+msgid ""
+"The commits on branch %s aren't on any other branch.\n"
+"Really delete branch %s?"
+msgstr ""
+"As revisões do ramo \"%s\" não existem em nenhum outro ramo.\n"
+"Você realmente deseja excluir ramo \"%s\"?"
+
+#: gitk:9231
+#, tcl-format
+msgid "Tags and heads: %s"
+msgstr "Referências: %s"
+
+#: gitk:9246
+msgid "Filter"
+msgstr "Filtro"
+
+#: gitk:9541
+msgid ""
+"Error reading commit topology information; branch and preceding/following "
+"tag information will be incomplete."
+msgstr ""
+"Erro ao ler a topologia das revisões; as informações dos ramos e etiquetas "
+"antecessoras/sucessoras estarão incompletas"
+
+#: gitk:10527
+msgid "Tag"
+msgstr "Etiqueta"
+
+#: gitk:10527
+msgid "Id"
+msgstr "Id"
+
+#: gitk:10576
+msgid "Gitk font chooser"
+msgstr "Selecionar fontes do Gitk"
+
+#: gitk:10593
+msgid "B"
+msgstr "B"
+
+#: gitk:10596
+msgid "I"
+msgstr "I"
+
+#: gitk:10714
+msgid "Gitk preferences"
+msgstr "Preferências do Gitk"
+
+#: gitk:10716
+msgid "Commit list display options"
+msgstr "Opções da lista de revisões"
+
+#: gitk:10719
+msgid "Maximum graph width (lines)"
+msgstr "Largura máxima do grafo (linhas)"
+
+#: gitk:10722
+#, tcl-format
+msgid "Maximum graph width (% of pane)"
+msgstr "Largura máxima do grafo (% do painel)"
+
+#: gitk:10725
+msgid "Show local changes"
+msgstr "Exibir mudanças locais"
+
+#: gitk:10728
+msgid "Auto-select SHA1"
+msgstr "Selecionar o SHA1 automaticamente"
+
+#: gitk:10731
+msgid "Hide remote refs"
+msgstr "Ocultar referências remotas"
+
+#: gitk:10735
+msgid "Diff display options"
+msgstr "Opções de exibição das alterações"
+
+#: gitk:10737
+msgid "Tab spacing"
+msgstr "Espaços por tabulação"
+
+#: gitk:10740
+msgid "Display nearby tags"
+msgstr "Exibir etiquetas próximas"
+
+#: gitk:10743
+msgid "Limit diffs to listed paths"
+msgstr "Limitar diferenças aos caminhos listados"
+
+#: gitk:10746
+msgid "Support per-file encodings"
+msgstr "Usar codificações distintas por arquivo"
+
+#: gitk:10752 gitk:10832
+msgid "External diff tool"
+msgstr "Ferramenta 'diff' externa"
+
+#: gitk:10753
+msgid "Choose..."
+msgstr "Selecionar..."
+
+#: gitk:10758
+msgid "General options"
+msgstr "Opções gerais"
+
+#: gitk:10761
+msgid "Use themed widgets"
+msgstr "Usar temas para as janelas"
+
+#: gitk:10763
+msgid "(change requires restart)"
+msgstr "(exige reinicialização)"
+
+#: gitk:10765
+msgid "(currently unavailable)"
+msgstr "(atualmente indisponível)"
+
+#: gitk:10769
+msgid "Colors: press to choose"
+msgstr "Cores: clique para escolher"
+
+#: gitk:10772
+msgid "Interface"
+msgstr "Interface"
+
+#: gitk:10773
+msgid "interface"
+msgstr "interface"
+
+#: gitk:10776
+msgid "Background"
+msgstr "Segundo plano"
+
+#: gitk:10777 gitk:10807
+msgid "background"
+msgstr "segundo plano"
+
+#: gitk:10780
+msgid "Foreground"
+msgstr "Primeiro plano"
+
+#: gitk:10781
+msgid "foreground"
+msgstr "primeiro plano"
+
+#: gitk:10784
+msgid "Diff: old lines"
+msgstr "Diff: linhas excluídas"
+
+#: gitk:10785
+msgid "diff old lines"
+msgstr "linhas excluídas"
+
+#: gitk:10789
+msgid "Diff: new lines"
+msgstr "Diff: linhas adicionadas"
+
+#: gitk:10790
+msgid "diff new lines"
+msgstr "linhas adicionadas"
+
+#: gitk:10794
+msgid "Diff: hunk header"
+msgstr "Diff: cabeçalho do bloco"
+
+#: gitk:10796
+msgid "diff hunk header"
+msgstr "cabeçalho do bloco"
+
+#: gitk:10800
+msgid "Marked line bg"
+msgstr "2º plano da linha marcada"
+
+#: gitk:10802
+msgid "marked line background"
+msgstr "segundo plano da linha marcada"
+
+#: gitk:10806
+msgid "Select bg"
+msgstr "2º plano da seleção"
+
+#: gitk:10810
+msgid "Fonts: press to choose"
+msgstr "Fontes: clique para escolher"
+
+#: gitk:10812
+msgid "Main font"
+msgstr "Fonte principal"
+
+#: gitk:10813
+msgid "Diff display font"
+msgstr "Fonte da lista de mudanças"
+
+#: gitk:10814
+msgid "User interface font"
+msgstr "Fonte da interface"
+
+#: gitk:10842
+#, tcl-format
+msgid "Gitk: choose color for %s"
+msgstr "Gitk: selecionar cor para %s"
+
+#: gitk:11445
+msgid "Cannot find a git repository here."
+msgstr "Não há nenhum repositório git aqui."
+
+#: gitk:11449
+#, tcl-format
+msgid "Cannot find the git directory \"%s\"."
+msgstr "Impossível encontrar o diretório git \"%s\"."
+
+#: gitk:11496
+#, tcl-format
+msgid "Ambiguous argument '%s': both revision and filename"
+msgstr ""
+"O argumento \"%s\" é ambíguo (especifica tanto uma revisão e um nome de "
+"arquivo)"
+
+#: gitk:11508
+msgid "Bad arguments to gitk:"
+msgstr "Argumentos incorretos para o gitk:"
+
+#: gitk:11604
+msgid "Command line"
+msgstr "Linha de comando"
diff --git a/gitk-git/po/sv.po b/gitk-git/po/sv.po
index 386763ade786d96b657dd0ab737b633eef44b424..2f07a2e2d93d655cfe5cf2d3535e7b86422233c6 100644 (file)
--- a/gitk-git/po/sv.po
+++ b/gitk-git/po/sv.po
# Swedish translation for gitk
-# Copyright (C) 2005-2009 Paul Mackerras
+# Copyright (C) 2005-2010 Paul Mackerras
# This file is distributed under the same license as the gitk package.
#
# Peter Krefting <peter@softwolves.pp.se>, 2008-2010.
msgstr ""
"Project-Id-Version: sv\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-01-28 13:16+0100\n"
-"PO-Revision-Date: 2010-01-28 13:48+0100\n"
+"POT-Creation-Date: 2010-09-12 21:14+0100\n"
+"PO-Revision-Date: 2010-09-12 21:16+0100\n"
"Last-Translator: Peter Krefting <peter@softwolves.pp.se>\n"
"Language-Team: Swedish <tp-sv@listor.tp-sv.se>\n"
"MIME-Version: 1.0\n"
msgid "Error parsing revisions:"
msgstr "Fel vid tolkning av revisioner:"
-#: gitk:329
+#: gitk:330
msgid "Error executing --argscmd command:"
msgstr "Fel vid körning av --argscmd-kommando:"
-#: gitk:342
+#: gitk:343
msgid "No files selected: --merge specified but no files are unmerged."
msgstr ""
"Inga filer valdes: --merge angavs men det finns inga filer som inte har "
"slagits samman."
-#: gitk:345
+#: gitk:346
msgid ""
"No files selected: --merge specified but no unmerged files are within file "
"limit."
"Inga filer valdes: --merge angavs men det finns inga filer inom "
"filbegränsningen."
-#: gitk:367 gitk:514
+#: gitk:368 gitk:516
msgid "Error executing git log:"
msgstr "Fel vid körning av git log:"
-#: gitk:385 gitk:530
+#: gitk:386 gitk:532
msgid "Reading"
msgstr "Läser"
-#: gitk:445 gitk:4261
+#: gitk:446 gitk:4271
msgid "Reading commits..."
msgstr "Läser incheckningar..."
-#: gitk:448 gitk:1578 gitk:4264
+#: gitk:449 gitk:1580 gitk:4274
msgid "No commits selected"
msgstr "Inga incheckningar markerade"
-#: gitk:1454
+#: gitk:1456
msgid "Can't parse git log output:"
msgstr "Kan inte tolka utdata från git log:"
-#: gitk:1674
+#: gitk:1676
msgid "No commit information available"
msgstr "Ingen incheckningsinformation är tillgänglig"
-#: gitk:1816
+#: gitk:1818
msgid "mc"
msgstr "mc"
-#: gitk:1851 gitk:4054 gitk:9044 gitk:10585 gitk:10804
+#: gitk:1853 gitk:4064 gitk:9067 gitk:10607 gitk:10817
msgid "OK"
msgstr "OK"
-#: gitk:1853 gitk:4056 gitk:8634 gitk:8713 gitk:8828 gitk:8877 gitk:9046
-#: gitk:10586 gitk:10805
+#: gitk:1855 gitk:4066 gitk:8657 gitk:8736 gitk:8851 gitk:8900 gitk:9069
+#: gitk:10608 gitk:10818
msgid "Cancel"
msgstr "Avbryt"
-#: gitk:1975
+#: gitk:1980
msgid "Update"
msgstr "Uppdatera"
-#: gitk:1976
+#: gitk:1981
msgid "Reload"
msgstr "Ladda om"
-#: gitk:1977
+#: gitk:1982
msgid "Reread references"
msgstr "Läs om referenser"
-#: gitk:1978
+#: gitk:1983
msgid "List references"
msgstr "Visa referenser"
-#: gitk:1980
+#: gitk:1985
msgid "Start git gui"
msgstr "Starta git gui"
-#: gitk:1982
+#: gitk:1987
msgid "Quit"
msgstr "Avsluta"
-#: gitk:1974
+#: gitk:1979
msgid "File"
msgstr "Arkiv"
-#: gitk:1986
+#: gitk:1991
msgid "Preferences"
msgstr "Inställningar"
-#: gitk:1985
+#: gitk:1990
msgid "Edit"
msgstr "Redigera"
-#: gitk:1990
+#: gitk:1995
msgid "New view..."
msgstr "Ny vy..."
-#: gitk:1991
+#: gitk:1996
msgid "Edit view..."
msgstr "Ändra vy..."
-#: gitk:1992
+#: gitk:1997
msgid "Delete view"
msgstr "Ta bort vy"
-#: gitk:1994
+#: gitk:1999
msgid "All files"
msgstr "Alla filer"
-#: gitk:1989 gitk:3808
+#: gitk:1994 gitk:3817
msgid "View"
msgstr "Visa"
-#: gitk:1999 gitk:2009 gitk:2780
+#: gitk:2004 gitk:2014 gitk:2787
msgid "About gitk"
msgstr "Om gitk"
-#: gitk:2000 gitk:2014
+#: gitk:2005 gitk:2019
msgid "Key bindings"
msgstr "Tangentbordsbindningar"
-#: gitk:1998 gitk:2013
+#: gitk:2003 gitk:2018
msgid "Help"
msgstr "Hjälp"
-#: gitk:2091 gitk:8110
+#: gitk:2096 gitk:8132
msgid "SHA1 ID:"
msgstr "SHA1-id:"
-#: gitk:2122
+#: gitk:2127
msgid "Row"
msgstr "Rad"
-#: gitk:2160
+#: gitk:2165
msgid "Find"
msgstr "Sök"
-#: gitk:2161
+#: gitk:2166
msgid "next"
msgstr "nästa"
-#: gitk:2162
+#: gitk:2167
msgid "prev"
msgstr "föreg"
-#: gitk:2163
+#: gitk:2168
msgid "commit"
msgstr "incheckning"
-#: gitk:2166 gitk:2168 gitk:4422 gitk:4445 gitk:4469 gitk:6410 gitk:6482
-#: gitk:6566
+#: gitk:2171 gitk:2173 gitk:4432 gitk:4455 gitk:4479 gitk:6420 gitk:6492
+#: gitk:6576
msgid "containing:"
msgstr "som innehåller:"
-#: gitk:2169 gitk:3290 gitk:3295 gitk:4497
+#: gitk:2174 gitk:3298 gitk:3303 gitk:4507
msgid "touching paths:"
msgstr "som rör sökväg:"
-#: gitk:2170 gitk:4502
+#: gitk:2175 gitk:4512
msgid "adding/removing string:"
msgstr "som lägger/till tar bort sträng:"
-#: gitk:2179 gitk:2181
+#: gitk:2184 gitk:2186
msgid "Exact"
msgstr "Exakt"
-#: gitk:2181 gitk:4577 gitk:6378
+#: gitk:2186 gitk:4587 gitk:6388
msgid "IgnCase"
msgstr "IgnVersaler"
-#: gitk:2181 gitk:4471 gitk:4575 gitk:6374
+#: gitk:2186 gitk:4481 gitk:4585 gitk:6384
msgid "Regexp"
msgstr "Reg.uttr."
-#: gitk:2183 gitk:2184 gitk:4596 gitk:4626 gitk:4633 gitk:6502 gitk:6570
+#: gitk:2188 gitk:2189 gitk:4606 gitk:4636 gitk:4643 gitk:6512 gitk:6580
msgid "All fields"
msgstr "Alla fält"
-#: gitk:2184 gitk:4594 gitk:4626 gitk:6441
+#: gitk:2189 gitk:4604 gitk:4636 gitk:6451
msgid "Headline"
msgstr "Rubrik"
-#: gitk:2185 gitk:4594 gitk:6441 gitk:6570 gitk:7003
+#: gitk:2190 gitk:4604 gitk:6451 gitk:6580 gitk:7013
msgid "Comments"
msgstr "Kommentarer"
-#: gitk:2185 gitk:4594 gitk:4598 gitk:4633 gitk:6441 gitk:6938 gitk:8285
-#: gitk:8300
+#: gitk:2190 gitk:4604 gitk:4608 gitk:4643 gitk:6451 gitk:6948 gitk:8307
+#: gitk:8322
msgid "Author"
msgstr "Författare"
-#: gitk:2185 gitk:4594 gitk:6441 gitk:6940
+#: gitk:2190 gitk:4604 gitk:6451 gitk:6950
msgid "Committer"
msgstr "Incheckare"
-#: gitk:2216
+#: gitk:2221
msgid "Search"
msgstr "Sök"
-#: gitk:2224
+#: gitk:2229
msgid "Diff"
msgstr "Diff"
-#: gitk:2226
+#: gitk:2231
msgid "Old version"
msgstr "Gammal version"
-#: gitk:2228
+#: gitk:2233
msgid "New version"
msgstr "Ny version"
-#: gitk:2230
+#: gitk:2235
msgid "Lines of context"
msgstr "Rader sammanhang"
-#: gitk:2240
+#: gitk:2245
msgid "Ignore space change"
msgstr "Ignorera ändringar i blanksteg"
-#: gitk:2299
+#: gitk:2304
msgid "Patch"
msgstr "Patch"
-#: gitk:2301
+#: gitk:2306
msgid "Tree"
msgstr "Träd"
-#: gitk:2456 gitk:2473
+#: gitk:2463 gitk:2480
msgid "Diff this -> selected"
msgstr "Diff denna -> markerad"
-#: gitk:2457 gitk:2474
+#: gitk:2464 gitk:2481
msgid "Diff selected -> this"
msgstr "Diff markerad -> denna"
-#: gitk:2458 gitk:2475
+#: gitk:2465 gitk:2482
msgid "Make patch"
msgstr "Skapa patch"
-#: gitk:2459 gitk:8692
+#: gitk:2466 gitk:8715
msgid "Create tag"
msgstr "Skapa tagg"
-#: gitk:2460 gitk:8808
+#: gitk:2467 gitk:8831
msgid "Write commit to file"
msgstr "Skriv incheckning till fil"
-#: gitk:2461 gitk:8865
+#: gitk:2468 gitk:8888
msgid "Create new branch"
msgstr "Skapa ny gren"
-#: gitk:2462
+#: gitk:2469
msgid "Cherry-pick this commit"
msgstr "Plocka denna incheckning"
-#: gitk:2463
+#: gitk:2470
msgid "Reset HEAD branch to here"
msgstr "Återställ HEAD-grenen hit"
-#: gitk:2464
+#: gitk:2471
msgid "Mark this commit"
msgstr "Markera denna incheckning"
-#: gitk:2465
+#: gitk:2472
msgid "Return to mark"
msgstr "Återgå till markering"
-#: gitk:2466
+#: gitk:2473
msgid "Find descendant of this and mark"
msgstr "Hitta efterföljare till denna och markera"
-#: gitk:2467
+#: gitk:2474
msgid "Compare with marked commit"
msgstr "Jämför med markerad incheckning"
-#: gitk:2481
+#: gitk:2488
msgid "Check out this branch"
msgstr "Checka ut denna gren"
-#: gitk:2482
+#: gitk:2489
msgid "Remove this branch"
msgstr "Ta bort denna gren"
-#: gitk:2489
+#: gitk:2496
msgid "Highlight this too"
msgstr "Markera även detta"
-#: gitk:2490
+#: gitk:2497
msgid "Highlight this only"
msgstr "Markera bara detta"
-#: gitk:2491
+#: gitk:2498
msgid "External diff"
msgstr "Extern diff"
-#: gitk:2492
+#: gitk:2499
msgid "Blame parent commit"
msgstr "Klandra föräldraincheckning"
-#: gitk:2499
+#: gitk:2506
msgid "Show origin of this line"
msgstr "Visa ursprunget för den här raden"
-#: gitk:2500
+#: gitk:2507
msgid "Run git gui blame on this line"
msgstr "Kör git gui blame på den här raden"
-#: gitk:2782
+#: gitk:2789
msgid ""
"\n"
"Gitk - a commit viewer for git\n"
"\n"
-"Copyright \\u00a9 2005-2010 Paul Mackerras\n"
+"Copyright ©9 2005-2010 Paul Mackerras\n"
"\n"
"Use and redistribute under the terms of the GNU General Public License"
msgstr ""
"\n"
"Gitk - en incheckningsvisare för git\n"
"\n"
-"Copyright \\u00a9 2005-2010 Paul Mackerras\n"
+"Copyright ©9 2005-2010 Paul Mackerras\n"
"\n"
"Använd och vidareförmedla enligt villkoren i GNU General Public License"
-#: gitk:2790 gitk:2854 gitk:9230
+#: gitk:2797 gitk:2862 gitk:9253
msgid "Close"
msgstr "Stäng"
-#: gitk:2811
+#: gitk:2818
msgid "Gitk key bindings"
msgstr "Tangentbordsbindningar för Gitk"
-#: gitk:2814
+#: gitk:2821
msgid "Gitk key bindings:"
msgstr "Tangentbordsbindningar för Gitk:"
-#: gitk:2816
+#: gitk:2823
#, tcl-format
msgid "<%s-Q>\t\tQuit"
msgstr "<%s-Q>\t\tAvsluta"
-#: gitk:2817
+#: gitk:2824
+#, tcl-format
+msgid "<%s-W>\t\tClose window"
+msgstr "<%s-W>\t\tStäng fönster"
+
+#: gitk:2825
msgid "<Home>\t\tMove to first commit"
msgstr "<Home>\t\tGå till första incheckning"
-#: gitk:2818
+#: gitk:2826
msgid "<End>\t\tMove to last commit"
msgstr "<End>\t\tGå till sista incheckning"
-#: gitk:2819
+#: gitk:2827
msgid "<Up>, p, i\tMove up one commit"
msgstr "<Upp>, p, i\tGå en incheckning upp"
-#: gitk:2820
+#: gitk:2828
msgid "<Down>, n, k\tMove down one commit"
msgstr "<Ned>, n, k\tGå en incheckning ned"
-#: gitk:2821
+#: gitk:2829
msgid "<Left>, z, j\tGo back in history list"
msgstr "<Vänster>, z, j\tGå bakåt i historiken"
-#: gitk:2822
+#: gitk:2830
msgid "<Right>, x, l\tGo forward in history list"
msgstr "<Höger>, x, l\tGå framåt i historiken"
-#: gitk:2823
+#: gitk:2831
msgid "<PageUp>\tMove up one page in commit list"
msgstr "<PageUp>\tGå upp en sida i incheckningslistan"
-#: gitk:2824
+#: gitk:2832
msgid "<PageDown>\tMove down one page in commit list"
msgstr "<PageDown>\tGå ned en sida i incheckningslistan"
-#: gitk:2825
+#: gitk:2833
#, tcl-format
msgid "<%s-Home>\tScroll to top of commit list"
msgstr "<%s-Home>\tRulla till början av incheckningslistan"
-#: gitk:2826
+#: gitk:2834
#, tcl-format
msgid "<%s-End>\tScroll to bottom of commit list"
msgstr "<%s-End>\tRulla till slutet av incheckningslistan"
-#: gitk:2827
+#: gitk:2835
#, tcl-format
msgid "<%s-Up>\tScroll commit list up one line"
msgstr "<%s-Upp>\tRulla incheckningslistan upp ett steg"
-#: gitk:2828
+#: gitk:2836
#, tcl-format
msgid "<%s-Down>\tScroll commit list down one line"
msgstr "<%s-Ned>\tRulla incheckningslistan ned ett steg"
-#: gitk:2829
+#: gitk:2837
#, tcl-format
msgid "<%s-PageUp>\tScroll commit list up one page"
msgstr "<%s-PageUp>\tRulla incheckningslistan upp en sida"
-#: gitk:2830
+#: gitk:2838
#, tcl-format
msgid "<%s-PageDown>\tScroll commit list down one page"
msgstr "<%s-PageDown>\tRulla incheckningslistan ned en sida"
-#: gitk:2831
+#: gitk:2839
msgid "<Shift-Up>\tFind backwards (upwards, later commits)"
msgstr "<Skift-Upp>\tSök bakåt (uppåt, senare incheckningar)"
-#: gitk:2832
+#: gitk:2840
msgid "<Shift-Down>\tFind forwards (downwards, earlier commits)"
msgstr "<Skift-Ned>\tSök framåt (nedåt, tidigare incheckningar)"
-#: gitk:2833
+#: gitk:2841
msgid "<Delete>, b\tScroll diff view up one page"
msgstr "<Delete>, b\tRulla diffvisningen upp en sida"
-#: gitk:2834
+#: gitk:2842
msgid "<Backspace>\tScroll diff view up one page"
msgstr "<Baksteg>\tRulla diffvisningen upp en sida"
-#: gitk:2835
+#: gitk:2843
msgid "<Space>\t\tScroll diff view down one page"
msgstr "<Blanksteg>\tRulla diffvisningen ned en sida"
-#: gitk:2836
+#: gitk:2844
msgid "u\t\tScroll diff view up 18 lines"
msgstr "u\t\tRulla diffvisningen upp 18 rader"
-#: gitk:2837
+#: gitk:2845
msgid "d\t\tScroll diff view down 18 lines"
msgstr "d\t\tRulla diffvisningen ned 18 rader"
-#: gitk:2838
+#: gitk:2846
#, tcl-format
msgid "<%s-F>\t\tFind"
msgstr "<%s-F>\t\tSök"
-#: gitk:2839
+#: gitk:2847
#, tcl-format
msgid "<%s-G>\t\tMove to next find hit"
msgstr "<%s-G>\t\tGå till nästa sökträff"
-#: gitk:2840
+#: gitk:2848
msgid "<Return>\tMove to next find hit"
msgstr "<Return>\t\tGå till nästa sökträff"
-#: gitk:2841
+#: gitk:2849
msgid "/\t\tFocus the search box"
msgstr "/\t\tFokusera sökrutan"
-#: gitk:2842
+#: gitk:2850
msgid "?\t\tMove to previous find hit"
msgstr "?\t\tGå till föregående sökträff"
-#: gitk:2843
+#: gitk:2851
msgid "f\t\tScroll diff view to next file"
msgstr "f\t\tRulla diffvisningen till nästa fil"
-#: gitk:2844
+#: gitk:2852
#, tcl-format
msgid "<%s-S>\t\tSearch for next hit in diff view"
msgstr "<%s-S>\t\tGå till nästa sökträff i diffvisningen"
-#: gitk:2845
+#: gitk:2853
#, tcl-format
msgid "<%s-R>\t\tSearch for previous hit in diff view"
msgstr "<%s-R>\t\tGå till föregående sökträff i diffvisningen"
-#: gitk:2846
+#: gitk:2854
#, tcl-format
msgid "<%s-KP+>\tIncrease font size"
msgstr "<%s-Num+>\tÖka teckenstorlek"
-#: gitk:2847
+#: gitk:2855
#, tcl-format
msgid "<%s-plus>\tIncrease font size"
msgstr "<%s-plus>\tÖka teckenstorlek"
-#: gitk:2848
+#: gitk:2856
#, tcl-format
msgid "<%s-KP->\tDecrease font size"
msgstr "<%s-Num->\tMinska teckenstorlek"
-#: gitk:2849
+#: gitk:2857
#, tcl-format
msgid "<%s-minus>\tDecrease font size"
msgstr "<%s-minus>\tMinska teckenstorlek"
-#: gitk:2850
+#: gitk:2858
msgid "<F5>\t\tUpdate"
msgstr "<F5>\t\tUppdatera"
-#: gitk:3305 gitk:3314
+#: gitk:3313 gitk:3322
#, tcl-format
msgid "Error creating temporary directory %s:"
msgstr "Fel vid skapande av temporär katalog %s:"
-#: gitk:3327
+#: gitk:3335
#, tcl-format
msgid "Error getting \"%s\" from %s:"
msgstr "Fel vid hämtning av \"%s\" från %s:"
-#: gitk:3390
+#: gitk:3398
msgid "command failed:"
msgstr "kommando misslyckades:"
-#: gitk:3539
+#: gitk:3547
msgid "No such commit"
msgstr "Incheckning saknas"
-#: gitk:3553
+#: gitk:3561
msgid "git gui blame: command failed:"
msgstr "git gui blame: kommando misslyckades:"
-#: gitk:3584
+#: gitk:3592
#, tcl-format
msgid "Couldn't read merge head: %s"
msgstr "Kunde inte läsa sammanslagningshuvud: %s"
-#: gitk:3592
+#: gitk:3600
#, tcl-format
msgid "Error reading index: %s"
msgstr "Fel vid läsning av index: %s"
-#: gitk:3617
+#: gitk:3625
#, tcl-format
msgid "Couldn't start git blame: %s"
msgstr "Kunde inte starta git blame: %s"
-#: gitk:3620 gitk:6409
+#: gitk:3628 gitk:6419
msgid "Searching"
msgstr "Söker"
-#: gitk:3652
+#: gitk:3660
#, tcl-format
msgid "Error running git blame: %s"
msgstr "Fel vid körning av git blame: %s"
-#: gitk:3680
+#: gitk:3688
#, tcl-format
msgid "That line comes from commit %s, which is not in this view"
msgstr "Raden kommer från incheckningen %s, som inte finns i denna vy"
-#: gitk:3694
+#: gitk:3702
msgid "External diff viewer failed:"
msgstr "Externt diff-verktyg misslyckades:"
-#: gitk:3812
+#: gitk:3820
msgid "Gitk view definition"
msgstr "Definition av Gitk-vy"
-#: gitk:3816
+#: gitk:3824
msgid "Remember this view"
msgstr "Spara denna vy"
-#: gitk:3817
+#: gitk:3825
msgid "References (space separated list):"
msgstr "Referenser (blankstegsavdelad lista):"
-#: gitk:3818
+#: gitk:3826
msgid "Branches & tags:"
msgstr "Grenar & taggar:"
-#: gitk:3819
+#: gitk:3827
msgid "All refs"
msgstr "Alla referenser"
-#: gitk:3820
+#: gitk:3828
msgid "All (local) branches"
msgstr "Alla (lokala) grenar"
-#: gitk:3821
+#: gitk:3829
msgid "All tags"
msgstr "Alla taggar"
-#: gitk:3822
+#: gitk:3830
msgid "All remote-tracking branches"
msgstr "Alla fjärrspårande grenar"
-#: gitk:3823
+#: gitk:3831
msgid "Commit Info (regular expressions):"
msgstr "Incheckningsinfo (reguljära uttryck):"
-#: gitk:3824
+#: gitk:3832
msgid "Author:"
msgstr "Författare:"
-#: gitk:3825
+#: gitk:3833
msgid "Committer:"
msgstr "Incheckare:"
-#: gitk:3826
+#: gitk:3834
msgid "Commit Message:"
msgstr "Incheckningsmeddelande:"
-#: gitk:3827
+#: gitk:3835
msgid "Matches all Commit Info criteria"
msgstr "Motsvarar alla kriterier för incheckningsinfo"
-#: gitk:3828
+#: gitk:3836
msgid "Changes to Files:"
msgstr "Ändringar av filer:"
-#: gitk:3829
+#: gitk:3837
msgid "Fixed String"
msgstr "Fast sträng"
-#: gitk:3830
+#: gitk:3838
msgid "Regular Expression"
msgstr "Reguljärt uttryck"
-#: gitk:3831
+#: gitk:3839
msgid "Search string:"
msgstr "Söksträng:"
-#: gitk:3832
+#: gitk:3840
msgid ""
"Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 "
"15:27:38\"):"
"Incheckingsdatum (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 "
"15:27:38\"):"
-#: gitk:3833
+#: gitk:3841
msgid "Since:"
msgstr "Från:"
-#: gitk:3834
+#: gitk:3842
msgid "Until:"
msgstr "Till:"
-#: gitk:3835
+#: gitk:3843
msgid "Limit and/or skip a number of revisions (positive integer):"
msgstr "Begränsa och/eller hoppa över ett antal revisioner (positivt heltal):"
-#: gitk:3836
+#: gitk:3844
msgid "Number to show:"
msgstr "Antal att visa:"
-#: gitk:3837
+#: gitk:3845
msgid "Number to skip:"
msgstr "Antal att hoppa över:"
-#: gitk:3838
+#: gitk:3846
msgid "Miscellaneous options:"
msgstr "Diverse alternativ:"
-#: gitk:3839
+#: gitk:3847
msgid "Strictly sort by date"
msgstr "Strikt datumsortering"
-#: gitk:3840
+#: gitk:3848
msgid "Mark branch sides"
msgstr "Markera sidogrenar"
-#: gitk:3841
+#: gitk:3849
msgid "Limit to first parent"
msgstr "Begränsa till första förälder"
-#: gitk:3842
+#: gitk:3850
msgid "Simple history"
msgstr "Enkel historik"
-#: gitk:3843
+#: gitk:3851
msgid "Additional arguments to git log:"
msgstr "Ytterligare argument till git log:"
-#: gitk:3844
+#: gitk:3852
msgid "Enter files and directories to include, one per line:"
msgstr "Ange filer och kataloger att ta med, en per rad:"
-#: gitk:3845
+#: gitk:3853
msgid "Command to generate more commits to include:"
msgstr "Kommando för att generera fler incheckningar att ta med:"
-#: gitk:3967
+#: gitk:3977
msgid "Gitk: edit view"
msgstr "Gitk: redigera vy"
-#: gitk:3975
+#: gitk:3985
msgid "-- criteria for selecting revisions"
msgstr " - kriterier för val av revisioner"
-#: gitk:3980
+#: gitk:3990
msgid "View Name"
msgstr "Namn på vy"
-#: gitk:4055
+#: gitk:4065
msgid "Apply (F5)"
msgstr "Använd (F5)"
-#: gitk:4093
+#: gitk:4103
msgid "Error in commit selection arguments:"
msgstr "Fel i argument för val av incheckningar:"
-#: gitk:4146 gitk:4198 gitk:4646 gitk:4660 gitk:5921 gitk:11534 gitk:11535
+#: gitk:4156 gitk:4208 gitk:4656 gitk:4670 gitk:5931 gitk:11551 gitk:11552
msgid "None"
msgstr "Inget"
-#: gitk:4594 gitk:6441 gitk:8287 gitk:8302
+#: gitk:4604 gitk:6451 gitk:8309 gitk:8324
msgid "Date"
msgstr "Datum"
-#: gitk:4594 gitk:6441
+#: gitk:4604 gitk:6451
msgid "CDate"
msgstr "Skapat datum"
-#: gitk:4743 gitk:4748
+#: gitk:4753 gitk:4758
msgid "Descendant"
msgstr "Avkomling"
-#: gitk:4744
+#: gitk:4754
msgid "Not descendant"
msgstr "Inte avkomling"
-#: gitk:4751 gitk:4756
+#: gitk:4761 gitk:4766
msgid "Ancestor"
msgstr "Förfader"
-#: gitk:4752
+#: gitk:4762
msgid "Not ancestor"
msgstr "Inte förfader"
-#: gitk:5042
+#: gitk:5052
msgid "Local changes checked in to index but not committed"
msgstr "Lokala ändringar sparade i indexet men inte incheckade"
-#: gitk:5078
+#: gitk:5088
msgid "Local uncommitted changes, not checked in to index"
msgstr "Lokala ändringar, ej sparade i indexet"
-#: gitk:6759
+#: gitk:6769
msgid "many"
msgstr "många"
-#: gitk:6942
+#: gitk:6952
msgid "Tags:"
msgstr "Taggar:"
-#: gitk:6959 gitk:6965 gitk:8280
+#: gitk:6969 gitk:6975 gitk:8302
msgid "Parent"
msgstr "Förälder"
-#: gitk:6970
+#: gitk:6980
msgid "Child"
msgstr "Barn"
-#: gitk:6979
+#: gitk:6989
msgid "Branch"
msgstr "Gren"
-#: gitk:6982
+#: gitk:6992
msgid "Follows"
msgstr "Följer"
-#: gitk:6985
+#: gitk:6995
msgid "Precedes"
msgstr "Föregår"
-#: gitk:7522
+#: gitk:7532
#, tcl-format
msgid "Error getting diffs: %s"
msgstr "Fel vid hämtning av diff: %s"
-#: gitk:8108
+#: gitk:8130
msgid "Goto:"
msgstr "Gå till:"
-#: gitk:8129
+#: gitk:8151
#, tcl-format
msgid "Short SHA1 id %s is ambiguous"
msgstr "Förkortat SHA1-id %s är tvetydigt"
-#: gitk:8136
+#: gitk:8158
#, tcl-format
msgid "Revision %s is not known"
msgstr "Revisionen %s är inte känd"
-#: gitk:8146
+#: gitk:8168
#, tcl-format
msgid "SHA1 id %s is not known"
msgstr "SHA-id:t %s är inte känt"
-#: gitk:8148
+#: gitk:8170
#, tcl-format
msgid "Revision %s is not in the current view"
msgstr "Revisionen %s finns inte i den nuvarande vyn"
-#: gitk:8290
+#: gitk:8312
msgid "Children"
msgstr "Barn"
-#: gitk:8348
+#: gitk:8370
#, tcl-format
msgid "Reset %s branch to here"
msgstr "Återställ grenen %s hit"
-#: gitk:8350
+#: gitk:8372
msgid "Detached head: can't reset"
msgstr "Frånkopplad head: kan inte återställa"
-#: gitk:8459 gitk:8465
+#: gitk:8481 gitk:8487
msgid "Skipping merge commit "
msgstr "Hoppar över sammanslagningsincheckning "
-#: gitk:8474 gitk:8479
+#: gitk:8496 gitk:8501
msgid "Error getting patch ID for "
msgstr "Fel vid hämtning av patch-id för "
-#: gitk:8475 gitk:8480
+#: gitk:8497 gitk:8502
msgid " - stopping\n"
msgstr " - stannar\n"
-#: gitk:8485 gitk:8488 gitk:8496 gitk:8510 gitk:8519
+#: gitk:8507 gitk:8510 gitk:8518 gitk:8532 gitk:8541
msgid "Commit "
msgstr "Incheckning "
-#: gitk:8489
+#: gitk:8511
msgid ""
" is the same patch as\n"
" "
" är samma patch som\n"
" "
-#: gitk:8497
+#: gitk:8519
msgid ""
" differs from\n"
" "
" skiljer sig från\n"
" "
-#: gitk:8499
+#: gitk:8521
msgid ""
"Diff of commits:\n"
"\n"
-msgstr "Skillnad mellan incheckningar:\n"
+msgstr ""
+"Skillnad mellan incheckningar:\n"
"\n"
-""
-#: gitk:8511 gitk:8520
+#: gitk:8533 gitk:8542
#, tcl-format
msgid " has %s children - stopping\n"
msgstr " har %s barn - stannar\n"
-#: gitk:8539
+#: gitk:8561
#, tcl-format
msgid "Error writing commit to file: %s"
msgstr "Fel vid skrivning av incheckning till fil: %s"
-#: gitk:8545
+#: gitk:8567
#, tcl-format
msgid "Error diffing commits: %s"
msgstr "Fel vid jämförelse av incheckningar: %s"
-#: gitk:8575
+#: gitk:8598
msgid "Top"
msgstr "Topp"
-#: gitk:8576
+#: gitk:8599
msgid "From"
msgstr "Från"
-#: gitk:8581
+#: gitk:8604
msgid "To"
msgstr "Till"
-#: gitk:8605
+#: gitk:8628
msgid "Generate patch"
msgstr "Generera patch"
-#: gitk:8607
+#: gitk:8630
msgid "From:"
msgstr "Från:"
-#: gitk:8616
+#: gitk:8639
msgid "To:"
msgstr "Till:"
-#: gitk:8625
+#: gitk:8648
msgid "Reverse"
msgstr "Vänd"
-#: gitk:8627 gitk:8822
+#: gitk:8650 gitk:8845
msgid "Output file:"
msgstr "Utdatafil:"
-#: gitk:8633
+#: gitk:8656
msgid "Generate"
msgstr "Generera"
-#: gitk:8671
+#: gitk:8694
msgid "Error creating patch:"
msgstr "Fel vid generering av patch:"
-#: gitk:8694 gitk:8810 gitk:8867
+#: gitk:8717 gitk:8833 gitk:8890
msgid "ID:"
msgstr "Id:"
-#: gitk:8703
+#: gitk:8726
msgid "Tag name:"
msgstr "Taggnamn:"
-#: gitk:8706
+#: gitk:8729
msgid "Tag message is optional"
msgstr "Taggmeddelandet är valfritt"
-#: gitk:8708
+#: gitk:8731
msgid "Tag message:"
msgstr "Taggmeddelande:"
-#: gitk:8712 gitk:8876
+#: gitk:8735 gitk:8899
msgid "Create"
msgstr "Skapa"
-#: gitk:8730
+#: gitk:8753
msgid "No tag name specified"
msgstr "Inget taggnamn angavs"
-#: gitk:8734
+#: gitk:8757
#, tcl-format
msgid "Tag \"%s\" already exists"
msgstr "Taggen \"%s\" finns redan"
-#: gitk:8744
+#: gitk:8767
msgid "Error creating tag:"
msgstr "Fel vid skapande av tagg:"
-#: gitk:8819
+#: gitk:8842
msgid "Command:"
msgstr "Kommando:"
-#: gitk:8827
+#: gitk:8850
msgid "Write"
msgstr "Skriv"
-#: gitk:8845
+#: gitk:8868
msgid "Error writing commit:"
msgstr "Fel vid skrivning av incheckning:"
-#: gitk:8872
+#: gitk:8895
msgid "Name:"
msgstr "Namn:"
-#: gitk:8895
+#: gitk:8918
msgid "Please specify a name for the new branch"
msgstr "Ange ett namn för den nya grenen"
-#: gitk:8900
+#: gitk:8923
#, tcl-format
msgid "Branch '%s' already exists. Overwrite?"
msgstr "Grenen \"%s\" finns redan. Skriva över?"
-#: gitk:8966
+#: gitk:8989
#, tcl-format
msgid "Commit %s is already included in branch %s -- really re-apply it?"
msgstr ""
"Incheckningen %s finns redan på grenen %s -- skall den verkligen appliceras "
"på nytt?"
-#: gitk:8971
+#: gitk:8994
msgid "Cherry-picking"
msgstr "Plockar"
-#: gitk:8980
+#: gitk:9003
#, tcl-format
msgid ""
"Cherry-pick failed because of local changes to file '%s'.\n"
"Checka in, återställ eller spara undan (stash) dina ändringar och försök "
"igen."
-#: gitk:8986
+#: gitk:9009
msgid ""
"Cherry-pick failed because of merge conflict.\n"
"Do you wish to run git citool to resolve it?"
"Cherry-pick misslyckades på grund av en sammanslagningskonflikt.\n"
"Vill du köra git citool för att lösa den?"
-#: gitk:9002
+#: gitk:9025
msgid "No changes committed"
msgstr "Inga ändringar incheckade"
-#: gitk:9028
+#: gitk:9051
msgid "Confirm reset"
msgstr "Bekräfta återställning"
-#: gitk:9030
+#: gitk:9053
#, tcl-format
msgid "Reset branch %s to %s?"
msgstr "Återställa grenen %s till %s?"
-#: gitk:9032
+#: gitk:9055
msgid "Reset type:"
msgstr "Typ av återställning:"
-#: gitk:9035
+#: gitk:9058
msgid "Soft: Leave working tree and index untouched"
msgstr "Mjuk: Rör inte utcheckning och index"
-#: gitk:9038
+#: gitk:9061
msgid "Mixed: Leave working tree untouched, reset index"
msgstr "Blandad: Rör inte utcheckning, återställ index"
-#: gitk:9041
+#: gitk:9064
msgid ""
"Hard: Reset working tree and index\n"
"(discard ALL local changes)"
"Hård: Återställ utcheckning och index\n"
"(förkastar ALLA lokala ändringar)"
-#: gitk:9058
+#: gitk:9081
msgid "Resetting"
msgstr "Återställer"
-#: gitk:9118
+#: gitk:9141
msgid "Checking out"
msgstr "Checkar ut"
-#: gitk:9171
+#: gitk:9194
msgid "Cannot delete the currently checked-out branch"
msgstr "Kan inte ta bort den just nu utcheckade grenen"
-#: gitk:9177
+#: gitk:9200
#, tcl-format
msgid ""
"The commits on branch %s aren't on any other branch.\n"
"Incheckningarna på grenen %s existerar inte på någon annan gren.\n"
"Vill du verkligen ta bort grenen %s?"
-#: gitk:9208
+#: gitk:9231
#, tcl-format
msgid "Tags and heads: %s"
msgstr "Taggar och huvuden: %s"
-#: gitk:9223
+#: gitk:9246
msgid "Filter"
msgstr "Filter"
-#: gitk:9518
+#: gitk:9541
msgid ""
"Error reading commit topology information; branch and preceding/following "
"tag information will be incomplete."
"Fel vid läsning av information om incheckningstopologi; information om "
"grenar och föregående/senare taggar kommer inte vara komplett."
-#: gitk:10504
+#: gitk:10527
msgid "Tag"
msgstr "Tagg"
-#: gitk:10504
+#: gitk:10527
msgid "Id"
msgstr "Id"
-#: gitk:10554
+#: gitk:10576
msgid "Gitk font chooser"
msgstr "Teckensnittsväljare för Gitk"
-#: gitk:10571
+#: gitk:10593
msgid "B"
msgstr "F"
-#: gitk:10574
+#: gitk:10596
msgid "I"
msgstr "K"
-#: gitk:10692
+#: gitk:10714
msgid "Gitk preferences"
msgstr "Inställningar för Gitk"
-#: gitk:10694
+#: gitk:10716
msgid "Commit list display options"
msgstr "Alternativ för incheckningslistvy"
-#: gitk:10697
+#: gitk:10719
msgid "Maximum graph width (lines)"
msgstr "Maximal grafbredd (rader)"
-#: gitk:10700
+#: gitk:10722
#, tcl-format
msgid "Maximum graph width (% of pane)"
msgstr "Maximal grafbredd (% av ruta)"
-#: gitk:10703
+#: gitk:10725
msgid "Show local changes"
msgstr "Visa lokala ändringar"
-#: gitk:10706
+#: gitk:10728
msgid "Auto-select SHA1"
msgstr "Välj SHA1 automatiskt"
-#: gitk:10709
+#: gitk:10731
msgid "Hide remote refs"
msgstr "Dölj fjärr-referenser"
-#: gitk:10713
+#: gitk:10735
msgid "Diff display options"
msgstr "Alternativ för diffvy"
-#: gitk:10715
+#: gitk:10737
msgid "Tab spacing"
msgstr "Blanksteg för tabulatortecken"
-#: gitk:10718
+#: gitk:10740
msgid "Display nearby tags"
msgstr "Visa närliggande taggar"
-#: gitk:10721
+#: gitk:10743
msgid "Limit diffs to listed paths"
msgstr "Begränsa diff till listade sökvägar"
-#: gitk:10724
+#: gitk:10746
msgid "Support per-file encodings"
msgstr "Stöd för filspecifika teckenkodningar"
-#: gitk:10730 gitk:10819
+#: gitk:10752 gitk:10832
msgid "External diff tool"
msgstr "Externt diff-verktyg"
-#: gitk:10731
+#: gitk:10753
msgid "Choose..."
msgstr "Välj..."
-#: gitk:10736
+#: gitk:10758
msgid "General options"
msgstr "Allmänna inställningar"
-#: gitk:10739
+#: gitk:10761
msgid "Use themed widgets"
msgstr "Använd tema på fönsterelement"
-#: gitk:10741
+#: gitk:10763
msgid "(change requires restart)"
msgstr "(ändringen kräver omstart)"
-#: gitk:10743
+#: gitk:10765
msgid "(currently unavailable)"
msgstr "(för närvarande inte tillgängligt)"
-#: gitk:10747
+#: gitk:10769
msgid "Colors: press to choose"
msgstr "Färger: tryck för att välja"
-#: gitk:10750
+#: gitk:10772
msgid "Interface"
msgstr "Gränssnitt"
-#: gitk:10751
+#: gitk:10773
msgid "interface"
msgstr "gränssnitt"
-#: gitk:10754
+#: gitk:10776
msgid "Background"
msgstr "Bakgrund"
-#: gitk:10755 gitk:10785
+#: gitk:10777 gitk:10807
msgid "background"
msgstr "bakgrund"
-#: gitk:10758
+#: gitk:10780
msgid "Foreground"
msgstr "Förgrund"
-#: gitk:10759
+#: gitk:10781
msgid "foreground"
msgstr "förgrund"
-#: gitk:10762
+#: gitk:10784
msgid "Diff: old lines"
msgstr "Diff: gamla rader"
-#: gitk:10763
+#: gitk:10785
msgid "diff old lines"
msgstr "diff gamla rader"
-#: gitk:10767
+#: gitk:10789
msgid "Diff: new lines"
msgstr "Diff: nya rader"
-#: gitk:10768
+#: gitk:10790
msgid "diff new lines"
msgstr "diff nya rader"
-#: gitk:10772
+#: gitk:10794
msgid "Diff: hunk header"
msgstr "Diff: delhuvud"
-#: gitk:10774
+#: gitk:10796
msgid "diff hunk header"
msgstr "diff delhuvud"
-#: gitk:10778
+#: gitk:10800
msgid "Marked line bg"
msgstr "Markerad rad bakgrund"
-#: gitk:10780
+#: gitk:10802
msgid "marked line background"
msgstr "markerad rad bakgrund"
-#: gitk:10784
+#: gitk:10806
msgid "Select bg"
msgstr "Markerad bakgrund"
-#: gitk:10788
+#: gitk:10810
msgid "Fonts: press to choose"
msgstr "Teckensnitt: tryck för att välja"
-#: gitk:10790
+#: gitk:10812
msgid "Main font"
msgstr "Huvudteckensnitt"
-#: gitk:10791
+#: gitk:10813
msgid "Diff display font"
msgstr "Teckensnitt för diffvisning"
-#: gitk:10792
+#: gitk:10814
msgid "User interface font"
msgstr "Teckensnitt för användargränssnitt"
-#: gitk:10829
+#: gitk:10842
#, tcl-format
msgid "Gitk: choose color for %s"
msgstr "Gitk: välj färg för %s"
-#: gitk:11433
+#: gitk:11445
msgid "Cannot find a git repository here."
-msgstr "Hittar inget gitk-arkiv här."
+msgstr "Hittar inget git-arkiv här."
-#: gitk:11437
+#: gitk:11449
#, tcl-format
msgid "Cannot find the git directory \"%s\"."
msgstr "Hittar inte git-katalogen \"%s\"."
-#: gitk:11484
+#: gitk:11496
#, tcl-format
msgid "Ambiguous argument '%s': both revision and filename"
msgstr "Tvetydigt argument \"%s\": både revision och filnamn"
-#: gitk:11496
+#: gitk:11508
msgid "Bad arguments to gitk:"
msgstr "Felaktiga argument till gitk:"
-#: gitk:11587
+#: gitk:11604
msgid "Command line"
msgstr "Kommandorad"
diff --git a/gitweb/INSTALL b/gitweb/INSTALL
index 823053173c92e4097dad54945db16f42b4b5d994..4964a679b3013caa9bfa2f438dc110eaa0274b69 100644 (file)
--- a/gitweb/INSTALL
+++ b/gitweb/INSTALL
- Perl modules: CGI, Encode, Fcntl, File::Find, File::Basename.
- web server
+The following optional Perl modules are required for extra features
+ - Digest::MD5 - for gravatar support
+ - CGI::Fast and FCGI - for running gitweb as FastCGI script
+ - HTML::TagCloud - for fancy tag cloud in project list view
+ - HTTP::Date or Time::ParseDate - to support If-Modified-Since for feeds
+
Example web server configuration
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/gitweb/Makefile b/gitweb/Makefile
index 2fb7c2d77bbd5f2041341822859dce51ae504d83..0a6ac00631ed8eac5c20248a345074e4b8ff3707 100644 (file)
--- a/gitweb/Makefile
+++ b/gitweb/Makefile
GITWEB_JS = static/gitweb.js
GITWEB_SITE_HEADER =
GITWEB_SITE_FOOTER =
+HIGHLIGHT_BIN = highlight
# include user config
-include ../config.mak.autogen
-include ../config.mak
+-include config.mak
# determine version
../GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE
-e 's|++GITWEB_FAVICON++|$(GITWEB_FAVICON)|g' \
-e 's|++GITWEB_JS++|$(GITWEB_JS)|g' \
-e 's|++GITWEB_SITE_HEADER++|$(GITWEB_SITE_HEADER)|g' \
- -e 's|++GITWEB_SITE_FOOTER++|$(GITWEB_SITE_FOOTER)|g'
+ -e 's|++GITWEB_SITE_FOOTER++|$(GITWEB_SITE_FOOTER)|g' \
+ -e 's|++HIGHLIGHT_BIN++|$(HIGHLIGHT_BIN)|g'
GITWEB-BUILD-OPTIONS: FORCE
@rm -f $@+
chmod +x $@+ && \
mv $@+ $@
+### Testing rules
+
+test:
+ $(MAKE) -C ../t gitweb-test
+
+test-installed:
+ GITWEB_TEST_INSTALLED='$(DESTDIR_SQ)$(gitwebdir_SQ)' \
+ $(MAKE) -C ../t gitweb-test
+
### Installation rules
install: all
clean:
$(RM) gitweb.cgi static/gitweb.min.js static/gitweb.min.css GITWEB-BUILD-OPTIONS
-.PHONY: all clean install .FORCE-GIT-VERSION-FILE FORCE
+.PHONY: all clean install test test-installed .FORCE-GIT-VERSION-FILE FORCE
diff --git a/gitweb/README b/gitweb/README
index d4811987965e5a5036b7ed71e32d93578579073a..4a673933acee475dc423007063d1300b44176a61 100644 (file)
--- a/gitweb/README
+++ b/gitweb/README
when gitweb.cgi is executed, then the file specified in the environment
variable will be loaded instead of the file specified when gitweb.cgi was
created. [Default: /etc/gitweb.conf]
+ * HIGHLIGHT_BIN
+ Path to the highlight executable to use (must be the one from
+ http://www.andre-simon.de due to assumptions about parameters and output).
+ Useful if highlight is not installed on your webserver's PATH.
+ [Default: highlight]
Runtime gitweb configuration
* $my_url, $my_uri
Full URL and absolute URL of gitweb script;
in earlier versions of gitweb you might have need to set those
- variables, now there should be no need to do it.
+ variables, now there should be no need to do it. See
+ $per_request_config if you need to set them still.
* $base_url
Base URL for relative URLs in pages generated by gitweb,
(e.g. $logo, $favicon, @stylesheets if they are relative URLs),
needed and used only for URLs with nonempty PATH_INFO via
<base href="$base_url">. Usually gitweb sets its value correctly,
and there is no need to set this variable, e.g. to $my_uri or "/".
+ See $per_request_config if you need to set it anyway.
* $home_link
Target of the home link on top of all pages (the first part of view
"breadcrumbs"). By default set to absolute URI of a page ($my_uri).
If server load exceed this value then return "503 Service Unavailable" error.
Server load is taken to be 0 if gitweb cannot determine its value. Set it to
undefined value to turn it off. The default is 300.
-
+ * $highlight_bin
+ Path to the highlight executable to use (must be the one from
+ http://www.andre-simon.de due to assumptions about parameters and output).
+ Useful if highlight is not installed on your webserver's PATH.
+ [Default: highlight]
+ * $per_request_config
+ If set to code reference, it would be run once per each request. You can
+ set parts of configuration that change per session, e.g. by setting it to
+ sub { $ENV{GL_USER} = $cgi->remote_user || "gitweb"; }
+ Otherwise it is treated as boolean value: if true gitweb would process
+ config file once per request, if false it would process config file only
+ once. Note: $my_url, $my_uri, and $base_url are overwritten with
+ their default values before every request, so if you want to change
+ them, be sure to set this variable to true or a code reference effecting
+ the desired changes. The default is true.
Projects list file format
~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl
index aad93099d271df4be10b35edd5e623cf5d7fcdac..1b9369d1a72f625bcb66e021bdab8559930cb0eb 100755 (executable)
--- a/gitweb/gitweb.perl
+++ b/gitweb/gitweb.perl
use Fcntl ':mode';
use File::Find qw();
use File::Basename qw(basename);
+use Time::HiRes qw(gettimeofday tv_interval);
binmode STDOUT, ':utf8';
-our $t0;
-if (eval { require Time::HiRes; 1; }) {
- $t0 = [Time::HiRes::gettimeofday()];
-}
+our $t0 = [ gettimeofday() ];
our $number_of_git_cmds = 0;
BEGIN {
# the gitweb domain.
our $prevent_xss = 0;
+# Path to the highlight executable to use (must be the one from
+# http://www.andre-simon.de due to assumptions about parameters and output).
+# Useful if highlight is not installed on your webserver's PATH.
+# [Default: highlight]
+our $highlight_bin = "++HIGHLIGHT_BIN++";
+
# information about snapshot formats that gitweb is capable of serving
our %known_snapshot_formats = (
# name => {
# main extensions, defining name of syntax;
# see files in /usr/share/highlight/langDefs/ directory
map { $_ => $_ }
- qw(py c cpp rb java css php sh pl js tex bib xml awk bat ini spec tcl),
+ qw(py c cpp rb java css php sh pl js tex bib xml awk bat ini spec tcl sql make),
# alternate extensions, see /etc/highlight/filetypes.conf
'h' => 'c',
+ map { $_ => 'sh' } qw(bash zsh ksh),
map { $_ => 'cpp' } qw(cxx c++ cc),
- map { $_ => 'php' } qw(php3 php4),
+ map { $_ => 'php' } qw(php3 php4 php5 phps),
map { $_ => 'pl' } qw(perl pm), # perhaps also 'cgi'
- 'mak' => 'make',
+ map { $_ => 'make'} qw(mak mk),
map { $_ => 'xml' } qw(xhtml html htm),
);
'sub' => sub { feature_bool('highlight', @_) },
'override' => 0,
'default' => [0]},
+
+ # Enable displaying of remote heads in the heads list
+
+ # To enable system wide have in $GITWEB_CONFIG
+ # $feature{'remote_heads'}{'default'} = [1];
+ # To have project specific config enable override in $GITWEB_CONFIG
+ # $feature{'remote_heads'}{'override'} = 1;
+ # and in project config gitweb.remote_heads = 0|1;
+ 'remote_heads' => {
+ 'sub' => sub { feature_bool('remote_heads', @_) },
+ 'override' => 0,
+ 'default' => [0]},
);
sub gitweb_get_feature {
!$known_snapshot_formats{$_}{'disabled'}} @fmts;
}
+# If it is set to code reference, it is code that it is to be run once per
+# request, allowing updating configurations that change with each request,
+# while running other code in config file only once.
+#
+# Otherwise, if it is false then gitweb would process config file only once;
+# if it is true then gitweb config would be run for each request.
+our $per_request_config = 1;
+
our ($GITWEB_CONFIG, $GITWEB_CONFIG_SYSTEM);
sub evaluate_gitweb_config {
our $GITWEB_CONFIG = $ENV{'GITWEB_CONFIG'} || "++GITWEB_CONFIG++";
"log" => \&git_log,
"patch" => \&git_patch,
"patches" => \&git_patches,
+ "remotes" => \&git_remotes,
"rss" => \&git_rss,
"atom" => \&git_atom,
"search" => \&git_search,
'history',
);
- # we want to catch
+ # we want to catch, among others
# [$hash_parent_base[:$file_parent]..]$hash_parent[:$file_name]
my ($parentrefname, $parentpathname, $refname, $pathname) =
- ($path_info =~ /^(?:(.+?)(?::(.+))?\.\.)?(.+?)(?::(.+))?$/);
+ ($path_info =~ /^(?:(.+?)(?::(.+))?\.\.)?([^:]+?)?(?::(.+))?$/);
# first, analyze the 'current' part
if (defined $pathname) {
# hash_base instead. It should also be noted that hand-crafted
# links having 'history' as an action and no pathname or hash
# set will fail, but that happens regardless of PATH_INFO.
- $input_params{'action'} ||= "shortlog";
- if (grep { $_ eq $input_params{'action'} } @wants_base) {
+ if (defined $parentrefname) {
+ # if there is parent let the default be 'shortlog' action
+ # (for http://git.example.com/repo.git/A..B links); if there
+ # is no parent, dispatch will detect type of object and set
+ # action appropriately if required (if action is not set)
+ $input_params{'action'} ||= "shortlog";
+ }
+ if ($input_params{'action'} &&
+ grep { $_ eq $input_params{'action'} } @wants_base) {
$input_params{'hash_base'} ||= $refname;
} else {
$input_params{'hash'} ||= $refname;
}
sub reset_timer {
- our $t0 = [Time::HiRes::gettimeofday()]
+ our $t0 = [ gettimeofday() ]
if defined $t0;
our $number_of_git_cmds = 0;
}
+our $first_request = 1;
sub run_request {
reset_timer();
evaluate_uri();
- evaluate_gitweb_config();
+ if ($first_request) {
+ evaluate_gitweb_config();
+ evaluate_git_version();
+ }
+ if ($per_request_config) {
+ if (ref($per_request_config) eq 'CODE') {
+ $per_request_config->();
+ } elsif (!$first_request) {
+ evaluate_gitweb_config();
+ }
+ }
check_loadavg();
# $projectroot and $projects_list might be set in gitweb config file
sub run {
evaluate_argv();
- evaluate_git_version();
+ $first_request = 1;
$pre_listen_hook->()
if $pre_listen_hook;
$post_dispatch_hook->()
if $post_dispatch_hook;
+ $first_request = 0;
last REQUEST if ($is_last_request->());
}
return (undef, undef);
}
+# Implementation note: when a single remote is wanted, we cannot use 'git
+# remote show -n' because that command always work (assuming it's a remote URL
+# if it's not defined), and we cannot use 'git remote show' because that would
+# try to make a network roundtrip. So the only way to find if that particular
+# remote is defined is to walk the list provided by 'git remote -v' and stop if
+# and when we find what we want.
+sub git_get_remotes_list {
+ my $wanted = shift;
+ my %remotes = ();
+
+ open my $fd, '-|' , git_cmd(), 'remote', '-v';
+ return unless $fd;
+ while (my $remote = <$fd>) {
+ chomp $remote;
+ $remote =~ s!\t(.*?)\s+\((\w+)\)$!!;
+ next if $wanted and not $remote eq $wanted;
+ my ($url, $key) = ($1, $2);
+
+ $remotes{$remote} ||= { 'heads' => () };
+ $remotes{$remote}{$key} = $url;
+ }
+ close $fd or return;
+ return wantarray ? %remotes : \%remotes;
+}
+
+# Takes a hash of remotes as first parameter and fills it by adding the
+# available remote heads for each of the indicated remotes.
+sub fill_remote_heads {
+ my $remotes = shift;
+ my @heads = map { "remotes/$_" } keys %$remotes;
+ my @remoteheads = git_get_heads_list(undef, @heads);
+ foreach my $remote (keys %$remotes) {
+ $remotes->{$remote}{'heads'} = [ grep {
+ $_->{'name'} =~ s!^$remote/!!
+ } @remoteheads ];
+ }
+}
+
sub git_get_references {
my $type = shift || "";
my %refs;
## parse to array of hashes functions
sub git_get_heads_list {
- my $limit = shift;
+ my ($limit, @classes) = @_;
+ @classes = ('heads') unless @classes;
+ my @patterns = map { "refs/$_" } @classes;
my @headslist;
open my $fd, '-|', git_cmd(), 'for-each-ref',
($limit ? '--count='.($limit+1) : ()), '--sort=-committerdate',
'--format=%(objectname) %(refname) %(subject)%00%(committer)',
- 'refs/heads'
+ @patterns
or return;
while (my $line = <$fd>) {
my %ref_item;
my ($committer, $epoch, $tz) =
($committerinfo =~ /^(.*) ([0-9]+) (.*)$/);
$ref_item{'fullname'} = $name;
- $name =~ s!^refs/heads/!!;
+ $name =~ s!^refs/(?:head|remote)s/!!;
$ref_item{'name'} = $name;
$ref_item{'id'} = $hash;
my ($fd, $highlight, $syntax) = @_;
return $fd unless ($highlight && defined $syntax);
- close $fd
- or die_error(404, "Reading blob failed");
+ close $fd;
open $fd, quote_command(git_cmd(), "cat-file", "blob", $hash)." | ".
- "highlight --xhtml --fragment --syntax $syntax |"
+ quote_command($highlight_bin).
+ " --fragment --syntax $syntax |"
or die_error(500, "Couldn't open file or run syntax highlighter");
return $fd;
}
$href_params{'-title'} = 'log';
}
- foreach my $format qw(RSS Atom) {
+ foreach my $format (qw(RSS Atom)) {
my $type = lc($format);
my %link_attr = (
'-rel' => 'alternate',
if (defined $project) {
print $cgi->a({-href => href(action=>"summary")}, esc_html($project));
if (defined $action) {
- print " / $action";
+ my $action_print = $action ;
+ if (defined $opts{-action_extra}) {
+ $action_print = $cgi->a({-href => href(action=>$action)},
+ $action);
+ }
+ print " / $action_print";
+ }
+ if (defined $opts{-action_extra}) {
+ print " / $opts{-action_extra}";
}
print "\n";
}
}
$href_params{'-title'} ||= 'log';
- foreach my $format qw(RSS Atom) {
+ foreach my $format (qw(RSS Atom)) {
$href_params{'action'} = lc($format);
print $cgi->a({-href => href(%href_params),
-title => "$href_params{'-title'} $format feed",
print "<div id=\"generating_info\">\n";
print 'This page took '.
'<span id="generating_time" class="time_span">'.
- Time::HiRes::tv_interval($t0, [Time::HiRes::gettimeofday()]).
+ tv_interval($t0, [ gettimeofday() ]).
' seconds </span>'.
' and '.
'<span id="generating_cmd">'.
"</div>\n";
}
+# returns a submenu for the nagivation of the refs views (tags, heads,
+# remotes) with the current view disabled and the remotes view only
+# available if the feature is enabled
+sub format_ref_views {
+ my ($current) = @_;
+ my @ref_views = qw{tags heads};
+ push @ref_views, 'remotes' if gitweb_check_feature('remote_heads');
+ return join " | ", map {
+ $_ eq $current ? $_ :
+ $cgi->a({-href => href(action=>$_)}, $_)
+ } @ref_views
+}
+
sub format_paging_nav {
my ($action, $page, $has_next_link) = @_;
my $paging_nav;
"\n</div>\n";
}
+sub format_repo_url {
+ my ($name, $url) = @_;
+ return "<tr class=\"metadata_url\"><td>$name</td><td>$url</td></tr>\n";
+}
+
+# Group output by placing it in a DIV element and adding a header.
+# Options for start_div() can be provided by passing a hash reference as the
+# first parameter to the function.
+# Options to git_print_header_div() can be provided by passing an array
+# reference. This must follow the options to start_div if they are present.
+# The content can be a scalar, which is output as-is, a scalar reference, which
+# is output after html escaping, an IO handle passed either as *handle or
+# *handle{IO}, or a function reference. In the latter case all following
+# parameters will be taken as argument to the content function call.
+sub git_print_section {
+ my ($div_args, $header_args, $content);
+ my $arg = shift;
+ if (ref($arg) eq 'HASH') {
+ $div_args = $arg;
+ $arg = shift;
+ }
+ if (ref($arg) eq 'ARRAY') {
+ $header_args = $arg;
+ $arg = shift;
+ }
+ $content = $arg;
+
+ print $cgi->start_div($div_args);
+ git_print_header_div(@$header_args);
+
+ if (ref($content) eq 'CODE') {
+ $content->(@_);
+ } elsif (ref($content) eq 'SCALAR') {
+ print esc_html($$content);
+ } elsif (ref($content) eq 'GLOB' or ref($content) eq 'IO::Handle') {
+ print <$content>;
+ } elsif (!ref($content) && defined($content)) {
+ print $content;
+ }
+
+ print $cgi->end_div;
+}
+
sub print_local_time {
print format_local_time(@_);
}
}
if ($diff->{'from_mode'} ne ('0' x 6)) {
$from_mode_oct = oct $diff->{'from_mode'};
- if (S_ISREG($to_mode_oct)) { # only for regular file
+ if (S_ISREG($from_mode_oct)) { # only for regular file
$from_mode_str = sprintf("%04o", $from_mode_oct & 0777); # permission bits
}
$from_file_type = file_type($diff->{'from_mode'});
"<td class=\"link\">" .
$cgi->a({-href => href(action=>"shortlog", hash=>$ref{'fullname'})}, "shortlog") . " | " .
$cgi->a({-href => href(action=>"log", hash=>$ref{'fullname'})}, "log") . " | " .
- $cgi->a({-href => href(action=>"tree", hash=>$ref{'fullname'}, hash_base=>$ref{'name'})}, "tree") .
+ $cgi->a({-href => href(action=>"tree", hash=>$ref{'fullname'}, hash_base=>$ref{'fullname'})}, "tree") .
"</td>\n" .
"</tr>";
}
print "</table>\n";
}
+# Display a single remote block
+sub git_remote_block {
+ my ($remote, $rdata, $limit, $head) = @_;
+
+ my $heads = $rdata->{'heads'};
+ my $fetch = $rdata->{'fetch'};
+ my $push = $rdata->{'push'};
+
+ my $urls_table = "<table class=\"projects_list\">\n" ;
+
+ if (defined $fetch) {
+ if ($fetch eq $push) {
+ $urls_table .= format_repo_url("URL", $fetch);
+ } else {
+ $urls_table .= format_repo_url("Fetch URL", $fetch);
+ $urls_table .= format_repo_url("Push URL", $push) if defined $push;
+ }
+ } elsif (defined $push) {
+ $urls_table .= format_repo_url("Push URL", $push);
+ } else {
+ $urls_table .= format_repo_url("", "No remote URL");
+ }
+
+ $urls_table .= "</table>\n";
+
+ my $dots;
+ if (defined $limit && $limit < @$heads) {
+ $dots = $cgi->a({-href => href(action=>"remotes", hash=>$remote)}, "...");
+ }
+
+ print $urls_table;
+ git_heads_body($heads, $head, 0, $limit, $dots);
+}
+
+# Display a list of remote names with the respective fetch and push URLs
+sub git_remotes_list {
+ my ($remotedata, $limit) = @_;
+ print "<table class=\"heads\">\n";
+ my $alternate = 1;
+ my @remotes = sort keys %$remotedata;
+
+ my $limited = $limit && $limit < @remotes;
+
+ $#remotes = $limit - 1 if $limited;
+
+ while (my $remote = shift @remotes) {
+ my $rdata = $remotedata->{$remote};
+ my $fetch = $rdata->{'fetch'};
+ my $push = $rdata->{'push'};
+ if ($alternate) {
+ print "<tr class=\"dark\">\n";
+ } else {
+ print "<tr class=\"light\">\n";
+ }
+ $alternate ^= 1;
+ print "<td>" .
+ $cgi->a({-href=> href(action=>'remotes', hash=>$remote),
+ -class=> "list name"},esc_html($remote)) .
+ "</td>";
+ print "<td class=\"link\">" .
+ (defined $fetch ? $cgi->a({-href=> $fetch}, "fetch") : "fetch") .
+ " | " .
+ (defined $push ? $cgi->a({-href=> $push}, "push") : "push") .
+ "</td>";
+
+ print "</tr>\n";
+ }
+
+ if ($limited) {
+ print "<tr>\n" .
+ "<td colspan=\"3\">" .
+ $cgi->a({-href => href(action=>"remotes")}, "...") .
+ "</td>\n" . "</tr>\n";
+ }
+
+ print "</table>";
+}
+
+# Display remote heads grouped by remote, unless there are too many
+# remotes, in which case we only display the remote names
+sub git_remotes_body {
+ my ($remotedata, $limit, $head) = @_;
+ if ($limit and $limit < keys %$remotedata) {
+ git_remotes_list($remotedata, $limit);
+ } else {
+ fill_remote_heads($remotedata);
+ while (my ($remote, $rdata) = each %$remotedata) {
+ git_print_section({-class=>"remote", -id=>$remote},
+ ["remotes", $remote, $remote], sub {
+ git_remote_block($remote, $rdata, $limit, $head);
+ });
+ }
+ }
+}
+
sub git_search_grep_body {
my ($commitlist, $from, $to, $extra) = @_;
$from = 0 unless defined $from;
my %co = parse_commit("HEAD");
my %cd = %co ? parse_date($co{'committer_epoch'}, $co{'committer_tz'}) : ();
my $head = $co{'id'};
+ my $remote_heads = gitweb_check_feature('remote_heads');
my $owner = git_get_project_owner($project);
# there are more ...
my @taglist = git_get_tags_list(16);
my @headlist = git_get_heads_list(16);
+ my %remotedata = $remote_heads ? git_get_remotes_list() : ();
my @forklist;
my $check_forks = gitweb_check_feature('forks');
@url_list = map { "$_/$project" } @git_base_url_list unless @url_list;
foreach my $git_url (@url_list) {
next unless $git_url;
- print "<tr class=\"metadata_url\"><td>$url_tag</td><td>$git_url</td></tr>\n";
+ print format_repo_url($url_tag, $git_url);
$url_tag = "";
}
$cgi->a({-href => href(action=>"heads")}, "..."));
}
+ if (%remotedata) {
+ git_print_header_div('remotes');
+ git_remotes_body(\%remotedata, 15, $head);
+ }
+
if (@forklist) {
git_print_header_div('forks');
git_project_list_body(\@forklist, 'age', 0, 15,
print 'END';
if (defined $t0 && gitweb_check_feature('timed')) {
print ' '.
- Time::HiRes::tv_interval($t0, [Time::HiRes::gettimeofday()]).
+ tv_interval($t0, [ gettimeofday() ]).
' '.$number_of_git_cmds;
}
print "\n";
sub git_tags {
my $head = git_get_head_hash($project);
git_header_html();
- git_print_page_nav('','', $head,undef,$head);
+ git_print_page_nav('','', $head,undef,$head,format_ref_views('tags'));
git_print_header_div('summary', $project);
my @tagslist = git_get_tags_list();
sub git_heads {
my $head = git_get_head_hash($project);
git_header_html();
- git_print_page_nav('','', $head,undef,$head);
+ git_print_page_nav('','', $head,undef,$head,format_ref_views('heads'));
git_print_header_div('summary', $project);
my @headslist = git_get_heads_list();
git_footer_html();
}
+# used both for single remote view and for list of all the remotes
+sub git_remotes {
+ gitweb_check_feature('remote_heads')
+ or die_error(403, "Remote heads view is disabled");
+
+ my $head = git_get_head_hash($project);
+ my $remote = $input_params{'hash'};
+
+ my $remotedata = git_get_remotes_list($remote);
+ die_error(500, "Unable to get remote information") unless defined $remotedata;
+
+ unless (%$remotedata) {
+ die_error(404, defined $remote ?
+ "Remote $remote not found" :
+ "No remotes found");
+ }
+
+ git_header_html(undef, undef, -action_extra => $remote);
+ git_print_page_nav('', '', $head, undef, $head,
+ format_ref_views($remote ? '' : 'remotes'));
+
+ fill_remote_heads($remotedata);
+ if (defined $remote) {
+ git_print_header_div('remotes', "$remote remote for $project");
+ git_remote_block($remote, $remotedata->{$remote}, undef, $head);
+ } else {
+ git_print_header_div('summary', "$project remotes");
+ git_remotes_body($remotedata, undef, $head);
+ }
+
+ git_footer_html();
+}
+
sub git_blob_plain {
my $type = shift;
my $expires;
index 4132aabcdb4ef402ceb1a890b61778b3254fdf18..79d7eebba797f3bd80a639f6ca0835e15016762d 100644 (file)
--- a/gitweb/static/gitweb.css
+++ b/gitweb/static/gitweb.css
font-style: italic;
}
+div.remote {
+ margin: .5em;
+ border: 1px solid #d9d8d1;
+ display: inline-block;
+}
+
/* Style definition generated by highlight 2.4.5, http://www.andre-simon.de/ */
/* Highlighting theme definition: */
index 82fb349be6f6ef8211d4828101cd8e88bcbb304f..63c4280cac9a87f867451c0d9787d1fe21ea9c2d 100644 (file)
--- a/grep.c
+++ b/grep.c
return compile_pattern_or(list);
}
-void compile_grep_patterns(struct grep_opt *opt)
+static struct grep_expr *grep_true_expr(void)
+{
+ struct grep_expr *z = xcalloc(1, sizeof(*z));
+ z->node = GREP_NODE_TRUE;
+ return z;
+}
+
+static struct grep_expr *grep_or_expr(struct grep_expr *left, struct grep_expr *right)
+{
+ struct grep_expr *z = xcalloc(1, sizeof(*z));
+ z->node = GREP_NODE_OR;
+ z->u.binary.left = left;
+ z->u.binary.right = right;
+ return z;
+}
+
+static struct grep_expr *prep_header_patterns(struct grep_opt *opt)
{
struct grep_pat *p;
- struct grep_expr *header_expr = NULL;
-
- if (opt->header_list) {
- p = opt->header_list;
- header_expr = compile_pattern_expr(&p);
- if (p)
- die("incomplete pattern expression: %s", p->pattern);
- for (p = opt->header_list; p; p = p->next) {
- switch (p->token) {
- case GREP_PATTERN: /* atom */
- case GREP_PATTERN_HEAD:
- case GREP_PATTERN_BODY:
- compile_regexp(p, opt);
- break;
- default:
- opt->extended = 1;
- break;
- }
+ struct grep_expr *header_expr;
+ struct grep_expr *(header_group[GREP_HEADER_FIELD_MAX]);
+ enum grep_header_field fld;
+
+ if (!opt->header_list)
+ return NULL;
+ p = opt->header_list;
+ for (p = opt->header_list; p; p = p->next) {
+ if (p->token != GREP_PATTERN_HEAD)
+ die("bug: a non-header pattern in grep header list.");
+ if (p->field < 0 || GREP_HEADER_FIELD_MAX <= p->field)
+ die("bug: unknown header field %d", p->field);
+ compile_regexp(p, opt);
+ }
+
+ for (fld = 0; fld < GREP_HEADER_FIELD_MAX; fld++)
+ header_group[fld] = NULL;
+
+ for (p = opt->header_list; p; p = p->next) {
+ struct grep_expr *h;
+ struct grep_pat *pp = p;
+
+ h = compile_pattern_atom(&pp);
+ if (!h || pp != p->next)
+ die("bug: malformed header expr");
+ if (!header_group[p->field]) {
+ header_group[p->field] = h;
+ continue;
}
+ header_group[p->field] = grep_or_expr(h, header_group[p->field]);
}
+ header_expr = NULL;
+
+ for (fld = 0; fld < GREP_HEADER_FIELD_MAX; fld++) {
+ if (!header_group[fld])
+ continue;
+ if (!header_expr)
+ header_expr = grep_true_expr();
+ header_expr = grep_or_expr(header_group[fld], header_expr);
+ }
+ return header_expr;
+}
+
+void compile_grep_patterns(struct grep_opt *opt)
+{
+ struct grep_pat *p;
+ struct grep_expr *header_expr = prep_header_patterns(opt);
+
for (p = opt->pattern_list; p; p = p->next) {
switch (p->token) {
case GREP_PATTERN: /* atom */
else if (!opt->extended)
return;
- /* Then bundle them up in an expression.
- * A classic recursive descent parser would do.
- */
p = opt->pattern_list;
if (p)
opt->pattern_expression = compile_pattern_expr(&p);
if (!header_expr)
return;
- if (opt->pattern_expression) {
- struct grep_expr *z;
- z = xcalloc(1, sizeof(*z));
- z->node = GREP_NODE_OR;
- z->u.binary.left = opt->pattern_expression;
- z->u.binary.right = header_expr;
- opt->pattern_expression = z;
- } else {
+ if (!opt->pattern_expression)
opt->pattern_expression = header_expr;
- }
+ else
+ opt->pattern_expression = grep_or_expr(opt->pattern_expression,
+ header_expr);
opt->all_match = 1;
}
static void free_pattern_expr(struct grep_expr *x)
{
switch (x->node) {
+ case GREP_NODE_TRUE:
case GREP_NODE_ATOM:
break;
case GREP_NODE_NOT:
if (!x)
die("Not a valid grep expression");
switch (x->node) {
+ case GREP_NODE_TRUE:
+ h = 1;
+ break;
case GREP_NODE_ATOM:
h = match_one_pattern(x->u.atom, bol, eol, ctx, &match, 0);
break;
index efa8cff980af2b2c06dad080876637d16b5c4985..06621fe663545af52fbc42827a8374ab5bd42f38 100644 (file)
--- a/grep.h
+++ b/grep.h
GREP_HEADER_AUTHOR = 0,
GREP_HEADER_COMMITTER
};
+#define GREP_HEADER_FIELD_MAX (GREP_HEADER_COMMITTER + 1)
struct grep_pat {
struct grep_pat *next;
GREP_NODE_ATOM,
GREP_NODE_NOT,
GREP_NODE_AND,
+ GREP_NODE_TRUE,
GREP_NODE_OR
};
index 1cd4c9d5c0945994b84bb25edd6e4685cf76b5c5..749ecfe4841a6a2af6ba4fdc6c540fd3016178f1 100644 (file)
--- a/hash.c
+++ b/hash.c
return insert_hash_entry(hash, ptr, table);
}
-int for_each_hash(const struct hash_table *table, int (*fn)(void *))
+int for_each_hash(const struct hash_table *table, int (*fn)(void *, void *), void *data)
{
int sum = 0;
unsigned int i;
void *ptr = array->ptr;
array++;
if (ptr) {
- int val = fn(ptr);
+ int val = fn(ptr, data);
if (val < 0)
return val;
sum += val;
index 69e33a47b9861df9ac12c354eae180b4f8fea857..b875ce67c49eb39a8ca8ad6a688a334985db0534 100644 (file)
--- a/hash.h
+++ b/hash.h
extern void *lookup_hash(unsigned int hash, const struct hash_table *table);
extern void **insert_hash(unsigned int hash, void *ptr, struct hash_table *table);
-extern int for_each_hash(const struct hash_table *table, int (*fn)(void *));
+extern int for_each_hash(const struct hash_table *table, int (*fn)(void *, void *), void *data);
extern void free_hash(struct hash_table *table);
static inline void init_hash(struct hash_table *table)
index 7f4928e45954465d0401964289952aec4bd59e2a..7654f1bfd608d151a213937614449cc497bb638b 100644 (file)
--- a/help.c
+++ b/help.c
#include "exec_cmd.h"
#include "levenshtein.h"
#include "help.h"
+#include "common-cmds.h"
/* most GUI terminals set COLUMNS (although some don't export it) */
static int term_columns(void)
}
/* An empirically derived magic number */
-#define SIMILAR_ENOUGH(x) ((x) < 6)
+#define SIMILARITY_FLOOR 7
+#define SIMILAR_ENOUGH(x) ((x) < SIMILARITY_FLOOR)
const char *help_unknown_cmd(const char *cmd)
{
sizeof(main_cmds.names), cmdname_compare);
uniq(&main_cmds);
- /* This reuses cmdname->len for similarity index */
- for (i = 0; i < main_cmds.cnt; ++i)
+ /* This abuses cmdname->len for levenshtein distance */
+ for (i = 0, n = 0; i < main_cmds.cnt; i++) {
+ int cmp = 0; /* avoid compiler stupidity */
+ const char *candidate = main_cmds.names[i]->name;
+
+ /* Does the candidate appear in common_cmds list? */
+ while (n < ARRAY_SIZE(common_cmds) &&
+ (cmp = strcmp(common_cmds[n].name, candidate)) < 0)
+ n++;
+ if ((n < ARRAY_SIZE(common_cmds)) && !cmp) {
+ /* Yes, this is one of the common commands */
+ n++; /* use the entry from common_cmds[] */
+ if (!prefixcmp(candidate, cmd)) {
+ /* Give prefix match a very good score */
+ main_cmds.names[i]->len = 0;
+ continue;
+ }
+ }
+
main_cmds.names[i]->len =
- levenshtein(cmd, main_cmds.names[i]->name, 0, 2, 1, 4);
+ levenshtein(cmd, candidate, 0, 2, 1, 4) + 1;
+ }
qsort(main_cmds.names, main_cmds.cnt,
sizeof(*main_cmds.names), levenshtein_compare);
if (!main_cmds.cnt)
die ("Uh oh. Your system reports no Git commands at all.");
- best_similarity = main_cmds.names[0]->len;
- n = 1;
- while (n < main_cmds.cnt && best_similarity == main_cmds.names[n]->len)
- ++n;
+ /* skip and count prefix matches */
+ for (n = 0; n < main_cmds.cnt && !main_cmds.names[n]->len; n++)
+ ; /* still counting */
+
+ if (main_cmds.cnt <= n) {
+ /* prefix matches with everything? that is too ambiguous */
+ best_similarity = SIMILARITY_FLOOR + 1;
+ } else {
+ /* count all the most similar ones */
+ for (best_similarity = main_cmds.names[n++]->len;
+ (n < main_cmds.cnt &&
+ best_similarity == main_cmds.names[n]->len);
+ n++)
+ ; /* still counting */
+ }
if (autocorrect && n == 1 && SIMILAR_ENOUGH(best_similarity)) {
const char *assumed = main_cmds.names[0]->name;
main_cmds.names[0] = NULL;
diff --git a/list-objects.c b/list-objects.c
index 8953548c07bb36f20798c7ca344d07960c22618c..61f6cc98d917c3e4395ec397ac74df6a40eeb07f 100644 (file)
--- a/list-objects.c
+++ b/list-objects.c
struct tree *tree,
show_object_fn show,
struct name_path *path,
+ struct strbuf *base,
const char *name)
{
struct object *obj = &tree->object;
struct tree_desc desc;
struct name_entry entry;
struct name_path me;
+ int all_interesting = (revs->diffopt.pathspec.nr == 0);
+ int baselen = base->len;
if (!revs->tree_objects)
return;
me.elem = name;
me.elem_len = strlen(name);
+ if (!all_interesting) {
+ strbuf_addstr(base, name);
+ if (base->len)
+ strbuf_addch(base, '/');
+ }
+
init_tree_desc(&desc, tree->buffer, tree->size);
while (tree_entry(&desc, &entry)) {
+ if (!all_interesting) {
+ int showit = tree_entry_interesting(&entry,
+ base, 0,
+ &revs->diffopt.pathspec);
+
+ if (showit < 0)
+ break;
+ else if (!showit)
+ continue;
+ else if (showit == 2)
+ all_interesting = 1;
+ }
+
if (S_ISDIR(entry.mode))
process_tree(revs,
lookup_tree(entry.sha1),
- show, &me, entry.path);
+ show, &me, base, entry.path);
else if (S_ISGITLINK(entry.mode))
process_gitlink(revs, entry.sha1,
show, &me, entry.path);
lookup_blob(entry.sha1),
show, &me, entry.path);
}
+ strbuf_setlen(base, baselen);
free(tree->buffer);
tree->buffer = NULL;
}
{
int i;
struct commit *commit;
+ struct strbuf base;
+ strbuf_init(&base, PATH_MAX);
while ((commit = get_revision(revs)) != NULL) {
add_pending_tree(revs, commit->tree);
show_commit(commit, data);
}
if (obj->type == OBJ_TREE) {
process_tree(revs, (struct tree *)obj, show_object,
- NULL, name);
+ NULL, &base, name);
continue;
}
if (obj->type == OBJ_BLOB) {
revs->pending.alloc = 0;
revs->pending.objects = NULL;
}
+ strbuf_release(&base);
}
diff --git a/ll-merge.c b/ll-merge.c
index 6bb3095c3a85d771bb60e8f2f025287d16e792df..6ce512efc4cce8042481e8a6947d033c272e78e6 100644 (file)
--- a/ll-merge.c
+++ b/ll-merge.c
mmfile_t *orig, const char *orig_name,
mmfile_t *src1, const char *name1,
mmfile_t *src2, const char *name2,
- int flag,
+ const struct ll_merge_options *opts,
int marker_size);
struct ll_merge_driver {
mmfile_t *orig, const char *orig_name,
mmfile_t *src1, const char *name1,
mmfile_t *src2, const char *name2,
- int flag, int marker_size)
+ const struct ll_merge_options *opts,
+ int marker_size)
{
+ mmfile_t *stolen;
+ assert(opts);
+
/*
* The tentative merge result is "ours" for the final round,
* or common ancestor for an internal merge. Still return
* "conflicted merge" status.
*/
- mmfile_t *stolen = (flag & LL_OPT_VIRTUAL_ANCESTOR) ? orig : src1;
+ stolen = opts->virtual_ancestor ? orig : src1;
result->ptr = stolen->ptr;
result->size = stolen->size;
mmfile_t *orig, const char *orig_name,
mmfile_t *src1, const char *name1,
mmfile_t *src2, const char *name2,
- int flag, int marker_size)
+ const struct ll_merge_options *opts,
+ int marker_size)
{
xmparam_t xmp;
+ assert(opts);
if (buffer_is_binary(orig->ptr, orig->size) ||
buffer_is_binary(src1->ptr, src1->size) ||
orig, orig_name,
src1, name1,
src2, name2,
- flag, marker_size);
+ opts, marker_size);
}
memset(&xmp, 0, sizeof(xmp));
xmp.level = XDL_MERGE_ZEALOUS;
- xmp.favor = ll_opt_favor(flag);
+ xmp.favor = opts->variant;
+ xmp.xpp.flags = opts->xdl_opts;
if (git_xmerge_style >= 0)
xmp.style = git_xmerge_style;
if (marker_size > 0)
mmfile_t *orig, const char *orig_name,
mmfile_t *src1, const char *name1,
mmfile_t *src2, const char *name2,
- int flag, int marker_size)
+ const struct ll_merge_options *opts,
+ int marker_size)
{
/* Use union favor */
- flag &= ~LL_OPT_FAVOR_MASK;
- flag |= create_ll_flag(XDL_MERGE_FAVOR_UNION);
+ struct ll_merge_options o;
+ assert(opts);
+ o = *opts;
+ o.variant = XDL_MERGE_FAVOR_UNION;
return ll_xdl_merge(drv_unused, result, path_unused,
orig, NULL, src1, NULL, src2, NULL,
- flag, marker_size);
- return 0;
+ &o, marker_size);
}
#define LL_BINARY_MERGE 0
mmfile_t *orig, const char *orig_name,
mmfile_t *src1, const char *name1,
mmfile_t *src2, const char *name2,
- int flag, int marker_size)
+ const struct ll_merge_options *opts,
+ int marker_size)
{
char temp[4][50];
struct strbuf cmd = STRBUF_INIT;
const char *args[] = { NULL, NULL };
int status, fd, i;
struct stat st;
+ assert(opts);
dict[0].placeholder = "O"; dict[0].value = temp[0];
dict[1].placeholder = "A"; dict[1].value = temp[1];
mmfile_t *ancestor, const char *ancestor_label,
mmfile_t *ours, const char *our_label,
mmfile_t *theirs, const char *their_label,
- int flag)
+ const struct ll_merge_options *opts)
{
static struct git_attr_check check[2];
+ static const struct ll_merge_options default_opts;
const char *ll_driver_name = NULL;
int marker_size = DEFAULT_CONFLICT_MARKER_SIZE;
const struct ll_merge_driver *driver;
- int virtual_ancestor = flag & LL_OPT_VIRTUAL_ANCESTOR;
- if (flag & LL_OPT_RENORMALIZE) {
+ if (!opts)
+ opts = &default_opts;
+
+ if (opts->renormalize) {
normalize_file(ancestor, path);
normalize_file(ours, path);
normalize_file(theirs, path);
}
}
driver = find_ll_merge_driver(ll_driver_name);
- if (virtual_ancestor && driver->recursive)
+ if (opts->virtual_ancestor && driver->recursive)
driver = find_ll_merge_driver(driver->recursive);
return driver->fn(driver, result_buf, path, ancestor, ancestor_label,
ours, our_label, theirs, their_label,
- flag, marker_size);
+ opts, marker_size);
}
int ll_merge_marker_size(const char *path)
diff --git a/ll-merge.h b/ll-merge.h
index ff7ca87bfa01d0e9705f4bfd83b9e4d2c155d588..244a31f55ac7375b59d7c257bfc2391957fdca94 100644 (file)
--- a/ll-merge.h
+++ b/ll-merge.h
#ifndef LL_MERGE_H
#define LL_MERGE_H
-#define LL_OPT_VIRTUAL_ANCESTOR (1 << 0)
-#define LL_OPT_FAVOR_MASK ((1 << 1) | (1 << 2))
-#define LL_OPT_FAVOR_SHIFT 1
-#define LL_OPT_RENORMALIZE (1 << 3)
-
-static inline int ll_opt_favor(int flag)
-{
- return (flag & LL_OPT_FAVOR_MASK) >> LL_OPT_FAVOR_SHIFT;
-}
-
-static inline int create_ll_flag(int favor)
-{
- return ((favor << LL_OPT_FAVOR_SHIFT) & LL_OPT_FAVOR_MASK);
-}
+struct ll_merge_options {
+ unsigned virtual_ancestor : 1;
+ unsigned variant : 2; /* favor ours, favor theirs, or union merge */
+ unsigned renormalize : 1;
+ long xdl_opts;
+};
int ll_merge(mmbuffer_t *result_buf,
const char *path,
mmfile_t *ancestor, const char *ancestor_label,
mmfile_t *ours, const char *our_label,
mmfile_t *theirs, const char *their_label,
- int flag);
+ const struct ll_merge_options *opts);
int ll_merge_marker_size(const char *path);
diff --git a/merge-file.c b/merge-file.c
index db4d0d50d32d8852d1cb5173125e4173c3badb49..f7f4533926e35867725db00f0c89522c4b290898 100644 (file)
--- a/merge-file.c
+++ b/merge-file.c
* common ancestor.
*/
merge_status = ll_merge(&res, path, base, NULL,
- our, ".our", their, ".their", 0);
+ our, ".our", their, ".their", NULL);
if (merge_status < 0)
return NULL;
diff --git a/merge-recursive.c b/merge-recursive.c
index c5746988196b8139ca3234f8b30d03af56d33324..16c2dbeab95a0f4099e3de8badf688bb4dee8189 100644 (file)
--- a/merge-recursive.c
+++ b/merge-recursive.c
return a && b && hashcmp(a, b) == 0;
}
+enum rename_type {
+ RENAME_NORMAL = 0,
+ RENAME_DELETE,
+ RENAME_ONE_FILE_TO_TWO
+};
+
+struct rename_df_conflict_info {
+ enum rename_type rename_type;
+ struct diff_filepair *pair1;
+ struct diff_filepair *pair2;
+ const char *branch1;
+ const char *branch2;
+ struct stage_data *dst_entry1;
+ struct stage_data *dst_entry2;
+};
+
/*
* Since we want to write the index eventually, we cannot reuse the index
* for these (temporary) data.
unsigned mode;
unsigned char sha[20];
} stages[4];
+ struct rename_df_conflict_info *rename_df_conflict_info;
unsigned processed:1;
};
+static inline void setup_rename_df_conflict_info(enum rename_type rename_type,
+ struct diff_filepair *pair1,
+ struct diff_filepair *pair2,
+ const char *branch1,
+ const char *branch2,
+ struct stage_data *dst_entry1,
+ struct stage_data *dst_entry2)
+{
+ struct rename_df_conflict_info *ci = xcalloc(1, sizeof(struct rename_df_conflict_info));
+ ci->rename_type = rename_type;
+ ci->pair1 = pair1;
+ ci->branch1 = branch1;
+ ci->branch2 = branch2;
+
+ ci->dst_entry1 = dst_entry1;
+ dst_entry1->rename_df_conflict_info = ci;
+ dst_entry1->processed = 0;
+
+ assert(!pair2 == !dst_entry2);
+ if (dst_entry2) {
+ ci->dst_entry2 = dst_entry2;
+ ci->pair2 = pair2;
+ dst_entry2->rename_df_conflict_info = ci;
+ dst_entry2->processed = 0;
+ }
+}
+
static int show(struct merge_options *o, int v)
{
return (!o->call_depth && o->verbosity >= v) || o->verbosity >= 5;
return unmerged;
}
+static void make_room_for_directories_of_df_conflicts(struct merge_options *o,
+ struct string_list *entries)
+{
+ /* If there are D/F conflicts, and the paths currently exist
+ * in the working copy as a file, we want to remove them to
+ * make room for the corresponding directory. Such paths will
+ * later be processed in process_df_entry() at the end. If
+ * the corresponding directory ends up being removed by the
+ * merge, then the file will be reinstated at that time;
+ * otherwise, if the file is not supposed to be removed by the
+ * merge, the contents of the file will be placed in another
+ * unique filename.
+ *
+ * NOTE: This function relies on the fact that entries for a
+ * D/F conflict will appear adjacent in the index, with the
+ * entries for the file appearing before entries for paths
+ * below the corresponding directory.
+ */
+ const char *last_file = NULL;
+ int last_len = 0;
+ struct stage_data *last_e;
+ int i;
+
+ for (i = 0; i < entries->nr; i++) {
+ const char *path = entries->items[i].string;
+ int len = strlen(path);
+ struct stage_data *e = entries->items[i].util;
+
+ /*
+ * Check if last_file & path correspond to a D/F conflict;
+ * i.e. whether path is last_file+'/'+<something>.
+ * If so, remove last_file to make room for path and friends.
+ */
+ if (last_file &&
+ len > last_len &&
+ memcmp(path, last_file, last_len) == 0 &&
+ path[last_len] == '/') {
+ output(o, 3, "Removing %s to make room for subdirectory; may re-add later.", last_file);
+ unlink(last_file);
+ }
+
+ /*
+ * Determine whether path could exist as a file in the
+ * working directory as a possible D/F conflict. This
+ * will only occur when it exists in stage 2 as a
+ * file.
+ */
+ if (S_ISREG(e->stages[2].mode) || S_ISLNK(e->stages[2].mode)) {
+ last_file = path;
+ last_len = len;
+ last_e = e;
+ } else {
+ last_file = NULL;
+ }
+ }
+}
+
struct rename
{
struct diff_filepair *pair;
opts.rename_limit = o->merge_rename_limit >= 0 ? o->merge_rename_limit :
o->diff_rename_limit >= 0 ? o->diff_rename_limit :
500;
+ opts.rename_score = o->rename_score;
opts.warn_on_too_large_rename = 1;
opts.output_format = DIFF_FORMAT_NO_OUTPUT;
if (diff_setup_done(&opts) < 0)
return renames;
}
-static int update_stages(const char *path, struct diff_filespec *o,
+static int update_stages_options(const char *path, struct diff_filespec *o,
struct diff_filespec *a, struct diff_filespec *b,
- int clear)
+ int clear, int options)
{
- int options = ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE;
if (clear)
if (remove_file_from_cache(path))
return -1;
return 0;
}
+static int update_stages(const char *path, struct diff_filespec *o,
+ struct diff_filespec *a, struct diff_filespec *b,
+ int clear)
+{
+ int options = ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE;
+ return update_stages_options(path, o, a, b, clear, options);
+}
+
+static int update_stages_and_entry(const char *path,
+ struct stage_data *entry,
+ struct diff_filespec *o,
+ struct diff_filespec *a,
+ struct diff_filespec *b,
+ int clear)
+{
+ int options;
+
+ entry->processed = 0;
+ entry->stages[1].mode = o->mode;
+ entry->stages[2].mode = a->mode;
+ entry->stages[3].mode = b->mode;
+ hashcpy(entry->stages[1].sha, o->sha1);
+ hashcpy(entry->stages[2].sha, a->sha1);
+ hashcpy(entry->stages[3].sha, b->sha1);
+ options = ADD_CACHE_OK_TO_ADD | ADD_CACHE_SKIP_DFCHECK;
+ return update_stages_options(path, o, a, b, clear, options);
+}
+
static int remove_file(struct merge_options *o, int clean,
const char *path, int no_wd)
{
const char *branch2)
{
mmfile_t orig, src1, src2;
+ struct ll_merge_options ll_opts = {0};
char *base_name, *name1, *name2;
int merge_status;
- int favor;
- if (o->call_depth)
- favor = 0;
- else {
+ ll_opts.renormalize = o->renormalize;
+ ll_opts.xdl_opts = o->xdl_opts;
+
+ if (o->call_depth) {
+ ll_opts.virtual_ancestor = 1;
+ ll_opts.variant = 0;
+ } else {
switch (o->recursive_variant) {
case MERGE_RECURSIVE_OURS:
- favor = XDL_MERGE_FAVOR_OURS;
+ ll_opts.variant = XDL_MERGE_FAVOR_OURS;
break;
case MERGE_RECURSIVE_THEIRS:
- favor = XDL_MERGE_FAVOR_THEIRS;
+ ll_opts.variant = XDL_MERGE_FAVOR_THEIRS;
break;
default:
- favor = 0;
+ ll_opts.variant = 0;
break;
}
}
read_mmblob(&src2, b->sha1);
merge_status = ll_merge(result_buf, a->path, &orig, base_name,
- &src1, name1, &src2, name2,
- ((o->call_depth ? LL_OPT_VIRTUAL_ANCESTOR : 0) |
- (o->renormalize ? LL_OPT_RENORMALIZE : 0) |
- create_ll_flag(favor)));
+ &src1, name1, &src2, name2, &ll_opts);
free(name1);
free(name2);
return result;
}
-static void conflict_rename_rename(struct merge_options *o,
- struct rename *ren1,
- const char *branch1,
- struct rename *ren2,
- const char *branch2)
+static void conflict_rename_delete(struct merge_options *o,
+ struct diff_filepair *pair,
+ const char *rename_branch,
+ const char *other_branch)
{
+ char *dest_name = pair->two->path;
+ int df_conflict = 0;
+ struct stat st;
+
+ output(o, 1, "CONFLICT (rename/delete): Rename %s->%s in %s "
+ "and deleted in %s",
+ pair->one->path, pair->two->path, rename_branch,
+ other_branch);
+ if (!o->call_depth)
+ update_stages(dest_name, NULL,
+ rename_branch == o->branch1 ? pair->two : NULL,
+ rename_branch == o->branch1 ? NULL : pair->two,
+ 1);
+ if (lstat(dest_name, &st) == 0 && S_ISDIR(st.st_mode)) {
+ dest_name = unique_path(o, dest_name, rename_branch);
+ df_conflict = 1;
+ }
+ update_file(o, 0, pair->two->sha1, pair->two->mode, dest_name);
+ if (df_conflict)
+ free(dest_name);
+}
+
+static void conflict_rename_rename_1to2(struct merge_options *o,
+ struct diff_filepair *pair1,
+ const char *branch1,
+ struct diff_filepair *pair2,
+ const char *branch2)
+{
+ /* One file was renamed in both branches, but to different names. */
char *del[2];
int delp = 0;
- const char *ren1_dst = ren1->pair->two->path;
- const char *ren2_dst = ren2->pair->two->path;
+ const char *ren1_dst = pair1->two->path;
+ const char *ren2_dst = pair2->two->path;
const char *dst_name1 = ren1_dst;
const char *dst_name2 = ren2_dst;
- if (string_list_has_string(&o->current_directory_set, ren1_dst)) {
+ struct stat st;
+ if (lstat(ren1_dst, &st) == 0 && S_ISDIR(st.st_mode)) {
dst_name1 = del[delp++] = unique_path(o, ren1_dst, branch1);
output(o, 1, "%s is a directory in %s adding as %s instead",
ren1_dst, branch2, dst_name1);
- remove_file(o, 0, ren1_dst, 0);
}
- if (string_list_has_string(&o->current_directory_set, ren2_dst)) {
+ if (lstat(ren2_dst, &st) == 0 && S_ISDIR(st.st_mode)) {
dst_name2 = del[delp++] = unique_path(o, ren2_dst, branch2);
output(o, 1, "%s is a directory in %s adding as %s instead",
ren2_dst, branch1, dst_name2);
- remove_file(o, 0, ren2_dst, 0);
}
if (o->call_depth) {
remove_file_from_cache(dst_name1);
/*
* Uncomment to leave the conflicting names in the resulting tree
*
- * update_file(o, 0, ren1->pair->two->sha1, ren1->pair->two->mode, dst_name1);
- * update_file(o, 0, ren2->pair->two->sha1, ren2->pair->two->mode, dst_name2);
+ * update_file(o, 0, pair1->two->sha1, pair1->two->mode, dst_name1);
+ * update_file(o, 0, pair2->two->sha1, pair2->two->mode, dst_name2);
*/
} else {
- update_stages(dst_name1, NULL, ren1->pair->two, NULL, 1);
- update_stages(dst_name2, NULL, NULL, ren2->pair->two, 1);
+ update_stages(ren1_dst, NULL, pair1->two, NULL, 1);
+ update_stages(ren2_dst, NULL, NULL, pair2->two, 1);
+
+ update_file(o, 0, pair1->two->sha1, pair1->two->mode, dst_name1);
+ update_file(o, 0, pair2->two->sha1, pair2->two->mode, dst_name2);
}
while (delp--)
free(del[delp]);
}
-static void conflict_rename_dir(struct merge_options *o,
- struct rename *ren1,
- const char *branch1)
-{
- char *new_path = unique_path(o, ren1->pair->two->path, branch1);
- output(o, 1, "Renaming %s to %s instead", ren1->pair->one->path, new_path);
- remove_file(o, 0, ren1->pair->two->path, 0);
- update_file(o, 0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path);
- free(new_path);
-}
-
-static void conflict_rename_rename_2(struct merge_options *o,
- struct rename *ren1,
- const char *branch1,
- struct rename *ren2,
- const char *branch2)
+static void conflict_rename_rename_2to1(struct merge_options *o,
+ struct rename *ren1,
+ const char *branch1,
+ struct rename *ren2,
+ const char *branch2)
{
+ /* Two files were renamed to the same thing. */
char *new_path1 = unique_path(o, ren1->pair->two->path, branch1);
char *new_path2 = unique_path(o, ren2->pair->two->path, branch2);
output(o, 1, "Renaming %s to %s and %s to %s instead",
ren2->dst_entry->processed = 1;
ren2->processed = 1;
if (strcmp(ren1_dst, ren2_dst) != 0) {
- clean_merge = 0;
- output(o, 1, "CONFLICT (rename/rename): "
- "Rename \"%s\"->\"%s\" in branch \"%s\" "
- "rename \"%s\"->\"%s\" in \"%s\"%s",
- src, ren1_dst, branch1,
- src, ren2_dst, branch2,
- o->call_depth ? " (left unresolved)": "");
- if (o->call_depth) {
- remove_file_from_cache(src);
- update_file(o, 0, ren1->pair->one->sha1,
- ren1->pair->one->mode, src);
- }
- conflict_rename_rename(o, ren1, branch1, ren2, branch2);
+ setup_rename_df_conflict_info(RENAME_ONE_FILE_TO_TWO,
+ ren1->pair,
+ ren2->pair,
+ branch1,
+ branch2,
+ ren1->dst_entry,
+ ren2->dst_entry);
} else {
- struct merge_file_info mfi;
remove_file(o, 1, ren1_src, 1);
- mfi = merge_file(o,
- ren1->pair->one,
- ren1->pair->two,
- ren2->pair->two,
- branch1,
- branch2);
- if (mfi.merge || !mfi.clean)
- output(o, 1, "Renaming %s->%s", src, ren1_dst);
-
- if (mfi.merge)
- output(o, 2, "Auto-merging %s", ren1_dst);
-
- if (!mfi.clean) {
- output(o, 1, "CONFLICT (content): merge conflict in %s",
- ren1_dst);
- clean_merge = 0;
-
- if (!o->call_depth)
- update_stages(ren1_dst,
- ren1->pair->one,
- ren1->pair->two,
- ren2->pair->two,
- 1 /* clear */);
- }
- update_file(o, mfi.clean, mfi.sha, mfi.mode, ren1_dst);
+ update_stages_and_entry(ren1_dst,
+ ren1->dst_entry,
+ ren1->pair->one,
+ ren1->pair->two,
+ ren2->pair->two,
+ 1 /* clear */);
}
} else {
/* Renamed in 1, maybe changed in 2 */
struct string_list_item *item;
/* we only use sha1 and mode of these */
struct diff_filespec src_other, dst_other;
- int try_merge, stage = a_renames == renames1 ? 3: 2;
+ int try_merge;
- remove_file(o, 1, ren1_src, o->call_depth || stage == 3);
+ /*
+ * unpack_trees loads entries from common-commit
+ * into stage 1, from head-commit into stage 2, and
+ * from merge-commit into stage 3. We keep track
+ * of which side corresponds to the rename.
+ */
+ int renamed_stage = a_renames == renames1 ? 2 : 3;
+ int other_stage = a_renames == renames1 ? 3 : 2;
- hashcpy(src_other.sha1, ren1->src_entry->stages[stage].sha);
- src_other.mode = ren1->src_entry->stages[stage].mode;
- hashcpy(dst_other.sha1, ren1->dst_entry->stages[stage].sha);
- dst_other.mode = ren1->dst_entry->stages[stage].mode;
+ remove_file(o, 1, ren1_src, o->call_depth || renamed_stage == 2);
+ hashcpy(src_other.sha1, ren1->src_entry->stages[other_stage].sha);
+ src_other.mode = ren1->src_entry->stages[other_stage].mode;
+ hashcpy(dst_other.sha1, ren1->dst_entry->stages[other_stage].sha);
+ dst_other.mode = ren1->dst_entry->stages[other_stage].mode;
try_merge = 0;
- if (string_list_has_string(&o->current_directory_set, ren1_dst)) {
- clean_merge = 0;
- output(o, 1, "CONFLICT (rename/directory): Rename %s->%s in %s "
- " directory %s added in %s",
- ren1_src, ren1_dst, branch1,
- ren1_dst, branch2);
- conflict_rename_dir(o, ren1, branch1);
- } else if (sha_eq(src_other.sha1, null_sha1)) {
- clean_merge = 0;
- output(o, 1, "CONFLICT (rename/delete): Rename %s->%s in %s "
- "and deleted in %s",
- ren1_src, ren1_dst, branch1,
- branch2);
- update_file(o, 0, ren1->pair->two->sha1, ren1->pair->two->mode, ren1_dst);
- if (!o->call_depth)
- update_stages(ren1_dst, NULL,
- branch1 == o->branch1 ?
- ren1->pair->two : NULL,
- branch1 == o->branch1 ?
- NULL : ren1->pair->two, 1);
+ if (sha_eq(src_other.sha1, null_sha1)) {
+ if (string_list_has_string(&o->current_directory_set, ren1_dst)) {
+ ren1->dst_entry->processed = 0;
+ setup_rename_df_conflict_info(RENAME_DELETE,
+ ren1->pair,
+ NULL,
+ branch1,
+ branch2,
+ ren1->dst_entry,
+ NULL);
+ } else {
+ clean_merge = 0;
+ conflict_rename_delete(o, ren1->pair, branch1, branch2);
+ }
} else if ((dst_other.mode == ren1->pair->two->mode) &&
sha_eq(dst_other.sha1, ren1->pair->two->sha1)) {
/* Added file on the other side
mfi.sha,
mfi.mode,
ren1_dst);
+ try_merge = 0;
} else {
new_path = unique_path(o, ren1_dst, branch2);
output(o, 1, "Adding as %s instead", new_path);
"Rename %s->%s in %s",
ren1_src, ren1_dst, branch1,
ren2->pair->one->path, ren2->pair->two->path, branch2);
- conflict_rename_rename_2(o, ren1, branch1, ren2, branch2);
+ conflict_rename_rename_2to1(o, ren1, branch1, ren2, branch2);
} else
try_merge = 1;
if (try_merge) {
struct diff_filespec *one, *a, *b;
- struct merge_file_info mfi;
src_other.path = (char *)ren1_src;
one = ren1->pair->one;
b = ren1->pair->two;
a = &src_other;
}
- mfi = merge_file(o, one, a, b,
- o->branch1, o->branch2);
-
- if (mfi.clean &&
- sha_eq(mfi.sha, ren1->pair->two->sha1) &&
- mfi.mode == ren1->pair->two->mode) {
- /*
- * This message is part of
- * t6022 test. If you change
- * it update the test too.
- */
- output(o, 3, "Skipped %s (merged same as existing)", ren1_dst);
-
- /* There may be higher stage entries left
- * in the index (e.g. due to a D/F
- * conflict) that need to be resolved.
- */
- if (!ren1->dst_entry->stages[2].mode !=
- !ren1->dst_entry->stages[3].mode)
- ren1->dst_entry->processed = 0;
- } else {
- if (mfi.merge || !mfi.clean)
- output(o, 1, "Renaming %s => %s", ren1_src, ren1_dst);
- if (mfi.merge)
- output(o, 2, "Auto-merging %s", ren1_dst);
- if (!mfi.clean) {
- output(o, 1, "CONFLICT (rename/modify): Merge conflict in %s",
- ren1_dst);
- clean_merge = 0;
-
- if (!o->call_depth)
- update_stages(ren1_dst,
- one, a, b, 1);
- }
- update_file(o, mfi.clean, mfi.sha, mfi.mode, ren1_dst);
+ update_stages_and_entry(ren1_dst, ren1->dst_entry, one, a, b, 1);
+ if (string_list_has_string(&o->current_directory_set, ren1_dst)) {
+ setup_rename_df_conflict_info(RENAME_NORMAL,
+ ren1->pair,
+ NULL,
+ branch1,
+ NULL,
+ ren1->dst_entry,
+ NULL);
}
}
}
return ret;
}
+static void handle_delete_modify(struct merge_options *o,
+ const char *path,
+ const char *new_path,
+ unsigned char *a_sha, int a_mode,
+ unsigned char *b_sha, int b_mode)
+{
+ if (!a_sha) {
+ output(o, 1, "CONFLICT (delete/modify): %s deleted in %s "
+ "and modified in %s. Version %s of %s left in tree%s%s.",
+ path, o->branch1,
+ o->branch2, o->branch2, path,
+ path == new_path ? "" : " at ",
+ path == new_path ? "" : new_path);
+ update_file(o, 0, b_sha, b_mode, new_path);
+ } else {
+ output(o, 1, "CONFLICT (delete/modify): %s deleted in %s "
+ "and modified in %s. Version %s of %s left in tree%s%s.",
+ path, o->branch2,
+ o->branch1, o->branch1, path,
+ path == new_path ? "" : " at ",
+ path == new_path ? "" : new_path);
+ update_file(o, 0, a_sha, a_mode, new_path);
+ }
+}
+
+static int merge_content(struct merge_options *o,
+ const char *path,
+ unsigned char *o_sha, int o_mode,
+ unsigned char *a_sha, int a_mode,
+ unsigned char *b_sha, int b_mode,
+ const char *df_rename_conflict_branch)
+{
+ const char *reason = "content";
+ struct merge_file_info mfi;
+ struct diff_filespec one, a, b;
+ struct stat st;
+ unsigned df_conflict_remains = 0;
+
+ if (!o_sha) {
+ reason = "add/add";
+ o_sha = (unsigned char *)null_sha1;
+ }
+ one.path = a.path = b.path = (char *)path;
+ hashcpy(one.sha1, o_sha);
+ one.mode = o_mode;
+ hashcpy(a.sha1, a_sha);
+ a.mode = a_mode;
+ hashcpy(b.sha1, b_sha);
+ b.mode = b_mode;
+
+ mfi = merge_file(o, &one, &a, &b, o->branch1, o->branch2);
+ if (df_rename_conflict_branch &&
+ lstat(path, &st) == 0 && S_ISDIR(st.st_mode)) {
+ df_conflict_remains = 1;
+ }
+
+ if (mfi.clean && !df_conflict_remains &&
+ sha_eq(mfi.sha, a_sha) && mfi.mode == a.mode)
+ output(o, 3, "Skipped %s (merged same as existing)", path);
+ else
+ output(o, 2, "Auto-merging %s", path);
+
+ if (!mfi.clean) {
+ if (S_ISGITLINK(mfi.mode))
+ reason = "submodule";
+ output(o, 1, "CONFLICT (%s): Merge conflict in %s",
+ reason, path);
+ }
+
+ if (df_conflict_remains) {
+ const char *new_path;
+ update_file_flags(o, mfi.sha, mfi.mode, path,
+ o->call_depth || mfi.clean, 0);
+ new_path = unique_path(o, path, df_rename_conflict_branch);
+ mfi.clean = 0;
+ output(o, 1, "Adding as %s instead", new_path);
+ update_file_flags(o, mfi.sha, mfi.mode, new_path, 0, 1);
+ } else {
+ update_file(o, mfi.clean, mfi.sha, mfi.mode, path);
+ }
+ return mfi.clean;
+
+}
+
/* Per entry merge function */
static int process_entry(struct merge_options *o,
const char *path, struct stage_data *entry)
unsigned char *a_sha = stage_sha(entry->stages[2].sha, a_mode);
unsigned char *b_sha = stage_sha(entry->stages[3].sha, b_mode);
+ if (entry->rename_df_conflict_info)
+ return 1; /* Such cases are handled elsewhere. */
+
entry->processed = 1;
if (o_sha && (!a_sha || !b_sha)) {
/* Case A: Deleted in one */
output(o, 2, "Removing %s", path);
/* do not touch working file if it did not exist */
remove_file(o, 1, path, !a_sha);
+ } else if (string_list_has_string(&o->current_directory_set,
+ path)) {
+ entry->processed = 0;
+ return 1; /* Assume clean until processed */
} else {
/* Deleted in one and changed in the other */
clean_merge = 0;
- if (!a_sha) {
- output(o, 1, "CONFLICT (delete/modify): %s deleted in %s "
- "and modified in %s. Version %s of %s left in tree.",
- path, o->branch1,
- o->branch2, o->branch2, path);
- update_file(o, 0, b_sha, b_mode, path);
- } else {
- output(o, 1, "CONFLICT (delete/modify): %s deleted in %s "
- "and modified in %s. Version %s of %s left in tree.",
- path, o->branch2,
- o->branch1, o->branch1, path);
- update_file(o, 0, a_sha, a_mode, path);
- }
+ handle_delete_modify(o, path, path,
+ a_sha, a_mode, b_sha, b_mode);
}
} else if ((!o_sha && a_sha && !b_sha) ||
if (string_list_has_string(&o->current_directory_set, path)) {
/* Handle D->F conflicts after all subfiles */
entry->processed = 0;
- /* But get any file out of the way now, so conflicted
- * entries below the directory of the same name can
- * be put in the working directory.
- */
- if (a_sha)
- output(o, 2, "Removing %s", path);
- /* do not touch working file if it did not exist */
- remove_file(o, 0, path, !a_sha);
- return 1; /* Assume clean till processed */
+ return 1; /* Assume clean until processed */
} else {
output(o, 2, "Adding %s", path);
update_file(o, 1, sha, mode, path);
} else if (a_sha && b_sha) {
/* Case C: Added in both (check for same permissions) and */
/* case D: Modified in both, but differently. */
- const char *reason = "content";
- struct merge_file_info mfi;
- struct diff_filespec one, a, b;
-
- if (!o_sha) {
- reason = "add/add";
- o_sha = (unsigned char *)null_sha1;
- }
- output(o, 2, "Auto-merging %s", path);
- one.path = a.path = b.path = (char *)path;
- hashcpy(one.sha1, o_sha);
- one.mode = o_mode;
- hashcpy(a.sha1, a_sha);
- a.mode = a_mode;
- hashcpy(b.sha1, b_sha);
- b.mode = b_mode;
-
- mfi = merge_file(o, &one, &a, &b,
- o->branch1, o->branch2);
-
- clean_merge = mfi.clean;
- if (!mfi.clean) {
- if (S_ISGITLINK(mfi.mode))
- reason = "submodule";
- output(o, 1, "CONFLICT (%s): Merge conflict in %s",
- reason, path);
- }
- update_file(o, mfi.clean, mfi.sha, mfi.mode, path);
+ clean_merge = merge_content(o, path,
+ o_sha, o_mode, a_sha, a_mode, b_sha, b_mode,
+ NULL);
} else if (!o_sha && !a_sha && !b_sha) {
/*
* this entry was deleted altogether. a_mode == 0 means
}
/*
- * Per entry merge function for D/F conflicts, to be called only after
- * all files below dir have been processed. We do this because in the
- * cases we can cleanly resolve D/F conflicts, process_entry() can clean
- * out all the files below the directory for us.
+ * Per entry merge function for D/F (and/or rename) conflicts. In the
+ * cases we can cleanly resolve D/F conflicts, process_entry() can
+ * clean out all the files below the directory for us. All D/F
+ * conflict cases must be handled here at the end to make sure any
+ * directories that can be cleaned out, are.
+ *
+ * Some rename conflicts may also be handled here that don't necessarily
+ * involve D/F conflicts, since the code to handle them is generic enough
+ * to handle those rename conflicts with or without D/F conflicts also
+ * being involved.
*/
static int process_df_entry(struct merge_options *o,
- const char *path, struct stage_data *entry)
+ const char *path, struct stage_data *entry)
{
int clean_merge = 1;
unsigned o_mode = entry->stages[1].mode;
unsigned char *o_sha = stage_sha(entry->stages[1].sha, o_mode);
unsigned char *a_sha = stage_sha(entry->stages[2].sha, a_mode);
unsigned char *b_sha = stage_sha(entry->stages[3].sha, b_mode);
- const char *add_branch;
- const char *other_branch;
- unsigned mode;
- const unsigned char *sha;
- const char *conf;
struct stat st;
- /* We currently only handle D->F cases */
- assert((!o_sha && a_sha && !b_sha) ||
- (!o_sha && !a_sha && b_sha));
-
entry->processed = 1;
-
- if (a_sha) {
- add_branch = o->branch1;
- other_branch = o->branch2;
- mode = a_mode;
- sha = a_sha;
- conf = "file/directory";
- } else {
- add_branch = o->branch2;
- other_branch = o->branch1;
- mode = b_mode;
- sha = b_sha;
- conf = "directory/file";
- }
- if (lstat(path, &st) == 0 && S_ISDIR(st.st_mode)) {
- const char *new_path = unique_path(o, path, add_branch);
+ if (entry->rename_df_conflict_info) {
+ struct rename_df_conflict_info *conflict_info = entry->rename_df_conflict_info;
+ char *src;
+ switch (conflict_info->rename_type) {
+ case RENAME_NORMAL:
+ clean_merge = merge_content(o, path,
+ o_sha, o_mode, a_sha, a_mode, b_sha, b_mode,
+ conflict_info->branch1);
+ break;
+ case RENAME_DELETE:
+ clean_merge = 0;
+ conflict_rename_delete(o, conflict_info->pair1,
+ conflict_info->branch1,
+ conflict_info->branch2);
+ break;
+ case RENAME_ONE_FILE_TO_TWO:
+ src = conflict_info->pair1->one->path;
+ clean_merge = 0;
+ output(o, 1, "CONFLICT (rename/rename): "
+ "Rename \"%s\"->\"%s\" in branch \"%s\" "
+ "rename \"%s\"->\"%s\" in \"%s\"%s",
+ src, conflict_info->pair1->two->path, conflict_info->branch1,
+ src, conflict_info->pair2->two->path, conflict_info->branch2,
+ o->call_depth ? " (left unresolved)" : "");
+ if (o->call_depth) {
+ remove_file_from_cache(src);
+ update_file(o, 0, conflict_info->pair1->one->sha1,
+ conflict_info->pair1->one->mode, src);
+ }
+ conflict_rename_rename_1to2(o, conflict_info->pair1,
+ conflict_info->branch1,
+ conflict_info->pair2,
+ conflict_info->branch2);
+ conflict_info->dst_entry2->processed = 1;
+ break;
+ default:
+ entry->processed = 0;
+ break;
+ }
+ } else if (o_sha && (!a_sha || !b_sha)) {
+ /* Modify/delete; deleted side may have put a directory in the way */
+ const char *new_path = path;
+ if (lstat(path, &st) == 0 && S_ISDIR(st.st_mode))
+ new_path = unique_path(o, path, a_sha ? o->branch1 : o->branch2);
clean_merge = 0;
- output(o, 1, "CONFLICT (%s): There is a directory with name %s in %s. "
- "Adding %s as %s",
- conf, path, other_branch, path, new_path);
- remove_file(o, 0, path, 0);
- update_file(o, 0, sha, mode, new_path);
+ handle_delete_modify(o, path, new_path,
+ a_sha, a_mode, b_sha, b_mode);
+ } else if (!o_sha && !!a_sha != !!b_sha) {
+ /* directory -> (directory, file) */
+ const char *add_branch;
+ const char *other_branch;
+ unsigned mode;
+ const unsigned char *sha;
+ const char *conf;
+
+ if (a_sha) {
+ add_branch = o->branch1;
+ other_branch = o->branch2;
+ mode = a_mode;
+ sha = a_sha;
+ conf = "file/directory";
+ } else {
+ add_branch = o->branch2;
+ other_branch = o->branch1;
+ mode = b_mode;
+ sha = b_sha;
+ conf = "directory/file";
+ }
+ if (lstat(path, &st) == 0 && S_ISDIR(st.st_mode)) {
+ const char *new_path = unique_path(o, path, add_branch);
+ clean_merge = 0;
+ output(o, 1, "CONFLICT (%s): There is a directory with name %s in %s. "
+ "Adding %s as %s",
+ conf, path, other_branch, path, new_path);
+ update_file(o, 0, sha, mode, new_path);
+ } else {
+ output(o, 2, "Adding %s", path);
+ update_file(o, 1, sha, mode, path);
+ }
} else {
- output(o, 2, "Adding %s", path);
- update_file(o, 1, sha, mode, path);
+ entry->processed = 0;
+ return 1; /* not handled; assume clean until processed */
}
return clean_merge;
get_files_dirs(o, merge);
entries = get_unmerged();
+ make_room_for_directories_of_df_conflicts(o, entries);
re_head = get_renames(o, head, common, head, merge, entries);
re_merge = get_renames(o, merge, common, head, merge, entries);
clean = process_renames(o, re_head, re_merge);
&& !process_df_entry(o, path, e))
clean = 0;
}
+ for (i = 0; i < entries->nr; i++) {
+ struct stage_data *e = entries->items[i].util;
+ if (!e->processed)
+ die("Unprocessed path??? %s",
+ entries->items[i].string);
+ }
string_list_clear(re_merge, 0);
string_list_clear(re_head, 0);
memset(&o->current_directory_set, 0, sizeof(struct string_list));
o->current_directory_set.strdup_strings = 1;
}
+
+int parse_merge_opt(struct merge_options *o, const char *s)
+{
+ if (!s || !*s)
+ return -1;
+ if (!strcmp(s, "ours"))
+ o->recursive_variant = MERGE_RECURSIVE_OURS;
+ else if (!strcmp(s, "theirs"))
+ o->recursive_variant = MERGE_RECURSIVE_THEIRS;
+ else if (!strcmp(s, "subtree"))
+ o->subtree_shift = "";
+ else if (!prefixcmp(s, "subtree="))
+ o->subtree_shift = s + strlen("subtree=");
+ else if (!strcmp(s, "patience"))
+ o->xdl_opts |= XDF_PATIENCE_DIFF;
+ else if (!strcmp(s, "ignore-space-change"))
+ o->xdl_opts |= XDF_IGNORE_WHITESPACE_CHANGE;
+ else if (!strcmp(s, "ignore-all-space"))
+ o->xdl_opts |= XDF_IGNORE_WHITESPACE;
+ else if (!strcmp(s, "ignore-space-at-eol"))
+ o->xdl_opts |= XDF_IGNORE_WHITESPACE_AT_EOL;
+ else if (!strcmp(s, "renormalize"))
+ o->renormalize = 1;
+ else if (!strcmp(s, "no-renormalize"))
+ o->renormalize = 0;
+ else if (!prefixcmp(s, "rename-threshold=")) {
+ const char *score = s + strlen("rename-threshold=");
+ if ((o->rename_score = parse_rename_score(&score)) == -1 || *score != 0)
+ return -1;
+ }
+ else
+ return -1;
+ return 0;
+}
diff --git a/merge-recursive.h b/merge-recursive.h
index 34492dbd6ea22d715c98acbeeabe04033ff9a794..981ed6ac94b5ed73f15a3b0dca723586374ca0b8 100644 (file)
--- a/merge-recursive.h
+++ b/merge-recursive.h
const char *subtree_shift;
unsigned buffer_output : 1;
unsigned renormalize : 1;
+ long xdl_opts;
int verbosity;
int diff_rename_limit;
int merge_rename_limit;
+ int rename_score;
int call_depth;
struct strbuf obuf;
struct string_list current_file_set;
void init_merge_options(struct merge_options *o);
struct tree *write_tree_from_memory(struct merge_options *o);
+int parse_merge_opt(struct merge_options *out, const char *s);
+
/* builtin/merge.c */
-int try_merge_command(const char *strategy, struct commit_list *common, const char *head_arg, struct commit_list *remotes);
+int try_merge_command(const char *strategy, size_t xopts_nr,
+ const char **xopts, struct commit_list *common,
+ const char *head_arg, struct commit_list *remotes);
#endif
diff --git a/name-hash.c b/name-hash.c
index 0031d78e8c98a32d61cd0dc0f939a033e24ed890..c6b6a3fe4cd94e48893b172c17b6e7df3bfa36f8 100644 (file)
--- a/name-hash.c
+++ b/name-hash.c
return hash;
}
+static void hash_index_entry_directories(struct index_state *istate, struct cache_entry *ce)
+{
+ /*
+ * Throw each directory component in the hash for quick lookup
+ * during a git status. Directory components are stored with their
+ * closing slash. Despite submodules being a directory, they never
+ * reach this point, because they are stored without a closing slash
+ * in the cache.
+ *
+ * Note that the cache_entry stored with the directory does not
+ * represent the directory itself. It is a pointer to an existing
+ * filename, and its only purpose is to represent existence of the
+ * directory in the cache. It is very possible multiple directory
+ * hash entries may point to the same cache_entry.
+ */
+ unsigned int hash;
+ void **pos;
+
+ const char *ptr = ce->name;
+ while (*ptr) {
+ while (*ptr && *ptr != '/')
+ ++ptr;
+ if (*ptr == '/') {
+ ++ptr;
+ hash = hash_name(ce->name, ptr - ce->name);
+ if (!lookup_hash(hash, &istate->name_hash)) {
+ pos = insert_hash(hash, ce, &istate->name_hash);
+ if (pos) {
+ ce->next = *pos;
+ *pos = ce;
+ }
+ }
+ }
+ }
+}
+
static void hash_index_entry(struct index_state *istate, struct cache_entry *ce)
{
void **pos;
ce->next = *pos;
*pos = ce;
}
+
+ if (ignore_case)
+ hash_index_entry_directories(istate, ce);
}
static void lazy_init_name_hash(struct index_state *istate)
@@ -97,7 +136,21 @@ static int same_name(const struct cache_entry *ce, const char *name, int namelen
if (len == namelen && !cache_name_compare(name, namelen, ce->name, len))
return 1;
- return icase && slow_same_name(name, namelen, ce->name, len);
+ if (!icase)
+ return 0;
+
+ /*
+ * If the entry we're comparing is a filename (no trailing slash), then compare
+ * the lengths exactly.
+ */
+ if (name[namelen - 1] != '/')
+ return slow_same_name(name, namelen, ce->name, len);
+
+ /*
+ * For a directory, we point to an arbitrary cache_entry filename. Just
+ * make sure the directory portion matches.
+ */
+ return slow_same_name(name, namelen, ce->name, namelen < len ? namelen : len);
}
struct cache_entry *index_name_exists(struct index_state *istate, const char *name, int namelen, int icase)
@@ -115,5 +168,22 @@ struct cache_entry *index_name_exists(struct index_state *istate, const char *na
}
ce = ce->next;
}
+
+ /*
+ * Might be a submodule. Despite submodules being directories,
+ * they are stored in the name hash without a closing slash.
+ * When ignore_case is 1, directories are stored in the name hash
+ * with their closing slash.
+ *
+ * The side effect of this storage technique is we have need to
+ * remove the slash from name and perform the lookup again without
+ * the slash. If a match is made, S_ISGITLINK(ce->mode) will be
+ * true.
+ */
+ if (icase && name[namelen - 1] == '/') {
+ ce = index_name_exists(istate, name, namelen - 1, icase);
+ if (ce && S_ISGITLINK(ce->ce_mode))
+ return ce;
+ }
return NULL;
}
diff --git a/notes-cache.c b/notes-cache.c
index dee6d62e72703dfa68b4a3fb8ed069ccdc2a5004..4c8984ede1e218d3e0aaadb6d8c72bfcafddeee9 100644 (file)
--- a/notes-cache.c
+++ b/notes-cache.c
if (write_sha1_file(data, size, "blob", value_sha1) < 0)
return -1;
- add_note(&c->tree, key_sha1, value_sha1, NULL);
- return 0;
+ return add_note(&c->tree, key_sha1, value_sha1, NULL);
}
diff --git a/notes-merge.c b/notes-merge.c
--- /dev/null
+++ b/notes-merge.c
@@ -0,0 +1,737 @@
+#include "cache.h"
+#include "commit.h"
+#include "refs.h"
+#include "diff.h"
+#include "diffcore.h"
+#include "xdiff-interface.h"
+#include "ll-merge.h"
+#include "dir.h"
+#include "notes.h"
+#include "notes-merge.h"
+#include "strbuf.h"
+
+struct notes_merge_pair {
+ unsigned char obj[20], base[20], local[20], remote[20];
+};
+
+void init_notes_merge_options(struct notes_merge_options *o)
+{
+ memset(o, 0, sizeof(struct notes_merge_options));
+ strbuf_init(&(o->commit_msg), 0);
+ o->verbosity = NOTES_MERGE_VERBOSITY_DEFAULT;
+}
+
+#define OUTPUT(o, v, ...) \
+ do { \
+ if ((o)->verbosity >= (v)) { \
+ printf(__VA_ARGS__); \
+ puts(""); \
+ } \
+ } while (0)
+
+static int path_to_sha1(const char *path, unsigned char *sha1)
+{
+ char hex_sha1[40];
+ int i = 0;
+ while (*path && i < 40) {
+ if (*path != '/')
+ hex_sha1[i++] = *path;
+ path++;
+ }
+ if (*path || i != 40)
+ return -1;
+ return get_sha1_hex(hex_sha1, sha1);
+}
+
+static int verify_notes_filepair(struct diff_filepair *p, unsigned char *sha1)
+{
+ switch (p->status) {
+ case DIFF_STATUS_MODIFIED:
+ assert(p->one->mode == p->two->mode);
+ assert(!is_null_sha1(p->one->sha1));
+ assert(!is_null_sha1(p->two->sha1));
+ break;
+ case DIFF_STATUS_ADDED:
+ assert(is_null_sha1(p->one->sha1));
+ break;
+ case DIFF_STATUS_DELETED:
+ assert(is_null_sha1(p->two->sha1));
+ break;
+ default:
+ return -1;
+ }
+ assert(!strcmp(p->one->path, p->two->path));
+ return path_to_sha1(p->one->path, sha1);
+}
+
+static struct notes_merge_pair *find_notes_merge_pair_pos(
+ struct notes_merge_pair *list, int len, unsigned char *obj,
+ int insert_new, int *occupied)
+{
+ /*
+ * Both diff_tree_remote() and diff_tree_local() tend to process
+ * merge_pairs in ascending order. Therefore, cache last returned
+ * index, and search sequentially from there until the appropriate
+ * position is found.
+ *
+ * Since inserts only happen from diff_tree_remote() (which mainly
+ * _appends_), we don't care that inserting into the middle of the
+ * list is expensive (using memmove()).
+ */
+ static int last_index;
+ int i = last_index < len ? last_index : len - 1;
+ int prev_cmp = 0, cmp = -1;
+ while (i >= 0 && i < len) {
+ cmp = hashcmp(obj, list[i].obj);
+ if (!cmp) /* obj belongs @ i */
+ break;
+ else if (cmp < 0 && prev_cmp <= 0) /* obj belongs < i */
+ i--;
+ else if (cmp < 0) /* obj belongs between i-1 and i */
+ break;
+ else if (cmp > 0 && prev_cmp >= 0) /* obj belongs > i */
+ i++;
+ else /* if (cmp > 0) */ { /* obj belongs between i and i+1 */
+ i++;
+ break;
+ }
+ prev_cmp = cmp;
+ }
+ if (i < 0)
+ i = 0;
+ /* obj belongs at, or immediately preceding, index i (0 <= i <= len) */
+
+ if (!cmp)
+ *occupied = 1;
+ else {
+ *occupied = 0;
+ if (insert_new && i < len) {
+ memmove(list + i + 1, list + i,
+ (len - i) * sizeof(struct notes_merge_pair));
+ memset(list + i, 0, sizeof(struct notes_merge_pair));
+ }
+ }
+ last_index = i;
+ return list + i;
+}
+
+static unsigned char uninitialized[20] =
+ "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" \
+ "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff";
+
+static struct notes_merge_pair *diff_tree_remote(struct notes_merge_options *o,
+ const unsigned char *base,
+ const unsigned char *remote,
+ int *num_changes)
+{
+ struct diff_options opt;
+ struct notes_merge_pair *changes;
+ int i, len = 0;
+
+ trace_printf("\tdiff_tree_remote(base = %.7s, remote = %.7s)\n",
+ sha1_to_hex(base), sha1_to_hex(remote));
+
+ diff_setup(&opt);
+ DIFF_OPT_SET(&opt, RECURSIVE);
+ opt.output_format = DIFF_FORMAT_NO_OUTPUT;
+ if (diff_setup_done(&opt) < 0)
+ die("diff_setup_done failed");
+ diff_tree_sha1(base, remote, "", &opt);
+ diffcore_std(&opt);
+
+ changes = xcalloc(diff_queued_diff.nr, sizeof(struct notes_merge_pair));
+
+ for (i = 0; i < diff_queued_diff.nr; i++) {
+ struct diff_filepair *p = diff_queued_diff.queue[i];
+ struct notes_merge_pair *mp;
+ int occupied;
+ unsigned char obj[20];
+
+ if (verify_notes_filepair(p, obj)) {
+ trace_printf("\t\tCannot merge entry '%s' (%c): "
+ "%.7s -> %.7s. Skipping!\n", p->one->path,
+ p->status, sha1_to_hex(p->one->sha1),
+ sha1_to_hex(p->two->sha1));
+ continue;
+ }
+ mp = find_notes_merge_pair_pos(changes, len, obj, 1, &occupied);
+ if (occupied) {
+ /* We've found an addition/deletion pair */
+ assert(!hashcmp(mp->obj, obj));
+ if (is_null_sha1(p->one->sha1)) { /* addition */
+ assert(is_null_sha1(mp->remote));
+ hashcpy(mp->remote, p->two->sha1);
+ } else if (is_null_sha1(p->two->sha1)) { /* deletion */
+ assert(is_null_sha1(mp->base));
+ hashcpy(mp->base, p->one->sha1);
+ } else
+ assert(!"Invalid existing change recorded");
+ } else {
+ hashcpy(mp->obj, obj);
+ hashcpy(mp->base, p->one->sha1);
+ hashcpy(mp->local, uninitialized);
+ hashcpy(mp->remote, p->two->sha1);
+ len++;
+ }
+ trace_printf("\t\tStored remote change for %s: %.7s -> %.7s\n",
+ sha1_to_hex(mp->obj), sha1_to_hex(mp->base),
+ sha1_to_hex(mp->remote));
+ }
+ diff_flush(&opt);
+ diff_tree_release_paths(&opt);
+
+ *num_changes = len;
+ return changes;
+}
+
+static void diff_tree_local(struct notes_merge_options *o,
+ struct notes_merge_pair *changes, int len,
+ const unsigned char *base,
+ const unsigned char *local)
+{
+ struct diff_options opt;
+ int i;
+
+ trace_printf("\tdiff_tree_local(len = %i, base = %.7s, local = %.7s)\n",
+ len, sha1_to_hex(base), sha1_to_hex(local));
+
+ diff_setup(&opt);
+ DIFF_OPT_SET(&opt, RECURSIVE);
+ opt.output_format = DIFF_FORMAT_NO_OUTPUT;
+ if (diff_setup_done(&opt) < 0)
+ die("diff_setup_done failed");
+ diff_tree_sha1(base, local, "", &opt);
+ diffcore_std(&opt);
+
+ for (i = 0; i < diff_queued_diff.nr; i++) {
+ struct diff_filepair *p = diff_queued_diff.queue[i];
+ struct notes_merge_pair *mp;
+ int match;
+ unsigned char obj[20];
+
+ if (verify_notes_filepair(p, obj)) {
+ trace_printf("\t\tCannot merge entry '%s' (%c): "
+ "%.7s -> %.7s. Skipping!\n", p->one->path,
+ p->status, sha1_to_hex(p->one->sha1),
+ sha1_to_hex(p->two->sha1));
+ continue;
+ }
+ mp = find_notes_merge_pair_pos(changes, len, obj, 0, &match);
+ if (!match) {
+ trace_printf("\t\tIgnoring local-only change for %s: "
+ "%.7s -> %.7s\n", sha1_to_hex(obj),
+ sha1_to_hex(p->one->sha1),
+ sha1_to_hex(p->two->sha1));
+ continue;
+ }
+
+ assert(!hashcmp(mp->obj, obj));
+ if (is_null_sha1(p->two->sha1)) { /* deletion */
+ /*
+ * Either this is a true deletion (1), or it is part
+ * of an A/D pair (2), or D/A pair (3):
+ *
+ * (1) mp->local is uninitialized; set it to null_sha1
+ * (2) mp->local is not uninitialized; don't touch it
+ * (3) mp->local is uninitialized; set it to null_sha1
+ * (will be overwritten by following addition)
+ */
+ if (!hashcmp(mp->local, uninitialized))
+ hashclr(mp->local);
+ } else if (is_null_sha1(p->one->sha1)) { /* addition */
+ /*
+ * Either this is a true addition (1), or it is part
+ * of an A/D pair (2), or D/A pair (3):
+ *
+ * (1) mp->local is uninitialized; set to p->two->sha1
+ * (2) mp->local is uninitialized; set to p->two->sha1
+ * (3) mp->local is null_sha1; set to p->two->sha1
+ */
+ assert(is_null_sha1(mp->local) ||
+ !hashcmp(mp->local, uninitialized));
+ hashcpy(mp->local, p->two->sha1);
+ } else { /* modification */
+ /*
+ * This is a true modification. p->one->sha1 shall
+ * match mp->base, and mp->local shall be uninitialized.
+ * Set mp->local to p->two->sha1.
+ */
+ assert(!hashcmp(p->one->sha1, mp->base));
+ assert(!hashcmp(mp->local, uninitialized));
+ hashcpy(mp->local, p->two->sha1);
+ }
+ trace_printf("\t\tStored local change for %s: %.7s -> %.7s\n",
+ sha1_to_hex(mp->obj), sha1_to_hex(mp->base),
+ sha1_to_hex(mp->local));
+ }
+ diff_flush(&opt);
+ diff_tree_release_paths(&opt);
+}
+
+static void check_notes_merge_worktree(struct notes_merge_options *o)
+{
+ if (!o->has_worktree) {
+ /*
+ * Must establish NOTES_MERGE_WORKTREE.
+ * Abort if NOTES_MERGE_WORKTREE already exists
+ */
+ if (file_exists(git_path(NOTES_MERGE_WORKTREE))) {
+ if (advice_resolve_conflict)
+ die("You have not concluded your previous "
+ "notes merge (%s exists).\nPlease, use "
+ "'git notes merge --commit' or 'git notes "
+ "merge --abort' to commit/abort the "
+ "previous merge before you start a new "
+ "notes merge.", git_path("NOTES_MERGE_*"));
+ else
+ die("You have not concluded your notes merge "
+ "(%s exists).", git_path("NOTES_MERGE_*"));
+ }
+
+ if (safe_create_leading_directories(git_path(
+ NOTES_MERGE_WORKTREE "/.test")))
+ die_errno("unable to create directory %s",
+ git_path(NOTES_MERGE_WORKTREE));
+ o->has_worktree = 1;
+ } else if (!file_exists(git_path(NOTES_MERGE_WORKTREE)))
+ /* NOTES_MERGE_WORKTREE should already be established */
+ die("missing '%s'. This should not happen",
+ git_path(NOTES_MERGE_WORKTREE));
+}
+
+static void write_buf_to_worktree(const unsigned char *obj,
+ const char *buf, unsigned long size)
+{
+ int fd;
+ char *path = git_path(NOTES_MERGE_WORKTREE "/%s", sha1_to_hex(obj));
+ if (safe_create_leading_directories(path))
+ die_errno("unable to create directory for '%s'", path);
+ if (file_exists(path))
+ die("found existing file at '%s'", path);
+
+ fd = open(path, O_WRONLY | O_TRUNC | O_CREAT, 0666);
+ if (fd < 0)
+ die_errno("failed to open '%s'", path);
+
+ while (size > 0) {
+ long ret = write_in_full(fd, buf, size);
+ if (ret < 0) {
+ /* Ignore epipe */
+ if (errno == EPIPE)
+ break;
+ die_errno("notes-merge");
+ } else if (!ret) {
+ die("notes-merge: disk full?");
+ }
+ size -= ret;
+ buf += ret;
+ }
+
+ close(fd);
+}
+
+static void write_note_to_worktree(const unsigned char *obj,
+ const unsigned char *note)
+{
+ enum object_type type;
+ unsigned long size;
+ void *buf = read_sha1_file(note, &type, &size);
+
+ if (!buf)
+ die("cannot read note %s for object %s",
+ sha1_to_hex(note), sha1_to_hex(obj));
+ if (type != OBJ_BLOB)
+ die("blob expected in note %s for object %s",
+ sha1_to_hex(note), sha1_to_hex(obj));
+ write_buf_to_worktree(obj, buf, size);
+ free(buf);
+}
+
+static int ll_merge_in_worktree(struct notes_merge_options *o,
+ struct notes_merge_pair *p)
+{
+ mmbuffer_t result_buf;
+ mmfile_t base, local, remote;
+ int status;
+
+ read_mmblob(&base, p->base);
+ read_mmblob(&local, p->local);
+ read_mmblob(&remote, p->remote);
+
+ status = ll_merge(&result_buf, sha1_to_hex(p->obj), &base, NULL,
+ &local, o->local_ref, &remote, o->remote_ref, 0);
+
+ free(base.ptr);
+ free(local.ptr);
+ free(remote.ptr);
+
+ if ((status < 0) || !result_buf.ptr)
+ die("Failed to execute internal merge");
+
+ write_buf_to_worktree(p->obj, result_buf.ptr, result_buf.size);
+ free(result_buf.ptr);
+
+ return status;
+}
+
+static int merge_one_change_manual(struct notes_merge_options *o,
+ struct notes_merge_pair *p,
+ struct notes_tree *t)
+{
+ const char *lref = o->local_ref ? o->local_ref : "local version";
+ const char *rref = o->remote_ref ? o->remote_ref : "remote version";
+
+ trace_printf("\t\t\tmerge_one_change_manual(obj = %.7s, base = %.7s, "
+ "local = %.7s, remote = %.7s)\n",
+ sha1_to_hex(p->obj), sha1_to_hex(p->base),
+ sha1_to_hex(p->local), sha1_to_hex(p->remote));
+
+ /* add "Conflicts:" section to commit message first time through */
+ if (!o->has_worktree)
+ strbuf_addstr(&(o->commit_msg), "\n\nConflicts:\n");
+
+ strbuf_addf(&(o->commit_msg), "\t%s\n", sha1_to_hex(p->obj));
+
+ OUTPUT(o, 2, "Auto-merging notes for %s", sha1_to_hex(p->obj));
+ check_notes_merge_worktree(o);
+ if (is_null_sha1(p->local)) {
+ /* D/F conflict, checkout p->remote */
+ assert(!is_null_sha1(p->remote));
+ OUTPUT(o, 1, "CONFLICT (delete/modify): Notes for object %s "
+ "deleted in %s and modified in %s. Version from %s "
+ "left in tree.", sha1_to_hex(p->obj), lref, rref, rref);
+ write_note_to_worktree(p->obj, p->remote);
+ } else if (is_null_sha1(p->remote)) {
+ /* D/F conflict, checkout p->local */
+ assert(!is_null_sha1(p->local));
+ OUTPUT(o, 1, "CONFLICT (delete/modify): Notes for object %s "
+ "deleted in %s and modified in %s. Version from %s "
+ "left in tree.", sha1_to_hex(p->obj), rref, lref, lref);
+ write_note_to_worktree(p->obj, p->local);
+ } else {
+ /* "regular" conflict, checkout result of ll_merge() */
+ const char *reason = "content";
+ if (is_null_sha1(p->base))
+ reason = "add/add";
+ assert(!is_null_sha1(p->local));
+ assert(!is_null_sha1(p->remote));
+ OUTPUT(o, 1, "CONFLICT (%s): Merge conflict in notes for "
+ "object %s", reason, sha1_to_hex(p->obj));
+ ll_merge_in_worktree(o, p);
+ }
+
+ trace_printf("\t\t\tremoving from partial merge result\n");
+ remove_note(t, p->obj);
+
+ return 1;
+}
+
+static int merge_one_change(struct notes_merge_options *o,
+ struct notes_merge_pair *p, struct notes_tree *t)
+{
+ /*
+ * Return 0 if change is successfully resolved (stored in notes_tree).
+ * Return 1 is change results in a conflict (NOT stored in notes_tree,
+ * but instead written to NOTES_MERGE_WORKTREE with conflict markers).
+ */
+ switch (o->strategy) {
+ case NOTES_MERGE_RESOLVE_MANUAL:
+ return merge_one_change_manual(o, p, t);
+ case NOTES_MERGE_RESOLVE_OURS:
+ OUTPUT(o, 2, "Using local notes for %s", sha1_to_hex(p->obj));
+ /* nothing to do */
+ return 0;
+ case NOTES_MERGE_RESOLVE_THEIRS:
+ OUTPUT(o, 2, "Using remote notes for %s", sha1_to_hex(p->obj));
+ if (add_note(t, p->obj, p->remote, combine_notes_overwrite))
+ die("BUG: combine_notes_overwrite failed");
+ return 0;
+ case NOTES_MERGE_RESOLVE_UNION:
+ OUTPUT(o, 2, "Concatenating local and remote notes for %s",
+ sha1_to_hex(p->obj));
+ if (add_note(t, p->obj, p->remote, combine_notes_concatenate))
+ die("failed to concatenate notes "
+ "(combine_notes_concatenate)");
+ return 0;
+ case NOTES_MERGE_RESOLVE_CAT_SORT_UNIQ:
+ OUTPUT(o, 2, "Concatenating unique lines in local and remote "
+ "notes for %s", sha1_to_hex(p->obj));
+ if (add_note(t, p->obj, p->remote, combine_notes_cat_sort_uniq))
+ die("failed to concatenate notes "
+ "(combine_notes_cat_sort_uniq)");
+ return 0;
+ }
+ die("Unknown strategy (%i).", o->strategy);
+}
+
+static int merge_changes(struct notes_merge_options *o,
+ struct notes_merge_pair *changes, int *num_changes,
+ struct notes_tree *t)
+{
+ int i, conflicts = 0;
+
+ trace_printf("\tmerge_changes(num_changes = %i)\n", *num_changes);
+ for (i = 0; i < *num_changes; i++) {
+ struct notes_merge_pair *p = changes + i;
+ trace_printf("\t\t%.7s: %.7s -> %.7s/%.7s\n",
+ sha1_to_hex(p->obj), sha1_to_hex(p->base),
+ sha1_to_hex(p->local), sha1_to_hex(p->remote));
+
+ if (!hashcmp(p->base, p->remote)) {
+ /* no remote change; nothing to do */
+ trace_printf("\t\t\tskipping (no remote change)\n");
+ } else if (!hashcmp(p->local, p->remote)) {
+ /* same change in local and remote; nothing to do */
+ trace_printf("\t\t\tskipping (local == remote)\n");
+ } else if (!hashcmp(p->local, uninitialized) ||
+ !hashcmp(p->local, p->base)) {
+ /* no local change; adopt remote change */
+ trace_printf("\t\t\tno local change, adopted remote\n");
+ if (add_note(t, p->obj, p->remote,
+ combine_notes_overwrite))
+ die("BUG: combine_notes_overwrite failed");
+ } else {
+ /* need file-level merge between local and remote */
+ trace_printf("\t\t\tneed content-level merge\n");
+ conflicts += merge_one_change(o, p, t);
+ }
+ }
+
+ return conflicts;
+}
+
+static int merge_from_diffs(struct notes_merge_options *o,
+ const unsigned char *base,
+ const unsigned char *local,
+ const unsigned char *remote, struct notes_tree *t)
+{
+ struct notes_merge_pair *changes;
+ int num_changes, conflicts;
+
+ trace_printf("\tmerge_from_diffs(base = %.7s, local = %.7s, "
+ "remote = %.7s)\n", sha1_to_hex(base), sha1_to_hex(local),
+ sha1_to_hex(remote));
+
+ changes = diff_tree_remote(o, base, remote, &num_changes);
+ diff_tree_local(o, changes, num_changes, base, local);
+
+ conflicts = merge_changes(o, changes, &num_changes, t);
+ free(changes);
+
+ OUTPUT(o, 4, "Merge result: %i unmerged notes and a %s notes tree",
+ conflicts, t->dirty ? "dirty" : "clean");
+
+ return conflicts ? -1 : 1;
+}
+
+void create_notes_commit(struct notes_tree *t, struct commit_list *parents,
+ const char *msg, unsigned char *result_sha1)
+{
+ unsigned char tree_sha1[20];
+
+ assert(t->initialized);
+
+ if (write_notes_tree(t, tree_sha1))
+ die("Failed to write notes tree to database");
+
+ if (!parents) {
+ /* Deduce parent commit from t->ref */
+ unsigned char parent_sha1[20];
+ if (!read_ref(t->ref, parent_sha1)) {
+ struct commit *parent = lookup_commit(parent_sha1);
+ if (!parent || parse_commit(parent))
+ die("Failed to find/parse commit %s", t->ref);
+ commit_list_insert(parent, &parents);
+ }
+ /* else: t->ref points to nothing, assume root/orphan commit */
+ }
+
+ if (commit_tree(msg, tree_sha1, parents, result_sha1, NULL))
+ die("Failed to commit notes tree to database");
+}
+
+int notes_merge(struct notes_merge_options *o,
+ struct notes_tree *local_tree,
+ unsigned char *result_sha1)
+{
+ unsigned char local_sha1[20], remote_sha1[20];
+ struct commit *local, *remote;
+ struct commit_list *bases = NULL;
+ const unsigned char *base_sha1, *base_tree_sha1;
+ int result = 0;
+
+ assert(o->local_ref && o->remote_ref);
+ assert(!strcmp(o->local_ref, local_tree->ref));
+ hashclr(result_sha1);
+
+ trace_printf("notes_merge(o->local_ref = %s, o->remote_ref = %s)\n",
+ o->local_ref, o->remote_ref);
+
+ /* Dereference o->local_ref into local_sha1 */
+ if (!resolve_ref(o->local_ref, local_sha1, 0, NULL))
+ die("Failed to resolve local notes ref '%s'", o->local_ref);
+ else if (!check_ref_format(o->local_ref) && is_null_sha1(local_sha1))
+ local = NULL; /* local_sha1 == null_sha1 indicates unborn ref */
+ else if (!(local = lookup_commit_reference(local_sha1)))
+ die("Could not parse local commit %s (%s)",
+ sha1_to_hex(local_sha1), o->local_ref);
+ trace_printf("\tlocal commit: %.7s\n", sha1_to_hex(local_sha1));
+
+ /* Dereference o->remote_ref into remote_sha1 */
+ if (get_sha1(o->remote_ref, remote_sha1)) {
+ /*
+ * Failed to get remote_sha1. If o->remote_ref looks like an
+ * unborn ref, perform the merge using an empty notes tree.
+ */
+ if (!check_ref_format(o->remote_ref)) {
+ hashclr(remote_sha1);
+ remote = NULL;
+ } else {
+ die("Failed to resolve remote notes ref '%s'",
+ o->remote_ref);
+ }
+ } else if (!(remote = lookup_commit_reference(remote_sha1))) {
+ die("Could not parse remote commit %s (%s)",
+ sha1_to_hex(remote_sha1), o->remote_ref);
+ }
+ trace_printf("\tremote commit: %.7s\n", sha1_to_hex(remote_sha1));
+
+ if (!local && !remote)
+ die("Cannot merge empty notes ref (%s) into empty notes ref "
+ "(%s)", o->remote_ref, o->local_ref);
+ if (!local) {
+ /* result == remote commit */
+ hashcpy(result_sha1, remote_sha1);
+ goto found_result;
+ }
+ if (!remote) {
+ /* result == local commit */
+ hashcpy(result_sha1, local_sha1);
+ goto found_result;
+ }
+ assert(local && remote);
+
+ /* Find merge bases */
+ bases = get_merge_bases(local, remote, 1);
+ if (!bases) {
+ base_sha1 = null_sha1;
+ base_tree_sha1 = EMPTY_TREE_SHA1_BIN;
+ OUTPUT(o, 4, "No merge base found; doing history-less merge");
+ } else if (!bases->next) {
+ base_sha1 = bases->item->object.sha1;
+ base_tree_sha1 = bases->item->tree->object.sha1;
+ OUTPUT(o, 4, "One merge base found (%.7s)",
+ sha1_to_hex(base_sha1));
+ } else {
+ /* TODO: How to handle multiple merge-bases? */
+ base_sha1 = bases->item->object.sha1;
+ base_tree_sha1 = bases->item->tree->object.sha1;
+ OUTPUT(o, 3, "Multiple merge bases found. Using the first "
+ "(%.7s)", sha1_to_hex(base_sha1));
+ }
+
+ OUTPUT(o, 4, "Merging remote commit %.7s into local commit %.7s with "
+ "merge-base %.7s", sha1_to_hex(remote->object.sha1),
+ sha1_to_hex(local->object.sha1), sha1_to_hex(base_sha1));
+
+ if (!hashcmp(remote->object.sha1, base_sha1)) {
+ /* Already merged; result == local commit */
+ OUTPUT(o, 2, "Already up-to-date!");
+ hashcpy(result_sha1, local->object.sha1);
+ goto found_result;
+ }
+ if (!hashcmp(local->object.sha1, base_sha1)) {
+ /* Fast-forward; result == remote commit */
+ OUTPUT(o, 2, "Fast-forward");
+ hashcpy(result_sha1, remote->object.sha1);
+ goto found_result;
+ }
+
+ result = merge_from_diffs(o, base_tree_sha1, local->tree->object.sha1,
+ remote->tree->object.sha1, local_tree);
+
+ if (result != 0) { /* non-trivial merge (with or without conflicts) */
+ /* Commit (partial) result */
+ struct commit_list *parents = NULL;
+ commit_list_insert(remote, &parents); /* LIFO order */
+ commit_list_insert(local, &parents);
+ create_notes_commit(local_tree, parents, o->commit_msg.buf,
+ result_sha1);
+ }
+
+found_result:
+ free_commit_list(bases);
+ strbuf_release(&(o->commit_msg));
+ trace_printf("notes_merge(): result = %i, result_sha1 = %.7s\n",
+ result, sha1_to_hex(result_sha1));
+ return result;
+}
+
+int notes_merge_commit(struct notes_merge_options *o,
+ struct notes_tree *partial_tree,
+ struct commit *partial_commit,
+ unsigned char *result_sha1)
+{
+ /*
+ * Iterate through files in .git/NOTES_MERGE_WORKTREE and add all
+ * found notes to 'partial_tree'. Write the updates notes tree to
+ * the DB, and commit the resulting tree object while reusing the
+ * commit message and parents from 'partial_commit'.
+ * Finally store the new commit object SHA1 into 'result_sha1'.
+ */
+ struct dir_struct dir;
+ const char *path = git_path(NOTES_MERGE_WORKTREE "/");
+ int path_len = strlen(path), i;
+ const char *msg = strstr(partial_commit->buffer, "\n\n");
+
+ OUTPUT(o, 3, "Committing notes in notes merge worktree at %.*s",
+ path_len - 1, path);
+
+ if (!msg || msg[2] == '\0')
+ die("partial notes commit has empty message");
+ msg += 2;
+
+ memset(&dir, 0, sizeof(dir));
+ read_directory(&dir, path, path_len, NULL);
+ for (i = 0; i < dir.nr; i++) {
+ struct dir_entry *ent = dir.entries[i];
+ struct stat st;
+ const char *relpath = ent->name + path_len;
+ unsigned char obj_sha1[20], blob_sha1[20];
+
+ if (ent->len - path_len != 40 || get_sha1_hex(relpath, obj_sha1)) {
+ OUTPUT(o, 3, "Skipping non-SHA1 entry '%s'", ent->name);
+ continue;
+ }
+
+ /* write file as blob, and add to partial_tree */
+ if (stat(ent->name, &st))
+ die_errno("Failed to stat '%s'", ent->name);
+ if (index_path(blob_sha1, ent->name, &st, 1))
+ die("Failed to write blob object from '%s'", ent->name);
+ if (add_note(partial_tree, obj_sha1, blob_sha1, NULL))
+ die("Failed to add resolved note '%s' to notes tree",
+ ent->name);
+ OUTPUT(o, 4, "Added resolved note for object %s: %s",
+ sha1_to_hex(obj_sha1), sha1_to_hex(blob_sha1));
+ }
+
+ create_notes_commit(partial_tree, partial_commit->parents, msg,
+ result_sha1);
+ OUTPUT(o, 4, "Finalized notes merge commit: %s",
+ sha1_to_hex(result_sha1));
+ return 0;
+}
+
+int notes_merge_abort(struct notes_merge_options *o)
+{
+ /* Remove .git/NOTES_MERGE_WORKTREE directory and all files within */
+ struct strbuf buf = STRBUF_INIT;
+ int ret;
+
+ strbuf_addstr(&buf, git_path(NOTES_MERGE_WORKTREE));
+ OUTPUT(o, 3, "Removing notes merge worktree at %s", buf.buf);
+ ret = remove_dir_recursively(&buf, 0);
+ strbuf_release(&buf);
+ return ret;
+}
diff --git a/notes-merge.h b/notes-merge.h
--- /dev/null
+++ b/notes-merge.h
@@ -0,0 +1,98 @@
+#ifndef NOTES_MERGE_H
+#define NOTES_MERGE_H
+
+#define NOTES_MERGE_WORKTREE "NOTES_MERGE_WORKTREE"
+
+enum notes_merge_verbosity {
+ NOTES_MERGE_VERBOSITY_DEFAULT = 2,
+ NOTES_MERGE_VERBOSITY_MAX = 5
+};
+
+struct notes_merge_options {
+ const char *local_ref;
+ const char *remote_ref;
+ struct strbuf commit_msg;
+ int verbosity;
+ enum {
+ NOTES_MERGE_RESOLVE_MANUAL = 0,
+ NOTES_MERGE_RESOLVE_OURS,
+ NOTES_MERGE_RESOLVE_THEIRS,
+ NOTES_MERGE_RESOLVE_UNION,
+ NOTES_MERGE_RESOLVE_CAT_SORT_UNIQ
+ } strategy;
+ unsigned has_worktree:1;
+};
+
+void init_notes_merge_options(struct notes_merge_options *o);
+
+/*
+ * Create new notes commit from the given notes tree
+ *
+ * Properties of the created commit:
+ * - tree: the result of converting t to a tree object with write_notes_tree().
+ * - parents: the given parents OR (if NULL) the commit referenced by t->ref.
+ * - author/committer: the default determined by commmit_tree().
+ * - commit message: msg
+ *
+ * The resulting commit SHA1 is stored in result_sha1.
+ */
+void create_notes_commit(struct notes_tree *t, struct commit_list *parents,
+ const char *msg, unsigned char *result_sha1);
+
+/*
+ * Merge notes from o->remote_ref into o->local_ref
+ *
+ * The given notes_tree 'local_tree' must be the notes_tree referenced by the
+ * o->local_ref. This is the notes_tree in which the object-level merge is
+ * performed.
+ *
+ * The commits given by the two refs are merged, producing one of the following
+ * outcomes:
+ *
+ * 1. The merge trivially results in an existing commit (e.g. fast-forward or
+ * already-up-to-date). 'local_tree' is untouched, the SHA1 of the result
+ * is written into 'result_sha1' and 0 is returned.
+ * 2. The merge successfully completes, producing a merge commit. local_tree
+ * contains the updated notes tree, the SHA1 of the resulting commit is
+ * written into 'result_sha1', and 1 is returned.
+ * 3. The merge results in conflicts. This is similar to #2 in that the
+ * partial merge result (i.e. merge result minus the unmerged entries)
+ * are stored in 'local_tree', and the SHA1 or the resulting commit
+ * (to be amended when the conflicts have been resolved) is written into
+ * 'result_sha1'. The unmerged entries are written into the
+ * .git/NOTES_MERGE_WORKTREE directory with conflict markers.
+ * -1 is returned.
+ *
+ * Both o->local_ref and o->remote_ref must be given (non-NULL), but either ref
+ * (although not both) may refer to a non-existing notes ref, in which case
+ * that notes ref is interpreted as an empty notes tree, and the merge
+ * trivially results in what the other ref points to.
+ */
+int notes_merge(struct notes_merge_options *o,
+ struct notes_tree *local_tree,
+ unsigned char *result_sha1);
+
+/*
+ * Finalize conflict resolution from an earlier notes_merge()
+ *
+ * The given notes tree 'partial_tree' must be the notes_tree corresponding to
+ * the given 'partial_commit', the partial result commit created by a previous
+ * call to notes_merge().
+ *
+ * This function will add the (now resolved) notes in .git/NOTES_MERGE_WORKTREE
+ * to 'partial_tree', and create a final notes merge commit, the SHA1 of which
+ * will be stored in 'result_sha1'.
+ */
+int notes_merge_commit(struct notes_merge_options *o,
+ struct notes_tree *partial_tree,
+ struct commit *partial_commit,
+ unsigned char *result_sha1);
+
+/*
+ * Abort conflict resolution from an earlier notes_merge()
+ *
+ * Removes the notes merge worktree in .git/NOTES_MERGE_WORKTREE.
+ */
+int notes_merge_abort(struct notes_merge_options *o);
+
+#endif
index 70d00135eb5b67cd6f21b416cde2ae4a1967fd0a..a013c1bc638dbf5a8111183a3f9d154721ec5e04 100644 (file)
--- a/notes.c
+++ b/notes.c
return NULL;
}
-/*
- * To insert a leaf_node:
- * Search to the tree location appropriate for the given leaf_node's key:
- * - If location is unused (NULL), store the tweaked pointer directly there
- * - If location holds a note entry that matches the note-to-be-inserted, then
- * combine the two notes (by calling the given combine_notes function).
- * - If location holds a note entry that matches the subtree-to-be-inserted,
- * then unpack the subtree-to-be-inserted into the location.
- * - If location holds a matching subtree entry, unpack the subtree at that
- * location, and restart the insert operation from that level.
- * - Else, create a new int_node, holding both the node-at-location and the
- * node-to-be-inserted, and store the new int_node into the location.
- */
-static void note_tree_insert(struct notes_tree *t, struct int_node *tree,
- unsigned char n, struct leaf_node *entry, unsigned char type,
- combine_notes_fn combine_notes)
-{
- struct int_node *new_node;
- struct leaf_node *l;
- void **p = note_tree_search(t, &tree, &n, entry->key_sha1);
-
- assert(GET_PTR_TYPE(entry) == 0); /* no type bits set */
- l = (struct leaf_node *) CLR_PTR_TYPE(*p);
- switch (GET_PTR_TYPE(*p)) {
- case PTR_TYPE_NULL:
- assert(!*p);
- *p = SET_PTR_TYPE(entry, type);
- return;
- case PTR_TYPE_NOTE:
- switch (type) {
- case PTR_TYPE_NOTE:
- if (!hashcmp(l->key_sha1, entry->key_sha1)) {
- /* skip concatenation if l == entry */
- if (!hashcmp(l->val_sha1, entry->val_sha1))
- return;
-
- if (combine_notes(l->val_sha1, entry->val_sha1))
- die("failed to combine notes %s and %s"
- " for object %s",
- sha1_to_hex(l->val_sha1),
- sha1_to_hex(entry->val_sha1),
- sha1_to_hex(l->key_sha1));
- free(entry);
- return;
- }
- break;
- case PTR_TYPE_SUBTREE:
- if (!SUBTREE_SHA1_PREFIXCMP(l->key_sha1,
- entry->key_sha1)) {
- /* unpack 'entry' */
- load_subtree(t, entry, tree, n);
- free(entry);
- return;
- }
- break;
- }
- break;
- case PTR_TYPE_SUBTREE:
- if (!SUBTREE_SHA1_PREFIXCMP(entry->key_sha1, l->key_sha1)) {
- /* unpack 'l' and restart insert */
- *p = NULL;
- load_subtree(t, l, tree, n);
- free(l);
- note_tree_insert(t, tree, n, entry, type,
- combine_notes);
- return;
- }
- break;
- }
-
- /* non-matching leaf_node */
- assert(GET_PTR_TYPE(*p) == PTR_TYPE_NOTE ||
- GET_PTR_TYPE(*p) == PTR_TYPE_SUBTREE);
- new_node = (struct int_node *) xcalloc(sizeof(struct int_node), 1);
- note_tree_insert(t, new_node, n + 1, l, GET_PTR_TYPE(*p),
- combine_notes);
- *p = SET_PTR_TYPE(new_node, PTR_TYPE_INTERNAL);
- note_tree_insert(t, new_node, n + 1, entry, type, combine_notes);
-}
-
/*
* How to consolidate an int_node:
* If there are > 1 non-NULL entries, give up and return non-zero.
i--;
}
+/*
+ * To insert a leaf_node:
+ * Search to the tree location appropriate for the given leaf_node's key:
+ * - If location is unused (NULL), store the tweaked pointer directly there
+ * - If location holds a note entry that matches the note-to-be-inserted, then
+ * combine the two notes (by calling the given combine_notes function).
+ * - If location holds a note entry that matches the subtree-to-be-inserted,
+ * then unpack the subtree-to-be-inserted into the location.
+ * - If location holds a matching subtree entry, unpack the subtree at that
+ * location, and restart the insert operation from that level.
+ * - Else, create a new int_node, holding both the node-at-location and the
+ * node-to-be-inserted, and store the new int_node into the location.
+ */
+static int note_tree_insert(struct notes_tree *t, struct int_node *tree,
+ unsigned char n, struct leaf_node *entry, unsigned char type,
+ combine_notes_fn combine_notes)
+{
+ struct int_node *new_node;
+ struct leaf_node *l;
+ void **p = note_tree_search(t, &tree, &n, entry->key_sha1);
+ int ret = 0;
+
+ assert(GET_PTR_TYPE(entry) == 0); /* no type bits set */
+ l = (struct leaf_node *) CLR_PTR_TYPE(*p);
+ switch (GET_PTR_TYPE(*p)) {
+ case PTR_TYPE_NULL:
+ assert(!*p);
+ if (is_null_sha1(entry->val_sha1))
+ free(entry);
+ else
+ *p = SET_PTR_TYPE(entry, type);
+ return 0;
+ case PTR_TYPE_NOTE:
+ switch (type) {
+ case PTR_TYPE_NOTE:
+ if (!hashcmp(l->key_sha1, entry->key_sha1)) {
+ /* skip concatenation if l == entry */
+ if (!hashcmp(l->val_sha1, entry->val_sha1))
+ return 0;
+
+ ret = combine_notes(l->val_sha1,
+ entry->val_sha1);
+ if (!ret && is_null_sha1(l->val_sha1))
+ note_tree_remove(t, tree, n, entry);
+ free(entry);
+ return ret;
+ }
+ break;
+ case PTR_TYPE_SUBTREE:
+ if (!SUBTREE_SHA1_PREFIXCMP(l->key_sha1,
+ entry->key_sha1)) {
+ /* unpack 'entry' */
+ load_subtree(t, entry, tree, n);
+ free(entry);
+ return 0;
+ }
+ break;
+ }
+ break;
+ case PTR_TYPE_SUBTREE:
+ if (!SUBTREE_SHA1_PREFIXCMP(entry->key_sha1, l->key_sha1)) {
+ /* unpack 'l' and restart insert */
+ *p = NULL;
+ load_subtree(t, l, tree, n);
+ free(l);
+ return note_tree_insert(t, tree, n, entry, type,
+ combine_notes);
+ }
+ break;
+ }
+
+ /* non-matching leaf_node */
+ assert(GET_PTR_TYPE(*p) == PTR_TYPE_NOTE ||
+ GET_PTR_TYPE(*p) == PTR_TYPE_SUBTREE);
+ if (is_null_sha1(entry->val_sha1)) { /* skip insertion of empty note */
+ free(entry);
+ return 0;
+ }
+ new_node = (struct int_node *) xcalloc(sizeof(struct int_node), 1);
+ ret = note_tree_insert(t, new_node, n + 1, l, GET_PTR_TYPE(*p),
+ combine_notes);
+ if (ret)
+ return ret;
+ *p = SET_PTR_TYPE(new_node, PTR_TYPE_INTERNAL);
+ return note_tree_insert(t, new_node, n + 1, entry, type, combine_notes);
+}
+
/* Free the entire notes data contained in the given tree */
static void note_tree_free(struct int_node *tree)
{
l->key_sha1[19] = (unsigned char) len;
type = PTR_TYPE_SUBTREE;
}
- note_tree_insert(t, node, n, l, type,
- combine_notes_concatenate);
+ if (note_tree_insert(t, node, n, l, type,
+ combine_notes_concatenate))
+ die("Failed to load %s %s into notes tree "
+ "from %s",
+ type == PTR_TYPE_NOTE ? "note" : "subtree",
+ sha1_to_hex(l->key_sha1), t->ref);
}
continue;
return 0;
}
- /* we will separate the notes by a newline anyway */
+ /* we will separate the notes by two newlines anyway */
if (cur_msg[cur_len - 1] == '\n')
cur_len--;
/* concatenate cur_msg and new_msg into buf */
- buf_len = cur_len + 1 + new_len;
+ buf_len = cur_len + 2 + new_len;
buf = (char *) xmalloc(buf_len);
memcpy(buf, cur_msg, cur_len);
buf[cur_len] = '\n';
- memcpy(buf + cur_len + 1, new_msg, new_len);
+ buf[cur_len + 1] = '\n';
+ memcpy(buf + cur_len + 2, new_msg, new_len);
free(cur_msg);
free(new_msg);
return 0;
}
+static int string_list_add_note_lines(struct string_list *sort_uniq_list,
+ const unsigned char *sha1)
+{
+ char *data;
+ unsigned long len;
+ enum object_type t;
+ struct strbuf buf = STRBUF_INIT;
+ struct strbuf **lines = NULL;
+ int i, list_index;
+
+ if (is_null_sha1(sha1))
+ return 0;
+
+ /* read_sha1_file NUL-terminates */
+ data = read_sha1_file(sha1, &t, &len);
+ if (t != OBJ_BLOB || !data || !len) {
+ free(data);
+ return t != OBJ_BLOB || !data;
+ }
+
+ strbuf_attach(&buf, data, len, len + 1);
+ lines = strbuf_split(&buf, '\n');
+
+ for (i = 0; lines[i]; i++) {
+ if (lines[i]->buf[lines[i]->len - 1] == '\n')
+ strbuf_setlen(lines[i], lines[i]->len - 1);
+ if (!lines[i]->len)
+ continue; /* skip empty lines */
+ list_index = string_list_find_insert_index(sort_uniq_list,
+ lines[i]->buf, 0);
+ if (list_index < 0)
+ continue; /* skip duplicate lines */
+ string_list_insert_at_index(sort_uniq_list, list_index,
+ lines[i]->buf);
+ }
+
+ strbuf_list_free(lines);
+ strbuf_release(&buf);
+ return 0;
+}
+
+static int string_list_join_lines_helper(struct string_list_item *item,
+ void *cb_data)
+{
+ struct strbuf *buf = cb_data;
+ strbuf_addstr(buf, item->string);
+ strbuf_addch(buf, '\n');
+ return 0;
+}
+
+int combine_notes_cat_sort_uniq(unsigned char *cur_sha1,
+ const unsigned char *new_sha1)
+{
+ struct string_list sort_uniq_list = { NULL, 0, 0, 1 };
+ struct strbuf buf = STRBUF_INIT;
+ int ret = 1;
+
+ /* read both note blob objects into unique_lines */
+ if (string_list_add_note_lines(&sort_uniq_list, cur_sha1))
+ goto out;
+ if (string_list_add_note_lines(&sort_uniq_list, new_sha1))
+ goto out;
+
+ /* create a new blob object from sort_uniq_list */
+ if (for_each_string_list(&sort_uniq_list,
+ string_list_join_lines_helper, &buf))
+ goto out;
+
+ ret = write_sha1_file(buf.buf, buf.len, blob_type, cur_sha1);
+
+out:
+ strbuf_release(&buf);
+ string_list_clear(&sort_uniq_list, 0);
+ return ret;
+}
+
static int string_list_add_one_ref(const char *path, const unsigned char *sha1,
int flag, void *cb)
{
return 0;
}
-static const char *default_notes_ref(void)
+const char *default_notes_ref(void)
{
const char *notes_ref = NULL;
if (!notes_ref)
return;
if (get_tree_entry(object_sha1, "", sha1, &mode))
die("Failed to read notes tree referenced by %s (%s)",
- notes_ref, object_sha1);
+ notes_ref, sha1_to_hex(object_sha1));
hashclr(root_tree.key_sha1);
hashcpy(root_tree.val_sha1, sha1);
string_list_clear(&display_notes_refs, 0);
}
-void add_note(struct notes_tree *t, const unsigned char *object_sha1,
+int add_note(struct notes_tree *t, const unsigned char *object_sha1,
const unsigned char *note_sha1, combine_notes_fn combine_notes)
{
struct leaf_node *l;
l = (struct leaf_node *) xmalloc(sizeof(struct leaf_node));
hashcpy(l->key_sha1, object_sha1);
hashcpy(l->val_sha1, note_sha1);
- note_tree_insert(t, t->root, 0, l, PTR_TYPE_NOTE, combine_notes);
+ return note_tree_insert(t, t->root, 0, l, PTR_TYPE_NOTE, combine_notes);
}
int remove_note(struct notes_tree *t, const unsigned char *object_sha1)
int copy_note(struct notes_tree *t,
const unsigned char *from_obj, const unsigned char *to_obj,
- int force, combine_notes_fn combine_fn)
+ int force, combine_notes_fn combine_notes)
{
const unsigned char *note = get_note(t, from_obj);
const unsigned char *existing_note = get_note(t, to_obj);
return 1;
if (note)
- add_note(t, to_obj, note, combine_fn);
+ return add_note(t, to_obj, note, combine_notes);
else if (existing_note)
- add_note(t, to_obj, null_sha1, combine_fn);
+ return add_note(t, to_obj, null_sha1, combine_notes);
return 0;
}
index 5106761534cfb6c5b97baede700e99b0aa5dfc3b..83bd6e0ec02a1a8650004f14cd7476eedbdff518 100644 (file)
--- a/notes.h
+++ b/notes.h
* resulting SHA1 into the first SHA1 argument (cur_sha1). A non-zero return
* value indicates failure.
*
- * The two given SHA1s must both be non-NULL and different from each other.
+ * The two given SHA1s shall both be non-NULL and different from each other.
+ * Either of them (but not both) may be == null_sha1, which indicates an
+ * empty/non-existent note. If the resulting SHA1 (cur_sha1) is == null_sha1,
+ * the note will be removed from the notes tree.
*
* The default combine_notes function (you get this when passing NULL) is
* combine_notes_concatenate(), which appends the contents of the new note to
int combine_notes_concatenate(unsigned char *cur_sha1, const unsigned char *new_sha1);
int combine_notes_overwrite(unsigned char *cur_sha1, const unsigned char *new_sha1);
int combine_notes_ignore(unsigned char *cur_sha1, const unsigned char *new_sha1);
+int combine_notes_cat_sort_uniq(unsigned char *cur_sha1, const unsigned char *new_sha1);
/*
* Notes tree object
int dirty;
} default_notes_tree;
+/*
+ * Return the default notes ref.
+ *
+ * The default notes ref is the notes ref that is used when notes_ref == NULL
+ * is passed to init_notes().
+ *
+ * This the first of the following to be defined:
+ * 1. The '--ref' option to 'git notes', if given
+ * 2. The $GIT_NOTES_REF environment variable, if set
+ * 3. The value of the core.notesRef config variable, if set
+ * 4. GIT_NOTES_DEFAULT_REF (i.e. "refs/notes/commits")
+ */
+const char *default_notes_ref(void);
+
/*
* Flags controlling behaviour of notes tree initialization
*
/*
* Add the given note object to the given notes_tree structure
*
+ * If there already exists a note for the given object_sha1, the given
+ * combine_notes function is invoked to break the tie. If not given (i.e.
+ * combine_notes == NULL), the default combine_notes function for the given
+ * notes_tree is used.
+ *
+ * Passing note_sha1 == null_sha1 indicates the addition of an
+ * empty/non-existent note. This is a (potentially expensive) no-op unless
+ * there already exists a note for the given object_sha1, AND combining that
+ * note with the empty note (using the given combine_notes function) results
+ * in a new/changed note.
+ *
+ * Returns zero on success; non-zero means combine_notes failed.
+ *
* IMPORTANT: The changes made by add_note() to the given notes_tree structure
* are not persistent until a subsequent call to write_notes_tree() returns
* zero.
*/
-void add_note(struct notes_tree *t, const unsigned char *object_sha1,
+int add_note(struct notes_tree *t, const unsigned char *object_sha1,
const unsigned char *note_sha1, combine_notes_fn combine_notes);
/*
/*
* Copy a note from one object to another in the given notes_tree.
*
- * Fails if the to_obj already has a note unless 'force' is true.
+ * Returns 1 if the to_obj already has a note and 'force' is false. Otherwise,
+ * returns non-zero if 'force' is true, but the given combine_notes function
+ * failed to combine from_obj's note with to_obj's existing note.
+ * Returns zero on success.
+ *
+ * IMPORTANT: The changes made by copy_note() to the given notes_tree structure
+ * are not persistent until a subsequent call to write_notes_tree() returns
+ * zero.
*/
int copy_note(struct notes_tree *t,
const unsigned char *from_obj, const unsigned char *to_obj,
- int force, combine_notes_fn combine_fn);
+ int force, combine_notes_fn combine_notes);
/*
* Flags controlling behaviour of for_each_note()
* notes tree) from within the callback:
* - add_note()
* - remove_note()
+ * - copy_note()
* - free_notes()
*/
typedef int each_note_fn(const unsigned char *object_sha1,
diff --git a/parse-options.c b/parse-options.c
index 0fa79bc31d322e2aab8fce738ca6489a35a51ca4..42b51ef14514f3c3df454e4b718718a9b58f1612 100644 (file)
--- a/parse-options.c
+++ b/parse-options.c
#define OPT_SHORT 1
#define OPT_UNSET 2
+static int optbug(const struct option *opt, const char *reason)
+{
+ if (opt->long_name)
+ return error("BUG: option '%s' %s", opt->long_name, reason);
+ return error("BUG: switch '%c' %s", opt->short_name, reason);
+}
+
static int opterror(const struct option *opt, const char *reason, int flags)
{
if (flags & OPT_SHORT)
return opterror(opt, "takes no value", flags);
if (unset && (opt->flags & PARSE_OPT_NONEG))
return opterror(opt, "isn't available", flags);
-
- if (!(flags & OPT_SHORT) && p->opt) {
- switch (opt->type) {
- case OPTION_CALLBACK:
- if (!(opt->flags & PARSE_OPT_NOARG))
- break;
- /* FALLTHROUGH */
- case OPTION_BOOLEAN:
- case OPTION_BIT:
- case OPTION_NEGBIT:
- case OPTION_SET_INT:
- case OPTION_SET_PTR:
- return opterror(opt, "takes no value", flags);
- default:
- break;
- }
- }
+ if (!(flags & OPT_SHORT) && p->opt && (opt->flags & PARSE_OPT_NOARG))
+ return opterror(opt, "takes no value", flags);
switch (opt->type) {
+ case OPTION_LOWLEVEL_CALLBACK:
+ return (*(parse_opt_ll_cb *)opt->callback)(p, opt, unset);
+
case OPTION_BIT:
if (unset)
*(int *)opt->value &= ~opt->defval;
for (; options->type != OPTION_END; options++) {
if (!(options->flags & PARSE_OPT_NODASH))
continue;
- if ((options->flags & PARSE_OPT_OPTARG) ||
- !(options->flags & PARSE_OPT_NOARG))
- die("BUG: dashless options don't support arguments");
- if (!(options->flags & PARSE_OPT_NONEG))
- die("BUG: dashless options don't support negation");
- if (options->long_name)
- die("BUG: dashless options can't be long");
if (options->short_name == arg[0] && arg[1] == '\0')
return get_value(p, options, OPT_SHORT);
}
for (; opts->type != OPTION_END; opts++) {
if ((opts->flags & PARSE_OPT_LASTARG_DEFAULT) &&
- (opts->flags & PARSE_OPT_OPTARG)) {
- if (opts->long_name) {
- error("`--%s` uses incompatible flags "
- "LASTARG_DEFAULT and OPTARG", opts->long_name);
- } else {
- error("`-%c` uses incompatible flags "
- "LASTARG_DEFAULT and OPTARG", opts->short_name);
- }
- err |= 1;
+ (opts->flags & PARSE_OPT_OPTARG))
+ err |= optbug(opts, "uses incompatible flags "
+ "LASTARG_DEFAULT and OPTARG");
+ if (opts->flags & PARSE_OPT_NODASH &&
+ ((opts->flags & PARSE_OPT_OPTARG) ||
+ !(opts->flags & PARSE_OPT_NOARG) ||
+ !(opts->flags & PARSE_OPT_NONEG) ||
+ opts->long_name))
+ err |= optbug(opts, "uses feature "
+ "not supported for dashless options");
+ switch (opts->type) {
+ case OPTION_BOOLEAN:
+ case OPTION_BIT:
+ case OPTION_NEGBIT:
+ case OPTION_SET_INT:
+ case OPTION_SET_PTR:
+ case OPTION_NUMBER:
+ if ((opts->flags & PARSE_OPT_OPTARG) ||
+ !(opts->flags & PARSE_OPT_NOARG))
+ err |= optbug(opts, "should not accept an argument");
+ default:
+ ; /* ok. (usually accepts an argument) */
}
}
-
if (err)
- exit(129);
+ exit(128);
}
void parse_options_start(struct parse_opt_ctx_t *ctx,
int argc, const char **argv, const char *prefix,
- int flags)
+ const struct option *options, int flags)
{
memset(ctx, 0, sizeof(*ctx));
ctx->argc = argc - 1;
if ((flags & PARSE_OPT_KEEP_UNKNOWN) &&
(flags & PARSE_OPT_STOP_AT_NON_OPTION))
die("STOP_AT_NON_OPTION and KEEP_UNKNOWN don't go together");
+ parse_options_check(options);
}
static int usage_with_options_internal(struct parse_opt_ctx_t *,
{
int internal_help = !(ctx->flags & PARSE_OPT_NO_INTERNAL_HELP);
- parse_options_check(options);
-
/* we must reset ->opt, unknown short option leave it dangling */
ctx->opt = NULL;
if (parse_nodash_opt(ctx, arg, options) == 0)
continue;
if (ctx->flags & PARSE_OPT_STOP_AT_NON_OPTION)
- break;
+ return PARSE_OPT_NON_OPTION;
ctx->out[ctx->cpidx++] = ctx->argv[0];
continue;
}
{
struct parse_opt_ctx_t ctx;
- parse_options_start(&ctx, argc, argv, prefix, flags);
+ parse_options_start(&ctx, argc, argv, prefix, options, flags);
switch (parse_options_step(&ctx, options, usagestr)) {
case PARSE_OPT_HELP:
exit(129);
+ case PARSE_OPT_NON_OPTION:
case PARSE_OPT_DONE:
break;
default: /* PARSE_OPT_UNKNOWN */
if (opts->type == OPTION_NUMBER)
pos += fprintf(outfile, "-NUM");
- if (!(opts->flags & PARSE_OPT_NOARG))
+ if ((opts->flags & PARSE_OPT_LITERAL_ARGHELP) ||
+ !(opts->flags & PARSE_OPT_NOARG))
pos += usage_argh(opts, outfile);
if (pos <= USAGE_OPTS_WIDTH)
diff --git a/parse-options.h b/parse-options.h
index d982f0f1bf181f00f54861eb768504007490e95d..d1b12fe979b34ac9bbf2ff637934b96f3878a53d 100644 (file)
--- a/parse-options.h
+++ b/parse-options.h
OPTION_STRING,
OPTION_INTEGER,
OPTION_CALLBACK,
+ OPTION_LOWLEVEL_CALLBACK,
OPTION_FILENAME
};
struct option;
typedef int parse_opt_cb(const struct option *, const char *arg, int unset);
+struct parse_opt_ctx_t;
+typedef int parse_opt_ll_cb(struct parse_opt_ctx_t *ctx,
+ const struct option *opt, int unset);
+
/*
* `type`::
* holds the type of the option, you must have an OPTION_END last in your
* useful for users of OPTION_NEGBIT.
*
* `callback`::
- * pointer to the callback to use for OPTION_CALLBACK.
+ * pointer to the callback to use for OPTION_CALLBACK or
+ * OPTION_LOWLEVEL_CALLBACK.
*
* `defval`::
* default value to fill (*->value) with for PARSE_OPT_OPTARG.
{ OPTION_NUMBER, 0, NULL, (v), NULL, (h), \
PARSE_OPT_NOARG | PARSE_OPT_NONEG, (f) }
#define OPT_FILENAME(s, l, v, h) { OPTION_FILENAME, (s), (l), (v), \
- "FILE", (h) }
+ "file", (h) }
#define OPT_COLOR_FLAG(s, l, v, h) \
{ OPTION_CALLBACK, (s), (l), (v), "when", (h), PARSE_OPT_OPTARG, \
parse_opt_color_flag_cb, (intptr_t)"always" }
enum {
PARSE_OPT_HELP = -1,
PARSE_OPT_DONE,
+ PARSE_OPT_NON_OPTION,
PARSE_OPT_UNKNOWN
};
extern void parse_options_start(struct parse_opt_ctx_t *ctx,
int argc, const char **argv, const char *prefix,
- int flags);
+ const struct option *options, int flags);
extern int parse_options_step(struct parse_opt_ctx_t *ctx,
const struct option *options,
extern int parse_opt_with_commit(const struct option *, const char *, int);
extern int parse_opt_tertiary(const struct option *, const char *, int);
-#define OPT__VERBOSE(var) OPT_BOOLEAN('v', "verbose", (var), "be verbose")
-#define OPT__QUIET(var) OPT_BOOLEAN('q', "quiet", (var), "be quiet")
+#define OPT__VERBOSE(var, h) OPT_BOOLEAN('v', "verbose", (var), (h))
+#define OPT__QUIET(var, h) OPT_BOOLEAN('q', "quiet", (var), (h))
#define OPT__VERBOSITY(var) \
{ OPTION_CALLBACK, 'v', "verbose", (var), NULL, "be more verbose", \
PARSE_OPT_NOARG, &parse_opt_verbosity_cb, 0 }, \
{ OPTION_CALLBACK, 'q', "quiet", (var), NULL, "be more quiet", \
PARSE_OPT_NOARG, &parse_opt_verbosity_cb, 0 }
-#define OPT__DRY_RUN(var) OPT_BOOLEAN('n', "dry-run", (var), "dry run")
+#define OPT__DRY_RUN(var, h) OPT_BOOLEAN('n', "dry-run", (var), (h))
+#define OPT__FORCE(var, h) OPT_BOOLEAN('f', "force", (var), (h))
#define OPT__ABBREV(var) \
{ OPTION_CALLBACK, 0, "abbrev", (var), "n", \
"use <n> digits to display SHA-1s", \
diff --git a/patch-delta.c b/patch-delta.c
index d218faa02bd12b0e6a0df298a6a0e5787e46d93f..56e0a5ede22c9396fc897bf1d3444dce92d8916f 100644 (file)
--- a/patch-delta.c
+++ b/patch-delta.c
if (cmd & 0x20) cp_size |= (*data++ << 8);
if (cmd & 0x40) cp_size |= (*data++ << 16);
if (cp_size == 0) cp_size = 0x10000;
- if (cp_off + cp_size < cp_size ||
+ if (unsigned_add_overflows(cp_off, cp_size) ||
cp_off + cp_size > src_size ||
cp_size > size)
break;
index a2c9d1e24afb9d1250c846791607c49d82e9565e..8951333cb88a6e28ef29538183ebf71e3b0913fa 100644 (file)
--- a/path.c
+++ b/path.c
return cleanup_path(pathname);
}
-/* git_mkstemp() - create tmp file honoring TMPDIR variable */
-int git_mkstemp(char *path, size_t len, const char *template)
-{
- const char *tmp;
- size_t n;
-
- tmp = getenv("TMPDIR");
- if (!tmp)
- tmp = "/tmp";
- n = snprintf(path, len, "%s/%s", tmp, template);
- if (len <= n) {
- errno = ENAMETOOLONG;
- return -1;
- }
- return mkstemp(path);
-}
-
-/* git_mkstemps() - create tmp file with suffix honoring TMPDIR variable. */
-int git_mkstemps(char *path, size_t len, const char *template, int suffix_len)
-{
- const char *tmp;
- size_t n;
-
- tmp = getenv("TMPDIR");
- if (!tmp)
- tmp = "/tmp";
- n = snprintf(path, len, "%s/%s", tmp, template);
- if (len <= n) {
- errno = ENAMETOOLONG;
- return -1;
- }
- return mkstemps(path, suffix_len);
-}
-
-/* Adapted from libiberty's mkstemp.c. */
-
-#undef TMP_MAX
-#define TMP_MAX 16384
-
-int git_mkstemps_mode(char *pattern, int suffix_len, int mode)
-{
- static const char letters[] =
- "abcdefghijklmnopqrstuvwxyz"
- "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
- "0123456789";
- static const int num_letters = 62;
- uint64_t value;
- struct timeval tv;
- char *template;
- size_t len;
- int fd, count;
-
- len = strlen(pattern);
-
- if (len < 6 + suffix_len) {
- errno = EINVAL;
- return -1;
- }
-
- if (strncmp(&pattern[len - 6 - suffix_len], "XXXXXX", 6)) {
- errno = EINVAL;
- return -1;
- }
-
- /*
- * Replace pattern's XXXXXX characters with randomness.
- * Try TMP_MAX different filenames.
- */
- gettimeofday(&tv, NULL);
- value = ((size_t)(tv.tv_usec << 16)) ^ tv.tv_sec ^ getpid();
- template = &pattern[len - 6 - suffix_len];
- for (count = 0; count < TMP_MAX; ++count) {
- uint64_t v = value;
- /* Fill in the random bits. */
- template[0] = letters[v % num_letters]; v /= num_letters;
- template[1] = letters[v % num_letters]; v /= num_letters;
- template[2] = letters[v % num_letters]; v /= num_letters;
- template[3] = letters[v % num_letters]; v /= num_letters;
- template[4] = letters[v % num_letters]; v /= num_letters;
- template[5] = letters[v % num_letters]; v /= num_letters;
-
- fd = open(pattern, O_CREAT | O_EXCL | O_RDWR, mode);
- if (fd > 0)
- return fd;
- /*
- * Fatal error (EPERM, ENOSPC etc).
- * It doesn't make sense to loop.
- */
- if (errno != EEXIST)
- break;
- /*
- * This is a random value. It is only necessary that
- * the next TMP_MAX values generated by adding 7777 to
- * VALUE are different with (module 2^32).
- */
- value += 7777;
- }
- /* We return the null string if we can't find a unique file name. */
- pattern[0] = '\0';
- return -1;
-}
-
-int git_mkstemp_mode(char *pattern, int mode)
-{
- /* mkstemp is just mkstemps with no suffix */
- return git_mkstemps_mode(pattern, 0, mode);
-}
-
-int gitmkstemps(char *pattern, int suffix_len)
-{
- return git_mkstemps_mode(pattern, suffix_len, 0600);
-}
-
int validate_headref(const char *path)
{
struct stat st;
diff --git a/perl/Git.pm b/perl/Git.pm
index 205e48aa3a7b912005565ac9c296205a897a1476..a86ab709c25b5e110aa2708941cea560f82c1fa8 100644 (file)
--- a/perl/Git.pm
+++ b/perl/Git.pm
use Carp qw(carp croak); # but croak is bad - throw instead
use Error qw(:try);
-use Cwd qw(abs_path);
+use Cwd qw(abs_path cwd);
use IPC::Open2 qw(open2);
use Fcntl qw(SEEK_SET SEEK_CUR);
}
sub command_bidi_pipe {
my ($pid, $in, $out);
+ my ($self) = _maybe_self(@_);
+ local %ENV = %ENV;
+ my $cwd_save = undef;
+ if ($self) {
+ shift;
+ $cwd_save = cwd();
+ _setup_git_cmd_env($self);
+ }
$pid = open2($in, $out, 'git', @_);
+ chdir($cwd_save) if $cwd_save;
return ($pid, $in, $out, join(' ', @_));
}
($self->{hash_object_pid}, $self->{hash_object_in},
$self->{hash_object_out}, $self->{hash_object_ctx}) =
- command_bidi_pipe(qw(hash-object -w --stdin-paths --no-filters));
+ $self->command_bidi_pipe(qw(hash-object -w --stdin-paths --no-filters));
}
sub _close_hash_and_insert_object {
($self->{cat_blob_pid}, $self->{cat_blob_in},
$self->{cat_blob_out}, $self->{cat_blob_ctx}) =
- command_bidi_pipe(qw(cat-file --batch));
+ $self->command_bidi_pipe(qw(cat-file --batch));
}
sub _close_cat_blob {
# for the given repository and execute the git command.
sub _cmd_exec {
my ($self, @args) = @_;
+ _setup_git_cmd_env($self);
+ _execv_git_cmd(@args);
+ die qq[exec "@args" failed: $!];
+}
+
+# set up the appropriate state for git command
+sub _setup_git_cmd_env {
+ my $self = shift;
if ($self) {
$self->repo_path() and $ENV{'GIT_DIR'} = $self->repo_path();
$self->repo_path() and $self->wc_path()
$self->wc_path() and chdir($self->wc_path());
$self->wc_subdir() and chdir($self->wc_subdir());
}
- _execv_git_cmd(@args);
- die qq[exec "@args" failed: $!];
}
# Execute the given Git command ($_[0]) with arguments ($_[1..])
diff --git a/preload-index.c b/preload-index.c
index e3d0bda31a98372eb9b6a8c2cd0fd65917a9dbde..49cb08df96faa90101bb25d8f96acaffc066f19b 100644 (file)
--- a/preload-index.c
+++ b/preload-index.c
struct index_state *index = p->index;
struct cache_entry **cep = index->cache + p->offset;
struct cache_def cache;
+ struct pathspec pathspec;
+ init_pathspec(&pathspec, p->pathspec);
memset(&cache, 0, sizeof(cache));
nr = p->nr;
if (nr + p->offset > index->cache_nr)
continue;
if (ce_uptodate(ce))
continue;
- if (!ce_path_match(ce, p->pathspec))
+ if (!ce_path_match(ce, &pathspec))
continue;
if (threaded_has_symlink_leading_path(&cache, ce->name, ce_namelen(ce)))
continue;
continue;
ce_mark_uptodate(ce);
} while (--nr > 0);
+ free_pathspec(&pathspec);
return NULL;
}
diff --git a/pretty.c b/pretty.c
index f85444b27d4815547f7cef542291b1f6616f77d5..85499347514ec3e1d62d6caa9f53b886c556e345 100644 (file)
--- a/pretty.c
+++ b/pretty.c
return strbuf_detach(&tmp, NULL);
}
-static char *logmsg_reencode(const struct commit *commit,
- const char *output_encoding)
+char *logmsg_reencode(const struct commit *commit,
+ const char *output_encoding)
{
static const char *utf8 = "UTF-8";
const char *use_encoding;
const struct pretty_print_context *pretty_ctx;
unsigned commit_header_parsed:1;
unsigned commit_message_parsed:1;
+ char *message;
size_t width, indent1, indent2;
/* These offsets are relative to the start of the commit message. */
static void parse_commit_header(struct format_commit_context *context)
{
- const char *msg = context->commit->buffer;
+ const char *msg = context->message;
int i;
for (i = 0; msg[i]; i++) {
static void parse_commit_message(struct format_commit_context *c)
{
- const char *msg = c->commit->buffer + c->message_off;
- const char *start = c->commit->buffer;
+ const char *msg = c->message + c->message_off;
+ const char *start = c->message;
msg = skip_empty_lines(msg);
c->subject_off = msg - start;
{
struct format_commit_context *c = context;
const struct commit *commit = c->commit;
- const char *msg = commit->buffer;
+ const char *msg = c->message;
struct commit_list *p;
int h1, h2;
case 'N':
if (c->pretty_ctx->show_notes) {
format_display_notes(commit->object.sha1, sb,
- git_log_output_encoding ? git_log_output_encoding
- : git_commit_encoding, 0);
+ get_log_output_encoding(), 0);
return 1;
}
return 0;
const struct pretty_print_context *pretty_ctx)
{
struct format_commit_context context;
+ static const char utf8[] = "UTF-8";
+ const char *enc;
+ const char *output_enc = pretty_ctx->output_encoding;
memset(&context, 0, sizeof(context));
context.commit = commit;
context.pretty_ctx = pretty_ctx;
context.wrap_start = sb->len;
+ context.message = commit->buffer;
+ if (output_enc) {
+ enc = get_header(commit, "encoding");
+ enc = enc ? enc : utf8;
+ if (strcmp(enc, output_enc))
+ context.message = logmsg_reencode(commit, output_enc);
+ }
+
strbuf_expand(sb, format, format_commit_item, &context);
rewrap_message_tail(sb, &context, 0, 0, 0);
+
+ if (context.message != commit->buffer)
+ free(context.message);
}
static void pp_header(enum cmit_fmt fmt,
@@ -1159,11 +1173,7 @@ char *reencode_commit_message(const struct commit *commit, const char **encoding
{
const char *encoding;
- encoding = (git_log_output_encoding
- ? git_log_output_encoding
- : git_commit_encoding);
- if (!encoding)
- encoding = "UTF-8";
+ encoding = get_log_output_encoding();
if (encoding_p)
*encoding_p = encoding;
return logmsg_reencode(commit, encoding);
index 38003bff5f97a11e051b106522a8548d43d24f6c..024e21d80ceaa37ec1f893acc907a62092560d21 100644 (file)
--- a/quote.h
+++ b/quote.h
#ifndef QUOTE_H
#define QUOTE_H
-#include <stddef.h>
-#include <stdio.h>
+struct strbuf;
/* Help to copy the thing properly quoted for the shell safety.
* any single quote is replaced with '\'', any exclamation point
diff --git a/read-cache.c b/read-cache.c
index 1f42473e8070a05ada8c56b0d60537227a5223ec..98d526bd48d2a0e764dd0efb4c6efe4965174c9a 100644 (file)
--- a/read-cache.c
+++ b/read-cache.c
if (fd >= 0) {
unsigned char sha1[20];
- if (!index_fd(sha1, fd, st, 0, OBJ_BLOB, ce->name))
+ if (!index_fd(sha1, fd, st, 0, OBJ_BLOB, ce->name, 0))
match = hashcmp(sha1, ce->sha1);
/* index_fd() closed the file descriptor already */
}
@@ -608,6 +608,29 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,
ce->ce_mode = ce_mode_from_stat(ent, st_mode);
}
+ /* When core.ignorecase=true, determine if a directory of the same name but differing
+ * case already exists within the Git repository. If it does, ensure the directory
+ * case of the file being added to the repository matches (is folded into) the existing
+ * entry's directory case.
+ */
+ if (ignore_case) {
+ const char *startPtr = ce->name;
+ const char *ptr = startPtr;
+ while (*ptr) {
+ while (*ptr && *ptr != '/')
+ ++ptr;
+ if (*ptr == '/') {
+ struct cache_entry *foundce;
+ ++ptr;
+ foundce = index_name_exists(&the_index, ce->name, ptr - ce->name, ignore_case);
+ if (foundce) {
+ memcpy((void *)startPtr, foundce->name + (startPtr - ce->name), ptr - startPtr);
+ startPtr = ptr;
+ }
+ }
+ }
+ }
+
alias = index_name_exists(istate, ce->name, ce_namelen(ce), ignore_case);
if (alias && !ce_stage(alias) && !ie_match_stat(istate, alias, st, ce_option)) {
/* Nothing changed, really */
return ce_namelen(b) == len && !memcmp(a->name, b->name, len);
}
-int ce_path_match(const struct cache_entry *ce, const char **pathspec)
+int ce_path_match(const struct cache_entry *ce, const struct pathspec *pathspec)
{
- const char *match, *name;
- int len;
-
- if (!pathspec)
- return 1;
-
- len = ce_namelen(ce);
- name = ce->name;
- while ((match = *pathspec++) != NULL) {
- int matchlen = strlen(match);
- if (matchlen > len)
- continue;
- if (memcmp(name, match, matchlen))
- continue;
- if (matchlen && name[matchlen-1] == '/')
- return 1;
- if (name[matchlen] == '/' || !name[matchlen])
- return 1;
- if (!matchlen)
- return 1;
- }
- return 0;
+ return match_pathspec_depth(pathspec, ce->name, ce_namelen(ce), 0, NULL);
}
/*
}
static void show_file(const char * fmt, const char * name, int in_porcelain,
- int * first, char *header_msg)
+ int * first, const char *header_msg)
{
if (in_porcelain && *first && header_msg) {
printf("%s\n", header_msg);
}
int refresh_index(struct index_state *istate, unsigned int flags, const char **pathspec,
- char *seen, char *header_msg)
+ char *seen, const char *header_msg)
{
int i;
int has_errors = 0;
diff --git a/remote-curl.c b/remote-curl.c
index 04d4813e4183c675b54aba942cd078d8ff632053..d0fb0a044a85ec6169c4623c7c2ab2143dbb5784 100644 (file)
--- a/remote-curl.c
+++ b/remote-curl.c
return size;
}
+static int run_slot(struct active_request_slot *slot)
+{
+ int err = 0;
+ struct slot_results results;
+
+ slot->results = &results;
+ slot->curl_result = curl_easy_perform(slot->curl);
+ finish_active_slot(slot);
+
+ if (results.curl_result != CURLE_OK) {
+ err |= error("RPC failed; result=%d, HTTP code = %ld",
+ results.curl_result, results.http_code);
+ }
+
+ return err;
+}
+
+static int probe_rpc(struct rpc_state *rpc)
+{
+ struct active_request_slot *slot;
+ struct curl_slist *headers = NULL;
+ struct strbuf buf = STRBUF_INIT;
+ int err;
+
+ slot = get_active_slot();
+
+ headers = curl_slist_append(headers, rpc->hdr_content_type);
+ headers = curl_slist_append(headers, rpc->hdr_accept);
+
+ curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 0);
+ curl_easy_setopt(slot->curl, CURLOPT_POST, 1);
+ curl_easy_setopt(slot->curl, CURLOPT_URL, rpc->service_url);
+ curl_easy_setopt(slot->curl, CURLOPT_ENCODING, "");
+ curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDS, "0000");
+ curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDSIZE, 4);
+ curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, headers);
+ curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
+ curl_easy_setopt(slot->curl, CURLOPT_FILE, &buf);
+
+ err = run_slot(slot);
+
+ curl_slist_free_all(headers);
+ strbuf_release(&buf);
+ return err;
+}
+
static int post_rpc(struct rpc_state *rpc)
{
struct active_request_slot *slot;
- struct slot_results results;
struct curl_slist *headers = NULL;
int use_gzip = rpc->gzip_request;
char *gzip_body = NULL;
- int err = 0, large_request = 0;
+ int err, large_request = 0;
/* Try to load the entire request, if we can fit it into the
* allocated buffer space we can use HTTP/1.0 and avoid the
rpc->len += n;
}
+ if (large_request) {
+ err = probe_rpc(rpc);
+ if (err)
+ return err;
+ }
+
slot = get_active_slot();
- slot->results = &results;
curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 0);
curl_easy_setopt(slot->curl, CURLOPT_POST, 1);
/* The request body is large and the size cannot be predicted.
* We must use chunked encoding to send it.
*/
- headers = curl_slist_append(headers, "Expect: 100-continue");
+ headers = curl_slist_append(headers, "Expect:");
headers = curl_slist_append(headers, "Transfer-Encoding: chunked");
rpc->initial_buffer = 1;
curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, rpc_out);
curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, rpc_in);
curl_easy_setopt(slot->curl, CURLOPT_FILE, rpc);
- slot->curl_result = curl_easy_perform(slot->curl);
- finish_active_slot(slot);
-
- if (results.curl_result != CURLE_OK) {
- err |= error("RPC failed; result=%d, HTTP code = %ld",
- results.curl_result, results.http_code);
- }
+ err = run_slot(slot);
curl_slist_free_all(headers);
free(gzip_body);
diff --git a/remote.c b/remote.c
index 9143ec7a1772c45b6c3d4c84f6fc000040400dee..ca42a126ad04514f0ed8378768ebce98cfc5a659 100644 (file)
--- a/remote.c
+++ b/remote.c
}
/*
- * We need to make sure the tracking branches are well formed, but a
+ * We need to make sure the remote-tracking branches are well formed, but a
* wildcard refspec in "struct refspec" must have a trailing slash. We
* temporarily drop the trailing '/' while calling check_ref_format(),
* and put it back. The caller knows that a CHECK_REF_FORMAT_ONELEVEL
diff --git a/rerere.c b/rerere.c
index 861ca7c815b4857f1fde399617860e5a179e2ca3..22996bd08b532fb8016f24391dd0e63010706385 100644 (file)
--- a/rerere.c
+++ b/rerere.c
#include "ll-merge.h"
#include "attr.h"
+#define RESOLVED 0
+#define PUNTED 1
+#define THREE_STAGED 2
+void *RERERE_RESOLVED = &RERERE_RESOLVED;
+
/* if rerere_enabled == -1, fall back to detection of .git/rr-cache */
static int rerere_enabled = -1;
@@ -325,7 +330,7 @@ static int handle_cache(const char *path, unsigned char *sha1, const char *outpu
*/
ll_merge(&result, path, &mmfile[0], NULL,
&mmfile[1], "ours",
- &mmfile[2], "theirs", 0);
+ &mmfile[2], "theirs", NULL);
for (i = 0; i < 3; i++)
free(mmfile[i].ptr);
@@ -345,21 +350,74 @@ static int handle_cache(const char *path, unsigned char *sha1, const char *outpu
return hunk_no;
}
-static int find_conflict(struct string_list *conflict)
+static int check_one_conflict(int i, int *type)
{
- int i;
- if (read_cache() < 0)
- return error("Could not read index");
- for (i = 0; i+1 < active_nr; i++) {
+ struct cache_entry *e = active_cache[i];
+
+ if (!ce_stage(e)) {
+ *type = RESOLVED;
+ return i + 1;
+ }
+
+ *type = PUNTED;
+ if (ce_stage(e) == 1) {
+ if (active_nr <= ++i)
+ return i + 1;
+ }
+
+ /* Only handle regular files with both stages #2 and #3 */
+ if (i + 1 < active_nr) {
struct cache_entry *e2 = active_cache[i];
- struct cache_entry *e3 = active_cache[i+1];
+ struct cache_entry *e3 = active_cache[i + 1];
if (ce_stage(e2) == 2 &&
ce_stage(e3) == 3 &&
- ce_same_name(e2, e3) &&
+ ce_same_name(e, e3) &&
S_ISREG(e2->ce_mode) &&
- S_ISREG(e3->ce_mode)) {
- string_list_insert(conflict, (const char *)e2->name);
- i++; /* skip over both #2 and #3 */
+ S_ISREG(e3->ce_mode))
+ *type = THREE_STAGED;
+ }
+
+ /* Skip the entries with the same name */
+ while (i < active_nr && ce_same_name(e, active_cache[i]))
+ i++;
+ return i;
+}
+
+static int find_conflict(struct string_list *conflict)
+{
+ int i;
+ if (read_cache() < 0)
+ return error("Could not read index");
+
+ for (i = 0; i < active_nr;) {
+ int conflict_type;
+ struct cache_entry *e = active_cache[i];
+ i = check_one_conflict(i, &conflict_type);
+ if (conflict_type == THREE_STAGED)
+ string_list_insert(conflict, (const char *)e->name);
+ }
+ return 0;
+}
+
+int rerere_remaining(struct string_list *merge_rr)
+{
+ int i;
+ if (read_cache() < 0)
+ return error("Could not read index");
+
+ for (i = 0; i < active_nr;) {
+ int conflict_type;
+ struct cache_entry *e = active_cache[i];
+ i = check_one_conflict(i, &conflict_type);
+ if (conflict_type == PUNTED)
+ string_list_insert(merge_rr, (const char *)e->name);
+ else if (conflict_type == RESOLVED) {
+ struct string_list_item *it;
+ it = string_list_lookup(merge_rr, (const char *)e->name);
+ if (it != NULL) {
+ free(it->util);
+ it->util = RERERE_RESOLVED;
+ }
}
}
return 0;
diff --git a/rerere.h b/rerere.h
index eaa9004dcdbb2f0dfd446e1ac68237c6dff18530..595f49f701dc1003927679085ca6eea8ad1083ae 100644 (file)
--- a/rerere.h
+++ b/rerere.h
#define RERERE_AUTOUPDATE 01
#define RERERE_NOAUTOUPDATE 02
+/*
+ * Marks paths that have been hand-resolved and added to the
+ * index. Set in the util field of such paths after calling
+ * rerere_remaining.
+ */
+extern void *RERERE_RESOLVED;
+
extern int setup_rerere(struct string_list *, int);
extern int rerere(int);
extern const char *rerere_path(const char *hex, const char *file);
extern int has_rerere_resolution(const char *hex);
extern int rerere_forget(const char **);
+extern int rerere_remaining(struct string_list *);
#define OPT_RERERE_AUTOUPDATE(v) OPT_UYN(0, "rerere-autoupdate", (v), \
"update the index with reused conflict resolution if possible")
diff --git a/revision.c b/revision.c
index ded881263ba0ff703d9fd468e6785270ea68df5f..86d24704896d71de7bb554a3925ea88e2be3417b 100644 (file)
--- a/revision.c
+++ b/revision.c
@@ -323,7 +323,7 @@ static int rev_compare_tree(struct rev_info *revs, struct commit *parent, struct
* tagged commit by specifying both --simplify-by-decoration
* and pathspec.
*/
- if (!revs->prune_data)
+ if (!revs->prune_data.nr)
return REV_TREE_SAME;
}
@@ -444,15 +444,15 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
commit->object.flags |= TREESAME;
}
-static void insert_by_date_cached(struct commit *p, struct commit_list **head,
+static void commit_list_insert_by_date_cached(struct commit *p, struct commit_list **head,
struct commit_list *cached_base, struct commit_list **cache)
{
struct commit_list *new_entry;
if (cached_base && p->date < cached_base->item->date)
- new_entry = insert_by_date(p, &cached_base->next);
+ new_entry = commit_list_insert_by_date(p, &cached_base->next);
else
- new_entry = insert_by_date(p, head);
+ new_entry = commit_list_insert_by_date(p, head);
if (cache && (!*cache || p->date < (*cache)->item->date))
*cache = new_entry;
if (p->object.flags & SEEN)
continue;
p->object.flags |= SEEN;
- insert_by_date_cached(p, list, cached_base, cache_ptr);
+ commit_list_insert_by_date_cached(p, list, cached_base, cache_ptr);
}
return 0;
}
p->object.flags |= left_flag;
if (!(p->object.flags & SEEN)) {
p->object.flags |= SEEN;
- insert_by_date_cached(p, list, cached_base, cache_ptr);
+ commit_list_insert_by_date_cached(p, list, cached_base, cache_ptr);
}
if (revs->first_parent_only)
break;
left_first = left_count < right_count;
init_patch_ids(&ids);
- if (revs->diffopt.nr_paths) {
- ids.diffopts.nr_paths = revs->diffopt.nr_paths;
- ids.diffopts.paths = revs->diffopt.paths;
- ids.diffopts.pathlens = revs->diffopt.pathlens;
- }
+ ids.diffopts.pathspec = revs->diffopt.pathspec;
/* Compute patch-ids for one side */
for (p = list; p; p = p->next) {
struct cache_entry *ce = active_cache[i];
if (!ce_stage(ce))
continue;
- if (ce_path_match(ce, revs->prune_data)) {
+ if (ce_path_match(ce, &revs->prune_data)) {
prune_num++;
prune = xrealloc(prune, sizeof(*prune) * prune_num);
prune[prune_num-2] = ce->name;
ce_same_name(ce, active_cache[i+1]))
i++;
}
- revs->prune_data = prune;
+ free_pathspec(&revs->prune_data);
+ init_pathspec(&revs->prune_data, prune);
revs->limited = 1;
}
@@ -1620,7 +1617,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
}
if (prune_data)
- revs->prune_data = get_pathspec(revs->prefix, prune_data);
+ init_pathspec(&revs->prune_data, get_pathspec(revs->prefix, prune_data));
if (revs->def == NULL)
revs->def = opt ? opt->def : NULL;
@@ -1651,13 +1648,13 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
if (revs->topo_order)
revs->limited = 1;
- if (revs->prune_data) {
- diff_tree_setup_paths(revs->prune_data, &revs->pruning);
+ if (revs->prune_data.nr) {
+ diff_tree_setup_paths(revs->prune_data.raw, &revs->pruning);
/* Can't prune commits with rename following: the paths change.. */
if (!DIFF_OPT_TST(&revs->diffopt, FOLLOW_RENAMES))
revs->prune = 1;
if (!revs->full_diff)
- diff_tree_setup_paths(revs->prune_data, &revs->diffopt);
+ diff_tree_setup_paths(revs->prune_data.raw, &revs->diffopt);
}
if (revs->combine_merges)
revs->ignore_merges = 0;
if (commit) {
if (!(commit->object.flags & SEEN)) {
commit->object.flags |= SEEN;
- insert_by_date(commit, &revs->commits);
+ commit_list_insert_by_date(commit, &revs->commits);
}
}
e++;
diff --git a/revision.h b/revision.h
index 05659c64acd7fe8eb7be011cee6174e397e57baf..82509dd1d9ad7b1824971be4884409060f10aee3 100644 (file)
--- a/revision.h
+++ b/revision.h
/* Basic information */
const char *prefix;
const char *def;
- void *prune_data;
+ struct pathspec prune_data;
unsigned int early_output;
/* Traversal flags */
diff --git a/run-command.c b/run-command.c
index 2a1041ef6599c84fff6a8d9faf5dea23a2af3ab0..f91e446c86be8e27f98554567143d7ce6f934bd1 100644 (file)
--- a/run-command.c
+++ b/run-command.c
}
trace_argv_printf(cmd->argv, "trace: run_command:");
+ fflush(NULL);
#ifndef WIN32
{
if (pipe(notify_pipe))
notify_pipe[0] = notify_pipe[1] = -1;
- fflush(NULL);
cmd->pid = fork();
if (!cmd->pid) {
/*
diff --git a/send-pack.h b/send-pack.h
index 60b4ba66eb8cac3378326378dc4e0cbdb88162ac..05d7ab118b3e1473cdf559d3f6337b26bb9aac81 100644 (file)
--- a/send-pack.h
+++ b/send-pack.h
unsigned verbose:1,
quiet:1,
porcelain:1,
+ progress:1,
send_mirror:1,
force_update:1,
use_thin_pack:1,
index 346ef2eb2d678b742280c0816a09b8ca174ca799..021d0133ae1d6cf7f3a6a8284cb5b6e9a42dbe0d 100644 (file)
--- a/setup.c
+++ b/setup.c
static int inside_git_dir = -1;
static int inside_work_tree = -1;
-const char *prefix_path(const char *prefix, int len, const char *path)
+char *prefix_path(const char *prefix, int len, const char *path)
{
const char *orig = path;
- char *sanitized = xmalloc(len + strlen(path) + 1);
- if (is_absolute_path(orig))
- strcpy(sanitized, path);
- else {
+ char *sanitized;
+ if (is_absolute_path(orig)) {
+ const char *temp = make_absolute_path(path);
+ sanitized = xmalloc(len + strlen(temp) + 1);
+ strcpy(sanitized, temp);
+ } else {
+ sanitized = xmalloc(len + strlen(path) + 1);
if (len)
memcpy(sanitized, prefix, len);
strcpy(sanitized + len, path);
return inside_work_tree;
}
-/*
- * set_work_tree() is only ever called if you set GIT_DIR explicitly.
- * The old behaviour (which we retain here) is to set the work tree root
- * to the cwd, unless overridden by the config, the command line, or
- * GIT_WORK_TREE.
- */
-static const char *set_work_tree(const char *dir)
-{
- char buffer[PATH_MAX + 1];
-
- if (!getcwd(buffer, sizeof(buffer)))
- die ("Could not get the current working directory");
- git_work_tree_cfg = xstrdup(buffer);
- inside_work_tree = 1;
-
- return NULL;
-}
-
void setup_work_tree(void)
{
const char *work_tree, *git_dir;
git_dir = make_absolute_path(git_dir);
if (!work_tree || chdir(work_tree))
die("This operation must be run in a work tree");
+
+ /*
+ * Make sure subsequent git processes find correct worktree
+ * if $GIT_WORK_TREE is set relative
+ */
+ if (getenv(GIT_WORK_TREE_ENVIRONMENT))
+ setenv(GIT_WORK_TREE_ENVIRONMENT, ".", 1);
+
set_git_dir(make_relative_path(git_dir, work_tree));
initialized = 1;
}
-static int check_repository_format_gently(int *nongit_ok)
+static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
{
- git_config(check_repository_format_version, NULL);
+ char repo_config[PATH_MAX+1];
+
+ /*
+ * git_config() can't be used here because it calls git_pathdup()
+ * to get $GIT_CONFIG/config. That call will make setup_git_env()
+ * set git_dir to ".git".
+ *
+ * We are in gitdir setup, no git dir has been found useable yet.
+ * Use a gentler version of git_config() to check if this repo
+ * is a good one.
+ */
+ snprintf(repo_config, PATH_MAX, "%s/config", gitdir);
+ git_config_early(check_repository_format_version, NULL, repo_config);
if (GIT_REPO_VERSION < repository_format_version) {
if (!nongit_ok)
die ("Expected git repo version <= %d, found %d",
}
static const char *setup_explicit_git_dir(const char *gitdirenv,
- const char *work_tree_env, int *nongit_ok)
+ char *cwd, int len,
+ int *nongit_ok)
{
- static char buffer[1024 + 1];
- const char *retval;
+ const char *work_tree_env = getenv(GIT_WORK_TREE_ENVIRONMENT);
+ const char *worktree;
+ char *gitfile;
if (PATH_MAX - 40 < strlen(gitdirenv))
die("'$%s' too big", GIT_DIR_ENVIRONMENT);
+
+ gitfile = (char*)read_gitfile_gently(gitdirenv);
+ if (gitfile) {
+ gitfile = xstrdup(gitfile);
+ gitdirenv = gitfile;
+ }
+
if (!is_git_directory(gitdirenv)) {
if (nongit_ok) {
*nongit_ok = 1;
+ free(gitfile);
return NULL;
}
die("Not a git repository: '%s'", gitdirenv);
}
- if (!work_tree_env) {
- retval = set_work_tree(gitdirenv);
- /* config may override worktree */
- if (check_repository_format_gently(nongit_ok))
- return NULL;
- return retval;
+
+ if (check_repository_format_gently(gitdirenv, nongit_ok)) {
+ free(gitfile);
+ return NULL;
}
- if (check_repository_format_gently(nongit_ok))
+
+ /* #3, #7, #11, #15, #19, #23, #27, #31 (see t1510) */
+ if (work_tree_env)
+ set_git_work_tree(work_tree_env);
+ else if (is_bare_repository_cfg > 0) {
+ if (git_work_tree_cfg) /* #22.2, #30 */
+ die("core.bare and core.worktree do not make sense");
+
+ /* #18, #26 */
+ set_git_dir(gitdirenv);
+ free(gitfile);
return NULL;
- retval = get_relative_cwd(buffer, sizeof(buffer) - 1,
- get_git_work_tree());
- if (!retval || !*retval)
+ }
+ else if (git_work_tree_cfg) { /* #6, #14 */
+ if (is_absolute_path(git_work_tree_cfg))
+ set_git_work_tree(git_work_tree_cfg);
+ else {
+ char core_worktree[PATH_MAX];
+ if (chdir(gitdirenv))
+ die_errno("Could not chdir to '%s'", gitdirenv);
+ if (chdir(git_work_tree_cfg))
+ die_errno("Could not chdir to '%s'", git_work_tree_cfg);
+ if (!getcwd(core_worktree, PATH_MAX))
+ die_errno("Could not get directory '%s'", git_work_tree_cfg);
+ if (chdir(cwd))
+ die_errno("Could not come back to cwd");
+ set_git_work_tree(core_worktree);
+ }
+ }
+ else /* #2, #10 */
+ set_git_work_tree(".");
+
+ /* set_git_work_tree() must have been called by now */
+ worktree = get_git_work_tree();
+
+ /* both get_git_work_tree() and cwd are already normalized */
+ if (!strcmp(cwd, worktree)) { /* cwd == worktree */
+ set_git_dir(gitdirenv);
+ free(gitfile);
return NULL;
- set_git_dir(make_absolute_path(gitdirenv));
- if (chdir(work_tree_env) < 0)
- die_errno ("Could not chdir to '%s'", work_tree_env);
- strcat(buffer, "/");
- return retval;
-}
+ }
-static int cwd_contains_git_dir(const char **gitfile_dirp)
-{
- const char *gitfile_dir = read_gitfile_gently(DEFAULT_GIT_DIR_ENVIRONMENT);
- *gitfile_dirp = gitfile_dir;
- if (gitfile_dir) {
- if (set_git_dir(gitfile_dir))
- die("Repository setup failed");
- return 1;
+ if (!prefixcmp(cwd, worktree) &&
+ cwd[strlen(worktree)] == '/') { /* cwd inside worktree */
+ set_git_dir(make_absolute_path(gitdirenv));
+ if (chdir(worktree))
+ die_errno("Could not chdir to '%s'", worktree);
+ cwd[len++] = '/';
+ cwd[len] = '\0';
+ free(gitfile);
+ return cwd + strlen(worktree) + 1;
}
- return is_git_directory(DEFAULT_GIT_DIR_ENVIRONMENT);
+
+ /* cwd outside worktree */
+ set_git_dir(gitdirenv);
+ free(gitfile);
+ return NULL;
}
-static const char *setup_discovered_git_dir(const char *work_tree_env,
- int offset, int len, char *cwd, int *nongit_ok)
+static const char *setup_discovered_git_dir(const char *gitdir,
+ char *cwd, int offset, int len,
+ int *nongit_ok)
{
- int root_len;
+ if (check_repository_format_gently(gitdir, nongit_ok))
+ return NULL;
- inside_git_dir = 0;
- if (!work_tree_env)
- inside_work_tree = 1;
- root_len = offset_1st_component(cwd);
- git_work_tree_cfg = xstrndup(cwd, offset > root_len ? offset : root_len);
- if (check_repository_format_gently(nongit_ok))
+ /* --work-tree is set without --git-dir; use discovered one */
+ if (getenv(GIT_WORK_TREE_ENVIRONMENT) || git_work_tree_cfg) {
+ if (offset != len && !is_absolute_path(gitdir))
+ gitdir = xstrdup(make_absolute_path(gitdir));
+ if (chdir(cwd))
+ die_errno("Could not come back to cwd");
+ return setup_explicit_git_dir(gitdir, cwd, len, nongit_ok);
+ }
+
+ /* #16.2, #17.2, #20.2, #21.2, #24, #25, #28, #29 (see t1510) */
+ if (is_bare_repository_cfg > 0) {
+ set_git_dir(offset == len ? gitdir : make_absolute_path(gitdir));
+ if (chdir(cwd))
+ die_errno("Could not come back to cwd");
return NULL;
+ }
+
+ /* #0, #1, #5, #8, #9, #12, #13 */
+ set_git_work_tree(".");
+ if (strcmp(gitdir, DEFAULT_GIT_DIR_ENVIRONMENT))
+ set_git_dir(gitdir);
+ inside_git_dir = 0;
+ inside_work_tree = 1;
if (offset == len)
return NULL;
return cwd + offset;
}
-static const char *setup_bare_git_dir(const char *work_tree_env,
- int offset, int len, char *cwd, int *nongit_ok)
+/* #16.1, #17.1, #20.1, #21.1, #22.1 (see t1510) */
+static const char *setup_bare_git_dir(char *cwd, int offset, int len, int *nongit_ok)
{
int root_len;
+ if (check_repository_format_gently(".", nongit_ok))
+ return NULL;
+
+ /* --work-tree is set without --git-dir; use discovered one */
+ if (getenv(GIT_WORK_TREE_ENVIRONMENT) || git_work_tree_cfg) {
+ const char *gitdir;
+
+ gitdir = offset == len ? "." : xmemdupz(cwd, offset);
+ if (chdir(cwd))
+ die_errno("Could not come back to cwd");
+ return setup_explicit_git_dir(gitdir, cwd, len, nongit_ok);
+ }
+
inside_git_dir = 1;
- if (!work_tree_env)
- inside_work_tree = 0;
+ inside_work_tree = 0;
if (offset != len) {
if (chdir(cwd))
die_errno("Cannot come back to cwd");
root_len = offset_1st_component(cwd);
cwd[offset > root_len ? offset : root_len] = '\0';
set_git_dir(cwd);
- } else
+ }
+ else
set_git_dir(".");
- check_repository_format_gently(nongit_ok);
return NULL;
}
*/
static const char *setup_git_directory_gently_1(int *nongit_ok)
{
- const char *work_tree_env = getenv(GIT_WORK_TREE_ENVIRONMENT);
const char *env_ceiling_dirs = getenv(CEILING_DIRECTORIES_ENVIRONMENT);
static char cwd[PATH_MAX+1];
- const char *gitdirenv;
- const char *gitfile_dir;
+ const char *gitdirenv, *ret;
+ char *gitfile;
int len, offset, ceil_offset;
dev_t current_device = 0;
int one_filesystem = 1;
if (nongit_ok)
*nongit_ok = 0;
+ if (!getcwd(cwd, sizeof(cwd)-1))
+ die_errno("Unable to read current working directory");
+ offset = len = strlen(cwd);
+
/*
* If GIT_DIR is set explicitly, we're not going
* to do any discovery, but we still do repository
*/
gitdirenv = getenv(GIT_DIR_ENVIRONMENT);
if (gitdirenv)
- return setup_explicit_git_dir(gitdirenv, work_tree_env, nongit_ok);
-
- if (!getcwd(cwd, sizeof(cwd)-1))
- die_errno("Unable to read current working directory");
+ return setup_explicit_git_dir(gitdirenv, cwd, len, nongit_ok);
ceil_offset = longest_ancestor_length(cwd, env_ceiling_dirs);
if (ceil_offset < 0 && has_dos_drive_prefix(cwd))
* - ../../.git/
* etc.
*/
- offset = len = strlen(cwd);
one_filesystem = !git_env_bool("GIT_DISCOVERY_ACROSS_FILESYSTEM", 0);
if (one_filesystem)
current_device = get_device_or_die(".", NULL);
for (;;) {
- if (cwd_contains_git_dir(&gitfile_dir))
- return setup_discovered_git_dir(work_tree_env, offset,
- len, cwd, nongit_ok);
+ gitfile = (char*)read_gitfile_gently(DEFAULT_GIT_DIR_ENVIRONMENT);
+ if (gitfile)
+ gitdirenv = gitfile = xstrdup(gitfile);
+ else {
+ if (is_git_directory(DEFAULT_GIT_DIR_ENVIRONMENT))
+ gitdirenv = DEFAULT_GIT_DIR_ENVIRONMENT;
+ }
+
+ if (gitdirenv) {
+ ret = setup_discovered_git_dir(gitdirenv,
+ cwd, offset, len,
+ nongit_ok);
+ free(gitfile);
+ return ret;
+ }
+ free(gitfile);
+
if (is_git_directory("."))
- return setup_bare_git_dir(work_tree_env, offset,
- len, cwd, nongit_ok);
+ return setup_bare_git_dir(cwd, offset, len, nongit_ok);
+
while (--offset > ceil_offset && cwd[offset] != '/');
if (offset <= ceil_offset)
return setup_nongit(cwd, nongit_ok);
const char *prefix;
prefix = setup_git_directory_gently_1(nongit_ok);
- if (startup_info)
+ if (startup_info) {
startup_info->have_repository = !nongit_ok || !*nongit_ok;
+ startup_info->prefix = prefix;
+ }
return prefix;
}
@@ -590,7 +682,7 @@ int check_repository_format_version(const char *var, const char *value, void *cb
int check_repository_format(void)
{
- return check_repository_format_gently(NULL);
+ return check_repository_format_gently(get_git_dir(), NULL);
}
/*
*/
const char *setup_git_directory(void)
{
- const char *retval = setup_git_directory_gently(NULL);
-
- /* If the work tree is not the default one, recompute prefix */
- if (inside_work_tree < 0) {
- static char buffer[PATH_MAX + 1];
- char *rel;
- if (retval && chdir(retval))
- die_errno ("Could not jump back into original cwd");
- rel = get_relative_cwd(buffer, PATH_MAX, get_git_work_tree());
- if (rel && *rel && chdir(get_git_work_tree()))
- die_errno ("Could not jump to working directory");
- return rel && *rel ? strcat(rel, "/") : NULL;
- }
-
- return retval;
+ return setup_git_directory_gently(NULL);
}
diff --git a/sha1_file.c b/sha1_file.c
index 0cd9435619f1e0637584289b45d52c1cdd8a9460..d949b35c3308ae5c06774da7f4fecdc84778d5eb 100644 (file)
--- a/sha1_file.c
+++ b/sha1_file.c
#include "commit.h"
#include "tag.h"
#include "tree.h"
+#include "tree-walk.h"
#include "refs.h"
#include "pack-revindex.h"
#include "sha1-lookup.h"
const unsigned char null_sha1[20];
+static int git_open_noatime(const char *name, struct packed_git *p);
+
+/*
+ * This is meant to hold a *small* number of objects that you would
+ * want read_sha1_file() to be able to return, but yet you do not want
+ * to write them into the object store (e.g. a browse-only
+ * application).
+ */
+static struct cached_object {
+ unsigned char sha1[20];
+ enum object_type type;
+ void *buf;
+ unsigned long size;
+} *cached_objects;
+static int cached_object_nr, cached_object_alloc;
+
+static struct cached_object empty_tree = {
+ EMPTY_TREE_SHA1_BIN_LITERAL,
+ OBJ_TREE,
+ "",
+ 0
+};
+
+static struct cached_object *find_cached_object(const unsigned char *sha1)
+{
+ int i;
+ struct cached_object *co = cached_objects;
+
+ for (i = 0; i < cached_object_nr; i++, co++) {
+ if (!hashcmp(co->sha1, sha1))
+ return co;
+ }
+ if (!hashcmp(sha1, empty_tree.sha1))
+ return &empty_tree;
+ return NULL;
+}
+
int safe_create_leading_directories(char *path)
{
char *pos = path + offset_1st_component(path);
int fd;
sprintf(path, "%s/%s", relative_base, alt_file_name);
- fd = open(path, O_RDONLY);
+ fd = git_open_noatime(path, NULL);
if (fd < 0)
return;
if (fstat(fd, &st) || (st.st_size == 0)) {
struct pack_idx_header *hdr;
size_t idx_size;
uint32_t version, nr, i, *index;
- int fd = open(path, O_RDONLY);
+ int fd = git_open_noatime(path, p);
struct stat st;
if (fd < 0)
; /* nothing */
}
+void *xmmap(void *start, size_t length,
+ int prot, int flags, int fd, off_t offset)
+{
+ void *ret = mmap(start, length, prot, flags, fd, offset);
+ if (ret == MAP_FAILED) {
+ if (!length)
+ return NULL;
+ release_pack_memory(length, fd);
+ ret = mmap(start, length, prot, flags, fd, offset);
+ if (ret == MAP_FAILED)
+ die_errno("Out of memory? mmap failed");
+ }
+ return ret;
+}
+
void close_pack_windows(struct packed_git *p)
{
while (p->windows) {
if (!p->index_data && open_pack_index(p))
return error("packfile %s index unavailable", p->pack_name);
- p->pack_fd = open(p->pack_name, O_RDONLY);
- while (p->pack_fd < 0 && errno == EMFILE && unuse_one_window(p, -1))
- p->pack_fd = open(p->pack_name, O_RDONLY);
+ p->pack_fd = git_open_noatime(p->pack_name, p);
if (p->pack_fd < 0 || fstat(p->pack_fd, &st))
return -1;
return p;
}
+static void try_to_free_pack_memory(size_t size)
+{
+ release_pack_memory(size, -1);
+}
+
struct packed_git *add_packed_git(const char *path, int path_len, int local)
{
+ static int have_set_try_to_free_routine;
struct stat st;
struct packed_git *p = alloc_packed_git(path_len + 2);
+ if (!have_set_try_to_free_routine) {
+ have_set_try_to_free_routine = 1;
+ set_try_to_free_routine(try_to_free_pack_memory);
+ }
+
/*
* Make sure a corresponding .pack file exists and that
* the index looks sane.
sprintf(path, "%s/pack", objdir);
len = strlen(path);
dir = opendir(path);
- while (!dir && errno == EMFILE && unuse_one_window(packed_git, -1))
+ while (!dir && errno == EMFILE && unuse_one_window(NULL, -1))
dir = opendir(path);
if (!dir) {
if (errno != ENOENT)
p->num_bad_objects++;
}
-static int has_packed_and_bad(const unsigned char *sha1)
+static const struct packed_git *has_packed_and_bad(const unsigned char *sha1)
{
struct packed_git *p;
unsigned i;
for (p = packed_git; p; p = p->next)
for (i = 0; i < p->num_bad_objects; i++)
if (!hashcmp(sha1, p->bad_object_sha1 + 20 * i))
- return 1;
- return 0;
+ return p;
+ return NULL;
}
int check_sha1_signature(const unsigned char *sha1, void *map, unsigned long size, const char *type)
@@ -1022,18 +1084,31 @@ int check_sha1_signature(const unsigned char *sha1, void *map, unsigned long siz
return hashcmp(sha1, real_sha1) ? -1 : 0;
}
-static int git_open_noatime(const char *name)
+static int git_open_noatime(const char *name, struct packed_git *p)
{
static int sha1_file_open_flag = O_NOATIME;
- int fd = open(name, O_RDONLY | sha1_file_open_flag);
- /* Might the failure be due to O_NOATIME? */
- if (fd < 0 && errno != ENOENT && sha1_file_open_flag) {
- fd = open(name, O_RDONLY);
+ for (;;) {
+ int fd = open(name, O_RDONLY | sha1_file_open_flag);
if (fd >= 0)
+ return fd;
+
+ /* Might the failure be insufficient file descriptors? */
+ if (errno == EMFILE) {
+ if (unuse_one_window(p, -1))
+ continue;
+ else
+ return -1;
+ }
+
+ /* Might the failure be due to O_NOATIME? */
+ if (errno != ENOENT && sha1_file_open_flag) {
sha1_file_open_flag = 0;
+ continue;
+ }
+
+ return -1;
}
- return fd;
}
static int open_sha1_file(const unsigned char *sha1)
char *name = sha1_file_name(sha1);
struct alternate_object_database *alt;
- fd = git_open_noatime(name);
+ fd = git_open_noatime(name, NULL);
if (fd >= 0)
return fd;
for (alt = alt_odb_list; alt; alt = alt->next) {
name = alt->name;
fill_sha1_path(name, sha1);
- fd = git_open_noatime(alt->base);
+ fd = git_open_noatime(alt->base, NULL);
if (fd >= 0)
return fd;
}
@@ -1946,9 +2021,17 @@ static int sha1_loose_object_info(const unsigned char *sha1, unsigned long *size
int sha1_object_info(const unsigned char *sha1, unsigned long *sizep)
{
+ struct cached_object *co;
struct pack_entry e;
int status;
+ co = find_cached_object(sha1);
+ if (co) {
+ if (sizep)
+ *sizep = co->size;
+ return co->type;
+ }
+
if (!find_pack_entry(sha1, &e)) {
/* Most likely it's a loose object. */
status = sha1_loose_object_info(sha1, sizep);
return data;
}
-/*
- * This is meant to hold a *small* number of objects that you would
- * want read_sha1_file() to be able to return, but yet you do not want
- * to write them into the object store (e.g. a browse-only
- * application).
- */
-static struct cached_object {
- unsigned char sha1[20];
- enum object_type type;
- void *buf;
- unsigned long size;
-} *cached_objects;
-static int cached_object_nr, cached_object_alloc;
-
-static struct cached_object empty_tree = {
- EMPTY_TREE_SHA1_BIN,
- OBJ_TREE,
- "",
- 0
-};
-
-static struct cached_object *find_cached_object(const unsigned char *sha1)
-{
- int i;
- struct cached_object *co = cached_objects;
-
- for (i = 0; i < cached_object_nr; i++, co++) {
- if (!hashcmp(co->sha1, sha1))
- return co;
- }
- if (!hashcmp(sha1, empty_tree.sha1))
- return &empty_tree;
- return NULL;
-}
-
int pretend_sha1_file(void *buf, unsigned long len, enum object_type type,
unsigned char *sha1)
{
return read_packed_sha1(sha1, type, size);
}
+/*
+ * This function dies on corrupt objects; the callers who want to
+ * deal with them should arrange to call read_object() and give error
+ * messages themselves.
+ */
void *read_sha1_file_repl(const unsigned char *sha1,
enum object_type *type,
unsigned long *size,
const unsigned char **replacement)
{
const unsigned char *repl = lookup_replace_object(sha1);
- void *data = read_object(repl, type, size);
+ void *data;
char *path;
+ const struct packed_git *p;
+
+ errno = 0;
+ data = read_object(repl, type, size);
+ if (data) {
+ if (replacement)
+ *replacement = repl;
+ return data;
+ }
+
+ if (errno && errno != ENOENT)
+ die_errno("failed to read object %s", sha1_to_hex(sha1));
/* die if we replaced an object with one that does not exist */
- if (!data && repl != sha1)
+ if (repl != sha1)
die("replacement %s not found for %s",
sha1_to_hex(repl), sha1_to_hex(sha1));
- /* legacy behavior is to die on corrupted objects */
- if (!data) {
- if (has_loose_object(repl)) {
- path = sha1_file_name(sha1);
- die("loose object %s (stored in %s) is corrupted", sha1_to_hex(repl), path);
- }
- if (has_packed_and_bad(repl)) {
- path = sha1_pack_name(sha1);
- die("packed object %s (stored in %s) is corrupted", sha1_to_hex(repl), path);
- }
+ if (has_loose_object(repl)) {
+ path = sha1_file_name(sha1);
+ die("loose object %s (stored in %s) is corrupt",
+ sha1_to_hex(repl), path);
}
- if (replacement)
- *replacement = repl;
+ if ((p = has_packed_and_bad(repl)) != NULL)
+ die("packed object %s (stored in %s) is corrupt",
+ sha1_to_hex(repl), p->pack_name);
- return data;
+ return NULL;
}
void *read_object_with_reference(const unsigned char *sha1,
@@ -2300,7 +2360,7 @@ static int write_loose_object(const unsigned char *sha1, char *hdr, int hdrlen,
filename = sha1_file_name(sha1);
fd = create_tmpfile(tmpfile, sizeof(tmpfile), filename);
- while (fd < 0 && errno == EMFILE && unuse_one_window(packed_git, -1))
+ while (fd < 0 && errno == EMFILE && unuse_one_window(NULL, -1))
fd = create_tmpfile(tmpfile, sizeof(tmpfile), filename);
if (fd < 0) {
if (errno == EACCES)
return has_loose_object(sha1);
}
+static void check_tree(const void *buf, size_t size)
+{
+ struct tree_desc desc;
+ struct name_entry entry;
+
+ init_tree_desc(&desc, buf, size);
+ while (tree_entry(&desc, &entry))
+ /* do nothing
+ * tree_entry() will die() on malformed entries */
+ ;
+}
+
+static void check_commit(const void *buf, size_t size)
+{
+ struct commit c;
+ memset(&c, 0, sizeof(c));
+ if (parse_commit_buffer(&c, buf, size))
+ die("corrupt commit");
+}
+
+static void check_tag(const void *buf, size_t size)
+{
+ struct tag t;
+ memset(&t, 0, sizeof(t));
+ if (parse_tag_buffer(&t, buf, size))
+ die("corrupt tag");
+}
+
static int index_mem(unsigned char *sha1, void *buf, size_t size,
- int write_object, enum object_type type, const char *path)
+ int write_object, enum object_type type,
+ const char *path, int format_check)
{
int ret, re_allocated = 0;
re_allocated = 1;
}
}
+ if (format_check) {
+ if (type == OBJ_TREE)
+ check_tree(buf, size);
+ if (type == OBJ_COMMIT)
+ check_commit(buf, size);
+ if (type == OBJ_TAG)
+ check_tag(buf, size);
+ }
if (write_object)
ret = write_sha1_file(buf, size, typename(type), sha1);
#define SMALL_FILE_SIZE (32*1024)
int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object,
- enum object_type type, const char *path)
+ enum object_type type, const char *path, int format_check)
{
int ret;
size_t size = xsize_t(st->st_size);
@@ -2461,23 +2558,25 @@ int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object,
struct strbuf sbuf = STRBUF_INIT;
if (strbuf_read(&sbuf, fd, 4096) >= 0)
ret = index_mem(sha1, sbuf.buf, sbuf.len, write_object,
- type, path);
+ type, path, format_check);
else
ret = -1;
strbuf_release(&sbuf);
} else if (!size) {
- ret = index_mem(sha1, NULL, size, write_object, type, path);
+ ret = index_mem(sha1, NULL, size, write_object, type, path,
+ format_check);
} else if (size <= SMALL_FILE_SIZE) {
char *buf = xmalloc(size);
if (size == read_in_full(fd, buf, size))
ret = index_mem(sha1, buf, size, write_object, type,
- path);
+ path, format_check);
else
ret = error("short read %s", strerror(errno));
free(buf);
} else {
void *buf = xmmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
- ret = index_mem(sha1, buf, size, write_object, type, path);
+ ret = index_mem(sha1, buf, size, write_object, type, path,
+ format_check);
munmap(buf, size);
}
close(fd);
@@ -2495,7 +2594,7 @@ int index_path(unsigned char *sha1, const char *path, struct stat *st, int write
if (fd < 0)
return error("open(\"%s\"): %s", path,
strerror(errno));
- if (index_fd(sha1, fd, st, write_object, OBJ_BLOB, path) < 0)
+ if (index_fd(sha1, fd, st, write_object, OBJ_BLOB, path, 0) < 0)
return error("%s: failed to insert into database",
path);
break;
diff --git a/sha1_name.c b/sha1_name.c
index 3e856b80369c289a3d9399d60ec3be22bbbc6788..709ff2eee64cf106191ad274bede82a95d00e2a3 100644 (file)
--- a/sha1_name.c
+++ b/sha1_name.c
#include "refs.h"
#include "remote.h"
+static int get_sha1_oneline(const char *, unsigned char *, struct commit_list *);
+
static int find_short_object_filename(int len, const char *name, unsigned char *sha1)
{
struct alternate_object_database *alt;
if (exists
? !status
: status == SHORT_NAME_NOT_FOUND) {
- hex[len] = 0;
+ int cut_at = len + unique_abbrev_extra_length;
+ cut_at = (cut_at < 40) ? cut_at : 40;
+ hex[cut_at] = 0;
return hex;
}
len++;
expected_type = OBJ_BLOB;
else if (sp[0] == '}')
expected_type = OBJ_NONE;
+ else if (sp[0] == '/')
+ expected_type = OBJ_COMMIT;
else
return -1;
if (!o || (!o->parsed && !parse_object(o->sha1)))
return -1;
hashcpy(sha1, o->sha1);
+ return 0;
}
- else {
+
+ /*
+ * At this point, the syntax look correct, so
+ * if we do not get the needed object, we should
+ * barf.
+ */
+ o = peel_to_type(name, len, o, expected_type);
+ if (!o)
+ return -1;
+
+ hashcpy(sha1, o->sha1);
+ if (sp[0] == '/') {
+ /* "$commit^{/foo}" */
+ char *prefix;
+ int ret;
+ struct commit_list *list = NULL;
+
/*
- * At this point, the syntax look correct, so
- * if we do not get the needed object, we should
- * barf.
+ * $commit^{/}. Some regex implementation may reject.
+ * We don't need regex anyway. '' pattern always matches.
*/
- o = peel_to_type(name, len, o, expected_type);
- if (o) {
- hashcpy(sha1, o->sha1);
+ if (sp[1] == '}')
return 0;
- }
- return -1;
+
+ prefix = xstrndup(sp + 1, name + len - 1 - (sp + 1));
+ commit_list_insert((struct commit *)o, &list);
+ ret = get_sha1_oneline(prefix, sha1, list);
+ free(prefix);
+ return ret;
}
return 0;
}
}
if (object->type != OBJ_COMMIT)
return 0;
- insert_by_date((struct commit *)object, list);
- object->flags |= ONELINE_SEEN;
+ commit_list_insert_by_date((struct commit *)object, list);
return 0;
}
-static int get_sha1_oneline(const char *prefix, unsigned char *sha1)
+static int get_sha1_oneline(const char *prefix, unsigned char *sha1,
+ struct commit_list *list)
{
- struct commit_list *list = NULL, *backup = NULL, *l;
- int retval = -1;
- char *temp_commit_buffer = NULL;
+ struct commit_list *backup = NULL, *l;
+ int found = 0;
regex_t regex;
if (prefix[0] == '!') {
if (regcomp(®ex, prefix, REG_EXTENDED))
die("Invalid search pattern: %s", prefix);
- for_each_ref(handle_one_ref, &list);
- for (l = list; l; l = l->next)
+ for (l = list; l; l = l->next) {
+ l->item->object.flags |= ONELINE_SEEN;
commit_list_insert(l->item, &backup);
+ }
while (list) {
- char *p;
+ char *p, *to_free = NULL;
struct commit *commit;
enum object_type type;
unsigned long size;
+ int matches;
commit = pop_most_recent_commit(&list, ONELINE_SEEN);
if (!parse_object(commit->object.sha1))
continue;
- free(temp_commit_buffer);
if (commit->buffer)
p = commit->buffer;
else {
p = read_sha1_file(commit->object.sha1, &type, &size);
if (!p)
continue;
- temp_commit_buffer = p;
+ to_free = p;
}
- if (!(p = strstr(p, "\n\n")))
- continue;
- if (!regexec(®ex, p + 2, 0, NULL, 0)) {
+
+ p = strstr(p, "\n\n");
+ matches = p && !regexec(®ex, p + 2, 0, NULL, 0);
+ free(to_free);
+
+ if (matches) {
hashcpy(sha1, commit->object.sha1);
- retval = 0;
+ found = 1;
break;
}
}
regfree(®ex);
- free(temp_commit_buffer);
free_commit_list(list);
for (l = backup; l; l = l->next)
clear_commit_marks(l->item, ONELINE_SEEN);
- return retval;
+ free_commit_list(backup);
+ return found ? 0 : -1;
}
struct grab_nth_branch_switch_cbdata {
return len;
}
+int strbuf_branchname(struct strbuf *sb, const char *name)
+{
+ int len = strlen(name);
+ if (interpret_branch_name(name, sb) == len)
+ return 0;
+ strbuf_add(sb, name, len);
+ return len;
+}
+
+int strbuf_check_branch_ref(struct strbuf *sb, const char *name)
+{
+ strbuf_branchname(sb, name);
+ if (name[0] == '-')
+ return CHECK_REF_FORMAT_ERROR;
+ strbuf_splice(sb, 0, 0, "refs/heads/", 11);
+ return check_ref_format(sb->buf);
+}
+
/*
* This is like "get_sha1_basic()", except it allows "sha1 expressions",
* notably "xyz^" for "parent of xyz"
@@ -1046,6 +1091,23 @@ int get_sha1_with_mode_1(const char *name, unsigned char *sha1, unsigned *mode,
return ret;
}
+static char *resolve_relative_path(const char *rel)
+{
+ if (prefixcmp(rel, "./") && prefixcmp(rel, "../"))
+ return NULL;
+
+ if (!startup_info)
+ die("BUG: startup_info struct is not initialized.");
+
+ if (!is_inside_work_tree())
+ die("relative path syntax can't be used outside working tree.");
+
+ /* die() inside prefix_path() if resolved path is outside worktree */
+ return prefix_path(startup_info->prefix,
+ startup_info->prefix ? strlen(startup_info->prefix) : 0,
+ rel);
+}
+
int get_sha1_with_context_1(const char *name, unsigned char *sha1,
struct object_context *oc,
int gently, const char *prefix)
if (!ret)
return ret;
/* sha1:path --> object name of path in ent sha1
- * :path -> object name of path in index
+ * :path -> object name of absolute path in index
+ * :./path -> object name of path relative to cwd in index
* :[0-3]:path -> object name of path in index at stage
* :/foo -> recent commit matching foo
*/
if (name[0] == ':') {
int stage = 0;
struct cache_entry *ce;
+ char *new_path = NULL;
int pos;
- if (namelen > 2 && name[1] == '/')
- /* don't need mode for commit */
- return get_sha1_oneline(name + 2, sha1);
+ if (namelen > 2 && name[1] == '/') {
+ struct commit_list *list = NULL;
+ for_each_ref(handle_one_ref, &list);
+ return get_sha1_oneline(name + 2, sha1, list);
+ }
if (namelen < 3 ||
name[2] != ':' ||
name[1] < '0' || '3' < name[1])
stage = name[1] - '0';
cp = name + 3;
}
- namelen = namelen - (cp - name);
+ new_path = resolve_relative_path(cp);
+ if (!new_path) {
+ namelen = namelen - (cp - name);
+ } else {
+ cp = new_path;
+ namelen = strlen(cp);
+ }
strncpy(oc->path, cp,
sizeof(oc->path));
if (ce_stage(ce) == stage) {
hashcpy(sha1, ce->sha1);
oc->mode = ce->ce_mode;
+ free(new_path);
return 0;
}
pos++;
}
if (!gently)
diagnose_invalid_index_path(stage, prefix, cp);
+ free(new_path);
return -1;
}
for (cp = name, bracket_depth = 0; *cp; cp++) {
}
if (!get_sha1_1(name, cp-name, tree_sha1)) {
const char *filename = cp+1;
+ char *new_filename = NULL;
+
+ new_filename = resolve_relative_path(filename);
+ if (new_filename)
+ filename = new_filename;
ret = get_tree_entry(tree_sha1, filename, sha1, &oc->mode);
if (!gently) {
diagnose_invalid_sha1_path(prefix, filename,
sizeof(oc->path));
oc->path[sizeof(oc->path)-1] = '\0';
+ free(new_filename);
return ret;
} else {
if (!gently)
index e4864e04da3b0e237342c2ca0548c0ec0082c171..dea4cfdd2c230af6afd6233cacfa5c776a9962f9 100644 (file)
--- a/shell.c
+++ b/shell.c
#include "quote.h"
#include "exec_cmd.h"
#include "strbuf.h"
+#include "run-command.h"
+
+#define COMMAND_DIR "git-shell-commands"
+#define HELP_COMMAND COMMAND_DIR "/help"
static int do_generic_cmd(const char *me, char *arg)
{
return execv_git_cmd(cvsserver_argv);
}
+static int is_valid_cmd_name(const char *cmd)
+{
+ /* Test command contains no . or / characters */
+ return cmd[strcspn(cmd, "./")] == '\0';
+}
+
+static char *make_cmd(const char *prog)
+{
+ char *prefix = xmalloc((strlen(prog) + strlen(COMMAND_DIR) + 2));
+ strcpy(prefix, COMMAND_DIR);
+ strcat(prefix, "/");
+ strcat(prefix, prog);
+ return prefix;
+}
+
+static void cd_to_homedir(void)
+{
+ const char *home = getenv("HOME");
+ if (!home)
+ die("could not determine user's home directory; HOME is unset");
+ if (chdir(home) == -1)
+ die("could not chdir to user's home directory");
+}
+
+static void run_shell(void)
+{
+ int done = 0;
+ static const char *help_argv[] = { HELP_COMMAND, NULL };
+ /* Print help if enabled */
+ run_command_v_opt(help_argv, RUN_SILENT_EXEC_FAILURE);
+
+ do {
+ struct strbuf line = STRBUF_INIT;
+ const char *prog;
+ char *full_cmd;
+ char *rawargs;
+ char *split_args;
+ const char **argv;
+ int code;
+ int count;
+
+ fprintf(stderr, "git> ");
+ if (strbuf_getline(&line, stdin, '\n') == EOF) {
+ fprintf(stderr, "\n");
+ strbuf_release(&line);
+ break;
+ }
+ strbuf_trim(&line);
+ rawargs = strbuf_detach(&line, NULL);
+ split_args = xstrdup(rawargs);
+ count = split_cmdline(split_args, &argv);
+ if (count < 0) {
+ fprintf(stderr, "invalid command format '%s': %s\n", rawargs,
+ split_cmdline_strerror(count));
+ free(split_args);
+ free(rawargs);
+ continue;
+ }
+
+ prog = argv[0];
+ if (!strcmp(prog, "")) {
+ } else if (!strcmp(prog, "quit") || !strcmp(prog, "logout") ||
+ !strcmp(prog, "exit") || !strcmp(prog, "bye")) {
+ done = 1;
+ } else if (is_valid_cmd_name(prog)) {
+ full_cmd = make_cmd(prog);
+ argv[0] = full_cmd;
+ code = run_command_v_opt(argv, RUN_SILENT_EXEC_FAILURE);
+ if (code == -1 && errno == ENOENT) {
+ fprintf(stderr, "unrecognized command '%s'\n", prog);
+ }
+ free(full_cmd);
+ } else {
+ fprintf(stderr, "invalid command format '%s'\n", prog);
+ }
+
+ free(argv);
+ free(rawargs);
+ } while (!done);
+}
static struct commands {
const char *name;
int main(int argc, char **argv)
{
char *prog;
+ const char **user_argv;
struct commands *cmd;
int devnull_fd;
+ int count;
/*
* Always open file descriptors 0/1/2 to avoid clobbering files
/*
* Special hack to pretend to be a CVS server
*/
- if (argc == 2 && !strcmp(argv[1], "cvs server"))
+ if (argc == 2 && !strcmp(argv[1], "cvs server")) {
argv--;
+ } else if (argc == 1) {
+ /* Allow the user to run an interactive shell */
+ cd_to_homedir();
+ if (access(COMMAND_DIR, R_OK | X_OK) == -1) {
+ die("Interactive git shell is not enabled.\n"
+ "hint: ~/" COMMAND_DIR " should exist "
+ "and have read and execute access.");
+ }
+ run_shell();
+ exit(0);
+ } else if (argc != 3 || strcmp(argv[1], "-c")) {
+ /*
+ * We do not accept any other modes except "-c" followed by
+ * "cmd arg", where "cmd" is a very limited subset of git
+ * commands or a command in the COMMAND_DIR
+ */
+ die("Run with no arguments or with -c cmd");
+ }
- /*
- * We do not accept anything but "-c" followed by "cmd arg",
- * where "cmd" is a very limited subset of git commands.
- */
- else if (argc != 3 || strcmp(argv[1], "-c"))
- die("What do you think I am? A shell?");
-
- prog = argv[2];
+ prog = xstrdup(argv[2]);
if (!strncmp(prog, "git", 3) && isspace(prog[3]))
/* Accept "git foo" as if the caller said "git-foo". */
prog[3] = '-';
}
exit(cmd->exec(cmd->name, arg));
}
- die("unrecognized command '%s'", prog);
+
+ cd_to_homedir();
+ count = split_cmdline(prog, &user_argv);
+ if (count >= 0) {
+ if (is_valid_cmd_name(user_argv[0])) {
+ prog = make_cmd(user_argv[0]);
+ user_argv[0] = prog;
+ execv(user_argv[0], (char *const *) user_argv);
+ }
+ free(prog);
+ free(user_argv);
+ die("unrecognized command '%s'", argv[2]);
+ } else {
+ free(prog);
+ die("invalid command format '%s': %s", argv[2],
+ split_cmdline_strerror(count));
+ }
}
diff --git a/strbuf.c b/strbuf.c
index bc3a0802ea7e7b1743602972de182391b4bf0b3f..07e8883ceb297bdde79f06ffa2d0519581ee60b9 100644 (file)
--- a/strbuf.c
+++ b/strbuf.c
void strbuf_grow(struct strbuf *sb, size_t extra)
{
- if (sb->len + extra + 1 <= sb->len)
+ if (unsigned_add_overflows(extra, 1) ||
+ unsigned_add_overflows(sb->len, extra + 1))
die("you want to use way too much memory");
if (!sb->alloc)
sb->buf = NULL;
void strbuf_splice(struct strbuf *sb, size_t pos, size_t len,
const void *data, size_t dlen)
{
- if (pos + len < pos)
+ if (unsigned_add_overflows(pos, len))
die("you want to use way too much memory");
if (pos > sb->len)
die("`pos' is too far after the end of the buffer");
return len;
}
-
-int strbuf_branchname(struct strbuf *sb, const char *name)
-{
- int len = strlen(name);
- if (interpret_branch_name(name, sb) == len)
- return 0;
- strbuf_add(sb, name, len);
- return len;
-}
-
-int strbuf_check_branch_ref(struct strbuf *sb, const char *name)
-{
- strbuf_branchname(sb, name);
- strbuf_splice(sb, 0, 0, "refs/heads/", 11);
- return check_ref_format(sb->buf);
-}
diff --git a/string-list.c b/string-list.c
index 9b023a25841cb7a9c1faecc30c484eba0e16d6af..51681189e8380cc43ed26d35526a6dff78f1a24c 100644 (file)
--- a/string-list.c
+++ b/string-list.c
@@ -153,6 +153,7 @@ struct string_list_item *string_list_append(struct string_list *list, const char
ALLOC_GROW(list->items, list->nr + 1, list->alloc);
list->items[list->nr].string =
list->strdup_strings ? xstrdup(string) : (char *)string;
+ list->items[list->nr].util = NULL;
return list->items + list->nr++;
}
diff --git a/submodule.c b/submodule.c
index 91a47587478ae0550be8f41c00cb1749c85834f4..6f1c10722f744f4a27f18dae4867b15ecbf57d49 100644 (file)
--- a/submodule.c
+++ b/submodule.c
#include "string-list.h"
struct string_list config_name_for_path;
+struct string_list config_fetch_recurse_submodules_for_name;
struct string_list config_ignore_for_name;
+static int config_fetch_recurse_submodules;
static int add_submodule_odb(const char *path)
{
}
}
-static int submodule_config(const char *var, const char *value, void *cb)
+int submodule_config(const char *var, const char *value, void *cb)
{
if (!prefixcmp(var, "submodule."))
return parse_submodule_config_option(var, value);
+ else if (!strcmp(var, "fetch.recursesubmodules")) {
+ config_fetch_recurse_submodules = git_config_bool(var, value);
+ return 0;
+ }
return 0;
}
config = string_list_append(&config_name_for_path, xstrdup(value));
config->util = strbuf_detach(&submodname, NULL);
strbuf_release(&submodname);
+ } else if ((len > 23) && !strcmp(var + len - 23, ".fetchrecursesubmodules")) {
+ strbuf_add(&submodname, var, len - 23);
+ config = unsorted_string_list_lookup(&config_fetch_recurse_submodules_for_name, submodname.buf);
+ if (!config)
+ config = string_list_append(&config_fetch_recurse_submodules_for_name,
+ strbuf_detach(&submodname, NULL));
+ config->util = git_config_bool(var, value) ? (void *)1 : NULL;
+ strbuf_release(&submodname);
} else if ((len > 7) && !strcmp(var + len - 7, ".ignore")) {
if (strcmp(value, "untracked") && strcmp(value, "dirty") &&
strcmp(value, "all") && strcmp(value, "none")) {
strbuf_release(&sb);
}
+void set_config_fetch_recurse_submodules(int value)
+{
+ config_fetch_recurse_submodules = value;
+}
+
+int fetch_populated_submodules(int num_options, const char **options,
+ const char *prefix, int ignore_config,
+ int quiet)
+{
+ int i, result = 0, argc = 0;
+ struct child_process cp;
+ const char **argv;
+ struct string_list_item *name_for_path;
+ const char *work_tree = get_git_work_tree();
+ if (!work_tree)
+ return 0;
+
+ if (!the_index.initialized)
+ if (read_cache() < 0)
+ die("index file corrupt");
+
+ /* 4: "fetch" (options) "--submodule-prefix" prefix NULL */
+ argv = xcalloc(num_options + 4, sizeof(const char *));
+ argv[argc++] = "fetch";
+ for (i = 0; i < num_options; i++)
+ argv[argc++] = options[i];
+ argv[argc++] = "--submodule-prefix";
+
+ memset(&cp, 0, sizeof(cp));
+ cp.argv = argv;
+ cp.env = local_repo_env;
+ cp.git_cmd = 1;
+ cp.no_stdin = 1;
+
+ for (i = 0; i < active_nr; i++) {
+ struct strbuf submodule_path = STRBUF_INIT;
+ struct strbuf submodule_git_dir = STRBUF_INIT;
+ struct strbuf submodule_prefix = STRBUF_INIT;
+ struct cache_entry *ce = active_cache[i];
+ const char *git_dir, *name;
+
+ if (!S_ISGITLINK(ce->ce_mode))
+ continue;
+
+ name = ce->name;
+ name_for_path = unsorted_string_list_lookup(&config_name_for_path, ce->name);
+ if (name_for_path)
+ name = name_for_path->util;
+
+ if (!ignore_config) {
+ struct string_list_item *fetch_recurse_submodules_option;
+ fetch_recurse_submodules_option = unsorted_string_list_lookup(&config_fetch_recurse_submodules_for_name, name);
+ if (fetch_recurse_submodules_option) {
+ if (!fetch_recurse_submodules_option->util)
+ continue;
+ } else {
+ if (!config_fetch_recurse_submodules)
+ continue;
+ }
+ }
+
+ strbuf_addf(&submodule_path, "%s/%s", work_tree, ce->name);
+ strbuf_addf(&submodule_git_dir, "%s/.git", submodule_path.buf);
+ strbuf_addf(&submodule_prefix, "%s%s/", prefix, ce->name);
+ git_dir = read_gitfile_gently(submodule_git_dir.buf);
+ if (!git_dir)
+ git_dir = submodule_git_dir.buf;
+ if (is_directory(git_dir)) {
+ if (!quiet)
+ printf("Fetching submodule %s%s\n", prefix, ce->name);
+ cp.dir = submodule_path.buf;
+ argv[argc] = submodule_prefix.buf;
+ if (run_command(&cp))
+ result = 1;
+ }
+ strbuf_release(&submodule_path);
+ strbuf_release(&submodule_git_dir);
+ strbuf_release(&submodule_prefix);
+ }
+ free(argv);
+ return result;
+}
+
unsigned is_submodule_modified(const char *path, int ignore_untracked)
{
ssize_t len;
diff --git a/submodule.h b/submodule.h
index 386f410a66d9c431a61c0e26c773f1b3452f0d09..4729023aa5bbd7d7c95981b995c379407ba6423d 100644 (file)
--- a/submodule.h
+++ b/submodule.h
void set_diffopt_flags_from_submodule_config(struct diff_options *diffopt,
const char *path);
+int submodule_config(const char *var, const char *value, void *cb);
void gitmodules_config();
int parse_submodule_config_option(const char *var, const char *value);
void handle_ignore_submodules_arg(struct diff_options *diffopt, const char *);
unsigned char one[20], unsigned char two[20],
unsigned dirty_submodule,
const char *del, const char *add, const char *reset);
+void set_config_fetch_recurse_submodules(int value);
+int fetch_populated_submodules(int num_options, const char **options,
+ const char *prefix, int ignore_config,
+ int quiet);
unsigned is_submodule_modified(const char *path, int ignore_untracked);
int merge_submodule(unsigned char result[20], const char *path, const unsigned char base[20],
const unsigned char a[20], const unsigned char b[20]);
diff --git a/t/Makefile b/t/Makefile
index c7baefb7eac27fff31e3e78c7862eb2bf73d9068..47cbeb6e68ec910a691f2feca18b7b160efd0ced 100644 (file)
--- a/t/Makefile
+++ b/t/Makefile
PERL_PATH ?= /usr/bin/perl
TAR ?= $(TAR)
RM ?= rm -f
+PROVE ?= prove
+DEFAULT_TEST_TARGET ?= test
# Shell quote;
SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
T = $(wildcard t[0-9][0-9][0-9][0-9]-*.sh)
TSVN = $(wildcard t91[0-9][0-9]-*.sh)
+TGITWEB = $(wildcard t95[0-9][0-9]-*.sh)
-all: pre-clean
+all: $(DEFAULT_TEST_TARGET)
+
+test: pre-clean $(TEST_LINT)
$(MAKE) aggregate-results-and-cleanup
+prove: pre-clean $(TEST_LINT)
+ @echo "*** prove ***"; GIT_CONFIG=.git/config $(PROVE) --exec '$(SHELL_PATH_SQ)' $(GIT_PROVE_OPTS) $(T) :: $(GIT_TEST_OPTS)
+ $(MAKE) clean
+
$(T):
@echo "*** $@ ***"; GIT_CONFIG=.git/config '$(SHELL_PATH_SQ)' $@ $(GIT_TEST_OPTS)
$(RM) -r valgrind/bin
$(RM) .prove
+test-lint: test-lint-duplicates test-lint-executable
+
+test-lint-duplicates:
+ @dups=`echo $(T) | tr ' ' '\n' | sed 's/-.*//' | sort | uniq -d` && \
+ test -z "$$dups" || { \
+ echo >&2 "duplicate test numbers:" $$dups; exit 1; }
+
+test-lint-executable:
+ @bad=`for i in $(T); do test -x "$$i" || echo $$i; done` && \
+ test -z "$$bad" || { \
+ echo >&2 "non-executable tests:" $$bad; exit 1; }
+
aggregate-results-and-cleanup: $(T)
$(MAKE) aggregate-results
$(MAKE) clean
$(MAKE) $(TSVN) GIT_SVN_NO_OPTIMIZE_COMMITS=1 LC_ALL=C
$(MAKE) $(TSVN) GIT_SVN_NO_OPTIMIZE_COMMITS=0 LC_ALL=en_US.UTF-8
+gitweb-test:
+ $(MAKE) $(TGITWEB)
+
valgrind:
GIT_TEST_OPTS=--valgrind $(MAKE)
diff --git a/t/README b/t/README
index a1eb7c8720ad3e91f78fca6d58acd336c71171a0..25f7d2d2e3cf70d54f5b854ad4199c831a74ae2a 100644 (file)
--- a/t/README
+++ b/t/README
# Repeat until no more failures
$ prove -j 15 --state=failed,save ./t[0-9]*.sh
+You can give DEFAULT_TEST_TARGET=prove on the make command (or define it
+in config.mak) to cause "make test" to run tests under prove.
+GIT_PROVE_OPTS can be used to pass additional options, e.g.
+
+ $ make DEFAULT_TEST_TARGET=prove GIT_PROVE_OPTS='--timer --jobs 16' test
+
You can also run each test individually from command line, like this:
$ sh ./t3010-ls-files-killed-modified.sh
test ...
That way all of the commands in your tests will succeed or fail. If
- you must ignore the return value of something (e.g., the return
- after unsetting a variable that was already unset is unportable) it's
- best to indicate so explicitly with a semicolon:
-
- unset HLAGH;
- git merge hla &&
- git push gh &&
- test ...
+ you must ignore the return value of something, consider using a
+ helper function (e.g. use sane_unset instead of unset, in order
+ to avoid unportable return value for unsetting a variable that was
+ already unset), or prepending the command with test_might_fail or
+ test_must_fail.
- Check the test coverage for your tests. See the "Test coverage"
below.
Tests that are likely to smoke out future regressions are better
than tests that just inflate the coverage metrics.
+ - When a test checks for an absolute path that a git command generated,
+ construct the expected value using $(pwd) rather than $PWD,
+ $TEST_DIRECTORY, or $TRASH_DIRECTORY. It makes a difference on
+ Windows, where the shell (MSYS bash) mangles absolute path names.
+ For details, see the commit message of 4114156ae9.
+
Don't:
- exit() within a <script> part.
Like test_expect_success this function can optionally use a three
argument invocation with a prerequisite as the first argument.
- - test_expect_code [<prereq>] <code> <message> <script>
-
- Analogous to test_expect_success, but pass the test if it exits
- with a given exit <code>
-
- test_expect_code 1 'Merge with d/f conflicts' 'git merge "merge msg" B master'
-
- test_debug <script>
This takes a single argument, <script>, and evaluates it only
'Perl API' \
"$PERL_PATH" "$TEST_DIRECTORY"/t9700/test.pl
+ - test_expect_code <exit-code> <command>
+
+ Run a command and ensure that it exits with the given exit code.
+ For example:
+
+ test_expect_success 'Merge with d/f conflicts' '
+ test_expect_code 1 git merge "merge msg" B master
+ '
+
- test_must_fail <git-command>
Run a git command and ensure it fails in a controlled way. Use
<expected> file. This behaves like "cmp" but produces more
helpful output when the test is run with "-v" option.
+ - test_line_count (= | -lt | -ge | ...) <length> <file>
+
+ Check whether a file has the length it is expected to.
+
- test_path_is_file <file> [<diagnosis>]
test_path_is_dir <dir> [<diagnosis>]
test_path_is_missing <path> [<diagnosis>]
diff --git a/t/annotate-tests.sh b/t/annotate-tests.sh
index 141b60cdcbf4c8f70813e090a950c5d736853a8e..d34208cc27623a6105f6b12f863c648429d2d34d 100644 (file)
--- a/t/annotate-tests.sh
+++ b/t/annotate-tests.sh
'prepare reference tree' \
'echo "1A quick brown fox jumps over the" >file &&
echo "lazy dog" >>file &&
- git add file
- GIT_AUTHOR_NAME="A" git commit -a -m "Initial."'
+ git add file &&
+ GIT_AUTHOR_NAME="A" GIT_AUTHOR_EMAIL="A@test.git" git commit -a -m "Initial."'
test_expect_success \
'check all lines blamed on A' \
'Setup new lines blamed on B' \
'echo "2A quick brown fox jumps over the" >>file &&
echo "lazy dog" >> file &&
- GIT_AUTHOR_NAME="B" git commit -a -m "Second."'
+ GIT_AUTHOR_NAME="B" GIT_AUTHOR_EMAIL="B@test.git" git commit -a -m "Second."'
test_expect_success \
'Two lines blamed on A, two on B' \
'git checkout -b branch1 master &&
echo "3A slow green fox jumps into the" >> file &&
echo "well." >> file &&
- GIT_AUTHOR_NAME="B1" git commit -a -m "Branch1-1"'
+ GIT_AUTHOR_NAME="B1" GIT_AUTHOR_EMAIL="B1@test.git" git commit -a -m "Branch1-1"'
test_expect_success \
'Two lines blamed on A, two on B, two on B1' \
'git checkout -b branch2 master &&
sed -e "s/2A quick brown/4A quick brown lazy dog/" < file > file.new &&
mv file.new file &&
- GIT_AUTHOR_NAME="B2" git commit -a -m "Branch2-1"'
+ GIT_AUTHOR_NAME="B2" GIT_AUTHOR_EMAIL="B2@test.git" git commit -a -m "Branch2-1"'
test_expect_success \
'Two lines blamed on A, one on B, one on B2' \
test_expect_success \
'an incomplete line added' \
'echo "incomplete" | tr -d "\\012" >>file &&
- GIT_AUTHOR_NAME="C" git commit -a -m "Incomplete"'
+ GIT_AUTHOR_NAME="C" GIT_AUTHOR_EMAIL="C@test.git" git commit -a -m "Incomplete"'
test_expect_success \
'With incomplete lines.' \
echo
} | sed -e "s/^3A/99/" -e "/^1A/d" -e "/^incomplete/d" > file &&
echo "incomplete" | tr -d "\\012" >>file &&
- GIT_AUTHOR_NAME="D" git commit -a -m "edit"'
+ GIT_AUTHOR_NAME="D" GIT_AUTHOR_EMAIL="D@test.git" git commit -a -m "edit"'
test_expect_success \
'some edit' \
diff --git a/t/gitweb-lib.sh b/t/gitweb-lib.sh
index 1b9523d02f3a8e785b0823a1d58dfaa22837ba39..143eb1f24092df070c5ddf75340bebb319a0c746 100644 (file)
--- a/t/gitweb-lib.sh
+++ b/t/gitweb-lib.sh
our \$site_header = '';
our \$site_footer = '';
our \$home_text = 'indextext.html';
-our @stylesheets = ('file:///$TEST_DIRECTORY/../gitweb/static/gitweb.css');
-our \$logo = 'file:///$TEST_DIRECTORY/../gitweb/static/git-logo.png';
-our \$favicon = 'file:///$TEST_DIRECTORY/../gitweb/static/git-favicon.png';
+our @stylesheets = ('file:///$GIT_BUILD_DIR/gitweb/static/gitweb.css');
+our \$logo = 'file:///$GIT_BUILD_DIR/gitweb/static/git-logo.png';
+our \$favicon = 'file:///$GIT_BUILD_DIR/gitweb/static/git-favicon.png';
our \$projects_list = '';
our \$export_ok = '';
our \$strict_export = '';
cat >.git/description <<EOF
$0 test repository
EOF
+
+ # You can set the GITWEB_TEST_INSTALLED environment variable to
+ # the gitwebdir (the directory where gitweb is installed / deployed to)
+ # of an existing gitweb instalation to test that installation,
+ # or simply to pathname of installed gitweb script.
+ if test -n "$GITWEB_TEST_INSTALLED" ; then
+ if test -d $GITWEB_TEST_INSTALLED; then
+ SCRIPT_NAME="$GITWEB_TEST_INSTALLED/gitweb.cgi"
+ else
+ SCRIPT_NAME="$GITWEB_TEST_INSTALLED"
+ fi
+ test -f "$SCRIPT_NAME" ||
+ error "Cannot find gitweb at $GITWEB_TEST_INSTALLED."
+ say "# Testing $SCRIPT_NAME"
+ else # normal case, use source version of gitweb
+ SCRIPT_NAME="$GIT_BUILD_DIR/gitweb/gitweb.perl"
+ fi
+ export SCRIPT_NAME
}
gitweb_run () {
GATEWAY_INTERFACE='CGI/1.1'
HTTP_ACCEPT='*/*'
REQUEST_METHOD='GET'
- SCRIPT_NAME="$TEST_DIRECTORY/../gitweb/gitweb.perl"
QUERY_STRING=""$1""
PATH_INFO=""$2""
export GATEWAY_INTERFACE HTTP_ACCEPT REQUEST_METHOD \
- SCRIPT_NAME QUERY_STRING PATH_INFO
+ QUERY_STRING PATH_INFO
GITWEB_CONFIG=$(pwd)/gitweb_config.perl
export GITWEB_CONFIG
}
close O;
' gitweb.output &&
- if grep '^[[]' gitweb.log >/dev/null 2>&1; then false; else true; fi
+ if grep '^[[]' gitweb.log >/dev/null 2>&1; then
+ test_debug 'cat gitweb.log >&2' &&
+ false
+ else
+ true
+ fi
# gitweb.log is left for debugging
# gitweb.output is used to parse HTTP output
fi
perl -MEncode -e '$e="";decode_utf8($e, Encode::FB_CROAK)' >/dev/null 2>&1 || {
- skip_all='skipping gitweb tests, perl version is too old'
- test_done
+ skip_all='skipping gitweb tests, perl version is too old'
+ test_done
}
gitweb_init
diff --git a/t/lib-git-svn.sh b/t/lib-git-svn.sh
index 6a9d9757239f0476422b1a97228927b1ba1ac934..199f22c231b5ac1479929a89cdf988ac7aff4268 100644 (file)
--- a/t/lib-git-svn.sh
+++ b/t/lib-git-svn.sh
svn "$orig_svncmd" --config-dir "$svnconf" "$@"
}
-if test -n "$SVN_HTTPD_PORT"
-then
+prepare_httpd () {
for d in \
"$SVN_HTTPD_PATH" \
/usr/sbin/apache2 \
done
if test -z "$SVN_HTTPD_PATH"
then
- skip_all='skipping git svn tests, Apache not found'
- test_done
+ echo >&2 '*** error: Apache not found'
+ return 1
fi
for d in \
"$SVN_HTTPD_MODULE_PATH" \
done
if test -z "$SVN_HTTPD_MODULE_PATH"
then
- skip_all='skipping git svn tests, Apache module dir not found'
- test_done
- fi
-fi
-
-start_httpd () {
- repo_base_path="$1"
- if test -z "$SVN_HTTPD_PORT"
- then
- echo >&2 'SVN_HTTPD_PORT is not defined!'
- return
+ echo >&2 '*** error: Apache module dir not found'
+ return 1
fi
- if test -z "$repo_base_path"
+ if test ! -f "$SVN_HTTPD_MODULE_PATH/mod_dav_svn.so"
then
- repo_base_path=svn
+ echo >&2 '*** error: Apache module "mod_dav_svn" not found'
+ return 1
fi
+ repo_base_path="${1-svn}"
mkdir "$GIT_DIR"/logs
cat > "$GIT_DIR/httpd.conf" <<EOF
SVNPath "$rawsvnrepo"
</Location>
EOF
+}
+
+start_httpd () {
+ if test -z "$SVN_HTTPD_PORT"
+ then
+ echo >&2 'SVN_HTTPD_PORT is not defined!'
+ return
+ fi
+
+ prepare_httpd "$1" || return 1
+
"$SVN_HTTPD_PATH" -f "$GIT_DIR"/httpd.conf -k start
svnrepo="http://127.0.0.1:$SVN_HTTPD_PORT/$repo_base_path"
}
stop_httpd () {
test -z "$SVN_HTTPD_PORT" && return
+ test ! -f "$GIT_DIR/httpd.conf" && return
"$SVN_HTTPD_PATH" -f "$GIT_DIR"/httpd.conf -k stop
}
diff --git a/t/lib-terminal.sh b/t/lib-terminal.sh
--- /dev/null
+++ b/t/lib-terminal.sh
@@ -0,0 +1,35 @@
+#!/bin/sh
+
+test_expect_success PERL 'set up terminal for tests' '
+ # Reading from the pty master seems to get stuck _sometimes_
+ # on Mac OS X 10.5.0, using Perl 5.10.0 or 5.8.9.
+ #
+ # Reproduction recipe: run
+ #
+ # i=0
+ # while ./test-terminal.perl echo hi $i
+ # do
+ # : $((i = $i + 1))
+ # done
+ #
+ # After 2000 iterations or so it hangs.
+ # https://rt.cpan.org/Ticket/Display.html?id=65692
+ #
+ if test "$(uname -s)" = Darwin
+ then
+ :
+ elif
+ "$PERL_PATH" "$TEST_DIRECTORY"/test-terminal.perl \
+ sh -c "test -t 1 && test -t 2"
+ then
+ test_set_prereq TTY &&
+ test_terminal () {
+ if ! test_declared_prereq TTY
+ then
+ echo >&4 "test_terminal: need to declare TTY prerequisite"
+ return 127
+ fi
+ "$PERL_PATH" "$TEST_DIRECTORY"/test-terminal.perl "$@"
+ }
+ fi
+'
diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh
index f688bd3ef5034e32983685cfae940df303b377cc..8deec75c3a0eef961986a0bb313a918ab532f58d 100755 (executable)
--- a/t/t0000-basic.sh
+++ b/t/t0000-basic.sh
chmod +x passing-todo.sh &&
./passing-todo.sh >out 2>err &&
! test -s err &&
-cat >expect <<EOF &&
-ok 1 - pretend we have fixed a known breakage # TODO known breakage
-# fixed 1 known breakage(s)
-# passed all 1 test(s)
-1..1
+sed -e 's/^> //' >expect <<EOF &&
+> ok 1 - pretend we have fixed a known breakage # TODO known breakage
+> # fixed 1 known breakage(s)
+> # passed all 1 test(s)
+> 1..1
EOF
test_cmp expect out)
"
test_when_finished clean=yes
'
-cleaner=no
-test_expect_code 1 'tests clean up even after a failure' '
- test_when_finished cleaner=yes &&
- (exit 1)
-'
-
-if test $clean$cleaner != yesyes
+if test $clean != yes
then
- say "bug in test framework: cleanup commands do not work reliably"
+ say "bug in test framework: basic cleanup command does not work reliably"
exit 1
fi
-test_expect_code 2 'failure to clean up causes the test to fail' '
- test_when_finished "(exit 2)"
+test_expect_success 'tests clean up even on failures' "
+ mkdir failing-cleanup &&
+ (cd failing-cleanup &&
+ cat >failing-cleanup.sh <<EOF &&
+#!$SHELL_PATH
+
+test_description='Failing tests with cleanup commands'
+
+# Point to the t/test-lib.sh, which isn't in ../ as usual
+TEST_DIRECTORY=\"$TEST_DIRECTORY\"
+. \"\$TEST_DIRECTORY\"/test-lib.sh
+
+test_expect_success 'tests clean up even after a failure' '
+ touch clean-after-failure &&
+ test_when_finished rm clean-after-failure &&
+ (exit 1)
+'
+
+test_expect_success 'failure to clean up causes the test to fail' '
+ test_when_finished \"(exit 2)\"
'
+test_done
+EOF
+ chmod +x failing-cleanup.sh &&
+ test_must_fail ./failing-cleanup.sh >out 2>err &&
+ ! test -s err &&
+ ! test -f \"trash directory.failing-cleanup/clean-after-failure\" &&
+sed -e 's/Z$//' -e 's/^> //' >expect <<\EOF &&
+> not ok - 1 tests clean up even after a failure
+> # Z
+> # touch clean-after-failure &&
+> # test_when_finished rm clean-after-failure &&
+> # (exit 1)
+> # Z
+> not ok - 2 failure to clean up causes the test to fail
+> # Z
+> # test_when_finished \"(exit 2)\"
+> # Z
+> # failed 2 among 2 test(s)
+> 1..2
+EOF
+ test_cmp expect out)
+"
+
################################################################
# Basics of the basics
diff --git a/t/t0001-init.sh b/t/t0001-init.sh
index 7fe8883ae060947da804abb5b287fb94d19e87c4..f6849932112f1bbea1dafad09150857a707b64e4 100755 (executable)
--- a/t/t0001-init.sh
+++ b/t/t0001-init.sh
test_expect_success 'plain' '
(
- unset GIT_DIR GIT_WORK_TREE
+ sane_unset GIT_DIR GIT_WORK_TREE &&
mkdir plain &&
cd plain &&
git init
check_config plain/.git false unset
'
+test_expect_success 'plain nested in bare' '
+ (
+ sane_unset GIT_DIR GIT_WORK_TREE &&
+ git init --bare bare-ancestor.git &&
+ cd bare-ancestor.git &&
+ mkdir plain-nested &&
+ cd plain-nested &&
+ git init
+ ) &&
+ check_config bare-ancestor.git/plain-nested/.git false unset
+'
+
+test_expect_success 'plain through aliased command, outside any git repo' '
+ (
+ sane_unset GIT_DIR GIT_WORK_TREE GIT_CONFIG_NOGLOBAL &&
+ HOME=$(pwd)/alias-config &&
+ export HOME &&
+ mkdir alias-config &&
+ echo "[alias] aliasedinit = init" >alias-config/.gitconfig &&
+
+ GIT_CEILING_DIRECTORIES=$(pwd) &&
+ export GIT_CEILING_DIRECTORIES &&
+
+ mkdir plain-aliased &&
+ cd plain-aliased &&
+ git aliasedinit
+ ) &&
+ check_config plain-aliased/.git false unset
+'
+
+test_expect_failure 'plain nested through aliased command' '
+ (
+ sane_unset GIT_DIR GIT_WORK_TREE &&
+ git init plain-ancestor-aliased &&
+ cd plain-ancestor-aliased &&
+ echo "[alias] aliasedinit = init" >>.git/config &&
+ mkdir plain-nested &&
+ cd plain-nested &&
+ git aliasedinit
+ ) &&
+ check_config plain-ancestor-aliased/plain-nested/.git false unset
+'
+
+test_expect_failure 'plain nested in bare through aliased command' '
+ (
+ sane_unset GIT_DIR GIT_WORK_TREE &&
+ git init --bare bare-ancestor-aliased.git &&
+ cd bare-ancestor-aliased.git &&
+ echo "[alias] aliasedinit = init" >>config &&
+ mkdir plain-nested &&
+ cd plain-nested &&
+ git aliasedinit
+ ) &&
+ check_config bare-ancestor-aliased.git/plain-nested/.git false unset
+'
+
test_expect_success 'plain with GIT_WORK_TREE' '
if (
- unset GIT_DIR
+ sane_unset GIT_DIR &&
mkdir plain-wt &&
cd plain-wt &&
GIT_WORK_TREE=$(pwd) git init
test_expect_success 'plain bare' '
(
- unset GIT_DIR GIT_WORK_TREE GIT_CONFIG
+ sane_unset GIT_DIR GIT_WORK_TREE GIT_CONFIG &&
mkdir plain-bare-1 &&
cd plain-bare-1 &&
git --bare init
test_expect_success 'plain bare with GIT_WORK_TREE' '
if (
- unset GIT_DIR GIT_CONFIG
+ sane_unset GIT_DIR GIT_CONFIG &&
mkdir plain-bare-2 &&
cd plain-bare-2 &&
GIT_WORK_TREE=$(pwd) git --bare init
test_expect_success 'GIT_DIR bare' '
(
- unset GIT_CONFIG
+ sane_unset GIT_CONFIG &&
mkdir git-dir-bare.git &&
GIT_DIR=git-dir-bare.git git init
) &&
test_expect_success 'init --bare' '
(
- unset GIT_DIR GIT_WORK_TREE GIT_CONFIG
+ sane_unset GIT_DIR GIT_WORK_TREE GIT_CONFIG &&
mkdir init-bare.git &&
cd init-bare.git &&
git init --bare
test_expect_success 'GIT_DIR non-bare' '
(
- unset GIT_CONFIG
+ sane_unset GIT_CONFIG &&
mkdir non-bare &&
cd non-bare &&
GIT_DIR=.git git init
test_expect_success 'GIT_DIR & GIT_WORK_TREE (1)' '
(
- unset GIT_CONFIG
+ sane_unset GIT_CONFIG &&
mkdir git-dir-wt-1.git &&
GIT_WORK_TREE=$(pwd) GIT_DIR=git-dir-wt-1.git git init
) &&
test_expect_success 'GIT_DIR & GIT_WORK_TREE (2)' '
if (
- unset GIT_CONFIG
+ sane_unset GIT_CONFIG &&
mkdir git-dir-wt-2.git &&
GIT_WORK_TREE=$(pwd) GIT_DIR=git-dir-wt-2.git git --bare init
)
test_expect_success 'reinit' '
(
- unset GIT_CONFIG GIT_WORK_TREE GIT_CONFIG
+ sane_unset GIT_CONFIG GIT_WORK_TREE GIT_CONFIG &&
mkdir again &&
cd again &&
git config -f "$test_config" init.templatedir "${HOME}/templatedir-source" &&
mkdir templatedir-set &&
cd templatedir-set &&
- unset GIT_CONFIG_NOGLOBAL &&
- unset GIT_TEMPLATE_DIR &&
+ sane_unset GIT_CONFIG_NOGLOBAL &&
+ sane_unset GIT_TEMPLATE_DIR &&
NO_SET_GIT_TEMPLATE_DIR=t &&
export NO_SET_GIT_TEMPLATE_DIR &&
git init
test_expect_success 'init --bare/--shared overrides system/global config' '
(
test_config="$HOME"/.gitconfig &&
- unset GIT_CONFIG_NOGLOBAL &&
+ sane_unset GIT_CONFIG_NOGLOBAL &&
git config -f "$test_config" core.bare false &&
git config -f "$test_config" core.sharedRepository 0640 &&
mkdir init-bare-shared-override &&
test_expect_success 'init honors global core.sharedRepository' '
(
test_config="$HOME"/.gitconfig &&
- unset GIT_CONFIG_NOGLOBAL &&
+ sane_unset GIT_CONFIG_NOGLOBAL &&
git config -f "$test_config" core.sharedRepository 0666 &&
mkdir shared-honor-global &&
cd shared-honor-global &&
diff --git a/t/t0003-attributes.sh b/t/t0003-attributes.sh
index de38c7f7aab16a0c2766ef020a3fc03fadece555..ebbc7554a7d4dce4c2ed33a79f5d4feb3a520f05 100755 (executable)
--- a/t/t0003-attributes.sh
+++ b/t/t0003-attributes.sh
echo "d/* test=a/b/d/*"
echo "d/yes notest"
) >a/b/.gitattributes
+ (
+ echo "global test=global"
+ ) >"$HOME"/global-gitattributes
'
'
+test_expect_success 'core.attributesfile' '
+ attr_check global unspecified &&
+ git config core.attributesfile "$HOME/global-gitattributes" &&
+ attr_check global global &&
+ git config core.attributesfile "~/global-gitattributes" &&
+ attr_check global global &&
+ echo "global test=precedence" >> .gitattributes &&
+ attr_check global precedence
+'
+
test_expect_success 'attribute test: read paths from stdin' '
- cat <<EOF > expect
+ cat <<EOF > expect &&
f: test: f
a/f: test: f
a/c/f: test: f
diff --git a/t/t0020-crlf.sh b/t/t0020-crlf.sh
index 234a94f3e6311c529b5bb476ee5a6a2ea705017f..1a8f44c44ca3cce70990ec56fec61894be9b3086 100755 (executable)
--- a/t/t0020-crlf.sh
+++ b/t/t0020-crlf.sh
git rm .gitattributes &&
echo "contentsQ" | q_to_cr > .file2 &&
git add .file2 &&
- git commit -m third
+ git commit -m third &&
git checkout master~1 &&
git checkout master &&
diff --git a/t/t0021-conversion.sh b/t/t0021-conversion.sh
index 828e35baf72d94908ad1f30dbd2e1aa6f9376e69..9078b84ae68b430285312749f62daee103dcc428 100755 (executable)
--- a/t/t0021-conversion.sh
+++ b/t/t0021-conversion.sh
cmp expanded-keywords expected-output
'
+# The use of %f in a filter definition is expanded to the path to
+# the filename being smudged or cleaned. It must be shell escaped.
+# First, set up some interesting file names and pet them in
+# .gitattributes.
+test_expect_success 'filter shell-escaped filenames' '
+ cat >argc.sh <<-EOF &&
+ #!$SHELL_PATH
+ cat >/dev/null
+ echo argc: \$# "\$@"
+ EOF
+ normal=name-no-magic &&
+ special="name with '\''sq'\'' and \$x" &&
+ echo some test text >"$normal" &&
+ echo some test text >"$special" &&
+ git add "$normal" "$special" &&
+ git commit -q -m "add files" &&
+ echo "name* filter=argc" >.gitattributes &&
+
+ # delete the files and check them out again, using a smudge filter
+ # that will count the args and echo the command-line back to us
+ git config filter.argc.smudge "sh ./argc.sh %f" &&
+ rm "$normal" "$special" &&
+ git checkout -- "$normal" "$special" &&
+
+ # make sure argc.sh counted the right number of args
+ echo "argc: 1 $normal" >expect &&
+ test_cmp expect "$normal" &&
+ echo "argc: 1 $special" >expect &&
+ test_cmp expect "$special" &&
+
+ # do the same thing, but with more args in the filter expression
+ git config filter.argc.smudge "sh ./argc.sh %f --my-extra-arg" &&
+ rm "$normal" "$special" &&
+ git checkout -- "$normal" "$special" &&
+
+ # make sure argc.sh counted the right number of args
+ echo "argc: 2 $normal --my-extra-arg" >expect &&
+ test_cmp expect "$normal" &&
+ echo "argc: 2 $special --my-extra-arg" >expect &&
+ test_cmp expect "$special" &&
+ :
+'
+
test_done
index c7d0324374e9df5131a58a9ae0cadf7ee4dc03e7..ec6c1b3f8a7eac0e1734883a0ad2f2d68f11bf96 100755 (executable)
--- a/t/t0024-crlf-archive.sh
+++ b/t/t0024-crlf-archive.sh
test_expect_success setup '
- git config core.autocrlf true
+ git config core.autocrlf true &&
printf "CRLF line ending\r\nAnd another\r\n" > sample &&
git add sample &&
test_expect_success 'tar archive' '
git archive --format=tar HEAD |
- ( mkdir untarred && cd untarred && "$TAR" -xf - )
+ ( mkdir untarred && cd untarred && "$TAR" -xf - ) &&
test_cmp sample untarred/sample
diff --git a/t/t0026-eol-config.sh b/t/t0026-eol-config.sh
index f37ac8fa0b720d1a72536033c3334a6f5244d5d7..fe0164be62ad9ae7a2ea426c6e576cd1133f3d67 100755 (executable)
--- a/t/t0026-eol-config.sh
+++ b/t/t0026-eol-config.sh
git config core.autocrlf false &&
- echo "one text" > .gitattributes
+ echo "one text" > .gitattributes &&
for w in Hello world how are you; do echo $w; done >one &&
for w in I am very very fine thank you; do echo $w; done >two &&
index 20924506af886c8d0ce28f3fcb2742214d80020f..ae266147b6db9e06abeb4dc30e4271b6c611deac 100755 (executable)
--- a/t/t0040-parse-options.sh
+++ b/t/t0040-parse-options.sh
--set23 set integer to 23
-t <time> get timestamp of <time>
-L, --length <str> get length of <str>
- -F, --file <FILE> set file to <FILE>
+ -F, --file <file> set file to <file>
String options
-s, --string <string>
diff --git a/t/t0050-filesystem.sh b/t/t0050-filesystem.sh
index 07357ee1fc2902d72cd4f524404862036d06f613..1542cf6a1313963fccdf42702c620be9e05f7143 100755 (executable)
--- a/t/t0050-filesystem.sh
+++ b/t/t0050-filesystem.sh
no_symlinks=
test_expect_success 'see what we expect' '
- test_case=test_expect_success
- test_unicode=test_expect_success
+ test_case=test_expect_success &&
+ test_unicode=test_expect_success &&
mkdir junk &&
echo good >junk/CamelCase &&
echo bad >junk/camelcase &&
if test "$(cat junk/CamelCase)" != good
then
- test_case=test_expect_failure
+ test_case=test_expect_failure &&
case_insensitive=t
fi &&
rm -fr junk &&
>junk/"$auml" &&
case "$(cd junk && echo *)" in
"$aumlcdiar")
- test_unicode=test_expect_failure
+ test_unicode=test_expect_failure &&
unibad=t
;;
*) ;;
{
ln -s x y 2> /dev/null &&
test -h y 2> /dev/null ||
- no_symlinks=1
+ no_symlinks=1 &&
rm -f y
}
'
cd unicode &&
touch "$aumlcdiar" &&
git add "$aumlcdiar" &&
- git commit -m initial
+ git commit -m initial &&
git tag initial &&
git checkout -b topic &&
git mv $aumlcdiar tmp &&
diff --git a/t/t0070-fundamental.sh b/t/t0070-fundamental.sh
index 680d7d68612b168a2642ab64e7bc6c15c316d5b4..9bee8bfd2e063c40bae2d76930370bd9e8ba8fa5 100755 (executable)
--- a/t/t0070-fundamental.sh
+++ b/t/t0070-fundamental.sh
test-ctype
'
+test_expect_success 'mktemp to nonexistent directory prints filename' '
+ test_must_fail test-mktemp doesnotexist/testXXXXXX 2>err &&
+ grep "doesnotexist/test" err
+'
+
+test_expect_success POSIXPERM 'mktemp to unwritable directory prints filename' '
+ mkdir cannotwrite &&
+ chmod -w cannotwrite &&
+ test_when_finished "chmod +w cannotwrite" &&
+ test_must_fail test-mktemp cannotwrite/testXXXXXX 2>err &&
+ grep "cannotwrite/test" err
+'
+
test_done
diff --git a/t/t0080-vcs-svn.sh b/t/t0080-vcs-svn.sh
index d3225ada681d205f43914a6b59f4bdf4f2263451..99a314b08078960b8736ce94890e805943e4f970 100755 (executable)
--- a/t/t0080-vcs-svn.sh
+++ b/t/t0080-vcs-svn.sh
test_cmp expected actual
'
-test_expect_success 'line buffer' '
- echo HELLO >expected1 &&
- printf "%s\n" "" HELLO >expected2 &&
- echo >expected3 &&
- printf "%s\n" "" Q | q_to_nul >expected4 &&
- printf "%s\n" foo "" >expected5 &&
- printf "%s\n" "" foo >expected6 &&
-
- test-line-buffer <<-\EOF >actual1 &&
- 5
- HELLO
- EOF
-
- test-line-buffer <<-\EOF >actual2 &&
- 0
-
- 5
- HELLO
- EOF
-
- q_to_nul <<-\EOF |
- 1
- Q
- EOF
- test-line-buffer >actual3 &&
-
- q_to_nul <<-\EOF |
- 0
-
- 1
- Q
- EOF
- test-line-buffer >actual4 &&
-
- test-line-buffer <<-\EOF >actual5 &&
- 5
- foo
- EOF
-
- test-line-buffer <<-\EOF >actual6 &&
- 0
-
- 5
- foo
- EOF
-
- test_cmp expected1 actual1 &&
- test_cmp expected2 actual2 &&
- test_cmp expected3 actual3 &&
- test_cmp expected4 actual4 &&
- test_cmp expected5 actual5 &&
- test_cmp expected6 actual6
-'
-
test_expect_success 'string pool' '
echo a does not equal b >expected.differ &&
echo a equals a >expected.match &&
diff --git a/t/t0081-line-buffer.sh b/t/t0081-line-buffer.sh
--- /dev/null
+++ b/t/t0081-line-buffer.sh
@@ -0,0 +1,201 @@
+#!/bin/sh
+
+test_description="Test the svn importer's input handling routines.
+
+These tests exercise the line_buffer library, but their real purpose
+is to check the assumptions that library makes of the platform's input
+routines. Processes engaged in bi-directional communication would
+hang if fread or fgets is too greedy.
+
+While at it, check that input of newlines and null bytes are handled
+correctly.
+"
+. ./test-lib.sh
+
+test -n "$GIT_REMOTE_SVN_TEST_BIG_FILES" && test_set_prereq EXPENSIVE
+
+generate_tens_of_lines () {
+ tens=$1 &&
+ line=$2 &&
+
+ i=0 &&
+ while test $i -lt "$tens"
+ do
+ for j in a b c d e f g h i j
+ do
+ echo "$line"
+ done &&
+ : $((i = $i + 1)) ||
+ return
+ done
+}
+
+long_read_test () {
+ : each line is 10 bytes, including newline &&
+ line=abcdefghi &&
+ echo "$line" >expect &&
+
+ if ! test_declared_prereq PIPE
+ then
+ echo >&4 "long_read_test: need to declare PIPE prerequisite"
+ return 127
+ fi &&
+ tens_of_lines=$(($1 / 100 + 1)) &&
+ lines=$(($tens_of_lines * 10)) &&
+ readsize=$((($lines - 1) * 10 + 3)) &&
+ copysize=7 &&
+ rm -f input &&
+ mkfifo input &&
+ {
+ {
+ generate_tens_of_lines $tens_of_lines "$line" &&
+ sleep 100
+ } >input &
+ } &&
+ test-line-buffer input <<-EOF >output &&
+ read $readsize
+ copy $copysize
+ EOF
+ kill $! &&
+ test_line_count = $lines output &&
+ tail -n 1 <output >actual &&
+ test_cmp expect actual
+}
+
+test_expect_success 'setup: have pipes?' '
+ rm -f frob &&
+ if mkfifo frob
+ then
+ test_set_prereq PIPE
+ fi
+'
+
+test_expect_success 'hello world' '
+ echo HELLO >expect &&
+ test-line-buffer <<-\EOF >actual &&
+ read 6
+ HELLO
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success PIPE '0-length read, no input available' '
+ >expect &&
+ rm -f input &&
+ mkfifo input &&
+ {
+ sleep 100 >input &
+ } &&
+ test-line-buffer input <<-\EOF >actual &&
+ read 0
+ copy 0
+ EOF
+ kill $! &&
+ test_cmp expect actual
+'
+
+test_expect_success '0-length read, send along greeting' '
+ echo HELLO >expect &&
+ test-line-buffer <<-\EOF >actual &&
+ read 0
+ copy 6
+ HELLO
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success PIPE '1-byte read, no input available' '
+ printf "%s" ab >expect &&
+ rm -f input &&
+ mkfifo input &&
+ {
+ {
+ printf "%s" a &&
+ printf "%s" b &&
+ sleep 100
+ } >input &
+ } &&
+ test-line-buffer input <<-\EOF >actual &&
+ read 1
+ copy 1
+ EOF
+ kill $! &&
+ test_cmp expect actual
+'
+
+test_expect_success PIPE 'long read (around 8192 bytes)' '
+ long_read_test 8192
+'
+
+test_expect_success PIPE,EXPENSIVE 'longer read (around 65536 bytes)' '
+ long_read_test 65536
+'
+
+test_expect_success 'read from file descriptor' '
+ rm -f input &&
+ echo hello >expect &&
+ echo hello >input &&
+ echo copy 6 |
+ test-line-buffer "&4" 4<input >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'buffer_read_string copes with null byte' '
+ >expect &&
+ q_to_nul <<-\EOF | test-line-buffer >actual &&
+ read 2
+ Q
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'skip, copy null byte' '
+ echo Q | q_to_nul >expect &&
+ q_to_nul <<-\EOF | test-line-buffer >actual &&
+ skip 2
+ Q
+ copy 2
+ Q
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'read null byte' '
+ echo ">QhelloQ" | q_to_nul >expect &&
+ q_to_nul <<-\EOF | test-line-buffer >actual &&
+ binary 8
+ QhelloQ
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'long reads are truncated' '
+ echo foo >expect &&
+ test-line-buffer <<-\EOF >actual &&
+ read 5
+ foo
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'long copies are truncated' '
+ printf "%s\n" "" foo >expect &&
+ test-line-buffer <<-\EOF >actual &&
+ read 1
+
+ copy 5
+ foo
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'long binary reads are truncated' '
+ echo ">foo" >expect &&
+ test-line-buffer <<-\EOF >actual &&
+ binary 5
+ foo
+ EOF
+ test_cmp expect actual
+'
+
+test_done
index 4f171722d9e1dda7668f54b4104267eb70d91c67..ca8a4098fa06afbdbe5c427983d3953550457bc2 100755 (executable)
test_expect_success \
'6 - must not exist in O && !A && !B case' "
rm -f .git/index DD &&
- echo DD >DD
+ echo DD >DD &&
git update-index --add DD &&
test_must_fail git read-tree -m $tree_O $tree_A $tree_B
"
index 93ca84f9e6d3dfd859d8bd4941f4d2c5a354ab6c..680d992f229cda9f1904240f593d66dc1cf3e32d 100755 (executable)
git checkout-index -u -f -q -a &&
git update-index --add yomin &&
read_tree_twoway $treeH $treeM &&
- git ls-files --stage >4.out || return 1
- git diff --no-index M.out 4.out >4diff.out
+ git ls-files --stage >4.out &&
+ test_must_fail git diff --no-index M.out 4.out >4diff.out &&
compare_change 4diff.out expected &&
check_cache_at yomin clean'
git update-index --add yomin &&
echo yomin yomin >yomin &&
read_tree_twoway $treeH $treeM &&
- git ls-files --stage >5.out || return 1
- git diff --no-index M.out 5.out >5diff.out
+ git ls-files --stage >5.out &&
+ test_must_fail git diff --no-index M.out 5.out >5diff.out &&
compare_change 5diff.out expected &&
check_cache_at yomin dirty'
echo nitfol nitfol >nitfol &&
git update-index --add nitfol &&
read_tree_twoway $treeH $treeM &&
- git ls-files --stage >14.out || return 1
- git diff --no-index M.out 14.out >14diff.out
+ git ls-files --stage >14.out &&
+ test_must_fail git diff --no-index M.out 14.out >14diff.out &&
compare_change 14diff.out expected &&
check_cache_at nitfol clean'
git update-index --add nitfol &&
echo nitfol nitfol nitfol >nitfol &&
read_tree_twoway $treeH $treeM &&
- git ls-files --stage >15.out || return 1
- git diff --no-index M.out 15.out >15diff.out
+ git ls-files --stage >15.out &&
+ test_must_fail git diff --no-index M.out 15.out >15diff.out &&
compare_change 15diff.out expected &&
check_cache_at nitfol dirty'
git ls-files --stage >treeM.out &&
rm -f a &&
- mkdir a
+ mkdir a &&
: >a/b &&
git update-index --add --remove a a/b &&
treeH=`git write-tree` &&
echo >file-a &&
echo >file-b &&
git add file-a file-b &&
- git commit -a -m "test for correct modified tree"
+ git commit -a -m "test for correct modified tree" &&
git branch initial-mod &&
echo b >file-b &&
git commit -a -m "B" &&
index 0241329a08af0be642131ffe4b619231d9ea3844..a4a17e001739a45fc7d53e76bd8bbb14426492f9 100755 (executable)
echo nitfol nitfol >nitfol &&
git update-index --add nitfol &&
git read-tree -m -u $treeH $treeM &&
- git ls-files --stage >14.out || return 1
- git diff -U0 --no-index M.out 14.out >14diff.out
+ git ls-files --stage >14.out &&
+ test_must_fail git diff -U0 --no-index M.out 14.out >14diff.out &&
compare_change 14diff.out expected &&
sum bozbar frotz >actual14.sum &&
grep -v nitfol M.sum > expected14.sum &&
git update-index --add nitfol &&
echo nitfol nitfol nitfol >nitfol &&
git read-tree -m -u $treeH $treeM &&
- git ls-files --stage >15.out || return 1
- git diff -U0 --no-index M.out 15.out >15diff.out
+ git ls-files --stage >15.out &&
+ test_must_fail git diff -U0 --no-index M.out 15.out >15diff.out &&
compare_change 15diff.out expected &&
check_cache_at nitfol dirty &&
sum bozbar frotz >actual15.sum &&
# Also make sure we did not break DF vs DF/DF case.
test_expect_success \
'DF vs DF/DF case setup.' \
- 'rm -f .git/index
+ 'rm -f .git/index &&
echo DF >DF &&
git update-index --add DF &&
treeDF=`git write-tree` &&
diff --git a/t/t1007-hash-object.sh b/t/t1007-hash-object.sh
index dd32432d626e4f3d192c2bbe4824772025bb08b1..6d52b824b115964c5551aaf4284205b98b885ce3 100755 (executable)
--- a/t/t1007-hash-object.sh
+++ b/t/t1007-hash-object.sh
pop_repo
done
+test_expect_success 'corrupt tree' '
+ echo abc >malformed-tree
+ test_must_fail git hash-object -t tree malformed-tree
+'
+
+test_expect_success 'corrupt commit' '
+ test_must_fail git hash-object -t commit --stdin </dev/null
+'
+
+test_expect_success 'corrupt tag' '
+ test_must_fail git hash-object -t tag --stdin </dev/null
+'
+
test_done
index 8008fa2d894baf76a19168a020ed4f4640619ce7..de84e35c4357c7329b3fae749228df28f31a9d3f 100755 (executable)
'
test_expect_success 'read-tree with .git/info/sparse-checkout but disabled' '
- echo >.git/info/sparse-checkout
+ echo >.git/info/sparse-checkout &&
git read-tree -m -u HEAD &&
git ls-files -t >result &&
test_cmp expected.swt result &&
test -f sub/added
'
-test_expect_failure 'match directories without trailing slash' '
- echo init.t >.git/info/sparse-checkout &&
+test_expect_success 'match directories without trailing slash' '
echo sub >>.git/info/sparse-checkout &&
git read-tree -m -u HEAD &&
git ls-files -t >result &&
- test_cmp expected.swt result &&
+ test_cmp expected.swt-noinit result &&
+ test ! -f init.t &&
+ test -f sub/added
+'
+
+test_expect_success 'match directory pattern' '
+ echo "s?b" >>.git/info/sparse-checkout &&
+ git read-tree -m -u HEAD &&
+ git ls-files -t >result &&
+ test_cmp expected.swt-noinit result &&
test ! -f init.t &&
test -f sub/added
'
index a3ac33801a28fe7bd2ce67d4d4ae306c1050f5c1..1fd187c5eb188671934f6afe85ca5003c529a942 100755 (executable)
--- a/t/t1020-subdirectory.sh
+++ b/t/t1020-subdirectory.sh
)
'
+test_expect_success 'alias expansion' '
+ (
+ git config alias.ss status &&
+ cd dir &&
+ git status &&
+ git ss
+ )
+'
test_expect_success 'no file/rev ambiguity check inside .git' '
git commit -a -m 1 &&
(
diff --git a/t/t1200-tutorial.sh b/t/t1200-tutorial.sh
index ab55eda158bb5a6ecad77302aa2fe17bd6e92be3..bfa2c2190d0368eba533a8411df739eee77fa1be 100755 (executable)
--- a/t/t1200-tutorial.sh
+++ b/t/t1200-tutorial.sh
'
test_expect_success 'tree' '
- tree=$(git write-tree 2>/dev/null)
+ tree=$(git write-tree 2>/dev/null) &&
test 8988da15d077d4829fc51d8544c097def6644dbb = $tree
'
diff --git a/t/t1300-repo-config.sh b/t/t1300-repo-config.sh
index 074f2f2e3e57f76b01f16502998e95b493676711..53fb8228cf18e2b58f3ea63e98a0220fa0fd39f2 100755 (executable)
--- a/t/t1300-repo-config.sh
+++ b/t/t1300-repo-config.sh
test_expect_success 'working --list' \
'git config --list > output && cmp output expect'
+cat > expect << EOF
+EOF
+
+test_expect_success '--list without repo produces empty output' '
+ git --git-dir=nonexistent config --list >output &&
+ test_cmp expect output
+'
+
cat > expect << EOF
beta.noindent sillyValue
nextsection.nonewline wow2 for me
trailingtilde = foo~
EOF
-test_expect_success 'set --path' '
+test_expect_success NOT_MINGW 'set --path' '
git config --path path.home "~/" &&
git config --path path.normal "/dev/null" &&
git config --path path.trailingtilde "foo~" &&
test_cmp expect .git/config'
-if test "${HOME+set}"
+if test_have_prereq NOT_MINGW && test "${HOME+set}"
then
test_set_prereq HOMEVAR
fi
foo~
EOF
-test_expect_success 'get --path copes with unset $HOME' '
+test_expect_success NOT_MINGW 'get --path copes with unset $HOME' '
(
unset HOME;
test_must_fail git config --get --path path.home \
'
+test_expect_success 'nonexistent configuration' '
+ (
+ GIT_CONFIG=doesnotexist &&
+ export GIT_CONFIG &&
+ test_must_fail git config --list &&
+ test_must_fail git config test.xyzzy
+ )
+'
+
+test_expect_success SYMLINKS 'symlink to nonexistent configuration' '
+ ln -s doesnotexist linktonada &&
+ ln -s linktonada linktolinktonada &&
+ (
+ GIT_CONFIG=linktonada &&
+ export GIT_CONFIG &&
+ test_must_fail git config --list &&
+ GIT_CONFIG=linktolinktonada &&
+ test_must_fail git config --list
+ )
+'
+
test_expect_success 'check split_cmdline return' "
git config alias.split-cmdline-fix 'echo \"' &&
test_must_fail git split-cmdline-fix &&
"
test_expect_success 'git -c "key=value" support' '
- test "z$(git -c name=value config name)" = zvalue &&
test "z$(git -c core.name=value config core.name)" = zvalue &&
- test "z$(git -c CamelCase=value config camelcase)" = zvalue &&
- test "z$(git -c flag config --bool flag)" = ztrue &&
- test_must_fail git -c core.name=value config name
+ test "z$(git -c foo.CamelCase=value config foo.camelcase)" = zvalue &&
+ test "z$(git -c foo.flag config --bool foo.flag)" = ztrue &&
+ test_must_fail git -c name=value config core.name
+'
+
+test_expect_success 'key sanity-checking' '
+ test_must_fail git config foo=bar &&
+ test_must_fail git config foo=.bar &&
+ test_must_fail git config foo.ba=r &&
+ test_must_fail git config foo.1bar &&
+ test_must_fail git config foo."ba
+ z".bar &&
+ test_must_fail git config . false &&
+ test_must_fail git config .foo false &&
+ test_must_fail git config foo. false &&
+ test_must_fail git config .foo. false &&
+ git config foo.bar true &&
+ git config foo."ba =z".bar false
'
test_done
index a6bf1bf4d6182f0883ea858e28aa8a9b3dd79024..0e476624069a4ac4259f35b07f36bf0945bfe986 100755 (executable)
--- a/t/t1302-repo-version.sh
+++ b/t/t1302-repo-version.sh
(
cd test2 &&
git config core.repositoryformatversion >../actual
- )
+ ) &&
test_cmp expect actual
'
diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh
index 54ba3df95f66ecc060adaec6846877c793016aa1..ff747f8229bb46df070ea9aad2702f4d1c703420 100755 (executable)
--- a/t/t1400-update-ref.sh
+++ b/t/t1400-update-ref.sh
test_expect_success \
"fail to create $n" \
- "touch .git/$n_dir
- git update-ref $n $A >out 2>err"'
- test $? != 0'
+ "touch .git/$n_dir &&
+ test_must_fail git update-ref $n $A >out 2>err"
rm -f .git/$n_dir out err
test_expect_success \
ld="Thu, 26 May 2005 18:43:00 -0500"
test_expect_success \
'Query "master@{May 25 2005}" (before history)' \
- 'rm -f o e
+ 'rm -f o e &&
git rev-parse --verify "master@{May 25 2005}" >o 2>e &&
test '"$C"' = $(cat o) &&
test "warning: Log for '\'master\'' only goes back to $ed." = "$(cat e)"'
test_expect_success \
"Query master@{2005-05-25} (before history)" \
- 'rm -f o e
+ 'rm -f o e &&
git rev-parse --verify master@{2005-05-25} >o 2>e &&
test '"$C"' = $(cat o) &&
echo test "warning: Log for '\'master\'' only goes back to $ed." = "$(cat e)"'
test_expect_success \
'Query "master@{May 26 2005 23:31:59}" (1 second before history)' \
- 'rm -f o e
+ 'rm -f o e &&
git rev-parse --verify "master@{May 26 2005 23:31:59}" >o 2>e &&
test '"$C"' = $(cat o) &&
test "warning: Log for '\''master'\'' only goes back to $ed." = "$(cat e)"'
test_expect_success \
'Query "master@{May 26 2005 23:32:00}" (exactly history start)' \
- 'rm -f o e
+ 'rm -f o e &&
git rev-parse --verify "master@{May 26 2005 23:32:00}" >o 2>e &&
test '"$C"' = $(cat o) &&
test "" = "$(cat e)"'
test_expect_success \
'Query "master@{May 26 2005 23:32:30}" (first non-creation change)' \
- 'rm -f o e
+ 'rm -f o e &&
git rev-parse --verify "master@{May 26 2005 23:32:30}" >o 2>e &&
test '"$A"' = $(cat o) &&
test "" = "$(cat e)"'
test_expect_success \
'Query "master@{2005-05-26 23:33:01}" (middle of history with gap)' \
- 'rm -f o e
+ 'rm -f o e &&
git rev-parse --verify "master@{2005-05-26 23:33:01}" >o 2>e &&
test '"$B"' = $(cat o) &&
test "warning: Log .git/logs/'"$m has gap after $gd"'." = "$(cat e)"'
test_expect_success \
'Query "master@{2005-05-26 23:38:00}" (middle of history)' \
- 'rm -f o e
+ 'rm -f o e &&
git rev-parse --verify "master@{2005-05-26 23:38:00}" >o 2>e &&
test '"$Z"' = $(cat o) &&
test "" = "$(cat e)"'
test_expect_success \
'Query "master@{2005-05-26 23:43:00}" (exact end of history)' \
- 'rm -f o e
+ 'rm -f o e &&
git rev-parse --verify "master@{2005-05-26 23:43:00}" >o 2>e &&
test '"$E"' = $(cat o) &&
test "" = "$(cat e)"'
test_expect_success \
'Query "master@{2005-05-28}" (past end of history)' \
- 'rm -f o e
+ 'rm -f o e &&
git rev-parse --verify "master@{2005-05-28}" >o 2>e &&
test '"$D"' = $(cat o) &&
test "warning: Log .git/logs/'"$m unexpectedly ended on $ld"'." = "$(cat e)"'
git add F &&
GIT_AUTHOR_DATE="2005-05-26 23:30" \
GIT_COMMITTER_DATE="2005-05-26 23:30" git commit -m add -a &&
- h_TEST=$(git rev-parse --verify HEAD)
+ h_TEST=$(git rev-parse --verify HEAD) &&
echo The other day this did not work. >M &&
echo And then Bob told me how to fix it. >>M &&
echo OTHER >F &&
index 7fa5f5b22a28f108b3063ff9920cffb530d950e6..2c96551ed0f2d3b95d661a0ce4d3b5b5d9ec0dfb 100755 (executable)
--- a/t/t1401-symbolic-ref.sh
+++ b/t/t1401-symbolic-ref.sh
reset_to_sane
test_expect_success 'symbolic-ref refuses bare sha1' '
- echo content >file && git add file && git commit -m one
+ echo content >file && git add file && git commit -m one &&
test_must_fail git symbolic-ref HEAD `git rev-parse HEAD`
'
reset_to_sane
index 782e75d00091ebf1fda93ca5dc8e532289a15638..1b0f82fa4c7928fc4605ccf31a3ae45e6ac9f38e 100755 (executable)
T=$(git write-tree) &&
sha1=$(echo A | git commit-tree $T) &&
git update-ref refs/heads/master $sha1 &&
- git update-ref refs/remotes/origin/master $sha1
+ git update-ref refs/remotes/origin/master $sha1 &&
git checkout master &&
git checkout origin/master &&
git checkout master &&
T=$(git write-tree) &&
sha1=$(echo A | git commit-tree $T) &&
git update-ref refs/heads/master $sha1 &&
- git update-ref refs/remotes/origin/master $sha1
+ git update-ref refs/remotes/origin/master $sha1 &&
git checkout master &&
git checkout origin/master &&
git checkout master &&
diff --git a/t/t1410-reflog.sh b/t/t1410-reflog.sh
index 25046c42081c8412b56c6b739dfd3a67ab700810..252fc828374583cfb4c2346853bb87560efdf01d 100755 (executable)
--- a/t/t1410-reflog.sh
+++ b/t/t1410-reflog.sh
test_tick &&
git commit -m tiger C &&
- HEAD_entry_count=$(git reflog | wc -l)
- master_entry_count=$(git reflog show master | wc -l)
+ HEAD_entry_count=$(git reflog | wc -l) &&
+ master_entry_count=$(git reflog show master | wc -l) &&
test $HEAD_entry_count = 5 &&
test $master_entry_count = 5 &&
test $HEAD_entry_count = $(git reflog | wc -l) &&
! grep ox < output &&
- master_entry_count=$(wc -l < output)
+ master_entry_count=$(wc -l < output) &&
git reflog delete HEAD@{1} &&
test $(($HEAD_entry_count -1)) = $(git reflog | wc -l) &&
test $master_entry_count = $(git reflog show master | wc -l) &&
- HEAD_entry_count=$(git reflog | wc -l)
+ HEAD_entry_count=$(git reflog | wc -l) &&
git reflog delete master@{07.04.2005.15:15:00.-0700} &&
git reflog show master > output &&
diff --git a/t/t1450-fsck.sh b/t/t1450-fsck.sh
index 1be415e334330b2fea398cc58a0f054b6b876da1..bb01d5ab8f8ebcaf49ef238d48ae89e3845d5f4b 100755 (executable)
--- a/t/t1450-fsck.sh
+++ b/t/t1450-fsck.sh
sha=$(echo blob | git hash-object -w --stdin) &&
old=$(echo $sha | sed "s+^..+&/+") &&
new=$(dirname $old)/ffffffffffffffffffffffffffffffffffffff &&
- sha="$(dirname $new)$(basename $new)"
+ sha="$(dirname $new)$(basename $new)" &&
mv .git/objects/$old .git/objects/$new &&
test_when_finished "remove_object $sha" &&
git update-index --add --cacheinfo 100644 $sha foo &&
'
test_expect_success 'tag pointing to nonexistent' '
- cat >invalid-tag <<-\EOF
+ cat >invalid-tag <<-\EOF &&
object ffffffffffffffffffffffffffffffffffffffff
type commit
tag invalid
diff --git a/t/t1501-worktree.sh b/t/t1501-worktree.sh
index 2c8f01f6684cbe492132575209f79371ad761d87..da6252b1179c47d39393c983d88c75d84a507cb7 100755 (executable)
--- a/t/t1501-worktree.sh
+++ b/t/t1501-worktree.sh
git --git-dir="$(pwd)//repo.git" --work-tree="$(pwd)" add dummy_file
'
+test_expect_success 'relative $GIT_WORK_TREE and git subprocesses' '
+ GIT_DIR=repo.git GIT_WORK_TREE=repo.git/work \
+ test-subprocess --setup-work-tree rev-parse --show-toplevel >actual &&
+ echo "$(pwd)/repo.git/work" >expected &&
+ test_cmp expected actual
+'
+
test_done
index b3195c47070b35d555f8cfae90497a2d00d10ca7..1efd7f76ddea8dbd788032c5a9076270d1159825 100755 (executable)
EOF
test_expect_success 'test --parseopt help output' '
- git rev-parse --parseopt -- -h > output < optionspec
+ test_expect_code 129 git rev-parse --parseopt -- -h > output < optionspec &&
test_cmp expect output
'
index df5ad8c686a959c9b03355c1ebc325f3c755ed46..cce87a5ab523d3a4232e9e11863aa3a6e8ca0211 100755 (executable)
--- a/t/t1504-ceiling-dirs.sh
+++ b/t/t1504-ceiling-dirs.sh
}
test_fail() {
- test_expect_code 128 "$1: prefix" \
- "git rev-parse --show-prefix"
+ test_expect_success "$1: prefix" '
+ test_expect_code 128 git rev-parse --show-prefix
+ '
}
TRASH_ROOT="$PWD"
index 0eeeb0e45090661c0576607792ff9b91d096e282..9f8adb1f824a5bafe8829e5ab613e2ab83065f4f 100755 (executable)
test $HASH_file = $(git rev-parse :0:file.txt) )
'
+test_expect_success 'correct relative file objects (0)' '
+ git rev-parse :file.txt >expected &&
+ git rev-parse :./file.txt >result &&
+ test_cmp expected result &&
+ git rev-parse :0:./file.txt >result &&
+ test_cmp expected result
+'
+
+test_expect_success 'correct relative file objects (1)' '
+ git rev-parse HEAD:file.txt >expected &&
+ git rev-parse HEAD:./file.txt >result &&
+ test_cmp expected result
+'
+
+test_expect_success 'correct relative file objects (2)' '
+ (
+ cd subdir &&
+ git rev-parse HEAD:../file.txt >result &&
+ test_cmp ../expected result
+ )
+'
+
+test_expect_success 'correct relative file objects (3)' '
+ (
+ cd subdir &&
+ git rev-parse HEAD:../subdir/../file.txt >result &&
+ test_cmp ../expected result
+ )
+'
+
+test_expect_success 'correct relative file objects (4)' '
+ git rev-parse HEAD:subdir/file.txt >expected &&
+ (
+ cd subdir &&
+ git rev-parse HEAD:./file.txt >result &&
+ test_cmp ../expected result
+ )
+'
+
+test_expect_success 'correct relative file objects (5)' '
+ git rev-parse :subdir/file.txt >expected &&
+ (
+ cd subdir &&
+ git rev-parse :./file.txt >result &&
+ test_cmp ../expected result &&
+ git rev-parse :0:./file.txt >result &&
+ test_cmp ../expected result
+ )
+'
+
+test_expect_success 'correct relative file objects (6)' '
+ git rev-parse :file.txt >expected &&
+ (
+ cd subdir &&
+ git rev-parse :../file.txt >result &&
+ test_cmp ../expected result &&
+ git rev-parse :0:../file.txt >result &&
+ test_cmp ../expected result
+ )
+'
+
test_expect_success 'incorrect revision id' '
test_must_fail git rev-parse foobar:file.txt 2>error &&
grep "Invalid object name '"'"'foobar'"'"'." error &&
grep "fatal: Log for [^ ]* only has [0-9][0-9]* entries." error
'
+test_expect_success 'relative path not found' '
+ (
+ cd subdir &&
+ test_must_fail git rev-parse HEAD:./nonexistent.txt 2>error &&
+ grep subdir/nonexistent.txt error
+ )
+'
+
+test_expect_success 'relative path outside worktree' '
+ test_must_fail git rev-parse HEAD:../file.txt >output 2>error &&
+ test -z "$(cat output)" &&
+ grep "outside repository" error
+'
+
+test_expect_success 'relative path when cwd is outside worktree' '
+ test_must_fail git --git-dir=.git --work-tree=subdir rev-parse HEAD:./file.txt >output 2>error &&
+ test -z "$(cat output)" &&
+ grep "relative path syntax can.t be used outside working tree." error
+'
+
+test_expect_success 'relative path when startup_info is NULL' '
+ test_must_fail test-match-trees HEAD:./file.txt HEAD:./file.txt 2>error &&
+ grep "BUG: startup_info struct is not initialized." error
+'
+
test_done
index 8c8dfdaf9f037f370629f419af02aa7a9d75f613..a4555510c37276d36387d4c5c818503bb286dbd1 100755 (executable)
git branch -t new my-side@{u} &&
git merge -s ours new@{u} &&
git show -s --pretty=format:%s >actual &&
- echo "Merge remote branch ${sq}origin/side${sq}" >expect &&
+ echo "Merge remote-tracking branch ${sq}origin/side${sq}" >expect &&
test_cmp expect actual
)
'
diff --git a/t/t1510-repo-setup.sh b/t/t1510-repo-setup.sh
--- /dev/null
+++ b/t/t1510-repo-setup.sh
@@ -0,0 +1,776 @@
+#!/bin/sh
+
+test_description="Tests of cwd/prefix/worktree/gitdir setup in all cases
+
+A few rules for repo setup:
+
+1. GIT_DIR is relative to user's cwd. --git-dir is equivalent to
+ GIT_DIR.
+
+2. .git file is relative to parent directory. .git file is basically
+ symlink in disguise. The directory where .git file points to will
+ become new git_dir.
+
+3. core.worktree is relative to git_dir.
+
+4. GIT_WORK_TREE is relative to user's cwd. --work-tree is
+ equivalent to GIT_WORK_TREE.
+
+5. GIT_WORK_TREE/core.worktree was originally meant to work only if
+ GIT_DIR is set, but earlier git didn't enforce it, and some scripts
+ depend on the implementation that happened to first discover .git by
+ going up from the users $cwd and then using the specified working tree
+ that may or may not have any relation to where .git was found in. This
+ historical behaviour must be kept.
+
+6. Effective GIT_WORK_TREE overrides core.worktree and core.bare
+
+7. Effective core.worktree conflicts with core.bare
+
+8. If GIT_DIR is set but neither worktree nor bare setting is given,
+ original cwd becomes worktree.
+
+9. If .git discovery is done inside a repo, the repo becomes a bare
+ repo. .git discovery is performed if GIT_DIR is not set.
+
+10. If no worktree is available, cwd remains unchanged, prefix is
+ NULL.
+
+11. When user's cwd is outside worktree, cwd remains unchanged,
+ prefix is NULL.
+"
+. ./test-lib.sh
+
+here=$(pwd)
+
+test_repo () {
+ (
+ cd "$1" &&
+ if test -n "$2"
+ then
+ GIT_DIR="$2" &&
+ export GIT_DIR
+ fi &&
+ if test -n "$3"
+ then
+ GIT_WORK_TREE="$3" &&
+ export GIT_WORK_TREE
+ fi &&
+ rm -f trace &&
+ GIT_TRACE="$(pwd)/trace" git symbolic-ref HEAD >/dev/null &&
+ grep '^setup: ' trace >result &&
+ test_cmp expected result
+ )
+}
+
+maybe_config () {
+ file=$1 var=$2 value=$3 &&
+ if test "$value" != unset
+ then
+ git config --file="$file" "$var" "$value"
+ fi
+}
+
+setup_repo () {
+ name=$1 worktreecfg=$2 gitfile=$3 barecfg=$4 &&
+ sane_unset GIT_DIR GIT_WORK_TREE &&
+
+ git init "$name" &&
+ maybe_config "$name/.git/config" core.worktree "$worktreecfg" &&
+ maybe_config "$name/.git/config" core.bare "$barecfg" &&
+ mkdir -p "$name/sub/sub" &&
+
+ if test "${gitfile:+set}"
+ then
+ mv "$name/.git" "$name.git" &&
+ echo "gitdir: ../$name.git" >"$name/.git"
+ fi
+}
+
+maybe_set () {
+ var=$1 value=$2 &&
+ if test "$value" != unset
+ then
+ eval "$var=\$value" &&
+ export $var
+ fi
+}
+
+setup_env () {
+ worktreenv=$1 gitdirenv=$2 &&
+ sane_unset GIT_DIR GIT_WORK_TREE &&
+ maybe_set GIT_DIR "$gitdirenv" &&
+ maybe_set GIT_WORK_TREE "$worktreeenv"
+}
+
+expect () {
+ cat >"$1/expected" <<-EOF
+ setup: git_dir: $2
+ setup: worktree: $3
+ setup: cwd: $4
+ setup: prefix: $5
+ EOF
+}
+
+try_case () {
+ name=$1 worktreeenv=$2 gitdirenv=$3 &&
+ setup_env "$worktreeenv" "$gitdirenv" &&
+ expect "$name" "$4" "$5" "$6" "$7" &&
+ test_repo "$name"
+}
+
+run_wt_tests () {
+ N=$1 gitfile=$2
+
+ absgit="$here/$N/.git"
+ dotgit=.git
+ dotdotgit=../../.git
+
+ if test "$gitfile"
+ then
+ absgit="$here/$N.git"
+ dotgit=$absgit dotdotgit=$absgit
+ fi
+
+ test_expect_success "#$N: explicit GIT_WORK_TREE and GIT_DIR at toplevel" '
+ try_case $N "$here/$N" .git \
+ "$dotgit" "$here/$N" "$here/$N" "(null)" &&
+ try_case $N . .git \
+ "$dotgit" "$here/$N" "$here/$N" "(null)" &&
+ try_case $N "$here/$N" "$here/$N/.git" \
+ "$absgit" "$here/$N" "$here/$N" "(null)" &&
+ try_case $N . "$here/$N/.git" \
+ "$absgit" "$here/$N" "$here/$N" "(null)"
+ '
+
+ test_expect_success "#$N: explicit GIT_WORK_TREE and GIT_DIR in subdir" '
+ try_case $N/sub/sub "$here/$N" ../../.git \
+ "$absgit" "$here/$N" "$here/$N" sub/sub/ &&
+ try_case $N/sub/sub ../.. ../../.git \
+ "$absgit" "$here/$N" "$here/$N" sub/sub/ &&
+ try_case $N/sub/sub "$here/$N" "$here/$N/.git" \
+ "$absgit" "$here/$N" "$here/$N" sub/sub/ &&
+ try_case $N/sub/sub ../.. "$here/$N/.git" \
+ "$absgit" "$here/$N" "$here/$N" sub/sub/
+ '
+
+ test_expect_success "#$N: explicit GIT_WORK_TREE from parent of worktree" '
+ try_case $N "$here/$N/wt" .git \
+ "$dotgit" "$here/$N/wt" "$here/$N" "(null)" &&
+ try_case $N wt .git \
+ "$dotgit" "$here/$N/wt" "$here/$N" "(null)" &&
+ try_case $N wt "$here/$N/.git" \
+ "$absgit" "$here/$N/wt" "$here/$N" "(null)" &&
+ try_case $N "$here/$N/wt" "$here/$N/.git" \
+ "$absgit" "$here/$N/wt" "$here/$N" "(null)"
+ '
+
+ test_expect_success "#$N: explicit GIT_WORK_TREE from nephew of worktree" '
+ try_case $N/sub/sub "$here/$N/wt" ../../.git \
+ "$dotdotgit" "$here/$N/wt" "$here/$N/sub/sub" "(null)" &&
+ try_case $N/sub/sub ../../wt ../../.git \
+ "$dotdotgit" "$here/$N/wt" "$here/$N/sub/sub" "(null)" &&
+ try_case $N/sub/sub ../../wt "$here/$N/.git" \
+ "$absgit" "$here/$N/wt" "$here/$N/sub/sub" "(null)" &&
+ try_case $N/sub/sub "$here/$N/wt" "$here/$N/.git" \
+ "$absgit" "$here/$N/wt" "$here/$N/sub/sub" "(null)"
+ '
+
+ test_expect_success "#$N: chdir_to_toplevel uses worktree, not git dir" '
+ try_case $N "$here" .git \
+ "$absgit" "$here" "$here" $N/ &&
+ try_case $N .. .git \
+ "$absgit" "$here" "$here" $N/ &&
+ try_case $N .. "$here/$N/.git" \
+ "$absgit" "$here" "$here" $N/ &&
+ try_case $N "$here" "$here/$N/.git" \
+ "$absgit" "$here" "$here" $N/
+ '
+
+ test_expect_success "#$N: chdir_to_toplevel uses worktree (from subdir)" '
+ try_case $N/sub/sub "$here" ../../.git \
+ "$absgit" "$here" "$here" $N/sub/sub/ &&
+ try_case $N/sub/sub ../../.. ../../.git \
+ "$absgit" "$here" "$here" $N/sub/sub/ &&
+ try_case $N/sub/sub ../../../ "$here/$N/.git" \
+ "$absgit" "$here" "$here" $N/sub/sub/ &&
+ try_case $N/sub/sub "$here" "$here/$N/.git" \
+ "$absgit" "$here" "$here" $N/sub/sub/
+ '
+}
+
+# try_repo #c GIT_WORK_TREE GIT_DIR core.worktree .gitfile? core.bare \
+# (git dir) (work tree) (cwd) (prefix) \ <-- at toplevel
+# (git dir) (work tree) (cwd) (prefix) <-- from subdir
+try_repo () {
+ name=$1 worktreeenv=$2 gitdirenv=$3 &&
+ setup_repo "$name" "$4" "$5" "$6" &&
+ shift 6 &&
+ try_case "$name" "$worktreeenv" "$gitdirenv" \
+ "$1" "$2" "$3" "$4" &&
+ shift 4 &&
+ case "$gitdirenv" in
+ /* | ?:/* | unset) ;;
+ *)
+ gitdirenv=../$gitdirenv ;;
+ esac &&
+ try_case "$name/sub" "$worktreeenv" "$gitdirenv" \
+ "$1" "$2" "$3" "$4"
+}
+
+# Bit 0 = GIT_WORK_TREE
+# Bit 1 = GIT_DIR
+# Bit 2 = core.worktree
+# Bit 3 = .git is a file
+# Bit 4 = bare repo
+# Case# = encoding of the above 5 bits
+
+test_expect_success '#0: nonbare repo, no explicit configuration' '
+ try_repo 0 unset unset unset "" unset \
+ .git "$here/0" "$here/0" "(null)" \
+ .git "$here/0" "$here/0" sub/ 2>message &&
+ ! test -s message
+'
+
+test_expect_success '#1: GIT_WORK_TREE without explicit GIT_DIR is accepted' '
+ mkdir -p wt &&
+ try_repo 1 "$here" unset unset "" unset \
+ "$here/1/.git" "$here" "$here" 1/ \
+ "$here/1/.git" "$here" "$here" 1/sub/ 2>message &&
+ ! test -s message
+'
+
+test_expect_success '#2: worktree defaults to cwd with explicit GIT_DIR' '
+ try_repo 2 unset "$here/2/.git" unset "" unset \
+ "$here/2/.git" "$here/2" "$here/2" "(null)" \
+ "$here/2/.git" "$here/2/sub" "$here/2/sub" "(null)"
+'
+
+test_expect_success '#2b: relative GIT_DIR' '
+ try_repo 2b unset ".git" unset "" unset \
+ ".git" "$here/2b" "$here/2b" "(null)" \
+ "../.git" "$here/2b/sub" "$here/2b/sub" "(null)"
+'
+
+test_expect_success '#3: setup' '
+ setup_repo 3 unset "" unset &&
+ mkdir -p 3/sub/sub 3/wt/sub
+'
+run_wt_tests 3
+
+test_expect_success '#4: core.worktree without GIT_DIR set is accepted' '
+ setup_repo 4 ../sub "" unset &&
+ mkdir -p 4/sub sub &&
+ try_case 4 unset unset \
+ .git "$here/4/sub" "$here/4" "(null)" \
+ "$here/4/.git" "$here/4/sub" "$here/4/sub" "(null)" 2>message &&
+ ! test -s message
+'
+
+test_expect_success '#5: core.worktree + GIT_WORK_TREE is accepted' '
+ # or: you cannot intimidate away the lack of GIT_DIR setting
+ try_repo 5 "$here" unset "$here/5" "" unset \
+ "$here/5/.git" "$here" "$here" 5/ \
+ "$here/5/.git" "$here" "$here" 5/sub/ 2>message &&
+ try_repo 5a .. unset "$here/5a" "" unset \
+ "$here/5a/.git" "$here" "$here" 5a/ \
+ "$here/5a/.git" "$here/5a" "$here/5a" sub/ &&
+ ! test -s message
+'
+
+test_expect_success '#6: setting GIT_DIR brings core.worktree to life' '
+ setup_repo 6 "$here/6" "" unset &&
+ try_case 6 unset .git \
+ .git "$here/6" "$here/6" "(null)" &&
+ try_case 6 unset "$here/6/.git" \
+ "$here/6/.git" "$here/6" "$here/6" "(null)" &&
+ try_case 6/sub/sub unset ../../.git \
+ "$here/6/.git" "$here/6" "$here/6" sub/sub/ &&
+ try_case 6/sub/sub unset "$here/6/.git" \
+ "$here/6/.git" "$here/6" "$here/6" sub/sub/
+'
+
+test_expect_success '#6b: GIT_DIR set, core.worktree relative' '
+ setup_repo 6b .. "" unset &&
+ try_case 6b unset .git \
+ .git "$here/6b" "$here/6b" "(null)" &&
+ try_case 6b unset "$here/6b/.git" \
+ "$here/6b/.git" "$here/6b" "$here/6b" "(null)" &&
+ try_case 6b/sub/sub unset ../../.git \
+ "$here/6b/.git" "$here/6b" "$here/6b" sub/sub/ &&
+ try_case 6b/sub/sub unset "$here/6b/.git" \
+ "$here/6b/.git" "$here/6b" "$here/6b" sub/sub/
+'
+
+test_expect_success '#6c: GIT_DIR set, core.worktree=../wt (absolute)' '
+ setup_repo 6c "$here/6c/wt" "" unset &&
+ mkdir -p 6c/wt/sub &&
+
+ try_case 6c unset .git \
+ .git "$here/6c/wt" "$here/6c" "(null)" &&
+ try_case 6c unset "$here/6c/.git" \
+ "$here/6c/.git" "$here/6c/wt" "$here/6c" "(null)" &&
+ try_case 6c/sub/sub unset ../../.git \
+ ../../.git "$here/6c/wt" "$here/6c/sub/sub" "(null)" &&
+ try_case 6c/sub/sub unset "$here/6c/.git" \
+ "$here/6c/.git" "$here/6c/wt" "$here/6c/sub/sub" "(null)"
+'
+
+test_expect_success '#6d: GIT_DIR set, core.worktree=../wt (relative)' '
+ setup_repo 6d "$here/6d/wt" "" unset &&
+ mkdir -p 6d/wt/sub &&
+
+ try_case 6d unset .git \
+ .git "$here/6d/wt" "$here/6d" "(null)" &&
+ try_case 6d unset "$here/6d/.git" \
+ "$here/6d/.git" "$here/6d/wt" "$here/6d" "(null)" &&
+ try_case 6d/sub/sub unset ../../.git \
+ ../../.git "$here/6d/wt" "$here/6d/sub/sub" "(null)" &&
+ try_case 6d/sub/sub unset "$here/6d/.git" \
+ "$here/6d/.git" "$here/6d/wt" "$here/6d/sub/sub" "(null)"
+'
+
+test_expect_success '#6e: GIT_DIR set, core.worktree=../.. (absolute)' '
+ setup_repo 6e "$here" "" unset &&
+ try_case 6e unset .git \
+ "$here/6e/.git" "$here" "$here" 6e/ &&
+ try_case 6e unset "$here/6e/.git" \
+ "$here/6e/.git" "$here" "$here" 6e/ &&
+ try_case 6e/sub/sub unset ../../.git \
+ "$here/6e/.git" "$here" "$here" 6e/sub/sub/ &&
+ try_case 6e/sub/sub unset "$here/6e/.git" \
+ "$here/6e/.git" "$here" "$here" 6e/sub/sub/
+'
+
+test_expect_success '#6f: GIT_DIR set, core.worktree=../.. (relative)' '
+ setup_repo 6f ../../ "" unset &&
+ try_case 6f unset .git \
+ "$here/6f/.git" "$here" "$here" 6f/ &&
+ try_case 6f unset "$here/6f/.git" \
+ "$here/6f/.git" "$here" "$here" 6f/ &&
+ try_case 6f/sub/sub unset ../../.git \
+ "$here/6f/.git" "$here" "$here" 6f/sub/sub/ &&
+ try_case 6f/sub/sub unset "$here/6f/.git" \
+ "$here/6f/.git" "$here" "$here" 6f/sub/sub/
+'
+
+# case #7: GIT_WORK_TREE overrides core.worktree.
+test_expect_success '#7: setup' '
+ setup_repo 7 non-existent "" unset &&
+ mkdir -p 7/sub/sub 7/wt/sub
+'
+run_wt_tests 7
+
+test_expect_success '#8: gitfile, easy case' '
+ try_repo 8 unset unset unset gitfile unset \
+ "$here/8.git" "$here/8" "$here/8" "(null)" \
+ "$here/8.git" "$here/8" "$here/8" sub/
+'
+
+test_expect_success '#9: GIT_WORK_TREE accepted with gitfile' '
+ mkdir -p 9/wt &&
+ try_repo 9 wt unset unset gitfile unset \
+ "$here/9.git" "$here/9/wt" "$here/9" "(null)" \
+ "$here/9.git" "$here/9/sub/wt" "$here/9/sub" "(null)" 2>message &&
+ ! test -s message
+'
+
+test_expect_success '#10: GIT_DIR can point to gitfile' '
+ try_repo 10 unset "$here/10/.git" unset gitfile unset \
+ "$here/10.git" "$here/10" "$here/10" "(null)" \
+ "$here/10.git" "$here/10/sub" "$here/10/sub" "(null)"
+'
+
+test_expect_success '#10b: relative GIT_DIR can point to gitfile' '
+ try_repo 10b unset .git unset gitfile unset \
+ "$here/10b.git" "$here/10b" "$here/10b" "(null)" \
+ "$here/10b.git" "$here/10b/sub" "$here/10b/sub" "(null)"
+'
+
+# case #11: GIT_WORK_TREE works, gitfile case.
+test_expect_success '#11: setup' '
+ setup_repo 11 unset gitfile unset &&
+ mkdir -p 11/sub/sub 11/wt/sub
+'
+run_wt_tests 11 gitfile
+
+test_expect_success '#12: core.worktree with gitfile is accepted' '
+ try_repo 12 unset unset "$here/12" gitfile unset \
+ "$here/12.git" "$here/12" "$here/12" "(null)" \
+ "$here/12.git" "$here/12" "$here/12" sub/ 2>message &&
+ ! test -s message
+'
+
+test_expect_success '#13: core.worktree+GIT_WORK_TREE accepted (with gitfile)' '
+ # or: you cannot intimidate away the lack of GIT_DIR setting
+ try_repo 13 non-existent-too unset non-existent gitfile unset \
+ "$here/13.git" "$here/13/non-existent-too" "$here/13" "(null)" \
+ "$here/13.git" "$here/13/sub/non-existent-too" "$here/13/sub" "(null)" 2>message &&
+ ! test -s message
+'
+
+# case #14.
+# If this were more table-driven, it could share code with case #6.
+
+test_expect_success '#14: core.worktree with GIT_DIR pointing to gitfile' '
+ setup_repo 14 "$here/14" gitfile unset &&
+ try_case 14 unset .git \
+ "$here/14.git" "$here/14" "$here/14" "(null)" &&
+ try_case 14 unset "$here/14/.git" \
+ "$here/14.git" "$here/14" "$here/14" "(null)" &&
+ try_case 14/sub/sub unset ../../.git \
+ "$here/14.git" "$here/14" "$here/14" sub/sub/ &&
+ try_case 14/sub/sub unset "$here/14/.git" \
+ "$here/14.git" "$here/14" "$here/14" sub/sub/ &&
+
+ setup_repo 14c "$here/14c/wt" gitfile unset &&
+ mkdir -p 14c/wt/sub &&
+
+ try_case 14c unset .git \
+ "$here/14c.git" "$here/14c/wt" "$here/14c" "(null)" &&
+ try_case 14c unset "$here/14c/.git" \
+ "$here/14c.git" "$here/14c/wt" "$here/14c" "(null)" &&
+ try_case 14c/sub/sub unset ../../.git \
+ "$here/14c.git" "$here/14c/wt" "$here/14c/sub/sub" "(null)" &&
+ try_case 14c/sub/sub unset "$here/14c/.git" \
+ "$here/14c.git" "$here/14c/wt" "$here/14c/sub/sub" "(null)" &&
+
+ setup_repo 14d "$here/14d/wt" gitfile unset &&
+ mkdir -p 14d/wt/sub &&
+
+ try_case 14d unset .git \
+ "$here/14d.git" "$here/14d/wt" "$here/14d" "(null)" &&
+ try_case 14d unset "$here/14d/.git" \
+ "$here/14d.git" "$here/14d/wt" "$here/14d" "(null)" &&
+ try_case 14d/sub/sub unset ../../.git \
+ "$here/14d.git" "$here/14d/wt" "$here/14d/sub/sub" "(null)" &&
+ try_case 14d/sub/sub unset "$here/14d/.git" \
+ "$here/14d.git" "$here/14d/wt" "$here/14d/sub/sub" "(null)" &&
+
+ setup_repo 14e "$here" gitfile unset &&
+ try_case 14e unset .git \
+ "$here/14e.git" "$here" "$here" 14e/ &&
+ try_case 14e unset "$here/14e/.git" \
+ "$here/14e.git" "$here" "$here" 14e/ &&
+ try_case 14e/sub/sub unset ../../.git \
+ "$here/14e.git" "$here" "$here" 14e/sub/sub/ &&
+ try_case 14e/sub/sub unset "$here/14e/.git" \
+ "$here/14e.git" "$here" "$here" 14e/sub/sub/
+'
+
+test_expect_success '#14b: core.worktree is relative to actual git dir' '
+ setup_repo 14b ../14b gitfile unset &&
+ try_case 14b unset .git \
+ "$here/14b.git" "$here/14b" "$here/14b" "(null)" &&
+ try_case 14b unset "$here/14b/.git" \
+ "$here/14b.git" "$here/14b" "$here/14b" "(null)" &&
+ try_case 14b/sub/sub unset ../../.git \
+ "$here/14b.git" "$here/14b" "$here/14b" sub/sub/ &&
+ try_case 14b/sub/sub unset "$here/14b/.git" \
+ "$here/14b.git" "$here/14b" "$here/14b" sub/sub/ &&
+
+ setup_repo 14f ../ gitfile unset &&
+ try_case 14f unset .git \
+ "$here/14f.git" "$here" "$here" 14f/ &&
+ try_case 14f unset "$here/14f/.git" \
+ "$here/14f.git" "$here" "$here" 14f/ &&
+ try_case 14f/sub/sub unset ../../.git \
+ "$here/14f.git" "$here" "$here" 14f/sub/sub/ &&
+ try_case 14f/sub/sub unset "$here/14f/.git" \
+ "$here/14f.git" "$here" "$here" 14f/sub/sub/
+'
+
+# case #15: GIT_WORK_TREE overrides core.worktree (gitfile case).
+test_expect_success '#15: setup' '
+ setup_repo 15 non-existent gitfile unset &&
+ mkdir -p 15/sub/sub 15/wt/sub
+'
+run_wt_tests 15 gitfile
+
+test_expect_success '#16a: implicitly bare repo (cwd inside .git dir)' '
+ setup_repo 16a unset "" unset &&
+ mkdir -p 16a/.git/wt/sub &&
+
+ try_case 16a/.git unset unset \
+ . "(null)" "$here/16a/.git" "(null)" &&
+ try_case 16a/.git/wt unset unset \
+ "$here/16a/.git" "(null)" "$here/16a/.git/wt" "(null)" &&
+ try_case 16a/.git/wt/sub unset unset \
+ "$here/16a/.git" "(null)" "$here/16a/.git/wt/sub" "(null)"
+'
+
+test_expect_success '#16b: bare .git (cwd inside .git dir)' '
+ setup_repo 16b unset "" true &&
+ mkdir -p 16b/.git/wt/sub &&
+
+ try_case 16b/.git unset unset \
+ . "(null)" "$here/16b/.git" "(null)" &&
+ try_case 16b/.git/wt unset unset \
+ "$here/16b/.git" "(null)" "$here/16b/.git/wt" "(null)" &&
+ try_case 16b/.git/wt/sub unset unset \
+ "$here/16b/.git" "(null)" "$here/16b/.git/wt/sub" "(null)"
+'
+
+test_expect_success '#16c: bare .git has no worktree' '
+ try_repo 16c unset unset unset "" true \
+ .git "(null)" "$here/16c" "(null)" \
+ "$here/16c/.git" "(null)" "$here/16c/sub" "(null)"
+'
+
+test_expect_success '#17: GIT_WORK_TREE without explicit GIT_DIR is accepted (bare case)' '
+ # Just like #16.
+ setup_repo 17a unset "" true &&
+ setup_repo 17b unset "" true &&
+ mkdir -p 17a/.git/wt/sub &&
+ mkdir -p 17b/.git/wt/sub &&
+
+ try_case 17a/.git "$here/17a" unset \
+ "$here/17a/.git" "$here/17a" "$here/17a" .git/ \
+ 2>message &&
+ try_case 17a/.git/wt "$here/17a" unset \
+ "$here/17a/.git" "$here/17a" "$here/17a" .git/wt/ &&
+ try_case 17a/.git/wt/sub "$here/17a" unset \
+ "$here/17a/.git" "$here/17a" "$here/17a" .git/wt/sub/ &&
+
+ try_case 17b/.git "$here/17b" unset \
+ "$here/17b/.git" "$here/17b" "$here/17b" .git/ &&
+ try_case 17b/.git/wt "$here/17b" unset \
+ "$here/17b/.git" "$here/17b" "$here/17b" .git/wt/ &&
+ try_case 17b/.git/wt/sub "$here/17b" unset \
+ "$here/17b/.git" "$here/17b" "$here/17b" .git/wt/sub/ &&
+
+ try_repo 17c "$here/17c" unset unset "" true \
+ .git "$here/17c" "$here/17c" "(null)" \
+ "$here/17c/.git" "$here/17c" "$here/17c" sub/ 2>message &&
+ ! test -s message
+'
+
+test_expect_success '#18: bare .git named by GIT_DIR has no worktree' '
+ try_repo 18 unset .git unset "" true \
+ .git "(null)" "$here/18" "(null)" \
+ ../.git "(null)" "$here/18/sub" "(null)" &&
+ try_repo 18b unset "$here/18b/.git" unset "" true \
+ "$here/18b/.git" "(null)" "$here/18b" "(null)" \
+ "$here/18b/.git" "(null)" "$here/18b/sub" "(null)"
+'
+
+# Case #19: GIT_DIR + GIT_WORK_TREE suppresses bareness.
+test_expect_success '#19: setup' '
+ setup_repo 19 unset "" true &&
+ mkdir -p 19/sub/sub 19/wt/sub
+'
+run_wt_tests 19
+
+test_expect_success '#20a: core.worktree without GIT_DIR accepted (inside .git)' '
+ # Unlike case #16a.
+ setup_repo 20a "$here/20a" "" unset &&
+ mkdir -p 20a/.git/wt/sub &&
+ try_case 20a/.git unset unset \
+ "$here/20a/.git" "$here/20a" "$here/20a" .git/ 2>message &&
+ try_case 20a/.git/wt unset unset \
+ "$here/20a/.git" "$here/20a" "$here/20a" .git/wt/ &&
+ try_case 20a/.git/wt/sub unset unset \
+ "$here/20a/.git" "$here/20a" "$here/20a" .git/wt/sub/ &&
+ ! test -s message
+'
+
+test_expect_success '#20b/c: core.worktree and core.bare conflict' '
+ setup_repo 20b non-existent "" true &&
+ mkdir -p 20b/.git/wt/sub &&
+ (
+ cd 20b/.git &&
+ test_must_fail git symbolic-ref HEAD >/dev/null
+ ) 2>message &&
+ grep "core.bare and core.worktree" message
+'
+
+# Case #21: core.worktree/GIT_WORK_TREE overrides core.bare' '
+test_expect_success '#21: setup, core.worktree warns before overriding core.bare' '
+ setup_repo 21 non-existent "" unset &&
+ mkdir -p 21/.git/wt/sub &&
+ (
+ cd 21/.git &&
+ GIT_WORK_TREE="$here/21" &&
+ export GIT_WORK_TREE &&
+ git symbolic-ref HEAD >/dev/null
+ ) 2>message &&
+ ! test -s message
+
+'
+run_wt_tests 21
+
+test_expect_success '#22a: core.worktree = GIT_DIR = .git dir' '
+ # like case #6.
+
+ setup_repo 22a "$here/22a/.git" "" unset &&
+ setup_repo 22ab . "" unset
+ mkdir -p 22a/.git/sub 22a/sub &&
+ mkdir -p 22ab/.git/sub 22ab/sub &&
+ try_case 22a/.git unset . \
+ . "$here/22a/.git" "$here/22a/.git" "(null)" &&
+ try_case 22a/.git unset "$here/22a/.git" \
+ "$here/22a/.git" "$here/22a/.git" "$here/22a/.git" "(null)" &&
+ try_case 22a/.git/sub unset .. \
+ "$here/22a/.git" "$here/22a/.git" "$here/22a/.git" sub/ &&
+ try_case 22a/.git/sub unset "$here/22a/.git" \
+ "$here/22a/.git" "$here/22a/.git" "$here/22a/.git" sub/ &&
+
+ try_case 22ab/.git unset . \
+ . "$here/22ab/.git" "$here/22ab/.git" "(null)" &&
+ try_case 22ab/.git unset "$here/22ab/.git" \
+ "$here/22ab/.git" "$here/22ab/.git" "$here/22ab/.git" "(null)" &&
+ try_case 22ab/.git/sub unset .. \
+ "$here/22ab/.git" "$here/22ab/.git" "$here/22ab/.git" sub/ &&
+ try_case 22ab/.git unset "$here/22ab/.git" \
+ "$here/22ab/.git" "$here/22ab/.git" "$here/22ab/.git" "(null)"
+'
+
+test_expect_success '#22b: core.worktree child of .git, GIT_DIR=.git' '
+ setup_repo 22b "$here/22b/.git/wt" "" unset &&
+ setup_repo 22bb wt "" unset &&
+ mkdir -p 22b/.git/sub 22b/sub 22b/.git/wt/sub 22b/wt/sub &&
+ mkdir -p 22bb/.git/sub 22bb/sub 22bb/.git/wt 22bb/wt &&
+
+ try_case 22b/.git unset . \
+ . "$here/22b/.git/wt" "$here/22b/.git" "(null)" &&
+ try_case 22b/.git unset "$here/22b/.git" \
+ "$here/22b/.git" "$here/22b/.git/wt" "$here/22b/.git" "(null)" &&
+ try_case 22b/.git/sub unset .. \
+ .. "$here/22b/.git/wt" "$here/22b/.git/sub" "(null)" &&
+ try_case 22b/.git/sub unset "$here/22b/.git" \
+ "$here/22b/.git" "$here/22b/.git/wt" "$here/22b/.git/sub" "(null)" &&
+
+ try_case 22bb/.git unset . \
+ . "$here/22bb/.git/wt" "$here/22bb/.git" "(null)" &&
+ try_case 22bb/.git unset "$here/22bb/.git" \
+ "$here/22bb/.git" "$here/22bb/.git/wt" "$here/22bb/.git" "(null)" &&
+ try_case 22bb/.git/sub unset .. \
+ .. "$here/22bb/.git/wt" "$here/22bb/.git/sub" "(null)" &&
+ try_case 22bb/.git/sub unset "$here/22bb/.git" \
+ "$here/22bb/.git" "$here/22bb/.git/wt" "$here/22bb/.git/sub" "(null)"
+'
+
+test_expect_success '#22c: core.worktree = .git/.., GIT_DIR=.git' '
+ setup_repo 22c "$here/22c" "" unset &&
+ setup_repo 22cb .. "" unset &&
+ mkdir -p 22c/.git/sub 22c/sub &&
+ mkdir -p 22cb/.git/sub 22cb/sub &&
+
+ try_case 22c/.git unset . \
+ "$here/22c/.git" "$here/22c" "$here/22c" .git/ &&
+ try_case 22c/.git unset "$here/22c/.git" \
+ "$here/22c/.git" "$here/22c" "$here/22c" .git/ &&
+ try_case 22c/.git/sub unset .. \
+ "$here/22c/.git" "$here/22c" "$here/22c" .git/sub/ &&
+ try_case 22c/.git/sub unset "$here/22c/.git" \
+ "$here/22c/.git" "$here/22c" "$here/22c" .git/sub/ &&
+
+ try_case 22cb/.git unset . \
+ "$here/22cb/.git" "$here/22cb" "$here/22cb" .git/ &&
+ try_case 22cb/.git unset "$here/22cb/.git" \
+ "$here/22cb/.git" "$here/22cb" "$here/22cb" .git/ &&
+ try_case 22cb/.git/sub unset .. \
+ "$here/22cb/.git" "$here/22cb" "$here/22cb" .git/sub/ &&
+ try_case 22cb/.git/sub unset "$here/22cb/.git" \
+ "$here/22cb/.git" "$here/22cb" "$here/22cb" .git/sub/
+'
+
+test_expect_success '#22.2: core.worktree and core.bare conflict' '
+ setup_repo 22 "$here/22" "" true &&
+ (
+ cd 22/.git &&
+ GIT_DIR=. &&
+ export GIT_DIR &&
+ test_must_fail git symbolic-ref HEAD 2>result
+ ) &&
+ (
+ cd 22 &&
+ GIT_DIR=.git &&
+ export GIT_DIR &&
+ test_must_fail git symbolic-ref HEAD 2>result
+ ) &&
+ grep "core.bare and core.worktree" 22/.git/result &&
+ grep "core.bare and core.worktree" 22/result
+'
+
+# Case #23: GIT_DIR + GIT_WORK_TREE(+core.worktree) suppresses bareness.
+test_expect_success '#23: setup' '
+ setup_repo 23 non-existent "" true &&
+ mkdir -p 23/sub/sub 23/wt/sub
+'
+run_wt_tests 23
+
+test_expect_success '#24: bare repo has no worktree (gitfile case)' '
+ try_repo 24 unset unset unset gitfile true \
+ "$here/24.git" "(null)" "$here/24" "(null)" \
+ "$here/24.git" "(null)" "$here/24/sub" "(null)"
+'
+
+test_expect_success '#25: GIT_WORK_TREE accepted if GIT_DIR unset (bare gitfile case)' '
+ try_repo 25 "$here/25" unset unset gitfile true \
+ "$here/25.git" "$here/25" "$here/25" "(null)" \
+ "$here/25.git" "$here/25" "$here/25" "sub/" 2>message &&
+ ! test -s message
+'
+
+test_expect_success '#26: bare repo has no worktree (GIT_DIR -> gitfile case)' '
+ try_repo 26 unset "$here/26/.git" unset gitfile true \
+ "$here/26.git" "(null)" "$here/26" "(null)" \
+ "$here/26.git" "(null)" "$here/26/sub" "(null)" &&
+ try_repo 26b unset .git unset gitfile true \
+ "$here/26b.git" "(null)" "$here/26b" "(null)" \
+ "$here/26b.git" "(null)" "$here/26b/sub" "(null)"
+'
+
+# Case #27: GIT_DIR + GIT_WORK_TREE suppresses bareness (with gitfile).
+test_expect_success '#27: setup' '
+ setup_repo 27 unset gitfile true &&
+ mkdir -p 27/sub/sub 27/wt/sub
+'
+run_wt_tests 27 gitfile
+
+test_expect_success '#28: core.worktree and core.bare conflict (gitfile case)' '
+ setup_repo 28 "$here/28" gitfile true &&
+ (
+ cd 28 &&
+ test_must_fail git symbolic-ref HEAD
+ ) 2>message &&
+ ! grep "^warning:" message &&
+ grep "core.bare and core.worktree" message
+'
+
+# Case #29: GIT_WORK_TREE(+core.worktree) overrides core.bare (gitfile case).
+test_expect_success '#29: setup' '
+ setup_repo 29 non-existent gitfile true &&
+ mkdir -p 29/sub/sub 29/wt/sub
+ (
+ cd 29 &&
+ GIT_WORK_TREE="$here/29" &&
+ export GIT_WORK_TREE &&
+ git symbolic-ref HEAD >/dev/null
+ ) 2>message &&
+ ! test -s message
+'
+run_wt_tests 29 gitfile
+
+test_expect_success '#30: core.worktree and core.bare conflict (gitfile version)' '
+ # Just like case #22.
+ setup_repo 30 "$here/30" gitfile true &&
+ (
+ cd 30 &&
+ GIT_DIR=.git &&
+ export GIT_DIR &&
+ test_must_fail git symbolic-ref HEAD 2>result
+ ) &&
+ grep "core.bare and core.worktree" 30/result
+'
+
+# Case #31: GIT_DIR + GIT_WORK_TREE(+core.worktree) suppresses
+# bareness (gitfile version).
+test_expect_success '#31: setup' '
+ setup_repo 31 non-existent gitfile true &&
+ mkdir -p 31/sub/sub 31/wt/sub
+'
+run_wt_tests 31 gitfile
+
+test_done
diff --git a/t/t1511-rev-parse-caret.sh b/t/t1511-rev-parse-caret.sh
--- /dev/null
@@ -0,0 +1,73 @@
+#!/bin/sh
+
+test_description='tests for ref^{stuff}'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+ echo blob >a-blob &&
+ git tag -a -m blob blob-tag `git hash-object -w a-blob`
+ mkdir a-tree &&
+ echo moreblobs >a-tree/another-blob &&
+ git add . &&
+ TREE_SHA1=`git write-tree` &&
+ git tag -a -m tree tree-tag "$TREE_SHA1" &&
+ git commit -m Initial &&
+ git tag -a -m commit commit-tag &&
+ git branch ref &&
+ git checkout master &&
+ echo modified >>a-blob &&
+ git add -u &&
+ git commit -m Modified
+'
+
+test_expect_success 'ref^{non-existent}' '
+ test_must_fail git rev-parse ref^{non-existent}
+'
+
+test_expect_success 'ref^{}' '
+ git rev-parse ref >expected &&
+ git rev-parse ref^{} >actual &&
+ test_cmp expected actual &&
+ git rev-parse commit-tag^{} >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'ref^{commit}' '
+ git rev-parse ref >expected &&
+ git rev-parse ref^{commit} >actual &&
+ test_cmp expected actual &&
+ git rev-parse commit-tag^{commit} >actual &&
+ test_cmp expected actual &&
+ test_must_fail git rev-parse tree-tag^{commit} &&
+ test_must_fail git rev-parse blob-tag^{commit}
+'
+
+test_expect_success 'ref^{tree}' '
+ echo $TREE_SHA1 >expected &&
+ git rev-parse ref^{tree} >actual &&
+ test_cmp expected actual &&
+ git rev-parse commit-tag^{tree} >actual &&
+ test_cmp expected actual &&
+ git rev-parse tree-tag^{tree} >actual &&
+ test_cmp expected actual &&
+ test_must_fail git rev-parse blob-tag^{tree}
+'
+
+test_expect_success 'ref^{/.}' '
+ git rev-parse master >expected &&
+ git rev-parse master^{/.} >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'ref^{/non-existent}' '
+ test_must_fail git rev-parse master^{/non-existent}
+'
+
+test_expect_success 'ref^{/Initial}' '
+ git rev-parse ref >expected &&
+ git rev-parse master^{/Initial} >actual &&
+ test_cmp expected actual
+'
+
+test_done
diff --git a/t/t2006-checkout-index-basic.sh b/t/t2006-checkout-index-basic.sh
--- /dev/null
@@ -0,0 +1,24 @@
+#!/bin/sh
+
+test_description='basic checkout-index tests
+'
+
+. ./test-lib.sh
+
+test_expect_success 'checkout-index --gobbledegook' '
+ test_expect_code 129 git checkout-index --gobbledegook 2>err &&
+ grep "[Uu]sage" err
+'
+
+test_expect_success 'checkout-index -h in broken repository' '
+ mkdir broken &&
+ (
+ cd broken &&
+ git init &&
+ >.git/index &&
+ test_expect_code 129 git checkout-index -h >usage 2>&1
+ ) &&
+ grep "[Uu]sage" broken/usage
+'
+
+test_done
index a74ee227b80391b7d8e2b86159a8374663cb674d..e6f59f1914667f0001fe990656a66bb76e14a41d 100755 (executable)
git branch side &&
echo goodbye >nitfol &&
- git add nitfol
+ git add nitfol &&
test_tick &&
git commit -m "master adds file nitfol" &&
index a463b13b27fec87e0f4adb1154fea26b0eee73d9..9cd0ac4ba3f14dc85b35fa14e20ceb16f191f724 100755 (executable)
'
test_expect_success PERL 'git checkout -p with staged changes' '
- set_state dir/foo work index
+ set_state dir/foo work index &&
(echo n; echo y) | git checkout -p &&
verify_saved_state bar &&
verify_state dir/foo index index
index 2d2f63f22ee59d8dc24dafaa5224928ad62dea0c..0e3b8582f2a3edebb0d9eea7fcebaa88f305fd73 100755 (executable)
test_expect_success 'Setup' '
echo "Initial" >"$TEST_FILE" &&
git add "$TEST_FILE" &&
- git commit -m "First Commit"
+ git commit -m "First Commit" &&
test_tick &&
echo "State 1" >>"$TEST_FILE" &&
git add "$TEST_FILE" &&
diff --git a/t/t2019-checkout-ambiguous-ref.sh b/t/t2019-checkout-ambiguous-ref.sh
--- /dev/null
@@ -0,0 +1,59 @@
+#!/bin/sh
+
+test_description='checkout handling of ambiguous (branch/tag) refs'
+. ./test-lib.sh
+
+test_expect_success 'setup ambiguous refs' '
+ test_commit branch file &&
+ git branch ambiguity &&
+ git branch vagueness &&
+ test_commit tag file &&
+ git tag ambiguity &&
+ git tag vagueness HEAD:file &&
+ test_commit other file
+'
+
+test_expect_success 'checkout ambiguous ref succeeds' '
+ git checkout ambiguity >stdout 2>stderr
+'
+
+test_expect_success 'checkout produces ambiguity warning' '
+ grep "warning.*ambiguous" stderr
+'
+
+test_expect_success 'checkout chooses branch over tag' '
+ echo refs/heads/ambiguity >expect &&
+ git symbolic-ref HEAD >actual &&
+ test_cmp expect actual &&
+ echo branch >expect &&
+ test_cmp expect file
+'
+
+test_expect_success 'checkout reports switch to branch' '
+ grep "Switched to branch" stderr &&
+ ! grep "^HEAD is now at" stderr
+'
+
+test_expect_success 'checkout vague ref succeeds' '
+ git checkout vagueness >stdout 2>stderr &&
+ test_set_prereq VAGUENESS_SUCCESS
+'
+
+test_expect_success VAGUENESS_SUCCESS 'checkout produces ambiguity warning' '
+ grep "warning.*ambiguous" stderr
+'
+
+test_expect_success VAGUENESS_SUCCESS 'checkout chooses branch over tag' '
+ echo refs/heads/vagueness >expect &&
+ git symbolic-ref HEAD >actual &&
+ test_cmp expect actual &&
+ echo branch >expect &&
+ test_cmp expect file
+'
+
+test_expect_success VAGUENESS_SUCCESS 'checkout reports switch to branch' '
+ grep "Switched to branch" stderr &&
+ ! grep "^HEAD is now at" stderr
+'
+
+test_done
diff --git a/t/t2020-checkout-detach.sh b/t/t2020-checkout-detach.sh
--- /dev/null
@@ -0,0 +1,95 @@
+#!/bin/sh
+
+test_description='checkout into detached HEAD state'
+. ./test-lib.sh
+
+check_detached () {
+ test_must_fail git symbolic-ref -q HEAD >/dev/null
+}
+
+check_not_detached () {
+ git symbolic-ref -q HEAD >/dev/null
+}
+
+reset () {
+ git checkout master &&
+ check_not_detached
+}
+
+test_expect_success 'setup' '
+ test_commit one &&
+ test_commit two &&
+ git branch branch &&
+ git tag tag
+'
+
+test_expect_success 'checkout branch does not detach' '
+ reset &&
+ git checkout branch &&
+ check_not_detached
+'
+
+test_expect_success 'checkout tag detaches' '
+ reset &&
+ git checkout tag &&
+ check_detached
+'
+
+test_expect_success 'checkout branch by full name detaches' '
+ reset &&
+ git checkout refs/heads/branch &&
+ check_detached
+'
+
+test_expect_success 'checkout non-ref detaches' '
+ reset &&
+ git checkout branch^ &&
+ check_detached
+'
+
+test_expect_success 'checkout ref^0 detaches' '
+ reset &&
+ git checkout branch^0 &&
+ check_detached
+'
+
+test_expect_success 'checkout --detach detaches' '
+ reset &&
+ git checkout --detach branch &&
+ check_detached
+'
+
+test_expect_success 'checkout --detach without branch name' '
+ reset &&
+ git checkout --detach &&
+ check_detached
+'
+
+test_expect_success 'checkout --detach errors out for non-commit' '
+ reset &&
+ test_must_fail git checkout --detach one^{tree} &&
+ check_not_detached
+'
+
+test_expect_success 'checkout --detach errors out for extra argument' '
+ reset &&
+ git checkout master &&
+ test_must_fail git checkout --detach tag one.t &&
+ check_not_detached
+'
+
+test_expect_success 'checkout --detached and -b are incompatible' '
+ reset &&
+ test_must_fail git checkout --detach -b newbranch tag &&
+ check_not_detached
+'
+
+test_expect_success 'checkout --detach moves HEAD' '
+ reset &&
+ git checkout one &&
+ git checkout --detach two &&
+ git diff --exit-code HEAD &&
+ git diff --exit-code two
+'
+
+test_done
index b7131d8c08daf20f328dd7f9ce7a1118a734516b..21f4659a9d1c22fcc8c9eb3261315d67988dad2f 100755 (executable)
test_expect_success 'post-commit hook used ordinarily' '
echo initial >top &&
-git add top
+git add top &&
git commit -m initial &&
test -r "${COMMIT_FILE}"
'
rm -rf "${COMMIT_FILE}"
test_expect_success 'post-commit-hook from sub dir' '
-echo changed again >top
+echo changed again >top &&
cd subdir &&
git --git-dir .git --work-tree .. add ../top &&
git --git-dir .git --work-tree .. commit -m subcommit &&
index 76ad7c344c0546603fb198a9ffb907d6142cbe84..c8bce8c2e4314aaf466019438818293102c12c9c 100755 (executable)
echo hello world >dir1/file3 &&
echo goodbye people >file2 &&
git update-index --add file2 dir1/file3 &&
- echo hello everybody >file2
+ echo hello everybody >file2 &&
echo happy >dir1/file3 &&
git update-index --again &&
git ls-files -s >current &&
diff --git a/t/t2107-update-index-basic.sh b/t/t2107-update-index-basic.sh
--- /dev/null
@@ -0,0 +1,32 @@
+#!/bin/sh
+
+test_description='basic update-index tests
+
+Tests for command-line parsing and basic operation.
+'
+
+. ./test-lib.sh
+
+test_expect_success 'update-index --nonsense fails' '
+ test_must_fail git update-index --nonsense 2>msg &&
+ cat msg &&
+ test -s msg
+'
+
+test_expect_success 'update-index --nonsense dumps usage' '
+ test_expect_code 129 git update-index --nonsense 2>err &&
+ grep "[Uu]sage: git update-index" err
+'
+
+test_expect_success 'update-index -h with corrupt index' '
+ mkdir broken &&
+ (
+ cd broken &&
+ git init &&
+ >.git/index &&
+ test_expect_code 129 git update-index -h >usage 2>&1
+ ) &&
+ grep "[Uu]sage: git update-index" broken/usage
+'
+
+test_done
diff --git a/t/t2200-add-update.sh b/t/t2200-add-update.sh
index 2ad2819a345af53ff6ab0b7c28fa313f1a0a5956..0692427cb69c62327da52061de6238ca8e12169d 100755 (executable)
--- a/t/t2200-add-update.sh
+++ b/t/t2200-add-update.sh
echo initial >dir1/sub2 &&
echo initial >dir2/sub3 &&
git add check dir1 dir2 top foo &&
- test_tick
+ test_tick &&
git commit -m initial &&
echo changed >check &&
index 6d2f2b67ee8d03e1f1dc4874da100cb2e179b6d1..c8fe9782672c0e6ba2f0fd0c1b708fc980263182 100755 (executable)
test_expect_success 'trailing slash in exclude forces directory match (1)' '
- >two
+ >two &&
git ls-files --others --exclude=two/ >output &&
grep "^two" output
diff --git a/t/t3004-ls-files-basic.sh b/t/t3004-ls-files-basic.sh
--- /dev/null
@@ -0,0 +1,39 @@
+#!/bin/sh
+
+test_description='basic ls-files tests
+
+This test runs git ls-files with various unusual or malformed
+command-line arguments.
+'
+
+. ./test-lib.sh
+
+>empty
+
+test_expect_success 'ls-files in empty repository' '
+ git ls-files >actual &&
+ test_cmp empty actual
+'
+
+test_expect_success 'ls-files with nonexistent path' '
+ git ls-files doesnotexist >actual &&
+ test_cmp empty actual
+'
+
+test_expect_success 'ls-files with nonsense option' '
+ test_expect_code 129 git ls-files --nonsense 2>actual &&
+ grep "[Uu]sage: git ls-files" actual
+'
+
+test_expect_success 'ls-files -h in corrupt repository' '
+ mkdir broken &&
+ (
+ cd broken &&
+ git init &&
+ >.git/index &&
+ test_expect_code 129 git ls-files -h >usage 2>&1
+ ) &&
+ grep "[Uu]sage: git ls-files " broken/usage
+'
+
+test_done
index e66e550b2449e76435a0bf16c6a9889c0794f858..34794f8a70e1c9b71384363e022d08da4ac8872e 100755 (executable)
git branch submod &&
git branch copy &&
git branch rename &&
+ if test_have_prereq SYMLINKS
+ then
+ git branch rename-ln
+ fi &&
echo hello >>a &&
cp a d/e &&
git mv a e &&
git add e &&
test_tick &&
- git commit -m "rename a->e"
+ git commit -m "rename a->e" &&
+ if test_have_prereq SYMLINKS
+ then
+ git checkout rename-ln &&
+ git mv a e &&
+ ln -s e a &&
+ git add a e &&
+ test_tick &&
+ git commit -m "rename a->e, symlink a->e"
+ fi
'
test_expect_success 'setup 9' '
echo "100644 $o0 0 c"
echo "100644 $o1 0 d/e"
) >expected &&
- test_cmp expected actual
+ test_cmp expected actual &&
git read-tree --prefix=z/ master &&
git ls-files -s >actual &&
test_cmp expected actual
'
+if test_have_prereq SYMLINKS
+then
+ test_expect_success 'merge-recursive rename vs. rename/symlink' '
+
+ git checkout -f rename &&
+ git merge rename-ln &&
+ ( git ls-tree -r HEAD ; git ls-files -s ) >actual &&
+ (
+ echo "100644 blob $o0 b"
+ echo "100644 blob $o0 c"
+ echo "100644 blob $o0 d/e"
+ echo "100644 blob $o0 e"
+ echo "100644 $o0 0 b"
+ echo "100644 $o0 0 c"
+ echo "100644 $o0 0 d/e"
+ echo "100644 $o0 0 e"
+ ) >expected &&
+ test_cmp expected actual
+ '
+fi
+
+
test_done
diff --git a/t/t3032-merge-recursive-options.sh b/t/t3032-merge-recursive-options.sh
--- /dev/null
@@ -0,0 +1,203 @@
+#!/bin/sh
+
+test_description='merge-recursive options
+
+* [master] Clarify
+ ! [remote] Remove cruft
+--
+ + [remote] Remove cruft
+* [master] Clarify
+*+ [remote^] Initial revision
+* ok 1: setup
+'
+
+. ./test-lib.sh
+
+test_have_prereq SED_STRIPS_CR && SED_OPTIONS=-b
+test_have_prereq MINGW && export GREP_OPTIONS=-U
+
+test_expect_success 'setup' '
+ conflict_hunks () {
+ sed $SED_OPTIONS -n -e "
+ /^<<<</ b conflict
+ b
+ : conflict
+ p
+ /^>>>>/ b
+ n
+ b conflict
+ " "$@"
+ } &&
+
+ cat <<-\EOF >text.txt &&
+ Hope, he says, cherishes the soul of him who lives in
+ justice and holiness and is the nurse of his age and the
+ companion of his journey;--hope which is mightiest to sway
+ the restless soul of man.
+
+ How admirable are his words! And the great blessing of riches, I do
+ not say to every man, but to a good man, is, that he has had no
+ occasion to deceive or to defraud others, either intentionally or
+ unintentionally; and when he departs to the world below he is not in
+ any apprehension about offerings due to the gods or debts which he owes
+ to men. Now to this peace of mind the possession of wealth greatly
+ contributes; and therefore I say, that, setting one thing against
+ another, of the many advantages which wealth has to give, to a man of
+ sense this is in my opinion the greatest.
+
+ Well said, Cephalus, I replied; but as concerning justice, what is
+ it?--to speak the truth and to pay your debts--no more than this? And
+ even to this are there not exceptions? Suppose that a friend when in
+ his right mind has deposited arms with me and he asks for them when he
+ is not in his right mind, ought I to give them back to him? No one
+ would say that I ought or that I should be right in doing so, any more
+ than they would say that I ought always to speak the truth to one who
+ is in his condition.
+
+ You are quite right, he replied.
+
+ But then, I said, speaking the truth and paying your debts is not a
+ correct definition of justice.
+
+ CEPHALUS - SOCRATES - POLEMARCHUS
+
+ Quite correct, Socrates, if Simonides is to be believed, said
+ Polemarchus interposing.
+
+ I fear, said Cephalus, that I must go now, for I have to look after the
+ sacrifices, and I hand over the argument to Polemarchus and the company.
+ EOF
+ git add text.txt &&
+ test_tick &&
+ git commit -m "Initial revision" &&
+
+ git checkout -b remote &&
+ sed -e "
+ s/\. /\. /g
+ s/[?] /? /g
+ s/ / /g
+ s/--/---/g
+ s/but as concerning/but as con cerning/
+ /CEPHALUS - SOCRATES - POLEMARCHUS/ d
+ " text.txt >text.txt+ &&
+ mv text.txt+ text.txt &&
+ git commit -a -m "Remove cruft" &&
+
+ git checkout master &&
+ sed -e "
+ s/\(not in his right mind\),\(.*\)/\1;\2Q/
+ s/Quite correct\(.*\)/It is too correct\1Q/
+ s/unintentionally/un intentionally/
+ /un intentionally/ s/$/Q/
+ s/Polemarchus interposing./Polemarchus, interposing.Q/
+ /justice and holiness/ s/$/Q/
+ /pay your debts/ s/$/Q/
+ " text.txt | q_to_cr >text.txt+ &&
+ mv text.txt+ text.txt &&
+ git commit -a -m "Clarify" &&
+ git show-branch --all
+'
+
+test_expect_success 'naive merge fails' '
+ git read-tree --reset -u HEAD &&
+ test_must_fail git merge-recursive HEAD^ -- HEAD remote &&
+ test_must_fail git update-index --refresh &&
+ grep "<<<<<<" text.txt
+'
+
+test_expect_success '--ignore-space-change makes merge succeed' '
+ git read-tree --reset -u HEAD &&
+ git merge-recursive --ignore-space-change HEAD^ -- HEAD remote
+'
+
+test_expect_success 'naive cherry-pick fails' '
+ git read-tree --reset -u HEAD &&
+ test_must_fail git cherry-pick --no-commit remote &&
+ git read-tree --reset -u HEAD &&
+ test_must_fail git cherry-pick remote &&
+ test_must_fail git update-index --refresh &&
+ grep "<<<<<<" text.txt
+'
+
+test_expect_success '-Xignore-space-change makes cherry-pick succeed' '
+ git read-tree --reset -u HEAD &&
+ git cherry-pick --no-commit -Xignore-space-change remote
+'
+
+test_expect_success '--ignore-space-change: our w/s-only change wins' '
+ q_to_cr <<-\EOF >expected &&
+ justice and holiness and is the nurse of his age and theQ
+ EOF
+
+ git read-tree --reset -u HEAD &&
+ git merge-recursive --ignore-space-change HEAD^ -- HEAD remote &&
+ grep "justice and holiness" text.txt >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success '--ignore-space-change: their real change wins over w/s' '
+ cat <<-\EOF >expected &&
+ it?---to speak the truth and to pay your debts---no more than this? And
+ EOF
+
+ git read-tree --reset -u HEAD &&
+ git merge-recursive --ignore-space-change HEAD^ -- HEAD remote &&
+ grep "pay your debts" text.txt >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success '--ignore-space-change: does not ignore new spaces' '
+ cat <<-\EOF >expected1 &&
+ Well said, Cephalus, I replied; but as con cerning justice, what is
+ EOF
+ q_to_cr <<-\EOF >expected2 &&
+ un intentionally; and when he departs to the world below he is not inQ
+ EOF
+
+ git read-tree --reset -u HEAD &&
+ git merge-recursive --ignore-space-change HEAD^ -- HEAD remote &&
+ grep "Well said" text.txt >actual1 &&
+ grep "when he departs" text.txt >actual2 &&
+ test_cmp expected1 actual1 &&
+ test_cmp expected2 actual2
+'
+
+test_expect_success '--ignore-all-space drops their new spaces' '
+ cat <<-\EOF >expected &&
+ Well said, Cephalus, I replied; but as concerning justice, what is
+ EOF
+
+ git read-tree --reset -u HEAD &&
+ git merge-recursive --ignore-all-space HEAD^ -- HEAD remote &&
+ grep "Well said" text.txt >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success '--ignore-all-space keeps our new spaces' '
+ q_to_cr <<-\EOF >expected &&
+ un intentionally; and when he departs to the world below he is not inQ
+ EOF
+
+ git read-tree --reset -u HEAD &&
+ git merge-recursive --ignore-all-space HEAD^ -- HEAD remote &&
+ grep "when he departs" text.txt >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success '--ignore-space-at-eol' '
+ q_to_cr <<-\EOF >expected &&
+ <<<<<<< HEAD
+ is not in his right mind; ought I to give them back to him? No oneQ
+ =======
+ is not in his right mind, ought I to give them back to him? No one
+ >>>>>>> remote
+ EOF
+
+ git read-tree --reset -u HEAD &&
+ test_must_fail git merge-recursive --ignore-space-at-eol \
+ HEAD^ -- HEAD remote &&
+ conflict_hunks text.txt >actual &&
+ test_cmp expected actual
+'
+
+test_done
index 4261e9641e00fb3b543384b6a8dbbcc1a214b598..2f5f41a012b5f6e38f2b9d631a99648ac8937c2d 100755 (executable)
cd sub &&
git init &&
>subfile &&
- git add subfile
+ git add subfile &&
git commit -m "subproject commit #1"
) &&
- >mainfile
+ >mainfile &&
git add sub mainfile &&
test_tick &&
git commit -m "superproject commit #1"
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index f54a533456d74a3eb2f745dbc77c8ad5a5ae960f..f308235f5dd28da2c19d91dc63803312aacd2cda 100755 (executable)
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh
! test -f .git/refs/heads/--help
'
+test_expect_success 'branch -h in broken repository' '
+ mkdir broken &&
+ (
+ cd broken &&
+ git init &&
+ >.git/refs/heads/master &&
+ test_expect_code 129 git branch -h >usage 2>&1
+ ) &&
+ grep "[Uu]sage" broken/usage
+'
+
test_expect_success \
'git branch abc should create a branch' \
'git branch abc && test -f .git/refs/heads/abc'
index 809d1c4ed49f06d06ba50cda6f52fe9201d2d625..6028748c6cecaedb087c35182de172ca0e93fe08 100755 (executable)
--- a/t/t3203-branch-output.sh
+++ b/t/t3203-branch-output.sh
'
test_expect_success 'make branches' '
- git branch branch-one
+ git branch branch-one &&
git branch branch-two HEAD^
'
test_expect_success 'make remote branches' '
- git update-ref refs/remotes/origin/branch-one branch-one
- git update-ref refs/remotes/origin/branch-two branch-two
+ git update-ref refs/remotes/origin/branch-one branch-one &&
+ git update-ref refs/remotes/origin/branch-two branch-two &&
git symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/branch-one
'
diff --git a/t/t3300-funny-names.sh b/t/t3300-funny-names.sh
index f39a261d806d567a3aadee892c58f6be02c64e0a..5e29a052599bc28c0edaf56dfedb5f069b5ff01e 100755 (executable)
--- a/t/t3300-funny-names.sh
+++ b/t/t3300-funny-names.sh
test_cmp expected current'
test_expect_success TABS_IN_FILENAMES 'setup expect' '
-t0=`git write-tree`
-echo "$t0" >t0
+t0=`git write-tree` &&
+echo "$t0" >t0 &&
cat > expected <<\EOF
just space
test_cmp expected current'
test_expect_success TABS_IN_FILENAMES 'setup expect' '
-t1=`git write-tree`
-echo "$t1" >t1
+t1=`git write-tree` &&
+echo "$t1" >t1 &&
cat > expected <<\EOF
just space
diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh
index a2b79a0430b2468241a578aaad479a09db774dd5..1921ca3a73370d378e6aedd0820fd8d2a030da03 100755 (executable)
--- a/t/t3301-notes.sh
+++ b/t/t3301-notes.sh
# 1 indicates caught gracefully by die, 128 means git-show barked
test_expect_success 'handle empty notes gracefully' '
- git notes show ; test 1 = $?
+ test_expect_code 1 git notes show
'
test_expect_success 'show non-existent notes entry with %N' '
test_expect_success 'Allow notes on non-commits (trees, blobs, tags)' '
git config core.notesRef refs/notes/other &&
- echo "Note on a tree" > expect
+ echo "Note on a tree" > expect &&
git notes add -m "Note on a tree" HEAD: &&
git notes show HEAD: > actual &&
test_cmp expect actual &&
- echo "Note on a blob" > expect
+ echo "Note on a blob" > expect &&
filename=$(git ls-tree --name-only HEAD | head -n1) &&
git notes add -m "Note on a blob" HEAD:$filename &&
git notes show HEAD:$filename > actual &&
test_cmp expect actual &&
- echo "Note on a tag" > expect
+ echo "Note on a tag" > expect &&
git tag -a -m "This is an annotated tag" foobar HEAD^ &&
git notes add -m "Note on a tag" foobar &&
git notes show foobar > actual &&
Notes (other):
a fresh note
+$whitespace
another fresh note
EOF
Notes (other):
a fresh note
+$whitespace
another fresh note
+$whitespace
append 1
+$whitespace
append 2
EOF
@@ -1061,4 +1065,23 @@ test_expect_success 'git notes copy diagnoses too many or too few parameters' '
test_must_fail git notes copy one two three
'
+test_expect_success 'git notes get-ref (no overrides)' '
+ git config --unset core.notesRef &&
+ sane_unset GIT_NOTES_REF &&
+ test "$(git notes get-ref)" = "refs/notes/commits"
+'
+
+test_expect_success 'git notes get-ref (core.notesRef)' '
+ git config core.notesRef refs/notes/foo &&
+ test "$(git notes get-ref)" = "refs/notes/foo"
+'
+
+test_expect_success 'git notes get-ref (GIT_NOTES_REF)' '
+ test "$(GIT_NOTES_REF=refs/notes/bar git notes get-ref)" = "refs/notes/bar"
+'
+
+test_expect_success 'git notes get-ref (--ref)' '
+ test "$(GIT_NOTES_REF=refs/notes/bar git notes --ref=baz get-ref)" = "refs/notes/baz"
+'
+
test_done
index 75ec18778e1be732593ae130aa257eca3290e36f..704aee81ef5618048bba5209629a0dfde136fdb3 100755 (executable)
}
verify_concatenated_notes () {
- git log | grep "^ " > output &&
- i=$number_of_commits &&
- while [ $i -gt 0 ]; do
- echo " commit #$i" &&
- echo " first note for commit #$i" &&
- echo " second note for commit #$i" &&
- i=$(($i-1));
- done > expect &&
- test_cmp expect output
+ git log | grep "^ " > output &&
+ i=$number_of_commits &&
+ while [ $i -gt 0 ]; do
+ echo " commit #$i" &&
+ echo " first note for commit #$i" &&
+ echo " " &&
+ echo " second note for commit #$i" &&
+ i=$(($i-1));
+ done > expect &&
+ test_cmp expect output
}
test_expect_success 'test notes in no fanout concatenated with 2/38-fanout' 'test_concatenated_notes "s|^..|&/|" ""'
diff --git a/t/t3307-notes-man.sh b/t/t3307-notes-man.sh
index 3269f2eebdbb71acd7c88edee2b6efe809637ade..2ea3be6546f43fd5c224aa8df6add96fa9df6ac2 100755 (executable)
--- a/t/t3307-notes-man.sh
+++ b/t/t3307-notes-man.sh
'
test_expect_success 'example 2: binary notes' '
- cp "$TEST_DIRECTORY"/test4012.png .
+ cp "$TEST_DIRECTORY"/test4012.png . &&
git checkout B &&
blob=$(git hash-object -w test4012.png) &&
git notes --ref=logo add -C "$blob" &&
diff --git a/t/t3308-notes-merge.sh b/t/t3308-notes-merge.sh
--- /dev/null
+++ b/t/t3308-notes-merge.sh
@@ -0,0 +1,368 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Johan Herland
+#
+
+test_description='Test merging of notes trees'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+ test_commit 1st &&
+ test_commit 2nd &&
+ test_commit 3rd &&
+ test_commit 4th &&
+ test_commit 5th &&
+ # Create notes on 4 first commits
+ git config core.notesRef refs/notes/x &&
+ git notes add -m "Notes on 1st commit" 1st &&
+ git notes add -m "Notes on 2nd commit" 2nd &&
+ git notes add -m "Notes on 3rd commit" 3rd &&
+ git notes add -m "Notes on 4th commit" 4th
+'
+
+commit_sha1=$(git rev-parse 1st^{commit})
+commit_sha2=$(git rev-parse 2nd^{commit})
+commit_sha3=$(git rev-parse 3rd^{commit})
+commit_sha4=$(git rev-parse 4th^{commit})
+commit_sha5=$(git rev-parse 5th^{commit})
+
+verify_notes () {
+ notes_ref="$1"
+ git -c core.notesRef="refs/notes/$notes_ref" notes |
+ sort >"output_notes_$notes_ref" &&
+ test_cmp "expect_notes_$notes_ref" "output_notes_$notes_ref" &&
+ git -c core.notesRef="refs/notes/$notes_ref" log --format="%H %s%n%N" \
+ >"output_log_$notes_ref" &&
+ test_cmp "expect_log_$notes_ref" "output_log_$notes_ref"
+}
+
+cat <<EOF | sort >expect_notes_x
+5e93d24084d32e1cb61f7070505b9d2530cca987 $commit_sha4
+8366731eeee53787d2bdf8fc1eff7d94757e8da0 $commit_sha3
+eede89064cd42441590d6afec6c37b321ada3389 $commit_sha2
+daa55ffad6cb99bf64226532147ffcaf5ce8bdd1 $commit_sha1
+EOF
+
+cat >expect_log_x <<EOF
+$commit_sha5 5th
+
+$commit_sha4 4th
+Notes on 4th commit
+
+$commit_sha3 3rd
+Notes on 3rd commit
+
+$commit_sha2 2nd
+Notes on 2nd commit
+
+$commit_sha1 1st
+Notes on 1st commit
+
+EOF
+
+test_expect_success 'verify initial notes (x)' '
+ verify_notes x
+'
+
+cp expect_notes_x expect_notes_y
+cp expect_log_x expect_log_y
+
+test_expect_success 'fail to merge empty notes ref into empty notes ref (z => y)' '
+ test_must_fail git -c "core.notesRef=refs/notes/y" notes merge z
+'
+
+test_expect_success 'fail to merge into various non-notes refs' '
+ test_must_fail git -c "core.notesRef=refs/notes" notes merge x &&
+ test_must_fail git -c "core.notesRef=refs/notes/" notes merge x &&
+ mkdir -p .git/refs/notes/dir &&
+ test_must_fail git -c "core.notesRef=refs/notes/dir" notes merge x &&
+ test_must_fail git -c "core.notesRef=refs/notes/dir/" notes merge x &&
+ test_must_fail git -c "core.notesRef=refs/heads/master" notes merge x &&
+ test_must_fail git -c "core.notesRef=refs/notes/y:" notes merge x &&
+ test_must_fail git -c "core.notesRef=refs/notes/y:foo" notes merge x &&
+ test_must_fail git -c "core.notesRef=refs/notes/foo^{bar" notes merge x
+'
+
+test_expect_success 'fail to merge various non-note-trees' '
+ git config core.notesRef refs/notes/y &&
+ test_must_fail git notes merge refs/notes &&
+ test_must_fail git notes merge refs/notes/ &&
+ test_must_fail git notes merge refs/notes/dir &&
+ test_must_fail git notes merge refs/notes/dir/ &&
+ test_must_fail git notes merge refs/heads/master &&
+ test_must_fail git notes merge x: &&
+ test_must_fail git notes merge x:foo &&
+ test_must_fail git notes merge foo^{bar
+'
+
+test_expect_success 'merge notes into empty notes ref (x => y)' '
+ git config core.notesRef refs/notes/y &&
+ git notes merge x &&
+ verify_notes y &&
+ # x and y should point to the same notes commit
+ test "$(git rev-parse refs/notes/x)" = "$(git rev-parse refs/notes/y)"
+'
+
+test_expect_success 'merge empty notes ref (z => y)' '
+ git notes merge z &&
+ # y should not change (still == x)
+ test "$(git rev-parse refs/notes/x)" = "$(git rev-parse refs/notes/y)"
+'
+
+test_expect_success 'change notes on other notes ref (y)' '
+ # Not touching notes to 1st commit
+ git notes remove 2nd &&
+ git notes append -m "More notes on 3rd commit" 3rd &&
+ git notes add -f -m "New notes on 4th commit" 4th &&
+ git notes add -m "Notes on 5th commit" 5th
+'
+
+test_expect_success 'merge previous notes commit (y^ => y) => No-op' '
+ pre_state="$(git rev-parse refs/notes/y)" &&
+ git notes merge y^ &&
+ # y should not move
+ test "$pre_state" = "$(git rev-parse refs/notes/y)"
+'
+
+cat <<EOF | sort >expect_notes_y
+0f2efbd00262f2fd41dfae33df8765618eeacd99 $commit_sha5
+dec2502dac3ea161543f71930044deff93fa945c $commit_sha4
+4069cdb399fd45463ec6eef8e051a16a03592d91 $commit_sha3
+daa55ffad6cb99bf64226532147ffcaf5ce8bdd1 $commit_sha1
+EOF
+
+cat >expect_log_y <<EOF
+$commit_sha5 5th
+Notes on 5th commit
+
+$commit_sha4 4th
+New notes on 4th commit
+
+$commit_sha3 3rd
+Notes on 3rd commit
+
+More notes on 3rd commit
+
+$commit_sha2 2nd
+
+$commit_sha1 1st
+Notes on 1st commit
+
+EOF
+
+test_expect_success 'verify changed notes on other notes ref (y)' '
+ verify_notes y
+'
+
+test_expect_success 'verify unchanged notes on original notes ref (x)' '
+ verify_notes x
+'
+
+test_expect_success 'merge original notes (x) into changed notes (y) => No-op' '
+ git notes merge -vvv x &&
+ verify_notes y &&
+ verify_notes x
+'
+
+cp expect_notes_y expect_notes_x
+cp expect_log_y expect_log_x
+
+test_expect_success 'merge changed (y) into original (x) => Fast-forward' '
+ git config core.notesRef refs/notes/x &&
+ git notes merge y &&
+ verify_notes x &&
+ verify_notes y &&
+ # x and y should point to same the notes commit
+ test "$(git rev-parse refs/notes/x)" = "$(git rev-parse refs/notes/y)"
+'
+
+test_expect_success 'merge empty notes ref (z => y)' '
+ # Prepare empty (but valid) notes ref (z)
+ git config core.notesRef refs/notes/z &&
+ git notes add -m "foo" &&
+ git notes remove &&
+ git notes >output_notes_z &&
+ test_cmp /dev/null output_notes_z &&
+ # Do the merge (z => y)
+ git config core.notesRef refs/notes/y &&
+ git notes merge z &&
+ verify_notes y &&
+ # y should no longer point to the same notes commit as x
+ test "$(git rev-parse refs/notes/x)" != "$(git rev-parse refs/notes/y)"
+'
+
+cat <<EOF | sort >expect_notes_y
+0f2efbd00262f2fd41dfae33df8765618eeacd99 $commit_sha5
+dec2502dac3ea161543f71930044deff93fa945c $commit_sha4
+4069cdb399fd45463ec6eef8e051a16a03592d91 $commit_sha3
+d000d30e6ddcfce3a8122c403226a2ce2fd04d9d $commit_sha2
+43add6bd0c8c0bc871ac7991e0f5573cfba27804 $commit_sha1
+EOF
+
+cat >expect_log_y <<EOF
+$commit_sha5 5th
+Notes on 5th commit
+
+$commit_sha4 4th
+New notes on 4th commit
+
+$commit_sha3 3rd
+Notes on 3rd commit
+
+More notes on 3rd commit
+
+$commit_sha2 2nd
+New notes on 2nd commit
+
+$commit_sha1 1st
+Notes on 1st commit
+
+More notes on 1st commit
+
+EOF
+
+test_expect_success 'change notes on other notes ref (y)' '
+ # Append to 1st commit notes
+ git notes append -m "More notes on 1st commit" 1st &&
+ # Add new notes to 2nd commit
+ git notes add -m "New notes on 2nd commit" 2nd &&
+ verify_notes y
+'
+
+cat <<EOF | sort >expect_notes_x
+0f2efbd00262f2fd41dfae33df8765618eeacd99 $commit_sha5
+1f257a3a90328557c452f0817d6cc50c89d315d4 $commit_sha4
+daa55ffad6cb99bf64226532147ffcaf5ce8bdd1 $commit_sha1
+EOF
+
+cat >expect_log_x <<EOF
+$commit_sha5 5th
+Notes on 5th commit
+
+$commit_sha4 4th
+New notes on 4th commit
+
+More notes on 4th commit
+
+$commit_sha3 3rd
+
+$commit_sha2 2nd
+
+$commit_sha1 1st
+Notes on 1st commit
+
+EOF
+
+test_expect_success 'change notes on notes ref (x)' '
+ git config core.notesRef refs/notes/x &&
+ git notes remove 3rd &&
+ git notes append -m "More notes on 4th commit" 4th &&
+ verify_notes x
+'
+
+cat <<EOF | sort >expect_notes_x
+0f2efbd00262f2fd41dfae33df8765618eeacd99 $commit_sha5
+1f257a3a90328557c452f0817d6cc50c89d315d4 $commit_sha4
+d000d30e6ddcfce3a8122c403226a2ce2fd04d9d $commit_sha2
+43add6bd0c8c0bc871ac7991e0f5573cfba27804 $commit_sha1
+EOF
+
+cat >expect_log_x <<EOF
+$commit_sha5 5th
+Notes on 5th commit
+
+$commit_sha4 4th
+New notes on 4th commit
+
+More notes on 4th commit
+
+$commit_sha3 3rd
+
+$commit_sha2 2nd
+New notes on 2nd commit
+
+$commit_sha1 1st
+Notes on 1st commit
+
+More notes on 1st commit
+
+EOF
+
+test_expect_success 'merge y into x => Non-conflicting 3-way merge' '
+ git notes merge y &&
+ verify_notes x &&
+ verify_notes y
+'
+
+cat <<EOF | sort >expect_notes_w
+05a4927951bcef347f51486575b878b2b60137f2 $commit_sha3
+d000d30e6ddcfce3a8122c403226a2ce2fd04d9d $commit_sha2
+EOF
+
+cat >expect_log_w <<EOF
+$commit_sha5 5th
+
+$commit_sha4 4th
+
+$commit_sha3 3rd
+New notes on 3rd commit
+
+$commit_sha2 2nd
+New notes on 2nd commit
+
+$commit_sha1 1st
+
+EOF
+
+test_expect_success 'create notes on new, separate notes ref (w)' '
+ git config core.notesRef refs/notes/w &&
+ # Add same note as refs/notes/y on 2nd commit
+ git notes add -m "New notes on 2nd commit" 2nd &&
+ # Add new note on 3rd commit (non-conflicting)
+ git notes add -m "New notes on 3rd commit" 3rd &&
+ # Verify state of notes on new, separate notes ref (w)
+ verify_notes w
+'
+
+cat <<EOF | sort >expect_notes_x
+0f2efbd00262f2fd41dfae33df8765618eeacd99 $commit_sha5
+1f257a3a90328557c452f0817d6cc50c89d315d4 $commit_sha4
+05a4927951bcef347f51486575b878b2b60137f2 $commit_sha3
+d000d30e6ddcfce3a8122c403226a2ce2fd04d9d $commit_sha2
+43add6bd0c8c0bc871ac7991e0f5573cfba27804 $commit_sha1
+EOF
+
+cat >expect_log_x <<EOF
+$commit_sha5 5th
+Notes on 5th commit
+
+$commit_sha4 4th
+New notes on 4th commit
+
+More notes on 4th commit
+
+$commit_sha3 3rd
+New notes on 3rd commit
+
+$commit_sha2 2nd
+New notes on 2nd commit
+
+$commit_sha1 1st
+Notes on 1st commit
+
+More notes on 1st commit
+
+EOF
+
+test_expect_success 'merge w into x => Non-conflicting history-less merge' '
+ git config core.notesRef refs/notes/x &&
+ git notes merge w &&
+ # Verify new state of notes on other notes ref (x)
+ verify_notes x &&
+ # Also verify that nothing changed on other notes refs (y and w)
+ verify_notes y &&
+ verify_notes w
+'
+
+test_done
diff --git a/t/t3309-notes-merge-auto-resolve.sh b/t/t3309-notes-merge-auto-resolve.sh
--- /dev/null
@@ -0,0 +1,647 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Johan Herland
+#
+
+test_description='Test notes merging with auto-resolving strategies'
+
+. ./test-lib.sh
+
+# Set up a notes merge scenario with all kinds of potential conflicts
+test_expect_success 'setup commits' '
+ test_commit 1st &&
+ test_commit 2nd &&
+ test_commit 3rd &&
+ test_commit 4th &&
+ test_commit 5th &&
+ test_commit 6th &&
+ test_commit 7th &&
+ test_commit 8th &&
+ test_commit 9th &&
+ test_commit 10th &&
+ test_commit 11th &&
+ test_commit 12th &&
+ test_commit 13th &&
+ test_commit 14th &&
+ test_commit 15th
+'
+
+commit_sha1=$(git rev-parse 1st^{commit})
+commit_sha2=$(git rev-parse 2nd^{commit})
+commit_sha3=$(git rev-parse 3rd^{commit})
+commit_sha4=$(git rev-parse 4th^{commit})
+commit_sha5=$(git rev-parse 5th^{commit})
+commit_sha6=$(git rev-parse 6th^{commit})
+commit_sha7=$(git rev-parse 7th^{commit})
+commit_sha8=$(git rev-parse 8th^{commit})
+commit_sha9=$(git rev-parse 9th^{commit})
+commit_sha10=$(git rev-parse 10th^{commit})
+commit_sha11=$(git rev-parse 11th^{commit})
+commit_sha12=$(git rev-parse 12th^{commit})
+commit_sha13=$(git rev-parse 13th^{commit})
+commit_sha14=$(git rev-parse 14th^{commit})
+commit_sha15=$(git rev-parse 15th^{commit})
+
+verify_notes () {
+ notes_ref="$1"
+ suffix="$2"
+ git -c core.notesRef="refs/notes/$notes_ref" notes |
+ sort >"output_notes_$suffix" &&
+ test_cmp "expect_notes_$suffix" "output_notes_$suffix" &&
+ git -c core.notesRef="refs/notes/$notes_ref" log --format="%H %s%n%N" \
+ >"output_log_$suffix" &&
+ test_cmp "expect_log_$suffix" "output_log_$suffix"
+}
+
+test_expect_success 'setup merge base (x)' '
+ git config core.notesRef refs/notes/x &&
+ git notes add -m "x notes on 6th commit" 6th &&
+ git notes add -m "x notes on 7th commit" 7th &&
+ git notes add -m "x notes on 8th commit" 8th &&
+ git notes add -m "x notes on 9th commit" 9th &&
+ git notes add -m "x notes on 10th commit" 10th &&
+ git notes add -m "x notes on 11th commit" 11th &&
+ git notes add -m "x notes on 12th commit" 12th &&
+ git notes add -m "x notes on 13th commit" 13th &&
+ git notes add -m "x notes on 14th commit" 14th &&
+ git notes add -m "x notes on 15th commit" 15th
+'
+
+cat <<EOF | sort >expect_notes_x
+457a85d6c814ea208550f15fcc48f804ac8dc023 $commit_sha15
+b0c95b954301d69da2bc3723f4cb1680d355937c $commit_sha14
+5d30216a129eeffa97d9694ffe8c74317a560315 $commit_sha13
+dd161bc149470fd890dd4ab52a4cbd79bbd18c36 $commit_sha12
+7abbc45126d680336fb24294f013a7cdfa3ed545 $commit_sha11
+b8d03e173f67f6505a76f6e00cf93440200dd9be $commit_sha10
+20c613c835011c48a5abe29170a2402ca6354910 $commit_sha9
+a3daf8a1e4e5dc3409a303ad8481d57bfea7f5d6 $commit_sha8
+897003322b53bc6ca098e9324ee508362347e734 $commit_sha7
+11d97fdebfa5ceee540a3da07bce6fa0222bc082 $commit_sha6
+EOF
+
+cat >expect_log_x <<EOF
+$commit_sha15 15th
+x notes on 15th commit
+
+$commit_sha14 14th
+x notes on 14th commit
+
+$commit_sha13 13th
+x notes on 13th commit
+
+$commit_sha12 12th
+x notes on 12th commit
+
+$commit_sha11 11th
+x notes on 11th commit
+
+$commit_sha10 10th
+x notes on 10th commit
+
+$commit_sha9 9th
+x notes on 9th commit
+
+$commit_sha8 8th
+x notes on 8th commit
+
+$commit_sha7 7th
+x notes on 7th commit
+
+$commit_sha6 6th
+x notes on 6th commit
+
+$commit_sha5 5th
+
+$commit_sha4 4th
+
+$commit_sha3 3rd
+
+$commit_sha2 2nd
+
+$commit_sha1 1st
+
+EOF
+
+test_expect_success 'verify state of merge base (x)' 'verify_notes x x'
+
+test_expect_success 'setup local branch (y)' '
+ git update-ref refs/notes/y refs/notes/x &&
+ git config core.notesRef refs/notes/y &&
+ git notes add -f -m "y notes on 3rd commit" 3rd &&
+ git notes add -f -m "y notes on 4th commit" 4th &&
+ git notes add -f -m "y notes on 5th commit" 5th &&
+ git notes remove 6th &&
+ git notes remove 7th &&
+ git notes remove 8th &&
+ git notes add -f -m "y notes on 12th commit" 12th &&
+ git notes add -f -m "y notes on 13th commit" 13th &&
+ git notes add -f -m "y notes on 14th commit" 14th &&
+ git notes add -f -m "y notes on 15th commit" 15th
+'
+
+cat <<EOF | sort >expect_notes_y
+68b8630d25516028bed862719855b3d6768d7833 $commit_sha15
+5de7ea7ad4f47e7ff91989fb82234634730f75df $commit_sha14
+3a631fdb6f41b05b55d8f4baf20728ba8f6fccbc $commit_sha13
+a66055fa82f7a03fe0c02a6aba3287a85abf7c62 $commit_sha12
+7abbc45126d680336fb24294f013a7cdfa3ed545 $commit_sha11
+b8d03e173f67f6505a76f6e00cf93440200dd9be $commit_sha10
+20c613c835011c48a5abe29170a2402ca6354910 $commit_sha9
+154508c7a0bcad82b6fe4b472bc4c26b3bf0825b $commit_sha5
+e2bfd06a37dd2031684a59a6e2b033e212239c78 $commit_sha4
+5772f42408c0dd6f097a7ca2d24de0e78d1c46b1 $commit_sha3
+EOF
+
+cat >expect_log_y <<EOF
+$commit_sha15 15th
+y notes on 15th commit
+
+$commit_sha14 14th
+y notes on 14th commit
+
+$commit_sha13 13th
+y notes on 13th commit
+
+$commit_sha12 12th
+y notes on 12th commit
+
+$commit_sha11 11th
+x notes on 11th commit
+
+$commit_sha10 10th
+x notes on 10th commit
+
+$commit_sha9 9th
+x notes on 9th commit
+
+$commit_sha8 8th
+
+$commit_sha7 7th
+
+$commit_sha6 6th
+
+$commit_sha5 5th
+y notes on 5th commit
+
+$commit_sha4 4th
+y notes on 4th commit
+
+$commit_sha3 3rd
+y notes on 3rd commit
+
+$commit_sha2 2nd
+
+$commit_sha1 1st
+
+EOF
+
+test_expect_success 'verify state of local branch (y)' 'verify_notes y y'
+
+test_expect_success 'setup remote branch (z)' '
+ git update-ref refs/notes/z refs/notes/x &&
+ git config core.notesRef refs/notes/z &&
+ git notes add -f -m "z notes on 2nd commit" 2nd &&
+ git notes add -f -m "y notes on 4th commit" 4th &&
+ git notes add -f -m "z notes on 5th commit" 5th &&
+ git notes remove 6th &&
+ git notes add -f -m "z notes on 8th commit" 8th &&
+ git notes remove 9th &&
+ git notes add -f -m "z notes on 11th commit" 11th &&
+ git notes remove 12th &&
+ git notes add -f -m "y notes on 14th commit" 14th &&
+ git notes add -f -m "z notes on 15th commit" 15th
+'
+
+cat <<EOF | sort >expect_notes_z
+9b4b2c61f0615412da3c10f98ff85b57c04ec765 $commit_sha15
+5de7ea7ad4f47e7ff91989fb82234634730f75df $commit_sha14
+5d30216a129eeffa97d9694ffe8c74317a560315 $commit_sha13
+7e3c53503a3db8dd996cb62e37c66e070b44b54d $commit_sha11
+b8d03e173f67f6505a76f6e00cf93440200dd9be $commit_sha10
+851e1638784a884c7dd26c5d41f3340f6387413a $commit_sha8
+897003322b53bc6ca098e9324ee508362347e734 $commit_sha7
+99fc34adfc400b95c67b013115e37e31aa9a6d23 $commit_sha5
+e2bfd06a37dd2031684a59a6e2b033e212239c78 $commit_sha4
+283b48219aee9a4105f6cab337e789065c82c2b9 $commit_sha2
+EOF
+
+cat >expect_log_z <<EOF
+$commit_sha15 15th
+z notes on 15th commit
+
+$commit_sha14 14th
+y notes on 14th commit
+
+$commit_sha13 13th
+x notes on 13th commit
+
+$commit_sha12 12th
+
+$commit_sha11 11th
+z notes on 11th commit
+
+$commit_sha10 10th
+x notes on 10th commit
+
+$commit_sha9 9th
+
+$commit_sha8 8th
+z notes on 8th commit
+
+$commit_sha7 7th
+x notes on 7th commit
+
+$commit_sha6 6th
+
+$commit_sha5 5th
+z notes on 5th commit
+
+$commit_sha4 4th
+y notes on 4th commit
+
+$commit_sha3 3rd
+
+$commit_sha2 2nd
+z notes on 2nd commit
+
+$commit_sha1 1st
+
+EOF
+
+test_expect_success 'verify state of remote branch (z)' 'verify_notes z z'
+
+# At this point, before merging z into y, we have the following status:
+#
+# commit | base/x | local/y | remote/z | diff from x to y/z | result
+# -------|---------|---------|----------|----------------------------|-------
+# 1st | [none] | [none] | [none] | unchanged / unchanged | [none]
+# 2nd | [none] | [none] | 283b482 | unchanged / added | 283b482
+# 3rd | [none] | 5772f42 | [none] | added / unchanged | 5772f42
+# 4th | [none] | e2bfd06 | e2bfd06 | added / added (same) | e2bfd06
+# 5th | [none] | 154508c | 99fc34a | added / added (diff) | ???
+# 6th | 11d97fd | [none] | [none] | removed / removed | [none]
+# 7th | 8970033 | [none] | 8970033 | removed / unchanged | [none]
+# 8th | a3daf8a | [none] | 851e163 | removed / changed | ???
+# 9th | 20c613c | 20c613c | [none] | unchanged / removed | [none]
+# 10th | b8d03e1 | b8d03e1 | b8d03e1 | unchanged / unchanged | b8d03e1
+# 11th | 7abbc45 | 7abbc45 | 7e3c535 | unchanged / changed | 7e3c535
+# 12th | dd161bc | a66055f | [none] | changed / removed | ???
+# 13th | 5d30216 | 3a631fd | 5d30216 | changed / unchanged | 3a631fd
+# 14th | b0c95b9 | 5de7ea7 | 5de7ea7 | changed / changed (same) | 5de7ea7
+# 15th | 457a85d | 68b8630 | 9b4b2c6 | changed / changed (diff) | ???
+
+test_expect_success 'merge z into y with invalid strategy => Fail/No changes' '
+ git config core.notesRef refs/notes/y &&
+ test_must_fail git notes merge --strategy=foo z &&
+ # Verify no changes (y)
+ verify_notes y y
+'
+
+cat <<EOF | sort >expect_notes_ours
+68b8630d25516028bed862719855b3d6768d7833 $commit_sha15
+5de7ea7ad4f47e7ff91989fb82234634730f75df $commit_sha14
+3a631fdb6f41b05b55d8f4baf20728ba8f6fccbc $commit_sha13
+a66055fa82f7a03fe0c02a6aba3287a85abf7c62 $commit_sha12
+7e3c53503a3db8dd996cb62e37c66e070b44b54d $commit_sha11
+b8d03e173f67f6505a76f6e00cf93440200dd9be $commit_sha10
+154508c7a0bcad82b6fe4b472bc4c26b3bf0825b $commit_sha5
+e2bfd06a37dd2031684a59a6e2b033e212239c78 $commit_sha4
+5772f42408c0dd6f097a7ca2d24de0e78d1c46b1 $commit_sha3
+283b48219aee9a4105f6cab337e789065c82c2b9 $commit_sha2
+EOF
+
+cat >expect_log_ours <<EOF
+$commit_sha15 15th
+y notes on 15th commit
+
+$commit_sha14 14th
+y notes on 14th commit
+
+$commit_sha13 13th
+y notes on 13th commit
+
+$commit_sha12 12th
+y notes on 12th commit
+
+$commit_sha11 11th
+z notes on 11th commit
+
+$commit_sha10 10th
+x notes on 10th commit
+
+$commit_sha9 9th
+
+$commit_sha8 8th
+
+$commit_sha7 7th
+
+$commit_sha6 6th
+
+$commit_sha5 5th
+y notes on 5th commit
+
+$commit_sha4 4th
+y notes on 4th commit
+
+$commit_sha3 3rd
+y notes on 3rd commit
+
+$commit_sha2 2nd
+z notes on 2nd commit
+
+$commit_sha1 1st
+
+EOF
+
+test_expect_success 'merge z into y with "ours" strategy => Non-conflicting 3-way merge' '
+ git notes merge --strategy=ours z &&
+ verify_notes y ours
+'
+
+test_expect_success 'reset to pre-merge state (y)' '
+ git update-ref refs/notes/y refs/notes/y^1 &&
+ # Verify pre-merge state
+ verify_notes y y
+'
+
+cat <<EOF | sort >expect_notes_theirs
+9b4b2c61f0615412da3c10f98ff85b57c04ec765 $commit_sha15
+5de7ea7ad4f47e7ff91989fb82234634730f75df $commit_sha14
+3a631fdb6f41b05b55d8f4baf20728ba8f6fccbc $commit_sha13
+7e3c53503a3db8dd996cb62e37c66e070b44b54d $commit_sha11
+b8d03e173f67f6505a76f6e00cf93440200dd9be $commit_sha10
+851e1638784a884c7dd26c5d41f3340f6387413a $commit_sha8
+99fc34adfc400b95c67b013115e37e31aa9a6d23 $commit_sha5
+e2bfd06a37dd2031684a59a6e2b033e212239c78 $commit_sha4
+5772f42408c0dd6f097a7ca2d24de0e78d1c46b1 $commit_sha3
+283b48219aee9a4105f6cab337e789065c82c2b9 $commit_sha2
+EOF
+
+cat >expect_log_theirs <<EOF
+$commit_sha15 15th
+z notes on 15th commit
+
+$commit_sha14 14th
+y notes on 14th commit
+
+$commit_sha13 13th
+y notes on 13th commit
+
+$commit_sha12 12th
+
+$commit_sha11 11th
+z notes on 11th commit
+
+$commit_sha10 10th
+x notes on 10th commit
+
+$commit_sha9 9th
+
+$commit_sha8 8th
+z notes on 8th commit
+
+$commit_sha7 7th
+
+$commit_sha6 6th
+
+$commit_sha5 5th
+z notes on 5th commit
+
+$commit_sha4 4th
+y notes on 4th commit
+
+$commit_sha3 3rd
+y notes on 3rd commit
+
+$commit_sha2 2nd
+z notes on 2nd commit
+
+$commit_sha1 1st
+
+EOF
+
+test_expect_success 'merge z into y with "theirs" strategy => Non-conflicting 3-way merge' '
+ git notes merge --strategy=theirs z &&
+ verify_notes y theirs
+'
+
+test_expect_success 'reset to pre-merge state (y)' '
+ git update-ref refs/notes/y refs/notes/y^1 &&
+ # Verify pre-merge state
+ verify_notes y y
+'
+
+cat <<EOF | sort >expect_notes_union
+7c4e546efd0fe939f876beb262ece02797880b54 $commit_sha15
+5de7ea7ad4f47e7ff91989fb82234634730f75df $commit_sha14
+3a631fdb6f41b05b55d8f4baf20728ba8f6fccbc $commit_sha13
+a66055fa82f7a03fe0c02a6aba3287a85abf7c62 $commit_sha12
+7e3c53503a3db8dd996cb62e37c66e070b44b54d $commit_sha11
+b8d03e173f67f6505a76f6e00cf93440200dd9be $commit_sha10
+851e1638784a884c7dd26c5d41f3340f6387413a $commit_sha8
+6c841cc36ea496027290967ca96bd2bef54dbb47 $commit_sha5
+e2bfd06a37dd2031684a59a6e2b033e212239c78 $commit_sha4
+5772f42408c0dd6f097a7ca2d24de0e78d1c46b1 $commit_sha3
+283b48219aee9a4105f6cab337e789065c82c2b9 $commit_sha2
+EOF
+
+cat >expect_log_union <<EOF
+$commit_sha15 15th
+y notes on 15th commit
+
+z notes on 15th commit
+
+$commit_sha14 14th
+y notes on 14th commit
+
+$commit_sha13 13th
+y notes on 13th commit
+
+$commit_sha12 12th
+y notes on 12th commit
+
+$commit_sha11 11th
+z notes on 11th commit
+
+$commit_sha10 10th
+x notes on 10th commit
+
+$commit_sha9 9th
+
+$commit_sha8 8th
+z notes on 8th commit
+
+$commit_sha7 7th
+
+$commit_sha6 6th
+
+$commit_sha5 5th
+y notes on 5th commit
+
+z notes on 5th commit
+
+$commit_sha4 4th
+y notes on 4th commit
+
+$commit_sha3 3rd
+y notes on 3rd commit
+
+$commit_sha2 2nd
+z notes on 2nd commit
+
+$commit_sha1 1st
+
+EOF
+
+test_expect_success 'merge z into y with "union" strategy => Non-conflicting 3-way merge' '
+ git notes merge --strategy=union z &&
+ verify_notes y union
+'
+
+test_expect_success 'reset to pre-merge state (y)' '
+ git update-ref refs/notes/y refs/notes/y^1 &&
+ # Verify pre-merge state
+ verify_notes y y
+'
+
+cat <<EOF | sort >expect_notes_union2
+d682107b8bf7a7aea1e537a8d5cb6a12b60135f1 $commit_sha15
+5de7ea7ad4f47e7ff91989fb82234634730f75df $commit_sha14
+3a631fdb6f41b05b55d8f4baf20728ba8f6fccbc $commit_sha13
+a66055fa82f7a03fe0c02a6aba3287a85abf7c62 $commit_sha12
+7e3c53503a3db8dd996cb62e37c66e070b44b54d $commit_sha11
+b8d03e173f67f6505a76f6e00cf93440200dd9be $commit_sha10
+851e1638784a884c7dd26c5d41f3340f6387413a $commit_sha8
+357b6ca14c7afd59b7f8b8aaaa6b8b723771135b $commit_sha5
+e2bfd06a37dd2031684a59a6e2b033e212239c78 $commit_sha4
+5772f42408c0dd6f097a7ca2d24de0e78d1c46b1 $commit_sha3
+283b48219aee9a4105f6cab337e789065c82c2b9 $commit_sha2
+EOF
+
+cat >expect_log_union2 <<EOF
+$commit_sha15 15th
+z notes on 15th commit
+
+y notes on 15th commit
+
+$commit_sha14 14th
+y notes on 14th commit
+
+$commit_sha13 13th
+y notes on 13th commit
+
+$commit_sha12 12th
+y notes on 12th commit
+
+$commit_sha11 11th
+z notes on 11th commit
+
+$commit_sha10 10th
+x notes on 10th commit
+
+$commit_sha9 9th
+
+$commit_sha8 8th
+z notes on 8th commit
+
+$commit_sha7 7th
+
+$commit_sha6 6th
+
+$commit_sha5 5th
+z notes on 5th commit
+
+y notes on 5th commit
+
+$commit_sha4 4th
+y notes on 4th commit
+
+$commit_sha3 3rd
+y notes on 3rd commit
+
+$commit_sha2 2nd
+z notes on 2nd commit
+
+$commit_sha1 1st
+
+EOF
+
+test_expect_success 'merge y into z with "union" strategy => Non-conflicting 3-way merge' '
+ git config core.notesRef refs/notes/z &&
+ git notes merge --strategy=union y &&
+ verify_notes z union2
+'
+
+test_expect_success 'reset to pre-merge state (z)' '
+ git update-ref refs/notes/z refs/notes/z^1 &&
+ # Verify pre-merge state
+ verify_notes z z
+'
+
+cat <<EOF | sort >expect_notes_cat_sort_uniq
+6be90240b5f54594203e25d9f2f64b7567175aee $commit_sha15
+5de7ea7ad4f47e7ff91989fb82234634730f75df $commit_sha14
+3a631fdb6f41b05b55d8f4baf20728ba8f6fccbc $commit_sha13
+a66055fa82f7a03fe0c02a6aba3287a85abf7c62 $commit_sha12
+7e3c53503a3db8dd996cb62e37c66e070b44b54d $commit_sha11
+b8d03e173f67f6505a76f6e00cf93440200dd9be $commit_sha10
+851e1638784a884c7dd26c5d41f3340f6387413a $commit_sha8
+660311d7f78dc53db12ac373a43fca7465381a7e $commit_sha5
+e2bfd06a37dd2031684a59a6e2b033e212239c78 $commit_sha4
+5772f42408c0dd6f097a7ca2d24de0e78d1c46b1 $commit_sha3
+283b48219aee9a4105f6cab337e789065c82c2b9 $commit_sha2
+EOF
+
+cat >expect_log_cat_sort_uniq <<EOF
+$commit_sha15 15th
+y notes on 15th commit
+z notes on 15th commit
+
+$commit_sha14 14th
+y notes on 14th commit
+
+$commit_sha13 13th
+y notes on 13th commit
+
+$commit_sha12 12th
+y notes on 12th commit
+
+$commit_sha11 11th
+z notes on 11th commit
+
+$commit_sha10 10th
+x notes on 10th commit
+
+$commit_sha9 9th
+
+$commit_sha8 8th
+z notes on 8th commit
+
+$commit_sha7 7th
+
+$commit_sha6 6th
+
+$commit_sha5 5th
+y notes on 5th commit
+z notes on 5th commit
+
+$commit_sha4 4th
+y notes on 4th commit
+
+$commit_sha3 3rd
+y notes on 3rd commit
+
+$commit_sha2 2nd
+z notes on 2nd commit
+
+$commit_sha1 1st
+
+EOF
+
+test_expect_success 'merge y into z with "cat_sort_uniq" strategy => Non-conflicting 3-way merge' '
+ git notes merge --strategy=cat_sort_uniq y &&
+ verify_notes z cat_sort_uniq
+'
+
+test_done
diff --git a/t/t3310-notes-merge-manual-resolve.sh b/t/t3310-notes-merge-manual-resolve.sh
--- /dev/null
@@ -0,0 +1,556 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Johan Herland
+#
+
+test_description='Test notes merging with manual conflict resolution'
+
+. ./test-lib.sh
+
+# Set up a notes merge scenario with different kinds of conflicts
+test_expect_success 'setup commits' '
+ test_commit 1st &&
+ test_commit 2nd &&
+ test_commit 3rd &&
+ test_commit 4th &&
+ test_commit 5th
+'
+
+commit_sha1=$(git rev-parse 1st^{commit})
+commit_sha2=$(git rev-parse 2nd^{commit})
+commit_sha3=$(git rev-parse 3rd^{commit})
+commit_sha4=$(git rev-parse 4th^{commit})
+commit_sha5=$(git rev-parse 5th^{commit})
+
+verify_notes () {
+ notes_ref="$1"
+ git -c core.notesRef="refs/notes/$notes_ref" notes |
+ sort >"output_notes_$notes_ref" &&
+ test_cmp "expect_notes_$notes_ref" "output_notes_$notes_ref" &&
+ git -c core.notesRef="refs/notes/$notes_ref" log --format="%H %s%n%N" \
+ >"output_log_$notes_ref" &&
+ test_cmp "expect_log_$notes_ref" "output_log_$notes_ref"
+}
+
+cat <<EOF | sort >expect_notes_x
+6e8e3febca3c2bb896704335cc4d0c34cb2f8715 $commit_sha4
+e5388c10860456ee60673025345fe2e153eb8cf8 $commit_sha3
+ceefa674873670e7ecd131814d909723cce2b669 $commit_sha2
+EOF
+
+cat >expect_log_x <<EOF
+$commit_sha5 5th
+
+$commit_sha4 4th
+x notes on 4th commit
+
+$commit_sha3 3rd
+x notes on 3rd commit
+
+$commit_sha2 2nd
+x notes on 2nd commit
+
+$commit_sha1 1st
+
+EOF
+
+test_expect_success 'setup merge base (x)' '
+ git config core.notesRef refs/notes/x &&
+ git notes add -m "x notes on 2nd commit" 2nd &&
+ git notes add -m "x notes on 3rd commit" 3rd &&
+ git notes add -m "x notes on 4th commit" 4th &&
+ verify_notes x
+'
+
+cat <<EOF | sort >expect_notes_y
+e2bfd06a37dd2031684a59a6e2b033e212239c78 $commit_sha4
+5772f42408c0dd6f097a7ca2d24de0e78d1c46b1 $commit_sha3
+b0a6021ec006d07e80e9b20ec9b444cbd9d560d3 $commit_sha1
+EOF
+
+cat >expect_log_y <<EOF
+$commit_sha5 5th
+
+$commit_sha4 4th
+y notes on 4th commit
+
+$commit_sha3 3rd
+y notes on 3rd commit
+
+$commit_sha2 2nd
+
+$commit_sha1 1st
+y notes on 1st commit
+
+EOF
+
+test_expect_success 'setup local branch (y)' '
+ git update-ref refs/notes/y refs/notes/x &&
+ git config core.notesRef refs/notes/y &&
+ git notes add -f -m "y notes on 1st commit" 1st &&
+ git notes remove 2nd &&
+ git notes add -f -m "y notes on 3rd commit" 3rd &&
+ git notes add -f -m "y notes on 4th commit" 4th &&
+ verify_notes y
+'
+
+cat <<EOF | sort >expect_notes_z
+cff59c793c20bb49a4e01bc06fb06bad642e0d54 $commit_sha4
+283b48219aee9a4105f6cab337e789065c82c2b9 $commit_sha2
+0a81da8956346e19bcb27a906f04af327e03e31b $commit_sha1
+EOF
+
+cat >expect_log_z <<EOF
+$commit_sha5 5th
+
+$commit_sha4 4th
+z notes on 4th commit
+
+$commit_sha3 3rd
+
+$commit_sha2 2nd
+z notes on 2nd commit
+
+$commit_sha1 1st
+z notes on 1st commit
+
+EOF
+
+test_expect_success 'setup remote branch (z)' '
+ git update-ref refs/notes/z refs/notes/x &&
+ git config core.notesRef refs/notes/z &&
+ git notes add -f -m "z notes on 1st commit" 1st &&
+ git notes add -f -m "z notes on 2nd commit" 2nd &&
+ git notes remove 3rd &&
+ git notes add -f -m "z notes on 4th commit" 4th &&
+ verify_notes z
+'
+
+# At this point, before merging z into y, we have the following status:
+#
+# commit | base/x | local/y | remote/z | diff from x to y/z
+# -------|---------|---------|----------|---------------------------
+# 1st | [none] | b0a6021 | 0a81da8 | added / added (diff)
+# 2nd | ceefa67 | [none] | 283b482 | removed / changed
+# 3rd | e5388c1 | 5772f42 | [none] | changed / removed
+# 4th | 6e8e3fe | e2bfd06 | cff59c7 | changed / changed (diff)
+# 5th | [none] | [none] | [none] | [none]
+
+cat <<EOF | sort >expect_conflicts
+$commit_sha1
+$commit_sha2
+$commit_sha3
+$commit_sha4
+EOF
+
+cat >expect_conflict_$commit_sha1 <<EOF
+<<<<<<< refs/notes/m
+y notes on 1st commit
+=======
+z notes on 1st commit
+>>>>>>> refs/notes/z
+EOF
+
+cat >expect_conflict_$commit_sha2 <<EOF
+z notes on 2nd commit
+EOF
+
+cat >expect_conflict_$commit_sha3 <<EOF
+y notes on 3rd commit
+EOF
+
+cat >expect_conflict_$commit_sha4 <<EOF
+<<<<<<< refs/notes/m
+y notes on 4th commit
+=======
+z notes on 4th commit
+>>>>>>> refs/notes/z
+EOF
+
+cp expect_notes_y expect_notes_m
+cp expect_log_y expect_log_m
+
+git rev-parse refs/notes/y > pre_merge_y
+git rev-parse refs/notes/z > pre_merge_z
+
+test_expect_success 'merge z into m (== y) with default ("manual") resolver => Conflicting 3-way merge' '
+ git update-ref refs/notes/m refs/notes/y &&
+ git config core.notesRef refs/notes/m &&
+ test_must_fail git notes merge z >output &&
+ # Output should point to where to resolve conflicts
+ grep -q "\\.git/NOTES_MERGE_WORKTREE" output &&
+ # Inspect merge conflicts
+ ls .git/NOTES_MERGE_WORKTREE >output_conflicts &&
+ test_cmp expect_conflicts output_conflicts &&
+ ( for f in $(cat expect_conflicts); do
+ test_cmp "expect_conflict_$f" ".git/NOTES_MERGE_WORKTREE/$f" ||
+ exit 1
+ done ) &&
+ # Verify that current notes tree (pre-merge) has not changed (m == y)
+ verify_notes y &&
+ verify_notes m &&
+ test "$(git rev-parse refs/notes/m)" = "$(cat pre_merge_y)"
+'
+
+cat <<EOF | sort >expect_notes_z
+00494adecf2d9635a02fa431308d67993f853968 $commit_sha4
+283b48219aee9a4105f6cab337e789065c82c2b9 $commit_sha2
+0a81da8956346e19bcb27a906f04af327e03e31b $commit_sha1
+EOF
+
+cat >expect_log_z <<EOF
+$commit_sha5 5th
+
+$commit_sha4 4th
+z notes on 4th commit
+
+More z notes on 4th commit
+
+$commit_sha3 3rd
+
+$commit_sha2 2nd
+z notes on 2nd commit
+
+$commit_sha1 1st
+z notes on 1st commit
+
+EOF
+
+test_expect_success 'change notes in z' '
+ git notes --ref z append -m "More z notes on 4th commit" 4th &&
+ verify_notes z
+'
+
+test_expect_success 'cannot do merge w/conflicts when previous merge is unfinished' '
+ test -d .git/NOTES_MERGE_WORKTREE &&
+ test_must_fail git notes merge z >output 2>&1 &&
+ # Output should indicate what is wrong
+ grep -q "\\.git/NOTES_MERGE_\\* exists" output
+'
+
+# Setup non-conflicting merge between x and new notes ref w
+
+cat <<EOF | sort >expect_notes_w
+ceefa674873670e7ecd131814d909723cce2b669 $commit_sha2
+f75d1df88cbfe4258d49852f26cfc83f2ad4494b $commit_sha1
+EOF
+
+cat >expect_log_w <<EOF
+$commit_sha5 5th
+
+$commit_sha4 4th
+
+$commit_sha3 3rd
+
+$commit_sha2 2nd
+x notes on 2nd commit
+
+$commit_sha1 1st
+w notes on 1st commit
+
+EOF
+
+test_expect_success 'setup unrelated notes ref (w)' '
+ git config core.notesRef refs/notes/w &&
+ git notes add -m "w notes on 1st commit" 1st &&
+ git notes add -m "x notes on 2nd commit" 2nd &&
+ verify_notes w
+'
+
+cat <<EOF | sort >expect_notes_w
+6e8e3febca3c2bb896704335cc4d0c34cb2f8715 $commit_sha4
+e5388c10860456ee60673025345fe2e153eb8cf8 $commit_sha3
+ceefa674873670e7ecd131814d909723cce2b669 $commit_sha2
+f75d1df88cbfe4258d49852f26cfc83f2ad4494b $commit_sha1
+EOF
+
+cat >expect_log_w <<EOF
+$commit_sha5 5th
+
+$commit_sha4 4th
+x notes on 4th commit
+
+$commit_sha3 3rd
+x notes on 3rd commit
+
+$commit_sha2 2nd
+x notes on 2nd commit
+
+$commit_sha1 1st
+w notes on 1st commit
+
+EOF
+
+test_expect_success 'can do merge without conflicts even if previous merge is unfinished (x => w)' '
+ test -d .git/NOTES_MERGE_WORKTREE &&
+ git notes merge x &&
+ verify_notes w &&
+ # Verify that other notes refs has not changed (x and y)
+ verify_notes x &&
+ verify_notes y
+'
+
+cat <<EOF | sort >expect_notes_m
+021faa20e931fb48986ffc6282b4bb05553ac946 $commit_sha4
+5772f42408c0dd6f097a7ca2d24de0e78d1c46b1 $commit_sha3
+283b48219aee9a4105f6cab337e789065c82c2b9 $commit_sha2
+0a59e787e6d688aa6309e56e8c1b89431a0fc1c1 $commit_sha1
+EOF
+
+cat >expect_log_m <<EOF
+$commit_sha5 5th
+
+$commit_sha4 4th
+y and z notes on 4th commit
+
+$commit_sha3 3rd
+y notes on 3rd commit
+
+$commit_sha2 2nd
+z notes on 2nd commit
+
+$commit_sha1 1st
+y and z notes on 1st commit
+
+EOF
+
+test_expect_success 'finalize conflicting merge (z => m)' '
+ # Resolve conflicts and finalize merge
+ cat >.git/NOTES_MERGE_WORKTREE/$commit_sha1 <<EOF &&
+y and z notes on 1st commit
+EOF
+ cat >.git/NOTES_MERGE_WORKTREE/$commit_sha4 <<EOF &&
+y and z notes on 4th commit
+EOF
+ git notes merge --commit &&
+ # No .git/NOTES_MERGE_* files left
+ test_must_fail ls .git/NOTES_MERGE_* >output 2>/dev/null &&
+ test_cmp /dev/null output &&
+ # Merge commit has pre-merge y and pre-merge z as parents
+ test "$(git rev-parse refs/notes/m^1)" = "$(cat pre_merge_y)" &&
+ test "$(git rev-parse refs/notes/m^2)" = "$(cat pre_merge_z)" &&
+ # Merge commit mentions the notes refs merged
+ git log -1 --format=%B refs/notes/m > merge_commit_msg &&
+ grep -q refs/notes/m merge_commit_msg &&
+ grep -q refs/notes/z merge_commit_msg &&
+ # Merge commit mentions conflicting notes
+ grep -q "Conflicts" merge_commit_msg &&
+ ( for sha1 in $(cat expect_conflicts); do
+ grep -q "$sha1" merge_commit_msg ||
+ exit 1
+ done ) &&
+ # Verify contents of merge result
+ verify_notes m &&
+ # Verify that other notes refs has not changed (w, x, y and z)
+ verify_notes w &&
+ verify_notes x &&
+ verify_notes y &&
+ verify_notes z
+'
+
+cat >expect_conflict_$commit_sha4 <<EOF
+<<<<<<< refs/notes/m
+y notes on 4th commit
+=======
+z notes on 4th commit
+
+More z notes on 4th commit
+>>>>>>> refs/notes/z
+EOF
+
+cp expect_notes_y expect_notes_m
+cp expect_log_y expect_log_m
+
+git rev-parse refs/notes/y > pre_merge_y
+git rev-parse refs/notes/z > pre_merge_z
+
+test_expect_success 'redo merge of z into m (== y) with default ("manual") resolver => Conflicting 3-way merge' '
+ git update-ref refs/notes/m refs/notes/y &&
+ git config core.notesRef refs/notes/m &&
+ test_must_fail git notes merge z >output &&
+ # Output should point to where to resolve conflicts
+ grep -q "\\.git/NOTES_MERGE_WORKTREE" output &&
+ # Inspect merge conflicts
+ ls .git/NOTES_MERGE_WORKTREE >output_conflicts &&
+ test_cmp expect_conflicts output_conflicts &&
+ ( for f in $(cat expect_conflicts); do
+ test_cmp "expect_conflict_$f" ".git/NOTES_MERGE_WORKTREE/$f" ||
+ exit 1
+ done ) &&
+ # Verify that current notes tree (pre-merge) has not changed (m == y)
+ verify_notes y &&
+ verify_notes m &&
+ test "$(git rev-parse refs/notes/m)" = "$(cat pre_merge_y)"
+'
+
+test_expect_success 'abort notes merge' '
+ git notes merge --abort &&
+ # No .git/NOTES_MERGE_* files left
+ test_must_fail ls .git/NOTES_MERGE_* >output 2>/dev/null &&
+ test_cmp /dev/null output &&
+ # m has not moved (still == y)
+ test "$(git rev-parse refs/notes/m)" = "$(cat pre_merge_y)"
+ # Verify that other notes refs has not changed (w, x, y and z)
+ verify_notes w &&
+ verify_notes x &&
+ verify_notes y &&
+ verify_notes z
+'
+
+git rev-parse refs/notes/y > pre_merge_y
+git rev-parse refs/notes/z > pre_merge_z
+
+test_expect_success 'redo merge of z into m (== y) with default ("manual") resolver => Conflicting 3-way merge' '
+ test_must_fail git notes merge z >output &&
+ # Output should point to where to resolve conflicts
+ grep -q "\\.git/NOTES_MERGE_WORKTREE" output &&
+ # Inspect merge conflicts
+ ls .git/NOTES_MERGE_WORKTREE >output_conflicts &&
+ test_cmp expect_conflicts output_conflicts &&
+ ( for f in $(cat expect_conflicts); do
+ test_cmp "expect_conflict_$f" ".git/NOTES_MERGE_WORKTREE/$f" ||
+ exit 1
+ done ) &&
+ # Verify that current notes tree (pre-merge) has not changed (m == y)
+ verify_notes y &&
+ verify_notes m &&
+ test "$(git rev-parse refs/notes/m)" = "$(cat pre_merge_y)"
+'
+
+cat <<EOF | sort >expect_notes_m
+304dfb4325cf243025b9957486eb605a9b51c199 $commit_sha5
+283b48219aee9a4105f6cab337e789065c82c2b9 $commit_sha2
+0a59e787e6d688aa6309e56e8c1b89431a0fc1c1 $commit_sha1
+EOF
+
+cat >expect_log_m <<EOF
+$commit_sha5 5th
+new note on 5th commit
+
+$commit_sha4 4th
+
+$commit_sha3 3rd
+
+$commit_sha2 2nd
+z notes on 2nd commit
+
+$commit_sha1 1st
+y and z notes on 1st commit
+
+EOF
+
+test_expect_success 'add + remove notes in finalized merge (z => m)' '
+ # Resolve one conflict
+ cat >.git/NOTES_MERGE_WORKTREE/$commit_sha1 <<EOF &&
+y and z notes on 1st commit
+EOF
+ # Remove another conflict
+ rm .git/NOTES_MERGE_WORKTREE/$commit_sha4 &&
+ # Remove a D/F conflict
+ rm .git/NOTES_MERGE_WORKTREE/$commit_sha3 &&
+ # Add a new note
+ echo "new note on 5th commit" > .git/NOTES_MERGE_WORKTREE/$commit_sha5 &&
+ # Finalize merge
+ git notes merge --commit &&
+ # No .git/NOTES_MERGE_* files left
+ test_must_fail ls .git/NOTES_MERGE_* >output 2>/dev/null &&
+ test_cmp /dev/null output &&
+ # Merge commit has pre-merge y and pre-merge z as parents
+ test "$(git rev-parse refs/notes/m^1)" = "$(cat pre_merge_y)" &&
+ test "$(git rev-parse refs/notes/m^2)" = "$(cat pre_merge_z)" &&
+ # Merge commit mentions the notes refs merged
+ git log -1 --format=%B refs/notes/m > merge_commit_msg &&
+ grep -q refs/notes/m merge_commit_msg &&
+ grep -q refs/notes/z merge_commit_msg &&
+ # Merge commit mentions conflicting notes
+ grep -q "Conflicts" merge_commit_msg &&
+ ( for sha1 in $(cat expect_conflicts); do
+ grep -q "$sha1" merge_commit_msg ||
+ exit 1
+ done ) &&
+ # Verify contents of merge result
+ verify_notes m &&
+ # Verify that other notes refs has not changed (w, x, y and z)
+ verify_notes w &&
+ verify_notes x &&
+ verify_notes y &&
+ verify_notes z
+'
+
+cp expect_notes_y expect_notes_m
+cp expect_log_y expect_log_m
+
+test_expect_success 'redo merge of z into m (== y) with default ("manual") resolver => Conflicting 3-way merge' '
+ git update-ref refs/notes/m refs/notes/y &&
+ test_must_fail git notes merge z >output &&
+ # Output should point to where to resolve conflicts
+ grep -q "\\.git/NOTES_MERGE_WORKTREE" output &&
+ # Inspect merge conflicts
+ ls .git/NOTES_MERGE_WORKTREE >output_conflicts &&
+ test_cmp expect_conflicts output_conflicts &&
+ ( for f in $(cat expect_conflicts); do
+ test_cmp "expect_conflict_$f" ".git/NOTES_MERGE_WORKTREE/$f" ||
+ exit 1
+ done ) &&
+ # Verify that current notes tree (pre-merge) has not changed (m == y)
+ verify_notes y &&
+ verify_notes m &&
+ test "$(git rev-parse refs/notes/m)" = "$(cat pre_merge_y)"
+'
+
+cp expect_notes_w expect_notes_m
+cp expect_log_w expect_log_m
+
+test_expect_success 'reset notes ref m to somewhere else (w)' '
+ git update-ref refs/notes/m refs/notes/w &&
+ verify_notes m &&
+ test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/w)"
+'
+
+test_expect_success 'fail to finalize conflicting merge if underlying ref has moved in the meantime (m != NOTES_MERGE_PARTIAL^1)' '
+ # Resolve conflicts
+ cat >.git/NOTES_MERGE_WORKTREE/$commit_sha1 <<EOF &&
+y and z notes on 1st commit
+EOF
+ cat >.git/NOTES_MERGE_WORKTREE/$commit_sha4 <<EOF &&
+y and z notes on 4th commit
+EOF
+ # Fail to finalize merge
+ test_must_fail git notes merge --commit >output 2>&1 &&
+ # .git/NOTES_MERGE_* must remain
+ test -f .git/NOTES_MERGE_PARTIAL &&
+ test -f .git/NOTES_MERGE_REF &&
+ test -f .git/NOTES_MERGE_WORKTREE/$commit_sha1 &&
+ test -f .git/NOTES_MERGE_WORKTREE/$commit_sha2 &&
+ test -f .git/NOTES_MERGE_WORKTREE/$commit_sha3 &&
+ test -f .git/NOTES_MERGE_WORKTREE/$commit_sha4 &&
+ # Refs are unchanged
+ test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/w)"
+ test "$(git rev-parse refs/notes/y)" = "$(git rev-parse NOTES_MERGE_PARTIAL^1)"
+ test "$(git rev-parse refs/notes/m)" != "$(git rev-parse NOTES_MERGE_PARTIAL^1)"
+ # Mention refs/notes/m, and its current and expected value in output
+ grep -q "refs/notes/m" output &&
+ grep -q "$(git rev-parse refs/notes/m)" output &&
+ grep -q "$(git rev-parse NOTES_MERGE_PARTIAL^1)" output &&
+ # Verify that other notes refs has not changed (w, x, y and z)
+ verify_notes w &&
+ verify_notes x &&
+ verify_notes y &&
+ verify_notes z
+'
+
+test_expect_success 'resolve situation by aborting the notes merge' '
+ git notes merge --abort &&
+ # No .git/NOTES_MERGE_* files left
+ test_must_fail ls .git/NOTES_MERGE_* >output 2>/dev/null &&
+ test_cmp /dev/null output &&
+ # m has not moved (still == w)
+ test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/w)"
+ # Verify that other notes refs has not changed (w, x, y and z)
+ verify_notes w &&
+ verify_notes x &&
+ verify_notes y &&
+ verify_notes z
+'
+
+test_done
diff --git a/t/t3311-notes-merge-fanout.sh b/t/t3311-notes-merge-fanout.sh
--- /dev/null
@@ -0,0 +1,436 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Johan Herland
+#
+
+test_description='Test notes merging at various fanout levels'
+
+. ./test-lib.sh
+
+verify_notes () {
+ notes_ref="$1"
+ commit="$2"
+ if test -f "expect_notes_$notes_ref"
+ then
+ git -c core.notesRef="refs/notes/$notes_ref" notes |
+ sort >"output_notes_$notes_ref" &&
+ test_cmp "expect_notes_$notes_ref" "output_notes_$notes_ref" ||
+ return 1
+ fi &&
+ git -c core.notesRef="refs/notes/$notes_ref" log --format="%H %s%n%N" \
+ "$commit" >"output_log_$notes_ref" &&
+ test_cmp "expect_log_$notes_ref" "output_log_$notes_ref"
+}
+
+verify_fanout () {
+ notes_ref="$1"
+ # Expect entire notes tree to have a fanout == 1
+ git rev-parse --quiet --verify "refs/notes/$notes_ref" >/dev/null &&
+ git ls-tree -r --name-only "refs/notes/$notes_ref" |
+ while read path
+ do
+ case "$path" in
+ ??/??????????????????????????????????????)
+ : true
+ ;;
+ *)
+ echo "Invalid path \"$path\"" &&
+ return 1
+ ;;
+ esac
+ done
+}
+
+verify_no_fanout () {
+ notes_ref="$1"
+ # Expect entire notes tree to have a fanout == 0
+ git rev-parse --quiet --verify "refs/notes/$notes_ref" >/dev/null &&
+ git ls-tree -r --name-only "refs/notes/$notes_ref" |
+ while read path
+ do
+ case "$path" in
+ ????????????????????????????????????????)
+ : true
+ ;;
+ *)
+ echo "Invalid path \"$path\"" &&
+ return 1
+ ;;
+ esac
+ done
+}
+
+# Set up a notes merge scenario with different kinds of conflicts
+test_expect_success 'setup a few initial commits with notes (notes ref: x)' '
+ git config core.notesRef refs/notes/x &&
+ for i in 1 2 3 4 5
+ do
+ test_commit "commit$i" >/dev/null &&
+ git notes add -m "notes for commit$i" || return 1
+ done
+'
+
+commit_sha1=$(git rev-parse commit1^{commit})
+commit_sha2=$(git rev-parse commit2^{commit})
+commit_sha3=$(git rev-parse commit3^{commit})
+commit_sha4=$(git rev-parse commit4^{commit})
+commit_sha5=$(git rev-parse commit5^{commit})
+
+cat <<EOF | sort >expect_notes_x
+aed91155c7a72c2188e781fdf40e0f3761b299db $commit_sha5
+99fab268f9d7ee7b011e091a436c78def8eeee69 $commit_sha4
+953c20ae26c7aa0b428c20693fe38bc687f9d1a9 $commit_sha3
+6358796131b8916eaa2dde6902642942a1cb37e1 $commit_sha2
+b02d459c32f0e68f2fe0981033bb34f38776ba47 $commit_sha1
+EOF
+
+cat >expect_log_x <<EOF
+$commit_sha5 commit5
+notes for commit5
+
+$commit_sha4 commit4
+notes for commit4
+
+$commit_sha3 commit3
+notes for commit3
+
+$commit_sha2 commit2
+notes for commit2
+
+$commit_sha1 commit1
+notes for commit1
+
+EOF
+
+test_expect_success 'sanity check (x)' '
+ verify_notes x commit5 &&
+ verify_no_fanout x
+'
+
+num=300
+
+cp expect_log_x expect_log_y
+
+test_expect_success 'Add a few hundred commits w/notes to trigger fanout (x -> y)' '
+ git update-ref refs/notes/y refs/notes/x &&
+ git config core.notesRef refs/notes/y &&
+ i=5 &&
+ while test $i -lt $num
+ do
+ i=$(($i + 1)) &&
+ test_commit "commit$i" >/dev/null &&
+ git notes add -m "notes for commit$i" || return 1
+ done &&
+ test "$(git rev-parse refs/notes/y)" != "$(git rev-parse refs/notes/x)" &&
+ # Expected number of commits and notes
+ test $(git rev-list HEAD | wc -l) = $num &&
+ test $(git notes list | wc -l) = $num &&
+ # 5 first notes unchanged
+ verify_notes y commit5
+'
+
+test_expect_success 'notes tree has fanout (y)' 'verify_fanout y'
+
+test_expect_success 'No-op merge (already included) (x => y)' '
+ git update-ref refs/notes/m refs/notes/y &&
+ git config core.notesRef refs/notes/m &&
+ git notes merge x &&
+ test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/y)"
+'
+
+test_expect_success 'Fast-forward merge (y => x)' '
+ git update-ref refs/notes/m refs/notes/x &&
+ git notes merge y &&
+ test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/y)"
+'
+
+cat <<EOF | sort >expect_notes_z
+9f506ee70e20379d7f78204c77b334f43d77410d $commit_sha3
+23a47d6ea7d589895faf800752054818e1e7627b $commit_sha2
+b02d459c32f0e68f2fe0981033bb34f38776ba47 $commit_sha1
+EOF
+
+cat >expect_log_z <<EOF
+$commit_sha5 commit5
+
+$commit_sha4 commit4
+
+$commit_sha3 commit3
+notes for commit3
+
+appended notes for commit3
+
+$commit_sha2 commit2
+new notes for commit2
+
+$commit_sha1 commit1
+notes for commit1
+
+EOF
+
+test_expect_success 'change some of the initial 5 notes (x -> z)' '
+ git update-ref refs/notes/z refs/notes/x &&
+ git config core.notesRef refs/notes/z &&
+ git notes add -f -m "new notes for commit2" commit2 &&
+ git notes append -m "appended notes for commit3" commit3 &&
+ git notes remove commit4 &&
+ git notes remove commit5 &&
+ verify_notes z commit5
+'
+
+test_expect_success 'notes tree has no fanout (z)' 'verify_no_fanout z'
+
+cp expect_log_z expect_log_m
+
+test_expect_success 'successful merge without conflicts (y => z)' '
+ git update-ref refs/notes/m refs/notes/z &&
+ git config core.notesRef refs/notes/m &&
+ git notes merge y &&
+ verify_notes m commit5 &&
+ # x/y/z unchanged
+ verify_notes x commit5 &&
+ verify_notes y commit5 &&
+ verify_notes z commit5
+'
+
+test_expect_success 'notes tree still has fanout after merge (m)' 'verify_fanout m'
+
+cat >expect_log_w <<EOF
+$commit_sha5 commit5
+
+$commit_sha4 commit4
+other notes for commit4
+
+$commit_sha3 commit3
+other notes for commit3
+
+$commit_sha2 commit2
+notes for commit2
+
+$commit_sha1 commit1
+other notes for commit1
+
+EOF
+
+test_expect_success 'introduce conflicting changes (y -> w)' '
+ git update-ref refs/notes/w refs/notes/y &&
+ git config core.notesRef refs/notes/w &&
+ git notes add -f -m "other notes for commit1" commit1 &&
+ git notes add -f -m "other notes for commit3" commit3 &&
+ git notes add -f -m "other notes for commit4" commit4 &&
+ git notes remove commit5 &&
+ verify_notes w commit5
+'
+
+cat >expect_log_m <<EOF
+$commit_sha5 commit5
+
+$commit_sha4 commit4
+other notes for commit4
+
+$commit_sha3 commit3
+other notes for commit3
+
+$commit_sha2 commit2
+new notes for commit2
+
+$commit_sha1 commit1
+other notes for commit1
+
+EOF
+
+test_expect_success 'successful merge using "ours" strategy (z => w)' '
+ git update-ref refs/notes/m refs/notes/w &&
+ git config core.notesRef refs/notes/m &&
+ git notes merge -s ours z &&
+ verify_notes m commit5 &&
+ # w/x/y/z unchanged
+ verify_notes w commit5 &&
+ verify_notes x commit5 &&
+ verify_notes y commit5 &&
+ verify_notes z commit5
+'
+
+test_expect_success 'notes tree still has fanout after merge (m)' 'verify_fanout m'
+
+cat >expect_log_m <<EOF
+$commit_sha5 commit5
+
+$commit_sha4 commit4
+
+$commit_sha3 commit3
+notes for commit3
+
+appended notes for commit3
+
+$commit_sha2 commit2
+new notes for commit2
+
+$commit_sha1 commit1
+other notes for commit1
+
+EOF
+
+test_expect_success 'successful merge using "theirs" strategy (z => w)' '
+ git update-ref refs/notes/m refs/notes/w &&
+ git notes merge -s theirs z &&
+ verify_notes m commit5 &&
+ # w/x/y/z unchanged
+ verify_notes w commit5 &&
+ verify_notes x commit5 &&
+ verify_notes y commit5 &&
+ verify_notes z commit5
+'
+
+test_expect_success 'notes tree still has fanout after merge (m)' 'verify_fanout m'
+
+cat >expect_log_m <<EOF
+$commit_sha5 commit5
+
+$commit_sha4 commit4
+other notes for commit4
+
+$commit_sha3 commit3
+other notes for commit3
+
+notes for commit3
+
+appended notes for commit3
+
+$commit_sha2 commit2
+new notes for commit2
+
+$commit_sha1 commit1
+other notes for commit1
+
+EOF
+
+test_expect_success 'successful merge using "union" strategy (z => w)' '
+ git update-ref refs/notes/m refs/notes/w &&
+ git notes merge -s union z &&
+ verify_notes m commit5 &&
+ # w/x/y/z unchanged
+ verify_notes w commit5 &&
+ verify_notes x commit5 &&
+ verify_notes y commit5 &&
+ verify_notes z commit5
+'
+
+test_expect_success 'notes tree still has fanout after merge (m)' 'verify_fanout m'
+
+cat >expect_log_m <<EOF
+$commit_sha5 commit5
+
+$commit_sha4 commit4
+other notes for commit4
+
+$commit_sha3 commit3
+appended notes for commit3
+notes for commit3
+other notes for commit3
+
+$commit_sha2 commit2
+new notes for commit2
+
+$commit_sha1 commit1
+other notes for commit1
+
+EOF
+
+test_expect_success 'successful merge using "cat_sort_uniq" strategy (z => w)' '
+ git update-ref refs/notes/m refs/notes/w &&
+ git notes merge -s cat_sort_uniq z &&
+ verify_notes m commit5 &&
+ # w/x/y/z unchanged
+ verify_notes w commit5 &&
+ verify_notes x commit5 &&
+ verify_notes y commit5 &&
+ verify_notes z commit5
+'
+
+test_expect_success 'notes tree still has fanout after merge (m)' 'verify_fanout m'
+
+# We're merging z into w. Here are the conflicts we expect:
+#
+# commit | x -> w | x -> z | conflict?
+# -------|-----------|-----------|----------
+# 1 | changed | unchanged | no, use w
+# 2 | unchanged | changed | no, use z
+# 3 | changed | changed | yes (w, then z in conflict markers)
+# 4 | changed | deleted | yes (w)
+# 5 | deleted | deleted | no, deleted
+
+test_expect_success 'fails to merge using "manual" strategy (z => w)' '
+ git update-ref refs/notes/m refs/notes/w &&
+ test_must_fail git notes merge z
+'
+
+test_expect_success 'notes tree still has fanout after merge (m)' 'verify_fanout m'
+
+cat <<EOF | sort >expect_conflicts
+$commit_sha3
+$commit_sha4
+EOF
+
+cat >expect_conflict_$commit_sha3 <<EOF
+<<<<<<< refs/notes/m
+other notes for commit3
+=======
+notes for commit3
+
+appended notes for commit3
+>>>>>>> refs/notes/z
+EOF
+
+cat >expect_conflict_$commit_sha4 <<EOF
+other notes for commit4
+EOF
+
+test_expect_success 'verify conflict entries (with no fanout)' '
+ ls .git/NOTES_MERGE_WORKTREE >output_conflicts &&
+ test_cmp expect_conflicts output_conflicts &&
+ ( for f in $(cat expect_conflicts); do
+ test_cmp "expect_conflict_$f" ".git/NOTES_MERGE_WORKTREE/$f" ||
+ exit 1
+ done ) &&
+ # Verify that current notes tree (pre-merge) has not changed (m == w)
+ test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/w)"
+'
+
+cat >expect_log_m <<EOF
+$commit_sha5 commit5
+
+$commit_sha4 commit4
+other notes for commit4
+
+$commit_sha3 commit3
+other notes for commit3
+
+appended notes for commit3
+
+$commit_sha2 commit2
+new notes for commit2
+
+$commit_sha1 commit1
+other notes for commit1
+
+EOF
+
+test_expect_success 'resolve and finalize merge (z => w)' '
+ cat >.git/NOTES_MERGE_WORKTREE/$commit_sha3 <<EOF &&
+other notes for commit3
+
+appended notes for commit3
+EOF
+ git notes merge --commit &&
+ verify_notes m commit5 &&
+ # w/x/y/z unchanged
+ verify_notes w commit5 &&
+ verify_notes x commit5 &&
+ verify_notes y commit5 &&
+ verify_notes z commit5
+'
+
+test_expect_success 'notes tree still has fanout after merge (m)' 'verify_fanout m'
+
+test_done
index 9e9474e9447c8835eb4ef30714b577896a06924d..7d8147bb93df482cf7e11338ac96acdf43cf551e 100755 (executable)
This test runs git rebase "interactively", by faking an edit, and verifies
that the result still makes sense.
+
+Initial setup:
+
+ one - two - three - four (conflict-branch)
+ /
+ A - B - C - D - E (master)
+ | \
+ | F - G - H (branch1)
+ | \
+ |\ I (branch2)
+ | \
+ | J - K - L - M (no-conflict-branch)
+ \
+ N - O - P (no-ff-branch)
+
+ where A, B, D and G all touch file1, and one, two, three, four all
+ touch file "conflict".
'
. ./test-lib.sh
. "$TEST_DIRECTORY"/lib-rebase.sh
+test_cmp_rev () {
+ git rev-parse --verify "$1" >expect.rev &&
+ git rev-parse --verify "$2" >actual.rev &&
+ test_cmp expect.rev actual.rev
+}
+
set_fake_editor
-# Set up the repository like this:
-#
-# one - two - three - four (conflict-branch)
-# /
-# A - B - C - D - E (master)
-# | \
-# | F - G - H (branch1)
-# | \
-# |\ I (branch2)
-# | \
-# | J - K - L - M (no-conflict-branch)
-# \
-# N - O - P (no-ff-branch)
-#
-# where A, B, D and G all touch file1, and one, two, three, four all
-# touch file "conflict".
-#
# WARNING: Modifications to the initial repository can change the SHA ID used
# in the expect2 file for the 'stop on conflicting pick' test.
-
test_expect_success 'setup' '
test_commit A file1 &&
test_commit B file1 &&
test_commit G file1 &&
test_commit H file5 &&
git checkout -b branch2 F &&
- test_commit I file6
+ test_commit I file6 &&
git checkout -b conflict-branch A &&
- for n in one two three four
- do
- test_commit $n conflict
- done &&
+ test_commit one conflict &&
+ test_commit two conflict &&
+ test_commit three conflict &&
+ test_commit four conflict &&
git checkout -b no-conflict-branch A &&
- for n in J K L M
- do
- test_commit $n file$n
- done &&
+ test_commit J fileJ &&
+ test_commit K fileK &&
+ test_commit L fileL &&
+ test_commit M fileM &&
git checkout -b no-ff-branch A &&
- for n in N O P
- do
- test_commit $n file$n
- done
+ test_commit N fileN &&
+ test_commit O fileO &&
+ test_commit P fileP
'
# "exec" commands are ran with the user shell by default, but this may
test_path_is_file touch-one &&
test_path_is_file touch-two &&
test_path_is_missing touch-three " (should have stopped before)" &&
- test $(git rev-parse C) = $(git rev-parse HEAD) || {
- echo "Stopped at wrong revision:"
- echo "($(git describe --tags HEAD) instead of C)"
- false
- } &&
+ test_cmp_rev C HEAD &&
git rebase --continue &&
test_path_is_file touch-three &&
test_path_is_file "touch-file name with spaces" &&
test_path_is_file touch-after-semicolon &&
- test $(git rev-parse master) = $(git rev-parse HEAD) || {
- echo "Stopped at wrong revision:"
- echo "($(git describe --tags HEAD) instead of master)"
- false
- } &&
+ test_cmp_rev master HEAD &&
rm -f touch-*
'
export FAKE_LINES &&
test_must_fail git rebase -i HEAD^
) &&
- test $(git rev-parse master^) = $(git rev-parse HEAD) || {
- echo "Stopped at wrong revision:"
- echo "($(git describe --tags HEAD) instead of master^)"
- false
- } &&
+ test_cmp_rev master^ HEAD &&
git reset --hard &&
git rebase --continue
'
git checkout -b branch4 HEAD &&
GIT_EDITOR=: git commit --amend \
- --author="Somebody else <somebody@else.com>"
+ --author="Somebody else <somebody@else.com>" &&
test $(git rev-parse branch3) != $(git rev-parse branch4) &&
git rebase -i branch3 &&
test $(git rev-parse branch3) = $(git rev-parse branch4)
git add elif && git commit -m "submodule initial"
) &&
echo 1 >file1 &&
- git add file1 sub
+ git add file1 sub &&
test_tick &&
git commit -m "One" &&
echo 2 >file1 &&
cat >expect <<EOF
an earlier note
+
a note
EOF
index 85fc7c4af8cebdb50a7fa294b274bb2e7988997b..fe5f936988bc5ec9bbe6d9175ef6e085c422eadc 100755 (executable)
'
test_expect_success 'rebase --stat' '
- git reset --hard start
+ git reset --hard start &&
git rebase --stat master >diffstat.txt &&
grep "^ fileX | *1 +$" diffstat.txt
'
test_expect_success 'rebase w/config rebase.stat' '
- git reset --hard start
+ git reset --hard start &&
git config rebase.stat true &&
git rebase master >diffstat.txt &&
grep "^ fileX | *1 +$" diffstat.txt
'
test_expect_success 'rebase -n overrides config rebase.stat config' '
- git reset --hard start
+ git reset --hard start &&
git config rebase.stat true &&
git rebase -n master >diffstat.txt &&
! grep "^ fileX | *1 +$" diffstat.txt
index 2062b858bbcb63a715d87f3b12adfd0e9ceb3a67..6b84e6042a6fcc9cf850a53ad2a885597fb178fc 100755 (executable)
git commit -a -m "A sample commit log message that has a long
summary that spills over multiple lines.
-But otherwise with a sane description."
+But otherwise with a sane description." &&
git branch side &&
index 74161a42ec34c2d33780a80425ec1be5a4028f03..19341e5ca1c7f415afdf2c82f67930b8fed5478b 100755 (executable)
git fetch &&
git rebase -p origin/topic &&
test 1 = $(git rev-list --all --pretty=oneline | grep "Modify A" | wc -l) &&
- test 1 = $(git rev-list --all --pretty=oneline | grep "Merge remote branch " | wc -l)
+ test 1 = $(git rev-list --all --pretty=oneline | grep "Merge remote-tracking branch " | wc -l)
)
'
diff --git a/t/t3412-rebase-root.sh b/t/t3412-rebase-root.sh
index 5869061c5bfdee4a84b156b8ec9d6e331a2c906c..086c91c7b47aa2fa7b593f4f9b2f84f6b7ba3724 100755 (executable)
--- a/t/t3412-rebase-root.sh
+++ b/t/t3412-rebase-root.sh
test_expect_success 'pre-rebase hook stops rebase' '
git checkout -b stops1 other &&
test_must_fail git rebase --root --onto master &&
- test "z$(git symbolic-ref HEAD)" = zrefs/heads/stops1
+ test "z$(git symbolic-ref HEAD)" = zrefs/heads/stops1 &&
test 0 = $(git rev-list other...stops1 | wc -l)
'
test_expect_success 'pre-rebase hook stops rebase -i' '
git checkout -b stops2 other &&
test_must_fail git rebase --root --onto master &&
- test "z$(git symbolic-ref HEAD)" = zrefs/heads/stops2
+ test "z$(git symbolic-ref HEAD)" = zrefs/heads/stops2 &&
test 0 = $(git rev-list other...stops2 | wc -l)
'
index fd2184ce7159122ae8d1d65f5a228a7b91c5e02a..b38be8e93723991d717b6b7fb690560efb58c36d 100755 (executable)
git add . &&
test_tick &&
git commit -m "first commit" &&
+ git tag first-commit &&
echo 3 >file3 &&
git add . &&
test_tick &&
git tag base
'
-test_auto_fixup() {
+test_auto_fixup () {
git reset --hard base &&
echo 1 >file1 &&
git add -u &&
test_must_fail test_auto_fixup final-fixup-config-false
'
-test_auto_squash() {
+test_auto_squash () {
git reset --hard base &&
echo 1 >file1 &&
git add -u &&
test 0 = $(git rev-list final-missquash...HEAD | wc -l)
'
+test_expect_success 'auto squash that matches 2 commits' '
+ git reset --hard base &&
+ echo 4 >file4 &&
+ git add file4 &&
+ test_tick &&
+ git commit -m "first new commit" &&
+ echo 1 >file1 &&
+ git add -u &&
+ test_tick &&
+ git commit -m "squash! first" &&
+ git tag final-multisquash &&
+ test_tick &&
+ git rebase --autosquash -i HEAD~4 &&
+ git log --oneline >actual &&
+ test 4 = $(wc -l <actual) &&
+ git diff --exit-code final-multisquash &&
+ test 1 = "$(git cat-file blob HEAD^^:file1)" &&
+ test 2 = $(git cat-file commit HEAD^^ | grep first | wc -l) &&
+ test 1 = $(git cat-file commit HEAD | grep first | wc -l)
+'
+
+test_expect_success 'auto squash that matches a commit after the squash' '
+ git reset --hard base &&
+ echo 1 >file1 &&
+ git add -u &&
+ test_tick &&
+ git commit -m "squash! third" &&
+ echo 4 >file4 &&
+ git add file4 &&
+ test_tick &&
+ git commit -m "third commit" &&
+ git tag final-presquash &&
+ test_tick &&
+ git rebase --autosquash -i HEAD~4 &&
+ git log --oneline >actual &&
+ test 5 = $(wc -l <actual) &&
+ git diff --exit-code final-presquash &&
+ test 0 = "$(git cat-file blob HEAD^^:file1)" &&
+ test 1 = "$(git cat-file blob HEAD^:file1)" &&
+ test 1 = $(git cat-file commit HEAD | grep third | wc -l) &&
+ test 1 = $(git cat-file commit HEAD^ | grep third | wc -l)
+'
+test_expect_success 'auto squash that matches a sha1' '
+ git reset --hard base &&
+ echo 1 >file1 &&
+ git add -u &&
+ test_tick &&
+ git commit -m "squash! $(git rev-parse --short HEAD^)" &&
+ git tag final-shasquash &&
+ test_tick &&
+ git rebase --autosquash -i HEAD^^^ &&
+ git log --oneline >actual &&
+ test 3 = $(wc -l <actual) &&
+ git diff --exit-code final-shasquash &&
+ test 1 = "$(git cat-file blob HEAD^:file1)" &&
+ test 1 = $(git cat-file commit HEAD^ | grep squash | wc -l)
+'
+
+test_expect_success 'auto squash that matches longer sha1' '
+ git reset --hard base &&
+ echo 1 >file1 &&
+ git add -u &&
+ test_tick &&
+ git commit -m "squash! $(git rev-parse --short=11 HEAD^)" &&
+ git tag final-longshasquash &&
+ test_tick &&
+ git rebase --autosquash -i HEAD^^^ &&
+ git log --oneline >actual &&
+ test 3 = $(wc -l <actual) &&
+ git diff --exit-code final-longshasquash &&
+ test 1 = "$(git cat-file blob HEAD^:file1)" &&
+ test 1 = $(git cat-file commit HEAD^ | grep squash | wc -l)
+'
+
+test_auto_commit_flags () {
+ git reset --hard base &&
+ echo 1 >file1 &&
+ git add -u &&
+ test_tick &&
+ git commit --$1 first-commit &&
+ git tag final-commit-$1 &&
+ test_tick &&
+ git rebase --autosquash -i HEAD^^^ &&
+ git log --oneline >actual &&
+ test 3 = $(wc -l <actual) &&
+ git diff --exit-code final-commit-$1 &&
+ test 1 = "$(git cat-file blob HEAD^:file1)" &&
+ test $2 = $(git cat-file commit HEAD^ | grep first | wc -l)
+}
+
+test_expect_success 'use commit --fixup' '
+ test_auto_commit_flags fixup 1
+'
+
+test_expect_success 'use commit --squash' '
+ test_auto_commit_flags squash 2
+'
+
test_done
index 220a740ee89af325d04321a45aafc275ed130610..1fb3e499b4a044d935c090bfa567a7ace271c054 100755 (executable)
git config core.whitespace "-blank-at-eol" &&
git reset --hard HEAD^ &&
cp third file && git add file && git commit -m third &&
- git rebase --whitespace=fix HEAD^^
+ git rebase --whitespace=fix HEAD^^ &&
git diff --exit-code HEAD^:file expect-second &&
test_cmp file third
'
index f7b3518a32763aa0fbad1a245dad8f0a5d866126..e6a64816efef0e53018c7a56784d1af62602e9d3 100755 (executable)
test_expect_success 'fixup' '
echo foo-dev >foo &&
git add foo && test_tick && git commit -q -m 4 &&
- git reset --hard HEAD^
+ git reset --hard HEAD^ &&
echo foo-dev >expect
'
'
test_expect_success 'reconfigure' '
- git config rerere.enabled false
+ git config rerere.enabled false &&
git reset --hard
'
index a5ccdbf8fc22efb03891c6b5fa2c9715350a191f..df921d1f33df34dd2b2631580f8d53b18270662a 100755 (executable)
test_description='Test cherry-pick with directory/file conflicts'
. ./test-lib.sh
-test_expect_success SYMLINKS 'Setup rename across paths each below D/F conflicts' '
+test_expect_success 'Initialize repository' '
mkdir a &&
>a/f &&
git add a &&
- git commit -m a &&
+ git commit -m a
+'
+test_expect_success SYMLINKS 'Setup rename across paths each below D/F conflicts' '
mkdir b &&
ln -s ../a b/a &&
git add b &&
git cherry-pick branch
'
+test_expect_success 'Setup rename with file on one side matching directory name on other' '
+ git checkout --orphan nick-testcase &&
+ git rm -rf . &&
+
+ >empty &&
+ git add empty &&
+ git commit -m "Empty file" &&
+
+ git checkout -b simple &&
+ mv empty file &&
+ mkdir empty &&
+ mv file empty &&
+ git add empty/file &&
+ git commit -m "Empty file under empty dir" &&
+
+ echo content >newfile &&
+ git add newfile &&
+ git commit -m "New file"
+'
+
+test_expect_success 'Cherry-pick succeeds with was_a_dir/file -> was_a_dir (resolve)' '
+ git reset --hard &&
+ git checkout -q nick-testcase^0 &&
+ git cherry-pick --strategy=resolve simple
+'
+
+test_expect_success 'Cherry-pick succeeds with was_a_dir/file -> was_a_dir (recursive)' '
+ git reset --hard &&
+ git checkout -q nick-testcase^0 &&
+ git cherry-pick --strategy=recursive simple
+'
+
+test_expect_success 'Setup rename with file on one side matching different dirname on other' '
+ git reset --hard &&
+ git checkout --orphan mergeme &&
+ git rm -rf . &&
+
+ mkdir sub &&
+ mkdir othersub &&
+ echo content > sub/file &&
+ echo foo > othersub/whatever &&
+ git add -A &&
+ git commit -m "Common commmit" &&
+
+ git rm -rf othersub &&
+ git mv sub/file othersub &&
+ git commit -m "Commit to merge" &&
+
+ git checkout -b newhead mergeme~1 &&
+ >independent-change &&
+ git add independent-change &&
+ git commit -m "Completely unrelated change"
+'
+
+test_expect_success 'Cherry-pick with rename to different D/F conflict succeeds (resolve)' '
+ git reset --hard &&
+ git checkout -q newhead^0 &&
+ git cherry-pick --strategy=resolve mergeme
+'
+
+test_expect_success 'Cherry-pick with rename to different D/F conflict succeeds (recursive)' '
+ git reset --hard &&
+ git checkout -q newhead^0 &&
+ git cherry-pick --strategy=recursive mergeme
+'
+
test_done
diff --git a/t/t3900-i18n-commit.sh b/t/t3900-i18n-commit.sh
index 256c4c970145aa9f59e58ee1b0da4c6281b6d9e5..c06a5ee7660c3fd7ca15860cfb761b2c4d953e08 100755 (executable)
--- a/t/t3900-i18n-commit.sh
+++ b/t/t3900-i18n-commit.sh
'
done
+test_commit_autosquash_flags () {
+ H=$1
+ flag=$2
+ test_expect_success "commit --$flag with $H encoding" '
+ git config i18n.commitencoding $H &&
+ git checkout -b $H-$flag C0 &&
+ echo $H >>F &&
+ git commit -a -F "$TEST_DIRECTORY"/t3900/$H.txt &&
+ test_tick &&
+ echo intermediate stuff >>G &&
+ git add G &&
+ git commit -a -m "intermediate commit" &&
+ test_tick &&
+ echo $H $flag >>F &&
+ git commit -a --$flag HEAD~1 $3 &&
+ E=$(git cat-file commit '$H-$flag' |
+ sed -ne "s/^encoding //p") &&
+ test "z$E" = "z$H" &&
+ git config --unset-all i18n.commitencoding &&
+ git rebase --autosquash -i HEAD^^^ &&
+ git log --oneline >actual &&
+ test 3 = $(wc -l <actual)
+ '
+}
+
+test_commit_autosquash_flags eucJP fixup
+
+test_commit_autosquash_flags ISO-2022-JP squash '-m "squash message"'
+
test_done
diff --git a/t/t3902-quoted.sh b/t/t3902-quoted.sh
index 7d4946984121b68ce1fe64dd3f5f363a53c63255..da82b655b3b33520b2c39d3992bc40368bf652be 100755 (executable)
--- a/t/t3902-quoted.sh
+++ b/t/t3902-quoted.sh
test_expect_success TABS_IN_FILENAMES 'setup' '
mkdir "$FN" &&
- for_each_name "echo initial >\"\$name\""
+ for_each_name "echo initial >\"\$name\"" &&
git add . &&
git commit -q -m Initial &&
for_each_name "echo second >\"\$name\"" &&
- git commit -a -m Second
+ git commit -a -m Second &&
for_each_name "echo modified >\"\$name\""
'
test_expect_success TABS_IN_FILENAMES 'setup expected files' '
-cat >expect.quoted <<\EOF
+cat >expect.quoted <<\EOF &&
Name
"Name and a\nLF"
"Name and an\tHT"
diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh
index 903a122efe0c54cb44eb6c3e7ed04bdbab81439d..6fd560ccf10db5d016a4f1dde9e5eacca70e6f5b 100755 (executable)
--- a/t/t3903-stash.sh
+++ b/t/t3903-stash.sh
test_expect_success 'stash branch' '
echo foo > file &&
- git commit file -m first
+ git commit file -m first &&
echo bar > file &&
echo bar2 > file2 &&
git add file2 &&
echo file >.gitignore &&
git stash save "rm and ignore" &&
test bar = "$(cat file)" &&
- test file = "$(cat .gitignore)"
+ test file = "$(cat .gitignore)" &&
git stash apply &&
! test -r file &&
test file = "$(cat .gitignore)"
git add .gitignore &&
git stash save "rm and ignore (stage .gitignore)" &&
test bar = "$(cat file)" &&
- ! test -r .gitignore
+ ! test -r .gitignore &&
git stash apply &&
! test -r file &&
test file = "$(cat .gitignore)"
diff --git a/t/t3904-stash-patch.sh b/t/t3904-stash-patch.sh
index d1819ca23a782eddc61d86c62060242b293b86b0..1e7193ac0bc650868f187d3103be32c3004a95b2 100755 (executable)
--- a/t/t3904-stash-patch.sh
+++ b/t/t3904-stash-patch.sh
# note: bar sorts before dir, so the first 'n' is always to skip 'bar'
test_expect_success PERL 'saying "n" does nothing' '
- set_state dir/foo work index
+ set_state dir/foo work index &&
(echo n; echo n) | test_must_fail git stash save -p &&
verify_state dir/foo work index &&
verify_saved_state bar
diff --git a/t/t4002-diff-basic.sh b/t/t4002-diff-basic.sh
index 73441a516572dcf826582c04f3fe9d3ad0c1a88c..9fb8ca06a84b3f3e60f466cc33bc2de786a9fc90 100755 (executable)
--- a/t/t4002-diff-basic.sh
+++ b/t/t4002-diff-basic.sh
'rm -fr Z [A-Z][A-Z] &&
git read-tree $tree_A &&
git checkout-index -f -a &&
- git read-tree --reset $tree_O || return 1
- git update-index --refresh >/dev/null ;# this can exit non-zero
+ git read-tree --reset $tree_O &&
+ test_must_fail git update-index --refresh -q &&
git diff-files >.test-a &&
cmp_diff_files_output .test-a .test-recursive-OA'
'rm -fr Z [A-Z][A-Z] &&
git read-tree $tree_B &&
git checkout-index -f -a &&
- git read-tree --reset $tree_O || return 1
- git update-index --refresh >/dev/null ;# this can exit non-zero
+ git read-tree --reset $tree_O &&
+ test_must_fail git update-index --refresh -q &&
git diff-files >.test-a &&
cmp_diff_files_output .test-a .test-recursive-OB'
'rm -fr Z [A-Z][A-Z] &&
git read-tree $tree_B &&
git checkout-index -f -a &&
- git read-tree --reset $tree_A || return 1
- git update-index --refresh >/dev/null ;# this can exit non-zero
+ git read-tree --reset $tree_A &&
+ test_must_fail git update-index --refresh -q &&
git diff-files >.test-a &&
cmp_diff_files_output .test-a .test-recursive-AB'
index c6130c40198ad5ed5ec8a0342341a4ec5cc49d7d..bfa88356382ab9f37e6e510516bf1fc6a3811961 100755 (executable)
--- a/t/t4003-diff-rename-1.sh
+++ b/t/t4003-diff-rename-1.sh
# copy-and-edit one, and rename-and-edit the other. We do not say
# anything about rezrov.
-GIT_DIFF_OPTS=--unified=0 git diff-index -M -p $tree >current
+GIT_DIFF_OPTS=--unified=0 git diff-index -C -p $tree >current
cat >expected <<\EOF
diff --git a/COPYING b/COPYING.1
copy from COPYING
index 92a65f4852b8eca089fb9de617e0cdc8b55e1475..6e562c80d12f9f58353c8f6444c0d7a0737dbae4 100755 (executable)
# a new creation.
test_expect_success SYMLINKS 'setup diff output' "
- GIT_DIFF_OPTS=--unified=0 git diff-index -M -p $tree >current &&
+ GIT_DIFF_OPTS=--unified=0 git diff-index -C -p $tree >current &&
cat >expected <<\EOF
diff --git a/bozbar b/bozbar
new file mode 120000
index 1ba359d478e3d3491aed5f622932382767c8f4dc..77d7f4946fb51a766399549e3c7364033d7668f0 100755 (executable)
--- a/t/t4005-diff-rename-2.sh
+++ b/t/t4005-diff-rename-2.sh
# and COPYING.2 are based on COPYING, and do not say anything about
# rezrov.
-git diff-index -M $tree >current
+git diff-index -C $tree >current
cat >expected <<\EOF
:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 0603b3238a076dc6c8022aedc6648fa523a17178 C1234 COPYING COPYING.1
index e19ca65885a1e49916f68eee4abbe65e98227c50..73b4a24f5ef676b8fa9fb3dc83183437710853c3 100755 (executable)
git checkout-index -f -u -a &&
sed -e "s/git/GIT/" file0 >file1 &&
sed -e "s/git/GET/" file0 >file2 &&
- rm -f file0
+ rm -f file0 &&
git update-index --add --remove file0 file1 file2'
test_expect_success \
'compare_diff_raw expected current'
test_expect_success \
- 'run diff with -B -M' \
- 'git diff-index -B -M "$tree" >current'
+ 'run diff with -B -C' \
+ 'git diff-index -B -C "$tree" >current'
cat >expected <<\EOF
:100644 100644 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 08bb2fb671deff4c03a4d4a0a1315dff98d5732c C095 file0 file1
index de3f17478efcaf008340a7ab81cb049f9a9e9a3a..f22c8e3dbaee8882160b6132160fcc3ccd7db057 100755 (executable)
--- a/t/t4009-diff-rename-4.sh
+++ b/t/t4009-diff-rename-4.sh
# and COPYING.2 are based on COPYING, and do not say anything about
# rezrov.
-git diff-index -z -M $tree >current
+git diff-index -z -C $tree >current
cat >expected <<\EOF
:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 0603b3238a076dc6c8022aedc6648fa523a17178 C1234
index 94df7ae53a0ef47c0ef10ca6b3215ffdf38fa399..fbc8cd8f05f4debeb30b935c1b1db86e94e49f0e 100755 (executable)
--- a/t/t4010-diff-pathspec.sh
+++ b/t/t4010-diff-pathspec.sh
test_cmp expected current
'
+EMPTY_TREE=4b825dc642cb6eb9a060e54bf8d69288fbee4904
+
+test_expect_success 'diff-tree with wildcard shows dir also matches' '
+ git diff-tree --name-only $EMPTY_TREE $tree -- "f*" >result &&
+ echo file0 >expected &&
+ test_cmp expected result
+'
+
+test_expect_success 'diff-tree -r with wildcard' '
+ git diff-tree -r --name-only $EMPTY_TREE $tree -- "*file1" >result &&
+ echo path1/file1 >expected &&
+ test_cmp expected result
+'
+
+test_expect_success 'diff-tree with wildcard shows dir also matches' '
+ git diff-tree --name-only $tree $tree2 -- "path1/f*" >result &&
+ echo path1 >expected &&
+ test_cmp expected result
+'
+
+test_expect_success 'diff-tree -r with wildcard from beginning' '
+ git diff-tree -r --name-only $tree $tree2 -- "path1/*file1" >result &&
+ echo path1/file1 >expected &&
+ test_cmp expected result
+'
+
+test_expect_success 'diff-tree -r with wildcard' '
+ git diff-tree -r --name-only $tree $tree2 -- "path1/f*" >result &&
+ echo path1/file1 >expected &&
+ test_cmp expected result
+'
+
test_done
index 19857f4326aa491a4c7d292900a0a766e7c45f80..b8f81d07c33d3596fef5f5a361f4774d5e357fca 100755 (executable)
--- a/t/t4013-diff-various.sh
+++ b/t/t4013-diff-various.sh
log -SF master
log -S F master
log -SF -p master
+log -GF master
+log -GF -p master
+log -GF -p --pickaxe-all master
log --decorate --all
log --decorate=full --all
test_must_fail git log -S
'
+test_expect_success 'diff --cached on unborn branch' '
+ echo ref: refs/heads/unborn >.git/HEAD &&
+ git diff --cached >result &&
+ test_cmp "$TEST_DIRECTORY/t4013/diff.diff_--cached" result
+'
+
+test_expect_success 'diff --cached -- file on unborn branch' '
+ git diff --cached -- file0 >result &&
+ test_cmp "$TEST_DIRECTORY/t4013/diff.diff_--cached_--_file0" result
+'
+
test_done
diff --git a/t/t4013/diff.diff_--cached b/t/t4013/diff.diff_--cached
--- /dev/null
@@ -0,0 +1,38 @@
+diff --git a/dir/sub b/dir/sub
+new file mode 100644
+index 0000000..992913c
+--- /dev/null
++++ b/dir/sub
+@@ -0,0 +1,8 @@
++A
++B
++C
++D
++E
++F
++1
++2
+diff --git a/file0 b/file0
+new file mode 100644
+index 0000000..10a8a9f
+--- /dev/null
++++ b/file0
+@@ -0,0 +1,9 @@
++1
++2
++3
++4
++5
++6
++A
++B
++C
+diff --git a/file1 b/file1
+new file mode 100644
+index 0000000..b1e6722
+--- /dev/null
++++ b/file1
+@@ -0,0 +1,3 @@
++A
++B
++C
diff --git a/t/t4013/diff.diff_--cached_--_file0 b/t/t4013/diff.diff_--cached_--_file0
--- /dev/null
@@ -0,0 +1,15 @@
+diff --git a/file0 b/file0
+new file mode 100644
+index 0000000..10a8a9f
+--- /dev/null
++++ b/file0
+@@ -0,0 +1,9 @@
++1
++2
++3
++4
++5
++6
++A
++B
++C
diff --git a/t/t4013/diff.log_-GF_-p_--pickaxe-all_master b/t/t4013/diff.log_-GF_-p_--pickaxe-all_master
--- /dev/null
@@ -0,0 +1,27 @@
+$ git log -GF -p --pickaxe-all master
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:02:00 2006 +0000
+
+ Third
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+diff --git a/file1 b/file1
+new file mode 100644
+index 0000000..b1e6722
+--- /dev/null
++++ b/file1
+@@ -0,0 +1,3 @@
++A
++B
++C
+$
diff --git a/t/t4013/diff.log_-GF_-p_master b/t/t4013/diff.log_-GF_-p_master
--- /dev/null
@@ -0,0 +1,18 @@
+$ git log -GF -p master
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:02:00 2006 +0000
+
+ Third
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+$
diff --git a/t/t4013/diff.log_-GF_master b/t/t4013/diff.log_-GF_master
--- /dev/null
@@ -0,0 +1,7 @@
+$ git log -GF master
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:02:00 2006 +0000
+
+ Third
+$
index f87434b9f8e0d520813389e6ea7c94f74222767b..027c13d52cd701ba28e3c5e29c5431acfccdad73 100755 (executable)
--- a/t/t4014-format-patch.sh
+++ b/t/t4014-format-patch.sh
test_description='various format-patch tests'
. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-terminal.sh
test_expect_success setup '
for i in 1 2 3 4 5 6 7 8 9 10; do echo "$i"; done >file &&
cat file >elif &&
git add file elif &&
+ test_tick &&
git commit -m Initial &&
git checkout -b side &&
for i in 1 2 5 6 A B C 7 8 9 10; do echo "$i"; done >file &&
test_chmod +x elif &&
+ test_tick &&
git commit -m "Side changes #1" &&
for i in D E F; do echo "$i"; done >>file &&
git update-index file &&
+ test_tick &&
git commit -m "Side changes #2" &&
git tag C2 &&
for i in 5 6 1 2 3 A 4 B C 7 8 9 10 D E F; do echo "$i"; done >file &&
git update-index file &&
+ test_tick &&
git commit -m "Side changes #3 with \\n backslash-n in it." &&
git checkout master &&
git diff-tree -p C2 | git apply --index &&
+ test_tick &&
git commit -m "Master accepts moral equivalent of #2"
'
'
+test_expect_success "format-patch doesn't consider merge commits" '
+
+ git checkout -b slave master &&
+ echo "Another line" >>file &&
+ test_tick &&
+ git commit -am "Slave change #1" &&
+ echo "Yet another line" >>file &&
+ test_tick &&
+ git commit -am "Slave change #2" &&
+ git checkout -b merger master &&
+ test_tick &&
+ git merge --no-ff slave &&
+ cnt=`git format-patch -3 --stdout | grep "^From " | wc -l` &&
+ test $cnt = 3
+'
+
test_expect_success "format-patch result applies" '
git checkout -b rebuild-0 master &&
! grep "^-- \$" output
'
+test_expect_success TTY 'format-patch --stdout paginates' '
+ rm -f pager_used &&
+ (
+ GIT_PAGER="wc >pager_used" &&
+ export GIT_PAGER &&
+ test_terminal git format-patch --stdout --all
+ ) &&
+ test_path_is_file pager_used
+'
+
+ test_expect_success TTY 'format-patch --stdout pagination can be disabled' '
+ rm -f pager_used &&
+ (
+ GIT_PAGER="wc >pager_used" &&
+ export GIT_PAGER &&
+ test_terminal git --no-pager format-patch --stdout --all &&
+ test_terminal git -c "pager.format-patch=false" format-patch --stdout --all
+ ) &&
+ test_path_is_missing pager_used &&
+ test_path_is_missing .git/pager_used
+'
+
test_done
index a8736f7cbeef65b3454847bb4b4df508c0079652..9059bcd69eec7a718b6017356c82ec163875c042 100755 (executable)
test_expect_success 'check spaces as indentation (indent-with-non-tab: off)' '
- git config core.whitespace "-indent-with-non-tab"
+ git config core.whitespace "-indent-with-non-tab" &&
echo " foo ();" > x &&
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" &&
@@ -352,6 +359,20 @@ test_expect_success 'check tabs and spaces as indentation (indent-with-non-tab:
'
+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" &&
@@ -376,6 +397,13 @@ test_expect_success 'check tabs and spaces as indentation (tab-in-indent: on)' '
'
+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" &&
diff --git a/t/t4017-diff-retval.sh b/t/t4017-diff-retval.sh
index 61589853df55e063fbe6489fc9c6effc4a9f33b6..95a7ca707045cad3362b92c18df73e82206a2844 100755 (executable)
--- a/t/t4017-diff-retval.sh
+++ b/t/t4017-diff-retval.sh
'
test_expect_success 'git diff-tree HEAD^ HEAD' '
- git diff-tree --exit-code HEAD^ HEAD
- test $? = 1
+ test_expect_code 1 git diff-tree --exit-code HEAD^ HEAD
'
test_expect_success 'git diff-tree HEAD^ HEAD -- a' '
git diff-tree --exit-code HEAD^ HEAD -- a
- test $? = 0
'
test_expect_success 'git diff-tree HEAD^ HEAD -- b' '
- git diff-tree --exit-code HEAD^ HEAD -- b
- test $? = 1
+ test_expect_code 1 git diff-tree --exit-code HEAD^ HEAD -- b
'
test_expect_success 'echo HEAD | git diff-tree --stdin' '
- echo $(git rev-parse HEAD) | git diff-tree --exit-code --stdin
- test $? = 1
+ echo $(git rev-parse HEAD) | test_expect_code 1 git diff-tree --exit-code --stdin
'
test_expect_success 'git diff-tree HEAD HEAD' '
git diff-tree --exit-code HEAD HEAD
- test $? = 0
'
test_expect_success 'git diff-files' '
git diff-files --exit-code
- test $? = 0
'
test_expect_success 'git diff-index --cached HEAD' '
git diff-index --exit-code --cached HEAD
- test $? = 0
'
test_expect_success 'git diff-index --cached HEAD^' '
- git diff-index --exit-code --cached HEAD^
- test $? = 1
+ test_expect_code 1 git diff-index --exit-code --cached HEAD^
'
test_expect_success 'git diff-index --cached HEAD^' '
echo text >>b &&
echo 3 >c &&
- git add . && {
- git diff-index --exit-code --cached HEAD^
- test $? = 1
- }
+ git add . &&
+ test_expect_code 1 git diff-index --exit-code --cached HEAD^
'
test_expect_success 'git diff-tree -Stext HEAD^ HEAD -- b' '
- git commit -m "text in b" && {
- git diff-tree -p --exit-code -Stext HEAD^ HEAD -- b
- test $? = 1
- }
+ git commit -m "text in b" &&
+ test_expect_code 1 git diff-tree -p --exit-code -Stext HEAD^ HEAD -- b
'
test_expect_success 'git diff-tree -Snot-found HEAD^ HEAD -- b' '
git diff-tree -p --exit-code -Snot-found HEAD^ HEAD -- b
- test $? = 0
'
test_expect_success 'git diff-files' '
- echo 3 >>c && {
- git diff-files --exit-code
- test $? = 1
- }
+ echo 3 >>c &&
+ test_expect_code 1 git diff-files --exit-code
'
test_expect_success 'git diff-index --cached HEAD' '
- git update-index c && {
- git diff-index --exit-code --cached HEAD
- test $? = 1
- }
+ git update-index c &&
+ test_expect_code 1 git diff-index --exit-code --cached HEAD
'
test_expect_success '--check --exit-code returns 0 for no difference' '
test_expect_success '--check --exit-code returns 1 for a clean difference' '
echo "good" > a &&
- git diff --check --exit-code
- test $? = 1
+ test_expect_code 1 git diff --check --exit-code
'
test_expect_success '--check --exit-code returns 3 for a dirty difference' '
echo "bad " >> a &&
- git diff --check --exit-code
- test $? = 3
+ test_expect_code 3 git diff --check --exit-code
'
test_expect_success '--check with --no-pager returns 2 for dirty difference' '
- git --no-pager diff --check
- test $? = 2
+ test_expect_code 2 git --no-pager diff --check
'
test_expect_success 'check should test not just the last line' '
echo "" >>a &&
- git --no-pager diff --check
- test $? = 2
+ test_expect_code 2 git --no-pager diff --check
'
echo binary >>b &&
git commit -m "side" b &&
test_must_fail git merge master &&
- git add b && (
- git --no-pager diff --cached --check >test.out
- test $? = 2
- ) &&
+ git add b &&
+ test_expect_code 2 git --no-pager diff --cached --check >test.out &&
test 3 = $(grep "conflict marker" test.out | wc -l) &&
git reset --hard
'
echo ">>>>>>> boo" >>b &&
echo "======" >>a &&
git diff --check a &&
- (
- git diff --check b
- test $? = 2
- ) &&
+ test_expect_code 2 git diff --check b &&
git reset --hard &&
echo ">>>>>>>> boo" >>b &&
echo "========" >>a &&
git diff --check &&
echo "b conflict-marker-size=8" >.gitattributes &&
- (
- git diff --check b
- test $? = 2
- ) &&
+ test_expect_code 2 git diff --check b &&
git diff --check a &&
git reset --hard
'
index 0a61b57b5f6f00ae4c0734a34ce45c0ba1fcf098..364693062399228cd6a18b52a21db173519d8e94 100755 (executable)
--- a/t/t4018-diff-funcname.sh
+++ b/t/t4018-diff-funcname.sh
sed 's/beer\\/beer,\\/' < Beer.java > Beer-correct.java
-builtin_patterns="bibtex cpp csharp fortran html java objc pascal php python ruby tex"
+builtin_patterns="bibtex cpp csharp fortran html java objc pascal perl php python ruby tex"
for p in $builtin_patterns
do
test_expect_success "builtin $p pattern compiles" '
index 87df0aeb59ecb22f07454ce08172f8cfa4940591..a5019759bc7593cf8affd8625f61bbd7ab1b9655 100755 (executable)
--- a/t/t4019-diff-wserror.sh
+++ b/t/t4019-diff-wserror.sh
git diff --color >output
$grep_a "$blue_grep" output >error
$grep_a -v "$blue_grep" output >normal
+ return 0
}
test_expect_success default '
- prepare_output
+ prepare_output &&
grep Eight normal >/dev/null &&
grep HT error >/dev/null &&
'
+test_expect_success 'default (attribute)' '
+
+ test_might_fail git config --unset core.whitespace &&
+ echo "F whitespace" >.gitattributes &&
+ prepare_output &&
+
+ grep Eight error >/dev/null &&
+ grep HT error >/dev/null &&
+ grep With error >/dev/null &&
+ grep Return error >/dev/null &&
+ grep No normal >/dev/null
+
+'
+
+test_expect_success 'default, tabwidth=10 (attribute)' '
+
+ git config core.whitespace "tabwidth=10" &&
+ echo "F whitespace" >.gitattributes &&
+ prepare_output &&
+
+ grep Eight normal >/dev/null &&
+ grep HT error >/dev/null &&
+ grep With error >/dev/null &&
+ grep Return error >/dev/null &&
+ grep No normal >/dev/null
+
+'
+
+test_expect_success 'no check (attribute)' '
+
+ test_might_fail git config --unset core.whitespace &&
+ echo "F -whitespace" >.gitattributes &&
+ prepare_output &&
+
+ grep Eight normal >/dev/null &&
+ grep HT normal >/dev/null &&
+ grep With normal >/dev/null &&
+ grep Return normal >/dev/null &&
+ grep No normal >/dev/null
+
+'
+
+test_expect_success 'no check, tabwidth=10 (attribute), must be irrelevant' '
+
+ git config core.whitespace "tabwidth=10" &&
+ echo "F -whitespace" >.gitattributes &&
+ prepare_output &&
+
+ grep Eight normal >/dev/null &&
+ grep HT normal >/dev/null &&
+ grep With normal >/dev/null &&
+ grep Return normal >/dev/null &&
+ grep No normal >/dev/null
+
+'
+
test_expect_success 'without -trail' '
- git config core.whitespace -trail
- prepare_output
+ rm -f .gitattributes &&
+ git config core.whitespace -trail &&
+ prepare_output &&
grep Eight normal >/dev/null &&
grep HT error >/dev/null &&
test_expect_success 'without -trail (attribute)' '
- git config --unset core.whitespace
- echo "F whitespace=-trail" >.gitattributes
- prepare_output
+ test_might_fail git config --unset core.whitespace &&
+ echo "F whitespace=-trail" >.gitattributes &&
+ prepare_output &&
grep Eight normal >/dev/null &&
grep HT error >/dev/null &&
test_expect_success 'without -space' '
- rm -f .gitattributes
- git config core.whitespace -space
- prepare_output
+ rm -f .gitattributes &&
+ git config core.whitespace -space &&
+ prepare_output &&
grep Eight normal >/dev/null &&
grep HT normal >/dev/null &&
test_expect_success 'without -space (attribute)' '
- git config --unset core.whitespace
- echo "F whitespace=-space" >.gitattributes
- prepare_output
+ test_might_fail git config --unset core.whitespace &&
+ echo "F whitespace=-space" >.gitattributes &&
+ prepare_output &&
grep Eight normal >/dev/null &&
grep HT normal >/dev/null &&
test_expect_success 'with indent-non-tab only' '
- rm -f .gitattributes
- git config core.whitespace indent,-trailing,-space
- prepare_output
+ rm -f .gitattributes &&
+ git config core.whitespace indent,-trailing,-space &&
+ prepare_output &&
grep Eight error >/dev/null &&
grep HT normal >/dev/null &&
test_expect_success 'with indent-non-tab only (attribute)' '
- git config --unset core.whitespace
- echo "F whitespace=indent,-trailing,-space" >.gitattributes
- prepare_output
+ test_might_fail git config --unset core.whitespace &&
+ echo "F whitespace=indent,-trailing,-space" >.gitattributes &&
+ prepare_output &&
grep Eight error >/dev/null &&
grep HT normal >/dev/null &&
'
+test_expect_success 'with indent-non-tab only, tabwidth=10' '
+
+ rm -f .gitattributes &&
+ git config core.whitespace indent,tabwidth=10,-trailing,-space &&
+ prepare_output &&
+
+ grep Eight normal >/dev/null &&
+ grep HT normal >/dev/null &&
+ grep With normal >/dev/null &&
+ grep Return normal >/dev/null &&
+ grep No normal >/dev/null
+
+'
+
+test_expect_success 'with indent-non-tab only, tabwidth=10 (attribute)' '
+
+ test_might_fail git config --unset core.whitespace &&
+ echo "F whitespace=indent,-trailing,-space,tabwidth=10" >.gitattributes &&
+ prepare_output &&
+
+ grep Eight normal >/dev/null &&
+ grep HT normal >/dev/null &&
+ grep With normal >/dev/null &&
+ grep Return normal >/dev/null &&
+ grep No normal >/dev/null
+
+'
+
test_expect_success 'with cr-at-eol' '
- rm -f .gitattributes
- git config core.whitespace cr-at-eol
- prepare_output
+ rm -f .gitattributes &&
+ git config core.whitespace cr-at-eol &&
+ prepare_output &&
grep Eight normal >/dev/null &&
grep HT error >/dev/null &&
test_expect_success 'with cr-at-eol (attribute)' '
- git config --unset core.whitespace
- echo "F whitespace=trailing,cr-at-eol" >.gitattributes
- prepare_output
+ test_might_fail git config --unset core.whitespace &&
+ echo "F whitespace=trailing,cr-at-eol" >.gitattributes &&
+ prepare_output &&
grep Eight normal >/dev/null &&
grep HT error >/dev/null &&
@@ -188,11 +274,11 @@ test_expect_success 'checkdiff shows correct line number for trailing blank line
'
test_expect_success 'do not color trailing cr in context' '
- git config --unset core.whitespace
+ test_might_fail git config --unset core.whitespace &&
rm -f .gitattributes &&
echo AAAQ | tr Q "\015" >G &&
git add G &&
- echo BBBQ | tr Q "\015" >>G
+ echo BBBQ | tr Q "\015" >>G &&
git diff --color G | tr "\015" Q >output &&
grep "BBB.*${blue_grep}Q" output &&
grep "AAA.*\[mQ" output
index 709b3231ca8d9da631727b4aadfb2f46049d37e9..886494b58f6739d40c2c34724064317b0d3be770 100755 (executable)
test_expect_success 'format.numbered = auto' '
- git config format.numbered auto
+ git config format.numbered auto &&
git format-patch --stdout HEAD~2 > patch5 &&
test_numbered patch5
diff --git a/t/t4026-color.sh b/t/t4026-color.sh
index d5ccdd0cf8061e797e88185bfddb0864f73291dd..3726a0e2012e534623ddbdd71da04f177c32d4d0 100755 (executable)
--- a/t/t4026-color.sh
+++ b/t/t4026-color.sh
'
test_expect_success 'unknown color slots are ignored (diff)' '
- git config --unset diff.color.new
git config color.diff.nosuchslotwilleverbedefined white &&
git diff --color
'
index d99814ac641c93383d7199f4419083bedcfc9ea8..241a74d2a20276d711944d3e203083b515486e77 100755 (executable)
test_expect_success 'conflicted submodule setup' '
# 39 efs
- c=fffffffffffffffffffffffffffffffffffffff
+ c=fffffffffffffffffffffffffffffffffffffff &&
(
- echo "000000 $_z40 0 sub"
- echo "160000 1$c 1 sub"
- echo "160000 2$c 2 sub"
+ echo "000000 $_z40 0 sub" &&
+ echo "160000 1$c 1 sub" &&
+ echo "160000 2$c 2 sub" &&
echo "160000 3$c 3 sub"
) | git update-index --index-info &&
echo >expect.nosub '\''diff --cc sub
index 7e7b307a24606131b4880817a0056af11973f3d2..7d7470f21b66a937e7414f4fe5419f8830fd8e86 100755 (executable)
grep "GIT binary patch" diff
'
+test_expect_success 'rewrite diff --stat shows binary changes' '
+ git diff -B --stat --summary >diff &&
+ grep "Bin" diff &&
+ grep "0 insertions.*0 deletions" diff &&
+ grep " rewrite file" diff
+'
+
{
echo "#!$SHELL_PATH"
cat <<'EOF'
diff --git a/t/t4034-diff-words.sh b/t/t4034-diff-words.sh
index 3f3c7577ca85c92e5ff986044e0c4afa95bf608d..37aeab0d5c7415a54384c90708626f6a5807acee 100755 (executable)
--- a/t/t4034-diff-words.sh
+++ b/t/t4034-diff-words.sh
. ./test-lib.sh
-test_expect_success setup '
-
- git config diff.color.old red
- git config diff.color.new green
- git config diff.color.func magenta
+cat >pre.simple <<-\EOF
+ h(4)
-'
+ a = b + c
+EOF
+cat >post.simple <<-\EOF
+ h(4),hh[44]
-word_diff () {
- test_must_fail git diff --no-index "$@" pre post > output &&
- test_decode_color <output >output.decrypted &&
- test_cmp expect output.decrypted
-}
+ a = b + c
-cat > pre <<\EOF
-h(4)
+ aa = a
-a = b + c
+ aeff = aeff * ( aaa )
EOF
+cat >expect.letter-runs-are-words <<-\EOF
+ <BOLD>diff --git a/pre b/post<RESET>
+ <BOLD>index 330b04f..5ed8eff 100644<RESET>
+ <BOLD>--- a/pre<RESET>
+ <BOLD>+++ b/post<RESET>
+ <CYAN>@@ -1,3 +1,7 @@<RESET>
+ h(4),<GREEN>hh<RESET>[44]
-cat > post <<\EOF
-h(4),hh[44]
+ a = b + c<RESET>
-a = b + c
+ <GREEN>aa = a<RESET>
-aa = a
-
-aeff = aeff * ( aaa )
+ <GREEN>aeff = aeff * ( aaa<RESET> )
EOF
+cat >expect.non-whitespace-is-word <<-\EOF
+ <BOLD>diff --git a/pre b/post<RESET>
+ <BOLD>index 330b04f..5ed8eff 100644<RESET>
+ <BOLD>--- a/pre<RESET>
+ <BOLD>+++ b/post<RESET>
+ <CYAN>@@ -1,3 +1,7 @@<RESET>
+ h(4)<GREEN>,hh[44]<RESET>
-cat > expect <<\EOF
-<BOLD>diff --git a/pre b/post<RESET>
-<BOLD>index 330b04f..5ed8eff 100644<RESET>
-<BOLD>--- a/pre<RESET>
-<BOLD>+++ b/post<RESET>
-<CYAN>@@ -1,3 +1,7 @@<RESET>
-<RED>h(4)<RESET><GREEN>h(4),hh[44]<RESET>
-
-a = b + c<RESET>
+ a = b + c<RESET>
-<GREEN>aa = a<RESET>
+ <GREEN>aa = a<RESET>
-<GREEN>aeff = aeff * ( aaa )<RESET>
+ <GREEN>aeff = aeff * ( aaa )<RESET>
EOF
-test_expect_success 'word diff with runs of whitespace' '
+word_diff () {
+ test_must_fail git diff --no-index "$@" pre post >output &&
+ test_decode_color <output >output.decrypted &&
+ test_cmp expect output.decrypted
+}
- word_diff --color-words
+test_language_driver () {
+ lang=$1
+ test_expect_success "diff driver '$lang'" '
+ cp "$TEST_DIRECTORY/t4034/'"$lang"'/pre" \
+ "$TEST_DIRECTORY/t4034/'"$lang"'/post" \
+ "$TEST_DIRECTORY/t4034/'"$lang"'/expect" . &&
+ echo "* diff='"$lang"'" >.gitattributes &&
+ word_diff --color-words
+ '
+}
+test_expect_success setup '
+ git config diff.color.old red &&
+ git config diff.color.new green &&
+ git config diff.color.func magenta
'
-test_expect_success '--word-diff=color' '
-
- word_diff --word-diff=color
-
+test_expect_success 'set up pre and post with runs of whitespace' '
+ cp pre.simple pre &&
+ cp post.simple post
'
-test_expect_success '--color --word-diff=color' '
-
+test_expect_success 'word diff with runs of whitespace' '
+ cat >expect <<-\EOF &&
+ <BOLD>diff --git a/pre b/post<RESET>
+ <BOLD>index 330b04f..5ed8eff 100644<RESET>
+ <BOLD>--- a/pre<RESET>
+ <BOLD>+++ b/post<RESET>
+ <CYAN>@@ -1,3 +1,7 @@<RESET>
+ <RED>h(4)<RESET><GREEN>h(4),hh[44]<RESET>
+
+ a = b + c<RESET>
+
+ <GREEN>aa = a<RESET>
+
+ <GREEN>aeff = aeff * ( aaa )<RESET>
+ EOF
+ word_diff --color-words &&
+ word_diff --word-diff=color &&
word_diff --color --word-diff=color
-
'
-sed 's/#.*$//' > expect <<EOF
-diff --git a/pre b/post
-index 330b04f..5ed8eff 100644
---- a/pre
-+++ b/post
-@@ -1,3 +1,7 @@
--h(4)
-+h(4),hh[44]
-~
- # significant space
-~
- a = b + c
-~
-~
-+aa = a
-~
-~
-+aeff = aeff * ( aaa )
-~
-EOF
-
test_expect_success '--word-diff=porcelain' '
-
+ sed 's/#.*$//' >expect <<-\EOF &&
+ diff --git a/pre b/post
+ index 330b04f..5ed8eff 100644
+ --- a/pre
+ +++ b/post
+ @@ -1,3 +1,7 @@
+ -h(4)
+ +h(4),hh[44]
+ ~
+ # significant space
+ ~
+ a = b + c
+ ~
+ ~
+ +aa = a
+ ~
+ ~
+ +aeff = aeff * ( aaa )
+ ~
+ EOF
word_diff --word-diff=porcelain
-
'
-cat > expect <<EOF
-diff --git a/pre b/post
-index 330b04f..5ed8eff 100644
---- a/pre
-+++ b/post
-@@ -1,3 +1,7 @@
-[-h(4)-]{+h(4),hh[44]+}
-
-a = b + c
-
-{+aa = a+}
-
-{+aeff = aeff * ( aaa )+}
-EOF
-
test_expect_success '--word-diff=plain' '
+ cat >expect <<-\EOF &&
+ diff --git a/pre b/post
+ index 330b04f..5ed8eff 100644
+ --- a/pre
+ +++ b/post
+ @@ -1,3 +1,7 @@
+ [-h(4)-]{+h(4),hh[44]+}
- word_diff --word-diff=plain
+ a = b + c
-'
-
-test_expect_success '--word-diff=plain --no-color' '
+ {+aa = a+}
+ {+aeff = aeff * ( aaa )+}
+ EOF
+ word_diff --word-diff=plain &&
word_diff --word-diff=plain --no-color
-
'
-cat > expect <<EOF
-<BOLD>diff --git a/pre b/post<RESET>
-<BOLD>index 330b04f..5ed8eff 100644<RESET>
-<BOLD>--- a/pre<RESET>
-<BOLD>+++ b/post<RESET>
-<CYAN>@@ -1,3 +1,7 @@<RESET>
-<RED>[-h(4)-]<RESET><GREEN>{+h(4),hh[44]+}<RESET>
-
-a = b + c<RESET>
+test_expect_success '--word-diff=plain --color' '
+ cat >expect <<-\EOF &&
+ <BOLD>diff --git a/pre b/post<RESET>
+ <BOLD>index 330b04f..5ed8eff 100644<RESET>
+ <BOLD>--- a/pre<RESET>
+ <BOLD>+++ b/post<RESET>
+ <CYAN>@@ -1,3 +1,7 @@<RESET>
+ <RED>[-h(4)-]<RESET><GREEN>{+h(4),hh[44]+}<RESET>
-<GREEN>{+aa = a+}<RESET>
+ a = b + c<RESET>
-<GREEN>{+aeff = aeff * ( aaa )+}<RESET>
-EOF
-
-test_expect_success '--word-diff=plain --color' '
+ <GREEN>{+aa = a+}<RESET>
+ <GREEN>{+aeff = aeff * ( aaa )+}<RESET>
+ EOF
word_diff --word-diff=plain --color
-
'
-cat > expect <<\EOF
-<BOLD>diff --git a/pre b/post<RESET>
-<BOLD>index 330b04f..5ed8eff 100644<RESET>
-<BOLD>--- a/pre<RESET>
-<BOLD>+++ b/post<RESET>
-<CYAN>@@ -1 +1 @@<RESET>
-<RED>h(4)<RESET><GREEN>h(4),hh[44]<RESET>
-<CYAN>@@ -3,0 +4,4 @@<RESET> <RESET><MAGENTA>a = b + c<RESET>
-
-<GREEN>aa = a<RESET>
-
-<GREEN>aeff = aeff * ( aaa )<RESET>
-EOF
-
test_expect_success 'word diff without context' '
-
+ cat >expect <<-\EOF &&
+ <BOLD>diff --git a/pre b/post<RESET>
+ <BOLD>index 330b04f..5ed8eff 100644<RESET>
+ <BOLD>--- a/pre<RESET>
+ <BOLD>+++ b/post<RESET>
+ <CYAN>@@ -1 +1 @@<RESET>
+ <RED>h(4)<RESET><GREEN>h(4),hh[44]<RESET>
+ <CYAN>@@ -3,0 +4,4 @@<RESET> <RESET><MAGENTA>a = b + c<RESET>
+
+ <GREEN>aa = a<RESET>
+
+ <GREEN>aeff = aeff * ( aaa )<RESET>
+ EOF
word_diff --color-words --unified=0
-
'
-cat > expect <<\EOF
-<BOLD>diff --git a/pre b/post<RESET>
-<BOLD>index 330b04f..5ed8eff 100644<RESET>
-<BOLD>--- a/pre<RESET>
-<BOLD>+++ b/post<RESET>
-<CYAN>@@ -1,3 +1,7 @@<RESET>
-h(4),<GREEN>hh<RESET>[44]
-
-a = b + c<RESET>
-
-<GREEN>aa = a<RESET>
-
-<GREEN>aeff = aeff * ( aaa<RESET> )
-EOF
-cp expect expect.letter-runs-are-words
-
test_expect_success 'word diff with a regular expression' '
-
+ cp expect.letter-runs-are-words expect &&
word_diff --color-words="[a-z]+"
-
'
-test_expect_success 'set a diff driver' '
+test_expect_success 'set up a diff driver' '
git config diff.testdriver.wordRegex "[^[:space:]]" &&
- cat <<EOF > .gitattributes
-pre diff=testdriver
-post diff=testdriver
-EOF
+ cat <<-\EOF >.gitattributes
+ pre diff=testdriver
+ post diff=testdriver
+ EOF
'
test_expect_success 'option overrides .gitattributes' '
-
+ cp expect.letter-runs-are-words expect &&
word_diff --color-words="[a-z]+"
-
'
-cat > expect <<\EOF
-<BOLD>diff --git a/pre b/post<RESET>
-<BOLD>index 330b04f..5ed8eff 100644<RESET>
-<BOLD>--- a/pre<RESET>
-<BOLD>+++ b/post<RESET>
-<CYAN>@@ -1,3 +1,7 @@<RESET>
-h(4)<GREEN>,hh[44]<RESET>
-
-a = b + c<RESET>
-
-<GREEN>aa = a<RESET>
-
-<GREEN>aeff = aeff * ( aaa )<RESET>
-EOF
-cp expect expect.non-whitespace-is-word
-
test_expect_success 'use regex supplied by driver' '
-
+ cp expect.non-whitespace-is-word expect &&
word_diff --color-words
-
'
-test_expect_success 'set diff.wordRegex option' '
+test_expect_success 'set up diff.wordRegex option' '
git config diff.wordRegex "[[:alnum:]]+"
'
-cp expect.letter-runs-are-words expect
-
test_expect_success 'command-line overrides config' '
+ cp expect.letter-runs-are-words expect &&
word_diff --color-words="[a-z]+"
'
-cat > expect <<\EOF
-<BOLD>diff --git a/pre b/post<RESET>
-<BOLD>index 330b04f..5ed8eff 100644<RESET>
-<BOLD>--- a/pre<RESET>
-<BOLD>+++ b/post<RESET>
-<CYAN>@@ -1,3 +1,7 @@<RESET>
-h(4),<GREEN>{+hh+}<RESET>[44]
-
-a = b + c<RESET>
+test_expect_success 'command-line overrides config: --word-diff-regex' '
+ cat >expect <<-\EOF &&
+ <BOLD>diff --git a/pre b/post<RESET>
+ <BOLD>index 330b04f..5ed8eff 100644<RESET>
+ <BOLD>--- a/pre<RESET>
+ <BOLD>+++ b/post<RESET>
+ <CYAN>@@ -1,3 +1,7 @@<RESET>
+ h(4),<GREEN>{+hh+}<RESET>[44]
-<GREEN>{+aa = a+}<RESET>
+ a = b + c<RESET>
-<GREEN>{+aeff = aeff * ( aaa+}<RESET> )
-EOF
+ <GREEN>{+aa = a+}<RESET>
-test_expect_success 'command-line overrides config: --word-diff-regex' '
+ <GREEN>{+aeff = aeff * ( aaa+}<RESET> )
+ EOF
word_diff --color --word-diff-regex="[a-z]+"
'
-cp expect.non-whitespace-is-word expect
-
test_expect_success '.gitattributes override config' '
+ cp expect.non-whitespace-is-word expect &&
word_diff --color-words
'
-test_expect_success 'remove diff driver regex' '
- git config --unset diff.testdriver.wordRegex
+test_expect_success 'setup: remove diff driver regex' '
+ test_might_fail git config --unset diff.testdriver.wordRegex
'
-cat > expect <<\EOF
-<BOLD>diff --git a/pre b/post<RESET>
-<BOLD>index 330b04f..5ed8eff 100644<RESET>
-<BOLD>--- a/pre<RESET>
-<BOLD>+++ b/post<RESET>
-<CYAN>@@ -1,3 +1,7 @@<RESET>
-h(4),<GREEN>hh[44<RESET>]
-
-a = b + c<RESET>
+test_expect_success 'use configured regex' '
+ cat >expect <<-\EOF &&
+ <BOLD>diff --git a/pre b/post<RESET>
+ <BOLD>index 330b04f..5ed8eff 100644<RESET>
+ <BOLD>--- a/pre<RESET>
+ <BOLD>+++ b/post<RESET>
+ <CYAN>@@ -1,3 +1,7 @@<RESET>
+ h(4),<GREEN>hh[44<RESET>]
-<GREEN>aa = a<RESET>
+ a = b + c<RESET>
-<GREEN>aeff = aeff * ( aaa<RESET> )
-EOF
+ <GREEN>aa = a<RESET>
-test_expect_success 'use configured regex' '
+ <GREEN>aeff = aeff * ( aaa<RESET> )
+ EOF
word_diff --color-words
'
-echo 'aaa (aaa)' > pre
-echo 'aaa (aaa) aaa' > post
-
-cat > expect <<\EOF
-<BOLD>diff --git a/pre b/post<RESET>
-<BOLD>index c29453b..be22f37 100644<RESET>
-<BOLD>--- a/pre<RESET>
-<BOLD>+++ b/post<RESET>
-<CYAN>@@ -1 +1 @@<RESET>
-aaa (aaa) <GREEN>aaa<RESET>
-EOF
-
test_expect_success 'test parsing words for newline' '
-
+ echo "aaa (aaa)" >pre &&
+ echo "aaa (aaa) aaa" >post &&
+ cat >expect <<-\EOF &&
+ <BOLD>diff --git a/pre b/post<RESET>
+ <BOLD>index c29453b..be22f37 100644<RESET>
+ <BOLD>--- a/pre<RESET>
+ <BOLD>+++ b/post<RESET>
+ <CYAN>@@ -1 +1 @@<RESET>
+ aaa (aaa) <GREEN>aaa<RESET>
+ EOF
word_diff --color-words="a+"
-
-
'
-echo '(:' > pre
-echo '(' > post
-
-cat > expect <<\EOF
-<BOLD>diff --git a/pre b/post<RESET>
-<BOLD>index 289cb9d..2d06f37 100644<RESET>
-<BOLD>--- a/pre<RESET>
-<BOLD>+++ b/post<RESET>
-<CYAN>@@ -1 +1 @@<RESET>
-(<RED>:<RESET>
-EOF
-
test_expect_success 'test when words are only removed at the end' '
-
+ echo "(:" >pre &&
+ echo "(" >post &&
+ cat >expect <<-\EOF &&
+ <BOLD>diff --git a/pre b/post<RESET>
+ <BOLD>index 289cb9d..2d06f37 100644<RESET>
+ <BOLD>--- a/pre<RESET>
+ <BOLD>+++ b/post<RESET>
+ <CYAN>@@ -1 +1 @@<RESET>
+ (<RED>:<RESET>
+ EOF
word_diff --color-words=.
-
'
-cat > expect <<\EOF
-diff --git a/pre b/post
-index 289cb9d..2d06f37 100644
---- a/pre
-+++ b/post
-@@ -1 +1 @@
--(:
-+(
-EOF
-
test_expect_success '--word-diff=none' '
-
+ echo "(:" >pre &&
+ echo "(" >post &&
+ cat >expect <<-\EOF &&
+ diff --git a/pre b/post
+ index 289cb9d..2d06f37 100644
+ --- a/pre
+ +++ b/post
+ @@ -1 +1 @@
+ -(:
+ +(
+ EOF
word_diff --word-diff=plain --word-diff=none
-
'
+test_language_driver bibtex
+test_language_driver cpp
+test_language_driver csharp
+test_language_driver fortran
+test_language_driver html
+test_language_driver java
+test_language_driver objc
+test_language_driver pascal
+test_language_driver perl
+test_language_driver php
+test_language_driver python
+test_language_driver ruby
+test_language_driver tex
+
test_done
diff --git a/t/t4034/bibtex/expect b/t/t4034/bibtex/expect
--- /dev/null
+++ b/t/t4034/bibtex/expect
@@ -0,0 +1,15 @@
+<BOLD>diff --git a/pre b/post<RESET>
+<BOLD>index 95cd55b..ddcba9b 100644<RESET>
+<BOLD>--- a/pre<RESET>
+<BOLD>+++ b/post<RESET>
+<CYAN>@@ -1,9 +1,10 @@<RESET>
+@article{aldous1987uie,<RESET>
+ title={{Ultimate instability of exponential back-off protocol for acknowledgment-based transmission control of random access communication channels}},<RESET>
+ author={Aldous, <RED>D.<RESET><GREEN>David<RESET>},
+ journal={Information Theory, IEEE Transactions on},<RESET>
+ volume={<RED>33<RESET><GREEN>Bogus.<RESET>},
+ number={<RED>2<RESET><GREEN>4<RESET>},
+ pages={219--223},<RESET>
+ year=<GREEN>1987,<RESET>
+<GREEN> note={This is in fact a rather funny read since ethernet works well in practice. The<RESET> {<RED>1987<RESET><GREEN>\em pre} reference is the right one, however.<RESET>}<RED>,<RESET>
+}<RESET>
diff --git a/t/t4034/bibtex/post b/t/t4034/bibtex/post
--- /dev/null
+++ b/t/t4034/bibtex/post
@@ -0,0 +1,10 @@
+@article{aldous1987uie,
+ title={{Ultimate instability of exponential back-off protocol for acknowledgment-based transmission control of random access communication channels}},
+ author={Aldous, David},
+ journal={Information Theory, IEEE Transactions on},
+ volume={Bogus.},
+ number={4},
+ pages={219--223},
+ year=1987,
+ note={This is in fact a rather funny read since ethernet works well in practice. The {\em pre} reference is the right one, however.}
+}
diff --git a/t/t4034/bibtex/pre b/t/t4034/bibtex/pre
--- /dev/null
+++ b/t/t4034/bibtex/pre
@@ -0,0 +1,9 @@
+@article{aldous1987uie,
+ title={{Ultimate instability of exponential back-off protocol for acknowledgment-based transmission control of random access communication channels}},
+ author={Aldous, D.},
+ journal={Information Theory, IEEE Transactions on},
+ volume={33},
+ number={2},
+ pages={219--223},
+ year={1987},
+}
diff --git a/t/t4034/cpp/expect b/t/t4034/cpp/expect
--- /dev/null
+++ b/t/t4034/cpp/expect
@@ -0,0 +1,36 @@
+<BOLD>diff --git a/pre b/post<RESET>
+<BOLD>index 23d5c8a..7e8c026 100644<RESET>
+<BOLD>--- a/pre<RESET>
+<BOLD>+++ b/post<RESET>
+<CYAN>@@ -1,19 +1,19 @@<RESET>
+Foo() : x(0<RED>&&1<RESET><GREEN>&42<RESET>) { <GREEN>bar(x);<RESET> }
+cout<<"Hello World<RED>!<RESET><GREEN>?<RESET>\n"<<endl;
+<GREEN>(<RESET>1<GREEN>) (<RESET>-1e10<GREEN>) (<RESET>0xabcdef<GREEN>)<RESET> '<RED>x<RESET><GREEN>y<RESET>'
+[<RED>a<RESET><GREEN>x<RESET>] <RED>a<RESET><GREEN>x<RESET>-><RED>b a<RESET><GREEN>y x<RESET>.<RED>b<RESET><GREEN>y<RESET>
+!<RED>a<RESET><GREEN>x<RESET> ~<RED>a a<RESET><GREEN>x x<RESET>++ <RED>a<RESET><GREEN>x<RESET>-- <RED>a<RESET><GREEN>x<RESET>*<RED>b a<RESET><GREEN>y x<RESET>&<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>*<RED>b a<RESET><GREEN>y x<RESET>/<RED>b a<RESET><GREEN>y x<RESET>%<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>+<RED>b a<RESET><GREEN>y x<RESET>-<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET><<<RED>b a<RESET><GREEN>y x<RESET>>><RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET><<RED>b a<RESET><GREEN>y x<RESET><=<RED>b a<RESET><GREEN>y x<RESET>><RED>b a<RESET><GREEN>y x<RESET>>=<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>==<RED>b a<RESET><GREEN>y x<RESET>!=<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>&<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>^<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>|<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>&&<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>||<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>?<RED>b<RESET><GREEN>y<RESET>:z
+<RED>a<RESET><GREEN>x<RESET>=<RED>b a<RESET><GREEN>y x<RESET>+=<RED>b a<RESET><GREEN>y x<RESET>-=<RED>b a<RESET><GREEN>y x<RESET>*=<RED>b a<RESET><GREEN>y x<RESET>/=<RED>b a<RESET><GREEN>y x<RESET>%=<RED>b a<RESET><GREEN>y x<RESET><<=<RED>b a<RESET><GREEN>y x<RESET>>>=<RED>b a<RESET><GREEN>y x<RESET>&=<RED>b a<RESET><GREEN>y x<RESET>^=<RED>b a<RESET><GREEN>y x<RESET>|=<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>,y
+<RED>a<RESET><GREEN>x<RESET>::<RED>b<RESET><GREEN>y<RESET>
diff --git a/t/t4034/cpp/post b/t/t4034/cpp/post
--- /dev/null
+++ b/t/t4034/cpp/post
@@ -0,0 +1,19 @@
+Foo() : x(0&42) { bar(x); }
+cout<<"Hello World?\n"<<endl;
+(1) (-1e10) (0xabcdef) 'y'
+[x] x->y x.y
+!x ~x x++ x-- x*y x&y
+x*y x/y x%y
+x+y x-y
+x<<y x>>y
+x<y x<=y x>y x>=y
+x==y x!=y
+x&y
+x^y
+x|y
+x&&y
+x||y
+x?y:z
+x=y x+=y x-=y x*=y x/=y x%=y x<<=y x>>=y x&=y x^=y x|=y
+x,y
+x::y
diff --git a/t/t4034/cpp/pre b/t/t4034/cpp/pre
--- /dev/null
+++ b/t/t4034/cpp/pre
@@ -0,0 +1,19 @@
+Foo():x(0&&1){}
+cout<<"Hello World!\n"<<endl;
+1 -1e10 0xabcdef 'x'
+[a] a->b a.b
+!a ~a a++ a-- a*b a&b
+a*b a/b a%b
+a+b a-b
+a<<b a>>b
+a<b a<=b a>b a>=b
+a==b a!=b
+a&b
+a^b
+a|b
+a&&b
+a||b
+a?b:z
+a=b a+=b a-=b a*=b a/=b a%=b a<<=b a>>=b a&=b a^=b a|=b
+a,y
+a::b
diff --git a/t/t4034/csharp/expect b/t/t4034/csharp/expect
--- /dev/null
+++ b/t/t4034/csharp/expect
@@ -0,0 +1,35 @@
+<BOLD>diff --git a/pre b/post<RESET>
+<BOLD>index 9106d63..dd5f421 100644<RESET>
+<BOLD>--- a/pre<RESET>
+<BOLD>+++ b/post<RESET>
+<CYAN>@@ -1,18 +1,18 @@<RESET>
+Foo() : x(0<RED>&&1<RESET><GREEN>&42<RESET>) { <GREEN>bar(x);<RESET> }
+cout<<"Hello World<RED>!<RESET><GREEN>?<RESET>\n"<<endl;
+<GREEN>(<RESET>1<GREEN>) (<RESET>-1e10<GREEN>) (<RESET>0xabcdef<GREEN>)<RESET> '<RED>x<RESET><GREEN>y<RESET>'
+[<RED>a<RESET><GREEN>x<RESET>] <RED>a<RESET><GREEN>x<RESET>-><RED>b a<RESET><GREEN>y x<RESET>.<RED>b<RESET><GREEN>y<RESET>
+!<RED>a<RESET><GREEN>x<RESET> ~<RED>a a<RESET><GREEN>x x<RESET>++ <RED>a<RESET><GREEN>x<RESET>-- <RED>a<RESET><GREEN>x<RESET>*<RED>b a<RESET><GREEN>y x<RESET>&<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>*<RED>b a<RESET><GREEN>y x<RESET>/<RED>b a<RESET><GREEN>y x<RESET>%<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>+<RED>b a<RESET><GREEN>y x<RESET>-<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET><<<RED>b a<RESET><GREEN>y x<RESET>>><RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET><<RED>b a<RESET><GREEN>y x<RESET><=<RED>b a<RESET><GREEN>y x<RESET>><RED>b a<RESET><GREEN>y x<RESET>>=<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>==<RED>b a<RESET><GREEN>y x<RESET>!=<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>&<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>^<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>|<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>&&<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>||<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>?<RED>b<RESET><GREEN>y<RESET>:z
+<RED>a<RESET><GREEN>x<RESET>=<RED>b a<RESET><GREEN>y x<RESET>+=<RED>b a<RESET><GREEN>y x<RESET>-=<RED>b a<RESET><GREEN>y x<RESET>*=<RED>b a<RESET><GREEN>y x<RESET>/=<RED>b a<RESET><GREEN>y x<RESET>%=<RED>b a<RESET><GREEN>y x<RESET><<=<RED>b a<RESET><GREEN>y x<RESET>>>=<RED>b a<RESET><GREEN>y x<RESET>&=<RED>b a<RESET><GREEN>y x<RESET>^=<RED>b a<RESET><GREEN>y x<RESET>|=<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>,y
diff --git a/t/t4034/csharp/post b/t/t4034/csharp/post
--- /dev/null
+++ b/t/t4034/csharp/post
@@ -0,0 +1,18 @@
+Foo() : x(0&42) { bar(x); }
+cout<<"Hello World?\n"<<endl;
+(1) (-1e10) (0xabcdef) 'y'
+[x] x->y x.y
+!x ~x x++ x-- x*y x&y
+x*y x/y x%y
+x+y x-y
+x<<y x>>y
+x<y x<=y x>y x>=y
+x==y x!=y
+x&y
+x^y
+x|y
+x&&y
+x||y
+x?y:z
+x=y x+=y x-=y x*=y x/=y x%=y x<<=y x>>=y x&=y x^=y x|=y
+x,y
diff --git a/t/t4034/csharp/pre b/t/t4034/csharp/pre
--- /dev/null
+++ b/t/t4034/csharp/pre
@@ -0,0 +1,18 @@
+Foo():x(0&&1){}
+cout<<"Hello World!\n"<<endl;
+1 -1e10 0xabcdef 'x'
+[a] a->b a.b
+!a ~a a++ a-- a*b a&b
+a*b a/b a%b
+a+b a-b
+a<<b a>>b
+a<b a<=b a>b a>=b
+a==b a!=b
+a&b
+a^b
+a|b
+a&&b
+a||b
+a?b:z
+a=b a+=b a-=b a*=b a/=b a%=b a<<=b a>>=b a&=b a^=b a|=b
+a,y
diff --git a/t/t4034/fortran/expect b/t/t4034/fortran/expect
--- /dev/null
+++ b/t/t4034/fortran/expect
@@ -0,0 +1,10 @@
+<BOLD>diff --git a/pre b/post<RESET>
+<BOLD>index 87f0d0b..d308da2 100644<RESET>
+<BOLD>--- a/pre<RESET>
+<BOLD>+++ b/post<RESET>
+<CYAN>@@ -1,5 +1,5 @@<RESET>
+print *, "Hello World<RED>!<RESET><GREEN>?<RESET>"
+
+DO10I = 1,10<RESET>
+<RED>DO10I<RESET><GREEN>DO 10 I<RESET> = 1,10
+<RED>DO10I<RESET><GREEN>DO 1 0 I<RESET> = 1,10
diff --git a/t/t4034/fortran/post b/t/t4034/fortran/post
--- /dev/null
+++ b/t/t4034/fortran/post
@@ -0,0 +1,5 @@
+print *, "Hello World?"
+
+DO10I = 1,10
+DO 10 I = 1,10
+DO 1 0 I = 1,10
diff --git a/t/t4034/fortran/pre b/t/t4034/fortran/pre
--- /dev/null
+++ b/t/t4034/fortran/pre
@@ -0,0 +1,5 @@
+print *, "Hello World!"
+
+DO10I = 1,10
+DO10I = 1,10
+DO10I = 1,10
diff --git a/t/t4034/html/expect b/t/t4034/html/expect
--- /dev/null
+++ b/t/t4034/html/expect
@@ -0,0 +1,8 @@
+<BOLD>diff --git a/pre b/post<RESET>
+<BOLD>index 8ca4aea..46921e5 100644<RESET>
+<BOLD>--- a/pre<RESET>
+<BOLD>+++ b/post<RESET>
+<CYAN>@@ -1,3 +1,3 @@<RESET>
+<tag <GREEN>newattr="newvalue"<RESET>><GREEN>added<RESET> content</tag>
+<tag attr=<RED>"value"<RESET><GREEN>"newvalue"<RESET>><RED>content<RESET><GREEN>changed<RESET></tag>
+<<RED>tag<RESET><GREEN>newtag<RESET>>content <RED>&entity;<RESET><GREEN>&newentity;<RESET><<RED>/tag<RESET><GREEN>/newtag<RESET>>
diff --git a/t/t4034/html/post b/t/t4034/html/post
--- /dev/null
+++ b/t/t4034/html/post
@@ -0,0 +1,3 @@
+<tag newattr="newvalue">added content</tag>
+<tag attr="newvalue">changed</tag>
+<newtag>content &newentity;</newtag>
diff --git a/t/t4034/html/pre b/t/t4034/html/pre
--- /dev/null
+++ b/t/t4034/html/pre
@@ -0,0 +1,3 @@
+<tag>content</tag>
+<tag attr="value">content</tag>
+<tag>content &entity;</tag>
diff --git a/t/t4034/java/expect b/t/t4034/java/expect
--- /dev/null
+++ b/t/t4034/java/expect
@@ -0,0 +1,36 @@
+<BOLD>diff --git a/pre b/post<RESET>
+<BOLD>index 23d5c8a..7e8c026 100644<RESET>
+<BOLD>--- a/pre<RESET>
+<BOLD>+++ b/post<RESET>
+<CYAN>@@ -1,19 +1,19 @@<RESET>
+Foo() : x(0<RED>&&1<RESET><GREEN>&42<RESET>) { <GREEN>bar(x);<RESET> }
+cout<<"Hello World<RED>!<RESET><GREEN>?<RESET>\n"<<endl;
+<GREEN>(<RESET>1<GREEN>) (<RESET>-1e10<GREEN>) (<RESET>0xabcdef<GREEN>)<RESET> '<RED>x<RESET><GREEN>y<RESET>'
+[<RED>a<RESET><GREEN>x<RESET>] <RED>a<RESET><GREEN>x<RESET>-><RED>b a<RESET><GREEN>y x<RESET>.<RED>b<RESET><GREEN>y<RESET>
+!<RED>a<RESET><GREEN>x<RESET> ~<RED>a a<RESET><GREEN>x x<RESET>++ <RED>a<RESET><GREEN>x<RESET>-- <RED>a<RESET><GREEN>x<RESET>*<RED>b a<RESET><GREEN>y x<RESET>&<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>*<RED>b a<RESET><GREEN>y x<RESET>/<RED>b a<RESET><GREEN>y x<RESET>%<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>+<RED>b a<RESET><GREEN>y x<RESET>-<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET><<<RED>b a<RESET><GREEN>y x<RESET>>><RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET><<RED>b a<RESET><GREEN>y x<RESET><=<RED>b a<RESET><GREEN>y x<RESET>><RED>b a<RESET><GREEN>y x<RESET>>=<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>==<RED>b a<RESET><GREEN>y x<RESET>!=<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>&<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>^<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>|<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>&&<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>||<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>?<RED>b<RESET><GREEN>y<RESET>:z
+<RED>a<RESET><GREEN>x<RESET>=<RED>b a<RESET><GREEN>y x<RESET>+=<RED>b a<RESET><GREEN>y x<RESET>-=<RED>b a<RESET><GREEN>y x<RESET>*=<RED>b a<RESET><GREEN>y x<RESET>/=<RED>b a<RESET><GREEN>y x<RESET>%=<RED>b a<RESET><GREEN>y x<RESET><<=<RED>b a<RESET><GREEN>y x<RESET>>>=<RED>b a<RESET><GREEN>y x<RESET>&=<RED>b a<RESET><GREEN>y x<RESET>^=<RED>b a<RESET><GREEN>y x<RESET>|=<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>,y
+<RED>a<RESET><GREEN>x<RESET>::<RED>b<RESET><GREEN>y<RESET>
diff --git a/t/t4034/java/post b/t/t4034/java/post
--- /dev/null
+++ b/t/t4034/java/post
@@ -0,0 +1,19 @@
+Foo() : x(0&42) { bar(x); }
+cout<<"Hello World?\n"<<endl;
+(1) (-1e10) (0xabcdef) 'y'
+[x] x->y x.y
+!x ~x x++ x-- x*y x&y
+x*y x/y x%y
+x+y x-y
+x<<y x>>y
+x<y x<=y x>y x>=y
+x==y x!=y
+x&y
+x^y
+x|y
+x&&y
+x||y
+x?y:z
+x=y x+=y x-=y x*=y x/=y x%=y x<<=y x>>=y x&=y x^=y x|=y
+x,y
+x::y
diff --git a/t/t4034/java/pre b/t/t4034/java/pre
--- /dev/null
+++ b/t/t4034/java/pre
@@ -0,0 +1,19 @@
+Foo():x(0&&1){}
+cout<<"Hello World!\n"<<endl;
+1 -1e10 0xabcdef 'x'
+[a] a->b a.b
+!a ~a a++ a-- a*b a&b
+a*b a/b a%b
+a+b a-b
+a<<b a>>b
+a<b a<=b a>b a>=b
+a==b a!=b
+a&b
+a^b
+a|b
+a&&b
+a||b
+a?b:z
+a=b a+=b a-=b a*=b a/=b a%=b a<<=b a>>=b a&=b a^=b a|=b
+a,y
+a::b
diff --git a/t/t4034/objc/expect b/t/t4034/objc/expect
--- /dev/null
+++ b/t/t4034/objc/expect
@@ -0,0 +1,35 @@
+<BOLD>diff --git a/pre b/post<RESET>
+<BOLD>index 9106d63..dd5f421 100644<RESET>
+<BOLD>--- a/pre<RESET>
+<BOLD>+++ b/post<RESET>
+<CYAN>@@ -1,18 +1,18 @@<RESET>
+Foo() : x(0<RED>&&1<RESET><GREEN>&42<RESET>) { <GREEN>bar(x);<RESET> }
+cout<<"Hello World<RED>!<RESET><GREEN>?<RESET>\n"<<endl;
+<GREEN>(<RESET>1<GREEN>) (<RESET>-1e10<GREEN>) (<RESET>0xabcdef<GREEN>)<RESET> '<RED>x<RESET><GREEN>y<RESET>'
+[<RED>a<RESET><GREEN>x<RESET>] <RED>a<RESET><GREEN>x<RESET>-><RED>b a<RESET><GREEN>y x<RESET>.<RED>b<RESET><GREEN>y<RESET>
+!<RED>a<RESET><GREEN>x<RESET> ~<RED>a a<RESET><GREEN>x x<RESET>++ <RED>a<RESET><GREEN>x<RESET>-- <RED>a<RESET><GREEN>x<RESET>*<RED>b a<RESET><GREEN>y x<RESET>&<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>*<RED>b a<RESET><GREEN>y x<RESET>/<RED>b a<RESET><GREEN>y x<RESET>%<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>+<RED>b a<RESET><GREEN>y x<RESET>-<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET><<<RED>b a<RESET><GREEN>y x<RESET>>><RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET><<RED>b a<RESET><GREEN>y x<RESET><=<RED>b a<RESET><GREEN>y x<RESET>><RED>b a<RESET><GREEN>y x<RESET>>=<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>==<RED>b a<RESET><GREEN>y x<RESET>!=<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>&<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>^<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>|<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>&&<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>||<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>?<RED>b<RESET><GREEN>y<RESET>:z
+<RED>a<RESET><GREEN>x<RESET>=<RED>b a<RESET><GREEN>y x<RESET>+=<RED>b a<RESET><GREEN>y x<RESET>-=<RED>b a<RESET><GREEN>y x<RESET>*=<RED>b a<RESET><GREEN>y x<RESET>/=<RED>b a<RESET><GREEN>y x<RESET>%=<RED>b a<RESET><GREEN>y x<RESET><<=<RED>b a<RESET><GREEN>y x<RESET>>>=<RED>b a<RESET><GREEN>y x<RESET>&=<RED>b a<RESET><GREEN>y x<RESET>^=<RED>b a<RESET><GREEN>y x<RESET>|=<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>,y
diff --git a/t/t4034/objc/post b/t/t4034/objc/post
--- /dev/null
+++ b/t/t4034/objc/post
@@ -0,0 +1,18 @@
+Foo() : x(0&42) { bar(x); }
+cout<<"Hello World?\n"<<endl;
+(1) (-1e10) (0xabcdef) 'y'
+[x] x->y x.y
+!x ~x x++ x-- x*y x&y
+x*y x/y x%y
+x+y x-y
+x<<y x>>y
+x<y x<=y x>y x>=y
+x==y x!=y
+x&y
+x^y
+x|y
+x&&y
+x||y
+x?y:z
+x=y x+=y x-=y x*=y x/=y x%=y x<<=y x>>=y x&=y x^=y x|=y
+x,y
diff --git a/t/t4034/objc/pre b/t/t4034/objc/pre
--- /dev/null
+++ b/t/t4034/objc/pre
@@ -0,0 +1,18 @@
+Foo():x(0&&1){}
+cout<<"Hello World!\n"<<endl;
+1 -1e10 0xabcdef 'x'
+[a] a->b a.b
+!a ~a a++ a-- a*b a&b
+a*b a/b a%b
+a+b a-b
+a<<b a>>b
+a<b a<=b a>b a>=b
+a==b a!=b
+a&b
+a^b
+a|b
+a&&b
+a||b
+a?b:z
+a=b a+=b a-=b a*=b a/=b a%=b a<<=b a>>=b a&=b a^=b a|=b
+a,y
diff --git a/t/t4034/pascal/expect b/t/t4034/pascal/expect
--- /dev/null
+++ b/t/t4034/pascal/expect
@@ -0,0 +1,35 @@
+<BOLD>diff --git a/pre b/post<RESET>
+<BOLD>index 077046c..8865e6b 100644<RESET>
+<BOLD>--- a/pre<RESET>
+<BOLD>+++ b/post<RESET>
+<CYAN>@@ -1,18 +1,18 @@<RESET>
+writeln("Hello World<RED>!<RESET><GREEN>?<RESET>");
+<GREEN>(<RESET>1<GREEN>) (<RESET>-1e10<GREEN>) (<RESET>0xabcdef<GREEN>)<RESET> '<RED>x<RESET><GREEN>y<RESET>'
+[<RED>a<RESET><GREEN>x<RESET>] <RED>a<RESET><GREEN>x<RESET>-><RED>b a<RESET><GREEN>y x<RESET>.<RED>b<RESET><GREEN>y<RESET>
+!<RED>a<RESET><GREEN>x<RESET> ~<RED>a a<RESET><GREEN>x x<RESET>++ <RED>a<RESET><GREEN>x<RESET>-- <RED>a<RESET><GREEN>x<RESET>*<RED>b a<RESET><GREEN>y x<RESET>&<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>*<RED>b a<RESET><GREEN>y x<RESET>/<RED>b a<RESET><GREEN>y x<RESET>%<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>+<RED>b a<RESET><GREEN>y x<RESET>-<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET><<<RED>b a<RESET><GREEN>y x<RESET>>><RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET><<RED>b a<RESET><GREEN>y x<RESET><=<RED>b a<RESET><GREEN>y x<RESET>><RED>b a<RESET><GREEN>y x<RESET>>=<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>==<RED>b a<RESET><GREEN>y x<RESET>!=<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>&<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>^<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>|<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>&&<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>||<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>?<RED>b<RESET><GREEN>y<RESET>:z
+<RED>a<RESET><GREEN>x<RESET>=<RED>b a<RESET><GREEN>y x<RESET>+=<RED>b a<RESET><GREEN>y x<RESET>-=<RED>b a<RESET><GREEN>y x<RESET>*=<RED>b a<RESET><GREEN>y x<RESET>/=<RED>b a<RESET><GREEN>y x<RESET>%=<RED>b a<RESET><GREEN>y x<RESET><<=<RED>b a<RESET><GREEN>y x<RESET>>>=<RED>b a<RESET><GREEN>y x<RESET>&=<RED>b a<RESET><GREEN>y x<RESET>^=<RED>b a<RESET><GREEN>y x<RESET>|=<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>,y
+<RED>a<RESET><GREEN>x<RESET>::<RED>b<RESET><GREEN>y<RESET>
diff --git a/t/t4034/pascal/post b/t/t4034/pascal/post
--- /dev/null
+++ b/t/t4034/pascal/post
@@ -0,0 +1,18 @@
+writeln("Hello World?");
+(1) (-1e10) (0xabcdef) 'y'
+[x] x->y x.y
+!x ~x x++ x-- x*y x&y
+x*y x/y x%y
+x+y x-y
+x<<y x>>y
+x<y x<=y x>y x>=y
+x==y x!=y
+x&y
+x^y
+x|y
+x&&y
+x||y
+x?y:z
+x=y x+=y x-=y x*=y x/=y x%=y x<<=y x>>=y x&=y x^=y x|=y
+x,y
+x::y
diff --git a/t/t4034/pascal/pre b/t/t4034/pascal/pre
--- /dev/null
+++ b/t/t4034/pascal/pre
@@ -0,0 +1,18 @@
+writeln("Hello World!");
+1 -1e10 0xabcdef 'x'
+[a] a->b a.b
+!a ~a a++ a-- a*b a&b
+a*b a/b a%b
+a+b a-b
+a<<b a>>b
+a<b a<=b a>b a>=b
+a==b a!=b
+a&b
+a^b
+a|b
+a&&b
+a||b
+a?b:z
+a=b a+=b a-=b a*=b a/=b a%=b a<<=b a>>=b a&=b a^=b a|=b
+a,y
+a::b
diff --git a/t/t4034/perl/expect b/t/t4034/perl/expect
--- /dev/null
+++ b/t/t4034/perl/expect
@@ -0,0 +1,13 @@
+<BOLD>diff --git a/pre b/post<RESET>
+<BOLD>index f6610d3..e8b72ef 100644<RESET>
+<BOLD>--- a/pre<RESET>
+<BOLD>+++ b/post<RESET>
+<CYAN>@@ -4,8 +4,8 @@<RESET>
+
+package Frotz;<RESET>
+sub new {<RESET>
+ my <GREEN>(<RESET>$class<GREEN>, %opts)<RESET> = <RED>shift<RESET><GREEN>@_<RESET>;
+ return bless { <GREEN>xyzzy => "nitfol", %opts<RESET> }, $class;
+}<RESET>
+
+__END__<RESET>
diff --git a/t/t4034/perl/post b/t/t4034/perl/post
--- /dev/null
+++ b/t/t4034/perl/post
@@ -0,0 +1,22 @@
+#!/usr/bin/perl
+
+use strict;
+
+package Frotz;
+sub new {
+ my ($class, %opts) = @_;
+ return bless { xyzzy => "nitfol", %opts }, $class;
+}
+
+__END__
+=head1 NAME
+
+frotz - Frotz
+
+=head1 SYNOPSIS
+
+ use frotz;
+
+ $nitfol = new Frotz();
+
+=cut
diff --git a/t/t4034/perl/pre b/t/t4034/perl/pre
--- /dev/null
+++ b/t/t4034/perl/pre
@@ -0,0 +1,22 @@
+#!/usr/bin/perl
+
+use strict;
+
+package Frotz;
+sub new {
+ my $class = shift;
+ return bless {}, $class;
+}
+
+__END__
+=head1 NAME
+
+frotz - Frotz
+
+=head1 SYNOPSIS
+
+ use frotz;
+
+ $nitfol = new Frotz();
+
+=cut
diff --git a/t/t4034/php/expect b/t/t4034/php/expect
--- /dev/null
+++ b/t/t4034/php/expect
@@ -0,0 +1,35 @@
+<BOLD>diff --git a/pre b/post<RESET>
+<BOLD>index cf6e06b..4420a49 100644<RESET>
+<BOLD>--- a/pre<RESET>
+<BOLD>+++ b/post<RESET>
+<CYAN>@@ -1,18 +1,18 @@<RESET>
+<GREEN>(<RESET>$var<GREEN>)<RESET> $ var
+<?="Hello World<RED>!<RESET><GREEN>?<RESET>"?>
+<GREEN>(<RESET>1<GREEN>) (<RESET>-1e10<GREEN>) (<RESET>0xabcdef<GREEN>)<RESET> '<RED>x<RESET><GREEN>y<RESET>'
+[<RED>a<RESET><GREEN>x<RESET>] <RED>a<RESET><GREEN>x<RESET>-><RED>b a<RESET><GREEN>y x<RESET>.<RED>b<RESET><GREEN>y<RESET>
+!<RED>a<RESET><GREEN>x<RESET> ~<RED>a a<RESET><GREEN>x x<RESET>++ <RED>a<RESET><GREEN>x<RESET>-- <RED>a<RESET><GREEN>x<RESET>*<RED>b a<RESET><GREEN>y x<RESET>&<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>*<RED>b a<RESET><GREEN>y x<RESET>/<RED>b a<RESET><GREEN>y x<RESET>%<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>+<RED>b a<RESET><GREEN>y x<RESET>-<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET><<<RED>b a<RESET><GREEN>y x<RESET>>><RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET><<RED>b a<RESET><GREEN>y x<RESET><=<RED>b a<RESET><GREEN>y x<RESET>><RED>b a<RESET><GREEN>y x<RESET>>=<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>==<RED>b a<RESET><GREEN>y x<RESET>!=<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>&<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>^<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>|<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>&&<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>||<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>?<RED>b<RESET><GREEN>y<RESET>:z
+<RED>a<RESET><GREEN>x<RESET>=<RED>b a<RESET><GREEN>y x<RESET>+=<RED>b a<RESET><GREEN>y x<RESET>-=<RED>b a<RESET><GREEN>y x<RESET>*=<RED>b a<RESET><GREEN>y x<RESET>/=<RED>b a<RESET><GREEN>y x<RESET>%=<RED>b a<RESET><GREEN>y x<RESET><<=<RED>b a<RESET><GREEN>y x<RESET>>>=<RED>b a<RESET><GREEN>y x<RESET>&=<RED>b a<RESET><GREEN>y x<RESET>^=<RED>b a<RESET><GREEN>y x<RESET>|=<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>,y
diff --git a/t/t4034/php/post b/t/t4034/php/post
--- /dev/null
+++ b/t/t4034/php/post
@@ -0,0 +1,18 @@
+($var) $ var
+<?="Hello World?"?>
+(1) (-1e10) (0xabcdef) 'y'
+[x] x->y x.y
+!x ~x x++ x-- x*y x&y
+x*y x/y x%y
+x+y x-y
+x<<y x>>y
+x<y x<=y x>y x>=y
+x==y x!=y
+x&y
+x^y
+x|y
+x&&y
+x||y
+x?y:z
+x=y x+=y x-=y x*=y x/=y x%=y x<<=y x>>=y x&=y x^=y x|=y
+x,y
diff --git a/t/t4034/php/pre b/t/t4034/php/pre
--- /dev/null
+++ b/t/t4034/php/pre
@@ -0,0 +1,18 @@
+$var $var
+<?= "Hello World!" ?>
+1 -1e10 0xabcdef 'x'
+[a] a->b a.b
+!a ~a a++ a-- a*b a&b
+a*b a/b a%b
+a+b a-b
+a<<b a>>b
+a<b a<=b a>b a>=b
+a==b a!=b
+a&b
+a^b
+a|b
+a&&b
+a||b
+a?b:z
+a=b a+=b a-=b a*=b a/=b a%=b a<<=b a>>=b a&=b a^=b a|=b
+a,y
diff --git a/t/t4034/python/expect b/t/t4034/python/expect
--- /dev/null
+++ b/t/t4034/python/expect
@@ -0,0 +1,34 @@
+<BOLD>diff --git a/pre b/post<RESET>
+<BOLD>index 438f776..68baf34 100644<RESET>
+<BOLD>--- a/pre<RESET>
+<BOLD>+++ b/post<RESET>
+<CYAN>@@ -1,17 +1,17 @@<RESET>
+print<RED>u<RESET> "Hello World<RED>!<RESET><GREEN>?<RESET>\n"<GREEN>; print<RESET>
+<GREEN>(<RESET>1<GREEN>) (<RESET>-1e10<GREEN>) (<RESET>0xabcdef<GREEN>) u<RESET>'<RED>x<RESET><GREEN>y<RESET>'
+[<RED>a<RESET><GREEN>x<RESET>] <RED>a<RESET><GREEN>x<RESET>-><RED>b a<RESET><GREEN>y x<RESET>.<RED>b<RESET><GREEN>y<RESET>
+!<RED>a<RESET><GREEN>x<RESET> ~<RED>a a<RESET><GREEN>x x<RESET>++ <RED>a<RESET><GREEN>x<RESET>-- <RED>a<RESET><GREEN>x<RESET>*<RED>b a<RESET><GREEN>y x<RESET>&<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>*<RED>b a<RESET><GREEN>y x<RESET>/<RED>b a<RESET><GREEN>y x<RESET>%<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>+<RED>b a<RESET><GREEN>y x<RESET>-<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET><<<RED>b a<RESET><GREEN>y x<RESET>>><RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET><<RED>b a<RESET><GREEN>y x<RESET><=<RED>b a<RESET><GREEN>y x<RESET>><RED>b a<RESET><GREEN>y x<RESET>>=<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>==<RED>b a<RESET><GREEN>y x<RESET>!=<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>&<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>^<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>|<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>&&<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>||<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>?<RED>b<RESET><GREEN>y<RESET>:z
+<RED>a<RESET><GREEN>x<RESET>=<RED>b a<RESET><GREEN>y x<RESET>+=<RED>b a<RESET><GREEN>y x<RESET>-=<RED>b a<RESET><GREEN>y x<RESET>*=<RED>b a<RESET><GREEN>y x<RESET>/=<RED>b a<RESET><GREEN>y x<RESET>%=<RED>b a<RESET><GREEN>y x<RESET><<=<RED>b a<RESET><GREEN>y x<RESET>>>=<RED>b a<RESET><GREEN>y x<RESET>&=<RED>b a<RESET><GREEN>y x<RESET>^=<RED>b a<RESET><GREEN>y x<RESET>|=<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>,y
diff --git a/t/t4034/python/post b/t/t4034/python/post
--- /dev/null
+++ b/t/t4034/python/post
@@ -0,0 +1,17 @@
+print "Hello World?\n"; print
+(1) (-1e10) (0xabcdef) u'y'
+[x] x->y x.y
+!x ~x x++ x-- x*y x&y
+x*y x/y x%y
+x+y x-y
+x<<y x>>y
+x<y x<=y x>y x>=y
+x==y x!=y
+x&y
+x^y
+x|y
+x&&y
+x||y
+x?y:z
+x=y x+=y x-=y x*=y x/=y x%=y x<<=y x>>=y x&=y x^=y x|=y
+x,y
diff --git a/t/t4034/python/pre b/t/t4034/python/pre
--- /dev/null
+++ b/t/t4034/python/pre
@@ -0,0 +1,17 @@
+print u"Hello World!\n"
+1 -1e10 0xabcdef 'x'
+[a] a->b a.b
+!a ~a a++ a-- a*b a&b
+a*b a/b a%b
+a+b a-b
+a<<b a>>b
+a<b a<=b a>b a>=b
+a==b a!=b
+a&b
+a^b
+a|b
+a&&b
+a||b
+a?b:z
+a=b a+=b a-=b a*=b a/=b a%=b a<<=b a>>=b a&=b a^=b a|=b
+a,y
diff --git a/t/t4034/ruby/expect b/t/t4034/ruby/expect
--- /dev/null
+++ b/t/t4034/ruby/expect
@@ -0,0 +1,34 @@
+<BOLD>diff --git a/pre b/post<RESET>
+<BOLD>index 30ed9a1..7678f14 100644<RESET>
+<BOLD>--- a/pre<RESET>
+<BOLD>+++ b/post<RESET>
+<CYAN>@@ -1,17 +1,17 @@<RESET>
+10.downto(1) {|<RED>x<RESET><GREEN>y<RESET>| puts <RED>x<RESET><GREEN>y<RESET>}
+<GREEN>(<RESET>1<GREEN>) (<RESET>-1e10<GREEN>) (<RESET>0xabcdef<GREEN>)<RESET> '<RED>x<RESET><GREEN>y<RESET>'
+[<RED>a<RESET><GREEN>x<RESET>] <RED>a<RESET><GREEN>x<RESET>-><RED>b a<RESET><GREEN>y x<RESET>.<RED>b<RESET><GREEN>y<RESET>
+!<RED>a<RESET><GREEN>x<RESET> ~<RED>a a<RESET><GREEN>x x<RESET>++ <RED>a<RESET><GREEN>x<RESET>-- <RED>a<RESET><GREEN>x<RESET>*<RED>b a<RESET><GREEN>y x<RESET>&<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>*<RED>b a<RESET><GREEN>y x<RESET>/<RED>b a<RESET><GREEN>y x<RESET>%<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>+<RED>b a<RESET><GREEN>y x<RESET>-<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET><<<RED>b a<RESET><GREEN>y x<RESET>>><RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET><<RED>b a<RESET><GREEN>y x<RESET><=<RED>b a<RESET><GREEN>y x<RESET>><RED>b a<RESET><GREEN>y x<RESET>>=<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>==<RED>b a<RESET><GREEN>y x<RESET>!=<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>&<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>^<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>|<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>&&<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>||<RED>b<RESET>
+<RED>a?b<RESET><GREEN>y<RESET>
+<GREEN>x?y<RESET>:z
+<RED>a<RESET><GREEN>x<RESET>=<RED>b a<RESET><GREEN>y x<RESET>+=<RED>b a<RESET><GREEN>y x<RESET>-=<RED>b a<RESET><GREEN>y x<RESET>*=<RED>b a<RESET><GREEN>y x<RESET>/=<RED>b a<RESET><GREEN>y x<RESET>%=<RED>b a<RESET><GREEN>y x<RESET><<=<RED>b a<RESET><GREEN>y x<RESET>>>=<RED>b a<RESET><GREEN>y x<RESET>&=<RED>b a<RESET><GREEN>y x<RESET>^=<RED>b a<RESET><GREEN>y x<RESET>|=<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>,y
diff --git a/t/t4034/ruby/post b/t/t4034/ruby/post
--- /dev/null
+++ b/t/t4034/ruby/post
@@ -0,0 +1,17 @@
+10.downto(1) {|y| puts y}
+(1) (-1e10) (0xabcdef) 'y'
+[x] x->y x.y
+!x ~x x++ x-- x*y x&y
+x*y x/y x%y
+x+y x-y
+x<<y x>>y
+x<y x<=y x>y x>=y
+x==y x!=y
+x&y
+x^y
+x|y
+x&&y
+x||y
+x?y:z
+x=y x+=y x-=y x*=y x/=y x%=y x<<=y x>>=y x&=y x^=y x|=y
+x,y
diff --git a/t/t4034/ruby/pre b/t/t4034/ruby/pre
--- /dev/null
+++ b/t/t4034/ruby/pre
@@ -0,0 +1,17 @@
+10.downto(1) {|x| puts x}
+1 -1e10 0xabcdef 'x'
+[a] a->b a.b
+!a ~a a++ a-- a*b a&b
+a*b a/b a%b
+a+b a-b
+a<<b a>>b
+a<b a<=b a>b a>=b
+a==b a!=b
+a&b
+a^b
+a|b
+a&&b
+a||b
+a?b:z
+a=b a+=b a-=b a*=b a/=b a%=b a<<=b a>>=b a&=b a^=b a|=b
+a,y
diff --git a/t/t4034/tex/expect b/t/t4034/tex/expect
--- /dev/null
+++ b/t/t4034/tex/expect
@@ -0,0 +1,9 @@
+<BOLD>diff --git a/pre b/post<RESET>
+<BOLD>index 2b2dfcb..65cab61 100644<RESET>
+<BOLD>--- a/pre<RESET>
+<BOLD>+++ b/post<RESET>
+<CYAN>@@ -1,4 +1,4 @@<RESET>
+\section{Something <GREEN>new<RESET>}
+<RED>\emph<RESET><GREEN>\textbf<RESET>{Macro style}
+{<RED>\em<RESET><GREEN>\bfseries<RESET> State toggle style}
+\\[<RED>1em<RESET><GREEN>1cm<RESET>]
diff --git a/t/t4034/tex/post b/t/t4034/tex/post
--- /dev/null
+++ b/t/t4034/tex/post
@@ -0,0 +1,4 @@
+\section{Something new}
+\textbf{Macro style}
+{\bfseries State toggle style}
+\\[1cm]
diff --git a/t/t4034/tex/pre b/t/t4034/tex/pre
--- /dev/null
+++ b/t/t4034/tex/pre
@@ -0,0 +1,4 @@
+\section{Something}
+\emph{Macro style}
+{\em State toggle style}
+\\[1em]
index 995bdfafecedf6b629acdb3300732a4bb691f23c..bf9a7526bd38a17e0e991739db8c4a1f8541b2f6 100755 (executable)
test_expect_success 'added submodule' "
git add sm1 &&
git diff-index -p --submodule=log HEAD >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
Submodule sm1 0000000...$head1 (new submodule)
EOF
+ test_cmp expected actual
"
commit_file sm1 &&
test_expect_success 'modified submodule(forward)' "
git diff-index -p --submodule=log HEAD >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
Submodule sm1 $head1..$head2:
> Add foo3
EOF
+ test_cmp expected actual
"
test_expect_success 'modified submodule(forward)' "
git diff --submodule=log >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
Submodule sm1 $head1..$head2:
> Add foo3
EOF
+ test_cmp expected actual
"
test_expect_success 'modified submodule(forward) --submodule' "
git diff --submodule >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
Submodule sm1 $head1..$head2:
> Add foo3
EOF
+ test_cmp expected actual
"
fullhead1=$(cd sm1; git rev-list --max-count=1 $head1)
fullhead2=$(cd sm1; git rev-list --max-count=1 $head2)
test_expect_success 'modified submodule(forward) --submodule=short' "
git diff --submodule=short >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
diff --git a/sm1 b/sm1
index $head1..$head2 160000
--- a/sm1
-Subproject commit $fullhead1
+Subproject commit $fullhead2
EOF
+ test_cmp expected actual
"
commit_file sm1 &&
test_expect_success 'modified submodule(backward)' "
git diff-index -p --submodule=log HEAD >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
Submodule sm1 $head2..$head3 (rewind):
< Add foo3
< Add foo2
EOF
+ test_cmp expected actual
"
head4=$(add_file sm1 foo4 foo5) &&
head4_full=$(GIT_DIR=sm1/.git git rev-parse --verify HEAD)
test_expect_success 'modified submodule(backward and forward)' "
git diff-index -p --submodule=log HEAD >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
Submodule sm1 $head2...$head4:
> Add foo5
> Add foo4
< Add foo3
< Add foo2
EOF
+ test_cmp expected actual
"
commit_file sm1 &&
test_expect_success 'typechanged submodule(submodule->blob), --cached' "
git diff --submodule=log --cached >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
Submodule sm1 41fbea9...0000000 (submodule deleted)
diff --git a/sm1 b/sm1
new file mode 100644
@@ -0,0 +1 @@
+sm1
EOF
+ test_cmp expected actual
"
test_expect_success 'typechanged submodule(submodule->blob)' "
git diff --submodule=log >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
diff --git a/sm1 b/sm1
deleted file mode 100644
index 9da5fb8..0000000
-sm1
Submodule sm1 0000000...$head4 (new submodule)
EOF
+ test_cmp expected actual
"
rm -rf sm1 &&
git checkout-index sm1
test_expect_success 'typechanged submodule(submodule->blob)' "
git diff-index -p --submodule=log HEAD >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
Submodule sm1 $head4...0000000 (submodule deleted)
diff --git a/sm1 b/sm1
new file mode 100644
@@ -0,0 +1 @@
+sm1
EOF
+ test_cmp expected actual
"
rm -f sm1 &&
fullhead6=$(cd sm1; git rev-list --max-count=1 $head6)
test_expect_success 'nonexistent commit' "
git diff-index -p --submodule=log HEAD >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
Submodule sm1 $head4...$head6 (commits not present)
EOF
+ test_cmp expected actual
"
commit_file
test_expect_success 'typechanged submodule(blob->submodule)' "
git diff-index -p --submodule=log HEAD >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
diff --git a/sm1 b/sm1
deleted file mode 100644
index $head5..0000000
-sm1
Submodule sm1 0000000...$head6 (new submodule)
EOF
+ test_cmp expected actual
"
commit_file sm1 &&
test_expect_success 'submodule is up to date' "
git diff-index -p --submodule=log HEAD >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
EOF
+ test_cmp expected actual
"
test_expect_success 'submodule contains untracked content' "
echo new > sm1/new-file &&
git diff-index -p --submodule=log HEAD >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
Submodule sm1 contains untracked content
EOF
+ test_cmp expected actual
"
test_expect_success 'submodule contains untracked content (untracked ignored)' "
test_expect_success 'submodule contains untracked and modifed content' "
echo new > sm1/foo6 &&
git diff-index -p --submodule=log HEAD >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
Submodule sm1 contains untracked content
Submodule sm1 contains modified content
EOF
+ test_cmp expected actual
"
test_expect_success 'submodule contains untracked and modifed content (untracked ignored)' "
echo new > sm1/foo6 &&
git diff-index -p --ignore-submodules=untracked --submodule=log HEAD >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
Submodule sm1 contains modified content
EOF
+ test_cmp expected actual
"
test_expect_success 'submodule contains untracked and modifed content (dirty ignored)' "
@@ -253,45 +269,50 @@ test_expect_success 'submodule contains untracked and modifed content (all ignor
test_expect_success 'submodule contains modifed content' "
rm -f sm1/new-file &&
git diff-index -p --submodule=log HEAD >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
Submodule sm1 contains modified content
EOF
+ test_cmp expected actual
"
(cd sm1; git commit -mchange foo6 >/dev/null) &&
head8=$(cd sm1; git rev-parse --verify HEAD | cut -c1-7) &&
test_expect_success 'submodule is modified' "
git diff-index -p --submodule=log HEAD >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
Submodule sm1 $head6..$head8:
> change
EOF
+ test_cmp expected actual
"
test_expect_success 'modified submodule contains untracked content' "
echo new > sm1/new-file &&
git diff-index -p --submodule=log HEAD >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
Submodule sm1 contains untracked content
Submodule sm1 $head6..$head8:
> change
EOF
+ test_cmp expected actual
"
test_expect_success 'modified submodule contains untracked content (untracked ignored)' "
git diff-index -p --ignore-submodules=untracked --submodule=log HEAD >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
Submodule sm1 $head6..$head8:
> change
EOF
+ test_cmp expected actual
"
test_expect_success 'modified submodule contains untracked content (dirty ignored)' "
git diff-index -p --ignore-submodules=dirty --submodule=log HEAD >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
Submodule sm1 $head6..$head8:
> change
EOF
+ test_cmp expected actual
"
test_expect_success 'modified submodule contains untracked content (all ignored)' "
@@ -302,31 +323,34 @@ test_expect_success 'modified submodule contains untracked content (all ignored)
test_expect_success 'modified submodule contains untracked and modifed content' "
echo modification >> sm1/foo6 &&
git diff-index -p --submodule=log HEAD >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
Submodule sm1 contains untracked content
Submodule sm1 contains modified content
Submodule sm1 $head6..$head8:
> change
EOF
+ test_cmp expected actual
"
test_expect_success 'modified submodule contains untracked and modifed content (untracked ignored)' "
echo modification >> sm1/foo6 &&
git diff-index -p --ignore-submodules=untracked --submodule=log HEAD >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
Submodule sm1 contains modified content
Submodule sm1 $head6..$head8:
> change
EOF
+ test_cmp expected actual
"
test_expect_success 'modified submodule contains untracked and modifed content (dirty ignored)' "
echo modification >> sm1/foo6 &&
git diff-index -p --ignore-submodules=dirty --submodule=log HEAD >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
Submodule sm1 $head6..$head8:
> change
EOF
+ test_cmp expected actual
"
test_expect_success 'modified submodule contains untracked and modifed content (all ignored)' "
@@ -338,19 +362,21 @@ test_expect_success 'modified submodule contains untracked and modifed content (
test_expect_success 'modified submodule contains modifed content' "
rm -f sm1/new-file &&
git diff-index -p --submodule=log HEAD >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
Submodule sm1 contains modified content
Submodule sm1 $head6..$head8:
> change
EOF
+ test_cmp expected actual
"
rm -rf sm1
test_expect_success 'deleted submodule' "
git diff-index -p --submodule=log HEAD >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
Submodule sm1 $head6...0000000 (submodule deleted)
EOF
+ test_cmp expected actual
"
test_create_repo sm2 &&
test_expect_success 'multiple submodules' "
git diff-index -p --submodule=log HEAD >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
Submodule sm1 $head6...0000000 (submodule deleted)
Submodule sm2 0000000...$head7 (new submodule)
EOF
+ test_cmp expected actual
"
test_expect_success 'path filter' "
git diff-index -p --submodule=log HEAD sm2 >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
Submodule sm2 0000000...$head7 (new submodule)
EOF
+ test_cmp expected actual
"
commit_file sm2
test_expect_success 'given commit' "
git diff-index -p --submodule=log HEAD^ >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
Submodule sm1 $head6...0000000 (submodule deleted)
Submodule sm2 0000000...$head7 (new submodule)
EOF
+ test_cmp expected actual
"
test_expect_success 'given commit --submodule' "
git diff-index -p --submodule HEAD^ >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
Submodule sm1 $head6...0000000 (submodule deleted)
Submodule sm2 0000000...$head7 (new submodule)
EOF
+ test_cmp expected actual
"
fullhead7=$(cd sm2; git rev-list --max-count=1 $head7)
test_expect_success 'given commit --submodule=short' "
git diff-index -p --submodule=short HEAD^ >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
diff --git a/sm1 b/sm1
deleted file mode 160000
index $head6..0000000
@@ -0,0 +1 @@
+Subproject commit $fullhead7
EOF
+ test_cmp expected actual
"
test_expect_success 'setup .git file for sm2' '
test_expect_success 'diff --submodule with .git file' '
git diff --submodule HEAD^ >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
Submodule sm1 $head6...0000000 (submodule deleted)
Submodule sm2 0000000...$head7 (new submodule)
EOF
+ test_cmp expected actual
'
test_done
index 08ad6d8b9e6b2869bfd2b574779380895709972d..dbbf56cba9f5108f79d767ad48f3092dc821a232 100755 (executable)
--- a/t/t4103-apply-binary.sh
+++ b/t/t4103-apply-binary.sh
"
test_expect_success 'stat binary diff -- should not fail.' \
- 'git checkout master
+ 'git checkout master &&
git apply --stat --summary B.diff'
test_expect_success 'stat binary diff (copy) -- should not fail.' \
- 'git checkout master
+ 'git checkout master &&
git apply --stat --summary C.diff'
test_expect_success 'check binary diff -- should fail.' \
'
test_expect_success 'check binary diff with replacement.' \
- 'git checkout master
+ 'git checkout master &&
git apply --check --allow-binary-replacement BF.diff'
test_expect_success 'check binary diff with replacement (copy).' \
- 'git checkout master
+ 'git checkout master &&
git apply --check --allow-binary-replacement CF.diff'
# Now we start applying them.
index a52d94ae21afe8faaea31721234a912b090e2e08..7c398432bad761c1b38c6d15548bed0c389221fd 100755 (executable)
--- a/t/t4111-apply-subdir.sh
+++ b/t/t4111-apply-subdir.sh
test_expect_success 'apply from .git dir' '
cp postimage expected &&
cp preimage .git/file &&
- cp preimage .git/objects/file
+ cp preimage .git/objects/file &&
(
cd .git &&
git apply "$patch"
test_expect_success 'apply from subdir of .git dir' '
cp postimage expected &&
cp preimage .git/file &&
- cp preimage .git/objects/file
+ cp preimage .git/objects/file &&
(
cd .git/objects &&
git apply "$patch"
index 3c73a783a7e908070308fb1f972f6b5d152e12a4..3d0384daa8a7b7369826b05bcb793cb445c3f9ee 100755 (executable)
--- a/t/t4119-apply-config.sh
+++ b/t/t4119-apply-config.sh
test_expect_success 'apply --whitespace=strip in subdir' '
cd "$D" &&
- git config --unset-all apply.whitespace
+ git config --unset-all apply.whitespace &&
rm -f sub/file1 &&
cp saved sub/file1 &&
git update-index --refresh &&
diff --git a/t/t4120-apply-popt.sh b/t/t4120-apply-popt.sh
index 579c9e61054521301464bfc31351040d45966836..a33d510bf6b27a6bb2c640cfc9d11f462cbdf7a6 100755 (executable)
--- a/t/t4120-apply-popt.sh
+++ b/t/t4120-apply-popt.sh
test_description='git apply -p handling.'
. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-prereq-FILEMODE.sh
test_expect_success setup '
mkdir sub &&
old mode 100644
new mode 100755
EOF
- chmod 644 file1 &&
- git apply -p2 patch.chmod &&
+ test_chmod -x file1 &&
+ git apply --index -p2 patch.chmod &&
+ case $(git ls-files -s file1) in 100755*) : good;; *) false;; esac
+'
+
+test_expect_success FILEMODE 'file mode was changed' '
test -x file1
'
index 8a676a5dcd113418c2bd4ea4aa885fddd5951a3a..6f6ee88b28bc5417035b45d87aaf4a9c974ab6c5 100755 (executable)
--- a/t/t4124-apply-ws-rule.sh
+++ b/t/t4124-apply-ws-rule.sh
# X RULE
# ! trailing-space
# @ space-before-tab
- # # indent-with-non-tab
+ # # indent-with-non-tab (default tab width 8)
+ # = indent-with-non-tab,tabwidth=16
# % tab-in-indent
sed -e "s/_/ /g" -e "s/>/ /" <<-\EOF
An_SP in an ordinary line>and a HT.
________>_Eight SP, a HT and a SP (@#%).
_______________Fifteen SP (#).
_______________>Fifteen SP and a HT (@#%).
- ________________Sixteen SP (#).
- ________________>Sixteen SP and a HT (@#%).
+ ________________Sixteen SP (#=).
+ ________________>Sixteen SP and a HT (@#%=).
_____a__Five SP, a non WS, two SP.
A line with a (!) trailing SP_
A line with a (!) trailing HT>
'
+test_expect_success 'spaces inserted by tab-in-indent' '
+
+ git config core.whitespace -trailing,-space,-indent,tab &&
+ rm -f .gitattributes &&
+ test_fix % &&
+ sed -e "s/_/ /g" -e "s/>/ /" <<-\EOF >expect &&
+ An_SP in an ordinary line>and a HT.
+ ________A HT (%).
+ ________A SP and a HT (@%).
+ _________A SP, a HT and a SP (@%).
+ _______Seven SP.
+ ________Eight SP (#).
+ ________Seven SP and a HT (@%).
+ ________________Eight SP and a HT (@#%).
+ _________Seven SP, a HT and a SP (@%).
+ _________________Eight SP, a HT and a SP (@#%).
+ _______________Fifteen SP (#).
+ ________________Fifteen SP and a HT (@#%).
+ ________________Sixteen SP (#=).
+ ________________________Sixteen SP and a HT (@#%=).
+ _____a__Five SP, a non WS, two SP.
+ A line with a (!) trailing SP_
+ A line with a (!) trailing HT>
+ EOF
+ test_cmp expect target
+
+'
+
for t in - ''
do
case "$t" in '') tt='!' ;; *) tt= ;; esac
case "$s" in '') ts='@' ;; *) ts= ;; esac
for i in - ''
do
- case "$i" in '') ti='#' ;; *) ti= ;; esac
+ case "$i" in '') ti='#' ti16='=';; *) ti= ti16= ;; esac
for h in - ''
do
[ -z "$h$i" ] && continue
test_fix "$tt$ts$ti$th"
'
+ test_expect_success "rule=$rule,tabwidth=16" '
+ git config core.whitespace "$rule,tabwidth=16" &&
+ test_fix "$tt$ts$ti16$th"
+ '
+
test_expect_success "rule=$rule (attributes)" '
git config --unset core.whitespace &&
echo "target whitespace=$rule" >.gitattributes &&
test_fix "$tt$ts$ti$th"
'
+ test_expect_success "rule=$rule,tabwidth=16 (attributes)" '
+ echo "target whitespace=$rule,tabwidth=16" >.gitattributes &&
+ test_fix "$tt$ts$ti16$th"
+ '
+
done
done
done
'
test_expect_success 'blank at EOF with --whitespace=fix (1)' '
- : these can fail depending on what we did before
- git config --unset core.whitespace
- rm -f .gitattributes
+ test_might_fail git config --unset core.whitespace &&
+ rm -f .gitattributes &&
{ echo a; echo b; echo c; } >one &&
git add one &&
git diff -- one >patch &&
echo a >one &&
- test_must_fail git apply patch
+ test_must_fail git apply patch &&
test_must_fail git apply --whitespace=fix patch &&
test_must_fail git apply --ignore-space-change --whitespace=fix patch
'
printf "b\r\n" >>one &&
printf "c\r\n" >>one &&
cp one save-one &&
- printf " \r\n" >>one
+ printf " \r\n" >>one &&
git add one &&
printf "d\r\n" >>one &&
cp one expect &&
printf "b\r\n" >>one &&
printf "c\r\n" >>one &&
cp one save-one &&
- printf " \r\n" >>one
+ printf " \r\n" >>one &&
git add one &&
cp one expect &&
printf "d\r\n" >>one &&
index 77200c0b2d969d621623f3be8621a3f9925d50a5..972946c174c18ee831d8595068c6ffa235c8a538 100755 (executable)
--- a/t/t4127-apply-same-fn.sh
+++ b/t/t4127-apply-same-fn.sh
'
test_expect_success 'apply same filename with overlapping changes' '
- git reset --hard
+ git reset --hard &&
modify "s/^d/z/" same_fn &&
git diff > patch0 &&
git add same_fn &&
'
test_expect_success 'apply same new filename after rename' '
- git reset --hard
- git mv same_fn new_fn
+ git reset --hard &&
+ git mv same_fn new_fn &&
modify "s/^d/z/" new_fn &&
git add new_fn &&
git diff -M --cached > patch1 &&
'
test_expect_success 'apply same old filename after rename -- should fail.' '
- git reset --hard
- git mv same_fn new_fn
+ git reset --hard &&
+ git mv same_fn new_fn &&
modify "s/^d/z/" new_fn &&
git add new_fn &&
git diff -M --cached > patch1 &&
- git mv new_fn same_fn
+ git mv new_fn same_fn &&
modify "s/^e/y/" same_fn &&
git diff >> patch1 &&
git reset --hard &&
'
test_expect_success 'apply A->B (rename), C->A (rename), A->A -- should pass.' '
- git reset --hard
- git mv same_fn new_fn
+ git reset --hard &&
+ git mv same_fn new_fn &&
modify "s/^d/z/" new_fn &&
git add new_fn &&
git diff -M --cached > patch1 &&
git commit -m "a rename" &&
- git mv other_fn same_fn
+ git mv other_fn same_fn &&
modify "s/^e/y/" same_fn &&
git add same_fn &&
git diff -M --cached >> patch1 &&
index 7cfa2d6287ff7c6c0fc7fbc2e86fd622bbc901ce..d173acde0f2c44031003144fda9770f4b1e726b4 100755 (executable)
git reset --hard &&
mv file1 tmp &&
mv file2 file1 &&
- mv file3 file2
+ mv file3 file2 &&
mv tmp file3 &&
cp file1 file1-swapped &&
cp file2 file2-swapped &&
index 34218071b64600812acef5b5e3277346889eda7f..94da99075c55c790aae7a260ff6c4964f950c47c 100755 (executable)
. ./test-lib.sh
test_expect_success setup '
- cat > bad1.patch <<EOF
+ cat > bad1.patch <<EOF &&
diff --git a/f b/f
new file mode 100644
index 0000000..d00491f
'
test_expect_success 'apply diff with inconsistent filenames in headers' '
- test_must_fail git apply bad1.patch 2>err
- grep "inconsistent new filename" err
- test_must_fail git apply bad2.patch 2>err
+ test_must_fail git apply bad1.patch 2>err &&
+ grep "inconsistent new filename" err &&
+ test_must_fail git apply bad2.patch 2>err &&
grep "inconsistent old filename" err
'
index 1b82f93cffb1d4444a8c789f95931b86dd3d93b6..0043930ca6ab31f6cd6a0bf6464ceb281663f9e8 100755 (executable)
. ./test-lib.sh
test_expect_success setup '
- cat > create-sm.patch <<EOF
+ cat > create-sm.patch <<EOF &&
diff --git a/dir/sm b/dir/sm
new file mode 160000
index 0000000..0123456
diff --git a/t/t4150-am.sh b/t/t4150-am.sh
index 1c3d8ed548e629689517661cd1fc6c21d98ccc80..850fc96d1f07b19310cb4672ab44374b37b82d67 100755 (executable)
--- a/t/t4150-am.sh
+++ b/t/t4150-am.sh
test_expect_success 'am --signoff does not add Signed-off-by: line if already there' '
git format-patch --stdout HEAD^ >patch3 &&
- sed -e "/^Subject/ s,\[PATCH,Re: Re: Re: & 1/5 v2," patch3 >patch4
+ sed -e "/^Subject/ s,\[PATCH,Re: Re: Re: & 1/5 v2," patch3 >patch4 &&
rm -fr .git/rebase-apply &&
git reset --hard &&
git checkout HEAD^ &&
diff --git a/t/t4201-shortlog.sh b/t/t4201-shortlog.sh
index cdb70b4b3356eeb45bb6e5ac62d1f82eb6b3ccdc..6872ba1a42ce289c0983b1ed62f05defb493bf49 100755 (executable)
--- a/t/t4201-shortlog.sh
+++ b/t/t4201-shortlog.sh
tr 1234 "\370\235\204\236")" a1 &&
echo 5 >a1 &&
- git commit --quiet -m "a 12 34 56 78" a1
+ git commit --quiet -m "a 12 34 56 78" a1 &&
echo 6 >a1 &&
git commit --quiet -m "Commit by someone else" \
diff --git a/t/t4202-log.sh b/t/t4202-log.sh
index 2043bb8867cec2c2b4a431cca1f5762f10fa5ba5..2fcc31a6f3d346e533b697eecf853e219d174cd0 100755 (executable)
--- a/t/t4202-log.sh
+++ b/t/t4202-log.sh
test_expect_success 'setup case sensitivity tests' '
echo case >one &&
test_tick &&
- git add one
+ git add one &&
git commit -a -m Second
'
test_commit octopus-b &&
git checkout master &&
test_commit seventh &&
- git merge octopus-a octopus-b
+ git merge octopus-a octopus-b &&
git merge reach
'
'
test_expect_success 'log.decorate configuration' '
- git config --unset-all log.decorate || :
+ test_might_fail git config --unset-all log.decorate &&
git log --oneline >expect.none &&
git log --oneline --decorate >expect.short &&
diff --git a/t/t4204-patch-id.sh b/t/t4204-patch-id.sh
index 68e2652814c6a52265407b0fdfb70162eb634d53..d2c930de87f721a0e876351e511295ae0b094108 100755 (executable)
--- a/t/t4204-patch-id.sh
+++ b/t/t4204-patch-id.sh
test_cmp patch-id_master patch-id_same
'
+cat >nonl <<\EOF
+diff --git i/a w/a
+index e69de29..2e65efe 100644
+--- i/a
++++ w/a
+@@ -0,0 +1 @@
++a
+\ No newline at end of file
+diff --git i/b w/b
+index e69de29..6178079 100644
+--- i/b
++++ w/b
+@@ -0,0 +1 @@
++b
+EOF
+
+cat >withnl <<\EOF
+diff --git i/a w/a
+index e69de29..7898192 100644
+--- i/a
++++ w/a
+@@ -0,0 +1 @@
++a
+diff --git i/b w/b
+index e69de29..6178079 100644
+--- i/b
++++ w/b
+@@ -0,0 +1 @@
++b
+EOF
+
+test_expect_success 'patch-id handles no-nl-at-eof markers' '
+ cat nonl | calc_patch_id nonl &&
+ cat withnl | calc_patch_id withnl &&
+ test_cmp patch-id_nonl patch-id_withnl
+'
test_done
diff --git a/t/t4252-am-options.sh b/t/t4252-am-options.sh
index f603c1b1336c4a00889177376d9b51077c9cc2ac..e758e634a34762c6a2e085e0f6ea8fc5cc00af5e 100755 (executable)
--- a/t/t4252-am-options.sh
+++ b/t/t4252-am-options.sh
'
test_expect_success 'apply to a funny path' '
- with_sq="with'\''sq"
+ with_sq="with'\''sq" &&
rm -fr .git/rebase-apply &&
git reset --hard initial &&
git am --directory="$with_sq" "$tm"/am-test-5-2 &&
diff --git a/t/t5000-tar-tree.sh b/t/t5000-tar-tree.sh
index 27bfba55bd9631a666ee382b343c3c04af1ad26d..cff1b3e0502e96ff6232611ef70c24752a970a96 100755 (executable)
--- a/t/t5000-tar-tree.sh
+++ b/t/t5000-tar-tree.sh
'git archive --output=b4.tar HEAD &&
test_cmp b.tar b4.tar'
-test_expect_success 'git archive --remote' \
+test_expect_success NOT_MINGW 'git archive --remote' \
'git archive --remote=. HEAD >b5.tar &&
test_cmp b.tar b5.tar'
diff --git a/t/t5300-pack-object.sh b/t/t5300-pack-object.sh
index bbb9c1251d97bdaf5bca8f8260e481c3cac56dea..602806d09cda72c7bf0f407da35b2fb859404bd7 100755 (executable)
--- a/t/t5300-pack-object.sh
+++ b/t/t5300-pack-object.sh
test_expect_success \
'setup' \
- 'rm -f .git/index*
+ 'rm -f .git/index* &&
perl -e "print \"a\" x 4096;" > a &&
perl -e "print \"b\" x 4096;" > b &&
perl -e "print \"c\" x 4096;" > c &&
index 0a24e61ff942ee91dfb25fe490330a0272480ac2..2fc5af6007c75b7789cf2a91163939e24e00ac09 100755 (executable)
test_expect_success \
'setup' \
- 'rm -f .git/index*
+ 'rm -f .git/index* &&
for i in a b c
do
echo $i >$i &&
git repack -a -d &&
test "`git count-objects`" = "0 objects, 0 kilobytes" &&
pack2=`ls .git/objects/pack/*.pack` &&
- test -f "$pack2"
+ test -f "$pack2" &&
test "$pack1" \!= "$pack2"'
test_expect_success \
diff --git a/t/t5302-pack-index.sh b/t/t5302-pack-index.sh
index fb3a270822c9a4ac2e4f79bfa71c784291859d4f..b34ea93a8056a7ae0edf111ffe14c99b7c5b33c5 100755 (executable)
--- a/t/t5302-pack-index.sh
+++ b/t/t5302-pack-index.sh
test_expect_success \
'setup' \
- 'rm -rf .git
+ 'rm -rf .git &&
git init &&
git config pack.threads 1 &&
i=1 &&
diff --git a/t/t5400-send-pack.sh b/t/t5400-send-pack.sh
index 5bcf0b867adf3f57018a15ab8828713b986aaac6..b0b2684a1f879cc173bf2bc67902724ed8f25d48 100755 (executable)
--- a/t/t5400-send-pack.sh
+++ b/t/t5400-send-pack.sh
test "$victim_orig" = "$victim_head"
'
-test_expect_success 'push --all excludes remote tracking hierarchy' '
+test_expect_success 'push --all excludes remote-tracking hierarchy' '
mkdir parent &&
(
cd parent &&
diff --git a/t/t5500-fetch-pack.sh b/t/t5500-fetch-pack.sh
index 18376d66081759c6a4959a2d8bc47ca441364660..bafcca765e4fea92f430e7127506a2370e062ec7 100755 (executable)
--- a/t/t5500-fetch-pack.sh
+++ b/t/t5500-fetch-pack.sh
prev=$cur &&
cur=$(($cur+1))
done &&
- add B1 $A1
+ add B1 $A1 &&
echo $ATIP > .git/refs/heads/A &&
echo $BTIP > .git/refs/heads/B &&
git symbolic-ref HEAD refs/heads/B
diff --git a/t/t5502-quickfetch.sh b/t/t5502-quickfetch.sh
index 1037a723fe74756f241346a077f4f3682dbbf45d..7a46cbdbe687d080def03f41721fd0920ccd316f 100755 (executable)
--- a/t/t5502-quickfetch.sh
+++ b/t/t5502-quickfetch.sh
cd cloned &&
git count-objects | sed -e "s/ *objects,.*//"
) ) &&
- test $cnt -eq 6
+ test $cnt -eq 6 &&
blob=$(git rev-parse HEAD:file | sed -e "s|..|&/|") &&
test -f "cloned/.git/objects/$blob" &&
diff --git a/t/t5503-tagfollow.sh b/t/t5503-tagfollow.sh
index 8a298a655fa007fbd9a43d6212b53f749c980eb6..60de2d6ede958e713aebe85d73ee65ddbc10201d 100755 (executable)
--- a/t/t5503-tagfollow.sh
+++ b/t/t5503-tagfollow.sh
. ./test-lib.sh
-case $(uname -s) in
-*MINGW*)
+if ! test_have_prereq NOT_MINGW; then
say "GIT_DEBUG_SEND_PACK not supported - skipping tests"
- ;;
-*)
- test_set_prereq NOT_MINGW
- ;;
-esac
+fi
# End state of the repository:
#
'
test_expect_success NOT_MINGW 'fetch A (new commit : 1 connection)' '
- rm -f $U
+ rm -f $U &&
(
cd cloned &&
GIT_DEBUG_SEND_PACK=3 git fetch 3>../$U &&
'
test_expect_success NOT_MINGW 'fetch C, T (new branch, tag : 1 connection)' '
- rm -f $U
+ rm -f $U &&
(
cd cloned &&
GIT_DEBUG_SEND_PACK=3 git fetch 3>../$U &&
'
test_expect_success NOT_MINGW 'fetch B, S (commit and tag : 1 connection)' '
- rm -f $U
+ rm -f $U &&
(
cd cloned &&
GIT_DEBUG_SEND_PACK=3 git fetch 3>../$U &&
diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
index 5d1c66ea7166b0b4d631b681f344e0eefb84d899..d189add2d0afeea9ae29ca6b82a786b55b445217 100755 (executable)
--- a/t/t5505-remote.sh
+++ b/t/t5505-remote.sh
)
'
-test_expect_success 'remove remote protects non-remote branches' '
+test_expect_success 'remove remote protects local branches' '
(
cd test &&
{ cat >expect1 <<EOF
-Note: A non-remote branch was not removed; to delete it, use:
+Note: A branch outside the refs/remotes/ hierarchy was not removed;
+to delete it, use:
git branch -d master
EOF
} &&
{ cat >expect2 <<EOF
-Note: Non-remote branches were not removed; to delete them, use:
+Note: Some branches outside the refs/remotes/ hierarchy were not removed;
+to delete them, use:
git branch -d foobranch
git branch -d master
EOF
diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh
index 9a884751ec73ff721d658ebb69cbfe1f0270be85..7e433b179f9fcb0b3ccdd0ec83c6ec850735e391 100755 (executable)
--- a/t/t5510-fetch.sh
+++ b/t/t5510-fetch.sh
test_expect_success 'fetch must not resolve short remote name' '
cd "$D" &&
- git update-ref refs/remotes/six/HEAD HEAD
+ git update-ref refs/remotes/six/HEAD HEAD &&
mkdir six &&
cd six &&
diff --git a/t/t5513-fetch-track.sh b/t/t5513-fetch-track.sh
index 9e7486274b3f0079cb993acbd03e90afc5638e38..65d1e05bd62af9c2b6dfa6cdd841d03622bf94ac 100755 (executable)
--- a/t/t5513-fetch-track.sh
+++ b/t/t5513-fetch-track.sh
#!/bin/sh
-test_description='fetch follows remote tracking branches correctly'
+test_description='fetch follows remote-tracking branches correctly'
. ./test-lib.sh
index b73733219d62cabf02c59ed0bf08caec1158faef..227dd56137c469311209ebda43cb89f9734c6e68 100755 (executable)
(
cd two && git branch another
) &&
- git clone --mirror two three
+ git clone --mirror two three &&
git clone one test
'
diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh
index b11da79c9cafebb5af572bd8e9f85dfc6f3c3f77..d73731e6446f71480db1ec7cceb73f27ad51ecd3 100755 (executable)
--- a/t/t5516-fetch-push.sh
+++ b/t/t5516-fetch-push.sh
'
test_expect_success 'warn on push to HEAD of non-bare repository' '
- mk_test heads/master
+ mk_test heads/master &&
(
cd testrepo &&
git checkout master &&
'
test_expect_success 'deny push to HEAD of non-bare repository' '
- mk_test heads/master
+ mk_test heads/master &&
(
cd testrepo &&
git checkout master &&
'
test_expect_success 'allow push to HEAD of bare repository (bare)' '
- mk_test heads/master
+ mk_test heads/master &&
(
cd testrepo &&
git checkout master &&
'
test_expect_success 'allow push to HEAD of non-bare repository (config)' '
- mk_test heads/master
+ mk_test heads/master &&
(
cd testrepo &&
git checkout master &&
index 96be5236a2faf7178edc3c60094ecc37da741a65..c00c9b071d696038f63e8d613e11beab68eb547e 100755 (executable)
(
cd alice-pub &&
git cat-file commit master >../bob-work/commit
- )
+ ) &&
(
# This time Bob does not pull from Alice, and
# the master branch at her public repository points
diff --git a/t/t5520-pull.sh b/t/t5520-pull.sh
index 0b489f5b1227268c050c1256d105d57d871f5698..0470a81be0edaf883788d313778ef6865ed34c6f 100755 (executable)
--- a/t/t5520-pull.sh
+++ b/t/t5520-pull.sh
)
'
+test_expect_success 'git pull --rebase against local branch' '
+ git checkout -b copy2 to-rebase-orig &&
+ git pull --rebase . to-rebase &&
+ test "conflicting modification" = "$(cat file)" &&
+ test file = "$(cat file2)"
+'
+
test_done
index 00da70763bc34fe05dcba90a48799e32880571ce..c229fe68f11007fbb96d7b3837c18fd2f306a0bf 100755 (executable)
--- a/t/t5523-push-upstream.sh
+++ b/t/t5523-push-upstream.sh
test_description='push with --set-upstream'
. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-terminal.sh
+
+ensure_fresh_upstream() {
+ rm -rf parent && git init --bare parent
+}
test_expect_success 'setup bare parent' '
- git init --bare parent &&
+ ensure_fresh_upstream &&
git remote add upstream parent
'
check_config headbranch upstream refs/heads/headbranch
'
+test_expect_success TTY 'progress messages go to tty' '
+ ensure_fresh_upstream &&
+
+ test_terminal git push -u upstream master >out 2>err &&
+ grep "Writing objects" err
+'
+
+test_expect_success 'progress messages do not go to non-tty' '
+ ensure_fresh_upstream &&
+
+ # skip progress messages, since stderr is non-tty
+ git push -u upstream master >out 2>err &&
+ ! grep "Writing objects" err
+'
+
+test_expect_success 'progress messages go to non-tty (forced)' '
+ ensure_fresh_upstream &&
+
+ # force progress messages to stderr, even though it is non-tty
+ git push -u --progress upstream master >out 2>err &&
+ grep "Writing objects" err
+'
+
+test_expect_success TTY 'push -q suppresses progress' '
+ ensure_fresh_upstream &&
+
+ test_terminal git push -u -q upstream master >out 2>err &&
+ ! grep "Writing objects" err
+'
+
+test_expect_failure TTY 'push --no-progress suppresses progress' '
+ ensure_fresh_upstream &&
+
+ test_terminal git push -u --no-progress upstream master >out 2>err &&
+ ! grep "Writing objects" err
+'
+
test_done
diff --git a/t/t5526-fetch-submodules.sh b/t/t5526-fetch-submodules.sh
--- /dev/null
@@ -0,0 +1,195 @@
+#!/bin/sh
+# Copyright (c) 2010, Jens Lehmann
+
+test_description='Recursive "git fetch" for submodules'
+
+. ./test-lib.sh
+
+pwd=$(pwd)
+
+add_upstream_commit() {
+ (
+ cd submodule &&
+ head1=$(git rev-parse --short HEAD) &&
+ echo new >> subfile &&
+ test_tick &&
+ git add subfile &&
+ git commit -m new subfile &&
+ head2=$(git rev-parse --short HEAD) &&
+ echo "From $pwd/submodule" > ../expect.err &&
+ echo " $head1..$head2 master -> origin/master" >> ../expect.err
+ ) &&
+ (
+ cd deepsubmodule &&
+ head1=$(git rev-parse --short HEAD) &&
+ echo new >> deepsubfile &&
+ test_tick &&
+ git add deepsubfile &&
+ git commit -m new deepsubfile &&
+ head2=$(git rev-parse --short HEAD) &&
+ echo "From $pwd/deepsubmodule" >> ../expect.err &&
+ echo " $head1..$head2 master -> origin/master" >> ../expect.err
+ )
+}
+
+test_expect_success setup '
+ mkdir deepsubmodule &&
+ (
+ cd deepsubmodule &&
+ git init &&
+ echo deepsubcontent > deepsubfile &&
+ git add deepsubfile &&
+ git commit -m new deepsubfile
+ ) &&
+ mkdir submodule &&
+ (
+ cd submodule &&
+ git init &&
+ echo subcontent > subfile &&
+ git add subfile &&
+ git submodule add "$pwd/deepsubmodule" deepsubmodule &&
+ git commit -a -m new
+ ) &&
+ git submodule add "$pwd/submodule" submodule &&
+ git commit -am initial &&
+ git clone . downstream &&
+ (
+ cd downstream &&
+ git submodule update --init --recursive
+ ) &&
+ echo "Fetching submodule submodule" > expect.out &&
+ echo "Fetching submodule submodule/deepsubmodule" >> expect.out
+'
+
+test_expect_success "fetch --recurse-submodules recurses into submodules" '
+ add_upstream_commit &&
+ (
+ cd downstream &&
+ git fetch --recurse-submodules >../actual.out 2>../actual.err
+ ) &&
+ test_cmp expect.out actual.out &&
+ test_cmp expect.err actual.err
+'
+
+test_expect_success "fetch alone only fetches superproject" '
+ add_upstream_commit &&
+ (
+ cd downstream &&
+ git fetch >../actual.out 2>../actual.err
+ ) &&
+ ! test -s actual.out &&
+ ! test -s actual.err
+'
+
+test_expect_success "fetch --no-recurse-submodules only fetches superproject" '
+ (
+ cd downstream &&
+ git fetch --no-recurse-submodules >../actual.out 2>../actual.err
+ ) &&
+ ! test -s actual.out &&
+ ! test -s actual.err
+'
+
+test_expect_success "using fetchRecurseSubmodules=true in .gitmodules recurses into submodules" '
+ (
+ cd downstream &&
+ git config -f .gitmodules submodule.submodule.fetchRecurseSubmodules true &&
+ git fetch >../actual.out 2>../actual.err
+ ) &&
+ test_cmp expect.out actual.out &&
+ test_cmp expect.err actual.err
+'
+
+test_expect_success "--no-recurse-submodules overrides .gitmodules config" '
+ add_upstream_commit &&
+ (
+ cd downstream &&
+ git fetch --no-recurse-submodules >../actual.out 2>../actual.err
+ ) &&
+ ! test -s actual.out &&
+ ! test -s actual.err
+'
+
+test_expect_success "using fetchRecurseSubmodules=false in .git/config overrides setting in .gitmodules" '
+ (
+ cd downstream &&
+ git config submodule.submodule.fetchRecurseSubmodules false &&
+ git fetch >../actual.out 2>../actual.err
+ ) &&
+ ! test -s actual.out &&
+ ! test -s actual.err
+'
+
+test_expect_success "--recurse-submodules overrides fetchRecurseSubmodules setting from .git/config" '
+ (
+ cd downstream &&
+ git fetch --recurse-submodules >../actual.out 2>../actual.err &&
+ git config --unset -f .gitmodules submodule.submodule.fetchRecurseSubmodules &&
+ git config --unset submodule.submodule.fetchRecurseSubmodules
+ ) &&
+ test_cmp expect.out actual.out &&
+ test_cmp expect.err actual.err
+'
+
+test_expect_success "--quiet propagates to submodules" '
+ (
+ cd downstream &&
+ git fetch --recurse-submodules --quiet >../actual.out 2>../actual.err
+ ) &&
+ ! test -s actual.out &&
+ ! test -s actual.err
+'
+
+test_expect_success "--dry-run propagates to submodules" '
+ add_upstream_commit &&
+ (
+ cd downstream &&
+ git fetch --recurse-submodules --dry-run >../actual.out 2>../actual.err
+ ) &&
+ test_cmp expect.out actual.out &&
+ test_cmp expect.err actual.err &&
+ (
+ cd downstream &&
+ git fetch --recurse-submodules >../actual.out 2>../actual.err
+ ) &&
+ test_cmp expect.out actual.out &&
+ test_cmp expect.err actual.err
+'
+
+test_expect_success "recurseSubmodules=true propagates into submodules" '
+ add_upstream_commit &&
+ (
+ cd downstream &&
+ git config fetch.recurseSubmodules true
+ git fetch >../actual.out 2>../actual.err
+ ) &&
+ test_cmp expect.out actual.out &&
+ test_cmp expect.err actual.err
+'
+
+test_expect_success "--recurse-submodules overrides config in submodule" '
+ add_upstream_commit &&
+ (
+ cd downstream &&
+ (
+ cd submodule &&
+ git config fetch.recurseSubmodules false
+ ) &&
+ git fetch --recurse-submodules >../actual.out 2>../actual.err
+ ) &&
+ test_cmp expect.out actual.out &&
+ test_cmp expect.err actual.err
+'
+
+test_expect_success "--no-recurse-submodules overrides config setting" '
+ add_upstream_commit &&
+ (
+ cd downstream &&
+ git config fetch.recurseSubmodules true
+ git fetch --no-recurse-submodules >../actual.out 2>../actual.err
+ ) &&
+ ! test -s actual.out &&
+ ! test -s actual.err
+'
+
+test_done
index 65d8d474bcc6aa5ab4a1c659e228ac2e61d71e04..faa2e9633783e96609f4a31beb4bcccedb876338 100755 (executable)
test_expect_success setup '
mkdir pub.git &&
- GIT_DIR=pub.git git init --bare
+ GIT_DIR=pub.git git init --bare &&
GIT_DIR=pub.git git config receive.fsckobjects true &&
mkdir work &&
(
index 94f9d2e8e0790c22b9884cee4b668b8a6d408d99..0ad7ce07c4550ed28a22743a3c543e16f4d4c558 100755 (executable)
HTTPD_DOCUMENT_ROOT_PATH="$TRASH_DIRECTORY"
+test_have_prereq MINGW && export GREP_OPTIONS=-U
+
run_backend() {
echo "$2" |
QUERY_STRING="${1#*\?}" \
- GIT_PROJECT_ROOT="$HTTPD_DOCUMENT_ROOT_PATH" \
- PATH_INFO="${1%%\?*}" \
+ PATH_TRANSLATED="$HTTPD_DOCUMENT_ROOT_PATH/${1%%\?*}" \
git http-backend >act.out 2>act.err
}
diff --git a/t/t556x_common b/t/t556x_common
index 51287d89d8deb27ae2ae49fec84268422377eb29..82926cfdb7b7c6a86ab7d73efdfa327a6be28c83 100755 (executable)
--- a/t/t556x_common
+++ b/t/t556x_common
SMART=smart
GIT_HTTP_EXPORT_ALL=1 && export GIT_HTTP_EXPORT_ALL
test_expect_success 'direct refs/heads/master not found' '
- log_div "refs/heads/master"
+ log_div "refs/heads/master" &&
GET refs/heads/master "404 Not Found"
'
test_expect_success 'static file is ok' '
- log_div "getanyfile default"
+ log_div "getanyfile default" &&
get_static_files "200 OK"
'
SMART=smart_noexport
unset GIT_HTTP_EXPORT_ALL
test_expect_success 'no export by default' '
- log_div "no git-daemon-export-ok"
+ log_div "no git-daemon-export-ok" &&
get_static_files "404 Not Found"
'
test_expect_success 'export if git-daemon-export-ok' '
- log_div "git-daemon-export-ok"
+ log_div "git-daemon-export-ok" &&
(cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
touch git-daemon-export-ok
) &&
SMART=smart
GIT_HTTP_EXPORT_ALL=1 && export GIT_HTTP_EXPORT_ALL
test_expect_success 'static file if http.getanyfile true is ok' '
- log_div "getanyfile true"
+ log_div "getanyfile true" &&
config http.getanyfile true &&
get_static_files "200 OK"
'
test_expect_success 'static file if http.getanyfile false fails' '
- log_div "getanyfile false"
+ log_div "getanyfile false" &&
config http.getanyfile false &&
get_static_files "403 Forbidden"
'
test_expect_success 'http.uploadpack default enabled' '
- log_div "uploadpack default"
+ log_div "uploadpack default" &&
GET info/refs?service=git-upload-pack "200 OK" &&
POST git-upload-pack 0000 "200 OK"
'
test_expect_success 'http.uploadpack true' '
- log_div "uploadpack true"
+ log_div "uploadpack true" &&
config http.uploadpack true &&
GET info/refs?service=git-upload-pack "200 OK" &&
POST git-upload-pack 0000 "200 OK"
'
test_expect_success 'http.uploadpack false' '
- log_div "uploadpack false"
+ log_div "uploadpack false" &&
config http.uploadpack false &&
GET info/refs?service=git-upload-pack "403 Forbidden" &&
POST git-upload-pack 0000 "403 Forbidden"
'
test_expect_success 'http.receivepack default disabled' '
- log_div "receivepack default"
+ log_div "receivepack default" &&
GET info/refs?service=git-receive-pack "403 Forbidden" &&
POST git-receive-pack 0000 "403 Forbidden"
'
test_expect_success 'http.receivepack true' '
- log_div "receivepack true"
+ log_div "receivepack true" &&
config http.receivepack true &&
GET info/refs?service=git-receive-pack "200 OK" &&
POST git-receive-pack 0000 "200 OK"
'
test_expect_success 'http.receivepack false' '
- log_div "receivepack false"
+ log_div "receivepack false" &&
config http.receivepack false &&
GET info/refs?service=git-receive-pack "403 Forbidden" &&
POST git-receive-pack 0000 "403 Forbidden"
index deffdaee490d620c44baaee143f11be604171a42..3f353d99e8f4255b131b4d44d7ec2a2c73140f0f 100755 (executable)
. ./test-lib.sh
test_expect_success setup '
- echo "#!/bin/sh" > not_ssh
- echo "echo \"\$*\" > not_ssh_output" >> not_ssh
- echo "exit 1" >> not_ssh
+ echo "#!/bin/sh" > not_ssh &&
+ echo "echo \"\$*\" > not_ssh_output" >> not_ssh &&
+ echo "exit 1" >> not_ssh &&
chmod +x not_ssh
'
test_expect_success 'clone calls git upload-pack unqualified with no -u option' '
- GIT_SSH=./not_ssh git clone localhost:/path/to/repo junk
- echo "localhost git-upload-pack '\''/path/to/repo'\''" >expected
+ (
+ GIT_SSH=./not_ssh &&
+ export GIT_SSH &&
+ test_must_fail git clone localhost:/path/to/repo junk
+ ) &&
+ echo "localhost git-upload-pack '\''/path/to/repo'\''" >expected &&
test_cmp expected not_ssh_output
'
test_expect_success 'clone calls specified git upload-pack with -u option' '
- GIT_SSH=./not_ssh git clone -u ./something/bin/git-upload-pack localhost:/path/to/repo junk
- echo "localhost ./something/bin/git-upload-pack '\''/path/to/repo'\''" >expected
+ (
+ GIT_SSH=./not_ssh &&
+ export GIT_SSH &&
+ test_must_fail git clone -u ./something/bin/git-upload-pack localhost:/path/to/repo junk
+ ) &&
+ echo "localhost ./something/bin/git-upload-pack '\''/path/to/repo'\''" >expected &&
test_cmp expected not_ssh_output
'
diff --git a/t/t5701-clone-local.sh b/t/t5701-clone-local.sh
index 8b4c356cd21846025d84a434077bfdc8ee2bab57..6972258b27f6039e05f6bd2129f9c18ca45404d4 100755 (executable)
--- a/t/t5701-clone-local.sh
+++ b/t/t5701-clone-local.sh
git clone --bare . a.git &&
git clone --bare . x &&
test "$(GIT_CONFIG=a.git/config git config --bool core.bare)" = true &&
- test "$(GIT_CONFIG=x/config git config --bool core.bare)" = true
+ test "$(GIT_CONFIG=x/config git config --bool core.bare)" = true &&
git bundle create b1.bundle --all &&
git bundle create b2.bundle master &&
mkdir dir &&
- cp b1.bundle dir/b3
+ cp b1.bundle dir/b3 &&
cp b1.bundle b4
'
cd "$D" &&
git clone b2.bundle b2 &&
cd b2 &&
- git fetch
+ git fetch &&
test ! -e .git/refs/heads/master
'
@@ -144,4 +144,17 @@ test_expect_success 'clone empty repository, and then push should not segfault.'
test_must_fail git push)
'
+test_expect_success 'cloning non-existent directory fails' '
+ cd "$D" &&
+ rm -rf does-not-exist &&
+ test_must_fail git clone does-not-exist
+'
+
+test_expect_success 'cloning non-git directory fails' '
+ cd "$D" &&
+ rm -rf not-a-git-repo not-a-git-repo-clone &&
+ mkdir not-a-git-repo &&
+ test_must_fail git clone not-a-git-repo not-a-git-repo-clone
+'
+
test_done
diff --git a/t/t6000-rev-list-misc.sh b/t/t6000-rev-list-misc.sh
--- /dev/null
+++ b/t/t6000-rev-list-misc.sh
@@ -0,0 +1,51 @@
+#!/bin/sh
+
+test_description='miscellaneous rev-list tests'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+ echo content1 >wanted_file &&
+ echo content2 >unwanted_file &&
+ git add wanted_file unwanted_file &&
+ git commit -m one
+'
+
+test_expect_success 'rev-list --objects heeds pathspecs' '
+ git rev-list --objects HEAD -- wanted_file >output &&
+ grep wanted_file output &&
+ ! grep unwanted_file output
+'
+
+test_expect_success 'rev-list --objects with pathspecs and deeper paths' '
+ mkdir foo &&
+ >foo/file &&
+ git add foo/file &&
+ git commit -m two &&
+
+ git rev-list --objects HEAD -- foo >output &&
+ grep foo/file output &&
+
+ git rev-list --objects HEAD -- foo/file >output &&
+ grep foo/file output &&
+ ! grep unwanted_file output
+'
+
+test_expect_success 'rev-list --objects with pathspecs and copied files' '
+ git checkout --orphan junio-testcase &&
+ git rm -rf . &&
+
+ mkdir two &&
+ echo frotz >one &&
+ cp one two/three &&
+ git add one two/three &&
+ test_tick &&
+ git commit -m that &&
+
+ ONE=$(git rev-parse HEAD:one)
+ git rev-list --objects HEAD two >output &&
+ grep "$ONE two/three" output &&
+ ! grep one output
+'
+
+test_done
index fc57e7d3fd69c60144ee3fb3f66b252a67369b42..8efcd130795890c36dfe6c5c630d2be44c6e1699 100755 (executable)
for type in basic parents parents-raw
do
test_expect_success 'without grafts' "
- rm -f .git/info/grafts
+ rm -f .git/info/grafts &&
check $type $B2 -- $B2 $B1 $B0
"
test_expect_success 'with grafts' "
- echo '$B0 $A2' >.git/info/grafts
+ echo '$B0 $A2' >.git/info/grafts &&
check $type $B2 -- $B2 $B1 $B0 $A2 $A1 $A0
"
test_expect_success 'without grafts, with pathlimit' "
- rm -f .git/info/grafts
+ rm -f .git/info/grafts &&
check $type $B2 subdir -- $B2 $B0
"
test_expect_success 'with grafts, with pathlimit' "
- echo '$B0 $A2' >.git/info/grafts
+ echo '$B0 $A2' >.git/info/grafts &&
check $type $B2 subdir -- $B2 $B0 $A2 $A0
"
index 5dabf1c5e354c28cc593bd0ea8e4b0d5f0d56d67..3e8c42ee0b93cff3be39f42a411e9b32d16c5d5d 100755 (executable)
#!/bin/sh
-test_description='git rev-list trivial path optimization test'
+test_description='git rev-list trivial path optimization test
+
+ d/z1
+ b0 b1
+ o------------------------*----o master
+ / /
+ o---------o----o----o----o side
+ a0 c0 c1 a1 c2
+ d/f0 d/f1
+ d/z0
+
+'
. ./test-lib.sh
test_expect_success setup '
-echo Hello > a &&
-git add a &&
-git commit -m "Initial commit" a &&
-initial=$(git rev-parse --verify HEAD)
+ echo Hello >a &&
+ mkdir d &&
+ echo World >d/f &&
+ echo World >d/z &&
+ git add a d &&
+ test_tick &&
+ git commit -m "Initial commit" &&
+ git rev-parse --verify HEAD &&
+ git tag initial
'
test_expect_success path-optimization '
- commit=$(echo "Unchanged tree" | git commit-tree "HEAD^{tree}" -p HEAD) &&
- test $(git rev-list $commit | wc -l) = 2 &&
- test $(git rev-list $commit -- . | wc -l) = 1
+ test_tick &&
+ commit=$(echo "Unchanged tree" | git commit-tree "HEAD^{tree}" -p HEAD) &&
+ test $(git rev-list $commit | wc -l) = 2 &&
+ test $(git rev-list $commit -- . | wc -l) = 1
'
test_expect_success 'further setup' '
git checkout -b side &&
echo Irrelevant >c &&
- git add c &&
+ echo Irrelevant >d/f &&
+ git add c d/f &&
+ test_tick &&
git commit -m "Side makes an irrelevant commit" &&
+ git tag side_c0 &&
echo "More Irrelevancy" >c &&
git add c &&
+ test_tick &&
git commit -m "Side makes another irrelevant commit" &&
echo Bye >a &&
git add a &&
+ test_tick &&
git commit -m "Side touches a" &&
- side=$(git rev-parse --verify HEAD) &&
+ git tag side_a1 &&
echo "Yet more Irrelevancy" >c &&
git add c &&
+ test_tick &&
git commit -m "Side makes yet another irrelevant commit" &&
git checkout master &&
echo Another >b &&
- git add b &&
+ echo Munged >d/z &&
+ git add b d/z &&
+ test_tick &&
git commit -m "Master touches b" &&
+ git tag master_b0 &&
git merge side &&
echo Touched >b &&
git add b &&
+ test_tick &&
git commit -m "Master touches b again"
'
test_expect_success 'path optimization 2' '
- ( echo "$side"; echo "$initial" ) >expected &&
+ git rev-parse side_a1 initial >expected &&
git rev-list HEAD -- a >actual &&
test_cmp expected actual
'
+test_expect_success 'pathspec with leading path' '
+ git rev-parse master^ master_b0 side_c0 initial >expected &&
+ git rev-list HEAD -- d >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'pathspec with glob (1)' '
+ git rev-parse master^ master_b0 side_c0 initial >expected &&
+ git rev-list HEAD -- "d/*" >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'pathspec with glob (2)' '
+ git rev-parse side_c0 initial >expected &&
+ git rev-list HEAD -- "d/[a-m]*" >actual &&
+ test_cmp expected actual
+'
+
test_done
index c8a96a9a994badde602c8bf7a7decda048a00525..52f7b277ceae3ae183bc6082c01af9f76bd39a86 100755 (executable)
commit one &&
- test_tick=$(($test_tick - 2400))
+ test_tick=$(($test_tick - 2400)) &&
commit two &&
commit three &&
diff --git a/t/t6010-merge-base.sh b/t/t6010-merge-base.sh
index 62197a3d35f257dee6545d0f7768f21242e696ca..082032edc36268f1b8e26ca6f409080093ac0f2b 100755 (executable)
--- a/t/t6010-merge-base.sh
+++ b/t/t6010-merge-base.sh
R2=$(doit 3 R2 $R1) &&
PL=$(doit 4 PL $L2 $C2) &&
- PR=$(doit 4 PR $C2 $R2)
+ PR=$(doit 4 PR $C2 $R2) &&
git name-rev $C2 >expected &&
diff --git a/t/t6016-rev-list-graph-simplify-history.sh b/t/t6016-rev-list-graph-simplify-history.sh
index 27fd52b7be8ee8a084bc96bd606813cc556c4bb0..f7181d1d6a143c60a5c4b26960bd42aa2c88035d 100755 (executable)
# Octopus merge B and C into branch A
git checkout A &&
git merge B C &&
- git tag A4
+ git tag A4 &&
test_commit A5 bar.txt &&
test_commit C4 bar.txt &&
git checkout A &&
git merge -s ours C &&
- git tag A6
+ git tag A6 &&
test_commit A7 bar.txt &&
# that undecorated merges are interesting, even with --simplify-by-decoration
test_expect_success '--graph --simplify-by-decoration' '
rm -f expected &&
- git tag -d A4
+ git tag -d A4 &&
echo "* $A7" >> expected &&
echo "* $A6" >> expected &&
echo "|\\ " >> expected &&
test_cmp expected actual
'
-# Get rid of all decorations on branch B, and graph with it simplified away
+test_expect_success 'setup: get rid of decorations on B' '
+ git tag -d B2 &&
+ git tag -d B1 &&
+ git branch -d B
+'
+
+# Graph with branch B simplified away
test_expect_success '--graph --simplify-by-decoration prune branch B' '
rm -f expected &&
- git tag -d B2
- git tag -d B1
- git branch -d B
echo "* $A7" >> expected &&
echo "* $A6" >> expected &&
echo "|\\ " >> expected &&
test_expect_success '--graph --full-history -- bar.txt' '
rm -f expected &&
- git tag -d B2
- git tag -d B1
- git branch -d B
echo "* $A7" >> expected &&
echo "* $A6" >> expected &&
echo "|\\ " >> expected &&
test_expect_success '--graph --full-history --simplify-merges -- bar.txt' '
rm -f expected &&
- git tag -d B2
- git tag -d B1
- git branch -d B
echo "* $A7" >> expected &&
echo "* $A6" >> expected &&
echo "|\\ " >> expected &&
test_expect_success '--graph -- bar.txt' '
rm -f expected &&
- git tag -d B2
- git tag -d B1
- git branch -d B
echo "* $A7" >> expected &&
echo "* $A5" >> expected &&
echo "* $A3" >> expected &&
test_expect_success '--graph --sparse -- bar.txt' '
rm -f expected &&
- git tag -d B2
- git tag -d B1
- git branch -d B
echo "* $A7" >> expected &&
echo "* $A6" >> expected &&
echo "* $A5" >> expected &&
diff --git a/t/t6020-merge-df.sh b/t/t6020-merge-df.sh
index 490d3971142a87e940f3d27e7e7f068b276ff602..eec8f4e3edd85e350f5b8d8c86415317cce2aba1 100755 (executable)
--- a/t/t6020-merge-df.sh
+++ b/t/t6020-merge-df.sh
test_description='Test merge with directory/file conflicts'
. ./test-lib.sh
-test_expect_success 'prepare repository' \
-'echo "Hello" > init &&
-git add init &&
-git commit -m "Initial commit" &&
-git branch B &&
-mkdir dir &&
-echo "foo" > dir/foo &&
-git add dir/foo &&
-git commit -m "File: dir/foo" &&
-git checkout B &&
-echo "file dir" > dir &&
-git add dir &&
-git commit -m "File: dir"'
-
-test_expect_code 1 'Merge with d/f conflicts' 'git merge "merge msg" B master'
+test_expect_success 'prepare repository' '
+ echo Hello >init &&
+ git add init &&
+ git commit -m initial &&
+
+ git branch B &&
+ mkdir dir &&
+ echo foo >dir/foo &&
+ git add dir/foo &&
+ git commit -m "File: dir/foo" &&
+
+ git checkout B &&
+ echo file dir >dir &&
+ git add dir &&
+ git commit -m "File: dir"
+'
+
+test_expect_success 'Merge with d/f conflicts' '
+ test_expect_code 1 git merge "merge msg" B master
+'
test_expect_success 'F/D conflict' '
git reset --hard &&
git merge master
'
+test_expect_success 'setup modify/delete + directory/file conflict' '
+ git checkout --orphan modify &&
+ git rm -rf . &&
+ git clean -fdqx &&
+
+ printf "a\nb\nc\nd\ne\nf\ng\nh\n" >letters &&
+ git add letters &&
+ git commit -m initial &&
+
+ echo i >>letters &&
+ git add letters &&
+ git commit -m modified &&
+
+ git checkout -b delete HEAD^ &&
+ git rm letters &&
+ mkdir letters &&
+ >letters/file &&
+ git add letters &&
+ git commit -m deleted
+'
+
+test_expect_success 'modify/delete + directory/file conflict' '
+ git checkout delete^0 &&
+ test_must_fail git merge modify &&
+
+ test 3 = $(git ls-files -s | wc -l) &&
+ test 2 = $(git ls-files -u | wc -l) &&
+ test 1 = $(git ls-files -o | wc -l) &&
+
+ test -f letters/file &&
+ test -f letters~modify
+'
+
+test_expect_success 'modify/delete + directory/file conflict; other way' '
+ git reset --hard &&
+ git clean -f &&
+ git checkout modify^0 &&
+ test_must_fail git merge delete &&
+
+ test 3 = $(git ls-files -s | wc -l) &&
+ test 2 = $(git ls-files -u | wc -l) &&
+ test 1 = $(git ls-files -o | wc -l) &&
+
+ test -f letters/file &&
+ test -f letters~HEAD
+'
+
test_done
index b66544b76d545a396ef068438f3980b3f544efdd..1ed259d864b4ef4c8c7060fa5f22634a25c8e032 100755 (executable)
--- a/t/t6022-merge-rename.sh
+++ b/t/t6022-merge-rename.sh
test_description='Merge-recursive merging renames'
. ./test-lib.sh
+modify () {
+ sed -e "$1" <"$2" >"$2.x" &&
+ mv "$2.x" "$2"
+}
+
test_expect_success setup \
'
cat >A <<\EOF &&
test_expect_success 'pull renaming branch into unrenaming one' \
'
- git show-branch
- git pull . white && {
- echo "BAD: should have conflicted"
- return 1
- }
- git ls-files -s
- test "$(git ls-files -u B | wc -l)" -eq 3 || {
- echo "BAD: should have left stages for B"
- return 1
- }
- test "$(git ls-files -s N | wc -l)" -eq 1 || {
- echo "BAD: should have merged N"
- return 1
- }
+ git show-branch &&
+ test_expect_code 1 git pull . white &&
+ git ls-files -s &&
+ git ls-files -u B >b.stages &&
+ test_line_count = 3 b.stages &&
+ git ls-files -s N >n.stages &&
+ test_line_count = 1 n.stages &&
sed -ne "/^g/{
p
q
- }" B | grep master || {
- echo "BAD: should have listed our change first"
- return 1
- }
- test "$(git diff white N | wc -l)" -eq 0 || {
- echo "BAD: should have taken colored branch"
- return 1
- }
+ }" B | grep master &&
+ git diff --exit-code white N
'
test_expect_success 'pull renaming branch into another renaming one' \
'
- rm -f B
- git reset --hard
- git checkout red
- git pull . white && {
- echo "BAD: should have conflicted"
- return 1
- }
- test "$(git ls-files -u B | wc -l)" -eq 3 || {
- echo "BAD: should have left stages"
- return 1
- }
- test "$(git ls-files -s N | wc -l)" -eq 1 || {
- echo "BAD: should have merged N"
- return 1
- }
+ rm -f B &&
+ git reset --hard &&
+ git checkout red &&
+ test_expect_code 1 git pull . white &&
+ git ls-files -u B >b.stages &&
+ test_line_count = 3 b.stages &&
+ git ls-files -s N >n.stages &&
+ test_line_count = 1 n.stages &&
sed -ne "/^g/{
p
q
- }" B | grep red || {
- echo "BAD: should have listed our change first"
- return 1
- }
- test "$(git diff white N | wc -l)" -eq 0 || {
- echo "BAD: should have taken colored branch"
- return 1
- }
+ }" B | grep red &&
+ git diff --exit-code white N
'
test_expect_success 'pull unrenaming branch into renaming one' \
'
- git reset --hard
- git show-branch
- git pull . master && {
- echo "BAD: should have conflicted"
- return 1
- }
- test "$(git ls-files -u B | wc -l)" -eq 3 || {
- echo "BAD: should have left stages"
- return 1
- }
- test "$(git ls-files -s N | wc -l)" -eq 1 || {
- echo "BAD: should have merged N"
- return 1
- }
+ git reset --hard &&
+ git show-branch &&
+ test_expect_code 1 git pull . master &&
+ git ls-files -u B >b.stages &&
+ test_line_count = 3 b.stages &&
+ git ls-files -s N >n.stages &&
+ test_line_count = 1 n.stages &&
sed -ne "/^g/{
p
q
- }" B | grep red || {
- echo "BAD: should have listed our change first"
- return 1
- }
- test "$(git diff white N | wc -l)" -eq 0 || {
- echo "BAD: should have taken colored branch"
- return 1
- }
+ }" B | grep red &&
+ git diff --exit-code white N
'
test_expect_success 'pull conflicting renames' \
'
- git reset --hard
- git show-branch
- git pull . blue && {
- echo "BAD: should have conflicted"
- return 1
- }
- test "$(git ls-files -u A | wc -l)" -eq 1 || {
- echo "BAD: should have left a stage"
- return 1
- }
- test "$(git ls-files -u B | wc -l)" -eq 1 || {
- echo "BAD: should have left a stage"
- return 1
- }
- test "$(git ls-files -u C | wc -l)" -eq 1 || {
- echo "BAD: should have left a stage"
- return 1
- }
- test "$(git ls-files -s N | wc -l)" -eq 1 || {
- echo "BAD: should have merged N"
- return 1
- }
+ git reset --hard &&
+ git show-branch &&
+ test_expect_code 1 git pull . blue &&
+ git ls-files -u A >a.stages &&
+ test_line_count = 1 a.stages &&
+ git ls-files -u B >b.stages &&
+ test_line_count = 1 b.stages &&
+ git ls-files -u C >c.stages &&
+ test_line_count = 1 c.stages &&
+ git ls-files -s N >n.stages &&
+ test_line_count = 1 n.stages &&
sed -ne "/^g/{
p
q
- }" B | grep red || {
- echo "BAD: should have listed our change first"
- return 1
- }
- test "$(git diff white N | wc -l)" -eq 0 || {
- echo "BAD: should have taken colored branch"
- return 1
- }
+ }" B | grep red &&
+ git diff --exit-code white N
'
test_expect_success 'interference with untracked working tree file' '
-
- git reset --hard
- git show-branch
- echo >A this file should not matter
- git pull . white && {
- echo "BAD: should have conflicted"
- return 1
- }
- test -f A || {
- echo "BAD: should have left A intact"
- return 1
- }
+ git reset --hard &&
+ git show-branch &&
+ echo >A this file should not matter &&
+ test_expect_code 1 git pull . white &&
+ test_path_is_file A
'
test_expect_success 'interference with untracked working tree file' '
-
- git reset --hard
- git checkout white
- git show-branch
- rm -f A
- echo >A this file should not matter
- git pull . red && {
- echo "BAD: should have conflicted"
- return 1
- }
- test -f A || {
- echo "BAD: should have left A intact"
- return 1
- }
+ git reset --hard &&
+ git checkout white &&
+ git show-branch &&
+ rm -f A &&
+ echo >A this file should not matter &&
+ test_expect_code 1 git pull . red &&
+ test_path_is_file A
'
test_expect_success 'interference with untracked working tree file' '
-
- git reset --hard
- rm -f A M
- git checkout -f master
- git tag -f anchor
- git show-branch
- git pull . yellow || {
- echo "BAD: should have cleanly merged"
- return 1
- }
- test -f M && {
- echo "BAD: should have removed M"
- return 1
- }
+ git reset --hard &&
+ rm -f A M &&
+ git checkout -f master &&
+ git tag -f anchor &&
+ git show-branch &&
+ git pull . yellow &&
+ test_path_is_missing M &&
git reset --hard anchor
'
test_expect_success 'updated working tree file should prevent the merge' '
-
- git reset --hard
- rm -f A M
- git checkout -f master
- git tag -f anchor
- git show-branch
- echo >>M one line addition
- cat M >M.saved
- git pull . yellow && {
- echo "BAD: should have complained"
- return 1
- }
- test_cmp M M.saved || {
- echo "BAD: should have left M intact"
- return 1
- }
+ git reset --hard &&
+ rm -f A M &&
+ git checkout -f master &&
+ git tag -f anchor &&
+ git show-branch &&
+ echo >>M one line addition &&
+ cat M >M.saved &&
+ test_expect_code 128 git pull . yellow &&
+ test_cmp M M.saved &&
rm -f M.saved
'
test_expect_success 'updated working tree file should prevent the merge' '
-
- git reset --hard
- rm -f A M
- git checkout -f master
- git tag -f anchor
- git show-branch
- echo >>M one line addition
- cat M >M.saved
- git update-index M
- git pull . yellow && {
- echo "BAD: should have complained"
- return 1
- }
- test_cmp M M.saved || {
- echo "BAD: should have left M intact"
- return 1
- }
+ git reset --hard &&
+ rm -f A M &&
+ git checkout -f master &&
+ git tag -f anchor &&
+ git show-branch &&
+ echo >>M one line addition &&
+ cat M >M.saved &&
+ git update-index M &&
+ test_expect_code 128 git pull . yellow &&
+ test_cmp M M.saved &&
rm -f M.saved
'
test_expect_success 'interference with untracked working tree file' '
-
- git reset --hard
- rm -f A M
- git checkout -f yellow
- git tag -f anchor
- git show-branch
- echo >M this file should not matter
- git pull . master || {
- echo "BAD: should have cleanly merged"
- return 1
- }
- test -f M || {
- echo "BAD: should have left M intact"
- return 1
- }
- git ls-files -s | grep M && {
- echo "BAD: M must be untracked in the result"
- return 1
- }
+ git reset --hard &&
+ rm -f A M &&
+ git checkout -f yellow &&
+ git tag -f anchor &&
+ git show-branch &&
+ echo >M this file should not matter &&
+ git pull . master &&
+ test_path_is_file M &&
+ ! {
+ git ls-files -s |
+ grep M
+ } &&
git reset --hard anchor
'
test_expect_success 'merge of identical changes in a renamed file' '
- rm -f A M N
+ rm -f A M N &&
git reset --hard &&
git checkout change+rename &&
GIT_MERGE_VERBOSITY=3 git merge change | grep "^Skipped B" &&
GIT_MERGE_VERBOSITY=3 git merge change+rename | grep "^Skipped B"
'
+test_expect_success 'setup for rename + d/f conflicts' '
+ git reset --hard &&
+ git checkout --orphan dir-in-way &&
+ git rm -rf . &&
+
+ mkdir sub &&
+ mkdir dir &&
+ printf "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n" >sub/file &&
+ echo foo >dir/file-in-the-way &&
+ git add -A &&
+ git commit -m "Common commmit" &&
+
+ echo 11 >>sub/file &&
+ echo more >>dir/file-in-the-way &&
+ git add -u &&
+ git commit -m "Commit to merge, with dir in the way" &&
+
+ git checkout -b dir-not-in-way &&
+ git reset --soft HEAD^ &&
+ git rm -rf dir &&
+ git commit -m "Commit to merge, with dir removed" -- dir sub/file &&
+
+ git checkout -b renamed-file-has-no-conflicts dir-in-way~1 &&
+ git rm -rf dir &&
+ git rm sub/file &&
+ printf "1\n2\n3\n4\n5555\n6\n7\n8\n9\n10\n" >dir &&
+ git add dir &&
+ git commit -m "Independent change" &&
+
+ git checkout -b renamed-file-has-conflicts dir-in-way~1 &&
+ git rm -rf dir &&
+ git mv sub/file dir &&
+ echo 12 >>dir &&
+ git add dir &&
+ git commit -m "Conflicting change"
+'
+
+printf "1\n2\n3\n4\n5555\n6\n7\n8\n9\n10\n11\n" >expected
+
+test_expect_success 'Rename+D/F conflict; renamed file merges + dir not in way' '
+ git reset --hard &&
+ git checkout -q renamed-file-has-no-conflicts^0 &&
+ git merge --strategy=recursive dir-not-in-way &&
+ git diff --quiet &&
+ test -f dir &&
+ test_cmp expected dir
+'
+
+test_expect_success 'Rename+D/F conflict; renamed file merges but dir in way' '
+ git reset --hard &&
+ rm -rf dir~* &&
+ git checkout -q renamed-file-has-no-conflicts^0 &&
+ test_must_fail git merge --strategy=recursive dir-in-way >output &&
+
+ grep "CONFLICT (delete/modify): dir/file-in-the-way" output &&
+ grep "Auto-merging dir" output &&
+ grep "Adding as dir~HEAD instead" output &&
+
+ test 2 -eq "$(git ls-files -u | wc -l)" &&
+ test 2 -eq "$(git ls-files -u dir/file-in-the-way | wc -l)" &&
+
+ test_must_fail git diff --quiet &&
+ test_must_fail git diff --cached --quiet &&
+
+ test -f dir/file-in-the-way &&
+ test -f dir~HEAD &&
+ test_cmp expected dir~HEAD
+'
+
+test_expect_success 'Same as previous, but merged other way' '
+ git reset --hard &&
+ rm -rf dir~* &&
+ git checkout -q dir-in-way^0 &&
+ test_must_fail git merge --strategy=recursive renamed-file-has-no-conflicts >output 2>errors &&
+
+ ! grep "error: refusing to lose untracked file at" errors &&
+ grep "CONFLICT (delete/modify): dir/file-in-the-way" output &&
+ grep "Auto-merging dir" output &&
+ grep "Adding as dir~renamed-file-has-no-conflicts instead" output &&
+
+ test 2 -eq "$(git ls-files -u | wc -l)" &&
+ test 2 -eq "$(git ls-files -u dir/file-in-the-way | wc -l)" &&
+
+ test_must_fail git diff --quiet &&
+ test_must_fail git diff --cached --quiet &&
+
+ test -f dir/file-in-the-way &&
+ test -f dir~renamed-file-has-no-conflicts &&
+ test_cmp expected dir~renamed-file-has-no-conflicts
+'
+
+cat >expected <<\EOF &&
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+<<<<<<< HEAD
+12
+=======
+11
+>>>>>>> dir-not-in-way
+EOF
+
+test_expect_success 'Rename+D/F conflict; renamed file cannot merge, dir not in way' '
+ git reset --hard &&
+ rm -rf dir~* &&
+ git checkout -q renamed-file-has-conflicts^0 &&
+ test_must_fail git merge --strategy=recursive dir-not-in-way &&
+
+ test 3 -eq "$(git ls-files -u | wc -l)" &&
+ test 3 -eq "$(git ls-files -u dir | wc -l)" &&
+
+ test_must_fail git diff --quiet &&
+ test_must_fail git diff --cached --quiet &&
+
+ test -f dir &&
+ test_cmp expected dir
+'
+
+test_expect_success 'Rename+D/F conflict; renamed file cannot merge and dir in the way' '
+ modify s/dir-not-in-way/dir-in-way/ expected &&
+
+ git reset --hard &&
+ rm -rf dir~* &&
+ git checkout -q renamed-file-has-conflicts^0 &&
+ test_must_fail git merge --strategy=recursive dir-in-way &&
+
+ test 5 -eq "$(git ls-files -u | wc -l)" &&
+ test 3 -eq "$(git ls-files -u dir | grep -v file-in-the-way | wc -l)" &&
+ test 2 -eq "$(git ls-files -u dir/file-in-the-way | wc -l)" &&
+
+ test_must_fail git diff --quiet &&
+ test_must_fail git diff --cached --quiet &&
+
+ test -f dir/file-in-the-way &&
+ test -f dir~HEAD &&
+ test_cmp expected dir~HEAD
+'
+
+cat >expected <<\EOF &&
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+<<<<<<< HEAD
+11
+=======
+12
+>>>>>>> renamed-file-has-conflicts
+EOF
+
+test_expect_success 'Same as previous, but merged other way' '
+ git reset --hard &&
+ rm -rf dir~* &&
+ git checkout -q dir-in-way^0 &&
+ test_must_fail git merge --strategy=recursive renamed-file-has-conflicts &&
+
+ test 5 -eq "$(git ls-files -u | wc -l)" &&
+ test 3 -eq "$(git ls-files -u dir | grep -v file-in-the-way | wc -l)" &&
+ test 2 -eq "$(git ls-files -u dir/file-in-the-way | wc -l)" &&
+
+ test_must_fail git diff --quiet &&
+ test_must_fail git diff --cached --quiet &&
+
+ test -f dir/file-in-the-way &&
+ test -f dir~renamed-file-has-conflicts &&
+ test_cmp expected dir~renamed-file-has-conflicts
+'
+
+test_expect_success 'setup both rename source and destination involved in D/F conflict' '
+ git reset --hard &&
+ git checkout --orphan rename-dest &&
+ git rm -rf . &&
+ git clean -fdqx &&
+
+ mkdir one &&
+ echo stuff >one/file &&
+ git add -A &&
+ git commit -m "Common commmit" &&
+
+ git mv one/file destdir &&
+ git commit -m "Renamed to destdir" &&
+
+ git checkout -b source-conflict HEAD~1 &&
+ git rm -rf one &&
+ mkdir destdir &&
+ touch one destdir/foo &&
+ git add -A &&
+ git commit -m "Conflicts in the way"
+'
+
+test_expect_success 'both rename source and destination involved in D/F conflict' '
+ git reset --hard &&
+ rm -rf dir~* &&
+ git checkout -q rename-dest^0 &&
+ test_must_fail git merge --strategy=recursive source-conflict &&
+
+ test 1 -eq "$(git ls-files -u | wc -l)" &&
+
+ test_must_fail git diff --quiet &&
+
+ test -f destdir/foo &&
+ test -f one &&
+ test -f destdir~HEAD &&
+ test "stuff" = "$(cat destdir~HEAD)"
+'
+
+test_expect_success 'setup pair rename to parent of other (D/F conflicts)' '
+ git reset --hard &&
+ git checkout --orphan rename-two &&
+ git rm -rf . &&
+ git clean -fdqx &&
+
+ mkdir one &&
+ mkdir two &&
+ echo stuff >one/file &&
+ echo other >two/file &&
+ git add -A &&
+ git commit -m "Common commmit" &&
+
+ git rm -rf one &&
+ git mv two/file one &&
+ git commit -m "Rename two/file -> one" &&
+
+ git checkout -b rename-one HEAD~1 &&
+ git rm -rf two &&
+ git mv one/file two &&
+ rm -r one &&
+ git commit -m "Rename one/file -> two"
+'
+
+test_expect_success 'pair rename to parent of other (D/F conflicts) w/ untracked dir' '
+ git checkout -q rename-one^0 &&
+ mkdir one &&
+ test_must_fail git merge --strategy=recursive rename-two &&
+
+ test 2 -eq "$(git ls-files -u | wc -l)" &&
+ test 1 -eq "$(git ls-files -u one | wc -l)" &&
+ test 1 -eq "$(git ls-files -u two | wc -l)" &&
+
+ test_must_fail git diff --quiet &&
+
+ test 4 -eq $(find . | grep -v .git | wc -l) &&
+
+ test -d one &&
+ test -f one~rename-two &&
+ test -f two &&
+ test "other" = $(cat one~rename-two) &&
+ test "stuff" = $(cat two)
+'
+
+test_expect_success 'pair rename to parent of other (D/F conflicts) w/ clean start' '
+ git reset --hard &&
+ git clean -fdqx &&
+ test_must_fail git merge --strategy=recursive rename-two &&
+
+ test 2 -eq "$(git ls-files -u | wc -l)" &&
+ test 1 -eq "$(git ls-files -u one | wc -l)" &&
+ test 1 -eq "$(git ls-files -u two | wc -l)" &&
+
+ test_must_fail git diff --quiet &&
+
+ test 3 -eq $(find . | grep -v .git | wc -l) &&
+
+ test -f one &&
+ test -f two &&
+ test "other" = $(cat one) &&
+ test "stuff" = $(cat two)
+'
+
+test_expect_success 'setup rename of one file to two, with directories in the way' '
+ git reset --hard &&
+ git checkout --orphan first-rename &&
+ git rm -rf . &&
+ git clean -fdqx &&
+
+ echo stuff >original &&
+ git add -A &&
+ git commit -m "Common commmit" &&
+
+ mkdir two &&
+ >two/file &&
+ git add two/file &&
+ git mv original one &&
+ git commit -m "Put two/file in the way, rename to one" &&
+
+ git checkout -b second-rename HEAD~1 &&
+ mkdir one &&
+ >one/file &&
+ git add one/file &&
+ git mv original two &&
+ git commit -m "Put one/file in the way, rename to two"
+'
+
+test_expect_success 'check handling of differently renamed file with D/F conflicts' '
+ git checkout -q first-rename^0 &&
+ test_must_fail git merge --strategy=recursive second-rename &&
+
+ test 5 -eq "$(git ls-files -s | wc -l)" &&
+ test 3 -eq "$(git ls-files -u | wc -l)" &&
+ test 1 -eq "$(git ls-files -u one | wc -l)" &&
+ test 1 -eq "$(git ls-files -u two | wc -l)" &&
+ test 1 -eq "$(git ls-files -u original | wc -l)" &&
+ test 2 -eq "$(git ls-files -o | wc -l)" &&
+
+ test -f one/file &&
+ test -f two/file &&
+ test -f one~HEAD &&
+ test -f two~second-rename &&
+ ! test -f original
+'
+
+test_expect_success 'setup rename one file to two; directories moving out of the way' '
+ git reset --hard &&
+ git checkout --orphan first-rename-redo &&
+ git rm -rf . &&
+ git clean -fdqx &&
+
+ echo stuff >original &&
+ mkdir one two &&
+ touch one/file two/file &&
+ git add -A &&
+ git commit -m "Common commmit" &&
+
+ git rm -rf one &&
+ git mv original one &&
+ git commit -m "Rename to one" &&
+
+ git checkout -b second-rename-redo HEAD~1 &&
+ git rm -rf two &&
+ git mv original two &&
+ git commit -m "Rename to two"
+'
+
+test_expect_success 'check handling of differently renamed file with D/F conflicts' '
+ git checkout -q first-rename-redo^0 &&
+ test_must_fail git merge --strategy=recursive second-rename-redo &&
+
+ test 3 -eq "$(git ls-files -u | wc -l)" &&
+ test 1 -eq "$(git ls-files -u one | wc -l)" &&
+ test 1 -eq "$(git ls-files -u two | wc -l)" &&
+ test 1 -eq "$(git ls-files -u original | wc -l)" &&
+ test 0 -eq "$(git ls-files -o | wc -l)" &&
+
+ test -f one &&
+ test -f two &&
+ ! test -f original
+'
+
test_done
index b3fbf659c003acbed785558c21950046b8caced8..755d30ce2a5d1c5e34751d8906ad41b02d553b03 100755 (executable)
test_tick &&
git commit -m delete &&
git checkout -b rename HEAD^ &&
- git mv a1 a2
+ git mv a1 a2 &&
test_tick &&
git commit -m rename &&
test_must_fail git merge delete &&
index 3900d9f61f84a9eab907e87fca7bb9429ea36f04..73fc240e8548911c65c4dffc4da0c6ff8ab4f27b 100755 (executable)
--- a/t/t6029-merge-subtree.sh
+++ b/t/t6029-merge-subtree.sh
test_expect_success setup '
- s="1 2 3 4 5 6 7 8"
+ s="1 2 3 4 5 6 7 8" &&
for i in $s; do echo $i; done >hello &&
git add hello &&
git commit -m initial &&
index 3b042aacd63f77651fdaf3d10b65f4fc85669a75..b5063b6fe6c37f4b41a89b71c6d52ac0b5c07127 100755 (executable)
add_line_into_file "2(para): line 2 on parallel branch" dir2/file2 &&
PARA_HASH2=$(git rev-parse --verify HEAD) &&
add_line_into_file "3(para): line 3 on parallel branch" dir2/file3 &&
- PARA_HASH3=$(git rev-parse --verify HEAD)
+ PARA_HASH3=$(git rev-parse --verify HEAD) &&
git merge -m "merge HASH4 and PARA_HASH3" "$HASH4" &&
- PARA_HASH4=$(git rev-parse --verify HEAD)
+ PARA_HASH4=$(git rev-parse --verify HEAD) &&
add_line_into_file "5(para): add line on parallel branch" dir1/file1 &&
- PARA_HASH5=$(git rev-parse --verify HEAD)
+ PARA_HASH5=$(git rev-parse --verify HEAD) &&
add_line_into_file "6(para): add line on parallel branch" dir2/file2 &&
- PARA_HASH6=$(git rev-parse --verify HEAD)
+ PARA_HASH6=$(git rev-parse --verify HEAD) &&
git merge -m "merge HASH7 and PARA_HASH6" "$HASH7" &&
PARA_HASH7=$(git rev-parse --verify HEAD)
'
index eac5ebac24a0174fa20625a19835861573147a26..fdb6c253718e3d4c8acfcd1fc197607aac04b4c4 100755 (executable)
test_rename 5 ok
test_rename 6 fail
+test_expect_success 'setup large simple rename' '
+ git config --unset merge.renamelimit &&
+ git config --unset diff.renamelimit &&
+
+ git reset --hard initial &&
+ for i in $(count 200); do
+ make_text foo bar baz >$i
+ done &&
+ git add . &&
+ git commit -m create-files &&
+
+ git branch simple-change &&
+ git checkout -b simple-rename &&
+
+ mkdir builtin &&
+ git mv [0-9]* builtin/ &&
+ git commit -m renamed &&
+
+ git checkout simple-change &&
+ >unrelated-change &&
+ git add unrelated-change &&
+ git commit -m unrelated-change
+'
+
+test_expect_success 'massive simple rename does not spam added files' '
+ unset GIT_MERGE_VERBOSITY &&
+ git merge --no-stat simple-rename | grep -v Removing >output &&
+ test 5 -gt "$(wc -l < output)"
+'
+
test_done
index b8741416588e9f3d7de7c0a0359a0b8ea7cf8045..871577d90cc2817ffcc0cfe1ecf76aab6be61723 100755 (executable)
# R1 R2
#
-test_expect_success setup '
+test_expect_success 'setup basic criss-cross + rename with no modifications' '
+ ten="0 1 2 3 4 5 6 7 8 9" &&
+ for i in $ten
+ do
+ echo line $i in a sample file
+ done >one &&
+ for i in $ten
+ do
+ echo line $i in another sample file
+ done >two &&
+ git add one two &&
+ test_tick && git commit -m initial &&
+
+ git branch L1 &&
+ git checkout -b R1 &&
+ git mv one three &&
+ test_tick && git commit -m R1 &&
+
+ git checkout L1 &&
+ git mv two three &&
+ test_tick && git commit -m L1 &&
+
+ git checkout L1^0 &&
+ test_tick && git merge -s ours R1 &&
+ git tag L2 &&
+
+ git checkout R1^0 &&
+ test_tick && git merge -s ours L1 &&
+ git tag R2
+'
+
+test_expect_success 'merge simple rename+criss-cross with no modifications' '
+ git reset --hard &&
+ git checkout L2^0 &&
+
+ test_must_fail git merge -s recursive R2^0 &&
+
+ test 5 = $(git ls-files -s | wc -l) &&
+ test 3 = $(git ls-files -u | wc -l) &&
+ test 0 = $(git ls-files -o | wc -l) &&
+
+ test $(git rev-parse :0:one) = $(git rev-parse L2:one) &&
+ test $(git rev-parse :0:two) = $(git rev-parse R2:two) &&
+ test $(git rev-parse :2:three) = $(git rev-parse L2:three) &&
+ test $(git rev-parse :3:three) = $(git rev-parse R2:three) &&
+
+ cp two merged &&
+ >empty &&
+ test_must_fail git merge-file \
+ -L "Temporary merge branch 2" \
+ -L "" \
+ -L "Temporary merge branch 1" \
+ merged empty one &&
+ test $(git rev-parse :1:three) = $(git hash-object merged)
+'
+
+#
+# Same as before, but modify L1 slightly:
+#
+# L1m L2
+# o---o
+# / \ / \
+# o X ?
+# \ / \ /
+# o---o
+# R1 R2
+#
+
+test_expect_success 'setup criss-cross + rename merges with basic modification' '
+ git rm -rf . &&
+ git clean -fdqx &&
+ rm -rf .git &&
+ git init &&
+
ten="0 1 2 3 4 5 6 7 8 9"
for i in $ten
do
git branch L1 &&
git checkout -b R1 &&
git mv one three &&
+ echo more >>two &&
+ git add two &&
test_tick && git commit -m R1 &&
git checkout L1 &&
git tag R2
'
-test_expect_success merge '
+test_expect_success 'merge criss-cross + rename merges with basic modification' '
git reset --hard &&
git checkout L2^0 &&
- test_must_fail git merge -s recursive R2^0
+ test_must_fail git merge -s recursive R2^0 &&
+
+ test 5 = $(git ls-files -s | wc -l) &&
+ test 3 = $(git ls-files -u | wc -l) &&
+ test 0 = $(git ls-files -o | wc -l) &&
+
+ test $(git rev-parse :0:one) = $(git rev-parse L2:one) &&
+ test $(git rev-parse :0:two) = $(git rev-parse R2:two) &&
+ test $(git rev-parse :2:three) = $(git rev-parse L2:three) &&
+ test $(git rev-parse :3:three) = $(git rev-parse R2:three) &&
+
+ head -n 10 two >merged &&
+ cp one merge-me &&
+ >empty &&
+ test_must_fail git merge-file \
+ -L "Temporary merge branch 2" \
+ -L "" \
+ -L "Temporary merge branch 1" \
+ merged empty merge-me &&
+ test $(git rev-parse :1:three) = $(git hash-object merged)
+'
+
+#
+# For the next test, we start with three commits in two lines of development
+# which setup a rename/add conflict:
+# Commit A: File 'a' exists
+# Commit B: Rename 'a' -> 'new_a'
+# Commit C: Modify 'a', create different 'new_a'
+# Later, two different people merge and resolve differently:
+# Commit D: Merge B & C, ignoring separately created 'new_a'
+# Commit E: Merge B & C making use of some piece of secondary 'new_a'
+# Finally, someone goes to merge D & E. Does git detect the conflict?
+#
+# B D
+# o---o
+# / \ / \
+# A o X ? F
+# \ / \ /
+# o---o
+# C E
+#
+
+test_expect_success 'setup differently handled merges of rename/add conflict' '
+ git rm -rf . &&
+ git clean -fdqx &&
+ rm -rf .git &&
+ git init &&
+
+ printf "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n" >a &&
+ git add a &&
+ test_tick && git commit -m A &&
+
+ git branch B &&
+ git checkout -b C &&
+ echo 10 >>a &&
+ echo "other content" >>new_a &&
+ git add a new_a &&
+ test_tick && git commit -m C &&
+
+ git checkout B &&
+ git mv a new_a &&
+ test_tick && git commit -m B &&
+
+ git checkout B^0 &&
+ test_must_fail git merge C &&
+ git clean -f &&
+ test_tick && git commit -m D &&
+ git tag D &&
+
+ git checkout C^0 &&
+ test_must_fail git merge B &&
+ rm new_a~HEAD new_a &&
+ printf "Incorrectly merged content" >>new_a &&
+ git add -u &&
+ test_tick && git commit -m E &&
+ git tag E
+'
+
+test_expect_success 'git detects differently handled merges conflict' '
+ git reset --hard &&
+ git checkout D^0 &&
+
+ git merge -s recursive E^0 && {
+ echo "BAD: should have conflicted"
+ test "Incorrectly merged content" = "$(cat new_a)" &&
+ echo "BAD: Silently accepted wrong content"
+ return 1
+ }
+
+ test 3 = $(git ls-files -s | wc -l) &&
+ test 3 = $(git ls-files -u | wc -l) &&
+ test 0 = $(git ls-files -o | wc -l) &&
+
+ test $(git rev-parse :2:new_a) = $(git rev-parse D:new_a) &&
+ test $(git rev-parse :3:new_a) = $(git rev-parse E:new_a) &&
+
+ git cat-file -p B:new_a >>merged &&
+ git cat-file -p C:new_a >>merge-me &&
+ >empty &&
+ test_must_fail git merge-file \
+ -L "Temporary merge branch 2" \
+ -L "" \
+ -L "Temporary merge branch 1" \
+ merged empty merge-me &&
+ test $(git rev-parse :1:new_a) = $(git hash-object merged)
'
test_done
index 52d0dc4bb8f80393c79935f9c951b5323287e41c..d9c2d386ddf8caff4b87fa457c23757f76c293c7 100755 (executable)
. ./test-lib.sh
+test_have_prereq SED_STRIPS_CR && SED_OPTIONS=-b
+
test_expect_success setup '
git config core.autocrlf false &&
test_expect_success 'set up fuzz_conflict() helper' '
fuzz_conflict() {
- sed -e "s/^\([<>=]......\) .*/\1/" "$@"
+ sed $SED_OPTIONS -e "s/^\([<>=]......\) .*/\1/" "$@"
}
'
index 1785e178a4cb8fddd58d1b1db8062cf12825155e..1e0447f615c55ecf98ae341553ea60f10a956ae3 100755 (executable)
--- a/t/t6040-tracking-info.sh
+++ b/t/t6040-tracking-info.sh
test_expect_success 'checkout with local tracked branch' '
git checkout master &&
- git checkout follower >actual
+ git checkout follower >actual &&
grep "is ahead of" actual
'
diff --git a/t/t6050-replace.sh b/t/t6050-replace.sh
index 95b180f4693658504fa44d8199efc2a31e85b43d..ae2194e07d835aa088a1c5b5f5fa5001f41ea7d5 100755 (executable)
--- a/t/t6050-replace.sh
+++ b/t/t6050-replace.sh
echo "line 12" >> hello &&
echo "line 13" >> hello &&
add_and_commit_file hello "2 more lines" &&
- HASH6=$(git rev-parse --verify HEAD)
+ HASH6=$(git rev-parse --verify HEAD) &&
echo "line 14" >> hello &&
echo "line 15" >> hello &&
echo "line 16" >> hello &&
index 71f6cad3c2b8e8ed0b16d292f4922760901702ef..9a168069217ef8d82173e563a04eaefe58d99f2a 100755 (executable)
--- a/t/t6200-fmt-merge-msg.sh
+++ b/t/t6200-fmt-merge-msg.sh
test_cmp expected actual2
'
+test_expect_success 'setup: clear [merge] configuration' '
+ test_might_fail git config --unset-all merge.log &&
+ test_might_fail git config --unset-all merge.summary
+'
+
+test_expect_success 'setup FETCH_HEAD' '
+ git checkout master &&
+ test_tick &&
+ git fetch . left
+'
+
+test_expect_success 'merge.log=3 limits shortlog length' '
+ cat >expected <<-EOF &&
+ Merge branch ${apos}left${apos}
+
+ * left: (5 commits)
+ Left #5
+ Left #4
+ Left #3
+ ...
+ EOF
+
+ git -c merge.log=3 fmt-merge-msg <.git/FETCH_HEAD >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'merge.log=5 shows all 5 commits' '
+ cat >expected <<-EOF &&
+ Merge branch ${apos}left${apos}
+
+ * left:
+ Left #5
+ Left #4
+ Left #3
+ Common #2
+ Common #1
+ EOF
+
+ git -c merge.log=5 fmt-merge-msg <.git/FETCH_HEAD >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'merge.log=0 disables shortlog' '
+ echo "Merge branch ${apos}left${apos}" >expected
+ git -c merge.log=0 fmt-merge-msg <.git/FETCH_HEAD >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success '--log=3 limits shortlog length' '
+ cat >expected <<-EOF &&
+ Merge branch ${apos}left${apos}
+
+ * left: (5 commits)
+ Left #5
+ Left #4
+ Left #3
+ ...
+ EOF
+
+ git fmt-merge-msg --log=3 <.git/FETCH_HEAD >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success '--log=5 shows all 5 commits' '
+ cat >expected <<-EOF &&
+ Merge branch ${apos}left${apos}
+
+ * left:
+ Left #5
+ Left #4
+ Left #3
+ Common #2
+ Common #1
+ EOF
+
+ git fmt-merge-msg --log=5 <.git/FETCH_HEAD >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success '--no-log disables shortlog' '
+ echo "Merge branch ${apos}left${apos}" >expected &&
+ git fmt-merge-msg --no-log <.git/FETCH_HEAD >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success '--log=0 disables shortlog' '
+ echo "Merge branch ${apos}left${apos}" >expected &&
+ git fmt-merge-msg --no-log <.git/FETCH_HEAD >actual &&
+ test_cmp expected actual
+'
+
test_expect_success 'fmt-merge-msg -m' '
echo "Sync with left" >expected &&
cat >expected.log <<-EOF &&
diff --git a/t/t6500-gc.sh b/t/t6500-gc.sh
--- /dev/null
+++ b/t/t6500-gc.sh
@@ -0,0 +1,28 @@
+#!/bin/sh
+
+test_description='basic git gc tests
+'
+
+. ./test-lib.sh
+
+test_expect_success 'gc empty repository' '
+ git gc
+'
+
+test_expect_success 'gc --gobbledegook' '
+ test_expect_code 129 git gc --nonsense 2>err &&
+ grep "[Uu]sage: git gc" err
+'
+
+test_expect_success 'gc -h with invalid configuration' '
+ mkdir broken &&
+ (
+ cd broken &&
+ git init &&
+ echo "[gc] pruneexpire = CORRUPT" >>.git/config &&
+ test_expect_code 129 git gc -h >usage 2>&1
+ ) &&
+ grep "[Uu]sage" broken/usage
+'
+
+test_done
diff --git a/t/t7001-mv.sh b/t/t7001-mv.sh
index 65a35d94a001b555ce9e4d6c528d588339a5300b..a845b154e4db50b52eb67eaefefcc42403c52c0e 100755 (executable)
--- a/t/t7001-mv.sh
+++ b/t/t7001-mv.sh
test_expect_success \
'checking -f on untracked file with existing target' \
'touch path0/untracked1 &&
- git mv -f untracked1 path0
+ test_must_fail git mv -f untracked1 path0 &&
test ! -f .git/index.lock &&
test -f untracked1 &&
test -f path0/untracked1'
git init &&
echo 1 >dirty &&
git add dirty &&
- entry="$(git ls-files --stage dirty | cut -f 1)"
+ entry="$(git ls-files --stage dirty | cut -f 1)" &&
git mv dirty dirty2 &&
[ "$entry" = "$(git ls-files --stage dirty2 | cut -f 1)" ] &&
echo 2 >dirty2 &&
diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh
index 6841c23c0a276934928aed65aef4a55cb55370e1..3e7baaf89f9bada46fc084e568544ce9df833594 100755 (executable)
--- a/t/t7004-tag.sh
+++ b/t/t7004-tag.sh
test_expect_success \
'message in editor has initial comment' '
- GIT_EDITOR=cat git tag -a initial-comment > actual
+ ! (GIT_EDITOR=cat git tag -a initial-comment > actual)
+'
+
+test_expect_success \
+ 'message in editor has initial comment: first line' '
# check the first line --- should be empty
- first=$(sed -e 1q <actual) &&
- test -z "$first" &&
+ echo >first.expect &&
+ sed -e 1q <actual >first.actual &&
+ test_cmp first.expect first.actual
+'
+
+test_expect_success \
+ 'message in editor has initial comment: remainder' '
# remove commented lines from the remainder -- should be empty
- rest=$(sed -e 1d -e '/^#/d' <actual) &&
- test -z "$rest"
+ >rest.expect
+ sed -e 1d -e '/^#/d' <actual >rest.actual &&
+ test_cmp rest.expect rest.actual
'
get_tag_header reuse $commit commit $time >expect
test_expect_success 'creating second commit and tag' '
echo foo-2.0 >foo &&
git add foo &&
- git commit -m second
+ git commit -m second &&
git tag v2.0
'
EOF
test_expect_success 'checking that first commit is in all tags (hash)' "
- git tag -l --contains $hash1 v* >actual
+ git tag -l --contains $hash1 v* >actual &&
test_cmp expected actual
"
# other ways of specifying the commit
test_expect_success 'checking that first commit is in all tags (tag)' "
- git tag -l --contains v1.0 v* >actual
+ git tag -l --contains v1.0 v* >actual &&
test_cmp expected actual
"
test_expect_success 'checking that first commit is in all tags (relative)' "
- git tag -l --contains HEAD~2 v* >actual
+ git tag -l --contains HEAD~2 v* >actual &&
test_cmp expected actual
"
EOF
test_expect_success 'checking that second commit only has one tag' "
- git tag -l --contains $hash2 v* >actual
+ git tag -l --contains $hash2 v* >actual &&
test_cmp expected actual
"
EOF
test_expect_success 'checking that third commit has no tags' "
- git tag -l --contains $hash3 v* >actual
+ git tag -l --contains $hash3 v* >actual &&
test_cmp expected actual
"
git branch stable v2.0 &&
git checkout stable &&
echo foo-3.0 > foo &&
- git commit foo -m fourth
+ git commit foo -m fourth &&
git tag v3.0
'
EOF
test_expect_success 'checking that branch head only has one tag' "
- git tag -l --contains $hash4 v* >actual
+ git tag -l --contains $hash4 v* >actual &&
test_cmp expected actual
"
EOF
test_expect_success 'checking that original branch head has one tag now' "
- git tag -l --contains $hash3 v* >actual
+ git tag -l --contains $hash3 v* >actual &&
test_cmp expected actual
"
EOF
test_expect_success 'checking that initial commit is in all tags' "
- git tag -l --contains $hash1 v* >actual
+ git tag -l --contains $hash1 v* >actual &&
test_cmp expected actual
"
# mixing modes and options:
test_expect_success 'mixing incompatibles modes and options is forbidden' '
- test_must_fail git tag -a
- test_must_fail git tag -l -v
- test_must_fail git tag -n 100
- test_must_fail git tag -l -m msg
- test_must_fail git tag -l -F some file
+ test_must_fail git tag -a &&
+ test_must_fail git tag -l -v &&
+ test_must_fail git tag -n 100 &&
+ test_must_fail git tag -l -m msg &&
+ test_must_fail git tag -l -F some file &&
test_must_fail git tag -v -s
'
diff --git a/t/t7006-pager.sh b/t/t7006-pager.sh
index fb744e3c4a9a19d9285fc04053044242ffed0c65..ed7575d0fdf9eebf65d808fa810ab4cc03095025 100755 (executable)
--- a/t/t7006-pager.sh
+++ b/t/t7006-pager.sh
. ./test-lib.sh
. "$TEST_DIRECTORY"/lib-pager.sh
+. "$TEST_DIRECTORY"/lib-terminal.sh
cleanup_fail() {
echo >&2 cleanup failed
(exit 1)
}
-test_expect_success 'set up terminal for tests' '
- rm -f stdout_is_tty ||
- cleanup_fail &&
-
- if test -t 1
- then
- >stdout_is_tty
- elif
- test_have_prereq PERL &&
- "$PERL_PATH" "$TEST_DIRECTORY"/t7006/test-terminal.perl \
- sh -c "test -t 1"
- then
- >test_terminal_works
- fi
-'
-
-if test -e stdout_is_tty
-then
- test_terminal() { "$@"; }
- test_set_prereq TTY
-elif test -e test_terminal_works
-then
- test_terminal() {
- "$PERL_PATH" "$TEST_DIRECTORY"/t7006/test-terminal.perl "$@"
- }
- test_set_prereq TTY
-else
- say "# no usable terminal, so skipping some tests"
-fi
-
test_expect_success 'setup' '
- unset GIT_PAGER GIT_PAGER_IN_USE;
+ sane_unset GIT_PAGER GIT_PAGER_IN_USE &&
test_might_fail git config --unset core.pager &&
PAGER="cat >paginated.out" &&
colorful colorful.log
'
-if test_have_prereq SIMPLEPAGER && test_have_prereq TTY
-then
- test_set_prereq SIMPLEPAGERTTY
-fi
-
# Use this helper to make it easy for the caller of your
# terminal-using function to specify whether it should fail.
# If you write
test_default_pager() {
parse_args "$@"
- $test_expectation SIMPLEPAGERTTY "$cmd - default pager is used by default" "
- unset PAGER GIT_PAGER;
+ $test_expectation SIMPLEPAGER,TTY "$cmd - default pager is used by default" "
+ sane_unset PAGER GIT_PAGER &&
test_might_fail git config --unset core.pager &&
rm -f default_pager_used ||
cleanup_fail &&
parse_args "$@"
$test_expectation TTY "$cmd - PAGER overrides default pager" "
- unset GIT_PAGER;
+ sane_unset GIT_PAGER &&
test_might_fail git config --unset core.pager &&
rm -f PAGER_used ||
cleanup_fail &&
parse_args "$@"
$test_expectation TTY "$cmd - repository-local core.pager setting $used_if_wanted" "
- unset GIT_PAGER;
+ sane_unset GIT_PAGER &&
rm -f core.pager_used ||
cleanup_fail &&
parse_args "$@"
$test_expectation TTY "$cmd - core.pager $used_if_wanted from subdirectory" "
- unset GIT_PAGER;
+ sane_unset GIT_PAGER &&
rm -f core.pager_used &&
rm -fr sub ||
cleanup_fail &&
test_core_pager_subdir expect_success test_must_fail \
'git -p apply </dev/null'
+test_expect_success TTY 'command-specific pager' '
+ unset PAGER GIT_PAGER;
+ echo "foo:initial" >expect &&
+ >actual &&
+ git config --unset core.pager &&
+ git config pager.log "sed s/^/foo:/ >actual" &&
+ test_terminal git log --format=%s -1 &&
+ test_cmp expect actual
+'
+
+test_expect_success TTY 'command-specific pager overrides core.pager' '
+ unset PAGER GIT_PAGER;
+ echo "foo:initial" >expect &&
+ >actual &&
+ git config core.pager "exit 1"
+ git config pager.log "sed s/^/foo:/ >actual" &&
+ test_terminal git log --format=%s -1 &&
+ test_cmp expect actual
+'
+
+test_expect_success TTY 'command-specific pager overridden by environment' '
+ GIT_PAGER="sed s/^/foo:/ >actual" && export GIT_PAGER &&
+ >actual &&
+ echo "foo:initial" >expect &&
+ git config pager.log "exit 1" &&
+ test_terminal git log --format=%s -1 &&
+ test_cmp expect actual
+'
+
test_done
diff --git a/t/t7006/test-terminal.perl b/t/t7006/test-terminal.perl
+++ /dev/null
@@ -1,59 +0,0 @@
-#!/usr/bin/perl
-use 5.008;
-use strict;
-use warnings;
-use IO::Pty;
-use File::Copy;
-
-# Run @$argv in the background with stdout redirected to $out.
-sub start_child {
- my ($argv, $out) = @_;
- my $pid = fork;
- if (not defined $pid) {
- die "fork failed: $!"
- } elsif ($pid == 0) {
- open STDOUT, ">&", $out;
- close $out;
- exec(@$argv) or die "cannot exec '$argv->[0]': $!"
- }
- return $pid;
-}
-
-# Wait for $pid to finish.
-sub finish_child {
- # Simplified from wait_or_whine() in run-command.c.
- my ($pid) = @_;
-
- my $waiting = waitpid($pid, 0);
- if ($waiting < 0) {
- die "waitpid failed: $!";
- } elsif ($? & 127) {
- my $code = $? & 127;
- warn "died of signal $code";
- return $code - 128;
- } else {
- return $? >> 8;
- }
-}
-
-sub xsendfile {
- my ($out, $in) = @_;
-
- # Note: the real sendfile() cannot read from a terminal.
-
- # It is unspecified by POSIX whether reads
- # from a disconnected terminal will return
- # EIO (as in AIX 4.x, IRIX, and Linux) or
- # end-of-file. Either is fine.
- copy($in, $out, 4096) or $!{EIO} or die "cannot copy from child: $!";
-}
-
-if ($#ARGV < 1) {
- die "usage: test-terminal program args";
-}
-my $master = new IO::Pty;
-my $slave = $master->slave;
-my $pid = start_child(\@ARGV, $slave);
-close $slave;
-xsendfile(\*STDOUT, $master);
-exit(finish_child($pid));
diff --git a/t/t7105-reset-patch.sh b/t/t7105-reset-patch.sh
index 9891e2c1f5e5da357df795ee05969fbde72887ba..95fab2036109c5a4aef8ff3d8100403fe335819f 100755 (executable)
--- a/t/t7105-reset-patch.sh
+++ b/t/t7105-reset-patch.sh
# note: bar sorts before foo, so the first 'n' is always to skip 'bar'
test_expect_success PERL 'saying "n" does nothing' '
- set_and_save_state dir/foo work work
+ set_and_save_state dir/foo work work &&
(echo n; echo n) | git reset -p &&
verify_saved_state dir/foo &&
verify_saved_state bar
# the failure case (and thus get out of the loop).
test_expect_success PERL 'git reset -p dir' '
- set_state dir/foo work work
+ set_state dir/foo work work &&
(echo y; echo n) | git reset -p dir &&
verify_state dir/foo work head &&
verify_saved_state bar
'
test_expect_success PERL 'git reset -p -- foo (inside dir)' '
- set_state dir/foo work work
+ set_state dir/foo work work &&
(echo y; echo n) | (cd dir && git reset -p -- foo) &&
verify_state dir/foo work head &&
verify_saved_state bar
diff --git a/t/t7300-clean.sh b/t/t7300-clean.sh
index 6c776e9bec78ab1cf960293d8c796e9de13045b5..02f67b73b762850f3c7f0faf0886bf4011f57308 100755 (executable)
--- a/t/t7300-clean.sh
+++ b/t/t7300-clean.sh
'
-test_expect_success 'git clean symbolic link' '
+test_expect_success SYMLINKS 'git clean symbolic link' '
mkdir -p build docs &&
touch a.out src/part3.c docs/manual.txt obj.o build/lib.so &&
- ln -s docs/manual.txt src/part4.c
+ ln -s docs/manual.txt src/part4.c &&
git clean &&
test -f Makefile &&
test -f README &&
index 294584452bbc3226e631ce0b81c546b04cd8d992..7d7fde057b04e4615e32b0ddc71fd3b07968a129 100755 (executable)
test_expect_success 'added submodule' "
git add sm1 &&
git submodule summary >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
* sm1 0000000...$head1 (2):
> Add foo2
EOF
+ test_cmp expected actual
"
commit_file sm1 &&
test_expect_success 'modified submodule(forward)' "
git submodule summary >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
* sm1 $head1...$head2 (1):
> Add foo3
EOF
+ test_cmp expected actual
"
test_expect_success 'modified submodule(forward), --files' "
git submodule summary --files >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
* sm1 $head1...$head2 (1):
> Add foo3
EOF
+ test_cmp expected actual
"
commit_file sm1 &&
test_expect_success 'modified submodule(backward)' "
git submodule summary >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
* sm1 $head2...$head3 (2):
< Add foo3
< Add foo2
EOF
+ test_cmp expected actual
"
head4=$(add_file sm1 foo4 foo5) &&
head4_full=$(GIT_DIR=sm1/.git git rev-parse --verify HEAD)
test_expect_success 'modified submodule(backward and forward)' "
git submodule summary >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
* sm1 $head2...$head4 (4):
> Add foo5
> Add foo4
< Add foo2
EOF
+ test_cmp expected actual
"
test_expect_success '--summary-limit' "
git submodule summary -n 3 >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
* sm1 $head2...$head4 (4):
> Add foo5
> Add foo4
< Add foo3
EOF
+ test_cmp expected actual
"
commit_file sm1 &&
test_expect_success 'typechanged submodule(submodule->blob), --cached' "
git submodule summary --cached >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
* sm1 $head4(submodule)->$head5(blob) (3):
< Add foo5
EOF
+ test_cmp actual expected
"
test_expect_success 'typechanged submodule(submodule->blob), --files' "
git submodule summary --files >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
* sm1 $head5(blob)->$head4(submodule) (3):
> Add foo5
EOF
+ test_cmp actual expected
"
rm -rf sm1 &&
git checkout-index sm1
test_expect_success 'typechanged submodule(submodule->blob)' "
git submodule summary >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
* sm1 $head4(submodule)->$head5(blob):
EOF
+ test_cmp actual expected
"
rm -f sm1 &&
head6=$(add_file sm1 foo6 foo7)
test_expect_success 'nonexistent commit' "
git submodule summary >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
* sm1 $head4...$head6:
Warn: sm1 doesn't contain commit $head4_full
EOF
+ test_cmp actual expected
"
commit_file
test_expect_success 'typechanged submodule(blob->submodule)' "
git submodule summary >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
* sm1 $head5(blob)->$head6(submodule) (2):
> Add foo7
EOF
+ test_cmp expected actual
"
commit_file sm1 &&
rm -rf sm1
test_expect_success 'deleted submodule' "
git submodule summary >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
* sm1 $head6...0000000:
EOF
+ test_cmp expected actual
"
test_create_repo sm2 &&
test_expect_success 'multiple submodules' "
git submodule summary >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
* sm1 $head6...0000000:
* sm2 0000000...$head7 (2):
> Add foo9
EOF
+ test_cmp expected actual
"
test_expect_success 'path filter' "
git submodule summary sm2 >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
* sm2 0000000...$head7 (2):
> Add foo9
EOF
+ test_cmp expected actual
"
commit_file sm2
test_expect_success 'given commit' "
git submodule summary HEAD^ >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
* sm1 $head6...0000000:
* sm2 0000000...$head7 (2):
> Add foo9
EOF
+ test_cmp expected actual
"
test_expect_success '--for-status' "
index 905a8baae9a03a4b47512bb3eb3e0f9ec84f5ca4..e5be13c271c92ce7c51601ee1eaf966d849d8ae4 100755 (executable)
test_cmp expect actual
'
+sed -e "/nested1 /s/.*/+$nested1sha1 nested1 (file2~1)/;/sub[1-3]/d" < expect > expect2
+mv -f expect2 expect
+
+test_expect_success 'ensure "status --cached --recursive" preserves the --cached flag' '
+ (
+ cd clone3 &&
+ (
+ cd nested1 &&
+ test_commit file2
+ ) &&
+ git submodule status --cached --recursive -- nested1 > ../actual
+ ) &&
+ if test_have_prereq MINGW
+ then
+ dos2unix actual
+ fi &&
+ test_cmp expect actual
+'
+
test_expect_success 'use "git clone --recursive" to checkout all submodules' '
git clone --recursive super clone4 &&
test -d clone4/.git &&
test -d clone4/nested1/nested2/nested3/submodule/.git
'
+test_expect_success 'test "update --recursive" with a flag with spaces' '
+ git clone super "common objects" &&
+ git clone super clone5 &&
+ (
+ cd clone5 &&
+ test ! -d nested1/.git &&
+ git submodule update --init --recursive --reference="$(dirname "$PWD")/common objects" &&
+ test -d nested1/.git &&
+ test -d nested1/nested2/.git &&
+ test -d nested1/nested2/nested3/.git &&
+ test -f nested1/.git/objects/info/alternates &&
+ test -f nested1/nested2/.git/objects/info/alternates &&
+ test -f nested1/nested2/nested3/.git/objects/info/alternates
+ )
+'
+
+test_expect_success 'use "update --recursive nested1" to checkout all submodules rooted in nested1' '
+ git clone super clone6 &&
+ (
+ cd clone6 &&
+ test ! -d sub1/.git &&
+ test ! -d sub2/.git &&
+ test ! -d sub3/.git &&
+ test ! -d nested1/.git &&
+ git submodule update --init --recursive -- nested1 &&
+ test ! -d sub1/.git &&
+ test ! -d sub2/.git &&
+ test ! -d sub3/.git &&
+ test -d nested1/.git &&
+ test -d nested1/nested2/.git &&
+ test -d nested1/nested2/nested3/.git &&
+ test -d nested1/nested2/nested3/submodule/.git
+ )
+'
+
test_done
diff --git a/t/t7500-commit.sh b/t/t7500-commit.sh
index 1590877c425c4ae4b884770ca812de7f1a5f359f..5976f598fc7bf48120865eca3ceef74a151e717a 100755 (executable)
--- a/t/t7500-commit.sh
+++ b/t/t7500-commit.sh
. ./test-lib.sh
commit_msg_is () {
- test "`git log --pretty=format:%s%b -1`" = "$1"
+ expect=commit_msg_is.expect
+ actual=commit_msg_is.actual
+
+ printf "%s" "$(git log --pretty=format:%s%b -1)" >$expect &&
+ printf "%s" "$1" >$actual &&
+ test_cmp $expect $actual
}
# A sanity check to see if commit is working at all.
commit_msg_is "hello there"
'
+commit_for_rebase_autosquash_setup () {
+ echo "first content line" >>foo &&
+ git add foo &&
+ cat >log <<EOF &&
+target message subject line
+
+target message body line 1
+target message body line 2
+EOF
+ git commit -F log &&
+ echo "second content line" >>foo &&
+ git add foo &&
+ git commit -m "intermediate commit" &&
+ echo "third content line" >>foo &&
+ git add foo
+}
+
+test_expect_success 'commit --fixup provides correct one-line commit message' '
+ commit_for_rebase_autosquash_setup &&
+ git commit --fixup HEAD~1 &&
+ commit_msg_is "fixup! target message subject line"
+'
+
+test_expect_success 'commit --squash works with -F' '
+ commit_for_rebase_autosquash_setup &&
+ echo "log message from file" >msgfile &&
+ git commit --squash HEAD~1 -F msgfile &&
+ commit_msg_is "squash! target message subject linelog message from file"
+'
+
+test_expect_success 'commit --squash works with -m' '
+ commit_for_rebase_autosquash_setup &&
+ git commit --squash HEAD~1 -m "foo bar\nbaz" &&
+ commit_msg_is "squash! target message subject linefoo bar\nbaz"
+'
+
+test_expect_success 'commit --squash works with -C' '
+ commit_for_rebase_autosquash_setup &&
+ git commit --squash HEAD~1 -C HEAD &&
+ commit_msg_is "squash! target message subject lineintermediate commit"
+'
+
+test_expect_success 'commit --squash works with -c' '
+ commit_for_rebase_autosquash_setup &&
+ test_set_editor "$TEST_DIRECTORY"/t7500/edit-content &&
+ git commit --squash HEAD~1 -c HEAD &&
+ commit_msg_is "squash! target message subject lineedited commit"
+'
+
+test_expect_success 'commit --squash works with -C for same commit' '
+ commit_for_rebase_autosquash_setup &&
+ git commit --squash HEAD -C HEAD &&
+ commit_msg_is "squash! intermediate commit"
+'
+
+test_expect_success 'commit --squash works with -c for same commit' '
+ commit_for_rebase_autosquash_setup &&
+ test_set_editor "$TEST_DIRECTORY"/t7500/edit-content &&
+ git commit --squash HEAD -c HEAD &&
+ commit_msg_is "squash! edited commit"
+'
+
+test_expect_success 'commit --squash works with editor' '
+ commit_for_rebase_autosquash_setup &&
+ test_set_editor "$TEST_DIRECTORY"/t7500/add-content &&
+ git commit --squash HEAD~1 &&
+ commit_msg_is "squash! target message subject linecommit message"
+'
+
+test_expect_success 'invalid message options when using --fixup' '
+ echo changes >>foo &&
+ echo "message" >log &&
+ git add foo &&
+ test_must_fail git commit --fixup HEAD~1 --squash HEAD~2 &&
+ test_must_fail git commit --fixup HEAD~1 -C HEAD~2 &&
+ test_must_fail git commit --fixup HEAD~1 -c HEAD~2 &&
+ test_must_fail git commit --fixup HEAD~1 -m "cmdline message" &&
+ test_must_fail git commit --fixup HEAD~1 -F log
+'
+
test_done
diff --git a/t/t7500/edit-content b/t/t7500/edit-content
--- /dev/null
+++ b/t/t7500/edit-content
@@ -0,0 +1,4 @@
+#!/bin/sh
+sed -e "s/intermediate/edited/g" <"$1" >"$1-"
+mv "$1-" "$1"
+exit 0
diff --git a/t/t7502-commit.sh b/t/t7502-commit.sh
index ac2e187a5720d1ff947e58073dd6cc403ef40d5d..50da034cd3934d0509e67a6f20e514a18e5659d4 100755 (executable)
--- a/t/t7502-commit.sh
+++ b/t/t7502-commit.sh
echo >>negative &&
(
- unset GIT_COMMITTER_EMAIL
- unset GIT_COMMITTER_NAME
+ sane_unset GIT_COMMITTER_EMAIL &&
+ sane_unset GIT_COMMITTER_NAME &&
# must fail because there is no change
test_must_fail git commit -e -m "sample"
) &&
test_expect_success 'commit --no-status' '
clear_config commit.status &&
- try_commit --no-status
+ try_commit --no-status &&
! grep "^# Changes to be committed:" .git/COMMIT_EDITMSG
'
index ff189624d48fb9f68997395d121d4e7056511245..5b4b694f1801f5c2284346f882cb496df9e7d74e 100755 (executable)
'
+test_expect_success 'with hook (merge)' '
+
+ head=`git rev-parse HEAD` &&
+ git checkout -b other HEAD@{1} &&
+ echo "more" >> file &&
+ git add file &&
+ git commit -m other &&
+ git checkout - &&
+ git merge other &&
+ test "`git log -1 --pretty=format:%s`" = merge
+'
+
cat > "$HOOK" <<'EOF'
#!/bin/sh
exit 1
diff --git a/t/t7508-status.sh b/t/t7508-status.sh
index c9300f3c8b246e3c81570532534748e510a2865f..f1dc5c3b6a36507e664e624bf9cebb1634bce335 100755 (executable)
--- a/t/t7508-status.sh
+++ b/t/t7508-status.sh
. ./test-lib.sh
+test_expect_success 'status -h in broken repository' '
+ mkdir broken &&
+ test_when_finished "rm -fr broken" &&
+ (
+ cd broken &&
+ git init &&
+ echo "[status] showuntrackedfiles = CORRUPT" >>.git/config &&
+ test_expect_code 129 git status -h >usage 2>&1
+ ) &&
+ grep "[Uu]sage" broken/usage
+'
+
+test_expect_success 'commit -h in broken repository' '
+ mkdir broken &&
+ test_when_finished "rm -fr broken" &&
+ (
+ cd broken &&
+ git init &&
+ echo "[status] showuntrackedfiles = CORRUPT" >>.git/config &&
+ test_expect_code 129 git commit -h >usage 2>&1
+ ) &&
+ grep "[Uu]sage" broken/usage
+'
+
test_expect_success 'setup' '
: >tracked &&
: >modified &&
#
# new file: dir2/added
#
-# Changed but not updated:
+# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
# Changes to be committed:
# new file: dir2/added
#
-# Changed but not updated:
+# Changes not staged for commit:
# modified: dir1/modified
#
# Untracked files:
#
# new file: dir2/added
#
-# Changed but not updated:
+# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
# Changes to be committed:
# new file: dir2/added
#
-# Changed but not updated:
+# Changes not staged for commit:
# modified: dir1/modified
#
# Untracked files not listed
#
# new file: dir2/added
#
-# Changed but not updated:
+# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
#
# new file: dir2/added
#
-# Changed but not updated:
+# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
#
# new file: ../dir2/added
#
-# Changed but not updated:
+# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
test_expect_success 'setup unique colors' '
- git config status.color.untracked blue
+ git config status.color.untracked blue &&
+ git config status.color.branch green
'
cat >expect <<\EOF
-# On branch master
+# On branch <GREEN>master<RESET>
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# <GREEN>new file: dir2/added<RESET>
#
-# Changed but not updated:
+# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
#
# new file: dir2/added
#
-# Changed but not updated:
+# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
# new file: dir2/added
# new file: sm
#
-# Changed but not updated:
+# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
# new file: dir2/added
# new file: sm
#
-# Changed but not updated:
+# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
cat >expect <<EOF
# On branch master
-# Changed but not updated:
+# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
# new file: dir2/added
# new file: sm
#
-# Changed but not updated:
+# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
#
# modified: sm
#
-# Changed but not updated:
+# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
#
# modified: sm
#
-# Changed but not updated:
+# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
# (commit or discard the untracked or modified content in submodules)
#
# modified: sm
#
-# Changed but not updated:
+# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
@@ -1067,7 +1092,7 @@ test_expect_success ".git/config ignore=dirty doesn't suppress submodule summary
cat > expect << EOF
# On branch master
-# Changed but not updated:
+# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
diff --git a/t/t7509-commit.sh b/t/t7509-commit.sh
index 643ab03f992be8339b169b3bef0922ff3cdb04b9..77b69200297e222319b5701c62312bee27b62ce9 100755 (executable)
--- a/t/t7509-commit.sh
+++ b/t/t7509-commit.sh
test_tick &&
git commit -a -C Initial --reset-author &&
echo "author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE" >expect &&
- author_header HEAD >actual
+ author_header HEAD >actual &&
test_cmp expect actual &&
message_body Initial >expect &&
diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh
index b4f40e4c3a53388e72347e8e3894edd05f17dab5..b147a1bd69e96e48d3808028a3775a609471639a 100755 (executable)
--- a/t/t7600-merge.sh
+++ b/t/t7600-merge.sh
test_must_fail git merge
'
+test_expect_success 'merge -h with invalid index' '
+ mkdir broken &&
+ (
+ cd broken &&
+ git init &&
+ >.git/index &&
+ test_expect_code 129 git merge -h 2>usage
+ ) &&
+ grep "[Uu]sage: git merge" broken/usage
+'
+
test_expect_success 'reject non-strategy with a git-merge-foo name' '
test_must_fail git merge -s index c1
'
index 7ba94ea99bc0785da5b398e494d23469ba44992d..b44b29395049308b30e61b00a55e2eb39f4355a1 100755 (executable)
test_expect_success 'merge picks up the best result' '
git config --unset-all pull.twohead &&
git reset --hard c5 &&
- git merge -s resolve c6
+ test_must_fail git merge -s resolve c6 &&
resolve_count=$(conflict_count) &&
git reset --hard c5 &&
- git merge -s recursive c6
+ test_must_fail git merge -s recursive c6 &&
recursive_count=$(conflict_count) &&
git reset --hard c5 &&
- git merge -s recursive -s resolve c6
+ test_must_fail git merge -s recursive -s resolve c6 &&
auto_count=$(conflict_count) &&
test $auto_count = $recursive_count &&
test $auto_count != $resolve_count
test_expect_success 'merge picks up the best result (from config)' '
git config pull.twohead "recursive resolve" &&
git reset --hard c5 &&
- git merge -s resolve c6
+ test_must_fail git merge -s resolve c6 &&
resolve_count=$(conflict_count) &&
git reset --hard c5 &&
- git merge -s recursive c6
+ test_must_fail git merge -s recursive c6 &&
recursive_count=$(conflict_count) &&
git reset --hard c5 &&
- git merge c6
+ test_must_fail git merge c6 &&
auto_count=$(conflict_count) &&
test $auto_count = $recursive_count &&
test $auto_count != $resolve_count
index 2746169514f9bd1629f05eed8f9ff2fcfdba8cb5..0a46795ae785fd4dfa0e565bfa0153ca107a904d 100755 (executable)
do
refs="$refs c$i"
i=`expr $i + 1`
- done
+ done &&
git merge $refs &&
test "$(git rev-parse c1)" != "$(git rev-parse HEAD)" &&
i=1 &&
index 4d5ce4e682c1ea69034c3f7789a8ac0c89b12466..5f731a1177267e76f19dbe1605a4d0f2e8e749bd 100755 (executable)
test_cmp important c0.c
'
+test_expect_success 'set up unborn branch and content' '
+ git symbolic-ref HEAD refs/heads/unborn &&
+ rm -f .git/index &&
+ echo foo > tracked-file &&
+ git add tracked-file &&
+ echo bar > untracked-file
+'
+
+test_expect_failure 'will not clobber WT/index when merging into unborn' '
+ git merge master &&
+ grep foo tracked-file &&
+ git show :tracked-file >expect &&
+ grep foo expect &&
+ grep bar untracked-file
+'
+
test_done
index 28d56797b17d5b1426d6c7c36e37b956731f1dfd..9225fa6f025cd9586b46f30464a69ccb57ca6b2a 100755 (executable)
check_oneline "Merge commit QambiguousQ"
'
-test_expect_success 'remote branch' '
+test_expect_success 'remote-tracking branch' '
git checkout -b remote master &&
test_commit remote-1 &&
git update-ref refs/remotes/origin/master remote &&
git checkout master &&
test_commit master-5 &&
git merge origin/master &&
- check_oneline "Merge remote branch Qorigin/masterQ"
+ check_oneline "Merge remote-tracking branch Qorigin/masterQ"
'
test_done
diff --git a/t/t7610-mergetool.sh b/t/t7610-mergetool.sh
index 3bd74042ef46ee8b4e4477d1c23ba0914963de33..dc838c93bcb00a80ad56c4d1233009fe9220b90e 100755 (executable)
--- a/t/t7610-mergetool.sh
+++ b/t/t7610-mergetool.sh
test_expect_success 'setup' '
git config rerere.enabled true &&
echo master >file1 &&
+ echo master file11 >file11 &&
+ echo master file12 >file12 &&
+ echo master file13 >file13 &&
+ echo master file14 >file14 &&
mkdir subdir &&
echo master sub >subdir/file3 &&
- git add file1 subdir/file3 &&
- git commit -m "added file1" &&
+ git add file1 file1[1-4] subdir/file3 &&
+ git commit -m "add initial versions" &&
git checkout -b branch1 master &&
echo branch1 change >file1 &&
echo branch1 newfile >file2 &&
+ echo branch1 change file11 >file11 &&
+ echo branch1 change file13 >file13 &&
echo branch1 sub >subdir/file3 &&
- git add file1 file2 subdir/file3 &&
+ git add file1 file11 file13 file2 subdir/file3 &&
+ git rm file12 &&
git commit -m "branch1 changes" &&
git checkout master &&
echo master updated >file1 &&
echo master new >file2 &&
+ echo master updated file12 >file12 &&
+ echo master updated file14 >file14 &&
echo master new sub >subdir/file3 &&
- git add file1 file2 subdir/file3 &&
+ git add file1 file12 file14 file2 subdir/file3 &&
+ git rm file11 &&
git commit -m "master updates" &&
git config merge.tool mytool &&
( yes "" | git mergetool file1 >/dev/null 2>&1 ) &&
( yes "" | git mergetool file2 >/dev/null 2>&1 ) &&
( yes "" | git mergetool subdir/file3 >/dev/null 2>&1 ) &&
+ ( yes "d" | git mergetool file11 >/dev/null 2>&1 ) &&
+ ( yes "d" | git mergetool file12 >/dev/null 2>&1 ) &&
test "$(cat file1)" = "master updated" &&
test "$(cat file2)" = "master new" &&
test "$(cat subdir/file3)" = "master new sub" &&
test_expect_success 'mergetool crlf' '
git config core.autocrlf true &&
- git checkout -b test2 branch1
+ git checkout -b test2 branch1 &&
test_must_fail git merge master >/dev/null 2>&1 &&
( yes "" | git mergetool file1 >/dev/null 2>&1 ) &&
( yes "" | git mergetool file2 >/dev/null 2>&1 ) &&
( yes "" | git mergetool subdir/file3 >/dev/null 2>&1 ) &&
+ ( yes "d" | git mergetool file11 >/dev/null 2>&1 ) &&
+ ( yes "d" | git mergetool file12 >/dev/null 2>&1 ) &&
test "$(printf x | cat file1 -)" = "$(printf "master updated\r\nx")" &&
test "$(printf x | cat file2 -)" = "$(printf "master new\r\nx")" &&
test "$(printf x | cat subdir/file3 -)" = "$(printf "master new sub\r\nx")" &&
cd subdir &&
( yes "" | git mergetool ../file1 >/dev/null 2>&1 ) &&
( yes "" | git mergetool ../file2 >/dev/null 2>&1 ) &&
+ ( yes "d" | git mergetool ../file11 >/dev/null 2>&1 ) &&
+ ( yes "d" | git mergetool ../file12 >/dev/null 2>&1 ) &&
test "$(cat ../file1)" = "master updated" &&
test "$(cat ../file2)" = "master new" &&
git commit -m "branch1 resolved with mergetool - subdir"
git checkout -b test4 branch1 &&
test_must_fail git merge master &&
test -n "$(git ls-files -u)" &&
+ ( yes "d" | git mergetool file11 >/dev/null 2>&1 ) &&
+ ( yes "d" | git mergetool file12 >/dev/null 2>&1 ) &&
output="$(git mergetool --no-prompt)" &&
test "$output" = "No files need merging" &&
git reset --hard
cd subdir &&
git config rerere.enabled false &&
test_must_fail git merge master &&
- git mergetool --no-prompt &&
+ ( yes "d" "d" | git mergetool --no-prompt ) &&
test "$(cat ../file1)" = "master updated" &&
test "$(cat ../file2)" = "master new" &&
test "$(cat file3)" = "master new sub" &&
- git add ../file1 ../file2 file3 &&
git commit -m "branch2 resolved by mergetool from subdir"
)
'
+test_expect_success 'mergetool skips resolved paths when rerere is active' '
+ git config rerere.enabled true &&
+ rm -rf .git/rr-cache &&
+ git checkout -b test5 branch1
+ test_must_fail git merge master >/dev/null 2>&1 &&
+ ( yes "d" "d" | git mergetool --no-prompt >/dev/null 2>&1 ) &&
+ output="$(yes "n" | git mergetool --no-prompt)" &&
+ test "$output" = "No files need merging" &&
+ git reset --hard
+'
+
test_done
diff --git a/t/t7611-merge-abort.sh b/t/t7611-merge-abort.sh
--- /dev/null
+++ b/t/t7611-merge-abort.sh
@@ -0,0 +1,313 @@
+#!/bin/sh
+
+test_description='test aborting in-progress merges
+
+Set up repo with conflicting and non-conflicting branches:
+
+There are three files foo/bar/baz, and the following graph illustrates the
+content of these files in each commit:
+
+# foo/bar/baz --- foo/bar/bazz <-- master
+# \
+# --- foo/barf/bazf <-- conflict_branch
+# \
+# --- foo/bart/baz <-- clean_branch
+
+Next, test git merge --abort with the following variables:
+- before/after successful merge (should fail when not in merge context)
+- with/without conflicts
+- clean/dirty index before merge
+- clean/dirty worktree before merge
+- dirty index before merge matches contents on remote branch
+- changed/unchanged worktree after merge
+- changed/unchanged index after merge
+'
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+ # Create the above repo
+ echo foo > foo &&
+ echo bar > bar &&
+ echo baz > baz &&
+ git add foo bar baz &&
+ git commit -m initial &&
+ echo bazz > baz &&
+ git commit -a -m "second" &&
+ git checkout -b conflict_branch HEAD^ &&
+ echo barf > bar &&
+ echo bazf > baz &&
+ git commit -a -m "conflict" &&
+ git checkout -b clean_branch HEAD^ &&
+ echo bart > bar &&
+ git commit -a -m "clean" &&
+ git checkout master
+'
+
+pre_merge_head="$(git rev-parse HEAD)"
+
+test_expect_success 'fails without MERGE_HEAD (unstarted merge)' '
+ test_must_fail git merge --abort 2>output &&
+ grep -q MERGE_HEAD output &&
+ test ! -f .git/MERGE_HEAD &&
+ test "$pre_merge_head" = "$(git rev-parse HEAD)"
+'
+
+test_expect_success 'fails without MERGE_HEAD (completed merge)' '
+ git merge clean_branch &&
+ test ! -f .git/MERGE_HEAD &&
+ # Merge successfully completed
+ post_merge_head="$(git rev-parse HEAD)" &&
+ test_must_fail git merge --abort 2>output &&
+ grep -q MERGE_HEAD output &&
+ test ! -f .git/MERGE_HEAD &&
+ test "$post_merge_head" = "$(git rev-parse HEAD)"
+'
+
+test_expect_success 'Forget previous merge' '
+ git reset --hard "$pre_merge_head"
+'
+
+test_expect_success 'Abort after --no-commit' '
+ # Redo merge, but stop before creating merge commit
+ git merge --no-commit clean_branch &&
+ test -f .git/MERGE_HEAD &&
+ # Abort non-conflicting merge
+ git merge --abort &&
+ test ! -f .git/MERGE_HEAD &&
+ test "$pre_merge_head" = "$(git rev-parse HEAD)" &&
+ test -z "$(git diff)" &&
+ test -z "$(git diff --staged)"
+'
+
+test_expect_success 'Abort after conflicts' '
+ # Create conflicting merge
+ test_must_fail git merge conflict_branch &&
+ test -f .git/MERGE_HEAD &&
+ # Abort conflicting merge
+ git merge --abort &&
+ test ! -f .git/MERGE_HEAD &&
+ test "$pre_merge_head" = "$(git rev-parse HEAD)" &&
+ test -z "$(git diff)" &&
+ test -z "$(git diff --staged)"
+'
+
+test_expect_success 'Clean merge with dirty index fails' '
+ echo xyzzy >> foo &&
+ git add foo &&
+ git diff --staged > expect &&
+ test_must_fail git merge clean_branch &&
+ test ! -f .git/MERGE_HEAD &&
+ test "$pre_merge_head" = "$(git rev-parse HEAD)" &&
+ test -z "$(git diff)" &&
+ git diff --staged > actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'Conflicting merge with dirty index fails' '
+ test_must_fail git merge conflict_branch &&
+ test ! -f .git/MERGE_HEAD &&
+ test "$pre_merge_head" = "$(git rev-parse HEAD)" &&
+ test -z "$(git diff)" &&
+ git diff --staged > actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'Reset index (but preserve worktree changes)' '
+ git reset "$pre_merge_head" &&
+ git diff > actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'Abort clean merge with non-conflicting dirty worktree' '
+ git merge --no-commit clean_branch &&
+ test -f .git/MERGE_HEAD &&
+ # Abort merge
+ git merge --abort &&
+ test ! -f .git/MERGE_HEAD &&
+ test "$pre_merge_head" = "$(git rev-parse HEAD)" &&
+ test -z "$(git diff --staged)" &&
+ git diff > actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'Abort conflicting merge with non-conflicting dirty worktree' '
+ test_must_fail git merge conflict_branch &&
+ test -f .git/MERGE_HEAD &&
+ # Abort merge
+ git merge --abort &&
+ test ! -f .git/MERGE_HEAD &&
+ test "$pre_merge_head" = "$(git rev-parse HEAD)" &&
+ test -z "$(git diff --staged)" &&
+ git diff > actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'Reset worktree changes' '
+ git reset --hard "$pre_merge_head"
+'
+
+test_expect_success 'Fail clean merge with conflicting dirty worktree' '
+ echo xyzzy >> bar &&
+ git diff > expect &&
+ test_must_fail git merge --no-commit clean_branch &&
+ test ! -f .git/MERGE_HEAD &&
+ test "$pre_merge_head" = "$(git rev-parse HEAD)" &&
+ test -z "$(git diff --staged)" &&
+ git diff > actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'Fail conflicting merge with conflicting dirty worktree' '
+ test_must_fail git merge conflict_branch &&
+ test ! -f .git/MERGE_HEAD &&
+ test "$pre_merge_head" = "$(git rev-parse HEAD)" &&
+ test -z "$(git diff --staged)" &&
+ git diff > actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'Reset worktree changes' '
+ git reset --hard "$pre_merge_head"
+'
+
+test_expect_success 'Fail clean merge with matching dirty worktree' '
+ echo bart > bar &&
+ git diff > expect &&
+ test_must_fail git merge --no-commit clean_branch &&
+ test ! -f .git/MERGE_HEAD &&
+ test "$pre_merge_head" = "$(git rev-parse HEAD)" &&
+ test -z "$(git diff --staged)" &&
+ git diff > actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'Abort clean merge with matching dirty index' '
+ git add bar &&
+ git diff --staged > expect &&
+ git merge --no-commit clean_branch &&
+ test -f .git/MERGE_HEAD &&
+ ### When aborting the merge, git will discard all staged changes,
+ ### including those that were staged pre-merge. In other words,
+ ### --abort will LOSE any staged changes (the staged changes that
+ ### are lost must match the merge result, or the merge would not
+ ### have been allowed to start). Change expectations accordingly:
+ rm expect &&
+ touch expect &&
+ # Abort merge
+ git merge --abort &&
+ test ! -f .git/MERGE_HEAD &&
+ test "$pre_merge_head" = "$(git rev-parse HEAD)" &&
+ git diff --staged > actual &&
+ test_cmp expect actual &&
+ test -z "$(git diff)"
+'
+
+test_expect_success 'Reset worktree changes' '
+ git reset --hard "$pre_merge_head"
+'
+
+test_expect_success 'Fail conflicting merge with matching dirty worktree' '
+ echo barf > bar &&
+ git diff > expect &&
+ test_must_fail git merge conflict_branch &&
+ test ! -f .git/MERGE_HEAD &&
+ test "$pre_merge_head" = "$(git rev-parse HEAD)" &&
+ test -z "$(git diff --staged)" &&
+ git diff > actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'Abort conflicting merge with matching dirty index' '
+ git add bar &&
+ git diff --staged > expect &&
+ test_must_fail git merge conflict_branch &&
+ test -f .git/MERGE_HEAD &&
+ ### When aborting the merge, git will discard all staged changes,
+ ### including those that were staged pre-merge. In other words,
+ ### --abort will LOSE any staged changes (the staged changes that
+ ### are lost must match the merge result, or the merge would not
+ ### have been allowed to start). Change expectations accordingly:
+ rm expect &&
+ touch expect &&
+ # Abort merge
+ git merge --abort &&
+ test ! -f .git/MERGE_HEAD &&
+ test "$pre_merge_head" = "$(git rev-parse HEAD)" &&
+ git diff --staged > actual &&
+ test_cmp expect actual &&
+ test -z "$(git diff)"
+'
+
+test_expect_success 'Reset worktree changes' '
+ git reset --hard "$pre_merge_head"
+'
+
+test_expect_success 'Abort merge with pre- and post-merge worktree changes' '
+ # Pre-merge worktree changes
+ echo xyzzy > foo &&
+ echo barf > bar &&
+ git add bar &&
+ git diff > expect &&
+ git diff --staged > expect-staged &&
+ # Perform merge
+ test_must_fail git merge conflict_branch &&
+ test -f .git/MERGE_HEAD &&
+ # Post-merge worktree changes
+ echo yzxxz > foo &&
+ echo blech > baz &&
+ ### When aborting the merge, git will discard staged changes (bar)
+ ### and unmerged changes (baz). Other changes that are neither
+ ### staged nor marked as unmerged (foo), will be preserved. For
+ ### these changed, git cannot tell pre-merge changes apart from
+ ### post-merge changes, so the post-merge changes will be
+ ### preserved. Change expectations accordingly:
+ git diff -- foo > expect &&
+ rm expect-staged &&
+ touch expect-staged &&
+ # Abort merge
+ git merge --abort &&
+ test ! -f .git/MERGE_HEAD &&
+ test "$pre_merge_head" = "$(git rev-parse HEAD)" &&
+ git diff > actual &&
+ test_cmp expect actual &&
+ git diff --staged > actual-staged &&
+ test_cmp expect-staged actual-staged
+'
+
+test_expect_success 'Reset worktree changes' '
+ git reset --hard "$pre_merge_head"
+'
+
+test_expect_success 'Abort merge with pre- and post-merge index changes' '
+ # Pre-merge worktree changes
+ echo xyzzy > foo &&
+ echo barf > bar &&
+ git add bar &&
+ git diff > expect &&
+ git diff --staged > expect-staged &&
+ # Perform merge
+ test_must_fail git merge conflict_branch &&
+ test -f .git/MERGE_HEAD &&
+ # Post-merge worktree changes
+ echo yzxxz > foo &&
+ echo blech > baz &&
+ git add foo bar &&
+ ### When aborting the merge, git will discard all staged changes
+ ### (foo, bar and baz), and no changes will be preserved. Whether
+ ### the changes were staged pre- or post-merge does not matter
+ ### (except for not preventing starting the merge).
+ ### Change expectations accordingly:
+ rm expect expect-staged &&
+ touch expect &&
+ touch expect-staged &&
+ # Abort merge
+ git merge --abort &&
+ test ! -f .git/MERGE_HEAD &&
+ test "$pre_merge_head" = "$(git rev-parse HEAD)" &&
+ git diff > actual &&
+ test_cmp expect actual &&
+ git diff --staged > actual-staged &&
+ test_cmp expect-staged actual-staged
+'
+
+test_done
diff --git a/t/t7700-repack.sh b/t/t7700-repack.sh
index c2f66ff1703c35d5f5b92285e31e8df157e51642..d954b846a17d6374dd8c7dae7f901170ff3c8c24 100755 (executable)
--- a/t/t7700-repack.sh
+++ b/t/t7700-repack.sh
'
test_expect_success 'packed obs in alt ODB are repacked even when local repo is packless' '
- mkdir alt_objects/pack
+ mkdir alt_objects/pack &&
mv .git/objects/pack/* alt_objects/pack &&
git repack -a &&
myidx=$(ls -1 .git/objects/pack/*.idx) &&
# swap the .keep so the commit object is in the pack with .keep
for p in alt_objects/pack/*.pack
do
- base_name=$(basename $p .pack)
+ base_name=$(basename $p .pack) &&
if test -f alt_objects/pack/$base_name.keep
then
rm alt_objects/pack/$base_name.keep
else
touch alt_objects/pack/$base_name.keep
fi
- done
+ done &&
git repack -a -d &&
myidx=$(ls -1 .git/objects/pack/*.idx) &&
test -f "$myidx" &&
diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh
index 58dc6f6452e69df2f91799b0ae23c620783311da..4048d106d4936ae1eef2ca3788a147e659bda65d 100755 (executable)
--- a/t/t7800-difftool.sh
+++ b/t/t7800-difftool.sh
# Specify the diff tool using $GIT_DIFF_TOOL
test_expect_success PERL 'GIT_DIFF_TOOL variable' '
- git config --unset diff.tool
+ test_might_fail git config --unset diff.tool &&
GIT_DIFF_TOOL=test-tool &&
export GIT_DIFF_TOOL &&
# Test that we don't have to pass --no-prompt when mergetool.prompt is false
test_expect_success PERL 'difftool merge.prompt = false' '
- git config --unset difftool.prompt
+ test_might_fail git config --unset difftool.prompt &&
git config mergetool.prompt false &&
diff=$(git difftool branch) &&
# git-difftool falls back to git-mergetool config variables
# so test that behavior here
test_expect_success PERL 'difftool + mergetool config variables' '
- remove_config_vars
+ remove_config_vars &&
git config merge.tool test-tool &&
git config mergetool.test-tool.cmd "cat \$LOCAL" &&
'
test_expect_success PERL 'difftool --extcmd echo arg1' '
- diff=$(git difftool --no-prompt --extcmd sh\ -c\ \"echo\ \$1\" branch)
+ diff=$(git difftool --no-prompt --extcmd sh\ -c\ \"echo\ \$1\" branch) &&
test "$diff" = file
'
test_expect_success PERL 'difftool --extcmd cat arg1' '
- diff=$(git difftool --no-prompt --extcmd sh\ -c\ \"cat\ \$1\" branch)
+ diff=$(git difftool --no-prompt --extcmd sh\ -c\ \"cat\ \$1\" branch) &&
test "$diff" = master
'
test_expect_success PERL 'difftool --extcmd cat arg2' '
- diff=$(git difftool --no-prompt --extcmd sh\ -c\ \"cat\ \$2\" branch)
+ diff=$(git difftool --no-prompt --extcmd sh\ -c\ \"cat\ \$2\" branch) &&
test "$diff" = branch
'
diff --git a/t/t7810-grep.sh b/t/t7810-grep.sh
index 023f225a4b1f22c66fb291a76690564696d56035..8a7788dc39f236b15e60912c384d835fd1db5a28 100755 (executable)
--- a/t/t7810-grep.sh
+++ b/t/t7810-grep.sh
test_cmp expected actual
'
+ test_expect_success "grep --max-depth 0 -- . t $L" '
+ {
+ echo ${HC}t/v:1:vvv
+ echo ${HC}v:1:vvv
+ } >expected &&
+ git grep --max-depth 0 -n -e vvv $H -- . t >actual &&
+ test_cmp expected actual
+ '
+
+ test_expect_success "grep --max-depth 0 -- t . $L" '
+ {
+ echo ${HC}t/v:1:vvv
+ echo ${HC}v:1:vvv
+ } >expected &&
+ git grep --max-depth 0 -n -e vvv $H -- t . >actual &&
+ test_cmp expected actual
+ '
+
done
cat >expected <<EOF
echo a >>file &&
test_tick &&
- git commit -a -m "third"
+ git commit -a -m "third" &&
+ echo a >>file &&
+ test_tick &&
+ GIT_AUTHOR_NAME="Night Fall" \
+ GIT_AUTHOR_EMAIL="nitfol@frobozz.com" \
+ git commit -a -m "fourth"
'
test_expect_success 'log grep (1)' '
test_cmp expect actual
'
+test_expect_success 'log with multiple --author uses union' '
+ git log --author="Thor" --author="Aster" --format=%s >actual &&
+ {
+ echo third && echo second && echo initial
+ } >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'log with --grep and multiple --author uses all-match' '
+ git log --author="Thor" --author="Night" --grep=i --format=%s >actual &&
+ {
+ echo third && echo initial
+ } >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'log with --grep and multiple --author uses all-match' '
+ git log --author="Thor" --author="Night" --grep=q --format=%s >actual &&
+ >expect &&
+ test_cmp expect actual
+'
+
test_expect_success 'grep with CE_VALID file' '
git update-index --assume-unchanged t/t &&
rm t/t &&
echo file1:hello &&
echo sub/file2:world
} >non/expect.full &&
- echo file2:world >non/expect.sub
+ echo file2:world >non/expect.sub &&
(
GIT_CEILING_DIRECTORIES="$(pwd)/non/git" &&
export GIT_CEILING_DIRECTORIES &&
echo sub/file2:world
} >is/expect.full &&
: >is/expect.empty &&
- echo file2:world >is/expect.sub
+ echo file2:world >is/expect.sub &&
(
cd is/git &&
git init &&
diff --git a/t/t8002-blame.sh b/t/t8002-blame.sh
index 597cf0486fbe1034594d3eec821f5278d9648d43..d3a51e12698bed50d4eef1474e75dbfd0be0d034 100755 (executable)
--- a/t/t8002-blame.sh
+++ b/t/t8002-blame.sh
PROG='git blame -c'
. "$TEST_DIRECTORY"/annotate-tests.sh
+PROG='git blame -c -e'
+test_expect_success 'Blame --show-email works' '
+ check_count "<A@test.git>" 1 "<B@test.git>" 1 "<B1@test.git>" 1 "<B2@test.git>" 1 "<author@example.com>" 1 "<C@test.git>" 1 "<D@test.git>" 1
+'
+
test_done
diff --git a/t/t8003-blame-corner-cases.sh b/t/t8003-blame-corner-cases.sh
--- /dev/null
@@ -0,0 +1,188 @@
+#!/bin/sh
+
+test_description='git blame corner cases'
+. ./test-lib.sh
+
+pick_fc='s/^[0-9a-f^]* *\([^ ]*\) *(\([^ ]*\) .*/\1-\2/'
+
+test_expect_success setup '
+
+ echo A A A A A >one &&
+ echo B B B B B >two &&
+ echo C C C C C >tres &&
+ echo ABC >mouse &&
+ for i in 1 2 3 4 5 6 7 8 9
+ do
+ echo $i
+ done >nine_lines &&
+ for i in 1 2 3 4 5 6 7 8 9 a
+ do
+ echo $i
+ done >ten_lines &&
+ git add one two tres mouse nine_lines ten_lines &&
+ test_tick &&
+ GIT_AUTHOR_NAME=Initial git commit -m Initial &&
+
+ cat one >uno &&
+ mv two dos &&
+ cat one >>tres &&
+ echo DEF >>mouse
+ git add uno dos tres mouse &&
+ test_tick &&
+ GIT_AUTHOR_NAME=Second git commit -a -m Second &&
+
+ echo GHIJK >>mouse &&
+ git add mouse &&
+ test_tick &&
+ GIT_AUTHOR_NAME=Third git commit -m Third &&
+
+ cat mouse >cow &&
+ git add cow &&
+ test_tick &&
+ GIT_AUTHOR_NAME=Fourth git commit -m Fourth &&
+
+ {
+ echo ABC
+ echo DEF
+ echo XXXX
+ echo GHIJK
+ } >cow &&
+ git add cow &&
+ test_tick &&
+ GIT_AUTHOR_NAME=Fifth git commit -m Fifth
+'
+
+test_expect_success 'straight copy without -C' '
+
+ git blame uno | grep Second
+
+'
+
+test_expect_success 'straight move without -C' '
+
+ git blame dos | grep Initial
+
+'
+
+test_expect_success 'straight copy with -C' '
+
+ git blame -C1 uno | grep Second
+
+'
+
+test_expect_success 'straight move with -C' '
+
+ git blame -C1 dos | grep Initial
+
+'
+
+test_expect_success 'straight copy with -C -C' '
+
+ git blame -C -C1 uno | grep Initial
+
+'
+
+test_expect_success 'straight move with -C -C' '
+
+ git blame -C -C1 dos | grep Initial
+
+'
+
+test_expect_success 'append without -C' '
+
+ git blame -L2 tres | grep Second
+
+'
+
+test_expect_success 'append with -C' '
+
+ git blame -L2 -C1 tres | grep Second
+
+'
+
+test_expect_success 'append with -C -C' '
+
+ git blame -L2 -C -C1 tres | grep Second
+
+'
+
+test_expect_success 'append with -C -C -C' '
+
+ git blame -L2 -C -C -C1 tres | grep Initial
+
+'
+
+test_expect_success 'blame wholesale copy' '
+
+ git blame -f -C -C1 HEAD^ -- cow | sed -e "$pick_fc" >current &&
+ {
+ echo mouse-Initial
+ echo mouse-Second
+ echo mouse-Third
+ } >expected &&
+ test_cmp expected current
+
+'
+
+test_expect_success 'blame wholesale copy and more' '
+
+ git blame -f -C -C1 HEAD -- cow | sed -e "$pick_fc" >current &&
+ {
+ echo mouse-Initial
+ echo mouse-Second
+ echo cow-Fifth
+ echo mouse-Third
+ } >expected &&
+ test_cmp expected current
+
+'
+
+test_expect_success 'blame path that used to be a directory' '
+ mkdir path &&
+ echo A A A A A >path/file &&
+ echo B B B B B >path/elif &&
+ git add path &&
+ test_tick &&
+ git commit -m "path was a directory" &&
+ rm -fr path &&
+ echo A A A A A >path &&
+ git add path &&
+ test_tick &&
+ git commit -m "path is a regular file" &&
+ git blame HEAD^.. -- path
+'
+
+test_expect_success 'blame to a commit with no author name' '
+ TREE=`git rev-parse HEAD:`
+ cat >badcommit <<EOF
+tree $TREE
+author <noname> 1234567890 +0000
+committer David Reiss <dreiss@facebook.com> 1234567890 +0000
+
+some message
+EOF
+ COMMIT=`git hash-object -t commit -w badcommit`
+ git --no-pager blame $COMMIT -- uno >/dev/null
+'
+
+test_expect_success 'blame -L with invalid start' '
+ test_must_fail git blame -L5 tres 2>errors &&
+ grep "has only 2 lines" errors
+'
+
+test_expect_success 'blame -L with invalid end' '
+ test_must_fail git blame -L1,5 tres 2>errors &&
+ grep "has only 2 lines" errors
+'
+
+test_expect_success 'indent of line numbers, nine lines' '
+ git blame nine_lines >actual &&
+ test $(grep -c " " actual) = 0
+'
+
+test_expect_success 'indent of line numbers, ten lines' '
+ git blame ten_lines >actual &&
+ test $(grep -c " " actual) = 9
+'
+
+test_done
diff --git a/t/t8003-blame.sh b/t/t8003-blame.sh
--- a/t/t8003-blame.sh
+++ /dev/null
@@ -1,188 +0,0 @@
-#!/bin/sh
-
-test_description='git blame corner cases'
-. ./test-lib.sh
-
-pick_fc='s/^[0-9a-f^]* *\([^ ]*\) *(\([^ ]*\) .*/\1-\2/'
-
-test_expect_success setup '
-
- echo A A A A A >one &&
- echo B B B B B >two &&
- echo C C C C C >tres &&
- echo ABC >mouse &&
- for i in 1 2 3 4 5 6 7 8 9
- do
- echo $i
- done >nine_lines &&
- for i in 1 2 3 4 5 6 7 8 9 a
- do
- echo $i
- done >ten_lines &&
- git add one two tres mouse nine_lines ten_lines &&
- test_tick &&
- GIT_AUTHOR_NAME=Initial git commit -m Initial &&
-
- cat one >uno &&
- mv two dos &&
- cat one >>tres &&
- echo DEF >>mouse
- git add uno dos tres mouse &&
- test_tick &&
- GIT_AUTHOR_NAME=Second git commit -a -m Second &&
-
- echo GHIJK >>mouse &&
- git add mouse &&
- test_tick &&
- GIT_AUTHOR_NAME=Third git commit -m Third &&
-
- cat mouse >cow &&
- git add cow &&
- test_tick &&
- GIT_AUTHOR_NAME=Fourth git commit -m Fourth &&
-
- {
- echo ABC
- echo DEF
- echo XXXX
- echo GHIJK
- } >cow &&
- git add cow &&
- test_tick &&
- GIT_AUTHOR_NAME=Fifth git commit -m Fifth
-'
-
-test_expect_success 'straight copy without -C' '
-
- git blame uno | grep Second
-
-'
-
-test_expect_success 'straight move without -C' '
-
- git blame dos | grep Initial
-
-'
-
-test_expect_success 'straight copy with -C' '
-
- git blame -C1 uno | grep Second
-
-'
-
-test_expect_success 'straight move with -C' '
-
- git blame -C1 dos | grep Initial
-
-'
-
-test_expect_success 'straight copy with -C -C' '
-
- git blame -C -C1 uno | grep Initial
-
-'
-
-test_expect_success 'straight move with -C -C' '
-
- git blame -C -C1 dos | grep Initial
-
-'
-
-test_expect_success 'append without -C' '
-
- git blame -L2 tres | grep Second
-
-'
-
-test_expect_success 'append with -C' '
-
- git blame -L2 -C1 tres | grep Second
-
-'
-
-test_expect_success 'append with -C -C' '
-
- git blame -L2 -C -C1 tres | grep Second
-
-'
-
-test_expect_success 'append with -C -C -C' '
-
- git blame -L2 -C -C -C1 tres | grep Initial
-
-'
-
-test_expect_success 'blame wholesale copy' '
-
- git blame -f -C -C1 HEAD^ -- cow | sed -e "$pick_fc" >current &&
- {
- echo mouse-Initial
- echo mouse-Second
- echo mouse-Third
- } >expected &&
- test_cmp expected current
-
-'
-
-test_expect_success 'blame wholesale copy and more' '
-
- git blame -f -C -C1 HEAD -- cow | sed -e "$pick_fc" >current &&
- {
- echo mouse-Initial
- echo mouse-Second
- echo cow-Fifth
- echo mouse-Third
- } >expected &&
- test_cmp expected current
-
-'
-
-test_expect_success 'blame path that used to be a directory' '
- mkdir path &&
- echo A A A A A >path/file &&
- echo B B B B B >path/elif &&
- git add path &&
- test_tick &&
- git commit -m "path was a directory" &&
- rm -fr path &&
- echo A A A A A >path &&
- git add path &&
- test_tick &&
- git commit -m "path is a regular file" &&
- git blame HEAD^.. -- path
-'
-
-test_expect_success 'blame to a commit with no author name' '
- TREE=`git rev-parse HEAD:`
- cat >badcommit <<EOF
-tree $TREE
-author <noname> 1234567890 +0000
-committer David Reiss <dreiss@facebook.com> 1234567890 +0000
-
-some message
-EOF
- COMMIT=`git hash-object -t commit -w badcommit`
- git --no-pager blame $COMMIT -- uno >/dev/null
-'
-
-test_expect_success 'blame -L with invalid start' '
- test_must_fail git blame -L5 tres 2>errors &&
- grep "has only 2 lines" errors
-'
-
-test_expect_success 'blame -L with invalid end' '
- test_must_fail git blame -L1,5 tres 2>errors &&
- grep "has only 2 lines" errors
-'
-
-test_expect_success 'indent of line numbers, nine lines' '
- git blame nine_lines >actual &&
- test $(grep -c " " actual) = 0
-'
-
-test_expect_success 'indent of line numbers, ten lines' '
- git blame ten_lines >actual &&
- test $(grep -c " " actual) = 9
-'
-
-test_done
diff --git a/t/t8004-blame-with-conflicts.sh b/t/t8004-blame-with-conflicts.sh
--- /dev/null
@@ -0,0 +1,73 @@
+#!/bin/sh
+
+# Based on a test case submitted by Björn Steinbrink.
+
+test_description='git blame on conflicted files'
+. ./test-lib.sh
+
+test_expect_success 'setup first case' '
+ # Create the old file
+ echo "Old line" > file1 &&
+ git add file1 &&
+ git commit --author "Old Line <ol@localhost>" -m file1.a &&
+
+ # Branch
+ git checkout -b foo &&
+
+ # Do an ugly move and change
+ git rm file1 &&
+ echo "New line ..." > file2 &&
+ echo "... and more" >> file2 &&
+ git add file2 &&
+ git commit --author "U Gly <ug@localhost>" -m ugly &&
+
+ # Back to master and change something
+ git checkout master &&
+ echo "
+
+bla" >> file1 &&
+ git commit --author "Old Line <ol@localhost>" -a -m file1.b &&
+
+ # Back to foo and merge master
+ git checkout foo &&
+ if git merge master; then
+ echo needed conflict here
+ exit 1
+ else
+ echo merge failed - resolving automatically
+ fi &&
+ echo "New line ...
+... and more
+
+bla
+Even more" > file2 &&
+ git rm file1 &&
+ git commit --author "M Result <mr@localhost>" -a -m merged &&
+
+ # Back to master and change file1 again
+ git checkout master &&
+ sed s/bla/foo/ <file1 >X &&
+ rm file1 &&
+ mv X file1 &&
+ git commit --author "No Bla <nb@localhost>" -a -m replace &&
+
+ # Try to merge into foo again
+ git checkout foo &&
+ if git merge master; then
+ echo needed conflict here
+ exit 1
+ else
+ echo merge failed - test is setup
+ fi
+'
+
+test_expect_success \
+ 'blame runs on unconflicted file while other file has conflicts' '
+ git blame file2
+'
+
+test_expect_success 'blame runs on conflicted file in stages 1,3' '
+ git blame file1
+'
+
+test_done
diff --git a/t/t8004-blame.sh b/t/t8004-blame.sh
--- a/t/t8004-blame.sh
+++ /dev/null
@@ -1,73 +0,0 @@
-#!/bin/sh
-
-# Based on a test case submitted by Björn Steinbrink.
-
-test_description='git blame on conflicted files'
-. ./test-lib.sh
-
-test_expect_success 'setup first case' '
- # Create the old file
- echo "Old line" > file1 &&
- git add file1 &&
- git commit --author "Old Line <ol@localhost>" -m file1.a &&
-
- # Branch
- git checkout -b foo &&
-
- # Do an ugly move and change
- git rm file1 &&
- echo "New line ..." > file2 &&
- echo "... and more" >> file2 &&
- git add file2 &&
- git commit --author "U Gly <ug@localhost>" -m ugly &&
-
- # Back to master and change something
- git checkout master &&
- echo "
-
-bla" >> file1 &&
- git commit --author "Old Line <ol@localhost>" -a -m file1.b &&
-
- # Back to foo and merge master
- git checkout foo &&
- if git merge master; then
- echo needed conflict here
- exit 1
- else
- echo merge failed - resolving automatically
- fi &&
- echo "New line ...
-... and more
-
-bla
-Even more" > file2 &&
- git rm file1 &&
- git commit --author "M Result <mr@localhost>" -a -m merged &&
-
- # Back to master and change file1 again
- git checkout master &&
- sed s/bla/foo/ <file1 >X &&
- rm file1 &&
- mv X file1 &&
- git commit --author "No Bla <nb@localhost>" -a -m replace &&
-
- # Try to merge into foo again
- git checkout foo &&
- if git merge master; then
- echo needed conflict here
- exit 1
- else
- echo merge failed - test is setup
- fi
-'
-
-test_expect_success \
- 'blame runs on unconflicted file while other file has conflicts' '
- git blame file2
-'
-
-test_expect_success 'blame runs on conflicted file in stages 1,3' '
- git blame file1
-'
-
-test_done
diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh
index f1047d2ef3ee473a924f5819441dd1a7b1829768..579ddb7572c27889060da315ddecb773b6fa1272 100755 (executable)
--- a/t/t9001-send-email.sh
+++ b/t/t9001-send-email.sh
grep "^To: to@example.com\$" msgtxt1
'
+test_expect_success $PREREQ 'tocmd works' '
+ clean_fake_sendmail &&
+ cp $patches tocmd.patch &&
+ echo tocmd--tocmd@example.com >>tocmd.patch &&
+ {
+ echo "#!$SHELL_PATH"
+ echo sed -n -e s/^tocmd--//p \"\$1\"
+ } > tocmd-sed &&
+ chmod +x tocmd-sed &&
+ git send-email \
+ --from="Example <nobody@example.com>" \
+ --to-cmd=./tocmd-sed \
+ --smtp-server="$(pwd)/fake.sendmail" \
+ tocmd.patch \
+ &&
+ grep "^To: tocmd@example.com" msgtxt1
+'
+
test_expect_success $PREREQ 'cccmd works' '
clean_fake_sendmail &&
cp $patches cccmd.patch &&
- echo cccmd--cccmd@example.com >>cccmd.patch &&
+ echo "cccmd-- cccmd@example.com" >>cccmd.patch &&
{
echo "#!$SHELL_PATH"
echo sed -n -e s/^cccmd--//p \"\$1\"
--to=nobody@example.com \
--smtp-server="$(pwd)/fake.sendmail" \
$patches &&
- sed "1,/^\$/d" < msgtxt1 > msgbody1
+ sed "1,/^\$/d" < msgtxt1 > msgbody1 &&
grep "From: A <author@example.com>" msgbody1
'
--to=nobody@example.com \
--smtp-server="$(pwd)/fake.sendmail" \
$patches &&
- sed "1,/^\$/d" < msgtxt1 > msgbody1
+ sed "1,/^\$/d" < msgtxt1 > msgbody1 &&
! grep "From: A <author@example.com>" msgbody1
'
--in-reply-to=" " \
--smtp-server="$(pwd)/fake.sendmail" \
$patches \
- 2>errors
+ 2>errors &&
! grep "^In-Reply-To: < *>" msgtxt1
'
! grep "^In-Reply-To: < *>" msgtxt1
'
+test_expect_success $PREREQ 'In-Reply-To without --chain-reply-to' '
+ clean_fake_sendmail &&
+ echo "<unique-message-id@example.com>" >expect &&
+ git send-email \
+ --from="Example <nobody@example.com>" \
+ --to=nobody@example.com \
+ --nochain-reply-to \
+ --in-reply-to="$(cat expect)" \
+ --smtp-server="$(pwd)/fake.sendmail" \
+ $patches $patches $patches \
+ 2>errors &&
+ # The first message is a reply to --in-reply-to
+ sed -n -e "s/^In-Reply-To: *\(.*\)/\1/p" msgtxt1 >actual &&
+ test_cmp expect actual &&
+ # Second and subsequent messages are replies to the first one
+ sed -n -e "s/^Message-Id: *\(.*\)/\1/p" msgtxt1 >expect &&
+ sed -n -e "s/^In-Reply-To: *\(.*\)/\1/p" msgtxt2 >actual &&
+ test_cmp expect actual &&
+ sed -n -e "s/^In-Reply-To: *\(.*\)/\1/p" msgtxt3 >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success $PREREQ 'In-Reply-To with --chain-reply-to' '
+ clean_fake_sendmail &&
+ echo "<unique-message-id@example.com>" >expect &&
+ git send-email \
+ --from="Example <nobody@example.com>" \
+ --to=nobody@example.com \
+ --chain-reply-to \
+ --in-reply-to="$(cat expect)" \
+ --smtp-server="$(pwd)/fake.sendmail" \
+ $patches $patches $patches \
+ 2>errors &&
+ sed -n -e "s/^In-Reply-To: *\(.*\)/\1/p" msgtxt1 >actual &&
+ test_cmp expect actual &&
+ sed -n -e "s/^Message-Id: *\(.*\)/\1/p" msgtxt1 >expect &&
+ sed -n -e "s/^In-Reply-To: *\(.*\)/\1/p" msgtxt2 >actual &&
+ test_cmp expect actual &&
+ sed -n -e "s/^Message-Id: *\(.*\)/\1/p" msgtxt2 >expect &&
+ sed -n -e "s/^In-Reply-To: *\(.*\)/\1/p" msgtxt3 >actual &&
+ test_cmp expect actual
+'
+
test_expect_success $PREREQ 'setup fake editor' '
(echo "#!$SHELL_PATH" &&
echo "echo fake edit >>\"\$1\""
"
test_expect_success $PREREQ '--suppress-cc=sob' '
- git config --unset sendemail.cccmd
+ test_might_fail git config --unset sendemail.cccmd &&
test_suppression sob
'
! grep "RCPT TO:<other@ex.com>" stdout
'
+test_expect_success $PREREQ 'patches To headers are used by default' '
+ patch=`git format-patch -1 --to="bodies@example.com"` &&
+ test_when_finished "rm $patch" &&
+ git send-email \
+ --dry-run \
+ --from="Example <nobody@example.com>" \
+ --smtp-server relay.example.com \
+ $patch >stdout &&
+ grep "RCPT TO:<bodies@example.com>" stdout
+'
+
+test_expect_success $PREREQ 'patches To headers are appended to' '
+ patch=`git format-patch -1 --to="bodies@example.com"` &&
+ test_when_finished "rm $patch" &&
+ git send-email \
+ --dry-run \
+ --from="Example <nobody@example.com>" \
+ --to=nobody@example.com \
+ --smtp-server relay.example.com \
+ $patch >stdout &&
+ grep "RCPT TO:<bodies@example.com>" stdout &&
+ grep "RCPT TO:<nobody@example.com>" stdout
+'
+
+test_expect_success $PREREQ 'To headers from files reset each patch' '
+ patch1=`git format-patch -1 --to="bodies@example.com"` &&
+ patch2=`git format-patch -1 --to="other@example.com" HEAD~` &&
+ test_when_finished "rm $patch1 && rm $patch2" &&
+ git send-email \
+ --dry-run \
+ --from="Example <nobody@example.com>" \
+ --to="nobody@example.com" \
+ --smtp-server relay.example.com \
+ $patch1 $patch2 >stdout &&
+ test $(grep -c "RCPT TO:<bodies@example.com>" stdout) = 1 &&
+ test $(grep -c "RCPT TO:<nobody@example.com>" stdout) = 2 &&
+ test $(grep -c "RCPT TO:<other@example.com>" stdout) = 1
+'
+
test_expect_success $PREREQ 'setup expect' '
cat >email-using-8bit <<EOF
From fe6ecc66ece37198fe5db91fa2fc41d9f4fe5cc4 Mon Sep 17 00:00:00 2001
diff --git a/t/t9010-svn-fe.sh b/t/t9010-svn-fe.sh
index a713dfc50bdd69878bb837b28e9f6c61c85f366a..5a6a4b9b7a6e6abbb9570ddceaac68baf5c2690b 100755 (executable)
--- a/t/t9010-svn-fe.sh
+++ b/t/t9010-svn-fe.sh
test_description='check svn dumpfile importer'
-. ./lib-git-svn.sh
-
-test_dump() {
- label=$1
- dump=$2
- test_expect_success "$dump" '
- svnadmin create "$label-svn" &&
- svnadmin load "$label-svn" < "$TEST_DIRECTORY/$dump" &&
- svn_cmd export "file://$PWD/$label-svn" "$label-svnco" &&
- git init "$label-git" &&
- test-svn-fe "$TEST_DIRECTORY/$dump" >"$label.fe" &&
- (
- cd "$label-git" &&
- git fast-import < ../"$label.fe"
- ) &&
- (
- cd "$label-svnco" &&
- git init &&
- git add . &&
- git fetch "../$label-git" master &&
- git diff --exit-code FETCH_HEAD
- )
- '
+. ./test-lib.sh
+
+reinit_git () {
+ rm -fr .git &&
+ git init
+}
+
+properties () {
+ while test "$#" -ne 0
+ do
+ property="$1" &&
+ value="$2" &&
+ printf "%s\n" "K ${#property}" &&
+ printf "%s\n" "$property" &&
+ printf "%s\n" "V ${#value}" &&
+ printf "%s\n" "$value" &&
+ shift 2 ||
+ return 1
+ done
}
-test_dump simple t9135/svn.dump
+text_no_props () {
+ text="$1
+" &&
+ printf "%s\n" "Prop-content-length: 10" &&
+ printf "%s\n" "Text-content-length: ${#text}" &&
+ printf "%s\n" "Content-length: $((${#text} + 10))" &&
+ printf "%s\n" "" "PROPS-END" &&
+ printf "%s\n" "$text"
+}
+
+>empty
+
+test_expect_success 'empty dump' '
+ reinit_git &&
+ echo "SVN-fs-dump-format-version: 2" >input &&
+ test-svn-fe input >stream &&
+ git fast-import <stream
+'
+
+test_expect_success 'v4 dumps not supported' '
+ reinit_git &&
+ echo "SVN-fs-dump-format-version: 4" >v4.dump &&
+ test_must_fail test-svn-fe v4.dump >stream &&
+ test_cmp empty stream
+'
+
+test_expect_failure 'empty revision' '
+ reinit_git &&
+ printf "rev <nobody, nobody@local>: %s\n" "" "" >expect &&
+ cat >emptyrev.dump <<-\EOF &&
+ SVN-fs-dump-format-version: 3
+
+ Revision-number: 1
+ Prop-content-length: 0
+ Content-length: 0
+
+ Revision-number: 2
+ Prop-content-length: 0
+ Content-length: 0
+
+ EOF
+ test-svn-fe emptyrev.dump >stream &&
+ git fast-import <stream &&
+ git log -p --format="rev <%an, %ae>: %s" HEAD >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'empty properties' '
+ reinit_git &&
+ printf "rev <nobody, nobody@local>: %s\n" "" "" >expect &&
+ cat >emptyprop.dump <<-\EOF &&
+ SVN-fs-dump-format-version: 3
+
+ Revision-number: 1
+ Prop-content-length: 10
+ Content-length: 10
+
+ PROPS-END
+
+ Revision-number: 2
+ Prop-content-length: 10
+ Content-length: 10
+
+ PROPS-END
+ EOF
+ test-svn-fe emptyprop.dump >stream &&
+ git fast-import <stream &&
+ git log -p --format="rev <%an, %ae>: %s" HEAD >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'author name and commit message' '
+ reinit_git &&
+ echo "<author@example.com, author@example.com@local>" >expect.author &&
+ cat >message <<-\EOF &&
+ A concise summary of the change
+
+ A detailed description of the change, why it is needed, what
+ was broken and why applying this is the best course of action.
+
+ * file.c
+ Details pertaining to an individual file.
+ EOF
+ {
+ properties \
+ svn:author author@example.com \
+ svn:log "$(cat message)" &&
+ echo PROPS-END
+ } >props &&
+ {
+ echo "SVN-fs-dump-format-version: 3" &&
+ echo &&
+ echo "Revision-number: 1" &&
+ echo Prop-content-length: $(wc -c <props) &&
+ echo Content-length: $(wc -c <props) &&
+ echo &&
+ cat props
+ } >log.dump &&
+ test-svn-fe log.dump >stream &&
+ git fast-import <stream &&
+ git log -p --format="%B" HEAD >actual.log &&
+ git log --format="<%an, %ae>" >actual.author &&
+ test_cmp message actual.log &&
+ test_cmp expect.author actual.author
+'
+
+test_expect_success 'unsupported properties are ignored' '
+ reinit_git &&
+ echo author >expect &&
+ cat >extraprop.dump <<-\EOF &&
+ SVN-fs-dump-format-version: 3
+
+ Revision-number: 1
+ Prop-content-length: 56
+ Content-length: 56
+
+ K 8
+ nonsense
+ V 1
+ y
+ K 10
+ svn:author
+ V 6
+ author
+ PROPS-END
+ EOF
+ test-svn-fe extraprop.dump >stream &&
+ git fast-import <stream &&
+ git log -p --format=%an HEAD >actual &&
+ test_cmp expect actual
+'
+
+test_expect_failure 'timestamp and empty file' '
+ echo author@example.com >expect.author &&
+ echo 1999-01-01 >expect.date &&
+ echo file >expect.files &&
+ reinit_git &&
+ {
+ properties \
+ svn:author author@example.com \
+ svn:date "1999-01-01T00:01:002.000000Z" \
+ svn:log "add empty file" &&
+ echo PROPS-END
+ } >props &&
+ {
+ cat <<-EOF &&
+ SVN-fs-dump-format-version: 3
+
+ Revision-number: 1
+ EOF
+ echo Prop-content-length: $(wc -c <props) &&
+ echo Content-length: $(wc -c <props) &&
+ echo &&
+ cat props &&
+ cat <<-\EOF
+
+ Node-path: empty-file
+ Node-kind: file
+ Node-action: add
+ Content-length: 0
+
+ EOF
+ } >emptyfile.dump &&
+ test-svn-fe emptyfile.dump >stream &&
+ git fast-import <stream &&
+ git log --format=%an HEAD >actual.author &&
+ git log --date=short --format=%ad HEAD >actual.date &&
+ git ls-tree -r --name-only HEAD >actual.files &&
+ test_cmp expect.author actual.author &&
+ test_cmp expect.date actual.date &&
+ test_cmp expect.files actual.files &&
+ git checkout HEAD empty-file &&
+ test_cmp empty file
+'
+
+test_expect_success 'directory with files' '
+ reinit_git &&
+ printf "%s\n" directory/file1 directory/file2 >expect.files &&
+ echo hi >hi &&
+ echo hello >hello &&
+ {
+ properties \
+ svn:author author@example.com \
+ svn:date "1999-02-01T00:01:002.000000Z" \
+ svn:log "add directory with some files in it" &&
+ echo PROPS-END
+ } >props &&
+ {
+ cat <<-EOF &&
+ SVN-fs-dump-format-version: 3
+
+ Revision-number: 1
+ EOF
+ echo Prop-content-length: $(wc -c <props) &&
+ echo Content-length: $(wc -c <props) &&
+ echo &&
+ cat props &&
+ cat <<-\EOF &&
+
+ Node-path: directory
+ Node-kind: dir
+ Node-action: add
+ Prop-content-length: 10
+ Content-length: 10
+
+ PROPS-END
+
+ Node-path: directory/file1
+ Node-kind: file
+ Node-action: add
+ EOF
+ text_no_props hello &&
+ cat <<-\EOF &&
+ Node-path: directory/file2
+ Node-kind: file
+ Node-action: add
+ EOF
+ text_no_props hi
+ } >directory.dump &&
+ test-svn-fe directory.dump >stream &&
+ git fast-import <stream &&
+
+ git ls-tree -r --name-only HEAD >actual.files &&
+ git checkout HEAD directory &&
+ test_cmp expect.files actual.files &&
+ test_cmp hello directory/file1 &&
+ test_cmp hi directory/file2
+'
+
+test_expect_success 'node without action' '
+ cat >inaction.dump <<-\EOF &&
+ SVN-fs-dump-format-version: 3
+
+ Revision-number: 1
+ Prop-content-length: 10
+ Content-length: 10
+
+ PROPS-END
+
+ Node-path: directory
+ Node-kind: dir
+ Prop-content-length: 10
+ Content-length: 10
+
+ PROPS-END
+ EOF
+ test_must_fail test-svn-fe inaction.dump
+'
+
+test_expect_success 'action: add node without text' '
+ cat >textless.dump <<-\EOF &&
+ SVN-fs-dump-format-version: 3
+
+ Revision-number: 1
+ Prop-content-length: 10
+ Content-length: 10
+
+ PROPS-END
+
+ Node-path: textless
+ Node-kind: file
+ Node-action: add
+ Prop-content-length: 10
+ Content-length: 10
+
+ PROPS-END
+ EOF
+ test_must_fail test-svn-fe textless.dump
+'
+
+test_expect_failure 'change file mode but keep old content' '
+ reinit_git &&
+ cat >expect <<-\EOF &&
+ OBJID
+ :120000 100644 OBJID OBJID T greeting
+ OBJID
+ :100644 120000 OBJID OBJID T greeting
+ OBJID
+ :000000 100644 OBJID OBJID A greeting
+ EOF
+ echo "link hello" >expect.blob &&
+ echo hello >hello &&
+ cat >filemode.dump <<-\EOF &&
+ SVN-fs-dump-format-version: 3
+
+ Revision-number: 1
+ Prop-content-length: 10
+ Content-length: 10
+
+ PROPS-END
+
+ Node-path: greeting
+ Node-kind: file
+ Node-action: add
+ Prop-content-length: 10
+ Text-content-length: 11
+ Content-length: 21
+
+ PROPS-END
+ link hello
+
+ Revision-number: 2
+ Prop-content-length: 10
+ Content-length: 10
+
+ PROPS-END
+
+ Node-path: greeting
+ Node-kind: file
+ Node-action: change
+ Prop-content-length: 33
+ Content-length: 33
+
+ K 11
+ svn:special
+ V 1
+ *
+ PROPS-END
+
+ Revision-number: 3
+ Prop-content-length: 10
+ Content-length: 10
+
+ PROPS-END
+
+ Node-path: greeting
+ Node-kind: file
+ Node-action: change
+ Prop-content-length: 10
+ Content-length: 10
+
+ PROPS-END
+ EOF
+ test-svn-fe filemode.dump >stream &&
+ git fast-import <stream &&
+ {
+ git rev-list HEAD |
+ git diff-tree --root --stdin |
+ sed "s/$_x40/OBJID/g"
+ } >actual &&
+ git show HEAD:greeting >actual.blob &&
+ git show HEAD^:greeting >actual.target &&
+ test_cmp expect actual &&
+ test_cmp expect.blob actual.blob &&
+ test_cmp hello actual.target
+'
+
+test_expect_success 'change file mode and reiterate content' '
+ reinit_git &&
+ cat >expect <<-\EOF &&
+ OBJID
+ :120000 100644 OBJID OBJID T greeting
+ OBJID
+ :100644 120000 OBJID OBJID T greeting
+ OBJID
+ :000000 100644 OBJID OBJID A greeting
+ EOF
+ echo "link hello" >expect.blob &&
+ echo hello >hello &&
+ cat >filemode.dump <<-\EOF &&
+ SVN-fs-dump-format-version: 3
+
+ Revision-number: 1
+ Prop-content-length: 10
+ Content-length: 10
+
+ PROPS-END
+
+ Node-path: greeting
+ Node-kind: file
+ Node-action: add
+ Prop-content-length: 10
+ Text-content-length: 11
+ Content-length: 21
+
+ PROPS-END
+ link hello
+
+ Revision-number: 2
+ Prop-content-length: 10
+ Content-length: 10
+
+ PROPS-END
+
+ Node-path: greeting
+ Node-kind: file
+ Node-action: change
+ Prop-content-length: 33
+ Text-content-length: 11
+ Content-length: 44
+
+ K 11
+ svn:special
+ V 1
+ *
+ PROPS-END
+ link hello
+
+ Revision-number: 3
+ Prop-content-length: 10
+ Content-length: 10
+
+ PROPS-END
+
+ Node-path: greeting
+ Node-kind: file
+ Node-action: change
+ Prop-content-length: 10
+ Text-content-length: 11
+ Content-length: 21
+
+ PROPS-END
+ link hello
+ EOF
+ test-svn-fe filemode.dump >stream &&
+ git fast-import <stream &&
+ {
+ git rev-list HEAD |
+ git diff-tree --root --stdin |
+ sed "s/$_x40/OBJID/g"
+ } >actual &&
+ git show HEAD:greeting >actual.blob &&
+ git show HEAD^:greeting >actual.target &&
+ test_cmp expect actual &&
+ test_cmp expect.blob actual.blob &&
+ test_cmp hello actual.target
+'
+
+test_expect_success 'deltas not supported' '
+ {
+ # (old) h + (inline) ello + (old) \n
+ printf "SVNQ%b%b%s" "Q\003\006\005\004" "\001Q\0204\001\002" "ello" |
+ q_to_nul
+ } >delta &&
+ {
+ properties \
+ svn:author author@example.com \
+ svn:date "1999-01-05T00:01:002.000000Z" \
+ svn:log "add greeting" &&
+ echo PROPS-END
+ } >props &&
+ {
+ properties \
+ svn:author author@example.com \
+ svn:date "1999-01-06T00:01:002.000000Z" \
+ svn:log "change it" &&
+ echo PROPS-END
+ } >props2 &&
+ {
+ echo SVN-fs-dump-format-version: 3 &&
+ echo &&
+ echo Revision-number: 1 &&
+ echo Prop-content-length: $(wc -c <props) &&
+ echo Content-length: $(wc -c <props) &&
+ echo &&
+ cat props &&
+ cat <<-\EOF &&
+
+ Node-path: hello
+ Node-kind: file
+ Node-action: add
+ Prop-content-length: 10
+ Text-content-length: 3
+ Content-length: 13
+
+ PROPS-END
+ hi
+
+ EOF
+ echo Revision-number: 2 &&
+ echo Prop-content-length: $(wc -c <props2) &&
+ echo Content-length: $(wc -c <props2) &&
+ echo &&
+ cat props2 &&
+ cat <<-\EOF &&
+
+ Node-path: hello
+ Node-kind: file
+ Node-action: change
+ Text-delta: true
+ Prop-content-length: 10
+ EOF
+ echo Text-content-length: $(wc -c <delta) &&
+ echo Content-length: $((10 + $(wc -c <delta))) &&
+ echo &&
+ echo PROPS-END &&
+ cat delta
+ } >delta.dump &&
+ test_must_fail test-svn-fe delta.dump
+'
+
+test_expect_success 'property deltas supported' '
+ reinit_git &&
+ cat >expect <<-\EOF &&
+ OBJID
+ :100755 100644 OBJID OBJID M script.sh
+ EOF
+ {
+ properties \
+ svn:author author@example.com \
+ svn:date "1999-03-06T00:01:002.000000Z" \
+ svn:log "make an executable, or chmod -x it" &&
+ echo PROPS-END
+ } >revprops &&
+ {
+ echo SVN-fs-dump-format-version: 3 &&
+ echo &&
+ echo Revision-number: 1 &&
+ echo Prop-content-length: $(wc -c <revprops) &&
+ echo Content-length: $(wc -c <revprops) &&
+ echo &&
+ cat revprops &&
+ echo &&
+ cat <<-\EOF &&
+ Node-path: script.sh
+ Node-kind: file
+ Node-action: add
+ Text-content-length: 0
+ Prop-content-length: 39
+ Content-length: 39
+
+ K 14
+ svn:executable
+ V 4
+ true
+ PROPS-END
+
+ EOF
+ echo Revision-number: 2 &&
+ echo Prop-content-length: $(wc -c <revprops) &&
+ echo Content-length: $(wc -c <revprops) &&
+ echo &&
+ cat revprops &&
+ echo &&
+ cat <<-\EOF
+ Node-path: script.sh
+ Node-kind: file
+ Node-action: change
+ Prop-delta: true
+ Prop-content-length: 30
+ Content-length: 30
+
+ D 14
+ svn:executable
+ PROPS-END
+ EOF
+ } >propdelta.dump &&
+ test-svn-fe propdelta.dump >stream &&
+ git fast-import <stream &&
+ {
+ git rev-list HEAD |
+ git diff-tree --stdin |
+ sed "s/$_x40/OBJID/g"
+ } >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'properties on /' '
+ reinit_git &&
+ cat <<-\EOF >expect &&
+ OBJID
+ OBJID
+ :000000 100644 OBJID OBJID A greeting
+ EOF
+ sed -e "s/X$//" <<-\EOF >changeroot.dump &&
+ SVN-fs-dump-format-version: 3
+
+ Revision-number: 1
+ Prop-content-length: 10
+ Content-length: 10
+
+ PROPS-END
+
+ Node-path: greeting
+ Node-kind: file
+ Node-action: add
+ Text-content-length: 0
+ Prop-content-length: 10
+ Content-length: 10
+
+ PROPS-END
+
+ Revision-number: 2
+ Prop-content-length: 10
+ Content-length: 10
+
+ PROPS-END
+
+ Node-path: X
+ Node-kind: dir
+ Node-action: change
+ Prop-delta: true
+ Prop-content-length: 43
+ Content-length: 43
+
+ K 10
+ svn:ignore
+ V 11
+ build-area
+
+ PROPS-END
+ EOF
+ test-svn-fe changeroot.dump >stream &&
+ git fast-import <stream &&
+ {
+ git rev-list HEAD |
+ git diff-tree --root --always --stdin |
+ sed "s/$_x40/OBJID/g"
+ } >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'deltas for typechange' '
+ reinit_git &&
+ cat >expect <<-\EOF &&
+ OBJID
+ :120000 100644 OBJID OBJID T test-file
+ OBJID
+ :100755 120000 OBJID OBJID T test-file
+ OBJID
+ :000000 100755 OBJID OBJID A test-file
+ EOF
+ cat >deleteprop.dump <<-\EOF &&
+ SVN-fs-dump-format-version: 3
+
+ Revision-number: 1
+ Prop-content-length: 10
+ Content-length: 10
+
+ PROPS-END
+
+ Node-path: test-file
+ Node-kind: file
+ Node-action: add
+ Prop-delta: true
+ Prop-content-length: 35
+ Text-content-length: 17
+ Content-length: 52
+
+ K 14
+ svn:executable
+ V 0
+
+ PROPS-END
+ link testing 123
+
+ Revision-number: 2
+ Prop-content-length: 10
+ Content-length: 10
+
+ PROPS-END
+
+ Node-path: test-file
+ Node-kind: file
+ Node-action: change
+ Prop-delta: true
+ Prop-content-length: 53
+ Text-content-length: 17
+ Content-length: 70
+
+ K 11
+ svn:special
+ V 1
+ *
+ D 14
+ svn:executable
+ PROPS-END
+ link testing 231
+
+ Revision-number: 3
+ Prop-content-length: 10
+ Content-length: 10
+
+ PROPS-END
+
+ Node-path: test-file
+ Node-kind: file
+ Node-action: change
+ Prop-delta: true
+ Prop-content-length: 27
+ Text-content-length: 17
+ Content-length: 44
+
+ D 11
+ svn:special
+ PROPS-END
+ link testing 321
+ EOF
+ test-svn-fe deleteprop.dump >stream &&
+ git fast-import <stream &&
+ {
+ git rev-list HEAD |
+ git diff-tree --root --stdin |
+ sed "s/$_x40/OBJID/g"
+ } >actual &&
+ test_cmp expect actual
+'
+
+
+test_expect_success 'set up svn repo' '
+ svnconf=$PWD/svnconf &&
+ mkdir -p "$svnconf" &&
+
+ if
+ svnadmin -h >/dev/null 2>&1 &&
+ svnadmin create simple-svn &&
+ svnadmin load simple-svn <"$TEST_DIRECTORY/t9135/svn.dump" &&
+ svn export --config-dir "$svnconf" "file://$PWD/simple-svn" simple-svnco
+ then
+ test_set_prereq SVNREPO
+ fi
+'
+
+test_expect_success SVNREPO 't9135/svn.dump' '
+ git init simple-git &&
+ test-svn-fe "$TEST_DIRECTORY/t9135/svn.dump" >simple.fe &&
+ (
+ cd simple-git &&
+ git fast-import <../simple.fe
+ ) &&
+ (
+ cd simple-svnco &&
+ git init &&
+ git add . &&
+ git fetch ../simple-git master &&
+ git diff --exit-code FETCH_HEAD
+ )
+'
test_done
index f7f3c5ab8ef8dd822a51d6c97281ba077c511642..13b179e721ef12993ce2bc8f0f59d9f49940447c 100755 (executable)
git svn init --minimize-url -i stunk "$svnrepo"/stunk &&
git svn fetch -i stunk &&
git svn init --minimize-url -i flunked "$svnrepo"/flunked &&
- git svn fetch -i flunked
+ git svn fetch -i flunked &&
test "`git rev-parse --verify refs/remotes/flunk@18`" \
= "`git rev-parse --verify refs/remotes/stunk`" &&
test "`git rev-parse --verify refs/remotes/flunk~1`" \
diff --git a/t/t9123-git-svn-rebuild-with-rewriteroot.sh b/t/t9123-git-svn-rebuild-with-rewriteroot.sh
index 0ed90d982d28810c4f7a018fab3d2f8699b78d95..fd8184787fba13bbbf3f1ef377ad3f979ef8f47a 100755 (executable)
test_expect_success 'init, fetch and checkout repository' '
git svn init --rewrite-root=http://invalid.invalid/ "$svnrepo" &&
- git svn fetch
+ git svn fetch &&
git checkout -b mybranch ${remotes_git_svn}
'
index d6b076f6b7de148c8d548c7a977e4f09b4be8e3b..aa841e12996aad8cd7284eea56c47f0d89c79c56 100755 (executable)
svn_cmd import -m "import for git svn" . "$svnrepo"
) &&
rm -rf import &&
- git svn init "$svnrepo"
+ git svn init "$svnrepo" &&
git svn fetch
'
diff --git a/t/t9143-git-svn-gc.sh b/t/t9143-git-svn-gc.sh
index 337ea59711ac7aca78a27afb92693f88e98aed9d..4594e1ae2f36b5a5582cdb1f2421fdfbd2f8c996 100755 (executable)
--- a/t/t9143-git-svn-gc.sh
+++ b/t/t9143-git-svn-gc.sh
test_expect_success 'git svn index removed' '! test -f .git/svn/refs/remotes/git-svn/index'
-if perl -MCompress::Zlib -e 0 2>/dev/null
+if test -r .git/svn/refs/remotes/git-svn/unhandled.log.gz
then
test_expect_success 'git svn gc produces a valid gzip file' '
gunzip .git/svn/refs/remotes/git-svn/unhandled.log.gz
'
-else
- say "# Perl Compress::Zlib unavailable, skipping gunzip test"
fi
test_expect_success 'git svn gc does not change unhandled.log files' '
index 565365cbd3ff80d816dd02b2072045cd25ae4b93..158c8e33ef3381f3310ac39a99932d185290d685 100755 (executable)
'
test_expect_success 'git svn rebase creates empty directory' '
- ( cd cloned && git svn rebase )
+ ( cd cloned && git svn rebase ) &&
test -d cloned/"! !"
'
index 250c651eaecf60103ee442bcfa2a6c65250320ec..4f6c06ecb2bc8671949326955acf3ff39a364c5d 100755 (executable)
--- a/t/t9151-svn-mergeinfo.sh
+++ b/t/t9151-svn-mergeinfo.sh
test_expect_success 'all svn merges became git merge commits' '
unmarked=$(git rev-list --parents --all --grep=Merge |
- grep -v " .* " | cut -f1 -d" ")
+ grep -v " .* " | cut -f1 -d" ") &&
[ -z "$unmarked" ]
'
test_expect_success 'cherry picks did not become git merge commits' '
bad_cherries=$(git rev-list --parents --all --grep=Cherry |
- grep " .* " | cut -f1 -d" ")
+ grep " .* " | cut -f1 -d" ") &&
[ -z "$bad_cherries" ]
'
test_expect_success 'svn non-merge merge commits did not become git merge commits' '
bad_non_merges=$(git rev-list --parents --all --grep=non-merge |
- grep " .* " | cut -f1 -d" ")
+ grep " .* " | cut -f1 -d" ") &&
[ -z "$bad_non_merges" ]
'
test_expect_success 'commit made to merged branch is reachable from the merge' '
- before_commit=$(git rev-list --all --grep="trunk commit before merging trunk to b2")
- merge_commit=$(git rev-list --all --grep="Merge trunk to b2")
- not_reachable=$(git rev-list -1 $before_commit --not $merge_commit)
+ before_commit=$(git rev-list --all --grep="trunk commit before merging trunk to b2") &&
+ merge_commit=$(git rev-list --all --grep="Merge trunk to b2") &&
+ not_reachable=$(git rev-list -1 $before_commit --not $merge_commit) &&
[ -z "$not_reachable" ]
'
test_expect_success 'merging two branches in one commit is detected correctly' '
- f1_commit=$(git rev-list --all --grep="make f1 branch from trunk")
- f2_commit=$(git rev-list --all --grep="make f2 branch from trunk")
- merge_commit=$(git rev-list --all --grep="Merge f1 and f2 to trunk")
- not_reachable=$(git rev-list -1 $f1_commit $f2_commit --not $merge_commit)
+ f1_commit=$(git rev-list --all --grep="make f1 branch from trunk") &&
+ f2_commit=$(git rev-list --all --grep="make f2 branch from trunk") &&
+ merge_commit=$(git rev-list --all --grep="Merge f1 and f2 to trunk") &&
+ not_reachable=$(git rev-list -1 $f1_commit $f2_commit --not $merge_commit) &&
[ -z "$not_reachable" ]
'
test_expect_failure 'everything got merged in the end' '
- unmerged=$(git rev-list --all --not master)
+ unmerged=$(git rev-list --all --not master) &&
[ -z "$unmerged" ]
'
test_description='git svn merge detection'
. ./lib-git-svn.sh
+svn_ver="$(svn --version --quiet)"
+case $svn_ver in
+0.* | 1.[0-4].*)
+ skip_all="skipping git-svn test - SVN too old ($svn_ver)"
+ test_done
+ ;;
+esac
+
test_expect_success 'initialize source svn repo' '
svn_cmd mkdir -m x "$svnrepo"/trunk &&
svn_cmd mkdir -m x "$svnrepo"/branches &&
diff --git a/t/t9158-git-svn-mergeinfo.sh b/t/t9158-git-svn-mergeinfo.sh
--- /dev/null
@@ -0,0 +1,41 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Steven Walter
+#
+
+test_description='git svn mergeinfo propagation'
+
+. ./lib-git-svn.sh
+
+say 'define NO_SVN_TESTS to skip git svn tests'
+
+test_expect_success 'initialize source svn repo' '
+ svn_cmd mkdir -m x "$svnrepo"/trunk &&
+ svn_cmd co "$svnrepo"/trunk "$SVN_TREE" &&
+ (
+ cd "$SVN_TREE" &&
+ touch foo &&
+ svn_cmd add foo &&
+ svn_cmd commit -m "initial commit"
+ ) &&
+ rm -rf "$SVN_TREE"
+'
+
+test_expect_success 'clone svn repo' '
+ git svn init "$svnrepo"/trunk &&
+ git svn fetch
+'
+
+test_expect_success 'change svn:mergeinfo' '
+ touch bar &&
+ git add bar &&
+ git commit -m "bar" &&
+ git svn dcommit --mergeinfo="/branches/foo:1-10"
+'
+
+test_expect_success 'verify svn:mergeinfo' '
+ mergeinfo=$(svn_cmd propget svn:mergeinfo "$svnrepo"/trunk)
+ test "$mergeinfo" = "/branches/foo:1-10"
+'
+
+test_done
diff --git a/t/t9300-fast-import.sh b/t/t9300-fast-import.sh
index 385e78c22c0e00b9e81117a5ed7378298460c47b..6b1ba6c858562b7c085951fb1d69d4d2371e866c 100755 (executable)
--- a/t/t9300-fast-import.sh
+++ b/t/t9300-fast-import.sh
. ./test-lib.sh
. "$TEST_DIRECTORY"/diff-lib.sh ;# test-lib chdir's into trash
+# Print $1 bytes from stdin to stdout.
+#
+# This could be written as "head -c $1", but IRIX "head" does not
+# support the -c option.
+head_c () {
+ perl -e '
+ my $len = $ARGV[1];
+ while ($len > 0) {
+ my $s;
+ my $nread = sysread(STDIN, $s, $len);
+ die "cannot read: $!" unless defined($nread);
+ print $s;
+ $len -= $nread;
+ }
+ ' - "$1"
+}
+
file2_data='file2
second line of EOF'
file6_data='#!/bin/sh
echo "$@"'
+>empty
+
+test_expect_success 'setup: have pipes?' '
+ rm -f frob &&
+ if mkfifo frob
+ then
+ test_set_prereq PIPE
+ fi
+'
+
###
### series A
###
test_tick
+
+test_expect_success 'empty stream succeeds' '
+ git fast-import </dev/null
+'
+
cat >input <<INPUT_END
blob
mark :2
'for p in .git/objects/pack/*.pack;do git verify-pack $p||exit;done'
test_expect_success \
'C: validate reuse existing blob' \
- 'test $newf = `git rev-parse --verify branch:file2/newf`
+ 'test $newf = `git rev-parse --verify branch:file2/newf` &&
test $oldf = `git rev-parse --verify branch:file2/oldf`'
cat >expect <<EOF
git diff-tree -C --find-copies-harder -r N4^ N4 >actual &&
compare_diff_raw expect actual'
+test_expect_success PIPE 'N: read and copy directory' '
+ cat >expect <<-\EOF
+ :100755 100755 f1fb5da718392694d0076d677d6d0e364c79b0bc f1fb5da718392694d0076d677d6d0e364c79b0bc C100 file2/newf file3/newf
+ :100644 100644 7123f7f44e39be127c5eb701e5968176ee9d78b1 7123f7f44e39be127c5eb701e5968176ee9d78b1 C100 file2/oldf file3/oldf
+ EOF
+ git update-ref -d refs/heads/N4 &&
+ rm -f backflow &&
+ mkfifo backflow &&
+ (
+ exec <backflow &&
+ cat <<-EOF &&
+ commit refs/heads/N4
+ committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+ data <<COMMIT
+ copy by tree hash, part 2
+ COMMIT
+
+ from refs/heads/branch^0
+ ls "file2"
+ EOF
+ read mode type tree filename &&
+ echo "M 040000 $tree file3"
+ ) |
+ git fast-import --cat-blob-fd=3 3>backflow &&
+ git diff-tree -C --find-copies-harder -r N4^ N4 >actual &&
+ compare_diff_raw expect actual
+'
+
+test_expect_success PIPE 'N: empty directory reads as missing' '
+ cat <<-\EOF >expect &&
+ OBJNAME
+ :000000 100644 OBJNAME OBJNAME A unrelated
+ EOF
+ echo "missing src" >expect.response &&
+ git update-ref -d refs/heads/read-empty &&
+ rm -f backflow &&
+ mkfifo backflow &&
+ (
+ exec <backflow &&
+ cat <<-EOF &&
+ commit refs/heads/read-empty
+ committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+ data <<COMMIT
+ read "empty" (missing) directory
+ COMMIT
+
+ M 100644 inline src/greeting
+ data <<BLOB
+ hello
+ BLOB
+ C src/greeting dst1/non-greeting
+ C src/greeting unrelated
+ # leave behind "empty" src directory
+ D src/greeting
+ ls "src"
+ EOF
+ read -r line &&
+ printf "%s\n" "$line" >response &&
+ cat <<-\EOF
+ D dst1
+ D dst2
+ EOF
+ ) |
+ git fast-import --cat-blob-fd=3 3>backflow &&
+ test_cmp expect.response response &&
+ git rev-list read-empty |
+ git diff-tree -r --root --stdin |
+ sed "s/$_x40/OBJNAME/g" >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success \
+ 'N: copy root directory by tree hash' \
+ 'cat >expect <<-\EOF &&
+ :100755 000000 f1fb5da718392694d0076d677d6d0e364c79b0bc 0000000000000000000000000000000000000000 D file3/newf
+ :100644 000000 7123f7f44e39be127c5eb701e5968176ee9d78b1 0000000000000000000000000000000000000000 D file3/oldf
+ EOF
+ root=$(git rev-parse refs/heads/branch^0^{tree}) &&
+ cat >input <<-INPUT_END &&
+ commit refs/heads/N6
+ committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+ data <<COMMIT
+ copy root directory by tree hash
+ COMMIT
+
+ from refs/heads/branch^0
+ M 040000 $root ""
+ INPUT_END
+ git fast-import <input &&
+ git diff-tree -C --find-copies-harder -r N4 N6 >actual &&
+ compare_diff_raw expect actual'
+
test_expect_success \
'N: delete directory by copying' \
'cat >expect <<-\EOF &&
git diff-tree -C --find-copies-harder -r N5^^ N5 >actual &&
compare_diff_raw expect actual'
+test_expect_success \
+ 'N: reject foo/ syntax' \
+ 'subdir=$(git rev-parse refs/heads/branch^0:file2) &&
+ test_must_fail git fast-import <<-INPUT_END
+ commit refs/heads/N5B
+ committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+ data <<COMMIT
+ copy with invalid syntax
+ COMMIT
+
+ from refs/heads/branch^0
+ M 040000 $subdir file3/
+ INPUT_END'
+
+test_expect_success \
+ 'N: copy to root by id and modify' \
+ 'echo "hello, world" >expect.foo &&
+ echo hello >expect.bar &&
+ git fast-import <<-SETUP_END &&
+ commit refs/heads/N7
+ committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+ data <<COMMIT
+ hello, tree
+ COMMIT
+
+ deleteall
+ M 644 inline foo/bar
+ data <<EOF
+ hello
+ EOF
+ SETUP_END
+
+ tree=$(git rev-parse --verify N7:) &&
+ git fast-import <<-INPUT_END &&
+ commit refs/heads/N8
+ committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+ data <<COMMIT
+ copy to root by id and modify
+ COMMIT
+
+ M 040000 $tree ""
+ M 644 inline foo/foo
+ data <<EOF
+ hello, world
+ EOF
+ INPUT_END
+ git show N8:foo/foo >actual.foo &&
+ git show N8:foo/bar >actual.bar &&
+ test_cmp expect.foo actual.foo &&
+ test_cmp expect.bar actual.bar'
+
+test_expect_success \
+ 'N: extract subtree' \
+ 'branch=$(git rev-parse --verify refs/heads/branch^{tree}) &&
+ cat >input <<-INPUT_END &&
+ commit refs/heads/N9
+ committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+ data <<COMMIT
+ extract subtree branch:newdir
+ COMMIT
+
+ M 040000 $branch ""
+ C "newdir" ""
+ INPUT_END
+ git fast-import <input &&
+ git diff --exit-code branch:newdir N9'
+
+test_expect_success \
+ 'N: modify subtree, extract it, and modify again' \
+ 'echo hello >expect.baz &&
+ echo hello, world >expect.qux &&
+ git fast-import <<-SETUP_END &&
+ commit refs/heads/N10
+ committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+ data <<COMMIT
+ hello, tree
+ COMMIT
+
+ deleteall
+ M 644 inline foo/bar/baz
+ data <<EOF
+ hello
+ EOF
+ SETUP_END
+
+ tree=$(git rev-parse --verify N10:) &&
+ git fast-import <<-INPUT_END &&
+ commit refs/heads/N11
+ committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+ data <<COMMIT
+ copy to root by id and modify
+ COMMIT
+
+ M 040000 $tree ""
+ M 100644 inline foo/bar/qux
+ data <<EOF
+ hello, world
+ EOF
+ R "foo" ""
+ C "bar/qux" "bar/quux"
+ INPUT_END
+ git show N11:bar/baz >actual.baz &&
+ git show N11:bar/qux >actual.qux &&
+ git show N11:bar/quux >actual.quux &&
+ test_cmp expect.baz actual.baz &&
+ test_cmp expect.qux actual.qux &&
+ test_cmp expect.qux actual.quux'
+
###
### series O
###
'cat input | git fast-import --export-marks=other.marks &&
grep :1 other.marks'
+test_expect_success 'R: catch typo in marks file name' '
+ test_must_fail git fast-import --import-marks=nonexistent.marks </dev/null &&
+ echo "feature import-marks=nonexistent.marks" |
+ test_must_fail git fast-import
+'
+
+test_expect_success 'R: import and output marks can be the same file' '
+ rm -f io.marks &&
+ blob=$(echo hi | git hash-object --stdin) &&
+ cat >expect <<-EOF &&
+ :1 $blob
+ :2 $blob
+ EOF
+ git fast-import --export-marks=io.marks <<-\EOF &&
+ blob
+ mark :1
+ data 3
+ hi
+
+ EOF
+ git fast-import --import-marks=io.marks --export-marks=io.marks <<-\EOF &&
+ blob
+ mark :2
+ data 3
+ hi
+
+ EOF
+ test_cmp expect io.marks
+'
+
+test_expect_success 'R: --import-marks=foo --output-marks=foo to create foo fails' '
+ rm -f io.marks &&
+ test_must_fail git fast-import --import-marks=io.marks --export-marks=io.marks <<-\EOF
+ blob
+ mark :1
+ data 3
+ hi
+
+ EOF
+'
+
+test_expect_success 'R: --import-marks-if-exists' '
+ rm -f io.marks &&
+ blob=$(echo hi | git hash-object --stdin) &&
+ echo ":1 $blob" >expect &&
+ git fast-import --import-marks-if-exists=io.marks --export-marks=io.marks <<-\EOF &&
+ blob
+ mark :1
+ data 3
+ hi
+
+ EOF
+ test_cmp expect io.marks
+'
+
cat >input << EOF
feature import-marks=marks.out
feature export-marks=marks.new
test_cmp marks.new non-relative.out
'
+test_expect_success 'R: feature ls supported' '
+ echo "feature ls" |
+ git fast-import
+'
+
+test_expect_success 'R: feature cat-blob supported' '
+ echo "feature cat-blob" |
+ git fast-import
+'
+
+test_expect_success 'R: cat-blob-fd must be a nonnegative integer' '
+ test_must_fail git fast-import --cat-blob-fd=-1 </dev/null
+'
+
+test_expect_success 'R: print old blob' '
+ blob=$(echo "yes it can" | git hash-object -w --stdin) &&
+ cat >expect <<-EOF &&
+ ${blob} blob 11
+ yes it can
+
+ EOF
+ echo "cat-blob $blob" |
+ git fast-import --cat-blob-fd=6 6>actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'R: in-stream cat-blob-fd not respected' '
+ echo hello >greeting &&
+ blob=$(git hash-object -w greeting) &&
+ cat >expect <<-EOF &&
+ ${blob} blob 6
+ hello
+
+ EOF
+ git fast-import --cat-blob-fd=3 3>actual.3 >actual.1 <<-EOF &&
+ cat-blob $blob
+ EOF
+ test_cmp expect actual.3 &&
+ test_cmp empty actual.1 &&
+ git fast-import 3>actual.3 >actual.1 <<-EOF &&
+ option cat-blob-fd=3
+ cat-blob $blob
+ EOF
+ test_cmp empty actual.3 &&
+ test_cmp expect actual.1
+'
+
+test_expect_success 'R: print new blob' '
+ blob=$(echo "yep yep yep" | git hash-object --stdin) &&
+ cat >expect <<-EOF &&
+ ${blob} blob 12
+ yep yep yep
+
+ EOF
+ git fast-import --cat-blob-fd=6 6>actual <<-\EOF &&
+ blob
+ mark :1
+ data <<BLOB_END
+ yep yep yep
+ BLOB_END
+ cat-blob :1
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'R: print new blob by sha1' '
+ blob=$(echo "a new blob named by sha1" | git hash-object --stdin) &&
+ cat >expect <<-EOF &&
+ ${blob} blob 25
+ a new blob named by sha1
+
+ EOF
+ git fast-import --cat-blob-fd=6 6>actual <<-EOF &&
+ blob
+ data <<BLOB_END
+ a new blob named by sha1
+ BLOB_END
+ cat-blob $blob
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'setup: big file' '
+ (
+ echo "the quick brown fox jumps over the lazy dog" >big &&
+ for i in 1 2 3
+ do
+ cat big big big big >bigger &&
+ cat bigger bigger bigger bigger >big ||
+ exit
+ done
+ )
+'
+
+test_expect_success 'R: print two blobs to stdout' '
+ blob1=$(git hash-object big) &&
+ blob1_len=$(wc -c <big) &&
+ blob2=$(echo hello | git hash-object --stdin) &&
+ {
+ echo ${blob1} blob $blob1_len &&
+ cat big &&
+ cat <<-EOF
+
+ ${blob2} blob 6
+ hello
+
+ EOF
+ } >expect &&
+ {
+ cat <<-\END_PART1 &&
+ blob
+ mark :1
+ data <<data_end
+ END_PART1
+ cat big &&
+ cat <<-\EOF
+ data_end
+ blob
+ mark :2
+ data <<data_end
+ hello
+ data_end
+ cat-blob :1
+ cat-blob :2
+ EOF
+ } |
+ git fast-import >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success PIPE 'R: copy using cat-file' '
+ expect_id=$(git hash-object big) &&
+ expect_len=$(wc -c <big) &&
+ echo $expect_id blob $expect_len >expect.response &&
+
+ rm -f blobs &&
+ cat >frontend <<-\FRONTEND_END &&
+ #!/bin/sh
+ FRONTEND_END
+
+ mkfifo blobs &&
+ (
+ export GIT_COMMITTER_NAME GIT_COMMITTER_EMAIL GIT_COMMITTER_DATE &&
+ cat <<-\EOF &&
+ feature cat-blob
+ blob
+ mark :1
+ data <<BLOB
+ EOF
+ cat big &&
+ cat <<-\EOF &&
+ BLOB
+ cat-blob :1
+ EOF
+
+ read blob_id type size <&3 &&
+ echo "$blob_id $type $size" >response &&
+ head_c $size >blob <&3 &&
+ read newline <&3 &&
+
+ cat <<-EOF &&
+ commit refs/heads/copied
+ committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+ data <<COMMIT
+ copy big file as file3
+ COMMIT
+ M 644 inline file3
+ data <<BLOB
+ EOF
+ cat blob &&
+ echo BLOB
+ ) 3<blobs |
+ git fast-import --cat-blob-fd=3 3>blobs &&
+ git show copied:file3 >actual &&
+ test_cmp expect.response response &&
+ test_cmp big actual
+'
+
+test_expect_success PIPE 'R: print blob mid-commit' '
+ rm -f blobs &&
+ echo "A blob from _before_ the commit." >expect &&
+ mkfifo blobs &&
+ (
+ exec 3<blobs &&
+ cat <<-EOF &&
+ feature cat-blob
+ blob
+ mark :1
+ data <<BLOB
+ A blob from _before_ the commit.
+ BLOB
+ commit refs/heads/temporary
+ committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+ data <<COMMIT
+ Empty commit
+ COMMIT
+ cat-blob :1
+ EOF
+
+ read blob_id type size <&3 &&
+ head_c $size >actual <&3 &&
+ read newline <&3 &&
+
+ echo
+ ) |
+ git fast-import --cat-blob-fd=3 3>blobs &&
+ test_cmp expect actual
+'
+
+test_expect_success PIPE 'R: print staged blob within commit' '
+ rm -f blobs &&
+ echo "A blob from _within_ the commit." >expect &&
+ mkfifo blobs &&
+ (
+ exec 3<blobs &&
+ cat <<-EOF &&
+ feature cat-blob
+ commit refs/heads/within
+ committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+ data <<COMMIT
+ Empty commit
+ COMMIT
+ M 644 inline within
+ data <<BLOB
+ A blob from _within_ the commit.
+ BLOB
+ EOF
+
+ to_get=$(
+ echo "A blob from _within_ the commit." |
+ git hash-object --stdin
+ ) &&
+ echo "cat-blob $to_get" &&
+
+ read blob_id type size <&3 &&
+ head_c $size >actual <&3 &&
+ read newline <&3 &&
+
+ echo deleteall
+ ) |
+ git fast-import --cat-blob-fd=3 3>blobs &&
+ test_cmp expect actual
+'
+
cat >input << EOF
option git quiet
blob
EOF
-touch empty
-
test_expect_success 'R: quiet option results in no stats being output' '
cat input | git fast-import 2> output &&
test_cmp empty output
test_must_fail git fast-import --non-existing-option < /dev/null
'
+test_expect_success 'R: die on invalid option argument' '
+ echo "option git active-branches=-5" |
+ test_must_fail git fast-import &&
+ echo "option git depth=" |
+ test_must_fail git fast-import &&
+ test_must_fail git fast-import --depth="5 elephants" </dev/null
+'
+
cat >input <<EOF
option non-existing-vcs non-existing-option
EOF
index 164edf0c3da8d89513782b842ebdfb4dc3d84c7e..463254c72734beaf74948b6c424367ef4fea9d1a 100755 (executable)
INPUT_END
+whitespace=" "
+
cat >expect <<EXPECT_END
fourth commit
pre-prefix of note for fourth commit
+$whitespace
prefix of note for fourth commit
+$whitespace
third note for fourth commit
third commit
prefix of note for third commit
+$whitespace
third note for third commit
second commit
third note for second commit
diff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh
index 8c8e679468f4b191f93ca68a973d4d58fa1b72d2..f823c05305e5021a8cca98d49256af3b1c008fb9 100755 (executable)
--- a/t/t9350-fast-export.sh
+++ b/t/t9350-fast-export.sh
test_tick &&
git tag rein &&
git checkout -b wer HEAD^ &&
- echo lange > file2
+ echo lange > file2 &&
test_tick &&
git commit -m sitzt file2 &&
test_tick &&
index 36c457e7f2312774223f853aab0e3b055659e916..9199550ef4ffa39e4ce8bdb36badfd723e95e55f 100755 (executable)
# as argument to co -d
test_expect_success 'basic checkout' \
'GIT_CONFIG="$git_config" cvs -Q co -d cvswork master &&
- test "$(echo $(grep -v ^D cvswork/CVS/Entries|cut -d/ -f2,3,5 | head -n 1))" = "empty/1.1/"
+ test "$(echo $(grep -v ^D cvswork/CVS/Entries|cut -d/ -f2,3,5 | head -n 1))" = "empty/1.1/" &&
test "$(echo $(grep -v ^D cvswork/CVS/Entries|cut -d/ -f2,3,5 | sed -ne \$p))" = "secondrootfile/1.1/"'
#------------------------
index 1bbfd824e50ba44f6684cbce3f220a99f17f6cc6..ff6d6fb473fe0b00a4e2135395cdf89c43250a1a 100755 (executable)
mkdir subdir &&
echo "Another text file" > subdir/file.h &&
echo "Another binary: Q (this time CR)" | q_to_cr > subdir/withCr.bin &&
- echo "Mixed up NUL, but marked text: Q <- there" | q_to_nul > mixedUp.c
+ echo "Mixed up NUL, but marked text: Q <- there" | q_to_nul > mixedUp.c &&
echo "Unspecified" > subdir/unspecified.other &&
echo "/*.bin -crlf" > .gitattributes &&
echo "/*.c crlf" >> .gitattributes &&
index 4f2b9b062b08302aedab42dcdd0a33776559c05e..afac5b56a87f3dcc6467b8ad260e1b59d041eb03 100755 (executable)
test_expect_success \
'no commits: projects_list (implicit)' \
'gitweb_run'
-test_debug 'cat gitweb.log'
test_expect_success \
'no commits: projects_index' \
'gitweb_run "a=project_index"'
-test_debug 'cat gitweb.log'
test_expect_success \
'no commits: .git summary (implicit)' \
'gitweb_run "p=.git"'
-test_debug 'cat gitweb.log'
test_expect_success \
'no commits: .git commit (implicit HEAD)' \
'gitweb_run "p=.git;a=commit"'
-test_debug 'cat gitweb.log'
test_expect_success \
'no commits: .git commitdiff (implicit HEAD)' \
'gitweb_run "p=.git;a=commitdiff"'
-test_debug 'cat gitweb.log'
test_expect_success \
'no commits: .git tree (implicit HEAD)' \
'gitweb_run "p=.git;a=tree"'
-test_debug 'cat gitweb.log'
test_expect_success \
'no commits: .git heads' \
'gitweb_run "p=.git;a=heads"'
-test_debug 'cat gitweb.log'
test_expect_success \
'no commits: .git tags' \
'gitweb_run "p=.git;a=tags"'
-test_debug 'cat gitweb.log'
# ----------------------------------------------------------------------
test_expect_success \
'projects_list (implicit)' \
'gitweb_run'
-test_debug 'cat gitweb.log'
test_expect_success \
'projects_index' \
'gitweb_run "a=project_index"'
-test_debug 'cat gitweb.log'
test_expect_success \
'.git summary (implicit)' \
'gitweb_run "p=.git"'
-test_debug 'cat gitweb.log'
test_expect_success \
'.git commit (implicit HEAD)' \
'gitweb_run "p=.git;a=commit"'
-test_debug 'cat gitweb.log'
test_expect_success \
'.git commitdiff (implicit HEAD, root commit)' \
'gitweb_run "p=.git;a=commitdiff"'
-test_debug 'cat gitweb.log'
test_expect_success \
'.git commitdiff_plain (implicit HEAD, root commit)' \
'gitweb_run "p=.git;a=commitdiff_plain"'
-test_debug 'cat gitweb.log'
test_expect_success \
'.git commit (HEAD)' \
'gitweb_run "p=.git;a=commit;h=HEAD"'
-test_debug 'cat gitweb.log'
test_expect_success \
'.git tree (implicit HEAD)' \
'gitweb_run "p=.git;a=tree"'
-test_debug 'cat gitweb.log'
test_expect_success \
'.git blob (file)' \
'gitweb_run "p=.git;a=blob;f=file"'
-test_debug 'cat gitweb.log'
test_expect_success \
'.git blob_plain (file)' \
'gitweb_run "p=.git;a=blob_plain;f=file"'
-test_debug 'cat gitweb.log'
# ----------------------------------------------------------------------
# nonexistent objects
test_expect_success \
'.git commit (non-existent)' \
'gitweb_run "p=.git;a=commit;h=non-existent"'
-test_debug 'cat gitweb.log'
test_expect_success \
'.git commitdiff (non-existent)' \
'gitweb_run "p=.git;a=commitdiff;h=non-existent"'
-test_debug 'cat gitweb.log'
test_expect_success \
'.git commitdiff (non-existent vs HEAD)' \
'gitweb_run "p=.git;a=commitdiff;hp=non-existent;h=HEAD"'
-test_debug 'cat gitweb.log'
test_expect_success \
'.git tree (0000000000000000000000000000000000000000)' \
'gitweb_run "p=.git;a=tree;h=0000000000000000000000000000000000000000"'
-test_debug 'cat gitweb.log'
test_expect_success \
'.git tag (0000000000000000000000000000000000000000)' \
'gitweb_run "p=.git;a=tag;h=0000000000000000000000000000000000000000"'
-test_debug 'cat gitweb.log'
test_expect_success \
'.git blob (non-existent)' \
'gitweb_run "p=.git;a=blob;f=non-existent"'
-test_debug 'cat gitweb.log'
test_expect_success \
'.git blob_plain (non-existent)' \
'gitweb_run "p=.git;a=blob_plain;f=non-existent"'
-test_debug 'cat gitweb.log'
# ----------------------------------------------------------------------
test_expect_success \
'commitdiff(0): root' \
'gitweb_run "p=.git;a=commitdiff"'
-test_debug 'cat gitweb.log'
test_expect_success \
'commitdiff(0): file added' \
git add new_file &&
git commit -a -m "File added." &&
gitweb_run "p=.git;a=commitdiff"'
-test_debug 'cat gitweb.log'
test_expect_success \
'commitdiff(0): mode change' \
'test_chmod +x new_file &&
git commit -a -m "Mode changed." &&
gitweb_run "p=.git;a=commitdiff"'
-test_debug 'cat gitweb.log'
test_expect_success \
'commitdiff(0): file renamed' \
'git mv new_file renamed_file &&
git commit -a -m "File renamed." &&
gitweb_run "p=.git;a=commitdiff"'
-test_debug 'cat gitweb.log'
test_expect_success SYMLINKS \
'commitdiff(0): file to symlink' \
ln -s file renamed_file &&
git commit -a -m "File to symlink." &&
gitweb_run "p=.git;a=commitdiff"'
-test_debug 'cat gitweb.log'
test_expect_success \
'commitdiff(0): file deleted' \
rm -f renamed_file &&
git commit -a -m "File removed." &&
gitweb_run "p=.git;a=commitdiff"'
-test_debug 'cat gitweb.log'
test_expect_success \
'commitdiff(0): file copied / new file' \
git add file2 &&
git commit -a -m "File copied." &&
gitweb_run "p=.git;a=commitdiff"'
-test_debug 'cat gitweb.log'
test_expect_success \
'commitdiff(0): mode change and modified' \
test_chmod +x file2 &&
git commit -a -m "Mode change and modification." &&
gitweb_run "p=.git;a=commitdiff"'
-test_debug 'cat gitweb.log'
test_expect_success \
'commitdiff(0): renamed and modified' \
echo "Propter nomen suum." >> file3 &&
git commit -a -m "File rename and modification." &&
gitweb_run "p=.git;a=commitdiff"'
-test_debug 'cat gitweb.log'
test_expect_success \
'commitdiff(0): renamed, mode change and modified' \
test_chmod +x file2 &&
git commit -a -m "File rename, mode change and modification." &&
gitweb_run "p=.git;a=commitdiff"'
-test_debug 'cat gitweb.log'
# ----------------------------------------------------------------------
# commitdiff testing (taken from t4114-apply-typechange.sh)
test_expect_success \
'commitdiff(2): file renamed from foo to foo/baz' \
'gitweb_run "p=.git;a=commitdiff;hp=initial;h=foo-baz-renamed-from-foo"'
-test_debug 'cat gitweb.log'
test_expect_success \
'commitdiff(2): file renamed from foo/baz to foo' \
'gitweb_run "p=.git;a=commitdiff;hp=foo-baz-renamed-from-foo;h=initial"'
-test_debug 'cat gitweb.log'
test_expect_success \
'commitdiff(2): directory becomes file' \
'gitweb_run "p=.git;a=commitdiff;hp=foo-becomes-a-directory;h=initial"'
-test_debug 'cat gitweb.log'
test_expect_success \
'commitdiff(2): file becomes directory' \
'gitweb_run "p=.git;a=commitdiff;hp=initial;h=foo-becomes-a-directory"'
-test_debug 'cat gitweb.log'
test_expect_success \
'commitdiff(2): file becomes symlink' \
'gitweb_run "p=.git;a=commitdiff;hp=initial;h=foo-symlinked-to-bar"'
-test_debug 'cat gitweb.log'
test_expect_success \
'commitdiff(2): symlink becomes file' \
'gitweb_run "p=.git;a=commitdiff;hp=foo-symlinked-to-bar;h=foo-back-to-file"'
-test_debug 'cat gitweb.log'
test_expect_success \
'commitdiff(2): symlink becomes directory' \
'gitweb_run "p=.git;a=commitdiff;hp=foo-symlinked-to-bar;h=foo-becomes-a-directory"'
-test_debug 'cat gitweb.log'
test_expect_success \
'commitdiff(2): directory becomes symlink' \
'gitweb_run "p=.git;a=commitdiff;hp=foo-becomes-a-directory;h=foo-symlinked-to-bar"'
-test_debug 'cat gitweb.log'
# ----------------------------------------------------------------------
# commit, commitdiff: merge, large
test_expect_success \
'commit(0): merge commit' \
'gitweb_run "p=.git;a=commit"'
-test_debug 'cat gitweb.log'
test_expect_success \
'commitdiff(0): merge commit' \
'gitweb_run "p=.git;a=commitdiff"'
-test_debug 'cat gitweb.log'
test_expect_success \
'Prepare large commit' \
test_expect_success \
'commit(1): large commit' \
'gitweb_run "p=.git;a=commit;h=b"'
-test_debug 'cat gitweb.log'
test_expect_success \
'commitdiff(1): large commit' \
'gitweb_run "p=.git;a=commitdiff;h=b"'
-test_debug 'cat gitweb.log'
# ----------------------------------------------------------------------
# tags testing
git tag lightweight/tag-tree HEAD^{tree} &&
git tag lightweight/tag-blob HEAD:file &&
gitweb_run "p=.git;a=tags"'
-test_debug 'cat gitweb.log'
test_expect_success \
'tag: Tag to commit object' \
'gitweb_run "p=.git;a=tag;h=tag-commit"'
-test_debug 'cat gitweb.log'
test_expect_success \
'tag: on lightweight tag (invalid)' \
'gitweb_run "p=.git;a=tag;h=lightweight/tag-commit"'
-test_debug 'cat gitweb.log'
# ----------------------------------------------------------------------
# logs
test_expect_success \
'logs: log (implicit HEAD)' \
'gitweb_run "p=.git;a=log"'
-test_debug 'cat gitweb.log'
test_expect_success \
'logs: shortlog (implicit HEAD)' \
'gitweb_run "p=.git;a=shortlog"'
-test_debug 'cat gitweb.log'
test_expect_success \
'logs: history (implicit HEAD, file)' \
'gitweb_run "p=.git;a=history;f=file"'
-test_debug 'cat gitweb.log'
test_expect_success \
'logs: history (implicit HEAD, non-existent file)' \
'gitweb_run "p=.git;a=history;f=non-existent"'
-test_debug 'cat gitweb.log'
test_expect_success \
'logs: history (implicit HEAD, deleted file)' \
git rm deleted_file &&
git commit -m "Delete file" &&
gitweb_run "p=.git;a=history;f=deleted_file"'
-test_debug 'cat gitweb.log'
# ----------------------------------------------------------------------
# path_info links
test_expect_success \
'path_info: project' \
'gitweb_run "" "/.git"'
-test_debug 'cat gitweb.log'
test_expect_success \
'path_info: project/branch' \
'gitweb_run "" "/.git/b"'
-test_debug 'cat gitweb.log'
test_expect_success \
'path_info: project/branch:file' \
'gitweb_run "" "/.git/master:file"'
-test_debug 'cat gitweb.log'
test_expect_success \
'path_info: project/branch:dir/' \
'gitweb_run "" "/.git/master:foo/"'
-test_debug 'cat gitweb.log'
test_expect_success \
'path_info: project/branch:file (non-existent)' \
'gitweb_run "" "/.git/master:non-existent"'
-test_debug 'cat gitweb.log'
test_expect_success \
'path_info: project/branch:dir/ (non-existent)' \
'gitweb_run "" "/.git/master:non-existent/"'
-test_debug 'cat gitweb.log'
test_expect_success \
'path_info: project/branch:/file' \
'gitweb_run "" "/.git/master:/file"'
-test_debug 'cat gitweb.log'
test_expect_success \
'path_info: project/:/file (implicit HEAD)' \
'gitweb_run "" "/.git/:/file"'
-test_debug 'cat gitweb.log'
test_expect_success \
'path_info: project/:/ (implicit HEAD, top tree)' \
'gitweb_run "" "/.git/:/"'
-test_debug 'cat gitweb.log'
# ----------------------------------------------------------------------
test_expect_success \
'feeds: OPML' \
'gitweb_run "a=opml"'
-test_debug 'cat gitweb.log'
test_expect_success \
'feed: RSS' \
'gitweb_run "p=.git;a=rss"'
-test_debug 'cat gitweb.log'
test_expect_success \
'feed: Atom' \
'gitweb_run "p=.git;a=atom"'
-test_debug 'cat gitweb.log'
# ----------------------------------------------------------------------
# encoding/decoding
test_expect_success \
'encode(commit): utf8' \
'. "$TEST_DIRECTORY"/t3901-utf8.txt &&
+ test_when_finished "GIT_AUTHOR_NAME=\"A U Thor\"" &&
+ test_when_finished "GIT_COMMITTER_NAME=\"C O Mitter\"" &&
echo "UTF-8" >> file &&
git add file &&
git commit -F "$TEST_DIRECTORY"/t3900/1-UTF-8.txt &&
gitweb_run "p=.git;a=commit"'
-test_debug 'cat gitweb.log'
test_expect_success \
'encode(commit): iso-8859-1' \
'. "$TEST_DIRECTORY"/t3901-8859-1.txt &&
+ test_when_finished "GIT_AUTHOR_NAME=\"A U Thor\"" &&
+ test_when_finished "GIT_COMMITTER_NAME=\"C O Mitter\"" &&
echo "ISO-8859-1" >> file &&
git add file &&
git config i18n.commitencoding ISO-8859-1 &&
+ test_when_finished "git config --unset i18n.commitencoding" &&
git commit -F "$TEST_DIRECTORY"/t3900/ISO8859-1.txt &&
- git config --unset i18n.commitencoding &&
gitweb_run "p=.git;a=commit"'
-test_debug 'cat gitweb.log'
test_expect_success \
'encode(log): utf-8 and iso-8859-1' \
'gitweb_run "p=.git;a=log"'
-test_debug 'cat gitweb.log'
# ----------------------------------------------------------------------
# extra options
test_expect_success \
'opt: log --no-merges' \
'gitweb_run "p=.git;a=log;opt=--no-merges"'
-test_debug 'cat gitweb.log'
test_expect_success \
'opt: atom --no-merges' \
'gitweb_run "p=.git;a=log;opt=--no-merges"'
-test_debug 'cat gitweb.log'
test_expect_success \
'opt: "file" history --no-merges' \
'gitweb_run "p=.git;a=history;f=file;opt=--no-merges"'
-test_debug 'cat gitweb.log'
test_expect_success \
'opt: log --no-such-option (invalid option)' \
'gitweb_run "p=.git;a=log;opt=--no-such-option"'
-test_debug 'cat gitweb.log'
test_expect_success \
'opt: tree --no-merges (invalid option for action)' \
'gitweb_run "p=.git;a=tree;opt=--no-merges"'
-test_debug 'cat gitweb.log'
# ----------------------------------------------------------------------
# testing config_to_multi / cloneurl
test_expect_success \
'URL: no project URLs, no base URL' \
'gitweb_run "p=.git;a=summary"'
-test_debug 'cat gitweb.log'
test_expect_success \
'URL: project URLs via gitweb.url' \
'git config --add gitweb.url git://example.com/git/trash.git &&
git config --add gitweb.url http://example.com/git/trash.git &&
gitweb_run "p=.git;a=summary"'
-test_debug 'cat gitweb.log'
cat >.git/cloneurl <<\EOF
git://example.com/git/trash.git
test_expect_success \
'URL: project URLs via cloneurl file' \
'gitweb_run "p=.git;a=summary"'
-test_debug 'cat gitweb.log'
# ----------------------------------------------------------------------
# gitweb config and repo config
test_expect_success \
'config override: projects list (implicit)' \
'gitweb_run'
-test_debug 'cat gitweb.log'
test_expect_success \
'config override: tree view, features not overridden in repo config' \
'gitweb_run "p=.git;a=tree"'
-test_debug 'cat gitweb.log'
test_expect_success \
'config override: tree view, features disabled in repo config' \
git config gitweb.snapshot none &&
git config gitweb.avatar gravatar &&
gitweb_run "p=.git;a=tree"'
-test_debug 'cat gitweb.log'
test_expect_success \
'config override: tree view, features enabled in repo config (1)' \
'git config gitweb.blame yes &&
git config gitweb.snapshot "zip,tgz, tbz2" &&
gitweb_run "p=.git;a=tree"'
-test_debug 'cat gitweb.log'
cat >.git/config <<\EOF
# testing noval and alternate separator
test_expect_success \
'config override: tree view, features enabled in repo config (2)' \
'gitweb_run "p=.git;a=tree"'
-test_debug 'cat gitweb.log'
# ----------------------------------------------------------------------
# non-ASCII in README.html
'echo "<b>UTF-8 example:</b><br />" > .git/README.html &&
cat "$TEST_DIRECTORY"/t3900/1-UTF-8.txt >> .git/README.html &&
gitweb_run "p=.git;a=summary"'
-test_debug 'cat gitweb.log'
# ----------------------------------------------------------------------
# syntax highlighting
-cat >>gitweb_config.perl <<\EOF
-$feature{'highlight'}{'override'} = 1;
-EOF
highlight --version >/dev/null 2>&1
if [ $? -eq 127 ]; then
say "Skipping syntax highlighting test, because 'highlight' was not found"
else
test_set_prereq HIGHLIGHT
+ cat >>gitweb_config.perl <<-\EOF
+ our $highlight_bin = "highlight";
+ $feature{'highlight'}{'override'} = 1;
+ EOF
fi
test_expect_success HIGHLIGHT \
- 'syntax highlighting (no highlight)' \
+ 'syntax highlighting (no highlight, unknown syntax)' \
'git config gitweb.highlight yes &&
gitweb_run "p=.git;a=blob;f=file"'
-test_debug 'cat gitweb.log'
test_expect_success HIGHLIGHT \
- 'syntax highlighting (highlighted)' \
+ 'syntax highlighting (highlighted, shell script)' \
'git config gitweb.highlight yes &&
echo "#!/usr/bin/sh" > test.sh &&
git add test.sh &&
git commit -m "Add test.sh" &&
gitweb_run "p=.git;a=blob;f=test.sh"'
-test_debug 'cat gitweb.log'
test_done
index 18825aff89f5699afaabc34df0fd7baab5863747..26102ee9b0c36a87ba17a75b0ca644cc42e2c1c4 100755 (executable)
grep "Status: 503 Service Unavailable" gitweb.headers &&
grep "503 - The load average on the server is too high" gitweb.body
'
-test_debug 'cat gitweb.log' # just in case
test_debug 'cat gitweb.headers'
# turn off load checking
diff --git a/t/t9600-cvsimport.sh b/t/t9600-cvsimport.sh
index 432b82e3d5a361d0eba329f5d5c56c17009738db..4c384ff02333e1413cc67b1b4a6bed47ae3d00db 100755 (executable)
--- a/t/t9600-cvsimport.sh
+++ b/t/t9600-cvsimport.sh
test_expect_success PERL 'update git module' '
(cd module-git &&
- git cvsimport -a -R -z 0 module &&
+ git config cvsimport.trackRevisions true &&
+ git cvsimport -a -z 0 module &&
git merge origin
) &&
test_cmp module-cvs/o_fortuna module-git/o_fortuna
(cd module-git &&
git config cvsimport.module module &&
- git cvsimport -a -R -z0 &&
+ git config cvsimport.trackRevisions true &&
+ git cvsimport -a -z0 &&
git merge origin
) &&
test_cmp module-cvs/tick module-git/tick
$CVS co -d import-from-wt module &&
(cd import-from-wt &&
+ git config cvsimport.trackRevisions false &&
git cvsimport -a -z0 &&
echo 1 >expect &&
git log -1 --pretty=format:%s%n >actual &&
diff --git a/t/t9700/test.pl b/t/t9700/test.pl
index c15ca2d6479a814d7649a38a671a554b6ed8b68f..13ba96e21a9295805aed40c89ecd5178db6bfae4 100755 (executable)
--- a/t/t9700/test.pl
+++ b/t/t9700/test.pl
my $dir_commit = $r2->command_oneline('log', '-n1', '--pretty=format:%H', '.');
isnt($last_commit, $dir_commit, 'log . does not show last commit');
+# commands outside working tree
+chdir($abs_repo_dir . '/..');
+my $r3 = Git->repository(Directory => $abs_repo_dir);
+my $tmpfile3 = "$abs_repo_dir/file3.tmp";
+open TEMPFILE3, "+>$tmpfile3" or die "Can't open $tmpfile3: $!";
+is($r3->cat_blob($file1hash, \*TEMPFILE3), 15, "cat_blob(outside): size");
+close TEMPFILE3;
+unlink $tmpfile3;
+chdir($abs_repo_dir);
+
printf "1..%d\n", Test::More->builder->current_test;
my $is_passing = eval { Test::More->is_passing };
diff --git a/t/t9800-git-p4.sh b/t/t9800-git-p4.sh
--- /dev/null
+++ b/t/t9800-git-p4.sh
@@ -0,0 +1,100 @@
+#!/bin/sh
+
+test_description='git-p4 tests'
+
+. ./test-lib.sh
+
+( p4 -h && p4d -h ) >/dev/null 2>&1 || {
+ skip_all='skipping git-p4 tests; no p4 or p4d'
+ test_done
+}
+
+GITP4=$GIT_BUILD_DIR/contrib/fast-import/git-p4
+P4DPORT=10669
+
+db="$TRASH_DIRECTORY/db"
+cli="$TRASH_DIRECTORY/cli"
+git="$TRASH_DIRECTORY/git"
+
+test_debug 'echo p4d -q -d -r "$db" -p $P4DPORT'
+test_expect_success setup '
+ mkdir -p "$db" &&
+ p4d -q -d -r "$db" -p $P4DPORT &&
+ mkdir -p "$cli" &&
+ mkdir -p "$git" &&
+ export P4PORT=localhost:$P4DPORT
+'
+
+test_expect_success 'add p4 files' '
+ cd "$cli" &&
+ p4 client -i <<-EOF &&
+ Client: client
+ Description: client
+ Root: $cli
+ View: //depot/... //client/...
+ EOF
+ export P4CLIENT=client &&
+ echo file1 >file1 &&
+ p4 add file1 &&
+ p4 submit -d "file1" &&
+ cd "$TRASH_DIRECTORY"
+'
+
+test_expect_success 'basic git-p4 clone' '
+ "$GITP4" clone --dest="$git" //depot &&
+ rm -rf "$git" && mkdir "$git"
+'
+
+test_expect_success 'exit when p4 fails to produce marshaled output' '
+ badp4dir="$TRASH_DIRECTORY/badp4dir" &&
+ mkdir -p "$badp4dir" &&
+ cat >"$badp4dir"/p4 <<-EOF &&
+ #!$SHELL_PATH
+ exit 1
+ EOF
+ chmod 755 "$badp4dir"/p4 &&
+ PATH="$badp4dir:$PATH" "$GITP4" clone --dest="$git" //depot >errs 2>&1 ; retval=$? &&
+ test $retval -eq 1 &&
+ test_must_fail grep -q Traceback errs
+'
+
+test_expect_success 'add p4 files with wildcards in the names' '
+ cd "$cli" &&
+ echo file-wild-hash >file-wild#hash &&
+ echo file-wild-star >file-wild\*star &&
+ echo file-wild-at >file-wild@at &&
+ echo file-wild-percent >file-wild%percent &&
+ p4 add -f file-wild* &&
+ p4 submit -d "file wildcards" &&
+ cd "$TRASH_DIRECTORY"
+'
+
+test_expect_success 'wildcard files git-p4 clone' '
+ "$GITP4" clone --dest="$git" //depot &&
+ cd "$git" &&
+ test -f file-wild#hash &&
+ test -f file-wild\*star &&
+ test -f file-wild@at &&
+ test -f file-wild%percent &&
+ cd "$TRASH_DIRECTORY" &&
+ rm -rf "$git" && mkdir "$git"
+'
+
+test_expect_success 'clone bare' '
+ "$GITP4" clone --dest="$git" --bare //depot &&
+ cd "$git" &&
+ test ! -d .git &&
+ bare=`git config --get core.bare` &&
+ test "$bare" = true &&
+ cd "$TRASH_DIRECTORY" &&
+ rm -rf "$git" && mkdir "$git"
+'
+
+test_expect_success 'shutdown' '
+ pid=`pgrep -f p4d` &&
+ test -n "$pid" &&
+ test_debug "ps wl `echo $pid`" &&
+ kill $pid
+'
+
+test_done
diff --git a/t/test-lib.sh b/t/test-lib.sh
index 7ffd7d36ade0520438fda3e2a1d39716b6f594d3..0fdc541a7cd7d6b694c4f4a3fd06b6cf21dc7380 100644 (file)
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
tr '\015' Q | sed -e 's/Q$//'
}
+# In some bourne shell implementations, the "unset" builtin returns
+# nonzero status when a variable to be unset was not set in the first
+# place.
+#
+# Use sane_unset when that should not be considered an error.
+
+sane_unset () {
+ unset "$@"
+ return 0
+}
+
test_tick () {
if test -z "${test_tick+set}"
then
test $total_prereq = $ok_prereq
}
+test_declared_prereq () {
+ case ",$test_prereq," in
+ *,$1,*)
+ return 0
+ ;;
+ esac
+ return 1
+}
+
# You are not expected to call test_ok_ and test_failure_ directly, use
# the text_expect_* functions instead.
break
esac
done
- if test -z "$to_skip" && test -n "$prereq" &&
- ! test_have_prereq "$prereq"
+ if test -z "$to_skip" && test -n "$test_prereq" &&
+ ! test_have_prereq "$test_prereq"
then
to_skip=t
fi
case "$to_skip" in
t)
of_prereq=
- if test "$missing_prereq" != "$prereq"
+ if test "$missing_prereq" != "$test_prereq"
then
- of_prereq=" of $prereq"
+ of_prereq=" of $test_prereq"
fi
say_color skip >&3 "skipping test: $@"
}
test_expect_failure () {
- test "$#" = 3 && { prereq=$1; shift; } || prereq=
+ test "$#" = 3 && { test_prereq=$1; shift; } || test_prereq=
test "$#" = 2 ||
error "bug in the test script: not 2 or 3 parameters to test-expect-failure"
+ export test_prereq
if ! test_skip "$@"
then
say >&3 "checking known breakage: $2"
}
test_expect_success () {
- test "$#" = 3 && { prereq=$1; shift; } || prereq=
+ test "$#" = 3 && { test_prereq=$1; shift; } || test_prereq=
test "$#" = 2 ||
error "bug in the test script: not 2 or 3 parameters to test-expect-success"
+ export test_prereq
if ! test_skip "$@"
then
say >&3 "expecting success: $2"
echo >&3 ""
}
-test_expect_code () {
- test "$#" = 4 && { prereq=$1; shift; } || prereq=
- test "$#" = 3 ||
- error "bug in the test script: not 3 or 4 parameters to test-expect-code"
- if ! test_skip "$@"
- then
- say >&3 "expecting exit code $1: $3"
- test_run_ "$3"
- if [ "$?" = 0 -a "$eval_ret" = "$1" ]
- then
- test_ok_ "$2"
- else
- test_failure_ "$@"
- fi
- fi
- echo >&3 ""
-}
-
# test_external runs external test scripts that provide continuous
# test output about their progress, and succeeds/fails on
# zero/non-zero exit code. It outputs the test output on stdout even
# Usage: test_external description command arguments...
# Example: test_external 'Perl API' perl ../path/to/test.pl
test_external () {
- test "$#" = 4 && { prereq=$1; shift; } || prereq=
+ test "$#" = 4 && { test_prereq=$1; shift; } || test_prereq=
test "$#" = 3 ||
error >&5 "bug in the test script: not 3 or 4 parameters to test_external"
descr="$1"
shift
+ export test_prereq
if ! test_skip "$descr" "$@"
then
# Announce the script to reduce confusion about the
fi
}
+# test_line_count checks that a file has the number of lines it
+# ought to. For example:
+#
+# test_expect_success 'produce exactly one line of output' '
+# do something >output &&
+# test_line_count = 1 output
+# '
+#
+# is like "test $(wc -l <output) = 1" except that it passes the
+# output through when the number of lines is wrong.
+
+test_line_count () {
+ if test $# != 3
+ then
+ error "bug in the test script: not 3 parameters to test_line_count"
+ elif ! test $(wc -l <"$3") "$1" "$2"
+ then
+ echo "test_line_count: line count for $3 !$1 $2"
+ cat "$3"
+ return 1
+ fi
+}
# This is not among top-level (test_expect_success | test_expect_failure)
# but is a prefix that can be used in the test script, like:
return 0
}
+# Similar to test_must_fail and test_might_fail, but check that a
+# given command exited with a given exit code. Meant to be used as:
+#
+# test_expect_success 'Merge with d/f conflicts' '
+# test_expect_code 1 git merge "merge msg" B master
+# '
+
+test_expect_code () {
+ want_code=$1
+ shift
+ "$@"
+ exit_code=$?
+ if test $exit_code = $want_code
+ then
+ echo >&2 "test_expect_code: command exited with $exit_code: $*"
+ return 0
+ else
+ echo >&2 "test_expect_code: command exited with $exit_code, we wanted $want_code $*"
+ return 1
+ fi
+}
+
# test_cmp is a helper function to compare actual and expected output.
# You can use it like:
#
# no POSIX permissions
# backslashes in pathspec are converted to '/'
# exec does not inherit the PID
+ test_set_prereq MINGW
+ test_set_prereq SED_STRIPS_CR
+ ;;
+*CYGWIN*)
+ test_set_prereq POSIXPERM
+ test_set_prereq EXECKEEPSPID
+ test_set_prereq NOT_MINGW
+ test_set_prereq SED_STRIPS_CR
;;
*)
test_set_prereq POSIXPERM
test_set_prereq BSLASHPSPEC
test_set_prereq EXECKEEPSPID
+ test_set_prereq NOT_MINGW
;;
esac
diff --git a/t/test-terminal.perl b/t/test-terminal.perl
--- /dev/null
+++ b/t/test-terminal.perl
@@ -0,0 +1,76 @@
+#!/usr/bin/perl
+use 5.008;
+use strict;
+use warnings;
+use IO::Pty;
+use File::Copy;
+
+# Run @$argv in the background with stdio redirected to $out and $err.
+sub start_child {
+ my ($argv, $out, $err) = @_;
+ my $pid = fork;
+ if (not defined $pid) {
+ die "fork failed: $!"
+ } elsif ($pid == 0) {
+ open STDOUT, ">&", $out;
+ open STDERR, ">&", $err;
+ close $out;
+ exec(@$argv) or die "cannot exec '$argv->[0]': $!"
+ }
+ return $pid;
+}
+
+# Wait for $pid to finish.
+sub finish_child {
+ # Simplified from wait_or_whine() in run-command.c.
+ my ($pid) = @_;
+
+ my $waiting = waitpid($pid, 0);
+ if ($waiting < 0) {
+ die "waitpid failed: $!";
+ } elsif ($? & 127) {
+ my $code = $? & 127;
+ warn "died of signal $code";
+ return $code - 128;
+ } else {
+ return $? >> 8;
+ }
+}
+
+sub xsendfile {
+ my ($out, $in) = @_;
+
+ # Note: the real sendfile() cannot read from a terminal.
+
+ # It is unspecified by POSIX whether reads
+ # from a disconnected terminal will return
+ # EIO (as in AIX 4.x, IRIX, and Linux) or
+ # end-of-file. Either is fine.
+ copy($in, $out, 4096) or $!{EIO} or die "cannot copy from child: $!";
+}
+
+sub copy_stdio {
+ my ($out, $err) = @_;
+ my $pid = fork;
+ defined $pid or die "fork failed: $!";
+ if (!$pid) {
+ close($out);
+ xsendfile(\*STDERR, $err);
+ exit 0;
+ }
+ close($err);
+ xsendfile(\*STDOUT, $out);
+ finish_child($pid) == 0
+ or exit 1;
+}
+
+if ($#ARGV < 1) {
+ die "usage: test-terminal program args";
+}
+my $master_out = new IO::Pty;
+my $master_err = new IO::Pty;
+my $pid = start_child(\@ARGV, $master_out->slave, $master_err->slave);
+close $master_out->slave;
+close $master_err->slave;
+copy_stdio($master_out, $master_err);
+exit(finish_child($pid));
index f789744ccaf5d5fb6f63ba0911a869492affc162..7d38cc0f4de1c16b5b52725ba7a6a361650a6b41 100644 (file)
--- a/tag.c
+++ b/tag.c
return strtoul(dateptr, NULL, 10);
}
-int parse_tag_buffer(struct tag *item, void *data, unsigned long size)
+int parse_tag_buffer(struct tag *item, const void *data, unsigned long size)
{
unsigned char sha1[20];
char type[20];
item->tagged = NULL;
}
- if (prefixcmp(bufptr, "tag "))
+ if (bufptr + 4 < tail && !prefixcmp(bufptr, "tag "))
+ ; /* good */
+ else
return -1;
bufptr += 4;
nl = memchr(bufptr, '\n', tail - bufptr);
item->tag = xmemdupz(bufptr, nl - bufptr);
bufptr = nl + 1;
- if (!prefixcmp(bufptr, "tagger "))
+ if (bufptr + 7 < tail && !prefixcmp(bufptr, "tagger "))
item->date = parse_tag_date(bufptr, tail);
else
item->date = 0;
index 85223700396f5ddb00c046a1a9fdb63c92aae40e..5ee88e6550cafa78b7e4acaa1285d5805974a037 100644 (file)
--- a/tag.h
+++ b/tag.h
};
extern struct tag *lookup_tag(const unsigned char *sha1);
-extern int parse_tag_buffer(struct tag *item, void *data, unsigned long size);
+extern int parse_tag_buffer(struct tag *item, const void *data, unsigned long size);
extern int parse_tag(struct tag *item);
extern struct object *deref_tag(struct object *, const char *, int);
extern size_t parse_signature(const char *buf, unsigned long size);
diff --git a/test-line-buffer.c b/test-line-buffer.c
index c11bf7f96708c90c992423fc27b2eba73d0c34a0..25b20b93fd4a1113629c99bd5a53dfee965119c1 100644 (file)
--- a/test-line-buffer.c
+++ b/test-line-buffer.c
/*
* test-line-buffer.c: code to exercise the svn importer's input helper
- *
- * Input format:
- * number NL
- * (number bytes) NL
- * number NL
- * ...
*/
#include "git-compat-util.h"
+#include "strbuf.h"
#include "vcs-svn/line_buffer.h"
static uint32_t strtouint32(const char *s)
return (uint32_t) n;
}
+static void handle_command(const char *command, const char *arg, struct line_buffer *buf)
+{
+ switch (*command) {
+ case 'b':
+ if (!prefixcmp(command, "binary ")) {
+ struct strbuf sb = STRBUF_INIT;
+ strbuf_addch(&sb, '>');
+ buffer_read_binary(buf, &sb, strtouint32(arg));
+ fwrite(sb.buf, 1, sb.len, stdout);
+ strbuf_release(&sb);
+ return;
+ }
+ case 'c':
+ if (!prefixcmp(command, "copy ")) {
+ buffer_copy_bytes(buf, strtouint32(arg));
+ return;
+ }
+ case 'r':
+ if (!prefixcmp(command, "read ")) {
+ const char *s = buffer_read_string(buf, strtouint32(arg));
+ fputs(s, stdout);
+ return;
+ }
+ case 's':
+ if (!prefixcmp(command, "skip ")) {
+ buffer_skip_bytes(buf, strtouint32(arg));
+ return;
+ }
+ default:
+ die("unrecognized command: %s", command);
+ }
+}
+
+static void handle_line(const char *line, struct line_buffer *stdin_buf)
+{
+ const char *arg = strchr(line, ' ');
+ if (!arg)
+ die("no argument in line: %s", line);
+ handle_command(line, arg + 1, stdin_buf);
+}
+
int main(int argc, char *argv[])
{
+ struct line_buffer stdin_buf = LINE_BUFFER_INIT;
+ struct line_buffer file_buf = LINE_BUFFER_INIT;
+ struct line_buffer *input = &stdin_buf;
+ const char *filename;
char *s;
- if (argc != 1)
- usage("test-line-buffer < input.txt");
- if (buffer_init(NULL))
+ if (argc == 1)
+ filename = NULL;
+ else if (argc == 2)
+ filename = argv[1];
+ else
+ usage("test-line-buffer [file | &fd] < script");
+
+ if (buffer_init(&stdin_buf, NULL))
die_errno("open error");
- while ((s = buffer_read_line())) {
- s = buffer_read_string(strtouint32(s));
- fputs(s, stdout);
- fputc('\n', stdout);
- buffer_skip_bytes(1);
- if (!(s = buffer_read_line()))
- break;
- buffer_copy_bytes(strtouint32(s) + 1);
+ if (filename) {
+ if (*filename == '&') {
+ if (buffer_fdinit(&file_buf, strtouint32(filename + 1)))
+ die_errno("error opening fd %s", filename + 1);
+ } else {
+ if (buffer_init(&file_buf, filename))
+ die_errno("error opening %s", filename);
+ }
+ input = &file_buf;
}
- if (buffer_deinit())
+
+ while ((s = buffer_read_line(&stdin_buf)))
+ handle_line(s, input);
+
+ if (filename && buffer_deinit(&file_buf))
+ die("error reading from %s", filename);
+ if (buffer_deinit(&stdin_buf))
die("input error");
if (ferror(stdout))
die("output error");
- buffer_reset();
+ buffer_reset(&stdin_buf);
return 0;
}
diff --git a/test-mktemp.c b/test-mktemp.c
--- /dev/null
+++ b/test-mktemp.c
@@ -0,0 +1,14 @@
+/*
+ * test-mktemp.c: code to exercise the creation of temporary files
+ */
+#include "git-compat-util.h"
+
+int main(int argc, char *argv[])
+{
+ if (argc != 2)
+ usage("Expected 1 parameter defining the temporary file template");
+
+ xmkstemp(xstrdup(argv[1]));
+
+ return 0;
+}
diff --git a/test-parse-options.c b/test-parse-options.c
index acd1a2ba70fc2ffb38cd8fb0784aa6e5e693183f..4e3710b9a8fc868642e1e0e8f4af37eeb1ce0ce4 100644 (file)
--- a/test-parse-options.c
+++ b/test-parse-options.c
OPT_DATE('t', NULL, ×tamp, "get timestamp of <time>"),
OPT_CALLBACK('L', "length", &integer, "str",
"get length of <str>", length_callback),
- OPT_FILENAME('F', "file", &file, "set file to <FILE>"),
+ OPT_FILENAME('F', "file", &file, "set file to <file>"),
OPT_GROUP("String options"),
OPT_STRING('s', "string", &string, "string", "get a string"),
OPT_STRING(0, "string2", &string, "str", "get another string"),
"negative ambiguity", PARSE_OPT_NOARG | PARSE_OPT_NONEG },
OPT_GROUP("Standard options"),
OPT__ABBREV(&abbrev),
- OPT__VERBOSE(&verbose),
- OPT__DRY_RUN(&dry_run),
- OPT__QUIET(&quiet),
+ OPT__VERBOSE(&verbose, "be verbose"),
+ OPT__DRY_RUN(&dry_run, "dry run"),
+ OPT__QUIET(&quiet, "be quiet"),
OPT_END(),
};
int i;
diff --git a/test-subprocess.c b/test-subprocess.c
--- /dev/null
+++ b/test-subprocess.c
@@ -0,0 +1,21 @@
+#include "cache.h"
+#include "run-command.h"
+
+int main(int argc, char **argv)
+{
+ const char *prefix;
+ struct child_process cp;
+ int nogit = 0;
+
+ prefix = setup_git_directory_gently(&nogit);
+ if (nogit)
+ die("No git repo found");
+ if (!strcmp(argv[1], "--setup-work-tree")) {
+ setup_work_tree();
+ argv++;
+ }
+ memset(&cp, 0, sizeof(cp));
+ cp.git_cmd = 1;
+ cp.argv = (const char **)argv+1;
+ return run_command(&cp);
+}
diff --git a/test-svn-fe.c b/test-svn-fe.c
index 77cf78abcfd18532b1b75e5ad5efa21b27f1a28c..b42ba789b176160285844e49cb834d60170d9a54 100644 (file)
--- a/test-svn-fe.c
+++ b/test-svn-fe.c
{
if (argc != 2)
usage("test-svn-fe <file>");
- svndump_init(argv[1]);
+ if (svndump_init(argv[1]))
+ return 1;
svndump_read(NULL);
svndump_deinit();
svndump_reset();
diff --git a/test-treap.c b/test-treap.c
index cdba5111e19cd850f262108a330ce5d4ce63a166..ab8c951c6eb39f54a9885dd15c173f974d7357d5 100644 (file)
--- a/test-treap.c
+++ b/test-treap.c
usage("test-treap < ints");
while (strbuf_getline(&sb, stdin, '\n') != EOF) {
- item = node_alloc(1);
- strtonode(node_pointer(item), sb.buf);
- treap_insert(&root, node_pointer(item));
+ struct int_node *node = node_pointer(node_alloc(1));
+
+ item = node_offset(node);
+ strtonode(node, sb.buf);
+ node = treap_insert(&root, node_pointer(item));
+ if (node_offset(node) != item)
+ die("inserted %"PRIu32" in place of %"PRIu32"",
+ node_offset(node), item);
}
item = node_offset(treap_first(&root));
diff --git a/thread-utils.h b/thread-utils.h
index 1727a03333b80f2a01ca029162a4b511480a86e8..6fb98c333c22a67512516bbf047c2cac270a66ae 100644 (file)
--- a/thread-utils.h
+++ b/thread-utils.h
#ifndef THREAD_COMPAT_H
#define THREAD_COMPAT_H
+#ifndef NO_PTHREADS
+#include <pthread.h>
+
extern int online_cpus(void);
extern int init_recursive_mutex(pthread_mutex_t*);
+#endif
#endif /* THREAD_COMPAT_H */
index 62586fa371b22e9b0b2d8f4e7eb9a4e3e100ac9f..35d388dce44a4a8e3d6053dfd6abcb652771818a 100644 (file)
--- a/trace.c
+++ b/trace.c
#include "cache.h"
#include "quote.h"
-static void do_nothing(size_t unused)
-{
-}
-
/* Get a trace file descriptor from GIT_TRACE env variable. */
static int get_trace_fd(int *need_close)
{
if (!fd)
return;
- set_try_to_free_routine(do_nothing); /* is never reset */
+ set_try_to_free_routine(NULL); /* is never reset */
strbuf_init(&buf, 64);
va_start(ap, fmt);
len = vsnprintf(buf.buf, strbuf_avail(&buf), fmt, ap);
if (!fd)
return;
- set_try_to_free_routine(do_nothing); /* is never reset */
+ set_try_to_free_routine(NULL); /* is never reset */
strbuf_init(&buf, 64);
va_start(ap, fmt);
len = vsnprintf(buf.buf, strbuf_avail(&buf), fmt, ap);
if (need_close)
close(fd);
}
+
+static const char *quote_crnl(const char *path)
+{
+ static char new_path[PATH_MAX];
+ const char *p2 = path;
+ char *p1 = new_path;
+
+ if (!path)
+ return NULL;
+
+ while (*p2) {
+ switch (*p2) {
+ case '\\': *p1++ = '\\'; *p1++ = '\\'; break;
+ case '\n': *p1++ = '\\'; *p1++ = 'n'; break;
+ case '\r': *p1++ = '\\'; *p1++ = 'r'; break;
+ default:
+ *p1++ = *p2;
+ }
+ p2++;
+ }
+ *p1 = '\0';
+ return new_path;
+}
+
+/* FIXME: move prefix to startup_info struct and get rid of this arg */
+void trace_repo_setup(const char *prefix)
+{
+ const char *git_work_tree;
+ char cwd[PATH_MAX];
+ char *trace = getenv("GIT_TRACE");
+
+ if (!trace || !strcmp(trace, "") ||
+ !strcmp(trace, "0") || !strcasecmp(trace, "false"))
+ return;
+
+ if (!getcwd(cwd, PATH_MAX))
+ die("Unable to get current working directory");
+
+ if (!(git_work_tree = get_git_work_tree()))
+ git_work_tree = "(null)";
+
+ if (!prefix)
+ prefix = "(null)";
+
+ trace_printf("setup: git_dir: %s\n", quote_crnl(get_git_dir()));
+ trace_printf("setup: worktree: %s\n", quote_crnl(git_work_tree));
+ trace_printf("setup: cwd: %s\n", quote_crnl(cwd));
+ trace_printf("setup: prefix: %s\n", quote_crnl(prefix));
+}
diff --git a/transport-helper.c b/transport-helper.c
index acfc88e3f1f0e99863512bf2c6ea82f1283cc35d..4e4754c32bd53f28f9335c2a4529f5804f3086f2 100644 (file)
--- a/transport-helper.c
+++ b/transport-helper.c
#include "quote.h"
#include "remote.h"
#include "string-list.h"
+#include "thread-utils.h"
static int debug;
transport->smart_options = &(data->transport_options);
return 0;
}
+
+/*
+ * Linux pipes can buffer 65536 bytes at once (and most platforms can
+ * buffer less), so attempt reads and writes with up to that size.
+ */
+#define BUFFERSIZE 65536
+/* This should be enough to hold debugging message. */
+#define PBUFFERSIZE 8192
+
+/* Print bidirectional transfer loop debug message. */
+static void transfer_debug(const char *fmt, ...)
+{
+ va_list args;
+ char msgbuf[PBUFFERSIZE];
+ static int debug_enabled = -1;
+
+ if (debug_enabled < 0)
+ debug_enabled = getenv("GIT_TRANSLOOP_DEBUG") ? 1 : 0;
+ if (!debug_enabled)
+ return;
+
+ va_start(args, fmt);
+ vsnprintf(msgbuf, PBUFFERSIZE, fmt, args);
+ va_end(args);
+ fprintf(stderr, "Transfer loop debugging: %s\n", msgbuf);
+}
+
+/* Stream state: More data may be coming in this direction. */
+#define SSTATE_TRANSFERING 0
+/*
+ * Stream state: No more data coming in this direction, flushing rest of
+ * data.
+ */
+#define SSTATE_FLUSHING 1
+/* Stream state: Transfer in this direction finished. */
+#define SSTATE_FINISHED 2
+
+#define STATE_NEEDS_READING(state) ((state) <= SSTATE_TRANSFERING)
+#define STATE_NEEDS_WRITING(state) ((state) <= SSTATE_FLUSHING)
+#define STATE_NEEDS_CLOSING(state) ((state) == SSTATE_FLUSHING)
+
+/* Unidirectional transfer. */
+struct unidirectional_transfer {
+ /* Source */
+ int src;
+ /* Destination */
+ int dest;
+ /* Is source socket? */
+ int src_is_sock;
+ /* Is destination socket? */
+ int dest_is_sock;
+ /* Transfer state (TRANSFERING/FLUSHING/FINISHED) */
+ int state;
+ /* Buffer. */
+ char buf[BUFFERSIZE];
+ /* Buffer used. */
+ size_t bufuse;
+ /* Name of source. */
+ const char *src_name;
+ /* Name of destination. */
+ const char *dest_name;
+};
+
+/* Closes the target (for writing) if transfer has finished. */
+static void udt_close_if_finished(struct unidirectional_transfer *t)
+{
+ if (STATE_NEEDS_CLOSING(t->state) && !t->bufuse) {
+ t->state = SSTATE_FINISHED;
+ if (t->dest_is_sock)
+ shutdown(t->dest, SHUT_WR);
+ else
+ close(t->dest);
+ transfer_debug("Closed %s.", t->dest_name);
+ }
+}
+
+/*
+ * Tries to read read data from source into buffer. If buffer is full,
+ * no data is read. Returns 0 on success, -1 on error.
+ */
+static int udt_do_read(struct unidirectional_transfer *t)
+{
+ ssize_t bytes;
+
+ if (t->bufuse == BUFFERSIZE)
+ return 0; /* No space for more. */
+
+ transfer_debug("%s is readable", t->src_name);
+ bytes = read(t->src, t->buf + t->bufuse, BUFFERSIZE - t->bufuse);
+ if (bytes < 0 && errno != EWOULDBLOCK && errno != EAGAIN &&
+ errno != EINTR) {
+ error("read(%s) failed: %s", t->src_name, strerror(errno));
+ return -1;
+ } else if (bytes == 0) {
+ transfer_debug("%s EOF (with %i bytes in buffer)",
+ t->src_name, t->bufuse);
+ t->state = SSTATE_FLUSHING;
+ } else if (bytes > 0) {
+ t->bufuse += bytes;
+ transfer_debug("Read %i bytes from %s (buffer now at %i)",
+ (int)bytes, t->src_name, (int)t->bufuse);
+ }
+ return 0;
+}
+
+/* Tries to write data from buffer into destination. If buffer is empty,
+ * no data is written. Returns 0 on success, -1 on error.
+ */
+static int udt_do_write(struct unidirectional_transfer *t)
+{
+ size_t bytes;
+
+ if (t->bufuse == 0)
+ return 0; /* Nothing to write. */
+
+ transfer_debug("%s is writable", t->dest_name);
+ bytes = write(t->dest, t->buf, t->bufuse);
+ if (bytes < 0 && errno != EWOULDBLOCK && errno != EAGAIN &&
+ errno != EINTR) {
+ error("write(%s) failed: %s", t->dest_name, strerror(errno));
+ return -1;
+ } else if (bytes > 0) {
+ t->bufuse -= bytes;
+ if (t->bufuse)
+ memmove(t->buf, t->buf + bytes, t->bufuse);
+ transfer_debug("Wrote %i bytes to %s (buffer now at %i)",
+ (int)bytes, t->dest_name, (int)t->bufuse);
+ }
+ return 0;
+}
+
+
+/* State of bidirectional transfer loop. */
+struct bidirectional_transfer_state {
+ /* Direction from program to git. */
+ struct unidirectional_transfer ptg;
+ /* Direction from git to program. */
+ struct unidirectional_transfer gtp;
+};
+
+static void *udt_copy_task_routine(void *udt)
+{
+ struct unidirectional_transfer *t = (struct unidirectional_transfer *)udt;
+ while (t->state != SSTATE_FINISHED) {
+ if (STATE_NEEDS_READING(t->state))
+ if (udt_do_read(t))
+ return NULL;
+ if (STATE_NEEDS_WRITING(t->state))
+ if (udt_do_write(t))
+ return NULL;
+ if (STATE_NEEDS_CLOSING(t->state))
+ udt_close_if_finished(t);
+ }
+ return udt; /* Just some non-NULL value. */
+}
+
+#ifndef NO_PTHREADS
+
+/*
+ * Join thread, with apporiate errors on failure. Name is name for the
+ * thread (for error messages). Returns 0 on success, 1 on failure.
+ */
+static int tloop_join(pthread_t thread, const char *name)
+{
+ int err;
+ void *tret;
+ err = pthread_join(thread, &tret);
+ if (!tret) {
+ error("%s thread failed", name);
+ return 1;
+ }
+ if (err) {
+ error("%s thread failed to join: %s", name, strerror(err));
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * Spawn the transfer tasks and then wait for them. Returns 0 on success,
+ * -1 on failure.
+ */
+static int tloop_spawnwait_tasks(struct bidirectional_transfer_state *s)
+{
+ pthread_t gtp_thread;
+ pthread_t ptg_thread;
+ int err;
+ int ret = 0;
+ err = pthread_create(>p_thread, NULL, udt_copy_task_routine,
+ &s->gtp);
+ if (err)
+ die("Can't start thread for copying data: %s", strerror(err));
+ err = pthread_create(&ptg_thread, NULL, udt_copy_task_routine,
+ &s->ptg);
+ if (err)
+ die("Can't start thread for copying data: %s", strerror(err));
+
+ ret |= tloop_join(gtp_thread, "Git to program copy");
+ ret |= tloop_join(ptg_thread, "Program to git copy");
+ return ret;
+}
+#else
+
+/* Close the source and target (for writing) for transfer. */
+static void udt_kill_transfer(struct unidirectional_transfer *t)
+{
+ t->state = SSTATE_FINISHED;
+ /*
+ * Socket read end left open isn't a disaster if nobody
+ * attempts to read from it (mingw compat headers do not
+ * have SHUT_RD)...
+ *
+ * We can't fully close the socket since otherwise gtp
+ * task would first close the socket it sends data to
+ * while closing the ptg file descriptors.
+ */
+ if (!t->src_is_sock)
+ close(t->src);
+ if (t->dest_is_sock)
+ shutdown(t->dest, SHUT_WR);
+ else
+ close(t->dest);
+}
+
+/*
+ * Join process, with apporiate errors on failure. Name is name for the
+ * process (for error messages). Returns 0 on success, 1 on failure.
+ */
+static int tloop_join(pid_t pid, const char *name)
+{
+ int tret;
+ if (waitpid(pid, &tret, 0) < 0) {
+ error("%s process failed to wait: %s", name, strerror(errno));
+ return 1;
+ }
+ if (!WIFEXITED(tret) || WEXITSTATUS(tret)) {
+ error("%s process failed", name);
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * Spawn the transfer tasks and then wait for them. Returns 0 on success,
+ * -1 on failure.
+ */
+static int tloop_spawnwait_tasks(struct bidirectional_transfer_state *s)
+{
+ pid_t pid1, pid2;
+ int ret = 0;
+
+ /* Fork thread #1: git to program. */
+ pid1 = fork();
+ if (pid1 < 0)
+ die_errno("Can't start thread for copying data");
+ else if (pid1 == 0) {
+ udt_kill_transfer(&s->ptg);
+ exit(udt_copy_task_routine(&s->gtp) ? 0 : 1);
+ }
+
+ /* Fork thread #2: program to git. */
+ pid2 = fork();
+ if (pid2 < 0)
+ die_errno("Can't start thread for copying data");
+ else if (pid2 == 0) {
+ udt_kill_transfer(&s->gtp);
+ exit(udt_copy_task_routine(&s->ptg) ? 0 : 1);
+ }
+
+ /*
+ * Close both streams in parent as to not interfere with
+ * end of file detection and wait for both tasks to finish.
+ */
+ udt_kill_transfer(&s->gtp);
+ udt_kill_transfer(&s->ptg);
+ ret |= tloop_join(pid1, "Git to program copy");
+ ret |= tloop_join(pid2, "Program to git copy");
+ return ret;
+}
+#endif
+
+/*
+ * Copies data from stdin to output and from input to stdout simultaneously.
+ * Additionally filtering through given filter. If filter is NULL, uses
+ * identity filter.
+ */
+int bidirectional_transfer_loop(int input, int output)
+{
+ struct bidirectional_transfer_state state;
+
+ /* Fill the state fields. */
+ state.ptg.src = input;
+ state.ptg.dest = 1;
+ state.ptg.src_is_sock = (input == output);
+ state.ptg.dest_is_sock = 0;
+ state.ptg.state = SSTATE_TRANSFERING;
+ state.ptg.bufuse = 0;
+ state.ptg.src_name = "remote input";
+ state.ptg.dest_name = "stdout";
+
+ state.gtp.src = 0;
+ state.gtp.dest = output;
+ state.gtp.src_is_sock = 0;
+ state.gtp.dest_is_sock = (input == output);
+ state.gtp.state = SSTATE_TRANSFERING;
+ state.gtp.bufuse = 0;
+ state.gtp.src_name = "stdin";
+ state.gtp.dest_name = "remote output";
+
+ return tloop_spawnwait_tasks(&state);
+}
diff --git a/transport.c b/transport.c
index 4dba6f8815a80093a8ac9edc226d99bba1bb6394..00786606117feea4b33b7e632b8ff8a3c26de4e4 100644 (file)
--- a/transport.c
+++ b/transport.c
@@ -789,6 +789,7 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re
args.use_thin_pack = data->options.thin;
args.verbose = (transport->verbose > 0);
args.quiet = (transport->verbose < 0);
+ args.progress = transport->progress;
args.dry_run = !!(flags & TRANSPORT_PUSH_DRY_RUN);
args.porcelain = !!(flags & TRANSPORT_PUSH_PORCELAIN);
diff --git a/transport.h b/transport.h
index c59d97388e6fdb1dccd4dca0d41dccc3d129f8fb..e803c0e7baf3785fac35fca64072e05e4f668a26 100644 (file)
--- a/transport.h
+++ b/transport.h
/* Transport methods defined outside transport.c */
int transport_helper_init(struct transport *transport, const char *name);
+int bidirectional_transfer_loop(int input, int output);
/* common methods used by transport.c and builtin-send-pack.c */
void transport_verify_remote_names(int nr_heads, const char **heads);
diff --git a/tree-diff.c b/tree-diff.c
index cd659c6fe447b6fcdedee2843113feb358f23849..3954281f509bbed9a9b095ed92d24b67275fed82 100644 (file)
--- a/tree-diff.c
+++ b/tree-diff.c
#include "diffcore.h"
#include "tree.h"
-static char *malloc_base(const char *base, int baselen, const char *path, int pathlen)
-{
- char *newbase = xmalloc(baselen + pathlen + 2);
- memcpy(newbase, base, baselen);
- memcpy(newbase + baselen, path, pathlen);
- memcpy(newbase + baselen + pathlen, "/", 2);
- return newbase;
-}
-
-static char *malloc_fullname(const char *base, int baselen, const char *path, int pathlen)
-{
- char *fullname = xmalloc(baselen + pathlen + 1);
- memcpy(fullname, base, baselen);
- memcpy(fullname + baselen, path, pathlen);
- fullname[baselen + pathlen] = 0;
- return fullname;
-}
+static void show_entry(struct diff_options *opt, const char *prefix,
+ struct tree_desc *desc, struct strbuf *base);
-static void show_entry(struct diff_options *opt, const char *prefix, struct tree_desc *desc,
- const char *base, int baselen);
-
-static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, const char *base, int baselen, struct diff_options *opt)
+static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2,
+ struct strbuf *base, struct diff_options *opt)
{
unsigned mode1, mode2;
const char *path1, *path2;
const unsigned char *sha1, *sha2;
int cmp, pathlen1, pathlen2;
- char *fullname;
+ int old_baselen = base->len;
+ int retval = 0;
sha1 = tree_entry_extract(t1, &path1, &mode1);
sha2 = tree_entry_extract(t2, &path2, &mode2);
pathlen2 = tree_entry_len(path2, sha2);
cmp = base_name_compare(path1, pathlen1, mode1, path2, pathlen2, mode2);
if (cmp < 0) {
- show_entry(opt, "-", t1, base, baselen);
+ show_entry(opt, "-", t1, base);
return -1;
}
if (cmp > 0) {
- show_entry(opt, "+", t2, base, baselen);
+ show_entry(opt, "+", t2, base);
return 1;
}
if (!DIFF_OPT_TST(opt, FIND_COPIES_HARDER) && !hashcmp(sha1, sha2) && mode1 == mode2)
@@ -57,147 +41,29 @@ static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, const
* file, we need to consider it a remove and an add.
*/
if (S_ISDIR(mode1) != S_ISDIR(mode2)) {
- show_entry(opt, "-", t1, base, baselen);
- show_entry(opt, "+", t2, base, baselen);
+ show_entry(opt, "-", t1, base);
+ show_entry(opt, "+", t2, base);
return 0;
}
+ strbuf_add(base, path1, pathlen1);
if (DIFF_OPT_TST(opt, RECURSIVE) && S_ISDIR(mode1)) {
- int retval;
- char *newbase = malloc_base(base, baselen, path1, pathlen1);
if (DIFF_OPT_TST(opt, TREE_IN_RECURSIVE)) {
- newbase[baselen + pathlen1] = 0;
opt->change(opt, mode1, mode2,
- sha1, sha2, newbase, 0, 0);
- newbase[baselen + pathlen1] = '/';
+ sha1, sha2, base->buf, 0, 0);
}
- retval = diff_tree_sha1(sha1, sha2, newbase, opt);
- free(newbase);
- return retval;
+ strbuf_addch(base, '/');
+ retval = diff_tree_sha1(sha1, sha2, base->buf, opt);
+ } else {
+ opt->change(opt, mode1, mode2, sha1, sha2, base->buf, 0, 0);
}
-
- fullname = malloc_fullname(base, baselen, path1, pathlen1);
- opt->change(opt, mode1, mode2, sha1, sha2, fullname, 0, 0);
- free(fullname);
+ strbuf_setlen(base, old_baselen);
return 0;
}
-/*
- * Is a tree entry interesting given the pathspec we have?
- *
- * Return:
- * - 2 for "yes, and all subsequent entries will be"
- * - 1 for yes
- * - zero for no
- * - negative for "no, and no subsequent entries will be either"
- */
-static int tree_entry_interesting(struct tree_desc *desc, const char *base, int baselen, struct diff_options *opt)
-{
- const char *path;
- const unsigned char *sha1;
- unsigned mode;
- int i;
- int pathlen;
- int never_interesting = -1;
-
- if (!opt->nr_paths)
- return 1;
-
- sha1 = tree_entry_extract(desc, &path, &mode);
-
- pathlen = tree_entry_len(path, sha1);
-
- for (i = 0; i < opt->nr_paths; i++) {
- const char *match = opt->paths[i];
- int matchlen = opt->pathlens[i];
- int m = -1; /* signals that we haven't called strncmp() */
-
- if (baselen >= matchlen) {
- /* If it doesn't match, move along... */
- if (strncmp(base, match, matchlen))
- continue;
-
- /*
- * If the base is a subdirectory of a path which
- * was specified, all of them are interesting.
- */
- if (!matchlen ||
- base[matchlen] == '/' ||
- match[matchlen - 1] == '/')
- return 2;
-
- /* Just a random prefix match */
- continue;
- }
-
- /* Does the base match? */
- if (strncmp(base, match, baselen))
- continue;
-
- match += baselen;
- matchlen -= baselen;
-
- if (never_interesting) {
- /*
- * We have not seen any match that sorts later
- * than the current path.
- */
-
- /*
- * Does match sort strictly earlier than path
- * with their common parts?
- */
- m = strncmp(match, path,
- (matchlen < pathlen) ? matchlen : pathlen);
- if (m < 0)
- continue;
-
- /*
- * If we come here even once, that means there is at
- * least one pathspec that would sort equal to or
- * later than the path we are currently looking at.
- * In other words, if we have never reached this point
- * after iterating all pathspecs, it means all
- * pathspecs are either outside of base, or inside the
- * base but sorts strictly earlier than the current
- * one. In either case, they will never match the
- * subsequent entries. In such a case, we initialized
- * the variable to -1 and that is what will be
- * returned, allowing the caller to terminate early.
- */
- never_interesting = 0;
- }
-
- if (pathlen > matchlen)
- continue;
-
- if (matchlen > pathlen) {
- if (match[pathlen] != '/')
- continue;
- if (!S_ISDIR(mode))
- continue;
- }
-
- if (m == -1)
- /*
- * we cheated and did not do strncmp(), so we do
- * that here.
- */
- m = strncmp(match, path, pathlen);
-
- /*
- * If common part matched earlier then it is a hit,
- * because we rejected the case where path is not a
- * leading directory and is shorter than match.
- */
- if (!m)
- return 1;
- }
- return never_interesting; /* No matches */
-}
-
/* A whole sub-tree went away or appeared */
-static void show_tree(struct diff_options *opt, const char *prefix, struct tree_desc *desc, const char *base, int baselen)
+static void show_tree(struct diff_options *opt, const char *prefix,
+ struct tree_desc *desc, struct strbuf *base)
{
int all_interesting = 0;
while (desc->size) {
@@ -206,31 +72,32 @@ static void show_tree(struct diff_options *opt, const char *prefix, struct tree_
if (all_interesting)
show = 1;
else {
- show = tree_entry_interesting(desc, base, baselen,
- opt);
+ show = tree_entry_interesting(&desc->entry, base, 0,
+ &opt->pathspec);
if (show == 2)
all_interesting = 1;
}
if (show < 0)
break;
if (show)
- show_entry(opt, prefix, desc, base, baselen);
+ show_entry(opt, prefix, desc, base);
update_tree_entry(desc);
}
}
/* A file entry went away or appeared */
-static void show_entry(struct diff_options *opt, const char *prefix, struct tree_desc *desc,
- const char *base, int baselen)
+static void show_entry(struct diff_options *opt, const char *prefix,
+ struct tree_desc *desc, struct strbuf *base)
{
unsigned mode;
const char *path;
const unsigned char *sha1 = tree_entry_extract(desc, &path, &mode);
int pathlen = tree_entry_len(path, sha1);
+ int old_baselen = base->len;
+ strbuf_add(base, path, pathlen);
if (DIFF_OPT_TST(opt, RECURSIVE) && S_ISDIR(mode)) {
enum object_type type;
- char *newbase = malloc_base(base, baselen, path, pathlen);
struct tree_desc inner;
void *tree;
unsigned long size;
@@ -239,37 +106,27 @@ static void show_entry(struct diff_options *opt, const char *prefix, struct tree
if (!tree || type != OBJ_TREE)
die("corrupt tree sha %s", sha1_to_hex(sha1));
- if (DIFF_OPT_TST(opt, TREE_IN_RECURSIVE)) {
- newbase[baselen + pathlen] = 0;
- opt->add_remove(opt, *prefix, mode, sha1, newbase, 0);
- newbase[baselen + pathlen] = '/';
- }
+ if (DIFF_OPT_TST(opt, TREE_IN_RECURSIVE))
+ opt->add_remove(opt, *prefix, mode, sha1, base->buf, 0);
- init_tree_desc(&inner, tree, size);
- show_tree(opt, prefix, &inner, newbase, baselen + 1 + pathlen);
+ strbuf_addch(base, '/');
+ init_tree_desc(&inner, tree, size);
+ show_tree(opt, prefix, &inner, base);
free(tree);
- free(newbase);
- } else {
- char *fullname = malloc_fullname(base, baselen, path, pathlen);
- opt->add_remove(opt, prefix[0], mode, sha1, fullname, 0);
- free(fullname);
- }
+ } else
+ opt->add_remove(opt, prefix[0], mode, sha1, base->buf, 0);
+
+ strbuf_setlen(base, old_baselen);
}
-static void skip_uninteresting(struct tree_desc *t, const char *base, int baselen, struct diff_options *opt)
+static void skip_uninteresting(struct tree_desc *t, struct strbuf *base,
+ struct diff_options *opt, int *all_interesting)
{
- int all_interesting = 0;
while (t->size) {
- int show;
-
- if (all_interesting)
- show = 1;
- else {
- show = tree_entry_interesting(t, base, baselen, opt);
- if (show == 2)
- all_interesting = 1;
- }
+ int show = tree_entry_interesting(&t->entry, base, 0, &opt->pathspec);
+ if (show == 2)
+ *all_interesting = 1;
if (!show) {
update_tree_entry(t);
continue;
@@ -281,31 +138,44 @@ static void skip_uninteresting(struct tree_desc *t, const char *base, int basele
}
}
-int diff_tree(struct tree_desc *t1, struct tree_desc *t2, const char *base, struct diff_options *opt)
+int diff_tree(struct tree_desc *t1, struct tree_desc *t2,
+ const char *base_str, struct diff_options *opt)
{
- int baselen = strlen(base);
+ struct strbuf base;
+ int baselen = strlen(base_str);
+ int all_t1_interesting = 0;
+ int all_t2_interesting = 0;
+
+ /* Enable recursion indefinitely */
+ opt->pathspec.recursive = DIFF_OPT_TST(opt, RECURSIVE);
+ opt->pathspec.max_depth = -1;
+
+ strbuf_init(&base, PATH_MAX);
+ strbuf_add(&base, base_str, baselen);
for (;;) {
if (DIFF_OPT_TST(opt, QUICK) &&
DIFF_OPT_TST(opt, HAS_CHANGES))
break;
- if (opt->nr_paths) {
- skip_uninteresting(t1, base, baselen, opt);
- skip_uninteresting(t2, base, baselen, opt);
+ if (opt->pathspec.nr) {
+ if (!all_t1_interesting)
+ skip_uninteresting(t1, &base, opt, &all_t1_interesting);
+ if (!all_t2_interesting)
+ skip_uninteresting(t2, &base, opt, &all_t2_interesting);
}
if (!t1->size) {
if (!t2->size)
break;
- show_entry(opt, "+", t2, base, baselen);
+ show_entry(opt, "+", t2, &base);
update_tree_entry(t2);
continue;
}
if (!t2->size) {
- show_entry(opt, "-", t1, base, baselen);
+ show_entry(opt, "-", t1, &base);
update_tree_entry(t1);
continue;
}
- switch (compare_tree_entry(t1, t2, base, baselen, opt)) {
+ switch (compare_tree_entry(t1, t2, &base, opt)) {
case -1:
update_tree_entry(t1);
continue;
@@ -318,6 +188,8 @@ int diff_tree(struct tree_desc *t1, struct tree_desc *t2, const char *base, stru
}
die("git diff-tree: internal error");
}
+
+ strbuf_release(&base);
return 0;
}
@@ -348,7 +220,7 @@ static void try_to_follow_renames(struct tree_desc *t1, struct tree_desc *t2, co
DIFF_OPT_SET(&diff_opts, RECURSIVE);
DIFF_OPT_SET(&diff_opts, FIND_COPIES_HARDER);
diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
- diff_opts.single_follow = opt->paths[0];
+ diff_opts.single_follow = opt->pathspec.raw[0];
diff_opts.break_opt = opt->break_opt;
paths[0] = NULL;
diff_tree_setup_paths(paths, &diff_opts);
@@ -368,15 +240,16 @@ static void try_to_follow_renames(struct tree_desc *t1, struct tree_desc *t2, co
* diff_queued_diff, we will also use that as the path in
* the future!
*/
- if ((p->status == 'R' || p->status == 'C') && !strcmp(p->two->path, opt->paths[0])) {
+ if ((p->status == 'R' || p->status == 'C') &&
+ !strcmp(p->two->path, opt->pathspec.raw[0])) {
/* Switch the file-pairs around */
q->queue[i] = choice;
choice = p;
/* Update the path we use from now on.. */
diff_tree_release_paths(opt);
- opt->paths[0] = xstrdup(p->one->path);
- diff_tree_setup_paths(opt->paths, opt);
+ opt->pathspec.raw[0] = xstrdup(p->one->path);
+ diff_tree_setup_paths(opt->pathspec.raw, opt);
/*
* The caller expects us to return a set of vanilla
@@ -451,36 +324,12 @@ int diff_root_tree_sha1(const unsigned char *new, const char *base, struct diff_
return retval;
}
-static int count_paths(const char **paths)
-{
- int i = 0;
- while (*paths++)
- i++;
- return i;
-}
-
void diff_tree_release_paths(struct diff_options *opt)
{
- free(opt->pathlens);
+ free_pathspec(&opt->pathspec);
}
void diff_tree_setup_paths(const char **p, struct diff_options *opt)
{
- opt->nr_paths = 0;
- opt->pathlens = NULL;
- opt->paths = NULL;
-
- if (p) {
- int i;
-
- opt->paths = p;
- opt->nr_paths = count_paths(p);
- if (opt->nr_paths == 0) {
- opt->pathlens = NULL;
- return;
- }
- opt->pathlens = xmalloc(opt->nr_paths * sizeof(int));
- for (i=0; i < opt->nr_paths; i++)
- opt->pathlens[i] = strlen(p[i]);
- }
+ init_pathspec(&opt->pathspec, p);
}
diff --git a/tree-walk.c b/tree-walk.c
index a9bbf4e2354df5ce6b010873b419731343a12c7d..322becc3b4ad50b8e87fae8f6d5d38f7edc51f01 100644 (file)
--- a/tree-walk.c
+++ b/tree-walk.c
#include "cache.h"
#include "tree-walk.h"
#include "unpack-trees.h"
+#include "dir.h"
#include "tree.h"
static const char *get_mode(const char *str, unsigned int *modep)
@@ -455,3 +456,186 @@ int get_tree_entry(const unsigned char *tree_sha1, const char *name, unsigned ch
free(tree);
return retval;
}
+
+static int match_entry(const struct name_entry *entry, int pathlen,
+ const char *match, int matchlen,
+ int *never_interesting)
+{
+ int m = -1; /* signals that we haven't called strncmp() */
+
+ if (*never_interesting) {
+ /*
+ * We have not seen any match that sorts later
+ * than the current path.
+ */
+
+ /*
+ * Does match sort strictly earlier than path
+ * with their common parts?
+ */
+ m = strncmp(match, entry->path,
+ (matchlen < pathlen) ? matchlen : pathlen);
+ if (m < 0)
+ return 0;
+
+ /*
+ * If we come here even once, that means there is at
+ * least one pathspec that would sort equal to or
+ * later than the path we are currently looking at.
+ * In other words, if we have never reached this point
+ * after iterating all pathspecs, it means all
+ * pathspecs are either outside of base, or inside the
+ * base but sorts strictly earlier than the current
+ * one. In either case, they will never match the
+ * subsequent entries. In such a case, we initialized
+ * the variable to -1 and that is what will be
+ * returned, allowing the caller to terminate early.
+ */
+ *never_interesting = 0;
+ }
+
+ if (pathlen > matchlen)
+ return 0;
+
+ if (matchlen > pathlen) {
+ if (match[pathlen] != '/')
+ return 0;
+ if (!S_ISDIR(entry->mode))
+ return 0;
+ }
+
+ if (m == -1)
+ /*
+ * we cheated and did not do strncmp(), so we do
+ * that here.
+ */
+ m = strncmp(match, entry->path, pathlen);
+
+ /*
+ * If common part matched earlier then it is a hit,
+ * because we rejected the case where path is not a
+ * leading directory and is shorter than match.
+ */
+ if (!m)
+ return 1;
+
+ return 0;
+}
+
+static int match_dir_prefix(const char *base, int baselen,
+ const char *match, int matchlen)
+{
+ if (strncmp(base, match, matchlen))
+ return 0;
+
+ /*
+ * If the base is a subdirectory of a path which
+ * was specified, all of them are interesting.
+ */
+ if (!matchlen ||
+ base[matchlen] == '/' ||
+ match[matchlen - 1] == '/')
+ return 1;
+
+ /* Just a random prefix match */
+ return 0;
+}
+
+/*
+ * Is a tree entry interesting given the pathspec we have?
+ *
+ * Pre-condition: either baselen == base_offset (i.e. empty path)
+ * or base[baselen-1] == '/' (i.e. with trailing slash).
+ *
+ * Return:
+ * - 2 for "yes, and all subsequent entries will be"
+ * - 1 for yes
+ * - zero for no
+ * - negative for "no, and no subsequent entries will be either"
+ */
+int tree_entry_interesting(const struct name_entry *entry,
+ struct strbuf *base, int base_offset,
+ const struct pathspec *ps)
+{
+ int i;
+ int pathlen, baselen = base->len - base_offset;
+ int never_interesting = ps->has_wildcard ? 0 : -1;
+
+ if (!ps->nr) {
+ if (!ps->recursive || ps->max_depth == -1)
+ return 2;
+ return !!within_depth(base->buf + base_offset, baselen,
+ !!S_ISDIR(entry->mode),
+ ps->max_depth);
+ }
+
+ pathlen = tree_entry_len(entry->path, entry->sha1);
+
+ for (i = ps->nr - 1; i >= 0; i--) {
+ const struct pathspec_item *item = ps->items+i;
+ const char *match = item->match;
+ const char *base_str = base->buf + base_offset;
+ int matchlen = item->len;
+
+ if (baselen >= matchlen) {
+ /* If it doesn't match, move along... */
+ if (!match_dir_prefix(base_str, baselen, match, matchlen))
+ goto match_wildcards;
+
+ if (!ps->recursive || ps->max_depth == -1)
+ return 2;
+
+ return !!within_depth(base_str + matchlen + 1,
+ baselen - matchlen - 1,
+ !!S_ISDIR(entry->mode),
+ ps->max_depth);
+ }
+
+ /* Does the base match? */
+ if (!strncmp(base_str, match, baselen)) {
+ if (match_entry(entry, pathlen,
+ match + baselen, matchlen - baselen,
+ &never_interesting))
+ return 1;
+
+ if (ps->items[i].has_wildcard) {
+ if (!fnmatch(match + baselen, entry->path, 0))
+ return 1;
+
+ /*
+ * Match all directories. We'll try to
+ * match files later on.
+ */
+ if (ps->recursive && S_ISDIR(entry->mode))
+ return 1;
+ }
+
+ continue;
+ }
+
+match_wildcards:
+ if (!ps->items[i].has_wildcard)
+ continue;
+
+ /*
+ * Concatenate base and entry->path into one and do
+ * fnmatch() on it.
+ */
+
+ strbuf_add(base, entry->path, pathlen);
+
+ if (!fnmatch(match, base->buf + base_offset, 0)) {
+ strbuf_setlen(base, base_offset + baselen);
+ return 1;
+ }
+ strbuf_setlen(base, base_offset + baselen);
+
+ /*
+ * Match all directories. We'll try to match files
+ * later on.
+ */
+ if (ps->recursive && S_ISDIR(entry->mode))
+ return 1;
+ }
+ return never_interesting; /* No matches */
+}
diff --git a/tree-walk.h b/tree-walk.h
index 7e3e0b5ad16710c06464726ac04d2b1c48af3708..39524b7dba6a1d0b63c4cd2b42db59a27a030b21 100644 (file)
--- a/tree-walk.h
+++ b/tree-walk.h
return info->pathlen + tree_entry_len(n->path, n->sha1);
}
+extern int tree_entry_interesting(const struct name_entry *, struct strbuf *, int, const struct pathspec *ps);
+
#endif
diff --git a/unpack-trees.c b/unpack-trees.c
index d5a453079a02ff8b7a63a3453d07935e0807c005..b68ec820dde935eb2578a8983c265290d0511add 100644 (file)
--- a/unpack-trees.c
+++ b/unpack-trees.c
static int verify_uptodate_sparse(struct cache_entry *ce, struct unpack_trees_options *o);
static int verify_absent_sparse(struct cache_entry *ce, enum unpack_trees_error_types, struct unpack_trees_options *o);
-static int will_have_skip_worktree(const struct cache_entry *ce, struct unpack_trees_options *o)
-{
- const char *basename;
-
- basename = strrchr(ce->name, '/');
- basename = basename ? basename+1 : ce->name;
- return excluded_from_list(ce->name, ce_namelen(ce), basename, NULL, o->el) <= 0;
-}
-
static int apply_sparse_checkout(struct cache_entry *ce, struct unpack_trees_options *o)
{
int was_skip_worktree = ce_skip_worktree(ce);
- if (!ce_stage(ce) && will_have_skip_worktree(ce, o))
+ if (ce->ce_flags & CE_NEW_SKIP_WORKTREE)
ce->ce_flags |= CE_SKIP_WORKTREE;
else
ce->ce_flags &= ~CE_SKIP_WORKTREE;
{
int i;
for (i = 0; i < index->cache_nr; i++)
- index->cache[i]->ce_flags &= ~CE_UNPACKED;
+ index->cache[i]->ce_flags &= ~(CE_UNPACKED | CE_ADDED | CE_NEW_SKIP_WORKTREE);
}
static int locate_in_src_index(struct cache_entry *ce,
static int unpack_index_entry(struct cache_entry *ce,
struct unpack_trees_options *o)
{
- struct cache_entry *src[5] = { NULL };
+ struct cache_entry *src[MAX_UNPACK_TREES + 1] = { NULL, };
int ret;
src[0] = ce;
return ret;
}
-static int traverse_trees_recursive(int n, unsigned long dirmask, unsigned long df_conflicts, struct name_entry *names, struct traverse_info *info)
+static int traverse_trees_recursive(int n, unsigned long dirmask,
+ unsigned long df_conflicts,
+ struct name_entry *names,
+ struct traverse_info *info)
{
int i, ret, bottom;
struct tree_desc t[MAX_UNPACK_TREES];
@@ -820,9 +814,177 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str
return mask;
}
+/* Whole directory matching */
+static int clear_ce_flags_dir(struct cache_entry **cache, int nr,
+ char *prefix, int prefix_len,
+ char *basename,
+ int select_mask, int clear_mask,
+ struct exclude_list *el)
+{
+ struct cache_entry **cache_end = cache + nr;
+ int dtype = DT_DIR;
+ int ret = excluded_from_list(prefix, prefix_len, basename, &dtype, el);
+
+ prefix[prefix_len++] = '/';
+
+ /* included, no clearing for any entries under this directory */
+ if (!ret) {
+ for (; cache != cache_end; cache++) {
+ struct cache_entry *ce = *cache;
+ if (strncmp(ce->name, prefix, prefix_len))
+ break;
+ }
+ return nr - (cache_end - cache);
+ }
+
+ /* excluded, clear all selected entries under this directory. */
+ if (ret == 1) {
+ for (; cache != cache_end; cache++) {
+ struct cache_entry *ce = *cache;
+ if (select_mask && !(ce->ce_flags & select_mask))
+ continue;
+ if (strncmp(ce->name, prefix, prefix_len))
+ break;
+ ce->ce_flags &= ~clear_mask;
+ }
+ return nr - (cache_end - cache);
+ }
+
+ return 0;
+}
+
+/*
+ * Traverse the index, find every entry that matches according to
+ * o->el. Do "ce_flags &= ~clear_mask" on those entries. Return the
+ * number of traversed entries.
+ *
+ * If select_mask is non-zero, only entries whose ce_flags has on of
+ * those bits enabled are traversed.
+ *
+ * cache : pointer to an index entry
+ * prefix_len : an offset to its path
+ *
+ * The current path ("prefix") including the trailing '/' is
+ * cache[0]->name[0..(prefix_len-1)]
+ * Top level path has prefix_len zero.
+ */
+static int clear_ce_flags_1(struct cache_entry **cache, int nr,
+ char *prefix, int prefix_len,
+ int select_mask, int clear_mask,
+ struct exclude_list *el)
+{
+ struct cache_entry **cache_end = cache + nr;
+
+ /*
+ * Process all entries that have the given prefix and meet
+ * select_mask condition
+ */
+ while(cache != cache_end) {
+ struct cache_entry *ce = *cache;
+ const char *name, *slash;
+ int len, dtype;
+
+ if (select_mask && !(ce->ce_flags & select_mask)) {
+ cache++;
+ continue;
+ }
+
+ if (prefix_len && strncmp(ce->name, prefix, prefix_len))
+ break;
+
+ name = ce->name + prefix_len;
+ slash = strchr(name, '/');
+
+ /* If it's a directory, try whole directory match first */
+ if (slash) {
+ int processed;
+
+ len = slash - name;
+ memcpy(prefix + prefix_len, name, len);
+
+ /*
+ * terminate the string (no trailing slash),
+ * clear_c_f_dir needs it
+ */
+ prefix[prefix_len + len] = '\0';
+ processed = clear_ce_flags_dir(cache, cache_end - cache,
+ prefix, prefix_len + len,
+ prefix + prefix_len,
+ select_mask, clear_mask,
+ el);
+
+ /* clear_c_f_dir eats a whole dir already? */
+ if (processed) {
+ cache += processed;
+ continue;
+ }
+
+ prefix[prefix_len + len++] = '/';
+ cache += clear_ce_flags_1(cache, cache_end - cache,
+ prefix, prefix_len + len,
+ select_mask, clear_mask, el);
+ continue;
+ }
+
+ /* Non-directory */
+ dtype = ce_to_dtype(ce);
+ if (excluded_from_list(ce->name, ce_namelen(ce), name, &dtype, el) > 0)
+ ce->ce_flags &= ~clear_mask;
+ cache++;
+ }
+ return nr - (cache_end - cache);
+}
+
+static int clear_ce_flags(struct cache_entry **cache, int nr,
+ int select_mask, int clear_mask,
+ struct exclude_list *el)
+{
+ char prefix[PATH_MAX];
+ return clear_ce_flags_1(cache, nr,
+ prefix, 0,
+ select_mask, clear_mask,
+ el);
+}
+
+/*
+ * Set/Clear CE_NEW_SKIP_WORKTREE according to $GIT_DIR/info/sparse-checkout
+ */
+static void mark_new_skip_worktree(struct exclude_list *el,
+ struct index_state *the_index,
+ int select_flag, int skip_wt_flag)
+{
+ int i;
+
+ /*
+ * 1. Pretend the narrowest worktree: only unmerged entries
+ * are checked out
+ */
+ for (i = 0; i < the_index->cache_nr; i++) {
+ struct cache_entry *ce = the_index->cache[i];
+
+ if (select_flag && !(ce->ce_flags & select_flag))
+ continue;
+
+ if (!ce_stage(ce))
+ ce->ce_flags |= skip_wt_flag;
+ else
+ ce->ce_flags &= ~skip_wt_flag;
+ }
+
+ /*
+ * 2. Widen worktree according to sparse-checkout file.
+ * Matched entries will have skip_wt_flag cleared (i.e. "in")
+ */
+ clear_ce_flags(the_index->cache, the_index->cache_nr,
+ select_flag, skip_wt_flag, el);
+}
+
+static int verify_absent(struct cache_entry *, enum unpack_trees_error_types, struct unpack_trees_options *);
/*
* N-way merge "len" trees. Returns 0 on success, -1 on failure to manipulate the
* resulting index, -2 on failure to reflect the changes to the work tree.
+ *
+ * CE_ADDED, CE_UNPACKED and CE_NEW_SKIP_WORKTREE are used internally
*/
int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options *o)
{
@@ -855,6 +1017,12 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
o->merge_size = len;
mark_all_ce_unused(o->src_index);
+ /*
+ * Sparse checkout loop #1: set NEW_SKIP_WORKTREE on existing entries
+ */
+ if (!o->skip_sparse_checkout)
+ mark_new_skip_worktree(o->el, o->src_index, 0, CE_NEW_SKIP_WORKTREE);
+
if (!dfc)
dfc = xcalloc(1, cache_entry_size(0));
o->df_conflict_entry = dfc;
@@ -908,9 +1076,29 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
if (!o->skip_sparse_checkout) {
int empty_worktree = 1;
- for (i = 0;i < o->result.cache_nr;i++) {
+
+ /*
+ * Sparse checkout loop #2: set NEW_SKIP_WORKTREE on entries not in loop #1
+ * If the will have NEW_SKIP_WORKTREE, also set CE_SKIP_WORKTREE
+ * so apply_sparse_checkout() won't attempt to remove it from worktree
+ */
+ mark_new_skip_worktree(o->el, &o->result, CE_ADDED, CE_SKIP_WORKTREE | CE_NEW_SKIP_WORKTREE);
+
+ for (i = 0; i < o->result.cache_nr; i++) {
struct cache_entry *ce = o->result.cache[i];
+ /*
+ * Entries marked with CE_ADDED in merged_entry() do not have
+ * verify_absent() check (the check is effectively disabled
+ * because CE_NEW_SKIP_WORKTREE is set unconditionally).
+ *
+ * Do the real check now because we have had
+ * correct CE_NEW_SKIP_WORKTREE
+ */
+ if (ce->ce_flags & CE_ADDED &&
+ verify_absent(ce, ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN, o))
+ return -1;
+
if (apply_sparse_checkout(ce, o)) {
ret = -1;
goto done;
@@ -920,6 +1108,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
}
if (o->result.cache_nr && empty_worktree) {
+ /* dubious---why should this fail??? */
ret = unpack_failed(o, "Sparse checkout leaves no entry on working directory");
goto done;
}
@@ -931,11 +1120,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
*o->dst_index = o->result;
done:
- for (i = 0;i < el.nr;i++)
- free(el.excludes[i]);
- if (el.excludes)
- free(el.excludes);
-
+ free_excludes(&el);
return ret;
return_failed:
static int verify_uptodate(struct cache_entry *ce,
struct unpack_trees_options *o)
{
- if (!o->skip_sparse_checkout && will_have_skip_worktree(ce, o))
+ if (!o->skip_sparse_checkout && (ce->ce_flags & CE_NEW_SKIP_WORKTREE))
return 0;
return verify_uptodate_1(ce, o, ERROR_NOT_UPTODATE_FILE);
}
char path[PATH_MAX + 1];
memcpy(path, ce->name, len);
path[len] = 0;
- lstat(path, &st);
+ if (lstat(path, &st))
+ return error("cannot stat '%s': %s", path,
+ strerror(errno));
return check_ok_to_remove(path, len, DT_UNKNOWN, NULL, &st,
error_type, o);
- } else if (!lstat(ce->name, &st))
+ } else if (lstat(ce->name, &st)) {
+ if (errno != ENOENT)
+ return error("cannot stat '%s': %s", ce->name,
+ strerror(errno));
+ return 0;
+ } else {
return check_ok_to_remove(ce->name, ce_namelen(ce),
- ce_to_dtype(ce), ce, &st,
- error_type, o);
-
- return 0;
+ ce_to_dtype(ce), ce, &st,
+ error_type, o);
+ }
}
static int verify_absent(struct cache_entry *ce,
enum unpack_trees_error_types error_type,
struct unpack_trees_options *o)
{
- if (!o->skip_sparse_checkout && will_have_skip_worktree(ce, o))
+ if (!o->skip_sparse_checkout && (ce->ce_flags & CE_NEW_SKIP_WORKTREE))
return 0;
return verify_absent_1(ce, error_type, o);
}
int update = CE_UPDATE;
if (!old) {
+ /*
+ * New index entries. In sparse checkout, the following
+ * verify_absent() will be delayed until after
+ * traverse_trees() finishes in unpack_trees(), then:
+ *
+ * - CE_NEW_SKIP_WORKTREE will be computed correctly
+ * - verify_absent() be called again, this time with
+ * correct CE_NEW_SKIP_WORKTREE
+ *
+ * verify_absent() call here does nothing in sparse
+ * checkout (i.e. o->skip_sparse_checkout == 0)
+ */
+ update |= CE_ADDED;
+ merge->ce_flags |= CE_NEW_SKIP_WORKTREE;
+
if (verify_absent(merge, ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN, o))
return -1;
- if (!o->skip_sparse_checkout && will_have_skip_worktree(merge, o))
- update |= CE_SKIP_WORKTREE;
invalidate_ce_path(merge, o);
} else if (!(old->ce_flags & CE_CONFLICTED)) {
/*
} else {
if (verify_uptodate(old, o))
return -1;
- if (ce_skip_worktree(old))
- update |= CE_SKIP_WORKTREE;
+ /* Migrate old flags over */
+ update |= old->ce_flags & (CE_SKIP_WORKTREE | CE_NEW_SKIP_WORKTREE);
invalidate_ce_path(old, o);
}
} else {
diff --git a/upload-pack.c b/upload-pack.c
index f05e4229d0d3a3ed34b4bba6636f4d333c74dc22..b40a43f27d0e16f0305153f984741938b3a6ab89 100644 (file)
--- a/upload-pack.c
+++ b/upload-pack.c
{
struct commit_list *work = NULL;
- insert_by_date(want, &work);
+ commit_list_insert_by_date(want, &work);
while (work) {
struct commit_list *list = work->next;
struct commit *commit = work->item;
for (list = commit->parents; list; list = list->next) {
struct commit *parent = list->item;
if (!(parent->object.flags & REACHABLE))
- insert_by_date(parent, &work);
+ commit_list_insert_by_date(parent, &work);
}
}
want->object.flags |= REACHABLE;
diff --git a/userdiff.c b/userdiff.c
index 2d5453697aaf5287f524b72d0088ee15679e0691..1ff47977d549992ada8c1236187d4516b648ee7d 100644 (file)
--- a/userdiff.c
+++ b/userdiff.c
static int drivers_alloc;
#define PATTERNS(name, pattern, word_regex) \
- { name, NULL, -1, { pattern, REG_EXTENDED }, word_regex }
+ { name, NULL, -1, { pattern, REG_EXTENDED }, \
+ word_regex "|[^[:space:]]|[\xc0-\xff][\x80-\xbf]+" }
#define IPATTERN(name, pattern, word_regex) \
- { name, NULL, -1, { pattern, REG_EXTENDED | REG_ICASE }, word_regex }
+ { name, NULL, -1, { pattern, REG_EXTENDED | REG_ICASE }, \
+ word_regex "|[^[:space:]]|[\xc0-\xff][\x80-\xbf]+" }
static struct userdiff_driver builtin_drivers[] = {
IPATTERN("fortran",
"!^([C*]|[ \t]*!)\n"
* Don't worry about format statements without leading digits since
* they would have been matched above as a variable anyway. */
"|[-+]?[0-9.]+([AaIiDdEeFfLlTtXx][Ss]?[-+]?[0-9.]*)?(_[a-zA-Z0-9][a-zA-Z0-9_]*)?"
- "|//|\\*\\*|::|[/<>=]="
- "|[^[:space:]]|[\x80-\xff]+"),
+ "|//|\\*\\*|::|[/<>=]="),
PATTERNS("html", "^[ \t]*(<[Hh][1-6][ \t].*>.*)$",
- "[^<>= \t]+|[^[:space:]]|[\x80-\xff]+"),
+ "[^<>= \t]+"),
PATTERNS("java",
"!^[ \t]*(catch|do|for|if|instanceof|new|return|switch|throw|while)\n"
"^[ \t]*(([A-Za-z_][A-Za-z_0-9]*[ \t]+)+[A-Za-z_][A-Za-z_0-9]*[ \t]*\\([^;]*)$",
"[a-zA-Z_][a-zA-Z0-9_]*"
"|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lL]?"
"|[-+*/<>%&^|=!]="
- "|--|\\+\\+|<<=?|>>>?=?|&&|\\|\\|"
- "|[^[:space:]]|[\x80-\xff]+"),
+ "|--|\\+\\+|<<=?|>>>?=?|&&|\\|\\|"),
PATTERNS("objc",
/* Negate C statements that can look like functions */
"!^[ \t]*(do|for|if|else|return|switch|while)\n"
/* -- */
"[a-zA-Z_][a-zA-Z0-9_]*"
"|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lL]?"
- "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->"
- "|[^[:space:]]|[\x80-\xff]+"),
+ "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->"),
PATTERNS("pascal",
- "^((procedure|function|constructor|destructor|interface|"
+ "^(((class[ \t]+)?(procedure|function)|constructor|destructor|interface|"
"implementation|initialization|finalization)[ \t]*.*)$"
"\n"
"^(.*=[ \t]*(class|record).*)$",
/* -- */
"[a-zA-Z_][a-zA-Z0-9_]*"
"|[-+0-9.e]+|0[xXbB]?[0-9a-fA-F]+"
- "|<>|<=|>=|:=|\\.\\."
- "|[^[:space:]]|[\x80-\xff]+"),
+ "|<>|<=|>=|:=|\\.\\."),
+PATTERNS("perl",
+ "^[ \t]*package .*;\n"
+ "^[ \t]*sub .* \\{\n"
+ "^[A-Z]+ \\{\n" /* BEGIN, END, ... */
+ "^=head[0-9] ", /* POD */
+ /* -- */
+ "[[:alpha:]_'][[:alnum:]_']*"
+ "|0[xb]?[0-9a-fA-F_]*"
+ /* taking care not to interpret 3..5 as (3.)(.5) */
+ "|[0-9a-fA-F_]+(\\.[0-9a-fA-F_]+)?([eE][-+]?[0-9_]+)?"
+ "|=>|-[rwxoRWXOezsfdlpSugkbctTBMAC>]|~~|::"
+ "|&&=|\\|\\|=|//=|\\*\\*="
+ "|&&|\\|\\||//|\\+\\+|--|\\*\\*|\\.\\.\\.?"
+ "|[-+*/%.^&<>=!|]="
+ "|=~|!~"
+ "|<<|<>|<=>|>>"),
PATTERNS("php",
"^[\t ]*(((public|protected|private|static)[\t ]+)*function.*)$\n"
"^[\t ]*(class.*)$",
/* -- */
"[a-zA-Z_][a-zA-Z0-9_]*"
"|[-+0-9.e]+|0[xXbB]?[0-9a-fA-F]+"
- "|[-+*/<>%&^|=!.]=|--|\\+\\+|<<=?|>>=?|===|&&|\\|\\||::|->"
- "|[^[:space:]]|[\x80-\xff]+"),
+ "|[-+*/<>%&^|=!.]=|--|\\+\\+|<<=?|>>=?|===|&&|\\|\\||::|->"),
PATTERNS("python", "^[ \t]*((class|def)[ \t].*)$",
/* -- */
"[a-zA-Z_][a-zA-Z0-9_]*"
"|[-+0-9.e]+[jJlL]?|0[xX]?[0-9a-fA-F]+[lL]?"
- "|[-+*/<>%&^|=!]=|//=?|<<=?|>>=?|\\*\\*=?"
- "|[^[:space:]]|[\x80-\xff]+"),
+ "|[-+*/<>%&^|=!]=|//=?|<<=?|>>=?|\\*\\*=?"),
/* -- */
PATTERNS("ruby", "^[ \t]*((class|module|def)[ \t].*)$",
/* -- */
"(@|@@|\\$)?[a-zA-Z_][a-zA-Z0-9_]*"
"|[-+0-9.e]+|0[xXbB]?[0-9a-fA-F]+|\\?(\\\\C-)?(\\\\M-)?."
- "|//=?|[-+*/<>%&^|=!]=|<<=?|>>=?|===|\\.{1,3}|::|[!=]~"
- "|[^[:space:]]|[\x80-\xff]+"),
+ "|//=?|[-+*/<>%&^|=!]=|<<=?|>>=?|===|\\.{1,3}|::|[!=]~"),
PATTERNS("bibtex", "(@[a-zA-Z]{1,}[ \t]*\\{{0,1}[ \t]*[^ \t\"@',\\#}{~%]*).*$",
"[={}\"]|[^={}\" \t]+"),
PATTERNS("tex", "^(\\\\((sub)*section|chapter|part)\\*{0,1}\\{.*)$",
- "\\\\[a-zA-Z@]+|\\\\.|[a-zA-Z0-9\x80-\xff]+|[^[:space:]]"),
+ "\\\\[a-zA-Z@]+|\\\\.|[a-zA-Z0-9\x80-\xff]+"),
PATTERNS("cpp",
/* Jump targets or access declarations */
"!^[ \t]*[A-Za-z_][A-Za-z_0-9]*:.*$\n"
/* -- */
"[a-zA-Z_][a-zA-Z0-9_]*"
"|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lL]?"
- "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->"
- "|[^[:space:]]|[\x80-\xff]+"),
+ "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->"),
PATTERNS("csharp",
/* Keywords */
"!^[ \t]*(do|while|for|if|else|instanceof|new|return|switch|case|throw|catch|using)\n"
/* -- */
"[a-zA-Z_][a-zA-Z0-9_]*"
"|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lL]?"
- "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->"
- "|[^[:space:]]|[\x80-\xff]+"),
+ "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->"),
{ "default", NULL, -1, { NULL, 0 } },
};
#undef PATTERNS
diff --git a/vcs-svn/fast_export.c b/vcs-svn/fast_export.c
index 6cfa256a3702332205faab546a405f6810cc16bd..260cf50e7725c9199fa44134a7f3408da7cad984 100644 (file)
--- a/vcs-svn/fast_export.c
+++ b/vcs-svn/fast_export.c
printf("progress Imported commit %"PRIu32".\n\n", revision);
}
-void fast_export_blob(uint32_t mode, uint32_t mark, uint32_t len)
+void fast_export_blob(uint32_t mode, uint32_t mark, uint32_t len, struct line_buffer *input)
{
if (mode == REPO_MODE_LNK) {
/* svn symlink blobs start with "link " */
- buffer_skip_bytes(5);
+ buffer_skip_bytes(input, 5);
len -= 5;
}
printf("blob\nmark :%"PRIu32"\ndata %"PRIu32"\n", mark, len);
- buffer_copy_bytes(len);
+ buffer_copy_bytes(input, len);
fputc('\n', stdout);
}
diff --git a/vcs-svn/fast_export.h b/vcs-svn/fast_export.h
index 2aaaea53d57bcc6a5280765dd39edf7fc5569c19..054e7d5eb1aeb4b2808acc0360e6371a376d40f7 100644 (file)
--- a/vcs-svn/fast_export.h
+++ b/vcs-svn/fast_export.h
#ifndef FAST_EXPORT_H_
#define FAST_EXPORT_H_
+#include "line_buffer.h"
+
void fast_export_delete(uint32_t depth, uint32_t *path);
void fast_export_modify(uint32_t depth, uint32_t *path, uint32_t mode,
uint32_t mark);
void fast_export_commit(uint32_t revision, uint32_t author, char *log,
uint32_t uuid, uint32_t url, unsigned long timestamp);
-void fast_export_blob(uint32_t mode, uint32_t mark, uint32_t len);
+void fast_export_blob(uint32_t mode, uint32_t mark, uint32_t len,
+ struct line_buffer *input);
#endif
diff --git a/vcs-svn/line_buffer.c b/vcs-svn/line_buffer.c
index 1543567093fad603e09ba61b2fc47ec232b4cae2..aedf105b70586f2ef7567547426bb6622799b067 100644 (file)
--- a/vcs-svn/line_buffer.c
+++ b/vcs-svn/line_buffer.c
#include "git-compat-util.h"
#include "line_buffer.h"
-#include "obj_pool.h"
+#include "strbuf.h"
-#define LINE_BUFFER_LEN 10000
#define COPY_BUFFER_LEN 4096
-/* Create memory pool for char sequence of known length */
-obj_pool_gen(blob, char, 4096)
+int buffer_init(struct line_buffer *buf, const char *filename)
+{
+ buf->infile = filename ? fopen(filename, "r") : stdin;
+ if (!buf->infile)
+ return -1;
+ return 0;
+}
-static char line_buffer[LINE_BUFFER_LEN];
-static char byte_buffer[COPY_BUFFER_LEN];
-static FILE *infile;
+int buffer_fdinit(struct line_buffer *buf, int fd)
+{
+ buf->infile = fdopen(fd, "r");
+ if (!buf->infile)
+ return -1;
+ return 0;
+}
-int buffer_init(const char *filename)
+int buffer_tmpfile_init(struct line_buffer *buf)
{
- infile = filename ? fopen(filename, "r") : stdin;
- if (!infile)
+ buf->infile = tmpfile();
+ if (!buf->infile)
return -1;
return 0;
}
-int buffer_deinit(void)
+int buffer_deinit(struct line_buffer *buf)
{
int err;
- if (infile == stdin)
- return ferror(infile);
- err = ferror(infile);
- err |= fclose(infile);
+ if (buf->infile == stdin)
+ return ferror(buf->infile);
+ err = ferror(buf->infile);
+ err |= fclose(buf->infile);
return err;
}
+FILE *buffer_tmpfile_rewind(struct line_buffer *buf)
+{
+ rewind(buf->infile);
+ return buf->infile;
+}
+
+long buffer_tmpfile_prepare_to_read(struct line_buffer *buf)
+{
+ long pos = ftell(buf->infile);
+ if (pos < 0)
+ return error("ftell error: %s", strerror(errno));
+ if (fseek(buf->infile, 0, SEEK_SET))
+ return error("seek error: %s", strerror(errno));
+ return pos;
+}
+
+int buffer_read_char(struct line_buffer *buf)
+{
+ return fgetc(buf->infile);
+}
+
/* Read a line without trailing newline. */
-char *buffer_read_line(void)
+char *buffer_read_line(struct line_buffer *buf)
{
char *end;
- if (!fgets(line_buffer, sizeof(line_buffer), infile))
+ if (!fgets(buf->line_buffer, sizeof(buf->line_buffer), buf->infile))
/* Error or data exhausted. */
return NULL;
- end = line_buffer + strlen(line_buffer);
+ end = buf->line_buffer + strlen(buf->line_buffer);
if (end[-1] == '\n')
end[-1] = '\0';
- else if (feof(infile))
+ else if (feof(buf->infile))
; /* No newline at end of file. That's fine. */
else
/*
* but for now let's return an error.
*/
return NULL;
- return line_buffer;
+ return buf->line_buffer;
+}
+
+char *buffer_read_string(struct line_buffer *buf, uint32_t len)
+{
+ strbuf_reset(&buf->blob_buffer);
+ strbuf_fread(&buf->blob_buffer, len, buf->infile);
+ return ferror(buf->infile) ? NULL : buf->blob_buffer.buf;
}
-char *buffer_read_string(uint32_t len)
+void buffer_read_binary(struct line_buffer *buf,
+ struct strbuf *sb, uint32_t size)
{
- char *s;
- blob_free(blob_pool.size);
- s = blob_pointer(blob_alloc(len + 1));
- s[fread(s, 1, len, infile)] = '\0';
- return ferror(infile) ? NULL : s;
+ strbuf_fread(sb, size, buf->infile);
}
-void buffer_copy_bytes(uint32_t len)
+void buffer_copy_bytes(struct line_buffer *buf, uint32_t len)
{
+ char byte_buffer[COPY_BUFFER_LEN];
uint32_t in;
- while (len > 0 && !feof(infile) && !ferror(infile)) {
+ while (len > 0 && !feof(buf->infile) && !ferror(buf->infile)) {
in = len < COPY_BUFFER_LEN ? len : COPY_BUFFER_LEN;
- in = fread(byte_buffer, 1, in, infile);
+ in = fread(byte_buffer, 1, in, buf->infile);
len -= in;
fwrite(byte_buffer, 1, in, stdout);
if (ferror(stdout)) {
- buffer_skip_bytes(len);
+ buffer_skip_bytes(buf, len);
return;
}
}
}
-void buffer_skip_bytes(uint32_t len)
+void buffer_skip_bytes(struct line_buffer *buf, uint32_t len)
{
+ char byte_buffer[COPY_BUFFER_LEN];
uint32_t in;
- while (len > 0 && !feof(infile) && !ferror(infile)) {
+ while (len > 0 && !feof(buf->infile) && !ferror(buf->infile)) {
in = len < COPY_BUFFER_LEN ? len : COPY_BUFFER_LEN;
- in = fread(byte_buffer, 1, in, infile);
+ in = fread(byte_buffer, 1, in, buf->infile);
len -= in;
}
}
-void buffer_reset(void)
+void buffer_reset(struct line_buffer *buf)
{
- blob_reset();
+ strbuf_release(&buf->blob_buffer);
}
diff --git a/vcs-svn/line_buffer.h b/vcs-svn/line_buffer.h
index 9c78ae11a1e0774611b61a5407fab6cabfec226a..96ce966a229110e6775cd33ee01a93d5f133404e 100644 (file)
--- a/vcs-svn/line_buffer.h
+++ b/vcs-svn/line_buffer.h
#ifndef LINE_BUFFER_H_
#define LINE_BUFFER_H_
-int buffer_init(const char *filename);
-int buffer_deinit(void);
-char *buffer_read_line(void);
-char *buffer_read_string(uint32_t len);
-void buffer_copy_bytes(uint32_t len);
-void buffer_skip_bytes(uint32_t len);
-void buffer_reset(void);
+#include "strbuf.h"
+
+#define LINE_BUFFER_LEN 10000
+
+struct line_buffer {
+ char line_buffer[LINE_BUFFER_LEN];
+ struct strbuf blob_buffer;
+ FILE *infile;
+};
+#define LINE_BUFFER_INIT {"", STRBUF_INIT, NULL}
+
+int buffer_init(struct line_buffer *buf, const char *filename);
+int buffer_fdinit(struct line_buffer *buf, int fd);
+int buffer_deinit(struct line_buffer *buf);
+void buffer_reset(struct line_buffer *buf);
+
+int buffer_tmpfile_init(struct line_buffer *buf);
+FILE *buffer_tmpfile_rewind(struct line_buffer *buf); /* prepare to write. */
+long buffer_tmpfile_prepare_to_read(struct line_buffer *buf);
+
+char *buffer_read_line(struct line_buffer *buf);
+char *buffer_read_string(struct line_buffer *buf, uint32_t len);
+int buffer_read_char(struct line_buffer *buf);
+void buffer_read_binary(struct line_buffer *buf, struct strbuf *sb, uint32_t len);
+void buffer_copy_bytes(struct line_buffer *buf, uint32_t len);
+void buffer_skip_bytes(struct line_buffer *buf, uint32_t len);
#endif
index 8906fb1f505e125ea58fd7a15c4f13bb0dc78f27..e89cc41d562448ad65a42b039dcf06a8c38e3335 100644 (file)
--- a/vcs-svn/line_buffer.txt
+++ b/vcs-svn/line_buffer.txt
The calling program:
+ - initializes a `struct line_buffer` to LINE_BUFFER_INIT
- specifies a file to read with `buffer_init`
- processes input with `buffer_read_line`, `buffer_read_string`,
`buffer_skip_bytes`, and `buffer_copy_bytes`
- closes the file with `buffer_deinit`, perhaps to start over and
read another file.
-Before exiting, the caller can use `buffer_reset` to deallocate
-resources for the benefit of profiling tools.
+When finished, the caller can use `buffer_reset` to deallocate
+resources.
+
+Using temporary files
+---------------------
+
+Temporary files provide a place to store data that should not outlive
+the calling program. A program
+
+ - initializes a `struct line_buffer` to LINE_BUFFER_INIT
+ - requests a temporary file with `buffer_tmpfile_init`
+ - acquires an output handle by calling `buffer_tmpfile_rewind`
+ - uses standard I/O functions like `fprintf` and `fwrite` to fill
+ the temporary file
+ - declares writing is over with `buffer_tmpfile_prepare_to_read`
+ - can re-read what was written with `buffer_read_line`,
+ `buffer_read_string`, and so on
+ - can reuse the temporary file by calling `buffer_tmpfile_rewind`
+ again
+ - removes the temporary file with `buffer_deinit`, perhaps to
+ reuse the line_buffer for some other file.
+
+When finished, the calling program can use `buffer_reset` to deallocate
+resources.
Functions
---------
-`buffer_init`::
- Open the named file for input. If filename is NULL,
- start reading from stdin. On failure, returns -1 (with
- errno indicating the nature of the failure).
+`buffer_init`, `buffer_fdinit`::
+ Open the named file or file descriptor for input.
+ buffer_init(buf, NULL) prepares to read from stdin.
+ On failure, returns -1 (with errno indicating the nature
+ of the failure).
`buffer_deinit`::
Stop reading from the current file (closing it unless
diff --git a/vcs-svn/repo_tree.c b/vcs-svn/repo_tree.c
index e94d91d12966562e4cd43df9c34990dddc3465c3..491f0135a798f518fe2d226c9d01af90e6dac736 100644 (file)
--- a/vcs-svn/repo_tree.c
+++ b/vcs-svn/repo_tree.c
if (dent == key) {
dent->mode = REPO_MODE_DIR;
dent->content_offset = 0;
- dent_insert(&dir->entries, dent);
+ dent = dent_insert(&dir->entries, dent);
}
if (dent_offset(dent) < dent_pool.committed) {
dent->name_offset = name;
dent->mode = REPO_MODE_DIR;
dent->content_offset = dir_o;
- dent_insert(&dir->entries, dent);
+ dent = dent_insert(&dir->entries, dent);
}
dir = repo_dir_from_dirent(dent);
repo_write_dirent(path, mode, blob_mark, 0);
}
-uint32_t repo_replace(uint32_t *path, uint32_t blob_mark)
+uint32_t repo_modify_path(uint32_t *path, uint32_t mode, uint32_t blob_mark)
{
- uint32_t mode = 0;
struct repo_dirent *src_dent;
src_dent = repo_read_dirent(active_commit, path);
- if (src_dent != NULL) {
- mode = src_dent->mode;
- repo_write_dirent(path, mode, blob_mark, 0);
- }
- return mode;
-}
-
-void repo_modify(uint32_t *path, uint32_t mode, uint32_t blob_mark)
-{
- struct repo_dirent *src_dent;
- src_dent = repo_read_dirent(active_commit, path);
- if (src_dent != NULL && blob_mark == 0)
+ if (!src_dent)
+ return 0;
+ if (!blob_mark)
blob_mark = src_dent->content_offset;
+ if (!mode)
+ mode = src_dent->mode;
repo_write_dirent(path, mode, blob_mark, 0);
+ return mode;
}
void repo_delete(uint32_t *path)
diff --git a/vcs-svn/repo_tree.h b/vcs-svn/repo_tree.h
index 5476175922740eaba663533a58deedfa981de659..68baeb582ff0ad8700061a78947cd186ff116536 100644 (file)
--- a/vcs-svn/repo_tree.h
+++ b/vcs-svn/repo_tree.h
uint32_t next_blob_mark(void);
uint32_t repo_copy(uint32_t revision, uint32_t *src, uint32_t *dst);
void repo_add(uint32_t *path, uint32_t mode, uint32_t blob_mark);
-uint32_t repo_replace(uint32_t *path, uint32_t blob_mark);
-void repo_modify(uint32_t *path, uint32_t mode, uint32_t blob_mark);
+uint32_t repo_modify_path(uint32_t *path, uint32_t mode, uint32_t blob_mark);
void repo_delete(uint32_t *path);
void repo_commit(uint32_t revision, uint32_t author, char *log, uint32_t uuid,
uint32_t url, long unsigned timestamp);
diff --git a/vcs-svn/svndump.c b/vcs-svn/svndump.c
index 53d0215d2d8f38fee589321890c9305c8da4d2c5..ee7c0bb2ea869c66cdbab4f4f23a0d2207e6b27d 100644 (file)
--- a/vcs-svn/svndump.c
+++ b/vcs-svn/svndump.c
/* Create memory pool for log messages */
obj_pool_gen(log, char, 4096)
-static char* log_copy(uint32_t length, char *log)
+static struct line_buffer input = LINE_BUFFER_INIT;
+
+static char *log_copy(uint32_t length, const char *log)
{
char *buffer;
log_free(log_pool.size);
}
static struct {
- uint32_t action, propLength, textLength, srcRev, srcMode, mark, type;
+ uint32_t action, propLength, textLength, srcRev, type;
uint32_t src[REPO_MAX_PATH_DEPTH], dst[REPO_MAX_PATH_DEPTH];
+ uint32_t text_delta, prop_delta;
} node_ctx;
static struct {
} rev_ctx;
static struct {
- uint32_t uuid, url;
+ uint32_t version, uuid, url;
} dump_ctx;
static struct {
uint32_t svn_log, svn_author, svn_date, svn_executable, svn_special, uuid,
revision_number, node_path, node_kind, node_action,
node_copyfrom_path, node_copyfrom_rev, text_content_length,
- prop_content_length, content_length;
+ prop_content_length, content_length, svn_fs_dump_format_version,
+ /* version 3 format */
+ text_delta, prop_delta;
} keys;
static void reset_node_ctx(char *fname)
node_ctx.textLength = LENGTH_UNKNOWN;
node_ctx.src[0] = ~0;
node_ctx.srcRev = 0;
- node_ctx.srcMode = 0;
pool_tok_seq(REPO_MAX_PATH_DEPTH, node_ctx.dst, "/", fname);
- node_ctx.mark = 0;
+ node_ctx.text_delta = 0;
+ node_ctx.prop_delta = 0;
}
static void reset_rev_ctx(uint32_t revision)
static void reset_dump_ctx(uint32_t url)
{
dump_ctx.url = url;
+ dump_ctx.version = 1;
dump_ctx.uuid = ~0;
}
keys.text_content_length = pool_intern("Text-content-length");
keys.prop_content_length = pool_intern("Prop-content-length");
keys.content_length = pool_intern("Content-length");
+ keys.svn_fs_dump_format_version = pool_intern("SVN-fs-dump-format-version");
+ /* version 3 format (Subversion 1.1.0) */
+ keys.text_delta = pool_intern("Text-delta");
+ keys.prop_delta = pool_intern("Prop-delta");
+}
+
+static void handle_property(uint32_t key, const char *val, uint32_t len,
+ uint32_t *type_set)
+{
+ if (key == keys.svn_log) {
+ if (!val)
+ die("invalid dump: unsets svn:log");
+ /* Value length excludes terminating nul. */
+ rev_ctx.log = log_copy(len + 1, val);
+ } else if (key == keys.svn_author) {
+ rev_ctx.author = pool_intern(val);
+ } else if (key == keys.svn_date) {
+ if (!val)
+ die("invalid dump: unsets svn:date");
+ if (parse_date_basic(val, &rev_ctx.timestamp, NULL))
+ warning("invalid timestamp: %s", val);
+ } else if (key == keys.svn_executable || key == keys.svn_special) {
+ if (*type_set) {
+ if (!val)
+ return;
+ die("invalid dump: sets type twice");
+ }
+ if (!val) {
+ node_ctx.type = REPO_MODE_BLB;
+ return;
+ }
+ *type_set = 1;
+ node_ctx.type = key == keys.svn_executable ?
+ REPO_MODE_EXE :
+ REPO_MODE_LNK;
+ }
}
static void read_props(void)
{
- uint32_t len;
uint32_t key = ~0;
- char *val = NULL;
- char *t;
- while ((t = buffer_read_line()) && strcmp(t, "PROPS-END")) {
- if (!strncmp(t, "K ", 2)) {
- len = atoi(&t[2]);
- key = pool_intern(buffer_read_string(len));
- buffer_read_line();
- } else if (!strncmp(t, "V ", 2)) {
- len = atoi(&t[2]);
- val = buffer_read_string(len);
- if (key == keys.svn_log) {
- /* Value length excludes terminating nul. */
- rev_ctx.log = log_copy(len + 1, val);
- } else if (key == keys.svn_author) {
- rev_ctx.author = pool_intern(val);
- } else if (key == keys.svn_date) {
- if (parse_date_basic(val, &rev_ctx.timestamp, NULL))
- fprintf(stderr, "Invalid timestamp: %s\n", val);
- } else if (key == keys.svn_executable) {
- node_ctx.type = REPO_MODE_EXE;
- } else if (key == keys.svn_special) {
- node_ctx.type = REPO_MODE_LNK;
- }
+ const char *t;
+ /*
+ * NEEDSWORK: to support simple mode changes like
+ * K 11
+ * svn:special
+ * V 1
+ * *
+ * D 14
+ * svn:executable
+ * we keep track of whether a mode has been set and reset to
+ * plain file only if not. We should be keeping track of the
+ * symlink and executable bits separately instead.
+ */
+ uint32_t type_set = 0;
+ while ((t = buffer_read_line(&input)) && strcmp(t, "PROPS-END")) {
+ uint32_t len;
+ const char *val;
+ const char type = t[0];
+
+ if (!type || t[1] != ' ')
+ die("invalid property line: %s\n", t);
+ len = atoi(&t[2]);
+ val = buffer_read_string(&input, len);
+ buffer_skip_bytes(&input, 1); /* Discard trailing newline. */
+
+ switch (type) {
+ case 'K':
+ key = pool_intern(val);
+ continue;
+ case 'D':
+ key = pool_intern(val);
+ val = NULL;
+ len = 0;
+ /* fall through */
+ case 'V':
+ handle_property(key, val, len, &type_set);
key = ~0;
- buffer_read_line();
+ continue;
+ default:
+ die("invalid property line: %s\n", t);
}
}
}
static void handle_node(void)
{
- if (node_ctx.propLength != LENGTH_UNKNOWN && node_ctx.propLength)
- read_props();
-
- if (node_ctx.srcRev)
- node_ctx.srcMode = repo_copy(node_ctx.srcRev, node_ctx.src, node_ctx.dst);
-
- if (node_ctx.textLength != LENGTH_UNKNOWN &&
- node_ctx.type != REPO_MODE_DIR)
- node_ctx.mark = next_blob_mark();
+ uint32_t mark = 0;
+ const uint32_t type = node_ctx.type;
+ const int have_props = node_ctx.propLength != LENGTH_UNKNOWN;
+ if (node_ctx.text_delta)
+ die("text deltas not supported");
+ if (node_ctx.textLength != LENGTH_UNKNOWN)
+ mark = next_blob_mark();
if (node_ctx.action == NODEACT_DELETE) {
+ if (mark || have_props || node_ctx.srcRev)
+ die("invalid dump: deletion node has "
+ "copyfrom info, text, or properties");
+ return repo_delete(node_ctx.dst);
+ }
+ if (node_ctx.action == NODEACT_REPLACE) {
repo_delete(node_ctx.dst);
- } else if (node_ctx.action == NODEACT_CHANGE ||
- node_ctx.action == NODEACT_REPLACE) {
- if (node_ctx.action == NODEACT_REPLACE &&
- node_ctx.type == REPO_MODE_DIR)
- repo_replace(node_ctx.dst, node_ctx.mark);
- else if (node_ctx.propLength != LENGTH_UNKNOWN)
- repo_modify(node_ctx.dst, node_ctx.type, node_ctx.mark);
- else if (node_ctx.textLength != LENGTH_UNKNOWN)
- node_ctx.srcMode = repo_replace(node_ctx.dst, node_ctx.mark);
+ node_ctx.action = NODEACT_ADD;
+ }
+ if (node_ctx.srcRev) {
+ repo_copy(node_ctx.srcRev, node_ctx.src, node_ctx.dst);
+ if (node_ctx.action == NODEACT_ADD)
+ node_ctx.action = NODEACT_CHANGE;
+ }
+ if (mark && type == REPO_MODE_DIR)
+ die("invalid dump: directories cannot have text attached");
+ if (node_ctx.action == NODEACT_CHANGE && !~*node_ctx.dst) {
+ if (type != REPO_MODE_DIR)
+ die("invalid dump: root of tree is not a regular file");
+ } else if (node_ctx.action == NODEACT_CHANGE) {
+ uint32_t mode = repo_modify_path(node_ctx.dst, 0, mark);
+ if (!mode)
+ die("invalid dump: path to be modified is missing");
+ if (mode == REPO_MODE_DIR && type != REPO_MODE_DIR)
+ die("invalid dump: cannot modify a directory into a file");
+ if (mode != REPO_MODE_DIR && type == REPO_MODE_DIR)
+ die("invalid dump: cannot modify a file into a directory");
+ node_ctx.type = mode;
} else if (node_ctx.action == NODEACT_ADD) {
- if (node_ctx.srcRev && node_ctx.propLength != LENGTH_UNKNOWN)
- repo_modify(node_ctx.dst, node_ctx.type, node_ctx.mark);
- else if (node_ctx.srcRev && node_ctx.textLength != LENGTH_UNKNOWN)
- node_ctx.srcMode = repo_replace(node_ctx.dst, node_ctx.mark);
- else if ((node_ctx.type == REPO_MODE_DIR && !node_ctx.srcRev) ||
- node_ctx.textLength != LENGTH_UNKNOWN)
- repo_add(node_ctx.dst, node_ctx.type, node_ctx.mark);
+ if (!mark && type != REPO_MODE_DIR)
+ die("invalid dump: adds node without text");
+ repo_add(node_ctx.dst, type, mark);
+ } else {
+ die("invalid dump: Node-path block lacks Node-action");
}
-
- if (node_ctx.propLength == LENGTH_UNKNOWN && node_ctx.srcMode)
- node_ctx.type = node_ctx.srcMode;
-
- if (node_ctx.mark)
- fast_export_blob(node_ctx.type, node_ctx.mark, node_ctx.textLength);
- else if (node_ctx.textLength != LENGTH_UNKNOWN)
- buffer_skip_bytes(node_ctx.textLength);
+ if (have_props) {
+ const uint32_t old_mode = node_ctx.type;
+ if (!node_ctx.prop_delta)
+ node_ctx.type = type;
+ if (node_ctx.propLength)
+ read_props();
+ if (node_ctx.type != old_mode)
+ repo_modify_path(node_ctx.dst, node_ctx.type, mark);
+ }
+ if (mark)
+ fast_export_blob(node_ctx.type, mark,
+ node_ctx.textLength, &input);
}
static void handle_revision(void)
uint32_t key;
reset_dump_ctx(pool_intern(url));
- while ((t = buffer_read_line())) {
+ while ((t = buffer_read_line(&input))) {
val = strstr(t, ": ");
if (!val)
continue;
*val++ = '\0';
key = pool_intern(t);
- if (key == keys.uuid) {
+ if (key == keys.svn_fs_dump_format_version) {
+ dump_ctx.version = atoi(val);
+ if (dump_ctx.version > 3)
+ die("expected svn dump format version <= 3, found %"PRIu32,
+ dump_ctx.version);
+ } else if (key == keys.uuid) {
dump_ctx.uuid = pool_intern(val);
} else if (key == keys.revision_number) {
if (active_ctx == NODE_CTX)
node_ctx.textLength = atoi(val);
} else if (key == keys.prop_content_length) {
node_ctx.propLength = atoi(val);
+ } else if (key == keys.text_delta) {
+ node_ctx.text_delta = !strcmp(val, "true");
+ } else if (key == keys.prop_delta) {
+ node_ctx.prop_delta = !strcmp(val, "true");
} else if (key == keys.content_length) {
len = atoi(val);
- buffer_read_line();
+ buffer_read_line(&input);
if (active_ctx == REV_CTX) {
read_props();
} else if (active_ctx == NODE_CTX) {
active_ctx = REV_CTX;
} else {
fprintf(stderr, "Unexpected content length header: %"PRIu32"\n", len);
- buffer_skip_bytes(len);
+ buffer_skip_bytes(&input, len);
}
}
}
handle_revision();
}
-void svndump_init(const char *filename)
+int svndump_init(const char *filename)
{
- buffer_init(filename);
+ if (buffer_init(&input, filename))
+ return error("cannot open %s: %s", filename, strerror(errno));
repo_init();
reset_dump_ctx(~0);
reset_rev_ctx(0);
reset_node_ctx(NULL);
init_keys();
+ return 0;
}
void svndump_deinit(void)
reset_dump_ctx(~0);
reset_rev_ctx(0);
reset_node_ctx(NULL);
- if (buffer_deinit())
+ if (buffer_deinit(&input))
fprintf(stderr, "Input error\n");
if (ferror(stdout))
fprintf(stderr, "Output error\n");
void svndump_reset(void)
{
log_reset();
- buffer_reset();
+ buffer_reset(&input);
repo_reset();
reset_dump_ctx(~0);
reset_rev_ctx(0);
diff --git a/vcs-svn/svndump.h b/vcs-svn/svndump.h
index 93c412f14a4ca48b9de18325c09d69f051d97647..df9ceb0e8d473b65fc22ac142d3856dc4f9980e0 100644 (file)
--- a/vcs-svn/svndump.h
+++ b/vcs-svn/svndump.h
#ifndef SVNDUMP_H_
#define SVNDUMP_H_
-void svndump_init(const char *filename);
+int svndump_init(const char *filename);
void svndump_read(const char *url);
void svndump_deinit(void);
void svndump_reset(void);
diff --git a/vcs-svn/trp.h b/vcs-svn/trp.h
index ee35c688a0008ac210cdd28a82191c5a7cb7987e..c32b9184e9b8420e4d4e2904806bd2f14814132b 100644 (file)
--- a/vcs-svn/trp.h
+++ b/vcs-svn/trp.h
@@ -188,11 +188,12 @@ a_attr uint32_t MAYBE_UNUSED a_pre##insert_recurse(uint32_t cur_node, uint32_t i
return ret; \
} \
} \
-a_attr void MAYBE_UNUSED a_pre##insert(struct trp_root *treap, a_type *node) \
+a_attr a_type *MAYBE_UNUSED a_pre##insert(struct trp_root *treap, a_type *node) \
{ \
uint32_t offset = trpn_offset(a_base, node); \
trp_node_new(a_base, a_field, offset); \
treap->trp_root = a_pre##insert_recurse(treap->trp_root, offset); \
+ return trpn_pointer(a_base, offset); \
} \
a_attr uint32_t MAYBE_UNUSED a_pre##remove_recurse(uint32_t cur_node, uint32_t rem_node) \
{ \
diff --git a/vcs-svn/trp.txt b/vcs-svn/trp.txt
index eb4c191875f4580d18c1db1913cfe99f658a5f83..5ca6b42edb289c5f5714ae2f0a09710d40dd14f3 100644 (file)
--- a/vcs-svn/trp.txt
+++ b/vcs-svn/trp.txt
. Allocates a `struct trp_root` variable and sets it to {~0}.
-. Adds new nodes to the set using `foo_insert`.
+. Adds new nodes to the set using `foo_insert`. Any pointers
+ to existing nodes cannot be relied upon any more, so the caller
+ might retrieve them anew with `foo_pointer`.
. Can find a specific item in the set using `foo_search`.
and returning a value less than, equal to, or greater than zero
according to the result of comparison.
-void foo_insert(struct trp_root *treap, node_type \*node)::
+node_type {asterisk}foo_insert(struct trp_root *treap, node_type \*node)::
Insert node into treap. If inserted multiple times,
a node will appear in the treap multiple times.
++
+The return value is the address of the node within the treap,
+which might differ from `node` if `pool_alloc` had to call
+`realloc` to expand the pool.
void foo_remove(struct trp_root *treap, node_type \*node)::
diff --git a/walker.c b/walker.c
index 11d9052ed86ab17b56ab86ef21b7c98095e410dc..dce7128daf8487a61e8d2f35cf15fca618964f86 100644 (file)
--- a/walker.c
+++ b/walker.c
struct commit *commit = lookup_commit_reference_gently(sha1, 1);
if (commit) {
commit->object.flags |= COMPLETE;
- insert_by_date(commit, &complete);
+ commit_list_insert_by_date(commit, &complete);
}
return 0;
}
diff --git a/wrapper.c b/wrapper.c
index fd8ead33ed72c37e690ee1fc5b8568f629c95145..4c147d6c48c000bab636fad3edc2fe7da6670948 100644 (file)
--- a/wrapper.c
+++ b/wrapper.c
*/
#include "cache.h"
-static void try_to_free_builtin(size_t size)
+static void do_nothing(size_t size)
{
- release_pack_memory(size, -1);
}
-static void (*try_to_free_routine)(size_t size) = try_to_free_builtin;
+static void (*try_to_free_routine)(size_t size) = do_nothing;
try_to_free_t set_try_to_free_routine(try_to_free_t routine)
{
try_to_free_t old = try_to_free_routine;
+ if (!routine)
+ routine = do_nothing;
try_to_free_routine = routine;
return old;
}
void *xmallocz(size_t size)
{
void *ret;
- if (size + 1 < size)
+ if (unsigned_add_overflows(size, 1))
die("Data too large to fit into virtual memory space.");
ret = xmalloc(size + 1);
((char*)ret)[size] = 0;
return ret;
}
-void *xmmap(void *start, size_t length,
- int prot, int flags, int fd, off_t offset)
-{
- void *ret = mmap(start, length, prot, flags, fd, offset);
- if (ret == MAP_FAILED) {
- if (!length)
- return NULL;
- release_pack_memory(length, fd);
- ret = mmap(start, length, prot, flags, fd, offset);
- if (ret == MAP_FAILED)
- die_errno("Out of memory? mmap failed");
- }
- return ret;
-}
-
/*
* xread() is the same a read(), but it automatically restarts read()
* operations with a recoverable error (EAGAIN and EINTR). xread()
int xmkstemp(char *template)
{
int fd;
+ char origtemplate[PATH_MAX];
+ strlcpy(origtemplate, template, sizeof(origtemplate));
fd = mkstemp(template);
- if (fd < 0)
- die_errno("Unable to create temporary file");
- return fd;
-}
+ if (fd < 0) {
+ int saved_errno = errno;
+ const char *nonrelative_template;
-int xmkstemp_mode(char *template, int mode)
-{
- int fd;
+ if (!template[0])
+ template = origtemplate;
- fd = git_mkstemp_mode(template, mode);
- if (fd < 0)
- die_errno("Unable to create temporary file");
+ nonrelative_template = make_nonrelative_path(template);
+ errno = saved_errno;
+ die_errno("Unable to create temporary file '%s'",
+ nonrelative_template);
+ }
return fd;
}
-/*
- * zlib wrappers to make sure we don't silently miss errors
- * at init time.
- */
-void git_inflate_init(z_streamp strm)
+/* git_mkstemp() - create tmp file honoring TMPDIR variable */
+int git_mkstemp(char *path, size_t len, const char *template)
{
- const char *err;
-
- switch (inflateInit(strm)) {
- case Z_OK:
- return;
-
- case Z_MEM_ERROR:
- err = "out of memory";
- break;
- case Z_VERSION_ERROR:
- err = "wrong version";
- break;
- default:
- err = "error";
+ const char *tmp;
+ size_t n;
+
+ tmp = getenv("TMPDIR");
+ if (!tmp)
+ tmp = "/tmp";
+ n = snprintf(path, len, "%s/%s", tmp, template);
+ if (len <= n) {
+ errno = ENAMETOOLONG;
+ return -1;
}
- die("inflateInit: %s (%s)", err, strm->msg ? strm->msg : "no message");
+ return mkstemp(path);
}
-void git_inflate_end(z_streamp strm)
+/* git_mkstemps() - create tmp file with suffix honoring TMPDIR variable. */
+int git_mkstemps(char *path, size_t len, const char *template, int suffix_len)
{
- if (inflateEnd(strm) != Z_OK)
- error("inflateEnd: %s", strm->msg ? strm->msg : "failed");
+ const char *tmp;
+ size_t n;
+
+ tmp = getenv("TMPDIR");
+ if (!tmp)
+ tmp = "/tmp";
+ n = snprintf(path, len, "%s/%s", tmp, template);
+ if (len <= n) {
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+ return mkstemps(path, suffix_len);
}
-int git_inflate(z_streamp strm, int flush)
+/* Adapted from libiberty's mkstemp.c. */
+
+#undef TMP_MAX
+#define TMP_MAX 16384
+
+int git_mkstemps_mode(char *pattern, int suffix_len, int mode)
{
- int ret = inflate(strm, flush);
- const char *err;
-
- switch (ret) {
- /* Out of memory is fatal. */
- case Z_MEM_ERROR:
- die("inflate: out of memory");
-
- /* Data corruption errors: we may want to recover from them (fsck) */
- case Z_NEED_DICT:
- err = "needs dictionary"; break;
- case Z_DATA_ERROR:
- err = "data stream error"; break;
- case Z_STREAM_ERROR:
- err = "stream consistency error"; break;
- default:
- err = "unknown error"; break;
-
- /* Z_BUF_ERROR: normal, needs more space in the output buffer */
- case Z_BUF_ERROR:
- case Z_OK:
- case Z_STREAM_END:
- return ret;
+ static const char letters[] =
+ "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "0123456789";
+ static const int num_letters = 62;
+ uint64_t value;
+ struct timeval tv;
+ char *template;
+ size_t len;
+ int fd, count;
+
+ len = strlen(pattern);
+
+ if (len < 6 + suffix_len) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (strncmp(&pattern[len - 6 - suffix_len], "XXXXXX", 6)) {
+ errno = EINVAL;
+ return -1;
}
- error("inflate: %s (%s)", err, strm->msg ? strm->msg : "no message");
- return ret;
-}
-int odb_mkstemp(char *template, size_t limit, const char *pattern)
-{
- int fd;
/*
- * we let the umask do its job, don't try to be more
- * restrictive except to remove write permission.
+ * Replace pattern's XXXXXX characters with randomness.
+ * Try TMP_MAX different filenames.
*/
- int mode = 0444;
- snprintf(template, limit, "%s/%s",
- get_object_directory(), pattern);
- fd = git_mkstemp_mode(template, mode);
- if (0 <= fd)
- return fd;
-
- /* slow path */
- /* some mkstemp implementations erase template on failure */
- snprintf(template, limit, "%s/%s",
- get_object_directory(), pattern);
- safe_create_leading_directories(template);
- return xmkstemp_mode(template, mode);
+ gettimeofday(&tv, NULL);
+ value = ((size_t)(tv.tv_usec << 16)) ^ tv.tv_sec ^ getpid();
+ template = &pattern[len - 6 - suffix_len];
+ for (count = 0; count < TMP_MAX; ++count) {
+ uint64_t v = value;
+ /* Fill in the random bits. */
+ template[0] = letters[v % num_letters]; v /= num_letters;
+ template[1] = letters[v % num_letters]; v /= num_letters;
+ template[2] = letters[v % num_letters]; v /= num_letters;
+ template[3] = letters[v % num_letters]; v /= num_letters;
+ template[4] = letters[v % num_letters]; v /= num_letters;
+ template[5] = letters[v % num_letters]; v /= num_letters;
+
+ fd = open(pattern, O_CREAT | O_EXCL | O_RDWR, mode);
+ if (fd > 0)
+ return fd;
+ /*
+ * Fatal error (EPERM, ENOSPC etc).
+ * It doesn't make sense to loop.
+ */
+ if (errno != EEXIST)
+ break;
+ /*
+ * This is a random value. It is only necessary that
+ * the next TMP_MAX values generated by adding 7777 to
+ * VALUE are different with (module 2^32).
+ */
+ value += 7777;
+ }
+ /* We return the null string if we can't find a unique file name. */
+ pattern[0] = '\0';
+ return -1;
+}
+
+int git_mkstemp_mode(char *pattern, int mode)
+{
+ /* mkstemp is just mkstemps with no suffix */
+ return git_mkstemps_mode(pattern, 0, mode);
}
-int odb_pack_keep(char *name, size_t namesz, unsigned char *sha1)
+int gitmkstemps(char *pattern, int suffix_len)
+{
+ return git_mkstemps_mode(pattern, suffix_len, 0600);
+}
+
+int xmkstemp_mode(char *template, int mode)
{
int fd;
+ char origtemplate[PATH_MAX];
+ strlcpy(origtemplate, template, sizeof(origtemplate));
- snprintf(name, namesz, "%s/pack/pack-%s.keep",
- get_object_directory(), sha1_to_hex(sha1));
- fd = open(name, O_RDWR|O_CREAT|O_EXCL, 0600);
- if (0 <= fd)
- return fd;
+ fd = git_mkstemp_mode(template, mode);
+ if (fd < 0) {
+ int saved_errno = errno;
+ const char *nonrelative_template;
+
+ if (!template[0])
+ template = origtemplate;
- /* slow path */
- safe_create_leading_directories(name);
- return open(name, O_RDWR|O_CREAT|O_EXCL, 0600);
+ nonrelative_template = make_nonrelative_path(template);
+ errno = saved_errno;
+ die_errno("Unable to create temporary file '%s'",
+ nonrelative_template);
+ }
+ return fd;
}
static int warn_if_unremovable(const char *op, const char *file, int rc)
index 7302f8f5a2cd450771aefcba0a385a90121ba0cf..9fb9b14760b100972a1adb5198bff9d6a9941808 100644 (file)
--- a/ws.c
+++ b/ws.c
rule |= whitespace_rule_names[i].rule_bits;
break;
}
+ if (strncmp(string, "tabwidth=", 9) == 0) {
+ unsigned tabwidth = atoi(string + 9);
+ if (0 < tabwidth && tabwidth < 0100) {
+ rule &= ~WS_TAB_WIDTH_MASK;
+ rule |= tabwidth;
+ }
+ else
+ warning("tabwidth %.*s out of range",
+ (int)(len - 9), string + 9);
+ }
string = ep;
}
value = attr_whitespace_rule.value;
if (ATTR_TRUE(value)) {
/* true (whitespace) */
- unsigned all_rule = 0;
+ unsigned all_rule = ws_tab_width(whitespace_rule_cfg);
int i;
for (i = 0; i < ARRAY_SIZE(whitespace_rule_names); i++)
if (!whitespace_rule_names[i].loosens_error &&
return all_rule;
} else if (ATTR_FALSE(value)) {
/* false (-whitespace) */
- return 0;
+ return ws_tab_width(whitespace_rule_cfg);
} else if (ATTR_UNSET(value)) {
/* reset to default (!whitespace) */
return whitespace_rule_cfg;
}
/* Check for indent using non-tab. */
- if ((ws_rule & WS_INDENT_WITH_NON_TAB) && i - written >= 8) {
+ if ((ws_rule & WS_INDENT_WITH_NON_TAB) && i - written >= ws_tab_width(ws_rule)) {
result |= WS_INDENT_WITH_NON_TAB;
if (stream) {
fputs(ws, stream);
@@ -320,7 +330,7 @@ void ws_fix_copy(struct strbuf *dst, const char *src, int len, unsigned ws_rule,
} else if (ch == ' ') {
last_space_in_indent = i;
if ((ws_rule & WS_INDENT_WITH_NON_TAB) &&
- 8 <= i - last_tab_in_indent)
+ ws_tab_width(ws_rule) <= i - last_tab_in_indent)
need_fix_leading_space = 1;
} else
break;
@@ -350,7 +360,7 @@ void ws_fix_copy(struct strbuf *dst, const char *src, int len, unsigned ws_rule,
strbuf_addch(dst, ch);
} else {
consecutive_spaces++;
- if (consecutive_spaces == 8) {
+ if (consecutive_spaces == ws_tab_width(ws_rule)) {
strbuf_addch(dst, '\t');
consecutive_spaces = 0;
}
@@ -363,12 +373,13 @@ void ws_fix_copy(struct strbuf *dst, const char *src, int len, unsigned ws_rule,
fixed = 1;
} else if ((ws_rule & WS_TAB_IN_INDENT) && last_tab_in_indent >= 0) {
/* Expand tabs into spaces */
+ int start = dst->len;
int last = last_tab_in_indent + 1;
for (i = 0; i < last; i++) {
if (src[i] == '\t')
do {
strbuf_addch(dst, ' ');
- } while (dst->len % 8);
+ } while ((dst->len - start) % ws_tab_width(ws_rule));
else
strbuf_addch(dst, src[i]);
}
diff --git a/wt-status.c b/wt-status.c
index 9624865e21739139a9fef5ebc31196cbcd22011a..a82b11d341c9dddb541cc72a5568769d9f538780 100644 (file)
--- a/wt-status.c
+++ b/wt-status.c
GIT_COLOR_RED, /* WT_STATUS_UNMERGED */
GIT_COLOR_GREEN, /* WT_STATUS_LOCAL_BRANCH */
GIT_COLOR_RED, /* WT_STATUS_REMOTE_BRANCH */
+ GIT_COLOR_NIL, /* WT_STATUS_ONBRANCH */
};
static const char *color(int slot, struct wt_status *s)
{
- return s->use_color > 0 ? s->color_palette[slot] : "";
+ const char *c = s->use_color > 0 ? s->color_palette[slot] : "";
+ if (slot == WT_STATUS_ONBRANCH && color_is_nil(c))
+ c = s->color_palette[WT_STATUS_HEADER];
+ return c;
}
void wt_status_prepare(struct wt_status *s)
{
const char *c = color(WT_STATUS_HEADER, s);
- color_fprintf_ln(s->fp, c, "# Changed but not updated:");
+ color_fprintf_ln(s->fp, c, "# Changes not staged for commit:");
if (!advice_status_hints)
return;
if (!has_deleted)
}
rev.diffopt.format_callback = wt_status_collect_changed_cb;
rev.diffopt.format_callback_data = s;
- rev.prune_data = s->pathspec;
+ init_pathspec(&rev.prune_data, s->pathspec);
run_diff_files(&rev, 0);
}
rev.diffopt.detect_rename = 1;
rev.diffopt.rename_limit = 200;
rev.diffopt.break_opt = 0;
- rev.prune_data = s->pathspec;
+ init_pathspec(&rev.prune_data, s->pathspec);
run_diff_index(&rev, 1);
}
static void wt_status_collect_changes_initial(struct wt_status *s)
{
+ struct pathspec pathspec;
int i;
+ init_pathspec(&pathspec, s->pathspec);
for (i = 0; i < active_nr; i++) {
struct string_list_item *it;
struct wt_status_change_data *d;
struct cache_entry *ce = active_cache[i];
- if (!ce_path_match(ce, s->pathspec))
+ if (!ce_path_match(ce, &pathspec))
continue;
it = string_list_insert(&s->change, ce->name);
d = it->util;
else
d->index_status = DIFF_STATUS_ADDED;
}
+ free_pathspec(&pathspec);
}
static void wt_status_collect_untracked(struct wt_status *s)
void wt_status_print(struct wt_status *s)
{
- const char *branch_color = color(WT_STATUS_HEADER, s);
+ const char *branch_color = color(WT_STATUS_ONBRANCH, s);
+ const char *branch_status_color = color(WT_STATUS_HEADER, s);
if (s->branch) {
const char *on_what = "On branch ";
branch_name += 11;
else if (!strcmp(branch_name, "HEAD")) {
branch_name = "";
- branch_color = color(WT_STATUS_NOBRANCH, s);
+ branch_status_color = color(WT_STATUS_NOBRANCH, s);
on_what = "Not currently on any branch.";
}
color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "# ");
- color_fprintf_ln(s->fp, branch_color, "%s%s", on_what, branch_name);
+ color_fprintf(s->fp, branch_status_color, "%s", on_what);
+ color_fprintf_ln(s->fp, branch_color, "%s", branch_name);
if (!s->is_initial)
wt_status_print_tracking(s);
}
diff --git a/wt-status.h b/wt-status.h
index 9df9c9fad2512d7c1d7d6456cdd645d641b5a089..20b17cf4393b8f9acce93320fb97998ad7cd609b 100644 (file)
--- a/wt-status.h
+++ b/wt-status.h
WT_STATUS_NOBRANCH,
WT_STATUS_UNMERGED,
WT_STATUS_LOCAL_BRANCH,
- WT_STATUS_REMOTE_BRANCH
+ WT_STATUS_REMOTE_BRANCH,
+ WT_STATUS_ONBRANCH,
+ WT_STATUS_MAXSLOT
};
enum untracked_status_type {
int show_ignored_files;
enum untracked_status_type show_untracked_files;
const char *ignore_submodule_arg;
- char color_palette[WT_STATUS_REMOTE_BRANCH+1][COLOR_MAXLEN];
+ char color_palette[WT_STATUS_MAXSLOT][COLOR_MAXLEN];
/* These are computed during processing of the individual sections */
int commitable;
diff --git a/zlib.c b/zlib.c
--- /dev/null
+++ b/zlib.c
@@ -0,0 +1,61 @@
+/*
+ * zlib wrappers to make sure we don't silently miss errors
+ * at init time.
+ */
+#include "cache.h"
+
+void git_inflate_init(z_streamp strm)
+{
+ const char *err;
+
+ switch (inflateInit(strm)) {
+ case Z_OK:
+ return;
+
+ case Z_MEM_ERROR:
+ err = "out of memory";
+ break;
+ case Z_VERSION_ERROR:
+ err = "wrong version";
+ break;
+ default:
+ err = "error";
+ }
+ die("inflateInit: %s (%s)", err, strm->msg ? strm->msg : "no message");
+}
+
+void git_inflate_end(z_streamp strm)
+{
+ if (inflateEnd(strm) != Z_OK)
+ error("inflateEnd: %s", strm->msg ? strm->msg : "failed");
+}
+
+int git_inflate(z_streamp strm, int flush)
+{
+ int ret = inflate(strm, flush);
+ const char *err;
+
+ switch (ret) {
+ /* Out of memory is fatal. */
+ case Z_MEM_ERROR:
+ die("inflate: out of memory");
+
+ /* Data corruption errors: we may want to recover from them (fsck) */
+ case Z_NEED_DICT:
+ err = "needs dictionary"; break;
+ case Z_DATA_ERROR:
+ err = "data stream error"; break;
+ case Z_STREAM_ERROR:
+ err = "stream consistency error"; break;
+ default:
+ err = "unknown error"; break;
+
+ /* Z_BUF_ERROR: normal, needs more space in the output buffer */
+ case Z_BUF_ERROR:
+ case Z_OK:
+ case Z_STREAM_END:
+ return ret;
+ }
+ error("inflate: %s (%s)", err, strm->msg ? strm->msg : "no message");
+ return ret;
+}