diff options
613 files changed, 22896 insertions, 5853 deletions
diff --git a/.gitignore b/.gitignore index 20560b810b..3dd6ef7d25 100644 --- a/.gitignore +++ b/.gitignore @@ -112,6 +112,8 @@ /git-remote-https /git-remote-ftp /git-remote-ftps +/git-remote-fd +/git-remote-ext /git-remote-testgit /git-repack /git-replace @@ -175,6 +177,7 @@ /test-sha1 /test-sigchain /test-string-pool +/test-subprocess /test-svn-fe /test-treap /common-cmds.h @@ -36,7 +36,7 @@ Lars Doelle <lars.doelle@on-line ! de> 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> diff --git a/Documentation/CodingGuidelines b/Documentation/CodingGuidelines index b8bf618a30..fe1c1e5bc2 100644 --- a/Documentation/CodingGuidelines +++ b/Documentation/CodingGuidelines @@ -31,25 +31,36 @@ But if you must have a list of rules, here they are. For shell scripts specifically (not exhaustive): + - We use tabs for indentation. + + - Case arms are indented at the same depth as case and esac lines. + - We prefer $( ... ) for command substitution; unlike ``, it properly nests. It should have been the way Bourne spelled it from day one, but unfortunately isn't. - - We use ${parameter-word} and its [-=?+] siblings, and their - colon'ed "unset or null" form. + - We use POSIX compliant parameter substitutions and avoid bashisms; + namely: - - We use ${parameter#word} and its [#%] siblings, and their - doubled "longest matching" form. + - We use ${parameter-word} and its [-=?+] siblings, and their + colon'ed "unset or null" form. - - We use Arithmetic Expansion $(( ... )). + - We use ${parameter#word} and its [#%] siblings, and their + doubled "longest matching" form. - - No "Substring Expansion" ${parameter:offset:length}. + - No "Substring Expansion" ${parameter:offset:length}. - - No shell arrays. + - No shell arrays. - - No strlen ${#parameter}. + - No strlen ${#parameter}. - - No regexp ${parameter/pattern/string}. + - No pattern replacement ${parameter/pattern/string}. + + - We use Arithmetic Expansion $(( ... )). + + - Inside Arithmetic Expansion, spell shell variables with $ in front + of them, as some shells do not grok $((x)) while accepting $(($x)) + just fine (e.g. dash older than 0.5.4). - We do not use Process Substitution <(list) or >(list). @@ -132,3 +143,55 @@ For C programs: - 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 e117bc4315..36989b7f65 100644 --- a/Documentation/Makefile +++ b/Documentation/Makefile @@ -63,35 +63,28 @@ endif # # 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.6.4.5.txt b/Documentation/RelNotes/1.6.4.5.txt new file mode 100644 index 0000000000..eb6307dcbb --- /dev/null +++ b/Documentation/RelNotes/1.6.4.5.txt @@ -0,0 +1,20 @@ +Git v1.6.4.5 Release Notes +========================== + +Fixes since v1.6.4.4 +-------------------- + + * Simplified base85 implementation. + + * An overlong line after ".gitdir: " in a git file caused out of bounds + access to an array on the stack. + + * "git count-objects" did not handle packs larger than 4G. + + * "git rev-parse --parseopt --stop-at-non-option" did not stop at non option + when --keep-dashdash was in effect. + + * "gitweb" can sometimes be tricked into parrotting a filename argument + given in a request without properly quoting. + +Other minor fixes and documentation updates are included. diff --git a/Documentation/RelNotes/1.6.5.9.txt b/Documentation/RelNotes/1.6.5.9.txt new file mode 100644 index 0000000000..bb469dd71e --- /dev/null +++ b/Documentation/RelNotes/1.6.5.9.txt @@ -0,0 +1,18 @@ +Git v1.6.5.9 Release Notes +========================== + +Fixes since v1.6.5.8 +-------------------- + + * An overlong line after ".gitdir: " in a git file caused out of bounds + access to an array on the stack. + + * "git blame -L $start,$end" segfaulted when too large $start was given. + + * "git rev-parse --parseopt --stop-at-non-option" did not stop at non option + when --keep-dashdash was in effect. + + * "gitweb" can sometimes be tricked into parrotting a filename argument + given in a request without properly quoting. + +Other minor fixes and documentation updates are included. diff --git a/Documentation/RelNotes/1.6.6.3.txt b/Documentation/RelNotes/1.6.6.3.txt new file mode 100644 index 0000000000..11483acaec --- /dev/null +++ b/Documentation/RelNotes/1.6.6.3.txt @@ -0,0 +1,23 @@ +Git v1.6.6.3 Release Notes +========================== + +Fixes since v1.6.6.2 +-------------------- + + * An overlong line after ".gitdir: " in a git file caused out of bounds + access to an array on the stack. + + * "git bisect $path" did not correctly diagnose an error when given a + non-existent path. + + * "git blame -L $start,$end" segfaulted when too large $start was given. + + * "git imap-send" did not write draft box with CRLF line endings per RFC. + + * "git rev-parse --parseopt --stop-at-non-option" did not stop at non option + when --keep-dashdash was in effect. + + * "gitweb" can sometimes be tricked into parrotting a filename argument + given in a request without properly quoting. + +Other minor fixes and documentation updates are included. diff --git a/Documentation/RelNotes/1.7.0.8.txt b/Documentation/RelNotes/1.7.0.8.txt new file mode 100644 index 0000000000..7f05b48e17 --- /dev/null +++ b/Documentation/RelNotes/1.7.0.8.txt @@ -0,0 +1,10 @@ +Git v1.7.0.8 Release Notes +========================== + +This is primarily to backport support for the new "add.ignoreErrors" +name given to the existing "add.ignore-errors" configuration variable. + +The next version, Git 1.7.4, and future versions, will support both +old and incorrect name and the new corrected name, but without this +backport, users who want to use the new name "add.ignoreErrors" in +their repositories cannot use older versions of Git. diff --git a/Documentation/RelNotes/1.7.0.9.txt b/Documentation/RelNotes/1.7.0.9.txt new file mode 100644 index 0000000000..bfb3166387 --- /dev/null +++ b/Documentation/RelNotes/1.7.0.9.txt @@ -0,0 +1,8 @@ +Git v1.7.0.9 Release Notes +========================== + +Fixes since v1.7.0.8 +-------------------- + + * "gitweb" can sometimes be tricked into parrotting a filename argument + given in a request without properly quoting. diff --git a/Documentation/RelNotes/1.7.1.3.txt b/Documentation/RelNotes/1.7.1.3.txt new file mode 100644 index 0000000000..5b18518449 --- /dev/null +++ b/Documentation/RelNotes/1.7.1.3.txt @@ -0,0 +1,10 @@ +Git v1.7.1.3 Release Notes +========================== + +This is primarily to backport support for the new "add.ignoreErrors" +name given to the existing "add.ignore-errors" configuration variable. + +The next version, Git 1.7.4, and future versions, will support both +old and incorrect name and the new corrected name, but without this +backport, users who want to use the new name "add.ignoreErrors" in +their repositories cannot use older versions of Git. diff --git a/Documentation/RelNotes/1.7.1.4.txt b/Documentation/RelNotes/1.7.1.4.txt new file mode 100644 index 0000000000..7c734b4f7b --- /dev/null +++ b/Documentation/RelNotes/1.7.1.4.txt @@ -0,0 +1,8 @@ +Git v1.7.1.4 Release Notes +========================== + +Fixes since v1.7.1.3 +-------------------- + + * "gitweb" can sometimes be tricked into parrotting a filename argument + given in a request without properly quoting. diff --git a/Documentation/RelNotes/1.7.2.4.txt b/Documentation/RelNotes/1.7.2.4.txt new file mode 100644 index 0000000000..f7950a4c04 --- /dev/null +++ b/Documentation/RelNotes/1.7.2.4.txt @@ -0,0 +1,10 @@ +Git v1.7.2.4 Release Notes +========================== + +This is primarily to backport support for the new "add.ignoreErrors" +name given to the existing "add.ignore-errors" configuration variable. + +The next version, Git 1.7.4, and future versions, will support both +old and incorrect name and the new corrected name, but without this +backport, users who want to use the new name "add.ignoreErrors" in +their repositories cannot use older versions of Git. diff --git a/Documentation/RelNotes/1.7.2.5.txt b/Documentation/RelNotes/1.7.2.5.txt new file mode 100644 index 0000000000..bf976c40db --- /dev/null +++ b/Documentation/RelNotes/1.7.2.5.txt @@ -0,0 +1,8 @@ +Git v1.7.2.5 Release Notes +========================== + +Fixes since v1.7.2.4 +-------------------- + + * "gitweb" can sometimes be tricked into parrotting a filename argument + given in a request without properly quoting. diff --git a/Documentation/RelNotes/1.7.3.1.txt b/Documentation/RelNotes/1.7.3.1.txt new file mode 100644 index 0000000000..002c93b961 --- /dev/null +++ b/Documentation/RelNotes/1.7.3.1.txt @@ -0,0 +1,14 @@ +Git v1.7.3.1 Release Notes +========================== + +Fixes since v1.7.3 +------------------ + + * "git stash show stash@{$n}" was accidentally broken in 1.7.3 ("git + stash show" without any argument still worked, though). + + * "git stash branch $branch stash@{$n}" was accidentally broken in + 1.7.3 and started dropping the named stash even when branch creation + failed. + +And other minor fixes and documentation updates. diff --git a/Documentation/RelNotes/1.7.3.2.txt b/Documentation/RelNotes/1.7.3.2.txt new file mode 100644 index 0000000000..5c93b85af4 --- /dev/null +++ b/Documentation/RelNotes/1.7.3.2.txt @@ -0,0 +1,5 @@ +Git v1.7.3.2 Release Notes +========================== + +This is primarily to push out many documentation fixes accumulated since +the 1.7.3.1 release. diff --git a/Documentation/RelNotes/1.7.3.3.txt b/Documentation/RelNotes/1.7.3.3.txt new file mode 100644 index 0000000000..9b2b2448df --- /dev/null +++ b/Documentation/RelNotes/1.7.3.3.txt @@ -0,0 +1,54 @@ +Git v1.7.3.3 Release Notes +========================== + +In addition to the usual fixes, this release also includes support for +the new "add.ignoreErrors" name given to the existing "add.ignore-errors" +configuration variable. + +The next version, Git 1.7.4, and future versions, will support both +old and incorrect name and the new corrected name, but without this +backport, users who want to use the new name "add.ignoreErrors" in +their repositories cannot use older versions of Git. + +Fixes since v1.7.3.2 +-------------------- + + * "git apply" segfaulted when a bogus input is fed to it. + + * Running "git cherry-pick --ff" on a root commit segfaulted. + + * "diff", "blame" and friends incorrectly applied textconv filters to + symlinks. + + * Highlighting of whitespace breakage in "diff" output was showing + incorrect amount of whitespaces when blank-at-eol is set and the line + consisted only of whitespaces and a TAB. + + * "diff" was overly inefficient when trying to find the line to use for + the function header (i.e. equivalent to --show-c-function of GNU diff). + + * "git imap-send" depends on libcrypto but our build rule relied on the + linker to implicitly link it via libssl, which was wrong. + + * "git merge-file" can be called from within a subdirectory now. + + * "git repack -f" expanded and recompressed non-delta objects in the + existing pack, which was wasteful. Use new "-F" option if you really + want to (e.g. when changing the pack.compression level). + + * "git rev-list --format="...%x00..." incorrectly chopped its output + at NUL. + + * "git send-email" did not correctly remove duplicate mail addresses from + the Cc: header that appear on the To: header. + + * The completion script (in contrib/completion) ignored lightweight tags + in __git_ps1(). + + * "git-blame" mode (in contrib/emacs) didn't say (require 'format-spec) + even though it depends on it; it didn't work with Emacs 22 or older + unless Gnus is used. + + * "git-p4" (in contrib/) did not correctly handle deleted files. + +Other minor fixes and documentation updates are also included. diff --git a/Documentation/RelNotes/1.7.3.4.txt b/Documentation/RelNotes/1.7.3.4.txt new file mode 100644 index 0000000000..e57f7c176d --- /dev/null +++ b/Documentation/RelNotes/1.7.3.4.txt @@ -0,0 +1,45 @@ +Git v1.7.3.4 Release Notes +========================== + +Fixes since v1.7.3.3 +-------------------- + + * Smart HTTP transport used to incorrectly retry redirected POST + request with GET request. + + * "git apply" did not correctly handle patches that only change modes + if told to apply while stripping leading paths with -p option. + + * "git apply" can deal with patches with timezone formatted with a + colon between the hours and minutes part (e.g. "-08:00" instead of + "-0800"). + + * "git checkout" removed an untracked file "foo" from the working + tree when switching to a branch that contains a tracked path + "foo/bar". Prevent this, just like the case where the conflicting + path were "foo" (c752e7f..7980872d). + + * "git cherry-pick" or "git revert" refused to work when a path that + would be modified by the operation was stat-dirty without a real + difference in the contents of the file. + + * "git diff --check" reported an incorrect line number for added + blank lines at the end of file. + + * "git imap-send" failed to build under NO_OPENSSL. + + * Setting log.decorate configuration variable to "0" or "1" to mean + "false" or "true" did not work. + + * "git push" over dumb HTTP protocol did not work against WebDAV + servers that did not terminate a collection name with a slash. + + * "git tag -v" did not work with GPG signatures in rfc1991 mode. + + * The post-receive-email sample hook was accidentally broken in 1.7.3.3 + update. + + * "gitweb" can sometimes be tricked into parrotting a filename argument + given in a request without properly quoting. + +Other minor fixes and documentation updates are also included. diff --git a/Documentation/RelNotes/1.7.3.5.txt b/Documentation/RelNotes/1.7.3.5.txt new file mode 100644 index 0000000000..40f3ba5795 --- /dev/null +++ b/Documentation/RelNotes/1.7.3.5.txt @@ -0,0 +1,34 @@ +Git 1.7.3.5 Release Notes +========================= + + * The xfuncname pattern used by "git diff" and "git grep" to show the + last notable line in context were broken for python and ruby for a long + time. + + * "git merge" into an unborn branch removed an untracked file "foo" from + the working tree when merged branch had "foo" (this fix was already in + 1.7.3.3 but was omitted from the release notes by mistake). + + * "git status -s" did not quote unprintable characters in paths as + documented. + + * "git am --abort" used to always reset to the commit at the beginning of + the last "am" invocation that has stopped, losing any unrelated commits + that may have been made since then. Now it refrains from doing so and + instead issues a warning. + + * "git blame" incorrectly reused bogusly cached result of textconv + filter for files from the working tree. + + * "git commit" used to abort after the user edited the log message + when the committer information was not correctly set up. It now + aborts before starting the editor. + + * "git commit --date=invalid" used to silently ignore the incorrectly + specified date; it is now diagnosed as an error. + + * "git rebase --skip" to skip the last commit in a series used to fail + to run post-rewrite hook and to copy notes from old commits that have + successfully been rebased so far. Now it do (backmerge ef88ad2). + + * "gitweb" tried to show a wrong feed logo when none was specified. diff --git a/Documentation/RelNotes/1.7.4.1.txt b/Documentation/RelNotes/1.7.4.1.txt new file mode 100644 index 0000000000..79923a6d2f --- /dev/null +++ b/Documentation/RelNotes/1.7.4.1.txt @@ -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.2.txt b/Documentation/RelNotes/1.7.4.2.txt new file mode 100644 index 0000000000..ef4ce1fcd3 --- /dev/null +++ b/Documentation/RelNotes/1.7.4.2.txt @@ -0,0 +1,58 @@ +Git v1.7.4.2 Release Notes +========================== + +Fixes since v1.7.4.1 +-------------------- + + * Many documentation updates to match "git cmd -h" output and the + git-cmd manual page. + + * We used to keep one file descriptor open for each and every packfile + that we have a mmap window on it (read: "in use"), even when for very + tiny packfiles. We now close the file descriptor early when the entire + packfile fits inside one mmap window. + + * "git bisect visualize" tried to run "gitk" in windowing + environments even when "gitk" is not installed, resulting in a + strange error message. + + * "git clone /no/such/path" did not fail correctly. + + * "git commit" did not correctly error out when the user asked to use a + non existent file as the commit message template. + + * "git diff --stat -B" ran on binary files counted the changes in lines, + which was nonsensical. + + * "git diff -M" opportunistically detected copies, which was not + necessarily a good thing, especially when it is internally run by + recursive merge. + + * "git difftool" didn't tell (g)vimdiff that the files it is reading are + to be opened read-only. + + * "git merge" didn't pay attention to prepare-commit-msg hook, even + though if a merge is conflicted and manually resolved, the subsequent + "git commit" would have triggered the hook, which was inconsistent. + + * "git patch-id" (and commands like "format-patch --ignore-in-upstream" + that use it as their internal logic) handled changes to files that end + with incomplete lines incorrectly. + + * The official value to tell "git push" to push the current branch back + to update the upstream branch it forked from is now called "upstream". + The old name "tracking" is and will be supported. + + * "git submodule update" used to honor the --merge/--rebase option (or + corresponding configuration variables) even for a newly cloned + subproject, which made no sense (so/submodule-no-update-first-time). + + * gitweb's "highlight" interface mishandled tabs. + + * gitweb didn't understand timezones with GMT offset that is not + multiple of a whole hour. + + * gitweb had a few forward-incompatible syntactic constructs and + also used incorrect variable when showing the file mode in a diff. + +And other minor fixes and documentation updates. diff --git a/Documentation/RelNotes/1.7.4.3.txt b/Documentation/RelNotes/1.7.4.3.txt new file mode 100644 index 0000000000..02a3d5bdf6 --- /dev/null +++ b/Documentation/RelNotes/1.7.4.3.txt @@ -0,0 +1,32 @@ +Git v1.7.4.3 Release Notes +========================== + +Fixes since v1.7.4.2 +-------------------- + + * "git apply" used to confuse lines updated by previous hunks as lines + that existed before when applying a hunk, contributing misapplication + of patches with offsets. + + * "git branch --track" (and "git checkout --track --branch") used to + allow setting up a random non-branch that does not make sense to follow + as the "upstream". The command correctly diagnoses it as an error. + + * "git checkout $other_branch" silently removed untracked symbolic links + in the working tree that are in the way in order to check out paths + under it from the named branch. + + * "git cvsimport" did not bail out immediately when the cvs server cannot + be reached, spewing unnecessary error messages that complain about the + server response that it never got. + + * "git diff --quiet" did not work very well with the "--diff-filter" + option. + + * "git grep -n" lacked a long-hand synonym --line-number. + + * "git stash apply" reported the result of its operation by running + "git status" from the top-level of the working tree; it should (and + now does) run it from the user's working directory. + +And other minor fixes and documentation updates. diff --git a/Documentation/RelNotes/1.7.4.txt b/Documentation/RelNotes/1.7.4.txt new file mode 100644 index 0000000000..d5bca731b5 --- /dev/null +++ b/Documentation/RelNotes/1.7.4.txt @@ -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/SubmittingPatches b/Documentation/SubmittingPatches index ece3c77482..c3b0816ed7 100644 --- a/Documentation/SubmittingPatches +++ b/Documentation/SubmittingPatches @@ -10,10 +10,18 @@ Checklist (and a short version for the impatient): description (50 characters is the soft limit, see DISCUSSION in git-commit(1)), and should skip the full stop - the body should provide a meaningful commit message, which: - - uses the imperative, present tense: "change", - not "changed" or "changes". - - includes motivation for the change, and contrasts - its implementation with previous behaviour + . explains the problem the change tries to solve, iow, what + is wrong with the current code without the change. + . justifies the way the change solves the problem, iow, why + the result with the change is better. + . alternate solutions considered but discarded, if any. + - describe changes in imperative mood, e.g. "make xyzzy do frotz" + instead of "[This patch] makes xyzzy do frotz" or "[I] changed + xyzzy to do frotz", as if you are giving orders to the codebase + to change its behaviour. + - try to make sure your explanation can be understood without + external resources. Instead of giving a URL to a mailing list + archive, summarize the relevant points of the discussion. - add a "Signed-off-by: Your Name <you@example.com>" line to the commit message (or just use the option "-s" when committing) to confirm that you agree to the Developer's Certificate of Origin @@ -90,7 +98,10 @@ your commit head. Instead, always make a commit with complete commit message and generate a series of patches from your repository. It is a good discipline. -Describe the technical detail of the change(s). +Give an explanation for the change(s) that is detailed enough so +that people can judge if it is good thing to do, without reading +the actual patch text to determine how well the code does what +the explanation promises to do. If your description starts to get too long, that's a sign that you probably need to split up your commit to finer grained pieces. @@ -99,9 +110,8 @@ help reviewers check the patch, and future maintainers understand the code, are the most beautiful patches. Descriptions that summarise the point in the subject well, and describe the motivation for the change, the approach taken by the change, and if relevant how this -differs substantially from the prior version, can be found on Usenet -archives back into the late 80's. Consider it like good Netiquette, -but for code. +differs substantially from the prior version, are all good things +to have. Oh, another thing. I am picky about whitespaces. Make sure your changes do not trigger errors with the sample pre-commit hook shipped @@ -264,12 +274,21 @@ the change to its true author (see (2) above). Also notice that a real name is used in the Signed-off-by: line. Please don't hide your real name. -Some people also put extra tags at the end. - -"Acked-by:" says that the patch was reviewed by the person who -is more familiar with the issues and the area the patch attempts -to modify. "Tested-by:" says the patch was tested by the person -and found to have the desired effect. +If you like, you can put extra tags at the end: + +1. "Reported-by:" is used to to credit someone who found the bug that + the patch attempts to fix. +2. "Acked-by:" says that the person who is more familiar with the area + the patch attempts to modify liked the patch. +3. "Reviewed-by:", unlike the other tags, can only be offered by the + reviewer and means that she is completely satisfied that the patch + is ready for application. It is usually offered only after a + detailed review. +4. "Tested-by:" is used to indicate that the person applied the patch + and found it to have the desired effect. + +You can also create your own tag or use one that's in common usage +such as "Thanks-to:", "Based-on-patch-by:", or "Mentored-by:". ------------------------------------------------ An ideal patch flow diff --git a/Documentation/config.txt b/Documentation/config.txt index d82c0da2cf..52ffbf4efb 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -62,7 +62,7 @@ Internal whitespace within a variable value is retained verbatim. The values following the equals sign in variable assign are all either a string, an integer, or a boolean. Boolean values may be given as yes/no, -0/1, true/false or on/off. Case is not significant in boolean values, when +1/0, true/false or on/off. Case is not significant in boolean values, when converting value to the canonical form using '--bool' type specifier; 'git config' will ensure that the output is "true" or "false". @@ -317,24 +317,26 @@ false), while all other repositories are assumed to be bare (bare = 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 @@ -459,6 +461,12 @@ core.askpass:: 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 @@ -507,6 +515,9 @@ core.whitespace:: 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. @@ -547,10 +558,20 @@ core.sparseCheckout:: Enable "sparse checkout" feature. See section "Sparse checkout" in linkgit:git-read-tree[1] for more information. +core.abbrev:: + Set the length object names are abbreviated to. If unspecified, + many commands abbreviate to 7 hexdigits, which may not be enough + for abbreviated object names to stay unique for sufficiently long + time. + add.ignore-errors:: +add.ignoreErrors:: Tells 'git add' to continue adding files when some files cannot be added due to indexing errors. Equivalent to the '--ignore-errors' - option of linkgit:git-add[1]. + option of linkgit:git-add[1]. Older versions of git accept only + `add.ignore-errors`, which does not follow the usual naming + convention for configuration variables. Newer versions of git + honor `add.ignoreErrors` as well. alias.*:: Command aliases for the linkgit:git[1] command wrapper - e.g. @@ -595,8 +616,9 @@ branch.autosetupmerge:: 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:: @@ -607,7 +629,7 @@ 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 @@ -674,7 +696,7 @@ color.branch:: 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 @@ -702,7 +724,7 @@ color.diff.<slot>:: 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 @@ -767,7 +789,8 @@ color.status.<slot>:: 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>. @@ -873,6 +896,11 @@ diff.wordRegex:: 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 @@ -967,7 +995,7 @@ gc.packrefs:: Running `git pack-refs` in a repository renders it unclonable by Git versions prior to 1.5.1.2 over dumb transports such as HTTP. This variable determines whether - 'git gc' runs `git pack-refs`. This can be set to `nobare` + 'git gc' runs `git pack-refs`. This can be set to `notbare` to enable it within all non-bare repos or it can be set to a boolean value. The default is `true`. @@ -1096,7 +1124,7 @@ gui.newbranchtemplate:: 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:: @@ -1466,6 +1494,10 @@ pack.compression:: not set, defaults to -1, the zlib default, which is "a default compromise between speed and compression (currently equivalent to level 6)." ++ +Note that changing the compression level will not automatically recompress +all existing objects. You can force recompression by passing the -F option +to linkgit:git-repack[1]. pack.deltaCacheSize:: The maximum memory in bytes used for caching deltas in @@ -1521,11 +1553,13 @@ pack.packSizeLimit:: 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 @@ -1550,12 +1584,13 @@ push.default:: no refspec is implied by any of the options given on the command line. Possible values are: + -* `nothing` do not push anything. -* `matching` push all matching branches. +* `nothing` - do not push anything. +* `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. -* `current` push the current branch to a branch of the same name. +* `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:: Whether to show a diffstat of what changed upstream since the last @@ -1727,6 +1762,7 @@ sendemail.to:: sendemail.smtpdomain:: sendemail.smtpserver:: sendemail.smtpserverport:: +sendemail.smtpserveroption:: sendemail.smtpuser:: sendemail.thread:: sendemail.validate:: @@ -1755,9 +1791,9 @@ status.showUntrackedFiles:: the untracked files. Possible values are: + -- - - 'no' - Show no untracked files - - 'normal' - Shows untracked files and directories - - 'all' - Shows also individual files in untracked directories. +* `no` - Show no untracked files. +* `normal` - Show untracked files and directories. +* `all` - Show also individual files in untracked directories. -- + If this variable is not specified, it defaults to 'normal'. @@ -1780,6 +1816,13 @@ submodule.<name>.update:: 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 diff --git a/Documentation/diff-generate-patch.txt b/Documentation/diff-generate-patch.txt index 8f9a2412fd..c57460c03d 100644 --- a/Documentation/diff-generate-patch.txt +++ b/Documentation/diff-generate-patch.txt @@ -9,16 +9,15 @@ patch file. You can customize the creation of such patches via the GIT_EXTERNAL_DIFF and the GIT_DIFF_OPTS environment variables. What the -p option produces is slightly different from the traditional -diff format. +diff format: -1. It is preceded with a "git diff" header, that looks like - this: +1. It is preceded with a "git diff" header that looks like this: diff --git a/file1 b/file2 + The `a/` and `b/` filenames are the same unless rename/copy is involved. Especially, even for a creation or a deletion, -`/dev/null` is _not_ used in place of `a/` or `b/` filenames. +`/dev/null` is _not_ used in place of the `a/` or `b/` filenames. + When rename/copy is involved, `file1` and `file2` show the name of the source file of the rename/copy and the name of @@ -37,27 +36,51 @@ the file that rename/copy produces, respectively. similarity index <number> dissimilarity index <number> index <hash>..<hash> <mode> - -3. TAB, LF, double quote and backslash characters in pathnames - are represented as `\t`, `\n`, `\"` and `\\`, respectively. - If there is need for such substitution then the whole - pathname is put in double quotes. - ++ +File modes are printed as 6-digit octal numbers including the file type +and file permission bits. ++ +Path names in extended headers do not include the `a/` and `b/` prefixes. ++ The similarity index is the percentage of unchanged lines, and the dissimilarity index is the percentage of changed lines. It is a rounded down integer, followed by a percent sign. The similarity index value of 100% is thus reserved for two equal files, while 100% dissimilarity means that no line from the old file made it into the new one. ++ +The index line includes the SHA-1 checksum before and after the change. +The <mode> is included if the file mode does not change; otherwise, +separate lines indicate the old and the new mode. + +3. TAB, LF, double quote and backslash characters in pathnames + are represented as `\t`, `\n`, `\"` and `\\`, respectively. + If there is need for such substitution then the whole + pathname is put in double quotes. + +4. All the `file1` files in the output refer to files before the + commit, and all the `file2` files refer to files after the commit. + It is incorrect to apply each change to each file sequentially. For + example, this patch will swap a and b: + + diff --git a/a b/b + rename from a + rename to b + diff --git a/b b/a + rename from b + rename to a combined diff format -------------------- -"git-diff-tree", "git-diff-files" and "git-diff" can take '-c' or -'--cc' option to produce 'combined diff'. For showing a merge commit -with "git log -p", this is the default format; you can force showing -full diff with the '-m' option. +Any diff-generating command can take the `-c` or `--cc` option to +produce a 'combined diff' when showing a merge. This is the default +format when showing merges with linkgit:git-diff[1] or +linkgit:git-show[1]. Note also that you can give the `-m' option to any +of these commands to force generation of diffs with individual parents +of a merge. + A 'combined diff' format looks like this: ------------ diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index 4656a97e60..c93124be79 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -48,9 +48,9 @@ endif::git-format-patch[] --patience:: Generate a diff using the "patience diff" algorithm. ---stat[=width[,name-width]]:: +--stat[=<width>[,<name-width>]]:: Generate a diffstat. You can override the default - output width for 80-column terminal by `--stat=width`. + output width for 80-column terminal by `--stat=<width>`. The width of the filename part can be controlled by giving another width to it separated by a comma. @@ -66,14 +66,14 @@ endif::git-format-patch[] number of modified files, as well as number of added and deleted lines. ---dirstat[=limit]:: +--dirstat[=<limit>]:: Output the distribution of relative amount of changes (number of lines added or removed) for each sub-directory. Directories with changes below a cut-off percent (3% by default) are not shown. The cut-off percent - can be set with `--dirstat=limit`. Changes in a child directory is not + can be set with `--dirstat=<limit>`. Changes in a child directory are not counted for the parent directory, unless `--cumulative` is used. ---dirstat-by-file[=limit]:: +--dirstat-by-file[=<limit>]:: Same as `--dirstat`, but counts changed files instead of lines. --summary:: @@ -207,6 +207,7 @@ endif::git-format-patch[] 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: + @@ -229,6 +230,7 @@ eligible for being picked up as a possible source of a rename to another file. -M[<n>]:: +--find-renames[=<n>]:: ifndef::git-log[] Detect renames. endif::git-log[] @@ -244,23 +246,10 @@ 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>`. -ifndef::git-format-patch[] ---diff-filter=[ACDMRTUXB*]:: - Select only files that are Added (`A`), Copied (`C`), - Deleted (`D`), Modified (`M`), Renamed (`R`), have their - type (i.e. regular file, symlink, submodule, ...) changed (`T`), - are Unmerged (`U`), are - Unknown (`X`), or have had their pairing Broken (`B`). - Any combination of the filter characters may be used. - When `*` (All-or-none) is added to the combination, all - paths are selected if there is any file that matches - other criteria in the comparison; if there is no file - that matches other criteria, nothing is selected. -endif::git-format-patch[] - --find-copies-harder:: For performance reasons, by default, `-C` option finds copies only if the original file of the copy was modified in the same @@ -278,14 +267,30 @@ endif::git-format-patch[] number. ifndef::git-format-patch[] +--diff-filter=[(A|C|D|M|R|T|U|X|B)...[*]]:: + Select only files that are Added (`A`), Copied (`C`), + Deleted (`D`), Modified (`M`), Renamed (`R`), have their + type (i.e. regular file, symlink, submodule, ...) changed (`T`), + are Unmerged (`U`), are + Unknown (`X`), or have had their pairing Broken (`B`). + Any combination of the filter characters (including none) can be used. + When `*` (All-or-none) is added to the combination, all + paths are selected if there is any file that matches + other criteria in the comparison; if there is no file + that matches other criteria, nothing is selected. + -S<string>:: Look for differences that introduce or remove an instance of <string>. Note that this is different than the string simply 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>. diff --git a/Documentation/everyday.txt b/Documentation/everyday.txt index e0ba8cc075..ae413e52a5 100644 --- a/Documentation/everyday.txt +++ b/Documentation/everyday.txt @@ -180,12 +180,12 @@ directory; clone from it to start a repository on the satellite 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. diff --git a/Documentation/fetch-options.txt b/Documentation/fetch-options.txt index 470ac31396..f37276e5ad 100644 --- a/Documentation/fetch-options.txt +++ b/Documentation/fetch-options.txt @@ -36,7 +36,7 @@ ifndef::git-pull[] -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[] @@ -53,6 +53,7 @@ endif::git-pull[] behavior for a remote may be specified with the remote.<name>.tagopt setting. See linkgit:git-config[1]. +ifndef::git-pull[] -t:: --tags:: Most of the tags are fetched automatically as branch @@ -64,6 +65,16 @@ endif::git-pull[] 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:: --update-head-ok:: By default 'git fetch' refuses to update the head which diff --git a/Documentation/git-add.txt b/Documentation/git-add.txt index 73378b2bef..a03448f923 100644 --- a/Documentation/git-add.txt +++ b/Documentation/git-add.txt @@ -92,9 +92,11 @@ See ``Interactive mode'' for details. edit it. After the editor was closed, adjust the hunk headers and apply the patch to the index. + -*NOTE*: Obviously, if you change anything else than the first character -on lines beginning with a space or a minus, the patch will no longer -apply. +The intent of this option is to pick and choose lines of the patch to +apply, or even to modify the contents of lines to be staged. This can be +quicker and more flexible than using the interactive hunk selector. +However, it is easy to confuse oneself and create a patch that does not +apply to the index. See EDITING PATCHES below. -u:: --update:: @@ -295,6 +297,78 @@ diff:: This lets you review what will be committed (i.e. between HEAD and index). + +EDITING PATCHES +--------------- + +Invoking `git add -e` or selecting `e` from the interactive hunk +selector will open a patch in your editor; after the editor exits, the +result is applied to the index. You are free to make arbitrary changes +to the patch, but note that some changes may have confusing results, or +even result in a patch that cannot be applied. If you want to abort the +operation entirely (i.e., stage nothing new in the index), simply delete +all lines of the patch. The list below describes some common things you +may see in a patch, and which editing operations make sense on them. + +-- +added content:: + +Added content is represented by lines beginning with "{plus}". You can +prevent staging any addition lines by deleting them. + +removed content:: + +Removed content is represented by lines beginning with "-". You can +prevent staging their removal by converting the "-" to a " " (space). + +modified content:: + +Modified content is represented by "-" lines (removing the old content) +followed by "{plus}" lines (adding the replacement content). You can +prevent staging the modification by converting "-" lines to " ", and +removing "{plus}" lines. Beware that modifying only half of the pair is +likely to introduce confusing changes to the index. +-- + +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 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. + +Avoid using these constructs, or do so with extreme caution. + +-- +removing untouched content:: + +Content which does not differ between the index and working tree may be +shown on context lines, beginning with a " " (space). You can stage +context lines for removal by converting the space to a "-". The +resulting working tree file will appear to re-add the content. + +modifying existing content:: + +One can also modify context lines by staging them for removal (by +converting " " to "-") and adding a "{plus}" line with the new content. +Similarly, one can modify "{plus}" lines for existing additions or +modifications. In all cases, the new modification will appear reverted +in the working tree. + +new content:: + +You may also add new content that does not exist in the patch; simply +add new lines, each starting with "{plus}". The addition will appear +reverted in the working tree. +-- + +There are also several operations which should be avoided entirely, as +they will make the patch impossible to apply: + +* adding context (" ") or removal ("-") lines +* deleting context or removal lines +* modifying the contents of context or removal lines + SEE ALSO -------- linkgit:git-status[1] diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt index 9e62f8778f..4d37de639d 100644 --- a/Documentation/git-am.txt +++ b/Documentation/git-am.txt @@ -14,7 +14,7 @@ SYNOPSIS [--ignore-date] [--ignore-space-change | --ignore-whitespace] [--whitespace=<option>] [-C<n>] [-p<n>] [--directory=<dir>] [--reject] [-q | --quiet] [--scissors | --no-scissors] - [<mbox> | <Maildir>...] + [(<mbox> | <Maildir>)...] 'git am' (--continue | --skip | --abort) DESCRIPTION @@ -25,7 +25,7 @@ current branch. OPTIONS ------- -<mbox>|<Maildir>...:: +(<mbox>|<Maildir>)...:: The list of mailbox files to read patches from. If you do not supply this argument, the command reads from the standard input. If you supply directories, they will be treated as Maildirs. @@ -173,9 +173,9 @@ aborts in the middle. You can recover from this in one of two ways: the index file to bring it into a state that the patch should have produced. Then run the command with the '--resolved' option. -The command refuses to process new mailboxes while the `.git/rebase-apply` -directory exists, so if you decide to start over from scratch, -run `rm -f -r .git/rebase-apply` before running the command with mailbox +The command refuses to process new mailboxes until the current +operation is finished, so if you decide to start over from scratch, +run `git am --abort` before running the command with mailbox names. Before any patches are applied, ORIG_HEAD is set to the tip of the diff --git a/Documentation/git-apply.txt b/Documentation/git-apply.txt index 4a74b23d40..2dcfc097d3 100644 --- a/Documentation/git-apply.txt +++ b/Documentation/git-apply.txt @@ -12,17 +12,17 @@ SYNOPSIS 'git apply' [--stat] [--numstat] [--summary] [--check] [--index] [--apply] [--no-add] [--build-fake-ancestor=<file>] [-R | --reverse] [--allow-binary-replacement | --binary] [--reject] [-z] - [-pNUM] [-CNUM] [--inaccurate-eof] [--recount] [--cached] + [-p<n>] [-C<n>] [--inaccurate-eof] [--recount] [--cached] [--ignore-space-change | --ignore-whitespace ] - [--whitespace=<nowarn|warn|fix|error|error-all>] - [--exclude=PATH] [--include=PATH] [--directory=<root>] + [--whitespace=(nowarn|warn|fix|error|error-all)] + [--exclude=<path>] [--include=<path>] [--directory=<root>] [--verbose] [<patch>...] DESCRIPTION ----------- Reads the supplied diff output (i.e. "a patch") and applies it to files. With the `--index` option the patch is also applied to the index, and -with the `--cache` option the patch is only applied to the index. +with the `--cached` option the patch is only applied to the index. Without these options, the command applies the patch only to files, and does not require them to be in a git repository. diff --git a/Documentation/git-archimport.txt b/Documentation/git-archimport.txt index 4f358c8d6c..2411ce5bfe 100644 --- a/Documentation/git-archimport.txt +++ b/Documentation/git-archimport.txt @@ -109,7 +109,7 @@ OPTIONS Author ------ -Written by Martin Langhoff <martin@catalyst.net.nz>. +Written by Martin Langhoff <martin@laptop.org>. Documentation -------------- diff --git a/Documentation/git-archive.txt b/Documentation/git-archive.txt index 8d3e66626f..bf5037ab2a 100644 --- a/Documentation/git-archive.txt +++ b/Documentation/git-archive.txt @@ -12,7 +12,7 @@ SYNOPSIS 'git archive' [--format=<fmt>] [--list] [--prefix=<prefix>/] [<extra>] [-o | --output=<file>] [--worktree-attributes] [--remote=<repo> [--exec=<git-upload-archive>]] <tree-ish> - [path...] + [<path>...] DESCRIPTION ----------- @@ -73,7 +73,7 @@ OPTIONS <tree-ish>:: The tree or commit to produce an archive for. -path:: +<path>:: Without an optional path parameter, all files and subdirectories of the current working directory are included in the archive. If one or more paths are specified, only these are included. @@ -116,7 +116,7 @@ Note that attributes are by default taken from the `.gitattributes` files in the tree that is being archived. If you want to tweak the way the output is generated after the fact (e.g. you committed without adding an appropriate export-ignore in its `.gitattributes`), adjust the checked out -`.gitattributes` file as necessary and use `--work-tree-attributes` +`.gitattributes` file as necessary and use `--worktree-attributes` option. Alternatively you can keep necessary attributes that should apply while archiving any tree in your `$GIT_DIR/info/attributes` file. diff --git a/Documentation/git-bisect.txt b/Documentation/git-bisect.txt index c39d957c3a..c443e0f307 100644 --- a/Documentation/git-bisect.txt +++ b/Documentation/git-bisect.txt @@ -241,7 +241,12 @@ exit(3) manual page), as the value is chopped with "& 0377". The special exit code 125 should be used when the current source code cannot be tested. If the script exits with this code, the current -revision will be skipped (see `git bisect skip` above). +revision will be skipped (see `git bisect skip` above). 125 was chosen +as the highest sensible value to use for this purpose, because 126 and 127 +are used by POSIX shells to signal specific error status (127 is for +command not found, 126 is for command found but not executable---these +details do not matter, as they are normal errors in the script, as far as +"bisect run" is concerned). You may often find that during a bisect session you want to have temporary modifications (e.g. s/#define DEBUG 0/#define DEBUG 1/ in a @@ -274,53 +279,68 @@ $ git bisect start HEAD origin -- # HEAD is bad, origin is good $ git bisect run make test # "make test" builds and tests ------------ -* Automatically bisect a broken test suite: +* Automatically bisect a broken test case: + ------------ $ cat ~/test.sh #!/bin/sh -make || exit 125 # this skips broken builds -make test # "make test" runs the test suite -$ git bisect start v1.3 v1.1 -- # v1.3 is bad, v1.1 is good +make || exit 125 # this skips broken builds +~/check_test_case.sh # does the test case pass? +$ git bisect start HEAD HEAD~10 -- # culprit is among the last 10 $ git bisect run ~/test.sh ------------ + Here we use a "test.sh" custom script. In this script, if "make" fails, we skip the current commit. +"check_test_case.sh" should "exit 0" if the test case passes, +and "exit 1" otherwise. + -It is safer to use a custom script outside the repository to prevent -interactions between the bisect, make and test processes and the -script. -+ -"make test" should "exit 0", if the test suite passes, and -"exit 1" otherwise. +It is safer if both "test.sh" and "check_test_case.sh" are +outside the repository to prevent interactions between the bisect, +make and test processes and the scripts. -* Automatically bisect a broken test case: +* Automatically bisect with temporary modifications (hot-fix): + ------------ $ cat ~/test.sh #!/bin/sh -make || exit 125 # this skips broken builds -~/check_test_case.sh # does the test case passes ? -$ git bisect start HEAD HEAD~10 -- # culprit is among the last 10 -$ git bisect run ~/test.sh + +# tweak the working tree by merging the hot-fix branch +# and then attempt a build +if git merge --no-commit hot-fix && + make +then + # run project specific test and report its status + ~/check_test_case.sh + status=$? +else + # tell the caller this is untestable + status=125 +fi + +# undo the tweak to allow clean flipping to the next commit +git reset --hard + +# return control +exit $status ------------ + -Here "check_test_case.sh" should "exit 0" if the test case passes, -and "exit 1" otherwise. -+ -It is safer if both "test.sh" and "check_test_case.sh" scripts are -outside the repository to prevent interactions between the bisect, -make and test processes and the scripts. +This applies modifications from a hot-fix branch before each test run, +e.g. in case your build or test environment changed so that older +revisions may need a fix which newer ones have already. (Make sure the +hot-fix branch is based off a commit which is contained in all revisions +which you are bisecting, so that the merge does not pull in too much, or +use `git cherry-pick` instead of `git merge`.) -* Automatically bisect a broken test suite: +* Automatically bisect a broken test case: + ------------ $ git bisect start HEAD HEAD~10 -- # culprit is among the last 10 $ git bisect run sh -c "make || exit 125; ~/check_test_case.sh" ------------ + -Does the same as the previous example, but on a single line. +This shows that you can do without a run script if you write the test +on a single line. Author ------ diff --git a/Documentation/git-blame.txt b/Documentation/git-blame.txt index a27f43950f..c71671b4f9 100644 --- a/Documentation/git-blame.txt +++ b/Documentation/git-blame.txt @@ -8,7 +8,7 @@ git-blame - Show what revision and author last modified each line of a 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> @@ -65,6 +65,10 @@ include::blame-options.txt[] -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. diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt index 1940256930..9106d38e40 100644 --- a/Documentation/git-branch.txt +++ b/Documentation/git-branch.txt @@ -37,11 +37,12 @@ Note that this will create the new branch, but it will not switch the 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 @@ -89,7 +90,8 @@ OPTIONS 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:: @@ -125,11 +127,11 @@ OPTIONS 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 diff --git a/Documentation/git-bundle.txt b/Documentation/git-bundle.txt index 38e59afb34..299007b206 100644 --- a/Documentation/git-bundle.txt +++ b/Documentation/git-bundle.txt @@ -11,8 +11,8 @@ SYNOPSIS [verse] 'git bundle' create <file> <git-rev-list-args> 'git bundle' verify <file> -'git bundle' list-heads <file> [refname...] -'git bundle' unbundle <file> [refname...] +'git bundle' list-heads <file> [<refname>...] +'git bundle' unbundle <file> [<refname>...] DESCRIPTION ----------- @@ -59,7 +59,7 @@ unbundle <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 @@ -68,7 +68,7 @@ unbundle <file>:: packaged. -[refname...]:: +[<refname>...]:: A list of references used to limit the references reported as available. This is principally of use to 'git fetch', which expects to receive only those references asked for and not diff --git a/Documentation/git-cat-file.txt b/Documentation/git-cat-file.txt index a3f56b07fd..544ba7ba21 100644 --- a/Documentation/git-cat-file.txt +++ b/Documentation/git-cat-file.txt @@ -27,7 +27,7 @@ OPTIONS <object>:: The name of the object to show. For a more complete list of ways to spell object names, see - the "SPECIFYING REVISIONS" section in linkgit:gitrevisions[1]. + the "SPECIFYING REVISIONS" section in linkgit:gitrevisions[7]. -t:: Instead of the content, show the object type identified by diff --git a/Documentation/git-check-ref-format.txt b/Documentation/git-check-ref-format.txt index f5c2e0601d..205d83dd0b 100644 --- a/Documentation/git-check-ref-format.txt +++ b/Documentation/git-check-ref-format.txt @@ -49,7 +49,7 @@ git imposes the following rules on how references are named: These rules make it easy for shell script based tools to parse reference names, pathname expansion by the shell when a reference name is used unquoted (by mistake), and also avoids ambiguities in certain -reference name expressions (see linkgit:gitrevisions[1]): +reference name expressions (see linkgit:gitrevisions[7]): . A double-dot `..` is often used as in `ref1..ref2`, and in some contexts this notation means `{caret}ref1 ref2` (i.e. not in diff --git a/Documentation/git-checkout-index.txt b/Documentation/git-checkout-index.txt index 62f9ab24c9..0c0a9c14bc 100644 --- a/Documentation/git-checkout-index.txt +++ b/Documentation/git-checkout-index.txt @@ -13,7 +13,7 @@ SYNOPSIS [--stage=<number>|all] [--temp] [-z] [--stdin] - [--] [<file>]* + [--] [<file>...] DESCRIPTION ----------- diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt index f88e9977d1..880763d391 100644 --- a/Documentation/git-checkout.txt +++ b/Documentation/git-checkout.txt @@ -45,14 +45,14 @@ successful. 'git checkout' [--patch] [<tree-ish>] [--] <pathspec>...:: - When <paths> or `--patch` are given, 'git checkout' *not* switch - branches. It updates the named paths in the working tree from - the index file or from a named <tree-ish> (most often a commit). In - this case, the `-b` and `--track` options are meaningless and giving - either of them results in an error. The <tree-ish> argument can be - used to specify a specific tree-ish (i.e. commit, tag or tree) - to update the index for the given paths before updating the - working tree. + When <paths> or `--patch` are given, 'git checkout' does *not* + switch branches. It updates the named paths in the working tree + from the index file or from a named <tree-ish> (most often a + commit). In this case, the `-b` and `--track` options are + meaningless and giving either of them results in an error. The + <tree-ish> argument can be used to specify a specific tree-ish + (i.e. commit, tag or tree) to update the index for the given + paths before updating the working tree. + The index may contain unmerged entries because of a previous failed merge. By default, if you try to check out such an entry from the index, the @@ -98,7 +98,7 @@ entries; instead, unmerged entries are ignored. "--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 diff --git a/Documentation/git-cherry-pick.txt b/Documentation/git-cherry-pick.txt index 2cef579316..73008705eb 100644 --- a/Documentation/git-cherry-pick.txt +++ b/Documentation/git-cherry-pick.txt @@ -21,7 +21,7 @@ OPTIONS <commit>...:: Commits to cherry-pick. For a more complete list of ways to spell commits, see - linkgit:gitrevisions[1]. + linkgit:gitrevisions[7]. Sets of commits can be passed but no traversal is done by default, as if the '--no-walk' option was specified, see linkgit:git-rev-list[1]. @@ -92,7 +92,7 @@ git cherry-pick ^HEAD master:: Apply the changes introduced by all commits that are ancestors of master but not of HEAD to produce new commits. -git cherry-pick master\~4 master~2:: +git cherry-pick master{tilde}4 master{tilde}2:: Apply the changes introduced by the fifth and third last commits pointed to by master and create 2 new commits with diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt index dc7d3d17b1..42e7021215 100644 --- a/Documentation/git-clone.txt +++ b/Documentation/git-clone.txt @@ -12,7 +12,8 @@ SYNOPSIS '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 ----------- @@ -128,7 +129,12 @@ objects from the source repository into a pack in the cloned repository. configuration variables are created. --mirror:: - Set up a mirror of the remote repository. This implies `--bare`. + 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-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. --origin <name>:: -o <name>:: @@ -162,6 +168,7 @@ objects from the source repository into a pack in the cloned 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 diff --git a/Documentation/git-commit-tree.txt b/Documentation/git-commit-tree.txt index 349366ee1e..5dcf4278fc 100644 --- a/Documentation/git-commit-tree.txt +++ b/Documentation/git-commit-tree.txt @@ -8,7 +8,7 @@ git-commit-tree - Create a new commit object SYNOPSIS -------- -'git commit-tree' <tree> [-p <parent commit>]* < changelog +'git commit-tree' <tree> [(-p <parent commit>)...] < changelog DESCRIPTION ----------- diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt index 42fb1f57b2..8f89f6f08c 100644 --- a/Documentation/git-commit.txt +++ b/Documentation/git-commit.txt @@ -9,10 +9,10 @@ SYNOPSIS -------- [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 ----------- @@ -70,6 +70,19 @@ OPTIONS 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. @@ -201,10 +214,11 @@ FROM UPSTREAM REBASE" section in linkgit:git-rebase[1].) -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: + @@ -212,9 +226,8 @@ 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:: diff --git a/Documentation/git-cvsexportcommit.txt b/Documentation/git-cvsexportcommit.txt index b2696efae9..d25661eb21 100644 --- a/Documentation/git-cvsexportcommit.txt +++ b/Documentation/git-cvsexportcommit.txt @@ -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 --- diff --git a/Documentation/git-cvsserver.txt b/Documentation/git-cvsserver.txt index f4472c61db..70cbb2cae7 100644 --- a/Documentation/git-cvsserver.txt +++ b/Documentation/git-cvsserver.txt @@ -399,13 +399,13 @@ This program is copyright The Open University UK - 2006. 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 --- diff --git a/Documentation/git-daemon.txt b/Documentation/git-daemon.txt index 01c9f8eb9e..d15cb6a845 100644 --- a/Documentation/git-daemon.txt +++ b/Documentation/git-daemon.txt @@ -9,15 +9,15 @@ SYNOPSIS -------- [verse] 'git daemon' [--verbose] [--syslog] [--export-all] - [--timeout=n] [--init-timeout=n] [--max-connections=n] - [--strict-paths] [--base-path=path] [--base-path-relaxed] - [--user-path | --user-path=path] - [--interpolated-path=pathtemplate] - [--reuseaddr] [--detach] [--pid-file=file] - [--enable=service] [--disable=service] - [--allow-override=service] [--forbid-override=service] - [--inetd | [--listen=host_or_ipaddr] [--port=n] [--user=user [--group=group]] - [directory...] + [--timeout=<n>] [--init-timeout=<n>] [--max-connections=<n>] + [--strict-paths] [--base-path=<path>] [--base-path-relaxed] + [--user-path | --user-path=<path>] + [--interpolated-path=<pathtemplate>] + [--reuseaddr] [--detach] [--pid-file=<file>] + [--enable=<service>] [--disable=<service>] + [--allow-override=<service>] [--forbid-override=<service>] + [--inetd | [--listen=<host_or_ipaddr>] [--port=<n>] [--user=<user> [--group=<group>]] + [<directory>...] DESCRIPTION ----------- @@ -48,7 +48,7 @@ OPTIONS 'git daemon' will refuse to start when this option is enabled and no whitelist is specified. ---base-path=path:: +--base-path=<path>:: Remap all the path requests as relative to the given path. This is sort of "GIT root" - if you run 'git daemon' with '--base-path=/srv/git' on example.com, then if you later try to pull @@ -61,7 +61,7 @@ OPTIONS This is useful for switching to --base-path usage, while still allowing the old paths. ---interpolated-path=pathtemplate:: +--interpolated-path=<pathtemplate>:: To support virtual hosting, an interpolated path template can be used to dynamically construct alternate paths. The template supports %H for the target hostname as supplied by the client but @@ -78,29 +78,31 @@ OPTIONS --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=<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:: +--port=<n>:: Listen on an alternative port. Incompatible with '--inetd' option. ---init-timeout=n:: +--init-timeout=<n>:: Timeout between the moment the connection is established and the client request is received (typically a rather low value, since that should be basically immediate). ---timeout=n:: +--timeout=<n>:: Timeout for specific client sub-requests. This includes the time it takes for the server to process the sub-request and the time spent waiting for the next client's request. ---max-connections=n:: +--max-connections=<n>:: Maximum number of concurrent clients, defaults to 32. Set it to zero for no limit. @@ -109,7 +111,7 @@ OPTIONS --verbose, thus by default only error conditions will be logged. --user-path:: ---user-path=path:: +--user-path=<path>:: Allow {tilde}user notation to be used in requests. When specified with no parameter, requests to git://host/{tilde}alice/foo is taken as a request to access @@ -129,12 +131,12 @@ OPTIONS --detach:: Detach from the shell. Implies --syslog. ---pid-file=file:: +--pid-file=<file>:: Save the process id in 'file'. Ignored when the daemon is run under `--inetd`. ---user=user:: ---group=group:: +--user=<user>:: +--group=<group>:: Change daemon's uid and gid before entering the service loop. When only `--user` is given without `--group`, the primary group ID for the user is used. The values of @@ -145,16 +147,16 @@ Giving these options is an error when used with `--inetd`; use the facility of inet daemon to achieve the same before spawning 'git daemon' if needed. ---enable=service:: ---disable=service:: +--enable=<service>:: +--disable=<service>:: Enable/disable the service site-wide per default. Note that a service disabled site-wide can still be enabled per repository if it is marked overridable and the repository enables the service with a configuration item. ---allow-override=service:: ---forbid-override=service:: +--allow-override=<service>:: +--forbid-override=<service>:: Allow/forbid overriding the site-wide default with per repository configuration. By default, all the services are overridable. diff --git a/Documentation/git-describe.txt b/Documentation/git-describe.txt index 7ef9d51577..02e015ad9c 100644 --- a/Documentation/git-describe.txt +++ b/Documentation/git-describe.txt @@ -37,7 +37,7 @@ OPTIONS --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 diff --git a/Documentation/git-diff.txt b/Documentation/git-diff.txt index 08fd4099ad..49105102db 100644 --- a/Documentation/git-diff.txt +++ b/Documentation/git-diff.txt @@ -8,12 +8,17 @@ git-diff - Show changes between commits, commit and working tree, etc 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>...]:: @@ -23,9 +28,9 @@ tree and the index file, or the index file and the working tree. further add to the index but you still haven't. You can stage these changes by using linkgit:git-add[1]. + -If exactly two paths are given, and at least one is untracked, -compare the two files / directories. This behavior can be -forced by --no-index. +If exactly two paths are given and at least one points outside +the current repository, 'git diff' will compare the two files / +directories. This behavior can be forced by --no-index. 'git diff' [--options] --cached [<commit>] [--] [<path>...]:: @@ -33,6 +38,8 @@ forced by --no-index. 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>...]:: @@ -64,15 +71,16 @@ forced by --no-index. Just in case if you are doing something exotic, it should be noted that all of the <commit> in the above description, except -for the last two forms that use ".." notations, can be any -<tree-ish>. +in the last two forms that use ".." notations, can be any +<tree>. The third form ('git diff <commit> <commit>') can also +be used to compare two <blob> objects. For a more complete list of ways to spell <commit>, see -"SPECIFYING REVISIONS" section in linkgit:gitrevisions[1]. +"SPECIFYING REVISIONS" section in linkgit:gitrevisions[7]. However, "diff" is about comparing two _endpoints_, not ranges, and the range notations ("<commit>..<commit>" and "<commit>\...<commit>") do not mean a range as defined in the -"SPECIFYING RANGES" section in linkgit:gitrevisions[1]. +"SPECIFYING RANGES" section in linkgit:gitrevisions[7]. OPTIONS ------- @@ -159,8 +167,12 @@ rewrites (very expensive). SEE ALSO -------- -linkgit:git-difftool[1]:: - Show changes using common diff tools +diff(1), +linkgit:git-difftool[1], +linkgit:git-log[1], +linkgit:gitdiffcore[7], +linkgit:git-format-patch[1], +linkgit:git-apply[1] Author ------ diff --git a/Documentation/git-difftool.txt b/Documentation/git-difftool.txt index 8250bad2ce..db87f1d423 100644 --- a/Documentation/git-difftool.txt +++ b/Documentation/git-difftool.txt @@ -7,13 +7,14 @@ git-difftool - Show changes using common diff tools 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 ------- @@ -55,14 +56,16 @@ the configured command line will be invoked with the following variables available: `$LOCAL` is set to the name of the temporary file containing the contents of the diff pre-image and `$REMOTE` is set to the name of the temporary file containing the contents -of the diff post-image. `$BASE` is provided for compatibility -with custom merge tool commands and has the same value as `$LOCAL`. +of the diff post-image. `$MERGED` is the name of the file which is +being compared. `$BASE` is provided for compatibility +with custom merge tool commands and has the same value as `$MERGED`. -x <command>:: --extcmd=<command>:: Specify a custom command for viewing diffs. 'git-difftool' ignores the configured defaults and runs `$command $LOCAL $REMOTE` when this option is specified. + Additionally, `$BASE` is set in the environment. -g:: --gui:: diff --git a/Documentation/git-fast-export.txt b/Documentation/git-fast-export.txt index fcad113276..e05b686b1e 100644 --- a/Documentation/git-fast-export.txt +++ b/Documentation/git-fast-export.txt @@ -96,7 +96,7 @@ marks the same across runs. in the commit (as opposed to just listing the files which are different from the commit's first parent). -[git-rev-list-args...]:: +[<git-rev-list-args>...]:: A list of arguments, acceptable to 'git rev-parse' and 'git rev-list', that specifies the specific objects and references to export. For example, `master{tilde}10..master` causes the diff --git a/Documentation/git-fast-import.txt b/Documentation/git-fast-import.txt index 966ba4f213..02bb49886c 100644 --- a/Documentation/git-fast-import.txt +++ b/Documentation/git-fast-import.txt @@ -92,6 +92,11 @@ OPTIONS --(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 @@ -320,6 +325,11 @@ and control the current import process. More detailed discussion 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. + `feature`:: Require that fast-import supports the specified feature, or abort if it does not. @@ -439,7 +449,7 @@ Marks must be declared (via `mark`) before they can be used. * A complete 40 byte or abbreviated commit SHA-1 in hex. * Any valid Git SHA-1 expression that resolves to a commit. See - ``SPECIFYING REVISIONS'' in linkgit:gitrevisions[1] for details. + ``SPECIFYING REVISIONS'' in linkgit:gitrevisions[7] for details. The special case of restarting an incremental import from the current branch value should be written as: @@ -532,6 +542,8 @@ The value of `<path>` must be in canonical form. That is it must 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` @@ -876,34 +888,72 @@ Placing a `progress` command immediately after a `checkpoint` will 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. + `feature` ~~~~~~~~~ Require that fast-import supports the specified feature, or abort if it does not. .... - 'feature' SP <feature> LF + 'feature' SP <feature> ('=' <argument>)? LF .... -The <feature> part of the command may be any string matching -^[a-zA-Z][a-zA-Z-]*$ and should be understood by fast-import. +The <feature> part of the command may be any one of the following: + +date-format:: +export-marks:: +relative-marks:: +no-relative-marks:: +force:: + Act as though the corresponding command-line option with + a leading '--' was passed on the command line + (see OPTIONS, above). + +import-marks:: + Like --import-marks except in two respects: first, only one + "feature import-marks" command is allowed per stream; + second, an --import-marks= command-line option overrides + any "feature import-marks" command in the stream. + +cat-blob:: + Ignored. Versions of fast-import not supporting the + "cat-blob" 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. -Feature work identical as their option counterparts with the -exception of the import-marks feature, see below. - -The following features are currently supported: - -* date-format -* import-marks -* export-marks -* relative-marks -* no-relative-marks -* force - -The import-marks behaves differently from when it is specified as -commandline option in that only one "feature import-marks" is allowed -per stream. Also, any --import-marks= specified on the commandline -will override those from the stream (if any). `option` ~~~~~~~~ @@ -930,6 +980,7 @@ not be passed as option: * date-format * import-marks * export-marks +* cat-blob-fd * force Crash Reports @@ -1230,6 +1281,13 @@ and lazy loading of subtrees, allows fast-import to efficiently import 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 ------ diff --git a/Documentation/git-fetch.txt b/Documentation/git-fetch.txt index 400fe7f956..c76e313923 100644 --- a/Documentation/git-fetch.txt +++ b/Documentation/git-fetch.txt @@ -12,7 +12,7 @@ SYNOPSIS 'git fetch' [<options>] <group> -'git fetch' --multiple [<options>] [<repository> | <group>]... +'git fetch' --multiple [<options>] [(<repository> | <group>)...] 'git fetch' --all [<options>] @@ -26,7 +26,7 @@ The ref names and their object names of fetched refs are stored 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 diff --git a/Documentation/git-filter-branch.txt b/Documentation/git-filter-branch.txt index 7357c8879a..aa69b8ef13 100644 --- a/Documentation/git-filter-branch.txt +++ b/Documentation/git-filter-branch.txt @@ -81,7 +81,7 @@ OPTIONS This filter may be used if you only need to modify the environment in which the commit will be performed. Specifically, you might want to rewrite the author/committer name/email/time environment - variables (see linkgit:git-commit[1] for details). Do not forget + variables (see linkgit:git-commit-tree[1] for details). Do not forget to re-export the variables. --tree-filter <command>:: @@ -117,7 +117,7 @@ OPTIONS This is the filter for performing the commit. If this filter is specified, it will be called instead of the 'git commit-tree' command, with arguments of the form - "<TREE_ID> [-p <PARENT_COMMIT_ID>]..." and the log message on + "<TREE_ID> [(-p <PARENT_COMMIT_ID>)...]" and the log message on stdin. The commit id is expected on stdout. + As a special extension, the commit filter may emit multiple @@ -361,7 +361,7 @@ git filter-branch --index-filter \ 'git ls-files -s | sed "s-\t\"*-&newsubdir/-" | GIT_INDEX_FILE=$GIT_INDEX_FILE.new \ git update-index --index-info && - mv $GIT_INDEX_FILE.new $GIT_INDEX_FILE' HEAD + mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"' HEAD --------------------------------------------------------------- diff --git a/Documentation/git-fmt-merge-msg.txt b/Documentation/git-fmt-merge-msg.txt index 302f56b889..75adf7a502 100644 --- a/Documentation/git-fmt-merge-msg.txt +++ b/Documentation/git-fmt-merge-msg.txt @@ -9,8 +9,8 @@ git-fmt-merge-msg - Produce a merge commit message 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 ----------- @@ -24,10 +24,12 @@ automatically invoking 'git merge'. 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 @@ -52,8 +54,10 @@ CONFIGURATION ------------- 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 diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt index d66fd9d231..152e695c81 100644 --- a/Documentation/git-for-each-ref.txt +++ b/Documentation/git-for-each-ref.txt @@ -9,7 +9,7 @@ SYNOPSIS -------- [verse] 'git for-each-ref' [--count=<count>] [--shell|--perl|--python|--tcl] - [--sort=<key>]* [--format=<format>] [<pattern>...] + [(--sort=<key>)...] [--format=<format>] [<pattern>...] DESCRIPTION ----------- @@ -123,7 +123,7 @@ EXAMPLES -------- An example directly producing formatted text. Show the most recent -3 tagged commits:: +3 tagged commits: ------------ #!/bin/sh @@ -140,7 +140,7 @@ Ref: %(*refname) A simple example showing the use of shell eval on the output, -demonstrating the use of --shell. List the prefixes of all heads:: +demonstrating the use of --shell. List the prefixes of all heads: ------------ #!/bin/sh @@ -154,7 +154,7 @@ done A bit more elaborate report on tags, demonstrating that the format -may be an entire script:: +may be an entire script: ------------ #!/bin/sh @@ -204,3 +204,15 @@ eval=`git for-each-ref --shell --format="$fmt" \ refs/tags` eval "$eval" ------------ + +Author +------ +Written by Junio C Hamano <gitster@pobox.com>. + +Documentation +------------- +Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>. + +GIT +--- +Part of the linkgit:git[1] suite diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt index 4b3f5ba535..9dcafc6d44 100644 --- a/Documentation/git-format-patch.txt +++ b/Documentation/git-format-patch.txt @@ -39,7 +39,7 @@ There are two ways to specify which commits to operate on. that leads to the <since> to be output. 2. Generic <revision range> expression (see "SPECIFYING - REVISIONS" section in linkgit:gitrevisions[1]) means the + REVISIONS" section in linkgit:gitrevisions[7]) means the commits in the specified range. The first rule takes precedence in the case of a single <commit>. To @@ -74,7 +74,7 @@ OPTIONS include::diff-options.txt[] -<n>:: - Limits the number of patches to prepare. + Prepare patches from the topmost <n> commits. -o <dir>:: --output-directory <dir>:: diff --git a/Documentation/git-fsck.txt b/Documentation/git-fsck.txt index 3ad48a6336..86f9b2bf91 100644 --- a/Documentation/git-fsck.txt +++ b/Documentation/git-fsck.txt @@ -123,9 +123,6 @@ dangling <type> <object>:: The <type> object <object>, is present in the database but never 'directly' used. A dangling commit could be a root node. -warning: git-fsck: tree <tree> has full pathnames in it:: - And it shouldn't... - sha1 mismatch <object>:: The database has an object who's sha1 doesn't match the database value. diff --git a/Documentation/git-gc.txt b/Documentation/git-gc.txt index 315f07ef1c..26632414b2 100644 --- a/Documentation/git-gc.txt +++ b/Documentation/git-gc.txt @@ -89,7 +89,7 @@ are not part of the current project most users will want to expire 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: ------------ @@ -107,7 +107,7 @@ how long records of conflicted merge you have not resolved are kept. This defaults to 15 days. The optional configuration variable 'gc.packrefs' determines if -'git gc' runs 'git pack-refs'. This can be set to "nobare" to enable +'git gc' runs 'git pack-refs'. This can be set to "notbare" to enable it within all non-bare repos or it can be set to a boolean value. This defaults to true. @@ -128,8 +128,8 @@ Notes '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). diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt index dab0a78fa8..791d4d4871 100644 --- a/Documentation/git-grep.txt +++ b/Documentation/git-grep.txt @@ -93,6 +93,7 @@ OPTIONS as a regex). -n:: +--line-number:: Prefix the line number to matching lines. -l:: diff --git a/Documentation/git-index-pack.txt b/Documentation/git-index-pack.txt index f3ccc72f0d..c2bb81042c 100644 --- a/Documentation/git-index-pack.txt +++ b/Documentation/git-index-pack.txt @@ -59,10 +59,10 @@ OPTIONS the newly constructed pack and index before refs can be updated to use objects contained in the pack. ---keep='why':: +--keep=<msg>:: Like --keep create a .keep file before moving the index into its final destination, but rather than creating an empty file - place 'why' followed by an LF into the .keep file. The 'why' + place '<msg>' followed by an LF into the .keep file. The '<msg>' message can later be searched for within all .keep files to locate any which have outlived their usefulness. diff --git a/Documentation/git-init.txt b/Documentation/git-init.txt index 246b07ebf9..00d4a124c9 100644 --- a/Documentation/git-init.txt +++ b/Documentation/git-init.txt @@ -31,7 +31,7 @@ current working directory. Specify the directory from which templates will be used. (See the "TEMPLATE DIRECTORY" section below.) ---shared[={false|true|umask|group|all|world|everybody|0xxx}]:: +--shared[=(false|true|umask|group|all|world|everybody|0xxx)]:: Specify that the git repository is to be shared amongst several users. This allows users belonging to the same group to push into that diff --git a/Documentation/git-log.txt b/Documentation/git-log.txt index c213bdbdc5..ff41784c60 100644 --- a/Documentation/git-log.txt +++ b/Documentation/git-log.txt @@ -31,7 +31,7 @@ OPTIONS either <since> or <until> is omitted, it defaults to `HEAD`, i.e. the tip of the current branch. For a more complete list of ways to spell <since> - and <until>, see linkgit:gitrevisions[1]. + and <until>, see linkgit:gitrevisions[7]. --follow:: Continue listing the history of a file beyond renames @@ -116,7 +116,7 @@ git log --follow builtin-rev-list.c:: 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:: diff --git a/Documentation/git-ls-files.txt b/Documentation/git-ls-files.txt index 347f447986..86abd13451 100644 --- a/Documentation/git-ls-files.txt +++ b/Documentation/git-ls-files.txt @@ -17,7 +17,7 @@ SYNOPSIS [--exclude-per-directory=<file>] [--exclude-standard] [--error-unmatch] [--with-tree=<tree-ish>] - [--full-name] [--abbrev] [--] [<file>]* + [--full-name] [--abbrev] [--] [<file>...] DESCRIPTION ----------- diff --git a/Documentation/git-ls-remote.txt b/Documentation/git-ls-remote.txt index abe7bf9ff9..3422765827 100644 --- a/Documentation/git-ls-remote.txt +++ b/Documentation/git-ls-remote.txt @@ -10,7 +10,7 @@ SYNOPSIS -------- [verse] 'git ls-remote' [--heads] [--tags] [-u <exec> | --upload-pack <exec>] - <repository> <refs>... + <repository> [<refs>...] DESCRIPTION ----------- diff --git a/Documentation/git-ls-tree.txt b/Documentation/git-ls-tree.txt index 1f89d36800..76ed625e7b 100644 --- a/Documentation/git-ls-tree.txt +++ b/Documentation/git-ls-tree.txt @@ -10,8 +10,8 @@ SYNOPSIS -------- [verse] 'git ls-tree' [-d] [-r] [-t] [-l] [-z] - [--name-only] [--name-status] [--full-name] [--full-tree] [--abbrev=[<n>]] - <tree-ish> [paths...] + [--name-only] [--name-status] [--full-name] [--full-tree] [--abbrev[=<n>]] + <tree-ish> [<path>...] DESCRIPTION ----------- @@ -19,11 +19,11 @@ Lists the contents of a given tree object, like what "/bin/ls -a" does in the current working directory. Note that: - the behaviour is slightly different from that of "/bin/ls" in that the - 'paths' denote just a list of patterns to match, e.g. so specifying + '<path>' denotes just a list of patterns to match, e.g. so specifying directory name (without '-r') will behave differently, and order of the arguments does not matter. - - the behaviour is similar to that of "/bin/ls" in that the 'paths' is + - the behaviour is similar to that of "/bin/ls" in that the '<path>' is taken as relative to the current working directory. E.g. when you are in a directory 'sub' that has a directory 'dir', you can run 'git ls-tree -r HEAD dir' to list the contents of the tree (that is @@ -72,7 +72,7 @@ OPTIONS Do not limit the listing to the current working directory. Implies --full-name. -paths:: +[<path>...]:: When paths are given, show them (note that this isn't really raw pathnames, but rather a list of patterns to match). Otherwise implicitly uses the root level of the tree as the sole path argument. diff --git a/Documentation/git-mailsplit.txt b/Documentation/git-mailsplit.txt index a634485281..71912a19a4 100644 --- a/Documentation/git-mailsplit.txt +++ b/Documentation/git-mailsplit.txt @@ -7,7 +7,7 @@ git-mailsplit - Simple UNIX mbox splitter program SYNOPSIS -------- -'git mailsplit' [-b] [-f<nn>] [-d<prec>] [--keep-cr] -o<directory> [--] [<mbox>|<Maildir>...] +'git mailsplit' [-b] [-f<nn>] [-d<prec>] [--keep-cr] -o<directory> [--] [(<mbox>|<Maildir>)...] DESCRIPTION ----------- diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt index 84043cc5b2..c1efaaa5c5 100644 --- a/Documentation/git-merge.txt +++ b/Documentation/git-merge.txt @@ -13,6 +13,7 @@ SYNOPSIS [-s <strategy>] [-X <strategy-option>] [--[no-]rerere-autoupdate] [-m <msg>] <commit>... 'git merge' <msg> HEAD <commit>... +'git merge' --abort DESCRIPTION ----------- @@ -47,6 +48,14 @@ The second syntax (<msg> `HEAD` <commit>...) is supported for 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. @@ -59,19 +68,31 @@ include::merge-options.txt[] -m <msg>:: Set the commit message to be used for the merge commit (in case one is created). - - If `--log` is specified, a shortlog of the commits being merged - will be appended to the specified message. - - The 'git fmt-merge-msg' command can be - used to give a good default for automated 'git merge' - invocations. ++ +If `--log` is specified, a shortlog of the commits being merged +will be appended to the specified message. ++ +The 'git fmt-merge-msg' command can be +used to give a good default for automated 'git merge' +invocations. --rerere-autoupdate:: --no-rerere-autoupdate:: 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 @@ -142,7 +163,7 @@ happens: 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 --------------------------- @@ -213,8 +234,8 @@ After seeing a conflict, you can do two things: * 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 diff --git a/Documentation/git-mergetool.txt b/Documentation/git-mergetool.txt index e4ed016146..1f75a848ba 100644 --- a/Documentation/git-mergetool.txt +++ b/Documentation/git-mergetool.txt @@ -7,7 +7,7 @@ git-mergetool - Run merge conflict resolution tools to resolve merge conflicts SYNOPSIS -------- -'git mergetool' [--tool=<tool>] [-y|--no-prompt|--prompt] [<file>]... +'git mergetool' [--tool=<tool>] [-y|--no-prompt|--prompt] [<file>...] DESCRIPTION ----------- diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt index 2981d8c5ef..296f314eae 100644 --- a/Documentation/git-notes.txt +++ b/Documentation/git-notes.txt @@ -14,8 +14,12 @@ SYNOPSIS '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 @@ -83,6 +87,21 @@ edit:: 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 @@ -91,6 +110,10 @@ remove:: 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:: @@ -133,9 +156,37 @@ OPTIONS 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 @@ -163,6 +214,38 @@ object, in which case the history of the notes can be read with `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 -------- diff --git a/Documentation/git-pack-objects.txt b/Documentation/git-pack-objects.txt index 8ed09c0b3c..96684bc510 100644 --- a/Documentation/git-pack-objects.txt +++ b/Documentation/git-pack-objects.txt @@ -11,8 +11,8 @@ SYNOPSIS [verse] 'git pack-objects' [-q | --progress | --all-progress] [--all-progress-implied] [--no-reuse-delta] [--delta-base-offset] [--non-empty] - [--local] [--incremental] [--window=N] [--depth=N] - [--revs [--unpacked | --all]*] [--stdout | base-name] + [--local] [--incremental] [--window=<n>] [--depth=<n>] + [--revs [--unpacked | --all]] [--stdout | base-name] [--keep-true-parents] < object-list @@ -82,8 +82,8 @@ base-name:: reference was included in the resulting packfile. This can be useful to send new tags to native git clients. ---window=[N]:: ---depth=[N]:: +--window=<n>:: +--depth=<n>:: These two options affect how the objects contained in the pack are stored using delta compression. The objects are first internally sorted by type, size and @@ -95,10 +95,10 @@ base-name:: times to get to the necessary object. The default value for --window is 10 and --depth is 50. ---window-memory=[N]:: +--window-memory=<n>:: This option provides an additional limit on top of `--window`; the window size will dynamically scale down so as to not take - up more than N bytes in memory. This is useful in + up more than '<n>' bytes in memory. This is useful in repositories with a mix of large and small objects to not run out of memory with a large window, but still be able to take advantage of the large window for the smaller objects. The @@ -106,7 +106,7 @@ base-name:: `--window-memory=0` makes memory usage unlimited, which is the default. ---max-pack-size=[N]:: +--max-pack-size=<n>:: Maximum size of each output pack file. The size can be suffixed with "k", "m", or "g". The minimum size allowed is limited to 1 MiB. If specified, multiple packfiles may be created. @@ -171,7 +171,7 @@ base-name:: wholesale enforcement of a different compression level on the packed data is desired. ---compression=[N]:: +--compression=<n>:: Specifies compression level for newly-compressed data in the generated pack. If not specified, pack compression level is determined first by pack.compression, then by core.compression, @@ -190,15 +190,20 @@ self-contained. Use `git index-pack --fix-thin` (see linkgit:git-index-pack[1]) to restore the self-contained property. --delta-base-offset:: - A packed archive can express base object of a delta as - either 20-byte object name or as an offset in the - stream, but older version of git does not understand the + A packed archive can express the base object of a delta as + either a 20-byte object name or as an offset in the + stream, but ancient versions of git don't understand the latter. By default, 'git pack-objects' only uses the former format for better compatibility. This option allows the command to use the latter format for compactness. Depending on the average delta chain length, this option typically shrinks the resulting packfile by 3-5 per-cent. ++ +Note: Porcelain commands such as `git gc` (see linkgit:git-gc[1]), +`git repack` (see linkgit:git-repack[1]) pass this option by default +in modern git when they put objects in your repository into pack files. +So does `git bundle` (see linkgit:git-bundle[1]) when it creates a bundle. --threads=<n>:: Specifies the number of threads to spawn when searching for best diff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt index c50f7dcb89..b33e6be872 100644 --- a/Documentation/git-pull.txt +++ b/Documentation/git-pull.txt @@ -26,9 +26,9 @@ With `--rebase`, it runs 'git rebase' instead of 'git merge'. <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 -(e.g., refs/heads/*:refs/remotes/origin/*), but usually it is -the name of a branch in the remote repository. +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. Default values for <repository> and <branch> are read from the "remote" and "merge" configuration for the current branch @@ -84,6 +84,15 @@ must be given before the options meant for 'git fetch'. --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 ~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -92,12 +101,15 @@ include::merge-options.txt[] :git-pull: 1 --rebase:: - Instead of a merge, perform a rebase after fetching. If - there is a remote ref for the upstream branch, and this branch - was rebased since last fetched, the rebase uses that information - to avoid rebasing non-local changes. To make this the default - for branch `<name>`, set configuration `branch.<name>.rebase` - to `true`. + Rebase the current branch on top of the upstream branch after + fetching. If there is a remote-tracking branch corresponding to + the upstream branch and the upstream branch was rebased since last + fetched, the rebase uses that information to avoid rebasing + non-local changes. ++ +See `branch.<name>.rebase` and `branch.autosetuprebase` in +linkgit:git-config[1] if you want to make `git pull` always use +`{litdd}rebase` instead of merging. + [NOTE] This is a potentially _dangerous_ mode of operation. @@ -134,7 +146,7 @@ and if there is not any such variable, the value on `URL: ` line 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>` @@ -147,9 +159,9 @@ refs/heads/*:refs/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 diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt index 020955ff5a..e11660a2e6 100644 --- a/Documentation/git-push.txt +++ b/Documentation/git-push.txt @@ -41,7 +41,7 @@ OPTIONS[[OPTIONS]] + The <src> is often the name of the branch you would want to push, but it can be any arbitrary "SHA-1 expression", such as `master~4` or -`HEAD` (see linkgit:gitrevisions[1]). +`HEAD` (see linkgit:gitrevisions[7]). + The <dst> tells which ref on the remote side is updated with this push. Arbitrary expressions cannot be used here, an actual ref must diff --git a/Documentation/git-read-tree.txt b/Documentation/git-read-tree.txt index 2e78da448f..634423a69e 100644 --- a/Documentation/git-read-tree.txt +++ b/Documentation/git-read-tree.txt @@ -11,7 +11,7 @@ SYNOPSIS '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 @@ -114,6 +114,10 @@ OPTIONS 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. @@ -412,13 +416,6 @@ turn `core.sparseCheckout` on in order to have sparse checkout 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]; diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index 30e5c0eb14..96680c8456 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -279,6 +279,10 @@ which makes little sense. --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-reflog.txt b/Documentation/git-reflog.txt index 5a0451aaf3..e50bd9b68d 100644 --- a/Documentation/git-reflog.txt +++ b/Documentation/git-reflog.txt @@ -40,7 +40,7 @@ see linkgit:git-log[1]. The reflog is useful in various git commands, to specify the old value of a reference. For example, `HEAD@\{2\}` means "where HEAD used to be two moves ago", `master@\{one.week.ago\}` means "where master used to -point to one week ago", and so on. See linkgit:gitrevisions[1] for +point to one week ago", and so on. See linkgit:gitrevisions[7] for more details. To delete single entries from the reflog, use the subcommand "delete" diff --git a/Documentation/git-relink.txt b/Documentation/git-relink.txt index 8a5842bb93..8fc809f82b 100644 --- a/Documentation/git-relink.txt +++ b/Documentation/git-relink.txt @@ -7,7 +7,7 @@ git-relink - Hardlink common objects in local repositories SYNOPSIS -------- -'git relink' [--safe] <dir> [<dir>]* <master_dir> +'git relink' [--safe] <dir>... <master_dir> DESCRIPTION ----------- diff --git a/Documentation/git-remote-ext.txt b/Documentation/git-remote-ext.txt new file mode 100644 index 0000000000..68263a6a53 --- /dev/null +++ b/Documentation/git-remote-ext.txt @@ -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 new file mode 100644 index 0000000000..4aecd4d187 --- /dev/null +++ b/Documentation/git-remote-fd.txt @@ -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 diff --git a/Documentation/git-remote-helpers.txt b/Documentation/git-remote-helpers.txt index 3a23477ce7..51de895822 100644 --- a/Documentation/git-remote-helpers.txt +++ b/Documentation/git-remote-helpers.txt @@ -201,12 +201,12 @@ REF LIST ATTRIBUTES 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'\}:: diff --git a/Documentation/git-remote.txt b/Documentation/git-remote.txt index aa021b0cb8..c258ea48db 100644 --- a/Documentation/git-remote.txt +++ b/Documentation/git-remote.txt @@ -20,7 +20,7 @@ SYNOPSIS 'git remote set-url --delete' [--push] <name> <url> 'git remote' [-v | --verbose] 'show' [-n] <name> 'git remote prune' [-n | --dry-run] <name> -'git remote' [-v | --verbose] 'update' [-p | --prune] [group | remote]... +'git remote' [-v | --verbose] 'update' [-p | --prune] [(<group> | <remote>)...] DESCRIPTION ----------- @@ -75,7 +75,7 @@ was passed. '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 @@ -84,7 +84,7 @@ the configuration file format. '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':: @@ -146,7 +146,7 @@ With `-n` option, the remote heads are not queried first with '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>". diff --git a/Documentation/git-repack.txt b/Documentation/git-repack.txt index 8c67d1724f..27f7865b06 100644 --- a/Documentation/git-repack.txt +++ b/Documentation/git-repack.txt @@ -8,7 +8,7 @@ git-repack - Pack unpacked objects in a repository SYNOPSIS -------- -'git repack' [-a] [-A] [-d] [-f] [-l] [-n] [-q] [--window=N] [--depth=N] +'git repack' [-a] [-A] [-d] [-f] [-F] [-l] [-n] [-q] [--window=<n>] [--depth=<n>] DESCRIPTION ----------- @@ -62,6 +62,10 @@ other objects in that pack they already have locally. linkgit:git-pack-objects[1]. -f:: + Pass the `--no-reuse-delta` option to `git-pack-objects`, see + linkgit:git-pack-objects[1]. + +-F:: Pass the `--no-reuse-object` option to `git-pack-objects`, see linkgit:git-pack-objects[1]. @@ -76,8 +80,8 @@ other objects in that pack they already have locally. this repository (or a direct copy of it) over HTTP or FTP. See linkgit:git-update-server-info[1]. ---window=[N]:: ---depth=[N]:: +--window=<n>:: +--depth=<n>:: These two options affect how the objects contained in the pack are stored using delta compression. The objects are first internally sorted by type, size and optionally names and compared against the @@ -87,10 +91,10 @@ other objects in that pack they already have locally. to be applied that many times to get to the necessary object. The default value for --window is 10 and --depth is 50. ---window-memory=[N]:: +--window-memory=<n>:: This option provides an additional limit on top of `--window`; the window size will dynamically scale down so as to not take - up more than N bytes in memory. This is useful in + up more than '<n>' bytes in memory. This is useful in repositories with a mix of large and small objects to not run out of memory with a large window, but still be able to take advantage of the large window for the smaller objects. The @@ -98,7 +102,7 @@ other objects in that pack they already have locally. `--window-memory=0` makes memory usage unlimited, which is the default. ---max-pack-size=[N]:: +--max-pack-size=<n>:: Maximum size of each output pack file. The size can be suffixed with "k", "m", or "g". The minimum size allowed is limited to 1 MiB. If specified, multiple packfiles may be created. diff --git a/Documentation/git-reset.txt b/Documentation/git-reset.txt index 9cf31485fe..927ecee2f2 100644 --- a/Documentation/git-reset.txt +++ b/Documentation/git-reset.txt @@ -15,17 +15,24 @@ SYNOPSIS DESCRIPTION ----------- In the first and second form, copy entries from <commit> to the index. -In the third form, set the current branch to <commit>, optionally -modifying index and worktree to match. The <commit> defaults to HEAD +In the third form, set the current branch head (HEAD) to <commit>, optionally +modifying index and working tree to match. The <commit> defaults to HEAD in all forms. 'git reset' [-q] [<commit>] [--] <paths>...:: This form resets the index entries for all <paths> to their - state at the <commit>. (It does not affect the worktree, nor + state at <commit>. (It does not affect the working tree, nor the current branch.) + This means that `git reset <paths>` is the opposite of `git add <paths>`. ++ +After running `git reset <paths>` to update the index entry, you can +use linkgit:git-checkout[1] to check the contents out of the index to +the working tree. +Alternatively, using linkgit:git-checkout[1] and specifying a commit, you +can copy the contents of a path out of a commit to the index and to the +working tree in one go. 'git reset' --patch|-p [<commit>] [--] [<paths>...]:: Interactively select hunks in the difference between the index @@ -36,16 +43,17 @@ This means that `git reset -p` is the opposite of `git add -p` (see linkgit:git-add[1]). 'git reset' [--<mode>] [<commit>]:: - This form points the current branch to <commit> and then - updates index and working tree according to <mode>, which must - be one of the following: + This form resets the current branch head to <commit> and + possibly updates the index (resetting it to the tree of <commit>) and + the working tree depending on <mode>, which + must be one of the following: + -- --soft:: - Does not touch the index file nor the working tree at all, but - requires them to be in a good order. This leaves all your changed - files "Changes to be committed", as 'git status' would - put it. + Does not touch the index file nor the working tree at all (but + resets the head to <commit>, just like all modes do). This leaves + all your changed files "Changes to be committed", as 'git status' + would put it. --mixed:: Resets the index but not the working tree (i.e., the changed files @@ -53,22 +61,25 @@ linkgit:git-add[1]). been updated. This is the default action. --hard:: - Matches the working tree and index to that of the tree being - switched to. Any changes to tracked files in the working tree - since <commit> are lost. + Resets the index and working tree. Any changes to tracked files in the + working tree since <commit> are discarded. --merge:: - Resets the index to match the tree recorded by the named commit, - and updates the files that are different between the named commit - and the current commit in the working tree. + Resets the index and updates the files in the working tree that are + different between <commit> and HEAD, but keeps those which are + different between the index and working tree (i.e. which have changes + which have not been added). + If a file that is different between <commit> and the index has unstaged + changes, reset is aborted. ++ +In other words, --merge does something like a 'git read-tree -u -m <commit>', +but carries forward unmerged index entries. --keep:: - Reset the index to the given commit, keeping local changes in - the working tree since the current commit, while updating - working tree files without local changes to what appears in - the given commit. If a file that is different between the - current commit and the given commit has local changes, reset - is aborted. + 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. -- If you want to undo a commit other than the latest on a branch, @@ -184,7 +195,7 @@ tip of the current branch in ORIG_HEAD, so resetting hard to it brings your index file and the working tree back to that state, and resets the tip of the branch to that commit. -Undo a merge or pull inside a dirty work tree:: +Undo a merge or pull inside a dirty working tree:: + ------------ $ git pull <1> @@ -257,7 +268,7 @@ Suppose you are working on something and you commit it, and then you continue working a bit more, but now you think that what you have in your working tree should be in another branch that has nothing to do with what you committed previously. You can start a new branch and -reset it while keeping the changes in your work tree. +reset it while keeping the changes in your working tree. + ------------ $ git tag start @@ -294,8 +305,10 @@ In these tables, A, B, C and D are some different states of a file. For example, the first line of the first table means that if a file is in state A in the working tree, in state B in the index, in state C in HEAD and in state D in the target, then "git reset --soft -target" will put the file in state A in the working tree, in state B -in the index and in state D in HEAD. +target" will leave the file in the working tree in state A and in the +index in state B. It resets (i.e. moves) the HEAD (i.e. the tip of +the current branch, if you are on one) to "target" (which has the file +in state D). working index HEAD target working index HEAD ---------------------------------------------------- @@ -346,11 +359,11 @@ in the index and in state D in HEAD. --keep B C C "reset --merge" is meant to be used when resetting out of a conflicted -merge. Any mergy operation guarantees that the work tree file that is +merge. Any mergy operation guarantees that the working tree file that is involved in the merge does not have local change wrt the index before -it starts, and that it writes the result out to the work tree. So if +it starts, and that it writes the result out to the working tree. So if we see some difference between the index and the target and also -between the index and the work tree, then it means that we are not +between the index and the working tree, then it means that we are not resetting out from a state that a mergy operation left after failing with a conflict. That is why we disallow --merge option in this case. diff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt index 173f3fc785..8e1e32908c 100644 --- a/Documentation/git-rev-list.txt +++ b/Documentation/git-rev-list.txt @@ -9,10 +9,10 @@ git-rev-list - Lists commit objects in reverse chronological order SYNOPSIS -------- [verse] -'git rev-list' [ \--max-count=number ] - [ \--skip=number ] - [ \--max-age=timestamp ] - [ \--min-age=timestamp ] +'git rev-list' [ \--max-count=<number> ] + [ \--skip=<number> ] + [ \--max-age=<timestamp> ] + [ \--min-age=<timestamp> ] [ \--sparse ] [ \--merges ] [ \--no-merges ] @@ -21,10 +21,10 @@ SYNOPSIS [ \--full-history ] [ \--not ] [ \--all ] - [ \--branches[=pattern] ] - [ \--tags[=pattern] ] - [ \--remotes[=pattern] ] - [ \--glob=glob-pattern ] + [ \--branches[=<pattern>] ] + [ \--tags[=<pattern>] ] + [ \--remotes[=<pattern>] ] + [ \--glob=<glob-pattern> ] [ \--stdin ] [ \--quiet ] [ \--topo-order ] @@ -37,7 +37,7 @@ SYNOPSIS [ \--regexp-ignore-case | -i ] [ \--extended-regexp | -E ] [ \--fixed-strings | -F ] - [ \--date={local|relative|default|iso|rfc|short} ] + [ \--date=(local|relative|default|iso|rfc|short) ] [ [\--objects | \--objects-edge] [ \--unpacked ] ] [ \--pretty | \--header ] [ \--bisect ] diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt index 341ca90c6e..ff23cb0219 100644 --- a/Documentation/git-rev-parse.txt +++ b/Documentation/git-rev-parse.txt @@ -95,7 +95,7 @@ OPTIONS unfortunately named tag "master"), and show them as full refnames (e.g. "refs/heads/master"). ---abbrev-ref[={strict|loose}]:: +--abbrev-ref[=(strict|loose)]:: A non-ambiguous short name of the objects name. The option core.warnAmbiguousRefs is used to select the strict abbreviation mode. @@ -136,7 +136,12 @@ appending `/{asterisk}`. 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 diff --git a/Documentation/git-revert.txt b/Documentation/git-revert.txt index b7d9ef7e47..752fc88e76 100644 --- a/Documentation/git-revert.txt +++ b/Documentation/git-revert.txt @@ -31,7 +31,7 @@ OPTIONS <commit>...:: Commits to revert. For a more complete list of ways to spell commit names, see - linkgit:gitrevisions[1]. + linkgit:gitrevisions[7]. Sets of commits can also be given but no traversal is done by default, see linkgit:git-rev-list[1] and its '--no-walk' option. @@ -87,7 +87,7 @@ git revert HEAD~3:: Revert the changes specified by the fourth last commit in HEAD and create a new commit with the reverted changes. -git revert -n master\~5..master~2:: +git revert -n master{tilde}5..master{tilde}2:: Revert the changes done by commits from the fifth last commit in master (included) to the third last commit in master diff --git a/Documentation/git-rm.txt b/Documentation/git-rm.txt index 71e3d9fc23..0adbe8b1f8 100644 --- a/Documentation/git-rm.txt +++ b/Documentation/git-rm.txt @@ -89,8 +89,8 @@ the paths that have disappeared from the filesystem. However, depending on the use case, there are several ways that can be done. -Using "git commit -a" -~~~~~~~~~~~~~~~~~~~~~ +Using ``git commit -a'' +~~~~~~~~~~~~~~~~~~~~~~~ If you intend that your next commit should record all modifications of tracked files in the working tree and record all removals of files that have been removed from the working tree with `rm` @@ -98,8 +98,8 @@ files that have been removed from the working tree with `rm` automatically notice and record all removals. You can also have a similar effect without committing by using `git add -u`. -Using "git add -A" -~~~~~~~~~~~~~~~~~~ +Using ``git add -A'' +~~~~~~~~~~~~~~~~~~~~ When accepting a new code drop for a vendor branch, you probably want to record both the removal of paths and additions of new paths as well as modifications of existing paths. @@ -111,8 +111,8 @@ tree using this command: git ls-files -z | xargs -0 rm -f ---------------- -and then "untar" the new code in the working tree. Alternately -you could "rsync" the changes into the working tree. +and then untar the new code in the working tree. Alternately +you could 'rsync' the changes into the working tree. After that, the easiest way to record all removals, additions, and modifications in the working tree is: diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt index c283084272..7ec9dabe68 100644 --- a/Documentation/git-send-email.txt +++ b/Documentation/git-send-email.txt @@ -82,11 +82,26 @@ See the CONFIGURATION section for 'sendemail.multiedit'. 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. @@ -97,7 +112,7 @@ See the CONFIGURATION section for 'sendemail.multiedit'. 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. @@ -165,6 +180,15 @@ user is prompted for a password while the input is masked for privacy. 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'. @@ -177,6 +201,12 @@ user is prompted for a password while the input is masked for privacy. 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. @@ -292,6 +322,9 @@ have been specified, in which case default to 'compose'. Default is the value of 'sendemail.validate'; if this is not set, default to '--validate'. +--force:: + Send emails even if safety checks would prevent it. + CONFIGURATION ------------- diff --git a/Documentation/git-shell.txt b/Documentation/git-shell.txt index 0f3ad811cf..6403126a02 100644 --- a/Documentation/git-shell.txt +++ b/Documentation/git-shell.txt @@ -3,24 +3,30 @@ git-shell(1) 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 ------ diff --git a/Documentation/git-shortlog.txt b/Documentation/git-shortlog.txt index bc1ac77495..5cc3baf48d 100644 --- a/Documentation/git-shortlog.txt +++ b/Documentation/git-shortlog.txt @@ -44,7 +44,7 @@ OPTIONS --email:: Show the email address of each author. ---format[='<format>']:: +--format[=<format>]:: Instead of the commit subject, use some other information to describe each commit. '<format>' can be any string accepted by the `--format` option of 'git log', such as '{asterisk} [%h] %s'. diff --git a/Documentation/git-show-branch.txt b/Documentation/git-show-branch.txt index 6453263340..3b0c88271a 100644 --- a/Documentation/git-show-branch.txt +++ b/Documentation/git-show-branch.txt @@ -12,7 +12,7 @@ SYNOPSIS [--current] [--color[=<when>] | --no-color] [--sparse] [--more=<n> | --list | --independent | --merge-base] [--no-name | --sha1-name] [--topics] - [<rev> | <glob>]... + [(<rev> | <glob>)...] 'git show-branch' (-g|--reflog)[=<n>[,<base>]] [--list] [<ref>] @@ -32,7 +32,7 @@ no <rev> nor <glob> is given on the command line. OPTIONS ------- <rev>:: - Arbitrary extended SHA1 expression (see linkgit:gitrevisions[1]) + Arbitrary extended SHA1 expression (see linkgit:gitrevisions[7]) that typically names a branch head or a tag. <glob>:: diff --git a/Documentation/git-show-ref.txt b/Documentation/git-show-ref.txt index 4696af7433..be0ec189af 100644 --- a/Documentation/git-show-ref.txt +++ b/Documentation/git-show-ref.txt @@ -84,7 +84,11 @@ OPTIONS <pattern>...:: - Show references matching one or more patterns. + Show references matching one or more patterns. Patterns are matched from + the end of the full name, and only complete parts are matched, e.g. + 'master' matches 'refs/heads/master', 'refs/remotes/origin/master', + 'refs/tags/jedi/master' but not 'refs/heads/mymaster' nor + 'refs/remotes/master/jedi'. OUTPUT ------ diff --git a/Documentation/git-show.txt b/Documentation/git-show.txt index 0002bfb045..f0a8a1aff3 100644 --- a/Documentation/git-show.txt +++ b/Documentation/git-show.txt @@ -36,7 +36,7 @@ OPTIONS <object>...:: The names of objects to show. For a more complete list of ways to spell object names, see - "SPECIFYING REVISIONS" section in linkgit:gitrevisions[1]. + "SPECIFYING REVISIONS" section in linkgit:gitrevisions[7]. include::pretty-options.txt[] @@ -54,6 +54,10 @@ git show v1.0.0:: git show v1.0.0^\{tree\}:: Shows the tree pointed to by the tag `v1.0.0`. +git show -s --format=%s v1.0.0^\{commit\}:: + Shows the subject of the commit pointed to by the + tag `v1.0.0`. + git show next~10:Documentation/README:: Shows the contents of the file `Documentation/README` as they were current in the 10th last commit of the branch diff --git a/Documentation/git-status.txt b/Documentation/git-status.txt index dae190a5f2..5102a23f8e 100644 --- a/Documentation/git-status.txt +++ b/Documentation/git-status.txt @@ -38,20 +38,20 @@ OPTIONS -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 diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index 4b84d08fc8..e161a40a73 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -56,6 +56,8 @@ COMMANDS as well, they take precedence. --no-metadata;; Set the 'noMetadata' option in the [svn-remote] config. + This option is not recommended, please read the 'svn.noMetadata' + section of this manpage before using this option. --use-svm-props;; Set the 'useSvmProps' option in the [svn-remote] config. --use-svnsync-props;; @@ -64,7 +66,7 @@ COMMANDS 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 @@ -436,13 +438,13 @@ git rebase --onto remotes/git-svn A^ master OPTIONS ------- ---shared[={false|true|umask|group|all|world|everybody}]:: +--shared[=(false|true|umask|group|all|world|everybody)]:: --template=<template_directory>:: 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 @@ -597,13 +599,22 @@ svn.noMetadata:: svn-remote.<name>.noMetadata:: This gets rid of the 'git-svn-id:' lines at the end of every commit. + -If you lose your .git/svn/git-svn/.rev_db file, 'git svn' will not -be able to rebuild it and you won't be able to fetch again, -either. This is fine for one-shot imports. +This option can only be used for one-shot imports as 'git svn' +will not be able to fetch again without metadata. Additionally, +if you lose your .git/svn/**/.rev_map.* files, 'git svn' will not +be able to rebuild them. + The 'git svn log' command will not work on repositories using this, either. Using this conflicts with the 'useSvmProps' option for (hopefully) obvious reasons. ++ +This option is NOT recommended as it makes it difficult to track down +old references to SVN revision numbers in existing documentation, bug +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 +reformatting of metadata for ease-of-reading and rewriting authorship +info for non-"svn.authorsFile" users. svn.useSvmProps:: svn-remote.<name>.useSvmProps:: @@ -718,8 +729,11 @@ have each person clone that repository with 'git clone': 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) diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt index 31c78a81e0..fa7ac12a3d 100644 --- a/Documentation/git-tag.txt +++ b/Documentation/git-tag.txt @@ -18,21 +18,22 @@ SYNOPSIS 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 @@ -164,20 +165,19 @@ You can test which tag you have by doing which should return 0123456789abcdef.. if you have the new version. -Sorry for inconvenience. +Sorry for the inconvenience. ------------ Does this seem a bit complicated? It *should* be. There is no -way that it would be correct to just "fix" it behind peoples -backs. People need to know that their tags might have been -changed. +way that it would be correct to just "fix" it automatically. +People need to know that their tags might have been changed. On Automatic following ~~~~~~~~~~~~~~~~~~~~~~ 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. @@ -188,9 +188,10 @@ the toplevel but not limited to them. Mere mortals when pulling from each other do not necessarily want to automatically get private anchor point tags from the other person. -You would notice "please pull" messages on the mailing list says -repo URL and branch name alone. This is designed to be easily -cut&pasted to a 'git fetch' command line: +Often, "please pull" messages on the mailing list just provide +two pieces of information: a repo URL and a branch name; this +is designed to be easily cut&pasted at the end of a 'git fetch' +command line: ------------ Linus, please pull from @@ -206,14 +207,14 @@ becomes: $ git pull git://git..../proj.git master ------------ -In such a case, you do not want to automatically follow other's -tags. +In such a case, you do not want to automatically follow the other +person's tags. -One important aspect of git is it is distributed, and being -distributed largely means there is no inherent "upstream" or +One important aspect of git is its distributed nature, which +largely means there is no inherent "upstream" or "downstream" in the system. On the face of it, the above example might seem to indicate that the tag namespace is owned -by upper echelon of people and tags only flow downwards, but +by the upper echelon of people and that tags only flow downwards, but that is not the case. It only shows that the usage pattern determines who are interested in whose tags. @@ -231,8 +232,8 @@ this case. 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 +they are most likely tracking each other's progress by +having remote-tracking branches. Again, the heuristic to automatically follow such tags is a good thing. @@ -241,21 +242,21 @@ On Backdating Tags If you have imported some changes from another VCS and would like to add tags for major releases of your work, it is useful to be able -to specify the date to embed inside of the tag object. The data in +to specify the date to embed inside of the tag object; such data in the tag object affects, for example, the ordering of tags in the gitweb interface. To set the date used in future tag objects, set the environment -variable GIT_COMMITTER_DATE to one or more of the date and time. The -date and time can be specified in a number of ways; the most common -is "YYYY-MM-DD HH:MM". +variable GIT_COMMITTER_DATE (see the later discussion of possible +values; the most common form is "YYYY-MM-DD HH:MM"). -An example follows. +For example: ------------ $ GIT_COMMITTER_DATE="2006-10-02 10:31" git tag -s v1.0.1 ------------ +include::date-formats.txt[] SEE ALSO -------- diff --git a/Documentation/git-update-index.txt b/Documentation/git-update-index.txt index 74d1d49dbf..1ca56c85aa 100644 --- a/Documentation/git-update-index.txt +++ b/Documentation/git-update-index.txt @@ -12,7 +12,7 @@ SYNOPSIS 'git update-index' [--add] [--remove | --force-remove] [--replace] [--refresh] [-q] [--unmerged] [--ignore-missing] - [--cacheinfo <mode> <object> <file>]* + [(--cacheinfo <mode> <object> <file>)...] [--chmod=(+|-)x] [--assume-unchanged | --no-assume-unchanged] [--skip-worktree | --no-skip-worktree] @@ -21,7 +21,7 @@ SYNOPSIS [--info-only] [--index-info] [-z] [--stdin] [--verbose] - [--] [<file>]* + [--] [<file>...] DESCRIPTION ----------- @@ -144,8 +144,8 @@ you will need to handle the situation manually. Report what is being added and removed from index. -z:: - Only meaningful with `--stdin`; paths are separated with - NUL character instead of LF. + Only meaningful with `--stdin` or `--index-info`; paths are + separated with NUL character instead of LF. \--:: Do not interpret any more arguments as options. diff --git a/Documentation/git-verify-pack.txt b/Documentation/git-verify-pack.txt index 916a38aa99..0f848de8b2 100644 --- a/Documentation/git-verify-pack.txt +++ b/Documentation/git-verify-pack.txt @@ -8,7 +8,7 @@ git-verify-pack - Validate packed git archive files SYNOPSIS -------- -'git verify-pack' [-v|--verbose] [--] <pack>.idx ... +'git verify-pack' [-v|--verbose] [-s|--stat-only] [--] <pack>.idx ... DESCRIPTION diff --git a/Documentation/git-verify-tag.txt b/Documentation/git-verify-tag.txt index dada21242c..711219749c 100644 --- a/Documentation/git-verify-tag.txt +++ b/Documentation/git-verify-tag.txt @@ -15,6 +15,10 @@ Validates the gpg signature created by 'git tag'. OPTIONS ------- +-v:: +--verbose:: + Print the contents of the tag object before validating it. + <tag>...:: SHA1 identifiers of git tag objects. diff --git a/Documentation/git-web--browse.txt b/Documentation/git-web--browse.txt index e1586c78c3..c0416e5e1a 100644 --- a/Documentation/git-web--browse.txt +++ b/Documentation/git-web--browse.txt @@ -20,8 +20,14 @@ The following browsers (or commands) are currently supported: * 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 @@ -32,19 +38,19 @@ Custom commands may also be specified. OPTIONS ------- --b BROWSER:: ---browser=BROWSER:: - Use the specified BROWSER. It must be in the list of supported +-b <browser>:: +--browser=<browser>:: + Use the specified browser. It must be in the list of supported browsers. --t BROWSER:: ---tool=BROWSER:: +-t <browser>:: +--tool=<browser>:: Same as above. --c CONF.VAR:: ---config=CONF.VAR:: +-c <conf.var>:: +--config=<conf.var>:: CONF.VAR is looked up in the git config files. If it's set, - then its value specify the browser that should be used. + then its value specifies the browser that should be used. CONFIGURATION VARIABLES ----------------------- diff --git a/Documentation/git.txt b/Documentation/git.txt index 646e761789..0c32d45248 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -9,11 +9,11 @@ git - the stupid content tracker SYNOPSIS -------- [verse] -'git' [--version] [--exec-path[=GIT_EXEC_PATH]] [--html-path] +'git' [--version] [--exec-path[=<path>]] [--html-path] [-p|--paginate|--no-pager] [--no-replace-objects] - [--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE] - [-c name=value] - [--help] COMMAND [ARGS] + [--bare] [--git-dir=<path>] [--work-tree=<path>] + [-c <name>=<value>] + [--help] <command> [<args>] DESCRIPTION ----------- @@ -28,7 +28,7 @@ also want to read linkgit:gitcvs-migration[7]. See the link:user-manual.html[Git User's Manual] for a more in-depth introduction. -The COMMAND is either a name of a Git command (see below) or an alias +The '<command>' is either a name of a Git command (see below) or an alias as defined in the configuration file (see linkgit:git-config[1]). Formatted and hyperlinked version of the latest git @@ -44,29 +44,46 @@ unreleased) version of git, that is available from 'master' branch of the `git.git` repository. Documentation for older releases are available here: -* link:v1.7.3/git.html[documentation for release 1.7.3] +* 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], @@ -76,16 +93,18 @@ Documentation for older releases are available here: 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], @@ -96,9 +115,10 @@ Documentation for older releases are available here: 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], @@ -251,7 +271,7 @@ help ...`. The <name> is expected in the same format as listed by 'git config' (subkeys separated by dots). ---exec-path:: +--exec-path[=<path>]:: Path to wherever your core git programs are installed. This can also be controlled by setting the GIT_EXEC_PATH environment variable. If no path is given, 'git' will print @@ -277,17 +297,12 @@ help ...`. 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 @@ -494,7 +509,7 @@ HEAD:: (i.e. the contents of `$GIT_DIR/refs/heads/<head>`). For a more complete list of ways to spell object names, see -"SPECIFYING REVISIONS" section in linkgit:gitrevisions[1]. +"SPECIFYING REVISIONS" section in linkgit:gitrevisions[7]. File/Directory Structure diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt index e5a27d875e..15aebc6062 100644 --- a/Documentation/gitattributes.txt +++ b/Documentation/gitattributes.txt @@ -62,14 +62,21 @@ consults `$GIT_DIR/info/attributes` file (which has the highest 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 @@ -328,6 +335,16 @@ input that is already correctly indented. In this case, the lack of a 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 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -477,6 +494,8 @@ patterns are available: - `csharp` suitable for source code in the C# language. +- `fortran` suitable for source code in the Fortran language. + - `html` suitable for HTML/XHTML documents. - `java` suitable for source code in the Java language. @@ -485,6 +504,8 @@ patterns are available: - `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. @@ -572,13 +593,46 @@ and now produces better output), you can remove the cache manually with `git update-ref -d refs/notes/textconv/jpg` (where "jpg" is the name of the diff driver, as in the example above). +Marking files as binary +^^^^^^^^^^^^^^^^^^^^^^^ + +Git usually guesses correctly whether a blob contains text or binary +data by examining the beginning of the contents. However, sometimes you +may want to override its decision, either because a blob contains binary +data later in the file, or because the content, while technically +composed of text characters, is opaque to a human reader. For example, +many postscript files contain only ascii characters, but produce noisy +and meaningless diffs. + +The simplest way to mark a file as binary is to unset the diff +attribute in the `.gitattributes` file: + +------------------------ +*.ps -diff +------------------------ + +This will cause git to generate `Binary files differ` (or a binary +patch, if binary patches are enabled) instead of a regular diff. + +However, one may also want to specify other diff driver attributes. For +example, you might want to use `textconv` to convert postscript files to +an ascii representation for human viewing, but otherwise treat them as +binary files. You cannot specify both `-diff` and `diff=ps` attributes. +The solution is to use the `diff.*.binary` config option: + +------------------------ +[diff "ps"] + textconv = ps2ascii + binary = true +------------------------ + Performing a three-way merge ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ `merge` ^^^^^^^ -The attribute `merge` affects how three versions of a file is +The attribute `merge` affects how three versions of a file are merged when a file-level merge is necessary during `git merge`, and other commands such as `git revert` and `git cherry-pick`. @@ -592,15 +646,15 @@ Unset:: Take the version from the current branch as the tentative merge result, and declare that the merge has - conflicts. This is suitable for binary files that does + conflicts. This is suitable for binary files that do not have a well-defined merge semantics. Unspecified:: By default, this uses the same built-in 3-way merge - driver as is the case the `merge` attribute is set. - However, `merge.default` configuration variable can name - different merge driver to be used for paths to which the + driver as is the case when the `merge` attribute is set. + However, the `merge.default` configuration variable can name + different merge driver to be used with paths for which the `merge` attribute is unspecified. String:: @@ -714,6 +768,8 @@ control per path. 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:: @@ -721,13 +777,13 @@ 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. diff --git a/Documentation/gitcore-tutorial.txt b/Documentation/gitcore-tutorial.txt index 5e9c5ebba3..c27d086f68 100644 --- a/Documentation/gitcore-tutorial.txt +++ b/Documentation/gitcore-tutorial.txt @@ -971,7 +971,7 @@ commits from the master branch. The string inside brackets before the commit log message is a short name you can use to name the commit. In the above example, 'master' and 'mybranch' are branch heads. 'master^' is the first parent of 'master' -branch head. Please see linkgit:gitrevisions[1] if you want to +branch head. Please see linkgit:gitrevisions[7] if you want to see more complex cases. [NOTE] diff --git a/Documentation/gitdiffcore.txt b/Documentation/gitdiffcore.txt index 5d91a7e5b3..6af29a4603 100644 --- a/Documentation/gitdiffcore.txt +++ b/Documentation/gitdiffcore.txt @@ -227,9 +227,9 @@ changes that touch a specified string, and is controlled by the 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 diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt index 7183aa9abb..28edefa202 100644 --- a/Documentation/githooks.txt +++ b/Documentation/githooks.txt @@ -350,10 +350,6 @@ rebase:: The commits are guaranteed to be listed in the order that they were processed by rebase. -There is no default 'post-rewrite' hook, but see the -`post-receive-copy-notes` script in `contrib/hooks` for an example -that copies your git-notes to the rewritten commits. - GIT --- diff --git a/Documentation/gitignore.txt b/Documentation/gitignore.txt index 7dc2e8b0bc..8416f3445a 100644 --- a/Documentation/gitignore.txt +++ b/Documentation/gitignore.txt @@ -14,11 +14,8 @@ DESCRIPTION A `gitignore` file specifies intentionally untracked files that git should ignore. -Note that all the `gitignore` files really concern only files -that are not already tracked by git; -in order to ignore uncommitted changes in already tracked files, -please refer to the 'git update-index --assume-unchanged' -documentation. +Files already tracked by git are not affected; see the NOTES +below for details. Each line in a `gitignore` file specifies a pattern. When deciding whether to ignore a path, git normally checks @@ -62,7 +59,8 @@ files specified by command-line options. Higher-level git tools, such as 'git status' and 'git add', use patterns from the sources specified above. -Patterns have the following format: +PATTERN FORMAT +-------------- - A blank line matches no files, so it can serve as a separator for readability. @@ -98,7 +96,20 @@ Patterns have the following format: For example, "/{asterisk}.c" matches "cat-file.c" but not "mozilla-sha1/sha1.c". -An example: +NOTES +----- + +The purpose of gitignore files is to ensure that certain files +not tracked by git remain untracked. + +To ignore uncommitted changes in a file that is already tracked, +use 'git update-index {litdd}assume-unchanged'. + +To stop tracking a file that is currently tracked, use +'git rm --cached'. + +EXAMPLES +-------- -------------------------------------------------------------- $ git status @@ -140,6 +151,11 @@ Another example: The second .gitignore prevents git from ignoring `arch/foo/kernel/vmlinux.lds.S`. +SEE ALSO +-------- +linkgit:git-rm[1], linkgit:git-update-index[1], +linkgit:gitrepository-layout[5] + Documentation ------------- Documentation by David Greaves, Junio C Hamano, Josh Triplett, diff --git a/Documentation/gitk.txt b/Documentation/gitk.txt index 05ac1c79f7..e21bac4f3f 100644 --- a/Documentation/gitk.txt +++ b/Documentation/gitk.txt @@ -69,7 +69,7 @@ frequently used options. the form "'<from>'..'<to>'" to show all revisions between '<from>' and back to '<to>'. Note, more advanced revision selection can be applied. For a more complete list of ways to spell object names, see - linkgit:gitrevisions[1]. + linkgit:gitrevisions[7]. <path>...:: diff --git a/Documentation/gitmodules.txt b/Documentation/gitmodules.txt index bcffd95ada..68977943e7 100644 --- a/Documentation/gitmodules.txt +++ b/Documentation/gitmodules.txt @@ -44,6 +44,14 @@ submodule.<name>.update:: 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 diff --git a/Documentation/gittutorial-2.txt b/Documentation/gittutorial-2.txt index ecab0c09d0..7fe5848d1f 100644 --- a/Documentation/gittutorial-2.txt +++ b/Documentation/gittutorial-2.txt @@ -373,7 +373,7 @@ $ git status # # 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 diff --git a/Documentation/gittutorial.txt b/Documentation/gittutorial.txt index 1c1606696e..0982f74ef6 100644 --- a/Documentation/gittutorial.txt +++ b/Documentation/gittutorial.txt @@ -385,7 +385,7 @@ alice$ git fetch bob 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: ------------------------------------- @@ -402,8 +402,8 @@ could merge the changes into her master branch: 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 diff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt index 1f029f8aa0..f04b48ef0d 100644 --- a/Documentation/glossary-content.txt +++ b/Documentation/glossary-content.txt @@ -131,7 +131,7 @@ to point at the new commit. 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:: @@ -260,7 +260,7 @@ This commit is referred to as a "merge commit", or sometimes just a 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`. @@ -349,6 +349,14 @@ This commit is referred to as a "merge commit", or sometimes just a 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 @@ -418,14 +426,6 @@ This commit is referred to as a "merge commit", or sometimes just a 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 0953a50b69..2933056120 100644 --- a/Documentation/howto/using-merge-subtree.txt +++ b/Documentation/howto/using-merge-subtree.txt @@ -71,5 +71,5 @@ Additional tips 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. diff --git a/Documentation/merge-config.txt b/Documentation/merge-config.txt index b72f533970..1e5c22c5e5 100644 --- a/Documentation/merge-config.txt +++ b/Documentation/merge-config.txt @@ -7,8 +7,10 @@ merge.conflictstyle:: 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 diff --git a/Documentation/merge-options.txt b/Documentation/merge-options.txt index 722d704ff2..e33e0f8e11 100644 --- a/Documentation/merge-options.txt +++ b/Documentation/merge-options.txt @@ -16,11 +16,11 @@ inspect and further tweak the merge result before committing. 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. diff --git a/Documentation/merge-strategies.txt b/Documentation/merge-strategies.txt index 049313d601..595a3cf1a7 100644 --- a/Documentation/merge-strategies.txt +++ b/Documentation/merge-strategies.txt @@ -40,6 +40,28 @@ the other tree did, declaring 'our' history contains all that happened in it. 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 @@ -52,7 +74,11 @@ no-renormalize;; Disables the `renormalize` option. This overrides the `merge.renormalize` configuration variable. -subtree[=path];; +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 match with each other when merging. Instead, the specified path diff --git a/Documentation/pretty-options.txt b/Documentation/pretty-options.txt index 9b6f3899ec..50923e2ce9 100644 --- a/Documentation/pretty-options.txt +++ b/Documentation/pretty-options.txt @@ -1,5 +1,5 @@ ---pretty[='<format>']:: ---format='<format>':: +--pretty[=<format>]:: +--format=<format>:: Pretty-print the contents of the commit logs in a given format, where '<format>' can be one of 'oneline', 'short', 'medium', diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt index e2237ae4a0..9c47ad885b 100644 --- a/Documentation/rev-list-options.txt +++ b/Documentation/rev-list-options.txt @@ -13,7 +13,7 @@ include::pretty-options.txt[] Synonym for `--date=relative`. ---date={relative,local,default,iso,rfc,short,raw}:: +--date=(relative|local|default|iso|rfc|short|raw):: Only takes effect for dates shown in human-readable format, such as when using "--pretty". `log.date` config variable sets a default @@ -45,13 +45,13 @@ endif::git-rev-list[] --parents:: - Print the parents of the commit. Also enables parent - rewriting, see 'History Simplification' below. + Print also the parents of the commit (in the form "commit parent..."). + Also enables parent rewriting, see 'History Simplification' below. --children:: - Print the children of the commit. Also enables parent - rewriting, see 'History Simplification' below. + Print also the children of the commit (in the form "commit child..."). + Also enables parent rewriting, see 'History Simplification' below. ifdef::git-rev-list[] --timestamp:: @@ -95,6 +95,8 @@ you would get an output like this: to be printed in between commits, in order for the graph history to be drawn properly. + +This enables parent rewriting, see 'History Simplification' below. ++ This implies the '--topo-order' option by default, but the '--date-order' option may also be specified. @@ -146,6 +148,9 @@ options may be given. See linkgit:git-diff-files[1] for more options. -t:: Show the tree objects in the diff output. This implies '-r'. + +-s:: + Suppress diff output. endif::git-rev-list[] Commit Limiting @@ -160,7 +165,7 @@ limiting may be applied. -n 'number':: --max-count=<number>:: - Limit the number of commits output. + Limit the number of commits to output. --skip=<number>:: @@ -246,29 +251,29 @@ endif::git-rev-list[] Pretend as if all the refs in `refs/` are listed on the command line as '<commit>'. ---branches[=pattern]:: +--branches[=<pattern>]:: Pretend as if all the refs in `refs/heads` are listed - on the command line as '<commit>'. If `pattern` is given, limit + on the command line as '<commit>'. If '<pattern>' is given, limit branches to ones matching given shell glob. If pattern lacks '?', '*', or '[', '/*' at the end is implied. ---tags[=pattern]:: +--tags[=<pattern>]:: Pretend as if all the refs in `refs/tags` are listed - on the command line as '<commit>'. If `pattern` is given, limit + on the command line as '<commit>'. If '<pattern>' is given, limit tags to ones matching given shell glob. If pattern lacks '?', '*', or '[', '/*' at the end is implied. ---remotes[=pattern]:: +--remotes[=<pattern>]:: 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. + on the command line as '<commit>'. If '<pattern>' is given, limit + remote-tracking branches to ones matching given shell glob. If pattern lacks '?', '*', or '[', '/*' at the end is implied. ---glob=glob-pattern:: - Pretend as if all the refs matching shell glob `glob-pattern` +--glob=<glob-pattern>:: + Pretend as if all the refs matching shell glob '<glob-pattern>' are listed on the command line as '<commit>'. Leading 'refs/', is automatically prepended if missing. If pattern lacks '?', '*', or '[', '/*' at the end is implied. diff --git a/Documentation/revisions.txt b/Documentation/revisions.txt index fe846f043c..9e92734bc1 100644 --- a/Documentation/revisions.txt +++ b/Documentation/revisions.txt @@ -106,12 +106,20 @@ the `$GIT_DIR/refs` directory or from the `$GIT_DIR/packed-refs` 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 starts with the specified text. + a commit whose commit message matches the specified regular expression. This name returns the youngest matching commit which is reachable from any ref. If the commit message starts with a '!', you have to repeat that; the special sequence ':/!', followed by something else than '!' is reserved for now. + The regular expression can match any part of the commit message. To + match messages starting with a string, one can use e.g. `:/^foo`. * A suffix ':' followed by a path (e.g. `HEAD:README`); this names the blob or tree at the given path in the tree-ish object named by the part @@ -119,6 +127,10 @@ the `$GIT_DIR/refs` directory or from the `$GIT_DIR/packed-refs` file. ':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 diff --git a/Documentation/technical/api-merge.txt b/Documentation/technical/api-merge.txt index a7e050bb7a..9dc1bed768 100644 --- a/Documentation/technical/api-merge.txt +++ b/Documentation/technical/api-merge.txt @@ -17,6 +17,40 @@ responsible for a few things. 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 ----------------------------- @@ -28,15 +62,24 @@ 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 @@ -47,18 +90,6 @@ The `ancestor_label`, `our_label`, and `their_label` parameters are 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 c5d141cd63..f6a4a361bd 100644 --- a/Documentation/technical/api-parse-options.txt +++ b/Documentation/technical/api-parse-options.txt @@ -118,13 +118,16 @@ There are some macros to easily define options: `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)`:: diff --git a/Documentation/technical/api-sigchain.txt b/Documentation/technical/api-sigchain.txt new file mode 100644 index 0000000000..9e1189ef01 --- /dev/null +++ b/Documentation/technical/api-sigchain.txt @@ -0,0 +1,41 @@ +sigchain API +============ + +Code often wants to set a signal handler to clean up temporary files or +other work-in-progress when we die unexpectedly. For multiple pieces of +code to do this without conflicting, each piece of code must remember +the old value of the handler and restore it either when: + + 1. The work-in-progress is finished, and the handler is no longer + necessary. The handler should revert to the original behavior + (either another handler, SIG_DFL, or SIG_IGN). + + 2. The signal is received. We should then do our cleanup, then chain + to the next handler (or die if it is SIG_DFL). + +Sigchain is a tiny library for keeping a stack of handlers. Your handler +and installation code should look something like: + +------------------------------------------ + void clean_foo_on_signal(int sig) + { + clean_foo(); + sigchain_pop(sig); + raise(sig); + } + + void other_func() + { + sigchain_push_common(clean_foo_on_signal); + mess_up_foo(); + clean_foo(); + } +------------------------------------------ + +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. + +You can sigchain_push and sigchain_pop individual signals. For +convenience, sigchain_push_common will push the handler onto the stack +for many common signals. diff --git a/Documentation/technical/index-format.txt b/Documentation/technical/index-format.txt new file mode 100644 index 0000000000..7b233ca196 --- /dev/null +++ b/Documentation/technical/index-format.txt @@ -0,0 +1,185 @@ +GIT index format +================ + += The git index file has the following format + + All binary numbers are in network byte order. Version 2 is described + here unless stated otherwise. + + - A 12-byte header consisting of + + 4-byte signature: + The signature is { 'D', 'I', 'R', 'C' } (stands for "dircache") + + 4-byte version number: + The current supported versions are 2 and 3. + + 32-bit number of index entries. + + - A number of sorted index entries (see below). + + - Extensions + + Extensions are identified by signature. Optional extensions can + be ignored if GIT does not understand them. + + GIT currently supports cached tree and resolve undo extensions. + + 4-byte extension signature. If the first byte is 'A'..'Z' the + extension is optional and can be ignored. + + 32-bit size of the extension + + Extension data + + - 160-bit SHA-1 over the content of the index file before this + checksum. + +== Index entry + + Index entries are sorted in ascending order on the name field, + interpreted as a string of unsigned bytes (i.e. memcmp() order, no + localization, no special casing of directory separator '/'). Entries + with the same name are sorted by their stage field. + + 32-bit ctime seconds, the last time a file's metadata changed + this is stat(2) data + + 32-bit ctime nanosecond fractions + this is stat(2) data + + 32-bit mtime seconds, the last time a file's data changed + this is stat(2) data + + 32-bit mtime nanosecond fractions + this is stat(2) data + + 32-bit dev + this is stat(2) data + + 32-bit ino + this is stat(2) data + + 32-bit mode, split into (high to low bits) + + 4-bit object type + valid values in binary are 1000 (regular file), 1010 (symbolic link) + and 1110 (gitlink) + + 3-bit unused + + 9-bit unix permission. Only 0755 and 0644 are valid for regular files. + Symbolic links and gitlinks have value 0 in this field. + + 32-bit uid + this is stat(2) data + + 32-bit gid + this is stat(2) data + + 32-bit file size + This is the on-disk size from stat(2), truncated to 32-bit. + + 160-bit SHA-1 for the represented object + + A 16-bit 'flags' field split into (high to low bits) + + 1-bit assume-valid flag + + 1-bit extended flag (must be zero in version 2) + + 2-bit stage (during merge) + + 12-bit name length if the length is less than 0xFFF; otherwise 0xFFF + is stored in this field. + + (Version 3) A 16-bit field, only applicable if the "extended flag" + above is 1, split into (high to low bits). + + 1-bit reserved for future + + 1-bit skip-worktree flag (used by sparse checkout) + + 1-bit intent-to-add flag (used by "git add -N") + + 13-bit unused, must be zero + + Entry path name (variable length) relative to top level directory + (without leading slash). '/' is used as path separator. The special + path components ".", ".." and ".git" (without quotes) are disallowed. + Trailing slash is also disallowed. + + The exact encoding is undefined, but the '.' and '/' characters + are encoded in 7-bit ASCII and the encoding cannot contain a NUL + byte (iow, this is a UNIX pathname). + + 1-8 nul bytes as necessary to pad the entry to a multiple of eight bytes + while keeping the name NUL-terminated. + +== Extensions + +=== Cached tree + + Cached tree extension contains pre-computed hashes for trees that can + be derived from the index. It helps speed up tree object generation + from index for a new commit. + + When a path is updated in index, the path must be invalidated and + removed from tree cache. + + The signature for this extension is { 'T', 'R', 'E', 'E' }. + + A series of entries fill the entire extension; each of which + consists of: + + - NUL-terminated path component (relative to its parent directory); + + - ASCII decimal number of entries in the index that is covered by the + tree this entry represents (entry_count); + + - A space (ASCII 32); + + - ASCII decimal number that represents the number of subtrees this + tree has; + + - A newline (ASCII 10); and + + - 160-bit object name for the object that would result from writing + this span of index as a tree. + + An entry can be in an invalidated state and is represented by having -1 + in the entry_count field. + + The entries are written out in the top-down, depth-first order. The + first entry represents the root level of the repository, followed by the + first subtree---let's call this A---of the root level (with its name + relative to the root level), followed by the first subtree of A (with + its name relative to A), ... + +=== Resolve undo + + A conflict is represented in the index as a set of higher stage entries. + When a conflict is resolved (e.g. with "git add path"), these higher + stage entries will be removed and a stage-0 entry with proper resoluton + is added. + + When these higher stage entries are removed, they are saved in the + resolve undo extension, so that conflicts can be recreated (e.g. with + "git checkout -m"), in case users want to redo a conflict resolution + from scratch. + + The signature for this extension is { 'R', 'E', 'U', 'C' }. + + A series of entries fill the entire extension; each of which + consists of: + + - NUL-terminated pathname the entry describes (relative to the root of + the repository, i.e. full pathname); + + - Three NUL-terminated ASCII octal numbers, entry mode of entries in + stage 1 to 3 (a missing stage is represented by "0" in this field); + and + + - At most three 160-bit object names of the entry in stages from 1 to 3 + (nothing is written for a missing stage). + diff --git a/Documentation/user-manual.txt b/Documentation/user-manual.txt index fecc4eb5b3..f13a846131 100644 --- a/Documentation/user-manual.txt +++ b/Documentation/user-manual.txt @@ -344,7 +344,8 @@ Examining branches from a remote repository 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]: ------------------------------------------------ @@ -359,13 +360,23 @@ $ git branch -r 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. @@ -397,7 +408,7 @@ is usually a shortcut for the HEAD branch in the repository "origin". For the complete list of paths which git checks for references, and the order it uses to decide which to choose when there are multiple references with the same shorthand name, see the "SPECIFYING -REVISIONS" section of linkgit:gitrevisions[1]. +REVISIONS" section of linkgit:gitrevisions[7]. [[Updating-a-repository-With-git-fetch]] Updating a repository with git fetch @@ -435,7 +446,7 @@ linux-nfs/master 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 @@ -568,7 +579,7 @@ We have seen several ways of naming commits already: - HEAD: refers to the head of the current branch There are many more; see the "SPECIFYING REVISIONS" section of the -linkgit:gitrevisions[1] man page for the complete list of ways to +linkgit:gitrevisions[7] man page for the complete list of ways to name revisions. Some examples: ------------------------------------------------- @@ -909,7 +920,7 @@ commits reachable from some head but not from any tag in the repository: $ gitk $( git show-ref --heads ) --not $( git show-ref --tags ) ------------------------------------------------- -(See linkgit:gitrevisions[1] for explanations of commit-selecting +(See linkgit:gitrevisions[7] for explanations of commit-selecting syntax such as `--not`.) [[making-a-release]] @@ -955,7 +966,7 @@ echo "git diff --stat --summary -M v$last v$new > ../diffstat-$new" and then he just cut-and-pastes the output commands after verifying that they look OK. -[[Finding-comments-With-given-Content]] +[[Finding-commits-With-given-Content]] Finding commits referencing a file with given content ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1635,7 +1646,7 @@ you've checked out. The reflogs are kept by default for 30 days, after which they may be pruned. See linkgit:git-reflog[1] and linkgit:git-gc[1] to learn how to control this pruning, and see the "SPECIFYING REVISIONS" -section of linkgit:gitrevisions[1] for details. +section of linkgit:gitrevisions[7] for details. Note that the reflog history is very different from normal git history. While normal history is shared by every repository that works on the @@ -1700,7 +1711,7 @@ may wish to check the original repository for updates and merge them 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: @@ -1716,15 +1727,21 @@ one step: $ 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 @@ -2171,11 +2188,14 @@ $ git push mytree release Now to apply some patches from the community. Think of a short snappy name for a branch to hold this patch (or related group of -patches), and create a new branch from the current tip of Linus's -branch: +patches), and create a new branch from a recent stable tag of +Linus's branch. Picking a stable base for your branch will: +1) help you: by avoiding inclusion of unrelated and perhaps lightly +tested changes +2) help future bug hunters that use "git bisect" to find problems ------------------------------------------------- -$ git checkout -b speed-up-spinlocks origin +$ git checkout -b speed-up-spinlocks v2.6.35 ------------------------------------------------- Now you apply the patch(es), run some tests, and commit the change(s). If @@ -2439,9 +2459,9 @@ You have performed no merges into mywork, so it is just a simple linear sequence of patches on top of "origin": ................................................ - o--o--o <-- origin + o--o--O <-- origin \ - o--o--o <-- mywork + a--b--c <-- mywork ................................................ Some more interesting work has been done in the upstream project, and @@ -2797,8 +2817,8 @@ Be aware that commits that the old version of example/master pointed at 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 @@ -3850,7 +3870,7 @@ You create a commit object by giving it the tree that describes the state at the time of the commit, and a list of parents: ------------------------------------------------- -$ git commit-tree <tree> -p <parent> [-p <parent2> ..] +$ git commit-tree <tree> -p <parent> [(-p <parent2>)...] ------------------------------------------------- and then giving the reason for the commit on stdin (either through diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index ac9c4dbf2f..cbd6e34644 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v1.7.3 +DEF_VER=v1.7.4.3 LF=' ' @@ -67,10 +67,10 @@ Issues of note: - A POSIX-compliant shell is required to run many scripts needed for everyday use (e.g. "bisect", "pull"). - - "Perl" is needed to use some of the features (e.g. preparing a - partial commit using "git add -i/-p", interacting with svn - repositories with "git svn"). If you can live without these, use - NO_PERL. + - "Perl" version 5.8 or later is needed to use some of the + features (e.g. preparing a partial commit using "git add -i/-p", + interacting with svn repositories with "git svn"). If you can + live without these, use NO_PERL. - "openssl" library is used by git-imap-send to use IMAP over SSL. If you don't need it, use NO_OPENSSL. @@ -122,8 +122,9 @@ Issues of note: 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 @@ -70,6 +70,11 @@ all:: # # 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 @@ -162,13 +167,13 @@ all:: # 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 @@ -270,6 +275,7 @@ STRIP ?= strip # 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. @@ -288,9 +294,11 @@ htmldir = share/doc/git-doc 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= @@ -390,12 +398,15 @@ SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \ $(patsubst %.py,%,$(SCRIPT_PYTHON)) \ git-instaweb +ETAGS_TARGET = TAGS + # Empty... EXTRA_PROGRAMS = # ... 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 @@ -420,9 +431,11 @@ TEST_PROGRAMS_NEED_X += test-run-command 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)) @@ -491,6 +504,9 @@ LIB_H += compat/bswap.h 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 @@ -512,6 +528,7 @@ LIB_H += mailmap.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 @@ -602,6 +619,7 @@ LIB_OBJS += merge-recursive.o 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 @@ -657,6 +675,7 @@ LIB_OBJS += write_or_die.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 @@ -723,6 +742,8 @@ BUILTIN_OBJS += builtin/read-tree.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 @@ -841,6 +862,7 @@ ifeq ($(uname_S),SunOS) NO_MKDTEMP = YesPlease NO_MKSTEMPS = YesPlease NO_REGEX = YesPlease + NO_FNMATCH_CASEFOLD = YesPlease ifeq ($(uname_R),5.6) SOCKLEN_T = int NO_HSTRERROR = YesPlease @@ -983,6 +1005,7 @@ ifeq ($(uname_S),IRIX) # 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 @@ -1002,6 +1025,7 @@ ifeq ($(uname_S),IRIX64) # 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 @@ -1018,6 +1042,7 @@ ifeq ($(uname_S),HP-UX) NO_UNSETENV = YesPlease NO_HSTRERROR = YesPlease NO_SYS_SELECT_H = YesPlease + NO_FNMATCH_CASEFOLD = YesPlease SNPRINTF_RETURNS_BOGUS = YesPlease NO_NSEC = YesPlease ifeq ($(uname_R),B.11.00) @@ -1047,6 +1072,7 @@ ifeq ($(uname_S),Windows) NO_STRCASESTR = YesPlease NO_STRLCPY = YesPlease NO_STRTOK_R = YesPlease + NO_FNMATCH = YesPlease NO_MEMMEM = YesPlease # NEEDS_LIBICONV = YesPlease NO_ICONV = YesPlease @@ -1059,7 +1085,6 @@ ifeq ($(uname_S),Windows) 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 @@ -1070,16 +1095,19 @@ ifeq ($(uname_S),Windows) 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 @@ -1091,6 +1119,25 @@ else 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 @@ -1102,6 +1149,7 @@ ifneq (,$(findstring MINGW,$(uname_S))) NO_STRCASESTR = YesPlease NO_STRLCPY = YesPlease NO_STRTOK_R = YesPlease + NO_FNMATCH = YesPlease NO_MEMMEM = YesPlease NEEDS_LIBICONV = YesPlease OLD_ICONV = YesPlease @@ -1112,7 +1160,6 @@ ifneq (,$(findstring MINGW,$(uname_S))) 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 @@ -1122,10 +1169,15 @@ ifneq (,$(findstring MINGW,$(uname_S))) NO_REGEX = YesPlease NO_PYTHON = YesPlease BLK_SHA1 = YesPlease - COMPAT_CFLAGS += -D__USE_MINGW_ACCESS -DNOGDI -Icompat -Icompat/fnmatch -Icompat/win32 + ETAGS_TARGET = ETAGS + 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 @@ -1240,9 +1292,6 @@ ifdef ZLIB_PATH endif EXTLIBS += -lz -ifndef NO_POSIX_ONLY_PROGRAMS - PROGRAM_OBJS += daemon.o -endif ifndef NO_OPENSSL OPENSSL_LIBSSL = -lssl ifdef OPENSSLDIR @@ -1259,11 +1308,15 @@ else BLK_SHA1 = 1 OPENSSL_LIBSSL = endif +ifdef NO_OPENSSL + LIB_4_CRYPTO = +else ifdef NEEDS_SSL_WITH_CRYPTO LIB_4_CRYPTO = $(OPENSSL_LINK) -lcrypto -lssl else LIB_4_CRYPTO = $(OPENSSL_LINK) -lcrypto endif +endif ifdef NEEDS_LIBICONV ifdef ICONVDIR BASIC_CFLAGS += -I$(ICONVDIR)/include @@ -1336,6 +1389,17 @@ ifdef NO_STRTOK_R 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 @@ -1354,6 +1418,15 @@ endif 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 @@ -1391,9 +1464,11 @@ endif 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 @@ -1408,6 +1483,10 @@ ifdef NO_DEFLATE_BOUND 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 @@ -1512,14 +1591,15 @@ ifndef V 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)) @@ -1604,6 +1684,8 @@ git$X: git.o $(BUILTIN_OBJS) $(GITLIBS) $(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)"' \ @@ -1759,6 +1841,8 @@ XDIFF_OBJS = xdiff/xdiffi.o xdiff/xprepare.o xdiff/xutils.o xdiff/xemit.o \ 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) @@ -1867,25 +1951,26 @@ 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 +http.o http-walker.o http-push.o http-fetch.o remote-curl.o: http.h url.h xdiff-interface.o $(XDIFF_OBJS): \ 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 = \ @@ -1898,6 +1983,8 @@ builtin/init-db.s builtin/init-db.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 @@ -1918,7 +2005,7 @@ git-%$X: %.o $(GITLIBS) git-imap-send$X: imap-send.o $(GITLIBS) $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ - $(LIBS) $(OPENSSL_LINK) $(OPENSSL_LIBSSL) + $(LIBS) $(OPENSSL_LINK) $(OPENSSL_LIBSSL) $(LIB_4_CRYPTO) git-http-fetch$X: revision.o http.o http-walker.o http-fetch.o $(GITLIBS) $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ @@ -1961,11 +2048,11 @@ info: pdf: $(MAKE) -C Documentation pdf -TAGS: - $(RM) TAGS - $(FIND) . -name '*.[hcS]' -print | xargs etags -a +$(ETAGS_TARGET): FORCE + $(RM) $(ETAGS_TARGET) + $(FIND) . -name '*.[hcS]' -print | xargs etags -a -o $(ETAGS_TARGET) -tags: +tags: FORCE $(RM) tags $(FIND) . -name '*.[hcS]' -print | xargs ctags -a @@ -1974,7 +2061,7 @@ cscope: $(FIND) . -name '*.[hcS]' -print | xargs cscope -b ### Detect prefix changes -TRACK_CFLAGS = $(subst ','\'',$(ALL_CFLAGS)):\ +TRACK_CFLAGS = $(CC):$(subst ','\'',$(ALL_CFLAGS)):\ $(bindir_SQ):$(gitexecdir_SQ):$(template_dir_SQ):$(prefix_SQ) GIT-CFLAGS: FORCE @@ -2235,7 +2322,7 @@ clean: $(RM) $(TEST_PROGRAMS) $(RM) -r bin-wrappers $(RM) -r $(dep_dirs) - $(RM) *.spec *.pyc *.pyo */*.pyc */*.pyo common-cmds.h TAGS tags cscope* + $(RM) *.spec *.pyc *.pyo */*.pyc */*.pyo common-cmds.h $(ETAGS_TARGET) tags cscope* $(RM) -r autom4te.cache $(RM) config.log config.mak.autogen config.mak.append config.status config.cache $(RM) -r $(GIT_TARNAME) .doc-tmp-dir @@ -2259,7 +2346,7 @@ endif .PHONY: all install clean strip .PHONY: shell_compatibility_test please_set_SHELL_PATH_to_a_more_modern_shell -.PHONY: FORCE TAGS tags cscope +.PHONY: FORCE cscope ### Check documentation # @@ -1 +1 @@ -Documentation/RelNotes/1.7.3.txt
\ No newline at end of file +Documentation/RelNotes/1.7.4.3.txt
\ No newline at end of file @@ -24,6 +24,10 @@ const char *make_absolute_path(const char *path) char *last_elem = NULL; struct stat st; + /* We've already done it */ + if (path == buf || path == next_buf) + return path; + if (strlcpy(buf, path, PATH_MAX) >= PATH_MAX) die ("Too long path: %.*s", 60, path); @@ -108,10 +112,14 @@ const char *make_nonrelative_path(const char *path) 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 d399de2671..f11bc7ed7d 100644 --- a/aclocal.m4 +++ b/aclocal.m4 @@ -13,7 +13,7 @@ AC_DEFUN([TYPE_SOCKLEN_T], 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> @@ -21,7 +21,7 @@ AC_DEFUN([TYPE_SOCKLEN_T], ],[ $t len; getpeername(0,0,&len); - ],[ + ])],[ git_cv_socklen_t_equiv="$t" break 2 ]) @@ -7,9 +7,9 @@ #include "unpack-trees.h" static char const * const archive_usage[] = { - "git archive [options] <tree-ish> [path...]", + "git archive [options] <tree-ish> [<path>...]", "git archive --list", - "git archive --remote <repo> [--exec <cmd>] [options] <tree-ish> [path...]", + "git archive --remote <repo> [--exec <cmd>] [options] <tree-ish> [<path>...]", "git archive --remote <repo> [--exec <cmd>] --list", NULL }; @@ -314,7 +314,7 @@ static int parse_archive_args(int argc, const char **argv, "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), @@ -1,5 +1,6 @@ #define NO_THE_INDEX_COMPATIBILITY_MACROS #include "cache.h" +#include "exec_cmd.h" #include "attr.h" const char git_attr__true[] = "(builtin)true"; @@ -10,6 +11,8 @@ static const char git_attr__unknown[] = "(builtin)unknown"; #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. @@ -462,6 +465,32 @@ static void drop_attr_stack(void) } } +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) { @@ -472,6 +501,25 @@ static void bootstrap_attr_stack(void) 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(""); @@ -499,7 +547,9 @@ static void prepare_attr_stack(const char *path, int dirlen) /* * 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 @@ -175,9 +175,14 @@ void create_branch(const char *head, die("Cannot setup tracking information; starting point is not a branch."); break; case 1: - /* Unique completion -- good, only if it is a real ref */ - if (explicit_tracking && !strcmp(real_ref, "HEAD")) - die("Cannot setup tracking information; starting point is not a branch."); + /* Unique completion -- good, only if it is a real branch */ + if (prefixcmp(real_ref, "refs/heads/") && + prefixcmp(real_ref, "refs/remotes/")) { + if (explicit_tracking) + die("Cannot setup tracking information; starting point is not a branch."); + else + real_ref = NULL; + } break; default: die("Ambiguous object name: '%s'.", start_name); @@ -22,8 +22,8 @@ void create_branch(const char *head, const char *name, const char *start_name, 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); @@ -7,15 +7,16 @@ #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; @@ -35,7 +36,7 @@ void finish_copy_notes_for_rewrite(struct notes_rewrite_cfg *c); extern int check_pager_config(const char *cmd); -extern int textconv_object(const char *path, const unsigned char *sha1, char **buf, unsigned long *buf_size); +extern int textconv_object(const char *path, unsigned mode, const unsigned char *sha1, char **buf, unsigned long *buf_size); extern int cmd_add(int argc, const char **argv, const char *prefix); extern int cmd_annotate(int argc, const char **argv, const char *prefix); @@ -107,6 +108,8 @@ extern int cmd_read_tree(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_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_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); diff --git a/builtin/add.c b/builtin/add.c index 56a4e0af6b..1d74763f58 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -21,8 +21,7 @@ static const char * const builtin_add_usage[] = { static int patch_interactive, add_interactive, edit_interactive; static int take_worktree_changes; -struct update_callback_data -{ +struct update_callback_data { int flags; int add_errors; }; @@ -313,13 +312,13 @@ static int verbose = 0, show_only = 0, ignored_too = 0, refresh_only = 0; 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"), @@ -331,7 +330,8 @@ static struct option builtin_add_options[] = { static int add_config(const char *var, const char *value, void *cb) { - if (!strcasecmp(var, "add.ignore-errors")) { + if (!strcasecmp(var, "add.ignoreerrors") || + !strcasecmp(var, "add.ignore-errors")) { ignore_add_errors = git_config_bool(var, value); return 0; } @@ -446,7 +446,8 @@ int cmd_add(int argc, const char **argv, const char *prefix) 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 23c18c573b..36e150768b 100644 --- a/builtin/apply.c +++ b/builtin/apply.c @@ -204,6 +204,7 @@ struct line { unsigned hash : 24; unsigned flag : 8; #define LINE_COMMON 1 +#define LINE_PATCHED 2 }; /* @@ -449,7 +450,7 @@ static char *find_name_gnu(const char *line, char *def, int p_value) return squash_slash(strbuf_detach(&name, NULL)); } -static size_t tz_len(const char *line, size_t len) +static size_t sane_tz_len(const char *line, size_t len) { const char *tz, *p; @@ -467,6 +468,24 @@ static size_t tz_len(const char *line, size_t len) return line + len - tz; } +static size_t tz_with_colon_len(const char *line, size_t len) +{ + const char *tz, *p; + + if (len < strlen(" +08:00") || line[len - strlen(":00")] != ':') + return 0; + tz = line + len - strlen(" +08:00"); + + if (tz[0] != ' ' || (tz[1] != '+' && tz[1] != '-')) + return 0; + p = tz + 2; + if (!isdigit(*p++) || !isdigit(*p++) || *p++ != ':' || + !isdigit(*p++) || !isdigit(*p++)) + return 0; + + return line + len - tz; +} + static size_t date_len(const char *line, size_t len) { const char *date, *p; @@ -561,7 +580,9 @@ static size_t diff_timestamp_len(const char *line, size_t len) if (!isdigit(end[-1])) return 0; - n = tz_len(line, end - line); + n = sane_tz_len(line, end - line); + if (!n) + n = tz_with_colon_len(line, end - line); end -= n; n = short_time_len(line, end - line); @@ -733,8 +754,8 @@ static int has_epoch_timestamp(const char *nameline) " " "[0-2][0-9]:[0-5][0-9]:00(\\.0+)?" " " - "([-+][0-2][0-9][0-5][0-9])\n"; - const char *timestamp = NULL, *cp; + "([-+][0-2][0-9]:?[0-5][0-9])\n"; + const char *timestamp = NULL, *cp, *colon; static regex_t *stamp; regmatch_t m[10]; int zoneoffset; @@ -764,8 +785,11 @@ static int has_epoch_timestamp(const char *nameline) return 0; } - zoneoffset = strtol(timestamp + m[3].rm_so + 1, NULL, 10); - zoneoffset = (zoneoffset / 100) * 60 + (zoneoffset % 100); + zoneoffset = strtol(timestamp + m[3].rm_so + 1, (char **) &colon, 10); + if (*colon == ':') + zoneoffset = zoneoffset * 60 + strtol(colon + 1, NULL, 10); + else + zoneoffset = (zoneoffset / 100) * 60 + (zoneoffset % 100); if (timestamp[m[3].rm_so] == '-') zoneoffset = -zoneoffset; @@ -919,28 +943,28 @@ static int gitdiff_newfile(const char *line, struct patch *patch) static int gitdiff_copysrc(const char *line, struct patch *patch) { patch->is_copy = 1; - patch->old_name = find_name(line, NULL, 0, 0); + patch->old_name = find_name(line, NULL, p_value ? p_value - 1 : 0, 0); return 0; } static int gitdiff_copydst(const char *line, struct patch *patch) { patch->is_copy = 1; - patch->new_name = find_name(line, NULL, 0, 0); + patch->new_name = find_name(line, NULL, p_value ? p_value - 1 : 0, 0); return 0; } static int gitdiff_renamesrc(const char *line, struct patch *patch) { patch->is_rename = 1; - patch->old_name = find_name(line, NULL, 0, 0); + patch->old_name = find_name(line, NULL, p_value ? p_value - 1 : 0, 0); return 0; } static int gitdiff_renamedst(const char *line, struct patch *patch) { patch->is_rename = 1; - patch->new_name = find_name(line, NULL, 0, 0); + patch->new_name = find_name(line, NULL, p_value ? p_value - 1 : 0, 0); return 0; } @@ -1025,7 +1049,7 @@ static char *git_header_name(char *line, int llen) { const char *name; const char *second = NULL; - size_t len; + size_t len, line_len; line += strlen("diff --git "); llen -= strlen("diff --git "); @@ -1125,6 +1149,10 @@ static char *git_header_name(char *line, int llen) * Accept a name only if it shows up twice, exactly the same * form. */ + second = strchr(name, '\n'); + if (!second) + return NULL; + line_len = second - name; for (len = 0 ; ; len++) { switch (name[len]) { default: @@ -1132,15 +1160,11 @@ static char *git_header_name(char *line, int llen) case '\n': return NULL; case '\t': case ' ': - second = name+len; - for (;;) { - char c = *second++; - if (c == '\n') - return NULL; - if (c == '/') - break; - } - if (second[len] == '\n' && !memcmp(name, second, len)) { + second = stop_at_slash(name + len, line_len - len); + if (!second) + return NULL; + second++; + if (second[len] == '\n' && !strncmp(name, second, len)) { return xmemdupz(name, len); } } @@ -2062,7 +2086,8 @@ static int match_fragment(struct image *img, /* Quick hash check */ for (i = 0; i < preimage_limit; i++) - if (preimage->line[i].hash != img->line[try_lno + i].hash) + if ((img->line[try_lno + i].flag & LINE_PATCHED) || + (preimage->line[i].hash != img->line[try_lno + i].hash)) return 0; if (preimage_limit == preimage->nr) { @@ -2405,11 +2430,15 @@ static void update_image(struct image *img, memcpy(img->line + applied_pos, postimage->line, postimage->nr * sizeof(*img->line)); + for (i = 0; i < postimage->nr; i++) + img->line[applied_pos + i].flag |= LINE_PATCHED; + img->nr = nr; } static int apply_one_fragment(struct image *img, struct fragment *frag, - int inaccurate_eof, unsigned ws_rule) + int inaccurate_eof, unsigned ws_rule, + int nth_fragment) { int match_beginning, match_end; const char *patch = frag->patch; @@ -2615,6 +2644,15 @@ static int apply_one_fragment(struct image *img, struct fragment *frag, apply = 0; } + if (apply_verbosely && applied_pos != pos) { + int offset = applied_pos - pos; + if (apply_in_reverse) + offset = 0 - offset; + fprintf(stderr, + "Hunk #%d succeeded at %d (offset %d lines).\n", + nth_fragment, applied_pos + 1, offset); + } + /* * Warn if it was necessary to reduce the number * of context lines. @@ -2645,6 +2683,12 @@ static int apply_binary_fragment(struct image *img, struct patch *patch) unsigned long len; void *dst; + if (!fragment) + return error("missing binary patch data for '%s'", + patch->new_name ? + patch->new_name : + patch->old_name); + /* Binary patch is irreversible without the optional second hunk */ if (apply_in_reverse) { if (!fragment->next) @@ -2756,12 +2800,14 @@ static int apply_fragments(struct image *img, struct patch *patch) const char *name = patch->old_name ? patch->old_name : patch->new_name; unsigned ws_rule = patch->ws_rule; unsigned inaccurate_eof = patch->inaccurate_eof; + int nth = 0; if (patch->is_binary) return apply_binary(img, patch); while (frag) { - if (apply_one_fragment(img, frag, inaccurate_eof, ws_rule)) { + nth++; + if (apply_one_fragment(img, frag, inaccurate_eof, ws_rule, nth)) { error("patch failed: %s:%ld", name, frag->oldpos); if (!apply_with_reject) return -1; @@ -3843,7 +3889,7 @@ int cmd_apply(int argc, const char **argv, const char *prefix_) "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 101535448f..f6b03f750a 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -83,6 +83,7 @@ struct origin { struct commit *commit; mmfile_t file; unsigned char blob_sha1[20]; + unsigned mode; char path[FLEX_ARRAY]; }; @@ -92,6 +93,7 @@ struct origin { * Return 1 if the conversion succeeds, 0 otherwise. */ int textconv_object(const char *path, + unsigned mode, const unsigned char *sha1, char **buf, unsigned long *buf_size) @@ -100,7 +102,7 @@ int textconv_object(const char *path, struct userdiff_driver *textconv; df = alloc_filespec(path); - fill_filespec(df, sha1, S_IFREG | 0664); + fill_filespec(df, sha1, mode); textconv = get_textconv(df); if (!textconv) { free_filespec(df); @@ -125,7 +127,7 @@ static void fill_origin_blob(struct diff_options *opt, num_read_blob++; if (DIFF_OPT_TST(opt, ALLOW_TEXTCONV) && - textconv_object(o->path, o->blob_sha1, &file->ptr, &file_size)) + textconv_object(o->path, o->mode, o->blob_sha1, &file->ptr, &file_size)) ; else file->ptr = read_sha1_file(o->blob_sha1, &type, &file_size); @@ -313,21 +315,23 @@ static struct origin *get_origin(struct scoreboard *sb, * for an origin is also used to pass the blame for the entire file to * the parent to detect the case where a child's blob is identical to * that of its parent's. + * + * This also fills origin->mode for corresponding tree path. */ -static int fill_blob_sha1(struct origin *origin) +static int fill_blob_sha1_and_mode(struct origin *origin) { - unsigned mode; if (!is_null_sha1(origin->blob_sha1)) return 0; if (get_tree_entry(origin->commit->object.sha1, origin->path, - origin->blob_sha1, &mode)) + origin->blob_sha1, &origin->mode)) goto error_out; if (sha1_object_info(origin->blob_sha1, NULL) != OBJ_BLOB) goto error_out; return 0; error_out: hashclr(origin->blob_sha1); + origin->mode = S_IFINVALID; return -1; } @@ -360,12 +364,14 @@ static struct origin *find_origin(struct scoreboard *sb, /* * If the origin was newly created (i.e. get_origin * would call make_origin if none is found in the - * scoreboard), it does not know the blob_sha1, + * scoreboard), it does not know the blob_sha1/mode, * so copy it. Otherwise porigin was in the - * scoreboard and already knows blob_sha1. + * scoreboard and already knows blob_sha1/mode. */ - if (porigin->refcnt == 1) + if (porigin->refcnt == 1) { hashcpy(porigin->blob_sha1, cached->blob_sha1); + porigin->mode = cached->mode; + } return porigin; } /* otherwise it was not very useful; free it */ @@ -400,6 +406,7 @@ static struct origin *find_origin(struct scoreboard *sb, /* The path is the same as parent */ porigin = get_origin(sb, parent, origin->path); hashcpy(porigin->blob_sha1, origin->blob_sha1); + porigin->mode = origin->mode; } else { /* * Since origin->path is a pathspec, if the parent @@ -425,6 +432,7 @@ static struct origin *find_origin(struct scoreboard *sb, case 'M': porigin = get_origin(sb, parent, origin->path); hashcpy(porigin->blob_sha1, p->one->sha1); + porigin->mode = p->one->mode; break; case 'A': case 'T': @@ -444,6 +452,7 @@ static struct origin *find_origin(struct scoreboard *sb, cached = make_origin(porigin->commit, porigin->path); hashcpy(cached->blob_sha1, porigin->blob_sha1); + cached->mode = porigin->mode; parent->util = cached; } return porigin; @@ -486,6 +495,7 @@ static struct origin *find_rename(struct scoreboard *sb, !strcmp(p->two->path, origin->path)) { porigin = get_origin(sb, parent, p->one->path); hashcpy(porigin->blob_sha1, p->one->sha1); + porigin->mode = p->one->mode; break; } } @@ -1099,6 +1109,7 @@ static int find_copy_in_parent(struct scoreboard *sb, norigin = get_origin(sb, parent, p->one->path); hashcpy(norigin->blob_sha1, p->one->sha1); + norigin->mode = p->one->mode; fill_origin_blob(&sb->revs->diffopt, norigin, &file_p); if (!file_p.ptr) continue; @@ -1301,8 +1312,7 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt) /* * Information on commits, used for output. */ -struct commit_info -{ +struct commit_info { const char *author; const char *author_mail; unsigned long author_time; @@ -1606,6 +1616,7 @@ static const char *format_time(unsigned long time, const char *tz_str, #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) { @@ -1671,12 +1682,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, @@ -1689,9 +1705,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)); @@ -1829,7 +1851,10 @@ static void find_alignment(struct scoreboard *sb, int *option) 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; } @@ -2075,7 +2100,7 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt, switch (st.st_mode & S_IFMT) { case S_IFREG: if (DIFF_OPT_TST(opt, ALLOW_TEXTCONV) && - textconv_object(read_from, null_sha1, &buf.buf, &buf_len)) + textconv_object(read_from, mode, null_sha1, &buf.buf, &buf_len)) buf.len = buf_len; else if (strbuf_read_file(&buf, read_from, st.st_size) != st.st_size) die_errno("cannot open or read '%s'", read_from); @@ -2278,6 +2303,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix) 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"), @@ -2298,8 +2324,8 @@ int cmd_blame(int argc, const char **argv, const char *prefix) 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: @@ -2455,11 +2481,11 @@ parse_done: } else { o = get_origin(&sb, sb.final, path); - if (fill_blob_sha1(o)) + if (fill_blob_sha1_and_mode(o)) die("no such path %s in %s", path, final_commit_name); if (DIFF_OPT_TST(&sb.revs->diffopt, ALLOW_TEXTCONV) && - textconv_object(path, o->blob_sha1, (char **) &sb.final_buf, + textconv_object(path, o->mode, o->blob_sha1, (char **) &sb.final_buf, &sb.final_buf_size)) ; else diff --git a/builtin/branch.c b/builtin/branch.c index 87976f0921..b9ba011f7b 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -134,7 +134,7 @@ static int branch_merged(int kind, const char *name, 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++]); @@ -395,6 +390,30 @@ static int matches_merge_filter(struct commit *commit) return (is_merged == (merge_filter == SHOW_MERGED)); } +static void add_verbose_info(struct strbuf *out, struct ref_item *item, + int verbose, int abbrev) +{ + struct strbuf subject = STRBUF_INIT, stat = STRBUF_INIT; + const char *sub = " **** invalid ref ****"; + struct commit *commit = item->commit; + + if (commit && !parse_commit(commit)) { + struct pretty_print_context ctx = {0}; + pretty_print_commit(CMIT_FMT_ONELINE, commit, + &subject, &ctx); + sub = subject.buf; + } + + if (item->kind == REF_LOCAL_BRANCH) + fill_tracking_info(&stat, item->name, verbose > 1); + + strbuf_addf(out, " %s %s%s", + find_unique_abbrev(item->commit->object.sha1, abbrev), + stat.buf, sub); + strbuf_release(&stat); + strbuf_release(&subject); +} + static void print_ref_item(struct ref_item *item, int maxwidth, int verbose, int abbrev, int current, char *prefix) { @@ -435,27 +454,9 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose, if (item->dest) strbuf_addf(&out, " -> %s", item->dest); - else if (verbose) { - struct strbuf subject = STRBUF_INIT, stat = STRBUF_INIT; - const char *sub = " **** invalid ref ****"; - - commit = item->commit; - if (commit && !parse_commit(commit)) { - struct pretty_print_context ctx = {0}; - pretty_print_commit(CMIT_FMT_ONELINE, commit, - &subject, &ctx); - sub = subject.buf; - } - - if (item->kind == REF_LOCAL_BRANCH) - fill_tracking_info(&stat, item->name, verbose > 1); - - strbuf_addf(&out, " %s %s%s", - find_unique_abbrev(item->commit->object.sha1, abbrev), - stat.buf, sub); - strbuf_release(&stat); - strbuf_release(&subject); - } + else if (verbose) + /* " f7c0c00 [ahead 58, behind 197] vcs-svn: drop obj_pool.h" */ + add_verbose_info(&out, item, verbose, abbrev); printf("%s\n", out.buf); strbuf_release(&name); strbuf_release(&out); @@ -621,7 +622,8 @@ int cmd_branch(int argc, const char **argv, const char *prefix) 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", @@ -651,7 +653,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) 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", @@ -667,6 +669,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix) 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) diff --git a/builtin/bundle.c b/builtin/bundle.c index 80649ba0b2..9b87fb9ac2 100644 --- a/builtin/bundle.c +++ b/builtin/bundle.c @@ -12,8 +12,8 @@ static const char builtin_bundle_usage[] = "git bundle create <file> <git-rev-list args>\n" " or: git bundle verify <file>\n" - " or: git bundle list-heads <file> [refname...]\n" - " or: git bundle unbundle <file> [refname...]"; + " or: git bundle list-heads <file> [<refname>...]\n" + " or: git bundle unbundle <file> [<refname>...]"; int cmd_bundle(int argc, const char **argv, const char *prefix) { diff --git a/builtin/cat-file.c b/builtin/cat-file.c index 76ec3fec92..94632dbdb4 100644 --- a/builtin/cat-file.c +++ b/builtin/cat-file.c @@ -143,7 +143,7 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name) die("git cat-file --textconv %s: <object> must be <sha1:path>", obj_name); - if (!textconv_object(obj_context.path, sha1, &buf, &size)) + if (!textconv_object(obj_context.path, obj_context.mode, sha1, &buf, &size)) die("git cat-file --textconv: unable to run textconv on %s", obj_name); break; diff --git a/builtin/checkout-index.c b/builtin/checkout-index.c index a7a5ee10f3..f1fec24745 100644 --- a/builtin/checkout-index.c +++ b/builtin/checkout-index.c @@ -155,7 +155,7 @@ static void checkout_all(const char *prefix, int prefix_length) } static const char * const builtin_checkout_index_usage[] = { - "git checkout-index [options] [--] <file>...", + "git checkout-index [options] [--] [<file>...]", NULL }; @@ -217,9 +217,9 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix) 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, @@ -241,6 +241,9 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix) 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 560eae1715..e98576f22e 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -161,7 +161,7 @@ static int checkout_merged(int pos, struct checkout *state) * 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); @@ -404,7 +404,7 @@ static int merge_working_tree(struct checkout_opts *opts, 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); @@ -678,7 +678,7 @@ static const char *unique_tracking_name(const char *name) int cmd_checkout(int argc, const char **argv, const char *prefix) { struct checkout_opts opts; - unsigned char rev[20]; + unsigned char rev[20], branch_rev[20]; const char *arg; struct branch_info new; struct tree *source_tree = NULL; @@ -686,21 +686,21 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) 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, "log for new branch"), - OPT_SET_INT('t', "track", &opts.track, "track", + OPT_BOOLEAN('l', NULL, &opts.new_branch_log, "create reflog for new branch"), + 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"), - OPT_SET_INT('2', "ours", &opts.writeout_stage, "stage", + OPT_SET_INT('2', "ours", &opts.writeout_stage, "checkout our version for unmerged files", 2), - OPT_SET_INT('3', "theirs", &opts.writeout_stage, "stage", + OPT_SET_INT('3', "theirs", &opts.writeout_stage, "checkout their version for unmerged files", 3), - OPT_BOOLEAN('f', "force", &opts.force, "force"), - OPT_BOOLEAN('m', "merge", &opts.merge, "merge"), + 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)"), OPT_BOOLEAN('p', "patch", &patch_mode, "select hunks interactively"), @@ -784,9 +784,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) * 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 + * was given, and there is a remote-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. + * 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). @@ -832,18 +832,21 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) argc--; new.name = arg; - if ((new.commit = lookup_commit_reference_gently(rev, 1))) { - setup_branch_path(&new); + 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; + 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 */ + + if (!(new.commit = lookup_commit_reference_gently(rev, 1))) { + /* not a commit */ + source_tree = parse_tree_indirect(rev); + } else { 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); diff --git a/builtin/clean.c b/builtin/clean.c index c8798f549e..4a312abc6b 100644 --- a/builtin/clean.c +++ b/builtin/clean.c @@ -38,7 +38,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix) { int i; int show_only = 0, remove_directories = 0, quiet = 0, ignored = 0; - int ignored_only = 0, baselen = 0, config_set = 0, errors = 0; + int ignored_only = 0, config_set = 0, errors = 0; int rm_flags = REMOVE_DIR_KEEP_NESTED_GIT; struct strbuf directory = STRBUF_INIT; struct dir_struct dir; @@ -48,9 +48,9 @@ int cmd_clean(int argc, const char **argv, const char *prefix) 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", @@ -138,7 +138,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix) if (pathspec) { memset(seen, 0, argc > 0 ? argc : 1); matches = match_pathspec(pathspec, ent->name, len, - baselen, seen); + 0, seen); } if (S_ISDIR(st.st_mode)) { @@ -153,7 +153,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix) printf("Removing %s\n", qname); if (remove_dir_recursively(&directory, rm_flags) != 0) { - warning("failed to remove '%s'", qname); + warning("failed to remove %s", qname); errors++; } } else if (show_only) { @@ -173,7 +173,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix) printf("Removing %s\n", qname); } if (unlink(ent->name) != 0) { - warning("failed to remove '%s'", qname); + warning("failed to remove %s", qname); errors++; } } diff --git a/builtin/clone.c b/builtin/clone.c index 19ed64041d..2ee1fa9846 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -66,8 +66,10 @@ static struct option builtin_clone_options[] = { "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", @@ -411,7 +413,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) 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 87f0591c2f..d083795e26 100644 --- a/builtin/commit-tree.c +++ b/builtin/commit-tree.c @@ -9,7 +9,7 @@ #include "builtin.h" #include "utf8.h" -static const char commit_tree_usage[] = "git commit-tree <sha1> [-p <sha1>]* < changelog"; +static const char commit_tree_usage[] = "git commit-tree <sha1> [(-p <sha1>)...] < changelog"; static void new_parent(struct commit *parent, struct commit_list **parents_p) { @@ -56,10 +56,12 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix) 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 0b6ce2fa37..6e32166a29 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -45,9 +45,9 @@ static const char implicit_ident_advice[] = " git config --global user.name \"Your Name\"\n" " git config --global user.email you@example.com\n" "\n" -"If the identity used for this commit is wrong, you can fix it with:\n" +"After doing this, you may fix the identity used for this commit with:\n" "\n" -" git commit --amend --author='Your Name <you@example.com>'\n"; +" git commit --amend --reset-author\n"; static const char empty_amend_advice[] = "You asked to amend the most recent commit, but doing so would make\n" @@ -69,7 +69,7 @@ static enum { static const char *logfile, *force_author; static const char *template_file; static char *edit_message, *use_message; -static char *author_name, *author_email, *author_date; +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; @@ -114,16 +114,18 @@ static int opt_parse_m(const struct option *opt, const char *arg, int unset) } 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"), @@ -143,12 +145,12 @@ static struct option builtin_commit_options[] = { 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, @@ -459,7 +461,7 @@ static int is_a_merge(const unsigned char *sha1) static const char sign_off_header[] = "Signed-off-by: "; -static void determine_author_info(void) +static void determine_author_info(struct strbuf *author_ident) { char *name, *email, *date; @@ -503,10 +505,8 @@ static void determine_author_info(void) if (force_date) date = force_date; - - author_name = name; - author_email = email; - author_date = date; + strbuf_addstr(author_ident, fmt_ident(name, email, date, + IDENT_ERROR_ON_NO_NAME)); } static int ends_rfc2822_footer(struct strbuf *sb) @@ -550,10 +550,21 @@ static int ends_rfc2822_footer(struct strbuf *sb) return 1; } +static char *cut_ident_timestamp_part(char *string) +{ + char *ket = strrchr(string, '>'); + if (!ket || ket[1] != ' ') + die("Malformed ident string: '%s'", string); + *++ket = '\0'; + return ket; +} + static int prepare_to_commit(const char *index_file, const char *prefix, - struct wt_status *s) + struct wt_status *s, + struct strbuf *author_ident) { struct stat statbuf; + struct strbuf committer_ident = STRBUF_INIT; int commitable, saved_color_setting; struct strbuf sb = STRBUF_INIT; char *buffer; @@ -565,6 +576,25 @@ static int prepare_to_commit(const char *index_file, const char *prefix, 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"; @@ -586,6 +616,16 @@ static int prepare_to_commit(const char *index_file, const char *prefix, 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"); @@ -594,7 +634,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, if (strbuf_read_file(&sb, git_path("SQUASH_MSG"), 0) < 0) die_errno("could not read SQUASH_MSG"); hook_arg1 = "squash"; - } else if (template_file && !stat(template_file, &statbuf)) { + } else if (template_file) { if (strbuf_read_file(&sb, template_file, 0) < 0) die_errno("could not read '%s'", template_file); hook_arg1 = "template"; @@ -607,6 +647,16 @@ static int prepare_to_commit(const char *index_file, const char *prefix, 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)); @@ -637,14 +687,13 @@ static int prepare_to_commit(const char *index_file, const char *prefix, strbuf_release(&sb); - determine_author_info(); + /* This checks and barfs if author is badly specified */ + determine_author_info(author_ident); /* This checks if committer ident is explicitly given */ - git_committer_info(0); + strbuf_addstr(&committer_ident, git_committer_info(0)); if (use_editor && include_status) { - char *author_ident; - const char *committer_ident; - + char *ai_tmp, *ci_tmp; if (in_merge) fprintf(fp, "#\n" @@ -672,23 +721,21 @@ static int prepare_to_commit(const char *index_file, const char *prefix, if (only_include_assumed) fprintf(fp, "# %s\n", only_include_assumed); - author_ident = xstrdup(fmt_name(author_name, author_email)); - committer_ident = fmt_name(getenv("GIT_COMMITTER_NAME"), - getenv("GIT_COMMITTER_EMAIL")); - if (strcmp(author_ident, committer_ident)) + ai_tmp = cut_ident_timestamp_part(author_ident->buf); + ci_tmp = cut_ident_timestamp_part(committer_ident.buf); + if (strcmp(author_ident->buf, committer_ident.buf)) fprintf(fp, "%s" "# Author: %s\n", ident_shown++ ? "" : "#\n", - author_ident); - free(author_ident); + author_ident->buf); if (!user_ident_sufficiently_given()) fprintf(fp, "%s" "# Committer: %s\n", ident_shown++ ? "" : "#\n", - committer_ident); + committer_ident.buf); if (ident_shown) fprintf(fp, "#\n"); @@ -697,6 +744,9 @@ static int prepare_to_commit(const char *index_file, const char *prefix, s->use_color = 0; commitable = run_status(fp, index_file, prefix, 1, s); s->use_color = saved_color_setting; + + *ai_tmp = ' '; + *ci_tmp = ' '; } else { unsigned char sha1[20]; const char *parent = "HEAD"; @@ -712,6 +762,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, else commitable = index_differs_from(parent, 0); } + strbuf_release(&committer_ident); fclose(fp); @@ -863,7 +914,7 @@ static int parse_and_validate_options(int argc, const char *argv[], 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; @@ -878,48 +929,35 @@ static int parse_and_validate_options(int argc, const char *argv[], 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 @@ -929,8 +967,6 @@ static int parse_and_validate_options(int argc, const char *argv[], */ if (use_message_buffer == NULL) use_message_buffer = xstrdup(commit->buffer); - if (enc != utf8) - free(enc); } if (!!also + !!only + !!all + !!interactive > 1) @@ -984,6 +1020,8 @@ static int parse_status_slot(const char *var, int offset) { 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; @@ -1048,13 +1086,13 @@ int cmd_status(int argc, const char **argv, const char *prefix) 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"), @@ -1070,6 +1108,9 @@ int cmd_status(int argc, const char **argv, const char *prefix) 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; @@ -1241,6 +1282,7 @@ static int run_rewrite_hook(const unsigned char *oldsha1, int cmd_commit(int argc, const char **argv, const char *prefix) { struct strbuf sb = STRBUF_INIT; + struct strbuf author_ident = STRBUF_INIT; const char *index_file, *reflog_msg; char *nl, *p; unsigned char commit_sha1[20]; @@ -1250,6 +1292,9 @@ int cmd_commit(int argc, const char **argv, const char *prefix) 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")); @@ -1268,7 +1313,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) /* Set up everything for writing the commit object. This includes running hooks, writing the trees, and interacting with the user. */ - if (!prepare_to_commit(index_file, prefix, &s)) { + if (!prepare_to_commit(index_file, prefix, &s, &author_ident)) { rollback_index_files(); return 1; } @@ -1347,11 +1392,11 @@ int cmd_commit(int argc, const char **argv, const char *prefix) } if (commit_tree(sb.buf, active_cache_tree->sha1, parents, commit_sha1, - fmt_ident(author_name, author_email, author_date, - IDENT_ERROR_ON_NO_NAME))) { + author_ident.buf)) { rollback_index_files(); die("failed to write commit object"); } + strbuf_release(&author_ident); ref_lock = lock_any_ref_for_update("HEAD", initial_commit ? NULL : head_sha1, diff --git a/builtin/config.c b/builtin/config.c index ca4a0db4a7..f87443698a 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -52,7 +52,7 @@ static struct option builtin_config_options[] = { 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), @@ -153,7 +153,6 @@ static int show_config(const char *key_, const char *value_, void *cb) 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; @@ -167,18 +166,32 @@ static int get_value(const char *key_, const char *regex_) 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_) { diff --git a/builtin/count-objects.c b/builtin/count-objects.c index 2bdd8ebde1..c37cb98c31 100644 --- a/builtin/count-objects.c +++ b/builtin/count-objects.c @@ -79,7 +79,7 @@ int cmd_count_objects(int argc, const char **argv, const char *prefix) 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 43caff2ffe..4afd1504a6 100644 --- a/builtin/describe.c +++ b/builtin/describe.c @@ -6,6 +6,7 @@ #include "exec_cmd.h" #include "parse-options.h" #include "diff.h" +#include "hash.h" #define SEEN (1u<<0) #define MAX_TAGS (FLAG_BITS - 1) @@ -20,9 +21,10 @@ static int debug; /* Display lots of verbose info */ static int all; /* Any valid ref can be used */ static int tags; /* Allow lightweight tags */ static int longformat; -static int abbrev = DEFAULT_ABBREV; +static int abbrev = -1; /* unspecified */ static int max_candidates = 10; -static int found_names; +static struct hash_table names; +static int have_util; static const char *pattern; static int always; static const char *dirty; @@ -34,16 +36,44 @@ static const char *diff_index_args[] = { struct commit_name { + struct commit_name *next; + unsigned char peeled[20]; struct tag *tag; unsigned prio:2; /* annotated tag = 2, tag = 1, head = 0 */ unsigned name_checked:1; unsigned char sha1[20]; - char path[FLEX_ARRAY]; /* more */ + const char *path; }; static const char *prio_names[] = { "head", "lightweight", "annotated", }; +static inline unsigned int hash_sha1(const unsigned char *sha1) +{ + unsigned int hash; + memcpy(&hash, sha1, sizeof(hash)); + return hash; +} + +static inline struct commit_name *find_commit_name(const unsigned char *peeled) +{ + struct commit_name *n = lookup_hash(hash_sha1(peeled), &names); + while (n && !!hashcmp(peeled, n->peeled)) + n = n->next; + return n; +} + +static int set_util(void *chain, void *data) +{ + struct commit_name *n; + for (n = chain; n; n = n->next) { + struct commit *c = lookup_commit_reference_gently(n->peeled, 1); + if (c) + c->util = n; + } + return 0; +} + static int replace_name(struct commit_name *e, int prio, const unsigned char *sha1, @@ -78,31 +108,36 @@ static int replace_name(struct commit_name *e, } static void add_to_known_names(const char *path, - struct commit *commit, + const unsigned char *peeled, int prio, const unsigned char *sha1) { - struct commit_name *e = commit->util; + struct commit_name *e = find_commit_name(peeled); struct tag *tag = NULL; if (replace_name(e, prio, sha1, &tag)) { - size_t len = strlen(path)+1; - free(e); - e = xmalloc(sizeof(struct commit_name) + len); + if (!e) { + void **pos; + e = xmalloc(sizeof(struct commit_name)); + hashcpy(e->peeled, peeled); + pos = insert_hash(hash_sha1(peeled), e, &names); + if (pos) { + e->next = *pos; + *pos = e; + } else { + e->next = NULL; + } + } e->tag = tag; e->prio = prio; e->name_checked = 0; hashcpy(e->sha1, sha1); - memcpy(e->path, path, len); - commit->util = e; + e->path = path; } - found_names = 1; } static int get_name(const char *path, const unsigned char *sha1, int flag, void *cb_data) { int might_be_tag = !prefixcmp(path, "refs/tags/"); - struct commit *commit; - struct object *object; unsigned char peeled[20]; int is_tag, prio; @@ -110,16 +145,10 @@ static int get_name(const char *path, const unsigned char *sha1, int flag, void return 0; if (!peel_ref(path, peeled) && !is_null_sha1(peeled)) { - commit = lookup_commit_reference_gently(peeled, 1); - if (!commit) - return 0; - is_tag = !!hashcmp(sha1, commit->object.sha1); + is_tag = !!hashcmp(sha1, peeled); } else { - commit = lookup_commit_reference_gently(sha1, 1); - object = parse_object(sha1); - if (!commit || !object) - return 0; - is_tag = object->type == OBJ_TAG; + hashcpy(peeled, sha1); + is_tag = 0; } /* If --all, then any refs are used. @@ -142,7 +171,7 @@ static int get_name(const char *path, const unsigned char *sha1, int flag, void if (!prio) return 0; } - add_to_known_names(all ? path + 5 : path + 10, commit, prio, sha1); + add_to_known_names(all ? path + 5 : path + 10, peeled, prio, sha1); return 0; } @@ -189,7 +218,7 @@ static unsigned long finish_depth_computation( 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; } @@ -240,7 +269,7 @@ static void describe(const char *arg, int last_one) if (!cmit) die("%s is not a valid '%s' object", arg, commit_type); - n = cmit->util; + n = find_commit_name(cmit->object.sha1); if (n && (tags || all || n->prio == 2)) { /* * Exact match to an existing ref. @@ -259,6 +288,11 @@ static void describe(const char *arg, int last_one) if (debug) fprintf(stderr, "searching to describe %s\n", arg); + if (!have_util) { + for_each_hash(&names, set_util, NULL); + have_util = 1; + } + list = NULL; cmit->object.flags = SEEN; commit_list_insert(cmit, &list); @@ -300,7 +334,7 @@ static void describe(const char *arg, int last_one) 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; } @@ -328,7 +362,7 @@ static void describe(const char *arg, int last_one) 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]); @@ -386,7 +420,11 @@ int cmd_describe(int argc, const char **argv, const char *prefix) OPT_END(), }; + git_config(git_default_config, NULL); argc = parse_options(argc, argv, prefix, options, describe_usage, 0); + if (abbrev < 0) + abbrev = DEFAULT_ABBREV; + if (max_candidates < 0) max_candidates = 0; else if (max_candidates > MAX_TAGS) @@ -418,8 +456,9 @@ int cmd_describe(int argc, const char **argv, const char *prefix) return cmd_name_rev(i + argc, args, prefix); } - for_each_ref(get_name, NULL); - if (!found_names && !always) + init_hash(&names); + for_each_rawref(get_name, NULL); + if (!names.nr && !always) die("No names found, cannot describe anything."); if (argc == 0) { diff --git a/builtin/diff.c b/builtin/diff.c index bab4bd9f57..d4d80c982e 100644 --- a/builtin/diff.c +++ b/builtin/diff.c @@ -22,7 +22,7 @@ struct blobinfo { }; 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, @@ -325,8 +325,11 @@ int cmd_diff(int argc, const char **argv, const char *prefix) 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; } } diff --git a/builtin/fast-export.c b/builtin/fast-export.c index c8fd46b872..b18fc85c4c 100644 --- a/builtin/fast-export.c +++ b/builtin/fast-export.c @@ -619,9 +619,9 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) 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"), diff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c index dbd8b7bcc8..b999413934 100644 --- a/builtin/fetch-pack.c +++ b/builtin/fetch-pack.c @@ -47,7 +47,7 @@ static void rev_list_push(struct commit *commit, int mark) 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++; @@ -436,7 +436,7 @@ static int mark_complete(const char *path, const unsigned char *sha1, int flag, 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 6fc5047703..7efecfe1cf 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -12,11 +12,12 @@ #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>...]]", "git fetch [<options>] <group>", - "git fetch --multiple [<options>] [<repository> | <group>]...", + "git fetch --multiple [<options>] [(<repository> | <group>)...]", "git fetch --all [<options>]", NULL }; @@ -27,13 +28,20 @@ enum { 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), @@ -41,10 +49,9 @@ static struct option builtin_fetch_options[] = { "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, @@ -52,15 +59,20 @@ static struct option builtin_fetch_options[] = { 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() }; @@ -98,7 +110,7 @@ static void add_merge_config(struct ref **head, 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 @@ -359,7 +371,7 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, what = rm->name + 10; } else if (!prefixcmp(rm->name, "refs/remotes/")) { - kind = "remote branch"; + kind = "remote-tracking branch"; what = rm->name + 13; } else { @@ -784,28 +796,36 @@ static int add_remote_or_group(const char *name, struct string_list *list) 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(); @@ -926,6 +946,21 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) } } + 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); diff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c index e7e12eea25..5189b16c9e 100644 --- a/builtin/fmt-merge-msg.c +++ b/builtin/fmt-merge-msg.c @@ -7,21 +7,22 @@ #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; } @@ -99,8 +100,8 @@ static int handle_line(char *line) 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 { @@ -232,7 +233,7 @@ static void do_fmt_merge_msg_title(struct strbuf *out, 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) { @@ -255,9 +256,9 @@ static void do_fmt_merge_msg_title(struct strbuf *out, 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; @@ -288,7 +289,7 @@ static int do_fmt_merge_msg(int merge_title, int merge_summary, if (merge_title) do_fmt_merge_msg_title(out, current_branch); - if (merge_summary) { + if (shortlog_len) { struct commit *head; struct rev_info rev; @@ -303,17 +304,14 @@ static int do_fmt_merge_msg(int merge_title, int merge_summary, 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) @@ -321,10 +319,13 @@ 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"), @@ -340,12 +341,14 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix) 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"); @@ -355,12 +358,13 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix) 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 0929c7f245..795aba087f 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -74,7 +74,13 @@ static int mark_object(struct object *obj, int type, void *data) { 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", @@ -84,6 +90,7 @@ static int mark_object(struct object *obj, int type, void *data) } 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) @@ -109,7 +116,7 @@ static void mark_object_reachable(struct object *obj) 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; @@ -138,7 +145,7 @@ static int traverse_reachable(void) 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; } @@ -385,10 +392,20 @@ static void add_sha1_list(unsigned char *sha1, unsigned long ino) 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; @@ -396,17 +413,13 @@ static void fsck_dir(int i, char *path) 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; } @@ -556,8 +569,8 @@ static int fsck_cache_tree(struct cache_tree *it) 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"); } @@ -572,7 +585,7 @@ static char const * const fsck_usage[] = { }; 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 c304638b78..1a80702b3d 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -180,7 +180,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix) 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 }, @@ -189,6 +189,9 @@ int cmd_gc(int argc, const char **argv, const char *prefix) 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 da32f3df34..bb0f93208f 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -17,14 +17,10 @@ #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...]", + "git grep [options] [-e] <pattern> [<rev>...] [[--] <path>...]", NULL }; @@ -44,8 +40,7 @@ enum work_type {WORK_SHA1, WORK_FILE}; * threads. The producer adds struct work_items to 'todo' and the * consumers pick work items from the same array. */ -struct work_item -{ +struct work_item { enum work_type type; char *name; @@ -869,7 +864,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix) OPT_BOOLEAN('F', "fixed-strings", &opt.fixed, "interpret patterns as fixed strings"), OPT_GROUP(""), - OPT_BOOLEAN('n', NULL, &opt.linenum, "show line numbers"), + OPT_BOOLEAN('n', "line-number", &opt.linenum, "show line numbers"), OPT_NEGBIT('h', NULL, &opt.pathname, "don't show filenames", 1), OPT_BIT('H', NULL, &opt.pathname, "show filenames", 1), OPT_NEGBIT(0, "full-name", &opt.relative, @@ -915,8 +910,8 @@ int cmd_grep(int argc, const char **argv, const char *prefix) { 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(""), diff --git a/builtin/index-pack.c b/builtin/index-pack.c index 2e680d7a7a..c7e600db47 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -11,10 +11,9 @@ #include "exec_cmd.h" static const char index_pack_usage[] = -"git index-pack [-v] [-o <index-file>] [{ --keep | --keep=<msg> }] [--strict] { <pack-file> | --stdin [--fix-thin] [<pack-file>] }"; +"git index-pack [-v] [-o <index-file>] [ --keep | --keep=<msg> ] [--strict] (<pack-file> | --stdin [--fix-thin] [<pack-file>])"; -struct object_entry -{ +struct object_entry { struct pack_idx_entry idx; unsigned long size; unsigned int hdr_size; @@ -44,8 +43,7 @@ struct base_data { #define FLAG_LINK (1u<<20) #define FLAG_CHECKED (1u<<21) -struct delta_entry -{ +struct delta_entry { union delta_base base; int obj_no; }; @@ -161,7 +159,7 @@ static void use(int bytes) input_offset += bytes; /* make sure off_t is sufficiently large not to wrap */ - if (consumed_bytes > consumed_bytes + bytes) + if (signed_add_overflows(consumed_bytes, bytes)) die("pack too large for current definition of off_t"); consumed_bytes += bytes; } diff --git a/builtin/init-db.c b/builtin/init-db.c index 0271285fad..fbeb380ee2 100644 --- a/builtin/init-db.c +++ b/builtin/init-db.c @@ -294,11 +294,26 @@ static int create_default_files(const char *template_path) return reinit; } +static void create_object_directory(void) +{ + const char *object_directory = get_object_directory(); + int len = strlen(object_directory); + char *path = xmalloc(len + 40); + + memcpy(path, object_directory, len); + + safe_create_dir(object_directory, 1); + strcpy(path+len, "/pack"); + safe_create_dir(path, 1); + strcpy(path+len, "/info"); + safe_create_dir(path, 1); + + free(path); +} + int init_db(const char *template_dir, unsigned int flags) { - const char *sha1_dir; - char *path; - int len, reinit; + int reinit; safe_create_dir(get_git_dir(), 0); @@ -313,16 +328,7 @@ int init_db(const char *template_dir, unsigned int flags) reinit = create_default_files(template_dir); - sha1_dir = get_object_directory(); - len = strlen(sha1_dir); - path = xmalloc(len + 40); - memcpy(path, sha1_dir, len); - - safe_create_dir(sha1_dir, 1); - strcpy(path+len, "/pack"); - safe_create_dir(path, 1); - strcpy(path+len, "/info"); - safe_create_dir(path, 1); + create_object_directory(); if (shared_repository) { char buf[10]; @@ -408,11 +414,12 @@ static const char *const init_db_usage[] = { 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, @@ -474,8 +481,8 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) * 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, @@ -491,23 +498,29 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) is_bare_repository_cfg = guess_repository_type(git_dir); if (!is_bare_repository_cfg) { - if (git_dir) { - const char *git_dir_parent = strrchr(git_dir, '/'); - if (git_dir_parent) { - char *rel = xstrndup(git_dir, git_dir_parent - git_dir); - git_work_tree_cfg = xstrdup(make_absolute_path(rel)); - free(rel); - } + const char *git_dir_parent = strrchr(git_dir, '/'); + if (git_dir_parent) { + char *rel = xstrndup(git_dir, git_dir_parent - git_dir); + git_work_tree_cfg = xstrdup(make_absolute_path(rel)); + free(rel); } if (!git_work_tree_cfg) { git_work_tree_cfg = xcalloc(PATH_MAX, 1); 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 eaa1ee0fa7..9a15d69617 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -263,7 +263,13 @@ static int cmd_log_walk(struct rev_info *rev) * retain that state information if replacing rev->diffopt in this loop */ while ((commit = get_revision(rev)) != NULL) { - log_tree_commit(rev, commit); + if (!log_tree_commit(rev, commit) && + rev->max_count >= 0) + /* + * We decremented max_count in get_revision, + * but we didn't actually show the commit. + */ + rev->max_count++; if (!rev->reflog_info) { /* we allow cycles in reflog ancestry */ free(commit->buffer); @@ -329,8 +335,7 @@ static void show_tagger(char *buf, int len, struct rev_info *rev) 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); } @@ -1056,8 +1061,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) 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)); @@ -1160,6 +1164,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) if (!use_stdout) output_directory = set_outdir(prefix, output_directory); + else + setup_pager(); if (output_directory) { if (use_stdout) @@ -1228,10 +1234,6 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) continue; } - /* ignore merges */ - if (commit->parents && commit->parents->next) - continue; - if (ignore_if_in_upstream && has_commit_patch_id(commit, &ids)) continue; @@ -1356,6 +1358,23 @@ static const char * const cherry_usage[] = { NULL }; +static void print_commit(char sign, struct commit *commit, int verbose, + int abbrev) +{ + if (!verbose) { + printf("%c %s\n", sign, + find_unique_abbrev(commit->object.sha1, abbrev)); + } else { + struct strbuf buf = STRBUF_INIT; + struct pretty_print_context ctx = {0}; + pretty_print_commit(CMIT_FMT_ONELINE, commit, &buf, &ctx); + printf("%c %s %s\n", sign, + find_unique_abbrev(commit->object.sha1, abbrev), + buf.buf); + strbuf_release(&buf); + } +} + int cmd_cherry(int argc, const char **argv, const char *prefix) { struct rev_info revs; @@ -1370,7 +1389,7 @@ int cmd_cherry(int argc, const char **argv, const char *prefix) struct option options[] = { OPT__ABBREV(&abbrev), - OPT__VERBOSE(&verbose), + OPT__VERBOSE(&verbose, "be verbose"), OPT_END() }; @@ -1440,22 +1459,7 @@ int cmd_cherry(int argc, const char **argv, const char *prefix) commit = list->item; if (has_commit_patch_id(commit, &ids)) sign = '-'; - - if (verbose) { - struct strbuf buf = STRBUF_INIT; - struct pretty_print_context ctx = {0}; - pretty_print_commit(CMIT_FMT_ONELINE, commit, - &buf, &ctx); - printf("%c %s %s\n", sign, - find_unique_abbrev(commit->object.sha1, abbrev), - buf.buf); - strbuf_release(&buf); - } - else { - printf("%c %s\n", sign, - find_unique_abbrev(commit->object.sha1, abbrev)); - } - + print_commit(sign, commit, verbose, abbrev); list = list->next; } diff --git a/builtin/ls-files.c b/builtin/ls-files.c index bb4f612b3d..fb2d5f4b1f 100644 --- a/builtin/ls-files.c +++ b/builtin/ls-files.c @@ -424,7 +424,7 @@ int report_path_error(const char *ps_matched, const char **pathspec, int prefix_ } static const char * const ls_files_usage[] = { - "git ls-files [options] [<file>]*", + "git ls-files [options] [<file>...]", NULL }; @@ -530,6 +530,9 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) 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/ls-tree.c b/builtin/ls-tree.c index a8187568bf..f73e6bd962 100644 --- a/builtin/ls-tree.c +++ b/builtin/ls-tree.c @@ -24,7 +24,7 @@ static int chomp_prefix; static const char *ls_tree_prefix; static const char * const ls_tree_usage[] = { - "git ls-tree [<options>] <tree-ish> [path...]", + "git ls-tree [<options>] <tree-ish> [<path>...]", NULL }; diff --git a/builtin/mailinfo.c b/builtin/mailinfo.c index 2320d981ce..71e6262a87 100644 --- a/builtin/mailinfo.c +++ b/builtin/mailinfo.c @@ -1032,7 +1032,7 @@ int cmd_mailinfo(int argc, const char **argv, const char *prefix) */ 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/mailsplit.c b/builtin/mailsplit.c index 99654d0222..2d4327801e 100644 --- a/builtin/mailsplit.c +++ b/builtin/mailsplit.c @@ -10,7 +10,7 @@ #include "strbuf.h" static const char git_mailsplit_usage[] = -"git mailsplit [-d<prec>] [-f<n>] [-b] [--keep-cr] -o<directory> [<mbox>|<Maildir>...]"; +"git mailsplit [-d<prec>] [-f<n>] [-b] [--keep-cr] -o<directory> [(<mbox>|<Maildir>)...]"; static int is_from_line(const char *line, int len) { diff --git a/builtin/merge-file.c b/builtin/merge-file.c index b6664d49be..237abd3c0b 100644 --- a/builtin/merge-file.c +++ b/builtin/merge-file.c @@ -28,6 +28,7 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) xmparam_t xmp = {{0}}; int ret = 0, i = 0, to_stdout = 0; int quiet = 0; + int prefixlen = 0; struct option options[] = { OPT_BOOLEAN('p', "stdout", &to_stdout, "send results to standard output"), OPT_SET_INT(0, "diff3", &xmp.style, "use a diff3 based merge", XDL_MERGE_DIFF3), @@ -39,7 +40,7 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) 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(), @@ -65,10 +66,14 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) "%s\n", strerror(errno)); } + if (prefix) + prefixlen = strlen(prefix); + for (i = 0; i < 3; i++) { + const char *fname = prefix_filename(prefix, prefixlen, argv[i]); if (!names[i]) names[i] = argv[i]; - if (read_mmfile(mmfs + i, argv[i])) + if (read_mmfile(mmfs + i, fname)) return -1; if (buffer_is_binary(mmfs[i].ptr, mmfs[i].size)) return error("Cannot merge binary files: %s\n", diff --git a/builtin/merge-recursive.c b/builtin/merge-recursive.c index 78b9db76a0..c33091b3ed 100644 --- a/builtin/merge-recursive.c +++ b/builtin/merge-recursive.c @@ -2,6 +2,7 @@ #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> ..."; @@ -40,19 +41,7 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix) 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 5f65c0c8a6..f9fb26dc33 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -42,7 +42,7 @@ static const char * const builtin_merge_usage[] = { 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; @@ -57,6 +57,7 @@ static const char *branch; 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 }, @@ -177,8 +178,9 @@ static struct option builtin_merge_options[] = { 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, @@ -193,9 +195,11 @@ static struct option builtin_merge_options[] = { 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() }; @@ -233,6 +237,24 @@ static void save_state(void) die("not a valid object: %s", buffer.buf); } +static void read_empty(unsigned const char *sha1, int verbose) +{ + int i = 0; + const char *args[7]; + + args[i++] = "read-tree"; + if (verbose) + args[i++] = "-v"; + args[i++] = "-m"; + args[i++] = "-u"; + args[i++] = EMPTY_TREE_SHA1_HEX; + args[i++] = sha1_to_hex(sha1); + args[i] = NULL; + + if (run_command_v_opt(args, RUN_GIT_CMD)) + die("read-tree failed"); +} + static void reset_hard(unsigned const char *sha1, int verbose) { int i = 0; @@ -402,7 +424,7 @@ static void merge_name(const char *remote, struct strbuf *msg) 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; } @@ -504,10 +526,17 @@ static int git_merge_config(const char *k, const char *v, void *cb) 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); } @@ -631,25 +660,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; @@ -782,6 +795,32 @@ static void add_strategies(const char *string, unsigned attr) } +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]; @@ -793,6 +832,7 @@ static int merge_trivial(void) 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(); @@ -822,6 +862,7 @@ static int finish_automerge(struct commit_list *common, } 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); @@ -909,22 +950,9 @@ int cmd_merge(int argc, const char **argv, const char *prefix) 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. @@ -943,6 +971,34 @@ int cmd_merge(int argc, const char **argv, const char *prefix) 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; @@ -993,7 +1049,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) die("%s - not something we can merge", argv[0]); update_ref("initial pull", "HEAD", remote_head->sha1, NULL, 0, DIE_ON_ERR); - reset_hard(remote_head->sha1, 0); + read_empty(remote_head->sha1, 0); return 0; } else { struct strbuf merge_names = STRBUF_INIT; @@ -1012,14 +1068,12 @@ int cmd_merge(int argc, const char **argv, const char *prefix) 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) @@ -1290,14 +1344,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) 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 cdbb09473c..93e8995d9e 100644 --- a/builtin/mv.c +++ b/builtin/mv.c @@ -55,8 +55,8 @@ int cmd_mv(int argc, const char **argv, const char *prefix) 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 6d07aac80c..0aab150c52 100644 --- a/builtin/notes.c +++ b/builtin/notes.c @@ -17,6 +17,7 @@ #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>]]", @@ -25,8 +26,12 @@ static const char * const git_notes_usage[] = { "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 }; @@ -61,6 +66,13 @@ static const char * const git_notes_show_usage[] = { 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 @@ -71,6 +83,11 @@ static const char * const git_notes_prune_usage[] = { NULL }; +static const char * const git_notes_get_ref_usage[] = { + "git notes get-ref", + NULL +}; + static const char note_template[] = "\n" "#\n" @@ -83,6 +100,16 @@ struct msg_arg { 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 */ @@ -290,27 +316,10 @@ int commit_notes(struct notes_tree *t, const char *msg) 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) @@ -321,6 +330,8 @@ 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; } @@ -526,19 +537,19 @@ static int add(int argc, const char **argv, const char *prefix) 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() }; @@ -573,8 +584,8 @@ static int add(int argc, const char **argv, const char *prefix) 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"); @@ -594,7 +605,7 @@ static int copy(int argc, const char **argv, const char *prefix) 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 " @@ -653,7 +664,8 @@ static int copy(int argc, const char **argv, const char *prefix) 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); @@ -670,16 +682,16 @@ static int append_edit(int argc, const char **argv, const char *prefix) 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() @@ -712,8 +724,8 @@ static int append_edit(int argc, const char **argv, const char *prefix) 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]); @@ -761,6 +773,180 @@ static int show(int argc, const char **argv, const char *prefix) 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[] = { @@ -804,9 +990,8 @@ static int prune(int argc, const char **argv, const char *prefix) 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() }; @@ -828,6 +1013,21 @@ static int prune(int argc, const char **argv, const char *prefix) 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; @@ -844,13 +1044,8 @@ int cmd_notes(int argc, const char **argv, const char *prefix) 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); } @@ -865,10 +1060,14 @@ int cmd_notes(int argc, const char **argv, const char *prefix) 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 0e81673118..b0503b202a 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -16,22 +16,18 @@ #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" + "git pack-objects [ -q | --progress | --all-progress ]\n" " [--all-progress-implied]\n" - " [--max-pack-size=N] [--local] [--incremental]\n" - " [--window=N] [--window-memory=N] [--depth=N]\n" + " [--max-pack-size=<n>] [--local] [--incremental]\n" + " [--window=<n>] [--window-memory=<n>] [--depth=<n>]\n" " [--no-reuse-delta] [--no-reuse-object] [--delta-base-offset]\n" - " [--threads=N] [--non-empty] [--revs [--unpacked | --all]*]\n" + " [--threads=<n>] [--non-empty] [--revs [--unpacked | --all]]\n" " [--reflog] [--stdout | base-name] [--include-tag]\n" - " [--keep-unreachable | --unpack-unreachable \n" - " [<ref-list | <object-list]"; + " [--keep-unreachable | --unpack-unreachable]\n" + " [< ref-list | < object-list]"; struct object_entry { struct pack_idx_entry idx; @@ -431,7 +427,7 @@ static int write_one(struct sha1file *f, written_list[nr_written++] = &e->idx; /* make sure off_t is sufficiently large not to wrap */ - if (*offset > *offset + size) + if (signed_add_overflows(*offset, size)) die("pack too large for current definition of off_t"); *offset += size; return 1; @@ -1298,9 +1294,23 @@ static int try_delta(struct unpacked *trg, struct unpacked *src, read_lock(); src->data = read_sha1_file(src_entry->idx.sha1, &type, &sz); read_unlock(); - if (!src->data) + if (!src->data) { + if (src_entry->preferred_base) { + static int warned = 0; + if (!warned++) + warning("object %s cannot be read", + sha1_to_hex(src_entry->idx.sha1)); + /* + * Those objects are not included in the + * resulting pack. Be resilient and ignore + * them if they can't be read, in case the + * pack could be created nevertheless. + */ + return 0; + } die("object %s cannot be read", sha1_to_hex(src_entry->idx.sha1)); + } if (sz != src_size) die("object %s inconsistent object length (%lu vs %lu)", sha1_to_hex(src_entry->idx.sha1), sz, src_size); @@ -1529,7 +1539,7 @@ static void try_to_free_from_threads(size_t size) 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 512530022e..49a0472a9b 100644 --- a/builtin/patch-id.c +++ b/builtin/patch-id.c @@ -73,6 +73,8 @@ int get_one_patchid(unsigned char *next_sha1, git_SHA_CTX *ctx) 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 99218ba49e..e65690ba37 100644 --- a/builtin/prune.c +++ b/builtin/prune.c @@ -125,9 +125,8 @@ int cmd_prune(int argc, const char **argv, const char *prefix) { 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 e655eb7695..31da418cf4 100644 --- a/builtin/push.c +++ b/builtin/push.c @@ -64,17 +64,17 @@ static void set_refspecs(const char **refs, int nr) } } -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); @@ -88,8 +88,8 @@ static void setup_default_push_refspecs(void) 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 9ad1e66916..93c92814cf 100644 --- a/builtin/read-tree.c +++ b/builtin/read-tree.c @@ -16,6 +16,7 @@ #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) @@ -32,7 +33,7 @@ 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 }; @@ -103,10 +104,12 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) 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), @@ -166,6 +169,11 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) 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 new file mode 100644 index 0000000000..ea71977c83 --- /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 new file mode 100644 index 0000000000..1f2467bdb7 --- /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 48e0a6bf26..cb26080956 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -15,7 +15,7 @@ static const char * const builtin_remote_usage[] = { "git remote set-head <name> (-a | -d | <branch>)", "git remote [-v | --verbose] show [-n] <name>", "git remote prune [-n | --dry-run] <name>", - "git remote [-v | --verbose] update [-p | --prune] [group | remote]", + "git remote [-v | --verbose] update [-p | --prune] [(<group> | <remote>)...]", "git remote set-branches <name> [--add] <branch>...", "git remote set-url <name> <newurl> [<oldurl>]", "git remote set-url --add <name> <newurl>", @@ -507,7 +507,7 @@ static int add_branch_for_removal(const char *refname, 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/")) @@ -791,9 +791,9 @@ static int rm(int argc, const char **argv) 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", @@ -1200,7 +1200,7 @@ static int prune(int argc, const char **argv) { int dry_run = 0, result = 0; struct option options[] = { - OPT__DRY_RUN(&dry_run), + OPT__DRY_RUN(&dry_run, "dry run"), OPT_END() }; @@ -1512,7 +1512,7 @@ static int show_all(void) 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/reset.c b/builtin/reset.c index 0037be4693..5de2bceeec 100644 --- a/builtin/reset.c +++ b/builtin/reset.c @@ -243,7 +243,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix) 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/rev-list.c b/builtin/rev-list.c index efe9360e2f..ba27d39f97 100644 --- a/builtin/rev-list.c +++ b/builtin/rev-list.c @@ -11,9 +11,9 @@ static const char rev_list_usage[] = "git rev-list [OPTION] <commit-id>... [ -- paths... ]\n" " limiting output:\n" -" --max-count=nr\n" -" --max-age=epoch\n" -" --min-age=epoch\n" +" --max-count=<n>\n" +" --max-age=<epoch>\n" +" --min-age=<epoch>\n" " --sparse\n" " --no-merges\n" " --remove-empty\n" @@ -33,7 +33,7 @@ static const char rev_list_usage[] = " --objects | --objects-edge\n" " --unpacked\n" " --header | --pretty\n" -" --abbrev=nr | --no-abbrev\n" +" --abbrev=<n> | --no-abbrev\n" " --abbrev-commit\n" " --left-right\n" " special purpose:\n" @@ -147,8 +147,10 @@ static void show_commit(struct commit *commit, void *data) } } else { if (revs->commit_format != CMIT_FMT_USERFORMAT || - buf.len) - printf("%s%c", buf.buf, info->hdr_termination); + buf.len) { + fwrite(buf.buf, 1, buf.len, stdout); + putchar(info->hdr_termination); + } } strbuf_release(&buf); } else { diff --git a/builtin/revert.c b/builtin/revert.c index 4b47ace36b..bb6e9e83b7 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -442,7 +442,7 @@ static int do_pick_commit(void) else parent = commit->parents->item; - if (allow_ff && !hashcmp(parent->object.sha1, head)) + if (allow_ff && parent && !hashcmp(parent->object.sha1, head)) return fast_forward_to(commit->object.sha1, head); if (parent && parse_commit(parent) < 0) @@ -547,6 +547,21 @@ static void prepare_revs(struct rev_info *revs) die("empty commit set passed"); } +static void read_and_refresh_cache(const char *me) +{ + static struct lock_file index_lock; + int index_fd = hold_locked_index(&index_lock, 0); + if (read_index_preload(&the_index, NULL) < 0) + die("git %s: failed to read the index", me); + refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, NULL, NULL, NULL); + if (the_index.cache_changed) { + if (write_index(&the_index, index_fd) || + commit_locked_index(&index_lock)) + die("git %s: failed to refresh the index", me); + } + rollback_lock_file(&index_lock); +} + static int revert_or_cherry_pick(int argc, const char **argv) { struct rev_info revs; @@ -567,8 +582,7 @@ static int revert_or_cherry_pick(int argc, const char **argv) die("cherry-pick --ff cannot be used with --edit"); } - if (read_cache() < 0) - die("git %s: failed to read the index", me); + read_and_refresh_cache(me); prepare_revs(&revs); diff --git a/builtin/rm.c b/builtin/rm.c index f3772c84de..ff491d7761 100644 --- a/builtin/rm.c +++ b/builtin/rm.c @@ -20,15 +20,6 @@ static struct { 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"), @@ -182,7 +173,8 @@ int cmd_rm(int argc, const char **argv, const char *prefix) 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 481602d8ae..2cd1c40b70 100644 --- a/builtin/send-pack.c +++ b/builtin/send-pack.c @@ -48,6 +48,7 @@ static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *ext NULL, NULL, NULL, + NULL, }; struct child_process po; int i; @@ -59,6 +60,8 @@ static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *ext 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; @@ -101,7 +104,7 @@ static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *ext } if (finish_command(&po)) - return error("pack-objects died with strange error"); + return -1; return 0; } diff --git a/builtin/shortlog.c b/builtin/shortlog.c index 2135b0dde1..1a21e4b053 100644 --- a/builtin/shortlog.c +++ b/builtin/shortlog.c @@ -268,8 +268,8 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix) 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 e8719aa9e9..da695815e2 100644 --- a/builtin/show-branch.c +++ b/builtin/show-branch.c @@ -6,7 +6,7 @@ #include "parse-options.h" static const char* show_branch_usage[] = { - "git show-branch [-a|--all] [-r|--remotes] [--topo-order | --date-order] [--current] [--color[=<when>] | --no-color] [--sparse] [--more=<n> | --list | --independent | --merge-base] [--no-name | --sha1-name] [--topics] [<rev> | <glob>]...", + "git show-branch [-a|--all] [-r|--remotes] [--topo-order | --date-order] [--current] [--color[=<when>] | --no-color] [--sparse] [--more=<n> | --list | --independent | --merge-base] [--no-name | --sha1-name] [--topics] [(<rev> | <glob>)...]", "git show-branch (-g|--reflog)[=<n>[,<base>]] [--list] [<ref>]", NULL }; @@ -243,7 +243,7 @@ static void join_revs(struct commit_list **list_p, 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); } } @@ -859,7 +859,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) */ 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++) @@ -868,7 +868,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) 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 be9b512eeb..45f0340c3e 100644 --- a/builtin/show-ref.c +++ b/builtin/show-ref.c @@ -193,7 +193,8 @@ static const struct option show_ref_options[] = { "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 ca855a5eb2..dea849c3c5 100644 --- a/builtin/symbolic-ref.c +++ b/builtin/symbolic-ref.c @@ -30,7 +30,8 @@ int cmd_symbolic_ref(int argc, const char **argv, const char *prefix) 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 d311491e49..7cf48abca8 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -29,8 +29,6 @@ struct tag_filter { struct commit_list *with_commit; }; -#define PGP_SIGNATURE "-----BEGIN PGP SIGNATURE-----" - static int show_reference(const char *refname, const unsigned char *sha1, int flag, void *cb_data) { @@ -70,9 +68,9 @@ static int show_reference(const char *refname, const unsigned char *sha1, return 0; } /* only take up to "lines" lines, and strip the signature */ + size = parse_signature(buf, size); for (i = 0, sp += 2; - i < filter->lines && sp < buf + size && - prefixcmp(sp, PGP_SIGNATURE "\n"); + i < filter->lines && sp < buf + size; i++) { if (i) printf("\n "); @@ -242,8 +240,7 @@ static void write_tag_body(int fd, const unsigned char *sha1) { unsigned long size; enum object_type type; - char *buf, *sp, *eob; - size_t len; + char *buf, *sp; buf = read_sha1_file(sha1, &type, &size); if (!buf) @@ -256,12 +253,7 @@ static void write_tag_body(int fd, const unsigned char *sha1) return; } sp += 2; /* skip the 2 LFs */ - eob = strstr(sp, "\n" PGP_SIGNATURE "\n"); - if (eob) - len = eob - sp; - else - len = buf + size - sp; - write_or_die(fd, sp, len); + write_or_die(fd, sp, parse_signature(sp, buf + size - sp)); free(buf); } @@ -384,13 +376,13 @@ int cmd_tag(int argc, const char **argv, const char *prefix) 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/unpack-objects.c b/builtin/unpack-objects.c index 685566e0b5..f63973c914 100644 --- a/builtin/unpack-objects.c +++ b/builtin/unpack-objects.c @@ -83,7 +83,7 @@ static void use(int bytes) offset += bytes; /* make sure off_t is sufficiently large not to wrap */ - if (consumed_bytes > consumed_bytes + bytes) + if (signed_add_overflows(consumed_bytes, bytes)) die("pack too large for current definition of off_t"); consumed_bytes += bytes; } diff --git a/builtin/update-index.c b/builtin/update-index.c index 3ab214d24e..56baf27fb7 100644 --- a/builtin/update-index.c +++ b/builtin/update-index.c @@ -10,6 +10,7 @@ #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 @@ -397,8 +398,10 @@ static void read_index_info(int line_termination) 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]; @@ -578,16 +581,214 @@ static int do_reupdate(int ac, const char **av, 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); @@ -602,151 +803,48 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) 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; @@ -770,10 +868,9 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) 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); } diff --git a/builtin/update-server-info.c b/builtin/update-server-info.c index 2b3fddcc69..b90dce6358 100644 --- a/builtin/update-server-info.c +++ b/builtin/update-server-info.c @@ -11,8 +11,7 @@ int cmd_update_server_info(int argc, const char **argv, const char *prefix) { 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 9f482c29f5..3134766049 100644 --- a/builtin/verify-tag.c +++ b/builtin/verify-tag.c @@ -17,13 +17,11 @@ static const char * const verify_tag_usage[] = { NULL }; -#define PGP_SIGNATURE "-----BEGIN PGP SIGNATURE-----" - static int run_gpg_verify(const char *buf, unsigned long size, int verbose) { struct child_process gpg; const char *args_gpg[] = {"gpg", "--verify", "FILE", "-", NULL}; - char path[PATH_MAX], *eol; + char path[PATH_MAX]; size_t len; int fd, ret; @@ -37,11 +35,7 @@ static int run_gpg_verify(const char *buf, unsigned long size, int verbose) close(fd); /* find the length without signature */ - len = 0; - while (len < size && prefixcmp(buf + len, PGP_SIGNATURE)) { - eol = memchr(buf + len, '\n', size - len); - len += eol ? eol - (buf + len) + 1 : size - len; - } + len = parse_signature(buf, size); if (verbose) write_in_full(1, buf, len); @@ -93,7 +87,7 @@ int cmd_verify_tag(int argc, const char **argv, const char *prefix) { 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() }; @@ -200,7 +200,7 @@ int create_bundle(struct bundle_header *header, const char *path, 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; @@ -346,7 +346,8 @@ int create_bundle(struct bundle_header *header, const char *path, 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; @@ -170,26 +170,26 @@ struct cache_entry { * * 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) @@ -277,9 +277,16 @@ static inline int ce_to_dtype(const struct cache_entry *ce) else return DT_UNKNOWN; } -#define canon_mode(mode) \ - (S_ISREG(mode) ? (S_IFREG | ce_permissions(mode)) : \ - S_ISLNK(mode) ? S_IFLNK : S_ISDIR(mode) ? S_IFDIR : S_IFGITLINK) +static inline unsigned int canon_mode(unsigned int mode) +{ + if (S_ISREG(mode)) + return S_IFREG | ce_permissions(mode); + if (S_ISLNK(mode)) + return S_IFLNK; + if (S_ISDIR(mode)) + return S_IFDIR; + return S_IFGITLINK; +} #define flexible_size(STRUCT,len) ((offsetof(struct STRUCT,name) + (len) + 8) & ~7) #define cache_entry_size(len) flexible_size(cache_entry,len) @@ -421,7 +428,7 @@ extern const char **get_pathspec(const char *prefix, const char **pathspec); 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); @@ -438,7 +445,7 @@ extern int init_db(const char *template_dir, unsigned int flags); * at least 'nr' entries; the number of entries currently allocated * is 'alloc', using the standard growing factor alloc_nr() macro. * - * DO NOT USE any expression with side-effect for 'x' or 'alloc'. + * DO NOT USE any expression with side-effect for 'x', 'nr', or 'alloc'. */ #define ALLOC_GROW(x, nr, alloc) \ do { \ @@ -534,6 +541,7 @@ extern int trust_executable_bit; extern int trust_ctime; extern int quote_path_fully; extern int has_symlinks; +extern int minimum_abbrev, default_abbrev; extern int ignore_case; extern int assume_unchanged; extern int prefer_symlink_refs; @@ -564,7 +572,7 @@ extern enum safe_crlf safe_crlf; enum auto_crlf { AUTO_CRLF_FALSE = 0, AUTO_CRLF_TRUE = 1, - AUTO_CRLF_INPUT = -1, + AUTO_CRLF_INPUT = -1 }; extern enum auto_crlf auto_crlf; @@ -601,7 +609,7 @@ enum rebase_setup_type { enum push_default_type { PUSH_DEFAULT_NOTHING = 0, PUSH_DEFAULT_MATCHING, - PUSH_DEFAULT_TRACKING, + PUSH_DEFAULT_UPSTREAM, PUSH_DEFAULT_CURRENT }; @@ -669,9 +677,11 @@ static inline void hashclr(unsigned char *hash) #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); @@ -751,8 +761,8 @@ static inline unsigned int hexval(unsigned char c) } /* Convert to/from hex/sha1 representation */ -#define MINIMUM_ABBREV 4 -#define DEFAULT_ABBREV 7 +#define MINIMUM_ABBREV minimum_abbrev +#define DEFAULT_ABBREV default_abbrev struct object_context { unsigned char tree[20]; @@ -853,7 +863,7 @@ struct cache_def { extern int has_symlink_leading_path(const char *name, int len); extern int threaded_has_symlink_leading_path(struct cache_def *, const char *, int); -extern int has_symlink_or_noent_leading_path(const char *name, int len); +extern int check_leading_path(const char *name, int len); extern int has_dirs_only_path(const char *name, int len, int prefix_len); extern void schedule_dir_for_removal(const char *name, int len); extern void remove_scheduled_dirs(void); @@ -890,7 +900,8 @@ extern struct packed_git { time_t mtime; int pack_fd; unsigned pack_local:1, - pack_keep:1; + pack_keep:1, + do_not_close:1; unsigned char sha1[20]; /* something like ".git/objects/pack/xxxxx.pack" */ char pack_name[FLEX_ARRAY]; /* more */ @@ -980,6 +991,7 @@ extern int git_config_parse_parameter(const char *text); 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 *); @@ -989,6 +1001,7 @@ extern int git_config_maybe_bool(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); @@ -997,6 +1010,9 @@ extern int git_env_bool(const char *, int); 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) @@ -1056,6 +1072,7 @@ __attribute__((format (printf, 1, 2))) 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 */ @@ -1081,15 +1098,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 *); @@ -1098,6 +1117,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); @@ -1111,6 +1131,7 @@ const char *split_cmdline_strerror(int cmdline_errno); /* git.c */ struct startup_info { int have_repository; + const char *prefix; }; extern struct startup_info *startup_info; @@ -211,3 +211,8 @@ int color_fprintf_ln(FILE *fp, const char *color, const char *fmt, ...) va_end(args); return r; } + +int color_is_nil(const char *c) +{ + return !strcmp(c, "NIL"); +} @@ -43,6 +43,9 @@ #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 */ @@ -62,4 +65,6 @@ int color_fprintf(FILE *fp, const char *color, const char *fmt, ...); __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 */ @@ -49,6 +49,19 @@ struct commit *lookup_commit(const unsigned char *sha1) 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; @@ -137,12 +150,8 @@ struct commit_graft *read_graft_line(char *buf, int len) 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; @@ -155,6 +164,11 @@ struct commit_graft *read_graft_line(char *buf, int len) 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) @@ -231,10 +245,10 @@ int unregister_shallow(const unsigned char *sha1) 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; @@ -360,7 +374,7 @@ void free_commit_list(struct commit_list *list) } } -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; @@ -398,7 +412,7 @@ struct commit *pop_most_recent_commit(struct commit_list **list, 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; } @@ -487,7 +501,7 @@ void sort_in_topological_order(struct commit_list ** list, int lifo) /* process the list in topological order */ if (!lifo) - sort_by_date(&work); + commit_list_sort_by_date(&work); pptr = list; *list = NULL; @@ -513,7 +527,7 @@ void sort_in_topological_order(struct commit_list ** list, int lifo) */ 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; } @@ -711,7 +725,7 @@ struct commit_list *get_merge_bases_many(struct commit *one, 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; @@ -36,22 +36,23 @@ struct commit *lookup_commit(const unsigned char *sha1); 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, @@ -67,8 +68,7 @@ enum cmit_fmt { CMIT_FMT_UNSPECIFIED }; -struct pretty_print_context -{ +struct pretty_print_context { int abbrev; const char *subject; const char *after_subject; @@ -76,6 +76,7 @@ struct pretty_print_context int need_8bit_cte; int show_notes; struct reflog_walk_info *reflog_info; + const char *output_encoding; }; struct userformat_want { @@ -84,6 +85,8 @@ 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/bswap.h b/compat/bswap.h index 54756dbb05..5061214f73 100644 --- a/compat/bswap.h +++ b/compat/bswap.h @@ -21,14 +21,16 @@ static inline uint32_t default_swab32(uint32_t val) #if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) -#define bswap32(x) ({ \ - uint32_t __res; \ - if (__builtin_constant_p(x)) { \ - __res = default_swab32(x); \ - } else { \ - __asm__("bswap %0" : "=r" (__res) : "0" ((uint32_t)(x))); \ - } \ - __res; }) +#define bswap32 git_bswap32 +static inline uint32_t git_bswap32(uint32_t x) +{ + uint32_t result; + if (__builtin_constant_p(x)) + result = default_swab32(x); + else + __asm__("bswap %0" : "=r" (result) : "0" (x)); + return result; +} #elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64)) diff --git a/compat/inet_ntop.c b/compat/inet_ntop.c index f44498258d..ea249c6ac6 100644 --- a/compat/inet_ntop.c +++ b/compat/inet_ntop.c @@ -17,9 +17,9 @@ #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> @@ -50,10 +50,7 @@ * 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"]; @@ -78,10 +75,7 @@ inet_ntop4(src, dst, size) * 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 @@ -178,11 +172,7 @@ inet_ntop6(src, dst, size) * 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 4078fc0877..2ec995e63d 100644 --- a/compat/inet_pton.c +++ b/compat/inet_pton.c @@ -17,9 +17,9 @@ #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> @@ -41,7 +41,9 @@ */ 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 f2d9e1fd97..bee6054419 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -127,7 +127,7 @@ 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); @@ -160,7 +160,7 @@ ssize_t mingw_write(int fd, const void *buf, size_t count) #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); } @@ -192,12 +192,16 @@ static inline time_t filetime_to_time_t(const FILETIME *ft) /* 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; - if (!(errno = get_file_attr(file_name, &fdata))) { + if (!(err = get_file_attr(file_name, &fdata))) { buf->st_ino = 0; buf->st_gid = 0; buf->st_uid = 0; @@ -209,8 +213,28 @@ static int do_lstat(const char *file_name, struct stat *buf) 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; return -1; } @@ -220,12 +244,12 @@ static int do_lstat(const char *file_name, struct stat *buf) * 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; @@ -244,7 +268,16 @@ int mingw_lstat(const char *file_name, struct stat *buf) 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 @@ -377,71 +410,6 @@ int pipe(int filedes[2]) 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 */ @@ -671,6 +639,14 @@ static int env_compare(const void *a, const void *b) 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) @@ -763,7 +739,26 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **env, 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, @@ -873,6 +868,30 @@ void mingw_execvp(const char *cmd, char *const *argv) 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; @@ -957,19 +976,22 @@ static int WSAAPI getaddrinfo_stub(const char *node, const char *service, 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; @@ -981,14 +1003,25 @@ static int WSAAPI getaddrinfo_stub(const char *node, const char *service, 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; @@ -1139,7 +1172,10 @@ int mingw_getnameinfo(const struct sockaddr *sa, socklen_t salen, 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 @@ -1169,6 +1205,45 @@ int mingw_connect(int sockfd, struct sockaddr *sa, size_t sz) 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) { @@ -1386,6 +1461,7 @@ void mingw_open_html(const char *unixpath) const char *, const char *, const char *, INT); T ShellExecute; HMODULE shell32; + int r; shell32 = LoadLibrary("shell32.dll"); if (!shell32) @@ -1395,9 +1471,12 @@ void mingw_open_html(const char *unixpath) 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) @@ -1436,62 +1515,54 @@ char *getpass(const char *prompt) 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; + } + + if (status) + GetExitCodeProcess(h, (LPDWORD)status); - /* 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; + 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); - return (struct dirent*)&dir->dd_dir; + 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 3b2477be5f..14211c6214 100644 --- a/compat/mingw.h +++ b/compat/mingw.h @@ -6,23 +6,34 @@ */ 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 @@ -34,6 +45,9 @@ typedef int pid_t; #define F_SETFD 2 #define FD_CLOEXEC 0x1 +#define EAFNOSUPPORT WSAEAFNOSUPPORT +#define ECONNABORTED WSAECONNABORTED + struct passwd { char *pw_name; char *pw_gecos; @@ -42,16 +56,6 @@ struct passwd { 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; @@ -66,6 +70,12 @@ struct itimerval { #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 */ @@ -75,17 +85,17 @@ static inline int symlink(const char *oldpath, const char *newpath) { 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; } @@ -117,13 +127,11 @@ static inline int mingw_unlink(const char *pathname) } #define unlink mingw_unlink -static inline int waitpid(pid_t pid, int *status, unsigned options) -{ - if (options == 0) - return _cwait(status, pid, 0); - errno = EINVAL; - return -1; -} +#define WNOHANG 1 +pid_t waitpid(pid_t pid, int *status, unsigned options); + +#define kill mingw_kill +int mingw_kill(pid_t pid, int sig); #ifndef NO_OPENSSL #include <openssl/ssl.h> @@ -154,11 +162,10 @@ int pipe(int filedes[2]); 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); @@ -206,6 +213,18 @@ int mingw_socket(int domain, int type, int protocol); 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 @@ -214,6 +233,22 @@ int mingw_getpagesize(void); #define getpagesize mingw_getpagesize #endif +struct rlimit { + unsigned int rlim_cur; +}; +#define RLIMIT_NOFILE 0 + +static inline int getrlimit(int resource, struct rlimit *rlp) +{ + if (resource != RLIMIT_NOFILE) { + errno = EINVAL; + return -1; + } + + rlp->rlim_cur = 2048; + return 0; +} + /* Use mingw_lstat() instead of lstat()/stat() and * mingw_fstat() instead of fstat() on Windows. */ @@ -222,10 +257,11 @@ int mingw_getpagesize(void); #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); @@ -236,6 +272,8 @@ pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **env, 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); } @@ -283,44 +321,17 @@ void free_environ(char **env); 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 ac04a4ccbd..71843d7eef 100644 --- a/compat/msvc.c +++ b/compat/msvc.c @@ -3,33 +3,4 @@ #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 deleted file mode 100644 index 440618db0d..0000000000 --- a/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_ */ diff --git a/compat/vcbuild/include/unistd.h b/compat/vcbuild/include/unistd.h index 2a4f276869..b14fcf94da 100644 --- a/compat/vcbuild/include/unistd.h +++ b/compat/vcbuild/include/unistd.h @@ -45,6 +45,10 @@ typedef unsigned long long uintmax_t; 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 new file mode 100644 index 0000000000..7a0debe51b --- /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 new file mode 100644 index 0000000000..927a25ca76 --- /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 new file mode 100644 index 0000000000..708a6c9bec --- /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 new file mode 100644 index 0000000000..b7aa59d973 --- /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 new file mode 100644 index 0000000000..42b95a9b51 --- /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 new file mode 100644 index 0000000000..70daa7c08b --- /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 */ @@ -20,8 +20,7 @@ static int zlib_compression_seen; const char *config_exclusive_filename = NULL; -struct config_item -{ +struct config_item { struct config_item *next; char *name; char *value; @@ -410,7 +409,7 @@ unsigned long git_config_ulong(const char *name, const char *value) return ret; } -int git_config_maybe_bool(const char *name, const char *value) +static int git_config_maybe_bool_text(const char *name, const char *value) { if (!value) return 1; @@ -427,9 +426,19 @@ int git_config_maybe_bool(const char *name, const char *value) return -1; } +int git_config_maybe_bool(const char *name, const char *value) +{ + long v = git_config_maybe_bool_text(name, value); + if (0 <= v) + return v; + if (git_parse_long(value, &v)) + return !!v; + return -1; +} + int git_config_bool_or_int(const char *name, const char *value, int *is_bool) { - int v = git_config_maybe_bool(name, value); + int v = git_config_maybe_bool_text(name, value); if (0 <= v) { *is_bool = 1; return v; @@ -514,6 +523,14 @@ static int git_default_core_config(const char *var, const char *value) return 0; } + if (!strcmp(var, "core.abbrev")) { + int abbrev = git_config_int(var, value); + if (abbrev < minimum_abbrev || abbrev > 40) + return -1; + default_abbrev = abbrev; + return 0; + } + if (!strcmp(var, "core.loosecompression")) { int level = git_config_int(var, value); if (level == -1) @@ -720,8 +737,10 @@ static int git_default_push_config(const char *var, const char *value) 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 { @@ -835,10 +854,9 @@ int git_config_from_parameters(config_fn_t fn, void *data) 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. */ @@ -860,19 +878,27 @@ int git_config(config_fn_t fn, void *data) 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; } @@ -1075,6 +1101,75 @@ int git_config_set(const char *key, const char *value) } /* + * 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. * if multi_replace==0, nothing, or only one matching key/value is replaced, @@ -1100,59 +1195,23 @@ int git_config_set(const char *key, const char *value) 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 a0c34eec15..9614973057 100644 --- a/config.mak.in +++ b/config.mak.in @@ -27,7 +27,7 @@ VPATH = @srcdir@ 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@ @@ -47,6 +47,8 @@ NO_C99_FORMAT=@NO_C99_FORMAT@ 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 56731c35c9..20039c546f 100644 --- a/configure.ac +++ b/configure.ac @@ -282,7 +282,15 @@ GIT_PARSE_WITH(iconv)) 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.) # @@ -337,7 +345,7 @@ esac 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 @@ -346,7 +354,7 @@ else 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 @@ -355,7 +363,7 @@ else 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 @@ -390,21 +398,21 @@ if test -n "$ASCIIDOC"; 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. @@ -464,15 +472,9 @@ if test -z "$NO_ICONV"; then 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" @@ -492,7 +494,7 @@ for l in $lib_order; do 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], @@ -520,18 +522,12 @@ fi 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]) @@ -609,25 +605,33 @@ AC_CHECK_HEADER([sys/select.h], [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]) @@ -810,6 +814,34 @@ GIT_CHECK_FUNC(strtok_r, [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=], @@ -860,6 +892,12 @@ GIT_CHECK_FUNC(mkstemps, [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. # @@ -877,18 +915,16 @@ AC_SUBST(NO_MKSTEMPS) # # 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>]], @@ -908,7 +944,7 @@ elif test -z "$PTHREAD_CFLAGS"; then 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" @@ -928,7 +964,7 @@ else 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" @@ -631,8 +631,12 @@ char *git_getpass(const char *prompt) askpass = askpass_program; if (!askpass) askpass = getenv("SSH_ASKPASS"); - if (!askpass || !(*askpass)) - return getpass(prompt); + if (!askpass || !(*askpass)) { + char *result = getpass(prompt); + if (!result) + die_errno("Could not read password"); + return result; + } args[0] = askpass; args[1] = prompt; diff --git a/contrib/ciabot/ciabot.py b/contrib/ciabot/ciabot.py index d0627e0852..9775dffb5d 100755 --- a/contrib/ciabot/ciabot.py +++ b/contrib/ciabot/ciabot.py @@ -122,7 +122,7 @@ def report(refname, merged): 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) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 67569901e7..893b7716ca 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -21,6 +21,11 @@ # 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)")]\$ ' # @@ -138,11 +143,12 @@ __git_ps1_show_upstream () # 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 @@ -255,7 +261,7 @@ __git_ps1 () (describe) git describe HEAD ;; (* | default) - git describe --exact-match HEAD ;; + git describe --tags --exact-match HEAD ;; esac 2>/dev/null)" || b="$(cut -c1-7 "$g/HEAD" 2>/dev/null)..." || @@ -321,11 +327,168 @@ __gitcomp_1 () 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 @@ -380,16 +543,20 @@ __git_tags () 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 @@ -401,6 +568,21 @@ __git_refs () 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 @@ -482,7 +664,8 @@ __git_compute_merge_strategies () __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%%:*}" @@ -530,7 +713,8 @@ __git_complete_file () __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%...*}..." @@ -550,11 +734,12 @@ __git_complete_revlist () __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) @@ -622,13 +807,14 @@ __git_complete_remote_or_refspec () __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=}" @@ -711,7 +897,6 @@ __git_list_porcelain_commands () quiltimport) : import;; read-tree) : plumbing;; receive-pack) : plumbing;; - reflog) : plumbing;; remote-*) : transport;; repo-config) : deprecated;; rerere) : plumbing;; @@ -750,6 +935,19 @@ __git_compute_porcelain_commands () : ${__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' @@ -788,10 +986,10 @@ __git_aliased_command () # __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" @@ -804,9 +1002,10 @@ __git_find_on_cmdline () __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)) @@ -818,7 +1017,8 @@ __git_whitespacelist="nowarn warn error error-all fix" _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 @@ -842,7 +1042,8 @@ _git_am () _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=}" @@ -865,7 +1066,8 @@ _git_add () { __git_has_doubledash && return - local cur="${COMP_WORDS[COMP_CWORD]}" + local cur + _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp " @@ -879,7 +1081,8 @@ _git_add () _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=}" @@ -907,12 +1110,16 @@ _git_bisect () 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)" ;; *) @@ -923,10 +1130,11 @@ _git_bisect () _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" ;; @@ -934,7 +1142,7 @@ _git_branch () c=$((++c)) done - case "${COMP_WORDS[COMP_CWORD]}" in + case "$cur" in --*) __gitcomp " --color --no-color --verbose --abbrev= --no-abbrev @@ -954,8 +1162,10 @@ _git_branch () _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" ;; @@ -976,7 +1186,8 @@ _git_checkout () { __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=}" @@ -988,7 +1199,13 @@ _git_checkout () " ;; *) - __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 } @@ -1000,7 +1217,8 @@ _git_cherry () _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" @@ -1015,7 +1233,8 @@ _git_clean () { __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" @@ -1027,7 +1246,8 @@ _git_clean () _git_clone () { - local cur="${COMP_WORDS[COMP_CWORD]}" + local cur + _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp " @@ -1054,7 +1274,8 @@ _git_commit () { __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 @@ -1089,7 +1310,8 @@ _git_commit () _git_describe () { - local cur="${COMP_WORDS[COMP_CWORD]}" + local cur + _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp " @@ -1121,11 +1343,12 @@ _git_diff () { __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 - --base --ours --theirs + --base --ours --theirs --no-index $__git_diff_common_options " return @@ -1142,7 +1365,8 @@ _git_difftool () { __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=}" @@ -1167,7 +1391,8 @@ __git_fetch_options=" _git_fetch () { - local cur="${COMP_WORDS[COMP_CWORD]}" + local cur + _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp "$__git_fetch_options" @@ -1179,7 +1404,8 @@ _git_fetch () _git_format_patch () { - local cur="${COMP_WORDS[COMP_CWORD]}" + local cur + _get_comp_words_by_ref -n =: cur case "$cur" in --thread=*) __gitcomp " @@ -1211,7 +1437,8 @@ _git_format_patch () _git_fsck () { - local cur="${COMP_WORDS[COMP_CWORD]}" + local cur + _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp " @@ -1226,7 +1453,8 @@ _git_fsck () _git_gc () { - local cur="${COMP_WORDS[COMP_CWORD]}" + local cur + _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp "--prune --aggressive" @@ -1245,7 +1473,8 @@ _git_grep () { __git_has_doubledash && return - local cur="${COMP_WORDS[COMP_CWORD]}" + local cur + _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp " @@ -1268,7 +1497,8 @@ _git_grep () _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" @@ -1286,7 +1516,8 @@ _git_help () _git_init () { - local cur="${COMP_WORDS[COMP_CWORD]}" + local cur + _get_comp_words_by_ref -n =: cur case "$cur" in --shared=*) __gitcomp " @@ -1306,7 +1537,8 @@ _git_ls_files () { __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 @@ -1360,20 +1592,21 @@ _git_log () { __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 ;; @@ -1419,7 +1652,8 @@ _git_merge () { __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" @@ -1430,7 +1664,8 @@ _git_merge () _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=}" @@ -1451,7 +1686,8 @@ _git_merge_base () _git_mv () { - local cur="${COMP_WORDS[COMP_CWORD]}" + local cur + _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp "--dry-run" @@ -1468,18 +1704,51 @@ _git_name_rev () _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 } @@ -1488,7 +1757,8 @@ _git_pull () { __git_complete_strategy && return - local cur="${COMP_WORDS[COMP_CWORD]}" + local cur + _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp " @@ -1504,8 +1774,9 @@ _git_pull () _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 @@ -1528,7 +1799,9 @@ _git_push () _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 @@ -1553,12 +1826,25 @@ _git_rebase () __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 " @@ -1600,9 +1886,11 @@ _git_stage () __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" @@ -1630,9 +1918,9 @@ __git_config_get_set_variables () _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 @@ -1642,13 +1930,13 @@ _git_config () 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)' \ @@ -1785,30 +2073,50 @@ _git_config () ;; 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 @@ -1822,21 +2130,29 @@ _git_config () 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 @@ -1846,6 +2162,7 @@ _git_config () core.repositoryFormatVersion core.safecrlf core.sharedRepository + core.sparseCheckout core.symlinks core.trustctime core.warnAmbiguousRefs @@ -1853,15 +2170,17 @@ _git_config () 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 @@ -1873,6 +2192,8 @@ _git_config () format.subjectprefix format.suffix format.thread + format.to + gc. gc.aggressiveWindow gc.auto gc.autopacklimit @@ -1910,15 +2231,20 @@ _git_config () 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 @@ -1927,6 +2253,7 @@ _git_config () imap.sslverify imap.tunnel imap.user + init.templatedir instaweb.browser instaweb.httpd instaweb.local @@ -1934,19 +2261,29 @@ _git_config () 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 @@ -1957,31 +2294,42 @@ _git_config () 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 @@ -1992,6 +2340,8 @@ _git_config () showbranch.default status.relativePaths status.showUntrackedFiles + status.submodulesummary + submodule. tar.umask transfer.unpackLimit url. @@ -2039,7 +2389,8 @@ _git_reset () { __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" @@ -2051,7 +2402,8 @@ _git_reset () _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" @@ -2065,7 +2417,8 @@ _git_rm () { __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" @@ -2079,7 +2432,8 @@ _git_shortlog () { __git_has_doubledash && return - local cur="${COMP_WORDS[COMP_CWORD]}" + local cur + _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp " @@ -2097,15 +2451,16 @@ _git_show () { __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 ;; @@ -2121,7 +2476,8 @@ _git_show () _git_show_branch () { - local cur="${COMP_WORDS[COMP_CWORD]}" + local cur + _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp " @@ -2138,7 +2494,8 @@ _git_show_branch () _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")" @@ -2183,7 +2540,8 @@ _git_submodule () 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" @@ -2227,7 +2585,8 @@ _git_svn () --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" @@ -2299,8 +2658,10 @@ _git_svn () _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)" @@ -2313,7 +2674,7 @@ _git_tag () c=$((++c)) done - case "${COMP_WORDS[COMP_CWORD-1]}" in + case "$prev" in -m|-F) COMPREPLY=() ;; @@ -2339,8 +2700,15 @@ _git () { 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="." ;; @@ -2352,7 +2720,7 @@ _git () done if [ -z "$command" ]; then - case "${COMP_WORDS[COMP_CWORD]}" in + case "$cur" in --*) __gitcomp " --paginate --no-pager @@ -2372,25 +2740,31 @@ _git () 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 " @@ -2417,3 +2791,29 @@ if [ Cygwin = "$(uname -o 2>/dev/null)" ]; then 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 diff --git a/contrib/emacs/git-blame.el b/contrib/emacs/git-blame.el index 7f4c792978..d351cfb6e7 100644 --- a/contrib/emacs/git-blame.el +++ b/contrib/emacs/git-blame.el @@ -79,6 +79,7 @@ ;;; Code: (eval-when-compile (require 'cl)) ; to use `push', `pop' +(require 'format-spec) (defface git-blame-prefix-face '((((background dark)) (:foreground "gray" diff --git a/contrib/examples/builtin-fetch--tool.c b/contrib/examples/builtin-fetch--tool.c index cd10dbcbc9..3140e405fa 100644 --- a/contrib/examples/builtin-fetch--tool.c +++ b/contrib/examples/builtin-fetch--tool.c @@ -148,7 +148,7 @@ static int append_fetch_head(FILE *fp, what = remote_name + 10; } else if (!strncmp(remote_name, "refs/remotes/", 13)) { - kind = "remote branch"; + kind = "remote-tracking branch"; what = remote_name + 13; } else { diff --git a/contrib/examples/git-svnimport.perl b/contrib/examples/git-svnimport.perl index 4576c4a862..b09ff8f12f 100755 --- a/contrib/examples/git-svnimport.perl +++ b/contrib/examples/git-svnimport.perl @@ -1,4 +1,4 @@ -#!/usr/bin/perl -w +#!/usr/bin/perl # This tool is copyright (c) 2005, Matthias Urlichs. # It is released under the Gnu Public License, version 2. @@ -289,7 +289,7 @@ my $current_rev = $opt_s || 1; 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; diff --git a/contrib/fast-import/git-p4 b/contrib/fast-import/git-p4 index c1ea643ace..04ce7e3b02 100755 --- a/contrib/fast-import/git-p4 +++ b/contrib/fast-import/git-p4 @@ -706,7 +706,9 @@ class P4Submit(Command): submitTemplate = self.prepareLogMessage(template, logMessage) if os.environ.has_key("P4DIFF"): del(os.environ["P4DIFF"]) - diff = p4_read_pipe("diff -du ...") + diff = "" + for editedFile in editedFiles: + diff += p4_read_pipe("diff -du %r" % editedFile) newdiff = "" for newFile in filesToAdd: diff --git a/contrib/fast-import/import-directories.perl b/contrib/fast-import/import-directories.perl index 3a5da4ab00..7f3afa5ac4 100755 --- a/contrib/fast-import/import-directories.perl +++ b/contrib/fast-import/import-directories.perl @@ -1,4 +1,4 @@ -#!/usr/bin/perl -w +#!/usr/bin/perl # # Copyright 2008-2009 Peter Krefting <peter@softwolves.pp.se> # @@ -140,6 +140,7 @@ by whitespace or other characters. # Globals use strict; +use warnings; use integer; my $crlfmode = 0; my @revs; diff --git a/contrib/git-shell-commands/README b/contrib/git-shell-commands/README new file mode 100644 index 0000000000..438463b160 --- /dev/null +++ b/contrib/git-shell-commands/README @@ -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 new file mode 100755 index 0000000000..535770c6ec --- /dev/null +++ b/contrib/git-shell-commands/help @@ -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 new file mode 100755 index 0000000000..6f89938821 --- /dev/null +++ b/contrib/git-shell-commands/list @@ -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 diff --git a/contrib/hooks/post-receive-email b/contrib/hooks/post-receive-email index 0085086437..21989fc6ab 100755 --- a/contrib/hooks/post-receive-email +++ b/contrib/hooks/post-receive-email @@ -71,19 +71,10 @@ # ---------------------------- Functions # -# Top level email generation function. This decides what type of update -# this is and calls the appropriate body-generation routine after outputting -# the common header +# Function to prepare for email generation. This decides what type +# of update this is and whether an email should even be generated. # -# Note this function doesn't actually generate any email output, that is -# taken care of by the functions it calls: -# - generate_email_header -# - generate_create_XXXX_email -# - generate_update_XXXX_email -# - generate_delete_XXXX_email -# - generate_email_footer -# -generate_email() +prep_for_email() { # --- Arguments oldrev=$(git rev-parse $1) @@ -153,13 +144,13 @@ generate_email() short_refname=${refname##refs/remotes/} echo >&2 "*** Push-update of tracking branch, $refname" echo >&2 "*** - no email generated." - exit 0 + return 1 ;; *) # Anything else (is there anything else?) echo >&2 "*** Unknown type of update to $refname ($rev_type)" echo >&2 "*** - no email generated" - exit 1 + return 1 ;; esac @@ -175,9 +166,32 @@ generate_email() esac echo >&2 "*** $config_name is not set so no email will be sent" echo >&2 "*** for $refname update $oldrev->$newrev" - exit 0 + return 1 fi + return 0 +} + +# +# Top level email generation function. This calls the appropriate +# body-generation routine after outputting the common header. +# +# Note this function doesn't actually generate any email output, that is +# taken care of by the functions it calls: +# - generate_email_header +# - generate_create_XXXX_email +# - generate_update_XXXX_email +# - generate_delete_XXXX_email +# - generate_email_footer +# +# Note also that this function cannot 'exit' from the script; when this +# function is running (in hook script mode), the send_mail() function +# is already executing in another process, connected via a pipe, and +# if this function exits without, whatever has been generated to that +# point will be sent as an email... even if nothing has been generated. +# +generate_email() +{ # Email parameters # The email subject will contain the best description of the ref # that we can build from the parameters @@ -695,7 +709,7 @@ if [ -z "$GIT_DIR" ]; then 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 @@ -717,10 +731,11 @@ if [ -n "$1" -a -n "$2" -a -n "$3" ]; then # Output to the terminal in command line mode - if someone wanted to # resend an email; they could redirect the output to sendmail # themselves - PAGER= generate_email $2 $3 $1 + prep_for_email $2 $3 $1 && PAGER= generate_email else while read oldrev newrev refname do - generate_email $oldrev $newrev $refname $maxlines | send_mail + prep_for_email $oldrev $newrev $refname || continue + generate_email $maxlines | send_mail done fi diff --git a/contrib/svn-fe/svn-fe.txt b/contrib/svn-fe/svn-fe.txt index 35f84bd9e7..cd075b96c5 100644 --- a/contrib/svn-fe/svn-fe.txt +++ b/contrib/svn-fe/svn-fe.txt @@ -18,6 +18,9 @@ Subversion repository mirrored on the local disk. Remote Subversion 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/contrib/thunderbird-patch-inline/appp.sh b/contrib/thunderbird-patch-inline/appp.sh index cc518f3c89..5eb4a51643 100755 --- a/contrib/thunderbird-patch-inline/appp.sh +++ b/contrib/thunderbird-patch-inline/appp.sh @@ -1,8 +1,8 @@ -#!/bin/bash +#!/bin/sh # Copyright 2008 Lukas Sandström <luksan@gmail.com> # # AppendPatch - A script to be used together with ExternalEditor -# for Mozilla Thunderbird to properly include pathes inline i e-mails. +# for Mozilla Thunderbird to properly include patches inline in e-mails. # ExternalEditor can be downloaded at http://globs.org/articles.php?lng=en&pg=2 diff --git a/contrib/workdir/git-new-workdir b/contrib/workdir/git-new-workdir index 3ad2c0cea5..75e8b25817 100755 --- a/contrib/workdir/git-new-workdir +++ b/contrib/workdir/git-new-workdir @@ -42,7 +42,7 @@ then fi # don't link to a workdir -if test -L "$git_dir/config" +if test -h "$git_dir/config" then die "\"$orig_git\" is a working directory only, please specify" \ "a complete repository." @@ -1,6 +1,7 @@ #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. @@ -17,7 +18,7 @@ enum action { CRLF_TEXT, CRLF_INPUT, CRLF_CRLF, - CRLF_AUTO, + CRLF_AUTO }; struct text_stat { @@ -318,6 +319,7 @@ struct filter_params { const char *src; unsigned long size; const char *cmd; + const char *path; }; static int filter_buffer(int in, int out, void *data) @@ -330,7 +332,23 @@ 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; @@ -350,6 +368,8 @@ static int filter_buffer(int in, int out, void *data) status = finish_command(&child_process); if (status) error("external filter %s failed %d", params->cmd, status); + + strbuf_release(&cmd); return (write_err || status); } @@ -377,6 +397,7 @@ static int apply_filter(const char *path, const char *src, size_t len, params.src = src; params.size = len; params.cmd = cmd; + params.path = path; fflush(NULL); if (start_async(&async)) @@ -3,8 +3,7 @@ #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 @@ -14,21 +13,25 @@ #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; static const char daemon_usage[] = "git daemon [--verbose] [--syslog] [--export-all]\n" -" [--timeout=n] [--init-timeout=n] [--max-connections=n]\n" -" [--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" -" [--[enable|disable|allow-override|forbid-override]=service]\n" -" [--inetd | [--listen=host_or_ipaddr] [--port=n]\n" -" [--user=user [--group=group]]\n" -" [directory...]"; +" [--timeout=<n>] [--init-timeout=<n>] [--max-connections=<n>]\n" +" [--strict-paths] [--base-path=<path>] [--base-path-relaxed]\n" +" [--user-path | --user-path=<path>]\n" +" [--interpolated-path=<path>]\n" +" [--reuseaddr] [--pid-file=<file>]\n" +" [--(enable|disable|allow-override|forbid-override)=<service>]\n" +" [--inetd | [--listen=<host_or_ipaddr>] [--port=<n>]\n" +" [--detach] [--user=<user> [--group=<group>]]\n" +" [<directory>...]"; /* List of acceptable pathname prefixes */ static char **ok_paths; @@ -68,12 +71,14 @@ static void logreport(int priority, const char *err, va_list params) 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); } } @@ -515,37 +520,14 @@ static void parse_host_arg(char *extra_args, int buflen) } -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)); @@ -615,17 +597,17 @@ static unsigned int live_children; 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)) @@ -634,19 +616,6 @@ static void add_child(pid_t pid, struct sockaddr *addr, int addrlen) *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". @@ -662,7 +631,7 @@ static void kill_some_child(void) for (; (next = blanket->next); blanket = next) if (!addrcmp(&blanket->address, &next->address)) { - kill(blanket->pid, SIGTERM); + kill(blanket->cld.pid, SIGTERM); break; } } @@ -672,18 +641,28 @@ static void check_dead_children(void) 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(); @@ -696,22 +675,37 @@ static void handle(int incoming, struct sockaddr *addr, int addrlen) } } - 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) @@ -734,11 +728,17 @@ static int set_reuse_addr(int sockfd) &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; @@ -753,8 +753,10 @@ static int socksetup(char *listen_addr, int listen_port, int **socklist_p) 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; @@ -795,8 +797,9 @@ static int socksetup(char *listen_addr, int listen_port, int **socklist_p) 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; @@ -804,13 +807,12 @@ static int socksetup(char *listen_addr, int listen_port, int **socklist_p) 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; @@ -851,22 +853,39 @@ static int socksetup(char *listen_addr, int listen_port, int **socklist_p) 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; } @@ -877,7 +896,7 @@ static int service_loop(int socknum, int *socklist) 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)); @@ -886,11 +905,17 @@ static int service_loop(int socknum, int *socklist) 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: @@ -901,7 +926,7 @@ static int service_loop(int socknum, int *socklist) die_errno("accept returned"); } } - handle(incoming, (struct sockaddr *)&ss, sslen); + handle(incoming, &ss.sa, sslen); } } } @@ -919,6 +944,62 @@ static void sanitize_stdfds(void) 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()) { @@ -936,6 +1017,7 @@ static void daemonize(void) close(2); sanitize_stdfds(); } +#endif static void store_pid(const char *path) { @@ -946,33 +1028,29 @@ 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]); @@ -981,7 +1059,7 @@ int main(int argc, char **argv) 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=")) { @@ -993,6 +1071,10 @@ int main(int argc, char **argv) continue; } } + if (!strcmp(arg, "--serve")) { + serve_mode = 1; + continue; + } if (!strcmp(arg, "--inetd")) { inetd_mode = 1; log_syslog = 1; @@ -1101,12 +1183,12 @@ int main(int argc, char **argv) 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; @@ -1114,21 +1196,8 @@ int main(int argc, char **argv) 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"); @@ -1138,19 +1207,13 @@ int main(int argc, char **argv) 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"); @@ -1161,5 +1224,13 @@ int main(int argc, char **argv) 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 392ce2bef0..f8e33256eb 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -103,7 +103,8 @@ int run_diff_files(struct rev_info *revs, unsigned int option) unsigned dirty_submodule = 0; if (DIFF_OPT_TST(&revs->diffopt, QUICK) && - DIFF_OPT_TST(&revs->diffopt, HAS_CHANGES)) + !revs->diffopt.filter && + DIFF_OPT_TST(&revs->diffopt, HAS_CHANGES)) break; if (!ce_path_match(ce, revs->prune_data)) @@ -245,6 +245,15 @@ static int fill_mmfile(mmfile_t *mf, struct diff_filespec *one) 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; @@ -606,16 +615,14 @@ static void diff_words_append(char *line, unsigned long len, buffer->text.ptr[buffer->text.size] = '\0'; } -struct diff_words_style_elem -{ +struct diff_words_style_elem { const char *prefix; const char *suffix; const char *color; /* NULL; filled in by the setup code if * color is enabled */ }; -struct diff_words_style -{ +struct diff_words_style { enum diff_words_type type; struct diff_words_style_elem new, old, ctx; const char *newline; @@ -1771,8 +1778,14 @@ static void emit_binary_diff(FILE *file, mmfile_t *one, mmfile_t *two, char *pre static void diff_filespec_load_driver(struct diff_filespec *one) { - if (!one->driver) + /* Use already-loaded driver */ + if (one->driver) + return; + + if (S_ISREG(one->mode)) one->driver = userdiff_find_by_path(one->path); + + /* Fallback to default settings */ if (!one->driver) one->driver = userdiff_find_by_name("default"); } @@ -1820,8 +1833,7 @@ struct userdiff_driver *get_textconv(struct diff_filespec *one) { if (!DIFF_FILE_VALID(one)) return NULL; - if (!S_ISREG(one->mode)) - return NULL; + diff_filespec_load_driver(one); if (!one->driver->textconv) return NULL; @@ -2074,25 +2086,28 @@ static void builtin_diffstat(const char *name_a, const char *name_b, 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; @@ -2100,7 +2115,6 @@ static void builtin_diffstat(const char *name_a, const char *name_b, &xpp, &xecfg); } - free_and_return: diff_free_filespec_data(one); diff_free_filespec_data(two); } @@ -2153,7 +2167,7 @@ static void builtin_checkdiff(const char *name_a, const char *name_b, ecbdata.ws_rule = data.ws_rule; check_blank_at_eof(&mf1, &mf2, &ecbdata); - blank_at_eof = ecbdata.blank_at_eof_in_preimage; + blank_at_eof = ecbdata.blank_at_eof_in_postimage; if (blank_at_eof) { static char *err; @@ -2386,10 +2400,14 @@ int diff_populate_filespec(struct diff_filespec *s, int size_only) } else { enum object_type type; - if (size_only) + if (size_only) { type = sha1_object_info(s->sha1, &s->size); - else { + if (type < 0) + die("unable to read %s", sha1_to_hex(s->sha1)); + } else { s->data = read_sha1_file(s->sha1, &type, &s->size); + if (!s->data) + die("unable to read %s", sha1_to_hex(s->sha1)); s->should_free = 1; } } @@ -3140,20 +3158,23 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) 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")) @@ -3271,12 +3292,17 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) } 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; @@ -3310,7 +3336,7 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) else if ((argcount = parse_long_opt("output", av, &optarg))) { options->file = fopen(optarg, "w"); if (!options->file) - die_errno("Could not open '%s'", arg + strlen("--output=")); + die_errno("Could not open '%s'", optarg); options->close_file = 1; return argcount; } else @@ -3318,7 +3344,7 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) 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; @@ -3361,10 +3387,26 @@ static int diff_scoreopt_parse(const char *opt) 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 { @@ -3374,7 +3416,7 @@ static int diff_scoreopt_parse(const char *opt) return -1; /* we expect -B80/99 or -B80 */ else { opt++; - opt2 = parse_num(&opt); + opt2 = parse_rename_score(&opt); } } if (*opt != 0) @@ -3527,7 +3569,7 @@ static void diff_flush_stat(struct diff_filepair *p, struct diff_options *o, if ((DIFF_FILE_VALID(p->one) && S_ISDIR(p->one->mode)) || (DIFF_FILE_VALID(p->two) && S_ISDIR(p->two->mode))) - return; /* no tree diffs in patch format */ + return; /* no useful stat for tree diffs */ run_diffstat(p, o, diffstat); } @@ -3540,7 +3582,7 @@ static void diff_flush_checkdiff(struct diff_filepair *p, if ((DIFF_FILE_VALID(p->one) && S_ISDIR(p->one->mode)) || (DIFF_FILE_VALID(p->two) && S_ISDIR(p->two->mode))) - return; /* no tree diffs in patch format */ + return; /* nothing to check in tree diffs */ run_checkdiff(p, o); } @@ -3865,7 +3907,7 @@ static int diff_get_patch_id(struct diff_options *options, unsigned char *sha1) xpp.flags = 0; xecfg.ctxlen = 3; - xecfg.flags = XDL_EMIT_FUNCNAMES; + xecfg.flags = 0; xdi_diff_outf(&mf1, &mf2, patch_id_consume, &data, &xpp, &xecfg); } @@ -4176,7 +4218,7 @@ void diffcore_std(struct diff_options *options) 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) @@ -4379,7 +4421,7 @@ size_t fill_textconv(struct userdiff_driver *driver, return df->size; } - if (driver->textconv_cache) { + if (driver->textconv_cache && df->sha1_valid) { *outbuf = notes_cache_get(driver->textconv_cache, df->sha1, &size); if (*outbuf) @@ -4390,7 +4432,7 @@ size_t fill_textconv(struct userdiff_driver *driver, if (!*outbuf) die("unable to read files to diff"); - if (driver->textconv_cache) { + if (driver->textconv_cache && df->sha1_valid) { /* ignore errors, as we might be in a readonly repository */ notes_cache_put(driver->textconv_cache, df->sha1, *outbuf, size); @@ -238,6 +238,9 @@ extern int diff_setup_done(struct diff_options *); #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 *); @@ -312,4 +315,6 @@ extern size_t fill_textconv(struct userdiff_driver *driver, 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 929de15aa9..ea03b9107e 100644 --- a/diffcore-pickaxe.c +++ b/diffcore-pickaxe.c @@ -1,9 +1,148 @@ /* * 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, @@ -48,8 +187,10 @@ static unsigned int contains(struct diff_filespec *one, 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; @@ -128,11 +269,19 @@ void diffcore_pickaxe(const char *needle, int opts) diff_free_filepair(p); } - if (opts & DIFF_PICKAXE_REGEX) { + if (opts & DIFF_PICKAXE_REGEX) regfree(®ex); - } free(q->queue); *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 df41be56de..0cd4c1305b 100644 --- a/diffcore-rename.c +++ b/diffcore-rename.c @@ -170,7 +170,7 @@ static int estimate_similarity(struct diff_filespec *src, * 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)) @@ -247,7 +247,8 @@ struct file_similarity { }; static int find_identical_files(struct file_similarity *src, - struct file_similarity *dst) + struct file_similarity *dst, + struct diff_options *options) { int renames = 0; @@ -277,6 +278,8 @@ static int find_identical_files(struct file_similarity *src, } /* 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; @@ -306,11 +309,12 @@ static void free_similarity_list(struct file_similarity *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 { @@ -329,7 +333,7 @@ static int find_same_files(void *ptr) * 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); @@ -377,7 +381,7 @@ static void insert_file_table(struct hash_table *table, int src_dst, int index, * 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; @@ -390,7 +394,7 @@ static int find_exact_renames(void) 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); @@ -414,6 +418,27 @@ static void record_if_better(struct diff_score m[], struct diff_score *o) 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; @@ -467,7 +492,7 @@ void diffcore_rename(struct diff_options *options) * 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) @@ -536,33 +561,9 @@ void diffcore_rename(struct diff_options *options) /* 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 8b3241ad13..b8f1fdecf4 100644 --- a/diffcore.h +++ b/diffcore.h @@ -107,7 +107,7 @@ extern void diff_q(struct diff_queue_struct *, struct diff_filepair *); 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 @@ -18,6 +18,22 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, in 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; @@ -91,16 +107,30 @@ static int match_one(const char *match, const char *name, int namelen) 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--; + } } @@ -109,8 +139,8 @@ static int match_one(const char *match, const char *name, int 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; @@ -223,6 +253,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, @@ -232,7 +274,7 @@ int add_excludes_from_file_to_list(const char *fname, { struct stat st; int fd, i; - size_t size; + size_t size = 0; char *buf, *entry; fd = open(fname, O_RDONLY); @@ -359,12 +401,6 @@ int excluded_from_list(const char *pathname, int to_exclude = x->to_exclude; if (x->flags & EXC_FLAG_MUSTBEDIR) { - if (!dtype) { - if (!prefixcmp(pathname, exclude)) - return to_exclude; - else - continue; - } if (*dtype == DT_UNKNOWN) *dtype = get_dtype(NULL, pathname, pathlen); if (*dtype != DT_DIR) @@ -374,14 +410,14 @@ int excluded_from_list(const char *pathname, 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; } } @@ -396,14 +432,14 @@ int excluded_from_list(const char *pathname, 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; } @@ -469,6 +505,39 @@ enum exist_status { }; /* + * 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 * a directory (which is defined not as an entry, but as @@ -477,7 +546,12 @@ enum exist_status { */ 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) { @@ -964,6 +1038,12 @@ char *get_relative_cwd(char *buffer, int size, const char *dir) case '/': return cwd + 1; default: + /* + * dir can end with a path separator when it's root + * directory. Return proper prefix in that case. + */ + if (dir[-1] == '/') + return cwd; return NULL; } } @@ -78,6 +78,7 @@ extern int add_excludes_from_file_to_list(const char *fname, const char *base, i 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); @@ -101,4 +102,8 @@ extern int remove_dir_recursively(struct strbuf *path, int flag); /* 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 @@ -106,14 +106,14 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout case S_IFLNK: new = read_blob_entry(ce, &size); if (!new) - return error("git checkout-index: unable to read sha1 file of %s (%s)", + return error("unable to read sha1 file of %s (%s)", path, sha1_to_hex(ce->sha1)); if (ce_mode_s_ifmt == S_IFLNK && has_symlinks && !to_tempfile) { ret = symlink(new, path); free(new); if (ret) - return error("git checkout-index: unable to create symlink %s (%s)", + return error("unable to create symlink %s (%s)", path, strerror(errno)); break; } @@ -141,7 +141,7 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout } if (fd < 0) { free(new); - return error("git checkout-index: unable to create file %s (%s)", + return error("unable to create file %s (%s)", path, strerror(errno)); } @@ -155,16 +155,16 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout close(fd); free(new); if (wrote != size) - return error("git checkout-index: unable to write file %s", path); + return error("unable to write file %s", path); break; case S_IFGITLINK: if (to_tempfile) - return error("git checkout-index: cannot create temporary subproject %s", path); + return error("cannot create temporary subproject %s", path); if (mkdir(path, 0777) < 0) - return error("git checkout-index: cannot create subproject directory %s", path); + return error("cannot create subproject directory %s", path); break; default: - return error("git checkout-index: unknown file mode for %s", path); + return error("unknown file mode for %s in index", path); } if (state->refresh_cache) { @@ -211,7 +211,7 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *t return 0; if (!state->force) { if (!state->quiet) - fprintf(stderr, "git-checkout-index: %s already exists\n", path); + fprintf(stderr, "%s already exists, no checkout\n", path); return -1; } diff --git a/environment.c b/environment.c index 2d0c315379..33c806421e 100644 --- a/environment.c +++ b/environment.c @@ -15,6 +15,7 @@ int user_ident_explicitly_given; int trust_executable_bit = 1; int trust_ctime = 1; int has_symlinks = 1; +int minimum_abbrev = 4, default_abbrev = 7; int ignore_case; int assume_unchanged; int prefer_symlink_refs; @@ -64,7 +65,7 @@ char *git_work_tree_cfg; static char *work_tree; static const char *git_dir; -static char *git_object_dir, *git_index_file, *git_refs_dir, *git_graft_file; +static char *git_object_dir, *git_index_file, *git_graft_file; /* * Repository-local GIT_* environment variables @@ -87,8 +88,11 @@ const char * const local_repo_env[LOCAL_REPO_ENV_SIZE + 1] = { static void setup_git_env(void) { git_dir = getenv(GIT_DIR_ENVIRONMENT); - if (!git_dir) + git_dir = git_dir ? xstrdup(git_dir) : NULL; + if (!git_dir) { git_dir = read_gitfile_gently(DEFAULT_GIT_DIR_ENVIRONMENT); + git_dir = git_dir ? xstrdup(git_dir) : NULL; + } if (!git_dir) git_dir = DEFAULT_GIT_DIR_ENVIRONMENT; git_object_dir = getenv(DB_ENVIRONMENT); @@ -96,8 +100,6 @@ static void setup_git_env(void) git_object_dir = xmalloc(strlen(git_dir) + 9); sprintf(git_object_dir, "%s/objects", git_dir); } - git_refs_dir = xmalloc(strlen(git_dir) + 6); - sprintf(git_refs_dir, "%s/refs", git_dir); git_index_file = getenv(INDEX_ENVIRONMENT); if (!git_index_file) { git_index_file = xmalloc(strlen(git_dir) + 7); @@ -137,30 +139,20 @@ static int git_work_tree_initialized; */ 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; } @@ -171,6 +163,43 @@ char *get_object_directory(void) 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) @@ -192,3 +221,14 @@ int set_git_dir(const char *path) 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/exec_cmd.c b/exec_cmd.c index bf225706ee..38545e8bfd 100644 --- a/exec_cmd.c +++ b/exec_cmd.c @@ -3,7 +3,6 @@ #include "quote.h" #define MAX_ARGS 32 -extern char **environ; static const char *argv_exec_path; static const char *argv0_path; diff --git a/fast-import.c b/fast-import.c index 2317b0fe75..b3ba778924 100644 --- a/fast-import.c +++ b/fast-import.c @@ -132,14 +132,17 @@ Format of STDIN stream: 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 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; + comment ::= '#' not_lf* lf; not_lf ::= # Any byte that is not ASCII newline (LF); */ @@ -156,14 +159,14 @@ Format of STDIN stream: #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) #define DEPTH_BITS 13 #define MAX_DEPTH ((1<<DEPTH_BITS)-1) -struct object_entry -{ +struct object_entry { struct pack_idx_entry idx; struct object_entry *next; uint32_t type : TYPE_BITS, @@ -171,16 +174,14 @@ struct object_entry depth : DEPTH_BITS; }; -struct object_entry_pool -{ +struct object_entry_pool { struct object_entry_pool *next_pool; struct object_entry *next_free; struct object_entry *end; struct object_entry entries[FLEX_ARRAY]; /* more */ }; -struct mark_set -{ +struct mark_set { union { struct object_entry *marked[1024]; struct mark_set *sets[1024]; @@ -188,57 +189,49 @@ struct mark_set unsigned int shift; }; -struct last_object -{ +struct last_object { struct strbuf data; off_t offset; unsigned int depth; unsigned no_swap : 1; }; -struct mem_pool -{ +struct mem_pool { struct mem_pool *next_pool; char *next_free; char *end; uintmax_t space[FLEX_ARRAY]; /* more */ }; -struct atom_str -{ +struct atom_str { struct atom_str *next_atom; unsigned short str_len; char str_dat[FLEX_ARRAY]; /* more */ }; struct tree_content; -struct tree_entry -{ +struct tree_entry { struct tree_content *tree; struct atom_str *name; - struct tree_entry_ms - { + struct tree_entry_ms { uint16_t mode; unsigned char sha1[20]; } versions[2]; }; -struct tree_content -{ +struct tree_content { unsigned int entry_capacity; /* must match avail_tree_content */ unsigned int entry_count; unsigned int delta_depth; struct tree_entry *entries[FLEX_ARRAY]; /* more */ }; -struct avail_tree_content -{ +struct avail_tree_content { unsigned int entry_capacity; /* must match tree_content */ struct avail_tree_content *next_avail; }; -struct branch -{ +struct branch { struct branch *table_next_branch; struct branch *active_next_branch; const char *name; @@ -250,16 +243,14 @@ struct branch unsigned char sha1[20]; }; -struct tag -{ +struct tag { struct tag *next_tag; const char *name; unsigned int pack_id; unsigned char sha1[20]; }; -struct hash_list -{ +struct hash_list { struct hash_list *next; unsigned char sha1[20]; }; @@ -270,8 +261,7 @@ typedef enum { WHENSPEC_NOW } whenspec_type; -struct recent_command -{ +struct recent_command { struct recent_command *prev; struct recent_command *next; char *buf; @@ -361,7 +351,14 @@ static uintmax_t next_mark; 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 write_branch_report(FILE *rpt, struct branch *b) { @@ -500,6 +497,32 @@ static NORETURN void die_nicely(const char *err, va_list params) 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; @@ -539,22 +562,17 @@ static struct object_entry *insert_object(unsigned char *sha1) { unsigned int h = sha1[0] << 8 | sha1[1]; struct object_entry *e = object_table[h]; - struct object_entry *p = NULL; while (e) { if (!hashcmp(sha1, e->idx.sha1)) return e; - p = e; e = e->next; } e = new_object(sha1); - e->next = NULL; + e->next = object_table[h]; e->idx.offset = 0; - if (p) - p->next = e; - else - object_table[h] = e; + object_table[h] = e; return e; } @@ -839,6 +857,7 @@ static void start_packfile(void) p = xcalloc(1, sizeof(*p) + strlen(tmpfile) + 2); strcpy(p->pack_name, tmpfile); p->pack_fd = pack_fd; + p->do_not_close = 1; pack_file = sha1fd(pack_fd, p->pack_name); hdr.hdr_signature = htonl(PACK_SIGNATURE); @@ -1437,6 +1456,20 @@ static void store_tree(struct tree_entry *root) 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, @@ -1444,7 +1477,7 @@ static int tree_content_set( 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; @@ -1459,9 +1492,12 @@ static int tree_content_set( 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 @@ -1514,7 +1550,7 @@ static int tree_content_remove( 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; @@ -1525,9 +1561,12 @@ static int tree_content_remove( 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 @@ -1572,7 +1611,7 @@ static int tree_content_get( 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; @@ -1583,9 +1622,12 @@ static int tree_content_get( 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)) @@ -1781,7 +1823,7 @@ static int read_next_command(void) return EOF; } - do { + for (;;) { if (unread_command_buf) { unread_command_buf = 0; } else { @@ -1814,9 +1856,14 @@ static int read_next_command(void) 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) @@ -2171,6 +2218,12 @@ static void file_change_m(struct branch *b) p = uq.buf; } + /* Git does not track empty, non-toplevel directories. */ + if (S_ISDIR(mode) && !memcmp(sha1, EMPTY_TREE_SHA1_BIN, 20) && *p) { + tree_content_remove(&b->branch_tree, p, NULL); + return; + } + if (S_ISGITLINK(mode)) { if (inline_data) die("Git links cannot be specified 'inline': %s", @@ -2209,6 +2262,10 @@ static void file_change_m(struct branch *b) 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); } @@ -2267,6 +2324,13 @@ static void file_change_cr(struct branch *b, int rename) 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, @@ -2680,14 +2744,95 @@ static void parse_reset_branch(void) 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 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(); } @@ -2737,16 +2882,25 @@ 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) @@ -2755,6 +2909,14 @@ 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) @@ -2808,12 +2970,16 @@ static int parse_one_feature(const char *feature, int from_stream) option_import_marks(feature + 13, from_stream); } 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")) { + ; /* do nothing; we have the feature */ } else { return 0; } @@ -2884,7 +3050,7 @@ static int git_pack_config(const char *k, const char *v, void *cb) } static const char fast_import_usage[] = -"git fast-import [--date-format=f] [--max-pack-size=n] [--big-file-threshold=n] [--depth=n] [--active-branches=n] [--export-marks=marks.file]"; +"git fast-import [--date-format=<f>] [--max-pack-size=<n>] [--big-file-threshold=<n>] [--depth=<n>] [--active-branches=<n>] [--export-marks=<marks.file>]"; static void parse_argv(void) { @@ -2902,6 +3068,11 @@ static void parse_argv(void) 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) @@ -2944,6 +3115,7 @@ int main(int argc, const char **argv) 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(); @@ -2965,6 +3137,9 @@ int main(int argc, const char **argv) /* 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/fetch-pack.h b/fetch-pack.h index fbe85ac05f..0608edae3f 100644 --- a/fetch-pack.h +++ b/fetch-pack.h @@ -1,8 +1,7 @@ #ifndef FETCH_PACK_H #define FETCH_PACK_H -struct fetch_pack_args -{ +struct fetch_pack_args { const char *uploadpack; int unpacklimit; int depth; diff --git a/generate-cmdlist.sh b/generate-cmdlist.sh index 75c68d948f..3ef4861d04 100755 --- a/generate-cmdlist.sh +++ b/generate-cmdlist.sh @@ -1,8 +1,7 @@ #!/bin/sh echo "/* Automatically generated by $0 */ -struct cmdname_help -{ +struct cmdname_help { char name[16]; char help[80]; }; diff --git a/git-add--interactive.perl b/git-add--interactive.perl index 27fc79347a..a329c5a1f8 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -1,6 +1,8 @@ -#!/usr/bin/perl -w +#!/usr/bin/perl +use 5.008; use strict; +use warnings; use Git; binmode(STDOUT, ":raw"); @@ -87,6 +89,7 @@ my %patch_modes = ( TARGET => '', PARTICIPLE => 'staging', FILTER => 'file-only', + IS_REVERSE => 0, }, 'stash' => { DIFF => 'diff-index -p HEAD', @@ -96,6 +99,7 @@ my %patch_modes = ( TARGET => '', PARTICIPLE => 'stashing', FILTER => undef, + IS_REVERSE => 0, }, 'reset_head' => { DIFF => 'diff-index -p --cached', @@ -105,6 +109,7 @@ my %patch_modes = ( TARGET => '', PARTICIPLE => 'unstaging', FILTER => 'index-only', + IS_REVERSE => 1, }, 'reset_nothead' => { DIFF => 'diff-index -R -p --cached', @@ -114,6 +119,7 @@ my %patch_modes = ( TARGET => ' to index', PARTICIPLE => 'applying', FILTER => 'index-only', + IS_REVERSE => 0, }, 'checkout_index' => { DIFF => 'diff-files -p', @@ -123,6 +129,7 @@ my %patch_modes = ( TARGET => ' from worktree', PARTICIPLE => 'discarding', FILTER => 'file-only', + IS_REVERSE => 1, }, 'checkout_head' => { DIFF => 'diff-index -p', @@ -132,6 +139,7 @@ my %patch_modes = ( TARGET => ' from index and worktree', PARTICIPLE => 'discarding', FILTER => undef, + IS_REVERSE => 1, }, 'checkout_nothead' => { DIFF => 'diff-index -R -p', @@ -141,6 +149,7 @@ my %patch_modes = ( TARGET => ' to index and worktree', PARTICIPLE => 'applying', FILTER => undef, + IS_REVERSE => 0, }, ); @@ -999,10 +1008,12 @@ sub edit_hunk_manually { print $fh "# Manual hunk edit mode -- see bottom for a quick guide\n"; print $fh @$oldtext; my $participle = $patch_mode_flavour{PARTICIPLE}; + my $is_reverse = $patch_mode_flavour{IS_REVERSE}; + my ($remove_plus, $remove_minus) = $is_reverse ? ('-', '+') : ('+', '-'); print $fh <<EOF; # --- -# To remove '-' lines, make them ' ' lines (context). -# To remove '+' lines, delete them. +# To remove '$remove_minus' lines, make them ' ' lines (context). +# To remove '$remove_plus' lines, delete them. # Lines starting with # will be removed. # # If the patch applies cleanly, the edited hunk will immediately be @@ -5,7 +5,7 @@ SUBDIRECTORY_OK=Yes OPTIONS_KEEPDASHDASH= OPTIONS_SPEC="\ -git am [options] [<mbox>|<Maildir>...] +git am [options] [(<mbox>|<Maildir>)...] git am [options] (--resolved | --skip | --abort) -- i,interactive run interactively @@ -68,9 +68,31 @@ sq () { stop_here () { echo "$1" >"$dotest/next" + git rev-parse --verify -q HEAD >"$dotest/abort-safety" exit 1 } +safe_to_abort () { + if test -f "$dotest/dirtyindex" + then + return 1 + fi + + if ! test -s "$dotest/abort-safety" + then + return 0 + fi + + abort_safety=$(cat "$dotest/abort-safety") + if test "z$(git rev-parse --verify -q HEAD)" = "z$abort_safety" + then + return 0 + fi + echo >&2 "You seem to have moved HEAD since the last 'am' failure." + echo >&2 "Not rewinding to ORIG_HEAD" + return 1 +} + stop_here_user_resolve () { if [ -n "$resolvemsg" ]; then printf '%s\n' "$resolvemsg" @@ -137,7 +159,7 @@ It does not apply to blobs recorded in its index." export GITHEAD_$his_tree if test -n "$GIT_QUIET" then - export GIT_MERGE_VERBOSITY=0 + GIT_MERGE_VERBOSITY=0 && export GIT_MERGE_VERBOSITY fi git-merge-recursive $orig_tree -- HEAD $his_tree || { git rerere $allow_rerere_autoupdate @@ -419,10 +441,11 @@ then exec git rebase --abort fi git rerere clear - test -f "$dotest/dirtyindex" || { + if safe_to_abort + then git read-tree --reset -u HEAD ORIG_HEAD git reset ORIG_HEAD - } + fi rm -fr "$dotest" exit ;; esac @@ -444,12 +467,12 @@ else 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 @@ -554,13 +577,6 @@ then resume= fi -if test "$this" -gt "$last" -then - say Nothing to do. - rm -fr "$dotest" - exit -fi - while test "$this" -le "$last" do msgnum=`printf "%0${prec}d" $this` diff --git a/git-archimport.perl b/git-archimport.perl index 98f3ede566..bc32f18d6d 100755 --- a/git-archimport.perl +++ b/git-archimport.perl @@ -1,4 +1,4 @@ -#!/usr/bin/perl -w +#!/usr/bin/perl # # This tool is copyright (c) 2005, Martin Langhoff. # It is released under the Gnu Public License, version 2. @@ -54,6 +54,7 @@ and can contain multiple, unrelated branches. =cut +use 5.008; use strict; use warnings; use Getopt::Std; diff --git a/git-bisect.sh b/git-bisect.sh index 6e2acb8ef2..415a8d04cc 100755 --- a/git-bisect.sh +++ b/git-bisect.sh @@ -288,10 +288,12 @@ bisect_visualize() { if test $# = 0 then - case "${DISPLAY+set}${SESSIONNAME+set}${MSYSTEM+set}${SECURITYSESSIONID+set}" in - '') set git log ;; - set*) set gitk ;; - esac + if test -n "${DISPLAY+set}${SESSIONNAME+set}${MSYSTEM+set}${SECURITYSESSIONID+set}" && + type gitk >/dev/null 2>&1; then + set gitk + else + set git log + fi else case "$1" in git*|tig) ;; @@ -316,7 +318,12 @@ bisect_reset() { *) 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() { @@ -338,6 +345,7 @@ 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 @@ -412,6 +420,10 @@ bisect_run () { done } +bisect_log () { + test -s "$GIT_DIR/BISECT_LOG" || die "We are not bisecting." + cat "$GIT_DIR/BISECT_LOG" +} case "$#" in 0) @@ -438,7 +450,7 @@ case "$#" in 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 81883e7270..79b5122b4f 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -28,6 +28,24 @@ #define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0])) #define bitsizeof(x) (CHAR_BIT * sizeof(x)) +#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. + * + * Requires: a >= 0, typeof(a) equals typeof(b) + */ +#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 @@ -92,9 +110,15 @@ #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/resource.h> #include <sys/socket.h> #include <sys/ioctl.h> #include <termios.h> @@ -106,7 +130,11 @@ #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> @@ -187,7 +215,10 @@ extern char *gitbasename(char *); #define is_dir_sep(c) ((c) == '/') #endif -#ifdef __GNUC__ +#if __HP_cc >= 61000 +#define NORETURN __attribute__((noreturn)) +#define NORETURN_PTR +#elif defined(__GNUC__) #define NORETURN __attribute__((__noreturn__)) #define NORETURN_PTR __attribute__((__noreturn__)) #elif defined(_MSC_VER) @@ -374,6 +405,14 @@ static inline void *gitmempcpy(void *dest, const void *src, size_t n) } #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); @@ -392,6 +431,7 @@ extern ssize_t xwrite(int fd, const void *buf, size_t len); 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-cvsexportcommit.perl b/git-cvsexportcommit.perl index 59b672213b..39a426e067 100755 --- a/git-cvsexportcommit.perl +++ b/git-cvsexportcommit.perl @@ -1,6 +1,8 @@ -#!/usr/bin/perl -w +#!/usr/bin/perl +use 5.008; use strict; +use warnings; use Getopt::Std; use File::Temp qw(tempdir); use Data::Dumper; diff --git a/git-cvsimport.perl b/git-cvsimport.perl index 9e03eee458..bbf327f3e8 100755 --- a/git-cvsimport.perl +++ b/git-cvsimport.perl @@ -1,4 +1,4 @@ -#!/usr/bin/perl -w +#!/usr/bin/perl # This tool is copyright (c) 2005, Matthias Urlichs. # It is released under the Gnu Public License, version 2. @@ -13,6 +13,7 @@ # The head revision is on branch "origin" by default. # You can change that with the '-o' option. +use 5.008; use strict; use warnings; use Getopt::Long; @@ -89,23 +90,40 @@ sub write_author_info($) { } # 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; + } } } } @@ -348,7 +366,9 @@ sub conn { $self->{'socketo'}->write("valid-requests\n"); $self->{'socketo'}->flush(); - chomp(my $rep=$self->readline()); + my $rep=$self->readline(); + die "Failed to read from server" unless defined $rep; + chomp($rep); if ($rep !~ s/^Valid-requests\s*//) { $rep="<unknown>" unless $rep; die "Expected Valid-requests from server, but got: $rep\n"; @@ -611,7 +631,7 @@ my %index; # holds filenames of one index per branch 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 e9f3037df3..1b8bff2cac 100755 --- a/git-cvsserver.perl +++ b/git-cvsserver.perl @@ -8,13 +8,14 @@ #### 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. #### #### +use 5.008; use strict; use warnings; use bytes; @@ -2680,7 +2681,7 @@ package GITCVS::log; #### Copyright The Open University UK - 2006. #### #### Authors: Martyn Smith <martyn@catalyst.net.nz> -#### Martin Langhoff <martin@catalyst.net.nz> +#### Martin Langhoff <martin@laptop.org> #### #### @@ -2847,7 +2848,7 @@ package GITCVS::updater; #### 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-difftool--helper.sh b/git-difftool--helper.sh index 524f5ea8ab..0594bf7ca5 100755 --- a/git-difftool--helper.sh +++ b/git-difftool--helper.sh @@ -49,6 +49,7 @@ launch_merge_tool () { fi if use_ext_cmd; then + export BASE eval $GIT_DIFFTOOL_EXTCMD '"$LOCAL"' '"$REMOTE"' else run_merge_tool "$merge_tool" diff --git a/git-difftool.perl b/git-difftool.perl index adc42de875..ced1615e21 100755 --- a/git-difftool.perl +++ b/git-difftool.perl @@ -10,6 +10,7 @@ # # Any arguments that are unknown to this script are forwarded to 'git diff'. +use 5.008; use strict; use warnings; use Cwd qw(abs_path); @@ -51,6 +52,7 @@ sub generate_command my @command = (exe('git'), 'diff'); my $skip_next = 0; my $idx = -1; + my $prompt = ''; for my $arg (@ARGV) { $idx++; if ($skip_next) { @@ -88,13 +90,11 @@ sub generate_command next; } if ($arg eq '-y' || $arg eq '--no-prompt') { - $ENV{GIT_DIFFTOOL_NO_PROMPT} = 'true'; - delete $ENV{GIT_DIFFTOOL_PROMPT}; + $prompt = 'no'; next; } if ($arg eq '--prompt') { - $ENV{GIT_DIFFTOOL_PROMPT} = 'true'; - delete $ENV{GIT_DIFFTOOL_NO_PROMPT}; + $prompt = 'yes'; next; } if ($arg eq '-h' || $arg eq '--help') { @@ -102,6 +102,11 @@ sub generate_command } push @command, $arg; } + if ($prompt eq 'yes') { + $ENV{GIT_DIFFTOOL_PROMPT} = 'true'; + } elsif ($prompt eq 'no') { + $ENV{GIT_DIFFTOOL_NO_PROMPT} = 'true'; + } return @command } diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh index 4617f29c26..d3acf0d213 100755 --- a/git-gui/git-gui.sh +++ b/git-gui/git-gui.sh @@ -83,6 +83,7 @@ if {![catch {set _verbose $env(GITGUI_VERBOSE)}]} { puts stderr "source $name" uplevel 1 real__source $name } + if {[tk windowingsystem] eq "win32"} { console show } } ###################################################################### @@ -444,6 +445,8 @@ proc _lappend_nice {cmd_var} { 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 {}} { @@ -673,6 +676,7 @@ bind . <Visibility> { 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)]} { @@ -874,12 +878,19 @@ if {![regsub {^git version } $_git_version {} _git_version]} { 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 .} @@ -1183,13 +1194,22 @@ if {![file isdirectory $_gitdir]} { # _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 @@ -2861,7 +2881,8 @@ proc usage {} { 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 } @@ -2934,7 +2955,11 @@ blame { 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 } } @@ -2973,18 +2998,19 @@ blame { 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 } } @@ -3286,6 +3312,7 @@ text $ui_diff -background white -foreground black \ -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 \ @@ -3296,8 +3323,16 @@ pack $ui_diff -side left -fill both -expand 1 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 diff --git a/git-gui/lib/branch_rename.tcl b/git-gui/lib/branch_rename.tcl index 63988773ba..6e510ec2e3 100644 --- a/git-gui/lib/branch_rename.tcl +++ b/git-gui/lib/branch_rename.tcl @@ -53,7 +53,7 @@ constructor dialog {} { 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 c628750276..dcf0711be0 100644 --- a/git-gui/lib/diff.tcl +++ b/git-gui/lib/diff.tcl @@ -294,7 +294,7 @@ proc start_show_diff {cont_info {add_opts {}}} { } lappend cmd -p - lappend cmd --no-color + lappend cmd --color if {$repo_config(gui.diffcontext) >= 1} { lappend cmd "-U$repo_config(gui.diffcontext)" } @@ -332,6 +332,23 @@ proc start_show_diff {cont_info {add_opts {}}} { 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 @@ -340,6 +357,9 @@ proc read_diff {fd cont_info} { $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} { @@ -434,11 +454,23 @@ proc read_diff {fd cont_info} { } } } + 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 e6f6ecda17..8bfa8a055c 100755 --- a/git-instaweb.sh +++ b/git-instaweb.sh @@ -558,12 +558,14 @@ my \$app = builder { # make it runnable as standalone app, # like it would be run via 'plackup' utility -if (__FILE__ eq \$0) { +if (caller) { + return \$app; +} else { require Plack::Runner; my \$runner = Plack::Runner->new(); \$runner->parse_options(qw(--env deployment --port $port), - "$local" ? qw(--host 127.0.0.1) : ()); + "$local" ? qw(--host 127.0.0.1) : ()); \$runner->run(\$app); } __END__ @@ -580,6 +582,8 @@ gitweb_conf() { 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 615753c83c..8643f74cb0 100755 --- a/git-merge-octopus.sh +++ b/git-merge-octopus.sh @@ -61,6 +61,11 @@ do 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 b5e1943b1d..78ce49e984 100644 --- a/git-mergetool--lib.sh +++ b/git-mergetool--lib.sh @@ -10,10 +10,10 @@ merge_mode() { translate_merge_tool_path () { case "$1" in - vimdiff) + vimdiff|vimdiff2) echo vim ;; - gvimdiff) + gvimdiff|gvimdiff2) echo gvim ;; emerge) @@ -47,7 +47,8 @@ check_unchanged () { 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 @@ -169,25 +170,30 @@ run_merge_tool () { "$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" -R -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" -R -f -d -c "wincmd l" \ "$LOCAL" "$REMOTE" fi ;; diff --git a/git-parse-remote.sh b/git-parse-remote.sh index 5f47b18141..9b950be28d 100644 --- a/git-parse-remote.sh +++ b/git-parse-remote.sh @@ -55,7 +55,8 @@ get_remote_url () { } get_default_remote () { - curr_branch=$(git symbolic-ref -q HEAD | sed -e 's|^refs/heads/||') + curr_branch=$(git symbolic-ref -q HEAD) + curr_branch="${curr_branch#refs/heads/}" origin=$(git config --get "branch.$curr_branch.remote") echo ${origin:-origin} } @@ -66,7 +67,7 @@ get_remote_merge_branch () { 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) ;; @@ -89,7 +90,13 @@ get_remote_merge_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 8eb74d45de..f6b7b84048 100755 --- a/git-pull.sh +++ b/git-pull.sh @@ -38,7 +38,7 @@ test -z "$(git ls-files -u)" || die_conflict 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/}" @@ -105,10 +105,16 @@ do --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 ;; *) @@ -201,10 +207,7 @@ test true = "$rebase" && { 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 && @@ -220,7 +223,7 @@ test true = "$rebase" && { 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) diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index a27952d9fd..5873ba4bc3 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -28,6 +28,7 @@ continue continue rebasing process 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 " @@ -153,14 +154,6 @@ run_pre_rebase_hook () { 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" @@ -557,7 +550,7 @@ do_next () { 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 @@ -675,9 +668,27 @@ get_saved_options () { # 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= @@ -687,14 +698,26 @@ rearrange_squash () { *" $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" @@ -727,6 +750,7 @@ do OK_TO_SKIP_PRE_REBASE=yes ;; --verify) + OK_TO_SKIP_PRE_REBASE= ;; --continue) is_standalone "$@" || usage @@ -768,7 +792,7 @@ first and then run 'git rebase --continue' again." record_in_rewritten "$(cat "$DOTEST"/stopped-sha)" - require_clean_work_tree + require_clean_work_tree "rebase" do_rest ;; --abort) @@ -866,11 +890,11 @@ first and then run 'git rebase --continue' again." comment_for_reflog start - require_clean_work_tree + require_clean_work_tree "rebase" "Please commit or stash them." if test ! -z "$1" then - output git checkout "$1" || + output git checkout "$1" -- || die "Could not checkout $1" fi @@ -997,7 +1021,7 @@ first and then run 'git rebase --continue' again." # e, edit = use commit, but stop for amending # s, squash = use commit, but meld into previous commit # f, fixup = like "squash", but discard this commit's log message -# x <cmd>, exec <cmd> = Run a shell command <cmd>, and stop if it fails +# x, exec = run command (the rest of the line) using shell # # If you remove a line here THAT COMMIT WILL BE LOST. # However, if you remove everything, the rebase will be aborted. diff --git a/git-rebase.sh b/git-rebase.sh index 3335cee70b..cbb0ea90ed 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -49,7 +49,8 @@ do_merge= dotest="$GIT_DIR"/rebase-merge prec=4 verbose= -diffstat=$(git config --bool rebase.stat) +diffstat= +test "$(git config --bool rebase.stat)" = true && diffstat=t git_am_opt= rebase_root= force_rebase= @@ -111,7 +112,7 @@ call_merge () { export GITHEAD_$cmt GITHEAD_$hd if test -n "$GIT_QUIET" then - export GIT_MERGE_VERBOSITY=1 + GIT_MERGE_VERBOSITY=1 && export GIT_MERGE_VERBOSITY fi eval 'git-merge-$strategy' $strategy_opts '"$cmt^" -- "$hd" "$cmt"' rv=$? @@ -205,6 +206,9 @@ do --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?" @@ -274,15 +278,16 @@ do die "No rebase in progress?" git rerere clear - if test -d "$dotest" - then - GIT_QUIET=$(cat "$dotest/quiet") - move_to_original_branch - else - dotest="$GIT_DIR"/rebase-apply - GIT_QUIET=$(cat "$dotest/quiet") - move_to_original_branch - fi + + test -d "$dotest" || dotest="$GIT_DIR"/rebase-apply + + head_name="$(cat "$dotest"/head-name)" && + case "$head_name" in + refs/*) + git symbolic-ref HEAD $head_name || + die "Could not move back to $head_name" + ;; + esac git reset --hard $(cat "$dotest/orig-head") rm -r "$dotest" exit @@ -311,10 +316,6 @@ do esac strategy_opts="$strategy_opts $(git rev-parse --sq-quote "--$newopt")" do_merge=t - if test -n "$strategy" - then - strategy=recursive - fi ;; -s=*|--s=*|--st=*|--str=*|--stra=*|--strat=*|--strate=*|\ --strateg=*|--strategy=*|\ @@ -416,19 +417,7 @@ else 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 @@ -493,6 +482,7 @@ case "$#" in then head_name="detached HEAD" else + echo >&2 "fatal: no such branch: $1" usage fi ;; @@ -524,7 +514,7 @@ then if test -z "$force_rebase" then # Lazily switch to the target branch if needed... - test -z "$switch_to" || git checkout "$switch_to" + test -z "$switch_to" || git checkout "$switch_to" -- say "Current branch $branch_name is up to date." exit 0 else diff --git a/git-relink.perl b/git-relink.perl index 937c69a748..e136732cea 100755 --- a/git-relink.perl +++ b/git-relink.perl @@ -6,7 +6,7 @@ # # Scan two git object-trees, and hardlink any common objects between them. -use 5.006; +use 5.008; use strict; use warnings; use Getopt::Long; @@ -163,7 +163,7 @@ sub link_two_files($$) { sub usage() { - print("Usage: git relink [--safe] <dir> [<dir> ...] <master_dir> \n"); + print("Usage: git relink [--safe] <dir>... <master_dir> \n"); print("All directories should contain a .git/objects/ subdirectory.\n"); print("Options\n"); print("\t--safe\t" . diff --git a/git-repack.sh b/git-repack.sh index 1eb3bca352..624feec26f 100755 --- a/git-repack.sh +++ b/git-repack.sh @@ -10,7 +10,8 @@ git repack [options] a pack everything in a single pack A same as -a, and turn unreachable objects loose d remove redundant packs, and run git-prune-packed -f pass --no-reuse-object to git-pack-objects +f pass --no-reuse-delta to git-pack-objects +F pass --no-reuse-object to git-pack-objects n do not run git-update-server-info q,quiet be quiet l pass --local to git-pack-objects @@ -34,7 +35,8 @@ do unpack_unreachable=--unpack-unreachable ;; -d) remove_redundant=t ;; -q) GIT_QUIET=t ;; - -f) no_reuse=--no-reuse-object ;; + -f) no_reuse=--no-reuse-delta ;; + -F) no_reuse=--no-reuse-object ;; -l) local=--local ;; --max-pack-size|--window|--window-memory|--depth) extra="$extra $1=$2"; shift ;; @@ -50,7 +52,7 @@ true) esac PACKDIR="$GIT_OBJECT_DIRECTORY/pack" -PACKTMP="$GIT_OBJECT_DIRECTORY/.tmp-$$-pack" +PACKTMP="$PACKDIR/.tmp-$$-pack" rm -f "$PACKTMP"-* trap 'rm -f "$PACKTMP"-*' 0 1 2 3 15 @@ -80,6 +82,8 @@ case ",$all_into_one," in ;; esac +mkdir -p "$PACKDIR" || exit + args="$args $local ${GIT_QUIET:+-q} $no_reuse$extra" names=$(git pack-objects --keep-true-parents --honor-pack-keep --non-empty --all --reflog $args </dev/null "$PACKTMP") || exit 1 @@ -88,7 +92,6 @@ if [ -z "$names" ]; then fi # Ok we have prepared all new packfiles. -mkdir -p "$PACKDIR" || exit # First see if there are packs of the same name and if so # if we can move them out of the way (this can happen if we diff --git a/git-send-email.perl b/git-send-email.perl index 6dab3bf6a7..76565de2ee 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -1,4 +1,4 @@ -#!/usr/bin/perl -w +#!/usr/bin/perl # # Copyright 2002,2005 Greg Kroah-Hartman <greg@kroah.com> # Copyright 2005 Ryan Anderson <ryan@michonline.com> @@ -16,6 +16,7 @@ # and second line is the subject of the message. # +use 5.008; use strict; use warnings; use Term::ReadLine; @@ -24,6 +25,7 @@ use Text::ParseWords; use Data::Dumper; use Term::ANSIColor; use File::Temp qw/ tempdir tempfile /; +use File::Spec::Functions qw(catfile); use Error qw(:try); use Git; @@ -60,6 +62,7 @@ git send-email [options] <file | directory | rev-list options > --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. @@ -70,6 +73,7 @@ git send-email [options] <file | directory | rev-list options > 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. @@ -85,6 +89,7 @@ git send-email [options] <file | directory | rev-list options > --[no-]validate * Perform patch sanity checks. Default on. --[no-]format-patch * understand any non optional arguments as `git format-patch` ones. + --force * Send even if safety checks would prevent it. EOT exit(1); @@ -134,11 +139,8 @@ my $have_mail_address = eval { require Mail::Address; 1 }; 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); @@ -162,6 +164,7 @@ if ($@) { my ($quiet, $dry_run) = (0, 0); my $format_patch; my $compose_filename; +my $force = 0; # Handle interactive edition of files. my $multiedit; @@ -187,9 +190,11 @@ sub do_edit { } # 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); @@ -210,10 +215,12 @@ my %config_bool_settings = ( 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, @@ -271,7 +278,8 @@ $SIG{INT} = \&signal_handler; 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, @@ -279,6 +287,7 @@ my $rc = GetOptions("sender|from=s" => \$sender, "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, @@ -301,6 +310,7 @@ my $rc = GetOptions("sender|from=s" => \$sender, "validate!" => \$validate, "format-patch!" => \$format_patch, "8bit-encoding=s" => \$auto_8bit_encoding, + "force" => \$force, ); unless ($rc) { @@ -364,7 +374,7 @@ my(%suppress_cc); 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; } } @@ -409,7 +419,7 @@ my ($repoauthor, $repocommitter); # Verify the user input -foreach my $entry (@to) { +foreach my $entry (@initial_to) { die "Comma in --to entry: $entry'\n" unless $entry !~ m/,/; } @@ -508,12 +518,12 @@ while (defined(my $f = shift @ARGV)) { 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 { @@ -545,7 +555,7 @@ if (@files) { usage(); } -sub get_patch_subject($) { +sub get_patch_subject { my $fn = shift; open (my $fh, '<', $fn); while (my $line = <$fh>) { @@ -563,7 +573,7 @@ if ($compose) { $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: $!"; @@ -571,7 +581,7 @@ if ($compose) { 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 @@ -584,9 +594,9 @@ In-Reply-To: $tpl_reply_to 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); @@ -594,23 +604,23 @@ EOT 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"; @@ -635,10 +645,10 @@ EOT 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"; @@ -675,7 +685,7 @@ sub ask { my %broken_encoding; -sub file_declares_8bit_cte($) { +sub file_declares_8bit_cte { my $fn = shift; open (my $fh, '<', $fn); while (my $line = <$fh>) { @@ -702,6 +712,16 @@ if (!defined $auto_8bit_encoding && scalar %broken_encoding) { default => "UTF-8"); } +if (!$force) { + for my $f (@files) { + 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"; + } + } +} + my $prompting = 0; if (!defined $sender) { $sender = $repoauthor || $repocommitter || ''; @@ -711,9 +731,9 @@ if (!defined $sender) { $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++; } @@ -731,8 +751,8 @@ sub expand_one_alias { 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); @@ -766,8 +786,8 @@ our ($message_id, %mail, $subject, $reply_to, $references, $message, 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)$/); @@ -808,7 +828,7 @@ sub make_message_id { 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>"; @@ -841,8 +861,8 @@ sub quote_rfc2047 { 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; } @@ -853,7 +873,7 @@ sub sanitize_address { my ($recipient_name, $recipient_addr) = ($recipient =~ /^(.*?)\s*(<.*)/); if (not $recipient_name) { - return "$recipient"; + return $recipient; } # if recipient_name is already quoted, do nothing @@ -870,7 +890,7 @@ sub sanitize_address { # 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"; @@ -895,7 +915,7 @@ sub sanitize_address { sub valid_fqdn { my $domain = shift; - return !($^O eq 'darwin' && $domain =~ /\.local$/) && $domain =~ /\./; + return defined $domain && !($^O eq 'darwin' && $domain =~ /\.local$/) && $domain =~ /\./; } sub maildomain_net { @@ -940,7 +960,7 @@ sub maildomain { sub send_message { my @recipients = unique_email_list(@to); @cc = (grep { my $cc = extract_valid_address($_); - not grep { $cc eq $_ } @recipients + not grep { $cc eq $_ || $_ =~ /<\Q${cc}\E>$/ } @recipients } map { sanitize_address($_) } @cc); @@ -1015,6 +1035,8 @@ X-Mailer: git-send-email $gitversion } } + unshift (@sendmail_parameters, @smtp_server_options); + if ($dry_run) { # We don't want to send the email. } elsif ($smtp_server =~ m#^/#) { @@ -1024,7 +1046,7 @@ X-Mailer: git-send-email $gitversion 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) { @@ -1130,12 +1152,13 @@ $subject = $initial_subject; $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; @@ -1143,7 +1166,7 @@ foreach my $t (@files) { $message = ""; $message_num++; # First unfold multiline header fields - while(<F>) { + while(<$fh>) { last if /^\s*$/; if (/^\s+\S/ and @header) { chomp($header[$#header]); @@ -1176,6 +1199,13 @@ foreach my $t (@files) { $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) { @@ -1219,7 +1249,7 @@ foreach my $t (@files) { } } # Now parse the message body - while(<F>) { + while(<$fh>) { $message .= $_; if (/^(Signed-off-by|Cc): (.*)$/i) { chomp; @@ -1236,23 +1266,12 @@ foreach my $t (@files) { $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; @@ -1293,13 +1312,15 @@ foreach my $t (@files) { ($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"; @@ -1310,15 +1331,38 @@ foreach my $t (@files) { $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 6131670860..aa16b83565 100644 --- a/git-sh-setup.sh +++ b/git-sh-setup.sh @@ -145,23 +145,49 @@ require_work_tree () { 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 /{ s/'\''/'\''\\'\'\''/g h s/^author \([^<]*\) <[^>]*> .*$/\1/ - s/'\''/'\''\'\'\''/g s/.*/GIT_AUTHOR_NAME='\''&'\''/p g s/^author [^<]* <\([^>]*\)> .*$/\1/ - s/'\''/'\''\'\'\''/g s/.*/GIT_AUTHOR_EMAIL='\''&'\''/p g s/^author [^<]* <[^>]*> \(.*\)$/\1/ - s/'\''/'\''\'\'\''/g s/.*/GIT_AUTHOR_DATE='\''&'\''/p q @@ -209,5 +235,20 @@ case $(uname -s) in 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-stash.sh b/git-stash.sh index 7ce818bd1b..a305fb19f1 100755 --- a/git-stash.sh +++ b/git-stash.sh @@ -12,12 +12,14 @@ USAGE="list [<options>] SUBDIRECTORY_OK=Yes OPTIONS_SPEC= +START_DIR=`pwd` . git-sh-setup require_work_tree cd_to_toplevel TMP="$GIT_DIR/.git-stash.$$" -trap 'rm -f "$TMP-*"' 0 +TMPindex=${GIT_INDEX_FILE-"$GIT_DIR/index"}.stash.$$ +trap 'rm -f "$TMP-"* "$TMPindex"' 0 ref_stash=refs/stash @@ -81,14 +83,12 @@ create_stash () { # state of the working tree w_tree=$( ( - rm -f "$TMP-index" && - cp -p ${GIT_INDEX_FILE-"$GIT_DIR/index"} "$TMP-index" && - GIT_INDEX_FILE="$TMP-index" && + git read-tree --index-output="$TMPindex" -m $i_tree && + GIT_INDEX_FILE="$TMPindex" && export GIT_INDEX_FILE && - git read-tree -m $i_tree && git diff --name-only -z HEAD | git update-index -z --add --remove --stdin && git write-tree && - rm -f "$TMP-index" + rm -f "$TMPindex" ) ) || die "Cannot save the current worktree state" @@ -265,28 +265,21 @@ parse_flags_and_rev() i_tree= REV=$(git rev-parse --no-flags --symbolic "$@" 2>/dev/null) - FLAGS=$(git rev-parse --no-revs -- "$@" 2>/dev/null) - - set -- $FLAGS FLAGS= - while test $# -ne 0 + for opt do - case "$1" in + case "$opt" in -q|--quiet) GIT_QUIET=-t ;; --index) INDEX_OPTION=--index ;; - --) - : - ;; - *) - FLAGS="${FLAGS}${FLAGS:+ }$1" + -*) + FLAGS="${FLAGS}${FLAGS:+ }$opt" ;; esac - shift done set -- $REV @@ -380,7 +373,7 @@ apply_stash () { if test -n "$GIT_QUIET" then - export GIT_MERGE_VERBOSITY=0 + GIT_MERGE_VERBOSITY=0 && export GIT_MERGE_VERBOSITY fi if git merge-recursive $b_tree -- $c_tree $w_tree then @@ -401,7 +394,7 @@ apply_stash () { then squelch='>/dev/null 2>&1' fi - eval "git status $squelch" || : + (cd "$START_DIR" && eval "git status $squelch") || : else # Merge conflict; keep the exit status from merge-recursive status=$? @@ -439,9 +432,9 @@ apply_to_branch () { assert_stash_like "$@" git checkout -b $branch $REV^ && - apply_stash "$@" - - test -z "$IS_STASH_REF" || drop_stash "$@" + apply_stash "$@" && { + test -z "$IS_STASH_REF" || drop_stash "$@" + } } PARSE_CACHE='--not-parsed' diff --git a/git-submodule.sh b/git-submodule.sh index 9ebbab798d..3a13397e05 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -37,12 +37,24 @@ resolve_relative_url () die "remote ($remote) does not have a url defined in .git/config" url="$1" remoteurl=${remoteurl%/} + sep=/ while test -n "$url" do case "$url" in ../*) url="${url#../}" - remoteurl="${remoteurl%/*}" + case "$remoteurl" in + */*) + remoteurl="${remoteurl%/*}" + ;; + *:*) + remoteurl="${remoteurl%:*}" + sep=: + ;; + *) + die "cannot strip one component off url '$remoteurl'" + ;; + esac ;; ./*) url="${url#./}" @@ -51,7 +63,7 @@ resolve_relative_url () break;; esac done - echo "$remoteurl/${url%/}" + echo "$remoteurl$sep${url%/}" } # @@ -93,20 +105,6 @@ module_clone() url=$2 reference="$3" - # If there already is a directory at the submodule path, - # expect it to be empty (since that is the default checkout - # action) and try to remove it. - # Note: if $path is a symlink to a directory the test will - # succeed but the rmdir will fail. We might want to fix this. - if test -d "$path" - then - rmdir "$path" 2>/dev/null || - die "Directory '$path' exists, but is neither empty nor a git repository" - fi - - test -e "$path" && - die "A file already exist at path '$path'" - if test -n "$reference" then git-clone "$reference" -n "$url" "$path" @@ -241,7 +239,7 @@ cmd_add() # ash fails to wordsplit ${branch:+-b "$branch"...} case "$branch" in '') git checkout -f -q ;; - ?*) git checkout -f -q -b "$branch" "origin/$branch" ;; + ?*) git checkout -f -q -B "$branch" "origin/$branch" ;; esac ) || die "Unable to checkout submodule '$path'" fi @@ -374,41 +372,35 @@ cmd_init() 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 ;; --) @@ -422,6 +414,8 @@ cmd_update() break ;; esac + orig_flags="$orig_flags $(git rev-parse --sq-quote "$1")" + shift done if test -n "$init" @@ -429,6 +423,7 @@ cmd_update() cmd_init "--" "$@" || return fi + cloned_modules= module_list "$@" | while read mode sha1 stage path do @@ -448,6 +443,7 @@ cmd_update() if ! test -d "$path"/.git -o -f "$path"/.git then module_clone "$path" "$url" "$reference"|| exit + cloned_modules="$cloned_modules;$name" subsha1= else subsha1=$(clear_local_git_env; cd "$path" && @@ -475,6 +471,13 @@ cmd_update() die "Unable to fetch in submodule path '$path'" fi + # Is this something we just cloned? + case ";$cloned_modules;" in + *";$name;"*) + # then there is no local change to integrate + update_module= ;; + esac + case "$update_module" in rebase) command="git rebase" @@ -500,7 +503,7 @@ cmd_update() 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 @@ -733,7 +736,7 @@ cmd_summary() { cmd_status() { # parse $args after "submodule ... status". - orig_args="$@" + orig_flags= while test $# -ne 0 do case "$1" in @@ -757,6 +760,7 @@ cmd_status() break ;; esac + orig_flags="$orig_flags $(git rev-parse --sq-quote "$1")" shift done @@ -790,7 +794,7 @@ cmd_status() 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 @@ -836,11 +840,12 @@ cmd_sync() ;; esac + say "Synchronizing submodule url for '$name'" + git config submodule."$name".url "$url" + if test -e "$path"/.git then ( - say "Synchronizing submodule url for '$name'" - git config submodule."$name".url "$url" clear_local_git_env cd "$path" remote=$(get_default_remote) diff --git a/git-svn.perl b/git-svn.perl index 9b046b693f..177dd259cd 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1,6 +1,7 @@ #!/usr/bin/env perl # Copyright (C) 2006, Eric Wong <normalperson@yhbt.net> # License: GPL v2 or later +use 5.008; use warnings; use strict; use vars qw/ $AUTHOR $VERSION @@ -83,7 +84,7 @@ my ($_stdin, $_help, $_edit, $_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, @@ -153,6 +154,7 @@ my %cmd = ( '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', @@ -568,6 +570,7 @@ sub cmd_dcommit { 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"; @@ -1513,7 +1516,8 @@ sub cmt_sha2rev_batch { sub working_head_info { my ($head, $refs) = @_; - my @args = ('log', '--no-color', '--first-parent', '--pretty=medium'); + my @args = qw/log --no-color --no-decorate --first-parent + --pretty=medium/; my ($fh, $ctx) = command_output_pipe(@args, $head); my $hash; my %max; @@ -3118,9 +3122,10 @@ sub _rev_list { sub check_cherry_pick { my $base = shift; my $tip = shift; + my $parents = shift; my @ranges = @_; my %commits = map { $_ => 1 } - _rev_list("--no-merges", $tip, "--not", $base); + _rev_list("--no-merges", $tip, "--not", $base, @$parents); for my $range ( @ranges ) { delete @commits{_rev_list($range)}; } @@ -3296,6 +3301,7 @@ sub find_extra_svn_parents { # double check that there are no missing non-merge commits my (@incomplete) = check_cherry_pick( $merge_base, $merge_tip, + $parents, @$ranges, ); @@ -4447,6 +4453,7 @@ sub new { $self->{path_prefix} = length $self->{svn_path} ? "$self->{svn_path}/" : ''; $self->{config} = $opts->{config}; + $self->{mergeinfo} = $opts->{mergeinfo}; return $self; } @@ -4756,6 +4763,11 @@ sub change_file_prop { $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"); @@ -4849,6 +4861,11 @@ sub apply_diff { 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 3fc4166b25..e9de241dd0 100755 --- a/git-web--browse.sh +++ b/git-web--browse.sh @@ -31,154 +31,161 @@ valid_custom_tool() 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 @@ -6,27 +6,35 @@ #include "run-command.h" const char git_usage_string[] = - "git [--version] [--exec-path[=GIT_EXEC_PATH]] [--html-path]\n" + "git [--version] [--exec-path[=<path>]] [--html-path]\n" " [-p|--paginate|--no-pager] [--no-replace-objects]\n" - " [--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE]\n" + " [--bare] [--git-dir=<path>] [--work-tree=<path>]\n" " [-c name=value] [--help]\n" - " COMMAND [ARGS]"; + " <command> [<args>]"; const char git_more_info_string[] = - "See 'git help COMMAND' for more information on a specific command."; + "See 'git help <command>' for more information on a specific command."; static struct startup_info git_startup_info; 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; } @@ -35,9 +43,12 @@ int check_pager_config(const char *cmd) { 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) { @@ -264,6 +275,10 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv) 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(); @@ -374,6 +389,8 @@ static void handle_internal_command(int argc, const char **argv) { "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 }, { "rerere", cmd_rerere, RUN_SETUP }, diff --git a/git_remote_helpers/setup.cfg b/git_remote_helpers/setup.cfg new file mode 100644 index 0000000000..4bff8878d1 --- /dev/null +++ b/git_remote_helpers/setup.cfg @@ -0,0 +1,3 @@ +[build] +build_purelib = build/lib +build_platlib = build/lib diff --git a/gitk-git/gitk b/gitk-git/gitk index 1b0e09a561..e82c6bfede 100644..100755 --- a/gitk-git/gitk +++ b/gitk-git/gitk @@ -131,6 +131,7 @@ proc unmerged_files {files} { 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 @@ -168,7 +169,7 @@ proc parseviewargs {n arglist} { 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" - @@ -177,6 +178,18 @@ proc parseviewargs {n arglist} { # 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" - @@ -313,6 +326,7 @@ proc start_rev_list {view} { 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 @@ -361,8 +375,8 @@ proc start_rev_list {view} { } 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 @@ -456,6 +470,7 @@ proc updatecommits {} { 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 @@ -508,8 +523,8 @@ proc updatecommits {} { 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 @@ -1970,6 +1985,8 @@ proc makewindow {} { 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 @@ -2243,6 +2260,15 @@ proc makewindow {} { ${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 \ @@ -2451,6 +2477,7 @@ proc makewindow {} { 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] @@ -7301,6 +7328,7 @@ proc getblobline {bf id} { [lindex [split $commentend .] 0]}] mark_ctext_line $lnum } + $ctext config -state disabled return 0 } $ctext config -state disabled @@ -7502,11 +7530,16 @@ proc changeignorespace {} { 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 @@ -7523,6 +7556,9 @@ proc getblobdiffs {ids} { 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)] } @@ -7608,6 +7644,7 @@ proc getblobdiffline {bdf ids} { 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 @@ -7747,15 +7784,28 @@ proc getblobdiffline {bdf ids} { # 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} { @@ -7770,6 +7820,8 @@ proc getblobdiffline {bdf ids} { lappend tags m$num } } + set words_pre_markup "{+" + set words_post_markup "+}" } if {$targetline ne {}} { if {$diffline == $targetline} { @@ -7779,8 +7831,17 @@ proc getblobdiffline {bdf ids} { 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 @@ -11391,6 +11452,7 @@ if {[tk windowingsystem] eq "win32"} { set diffcolors {red "#00a000" blue} set diffcontext 3 set ignorespace 0 +set worddiff "" set markbgcolor "#e0e0ff" set circlecolors {white blue gray blue blue} @@ -11521,6 +11583,11 @@ set NS [expr {$use_ttk ? "ttk" : ""}] 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 new file mode 100644 index 0000000000..1486e3205a --- /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 386763ade7..2f07a2e2d9 100644 --- a/gitk-git/po/sv.po +++ b/gitk-git/po/sv.po @@ -1,5 +1,5 @@ # 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. @@ -8,8 +8,8 @@ msgid "" 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" @@ -24,17 +24,17 @@ msgstr "Kunde inte hämta lista över ej sammanslagna filer:" 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." @@ -42,600 +42,605 @@ msgstr "" "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\"):" @@ -643,201 +648,201 @@ msgstr "" "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" " " @@ -845,7 +850,7 @@ msgstr "" " är samma patch som\n" " " -#: gitk:8497 +#: gitk:8519 msgid "" " differs from\n" " " @@ -853,139 +858,139 @@ msgstr "" " 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" @@ -995,7 +1000,7 @@ msgstr "" "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?" @@ -1003,32 +1008,32 @@ msgstr "" "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)" @@ -1036,19 +1041,19 @@ msgstr "" "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" @@ -1057,16 +1062,16 @@ msgstr "" "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." @@ -1074,203 +1079,203 @@ msgstr "" "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 823053173c..4964a679b3 100644 --- a/gitweb/INSTALL +++ b/gitweb/INSTALL @@ -237,6 +237,12 @@ Requirements - 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 2fb7c2d77b..0a6ac00631 100644 --- a/gitweb/Makefile +++ b/gitweb/Makefile @@ -35,10 +35,12 @@ GITWEB_FAVICON = static/git-favicon.png 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 @@ -129,7 +131,8 @@ GITWEB_REPLACE = \ -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 $@+ @@ -143,6 +146,15 @@ gitweb.cgi: gitweb.perl GITWEB-BUILD-OPTIONS 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 @@ -156,5 +168,5 @@ 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 d481198796..4a673933ac 100644 --- a/gitweb/README +++ b/gitweb/README @@ -114,6 +114,11 @@ You can specify the following configuration variables when building GIT: 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 @@ -172,13 +177,15 @@ not include variables usually directly set during build): * $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). @@ -236,7 +243,21 @@ not include variables usually directly set during build): 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 a85e2f6319..46186ab909 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -7,6 +7,7 @@ # # This program is licensed under the GPLv2 +use 5.008; use strict; use warnings; use CGI qw(:standard :escapeHTML -nosticky); @@ -16,12 +17,10 @@ use Encode; 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 { @@ -165,6 +164,12 @@ our @diff_opts = ('-M'); # taken from git_commit # 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 => { @@ -245,13 +250,14 @@ our %highlight_ext = ( # 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), ); @@ -486,6 +492,18 @@ our %feature = ( '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 { @@ -594,6 +612,14 @@ sub filter_snapshot_fmts { !$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++"; @@ -700,6 +726,7 @@ our %actions = ( "log" => \&git_log, "patch" => \&git_patch, "patches" => \&git_patches, + "remotes" => \&git_remotes, "rss" => \&git_rss, "atom" => \&git_atom, "search" => \&git_search, @@ -774,10 +801,10 @@ sub evaluate_path_info { '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) { @@ -813,8 +840,15 @@ sub evaluate_path_info { # 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; @@ -1051,16 +1085,27 @@ sub dispatch { } 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 @@ -1113,8 +1158,8 @@ sub evaluate_argv { sub run { evaluate_argv(); - evaluate_git_version(); + $first_request = 1; $pre_listen_hook->() if $pre_listen_hook; @@ -1127,6 +1172,7 @@ sub run { $post_dispatch_hook->() if $post_dispatch_hook; + $first_request = 0; last REQUEST if ($is_last_request->()); } @@ -1185,7 +1231,7 @@ sub href { $href =~ s,/$,,; # Then add the project name, if present - $href .= "/".esc_url($params{'project'}); + $href .= "/".esc_path_info($params{'project'}); delete $params{'project'}; # since we destructively absorb parameters, we keep this @@ -1195,7 +1241,8 @@ sub href { # Summary just uses the project path URL, any other action is # added to the URL if (defined $params{'action'}) { - $href .= "/".esc_url($params{'action'}) unless $params{'action'} eq 'summary'; + $href .= "/".esc_path_info($params{'action'}) + unless $params{'action'} eq 'summary'; delete $params{'action'}; } @@ -1205,13 +1252,13 @@ sub href { || $params{'hash_parent'} || $params{'hash'}); if (defined $params{'hash_base'}) { if (defined $params{'hash_parent_base'}) { - $href .= esc_url($params{'hash_parent_base'}); + $href .= esc_path_info($params{'hash_parent_base'}); # skip the file_parent if it's the same as the file_name if (defined $params{'file_parent'}) { if (defined $params{'file_name'} && $params{'file_parent'} eq $params{'file_name'}) { delete $params{'file_parent'}; } elsif ($params{'file_parent'} !~ /\.\./) { - $href .= ":/".esc_url($params{'file_parent'}); + $href .= ":/".esc_path_info($params{'file_parent'}); delete $params{'file_parent'}; } } @@ -1219,19 +1266,19 @@ sub href { delete $params{'hash_parent'}; delete $params{'hash_parent_base'}; } elsif (defined $params{'hash_parent'}) { - $href .= esc_url($params{'hash_parent'}). ".."; + $href .= esc_path_info($params{'hash_parent'}). ".."; delete $params{'hash_parent'}; } - $href .= esc_url($params{'hash_base'}); + $href .= esc_path_info($params{'hash_base'}); if (defined $params{'file_name'} && $params{'file_name'} !~ /\.\./) { - $href .= ":/".esc_url($params{'file_name'}); + $href .= ":/".esc_path_info($params{'file_name'}); delete $params{'file_name'}; } delete $params{'hash'}; delete $params{'hash_base'}; } elsif (defined $params{'hash'}) { - $href .= esc_url($params{'hash'}); + $href .= esc_path_info($params{'hash'}); delete $params{'hash'}; } @@ -1264,6 +1311,9 @@ sub href { } $href .= "?" . join(';', @result) if scalar @result; + # final transformation: trailing spaces must be escaped (URI-encoded) + $href =~ s/(\s+)$/CGI::escape($1)/e; + return $href; } @@ -1346,6 +1396,17 @@ sub esc_param { return $str; } +# the quoting rules for path_info fragment are slightly different +sub esc_path_info { + my $str = shift; + return undef unless defined $str; + + # path_info doesn't treat '+' as space (specially), but '?' must be escaped + $str =~ s/([^A-Za-z0-9\-_.~();\/;:@&= +]+)/CGI::escape($1)/eg; + + return $str; +} + # quote unsafe chars in whole URL, so some characters cannot be quoted sub esc_url { my $str = shift; @@ -1355,6 +1416,13 @@ sub esc_url { return $str; } +# quote unsafe characters in HTML attributes +sub esc_attr { + + # for XHTML conformance escaping '"' to '"' is not enough + return esc_html(@_); +} + # replace invalid utf8 character with SUBSTITUTION sequence sub esc_html { my $str = shift; @@ -1760,7 +1828,7 @@ sub format_ref_marker { hash=>$dest )}, $name); - $markers .= " <span class=\"$class\" title=\"$ref\">" . + $markers .= " <span class=\"".esc_attr($class)."\" title=\"".esc_attr($ref)."\">" . $link . "</span>"; } } @@ -1844,7 +1912,7 @@ sub git_get_avatar { return $pre_white . "<img width=\"$size\" " . "class=\"avatar\" " . - "src=\"$url\" " . + "src=\"".esc_url($url)."\" " . "alt=\"\" " . "/>" . $post_white; } else { @@ -2555,7 +2623,7 @@ sub git_show_project_tagcloud { } else { my @tags = sort { $cloud->{$a}->{count} <=> $cloud->{$b}->{count} } keys %$cloud; return '<p align="center">' . join (', ', map { - "<a href=\"$home_link?by_tag=$_\">$cloud->{$_}->{topname}</a>" + $cgi->a({-href=>"$home_link?by_tag=$_"}, $cloud->{$_}->{topname}) } splice(@tags, 0, $count)) . '</p>'; } } @@ -2745,6 +2813,44 @@ sub git_get_last_activity { 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; @@ -2807,8 +2913,10 @@ sub parse_date { $date{'iso-8601'} = sprintf "%04d-%02d-%02dT%02d:%02d:%02dZ", 1900+$year, 1+$mon, $mday, $hour ,$min, $sec; - $tz =~ m/^([+\-][0-9][0-9])([0-9][0-9])$/; - my $local = $epoch + ((int $1 + ($2/60)) * 3600); + my ($tz_sign, $tz_hour, $tz_min) = + ($tz =~ m/^([-+])(\d\d)(\d\d)$/); + $tz_sign = ($tz_sign eq '-' ? -1 : +1); + my $local = $epoch + $tz_sign*((($tz_hour*60) + $tz_min)*60); ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($local); $date{'hour_local'} = $hour; $date{'minute_local'} = $min; @@ -3143,13 +3251,15 @@ sub parse_from_to_diffinfo { ## 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; @@ -3160,7 +3270,7 @@ sub git_get_heads_list { 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; @@ -3357,10 +3467,10 @@ sub run_highlighter { 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). + " --replace-tabs=8 --fragment --syntax $syntax |" or die_error(500, "Couldn't open file or run syntax highlighter"); return $fd; } @@ -3386,6 +3496,51 @@ sub get_page_title { return $title; } +sub print_feed_meta { + if (defined $project) { + my %href_params = get_feed_info(); + if (!exists $href_params{'-title'}) { + $href_params{'-title'} = 'log'; + } + + foreach my $format (qw(RSS Atom)) { + my $type = lc($format); + my %link_attr = ( + '-rel' => 'alternate', + '-title' => esc_attr("$project - $href_params{'-title'} - $format feed"), + '-type' => "application/$type+xml" + ); + + $href_params{'action'} = $type; + $link_attr{'-href'} = href(%href_params); + print "<link ". + "rel=\"$link_attr{'-rel'}\" ". + "title=\"$link_attr{'-title'}\" ". + "href=\"$link_attr{'-href'}\" ". + "type=\"$link_attr{'-type'}\" ". + "/>\n"; + + $href_params{'extra_options'} = '--no-merges'; + $link_attr{'-href'} = href(%href_params); + $link_attr{'-title'} .= ' (no merges)'; + print "<link ". + "rel=\"$link_attr{'-rel'}\" ". + "title=\"$link_attr{'-title'}\" ". + "href=\"$link_attr{'-href'}\" ". + "type=\"$link_attr{'-type'}\" ". + "/>\n"; + } + + } else { + printf('<link rel="alternate" title="%s projects list" '. + 'href="%s" type="text/plain; charset=utf-8" />'."\n", + esc_attr($site_name), href(project=>undef, action=>"project_index")); + printf('<link rel="alternate" title="%s projects feeds" '. + 'href="%s" type="text/x-opml" />'."\n", + esc_attr($site_name), href(project=>undef, action=>"opml")); + } +} + sub git_header_html { my $status = shift || "200 OK"; my $expires = shift; @@ -3428,57 +3583,17 @@ EOF # print out each stylesheet that exist, providing backwards capability # for those people who defined $stylesheet in a config file if (defined $stylesheet) { - print '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'"/>'."\n"; + print '<link rel="stylesheet" type="text/css" href="'.esc_url($stylesheet).'"/>'."\n"; } else { foreach my $stylesheet (@stylesheets) { next unless $stylesheet; - print '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'"/>'."\n"; + print '<link rel="stylesheet" type="text/css" href="'.esc_url($stylesheet).'"/>'."\n"; } } - if (defined $project) { - my %href_params = get_feed_info(); - if (!exists $href_params{'-title'}) { - $href_params{'-title'} = 'log'; - } - - foreach my $format qw(RSS Atom) { - my $type = lc($format); - my %link_attr = ( - '-rel' => 'alternate', - '-title' => "$project - $href_params{'-title'} - $format feed", - '-type' => "application/$type+xml" - ); - - $href_params{'action'} = $type; - $link_attr{'-href'} = href(%href_params); - print "<link ". - "rel=\"$link_attr{'-rel'}\" ". - "title=\"$link_attr{'-title'}\" ". - "href=\"$link_attr{'-href'}\" ". - "type=\"$link_attr{'-type'}\" ". - "/>\n"; - - $href_params{'extra_options'} = '--no-merges'; - $link_attr{'-href'} = href(%href_params); - $link_attr{'-title'} .= ' (no merges)'; - print "<link ". - "rel=\"$link_attr{'-rel'}\" ". - "title=\"$link_attr{'-title'}\" ". - "href=\"$link_attr{'-href'}\" ". - "type=\"$link_attr{'-type'}\" ". - "/>\n"; - } - - } else { - printf('<link rel="alternate" title="%s projects list" '. - 'href="%s" type="text/plain; charset=utf-8" />'."\n", - $site_name, href(project=>undef, action=>"project_index")); - printf('<link rel="alternate" title="%s projects feeds" '. - 'href="%s" type="text/x-opml" />'."\n", - $site_name, href(project=>undef, action=>"opml")); - } + print_feed_meta() + if ($status eq '200 OK'); if (defined $favicon) { - print qq(<link rel="shortcut icon" href="$favicon" type="image/png" />\n); + print qq(<link rel="shortcut icon" href=").esc_url($favicon).qq(" type="image/png" />\n); } print "</head>\n" . @@ -3488,15 +3603,28 @@ EOF insert_file($site_header); } - print "<div class=\"page_header\">\n" . - $cgi->a({-href => esc_url($logo_url), - -title => $logo_label}, - qq(<img src="$logo" width="72" height="27" alt="git" class="logo"/>)); + print "<div class=\"page_header\">\n"; + if (defined $logo) { + print $cgi->a({-href => esc_url($logo_url), + -title => $logo_label}, + $cgi->img({-src => esc_url($logo), + -width => 72, -height => 27, + -alt => "git", + -class => "logo"})); + } print $cgi->a({-href => esc_url($home_link)}, $home_link_str) . " / "; 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"; } @@ -3556,7 +3684,7 @@ sub git_footer_html { } $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", @@ -3575,7 +3703,7 @@ sub git_footer_html { 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">'. @@ -3589,7 +3717,7 @@ sub git_footer_html { insert_file($site_footer); } - print qq!<script type="text/javascript" src="$javascript"></script>\n!; + print qq!<script type="text/javascript" src="!.esc_url($javascript).qq!"></script>\n!; if (defined $action && $action eq 'blame_incremental') { print qq!<script type="text/javascript">\n!. @@ -3703,6 +3831,19 @@ sub git_print_page_nav { "</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; @@ -3746,6 +3887,49 @@ sub git_print_header_div { "\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(@_); } @@ -4230,7 +4414,7 @@ sub git_difftree_body { } 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'}); @@ -4724,7 +4908,6 @@ sub git_log_body { next if !%co; my $commit = $co{'id'}; my $ref = format_ref_marker($refs, $commit); - my %ad = parse_date($co{'author_epoch'}); git_print_header_div('commit', "<span class=\"age\">$co{'age_string'}</span>" . esc_html($co{'title'}) . $ref, @@ -4945,7 +5128,7 @@ sub git_heads_body { "<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>"; } @@ -4957,6 +5140,101 @@ sub git_heads_body { 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; @@ -5094,6 +5372,7 @@ sub git_summary { 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); @@ -5102,6 +5381,7 @@ sub git_summary { # 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'); @@ -5127,7 +5407,7 @@ sub git_summary { @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 = ""; } @@ -5179,6 +5459,11 @@ sub git_summary { $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, @@ -5283,7 +5568,7 @@ sub git_blame_common { 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"; @@ -5470,7 +5755,7 @@ sub git_blame_data { 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(); @@ -5483,7 +5768,7 @@ sub git_tags { 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(); @@ -5493,6 +5778,39 @@ sub git_heads { 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; @@ -5609,14 +5927,14 @@ sub git_blob { } else { print "<div class=\"page_nav\">\n" . "<br/><br/></div>\n" . - "<div class=\"title\">$hash</div>\n"; + "<div class=\"title\">".esc_html($hash)."</div>\n"; } git_print_page_path($file_name, "blob", $hash_base); print "<div class=\"page_body\">\n"; if ($mimetype =~ m!^image/!) { - print qq!<img type="$mimetype"!; + print qq!<img type="!.esc_attr($mimetype).qq!"!; if ($file_name) { - print qq! alt="$file_name" title="$file_name"!; + print qq! alt="!.esc_attr($file_name).qq!" title="!.esc_attr($file_name).qq!"!; } print qq! src="! . href(action=>"blob_plain", hash=>$hash, @@ -5629,7 +5947,7 @@ sub git_blob { $nr++; $line = untabify($line); printf qq!<div class="pre"><a id="l%i" href="%s#l%i" class="linenr">%4i</a> %s</div>\n!, - $nr, href(-replay => 1), $nr, $nr, $syntax ? $line : esc_html($line, -nbsp=>1); + $nr, esc_attr(href(-replay => 1)), $nr, $nr, $syntax ? $line : esc_html($line, -nbsp=>1); } } close $fd @@ -5691,7 +6009,7 @@ sub git_tree { undef $hash_base; print "<div class=\"page_nav\">\n"; print "<br/><br/></div>\n"; - print "<div class=\"title\">$hash</div>\n"; + print "<div class=\"title\">".esc_html($hash)."</div>\n"; } if (defined $file_name) { $basedir = $file_name; @@ -6159,7 +6477,7 @@ sub git_blobdiff { git_print_header_div('commit', esc_html($co{'title'}), $hash_base); } else { print "<div class=\"page_nav\"><br/>$formats_nav<br/></div>\n"; - print "<div class=\"title\">$hash vs $hash_parent</div>\n"; + print "<div class=\"title\">".esc_html("$hash vs $hash_parent")."</div>\n"; } if (defined $file_name) { git_print_page_path($file_name, "blob", $hash_base); @@ -6747,7 +7065,7 @@ sub git_feed { if (defined($commitlist[0])) { %latest_commit = %{$commitlist[0]}; my $latest_epoch = $latest_commit{'committer_epoch'}; - %latest_date = parse_date($latest_epoch); + %latest_date = parse_date($latest_epoch, $latest_commit{'comitter_tz'}); my $if_modified = $cgi->http('IF_MODIFIED_SINCE'); if (defined $if_modified) { my $since; @@ -6857,7 +7175,7 @@ XML if (defined $favicon) { print "<icon>" . esc_url($favicon) . "</icon>\n"; } - if (defined $logo_url) { + if (defined $logo) { # not twice as wide as tall: 72 x 27 pixels print "<logo>" . esc_url($logo) . "</logo>\n"; } @@ -6878,7 +7196,7 @@ XML if (($i >= 20) && ((time - $co{'author_epoch'}) > 48*60*60)) { last; } - my %cd = parse_date($co{'author_epoch'}); + my %cd = parse_date($co{'author_epoch'}, $co{'author_tz'}); # get list of changed files open my $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts, diff --git a/gitweb/static/gitweb.css b/gitweb/static/gitweb.css index 4132aabcdb..79d7eebba7 100644 --- a/gitweb/static/gitweb.css +++ b/gitweb/static/gitweb.css @@ -573,6 +573,12 @@ div.binary { 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: */ @@ -189,30 +189,74 @@ static struct grep_expr *compile_pattern_expr(struct grep_pat **list) 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 */ @@ -231,9 +275,6 @@ void compile_grep_patterns(struct grep_opt *opt) 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); @@ -243,22 +284,18 @@ void compile_grep_patterns(struct grep_opt *opt) 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: @@ -487,6 +524,9 @@ static int match_expr_eval(struct grep_expr *x, char *bol, char *eol, 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; @@ -22,6 +22,7 @@ enum grep_header_field { GREP_HEADER_AUTHOR = 0, GREP_HEADER_COMMITTER }; +#define GREP_HEADER_FIELD_MAX (GREP_HEADER_COMMITTER + 1) struct grep_pat { struct grep_pat *next; @@ -41,6 +42,7 @@ enum grep_expr_node { GREP_NODE_ATOM, GREP_NODE_NOT, GREP_NODE_AND, + GREP_NODE_TRUE, GREP_NODE_OR }; @@ -81,7 +81,7 @@ void **insert_hash(unsigned int hash, void *ptr, struct hash_table *table) 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; @@ -92,7 +92,7 @@ int for_each_hash(const struct hash_table *table, int (*fn)(void *)) void *ptr = array->ptr; array++; if (ptr) { - int val = fn(ptr); + int val = fn(ptr, data); if (val < 0) return val; sum += val; @@ -30,7 +30,7 @@ struct hash_table { 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) @@ -3,6 +3,7 @@ #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) @@ -298,7 +299,8 @@ static void add_cmd_list(struct cmdnames *cmds, struct cmdnames *old) } /* 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) { @@ -319,10 +321,28 @@ 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); @@ -330,10 +350,21 @@ const char *help_unknown_cmd(const char *cmd) 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/http-backend.c b/http-backend.c index 14c90c2e84..85015048dd 100644 --- a/http-backend.c +++ b/http-backend.c @@ -510,9 +510,7 @@ static char* getdir(void) die("GIT_PROJECT_ROOT is set but PATH_INFO is not"); if (daemon_avoid_alias(pathinfo)) die("'%s': aliased", pathinfo); - strbuf_addstr(&buf, root); - if (buf.buf[buf.len - 1] != '/') - strbuf_addch(&buf, '/'); + end_url_with_slash(&buf, root); if (pathinfo[0] == '/') pathinfo++; strbuf_addstr(&buf, pathinfo); diff --git a/http-fetch.c b/http-fetch.c index 762c750d7a..923904f97f 100644 --- a/http-fetch.c +++ b/http-fetch.c @@ -14,8 +14,7 @@ int main(int argc, const char **argv) int commits; const char **write_ref = NULL; char **commit_id; - const char *url; - char *rewritten_url = NULL; + char *url = NULL; int arg = 1; int rc = 0; int get_tree = 0; @@ -57,19 +56,14 @@ int main(int argc, const char **argv) commit_id = (char **) &argv[arg++]; commits = 1; } - url = argv[arg]; + + if (argv[arg]) + str_end_url_with_slash(argv[arg], &url); prefix = setup_git_directory(); git_config(git_default_config, NULL); - if (url && url[strlen(url)-1] != '/') { - rewritten_url = xmalloc(strlen(url)+2); - strcpy(rewritten_url, url); - strcat(rewritten_url, "/"); - url = rewritten_url; - } - http_init(NULL); walker = get_http_walker(url); walker->get_tree = get_tree; @@ -93,7 +87,7 @@ int main(int argc, const char **argv) walker_free(walker); http_cleanup(); - free(rewritten_url); + free(url); return rc; } diff --git a/http-push.c b/http-push.c index c9bcd11697..d18346c0f5 100644 --- a/http-push.c +++ b/http-push.c @@ -82,8 +82,7 @@ static int helper_status; static struct object_list *objects; -struct repo -{ +struct repo { char *url; char *path; int path_len; @@ -108,8 +107,7 @@ enum transfer_state { COMPLETE }; -struct transfer_request -{ +struct transfer_request { struct object *obj; char *url; char *dest; @@ -127,8 +125,7 @@ struct transfer_request static struct transfer_request *request_queue_head; -struct xml_ctx -{ +struct xml_ctx { char *name; int len; char *cdata; @@ -136,8 +133,7 @@ struct xml_ctx void *userData; }; -struct remote_lock -{ +struct remote_lock { char *url; char *owner; char *token; @@ -156,8 +152,7 @@ struct remote_lock /* Flags that remote_ls passes to callback functions */ #define IS_DIR (1u << 0) -struct remote_ls_ctx -{ +struct remote_ls_ctx { char *path; void (*userFunc)(struct remote_ls_ctx *ls); void *userData; @@ -1090,6 +1085,10 @@ static void handle_remote_ls_ctx(struct xml_ctx *ctx, int tag_closed) if (tag_closed) { if (!strcmp(ctx->name, DAV_PROPFIND_RESP) && ls->dentry_name) { if (ls->dentry_flags & IS_DIR) { + + /* ensure collection names end with slash */ + str_end_url_with_slash(ls->dentry_name, &ls->dentry_name); + if (ls->flags & PROCESS_DIRS) { ls->userFunc(ls); } @@ -1112,8 +1111,16 @@ static void handle_remote_ls_ctx(struct xml_ctx *ctx, int tag_closed) } } if (path) { - path += repo->path_len; - ls->dentry_name = xstrdup(path); + const char *url = repo->url; + if (repo->path) + url = repo->path; + if (strncmp(path, url, repo->path_len)) + error("Parsed path '%s' does not match url: '%s'\n", + path, url); + else { + path += repo->path_len; + ls->dentry_name = xstrdup(path); + } } } else if (!strcmp(ctx->name, DAV_PROPFIND_COLLECTION)) { ls->dentry_flags |= IS_DIR; @@ -1789,7 +1796,6 @@ int main(int argc, char **argv) int new_refs; struct ref *ref, *local_refs; struct remote *remote; - char *rewritten_url = NULL; git_extract_argv0_path(argv[0]); @@ -1835,8 +1841,8 @@ int main(int argc, char **argv) } if (!repo->url) { char *path = strstr(arg, "//"); - repo->url = arg; - repo->path_len = strlen(arg); + str_end_url_with_slash(arg, &repo->url); + repo->path_len = strlen(repo->url); if (path) { repo->path = strchr(path+2, '/'); if (repo->path) @@ -1872,15 +1878,6 @@ int main(int argc, char **argv) remote->url[remote->url_nr++] = repo->url; http_init(remote); - if (repo->url && repo->url[strlen(repo->url)-1] != '/') { - rewritten_url = xmalloc(strlen(repo->url)+2); - strcpy(rewritten_url, repo->url); - strcat(rewritten_url, "/"); - repo->path = rewritten_url + (repo->path - repo->url); - repo->path_len++; - repo->url = rewritten_url; - } - #ifdef USE_CURL_MULTI is_running_queue = 0; #endif @@ -2088,7 +2085,6 @@ int main(int argc, char **argv) } cleanup: - free(rewritten_url); if (info_ref_lock) unlock_remote(info_ref_lock); free(repo); diff --git a/http-walker.c b/http-walker.c index 18bd6504be..9bc8114c3b 100644 --- a/http-walker.c +++ b/http-walker.c @@ -3,8 +3,7 @@ #include "walker.h" #include "http.h" -struct alt_base -{ +struct alt_base { char *base; int got_indices; struct packed_git *packs; @@ -18,8 +17,7 @@ enum object_request_state { COMPLETE }; -struct object_request -{ +struct object_request { struct walker *walker; unsigned char sha1[20]; struct alt_base *repo; @@ -2,6 +2,7 @@ #include "pack.h" #include "sideband.h" #include "run-command.h" +#include "url.h" int data_received; int active_requests; @@ -279,6 +280,11 @@ static CURL *get_curl_handle(void) } curl_easy_setopt(result, CURLOPT_FOLLOWLOCATION, 1); +#if LIBCURL_VERSION_NUM >= 0x071301 + curl_easy_setopt(result, CURLOPT_POSTREDIR, CURL_REDIR_POST_ALL); +#elif LIBCURL_VERSION_NUM >= 0x071101 + curl_easy_setopt(result, CURLOPT_POST301, 1); +#endif if (getenv("GIT_CURL_VERBOSE")) curl_easy_setopt(result, CURLOPT_VERBOSE, 1); @@ -297,7 +303,7 @@ static CURL *get_curl_handle(void) static void http_auth_init(const char *url) { - char *at, *colon, *cp, *slash; + char *at, *colon, *cp, *slash, *decoded; int len; cp = strstr(url, "://"); @@ -322,16 +328,25 @@ static void http_auth_init(const char *url) user_name = xmalloc(len + 1); memcpy(user_name, cp, len); user_name[len] = '\0'; + decoded = url_decode(user_name); + free(user_name); + user_name = decoded; user_pass = NULL; } else { len = colon - cp; user_name = xmalloc(len + 1); memcpy(user_name, cp, len); user_name[len] = '\0'; + decoded = url_decode(user_name); + free(user_name); + user_name = decoded; len = at - (colon + 1); user_pass = xmalloc(len + 1); memcpy(user_pass, colon + 1, len); user_pass[len] = '\0'; + decoded = url_decode(user_pass); + free(user_pass); + user_pass = decoded; } } @@ -728,13 +743,6 @@ static inline int hex(int v) return 'A' + v - 10; } -void end_url_with_slash(struct strbuf *buf, const char *url) -{ - strbuf_addstr(buf, url); - if (buf->len && buf->buf[buf->len - 1] != '/') - strbuf_addstr(buf, "/"); -} - static char *quote_ref_url(const char *base, const char *ref) { struct strbuf buf = STRBUF_INIT; @@ -8,6 +8,7 @@ #include "strbuf.h" #include "remote.h" +#include "url.h" /* * We detect based on the cURL version if multi-transfer is @@ -41,14 +42,12 @@ #define NO_CURL_IOCTL #endif -struct slot_results -{ +struct slot_results { CURLcode curl_result; long http_code; }; -struct active_request_slot -{ +struct active_request_slot { CURL *curl; FILE *local; int in_use; @@ -61,8 +60,7 @@ struct active_request_slot struct active_request_slot *next; }; -struct buffer -{ +struct buffer { struct strbuf buf; size_t posn; }; @@ -117,7 +115,6 @@ extern void append_remote_object_url(struct strbuf *buf, const char *url, int only_two_digit_prefix); extern char *get_remote_object_url(const char *url, const char *hex, int only_two_digit_prefix); -extern void end_url_with_slash(struct strbuf *buf, const char *url); /* Options for http_request_*() */ #define HTTP_NO_CACHE 1 @@ -149,8 +146,7 @@ extern int http_fetch_ref(const char *base, struct ref *ref); extern int http_get_info_packs(const char *base_url, struct packed_git **packs_head); -struct http_pack_request -{ +struct http_pack_request { char *url; struct packed_git *target; struct packed_git **lst; @@ -166,8 +162,7 @@ extern int finish_http_pack_request(struct http_pack_request *preq); extern void release_http_pack_request(struct http_pack_request *preq); /* Helpers for fetching object */ -struct http_object_request -{ +struct http_object_request { char *url; char tmpfile[PATH_MAX]; int localfile; @@ -217,8 +217,10 @@ const char *fmt_ident(const char *name, const char *email, } strcpy(date, git_default_date); - if (!name_addr_only && date_str) - parse_date(date_str, date, sizeof(date)); + if (!name_addr_only && date_str && date_str[0]) { + if (parse_date(date_str, date, sizeof(date)) < 0) + die("invalid date format: %s", date_str); + } i = copy(buffer, sizeof(buffer), 0, name); i = add_raw(buffer, sizeof(buffer), i, " <"); diff --git a/list-objects.c b/list-objects.c index 8953548c07..44fa5a92e2 100644 --- a/list-objects.c +++ b/list-objects.c @@ -148,7 +148,12 @@ void traverse_commit_list(struct rev_info *revs, struct commit *commit; while ((commit = get_revision(revs)) != NULL) { - add_pending_tree(revs, commit->tree); + /* + * an uninteresting boundary commit may not have its tree + * parsed yet, but we are not going to show them anyway + */ + if (commit->tree) + add_pending_tree(revs, commit->tree); show_commit(commit, data); } for (i = 0; i < revs->pending.nr; i++) { diff --git a/ll-merge.c b/ll-merge.c index 6bb3095c3a..6ce512efc4 100644 --- a/ll-merge.c +++ b/ll-merge.c @@ -18,7 +18,7 @@ typedef int (*ll_merge_fn)(const 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, + const struct ll_merge_options *opts, int marker_size); struct ll_merge_driver { @@ -39,14 +39,18 @@ static int ll_binary_merge(const struct ll_merge_driver *drv_unused, 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; @@ -60,9 +64,11 @@ static int ll_xdl_merge(const struct ll_merge_driver *drv_unused, 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) || @@ -74,12 +80,13 @@ static int ll_xdl_merge(const struct ll_merge_driver *drv_unused, 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) @@ -96,15 +103,17 @@ static int ll_union_merge(const struct ll_merge_driver *drv_unused, 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 @@ -136,7 +145,8 @@ static int ll_ext_merge(const struct ll_merge_driver *fn, 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; @@ -144,6 +154,7 @@ static int ll_ext_merge(const struct ll_merge_driver *fn, 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]; @@ -337,15 +348,18 @@ int ll_merge(mmbuffer_t *result_buf, 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); @@ -359,11 +373,11 @@ int ll_merge(mmbuffer_t *result_buf, } } 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 ff7ca87bfa..244a31f55a 100644 --- a/ll-merge.h +++ b/ll-merge.h @@ -5,27 +5,19 @@ #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); @@ -79,12 +79,14 @@ static void add_mapping(struct string_list *map, if (old_name == NULL) { debug_mm("mailmap: adding (simple) entry for %s at index %d\n", old_email, index); /* Replace current name and new email for simple entry */ - free(me->name); - free(me->email); - if (new_name) + if (new_name) { + free(me->name); me->name = xstrdup(new_name); - if (new_email) + } + if (new_email) { + free(me->email); me->email = xstrdup(new_email); + } } else { struct mailmap_info *mi = xmalloc(sizeof(struct mailmap_info)); debug_mm("mailmap: adding (complex) entry for %s at index %d\n", old_email, index); diff --git a/merge-file.c b/merge-file.c index db4d0d50d3..f7f4533926 100644 --- a/merge-file.c +++ b/merge-file.c @@ -37,7 +37,7 @@ static void *three_way_filemerge(const char *path, mmfile_t *base, mmfile_t *our * 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 c574698819..42d52cb5bc 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -63,20 +63,62 @@ static int sha_eq(const unsigned char *a, const unsigned char *b) 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. */ -struct stage_data -{ - struct - { +struct stage_data { + struct { 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; @@ -302,8 +344,64 @@ static struct string_list *get_unmerged(void) return unmerged; } -struct rename +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; struct stage_data *src_entry; struct stage_data *dst_entry; @@ -334,6 +432,7 @@ static struct string_list *get_renames(struct merge_options *o, 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) @@ -373,11 +472,10 @@ static struct string_list *get_renames(struct merge_options *o, 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; @@ -393,6 +491,34 @@ static int update_stages(const char *path, struct diff_filespec *o, 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) { @@ -588,8 +714,7 @@ static void update_file(struct merge_options *o, /* Low level file merging, update and removal */ -struct merge_file_info -{ +struct merge_file_info { unsigned char sha[20]; unsigned mode; unsigned clean:1, @@ -605,22 +730,26 @@ static int merge_3way(struct merge_options *o, 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; } } @@ -643,10 +772,7 @@ static int merge_3way(struct merge_options *o, 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); @@ -730,29 +856,56 @@ static struct merge_file_info merge_file(struct merge_options *o, 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); @@ -760,34 +913,27 @@ static void conflict_rename_rename(struct merge_options *o, /* * 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", @@ -877,84 +1023,60 @@ static int process_renames(struct merge_options *o, 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 @@ -989,6 +1111,7 @@ static int process_renames(struct merge_options *o, 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); @@ -1003,13 +1126,12 @@ static int process_renames(struct merge_options *o, "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; @@ -1020,41 +1142,15 @@ static int process_renames(struct merge_options *o, 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); } } } @@ -1117,6 +1213,90 @@ error_return: 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) @@ -1134,6 +1314,9 @@ static int process_entry(struct merge_options *o, 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 */ @@ -1146,22 +1329,15 @@ static int process_entry(struct merge_options *o, 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) || @@ -1180,15 +1356,7 @@ static int process_entry(struct merge_options *o, 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); @@ -1196,34 +1364,9 @@ static int process_entry(struct merge_options *o, } 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 @@ -1237,13 +1380,19 @@ static int process_entry(struct merge_options *o, } /* - * 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; @@ -1252,43 +1401,91 @@ static int process_df_entry(struct merge_options *o, 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; @@ -1333,6 +1530,7 @@ int merge_trees(struct merge_options *o, 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); @@ -1350,6 +1548,12 @@ int merge_trees(struct merge_options *o, && !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); @@ -1550,3 +1754,37 @@ void init_merge_options(struct merge_options *o) 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 34492dbd6e..c8135b0ec7 100644 --- a/merge-recursive.h +++ b/merge-recursive.h @@ -15,9 +15,11 @@ struct merge_options { 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; @@ -52,6 +54,8 @@ int merge_recursive_generic(struct merge_options *o, 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); diff --git a/name-hash.c b/name-hash.c index 0031d78e8c..c6b6a3fe4c 100644 --- a/name-hash.c +++ b/name-hash.c @@ -32,6 +32,42 @@ static unsigned int hash_name(const char *name, int namelen) 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; @@ -47,6 +83,9 @@ static void hash_index_entry(struct index_state *istate, struct cache_entry *ce) 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 dee6d62e72..4c8984ede1 100644 --- a/notes-cache.c +++ b/notes-cache.c @@ -89,6 +89,5 @@ int notes_cache_put(struct notes_cache *c, unsigned char key_sha1[20], 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 new file mode 100644 index 0000000000..1467ad3179 --- /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 new file mode 100644 index 0000000000..168a6724cd --- /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 @@ -150,86 +150,6 @@ static struct leaf_node *note_tree_find(struct notes_tree *t, } /* - * 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. * Otherwise replace the int_node at the given index in the given parent node @@ -305,6 +225,93 @@ static void note_tree_remove(struct notes_tree *t, 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) { @@ -445,8 +452,12 @@ static void load_subtree(struct notes_tree *t, struct leaf_node *subtree, 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; @@ -804,16 +815,17 @@ int combine_notes_concatenate(unsigned char *cur_sha1, 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); @@ -836,6 +848,82 @@ int combine_notes_ignore(unsigned char *cur_sha1, 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) { @@ -893,7 +981,7 @@ static int notes_display_config(const char *k, const char *v, 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) @@ -935,7 +1023,7 @@ void init_notes(struct notes_tree *t, const char *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); @@ -989,7 +1077,7 @@ void init_display_notes(struct display_notes_opt *opt) 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; @@ -1003,7 +1091,7 @@ void add_note(struct notes_tree *t, const unsigned char *object_sha1, 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) @@ -1182,7 +1270,7 @@ void format_display_notes(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); @@ -1191,9 +1279,9 @@ int copy_note(struct notes_tree *t, 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; } @@ -12,7 +12,10 @@ * 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 @@ -24,6 +27,7 @@ typedef int (*combine_notes_fn)(unsigned char *cur_sha1, const unsigned char *ne 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 @@ -44,6 +48,20 @@ extern struct notes_tree { } 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 * * Default behaviour is to initialize the notes tree from the tree object @@ -76,11 +94,24 @@ void init_notes(struct notes_tree *t, const char *notes_ref, /* * 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); /* @@ -105,11 +136,18 @@ const unsigned char *get_note(struct notes_tree *t, /* * 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() @@ -151,6 +189,7 @@ int copy_note(struct notes_tree *t, * 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/pack-check.c b/pack-check.c index 9d0cb9a114..c3bf21dbcb 100644 --- a/pack-check.c +++ b/pack-check.c @@ -2,8 +2,7 @@ #include "pack.h" #include "pack-revindex.h" -struct idx_entry -{ +struct idx_entry { off_t offset; const unsigned char *sha1; unsigned int nr; diff --git a/parse-options.c b/parse-options.c index 0fa79bc31d..42b51ef145 100644 --- a/parse-options.c +++ b/parse-options.c @@ -11,6 +11,13 @@ static int parse_options_usage(struct parse_opt_ctx_t *ctx, #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) @@ -55,25 +62,13 @@ static int get_value(struct parse_opt_ctx_t *p, 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; @@ -281,13 +276,6 @@ static int parse_nodash_opt(struct parse_opt_ctx_t *p, const char *arg, 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); } @@ -320,25 +308,37 @@ static void parse_options_check(const struct option *opts) 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; @@ -350,6 +350,7 @@ void parse_options_start(struct parse_opt_ctx_t *ctx, 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 *, @@ -362,8 +363,6 @@ int parse_options_step(struct parse_opt_ctx_t *ctx, { 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; @@ -374,7 +373,7 @@ int parse_options_step(struct parse_opt_ctx_t *ctx, 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; } @@ -452,10 +451,11 @@ int parse_options(int argc, const char **argv, const char *prefix, { 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 */ @@ -541,7 +541,8 @@ static int usage_with_options_internal(struct parse_opt_ctx_t *ctx, 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 d982f0f1bf..d1b12fe979 100644 --- a/parse-options.h +++ b/parse-options.h @@ -17,6 +17,7 @@ enum parse_opt_type { OPTION_STRING, OPTION_INTEGER, OPTION_CALLBACK, + OPTION_LOWLEVEL_CALLBACK, OPTION_FILENAME }; @@ -43,6 +44,10 @@ enum parse_opt_option_flags { 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 @@ -87,7 +92,8 @@ typedef int parse_opt_cb(const struct option *, const char *arg, int unset); * 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. @@ -135,7 +141,7 @@ struct option { { 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" } @@ -161,6 +167,7 @@ extern NORETURN void usage_msg_opt(const char *msg, enum { PARSE_OPT_HELP = -1, PARSE_OPT_DONE, + PARSE_OPT_NON_OPTION, PARSE_OPT_UNKNOWN }; @@ -180,7 +187,7 @@ struct parse_opt_ctx_t { 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, @@ -198,14 +205,15 @@ extern int parse_opt_verbosity_cb(const struct option *, const char *, int); 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 d218faa02b..56e0a5ede2 100644 --- a/patch-delta.c +++ b/patch-delta.c @@ -48,7 +48,7 @@ void *patch_delta(const void *src_buf, unsigned long src_size, 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; @@ -161,119 +161,6 @@ char *git_path_submodule(const char *path, const char *fmt, ...) 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 6cb0dd1934..a86ab709c2 100644 --- a/perl/Git.pm +++ b/perl/Git.pm @@ -7,6 +7,7 @@ Git - Perl interface to the Git version control system package Git; +use 5.008; use strict; @@ -98,7 +99,7 @@ increase notwithstanding). 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); } @@ -395,7 +396,16 @@ See C<command_close_bidi_pipe()> for details. 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(' ', @_)); } @@ -842,7 +852,7 @@ sub _open_hash_and_insert_object_if_needed { ($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 { @@ -931,7 +941,7 @@ sub _open_cat_blob_if_needed { ($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 { @@ -1278,6 +1288,14 @@ sub _command_common_pipe { # 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() @@ -1285,8 +1303,6 @@ sub _cmd_exec { $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..]) @@ -216,36 +216,53 @@ static int is_rfc2047_special(char ch) static void add_rfc2047(struct strbuf *sb, const char *line, int len, const char *encoding) { - int i, last; + static const int max_length = 78; /* per rfc2822 */ + int i; + int line_len; + + /* How many bytes are already used on the current line? */ + for (i = sb->len - 1; i >= 0; i--) + if (sb->buf[i] == '\n') + break; + line_len = sb->len - (i+1); for (i = 0; i < len; i++) { int ch = line[i]; - if (non_ascii(ch)) + if (non_ascii(ch) || ch == '\n') goto needquote; if ((i + 1 < len) && (ch == '=' && line[i+1] == '?')) goto needquote; } - strbuf_add(sb, line, len); + strbuf_add_wrapped_bytes(sb, line, len, 0, 1, max_length - line_len); return; needquote: strbuf_grow(sb, len * 3 + strlen(encoding) + 100); strbuf_addf(sb, "=?%s?q?", encoding); - for (i = last = 0; i < len; i++) { + line_len += strlen(encoding) + 5; /* 5 for =??q? */ + for (i = 0; i < len; i++) { unsigned ch = line[i] & 0xFF; + + if (line_len >= max_length - 2) { + strbuf_addf(sb, "?=\n =?%s?q?", encoding); + line_len = strlen(encoding) + 5 + 1; /* =??q? plus SP */ + } + /* * We encode ' ' using '=20' even though rfc2047 * allows using '_' for readability. Unfortunately, * many programs do not understand this and just * leave the underscore in place. */ - if (is_rfc2047_special(ch) || ch == ' ') { - strbuf_add(sb, line + last, i - last); + if (is_rfc2047_special(ch) || ch == ' ' || ch == '\n') { strbuf_addf(sb, "=%02X", ch); - last = i + 1; + line_len += 3; + } + else { + strbuf_addch(sb, ch); + line_len++; } } - strbuf_add(sb, line + last, len - last); strbuf_addstr(sb, "?="); } @@ -403,8 +420,8 @@ static char *replace_encoding_header(char *buf, const char *encoding) 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; @@ -555,6 +572,7 @@ struct format_commit_context { 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. */ @@ -591,7 +609,7 @@ static int add_again(struct strbuf *sb, struct chunk *chunk) 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++) { @@ -677,8 +695,8 @@ const char *format_subject(struct strbuf *sb, const char *msg, 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; @@ -741,7 +759,7 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder, { 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; @@ -886,8 +904,7 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder, 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; @@ -1012,13 +1029,27 @@ void format_commit_message(const struct commit *commit, 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, @@ -1092,11 +1123,10 @@ void pp_title_line(enum cmit_fmt fmt, const char *encoding, int need_8bit_cte) { - const char *line_separator = (fmt == CMIT_FMT_EMAIL) ? "\n " : " "; struct strbuf title; strbuf_init(&title, 80); - *msg_p = format_subject(&title, *msg_p, line_separator); + *msg_p = format_subject(&title, *msg_p, " "); strbuf_grow(sb, title.len + 1024); if (subject) { @@ -1159,11 +1189,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); @@ -1,8 +1,7 @@ #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 971e27705c..0480d9455c 100644 --- a/read-cache.c +++ b/read-cache.c @@ -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 */ diff --git a/reflog-walk.c b/reflog-walk.c index 4879615cad..5d81d39a52 100644 --- a/reflog-walk.c +++ b/reflog-walk.c @@ -239,7 +239,6 @@ void fake_reflog_parent(struct reflog_walk_info *info, struct commit *commit) commit->parents = xcalloc(sizeof(struct commit_list), 1); commit->parents->item = commit_info->commit; - commit->object.flags &= ~(ADDED | SEEN | SHOWN); } void get_reflog_selector(struct strbuf *sb, @@ -493,7 +493,7 @@ static void read_config(void) } /* - * 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 @@ -325,7 +325,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); diff --git a/revision.c b/revision.c index b1c18906ba..7b9eaefae4 100644 --- a/revision.c +++ b/revision.c @@ -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; @@ -494,7 +494,7 @@ static int add_parents_to_list(struct rev_info *revs, struct commit *commit, 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; } @@ -521,7 +521,7 @@ static int add_parents_to_list(struct rev_info *revs, struct commit *commit, 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; @@ -1891,7 +1891,7 @@ int prepare_revision_walk(struct rev_info *revs) 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++; @@ -2030,8 +2030,10 @@ static struct commit *get_revision_1(struct rev_info *revs) revs->commits = entry->next; free(entry); - if (revs->reflog_info) + if (revs->reflog_info) { fake_reflog_parent(revs->reflog_info, commit); + commit->object.flags &= ~(ADDED | SEEN | SHOWN); + } /* * If we haven't done the list limiting, we need to look at diff --git a/run-command.c b/run-command.c index 2a1041ef65..f91e446c86 100644 --- a/run-command.c +++ b/run-command.c @@ -194,6 +194,7 @@ fail_pipe: } trace_argv_printf(cmd->argv, "trace: run_command:"); + fflush(NULL); #ifndef WIN32 { @@ -201,7 +202,6 @@ fail_pipe: 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 60b4ba66eb..05d7ab118b 100644 --- a/send-pack.h +++ b/send-pack.h @@ -5,6 +5,7 @@ struct send_pack_args { unsigned verbose:1, quiet:1, porcelain:1, + progress:1, send_mirror:1, force_update:1, use_thin_pack:1, @@ -4,7 +4,7 @@ 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); @@ -46,7 +46,7 @@ const char *prefix_filename(const char *pfx, int pfx_len, const char *arg) { static char path[PATH_MAX]; #ifndef WIN32 - if (!pfx || !*pfx || is_absolute_path(arg)) + if (!pfx_len || is_absolute_path(arg)) return arg; memcpy(path, pfx, pfx_len); strcpy(path + pfx_len, arg); @@ -55,7 +55,7 @@ const char *prefix_filename(const char *pfx, int pfx_len, const char *arg) /* don't add prefix to absolute paths, but still replace '\' by '/' */ if (is_absolute_path(arg)) pfx_len = 0; - else + else if (pfx_len) memcpy(path, pfx, pfx_len); strcpy(path + pfx_len, arg); for (p = path + pfx_len; *p; p++) @@ -208,24 +208,6 @@ int is_inside_work_tree(void) 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; @@ -239,13 +221,33 @@ void setup_work_tree(void) 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", @@ -314,64 +316,124 @@ const char *read_gitfile_gently(const char *path) } 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; @@ -382,23 +444,35 @@ static const char *setup_discovered_git_dir(const char *work_tree_env, 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; } @@ -428,11 +502,10 @@ static dev_t get_device_or_die(const char *path, const char *prefix) */ 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; @@ -445,6 +518,10 @@ static const char *setup_git_directory_gently_1(int *nongit_ok) 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 @@ -452,10 +529,7 @@ static const char *setup_git_directory_gently_1(int *nongit_ok) */ 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)) @@ -472,17 +546,30 @@ static const char *setup_git_directory_gently_1(int *nongit_ok) * - ../../.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); @@ -512,8 +599,10 @@ const char *setup_git_directory_gently(int *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 +679,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); } /* @@ -601,19 +690,5 @@ int check_repository_format(void) */ 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 0cd9435619..e194f6a128 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -35,6 +35,43 @@ static size_t sz_fmt(size_t s) { return s; } 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); @@ -298,7 +335,7 @@ static void read_info_alternates(const char * relative_base, int depth) 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)) { @@ -380,6 +417,8 @@ static unsigned int pack_used_ctr; static unsigned int pack_mmap_calls; static unsigned int peak_pack_open_windows; static unsigned int pack_open_windows; +static unsigned int pack_open_fds; +static unsigned int pack_max_fds; static size_t peak_pack_mapped; static size_t pack_mapped; struct packed_git *packed_git; @@ -411,7 +450,7 @@ static int check_packed_git_idx(const char *path, struct packed_git *p) 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) @@ -557,8 +596,10 @@ static int unuse_one_window(struct packed_git *current, int keep_fd) lru_l->next = lru_w->next; else { lru_p->windows = lru_w->next; - if (!lru_p->windows && lru_p->pack_fd != keep_fd) { + if (!lru_p->windows && lru_p->pack_fd != -1 + && lru_p->pack_fd != keep_fd) { close(lru_p->pack_fd); + pack_open_fds--; lru_p->pack_fd = -1; } } @@ -576,6 +617,21 @@ void release_pack_memory(size_t need, int fd) ; /* 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) { @@ -628,8 +684,10 @@ void free_pack_by_name(const char *pack_name) if (strcmp(pack_name, p->pack_name) == 0) { clear_delta_base_cache(); close_pack_windows(p); - if (p->pack_fd != -1) + if (p->pack_fd != -1) { close(p->pack_fd); + pack_open_fds--; + } close_pack_index(p); free(p->bad_object_sha1); *pp = p->next; @@ -655,11 +713,29 @@ static int open_packed_git_1(struct packed_git *p) 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); + if (!pack_max_fds) { + struct rlimit lim; + unsigned int max_fds; + + if (getrlimit(RLIMIT_NOFILE, &lim)) + die_errno("cannot get RLIMIT_NOFILE"); + + max_fds = lim.rlim_cur; + + /* Save 3 for stdin/stdout/stderr, 22 for work */ + if (25 < max_fds) + pack_max_fds = max_fds - 25; + else + pack_max_fds = 1; + } + + while (pack_max_fds <= pack_open_fds && unuse_one_window(NULL, -1)) + ; /* nothing */ + + p->pack_fd = git_open_noatime(p->pack_name, p); if (p->pack_fd < 0 || fstat(p->pack_fd, &st)) return -1; + pack_open_fds++; /* If we created the struct before we had the pack we lack size. */ if (!p->pack_size) { @@ -711,6 +787,7 @@ static int open_packed_git(struct packed_git *p) return 0; if (p->pack_fd != -1) { close(p->pack_fd); + pack_open_fds--; p->pack_fd = -1; } return -1; @@ -736,14 +813,13 @@ unsigned char *use_pack(struct packed_git *p, { struct pack_window *win = *w_cursor; - if (p->pack_fd == -1 && open_packed_git(p)) - die("packfile %s cannot be accessed", p->pack_name); - /* Since packfiles end in a hash of their content and it's * pointless to ask for an offset into the middle of that * hash, and the in_window function above wouldn't match * don't allow an offset too close to the end of the file. */ + if (!p->pack_size && p->pack_fd == -1 && open_packed_git(p)) + die("packfile %s cannot be accessed", p->pack_name); if (offset > (p->pack_size - 20)) die("offset beyond end of packfile (truncated pack?)"); @@ -757,6 +833,10 @@ unsigned char *use_pack(struct packed_git *p, if (!win) { size_t window_align = packed_git_window_size / 2; off_t len; + + if (p->pack_fd == -1 && open_packed_git(p)) + die("packfile %s cannot be accessed", p->pack_name); + win = xcalloc(1, sizeof(*win)); win->offset = (offset / window_align) * window_align; len = p->pack_size - win->offset; @@ -774,6 +854,12 @@ unsigned char *use_pack(struct packed_git *p, die("packfile %s cannot be mapped: %s", p->pack_name, strerror(errno)); + if (!win->offset && win->len == p->pack_size + && !p->do_not_close) { + close(p->pack_fd); + pack_open_fds--; + p->pack_fd = -1; + } pack_mmap_calls++; pack_open_windows++; if (pack_mapped > peak_pack_mapped) @@ -803,11 +889,22 @@ static struct packed_git *alloc_packed_git(int extra) 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. @@ -857,6 +954,9 @@ struct packed_git *parse_pack_index(unsigned char *sha1, const char *idx_path) void install_packed_git(struct packed_git *pack) { + if (pack->pack_fd != -1) + pack_open_fds++; + pack->next = packed_git; packed_git = pack; } @@ -874,8 +974,6 @@ static void prepare_packed_git_one(char *objdir, int local) sprintf(path, "%s/pack", objdir); len = strlen(path); dir = opendir(path); - while (!dir && errno == EMFILE && unuse_one_window(packed_git, -1)) - dir = opendir(path); if (!dir) { if (errno != ENOENT) error("unable to open object pack directory: %s: %s", @@ -1003,7 +1101,7 @@ static void mark_bad_packed_object(struct packed_git *p, 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; @@ -1011,8 +1109,8 @@ static int has_packed_and_bad(const unsigned char *sha1) 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 +1120,23 @@ 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 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) @@ -1042,7 +1145,7 @@ 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; @@ -1051,7 +1154,7 @@ static int open_sha1_file(const unsigned char *sha1) 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; } @@ -1857,6 +1960,27 @@ off_t find_pack_entry_one(const unsigned char *sha1, return 0; } +static int is_pack_valid(struct packed_git *p) +{ + /* An already open pack is known to be valid. */ + if (p->pack_fd != -1) + return 1; + + /* If the pack has one window completely covering the + * file size, the pack is known to be valid even if + * the descriptor is not currently open. + */ + if (p->windows) { + struct pack_window *w = p->windows; + + if (!w->offset && w->len == p->pack_size) + return 1; + } + + /* Force the pack to open to prove its valid. */ + return !open_packed_git(p); +} + static int find_pack_entry(const unsigned char *sha1, struct pack_entry *e) { static struct packed_git *last_found = (void *)1; @@ -1886,7 +2010,7 @@ static int find_pack_entry(const unsigned char *sha1, struct pack_entry *e) * it may have been deleted since the index * was loaded! */ - if (p->pack_fd == -1 && open_packed_git(p)) { + if (!is_pack_valid(p)) { error("packfile %s cannot be accessed", p->pack_name); goto next; } @@ -1946,9 +2070,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); @@ -1994,41 +2126,6 @@ static void *read_packed_sha1(const unsigned char *sha1, 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) { @@ -2079,36 +2176,48 @@ static void *read_object(const unsigned char *sha1, enum object_type *type, 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,8 +2409,6 @@ 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)) - fd = create_tmpfile(tmpfile, sizeof(tmpfile), filename); if (fd < 0) { if (errno == EACCES) return error("insufficient permission for adding an object to repository database %s\n", get_object_directory()); diff --git a/sha1_name.c b/sha1_name.c index 7b7e61719f..faea58dc8c 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -7,6 +7,8 @@ #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; @@ -560,6 +562,8 @@ static int peel_onion(const char *name, int len, unsigned char *sha1) expected_type = OBJ_BLOB; else if (sp[0] == '}') expected_type = OBJ_NONE; + else if (sp[0] == '/') + expected_type = OBJ_COMMIT; else return -1; @@ -574,19 +578,37 @@ static int peel_onion(const char *name, int len, unsigned char *sha1) 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; } @@ -683,16 +705,15 @@ static int handle_one_ref(const char *path, } 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] == '!') { @@ -704,41 +725,45 @@ static int get_sha1_oneline(const char *prefix, unsigned char *sha1) 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 { @@ -934,6 +959,24 @@ int interpret_branch_name(const char *name, struct strbuf *buf) 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 +1089,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) @@ -1060,15 +1120,21 @@ int get_sha1_with_context_1(const char *name, unsigned char *sha1, 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] == '/') - 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]) @@ -1077,7 +1143,13 @@ int get_sha1_with_context_1(const char *name, unsigned char *sha1, 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)); @@ -1095,12 +1167,15 @@ int get_sha1_with_context_1(const char *name, unsigned char *sha1, break; 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++) { @@ -1121,6 +1196,11 @@ int get_sha1_with_context_1(const char *name, unsigned char *sha1, } 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, @@ -1132,6 +1212,7 @@ int get_sha1_with_context_1(const char *name, unsigned char *sha1, sizeof(oc->path)); oc->path[sizeof(oc->path)-1] = '\0'; + free(new_filename); return ret; } else { if (!gently) @@ -2,6 +2,10 @@ #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) { @@ -33,6 +37,86 @@ static int do_cvs_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; @@ -48,8 +132,10 @@ static struct commands { 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 @@ -66,17 +152,28 @@ int main(int argc, char **argv) /* * 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] = '-'; @@ -99,5 +196,21 @@ int main(int argc, char **argv) } 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)); + } } @@ -63,7 +63,8 @@ void strbuf_attach(struct strbuf *sb, void *buf, size_t len, size_t alloc) 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; @@ -152,7 +153,7 @@ int strbuf_cmp(const struct strbuf *a, const struct strbuf *b) 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"); @@ -386,19 +387,3 @@ int strbuf_read_file(struct strbuf *sb, const char *path, size_t hint) 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); -} @@ -1,42 +1,7 @@ #ifndef STRBUF_H #define STRBUF_H -/* - * Strbuf's can be use in many ways: as a byte array, or to store arbitrary - * long, overflow safe strings. - * - * Strbufs has some invariants that are very important to keep in mind: - * - * 1. the ->buf member is always malloc-ed, hence strbuf's can be used to - * build complex strings/buffers whose final size isn't easily known. - * - * It is NOT legal to copy the ->buf pointer away. - * `strbuf_detach' is the operation that detaches a buffer from its shell - * while keeping the shell valid wrt its invariants. - * - * 2. the ->buf member is a byte array that has at least ->len + 1 bytes - * allocated. The extra byte is used to store a '\0', allowing the ->buf - * member to be a valid C-string. Every strbuf function ensure this - * invariant is preserved. - * - * Note that it is OK to "play" with the buffer directly if you work it - * that way: - * - * strbuf_grow(sb, SOME_SIZE); - * ... Here, the memory array starting at sb->buf, and of length - * ... strbuf_avail(sb) is all yours, and you are sure that - * ... strbuf_avail(sb) is at least SOME_SIZE. - * strbuf_setlen(sb, sb->len + SOME_OTHER_SIZE); - * - * Of course, SOME_OTHER_SIZE must be smaller or equal to strbuf_avail(sb). - * - * Doing so is safe, though if it has to be done in many places, adding the - * missing API to the strbuf module is the way to go. - * - * XXX: do _not_ assume that the area that is yours is of size ->alloc - 1 - * even if it's true in the current implementation. Alloc is somehow a - * "private" member that should not be messed with. - */ +/* See Documentation/technical/api-strbuf.txt */ #include <assert.h> diff --git a/string-list.c b/string-list.c index 9b023a2584..51681189e8 100644 --- 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/string-list.h b/string-list.h index 494693898b..bda6983983 100644 --- a/string-list.h +++ b/string-list.h @@ -5,8 +5,7 @@ struct string_list_item { char *string; void *util; }; -struct string_list -{ +struct string_list { struct string_list_item *items; unsigned int nr, alloc; unsigned int strdup_strings:1; diff --git a/submodule.c b/submodule.c index 91a4758747..e9f2b19e1c 100644 --- a/submodule.c +++ b/submodule.c @@ -10,7 +10,9 @@ #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) { @@ -63,10 +65,14 @@ void set_diffopt_flags_from_submodule_config(struct diff_options *diffopt, } } -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; } @@ -100,6 +106,14 @@ int parse_submodule_config_option(const char *var, const char *value) 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")) { @@ -138,17 +152,69 @@ void handle_ignore_submodules_arg(struct diff_options *diffopt, die("bad --ignore-submodules argument: %s", arg); } +static int prepare_submodule_summary(struct rev_info *rev, const char *path, + struct commit *left, struct commit *right, + int *fast_forward, int *fast_backward) +{ + struct commit_list *merge_bases, *list; + + init_revisions(rev, NULL); + setup_revisions(0, NULL, rev, NULL); + rev->left_right = 1; + rev->first_parent_only = 1; + left->object.flags |= SYMMETRIC_LEFT; + add_pending_object(rev, &left->object, path); + add_pending_object(rev, &right->object, path); + merge_bases = get_merge_bases(left, right, 1); + if (merge_bases) { + if (merge_bases->item == left) + *fast_forward = 1; + else if (merge_bases->item == right) + *fast_backward = 1; + } + for (list = merge_bases; list; list = list->next) { + list->item->object.flags |= UNINTERESTING; + add_pending_object(rev, &list->item->object, + sha1_to_hex(list->item->object.sha1)); + } + return prepare_revision_walk(rev); +} + +static void print_submodule_summary(struct rev_info *rev, FILE *f, + const char *del, const char *add, const char *reset) +{ + static const char format[] = " %m %s"; + struct strbuf sb = STRBUF_INIT; + struct commit *commit; + + while ((commit = get_revision(rev))) { + struct pretty_print_context ctx = {0}; + ctx.date_mode = rev->date_mode; + strbuf_setlen(&sb, 0); + if (commit->object.flags & SYMMETRIC_LEFT) { + if (del) + strbuf_addstr(&sb, del); + } + else if (add) + strbuf_addstr(&sb, add); + format_commit_message(commit, format, &sb, &ctx); + if (reset) + strbuf_addstr(&sb, reset); + strbuf_addch(&sb, '\n'); + fprintf(f, "%s", sb.buf); + } + strbuf_release(&sb); +} + void show_submodule_summary(FILE *f, const char *path, unsigned char one[20], unsigned char two[20], unsigned dirty_submodule, const char *del, const char *add, const char *reset) { struct rev_info rev; - struct commit *commit, *left = left, *right = right; - struct commit_list *merge_bases, *list; + struct commit *left = left, *right = right; const char *message = NULL; struct strbuf sb = STRBUF_INIT; - static const char *format = " %m %s"; int fast_forward = 0, fast_backward = 0; if (is_null_sha1(two)) @@ -161,29 +227,10 @@ void show_submodule_summary(FILE *f, const char *path, !(right = lookup_commit_reference(two))) message = "(commits not present)"; - if (!message) { - init_revisions(&rev, NULL); - setup_revisions(0, NULL, &rev, NULL); - rev.left_right = 1; - rev.first_parent_only = 1; - left->object.flags |= SYMMETRIC_LEFT; - add_pending_object(&rev, &left->object, path); - add_pending_object(&rev, &right->object, path); - merge_bases = get_merge_bases(left, right, 1); - if (merge_bases) { - if (merge_bases->item == left) - fast_forward = 1; - else if (merge_bases->item == right) - fast_backward = 1; - } - for (list = merge_bases; list; list = list->next) { - list->item->object.flags |= UNINTERESTING; - add_pending_object(&rev, &list->item->object, - sha1_to_hex(list->item->object.sha1)); - } - if (prepare_revision_walk(&rev)) - message = "(revision walker failed)"; - } + if (!message && + prepare_submodule_summary(&rev, path, left, right, + &fast_forward, &fast_backward)) + message = "(revision walker failed)"; if (dirty_submodule & DIRTY_SUBMODULE_UNTRACKED) fprintf(f, "Submodule %s contains untracked content\n", path); @@ -207,28 +254,97 @@ void show_submodule_summary(FILE *f, const char *path, fwrite(sb.buf, sb.len, 1, f); if (!message) { - while ((commit = get_revision(&rev))) { - struct pretty_print_context ctx = {0}; - ctx.date_mode = rev.date_mode; - strbuf_setlen(&sb, 0); - if (commit->object.flags & SYMMETRIC_LEFT) { - if (del) - strbuf_addstr(&sb, del); - } - else if (add) - strbuf_addstr(&sb, add); - format_commit_message(commit, format, &sb, &ctx); - if (reset) - strbuf_addstr(&sb, reset); - strbuf_addch(&sb, '\n'); - fprintf(f, "%s", sb.buf); - } + print_submodule_summary(&rev, f, del, add, reset); clear_commit_marks(left, ~0); clear_commit_marks(right, ~0); } + 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 386f410a66..4729023aa5 100644 --- a/submodule.h +++ b/submodule.h @@ -5,6 +5,7 @@ struct diff_options; 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 *); @@ -12,6 +13,10 @@ void show_submodule_summary(FILE *f, const char *path, 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/symlinks.c b/symlinks.c index 8860120011..034943bda0 100644 --- a/symlinks.c +++ b/symlinks.c @@ -64,11 +64,13 @@ static inline void reset_lstat_cache(struct cache_def *cache) * of the prefix, where the cache should use the stat() function * instead of the lstat() function to test each path component. */ -static int lstat_cache(struct cache_def *cache, const char *name, int len, - int track_flags, int prefix_len_stat_func) +static int lstat_cache_matchlen(struct cache_def *cache, + const char *name, int len, + int *ret_flags, int track_flags, + int prefix_len_stat_func) { int match_len, last_slash, last_slash_dir, previous_slash; - int match_flags, ret_flags, save_flags, max_len, ret; + int save_flags, max_len, ret; struct stat st; if (cache->track_flags != track_flags || @@ -90,13 +92,13 @@ static int lstat_cache(struct cache_def *cache, const char *name, int len, match_len = last_slash = longest_path_match(name, len, cache->path, cache->len, &previous_slash); - match_flags = cache->flags & track_flags & (FL_NOENT|FL_SYMLINK); + *ret_flags = cache->flags & track_flags & (FL_NOENT|FL_SYMLINK); if (!(track_flags & FL_FULLPATH) && match_len == len) match_len = last_slash = previous_slash; - if (match_flags && match_len == cache->len) - return match_flags; + if (*ret_flags && match_len == cache->len) + return match_len; /* * If we now have match_len > 0, we would know that * the matched part will always be a directory. @@ -105,16 +107,16 @@ static int lstat_cache(struct cache_def *cache, const char *name, int len, * a substring of the cache on a path component basis, * we can return immediately. */ - match_flags = track_flags & FL_DIR; - if (match_flags && len == match_len) - return match_flags; + *ret_flags = track_flags & FL_DIR; + if (*ret_flags && len == match_len) + return match_len; } /* * Okay, no match from the cache so far, so now we have to * check the rest of the path components. */ - ret_flags = FL_DIR; + *ret_flags = FL_DIR; last_slash_dir = last_slash; max_len = len < PATH_MAX ? len : PATH_MAX; while (match_len < max_len) { @@ -133,16 +135,16 @@ static int lstat_cache(struct cache_def *cache, const char *name, int len, ret = lstat(cache->path, &st); if (ret) { - ret_flags = FL_LSTATERR; + *ret_flags = FL_LSTATERR; if (errno == ENOENT) - ret_flags |= FL_NOENT; + *ret_flags |= FL_NOENT; } else if (S_ISDIR(st.st_mode)) { last_slash_dir = last_slash; continue; } else if (S_ISLNK(st.st_mode)) { - ret_flags = FL_SYMLINK; + *ret_flags = FL_SYMLINK; } else { - ret_flags = FL_ERR; + *ret_flags = FL_ERR; } break; } @@ -152,7 +154,7 @@ static int lstat_cache(struct cache_def *cache, const char *name, int len, * path types, FL_NOENT, FL_SYMLINK and FL_DIR, can be cached * for the moment! */ - save_flags = ret_flags & track_flags & (FL_NOENT|FL_SYMLINK); + save_flags = *ret_flags & track_flags & (FL_NOENT|FL_SYMLINK); if (save_flags && last_slash > 0 && last_slash <= PATH_MAX) { cache->path[last_slash] = '\0'; cache->len = last_slash; @@ -176,7 +178,16 @@ static int lstat_cache(struct cache_def *cache, const char *name, int len, } else { reset_lstat_cache(cache); } - return ret_flags; + return match_len; +} + +static int lstat_cache(struct cache_def *cache, const char *name, int len, + int track_flags, int prefix_len_stat_func) +{ + int flags; + (void)lstat_cache_matchlen(cache, name, len, &flags, track_flags, + prefix_len_stat_func); + return flags; } #define USE_ONLY_LSTAT 0 @@ -198,15 +209,26 @@ int has_symlink_leading_path(const char *name, int len) } /* - * Return non-zero if path 'name' has a leading symlink component or + * Return zero if path 'name' has a leading symlink component or * if some leading path component does not exists. + * + * Return -1 if leading path exists and is a directory. + * + * Return path length if leading path exists and is neither a + * directory nor a symlink. */ -int has_symlink_or_noent_leading_path(const char *name, int len) +int check_leading_path(const char *name, int len) { struct cache_def *cache = &default_cache; /* FIXME */ - return lstat_cache(cache, name, len, - FL_SYMLINK|FL_NOENT|FL_DIR, USE_ONLY_LSTAT) & - (FL_SYMLINK|FL_NOENT); + int flags; + int match_len = lstat_cache_matchlen(cache, name, len, &flags, + FL_SYMLINK|FL_NOENT|FL_DIR, USE_ONLY_LSTAT); + if (flags & FL_NOENT) + return 0; + else if (flags & FL_DIR) + return -1; + else + return match_len; } /* diff --git a/t/Makefile b/t/Makefile index c7baefb7ea..47cbeb6e68 100644 --- a/t/Makefile +++ b/t/Makefile @@ -11,16 +11,25 @@ SHELL_PATH ?= $(SHELL) 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) @@ -32,6 +41,18 @@ clean: $(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 @@ -46,6 +67,9 @@ full-svn-test: $(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) @@ -50,6 +50,12 @@ prove and other harnesses come with a lot of useful options. The # 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 @@ -92,6 +98,13 @@ appropriately before running "make". not see any output, this option implies --verbose. For convenience, it also implies --tee. + Note that valgrind is run with the option --leak-check=no, + as the git process is short-lived and some errors are not + interesting. In order to run a single command under the same + conditions manually, you should set GIT_VALGRIND to point to + the 't/valgrind/' directory and use the commands under + 't/valgrind/bin/'. + --tee:: In addition to printing the test output to the terminal, write it to files named 't/test-results/$TEST_NAME.out'. @@ -184,7 +197,7 @@ we are testing. If you create files under t/ directory (i.e. here) that is not the top-level test script, never name the file to match the above pattern. The Makefile here considers all such files as the -top-level test script and tries to run all of them. A care is +top-level test script and tries to run all of them. Care is especially needed if you are creating a common test library file, similar to test-lib.sh, because such a library file may not be suitable for standalone execution. @@ -259,27 +272,29 @@ Do: 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. - Don't blindly follow test coverage metrics, they're a good way to - spot if you've missed something. If a new function you added - doesn't have any coverage you're probably doing something wrong, + Don't blindly follow test coverage metrics; if a new function you added + doesn't have any coverage, then you're probably doing something wrong, but having 100% coverage doesn't necessarily mean that you tested everything. 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. @@ -319,7 +334,7 @@ Keep in mind: Skipping tests -------------- -If you need to skip tests you should do so be using the three-arg form +If you need to skip tests you should do so by using the three-arg form of the test_* functions (see the "Test harness library" section below), e.g.: @@ -395,13 +410,6 @@ library for your script to use. 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 @@ -418,7 +426,7 @@ library for your script to use. - test_tick Make commit and tag names consistent by setting the author and - committer times to defined stated. Subsequent calls will + committer times to defined state. Subsequent calls will advance the times by a fixed amount. - test_commit <message> [<filename> [<contents>]] @@ -482,6 +490,15 @@ library for your script to use. '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 @@ -501,6 +518,10 @@ library for your script to 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 396b9653a3..d34208cc27 100644 --- a/t/annotate-tests.sh +++ b/t/annotate-tests.sh @@ -8,27 +8,27 @@ check_count () { $PROG file $head >.result || return 1 cat .result | perl -e ' my %expect = (@ARGV); - my %count = (); + my %count = map { $_ => 0 } keys %expect; while (<STDIN>) { if (/^[0-9a-f]+\t\(([^\t]+)\t/) { my $author = $1; for ($author) { s/^\s*//; s/\s*$//; } - if (exists $expect{$author}) { - $count{$author}++; - } + $count{$author}++; } } my $bad = 0; while (my ($author, $count) = each %count) { my $ok; - if ($expect{$author} != $count) { + my $value = 0; + $value = $expect{$author} if defined $expect{$author}; + if ($value != $count) { $bad = 1; $ok = "bad"; } else { $ok = "good"; } - print STDERR "Author $author (expected $expect{$author}, attributed $count) $ok\n"; + print STDERR "Author $author (expected $value, attributed $count) $ok\n"; } exit($bad); ' "$@" @@ -38,8 +38,8 @@ test_expect_success \ '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' \ @@ -49,7 +49,7 @@ test_expect_success \ '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' \ @@ -60,7 +60,7 @@ test_expect_success \ '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' \ @@ -71,7 +71,7 @@ test_expect_success \ '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' \ @@ -105,7 +105,7 @@ test_expect_success \ 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.' \ @@ -119,7 +119,7 @@ test_expect_success \ 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 81ef2a0969..143eb1f240 100644 --- a/t/gitweb-lib.sh +++ b/t/gitweb-lib.sh @@ -19,9 +19,9 @@ our \$site_name = '[localhost]'; 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 = ''; @@ -32,17 +32,34 @@ EOF 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 @@ -65,7 +82,12 @@ gitweb_run () { } 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 @@ -80,9 +102,9 @@ if ! test_have_prereq PERL; then test_done fi -perl -MEncode -e 'decode_utf8("", Encode::FB_CROAK)' >/dev/null 2>&1 || { - skip_all='skipping gitweb tests, perl version is too old' - test_done +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 } gitweb_init diff --git a/t/lib-git-svn.sh b/t/lib-git-svn.sh index 92d6d31942..199f22c231 100644 --- a/t/lib-git-svn.sh +++ b/t/lib-git-svn.sh @@ -68,41 +68,46 @@ svn_cmd () { svn "$orig_svncmd" --config-dir "$svnconf" "$@" } -for d in \ - "$SVN_HTTPD_PATH" \ - /usr/sbin/apache2 \ - /usr/sbin/httpd \ -; do - if test -f "$d" +prepare_httpd () { + for d in \ + "$SVN_HTTPD_PATH" \ + /usr/sbin/apache2 \ + /usr/sbin/httpd \ + ; do + if test -f "$d" + then + SVN_HTTPD_PATH="$d" + break + fi + done + if test -z "$SVN_HTTPD_PATH" then - SVN_HTTPD_PATH="$d" - break + echo >&2 '*** error: Apache not found' + return 1 fi -done -for d in \ - "$SVN_HTTPD_MODULE_PATH" \ - /usr/lib/apache2/modules \ - /usr/libexec/apache2 \ -; do - if test -d "$d" + for d in \ + "$SVN_HTTPD_MODULE_PATH" \ + /usr/lib/apache2/modules \ + /usr/libexec/apache2 \ + ; do + if test -d "$d" + then + SVN_HTTPD_MODULE_PATH="$d" + break + fi + done + if test -z "$SVN_HTTPD_MODULE_PATH" then - SVN_HTTPD_MODULE_PATH="$d" - break + echo >&2 '*** error: Apache module dir not found' + return 1 fi -done - -start_httpd () { - repo_base_path="$1" - if test -z "$SVN_HTTPD_PORT" + if test ! -f "$SVN_HTTPD_MODULE_PATH/mod_dav_svn.so" then - echo >&2 'SVN_HTTPD_PORT is not defined!' - return - fi - if test -z "$repo_base_path" - 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 @@ -119,12 +124,24 @@ LoadModule dav_svn_module $SVN_HTTPD_MODULE_PATH/mod_dav_svn.so 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-httpd.sh b/t/lib-httpd.sh index e733f6516f..3f24384371 100644 --- a/t/lib-httpd.sh +++ b/t/lib-httpd.sh @@ -75,12 +75,14 @@ fi prepare_httpd() { mkdir -p "$HTTPD_DOCUMENT_ROOT_PATH" + cp "$TEST_PATH"/passwd "$HTTPD_ROOT_PATH" ln -s "$LIB_HTTPD_MODULE_PATH" "$HTTPD_ROOT_PATH/modules" if test -n "$LIB_HTTPD_SSL" then HTTPD_URL=https://127.0.0.1:$LIB_HTTPD_PORT + AUTH_HTTPD_URL=https://user%40host:user%40host@127.0.0.1:$LIB_HTTPD_PORT RANDFILE_PATH="$HTTPD_ROOT_PATH"/.rnd openssl req \ -config "$TEST_PATH/ssl.cnf" \ @@ -92,6 +94,7 @@ prepare_httpd() { HTTPD_PARA="$HTTPD_PARA -DSSL" else HTTPD_URL=http://127.0.0.1:$LIB_HTTPD_PORT + AUTH_HTTPD_URL=http://user%40host:user%40host@127.0.0.1:$LIB_HTTPD_PORT fi if test -n "$LIB_HTTPD_DAV" -o -n "$LIB_HTTPD_SVN" diff --git a/t/lib-httpd/apache.conf b/t/lib-httpd/apache.conf index 4961505d1d..0a4cdfa93e 100644 --- a/t/lib-httpd/apache.conf +++ b/t/lib-httpd/apache.conf @@ -17,8 +17,33 @@ ErrorLog error.log <IfModule !mod_env.c> LoadModule env_module modules/mod_env.so </IfModule> +<IfModule !mod_rewrite.c> + LoadModule rewrite_module modules/mod_rewrite.so +</IFModule> +<IfModule !mod_version.c> + LoadModule version_module modules/mod_version.so +</IfModule> + +<IfVersion < 2.1> +<IfModule !mod_auth.c> + LoadModule auth_module modules/mod_auth.so +</IfModule> +</IfVersion> + +<IfVersion >= 2.1> +<IfModule !mod_auth_basic.c> + LoadModule auth_basic_module modules/mod_auth_basic.so +</IfModule> +<IfModule !mod_authn_file.c> + LoadModule authn_file_module modules/mod_authn_file.so +</IfModule> +<IfModule !mod_authz_user.c> + LoadModule authz_user_module modules/mod_authz_user.so +</IfModule> +</IfVersion> Alias /dumb/ www/ +Alias /auth/ www/auth/ <Location /smart/> SetEnv GIT_EXEC_PATH ${GIT_EXEC_PATH} @@ -36,6 +61,10 @@ ScriptAlias /smart_noexport/ ${GIT_EXEC_PATH}/git-http-backend/ Options ExecCGI </Files> +RewriteEngine on +RewriteRule ^/smart-redir-perm/(.*)$ /smart/$1 [R=301] +RewriteRule ^/smart-redir-temp/(.*)$ /smart/$1 [R=302] + <IfDefine SSL> LoadModule ssl_module modules/mod_ssl.so @@ -48,6 +77,13 @@ SSLMutex file:ssl_mutex SSLEngine On </IfDefine> +<Location /auth/> + AuthType Basic + AuthName "git-auth" + AuthUserFile passwd + Require valid-user +</Location> + <IfDefine DAV> LoadModule dav_module modules/mod_dav.so LoadModule dav_fs_module modules/mod_dav_fs.so diff --git a/t/lib-httpd/passwd b/t/lib-httpd/passwd new file mode 100644 index 0000000000..f2fbcad33e --- /dev/null +++ b/t/lib-httpd/passwd @@ -0,0 +1 @@ +user@host:nKpa8pZUHx/ic diff --git a/t/lib-terminal.sh b/t/lib-terminal.sh new file mode 100644 index 0000000000..58d911d21b --- /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 f688bd3ef5..8deec75c3a 100755 --- a/t/t0000-basic.sh +++ b/t/t0000-basic.sh @@ -80,11 +80,11 @@ EOF 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) " @@ -130,22 +130,57 @@ test_expect_success 'tests clean up after themselves' ' 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 7fe8883ae0..f684993211 100755 --- a/t/t0001-init.sh +++ b/t/t0001-init.sh @@ -25,7 +25,7 @@ check_config () { test_expect_success 'plain' ' ( - unset GIT_DIR GIT_WORK_TREE + sane_unset GIT_DIR GIT_WORK_TREE && mkdir plain && cd plain && git init @@ -33,9 +33,65 @@ test_expect_success 'plain' ' 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 @@ -48,7 +104,7 @@ test_expect_success 'plain with GIT_WORK_TREE' ' 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 @@ -58,7 +114,7 @@ test_expect_success 'plain bare' ' 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 @@ -72,7 +128,7 @@ test_expect_success 'plain bare with GIT_WORK_TREE' ' 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 ) && @@ -82,7 +138,7 @@ test_expect_success 'GIT_DIR bare' ' 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 @@ -93,7 +149,7 @@ test_expect_success '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 @@ -104,7 +160,7 @@ test_expect_success 'GIT_DIR non-bare' ' 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 ) && @@ -114,7 +170,7 @@ test_expect_success 'GIT_DIR & GIT_WORK_TREE (1)' ' 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 ) @@ -127,7 +183,7 @@ test_expect_success 'GIT_DIR & GIT_WORK_TREE (2)' ' 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 && @@ -175,8 +231,8 @@ test_expect_success 'init with init.templatedir set' ' 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 @@ -187,7 +243,7 @@ test_expect_success 'init with init.templatedir set' ' 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 && @@ -202,7 +258,7 @@ test_expect_success 'init --bare/--shared overrides system/global config' ' 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 de38c7f7aa..ebbc7554a7 100755 --- a/t/t0003-attributes.sh +++ b/t/t0003-attributes.sh @@ -36,6 +36,9 @@ test_expect_success 'setup' ' echo "d/* test=a/b/d/*" echo "d/yes notest" ) >a/b/.gitattributes + ( + echo "global test=global" + ) >"$HOME"/global-gitattributes ' @@ -57,9 +60,19 @@ test_expect_success 'attribute test' ' ' +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/t0004-unwritable.sh b/t/t0004-unwritable.sh index 385b1265de..e3137d638e 100755 --- a/t/t0004-unwritable.sh +++ b/t/t0004-unwritable.sh @@ -16,53 +16,29 @@ test_expect_success setup ' ' test_expect_success POSIXPERM,SANITY 'write-tree should notice unwritable repository' ' - - ( - chmod a-w .git/objects .git/objects/?? && - test_must_fail git write-tree - ) - status=$? - chmod 775 .git/objects .git/objects/?? - (exit $status) - + test_when_finished "chmod 775 .git/objects .git/objects/??" && + chmod a-w .git/objects .git/objects/?? && + test_must_fail git write-tree ' test_expect_success POSIXPERM,SANITY 'commit should notice unwritable repository' ' - - ( - chmod a-w .git/objects .git/objects/?? && - test_must_fail git commit -m second - ) - status=$? - chmod 775 .git/objects .git/objects/?? - (exit $status) - + test_when_finished "chmod 775 .git/objects .git/objects/??" && + chmod a-w .git/objects .git/objects/?? && + test_must_fail git commit -m second ' test_expect_success POSIXPERM,SANITY 'update-index should notice unwritable repository' ' - - ( - echo 6O >file && - chmod a-w .git/objects .git/objects/?? && - test_must_fail git update-index file - ) - status=$? - chmod 775 .git/objects .git/objects/?? - (exit $status) - + test_when_finished "chmod 775 .git/objects .git/objects/??" && + echo 6O >file && + chmod a-w .git/objects .git/objects/?? && + test_must_fail git update-index file ' test_expect_success POSIXPERM,SANITY 'add should notice unwritable repository' ' - - ( - echo b >file && - chmod a-w .git/objects .git/objects/?? && - test_must_fail git add file - ) - status=$? - chmod 775 .git/objects .git/objects/?? - (exit $status) - + test_when_finished "chmod 775 .git/objects .git/objects/??" && + echo b >file && + chmod a-w .git/objects .git/objects/?? && + test_must_fail git add file ' test_done diff --git a/t/t0020-crlf.sh b/t/t0020-crlf.sh index 234a94f3e6..1a8f44c44c 100755 --- a/t/t0020-crlf.sh +++ b/t/t0020-crlf.sh @@ -439,7 +439,7 @@ test_expect_success 'checkout when deleting .gitattributes' ' 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 828e35baf7..9078b84ae6 100755 --- a/t/t0021-conversion.sh +++ b/t/t0021-conversion.sh @@ -93,4 +93,47 @@ test_expect_success expanded_in_repo ' 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 diff --git a/t/t0024-crlf-archive.sh b/t/t0024-crlf-archive.sh index c7d0324374..ec6c1b3f8a 100755 --- a/t/t0024-crlf-archive.sh +++ b/t/t0024-crlf-archive.sh @@ -7,7 +7,7 @@ UNZIP=${UNZIP:-unzip} 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 && @@ -20,7 +20,7 @@ test_expect_success setup ' 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 f37ac8fa0b..fe0164be62 100755 --- a/t/t0026-eol-config.sh +++ b/t/t0026-eol-config.sh @@ -12,7 +12,7 @@ test_expect_success setup ' 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 && diff --git a/t/t0040-parse-options.sh b/t/t0040-parse-options.sh index 20924506af..ae266147b6 100755 --- a/t/t0040-parse-options.sh +++ b/t/t0040-parse-options.sh @@ -19,7 +19,7 @@ usage: test-parse-options <options> --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 41df6bcf27..1542cf6a13 100755 --- a/t/t0050-filesystem.sh +++ b/t/t0050-filesystem.sh @@ -4,22 +4,22 @@ test_description='Various filesystem issues' . ./test-lib.sh -auml=`printf '\xc3\xa4'` -aumlcdiar=`printf '\x61\xcc\x88'` +auml=$(printf '\303\244') +aumlcdiar=$(printf '\141\314\210') case_insensitive= unibad= 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 && @@ -27,7 +27,7 @@ test_expect_success 'see what we expect' ' >junk/"$auml" && case "$(cd junk && echo *)" in "$aumlcdiar") - test_unicode=test_expect_failure + test_unicode=test_expect_failure && unibad=t ;; *) ;; @@ -36,7 +36,7 @@ test_expect_success 'see what we expect' ' { ln -s x y 2> /dev/null && test -h y 2> /dev/null || - no_symlinks=1 + no_symlinks=1 && rm -f y } ' @@ -128,7 +128,7 @@ test_expect_success "setup unicode normalization tests" ' 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 680d7d6861..9bee8bfd2e 100755 --- a/t/t0070-fundamental.sh +++ b/t/t0070-fundamental.sh @@ -12,4 +12,17 @@ test_expect_success 'character classes (isspace, isalpha etc.)' ' 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/t1000-read-tree-m-3way.sh b/t/t1000-read-tree-m-3way.sh index 4f171722d9..ca8a4098fa 100755 --- a/t/t1000-read-tree-m-3way.sh +++ b/t/t1000-read-tree-m-3way.sh @@ -309,7 +309,7 @@ test_expect_success \ 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 " diff --git a/t/t1001-read-tree-m-2way.sh b/t/t1001-read-tree-m-2way.sh index 93ca84f9e6..680d992f22 100755 --- a/t/t1001-read-tree-m-2way.sh +++ b/t/t1001-read-tree-m-2way.sh @@ -98,8 +98,8 @@ test_expect_success \ 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' @@ -112,8 +112,8 @@ test_expect_success \ 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' @@ -213,8 +213,8 @@ test_expect_success \ 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' @@ -227,8 +227,8 @@ test_expect_success \ 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' @@ -377,7 +377,7 @@ test_expect_success \ 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` && @@ -394,7 +394,7 @@ test_expect_success '-m references the correct modified 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" && diff --git a/t/t1002-read-tree-m-u-2way.sh b/t/t1002-read-tree-m-u-2way.sh index 0241329a08..a4a17e0017 100755 --- a/t/t1002-read-tree-m-u-2way.sh +++ b/t/t1002-read-tree-m-u-2way.sh @@ -205,8 +205,8 @@ test_expect_success \ 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 && @@ -226,8 +226,8 @@ test_expect_success \ 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 && @@ -314,7 +314,7 @@ test_expect_success \ # 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/t1011-read-tree-sparse-checkout.sh b/t/t1011-read-tree-sparse-checkout.sh index 9a07de1a5b..de84e35c43 100755 --- a/t/t1011-read-tree-sparse-checkout.sh +++ b/t/t1011-read-tree-sparse-checkout.sh @@ -17,17 +17,19 @@ test_expect_success 'setup' ' cat >expected <<-\EOF && 100644 77f0ba1734ed79d12881f81b36ee134de6a3327b 0 init.t 100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 sub/added + 100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 subsub/added EOF cat >expected.swt <<-\EOF && H init.t H sub/added + H subsub/added EOF test_commit init && echo modified >>init.t && - mkdir sub && - touch sub/added && - git add init.t sub/added && + mkdir sub subsub && + touch sub/added subsub/added && + git add init.t sub/added subsub/added && git commit -m "modified and added" && git tag top && git rm sub/added && @@ -47,7 +49,7 @@ test_expect_success 'read-tree without .git/info/sparse-checkout' ' ' 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 && @@ -81,6 +83,7 @@ test_expect_success 'match directories with trailing slash' ' cat >expected.swt-noinit <<-\EOF && S init.t H sub/added + S subsub/added EOF echo sub/ > .git/info/sparse-checkout && @@ -91,12 +94,20 @@ test_expect_success 'match directories with trailing slash' ' 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 ' @@ -105,6 +116,7 @@ test_expect_success 'checkout area changes' ' cat >expected.swt-nosub <<-\EOF && H init.t S sub/added + S subsub/added EOF echo init.t >.git/info/sparse-checkout && diff --git a/t/t1020-subdirectory.sh b/t/t1020-subdirectory.sh index a3ac33801a..1fd187c5eb 100755 --- a/t/t1020-subdirectory.sh +++ b/t/t1020-subdirectory.sh @@ -110,6 +110,14 @@ test_expect_success 'read-tree' ' ) ' +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 ab55eda158..bfa2c2190d 100755 --- a/t/t1200-tutorial.sh +++ b/t/t1200-tutorial.sh @@ -42,7 +42,7 @@ test_expect_success 'git diff' ' ' 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 074f2f2e3e..53fb8228cf 100755 --- a/t/t1300-repo-config.sh +++ b/t/t1300-repo-config.sh @@ -289,6 +289,14 @@ 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 EOF @@ -701,13 +709,13 @@ cat >expect <<\EOF 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 @@ -730,7 +738,7 @@ cat >expect <<\EOF 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 \ @@ -836,6 +844,27 @@ test_expect_success SYMLINKS 'symlinked configuration' ' ' +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 && @@ -847,11 +876,25 @@ test_expect_success 'check split_cmdline return' " " 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 diff --git a/t/t1302-repo-version.sh b/t/t1302-repo-version.sh index a6bf1bf4d6..0e47662406 100755 --- a/t/t1302-repo-version.sh +++ b/t/t1302-repo-version.sh @@ -39,7 +39,7 @@ test_expect_success 'gitdir selection on unsupported repo' ' ( 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 54ba3df95f..ff747f8229 100755 --- a/t/t1400-update-ref.sh +++ b/t/t1400-update-ref.sh @@ -52,9 +52,8 @@ rm -f .git/$m 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 \ @@ -185,55 +184,55 @@ gd="Thu, 26 May 2005 18:33:00 -0500" 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)"' @@ -247,7 +246,7 @@ test_expect_success \ 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 && diff --git a/t/t1401-symbolic-ref.sh b/t/t1401-symbolic-ref.sh index 7fa5f5b22a..2c96551ed0 100755 --- a/t/t1401-symbolic-ref.sh +++ b/t/t1401-symbolic-ref.sh @@ -28,7 +28,7 @@ test_expect_success 'symbolic-ref refuses non-ref for HEAD' ' 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 diff --git a/t/t1402-check-ref-format.sh b/t/t1402-check-ref-format.sh index 782e75d000..1b0f82fa4c 100755 --- a/t/t1402-check-ref-format.sh +++ b/t/t1402-check-ref-format.sh @@ -32,7 +32,7 @@ test_expect_success "check-ref-format --branch @{-1}" ' 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 && @@ -47,7 +47,7 @@ test_expect_success 'check-ref-format --branch from subdir' ' 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 25046c4208..252fc82837 100755 --- a/t/t1410-reflog.sh +++ b/t/t1410-reflog.sh @@ -186,8 +186,8 @@ test_expect_success 'delete' ' 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 && @@ -199,13 +199,13 @@ test_expect_success 'delete' ' 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/t1412-reflog-loop.sh b/t/t1412-reflog-loop.sh new file mode 100755 index 0000000000..7f519e5ebe --- /dev/null +++ b/t/t1412-reflog-loop.sh @@ -0,0 +1,34 @@ +#!/bin/sh + +test_description='reflog walk shows repeated commits again' +. ./test-lib.sh + +test_expect_success 'setup commits' ' + test_tick && + echo content >file && git add file && git commit -m one && + git tag one && + echo content >>file && git add file && git commit -m two && + git tag two +' + +test_expect_success 'setup reflog with alternating commits' ' + git checkout -b topic && + git reset one && + git reset two && + git reset one && + git reset two +' + +test_expect_success 'reflog shows all entries' ' + cat >expect <<-\EOF + topic@{0} two: updating HEAD + topic@{1} one: updating HEAD + topic@{2} two: updating HEAD + topic@{3} one: updating HEAD + topic@{4} branch: Created from HEAD + EOF + git log -g --format="%gd %gs" topic >actual && + test_cmp expect actual +' + +test_done diff --git a/t/t1450-fsck.sh b/t/t1450-fsck.sh index 1be415e334..bb01d5ab8f 100755 --- a/t/t1450-fsck.sh +++ b/t/t1450-fsck.sh @@ -61,7 +61,7 @@ test_expect_success 'object with bad sha1' ' 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 && @@ -111,7 +111,7 @@ test_expect_success 'email with embedded > is not okay' ' ' 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 2c8f01f668..da6252b117 100755 --- a/t/t1501-worktree.sh +++ b/t/t1501-worktree.sh @@ -340,4 +340,11 @@ test_expect_success 'make_relative_path handles double slashes in GIT_DIR' ' 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 diff --git a/t/t1502-rev-parse-parseopt.sh b/t/t1502-rev-parse-parseopt.sh index b3195c4707..1efd7f76dd 100755 --- a/t/t1502-rev-parse-parseopt.sh +++ b/t/t1502-rev-parse-parseopt.sh @@ -40,7 +40,7 @@ extra1 line above used to cause a segfault but no longer does 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 ' diff --git a/t/t1503-rev-parse-verify.sh b/t/t1503-rev-parse-verify.sh index 100f857b16..813cc1b3e2 100755 --- a/t/t1503-rev-parse-verify.sh +++ b/t/t1503-rev-parse-verify.sh @@ -106,8 +106,8 @@ test_expect_success 'use --default' ' test_expect_success 'master@{n} for various n' ' N=$(git reflog | wc -l) && - Nm1=$((N-1)) && - Np1=$((N+1)) && + Nm1=$(($N-1)) && + Np1=$(($N+1)) && git rev-parse --verify master@{0} && git rev-parse --verify master@{1} && git rev-parse --verify master@{$Nm1} && diff --git a/t/t1504-ceiling-dirs.sh b/t/t1504-ceiling-dirs.sh index df5ad8c686..cce87a5ab5 100755 --- a/t/t1504-ceiling-dirs.sh +++ b/t/t1504-ceiling-dirs.sh @@ -9,8 +9,9 @@ test_prefix() { } 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" diff --git a/t/t1506-rev-parse-diagnosis.sh b/t/t1506-rev-parse-diagnosis.sh index 0eeeb0e450..9f8adb1f82 100755 --- a/t/t1506-rev-parse-diagnosis.sh +++ b/t/t1506-rev-parse-diagnosis.sh @@ -31,6 +31,67 @@ test_expect_success 'correct file objects' ' 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 && @@ -75,4 +136,29 @@ test_expect_success 'invalid @{n} reference' ' 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 diff --git a/t/t1507-rev-parse-upstream.sh b/t/t1507-rev-parse-upstream.sh index 8c8dfdaf9f..a4555510c3 100755 --- a/t/t1507-rev-parse-upstream.sh +++ b/t/t1507-rev-parse-upstream.sh @@ -85,7 +85,7 @@ test_expect_success 'merge my-side@{u} records the correct name' ' 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/t1509-root-worktree.sh b/t/t1509-root-worktree.sh index 7f60fd0b2e..335420fd87 100755 --- a/t/t1509-root-worktree.sh +++ b/t/t1509-root-worktree.sh @@ -134,8 +134,8 @@ cat >ls.expected <<EOF 100644 $ONE_SHA1 0 me EOF -export GIT_DIR="$TRASH_DIRECTORY/.git" -export GIT_WORK_TREE=/ +GIT_DIR="$TRASH_DIRECTORY/.git" && export GIT_DIR +GIT_WORK_TREE=/ && export GIT_WORK_TREE test_vars 'abs gitdir, root' "$GIT_DIR" "/" "" test_foobar_root @@ -154,24 +154,24 @@ say "GIT_DIR relative, GIT_WORK_TREE set" test_expect_success 'go to /' 'cd /' -export GIT_DIR="$(echo $TRASH_DIRECTORY|sed 's,^/,,')/.git" -export GIT_WORK_TREE=/ +GIT_DIR="$(echo $TRASH_DIRECTORY|sed 's,^/,,')/.git" && export GIT_DIR +GIT_WORK_TREE=/ && export GIT_WORK_TREE test_vars 'rel gitdir, root' "$GIT_DIR" "/" "" test_foobar_root test_expect_success 'go to /foo' 'cd /foo' -export GIT_DIR="../$TRASH_DIRECTORY/.git" -export GIT_WORK_TREE=/ +GIT_DIR="../$TRASH_DIRECTORY/.git" && export GIT_DIR +GIT_WORK_TREE=/ && export GIT_WORK_TREE test_vars 'rel gitdir, foo' "$TRASH_DIRECTORY/.git" "/" "foo/" test_foobar_foo test_expect_success 'go to /foo/bar' 'cd /foo/bar' -export GIT_DIR="../../$TRASH_DIRECTORY/.git" -export GIT_WORK_TREE=/ +GIT_DIR="../../$TRASH_DIRECTORY/.git" && export GIT_DIR +GIT_WORK_TREE=/ && export GIT_WORK_TREE test_vars 'rel gitdir, foo/bar' "$TRASH_DIRECTORY/.git" "/" "foo/bar/" test_foobar_foobar @@ -180,24 +180,24 @@ say "GIT_DIR relative, GIT_WORK_TREE relative" test_expect_success 'go to /' 'cd /' -export GIT_DIR="$(echo $TRASH_DIRECTORY|sed 's,^/,,')/.git" -export GIT_WORK_TREE=. +GIT_DIR="$(echo $TRASH_DIRECTORY|sed 's,^/,,')/.git" && export GIT_DIR +GIT_WORK_TREE=. && export GIT_WORK_TREE test_vars 'rel gitdir, root' "$GIT_DIR" "/" "" test_foobar_root test_expect_success 'go to /' 'cd /foo' -export GIT_DIR="../$TRASH_DIRECTORY/.git" -export GIT_WORK_TREE=.. +GIT_DIR="../$TRASH_DIRECTORY/.git" && export GIT_DIR +GIT_WORK_TREE=.. && export GIT_WORK_TREE test_vars 'rel gitdir, foo' "$TRASH_DIRECTORY/.git" "/" "foo/" test_foobar_foo test_expect_success 'go to /foo/bar' 'cd /foo/bar' -export GIT_DIR="../../$TRASH_DIRECTORY/.git" -export GIT_WORK_TREE=../.. +GIT_DIR="../../$TRASH_DIRECTORY/.git" && export GIT_DIR +GIT_WORK_TREE=../.. && export GIT_WORK_TREE test_vars 'rel gitdir, foo/bar' "$TRASH_DIRECTORY/.git" "/" "foo/bar/" test_foobar_foobar diff --git a/t/t1510-repo-setup.sh b/t/t1510-repo-setup.sh new file mode 100755 index 0000000000..15101d5e03 --- /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 new file mode 100755 index 0000000000..e043cb7c64 --- /dev/null +++ b/t/t1511-rev-parse-caret.sh @@ -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 new file mode 100755 index 0000000000..b8559838b1 --- /dev/null +++ b/t/t2006-checkout-index-basic.sh @@ -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 diff --git a/t/t2007-checkout-symlink.sh b/t/t2007-checkout-symlink.sh index a74ee227b8..e6f59f1914 100755 --- a/t/t2007-checkout-symlink.sh +++ b/t/t2007-checkout-symlink.sh @@ -17,7 +17,7 @@ test_expect_success SYMLINKS setup ' git branch side && echo goodbye >nitfol && - git add nitfol + git add nitfol && test_tick && git commit -m "master adds file nitfol" && diff --git a/t/t2016-checkout-patch.sh b/t/t2016-checkout-patch.sh index a463b13b27..9cd0ac4ba3 100755 --- a/t/t2016-checkout-patch.sh +++ b/t/t2016-checkout-patch.sh @@ -32,7 +32,7 @@ test_expect_success PERL 'git checkout -p' ' ' 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 diff --git a/t/t2017-checkout-orphan.sh b/t/t2017-checkout-orphan.sh index 2d2f63f22e..0e3b8582f2 100755 --- a/t/t2017-checkout-orphan.sh +++ b/t/t2017-checkout-orphan.sh @@ -14,7 +14,7 @@ TEST_FILE=foo 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 new file mode 100755 index 0000000000..943541d40d --- /dev/null +++ b/t/t2019-checkout-ambiguous-ref.sh @@ -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/t2021-checkout-overwrite.sh b/t/t2021-checkout-overwrite.sh new file mode 100755 index 0000000000..27db2ad529 --- /dev/null +++ b/t/t2021-checkout-overwrite.sh @@ -0,0 +1,50 @@ +#!/bin/sh + +test_description='checkout must not overwrite an untracked objects' +. ./test-lib.sh + +test_expect_success 'setup' ' + + mkdir -p a/b/c && + >a/b/c/d && + git add -A && + git commit -m base && + git tag start +' + +test_expect_success 'create a commit where dir a/b changed to file' ' + + git checkout -b file && + rm -rf a/b && + >a/b && + git add -A && + git commit -m "dir to file" +' + +test_expect_success 'checkout commit with dir must not remove untracked a/b' ' + + git rm --cached a/b && + git commit -m "un-track the file" && + test_must_fail git checkout start && + test -f a/b +' + +test_expect_success SYMLINKS 'create a commit where dir a/b changed to symlink' ' + + rm -rf a/b && # cleanup if previous test failed + git checkout -f -b symlink start && + rm -rf a/b && + ln -s foo a/b && + git add -A && + git commit -m "dir to symlink" +' + +test_expect_failure SYMLINKS 'checkout commit with dir must not remove untracked a/b' ' + + git rm --cached a/b && + git commit -m "un-track the symlink" && + test_must_fail git checkout start && + test -h a/b +' + +test_done diff --git a/t/t2050-git-dir-relative.sh b/t/t2050-git-dir-relative.sh index b7131d8c08..21f4659a9d 100755 --- a/t/t2050-git-dir-relative.sh +++ b/t/t2050-git-dir-relative.sh @@ -26,7 +26,7 @@ chmod +x .git/hooks/post-commit' 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}" ' @@ -45,7 +45,7 @@ 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 && diff --git a/t/t2101-update-index-reupdate.sh b/t/t2101-update-index-reupdate.sh index 76ad7c344c..c8bce8c2e4 100755 --- a/t/t2101-update-index-reupdate.sh +++ b/t/t2101-update-index-reupdate.sh @@ -51,7 +51,7 @@ test_expect_success 'update-index again' \ 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 new file mode 100755 index 0000000000..809fafe208 --- /dev/null +++ b/t/t2107-update-index-basic.sh @@ -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 2ad2819a34..0692427cb6 100755 --- a/t/t2200-add-update.sh +++ b/t/t2200-add-update.sh @@ -25,7 +25,7 @@ test_expect_success setup ' 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 && diff --git a/t/t3001-ls-files-others-exclude.sh b/t/t3001-ls-files-others-exclude.sh index 6d2f2b67ee..c8fe978267 100755 --- a/t/t3001-ls-files-others-exclude.sh +++ b/t/t3001-ls-files-others-exclude.sh @@ -156,7 +156,7 @@ test_expect_success 'trailing slash in exclude allows directory match (2)' ' 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 new file mode 100755 index 0000000000..490e052875 --- /dev/null +++ b/t/t3004-ls-files-basic.sh @@ -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 diff --git a/t/t3020-ls-files-error-unmatch.sh b/t/t3020-ls-files-error-unmatch.sh index a7d8187169..ca01053bcc 100755 --- a/t/t3020-ls-files-error-unmatch.sh +++ b/t/t3020-ls-files-error-unmatch.sh @@ -26,4 +26,3 @@ test_expect_success \ 'git ls-files --error-unmatch foo bar' test_done -1 diff --git a/t/t3030-merge-recursive.sh b/t/t3030-merge-recursive.sh index e66e550b24..34794f8a70 100755 --- a/t/t3030-merge-recursive.sh +++ b/t/t3030-merge-recursive.sh @@ -25,6 +25,10 @@ test_expect_success 'setup 1' ' 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 && @@ -255,7 +259,16 @@ test_expect_success 'setup 8' ' 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' ' @@ -544,7 +557,7 @@ test_expect_success 'reset and bind merge' ' 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 && @@ -615,4 +628,26 @@ test_expect_success 'merge-recursive copy vs. rename' ' 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 new file mode 100755 index 0000000000..44f5421be4 --- /dev/null +++ b/t/t3032-merge-recursive-options.sh @@ -0,0 +1,189 @@ +#!/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 '--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 diff --git a/t/t3050-subprojects-fetch.sh b/t/t3050-subprojects-fetch.sh index 4261e9641e..2f5f41a012 100755 --- a/t/t3050-subprojects-fetch.sh +++ b/t/t3050-subprojects-fetch.sh @@ -10,10 +10,10 @@ test_expect_success setup ' 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 f54a533456..78ce09f9d7 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -26,6 +26,17 @@ test_expect_success \ ! 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' @@ -212,6 +223,11 @@ test_expect_success \ 'branch from non-branch HEAD w/--track causes failure' \ 'test_must_fail git branch --track my10 HEAD^' +test_expect_success \ + 'branch from tag w/--track causes failure' \ + 'git tag foobar && + test_must_fail git branch --track my11 foobar' + # Keep this test last, as it changes the current branch cat >expect <<EOF 0000000000000000000000000000000000000000 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 branch: Created from master @@ -477,6 +493,15 @@ test_expect_success 'autosetuprebase always on an untracked remote branch' ' test "z$(git config branch.myr20.rebase)" = z ' +test_expect_success 'autosetuprebase always on detached HEAD' ' + git config branch.autosetupmerge always && + test_when_finished git checkout master && + git checkout HEAD^0 && + git branch my11 && + test -z "$(git config branch.my11.remote)" && + test -z "$(git config branch.my11.merge)" +' + test_expect_success 'detect misconfigured autosetuprebase (bad value)' ' git config branch.autosetuprebase garbage && test_must_fail git branch diff --git a/t/t3203-branch-output.sh b/t/t3203-branch-output.sh index 809d1c4ed4..6028748c6c 100755 --- a/t/t3203-branch-output.sh +++ b/t/t3203-branch-output.sh @@ -12,13 +12,13 @@ test_expect_success 'make commits' ' ' 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 f39a261d80..5e29a05259 100755 --- a/t/t3300-funny-names.sh +++ b/t/t3300-funny-names.sh @@ -43,8 +43,8 @@ test_expect_success TABS_IN_FILENAMES 'git ls-files no-funny' \ 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 @@ -69,8 +69,8 @@ test_expect_success TABS_IN_FILENAMES 'git ls-files -z with-funny' \ 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 a2b79a0430..1921ca3a73 100755 --- a/t/t3301-notes.sh +++ b/t/t3301-notes.sh @@ -52,7 +52,7 @@ test_expect_success 'refusing to edit notes in refs/remotes/' ' # 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' ' @@ -627,16 +627,16 @@ test_expect_success '--show-notes=ref accumulates' ' 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 && @@ -962,6 +962,7 @@ Date: Thu Apr 7 15:27:13 2005 -0700 Notes (other): a fresh note +$whitespace another fresh note EOF @@ -983,8 +984,11 @@ Date: Thu Apr 7 15:27:13 2005 -0700 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 diff --git a/t/t3303-notes-subtrees.sh b/t/t3303-notes-subtrees.sh index 75ec18778e..704aee81ef 100755 --- a/t/t3303-notes-subtrees.sh +++ b/t/t3303-notes-subtrees.sh @@ -168,15 +168,16 @@ INPUT_END } 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 3269f2eebd..2ea3be6546 100755 --- a/t/t3307-notes-man.sh +++ b/t/t3307-notes-man.sh @@ -26,7 +26,7 @@ test_expect_success 'example 1: notes to add an Acked-by line' ' ' 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 new file mode 100755 index 0000000000..24d82b49bb --- /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 new file mode 100755 index 0000000000..461fd84755 --- /dev/null +++ b/t/t3309-notes-merge-auto-resolve.sh @@ -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 new file mode 100755 index 0000000000..4ec4d11450 --- /dev/null +++ b/t/t3310-notes-merge-manual-resolve.sh @@ -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 new file mode 100755 index 0000000000..93516ef67c --- /dev/null +++ b/t/t3311-notes-merge-fanout.sh @@ -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 diff --git a/t/t3402-rebase-merge.sh b/t/t3402-rebase-merge.sh index 2bea65634a..be8c1d5ef9 100755 --- a/t/t3402-rebase-merge.sh +++ b/t/t3402-rebase-merge.sh @@ -117,4 +117,25 @@ test_expect_success 'picking rebase' ' esac ' +test_expect_success 'rebase -s funny -Xopt' ' + test_when_finished "rm -fr test-bin funny.was.run" && + mkdir test-bin && + cat >test-bin/git-merge-funny <<-EOF && + #!$SHELL_PATH + case "\$1" in --opt) ;; *) exit 2 ;; esac + shift && + >funny.was.run && + exec git merge-recursive "\$@" + EOF + chmod +x test-bin/git-merge-funny && + git reset --hard && + git checkout -b test-funny master^ && + test_commit funny && + ( + PATH=./test-bin:$PATH + git rebase -s funny -Xopt master + ) && + test -f funny.was.run +' + test_done diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index 7d20a74c5c..7d8147bb93 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -7,34 +7,39 @@ test_description='git rebase interactive 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 && @@ -46,29 +51,29 @@ test_expect_success 'setup' ' 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 # be non-POSIX. For example, if SHELL=zsh then ">file" doesn't work # to create a file. Unseting SHELL avoids such non-portable behavior -# in tests. +# in tests. It must be exported for it to take effect where needed. SHELL= +export SHELL test_expect_success 'rebase -i with the exec command' ' git checkout master && @@ -82,20 +87,12 @@ test_expect_success 'rebase -i with the exec command' ' 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-* ' @@ -116,11 +113,7 @@ test_expect_success 'rebase -i with the exec command checks tree cleanness' ' 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 ' @@ -584,7 +577,7 @@ test_expect_success 'do "noop" when there is nothing to cherry-pick' ' 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) @@ -599,7 +592,7 @@ test_expect_success 'submodule rebase setup' ' 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 && @@ -655,6 +648,7 @@ test_expect_success 'rebase -i can copy notes' ' cat >expect <<EOF an earlier note + a note EOF diff --git a/t/t3406-rebase-message.sh b/t/t3406-rebase-message.sh index 85fc7c4af8..fe5f936988 100755 --- a/t/t3406-rebase-message.sh +++ b/t/t3406-rebase-message.sh @@ -43,20 +43,20 @@ test_expect_success 'rebase -m' ' ' 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 diff --git a/t/t3407-rebase-abort.sh b/t/t3407-rebase-abort.sh index fbb3f2e0df..e573dc845b 100755 --- a/t/t3407-rebase-abort.sh +++ b/t/t3407-rebase-abort.sh @@ -72,6 +72,18 @@ testrebase() { test $(git rev-parse to-rebase) = $(git rev-parse pre-rebase) && test ! -d "$dotest" ' + + test_expect_success "rebase$type --abort does not update reflog" ' + cd "$work_dir" && + # Clean up the state from the previous one + git reset --hard pre-rebase && + git reflog show to-rebase > reflog_before && + test_must_fail git rebase$type master && + git rebase --abort && + git reflog show to-rebase > reflog_after && + test_cmp reflog_before reflog_after && + rm reflog_before reflog_after + ' } testrebase "" .git/rebase-apply diff --git a/t/t3408-rebase-multi-line.sh b/t/t3408-rebase-multi-line.sh index 2062b858bb..6b84e6042a 100755 --- a/t/t3408-rebase-multi-line.sh +++ b/t/t3408-rebase-multi-line.sh @@ -16,7 +16,7 @@ test_expect_success setup ' 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 && diff --git a/t/t3409-rebase-preserve-merges.sh b/t/t3409-rebase-preserve-merges.sh index 74161a42ec..19341e5ca1 100755 --- a/t/t3409-rebase-preserve-merges.sh +++ b/t/t3409-rebase-preserve-merges.sh @@ -72,7 +72,7 @@ test_expect_success 'rebase -p fakes interactive rebase' ' 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 5869061c5b..086c91c7b4 100755 --- a/t/t3412-rebase-root.sh +++ b/t/t3412-rebase-root.sh @@ -173,14 +173,14 @@ EOF 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) ' diff --git a/t/t3415-rebase-autosquash.sh b/t/t3415-rebase-autosquash.sh index 37cb89ab53..b38be8e937 100755 --- a/t/t3415-rebase-autosquash.sh +++ b/t/t3415-rebase-autosquash.sh @@ -14,6 +14,7 @@ test_expect_success setup ' git add . && test_tick && git commit -m "first commit" && + git tag first-commit && echo 3 >file3 && git add . && test_tick && @@ -21,12 +22,12 @@ test_expect_success setup ' git tag base ' -test_auto_fixup() { +test_auto_fixup () { git reset --hard base && echo 1 >file1 && git add -u && test_tick && - git commit -m "fixup! first" + git commit -m "fixup! first" && git tag $1 && test_tick && @@ -50,12 +51,12 @@ test_expect_success 'auto fixup (config)' ' 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_tick && - git commit -m "squash! first" + git commit -m "squash! first" && git tag $1 && test_tick && @@ -84,7 +85,7 @@ test_expect_success 'misspelled auto squash' ' echo 1 >file1 && git add -u && test_tick && - git commit -m "squash! forst" + git commit -m "squash! forst" && git tag final-missquash && test_tick && git rebase --autosquash -i HEAD^^^ && @@ -94,4 +95,102 @@ test_expect_success 'misspelled auto squash' ' 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 diff --git a/t/t3417-rebase-whitespace-fix.sh b/t/t3417-rebase-whitespace-fix.sh index 220a740ee8..1fb3e499b4 100755 --- a/t/t3417-rebase-whitespace-fix.sh +++ b/t/t3417-rebase-whitespace-fix.sh @@ -89,7 +89,7 @@ test_expect_success 'same, but do not remove trailing spaces' ' 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 ' diff --git a/t/t3419-rebase-patch-id.sh b/t/t3419-rebase-patch-id.sh new file mode 100755 index 0000000000..bd8efaf005 --- /dev/null +++ b/t/t3419-rebase-patch-id.sh @@ -0,0 +1,109 @@ +#!/bin/sh + +test_description='git rebase - test patch id computation' + +. ./test-lib.sh + +test_set_prereq NOT_EXPENSIVE +test -n "$GIT_PATCHID_TIMING_TESTS" && test_set_prereq EXPENSIVE +test -x /usr/bin/time && test_set_prereq USR_BIN_TIME + +count() +{ + i=0 + while test $i -lt $1 + do + echo "$i" + i=$(($i+1)) + done +} + +scramble() +{ + i=0 + while read x + do + if test $i -ne 0 + then + echo "$x" + fi + i=$((($i+1) % 10)) + done < "$1" > "$1.new" + mv -f "$1.new" "$1" +} + +run() +{ + echo \$ "$@" + /usr/bin/time "$@" >/dev/null +} + +test_expect_success 'setup' ' + git commit --allow-empty -m initial + git tag root +' + +do_tests() +{ + pr=$1 + nlines=$2 + + test_expect_success $pr "setup: $nlines lines" " + rm -f .gitattributes && + git checkout -q -f master && + git reset --hard root && + count $nlines >file && + git add file && + git commit -q -m initial && + git branch -f other && + + scramble file && + git add file && + git commit -q -m 'change big file' && + + git checkout -q other && + : >newfile && + git add newfile && + git commit -q -m 'add small file' && + + git cherry-pick master >/dev/null 2>&1 + " + + test_debug " + run git diff master^\! + " + + test_expect_success $pr 'setup attributes' " + echo 'file binary' >.gitattributes + " + + test_debug " + run git format-patch --stdout master && + run git format-patch --stdout --ignore-if-in-upstream master + " + + test_expect_success $pr 'detect upstream patch' " + git checkout -q master && + scramble file && + git add file && + git commit -q -m 'change big file again' && + git checkout -q other^{} && + git rebase master && + test_must_fail test -n \"\$(git rev-list master...HEAD~)\" + " + + test_expect_success $pr 'do not drop patch' " + git branch -f squashed master && + git checkout -q -f squashed && + git reset -q --soft HEAD~2 && + git commit -q -m squashed && + git checkout -q other^{} && + test_must_fail git rebase squashed && + rm -rf .git/rebase-apply + " +} + +do_tests NOT_EXPENSIVE 500 +do_tests EXPENSIVE 50000 + +test_done diff --git a/t/t3501-revert-cherry-pick.sh b/t/t3501-revert-cherry-pick.sh index bc7aedd048..043954422c 100755 --- a/t/t3501-revert-cherry-pick.sh +++ b/t/t3501-revert-cherry-pick.sh @@ -81,6 +81,16 @@ test_expect_success 'revert after renaming branch' ' ' +test_expect_success 'cherry-pick on stat-dirty working tree' ' + git clone . copy && + ( + cd copy && + git checkout initial && + test-chmtime +40 oops && + git cherry-pick added + ) +' + test_expect_success 'revert forbidden on dirty working tree' ' echo content >extra_file && diff --git a/t/t3504-cherry-pick-rerere.sh b/t/t3504-cherry-pick-rerere.sh index f7b3518a32..e6a64816ef 100755 --- a/t/t3504-cherry-pick-rerere.sh +++ b/t/t3504-cherry-pick-rerere.sh @@ -23,7 +23,7 @@ test_expect_success 'conflicting merge' ' 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 ' @@ -33,7 +33,7 @@ test_expect_success 'cherry-pick conflict' ' ' test_expect_success 'reconfigure' ' - git config rerere.enabled false + git config rerere.enabled false && git reset --hard ' diff --git a/t/t3506-cherry-pick-ff.sh b/t/t3506-cherry-pick-ff.sh index e17ae712b1..51ca391e47 100755 --- a/t/t3506-cherry-pick-ff.sh +++ b/t/t3506-cherry-pick-ff.sh @@ -95,4 +95,14 @@ test_expect_success 'cherry pick a merge relative to nonexistent parent with --f test_must_fail git cherry-pick --ff -m 3 C ' +test_expect_success 'cherry pick a root commit with --ff' ' + git reset --hard first -- && + git rm file1 && + echo first >file2 && + git add file2 && + git commit --amend -m "file2" && + git cherry-pick --ff first && + test "$(git rev-parse --verify HEAD)" = "1df192cd8bc58a2b275d842cede4d221ad9000d1" +' + test_done diff --git a/t/t3509-cherry-pick-merge-df.sh b/t/t3509-cherry-pick-merge-df.sh index a5ccdbf8fc..df921d1f33 100755 --- a/t/t3509-cherry-pick-merge-df.sh +++ b/t/t3509-cherry-pick-merge-df.sh @@ -3,12 +3,14 @@ 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 && @@ -32,4 +34,70 @@ test_expect_success SYMLINKS 'Cherry-pick succeeds with rename across D/F confli 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/t3600-rm.sh b/t/t3600-rm.sh index b26cabd571..cd093bd347 100755 --- a/t/t3600-rm.sh +++ b/t/t3600-rm.sh @@ -96,7 +96,7 @@ test_expect_success FUNNYNAMES \ "git rm -f 'space embedded' 'tab embedded' 'newline embedded'" -test_expect_success RO_DIR 'Test that "git rm -f" fails if its rm fails' ' +test_expect_success SANITY 'Test that "git rm -f" fails if its rm fails' ' chmod a-w . && test_must_fail git rm -f baz && chmod 775 . diff --git a/t/t3900-i18n-commit.sh b/t/t3900-i18n-commit.sh index 256c4c9701..c06a5ee766 100755 --- a/t/t3900-i18n-commit.sh +++ b/t/t3900-i18n-commit.sh @@ -133,4 +133,33 @@ do ' 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 7d49469841..da82b655b3 100755 --- a/t/t3902-quoted.sh +++ b/t/t3902-quoted.sh @@ -36,19 +36,19 @@ for_each_name () { 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 a283dca25f..f62aaf5816 100755 --- a/t/t3903-stash.sh +++ b/t/t3903-stash.sh @@ -157,7 +157,7 @@ EOF 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 && @@ -255,7 +255,7 @@ test_expect_success 'stash rm and ignore' ' 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)" @@ -268,7 +268,7 @@ test_expect_success 'stash rm and ignore (stage .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)" @@ -416,8 +416,37 @@ test_expect_success 'stash show - stashes on stack, stash-like argument' ' echo bar >> file && STASH_ID=$(git stash create) && git reset --hard && - git stash show ${STASH_ID} + cat >expected <<-EOF && + file | 1 + + 1 files changed, 1 insertions(+), 0 deletions(-) + EOF + git stash show ${STASH_ID} >actual && + test_cmp expected actual ' + +test_expect_success 'stash show -p - stashes on stack, stash-like argument' ' + git stash clear && + test_when_finished "git reset --hard HEAD" && + git reset --hard && + echo foo >> file && + git stash && + test_when_finished "git stash drop" && + echo bar >> file && + STASH_ID=$(git stash create) && + git reset --hard && + cat >expected <<-EOF && + diff --git a/file b/file + index 7601807..935fbd3 100644 + --- a/file + +++ b/file + @@ -1 +1,2 @@ + baz + +bar + EOF + git stash show -p ${STASH_ID} >actual && + test_cmp expected actual +' + test_expect_success 'stash show - no stashes on stack, stash-like argument' ' git stash clear && test_when_finished "git reset --hard HEAD" && @@ -425,7 +454,32 @@ test_expect_success 'stash show - no stashes on stack, stash-like argument' ' echo foo >> file && STASH_ID=$(git stash create) && git reset --hard && - git stash show ${STASH_ID} + cat >expected <<-EOF && + file | 1 + + 1 files changed, 1 insertions(+), 0 deletions(-) + EOF + git stash show ${STASH_ID} >actual && + test_cmp expected actual +' + +test_expect_success 'stash show -p - no stashes on stack, stash-like argument' ' + git stash clear && + test_when_finished "git reset --hard HEAD" && + git reset --hard && + echo foo >> file && + STASH_ID=$(git stash create) && + git reset --hard && + cat >expected <<-EOF && + diff --git a/file b/file + index 7601807..71b52c4 100644 + --- a/file + +++ b/file + @@ -1 +1,2 @@ + baz + +foo + EOF + git stash show -p ${STASH_ID} >actual && + test_cmp expected actual ' test_expect_success 'stash drop - fail early if specified stash is not a stash reference' ' @@ -491,4 +545,34 @@ test_expect_success 'invalid ref of the form stash@{n}, n >= N' ' git stash drop ' +test_expect_success 'stash branch should not drop the stash if the branch exists' ' + git stash clear && + echo foo >file && + git add file && + git commit -m initial && + echo bar >file && + git stash && + test_must_fail git stash branch master stash@{0} && + git rev-parse stash@{0} -- +' + +test_expect_success 'stash apply shows status same as git status (relative to current directory)' ' + git stash clear && + echo 1 >subdir/subfile1 && + echo 2 >subdir/subfile2 && + git add subdir/subfile1 && + git commit -m subdir && + ( + cd subdir && + echo x >subfile1 && + echo x >../file && + git status >../expect && + git stash && + sane_unset GIT_MERGE_VERBOSITY && + git stash apply + ) | + sed -e 1,2d >actual && # drop "Saved..." and "HEAD is now..." + test_cmp expect actual +' + test_done diff --git a/t/t3904-stash-patch.sh b/t/t3904-stash-patch.sh index d1819ca23a..1e7193ac0b 100755 --- a/t/t3904-stash-patch.sh +++ b/t/t3904-stash-patch.sh @@ -20,7 +20,7 @@ test_expect_success PERL 'setup' ' # 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 73441a5165..9fb8ca06a8 100755 --- a/t/t4002-diff-basic.sh +++ b/t/t4002-diff-basic.sh @@ -205,8 +205,8 @@ test_expect_success \ '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' @@ -215,8 +215,8 @@ test_expect_success \ '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' @@ -225,8 +225,8 @@ test_expect_success \ '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' diff --git a/t/t4003-diff-rename-1.sh b/t/t4003-diff-rename-1.sh index c6130c4019..bfa8835638 100755 --- a/t/t4003-diff-rename-1.sh +++ b/t/t4003-diff-rename-1.sh @@ -29,7 +29,7 @@ test_expect_success \ # 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 diff --git a/t/t4004-diff-rename-symlink.sh b/t/t4004-diff-rename-symlink.sh index 92a65f4852..6e562c80d1 100755 --- a/t/t4004-diff-rename-symlink.sh +++ b/t/t4004-diff-rename-symlink.sh @@ -35,7 +35,7 @@ test_expect_success SYMLINKS \ # 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 diff --git a/t/t4005-diff-rename-2.sh b/t/t4005-diff-rename-2.sh index 1ba359d478..77d7f4946f 100755 --- a/t/t4005-diff-rename-2.sh +++ b/t/t4005-diff-rename-2.sh @@ -29,7 +29,7 @@ test_expect_success \ # 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 diff --git a/t/t4008-diff-break-rewrite.sh b/t/t4008-diff-break-rewrite.sh index e19ca65885..73b4a24f5e 100755 --- a/t/t4008-diff-break-rewrite.sh +++ b/t/t4008-diff-break-rewrite.sh @@ -155,7 +155,7 @@ test_expect_success \ 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 \ @@ -173,8 +173,8 @@ 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 diff --git a/t/t4009-diff-rename-4.sh b/t/t4009-diff-rename-4.sh index de3f17478e..f22c8e3dba 100755 --- a/t/t4009-diff-rename-4.sh +++ b/t/t4009-diff-rename-4.sh @@ -29,7 +29,7 @@ test_expect_success \ # 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 diff --git a/t/t4011-diff-symlink.sh b/t/t4011-diff-symlink.sh index 6f6948925f..408a19c4c2 100755 --- a/t/t4011-diff-symlink.sh +++ b/t/t4011-diff-symlink.sh @@ -88,4 +88,30 @@ test_expect_success SYMLINKS \ test_must_fail git diff --no-index pinky brain > output 2> output.err && grep narf output && ! grep error output.err' + +test_expect_success SYMLINKS 'setup symlinks with attributes' ' + echo "*.bin diff=bin" >>.gitattributes && + echo content >file.bin && + ln -s file.bin link.bin && + git add -N file.bin link.bin +' + +cat >expect <<'EOF' +diff --git a/file.bin b/file.bin +index e69de29..d95f3ad 100644 +Binary files a/file.bin and b/file.bin differ +diff --git a/link.bin b/link.bin +index e69de29..dce41ec 120000 +--- a/link.bin ++++ b/link.bin +@@ -0,0 +1 @@ ++file.bin +\ No newline at end of file +EOF +test_expect_success SYMLINKS 'symlinks do not respect userdiff config by path' ' + git config diff.bin.binary true && + git diff file.bin link.bin >actual && + test_cmp expect actual +' + test_done diff --git a/t/t4012-diff-binary.sh b/t/t4012-diff-binary.sh index bc46563afc..05ec062832 100755 --- a/t/t4012-diff-binary.sh +++ b/t/t4012-diff-binary.sh @@ -77,10 +77,6 @@ test_expect_success 'apply binary patch' \ tree1=`git write-tree` && test "$tree1" = "$tree0"' -nul_to_q() { - perl -pe 'y/\000/Q/' -} - test_expect_success 'diff --no-index with binary creation' ' echo Q | q_to_nul >binary && (: hide error code from diff, which just indicates differences diff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh index 19857f4326..5daa0f2a0c 100755 --- a/t/t4013-diff-various.sh +++ b/t/t4013-diff-various.sh @@ -210,6 +210,12 @@ log -m -p master log -SF master log -S F master log -SF -p master +log -SF master --max-count=0 +log -SF master --max-count=1 +log -SF master --max-count=2 +log -GF master +log -GF -p master +log -GF -p --pickaxe-all master log --decorate --all log --decorate=full --all @@ -287,4 +293,15 @@ test_expect_success 'log -S requires an argument' ' 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 new file mode 100644 index 0000000000..ff16e83e7c --- /dev/null +++ b/t/t4013/diff.diff_--cached @@ -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 new file mode 100644 index 0000000000..b9bb858a03 --- /dev/null +++ b/t/t4013/diff.diff_--cached_--_file0 @@ -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 new file mode 100644 index 0000000000..d36f88098b --- /dev/null +++ b/t/t4013/diff.log_-GF_-p_--pickaxe-all_master @@ -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 new file mode 100644 index 0000000000..9d93f2c23a --- /dev/null +++ b/t/t4013/diff.log_-GF_-p_master @@ -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 new file mode 100644 index 0000000000..4c6708d2d0 --- /dev/null +++ b/t/t4013/diff.log_-GF_master @@ -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 +$ diff --git a/t/t4013/diff.log_-SF_master_--max-count=0 b/t/t4013/diff.log_-SF_master_--max-count=0 new file mode 100644 index 0000000000..c1fc6c8731 --- /dev/null +++ b/t/t4013/diff.log_-SF_master_--max-count=0 @@ -0,0 +1,2 @@ +$ git log -SF master --max-count=0 +$ diff --git a/t/t4013/diff.log_-SF_master_--max-count=1 b/t/t4013/diff.log_-SF_master_--max-count=1 new file mode 100644 index 0000000000..c981a03814 --- /dev/null +++ b/t/t4013/diff.log_-SF_master_--max-count=1 @@ -0,0 +1,7 @@ +$ git log -SF master --max-count=1 +commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0 +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:02:00 2006 +0000 + + Third +$ diff --git a/t/t4013/diff.log_-SF_master_--max-count=2 b/t/t4013/diff.log_-SF_master_--max-count=2 new file mode 100644 index 0000000000..a6c55fd482 --- /dev/null +++ b/t/t4013/diff.log_-SF_master_--max-count=2 @@ -0,0 +1,7 @@ +$ git log -SF master --max-count=2 +commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0 +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:02:00 2006 +0000 + + Third +$ diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh index f87434b9f8..9c663677df 100755 --- a/t/t4014-format-patch.sh +++ b/t/t4014-format-patch.sh @@ -6,30 +6,36 @@ 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" ' @@ -51,6 +57,22 @@ test_expect_success "format-patch --ignore-if-in-upstream" ' ' +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 && @@ -665,4 +687,110 @@ test_expect_success 'format-patch --signature="" supresses signatures' ' ! 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_expect_success 'format-patch handles multi-line subjects' ' + rm -rf patches/ && + echo content >>file && + for i in one two three; do echo $i; done >msg && + git add file && + git commit -F msg && + git format-patch -o patches -1 && + grep ^Subject: patches/0001-one.patch >actual && + echo "Subject: [PATCH] one two three" >expect && + test_cmp expect actual +' + +test_expect_success 'format-patch handles multi-line encoded subjects' ' + rm -rf patches/ && + echo content >>file && + for i in en två tre; do echo $i; done >msg && + git add file && + git commit -F msg && + git format-patch -o patches -1 && + grep ^Subject: patches/0001-en.patch >actual && + echo "Subject: [PATCH] =?UTF-8?q?en=20tv=C3=A5=20tre?=" >expect && + test_cmp expect actual +' + +M8="foo bar " +M64=$M8$M8$M8$M8$M8$M8$M8$M8 +M512=$M64$M64$M64$M64$M64$M64$M64$M64 +cat >expect <<'EOF' +Subject: [PATCH] foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo + bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar + foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo + bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar + foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo + bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar + foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo + bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar + foo bar foo bar foo bar foo bar +EOF +test_expect_success 'format-patch wraps extremely long headers (ascii)' ' + echo content >>file && + git add file && + git commit -m "$M512" && + git format-patch --stdout -1 >patch && + sed -n "/^Subject/p; /^ /p; /^$/q" <patch >subject && + test_cmp expect subject +' + +M8="föö bar " +M64=$M8$M8$M8$M8$M8$M8$M8$M8 +M512=$M64$M64$M64$M64$M64$M64$M64$M64 +cat >expect <<'EOF' +Subject: [PATCH] =?UTF-8?q?f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6?= + =?UTF-8?q?=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6?= + =?UTF-8?q?=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6?= + =?UTF-8?q?=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6?= + =?UTF-8?q?=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6?= + =?UTF-8?q?=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6?= + =?UTF-8?q?=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6?= + =?UTF-8?q?=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6?= + =?UTF-8?q?=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6?= + =?UTF-8?q?=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6?= + =?UTF-8?q?=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6?= + =?UTF-8?q?=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6?= + =?UTF-8?q?=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6?= + =?UTF-8?q?=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6?= + =?UTF-8?q?=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6?= + =?UTF-8?q?=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6?= + =?UTF-8?q?=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6?= + =?UTF-8?q?=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6?= + =?UTF-8?q?=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6?= + =?UTF-8?q?=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6?= + =?UTF-8?q?=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6?= + =?UTF-8?q?=C3=B6=20bar=20f=C3=B6=C3=B6=20bar?= +EOF +test_expect_success 'format-patch wraps extremely long headers (rfc2047)' ' + rm -rf patches/ && + echo content >>file && + git add file && + git commit -m "$M512" && + git format-patch --stdout -1 >patch && + sed -n "/^Subject/p; /^ /p; /^$/q" <patch >subject && + test_cmp expect subject +' + test_done diff --git a/t/t4015-diff-whitespace.sh b/t/t4015-diff-whitespace.sh index 935d101fe8..9059bcd69e 100755 --- a/t/t4015-diff-whitespace.sh +++ b/t/t4015-diff-whitespace.sh @@ -330,7 +330,7 @@ test_expect_success 'check space before tab in indent (space-before-tab: on)' ' 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 @@ -344,6 +344,13 @@ test_expect_success 'check spaces as indentation (indent-with-non-tab: on)' ' ' +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" && @@ -491,4 +519,41 @@ test_expect_success 'combined diff with autocrlf conversion' ' ' +# Start testing the colored format for whitespace checks + +test_expect_success 'setup diff colors' ' + git config color.diff always && + git config color.diff.plain normal && + git config color.diff.meta bold && + git config color.diff.frag cyan && + git config color.diff.func normal && + git config color.diff.old red && + git config color.diff.new green && + git config color.diff.commit yellow && + git config color.diff.whitespace "normal red" && + + git config core.autocrlf false +' +cat >expected <<\EOF +<BOLD>diff --git a/x b/x<RESET> +<BOLD>index 9daeafb..2874b91 100644<RESET> +<BOLD>--- a/x<RESET> +<BOLD>+++ b/x<RESET> +<CYAN>@@ -1 +1,4 @@<RESET> + test<RESET> +<GREEN>+<RESET><GREEN>{<RESET> +<GREEN>+<RESET><BRED> <RESET> +<GREEN>+<RESET><GREEN>}<RESET> +EOF + +test_expect_success 'diff that introduces a line with only tabs' ' + git config core.whitespace blank-at-eol && + git reset --hard && + echo "test" > x && + git commit -m "initial" x && + echo "{NTN}" | tr "NT" "\n\t" >> x && + git -c color.diff=always diff | test_decode_color >current && + test_cmp expected current +' + test_done diff --git a/t/t4017-diff-retval.sh b/t/t4017-diff-retval.sh index 61589853df..95a7ca7070 100755 --- a/t/t4017-diff-retval.sh +++ b/t/t4017-diff-retval.sh @@ -29,66 +29,49 @@ test_expect_success 'git diff --quiet -w HEAD^ HEAD' ' ' 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' ' @@ -100,30 +83,26 @@ 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 ' @@ -133,10 +112,8 @@ test_expect_success 'check detects leftover conflict markers' ' 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 ' @@ -146,19 +123,13 @@ test_expect_success 'check honors conflict marker length' ' 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 ' diff --git a/t/t4018-diff-funcname.sh b/t/t4018-diff-funcname.sh index c8e19372b0..3646930623 100755 --- a/t/t4018-diff-funcname.sh +++ b/t/t4018-diff-funcname.sh @@ -32,7 +32,7 @@ EOF sed 's/beer\\/beer,\\/' < Beer.java > Beer-correct.java -builtin_patterns="bibtex cpp csharp 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" ' diff --git a/t/t4019-diff-wserror.sh b/t/t4019-diff-wserror.sh index f6d1f1ebab..a5019759bc 100755 --- a/t/t4019-diff-wserror.sh +++ b/t/t4019-diff-wserror.sh @@ -36,11 +36,12 @@ prepare_output () { 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 && @@ -50,10 +51,67 @@ test_expect_success default ' ' +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 && @@ -65,9 +123,9 @@ test_expect_success 'without -trail' ' 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 && @@ -79,9 +137,9 @@ test_expect_success 'without -trail (attribute)' ' 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 && @@ -93,9 +151,9 @@ test_expect_success 'without -space' ' 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 && @@ -107,9 +165,9 @@ test_expect_success 'without -space (attribute)' ' 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 && @@ -121,9 +179,9 @@ test_expect_success 'with indent-non-tab only' ' 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 && @@ -133,11 +191,39 @@ test_expect_success 'with indent-non-tab only (attribute)' ' ' +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 && @@ -149,9 +235,9 @@ test_expect_success 'with cr-at-eol' ' 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 && @@ -178,12 +264,21 @@ test_expect_success 'trailing empty lines (2)' ' ' +test_expect_success 'checkdiff shows correct line number for trailing blank lines' ' + + printf "a\nb\n" > G && + git add G && + printf "x\nx\nx\na\nb\nc\n\n" > G && + [ "$(git diff --check -- G)" = "G:7: new blank line at EOF." ] + +' + 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 diff --git a/t/t4021-format-patch-numbered.sh b/t/t4021-format-patch-numbered.sh index 709b3231ca..886494b58f 100755 --- a/t/t4021-format-patch-numbered.sh +++ b/t/t4021-format-patch-numbered.sh @@ -95,7 +95,7 @@ test_expect_success 'format.numbered && --keep-subject' ' 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 d5ccdd0cf8..3726a0e201 100755 --- a/t/t4026-color.sh +++ b/t/t4026-color.sh @@ -74,7 +74,6 @@ test_expect_success 'extra character after attribute' ' ' test_expect_success 'unknown color slots are ignored (diff)' ' - git config --unset diff.color.new git config color.diff.nosuchslotwilleverbedefined white && git diff --color ' diff --git a/t/t4027-diff-submodule.sh b/t/t4027-diff-submodule.sh index d99814ac64..241a74d2a2 100755 --- a/t/t4027-diff-submodule.sh +++ b/t/t4027-diff-submodule.sh @@ -316,11 +316,11 @@ test_expect_success 'git diff (empty submodule dir)' ' 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 diff --git a/t/t4031-diff-rewrite-binary.sh b/t/t4031-diff-rewrite-binary.sh index 7e7b307a24..7d7470f21b 100755 --- a/t/t4031-diff-rewrite-binary.sh +++ b/t/t4031-diff-rewrite-binary.sh @@ -44,6 +44,13 @@ test_expect_success 'rewrite diff can show binary patch' ' 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 6f7548c3a1..8096d8a337 100755 --- a/t/t4034-diff-words.sh +++ b/t/t4034-diff-words.sh @@ -6,8 +6,8 @@ test_description='word diff colors' test_expect_success setup ' - git config diff.color.old red - git config diff.color.new green + git config diff.color.old red && + git config diff.color.new green && git config diff.color.func magenta ' @@ -35,10 +35,10 @@ aeff = aeff * ( aaa ) EOF cat > expect <<\EOF -<WHITE>diff --git a/pre b/post<RESET> -<WHITE>index 330b04f..5ed8eff 100644<RESET> -<WHITE>--- a/pre<RESET> -<WHITE>+++ b/post<RESET> +<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> @@ -122,10 +122,10 @@ test_expect_success '--word-diff=plain --no-color' ' ' cat > expect <<EOF -<WHITE>diff --git a/pre b/post<RESET> -<WHITE>index 330b04f..5ed8eff 100644<RESET> -<WHITE>--- a/pre<RESET> -<WHITE>+++ b/post<RESET> +<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> @@ -143,10 +143,10 @@ test_expect_success '--word-diff=plain --color' ' ' cat > expect <<\EOF -<WHITE>diff --git a/pre b/post<RESET> -<WHITE>index 330b04f..5ed8eff 100644<RESET> -<WHITE>--- a/pre<RESET> -<WHITE>+++ b/post<RESET> +<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> @@ -163,10 +163,10 @@ test_expect_success 'word diff without context' ' ' cat > expect <<\EOF -<WHITE>diff --git a/pre b/post<RESET> -<WHITE>index 330b04f..5ed8eff 100644<RESET> -<WHITE>--- a/pre<RESET> -<WHITE>+++ b/post<RESET> +<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] @@ -199,10 +199,10 @@ test_expect_success 'option overrides .gitattributes' ' ' cat > expect <<\EOF -<WHITE>diff --git a/pre b/post<RESET> -<WHITE>index 330b04f..5ed8eff 100644<RESET> -<WHITE>--- a/pre<RESET> -<WHITE>+++ b/post<RESET> +<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> @@ -231,10 +231,10 @@ test_expect_success 'command-line overrides config' ' ' cat > expect <<\EOF -<WHITE>diff --git a/pre b/post<RESET> -<WHITE>index 330b04f..5ed8eff 100644<RESET> -<WHITE>--- a/pre<RESET> -<WHITE>+++ b/post<RESET> +<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] @@ -260,10 +260,10 @@ test_expect_success 'remove diff driver regex' ' ' cat > expect <<\EOF -<WHITE>diff --git a/pre b/post<RESET> -<WHITE>index 330b04f..5ed8eff 100644<RESET> -<WHITE>--- a/pre<RESET> -<WHITE>+++ b/post<RESET> +<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>] @@ -282,10 +282,10 @@ echo 'aaa (aaa)' > pre echo 'aaa (aaa) aaa' > post cat > expect <<\EOF -<WHITE>diff --git a/pre b/post<RESET> -<WHITE>index c29453b..be22f37 100644<RESET> -<WHITE>--- a/pre<RESET> -<WHITE>+++ b/post<RESET> +<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 @@ -301,10 +301,10 @@ echo '(:' > pre echo '(' > post cat > expect <<\EOF -<WHITE>diff --git a/pre b/post<RESET> -<WHITE>index 289cb9d..2d06f37 100644<RESET> -<WHITE>--- a/pre<RESET> -<WHITE>+++ b/post<RESET> +<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 diff --git a/t/t4040-whitespace-status.sh b/t/t4040-whitespace-status.sh index a30b03bcf2..abc49348b1 100755 --- a/t/t4040-whitespace-status.sh +++ b/t/t4040-whitespace-status.sh @@ -60,4 +60,11 @@ test_expect_success 'diff-files -b -p --exit-code' ' git diff-files -b -p --exit-code ' +test_expect_success 'diff-files --diff-filter --quiet' ' + git reset --hard && + rm a/d && + echo x >>b/e && + test_must_fail git diff-files --diff-filter=M --quiet +' + test_done diff --git a/t/t4041-diff-submodule-option.sh b/t/t4041-diff-submodule-option.sh index 995bdfafec..bf9a7526bd 100755 --- a/t/t4041-diff-submodule-option.sh +++ b/t/t4041-diff-submodule-option.sh @@ -37,9 +37,10 @@ head1=$(add_file sm1 foo1 foo2) 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 && @@ -47,33 +48,36 @@ head2=$(add_file sm1 foo3) 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 @@ -82,6 +86,7 @@ index $head1..$head2 160000 -Subproject commit $fullhead1 +Subproject commit $fullhead2 EOF + test_cmp expected actual " commit_file sm1 && @@ -93,24 +98,26 @@ head3=$( 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 && @@ -123,7 +130,7 @@ mv sm1-bak 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 @@ -133,11 +140,12 @@ index 0000000..9da5fb8 @@ -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 @@ -147,13 +155,14 @@ 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 @@ -163,6 +172,7 @@ index 0000000..$head5 @@ -0,0 +1 @@ +sm1 EOF + test_cmp expected actual " rm -f sm1 && @@ -171,15 +181,16 @@ head6=$(add_file sm1 foo6 foo7) 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 @@ -189,21 +200,24 @@ 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)' " @@ -224,18 +238,20 @@ test_expect_success 'submodule contains untracked content (all 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 && @@ -359,41 +385,45 @@ git add 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 @@ -409,6 +439,7 @@ index 0000000..$head7 @@ -0,0 +1 @@ +Subproject commit $fullhead7 EOF + test_cmp expected actual " test_expect_success 'setup .git file for sm2' ' @@ -420,10 +451,11 @@ 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 diff --git a/t/t4103-apply-binary.sh b/t/t4103-apply-binary.sh index 9692f16f35..dbbf56cba9 100755 --- a/t/t4103-apply-binary.sh +++ b/t/t4103-apply-binary.sh @@ -37,15 +37,24 @@ test_expect_success 'setup' " git diff-tree -p -C master binary >C.diff && git diff-tree -p --binary master binary >BF.diff && - git diff-tree -p --binary -C master binary >CF.diff + git diff-tree -p --binary -C master binary >CF.diff && + + git diff-tree -p --full-index master binary >B-index.diff && + git diff-tree -p -C --full-index master binary >C-index.diff && + + git init other-repo && + (cd other-repo && + git fetch .. master && + git reset --hard FETCH_HEAD + ) " 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.' \ @@ -69,11 +78,11 @@ test_expect_success \ ' 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. @@ -100,6 +109,22 @@ test_expect_success 'apply binary diff (copy) -- should fail.' \ 'do_reset && test_must_fail git apply --index C.diff' +test_expect_success 'apply binary diff with full-index' ' + do_reset && + git apply B-index.diff +' + +test_expect_success 'apply binary diff with full-index (copy)' ' + do_reset && + git apply C-index.diff +' + +test_expect_success 'apply full-index binary diff in new repo' ' + (cd other-repo && + do_reset && + test_must_fail git apply ../B-index.diff) +' + test_expect_success 'apply binary diff without replacement.' \ 'do_reset && git apply BF.diff' diff --git a/t/t4111-apply-subdir.sh b/t/t4111-apply-subdir.sh index a52d94ae21..7c398432ba 100755 --- a/t/t4111-apply-subdir.sh +++ b/t/t4111-apply-subdir.sh @@ -89,7 +89,7 @@ test_expect_success 'apply --index from subdir of toplevel' ' 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" @@ -100,7 +100,7 @@ test_expect_success 'apply from .git dir' ' 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" diff --git a/t/t4119-apply-config.sh b/t/t4119-apply-config.sh index 3c73a783a7..3d0384daa8 100755 --- a/t/t4119-apply-config.sh +++ b/t/t4119-apply-config.sh @@ -73,7 +73,7 @@ D=`pwd` 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 2b2d00b334..a33d510bf6 100755 --- a/t/t4120-apply-popt.sh +++ b/t/t4120-apply-popt.sh @@ -6,6 +6,7 @@ test_description='git apply -p handling.' . ./test-lib.sh +. "$TEST_DIRECTORY"/lib-prereq-FILEMODE.sh test_expect_success setup ' mkdir sub && @@ -56,4 +57,34 @@ test_expect_success 'apply with too large -p and fancy filename' ' grep "removing 3 leading" err ' +test_expect_success 'apply (-p2) diff, mode change only' ' + cat >patch.chmod <<-\EOF && + diff --git a/sub/file1 b/sub/file1 + old mode 100644 + new mode 100755 + EOF + 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 +' + +test_expect_success 'apply (-p2) diff, rename' ' + cat >patch.rename <<-\EOF && + diff --git a/sub/file1 b/sub/file2 + similarity index 100% + rename from sub/file1 + rename to sub/file2 + EOF + echo A >expected && + + cp file1.saved file1 && + rm -f file2 && + git apply -p2 patch.rename && + test_cmp expected file2 +' + test_done diff --git a/t/t4124-apply-ws-rule.sh b/t/t4124-apply-ws-rule.sh index 8a676a5dcd..6f6ee88b28 100755 --- a/t/t4124-apply-ws-rule.sh +++ b/t/t4124-apply-ws-rule.sh @@ -10,7 +10,8 @@ prepare_test_file () { # 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. @@ -25,8 +26,8 @@ prepare_test_file () { ________>_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> @@ -121,6 +122,34 @@ test_expect_success 'whitespace=error-all, no rule (attribute)' ' ' +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 @@ -129,7 +158,7 @@ do 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 @@ -142,12 +171,22 @@ do 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 @@ -176,9 +215,8 @@ test_expect_success 'trailing whitespace & no newline at the end of file' ' ' 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 && @@ -368,7 +406,7 @@ test_expect_success 'missing blanks at EOF must only match blank lines' ' 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 ' @@ -419,7 +457,7 @@ test_expect_success 'same, but with CR-LF line endings && cr-at-eol set' ' 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 && @@ -436,7 +474,7 @@ test_expect_success 'same, but with CR-LF line endings && cr-at-eol unset' ' 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 && diff --git a/t/t4127-apply-same-fn.sh b/t/t4127-apply-same-fn.sh index 77200c0b2d..972946c174 100755 --- a/t/t4127-apply-same-fn.sh +++ b/t/t4127-apply-same-fn.sh @@ -31,7 +31,7 @@ test_expect_success 'apply same filename with independent changes' ' ' 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 && @@ -44,8 +44,8 @@ test_expect_success 'apply same filename with overlapping changes' ' ' 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 && @@ -58,12 +58,12 @@ test_expect_success 'apply same new filename after rename' ' ' 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 && @@ -71,13 +71,13 @@ test_expect_success 'apply same old filename after rename -- should fail.' ' ' 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 && diff --git a/t/t4130-apply-criss-cross-rename.sh b/t/t4130-apply-criss-cross-rename.sh index 7cfa2d6287..d173acde0f 100755 --- a/t/t4130-apply-criss-cross-rename.sh +++ b/t/t4130-apply-criss-cross-rename.sh @@ -44,7 +44,7 @@ test_expect_success 'criss-cross rename' ' 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 && diff --git a/t/t4132-apply-removal.sh b/t/t4132-apply-removal.sh index bb1ffe3b6c..a2bc1cd37d 100755 --- a/t/t4132-apply-removal.sh +++ b/t/t4132-apply-removal.sh @@ -30,6 +30,7 @@ test_expect_success setup ' epocWest="1969-12-31 16:00:00.000000000 -0800" && epocGMT="1970-01-01 00:00:00.000000000 +0000" && epocEast="1970-01-01 09:00:00.000000000 +0900" && + epocWest2="1969-12-31 16:00:00 -08:00" && sed -e "s/TS0/$epocWest/" -e "s/TS1/$timeWest/" <c >createWest.patch && sed -e "s/TS0/$epocEast/" -e "s/TS1/$timeEast/" <c >createEast.patch && @@ -46,6 +47,7 @@ test_expect_success setup ' sed -e "s/TS0/$timeWest/" -e "s/TS1/$epocWest/" <d >removeWest.patch && sed -e "s/TS0/$timeEast/" -e "s/TS1/$epocEast/" <d >removeEast.patch && sed -e "s/TS0/$timeGMT/" -e "s/TS1/$epocGMT/" <d >removeGMT.patch && + sed -e "s/TS0/$timeWest/" -e "s/TS1/$epocWest2/" <d >removeWest2.patch && echo something >something && >empty diff --git a/t/t4133-apply-filenames.sh b/t/t4133-apply-filenames.sh index 34218071b6..94da99075c 100755 --- a/t/t4133-apply-filenames.sh +++ b/t/t4133-apply-filenames.sh @@ -8,7 +8,7 @@ test_description='git apply filename consistency check' . ./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 @@ -29,9 +29,9 @@ EOF ' 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 ' diff --git a/t/t4134-apply-submodule.sh b/t/t4134-apply-submodule.sh index 1b82f93cff..0043930ca6 100755 --- a/t/t4134-apply-submodule.sh +++ b/t/t4134-apply-submodule.sh @@ -8,7 +8,7 @@ test_description='git apply submodule tests' . ./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/t4135-apply-weird-filenames.sh b/t/t4135-apply-weird-filenames.sh index 1e5aad57ab..bf5dc57286 100755 --- a/t/t4135-apply-weird-filenames.sh +++ b/t/t4135-apply-weird-filenames.sh @@ -72,4 +72,20 @@ test_expect_success 'whitespace-damaged traditional patch' ' test_cmp expected postimage.txt ' +test_expect_success 'traditional patch with colon in timezone' ' + echo postimage >expected && + reset_preimage && + rm -f "post image.txt" && + git apply "$vector/funny-tz.diff" && + test_cmp expected "post image.txt" +' + +test_expect_success 'traditional, whitespace-damaged, colon in timezone' ' + echo postimage >expected && + reset_preimage && + rm -f "post image.txt" && + git apply "$vector/damaged-tz.diff" && + test_cmp expected "post image.txt" +' + test_done diff --git a/t/t4135/damaged-tz.diff b/t/t4135/damaged-tz.diff new file mode 100644 index 0000000000..07aaf08370 --- /dev/null +++ b/t/t4135/damaged-tz.diff @@ -0,0 +1,5 @@ +diff -urN -X /usr/people/jes/exclude-linux linux-2.6.12-rc2-mm3-vanilla/post image.txt linux-2.6.12-rc2-mm3/post image.txt +--- linux-2.6.12-rc2-mm3-vanilla/post image.txt 1969-12-31 16:00:00 -08:00 ++++ linux-2.6.12-rc2-mm3/post image.txt 2005-04-12 02:14:06 -07:00 +@@ -0,0 +1 @@ ++postimage diff --git a/t/t4135/funny-tz.diff b/t/t4135/funny-tz.diff new file mode 100644 index 0000000000..998e3a867e --- /dev/null +++ b/t/t4135/funny-tz.diff @@ -0,0 +1,5 @@ +diff -urN -X /usr/people/jes/exclude-linux linux-2.6.12-rc2-mm3-vanilla/post image.txt linux-2.6.12-rc2-mm3/post image.txt +--- linux-2.6.12-rc2-mm3-vanilla/post image.txt 1969-12-31 16:00:00 -08:00 ++++ linux-2.6.12-rc2-mm3/post image.txt 2005-04-12 02:14:06 -07:00 +@@ -0,0 +1 @@ ++postimage diff --git a/t/t4150-am.sh b/t/t4150-am.sh index 1c3d8ed548..850fc96d1f 100755 --- a/t/t4150-am.sh +++ b/t/t4150-am.sh @@ -219,7 +219,7 @@ test_expect_success 'am stays in branch' ' 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/t4151-am-abort.sh b/t/t4151-am-abort.sh index b55c411788..c95c4ccc39 100755 --- a/t/t4151-am-abort.sh +++ b/t/t4151-am-abort.sh @@ -62,4 +62,13 @@ do done +test_expect_success 'am --abort will keep the local commits intact' ' + test_must_fail git am 0004-*.patch && + test_commit unrelated && + git rev-parse HEAD >expect && + git am --abort && + git rev-parse HEAD >actual && + test_cmp expect actual +' + test_done diff --git a/t/t4201-shortlog.sh b/t/t4201-shortlog.sh index cdb70b4b33..6872ba1a42 100755 --- a/t/t4201-shortlog.sh +++ b/t/t4201-shortlog.sh @@ -35,7 +35,7 @@ test_expect_success 'setup' ' 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 2e51356947..2fcc31a6f3 100755 --- a/t/t4202-log.sh +++ b/t/t4202-log.sh @@ -191,7 +191,7 @@ test_expect_success 'git show <commits> leaves list of commits as given' ' test_expect_success 'setup case sensitivity tests' ' echo case >one && test_tick && - git add one + git add one && git commit -a -m Second ' @@ -341,7 +341,7 @@ test_expect_success 'set up more tangled history' ' 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 ' @@ -393,7 +393,7 @@ test_expect_success 'log --graph with merge' ' ' 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 && @@ -422,6 +422,15 @@ test_expect_success 'log.decorate configuration' ' test_cmp expect.full actual && git config --unset-all log.decorate && + git config log.decorate 1 && + git log --oneline >actual && + test_cmp expect.short actual && + git log --oneline --decorate=full >actual && + test_cmp expect.full actual && + git log --oneline --decorate=no >actual && + test_cmp expect.none actual && + + git config --unset-all log.decorate && git config log.decorate short && git log --oneline >actual && test_cmp expect.short actual && diff --git a/t/t4203-mailmap.sh b/t/t4203-mailmap.sh index 9a7d1b4466..e818de6ddd 100755 --- a/t/t4203-mailmap.sh +++ b/t/t4203-mailmap.sh @@ -4,6 +4,14 @@ test_description='.mailmap configurations' . ./test-lib.sh +fuzz_blame () { + sed " + s/$_x05[0-9a-f][0-9a-f][0-9a-f]/OBJID/g + s/$_x05[0-9a-f][0-9a-f]/OBJI/g + s/[-0-9]\{10\} [:0-9]\{8\} [-+][0-9]\{4\}/DATE/g + " "$@" +} + test_expect_success setup ' echo one >one && git add one && @@ -11,6 +19,7 @@ test_expect_success setup ' git commit -m initial && echo two >>one && git add one && + test_tick && git commit --author "nick1 <bugs@company.xx>" -m second ' @@ -54,7 +63,7 @@ Repo Guy (1): EOF test_expect_success 'mailmap.file set' ' - mkdir internal_mailmap && + mkdir -p internal_mailmap && echo "Internal Guy <bugs@company.xx>" > internal_mailmap/.mailmap && git config mailmap.file internal_mailmap/.mailmap && git shortlog HEAD >actual && @@ -93,6 +102,40 @@ test_expect_success 'mailmap.file non-existant' ' ' cat >expect <<\EOF +Internal Guy (1): + second + +Repo Guy (1): + initial + +EOF + +test_expect_success 'name entry after email entry' ' + mkdir -p internal_mailmap && + echo "<bugs@company.xy> <bugs@company.xx>" >internal_mailmap/.mailmap && + echo "Internal Guy <bugs@company.xx>" >>internal_mailmap/.mailmap && + git shortlog HEAD >actual && + test_cmp expect actual +' + +cat >expect <<\EOF +Internal Guy (1): + second + +Repo Guy (1): + initial + +EOF + +test_expect_success 'name entry after email entry, case-insensitive' ' + mkdir -p internal_mailmap && + echo "<bugs@company.xy> <bugs@company.xx>" >internal_mailmap/.mailmap && + echo "Internal Guy <BUGS@Company.xx>" >>internal_mailmap/.mailmap && + git shortlog HEAD >actual && + test_cmp expect actual +' + +cat >expect <<\EOF A U Thor (1): initial @@ -101,7 +144,7 @@ nick1 (1): EOF test_expect_success 'No mailmap files, but configured' ' - rm .mailmap && + rm -f .mailmap internal_mailmap/.mailmap && git shortlog HEAD >actual && test_cmp expect actual ' @@ -153,7 +196,7 @@ test_expect_success 'Shortlog output (complex mapping)' ' test_tick && git commit --author "CTO <cto@coompany.xx>" -m seventh && - mkdir internal_mailmap && + mkdir -p internal_mailmap && echo "Committed <committer@example.com>" > internal_mailmap/.mailmap && echo "<cto@company.xx> <cto@coompany.xx>" >> internal_mailmap/.mailmap && echo "Some Dude <some@dude.xx> nick1 <bugs@company.xx>" >> internal_mailmap/.mailmap && @@ -198,18 +241,18 @@ test_expect_success 'Log output (complex mapping)' ' # git blame cat >expect <<\EOF -^3a2fdcb (A U Thor 2005-04-07 15:13:13 -0700 1) one -7de6f99b (Some Dude 2005-04-07 15:13:13 -0700 2) two -5815879d (Other Author 2005-04-07 15:14:13 -0700 3) three -ff859d96 (Other Author 2005-04-07 15:15:13 -0700 4) four -5ab6d4fa (Santa Claus 2005-04-07 15:16:13 -0700 5) five -38a42d8b (Santa Claus 2005-04-07 15:17:13 -0700 6) six -8ddc0386 (CTO 2005-04-07 15:18:13 -0700 7) seven +^OBJI (A U Thor DATE 1) one +OBJID (Some Dude DATE 2) two +OBJID (Other Author DATE 3) three +OBJID (Other Author DATE 4) four +OBJID (Santa Claus DATE 5) five +OBJID (Santa Claus DATE 6) six +OBJID (CTO DATE 7) seven EOF - test_expect_success 'Blame output (complex mapping)' ' git blame one >actual && - test_cmp expect actual + fuzz_blame actual >actual.fuzz && + test_cmp expect actual.fuzz ' test_done diff --git a/t/t4204-patch-id.sh b/t/t4204-patch-id.sh index 68e2652814..d2c930de87 100755 --- a/t/t4204-patch-id.sh +++ b/t/t4204-patch-id.sh @@ -63,4 +63,40 @@ test_expect_success 'patch-id supports git-format-patch MIME output' ' 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 f603c1b133..e758e634a3 100755 --- a/t/t4252-am-options.sh +++ b/t/t4252-am-options.sh @@ -59,7 +59,7 @@ test_expect_success 'interrupted am --directory="frotz nitfol"' ' ' 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 27bfba55bd..cff1b3e050 100755 --- a/t/t5000-tar-tree.sh +++ b/t/t5000-tar-tree.sh @@ -94,7 +94,7 @@ test_expect_success 'git archive with --output' \ '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 bbb9c1251d..602806d09c 100755 --- a/t/t5300-pack-object.sh +++ b/t/t5300-pack-object.sh @@ -12,7 +12,7 @@ TRASH=`pwd` 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 && diff --git a/t/t5301-sliding-window.sh b/t/t5301-sliding-window.sh index 0a24e61ff9..2fc5af6007 100755 --- a/t/t5301-sliding-window.sh +++ b/t/t5301-sliding-window.sh @@ -8,7 +8,7 @@ test_description='mmap sliding window tests' test_expect_success \ 'setup' \ - 'rm -f .git/index* + 'rm -f .git/index* && for i in a b c do echo $i >$i && @@ -48,7 +48,7 @@ test_expect_success \ 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 fb3a270822..b34ea93a80 100755 --- a/t/t5302-pack-index.sh +++ b/t/t5302-pack-index.sh @@ -8,7 +8,7 @@ test_description='pack index with 64-bit offsets and object CRC' 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 5bcf0b867a..b0b2684a1f 100755 --- a/t/t5400-send-pack.sh +++ b/t/t5400-send-pack.sh @@ -129,7 +129,7 @@ test_expect_success 'denyNonFastforwards trumps --force' ' 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/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh index 552da65a61..baa670cea5 100755 --- a/t/t5407-post-rewrite-hook.sh +++ b/t/t5407-post-rewrite-hook.sh @@ -10,7 +10,11 @@ test_expect_success 'setup' ' test_commit A foo A && test_commit B foo B && test_commit C foo C && - test_commit D foo D + test_commit D foo D && + git checkout A^0 && + test_commit E bar E && + test_commit F foo F && + git checkout master ' mkdir .git/hooks @@ -79,6 +83,18 @@ EOF verify_hook_input ' +test_expect_success 'git rebase --skip the last one' ' + git reset --hard F && + clear_hook_input && + test_must_fail git rebase --onto D A && + git rebase --skip && + echo rebase >expected.args && + cat >expected.data <<EOF && +$(git rev-parse E) $(git rev-parse HEAD) +EOF + verify_hook_input +' + test_expect_success 'git rebase -m' ' git reset --hard D && clear_hook_input && diff --git a/t/t5500-fetch-pack.sh b/t/t5500-fetch-pack.sh index 18376d6608..bafcca765e 100755 --- a/t/t5500-fetch-pack.sh +++ b/t/t5500-fetch-pack.sh @@ -91,7 +91,7 @@ test_expect_success 'setup' ' 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 1037a723fe..7a46cbdbe6 100755 --- a/t/t5502-quickfetch.sh +++ b/t/t5502-quickfetch.sh @@ -57,7 +57,7 @@ test_expect_success 'copy commit and tree but not blob by hand' ' 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 8a298a655f..60de2d6ede 100755 --- a/t/t5503-tagfollow.sh +++ b/t/t5503-tagfollow.sh @@ -4,14 +4,9 @@ test_description='test automatic tag following' . ./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: # @@ -54,7 +49,7 @@ EOF ' 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 && @@ -87,7 +82,7 @@ EOF ' 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 && @@ -126,7 +121,7 @@ EOF ' 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 5d1c66ea71..d189add2d0 100755 --- a/t/t5505-remote.sh +++ b/t/t5505-remote.sh @@ -107,16 +107,18 @@ test_expect_success 'remove remote' ' ) ' -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 9a884751ec..7e433b179f 100755 --- a/t/t5510-fetch.sh +++ b/t/t5510-fetch.sh @@ -119,7 +119,7 @@ test_expect_success 'fetch must not resolve short tag name' ' 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 9e7486274b..65d1e05bd6 100755 --- a/t/t5513-fetch-track.sh +++ b/t/t5513-fetch-track.sh @@ -1,6 +1,6 @@ #!/bin/sh -test_description='fetch follows remote tracking branches correctly' +test_description='fetch follows remote-tracking branches correctly' . ./test-lib.sh diff --git a/t/t5514-fetch-multiple.sh b/t/t5514-fetch-multiple.sh index b73733219d..227dd56137 100755 --- a/t/t5514-fetch-multiple.sh +++ b/t/t5514-fetch-multiple.sh @@ -27,7 +27,7 @@ test_expect_success setup ' ( 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 b11da79c9c..d73731e644 100755 --- a/t/t5516-fetch-push.sh +++ b/t/t5516-fetch-push.sh @@ -586,7 +586,7 @@ test_expect_success 'push --delete refuses src:dest refspecs' ' ' 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 && @@ -597,7 +597,7 @@ test_expect_success 'warn on push to HEAD of non-bare repository' ' ' test_expect_success 'deny push to HEAD of non-bare repository' ' - mk_test heads/master + mk_test heads/master && ( cd testrepo && git checkout master && @@ -607,7 +607,7 @@ test_expect_success 'deny push to HEAD of non-bare repository' ' ' test_expect_success 'allow push to HEAD of bare repository (bare)' ' - mk_test heads/master + mk_test heads/master && ( cd testrepo && git checkout master && @@ -619,7 +619,7 @@ test_expect_success 'allow push to HEAD of bare repository (bare)' ' ' 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 && diff --git a/t/t5519-push-alternates.sh b/t/t5519-push-alternates.sh index 96be5236a2..c00c9b071d 100755 --- a/t/t5519-push-alternates.sh +++ b/t/t5519-push-alternates.sh @@ -123,7 +123,7 @@ test_expect_success 'bob works and pushes again' ' ( 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 0b489f5b12..0470a81be0 100755 --- a/t/t5520-pull.sh +++ b/t/t5520-pull.sh @@ -222,4 +222,11 @@ test_expect_success 'git pull --rebase does not reapply old patches' ' ) ' +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 diff --git a/t/t5523-push-upstream.sh b/t/t5523-push-upstream.sh index 00da70763b..c229fe68f1 100755 --- a/t/t5523-push-upstream.sh +++ b/t/t5523-push-upstream.sh @@ -2,9 +2,14 @@ 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 ' @@ -66,4 +71,41 @@ test_expect_success 'push -u HEAD' ' 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 new file mode 100755 index 0000000000..a5f458533f --- /dev/null +++ b/t/t5526-fetch-submodules.sh @@ -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 diff --git a/t/t5531-deep-submodule-push.sh b/t/t5531-deep-submodule-push.sh index 65d8d474bc..faa2e96337 100755 --- a/t/t5531-deep-submodule-push.sh +++ b/t/t5531-deep-submodule-push.sh @@ -6,7 +6,7 @@ test_description='unpack-objects' 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 && ( diff --git a/t/t5550-http-fetch.sh b/t/t5550-http-fetch.sh index 2fb48d09ed..a1883ca6b6 100755 --- a/t/t5550-http-fetch.sh +++ b/t/t5550-http-fetch.sh @@ -30,18 +30,37 @@ test_expect_success 'create http-accessible bare repository' ' ' test_expect_success 'clone http repository' ' - git clone $HTTPD_URL/dumb/repo.git clone && + git clone $HTTPD_URL/dumb/repo.git clone-tmpl && + cp -R clone-tmpl clone && test_cmp file clone/file ' +test_expect_success 'clone http repository with authentication' ' + mkdir "$HTTPD_DOCUMENT_ROOT_PATH/auth/" && + cp -Rf "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" "$HTTPD_DOCUMENT_ROOT_PATH/auth/repo.git" && + git clone $AUTH_HTTPD_URL/auth/repo.git clone-auth && + test_cmp file clone-auth/file +' + test_expect_success 'fetch changes via http' ' echo content >>file && git commit -a -m two && - git push public + git push public && (cd clone && git pull) && test_cmp file clone/file ' +test_expect_success 'fetch changes via manual http-fetch' ' + cp -R clone-tmpl clone2 && + + HEAD=$(git rev-parse --verify HEAD) && + (cd clone2 && + git http-fetch -a -w heads/master-new $HEAD $(git config remote.origin.url) && + git checkout master-new && + test $HEAD = $(git rev-parse --verify HEAD)) && + test_cmp file clone2/file +' + test_expect_success 'http remote detects correct HEAD' ' git push public master:other && (cd clone && diff --git a/t/t5551-http-fetch.sh b/t/t5551-http-fetch.sh index fd19121372..26d355725f 100755 --- a/t/t5551-http-fetch.sh +++ b/t/t5551-http-fetch.sh @@ -101,5 +101,13 @@ test_expect_success 'used upload-pack service' ' test_cmp exp act ' +test_expect_success 'follow redirects (301)' ' + git clone $HTTPD_URL/smart-redir-perm/repo.git --quiet repo-p +' + +test_expect_success 'follow redirects (302)' ' + git clone $HTTPD_URL/smart-redir-temp/repo.git --quiet repo-t +' + stop_httpd test_done diff --git a/t/t5560-http-backend-noserver.sh b/t/t5560-http-backend-noserver.sh index 44885b850c..0ad7ce07c4 100755 --- a/t/t5560-http-backend-noserver.sh +++ b/t/t5560-http-backend-noserver.sh @@ -5,16 +5,17 @@ test_description='test git-http-backend-noserver' 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 } GET() { - export REQUEST_METHOD="GET" && + REQUEST_METHOD="GET" && export REQUEST_METHOD && run_backend "/repo.git/$1" && unset REQUEST_METHOD && if ! grep "Status" act.out >act @@ -26,8 +27,8 @@ GET() { } POST() { - export REQUEST_METHOD="POST" && - export CONTENT_TYPE="application/x-$1-request" && + REQUEST_METHOD="POST" && export REQUEST_METHOD && + CONTENT_TYPE="application/x-$1-request" && export CONTENT_TYPE && run_backend "/repo.git/$1" "$2" && unset REQUEST_METHOD && unset CONTENT_TYPE && @@ -46,7 +47,7 @@ log_div() { . "$TEST_DIRECTORY"/t556x_common expect_aliased() { - export REQUEST_METHOD="GET" && + REQUEST_METHOD="GET" && export REQUEST_METHOD && if test $1 = 0; then run_backend "$2" else diff --git a/t/t556x_common b/t/t556x_common index be024e551c..82926cfdb7 100755 --- a/t/t556x_common +++ b/t/t556x_common @@ -50,72 +50,72 @@ get_static_files() { } SMART=smart -export GIT_HTTP_EXPORT_ALL=1 +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 ) && get_static_files "200 OK" ' SMART=smart -export GIT_HTTP_EXPORT_ALL=1 +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" diff --git a/t/t5602-clone-remote-exec.sh b/t/t5602-clone-remote-exec.sh index deffdaee49..3f353d99e8 100755 --- a/t/t5602-clone-remote-exec.sh +++ b/t/t5602-clone-remote-exec.sh @@ -5,21 +5,29 @@ test_description=clone . ./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 8b4c356cd2..6972258b27 100755 --- a/t/t5701-clone-local.sh +++ b/t/t5701-clone-local.sh @@ -10,11 +10,11 @@ test_expect_success 'preparing origin repository' ' 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 ' @@ -112,7 +112,7 @@ test_expect_success 'bundle clone with nonexistent HEAD' ' 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/t6001-rev-list-graft.sh b/t/t6001-rev-list-graft.sh index fc57e7d3fd..8efcd13079 100755 --- a/t/t6001-rev-list-graft.sh +++ b/t/t6001-rev-list-graft.sh @@ -90,22 +90,22 @@ check () { 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 " diff --git a/t/t6006-rev-list-format.sh b/t/t6006-rev-list-format.sh index cccacd4add..d918cc02d0 100755 --- a/t/t6006-rev-list-format.sh +++ b/t/t6006-rev-list-format.sh @@ -162,6 +162,14 @@ commit 131a310eb913d107dd3c09a65d1651175898735d commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873 EOF +test_expect_success '%x00 shows NUL' ' + echo >expect commit f58db70b055c5718631e5c61528b28b12090cdea && + echo >>expect fooQbar && + git rev-list -1 --format=foo%x00bar HEAD >actual.nul && + nul_to_q <actual.nul >actual && + test_cmp expect actual +' + test_expect_success '%ad respects --date=' ' echo 2005-04-07 >expect.ad-short && git log -1 --date=short --pretty=tformat:%ad >output.ad-short master && diff --git a/t/t6009-rev-list-parent.sh b/t/t6009-rev-list-parent.sh index c8a96a9a99..52f7b277ce 100755 --- a/t/t6009-rev-list-parent.sh +++ b/t/t6009-rev-list-parent.sh @@ -18,7 +18,7 @@ test_expect_success setup ' 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 62197a3d35..082032edc3 100755 --- a/t/t6010-merge-base.sh +++ b/t/t6010-merge-base.sh @@ -131,7 +131,7 @@ test_expect_success 'unsynchronized clocks' ' 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 27fd52b7be..f7181d1d6a 100755 --- a/t/t6016-rev-list-graph-simplify-history.sh +++ b/t/t6016-rev-list-graph-simplify-history.sh @@ -29,7 +29,7 @@ test_expect_success 'set up rev-list --graph test' ' # 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 && @@ -39,7 +39,7 @@ test_expect_success 'set up rev-list --graph test' ' test_commit C4 bar.txt && git checkout A && git merge -s ours C && - git tag A6 + git tag A6 && test_commit A7 bar.txt && @@ -90,7 +90,7 @@ test_expect_success '--graph --all' ' # 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 && @@ -116,12 +116,15 @@ test_expect_success '--graph --simplify-by-decoration' ' 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 && @@ -143,9 +146,6 @@ test_expect_success '--graph --simplify-by-decoration prune branch B' ' 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 && @@ -163,9 +163,6 @@ test_expect_success '--graph --full-history -- bar.txt' ' 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 && @@ -181,9 +178,6 @@ test_expect_success '--graph --full-history --simplify-merges -- bar.txt' ' 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 && @@ -196,9 +190,6 @@ test_expect_success '--graph -- bar.txt' ' 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 490d397114..eec8f4e3ed 100755 --- a/t/t6020-merge-df.sh +++ b/t/t6020-merge-df.sh @@ -6,21 +6,26 @@ 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 && @@ -45,4 +50,51 @@ test_expect_success 'F/D conflict' ' 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 diff --git a/t/t6022-merge-rename.sh b/t/t6022-merge-rename.sh index b66544b76d..1ed259d864 100755 --- a/t/t6022-merge-rename.sh +++ b/t/t6022-merge-rename.sh @@ -3,6 +3,11 @@ 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 && @@ -94,245 +99,147 @@ git checkout master' 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" && @@ -341,4 +248,365 @@ test_expect_success 'merge of identical changes in a renamed file' ' 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 diff --git a/t/t6023-merge-file.sh b/t/t6023-merge-file.sh index d486d73994..d9f343942c 100755 --- a/t/t6023-merge-file.sh +++ b/t/t6023-merge-file.sh @@ -64,6 +64,14 @@ cp new1.txt test.txt test_expect_success "merge without conflict" \ "git merge-file test.txt orig.txt new2.txt" +test_expect_success 'works in subdirectory' ' + mkdir dir && + cp new1.txt dir/a.txt && + cp orig.txt dir/o.txt && + cp new2.txt dir/b.txt && + ( cd dir && git merge-file a.txt o.txt b.txt ) +' + cp new1.txt test.txt test_expect_success "merge without conflict (--quiet)" \ "git merge-file --quiet test.txt orig.txt new2.txt" diff --git a/t/t6024-recursive-merge.sh b/t/t6024-recursive-merge.sh index b3fbf659c0..755d30ce2a 100755 --- a/t/t6024-recursive-merge.sh +++ b/t/t6024-recursive-merge.sh @@ -104,7 +104,7 @@ test_expect_success 'mark rename/delete as unmerged' ' 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 && diff --git a/t/t6029-merge-subtree.sh b/t/t6029-merge-subtree.sh index 3900d9f61f..73fc240e85 100755 --- a/t/t6029-merge-subtree.sh +++ b/t/t6029-merge-subtree.sh @@ -6,7 +6,7 @@ test_description='subtree merge strategy' 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 && diff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh index 3b042aacd6..b5063b6fe6 100755 --- a/t/t6030-bisect-porcelain.sh +++ b/t/t6030-bisect-porcelain.sh @@ -517,13 +517,13 @@ test_expect_success '"parallel" side branch creation' ' 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) ' diff --git a/t/t6032-merge-large-rename.sh b/t/t6032-merge-large-rename.sh index eac5ebac24..fdb6c25371 100755 --- a/t/t6032-merge-large-rename.sh +++ b/t/t6032-merge-large-rename.sh @@ -70,4 +70,34 @@ test_expect_success 'set merge.renamelimit to 5' ' 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 diff --git a/t/t6035-merge-dir-to-symlink.sh b/t/t6035-merge-dir-to-symlink.sh index 92e02d5d77..2599ae50eb 100755 --- a/t/t6035-merge-dir-to-symlink.sh +++ b/t/t6035-merge-dir-to-symlink.sh @@ -17,13 +17,21 @@ test_expect_success SYMLINKS 'create a commit where dir a/b changed to symlink' git commit -m "dir to symlink" ' -test_expect_success SYMLINKS 'keep a/b-2/c/d across checkout' ' +test_expect_success SYMLINKS 'checkout does not clobber untracked symlink' ' git checkout HEAD^0 && git reset --hard master && git rm --cached a/b && git commit -m "untracked symlink remains" && - git checkout start^0 && - test -f a/b-2/c/d + test_must_fail git checkout start^0 +' + +test_expect_success SYMLINKS 'a/b-2/c/d is kept when clobbering symlink b' ' + git checkout HEAD^0 && + git reset --hard master && + git rm --cached a/b && + git commit -m "untracked symlink remains" && + git checkout -f start^0 && + test -f a/b-2/c/d ' test_expect_success SYMLINKS 'checkout should not have deleted a/b-2/c/d' ' diff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh index b874141658..871577d90c 100755 --- a/t/t6036-recursive-corner-cases.sh +++ b/t/t6036-recursive-corner-cases.sh @@ -14,7 +14,80 @@ test_description='recursive merge corner cases' # 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 @@ -30,6 +103,8 @@ test_expect_success setup ' 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 && @@ -45,11 +120,115 @@ test_expect_success setup ' 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 diff --git a/t/t6038-merge-text-auto.sh b/t/t6038-merge-text-auto.sh index 52d0dc4bb8..d9c2d386dd 100755 --- a/t/t6038-merge-text-auto.sh +++ b/t/t6038-merge-text-auto.sh @@ -14,6 +14,8 @@ test_description='CRLF merge conflict across text=auto change . ./test-lib.sh +test_have_prereq SED_STRIPS_CR && SED_OPTIONS=-b + test_expect_success setup ' git config core.autocrlf false && @@ -60,7 +62,7 @@ test_expect_success setup ' test_expect_success 'set up fuzz_conflict() helper' ' fuzz_conflict() { - sed -e "s/^\([<>=]......\) .*/\1/" "$@" + sed $SED_OPTIONS -e "s/^\([<>=]......\) .*/\1/" "$@" } ' diff --git a/t/t6040-tracking-info.sh b/t/t6040-tracking-info.sh index 1785e178a4..cb85132642 100755 --- a/t/t6040-tracking-info.sh +++ b/t/t6040-tracking-info.sh @@ -60,7 +60,7 @@ test_expect_success 'checkout' ' test_expect_success 'checkout with local tracked branch' ' git checkout master && - git checkout follower >actual + git checkout follower >actual && grep "is ahead of" actual ' @@ -74,20 +74,20 @@ test_expect_success 'status' ' grep "have 1 and 1 different" actual ' -test_expect_success 'status when tracking lightweight tags' ' +test_expect_success 'fail to track lightweight tags' ' git checkout master && git tag light && - git branch --track lighttrack light >actual && - grep "set up to track" actual && - git checkout lighttrack + test_must_fail git branch --track lighttrack light >actual && + test_must_fail grep "set up to track" actual && + test_must_fail git checkout lighttrack ' -test_expect_success 'status when tracking annotated tags' ' +test_expect_success 'fail to track annotated tags' ' git checkout master && git tag -m heavy heavy && - git branch --track heavytrack heavy >actual && - grep "set up to track" actual && - git checkout heavytrack + test_must_fail git branch --track heavytrack heavy >actual && + test_must_fail grep "set up to track" actual && + test_must_fail git checkout heavytrack ' test_expect_success 'setup tracking with branch --set-upstream on existing branch' ' diff --git a/t/t6050-replace.sh b/t/t6050-replace.sh index c907523e7e..ae2194e07d 100755 --- a/t/t6050-replace.sh +++ b/t/t6050-replace.sh @@ -53,7 +53,7 @@ test_expect_success 'set up buggy branch' ' 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 && @@ -205,9 +205,16 @@ test_expect_success 'fetch branch with replacement' ' git branch tofetch $HASH6 && ( cd clone_dir && - git fetch origin refs/heads/tofetch:refs/heads/parallel3 - git log --pretty=oneline parallel3 | grep $PARA3 - git show $PARA3 | grep "A U Thor" + git fetch origin refs/heads/tofetch:refs/heads/parallel3 && + git log --pretty=oneline parallel3 > output.txt && + ! grep $PARA3 output.txt && + git show $PARA3 > para3.txt && + grep "A U Thor" para3.txt && + git fetch origin "refs/replace/*:refs/replace/*" && + git log --pretty=oneline parallel3 > output.txt && + grep $PARA3 output.txt && + git show $PARA3 > para3.txt && + grep "O Thor" para3.txt ) ' diff --git a/t/t6110-rev-list-sparse.sh b/t/t6110-rev-list-sparse.sh new file mode 100755 index 0000000000..2a267e84cd --- /dev/null +++ b/t/t6110-rev-list-sparse.sh @@ -0,0 +1,27 @@ +#!/bin/sh + +test_description='operations that cull histories in unusual ways' +. ./test-lib.sh + +test_commit () { + echo "$1" >"$1.file" && + git add "$1.file" && + test_tick && + git commit -m "$1" +} + +test_expect_success setup ' + test_commit A && + test_commit B && + test_commit C && + git checkout -b side HEAD^ && + test_commit D && + test_commit E && + git merge master +' + +test_expect_success 'rev-list --first-parent --boundary' ' + git rev-list --first-parent --boundary HEAD^.. +' + +test_done diff --git a/t/t6200-fmt-merge-msg.sh b/t/t6200-fmt-merge-msg.sh index 71f6cad3c2..9a16806921 100755 --- a/t/t6200-fmt-merge-msg.sh +++ b/t/t6200-fmt-merge-msg.sh @@ -129,6 +129,97 @@ test_expect_success '[merge] summary/log configuration' ' 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 new file mode 100755 index 0000000000..82f3639937 --- /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 65a35d94a0..a845b154e4 100755 --- a/t/t7001-mv.sh +++ b/t/t7001-mv.sh @@ -61,7 +61,7 @@ test_expect_success \ 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' @@ -207,7 +207,7 @@ test_expect_success 'git mv should not change sha1 of moved cache entry' ' 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 ac943f5eee..3e7baaf89f 100755 --- a/t/t7004-tag.sh +++ b/t/t7004-tag.sh @@ -1030,6 +1030,72 @@ test_expect_success GPG \ test_cmp expect actual ' +# usage with rfc1991 signatures +echo "rfc1991" > gpghome/gpg.conf +get_tag_header rfc1991-signed-tag $commit commit $time >expect +echo "RFC1991 signed tag" >>expect +echo '-----BEGIN PGP MESSAGE-----' >>expect +test_expect_success GPG \ + 'creating a signed tag with rfc1991' ' + git tag -s -m "RFC1991 signed tag" rfc1991-signed-tag $commit && + get_tag_msg rfc1991-signed-tag >actual && + test_cmp expect actual +' + +cat >fakeeditor <<'EOF' +#!/bin/sh +cp "$1" actual +EOF +chmod +x fakeeditor + +test_expect_success GPG \ + 'reediting a signed tag body omits signature' ' + echo "RFC1991 signed tag" >expect && + GIT_EDITOR=./fakeeditor git tag -f -s rfc1991-signed-tag $commit && + test_cmp expect actual +' + +test_expect_success GPG \ + 'verifying rfc1991 signature' ' + git tag -v rfc1991-signed-tag +' + +test_expect_success GPG \ + 'list tag with rfc1991 signature' ' + echo "rfc1991-signed-tag RFC1991 signed tag" >expect && + git tag -l -n1 rfc1991-signed-tag >actual && + test_cmp expect actual && + git tag -l -n2 rfc1991-signed-tag >actual && + test_cmp expect actual && + git tag -l -n999 rfc1991-signed-tag >actual && + test_cmp expect actual +' + +rm -f gpghome/gpg.conf + +test_expect_success GPG \ + 'verifying rfc1991 signature without --rfc1991' ' + git tag -v rfc1991-signed-tag +' + +test_expect_success GPG \ + 'list tag with rfc1991 signature without --rfc1991' ' + echo "rfc1991-signed-tag RFC1991 signed tag" >expect && + git tag -l -n1 rfc1991-signed-tag >actual && + test_cmp expect actual && + git tag -l -n2 rfc1991-signed-tag >actual && + test_cmp expect actual && + git tag -l -n999 rfc1991-signed-tag >actual && + test_cmp expect actual +' + +test_expect_success GPG \ + 'reediting a signed tag body omits signature' ' + echo "RFC1991 signed tag" >expect && + GIT_EDITOR=./fakeeditor git tag -f -s rfc1991-signed-tag $commit && + test_cmp expect actual +' + # try to sign with bad user.signingkey git config user.signingkey BobTheMouse test_expect_success GPG \ @@ -1051,13 +1117,23 @@ test_expect_success \ 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 @@ -1097,7 +1173,7 @@ hash1=$(git rev-parse HEAD) 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 ' @@ -1122,18 +1198,18 @@ 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 " @@ -1142,7 +1218,7 @@ v2.0 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 " @@ -1151,7 +1227,7 @@ cat > expected <<EOF 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 " @@ -1161,7 +1237,7 @@ test_expect_success 'creating simple branch' ' 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 ' @@ -1172,7 +1248,7 @@ 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 " @@ -1186,7 +1262,7 @@ v4.0 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 " @@ -1201,18 +1277,18 @@ v4.0 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 fb744e3c4a..ed7575d0fd 100755 --- a/t/t7006-pager.sh +++ b/t/t7006-pager.sh @@ -4,44 +4,15 @@ test_description='Test automatic use of a pager.' . ./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" && @@ -213,11 +184,6 @@ test_expect_success 'color when writing to a file intended for a pager' ' 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 @@ -253,8 +219,8 @@ parse_args() { 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 && @@ -277,7 +243,7 @@ test_PAGER_overrides() { 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 && @@ -305,7 +271,7 @@ test_core_pager() { 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 && @@ -333,7 +299,7 @@ test_pager_subdir_helper() { 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 && @@ -435,4 +401,33 @@ test_core_pager_subdir expect_success 'git -p shortlog' 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/t7105-reset-patch.sh b/t/t7105-reset-patch.sh index 9891e2c1f5..95fab20361 100755 --- a/t/t7105-reset-patch.sh +++ b/t/t7105-reset-patch.sh @@ -18,7 +18,7 @@ test_expect_success PERL 'setup' ' # 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 @@ -42,14 +42,14 @@ test_expect_success PERL 'git reset -p HEAD^' ' # 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/t7201-co.sh b/t/t7201-co.sh index 1337fa5a22..0c002ab695 100755 --- a/t/t7201-co.sh +++ b/t/t7201-co.sh @@ -408,6 +408,15 @@ test_expect_success 'checkout w/--track from non-branch HEAD fails' ' test "z$(git rev-parse master^0)" = "z$(git rev-parse HEAD)" ' +test_expect_success 'checkout w/--track from tag fails' ' + git checkout master^0 && + test_must_fail git symbolic-ref HEAD && + test_must_fail git checkout --track -b track frotz && + test_must_fail git rev-parse --verify track && + test_must_fail git symbolic-ref HEAD && + test "z$(git rev-parse master^0)" = "z$(git rev-parse HEAD)" +' + test_expect_success 'detach a symbolic link HEAD' ' git checkout master && git config --bool core.prefersymlinkrefs yes && @@ -423,7 +432,6 @@ test_expect_success 'detach a symbolic link HEAD' ' test_expect_success \ 'checkout with --track fakes a sensible -b <name>' ' git update-ref refs/remotes/origin/koala/bear renamer && - git update-ref refs/new/koala/bear renamer && git checkout --track origin/koala/bear && test "refs/heads/koala/bear" = "$(git symbolic-ref HEAD)" && @@ -439,12 +447,6 @@ test_expect_success \ git checkout --track remotes/origin/koala/bear && test "refs/heads/koala/bear" = "$(git symbolic-ref HEAD)" && - test "$(git rev-parse HEAD)" = "$(git rev-parse renamer)" && - - git checkout master && git branch -D koala/bear && - - git checkout --track refs/new/koala/bear && - test "refs/heads/koala/bear" = "$(git symbolic-ref HEAD)" && test "$(git rev-parse HEAD)" = "$(git rev-parse renamer)" ' diff --git a/t/t7300-clean.sh b/t/t7300-clean.sh index 6c776e9bec..02f67b73b7 100755 --- a/t/t7300-clean.sh +++ b/t/t7300-clean.sh @@ -179,11 +179,11 @@ test_expect_success 'git clean -d with prefix and path' ' ' -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 && diff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh index 782b0a3ece..874279e32d 100755 --- a/t/t7400-submodule-basic.sh +++ b/t/t7400-submodule-basic.sh @@ -421,11 +421,67 @@ test_expect_success 'add submodules without specifying an explicit path' ' git commit -m "repo commit 1" ) && git clone --bare repo/ bare.git && - cd addtest && - git submodule add "$submodurl/repo" && - git config -f .gitmodules submodule.repo.path repo && - git submodule add "$submodurl/bare.git" && - git config -f .gitmodules submodule.bare.path bare + ( + cd addtest && + git submodule add "$submodurl/repo" && + git config -f .gitmodules submodule.repo.path repo && + git submodule add "$submodurl/bare.git" && + git config -f .gitmodules submodule.bare.path bare + ) +' + +test_expect_success 'add should fail when path is used by a file' ' + ( + cd addtest && + touch file && + test_must_fail git submodule add "$submodurl/repo" file + ) +' + +test_expect_success 'add should fail when path is used by an existing directory' ' + ( + cd addtest && + mkdir empty-dir && + test_must_fail git submodule add "$submodurl/repo" empty-dir + ) +' + +test_expect_success 'set up for relative path tests' ' + mkdir reltest && + ( + cd reltest && + git init && + mkdir sub && + ( + cd sub && + git init && + test_commit foo + ) && + git add sub && + git config -f .gitmodules submodule.sub.path sub && + git config -f .gitmodules submodule.sub.url ../subrepo && + cp .git/config pristine-.git-config + ) +' + +test_expect_success 'relative path works with URL' ' + ( + cd reltest && + cp pristine-.git-config .git/config && + git config remote.origin.url ssh://hostname/repo && + git submodule init && + test "$(git config submodule.sub.url)" = ssh://hostname/subrepo + ) +' + +test_expect_success 'relative path works with user@host:path' ' + ( + cd reltest && + cp pristine-.git-config .git/config && + git config remote.origin.url user@host:repo && + git submodule init && + test "$(git config submodule.sub.url)" = user@host:subrepo + ) ' test_done diff --git a/t/t7401-submodule-summary.sh b/t/t7401-submodule-summary.sh index 294584452b..7d7fde057b 100755 --- a/t/t7401-submodule-summary.sh +++ b/t/t7401-submodule-summary.sh @@ -37,11 +37,12 @@ head1=$(add_file sm1 foo1 foo2) 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 && @@ -49,20 +50,22 @@ head2=$(add_file sm1 foo3) 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 && @@ -74,19 +77,20 @@ head3=$( 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 @@ -94,17 +98,19 @@ test_expect_success 'modified submodule(backward and forward)' " < 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 && @@ -117,30 +123,33 @@ mv sm1-bak 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 && @@ -148,31 +157,34 @@ test_create_repo 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 && @@ -181,34 +193,37 @@ git add 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' " diff --git a/t/t7403-submodule-sync.sh b/t/t7403-submodule-sync.sh index 02522f9627..e5b19538b0 100755 --- a/t/t7403-submodule-sync.sh +++ b/t/t7403-submodule-sync.sh @@ -23,7 +23,9 @@ test_expect_success setup ' git commit -m "submodule" ) && git clone super super-clone && - (cd super-clone && git submodule update --init) + (cd super-clone && git submodule update --init) && + git clone super empty-clone && + (cd empty-clone && git submodule init) ' test_expect_success 'change submodule' ' @@ -64,4 +66,12 @@ test_expect_success '"git submodule sync" should update submodule URLs' ' ) ' +test_expect_success '"git submodule sync" should update submodule URLs if not yet cloned' ' + (cd empty-clone && + git pull && + git submodule sync && + test -d "$(git config submodule.submodule.url)" + ) +' + test_done diff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh index bfb4975e94..fa9d23aa31 100755 --- a/t/t7406-submodule-update.sh +++ b/t/t7406-submodule-update.sh @@ -203,4 +203,56 @@ test_expect_success 'submodule init picks up merge' ' ) ' +test_expect_success 'submodule update --merge - ignores --merge for new submodules' ' + (cd super && + rm -rf submodule && + git submodule update submodule && + git status -s submodule >expect && + rm -rf submodule && + git submodule update --merge submodule && + git status -s submodule >actual && + test_cmp expect actual + ) +' + +test_expect_success 'submodule update --rebase - ignores --rebase for new submodules' ' + (cd super && + rm -rf submodule && + git submodule update submodule && + git status -s submodule >expect && + rm -rf submodule && + git submodule update --rebase submodule && + git status -s submodule >actual && + test_cmp expect actual + ) +' + +test_expect_success 'submodule update ignores update=merge config for new submodules' ' + (cd super && + rm -rf submodule && + git submodule update submodule && + git status -s submodule >expect && + rm -rf submodule && + git config submodule.submodule.update merge && + git submodule update submodule && + git status -s submodule >actual && + git config --unset submodule.submodule.update && + test_cmp expect actual + ) +' + +test_expect_success 'submodule update ignores update=rebase config for new submodules' ' + (cd super && + rm -rf submodule && + git submodule update submodule && + git status -s submodule >expect && + rm -rf submodule && + git config submodule.submodule.update rebase && + git submodule update submodule && + git status -s submodule >actual && + git config --unset submodule.submodule.update && + test_cmp expect actual + ) +' + test_done diff --git a/t/t7407-submodule-foreach.sh b/t/t7407-submodule-foreach.sh index 905a8baae9..e5be13c271 100755 --- a/t/t7407-submodule-foreach.sh +++ b/t/t7407-submodule-foreach.sh @@ -226,6 +226,25 @@ test_expect_success 'test "status --recursive"' ' 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 && @@ -238,4 +257,39 @@ test_expect_success 'use "git clone --recursive" to checkout all submodules' ' 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 aa9c577e9e..5976f598fc 100755 --- a/t/t7500-commit.sh +++ b/t/t7500-commit.sh @@ -10,7 +10,12 @@ Tests for selected commit options.' . ./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. @@ -23,13 +28,21 @@ test_expect_success 'a basic commit in an empty tree should succeed' ' test_expect_success 'nonexistent template file should return error' ' echo changes >> foo && git add foo && - test_must_fail git commit --template "$PWD"/notexist + ( + GIT_EDITOR="echo hello >\"\$1\"" && + export GIT_EDITOR && + test_must_fail git commit --template "$PWD"/notexist + ) ' test_expect_success 'nonexistent template file in config should return error' ' git config commit.template "$PWD"/notexist && - test_must_fail git commit && - git config --unset commit.template + test_when_finished "git config --unset commit.template" && + ( + GIT_EDITOR="echo hello >\"\$1\"" && + export GIT_EDITOR && + test_must_fail git commit + ) ' # From now on we'll use a template file that exists. @@ -215,4 +228,84 @@ test_expect_success 'Commit a message with --allow-empty-message' ' 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 new file mode 100755 index 0000000000..08db9fdd2e --- /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/t7501-commit.sh b/t/t7501-commit.sh index 8297cb4f1e..8980738c75 100755 --- a/t/t7501-commit.sh +++ b/t/t7501-commit.sh @@ -230,6 +230,10 @@ test_expect_success 'amend commit to fix date' ' ' +test_expect_success 'commit complains about bogus date' ' + test_must_fail git commit --amend --date=10.11.2010 +' + test_expect_success 'sign off (1)' ' echo 1 >positive && diff --git a/t/t7502-commit.sh b/t/t7502-commit.sh index ac2e187a57..50da034cd3 100755 --- a/t/t7502-commit.sh +++ b/t/t7502-commit.sh @@ -252,8 +252,8 @@ test_expect_success 'committer is automatic' ' 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" ) && @@ -390,7 +390,7 @@ try_commit_status_combo () { 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 ' diff --git a/t/t7505-prepare-commit-msg-hook.sh b/t/t7505-prepare-commit-msg-hook.sh index ff189624d4..5b4b694f18 100755 --- a/t/t7505-prepare-commit-msg-hook.sh +++ b/t/t7505-prepare-commit-msg-hook.sh @@ -132,6 +132,18 @@ test_expect_success 'with hook (-c)' ' ' +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 c9300f3c8b..f1dc5c3b6a 100755 --- a/t/t7508-status.sh +++ b/t/t7508-status.sh @@ -7,6 +7,30 @@ test_description='git status' . ./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 && @@ -44,7 +68,7 @@ cat >expect <<\EOF # # 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) # @@ -73,7 +97,7 @@ cat >expect <<\EOF # Changes to be committed: # new file: dir2/added # -# Changed but not updated: +# Changes not staged for commit: # modified: dir1/modified # # Untracked files: @@ -140,7 +164,7 @@ cat >expect <<EOF # # 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) # @@ -167,7 +191,7 @@ cat >expect <<EOF # Changes to be committed: # new file: dir2/added # -# Changed but not updated: +# Changes not staged for commit: # modified: dir1/modified # # Untracked files not listed @@ -202,7 +226,7 @@ cat >expect <<EOF # # 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) # @@ -260,7 +284,7 @@ cat >expect <<EOF # # 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) # @@ -320,7 +344,7 @@ cat >expect <<\EOF # # 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) # @@ -381,18 +405,19 @@ test_expect_success 'status --porcelain ignores relative paths setting' ' 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) # @@ -521,7 +546,7 @@ cat >expect <<\EOF # # 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) # @@ -614,7 +639,7 @@ cat >expect <<EOF # 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) # @@ -673,7 +698,7 @@ cat >expect <<EOF # 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) # @@ -718,7 +743,7 @@ test_expect_success 'status -s 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) # @@ -766,7 +791,7 @@ cat >expect <<EOF # 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) # @@ -819,7 +844,7 @@ cat > expect << EOF # # 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) # @@ -931,7 +956,7 @@ cat > expect << EOF # # 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) @@ -989,7 +1014,7 @@ cat > expect << EOF # # 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 643ab03f99..77b6920029 100755 --- a/t/t7509-commit.sh +++ b/t/t7509-commit.sh @@ -40,7 +40,7 @@ test_expect_success '-C option copies only the message with --reset-author' ' 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 b4f40e4c3a..b147a1bd69 100755 --- a/t/t7600-merge.sh +++ b/t/t7600-merge.sh @@ -144,6 +144,17 @@ test_expect_success 'test option parsing' ' 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 ' diff --git a/t/t7601-merge-pull-config.sh b/t/t7601-merge-pull-config.sh index 7ba94ea99b..b44b293950 100755 --- a/t/t7601-merge-pull-config.sh +++ b/t/t7601-merge-pull-config.sh @@ -114,13 +114,13 @@ test_expect_success 'setup conflicted merge' ' 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 @@ -129,13 +129,13 @@ test_expect_success 'merge picks up the best result' ' 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 diff --git a/t/t7602-merge-octopus-many.sh b/t/t7602-merge-octopus-many.sh index 2746169514..0a46795ae7 100755 --- a/t/t7602-merge-octopus-many.sh +++ b/t/t7602-merge-octopus-many.sh @@ -31,7 +31,7 @@ test_expect_success 'merge c1 with c2, c3, c4, ... c29' ' 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 && diff --git a/t/t7607-merge-overwrite.sh b/t/t7607-merge-overwrite.sh index d82349a6a8..4d5ce4e682 100755 --- a/t/t7607-merge-overwrite.sh +++ b/t/t7607-merge-overwrite.sh @@ -7,48 +7,54 @@ Do not overwrite changes.' . ./test-lib.sh test_expect_success 'setup' ' - echo c0 > c0.c && - git add c0.c && - git commit -m c0 && - git tag c0 && - echo c1 > c1.c && - git add c1.c && - git commit -m c1 && - git tag c1 && + test_commit c0 c0.c && + test_commit c1 c1.c && + test_commit c1a c1.c "c1 a" && git reset --hard c0 && - echo c2 > c2.c && - git add c2.c && - git commit -m c2 && - git tag c2 && - git reset --hard c1 && - echo "c1 a" > c1.c && - git add c1.c && - git commit -m "c1 a" && - git tag c1a && + test_commit c2 c2.c && + git reset --hard c0 && + mkdir sub && + echo "sub/f" > sub/f && + mkdir sub2 && + echo "sub2/f" > sub2/f && + git add sub/f sub2/f && + git commit -m sub && + git tag sub && echo "VERY IMPORTANT CHANGES" > important ' test_expect_success 'will not overwrite untracked file' ' git reset --hard c1 && - cat important > c2.c && + cp important c2.c && test_must_fail git merge c2 && + test_path_is_missing .git/MERGE_HEAD && test_cmp important c2.c ' +test_expect_success 'will overwrite tracked file' ' + git reset --hard c1 && + cp important c2.c && + git add c2.c && + git commit -m important && + git checkout c2 +' + test_expect_success 'will not overwrite new file' ' git reset --hard c1 && - cat important > c2.c && + cp important c2.c && git add c2.c && test_must_fail git merge c2 && + test_path_is_missing .git/MERGE_HEAD && test_cmp important c2.c ' test_expect_success 'will not overwrite staged changes' ' git reset --hard c1 && - cat important > c2.c && + cp important c2.c && git add c2.c && rm c2.c && test_must_fail git merge c2 && + test_path_is_missing .git/MERGE_HEAD && git checkout c2.c && test_cmp important c2.c ' @@ -57,7 +63,7 @@ test_expect_success 'will not overwrite removed file' ' git reset --hard c1 && git rm c1.c && git commit -m "rm c1.c" && - cat important > c1.c && + cp important c1.c && test_must_fail git merge c1a && test_cmp important c1.c ' @@ -66,9 +72,10 @@ test_expect_success 'will not overwrite re-added file' ' git reset --hard c1 && git rm c1.c && git commit -m "rm c1.c" && - cat important > c1.c && + cp important c1.c && git add c1.c && test_must_fail git merge c1a && + test_path_is_missing .git/MERGE_HEAD && test_cmp important c1.c ' @@ -76,12 +83,77 @@ test_expect_success 'will not overwrite removed file with staged changes' ' git reset --hard c1 && git rm c1.c && git commit -m "rm c1.c" && - cat important > c1.c && + cp important c1.c && git add c1.c && rm c1.c && test_must_fail git merge c1a && + test_path_is_missing .git/MERGE_HEAD && git checkout c1.c && test_cmp important c1.c ' +test_expect_success 'will not overwrite untracked subtree' ' + git reset --hard c0 && + rm -rf sub && + mkdir -p sub/f && + cp important sub/f/important && + test_must_fail git merge sub && + test_path_is_missing .git/MERGE_HEAD && + test_cmp important sub/f/important +' + +cat >expect <<\EOF +error: The following untracked working tree files would be overwritten by merge: + sub + sub2 +Please move or remove them before you can merge. +EOF + +test_expect_success 'will not overwrite untracked file in leading path' ' + git reset --hard c0 && + rm -rf sub && + cp important sub && + cp important sub2 && + test_must_fail git merge sub 2>out && + test_cmp out expect && + test_path_is_missing .git/MERGE_HEAD && + test_cmp important sub && + test_cmp important sub2 && + rm -f sub sub2 +' + +test_expect_failure SYMLINKS 'will not overwrite untracked symlink in leading path' ' + git reset --hard c0 && + rm -rf sub && + mkdir sub2 && + ln -s sub2 sub && + test_must_fail git merge sub && + test_path_is_missing .git/MERGE_HEAD +' + +test_expect_success SYMLINKS 'will not be confused by symlink in leading path' ' + git reset --hard c0 && + rm -rf sub && + ln -s sub2 sub && + git add sub && + git commit -m ln && + git checkout sub +' + +cat >expect <<\EOF +error: Untracked working tree file 'c0.c' would be overwritten by merge. +fatal: read-tree failed +EOF + +test_expect_success 'will not overwrite untracked file on unborn branch' ' + git reset --hard c0 && + git rm -fr . && + git checkout --orphan new && + cp important c0.c && + test_must_fail git merge c0 2>out && + test_cmp out expect && + test_path_is_missing .git/MERGE_HEAD && + test_cmp important c0.c +' + test_done diff --git a/t/t7608-merge-messages.sh b/t/t7608-merge-messages.sh index 28d56797b1..9225fa6f02 100755 --- a/t/t7608-merge-messages.sh +++ b/t/t7608-merge-messages.sh @@ -47,14 +47,14 @@ test_expect_success 'ambiguous tag' ' 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/t7609-merge-co-error-msgs.sh b/t/t7609-merge-co-error-msgs.sh index 114d2bd785..c994836c53 100755 --- a/t/t7609-merge-co-error-msgs.sh +++ b/t/t7609-merge-co-error-msgs.sh @@ -27,10 +27,10 @@ test_expect_success 'setup' ' cat >expect <<\EOF error: The following untracked working tree files would be overwritten by merge: - two - three - four five + four + three + two Please move or remove them before you can merge. EOF @@ -49,9 +49,9 @@ test_expect_success 'untracked files overwritten by merge (fast and non-fast for cat >expect <<\EOF error: Your local changes to the following files would be overwritten by merge: - two - three four + three + two Please, commit your changes or stash them before you can merge. error: The following untracked working tree files would be overwritten by merge: five @@ -68,8 +68,8 @@ test_expect_success 'untracked files or local changes ovewritten by merge' ' cat >expect <<\EOF error: Your local changes to the following files would be overwritten by checkout: - rep/two rep/one + rep/two Please, commit your changes or stash them before you can switch branches. EOF @@ -89,8 +89,8 @@ test_expect_success 'cannot switch branches because of local changes' ' cat >expect <<\EOF error: Your local changes to the following files would be overwritten by checkout: - rep/two rep/one + rep/two Please, commit your changes or stash them before you can switch branches. EOF @@ -102,8 +102,8 @@ test_expect_success 'not uptodate file porcelain checkout error' ' cat >expect <<\EOF error: Updating the following directories would lose untracked files in it: - rep2 rep + rep2 EOF diff --git a/t/t7610-mergetool.sh b/t/t7610-mergetool.sh index 3bd74042ef..d78bdec330 100755 --- a/t/t7610-mergetool.sh +++ b/t/t7610-mergetool.sh @@ -54,7 +54,7 @@ test_expect_success 'custom mergetool' ' 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 ) && diff --git a/t/t7611-merge-abort.sh b/t/t7611-merge-abort.sh new file mode 100755 index 0000000000..61890bc892 --- /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 c2f66ff170..d954b846a1 100755 --- a/t/t7700-repack.sh +++ b/t/t7700-repack.sh @@ -56,7 +56,7 @@ test_expect_success 'loose objects in alternate ODB are not repacked' ' ' 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) && @@ -95,14 +95,14 @@ test_expect_success 'packed obs in alternate ODB kept pack are repacked' ' # 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 58dc6f6452..4048d106d4 100755 --- a/t/t7800-difftool.sh +++ b/t/t7800-difftool.sh @@ -98,7 +98,7 @@ test_expect_success PERL 'difftool --gui works without configured diff.guitool' # 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 && @@ -166,7 +166,7 @@ test_expect_success PERL 'difftool.prompt config variable is false' ' # 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) && @@ -211,7 +211,7 @@ test_expect_success PERL 'difftool last flag wins' ' # 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" && @@ -254,17 +254,17 @@ test_expect_success PERL 'difftool -x cat' ' ' 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 023f225a4b..c8777589ca 100755 --- a/t/t7810-grep.sh +++ b/t/t7810-grep.sh @@ -324,8 +324,13 @@ test_expect_success 'log grep setup' ' 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)' ' @@ -372,6 +377,28 @@ test_expect_success 'log --grep --author implicitly uses all-match' ' 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 && @@ -452,7 +479,7 @@ test_expect_success 'outside of git repository' ' 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 && @@ -478,7 +505,7 @@ test_expect_success 'inside git repository but with --no-index' ' 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/t8001-annotate.sh b/t/t8001-annotate.sh index 45cb60ea4b..41962f04a7 100755 --- a/t/t8001-annotate.sh +++ b/t/t8001-annotate.sh @@ -6,10 +6,11 @@ test_description='git annotate' PROG='git annotate' . "$TEST_DIRECTORY"/annotate-tests.sh -test_expect_success \ - 'Annotating an old revision works' \ - '[ $(git annotate file master | awk "{print \$3}" | grep -c "^A$") -eq 2 ] && \ - [ $(git annotate file master | awk "{print \$3}" | grep -c "^B$") -eq 2 ]' - +test_expect_success 'Annotating an old revision works' ' + git annotate file master >result && + awk "{ print \$3; }" <result >authors && + test 2 = $(grep A <authors | wc -l) && + test 2 = $(grep B <authors | wc -l) +' test_done diff --git a/t/t8002-blame.sh b/t/t8002-blame.sh index 597cf0486f..d3a51e1269 100755 --- a/t/t8002-blame.sh +++ b/t/t8002-blame.sh @@ -6,4 +6,9 @@ test_description='git blame' 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.sh b/t/t8003-blame-corner-cases.sh index 230143cf31..230143cf31 100755 --- a/t/t8003-blame.sh +++ b/t/t8003-blame-corner-cases.sh diff --git a/t/t8004-blame.sh b/t/t8004-blame-with-conflicts.sh index ba19ac127e..ba19ac127e 100755 --- a/t/t8004-blame.sh +++ b/t/t8004-blame-with-conflicts.sh diff --git a/t/t8006-blame-textconv.sh b/t/t8006-blame-textconv.sh index 9ad96d4d32..32ec82ad67 100755 --- a/t/t8006-blame-textconv.sh +++ b/t/t8006-blame-textconv.sh @@ -9,22 +9,30 @@ find_blame() { cat >helper <<'EOF' #!/bin/sh -sed 's/^/converted: /' "$@" +grep -q '^bin: ' "$1" || { echo "E: $1 is not \"binary\" file" 1>&2; exit 1; } +sed 's/^bin: /converted: /' "$1" EOF chmod +x helper test_expect_success 'setup ' ' - echo test 1 >one.bin && - echo test number 2 >two.bin && + echo "bin: test 1" >one.bin && + echo "bin: test number 2" >two.bin && + if test_have_prereq SYMLINKS; then + ln -s one.bin symlink.bin + fi && git add . && GIT_AUTHOR_NAME=Number1 git commit -a -m First --date="2010-01-01 18:00:00" && - echo test 1 version 2 >one.bin && - echo test number 2 version 2 >>two.bin && + echo "bin: test 1 version 2" >one.bin && + echo "bin: test number 2 version 2" >>two.bin && + if test_have_prereq SYMLINKS; then + rm symlink.bin && + ln -s two.bin symlink.bin + fi && GIT_AUTHOR_NAME=Number2 git commit -a -m Second --date="2010-01-01 20:00:00" ' cat >expected <<EOF -(Number2 2010-01-01 20:00:00 +0000 1) test 1 version 2 +(Number2 2010-01-01 20:00:00 +0000 1) bin: test 1 version 2 EOF test_expect_success 'no filter specified' ' @@ -66,8 +74,29 @@ test_expect_success 'blame --textconv going through revisions' ' test_cmp expected result ' +test_expect_success 'setup +cachetextconv' ' + git config diff.test.cachetextconv true +' + +cat >expected_one <<EOF +(Number2 2010-01-01 20:00:00 +0000 1) converted: test 1 version 2 +EOF + +test_expect_success 'blame --textconv works with textconvcache' ' + git blame --textconv two.bin >blame && + find_blame <blame >result && + test_cmp expected result && + git blame --textconv one.bin >blame && + find_blame <blame >result && + test_cmp expected_one result +' + +test_expect_success 'setup -cachetextconv' ' + git config diff.test.cachetextconv false +' + test_expect_success 'make a new commit' ' - echo "test number 2 version 3" >>two.bin && + echo "bin: test number 2 version 3" >>two.bin && GIT_AUTHOR_NAME=Number3 git commit -a -m Third --date="2010-01-01 22:00:00" ' @@ -77,4 +106,45 @@ test_expect_success 'blame from previous revision' ' test_cmp expected result ' +cat >expected <<EOF +(Number2 2010-01-01 20:00:00 +0000 1) two.bin +EOF + +test_expect_success SYMLINKS 'blame with --no-textconv (on symlink)' ' + git blame --no-textconv symlink.bin >blame && + find_blame <blame >result && + test_cmp expected result +' + +test_expect_success SYMLINKS 'blame --textconv (on symlink)' ' + git blame --textconv symlink.bin >blame && + find_blame <blame >result && + test_cmp expected result +' + +# cp two.bin three.bin and make small tweak +# (this will direct blame -C -C three.bin to consider two.bin and symlink.bin) +test_expect_success SYMLINKS 'make another new commit' ' + cat >three.bin <<\EOF && +bin: test number 2 +bin: test number 2 version 2 +bin: test number 2 version 3 +bin: test number 3 +EOF + git add three.bin && + GIT_AUTHOR_NAME=Number4 git commit -a -m Fourth --date="2010-01-01 23:00:00" +' + +test_expect_success SYMLINKS 'blame on last commit (-C -C, symlink)' ' + git blame -C -C three.bin >blame && + find_blame <blame >result && + cat >expected <<\EOF && +(Number1 2010-01-01 18:00:00 +0000 1) converted: test number 2 +(Number2 2010-01-01 20:00:00 +0000 2) converted: test number 2 version 2 +(Number3 2010-01-01 22:00:00 +0000 3) converted: test number 2 version 3 +(Number4 2010-01-01 23:00:00 +0000 4) converted: test number 3 +EOF + test_cmp expected result +' + test_done diff --git a/t/t8007-cat-file-textconv.sh b/t/t8007-cat-file-textconv.sh index 38ac05e4a0..78a0085e64 100755 --- a/t/t8007-cat-file-textconv.sh +++ b/t/t8007-cat-file-textconv.sh @@ -5,15 +5,19 @@ test_description='git cat-file textconv support' cat >helper <<'EOF' #!/bin/sh -sed 's/^/converted: /' "$@" +grep -q '^bin: ' "$1" || { echo "E: $1 is not \"binary\" file" 1>&2; exit 1; } +sed 's/^bin: /converted: /' "$1" EOF chmod +x helper test_expect_success 'setup ' ' - echo test >one.bin && + echo "bin: test" >one.bin && + if test_have_prereq SYMLINKS; then + ln -s one.bin symlink.bin + fi && git add . && GIT_AUTHOR_NAME=Number1 git commit -a -m First --date="2010-01-01 18:00:00" && - echo test version 2 >one.bin && + echo "bin: test version 2" >one.bin && GIT_AUTHOR_NAME=Number2 git commit -a -m Second --date="2010-01-01 20:00:00" ' @@ -33,7 +37,7 @@ test_expect_success 'setup textconv filters' ' ' cat >expected <<EOF -test version 2 +bin: test version 2 EOF test_expect_success 'cat-file without --textconv' ' @@ -42,7 +46,7 @@ test_expect_success 'cat-file without --textconv' ' ' cat >expected <<EOF -test +bin: test EOF test_expect_success 'cat-file without --textconv on previous commit' ' @@ -67,4 +71,28 @@ test_expect_success 'cat-file --textconv on previous commit' ' git cat-file --textconv HEAD^:one.bin >result && test_cmp expected result ' + +test_expect_success SYMLINKS 'cat-file without --textconv (symlink)' ' + git cat-file blob :symlink.bin >result && + printf "%s" "one.bin" >expected + test_cmp expected result +' + + +test_expect_success SYMLINKS 'cat-file --textconv on index (symlink)' ' + ! git cat-file --textconv :symlink.bin 2>result && + cat >expected <<\EOF && +fatal: git cat-file --textconv: unable to run textconv on :symlink.bin +EOF + test_cmp expected result +' + +test_expect_success SYMLINKS 'cat-file --textconv on HEAD (symlink)' ' + ! git cat-file --textconv HEAD:symlink.bin 2>result && + cat >expected <<EOF && +fatal: git cat-file --textconv: unable to run textconv on HEAD:symlink.bin +EOF + test_cmp expected result +' + test_done diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh index 71b3df9b55..579ddb7572 100755 --- a/t/t9001-send-email.sh +++ b/t/t9001-send-email.sh @@ -201,10 +201,28 @@ test_expect_success $PREREQ 'Prompting works' ' 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\" @@ -247,7 +265,7 @@ test_expect_success $PREREQ 'Author From: in message body' ' --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 ' @@ -258,7 +276,7 @@ test_expect_success $PREREQ 'Author From: not in message body' ' --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 ' @@ -279,8 +297,8 @@ test_expect_success $PREREQ 'Invalid In-Reply-To' ' --to=nobody@example.com \ --in-reply-to=" " \ --smtp-server="$(pwd)/fake.sendmail" \ - $patches - 2>errors + $patches \ + 2>errors && ! grep "^In-Reply-To: < *>" msgtxt1 ' @@ -295,6 +313,49 @@ test_expect_success $PREREQ 'Valid In-Reply-To when prompting' ' ! 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\"" @@ -556,7 +617,7 @@ EOF " test_expect_success $PREREQ '--suppress-cc=sob' ' - git config --unset sendemail.cccmd + test_might_fail git config --unset sendemail.cccmd && test_suppression sob ' @@ -947,6 +1008,45 @@ test_expect_success $PREREQ '--no-bcc overrides sendemail.bcc' ' ! 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 @@ -1032,4 +1132,40 @@ test_expect_success $PREREQ '--8bit-encoding also treats subject' ' test_cmp expected actual ' +# Note that the patches in this test are deliberately out of order; we +# want to make sure it works even if the cover-letter is not in the +# first mail. +test_expect_success $PREREQ 'refusing to send cover letter template' ' + clean_fake_sendmail && + rm -fr outdir && + git format-patch --cover-letter -2 -o outdir && + test_must_fail git send-email \ + --from="Example <nobody@example.com>" \ + --to=nobody@example.com \ + --smtp-server="$(pwd)/fake.sendmail" \ + outdir/0002-*.patch \ + outdir/0000-*.patch \ + outdir/0001-*.patch \ + 2>errors >out && + grep "SUBJECT HERE" errors && + test -z "$(ls msgtxt*)" +' + +test_expect_success $PREREQ '--force sends cover letter template anyway' ' + clean_fake_sendmail && + rm -fr outdir && + git format-patch --cover-letter -2 -o outdir && + git send-email \ + --force \ + --from="Example <nobody@example.com>" \ + --to=nobody@example.com \ + --smtp-server="$(pwd)/fake.sendmail" \ + outdir/0002-*.patch \ + outdir/0000-*.patch \ + outdir/0001-*.patch \ + 2>errors >out && + ! grep "SUBJECT HERE" errors && + test -n "$(ls msgtxt*)" +' + test_done diff --git a/t/t9010-svn-fe.sh b/t/t9010-svn-fe.sh index a713dfc50b..88a9751dd3 100755 --- a/t/t9010-svn-fe.sh +++ b/t/t9010-svn-fe.sh @@ -2,31 +2,57 @@ 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 } -test_dump simple t9135/svn.dump +>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 'v3 dumps not supported' ' + reinit_git && + echo "SVN-fs-dump-format-version: 3" >input && + test_must_fail test-svn-fe input >stream && + test_cmp empty stream +' + +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 diff --git a/t/t9100-git-svn-basic.sh b/t/t9100-git-svn-basic.sh index 2f458f7a99..b041516a1d 100755 --- a/t/t9100-git-svn-basic.sh +++ b/t/t9100-git-svn-basic.sh @@ -143,7 +143,7 @@ test_expect_success "$name" ' git svn set-tree --find-copies-harder --rmdir \ ${remotes_git_svn}..mybranch5 && svn_cmd up "$SVN_TREE" && - test -L "$SVN_TREE"/exec.sh' + test -h "$SVN_TREE"/exec.sh' name='new symlink is added to a file that was also just made executable' @@ -156,7 +156,7 @@ test_expect_success "$name" ' ${remotes_git_svn}..mybranch5 && svn_cmd up "$SVN_TREE" && test -x "$SVN_TREE"/bar/zzz && - test -L "$SVN_TREE"/exec-2.sh' + test -h "$SVN_TREE"/exec-2.sh' name='modify a symlink to become a file' test_expect_success "$name" ' @@ -169,7 +169,7 @@ test_expect_success "$name" ' ${remotes_git_svn}..mybranch5 && svn_cmd up "$SVN_TREE" && test -f "$SVN_TREE"/exec-2.sh && - test ! -L "$SVN_TREE"/exec-2.sh && + test ! -h "$SVN_TREE"/exec-2.sh && test_cmp help "$SVN_TREE"/exec-2.sh' name="commit with UTF-8 message: locale: $GIT_SVN_LC_ALL" diff --git a/t/t9104-git-svn-follow-parent.sh b/t/t9104-git-svn-follow-parent.sh index f7f3c5ab8e..13b179e721 100755 --- a/t/t9104-git-svn-follow-parent.sh +++ b/t/t9104-git-svn-follow-parent.sh @@ -190,7 +190,7 @@ test_expect_success "follow-parent is atomic" ' 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/t9119-git-svn-info.sh b/t/t9119-git-svn-info.sh index f3f397cdda..ff19695e77 100755 --- a/t/t9119-git-svn-info.sh +++ b/t/t9119-git-svn-info.sh @@ -18,21 +18,14 @@ case $v in ;; esac -ptouch() { - perl -w -e ' - use strict; - use POSIX qw(mktime); - die "ptouch requires exactly 2 arguments" if @ARGV != 2; - my $text_last_updated = shift @ARGV; - my $git_file = shift @ARGV; - die "\"$git_file\" does not exist" if ! -e $git_file; - if ($text_last_updated - =~ /(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/) { - my $mtime = mktime($6, $5, $4, $3, $2 - 1, $1 - 1900); - my $atime = $mtime; - utime $atime, $mtime, $git_file; - } - ' "`svn_cmd info $2 | grep '^Text Last Updated:'`" "$1" +# On the "Text Last Updated" line, "git svn info" does not return the +# same value as "svn info" (i.e. the commit timestamp that touched the +# path most recently); do not expect that field to match. +test_cmp_info () { + sed -e '/^Text Last Updated:/d' "$1" >tmp.expect + sed -e '/^Text Last Updated:/d' "$2" >tmp.actual + test_cmp tmp.expect tmp.actual && + rm -f tmp.expect tmp.actual } quoted_svnrepo="$(echo $svnrepo | sed 's/ /%20/')" @@ -62,17 +55,13 @@ test_expect_success 'setup repository and import' ' cd gitwc && git svn init "$svnrepo" && git svn fetch - ) && - ptouch gitwc/file svnwc/file && - ptouch gitwc/directory svnwc/directory && - ptouch gitwc/symlink-file svnwc/symlink-file && - ptouch gitwc/symlink-directory svnwc/symlink-directory + ) ' test_expect_success 'info' " (cd svnwc; svn info) > expected.info && (cd gitwc; git svn info) > actual.info && - test_cmp expected.info actual.info + test_cmp_info expected.info actual.info " test_expect_success 'info --url' ' @@ -82,7 +71,7 @@ test_expect_success 'info --url' ' test_expect_success 'info .' " (cd svnwc; svn info .) > expected.info-dot && (cd gitwc; git svn info .) > actual.info-dot && - test_cmp expected.info-dot actual.info-dot + test_cmp_info expected.info-dot actual.info-dot " test_expect_success 'info --url .' ' @@ -92,7 +81,7 @@ test_expect_success 'info --url .' ' test_expect_success 'info file' " (cd svnwc; svn info file) > expected.info-file && (cd gitwc; git svn info file) > actual.info-file && - test_cmp expected.info-file actual.info-file + test_cmp_info expected.info-file actual.info-file " test_expect_success 'info --url file' ' @@ -102,13 +91,13 @@ test_expect_success 'info --url file' ' test_expect_success 'info directory' " (cd svnwc; svn info directory) > expected.info-directory && (cd gitwc; git svn info directory) > actual.info-directory && - test_cmp expected.info-directory actual.info-directory + test_cmp_info expected.info-directory actual.info-directory " test_expect_success 'info inside directory' " (cd svnwc/directory; svn info) > expected.info-inside-directory && (cd gitwc/directory; git svn info) > actual.info-inside-directory && - test_cmp expected.info-inside-directory actual.info-inside-directory + test_cmp_info expected.info-inside-directory actual.info-inside-directory " test_expect_success 'info --url directory' ' @@ -118,7 +107,7 @@ test_expect_success 'info --url directory' ' test_expect_success 'info symlink-file' " (cd svnwc; svn info symlink-file) > expected.info-symlink-file && (cd gitwc; git svn info symlink-file) > actual.info-symlink-file && - test_cmp expected.info-symlink-file actual.info-symlink-file + test_cmp_info expected.info-symlink-file actual.info-symlink-file " test_expect_success 'info --url symlink-file' ' @@ -131,7 +120,7 @@ test_expect_success 'info symlink-directory' " > expected.info-symlink-directory && (cd gitwc; git svn info symlink-directory) \ > actual.info-symlink-directory && - test_cmp expected.info-symlink-directory actual.info-symlink-directory + test_cmp_info expected.info-symlink-directory actual.info-symlink-directory " test_expect_success 'info --url symlink-directory' ' @@ -146,14 +135,13 @@ test_expect_success 'info added-file' " git add added-file ) && cp gitwc/added-file svnwc/added-file && - ptouch gitwc/added-file svnwc/added-file && ( cd svnwc && svn_cmd add added-file > /dev/null ) && (cd svnwc; svn info added-file) > expected.info-added-file && (cd gitwc; git svn info added-file) > actual.info-added-file && - test_cmp expected.info-added-file actual.info-added-file + test_cmp_info expected.info-added-file actual.info-added-file " test_expect_success 'info --url added-file' ' @@ -163,7 +151,6 @@ test_expect_success 'info --url added-file' ' test_expect_success 'info added-directory' " mkdir gitwc/added-directory svnwc/added-directory && - ptouch gitwc/added-directory svnwc/added-directory && touch gitwc/added-directory/.placeholder && ( cd svnwc && @@ -177,7 +164,7 @@ test_expect_success 'info added-directory' " > expected.info-added-directory && (cd gitwc; git svn info added-directory) \ > actual.info-added-directory && - test_cmp expected.info-added-directory actual.info-added-directory + test_cmp_info expected.info-added-directory actual.info-added-directory " test_expect_success 'info --url added-directory' ' @@ -196,13 +183,12 @@ test_expect_success 'info added-symlink-file' " ln -s added-file added-symlink-file && svn_cmd add added-symlink-file > /dev/null ) && - ptouch gitwc/added-symlink-file svnwc/added-symlink-file && (cd svnwc; svn info added-symlink-file) \ > expected.info-added-symlink-file && (cd gitwc; git svn info added-symlink-file) \ > actual.info-added-symlink-file && - test_cmp expected.info-added-symlink-file \ - actual.info-added-symlink-file + test_cmp_info expected.info-added-symlink-file \ + actual.info-added-symlink-file " test_expect_success 'info --url added-symlink-file' ' @@ -221,13 +207,12 @@ test_expect_success 'info added-symlink-directory' " ln -s added-directory added-symlink-directory && svn_cmd add added-symlink-directory > /dev/null ) && - ptouch gitwc/added-symlink-directory svnwc/added-symlink-directory && (cd svnwc; svn info added-symlink-directory) \ > expected.info-added-symlink-directory && (cd gitwc; git svn info added-symlink-directory) \ > actual.info-added-symlink-directory && - test_cmp expected.info-added-symlink-directory \ - actual.info-added-symlink-directory + test_cmp_info expected.info-added-symlink-directory \ + actual.info-added-symlink-directory " test_expect_success 'info --url added-symlink-directory' ' @@ -235,11 +220,6 @@ test_expect_success 'info --url added-symlink-directory' ' = "$quoted_svnrepo/added-symlink-directory" ' -# The next few tests replace the "Text Last Updated" value with a -# placeholder since git doesn't have a way to know the date that a -# now-deleted file was last checked out locally. Internally it -# simply reuses the Last Changed Date. - test_expect_success 'info deleted-file' " ( cd gitwc && @@ -249,13 +229,9 @@ test_expect_success 'info deleted-file' " cd svnwc && svn_cmd rm --force file > /dev/null ) && - (cd svnwc; svn info file) | - sed -e 's/^\(Text Last Updated:\).*/\1 TEXT-LAST-UPDATED-STRING/' \ - > expected.info-deleted-file && - (cd gitwc; git svn info file) | - sed -e 's/^\(Text Last Updated:\).*/\1 TEXT-LAST-UPDATED-STRING/' \ - > actual.info-deleted-file && - test_cmp expected.info-deleted-file actual.info-deleted-file + (cd svnwc; svn info file) >expected.info-deleted-file && + (cd gitwc; git svn info file) >actual.info-deleted-file && + test_cmp_info expected.info-deleted-file actual.info-deleted-file " test_expect_success 'info --url file (deleted)' ' @@ -272,13 +248,9 @@ test_expect_success 'info deleted-directory' " cd svnwc && svn_cmd rm --force directory > /dev/null ) && - (cd svnwc; svn info directory) | - sed -e 's/^\(Text Last Updated:\).*/\1 TEXT-LAST-UPDATED-STRING/' \ - > expected.info-deleted-directory && - (cd gitwc; git svn info directory) | - sed -e 's/^\(Text Last Updated:\).*/\1 TEXT-LAST-UPDATED-STRING/' \ - > actual.info-deleted-directory && - test_cmp expected.info-deleted-directory actual.info-deleted-directory + (cd svnwc; svn info directory) >expected.info-deleted-directory && + (cd gitwc; git svn info directory) >actual.info-deleted-directory && + test_cmp_info expected.info-deleted-directory actual.info-deleted-directory " test_expect_success 'info --url directory (deleted)' ' @@ -295,14 +267,9 @@ test_expect_success 'info deleted-symlink-file' " cd svnwc && svn_cmd rm --force symlink-file > /dev/null ) && - (cd svnwc; svn info symlink-file) | - sed -e 's/^\(Text Last Updated:\).*/\1 TEXT-LAST-UPDATED-STRING/' \ - > expected.info-deleted-symlink-file && - (cd gitwc; git svn info symlink-file) | - sed -e 's/^\(Text Last Updated:\).*/\1 TEXT-LAST-UPDATED-STRING/' \ - > actual.info-deleted-symlink-file && - test_cmp expected.info-deleted-symlink-file \ - actual.info-deleted-symlink-file + (cd svnwc; svn info symlink-file) >expected.info-deleted-symlink-file && + (cd gitwc; git svn info symlink-file) >actual.info-deleted-symlink-file && + test_cmp_info expected.info-deleted-symlink-file actual.info-deleted-symlink-file " test_expect_success 'info --url symlink-file (deleted)' ' @@ -319,14 +286,9 @@ test_expect_success 'info deleted-symlink-directory' " cd svnwc && svn_cmd rm --force symlink-directory > /dev/null ) && - (cd svnwc; svn info symlink-directory) | - sed -e 's/^\(Text Last Updated:\).*/\1 TEXT-LAST-UPDATED-STRING/' \ - > expected.info-deleted-symlink-directory && - (cd gitwc; git svn info symlink-directory) | - sed -e 's/^\(Text Last Updated:\).*/\1 TEXT-LAST-UPDATED-STRING/' \ - > actual.info-deleted-symlink-directory && - test_cmp expected.info-deleted-symlink-directory \ - actual.info-deleted-symlink-directory + (cd svnwc; svn info symlink-directory) >expected.info-deleted-symlink-directory && + (cd gitwc; git svn info symlink-directory) >actual.info-deleted-symlink-directory && + test_cmp_info expected.info-deleted-symlink-directory actual.info-deleted-symlink-directory " test_expect_success 'info --url symlink-directory (deleted)' ' diff --git a/t/t9123-git-svn-rebuild-with-rewriteroot.sh b/t/t9123-git-svn-rebuild-with-rewriteroot.sh index 0ed90d982d..fd8184787f 100755 --- a/t/t9123-git-svn-rebuild-with-rewriteroot.sh +++ b/t/t9123-git-svn-rebuild-with-rewriteroot.sh @@ -16,7 +16,7 @@ rm -rf import 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} ' diff --git a/t/t9124-git-svn-dcommit-auto-props.sh b/t/t9124-git-svn-dcommit-auto-props.sh index d6b076f6b7..aa841e1299 100755 --- a/t/t9124-git-svn-dcommit-auto-props.sh +++ b/t/t9124-git-svn-dcommit-auto-props.sh @@ -24,7 +24,7 @@ test_expect_success 'initialize git svn' ' 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/t9131-git-svn-empty-symlink.sh b/t/t9131-git-svn-empty-symlink.sh index 9a24a65b64..f762038f0e 100755 --- a/t/t9131-git-svn-empty-symlink.sh +++ b/t/t9131-git-svn-empty-symlink.sh @@ -88,7 +88,7 @@ test_expect_success 'enable broken symlink workaround' \ test_expect_success '"bar" is an empty file' 'test -f x/bar && ! test -s x/bar' test_expect_success 'get "bar" => symlink fix from svn' \ '(cd x && git svn rebase)' -test_expect_success SYMLINKS '"bar" becomes a symlink' 'test -L x/bar' +test_expect_success SYMLINKS '"bar" becomes a symlink' 'test -h x/bar' test_expect_success 'clone using git svn' 'git svn clone -r1 "$svnrepo" y' diff --git a/t/t9142-git-svn-shallow-clone.sh b/t/t9142-git-svn-shallow-clone.sh index 1236accd99..e21ee5f663 100755 --- a/t/t9142-git-svn-shallow-clone.sh +++ b/t/t9142-git-svn-shallow-clone.sh @@ -17,11 +17,10 @@ test_expect_success 'setup test repository' ' > foo && svn_cmd add foo && svn_cmd commit -m "add foo" - ) + ) && + start_httpd ' -start_httpd - test_expect_success 'clone trunk with "-r HEAD"' ' git svn clone -r HEAD "$svnrepo/trunk" g && ( cd g && git rev-parse --symbolic --verify HEAD ) diff --git a/t/t9143-git-svn-gc.sh b/t/t9143-git-svn-gc.sh index 337ea59711..4594e1ae2f 100755 --- a/t/t9143-git-svn-gc.sh +++ b/t/t9143-git-svn-gc.sh @@ -37,13 +37,11 @@ test_expect_success 'git svn gc runs' 'git svn gc' 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' ' diff --git a/t/t9146-git-svn-empty-dirs.sh b/t/t9146-git-svn-empty-dirs.sh index 565365cbd3..158c8e33ef 100755 --- a/t/t9146-git-svn-empty-dirs.sh +++ b/t/t9146-git-svn-empty-dirs.sh @@ -33,7 +33,7 @@ test_expect_success 'more emptiness' ' ' test_expect_success 'git svn rebase creates empty directory' ' - ( cd cloned && git svn rebase ) + ( cd cloned && git svn rebase ) && test -d cloned/"! !" ' diff --git a/t/t9151-svn-mergeinfo.sh b/t/t9151-svn-mergeinfo.sh index 250c651eae..4f6c06ecb2 100755 --- a/t/t9151-svn-mergeinfo.sh +++ b/t/t9151-svn-mergeinfo.sh @@ -18,39 +18,39 @@ test_expect_success 'load svn dump' " 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" ] ' diff --git a/t/t9157-git-svn-fetch-merge.sh b/t/t9157-git-svn-fetch-merge.sh new file mode 100755 index 0000000000..991d2aa1be --- /dev/null +++ b/t/t9157-git-svn-fetch-merge.sh @@ -0,0 +1,58 @@ +#!/bin/sh +# +# Copyright (c) 2010 Steven Walter +# + +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 && + svn_cmd co "$svnrepo"/trunk "$SVN_TREE" && + ( + cd "$SVN_TREE" && + touch foo && + svn add foo && + svn commit -m "initial commit" && + svn cp -m branch "$svnrepo"/trunk "$svnrepo"/branches/branch1 && + touch bar && + svn add bar && + svn commit -m x && + svn cp -m branch "$svnrepo"/trunk "$svnrepo"/branches/branch2 && + svn switch "$svnrepo"/branches/branch1 && + touch baz && + svn add baz && + svn commit -m x && + svn switch "$svnrepo"/trunk && + svn merge "$svnrepo"/branches/branch1 && + svn commit -m "merge" && + svn switch "$svnrepo"/branches/branch1 && + svn commit -m x && + svn switch "$svnrepo"/branches/branch2 && + svn merge "$svnrepo"/branches/branch1 && + svn commit -m "merge branch1" && + svn switch "$svnrepo"/trunk && + svn merge "$svnrepo"/branches/branch2 && + svn resolved baz && + svn commit -m "merge branch2" + ) && + rm -rf "$SVN_TREE" +' + +test_expect_success 'clone svn repo' ' + git svn init -s "$svnrepo" && + git svn fetch +' + +test_expect_success 'verify merge commit' 'git rev-parse HEAD^2' + +test_done diff --git a/t/t9158-git-svn-mergeinfo.sh b/t/t9158-git-svn-mergeinfo.sh new file mode 100755 index 0000000000..3ab43902b3 --- /dev/null +++ b/t/t9158-git-svn-mergeinfo.sh @@ -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 7c059204e9..986bc14d58 100755 --- a/t/t9300-fast-import.sh +++ b/t/t9300-fast-import.sh @@ -7,6 +7,23 @@ test_description='test git fast-import utility' . ./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' @@ -23,11 +40,18 @@ file5_data='an inline file. file6_data='#!/bin/sh echo "$@"' +>empty + ### ### series A ### test_tick + +test_expect_success 'empty stream succeeds' ' + git fast-import </dev/null +' + cat >input <<INPUT_END blob mark :2 @@ -321,7 +345,7 @@ test_expect_success \ '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 @@ -875,6 +899,69 @@ test_expect_success \ compare_diff_raw 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 && + OBJID + :100644 000000 OBJID OBJID D foo/bar/qux + OBJID + :000000 100644 OBJID OBJID A foo/bar/baz + :000000 100644 OBJID OBJID A foo/bar/qux + EOF + empty_tree=$(git mktree </dev/null) && + cat >input <<-INPUT_END && + commit refs/heads/N-delete + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + collect data to be deleted + COMMIT + + deleteall + M 100644 inline foo/bar/baz + data <<DATA_END + hello + DATA_END + C "foo/bar/baz" "foo/bar/qux" + C "foo/bar/baz" "foo/bar/quux/1" + C "foo/bar/baz" "foo/bar/quuux" + M 040000 $empty_tree foo/bar/quux + M 040000 $empty_tree foo/bar/quuux + + commit refs/heads/N-delete + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + delete subdirectory + COMMIT + + M 040000 $empty_tree foo/bar/qux + INPUT_END + git fast-import <input && + git rev-list N-delete | + git diff-tree -r --stdin --root --always | + sed -e "s/$_x40/OBJID/g" >actual && + test_cmp expect actual' + +test_expect_success \ 'N: modify copied tree' \ 'cat >expect <<-\EOF && :100644 100644 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 C100 newdir/interesting file3/file5 @@ -907,6 +994,114 @@ test_expect_success \ 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 ### @@ -1611,6 +1806,253 @@ test_expect_success 'R: feature no-relative-marks should be honoured' ' test_cmp marks.new non-relative.out ' +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 'setup: have pipes?' ' + rm -f frob && + if mkfifo frob + then + test_set_prereq PIPE + fi +' + +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 @@ -1619,8 +2061,6 @@ hi 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 @@ -1638,6 +2078,14 @@ test_expect_success 'R: unknown commandline options are rejected' '\ 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 diff --git a/t/t9301-fast-import-notes.sh b/t/t9301-fast-import-notes.sh index a5c99d8507..463254c727 100755 --- a/t/t9301-fast-import-notes.sh +++ b/t/t9301-fast-import-notes.sh @@ -120,6 +120,7 @@ test_expect_success 'add notes with simple M command' ' test_tick cat >input <<INPUT_END +feature notes commit refs/notes/test committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE data <<COMMIT @@ -255,13 +256,18 @@ EOF 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 8c8e679468..f823c05305 100755 --- a/t/t9350-fast-export.sh +++ b/t/t9350-fast-export.sh @@ -26,7 +26,7 @@ test_expect_success 'setup' ' 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 && diff --git a/t/t9400-git-cvsserver-server.sh b/t/t9400-git-cvsserver-server.sh index 36c457e7f2..9199550ef4 100755 --- a/t/t9400-git-cvsserver-server.sh +++ b/t/t9400-git-cvsserver-server.sh @@ -57,7 +57,7 @@ test_expect_success 'setup' ' # 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/"' #------------------------ diff --git a/t/t9401-git-cvsserver-crlf.sh b/t/t9401-git-cvsserver-crlf.sh index 1bbfd824e5..ff6d6fb473 100755 --- a/t/t9401-git-cvsserver-crlf.sh +++ b/t/t9401-git-cvsserver-crlf.sh @@ -70,7 +70,7 @@ test_expect_success 'setup' ' 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 && diff --git a/t/t9500-gitweb-standalone-no-errors.sh b/t/t9500-gitweb-standalone-no-errors.sh index 4f2b9b062b..35c151d7ea 100755 --- a/t/t9500-gitweb-standalone-no-errors.sh +++ b/t/t9500-gitweb-standalone-no-errors.sh @@ -18,42 +18,34 @@ or warnings to log.' 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' # ---------------------------------------------------------------------- @@ -69,52 +61,42 @@ test_expect_success \ 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 @@ -122,37 +104,30 @@ test_debug 'cat gitweb.log' 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' # ---------------------------------------------------------------------- @@ -161,7 +136,6 @@ 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' \ @@ -169,21 +143,18 @@ test_expect_success \ 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' \ @@ -191,7 +162,6 @@ test_expect_success SYMLINKS \ 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' \ @@ -199,7 +169,6 @@ test_expect_success \ 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' \ @@ -207,7 +176,6 @@ test_expect_success \ 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' \ @@ -215,7 +183,6 @@ test_expect_success \ 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' \ @@ -233,7 +200,6 @@ EOF 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' \ @@ -242,7 +208,6 @@ test_expect_success \ 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) @@ -279,42 +244,34 @@ test_expect_success SYMLINKS 'setup typechange commits' ' 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 @@ -330,12 +287,10 @@ test_expect_success \ 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' \ @@ -371,12 +326,10 @@ test_expect_success \ 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 @@ -394,17 +347,14 @@ test_expect_success \ 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 @@ -412,22 +362,18 @@ test_debug 'cat gitweb.log' 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)' \ @@ -438,55 +384,45 @@ test_expect_success \ 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' # ---------------------------------------------------------------------- @@ -495,17 +431,14 @@ 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 @@ -517,7 +450,6 @@ test_expect_success \ 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' \ @@ -528,12 +460,10 @@ test_expect_success \ 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 @@ -541,27 +471,22 @@ test_debug 'cat gitweb.log' 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 @@ -569,14 +494,12 @@ test_debug 'cat gitweb.log' 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 @@ -586,7 +509,6 @@ EOF 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 @@ -604,12 +526,10 @@ EOF 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' \ @@ -617,14 +537,12 @@ test_expect_success \ 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 @@ -635,7 +553,6 @@ EOF 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 @@ -645,35 +562,33 @@ test_expect_success \ '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 diff --git a/t/t9501-gitweb-standalone-http-status.sh b/t/t9501-gitweb-standalone-http-status.sh index 2487da1296..26102ee9b0 100755 --- a/t/t9501-gitweb-standalone-http-status.sh +++ b/t/t9501-gitweb-standalone-http-status.sh @@ -16,7 +16,7 @@ code and message.' # snapshot settings test_expect_success 'setup' " - test_commit 'SnapshotTests' 'i can has snapshot?' + test_commit 'SnapshotTests' 'i can has snapshot' " @@ -126,7 +126,6 @@ test_expect_success 'load checking: load too high (default action)' ' 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 432b82e3d5..4c384ff023 100755 --- a/t/t9600-cvsimport.sh +++ b/t/t9600-cvsimport.sh @@ -89,7 +89,8 @@ EOF 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 @@ -117,7 +118,8 @@ test_expect_success PERL 'cvsimport.module config works' ' (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 @@ -137,6 +139,7 @@ test_expect_success PERL 'import from a CVS working tree' ' $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 671f38db2b..13ba96e21a 100755 --- a/t/t9700/test.pl +++ b/t/t9700/test.pl @@ -1,7 +1,7 @@ #!/usr/bin/perl use lib (split(/:/, $ENV{GITPERLLIB})); -use 5.006002; +use 5.008; use warnings; use strict; @@ -113,6 +113,16 @@ like($last_commit, qr/^[0-9a-fA-F]{40}$/, 'rev-parse returned hash'); 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/test-lib.sh b/t/test-lib.sh index 830e5e7360..c91e232437 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -70,6 +70,9 @@ unset GIT_NOTES_REF unset GIT_NOTES_DISPLAY_REF unset GIT_NOTES_REWRITE_REF unset GIT_NOTES_REWRITE_MODE +unset GIT_REFLOG_ACTION +unset GIT_CHERRY_PICK_HELP +unset GIT_QUIET GIT_MERGE_VERBOSITY=5 export GIT_MERGE_VERBOSITY export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME @@ -238,14 +241,51 @@ test_set_editor () { } test_decode_color () { - sed -e 's/.\[1m/<WHITE>/g' \ - -e 's/.\[31m/<RED>/g' \ - -e 's/.\[32m/<GREEN>/g' \ - -e 's/.\[33m/<YELLOW>/g' \ - -e 's/.\[34m/<BLUE>/g' \ - -e 's/.\[35m/<MAGENTA>/g' \ - -e 's/.\[36m/<CYAN>/g' \ - -e 's/.\[m/<RESET>/g' + awk ' + function name(n) { + if (n == 0) return "RESET"; + if (n == 1) return "BOLD"; + if (n == 30) return "BLACK"; + if (n == 31) return "RED"; + if (n == 32) return "GREEN"; + if (n == 33) return "YELLOW"; + if (n == 34) return "BLUE"; + if (n == 35) return "MAGENTA"; + if (n == 36) return "CYAN"; + if (n == 37) return "WHITE"; + if (n == 40) return "BLACK"; + if (n == 41) return "BRED"; + if (n == 42) return "BGREEN"; + if (n == 43) return "BYELLOW"; + if (n == 44) return "BBLUE"; + if (n == 45) return "BMAGENTA"; + if (n == 46) return "BCYAN"; + if (n == 47) return "BWHITE"; + } + { + while (match($0, /\033\[[0-9;]*m/) != 0) { + printf "%s<", substr($0, 1, RSTART-1); + codes = substr($0, RSTART+2, RLENGTH-3); + if (length(codes) == 0) + printf "%s", name(0) + else { + n = split(codes, ary, ";"); + sep = ""; + for (i = 1; i <= n; i++) { + printf "%s%s", sep, name(ary[i]); + sep = ";" + } + } + printf ">"; + $0 = substr($0, RSTART + RLENGTH, length($0) - RSTART - RLENGTH + 1); + } + print + } + ' +} + +nul_to_q () { + perl -pe 'y/\000/Q/' } q_to_nul () { @@ -268,6 +308,17 @@ remove_cr () { 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 @@ -362,6 +413,15 @@ test_have_prereq () { 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. @@ -414,17 +474,17 @@ test_skip () { 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: $@" @@ -438,9 +498,10 @@ test_skip () { } 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" @@ -456,9 +517,10 @@ test_expect_failure () { } 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" @@ -473,24 +535,6 @@ test_expect_success () { 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 @@ -500,11 +544,12 @@ test_expect_code () { # 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 @@ -605,6 +650,28 @@ test_path_is_missing () { 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: @@ -658,6 +725,28 @@ test_might_fail () { 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: # @@ -915,14 +1004,14 @@ rm -fr "$test" || { exit 1 } +HOME="$TRASH_DIRECTORY" +export HOME + test_create_repo "$test" # Use -P to resolve symlinks in our working directory so that the cwd # in subprocesses like git equals our $PWD (for pathname comparisons). cd -P "$test" || exit 1 -HOME=$(pwd) -export HOME - this_test=${0##*/} this_test=${this_test%%-*} for skp in $GIT_SKIP_TESTS @@ -970,11 +1059,20 @@ case $(uname -s) in # 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/t7006/test-terminal.perl b/t/test-terminal.perl index 73ff809371..ee01eb957e 100755 --- a/t/t7006/test-terminal.perl +++ b/t/test-terminal.perl @@ -1,17 +1,19 @@ #!/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. +# Run @$argv in the background with stdio redirected to $out and $err. sub start_child { - my ($argv, $out) = @_; + 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]': $!" } @@ -47,12 +49,28 @@ sub xsendfile { 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 = new IO::Pty; -my $slave = $master->slave; -my $pid = start_child(\@ARGV, $slave); -close $slave; -xsendfile(\*STDOUT, $master); +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)); diff --git a/t/valgrind/default.supp b/t/valgrind/default.supp index 9e013fa3b2..0a6724fcc4 100644 --- a/t/valgrind/default.supp +++ b/t/valgrind/default.supp @@ -43,3 +43,9 @@ fun:write_buffer fun:write_loose_object } + +{ + ignore-sse-strlen-invalid-read-size + Memcheck:Addr4 + fun:copy_ref +} @@ -4,6 +4,9 @@ #include "tree.h" #include "blob.h" +#define PGP_SIGNATURE "-----BEGIN PGP SIGNATURE-----" +#define PGP_MESSAGE "-----BEGIN PGP MESSAGE-----" + const char *tag_type = "tag"; struct object *deref_tag(struct object *o, const char *warn, int warnlen) @@ -53,7 +56,7 @@ static unsigned long parse_tag_date(const char *buf, const char *tail) 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]; @@ -94,7 +97,9 @@ int parse_tag_buffer(struct tag *item, void *data, unsigned long size) 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); @@ -103,7 +108,7 @@ int parse_tag_buffer(struct tag *item, void *data, unsigned long size) 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; @@ -133,3 +138,15 @@ int parse_tag(struct tag *item) free(data); return ret; } + +size_t parse_signature(const char *buf, unsigned long size) +{ + char *eol; + size_t len = 0; + while (len < size && prefixcmp(buf + len, PGP_SIGNATURE) && + prefixcmp(buf + len, PGP_MESSAGE)) { + eol = memchr(buf + len, '\n', size - len); + len += eol ? eol - (buf + len) + 1 : size - len; + } + return len; +} @@ -13,8 +13,9 @@ struct tag { }; 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); #endif /* TAG_H */ diff --git a/test-mktemp.c b/test-mktemp.c new file mode 100644 index 0000000000..c8c54213a3 --- /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 acd1a2ba70..4e3710b9a8 100644 --- a/test-parse-options.c +++ b/test-parse-options.c @@ -46,7 +46,7 @@ int main(int argc, const char **argv) 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"), @@ -66,9 +66,9 @@ int main(int argc, const char **argv) "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 new file mode 100644 index 0000000000..667d3e5079 --- /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-treap.c b/test-treap.c index cdba5111e1..ab8c951c6e 100644 --- a/test-treap.c +++ b/test-treap.c @@ -38,9 +38,14 @@ int main(int argc, char *argv[]) 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 1727a03333..6fb98c333c 100644 --- a/thread-utils.h +++ b/thread-utils.h @@ -1,7 +1,11 @@ #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 */ @@ -25,10 +25,6 @@ #include "cache.h" #include "quote.h" -void do_nothing(size_t unused) -{ -} - /* Get a trace file descriptor from GIT_TRACE env variable. */ static int get_trace_fd(int *need_close) { @@ -76,7 +72,7 @@ void trace_printf(const char *fmt, ...) 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); @@ -108,7 +104,7 @@ void trace_argv_printf(const char **argv, const char *fmt, ...) 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); @@ -131,3 +127,52 @@ void trace_argv_printf(const char **argv, const char *fmt, ...) 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 acfc88e3f1..8866adf088 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -8,11 +8,11 @@ #include "quote.h" #include "remote.h" #include "string-list.h" +#include "thread-utils.h" static int debug; -struct helper_data -{ +struct helper_data { const char *name; struct child_process *helper; FILE *out; @@ -862,3 +862,314 @@ int transport_helper_init(struct transport *transport, const char *name) 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 4dba6f8815..0078660611 100644 --- 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 c59d97388e..e803c0e7ba 100644 --- a/transport.h +++ b/transport.h @@ -154,6 +154,7 @@ int transport_connect(struct transport *transport, const char *name, /* 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 cd659c6fe4..12c9a88884 100644 --- a/tree-diff.c +++ b/tree-diff.c @@ -85,6 +85,8 @@ static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, const /* * Is a tree entry interesting given the pathspec we have? * + * Pre-condition: baselen == 0 || base[baselen-1] == '/' + * * Return: * - 2 for "yes, and all subsequent entries will be" * - 1 for yes @@ -101,7 +103,7 @@ static int tree_entry_interesting(struct tree_desc *desc, const char *base, int int never_interesting = -1; if (!opt->nr_paths) - return 1; + return 2; sha1 = tree_entry_extract(desc, &path, &mode); @@ -257,19 +259,12 @@ static void show_entry(struct diff_options *opt, const char *prefix, struct tree } } -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, const char *base, int baselen, 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, base, baselen, opt); + if (show == 2) + *all_interesting = 1; if (!show) { update_tree_entry(t); continue; @@ -284,14 +279,20 @@ 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 baselen = strlen(base); + int all_t1_interesting = 0; + int all_t2_interesting = 0; 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 (!all_t1_interesting) + skip_uninteresting(t1, base, baselen, opt, + &all_t1_interesting); + if (!all_t2_interesting) + skip_uninteresting(t2, base, baselen, opt, + &all_t2_interesting); } if (!t1->size) { if (!t2->size) diff --git a/unpack-trees.c b/unpack-trees.c index 803445aa7b..1ca41b1a69 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -53,6 +53,7 @@ const char *unpack_plumbing_errors[NB_UNPACK_TREES_ERROR_TYPES] = { void setup_unpack_trees_porcelain(struct unpack_trees_options *opts, const char *cmd) { + int i; const char **msgs = opts->msgs; const char *msg; char *tmp; @@ -96,6 +97,9 @@ void setup_unpack_trees_porcelain(struct unpack_trees_options *opts, "The following Working tree files would be removed by sparse checkout update:\n%s"; opts->show_all_errors = 1; + /* rejected paths may not have a static buffer */ + for (i = 0; i < ARRAY_SIZE(opts->unpack_rejects); i++) + opts->unpack_rejects[i].strdup_strings = 1; } static void add_entry(struct unpack_trees_options *o, struct cache_entry *ce, @@ -124,7 +128,6 @@ static int add_rejected_path(struct unpack_trees_options *o, enum unpack_trees_error_types e, const char *path) { - struct rejected_paths_list *newentry; if (!o->show_all_errors) return error(ERRORMSG(o, e), path); @@ -132,45 +135,28 @@ static int add_rejected_path(struct unpack_trees_options *o, * Otherwise, insert in a list for future display by * display_error_msgs() */ - newentry = xmalloc(sizeof(struct rejected_paths_list)); - newentry->path = (char *)path; - newentry->next = o->unpack_rejects[e]; - o->unpack_rejects[e] = newentry; + string_list_append(&o->unpack_rejects[e], path); return -1; } /* - * free all the structures allocated for the error <e> - */ -static void free_rejected_paths(struct unpack_trees_options *o, - enum unpack_trees_error_types e) -{ - while (o->unpack_rejects[e]) { - struct rejected_paths_list *del = o->unpack_rejects[e]; - o->unpack_rejects[e] = o->unpack_rejects[e]->next; - free(del); - } - free(o->unpack_rejects[e]); -} - -/* * display all the error messages stored in a nice way */ static void display_error_msgs(struct unpack_trees_options *o) { - int e; + int e, i; int something_displayed = 0; for (e = 0; e < NB_UNPACK_TREES_ERROR_TYPES; e++) { - if (o->unpack_rejects[e]) { - struct rejected_paths_list *rp; + struct string_list *rejects = &o->unpack_rejects[e]; + if (rejects->nr > 0) { struct strbuf path = STRBUF_INIT; something_displayed = 1; - for (rp = o->unpack_rejects[e]; rp; rp = rp->next) - strbuf_addf(&path, "\t%s\n", rp->path); + for (i = 0; i < rejects->nr; i++) + strbuf_addf(&path, "\t%s\n", rejects->items[i].string); error(ERRORMSG(o, e), path.buf); strbuf_release(&path); - free_rejected_paths(o, e); } + string_list_clear(rejects, 0); } if (something_displayed) printf("Aborting\n"); @@ -182,7 +168,7 @@ static void display_error_msgs(struct unpack_trees_options *o) */ static void unlink_entry(struct cache_entry *ce) { - if (has_symlink_or_noent_leading_path(ce->name, ce_namelen(ce))) + if (!check_leading_path(ce->name, ce_namelen(ce))) return; if (remove_or_warn(ce->ce_mode, ce->name)) return; @@ -245,20 +231,11 @@ static int check_updates(struct unpack_trees_options *o) 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; @@ -333,7 +310,7 @@ static void mark_all_ce_unused(struct index_state *index) { 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, @@ -834,9 +811,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) { @@ -869,6 +1014,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; @@ -922,9 +1073,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; @@ -945,11 +1116,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: @@ -1017,7 +1184,7 @@ static int verify_uptodate_1(struct cache_entry *ce, 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); } @@ -1127,14 +1294,65 @@ static int verify_clean_subdirectory(struct cache_entry *ce, * See if we can find a case-insensitive match in the index that also * matches the stat information, and assume it's that other file! */ -static int icase_exists(struct unpack_trees_options *o, struct cache_entry *dst, struct stat *st) +static int icase_exists(struct unpack_trees_options *o, const char *name, int len, struct stat *st) { struct cache_entry *src; - src = index_name_exists(o->src_index, dst->name, ce_namelen(dst), 1); + src = index_name_exists(o->src_index, name, len, 1); return src && !ie_match_stat(o->src_index, src, st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE); } +static int check_ok_to_remove(const char *name, int len, int dtype, + struct cache_entry *ce, struct stat *st, + enum unpack_trees_error_types error_type, + struct unpack_trees_options *o) +{ + struct cache_entry *result; + + /* + * It may be that the 'lstat()' succeeded even though + * target 'ce' was absent, because there is an old + * entry that is different only in case.. + * + * Ignore that lstat() if it matches. + */ + if (ignore_case && icase_exists(o, name, len, st)) + return 0; + + if (o->dir && excluded(o->dir, name, &dtype)) + /* + * ce->name is explicitly excluded, so it is Ok to + * overwrite it. + */ + return 0; + if (S_ISDIR(st->st_mode)) { + /* + * We are checking out path "foo" and + * found "foo/." in the working tree. + * This is tricky -- if we have modified + * files that are in "foo/" we would lose + * them. + */ + if (verify_clean_subdirectory(ce, error_type, o) < 0) + return -1; + return 0; + } + + /* + * The previous round may already have decided to + * delete this path, which is in a subdirectory that + * is being replaced with a blob. + */ + result = index_name_exists(&o->result, name, len, 0); + if (result) { + if (result->ce_flags & CE_REMOVE) + return 0; + } + + return o->gently ? -1 : + add_rejected_path(o, error_type, name); +} + /* * We do not want to remove or overwrite a working tree file that * is not tracked, unless it is ignored. @@ -1143,68 +1361,36 @@ static int verify_absent_1(struct cache_entry *ce, enum unpack_trees_error_types error_type, struct unpack_trees_options *o) { + int len; struct stat st; if (o->index_only || o->reset || !o->update) return 0; - if (has_symlink_or_noent_leading_path(ce->name, ce_namelen(ce))) + len = check_leading_path(ce->name, ce_namelen(ce)); + if (!len) return 0; + else if (len > 0) { + char path[PATH_MAX + 1]; + memcpy(path, ce->name, len); + path[len] = 0; + lstat(path, &st); + + return check_ok_to_remove(path, len, DT_UNKNOWN, NULL, &st, + error_type, o); + } else if (!lstat(ce->name, &st)) + return check_ok_to_remove(ce->name, ce_namelen(ce), + ce_to_dtype(ce), ce, &st, + error_type, o); - if (!lstat(ce->name, &st)) { - int dtype = ce_to_dtype(ce); - struct cache_entry *result; - - /* - * It may be that the 'lstat()' succeeded even though - * target 'ce' was absent, because there is an old - * entry that is different only in case.. - * - * Ignore that lstat() if it matches. - */ - if (ignore_case && icase_exists(o, ce, &st)) - return 0; - - if (o->dir && excluded(o->dir, ce->name, &dtype)) - /* - * ce->name is explicitly excluded, so it is Ok to - * overwrite it. - */ - return 0; - if (S_ISDIR(st.st_mode)) { - /* - * We are checking out path "foo" and - * found "foo/." in the working tree. - * This is tricky -- if we have modified - * files that are in "foo/" we would lose - * them. - */ - if (verify_clean_subdirectory(ce, error_type, o) < 0) - return -1; - return 0; - } - - /* - * The previous round may already have decided to - * delete this path, which is in a subdirectory that - * is being replaced with a blob. - */ - result = index_name_exists(&o->result, ce->name, ce_namelen(ce), 0); - if (result) { - if (result->ce_flags & CE_REMOVE) - return 0; - } - - return o->gently ? -1 : - add_rejected_path(o, error_type, ce->name); - } return 0; } + 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); } @@ -1226,10 +1412,23 @@ static int merged_entry(struct cache_entry *merge, struct cache_entry *old, 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)) { /* @@ -1245,8 +1444,8 @@ static int merged_entry(struct cache_entry *merge, struct cache_entry *old, } 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/unpack-trees.h b/unpack-trees.h index 7c0187d11a..cd11a08365 100644 --- a/unpack-trees.h +++ b/unpack-trees.h @@ -1,6 +1,8 @@ #ifndef UNPACK_TREES_H #define UNPACK_TREES_H +#include "string-list.h" + #define MAX_UNPACK_TREES 8 struct unpack_trees_options; @@ -29,11 +31,6 @@ enum unpack_trees_error_types { void setup_unpack_trees_porcelain(struct unpack_trees_options *opts, const char *cmd); -struct rejected_paths_list { - char *path; - struct rejected_paths_list *next; -}; - struct unpack_trees_options { unsigned int reset, merge, @@ -59,7 +56,7 @@ struct unpack_trees_options { * Store error messages in an array, each case * corresponding to a error message type */ - struct rejected_paths_list *unpack_rejects[NB_UNPACK_TREES_ERROR_TYPES]; + struct string_list unpack_rejects[NB_UNPACK_TREES_ERROR_TYPES]; int head_idx; int merge_size; diff --git a/upload-pack.c b/upload-pack.c index 92f9530c65..b40a43f27d 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -11,7 +11,7 @@ #include "list-objects.h" #include "run-command.h" -static const char upload_pack_usage[] = "git upload-pack [--strict] [--timeout=nn] <dir>"; +static const char upload_pack_usage[] = "git upload-pack [--strict] [--timeout=<n>] <dir>"; /* bits #0..7 in revision.h, #8..10 in commit.c */ #define THEY_HAVE (1u << 11) @@ -366,7 +366,7 @@ static int reachable(struct commit *want) { 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; @@ -387,7 +387,7 @@ static int reachable(struct commit *want) 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; @@ -125,3 +125,17 @@ char *url_decode_parameter_value(const char **query) struct strbuf out = STRBUF_INIT; return url_decode_internal(query, "&", &out, 1); } + +void end_url_with_slash(struct strbuf *buf, const char *url) +{ + strbuf_addstr(buf, url); + if (buf->len && buf->buf[buf->len - 1] != '/') + strbuf_addstr(buf, "/"); +} + +void str_end_url_with_slash(const char *url, char **dest) { + struct strbuf buf = STRBUF_INIT; + end_url_with_slash(&buf, url); + free(*dest); + *dest = strbuf_detach(&buf, NULL); +} @@ -7,4 +7,7 @@ extern char *url_decode(const char *url); extern char *url_decode_parameter_name(const char **query); extern char *url_decode_parameter_value(const char **query); +extern void end_url_with_slash(struct strbuf *buf, const char *url); +extern void str_end_url_with_slash(const char *url, char **dest); + #endif /* URL_H */ diff --git a/userdiff.c b/userdiff.c index e5522159b3..9ebf231ea5 100644 --- a/userdiff.c +++ b/userdiff.c @@ -9,7 +9,23 @@ static int drivers_alloc; #define PATTERNS(name, pattern, word_regex) \ { name, NULL, -1, { pattern, REG_EXTENDED }, word_regex } +#define IPATTERN(name, pattern, word_regex) \ + { name, NULL, -1, { pattern, REG_EXTENDED | REG_ICASE }, word_regex } static struct userdiff_driver builtin_drivers[] = { +IPATTERN("fortran", + "!^([C*]|[ \t]*!)\n" + "!^[ \t]*MODULE[ \t]+PROCEDURE[ \t]\n" + "^[ \t]*((END[ \t]+)?(PROGRAM|MODULE|BLOCK[ \t]+DATA" + "|([^'\" \t]+[ \t]+)*(SUBROUTINE|FUNCTION))[ \t]+[A-Z].*)$", + /* -- */ + "[a-zA-Z][a-zA-Z0-9_]*" + "|\\.([Ee][Qq]|[Nn][Ee]|[Gg][TtEe]|[Ll][TtEe]|[Tt][Rr][Uu][Ee]|[Ff][Aa][Ll][Ss][Ee]|[Aa][Nn][Dd]|[Oo][Rr]|[Nn]?[Ee][Qq][Vv]|[Nn][Oo][Tt])\\." + /* numbers and format statements like 2E14.4, or ES12.6, 9X. + * 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]+"), PATTERNS("java", @@ -36,7 +52,7 @@ PATTERNS("objc", "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->" "|[^[: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).*)$", @@ -45,6 +61,23 @@ PATTERNS("pascal", "|[-+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>]|~~|::" + "|&&=|\\|\\|=|//=|\\*\\*=" + "|&&|\\|\\||//|\\+\\+|--|\\*\\*|\\.\\.\\.?" + "|[-+*/%.^&<>=!|]=" + "|=~|!~" + "|<<|<>|<=>|>>" + "|[^[:space:]]"), PATTERNS("php", "^[\t ]*(((public|protected|private|static)[\t ]+)*function.*)$\n" "^[\t ]*(class.*)$", @@ -58,14 +91,14 @@ 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]+"), + "|[^[: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]+"), + "|[^[:space:]]|[\x80-\xff]+"), PATTERNS("bibtex", "(@[a-zA-Z]{1,}[ \t]*\\{{0,1}[ \t]*[^ \t\"@',\\#}{~%]*).*$", "[={}\"]|[^={}\" \t]+"), PATTERNS("tex", "^(\\\\((sub)*section|chapter|part)\\*{0,1}\\{.*)$", @@ -101,6 +134,7 @@ PATTERNS("csharp", { "default", NULL, -1, { NULL, 0 } }, }; #undef PATTERNS +#undef IPATTERN static struct userdiff_driver driver_true = { "diff=true", @@ -405,6 +405,15 @@ new_line: } } +int strbuf_add_wrapped_bytes(struct strbuf *buf, const char *data, int len, + int indent, int indent2, int width) +{ + char *tmp = xstrndup(data, len); + int r = strbuf_add_wrapped_text(buf, tmp, indent, indent2, width); + free(tmp); + return r; +} + int is_encoding_utf8(const char *name) { if (!name) @@ -10,6 +10,8 @@ int is_encoding_utf8(const char *name); int strbuf_add_wrapped_text(struct strbuf *buf, const char *text, int indent, int indent2, int width); +int strbuf_add_wrapped_bytes(struct strbuf *buf, const char *data, int len, + int indent, int indent2, int width); #ifndef NO_ICONV char *reencode_string(const char *in, const char *out_encoding, const char *in_encoding); diff --git a/vcs-svn/repo_tree.c b/vcs-svn/repo_tree.c index e94d91d129..e3d1fa3544 100644 --- a/vcs-svn/repo_tree.c +++ b/vcs-svn/repo_tree.c @@ -131,7 +131,7 @@ static void repo_write_dirent(uint32_t *path, uint32_t mode, 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) { @@ -142,7 +142,7 @@ static void repo_write_dirent(uint32_t *path, uint32_t mode, 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); diff --git a/vcs-svn/svndump.c b/vcs-svn/svndump.c index 53d0215d2d..2ad2c307dd 100644 --- a/vcs-svn/svndump.c +++ b/vcs-svn/svndump.c @@ -51,14 +51,14 @@ 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; } keys; static void reset_node_ctx(char *fname) @@ -85,6 +85,7 @@ 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; } @@ -105,6 +106,7 @@ static void init_keys(void) 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"); } static void read_props(void) @@ -206,7 +208,12 @@ void svndump_read(const char *url) *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 > 2) + die("expected svn dump format version <= 2, 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) diff --git a/vcs-svn/trp.h b/vcs-svn/trp.h index ee35c688a0..c32b9184e9 100644 --- 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 eb4c191875..5ca6b42edb 100644 --- a/vcs-svn/trp.txt +++ b/vcs-svn/trp.txt @@ -21,7 +21,9 @@ The caller: . 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`. @@ -73,10 +75,14 @@ int (*cmp)(node_type \*a, node_type \*b) 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):: @@ -207,7 +207,7 @@ static int mark_complete(const char *path, const unsigned char *sha1, int flag, 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; } @@ -3,16 +3,17 @@ */ #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; } @@ -52,7 +53,7 @@ void *xmalloc(size_t size) 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; @@ -108,21 +109,6 @@ void *xcalloc(size_t nmemb, size_t size) 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() @@ -212,118 +198,158 @@ FILE *xfdopen(int fd, const char *mode) 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) @@ -56,6 +56,16 @@ unsigned parse_whitespace_rule(const char *string) 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; } @@ -84,7 +94,7 @@ unsigned whitespace_rule(const char *pathname) 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 && @@ -93,7 +103,7 @@ unsigned whitespace_rule(const char *pathname) 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; @@ -174,8 +184,11 @@ static unsigned ws_check_emit_1(const char *line, int len, unsigned ws_rule, } } + if (trailing_whitespace == -1) + trailing_whitespace = len; + /* Check indentation */ - for (i = 0; i < len; i++) { + for (i = 0; i < trailing_whitespace; i++) { if (line[i] == ' ') continue; if (line[i] != '\t') @@ -203,7 +216,7 @@ static unsigned ws_check_emit_1(const char *line, int len, unsigned ws_rule, } /* 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); @@ -218,8 +231,6 @@ static unsigned ws_check_emit_1(const char *line, int len, unsigned ws_rule, * Now the rest of the line starts at "written". * The non-highlighted part ends at "trailing_whitespace". */ - if (trailing_whitespace == -1) - trailing_whitespace = len; /* Emit non-highlighted (middle) segment. */ if (trailing_whitespace - written > 0) { @@ -319,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; @@ -349,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; } @@ -362,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 54b6b03b9c..123582b6cb 100644 --- a/wt-status.c +++ b/wt-status.c @@ -21,11 +21,15 @@ static char default_wt_status_colors[][COLOR_MAXLEN] = { 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) @@ -88,7 +92,7 @@ static void wt_status_print_dirty_header(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) @@ -390,11 +394,9 @@ static void wt_status_collect_untracked(struct wt_status *s) fill_directory(&dir, s->pathspec); for (i = 0; i < dir.nr; i++) { struct dir_entry *ent = dir.entries[i]; - if (!cache_name_is_other(ent->name, ent->len)) - continue; - if (!match_pathspec(s->pathspec, ent->name, ent->len, 0, NULL)) - continue; - string_list_insert(&s->untracked, ent->name); + if (cache_name_is_other(ent->name, ent->len) && + match_pathspec(s->pathspec, ent->name, ent->len, 0, NULL)) + string_list_insert(&s->untracked, ent->name); free(ent); } @@ -404,11 +406,9 @@ static void wt_status_collect_untracked(struct wt_status *s) fill_directory(&dir, s->pathspec); for (i = 0; i < dir.nr; i++) { struct dir_entry *ent = dir.entries[i]; - if (!cache_name_is_other(ent->name, ent->len)) - continue; - if (!match_pathspec(s->pathspec, ent->name, ent->len, 0, NULL)) - continue; - string_list_insert(&s->ignored, ent->name); + if (cache_name_is_other(ent->name, ent->len) && + match_pathspec(s->pathspec, ent->name, ent->len, 0, NULL)) + string_list_insert(&s->ignored, ent->name); free(ent); } } @@ -629,7 +629,8 @@ static void wt_status_print_tracking(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 "; @@ -638,11 +639,12 @@ void wt_status_print(struct wt_status *s) 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); } @@ -748,10 +750,20 @@ static void wt_shortstatus_status(int null_termination, struct string_list_item const char *one; if (d->head_path) { one = quote_path(d->head_path, -1, &onebuf, s->prefix); + if (*one != '"' && strchr(one, ' ') != NULL) { + putchar('"'); + strbuf_addch(&onebuf, '"'); + one = onebuf.buf; + } printf("%s -> ", one); strbuf_release(&onebuf); } one = quote_path(it->string, -1, &onebuf, s->prefix); + if (*one != '"' && strchr(one, ' ') != NULL) { + putchar('"'); + strbuf_addch(&onebuf, '"'); + one = onebuf.buf; + } printf("%s\n", one); strbuf_release(&onebuf); } diff --git a/wt-status.h b/wt-status.h index 9df9c9fad2..20b17cf439 100644 --- a/wt-status.h +++ b/wt-status.h @@ -13,7 +13,9 @@ enum color_wt_status { 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 { @@ -46,7 +48,7 @@ struct wt_status { 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/xdiff-interface.c b/xdiff-interface.c index e1e054e4d9..164581f87f 100644 --- a/xdiff-interface.c +++ b/xdiff-interface.c @@ -212,8 +212,10 @@ int read_mmfile(mmfile_t *ptr, const char *filename) return error("Could not open %s", filename); sz = xsize_t(st.st_size); ptr->ptr = xmalloc(sz ? sz : 1); - if (sz && fread(ptr->ptr, sz, 1, f) != 1) + if (sz && fread(ptr->ptr, sz, 1, f) != 1) { + fclose(f); return error("Could not read %s", filename); + } fclose(f); ptr->size = sz; return 0; diff --git a/xdiff/xemit.c b/xdiff/xemit.c index c4bedf0d1c..277e2eec5b 100644 --- a/xdiff/xemit.c +++ b/xdiff/xemit.c @@ -85,27 +85,6 @@ static long def_ff(const char *rec, long len, char *buf, long sz, void *priv) return -1; } -static void xdl_find_func(xdfile_t *xf, long i, char *buf, long sz, long *ll, - find_func_t ff, void *ff_priv) { - - /* - * Be quite stupid about this for now. Find a line in the old file - * before the start of the hunk (and context) which starts with a - * plausible character. - */ - - const char *rec; - long len; - - while (i-- > 0) { - len = xdl_get_rec(xf, i, &rec); - if ((*ll = ff(rec, len, buf, sz, ff_priv)) >= 0) - return; - } - *ll = 0; -} - - static int xdl_emit_common(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, xdemitconf_t const *xecfg) { xdfile_t *xdf = &xe->xdf1; @@ -127,6 +106,7 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, xdchange_t *xch, *xche; char funcbuf[80]; long funclen = 0; + long funclineprev = -1; find_func_t ff = xecfg->find_func ? xecfg->find_func : def_ff; if (xecfg->flags & XDL_EMIT_COMMON) @@ -150,9 +130,19 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, */ if (xecfg->flags & XDL_EMIT_FUNCNAMES) { - xdl_find_func(&xe->xdf1, s1, funcbuf, - sizeof(funcbuf), &funclen, - ff, xecfg->find_func_priv); + long l; + for (l = s1 - 1; l >= 0 && l > funclineprev; l--) { + const char *rec; + long reclen = xdl_get_rec(&xe->xdf1, l, &rec); + long newfunclen = ff(rec, reclen, funcbuf, + sizeof(funcbuf), + xecfg->find_func_priv); + if (newfunclen >= 0) { + funclen = newfunclen; + break; + } + } + funclineprev = s1 - 1; } if (xdl_emit_hunk_hdr(s1 + 1, e1 - s1, s2 + 1, e2 - s2, funcbuf, funclen, ecb) < 0) diff --git a/xdiff/xmacros.h b/xdiff/xmacros.h index 8ef232cfad..165a895a93 100644 --- a/xdiff/xmacros.h +++ b/xdiff/xmacros.h @@ -30,6 +30,7 @@ #define XDL_MAX(a, b) ((a) > (b) ? (a): (b)) #define XDL_ABS(v) ((v) >= 0 ? (v): -(v)) #define XDL_ISDIGIT(c) ((c) >= '0' && (c) <= '9') +#define XDL_ISSPACE(c) (isspace((unsigned char)(c))) #define XDL_ADDBITS(v,b) ((v) + ((v) >> (b))) #define XDL_MASKBITS(b) ((1UL << (b)) - 1) #define XDL_HASHLONG(v,b) (XDL_ADDBITS((unsigned long)(v), b) & XDL_MASKBITS(b)) diff --git a/xdiff/xmerge.c b/xdiff/xmerge.c index 6d6fc1bc5e..9e13b25abc 100644 --- a/xdiff/xmerge.c +++ b/xdiff/xmerge.c @@ -336,7 +336,7 @@ static int xdl_refine_conflicts(xdfenv_t *xe1, xdfenv_t *xe2, xdmerge_t *m, static int line_contains_alnum(const char *ptr, long size) { while (size--) - if (isalnum(*(ptr++))) + if (isalnum((unsigned char)*(ptr++))) return 1; return 0; } diff --git a/xdiff/xutils.c b/xdiff/xutils.c index 22f9bd692c..ab6503460f 100644 --- a/xdiff/xutils.c +++ b/xdiff/xutils.c @@ -211,18 +211,18 @@ int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags) if (l1[i1++] != l2[i2++]) return 0; skip_ws: - while (i1 < s1 && isspace(l1[i1])) + while (i1 < s1 && XDL_ISSPACE(l1[i1])) i1++; - while (i2 < s2 && isspace(l2[i2])) + while (i2 < s2 && XDL_ISSPACE(l2[i2])) i2++; } } else if (flags & XDF_IGNORE_WHITESPACE_CHANGE) { while (i1 < s1 && i2 < s2) { - if (isspace(l1[i1]) && isspace(l2[i2])) { + if (XDL_ISSPACE(l1[i1]) && XDL_ISSPACE(l2[i2])) { /* Skip matching spaces and try again */ - while (i1 < s1 && isspace(l1[i1])) + while (i1 < s1 && XDL_ISSPACE(l1[i1])) i1++; - while (i2 < s2 && isspace(l2[i2])) + while (i2 < s2 && XDL_ISSPACE(l2[i2])) i2++; continue; } @@ -241,13 +241,13 @@ int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags) * while there still are characters remaining on both lines. */ if (i1 < s1) { - while (i1 < s1 && isspace(l1[i1])) + while (i1 < s1 && XDL_ISSPACE(l1[i1])) i1++; if (s1 != i1) return 0; } if (i2 < s2) { - while (i2 < s2 && isspace(l2[i2])) + while (i2 < s2 && XDL_ISSPACE(l2[i2])) i2++; return (s2 == i2); } @@ -260,10 +260,10 @@ static unsigned long xdl_hash_record_with_whitespace(char const **data, char const *ptr = *data; for (; ptr < top && *ptr != '\n'; ptr++) { - if (isspace(*ptr)) { + if (XDL_ISSPACE(*ptr)) { const char *ptr2 = ptr; int at_eol; - while (ptr + 1 < top && isspace(ptr[1]) + while (ptr + 1 < top && XDL_ISSPACE(ptr[1]) && ptr[1] != '\n') ptr++; at_eol = (top <= ptr + 1 || ptr[1] == '\n'); diff --git a/zlib.c b/zlib.c new file mode 100644 index 0000000000..c4d58da4e9 --- /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; +} |