diff options
153 files changed, 4877 insertions, 2700 deletions
diff --git a/.gitignore b/.gitignore index 2cf3ca5808..711fce7e08 100644 --- a/.gitignore +++ b/.gitignore @@ -101,7 +101,9 @@ /git-quiltimport /git-read-tree /git-rebase +/git-rebase--am /git-rebase--interactive +/git-rebase--merge /git-receive-pack /git-reflog /git-relink diff --git a/Documentation/RelNotes/1.7.6.txt b/Documentation/RelNotes/1.7.6.txt new file mode 100644 index 0000000000..f644378d21 --- /dev/null +++ b/Documentation/RelNotes/1.7.6.txt @@ -0,0 +1,95 @@ +Git v1.7.6 Release Notes (draft) +======================== + +Updates since v1.7.5 +-------------------- + + * Various git-svn updates. + + * Updates the way content tags are handled in gitweb. + + * Clean-up of the C part of i18n (but not l10n---please wait) + continues. + + * Processes spawned by "[alias] <name> = !process" in the configuration + can inspect GIT_PREFIX environment variable to learn where in the + working tree the original command was invoked. + + * "git blame" learned "--abbrev[=<n>]" option to control the minimum + number of hexdigits shown for commit object names. + + * "git diff -C -C" used to disable the rename detection entirely when + there are too many copy candidate paths in the tree; now it falls + back to "-C" when doing so would keep the copy candidate paths + under the rename detection limit. + + * "git diff" and its family of commands learned --dirstat=0 to show + directories that contribute less than 0.1% of changes. + + * "git diff" and its family of commands learned --dirstat=lines mode to + assess damage to the directory based on number of lines in the patch + output, not based on the similarity numbers. + + * "git format-patch" learned "--quiet" option to suppress the output of + the names of generated files. + + * "git format-patch" quotes people's names when it has RFC822 special + characters in it, e.g. "Junio C. Hamano" <jch@example.com>. Earlier + it was up to the user to do this when using its output. + + * "git log" and friends learned a new "--notes" option to replace the + "--show-notes" option. Unlike "--show-notes", "--notes=<ref>" does + not imply showing the default notes. + + * "git merge" learned "-" as a short-hand for "the previous branch", just + like the way "git checkout -" works. + + * "git rebase" that does not specify on top of which branch to rebase + the current branch now uses @{upstream} of the current branch. + + * "git rev-list --count" used with "--cherry-mark" counts the cherry-picked + commits separately, producing more a useful output. + + * "git submodule update" learned "--force" option to get rid of local + changes in submodules and replace them with the up-to-date version. + + * Compressed tarball gitweb generates is made without the timestamp of + the tarball generation; snapshot from the same tree should result in + a same tarball. + +Also contains various documentation updates and minor miscellaneous +changes. + + +Fixes since v1.7.5 +------------------ + +Unless otherwise noted, all the fixes in 1.7.5.X maintenance track are +included in this release. + + * "git add -p" did not work correctly when a hunk is split and then + one of them was given to the editor. + (merge jc/maint-add-p-overlapping-hunks later) + + * "git add -u" did not resolve a conflict where our history deleted and + their history modified the same file, and the working tree resolved to + keep a file. + (merge jc/fix-add-u-unmerged later) + + * "git config" used to choke with an insanely long line. + (merge ef/maint-strbuf-init later) + + * In "git merge", per-branch branch.<name>.mergeoptions configuration + variables did not override the fallback default merge.<option> + configuration variables such as merge.ff, merge.log, etc. + (merge jc/maint-branch-mergeoptions later) + + * "git send-pack" (hence "git push") over smalt-HTTP protocol could + deadlock when the client side pack-object died early. + (merge js/maint-send-pack-stateless-rpc-deadlock-fix later) + +--- +exec >/var/tmp/1 +echo O=$(git describe master) +O=v1.7.5.1-288-ge4ae6ef +git shortlog --no-merges ^maint ^$O master diff --git a/Documentation/SubmittingPatches b/Documentation/SubmittingPatches index c6a5032912..938eccf2a5 100644 --- a/Documentation/SubmittingPatches +++ b/Documentation/SubmittingPatches @@ -344,50 +344,20 @@ MUA specific hints Some of patches I receive or pick up from the list share common patterns of breakage. Please make sure your MUA is set up -properly not to corrupt whitespaces. Here are two common ones -I have seen: +properly not to corrupt whitespaces. -* Empty context lines that do not have _any_ whitespace. +See the DISCUSSION section of git-format-patch(1) for hints on +checking your patch by mailing it to yourself and applying with +git-am(1). -* Non empty context lines that have one extra whitespace at the - beginning. - -One test you could do yourself if your MUA is set up correctly is: - -* Send the patch to yourself, exactly the way you would, except - To: and Cc: lines, which would not contain the list and - maintainer address. - -* Save that patch to a file in UNIX mailbox format. Call it say - a.patch. - -* Try to apply to the tip of the "master" branch from the - git.git public repository: - - $ git fetch http://kernel.org/pub/scm/git/git.git master:test-apply - $ git checkout test-apply - $ git reset --hard - $ git am a.patch - -If it does not apply correctly, there can be various reasons. - -* Your patch itself does not apply cleanly. That is _bad_ but - does not have much to do with your MUA. Please rebase the - patch appropriately. - -* Your MUA corrupted your patch; "am" would complain that - the patch does not apply. Look at .git/rebase-apply/ subdirectory and - see what 'patch' file contains and check for the common - corruption patterns mentioned above. - -* While you are at it, check what are in 'info' and - 'final-commit' files as well. If what is in 'final-commit' is - not exactly what you would want to see in the commit log - message, it is very likely that your maintainer would end up - hand editing the log message when he applies your patch. - Things like "Hi, this is my first patch.\n", if you really - want to put in the patch e-mail, should come after the - three-dash line that signals the end of the commit message. +While you are at it, check the resulting commit log message from +a trial run of applying the patch. If what is in the resulting +commit is not exactly what you would want to see, it is very +likely that your maintainer would end up hand editing the log +message when he applies your patch. Things like "Hi, this is my +first patch.\n", if you really want to put in the patch e-mail, +should come after the three-dash line that signals the end of the +commit message. Pine @@ -443,89 +413,10 @@ that or Gentoo did it.) So you need to set the it. -Thunderbird ------------ - -(A Large Angry SCM) - -By default, Thunderbird will both wrap emails as well as flag them as -being 'format=flowed', both of which will make the resulting email unusable -by git. - -Here are some hints on how to successfully submit patches inline using -Thunderbird. - -There are two different approaches. One approach is to configure -Thunderbird to not mangle patches. The second approach is to use -an external editor to keep Thunderbird from mangling the patches. - -Approach #1 (configuration): - -This recipe is current as of Thunderbird 2.0.0.19. Three steps: - 1. Configure your mail server composition as plain text - Edit...Account Settings...Composition & Addressing, - uncheck 'Compose Messages in HTML'. - 2. Configure your general composition window to not wrap - Edit..Preferences..Composition, wrap plain text messages at 0 - 3. Disable the use of format=flowed - Edit..Preferences..Advanced..Config Editor. Search for: - mailnews.send_plaintext_flowed - toggle it to make sure it is set to 'false'. - -After that is done, you should be able to compose email as you -otherwise would (cut + paste, git-format-patch | git-imap-send, etc), -and the patches should not be mangled. - -Approach #2 (external editor): - -This recipe appears to work with the current [*1*] Thunderbird from Suse. - -The following Thunderbird extensions are needed: - AboutConfig 0.5 - http://aboutconfig.mozdev.org/ - External Editor 0.7.2 - http://globs.org/articles.php?lng=en&pg=8 - -1) Prepare the patch as a text file using your method of choice. - -2) Before opening a compose window, use Edit->Account Settings to -uncheck the "Compose messages in HTML format" setting in the -"Composition & Addressing" panel of the account to be used to send the -patch. [*2*] - -3) In the main Thunderbird window, _before_ you open the compose window -for the patch, use Tools->about:config to set the following to the -indicated values: - mailnews.send_plaintext_flowed => false - mailnews.wraplength => 0 - -4) Open a compose window and click the external editor icon. - -5) In the external editor window, read in the patch file and exit the -editor normally. - -6) Back in the compose window: Add whatever other text you wish to the -message, complete the addressing and subject fields, and press send. - -7) Optionally, undo the about:config/account settings changes made in -steps 2 & 3. +Thunderbird, KMail, GMail +------------------------- - -[Footnotes] -*1* Version 1.0 (20041207) from the MozillaThunderbird-1.0-5 rpm of Suse -9.3 professional updates. - -*2* It may be possible to do this with about:config and the following -settings but I haven't tried, yet. - mail.html_compose => false - mail.identity.default.compose_html => false - mail.identity.id?.compose_html => false - -(Lukas Sandström) - -There is a script in contrib/thunderbird-patch-inline which can help -you include patches with Thunderbird in an easy way. To use it, do the -steps above and then use the script as the external editor. +See the MUA-SPECIFIC HINTS section of git-format-patch(1). Gnus ---- @@ -540,71 +431,3 @@ characters (most notably in people's names), and also whitespaces (fatal in patches). Running 'C-u g' to display the message in raw form before using '|' to run the pipe can work this problem around. - - -KMail ------ - -This should help you to submit patches inline using KMail. - -1) Prepare the patch as a text file. - -2) Click on New Mail. - -3) Go under "Options" in the Composer window and be sure that -"Word wrap" is not set. - -4) Use Message -> Insert file... and insert the patch. - -5) Back in the compose window: add whatever other text you wish to the -message, complete the addressing and subject fields, and press send. - - -Gmail ------ - -GMail does not appear to have any way to turn off line wrapping in the web -interface, so this will mangle any emails that you send. You can however -use "git send-email" and send your patches through the GMail SMTP server, or -use any IMAP email client to connect to the google IMAP server and forward -the emails through that. - -To use "git send-email" and send your patches through the GMail SMTP server, -edit ~/.gitconfig to specify your account settings: - -[sendemail] - smtpencryption = tls - smtpserver = smtp.gmail.com - smtpuser = user@gmail.com - smtppass = p4ssw0rd - smtpserverport = 587 - -Once your commits are ready to be sent to the mailing list, run the -following commands: - - $ git format-patch --cover-letter -M origin/master -o outgoing/ - $ edit outgoing/0000-* - $ git send-email outgoing/* - -To submit using the IMAP interface, first, edit your ~/.gitconfig to specify your -account settings: - -[imap] - folder = "[Gmail]/Drafts" - host = imaps://imap.gmail.com - user = user@gmail.com - pass = p4ssw0rd - port = 993 - sslverify = false - -You might need to instead use: folder = "[Google Mail]/Drafts" if you get an error -that the "Folder doesn't exist". - -Once your commits are ready to be sent to the mailing list, run the -following commands: - - $ git format-patch --cover-letter -M --stdout origin/master | git imap-send - -Just make sure to disable line wrapping in the email client (GMail web -interface will line wrap no matter what, so you need to use a real -IMAP client). diff --git a/Documentation/config.txt b/Documentation/config.txt index 0906499e7d..285c7f73ca 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -587,6 +587,8 @@ it will be treated as a shell command. For example, defining "gitk --all --not ORIG_HEAD". Note that shell commands will be executed from the top-level directory of a repository, which may not necessarily be the current directory. +'GIT_PREFIX' is set as returned by running 'git rev-parse --show-prefix' +from the original current directory. See linkgit:git-rev-parse[1]. am.keepcr:: If true, git-am will call git-mailsplit for patches in mbox format @@ -641,7 +643,7 @@ branch.<name>.remote:: branch.<name>.merge:: Defines, together with branch.<name>.remote, the upstream branch - for the given branch. It tells 'git fetch'/'git pull' which + for the given branch. It tells 'git fetch'/'git pull'/'git rebase' which branch to merge and can also affect 'git push' (see push.default). When in branch <name>, it tells 'git fetch' the default refspec to be marked for merging in FETCH_HEAD. The value is @@ -706,9 +708,16 @@ second is the background. The position of the attribute, if any, doesn't matter. color.diff:: - When set to `always`, always use colors in patch. - When false (or `never`), never. When set to `true` or `auto`, use - colors only when the output is to the terminal. Defaults to false. + Whether to use ANSI escape sequences to add color to patches. + If this is set to `always`, linkgit:git-diff[1], + linkgit:git-log[1], and linkgit:git-show[1] will use color + for all patches. If it is set to `true` or `auto`, those + commands will only use color when output is to the terminal. + Defaults to false. ++ +This does not affect linkgit:git-format-patch[1] nor the +'git-diff-{asterisk}' plumbing commands. Can be overridden on the +command line with the `--color[=<when>]` option. color.diff.<slot>:: Use customized color for diff colorization. `<slot>` specifies @@ -794,11 +803,15 @@ color.status.<slot>:: color.branch.<slot>. color.ui:: - When set to `always`, always use colors in all git commands which - are capable of colored output. When false (or `never`), never. When - set to `true` or `auto`, use colors only when the output is to the - terminal. When more specific variables of color.* are set, they always - take precedence over this setting. Defaults to false. + This variable determines the default value for variables such + as `color.diff` and `color.grep` that control the use of color + per command family. Its scope will expand as more commands learn + configuration to set a default for the `--color` option. Set it + to `always` if you want all output not intended for machine + consumption to use color, to `true` or `auto` if you want such + output to use color when written to the terminal, or to `false` or + `never` if you prefer git commands not to use color unless enabled + explicitly with some other configuration or the `--color` option. commit.status:: A boolean to enable/disable inclusion of status information in the @@ -810,68 +823,7 @@ commit.template:: "{tilde}/" is expanded to the value of `$HOME` and "{tilde}user/" to the specified user's home directory. -diff.autorefreshindex:: - When using 'git diff' to compare with work tree - files, do not consider stat-only change as changed. - Instead, silently run `git update-index --refresh` to - update the cached stat information for paths whose - contents in the work tree match the contents in the - index. This option defaults to true. Note that this - affects only 'git diff' Porcelain, and not lower level - 'diff' commands such as 'git diff-files'. - -diff.external:: - If this config variable is set, diff generation is not - performed using the internal diff machinery, but using the - given command. Can be overridden with the `GIT_EXTERNAL_DIFF' - environment variable. The command is called with parameters - as described under "git Diffs" in linkgit:git[1]. Note: if - you want to use an external diff program only on a subset of - your files, you might want to use linkgit:gitattributes[5] instead. - -diff.mnemonicprefix:: - If set, 'git diff' uses a prefix pair that is different from the - standard "a/" and "b/" depending on what is being compared. When - this configuration is in effect, reverse diff output also swaps - the order of the prefixes: -`git diff`;; - compares the (i)ndex and the (w)ork tree; -`git diff HEAD`;; - compares a (c)ommit and the (w)ork tree; -`git diff --cached`;; - compares a (c)ommit and the (i)ndex; -`git diff HEAD:file1 file2`;; - compares an (o)bject and a (w)ork tree entity; -`git diff --no-index a b`;; - compares two non-git things (1) and (2). - -diff.noprefix:: - If set, 'git diff' does not show any source or destination prefix. - -diff.renameLimit:: - The number of files to consider when performing the copy/rename - detection; equivalent to the 'git diff' option '-l'. - -diff.renames:: - Tells git to detect renames. If set to any boolean value, it - will enable basic rename detection. If set to "copies" or - "copy", it will detect copies, as well. - -diff.ignoreSubmodules:: - Sets the default value of --ignore-submodules. Note that this - affects only 'git diff' Porcelain, and not lower level 'diff' - commands such as 'git diff-files'. 'git checkout' also honors - this setting when reporting uncommitted changes. - -diff.suppressBlankEmpty:: - A boolean to inhibit the standard behavior of printing a space - before each empty output line. Defaults to false. - -diff.tool:: - Controls which diff tool is used. `diff.tool` overrides - `merge.tool` when used by linkgit:git-difftool[1] and has - the same valid values as `merge.tool` minus "tortoisemerge" - and plus "kompare". +include::diff-config.txt[] difftool.<tool>.path:: Override the path for the given tool. This is useful in case @@ -975,6 +927,16 @@ format.signoff:: the rights to submit this work under the same open source license. Please see the 'SubmittingPatches' document for further discussion. +filter.<driver>.clean:: + The command which is used to convert the content of a worktree + file to a blob upon checkin. See linkgit:gitattributes[5] for + details. + +filter.<driver>.smudge:: + The command which is used to convert the content of a blob + object to a worktree file upon checkout. See + linkgit:gitattributes[5] for details. + gc.aggressiveWindow:: The window size parameter used in the delta compression algorithm used by 'git gc --aggressive'. This defaults @@ -1347,8 +1309,9 @@ instaweb.port:: interactive.singlekey:: In interactive commands, allow the user to provide one-letter input with a single key (i.e., without hitting enter). - Currently this is used only by the `\--patch` mode of - linkgit:git-add[1]. Note that this setting is silently + Currently this is used by the `\--patch` mode of + linkgit:git-add[1], linkgit:git-reset[1], linkgit:git-stash[1] and + linkgit:git-checkout[1]. Note that this setting is silently ignored if portable keystroke input is not available. log.date:: diff --git a/Documentation/diff-config.txt b/Documentation/diff-config.txt new file mode 100644 index 0000000000..1aed79e7dc --- /dev/null +++ b/Documentation/diff-config.txt @@ -0,0 +1,136 @@ +diff.autorefreshindex:: + When using 'git diff' to compare with work tree + files, do not consider stat-only change as changed. + Instead, silently run `git update-index --refresh` to + update the cached stat information for paths whose + contents in the work tree match the contents in the + index. This option defaults to true. Note that this + affects only 'git diff' Porcelain, and not lower level + 'diff' commands such as 'git diff-files'. + +diff.dirstat:: + A comma separated list of `--dirstat` parameters specifying the + default behavior of the `--dirstat` option to linkgit:git-diff[1]` + and friends. The defaults can be overridden on the command line + (using `--dirstat=<param1,param2,...>`). The fallback defaults + (when not changed by `diff.dirstat`) are `changes,noncumulative,3`. + The following parameters are available: ++ +-- +`changes`;; + Compute the dirstat numbers by counting the lines that have been + removed from the source, or added to the destination. This ignores + the amount of pure code movements within a file. In other words, + rearranging lines in a file is not counted as much as other changes. + This is the default behavior when no parameter is given. +`lines`;; + Compute the dirstat numbers by doing the regular line-based diff + analysis, and summing the removed/added line counts. (For binary + files, count 64-byte chunks instead, since binary files have no + natural concept of lines). This is a more expensive `--dirstat` + behavior than the `changes` behavior, but it does count rearranged + lines within a file as much as other changes. The resulting output + is consistent with what you get from the other `--*stat` options. +`files`;; + Compute the dirstat numbers by counting the number of files changed. + Each changed file counts equally in the dirstat analysis. This is + the computationally cheapest `--dirstat` behavior, since it does + not have to look at the file contents at all. +`cumulative`;; + Count changes in a child directory for the parent directory as well. + Note that when using `cumulative`, the sum of the percentages + reported may exceed 100%. The default (non-cumulative) behavior can + be specified with the `noncumulative` parameter. +<limit>;; + An integer parameter specifies a cut-off percent (3% by default). + Directories contributing less than this percentage of the changes + are not shown in the output. +-- ++ +Example: The following will count changed files, while ignoring +directories with less than 10% of the total amount of changed files, +and accumulating child directory counts in the parent directories: +`files,10,cumulative`. + +diff.external:: + If this config variable is set, diff generation is not + performed using the internal diff machinery, but using the + given command. Can be overridden with the `GIT_EXTERNAL_DIFF' + environment variable. The command is called with parameters + as described under "git Diffs" in linkgit:git[1]. Note: if + you want to use an external diff program only on a subset of + your files, you might want to use linkgit:gitattributes[5] instead. + +diff.ignoreSubmodules:: + Sets the default value of --ignore-submodules. Note that this + affects only 'git diff' Porcelain, and not lower level 'diff' + commands such as 'git diff-files'. 'git checkout' also honors + this setting when reporting uncommitted changes. + +diff.mnemonicprefix:: + If set, 'git diff' uses a prefix pair that is different from the + standard "a/" and "b/" depending on what is being compared. When + this configuration is in effect, reverse diff output also swaps + the order of the prefixes: +`git diff`;; + compares the (i)ndex and the (w)ork tree; +`git diff HEAD`;; + compares a (c)ommit and the (w)ork tree; +`git diff --cached`;; + compares a (c)ommit and the (i)ndex; +`git diff HEAD:file1 file2`;; + compares an (o)bject and a (w)ork tree entity; +`git diff --no-index a b`;; + compares two non-git things (1) and (2). + +diff.noprefix:: + If set, 'git diff' does not show any source or destination prefix. + +diff.renameLimit:: + The number of files to consider when performing the copy/rename + detection; equivalent to the 'git diff' option '-l'. + +diff.renames:: + Tells git to detect renames. If set to any boolean value, it + will enable basic rename detection. If set to "copies" or + "copy", it will detect copies, as well. + +diff.suppressBlankEmpty:: + A boolean to inhibit the standard behavior of printing a space + before each empty output line. Defaults to false. + +diff.<driver>.command:: + The custom diff driver command. See linkgit:gitattributes[5] + for details. + +diff.<driver>.xfuncname:: + The regular expression that the diff driver should use to + recognize the hunk header. A built-in pattern may also be used. + See linkgit:gitattributes[5] for details. + +diff.<driver>.binary:: + Set this option to true to make the diff driver treat files as + binary. See linkgit:gitattributes[5] for details. + +diff.<driver>.textconv:: + The command that the diff driver should call to generate the + text-converted version of a file. The result of the + conversion is used to generate a human-readable diff. See + linkgit:gitattributes[5] for details. + +diff.<driver>.wordregex:: + The regular expression that the diff driver should use to + split words in a line. See linkgit:gitattributes[5] for + details. + +diff.<driver>.cachetextconv:: + Set this option to true to make the diff driver cache the text + conversion outputs. See linkgit:gitattributes[5] for details. + +diff.tool:: + The diff tool to be used by linkgit:git-difftool[1]. This + option overrides `merge.tool`, and has the same valid built-in + values as `merge.tool` minus "tortoisemerge" and plus + "kompare". Any other value is treated as a custom diff tool, + and there must be a corresponding `difftool.<tool>.cmd` + option. diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index c32105f1ed..c7ed946357 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -66,19 +66,49 @@ endif::git-format-patch[] number of modified files, as well as number of added and deleted lines. ---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 are not - counted for the parent directory, unless `--cumulative` is used. +--dirstat[=<param1,param2,...>]:: + Output the distribution of relative amount of changes for each + sub-directory. The behavior of `--dirstat` can be customized by + passing it a comma separated list of parameters. + The defaults are controlled by the `diff.dirstat` configuration + variable (see linkgit:git-config[1]). + The following parameters are available: + -Note that the `--dirstat` option computes the changes while ignoring -the amount of pure code movements within a file. In other words, -rearranging lines in a file is not counted as much as other changes. - ---dirstat-by-file[=<limit>]:: - Same as `--dirstat`, but counts changed files instead of lines. +-- +`changes`;; + Compute the dirstat numbers by counting the lines that have been + removed from the source, or added to the destination. This ignores + the amount of pure code movements within a file. In other words, + rearranging lines in a file is not counted as much as other changes. + This is the default behavior when no parameter is given. +`lines`;; + Compute the dirstat numbers by doing the regular line-based diff + analysis, and summing the removed/added line counts. (For binary + files, count 64-byte chunks instead, since binary files have no + natural concept of lines). This is a more expensive `--dirstat` + behavior than the `changes` behavior, but it does count rearranged + lines within a file as much as other changes. The resulting output + is consistent with what you get from the other `--*stat` options. +`files`;; + Compute the dirstat numbers by counting the number of files changed. + Each changed file counts equally in the dirstat analysis. This is + the computationally cheapest `--dirstat` behavior, since it does + not have to look at the file contents at all. +`cumulative`;; + Count changes in a child directory for the parent directory as well. + Note that when using `cumulative`, the sum of the percentages + reported may exceed 100%. The default (non-cumulative) behavior can + be specified with the `noncumulative` parameter. +<limit>;; + An integer parameter specifies a cut-off percent (3% by default). + Directories contributing less than this percentage of the changes + are not shown in the output. +-- ++ +Example: The following will count changed files, while ignoring +directories with less than 10% of the total amount of changed files, +and accumulating child directory counts in the parent directories: +`--dirstat=files,10,cumulative`. --summary:: Output a condensed summary of extended header information @@ -124,12 +154,19 @@ any of those replacements occurred. --color[=<when>]:: Show colored diff. - The value must be always (the default), never, or auto. + The value must be `always` (the default for `<when>`), `never`, or `auto`. + The default value is `never`. +ifdef::git-diff[] + It can be changed by the `color.ui` and `color.diff` + configuration settings. +endif::git-diff[] --no-color:: - Turn off colored diff, even when the configuration file - gives the default to color output. - Same as `--color=never`. + Turn off colored diff. +ifdef::git-diff[] + This can be used to override configuration settings. +endif::git-diff[] + It is the same as `--color=never`. --word-diff[=<mode>]:: Show a word diff, using the <mode> to delimit changed words. @@ -263,6 +300,19 @@ endif::git-log[] projects, so use it with caution. Giving more than one `-C` option has the same effect. +-D:: +--irreversible-delete:: + Omit the preimage for deletes, i.e. print only the header but not + the diff between the preimage and `/dev/null`. The resulting patch + is not meant to be applied with `patch` nor `git apply`; this is + solely for people who want to just concentrate on reviewing the + text after the change. In addition, the output obviously lack + enough information to apply such a patch in reverse, even manually, + hence the name of the option. ++ +When used together with `-B`, omit also the preimage in the deletion part +of a delete/create pair. + -l<num>:: The `-M` and `-C` options require O(n^2) processing time where n is the number of potential rename/copy targets. This diff --git a/Documentation/git-add.txt b/Documentation/git-add.txt index 35cb5d3f64..9c1d395722 100644 --- a/Documentation/git-add.txt +++ b/Documentation/git-add.txt @@ -274,7 +274,8 @@ patch:: This lets you choose one path out of a 'status' like selection. After choosing the path, it presents the diff between the index and the working tree file and asks you if you want to stage - the change of each hunk. You can say: + the change of each hunk. You can select one of the following + options and type return: y - stage this hunk n - do not stage this hunk @@ -293,6 +294,9 @@ patch:: + After deciding the fate for all hunks, if there is any hunk that was chosen, the index is updated with the selected hunks. ++ +You can omit having to type return here, by setting the configuration +variable `interactive.singlekey` to `true`. diff:: diff --git a/Documentation/git-blame.txt b/Documentation/git-blame.txt index c4d1ff86c9..bb8edb4abc 100644 --- a/Documentation/git-blame.txt +++ b/Documentation/git-blame.txt @@ -9,7 +9,7 @@ SYNOPSIS -------- [verse] '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>] + [-S <revs-file>] [-M] [-C] [-C] [-C] [--since=<date>] [--abbrev=<n>] [<rev> | --contents <file> | --reverse <rev>] [--] <file> DESCRIPTION @@ -73,6 +73,11 @@ include::blame-options.txt[] Ignore whitespace when comparing the parent's version and the child's to find where the lines came from. +--abbrev=<n>:: + Instead of using the default 7+1 hexadecimal digits as the + abbreviated object name, use <n>+1 digits. Note that 1 column + is used for a caret to mark the boundary commit. + THE PORCELAIN FORMAT -------------------- diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt index 1063f69023..c0a96e6c1e 100644 --- a/Documentation/git-checkout.txt +++ b/Documentation/git-checkout.txt @@ -12,7 +12,7 @@ SYNOPSIS 'git checkout' [-q] [-f] [-m] [--detach] [<commit>] 'git checkout' [-q] [-f] [-m] [[-b|-B|--orphan] <new_branch>] [<start_point>] 'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <paths>... -'git checkout' --patch [<tree-ish>] [--] [<paths>...] +'git checkout' [-p|--patch] [<tree-ish>] [--] [<paths>...] DESCRIPTION ----------- @@ -45,7 +45,7 @@ $ git checkout <branch> that is to say, the branch is not reset/created unless "git checkout" is successful. -'git checkout' [--patch] [<tree-ish>] [--] <pathspec>...:: +'git checkout' [-p|--patch] [<tree-ish>] [--] <pathspec>...:: When <paths> or `--patch` are given, 'git checkout' does *not* switch branches. It updates the named paths in the working tree @@ -183,7 +183,8 @@ the conflicted merge in the specified paths. working tree (and if a <tree-ish> was specified, the index). + This means that you can use `git checkout -p` to selectively discard -edits from your current working tree. +edits from your current working tree. See the ``Interactive Mode'' +section of linkgit:git-add[1] to learn how to operate the `\--patch` mode. <branch>:: Branch to checkout; if it refers to a branch (i.e., a name that, diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt index 66aba8a2e9..d13c9b23f7 100644 --- a/Documentation/git-format-patch.txt +++ b/Documentation/git-format-patch.txt @@ -289,6 +289,175 @@ title is likely to be different from the subject of the discussion the patch is in response to, so it is likely that you would want to keep the Subject: line, like the example above. +Checking for patch corruption +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Many mailers if not set up properly will corrupt whitespace. Here are +two common types of corruption: + +* Empty context lines that do not have _any_ whitespace. + +* Non-empty context lines that have one extra whitespace at the + beginning. + +One way to test if your MUA is set up correctly is: + +* Send the patch to yourself, exactly the way you would, except + with To: and Cc: lines that do not contain the list and + maintainer address. + +* Save that patch to a file in UNIX mailbox format. Call it a.patch, + say. + +* Apply it: + + $ git fetch <project> master:test-apply + $ git checkout test-apply + $ git reset --hard + $ git am a.patch + +If it does not apply correctly, there can be various reasons. + +* The patch itself does not apply cleanly. That is _bad_ but + does not have much to do with your MUA. You might want to rebase + the patch with linkgit:git-rebase[1] before regenerating it in + this case. + +* The MUA corrupted your patch; "am" would complain that + the patch does not apply. Look in the .git/rebase-apply/ subdirectory and + see what 'patch' file contains and check for the common + corruption patterns mentioned above. + +* While at it, check the 'info' and 'final-commit' files as well. + If what is in 'final-commit' is not exactly what you would want to + see in the commit log message, it is very likely that the + receiver would end up hand editing the log message when applying + your patch. Things like "Hi, this is my first patch.\n" in the + patch e-mail should come after the three-dash line that signals + the end of the commit message. + +MUA-SPECIFIC HINTS +------------------ +Here are some hints on how to successfully submit patches inline using +various mailers. + +GMail +~~~~~ +GMail does not have any way to turn off line wrapping in the web +interface, so it will mangle any emails that you send. You can however +use "git send-email" and send your patches through the GMail SMTP server, or +use any IMAP email client to connect to the google IMAP server and forward +the emails through that. + +For hints on using 'git send-email' to send your patches through the +GMail SMTP server, see the EXAMPLE section of linkgit:git-send-email[1]. + +For hints on submission using the IMAP interface, see the EXAMPLE +section of linkgit:git-imap-send[1]. + +Thunderbird +~~~~~~~~~~~ +By default, Thunderbird will both wrap emails as well as flag +them as being 'format=flowed', both of which will make the +resulting email unusable by git. + +There are three different approaches: use an add-on to turn off line wraps, +configure Thunderbird to not mangle patches, or use +an external editor to keep Thunderbird from mangling the patches. + +Approach #1 (add-on) +^^^^^^^^^^^^^^^^^^^^ + +Install the Toggle Word Wrap add-on that is available from +https://addons.mozilla.org/thunderbird/addon/toggle-word-wrap/ +It adds a menu entry "Enable Word Wrap" in the composer's "Options" menu +that you can tick off. Now you can compose the message as you otherwise do +(cut + paste, 'git format-patch' | 'git imap-send', etc), but you have to +insert line breaks manually in any text that you type. + +Approach #2 (configuration) +^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Three steps: + +1. Configure your mail server composition as plain text: + Edit...Account Settings...Composition & Addressing, + uncheck "Compose Messages in HTML". + +2. Configure your general composition window to not wrap. ++ +In Thunderbird 2: +Edit..Preferences..Composition, wrap plain text messages at 0 ++ +In Thunderbird 3: +Edit..Preferences..Advanced..Config Editor. Search for +"mail.wrap_long_lines". +Toggle it to make sure it is set to `false`. + +3. Disable the use of format=flowed: +Edit..Preferences..Advanced..Config Editor. Search for +"mailnews.send_plaintext_flowed". +Toggle it to make sure it is set to `false`. + +After that is done, you should be able to compose email as you +otherwise would (cut + paste, 'git format-patch' | 'git imap-send', etc), +and the patches will not be mangled. + +Approach #3 (external editor) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The following Thunderbird extensions are needed: +AboutConfig from http://aboutconfig.mozdev.org/ and +External Editor from http://globs.org/articles.php?lng=en&pg=8 + +1. Prepare the patch as a text file using your method of choice. + +2. Before opening a compose window, use Edit->Account Settings to + uncheck the "Compose messages in HTML format" setting in the + "Composition & Addressing" panel of the account to be used to + send the patch. + +3. In the main Thunderbird window, 'before' you open the compose + window for the patch, use Tools->about:config to set the + following to the indicated values: ++ +---------- + mailnews.send_plaintext_flowed => false + mailnews.wraplength => 0 +---------- + +4. Open a compose window and click the external editor icon. + +5. In the external editor window, read in the patch file and exit + the editor normally. + +Side note: it may be possible to do step 2 with +about:config and the following settings but no one's tried yet. + +---------- + mail.html_compose => false + mail.identity.default.compose_html => false + mail.identity.id?.compose_html => false +---------- + +There is a script in contrib/thunderbird-patch-inline which can help +you include patches with Thunderbird in an easy way. To use it, do the +steps above and then use the script as the external editor. + +KMail +~~~~~ +This should help you to submit patches inline using KMail. + +1. Prepare the patch as a text file. + +2. Click on New Mail. + +3. Go under "Options" in the Composer window and be sure that + "Word wrap" is not set. + +4. Use Message -> Insert file... and insert the patch. + +5. Back in the compose window: add whatever other text you wish to the + message, complete the addressing and subject fields, and press send. + EXAMPLES -------- diff --git a/Documentation/git-imap-send.txt b/Documentation/git-imap-send.txt index d3013d6a29..4e09708cc9 100644 --- a/Documentation/git-imap-send.txt +++ b/Documentation/git-imap-send.txt @@ -111,6 +111,31 @@ Using direct mode with SSL: .......................... +EXAMPLE +------- +To submit patches using GMail's IMAP interface, first, edit your ~/.gitconfig +to specify your account settings: + +--------- +[imap] + folder = "[Gmail]/Drafts" + host = imaps://imap.gmail.com + user = user@gmail.com + port = 993 + sslverify = false +--------- + +You might need to instead use: folder = "[Google Mail]/Drafts" if you get an error +that the "Folder doesn't exist". + +Once the commits are ready to be sent, run the following command: + + $ git format-patch --cover-letter -M --stdout origin/master | git imap-send + +Just make sure to disable line wrapping in the email client (GMail's web +interface will wrap lines no matter what, so you need to use a real +IMAP client). + CAUTION ------- It is still your responsibility to make sure that the email message @@ -124,6 +149,10 @@ Thunderbird in particular is known to be problematic. Thunderbird users may wish to visit this web page for more information: http://kb.mozillazine.org/Plain_text_e-mail_-_Thunderbird#Completely_plain_email +SEE ALSO +-------- +linkgit:git-format-patch[1], linkgit:git-send-email[1], mbox(5) + GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-log.txt b/Documentation/git-log.txt index 2c84028838..de5c0d37a5 100644 --- a/Documentation/git-log.txt +++ b/Documentation/git-log.txt @@ -178,9 +178,9 @@ May be an unabbreviated ref name or a glob and may be specified multiple times. A warning will be issued for refs that do not exist, but a glob that does not match any refs is silently ignored. + -This setting can be disabled by the `--no-standard-notes` option, +This setting can be disabled by the `--no-notes` option, overridden by the 'GIT_NOTES_DISPLAY_REF' environment variable, -and supplemented by the `--show-notes` option. +and overridden by the `--notes=<ref>` option. GIT --- diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt index 296f314eae..913ecd8c43 100644 --- a/Documentation/git-notes.txt +++ b/Documentation/git-notes.txt @@ -57,8 +57,11 @@ list:: add:: Add notes for a given object (defaults to HEAD). Abort if the - object already has notes (use `-f` to overwrite an - existing note). + object already has notes (use `-f` to overwrite existing notes). + However, if you're using `add` interactively (using an editor + to supply the notes contents), then - instead of aborting - + the existing notes will be opened in the editor (like the `edit` + subcommand). copy:: Copy the notes for the first object onto the second object. diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index 620d50e71f..9a075bc4d2 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -9,7 +9,7 @@ SYNOPSIS -------- [verse] 'git rebase' [-i | --interactive] [options] [--onto <newbase>] - <upstream> [<branch>] + [<upstream>] [<branch>] 'git rebase' [-i | --interactive] [options] --onto <newbase> --root [<branch>] @@ -21,6 +21,12 @@ If <branch> is specified, 'git rebase' will perform an automatic `git checkout <branch>` before doing anything else. Otherwise it remains on the current branch. +If <upstream> is not specified, the upstream configured in +branch.<name>.remote and branch.<name>.merge options will be used; see +linkgit:git-config[1] for details. If you are currently not on any +branch or if the current branch does not have a configured upstream, +the rebase will abort. + All changes made by commits in the current branch but that are not in <upstream> are saved to a temporary area. This is the same set of commits that would be shown by `git log <upstream>..HEAD` (or @@ -217,7 +223,8 @@ leave out at most one of A and B, in which case it defaults to HEAD. <upstream>:: Upstream branch to compare against. May be any valid commit, - not just an existing branch name. + not just an existing branch name. Defaults to the configured + upstream for the current branch. <branch>:: Working branch; defaults to HEAD. diff --git a/Documentation/git-reset.txt b/Documentation/git-reset.txt index 8481f9db74..b2832fc7eb 100644 --- a/Documentation/git-reset.txt +++ b/Documentation/git-reset.txt @@ -9,7 +9,7 @@ SYNOPSIS -------- [verse] 'git reset' [-q] [<commit>] [--] <paths>... -'git reset' --patch [<commit>] [--] [<paths>...] +'git reset' [--patch|-p] [<commit>] [--] [<paths>...] 'git reset' [--soft | --mixed | --hard | --merge | --keep] [-q] [<commit>] DESCRIPTION @@ -39,8 +39,9 @@ working tree in one go. and <commit> (defaults to HEAD). The chosen hunks are applied in reverse to the index. + -This means that `git reset -p` is the opposite of `git add -p` (see -linkgit:git-add[1]). +This means that `git reset -p` is the opposite of `git add -p`, i.e. +you can use it to selectively reset hunks. See the ``Interactive Mode'' +section of linkgit:git-add[1] to learn how to operate the `\--patch` mode. 'git reset' [--<mode>] [<commit>]:: This form resets the current branch head to <commit> and diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt index ee14f74fd3..5a168cfab2 100644 --- a/Documentation/git-send-email.txt +++ b/Documentation/git-send-email.txt @@ -348,10 +348,12 @@ sendemail.confirm:: one of 'always', 'never', 'cc', 'compose', or 'auto'. See '--confirm' in the previous section for the meaning of these values. +EXAMPLE +------- Use gmail as the smtp server ----------------------------- - -Add the following section to the config file: +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +To use 'git send-email' to send your patches through the GMail SMTP server, +edit ~/.gitconfig to specify your account settings: [sendemail] smtpencryption = tls @@ -359,9 +361,20 @@ Add the following section to the config file: smtpuser = yourname@gmail.com smtpserverport = 587 +Once your commits are ready to be sent to the mailing list, run the +following commands: + + $ git format-patch --cover-letter -M origin/master -o outgoing/ + $ edit outgoing/0000-* + $ git send-email outgoing/* + Note: the following perl modules are required Net::SMTP::SSL, MIME::Base64 and Authen::SASL +SEE ALSO +-------- +linkgit:git-format-patch[1], linkgit:git-imap-send[1], mbox(5) + GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt index 79abc38e50..15f051fa44 100644 --- a/Documentation/git-stash.txt +++ b/Documentation/git-stash.txt @@ -13,7 +13,7 @@ SYNOPSIS 'git stash' drop [-q|--quiet] [<stash>] 'git stash' ( pop | apply ) [--index] [-q|--quiet] [<stash>] 'git stash' branch <branchname> [<stash>] -'git stash' [save [--patch] [-k|--[no-]keep-index] [-q|--quiet] [<message>]] +'git stash' [save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet] [<message>]] 'git stash' clear 'git stash' create @@ -42,7 +42,7 @@ is also possible). OPTIONS ------- -save [--patch] [--[no-]keep-index] [-q|--quiet] [<message>]:: +save [-p|--patch] [--[no-]keep-index] [-q|--quiet] [<message>]:: Save your local modifications to a new 'stash', and run `git reset --hard` to revert them. The <message> part is optional and gives @@ -54,12 +54,13 @@ save [--patch] [--[no-]keep-index] [-q|--quiet] [<message>]:: If the `--keep-index` option is used, all changes already added to the index are left intact. + -With `--patch`, you can interactively select hunks from in the diff +With `--patch`, you can interactively select hunks from the diff between HEAD and the working tree to be stashed. The stash entry is constructed such that its index state is the same as the index state of your repository, and its worktree contains only the changes you selected interactively. The selected changes are then rolled back -from your worktree. +from your worktree. See the ``Interactive Mode'' section of +linkgit:git-add[1] to learn how to operate the `\--patch` mode. + The `--patch` option implies `--keep-index`. You can use `--no-keep-index` to override this. diff --git a/Documentation/git-submodule.txt b/Documentation/git-submodule.txt index 1a16ff6044..5e7a4130ee 100644 --- a/Documentation/git-submodule.txt +++ b/Documentation/git-submodule.txt @@ -186,8 +186,10 @@ OPTIONS -f:: --force:: - This option is only valid for the add command. - Allow adding an otherwise ignored submodule path. + This option is only valid for add and update commands. + When running add, allow adding an otherwise ignored submodule path. + When running update, throw away local changes in submodules when + switching to a different commit. --cached:: This option is only valid for status and summary commands. These diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index 509dc03376..713e523034 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -339,6 +339,8 @@ Any other arguments are passed directly to 'git log' Empty directories are automatically recreated when using "git svn clone" and "git svn rebase", so "mkdirs" is intended for use after commands like "git checkout" or "git reset". + (See the svn-remote.<name>.automkdirs config file option for + more information.) 'commit-diff':: Commits the diff of two tree-ish arguments from the @@ -680,6 +682,14 @@ svn.pathnameencoding:: locales to avoid corrupted file names with non-ASCII characters. Valid encodings are the ones supported by Perl's Encode module. +svn-remote.<name>.automkdirs:: + Normally, the "git svn clone" and "git svn rebase" commands + attempt to recreate empty directories that are in the + Subversion repository. If this option is set to "false", then + empty directories will only be created if the "git svn mkdirs" + command is run explicitly. If unset, 'git svn' assumes this + option to be "true". + Since the noMetadata, rewriteRoot, rewriteUUID, useSvnsyncProps and useSvmProps options all affect the metadata generated and used by 'git svn'; they *must* be set in the configuration file before any history is imported @@ -774,10 +784,9 @@ use `git svn rebase` to update your work branch instead of `git pull` or when committing into SVN, which can lead to merge commits reversing previous commits in SVN. -DESIGN PHILOSOPHY ------------------ -Merge tracking in Subversion is lacking and doing branched development -with Subversion can be cumbersome as a result. While 'git svn' can track +MERGE TRACKING +-------------- +While 'git svn' can track copy history (including branches and tags) for repositories adopting a standard layout, it cannot yet represent merge history that happened inside git back upstream to SVN users. Therefore it is advised that @@ -787,16 +796,15 @@ compatibility with SVN (see the CAVEATS section below). CAVEATS ------- -For the sake of simplicity and interoperating with a less-capable system -(SVN), it is recommended that all 'git svn' users clone, fetch and dcommit +For the sake of simplicity and interoperating with Subversion, +it is recommended that all 'git svn' users clone, fetch and dcommit directly from the SVN server, and avoid all 'git clone'/'pull'/'merge'/'push' operations between git repositories and branches. The recommended method of exchanging code between git branches and users is 'git format-patch' and 'git am', or just 'dcommit'ing to the SVN repository. Running 'git merge' or 'git pull' is NOT recommended on a branch you -plan to 'dcommit' from. Subversion does not represent merges in any -reasonable or useful fashion; so users using Subversion cannot see any +plan to 'dcommit' from because Subversion users cannot see any merges you've made. Furthermore, if you merge or pull from a git branch that is a mirror of an SVN branch, 'dcommit' may commit to the wrong branch. @@ -846,7 +854,7 @@ Renamed and copied directories are not detected by git and hence not tracked when committing to SVN. I do not plan on adding support for this as it's quite difficult and time-consuming to get working for all the possible corner cases (git doesn't do it, either). Committing -renamed and copied files are fully supported if they're similar enough +renamed and copied files is fully supported if they're similar enough for git to detect them. CONFIGURATION diff --git a/Documentation/git.txt b/Documentation/git.txt index 78420b133a..f1292fc6ce 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -9,7 +9,7 @@ git - the stupid content tracker SYNOPSIS -------- [verse] -'git' [--version] [--exec-path[=<path>]] [--html-path] +'git' [--version] [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path] [-p|--paginate|--no-pager] [--no-replace-objects] [--bare] [--git-dir=<path>] [--work-tree=<path>] [-c <name>=<value>] @@ -288,8 +288,16 @@ help ...`. the current setting and then exit. --html-path:: - Print the path to wherever your git HTML documentation is installed - and exit. + Print the path, without trailing slash, where git's HTML + documentation is installed and exit. + +--man-path:: + Print the manpath (see `man(1)`) for the man pages for + this version of git and exit. + +--info-path:: + Print the path where the Info files documenting this + version of git are installed and exit. -p:: --paginate:: diff --git a/Documentation/pretty-options.txt b/Documentation/pretty-options.txt index 50923e2ce9..d5c977262a 100644 --- a/Documentation/pretty-options.txt +++ b/Documentation/pretty-options.txt @@ -30,19 +30,34 @@ people using 80-column terminals. preferred by the user. For non plumbing commands this defaults to UTF-8. ---no-notes:: ---show-notes[=<ref>]:: +--notes[=<ref>]:: Show the notes (see linkgit:git-notes[1]) that annotate the commit, when showing the commit log message. This is the default for `git log`, `git show` and `git whatchanged` commands when - there is no `--pretty`, `--format` nor `--oneline` option is - given on the command line. + there is no `--pretty`, `--format` nor `--oneline` option given + on the command line. ++ +By default, the notes shown are from the notes refs listed in the +'core.notesRef' and 'notes.displayRef' variables (or corresponding +environment overrides). See linkgit:git-config[1] for more details. ++ +With an optional '<ref>' argument, show this notes ref instead of the +default notes ref(s). The ref is taken to be in `refs/notes/` if it +is not qualified. + -With an optional argument, add this ref to the list of notes. The ref -is taken to be in `refs/notes/` if it is not qualified. +Multiple --notes options can be combined to control which notes are +being displayed. Examples: "--notes=foo" will show only notes from +"refs/notes/foo"; "--notes=foo --notes" will show both notes from +"refs/notes/foo" and from the default notes ref(s). +--no-notes:: + Do not show notes. This negates the above `--notes` option, by + resetting the list of notes refs from which notes are shown. + Options are parsed in the order given on the command line, so e.g. + "--notes --notes=foo --no-notes --notes=bar" will only show notes + from "refs/notes/bar". + +--show-notes[=<ref>]:: --[no-]standard-notes:: - Enable or disable populating the notes ref list from the - 'core.notesRef' and 'notes.displayRef' variables (or - corresponding environment overrides). Enabled by default. - See linkgit:git-config[1]. + These options are deprecated. Use the above --notes/--no-notes + options instead. diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt index 73111bb051..52bae31fcb 100644 --- a/Documentation/rev-list-options.txt +++ b/Documentation/rev-list-options.txt @@ -730,7 +730,10 @@ ifdef::git-rev-list[] Print a number stating how many commits would have been listed, and suppress all other output. When used together with '--left-right', instead print the counts for left and - right commits, separated by a tab. + right commits, separated by a tab. When used together with + '--cherry-mark', omit patch equivalent commits from these + counts and print the count for equivalent commits separated + by a tab. endif::git-rev-list[] diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 38ecb5314d..7f48227494 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v1.7.5.1 +DEF_VER=v1.7.5.GIT LF=' ' @@ -323,9 +323,7 @@ GCOV = gcov export TCL_PATH TCLTK_PATH -# sparse is architecture-neutral, which means that we need to tell it -# explicitly what architecture to check for. Fix this up for yours.. -SPARSE_FLAGS = -D__BIG_ENDIAN__ -D__powerpc__ +SPARSE_FLAGS = @@ -370,7 +368,6 @@ SCRIPT_SH += git-merge-resolve.sh SCRIPT_SH += git-mergetool.sh SCRIPT_SH += git-pull.sh SCRIPT_SH += git-quiltimport.sh -SCRIPT_SH += git-rebase--interactive.sh SCRIPT_SH += git-rebase.sh SCRIPT_SH += git-repack.sh SCRIPT_SH += git-request-pull.sh @@ -380,6 +377,9 @@ SCRIPT_SH += git-web--browse.sh SCRIPT_LIB += git-mergetool--lib SCRIPT_LIB += git-parse-remote +SCRIPT_LIB += git-rebase--am +SCRIPT_LIB += git-rebase--interactive +SCRIPT_LIB += git-rebase--merge SCRIPT_LIB += git-sh-setup SCRIPT_PERL += git-add--interactive.perl @@ -527,6 +527,7 @@ LIB_H += list-objects.h LIB_H += ll-merge.h LIB_H += log-tree.h LIB_H += mailmap.h +LIB_H += merge-file.h LIB_H += merge-recursive.h LIB_H += notes.h LIB_H += notes-cache.h @@ -924,6 +925,7 @@ ifeq ($(uname_O),Cygwin) X = .exe COMPAT_OBJS += compat/cygwin.o UNRELIABLE_FSTAT = UnfortunatelyYes + SPARSE_FLAGS = -isystem /usr/include/w32api -Wno-one-bit-signed-bitfield endif ifeq ($(uname_S),FreeBSD) NEEDS_LIBICONV = YesPlease @@ -1177,6 +1179,7 @@ ifneq (,$(findstring MINGW,$(uname_S))) EXTLIBS += -lws2_32 PTHREAD_LIBS = X = .exe + SPARSE_FLAGS = -Wno-one-bit-signed-bitfield ifneq (,$(wildcard ../THIS_IS_MSYSGIT)) htmldir=doc/git/html/ prefix = @@ -1580,6 +1583,7 @@ ifndef V QUIET_LNCP = @echo ' ' LN/CP $@; QUIET_XGETTEXT = @echo ' ' XGETTEXT $@; QUIET_GCOV = @echo ' ' GCOV $@; + QUIET_SP = @echo ' ' SP $<; QUIET_SUBDIR0 = +@subdir= QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \ $(MAKE) $(PRINT_DIR) -C $$subdir @@ -1675,17 +1679,19 @@ strip: $(PROGRAMS) git$X $(STRIP) $(STRIP_OPTS) $(PROGRAMS) git$X git.o: common-cmds.h -git.s git.o: EXTRA_CPPFLAGS = -DGIT_VERSION='"$(GIT_VERSION)"' \ - '-DGIT_HTML_PATH="$(htmldir_SQ)"' +git.sp git.s git.o: EXTRA_CPPFLAGS = -DGIT_VERSION='"$(GIT_VERSION)"' \ + '-DGIT_HTML_PATH="$(htmldir_SQ)"' \ + '-DGIT_MAN_PATH="$(mandir_SQ)"' \ + '-DGIT_INFO_PATH="$(infodir_SQ)"' 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 +help.sp help.o: common-cmds.h -builtin/help.o: common-cmds.h -builtin/help.s builtin/help.o: EXTRA_CPPFLAGS = \ +builtin/help.sp builtin/help.o: common-cmds.h +builtin/help.sp builtin/help.s builtin/help.o: EXTRA_CPPFLAGS = \ '-DGIT_HTML_PATH="$(htmldir_SQ)"' \ '-DGIT_MAN_PATH="$(mandir_SQ)"' \ '-DGIT_INFO_PATH="$(infodir_SQ)"' @@ -1945,30 +1951,34 @@ $(VCSSVN_OBJS) $(VCSSVN_TEST_OBJS): $(LIB_H) \ test-svn-fe.o: vcs-svn/svndump.h endif -exec_cmd.s exec_cmd.o: EXTRA_CPPFLAGS = \ +exec_cmd.sp exec_cmd.s exec_cmd.o: EXTRA_CPPFLAGS = \ '-DGIT_EXEC_PATH="$(gitexecdir_SQ)"' \ '-DBINDIR="$(bindir_relative_SQ)"' \ '-DPREFIX="$(prefix_SQ)"' -builtin/init-db.s builtin/init-db.o: EXTRA_CPPFLAGS = \ +builtin/init-db.sp builtin/init-db.s builtin/init-db.o: EXTRA_CPPFLAGS = \ -DDEFAULT_GIT_TEMPLATE_DIR='"$(template_dir_SQ)"' -config.s config.o: EXTRA_CPPFLAGS = -DETC_GITCONFIG='"$(ETC_GITCONFIG_SQ)"' +config.sp config.s config.o: EXTRA_CPPFLAGS = \ + -DETC_GITCONFIG='"$(ETC_GITCONFIG_SQ)"' -attr.s attr.o: EXTRA_CPPFLAGS = -DETC_GITATTRIBUTES='"$(ETC_GITATTRIBUTES_SQ)"' +attr.sp attr.s attr.o: EXTRA_CPPFLAGS = \ + -DETC_GITATTRIBUTES='"$(ETC_GITATTRIBUTES_SQ)"' -http.s http.o: EXTRA_CPPFLAGS = -DGIT_HTTP_USER_AGENT='"git/$(GIT_VERSION)"' +http.sp http.s http.o: EXTRA_CPPFLAGS = \ + -DGIT_HTTP_USER_AGENT='"git/$(GIT_VERSION)"' ifdef NO_EXPAT -http-walker.s http-walker.o: EXTRA_CPPFLAGS = -DNO_EXPAT +http-walker.sp http-walker.s http-walker.o: EXTRA_CPPFLAGS = -DNO_EXPAT endif ifdef NO_REGEX -compat/regex/regex.o: EXTRA_CPPFLAGS = -DGAWK -DNO_MBSUPPORT +compat/regex/regex.sp compat/regex/regex.o: EXTRA_CPPFLAGS = \ + -DGAWK -DNO_MBSUPPORT endif ifdef USE_NED_ALLOCATOR -compat/nedmalloc/nedmalloc.o: EXTRA_CPPFLAGS = \ +compat/nedmalloc/nedmalloc.sp compat/nedmalloc/nedmalloc.o: EXTRA_CPPFLAGS = \ -DNDEBUG -DOVERRIDE_STRDUP -DREPLACE_SYSTEM_ALLOCATOR endif @@ -2134,13 +2144,20 @@ test-%$X: test-%.o $(GITLIBS) check-sha1:: test-sha1$X ./test-sha1.sh +SP_OBJ = $(patsubst %.o,%.sp,$(C_OBJ)) + +$(SP_OBJ): %.sp: %.c GIT-CFLAGS FORCE + $(QUIET_SP)cgcc -no-compile $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) \ + $(SPARSE_FLAGS) $< + +.PHONY: sparse $(SP_OBJ) +sparse: $(SP_OBJ) + check: common-cmds.h - if sparse; \ + @if sparse; \ then \ - for i in $(patsubst %.o, %.c, $(GIT_OBJS)); \ - do \ - sparse $(ALL_CFLAGS) $(SPARSE_FLAGS) $$i || exit; \ - done; \ + echo 2>&1 "Use 'make sparse' instead"; \ + $(MAKE) --no-print-directory sparse; \ else \ echo 2>&1 "Did you mean 'make test'?"; \ exit 1; \ @@ -1 +1 @@ -Documentation/RelNotes/1.7.5.2.txt
\ No newline at end of file +Documentation/RelNotes/1.7.6.txt
\ No newline at end of file @@ -157,6 +157,7 @@ int write_archive_entries(struct archiver_args *args, struct archiver_context context; struct unpack_trees_options opts; struct tree_desc t; + struct pathspec pathspec; int err; if (args->baselen > 0 && args->base[args->baselen - 1] == '/') { @@ -191,8 +192,10 @@ int write_archive_entries(struct archiver_args *args, git_attr_set_direction(GIT_ATTR_INDEX, &the_index); } - err = read_tree_recursive(args->tree, "", 0, 0, args->pathspec, + init_pathspec(&pathspec, args->pathspec); + err = read_tree_recursive(args->tree, "", 0, 0, &pathspec, write_archive_entry, &context); + free_pathspec(&pathspec); if (err == READ_TREE_RECURSIVE) err = 0; return err; @@ -221,11 +224,14 @@ static int reject_entry(const unsigned char *sha1, const char *base, static int path_exists(struct tree *tree, const char *path) { - const char *pathspec[] = { path, NULL }; - - if (read_tree_recursive(tree, "", 0, 0, pathspec, reject_entry, NULL)) - return 1; - return 0; + const char *paths[] = { path, NULL }; + struct pathspec pathspec; + int ret; + + init_pathspec(&pathspec, paths); + ret = read_tree_recursive(tree, "", 0, 0, &pathspec, reject_entry, NULL); + free_pathspec(&pathspec); + return ret != 0; } static void parse_pathspec_arg(const char **pathspec, @@ -465,7 +465,7 @@ static void drop_attr_stack(void) } } -const char *git_etc_gitattributes(void) +static const char *git_etc_gitattributes(void) { static const char *system_wide; if (!system_wide) @@ -473,7 +473,7 @@ const char *git_etc_gitattributes(void) return system_wide; } -int git_attr_system(void) +static int git_attr_system(void) { return !git_env_bool("GIT_ATTR_NOSYSTEM", 0); } diff --git a/builtin/blame.c b/builtin/blame.c index 41525f1ea1..4242e4b513 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -41,6 +41,7 @@ static int reverse; static int blank_boundary; static int incremental; static int xdl_opts; +static int abbrev = -1; static enum date_mode blame_date_mode = DATE_ISO8601; static size_t blame_date_width; @@ -1670,7 +1671,7 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt) cp = nth_line(sb, ent->lno); for (cnt = 0; cnt < ent->num_lines; cnt++) { char ch; - int length = (opt & OUTPUT_LONG_OBJECT_NAME) ? 40 : 8; + int length = (opt & OUTPUT_LONG_OBJECT_NAME) ? 40 : abbrev; if (suspect->commit->object.flags & UNINTERESTING) { if (blank_boundary) @@ -2310,6 +2311,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix) { OPTION_CALLBACK, 'C', NULL, &opt, "score", "Find line copies within and across files", PARSE_OPT_OPTARG, blame_copy_callback }, { OPTION_CALLBACK, 'M', NULL, &opt, "score", "Find line movements within and across files", PARSE_OPT_OPTARG, blame_move_callback }, OPT_CALLBACK('L', NULL, &bottomtop, "n,m", "Process only line range n,m, counting from 1", blame_bottomtop_callback), + OPT__ABBREV(&abbrev), OPT_END() }; @@ -2345,6 +2347,11 @@ int cmd_blame(int argc, const char **argv, const char *prefix) parse_done: argc = parse_options_end(&ctx); + if (abbrev == -1) + abbrev = default_abbrev; + /* one more abbrev length is needed for the boundary commit */ + abbrev++; + if (revs_file && read_ancestry(revs_file)) die_errno("reading graft file '%s' failed", revs_file); diff --git a/builtin/checkout.c b/builtin/checkout.c index eece5d6ac0..4761769512 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -79,7 +79,10 @@ static int update_some(const unsigned char *sha1, const char *base, int baselen, static int read_tree_some(struct tree *tree, const char **pathspec) { - read_tree_recursive(tree, "", 0, 0, pathspec, update_some, NULL); + struct pathspec ps; + init_pathspec(&ps, pathspec); + read_tree_recursive(tree, "", 0, 0, &ps, update_some, NULL); + free_pathspec(&ps); /* update the index with the given tree's info * for all args, expanding wildcards, and exit @@ -648,18 +651,30 @@ static void suggest_reattach(struct commit *commit, struct rev_info *revs) if (more == 1) describe_one_orphan(&sb, last); else - strbuf_addf(&sb, " ... and %d more.\n", more); + strbuf_addf(&sb, _(" ... and %d more.\n"), more); } fprintf(stderr, - "Warning: you are leaving %d commit%s behind, " + Q_( + /* The singular version */ + "Warning: you are leaving %d commit behind, " + "not connected to\n" + "any of your branches:\n\n" + "%s\n" + "If you want to keep it by creating a new branch, " + "this may be a good time\nto do so with:\n\n" + " git branch new_branch_name %s\n\n", + /* The plural version */ + "Warning: you are leaving %d commits behind, " "not connected to\n" "any of your branches:\n\n" "%s\n" "If you want to keep them by creating a new branch, " "this may be a good time\nto do so with:\n\n" " git branch new_branch_name %s\n\n", - lost, ((1 < lost) ? "s" : ""), + /* Give ngettext() the count */ + lost), + lost, sb.buf, sha1_to_hex(commit->object.sha1)); strbuf_release(&sb); @@ -961,9 +976,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) die (_("--patch is incompatible with all other options")); if (opts.force_detach && (opts.new_branch || opts.new_orphan_branch)) - die("--detach cannot be used with -b/-B/--orphan"); + die(_("--detach cannot be used with -b/-B/--orphan")); if (opts.force_detach && 0 < opts.track) - die("--detach cannot be used with -t"); + die(_("--detach cannot be used with -t")); /* --track without -b should DWIM */ if (0 < opts.track && !opts.new_branch) { @@ -1043,7 +1058,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) } if (opts.force_detach) - die("git checkout: --detach does not take a path argument"); + die(_("git checkout: --detach does not take a path argument")); if (1 < !!opts.writeout_stage + !!opts.force + !!opts.merge) die(_("git checkout: --ours/--theirs, --force and --merge are incompatible when\nchecking out of the index.")); diff --git a/builtin/clone.c b/builtin/clone.c index 4144bcf5ca..49c838fd3f 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -417,7 +417,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) if (path) repo = xstrdup(absolute_path(repo_name)); else if (!strchr(repo_name, ':')) - die("repository '%s' does not exist", repo_name); + die(_("repository '%s' does not exist"), repo_name); else repo = repo_name; is_local = path && !is_bundle; diff --git a/builtin/commit.c b/builtin/commit.c index 67757e999f..411d5e4153 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -615,6 +615,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, const char *hook_arg1 = NULL; const char *hook_arg2 = NULL; int ident_shown = 0; + int clean_message_contents = (cleanup_mode != CLEANUP_NONE); if (!no_verify && run_hook(index_file, "pre-commit", NULL)) return 0; @@ -681,6 +682,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, if (strbuf_read_file(&sb, template_file, 0) < 0) die_errno(_("could not read '%s'"), template_file); hook_arg1 = "template"; + clean_message_contents = 0; } /* @@ -708,7 +710,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, if (s->fp == NULL) die_errno(_("could not open '%s'"), git_path(commit_editmsg)); - if (cleanup_mode != CLEANUP_NONE) + if (clean_message_contents) stripspace(&sb, 0); if (signoff) { diff --git a/builtin/diff-tree.c b/builtin/diff-tree.c index 0d2a3e9fa2..be6417d166 100644 --- a/builtin/diff-tree.c +++ b/builtin/diff-tree.c @@ -163,6 +163,9 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix) } if (read_stdin) { + int saved_nrl = 0; + int saved_dcctc = 0; + if (opt->diffopt.detect_rename) opt->diffopt.setup |= (DIFF_SETUP_USE_SIZE_CACHE | DIFF_SETUP_USE_CACHE); @@ -173,9 +176,16 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix) fputs(line, stdout); fflush(stdout); } - else + else { diff_tree_stdin(line); + if (saved_nrl < opt->diffopt.needed_rename_limit) + saved_nrl = opt->diffopt.needed_rename_limit; + if (opt->diffopt.degraded_cc_to_c) + saved_dcctc = 1; + } } + opt->diffopt.degraded_cc_to_c = saved_dcctc; + opt->diffopt.needed_rename_limit = saved_nrl; } return diff_result_code(&opt->diffopt, 0); diff --git a/builtin/diff.c b/builtin/diff.c index 717fa1a341..14bd14fce0 100644 --- a/builtin/diff.c +++ b/builtin/diff.c @@ -202,7 +202,6 @@ static void refresh_index_quietly(void) static int builtin_diff_files(struct rev_info *revs, int argc, const char **argv) { - int result; unsigned int options = 0; while (1 < argc && argv[1][0] == '-') { @@ -236,8 +235,7 @@ static int builtin_diff_files(struct rev_info *revs, int argc, const char **argv perror("read_cache_preload"); return -1; } - result = run_diff_files(revs, options); - return diff_result_code(&revs->diffopt, result); + return run_diff_files(revs, options); } int cmd_diff(int argc, const char **argv, const char *prefix) diff --git a/builtin/grep.c b/builtin/grep.c index 10a1f65310..3ee2ec51de 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -533,18 +533,18 @@ static int grep_cache(struct grep_opt *opt, const struct pathspec *pathspec, int static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec, struct tree_desc *tree, struct strbuf *base, int tn_len) { - int hit = 0, matched = 0; + int hit = 0, match = 0; struct name_entry entry; int old_baselen = base->len; while (tree_entry(tree, &entry)) { int te_len = tree_entry_len(entry.path, entry.sha1); - if (matched != 2) { - matched = tree_entry_interesting(&entry, base, tn_len, pathspec); - if (matched == -1) - break; /* no more matches */ - if (!matched) + if (match != 2) { + match = tree_entry_interesting(&entry, base, tn_len, pathspec); + if (match < 0) + break; + if (match == 0) continue; } diff --git a/builtin/index-pack.c b/builtin/index-pack.c index 31f001f105..e40451ffb4 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -1,4 +1,4 @@ -#include "cache.h" +#include "builtin.h" #include "delta.h" #include "pack.h" #include "csum-file.h" diff --git a/builtin/init-db.c b/builtin/init-db.c index b7370d9bb8..ba13a54793 100644 --- a/builtin/init-db.c +++ b/builtin/init-db.c @@ -319,10 +319,10 @@ int set_git_dir_init(const char *git_dir, const char *real_git_dir, struct stat st; if (!exist_ok && !stat(git_dir, &st)) - die("%s already exists", git_dir); + die(_("%s already exists"), git_dir); if (!exist_ok && !stat(real_git_dir, &st)) - die("%s already exists", real_git_dir); + die(_("%s already exists"), real_git_dir); /* * make sure symlinks are resolved because we'll be @@ -351,15 +351,15 @@ static void separate_git_dir(const char *git_dir) else if (S_ISDIR(st.st_mode)) src = git_link; else - die("unable to handle file type %d", st.st_mode); + die(_("unable to handle file type %d"), st.st_mode); if (rename(src, git_dir)) - die_errno("unable to move %s to %s", src, git_dir); + die_errno(_("unable to move %s to %s"), src, git_dir); } fp = fopen(git_link, "w"); if (!fp) - die("Could not create git link %s", git_link); + die(_("Could not create git link %s"), git_link); fprintf(fp, "gitdir: %s\n", git_dir); fclose(fp); } diff --git a/builtin/log.c b/builtin/log.c index c6dce9b895..f6219909a7 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -25,12 +25,15 @@ static const char *default_date_mode = NULL; static int default_show_root = 1; static int decoration_style; +static int decoration_given; static const char *fmt_patch_subject_prefix = "PATCH"; static const char *fmt_pretty; -static const char * const builtin_log_usage = +static const char * const builtin_log_usage[] = { "git log [<options>] [<since>..<until>] [[--] <path>...]\n" - " or: git show [options] <object>..."; + " or: git show [options] <object>...", + NULL +}; static int parse_decoration_style(const char *var, const char *value) { @@ -49,6 +52,23 @@ static int parse_decoration_style(const char *var, const char *value) return -1; } +static int decorate_callback(const struct option *opt, const char *arg, int unset) +{ + if (unset) + decoration_style = 0; + else if (arg) + decoration_style = parse_decoration_style("command line", arg); + else + decoration_style = DECORATE_SHORT_REFS; + + if (decoration_style < 0) + die("invalid --decorate option: %s", arg); + + decoration_given = 1; + + return 0; +} + static void cmd_log_init_defaults(struct rev_info *rev) { rev->abbrev = DEFAULT_ABBREV; @@ -68,17 +88,28 @@ static void cmd_log_init_defaults(struct rev_info *rev) static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, struct rev_info *rev, struct setup_revision_opt *opt) { - int i; - int decoration_given = 0; struct userformat_want w; - /* - * Check for -h before setup_revisions(), or "git log -h" will - * fail when run without a git directory. - */ - if (argc == 2 && !strcmp(argv[1], "-h")) - usage(builtin_log_usage); + int quiet = 0, source = 0; + + const struct option builtin_log_options[] = { + OPT_BOOLEAN(0, "quiet", &quiet, "supress diff output"), + OPT_BOOLEAN(0, "source", &source, "show source"), + { OPTION_CALLBACK, 0, "decorate", NULL, NULL, "decorate options", + PARSE_OPT_OPTARG, decorate_callback}, + OPT_END() + }; + + argc = parse_options(argc, argv, prefix, + builtin_log_options, builtin_log_usage, + PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN | + PARSE_OPT_KEEP_DASHDASH); + argc = setup_revisions(argc, argv, rev, opt); + /* Any arguments at this point are not recognized */ + if (argc > 1) + die("unrecognized argument: %s", argv[1]); + memset(&w, 0, sizeof(w)); userformat_find_requirements(NULL, &w); @@ -94,26 +125,9 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, if (rev->diffopt.pathspec.nr != 1) usage("git logs can only follow renames on one pathname at a time"); } - for (i = 1; i < argc; i++) { - const char *arg = argv[i]; - if (!strcmp(arg, "--decorate")) { - decoration_style = DECORATE_SHORT_REFS; - decoration_given = 1; - } else if (!prefixcmp(arg, "--decorate=")) { - const char *v = skip_prefix(arg, "--decorate="); - decoration_style = parse_decoration_style(arg, v); - if (decoration_style < 0) - die(_("invalid --decorate option: %s"), arg); - decoration_given = 1; - } else if (!strcmp(arg, "--no-decorate")) { - decoration_style = 0; - } else if (!strcmp(arg, "--source")) { - rev->show_source = 1; - } else if (!strcmp(arg, "-h")) { - usage(builtin_log_usage); - } else - die(_("unrecognized argument: %s"), arg); - } + + if (source) + rev->show_source = 1; /* * defeat log.decorate configuration interacting with --pretty=raw @@ -256,6 +270,8 @@ static void finish_early_output(struct rev_info *rev) static int cmd_log_walk(struct rev_info *rev) { struct commit *commit; + int saved_nrl = 0; + int saved_dcctc = 0; if (rev->early_output) setup_early_output(rev); @@ -286,7 +302,14 @@ static int cmd_log_walk(struct rev_info *rev) } free_commit_list(commit->parents); commit->parents = NULL; + if (saved_nrl < rev->diffopt.needed_rename_limit) + saved_nrl = rev->diffopt.needed_rename_limit; + if (rev->diffopt.degraded_cc_to_c) + saved_dcctc = 1; } + rev->diffopt.degraded_cc_to_c = saved_dcctc; + rev->diffopt.needed_rename_limit = saved_nrl; + if (rev->diffopt.output_format & DIFF_FORMAT_CHECKDIFF && DIFF_OPT_TST(&rev->diffopt, CHECK_FAILED)) { return 02; @@ -405,6 +428,7 @@ int cmd_show(int argc, const char **argv, const char *prefix) struct rev_info rev; struct object_array_entry *objects; struct setup_revision_opt opt; + struct pathspec match_all; int i, count, ret = 0; git_config(git_log_config, NULL); @@ -412,6 +436,7 @@ int cmd_show(int argc, const char **argv, const char *prefix) if (diff_use_color_default == -1) diff_use_color_default = git_use_color_default; + init_pathspec(&match_all, NULL); init_revisions(&rev, prefix); rev.diff = 1; rev.always_show_header = 1; @@ -458,7 +483,7 @@ int cmd_show(int argc, const char **argv, const char *prefix) diff_get_color_opt(&rev.diffopt, DIFF_COMMIT), name, diff_get_color_opt(&rev.diffopt, DIFF_RESET)); - read_tree_recursive((struct tree *)o, "", 0, 0, NULL, + read_tree_recursive((struct tree *)o, "", 0, 0, &match_all, show_tree_object, NULL); rev.shown_one = 1; break; diff --git a/builtin/ls-files.c b/builtin/ls-files.c index fb2d5f4b1f..15701233e2 100644 --- a/builtin/ls-files.c +++ b/builtin/ls-files.c @@ -338,7 +338,7 @@ void overlay_tree_on_cache(const char *tree_name, const char *prefix) { struct tree *tree; unsigned char sha1[20]; - const char **match; + struct pathspec pathspec; struct cache_entry *last_stage0 = NULL; int i; @@ -360,10 +360,11 @@ void overlay_tree_on_cache(const char *tree_name, const char *prefix) static const char *(matchbuf[2]); matchbuf[0] = prefix; matchbuf[1] = NULL; - match = matchbuf; + init_pathspec(&pathspec, matchbuf); + pathspec.items[0].use_wildcard = 0; } else - match = NULL; - if (read_tree(tree, 1, match)) + init_pathspec(&pathspec, NULL); + if (read_tree(tree, 1, &pathspec)) die("unable to read tree entries %s", tree_name); for (i = 0; i < active_nr; i++) { diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c index f73e6bd962..f08c5b0c94 100644 --- a/builtin/ls-tree.c +++ b/builtin/ls-tree.c @@ -19,7 +19,7 @@ static int line_termination = '\n'; #define LS_SHOW_SIZE 16 static int abbrev; static int ls_options; -static const char **pathspec; +static struct pathspec pathspec; static int chomp_prefix; static const char *ls_tree_prefix; @@ -35,7 +35,7 @@ static int show_recursive(const char *base, int baselen, const char *pathname) if (ls_options & LS_RECURSIVE) return 1; - s = pathspec; + s = pathspec.raw; if (!s) return 0; @@ -120,7 +120,7 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix) { unsigned char sha1[20]; struct tree *tree; - int full_tree = 0; + int i, full_tree = 0; const struct option ls_tree_options[] = { OPT_BIT('d', NULL, &ls_options, "only show trees", LS_TREE_ONLY), @@ -166,11 +166,14 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix) if (get_sha1(argv[0], sha1)) die("Not a valid object name %s", argv[0]); - pathspec = get_pathspec(prefix, argv + 1); + init_pathspec(&pathspec, get_pathspec(prefix, argv + 1)); + for (i = 0; i < pathspec.nr; i++) + pathspec.items[i].use_wildcard = 0; + pathspec.has_wildcard = 0; tree = parse_tree_indirect(sha1); if (!tree) die("not a tree object"); - read_tree_recursive(tree, "", 0, 0, pathspec, show_tree, NULL); + read_tree_recursive(tree, "", 0, 0, &pathspec, show_tree, NULL); return 0; } diff --git a/builtin/merge-tree.c b/builtin/merge-tree.c index 19917426fb..897a563bc6 100644 --- a/builtin/merge-tree.c +++ b/builtin/merge-tree.c @@ -3,6 +3,7 @@ #include "xdiff-interface.h" #include "blob.h" #include "exec_cmd.h" +#include "merge-file.h" static const char merge_tree_usage[] = "git merge-tree <base-tree> <branch1> <branch2>"; static int resolve_directories = 1; @@ -54,8 +55,6 @@ static const char *explanation(struct merge_list *entry) return "removed in remote"; } -extern void *merge_file(const char *, struct blob *, struct blob *, struct blob *, unsigned long *); - static void *result(struct merge_list *entry, unsigned long *size) { enum object_type type; diff --git a/builtin/merge.c b/builtin/merge.c index 0f03dff116..9661c8f425 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -831,7 +831,7 @@ 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")); + die_errno(_("Could not read from '%s'"), git_path("MERGE_MSG")); } static void run_prepare_commit_msg(void) @@ -971,16 +971,16 @@ static int setup_with_upstream(const char ***argv) const char **args; if (!branch) - die("No current branch."); + die(_("No current branch.")); if (!branch->remote) - die("No remote for the current branch."); + die(_("No remote for the current branch.")); if (!branch->merge_nr) - die("No default upstream defined for the current branch."); + die(_("No default upstream defined for the current branch.")); args = xcalloc(branch->merge_nr + 1, sizeof(char *)); for (i = 0; i < branch->merge_nr; i++) { if (!branch->merge[i]->dst) - die("No remote tracking branch for %s from %s", + die(_("No remote tracking branch for %s from %s"), branch->merge[i]->src, branch->remote_name); args[i] = branch->merge[i]->dst; } @@ -1054,10 +1054,10 @@ int cmd_merge(int argc, const char **argv, const char *prefix) } if (file_exists(git_path("CHERRY_PICK_HEAD"))) { if (advice_resolve_conflict) - die("You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists).\n" - "Please, commit your changes before you can merge."); + die(_("You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists).\n" + "Please, commit your changes before you can merge.")); else - die("You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists)."); + die(_("You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists).")); } resolve_undo_clear(); @@ -1073,9 +1073,12 @@ int cmd_merge(int argc, const char **argv, const char *prefix) if (!allow_fast_forward && fast_forward_only) die(_("You cannot combine --no-ff with --ff-only.")); - if (!argc && !abort_current_merge && default_to_upstream) - argc = setup_with_upstream(&argv); - + if (!abort_current_merge) { + if (!argc && default_to_upstream) + argc = setup_with_upstream(&argv); + else if (argc == 1 && !strcmp(argv[0], "-")) + argv[0] = "@{-1}"; + } if (!argc) usage_with_options(builtin_merge_usage, builtin_merge_options); diff --git a/builtin/notes.c b/builtin/notes.c index d6dcfcb014..1fb1f73439 100644 --- a/builtin/notes.c +++ b/builtin/notes.c @@ -100,16 +100,6 @@ 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) @@ -529,6 +519,8 @@ static int list(int argc, const char **argv, const char *prefix) return retval; } +static int append_edit(int argc, const char **argv, const char *prefix); + static int add(int argc, const char **argv, const char *prefix) { int retval = 0, force = 0; @@ -556,14 +548,14 @@ static int add(int argc, const char **argv, const char *prefix) }; argc = parse_options(argc, argv, prefix, options, git_notes_add_usage, - 0); + PARSE_OPT_KEEP_ARGV0); - if (1 < argc) { + if (2 < argc) { error(_("too many parameters")); usage_with_options(git_notes_add_usage, options); } - object_ref = argc ? argv[0] : "HEAD"; + object_ref = argc > 1 ? argv[1] : "HEAD"; if (get_sha1(object_ref, object)) die(_("Failed to resolve '%s' as a valid ref."), object_ref); @@ -573,6 +565,18 @@ static int add(int argc, const char **argv, const char *prefix) if (note) { if (!force) { + if (!msg.given) { + /* + * Redirect to "edit" subcommand. + * + * We only end up here if none of -m/-F/-c/-C + * or -f are given. The original args are + * therefore still in argv[0-1]. + */ + argv[0] = "edit"; + free_notes(t); + return append_edit(argc, argv, prefix); + } retval = error(_("Cannot add notes. Found existing notes " "for object %s. Use '-f' to overwrite " "existing notes"), sha1_to_hex(object)); diff --git a/builtin/rerere.c b/builtin/rerere.c index 82358855d1..08213c7c0b 100644 --- a/builtin/rerere.c +++ b/builtin/rerere.c @@ -12,74 +12,6 @@ static const char * const rerere_usage[] = { NULL, }; -/* these values are days */ -static int cutoff_noresolve = 15; -static int cutoff_resolve = 60; - -static time_t rerere_created_at(const char *name) -{ - struct stat st; - return stat(rerere_path(name, "preimage"), &st) ? (time_t) 0 : st.st_mtime; -} - -static time_t rerere_last_used_at(const char *name) -{ - struct stat st; - return stat(rerere_path(name, "postimage"), &st) ? (time_t) 0 : st.st_mtime; -} - -static void unlink_rr_item(const char *name) -{ - unlink(rerere_path(name, "thisimage")); - unlink(rerere_path(name, "preimage")); - unlink(rerere_path(name, "postimage")); - rmdir(git_path("rr-cache/%s", name)); -} - -static int git_rerere_gc_config(const char *var, const char *value, void *cb) -{ - if (!strcmp(var, "gc.rerereresolved")) - cutoff_resolve = git_config_int(var, value); - else if (!strcmp(var, "gc.rerereunresolved")) - cutoff_noresolve = git_config_int(var, value); - else - return git_default_config(var, value, cb); - return 0; -} - -static void garbage_collect(struct string_list *rr) -{ - struct string_list to_remove = STRING_LIST_INIT_DUP; - DIR *dir; - struct dirent *e; - int i, cutoff; - time_t now = time(NULL), then; - - git_config(git_rerere_gc_config, NULL); - dir = opendir(git_path("rr-cache")); - if (!dir) - die_errno("unable to open rr-cache directory"); - while ((e = readdir(dir))) { - if (is_dot_or_dotdot(e->d_name)) - continue; - - then = rerere_last_used_at(e->d_name); - if (then) { - cutoff = cutoff_resolve; - } else { - then = rerere_created_at(e->d_name); - if (!then) - continue; - cutoff = cutoff_noresolve; - } - if (then < now - cutoff * 86400) - string_list_append(&to_remove, e->d_name); - } - for (i = 0; i < to_remove.nr; i++) - unlink_rr_item(to_remove.items[i].string); - string_list_clear(&to_remove, 0); -} - static int outf(void *dummy, mmbuffer_t *ptr, int nbuf) { int i; @@ -148,14 +80,9 @@ int cmd_rerere(int argc, const char **argv, const char *prefix) return 0; if (!strcmp(argv[0], "clear")) { - for (i = 0; i < merge_rr.nr; i++) { - const char *name = (const char *)merge_rr.items[i].util; - if (!has_rerere_resolution(name)) - unlink_rr_item(name); - } - unlink_or_warn(git_path("MERGE_RR")); + rerere_clear(&merge_rr); } else if (!strcmp(argv[0], "gc")) - garbage_collect(&merge_rr); + rerere_gc(&merge_rr); else if (!strcmp(argv[0], "status")) for (i = 0; i < merge_rr.nr; i++) printf("%s\n", merge_rr.items[i].string); diff --git a/builtin/rev-list.c b/builtin/rev-list.c index 9bfb94201f..4be66998f6 100644 --- a/builtin/rev-list.c +++ b/builtin/rev-list.c @@ -55,7 +55,9 @@ static void show_commit(struct commit *commit, void *data) graph_show_commit(revs->graph); if (revs->count) { - if (commit->object.flags & SYMMETRIC_LEFT) + if (commit->object.flags & PATCHSAME) + revs->count_same++; + else if (commit->object.flags & SYMMETRIC_LEFT) revs->count_left++; else revs->count_right++; @@ -406,8 +408,12 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) &info); if (revs.count) { - if (revs.left_right) + if (revs.left_right && revs.cherry_mark) + printf("%d\t%d\t%d\n", revs.count_left, revs.count_right, revs.count_same); + else if (revs.left_right) printf("%d\t%d\n", revs.count_left, revs.count_right); + else if (revs.cherry_mark) + printf("%d\t%d\n", revs.count_left + revs.count_right, revs.count_same); else printf("%d\n", revs.count_left + revs.count_right); } diff --git a/builtin/send-pack.c b/builtin/send-pack.c index 8b0911c0d2..4ac2ca984f 100644 --- a/builtin/send-pack.c +++ b/builtin/send-pack.c @@ -229,6 +229,9 @@ static void print_helper_status(struct ref *ref) static int sideband_demux(int in, int out, void *data) { int *fd = data; +#ifdef NO_PTHREADS + close(fd[1]); +#endif int ret = recv_sideband("send-pack", fd[0], out); close(out); return ret; @@ -339,6 +342,8 @@ int send_pack(struct send_pack_args *args, if (pack_objects(out, remote_refs, extra_have, args) < 0) { for (ref = remote_refs; ref; ref = ref->next) ref->status = REF_STATUS_NONE; + if (args->stateless_rpc) + close(out); if (use_sideband) finish_async(&demux); return -1; diff --git a/builtin/shortlog.c b/builtin/shortlog.c index f5efc67c9c..b6f4b0eb03 100644 --- a/builtin/shortlog.c +++ b/builtin/shortlog.c @@ -29,9 +29,6 @@ static int compare_by_number(const void *a1, const void *a2) return -1; } -const char *format_subject(struct strbuf *sb, const char *msg, - const char *line_separator); - static void insert_one_record(struct shortlog *log, const char *author, const char *oneline) diff --git a/builtin/show-branch.c b/builtin/show-branch.c index da695815e2..1abcd9e02e 100644 --- a/builtin/show-branch.c +++ b/builtin/show-branch.c @@ -12,16 +12,6 @@ static const char* show_branch_usage[] = { }; static int showbranch_use_color = -1; -static char column_colors[][COLOR_MAXLEN] = { - GIT_COLOR_RED, - GIT_COLOR_GREEN, - GIT_COLOR_YELLOW, - GIT_COLOR_BLUE, - GIT_COLOR_MAGENTA, - GIT_COLOR_CYAN, -}; - -#define COLUMN_COLORS_MAX (ARRAY_SIZE(column_colors)) static int default_num; static int default_alloc; @@ -37,7 +27,7 @@ static const char **default_arg; static const char *get_color_code(int idx) { if (showbranch_use_color) - return column_colors[idx]; + return column_colors_ansi[idx % column_colors_ansi_max]; return ""; } @@ -892,7 +882,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) for (j = 0; j < i; j++) putchar(' '); printf("%s%c%s [%s] ", - get_color_code(i % COLUMN_COLORS_MAX), + get_color_code(i), is_head ? '*' : '!', get_color_reset_code(), ref_name[i]); } @@ -954,7 +944,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) else mark = '+'; printf("%s%c%s", - get_color_code(i % COLUMN_COLORS_MAX), + get_color_code(i), mark, get_color_reset_code()); } putchar(' '); @@ -511,7 +511,7 @@ struct pathspec { struct pathspec_item { const char *match; int len; - unsigned int has_wildcard:1; + unsigned int use_wildcard:1; } *items; }; @@ -676,14 +676,24 @@ extern char *sha1_pack_name(const unsigned char *sha1); extern char *sha1_pack_index_name(const unsigned char *sha1); extern const char *find_unique_abbrev(const unsigned char *sha1, int); extern const unsigned char null_sha1[20]; -static inline int is_null_sha1(const unsigned char *sha1) + +static inline int hashcmp(const unsigned char *sha1, const unsigned char *sha2) { - return !memcmp(sha1, null_sha1, 20); + int i; + + for (i = 0; i < 20; i++, sha1++, sha2++) { + if (*sha1 != *sha2) + return *sha1 - *sha2; + } + + return 0; } -static inline int hashcmp(const unsigned char *sha1, const unsigned char *sha2) + +static inline int is_null_sha1(const unsigned char *sha1) { - return memcmp(sha1, sha2, 20); + return !hashcmp(sha1, null_sha1); } + static inline void hashcpy(unsigned char *sha_dst, const unsigned char *sha_src) { memcpy(sha_dst, sha_src, 20); @@ -3,6 +3,28 @@ int git_use_color_default = 0; +/* + * The list of available column colors. + */ +const char *column_colors_ansi[] = { + GIT_COLOR_RED, + GIT_COLOR_GREEN, + GIT_COLOR_YELLOW, + GIT_COLOR_BLUE, + GIT_COLOR_MAGENTA, + GIT_COLOR_CYAN, + GIT_COLOR_BOLD_RED, + GIT_COLOR_BOLD_GREEN, + GIT_COLOR_BOLD_YELLOW, + GIT_COLOR_BOLD_BLUE, + GIT_COLOR_BOLD_MAGENTA, + GIT_COLOR_BOLD_CYAN, + GIT_COLOR_RESET, +}; + +/* Ignore the RESET at the end when giving the size */ +const int column_colors_ansi_max = ARRAY_SIZE(column_colors_ansi) - 1; + static int parse_color(const char *name, int len) { static const char * const color_names[] = { @@ -53,6 +53,9 @@ struct strbuf; */ extern int git_use_color_default; +/* A default list of colors to use for commit graphs and show-branch output */ +extern const char *column_colors_ansi[]; +extern const int column_colors_ansi_max; /* * Use this instead of git_default_config if you need the value of color.ui. @@ -90,6 +90,8 @@ extern char *logmsg_reencode(const struct commit *commit, extern char *reencode_commit_message(const struct commit *commit, const char **encoding_p); extern void get_commit_format(const char *arg, struct rev_info *); +extern const char *format_subject(struct strbuf *sb, const char *msg, + const char *line_separator); extern void userformat_find_requirements(const char *fmt, struct userformat_want *w); extern void format_commit_message(const struct commit *commit, const char *format, struct strbuf *sb, @@ -133,23 +133,21 @@ static int get_next_char(void) static char *parse_value(void) { - static char value[1024]; - int quote = 0, comment = 0, len = 0, space = 0; + static struct strbuf value = STRBUF_INIT; + int quote = 0, comment = 0, space = 0; + strbuf_reset(&value); for (;;) { int c = get_next_char(); - if (len >= sizeof(value) - 1) - return NULL; if (c == '\n') { if (quote) return NULL; - value[len] = 0; - return value; + return value.buf; } if (comment) continue; if (isspace(c) && !quote) { - if (len) + if (value.len) space++; continue; } @@ -160,7 +158,7 @@ static char *parse_value(void) } } for (; space; space--) - value[len++] = ' '; + strbuf_addch(&value, ' '); if (c == '\\') { c = get_next_char(); switch (c) { @@ -182,14 +180,14 @@ static char *parse_value(void) default: return NULL; } - value[len++] = c; + strbuf_addch(&value, c); continue; } if (c == '"') { quote = 1-quote; continue; } - value[len++] = c; + strbuf_addch(&value, c); } } diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 840ae38760..b81f444496 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -1,6 +1,6 @@ #!bash # -# bash completion support for core Git. +# bash/zsh completion support for core Git. # # Copyright (C) 2006,2007 Shawn O. Pearce <spearce@spearce.org> # Conceptually based on gitcompletion (http://gitweb.hawaga.org.uk/). @@ -18,16 +18,12 @@ # To use these routines: # # 1) Copy this file to somewhere (e.g. ~/.git-completion.sh). -# 2) Added the following line to your .bashrc: -# source ~/.git-completion.sh -# -# Or, add the following lines to your .zshrc: -# autoload bashcompinit -# bashcompinit +# 2) Add the following line to your .bashrc/.zshrc: # source ~/.git-completion.sh # # 3) Consider changing your PS1 to also show the current branch: -# PS1='[\u@\h \W$(__git_ps1 " (%s)")]\$ ' +# Bash: PS1='[\u@\h \W$(__git_ps1 " (%s)")]\$ ' +# ZSH: PS1='[%n@%m %c$(__git_ps1 " (%s)")]\$ ' # # The argument to __git_ps1 will be displayed only if you # are currently in a git repository. The %s token will be @@ -77,6 +73,10 @@ # git@vger.kernel.org # +if [[ -n ${ZSH_VERSION-} ]]; then + autoload -U +X bashcompinit && bashcompinit +fi + case "$COMP_WORDBREAKS" in *:*) : great ;; *) COMP_WORDBREAKS="$COMP_WORDBREAKS:" @@ -489,12 +489,12 @@ fi # generates completion reply with compgen __gitcomp () { - local cur - _get_comp_words_by_ref -n =: cur + local cur_="$cur" + if [ $# -gt 2 ]; then - cur="$3" + cur_="$3" fi - case "$cur" in + case "$cur_" in --*=) COMPREPLY=() ;; @@ -502,7 +502,7 @@ __gitcomp () local IFS=$'\n' COMPREPLY=($(compgen -P "${2-}" \ -W "$(__gitcomp_1 "${1-}" "${4-}")" \ - -- "$cur")) + -- "$cur_")) ;; esac } @@ -551,8 +551,7 @@ __git_tags () __git_refs () { local i is_hash=y dir="$(__gitdir "${1-}")" track="${2-}" - local cur format refs - _get_comp_words_by_ref -n =: cur + local format refs if [ -d "$dir" ]; then case "$cur" in refs|refs/*) @@ -666,19 +665,18 @@ __git_compute_merge_strategies () __git_complete_revlist_file () { - local pfx ls ref cur - _get_comp_words_by_ref -n =: cur - case "$cur" in + local pfx ls ref cur_="$cur" + case "$cur_" in *..?*:*) return ;; ?*:*) - ref="${cur%%:*}" - cur="${cur#*:}" - case "$cur" in + ref="${cur_%%:*}" + cur_="${cur_#*:}" + case "$cur_" in ?*/*) - pfx="${cur%/*}" - cur="${cur##*/}" + pfx="${cur_%/*}" + cur_="${cur_##*/}" ls="$ref:$pfx" pfx="$pfx/" ;; @@ -708,17 +706,17 @@ __git_complete_revlist_file () s,$,/, } s/^.* //')" \ - -- "$cur")) + -- "$cur_")) ;; *...*) - pfx="${cur%...*}..." - cur="${cur#*...}" - __gitcomp "$(__git_refs)" "$pfx" "$cur" + pfx="${cur_%...*}..." + cur_="${cur_#*...}" + __gitcomp "$(__git_refs)" "$pfx" "$cur_" ;; *..*) - pfx="${cur%..*}.." - cur="${cur#*..}" - __gitcomp "$(__git_refs)" "$pfx" "$cur" + pfx="${cur_%..*}.." + cur_="${cur_#*..}" + __gitcomp "$(__git_refs)" "$pfx" "$cur_" ;; *) __gitcomp "$(__git_refs)" @@ -739,9 +737,7 @@ __git_complete_revlist () __git_complete_remote_or_refspec () { - local cur words cword - _get_comp_words_by_ref -n =: cur words cword - local cmd="${words[1]}" + local cur_="$cur" cmd="${words[1]}" local i c=2 remote="" pfx="" lhs=1 no_complete_refspec=0 while [ $c -lt $cword ]; do i="${words[c]}" @@ -771,40 +767,40 @@ __git_complete_remote_or_refspec () return fi [ "$remote" = "." ] && remote= - case "$cur" in + case "$cur_" in *:*) case "$COMP_WORDBREAKS" in *:*) : great ;; - *) pfx="${cur%%:*}:" ;; + *) pfx="${cur_%%:*}:" ;; esac - cur="${cur#*:}" + cur_="${cur_#*:}" lhs=0 ;; +*) pfx="+" - cur="${cur#+}" + cur_="${cur_#+}" ;; esac case "$cmd" in fetch) if [ $lhs = 1 ]; then - __gitcomp "$(__git_refs2 "$remote")" "$pfx" "$cur" + __gitcomp "$(__git_refs2 "$remote")" "$pfx" "$cur_" else - __gitcomp "$(__git_refs)" "$pfx" "$cur" + __gitcomp "$(__git_refs)" "$pfx" "$cur_" fi ;; pull) if [ $lhs = 1 ]; then - __gitcomp "$(__git_refs "$remote")" "$pfx" "$cur" + __gitcomp "$(__git_refs "$remote")" "$pfx" "$cur_" else - __gitcomp "$(__git_refs)" "$pfx" "$cur" + __gitcomp "$(__git_refs)" "$pfx" "$cur_" fi ;; push) if [ $lhs = 1 ]; then - __gitcomp "$(__git_refs)" "$pfx" "$cur" + __gitcomp "$(__git_refs)" "$pfx" "$cur_" else - __gitcomp "$(__git_refs "$remote")" "$pfx" "$cur" + __gitcomp "$(__git_refs "$remote")" "$pfx" "$cur_" fi ;; esac @@ -812,8 +808,6 @@ __git_complete_remote_or_refspec () __git_complete_strategy () { - local cur prev - _get_comp_words_by_ref -n =: cur prev __git_compute_merge_strategies case "$prev" in -s|--strategy) @@ -991,8 +985,7 @@ __git_aliased_command () # __git_find_on_cmdline requires 1 argument __git_find_on_cmdline () { - local word subcommand c=1 words cword - _get_comp_words_by_ref -n =: words cword + local word subcommand c=1 while [ $c -lt $cword ]; do word="${words[c]}" for subcommand in $1; do @@ -1007,8 +1000,7 @@ __git_find_on_cmdline () __git_has_doubledash () { - local c=1 words cword - _get_comp_words_by_ref -n =: words cword + local c=1 while [ $c -lt $cword ]; do if [ "--" = "${words[c]}" ]; then return 0 @@ -1022,8 +1014,7 @@ __git_whitespacelist="nowarn warn error error-all fix" _git_am () { - local cur dir="$(__gitdir)" - _get_comp_words_by_ref -n =: cur + local dir="$(__gitdir)" if [ -d "$dir"/rebase-apply ]; then __gitcomp "--skip --continue --resolved --abort" return @@ -1047,8 +1038,6 @@ _git_am () _git_apply () { - local cur - _get_comp_words_by_ref -n =: cur case "$cur" in --whitespace=*) __gitcomp "$__git_whitespacelist" "" "${cur##--whitespace=}" @@ -1071,8 +1060,6 @@ _git_add () { __git_has_doubledash && return - local cur - _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp " @@ -1086,8 +1073,6 @@ _git_add () _git_archive () { - local cur - _get_comp_words_by_ref -n =: cur case "$cur" in --format=*) __gitcomp "$(git archive --list)" "" "${cur##--format=}" @@ -1135,9 +1120,8 @@ _git_bisect () _git_branch () { - local i c=1 only_local_ref="n" has_r="n" cur words cword + local i c=1 only_local_ref="n" has_r="n" - _get_comp_words_by_ref -n =: cur words cword while [ $c -lt $cword ]; do i="${words[c]}" case "$i" in @@ -1167,8 +1151,6 @@ _git_branch () _git_bundle () { - local words cword - _get_comp_words_by_ref -n =: words cword local cmd="${words[2]}" case "$cword" in 2) @@ -1191,8 +1173,6 @@ _git_checkout () { __git_has_doubledash && return - local cur - _get_comp_words_by_ref -n =: cur case "$cur" in --conflict=*) __gitcomp "diff3 merge" "" "${cur##--conflict=}" @@ -1222,8 +1202,6 @@ _git_cherry () _git_cherry_pick () { - local cur - _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp "--edit --no-commit" @@ -1238,8 +1216,6 @@ _git_clean () { __git_has_doubledash && return - local cur - _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp "--dry-run --quiet" @@ -1251,8 +1227,6 @@ _git_clean () _git_clone () { - local cur - _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp " @@ -1279,8 +1253,6 @@ _git_commit () { __git_has_doubledash && return - local cur - _get_comp_words_by_ref -n =: cur case "$cur" in --cleanup=*) __gitcomp "default strip verbatim whitespace @@ -1315,8 +1287,6 @@ _git_commit () _git_describe () { - local cur - _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp " @@ -1348,8 +1318,6 @@ _git_diff () { __git_has_doubledash && return - local cur - _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp "--cached --staged --pickaxe-all --pickaxe-regex @@ -1370,8 +1338,6 @@ _git_difftool () { __git_has_doubledash && return - local cur - _get_comp_words_by_ref -n =: cur case "$cur" in --tool=*) __gitcomp "$__git_mergetools_common kompare" "" "${cur##--tool=}" @@ -1396,8 +1362,6 @@ __git_fetch_options=" _git_fetch () { - local cur - _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp "$__git_fetch_options" @@ -1409,8 +1373,6 @@ _git_fetch () _git_format_patch () { - local cur - _get_comp_words_by_ref -n =: cur case "$cur" in --thread=*) __gitcomp " @@ -1442,8 +1404,6 @@ _git_format_patch () _git_fsck () { - local cur - _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp " @@ -1458,8 +1418,6 @@ _git_fsck () _git_gc () { - local cur - _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp "--prune --aggressive" @@ -1478,8 +1436,6 @@ _git_grep () { __git_has_doubledash && return - local cur - _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp " @@ -1502,8 +1458,6 @@ _git_grep () _git_help () { - local cur - _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp "--all --info --man --web" @@ -1521,8 +1475,6 @@ _git_help () _git_init () { - local cur - _get_comp_words_by_ref -n =: cur case "$cur" in --shared=*) __gitcomp " @@ -1542,8 +1494,6 @@ _git_ls_files () { __git_has_doubledash && return - local cur - _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp "--cached --deleted --modified --others --ignored @@ -1584,7 +1534,7 @@ __git_log_common_options=" __git_log_gitk_options=" --dense --sparse --full-history --simplify-merges --simplify-by-decoration - --left-right + --left-right --notes --no-notes " # Options that go well for log and shortlog (not gitk) __git_log_shortlog_options=" @@ -1604,8 +1554,6 @@ _git_log () 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 $(__git_pretty_aliases) @@ -1659,8 +1607,6 @@ _git_merge () { __git_complete_strategy && return - local cur - _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp "$__git_merge_options" @@ -1671,8 +1617,6 @@ _git_merge () _git_mergetool () { - local cur - _get_comp_words_by_ref -n =: cur case "$cur" in --tool=*) __gitcomp "$__git_mergetools_common tortoisemerge" "" "${cur##--tool=}" @@ -1693,8 +1637,6 @@ _git_merge_base () _git_mv () { - local cur - _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp "--dry-run" @@ -1713,8 +1655,6 @@ _git_notes () { 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 "$subcommand,$cur" in ,--*) @@ -1764,8 +1704,6 @@ _git_pull () { __git_complete_strategy && return - local cur - _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp " @@ -1781,8 +1719,6 @@ _git_pull () _git_push () { - local cur prev - _get_comp_words_by_ref -n =: cur prev case "$prev" in --repo) __gitcomp "$(__git_remotes)" @@ -1807,8 +1743,6 @@ _git_push () _git_rebase () { 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 @@ -1850,8 +1784,6 @@ __git_send_email_suppresscc_options="author self cc bodycc sob cccmd body all" _git_send_email () { - local cur - _get_comp_words_by_ref -n =: cur case "$cur" in --confirm=*) __gitcomp " @@ -1893,8 +1825,6 @@ _git_stage () __git_config_get_set_variables () { - local words cword - _get_comp_words_by_ref -n =: words cword local prevword word config_file= c=$cword while [ $c -gt 1 ]; do word="${words[c]}" @@ -1925,8 +1855,6 @@ __git_config_get_set_variables () _git_config () { - local cur prev - _get_comp_words_by_ref -n =: cur prev case "$prev" in branch.*.remote) __gitcomp "$(__git_remotes)" @@ -2012,70 +1940,60 @@ _git_config () return ;; branch.*.*) - local pfx="${cur%.*}." - cur="${cur##*.}" - __gitcomp "remote merge mergeoptions rebase" "$pfx" "$cur" + local pfx="${cur%.*}." cur_="${cur##*.}" + __gitcomp "remote merge mergeoptions rebase" "$pfx" "$cur_" return ;; branch.*) - local pfx="${cur%.*}." - cur="${cur#*.}" - __gitcomp "$(__git_heads)" "$pfx" "$cur" "." + local pfx="${cur%.*}." cur_="${cur#*.}" + __gitcomp "$(__git_heads)" "$pfx" "$cur_" "." return ;; guitool.*.*) - local pfx="${cur%.*}." - cur="${cur##*.}" + local pfx="${cur%.*}." cur_="${cur##*.}" __gitcomp " argprompt cmd confirm needsfile noconsole norescan prompt revprompt revunmerged title - " "$pfx" "$cur" + " "$pfx" "$cur_" return ;; difftool.*.*) - local pfx="${cur%.*}." - cur="${cur##*.}" - __gitcomp "cmd path" "$pfx" "$cur" + local pfx="${cur%.*}." cur_="${cur##*.}" + __gitcomp "cmd path" "$pfx" "$cur_" return ;; man.*.*) - local pfx="${cur%.*}." - cur="${cur##*.}" - __gitcomp "cmd path" "$pfx" "$cur" + local pfx="${cur%.*}." cur_="${cur##*.}" + __gitcomp "cmd path" "$pfx" "$cur_" return ;; mergetool.*.*) - local pfx="${cur%.*}." - cur="${cur##*.}" - __gitcomp "cmd path trustExitCode" "$pfx" "$cur" + local pfx="${cur%.*}." cur_="${cur##*.}" + __gitcomp "cmd path trustExitCode" "$pfx" "$cur_" return ;; pager.*) - local pfx="${cur%.*}." - cur="${cur#*.}" + local pfx="${cur%.*}." cur_="${cur#*.}" __git_compute_all_commands - __gitcomp "$__git_all_commands" "$pfx" "$cur" + __gitcomp "$__git_all_commands" "$pfx" "$cur_" return ;; remote.*.*) - local pfx="${cur%.*}." - cur="${cur##*.}" + local pfx="${cur%.*}." cur_="${cur##*.}" __gitcomp " url proxy fetch push mirror skipDefaultUpdate receivepack uploadpack tagopt pushurl - " "$pfx" "$cur" + " "$pfx" "$cur_" return ;; remote.*) - local pfx="${cur%.*}." - cur="${cur#*.}" - __gitcomp "$(__git_remotes)" "$pfx" "$cur" "." + local pfx="${cur%.*}." cur_="${cur#*.}" + __gitcomp "$(__git_remotes)" "$pfx" "$cur_" "." return ;; url.*.*) - local pfx="${cur%.*}." - cur="${cur##*.}" - __gitcomp "insteadOf pushInsteadOf" "$pfx" "$cur" + local pfx="${cur%.*}." cur_="${cur##*.}" + __gitcomp "insteadOf pushInsteadOf" "$pfx" "$cur_" return ;; esac @@ -2396,8 +2314,6 @@ _git_reset () { __git_has_doubledash && return - local cur - _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp "--merge --mixed --hard --soft --patch" @@ -2409,8 +2325,6 @@ _git_reset () _git_revert () { - local cur - _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp "--edit --mainline --no-edit --no-commit --signoff" @@ -2424,8 +2338,6 @@ _git_rm () { __git_has_doubledash && return - local cur - _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp "--cached --dry-run --ignore-unmatch --quiet" @@ -2439,8 +2351,6 @@ _git_shortlog () { __git_has_doubledash && return - local cur - _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp " @@ -2458,8 +2368,6 @@ _git_show () { __git_has_doubledash && return - local cur - _get_comp_words_by_ref -n =: cur case "$cur" in --pretty=*) __gitcomp "$__git_log_pretty_formats $(__git_pretty_aliases) @@ -2483,8 +2391,6 @@ _git_show () _git_show_branch () { - local cur - _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp " @@ -2501,8 +2407,6 @@ _git_show_branch () _git_stash () { - 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")" @@ -2547,8 +2451,6 @@ _git_submodule () local subcommands="add status init update summary foreach sync" if [ -z "$(__git_find_on_cmdline "$subcommands")" ]; then - local cur - _get_comp_words_by_ref -n =: cur case "$cur" in --*) __gitcomp "--quiet --cached" @@ -2592,8 +2494,6 @@ _git_svn () --edit --rmdir --find-copies-harder --copy-similarity= " - local cur - _get_comp_words_by_ref -n =: cur case "$subcommand,$cur" in fetch,--*) __gitcomp "--revision= --fetch-all $fc_opts" @@ -2665,8 +2565,6 @@ _git_svn () _git_tag () { local i c=1 f=0 - local words cword prev - _get_comp_words_by_ref -n =: words cword prev while [ $c -lt $cword ]; do i="${words[c]}" case "$i" in @@ -2712,8 +2610,8 @@ _git () setopt KSH_TYPESET fi - local cur words cword - _get_comp_words_by_ref -n =: cur words cword + local cur words cword prev + _get_comp_words_by_ref -n =: cur words cword prev while [ $c -lt $cword ]; do i="${words[c]}" case "$i" in @@ -2763,15 +2661,16 @@ _gitk () setopt KSH_TYPESET fi + local cur words cword prev + _get_comp_words_by_ref -n =: cur words cword prev + __git_has_doubledash && return - 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 " @@ -31,6 +31,7 @@ static const char *external_diff_cmd_cfg; int diff_auto_refresh_index = 1; static int diff_mnemonic_prefix; static int diff_no_prefix; +static int diff_dirstat_permille_default = 30; static struct diff_options default_diff_options; static char diff_colors[][COLOR_MAXLEN] = { @@ -66,6 +67,58 @@ static int parse_diff_color_slot(const char *var, int ofs) return -1; } +static int parse_dirstat_params(struct diff_options *options, const char *params, + struct strbuf *errmsg) +{ + const char *p = params; + int p_len, ret = 0; + + while (*p) { + p_len = strchrnul(p, ',') - p; + if (!memcmp(p, "changes", p_len)) { + DIFF_OPT_CLR(options, DIRSTAT_BY_LINE); + DIFF_OPT_CLR(options, DIRSTAT_BY_FILE); + } else if (!memcmp(p, "lines", p_len)) { + DIFF_OPT_SET(options, DIRSTAT_BY_LINE); + DIFF_OPT_CLR(options, DIRSTAT_BY_FILE); + } else if (!memcmp(p, "files", p_len)) { + DIFF_OPT_CLR(options, DIRSTAT_BY_LINE); + DIFF_OPT_SET(options, DIRSTAT_BY_FILE); + } else if (!memcmp(p, "noncumulative", p_len)) { + DIFF_OPT_CLR(options, DIRSTAT_CUMULATIVE); + } else if (!memcmp(p, "cumulative", p_len)) { + DIFF_OPT_SET(options, DIRSTAT_CUMULATIVE); + } else if (isdigit(*p)) { + char *end; + int permille = strtoul(p, &end, 10) * 10; + if (*end == '.' && isdigit(*++end)) { + /* only use first digit */ + permille += *end - '0'; + /* .. and ignore any further digits */ + while (isdigit(*++end)) + ; /* nothing */ + } + if (end - p == p_len) + options->dirstat_permille = permille; + else { + strbuf_addf(errmsg, _(" Failed to parse dirstat cut-off percentage '%.*s'\n"), + p_len, p); + ret++; + } + } else { + strbuf_addf(errmsg, _(" Unknown dirstat parameter '%.*s'\n"), + p_len, p); + ret++; + } + + p += p_len; + + if (*p) + p++; /* more parameters, swallow separator */ + } + return ret; +} + static int git_config_rename(const char *var, const char *value) { if (!value) @@ -145,6 +198,17 @@ int git_diff_basic_config(const char *var, const char *value, void *cb) return 0; } + if (!strcmp(var, "diff.dirstat")) { + struct strbuf errmsg = STRBUF_INIT; + default_diff_options.dirstat_permille = diff_dirstat_permille_default; + if (parse_dirstat_params(&default_diff_options, value, &errmsg)) + warning(_("Found errors in 'diff.dirstat' config variable:\n%s"), + errmsg.buf); + strbuf_release(&errmsg); + diff_dirstat_permille_default = default_diff_options.dirstat_permille; + return 0; + } + if (!prefixcmp(var, "submodule.")) return parse_submodule_config_option(var, value); @@ -581,11 +645,14 @@ static void emit_rewrite_diff(const char *name_a, line_prefix, metainfo, a_name.buf, name_a_tab, reset, line_prefix, metainfo, b_name.buf, name_b_tab, reset, line_prefix, fraginfo); - print_line_count(o->file, lc_a); + if (!o->irreversible_delete) + print_line_count(o->file, lc_a); + else + fprintf(o->file, "?,?"); fprintf(o->file, " +"); print_line_count(o->file, lc_b); fprintf(o->file, " @@%s\n", reset); - if (lc_a) + if (lc_a && !o->irreversible_delete) emit_rewrite_lines(&ecbdata, '-', data_one, size_one); if (lc_b) emit_rewrite_lines(&ecbdata, '+', data_two, size_two); @@ -1452,7 +1519,7 @@ struct dirstat_file { struct dirstat_dir { struct dirstat_file *files; - int alloc, nr, percent, cumulative; + int alloc, nr, permille, cumulative; }; static long gather_dirstat(struct diff_options *opt, struct dirstat_dir *dir, @@ -1499,12 +1566,11 @@ static long gather_dirstat(struct diff_options *opt, struct dirstat_dir *dir, * under this directory (sources == 1). */ if (baselen && sources != 1) { - int permille = this_dir * 1000 / changed; - if (permille) { - int percent = permille / 10; - if (percent >= dir->percent) { + if (this_dir) { + int permille = this_dir * 1000 / changed; + if (permille >= dir->permille) { fprintf(opt->file, "%s%4d.%01d%% %.*s\n", line_prefix, - percent, permille % 10, baselen, base); + permille / 10, permille % 10, baselen, base); if (!dir->cumulative) return 0; } @@ -1530,7 +1596,7 @@ static void show_dirstat(struct diff_options *options) dir.files = NULL; dir.alloc = 0; dir.nr = 0; - dir.percent = options->dirstat_percent; + dir.permille = options->dirstat_permille; dir.cumulative = DIFF_OPT_TST(options, DIRSTAT_CUMULATIVE); changed = 0; @@ -1619,6 +1685,50 @@ found_damage: gather_dirstat(options, &dir, changed, "", 0); } +static void show_dirstat_by_line(struct diffstat_t *data, struct diff_options *options) +{ + int i; + unsigned long changed; + struct dirstat_dir dir; + + if (data->nr == 0) + return; + + dir.files = NULL; + dir.alloc = 0; + dir.nr = 0; + dir.permille = options->dirstat_permille; + dir.cumulative = DIFF_OPT_TST(options, DIRSTAT_CUMULATIVE); + + changed = 0; + for (i = 0; i < data->nr; i++) { + struct diffstat_file *file = data->files[i]; + unsigned long damage = file->added + file->deleted; + if (file->is_binary) + /* + * binary files counts bytes, not lines. Must find some + * way to normalize binary bytes vs. textual lines. + * The following heuristic assumes that there are 64 + * bytes per "line". + * This is stupid and ugly, but very cheap... + */ + damage = (damage + 63) / 64; + ALLOC_GROW(dir.files, dir.nr + 1, dir.alloc); + dir.files[dir.nr].name = file->name; + dir.files[dir.nr].changed = damage; + changed += damage; + dir.nr++; + } + + /* This can happen even with many files, if everything was renames */ + if (!changed) + return; + + /* Show all directories with more than x% of the changes */ + qsort(dir.files, dir.nr, sizeof(dir.files[0]), dirstat_compare); + gather_dirstat(options, &dir, changed, "", 0); +} + static void free_diffstat_info(struct diffstat_t *diffstat) { int i; @@ -1981,7 +2091,11 @@ static void builtin_diff(const char *name_a, } } - if (!DIFF_OPT_TST(o, TEXT) && + if (o->irreversible_delete && lbl[1][0] == '/') { + fprintf(o->file, "%s", header.buf); + strbuf_reset(&header); + goto free_ab_and_return; + } else if (!DIFF_OPT_TST(o, TEXT) && ( (!textconv_one && diff_filespec_is_binary(one)) || (!textconv_two && diff_filespec_is_binary(two)) )) { if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0) @@ -2001,8 +2115,7 @@ static void builtin_diff(const char *name_a, fprintf(o->file, "%sBinary files %s and %s differ\n", line_prefix, lbl[0], lbl[1]); o->found_changes = 1; - } - else { + } else { /* Crazy xdl interfaces.. */ const char *diffopts = getenv("GIT_DIFF_OPTS"); xpparam_t xpp; @@ -2885,7 +2998,7 @@ void diff_setup(struct diff_options *options) options->line_termination = '\n'; options->break_opt = -1; options->rename_limit = -1; - options->dirstat_percent = 3; + options->dirstat_permille = diff_dirstat_permille_default; options->context = 3; options->change = diff_change; @@ -3143,6 +3256,21 @@ static int stat_opt(struct diff_options *options, const char **av) return argcount; } +static int parse_dirstat_opt(struct diff_options *options, const char *params) +{ + struct strbuf errmsg = STRBUF_INIT; + if (parse_dirstat_params(options, params, &errmsg)) + die(_("Failed to parse --dirstat/-X option parameter:\n%s"), + errmsg.buf); + strbuf_release(&errmsg); + /* + * The caller knows a dirstat-related option is given from the command + * line; allow it to say "return this_function();" + */ + options->output_format |= DIFF_FORMAT_DIRSTAT; + return 1; +} + int diff_opt_parse(struct diff_options *options, const char **av, int ac) { const char *arg = av[0]; @@ -3162,15 +3290,19 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) options->output_format |= DIFF_FORMAT_NUMSTAT; else if (!strcmp(arg, "--shortstat")) options->output_format |= DIFF_FORMAT_SHORTSTAT; - else if (opt_arg(arg, 'X', "dirstat", &options->dirstat_percent)) - options->output_format |= DIFF_FORMAT_DIRSTAT; - else if (!strcmp(arg, "--cumulative")) { - options->output_format |= DIFF_FORMAT_DIRSTAT; - DIFF_OPT_SET(options, DIRSTAT_CUMULATIVE); - } else if (opt_arg(arg, 0, "dirstat-by-file", - &options->dirstat_percent)) { - options->output_format |= DIFF_FORMAT_DIRSTAT; - DIFF_OPT_SET(options, DIRSTAT_BY_FILE); + else if (!strcmp(arg, "-X") || !strcmp(arg, "--dirstat")) + return parse_dirstat_opt(options, ""); + else if (!prefixcmp(arg, "-X")) + return parse_dirstat_opt(options, arg + 2); + else if (!prefixcmp(arg, "--dirstat=")) + return parse_dirstat_opt(options, arg + 10); + else if (!strcmp(arg, "--cumulative")) + return parse_dirstat_opt(options, "cumulative"); + else if (!strcmp(arg, "--dirstat-by-file")) + return parse_dirstat_opt(options, "files"); + else if (!prefixcmp(arg, "--dirstat-by-file=")) { + parse_dirstat_opt(options, "files"); + return parse_dirstat_opt(options, arg + 18); } else if (!strcmp(arg, "--check")) options->output_format |= DIFF_FORMAT_CHECKDIFF; @@ -3200,6 +3332,9 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) return error("invalid argument to -M: %s", arg+2); options->detect_rename = DIFF_DETECT_RENAME; } + else if (!strcmp(arg, "-D") || !strcmp(arg, "--irreversible-delete")) { + options->irreversible_delete = 1; + } else if (!prefixcmp(arg, "-C") || !prefixcmp(arg, "--find-copies=") || !strcmp(arg, "--find-copies")) { if (options->detect_rename == DIFF_DETECT_COPY) @@ -3987,11 +4122,34 @@ static int is_summary_empty(const struct diff_queue_struct *q) return 1; } +static const char rename_limit_warning[] = +"inexact rename detection was skipped due to too many files."; + +static const char degrade_cc_to_c_warning[] = +"only found copies from modified paths due to too many files."; + +static const char rename_limit_advice[] = +"you may want to set your %s variable to at least " +"%d and retry the command."; + +void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc) +{ + if (degraded_cc) + warning(degrade_cc_to_c_warning); + else if (needed) + warning(rename_limit_warning); + else + return; + if (0 < needed && needed < 32767) + warning(rename_limit_advice, varname, needed); +} + void diff_flush(struct diff_options *options) { struct diff_queue_struct *q = &diff_queued_diff; int i, output_format = options->output_format; int separator = 0; + int dirstat_by_line = 0; /* * Order: raw, stat, summary, patch @@ -4012,7 +4170,11 @@ void diff_flush(struct diff_options *options) separator++; } - if (output_format & (DIFF_FORMAT_DIFFSTAT|DIFF_FORMAT_SHORTSTAT|DIFF_FORMAT_NUMSTAT)) { + if (output_format & DIFF_FORMAT_DIRSTAT && DIFF_OPT_TST(options, DIRSTAT_BY_LINE)) + dirstat_by_line = 1; + + if (output_format & (DIFF_FORMAT_DIFFSTAT|DIFF_FORMAT_SHORTSTAT|DIFF_FORMAT_NUMSTAT) || + dirstat_by_line) { struct diffstat_t diffstat; memset(&diffstat, 0, sizeof(struct diffstat_t)); @@ -4027,10 +4189,12 @@ void diff_flush(struct diff_options *options) show_stats(&diffstat, options); if (output_format & DIFF_FORMAT_SHORTSTAT) show_shortstats(&diffstat, options); + if (output_format & DIFF_FORMAT_DIRSTAT) + show_dirstat_by_line(&diffstat, options); free_diffstat_info(&diffstat); separator++; } - if (output_format & DIFF_FORMAT_DIRSTAT) + if ((output_format & DIFF_FORMAT_DIRSTAT) && !dirstat_by_line) show_dirstat(options); if (output_format & DIFF_FORMAT_SUMMARY && !is_summary_empty(q)) { @@ -4268,6 +4432,10 @@ void diffcore_std(struct diff_options *options) int diff_result_code(struct diff_options *opt, int status) { int result = 0; + + diff_warn_rename_limit("diff.renamelimit", + opt->needed_rename_limit, + opt->degraded_cc_to_c); if (!DIFF_OPT_TST(opt, EXIT_WITH_STATUS) && !(opt->output_format & DIFF_FORMAT_CHECKDIFF)) return status; @@ -78,6 +78,7 @@ typedef struct strbuf *(*diff_prefix_fn_t)(struct diff_options *opt, void *data) #define DIFF_OPT_IGNORE_UNTRACKED_IN_SUBMODULES (1 << 25) #define DIFF_OPT_IGNORE_DIRTY_SUBMODULES (1 << 26) #define DIFF_OPT_OVERRIDE_SUBMODULE_CONFIG (1 << 27) +#define DIFF_OPT_DIRSTAT_BY_LINE (1 << 28) #define DIFF_OPT_TST(opts, flag) ((opts)->flags & DIFF_OPT_##flag) #define DIFF_OPT_SET(opts, flag) ((opts)->flags |= DIFF_OPT_##flag) @@ -104,6 +105,7 @@ struct diff_options { int interhunkcontext; int break_opt; int detect_rename; + int irreversible_delete; int skip_stat_unmatch; int line_termination; int output_format; @@ -111,8 +113,9 @@ struct diff_options { int rename_score; int rename_limit; int needed_rename_limit; + int degraded_cc_to_c; int show_rename_progress; - int dirstat_percent; + int dirstat_permille; int setup; int abbrev; const char *prefix; @@ -270,6 +273,7 @@ extern void diffcore_fix_diff_index(struct diff_options *); extern int diff_queue_is_empty(void); extern void diff_flush(struct diff_options*); +extern void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc); /* diff-raw status letters */ #define DIFF_STATUS_ADDED 'A' diff --git a/diffcore-rename.c b/diffcore-rename.c index 0b9e99a04e..f639601c76 100644 --- a/diffcore-rename.c +++ b/diffcore-rename.c @@ -55,22 +55,23 @@ static struct diff_rename_dst *locate_rename_dst(struct diff_filespec *two, /* Table of rename/copy src files */ static struct diff_rename_src { - struct diff_filespec *one; + struct diff_filepair *p; unsigned short score; /* to remember the break score */ } *rename_src; static int rename_src_nr, rename_src_alloc; -static struct diff_rename_src *register_rename_src(struct diff_filespec *one, - unsigned short score) +static struct diff_rename_src *register_rename_src(struct diff_filepair *p) { int first, last; + struct diff_filespec *one = p->one; + unsigned short score = p->score; first = 0; last = rename_src_nr; while (last > first) { int next = (last + first) >> 1; struct diff_rename_src *src = &(rename_src[next]); - int cmp = strcmp(one->path, src->one->path); + int cmp = strcmp(one->path, src->p->one->path); if (!cmp) return src; if (cmp < 0) { @@ -90,7 +91,7 @@ static struct diff_rename_src *register_rename_src(struct diff_filespec *one, if (first < rename_src_nr) memmove(rename_src + first + 1, rename_src + first, (rename_src_nr - first - 1) * sizeof(*rename_src)); - rename_src[first].one = one; + rename_src[first].p = p; rename_src[first].score = score; return &(rename_src[first]); } @@ -205,7 +206,7 @@ static void record_rename_pair(int dst_index, int src_index, int score) if (rename_dst[dst_index].pair) die("internal error: dst already matched."); - src = rename_src[src_index].one; + src = rename_src[src_index].p->one; src->rename_used++; src->count++; @@ -389,7 +390,7 @@ static int find_exact_renames(struct diff_options *options) init_hash(&file_table); for (i = 0; i < rename_src_nr; i++) - insert_file_table(&file_table, -1, i, rename_src[i].one); + insert_file_table(&file_table, -1, i, rename_src[i].p->one); for (i = 0; i < rename_dst_nr; i++) insert_file_table(&file_table, 1, i, rename_dst[i].two); @@ -419,6 +420,55 @@ static void record_if_better(struct diff_score m[], struct diff_score *o) m[worst] = *o; } +/* + * Returns: + * 0 if we are under the limit; + * 1 if we need to disable inexact rename detection; + * 2 if we would be under the limit if we were given -C instead of -C -C. + */ +static int too_many_rename_candidates(int num_create, + struct diff_options *options) +{ + int rename_limit = options->rename_limit; + int num_src = rename_src_nr; + int i; + + options->needed_rename_limit = 0; + + /* + * This basically does a test for the rename matrix not + * growing larger than a "rename_limit" square matrix, ie: + * + * num_create * num_src > rename_limit * rename_limit + * + * but handles the potential overflow case specially (and we + * assume at least 32-bit integers) + */ + if (rename_limit <= 0 || rename_limit > 32767) + rename_limit = 32767; + if ((num_create <= rename_limit || num_src <= rename_limit) && + (num_create * num_src <= rename_limit * rename_limit)) + return 0; + + options->needed_rename_limit = + num_src > num_create ? num_src : num_create; + + /* Are we running under -C -C? */ + if (!DIFF_OPT_TST(options, FIND_COPIES_HARDER)) + return 1; + + /* Would we bust the limit if we were running under -C? */ + for (num_src = i = 0; i < rename_src_nr; i++) { + if (diff_unmodified_pair(rename_src[i].p)) + continue; + num_src++; + } + if ((num_create <= rename_limit || num_src <= rename_limit) && + (num_create * num_src <= rename_limit * rename_limit)) + return 2; + return 1; +} + static int find_renames(struct diff_score *mx, int dst_cnt, int minimum_score, int copies) { int count = 0, i; @@ -432,7 +482,7 @@ static int find_renames(struct diff_score *mx, int dst_cnt, int minimum_score, i 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) + if (!copies && rename_src[mx[i].src].p->one->rename_used) continue; record_rename_pair(mx[i].dst, mx[i].src, mx[i].score); count++; @@ -444,12 +494,11 @@ void diffcore_rename(struct diff_options *options) { int detect_rename = options->detect_rename; int minimum_score = options->rename_score; - int rename_limit = options->rename_limit; struct diff_queue_struct *q = &diff_queued_diff; struct diff_queue_struct outq; struct diff_score *mx; - int i, j, rename_count; - int num_create, num_src, dst_cnt; + int i, j, rename_count, skip_unmodified = 0; + int num_create, dst_cnt; struct progress *progress = NULL; if (!minimum_score) @@ -476,7 +525,7 @@ void diffcore_rename(struct diff_options *options) */ if (p->broken_pair && !p->score) p->one->rename_used++; - register_rename_src(p->one, p->score); + register_rename_src(p); } else if (detect_rename == DIFF_DETECT_COPY) { /* @@ -484,7 +533,7 @@ void diffcore_rename(struct diff_options *options) * one, to indicate ourselves as a user. */ p->one->rename_used++; - register_rename_src(p->one, p->score); + register_rename_src(p); } } if (rename_dst_nr == 0 || rename_src_nr == 0) @@ -505,29 +554,20 @@ void diffcore_rename(struct diff_options *options) * files still remain as options for rename/copies!) */ num_create = (rename_dst_nr - rename_count); - num_src = rename_src_nr; /* All done? */ if (!num_create) goto cleanup; - /* - * This basically does a test for the rename matrix not - * growing larger than a "rename_limit" square matrix, ie: - * - * num_create * num_src > rename_limit * rename_limit - * - * but handles the potential overflow case specially (and we - * assume at least 32-bit integers) - */ - options->needed_rename_limit = 0; - if (rename_limit <= 0 || rename_limit > 32767) - rename_limit = 32767; - if ((num_create > rename_limit && num_src > rename_limit) || - (num_create * num_src > rename_limit * rename_limit)) { - options->needed_rename_limit = - num_src > num_create ? num_src : num_create; + switch (too_many_rename_candidates(num_create, options)) { + case 1: goto cleanup; + case 2: + options->degraded_cc_to_c = 1; + skip_unmodified = 1; + break; + default: + break; } if (options->show_rename_progress) { @@ -549,8 +589,13 @@ void diffcore_rename(struct diff_options *options) m[j].dst = -1; for (j = 0; j < rename_src_nr; j++) { - struct diff_filespec *one = rename_src[j].one; + struct diff_filespec *one = rename_src[j].p->one; struct diff_score this_src; + + if (skip_unmodified && + diff_unmodified_pair(rename_src[j].p)) + continue; + this_src.score = estimate_similarity(one, two, minimum_score); this_src.name_score = basename_same(one, two); @@ -230,7 +230,7 @@ static int match_pathspec_item(const struct pathspec_item *item, int prefix, return MATCHED_RECURSIVELY; } - if (item->has_wildcard && !fnmatch(match, name, 0)) + if (item->use_wildcard && !fnmatch(match, name, 0)) return MATCHED_FNMATCH; return 0; @@ -1105,57 +1105,45 @@ int file_exists(const char *f) } /* - * get_relative_cwd() gets the prefix of the current working directory - * relative to 'dir'. If we are not inside 'dir', it returns NULL. - * - * As a convenience, it also returns NULL if 'dir' is already NULL. The - * reason for this behaviour is that it is natural for functions returning - * directory names to return NULL to say "this directory does not exist" - * or "this directory is invalid". These cases are usually handled the - * same as if the cwd is not inside 'dir' at all, so get_relative_cwd() - * returns NULL for both of them. - * - * Most notably, get_relative_cwd(buffer, size, get_git_work_tree()) - * unifies the handling of "outside work tree" with "no work tree at all". + * Given two normalized paths (a trailing slash is ok), if subdir is + * outside dir, return -1. Otherwise return the offset in subdir that + * can be used as relative path to dir. */ -char *get_relative_cwd(char *buffer, int size, const char *dir) +int dir_inside_of(const char *subdir, const char *dir) { - char *cwd = buffer; - - if (!dir) - return NULL; - if (!getcwd(buffer, size)) - die_errno("can't find the current directory"); + int offset = 0; - if (!is_absolute_path(dir)) - dir = real_path(dir); + assert(dir && subdir && *dir && *subdir); - while (*dir && *dir == *cwd) { + while (*dir && *subdir && *dir == *subdir) { dir++; - cwd++; - } - if (*dir) - return NULL; - switch (*cwd) { - case '\0': - return cwd; - 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; + subdir++; + offset++; } + + /* hel[p]/me vs hel[l]/yeah */ + if (*dir && *subdir) + return -1; + + if (!*subdir) + return !*dir ? offset : -1; /* same dir */ + + /* foo/[b]ar vs foo/[] */ + if (is_dir_sep(dir[-1])) + return is_dir_sep(subdir[-1]) ? offset : -1; + + /* foo[/]bar vs foo[] */ + return is_dir_sep(*subdir) ? offset + 1 : -1; } int is_inside_dir(const char *dir) { - char buffer[PATH_MAX]; - return get_relative_cwd(buffer, sizeof(buffer), dir) != NULL; + char cwd[PATH_MAX]; + if (!dir) + return 0; + if (!getcwd(cwd, sizeof(cwd))) + die_errno("can't find the current directory"); + return dir_inside_of(cwd, dir) >= 0; } int is_empty_dir(const char *path) @@ -1286,8 +1274,8 @@ int init_pathspec(struct pathspec *pathspec, const char **paths) item->match = path; item->len = strlen(path); - item->has_wildcard = !no_wildcard(path); - if (item->has_wildcard) + item->use_wildcard = !no_wildcard(path); + if (item->use_wildcard) pathspec->has_wildcard = 1; } @@ -85,8 +85,8 @@ extern void add_exclude(const char *string, const char *base, 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); extern int is_inside_dir(const char *dir); +extern int dir_inside_of(const char *subdir, const char *dir); static inline int is_dot_or_dotdot(const char *name) { diff --git a/git-parse-remote.sh b/git-parse-remote.sh index ea093d251d..b24119d69c 100644 --- a/git-parse-remote.sh +++ b/git-parse-remote.sh @@ -50,3 +50,41 @@ get_remote_merge_branch () { esac esac } + +error_on_missing_default_upstream () { + cmd="$1" + op_type="$2" + op_prep="$3" + example="$4" + branch_name=$(git symbolic-ref -q HEAD) + if test -z "$branch_name" + then + echo "You are not currently on a branch, so I cannot use any +'branch.<branchname>.merge' in your configuration file. +Please specify which branch you want to $op_type $op_prep on the command +line and try again (e.g. '$example'). +See git-${cmd}(1) for details." + else + echo "You asked me to $cmd without telling me which branch you +want to $op_type $op_prep, and 'branch.${branch_name#refs/heads/}.merge' in +your configuration file does not tell me, either. Please +specify which branch you want to use on the command line and +try again (e.g. '$example'). +See git-${cmd}(1) for details. + +If you often $op_type $op_prep the same branch, you may want to +use something like the following in your configuration file: + [branch \"${branch_name#refs/heads/}\"] + remote = <nickname> + merge = <remote-ref>" + test rebase = "$op_type" && + echo " rebase = true" + echo " + [remote \"<nickname>\"] + url = <url> + fetch = <refspec> + +See git-config(1) for details." + fi + exit 1 +} diff --git a/git-pull.sh b/git-pull.sh index 4e9e0e49ec..fb9e2df931 100755 --- a/git-pull.sh +++ b/git-pull.sh @@ -168,34 +168,10 @@ error_on_no_merge_candidates () { echo "You asked to pull from the remote '$1', but did not specify" echo "a branch. Because this is not the default configured remote" echo "for your current branch, you must specify a branch on the command line." - elif [ -z "$curr_branch" ]; then - echo "You are not currently on a branch, so I cannot use any" - echo "'branch.<branchname>.merge' in your configuration file." - echo "Please specify which remote branch you want to use on the command" - echo "line and try again (e.g. 'git pull <repository> <refspec>')." - echo "See git-pull(1) for details." - elif [ -z "$upstream" ]; then - echo "You asked me to pull without telling me which branch you" - echo "want to $op_type $op_prep, and 'branch.${curr_branch}.merge' in" - echo "your configuration file does not tell me, either. Please" - echo "specify which branch you want to use on the command line and" - echo "try again (e.g. 'git pull <repository> <refspec>')." - echo "See git-pull(1) for details." - echo - echo "If you often $op_type $op_prep the same branch, you may want to" - echo "use something like the following in your configuration file:" - echo - echo " [branch \"${curr_branch}\"]" - echo " remote = <nickname>" - echo " merge = <remote-ref>" - test rebase = "$op_type" && - echo " rebase = true" - echo - echo " [remote \"<nickname>\"]" - echo " url = <url>" - echo " fetch = <refspec>" - echo - echo "See git-config(1) for details." + elif [ -z "$curr_branch" -o -z "$upstream" ]; then + . git-parse-remote + error_on_missing_default_upstream "pull" $op_type $op_prep \ + "git pull <repository> <refspec>" else echo "Your configuration specifies to $op_type $op_prep the ref '${upstream#refs/heads/}'" echo "from the remote, but no such ref was fetched." diff --git a/git-rebase--am.sh b/git-rebase--am.sh new file mode 100644 index 0000000000..c815a2412c --- /dev/null +++ b/git-rebase--am.sh @@ -0,0 +1,30 @@ +#!/bin/sh +# +# Copyright (c) 2010 Junio C Hamano. +# + +. git-sh-setup + +case "$action" in +continue) + git am --resolved --resolvemsg="$resolvemsg" && + move_to_original_branch + exit + ;; +skip) + git am --skip --resolvemsg="$resolvemsg" && + move_to_original_branch + exit + ;; +esac + +test -n "$rebase_root" && root_flag=--root + +git format-patch -k --stdout --full-index --ignore-if-in-upstream \ + --src-prefix=a/ --dst-prefix=b/ \ + --no-renames $root_flag "$revisions" | +git am $git_am_opt --rebasing --resolvemsg="$resolvemsg" && +move_to_original_branch +ret=$? +test 0 != $ret -a -d "$state_dir" && write_basic_state +exit $ret diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index c308529a9f..41ba96aeb7 100755..100644 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -10,48 +10,22 @@ # The original idea comes from Eric W. Biederman, in # http://article.gmane.org/gmane.comp.version-control.git/22407 -OPTIONS_KEEPDASHDASH= -OPTIONS_SPEC="\ -git-rebase [-i] [options] [--] <upstream> [<branch>] -git-rebase [-i] (--continue | --abort | --skip) --- - Available options are -v,verbose display a diffstat of what changed upstream -onto= rebase onto given branch instead of upstream -p,preserve-merges try to recreate merges instead of ignoring them -s,strategy= use the given merge strategy -no-ff cherry-pick all commits, even if unchanged -m,merge always used (no-op) -i,interactive always used (no-op) - Actions: -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 -" - . git-sh-setup -require_work_tree - -DOTEST="$GIT_DIR/rebase-merge" # The file containing rebase commands, comments, and empty lines. # This file is created by "git rebase -i" then edited by the user. As # the lines are processed, they are removed from the front of this -# file and written to the tail of $DONE. -TODO="$DOTEST"/git-rebase-todo +# file and written to the tail of $done. +todo="$state_dir"/git-rebase-todo # The rebase command lines that have already been processed. A line # is moved here when it is first handled, before any associated user # actions. -DONE="$DOTEST"/done +done="$state_dir"/done # The commit message that is planned to be used for any changes that # need to be committed following a user interaction. -MSG="$DOTEST"/message +msg="$state_dir"/message # The file into which is accumulated the suggested commit message for # squash/fixup commands. When the first of a series of squash/fixups @@ -61,34 +35,34 @@ MSG="$DOTEST"/message # is appended to the file as it is processed. # # The first line of the file is of the form -# # This is a combination of $COUNT commits. -# where $COUNT is the number of commits whose messages have been +# # This is a combination of $count commits. +# where $count is the number of commits whose messages have been # written to the file so far (including the initial "pick" commit). # Each time that a commit message is processed, this line is read and # updated. It is deleted just before the combined commit is made. -SQUASH_MSG="$DOTEST"/message-squash +squash_msg="$state_dir"/message-squash # If the current series of squash/fixups has not yet included a squash # command, then this file exists and holds the commit message of the # original "pick" commit. (If the series ends without a "squash" # command, then this can be used as the commit message of the combined # commit without opening the editor.) -FIXUP_MSG="$DOTEST"/message-fixup +fixup_msg="$state_dir"/message-fixup -# $REWRITTEN is the name of a directory containing files for each -# commit that is reachable by at least one merge base of $HEAD and -# $UPSTREAM. They are not necessarily rewritten, but their children +# $rewritten is the name of a directory containing files for each +# commit that is reachable by at least one merge base of $head and +# $upstream. They are not necessarily rewritten, but their children # might be. This ensures that commits on merged, but otherwise # unrelated side branches are left alone. (Think "X" in the man page's # example.) -REWRITTEN="$DOTEST"/rewritten +rewritten="$state_dir"/rewritten -DROPPED="$DOTEST"/dropped +dropped="$state_dir"/dropped # A script to set the GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and # GIT_AUTHOR_DATE that will be used for the commit that is currently # being rebased. -AUTHOR_SCRIPT="$DOTEST"/author-script +author_script="$state_dir"/author-script # When an "edit" rebase command is being processed, the SHA1 of the # commit to be edited is recorded in this file. When "git rebase @@ -96,69 +70,31 @@ AUTHOR_SCRIPT="$DOTEST"/author-script # will be amended to the HEAD commit, but only provided the HEAD # commit is still the commit to be edited. When any other rebase # command is processed, this file is deleted. -AMEND="$DOTEST"/amend +amend="$state_dir"/amend # For the post-rewrite hook, we make a list of rewritten commits and # their new sha1s. The rewritten-pending list keeps the sha1s of # commits that have been processed, but not committed yet, # e.g. because they are waiting for a 'squash' command. -REWRITTEN_LIST="$DOTEST"/rewritten-list -REWRITTEN_PENDING="$DOTEST"/rewritten-pending - -PRESERVE_MERGES= -STRATEGY= -ONTO= -VERBOSE= -OK_TO_SKIP_PRE_REBASE= -REBASE_ROOT= -AUTOSQUASH= -test "$(git config --bool rebase.autosquash)" = "true" && AUTOSQUASH=t -NEVER_FF= - -GIT_CHERRY_PICK_HELP="\ -hint: after resolving the conflicts, mark the corrected paths -hint: with 'git add <paths>' and run 'git rebase --continue'" +rewritten_list="$state_dir"/rewritten-list +rewritten_pending="$state_dir"/rewritten-pending + +GIT_CHERRY_PICK_HELP="$resolvemsg" export GIT_CHERRY_PICK_HELP warn () { printf '%s\n' "$*" >&2 } -output () { - case "$VERBOSE" in - '') - output=$("$@" 2>&1 ) - status=$? - test $status != 0 && printf "%s\n" "$output" - return $status - ;; - *) - "$@" - ;; - esac -} - # Output the commit message for the specified commit. commit_message () { git cat-file commit "$1" | sed "1,/^$/d" } -run_pre_rebase_hook () { - if test -z "$OK_TO_SKIP_PRE_REBASE" && - test -x "$GIT_DIR/hooks/pre-rebase" - then - "$GIT_DIR/hooks/pre-rebase" ${1+"$@"} || { - echo >&2 "The pre-rebase hook refused to rebase." - exit 1 - } - fi -} - - -ORIG_REFLOG_ACTION="$GIT_REFLOG_ACTION" +orig_reflog_action="$GIT_REFLOG_ACTION" comment_for_reflog () { - case "$ORIG_REFLOG_ACTION" in + case "$orig_reflog_action" in ''|rebase*) GIT_REFLOG_ACTION="rebase -i ($1)" export GIT_REFLOG_ACTION @@ -168,16 +104,16 @@ comment_for_reflog () { last_count= mark_action_done () { - sed -e 1q < "$TODO" >> "$DONE" - sed -e 1d < "$TODO" >> "$TODO".new - mv -f "$TODO".new "$TODO" - count=$(sane_grep -c '^[^#]' < "$DONE") - total=$(($count+$(sane_grep -c '^[^#]' < "$TODO"))) - if test "$last_count" != "$count" + sed -e 1q < "$todo" >> "$done" + sed -e 1d < "$todo" >> "$todo".new + mv -f "$todo".new "$todo" + new_count=$(sane_grep -c '^[^#]' < "$done") + total=$(($new_count+$(sane_grep -c '^[^#]' < "$todo"))) + if test "$last_count" != "$new_count" then - last_count=$count - printf "Rebasing (%d/%d)\r" $count $total - test -z "$VERBOSE" || echo + last_count=$new_count + printf "Rebasing (%d/%d)\r" $new_count $total + test -z "$verbose" || echo fi } @@ -193,22 +129,22 @@ make_patch () { *) echo "Root commit" ;; - esac > "$DOTEST"/patch - test -f "$MSG" || - commit_message "$1" > "$MSG" - test -f "$AUTHOR_SCRIPT" || - get_author_ident_from_commit "$1" > "$AUTHOR_SCRIPT" + esac > "$state_dir"/patch + test -f "$msg" || + commit_message "$1" > "$msg" + test -f "$author_script" || + get_author_ident_from_commit "$1" > "$author_script" } die_with_patch () { - echo "$1" > "$DOTEST"/stopped-sha + echo "$1" > "$state_dir"/stopped-sha make_patch "$1" git rerere die "$2" } die_abort () { - rm -rf "$DOTEST" + rm -rf "$state_dir" die "$1" } @@ -228,15 +164,10 @@ do_with_author () { pick_one () { ff=--ff case "$1" in -n) sha1=$2; ff= ;; *) sha1=$1 ;; esac - case "$NEVER_FF" in '') ;; ?*) ff= ;; esac + case "$force_rebase" in '') ;; ?*) ff= ;; esac output git rev-parse --verify $sha1 || die "Invalid commit name: $sha1" - test -d "$REWRITTEN" && + test -d "$rewritten" && pick_one_preserving_merges "$@" && return - if test -n "$REBASE_ROOT" - then - output git cherry-pick "$@" - return - fi output git cherry-pick $ff "$@" } @@ -253,20 +184,20 @@ pick_one_preserving_merges () { esac sha1=$(git rev-parse $sha1) - if test -f "$DOTEST"/current-commit + if test -f "$state_dir"/current-commit then if test "$fast_forward" = t then while read current_commit do - git rev-parse HEAD > "$REWRITTEN"/$current_commit - done <"$DOTEST"/current-commit - rm "$DOTEST"/current-commit || + git rev-parse HEAD > "$rewritten"/$current_commit + done <"$state_dir"/current-commit + rm "$state_dir"/current-commit || die "Cannot write current commit's replacement sha1" fi fi - echo $sha1 >> "$DOTEST"/current-commit + echo $sha1 >> "$state_dir"/current-commit # rewrite parents; if none were rewritten, we can fast-forward. new_parents= @@ -280,9 +211,9 @@ pick_one_preserving_merges () { p=$(expr "$pend" : ' \([^ ]*\)') pend="${pend# $p}" - if test -f "$REWRITTEN"/$p + if test -f "$rewritten"/$p then - new_p=$(cat "$REWRITTEN"/$p) + new_p=$(cat "$rewritten"/$p) # If the todo reordered commits, and our parent is marked for # rewriting, but hasn't been gotten to yet, assume the user meant to @@ -301,10 +232,10 @@ pick_one_preserving_merges () { ;; esac else - if test -f "$DROPPED"/$p + if test -f "$dropped"/$p then fast_forward=f - replacement="$(cat "$DROPPED"/$p)" + replacement="$(cat "$dropped"/$p)" test -z "$replacement" && replacement=root pend=" $replacement$pend" else @@ -333,18 +264,19 @@ pick_one_preserving_merges () { test "a$1" = a-n && die "Refusing to squash a merge: $sha1" # redo merge - author_script=$(get_author_ident_from_commit $sha1) - eval "$author_script" - msg="$(commit_message $sha1)" + author_script_content=$(get_author_ident_from_commit $sha1) + eval "$author_script_content" + msg_content="$(commit_message $sha1)" # No point in merging the first parent, that's HEAD new_parents=${new_parents# $first_parent} if ! do_with_author output \ - git merge --no-ff $STRATEGY -m "$msg" $new_parents + git merge --no-ff ${strategy:+-s $strategy} -m \ + "$msg_content" $new_parents then - printf "%s\n" "$msg" > "$GIT_DIR"/MERGE_MSG + printf "%s\n" "$msg_content" > "$GIT_DIR"/MERGE_MSG die_with_patch $sha1 "Error redoing merge $sha1" fi - echo "$sha1 $(git rev-parse HEAD^0)" >> "$REWRITTEN_LIST" + echo "$sha1 $(git rev-parse HEAD^0)" >> "$rewritten_list" ;; *) output git cherry-pick "$@" || @@ -365,46 +297,46 @@ nth_string () { } update_squash_messages () { - if test -f "$SQUASH_MSG"; then - mv "$SQUASH_MSG" "$SQUASH_MSG".bak || exit - COUNT=$(($(sed -n \ + if test -f "$squash_msg"; then + mv "$squash_msg" "$squash_msg".bak || exit + count=$(($(sed -n \ -e "1s/^# This is a combination of \(.*\) commits\./\1/p" \ - -e "q" < "$SQUASH_MSG".bak)+1)) + -e "q" < "$squash_msg".bak)+1)) { - echo "# This is a combination of $COUNT commits." + echo "# This is a combination of $count commits." sed -e 1d -e '2,/^./{ /^$/d - }' <"$SQUASH_MSG".bak - } >"$SQUASH_MSG" + }' <"$squash_msg".bak + } >"$squash_msg" else - commit_message HEAD > "$FIXUP_MSG" || die "Cannot write $FIXUP_MSG" - COUNT=2 + commit_message HEAD > "$fixup_msg" || die "Cannot write $fixup_msg" + count=2 { echo "# This is a combination of 2 commits." echo "# The first commit's message is:" echo - cat "$FIXUP_MSG" - } >"$SQUASH_MSG" + cat "$fixup_msg" + } >"$squash_msg" fi case $1 in squash) - rm -f "$FIXUP_MSG" + rm -f "$fixup_msg" echo - echo "# This is the $(nth_string $COUNT) commit message:" + echo "# This is the $(nth_string $count) commit message:" echo commit_message $2 ;; fixup) echo - echo "# The $(nth_string $COUNT) commit message will be skipped:" + echo "# The $(nth_string $count) commit message will be skipped:" echo commit_message $2 | sed -e 's/^/# /' ;; - esac >>"$SQUASH_MSG" + esac >>"$squash_msg" } peek_next_command () { - sed -n -e "/^#/d" -e '/^$/d' -e "s/ .*//p" -e "q" < "$TODO" + sed -n -e "/^#/d" -e '/^$/d' -e "s/ .*//p" -e "q" < "$todo" } # A squash/fixup has failed. Prepare the long version of the squash @@ -414,24 +346,24 @@ peek_next_command () { # messages, effectively causing the combined commit to be used as the # new basis for any further squash/fixups. Args: sha1 rest die_failed_squash() { - mv "$SQUASH_MSG" "$MSG" || exit - rm -f "$FIXUP_MSG" - cp "$MSG" "$GIT_DIR"/MERGE_MSG || exit + mv "$squash_msg" "$msg" || exit + rm -f "$fixup_msg" + cp "$msg" "$GIT_DIR"/MERGE_MSG || exit warn warn "Could not apply $1... $2" die_with_patch $1 "" } flush_rewritten_pending() { - test -s "$REWRITTEN_PENDING" || return + test -s "$rewritten_pending" || return newsha1="$(git rev-parse HEAD^0)" - sed "s/$/ $newsha1/" < "$REWRITTEN_PENDING" >> "$REWRITTEN_LIST" - rm -f "$REWRITTEN_PENDING" + sed "s/$/ $newsha1/" < "$rewritten_pending" >> "$rewritten_list" + rm -f "$rewritten_pending" } record_in_rewritten() { oldsha1="$(git rev-parse $1)" - echo "$oldsha1" >> "$REWRITTEN_PENDING" + echo "$oldsha1" >> "$rewritten_pending" case "$(peek_next_command)" in squash|s|fixup|f) @@ -443,8 +375,8 @@ record_in_rewritten() { } do_next () { - rm -f "$MSG" "$AUTHOR_SCRIPT" "$AMEND" || exit - read -r command sha1 rest < "$TODO" + rm -f "$msg" "$author_script" "$amend" || exit + read -r command sha1 rest < "$todo" case "$command" in '#'*|''|noop) mark_action_done @@ -472,9 +404,9 @@ do_next () { mark_action_done pick_one $sha1 || die_with_patch $sha1 "Could not apply $sha1... $rest" - echo "$sha1" > "$DOTEST"/stopped-sha + echo "$sha1" > "$state_dir"/stopped-sha make_patch $sha1 - git rev-parse --verify HEAD > "$AMEND" + git rev-parse --verify HEAD > "$amend" warn "Stopped at $sha1... $rest" warn "You can amend the commit now, with" warn @@ -497,47 +429,47 @@ do_next () { esac comment_for_reflog $squash_style - test -f "$DONE" && has_action "$DONE" || + test -f "$done" && has_action "$done" || die "Cannot '$squash_style' without a previous commit" mark_action_done update_squash_messages $squash_style $sha1 - author_script=$(get_author_ident_from_commit HEAD) - echo "$author_script" > "$AUTHOR_SCRIPT" - eval "$author_script" + author_script_content=$(get_author_ident_from_commit HEAD) + echo "$author_script_content" > "$author_script" + eval "$author_script_content" output git reset --soft HEAD^ pick_one -n $sha1 || die_failed_squash $sha1 "$rest" case "$(peek_next_command)" in squash|s|fixup|f) # This is an intermediate commit; its message will only be # used in case of trouble. So use the long version: - do_with_author output git commit --no-verify -F "$SQUASH_MSG" || + do_with_author output git commit --no-verify -F "$squash_msg" || die_failed_squash $sha1 "$rest" ;; *) # This is the final command of this squash/fixup group - if test -f "$FIXUP_MSG" + if test -f "$fixup_msg" then - do_with_author git commit --no-verify -F "$FIXUP_MSG" || + do_with_author git commit --no-verify -F "$fixup_msg" || die_failed_squash $sha1 "$rest" else - cp "$SQUASH_MSG" "$GIT_DIR"/SQUASH_MSG || exit + cp "$squash_msg" "$GIT_DIR"/SQUASH_MSG || exit rm -f "$GIT_DIR"/MERGE_MSG do_with_author git commit --no-verify -e || die_failed_squash $sha1 "$rest" fi - rm -f "$SQUASH_MSG" "$FIXUP_MSG" + rm -f "$squash_msg" "$fixup_msg" ;; esac record_in_rewritten $sha1 ;; x|"exec") - read -r command rest < "$TODO" + read -r command rest < "$todo" mark_action_done printf 'Executing: %s\n' "$rest" # "exec" command doesn't take a sha1 in the todo-list. # => can't just use $sha1 here. - git rev-parse --verify HEAD > "$DOTEST"/stopped-sha + git rev-parse --verify HEAD > "$state_dir"/stopped-sha ${SHELL:-@SHELL_PATH@} -c "$rest" # Actual execution status=$? if test "$status" -ne 0 @@ -563,42 +495,40 @@ do_next () { warn "Unknown command: $command $sha1 $rest" if git rev-parse --verify -q "$sha1" >/dev/null then - die_with_patch $sha1 "Please fix this in the file $TODO." + die_with_patch $sha1 "Please fix this in the file $todo." else - die "Please fix this in the file $TODO." + die "Please fix this in the file $todo." fi ;; esac - test -s "$TODO" && return + test -s "$todo" && return comment_for_reflog finish && - HEADNAME=$(cat "$DOTEST"/head-name) && - OLDHEAD=$(cat "$DOTEST"/head) && - SHORTONTO=$(git rev-parse --short $(cat "$DOTEST"/onto)) && - NEWHEAD=$(git rev-parse HEAD) && - case $HEADNAME in + shortonto=$(git rev-parse --short $onto) && + newhead=$(git rev-parse HEAD) && + case $head_name in refs/*) - message="$GIT_REFLOG_ACTION: $HEADNAME onto $SHORTONTO" && - git update-ref -m "$message" $HEADNAME $NEWHEAD $OLDHEAD && - git symbolic-ref HEAD $HEADNAME + message="$GIT_REFLOG_ACTION: $head_name onto $shortonto" && + git update-ref -m "$message" $head_name $newhead $orig_head && + git symbolic-ref HEAD $head_name ;; esac && { - test ! -f "$DOTEST"/verbose || - git diff-tree --stat $(cat "$DOTEST"/head)..HEAD + test ! -f "$state_dir"/verbose || + git diff-tree --stat $orig_head..HEAD } && { - test -s "$REWRITTEN_LIST" && - git notes copy --for-rewrite=rebase < "$REWRITTEN_LIST" || + test -s "$rewritten_list" && + git notes copy --for-rewrite=rebase < "$rewritten_list" || true # we don't care if this copying failed } && if test -x "$GIT_DIR"/hooks/post-rewrite && - test -s "$REWRITTEN_LIST"; then - "$GIT_DIR"/hooks/post-rewrite rebase < "$REWRITTEN_LIST" + test -s "$rewritten_list"; then + "$GIT_DIR"/hooks/post-rewrite rebase < "$rewritten_list" true # we don't care if this hook failed fi && - rm -rf "$DOTEST" && + rm -rf "$state_dir" && git gc --auto && - warn "Successfully rebased and updated $HEADNAME." + warn "Successfully rebased and updated $head_name." exit } @@ -618,11 +548,11 @@ skip_unnecessary_picks () { # fd=3 means we skip the command case "$fd,$command" in 3,pick|3,p) - # pick a commit whose parent is current $ONTO -> skip + # pick a commit whose parent is current $onto -> skip sha1=${rest%% *} case "$(git rev-parse --verify --quiet "$sha1"^)" in - "$ONTO"*) - ONTO=$sha1 + "$onto"*) + onto=$sha1 ;; *) fd=1 @@ -637,32 +567,16 @@ skip_unnecessary_picks () { ;; esac printf '%s\n' "$command${rest:+ }$rest" >&$fd - done <"$TODO" >"$TODO.new" 3>>"$DONE" && - mv -f "$TODO".new "$TODO" && + done <"$todo" >"$todo.new" 3>>"$done" && + mv -f "$todo".new "$todo" && case "$(peek_next_command)" in squash|s|fixup|f) - record_in_rewritten "$ONTO" + record_in_rewritten "$onto" ;; esac || die "Could not skip unnecessary pick commands" } -# check if no other options are set -is_standalone () { - test $# -eq 2 -a "$2" = '--' && - test -z "$ONTO" && - test -z "$PRESERVE_MERGES" && - test -z "$STRATEGY" && - test -z "$VERBOSE" -} - -get_saved_options () { - test -d "$REWRITTEN" && PRESERVE_MERGES=t - test -f "$DOTEST"/strategy && STRATEGY="$(cat "$DOTEST"/strategy)" - test -f "$DOTEST"/verbose && VERBOSE=t - test -f "$DOTEST"/rebase-root && REBASE_ROOT=t -} - # Rearrange the todo list that has both "pick sha1 msg" and # "pick sha1 fixup!/squash! msg" appears in it so that the latter # comes immediately after the former, and change "pick" to @@ -699,7 +613,7 @@ rearrange_squash () { esac printf '%s\n' "$pick $sha1 $message" used="$used$sha1 " - while read -r squash action msg + while read -r squash action msg_content do case " $used" in *" $squash "*) continue ;; @@ -709,13 +623,13 @@ rearrange_squash () { +*) action="${action#+}" # full sha1 prefix test - case "$msg" in "$sha1"*) emit=1;; esac ;; + case "$msg_content" in "$sha1"*) emit=1;; esac ;; *) # message prefix test - case "$message" in "$msg"*) emit=1;; esac ;; + case "$message" in "$msg_content"*) emit=1;; esac ;; esac if test $emit = 1; then - printf '%s\n' "$action $squash $action! $msg" + printf '%s\n' "$action $squash $action! $msg_content" used="$used$squash " fi done <"$1.sq" @@ -724,296 +638,159 @@ rearrange_squash () { rm -f "$1.sq" "$1.rearranged" } -LF=' -' -parse_onto () { - case "$1" in - *...*) - if left=${1%...*} right=${1#*...} && - onto=$(git merge-base --all ${left:-HEAD} ${right:-HEAD}) - then - case "$onto" in - ?*"$LF"?* | '') - exit 1 ;; - esac - echo "$onto" - exit 0 - fi - esac - git rev-parse --verify "$1^0" -} - -while test $# != 0 -do - case "$1" in - --no-verify) - OK_TO_SKIP_PRE_REBASE=yes - ;; - --verify) - OK_TO_SKIP_PRE_REBASE= - ;; - --continue) - is_standalone "$@" || usage - get_saved_options - comment_for_reflog continue - - test -d "$DOTEST" || die "No interactive rebase running" - - # Sanity check - git rev-parse --verify HEAD >/dev/null || - die "Cannot read HEAD" - git update-index --ignore-submodules --refresh && - git diff-files --quiet --ignore-submodules || - die "Working tree is dirty" - - # do we have anything to commit? - if git diff-index --cached --quiet --ignore-submodules HEAD -- +case "$action" in +continue) + # do we have anything to commit? + if git diff-index --cached --quiet --ignore-submodules HEAD -- + then + : Nothing to commit -- skip this + else + . "$author_script" || + die "Cannot find the author identity" + current_head= + if test -f "$amend" then - : Nothing to commit -- skip this - else - . "$AUTHOR_SCRIPT" || - die "Cannot find the author identity" - amend= - if test -f "$AMEND" - then - amend=$(git rev-parse --verify HEAD) - test "$amend" = $(cat "$AMEND") || - die "\ + current_head=$(git rev-parse --verify HEAD) + test "$current_head" = $(cat "$amend") || + die "\ You have uncommitted changes in your working tree. Please, commit them first and then run 'git rebase --continue' again." - git reset --soft HEAD^ || - die "Cannot rewind the HEAD" - fi - do_with_author git commit --no-verify -F "$MSG" -e || { - test -n "$amend" && git reset --soft $amend - die "Could not commit staged changes." - } + git reset --soft HEAD^ || + die "Cannot rewind the HEAD" fi + do_with_author git commit --no-verify -F "$msg" -e || { + test -n "$current_head" && git reset --soft $current_head + die "Could not commit staged changes." + } + fi - record_in_rewritten "$(cat "$DOTEST"/stopped-sha)" - - require_clean_work_tree "rebase" - do_rest - ;; - --abort) - is_standalone "$@" || usage - get_saved_options - comment_for_reflog abort - - git rerere clear - test -d "$DOTEST" || die "No interactive rebase running" - - HEADNAME=$(cat "$DOTEST"/head-name) - HEAD=$(cat "$DOTEST"/head) - case $HEADNAME in - refs/*) - git symbolic-ref HEAD $HEADNAME - ;; - esac && - output git reset --hard $HEAD && - rm -rf "$DOTEST" - exit - ;; - --skip) - is_standalone "$@" || usage - get_saved_options - comment_for_reflog skip - - git rerere clear - test -d "$DOTEST" || die "No interactive rebase running" - - output git reset --hard && do_rest - ;; - -s) - case "$#,$1" in - *,*=*) - STRATEGY="-s "$(expr "z$1" : 'z-[^=]*=\(.*\)') ;; - 1,*) - usage ;; - *) - STRATEGY="-s $2" - shift ;; - esac - ;; - -m) - # we use merge anyway - ;; - -v) - VERBOSE=t - ;; - -p) - PRESERVE_MERGES=t - ;; - -i) - # yeah, we know - ;; - --no-ff) - NEVER_FF=t - ;; - --root) - REBASE_ROOT=t - ;; - --autosquash) - AUTOSQUASH=t - ;; - --no-autosquash) - AUTOSQUASH= - ;; - --onto) - shift - ONTO=$(parse_onto "$1") || - die "Does not point to a valid commit: $1" - ;; - --) - shift - test -z "$REBASE_ROOT" -a $# -ge 1 -a $# -le 2 || - test ! -z "$REBASE_ROOT" -a $# -le 1 || usage - test -d "$DOTEST" && - die "Interactive rebase already started" - - git var GIT_COMMITTER_IDENT >/dev/null || - die "You need to set your committer info first" + record_in_rewritten "$(cat "$state_dir"/stopped-sha)" - if test -z "$REBASE_ROOT" - then - UPSTREAM_ARG="$1" - UPSTREAM=$(git rev-parse --verify "$1") || die "Invalid base" - test -z "$ONTO" && ONTO=$UPSTREAM - shift - else - UPSTREAM= - UPSTREAM_ARG=--root - test -z "$ONTO" && - die "You must specify --onto when using --root" - fi - run_pre_rebase_hook "$UPSTREAM_ARG" "$@" + require_clean_work_tree "rebase" + do_rest + ;; +skip) + git rerere clear - comment_for_reflog start + do_rest + ;; +esac - require_clean_work_tree "rebase" "Please commit or stash them." +git var GIT_COMMITTER_IDENT >/dev/null || + die "You need to set your committer info first" - if test ! -z "$1" - then - output git checkout "$1" -- || - die "Could not checkout $1" - fi +comment_for_reflog start - HEAD=$(git rev-parse --verify HEAD) || die "No HEAD?" - mkdir "$DOTEST" || die "Could not create temporary $DOTEST" +if test ! -z "$switch_to" +then + output git checkout "$switch_to" -- || + die "Could not checkout $switch_to" +fi - : > "$DOTEST"/interactive || die "Could not mark as interactive" - git symbolic-ref HEAD > "$DOTEST"/head-name 2> /dev/null || - echo "detached HEAD" > "$DOTEST"/head-name +orig_head=$(git rev-parse --verify HEAD) || die "No HEAD?" +mkdir "$state_dir" || die "Could not create temporary $state_dir" - echo $HEAD > "$DOTEST"/head - case "$REBASE_ROOT" in - '') - rm -f "$DOTEST"/rebase-root ;; - *) - : >"$DOTEST"/rebase-root ;; - esac - echo $ONTO > "$DOTEST"/onto - test -z "$STRATEGY" || echo "$STRATEGY" > "$DOTEST"/strategy - test t = "$VERBOSE" && : > "$DOTEST"/verbose - if test t = "$PRESERVE_MERGES" - then - if test -z "$REBASE_ROOT" - then - mkdir "$REWRITTEN" && - for c in $(git merge-base --all $HEAD $UPSTREAM) - do - echo $ONTO > "$REWRITTEN"/$c || - die "Could not init rewritten commits" - done - else - mkdir "$REWRITTEN" && - echo $ONTO > "$REWRITTEN"/root || - die "Could not init rewritten commits" - fi - # No cherry-pick because our first pass is to determine - # parents to rewrite and skipping dropped commits would - # prematurely end our probe - MERGES_OPTION= - first_after_upstream="$(git rev-list --reverse --first-parent $UPSTREAM..$HEAD | head -n 1)" - else - MERGES_OPTION="--no-merges --cherry-pick" - fi - - SHORTHEAD=$(git rev-parse --short $HEAD) - SHORTONTO=$(git rev-parse --short $ONTO) - if test -z "$REBASE_ROOT" - # this is now equivalent to ! -z "$UPSTREAM" - then - SHORTUPSTREAM=$(git rev-parse --short $UPSTREAM) - REVISIONS=$UPSTREAM...$HEAD - SHORTREVISIONS=$SHORTUPSTREAM..$SHORTHEAD - else - REVISIONS=$ONTO...$HEAD - SHORTREVISIONS=$SHORTHEAD - fi - git rev-list $MERGES_OPTION --pretty=oneline --abbrev-commit \ - --abbrev=7 --reverse --left-right --topo-order \ - $REVISIONS | \ - sed -n "s/^>//p" | - while read -r shortsha1 rest +: > "$state_dir"/interactive || die "Could not mark as interactive" +write_basic_state +if test t = "$preserve_merges" +then + if test -z "$rebase_root" + then + mkdir "$rewritten" && + for c in $(git merge-base --all $orig_head $upstream) do - if test t != "$PRESERVE_MERGES" - then - printf '%s\n' "pick $shortsha1 $rest" >> "$TODO" - else - sha1=$(git rev-parse $shortsha1) - if test -z "$REBASE_ROOT" - then - preserve=t - for p in $(git rev-list --parents -1 $sha1 | cut -d' ' -s -f2-) - do - if test -f "$REWRITTEN"/$p -a \( $p != $ONTO -o $sha1 = $first_after_upstream \) - then - preserve=f - fi - done - else - preserve=f - fi - if test f = "$preserve" - then - touch "$REWRITTEN"/$sha1 - printf '%s\n' "pick $shortsha1 $rest" >> "$TODO" - fi - fi + echo $onto > "$rewritten"/$c || + die "Could not init rewritten commits" done - - # Watch for commits that been dropped by --cherry-pick - if test t = "$PRESERVE_MERGES" + else + mkdir "$rewritten" && + echo $onto > "$rewritten"/root || + die "Could not init rewritten commits" + fi + # No cherry-pick because our first pass is to determine + # parents to rewrite and skipping dropped commits would + # prematurely end our probe + merges_option= + first_after_upstream="$(git rev-list --reverse --first-parent $upstream..$orig_head | head -n 1)" +else + merges_option="--no-merges --cherry-pick" +fi + +shorthead=$(git rev-parse --short $orig_head) +shortonto=$(git rev-parse --short $onto) +if test -z "$rebase_root" + # this is now equivalent to ! -z "$upstream" +then + shortupstream=$(git rev-parse --short $upstream) + revisions=$upstream...$orig_head + shortrevisions=$shortupstream..$shorthead +else + revisions=$onto...$orig_head + shortrevisions=$shorthead +fi +git rev-list $merges_option --pretty=oneline --abbrev-commit \ + --abbrev=7 --reverse --left-right --topo-order \ + $revisions | \ + sed -n "s/^>//p" | +while read -r shortsha1 rest +do + if test t != "$preserve_merges" + then + printf '%s\n' "pick $shortsha1 $rest" >> "$todo" + else + sha1=$(git rev-parse $shortsha1) + if test -z "$rebase_root" then - mkdir "$DROPPED" - # Save all non-cherry-picked changes - git rev-list $REVISIONS --left-right --cherry-pick | \ - sed -n "s/^>//p" > "$DOTEST"/not-cherry-picks - # Now all commits and note which ones are missing in - # not-cherry-picks and hence being dropped - git rev-list $REVISIONS | - while read rev + preserve=t + for p in $(git rev-list --parents -1 $sha1 | cut -d' ' -s -f2-) do - if test -f "$REWRITTEN"/$rev -a "$(sane_grep "$rev" "$DOTEST"/not-cherry-picks)" = "" + if test -f "$rewritten"/$p -a \( $p != $onto -o $sha1 = $first_after_upstream \) then - # Use -f2 because if rev-list is telling us this commit is - # not worthwhile, we don't want to track its multiple heads, - # just the history of its first-parent for others that will - # be rebasing on top of it - git rev-list --parents -1 $rev | cut -d' ' -s -f2 > "$DROPPED"/$rev - short=$(git rev-list -1 --abbrev-commit --abbrev=7 $rev) - sane_grep -v "^[a-z][a-z]* $short" <"$TODO" > "${TODO}2" ; mv "${TODO}2" "$TODO" - rm "$REWRITTEN"/$rev + preserve=f fi done + else + preserve=f + fi + if test f = "$preserve" + then + touch "$rewritten"/$sha1 + printf '%s\n' "pick $shortsha1 $rest" >> "$todo" fi + fi +done - test -s "$TODO" || echo noop >> "$TODO" - test -n "$AUTOSQUASH" && rearrange_squash "$TODO" - cat >> "$TODO" << EOF +# Watch for commits that been dropped by --cherry-pick +if test t = "$preserve_merges" +then + mkdir "$dropped" + # Save all non-cherry-picked changes + git rev-list $revisions --left-right --cherry-pick | \ + sed -n "s/^>//p" > "$state_dir"/not-cherry-picks + # Now all commits and note which ones are missing in + # not-cherry-picks and hence being dropped + git rev-list $revisions | + while read rev + do + if test -f "$rewritten"/$rev -a "$(sane_grep "$rev" "$state_dir"/not-cherry-picks)" = "" + then + # Use -f2 because if rev-list is telling us this commit is + # not worthwhile, we don't want to track its multiple heads, + # just the history of its first-parent for others that will + # be rebasing on top of it + git rev-list --parents -1 $rev | cut -d' ' -s -f2 > "$dropped"/$rev + short=$(git rev-list -1 --abbrev-commit --abbrev=7 $rev) + sane_grep -v "^[a-z][a-z]* $short" <"$todo" > "${todo}2" ; mv "${todo}2" "$todo" + rm "$rewritten"/$rev + fi + done +fi + +test -s "$todo" || echo noop >> "$todo" +test -n "$autosquash" && rearrange_squash "$todo" +cat >> "$todo" << EOF -# Rebase $SHORTREVISIONS onto $SHORTONTO +# Rebase $shortrevisions onto $shortonto # # Commands: # p, pick = use commit @@ -1028,22 +805,18 @@ first and then run 'git rebase --continue' again." # EOF - has_action "$TODO" || - die_abort "Nothing to do" +has_action "$todo" || + die_abort "Nothing to do" - cp "$TODO" "$TODO".backup - git_editor "$TODO" || - die_abort "Could not execute editor" +cp "$todo" "$todo".backup +git_editor "$todo" || + die_abort "Could not execute editor" - has_action "$TODO" || - die_abort "Nothing to do" +has_action "$todo" || + die_abort "Nothing to do" - test -d "$REWRITTEN" || test -n "$NEVER_FF" || skip_unnecessary_picks +test -d "$rewritten" || test -n "$force_rebase" || skip_unnecessary_picks - output git checkout $ONTO || die_abort "could not detach HEAD" - git update-ref ORIG_HEAD $HEAD - do_rest - ;; - esac - shift -done +output git checkout $onto || die_abort "could not detach HEAD" +git update-ref ORIG_HEAD $orig_head +do_rest diff --git a/git-rebase--merge.sh b/git-rebase--merge.sh new file mode 100644 index 0000000000..26afc75cc7 --- /dev/null +++ b/git-rebase--merge.sh @@ -0,0 +1,151 @@ +#!/bin/sh +# +# Copyright (c) 2010 Junio C Hamano. +# + +. git-sh-setup + +prec=4 + +read_state () { + onto_name=$(cat "$state_dir"/onto_name) && + end=$(cat "$state_dir"/end) && + msgnum=$(cat "$state_dir"/msgnum) +} + +continue_merge () { + test -d "$state_dir" || die "$state_dir directory does not exist" + + unmerged=$(git ls-files -u) + if test -n "$unmerged" + then + echo "You still have unmerged paths in your index" + echo "did you forget to use git add?" + die "$resolvemsg" + fi + + cmt=`cat "$state_dir/current"` + if ! git diff-index --quiet --ignore-submodules HEAD -- + then + if ! git commit --no-verify -C "$cmt" + then + echo "Commit failed, please do not call \"git commit\"" + echo "directly, but instead do one of the following: " + die "$resolvemsg" + fi + if test -z "$GIT_QUIET" + then + printf "Committed: %0${prec}d " $msgnum + fi + echo "$cmt $(git rev-parse HEAD^0)" >> "$state_dir/rewritten" + else + if test -z "$GIT_QUIET" + then + printf "Already applied: %0${prec}d " $msgnum + fi + fi + test -z "$GIT_QUIET" && + GIT_PAGER='' git log --format=%s -1 "$cmt" + + # onto the next patch: + msgnum=$(($msgnum + 1)) + echo "$msgnum" >"$state_dir/msgnum" +} + +call_merge () { + cmt="$(cat "$state_dir/cmt.$1")" + echo "$cmt" > "$state_dir/current" + hd=$(git rev-parse --verify HEAD) + cmt_name=$(git symbolic-ref HEAD 2> /dev/null || echo HEAD) + msgnum=$(cat "$state_dir/msgnum") + eval GITHEAD_$cmt='"${cmt_name##refs/heads/}~$(($end - $msgnum))"' + eval GITHEAD_$hd='$onto_name' + export GITHEAD_$cmt GITHEAD_$hd + if test -n "$GIT_QUIET" + then + GIT_MERGE_VERBOSITY=1 && export GIT_MERGE_VERBOSITY + fi + test -z "$strategy" && strategy=recursive + eval 'git-merge-$strategy' $strategy_opts '"$cmt^" -- "$hd" "$cmt"' + rv=$? + case "$rv" in + 0) + unset GITHEAD_$cmt GITHEAD_$hd + return + ;; + 1) + git rerere $allow_rerere_autoupdate + die "$resolvemsg" + ;; + 2) + echo "Strategy: $strategy failed, try another" 1>&2 + die "$resolvemsg" + ;; + *) + die "Unknown exit code ($rv) from command:" \ + "git-merge-$strategy $cmt^ -- HEAD $cmt" + ;; + esac +} + +finish_rb_merge () { + move_to_original_branch + git notes copy --for-rewrite=rebase < "$state_dir"/rewritten + if test -x "$GIT_DIR"/hooks/post-rewrite && + test -s "$state_dir"/rewritten; then + "$GIT_DIR"/hooks/post-rewrite rebase < "$state_dir"/rewritten + fi + rm -r "$state_dir" + say All done. +} + +case "$action" in +continue) + read_state + continue_merge + while test "$msgnum" -le "$end" + do + call_merge "$msgnum" + continue_merge + done + finish_rb_merge + exit + ;; +skip) + read_state + git rerere clear + msgnum=$(($msgnum + 1)) + while test "$msgnum" -le "$end" + do + call_merge "$msgnum" + continue_merge + done + finish_rb_merge + exit + ;; +esac + +mkdir -p "$state_dir" +echo "$onto_name" > "$state_dir/onto_name" +write_basic_state + +msgnum=0 +for cmt in `git rev-list --reverse --no-merges "$revisions"` +do + msgnum=$(($msgnum + 1)) + echo "$cmt" > "$state_dir/cmt.$msgnum" +done + +echo 1 >"$state_dir/msgnum" +echo $msgnum >"$state_dir/end" + +end=$msgnum +msgnum=1 + +while test "$msgnum" -le "$end" +do + call_merge "$msgnum" + continue_merge +done + +finish_rb_merge diff --git a/git-rebase.sh b/git-rebase.sh index cbb0ea90ed..7a54bfc618 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -3,7 +3,7 @@ # Copyright (c) 2005 Junio C Hamano. # -USAGE='[--interactive | -i] [-v] [--force-rebase | -f] [--no-ff] [--onto <newbase>] (<upstream>|--root) [<branch>] [--quiet | -q]' +USAGE='[--interactive | -i] [-v] [--force-rebase | -f] [--no-ff] [--onto <newbase>] [<upstream>|--root] [<branch>] [--quiet | -q]' LONG_USAGE='git-rebase replaces <branch> with a new branch of the same name. When the --onto option is provided the new branch starts out with a HEAD equal to <newbase>, otherwise it is equal to <upstream> @@ -28,7 +28,39 @@ Example: git-rebase master~1 topic ' SUBDIRECTORY_OK=Yes -OPTIONS_SPEC= +OPTIONS_KEEPDASHDASH= +OPTIONS_SPEC="\ +git rebase [-i] [options] [--onto <newbase>] [<upstream>] [<branch>] +git rebase [-i] [options] --onto <newbase> --root [<branch>] +git-rebase [-i] --continue | --abort | --skip +-- + Available options are +v,verbose! display a diffstat of what changed upstream +q,quiet! be quiet. implies --no-stat +onto=! rebase onto given branch instead of upstream +p,preserve-merges! try to recreate merges instead of ignoring them +s,strategy=! use the given merge strategy +no-ff! cherry-pick all commits, even if unchanged +m,merge! use merging strategies to rebase +i,interactive! let the user edit the list of commits to rebase +f,force-rebase! force rebase even if branch is up to date +X,strategy-option=! pass the argument through to the merge strategy +stat! display a diffstat of what changed upstream +n,no-stat! do not show diffstat of what changed upstream +verify allow pre-rebase hook to run +rerere-autoupdate allow rerere to update index with resolved conflicts +root! rebase all reachable commits up to the root(s) +autosquash move commits that begin with squash!/fixup! under -i +committer-date-is-author-date! passed to 'git am' +ignore-date! passed to 'git am' +whitespace=! passed to 'git apply' +ignore-whitespace! passed to 'git apply' +C=! passed to 'git apply' + Actions: +continue! continue rebasing process +abort! abort rebasing process and restore original branch +skip! skip current patch and continue rebasing process +" . git-sh-setup set_reflog_action rebase require_work_tree @@ -36,18 +68,18 @@ cd_to_toplevel LF=' ' -OK_TO_SKIP_PRE_REBASE= -RESOLVEMSG=" +ok_to_skip_pre_rebase= +resolvemsg=" When you have resolved this problem run \"git rebase --continue\". If you would prefer to skip this patch, instead run \"git rebase --skip\". To restore the original branch and stop rebasing run \"git rebase --abort\". " -unset newbase -strategy=recursive +unset onto +strategy= strategy_opts= do_merge= -dotest="$GIT_DIR"/rebase-merge -prec=4 +merge_dir="$GIT_DIR"/rebase-merge +apply_dir="$GIT_DIR"/rebase-apply verbose= diffstat= test "$(git config --bool rebase.stat)" = true && diffstat=t @@ -55,92 +87,67 @@ git_am_opt= rebase_root= force_rebase= allow_rerere_autoupdate= - -continue_merge () { - test -n "$prev_head" || die "prev_head must be defined" - test -d "$dotest" || die "$dotest directory does not exist" - - unmerged=$(git ls-files -u) - if test -n "$unmerged" - then - echo "You still have unmerged paths in your index" - echo "did you forget to use git add?" - die "$RESOLVEMSG" - fi - - cmt=`cat "$dotest/current"` - if ! git diff-index --quiet --ignore-submodules HEAD -- +# Non-empty if a rebase was in progress when 'git rebase' was invoked +in_progress= +# One of {am, merge, interactive} +type= +# One of {"$GIT_DIR"/rebase-apply, "$GIT_DIR"/rebase-merge} +state_dir= +# One of {'', continue, skip, abort}, as parsed from command line +action= +preserve_merges= +autosquash= +test "$(git config --bool rebase.autosquash)" = "true" && autosquash=t + +read_basic_state () { + head_name=$(cat "$state_dir"/head-name) && + onto=$(cat "$state_dir"/onto) && + # We always write to orig-head, but interactive rebase used to write to + # head. Fall back to reading from head to cover for the case that the + # user upgraded git with an ongoing interactive rebase. + if test -f "$state_dir"/orig-head then - if ! git commit --no-verify -C "$cmt" - then - echo "Commit failed, please do not call \"git commit\"" - echo "directly, but instead do one of the following: " - die "$RESOLVEMSG" - fi - if test -z "$GIT_QUIET" - then - printf "Committed: %0${prec}d " $msgnum - fi - echo "$cmt $(git rev-parse HEAD^0)" >> "$dotest/rewritten" + orig_head=$(cat "$state_dir"/orig-head) else - if test -z "$GIT_QUIET" - then - printf "Already applied: %0${prec}d " $msgnum - fi - fi - test -z "$GIT_QUIET" && - GIT_PAGER='' git log --format=%s -1 "$cmt" - - prev_head=`git rev-parse HEAD^0` - # save the resulting commit so we can read-tree on it later - echo "$prev_head" > "$dotest/prev_head" + orig_head=$(cat "$state_dir"/head) + fi && + GIT_QUIET=$(cat "$state_dir"/quiet) && + test -f "$state_dir"/verbose && verbose=t + test -f "$state_dir"/strategy && strategy="$(cat "$state_dir"/strategy)" + test -f "$state_dir"/strategy_opts && + strategy_opts="$(cat "$state_dir"/strategy_opts)" + test -f "$state_dir"/allow_rerere_autoupdate && + allow_rerere_autoupdate="$(cat "$state_dir"/allow_rerere_autoupdate)" +} - # onto the next patch: - msgnum=$(($msgnum + 1)) - echo "$msgnum" >"$dotest/msgnum" +write_basic_state () { + echo "$head_name" > "$state_dir"/head-name && + echo "$onto" > "$state_dir"/onto && + echo "$orig_head" > "$state_dir"/orig-head && + echo "$GIT_QUIET" > "$state_dir"/quiet && + test t = "$verbose" && : > "$state_dir"/verbose + test -n "$strategy" && echo "$strategy" > "$state_dir"/strategy + test -n "$strategy_opts" && echo "$strategy_opts" > \ + "$state_dir"/strategy_opts + test -n "$allow_rerere_autoupdate" && echo "$allow_rerere_autoupdate" > \ + "$state_dir"/allow_rerere_autoupdate } -call_merge () { - cmt="$(cat "$dotest/cmt.$1")" - echo "$cmt" > "$dotest/current" - hd=$(git rev-parse --verify HEAD) - cmt_name=$(git symbolic-ref HEAD 2> /dev/null || echo HEAD) - msgnum=$(cat "$dotest/msgnum") - end=$(cat "$dotest/end") - eval GITHEAD_$cmt='"${cmt_name##refs/heads/}~$(($end - $msgnum))"' - eval GITHEAD_$hd='$(cat "$dotest/onto_name")' - export GITHEAD_$cmt GITHEAD_$hd - if test -n "$GIT_QUIET" - then - GIT_MERGE_VERBOSITY=1 && export GIT_MERGE_VERBOSITY - fi - eval 'git-merge-$strategy' $strategy_opts '"$cmt^" -- "$hd" "$cmt"' - rv=$? - case "$rv" in - 0) - unset GITHEAD_$cmt GITHEAD_$hd - return - ;; - 1) - git rerere $allow_rerere_autoupdate - die "$RESOLVEMSG" - ;; - 2) - echo "Strategy: $rv $strategy failed, try another" 1>&2 - die "$RESOLVEMSG" +output () { + case "$verbose" in + '') + output=$("$@" 2>&1 ) + status=$? + test $status != 0 && printf "%s\n" "$output" + return $status ;; *) - die "Unknown exit code ($rv) from command:" \ - "git-merge-$strategy $cmt^ -- HEAD $cmt" + "$@" ;; esac } move_to_original_branch () { - test -z "$head_name" && - head_name="$(cat "$dotest"/head-name)" && - onto="$(cat "$dotest"/onto)" && - orig_head="$(cat "$dotest"/orig-head)" case "$head_name" in refs/*) message="rebase finished: $head_name onto $onto" @@ -152,42 +159,16 @@ move_to_original_branch () { esac } -finish_rb_merge () { - move_to_original_branch - git notes copy --for-rewrite=rebase < "$dotest"/rewritten - if test -x "$GIT_DIR"/hooks/post-rewrite && - test -s "$dotest"/rewritten; then - "$GIT_DIR"/hooks/post-rewrite rebase < "$dotest"/rewritten - fi - rm -r "$dotest" - say All done. -} - -is_interactive () { - while test $# != 0 - do - case "$1" in - -i|--interactive) - interactive_rebase=explicit - break - ;; - -p|--preserve-merges) - interactive_rebase=implied - ;; - esac - shift - done - +run_specific_rebase () { if [ "$interactive_rebase" = implied ]; then GIT_EDITOR=: export GIT_EDITOR fi - - test -n "$interactive_rebase" || test -f "$dotest"/interactive + . git-rebase--$type } run_pre_rebase_hook () { - if test -z "$OK_TO_SKIP_PRE_REBASE" && + if test -z "$ok_to_skip_pre_rebase" && test -x "$GIT_DIR/hooks/pre-rebase" then "$GIT_DIR/hooks/pre-rebase" ${1+"$@"} || @@ -195,163 +176,94 @@ run_pre_rebase_hook () { fi } -test -f "$GIT_DIR"/rebase-apply/applying && +test -f "$apply_dir"/applying && die 'It looks like git-am is in progress. Cannot rebase.' -is_interactive "$@" && exec git-rebase--interactive "$@" +if test -d "$apply_dir" +then + type=am + state_dir="$apply_dir" +elif test -d "$merge_dir" +then + if test -f "$merge_dir"/interactive + then + type=interactive + interactive_rebase=explicit + else + type=merge + fi + state_dir="$merge_dir" +fi +test -n "$type" && in_progress=t +total_argc=$# while test $# != 0 do case "$1" in --no-verify) - OK_TO_SKIP_PRE_REBASE=yes + 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?" - - git update-index --ignore-submodules --refresh && - git diff-files --quiet --ignore-submodules || { - echo "You must edit all merge conflicts and then" - echo "mark them as resolved using git add" - exit 1 - } - if test -d "$dotest" - then - prev_head=$(cat "$dotest/prev_head") - end=$(cat "$dotest/end") - msgnum=$(cat "$dotest/msgnum") - onto=$(cat "$dotest/onto") - GIT_QUIET=$(cat "$dotest/quiet") - continue_merge - while test "$msgnum" -le "$end" - do - call_merge "$msgnum" - continue_merge - done - finish_rb_merge - exit - fi - head_name=$(cat "$GIT_DIR"/rebase-apply/head-name) && - onto=$(cat "$GIT_DIR"/rebase-apply/onto) && - orig_head=$(cat "$GIT_DIR"/rebase-apply/orig-head) && - GIT_QUIET=$(cat "$GIT_DIR"/rebase-apply/quiet) - git am --resolved --3way --resolvemsg="$RESOLVEMSG" && - move_to_original_branch - exit + ok_to_skip_pre_rebase= ;; - --skip) - test -d "$dotest" -o -d "$GIT_DIR"/rebase-apply || - die "No rebase in progress?" - - git reset --hard HEAD || exit $? - if test -d "$dotest" - then - git rerere clear - prev_head=$(cat "$dotest/prev_head") - end=$(cat "$dotest/end") - msgnum=$(cat "$dotest/msgnum") - msgnum=$(($msgnum + 1)) - onto=$(cat "$dotest/onto") - GIT_QUIET=$(cat "$dotest/quiet") - while test "$msgnum" -le "$end" - do - call_merge "$msgnum" - continue_merge - done - finish_rb_merge - exit - fi - head_name=$(cat "$GIT_DIR"/rebase-apply/head-name) && - onto=$(cat "$GIT_DIR"/rebase-apply/onto) && - orig_head=$(cat "$GIT_DIR"/rebase-apply/orig-head) && - GIT_QUIET=$(cat "$GIT_DIR"/rebase-apply/quiet) - git am -3 --skip --resolvemsg="$RESOLVEMSG" && - move_to_original_branch - exit - ;; - --abort) - test -d "$dotest" -o -d "$GIT_DIR"/rebase-apply || - die "No rebase in progress?" - - git rerere clear - - 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 + --continue|--skip|--abort) + test $total_argc -eq 2 || usage + action=${1##--} ;; --onto) test 2 -le "$#" || usage - newbase="$2" + onto="$2" shift ;; - -M|-m|--m|--me|--mer|--merg|--merge) + -i) + interactive_rebase=explicit + ;; + -p) + preserve_merges=t + test -z "$interactive_rebase" && interactive_rebase=implied + ;; + --autosquash) + autosquash=t + ;; + --no-autosquash) + autosquash= + ;; + -M|-m) do_merge=t ;; - -X*|--strategy-option*) - case "$#,$1" in - 1,-X|1,--strategy-option) - usage ;; - *,-X|*,--strategy-option) - newopt="$2" - shift ;; - *,--strategy-option=*) - newopt="$(expr " $1" : ' --strategy-option=\(.*\)')" ;; - *,-X*) - newopt="$(expr " $1" : ' -X\(.*\)')" ;; - 1,*) - usage ;; - esac - strategy_opts="$strategy_opts $(git rev-parse --sq-quote "--$newopt")" + -X) + shift + strategy_opts="$strategy_opts $(git rev-parse --sq-quote "--$1")" do_merge=t + test -z "$strategy" && strategy=recursive ;; - -s=*|--s=*|--st=*|--str=*|--stra=*|--strat=*|--strate=*|\ - --strateg=*|--strategy=*|\ - -s|--s|--st|--str|--stra|--strat|--strate|--strateg|--strategy) - case "$#,$1" in - *,*=*) - strategy=`expr "z$1" : 'z-[^=]*=\(.*\)'` ;; - 1,*) - usage ;; - *) - strategy="$2" - shift ;; - esac + -s) + shift + strategy="$1" do_merge=t ;; - -n|--no-stat) + -n) diffstat= ;; --stat) diffstat=t ;; - -v|--verbose) + -v) verbose=t diffstat=t GIT_QUIET= ;; - -q|--quiet) + -q) GIT_QUIET=t git_am_opt="$git_am_opt -q" verbose= diffstat= ;; - --whitespace=*) - git_am_opt="$git_am_opt $1" + --whitespace) + shift + git_am_opt="$git_am_opt --whitespace=$1" case "$1" in - --whitespace=fix|--whitespace=strip) + fix|strip) force_rebase=t ;; esac @@ -363,22 +275,21 @@ do git_am_opt="$git_am_opt $1" force_rebase=t ;; - -C*) - git_am_opt="$git_am_opt $1" + -C) + shift + git_am_opt="$git_am_opt -C$1" ;; --root) rebase_root=t ;; - -f|--f|--fo|--for|--forc|--force|--force-r|--force-re|--force-reb|--force-reba|--force-rebas|--force-rebase|--no-ff) + -f|--no-ff) force_rebase=t ;; --rerere-autoupdate|--no-rerere-autoupdate) allow_rerere_autoupdate="$1" ;; - -*) - usage - ;; - *) + --) + shift break ;; esac @@ -386,58 +297,106 @@ do done test $# -gt 2 && usage -if test $# -eq 0 && test -z "$rebase_root" +if test -n "$action" then - test -d "$dotest" -o -d "$GIT_DIR"/rebase-apply || usage - test -d "$dotest" -o -f "$GIT_DIR"/rebase-apply/rebasing && - die 'A rebase is in progress, try --continue, --skip or --abort.' + test -z "$in_progress" && die "No rebase in progress?" + # Only interactive rebase uses detailed reflog messages + if test "$type" = interactive && test "$GIT_REFLOG_ACTION" = rebase + then + GIT_REFLOG_ACTION="rebase -i ($action)" + export GIT_REFLOG_ACTION + fi fi -# Make sure we do not have $GIT_DIR/rebase-apply -if test -z "$do_merge" +case "$action" in +continue) + # Sanity check + git rev-parse --verify HEAD >/dev/null || + die "Cannot read HEAD" + git update-index --ignore-submodules --refresh && + git diff-files --quiet --ignore-submodules || { + echo "You must edit all merge conflicts and then" + echo "mark them as resolved using git add" + exit 1 + } + read_basic_state + run_specific_rebase + ;; +skip) + output git reset --hard HEAD || exit $? + read_basic_state + run_specific_rebase + ;; +abort) + git rerere clear + read_basic_state + case "$head_name" in + refs/*) + git symbolic-ref HEAD $head_name || + die "Could not move back to $head_name" + ;; + esac + output git reset --hard $orig_head + rm -r "$state_dir" + exit + ;; +esac + +# Make sure no rebase is in progress +if test -n "$in_progress" then - if mkdir "$GIT_DIR"/rebase-apply 2>/dev/null - then - rmdir "$GIT_DIR"/rebase-apply - else - echo >&2 ' -It seems that I cannot create a rebase-apply directory, and -I wonder if you are in the middle of patch application or another -rebase. If that is not the case, please - rm -fr '"$GIT_DIR"'/rebase-apply + die ' +It seems that there is already a '"${state_dir##*/}"' directory, and +I wonder if you are in the middle of another rebase. If that is the +case, please try + git rebase (--continue | --abort | --skip) +If that is not the case, please + rm -fr '"$state_dir"' and run me again. I am stopping in case you still have something valuable there.' - exit 1 - fi -else - if test -d "$dotest" - then - die "previous rebase directory $dotest still exists." \ - 'Try git rebase (--continue | --abort | --skip)' - fi fi -require_clean_work_tree "rebase" "Please commit or stash them." +if test -n "$interactive_rebase" +then + type=interactive + state_dir="$merge_dir" +elif test -n "$do_merge" +then + type=merge + state_dir="$merge_dir" +else + type=am + state_dir="$apply_dir" +fi if test -z "$rebase_root" then - # The upstream head must be given. Make sure it is valid. - upstream_name="$1" - shift + case "$#" in + 0) + if ! upstream_name=$(git rev-parse --symbolic-full-name \ + --verify -q @{upstream} 2>/dev/null) + then + . git-parse-remote + error_on_missing_default_upstream "rebase" "rebase" \ + "against" "git rebase <upstream branch>" + fi + ;; + *) upstream_name="$1" + shift + ;; + esac upstream=`git rev-parse --verify "${upstream_name}^0"` || die "invalid upstream $upstream_name" - unset root_flag upstream_arg="$upstream_name" else - test -z "$newbase" && die "--root must be used with --onto" + test -z "$onto" && die "You must specify --onto when using --root" unset upstream_name unset upstream - root_flag="--root" - upstream_arg="$root_flag" + upstream_arg=--root fi # Make sure the branch to rebase onto is valid. -onto_name=${newbase-"$upstream_name"} +onto_name=${onto-"$upstream_name"} case "$onto_name" in *...*) if left=${onto_name%...*} right=${onto_name#*...} && @@ -456,13 +415,11 @@ case "$onto_name" in fi ;; *) - onto=$(git rev-parse --verify "${onto_name}^0") || exit + onto=$(git rev-parse --verify "${onto_name}^0") || + die "Does not point to a valid commit: $1" ;; esac -# If a hook exists, give it a chance to interrupt -run_pre_rebase_hook "$upstream_arg" "$@" - # If the branch to rebase is given, that is the branch we will rebase # $branch_name -- branch being rebased, or HEAD (already detached) # $orig_head -- commit object name of tip of the branch before rebasing @@ -475,10 +432,10 @@ case "$#" in switch_to="$1" if git show-ref --verify --quiet -- "refs/heads/$1" && - branch=$(git rev-parse -q --verify "refs/heads/$1") + orig_head=$(git rev-parse -q --verify "refs/heads/$1") then head_name="refs/heads/$1" - elif branch=$(git rev-parse -q --verify "$1") + elif orig_head=$(git rev-parse -q --verify "$1") then head_name="detached HEAD" else @@ -496,20 +453,23 @@ case "$#" in head_name="detached HEAD" branch_name=HEAD ;# detached fi - branch=$(git rev-parse --verify "${branch_name}^0") || exit + orig_head=$(git rev-parse --verify "${branch_name}^0") || exit ;; esac -orig_head=$branch -# Now we are rebasing commits $upstream..$branch (or with --root, -# everything leading up to $branch) on top of $onto +require_clean_work_tree "rebase" "Please commit or stash them." + +# Now we are rebasing commits $upstream..$orig_head (or with --root, +# everything leading up to $orig_head) on top of $onto # Check if we are already based on $onto with linear history, -# but this should be done only when upstream and onto are the same. -mb=$(git merge-base "$onto" "$branch") -if test "$upstream" = "$onto" && test "$mb" = "$onto" && +# but this should be done only when upstream and onto are the same +# and if this is not an interactive rebase. +mb=$(git merge-base "$onto" "$orig_head") +if test "$type" != interactive && test "$upstream" = "$onto" && + test "$mb" = "$onto" && # linear history? - ! (git rev-list --parents "$onto".."$branch" | sane_grep " .* ") > /dev/null + ! (git rev-list --parents "$onto".."$orig_head" | sane_grep " .* ") > /dev/null then if test -z "$force_rebase" then @@ -522,10 +482,8 @@ then fi fi -# Detach HEAD and reset the tree -say "First, rewinding head to replay your work on top of it..." -git checkout -q "$onto^0" || die "could not detach HEAD" -git update-ref ORIG_HEAD $branch +# If a hook exists, give it a chance to interrupt +run_pre_rebase_hook "$upstream_arg" "$@" if test -n "$diffstat" then @@ -537,9 +495,16 @@ then GIT_PAGER='' git diff --stat --summary "$mb" "$onto" fi +test "$type" = interactive && run_specific_rebase + +# Detach HEAD and reset the tree +say "First, rewinding head to replay your work on top of it..." +git checkout -q "$onto^0" || die "could not detach HEAD" +git update-ref ORIG_HEAD $orig_head + # If the $onto is a proper descendant of the tip of the branch, then # we just fast-forwarded. -if test "$mb" = "$branch" +if test "$mb" = "$orig_head" then say "Fast-forwarded $branch_name to $onto_name." move_to_original_branch @@ -553,51 +518,4 @@ else revisions="$upstream..$orig_head" fi -if test -z "$do_merge" -then - git format-patch -k --stdout --full-index --ignore-if-in-upstream \ - --src-prefix=a/ --dst-prefix=b/ \ - --no-renames $root_flag "$revisions" | - git am $git_am_opt --rebasing --resolvemsg="$RESOLVEMSG" && - move_to_original_branch - ret=$? - test 0 != $ret -a -d "$GIT_DIR"/rebase-apply && - echo $head_name > "$GIT_DIR"/rebase-apply/head-name && - echo $onto > "$GIT_DIR"/rebase-apply/onto && - echo $orig_head > "$GIT_DIR"/rebase-apply/orig-head && - echo "$GIT_QUIET" > "$GIT_DIR"/rebase-apply/quiet - exit $ret -fi - -# start doing a rebase with git-merge -# this is rename-aware if the recursive (default) strategy is used - -mkdir -p "$dotest" -echo "$onto" > "$dotest/onto" -echo "$onto_name" > "$dotest/onto_name" -prev_head=$orig_head -echo "$prev_head" > "$dotest/prev_head" -echo "$orig_head" > "$dotest/orig-head" -echo "$head_name" > "$dotest/head-name" -echo "$GIT_QUIET" > "$dotest/quiet" - -msgnum=0 -for cmt in `git rev-list --reverse --no-merges "$revisions"` -do - msgnum=$(($msgnum + 1)) - echo "$cmt" > "$dotest/cmt.$msgnum" -done - -echo 1 >"$dotest/msgnum" -echo $msgnum >"$dotest/end" - -end=$msgnum -msgnum=1 - -while test "$msgnum" -le "$end" -do - call_merge "$msgnum" - continue_merge -done - -finish_rb_merge +run_specific_rebase diff --git a/git-submodule.sh b/git-submodule.sh index b010a67309..bf110e9cb7 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -8,7 +8,7 @@ dashless=$(basename "$0" | sed -e 's/-/ /') USAGE="[--quiet] add [-b branch] [-f|--force] [--reference <repository>] [--] <repository> [<path>] or: $dashless [--quiet] status [--cached] [--recursive] [--] [<path>...] or: $dashless [--quiet] init [--] [<path>...] - or: $dashless [--quiet] update [--init] [-N|--no-fetch] [--rebase] [--reference <repository>] [--merge] [--recursive] [--] [<path>...] + or: $dashless [--quiet] update [--init] [-N|--no-fetch] [-f|--force] [--rebase] [--reference <repository>] [--merge] [--recursive] [--] [<path>...] or: $dashless [--quiet] summary [--cached|--files] [--summary-limit <n>] [commit] [--] [<path>...] or: $dashless [--quiet] foreach [--recursive] <command> or: $dashless [--quiet] sync [--] [<path>...]" @@ -402,6 +402,9 @@ cmd_update() -N|--no-fetch) nofetch=1 ;; + -f|--force) + force=$1 + ;; -r|--rebase) update="rebase" ;; @@ -480,10 +483,11 @@ cmd_update() if test "$subsha1" != "$sha1" then - force= - if test -z "$subsha1" + subforce=$force + # If we don't already have a -f flag and the submodule has never been checked out + if test -z "$subsha1" -a -z "$force" then - force="-f" + subforce="-f" fi if test -z "$nofetch" @@ -515,7 +519,7 @@ cmd_update() msg="merged in" ;; *) - command="git checkout $force -q" + command="git checkout $subforce -q" action="checkout" msg="checked out" ;; diff --git a/git-svn.perl b/git-svn.perl index bf0451b468..0fd2fd2188 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -784,6 +784,15 @@ sub cmd_find_rev { print "$result\n" if $result; } +sub auto_create_empty_directories { + my ($gs) = @_; + my $var = eval { command_oneline('config', '--get', '--bool', + "svn-remote.$gs->{repo_id}.automkdirs") }; + # By default, create empty directories by consulting the unhandled log, + # but allow setting it to 'false' to skip it. + return !($var && $var eq 'false'); +} + sub cmd_rebase { command_noisy(qw/update-index --refresh/); my ($url, $rev, $uuid, $gs) = working_head_info('HEAD'); @@ -807,7 +816,9 @@ sub cmd_rebase { $_fetch_all ? $gs->fetch_all : $gs->fetch; } command_noisy(rebase_cmd(), $gs->refname); - $gs->mkemptydirs; + if (auto_create_empty_directories($gs)) { + $gs->mkemptydirs; + } } sub cmd_show_ignore { @@ -1245,7 +1256,9 @@ sub post_fetch_checkout { command_noisy(qw/read-tree -m -u -v HEAD HEAD/); print STDERR "Checked out HEAD:\n ", $gs->full_url, " r", $gs->last_rev, "\n"; - $gs->mkemptydirs($gs->last_rev); + if (auto_create_empty_directories($gs)) { + $gs->mkemptydirs($gs->last_rev); + } } sub complete_svn_url { @@ -6,7 +6,7 @@ #include "run-command.h" const char git_usage_string[] = - "git [--version] [--exec-path[=<path>]] [--html-path]\n" + "git [--version] [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]\n" " [-p|--paginate|--no-pager] [--no-replace-objects]\n" " [--bare] [--git-dir=<path>] [--work-tree=<path>]\n" " [-c name=value] [--help]\n" @@ -95,6 +95,12 @@ static int handle_options(const char ***argv, int *argc, int *envchanged) } else if (!strcmp(cmd, "--html-path")) { puts(system_path(GIT_HTML_PATH)); exit(0); + } else if (!strcmp(cmd, "--man-path")) { + puts(system_path(GIT_MAN_PATH)); + exit(0); + } else if (!strcmp(cmd, "--info-path")) { + puts(system_path(GIT_INFO_PATH)); + exit(0); } else if (!strcmp(cmd, "-p") || !strcmp(cmd, "--paginate")) { use_pager = 1; } else if (!strcmp(cmd, "--no-pager")) { @@ -179,6 +185,8 @@ static int handle_alias(int *argcp, const char ***argv) if (alias_string[0] == '!') { const char **alias_argv; int argc = *argcp, i; + struct strbuf sb = STRBUF_INIT; + const char *env[2]; commit_pager_choice(); @@ -189,7 +197,13 @@ static int handle_alias(int *argcp, const char ***argv) alias_argv[i] = (*argv)[i]; alias_argv[argc] = NULL; - ret = run_command_v_opt(alias_argv, RUN_USING_SHELL); + strbuf_addstr(&sb, "GIT_PREFIX="); + if (subdir) + strbuf_addstr(&sb, subdir); + env[0] = sb.buf; + env[1] = NULL; + ret = run_command_v_opt_cd_env(alias_argv, RUN_USING_SHELL, NULL, env); + strbuf_release(&sb); if (ret >= 0) /* normal exit */ exit(ret); diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index ee69ea683a..66eadb4ab2 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -186,7 +186,7 @@ our %known_snapshot_formats = ( 'type' => 'application/x-gzip', 'suffix' => '.tar.gz', 'format' => 'tar', - 'compressor' => ['gzip']}, + 'compressor' => ['gzip', '-n']}, 'tbz2' => { 'display' => 'tar.bz2', @@ -412,20 +412,23 @@ our %feature = ( 'override' => 0, 'default' => []}, - # Allow gitweb scan project content tags described in ctags/ - # of project repository, and display the popular Web 2.0-ish - # "tag cloud" near the project list. Note that this is something - # COMPLETELY different from the normal Git tags. + # Allow gitweb scan project content tags of project repository, + # and display the popular Web 2.0-ish "tag cloud" near the projects + # list. Note that this is something COMPLETELY different from the + # normal Git tags. # gitweb by itself can show existing tags, but it does not handle - # tagging itself; you need an external application for that. - # For an example script, check Girocco's cgi/tagproj.cgi. + # tagging itself; you need to do it externally, outside gitweb. + # The format is described in git_get_project_ctags() subroutine. # You may want to install the HTML::TagCloud Perl module to get # a pretty tag cloud instead of just a list of tags. # To enable system wide have in $GITWEB_CONFIG - # $feature{'ctags'}{'default'} = ['path_to_tag_script']; + # $feature{'ctags'}{'default'} = [1]; # Project specific override is not supported. + + # In the future whether ctags editing is enabled might depend + # on the value, but using 1 should always mean no editing of ctags. 'ctags' => { 'override' => 0, 'default' => [0]}, @@ -703,6 +706,7 @@ our @cgi_param_mapping = ( snapshot_format => "sf", extra_options => "opt", search_use_regexp => "sr", + ctag => "by_tag", # this must be last entry (for manipulation from JavaScript) javascript => "js" ); @@ -2572,23 +2576,66 @@ sub git_get_project_description { return $descr; } +# supported formats: +# * $GIT_DIR/ctags/<tagname> file (in 'ctags' subdirectory) +# - if its contents is a number, use it as tag weight, +# - otherwise add a tag with weight 1 +# * $GIT_DIR/ctags file, each line is a tag (with weight 1) +# the same value multiple times increases tag weight +# * `gitweb.ctag' multi-valued repo config variable sub git_get_project_ctags { - my $path = shift; + my $project = shift; my $ctags = {}; - $git_dir = "$projectroot/$path"; - opendir my $dh, "$git_dir/ctags" - or return $ctags; - foreach (grep { -f $_ } map { "$git_dir/ctags/$_" } readdir($dh)) { - open my $ct, '<', $_ or next; - my $val = <$ct>; - chomp $val; - close $ct; - my $ctag = $_; $ctag =~ s#.*/##; - $ctags->{$ctag} = $val; + $git_dir = "$projectroot/$project"; + if (opendir my $dh, "$git_dir/ctags") { + my @files = grep { -f $_ } map { "$git_dir/ctags/$_" } readdir($dh); + foreach my $tagfile (@files) { + open my $ct, '<', $tagfile + or next; + my $val = <$ct>; + chomp $val if $val; + close $ct; + + (my $ctag = $tagfile) =~ s#.*/##; + if ($val =~ /\d+/) { + $ctags->{$ctag} = $val; + } else { + $ctags->{$ctag} = 1; + } + } + closedir $dh; + + } elsif (open my $fh, '<', "$git_dir/ctags") { + while (my $line = <$fh>) { + chomp $line; + $ctags->{$line}++ if $line; + } + close $fh; + + } else { + my $taglist = config_to_multi(git_get_project_config('ctag')); + foreach my $tag (@$taglist) { + $ctags->{$tag}++; + } } - closedir $dh; - $ctags; + + return $ctags; +} + +# return hash, where keys are content tags ('ctags'), +# and values are sum of weights of given tag in every project +sub git_gather_all_ctags { + my $projects = shift; + my $ctags = {}; + + foreach my $p (@$projects) { + foreach my $ct (keys %{$p->{'ctags'}}) { + $ctags->{$ct} += $p->{'ctags'}->{$ct}; + } + } + + return $ctags; } sub git_populate_project_tagcloud { @@ -2606,33 +2653,49 @@ sub git_populate_project_tagcloud { } my $cloud; + my $matched = $cgi->param('by_tag'); if (eval { require HTML::TagCloud; 1; }) { $cloud = HTML::TagCloud->new; - foreach (sort keys %ctags_lc) { + foreach my $ctag (sort keys %ctags_lc) { # Pad the title with spaces so that the cloud looks # less crammed. - my $title = $ctags_lc{$_}->{topname}; + my $title = esc_html($ctags_lc{$ctag}->{topname}); $title =~ s/ / /g; $title =~ s/^/ /g; $title =~ s/$/ /g; - $cloud->add($title, $home_link."?by_tag=".$_, $ctags_lc{$_}->{count}); + if (defined $matched && $matched eq $ctag) { + $title = qq(<span class="match">$title</span>); + } + $cloud->add($title, href(project=>undef, ctag=>$ctag), + $ctags_lc{$ctag}->{count}); } } else { - $cloud = \%ctags_lc; + $cloud = {}; + foreach my $ctag (keys %ctags_lc) { + my $title = esc_html($ctags_lc{$ctag}->{topname}, -nbsp=>1); + if (defined $matched && $matched eq $ctag) { + $title = qq(<span class="match">$title</span>); + } + $cloud->{$ctag}{count} = $ctags_lc{$ctag}->{count}; + $cloud->{$ctag}{ctag} = + $cgi->a({-href=>href(project=>undef, ctag=>$ctag)}, $title); + } } - $cloud; + return $cloud; } sub git_show_project_tagcloud { my ($cloud, $count) = @_; - print STDERR ref($cloud)."..\n"; if (ref $cloud eq 'HTML::TagCloud') { return $cloud->html_and_css($count); } else { - my @tags = sort { $cloud->{$a}->{count} <=> $cloud->{$b}->{count} } keys %$cloud; - return '<p align="center">' . join (', ', map { - $cgi->a({-href=>"$home_link?by_tag=$_"}, $cloud->{$_}->{topname}) - } splice(@tags, 0, $count)) . '</p>'; + my @tags = sort { $cloud->{$a}->{'count'} <=> $cloud->{$b}->{'count'} } keys %$cloud; + return + '<div id="htmltagcloud"'.($project ? '' : ' align="center"').'>' . + join (', ', map { + $cloud->{$_}->{'ctag'} + } splice(@tags, 0, $count)) . + '</div>'; } } @@ -2651,21 +2714,23 @@ sub git_get_project_url_list { } sub git_get_projects_list { - my ($filter) = @_; + my $filter = shift || ''; my @list; - $filter ||= ''; $filter =~ s/\.git$//; - my $check_forks = gitweb_check_feature('forks'); - if (-d $projects_list) { # search in directory - my $dir = $projects_list . ($filter ? "/$filter" : ''); + my $dir = $projects_list; # remove the trailing "/" $dir =~ s!/+$!!; - my $pfxlen = length("$dir"); - my $pfxdepth = ($dir =~ tr!/!!); + my $pfxlen = length("$projects_list"); + my $pfxdepth = ($projects_list =~ tr!/!!); + # when filtering, search only given subdirectory + if ($filter) { + $dir .= "/$filter"; + $dir =~ s!/+$!!; + } File::Find::find({ follow_fast => 1, # follow symbolic links @@ -2680,14 +2745,14 @@ sub git_get_projects_list { # only directories can be git repositories return unless (-d $_); # don't traverse too deep (Find is super slow on os x) + # $project_maxdepth excludes depth of $projectroot if (($File::Find::name =~ tr!/!!) - $pfxdepth > $project_maxdepth) { $File::Find::prune = 1; return; } - my $subdir = substr($File::Find::name, $pfxlen + 1); + my $path = substr($File::Find::name, $pfxlen + 1); # we check related file in $projectroot - my $path = ($filter ? "$filter/" : '') . $subdir; if (check_export_ok("$projectroot/$path")) { push @list, { path => $path }; $File::Find::prune = 1; @@ -2700,7 +2765,6 @@ sub git_get_projects_list { # 'git%2Fgit.git Linus+Torvalds' # 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin' # 'linux%2Fhotplug%2Fudev.git Greg+Kroah-Hartman' - my %paths; open my $fd, '<', $projects_list or return; PROJECT: while (my $line = <$fd>) { @@ -2711,32 +2775,9 @@ sub git_get_projects_list { if (!defined $path) { next; } - if ($filter ne '') { - # looking for forks; - my $pfx = substr($path, 0, length($filter)); - if ($pfx ne $filter) { - next PROJECT; - } - my $sfx = substr($path, length($filter)); - if ($sfx !~ /^\/.*\.git$/) { - next PROJECT; - } - } elsif ($check_forks) { - PATH: - foreach my $filter (keys %paths) { - # looking for forks; - my $pfx = substr($path, 0, length($filter)); - if ($pfx ne $filter) { - next PATH; - } - my $sfx = substr($path, length($filter)); - if ($sfx !~ /^\/.*\.git$/) { - next PATH; - } - # is a fork, don't include it in - # the list - next PROJECT; - } + # if $filter is rpovided, check if $path begins with $filter + if ($filter && $path !~ m!^\Q$filter\E/!) { + next; } if (check_export_ok("$projectroot/$path")) { my $pr = { @@ -2744,8 +2785,6 @@ sub git_get_projects_list { owner => to_utf8($owner), }; push @list, $pr; - (my $forks_path = $path) =~ s/\.git$//; - $paths{$forks_path}++; } } close $fd; @@ -2753,6 +2792,98 @@ sub git_get_projects_list { return @list; } +# written with help of Tree::Trie module (Perl Artistic License, GPL compatibile) +# as side effects it sets 'forks' field to list of forks for forked projects +sub filter_forks_from_projects_list { + my $projects = shift; + + my %trie; # prefix tree of directories (path components) + # generate trie out of those directories that might contain forks + foreach my $pr (@$projects) { + my $path = $pr->{'path'}; + $path =~ s/\.git$//; # forks of 'repo.git' are in 'repo/' directory + next if ($path =~ m!/$!); # skip non-bare repositories, e.g. 'repo/.git' + next unless ($path); # skip '.git' repository: tests, git-instaweb + next unless (-d $path); # containing directory exists + $pr->{'forks'} = []; # there can be 0 or more forks of project + + # add to trie + my @dirs = split('/', $path); + # walk the trie, until either runs out of components or out of trie + my $ref = \%trie; + while (scalar @dirs && + exists($ref->{$dirs[0]})) { + $ref = $ref->{shift @dirs}; + } + # create rest of trie structure from rest of components + foreach my $dir (@dirs) { + $ref = $ref->{$dir} = {}; + } + # create end marker, store $pr as a data + $ref->{''} = $pr if (!exists $ref->{''}); + } + + # filter out forks, by finding shortest prefix match for paths + my @filtered; + PROJECT: + foreach my $pr (@$projects) { + # trie lookup + my $ref = \%trie; + DIR: + foreach my $dir (split('/', $pr->{'path'})) { + if (exists $ref->{''}) { + # found [shortest] prefix, is a fork - skip it + push @{$ref->{''}{'forks'}}, $pr; + next PROJECT; + } + if (!exists $ref->{$dir}) { + # not in trie, cannot have prefix, not a fork + push @filtered, $pr; + next PROJECT; + } + # If the dir is there, we just walk one step down the trie. + $ref = $ref->{$dir}; + } + # we ran out of trie + # (shouldn't happen: it's either no match, or end marker) + push @filtered, $pr; + } + + return @filtered; +} + +# note: fill_project_list_info must be run first, +# for 'descr_long' and 'ctags' to be filled +sub search_projects_list { + my ($projlist, %opts) = @_; + my $tagfilter = $opts{'tagfilter'}; + my $searchtext = $opts{'searchtext'}; + + return @$projlist + unless ($tagfilter || $searchtext); + + my @projects; + PROJECT: + foreach my $pr (@$projlist) { + + if ($tagfilter) { + next unless ref($pr->{'ctags'}) eq 'HASH'; + next unless + grep { lc($_) eq lc($tagfilter) } keys %{$pr->{'ctags'}}; + } + + if ($searchtext) { + next unless + $pr->{'path'} =~ /$searchtext/ || + $pr->{'descr_long'} =~ /$searchtext/; + } + + push @projects, $pr; + } + + return @projects; +} + our $gitweb_project_owner = undef; sub git_get_project_list_from_file { @@ -4742,7 +4873,7 @@ sub git_patchset_body { # project in the list, removing invalid projects from returned list # NOTE: modifies $projlist, but does not remove entries from it sub fill_project_list_info { - my ($projlist, $check_forks) = @_; + my $projlist = shift; my @projects; my $show_ctags = gitweb_check_feature('ctags'); @@ -4762,23 +4893,36 @@ sub fill_project_list_info { if (!defined $pr->{'owner'}) { $pr->{'owner'} = git_get_project_owner("$pr->{'path'}") || ""; } - if ($check_forks) { - my $pname = $pr->{'path'}; - if (($pname =~ s/\.git$//) && - ($pname !~ /\/$/) && - (-d "$projectroot/$pname")) { - $pr->{'forks'} = "-d $projectroot/$pname"; - } else { - $pr->{'forks'} = 0; - } + if ($show_ctags) { + $pr->{'ctags'} = git_get_project_ctags($pr->{'path'}); } - $show_ctags and $pr->{'ctags'} = git_get_project_ctags($pr->{'path'}); push @projects, $pr; } return @projects; } +sub sort_projects_list { + my ($projlist, $order) = @_; + my @projects; + + my %order_info = ( + project => { key => 'path', type => 'str' }, + descr => { key => 'descr_long', type => 'str' }, + owner => { key => 'owner', type => 'str' }, + age => { key => 'age', type => 'num' } + ); + my $oi = $order_info{$order}; + return @$projlist unless defined $oi; + if ($oi->{'type'} eq 'str') { + @projects = sort {$a->{$oi->{'key'}} cmp $b->{$oi->{'key'}}} @$projlist; + } else { + @projects = sort {$a->{$oi->{'key'}} <=> $b->{$oi->{'key'}}} @$projlist; + } + + return @projects; +} + # print 'sort by' <th> element, generating 'sort by $name' replay link # if that order is not selected sub print_sort_th { @@ -4805,36 +4949,42 @@ sub format_sort_th { sub git_project_list_body { # actually uses global variable $project my ($projlist, $order, $from, $to, $extra, $no_header) = @_; + my @projects = @$projlist; my $check_forks = gitweb_check_feature('forks'); - my @projects = fill_project_list_info($projlist, $check_forks); + my $show_ctags = gitweb_check_feature('ctags'); + my $tagfilter = $show_ctags ? $cgi->param('by_tag') : undef; + $check_forks = undef + if ($tagfilter || $searchtext); + + # filtering out forks before filling info allows to do less work + @projects = filter_forks_from_projects_list(\@projects) + if ($check_forks); + @projects = fill_project_list_info(\@projects); + # searching projects require filling to be run before it + @projects = search_projects_list(\@projects, + 'searchtext' => $searchtext, + 'tagfilter' => $tagfilter) + if ($tagfilter || $searchtext); $order ||= $default_projects_order; $from = 0 unless defined $from; $to = $#projects if (!defined $to || $#projects < $to); - my %order_info = ( - project => { key => 'path', type => 'str' }, - descr => { key => 'descr_long', type => 'str' }, - owner => { key => 'owner', type => 'str' }, - age => { key => 'age', type => 'num' } - ); - my $oi = $order_info{$order}; - if ($oi->{'type'} eq 'str') { - @projects = sort {$a->{$oi->{'key'}} cmp $b->{$oi->{'key'}}} @projects; - } else { - @projects = sort {$a->{$oi->{'key'}} <=> $b->{$oi->{'key'}}} @projects; + # short circuit + if ($from > $to) { + print "<center>\n". + "<b>No such projects found</b><br />\n". + "Click ".$cgi->a({-href=>href(project=>undef)},"here")." to view all projects<br />\n". + "</center>\n<br />\n"; + return; } - my $show_ctags = gitweb_check_feature('ctags'); + @projects = sort_projects_list(\@projects, $order); + if ($show_ctags) { - my %ctags; - foreach my $p (@projects) { - foreach my $ct (keys %{$p->{'ctags'}}) { - $ctags{$ct} += $p->{'ctags'}->{$ct}; - } - } - my $cloud = git_populate_project_tagcloud(\%ctags); + my $ctags = git_gather_all_ctags(\@projects); + my $cloud = git_populate_project_tagcloud($ctags); print git_show_project_tagcloud($cloud, 64); } @@ -4852,32 +5002,26 @@ sub git_project_list_body { "</tr>\n"; } my $alternate = 1; - my $tagfilter = $cgi->param('by_tag'); for (my $i = $from; $i <= $to; $i++) { my $pr = $projects[$i]; - next if $tagfilter and $show_ctags and not grep { lc $_ eq lc $tagfilter } keys %{$pr->{'ctags'}}; - next if $searchtext and not $pr->{'path'} =~ /$searchtext/ - and not $pr->{'descr_long'} =~ /$searchtext/; - # Weed out forks or non-matching entries of search - if ($check_forks) { - my $forkbase = $project; $forkbase ||= ''; $forkbase =~ s#\.git$#/#; - $forkbase="^$forkbase" if $forkbase; - next if not $searchtext and not $tagfilter and $show_ctags - and $pr->{'path'} =~ m#$forkbase.*/.*#; # regexp-safe - } - if ($alternate) { print "<tr class=\"dark\">\n"; } else { print "<tr class=\"light\">\n"; } $alternate ^= 1; + if ($check_forks) { print "<td>"; if ($pr->{'forks'}) { - print "<!-- $pr->{'forks'} -->\n"; - print $cgi->a({-href => href(project=>$pr->{'path'}, action=>"forks")}, "+"); + my $nforks = scalar @{$pr->{'forks'}}; + if ($nforks > 0) { + print $cgi->a({-href => href(project=>$pr->{'path'}, action=>"forks"), + -title => "$nforks forks"}, "+"); + } else { + print $cgi->span({-title => "$nforks forks"}, "+"); + } } print "</td>\n"; } @@ -5357,7 +5501,10 @@ sub git_forks { } sub git_project_index { - my @projects = git_get_projects_list($project); + my @projects = git_get_projects_list(); + if (!@projects) { + die_error(404, "No projects found"); + } print $cgi->header( -type => 'text/plain', @@ -5399,7 +5546,11 @@ sub git_summary { my $check_forks = gitweb_check_feature('forks'); if ($check_forks) { + # find forks of a project @forklist = git_get_projects_list($project); + # filter out forks of forks + @forklist = filter_forks_from_projects_list(\@forklist) + if (@forklist); } git_header_html(); @@ -5428,13 +5579,14 @@ sub git_summary { my $show_ctags = gitweb_check_feature('ctags'); if ($show_ctags) { my $ctags = git_get_project_ctags($project); - my $cloud = git_populate_project_tagcloud($ctags); - print "<tr id=\"metadata_ctags\"><td>Content tags:<br />"; - print "</td>\n<td>" unless %$ctags; - print "<form action=\"$show_ctags\" method=\"post\"><input type=\"hidden\" name=\"p\" value=\"$project\" />Add: <input type=\"text\" name=\"t\" size=\"8\" /></form>"; - print "</td>\n<td>" if %$ctags; - print git_show_project_tagcloud($cloud, 48); - print "</td></tr>"; + if (%$ctags) { + # without ability to add tags, don't show if there are none + my $cloud = git_populate_project_tagcloud($ctags); + print "<tr id=\"metadata_ctags\">" . + "<td>content tags</td>" . + "<td>".git_show_project_tagcloud($cloud, 48)."</td>" . + "</tr>\n"; + } } print "</table>\n"; @@ -7319,6 +7471,9 @@ sub git_atom { sub git_opml { my @list = git_get_projects_list(); + if (!@list) { + die_error(404, "No projects found"); + } print $cgi->header( -type => 'text/xml', @@ -59,27 +59,6 @@ enum graph_state { GRAPH_COLLAPSING }; -/* - * The list of available column colors. - */ -static const char *column_colors_ansi[] = { - GIT_COLOR_RED, - GIT_COLOR_GREEN, - GIT_COLOR_YELLOW, - GIT_COLOR_BLUE, - GIT_COLOR_MAGENTA, - GIT_COLOR_CYAN, - GIT_COLOR_BOLD_RED, - GIT_COLOR_BOLD_GREEN, - GIT_COLOR_BOLD_YELLOW, - GIT_COLOR_BOLD_BLUE, - GIT_COLOR_BOLD_MAGENTA, - GIT_COLOR_BOLD_CYAN, - GIT_COLOR_RESET, -}; - -#define COLUMN_COLORS_ANSI_MAX (ARRAY_SIZE(column_colors_ansi) - 1) - static const char **column_colors; static unsigned short column_colors_max; @@ -228,7 +207,7 @@ struct git_graph *graph_init(struct rev_info *opt) if (!column_colors) graph_set_column_colors(column_colors_ansi, - COLUMN_COLORS_ANSI_MAX); + column_colors_ansi_max); graph->commit = NULL; graph->revs = opt; diff --git a/http-push.c b/http-push.c index d18346c0f5..28bfe768f7 100644 --- a/http-push.c +++ b/http-push.c @@ -169,7 +169,7 @@ enum dav_header_flag { DAV_HEADER_TIMEOUT = (1u << 2) }; -static char *xml_entities(char *s) +static char *xml_entities(const char *s) { struct strbuf buf = STRBUF_INIT; while (*s) { @@ -197,6 +197,34 @@ static char *xml_entities(char *s) return strbuf_detach(&buf, NULL); } +static void curl_setup_http_get(CURL *curl, const char *url, + const char *custom_req) +{ + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1); + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, custom_req); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite_null); +} + +static void curl_setup_http(CURL *curl, const char *url, + const char *custom_req, struct buffer *buffer, + curl_write_callback write_fn) +{ + curl_easy_setopt(curl, CURLOPT_PUT, 1); + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_INFILE, buffer); + curl_easy_setopt(curl, CURLOPT_INFILESIZE, buffer->buf.len); + curl_easy_setopt(curl, CURLOPT_READFUNCTION, fread_buffer); +#ifndef NO_CURL_IOCTL + curl_easy_setopt(curl, CURLOPT_IOCTLFUNCTION, ioctl_buffer); + curl_easy_setopt(curl, CURLOPT_IOCTLDATA, &buffer); +#endif + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_fn); + curl_easy_setopt(curl, CURLOPT_NOBODY, 0); + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, custom_req); + curl_easy_setopt(curl, CURLOPT_UPLOAD, 1); +} + static struct curl_slist *get_dav_token_headers(struct remote_lock *lock, enum dav_header_flag options) { struct strbuf buf = STRBUF_INIT; @@ -272,11 +300,8 @@ static void start_mkcol(struct transfer_request *request) slot = get_active_slot(); slot->callback_func = process_response; slot->callback_data = request; - curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1); /* undo PUT setup */ - curl_easy_setopt(slot->curl, CURLOPT_URL, request->url); + curl_setup_http_get(slot->curl, request->url, DAV_MKCOL); curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, request->errorstr); - curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_MKCOL); - curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null); if (start_active_slot(slot)) { request->slot = slot; @@ -395,19 +420,8 @@ static void start_put(struct transfer_request *request) slot = get_active_slot(); slot->callback_func = process_response; slot->callback_data = request; - curl_easy_setopt(slot->curl, CURLOPT_INFILE, &request->buffer); - curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, request->buffer.buf.len); - curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer); -#ifndef NO_CURL_IOCTL - curl_easy_setopt(slot->curl, CURLOPT_IOCTLFUNCTION, ioctl_buffer); - curl_easy_setopt(slot->curl, CURLOPT_IOCTLDATA, &request->buffer); -#endif - curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null); - curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 0); - curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_PUT); - curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 1); - curl_easy_setopt(slot->curl, CURLOPT_PUT, 1); - curl_easy_setopt(slot->curl, CURLOPT_URL, request->url); + curl_setup_http(slot->curl, request->url, DAV_PUT, + &request->buffer, fwrite_null); if (start_active_slot(slot)) { request->slot = slot; @@ -427,13 +441,10 @@ static void start_move(struct transfer_request *request) slot = get_active_slot(); slot->callback_func = process_response; slot->callback_data = request; - curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1); /* undo PUT setup */ - curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_MOVE); + curl_setup_http_get(slot->curl, request->url, DAV_MOVE); dav_headers = curl_slist_append(dav_headers, request->dest); dav_headers = curl_slist_append(dav_headers, "Overwrite: T"); curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers); - curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null); - curl_easy_setopt(slot->curl, CURLOPT_URL, request->url); if (start_active_slot(slot)) { request->slot = slot; @@ -458,10 +469,7 @@ static int refresh_lock(struct remote_lock *lock) slot = get_active_slot(); slot->results = &results; - curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1); - curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null); - curl_easy_setopt(slot->curl, CURLOPT_URL, lock->url); - curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_LOCK); + curl_setup_http_get(slot->curl, lock->url, DAV_LOCK); curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers); if (start_active_slot(slot)) { @@ -797,7 +805,7 @@ static void handle_new_lock_ctx(struct xml_ctx *ctx, int tag_closed) } } -static void one_remote_ref(char *refname); +static void one_remote_ref(const char *refname); static void xml_start_tag(void *userData, const char *name, const char **atts) @@ -876,10 +884,7 @@ static struct remote_lock *lock_remote(const char *path, long timeout) ep[1] = '\0'; slot = get_active_slot(); slot->results = &results; - curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1); - curl_easy_setopt(slot->curl, CURLOPT_URL, url); - curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_MKCOL); - curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null); + curl_setup_http_get(slot->curl, url, DAV_MKCOL); if (start_active_slot(slot)) { run_active_slot(slot); if (results.curl_result != CURLE_OK && @@ -909,19 +914,9 @@ static struct remote_lock *lock_remote(const char *path, long timeout) slot = get_active_slot(); slot->results = &results; - curl_easy_setopt(slot->curl, CURLOPT_INFILE, &out_buffer); - curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.buf.len); - curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer); -#ifndef NO_CURL_IOCTL - curl_easy_setopt(slot->curl, CURLOPT_IOCTLFUNCTION, ioctl_buffer); - curl_easy_setopt(slot->curl, CURLOPT_IOCTLDATA, &out_buffer); -#endif - curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer); - curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer); - curl_easy_setopt(slot->curl, CURLOPT_URL, url); - curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 1); - curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_LOCK); + curl_setup_http(slot->curl, url, DAV_LOCK, &out_buffer, fwrite_buffer); curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers); + curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer); lock = xcalloc(1, sizeof(*lock)); lock->timeout = -1; @@ -987,9 +982,7 @@ static int unlock_remote(struct remote_lock *lock) slot = get_active_slot(); slot->results = &results; - curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null); - curl_easy_setopt(slot->curl, CURLOPT_URL, lock->url); - curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_UNLOCK); + curl_setup_http_get(slot->curl, lock->url, DAV_UNLOCK); curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers); if (start_active_slot(slot)) { @@ -1167,19 +1160,10 @@ static void remote_ls(const char *path, int flags, slot = get_active_slot(); slot->results = &results; - curl_easy_setopt(slot->curl, CURLOPT_INFILE, &out_buffer); - curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.buf.len); - curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer); -#ifndef NO_CURL_IOCTL - curl_easy_setopt(slot->curl, CURLOPT_IOCTLFUNCTION, ioctl_buffer); - curl_easy_setopt(slot->curl, CURLOPT_IOCTLDATA, &out_buffer); -#endif - curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer); - curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer); - curl_easy_setopt(slot->curl, CURLOPT_URL, url); - curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 1); - curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_PROPFIND); + curl_setup_http(slot->curl, url, DAV_PROPFIND, + &out_buffer, fwrite_buffer); curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers); + curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer); if (start_active_slot(slot)) { run_active_slot(slot); @@ -1250,19 +1234,10 @@ static int locking_available(void) slot = get_active_slot(); slot->results = &results; - curl_easy_setopt(slot->curl, CURLOPT_INFILE, &out_buffer); - curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.buf.len); - curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer); -#ifndef NO_CURL_IOCTL - curl_easy_setopt(slot->curl, CURLOPT_IOCTLFUNCTION, ioctl_buffer); - curl_easy_setopt(slot->curl, CURLOPT_IOCTLDATA, &out_buffer); -#endif - curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer); - curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer); - curl_easy_setopt(slot->curl, CURLOPT_URL, repo->url); - curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 1); - curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_PROPFIND); + curl_setup_http(slot->curl, repo->url, DAV_PROPFIND, + &out_buffer, fwrite_buffer); curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers); + curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer); if (start_active_slot(slot)) { run_active_slot(slot); @@ -1436,19 +1411,9 @@ static int update_remote(unsigned char *sha1, struct remote_lock *lock) slot = get_active_slot(); slot->results = &results; - curl_easy_setopt(slot->curl, CURLOPT_INFILE, &out_buffer); - curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.buf.len); - curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer); -#ifndef NO_CURL_IOCTL - curl_easy_setopt(slot->curl, CURLOPT_IOCTLFUNCTION, ioctl_buffer); - curl_easy_setopt(slot->curl, CURLOPT_IOCTLDATA, &out_buffer); -#endif - curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null); - curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_PUT); + curl_setup_http(slot->curl, lock->url, DAV_PUT, + &out_buffer, fwrite_null); curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers); - curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 1); - curl_easy_setopt(slot->curl, CURLOPT_PUT, 1); - curl_easy_setopt(slot->curl, CURLOPT_URL, lock->url); if (start_active_slot(slot)) { run_active_slot(slot); @@ -1471,7 +1436,7 @@ static int update_remote(unsigned char *sha1, struct remote_lock *lock) static struct ref *remote_refs; -static void one_remote_ref(char *refname) +static void one_remote_ref(const char *refname) { struct ref *ref; struct object *obj; @@ -1572,19 +1537,9 @@ static void update_remote_info_refs(struct remote_lock *lock) slot = get_active_slot(); slot->results = &results; - curl_easy_setopt(slot->curl, CURLOPT_INFILE, &buffer); - curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, buffer.buf.len); - curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer); -#ifndef NO_CURL_IOCTL - curl_easy_setopt(slot->curl, CURLOPT_IOCTLFUNCTION, ioctl_buffer); - curl_easy_setopt(slot->curl, CURLOPT_IOCTLDATA, &buffer); -#endif - curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null); - curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_PUT); + curl_setup_http(slot->curl, lock->url, DAV_PUT, + &buffer, fwrite_null); curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers); - curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 1); - curl_easy_setopt(slot->curl, CURLOPT_PUT, 1); - curl_easy_setopt(slot->curl, CURLOPT_URL, lock->url); if (start_active_slot(slot)) { run_active_slot(slot); @@ -1660,7 +1615,7 @@ static int verify_merge_base(unsigned char *head_sha1, unsigned char *branch_sha return (merge_bases && !merge_bases->next && merge_bases->item == branch); } -static int delete_remote_branch(char *pattern, int force) +static int delete_remote_branch(const char *pattern, int force) { struct ref *refs = remote_refs; struct ref *remote_ref = NULL; @@ -1742,10 +1697,7 @@ static int delete_remote_branch(char *pattern, int force) sprintf(url, "%s%s", repo->url, remote_ref->name); slot = get_active_slot(); slot->results = &results; - curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1); - curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null); - curl_easy_setopt(slot->curl, CURLOPT_URL, url); - curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_DELETE); + curl_setup_http_get(slot->curl, url, DAV_DELETE); if (start_active_slot(slot)) { run_active_slot(slot); free(url); diff --git a/http-walker.c b/http-walker.c index 9bc8114c3b..51a906e9e3 100644 --- a/http-walker.c +++ b/http-walker.c @@ -185,7 +185,7 @@ static void process_alternates_response(void *callback_data) struct active_request_slot *slot = alt_req->slot; struct alt_base *tail = cdata->alt; const char *base = alt_req->base; - static const char null_byte = '\0'; + const char null_byte = '\0'; char *data; int i = 0; @@ -218,7 +218,7 @@ static void process_alternates_response(void *callback_data) } } - fwrite_buffer(&null_byte, 1, 1, alt_req->buffer); + fwrite_buffer((char *)&null_byte, 1, 1, alt_req->buffer); alt_req->buffer->len--; data = alt_req->buffer->buf; @@ -60,7 +60,7 @@ static struct curl_slist *no_pragma_header; static struct active_request_slot *active_queue_head; -size_t fread_buffer(void *ptr, size_t eltsize, size_t nmemb, void *buffer_) +size_t fread_buffer(char *ptr, size_t eltsize, size_t nmemb, void *buffer_) { size_t size = eltsize * nmemb; struct buffer *buffer = buffer_; @@ -92,7 +92,7 @@ curlioerr ioctl_buffer(CURL *handle, int cmd, void *clientp) } #endif -size_t fwrite_buffer(const void *ptr, size_t eltsize, size_t nmemb, void *buffer_) +size_t fwrite_buffer(char *ptr, size_t eltsize, size_t nmemb, void *buffer_) { size_t size = eltsize * nmemb; struct strbuf *buffer = buffer_; @@ -102,7 +102,7 @@ size_t fwrite_buffer(const void *ptr, size_t eltsize, size_t nmemb, void *buffer return size; } -size_t fwrite_null(const void *ptr, size_t eltsize, size_t nmemb, void *strbuf) +size_t fwrite_null(char *ptr, size_t eltsize, size_t nmemb, void *strbuf) { data_received++; return eltsize * nmemb; @@ -1167,7 +1167,7 @@ abort: } /* Helpers for fetching objects (loose) */ -static size_t fwrite_sha1_file(void *ptr, size_t eltsize, size_t nmemb, +static size_t fwrite_sha1_file(char *ptr, size_t eltsize, size_t nmemb, void *data) { unsigned char expn[4096]; @@ -1184,7 +1184,7 @@ static size_t fwrite_sha1_file(void *ptr, size_t eltsize, size_t nmemb, } while (posn < size); freq->stream.avail_in = size; - freq->stream.next_in = ptr; + freq->stream.next_in = (void *)ptr; do { freq->stream.next_out = expn; freq->stream.avail_out = sizeof(expn); @@ -1203,7 +1203,7 @@ struct http_object_request *new_http_object_request(const char *base_url, char *filename; char prevfile[PATH_MAX]; int prevlocal; - unsigned char prev_buf[PREV_BUF_SIZE]; + char prev_buf[PREV_BUF_SIZE]; ssize_t prev_read = 0; long prev_posn = 0; char range[RANGE_HEADER_SIZE]; @@ -66,9 +66,9 @@ struct buffer { }; /* Curl request read/write callbacks */ -extern size_t fread_buffer(void *ptr, size_t eltsize, size_t nmemb, void *strbuf); -extern size_t fwrite_buffer(const void *ptr, size_t eltsize, size_t nmemb, void *strbuf); -extern size_t fwrite_null(const void *ptr, size_t eltsize, size_t nmemb, void *strbuf); +extern size_t fread_buffer(char *ptr, size_t eltsize, size_t nmemb, void *strbuf); +extern size_t fwrite_buffer(char *ptr, size_t eltsize, size_t nmemb, void *strbuf); +extern size_t fwrite_null(char *ptr, size_t eltsize, size_t nmemb, void *strbuf); #ifndef NO_CURL_IOCTL extern curlioerr ioctl_buffer(CURL *handle, int cmd, void *clientp); #endif diff --git a/imap-send.c b/imap-send.c index 9adf4b9819..e1ad1a48ce 100644 --- a/imap-send.c +++ b/imap-send.c @@ -1193,13 +1193,13 @@ static struct store *imap_open_store(struct imap_server_conf *srvc) if (!preauth) { #ifndef NO_OPENSSL if (!srvc->use_ssl && CAP(STARTTLS)) { - if (imap_exec(ctx, 0, "STARTTLS") != RESP_OK) + if (imap_exec(ctx, NULL, "STARTTLS") != RESP_OK) goto bail; if (ssl_socket_connect(&imap->buf.sock, 1, srvc->ssl_verify)) goto bail; /* capabilities may have changed, so get the new capabilities */ - if (imap_exec(ctx, 0, "CAPABILITY") != RESP_OK) + if (imap_exec(ctx, NULL, "CAPABILITY") != RESP_OK) goto bail; } #endif diff --git a/list-objects.c b/list-objects.c index 838b6a732e..0fb44e7ed7 100644 --- a/list-objects.c +++ b/list-objects.c @@ -68,7 +68,7 @@ static void process_tree(struct rev_info *revs, struct tree_desc desc; struct name_entry entry; struct name_path me; - int all_interesting = (revs->diffopt.pathspec.nr == 0); + int match = revs->diffopt.pathspec.nr == 0 ? 2 : 0; int baselen = base->len; if (!revs->tree_objects) @@ -85,7 +85,7 @@ static void process_tree(struct rev_info *revs, me.elem = name; me.elem_len = strlen(name); - if (!all_interesting) { + if (!match) { strbuf_addstr(base, name); if (base->len) strbuf_addch(base, '/'); @@ -94,17 +94,13 @@ static void process_tree(struct rev_info *revs, init_tree_desc(&desc, tree->buffer, tree->size); while (tree_entry(&desc, &entry)) { - if (!all_interesting) { - int showit = tree_entry_interesting(&entry, - base, 0, - &revs->diffopt.pathspec); - - if (showit < 0) + if (match != 2) { + match = tree_entry_interesting(&entry, base, 0, + &revs->diffopt.pathspec); + if (match < 0) break; - else if (!showit) + if (match == 0) continue; - else if (showit == 2) - all_interesting = 1; } if (S_ISDIR(entry.mode)) diff --git a/merge-file.c b/merge-file.c index f7f4533926..7845528e88 100644 --- a/merge-file.c +++ b/merge-file.c @@ -3,6 +3,7 @@ #include "xdiff-interface.h" #include "ll-merge.h" #include "blob.h" +#include "merge-file.h" static int fill_mmfile_blob(mmfile_t *f, struct blob *obj) { diff --git a/merge-file.h b/merge-file.h new file mode 100644 index 0000000000..9b3b83ac6c --- /dev/null +++ b/merge-file.h @@ -0,0 +1,7 @@ +#ifndef MERGE_FILE_H +#define MERGE_FILE_H + +extern void *merge_file(const char *path, struct blob *base, struct blob *our, + struct blob *their, unsigned long *size); + +#endif diff --git a/merge-recursive.c b/merge-recursive.c index dba27643bd..07ad1a3be4 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -22,11 +22,6 @@ #include "dir.h" #include "submodule.h" -static const char rename_limit_advice[] = -"inexact rename detection was skipped because there were too many\n" -" files. You may want to set your merge.renamelimit variable to at least\n" -" %d and retry this merge."; - static struct tree *shift_tree_object(struct tree *one, struct tree *two, const char *subtree_shift) { @@ -278,7 +273,9 @@ static int save_files_dirs(const unsigned char *sha1, static int get_files_dirs(struct merge_options *o, struct tree *tree) { int n; - if (read_tree_recursive(tree, "", 0, 0, NULL, save_files_dirs, o)) + struct pathspec match_all; + init_pathspec(&match_all, NULL); + if (read_tree_recursive(tree, "", 0, 0, &match_all, save_files_dirs, o)) return 0; n = o->current_file_set.nr + o->current_directory_set.nr; return n; @@ -344,10 +341,11 @@ static void make_room_for_directories_of_df_conflicts(struct merge_options *o, * 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. + * merge, then the file will be reinstated at that time + * (albeit with a different timestamp!); 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 @@ -358,6 +356,13 @@ static void make_room_for_directories_of_df_conflicts(struct merge_options *o, int last_len = 0; int i; + /* + * Do not do any of this crazyness during the recursive; we don't + * even write anything to the working tree! + */ + if (o->call_depth) + return; + for (i = 0; i < entries->nr; i++) { const char *path = entries->items[i].string; int len = strlen(path); @@ -1260,9 +1265,13 @@ static int merge_content(struct merge_options *o, } if (mfi.clean && !df_conflict_remains && - sha_eq(mfi.sha, a_sha) && mfi.mode == a.mode) + sha_eq(mfi.sha, a_sha) && mfi.mode == a.mode && + !o->call_depth && !lstat(path, &st)) { output(o, 3, "Skipped %s (merged same as existing)", path); - else + add_cacheinfo(mfi.mode, mfi.sha, path, + 0 /*stage*/, 1 /*refresh*/, 0 /*options*/); + return mfi.clean; + } else output(o, 2, "Auto-merging %s", path); if (!mfi.clean) { @@ -1652,8 +1661,9 @@ int merge_recursive(struct merge_options *o, commit_list_insert(h2, &(*result)->parents->next); } flush_output(o); - if (o->needed_rename_limit) - warning(rename_limit_advice, o->needed_rename_limit); + if (show(o, 2)) + diff_warn_rename_limit("merge.renamelimit", + o->needed_rename_limit, 0); return clean; } @@ -1053,7 +1053,8 @@ void init_display_notes(struct display_notes_opt *opt) assert(!display_notes_trees); - if (!opt || !opt->suppress_default_notes) { + if (!opt || opt->use_default_notes > 0 || + (opt->use_default_notes == -1 && !opt->extra_notes_refs.nr)) { string_list_append(&display_notes_refs, default_notes_ref()); display_ref_env = getenv(GIT_NOTES_DISPLAY_REF_ENVIRONMENT); if (display_ref_env) { @@ -1066,9 +1067,9 @@ void init_display_notes(struct display_notes_opt *opt) git_config(notes_display_config, &load_config_refs); - if (opt && opt->extra_notes_refs) { + if (opt) { struct string_list_item *item; - for_each_string_list_item(item, opt->extra_notes_refs) + for_each_string_list_item(item, &opt->extra_notes_refs) string_list_add_refs_by_glob(&display_notes_refs, item->string); } @@ -1285,3 +1286,13 @@ int copy_note(struct notes_tree *t, return 0; } + +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); +} @@ -1,6 +1,8 @@ #ifndef NOTES_H #define NOTES_H +#include "string-list.h" + /* * Function type for combining two notes annotating the same object. * @@ -256,8 +258,8 @@ void format_note(struct notes_tree *t, const unsigned char *object_sha1, struct string_list; struct display_notes_opt { - unsigned int suppress_default_notes:1; - struct string_list *extra_notes_refs; + int use_default_notes; + struct string_list extra_notes_refs; }; /* @@ -307,4 +309,7 @@ void string_list_add_refs_by_glob(struct string_list *list, const char *glob); void string_list_add_refs_from_colon_sep(struct string_list *list, const char *globs); +/* Expand inplace a note ref like "foo" or "notes/foo" into "refs/notes/foo" */ +void expand_notes_ref(struct strbuf *sb); + #endif @@ -339,6 +339,7 @@ void pp_user_info(const char *what, enum cmit_fmt fmt, struct strbuf *sb, if (fmt == CMIT_FMT_EMAIL) { char *name_tail = strchr(line, '<'); int display_name_length; + int final_line; if (!name_tail) return; while (line < name_tail && isspace(name_tail[-1])) @@ -353,6 +354,14 @@ void pp_user_info(const char *what, enum cmit_fmt fmt, struct strbuf *sb, add_rfc2047(sb, quoted.buf, quoted.len, encoding); strbuf_release("ed); } + for (final_line = 0; final_line < sb->len; final_line++) + if (sb->buf[sb->len - final_line - 1] == '\n') + break; + if (namelen - display_name_length + final_line > 78) { + strbuf_addch(sb, '\n'); + if (!isspace(name_tail[0])) + strbuf_addch(sb, ' '); + } strbuf_add(sb, name_tail, namelen - display_name_length); strbuf_addch(sb, '\n'); } else { diff --git a/remote-curl.c b/remote-curl.c index 775d614303..17d8a9b377 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -347,7 +347,7 @@ static curlioerr rpc_ioctl(CURL *handle, int cmd, void *clientp) } #endif -static size_t rpc_in(const void *ptr, size_t eltsize, +static size_t rpc_in(char *ptr, size_t eltsize, size_t nmemb, void *buffer_) { size_t size = eltsize * nmemb; @@ -671,3 +671,87 @@ int rerere_forget(const char **pathspec) } return write_rr(&merge_rr, fd); } + +static time_t rerere_created_at(const char *name) +{ + struct stat st; + return stat(rerere_path(name, "preimage"), &st) ? (time_t) 0 : st.st_mtime; +} + +static time_t rerere_last_used_at(const char *name) +{ + struct stat st; + return stat(rerere_path(name, "postimage"), &st) ? (time_t) 0 : st.st_mtime; +} + +static void unlink_rr_item(const char *name) +{ + unlink(rerere_path(name, "thisimage")); + unlink(rerere_path(name, "preimage")); + unlink(rerere_path(name, "postimage")); + rmdir(git_path("rr-cache/%s", name)); +} + +struct rerere_gc_config_cb { + int cutoff_noresolve; + int cutoff_resolve; +}; + +static int git_rerere_gc_config(const char *var, const char *value, void *cb) +{ + struct rerere_gc_config_cb *cf = cb; + + if (!strcmp(var, "gc.rerereresolved")) + cf->cutoff_resolve = git_config_int(var, value); + else if (!strcmp(var, "gc.rerereunresolved")) + cf->cutoff_noresolve = git_config_int(var, value); + else + return git_default_config(var, value, cb); + return 0; +} + +void rerere_gc(struct string_list *rr) +{ + struct string_list to_remove = STRING_LIST_INIT_DUP; + DIR *dir; + struct dirent *e; + int i, cutoff; + time_t now = time(NULL), then; + struct rerere_gc_config_cb cf = { 15, 60 }; + + git_config(git_rerere_gc_config, &cf); + dir = opendir(git_path("rr-cache")); + if (!dir) + die_errno("unable to open rr-cache directory"); + while ((e = readdir(dir))) { + if (is_dot_or_dotdot(e->d_name)) + continue; + + then = rerere_last_used_at(e->d_name); + if (then) { + cutoff = cf.cutoff_resolve; + } else { + then = rerere_created_at(e->d_name); + if (!then) + continue; + cutoff = cf.cutoff_noresolve; + } + if (then < now - cutoff * 86400) + string_list_append(&to_remove, e->d_name); + } + for (i = 0; i < to_remove.nr; i++) + unlink_rr_item(to_remove.items[i].string); + string_list_clear(&to_remove, 0); +} + +void rerere_clear(struct string_list *merge_rr) +{ + int i; + + for (i = 0; i < merge_rr->nr; i++) { + const char *name = (const char *)merge_rr->items[i].util; + if (!has_rerere_resolution(name)) + unlink_rr_item(name); + } + unlink_or_warn(git_path("MERGE_RR")); +} @@ -19,6 +19,8 @@ extern const char *rerere_path(const char *hex, const char *file); extern int has_rerere_resolution(const char *hex); extern int rerere_forget(const char **); extern int rerere_remaining(struct string_list *); +extern void rerere_clear(struct string_list *); +extern void rerere_gc(struct string_list *); #define OPT_RERERE_AUTOUPDATE(v) OPT_UYN(0, "rerere-autoupdate", (v), \ "update the index with reused conflict resolution if possible") diff --git a/revision.c b/revision.c index 0f38364cf3..a7cf79bf2e 100644 --- a/revision.c +++ b/revision.c @@ -955,6 +955,8 @@ void init_revisions(struct rev_info *revs, const char *prefix) revs->diffopt.prefix = prefix; revs->diffopt.prefix_length = strlen(prefix); } + + revs->notes_opt.use_default_notes = -1; } static void add_pending_commit_list(struct rev_info *revs, @@ -1178,7 +1180,9 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg !strcmp(arg, "--tags") || !strcmp(arg, "--remotes") || !strcmp(arg, "--reflog") || !strcmp(arg, "--not") || !strcmp(arg, "--no-walk") || !strcmp(arg, "--do-walk") || - !strcmp(arg, "--bisect")) + !strcmp(arg, "--bisect") || !prefixcmp(arg, "--glob=") || + !prefixcmp(arg, "--branches=") || !prefixcmp(arg, "--tags=") || + !prefixcmp(arg, "--remotes=")) { unkv[(*unkc)++] = arg; return 1; @@ -1365,32 +1369,39 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg revs->verbose_header = 1; revs->pretty_given = 1; get_commit_format(arg+9, revs); - } else if (!strcmp(arg, "--show-notes")) { + } else if (!strcmp(arg, "--show-notes") || !strcmp(arg, "--notes")) { revs->show_notes = 1; revs->show_notes_given = 1; - } else if (!prefixcmp(arg, "--show-notes=")) { + revs->notes_opt.use_default_notes = 1; + } else if (!prefixcmp(arg, "--show-notes=") || + !prefixcmp(arg, "--notes=")) { struct strbuf buf = STRBUF_INIT; revs->show_notes = 1; revs->show_notes_given = 1; - if (!revs->notes_opt.extra_notes_refs) - revs->notes_opt.extra_notes_refs = xcalloc(1, sizeof(struct string_list)); - if (!prefixcmp(arg+13, "refs/")) - /* happy */; - else if (!prefixcmp(arg+13, "notes/")) - strbuf_addstr(&buf, "refs/"); + if (!prefixcmp(arg, "--show-notes")) { + if (revs->notes_opt.use_default_notes < 0) + revs->notes_opt.use_default_notes = 1; + strbuf_addstr(&buf, arg+13); + } else - strbuf_addstr(&buf, "refs/notes/"); - strbuf_addstr(&buf, arg+13); - string_list_append(revs->notes_opt.extra_notes_refs, + strbuf_addstr(&buf, arg+8); + expand_notes_ref(&buf); + string_list_append(&revs->notes_opt.extra_notes_refs, strbuf_detach(&buf, NULL)); } else if (!strcmp(arg, "--no-notes")) { revs->show_notes = 0; revs->show_notes_given = 1; + revs->notes_opt.use_default_notes = -1; + /* we have been strdup'ing ourselves, so trick + * string_list into free()ing strings */ + revs->notes_opt.extra_notes_refs.strdup_strings = 1; + string_list_clear(&revs->notes_opt.extra_notes_refs, 0); + revs->notes_opt.extra_notes_refs.strdup_strings = 0; } else if (!strcmp(arg, "--standard-notes")) { revs->show_notes_given = 1; - revs->notes_opt.suppress_default_notes = 0; + revs->notes_opt.use_default_notes = 1; } else if (!strcmp(arg, "--no-standard-notes")) { - revs->notes_opt.suppress_default_notes = 1; + revs->notes_opt.use_default_notes = 0; } else if (!strcmp(arg, "--oneline")) { revs->verbose_header = 1; get_commit_format("oneline", revs); @@ -1526,6 +1537,69 @@ static void append_prune_data(const char ***prune_data, const char **av) *prune_data = prune; } +static int handle_revision_pseudo_opt(const char *submodule, + struct rev_info *revs, + int argc, const char **argv, int *flags) +{ + const char *arg = argv[0]; + const char *optarg; + int argcount; + + /* + * NOTE! + * + * Commands like "git shortlog" will not accept the options below + * unless parse_revision_opt queues them (as opposed to erroring + * out). + * + * When implementing your new pseudo-option, remember to + * register it in the list at the top of handle_revision_opt. + */ + if (!strcmp(arg, "--all")) { + handle_refs(submodule, revs, *flags, for_each_ref_submodule); + handle_refs(submodule, revs, *flags, head_ref_submodule); + } else if (!strcmp(arg, "--branches")) { + handle_refs(submodule, revs, *flags, for_each_branch_ref_submodule); + } else if (!strcmp(arg, "--bisect")) { + handle_refs(submodule, revs, *flags, for_each_bad_bisect_ref); + handle_refs(submodule, revs, *flags ^ UNINTERESTING, for_each_good_bisect_ref); + revs->bisect = 1; + } else if (!strcmp(arg, "--tags")) { + handle_refs(submodule, revs, *flags, for_each_tag_ref_submodule); + } else if (!strcmp(arg, "--remotes")) { + handle_refs(submodule, revs, *flags, for_each_remote_ref_submodule); + } else if ((argcount = parse_long_opt("glob", argv, &optarg))) { + struct all_refs_cb cb; + init_all_refs_cb(&cb, revs, *flags); + for_each_glob_ref(handle_one_ref, optarg, &cb); + return argcount; + } else if (!prefixcmp(arg, "--branches=")) { + struct all_refs_cb cb; + init_all_refs_cb(&cb, revs, *flags); + for_each_glob_ref_in(handle_one_ref, arg + 11, "refs/heads/", &cb); + } else if (!prefixcmp(arg, "--tags=")) { + struct all_refs_cb cb; + init_all_refs_cb(&cb, revs, *flags); + for_each_glob_ref_in(handle_one_ref, arg + 7, "refs/tags/", &cb); + } else if (!prefixcmp(arg, "--remotes=")) { + struct all_refs_cb cb; + init_all_refs_cb(&cb, revs, *flags); + for_each_glob_ref_in(handle_one_ref, arg + 10, "refs/remotes/", &cb); + } else if (!strcmp(arg, "--reflog")) { + handle_reflog(revs, *flags); + } else if (!strcmp(arg, "--not")) { + *flags ^= UNINTERESTING; + } else if (!strcmp(arg, "--no-walk")) { + revs->no_walk = 1; + } else if (!strcmp(arg, "--do-walk")) { + revs->no_walk = 0; + } else { + return 0; + } + + return 1; +} + /* * Parse revision information, filling in the "rev_info" structure, * and removing the used arguments from the argument list. @@ -1538,8 +1612,6 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s int i, flags, left, seen_dashdash, read_from_stdin, got_rev_arg = 0; const char **prune_data = NULL; const char *submodule = NULL; - const char *optarg; - int argcount; if (opt) submodule = opt->submodule; @@ -1566,70 +1638,14 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s if (*arg == '-') { int opts; - if (!strcmp(arg, "--all")) { - handle_refs(submodule, revs, flags, for_each_ref_submodule); - handle_refs(submodule, revs, flags, head_ref_submodule); - continue; - } - if (!strcmp(arg, "--branches")) { - handle_refs(submodule, revs, flags, for_each_branch_ref_submodule); - continue; - } - if (!strcmp(arg, "--bisect")) { - handle_refs(submodule, revs, flags, for_each_bad_bisect_ref); - handle_refs(submodule, revs, flags ^ UNINTERESTING, for_each_good_bisect_ref); - revs->bisect = 1; - continue; - } - if (!strcmp(arg, "--tags")) { - handle_refs(submodule, revs, flags, for_each_tag_ref_submodule); - continue; - } - if (!strcmp(arg, "--remotes")) { - handle_refs(submodule, revs, flags, for_each_remote_ref_submodule); - continue; - } - if ((argcount = parse_long_opt("glob", argv + i, &optarg))) { - struct all_refs_cb cb; - i += argcount - 1; - init_all_refs_cb(&cb, revs, flags); - for_each_glob_ref(handle_one_ref, optarg, &cb); - continue; - } - if (!prefixcmp(arg, "--branches=")) { - struct all_refs_cb cb; - init_all_refs_cb(&cb, revs, flags); - for_each_glob_ref_in(handle_one_ref, arg + 11, "refs/heads/", &cb); - continue; - } - if (!prefixcmp(arg, "--tags=")) { - struct all_refs_cb cb; - init_all_refs_cb(&cb, revs, flags); - for_each_glob_ref_in(handle_one_ref, arg + 7, "refs/tags/", &cb); - continue; - } - if (!prefixcmp(arg, "--remotes=")) { - struct all_refs_cb cb; - init_all_refs_cb(&cb, revs, flags); - for_each_glob_ref_in(handle_one_ref, arg + 10, "refs/remotes/", &cb); - continue; - } - if (!strcmp(arg, "--reflog")) { - handle_reflog(revs, flags); - continue; - } - if (!strcmp(arg, "--not")) { - flags ^= UNINTERESTING; - continue; - } - if (!strcmp(arg, "--no-walk")) { - revs->no_walk = 1; - continue; - } - if (!strcmp(arg, "--do-walk")) { - revs->no_walk = 0; + opts = handle_revision_pseudo_opt(submodule, + revs, argc - i, argv + i, + &flags); + if (opts > 0) { + i += opts - 1; continue; } + if (!strcmp(arg, "--stdin")) { if (revs->disable_stdin) { argv[left++] = arg; diff --git a/revision.h b/revision.h index 9fd8f3016f..bca9947977 100644 --- a/revision.h +++ b/revision.h @@ -141,6 +141,7 @@ struct rev_info { /* commit counts */ int count_left; int count_right; + int count_same; }; #define REV_TREE_SAME 0 diff --git a/run-command.c b/run-command.c index f91e446c86..70e8a249d0 100644 --- a/run-command.c +++ b/run-command.c @@ -67,21 +67,24 @@ static int child_notifier = -1; static void notify_parent(void) { - ssize_t unused; - unused = write(child_notifier, "", 1); + /* + * execvp failed. If possible, we'd like to let start_command + * know, so failures like ENOENT can be handled right away; but + * otherwise, finish_command will still report the error. + */ + xwrite(child_notifier, "", 1); } static NORETURN void die_child(const char *err, va_list params) { char msg[4096]; - ssize_t unused; int len = vsnprintf(msg, sizeof(msg), err, params); if (len > sizeof(msg)) len = sizeof(msg); - unused = write(child_err, "fatal: ", 7); - unused = write(child_err, msg, len); - unused = write(child_err, "\n", 1); + write_in_full(child_err, "fatal: ", 7); + write_in_full(child_err, msg, len); + write_in_full(child_err, "\n", 1); exit(128); } #endif @@ -325,6 +325,7 @@ static const char *setup_explicit_git_dir(const char *gitdirenv, const char *work_tree_env = getenv(GIT_WORK_TREE_ENVIRONMENT); const char *worktree; char *gitfile; + int offset; if (PATH_MAX - 40 < strlen(gitdirenv)) die("'$%s' too big", GIT_DIR_ENVIRONMENT); @@ -390,15 +391,15 @@ static const char *setup_explicit_git_dir(const char *gitdirenv, return NULL; } - if (!prefixcmp(cwd, worktree) && - cwd[strlen(worktree)] == '/') { /* cwd inside worktree */ + offset = dir_inside_of(cwd, worktree); + if (offset >= 0) { /* cwd inside worktree? */ set_git_dir(real_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 cwd + offset; } /* cwd outside worktree */ @@ -30,8 +30,10 @@ void strbuf_init(struct strbuf *sb, size_t hint) { sb->alloc = sb->len = 0; sb->buf = strbuf_slopbuf; - if (hint) + if (hint) { strbuf_grow(sb, hint); + sb->buf[0] = '\0'; + } } void strbuf_release(struct strbuf *sb) @@ -3,8 +3,6 @@ /* See Documentation/technical/api-strbuf.txt */ -#include <assert.h> - extern char strbuf_slopbuf[]; struct strbuf { size_t alloc; @@ -33,9 +31,8 @@ static inline size_t strbuf_avail(const struct strbuf *sb) { extern void strbuf_grow(struct strbuf *, size_t); static inline void strbuf_setlen(struct strbuf *sb, size_t len) { - if (!sb->alloc) - strbuf_grow(sb, 0); - assert(len < sb->alloc); + if (len > (sb->alloc ? sb->alloc - 1 : 0)) + die("BUG: strbuf_setlen() beyond buffer"); sb->len = len; sb->buf[len] = '\0'; } @@ -379,7 +379,7 @@ library for your script to use. - test_expect_success [<prereq>] <message> <script> - Usually takes two strings as parameter, and evaluates the + Usually takes two strings as parameters, and evaluates the <script>. If it yields success, test is considered successful. <message> should state what it is testing. @@ -390,7 +390,7 @@ library for your script to use. 'tree=$(git-write-tree)' If you supply three parameters the first will be taken to be a - prerequisite, see the test_set_prereq and test_have_prereq + prerequisite; see the test_set_prereq and test_have_prereq documentation below: test_expect_success TTY 'git --paginate rev-list uses a pager' \ @@ -446,7 +446,7 @@ library for your script to use. Merges the given rev using the given message. Like test_commit, creates a tag and calls test_tick before committing. - - test_set_prereq SOME_PREREQ + - test_set_prereq <prereq> Set a test prerequisite to be used later with test_have_prereq. The test-lib will set some prerequisites for you, see the @@ -456,7 +456,7 @@ library for your script to use. test_have_prereq directly, or the three argument invocation of test_expect_success and test_expect_failure. - - test_have_prereq SOME PREREQ + - test_have_prereq <prereq> Check if we have a prerequisite previously set with test_set_prereq. The most common use of this directly is to skip @@ -526,12 +526,13 @@ library for your script to use. 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_file <path> [<diagnosis>] + test_path_is_dir <path> [<diagnosis>] test_path_is_missing <path> [<diagnosis>] - Check whether a file/directory exists or doesn't. <diagnosis> will - be displayed if the test fails. + Check if the named path is a file, if the named path is a + directory, or if the named path does not exist, respectively, + and fail otherwise, showing the <diagnosis> text. - test_when_finished <script> diff --git a/t/lib-httpd.sh b/t/lib-httpd.sh index d3829b8d0a..b8996a373a 100644 --- a/t/lib-httpd.sh +++ b/t/lib-httpd.sh @@ -157,8 +157,7 @@ test_http_push_nonff() { grep "^ ! \[rejected\][ ]*$BRANCH -> $BRANCH (non-fast-forward)$" output ' - test_expect_success C_LOCALE_OUTPUT 'non-fast-forward push shows help message' ' - grep "To prevent you from losing history, non-fast-forward updates were rejected" \ - output + test_expect_success 'non-fast-forward push shows help message' ' + test_i18ngrep "To prevent you from losing history, non-fast-forward updates were rejected" output ' } diff --git a/t/t0001-init.sh b/t/t0001-init.sh index 54520f6fa6..8106af8fba 100755 --- a/t/t0001-init.sh +++ b/t/t0001-init.sh @@ -180,7 +180,7 @@ test_expect_success 'GIT_DIR & GIT_WORK_TREE (2)' ' fi ' -test_expect_success C_LOCALE_OUTPUT 'reinit' ' +test_expect_success 'reinit' ' ( sane_unset GIT_CONFIG GIT_WORK_TREE GIT_CONFIG && @@ -190,11 +190,11 @@ test_expect_success C_LOCALE_OUTPUT 'reinit' ' git init >out1 2>err1 && git init >out2 2>err2 ) && - grep "Initialized empty" again/out1 && - grep "Reinitialized existing" again/out2 && + test_i18ngrep "Initialized empty" again/out1 && + test_i18ngrep "Reinitialized existing" again/out2 && >again/empty && - test_cmp again/empty again/err1 && - test_cmp again/empty again/err2 + test_i18ncmp again/empty again/err1 && + test_i18ncmp again/empty again/err2 ' test_expect_success 'init with --template' ' diff --git a/t/t0061-run-command.sh b/t/t0061-run-command.sh index 10b26e4d8e..8d4938f019 100755 --- a/t/t0061-run-command.sh +++ b/t/t0061-run-command.sh @@ -7,8 +7,31 @@ test_description='Test run command' . ./test-lib.sh +cat >hello-script <<-EOF + #!$SHELL_PATH + cat hello-script +EOF +>empty + test_expect_success 'start_command reports ENOENT' ' test-run-command start-command-ENOENT ./does-not-exist ' +test_expect_success 'run_command can run a command' ' + cat hello-script >hello.sh && + chmod +x hello.sh && + test-run-command run-command ./hello.sh >actual 2>err && + + test_cmp hello-script actual && + test_cmp empty err +' + +test_expect_success POSIXPERM 'run_command reports EACCES' ' + cat hello-script >hello.sh && + chmod -x hello.sh && + test_must_fail test-run-command run-command ./hello.sh 2>err && + + grep "fatal: cannot exec.*hello.sh" err +' + test_done diff --git a/t/t1020-subdirectory.sh b/t/t1020-subdirectory.sh index 1fd187c5eb..ddc3921ac6 100755 --- a/t/t1020-subdirectory.sh +++ b/t/t1020-subdirectory.sh @@ -118,6 +118,27 @@ test_expect_success 'alias expansion' ' git ss ) ' + +test_expect_success '!alias expansion' ' + pwd >expect && + ( + git config alias.test !pwd && + cd dir && + git test >../actual + ) && + test_cmp expect actual +' + +test_expect_success 'GIT_PREFIX for !alias' ' + printf "dir/" >expect && + ( + git config alias.test "!sh -c \"printf \$GIT_PREFIX\"" && + cd dir && + git test >../actual + ) && + test_cmp expect actual +' + 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 3264fefbad..5e29e13782 100755 --- a/t/t1200-tutorial.sh +++ b/t/t1200-tutorial.sh @@ -166,8 +166,8 @@ test_expect_success 'git resolve' ' -e "s/^Fast[- ]forward /FASTFORWARD /" >resolve.output ' -test_expect_success C_LOCALE_OUTPUT 'git resolve output' ' - test_cmp resolve.expect resolve.output +test_expect_success 'git resolve output' ' + test_i18ncmp resolve.expect resolve.output ' cat > show-branch2.expect << EOF diff --git a/t/t1303-wacky-config.sh b/t/t1303-wacky-config.sh index 080117c6bc..46103a1591 100755 --- a/t/t1303-wacky-config.sh +++ b/t/t1303-wacky-config.sh @@ -44,7 +44,7 @@ LONG_VALUE=$(printf "x%01021dx a" 7) test_expect_success 'do not crash on special long config line' ' setup && git config section.key "$LONG_VALUE" && - check section.key "fatal: bad config file line 2 in .git/config" + check section.key "$LONG_VALUE" ' test_done diff --git a/t/t2019-checkout-ambiguous-ref.sh b/t/t2019-checkout-ambiguous-ref.sh index cc34e5535b..b99d5192a9 100755 --- a/t/t2019-checkout-ambiguous-ref.sh +++ b/t/t2019-checkout-ambiguous-ref.sh @@ -29,9 +29,9 @@ test_expect_success 'checkout chooses branch over tag' ' test_cmp expect file ' -test_expect_success C_LOCALE_OUTPUT 'checkout reports switch to branch' ' - grep "Switched to branch" stderr && - ! grep "^HEAD is now at" stderr +test_expect_success 'checkout reports switch to branch' ' + test_i18ngrep "Switched to branch" stderr && + test_i18ngrep ! "^HEAD is now at" stderr ' test_expect_success 'checkout vague ref succeeds' ' @@ -51,9 +51,9 @@ test_expect_success VAGUENESS_SUCCESS 'checkout chooses branch over tag' ' test_cmp expect file ' -test_expect_success VAGUENESS_SUCCESS,C_LOCALE_OUTPUT 'checkout reports switch to branch' ' - grep "Switched to branch" stderr && - ! grep "^HEAD is now at" stderr +test_expect_success VAGUENESS_SUCCESS 'checkout reports switch to branch' ' + test_i18ngrep "Switched to branch" stderr && + test_i18ngrep ! "^HEAD is now at" stderr ' test_done diff --git a/t/t2020-checkout-detach.sh b/t/t2020-checkout-detach.sh index 569b27fe8d..2366f0f414 100755 --- a/t/t2020-checkout-detach.sh +++ b/t/t2020-checkout-detach.sh @@ -13,10 +13,10 @@ check_not_detached () { ORPHAN_WARNING='you are leaving .* commit.*behind' check_orphan_warning() { - grep "$ORPHAN_WARNING" "$1" + test_i18ngrep "$ORPHAN_WARNING" "$1" } check_no_orphan_warning() { - ! grep "$ORPHAN_WARNING" "$1" + test_i18ngrep ! "$ORPHAN_WARNING" "$1" } reset () { @@ -108,21 +108,30 @@ test_expect_success 'checkout warns on orphan commits' ' echo content >orphan && git add orphan && git commit -a -m orphan && - git checkout master 2>stderr && + git checkout master 2>stderr +' + +test_expect_success 'checkout warns on orphan commits: output' ' check_orphan_warning stderr ' test_expect_success 'checkout does not warn leaving ref tip' ' reset && git checkout --detach two && - git checkout master 2>stderr && + git checkout master 2>stderr +' + +test_expect_success 'checkout does not warn leaving ref tip' ' check_no_orphan_warning stderr ' test_expect_success 'checkout does not warn leaving reachable commit' ' reset && git checkout --detach HEAD^ && - git checkout master 2>stderr && + git checkout master 2>stderr +' + +test_expect_success 'checkout does not warn leaving reachable commit' ' check_no_orphan_warning stderr ' diff --git a/t/t2200-add-update.sh b/t/t2200-add-update.sh index 7206c13741..4cdebda6a5 100755 --- a/t/t2200-add-update.sh +++ b/t/t2200-add-update.sh @@ -111,7 +111,7 @@ test_expect_success 'touch and then add explicitly' ' ' -test_expect_success C_LOCALE_OUTPUT 'add -n -u should not add but just report' ' +test_expect_success 'add -n -u should not add but just report' ' ( echo "add '\''check'\''" && @@ -124,7 +124,7 @@ test_expect_success C_LOCALE_OUTPUT 'add -n -u should not add but just report' ' after=$(git ls-files -s check top) && test "$before" = "$after" && - test_cmp expect actual + test_i18ncmp expect actual ' diff --git a/t/t2204-add-ignored.sh b/t/t2204-add-ignored.sh index 49753362f0..8340ac2f07 100755 --- a/t/t2204-add-ignored.sh +++ b/t/t2204-add-ignored.sh @@ -34,8 +34,8 @@ do ! test -s out ' - test_expect_success C_LOCALE_OUTPUT "complaints for ignored $i output" ' - grep -e "Use -f if" err + test_expect_success "complaints for ignored $i output" ' + test_i18ngrep -e "Use -f if" err ' test_expect_success "complaints for ignored $i with unignored file" ' @@ -44,8 +44,8 @@ do git ls-files "$i" >out && ! test -s out ' - test_expect_success C_LOCALE_OUTPUT "complaints for ignored $i with unignored file output" ' - grep -e "Use -f if" err + test_expect_success "complaints for ignored $i with unignored file output" ' + test_i18ngrep -e "Use -f if" err ' done @@ -61,10 +61,10 @@ do ) ' - test_expect_success C_LOCALE_OUTPUT "complaints for ignored $i in dir output" ' + test_expect_success "complaints for ignored $i in dir output" ' ( cd dir && - grep -e "Use -f if" err + test_i18ngrep -e "Use -f if" err ) ' done @@ -81,10 +81,10 @@ do ) ' - test_expect_success C_LOCALE_OUTPUT "complaints for ignored $i in sub output" ' + test_expect_success "complaints for ignored $i in sub output" ' ( cd sub && - grep -e "Use -f if" err + test_i18ngrep -e "Use -f if" err ) ' done diff --git a/t/t3030-merge-recursive.sh b/t/t3030-merge-recursive.sh index 806fdccce1..0c02d56952 100755 --- a/t/t3030-merge-recursive.sh +++ b/t/t3030-merge-recursive.sh @@ -312,20 +312,20 @@ test_expect_success 'merge-recursive result' ' ' -test_expect_success C_LOCALE_OUTPUT 'fail if the index has unresolved entries' ' +test_expect_success 'fail if the index has unresolved entries' ' rm -fr [abcd] && git checkout -f "$c1" && test_must_fail git merge "$c5" && test_must_fail git merge "$c5" 2> out && - grep "not possible because you have unmerged files" out && + test_i18ngrep "not possible because you have unmerged files" out && git add -u && test_must_fail git merge "$c5" 2> out && - grep "You have not concluded your merge" out && + test_i18ngrep "You have not concluded your merge" out && rm -f .git/MERGE_HEAD && test_must_fail git merge "$c5" 2> out && - grep "Your local changes to the following files would be overwritten by merge:" out + test_i18ngrep "Your local changes to the following files would be overwritten by merge:" out ' test_expect_success 'merge-recursive remove conflict' ' diff --git a/t/t3102-ls-tree-wildcards.sh b/t/t3102-ls-tree-wildcards.sh new file mode 100755 index 0000000000..c286854485 --- /dev/null +++ b/t/t3102-ls-tree-wildcards.sh @@ -0,0 +1,22 @@ +#!/bin/sh + +test_description='ls-tree with(out) globs' + +. ./test-lib.sh + +test_expect_success 'setup' ' + mkdir a aa "a[a]" && + touch a/one aa/two "a[a]/three" && + git add a/one aa/two "a[a]/three" && + git commit -m test +' + +test_expect_success 'ls-tree a[a] matches literally' ' + cat >expected <<EOF && +100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 a[a]/three +EOF + git ls-tree -r HEAD "a[a]" >actual && + test_cmp expected actual +' + +test_done diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index 463ef1909f..9e69c8c926 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -203,10 +203,12 @@ test_expect_success 'test deleting branch deletes branch config' \ test -z "$(git config branch.my7.remote)" && test -z "$(git config branch.my7.merge)"' -test_expect_success C_LOCALE_OUTPUT 'test deleting branch without config' \ +test_expect_success 'test deleting branch without config' \ 'git branch my7 s && sha1=$(git rev-parse my7 | cut -c 1-7) && - test "$(git branch -d my7 2>&1)" = "Deleted branch my7 (was $sha1)."' + echo "Deleted branch my7 (was $sha1)." >expect && + git branch -d my7 >actual 2>&1 && + test_i18ncmp expect actual' test_expect_success 'test --track without .fetch entries' \ 'git branch --track my8 && diff --git a/t/t3203-branch-output.sh b/t/t3203-branch-output.sh index 4ef7d09115..6b7c118e4f 100755 --- a/t/t3203-branch-output.sh +++ b/t/t3203-branch-output.sh @@ -72,10 +72,10 @@ cat >expect <<'EOF' branch-two master EOF -test_expect_success C_LOCALE_OUTPUT 'git branch shows detached HEAD properly' ' +test_expect_success 'git branch shows detached HEAD properly' ' git checkout HEAD^0 && git branch >actual && - test_cmp expect actual + test_i18ncmp expect actual ' test_done diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh index 1921ca3a73..28e17c8920 100755 --- a/t/t3301-notes.sh +++ b/t/t3301-notes.sh @@ -101,8 +101,8 @@ test_expect_success 'edit existing notes' ' test_must_fail git notes show HEAD^ ' -test_expect_success 'cannot add note where one exists' ' - ! MSG=b2 git notes add && +test_expect_success 'cannot "git notes add -m" where notes already exists' ' + test_must_fail git notes add -m "b2" && test ! -f .git/NOTES_EDITMSG && test 1 = $(git ls-tree refs/notes/commits | wc -l) && test b3 = $(git notes show) && @@ -110,6 +110,24 @@ test_expect_success 'cannot add note where one exists' ' test_must_fail git notes show HEAD^ ' +test_expect_success 'can overwrite existing note with "git notes add -f -m"' ' + git notes add -f -m "b1" && + test ! -f .git/NOTES_EDITMSG && + test 1 = $(git ls-tree refs/notes/commits | wc -l) && + test b1 = $(git notes show) && + git show HEAD^ && + test_must_fail git notes show HEAD^ +' + +test_expect_success 'add w/no options on existing note morphs into edit' ' + MSG=b2 git notes add && + test ! -f .git/NOTES_EDITMSG && + test 1 = $(git ls-tree refs/notes/commits | wc -l) && + test b2 = $(git notes show) && + git show HEAD^ && + test_must_fail git notes show HEAD^ +' + test_expect_success 'can overwrite existing note with "git notes add -f"' ' MSG=b1 git notes add -f && test ! -f .git/NOTES_EDITMSG && @@ -194,6 +212,13 @@ test_expect_success 'show -F notes' ' test_cmp expect-F output ' +test_expect_success 'Re-adding -F notes without -f fails' ' + echo "zyxxy" > note5 && + test_must_fail git notes add -F note5 && + git log -3 > output && + test_cmp expect-F output +' + cat >expect << EOF commit 15023535574ded8b1a89052b32673f84cf9582b8 tree e070e3af51011e47b183c33adf9736736a525709 @@ -247,6 +272,44 @@ do ' done +test_expect_success 'setup alternate notes ref' ' + git notes --ref=alternate add -m alternate +' + +test_expect_success 'git log --notes shows default notes' ' + git log -1 --notes >output && + grep xyzzy output && + ! grep alternate output +' + +test_expect_success 'git log --notes=X shows only X' ' + git log -1 --notes=alternate >output && + ! grep xyzzy output && + grep alternate output +' + +test_expect_success 'git log --notes --notes=X shows both' ' + git log -1 --notes --notes=alternate >output && + grep xyzzy output && + grep alternate output +' + +test_expect_success 'git log --no-notes resets default state' ' + git log -1 --notes --notes=alternate \ + --no-notes --notes=alternate \ + >output && + ! grep xyzzy output && + grep alternate output +' + +test_expect_success 'git log --no-notes resets ref list' ' + git log -1 --notes --notes=alternate \ + --no-notes --notes \ + >output && + grep xyzzy output && + ! grep alternate output +' + test_expect_success 'create -m notes (setup)' ' : > a5 && git add a5 && diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh index 349eebd542..6eaecec906 100755 --- a/t/t3400-rebase.sh +++ b/t/t3400-rebase.sh @@ -158,15 +158,24 @@ test_expect_success 'Show verbose error when HEAD could not be detached' ' ' rm -f B -test_expect_success 'dump usage when upstream arg is missing' ' - git checkout -b usage topic && - test_must_fail git rebase 2>error1 && - grep "[Uu]sage" error1 && - test_must_fail git rebase --abort 2>error2 && - grep "No rebase in progress" error2 && - test_must_fail git rebase --onto master 2>error3 && - grep "[Uu]sage" error3 && - ! grep "can.t shift" error3 +test_expect_success 'fail when upstream arg is missing and not on branch' ' + git checkout topic && + test_must_fail git rebase >output.out && + grep "You are not currently on a branch" output.out +' + +test_expect_success 'fail when upstream arg is missing and not configured' ' + git checkout -b no-config topic && + test_must_fail git rebase >output.out && + grep "branch.no-config.merge" output.out +' + +test_expect_success 'default to @{upstream} when upstream arg is missing' ' + git checkout -b default topic && + git config branch.default.remote . + git config branch.default.merge refs/heads/master + git rebase && + test "$(git rev-parse default~1)" = "$(git rev-parse master)" ' test_expect_success 'rebase -q is quiet' ' diff --git a/t/t3403-rebase-skip.sh b/t/t3403-rebase-skip.sh index 64446e3db3..826500bd18 100755 --- a/t/t3403-rebase-skip.sh +++ b/t/t3403-rebase-skip.sh @@ -35,6 +35,11 @@ test_expect_success 'rebase with git am -3 (default)' ' test_must_fail git rebase master ' +test_expect_success 'rebase --skip can not be used with other options' ' + test_must_fail git rebase -v --skip && + test_must_fail git rebase --skip -v +' + test_expect_success 'rebase --skip with am -3' ' git rebase --skip ' diff --git a/t/t3407-rebase-abort.sh b/t/t3407-rebase-abort.sh index e573dc845b..a6a6c40a98 100755 --- a/t/t3407-rebase-abort.sh +++ b/t/t3407-rebase-abort.sh @@ -84,6 +84,16 @@ testrebase() { test_cmp reflog_before reflog_after && rm reflog_before reflog_after ' + + test_expect_success 'rebase --abort can not be used with other options' ' + cd "$work_dir" && + # Clean up the state from the previous one + git reset --hard pre-rebase && + test_must_fail git rebase$type master && + test_must_fail git rebase -v --abort && + test_must_fail git rebase --abort -v && + git rebase --abort + ' } testrebase "" .git/rebase-apply diff --git a/t/t3418-rebase-continue.sh b/t/t3418-rebase-continue.sh index 3b0d27350e..1e855cdae5 100755 --- a/t/t3418-rebase-continue.sh +++ b/t/t3418-rebase-continue.sh @@ -40,4 +40,59 @@ test_expect_success 'non-interactive rebase --continue works with touched file' git rebase --continue ' +test_expect_success 'rebase --continue can not be used with other options' ' + test_must_fail git rebase -v --continue && + test_must_fail git rebase --continue -v +' + +test_expect_success 'rebase --continue remembers merge strategy and options' ' + rm -fr .git/rebase-* && + git reset --hard commit-new-file-F2-on-topic-branch && + test_commit "commit-new-file-F3-on-topic-branch" F3 32 && + 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 && + ( + PATH=./test-bin:$PATH + test_must_fail git rebase -s funny -Xopt master topic + ) && + test -f funny.was.run && + rm funny.was.run && + echo "Resolved" >F2 && + git add F2 && + ( + PATH=./test-bin:$PATH + git rebase --continue + ) && + test -f funny.was.run +' + +test_expect_success 'rebase --continue remembers --rerere-autoupdate' ' + rm -fr .git/rebase-* && + git reset --hard commit-new-file-F3-on-topic-branch && + git checkout master + test_commit "commit-new-file-F3" F3 3 && + git config rerere.enabled true && + test_must_fail git rebase -m master topic && + echo "Resolved" >F2 && + git add F2 && + test_must_fail git rebase --continue && + echo "Resolved" >F3 && + git add F3 && + git rebase --continue && + git reset --hard topic@{1} && + test_must_fail git rebase -m --rerere-autoupdate master && + test "$(cat F2)" = "Resolved" && + test_must_fail git rebase --continue && + test "$(cat F3)" = "Resolved" && + git rebase --continue +' + test_done diff --git a/t/t3501-revert-cherry-pick.sh b/t/t3501-revert-cherry-pick.sh index 753a6c972c..595d2ff990 100755 --- a/t/t3501-revert-cherry-pick.sh +++ b/t/t3501-revert-cherry-pick.sh @@ -91,12 +91,12 @@ test_expect_success 'cherry-pick on stat-dirty working tree' ' ) ' -test_expect_success C_LOCALE_OUTPUT 'revert forbidden on dirty working tree' ' +test_expect_success 'revert forbidden on dirty working tree' ' echo content >extra_file && git add extra_file && test_must_fail git revert HEAD 2>errors && - grep "Your local changes would be overwritten by " errors + test_i18ngrep "Your local changes would be overwritten by " errors ' diff --git a/t/t3507-cherry-pick-conflict.sh b/t/t3507-cherry-pick-conflict.sh index c0c8330c20..212ec54aaf 100755 --- a/t/t3507-cherry-pick-conflict.sh +++ b/t/t3507-cherry-pick-conflict.sh @@ -44,7 +44,7 @@ test_expect_success 'failed cherry-pick does not advance HEAD' ' test "$head" = "$newhead" ' -test_expect_success C_LOCALE_OUTPUT 'advice from failed cherry-pick' " +test_expect_success 'advice from failed cherry-pick' " pristine_detach initial && picked=\$(git rev-parse --short picked) && @@ -56,7 +56,7 @@ test_expect_success C_LOCALE_OUTPUT 'advice from failed cherry-pick' " EOF test_must_fail git cherry-pick picked 2>actual && - test_cmp expected actual + test_i18ncmp expected actual " test_expect_success 'failed cherry-pick sets CHERRY_PICK_HEAD' ' diff --git a/t/t3700-add.sh b/t/t3700-add.sh index 7de42faf48..575d9508a0 100755 --- a/t/t3700-add.sh +++ b/t/t3700-add.sh @@ -271,9 +271,9 @@ test_expect_success 'git add --dry-run of non-existing file' " test_must_fail git add --dry-run track-this ignored-file >actual 2>&1 " -test_expect_success C_LOCALE_OUTPUT 'git add --dry-run of an existing file output' " +test_expect_success 'git add --dry-run of an existing file output' " echo \"fatal: pathspec 'ignored-file' did not match any files\" >expect && - test_cmp expect actual + test_i18ncmp expect actual " cat >expect.err <<\EOF @@ -290,9 +290,9 @@ test_expect_success 'git add --dry-run --ignore-missing of non-existing file' ' test_must_fail git add --dry-run --ignore-missing track-this ignored-file >actual.out 2>actual.err ' -test_expect_success C_LOCALE_OUTPUT 'git add --dry-run --ignore-missing of non-existing file output' ' - test_cmp expect.out actual.out && - test_cmp expect.err actual.err +test_expect_success 'git add --dry-run --ignore-missing of non-existing file output' ' + test_i18ncmp expect.out actual.out && + test_i18ncmp expect.err actual.err ' test_done diff --git a/t/t4001-diff-rename.sh b/t/t4001-diff-rename.sh index cad85450b7..844277cfa6 100755 --- a/t/t4001-diff-rename.sh +++ b/t/t4001-diff-rename.sh @@ -64,17 +64,42 @@ test_expect_success \ 'validate the output.' \ 'compare_diff_patch current expected' -test_expect_success C_LOCALE_OUTPUT 'favour same basenames over different ones' ' +test_expect_success 'favour same basenames over different ones' ' cp path1 another-path && git add another-path && git commit -m 1 && git rm path1 && mkdir subdir && git mv another-path subdir/path1 && - git status | grep "renamed: .*path1 -> subdir/path1"' + git status | test_i18ngrep "renamed: .*path1 -> subdir/path1"' -test_expect_success C_LOCALE_OUTPUT 'favour same basenames even with minor differences' ' +test_expect_success 'favour same basenames even with minor differences' ' git show HEAD:path1 | sed "s/15/16/" > subdir/path1 && - git status | grep "renamed: .*path1 -> subdir/path1"' + git status | test_i18ngrep "renamed: .*path1 -> subdir/path1"' + +test_expect_success 'setup for many rename source candidates' ' + git reset --hard && + for i in 0 1 2 3 4 5 6 7 8 9; + do + for j in 0 1 2 3 4 5 6 7 8 9; + do + echo "$i$j" >"path$i$j" + done + done && + git add "path??" && + test_tick && + git commit -m "hundred" && + (cat path1; echo new) >new-path && + echo old >>path1 && + git add new-path path1 && + git diff -l 4 -C -C --cached --name-status >actual 2>actual.err && + sed -e "s/^\([CM]\)[0-9]* /\1 /" actual >actual.munged && + cat >expect <<-EOF && + C path1 new-path + M path1 + EOF + test_cmp expect actual.munged && + grep warning actual.err +' test_done diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh index dd406c44b5..045cee312c 100755 --- a/t/t4014-format-patch.sh +++ b/t/t4014-format-patch.sh @@ -614,13 +614,13 @@ echo "fatal: --name-only does not make sense" > expect.name-only echo "fatal: --name-status does not make sense" > expect.name-status echo "fatal: --check does not make sense" > expect.check -test_expect_success C_LOCALE_OUTPUT 'options no longer allowed for format-patch' ' +test_expect_success 'options no longer allowed for format-patch' ' test_must_fail git format-patch --name-only 2> output && - test_cmp expect.name-only output && + test_i18ncmp expect.name-only output && test_must_fail git format-patch --name-status 2> output && - test_cmp expect.name-status output && + test_i18ncmp expect.name-status output && test_must_fail git format-patch --check 2> output && - test_cmp expect.check output' + test_i18ncmp expect.check output' test_expect_success 'format-patch --numstat should produce a patch' ' git format-patch --numstat --stdout master..side > output && @@ -793,6 +793,22 @@ test_expect_success 'format-patch wraps extremely long headers (rfc2047)' ' test_cmp expect subject ' +M8="foo_bar_" +M64=$M8$M8$M8$M8$M8$M8$M8$M8 +cat >expect <<EOF +From: $M64 + <foobar@foo.bar> +EOF +test_expect_success 'format-patch wraps non-quotable headers' ' + rm -rf patches/ && + echo content >>file && + git add file && + git commit -mfoo --author "$M64 <foobar@foo.bar>" && + git format-patch --stdout -1 >patch && + sed -n "/^From: /p; /^ /p; /^$/q" <patch >from && + test_cmp expect from +' + check_author() { echo content >>file && git add file && diff --git a/t/t4022-diff-rewrite.sh b/t/t4022-diff-rewrite.sh index 2a537a21e8..c00a94b9ba 100755 --- a/t/t4022-diff-rewrite.sh +++ b/t/t4022-diff-rewrite.sh @@ -11,7 +11,9 @@ test_expect_success setup ' tr \ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" \ "nopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZABCDEFGHIJKLM" \ - <"$TEST_DIRECTORY"/../COPYING >test + <"$TEST_DIRECTORY"/../COPYING >test && + echo "to be deleted" >test2 && + git add test2 ' @@ -25,5 +27,44 @@ test_expect_success 'detect rewrite' ' ' +cat >expect <<EOF +diff --git a/test2 b/test2 +deleted file mode 100644 +index 4202011..0000000 +--- a/test2 ++++ /dev/null +@@ -1 +0,0 @@ +-to be deleted +EOF +test_expect_success 'show deletion diff without -D' ' + + rm test2 && + git diff -- test2 >actual && + test_cmp expect actual +' + +cat >expect <<EOF +diff --git a/test2 b/test2 +deleted file mode 100644 +index 4202011..0000000 +EOF +test_expect_success 'suppress deletion diff with -D' ' + + git diff -D -- test2 >actual && + test_cmp expect actual +' + +test_expect_success 'show deletion diff with -B' ' + + git diff -B -- test >actual && + grep "Linus Torvalds" actual +' + +test_expect_success 'suppress deletion diff with -B -D' ' + + git diff -B -D -- test >actual && + grep -v "Linus Torvalds" actual +' + test_done diff --git a/t/t4047-diff-dirstat.sh b/t/t4047-diff-dirstat.sh new file mode 100755 index 0000000000..29e80a58cd --- /dev/null +++ b/t/t4047-diff-dirstat.sh @@ -0,0 +1,979 @@ +#!/bin/sh + +test_description='diff --dirstat tests' +. ./test-lib.sh + +# set up two commits where the second commit has these files +# (10 lines in each file): +# +# unchanged/text (unchanged from 1st commit) +# changed/text (changed 1st line) +# rearranged/text (swapped 1st and 2nd line) +# dst/copy/unchanged/text (copied from src/copy/unchanged/text, unchanged) +# dst/copy/changed/text (copied from src/copy/changed/text, changed) +# dst/copy/rearranged/text (copied from src/copy/rearranged/text, rearranged) +# dst/move/unchanged/text (moved from src/move/unchanged/text, unchanged) +# dst/move/changed/text (moved from src/move/changed/text, changed) +# dst/move/rearranged/text (moved from src/move/rearranged/text, rearranged) + +test_expect_success 'setup' ' + mkdir unchanged && + mkdir changed && + mkdir rearranged && + mkdir src && + mkdir src/copy && + mkdir src/copy/unchanged && + mkdir src/copy/changed && + mkdir src/copy/rearranged && + mkdir src/move && + mkdir src/move/unchanged && + mkdir src/move/changed && + mkdir src/move/rearranged && + cat <<EOF >unchanged/text && +unchanged line #0 +unchanged line #1 +unchanged line #2 +unchanged line #3 +unchanged line #4 +unchanged line #5 +unchanged line #6 +unchanged line #7 +unchanged line #8 +unchanged line #9 +EOF + cat <<EOF >changed/text && +changed line #0 +changed line #1 +changed line #2 +changed line #3 +changed line #4 +changed line #5 +changed line #6 +changed line #7 +changed line #8 +changed line #9 +EOF + cat <<EOF >rearranged/text && +rearranged line #0 +rearranged line #1 +rearranged line #2 +rearranged line #3 +rearranged line #4 +rearranged line #5 +rearranged line #6 +rearranged line #7 +rearranged line #8 +rearranged line #9 +EOF + cat <<EOF >src/copy/unchanged/text && +copy unchanged line #0 +copy unchanged line #1 +copy unchanged line #2 +copy unchanged line #3 +copy unchanged line #4 +copy unchanged line #5 +copy unchanged line #6 +copy unchanged line #7 +copy unchanged line #8 +copy unchanged line #9 +EOF + cat <<EOF >src/copy/changed/text && +copy changed line #0 +copy changed line #1 +copy changed line #2 +copy changed line #3 +copy changed line #4 +copy changed line #5 +copy changed line #6 +copy changed line #7 +copy changed line #8 +copy changed line #9 +EOF + cat <<EOF >src/copy/rearranged/text && +copy rearranged line #0 +copy rearranged line #1 +copy rearranged line #2 +copy rearranged line #3 +copy rearranged line #4 +copy rearranged line #5 +copy rearranged line #6 +copy rearranged line #7 +copy rearranged line #8 +copy rearranged line #9 +EOF + cat <<EOF >src/move/unchanged/text && +move unchanged line #0 +move unchanged line #1 +move unchanged line #2 +move unchanged line #3 +move unchanged line #4 +move unchanged line #5 +move unchanged line #6 +move unchanged line #7 +move unchanged line #8 +move unchanged line #9 +EOF + cat <<EOF >src/move/changed/text && +move changed line #0 +move changed line #1 +move changed line #2 +move changed line #3 +move changed line #4 +move changed line #5 +move changed line #6 +move changed line #7 +move changed line #8 +move changed line #9 +EOF + cat <<EOF >src/move/rearranged/text && +move rearranged line #0 +move rearranged line #1 +move rearranged line #2 +move rearranged line #3 +move rearranged line #4 +move rearranged line #5 +move rearranged line #6 +move rearranged line #7 +move rearranged line #8 +move rearranged line #9 +EOF + git add . && + git commit -m "initial" && + mkdir dst && + mkdir dst/copy && + mkdir dst/copy/unchanged && + mkdir dst/copy/changed && + mkdir dst/copy/rearranged && + mkdir dst/move && + mkdir dst/move/unchanged && + mkdir dst/move/changed && + mkdir dst/move/rearranged && + cat <<EOF >changed/text && +CHANGED XXXXXXX line #0 +changed line #1 +changed line #2 +changed line #3 +changed line #4 +changed line #5 +changed line #6 +changed line #7 +changed line #8 +changed line #9 +EOF + cat <<EOF >rearranged/text && +rearranged line #1 +rearranged line #0 +rearranged line #2 +rearranged line #3 +rearranged line #4 +rearranged line #5 +rearranged line #6 +rearranged line #7 +rearranged line #8 +rearranged line #9 +EOF + cat <<EOF >dst/copy/unchanged/text && +copy unchanged line #0 +copy unchanged line #1 +copy unchanged line #2 +copy unchanged line #3 +copy unchanged line #4 +copy unchanged line #5 +copy unchanged line #6 +copy unchanged line #7 +copy unchanged line #8 +copy unchanged line #9 +EOF + cat <<EOF >dst/copy/changed/text && +copy XXXCHANGED line #0 +copy changed line #1 +copy changed line #2 +copy changed line #3 +copy changed line #4 +copy changed line #5 +copy changed line #6 +copy changed line #7 +copy changed line #8 +copy changed line #9 +EOF + cat <<EOF >dst/copy/rearranged/text && +copy rearranged line #1 +copy rearranged line #0 +copy rearranged line #2 +copy rearranged line #3 +copy rearranged line #4 +copy rearranged line #5 +copy rearranged line #6 +copy rearranged line #7 +copy rearranged line #8 +copy rearranged line #9 +EOF + cat <<EOF >dst/move/unchanged/text && +move unchanged line #0 +move unchanged line #1 +move unchanged line #2 +move unchanged line #3 +move unchanged line #4 +move unchanged line #5 +move unchanged line #6 +move unchanged line #7 +move unchanged line #8 +move unchanged line #9 +EOF + cat <<EOF >dst/move/changed/text && +move XXXCHANGED line #0 +move changed line #1 +move changed line #2 +move changed line #3 +move changed line #4 +move changed line #5 +move changed line #6 +move changed line #7 +move changed line #8 +move changed line #9 +EOF + cat <<EOF >dst/move/rearranged/text && +move rearranged line #1 +move rearranged line #0 +move rearranged line #2 +move rearranged line #3 +move rearranged line #4 +move rearranged line #5 +move rearranged line #6 +move rearranged line #7 +move rearranged line #8 +move rearranged line #9 +EOF + git add . && + git rm -r src/move/unchanged && + git rm -r src/move/changed && + git rm -r src/move/rearranged && + git commit -m "changes" +' + +cat <<EOF >expect_diff_stat + changed/text | 2 +- + dst/copy/changed/text | 10 ++++++++++ + dst/copy/rearranged/text | 10 ++++++++++ + dst/copy/unchanged/text | 10 ++++++++++ + dst/move/changed/text | 10 ++++++++++ + dst/move/rearranged/text | 10 ++++++++++ + dst/move/unchanged/text | 10 ++++++++++ + rearranged/text | 2 +- + src/move/changed/text | 10 ---------- + src/move/rearranged/text | 10 ---------- + src/move/unchanged/text | 10 ---------- + 11 files changed, 62 insertions(+), 32 deletions(-) +EOF + +cat <<EOF >expect_diff_stat_M + changed/text | 2 +- + dst/copy/changed/text | 10 ++++++++++ + dst/copy/rearranged/text | 10 ++++++++++ + dst/copy/unchanged/text | 10 ++++++++++ + {src => dst}/move/changed/text | 2 +- + {src => dst}/move/rearranged/text | 2 +- + {src => dst}/move/unchanged/text | 0 + rearranged/text | 2 +- + 8 files changed, 34 insertions(+), 4 deletions(-) +EOF + +cat <<EOF >expect_diff_stat_CC + changed/text | 2 +- + {src => dst}/copy/changed/text | 2 +- + {src => dst}/copy/rearranged/text | 2 +- + {src => dst}/copy/unchanged/text | 0 + {src => dst}/move/changed/text | 2 +- + {src => dst}/move/rearranged/text | 2 +- + {src => dst}/move/unchanged/text | 0 + rearranged/text | 2 +- + 8 files changed, 6 insertions(+), 6 deletions(-) +EOF + +test_expect_success 'sanity check setup (--stat)' ' + git diff --stat HEAD^..HEAD >actual_diff_stat && + test_cmp expect_diff_stat actual_diff_stat && + git diff --stat -M HEAD^..HEAD >actual_diff_stat_M && + test_cmp expect_diff_stat_M actual_diff_stat_M && + git diff --stat -C -C HEAD^..HEAD >actual_diff_stat_CC && + test_cmp expect_diff_stat_CC actual_diff_stat_CC +' + +# changed/text and rearranged/text falls below default 3% threshold +cat <<EOF >expect_diff_dirstat + 10.8% dst/copy/changed/ + 10.8% dst/copy/rearranged/ + 10.8% dst/copy/unchanged/ + 10.8% dst/move/changed/ + 10.8% dst/move/rearranged/ + 10.8% dst/move/unchanged/ + 10.8% src/move/changed/ + 10.8% src/move/rearranged/ + 10.8% src/move/unchanged/ +EOF + +# rearranged/text falls below default 3% threshold +cat <<EOF >expect_diff_dirstat_M + 5.8% changed/ + 29.3% dst/copy/changed/ + 29.3% dst/copy/rearranged/ + 29.3% dst/copy/unchanged/ + 5.8% dst/move/changed/ +EOF + +# rearranged/text falls below default 3% threshold +cat <<EOF >expect_diff_dirstat_CC + 32.6% changed/ + 32.6% dst/copy/changed/ + 32.6% dst/move/changed/ +EOF + +test_expect_success 'various ways to misspell --dirstat' ' + test_must_fail git show --dirstat10 && + test_must_fail git show --dirstat10,files && + test_must_fail git show -X=20 && + test_must_fail git show -X=20,cumulative +' + +test_expect_success 'vanilla --dirstat' ' + git diff --dirstat HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git diff --dirstat -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git diff --dirstat -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +test_expect_success 'vanilla -X' ' + git diff -X HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git diff -X -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git diff -X -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +test_expect_success 'explicit defaults: --dirstat=changes,noncumulative,3' ' + git diff --dirstat=changes,noncumulative,3 HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git diff --dirstat=changes,noncumulative,3 -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git diff --dirstat=changes,noncumulative,3 -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +test_expect_success 'explicit defaults: -Xchanges,noncumulative,3' ' + git diff -Xchanges,noncumulative,3 HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git diff -Xchanges,noncumulative,3 -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git diff -Xchanges,noncumulative,3 -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +test_expect_success 'later options override earlier options:' ' + git diff --dirstat=files,10,cumulative,changes,noncumulative,3 HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git diff --dirstat=files,10,cumulative,changes,noncumulative,3 -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git diff --dirstat=files,10,cumulative,changes,noncumulative,3 -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC + git diff --dirstat=files --dirstat=10 --dirstat=cumulative --dirstat=changes --dirstat=noncumulative -X3 HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git diff --dirstat=files --dirstat=10 --dirstat=cumulative --dirstat=changes --dirstat=noncumulative -X3 -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git diff --dirstat=files --dirstat=10 --dirstat=cumulative --dirstat=changes --dirstat=noncumulative -X3 -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +test_expect_success 'non-defaults in config overridden by explicit defaults on command line' ' + git -c diff.dirstat=files,cumulative,50 diff --dirstat=changes,noncumulative,3 HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git -c diff.dirstat=files,cumulative,50 diff --dirstat=changes,noncumulative,3 -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git -c diff.dirstat=files,cumulative,50 diff --dirstat=changes,noncumulative,3 -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +cat <<EOF >expect_diff_dirstat + 2.1% changed/ + 10.8% dst/copy/changed/ + 10.8% dst/copy/rearranged/ + 10.8% dst/copy/unchanged/ + 10.8% dst/move/changed/ + 10.8% dst/move/rearranged/ + 10.8% dst/move/unchanged/ + 0.0% rearranged/ + 10.8% src/move/changed/ + 10.8% src/move/rearranged/ + 10.8% src/move/unchanged/ +EOF + +cat <<EOF >expect_diff_dirstat_M + 5.8% changed/ + 29.3% dst/copy/changed/ + 29.3% dst/copy/rearranged/ + 29.3% dst/copy/unchanged/ + 5.8% dst/move/changed/ + 0.1% dst/move/rearranged/ + 0.1% rearranged/ +EOF + +cat <<EOF >expect_diff_dirstat_CC + 32.6% changed/ + 32.6% dst/copy/changed/ + 0.6% dst/copy/rearranged/ + 32.6% dst/move/changed/ + 0.6% dst/move/rearranged/ + 0.6% rearranged/ +EOF + +test_expect_success '--dirstat=0' ' + git diff --dirstat=0 HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git diff --dirstat=0 -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git diff --dirstat=0 -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +test_expect_success '-X0' ' + git diff -X0 HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git diff -X0 -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git diff -X0 -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +test_expect_success 'diff.dirstat=0' ' + git -c diff.dirstat=0 diff --dirstat HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git -c diff.dirstat=0 diff --dirstat -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git -c diff.dirstat=0 diff --dirstat -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +cat <<EOF >expect_diff_dirstat + 2.1% changed/ + 10.8% dst/copy/changed/ + 10.8% dst/copy/rearranged/ + 10.8% dst/copy/unchanged/ + 32.5% dst/copy/ + 10.8% dst/move/changed/ + 10.8% dst/move/rearranged/ + 10.8% dst/move/unchanged/ + 32.5% dst/move/ + 65.1% dst/ + 0.0% rearranged/ + 10.8% src/move/changed/ + 10.8% src/move/rearranged/ + 10.8% src/move/unchanged/ + 32.5% src/move/ +EOF + +cat <<EOF >expect_diff_dirstat_M + 5.8% changed/ + 29.3% dst/copy/changed/ + 29.3% dst/copy/rearranged/ + 29.3% dst/copy/unchanged/ + 88.0% dst/copy/ + 5.8% dst/move/changed/ + 0.1% dst/move/rearranged/ + 5.9% dst/move/ + 94.0% dst/ + 0.1% rearranged/ +EOF + +cat <<EOF >expect_diff_dirstat_CC + 32.6% changed/ + 32.6% dst/copy/changed/ + 0.6% dst/copy/rearranged/ + 33.3% dst/copy/ + 32.6% dst/move/changed/ + 0.6% dst/move/rearranged/ + 33.3% dst/move/ + 66.6% dst/ + 0.6% rearranged/ +EOF + +test_expect_success '--dirstat=0 --cumulative' ' + git diff --dirstat=0 --cumulative HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git diff --dirstat=0 --cumulative -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git diff --dirstat=0 --cumulative -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +test_expect_success '--dirstat=0,cumulative' ' + git diff --dirstat=0,cumulative HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git diff --dirstat=0,cumulative -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git diff --dirstat=0,cumulative -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +test_expect_success '-X0,cumulative' ' + git diff -X0,cumulative HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git diff -X0,cumulative -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git diff -X0,cumulative -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +test_expect_success 'diff.dirstat=0,cumulative' ' + git -c diff.dirstat=0,cumulative diff --dirstat HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git -c diff.dirstat=0,cumulative diff --dirstat -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git -c diff.dirstat=0,cumulative diff --dirstat -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +test_expect_success 'diff.dirstat=0 & --dirstat=cumulative' ' + git -c diff.dirstat=0 diff --dirstat=cumulative HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git -c diff.dirstat=0 diff --dirstat=cumulative -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git -c diff.dirstat=0 diff --dirstat=cumulative -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +cat <<EOF >expect_diff_dirstat + 9.0% changed/ + 9.0% dst/copy/changed/ + 9.0% dst/copy/rearranged/ + 9.0% dst/copy/unchanged/ + 9.0% dst/move/changed/ + 9.0% dst/move/rearranged/ + 9.0% dst/move/unchanged/ + 9.0% rearranged/ + 9.0% src/move/changed/ + 9.0% src/move/rearranged/ + 9.0% src/move/unchanged/ +EOF + +cat <<EOF >expect_diff_dirstat_M + 14.2% changed/ + 14.2% dst/copy/changed/ + 14.2% dst/copy/rearranged/ + 14.2% dst/copy/unchanged/ + 14.2% dst/move/changed/ + 14.2% dst/move/rearranged/ + 14.2% rearranged/ +EOF + +cat <<EOF >expect_diff_dirstat_CC + 16.6% changed/ + 16.6% dst/copy/changed/ + 16.6% dst/copy/rearranged/ + 16.6% dst/move/changed/ + 16.6% dst/move/rearranged/ + 16.6% rearranged/ +EOF + +test_expect_success '--dirstat-by-file' ' + git diff --dirstat-by-file HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git diff --dirstat-by-file -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git diff --dirstat-by-file -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +test_expect_success '--dirstat=files' ' + git diff --dirstat=files HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git diff --dirstat=files -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git diff --dirstat=files -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +test_expect_success 'diff.dirstat=files' ' + git -c diff.dirstat=files diff --dirstat HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git -c diff.dirstat=files diff --dirstat -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git -c diff.dirstat=files diff --dirstat -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +cat <<EOF >expect_diff_dirstat + 27.2% dst/copy/ + 27.2% dst/move/ + 27.2% src/move/ +EOF + +cat <<EOF >expect_diff_dirstat_M + 14.2% changed/ + 14.2% dst/copy/changed/ + 14.2% dst/copy/rearranged/ + 14.2% dst/copy/unchanged/ + 14.2% dst/move/changed/ + 14.2% dst/move/rearranged/ + 14.2% rearranged/ +EOF + +cat <<EOF >expect_diff_dirstat_CC + 16.6% changed/ + 16.6% dst/copy/changed/ + 16.6% dst/copy/rearranged/ + 16.6% dst/move/changed/ + 16.6% dst/move/rearranged/ + 16.6% rearranged/ +EOF + +test_expect_success '--dirstat-by-file=10' ' + git diff --dirstat-by-file=10 HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git diff --dirstat-by-file=10 -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git diff --dirstat-by-file=10 -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +test_expect_success '--dirstat=files,10' ' + git diff --dirstat=files,10 HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git diff --dirstat=files,10 -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git diff --dirstat=files,10 -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +test_expect_success 'diff.dirstat=10,files' ' + git -c diff.dirstat=10,files diff --dirstat HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git -c diff.dirstat=10,files diff --dirstat -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git -c diff.dirstat=10,files diff --dirstat -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +cat <<EOF >expect_diff_dirstat + 9.0% changed/ + 9.0% dst/copy/changed/ + 9.0% dst/copy/rearranged/ + 9.0% dst/copy/unchanged/ + 27.2% dst/copy/ + 9.0% dst/move/changed/ + 9.0% dst/move/rearranged/ + 9.0% dst/move/unchanged/ + 27.2% dst/move/ + 54.5% dst/ + 9.0% rearranged/ + 9.0% src/move/changed/ + 9.0% src/move/rearranged/ + 9.0% src/move/unchanged/ + 27.2% src/move/ +EOF + +cat <<EOF >expect_diff_dirstat_M + 14.2% changed/ + 14.2% dst/copy/changed/ + 14.2% dst/copy/rearranged/ + 14.2% dst/copy/unchanged/ + 42.8% dst/copy/ + 14.2% dst/move/changed/ + 14.2% dst/move/rearranged/ + 28.5% dst/move/ + 71.4% dst/ + 14.2% rearranged/ +EOF + +cat <<EOF >expect_diff_dirstat_CC + 16.6% changed/ + 16.6% dst/copy/changed/ + 16.6% dst/copy/rearranged/ + 33.3% dst/copy/ + 16.6% dst/move/changed/ + 16.6% dst/move/rearranged/ + 33.3% dst/move/ + 66.6% dst/ + 16.6% rearranged/ +EOF + +test_expect_success '--dirstat-by-file --cumulative' ' + git diff --dirstat-by-file --cumulative HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git diff --dirstat-by-file --cumulative -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git diff --dirstat-by-file --cumulative -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +test_expect_success '--dirstat=files,cumulative' ' + git diff --dirstat=files,cumulative HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git diff --dirstat=files,cumulative -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git diff --dirstat=files,cumulative -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +test_expect_success 'diff.dirstat=cumulative,files' ' + git -c diff.dirstat=cumulative,files diff --dirstat HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git -c diff.dirstat=cumulative,files diff --dirstat -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git -c diff.dirstat=cumulative,files diff --dirstat -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +cat <<EOF >expect_diff_dirstat + 27.2% dst/copy/ + 27.2% dst/move/ + 54.5% dst/ + 27.2% src/move/ +EOF + +cat <<EOF >expect_diff_dirstat_M + 14.2% changed/ + 14.2% dst/copy/changed/ + 14.2% dst/copy/rearranged/ + 14.2% dst/copy/unchanged/ + 42.8% dst/copy/ + 14.2% dst/move/changed/ + 14.2% dst/move/rearranged/ + 28.5% dst/move/ + 71.4% dst/ + 14.2% rearranged/ +EOF + +cat <<EOF >expect_diff_dirstat_CC + 16.6% changed/ + 16.6% dst/copy/changed/ + 16.6% dst/copy/rearranged/ + 33.3% dst/copy/ + 16.6% dst/move/changed/ + 16.6% dst/move/rearranged/ + 33.3% dst/move/ + 66.6% dst/ + 16.6% rearranged/ +EOF + +test_expect_success '--dirstat=files,cumulative,10' ' + git diff --dirstat=files,cumulative,10 HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git diff --dirstat=files,cumulative,10 -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git diff --dirstat=files,cumulative,10 -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +test_expect_success 'diff.dirstat=10,cumulative,files' ' + git -c diff.dirstat=10,cumulative,files diff --dirstat HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git -c diff.dirstat=10,cumulative,files diff --dirstat -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git -c diff.dirstat=10,cumulative,files diff --dirstat -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +cat <<EOF >expect_diff_dirstat + 27.2% dst/copy/ + 27.2% dst/move/ + 54.5% dst/ + 27.2% src/move/ +EOF + +cat <<EOF >expect_diff_dirstat_M + 42.8% dst/copy/ + 28.5% dst/move/ + 71.4% dst/ +EOF + +cat <<EOF >expect_diff_dirstat_CC + 33.3% dst/copy/ + 33.3% dst/move/ + 66.6% dst/ +EOF + +test_expect_success '--dirstat=files,cumulative,16.7' ' + git diff --dirstat=files,cumulative,16.7 HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git diff --dirstat=files,cumulative,16.7 -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git diff --dirstat=files,cumulative,16.7 -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +test_expect_success 'diff.dirstat=16.7,cumulative,files' ' + git -c diff.dirstat=16.7,cumulative,files diff --dirstat HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git -c diff.dirstat=16.7,cumulative,files diff --dirstat -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git -c diff.dirstat=16.7,cumulative,files diff --dirstat -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +test_expect_success 'diff.dirstat=16.70,cumulative,files' ' + git -c diff.dirstat=16.70,cumulative,files diff --dirstat HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git -c diff.dirstat=16.70,cumulative,files diff --dirstat -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git -c diff.dirstat=16.70,cumulative,files diff --dirstat -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +test_expect_success '--dirstat=files,cumulative,27.2' ' + git diff --dirstat=files,cumulative,27.2 HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git diff --dirstat=files,cumulative,27.2 -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git diff --dirstat=files,cumulative,27.2 -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +test_expect_success '--dirstat=files,cumulative,27.09' ' + git diff --dirstat=files,cumulative,27.09 HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git diff --dirstat=files,cumulative,27.09 -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git diff --dirstat=files,cumulative,27.09 -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +cat <<EOF >expect_diff_dirstat + 10.6% dst/copy/changed/ + 10.6% dst/copy/rearranged/ + 10.6% dst/copy/unchanged/ + 10.6% dst/move/changed/ + 10.6% dst/move/rearranged/ + 10.6% dst/move/unchanged/ + 10.6% src/move/changed/ + 10.6% src/move/rearranged/ + 10.6% src/move/unchanged/ +EOF + +cat <<EOF >expect_diff_dirstat_M + 5.2% changed/ + 26.3% dst/copy/changed/ + 26.3% dst/copy/rearranged/ + 26.3% dst/copy/unchanged/ + 5.2% dst/move/changed/ + 5.2% dst/move/rearranged/ + 5.2% rearranged/ +EOF + +cat <<EOF >expect_diff_dirstat_CC + 16.6% changed/ + 16.6% dst/copy/changed/ + 16.6% dst/copy/rearranged/ + 16.6% dst/move/changed/ + 16.6% dst/move/rearranged/ + 16.6% rearranged/ +EOF + +test_expect_success '--dirstat=lines' ' + git diff --dirstat=lines HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git diff --dirstat=lines -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git diff --dirstat=lines -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +test_expect_success 'diff.dirstat=lines' ' + git -c diff.dirstat=lines diff --dirstat HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git -c diff.dirstat=lines diff --dirstat -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git -c diff.dirstat=lines diff --dirstat -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +cat <<EOF >expect_diff_dirstat + 2.1% changed/ + 10.6% dst/copy/changed/ + 10.6% dst/copy/rearranged/ + 10.6% dst/copy/unchanged/ + 10.6% dst/move/changed/ + 10.6% dst/move/rearranged/ + 10.6% dst/move/unchanged/ + 2.1% rearranged/ + 10.6% src/move/changed/ + 10.6% src/move/rearranged/ + 10.6% src/move/unchanged/ +EOF + +cat <<EOF >expect_diff_dirstat_M + 5.2% changed/ + 26.3% dst/copy/changed/ + 26.3% dst/copy/rearranged/ + 26.3% dst/copy/unchanged/ + 5.2% dst/move/changed/ + 5.2% dst/move/rearranged/ + 5.2% rearranged/ +EOF + +cat <<EOF >expect_diff_dirstat_CC + 16.6% changed/ + 16.6% dst/copy/changed/ + 16.6% dst/copy/rearranged/ + 16.6% dst/move/changed/ + 16.6% dst/move/rearranged/ + 16.6% rearranged/ +EOF + +test_expect_success '--dirstat=lines,0' ' + git diff --dirstat=lines,0 HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git diff --dirstat=lines,0 -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git diff --dirstat=lines,0 -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +test_expect_success 'diff.dirstat=0,lines' ' + git -c diff.dirstat=0,lines diff --dirstat HEAD^..HEAD >actual_diff_dirstat && + test_cmp expect_diff_dirstat actual_diff_dirstat && + git -c diff.dirstat=0,lines diff --dirstat -M HEAD^..HEAD >actual_diff_dirstat_M && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + git -c diff.dirstat=0,lines diff --dirstat -C -C HEAD^..HEAD >actual_diff_dirstat_CC && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC +' + +test_expect_success '--dirstat=future_param,lines,0 should fail loudly' ' + test_must_fail git diff --dirstat=future_param,lines,0 HEAD^..HEAD >actual_diff_dirstat 2>actual_error && + test_debug "cat actual_error" && + test_cmp /dev/null actual_diff_dirstat && + test_i18ngrep -q "future_param" actual_error && + test_i18ngrep -q "\--dirstat" actual_error +' + +test_expect_success '--dirstat=dummy1,cumulative,2dummy should report both unrecognized parameters' ' + test_must_fail git diff --dirstat=dummy1,cumulative,2dummy HEAD^..HEAD >actual_diff_dirstat 2>actual_error && + test_debug "cat actual_error" && + test_cmp /dev/null actual_diff_dirstat && + test_i18ngrep -q "dummy1" actual_error && + test_i18ngrep -q "2dummy" actual_error && + test_i18ngrep -q "\--dirstat" actual_error +' + +test_expect_success 'diff.dirstat=future_param,0,lines should warn, but still work' ' + git -c diff.dirstat=future_param,0,lines diff --dirstat HEAD^..HEAD >actual_diff_dirstat 2>actual_error && + test_debug "cat actual_error" && + test_cmp expect_diff_dirstat actual_diff_dirstat && + test_i18ngrep -q "future_param" actual_error && + test_i18ngrep -q "diff\\.dirstat" actual_error && + + git -c diff.dirstat=future_param,0,lines diff --dirstat -M HEAD^..HEAD >actual_diff_dirstat_M 2>actual_error && + test_debug "cat actual_error" && + test_cmp expect_diff_dirstat_M actual_diff_dirstat_M && + test_i18ngrep -q "future_param" actual_error && + test_i18ngrep -q "diff\\.dirstat" actual_error && + + git -c diff.dirstat=future_param,0,lines diff --dirstat -C -C HEAD^..HEAD >actual_diff_dirstat_CC 2>actual_error && + test_debug "cat actual_error" && + test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC && + test_i18ngrep -q "future_param" actual_error && + test_i18ngrep -q "diff\\.dirstat" actual_error +' + +test_done diff --git a/t/t5526-fetch-submodules.sh b/t/t5526-fetch-submodules.sh index af78e21ba9..a1fddd4d15 100755 --- a/t/t5526-fetch-submodules.sh +++ b/t/t5526-fetch-submodules.sh @@ -66,12 +66,9 @@ test_expect_success "fetch --recurse-submodules recurses into submodules" ' ( cd downstream && git fetch --recurse-submodules >../actual.out 2>../actual.err - ) -' - -test_expect_success C_LOCALE_OUTPUT "fetch --recurse-submodules recurses into submodules: output" ' - test_cmp expect.out actual.out && - test_cmp expect.err actual.err + ) && + test_i18ncmp expect.out actual.out && + test_i18ncmp expect.err actual.err ' test_expect_success "fetch alone only fetches superproject" ' @@ -98,12 +95,9 @@ test_expect_success "using fetchRecurseSubmodules=true in .gitmodules recurses i cd downstream && git config -f .gitmodules submodule.submodule.fetchRecurseSubmodules true && git fetch >../actual.out 2>../actual.err - ) -' - -test_expect_success C_LOCALE_OUTPUT "using fetchRecurseSubmodules=true in .gitmodules recurses into submodules" ' - test_cmp expect.out actual.out && - test_cmp expect.err actual.err + ) && + test_i18ncmp expect.out actual.out && + test_i18ncmp expect.err actual.err ' test_expect_success "--no-recurse-submodules overrides .gitmodules config" ' @@ -132,12 +126,9 @@ test_expect_success "--recurse-submodules overrides fetchRecurseSubmodules setti git fetch --recurse-submodules >../actual.out 2>../actual.err && git config --unset -f .gitmodules submodule.submodule.fetchRecurseSubmodules && git config --unset submodule.submodule.fetchRecurseSubmodules - ) -' - -test_expect_success C_LOCALE_OUTPUT "--recurse-submodules overrides fetchRecurseSubmodules setting from .git/config: output" ' - test_cmp expect.out actual.out && - test_cmp expect.err actual.err + ) && + test_i18ncmp expect.out actual.out && + test_i18ncmp expect.err actual.err ' test_expect_success "--quiet propagates to submodules" ' @@ -154,24 +145,18 @@ test_expect_success "--dry-run propagates to submodules" ' ( cd downstream && git fetch --recurse-submodules --dry-run >../actual.out 2>../actual.err - ) -' - -test_expect_success C_LOCALE_OUTPUT "--dry-run propagates to submodules: output" ' - test_cmp expect.out actual.out && - test_cmp expect.err actual.err + ) && + test_i18ncmp expect.out actual.out && + test_i18ncmp expect.err actual.err ' test_expect_success "Without --dry-run propagates to submodules" ' ( cd downstream && git fetch --recurse-submodules >../actual.out 2>../actual.err - ) -' - -test_expect_success C_LOCALE_OUTPUT "Without --dry-run propagates to submodules: output" ' - test_cmp expect.out actual.out && - test_cmp expect.err actual.err + ) && + test_i18ncmp expect.out actual.out && + test_i18ncmp expect.err actual.err ' test_expect_success "recurseSubmodules=true propagates into submodules" ' @@ -180,12 +165,9 @@ test_expect_success "recurseSubmodules=true propagates into submodules" ' cd downstream && git config fetch.recurseSubmodules true git fetch >../actual.out 2>../actual.err - ) -' - -test_expect_success C_LOCALE_OUTPUT "recurseSubmodules=true propagates into submodules: output" ' - test_cmp expect.out actual.out && - test_cmp expect.err actual.err + ) && + test_i18ncmp expect.out actual.out && + test_i18ncmp expect.err actual.err ' test_expect_success "--recurse-submodules overrides config in submodule" ' @@ -197,12 +179,9 @@ test_expect_success "--recurse-submodules overrides config in submodule" ' git config fetch.recurseSubmodules false ) && git fetch --recurse-submodules >../actual.out 2>../actual.err - ) -' - -test_expect_success C_LOCALE_OUTPUT "--recurse-submodules overrides config in submodule: output" ' - test_cmp expect.out actual.out && - test_cmp expect.err actual.err + ) && + test_i18ncmp expect.out actual.out && + test_i18ncmp expect.err actual.err ' test_expect_success "--no-recurse-submodules overrides config setting" ' @@ -243,8 +222,8 @@ test_expect_success "Recursion stops when no new submodule commits are fetched" cd downstream && git fetch >../actual.out 2>../actual.err ) && - test_cmp expect.err.sub actual.err && - test_cmp expect.out.sub actual.out + test_i18ncmp expect.err.sub actual.err && + test_i18ncmp expect.out.sub actual.out ' test_expect_success "Recursion doesn't happen when new superproject commits don't change any submodules" ' @@ -261,7 +240,7 @@ test_expect_success "Recursion doesn't happen when new superproject commits don' git fetch >../actual.out 2>../actual.err ) && ! test -s actual.out && - test_cmp expect.err.file actual.err + test_i18ncmp expect.err.file actual.err ' test_expect_success "Recursion picks up config in submodule" ' @@ -289,8 +268,8 @@ test_expect_success "Recursion picks up config in submodule" ' git config --unset fetch.recurseSubmodules ) ) && - test_cmp expect.err.sub actual.err && - test_cmp expect.out actual.out + test_i18ncmp expect.err.sub actual.err && + test_i18ncmp expect.out actual.out ' test_expect_success "Recursion picks up all submodules when necessary" ' @@ -321,8 +300,8 @@ test_expect_success "Recursion picks up all submodules when necessary" ' cd downstream && git fetch >../actual.out 2>../actual.err ) && - test_cmp expect.err.2 actual.err && - test_cmp expect.out actual.out + test_i18ncmp expect.err.2 actual.err && + test_i18ncmp expect.out actual.out ' test_expect_success "'--recurse-submodules=on-demand' doesn't recurse when no new commits are fetched in the superproject (and ignores config)" ' @@ -375,8 +354,8 @@ test_expect_success "'--recurse-submodules=on-demand' recurses as deep as necess git config --unset -f .gitmodules submodule.deepsubmodule.fetchRecursive ) ) && - test_cmp expect.out actual.out && - test_cmp expect.err actual.err + test_i18ncmp expect.out actual.out && + test_i18ncmp expect.err actual.err ' test_expect_success "'--recurse-submodules=on-demand' stops when no new submodule commits are found in the superproject (and ignores config)" ' @@ -393,7 +372,7 @@ test_expect_success "'--recurse-submodules=on-demand' stops when no new submodul git fetch --recurse-submodules=on-demand >../actual.out 2>../actual.err ) && ! test -s actual.out && - test_cmp expect.err.file actual.err + test_i18ncmp expect.err.file actual.err ' test_expect_success "'fetch.recurseSubmodules=on-demand' overrides global config" ' @@ -420,8 +399,8 @@ test_expect_success "'fetch.recurseSubmodules=on-demand' overrides global config cd downstream && git config --unset fetch.recurseSubmodules ) && - test_cmp expect.out.sub actual.out && - test_cmp expect.err.2 actual.err + test_i18ncmp expect.out.sub actual.out && + test_i18ncmp expect.err.2 actual.err ' test_expect_success "'submodule.<sub>.fetchRecurseSubmodules=on-demand' overrides fetch.recurseSubmodules" ' @@ -448,8 +427,8 @@ test_expect_success "'submodule.<sub>.fetchRecurseSubmodules=on-demand' override cd downstream && git config --unset submodule.submodule.fetchRecurseSubmodules ) && - test_cmp expect.out.sub actual.out && - test_cmp expect.err.2 actual.err + test_i18ncmp expect.out.sub actual.out && + test_i18ncmp expect.err.2 actual.err ' test_expect_success "don't fetch submodule when newly recorded commits are already present" ' @@ -468,7 +447,7 @@ test_expect_success "don't fetch submodule when newly recorded commits are alrea git fetch >../actual.out 2>../actual.err ) && ! test -s actual.out && - test_cmp expect.err actual.err + test_i18ncmp expect.err actual.err ' test_done diff --git a/t/t5541-http-push.sh b/t/t5541-http-push.sh index 0492877d51..a73c82635f 100755 --- a/t/t5541-http-push.sh +++ b/t/t5541-http-push.sh @@ -65,14 +65,16 @@ test_expect_success 'clone remote repository' ' git clone $HTTPD_URL/smart/test_repo.git test_repo_clone ' -test_expect_success 'push to remote repository' ' +test_expect_success 'push to remote repository (standard)' ' cd "$ROOT_PATH"/test_repo_clone && : >path2 && git add path2 && test_tick && git commit -m path2 && HEAD=$(git rev-parse --verify HEAD) && - git push && + GIT_CURL_VERBOSE=1 git push -v -v 2>err && + ! grep "Expect: 100-continue" err && + grep "POST git-receive-pack ([0-9]* bytes)" err && (cd "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git && test $HEAD = $(git rev-parse --verify HEAD)) ' @@ -135,10 +137,22 @@ test_expect_success 'push fails for non-fast-forward refs unmatched by remote he grep "^ ! \[rejected\] *master -> retsam (non-fast-forward)$" output ' -test_expect_success C_LOCALE_OUTPUT 'push fails for non-fast-forward refs unmatched by remote helper: our output' ' - grep "To prevent you from losing history, non-fast-forward updates were rejected" \ +test_expect_success 'push fails for non-fast-forward refs unmatched by remote helper: our output' ' + test_i18ngrep "To prevent you from losing history, non-fast-forward updates were rejected" \ output ' +test_expect_success 'push (chunked)' ' + git checkout master && + test_commit commit path3 && + HEAD=$(git rev-parse --verify HEAD) && + git config http.postbuffer 4 && + test_when_finished "git config --unset http.postbuffer" && + git push -v -v origin $BRANCH 2>err && + grep "POST git-receive-pack (chunked)" err && + (cd "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git && + test $HEAD = $(git rev-parse --verify HEAD)) +' + stop_httpd test_done diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh index 5a068b21e4..151ea531bd 100755 --- a/t/t5601-clone.sh +++ b/t/t5601-clone.sh @@ -194,11 +194,14 @@ test_expect_success 'do not respect url-encoding of non-url path' ' test_expect_success 'clone separate gitdir' ' rm -rf dst && git clone --separate-git-dir realgitdir src dst && - echo "gitdir: `pwd`/realgitdir" >expected && - test_cmp expected dst/.git && test -d realgitdir/refs ' +test_expect_success 'clone separate gitdir: output' ' + echo "gitdir: `pwd`/realgitdir" >expected && + test_cmp expected dst/.git +' + test_expect_success 'clone separate gitdir where target already exists' ' rm -rf dst && test_must_fail git clone --separate-git-dir realgitdir src dst diff --git a/t/t6007-rev-list-cherry-pick-file.sh b/t/t6007-rev-list-cherry-pick-file.sh index cacf3de6c9..28d4f6b259 100755 --- a/t/t6007-rev-list-cherry-pick-file.sh +++ b/t/t6007-rev-list-cherry-pick-file.sh @@ -157,6 +157,33 @@ test_expect_success '--cherry' ' test_cmp actual.named expect ' +cat >expect <<EOF +1 1 +EOF + +test_expect_success '--cherry --count' ' + git rev-list --cherry --count F...E -- bar > actual && + test_cmp actual expect +' + +cat >expect <<EOF +2 2 +EOF + +test_expect_success '--cherry-mark --count' ' + git rev-list --cherry-mark --count F...E -- bar > actual && + test_cmp actual expect +' + +cat >expect <<EOF +1 1 2 +EOF + +test_expect_success '--cherry-mark --left-right --count' ' + git rev-list --cherry-mark --left-right --count F...E -- bar > actual && + test_cmp actual expect +' + test_expect_success '--cherry-pick with independent, but identical branches' ' git symbolic-ref HEAD refs/heads/independent && rm .git/index && diff --git a/t/t6010-merge-base.sh b/t/t6010-merge-base.sh index 082032edc3..f80bba871c 100755 --- a/t/t6010-merge-base.sh +++ b/t/t6010-merge-base.sh @@ -8,38 +8,38 @@ test_description='Merge base and parent list computation. . ./test-lib.sh -test_expect_success 'setup' ' - T=$(git write-tree) && +M=1130000000 +Z=+0000 - M=1130000000 && - Z=+0000 && +GIT_COMMITTER_EMAIL=git@comm.iter.xz +GIT_COMMITTER_NAME='C O Mmiter' +GIT_AUTHOR_NAME='A U Thor' +GIT_AUTHOR_EMAIL=git@au.thor.xz +export GIT_COMMITTER_EMAIL GIT_COMMITTER_NAME GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL - GIT_COMMITTER_EMAIL=git@comm.iter.xz && - GIT_COMMITTER_NAME="C O Mmiter" && - GIT_AUTHOR_NAME="A U Thor" && - GIT_AUTHOR_EMAIL=git@au.thor.xz && - export GIT_COMMITTER_EMAIL GIT_COMMITTER_NAME GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL && +doit () { + OFFSET=$1 && + NAME=$2 && + shift 2 && - doit() { - OFFSET=$1 && - NAME=$2 && - shift 2 && + PARENTS= && + for P + do + PARENTS="${PARENTS}-p $P " + done && - PARENTS= && - for P - do - PARENTS="${PARENTS}-p $P " - done && + GIT_COMMITTER_DATE="$(($M + $OFFSET)) $Z" && + GIT_AUTHOR_DATE=$GIT_COMMITTER_DATE && + export GIT_COMMITTER_DATE GIT_AUTHOR_DATE && - GIT_COMMITTER_DATE="$(($M + $OFFSET)) $Z" && - GIT_AUTHOR_DATE=$GIT_COMMITTER_DATE && - export GIT_COMMITTER_DATE GIT_AUTHOR_DATE && + commit=$(echo $NAME | git commit-tree $T $PARENTS) && - commit=$(echo $NAME | git commit-tree $T $PARENTS) && + echo $commit >.git/refs/tags/$NAME && + echo $commit +} - echo $commit >.git/refs/tags/$NAME && - echo $commit - } +test_expect_success 'setup' ' + T=$(git mktree </dev/null) ' test_expect_success 'set up G and H' ' diff --git a/t/t6018-rev-list-glob.sh b/t/t6018-rev-list-glob.sh index fb8291c812..f00cebff3e 100755 --- a/t/t6018-rev-list-glob.sh +++ b/t/t6018-rev-list-glob.sh @@ -69,6 +69,18 @@ test_expect_success 'rev-parse --glob=heads/subspace' ' ' +test_expect_failure 'rev-parse accepts --glob as detached option' ' + + compare rev-parse "subspace/one subspace/two" "--glob heads/subspace" + +' + +test_expect_failure 'rev-parse is not confused by option-like glob' ' + + compare rev-parse "master" "--glob --symbolic master" + +' + test_expect_success 'rev-parse --branches=subspace/*' ' compare rev-parse "subspace/one subspace/two" "--branches=subspace/*" @@ -129,6 +141,12 @@ test_expect_success 'rev-list --glob refs/heads/subspace/*' ' ' +test_expect_success 'rev-list not confused by option-like --glob arg' ' + + compare rev-list "master" "--glob -0 master" + +' + test_expect_success 'rev-list --glob=heads/subspace/*' ' compare rev-list "subspace/one subspace/two" "--glob=heads/subspace/*" @@ -213,4 +231,36 @@ test_expect_success 'rev-list --remotes=foo' ' ' +test_expect_success 'shortlog accepts --glob/--tags/--remotes' ' + + compare shortlog "subspace/one subspace/two" --branches=subspace && + compare shortlog \ + "master subspace-x someref other/three subspace/one subspace/two" \ + --branches && + compare shortlog master "--glob=heads/someref/* master" && + compare shortlog "subspace/one subspace/two other/three" \ + "--glob=heads/subspace/* --glob=heads/other/*" && + compare shortlog \ + "master other/three someref subspace-x subspace/one subspace/two" \ + "--glob=heads/*" && + compare shortlog foo/bar --tags=foo && + compare shortlog foo/bar --tags && + compare shortlog foo/baz --remotes=foo + +' + +test_expect_failure 'shortlog accepts --glob as detached option' ' + + compare shortlog \ + "master other/three someref subspace-x subspace/one subspace/two" \ + "--glob heads/*" + +' + +test_expect_failure 'shortlog --glob is not confused by option-like argument' ' + + compare shortlog master "--glob -e master" + +' + test_done diff --git a/t/t6022-merge-rename.sh b/t/t6022-merge-rename.sh index 1ed259d864..5f3b604fd9 100755 --- a/t/t6022-merge-rename.sh +++ b/t/t6022-merge-rename.sh @@ -609,4 +609,67 @@ test_expect_success 'check handling of differently renamed file with D/F conflic ! test -f original ' +test_expect_success 'setup avoid unnecessary update, normal rename' ' + git reset --hard && + git checkout --orphan avoid-unnecessary-update-1 && + git rm -rf . && + git clean -fdqx && + + printf "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n" >original && + git add -A && + git commit -m "Common commmit" && + + git mv original rename && + echo 11 >>rename && + git add -u && + git commit -m "Renamed and modified" && + + git checkout -b merge-branch-1 HEAD~1 && + echo "random content" >random-file && + git add -A && + git commit -m "Random, unrelated changes" +' + +test_expect_success 'avoid unnecessary update, normal rename' ' + git checkout -q avoid-unnecessary-update-1^0 && + test-chmtime =1000000000 rename && + test-chmtime -v +0 rename >expect && + git merge merge-branch-1 && + test-chmtime -v +0 rename >actual && + test_cmp expect actual # "rename" should have stayed intact +' + +test_expect_success 'setup to test avoiding unnecessary update, with D/F conflict' ' + git reset --hard && + git checkout --orphan avoid-unnecessary-update-2 && + git rm -rf . && + git clean -fdqx && + + mkdir df && + printf "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n" >df/file && + git add -A && + git commit -m "Common commmit" && + + git mv df/file temp && + rm -rf df && + git mv temp df && + echo 11 >>df && + git add -u && + git commit -m "Renamed and modified" && + + git checkout -b merge-branch-2 HEAD~1 && + >unrelated-change && + git add unrelated-change && + git commit -m "Only unrelated changes" +' + +test_expect_failure 'avoid unnecessary update, with D/F conflict' ' + git checkout -q avoid-unnecessary-update-2^0 && + test-chmtime =1000000000 df && + test-chmtime -v +0 df >expect && + git merge merge-branch-2 && + test-chmtime -v +0 df >actual && + test_cmp expect actual # "df" should have stayed intact +' + test_done diff --git a/t/t6040-tracking-info.sh b/t/t6040-tracking-info.sh index 6c3719141a..a9b0ac1efc 100755 --- a/t/t6040-tracking-info.sh +++ b/t/t6040-tracking-info.sh @@ -42,13 +42,13 @@ b3 behind 1 b4 ahead 2 EOF -test_expect_success C_LOCALE_OUTPUT 'branch -v' ' +test_expect_success 'branch -v' ' ( cd test && git branch -v ) | sed -n -e "$script" >actual && - test_cmp expect actual + test_i18ncmp expect actual ' test_expect_success 'checkout' ' diff --git a/t/t6120-describe.sh b/t/t6120-describe.sh index 1826996245..f67aa6ff6a 100755 --- a/t/t6120-describe.sh +++ b/t/t6120-describe.sh @@ -123,8 +123,8 @@ cat - >err.expect <<EOF warning: tag 'A' is really 'Q' here EOF check_describe A-* HEAD -test_expect_success C_LOCALE_OUTPUT 'warning was displayed for Q' ' - test_cmp err.expect err.actual +test_expect_success 'warning was displayed for Q' ' + test_i18ncmp err.expect err.actual ' test_expect_success 'rename tag Q back to A' ' mv .git/refs/tags/Q .git/refs/tags/A diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh index 1dedfd0c83..2ac1c66079 100755 --- a/t/t7004-tag.sh +++ b/t/t7004-tag.sh @@ -1120,13 +1120,11 @@ test_expect_success \ ! (GIT_EDITOR=cat git tag -a initial-comment > actual) ' -test_expect_success \ - C_LOCALE_OUTPUT \ - 'message in editor has initial comment: first line' ' +test_expect_success 'message in editor has initial comment: first line' ' # check the first line --- should be empty echo >first.expect && sed -e 1q <actual >first.actual && - test_cmp first.expect first.actual + test_i18ncmp first.expect first.actual ' test_expect_success \ diff --git a/t/t7012-skip-worktree-writing.sh b/t/t7012-skip-worktree-writing.sh index c4104009e1..9ceaa4049f 100755 --- a/t/t7012-skip-worktree-writing.sh +++ b/t/t7012-skip-worktree-writing.sh @@ -124,16 +124,16 @@ cat >expected <<EOF Would remove expected Would remove result EOF -test_expect_success C_LOCALE_OUTPUT 'git-clean, absent case' ' +test_expect_success 'git-clean, absent case' ' setup_absent && git clean -n > result && - test_cmp expected result + test_i18ncmp expected result ' -test_expect_success C_LOCALE_OUTPUT 'git-clean, dirty case' ' +test_expect_success 'git-clean, dirty case' ' setup_dirty && git clean -n > result && - test_cmp expected result + test_i18ncmp expected result ' #TODO test_expect_failure 'git-apply adds file' false diff --git a/t/t7060-wtstatus.sh b/t/t7060-wtstatus.sh index 3a5d927f83..b8cb4906aa 100755 --- a/t/t7060-wtstatus.sh +++ b/t/t7060-wtstatus.sh @@ -38,7 +38,7 @@ cat >expect <<EOF no changes added to commit (use "git add" and/or "git commit -a") EOF -test_expect_success C_LOCALE_OUTPUT 'M/D conflict does not segfault' ' +test_expect_success 'M/D conflict does not segfault' ' mkdir mdconflict && ( cd mdconflict && @@ -50,9 +50,9 @@ test_expect_success C_LOCALE_OUTPUT 'M/D conflict does not segfault' ' git commit -m delete && test_must_fail git merge master && test_must_fail git commit --dry-run >../actual && - test_cmp ../expect ../actual && + test_i18ncmp ../expect ../actual && git status >../actual && - test_cmp ../expect ../actual + test_i18ncmp ../expect ../actual ) ' diff --git a/t/t7102-reset.sh b/t/t7102-reset.sh index 7be2ff38fc..f1cfc9ac95 100755 --- a/t/t7102-reset.sh +++ b/t/t7102-reset.sh @@ -423,10 +423,10 @@ Unstaged changes after reset: M file2 EOF -test_expect_success C_LOCALE_OUTPUT '--mixed refreshes the index' ' +test_expect_success '--mixed refreshes the index' ' echo 123 >> file2 && git reset --mixed HEAD > output && - test_cmp expect output + test_i18ncmp expect output ' test_expect_success 'disambiguation (1)' ' diff --git a/t/t7110-reset-merge.sh b/t/t7110-reset-merge.sh index b42820ad69..a82a07a04a 100755 --- a/t/t7110-reset-merge.sh +++ b/t/t7110-reset-merge.sh @@ -233,11 +233,11 @@ test_expect_success '"reset --merge HEAD^" is ok with pending merge' ' # working index HEAD target working index HEAD # ---------------------------------------------------- # file1: X U B C --keep (disallowed) -test_expect_success C_LOCALE_OUTPUT '"reset --keep HEAD^" fails with pending merge' ' +test_expect_success '"reset --keep HEAD^" fails with pending merge' ' git reset --hard third && test_must_fail git merge branch1 && test_must_fail git reset --keep HEAD^ 2>err.log && - grep "middle of a merge" err.log + test_i18ngrep "middle of a merge" err.log ' # The next test will test the following: @@ -259,11 +259,11 @@ test_expect_success '"reset --merge HEAD" is ok with pending merge' ' # working index HEAD target working index HEAD # ---------------------------------------------------- # file1: X U B B --keep (disallowed) -test_expect_success C_LOCALE_OUTPUT '"reset --keep HEAD" fails with pending merge' ' +test_expect_success '"reset --keep HEAD" fails with pending merge' ' git reset --hard third && test_must_fail git merge branch1 && test_must_fail git reset --keep HEAD 2>err.log && - grep "middle of a merge" err.log + test_i18ngrep "middle of a merge" err.log ' test_expect_success '--merge is ok with added/deleted merge' ' @@ -280,7 +280,7 @@ test_expect_success '--merge is ok with added/deleted merge' ' git diff --exit-code --cached ' -test_expect_success C_LOCALE_OUTPUT '--keep fails with added/deleted merge' ' +test_expect_success '--keep fails with added/deleted merge' ' git reset --hard third && rm -f file2 && test_must_fail git merge branch3 && @@ -289,7 +289,7 @@ test_expect_success C_LOCALE_OUTPUT '--keep fails with added/deleted merge' ' git diff --exit-code file3 && git diff --exit-code branch3 file3 && test_must_fail git reset --keep HEAD 2>err.log && - grep "middle of a merge" err.log + test_i18ngrep "middle of a merge" err.log ' test_done diff --git a/t/t7201-co.sh b/t/t7201-co.sh index 37ed0931d9..07fb53adcb 100755 --- a/t/t7201-co.sh +++ b/t/t7201-co.sh @@ -223,12 +223,12 @@ test_expect_success 'checkout --merge --conflict=diff3 <branch>' ' test_cmp two expect ' -test_expect_success C_LOCALE_OUTPUT 'checkout to detach HEAD (with advice declined)' ' +test_expect_success 'checkout to detach HEAD (with advice declined)' ' git config advice.detachedHead false && git checkout -f renamer && git clean -f && git checkout renamer^ 2>messages && - grep "HEAD is now at 7329388" messages && + test_i18ngrep "HEAD is now at 7329388" messages && test 1 -eq $(wc -l <messages) && H=$(git rev-parse --verify HEAD) && M=$(git show-ref -s --verify refs/heads/master) && @@ -242,11 +242,11 @@ test_expect_success C_LOCALE_OUTPUT 'checkout to detach HEAD (with advice declin fi ' -test_expect_success C_LOCALE_OUTPUT 'checkout to detach HEAD' ' +test_expect_success 'checkout to detach HEAD' ' git config advice.detachedHead true && git checkout -f renamer && git clean -f && git checkout renamer^ 2>messages && - grep "HEAD is now at 7329388" messages && + test_i18ngrep "HEAD is now at 7329388" messages && test 1 -lt $(wc -l <messages) && H=$(git rev-parse --verify HEAD) && M=$(git show-ref -s --verify refs/heads/master) && @@ -260,7 +260,7 @@ test_expect_success C_LOCALE_OUTPUT 'checkout to detach HEAD' ' fi ' -test_expect_success C_LOCALE_OUTPUT 'checkout to detach HEAD with branchname^' ' +test_expect_success 'checkout to detach HEAD with branchname^' ' git checkout -f master && git clean -f && git checkout renamer^ && @@ -276,7 +276,7 @@ test_expect_success C_LOCALE_OUTPUT 'checkout to detach HEAD with branchname^' ' fi ' -test_expect_success C_LOCALE_OUTPUT 'checkout to detach HEAD with :/message' ' +test_expect_success 'checkout to detach HEAD with :/message' ' git checkout -f master && git clean -f && git checkout ":/Initial" && @@ -292,7 +292,7 @@ test_expect_success C_LOCALE_OUTPUT 'checkout to detach HEAD with :/message' ' fi ' -test_expect_success C_LOCALE_OUTPUT 'checkout to detach HEAD with HEAD^0' ' +test_expect_success 'checkout to detach HEAD with HEAD^0' ' git checkout -f master && git clean -f && git checkout HEAD^0 && diff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh index bf7c788735..4f16fcce2b 100755 --- a/t/t7406-submodule-update.sh +++ b/t/t7406-submodule-update.sh @@ -94,6 +94,29 @@ test_expect_success 'submodule update does not fetch already present commits' ' ! test -s actual.err ' +test_expect_success 'submodule update should fail due to local changes' ' + (cd super/submodule && + git reset --hard HEAD~1 && + echo "local change" > file + ) && + (cd super && + (cd submodule && + compare_head + ) && + test_must_fail git submodule update submodule + ) +' +test_expect_success 'submodule update should throw away changes with --force ' ' + (cd super && + (cd submodule && + compare_head + ) && + git submodule update --force submodule && + cd submodule && + ! compare_head + ) +' + test_expect_success 'submodule update --rebase staying on master' ' (cd super/submodule && git checkout master diff --git a/t/t7500-commit.sh b/t/t7500-commit.sh index bcdf0847d0..1c908f4d39 100755 --- a/t/t7500-commit.sh +++ b/t/t7500-commit.sh @@ -15,7 +15,7 @@ commit_msg_is () { printf "%s" "$(git log --pretty=format:%s%b -1)" >$expect && printf "%s" "$1" >$actual && - test_cmp $expect $actual + test_i18ncmp $expect $actual } # A sanity check to see if commit is working at all. @@ -72,7 +72,7 @@ test_expect_success 'adding comments to a template should not commit' ' ) ' -test_expect_success C_LOCALE_OUTPUT 'adding real content to a template should commit' ' +test_expect_success 'adding real content to a template should commit' ' ( test_set_editor "$TEST_DIRECTORY"/t7500/add-content && git commit --template "$TEMPLATE" @@ -80,7 +80,7 @@ test_expect_success C_LOCALE_OUTPUT 'adding real content to a template should co commit_msg_is "template linecommit message" ' -test_expect_success C_LOCALE_OUTPUT '-t option should be short for --template' ' +test_expect_success '-t option should be short for --template' ' echo "short template" > "$TEMPLATE" && echo "new content" >> foo && git add foo && @@ -91,7 +91,7 @@ test_expect_success C_LOCALE_OUTPUT '-t option should be short for --template' ' commit_msg_is "short templatecommit message" ' -test_expect_success C_LOCALE_OUTPUT 'config-specified template should commit' ' +test_expect_success 'config-specified template should commit' ' echo "new template" > "$TEMPLATE" && git config commit.template "$TEMPLATE" && echo "more content" >> foo && @@ -123,6 +123,20 @@ test_expect_success 'commit message from file should override template' ' commit_msg_is "standard input msg" ' +cat >"$TEMPLATE" <<\EOF + + +### template + +EOF +test_expect_success 'commit message from template with whitespace issue' ' + echo "content galore" >>foo && + git add foo && + GIT_EDITOR="$TEST_DIRECTORY"/t7500/add-whitespaced-content git commit \ + --template "$TEMPLATE" && + commit_msg_is "commit message" +' + test_expect_success 'using alternate GIT_INDEX_FILE (1)' ' cp .git/index saved-index && @@ -290,7 +304,7 @@ test_expect_success 'commit --squash works with -c for same commit' ' commit_msg_is "squash! edited commit" ' -test_expect_success C_LOCALE_OUTPUT 'commit --squash works with editor' ' +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 && diff --git a/t/t7500/add-whitespaced-content b/t/t7500/add-whitespaced-content new file mode 100755 index 0000000000..ccf07c61a4 --- /dev/null +++ b/t/t7500/add-whitespaced-content @@ -0,0 +1,8 @@ +#!/bin/sh +sed -e 's/|$//' >>"$1" <<\EOF + + | +commit message | + +EOF +exit 0 diff --git a/t/t7501-commit.sh b/t/t7501-commit.sh index a76c474195..7f7f7c7b95 100755 --- a/t/t7501-commit.sh +++ b/t/t7501-commit.sh @@ -16,9 +16,10 @@ test_expect_success \ "echo 'bongo bongo' >file && git add file" -test_expect_success C_LOCALE_OUTPUT \ - "Constructing initial commit" \ - "git status | grep 'Initial commit'" +test_expect_success "Constructing initial commit" ' + git status >actual && + test_i18ngrep "Initial commit" actual +' test_expect_success \ "fail initial amend" \ diff --git a/t/t7502-commit.sh b/t/t7502-commit.sh index cfb569eaba..3f3adc31b9 100755 --- a/t/t7502-commit.sh +++ b/t/t7502-commit.sh @@ -22,10 +22,7 @@ check_summary_oneline() { SUMMARY_POSTFIX="$(git log -1 --pretty='format:%h')" echo "[$SUMMARY_PREFIX $SUMMARY_POSTFIX] $2" >exp && - if test_have_prereq C_LOCALE_OUTPUT - then - test_cmp exp act - fi + test_i18ncmp exp act } test_expect_success 'output summary format' ' @@ -234,23 +231,19 @@ echo "sample # Please enter the commit message for your changes. Lines starting # with '#' will be ignored, and an empty message aborts the commit." >expect -test_expect_success C_LOCALE_OUTPUT 'cleanup commit messages (strip,-F,-e): output' ' - test_cmp expect actual +test_expect_success 'cleanup commit messages (strip,-F,-e): output' ' + test_i18ncmp expect actual ' echo "# # Author: $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> #" >> expect -test_expect_success C_LOCALE_OUTPUT 'author different from committer' ' - +test_expect_success 'author different from committer' ' echo >>negative && - git commit -e -m "sample" - head -n 7 .git/COMMIT_EDITMSG >actual -' - -test_expect_success C_LOCALE_OUTPUT 'author different from committer: output' ' - test_cmp expect actual + test_might_fail git commit -e -m "sample" && + head -n 7 .git/COMMIT_EDITMSG >actual && + test_i18ncmp expect actual ' mv expect expect.tmp @@ -259,7 +252,7 @@ rm -f expect.tmp echo "# Committer: #" >> expect -test_expect_success C_LOCALE_OUTPUT 'committer is automatic' ' +test_expect_success 'committer is automatic' ' echo >>negative && ( @@ -270,10 +263,7 @@ test_expect_success C_LOCALE_OUTPUT 'committer is automatic' ' ) && head -n 8 .git/COMMIT_EDITMSG | \ sed "s/^# Committer: .*/# Committer:/" >actual -' - -test_expect_success C_LOCALE_OUTPUT 'committer is automatic: output' ' - test_cmp expect actual + test_i18ncmp expect actual ' pwd=`pwd` @@ -376,78 +366,78 @@ try_commit () { GIT_EDITOR=.git/FAKE_EDITOR git commit -a $* $use_template && case "$use_template" in '') - ! grep "^## Custom template" .git/COMMIT_EDITMSG ;; + test_i18ngrep ! "^## Custom template" .git/COMMIT_EDITMSG ;; *) - grep "^## Custom template" .git/COMMIT_EDITMSG ;; + test_i18ngrep "^## Custom template" .git/COMMIT_EDITMSG ;; esac } try_commit_status_combo () { - test_expect_success C_LOCALE_OUTPUT 'commit' ' + test_expect_success 'commit' ' clear_config commit.status && try_commit "" && - grep "^# Changes to be committed:" .git/COMMIT_EDITMSG + test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG ' - test_expect_success C_LOCALE_OUTPUT 'commit' ' + test_expect_success 'commit' ' clear_config commit.status && try_commit "" && - grep "^# Changes to be committed:" .git/COMMIT_EDITMSG + test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG ' - test_expect_success C_LOCALE_OUTPUT 'commit --status' ' + test_expect_success 'commit --status' ' clear_config commit.status && try_commit --status && - grep "^# Changes to be committed:" .git/COMMIT_EDITMSG + test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG ' - test_expect_success C_LOCALE_OUTPUT 'commit --no-status' ' + test_expect_success 'commit --no-status' ' clear_config commit.status && try_commit --no-status && - ! grep "^# Changes to be committed:" .git/COMMIT_EDITMSG + test_i18ngrep ! "^# Changes to be committed:" .git/COMMIT_EDITMSG ' - test_expect_success C_LOCALE_OUTPUT 'commit with commit.status = yes' ' + test_expect_success 'commit with commit.status = yes' ' clear_config commit.status && git config commit.status yes && try_commit "" && - grep "^# Changes to be committed:" .git/COMMIT_EDITMSG + test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG ' - test_expect_success C_LOCALE_OUTPUT 'commit with commit.status = no' ' + test_expect_success 'commit with commit.status = no' ' clear_config commit.status && git config commit.status no && try_commit "" && - ! grep "^# Changes to be committed:" .git/COMMIT_EDITMSG + test_i18ngrep ! "^# Changes to be committed:" .git/COMMIT_EDITMSG ' - test_expect_success C_LOCALE_OUTPUT 'commit --status with commit.status = yes' ' + test_expect_success 'commit --status with commit.status = yes' ' clear_config commit.status && git config commit.status yes && try_commit --status && - grep "^# Changes to be committed:" .git/COMMIT_EDITMSG + test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG ' - test_expect_success C_LOCALE_OUTPUT 'commit --no-status with commit.status = yes' ' + test_expect_success 'commit --no-status with commit.status = yes' ' clear_config commit.status && git config commit.status yes && try_commit --no-status && - ! grep "^# Changes to be committed:" .git/COMMIT_EDITMSG + test_i18ngrep ! "^# Changes to be committed:" .git/COMMIT_EDITMSG ' - test_expect_success C_LOCALE_OUTPUT 'commit --status with commit.status = no' ' + test_expect_success 'commit --status with commit.status = no' ' clear_config commit.status && git config commit.status no && try_commit --status && - grep "^# Changes to be committed:" .git/COMMIT_EDITMSG + test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG ' - test_expect_success C_LOCALE_OUTPUT 'commit --no-status with commit.status = no' ' + test_expect_success 'commit --no-status with commit.status = no' ' clear_config commit.status && git config commit.status no && try_commit --no-status && - ! grep "^# Changes to be committed:" .git/COMMIT_EDITMSG + test_i18ngrep ! "^# Changes to be committed:" .git/COMMIT_EDITMSG ' } diff --git a/t/t7506-status-submodule.sh b/t/t7506-status-submodule.sh index c56733253f..c8d50a6567 100755 --- a/t/t7506-status-submodule.sh +++ b/t/t7506-status-submodule.sh @@ -20,21 +20,21 @@ test_expect_success 'setup' ' git commit -m "Add submodule sub" ' -test_expect_success C_LOCALE_OUTPUT 'status clean' ' +test_expect_success 'status clean' ' git status >output && - grep "nothing to commit" output + test_i18ngrep "nothing to commit" output ' -test_expect_success C_LOCALE_OUTPUT 'commit --dry-run -a clean' ' +test_expect_success 'commit --dry-run -a clean' ' test_must_fail git commit --dry-run -a >output && - grep "nothing to commit" output + test_i18ngrep "nothing to commit" output ' -test_expect_success C_LOCALE_OUTPUT 'status with modified file in submodule' ' +test_expect_success 'status with modified file in submodule' ' (cd sub && git reset --hard) && echo "changed" >sub/foo && git status >output && - grep "modified: sub (modified content)" output + test_i18ngrep "modified: sub (modified content)" output ' test_expect_success 'status with modified file in submodule (porcelain)' ' @@ -46,10 +46,10 @@ test_expect_success 'status with modified file in submodule (porcelain)' ' EOF ' -test_expect_success C_LOCALE_OUTPUT 'status with added file in submodule' ' +test_expect_success 'status with added file in submodule' ' (cd sub && git reset --hard && echo >foo && git add foo) && git status >output && - grep "modified: sub (modified content)" output + test_i18ngrep "modified: sub (modified content)" output ' test_expect_success 'status with added file in submodule (porcelain)' ' @@ -60,16 +60,16 @@ test_expect_success 'status with added file in submodule (porcelain)' ' EOF ' -test_expect_success C_LOCALE_OUTPUT 'status with untracked file in submodule' ' +test_expect_success 'status with untracked file in submodule' ' (cd sub && git reset --hard) && echo "content" >sub/new-file && git status >output && - grep "modified: sub (untracked content)" output + test_i18ngrep "modified: sub (untracked content)" output ' -test_expect_success C_LOCALE_OUTPUT 'status -uno with untracked file in submodule' ' +test_expect_success 'status -uno with untracked file in submodule' ' git status -uno >output && - grep "^nothing to commit" output + test_i18ngrep "^nothing to commit" output ' test_expect_success 'status with untracked file in submodule (porcelain)' ' @@ -79,11 +79,11 @@ test_expect_success 'status with untracked file in submodule (porcelain)' ' EOF ' -test_expect_success C_LOCALE_OUTPUT 'status with added and untracked file in submodule' ' +test_expect_success 'status with added and untracked file in submodule' ' (cd sub && git reset --hard && echo >foo && git add foo) && echo "content" >sub/new-file && git status >output && - grep "modified: sub (modified content, untracked content)" output + test_i18ngrep "modified: sub (modified content, untracked content)" output ' test_expect_success 'status with added and untracked file in submodule (porcelain)' ' @@ -95,13 +95,13 @@ test_expect_success 'status with added and untracked file in submodule (porcelai EOF ' -test_expect_success C_LOCALE_OUTPUT 'status with modified file in modified submodule' ' +test_expect_success 'status with modified file in modified submodule' ' (cd sub && git reset --hard) && rm sub/new-file && (cd sub && echo "next change" >foo && git commit -m "next change" foo) && echo "changed" >sub/foo && git status >output && - grep "modified: sub (new commits, modified content)" output + test_i18ngrep "modified: sub (new commits, modified content)" output ' test_expect_success 'status with modified file in modified submodule (porcelain)' ' @@ -113,10 +113,10 @@ test_expect_success 'status with modified file in modified submodule (porcelain) EOF ' -test_expect_success C_LOCALE_OUTPUT 'status with added file in modified submodule' ' +test_expect_success 'status with added file in modified submodule' ' (cd sub && git reset --hard && echo >foo && git add foo) && git status >output && - grep "modified: sub (new commits, modified content)" output + test_i18ngrep "modified: sub (new commits, modified content)" output ' test_expect_success 'status with added file in modified submodule (porcelain)' ' @@ -127,11 +127,11 @@ test_expect_success 'status with added file in modified submodule (porcelain)' ' EOF ' -test_expect_success C_LOCALE_OUTPUT 'status with untracked file in modified submodule' ' +test_expect_success 'status with untracked file in modified submodule' ' (cd sub && git reset --hard) && echo "content" >sub/new-file && git status >output && - grep "modified: sub (new commits, untracked content)" output + test_i18ngrep "modified: sub (new commits, untracked content)" output ' test_expect_success 'status with untracked file in modified submodule (porcelain)' ' @@ -141,11 +141,11 @@ test_expect_success 'status with untracked file in modified submodule (porcelain EOF ' -test_expect_success C_LOCALE_OUTPUT 'status with added and untracked file in modified submodule' ' +test_expect_success 'status with added and untracked file in modified submodule' ' (cd sub && git reset --hard && echo >foo && git add foo) && echo "content" >sub/new-file && git status >output && - grep "modified: sub (new commits, modified content, untracked content)" output + test_i18ngrep "modified: sub (new commits, modified content, untracked content)" output ' test_expect_success 'status with added and untracked file in modified submodule (porcelain)' ' @@ -167,24 +167,24 @@ test_expect_success 'setup .git file for sub' ' git commit -m "added .real to .gitignore" .gitignore ' -test_expect_success C_LOCALE_OUTPUT 'status with added file in modified submodule with .git file' ' +test_expect_success 'status with added file in modified submodule with .git file' ' (cd sub && git reset --hard && echo >foo && git add foo) && git status >output && - grep "modified: sub (new commits, modified content)" output + test_i18ngrep "modified: sub (new commits, modified content)" output ' test_expect_success 'rm submodule contents' ' rm -rf sub/* sub/.git ' -test_expect_success C_LOCALE_OUTPUT 'status clean (empty submodule dir)' ' +test_expect_success 'status clean (empty submodule dir)' ' git status >output && - grep "nothing to commit" output + test_i18ngrep "nothing to commit" output ' -test_expect_success C_LOCALE_OUTPUT 'status -a clean (empty submodule dir)' ' +test_expect_success 'status -a clean (empty submodule dir)' ' test_must_fail git commit --dry-run -a >output && - grep "nothing to commit" output + test_i18ngrep "nothing to commit" output ' test_done diff --git a/t/t7508-status.sh b/t/t7508-status.sh index a93e70fac4..cd6e2c5e87 100755 --- a/t/t7508-status.sh +++ b/t/t7508-status.sh @@ -16,7 +16,7 @@ test_expect_success 'status -h in broken repository' ' echo "[status] showuntrackedfiles = CORRUPT" >>.git/config && test_expect_code 129 git status -h >usage 2>&1 ) && - grep "[Uu]sage" broken/usage + test_i18ngrep "[Uu]sage" broken/usage ' test_expect_success 'commit -h in broken repository' ' @@ -28,7 +28,7 @@ test_expect_success 'commit -h in broken repository' ' echo "[status] showuntrackedfiles = CORRUPT" >>.git/config && test_expect_code 129 git commit -h >usage 2>&1 ) && - grep "[Uu]sage" broken/usage + test_i18ngrep "[Uu]sage" broken/usage ' test_expect_success 'setup' ' @@ -55,10 +55,8 @@ test_expect_success 'setup' ' git add dir2/added ' -test_expect_success C_LOCALE_OUTPUT 'status (1)' ' - - grep "use \"git rm --cached <file>\.\.\.\" to unstage" output - +test_expect_success 'status (1)' ' + test_i18ngrep "use \"git rm --cached <file>\.\.\.\" to unstage" output ' cat >expect <<\EOF @@ -85,11 +83,9 @@ cat >expect <<\EOF # untracked EOF -test_expect_success C_LOCALE_OUTPUT 'status (2)' ' - +test_expect_success 'status (2)' ' git status >output && - test_cmp expect output - + test_i18ncmp expect output ' cat >expect <<\EOF @@ -109,17 +105,14 @@ cat >expect <<\EOF # untracked EOF -git config advice.statusHints false - -test_expect_success C_LOCALE_OUTPUT 'status (advice.statusHints false)' ' - +test_expect_success 'status (advice.statusHints false)' ' + test_when_finished "git config --unset advice.statusHints" && + git config advice.statusHints false && git status >output && - test_cmp expect output + test_i18ncmp expect output ' -git config --unset advice.statusHints - cat >expect <<\EOF M dir1/modified A dir2/added @@ -178,16 +171,16 @@ cat >expect <<EOF # # Untracked files not listed (use -u option to show untracked files) EOF -test_expect_success C_LOCALE_OUTPUT 'status -uno' ' +test_expect_success 'status -uno' ' git status -uno >output && - test_cmp expect output + test_i18ncmp expect output ' -test_expect_success C_LOCALE_OUTPUT 'status (status.showUntrackedFiles no)' ' +test_expect_success 'status (status.showUntrackedFiles no)' ' git config status.showuntrackedfiles no test_when_finished "git config --unset status.showuntrackedfiles" && git status >output && - test_cmp expect output + test_i18ncmp expect output ' cat >expect <<EOF @@ -201,9 +194,9 @@ cat >expect <<EOF # Untracked files not listed EOF git config advice.statusHints false -test_expect_success C_LOCALE_OUTPUT 'status -uno (advice.statusHints false)' ' +test_expect_success 'status -uno (advice.statusHints false)' ' git status -uno >output && - test_cmp expect output + test_i18ncmp expect output ' git config --unset advice.statusHints @@ -246,16 +239,16 @@ cat >expect <<EOF # output # untracked EOF -test_expect_success C_LOCALE_OUTPUT 'status -unormal' ' +test_expect_success 'status -unormal' ' git status -unormal >output && - test_cmp expect output + test_i18ncmp expect output ' -test_expect_success C_LOCALE_OUTPUT 'status (status.showUntrackedFiles normal)' ' +test_expect_success 'status (status.showUntrackedFiles normal)' ' git config status.showuntrackedfiles normal test_when_finished "git config --unset status.showuntrackedfiles" && git status >output && - test_cmp expect output + test_i18ncmp expect output ' cat >expect <<EOF @@ -305,15 +298,16 @@ cat >expect <<EOF # output # untracked EOF -test_expect_success C_LOCALE_OUTPUT 'status -uall' ' +test_expect_success 'status -uall' ' git status -uall >output && - test_cmp expect output + test_i18ncmp expect output ' -test_expect_success C_LOCALE_OUTPUT 'status (status.showUntrackedFiles all)' ' + +test_expect_success 'status (status.showUntrackedFiles all)' ' git config status.showuntrackedfiles all test_when_finished "git config --unset status.showuntrackedfiles" && git status >output && - test_cmp expect output + test_i18ncmp expect output ' test_expect_success 'teardown dir3' ' @@ -367,11 +361,9 @@ cat >expect <<\EOF # ../untracked EOF -test_expect_success C_LOCALE_OUTPUT 'status with relative paths' ' - +test_expect_success 'status with relative paths' ' (cd dir1 && git status) >output && - test_cmp expect output - + test_i18ncmp expect output ' cat >expect <<\EOF @@ -440,22 +432,18 @@ cat >expect <<\EOF # <BLUE>untracked<RESET> EOF -test_expect_success C_LOCALE_OUTPUT 'status with color.ui' ' - +test_expect_success 'status with color.ui' ' git config color.ui always && test_when_finished "git config --unset color.ui" && git status | test_decode_color >output && - test_cmp expect output - + test_i18ncmp expect output ' -test_expect_success C_LOCALE_OUTPUT 'status with color.status' ' - +test_expect_success 'status with color.status' ' git config color.status always && test_when_finished "git config --unset color.status" && git status | test_decode_color >output && - test_cmp expect output - + test_i18ncmp expect output ' cat >expect <<\EOF @@ -570,12 +558,12 @@ cat >expect <<\EOF EOF -test_expect_success C_LOCALE_OUTPUT 'status without relative paths' ' +test_expect_success 'status without relative paths' ' git config status.relativePaths false && test_when_finished "git config --unset status.relativePaths" && (cd dir1 && git status) >output && - test_cmp expect output + test_i18ncmp expect output ' @@ -616,11 +604,8 @@ cat <<EOF >expect # untracked EOF test_expect_success 'dry-run of partial commit excluding new file in index' ' - git commit --dry-run dir1/modified >output -' - -test_expect_success C_LOCALE_OUTPUT 'dry-run of partial commit excluding new file in index: output' ' - test_cmp expect output + git commit --dry-run dir1/modified >output && + test_i18ncmp expect output ' cat >expect <<EOF @@ -667,15 +652,15 @@ cat >expect <<EOF # output # untracked EOF -test_expect_success C_LOCALE_OUTPUT 'status submodule summary is disabled by default' ' +test_expect_success 'status submodule summary is disabled by default' ' git status >output && - test_cmp expect output + test_i18ncmp expect output ' # we expect the same as the previous test -test_expect_success C_LOCALE_OUTPUT 'status --untracked-files=all does not show submodule' ' +test_expect_success 'status --untracked-files=all does not show submodule' ' git status --untracked-files=all >output && - test_cmp expect output + test_i18ncmp expect output ' cat >expect <<EOF @@ -731,10 +716,10 @@ cat >expect <<EOF # output # untracked EOF -test_expect_success C_LOCALE_OUTPUT 'status submodule summary' ' +test_expect_success 'status submodule summary' ' git config status.submodulesummary 10 && git status >output && - test_cmp expect output + test_i18ncmp expect output ' cat >expect <<EOF @@ -773,15 +758,12 @@ cat >expect <<EOF no changes added to commit (use "git add" and/or "git commit -a") EOF test_expect_success 'status submodule summary (clean submodule): commit' ' - git commit -m "commit submodule" -' - -test_expect_success C_LOCALE_OUTPUT 'status submodule summary (clean submodule): output' ' + git commit -m "commit submodule" && git config status.submodulesummary 10 && test_must_fail git commit --dry-run >output && - test_cmp expect output && + test_i18ncmp expect output && git status >output && - test_cmp expect output + test_i18ncmp expect output ' cat >expect <<EOF @@ -827,10 +809,10 @@ cat >expect <<EOF # output # untracked EOF -test_expect_success C_LOCALE_OUTPUT 'commit --dry-run submodule summary (--amend)' ' +test_expect_success 'commit --dry-run submodule summary (--amend)' ' git config status.submodulesummary 10 && git commit --dry-run --amend >output && - test_cmp expect output + test_i18ncmp expect output ' test_expect_success POSIXPERM,SANITY 'status succeeds in a read-only repository' ' @@ -882,84 +864,84 @@ cat > expect << EOF # untracked EOF -test_expect_success C_LOCALE_OUTPUT '--ignore-submodules=untracked suppresses submodules with untracked content' ' - echo modified > sm/untracked && - git status --ignore-submodules=untracked > output && - test_cmp expect output +test_expect_success '--ignore-submodules=untracked suppresses submodules with untracked content' ' + echo modified sm/untracked && + git status --ignore-submodules=untracked >output && + test_i18ncmp expect output ' -test_expect_success C_LOCALE_OUTPUT '.gitmodules ignore=untracked suppresses submodules with untracked content' ' +test_expect_success '.gitmodules ignore=untracked suppresses submodules with untracked content' ' git config diff.ignoreSubmodules dirty && git status >output && - test_cmp expect output && + test_i18ncmp expect output && git config --add -f .gitmodules submodule.subname.ignore untracked && git config --add -f .gitmodules submodule.subname.path sm && - git status > output && - test_cmp expect output && + git status >output && + test_i18ncmp expect output && git config -f .gitmodules --remove-section submodule.subname && git config --unset diff.ignoreSubmodules ' -test_expect_success C_LOCALE_OUTPUT '.git/config ignore=untracked suppresses submodules with untracked content' ' +test_expect_success '.git/config ignore=untracked suppresses submodules with untracked content' ' git config --add -f .gitmodules submodule.subname.ignore none && git config --add -f .gitmodules submodule.subname.path sm && git config --add submodule.subname.ignore untracked && git config --add submodule.subname.path sm && - git status > output && - test_cmp expect output && + git status >output && + test_i18ncmp expect output && git config --remove-section submodule.subname && git config --remove-section -f .gitmodules submodule.subname ' -test_expect_success C_LOCALE_OUTPUT '--ignore-submodules=dirty suppresses submodules with untracked content' ' - git status --ignore-submodules=dirty > output && - test_cmp expect output +test_expect_success '--ignore-submodules=dirty suppresses submodules with untracked content' ' + git status --ignore-submodules=dirty >output && + test_i18ncmp expect output ' -test_expect_success C_LOCALE_OUTPUT '.gitmodules ignore=dirty suppresses submodules with untracked content' ' +test_expect_success '.gitmodules ignore=dirty suppresses submodules with untracked content' ' git config diff.ignoreSubmodules dirty && git status >output && ! test -s actual && git config --add -f .gitmodules submodule.subname.ignore dirty && git config --add -f .gitmodules submodule.subname.path sm && - git status > output && - test_cmp expect output && + git status >output && + test_i18ncmp expect output && git config -f .gitmodules --remove-section submodule.subname && git config --unset diff.ignoreSubmodules ' -test_expect_success C_LOCALE_OUTPUT '.git/config ignore=dirty suppresses submodules with untracked content' ' +test_expect_success '.git/config ignore=dirty suppresses submodules with untracked content' ' git config --add -f .gitmodules submodule.subname.ignore none && git config --add -f .gitmodules submodule.subname.path sm && git config --add submodule.subname.ignore dirty && git config --add submodule.subname.path sm && - git status > output && - test_cmp expect output && + git status >output && + test_i18ncmp expect output && git config --remove-section submodule.subname && git config -f .gitmodules --remove-section submodule.subname ' -test_expect_success C_LOCALE_OUTPUT '--ignore-submodules=dirty suppresses submodules with modified content' ' - echo modified > sm/foo && - git status --ignore-submodules=dirty > output && - test_cmp expect output +test_expect_success '--ignore-submodules=dirty suppresses submodules with modified content' ' + echo modified >sm/foo && + git status --ignore-submodules=dirty >output && + test_i18ncmp expect output ' -test_expect_success C_LOCALE_OUTPUT '.gitmodules ignore=dirty suppresses submodules with modified content' ' +test_expect_success '.gitmodules ignore=dirty suppresses submodules with modified content' ' git config --add -f .gitmodules submodule.subname.ignore dirty && git config --add -f .gitmodules submodule.subname.path sm && - git status > output && - test_cmp expect output && + git status >output && + test_i18ncmp expect output && git config -f .gitmodules --remove-section submodule.subname ' -test_expect_success C_LOCALE_OUTPUT '.git/config ignore=dirty suppresses submodules with modified content' ' +test_expect_success '.git/config ignore=dirty suppresses submodules with modified content' ' git config --add -f .gitmodules submodule.subname.ignore none && git config --add -f .gitmodules submodule.subname.path sm && git config --add submodule.subname.ignore dirty && git config --add submodule.subname.path sm && - git status > output && - test_cmp expect output && + git status >output && + test_i18ncmp expect output && git config --remove-section submodule.subname && git config -f .gitmodules --remove-section submodule.subname ' @@ -996,26 +978,26 @@ cat > expect << EOF # untracked EOF -test_expect_success C_LOCALE_OUTPUT "--ignore-submodules=untracked doesn't suppress submodules with modified content" ' +test_expect_success "--ignore-submodules=untracked doesn't suppress submodules with modified content" ' git status --ignore-submodules=untracked > output && - test_cmp expect output + test_i18ncmp expect output ' -test_expect_success C_LOCALE_OUTPUT ".gitmodules ignore=untracked doesn't suppress submodules with modified content" ' +test_expect_success ".gitmodules ignore=untracked doesn't suppress submodules with modified content" ' git config --add -f .gitmodules submodule.subname.ignore untracked && git config --add -f .gitmodules submodule.subname.path sm && - git status > output && - test_cmp expect output && + git status >output && + test_i18ncmp expect output && git config -f .gitmodules --remove-section submodule.subname ' -test_expect_success C_LOCALE_OUTPUT ".git/config ignore=untracked doesn't suppress submodules with modified content" ' +test_expect_success ".git/config ignore=untracked doesn't suppress submodules with modified content" ' git config --add -f .gitmodules submodule.subname.ignore none && git config --add -f .gitmodules submodule.subname.path sm && git config --add submodule.subname.ignore untracked && git config --add submodule.subname.path sm && - git status > output && - test_cmp expect output && + git status >output && + test_i18ncmp expect output && git config --remove-section submodule.subname && git config -f .gitmodules --remove-section submodule.subname ' @@ -1058,49 +1040,49 @@ cat > expect << EOF # untracked EOF -test_expect_success C_LOCALE_OUTPUT "--ignore-submodules=untracked doesn't suppress submodule summary" ' +test_expect_success "--ignore-submodules=untracked doesn't suppress submodule summary" ' git status --ignore-submodules=untracked > output && - test_cmp expect output + test_i18ncmp expect output ' -test_expect_success C_LOCALE_OUTPUT ".gitmodules ignore=untracked doesn't suppress submodule summary" ' +test_expect_success ".gitmodules ignore=untracked doesn't suppress submodule summary" ' git config --add -f .gitmodules submodule.subname.ignore untracked && git config --add -f .gitmodules submodule.subname.path sm && - git status > output && - test_cmp expect output && + git status >output && + test_i18ncmp expect output && git config -f .gitmodules --remove-section submodule.subname ' -test_expect_success C_LOCALE_OUTPUT ".git/config ignore=untracked doesn't suppress submodule summary" ' +test_expect_success ".git/config ignore=untracked doesn't suppress submodule summary" ' git config --add -f .gitmodules submodule.subname.ignore none && git config --add -f .gitmodules submodule.subname.path sm && git config --add submodule.subname.ignore untracked && git config --add submodule.subname.path sm && - git status > output && - test_cmp expect output && + git status >output && + test_i18ncmp expect output && git config --remove-section submodule.subname && git config -f .gitmodules --remove-section submodule.subname ' -test_expect_success C_LOCALE_OUTPUT "--ignore-submodules=dirty doesn't suppress submodule summary" ' +test_expect_success "--ignore-submodules=dirty doesn't suppress submodule summary" ' git status --ignore-submodules=dirty > output && - test_cmp expect output + test_i18ncmp expect output ' -test_expect_success C_LOCALE_OUTPUT ".gitmodules ignore=dirty doesn't suppress submodule summary" ' +test_expect_success ".gitmodules ignore=dirty doesn't suppress submodule summary" ' git config --add -f .gitmodules submodule.subname.ignore dirty && git config --add -f .gitmodules submodule.subname.path sm && - git status > output && - test_cmp expect output && + git status >output && + test_i18ncmp expect output && git config -f .gitmodules --remove-section submodule.subname ' -test_expect_success C_LOCALE_OUTPUT ".git/config ignore=dirty doesn't suppress submodule summary" ' +test_expect_success ".git/config ignore=dirty doesn't suppress submodule summary" ' git config --add -f .gitmodules submodule.subname.ignore none && git config --add -f .gitmodules submodule.subname.path sm && git config --add submodule.subname.ignore dirty && git config --add submodule.subname.path sm && - git status > output && - test_cmp expect output && + git status >output && + test_i18ncmp expect output && git config --remove-section submodule.subname && git config -f .gitmodules --remove-section submodule.subname ' @@ -1126,9 +1108,9 @@ cat > expect << EOF no changes added to commit (use "git add" and/or "git commit -a") EOF -test_expect_success C_LOCALE_OUTPUT "--ignore-submodules=all suppresses submodule summary" ' +test_expect_success "--ignore-submodules=all suppresses submodule summary" ' git status --ignore-submodules=all > output && - test_cmp expect output + test_i18ncmp expect output ' test_expect_failure '.gitmodules ignore=all suppresses submodule summary' ' diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh index 5463f87e68..a07caa8eae 100755 --- a/t/t7600-merge.sh +++ b/t/t7600-merge.sh @@ -28,80 +28,79 @@ Testing basic merge operations/option parsing. . ./test-lib.sh -test_expect_success 'set up test data and helpers' ' - printf "%s\n" 1 2 3 4 5 6 7 8 9 >file && - printf "%s\n" "1 X" 2 3 4 5 6 7 8 9 >file.1 && - printf "%s\n" 1 2 3 4 "5 X" 6 7 8 9 >file.5 && - printf "%s\n" 1 2 3 4 5 6 7 8 "9 X" >file.9 && - printf "%s\n" "1 X" 2 3 4 5 6 7 8 9 >result.1 && - printf "%s\n" "1 X" 2 3 4 "5 X" 6 7 8 9 >result.1-5 && - printf "%s\n" "1 X" 2 3 4 "5 X" 6 7 8 "9 X" >result.1-5-9 && - - create_merge_msgs() { - echo "Merge commit '\''c2'\''" >msg.1-5 && - echo "Merge commit '\''c2'\''; commit '\''c3'\''" >msg.1-5-9 && - { - echo "Squashed commit of the following:" && - echo && - git log --no-merges ^HEAD c1 - } >squash.1 && - { - echo "Squashed commit of the following:" && - echo && - git log --no-merges ^HEAD c2 - } >squash.1-5 && - { - echo "Squashed commit of the following:" && - echo && - git log --no-merges ^HEAD c2 c3 - } >squash.1-5-9 && - echo >msg.nolog && - { - echo "* commit '\''c3'\'':" && - echo " commit 3" && - echo - } >msg.log - } && - - verify_merge() { - test_cmp "$2" "$1" && - git update-index --refresh && - git diff --exit-code && - if test -n "$3" - then - git show -s --pretty=format:%s HEAD >msg.act && - test_cmp "$3" msg.act - fi - } && - - verify_head() { - echo "$1" >head.expected && - git rev-parse HEAD >head.actual && - test_cmp head.expected head.actual - } && - - verify_parents() { - printf "%s\n" "$@" >parents.expected && - >parents.actual && - i=1 && - while test $i -le $# - do - git rev-parse HEAD^$i >>parents.actual && - i=$(expr $i + 1) || - return 1 - done && - test_cmp parents.expected parents.actual - } && - - verify_mergeheads() { - printf "%s\n" "$@" >mergehead.expected && - test_cmp mergehead.expected .git/MERGE_HEAD - } && - - verify_no_mergehead() { - ! test -e .git/MERGE_HEAD - } -' +printf '%s\n' 1 2 3 4 5 6 7 8 9 >file +printf '%s\n' '1 X' 2 3 4 5 6 7 8 9 >file.1 +printf '%s\n' 1 2 3 4 '5 X' 6 7 8 9 >file.5 +printf '%s\n' 1 2 3 4 5 6 7 8 '9 X' >file.9 +printf '%s\n' '1 X' 2 3 4 5 6 7 8 9 >result.1 +printf '%s\n' '1 X' 2 3 4 '5 X' 6 7 8 9 >result.1-5 +printf '%s\n' '1 X' 2 3 4 '5 X' 6 7 8 '9 X' >result.1-5-9 + +create_merge_msgs () { + echo "Merge commit 'c2'" >msg.1-5 && + echo "Merge commit 'c2'; commit 'c3'" >msg.1-5-9 && + { + echo "Squashed commit of the following:" && + echo && + git log --no-merges ^HEAD c1 + } >squash.1 && + { + echo "Squashed commit of the following:" && + echo && + git log --no-merges ^HEAD c2 + } >squash.1-5 && + { + echo "Squashed commit of the following:" && + echo && + git log --no-merges ^HEAD c2 c3 + } >squash.1-5-9 && + echo >msg.nolog && + { + echo "* commit 'c3':" && + echo " commit 3" && + echo + } >msg.log +} + +verify_merge () { + test_cmp "$2" "$1" && + git update-index --refresh && + git diff --exit-code && + if test -n "$3" + then + git show -s --pretty=format:%s HEAD >msg.act && + test_cmp "$3" msg.act + fi +} + +verify_head () { + echo "$1" >head.expected && + git rev-parse HEAD >head.actual && + test_cmp head.expected head.actual +} + +verify_parents () { + printf '%s\n' "$@" >parents.expected && + >parents.actual && + i=1 && + while test $i -le $# + do + git rev-parse HEAD^$i >>parents.actual && + i=$(expr $i + 1) || + return 1 + done && + test_must_fail git rev-parse --verify "HEAD^$i" && + test_cmp parents.expected parents.actual +} + +verify_mergeheads () { + printf '%s\n' "$@" >mergehead.expected && + test_cmp mergehead.expected .git/MERGE_HEAD +} + +verify_no_mergehead () { + ! test -e .git/MERGE_HEAD +} test_expect_success 'setup' ' git add file && @@ -527,10 +526,10 @@ test_expect_success 'merge fast-forward in a dirty tree' ' test_debug 'git log --graph --decorate --oneline --all' -test_expect_success C_LOCALE_OUTPUT 'in-index merge' ' +test_expect_success 'in-index merge' ' git reset --hard c0 && git merge --no-ff -s resolve c1 >out && - grep "Wonderful." out && + test_i18ngrep "Wonderful." out && verify_parents $c0 $c1 ' diff --git a/t/t7607-merge-overwrite.sh b/t/t7607-merge-overwrite.sh index ef84f04102..72a8731d5e 100755 --- a/t/t7607-merge-overwrite.sh +++ b/t/t7607-merge-overwrite.sh @@ -150,11 +150,8 @@ test_expect_success 'will not overwrite untracked file on unborn branch' ' git rm -fr . && git checkout --orphan new && cp important c0.c && - test_must_fail git merge c0 2>out -' - -test_expect_success C_LOCALE_OUTPUT 'will not overwrite untracked file on unborn branch: output' ' - test_cmp out expect + test_must_fail git merge c0 2>out && + test_i18ncmp out expect ' test_expect_success 'will not overwrite untracked file on unborn branch .git/MERGE_HEAD sanity etc.' ' diff --git a/t/t7611-merge-abort.sh b/t/t7611-merge-abort.sh index cdb3f444cd..7b4798e8e4 100755 --- a/t/t7611-merge-abort.sh +++ b/t/t7611-merge-abort.sh @@ -46,11 +46,8 @@ test_expect_success 'setup' ' pre_merge_head="$(git rev-parse HEAD)" test_expect_success 'fails without MERGE_HEAD (unstarted merge)' ' - test_must_fail git merge --abort 2>output -' - -test_expect_success C_LOCALE_OUTPUT 'fails without MERGE_HEAD (unstarted merge): fatal output' ' - grep -q MERGE_HEAD output + test_must_fail git merge --abort 2>output && + test_i18ngrep MERGE_HEAD output ' test_expect_success 'fails without MERGE_HEAD (unstarted merge): .git/MERGE_HEAD sanity' ' @@ -63,11 +60,8 @@ test_expect_success 'fails without MERGE_HEAD (completed merge)' ' test ! -f .git/MERGE_HEAD && # Merge successfully completed post_merge_head="$(git rev-parse HEAD)" && - test_must_fail git merge --abort 2>output -' - -test_expect_success C_LOCALE_OUTPUT 'fails without MERGE_HEAD (completed merge): output' ' - grep -q MERGE_HEAD output + test_must_fail git merge --abort 2>output && + test_i18ngrep MERGE_HEAD output ' test_expect_success 'fails without MERGE_HEAD (completed merge): .git/MERGE_HEAD sanity' ' diff --git a/t/t7811-grep-open.sh b/t/t7811-grep-open.sh index aedf484fee..a8957782cf 100755 --- a/t/t7811-grep-open.sh +++ b/t/t7811-grep-open.sh @@ -61,9 +61,9 @@ test_expect_success SIMPLEPAGER 'git grep -O' ' test_cmp empty out ' -test_expect_success C_LOCALE_OUTPUT 'git grep -O --cached' ' +test_expect_success 'git grep -O --cached' ' test_must_fail git grep --cached -O GREP_PATTERN >out 2>msg && - grep open-files-in-pager msg + test_i18ngrep open-files-in-pager msg ' test_expect_success 'git grep -O --no-index' ' diff --git a/t/t9146-git-svn-empty-dirs.sh b/t/t9146-git-svn-empty-dirs.sh index 158c8e33ef..6d3130e618 100755 --- a/t/t9146-git-svn-empty-dirs.sh +++ b/t/t9146-git-svn-empty-dirs.sh @@ -28,6 +28,23 @@ test_expect_success 'empty directories exist' ' ) ' +test_expect_success 'option automkdirs set to false' ' + ( + git svn init "$svnrepo" cloned-no-mkdirs && + cd cloned-no-mkdirs && + git config svn-remote.svn.automkdirs false && + git svn fetch && + for i in a b c d d/e d/e/f "weird file name" + do + if test -d "$i" + then + echo >&2 "$i exists" + exit 1 + fi + done + ) +' + test_expect_success 'more emptiness' ' svn_cmd mkdir -m "bang bang" "$svnrepo"/"! !" ' diff --git a/t/t9500-gitweb-standalone-no-errors.sh b/t/t9500-gitweb-standalone-no-errors.sh index afac5b56a8..71ef0acb1b 100755 --- a/t/t9500-gitweb-standalone-no-errors.sh +++ b/t/t9500-gitweb-standalone-no-errors.sh @@ -595,4 +595,53 @@ test_expect_success HIGHLIGHT \ git commit -m "Add test.sh" && gitweb_run "p=.git;a=blob;f=test.sh"' +# ---------------------------------------------------------------------- +# forks of projects + +cat >>gitweb_config.perl <<\EOF && +$feature{'forks'}{'default'} = [1]; +EOF + +test_expect_success \ + 'forks: prepare' \ + 'git init --bare foo.git && + git --git-dir=foo.git --work-tree=. add file && + git --git-dir=foo.git --work-tree=. commit -m "Initial commit" && + echo "foo" > foo.git/description && + mkdir -p foo && + (cd foo && + git clone --shared --bare ../foo.git foo-forked.git && + echo "fork of foo" > foo-forked.git/description)' + +test_expect_success \ + 'forks: projects list' \ + 'gitweb_run' + +test_expect_success \ + 'forks: forks action' \ + 'gitweb_run "p=foo.git;a=forks"' + +# ---------------------------------------------------------------------- +# content tags (tag cloud) + +cat >>gitweb_config.perl <<-\EOF && +# we don't test _setting_ content tags, so any true value is good +$feature{'ctags'}{'default'} = ['ctags_script.cgi']; +EOF + +test_expect_success \ + 'ctags: tag cloud in projects list' \ + 'mkdir .git/ctags && + echo "2" > .git/ctags/foo && + echo "1" > .git/ctags/bar && + gitweb_run' + +test_expect_success \ + 'ctags: search projects by existing tag' \ + 'gitweb_run "by_tag=foo"' + +test_expect_success \ + 'ctags: search projects by non existent tag' \ + 'gitweb_run "by_tag=non-existent"' + test_done diff --git a/t/t9502-gitweb-standalone-parse-output.sh b/t/t9502-gitweb-standalone-parse-output.sh index dd83890001..731e64c3ad 100755 --- a/t/t9502-gitweb-standalone-parse-output.sh +++ b/t/t9502-gitweb-standalone-parse-output.sh @@ -112,4 +112,78 @@ test_expect_success 'snapshot: hierarchical branch name (xx/test)' ' ' test_debug 'cat gitweb.headers' +# ---------------------------------------------------------------------- +# forks of projects + +test_expect_success 'forks: setup' ' + git init --bare foo.git && + echo file > file && + git --git-dir=foo.git --work-tree=. add file && + git --git-dir=foo.git --work-tree=. commit -m "Initial commit" && + echo "foo" > foo.git/description && + git clone --bare foo.git foo.bar.git && + echo "foo.bar" > foo.bar.git/description && + git clone --bare foo.git foo_baz.git && + echo "foo_baz" > foo_baz.git/description && + rm -fr foo && + mkdir -p foo && + ( + cd foo && + git clone --shared --bare ../foo.git foo-forked.git && + echo "fork of foo" > foo-forked.git/description + ) +' + +test_expect_success 'forks: not skipped unless "forks" feature enabled' ' + gitweb_run "a=project_list" && + grep -q ">\\.git<" gitweb.body && + grep -q ">foo\\.git<" gitweb.body && + grep -q ">foo_baz\\.git<" gitweb.body && + grep -q ">foo\\.bar\\.git<" gitweb.body && + grep -q ">foo_baz\\.git<" gitweb.body && + grep -q ">foo/foo-forked\\.git<" gitweb.body && + grep -q ">fork of .*<" gitweb.body +' + +cat >>gitweb_config.perl <<\EOF && +$feature{'forks'}{'default'} = [1]; +EOF + +test_expect_success 'forks: forks skipped if "forks" feature enabled' ' + gitweb_run "a=project_list" && + grep -q ">\\.git<" gitweb.body && + grep -q ">foo\\.git<" gitweb.body && + grep -q ">foo_baz\\.git<" gitweb.body && + grep -q ">foo\\.bar\\.git<" gitweb.body && + grep -q ">foo_baz\\.git<" gitweb.body && + grep -v ">foo/foo-forked\\.git<" gitweb.body && + grep -v ">fork of .*<" gitweb.body +' + +test_expect_success 'forks: "forks" action for forked repository' ' + gitweb_run "p=foo.git;a=forks" && + grep -q ">foo/foo-forked\\.git<" gitweb.body && + grep -q ">fork of foo<" gitweb.body +' + +test_expect_success 'forks: can access forked repository' ' + gitweb_run "p=foo/foo-forked.git;a=summary" && + grep -q "200 OK" gitweb.headers && + grep -q ">fork of foo<" gitweb.body +' + +test_expect_success 'forks: project_index lists all projects (incl. forks)' ' + cat >expected <<-\EOF + .git + foo.bar.git + foo.git + foo/foo-forked.git + foo_baz.git + EOF + gitweb_run "a=project_index" && + sed -e "s/ .*//" <gitweb.body | sort >actual && + test_cmp expected actual +' + + test_done diff --git a/t/test-lib.sh b/t/test-lib.sh index 8a274fbe79..b2ce2bc4b2 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -578,7 +578,7 @@ test_external () { test_external_without_stderr () { # The temporary file has no (and must have no) security # implications. - tmp="$TMPDIR"; if [ -z "$tmp" ]; then tmp=/tmp; fi + tmp=${TMPDIR:-/tmp} stderr="$tmp/git-external-stderr.$$.tmp" test_external "$@" 4> "$stderr" [ -f "$stderr" ] || error "Internal error: $stderr disappeared." @@ -804,12 +804,14 @@ test_done () { mkdir -p "$test_results_dir" test_results_path="$test_results_dir/${0%.sh}-$$.counts" - echo "total $test_count" >> $test_results_path - echo "success $test_success" >> $test_results_path - echo "fixed $test_fixed" >> $test_results_path - echo "broken $test_broken" >> $test_results_path - echo "failed $test_failure" >> $test_results_path - echo "" >> $test_results_path + cat >>"$test_results_path" <<-EOF + total $test_count + success $test_success + fixed $test_fixed + broken $test_broken + failed $test_failure + + EOF fi if test "$test_fixed" != 0 @@ -1080,6 +1082,32 @@ else test_set_prereq C_LOCALE_OUTPUT fi +# Use this instead of test_cmp to compare files that contain expected and +# actual output from git commands that can be translated. When running +# under GETTEXT_POISON this pretends that the command produced expected +# results. +test_i18ncmp () { + test -n "$GETTEXT_POISON" || test_cmp "$@" +} + +# Use this instead of "grep expected-string actual" to see if the +# output from a git command that can be translated either contains an +# expected string, or does not contain an unwanted one. When running +# under GETTEXT_POISON this pretends that the command produced expected +# results. +test_i18ngrep () { + if test -n "$GETTEXT_POISON" + then + : # pretend success + elif test "x!" = "x$1" + then + shift + ! grep "$@" + else + grep "$@" + fi +} + # test whether the filesystem supports symbolic links ln -s x y 2>/dev/null && test -h y 2>/dev/null && test_set_prereq SYMLINKS rm -f y diff --git a/test-run-command.c b/test-run-command.c index 0612bfa7cd..37918e15f5 100644 --- a/test-run-command.c +++ b/test-run-command.c @@ -29,6 +29,8 @@ int main(int argc, char **argv) fprintf(stderr, "FAIL %s\n", argv[1]); return 1; } + if (!strcmp(argv[1], "run-command")) + exit(run_command(&proc)); fprintf(stderr, "check usage\n"); return 1; diff --git a/tree-diff.c b/tree-diff.c index 76f83fcc27..3f4072525b 100644 --- a/tree-diff.c +++ b/tree-diff.c @@ -64,23 +64,17 @@ static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, static void show_tree(struct diff_options *opt, const char *prefix, struct tree_desc *desc, struct strbuf *base) { - int all_interesting = 0; - while (desc->size) { - int show; - - if (all_interesting) - show = 1; - else { - show = tree_entry_interesting(&desc->entry, base, 0, - &opt->pathspec); - if (show == 2) - all_interesting = 1; + int match = 0; + for (; desc->size; update_tree_entry(desc)) { + if (match != 2) { + match = tree_entry_interesting(&desc->entry, base, 0, + &opt->pathspec); + if (match < 0) + break; + if (match == 0) + continue; } - if (show < 0) - break; - if (show) - show_entry(opt, prefix, desc, base); - update_tree_entry(desc); + show_entry(opt, prefix, desc, base); } } @@ -120,20 +114,16 @@ static void show_entry(struct diff_options *opt, const char *prefix, } static void skip_uninteresting(struct tree_desc *t, struct strbuf *base, - struct diff_options *opt, int *all_interesting) + struct diff_options *opt, int *match) { while (t->size) { - int show = tree_entry_interesting(&t->entry, base, 0, &opt->pathspec); - if (show == 2) - *all_interesting = 1; - if (!show) { - update_tree_entry(t); - continue; + *match = tree_entry_interesting(&t->entry, base, 0, &opt->pathspec); + if (*match) { + if (*match < 0) + t->size = 0; + break; } - /* Skip it all? */ - if (show < 0) - t->size = 0; - return; + update_tree_entry(t); } } @@ -142,8 +132,7 @@ int diff_tree(struct tree_desc *t1, struct tree_desc *t2, { struct strbuf base; int baselen = strlen(base_str); - int all_t1_interesting = 0; - int all_t2_interesting = 0; + int t1_match = 0, t2_match = 0; /* Enable recursion indefinitely */ opt->pathspec.recursive = DIFF_OPT_TST(opt, RECURSIVE); @@ -157,10 +146,8 @@ int diff_tree(struct tree_desc *t1, struct tree_desc *t2, DIFF_OPT_TST(opt, HAS_CHANGES)) break; if (opt->pathspec.nr) { - if (!all_t1_interesting) - skip_uninteresting(t1, &base, opt, &all_t1_interesting); - if (!all_t2_interesting) - skip_uninteresting(t2, &base, opt, &all_t2_interesting); + skip_uninteresting(t1, &base, opt, &t1_match); + skip_uninteresting(t2, &base, opt, &t2_match); } if (!t1->size) { if (!t2->size) diff --git a/tree-walk.c b/tree-walk.c index 322becc3b4..33f749e1e7 100644 --- a/tree-walk.c +++ b/tree-walk.c @@ -598,7 +598,7 @@ int tree_entry_interesting(const struct name_entry *entry, &never_interesting)) return 1; - if (ps->items[i].has_wildcard) { + if (ps->items[i].use_wildcard) { if (!fnmatch(match + baselen, entry->path, 0)) return 1; @@ -614,7 +614,7 @@ int tree_entry_interesting(const struct name_entry *entry, } match_wildcards: - if (!ps->items[i].has_wildcard) + if (!ps->items[i].use_wildcard) continue; /* @@ -45,62 +45,14 @@ static int read_one_entry_quick(const unsigned char *sha1, const char *base, int ADD_CACHE_JUST_APPEND); } -static int match_tree_entry(const char *base, int baselen, const char *path, unsigned int mode, const char **paths) -{ - const char *match; - int pathlen; - - if (!paths) - return 1; - pathlen = strlen(path); - while ((match = *paths++) != NULL) { - int matchlen = strlen(match); - - if (baselen >= matchlen) { - /* If it doesn't match, move along... */ - if (strncmp(base, match, matchlen)) - continue; - /* pathspecs match only at the directory boundaries */ - if (!matchlen || - baselen == matchlen || - base[matchlen] == '/' || - match[matchlen - 1] == '/') - return 1; - continue; - } - - /* Does the base match? */ - if (strncmp(base, match, baselen)) - continue; - - match += baselen; - matchlen -= baselen; - - if (pathlen > matchlen) - continue; - - if (matchlen > pathlen) { - if (match[pathlen] != '/') - continue; - if (!S_ISDIR(mode)) - continue; - } - - if (strncmp(path, match, pathlen)) - continue; - - return 1; - } - return 0; -} - -int read_tree_recursive(struct tree *tree, - const char *base, int baselen, - int stage, const char **match, - read_tree_fn_t fn, void *context) +static int read_tree_1(struct tree *tree, struct strbuf *base, + int stage, struct pathspec *pathspec, + read_tree_fn_t fn, void *context) { struct tree_desc desc; struct name_entry entry; + unsigned char sha1[20]; + int len, retval = 0, oldlen = base->len; if (parse_tree(tree)) return -1; @@ -108,10 +60,16 @@ int read_tree_recursive(struct tree *tree, init_tree_desc(&desc, tree->buffer, tree->size); while (tree_entry(&desc, &entry)) { - if (!match_tree_entry(base, baselen, entry.path, entry.mode, match)) - continue; + if (retval != 2) { + retval = tree_entry_interesting(&entry, base, 0, pathspec); + if (retval < 0) + break; + if (retval == 0) + continue; + } - switch (fn(entry.sha1, base, baselen, entry.path, entry.mode, stage, context)) { + switch (fn(entry.sha1, base->buf, base->len, + entry.path, entry.mode, stage, context)) { case 0: continue; case READ_TREE_RECURSIVE: @@ -119,56 +77,55 @@ int read_tree_recursive(struct tree *tree, default: return -1; } - if (S_ISDIR(entry.mode)) { - int retval; - char *newbase; - unsigned int pathlen = tree_entry_len(entry.path, entry.sha1); - - newbase = xmalloc(baselen + 1 + pathlen); - memcpy(newbase, base, baselen); - memcpy(newbase + baselen, entry.path, pathlen); - newbase[baselen + pathlen] = '/'; - retval = read_tree_recursive(lookup_tree(entry.sha1), - newbase, - baselen + pathlen + 1, - stage, match, fn, context); - free(newbase); - if (retval) - return -1; - continue; - } else if (S_ISGITLINK(entry.mode)) { - int retval; - struct strbuf path; - unsigned int entrylen; - struct commit *commit; - entrylen = tree_entry_len(entry.path, entry.sha1); - strbuf_init(&path, baselen + entrylen + 1); - strbuf_add(&path, base, baselen); - strbuf_add(&path, entry.path, entrylen); - strbuf_addch(&path, '/'); + if (S_ISDIR(entry.mode)) + hashcpy(sha1, entry.sha1); + else if (S_ISGITLINK(entry.mode)) { + struct commit *commit; commit = lookup_commit(entry.sha1); if (!commit) - die("Commit %s in submodule path %s not found", - sha1_to_hex(entry.sha1), path.buf); + die("Commit %s in submodule path %s%s not found", + sha1_to_hex(entry.sha1), + base->buf, entry.path); if (parse_commit(commit)) - die("Invalid commit %s in submodule path %s", - sha1_to_hex(entry.sha1), path.buf); - - retval = read_tree_recursive(commit->tree, - path.buf, path.len, - stage, match, fn, context); - strbuf_release(&path); - if (retval) - return -1; - continue; + die("Invalid commit %s in submodule path %s%s", + sha1_to_hex(entry.sha1), + base->buf, entry.path); + + hashcpy(sha1, commit->tree->object.sha1); } + else + continue; + + len = tree_entry_len(entry.path, entry.sha1); + strbuf_add(base, entry.path, len); + strbuf_addch(base, '/'); + retval = read_tree_1(lookup_tree(sha1), + base, stage, pathspec, + fn, context); + strbuf_setlen(base, oldlen); + if (retval) + return -1; } return 0; } +int read_tree_recursive(struct tree *tree, + const char *base, int baselen, + int stage, struct pathspec *pathspec, + read_tree_fn_t fn, void *context) +{ + struct strbuf sb = STRBUF_INIT; + int ret; + + strbuf_add(&sb, base, baselen); + ret = read_tree_1(tree, &sb, stage, pathspec, fn, context); + strbuf_release(&sb); + return ret; +} + static int cmp_cache_name_compare(const void *a_, const void *b_) { const struct cache_entry *ce1, *ce2; @@ -179,7 +136,7 @@ static int cmp_cache_name_compare(const void *a_, const void *b_) ce2->name, ce2->ce_flags); } -int read_tree(struct tree *tree, int stage, const char **match) +int read_tree(struct tree *tree, int stage, struct pathspec *match) { read_tree_fn_t fn = NULL; int i, err; @@ -25,9 +25,9 @@ typedef int (*read_tree_fn_t)(const unsigned char *, const char *, int, const ch extern int read_tree_recursive(struct tree *tree, const char *base, int baselen, - int stage, const char **match, + int stage, struct pathspec *pathspec, read_tree_fn_t fn, void *context); -extern int read_tree(struct tree *tree, int stage, const char **paths); +extern int read_tree(struct tree *tree, int stage, struct pathspec *pathspec); #endif /* TREE_H */ diff --git a/vcs-svn/svndump.c b/vcs-svn/svndump.c index 572a995966..bc792223b2 100644 --- a/vcs-svn/svndump.c +++ b/vcs-svn/svndump.c @@ -13,6 +13,7 @@ #include "line_buffer.h" #include "string_pool.h" #include "strbuf.h" +#include "svndump.h" /* * Compare start of string to literal of equal length; |