diff options
201 files changed, 5087 insertions, 1708 deletions
diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..6b9c715d21 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +* whitespace=!indent,trail,space +*.[ch] whitespace diff --git a/.gitignore b/.gitignore index 7f8421dcd0..165b256e2e 100644 --- a/.gitignore +++ b/.gitignore @@ -50,7 +50,6 @@ git-gc git-get-tar-commit-id git-grep git-hash-object -git-help--browse git-http-fetch git-http-push git-imap-send @@ -136,6 +135,7 @@ git-upload-pack git-var git-verify-pack git-verify-tag +git-web--browse git-whatchanged git-write-tree git-core-*/?* @@ -17,6 +17,7 @@ H. Peter Anvin <hpa@bonde.sc.orionmulti.com> H. Peter Anvin <hpa@tazenda.sc.orionmulti.com> H. Peter Anvin <hpa@trantor.hos.anvin.org> Horst H. von Brand <vonbrand@inf.utfsm.cl> +Jay Soffian <jaysoffian+git@gmail.com> Joachim Berdal Haga <cjhaga@fys.uio.no> Jon Loeliger <jdl@freescale.com> Jon Seymour <jon@blackcubes.dyndns.org> diff --git a/Documentation/.gitattributes b/Documentation/.gitattributes new file mode 100644 index 0000000000..ddb030137d --- /dev/null +++ b/Documentation/.gitattributes @@ -0,0 +1 @@ +*.txt whitespace diff --git a/Documentation/RelNotes-1.5.5.txt b/Documentation/RelNotes-1.5.5.txt new file mode 100644 index 0000000000..c8b4f72c23 --- /dev/null +++ b/Documentation/RelNotes-1.5.5.txt @@ -0,0 +1,78 @@ +GIT v1.5.5 Release Notes +======================== + +Updates since v1.5.4 +-------------------- + +(performance) + + * On platforms with suboptimal qsort(3) implementation, there + is an option to use more reasonable substitute we ship with + our software. + + * New configuration variable "pack.packsizelimit" can be used + in place of command line option --max-pack-size. + + * "git fetch" over the native git protocol used to make a + connection to find out the set of current remote refs and + another to actually download the pack data. We now use only + one connection for these tasks. + + * "git commit" does not run lstat(2) more than necessary + anymore. + +(usability, bells and whistles) + + * You can be warned when core.autocrlf conversion is applied in + such a way that results in an irreversible conversion. + + * A pattern "foo/" in .gitignore file now matches a directory + "foo". Pattern "foo" also matches as before. + + * "git describe" learned to limit the tags to be used for + naming with --match option. + + * "git describe --contains" now barfs when the named commit + cannot be described. + + * bash completion's prompt helper function can talk about + operation in-progress (e.g. merge, rebase, etc.). + + * "git commit" learned a new hook "prepare-commit-msg" that can + inspect what is going to be committed and prepare the commit + log message template to be edited. + + * "git gui" learned an auto-spell checking. + + * "git send-email" learned to prompt for passwords + interactively. + + * "git send-email" learned an easier way to suppress CC + recipients. + + * Various "git cvsimport", "git cvsexportcommit", "git svn" and + "git p4" improvements. + +(internal) + + * Duplicated code between git-help and git-instaweb that + launches user's preferred browser has been refactored. + + * It is now easier to write test scripts that records known + breakages. + + +Fixes since v1.5.4 +------------------ + +All of the fixes in v1.5.4 maintenance series are included in +this release, unless otherwise noted. + + +--- +exec >/var/tmp/1 +O=v1.5.4 +O=v1.5.4.2-122-g7cb97da +echo O=`git describe refs/heads/master` +git shortlog --no-merges $O..refs/heads/master ^refs/heads/maint + diff --git a/Documentation/SubmittingPatches b/Documentation/SubmittingPatches index de08d094e3..0e155c936c 100644 --- a/Documentation/SubmittingPatches +++ b/Documentation/SubmittingPatches @@ -34,9 +34,9 @@ Checklist (and a short version for the impatient): - if your name is not writable in ASCII, make sure that you send off a message in the correct encoding. - send the patch to the list (git@vger.kernel.org) and the - maintainer (gitster@pobox.com). If you use - git-send-email(1), please test it first by sending - email to yourself. + maintainer (gitster@pobox.com) if (and only if) the patch + is ready for inclusion. If you use git-send-email(1), + please test it first by sending email to yourself. Long version: @@ -112,7 +112,12 @@ lose tabs that way if you are not careful. It is a common convention to prefix your subject line with [PATCH]. This lets people easily distinguish patches from other -e-mail discussions. +e-mail discussions. Use of additional markers after PATCH and +the closing bracket to mark the nature of the patch is also +encouraged. E.g. [PATCH/RFC] is often used when the patch is +not ready to be applied but it is for discussion, [PATCH v2], +[PATCH v3] etc. are often seen when you are sending an update to +what you have previously sent. "git format-patch" command follows the best current practice to format the body of an e-mail message. At the beginning of the @@ -157,7 +162,8 @@ Note that your maintainer does not necessarily read everything on the git mailing list. If your patch is for discussion first, send it "To:" the mailing list, and optionally "cc:" him. If it is trivially correct or after the list reached a consensus, send -it "To:" the maintainer and optionally "cc:" the list. +it "To:" the maintainer and optionally "cc:" the list for +inclusion. Also note that your maintainer does not actively involve himself in maintaining what are in contrib/ hierarchy. When you send fixes and @@ -210,10 +216,53 @@ then you just add a line saying This line can be automatically added by git if you run the git-commit command with the -s option. -Some people also put extra tags at the end. They'll just be ignored for -now, but you can do this to mark internal company procedures or just -point out some special detail about the sign-off. +Notice that you can place your own Signed-off-by: line when +forwarding somebody else's patch with the above rules for +D-C-O. Indeed you are encouraged to do so. Do not forget to +place an in-body "From: " line at the beginning to properly attribute +the change to its true author (see (2) above). +Some people also put extra tags at the end. + +"Acked-by:" says that the patch was reviewed by the person who +is more familiar with the issues and the area the patch attempts +to modify. "Tested-by:" says the patch was tested by the person +and found to have the desired effect. + +------------------------------------------------ +An ideal patch flow + +Here is an ideal patch flow for this project the current maintainer +suggests to the contributors: + + (0) You come up with an itch. You code it up. + + (1) Send it to the list and cc people who may need to know about + the change. + + The people who may need to know are the ones whose code you + are butchering. These people happen to be the ones who are + most likely to be knowledgeable enough to help you, but + they have no obligation to help you (i.e. you ask for help, + don't demand). "git log -p -- $area_you_are_modifying" would + help you find out who they are. + + (2) You get comments and suggestions for improvements. You may + even get them in a "on top of your change" patch form. + + (3) Polish, refine, and re-send to the list and the people who + spend their time to improve your patch. Go back to step (2). + + (4) The list forms consensus that the last round of your patch is + good. Send it to the list and cc the maintainer. + + (5) A topic branch is created with the patch and is merged to 'next', + and cooked further and eventually graduates to 'master'. + +In any time between the (2)-(3) cycle, the maintainer may pick it up +from the list and queue it to 'pu', in order to make it easier for +people play with it without having to pick up and apply the patch to +their trees themselves. ------------------------------------------------ MUA specific hints diff --git a/Documentation/config.txt b/Documentation/config.txt index 6d8cca46ab..7b676710ba 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -139,6 +139,51 @@ core.autocrlf:: "text" (i.e. be subjected to the autocrlf mechanism) is decided purely based on the contents. +core.safecrlf:: + If true, makes git check if converting `CRLF` as controlled by + `core.autocrlf` is reversible. Git will verify if a command + modifies a file in the work tree either directly or indirectly. + For example, committing a file followed by checking out the + same file should yield the original file in the work tree. If + this is not the case for the current setting of + `core.autocrlf`, git will reject the file. The variable can + be set to "warn", in which case git will only warn about an + irreversible conversion but continue the operation. ++ +CRLF conversion bears a slight chance of corrupting data. +autocrlf=true will convert CRLF to LF during commit and LF to +CRLF during checkout. A file that contains a mixture of LF and +CRLF before the commit cannot be recreated by git. For text +files this is the right thing to do: it corrects line endings +such that we have only LF line endings in the repository. +But for binary files that are accidentally classified as text the +conversion can corrupt data. ++ +If you recognize such corruption early you can easily fix it by +setting the conversion type explicitly in .gitattributes. Right +after committing you still have the original file in your work +tree and this file is not yet corrupted. You can explicitly tell +git that this file is binary and git will handle the file +appropriately. ++ +Unfortunately, the desired effect of cleaning up text files with +mixed line endings and the undesired effect of corrupting binary +files cannot be distinguished. In both cases CRLFs are removed +in an irreversible way. For text files this is the right thing +to do because CRLFs are line endings, while for binary files +converting CRLFs corrupts data. ++ +Note, this safety check does not mean that a checkout will generate a +file identical to the original file for a different setting of +`core.autocrlf`, but only for the current one. For example, a text +file with `LF` would be accepted with `core.autocrlf=input` and could +later be checked out with `core.autocrlf=true`, in which case the +resulting file would contain `CRLF`, although the original file +contained `LF`. However, in both work trees the line endings would be +consistent, that is either all `LF` or all `CRLF`, but never mixed. A +file with mixed line endings would be reported by the `core.safecrlf` +mechanism. + core.symlinks:: If false, symbolic links are checked out as small plain files that contain the link text. linkgit:git-update-index[1] and @@ -444,6 +489,13 @@ color.status.<slot>:: commit.template:: Specify a file to use as the template for new commit messages. +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. + diff.autorefreshindex:: When using `git diff` to compare with work tree files, do not consider stat-only change as changed. @@ -766,6 +818,12 @@ pack.indexVersion:: whenever the corresponding pack is larger than 2 GB. Otherwise the default is 1. +pack.packSizeLimit: + The default maximum size of a pack. This setting only affects + packing to a file, i.e. the git:// protocol is unaffected. It + can be overridden by the `\--max-pack-size` option of + linkgit:git-repack[1]. + pull.octopus:: The default merge strategy to use when pulling multiple branches at once. diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt index c3725b2ed9..b4ae61ff46 100644 --- a/Documentation/git-commit.txt +++ b/Documentation/git-commit.txt @@ -280,8 +280,8 @@ order). HOOKS ----- -This command can run `commit-msg`, `pre-commit`, and -`post-commit` hooks. See link:hooks.html[hooks] for more +This command can run `commit-msg`, `prepare-commit-msg`, `pre-commit`, +and `post-commit` hooks. See link:hooks.html[hooks] for more information. diff --git a/Documentation/git-describe.txt b/Documentation/git-describe.txt index 0742152b81..1c3dfb40c6 100644 --- a/Documentation/git-describe.txt +++ b/Documentation/git-describe.txt @@ -51,6 +51,10 @@ OPTIONS being employed to standard error. The tag name will still be printed to standard out. +--match <pattern>:: + Only consider tags matching the given pattern (can be used to avoid + leaking private tags made from the repository). + EXAMPLES -------- diff --git a/Documentation/git-fast-import.txt b/Documentation/git-fast-import.txt index bd625ababf..96f6767075 100644 --- a/Documentation/git-fast-import.txt +++ b/Documentation/git-fast-import.txt @@ -805,6 +805,93 @@ Placing a `progress` command immediately after a `checkpoint` will inform the reader when the `checkpoint` has been completed and it can safely access the refs that fast-import updated. +Crash Reports +------------- +If fast-import is supplied invalid input it will terminate with a +non-zero exit status and create a crash report in the top level of +the Git repository it was importing into. Crash reports contain +a snapshot of the internal fast-import state as well as the most +recent commands that lead up to the crash. + +All recent commands (including stream comments, file changes and +progress commands) are shown in the command history within the crash +report, but raw file data and commit messages are excluded from the +crash report. This exclusion saves space within the report file +and reduces the amount of buffering that fast-import must perform +during execution. + +After writing a crash report fast-import will close the current +packfile and export the marks table. This allows the frontend +developer to inspect the repository state and resume the import from +the point where it crashed. The modified branches and tags are not +updated during a crash, as the import did not complete successfully. +Branch and tag information can be found in the crash report and +must be applied manually if the update is needed. + +An example crash: + +==== + $ cat >in <<END_OF_INPUT + # my very first test commit + commit refs/heads/master + committer Shawn O. Pearce <spearce> 19283 -0400 + # who is that guy anyway? + data <<EOF + this is my commit + EOF + M 644 inline .gitignore + data <<EOF + .gitignore + EOF + M 777 inline bob + END_OF_INPUT + + $ git-fast-import <in + fatal: Corrupt mode: M 777 inline bob + fast-import: dumping crash report to .git/fast_import_crash_8434 + + $ cat .git/fast_import_crash_8434 + fast-import crash report: + fast-import process: 8434 + parent process : 1391 + at Sat Sep 1 00:58:12 2007 + + fatal: Corrupt mode: M 777 inline bob + + Most Recent Commands Before Crash + --------------------------------- + # my very first test commit + commit refs/heads/master + committer Shawn O. Pearce <spearce> 19283 -0400 + # who is that guy anyway? + data <<EOF + M 644 inline .gitignore + data <<EOF + * M 777 inline bob + + Active Branch LRU + ----------------- + active_branches = 1 cur, 5 max + + pos clock name + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 1) 0 refs/heads/master + + Inactive Branches + ----------------- + refs/heads/master: + status : active loaded dirty + tip commit : 0000000000000000000000000000000000000000 + old tree : 0000000000000000000000000000000000000000 + cur tree : 0000000000000000000000000000000000000000 + commit clock: 0 + last pack : + + + ------------------- + END OF CRASH REPORT +==== + Tips and Tricks --------------- The following tips and tricks have been collected from various diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt index f3cb24f252..71a73354f8 100644 --- a/Documentation/git-grep.txt +++ b/Documentation/git-grep.txt @@ -75,9 +75,11 @@ OPTIONS -n:: Prefix the line number to matching lines. --l | --files-with-matches | -L | --files-without-match:: +-l | --files-with-matches | --name-only | -L | --files-without-match:: Instead of showing every matched line, show only the names of files that contain (or do not contain) matches. + For better compatability with git-diff, --name-only is a + synonym for --files-with-matches. -c | --count:: Instead of showing every matched line, show the number of diff --git a/Documentation/git-help.txt b/Documentation/git-help.txt index fb77ca3a57..0926dc12ba 100644 --- a/Documentation/git-help.txt +++ b/Documentation/git-help.txt @@ -47,27 +47,9 @@ OPTIONS + The web browser can be specified using the configuration variable 'help.browser', or 'web.browser' if the former is not set. If none of -these config variables is set, the 'git-help--browse' helper script -(called by 'git-help') will pick a suitable default. -+ -You can explicitly provide a full path to your preferred browser by -setting the configuration variable 'browser.<tool>.path'. For example, -you can configure the absolute path to firefox by setting -'browser.firefox.path'. Otherwise, 'git-help--browse' assumes the tool -is available in PATH. -+ -Note that the script tries, as much as possible, to display the HTML -page in a new tab on an already opened browser. -+ -The following browsers are currently supported by 'git-help--browse': -+ -* firefox (this is the default under X Window when not using KDE) -* iceweasel -* konqueror (this is the default under KDE) -* w3m (this is the default outside X Window) -* links -* lynx -* dillo +these config variables is set, the 'git-web--browse' helper script +(called by 'git-help') will pick a suitable default. See +linkgit:git-web--browse[1] for more information about this. CONFIGURATION VARIABLES ----------------------- @@ -84,7 +66,7 @@ line option: The 'help.browser', 'web.browser' and 'browser.<tool>.path' will also be checked if the 'web' format is chosen (either by command line option or configuration variable). See '-w|--web' in the OPTIONS -section above. +section above and linkgit:git-web--browse[1]. Note that these configuration variables should probably be set using the '--global' flag, for example like this: diff --git a/Documentation/git-instaweb.txt b/Documentation/git-instaweb.txt index 841e8fac7f..51f1532ef7 100644 --- a/Documentation/git-instaweb.txt +++ b/Documentation/git-instaweb.txt @@ -38,10 +38,11 @@ OPTIONS The port number to bind the httpd to. (Default: 1234) -b|--browser:: - - The web browser command-line to execute to view the gitweb page. - If blank, the URL of the gitweb instance will be printed to - stdout. (Default: 'firefox') + The web browser that should be used to view the gitweb + page. This will be passed to the 'git-web--browse' helper + script along with the URL of the gitweb instance. See + linkgit:git-web--browse[1] for more information about this. If + the script fails, the URL will be printed to stdout. --start:: Start the httpd instance and exit. This does not generate @@ -72,7 +73,8 @@ You may specify configuration in your .git/config ----------------------------------------------------------------------- If the configuration variable 'instaweb.browser' is not set, -'web.browser' will be used instead if it is defined. +'web.browser' will be used instead if it is defined. See +linkgit:git-web--browse[1] for more information about this. Author ------ diff --git a/Documentation/git-merge-index.txt b/Documentation/git-merge-index.txt index 5d816d0d8b..19ee017aed 100644 --- a/Documentation/git-merge-index.txt +++ b/Documentation/git-merge-index.txt @@ -8,7 +8,7 @@ git-merge-index - Run a merge for files needing merging SYNOPSIS -------- -'git-merge-index' [-o] [-q] <merge-program> (-a | \-- | <file>\*) +'git-merge-index' [-o] [-q] <merge-program> (-a | [--] <file>\*) DESCRIPTION ----------- diff --git a/Documentation/git-pack-objects.txt b/Documentation/git-pack-objects.txt index 74cc7c1cb8..8353be186f 100644 --- a/Documentation/git-pack-objects.txt +++ b/Documentation/git-pack-objects.txt @@ -99,7 +99,8 @@ base-name:: --max-pack-size=<n>:: Maximum size of each output packfile, expressed in MiB. If specified, multiple packfiles may be created. - The default is unlimited. + The default is unlimited, unless the config variable + `pack.packSizeLimit` is set. --incremental:: This flag causes an object already in a pack ignored diff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt index 179bdfc69d..737894390d 100644 --- a/Documentation/git-pull.txt +++ b/Documentation/git-pull.txt @@ -15,6 +15,7 @@ DESCRIPTION ----------- Runs `git-fetch` with the given parameters, and calls `git-merge` to merge the retrieved head(s) into the current branch. +With `--rebase`, calls `git-rebase` instead of `git-merge`. Note that you can use `.` (current directory) as the <repository> to pull from the local repository -- this is useful @@ -26,19 +27,14 @@ OPTIONS include::merge-options.txt[] :git-pull: 1 -include::fetch-options.txt[] - -include::pull-fetch-param.txt[] - -include::urls-remotes.txt[] - -include::merge-strategies.txt[] \--rebase:: Instead of a merge, perform a rebase after fetching. If there is a remote ref for the upstream branch, and this branch was rebased since last fetched, the rebase uses that information - to avoid rebasing non-local changes. + to avoid rebasing non-local changes. To make this the default + for branch `<name>`, set configuration `branch.<name>.rebase` + to `true`. + *NOTE:* This is a potentially _dangerous_ mode of operation. It rewrites history, which does not bode well when you @@ -48,6 +44,14 @@ unless you have read linkgit:git-rebase[1] carefully. \--no-rebase:: Override earlier \--rebase. +include::fetch-options.txt[] + +include::pull-fetch-param.txt[] + +include::urls-remotes.txt[] + +include::merge-strategies.txt[] + DEFAULT BEHAVIOUR ----------------- diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt index 0554f2b374..336d797e80 100644 --- a/Documentation/git-send-email.txt +++ b/Documentation/git-send-email.txt @@ -96,11 +96,40 @@ The --cc option must be repeated for each user you want on the cc list. servers typically listen to smtp port 25 and ssmtp port 465). ---smtp-user, --smtp-pass:: - Username and password for SMTP-AUTH. Defaults are the values of - the configuration values 'sendemail.smtpuser' and - 'sendemail.smtppass', but see also 'sendemail.identity'. - If not set, authentication is not attempted. +--smtp-user:: + Username for SMTP-AUTH. In place of this option, the following + configuration variables can be specified: ++ +-- + * sendemail.smtpuser + * sendemail.<identity>.smtpuser (see sendemail.identity). +-- ++ +However, --smtp-user always overrides these variables. ++ +If a username is not specified (with --smtp-user or a +configuration variable), then authentication is not attempted. + +--smtp-pass:: + Password for SMTP-AUTH. The argument is optional: If no + argument is specified, then the empty string is used as + the password. ++ +In place of this option, the following configuration variables +can be specified: ++ +-- + * sendemail.smtppass + * sendemail.<identity>.smtppass (see sendemail.identity). +-- ++ +However, --smtp-pass always overrides these variables. ++ +Furthermore, passwords need not be specified in configuration files +or on the command line. If a username has been specified (with +--smtp-user or a configuration variable), but no password has been +specified (with --smtp-pass or a configuration variable), then the +user is prompted for a password while the input is masked for privacy. --smtp-ssl:: If set, connects to the SMTP server using SSL. @@ -117,6 +146,17 @@ The --cc option must be repeated for each user you want on the cc list. Default is the value of 'sendemail.suppressfrom' configuration value; if that is unspecified, default to --no-suppress-from. +--suppress-cc:: + Specify an additional category of recipients to suppress the + auto-cc of. 'self' will avoid including the sender, 'author' will + avoid including the patch author, 'cc' will avoid including anyone + mentioned in Cc lines in the patch, 'sob' will avoid including + anyone mentioned in Signed-off-by lines, and 'cccmd' will avoid + running the --cc-cmd. 'all' will suppress all auto cc values. + Default is the value of 'sendemail.suppresscc' configuration value; + if that is unspecified, default to 'self' if --suppress-from is + specified, as well as 'sob' if --no-signed-off-cc is specified. + --thread, --no-thread:: If this is set, the In-Reply-To header will be set on each email sent. If disabled with "--no-thread", no emails will have the In-Reply-To diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index b1d527f74c..340f1be02a 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -161,6 +161,13 @@ New features: + Any other arguments are passed directly to `git log' +'blame':: + Show what revision and author last modified each line of a file. This is + identical to `git blame', but SVN revision numbers are shown instead of git + commit hashes. ++ +All arguments are passed directly to `git blame'. + -- 'find-rev':: When given an SVN revision number of the form 'rN', returns the diff --git a/Documentation/git-web--browse.txt b/Documentation/git-web--browse.txt new file mode 100644 index 0000000000..df57d010e5 --- /dev/null +++ b/Documentation/git-web--browse.txt @@ -0,0 +1,78 @@ +git-web--browse(1) +================== + +NAME +---- +git-web--browse - git helper script to launch a web browser + +SYNOPSIS +-------- +'git-web--browse' [OPTIONS] URL/FILE ... + +DESCRIPTION +----------- + +This script tries, as much as possible, to display the URLs and FILEs +that are passed as arguments, as HTML pages in new tabs on an already +opened web browser. + +The following browsers (or commands) are currently supported: + +* firefox (this is the default under X Window when not using KDE) +* iceweasel +* konqueror (this is the default under KDE) +* w3m (this is the default outside graphical environments) +* links +* lynx +* dillo +* open (this is the default under Mac OS X GUI) + +OPTIONS +------- +-b BROWSER|--browser=BROWSER:: + Use the specified BROWSER. It must be in the list of supported + browsers. + +-t BROWSER|--tool=BROWSER:: + Same as above. + +-c CONF.VAR|--config=CONF.VAR:: + CONF.VAR is looked up in the git config files. If it's set, + then its value specify the browser that should be used. + +CONFIGURATION VARIABLES +----------------------- + +The web browser can be specified using a configuration variable passed +with the -c (or --config) command line option, or the 'web.browser' +configuration variable if the former is not used. + +You can explicitly provide a full path to your preferred browser by +setting the configuration variable 'browser.<tool>.path'. For example, +you can configure the absolute path to firefox by setting +'browser.firefox.path'. Otherwise, 'git-web--browse' assumes the tool +is available in PATH. + +Note that these configuration variables should probably be set using +the '--global' flag, for example like this: + +------------------------------------------------ +$ git config --global web.browser firefox +------------------------------------------------ + +as they are probably more user specific than repository specific. +See linkgit:git-config[1] for more information about this. + +Author +------ +Written by Christian Couder <chriscool@tuxfamily.org> and the git-list +<git@vger.kernel.org>, based on git-mergetool by Theodore Y. Ts'o. + +Documentation +------------- +Documentation by Christian Couder <chriscool@tuxfamily.org> and the +git-list <git@vger.kernel.org>. + +GIT +--- +Part of the linkgit:git[7] suite diff --git a/Documentation/git.txt b/Documentation/git.txt index 17aee93ec5..d57bed618f 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -43,9 +43,11 @@ unreleased) version of git, that is available from 'master' branch of the `git.git` repository. Documentation for older releases are available here: -* link:v1.5.4/git.html[documentation for release 1.5.4] +* link:v1.5.4.2/git.html[documentation for release 1.5.4.2] * release notes for + link:RelNotes-1.5.4.2.txt[1.5.4.2], + link:RelNotes-1.5.4.1.txt[1.5.4.1], link:RelNotes-1.5.4.txt[1.5.4]. * link:v1.5.3.8/git.html[documentation for release 1.5.3.8] diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt index 35a29fd60c..84ec9623a2 100644 --- a/Documentation/gitattributes.txt +++ b/Documentation/gitattributes.txt @@ -133,6 +133,26 @@ When `core.autocrlf` is set to "input", line endings are converted to LF upon checkin, but there is no conversion done upon checkout. +If `core.safecrlf` is set to "true" or "warn", git verifies if +the conversion is reversible for the current setting of +`core.autocrlf`. For "true", git rejects irreversible +conversions; for "warn", git only prints a warning but accepts +an irreversible conversion. The safety triggers to prevent such +a conversion done to the files in the work tree, but there are a +few exceptions. Even though... + +- "git add" itself does not touch the files in the work tree, the + next checkout would, so the safety triggers; + +- "git apply" to update a text file with a patch does touch the files + in the work tree, but the operation is about text files and CRLF + conversion is about fixing the line ending inconsistencies, so the + safety does not trigger; + +- "git diff" itself does not touch the files in the work tree, it is + often run to inspect the changes you intend to next "git add". To + catch potential problems early, safety triggers. + `ident` ^^^^^^^ diff --git a/Documentation/gitignore.txt b/Documentation/gitignore.txt index 08373f52bb..e847b3ba63 100644 --- a/Documentation/gitignore.txt +++ b/Documentation/gitignore.txt @@ -57,6 +57,13 @@ Patterns have the following format: included again. If a negated pattern matches, this will override lower precedence patterns sources. + - If the pattern ends with a slash, it is removed for the + purpose of the following description, but it would only find + a match with a directory. In other words, `foo/` will match a + directory `foo` and paths underneath it, but will not match a + regular file or a symbolic link `foo` (this is consistent + with the way how pathspec works in general in git). + - If the pattern does not contain a slash '/', git treats it as a shell glob pattern and checks for a match against the pathname without leading directories. diff --git a/Documentation/hooks.txt b/Documentation/hooks.txt index f110162b01..76b8d77460 100644 --- a/Documentation/hooks.txt +++ b/Documentation/hooks.txt @@ -61,6 +61,35 @@ The default 'pre-commit' hook, when enabled, catches introduction of lines with trailing whitespaces and aborts the commit when such a line is found. +All the `git-commit` hooks are invoked with the environment +variable `GIT_EDITOR=:` if the command will not bring up an editor +to modify the commit message. + +prepare-commit-msg +------------------ + +This hook is invoked by `git-commit` right after preparing the +default log message, and before the editor is started. + +It takes one to three parameters. The first is the name of the file +that the commit log message. The second is the source of the commit +message, and can be: `message` (if a `\-m` or `\-F` option was +given); `template` (if a `\-t` option was given or the +configuration option `commit.template` is set); `merge` (if the +commit is a merge or a `.git/MERGE_MSG` file exists); `squash` +(if a `.git/SQUASH_MSG` file exists); or `commit`, followed by +a commit SHA1 (if a `\-c`, `\-C` or `\--amend` option was given). + +If the exit status is non-zero, `git-commit` will abort. + +The purpose of the hook is to edit the message file in place, and +it is not suppressed by the `\--no-verify` option. A non-zero exit +means a failure of the hook and aborts the commit. It should not +be used as replacement for pre-commit hook. + +The sample `prepare-commit-msg` hook that comes with git comments +out the `Conflicts:` part of a merge's commit message. + commit-msg ---------- diff --git a/Documentation/technical/api-remote.txt b/Documentation/technical/api-remote.txt new file mode 100644 index 0000000000..073b22bd83 --- /dev/null +++ b/Documentation/technical/api-remote.txt @@ -0,0 +1,123 @@ +Remotes configuration API +========================= + +The API in remote.h gives access to the configuration related to +remotes. It handles all three configuration mechanisms historically +and currently used by git, and presents the information in a uniform +fashion. Note that the code also handles plain URLs without any +configuration, giving them just the default information. + +struct remote +------------- + +`name`:: + + The user's nickname for the remote + +`url`:: + + An array of all of the url_nr URLs configured for the remote + +`push`:: + + An array of refspecs configured for pushing, with + push_refspec being the literal strings, and push_refspec_nr + being the quantity. + +`fetch`:: + + An array of refspecs configured for fetching, with + fetch_refspec being the literal strings, and fetch_refspec_nr + being the quantity. + +`fetch_tags`:: + + The setting for whether to fetch tags (as a separate rule from + the configured refspecs); -1 means never to fetch tags, 0 + means to auto-follow tags based on the default heuristic, 1 + means to always auto-follow tags, and 2 means to fetch all + tags. + +`receivepack`, `uploadpack`:: + + The configured helper programs to run on the remote side, for + git-native protocols. + +`http_proxy`:: + + The proxy to use for curl (http, https, ftp, etc.) URLs. + +struct remotes can be found by name with remote_get(), and iterated +through with for_each_remote(). remote_get(NULL) will return the +default remote, given the current branch and configuration. + +struct refspec +-------------- + +A struct refspec holds the parsed interpretation of a refspec. If it +will force updates (starts with a '+'), force is true. If it is a +pattern (sides end with '*') pattern is true. src and dest are the two +sides (if a pattern, only the part outside of the wildcards); if there +is only one side, it is src, and dst is NULL; if sides exist but are +empty (i.e., the refspec either starts or ends with ':'), the +corresponding side is "". + +This parsing can be done to an array of strings to give an array of +struct refpsecs with parse_ref_spec(). + +remote_find_tracking(), given a remote and a struct refspec with +either src or dst filled out, will fill out the other such that the +result is in the "fetch" specification for the remote (note that this +evaluates patterns and returns a single result). + +struct branch +------------- + +Note that this may end up moving to branch.h + +struct branch holds the configuration for a branch. It can be looked +up with branch_get(name) for "refs/heads/{name}", or with +branch_get(NULL) for HEAD. + +It contains: + +`name`:: + + The short name of the branch. + +`refname`:: + + The full path for the branch ref. + +`remote_name`:: + + The name of the remote listed in the configuration. + +`remote`:: + + The struct remote for that remote. + +`merge_name`:: + + An array of the "merge" lines in the configuration. + +`merge`:: + + An array of the struct refspecs used for the merge lines. That + is, merge[i]->dst is a local tracking ref which should be + merged into this branch by default. + +`merge_nr`:: + + The number of merge configurations + +branch_has_merge_config() returns true if the given branch has merge +configuration given. + +Other stuff +----------- + +There is other stuff in remote.h that is related, in general, to the +process of interacting with remotes. + +(Daniel Barkalow) diff --git a/Documentation/technical/api-run-command.txt b/Documentation/technical/api-run-command.txt index 19d2f64f73..dfbf9ac5d0 100644 --- a/Documentation/technical/api-run-command.txt +++ b/Documentation/technical/api-run-command.txt @@ -1,10 +1,171 @@ run-command API =============== -Talk about <run-command.h>, and things like: +The run-command API offers a versatile tool to run sub-processes with +redirected input and output as well as with a modified environment +and an alternate current directory. -* Environment the command runs with (e.g. GIT_DIR); -* File descriptors and pipes; -* Exit status; +A similar API offers the capability to run a function asynchronously, +which is primarily used to capture the output that the function +produces in the caller in order to process it. -(Hannes, Dscho, Shawn) + +Functions +--------- + +`start_command`:: + + Start a sub-process. Takes a pointer to a `struct child_process` + that specifies the details and returns pipe FDs (if requested). + See below for details. + +`finish_command`:: + + Wait for the completion of a sub-process that was started with + start_command(). + +`run_command`:: + + A convenience function that encapsulates a sequence of + start_command() followed by finish_command(). Takes a pointer + to a `struct child_process` that specifies the details. + +`run_command_v_opt`, `run_command_v_opt_dir`, `run_command_v_opt_cd_env`:: + + Convenience functions that encapsulate a sequence of + start_command() followed by finish_command(). The argument argv + specifies the program and its arguments. The argument opt is zero + or more of the flags `RUN_COMMAND_NO_STDIN`, `RUN_GIT_CMD`, or + `RUN_COMMAND_STDOUT_TO_STDERR` that correspond to the members + .no_stdin, .git_cmd, .stdout_to_stderr of `struct child_process`. + The argument dir corresponds the member .dir. The argument env + corresponds to the member .env. + +`start_async`:: + + Run a function asynchronously. Takes a pointer to a `struct + async` that specifies the details and returns a pipe FD + from which the caller reads. See below for details. + +`finish_async`:: + + Wait for the completeion of an asynchronous function that was + started with start_async(). + + +Data structures +--------------- + +* `struct child_process` + +This describes the arguments, redirections, and environment of a +command to run in a sub-process. + +The caller: + +1. allocates and clears (memset(&chld, '0', sizeof(chld));) a + struct child_process variable; +2. initializes the members; +3. calls start_command(); +4. processes the data; +5. closes file descriptors (if necessary; see below); +6. calls finish_command(). + +The .argv member is set up as an array of string pointers (NULL +terminated), of which .argv[0] is the program name to run (usually +without a path). If the command to run is a git command, set argv[0] to +the command name without the 'git-' prefix and set .git_cmd = 1. + +The members .in, .out, .err are used to redirect stdin, stdout, +stderr as follows: + +. Specify 0 to request no special redirection. No new file descriptor + is allocated. The child process simply inherits the channel from the + parent. + +. Specify -1 to have a pipe allocated; start_command() replaces -1 + by the pipe FD in the following way: + + .in: Returns the writable pipe end into which the caller writes; + the readable end of the pipe becomes the child's stdin. + + .out, .err: Returns the readable pipe end from which the caller + reads; the writable end of the pipe end becomes child's + stdout/stderr. + + The caller of start_command() must close the so returned FDs + after it has completed reading from/writing to it! + +. Specify a file descriptor > 0 to be used by the child: + + .in: The FD must be readable; it becomes child's stdin. + .out: The FD must be writable; it becomes child's stdout. + .err > 0 is not supported. + + The specified FD is closed by start_command(), even if it fails to + run the sub-process! + +. Special forms of redirection are available by setting these members + to 1: + + .no_stdin, .no_stdout, .no_stderr: The respective channel is + redirected to /dev/null. + + .stdout_to_stderr: stdout of the child is redirected to the + parent's stderr (i.e. *not* to what .err or + .no_stderr specify). + +To modify the environment of the sub-process, specify an array of +string pointers (NULL terminated) in .env: + +. If the string is of the form "VAR=value", i.e. it contains '=' + the variable is added to the child process's environment. + +. If the string does not contain '=', it names an environement + variable that will be removed from the child process's envionment. + +To specify a new initial working directory for the sub-process, +specify it in the .dir member. + + +* `struct async` + +This describes a function to run asynchronously, whose purpose is +to produce output that the caller reads. + +The caller: + +1. allocates and clears (memset(&asy, '0', sizeof(asy));) a + struct async variable; +2. initializes .proc and .data; +3. calls start_async(); +4. processes the data by reading from the fd in .out; +5. closes .out; +6. calls finish_async(). + +The function pointer in .proc has the following signature: + + int proc(int fd, void *data); + +. fd specifies a writable file descriptor to which the function must + write the data that it produces. The function *must* close this + descriptor before it returns. + +. data is the value that the caller has specified in the .data member + of struct async. + +. The return value of the function is 0 on success and non-zero + on failure. If the function indicates failure, finish_async() will + report failure as well. + + +There are serious restrictions on what the asynchronous function can do +because this facility is implemented by a pipe to a forked process on +UNIX, but by a thread in the same address space on Windows: + +. It cannot change the program's state (global variables, environment, + etc.) in a way that the caller notices; in other words, .out is the + only communication channel to the caller. + +. It must not change the program's state that the caller of the + facility also uses. diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 03fb9d76ae..1ad324e236 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v1.5.4.2.GIT +DEF_VER=v1.5.4.GIT LF=' ' @@ -3,6 +3,9 @@ all:: # Define V=1 to have a more verbose compile. # +# Define FREAD_READS_DIRECTORIES if your are on a system which succeeds +# when attempting to read from an fopen'ed directory. +# # Define NO_OPENSSL environment variable if you do not have OpenSSL. # This also implies MOZILLA_SHA1. # @@ -137,6 +140,10 @@ all:: # Define THREADED_DELTA_SEARCH if you have pthreads and wish to exploit # parallel delta searching when packing objects. # +# Define INTERNAL_QSORT to use Git's implementation of qsort(), which +# is a simplified version of the merge sort used in glibc. This is +# recommended if Git triggers O(n^2) behavior in your platform's qsort(). +# GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE @$(SHELL_PATH) ./GIT-VERSION-GEN @@ -231,7 +238,7 @@ SCRIPT_SH = \ git-lost-found.sh git-quiltimport.sh git-submodule.sh \ git-filter-branch.sh \ git-stash.sh \ - git-help--browse.sh + git-web--browse.sh SCRIPT_PERL = \ git-add--interactive.perl \ @@ -618,6 +625,10 @@ endif ifdef NO_C99_FORMAT BASIC_CFLAGS += -DNO_C99_FORMAT endif +ifdef FREAD_READS_DIRECTORIES + COMPAT_CFLAGS += -DFREAD_READS_DIRECTORIES + COMPAT_OBJS += compat/fopen.o +endif ifdef NO_SYMLINK_HEAD BASIC_CFLAGS += -DNO_SYMLINK_HEAD endif @@ -722,6 +733,10 @@ ifdef NO_MEMMEM COMPAT_CFLAGS += -DNO_MEMMEM COMPAT_OBJS += compat/memmem.o endif +ifdef INTERNAL_QSORT + COMPAT_CFLAGS += -DINTERNAL_QSORT + COMPAT_OBJS += compat/qsort.o +endif ifdef THREADED_DELTA_SEARCH BASIC_CFLAGS += -DTHREADED_DELTA_SEARCH @@ -819,6 +834,7 @@ git$X: git.o $(BUILTIN_OBJS) $(GITLIBS) help.o: help.c common-cmds.h GIT-CFLAGS $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) \ + '-DGIT_HTML_PATH="$(htmldir_SQ)"' \ '-DGIT_MAN_PATH="$(mandir_SQ)"' \ '-DGIT_INFO_PATH="$(infodir_SQ)"' $< @@ -839,7 +855,6 @@ $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh -e 's|@@PERL@@|$(PERL_PATH_SQ)|g' \ -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \ -e 's/@@NO_CURL@@/$(NO_CURL)/g' \ - -e 's|@@HTMLDIR@@|$(htmldir_SQ)|g' \ $@.sh >$@+ && \ chmod +x $@+ && \ mv $@+ $@ @@ -1 +1 @@ -Documentation/RelNotes-1.5.4.2.txt
\ No newline at end of file +Documentation/RelNotes-1.5.5.txt
\ No newline at end of file diff --git a/builtin-add.c b/builtin-add.c index 4a91e3eb11..820110e085 100644 --- a/builtin-add.c +++ b/builtin-add.c @@ -228,6 +228,18 @@ int cmd_add(int argc, const char **argv, const char *prefix) goto finish; } + if (*argv) { + /* Was there an invalid path? */ + if (pathspec) { + int num; + for (num = 0; pathspec[num]; num++) + ; /* just counting */ + if (argc != num) + exit(1); /* error message already given */ + } else + exit(1); /* error message already given */ + } + fill_directory(&dir, pathspec, ignored_too); if (show_only) { diff --git a/builtin-apply.c b/builtin-apply.c index a11b1bbeee..6a88ff018d 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -1430,7 +1430,7 @@ static int read_old_data(struct stat *st, const char *path, struct strbuf *buf) case S_IFREG: if (strbuf_read_file(buf, path, st->st_size) != st->st_size) return error("unable to open or read %s", path); - convert_to_git(path, buf->buf, buf->len, buf); + convert_to_git(path, buf->buf, buf->len, buf, 0); return 0; default: return -1; @@ -1946,7 +1946,7 @@ static int read_file_or_gitlink(struct cache_entry *ce, struct strbuf *buf) if (!ce) return 0; - if (S_ISGITLINK(ntohl(ce->ce_mode))) { + if (S_ISGITLINK(ce->ce_mode)) { strbuf_grow(buf, 100); strbuf_addf(buf, "Subproject commit %s\n", sha1_to_hex(ce->sha1)); } else { @@ -2023,7 +2023,7 @@ static int check_to_create_blob(const char *new_name, int ok_if_exists) static int verify_index_match(struct cache_entry *ce, struct stat *st) { - if (S_ISGITLINK(ntohl(ce->ce_mode))) { + if (S_ISGITLINK(ce->ce_mode)) { if (!S_ISDIR(st->st_mode)) return -1; return 0; @@ -2082,12 +2082,12 @@ static int check_patch(struct patch *patch, struct patch *prev_patch) return error("%s: does not match index", old_name); if (cached) - st_mode = ntohl(ce->ce_mode); + st_mode = ce->ce_mode; } else if (stat_ret < 0) return error("%s: %s", old_name, strerror(errno)); if (!cached) - st_mode = ntohl(ce_mode_from_stat(ce, st.st_mode)); + st_mode = ce_mode_from_stat(ce, st.st_mode); if (patch->is_new < 0) patch->is_new = 0; @@ -2388,7 +2388,7 @@ static void add_index_file(const char *path, unsigned mode, void *buf, unsigned ce = xcalloc(1, ce_size); memcpy(ce->name, path, namelen); ce->ce_mode = create_ce_mode(mode); - ce->ce_flags = htons(namelen); + ce->ce_flags = namelen; if (S_ISGITLINK(mode)) { const char *s = buf; diff --git a/builtin-blame.c b/builtin-blame.c index 9b4c02e87f..59d7237f21 100644 --- a/builtin-blame.c +++ b/builtin-blame.c @@ -1894,9 +1894,7 @@ static unsigned parse_score(const char *arg) static const char *add_prefix(const char *prefix, const char *path) { - if (!prefix || !prefix[0]) - return path; - return prefix_path(prefix, strlen(prefix), path); + return prefix_path(prefix, prefix ? strlen(prefix) : 0, path); } /* @@ -2073,7 +2071,7 @@ static struct commit *fake_working_tree_commit(const char *path, const char *con if (strbuf_read(&buf, 0, 0) < 0) die("read error %s from stdin", strerror(errno)); } - convert_to_git(path, buf.buf, buf.len, &buf); + convert_to_git(path, buf.buf, buf.len, &buf, 0); origin->file.ptr = buf.buf; origin->file.size = buf.len; pretend_sha1_file(buf.buf, buf.len, OBJ_BLOB, origin->blob_sha1); @@ -2092,7 +2090,7 @@ static struct commit *fake_working_tree_commit(const char *path, const char *con if (!mode) { int pos = cache_name_pos(path, len); if (0 <= pos) - mode = ntohl(active_cache[pos]->ce_mode); + mode = active_cache[pos]->ce_mode; else /* Let's not bother reading from HEAD tree */ mode = S_IFREG | 0644; @@ -2369,7 +2367,8 @@ int cmd_blame(int argc, const char **argv, const char *prefix) * bottom commits we would reach while traversing as * uninteresting. */ - prepare_revision_walk(&revs); + if (prepare_revision_walk(&revs)) + die("revision walk setup failed"); if (is_null_sha1(sb.final->object.sha1)) { char *buf; diff --git a/builtin-branch.c b/builtin-branch.c index e414c88983..9edf2eb816 100644 --- a/builtin-branch.c +++ b/builtin-branch.c @@ -31,7 +31,7 @@ static unsigned char head_sha1[20]; static int branch_track = 1; -static int branch_use_color; +static int branch_use_color = -1; static char branch_colors[][COLOR_MAXLEN] = { "\033[m", /* reset */ "", /* PLAIN (normal) */ @@ -79,12 +79,12 @@ static int git_branch_config(const char *var, const char *value) branch_track = git_config_bool(var, value); return 0; } - return git_default_config(var, value); + return git_color_default_config(var, value); } static const char *branch_get_color(enum color_branch ix) { - if (branch_use_color) + if (branch_use_color > 0) return branch_colors[ix]; return ""; } @@ -588,6 +588,10 @@ int cmd_branch(int argc, const char **argv, const char *prefix) }; git_config(git_branch_config); + + if (branch_use_color == -1) + branch_use_color = git_use_color_default; + track = branch_track; argc = parse_options(argc, argv, options, builtin_branch_usage, 0); if (!!delete + !!rename + !!force_create > 1) diff --git a/builtin-clean.c b/builtin-clean.c index eb853a37cf..3b220d5060 100644 --- a/builtin-clean.c +++ b/builtin-clean.c @@ -29,7 +29,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix) { int i; int show_only = 0, remove_directories = 0, quiet = 0, ignored = 0; - int ignored_only = 0, baselen = 0, config_set = 0; + int ignored_only = 0, baselen = 0, config_set = 0, errors = 0; struct strbuf directory; struct dir_struct dir; const char *path, *base; @@ -137,12 +137,15 @@ int cmd_clean(int argc, const char **argv, const char *prefix) if (show_only && (remove_directories || matches)) { printf("Would remove %s\n", directory.buf + prefix_offset); - } else if (quiet && (remove_directories || matches)) { - remove_dir_recursively(&directory, 0); } else if (remove_directories || matches) { - printf("Removing %s\n", - directory.buf + prefix_offset); - remove_dir_recursively(&directory, 0); + if (!quiet) + printf("Removing %s\n", + directory.buf + prefix_offset); + if (remove_dir_recursively(&directory, 0) != 0) { + warning("failed to remove '%s'", + directory.buf + prefix_offset); + errors++; + } } else if (show_only) { printf("Would not remove %s\n", directory.buf + prefix_offset); @@ -162,11 +165,14 @@ int cmd_clean(int argc, const char **argv, const char *prefix) printf("Removing %s\n", ent->name + prefix_offset); } - unlink(ent->name); + if (unlink(ent->name) != 0) { + warning("failed to remove '%s'", ent->name); + errors++; + } } } free(seen); strbuf_release(&directory); - return 0; + return (errors != 0); } diff --git a/builtin-commit.c b/builtin-commit.c index 45232a11c4..065e1f7b7f 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -7,6 +7,7 @@ #include "cache.h" #include "cache-tree.h" +#include "color.h" #include "dir.h" #include "builtin.h" #include "diff.h" @@ -160,7 +161,7 @@ static int list_paths(struct path_list *list, const char *with_tree, for (i = 0; i < active_nr; i++) { struct cache_entry *ce = active_cache[i]; - if (ce->ce_flags & htons(CE_UPDATE)) + if (ce->ce_flags & CE_UPDATE) continue; if (!pathspec_match(pattern, m, ce->name, 0)) continue; @@ -347,45 +348,107 @@ static int run_status(FILE *fp, const char *index_file, const char *prefix, int return s.commitable; } +static int run_hook(const char *index_file, const char *name, ...) +{ + struct child_process hook; + const char *argv[10], *env[2]; + char index[PATH_MAX]; + va_list args; + int i; + + va_start(args, name); + argv[0] = git_path("hooks/%s", name); + i = 0; + do { + if (++i >= ARRAY_SIZE(argv)) + die ("run_hook(): too many arguments"); + argv[i] = va_arg(args, const char *); + } while (argv[i]); + va_end(args); + + snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", index_file); + env[0] = index; + env[1] = NULL; + + if (access(argv[0], X_OK) < 0) + return 0; + + memset(&hook, 0, sizeof(hook)); + hook.argv = argv; + hook.no_stdin = 1; + hook.stdout_to_stderr = 1; + hook.env = env; + + return run_command(&hook); +} + +static int is_a_merge(const unsigned char *sha1) +{ + struct commit *commit = lookup_commit(sha1); + if (!commit || parse_commit(commit)) + die("could not parse HEAD commit"); + return !!(commit->parents && commit->parents->next); +} + static const char sign_off_header[] = "Signed-off-by: "; -static int prepare_log_message(const char *index_file, const char *prefix) +static int prepare_to_commit(const char *index_file, const char *prefix) { struct stat statbuf; int commitable, saved_color_setting; struct strbuf sb; char *buffer; FILE *fp; + const char *hook_arg1 = NULL; + const char *hook_arg2 = NULL; + + if (!no_verify && run_hook(index_file, "pre-commit", NULL)) + return 0; strbuf_init(&sb, 0); if (message.len) { strbuf_addbuf(&sb, &message); + hook_arg1 = "message"; } else if (logfile && !strcmp(logfile, "-")) { if (isatty(0)) fprintf(stderr, "(reading log message from standard input)\n"); if (strbuf_read(&sb, 0, 0) < 0) die("could not read log from standard input"); + hook_arg1 = "message"; } else if (logfile) { if (strbuf_read_file(&sb, logfile, 0) < 0) die("could not read log file '%s': %s", logfile, strerror(errno)); + hook_arg1 = "message"; } else if (use_message) { buffer = strstr(use_message_buffer, "\n\n"); if (!buffer || buffer[2] == '\0') die("commit has empty message"); strbuf_add(&sb, buffer + 2, strlen(buffer + 2)); + hook_arg1 = "commit"; + hook_arg2 = use_message; } else if (!stat(git_path("MERGE_MSG"), &statbuf)) { if (strbuf_read_file(&sb, git_path("MERGE_MSG"), 0) < 0) die("could not read MERGE_MSG: %s", strerror(errno)); + hook_arg1 = "merge"; } else if (!stat(git_path("SQUASH_MSG"), &statbuf)) { if (strbuf_read_file(&sb, git_path("SQUASH_MSG"), 0) < 0) die("could not read SQUASH_MSG: %s", strerror(errno)); + hook_arg1 = "squash"; } else if (template_file && !stat(template_file, &statbuf)) { if (strbuf_read_file(&sb, template_file, 0) < 0) die("could not read %s: %s", template_file, strerror(errno)); + hook_arg1 = "template"; } + /* + * This final case does not modify the template message, + * it just sets the argument to the prepare-commit-msg hook. + */ + else if (in_merge) + hook_arg1 = "merge"; + fp = fopen(git_path(commit_editmsg), "w"); if (fp == NULL) die("could not open %s", git_path(commit_editmsg)); @@ -417,13 +480,38 @@ static int prepare_log_message(const char *index_file, const char *prefix) strbuf_release(&sb); - if (!use_editor) { + if (use_editor) { + if (in_merge) + fprintf(fp, + "#\n" + "# It looks like you may be committing a MERGE.\n" + "# If this is not correct, please remove the file\n" + "# %s\n" + "# and try again.\n" + "#\n", + git_path("MERGE_HEAD")); + + fprintf(fp, + "\n" + "# Please enter the commit message for your changes.\n" + "# (Comment lines starting with '#' will "); + if (cleanup_mode == CLEANUP_ALL) + fprintf(fp, "not be included)\n"); + else /* CLEANUP_SPACE, that is. */ + fprintf(fp, "be kept.\n" + "# You can remove them yourself if you want to)\n"); + if (only_include_assumed) + fprintf(fp, "# %s\n", only_include_assumed); + + saved_color_setting = wt_status_use_color; + wt_status_use_color = 0; + commitable = run_status(fp, index_file, prefix, 1); + wt_status_use_color = saved_color_setting; + } else { struct rev_info rev; unsigned char sha1[20]; const char *parent = "HEAD"; - fclose(fp); - if (!active_nr && read_cache() < 0) die("Cannot read index"); @@ -431,48 +519,60 @@ static int prepare_log_message(const char *index_file, const char *prefix) parent = "HEAD^1"; if (get_sha1(parent, sha1)) - return !!active_nr; + commitable = !!active_nr; + else { + init_revisions(&rev, ""); + rev.abbrev = 0; + setup_revisions(0, NULL, &rev, parent); + DIFF_OPT_SET(&rev.diffopt, QUIET); + DIFF_OPT_SET(&rev.diffopt, EXIT_WITH_STATUS); + run_diff_index(&rev, 1 /* cached */); + + commitable = !!DIFF_OPT_TST(&rev.diffopt, HAS_CHANGES); + } + } - init_revisions(&rev, ""); - rev.abbrev = 0; - setup_revisions(0, NULL, &rev, parent); - DIFF_OPT_SET(&rev.diffopt, QUIET); - DIFF_OPT_SET(&rev.diffopt, EXIT_WITH_STATUS); - run_diff_index(&rev, 1 /* cached */); + fclose(fp); - return !!DIFF_OPT_TST(&rev.diffopt, HAS_CHANGES); + if (!commitable && !in_merge && !allow_empty && + !(amend && is_a_merge(head_sha1))) { + run_status(stdout, index_file, prefix, 0); + unlink(commit_editmsg); + return 0; } - if (in_merge) - fprintf(fp, - "#\n" - "# It looks like you may be committing a MERGE.\n" - "# If this is not correct, please remove the file\n" - "# %s\n" - "# and try again.\n" - "#\n", - git_path("MERGE_HEAD")); - - fprintf(fp, - "\n" - "# Please enter the commit message for your changes.\n" - "# (Comment lines starting with '#' will "); - if (cleanup_mode == CLEANUP_ALL) - fprintf(fp, "not be included)\n"); - else /* CLEANUP_SPACE, that is. */ - fprintf(fp, "be kept.\n" - "# You can remove them yourself if you want to)\n"); - if (only_include_assumed) - fprintf(fp, "# %s\n", only_include_assumed); - - saved_color_setting = wt_status_use_color; - wt_status_use_color = 0; - commitable = run_status(fp, index_file, prefix, 1); - wt_status_use_color = saved_color_setting; + /* + * Re-read the index as pre-commit hook could have updated it, + * and write it out as a tree. We must do this before we invoke + * the editor and after we invoke run_status above. + */ + discard_cache(); + read_cache_from(index_file); + if (!active_cache_tree) + active_cache_tree = cache_tree(); + if (cache_tree_update(active_cache_tree, + active_cache, active_nr, 0, 0) < 0) { + error("Error building trees"); + return 0; + } - fclose(fp); + if (run_hook(index_file, "prepare-commit-msg", + git_path(commit_editmsg), hook_arg1, hook_arg2, NULL)) + return 0; + + if (use_editor) { + char index[PATH_MAX]; + const char *env[2] = { index, NULL }; + snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", index_file); + launch_editor(git_path(commit_editmsg), NULL, env); + } - return commitable; + if (!no_verify && + run_hook(index_file, "commit-msg", git_path(commit_editmsg), NULL)) { + return 0; + } + + return 1; } /* @@ -569,6 +669,8 @@ static int parse_and_validate_options(int argc, const char *argv[], use_editor = 0; if (edit_flag) use_editor = 1; + if (!use_editor) + setenv("GIT_EDITOR", ":", 1); if (get_sha1("HEAD", head_sha1)) initial_commit = 1; @@ -670,6 +772,9 @@ int cmd_status(int argc, const char **argv, const char *prefix) git_config(git_status_config); + if (wt_status_use_color == -1) + wt_status_use_color = git_use_color_default; + argc = parse_and_validate_options(argc, argv, builtin_status_usage); index_file = prepare_index(argc, argv, prefix); @@ -681,31 +786,6 @@ int cmd_status(int argc, const char **argv, const char *prefix) return commitable ? 0 : 1; } -static int run_hook(const char *index_file, const char *name, const char *arg) -{ - struct child_process hook; - const char *argv[3], *env[2]; - char index[PATH_MAX]; - - argv[0] = git_path("hooks/%s", name); - argv[1] = arg; - argv[2] = NULL; - snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", index_file); - env[0] = index; - env[1] = NULL; - - if (access(argv[0], X_OK) < 0) - return 0; - - memset(&hook, 0, sizeof(hook)); - hook.argv = argv; - hook.no_stdin = 1; - hook.stdout_to_stderr = 1; - hook.env = env; - - return run_command(&hook); -} - static void print_summary(const char *prefix, const unsigned char *sha1) { struct rev_info rev; @@ -756,14 +836,6 @@ int git_commit_config(const char *k, const char *v) return git_status_config(k, v); } -static int is_a_merge(const unsigned char *sha1) -{ - struct commit *commit = lookup_commit(sha1); - if (!commit || parse_commit(commit)) - die("could not parse HEAD commit"); - return !!(commit->parents && commit->parents->next); -} - static const char commit_utf8_warn[] = "Warning: commit message does not conform to UTF-8.\n" "You may want to amend it after fixing the message, or set the config\n" @@ -795,33 +867,13 @@ int cmd_commit(int argc, const char **argv, const char *prefix) index_file = prepare_index(argc, argv, prefix); - if (!no_verify && run_hook(index_file, "pre-commit", NULL)) { + /* Set up everything for writing the commit object. This includes + running hooks, writing the trees, and interacting with the user. */ + if (!prepare_to_commit(index_file, prefix)) { rollback_index_files(); return 1; } - if (!prepare_log_message(index_file, prefix) && !in_merge && - !allow_empty && !(amend && is_a_merge(head_sha1))) { - run_status(stdout, index_file, prefix, 0); - rollback_index_files(); - unlink(commit_editmsg); - return 1; - } - - /* - * Re-read the index as pre-commit hook could have updated it, - * and write it out as a tree. - */ - discard_cache(); - read_cache_from(index_file); - if (!active_cache_tree) - active_cache_tree = cache_tree(); - if (cache_tree_update(active_cache_tree, - active_cache, active_nr, 0, 0) < 0) { - rollback_index_files(); - die("Error building trees"); - } - /* * The commit object */ @@ -873,19 +925,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix) strbuf_addf(&sb, "encoding %s\n", git_commit_encoding); strbuf_addch(&sb, '\n'); - /* Get the commit message and validate it */ + /* Finally, get the commit message */ header_len = sb.len; - if (use_editor) { - char index[PATH_MAX]; - const char *env[2] = { index, NULL }; - snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", index_file); - launch_editor(git_path(commit_editmsg), NULL, env); - } - if (!no_verify && - run_hook(index_file, "commit-msg", git_path(commit_editmsg))) { - rollback_index_files(); - exit(1); - } if (strbuf_read_file(&sb, git_path(commit_editmsg), 0) < 0) { rollback_index_files(); die("could not read commit message"); diff --git a/builtin-config.c b/builtin-config.c index 077d8ef2df..2b9a4261d4 100644 --- a/builtin-config.c +++ b/builtin-config.c @@ -79,9 +79,10 @@ static int get_value(const char* key_, const char* regex_) local = getenv(CONFIG_LOCAL_ENVIRONMENT); if (!local) local = repo_config = xstrdup(git_path("config")); - if (home) + if (git_config_global() && home) global = xstrdup(mkpath("%s/.gitconfig", home)); - system_wide = git_etc_gitconfig(); + if (git_config_system()) + system_wide = git_etc_gitconfig(); } key = xstrdup(key_); diff --git a/builtin-describe.c b/builtin-describe.c index 7a148a2c26..3428483134 100644 --- a/builtin-describe.c +++ b/builtin-describe.c @@ -19,6 +19,7 @@ static int all; /* Default to annotated tags only */ static int tags; /* But allow any tags if --tags is specified */ static int abbrev = DEFAULT_ABBREV; static int max_candidates = 10; +const char *pattern = NULL; struct commit_name { int prio; /* annotated tag = 2, tag = 1, head = 0 */ @@ -57,9 +58,11 @@ static int get_name(const char *path, const unsigned char *sha1, int flag, void * Otherwise only annotated tags are used. */ if (!prefixcmp(path, "refs/tags/")) { - if (object->type == OBJ_TAG) + if (object->type == OBJ_TAG) { prio = 2; - else + if (pattern && fnmatch(pattern, path + 10, 0)) + prio = 0; + } else prio = 1; } else @@ -253,7 +256,9 @@ int cmd_describe(int argc, const char **argv, const char *prefix) OPT_BOOLEAN(0, "tags", &tags, "use any tag in .git/refs/tags"), OPT__ABBREV(&abbrev), OPT_INTEGER(0, "candidates", &max_candidates, - "consider <n> most recent tags (default: 10)"), + "consider <n> most recent tags (default: 10)"), + OPT_STRING(0, "match", &pattern, "pattern", + "only consider tags matching <pattern>"), OPT_END(), }; @@ -266,12 +271,19 @@ int cmd_describe(int argc, const char **argv, const char *prefix) save_commit_buffer = 0; if (contains) { - const char **args = xmalloc((4 + argc) * sizeof(char*)); + const char **args = xmalloc((6 + argc) * sizeof(char*)); int i = 0; args[i++] = "name-rev"; args[i++] = "--name-only"; - if (!all) + args[i++] = "--no-undefined"; + if (!all) { args[i++] = "--tags"; + if (pattern) { + char *s = xmalloc(strlen("--refs=refs/tags/") + strlen(pattern) + 1); + sprintf(s, "--refs=refs/tags/%s", pattern); + args[i++] = s; + } + } memcpy(args + i, argv, argc * sizeof(char*)); args[i + argc] = NULL; return cmd_name_rev(i + argc, args, prefix); diff --git a/builtin-diff.c b/builtin-diff.c index 8d7a5697f2..8f53f52dcb 100644 --- a/builtin-diff.c +++ b/builtin-diff.c @@ -4,6 +4,7 @@ * Copyright (c) 2006 Junio C Hamano */ #include "cache.h" +#include "color.h" #include "commit.h" #include "blob.h" #include "tag.h" @@ -229,6 +230,10 @@ int cmd_diff(int argc, const char **argv, const char *prefix) prefix = setup_git_directory_gently(&nongit); git_config(git_diff_ui_config); + + if (diff_use_color_default == -1) + diff_use_color_default = git_use_color_default; + init_revisions(&rev, prefix); rev.diffopt.skip_stat_unmatch = !!diff_auto_refresh_index; diff --git a/builtin-fast-export.c b/builtin-fast-export.c index ef27eee71b..f741df5220 100755 --- a/builtin-fast-export.c +++ b/builtin-fast-export.c @@ -383,7 +383,8 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) get_tags_and_duplicates(&revs.pending, &extra_refs); - prepare_revision_walk(&revs); + if (prepare_revision_walk(&revs)) + die("revision walk setup failed"); revs.diffopt.format_callback = show_filemodify; DIFF_OPT_SET(&revs.diffopt, RECURSIVE); while ((commit = get_revision(&revs))) { diff --git a/builtin-fetch-pack.c b/builtin-fetch-pack.c index e68e01592d..f40135248a 100644 --- a/builtin-fetch-pack.c +++ b/builtin-fetch-pack.c @@ -7,6 +7,7 @@ #include "pack.h" #include "sideband.h" #include "fetch-pack.h" +#include "remote.h" #include "run-command.h" static int transfer_unpack_limit = -1; @@ -548,14 +549,14 @@ static int get_pack(int xd[2], char **pack_lockfile) } static struct ref *do_fetch_pack(int fd[2], + const struct ref *orig_ref, int nr_match, char **match, char **pack_lockfile) { - struct ref *ref; + struct ref *ref = copy_ref_list(orig_ref); unsigned char sha1[20]; - get_remote_heads(fd[0], &ref, 0, NULL, 0); if (is_repository_shallow() && !server_supports("shallow")) die("Server does not support shallow clients"); if (server_supports("multi_ack")) { @@ -573,10 +574,6 @@ static struct ref *do_fetch_pack(int fd[2], fprintf(stderr, "Server supports side-band\n"); use_sideband = 1; } - if (!ref) { - packet_flush(fd[1]); - die("no matching remote head"); - } if (everything_local(&ref, nr_match, match)) { packet_flush(fd[1]); goto all_done; @@ -650,8 +647,10 @@ static void fetch_pack_setup(void) int cmd_fetch_pack(int argc, const char **argv, const char *prefix) { int i, ret, nr_heads; - struct ref *ref; + struct ref *ref = NULL; char *dest = NULL, **heads; + int fd[2]; + struct child_process *conn; nr_heads = 0; heads = NULL; @@ -706,9 +705,33 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) if (!dest) usage(fetch_pack_usage); - ref = fetch_pack(&args, dest, nr_heads, heads, NULL); + conn = git_connect(fd, (char *)dest, args.uploadpack, + args.verbose ? CONNECT_VERBOSE : 0); + if (conn) { + get_remote_heads(fd[0], &ref, 0, NULL, 0); + + ref = fetch_pack(&args, fd, conn, ref, dest, nr_heads, heads, NULL); + close(fd[0]); + close(fd[1]); + if (finish_connect(conn)) + ref = NULL; + } else { + ref = NULL; + } ret = !ref; + if (!ret && nr_heads) { + /* If the heads to pull were given, we should have + * consumed all of them by matching the remote. + * Otherwise, 'git-fetch remote no-such-ref' would + * silently succeed without issuing an error. + */ + for (i = 0; i < nr_heads; i++) + if (heads[i] && heads[i][0]) { + error("no such remote ref %s", heads[i]); + ret = 1; + } + } while (ref) { printf("%s %s\n", sha1_to_hex(ref->old_sha1), ref->name); @@ -719,16 +742,15 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) } struct ref *fetch_pack(struct fetch_pack_args *my_args, + int fd[], struct child_process *conn, + const struct ref *ref, const char *dest, int nr_heads, char **heads, char **pack_lockfile) { - int i, ret; - int fd[2]; - struct child_process *conn; - struct ref *ref; struct stat st; + struct ref *ref_cpy; fetch_pack_setup(); memcpy(&args, my_args, sizeof(args)); @@ -737,29 +759,15 @@ struct ref *fetch_pack(struct fetch_pack_args *my_args, st.st_mtime = 0; } - conn = git_connect(fd, (char *)dest, args.uploadpack, - args.verbose ? CONNECT_VERBOSE : 0); if (heads && nr_heads) nr_heads = remove_duplicates(nr_heads, heads); - ref = do_fetch_pack(fd, nr_heads, heads, pack_lockfile); - close(fd[0]); - close(fd[1]); - ret = finish_connect(conn); - - if (!ret && nr_heads) { - /* If the heads to pull were given, we should have - * consumed all of them by matching the remote. - * Otherwise, 'git-fetch remote no-such-ref' would - * silently succeed without issuing an error. - */ - for (i = 0; i < nr_heads; i++) - if (heads[i] && heads[i][0]) { - error("no such remote ref %s", heads[i]); - ret = 1; - } + if (!ref) { + packet_flush(fd[1]); + die("no matching remote head"); } + ref_cpy = do_fetch_pack(fd, ref, nr_heads, heads, pack_lockfile); - if (!ret && args.depth > 0) { + if (args.depth > 0) { struct cache_time mtime; char *shallow = git_path("shallow"); int fd; @@ -787,8 +795,5 @@ struct ref *fetch_pack(struct fetch_pack_args *my_args, } } - if (ret) - ref = NULL; - - return ref; + return ref_cpy; } diff --git a/builtin-fetch.c b/builtin-fetch.c index 320e235682..ac335f20fe 100644 --- a/builtin-fetch.c +++ b/builtin-fetch.c @@ -557,6 +557,8 @@ static int do_fetch(struct transport *transport, free_refs(fetch_map); + transport_disconnect(transport); + return 0; } diff --git a/builtin-fmt-merge-msg.c b/builtin-fmt-merge-msg.c index 6163bd4975..ebb3f37cf1 100644 --- a/builtin-fmt-merge-msg.c +++ b/builtin-fmt-merge-msg.c @@ -187,7 +187,8 @@ static void shortlog(const char *name, unsigned char *sha1, add_pending_object(rev, branch, name); add_pending_object(rev, &head->object, "^HEAD"); head->object.flags |= UNINTERESTING; - prepare_revision_walk(rev); + if (prepare_revision_walk(rev)) + die("revision walk setup failed"); while ((commit = get_revision(rev)) != NULL) { char *oneline, *bol, *eol; diff --git a/builtin-fsck.c b/builtin-fsck.c index 2a6e94deaf..cc7524be80 100644 --- a/builtin-fsck.c +++ b/builtin-fsck.c @@ -360,6 +360,9 @@ static int fsck_commit(struct commit *commit) fprintf(stderr, "Checking commit %s\n", sha1_to_hex(commit->object.sha1)); + if (!commit->date) + return objerror(&commit->object, "invalid author/committer line"); + if (memcmp(buffer, "tree ", 5)) return objerror(&commit->object, "invalid format - expected 'tree' line"); if (get_sha1_hex(buffer+5, tree_sha1) || buffer[45] != '\n') @@ -378,9 +381,6 @@ static int fsck_commit(struct commit *commit) return objerror(&commit->object, "could not load commit's tree %s", tree_sha1); if (!commit->parents && show_root) printf("root %s\n", sha1_to_hex(commit->object.sha1)); - if (!commit->date) - printf("bad commit date in %s\n", - sha1_to_hex(commit->object.sha1)); return 0; } @@ -765,7 +765,7 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) struct blob *blob; struct object *obj; - mode = ntohl(active_cache[i]->ce_mode); + mode = active_cache[i]->ce_mode; if (S_ISGITLINK(mode)) continue; blob = lookup_blob(active_cache[i]->sha1); diff --git a/builtin-grep.c b/builtin-grep.c index 0d6cc7361f..f4f4ecb11b 100644 --- a/builtin-grep.c +++ b/builtin-grep.c @@ -331,7 +331,7 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached) struct cache_entry *ce = active_cache[i]; char *name; int kept; - if (!S_ISREG(ntohl(ce->ce_mode))) + if (!S_ISREG(ce->ce_mode)) continue; if (!pathspec_matches(paths, ce->name)) continue; @@ -387,7 +387,7 @@ static int grep_cache(struct grep_opt *opt, const char **paths, int cached) for (nr = 0; nr < active_nr; nr++) { struct cache_entry *ce = active_cache[nr]; - if (!S_ISREG(ntohl(ce->ce_mode))) + if (!S_ISREG(ce->ce_mode)) continue; if (!pathspec_matches(paths, ce->name)) continue; @@ -578,6 +578,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix) continue; } if (!strcmp("-l", arg) || + !strcmp("--name-only", arg) || !strcmp("--files-with-matches", arg)) { opt.name_only = 1; continue; diff --git a/builtin-init-db.c b/builtin-init-db.c index e1393b8d1e..5d7cdda933 100644 --- a/builtin-init-db.c +++ b/builtin-init-db.c @@ -141,9 +141,9 @@ static void copy_templates(const char *git_dir, int len, const char *template_di */ template_dir = DEFAULT_GIT_TEMPLATE_DIR; if (!is_absolute_path(template_dir)) { - const char *exec_path = git_exec_path(); - template_dir = prefix_path(exec_path, strlen(exec_path), - template_dir); + struct strbuf d = STRBUF_INIT; + strbuf_addf(&d, "%s/%s", git_exec_path(), template_dir); + template_dir = strbuf_detach(&d, NULL); } } strcpy(template_path, template_dir); diff --git a/builtin-log.c b/builtin-log.c index 99d69f0791..c67d63cb1c 100644 --- a/builtin-log.c +++ b/builtin-log.c @@ -5,6 +5,7 @@ * 2006 Junio Hamano */ #include "cache.h" +#include "color.h" #include "commit.h" #include "diff.h" #include "revision.h" @@ -197,7 +198,8 @@ static int cmd_log_walk(struct rev_info *rev) if (rev->early_output) setup_early_output(rev); - prepare_revision_walk(rev); + if (prepare_revision_walk(rev)) + die("revision walk setup failed"); if (rev->early_output) finish_early_output(rev); @@ -235,6 +237,10 @@ int cmd_whatchanged(int argc, const char **argv, const char *prefix) struct rev_info rev; git_config(git_log_config); + + if (diff_use_color_default == -1) + diff_use_color_default = git_use_color_default; + init_revisions(&rev, prefix); rev.diff = 1; rev.simplify_history = 0; @@ -307,6 +313,10 @@ int cmd_show(int argc, const char **argv, const char *prefix) int i, count, ret = 0; git_config(git_log_config); + + if (diff_use_color_default == -1) + diff_use_color_default = git_use_color_default; + init_revisions(&rev, prefix); rev.diff = 1; rev.combine_merges = 1; @@ -367,6 +377,10 @@ int cmd_log_reflog(int argc, const char **argv, const char *prefix) struct rev_info rev; git_config(git_log_config); + + if (diff_use_color_default == -1) + diff_use_color_default = git_use_color_default; + init_revisions(&rev, prefix); init_reflog_walk(&rev.reflog_info); rev.abbrev_commit = 1; @@ -395,6 +409,10 @@ int cmd_log(int argc, const char **argv, const char *prefix) struct rev_info rev; git_config(git_log_config); + + if (diff_use_color_default == -1) + diff_use_color_default = git_use_color_default; + init_revisions(&rev, prefix); rev.always_show_header = 1; cmd_log_init(argc, argv, prefix, &rev); @@ -556,7 +574,8 @@ static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids, const cha o2->flags ^= UNINTERESTING; add_pending_object(&check_rev, o1, "o1"); add_pending_object(&check_rev, o2, "o2"); - prepare_revision_walk(&check_rev); + if (prepare_revision_walk(&check_rev)) + die("revision walk setup failed"); while ((commit = get_revision(&check_rev)) != NULL) { /* ignore merges */ @@ -781,7 +800,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) if (!use_stdout) realstdout = xfdopen(xdup(1), "w"); - prepare_revision_walk(&rev); + if (prepare_revision_walk(&rev)) + die("revision walk setup failed"); while ((commit = get_revision(&rev)) != NULL) { /* ignore merges */ if (commit->parents && commit->parents->next) @@ -923,7 +943,8 @@ int cmd_cherry(int argc, const char **argv, const char *prefix) die("Unknown commit %s", limit); /* reverse the list of commits */ - prepare_revision_walk(&revs); + if (prepare_revision_walk(&revs)) + die("revision walk setup failed"); while ((commit = get_revision(&revs)) != NULL) { /* ignore merges */ if (commit->parents && commit->parents->next) diff --git a/builtin-ls-files.c b/builtin-ls-files.c index 0f0ab2da16..25dbfb4499 100644 --- a/builtin-ls-files.c +++ b/builtin-ls-files.c @@ -189,7 +189,7 @@ static void show_ce_entry(const char *tag, struct cache_entry *ce) return; if (tag && *tag && show_valid_bit && - (ce->ce_flags & htons(CE_VALID))) { + (ce->ce_flags & CE_VALID)) { static char alttag[4]; memcpy(alttag, tag, 3); if (isalpha(tag[0])) @@ -210,7 +210,7 @@ static void show_ce_entry(const char *tag, struct cache_entry *ce) } else { printf("%s%06o %s %d\t", tag, - ntohl(ce->ce_mode), + ce->ce_mode, abbrev ? find_unique_abbrev(ce->sha1,abbrev) : sha1_to_hex(ce->sha1), ce_stage(ce)); @@ -238,11 +238,12 @@ static void show_files(struct dir_struct *dir, const char *prefix) if (show_cached | show_stage) { for (i = 0; i < active_nr; i++) { struct cache_entry *ce = active_cache[i]; - if (excluded(dir, ce->name) != dir->show_ignored) + int dtype = ce_to_dtype(ce); + if (excluded(dir, ce->name, &dtype) != dir->show_ignored) continue; if (show_unmerged && !ce_stage(ce)) continue; - if (ce->ce_flags & htons(CE_UPDATE)) + if (ce->ce_flags & CE_UPDATE) continue; show_ce_entry(ce_stage(ce) ? tag_unmerged : tag_cached, ce); } @@ -252,7 +253,8 @@ static void show_files(struct dir_struct *dir, const char *prefix) struct cache_entry *ce = active_cache[i]; struct stat st; int err; - if (excluded(dir, ce->name) != dir->show_ignored) + int dtype = ce_to_dtype(ce); + if (excluded(dir, ce->name, &dtype) != dir->show_ignored) continue; err = lstat(ce->name, &st); if (show_deleted && err) @@ -350,7 +352,7 @@ void overlay_tree_on_cache(const char *tree_name, const char *prefix) struct cache_entry *ce = active_cache[i]; if (!ce_stage(ce)) continue; - ce->ce_flags |= htons(CE_STAGEMASK); + ce->ce_flags |= CE_STAGEMASK; } if (prefix) { @@ -379,7 +381,7 @@ void overlay_tree_on_cache(const char *tree_name, const char *prefix) */ if (last_stage0 && !strcmp(last_stage0->name, ce->name)) - ce->ce_flags |= htons(CE_UPDATE); + ce->ce_flags |= CE_UPDATE; } } } @@ -572,8 +574,17 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) pathspec = get_pathspec(prefix, argv + i); /* Verify that the pathspec matches the prefix */ - if (pathspec) + if (pathspec) { + if (argc != i) { + int cnt; + for (cnt = 0; pathspec[cnt]; cnt++) + ; + if (cnt != (argc - i)) + exit(1); /* error message already given */ + } prefix = verify_pathspec(prefix); + } else if (argc != i) + exit(1); /* error message already given */ /* Treat unmatching pathspec elements as errors */ if (pathspec && error_unmatch) { diff --git a/builtin-ls-remote.c b/builtin-ls-remote.c index 6dd31d1dd6..023754986e 100644 --- a/builtin-ls-remote.c +++ b/builtin-ls-remote.c @@ -94,6 +94,7 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) transport_set_option(transport, TRANS_OPT_UPLOADPACK, uploadpack); ref = transport_get_remote_refs(transport); + transport_disconnect(transport); if (!ref) return 1; diff --git a/builtin-mv.c b/builtin-mv.c index 990e21355d..68aa2a68bb 100644 --- a/builtin-mv.c +++ b/builtin-mv.c @@ -19,6 +19,7 @@ static const char **copy_pathspec(const char *prefix, const char **pathspec, int count, int base_name) { int i; + int len = prefix ? strlen(prefix) : 0; const char **result = xmalloc((count + 1) * sizeof(const char *)); memcpy(result, pathspec, count * sizeof(const char *)); result[count] = NULL; @@ -32,8 +33,11 @@ static const char **copy_pathspec(const char *prefix, const char **pathspec, if (last_slash) result[i] = last_slash + 1; } + result[i] = prefix_path(prefix, len, result[i]); + if (!result[i]) + exit(1); /* error already given */ } - return get_pathspec(prefix, result); + return result; } static void show_list(const char *label, struct path_list *list) @@ -164,7 +168,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) } dst = add_slash(dst); - dst_len = strlen(dst) - 1; + dst_len = strlen(dst); for (j = 0; j < last - first; j++) { const char *path = @@ -172,7 +176,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) source[argc + j] = path; destination[argc + j] = prefix_path(dst, dst_len, - path + length); + path + length + 1); modes[argc + j] = INDEX; } argc += last - first; diff --git a/builtin-name-rev.c b/builtin-name-rev.c index a0c89a827b..f22c8b5f5d 100644 --- a/builtin-name-rev.c +++ b/builtin-name-rev.c @@ -125,18 +125,18 @@ static int name_ref(const char *path, const unsigned char *sha1, int flags, void } /* returns a static buffer */ -static const char* get_rev_name(struct object *o) +static const char *get_rev_name(struct object *o) { static char buffer[1024]; struct rev_name *n; struct commit *c; if (o->type != OBJ_COMMIT) - return "undefined"; + return NULL; c = (struct commit *) o; n = c->util; if (!n) - return "undefined"; + return NULL; if (!n->generation) return n->tip_name; @@ -159,7 +159,7 @@ static char const * const name_rev_usage[] = { int cmd_name_rev(int argc, const char **argv, const char *prefix) { struct object_array revs = { 0, 0, NULL }; - int all = 0, transform_stdin = 0; + int all = 0, transform_stdin = 0, allow_undefined = 1; struct name_ref_data data = { 0, 0, NULL }; struct option opts[] = { OPT_BOOLEAN(0, "name-only", &data.name_only, "print only names (no SHA-1)"), @@ -169,6 +169,7 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix) OPT_GROUP(""), OPT_BOOLEAN(0, "all", &all, "list all commits reachable from all refs"), OPT_BOOLEAN(0, "stdin", &transform_stdin, "read from stdin"), + OPT_BOOLEAN(0, "undefined", &allow_undefined, "allow to print `undefined` names"), OPT_END(), }; @@ -226,7 +227,7 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix) else if (++forty == 40 && !ishex(*(p+1))) { unsigned char sha1[40]; - const char *name = "undefined"; + const char *name = NULL; char c = *(p+1); forty = 0; @@ -240,11 +241,10 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix) } *(p+1) = c; - if (!strcmp(name, "undefined")) + if (!name) continue; - fwrite(p_start, p - p_start + 1, 1, - stdout); + fwrite(p_start, p - p_start + 1, 1, stdout); printf(" (%s)", name); p_start = p + 1; } @@ -260,18 +260,32 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix) max = get_max_object_index(); for (i = 0; i < max; i++) { struct object * obj = get_indexed_object(i); + const char *name; if (!obj) continue; if (!data.name_only) printf("%s ", sha1_to_hex(obj->sha1)); - printf("%s\n", get_rev_name(obj)); + name = get_rev_name(obj); + if (name) + printf("%s\n", name); + else if (allow_undefined) + printf("undefined\n"); + else + die("cannot describe '%s'", sha1_to_hex(obj->sha1)); } } else { int i; for (i = 0; i < revs.nr; i++) { + const char *name; if (!data.name_only) printf("%s ", revs.objects[i].name); - printf("%s\n", get_rev_name(revs.objects[i].item)); + name = get_rev_name(revs.objects[i].item); + if (name) + printf("%s\n", name); + else if (allow_undefined) + printf("undefined\n"); + else + die("cannot describe '%s'", sha1_to_hex(revs.objects[i].item->sha1)); } } diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index d3efeff03f..d2bb12e574 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -68,7 +68,7 @@ static int allow_ofs_delta; static const char *base_name; static int progress = 1; static int window = 10; -static uint32_t pack_size_limit; +static uint32_t pack_size_limit, pack_size_limit_cfg; static int depth = 50; static int delta_search_threads = 1; static int pack_to_stdout; @@ -1867,6 +1867,10 @@ static int git_pack_config(const char *k, const char *v) die("bad pack.indexversion=%d", pack_idx_default_version); return 0; } + if (!strcmp(k, "pack.packsizelimit")) { + pack_size_limit_cfg = git_config_ulong(k, v); + return 0; + } return git_default_config(k, v); } @@ -2029,7 +2033,8 @@ static void get_object_list(int ac, const char **av) die("bad revision '%s'", line); } - prepare_revision_walk(&revs); + if (prepare_revision_walk(&revs)) + die("revision walk setup failed"); mark_edges_uninteresting(revs.commits, &revs, show_edge); traverse_commit_list(&revs, show_commit, show_object); @@ -2096,6 +2101,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) } if (!prefixcmp(arg, "--max-pack-size=")) { char *end; + pack_size_limit_cfg = 0; pack_size_limit = strtoul(arg+16, &end, 0) * 1024 * 1024; if (!arg[16] || *end) usage(pack_usage); @@ -2220,6 +2226,9 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) if (pack_to_stdout != !base_name) usage(pack_usage); + if (!pack_to_stdout && !pack_size_limit) + pack_size_limit = pack_size_limit_cfg; + if (pack_to_stdout && pack_size_limit) die("--max-pack-size cannot be used to build a pack for transfer."); diff --git a/builtin-read-tree.c b/builtin-read-tree.c index c0ea0342b7..726fb0b588 100644 --- a/builtin-read-tree.c +++ b/builtin-read-tree.c @@ -45,8 +45,7 @@ static int read_cache_unmerged(void) continue; cache_tree_invalidate_path(active_cache_tree, ce->name); last = ce; - ce->ce_mode = 0; - ce->ce_flags &= ~htons(CE_STAGEMASK); + continue; } *dst++ = ce; } diff --git a/builtin-rerere.c b/builtin-rerere.c index a9e3ebc137..b0c17bde87 100644 --- a/builtin-rerere.c +++ b/builtin-rerere.c @@ -149,8 +149,8 @@ static int find_conflict(struct path_list *conflict) if (ce_stage(e2) == 2 && ce_stage(e3) == 3 && ce_same_name(e2, e3) && - S_ISREG(ntohl(e2->ce_mode)) && - S_ISREG(ntohl(e3->ce_mode))) { + S_ISREG(e2->ce_mode) && + S_ISREG(e3->ce_mode)) { path_list_insert((const char *)e2->name, conflict); i++; /* skip over both #2 and #3 */ } diff --git a/builtin-rev-list.c b/builtin-rev-list.c index de80158fd4..6f7d5f8214 100644 --- a/builtin-rev-list.c +++ b/builtin-rev-list.c @@ -60,6 +60,8 @@ static void show_commit(struct commit *commit) fputs(header_prefix, stdout); if (commit->object.flags & BOUNDARY) putchar('-'); + else if (commit->object.flags & UNINTERESTING) + putchar('^'); else if (revs.left_right) { if (commit->object.flags & SYMMETRIC_LEFT) putchar('<'); @@ -84,7 +86,7 @@ static void show_commit(struct commit *commit) else putchar('\n'); - if (revs.verbose_header) { + if (revs.verbose_header && commit->buffer) { struct strbuf buf; strbuf_init(&buf, 0); pretty_print_commit(revs.commit_format, commit, @@ -609,7 +611,8 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) if (bisect_list) revs.limited = 1; - prepare_revision_walk(&revs); + if (prepare_revision_walk(&revs)) + die("revision walk setup failed"); if (revs.tree_objects) mark_edges_uninteresting(revs.commits, &revs, show_edge); diff --git a/builtin-revert.c b/builtin-revert.c index 358af53747..e219859f9b 100644 --- a/builtin-revert.c +++ b/builtin-revert.c @@ -8,6 +8,7 @@ #include "exec_cmd.h" #include "utf8.h" #include "parse-options.h" +#include "cache-tree.h" /* * This implements the builtins revert and cherry-pick. @@ -270,7 +271,7 @@ static int revert_or_cherry_pick(int argc, const char **argv) * that represents the "current" state for merge-recursive * to work on. */ - if (write_tree(head, 0, NULL)) + if (write_cache_as_tree(head, 0, NULL)) die ("Your index file is unmerged."); } else { struct wt_status s; @@ -357,7 +358,7 @@ static int revert_or_cherry_pick(int argc, const char **argv) if (merge_recursive(sha1_to_hex(base->object.sha1), sha1_to_hex(head), "HEAD", sha1_to_hex(next->object.sha1), oneline) || - write_tree(head, 0, NULL)) { + write_cache_as_tree(head, 0, NULL)) { add_to_msg("\nConflicts:\n\n"); read_cache(); for (i = 0; i < active_nr;) { diff --git a/builtin-shortlog.c b/builtin-shortlog.c index fa8bc7d02a..0055a57aeb 100644 --- a/builtin-shortlog.c +++ b/builtin-shortlog.c @@ -136,7 +136,8 @@ static void get_from_rev(struct rev_info *rev, struct path_list *list) { struct commit *commit; - prepare_revision_walk(rev); + if (prepare_revision_walk(rev)) + die("revision walk setup failed"); while ((commit = get_revision(rev)) != NULL) { const char *author = NULL, *buffer; diff --git a/builtin-show-ref.c b/builtin-show-ref.c index 65051d14fd..a323633e29 100644 --- a/builtin-show-ref.c +++ b/builtin-show-ref.c @@ -86,6 +86,9 @@ match: sha1_to_hex(sha1)); if (obj->type == OBJ_TAG) { obj = deref_tag(obj, refname, 0); + if (!obj) + die("git-show-ref: bad tag at ref %s (%s)", refname, + sha1_to_hex(sha1)); hex = find_unique_abbrev(obj->sha1, abbrev); printf("%s %s^{}\n", hex, refname); } diff --git a/builtin-tag.c b/builtin-tag.c index 4a4a88c10b..716b4fff32 100644 --- a/builtin-tag.c +++ b/builtin-tag.c @@ -236,9 +236,6 @@ static int do_sign(struct strbuf *buffer) if (finish_command(&gpg) || !len || len < 0) return error("gpg failed to sign the tag"); - if (len < 0) - return error("could not read the entire signature from gpg."); - return 0; } diff --git a/builtin-update-index.c b/builtin-update-index.c index c3a14c74ed..a8795d3d5f 100644 --- a/builtin-update-index.c +++ b/builtin-update-index.c @@ -47,10 +47,10 @@ static int mark_valid(const char *path) if (0 <= pos) { switch (mark_valid_only) { case MARK_VALID: - active_cache[pos]->ce_flags |= htons(CE_VALID); + active_cache[pos]->ce_flags |= CE_VALID; break; case UNMARK_VALID: - active_cache[pos]->ce_flags &= ~htons(CE_VALID); + active_cache[pos]->ce_flags &= ~CE_VALID; break; } cache_tree_invalidate_path(active_cache_tree, path); @@ -95,7 +95,7 @@ static int add_one_path(struct cache_entry *old, const char *path, int len, stru size = cache_entry_size(len); ce = xcalloc(1, size); memcpy(ce->name, path, len); - ce->ce_flags = htons(len); + ce->ce_flags = len; fill_stat_cache_info(ce, st); ce->ce_mode = ce_mode_from_stat(old, st->st_mode); @@ -139,7 +139,7 @@ static int process_directory(const char *path, int len, struct stat *st) /* Exact match: file or existing gitlink */ if (pos >= 0) { struct cache_entry *ce = active_cache[pos]; - if (S_ISGITLINK(ntohl(ce->ce_mode))) { + if (S_ISGITLINK(ce->ce_mode)) { /* Do nothing to the index if there is no HEAD! */ if (resolve_gitlink_ref(path, "HEAD", sha1) < 0) @@ -183,7 +183,7 @@ static int process_file(const char *path, int len, struct stat *st) int pos = cache_name_pos(path, len); struct cache_entry *ce = pos < 0 ? NULL : active_cache[pos]; - if (ce && S_ISGITLINK(ntohl(ce->ce_mode))) + if (ce && S_ISGITLINK(ce->ce_mode)) return error("%s is already a gitlink, not replacing", path); return add_one_path(ce, path, len, st); @@ -226,7 +226,7 @@ static int add_cacheinfo(unsigned int mode, const unsigned char *sha1, ce->ce_flags = create_ce_flags(len, stage); ce->ce_mode = create_ce_mode(mode); if (assume_unchanged) - ce->ce_flags |= htons(CE_VALID); + ce->ce_flags |= CE_VALID; option = allow_add ? ADD_CACHE_OK_TO_ADD : 0; option |= allow_replace ? ADD_CACHE_OK_TO_REPLACE : 0; if (add_cache_entry(ce, option)) @@ -246,14 +246,14 @@ static void chmod_path(int flip, const char *path) if (pos < 0) goto fail; ce = active_cache[pos]; - mode = ntohl(ce->ce_mode); + mode = ce->ce_mode; if (!S_ISREG(mode)) goto fail; switch (flip) { case '+': - ce->ce_mode |= htonl(0111); break; + ce->ce_mode |= 0111; break; case '-': - ce->ce_mode &= htonl(~0111); break; + ce->ce_mode &= ~0111; break; default: goto fail; } diff --git a/builtin-write-tree.c b/builtin-write-tree.c index d16b9ed009..e838d01233 100644 --- a/builtin-write-tree.c +++ b/builtin-write-tree.c @@ -11,63 +11,12 @@ static const char write_tree_usage[] = "git-write-tree [--missing-ok] [--prefix=<prefix>/]"; -int write_tree(unsigned char *sha1, int missing_ok, const char *prefix) -{ - int entries, was_valid, newfd; - - /* We can't free this memory, it becomes part of a linked list parsed atexit() */ - struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file)); - - newfd = hold_locked_index(lock_file, 1); - - entries = read_cache(); - if (entries < 0) - die("git-write-tree: error reading cache"); - - if (!active_cache_tree) - active_cache_tree = cache_tree(); - - was_valid = cache_tree_fully_valid(active_cache_tree); - - if (!was_valid) { - if (cache_tree_update(active_cache_tree, - active_cache, active_nr, - missing_ok, 0) < 0) - die("git-write-tree: error building trees"); - if (0 <= newfd) { - if (!write_cache(newfd, active_cache, active_nr) && - !commit_lock_file(lock_file)) - newfd = -1; - } - /* Not being able to write is fine -- we are only interested - * in updating the cache-tree part, and if the next caller - * ends up using the old index with unupdated cache-tree part - * it misses the work we did here, but that is just a - * performance penalty and not a big deal. - */ - } - - if (prefix) { - struct cache_tree *subtree = - cache_tree_find(active_cache_tree, prefix); - if (!subtree) - die("git-write-tree: prefix %s not found", prefix); - hashcpy(sha1, subtree->sha1); - } - else - hashcpy(sha1, active_cache_tree->sha1); - - if (0 <= newfd) - rollback_lock_file(lock_file); - - return 0; -} - int cmd_write_tree(int argc, const char **argv, const char *unused_prefix) { int missing_ok = 0, ret; const char *prefix = NULL; unsigned char sha1[20]; + const char *me = "git-write-tree"; git_config(git_default_config); while (1 < argc) { @@ -84,8 +33,20 @@ int cmd_write_tree(int argc, const char **argv, const char *unused_prefix) if (argc > 2) die("too many options"); - ret = write_tree(sha1, missing_ok, prefix); - printf("%s\n", sha1_to_hex(sha1)); - + ret = write_cache_as_tree(sha1, missing_ok, prefix); + switch (ret) { + case 0: + printf("%s\n", sha1_to_hex(sha1)); + break; + case WRITE_TREE_UNREADABLE_INDEX: + die("%s: error reading the index", me); + break; + case WRITE_TREE_UNMERGED_INDEX: + die("%s: error building trees; the index is unmerged?", me); + break; + case WRITE_TREE_PREFIX_ERROR: + die("%s: prefix %s not found", me, prefix); + break; + } return ret; } @@ -8,7 +8,6 @@ extern const char git_usage_string[]; extern void list_common_cmds_help(void); extern void help_unknown_cmd(const char *cmd); -extern int write_tree(unsigned char *sha1, int missing_ok, const char *prefix); extern void prune_packed_objects(int); extern int cmd_add(int argc, const char **argv, const char *prefix); @@ -128,7 +128,8 @@ int verify_bundle(struct bundle_header *header, int verbose) add_object_array(e->item, e->name, &refs); } - prepare_revision_walk(&revs); + if (prepare_revision_walk(&revs)) + die("revision walk setup failed"); i = req_nr; while (i && (commit = get_revision(&revs))) diff --git a/cache-tree.c b/cache-tree.c index 50b35264fd..39da54d1e5 100644 --- a/cache-tree.c +++ b/cache-tree.c @@ -320,13 +320,13 @@ static int update_one(struct cache_tree *it, } else { sha1 = ce->sha1; - mode = ntohl(ce->ce_mode); + mode = ce->ce_mode; entlen = pathlen - baselen; } if (mode != S_IFGITLINK && !missing_ok && !has_sha1_file(sha1)) return error("invalid object %s", sha1_to_hex(sha1)); - if (!ce->ce_mode) + if (ce->ce_flags & CE_REMOVE) continue; /* entry being removed */ strbuf_grow(&buffer, entlen + 100); @@ -529,3 +529,58 @@ struct cache_tree *cache_tree_find(struct cache_tree *it, const char *path) } return it; } + +int write_cache_as_tree(unsigned char *sha1, int missing_ok, const char *prefix) +{ + int entries, was_valid, newfd; + + /* + * We can't free this memory, it becomes part of a linked list + * parsed atexit() + */ + struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file)); + + newfd = hold_locked_index(lock_file, 1); + + entries = read_cache(); + if (entries < 0) + return WRITE_TREE_UNREADABLE_INDEX; + + if (!active_cache_tree) + active_cache_tree = cache_tree(); + + was_valid = cache_tree_fully_valid(active_cache_tree); + + if (!was_valid) { + if (cache_tree_update(active_cache_tree, + active_cache, active_nr, + missing_ok, 0) < 0) + return WRITE_TREE_UNMERGED_INDEX; + if (0 <= newfd) { + if (!write_cache(newfd, active_cache, active_nr) && + !commit_lock_file(lock_file)) + newfd = -1; + } + /* Not being able to write is fine -- we are only interested + * in updating the cache-tree part, and if the next caller + * ends up using the old index with unupdated cache-tree part + * it misses the work we did here, but that is just a + * performance penalty and not a big deal. + */ + } + + if (prefix) { + struct cache_tree *subtree = + cache_tree_find(active_cache_tree, prefix); + if (!subtree) + return WRITE_TREE_PREFIX_ERROR; + hashcpy(sha1, subtree->sha1); + } + else + hashcpy(sha1, active_cache_tree->sha1); + + if (0 <= newfd) + rollback_lock_file(lock_file); + + return 0; +} diff --git a/cache-tree.h b/cache-tree.h index 8243228e49..44aad426d3 100644 --- a/cache-tree.h +++ b/cache-tree.h @@ -30,4 +30,9 @@ int cache_tree_update(struct cache_tree *, struct cache_entry **, int, int, int) struct cache_tree *cache_tree_find(struct cache_tree *, const char *); +#define WRITE_TREE_UNREADABLE_INDEX (-1) +#define WRITE_TREE_UNMERGED_INDEX (-2) +#define WRITE_TREE_PREFIX_ERROR (-3) + +int write_cache_as_tree(unsigned char *sha1, int missing_ok, const char *prefix); #endif @@ -3,6 +3,7 @@ #include "git-compat-util.h" #include "strbuf.h" +#include "hash.h" #include SHA1_HEADER #include <zlib.h> @@ -94,66 +95,116 @@ struct cache_time { * We save the fields in big-endian order to allow using the * index file over NFS transparently. */ +struct ondisk_cache_entry { + struct cache_time ctime; + struct cache_time mtime; + unsigned int dev; + unsigned int ino; + unsigned int mode; + unsigned int uid; + unsigned int gid; + unsigned int size; + unsigned char sha1[20]; + unsigned short flags; + char name[FLEX_ARRAY]; /* more */ +}; + struct cache_entry { - struct cache_time ce_ctime; - struct cache_time ce_mtime; + struct cache_entry *next; + unsigned int ce_ctime; + unsigned int ce_mtime; unsigned int ce_dev; unsigned int ce_ino; unsigned int ce_mode; unsigned int ce_uid; unsigned int ce_gid; unsigned int ce_size; + unsigned int ce_flags; unsigned char sha1[20]; - unsigned short ce_flags; char name[FLEX_ARRAY]; /* more */ }; #define CE_NAMEMASK (0x0fff) #define CE_STAGEMASK (0x3000) -#define CE_UPDATE (0x4000) #define CE_VALID (0x8000) #define CE_STAGESHIFT 12 -#define create_ce_flags(len, stage) htons((len) | ((stage) << CE_STAGESHIFT)) -#define ce_namelen(ce) (CE_NAMEMASK & ntohs((ce)->ce_flags)) +/* In-memory only */ +#define CE_UPDATE (0x10000) +#define CE_REMOVE (0x20000) +#define CE_UPTODATE (0x40000) +#define CE_UNHASHED (0x80000) + +static inline unsigned create_ce_flags(size_t len, unsigned stage) +{ + if (len >= CE_NAMEMASK) + len = CE_NAMEMASK; + return (len | (stage << CE_STAGESHIFT)); +} + +static inline size_t ce_namelen(const struct cache_entry *ce) +{ + size_t len = ce->ce_flags & CE_NAMEMASK; + if (len < CE_NAMEMASK) + return len; + return strlen(ce->name + CE_NAMEMASK) + CE_NAMEMASK; +} + #define ce_size(ce) cache_entry_size(ce_namelen(ce)) -#define ce_stage(ce) ((CE_STAGEMASK & ntohs((ce)->ce_flags)) >> CE_STAGESHIFT) +#define ondisk_ce_size(ce) ondisk_cache_entry_size(ce_namelen(ce)) +#define ce_stage(ce) ((CE_STAGEMASK & (ce)->ce_flags) >> CE_STAGESHIFT) +#define ce_uptodate(ce) ((ce)->ce_flags & CE_UPTODATE) +#define ce_mark_uptodate(ce) ((ce)->ce_flags |= CE_UPTODATE) #define ce_permissions(mode) (((mode) & 0100) ? 0755 : 0644) static inline unsigned int create_ce_mode(unsigned int mode) { if (S_ISLNK(mode)) - return htonl(S_IFLNK); + return S_IFLNK; if (S_ISDIR(mode) || S_ISGITLINK(mode)) - return htonl(S_IFGITLINK); - return htonl(S_IFREG | ce_permissions(mode)); + return S_IFGITLINK; + return S_IFREG | ce_permissions(mode); } static inline unsigned int ce_mode_from_stat(struct cache_entry *ce, unsigned int mode) { extern int trust_executable_bit, has_symlinks; if (!has_symlinks && S_ISREG(mode) && - ce && S_ISLNK(ntohl(ce->ce_mode))) + ce && S_ISLNK(ce->ce_mode)) return ce->ce_mode; if (!trust_executable_bit && S_ISREG(mode)) { - if (ce && S_ISREG(ntohl(ce->ce_mode))) + if (ce && S_ISREG(ce->ce_mode)) return ce->ce_mode; return create_ce_mode(0666); } return create_ce_mode(mode); } +static inline int ce_to_dtype(const struct cache_entry *ce) +{ + unsigned ce_mode = ntohl(ce->ce_mode); + if (S_ISREG(ce_mode)) + return DT_REG; + else if (S_ISDIR(ce_mode) || S_ISGITLINK(ce_mode)) + return DT_DIR; + else if (S_ISLNK(ce_mode)) + return DT_LNK; + else + return DT_UNKNOWN; +} #define canon_mode(mode) \ (S_ISREG(mode) ? (S_IFREG | ce_permissions(mode)) : \ S_ISLNK(mode) ? S_IFLNK : S_ISDIR(mode) ? S_IFDIR : S_IFGITLINK) #define cache_entry_size(len) ((offsetof(struct cache_entry,name) + (len) + 8) & ~7) +#define ondisk_cache_entry_size(len) ((offsetof(struct ondisk_cache_entry,name) + (len) + 8) & ~7) struct index_state { struct cache_entry **cache; unsigned int cache_nr, cache_alloc, cache_changed; struct cache_tree *cache_tree; time_t timestamp; - void *mmap; - size_t mmap_size; + void *alloc; + unsigned name_hash_initialized : 1; + struct hash_table name_hash; }; extern struct index_state the_index; @@ -177,6 +228,7 @@ extern struct index_state the_index; #define refresh_cache(flags) refresh_index(&the_index, (flags), NULL, NULL) #define ce_match_stat(ce, st, options) ie_match_stat(&the_index, (ce), (st), (options)) #define ce_modified(ce, st, options) ie_modified(&the_index, (ce), (st), (options)) +#define cache_name_exists(name, namelen) index_name_exists(&the_index, (name), (namelen)) #endif enum object_type { @@ -263,6 +315,7 @@ extern int read_index_from(struct index_state *, const char *path); extern int write_index(struct index_state *, int newfd); extern int discard_index(struct index_state *); extern int verify_path(const char *path); +extern int index_name_exists(struct index_state *istate, const char *name, int namelen); extern int index_name_pos(struct index_state *, const char *name, int namelen); #define ADD_CACHE_OK_TO_ADD 1 /* Ok to add */ #define ADD_CACHE_OK_TO_REPLACE 2 /* Ok to replace file/directory */ @@ -330,6 +383,14 @@ extern size_t packed_git_limit; extern size_t delta_base_cache_limit; extern int auto_crlf; +enum safe_crlf { + SAFE_CRLF_FALSE = 0, + SAFE_CRLF_FAIL = 1, + SAFE_CRLF_WARN = 2, +}; + +extern enum safe_crlf safe_crlf; + #define GIT_REPO_VERSION 0 extern int repository_format_version; extern int check_repository_format(void); @@ -590,6 +651,9 @@ extern int git_config_set_multivar(const char *, const char *, const char *, int extern int git_config_rename_section(const char *, const char *); extern const char *git_etc_gitconfig(void); extern int check_repository_format_version(const char *var, const char *value); +extern int git_env_bool(const char *, int); +extern int git_config_system(void); +extern int git_config_global(void); extern int config_error_nonbool(const char *); #define MAX_GITNAME (1000) @@ -635,7 +699,8 @@ extern void trace_argv_printf(const char **argv, const char *format, ...); /* convert.c */ /* returns 1 if *dst was used */ -extern int convert_to_git(const char *path, const char *src, size_t len, struct strbuf *dst); +extern int convert_to_git(const char *path, const char *src, size_t len, + struct strbuf *dst, enum safe_crlf checksafe); extern int convert_to_working_tree(const char *path, const char *src, size_t len, struct strbuf *dst); /* add */ @@ -3,6 +3,8 @@ #define COLOR_RESET "\033[m" +int git_use_color_default = 0; + static int parse_color(const char *name, int len) { static const char * const color_names[] = { @@ -143,6 +145,16 @@ int git_config_colorbool(const char *var, const char *value, int stdout_is_tty) return 0; } +int git_color_default_config(const char *var, const char *value) +{ + if (!strcmp(var, "color.ui")) { + git_use_color_default = git_config_colorbool(var, value, -1); + return 0; + } + + return git_default_config(var, value); +} + static int color_vfprintf(FILE *fp, const char *color, const char *fmt, va_list args, const char *trail) { @@ -4,6 +4,17 @@ /* "\033[1;38;5;2xx;48;5;2xxm\0" is 23 bytes */ #define COLOR_MAXLEN 24 +/* + * This variable stores the value of color.ui + */ +extern int git_use_color_default; + + +/* + * Use this instead of git_default_config if you need the value of color.ui. + */ +int git_color_default_config(const char *var, const char *value); + int git_config_colorbool(const char *var, const char *value, int stdout_is_tty); void color_parse(const char *var, const char *value, char *dst); int color_fprintf(FILE *fp, const char *color, const char *fmt, ...); @@ -311,6 +311,8 @@ int parse_commit(struct commit *item) unsigned long size; int ret; + if (!item) + return -1; if (item->object.parsed) return 0; buffer = read_sha1_file(item->object.sha1, &type, &size); @@ -385,8 +387,7 @@ struct commit *pop_most_recent_commit(struct commit_list **list, while (parents) { struct commit *commit = parents->item; - parse_commit(commit); - if (!(commit->object.flags & mark)) { + if (!parse_commit(commit) && !(commit->object.flags & mark)) { commit->object.flags |= mark; insert_by_date(commit, list); } @@ -552,8 +553,10 @@ static struct commit_list *merge_bases(struct commit *one, struct commit *two) */ return commit_list_insert(one, &result); - parse_commit(one); - parse_commit(two); + if (parse_commit(one)) + return NULL; + if (parse_commit(two)) + return NULL; one->object.flags |= PARENT1; two->object.flags |= PARENT2; @@ -586,7 +589,8 @@ static struct commit_list *merge_bases(struct commit *one, struct commit *two) parents = parents->next; if ((p->object.flags & flags) == flags) continue; - parse_commit(p); + if (parse_commit(p)) + return NULL; p->object.flags |= flags; insert_by_date(p, &list); } diff --git a/compat/fopen.c b/compat/fopen.c new file mode 100644 index 0000000000..ccb9e89fa4 --- /dev/null +++ b/compat/fopen.c @@ -0,0 +1,26 @@ +#include "../git-compat-util.h" +#undef fopen +FILE *git_fopen(const char *path, const char *mode) +{ + FILE *fp; + struct stat st; + + if (mode[0] == 'w' || mode[0] == 'a') + return fopen(path, mode); + + if (!(fp = fopen(path, mode))) + return NULL; + + if (fstat(fileno(fp), &st)) { + fclose(fp); + return NULL; + } + + if (S_ISDIR(st.st_mode)) { + fclose(fp); + errno = EISDIR; + return NULL; + } + + return fp; +} diff --git a/compat/qsort.c b/compat/qsort.c new file mode 100644 index 0000000000..d93dce2cf8 --- /dev/null +++ b/compat/qsort.c @@ -0,0 +1,62 @@ +#include "../git-compat-util.h" + +/* + * A merge sort implementation, simplified from the qsort implementation + * by Mike Haertel, which is a part of the GNU C Library. + */ + +static void msort_with_tmp(void *b, size_t n, size_t s, + int (*cmp)(const void *, const void *), + char *t) +{ + char *tmp; + char *b1, *b2; + size_t n1, n2; + + if (n <= 1) + return; + + n1 = n / 2; + n2 = n - n1; + b1 = b; + b2 = (char *)b + (n1 * s); + + msort_with_tmp(b1, n1, s, cmp, t); + msort_with_tmp(b2, n2, s, cmp, t); + + tmp = t; + + while (n1 > 0 && n2 > 0) { + if (cmp(b1, b2) <= 0) { + memcpy(tmp, b1, s); + tmp += s; + b1 += s; + --n1; + } else { + memcpy(tmp, b2, s); + tmp += s; + b2 += s; + --n2; + } + } + if (n1 > 0) + memcpy(tmp, b1, n1 * s); + memcpy(b, t, (n - n2) * s); +} + +void git_qsort(void *b, size_t n, size_t s, + int (*cmp)(const void *, const void *)) +{ + const size_t size = n * s; + char buf[1024]; + + if (size < sizeof(buf)) { + /* The temporary array fits on the small on-stack buffer. */ + msort_with_tmp(b, n, s, cmp, buf); + } else { + /* It's somewhat large, so malloc it. */ + char *tmp = malloc(size); + msort_with_tmp(b, n, s, cmp, tmp); + free(tmp); + } +} @@ -280,11 +280,18 @@ int git_parse_ulong(const char *value, unsigned long *ret) return 0; } +static void die_bad_config(const char *name) +{ + if (config_file_name) + die("bad config value for '%s' in %s", name, config_file_name); + die("bad config value for '%s'", name); +} + int git_config_int(const char *name, const char *value) { long ret; if (!git_parse_long(value, &ret)) - die("bad config value for '%s' in %s", name, config_file_name); + die_bad_config(name); return ret; } @@ -292,7 +299,7 @@ unsigned long git_config_ulong(const char *name, const char *value) { unsigned long ret; if (!git_parse_ulong(value, &ret)) - die("bad config value for '%s' in %s", name, config_file_name); + die_bad_config(name); return ret; } @@ -415,6 +422,15 @@ int git_default_config(const char *var, const char *value) return 0; } + if (!strcmp(var, "core.safecrlf")) { + if (value && !strcasecmp(value, "warn")) { + safe_crlf = SAFE_CRLF_WARN; + return 0; + } + safe_crlf = git_config_bool(var, value); + return 0; + } + if (!strcmp(var, "user.name")) { if (!value) return config_error_nonbool(var); @@ -485,14 +501,30 @@ const char *git_etc_gitconfig(void) system_wide = ETC_GITCONFIG; if (!is_absolute_path(system_wide)) { /* interpret path relative to exec-dir */ - const char *exec_path = git_exec_path(); - system_wide = prefix_path(exec_path, strlen(exec_path), - system_wide); + struct strbuf d = STRBUF_INIT; + strbuf_addf(&d, "%s/%s", git_exec_path(), system_wide); + system_wide = strbuf_detach(&d, NULL); } } return system_wide; } +int git_env_bool(const char *k, int def) +{ + const char *v = getenv(k); + return v ? git_config_bool(k, v) : def; +} + +int git_config_system(void) +{ + return !git_env_bool("GIT_CONFIG_NOSYSTEM", 0); +} + +int git_config_global(void) +{ + return !git_env_bool("GIT_CONFIG_NOGLOBAL", 0); +} + int git_config(config_fn_t fn) { int ret = 0; @@ -505,7 +537,7 @@ int git_config(config_fn_t fn) * config file otherwise. */ filename = getenv(CONFIG_ENVIRONMENT); if (!filename) { - if (!access(git_etc_gitconfig(), R_OK)) + if (git_config_system() && !access(git_etc_gitconfig(), R_OK)) ret += git_config_from_file(fn, git_etc_gitconfig()); home = getenv("HOME"); filename = getenv(CONFIG_LOCAL_ENVIRONMENT); @@ -513,7 +545,7 @@ int git_config(config_fn_t fn) filename = repo_config = xstrdup(git_path("config")); } - if (home) { + if (git_config_global() && home) { char *user_config = xstrdup(mkpath("%s/.gitconfig", home)); if (!access(user_config, R_OK)) ret = git_config_from_file(fn, user_config); @@ -474,14 +474,18 @@ char *get_port(char *host) return NULL; } +static struct child_process no_fork; + /* - * This returns NULL if the transport protocol does not need fork(2), or a - * struct child_process object if it does. Once done, finish the connection - * with finish_connect() with the value returned from this function - * (it is safe to call finish_connect() with NULL to support the former - * case). + * This returns a dummy child_process if the transport protocol does not + * need fork(2), or a struct child_process object if it does. Once done, + * finish the connection with finish_connect() with the value returned from + * this function (it is safe to call finish_connect() with NULL to support + * the former case). * - * If it returns, the connect is successful; it just dies on errors. + * If it returns, the connect is successful; it just dies on errors (this + * will hopefully be changed in a libification effort, to return NULL when + * the connection failed). */ struct child_process *git_connect(int fd[2], const char *url_orig, const char *prog, int flags) @@ -579,7 +583,7 @@ struct child_process *git_connect(int fd[2], const char *url_orig, free(url); if (free_path) free(path); - return NULL; + return &no_fork; } conn = xcalloc(1, sizeof(*conn)); @@ -637,7 +641,7 @@ struct child_process *git_connect(int fd[2], const char *url_orig, int finish_connect(struct child_process *conn) { int code; - if (!conn) + if (!conn || conn == &no_fork) return 0; code = finish_command(conn); diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 0d33f9a3dc..4ea727b143 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -64,12 +64,41 @@ __gitdir () __git_ps1 () { - local b="$(git symbolic-ref HEAD 2>/dev/null)" - if [ -n "$b" ]; then + local g="$(git rev-parse --git-dir 2>/dev/null)" + if [ -n "$g" ]; then + local r + local b + if [ -d "$g/../.dotest" ] + then + r="|AM/REBASE" + b="$(git symbolic-ref HEAD 2>/dev/null)" + elif [ -f "$g/.dotest-merge/interactive" ] + then + r="|REBASE-i" + b="$(cat $g/.dotest-merge/head-name)" + elif [ -d "$g/.dotest-merge" ] + then + r="|REBASE-m" + b="$(cat $g/.dotest-merge/head-name)" + elif [ -f "$g/MERGE_HEAD" ] + then + r="|MERGING" + b="$(git symbolic-ref HEAD 2>/dev/null)" + else + if [ -f $g/BISECT_LOG ] + then + r="|BISECTING" + fi + if ! b="$(git symbolic-ref HEAD 2>/dev/null)" + then + b="$(cut -c1-7 $g/HEAD)..." + fi + fi + if [ -n "$1" ]; then - printf "$1" "${b##refs/heads/}" + printf "$1" "${b##refs/heads/}$r" else - printf " (%s)" "${b##refs/heads/}" + printf " (%s)" "${b##refs/heads/}$r" fi fi } diff --git a/contrib/emacs/git-blame.el b/contrib/emacs/git-blame.el index bb671d561e..9f92cd250b 100644 --- a/contrib/emacs/git-blame.el +++ b/contrib/emacs/git-blame.el @@ -105,6 +105,13 @@ selected element from l." (setq ,l (remove e ,l)) e)) +(defvar git-blame-log-oneline-format + "format:[%cr] %cn: %s" + "*Formatting option used for describing current line in the minibuffer. + +This option is used to pass to git log --pretty= command-line option, +and describe which commit the current line was made.") + (defvar git-blame-dark-colors (git-blame-color-scale "0c" "04" "24" "1c" "2c" "34" "14" "3c") "*List of colors (format #RGB) to use in a dark environment. @@ -371,7 +378,8 @@ See also function `git-blame-mode'." (defun git-describe-commit (hash) (with-temp-buffer (call-process "git" nil t nil - "log" "-1" "--pretty=oneline" + "log" "-1" + (concat "--pretty=" git-blame-log-oneline-format) hash) (buffer-substring (point-min) (1- (point-max))))) diff --git a/contrib/emacs/git.el b/contrib/emacs/git.el index d8a06381f4..f69b697f8d 100644 --- a/contrib/emacs/git.el +++ b/contrib/emacs/git.el @@ -35,7 +35,6 @@ ;; ;; TODO ;; - portability to XEmacs -;; - better handling of subprocess errors ;; - diff against other branch ;; - renaming files from the status buffer ;; - creating tags @@ -186,14 +185,25 @@ if there is already one that displays the same directory." (defun git-call-process-env (buffer env &rest args) "Wrapper for call-process that sets environment strings." - (if env - (apply #'call-process "env" nil buffer nil - (append (git-get-env-strings env) (list "git") args)) + (let ((process-environment (append (git-get-env-strings env) + process-environment))) (apply #'call-process "git" nil buffer nil args))) +(defun git-call-process-display-error (&rest args) + "Wrapper for call-process that displays error messages." + (let* ((dir default-directory) + (buffer (get-buffer-create "*Git Command Output*")) + (ok (with-current-buffer buffer + (let ((default-directory dir) + (buffer-read-only nil)) + (erase-buffer) + (eq 0 (apply 'call-process "git" nil (list buffer t) nil args)))))) + (unless ok (display-message-or-buffer buffer)) + ok)) + (defun git-call-process-env-string (env &rest args) "Wrapper for call-process that sets environment strings, -and returns the process output as a string." +and returns the process output as a string, or nil if the git failed." (with-temp-buffer (and (eq 0 (apply #' git-call-process-env t env args)) (buffer-string)))) @@ -377,7 +387,7 @@ and returns the process output as a string." (when reason (push reason args) (push "-m" args)) - (eq 0 (apply #'git-call-process-env nil nil "update-ref" args)))) + (apply 'git-call-process-display-error "update-ref" args))) (defun git-read-tree (tree &optional index-file) "Read a tree into the index file." @@ -558,12 +568,15 @@ and returns the process output as a string." (?\100 " (type change file -> subproject)") (?\120 " (type change symlink -> subproject)") (t " (subproject)"))) + (?\110 nil) ;; directory (internal, not a real git state) (?\000 ;; deleted or unknown (case old-type (?\120 " (symlink)") (?\160 " (subproject)"))) (t (format " (unknown type %o)" new-type))))) - (if str (propertize str 'face 'git-status-face) ""))) + (cond (str (propertize str 'face 'git-status-face)) + ((eq new-type ?\110) "/") + (t "")))) (defun git-rename-as-string (info) "Return a string describing the copy or rename associated with INFO, or an empty string if none." @@ -666,9 +679,11 @@ Return the list of files that haven't been handled." (with-temp-buffer (apply #'git-call-process-env t nil "ls-files" "-z" (append options (list "--") files)) (goto-char (point-min)) - (while (re-search-forward "\\([^\0]*\\)\0" nil t 1) + (while (re-search-forward "\\([^\0]*?\\)\\(/?\\)\0" nil t 1) (let ((name (match-string 1))) - (push (git-create-fileinfo default-state name) infolist) + (push (git-create-fileinfo default-state name 0 + (if (string-equal "/" (match-string 2)) (lsh ?\110 9) 0)) + infolist) (setq files (delete name files))))) (git-insert-info-list status infolist) files)) @@ -713,7 +728,7 @@ Return the list of files that haven't been handled." (defun git-run-ls-files-with-excludes (status files default-state &rest options) "Run git-ls-files on FILES with appropriate --exclude-from options." (let ((exclude-files (git-get-exclude-files))) - (apply #'git-run-ls-files status files default-state + (apply #'git-run-ls-files status files default-state "--directory" (concat "--exclude-per-directory=" git-per-dir-ignore-file) (append options (mapcar (lambda (f) (concat "--exclude-from=" f)) exclude-files))))) @@ -735,6 +750,27 @@ Return the list of files that haven't been handled." (git-refresh-files) (git-refresh-ewoc-hf git-status))) +(defun git-mark-files (status files) + "Mark all the specified FILES, and unmark the others." + (setq files (sort files #'string-lessp)) + (let ((file (and files (pop files))) + (node (ewoc-nth status 0))) + (while node + (let ((info (ewoc-data node))) + (if (and file (string-equal (git-fileinfo->name info) file)) + (progn + (unless (git-fileinfo->marked info) + (setf (git-fileinfo->marked info) t) + (setf (git-fileinfo->needs-refresh info) t)) + (setq file (pop files)) + (setq node (ewoc-next status node))) + (when (git-fileinfo->marked info) + (setf (git-fileinfo->marked info) nil) + (setf (git-fileinfo->needs-refresh info) t)) + (if (and file (string-lessp file (git-fileinfo->name info))) + (setq file (pop files)) + (setq node (ewoc-next status node)))))))) + (defun git-marked-files () "Return a list of all marked files, or if none a list containing just the file at cursor position." (unless git-status (error "Not in git-status buffer.")) @@ -840,16 +876,17 @@ Return the list of files that haven't been handled." (if (or (not (string-equal tree head-tree)) (yes-or-no-p "The tree was not modified, do you really want to perform an empty commit? ")) (let ((commit (git-commit-tree buffer tree head))) - (condition-case nil (delete-file ".git/MERGE_HEAD") (error nil)) - (condition-case nil (delete-file ".git/MERGE_MSG") (error nil)) - (with-current-buffer buffer (erase-buffer)) - (git-update-status-files (git-get-filenames files) 'uptodate) - (git-call-process-env nil nil "rerere") - (git-call-process-env nil nil "gc" "--auto") - (git-refresh-files) - (git-refresh-ewoc-hf git-status) - (message "Committed %s." commit) - (git-run-hook "post-commit" nil)) + (when commit + (condition-case nil (delete-file ".git/MERGE_HEAD") (error nil)) + (condition-case nil (delete-file ".git/MERGE_MSG") (error nil)) + (with-current-buffer buffer (erase-buffer)) + (git-update-status-files (git-get-filenames files) 'uptodate) + (git-call-process-env nil nil "rerere") + (git-call-process-env nil nil "gc" "--auto") + (git-refresh-files) + (git-refresh-ewoc-hf git-status) + (message "Committed %s." commit) + (git-run-hook "post-commit" nil))) (message "Commit aborted.")))) (message "No files to commit."))) (delete-file index-file)))))) @@ -957,11 +994,12 @@ Return the list of files that haven't been handled." "Add marked file(s) to the index cache." (interactive) (let ((files (git-get-filenames (git-marked-files-state 'unknown 'ignored)))) + ;; FIXME: add support for directories (unless files (push (file-relative-name (read-file-name "File to add: " nil nil t)) files)) - (apply #'git-call-process-env nil nil "update-index" "--add" "--" files) - (git-update-status-files files 'uptodate) - (git-success-message "Added" files))) + (when (apply 'git-call-process-display-error "update-index" "--add" "--" files) + (git-update-status-files files 'uptodate) + (git-success-message "Added" files)))) (defun git-ignore-file () "Add marked file(s) to the ignore list." @@ -983,16 +1021,19 @@ Return the list of files that haven't been handled." (format "Remove %d file%s? " (length files) (if (> (length files) 1) "s" ""))) (progn (dolist (name files) - (when (file-exists-p name) (delete-file name))) - (apply #'git-call-process-env nil nil "update-index" "--remove" "--" files) - (git-update-status-files files nil) - (git-success-message "Removed" files)) + (ignore-errors + (if (file-directory-p name) + (delete-directory name) + (delete-file name)))) + (when (apply 'git-call-process-display-error "update-index" "--remove" "--" files) + (git-update-status-files files nil) + (git-success-message "Removed" files))) (message "Aborting")))) (defun git-revert-file () "Revert changes to the marked file(s)." (interactive) - (let ((files (git-marked-files)) + (let ((files (git-marked-files-state 'added 'deleted 'modified 'unmerged)) added modified) (when (and files (yes-or-no-p @@ -1003,21 +1044,31 @@ Return the list of files that haven't been handled." ('deleted (push (git-fileinfo->name info) modified)) ('unmerged (push (git-fileinfo->name info) modified)) ('modified (push (git-fileinfo->name info) modified)))) - (when added - (apply #'git-call-process-env nil nil "update-index" "--force-remove" "--" added)) - (when modified - (apply #'git-call-process-env nil nil "checkout" "HEAD" modified)) - (git-update-status-files (append added modified) 'uptodate) - (git-success-message "Reverted" (git-get-filenames files))))) + ;; check if a buffer contains one of the files and isn't saved + (dolist (file modified) + (let ((buffer (get-file-buffer file))) + (when (and buffer (buffer-modified-p buffer)) + (error "Buffer %s is modified. Please kill or save modified buffers before reverting." (buffer-name buffer))))) + (let ((ok (and + (or (not added) + (apply 'git-call-process-display-error "update-index" "--force-remove" "--" added)) + (or (not modified) + (apply 'git-call-process-display-error "checkout" "HEAD" modified))))) + (git-update-status-files (append added modified) 'uptodate) + (when ok + (dolist (file modified) + (let ((buffer (get-file-buffer file))) + (when buffer (with-current-buffer buffer (revert-buffer t t t))))) + (git-success-message "Reverted" (git-get-filenames files))))))) (defun git-resolve-file () "Resolve conflicts in marked file(s)." (interactive) (let ((files (git-get-filenames (git-marked-files-state 'unmerged)))) (when files - (apply #'git-call-process-env nil nil "update-index" "--" files) - (git-update-status-files files 'uptodate) - (git-success-message "Resolved" files)))) + (when (apply 'git-call-process-display-error "update-index" "--" files) + (git-update-status-files files 'uptodate) + (git-success-message "Resolved" files))))) (defun git-remove-handled () "Remove handled files from the status list." @@ -1063,6 +1114,16 @@ Return the list of files that haven't been handled." (message "Inserting unknown files...done")) (git-remove-handled))) +(defun git-expand-directory (info) + "Expand the directory represented by INFO to list its files." + (when (eq (lsh (git-fileinfo->new-perm info) -9) ?\110) + (let ((dir (git-fileinfo->name info))) + (git-set-filenames-state git-status (list dir) nil) + (git-run-ls-files-with-excludes git-status (list (concat dir "/")) 'unknown "-o") + (git-refresh-files) + (git-refresh-ewoc-hf git-status) + t))) + (defun git-setup-diff-buffer (buffer) "Setup a buffer for displaying a diff." (let ((dir default-directory)) @@ -1199,7 +1260,8 @@ Return the list of files that haven't been handled." (goto-char (point-min)) (when (re-search-forward "\n+\\'" nil t) (replace-match "\n" t t)) - (when sign-off (git-append-sign-off committer-name committer-email))))) + (when sign-off (git-append-sign-off committer-name committer-email))) + buffer)) (defun git-commit-file () "Commit the marked file(s), asking for a commit message." @@ -1232,14 +1294,61 @@ Return the list of files that haven't been handled." (setq buffer-file-coding-system coding-system) (re-search-forward (regexp-quote (concat git-log-msg-separator "\n")) nil t)))) +(defun git-setup-commit-buffer (commit) + "Setup the commit buffer with the contents of COMMIT." + (let (author-name author-email subject date msg) + (with-temp-buffer + (let ((coding-system (git-get-logoutput-coding-system))) + (git-call-process-env t nil "log" "-1" commit) + (goto-char (point-min)) + (when (re-search-forward "^Author: *\\(.*\\) <\\(.*\\)>$" nil t) + (setq author-name (match-string 1)) + (setq author-email (match-string 2))) + (when (re-search-forward "^Date: *\\(.*\\)$" nil t) + (setq date (match-string 1))) + (while (re-search-forward "^ \\(.*\\)$" nil t) + (push (match-string 1) msg)) + (setq msg (nreverse msg)) + (setq subject (pop msg)) + (while (and msg (zerop (length (car msg))) (pop msg))))) + (git-setup-log-buffer (get-buffer-create "*git-commit*") + author-name author-email subject date + (mapconcat #'identity msg "\n")))) + +(defun git-get-commit-files (commit) + "Retrieve the list of files modified by COMMIT." + (let (files) + (with-temp-buffer + (git-call-process-env t nil "diff-tree" "-r" "-z" "--name-only" "--no-commit-id" commit) + (goto-char (point-min)) + (while (re-search-forward "\\([^\0]*\\)\0" nil t 1) + (push (match-string 1) files))) + files)) + +(defun git-amend-commit () + "Undo the last commit on HEAD, and set things up to commit an +amended version of it." + (interactive) + (unless git-status (error "Not in git-status buffer.")) + (when (git-empty-db-p) (error "No commit to amend.")) + (let* ((commit (git-rev-parse "HEAD")) + (files (git-get-commit-files commit))) + (when (git-call-process-display-error "reset" "--soft" "HEAD^") + (git-update-status-files (copy-sequence files) 'uptodate) + (git-mark-files git-status files) + (git-refresh-files) + (git-setup-commit-buffer commit) + (git-commit-file)))) + (defun git-find-file () "Visit the current file in its own buffer." (interactive) (unless git-status (error "Not in git-status buffer.")) (let ((info (ewoc-data (ewoc-locate git-status)))) - (find-file (git-fileinfo->name info)) - (when (eq 'unmerged (git-fileinfo->state info)) - (smerge-mode 1)))) + (unless (git-expand-directory info) + (find-file (git-fileinfo->name info)) + (when (eq 'unmerged (git-fileinfo->state info)) + (smerge-mode 1))))) (defun git-find-file-other-window () "Visit the current file in its own buffer in another window." @@ -1309,6 +1418,7 @@ Return the list of files that haven't been handled." (unless git-status-mode-map (let ((map (make-keymap)) + (commit-map (make-sparse-keymap)) (diff-map (make-sparse-keymap)) (toggle-map (make-sparse-keymap))) (suppress-keymap map) @@ -1317,6 +1427,7 @@ Return the list of files that haven't been handled." (define-key map " " 'git-next-file) (define-key map "a" 'git-add-file) (define-key map "c" 'git-commit-file) + (define-key map "\C-c" commit-map) (define-key map "d" diff-map) (define-key map "=" 'git-diff-file) (define-key map "f" 'git-find-file) @@ -1342,6 +1453,8 @@ Return the list of files that haven't been handled." (define-key map "x" 'git-remove-handled) (define-key map "\C-?" 'git-unmark-file-up) (define-key map "\M-\C-?" 'git-unmark-all) + ; the commit submap + (define-key commit-map "\C-a" 'git-amend-commit) ; the diff submap (define-key diff-map "b" 'git-diff-file-base) (define-key diff-map "c" 'git-diff-file-combined) diff --git a/contrib/fast-import/git-p4 b/contrib/fast-import/git-p4 index c80a6da252..781a0cbbbc 100755 --- a/contrib/fast-import/git-p4 +++ b/contrib/fast-import/git-p4 @@ -469,9 +469,7 @@ class P4Submit(Command): optparse.make_option("--origin", dest="origin"), optparse.make_option("--reset", action="store_true", dest="reset"), optparse.make_option("--log-substitutions", dest="substFile"), - optparse.make_option("--dry-run", action="store_true"), optparse.make_option("--direct", dest="directSubmit", action="store_true"), - optparse.make_option("--trust-me-like-a-fool", dest="trustMeLikeAFool", action="store_true"), optparse.make_option("-M", dest="detectRename", action="store_true"), ] self.description = "Submit changes from git to the perforce depot." @@ -479,12 +477,10 @@ class P4Submit(Command): self.firstTime = True self.reset = False self.interactive = True - self.dryRun = False self.substFile = "" self.firstTime = True self.origin = "" self.directSubmit = False - self.trustMeLikeAFool = False self.detectRename = False self.verbose = False self.isWindows = (platform.system() == "Windows") @@ -681,57 +677,30 @@ class P4Submit(Command): separatorLine += "\r" separatorLine += "\n" - response = "e" - if self.trustMeLikeAFool: - response = "y" - - firstIteration = True - while response == "e": - if not firstIteration: - response = raw_input("Do you want to submit this change? [y]es/[e]dit/[n]o/[s]kip ") - firstIteration = False - if response == "e": - [handle, fileName] = tempfile.mkstemp() - tmpFile = os.fdopen(handle, "w+") - tmpFile.write(submitTemplate + separatorLine + diff) - tmpFile.close() - defaultEditor = "vi" - if platform.system() == "Windows": - defaultEditor = "notepad" - editor = os.environ.get("EDITOR", defaultEditor); - system(editor + " " + fileName) - tmpFile = open(fileName, "rb") - message = tmpFile.read() - tmpFile.close() - os.remove(fileName) - submitTemplate = message[:message.index(separatorLine)] - if self.isWindows: - submitTemplate = submitTemplate.replace("\r\n", "\n") - - if response == "y" or response == "yes": - if self.dryRun: - print submitTemplate - raw_input("Press return to continue...") - else: - if self.directSubmit: - print "Submitting to git first" - os.chdir(self.oldWorkingDirectory) - write_pipe("git commit -a -F -", submitTemplate) - os.chdir(self.clientPath) - - write_pipe("p4 submit -i", submitTemplate) - elif response == "s": - for f in editedFiles: - system("p4 revert \"%s\"" % f); - for f in filesToAdd: - system("p4 revert \"%s\"" % f); - system("rm %s" %f) - for f in filesToDelete: - system("p4 delete \"%s\"" % f); - return - else: - print "Not submitting!" - self.interactive = False + [handle, fileName] = tempfile.mkstemp() + tmpFile = os.fdopen(handle, "w+") + tmpFile.write(submitTemplate + separatorLine + diff) + tmpFile.close() + defaultEditor = "vi" + if platform.system() == "Windows": + defaultEditor = "notepad" + editor = os.environ.get("EDITOR", defaultEditor); + system(editor + " " + fileName) + tmpFile = open(fileName, "rb") + message = tmpFile.read() + tmpFile.close() + os.remove(fileName) + submitTemplate = message[:message.index(separatorLine)] + if self.isWindows: + submitTemplate = submitTemplate.replace("\r\n", "\n") + + if self.directSubmit: + print "Submitting to git first" + os.chdir(self.oldWorkingDirectory) + write_pipe("git commit -a -F -", submitTemplate) + os.chdir(self.clientPath) + + write_pipe("p4 submit -i", submitTemplate) else: fileName = "submit.txt" file = open(fileName, "w+") @@ -828,10 +797,8 @@ class P4Submit(Command): sync = P4Sync() sync.run([]) - response = raw_input("Do you want to rebase current HEAD from Perforce now using git-p4 rebase? [y]es/[n]o ") - if response == "y" or response == "yes": - rebase = P4Rebase() - rebase.rebase() + rebase = P4Rebase() + rebase.rebase() os.remove(self.configFile) return True @@ -964,9 +931,13 @@ class P4Sync(Command): stat = filedata[j] j += 1 text = '' - while j < len(filedata) and filedata[j]['code'] in ('text', - 'binary'): - text += filedata[j]['data'] + while j < len(filedata) and filedata[j]['code'] in ('text', 'unicode', 'binary'): + tmp = filedata[j]['data'] + if stat['type'] in ('text+ko', 'unicode+ko', 'binary+ko'): + tmp = re.sub(r'(?i)\$(Id|Header):[^$]*\$',r'$\1$', tmp) + elif stat['type'] in ('text+k', 'ktext', 'kxtext', 'unicode+k', 'binary+k'): + tmp = re.sub(r'(?i)\$(Id|Header|Author|Date|DateTime|Change|File|Revision):[^$]*\$',r'$\1$', tmp) + text += tmp j += 1 @@ -1640,6 +1611,11 @@ class P4Rebase(Command): return self.rebase() def rebase(self): + if os.system("git update-index --refresh") != 0: + die("Some files in your working directory are modified and different than what is in your index. You can use git update-index <filename> to bring the index up-to-date or stash away all your changes with git stash."); + if len(read_pipe("git diff-index HEAD --")) > 0: + die("You have uncommited changes. Please commit them before rebasing or stash them away with git stash."); + [upstream, settings] = findUpstreamBranchPoint() if len(upstream) == 0: die("Cannot find upstream branchpoint for rebase") @@ -1670,7 +1646,7 @@ class P4Clone(P4Sync): depotPath = args[0] depotDir = re.sub("(@[^@]*)$", "", depotPath) depotDir = re.sub("(#[^#]*)$", "", depotDir) - depotDir = re.sub(r"\.\.\.$,", "", depotDir) + depotDir = re.sub(r"\.\.\.$", "", depotDir) depotDir = re.sub(r"/$", "", depotDir) return os.path.split(depotDir)[1] @@ -85,8 +85,39 @@ static int is_binary(unsigned long size, struct text_stat *stats) return 0; } +static void check_safe_crlf(const char *path, int action, + struct text_stat *stats, enum safe_crlf checksafe) +{ + if (!checksafe) + return; + + if (action == CRLF_INPUT || auto_crlf <= 0) { + /* + * CRLFs would not be restored by checkout: + * check if we'd remove CRLFs + */ + if (stats->crlf) { + if (checksafe == SAFE_CRLF_WARN) + warning("CRLF will be replaced by LF in %s.", path); + else /* i.e. SAFE_CRLF_FAIL */ + die("CRLF would be replaced by LF in %s.", path); + } + } else if (auto_crlf > 0) { + /* + * CRLFs would be added by checkout: + * check if we have "naked" LFs + */ + if (stats->lf != stats->crlf) { + if (checksafe == SAFE_CRLF_WARN) + warning("LF will be replaced by CRLF in %s", path); + else /* i.e. SAFE_CRLF_FAIL */ + die("LF would be replaced by CRLF in %s", path); + } + } +} + static int crlf_to_git(const char *path, const char *src, size_t len, - struct strbuf *buf, int action) + struct strbuf *buf, int action, enum safe_crlf checksafe) { struct text_stat stats; char *dst; @@ -95,9 +126,6 @@ static int crlf_to_git(const char *path, const char *src, size_t len, return 0; gather_stats(src, len, &stats); - /* No CR? Nothing to convert, regardless. */ - if (!stats.cr) - return 0; if (action == CRLF_GUESS) { /* @@ -115,6 +143,12 @@ static int crlf_to_git(const char *path, const char *src, size_t len, return 0; } + check_safe_crlf(path, action, &stats, checksafe); + + /* Optimization: No CR? Nothing to convert, regardless. */ + if (!stats.cr) + return 0; + /* only grow if not in place */ if (strbuf_avail(buf) + buf->len < len) strbuf_grow(buf, len - buf->len); @@ -536,7 +570,8 @@ static int git_path_check_ident(const char *path, struct git_attr_check *check) return !!ATTR_TRUE(value); } -int convert_to_git(const char *path, const char *src, size_t len, struct strbuf *dst) +int convert_to_git(const char *path, const char *src, size_t len, + struct strbuf *dst, enum safe_crlf checksafe) { struct git_attr_check check[3]; int crlf = CRLF_GUESS; @@ -558,7 +593,7 @@ int convert_to_git(const char *path, const char *src, size_t len, struct strbuf src = dst->buf; len = dst->len; } - ret |= crlf_to_git(path, src, len, dst, crlf); + ret |= crlf_to_git(path, src, len, dst, crlf, checksafe); if (ret) { src = dst->buf; len = dst->len; diff --git a/diff-lib.c b/diff-lib.c index d85d8f34ba..03eaa7cef3 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -9,6 +9,7 @@ #include "revision.h" #include "cache-tree.h" #include "path-list.h" +#include "unpack-trees.h" /* * diff-files @@ -37,7 +38,7 @@ static int get_mode(const char *path, int *mode) if (!path || !strcmp(path, "/dev/null")) *mode = 0; else if (!strcmp(path, "-")) - *mode = ntohl(create_ce_mode(0666)); + *mode = create_ce_mode(0666); else if (stat(path, &st)) return error("Could not access '%s'", path); else @@ -384,7 +385,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option) continue; } else - dpath->mode = ntohl(ce_mode_from_stat(ce, st.st_mode)); + dpath->mode = ce_mode_from_stat(ce, st.st_mode); while (i < entries) { struct cache_entry *nce = active_cache[i]; @@ -398,10 +399,10 @@ int run_diff_files(struct rev_info *revs, unsigned int option) */ stage = ce_stage(nce); if (2 <= stage) { - int mode = ntohl(nce->ce_mode); + int mode = nce->ce_mode; num_compare_stages++; hashcpy(dpath->parent[stage-2].sha1, nce->sha1); - dpath->parent[stage-2].mode = ntohl(ce_mode_from_stat(nce, mode)); + dpath->parent[stage-2].mode = ce_mode_from_stat(nce, mode); dpath->parent[stage-2].status = DIFF_STATUS_MODIFIED; } @@ -435,6 +436,8 @@ int run_diff_files(struct rev_info *revs, unsigned int option) continue; } + if (ce_uptodate(ce)) + continue; if (lstat(ce->name, &st) < 0) { if (errno != ENOENT && errno != ENOTDIR) { perror(ce->name); @@ -442,15 +445,15 @@ int run_diff_files(struct rev_info *revs, unsigned int option) } if (silent_on_removed) continue; - diff_addremove(&revs->diffopt, '-', ntohl(ce->ce_mode), + diff_addremove(&revs->diffopt, '-', ce->ce_mode, ce->sha1, ce->name, NULL); continue; } changed = ce_match_stat(ce, &st, ce_option); if (!changed && !DIFF_OPT_TST(&revs->diffopt, FIND_COPIES_HARDER)) continue; - oldmode = ntohl(ce->ce_mode); - newmode = ntohl(ce_mode_from_stat(ce, st.st_mode)); + oldmode = ce->ce_mode; + newmode = ce_mode_from_stat(ce, st.st_mode); diff_change(&revs->diffopt, oldmode, newmode, ce->sha1, (changed ? null_sha1 : ce->sha1), ce->name, NULL); @@ -471,7 +474,7 @@ static void diff_index_show_file(struct rev_info *revs, struct cache_entry *ce, unsigned char *sha1, unsigned int mode) { - diff_addremove(&revs->diffopt, prefix[0], ntohl(mode), + diff_addremove(&revs->diffopt, prefix[0], mode, sha1, ce->name, NULL); } @@ -550,14 +553,14 @@ static int show_modified(struct rev_info *revs, p->len = pathlen; memcpy(p->path, new->name, pathlen); p->path[pathlen] = 0; - p->mode = ntohl(mode); + p->mode = mode; hashclr(p->sha1); memset(p->parent, 0, 2 * sizeof(struct combine_diff_parent)); p->parent[0].status = DIFF_STATUS_MODIFIED; - p->parent[0].mode = ntohl(new->ce_mode); + p->parent[0].mode = new->ce_mode; hashcpy(p->parent[0].sha1, new->sha1); p->parent[1].status = DIFF_STATUS_MODIFIED; - p->parent[1].mode = ntohl(old->ce_mode); + p->parent[1].mode = old->ce_mode; hashcpy(p->parent[1].sha1, old->sha1); show_combined_diff(p, 2, revs->dense_combined_merges, revs); free(p); @@ -569,119 +572,154 @@ static int show_modified(struct rev_info *revs, !DIFF_OPT_TST(&revs->diffopt, FIND_COPIES_HARDER)) return 0; - mode = ntohl(mode); - oldmode = ntohl(oldmode); - diff_change(&revs->diffopt, oldmode, mode, old->sha1, sha1, old->name, NULL); return 0; } -static int diff_cache(struct rev_info *revs, - struct cache_entry **ac, int entries, - const char **pathspec, - int cached, int match_missing) +/* + * This turns all merge entries into "stage 3". That guarantees that + * when we read in the new tree (into "stage 1"), we won't lose sight + * of the fact that we had unmerged entries. + */ +static void mark_merge_entries(void) { - while (entries) { - struct cache_entry *ce = *ac; - int same = (entries > 1) && ce_same_name(ce, ac[1]); + int i; + for (i = 0; i < active_nr; i++) { + struct cache_entry *ce = active_cache[i]; + if (!ce_stage(ce)) + continue; + ce->ce_flags |= CE_STAGEMASK; + } +} - if (DIFF_OPT_TST(&revs->diffopt, QUIET) && - DIFF_OPT_TST(&revs->diffopt, HAS_CHANGES)) - break; +/* + * This gets a mix of an existing index and a tree, one pathname entry + * at a time. The index entry may be a single stage-0 one, but it could + * also be multiple unmerged entries (in which case idx_pos/idx_nr will + * give you the position and number of entries in the index). + */ +static void do_oneway_diff(struct unpack_trees_options *o, + struct cache_entry *idx, + struct cache_entry *tree, + int idx_pos, int idx_nr) +{ + struct rev_info *revs = o->unpack_data; + int match_missing, cached; - if (!ce_path_match(ce, pathspec)) - goto skip_entry; + /* + * Backward compatibility wart - "diff-index -m" does + * not mean "do not ignore merges", but "match_missing". + * + * But with the revision flag parsing, that's found in + * "!revs->ignore_merges". + */ + cached = o->index_only; + match_missing = !revs->ignore_merges; + + if (cached && idx && ce_stage(idx)) { + if (tree) + diff_unmerge(&revs->diffopt, idx->name, idx->ce_mode, idx->sha1); + return; + } + + /* + * Something added to the tree? + */ + if (!tree) { + show_new_file(revs, idx, cached, match_missing); + return; + } - switch (ce_stage(ce)) { - case 0: - /* No stage 1 entry? That means it's a new file */ - if (!same) { - show_new_file(revs, ce, cached, match_missing); + /* + * Something removed from the tree? + */ + if (!idx) { + diff_index_show_file(revs, "-", tree, tree->sha1, tree->ce_mode); + return; + } + + /* Show difference between old and new */ + show_modified(revs, tree, idx, 1, cached, match_missing); +} + +/* + * Count how many index entries go with the first one + */ +static inline int count_skip(const struct cache_entry *src, int pos) +{ + int skip = 1; + + /* We can only have multiple entries if the first one is not stage-0 */ + if (ce_stage(src)) { + struct cache_entry **p = active_cache + pos; + int namelen = ce_namelen(src); + + for (;;) { + const struct cache_entry *ce; + pos++; + if (pos >= active_nr) break; - } - /* Show difference between old and new */ - show_modified(revs, ac[1], ce, 1, - cached, match_missing); - break; - case 1: - /* No stage 3 (merge) entry? - * That means it's been deleted. - */ - if (!same) { - diff_index_show_file(revs, "-", ce, - ce->sha1, ce->ce_mode); + ce = *++p; + if (ce_namelen(ce) != namelen) break; - } - /* We come here with ce pointing at stage 1 - * (original tree) and ac[1] pointing at stage - * 3 (unmerged). show-modified with - * report-missing set to false does not say the - * file is deleted but reports true if work - * tree does not have it, in which case we - * fall through to report the unmerged state. - * Otherwise, we show the differences between - * the original tree and the work tree. - */ - if (!cached && - !show_modified(revs, ce, ac[1], 0, - cached, match_missing)) + if (memcmp(ce->name, src->name, namelen)) break; - diff_unmerge(&revs->diffopt, ce->name, - ntohl(ce->ce_mode), ce->sha1); - break; - case 3: - diff_unmerge(&revs->diffopt, ce->name, - 0, null_sha1); - break; - - default: - die("impossible cache entry stage"); + skip++; } - -skip_entry: - /* - * Ignore all the different stages for this file, - * we've handled the relevant cases now. - */ - do { - ac++; - entries--; - } while (entries && ce_same_name(ce, ac[0])); } - return 0; + return skip; } /* - * This turns all merge entries into "stage 3". That guarantees that - * when we read in the new tree (into "stage 1"), we won't lose sight - * of the fact that we had unmerged entries. + * The unpack_trees() interface is designed for merging, so + * the different source entries are designed primarily for + * the source trees, with the old index being really mainly + * used for being replaced by the result. + * + * For diffing, the index is more important, and we only have a + * single tree. + * + * We're supposed to return how many index entries we want to skip. + * + * This wrapper makes it all more readable, and takes care of all + * the fairly complex unpack_trees() semantic requirements, including + * the skipping, the path matching, the type conflict cases etc. */ -static void mark_merge_entries(void) +static int oneway_diff(struct cache_entry **src, + struct unpack_trees_options *o, + int index_pos) { - int i; - for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; - if (!ce_stage(ce)) - continue; - ce->ce_flags |= htons(CE_STAGEMASK); - } + int skip = 0; + struct cache_entry *idx = src[0]; + struct cache_entry *tree = src[1]; + struct rev_info *revs = o->unpack_data; + + if (index_pos >= 0) + skip = count_skip(idx, index_pos); + + /* + * Unpack-trees generates a DF/conflict entry if + * there was a directory in the index and a tree + * in the tree. From a diff standpoint, that's a + * delete of the tree and a create of the file. + */ + if (tree == o->df_conflict_entry) + tree = NULL; + + if (ce_path_match(idx ? idx : tree, revs->prune_data)) + do_oneway_diff(o, idx, tree, index_pos, skip); + + return skip; } int run_diff_index(struct rev_info *revs, int cached) { - int ret; struct object *ent; struct tree *tree; const char *tree_name; - int match_missing = 0; - - /* - * Backward compatibility wart - "diff-index -m" does - * not mean "do not ignore merges", but totally different. - */ - if (!revs->ignore_merges) - match_missing = 1; + struct unpack_trees_options opts; + struct tree_desc t; mark_merge_entries(); @@ -690,13 +728,20 @@ int run_diff_index(struct rev_info *revs, int cached) tree = parse_tree_indirect(ent->sha1); if (!tree) return error("bad tree object %s", tree_name); - if (read_tree(tree, 1, revs->prune_data)) - return error("unable to read tree object %s", tree_name); - ret = diff_cache(revs, active_cache, active_nr, revs->prune_data, - cached, match_missing); + + memset(&opts, 0, sizeof(opts)); + opts.head_idx = 1; + opts.index_only = cached; + opts.merge = 1; + opts.fn = oneway_diff; + opts.unpack_data = revs; + + init_tree_desc(&t, tree->buffer, tree->size); + unpack_trees(1, &t, &opts); + diffcore_std(&revs->diffopt); diff_flush(&revs->diffopt); - return ret; + return 0; } int do_diff_cache(const unsigned char *tree_sha1, struct diff_options *opt) @@ -706,6 +751,8 @@ int do_diff_cache(const unsigned char *tree_sha1, struct diff_options *opt) int i; struct cache_entry **dst; struct cache_entry *last = NULL; + struct unpack_trees_options opts; + struct tree_desc t; /* * This is used by git-blame to run diff-cache internally; @@ -722,8 +769,7 @@ int do_diff_cache(const unsigned char *tree_sha1, struct diff_options *opt) cache_tree_invalidate_path(active_cache_tree, ce->name); last = ce; - ce->ce_mode = 0; - ce->ce_flags &= ~htons(CE_STAGEMASK); + ce->ce_flags |= CE_REMOVE; } *dst++ = ce; } @@ -734,8 +780,15 @@ int do_diff_cache(const unsigned char *tree_sha1, struct diff_options *opt) tree = parse_tree_indirect(tree_sha1); if (!tree) die("bad tree object %s", sha1_to_hex(tree_sha1)); - if (read_tree(tree, 1, opt->paths)) - return error("unable to read tree %s", sha1_to_hex(tree_sha1)); - return diff_cache(&revs, active_cache, active_nr, revs.prune_data, - 1, 0); + + memset(&opts, 0, sizeof(opts)); + opts.head_idx = 1; + opts.index_only = 1; + opts.merge = 1; + opts.fn = oneway_diff; + opts.unpack_data = &revs; + + init_tree_desc(&t, tree->buffer, tree->size); + unpack_trees(1, &t, &opts); + return 0; } @@ -20,7 +20,7 @@ static int diff_detect_rename_default; static int diff_rename_limit_default = 100; -static int diff_use_color_default; +int diff_use_color_default = -1; static const char *external_diff_cmd_cfg; int diff_auto_refresh_index = 1; @@ -191,7 +191,7 @@ int git_diff_basic_config(const char *var, const char *value) } } - return git_default_config(var, value); + return git_color_default_config(var, value); } static char *quote_two(const char *one, const char *two) @@ -1199,7 +1199,7 @@ static struct builtin_funcname_pattern { "new\\|return\\|switch\\|throw\\|while\\)\n" "^[ ]*\\(\\([ ]*" "[A-Za-z_][A-Za-z_0-9]*\\)\\{2,\\}" - "[ ]*([^;]*$\\)" }, + "[ ]*([^;]*\\)$" }, { "tex", "^\\(\\\\\\(sub\\)*section{.*\\)$" }, }; @@ -1512,17 +1512,22 @@ static int reuse_worktree_file(const char *name, const unsigned char *sha1, int if (pos < 0) return 0; ce = active_cache[pos]; - if ((lstat(name, &st) < 0) || - !S_ISREG(st.st_mode) || /* careful! */ - ce_match_stat(ce, &st, 0) || - hashcmp(sha1, ce->sha1)) + + /* + * This is not the sha1 we are looking for, or + * unreusable because it is not a regular file. + */ + if (hashcmp(sha1, ce->sha1) || !S_ISREG(ce->ce_mode)) return 0; - /* we return 1 only when we can stat, it is a regular file, - * stat information matches, and sha1 recorded in the cache - * matches. I.e. we know the file in the work tree really is - * the same as the <name, sha1> pair. + + /* + * If ce matches the file in the work tree, we can reuse it. */ - return 1; + if (ce_uptodate(ce) || + (!lstat(name, &st) && !ce_match_stat(ce, &st, 0))) + return 1; + + return 0; } static int populate_from_stdin(struct diff_filespec *s) @@ -1626,7 +1631,7 @@ int diff_populate_filespec(struct diff_filespec *s, int size_only) * Convert from working tree format to canonical git format */ strbuf_init(&buf, 0); - if (convert_to_git(s->path, s->data, s->size, &buf)) { + if (convert_to_git(s->path, s->data, s->size, &buf, safe_crlf)) { size_t size = 0; munmap(s->data, s->size); s->should_munmap = 0; @@ -2050,7 +2055,7 @@ void diff_setup(struct diff_options *options) options->change = diff_change; options->add_remove = diff_addremove; - if (diff_use_color_default) + if (diff_use_color_default > 0) DIFF_OPT_SET(options, COLOR_DIFF); else DIFF_OPT_CLR(options, COLOR_DIFF); @@ -174,6 +174,7 @@ extern void diff_unmerge(struct diff_options *, extern int git_diff_basic_config(const char *var, const char *value); extern int git_diff_ui_config(const char *var, const char *value); +extern int diff_use_color_default; extern void diff_setup(struct diff_options *); extern int diff_opt_parse(struct diff_options *, const char **, int); extern int diff_setup_done(struct diff_options *); @@ -17,6 +17,7 @@ struct path_simplify { static int read_directory_recursive(struct dir_struct *dir, const char *path, const char *base, int baselen, int check_only, const struct path_simplify *simplify); +static int get_dtype(struct dirent *de, const char *path); int common_prefix(const char **pathspec) { @@ -126,18 +127,34 @@ static int no_wildcard(const char *string) void add_exclude(const char *string, const char *base, int baselen, struct exclude_list *which) { - struct exclude *x = xmalloc(sizeof (*x)); + struct exclude *x; + size_t len; + int to_exclude = 1; + int flags = 0; - x->to_exclude = 1; if (*string == '!') { - x->to_exclude = 0; + to_exclude = 0; string++; } - x->pattern = string; + len = strlen(string); + if (len && string[len - 1] == '/') { + char *s; + x = xmalloc(sizeof(*x) + len); + s = (char*)(x+1); + memcpy(s, string, len - 1); + s[len - 1] = '\0'; + string = s; + x->pattern = s; + flags = EXC_FLAG_MUSTBEDIR; + } else { + x = xmalloc(sizeof(*x)); + x->pattern = string; + } + x->to_exclude = to_exclude; x->patternlen = strlen(string); x->base = base; x->baselen = baselen; - x->flags = 0; + x->flags = flags; if (!strchr(string, '/')) x->flags |= EXC_FLAG_NODIR; if (no_wildcard(string)) @@ -261,7 +278,7 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen) * Return 1 for exclude, 0 for include and -1 for undecided. */ static int excluded_1(const char *pathname, - int pathlen, const char *basename, + int pathlen, const char *basename, int *dtype, struct exclude_list *el) { int i; @@ -272,6 +289,13 @@ static int excluded_1(const char *pathname, const char *exclude = x->pattern; int to_exclude = x->to_exclude; + if (x->flags & EXC_FLAG_MUSTBEDIR) { + if (*dtype == DT_UNKNOWN) + *dtype = get_dtype(NULL, pathname); + if (*dtype != DT_DIR) + continue; + } + if (x->flags & EXC_FLAG_NODIR) { /* match basename */ if (x->flags & EXC_FLAG_NOWILDCARD) { @@ -314,7 +338,7 @@ static int excluded_1(const char *pathname, return -1; /* undecided */ } -int excluded(struct dir_struct *dir, const char *pathname) +int excluded(struct dir_struct *dir, const char *pathname, int *dtype_p) { int pathlen = strlen(pathname); int st; @@ -323,7 +347,8 @@ int excluded(struct dir_struct *dir, const char *pathname) prep_exclude(dir, pathname, basename-pathname); for (st = EXC_CMDL; st <= EXC_FILE; st++) { - switch (excluded_1(pathname, pathlen, basename, &dir->exclude_list[st])) { + switch (excluded_1(pathname, pathlen, basename, + dtype_p, &dir->exclude_list[st])) { case 0: return 0; case 1: @@ -346,7 +371,7 @@ static struct dir_entry *dir_entry_new(const char *pathname, int len) struct dir_entry *dir_add_name(struct dir_struct *dir, const char *pathname, int len) { - if (cache_name_pos(pathname, len) >= 0) + if (cache_name_exists(pathname, len)) return NULL; ALLOC_GROW(dir->entries, dir->nr+1, dir->alloc); @@ -391,7 +416,7 @@ static enum exist_status directory_exists_in_index(const char *dirname, int len) break; if (endchar == '/') return index_directory; - if (!endchar && S_ISGITLINK(ntohl(ce->ce_mode))) + if (!endchar && S_ISGITLINK(ce->ce_mode)) return index_gitdir; } return index_nonexistent; @@ -508,7 +533,7 @@ static int in_pathspec(const char *path, int len, const struct path_simplify *si static int get_dtype(struct dirent *de, const char *path) { - int dtype = DTYPE(de); + int dtype = de ? DTYPE(de) : DT_UNKNOWN; struct stat st; if (dtype != DT_UNKNOWN) @@ -560,7 +585,8 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co if (simplify_away(fullname, baselen + len, simplify)) continue; - exclude = excluded(dir, fullname); + dtype = DTYPE(de); + exclude = excluded(dir, fullname, &dtype); if (exclude && dir->collect_ignored && in_pathspec(fullname, baselen + len, simplify)) dir_add_ignored(dir, fullname, baselen + len); @@ -572,7 +598,8 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co if (exclude && !dir->show_ignored) continue; - dtype = get_dtype(de, fullname); + if (dtype == DT_UNKNOWN) + dtype = get_dtype(de, fullname); /* * Do we want to see just the ignored files? @@ -9,6 +9,7 @@ struct dir_entry { #define EXC_FLAG_NODIR 1 #define EXC_FLAG_NOWILDCARD 2 #define EXC_FLAG_ENDSWITH 4 +#define EXC_FLAG_MUSTBEDIR 8 struct exclude_list { int nr; @@ -67,7 +68,7 @@ extern int match_pathspec(const char **pathspec, const char *name, int namelen, extern int read_directory(struct dir_struct *, const char *path, const char *base, int baselen, const char **pathspec); -extern int excluded(struct dir_struct *, const char *); +extern int excluded(struct dir_struct *, const char *, int *); extern void add_excludes_from_file(struct dir_struct *, const char *fname); extern void add_exclude(const char *string, const char *base, int baselen, struct exclude_list *which); @@ -103,7 +103,7 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout int fd; long wrote; - switch (ntohl(ce->ce_mode) & S_IFMT) { + switch (ce->ce_mode & S_IFMT) { char *new; struct strbuf buf; unsigned long size; @@ -129,7 +129,7 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout strcpy(path, ".merge_file_XXXXXX"); fd = mkstemp(path); } else - fd = create_file(path, ntohl(ce->ce_mode)); + fd = create_file(path, ce->ce_mode); if (fd < 0) { free(new); return error("git-checkout-index: unable to create file %s (%s)", @@ -221,7 +221,7 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *t unlink(path); if (S_ISDIR(st.st_mode)) { /* If it is a gitlink, leave it alone! */ - if (S_ISGITLINK(ntohl(ce->ce_mode))) + if (S_ISGITLINK(ce->ce_mode)) return 0; if (!state->force) return error("%s is a directory", path); diff --git a/environment.c b/environment.c index fa3633372b..3527f1663f 100644 --- a/environment.c +++ b/environment.c @@ -35,6 +35,7 @@ int pager_use_color = 1; const char *editor_program; const char *excludes_file; int auto_crlf = 0; /* 1: both ways, -1: only when adding git objects */ +enum safe_crlf safe_crlf = SAFE_CRLF_WARN; unsigned whitespace_rule_cfg = WS_DEFAULT_RULE; /* This is set by setup_git_dir_gently() and/or git_default_config() */ diff --git a/fast-import.c b/fast-import.c index 9b71ccc479..0d3449f2ce 100644 --- a/fast-import.c +++ b/fast-import.c @@ -372,6 +372,8 @@ static void write_branch_report(FILE *rpt, struct branch *b) fputc('\n', rpt); } +static void dump_marks_helper(FILE *, uintmax_t, struct mark_set *); + static void write_crash_report(const char *err) { char *loc = git_path("fast_import_crash_%d", getpid()); @@ -430,12 +432,37 @@ static void write_crash_report(const char *err) write_branch_report(rpt, b); } + if (first_tag) { + struct tag *tg; + fputc('\n', rpt); + fputs("Annotated Tags\n", rpt); + fputs("--------------\n", rpt); + for (tg = first_tag; tg; tg = tg->next_tag) { + fputs(sha1_to_hex(tg->sha1), rpt); + fputc(' ', rpt); + fputs(tg->name, rpt); + fputc('\n', rpt); + } + } + + fputc('\n', rpt); + fputs("Marks\n", rpt); + fputs("-----\n", rpt); + if (mark_file) + fprintf(rpt, " exported to %s\n", mark_file); + else + dump_marks_helper(rpt, 0, marks); + fputc('\n', rpt); fputs("-------------------\n", rpt); fputs("END OF CRASH REPORT\n", rpt); fclose(rpt); } +static void end_packfile(void); +static void unkeep_all_packs(void); +static void dump_marks(void); + static NORETURN void die_nicely(const char *err, va_list params) { static int zombie; @@ -449,6 +476,9 @@ static NORETURN void die_nicely(const char *err, va_list params) if (!zombie) { zombie = 1; write_crash_report(message); + end_packfile(); + unkeep_all_packs(); + dump_marks(); } exit(128); } diff --git a/fetch-pack.h b/fetch-pack.h index a7888ea302..8d35ef60bf 100644 --- a/fetch-pack.h +++ b/fetch-pack.h @@ -16,6 +16,8 @@ struct fetch_pack_args }; struct ref *fetch_pack(struct fetch_pack_args *args, + int fd[], struct child_process *conn, + const struct ref *ref, const char *dest, int nr_heads, char **heads, diff --git a/git-add--interactive.perl b/git-add--interactive.perl index 17ca5b84f0..a0a81f134a 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -82,6 +82,19 @@ sub list_untracked { my $status_fmt = '%12s %12s %s'; my $status_head = sprintf($status_fmt, 'staged', 'unstaged', 'path'); +{ + my $initial; + sub is_initial_commit { + $initial = system('git rev-parse HEAD -- >/dev/null 2>&1') != 0 + unless defined $initial; + return $initial; + } +} + +sub get_empty_tree { + return '4b825dc642cb6eb9a060e54bf8d69288fbee4904'; +} + # Returns list of hashes, contents of each of which are: # VALUE: pathname # BINARY: is a binary path @@ -103,8 +116,10 @@ sub list_modified { return if (!@tracked); } + my $reference = is_initial_commit() ? get_empty_tree() : 'HEAD'; for (run_cmd_pipe(qw(git diff-index --cached - --numstat --summary HEAD --), @tracked)) { + --numstat --summary), $reference, + '--', @tracked)) { if (($add, $del, $file) = /^([-\d]+) ([-\d]+) (.*)/) { my ($change, $bin); @@ -476,21 +491,27 @@ sub revert_cmd { HEADER => $status_head, }, list_modified()); if (@update) { - my @lines = run_cmd_pipe(qw(git ls-tree HEAD --), - map { $_->{VALUE} } @update); - my $fh; - open $fh, '| git update-index --index-info' - or die; - for (@lines) { - print $fh $_; + if (is_initial_commit()) { + system(qw(git rm --cached), + map { $_->{VALUE} } @update); } - close($fh); - for (@update) { - if ($_->{INDEX_ADDDEL} && - $_->{INDEX_ADDDEL} eq 'create') { - system(qw(git update-index --force-remove --), - $_->{VALUE}); - print "note: $_->{VALUE} is untracked now.\n"; + else { + my @lines = run_cmd_pipe(qw(git ls-tree HEAD --), + map { $_->{VALUE} } @update); + my $fh; + open $fh, '| git update-index --index-info' + or die; + for (@lines) { + print $fh $_; + } + close($fh); + for (@update) { + if ($_->{INDEX_ADDDEL} && + $_->{INDEX_ADDDEL} eq 'create') { + system(qw(git update-index --force-remove --), + $_->{VALUE}); + print "note: $_->{VALUE} is untracked now.\n"; + } } } refresh(); @@ -956,7 +977,9 @@ sub diff_cmd { HEADER => $status_head, }, @mods); return if (!@them); - system(qw(git diff -p --cached HEAD --), map { $_->{VALUE} } @them); + my $reference = is_initial_commit() ? get_empty_tree() : 'HEAD'; + system(qw(git diff -p --cached), $reference, '--', + map { $_->{VALUE} } @them); } sub quit_cmd { diff --git a/git-bisect.sh b/git-bisect.sh index 6594a62919..74715edf0b 100755 --- a/git-bisect.sh +++ b/git-bisect.sh @@ -331,9 +331,9 @@ bisect_visualize() { if test $# = 0 then - case "${DISPLAY+set}" in + case "${DISPLAY+set}${MSYSTEM+set}${SECURITYSESSIONID+set}" in '') set git log ;; - set) set gitk ;; + set*) set gitk ;; esac else case "$1" in diff --git a/git-compat-util.h b/git-compat-util.h index 4df90cb34e..2a40703c85 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -204,6 +204,11 @@ void *gitmemmem(const void *haystack, size_t haystacklen, const void *needle, size_t needlelen); #endif +#ifdef FREAD_READS_DIRECTORIES +#define fopen(a,b) git_fopen(a,b) +extern FILE *git_fopen(const char*, const char*); +#endif + #ifdef __GLIBC_PREREQ #if __GLIBC_PREREQ(2, 1) #define HAVE_STRCHRNUL @@ -426,4 +431,10 @@ static inline int strtol_i(char const *s, int base, int *result) return 0; } +#ifdef INTERNAL_QSORT +void git_qsort(void *base, size_t nmemb, size_t size, + int(*compar)(const void *, const void *)); +#define qsort git_qsort +#endif + #endif diff --git a/git-cvsexportcommit.perl b/git-cvsexportcommit.perl index d2e50c3429..b6036bd4d3 100755 --- a/git-cvsexportcommit.perl +++ b/git-cvsexportcommit.perl @@ -5,6 +5,7 @@ use Getopt::Std; use File::Temp qw(tempdir); use Data::Dumper; use File::Basename qw(basename dirname); +use File::Spec; our ($opt_h, $opt_P, $opt_p, $opt_v, $opt_c, $opt_f, $opt_a, $opt_m, $opt_d, $opt_u, $opt_w); @@ -15,17 +16,15 @@ $opt_h && usage(); die "Need at least one commit identifier!" unless @ARGV; if ($opt_w) { + # Remember where GIT_DIR is before changing to CVS checkout unless ($ENV{GIT_DIR}) { - # Remember where our GIT_DIR is before changing to CVS checkout + # No GIT_DIR set. Figure it out for ourselves my $gd =`git-rev-parse --git-dir`; chomp($gd); - if ($gd eq '.git') { - my $wd = `pwd`; - chomp($wd); - $gd = $wd."/.git" ; - } $ENV{GIT_DIR} = $gd; } + # Make sure GIT_DIR is absolute + $ENV{GIT_DIR} = File::Spec->rel2abs($ENV{GIT_DIR}); if (! -d $opt_w."/CVS" ) { die "$opt_w is not a CVS checkout"; @@ -198,15 +197,39 @@ if (@canstatusfiles) { my @updated = xargs_safe_pipe_capture([@cvs, 'update'], @canstatusfiles); print @updated; } - my @cvsoutput; - @cvsoutput = xargs_safe_pipe_capture([@cvs, 'status'], @canstatusfiles); - my $matchcount = 0; - foreach my $l (@cvsoutput) { - chomp $l; - if ( $l =~ /^File:/ and $l =~ /Status: (.*)$/ ) { - $cvsstat{$canstatusfiles[$matchcount]} = $1; - $matchcount++; + # "cvs status" reorders the parameters, notably when there are multiple + # arguments with the same basename. So be precise here. + + my %added = map { $_ => 1 } @afiles; + my %todo = map { $_ => 1 } @canstatusfiles; + + while (%todo) { + my @canstatusfiles2 = (); + my %fullname = (); + foreach my $name (keys %todo) { + my $basename = basename($name); + + $basename = "no file " . $basename if (exists($added{$basename})); + chomp($basename); + + if (!exists($fullname{$basename})) { + $fullname{$basename} = $name; + push (@canstatusfiles2, $name); + delete($todo{$name}); } + } + my @cvsoutput; + @cvsoutput = xargs_safe_pipe_capture([@cvs, 'status'], @canstatusfiles2); + foreach my $l (@cvsoutput) { + chomp $l; + if ($l =~ /^File:\s+(.*\S)\s+Status: (.*)$/) { + if (!exists($fullname{$1})) { + print STDERR "Huh? Status reported for unexpected file '$1'\n"; + } else { + $cvsstat{$fullname{$1}} = $2; + } + } + } } } diff --git a/git-gui/Makefile b/git-gui/Makefile index 34438cdf5c..081d7550a7 100644 --- a/git-gui/Makefile +++ b/git-gui/Makefile @@ -13,6 +13,7 @@ GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') uname_O := $(shell sh -c 'uname -o 2>/dev/null || echo not') +uname_R := $(shell sh -c 'uname -r 2>/dev/null || echo not') SCRIPT_SH = git-gui.sh GITGUI_MAIN := git-gui @@ -93,7 +94,14 @@ endif TCL_PATH ?= tclsh TCLTK_PATH ?= wish -TKFRAMEWORK = /Library/Frameworks/Tk.framework/Resources/Wish.app + +ifeq ($(uname_S),Darwin) + TKFRAMEWORK = /Library/Frameworks/Tk.framework/Resources/Wish.app + ifeq ($(shell expr "$(uname_R)" : '9\.'),2) + TKFRAMEWORK = /System/Library/Frameworks/Tk.framework/Resources/Wish\ Shell.app + endif + TKEXECUTABLE = $(shell basename "$(TKFRAMEWORK)" .app) +endif ifeq ($(findstring $(MAKEFLAGS),s),s) QUIET_GEN = @@ -147,7 +155,7 @@ git-gui: GIT-VERSION-FILE GIT-GUI-VARS echo then >>$@+ && \ echo ' 'echo \'git-gui version '$(GITGUI_VERSION)'\' >>$@+ && \ echo else >>$@+ && \ - echo ' 'exec \''$(libdir_SQ)/Git Gui.app/Contents/MacOS/Wish'\' \ + echo ' 'exec \''$(libdir_SQ)/Git Gui.app/Contents/MacOS/$(subst \,,$(TKEXECUTABLE))'\' \ '"$$0" "$$@"' >>$@+ && \ echo fi >>$@+ && \ chmod +x $@+ && \ @@ -157,14 +165,15 @@ Git\ Gui.app: GIT-VERSION-FILE GIT-GUI-VARS \ macosx/Info.plist \ macosx/git-gui.icns \ macosx/AppMain.tcl \ - $(TKFRAMEWORK)/Contents/MacOS/Wish + $(TKFRAMEWORK)/Contents/MacOS/$(TKEXECUTABLE) $(QUIET_GEN)rm -rf '$@' '$@'+ && \ mkdir -p '$@'+/Contents/MacOS && \ mkdir -p '$@'+/Contents/Resources/Scripts && \ - cp '$(subst ','\'',$(TKFRAMEWORK))/Contents/MacOS/Wish' \ + cp '$(subst ','\'',$(subst \,,$(TKFRAMEWORK)/Contents/MacOS/$(TKEXECUTABLE)))' \ '$@'+/Contents/MacOS && \ cp macosx/git-gui.icns '$@'+/Contents/Resources && \ sed -e 's/@@GITGUI_VERSION@@/$(GITGUI_VERSION)/g' \ + -e 's/@@GITGUI_TKEXECUTABLE@@/$(TKEXECUTABLE)/g' \ macosx/Info.plist \ >'$@'+/Contents/Info.plist && \ sed -e 's|@@gitexecdir@@|$(gitexecdir_SQ)|' \ diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh index f42e461fd4..5d65272e26 100755 --- a/git-gui/git-gui.sh +++ b/git-gui/git-gui.sh @@ -612,6 +612,7 @@ set default_config(gui.pruneduringfetch) false set default_config(gui.trustmtime) false set default_config(gui.diffcontext) 5 set default_config(gui.newbranchtemplate) {} +set default_config(gui.spellingdictionary) {} set default_config(gui.fontui) [font configure font_ui] set default_config(gui.fontdiff) [font configure font_diff] set font_descs { @@ -1683,6 +1684,7 @@ set is_quitting 0 proc do_quit {} { global ui_comm is_quitting repo_config commit_type global GITGUI_BCK_exists GITGUI_BCK_i + global ui_comm_spell if {$is_quitting} return set is_quitting 1 @@ -1710,6 +1712,12 @@ proc do_quit {} { } } + # -- Cancel our spellchecker if its running. + # + if {[info exists ui_comm_spell]} { + $ui_comm_spell stop + } + # -- Remove our editor backup, its not needed. # after cancel $GITGUI_BCK_i @@ -2454,7 +2462,7 @@ $ctxm add separator $ctxm add command \ -label [mc "Sign Off"] \ -command do_signoff -bind_button3 $ui_comm "tk_popup $ctxm %X %Y" +set ui_comm_ctxm $ctxm # -- Diff Header # @@ -2857,6 +2865,30 @@ if {[winfo exists $ui_comm]} { } backup_commit_buffer + + # -- If the user has aspell available we can drive it + # in pipe mode to spellcheck the commit message. + # + set spell_cmd [list |] + set spell_dict [get_config gui.spellingdictionary] + lappend spell_cmd aspell + if {$spell_dict ne {}} { + lappend spell_cmd --master=$spell_dict + } + lappend spell_cmd --mode=none + lappend spell_cmd --encoding=utf-8 + lappend spell_cmd pipe + if {$spell_dict eq {none} + || [catch {set spell_fd [open $spell_cmd r+]} spell_err]} { + bind_button3 $ui_comm [list tk_popup $ui_comm_ctxm %X %Y] + } else { + set ui_comm_spell [spellcheck::init \ + $spell_fd \ + $ui_comm \ + $ui_comm_ctxm \ + ] + } + unset -nocomplain spell_cmd spell_fd spell_err spell_dict } lock_index begin-read diff --git a/git-gui/lib/about.tcl b/git-gui/lib/about.tcl index 719fc547b3..47be8eb97a 100644 --- a/git-gui/lib/about.tcl +++ b/git-gui/lib/about.tcl @@ -4,6 +4,7 @@ proc do_about {} { global appvers copyright oguilib global tcl_patchLevel tk_patchLevel + global ui_comm_spell set w .about_dialog toplevel $w @@ -40,6 +41,10 @@ proc do_about {} { append v "Tcl version $tcl_patchLevel" append v ", Tk version $tk_patchLevel" } + if {[info exists ui_comm_spell]} { + append v "\n" + append v [$ui_comm_spell version] + } set d {} append d "git wrapper: $::_git\n" diff --git a/git-gui/lib/checkout_op.tcl b/git-gui/lib/checkout_op.tcl index f243966924..6e1411711b 100644 --- a/git-gui/lib/checkout_op.tcl +++ b/git-gui/lib/checkout_op.tcl @@ -280,7 +280,7 @@ The rescan will be automatically started now. } elseif {[is_config_true gui.trustmtime]} { _readtree $this } else { - ui_status {Refreshing file status...} + ui_status [mc "Refreshing file status..."] set fd [git_read update-index \ -q \ --unmerged \ @@ -320,7 +320,7 @@ method _readtree {} { set readtree_d {} $::main_status start \ [mc "Updating working directory to '%s'..." [_name $this]] \ - {files checked out} + [mc "files checked out"] set fd [git_read --stderr read-tree \ -m \ @@ -447,7 +447,7 @@ If you wanted to be on a branch, create one now starting from 'This Detached Che } else { repository_state commit_type HEAD MERGE_HEAD set PARENT $HEAD - ui_status "Checked out '$name'." + ui_status [mc "Checked out '%s'." $name] } delete_this } diff --git a/git-gui/lib/commit.tcl b/git-gui/lib/commit.tcl index 947b201c32..40a7103557 100644 --- a/git-gui/lib/commit.tcl +++ b/git-gui/lib/commit.tcl @@ -218,7 +218,7 @@ A good commit message has the following format: return } - ui_status {Calling pre-commit hook...} + ui_status [mc "Calling pre-commit hook..."] set pch_error {} fconfigure $fd_ph -blocking 0 -translation binary -eofchar {} fileevent $fd_ph readable \ @@ -233,7 +233,7 @@ proc commit_prehook_wait {fd_ph curHEAD msg_p} { if {[eof $fd_ph]} { if {[catch {close $fd_ph}]} { catch {file delete $msg_p} - ui_status {Commit declined by pre-commit hook.} + ui_status [mc "Commit declined by pre-commit hook."] hook_failed_popup pre-commit $pch_error unlock_index } else { @@ -256,7 +256,7 @@ proc commit_commitmsg {curHEAD msg_p} { return } - ui_status {Calling commit-msg hook...} + ui_status [mc "Calling commit-msg hook..."] set pch_error {} fconfigure $fd_ph -blocking 0 -translation binary -eofchar {} fileevent $fd_ph readable \ @@ -271,7 +271,7 @@ proc commit_commitmsg_wait {fd_ph curHEAD msg_p} { if {[eof $fd_ph]} { if {[catch {close $fd_ph}]} { catch {file delete $msg_p} - ui_status {Commit declined by commit-msg hook.} + ui_status [mc "Commit declined by commit-msg hook."] hook_failed_popup commit-msg $pch_error unlock_index } else { @@ -284,7 +284,7 @@ proc commit_commitmsg_wait {fd_ph curHEAD msg_p} { } proc commit_writetree {curHEAD msg_p} { - ui_status {Committing changes...} + ui_status [mc "Committing changes..."] set fd_wt [git_read write-tree] fileevent $fd_wt readable \ [list commit_committree $fd_wt $curHEAD $msg_p] @@ -301,7 +301,7 @@ proc commit_committree {fd_wt curHEAD msg_p} { if {[catch {close $fd_wt} err]} { catch {file delete $msg_p} error_popup [strcat [mc "write-tree failed:"] "\n\n$err"] - ui_status {Commit failed.} + ui_status [mc "Commit failed."] unlock_index return } @@ -345,7 +345,7 @@ A rescan will be automatically started now. if {[catch {set cmt_id [eval git $cmd]} err]} { catch {file delete $msg_p} error_popup [strcat [mc "commit-tree failed:"] "\n\n$err"] - ui_status {Commit failed.} + ui_status [mc "Commit failed."] unlock_index return } @@ -365,7 +365,7 @@ A rescan will be automatically started now. } err]} { catch {file delete $msg_p} error_popup [strcat [mc "update-ref failed:"] "\n\n$err"] - ui_status {Commit failed.} + ui_status [mc "Commit failed."] unlock_index return } diff --git a/git-gui/lib/index.tcl b/git-gui/lib/index.tcl index 30a244cc17..3c1fce7475 100644 --- a/git-gui/lib/index.tcl +++ b/git-gui/lib/index.tcl @@ -310,7 +310,7 @@ proc add_helper {txt paths} { update_index \ $txt \ $pathList \ - [concat $after {ui_status {Ready to commit.}}] + [concat $after {ui_status [mc "Ready to commit."]}] } } diff --git a/git-gui/lib/merge.tcl b/git-gui/lib/merge.tcl index 63e14279c1..cc26b07808 100644 --- a/git-gui/lib/merge.tcl +++ b/git-gui/lib/merge.tcl @@ -116,8 +116,7 @@ method _start {} { lappend cmd HEAD lappend cmd $name - set msg [mc "Merging %s and %s" $current_branch $stitle] - ui_status "$msg..." + ui_status [mc "Merging %s and %s..." $current_branch $stitle] set cons [console::new [mc "Merge"] "merge $stitle"] console::exec $cons $cmd [cb _finish $cons] @@ -236,7 +235,7 @@ Continue with resetting the current changes?"] set fd [git_read --stderr read-tree --reset -u -v HEAD] fconfigure $fd -blocking 0 -translation binary fileevent $fd readable [namespace code [list _reset_wait $fd]] - $::main_status start [mc "Aborting"] {files reset} + $::main_status start [mc "Aborting"] [mc "files reset"] } else { unlock_index } diff --git a/git-gui/lib/option.tcl b/git-gui/lib/option.tcl index f812e5e89a..ea80df0092 100644 --- a/git-gui/lib/option.tcl +++ b/git-gui/lib/option.tcl @@ -5,6 +5,7 @@ proc save_config {} { global default_config font_descs global repo_config global_config global repo_config_new global_config_new + global ui_comm_spell foreach option $font_descs { set name [lindex $option 0] @@ -52,11 +53,23 @@ proc save_config {} { set repo_config($name) $value } } + + if {[info exists repo_config(gui.spellingdictionary)]} { + set value $repo_config(gui.spellingdictionary) + if {$value eq {none}} { + if {[info exists ui_comm_spell]} { + $ui_comm_spell stop + } + } elseif {[info exists ui_comm_spell]} { + $ui_comm_spell lang $value + } + } } proc do_options {} { global repo_config global_config font_descs global repo_config_new global_config_new + global ui_comm_spell array unset repo_config_new array unset global_config_new @@ -159,6 +172,32 @@ proc do_options {} { } } + set all_dicts [linsert \ + [spellcheck::available_langs] \ + 0 \ + none] + incr optid + foreach f {repo global} { + if {![info exists ${f}_config_new(gui.spellingdictionary)]} { + if {[info exists ui_comm_spell]} { + set value [$ui_comm_spell lang] + } else { + set value none + } + set ${f}_config_new(gui.spellingdictionary) $value + } + + frame $w.$f.$optid + label $w.$f.$optid.l -text [mc "Spelling Dictionary:"] + eval tk_optionMenu $w.$f.$optid.v \ + ${f}_config_new(gui.spellingdictionary) \ + $all_dicts + pack $w.$f.$optid.l -side left -anchor w -fill x + pack $w.$f.$optid.v -side right -anchor e -padx 5 + pack $w.$f.$optid -side top -anchor w -fill x + } + unset all_dicts + set all_fonts [lsort [font families]] foreach option $font_descs { set name [lindex $option 0] diff --git a/git-gui/lib/spellcheck.tcl b/git-gui/lib/spellcheck.tcl new file mode 100644 index 0000000000..7f018e4009 --- /dev/null +++ b/git-gui/lib/spellcheck.tcl @@ -0,0 +1,363 @@ +# git-gui spellchecking support through aspell +# Copyright (C) 2008 Shawn Pearce + +class spellcheck { + +field s_fd {} ; # pipe to aspell +field s_version ; # aspell version string +field s_lang ; # current language code + +field w_text ; # text widget we are spelling +field w_menu ; # context menu for the widget +field s_menuidx 0 ; # last index of insertion into $w_menu + +field s_i ; # timer registration for _run callbacks +field s_clear 0 ; # did we erase mispelled tags yet? +field s_seen [list] ; # lines last seen from $w_text in _run +field s_checked [list] ; # lines already checked +field s_pending [list] ; # [$line $data] sent to aspell +field s_suggest ; # array, list of suggestions, keyed by misspelling + +constructor init {pipe_fd ui_text ui_menu} { + set w_text $ui_text + set w_menu $ui_menu + + _connect $this $pipe_fd + return $this +} + +method _connect {pipe_fd} { + fconfigure $pipe_fd \ + -encoding utf-8 \ + -eofchar {} \ + -translation lf + + if {[gets $pipe_fd s_version] <= 0} { + close $pipe_fd + error [mc "Not connected to aspell"] + } + if {{@(#) } ne [string range $s_version 0 4]} { + close $pipe_fd + error [strcat [mc "Unrecognized aspell version"] ": $s_version"] + } + set s_version [string range $s_version 5 end] + + puts $pipe_fd ! ; # enable terse mode + puts $pipe_fd {$$cr master} ; # fetch the language + flush $pipe_fd + + gets $pipe_fd s_lang + regexp {[/\\]([^/\\]+)\.[^\.]+$} $s_lang _ s_lang + + if {$::default_config(gui.spellingdictionary) eq {} + && [get_config gui.spellingdictionary] eq {}} { + set ::default_config(gui.spellingdictionary) $s_lang + } + + if {$s_fd ne {}} { + catch {close $s_fd} + } + set s_fd $pipe_fd + + fconfigure $s_fd -blocking 0 + fileevent $s_fd readable [cb _read] + + $w_text tag conf misspelled \ + -foreground red \ + -underline 1 + bind_button3 $w_text [cb _popup_suggest %X %Y @%x,%y] + + array unset s_suggest + set s_seen [list] + set s_checked [list] + set s_pending [list] + _run $this +} + +method lang {{n {}}} { + if {$n ne {} && $s_lang ne $n} { + set spell_cmd [list |] + lappend spell_cmd aspell + lappend spell_cmd --master=$n + lappend spell_cmd --mode=none + lappend spell_cmd --encoding=UTF-8 + lappend spell_cmd pipe + _connect $this [open $spell_cmd r+] + } + return $s_lang +} + +method version {} { + return "$s_version, $s_lang" +} + +method stop {} { + while {$s_menuidx > 0} { + $w_menu delete 0 + incr s_menuidx -1 + } + $w_text tag delete misspelled + + catch {close $s_fd} + catch {after cancel $s_i} + set s_fd {} + set s_i {} + set s_lang {} +} + +method _popup_suggest {X Y pos} { + while {$s_menuidx > 0} { + $w_menu delete 0 + incr s_menuidx -1 + } + + set b_loc [$w_text index "$pos wordstart"] + set e_loc [_wordend $this $b_loc] + set orig [$w_text get $b_loc $e_loc] + set tags [$w_text tag names $b_loc] + + if {[lsearch -exact $tags misspelled] >= 0} { + if {[info exists s_suggest($orig)]} { + set cnt 0 + foreach s $s_suggest($orig) { + if {$cnt < 5} { + $w_menu insert $s_menuidx command \ + -label $s \ + -command [cb _replace $b_loc $e_loc $s] + incr s_menuidx + incr cnt + } else { + break + } + } + } else { + $w_menu insert $s_menuidx command \ + -label [mc "No Suggestions"] \ + -state disabled + incr s_menuidx + } + $w_menu insert $s_menuidx separator + incr s_menuidx + } + + $w_text mark set saved-insert insert + tk_popup $w_menu $X $Y +} + +method _replace {b_loc e_loc word} { + $w_text configure -autoseparators 0 + $w_text edit separator + + $w_text delete $b_loc $e_loc + $w_text insert $b_loc $word + + $w_text edit separator + $w_text configure -autoseparators 1 + $w_text mark set insert saved-insert +} + +method _restart_timer {} { + set s_i [after 300 [cb _run]] +} + +proc _match_length {max_line arr_name} { + upvar $arr_name a + + if {[llength $a] > $max_line} { + set a [lrange $a 0 $max_line] + } + while {[llength $a] <= $max_line} { + lappend a {} + } +} + +method _wordend {pos} { + set pos [$w_text index "$pos wordend"] + set tags [$w_text tag names $pos] + while {[lsearch -exact $tags misspelled] >= 0} { + set pos [$w_text index "$pos +1c"] + set tags [$w_text tag names $pos] + } + return $pos +} + +method _run {} { + set cur_pos [$w_text index {insert -1c}] + set cur_line [lindex [split $cur_pos .] 0] + set max_line [lindex [split [$w_text index end] .] 0] + _match_length $max_line s_seen + _match_length $max_line s_checked + + # Nothing in the message buffer? Nothing to spellcheck. + # + if {$cur_line == 1 + && $max_line == 2 + && [$w_text get 1.0 end] eq "\n"} { + array unset s_suggest + _restart_timer $this + return + } + + set active 0 + for {set n 1} {$n <= $max_line} {incr n} { + set s [$w_text get "$n.0" "$n.end"] + + # Don't spellcheck the current line unless we are at + # a word boundary. The user might be typing on it. + # + if {$n == $cur_line + && ![regexp {^\W$} [$w_text get $cur_pos insert]]} { + + # If the current word is mispelled remove the tag + # but force a spellcheck later. + # + set tags [$w_text tag names $cur_pos] + if {[lsearch -exact $tags misspelled] >= 0} { + $w_text tag remove misspelled \ + "$cur_pos wordstart" \ + [_wordend $this $cur_pos] + lset s_seen $n $s + lset s_checked $n {} + } + + continue + } + + if {[lindex $s_seen $n] eq $s + && [lindex $s_checked $n] ne $s} { + # Don't send empty lines to Aspell it doesn't check them. + # + if {$s eq {}} { + lset s_checked $n $s + continue + } + + # Don't send typical s-b-o lines as the emails are + # almost always misspelled according to Aspell. + # + if {[regexp -nocase {^[a-z-]+-by:.*<.*@.*>$} $s]} { + $w_text tag remove misspelled "$n.0" "$n.end" + lset s_checked $n $s + continue + } + + puts $s_fd ^$s + lappend s_pending [list $n $s] + set active 1 + } else { + # Delay until another idle loop to make sure we don't + # spellcheck lines the user is actively changing. + # + lset s_seen $n $s + } + } + + if {$active} { + set s_clear 1 + flush $s_fd + } else { + _restart_timer $this + } +} + +method _read {} { + while {[gets $s_fd line] >= 0} { + set lineno [lindex $s_pending 0 0] + + if {$s_clear} { + $w_text tag remove misspelled "$lineno.0" "$lineno.end" + set s_clear 0 + } + + if {$line eq {}} { + lset s_checked $lineno [lindex $s_pending 0 1] + set s_pending [lrange $s_pending 1 end] + set s_clear 1 + continue + } + + set sugg [list] + switch -- [string range $line 0 1] { + {& } { + set line [split [string range $line 2 end] :] + set info [split [lindex $line 0] { }] + set orig [lindex $info 0] + set offs [lindex $info 2] + foreach s [split [lindex $line 1] ,] { + lappend sugg [string range $s 1 end] + } + } + {# } { + set info [split [string range $line 2 end] { }] + set orig [lindex $info 0] + set offs [lindex $info 1] + } + default { + puts stderr "<spell> $line" + continue + } + } + + incr offs -1 + set b_loc "$lineno.$offs" + set e_loc [$w_text index "$lineno.$offs wordend"] + set curr [$w_text get $b_loc $e_loc] + + # At least for English curr = "bob", orig = "bob's" + # so Tk didn't include the 's but Aspell did. We + # try to round out the word. + # + while {$curr ne $orig + && [string equal -length [string length $curr] $curr $orig]} { + set n_loc [$w_text index "$e_loc +1c"] + set n_curr [$w_text get $b_loc $n_loc] + if {$n_curr eq $curr} { + break + } + set curr $n_curr + set e_loc $n_loc + } + + if {$curr eq $orig} { + $w_text tag add misspelled $b_loc $e_loc + if {[llength $sugg] > 0} { + set s_suggest($orig) $sugg + } else { + unset -nocomplain s_suggest($orig) + } + } else { + unset -nocomplain s_suggest($orig) + } + } + + fconfigure $s_fd -block 1 + if {[eof $s_fd]} { + if {![catch {close $s_fd} err]} { + set err [mc "unexpected eof from aspell"] + } + catch {after cancel $s_i} + $w_text tag remove misspelled 1.0 end + error_popup [strcat "Spell Checker Failed" "\n\n" $err] + return + } + fconfigure $s_fd -block 0 + + if {[llength $s_pending] == 0} { + _restart_timer $this + } +} + +proc available_langs {} { + set langs [list] + catch { + set fd [open [list | aspell dump dicts] r] + while {[gets $fd line] >= 0} { + if {$line eq {}} continue + lappend langs $line + } + close $fd + } + return $langs +} + +} diff --git a/git-gui/macosx/Info.plist b/git-gui/macosx/Info.plist index 99913ec57a..b3bf15fa1c 100644 --- a/git-gui/macosx/Info.plist +++ b/git-gui/macosx/Info.plist @@ -5,7 +5,7 @@ <key>CFBundleDevelopmentRegion</key> <string>English</string> <key>CFBundleExecutable</key> - <string>Wish</string> + <string>@@GITGUI_TKEXECUTABLE@@</string> <key>CFBundleGetInfoString</key> <string>Git Gui @@GITGUI_VERSION@@ © 2006-2007 Shawn Pearce, et. al.</string> <key>CFBundleIconFile</key> diff --git a/git-gui/po/de.po b/git-gui/po/de.po index 2dfe07e06f..d7c38f9c73 100644 --- a/git-gui/po/de.po +++ b/git-gui/po/de.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: git-gui\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2007-11-24 10:36+0100\n" -"PO-Revision-Date: 2008-01-15 20:33+0100\n" +"POT-Creation-Date: 2008-02-02 10:14+0100\n" +"PO-Revision-Date: 2008-02-02 10:18+0100\n" "Last-Translator: Christian Stimming <stimming@tuhh.de>\n" "Language-Team: German\n" "MIME-Version: 1.0\n" @@ -343,7 +343,9 @@ msgstr "Online-Dokumentation" #: git-gui.sh:2201 #, tcl-format msgid "fatal: cannot stat path %s: No such file or directory" -msgstr "Fehler: Verzeichnis »%s« kann nicht gelesen werden: Datei oder Verzeichnis nicht gefunden" +msgstr "" +"Fehler: Verzeichnis »%s« kann nicht gelesen werden: Datei oder Verzeichnis " +"nicht gefunden" #: git-gui.sh:2234 msgid "Current Branch:" @@ -371,19 +373,19 @@ msgstr "Erste Versionsbeschreibung:" #: git-gui.sh:2370 msgid "Amended Commit Message:" -msgstr "Nachgebesserte Versionsbeschreibung:" +msgstr "Nachgebesserte Beschreibung:" #: git-gui.sh:2371 msgid "Amended Initial Commit Message:" -msgstr "Nachgebesserte erste Versionsbeschreibung:" +msgstr "Nachgebesserte erste Beschreibung:" #: git-gui.sh:2372 msgid "Amended Merge Commit Message:" -msgstr "Nachgebesserte Zusammenführungs-Versionsbeschreibung:" +msgstr "Nachgebesserte Zusammenführungs-Beschreibung:" #: git-gui.sh:2373 msgid "Merge Commit Message:" -msgstr "Zusammenführungs-Versionsbeschreibung:" +msgstr "Zusammenführungs-Beschreibung:" #: git-gui.sh:2374 msgid "Commit Message:" @@ -397,31 +399,31 @@ msgstr "Alle kopieren" msgid "File:" msgstr "Datei:" -#: git-gui.sh:2545 -msgid "Refresh" -msgstr "Aktualisieren" - -#: git-gui.sh:2566 +#: git-gui.sh:2573 msgid "Apply/Reverse Hunk" msgstr "Kontext anwenden/umkehren" -#: git-gui.sh:2572 -msgid "Decrease Font Size" -msgstr "Schriftgröße verkleinern" - -#: git-gui.sh:2576 -msgid "Increase Font Size" -msgstr "Schriftgröße vergrößern" - -#: git-gui.sh:2581 +#: git-gui.sh:2579 msgid "Show Less Context" msgstr "Weniger Zeilen anzeigen" -#: git-gui.sh:2588 +#: git-gui.sh:2586 msgid "Show More Context" msgstr "Mehr Zeilen anzeigen" -#: git-gui.sh:2602 +#: git-gui.sh:2594 +msgid "Refresh" +msgstr "Aktualisieren" + +#: git-gui.sh:2615 +msgid "Decrease Font Size" +msgstr "Schriftgröße verkleinern" + +#: git-gui.sh:2619 +msgid "Increase Font Size" +msgstr "Schriftgröße vergrößern" + +#: git-gui.sh:2630 msgid "Unstage Hunk From Commit" msgstr "Kontext aus Bereitstellung herausnehmen" @@ -542,7 +544,7 @@ msgstr "Kopiert oder verschoben durch:" #: lib/branch_checkout.tcl:14 lib/branch_checkout.tcl:19 msgid "Checkout Branch" -msgstr "Zweig umstellen" +msgstr "Auf Zweig umstellen" #: lib/branch_checkout.tcl:23 msgid "Checkout" @@ -805,11 +807,15 @@ msgstr "" msgid "Updating working directory to '%s'..." msgstr "Arbeitskopie umstellen auf »%s«..." +#: lib/checkout_op.tcl:323 +msgid "files checked out" +msgstr "Dateien aktualisiert" + #: lib/checkout_op.tcl:353 #, tcl-format msgid "Aborted checkout of '%s' (file level merging is required)." msgstr "" -"Zweig umstellen von »%s« abgebrochen (Zusammenführen der Dateien ist " +"Auf Zweig »%s« umstellen abgebrochen (Zusammenführen der Dateien ist " "notwendig)." #: lib/checkout_op.tcl:354 @@ -1069,15 +1075,21 @@ msgstr "Für Objekt konnte kein Hardlink erstellt werden: %s" #: lib/choose_repository.tcl:847 msgid "Cannot fetch branches and objects. See console output for details." -msgstr "Zweige und Objekte konnten nicht angefordert werden. Kontrollieren Sie die Ausgaben auf der Konsole für weitere Angaben." +msgstr "" +"Zweige und Objekte konnten nicht angefordert werden. Kontrollieren Sie die " +"Ausgaben auf der Konsole für weitere Angaben." #: lib/choose_repository.tcl:858 msgid "Cannot fetch tags. See console output for details." -msgstr "Markierungen konnten nicht angefordert werden. Kontrollieren Sie die Ausgaben auf der Konsole für weitere Angaben." +msgstr "" +"Markierungen konnten nicht angefordert werden. Kontrollieren Sie die " +"Ausgaben auf der Konsole für weitere Angaben." #: lib/choose_repository.tcl:882 msgid "Cannot determine HEAD. See console output for details." -msgstr "Die Zweigspitze (HEAD) konnte nicht gefunden werden. Kontrollieren Sie die Ausgaben auf der Konsole für weitere Angaben." +msgstr "" +"Die Zweigspitze (HEAD) konnte nicht gefunden werden. Kontrollieren Sie die " +"Ausgaben auf der Konsole für weitere Angaben." #: lib/choose_repository.tcl:891 #, tcl-format @@ -1273,11 +1285,40 @@ msgstr "" "\n" "- Rest: Eine ausführliche Beschreibung, warum diese Änderung hilfreich ist.\n" -#: lib/commit.tcl:257 +#: lib/commit.tcl:207 +#, tcl-format +msgid "warning: Tcl does not support encoding '%s'." +msgstr "Warning: Tcl/Tk unterstützt die Zeichencodierung »%s« nicht." + +#: lib/commit.tcl:221 +msgid "Calling pre-commit hook..." +msgstr "" + +#: lib/commit.tcl:236 +msgid "Commit declined by pre-commit hook." +msgstr "" + +#: lib/commit.tcl:259 +msgid "Calling commit-msg hook..." +msgstr "" + +#: lib/commit.tcl:274 +msgid "Commit declined by commit-msg hook." +msgstr "" + +#: lib/commit.tcl:287 +msgid "Committing changes..." +msgstr "Änderungen eintragen..." + +#: lib/commit.tcl:303 msgid "write-tree failed:" msgstr "write-tree fehlgeschlagen:" -#: lib/commit.tcl:275 +#: lib/commit.tcl:304 lib/commit.tcl:348 lib/commit.tcl:368 +msgid "Commit failed." +msgstr "Eintragen fehlgeschlagen." + +#: lib/commit.tcl:321 #, tcl-format msgid "Commit %s appears to be corrupt" msgstr "Version »%s« scheint beschädigt zu sein" @@ -1301,12 +1342,7 @@ msgstr "" msgid "No changes to commit." msgstr "Keine Änderungen, die eingetragen werden können." -#: lib/commit.tcl:303 -#, tcl-format -msgid "warning: Tcl does not support encoding '%s'." -msgstr "Warning: Tcl/Tk unterstützt die Zeichencodierung »%s« nicht." - -#: lib/commit.tcl:317 +#: lib/commit.tcl:347 msgid "commit-tree failed:" msgstr "commit-tree fehlgeschlagen:" @@ -1440,7 +1476,8 @@ msgstr "Fehler beim Laden des Vergleichs:" #: lib/diff.tcl:302 msgid "Failed to unstage selected hunk." -msgstr "Fehler beim Herausnehmen des gewählten Kontexts aus der Bereitstellung." +msgstr "" +"Fehler beim Herausnehmen des gewählten Kontexts aus der Bereitstellung." #: lib/diff.tcl:309 msgid "Failed to stage selected hunk." @@ -1471,7 +1508,10 @@ msgstr "Fehler in Bereitstellung" msgid "" "Updating the Git index failed. A rescan will be automatically started to " "resynchronize git-gui." -msgstr "Das Aktualisieren der Git-Bereitstellung ist fehlgeschlagen. Eine allgemeine Git-Aktualisierung wird jetzt gestartet, um git-gui wieder mit git zu synchronisieren." +msgstr "" +"Das Aktualisieren der Git-Bereitstellung ist fehlgeschlagen. Eine allgemeine " +"Git-Aktualisierung wird jetzt gestartet, um git-gui wieder mit git zu " +"synchronisieren." #: lib/index.tcl:27 msgid "Continue" @@ -1486,6 +1526,10 @@ msgstr "Bereitstellung freigeben" msgid "Unstaging %s from commit" msgstr "Datei »%s« aus der Bereitstellung herausnehmen" +#: lib/index.tcl:313 +msgid "Ready to commit." +msgstr "Bereit zum Eintragen." + #: lib/index.tcl:326 #, tcl-format msgid "Adding %s" @@ -1503,7 +1547,8 @@ msgstr "Änderungen in den gewählten %i Dateien verwerfen?" #: lib/index.tcl:389 msgid "Any unstaged changes will be permanently lost by the revert." -msgstr "Alle nicht bereitgestellten Änderungen werden beim Verwerfen verloren gehen." +msgstr "" +"Alle nicht bereitgestellten Änderungen werden beim Verwerfen verloren gehen." #: lib/index.tcl:392 msgid "Do Nothing" @@ -1641,7 +1686,11 @@ msgstr "" msgid "Aborting" msgstr "Abbruch" -#: lib/merge.tcl:266 +#: lib/merge.tcl:238 +msgid "files reset" +msgstr "Dateien zurückgesetzt" + +#: lib/merge.tcl:265 msgid "Abort failed." msgstr "Abbruch fehlgeschlagen." diff --git a/git-gui/po/git-gui.pot b/git-gui/po/git-gui.pot index dfa48ae263..3f139da6c7 100644 --- a/git-gui/po/git-gui.pot +++ b/git-gui/po/git-gui.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2007-11-24 10:36+0100\n" +"POT-Creation-Date: 2008-02-02 10:14+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" @@ -386,31 +386,31 @@ msgstr "" msgid "File:" msgstr "" -#: git-gui.sh:2545 -msgid "Refresh" +#: git-gui.sh:2573 +msgid "Apply/Reverse Hunk" msgstr "" -#: git-gui.sh:2566 -msgid "Apply/Reverse Hunk" +#: git-gui.sh:2579 +msgid "Show Less Context" msgstr "" -#: git-gui.sh:2572 -msgid "Decrease Font Size" +#: git-gui.sh:2586 +msgid "Show More Context" msgstr "" -#: git-gui.sh:2576 -msgid "Increase Font Size" +#: git-gui.sh:2594 +msgid "Refresh" msgstr "" -#: git-gui.sh:2581 -msgid "Show Less Context" +#: git-gui.sh:2615 +msgid "Decrease Font Size" msgstr "" -#: git-gui.sh:2588 -msgid "Show More Context" +#: git-gui.sh:2619 +msgid "Increase Font Size" msgstr "" -#: git-gui.sh:2602 +#: git-gui.sh:2630 msgid "Unstage Hunk From Commit" msgstr "" @@ -766,6 +766,10 @@ msgstr "" msgid "Updating working directory to '%s'..." msgstr "" +#: lib/checkout_op.tcl:323 +msgid "files checked out" +msgstr "" + #: lib/checkout_op.tcl:353 #, tcl-format msgid "Aborted checkout of '%s' (file level merging is required)." @@ -1182,11 +1186,40 @@ msgid "" "- Remaining lines: Describe why this change is good.\n" msgstr "" -#: lib/commit.tcl:257 +#: lib/commit.tcl:207 +#, tcl-format +msgid "warning: Tcl does not support encoding '%s'." +msgstr "" + +#: lib/commit.tcl:221 +msgid "Calling pre-commit hook..." +msgstr "" + +#: lib/commit.tcl:236 +msgid "Commit declined by pre-commit hook." +msgstr "" + +#: lib/commit.tcl:259 +msgid "Calling commit-msg hook..." +msgstr "" + +#: lib/commit.tcl:274 +msgid "Commit declined by commit-msg hook." +msgstr "" + +#: lib/commit.tcl:287 +msgid "Committing changes..." +msgstr "" + +#: lib/commit.tcl:303 msgid "write-tree failed:" msgstr "" -#: lib/commit.tcl:275 +#: lib/commit.tcl:304 lib/commit.tcl:348 lib/commit.tcl:368 +msgid "Commit failed." +msgstr "" + +#: lib/commit.tcl:321 #, tcl-format msgid "Commit %s appears to be corrupt" msgstr "" @@ -1204,12 +1237,7 @@ msgstr "" msgid "No changes to commit." msgstr "" -#: lib/commit.tcl:303 -#, tcl-format -msgid "warning: Tcl does not support encoding '%s'." -msgstr "" - -#: lib/commit.tcl:317 +#: lib/commit.tcl:347 msgid "commit-tree failed:" msgstr "" @@ -1373,6 +1401,10 @@ msgstr "" msgid "Unstaging %s from commit" msgstr "" +#: lib/index.tcl:313 +msgid "Ready to commit." +msgstr "" + #: lib/index.tcl:326 #, tcl-format msgid "Adding %s" @@ -1491,7 +1523,11 @@ msgstr "" msgid "Aborting" msgstr "" -#: lib/merge.tcl:266 +#: lib/merge.tcl:238 +msgid "files reset" +msgstr "" + +#: lib/merge.tcl:265 msgid "Abort failed." msgstr "" diff --git a/git-instaweb.sh b/git-instaweb.sh index 3e4452bc4b..6f91c8f845 100755 --- a/git-instaweb.sh +++ b/git-instaweb.sh @@ -24,8 +24,6 @@ restart restart the web server fqgitdir="$GIT_DIR" local="`git config --bool --get instaweb.local`" httpd="`git config --get instaweb.httpd`" -browser="`git config --get instaweb.browser`" -test -z "$browser" && browser="`git config --get web.browser`" port=`git config --get instaweb.port` module_path="`git config --get instaweb.modulepath`" @@ -36,9 +34,6 @@ conf="$GIT_DIR/gitweb/httpd.conf" # if installed, it doesn't need further configuration (module_path) test -z "$httpd" && httpd='lighttpd -f' -# probably the most popular browser among gitweb users -test -z "$browser" && browser='firefox' - # any untaken local port will do... test -z "$port" && port=1234 @@ -274,14 +269,11 @@ webrick) ;; esac -init_browser_path() { - browser_path="`git config browser.$1.path`" - test -z "$browser_path" && browser_path="$1" -} - start_httpd url=http://127.0.0.1:$port -test -n "$browser" && { - init_browser_path "$browser" - "$browser_path" $url -} || echo $url + +if test -n "$browser"; then + git web--browse -b "$browser" $url || echo $url +else + git web--browse -c "instaweb.browser" $url || echo $url +fi diff --git a/git-remote.perl b/git-remote.perl index d13e4c1fea..5cd69513cf 100755 --- a/git-remote.perl +++ b/git-remote.perl @@ -1,5 +1,6 @@ #!/usr/bin/perl -w +use strict; use Git; my $git = Git->repository(); @@ -296,12 +297,13 @@ sub add_remote { sub update_remote { my ($name) = @_; + my @remotes; my $conf = $git->config("remotes." . $name); if (defined($conf)) { @remotes = split(' ', $conf); } elsif ($name eq 'default') { - undef @remotes; + @remotes = (); for (sort keys %$remote) { my $do_fetch = $git->config_bool("remote." . $_ . ".skipDefaultUpdate"); @@ -341,7 +343,7 @@ sub rm_remote { my @refs = $git->command('for-each-ref', '--format=%(refname) %(objectname)', "refs/remotes/$name"); for (@refs) { - ($ref, $object) = split; + my ($ref, $object) = split; $git->command(qw(update-ref -d), $ref, $object); } return 0; @@ -352,7 +354,7 @@ sub add_usage { exit(1); } -local $VERBOSE = 0; +my $VERBOSE = 0; @ARGV = grep { if ($_ eq '-v' or $_ eq '--verbose') { $VERBOSE=1; @@ -395,7 +397,7 @@ elsif ($ARGV[0] eq 'update') { update_remote("default"); exit(1); } - for ($i = 1; $i < @ARGV; $i++) { + for (my $i = 1; $i < @ARGV; $i++) { update_remote($ARGV[$i]); } } diff --git a/git-send-email.perl b/git-send-email.perl index 8e6f3b22c8..29b1105c4c 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -24,8 +24,6 @@ use Data::Dumper; use Term::ANSIColor; use Git; -$SIG{INT} = sub { print color("reset"), "\n"; exit }; - package FakeTerm; sub new { my ($class, $reason) = @_; @@ -88,6 +86,12 @@ Options: --smtp-ssl If set, connects to the SMTP server using SSL. + --suppress-cc Suppress the specified category of auto-CC. The category + can be one of 'author' for the patch author, 'self' to + avoid copying yourself, 'sob' for Signed-off-by lines, + 'cccmd' for the output of the cccmd, or 'all' to suppress + all of these. + --suppress-from Suppress sending emails to yourself. Defaults to off. --thread Specify that the "In-Reply-To:" header should be set on all @@ -157,7 +161,7 @@ my $compose_filename = ".msg.$$"; # Variables we fill in automatically, or via prompting: my (@to,@cc,@initial_cc,@bcclist,@xh, - $initial_reply_to,$initial_subject,@files,$author,$sender,$compose,$time); + $initial_reply_to,$initial_subject,@files,$author,$sender,$smtp_authpass,$compose,$time); my $envelope_sender; @@ -166,7 +170,9 @@ my $envelope_sender; my $repo = Git->repository(); my $term = eval { - new Term::ReadLine 'git-send-email'; + $ENV{"GIT_SEND_EMAIL_NOTTY"} + ? new Term::ReadLine 'git-send-email', \*STDIN, \*STDOUT + : new Term::ReadLine 'git-send-email'; }; if ($@) { $term = new FakeTerm "$@: going non-interactive"; @@ -177,15 +183,16 @@ my ($quiet, $dry_run) = (0, 0); # Variables with corresponding config settings my ($thread, $chain_reply_to, $suppress_from, $signed_off_cc, $cc_cmd); -my ($smtp_server, $smtp_server_port, $smtp_authuser, $smtp_authpass, $smtp_ssl); +my ($smtp_server, $smtp_server_port, $smtp_authuser, $smtp_ssl); my ($identity, $aliasfiletype, @alias_files, @smtp_host_parts); my ($no_validate); +my (@suppress_cc); my %config_bool_settings = ( "thread" => [\$thread, 1], "chainreplyto" => [\$chain_reply_to, 1], - "suppressfrom" => [\$suppress_from, 0], - "signedoffcc" => [\$signed_off_cc, 1], + "suppressfrom" => [\$suppress_from, undef], + "signedoffcc" => [\$signed_off_cc, undef], "smtpssl" => [\$smtp_ssl, 0], ); @@ -199,8 +206,32 @@ my %config_settings = ( "aliasfiletype" => \$aliasfiletype, "bcc" => \@bcclist, "aliasesfile" => \@alias_files, + "suppresscc" => \@suppress_cc, ); +# Handle Uncouth Termination +sub signal_handler { + + # Make text normal + print color("reset"), "\n"; + + # SMTP password masked + system "stty echo"; + + # tmp files from --compose + if (-e $compose_filename) { + print "'$compose_filename' contains an intermediate version of the email you were composing.\n"; + } + if (-e ($compose_filename . ".final")) { + print "'$compose_filename.final' contains the composed email.\n" + } + + exit; +}; + +$SIG{TERM} = \&signal_handler; +$SIG{INT} = \&signal_handler; + # Begin by accumulating all the variables (defined above), that we will end up # needing, first, from the command line: @@ -214,13 +245,14 @@ my $rc = GetOptions("sender|from=s" => \$sender, "smtp-server=s" => \$smtp_server, "smtp-server-port=s" => \$smtp_server_port, "smtp-user=s" => \$smtp_authuser, - "smtp-pass=s" => \$smtp_authpass, + "smtp-pass:s" => \$smtp_authpass, "smtp-ssl!" => \$smtp_ssl, "identity=s" => \$identity, "compose" => \$compose, "quiet" => \$quiet, "cc-cmd=s" => \$cc_cmd, "suppress-from!" => \$suppress_from, + "suppress-cc=s" => \@suppress_cc, "signed-off-cc|signed-off-by-cc!" => \$signed_off_cc, "dry-run" => \$dry_run, "envelope-sender=s" => \$envelope_sender, @@ -266,6 +298,35 @@ foreach my $setting (values %config_bool_settings) { ${$setting->[0]} = $setting->[1] unless (defined (${$setting->[0]})); } +# Set CC suppressions +my(%suppress_cc); +if (@suppress_cc) { + foreach my $entry (@suppress_cc) { + die "Unknown --suppress-cc field: '$entry'\n" + unless $entry =~ /^(all|cccmd|cc|author|self|sob)$/; + $suppress_cc{$entry} = 1; + } +} + +if ($suppress_cc{'all'}) { + foreach my $entry (qw (ccmd cc author self sob)) { + $suppress_cc{$entry} = 1; + } + delete $suppress_cc{'all'}; +} + +# If explicit old-style ones are specified, they trump --suppress-cc. +$suppress_cc{'self'} = $suppress_from if defined $suppress_from; +$suppress_cc{'sob'} = $signed_off_cc if defined $signed_off_cc; + +# Debugging, print out the suppressions. +if (0) { + print "suppressions:\n"; + foreach my $entry (keys %suppress_cc) { + printf " %-5s -> $suppress_cc{$entry}\n", $entry; + } +} + my ($repoauthor) = $repo->ident_person('author'); my ($repocommitter) = $repo->ident_person('committer'); @@ -355,9 +416,12 @@ if (@files) { my $prompting = 0; if (!defined $sender) { $sender = $repoauthor || $repocommitter; - do { + + while (1) { $_ = $term->readline("Who should the emails appear to be from? [$sender] "); - } while (!defined $_); + last if defined $_; + print "\n"; + } $sender = $_ if ($_); print "Emails will be sent from: ", $sender, "\n"; @@ -365,10 +429,14 @@ if (!defined $sender) { } if (!@to) { - do { - $_ = $term->readline("Who should the emails be sent to? ", - ""); - } while (!defined $_); + + + while (1) { + $_ = $term->readline("Who should the emails be sent to? ", ""); + last if defined $_; + print "\n"; + } + my $to = $_; push @to, split /,/, $to; $prompting++; @@ -390,25 +458,29 @@ sub expand_aliases { @bcclist = expand_aliases(@bcclist); if (!defined $initial_subject && $compose) { - do { - $_ = $term->readline("What subject should the initial email start with? ", - $initial_subject); - } while (!defined $_); + while (1) { + $_ = $term->readline("What subject should the initial email start with? ", $initial_subject); + last if defined $_; + print "\n"; + } + $initial_subject = $_; $prompting++; } if ($thread && !defined $initial_reply_to && $prompting) { - do { - $_= $term->readline("Message-ID to be used as In-Reply-To for the first email? ", - $initial_reply_to); - } while (!defined $_); + while (1) { + $_= $term->readline("Message-ID to be used as In-Reply-To for the first email? ", $initial_reply_to); + last if defined $_; + print "\n"; + } $initial_reply_to = $_; } if (defined $initial_reply_to) { - $initial_reply_to =~ s/^\s*<?/</; - $initial_reply_to =~ s/>?\s*$/>/; + $initial_reply_to =~ s/^\s*<?//; + $initial_reply_to =~ s/>?\s*$//; + $initial_reply_to = "<$initial_reply_to>" if $initial_reply_to ne ''; } if (!defined $smtp_server) { @@ -453,9 +525,11 @@ EOT close(C); close(C2); - do { + while (1) { $_ = $term->readline("Send this email? (y|n) "); - } while (!defined $_); + last if defined $_; + print "\n"; + } if (uc substr($_,0,1) ne 'Y') { cleanup_compose_files(); @@ -647,9 +721,26 @@ X-Mailer: git-send-email $gitversion die "Unable to initialize SMTP properly. Is there something wrong with your config?"; } - if ((defined $smtp_authuser) && (defined $smtp_authpass)) { + if (defined $smtp_authuser) { + + if (!defined $smtp_authpass) { + + system "stty -echo"; + + do { + print "Password: "; + $_ = <STDIN>; + print "\n"; + } while (!defined $_); + + chomp($smtp_authpass = $_); + + system "stty echo"; + } + $auth ||= $smtp->auth( $smtp_authuser, $smtp_authpass ) or die $smtp->message; } + $smtp->mail( $raw_from ) or die $smtp->message; $smtp->to( @recipients ) or die $smtp->message; $smtp->data or die $smtp->message; @@ -711,11 +802,14 @@ foreach my $t (@files) { } elsif (/^(Cc|From):\s+(.*)$/) { if (unquote_rfc2047($2) eq $sender) { - next if ($suppress_from); + next if ($suppress_cc{'self'}); } elsif ($1 eq 'From') { ($author, $author_encoding) = unquote_rfc2047($2); + next if ($suppress_cc{'author'}); + } else { + next if ($suppress_cc{'cc'}); } printf("(mbox) Adding cc: %s from line '%s'\n", $2, $_) unless $quiet; @@ -742,7 +836,7 @@ foreach my $t (@files) { # line 2 = subject # So let's support that, too. $input_format = 'lots'; - if (@cc == 0) { + if (@cc == 0 && !$suppress_cc{'cc'}) { printf("(non-mbox) Adding cc: %s from line '%s'\n", $_, $_) unless $quiet; @@ -759,10 +853,11 @@ foreach my $t (@files) { } } else { $message .= $_; - if (/^(Signed-off-by|Cc): (.*)$/i && $signed_off_cc) { + if (/^(Signed-off-by|Cc): (.*)$/i) { + next if ($suppress_cc{'sob'}); my $c = $2; chomp $c; - next if ($c eq $sender and $suppress_from); + next if ($c eq $sender and $suppress_cc{'self'}); push @cc, $c; printf("(sob) Adding cc: %s from line '%s'\n", $c, $_) unless $quiet; @@ -771,7 +866,7 @@ foreach my $t (@files) { } close F; - if (defined $cc_cmd) { + if (defined $cc_cmd && !$suppress_cc{'cccmd'}) { open(F, "$cc_cmd $t |") or die "(cc-cmd) Could not execute '$cc_cmd'"; while(<F>) { diff --git a/git-sh-setup.sh b/git-sh-setup.sh index f38827529f..a44b1c74a3 100755 --- a/git-sh-setup.sh +++ b/git-sh-setup.sh @@ -127,20 +127,14 @@ get_author_ident_from_commit () { # if we require to be in a git repository. if test -z "$NONGIT_OK" then + GIT_DIR=$(git rev-parse --git-dir) || exit if [ -z "$SUBDIRECTORY_OK" ] then - : ${GIT_DIR=.git} test -z "$(git rev-parse --show-cdup)" || { exit=$? echo >&2 "You need to run this command from the toplevel of the working tree." exit $exit } - else - GIT_DIR=$(git rev-parse --git-dir) || { - exit=$? - echo >&2 "Failed to find a valid git directory." - exit $exit - } fi test -n "$GIT_DIR" && GIT_DIR=$(cd "$GIT_DIR" && pwd) || { echo >&2 "Unable to determine absolute path of git directory" diff --git a/git-svn.perl b/git-svn.perl index 75e97cc72f..05fb3582d9 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -186,6 +186,9 @@ my %cmd = ( "Show info about the latest SVN revision on the current branch", { 'url' => \$_url, } ], + 'blame' => [ \&Git::SVN::Log::cmd_blame, + "Show what revision and author last modified each line of a file", + {} ], ); my $cmd; @@ -1247,7 +1250,8 @@ use File::Path qw/mkpath/; use File::Copy qw/copy/; use IPC::Open3; -my $_repack_nr; +my ($_gc_nr, $_gc_period); + # properties that we do not log: my %SKIP_PROP; BEGIN { @@ -1408,9 +1412,10 @@ sub read_all_remotes { } sub init_vars { - $_repack = 1000 unless (defined $_repack && $_repack > 0); - $_repack_nr = $_repack; - $_repack_flags ||= '-d'; + $_gc_nr = $_gc_period = 1000; + if (defined $_repack || defined $_repack_flags) { + warn "Repack options are obsolete; they have no effect.\n"; + } } sub verify_remotes_sanity { @@ -2096,6 +2101,10 @@ sub restore_commit_header_env { } } +sub gc { + command_noisy('gc', '--auto'); +}; + sub do_git_commit { my ($self, $log_entry) = @_; my $lr = $self->last_rev; @@ -2149,12 +2158,9 @@ sub do_git_commit { 0, $self->svm_uuid); } print " = $commit ($self->{ref_id})\n"; - if ($_repack && (--$_repack_nr == 0)) { - $_repack_nr = $_repack; - # repack doesn't use any arguments with spaces in them, does it? - print "Running git repack $_repack_flags ...\n"; - command_noisy('repack', split(/\s+/, $_repack_flags)); - print "Done repacking\n"; + if (--$_gc_nr == 0) { + $_gc_nr = $_gc_period; + gc(); } return $commit; } @@ -2226,7 +2232,12 @@ sub find_parent_branch { # just grow a tail if we're not unique enough :x $ref_id .= '-' while find_ref($ref_id); print STDERR "Initializing parent: $ref_id\n"; - $gs = Git::SVN->init($new_url, '', $ref_id, $ref_id, 1); + my ($u, $p) = ($new_url, ''); + if ($u =~ s#^\Q$url\E(/|$)##) { + $p = $u; + $u = $url; + } + $gs = Git::SVN->init($u, $p, $self->{repo_id}, $ref_id, 1); } my ($r0, $parent) = $gs->find_rev_before($r, 1); if (!defined $r0 || !defined $parent) { @@ -3983,6 +3994,7 @@ sub gs_fetch_loop_common { $max += $inc; $max = $head if ($max > $head); } + Git::SVN::gc(); } sub match_globs { @@ -4439,6 +4451,24 @@ out: print commit_log_separator unless $incremental || $oneline; } +sub cmd_blame { + my $path = shift; + + config_pager(); + run_pager(); + + my ($fh, $ctx) = command_output_pipe('blame', @_, $path); + while (my $line = <$fh>) { + if ($line =~ /^\^?([[:xdigit:]]+)\s/) { + my (undef, $rev, undef) = ::cmt_metadata($1); + $rev = sprintf('%-10s', $rev); + $line =~ s/^\^?[[:xdigit:]]+(\s)/$rev$1/; + } + print $line; + } + command_close_pipe($fh, $ctx); +} + package Git::SVN::Migration; # these version numbers do NOT correspond to actual version numbers # of git nor git-svn. They are just relative. diff --git a/git-help--browse.sh b/git-web--browse.sh index 10b0a36a3d..1023b90859 100755 --- a/git-help--browse.sh +++ b/git-web--browse.sh @@ -16,21 +16,16 @@ # git maintainer. # -USAGE='[--browser=browser|--tool=browser] [cmd to display] ...' +USAGE='[--browser=browser|--tool=browser] [--config=conf.var] url/file ...' # This must be capable of running outside of git directory, so # the vanilla git-sh-setup should not be used. NONGIT_OK=Yes . git-sh-setup -# Install data. -html_dir="@@HTMLDIR@@" - -test -f "$html_dir/git.html" || die "No documentation directory found." - valid_tool() { case "$1" in - firefox | iceweasel | konqueror | w3m | links | lynx | dillo) + firefox | iceweasel | konqueror | w3m | links | lynx | dillo | open) ;; # happy *) return 1 @@ -39,8 +34,8 @@ valid_tool() { } init_browser_path() { - browser_path=`git config browser.$1.path` - test -z "$browser_path" && browser_path=$1 + browser_path=$(git config "browser.$1.path") + test -z "$browser_path" && browser_path="$1" } while test $# != 0 @@ -58,6 +53,18 @@ do shift ;; esac ;; + -c|--config*) + case "$#,$1" in + *,*=*) + conf=`expr "z$1" : 'z-[^=]*=\(.*\)'` + ;; + 1,*) + usage ;; + *) + conf="$2" + shift ;; + esac + ;; --) break ;; @@ -71,17 +78,20 @@ do shift done +test $# = 0 && usage + if test -z "$browser" then - for opt in "help.browser" "web.browser" + for opt in "$conf" "web.browser" do + test -z "$opt" && continue browser="`git config $opt`" test -z "$browser" || break done if test -n "$browser" && ! valid_tool "$browser"; then - echo >&2 "git config option $opt set to unknown browser: $browser" - echo >&2 "Resetting to default..." - unset browser + echo >&2 "git config option $opt set to unknown browser: $browser" + echo >&2 "Resetting to default..." + unset browser fi fi @@ -94,6 +104,10 @@ if test -z "$browser" ; then else browser_candidates="w3m links lynx" fi + # SECURITYSESSIONID indicates an OS X GUI login session + if test -n "$SECURITYSESSIONID"; then + browser_candidates="open $browser_candidates" + fi for i in $browser_candidates; do init_browser_path $i @@ -113,16 +127,13 @@ else fi fi -pages=$(for p in "$@"; do echo "$html_dir/$p.html" ; done) -test -z "$pages" && pages="$html_dir/git.html" - case "$browser" in firefox|iceweasel) # Check version because firefox < 2.0 does not support "-new-tab". vers=$(expr "$($browser_path -version)" : '.* \([0-9][0-9]*\)\..*') NEWTAB='-new-tab' test "$vers" -lt 2 && NEWTAB='' - nohup "$browser_path" $NEWTAB $pages & + "$browser_path" $NEWTAB "$@" & ;; konqueror) case "$(basename "$browser_path")" in @@ -130,20 +141,20 @@ case "$browser" in # It's simpler to use kfmclient to open a new tab in konqueror. browser_path="$(echo "$browser_path" | sed -e 's/konqueror$/kfmclient/')" type "$browser_path" > /dev/null 2>&1 || die "No '$browser_path' found." - eval "$browser_path" newTab $pages + eval "$browser_path" newTab "$@" ;; kfmclient) - eval "$browser_path" newTab $pages + eval "$browser_path" newTab "$@" ;; *) - nohup "$browser_path" $pages & + "$browser_path" "$@" & ;; esac ;; - w3m|links|lynx) - eval "$browser_path" $pages + w3m|links|lynx|open) + eval "$browser_path" "$@" ;; dillo) - nohup "$browser_path" $pages & + "$browser_path" "$@" & ;; esac diff --git a/gitk-git/gitk b/gitk-git/gitk index 5560e4dc56..f1f21e97bf 100644 --- a/gitk-git/gitk +++ b/gitk-git/gitk @@ -240,11 +240,12 @@ proc getcommitlines {fd view} { set listed 1 if {$j >= 0 && [string match "commit *" $cmit]} { set ids [string range $cmit 7 [expr {$j - 1}]] - if {[string match {[-<>]*} $ids]} { + if {[string match {[-^<>]*} $ids]} { switch -- [string index $ids 0] { "-" {set listed 0} - "<" {set listed 2} - ">" {set listed 3} + "^" {set listed 2} + "<" {set listed 3} + ">" {set listed 4} } set ids [string range $ids 1 end] } @@ -632,6 +633,7 @@ proc makewindow {} { global findtype findtypemenu findloc findstring fstring geometry global entries sha1entry sha1string sha1but global diffcontextstring diffcontext + global ignorespace global maincursor textcursor curtextcursor global rowctxmenu fakerowmenu mergemax wrapcomment global highlight_files gdttype @@ -849,6 +851,9 @@ proc makewindow {} { trace add variable diffcontextstring write diffcontextchange lappend entries .bleft.mid.diffcontext pack .bleft.mid.labeldiffcontext .bleft.mid.diffcontext -side left + checkbutton .bleft.mid.ignspace -text [mc "Ignore space change"] \ + -command changeignorespace -variable ignorespace + pack .bleft.mid.ignspace -side left -padx 5 set ctext .bleft.ctext text $ctext -background $bgcolor -foreground $fgcolor \ -state disabled -font textfont \ @@ -1307,45 +1312,45 @@ proc keys {} { } toplevel $w wm title $w [mc "Gitk key bindings"] - message $w.m -text [mc " -Gitk key bindings: - -<$M1T-Q> Quit -<Home> Move to first commit -<End> Move to last commit -<Up>, p, i Move up one commit -<Down>, n, k Move down one commit -<Left>, z, j Go back in history list -<Right>, x, l Go forward in history list -<PageUp> Move up one page in commit list -<PageDown> Move down one page in commit list -<$M1T-Home> Scroll to top of commit list -<$M1T-End> Scroll to bottom of commit list -<$M1T-Up> Scroll commit list up one line -<$M1T-Down> Scroll commit list down one line -<$M1T-PageUp> Scroll commit list up one page -<$M1T-PageDown> Scroll commit list down one page -<Shift-Up> Find backwards (upwards, later commits) -<Shift-Down> Find forwards (downwards, earlier commits) -<Delete>, b Scroll diff view up one page -<Backspace> Scroll diff view up one page -<Space> Scroll diff view down one page -u Scroll diff view up 18 lines -d Scroll diff view down 18 lines -<$M1T-F> Find -<$M1T-G> Move to next find hit -<Return> Move to next find hit -/ Move to next find hit, or redo find -? Move to previous find hit -f Scroll diff view to next file -<$M1T-S> Search for next hit in diff view -<$M1T-R> Search for previous hit in diff view -<$M1T-KP+> Increase font size -<$M1T-plus> Increase font size -<$M1T-KP-> Decrease font size -<$M1T-minus> Decrease font size -<F5> Update -"] \ + message $w.m -text " +[mc "Gitk key bindings:"] + +[mc "<%s-Q> Quit" $M1T] +[mc "<Home> Move to first commit"] +[mc "<End> Move to last commit"] +[mc "<Up>, p, i Move up one commit"] +[mc "<Down>, n, k Move down one commit"] +[mc "<Left>, z, j Go back in history list"] +[mc "<Right>, x, l Go forward in history list"] +[mc "<PageUp> Move up one page in commit list"] +[mc "<PageDown> Move down one page in commit list"] +[mc "<%s-Home> Scroll to top of commit list" $M1T] +[mc "<%s-End> Scroll to bottom of commit list" $M1T] +[mc "<%s-Up> Scroll commit list up one line" $M1T] +[mc "<%s-Down> Scroll commit list down one line" $M1T] +[mc "<%s-PageUp> Scroll commit list up one page" $M1T] +[mc "<%s-PageDown> Scroll commit list down one page" $M1T] +[mc "<Shift-Up> Find backwards (upwards, later commits)"] +[mc "<Shift-Down> Find forwards (downwards, earlier commits)"] +[mc "<Delete>, b Scroll diff view up one page"] +[mc "<Backspace> Scroll diff view up one page"] +[mc "<Space> Scroll diff view down one page"] +[mc "u Scroll diff view up 18 lines"] +[mc "d Scroll diff view down 18 lines"] +[mc "<%s-F> Find" $M1T] +[mc "<%s-G> Move to next find hit" $M1T] +[mc "<Return> Move to next find hit"] +[mc "/ Move to next find hit, or redo find"] +[mc "? Move to previous find hit"] +[mc "f Scroll diff view to next file"] +[mc "<%s-S> Search for next hit in diff view" $M1T] +[mc "<%s-R> Search for previous hit in diff view" $M1T] +[mc "<%s-KP+> Increase font size" $M1T] +[mc "<%s-plus> Increase font size" $M1T] +[mc "<%s-KP-> Decrease font size" $M1T] +[mc "<%s-minus> Decrease font size" $M1T] +[mc "<F5> Update"] +" \ -justify left -bg white -border 2 -relief groove pack $w.m -side top -fill both -padx 2 -pady 2 button $w.ok -text [mc "Close"] -command "destroy $w" -default active @@ -3627,23 +3632,23 @@ proc drawcmittext {id row col} { global linehtag linentag linedtag selectedline global canvxmax boldrows boldnamerows fgcolor nullid nullid2 - # listed is 0 for boundary, 1 for normal, 2 for left, 3 for right + # listed is 0 for boundary, 1 for normal, 2 for negative, 3 for left, 4 for right set listed [lindex $commitlisted $row] if {$id eq $nullid} { set ofill red } elseif {$id eq $nullid2} { set ofill green } else { - set ofill [expr {$listed != 0? "blue": "white"}] + set ofill [expr {$listed != 0 ? $listed == 2 ? "gray" : "blue" : "white"}] } set x [xc $row $col] set y [yc $row] set orad [expr {$linespc / 3}] - if {$listed <= 1} { + if {$listed <= 2} { set t [$canv create oval [expr {$x - $orad}] [expr {$y - $orad}] \ [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \ -fill $ofill -outline $fgcolor -width 1 -tags circle] - } elseif {$listed == 2} { + } elseif {$listed == 3} { # triangle pointing left for left-side commits set t [$canv create polygon \ [expr {$x - $orad}] $y \ @@ -5027,13 +5032,14 @@ proc getblobline {bf id} { proc mergediff {id l} { global diffmergeid mdifffd global diffids + global diffcontext global parentlist global limitdiffs viewfiles curview set diffmergeid $id set diffids $id # this doesn't seem to actually affect anything... - set cmd [concat | git diff-tree --no-commit-id --cc $id] + set cmd [concat | git diff-tree --no-commit-id --cc -U$diffcontext $id] if {$limitdiffs && $viewfiles($curview) ne {}} { set cmd [concat $cmd -- $viewfiles($curview)] } @@ -5270,13 +5276,21 @@ proc diffcontextchange {n1 n2 op} { } } +proc changeignorespace {} { + reselectline +} + proc getblobdiffs {ids} { global blobdifffd diffids env global diffinhdr treediffs global diffcontext + global ignorespace global limitdiffs viewfiles curview set cmd [diffcmd $ids "-p -C --no-commit-id -U$diffcontext"] + if {$ignorespace} { + append cmd " -w" + } if {$limitdiffs && $viewfiles($curview) ne {}} { set cmd [concat $cmd -- $viewfiles($curview)] } @@ -6137,11 +6151,7 @@ proc domktag {} { return } if {[catch { - set dir [gitdir] - set fname [file join $dir "refs/tags" $tag] - set f [open $fname w] - puts $f $id - close $f + exec git tag $tag $id } err]} { error_popup "[mc "Error creating tag:"] $err" return @@ -8459,6 +8469,7 @@ set bgcolor white set fgcolor black set diffcolors {red "#00a000" blue} set diffcontext 3 +set ignorespace 0 set selectbgcolor gray85 ## For msgcat loading, first locate the installation location. diff --git a/gitweb/README b/gitweb/README index 4c8bedf744..2163071047 100644 --- a/gitweb/README +++ b/gitweb/README @@ -233,6 +233,10 @@ You can use the following files in repository: Displayed in the project summary page. You can use multiple-valued gitweb.url repository configuration variable for that, but the file takes precendence. + * gitweb.owner + You can use the gitweb.owner repository configuration variable to set + repository's owner. It is displayed in the project list and summary + page. If it's not set, filesystem directory's owner is used. * various gitweb.* config variables (in config) Read description of %feature hash for detailed list, and some descriptions. diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index ae2d05763f..326e27cf88 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -611,6 +611,8 @@ sub href(%) { ); my %mapping = @mapping; + $params{'project'} = $project unless exists $params{'project'}; + if ($params{-replay}) { while (my ($name, $symbol) = each %mapping) { if (!exists $params{$name}) { @@ -620,8 +622,6 @@ sub href(%) { } } - $params{'project'} = $project unless exists $params{'project'}; - my ($use_pathinfo) = gitweb_check_feature('pathinfo'); if ($use_pathinfo) { # use PATH_INFO for project name @@ -753,29 +753,40 @@ sub esc_path { # Make control characters "printable", using character escape codes (CEC) sub quot_cec { my $cntrl = shift; + my %opts = @_; my %es = ( # character escape codes, aka escape sequences - "\t" => '\t', # tab (HT) - "\n" => '\n', # line feed (LF) - "\r" => '\r', # carrige return (CR) - "\f" => '\f', # form feed (FF) - "\b" => '\b', # backspace (BS) - "\a" => '\a', # alarm (bell) (BEL) - "\e" => '\e', # escape (ESC) - "\013" => '\v', # vertical tab (VT) - "\000" => '\0', # nul character (NUL) - ); + "\t" => '\t', # tab (HT) + "\n" => '\n', # line feed (LF) + "\r" => '\r', # carrige return (CR) + "\f" => '\f', # form feed (FF) + "\b" => '\b', # backspace (BS) + "\a" => '\a', # alarm (bell) (BEL) + "\e" => '\e', # escape (ESC) + "\013" => '\v', # vertical tab (VT) + "\000" => '\0', # nul character (NUL) + ); my $chr = ( (exists $es{$cntrl}) ? $es{$cntrl} : sprintf('\%03o', ord($cntrl)) ); - return "<span class=\"cntrl\">$chr</span>"; + if ($opts{-nohtml}) { + return $chr; + } else { + return "<span class=\"cntrl\">$chr</span>"; + } } # Alternatively use unicode control pictures codepoints, # Unicode "printable representation" (PR) sub quot_upr { my $cntrl = shift; + my %opts = @_; + my $chr = sprintf('&#%04d;', 0x2400+ord($cntrl)); - return "<span class=\"cntrl\">$chr</span>"; + if ($opts{-nohtml}) { + return $chr; + } else { + return "<span class=\"cntrl\">$chr</span>"; + } } # git may return quoted and escaped filenames @@ -800,7 +811,7 @@ sub unquote { return chr(oct($seq)); } elsif (exists $es{$seq}) { # C escape sequence, aka character escape code - return $es{$seq} + return $es{$seq}; } # quoted ordinary character return $seq; @@ -866,8 +877,8 @@ sub chop_and_escape_str { if ($chopped eq $str) { return esc_html($chopped); } else { - return qq{<span title="} . esc_html($str) . qq{">} . - esc_html($chopped) . qq{</span>}; + $str =~ s/([[:cntrl:]])/?/g; + return $cgi->span({-title=>$str}, esc_html($chopped)); } } @@ -1620,7 +1631,7 @@ sub git_get_project_url_list { my $path = shift; $git_dir = "$projectroot/$path"; - open my $fd, "$projectroot/$path/cloneurl" + open my $fd, "$git_dir/cloneurl" or return wantarray ? @{ config_to_multi(git_get_project_config('url')) } : config_to_multi(git_get_project_config('url')); @@ -1759,6 +1770,7 @@ sub git_get_project_owner { my $owner; return undef unless $project; + $git_dir = "$projectroot/$project"; if (!defined $gitweb_project_owner) { git_get_project_list_from_file(); @@ -1767,8 +1779,11 @@ sub git_get_project_owner { if (exists $gitweb_project_owner->{$project}) { $owner = $gitweb_project_owner->{$project}; } + if (!defined $owner){ + $owner = git_get_project_config('owner'); + } if (!defined $owner) { - $owner = get_file_owner("$projectroot/$project"); + $owner = get_file_owner("$git_dir"); } return $owner; @@ -3769,18 +3784,24 @@ sub git_search_grep_body { print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" . "<td><i>" . $author . "</i></td>\n" . "<td>" . - $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}), -class => "list subject"}, - chop_and_escape_str($co{'title'}, 50) . "<br/>"); + $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}), + -class => "list subject"}, + chop_and_escape_str($co{'title'}, 50) . "<br/>"); my $comment = $co{'comment'}; foreach my $line (@$comment) { if ($line =~ m/^(.*)($search_regexp)(.*)$/i) { - my $lead = esc_html($1) || ""; - $lead = chop_str($lead, 30, 10); - my $match = esc_html($2) || ""; - my $trail = esc_html($3) || ""; - $trail = chop_str($trail, 30, 10); - my $text = "$lead<span class=\"match\">$match</span>$trail"; - print chop_str($text, 80, 5) . "<br/>\n"; + my ($lead, $match, $trail) = ($1, $2, $3); + $match = chop_str($match, 70, 5); # in case match is very long + my $contextlen = (80 - len($match))/2; # is left for the remainder + $contextlen = 30 if ($contextlen > 30); # but not too much + $lead = chop_str($lead, $contextlen, 10); + $trail = chop_str($trail, $contextlen, 10); + + $lead = esc_html($lead); + $match = esc_html($match); + $trail = esc_html($trail); + + print "$lead<span class=\"match\">$match</span>$trail<br />"; } } print "</td>\n" . @@ -5565,7 +5586,7 @@ XML or next; # print element (entry, item) - my $co_url = href(-full=>1, action=>"commit", hash=>$commit); + my $co_url = href(-full=>1, action=>"commitdiff", hash=>$commit); if ($format eq 'rss') { print "<item>\n" . "<title>" . esc_html($co{'title'}) . "</title>\n" . @@ -39,12 +39,8 @@ static void parse_help_format(const char *format) static int git_help_config(const char *var, const char *value) { - if (!strcmp(var, "help.format")) { - if (!value) - return config_error_nonbool(var); - help_default_format = xstrdup(value); - return 0; - } + if (!strcmp(var, "help.format")) + return git_config_string(&help_default_format, var, value); return git_default_config(var, value); } @@ -330,10 +326,26 @@ static void show_info_page(const char *git_cmd) execlp("info", "info", "gitman", page, NULL); } +static void get_html_page_path(struct strbuf *page_path, const char *page) +{ + struct stat st; + + /* Check that we have a git documentation directory. */ + if (stat(GIT_HTML_PATH "/git.html", &st) || !S_ISREG(st.st_mode)) + die("'%s': not a documentation directory.", GIT_HTML_PATH); + + strbuf_init(page_path, 0); + strbuf_addf(page_path, GIT_HTML_PATH "/%s.html", page); +} + static void show_html_page(const char *git_cmd) { const char *page = cmd_to_page(git_cmd); - execl_git_cmd("help--browse", page, NULL); + struct strbuf page_path; /* it leaks but we exec bellow */ + + get_html_page_path(&page_path, page); + + execl_git_cmd("web--browse", "-c", "help.browser", page_path.buf, NULL); } void help_unknown_cmd(const char *cmd) diff --git a/http-push.c b/http-push.c index b2b410df90..0beb7406c3 100644 --- a/http-push.c +++ b/http-push.c @@ -1634,12 +1634,19 @@ static struct object_list **process_tree(struct tree *tree, init_tree_desc(&desc, tree->buffer, tree->size); - while (tree_entry(&desc, &entry)) { - if (S_ISDIR(entry.mode)) + while (tree_entry(&desc, &entry)) + switch (object_type(entry.mode)) { + case OBJ_TREE: p = process_tree(lookup_tree(entry.sha1), p, &me, name); - else + break; + case OBJ_BLOB: p = process_blob(lookup_blob(entry.sha1), p, &me, name); - } + break; + default: + /* Subproject commit - not in this repository */ + break; + } + free(tree->buffer); tree->buffer = NULL; return p; @@ -2383,7 +2390,8 @@ int main(int argc, char **argv) /* Generate a list of objects that need to be pushed */ pushing = 0; - prepare_revision_walk(&revs); + if (prepare_revision_walk(&revs)) + die("revision walk setup failed"); mark_edges_uninteresting(revs.commits); objects_to_send = get_delta(&revs, ref_lock); finish_all_active_slots(); @@ -2398,15 +2406,17 @@ int main(int argc, char **argv) fill_active_slots(); add_fill_function(NULL, fill_active_slot); #endif - finish_all_active_slots(); + do { + finish_all_active_slots(); +#ifdef USE_CURL_MULTI + fill_active_slots(); +#endif + } while (request_queue_head && !aborted); /* Update the remote branch if all went well */ - if (aborted || !update_remote(ref->new_sha1, ref_lock)) { + if (aborted || !update_remote(ref->new_sha1, ref_lock)) rc = 1; - goto unlock; - } - unlock: if (!rc) fprintf(stderr, " done\n"); unlock_remote(ref_lock); diff --git a/list-objects.c b/list-objects.c index 4ef58e7ec0..c8b8375e49 100644 --- a/list-objects.c +++ b/list-objects.c @@ -18,6 +18,8 @@ static void process_blob(struct rev_info *revs, if (!revs->blob_objects) return; + if (!obj) + die("bad blob object"); if (obj->flags & (UNINTERESTING | SEEN)) return; obj->flags |= SEEN; @@ -69,6 +71,8 @@ static void process_tree(struct rev_info *revs, if (!revs->tree_objects) return; + if (!obj) + die("bad tree object"); if (obj->flags & (UNINTERESTING | SEEN)) return; if (parse_tree(tree) < 0) diff --git a/log-tree.c b/log-tree.c index 1f3fcf16ad..e9ba6df9d2 100644 --- a/log-tree.c +++ b/log-tree.c @@ -149,10 +149,12 @@ void show_log(struct rev_info *opt, const char *sep) opt->loginfo = NULL; if (!opt->verbose_header) { - if (opt->left_right) { - if (commit->object.flags & BOUNDARY) - putchar('-'); - else if (commit->object.flags & SYMMETRIC_LEFT) + if (commit->object.flags & BOUNDARY) + putchar('-'); + else if (commit->object.flags & UNINTERESTING) + putchar('^'); + else if (opt->left_right) { + if (commit->object.flags & SYMMETRIC_LEFT) putchar('<'); else putchar('>'); @@ -250,6 +252,8 @@ void show_log(struct rev_info *opt, const char *sep) fputs("commit ", stdout); if (commit->object.flags & BOUNDARY) putchar('-'); + else if (commit->object.flags & UNINTERESTING) + putchar('^'); else if (opt->left_right) { if (commit->object.flags & SYMMETRIC_LEFT) putchar('<'); @@ -278,6 +282,9 @@ void show_log(struct rev_info *opt, const char *sep) } } + if (!commit->buffer) + return; + /* * And then the pretty-printed message itself */ diff --git a/merge-index.c b/merge-index.c index fa719cb0b1..7491c56ad2 100644 --- a/merge-index.c +++ b/merge-index.c @@ -48,7 +48,7 @@ static int merge_entry(int pos, const char *path) break; found++; strcpy(hexbuf[stage], sha1_to_hex(ce->sha1)); - sprintf(ownbuf[stage], "%o", ntohl(ce->ce_mode)); + sprintf(ownbuf[stage], "%o", ce->ce_mode); arguments[stage] = hexbuf[stage]; arguments[stage + 4] = ownbuf[stage]; } while (++pos < active_nr); @@ -91,7 +91,7 @@ int main(int argc, char **argv) signal(SIGCHLD, SIG_DFL); if (argc < 3) - usage("git-merge-index [-o] [-q] <merge-program> (-a | <filename>*)"); + usage("git-merge-index [-o] [-q] <merge-program> (-a | [--] <filename>*)"); setup_git_directory(); read_cache(); diff --git a/merge-recursive.c b/merge-recursive.c index 34e3167caf..55ef76f5a5 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -333,7 +333,7 @@ static struct path_list *get_unmerged(void) item->util = xcalloc(1, sizeof(struct stage_data)); } e = item->util; - e->stages[ce_stage(ce)].mode = ntohl(ce->ce_mode); + e->stages[ce_stage(ce)].mode = ce->ce_mode; hashcpy(e->stages[ce_stage(ce)].sha, ce->sha1); } @@ -1673,6 +1673,8 @@ static struct commit *get_ref(const char *ref) if (get_sha1(ref, sha1)) die("Could not resolve ref '%s'", ref); object = deref_tag(parse_object(sha1), ref, strlen(ref)); + if (!object) + return NULL; if (object->type == OBJ_TREE) return make_virtual_commit((struct tree*)object, better_branch_name(ref)); @@ -140,7 +140,8 @@ struct object *parse_object_buffer(const unsigned char *sha1, enum object_type t if (type == OBJ_BLOB) { struct blob *blob = lookup_blob(sha1); if (blob) { - parse_blob_buffer(blob, buffer, size); + if (parse_blob_buffer(blob, buffer, size)) + return NULL; obj = &blob->object; } } else if (type == OBJ_TREE) { @@ -148,14 +149,16 @@ struct object *parse_object_buffer(const unsigned char *sha1, enum object_type t if (tree) { obj = &tree->object; if (!tree->object.parsed) { - parse_tree_buffer(tree, buffer, size); + if (parse_tree_buffer(tree, buffer, size)) + return NULL; eaten = 1; } } } else if (type == OBJ_COMMIT) { struct commit *commit = lookup_commit(sha1); if (commit) { - parse_commit_buffer(commit, buffer, size); + if (parse_commit_buffer(commit, buffer, size)) + return NULL; if (!commit->buffer) { commit->buffer = buffer; eaten = 1; @@ -165,7 +168,8 @@ struct object *parse_object_buffer(const unsigned char *sha1, enum object_type t } else if (type == OBJ_TAG) { struct tag *tag = lookup_tag(sha1); if (tag) { - parse_tag_buffer(tag, buffer, size); + if (parse_tag_buffer(tag, buffer, size)) + return NULL; obj = &tag->object; } } else { @@ -57,6 +57,7 @@ void setup_pager(void) /* return in the child */ if (!pid) { dup2(fd[1], 1); + dup2(fd[1], 2); close(fd[0]); close(fd[1]); return; @@ -282,59 +282,59 @@ static char *logmsg_reencode(const struct commit *commit, return out; } -static void format_person_part(struct strbuf *sb, char part, +static size_t format_person_part(struct strbuf *sb, char part, const char *msg, int len) { + /* currently all placeholders have same length */ + const int placeholder_len = 2; int start, end, tz = 0; - unsigned long date; + unsigned long date = 0; char *ep; - /* parse name */ + /* advance 'end' to point to email start delimiter */ for (end = 0; end < len && msg[end] != '<'; end++) ; /* do nothing */ + /* - * If it does not even have a '<' and '>', that is - * quite a bogus commit author and we discard it; - * this is in line with add_user_info() that is used - * in the normal codepath. When end points at the '<' - * that we found, it should have matching '>' later, - * which means start (beginning of email address) must - * be strictly below len. + * When end points at the '<' that we found, it should have + * matching '>' later, which means 'end' must be strictly + * below len - 1. */ - start = end + 1; - if (start >= len - 1) - return; - while (end > 0 && isspace(msg[end - 1])) - end--; + if (end >= len - 2) + goto skip; + if (part == 'n') { /* name */ + while (end > 0 && isspace(msg[end - 1])) + end--; strbuf_add(sb, msg, end); - return; + return placeholder_len; } + start = ++end; /* save email start position */ - /* parse email */ - for (end = start; end < len && msg[end] != '>'; end++) + /* advance 'end' to point to email end delimiter */ + for ( ; end < len && msg[end] != '>'; end++) ; /* do nothing */ if (end >= len) - return; + goto skip; if (part == 'e') { /* email */ strbuf_add(sb, msg + start, end - start); - return; + return placeholder_len; } - /* parse date */ + /* advance 'start' to point to date start delimiter */ for (start = end + 1; start < len && isspace(msg[start]); start++) ; /* do nothing */ if (start >= len) - return; + goto skip; date = strtoul(msg + start, &ep, 10); if (msg + start == ep) - return; + goto skip; if (part == 't') { /* date, UNIX timestamp */ strbuf_add(sb, msg + start, ep - (msg + start)); - return; + return placeholder_len; } /* parse tz */ @@ -349,17 +349,28 @@ static void format_person_part(struct strbuf *sb, char part, switch (part) { case 'd': /* date */ strbuf_addstr(sb, show_date(date, tz, DATE_NORMAL)); - return; + return placeholder_len; case 'D': /* date, RFC2822 style */ strbuf_addstr(sb, show_date(date, tz, DATE_RFC2822)); - return; + return placeholder_len; case 'r': /* date, relative */ strbuf_addstr(sb, show_date(date, tz, DATE_RELATIVE)); - return; + return placeholder_len; case 'i': /* date, ISO 8601 */ strbuf_addstr(sb, show_date(date, tz, DATE_ISO8601)); - return; + return placeholder_len; } + +skip: + /* + * bogus commit, 'sb' cannot be updated, but we still need to + * compute a valid return value. + */ + if (part == 'n' || part == 'e' || part == 't' || part == 'd' + || part == 'D' || part == 'r' || part == 'i') + return placeholder_len; + + return 0; /* unknown placeholder */ } struct chunk { @@ -440,7 +451,7 @@ static void parse_commit_header(struct format_commit_context *context) context->commit_header_parsed = 1; } -static void format_commit_item(struct strbuf *sb, const char *placeholder, +static size_t format_commit_item(struct strbuf *sb, const char *placeholder, void *context) { struct format_commit_context *c = context; @@ -451,23 +462,23 @@ static void format_commit_item(struct strbuf *sb, const char *placeholder, /* these are independent of the commit */ switch (placeholder[0]) { case 'C': - switch (placeholder[3]) { - case 'd': /* red */ + if (!prefixcmp(placeholder + 1, "red")) { strbuf_addstr(sb, "\033[31m"); - return; - case 'e': /* green */ + return 4; + } else if (!prefixcmp(placeholder + 1, "green")) { strbuf_addstr(sb, "\033[32m"); - return; - case 'u': /* blue */ + return 6; + } else if (!prefixcmp(placeholder + 1, "blue")) { strbuf_addstr(sb, "\033[34m"); - return; - case 's': /* reset color */ + return 5; + } else if (!prefixcmp(placeholder + 1, "reset")) { strbuf_addstr(sb, "\033[m"); - return; - } + return 6; + } else + return 0; case 'n': /* newline */ strbuf_addch(sb, '\n'); - return; + return 1; } /* these depend on the commit */ @@ -477,34 +488,34 @@ static void format_commit_item(struct strbuf *sb, const char *placeholder, switch (placeholder[0]) { case 'H': /* commit hash */ strbuf_addstr(sb, sha1_to_hex(commit->object.sha1)); - return; + return 1; case 'h': /* abbreviated commit hash */ if (add_again(sb, &c->abbrev_commit_hash)) - return; + return 1; strbuf_addstr(sb, find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV)); c->abbrev_commit_hash.len = sb->len - c->abbrev_commit_hash.off; - return; + return 1; case 'T': /* tree hash */ strbuf_addstr(sb, sha1_to_hex(commit->tree->object.sha1)); - return; + return 1; case 't': /* abbreviated tree hash */ if (add_again(sb, &c->abbrev_tree_hash)) - return; + return 1; strbuf_addstr(sb, find_unique_abbrev(commit->tree->object.sha1, DEFAULT_ABBREV)); c->abbrev_tree_hash.len = sb->len - c->abbrev_tree_hash.off; - return; + return 1; case 'P': /* parent hashes */ for (p = commit->parents; p; p = p->next) { if (p != commit->parents) strbuf_addch(sb, ' '); strbuf_addstr(sb, sha1_to_hex(p->item->object.sha1)); } - return; + return 1; case 'p': /* abbreviated parent hashes */ if (add_again(sb, &c->abbrev_parent_hashes)) - return; + return 1; for (p = commit->parents; p; p = p->next) { if (p != commit->parents) strbuf_addch(sb, ' '); @@ -513,14 +524,14 @@ static void format_commit_item(struct strbuf *sb, const char *placeholder, } c->abbrev_parent_hashes.len = sb->len - c->abbrev_parent_hashes.off; - return; + return 1; case 'm': /* left/right/bottom */ strbuf_addch(sb, (commit->object.flags & BOUNDARY) ? '-' : (commit->object.flags & SYMMETRIC_LEFT) ? '<' : '>'); - return; + return 1; } /* For the rest we have to parse the commit header. */ @@ -528,66 +539,33 @@ static void format_commit_item(struct strbuf *sb, const char *placeholder, parse_commit_header(c); switch (placeholder[0]) { - case 's': + case 's': /* subject */ strbuf_add(sb, msg + c->subject.off, c->subject.len); - return; - case 'a': - format_person_part(sb, placeholder[1], + return 1; + case 'a': /* author ... */ + return format_person_part(sb, placeholder[1], msg + c->author.off, c->author.len); - return; - case 'c': - format_person_part(sb, placeholder[1], + case 'c': /* committer ... */ + return format_person_part(sb, placeholder[1], msg + c->committer.off, c->committer.len); - return; - case 'e': + case 'e': /* encoding */ strbuf_add(sb, msg + c->encoding.off, c->encoding.len); - return; - case 'b': + return 1; + case 'b': /* body */ strbuf_addstr(sb, msg + c->body_off); - return; + return 1; } + return 0; /* unknown placeholder */ } void format_commit_message(const struct commit *commit, const void *format, struct strbuf *sb) { - const char *placeholders[] = { - "H", /* commit hash */ - "h", /* abbreviated commit hash */ - "T", /* tree hash */ - "t", /* abbreviated tree hash */ - "P", /* parent hashes */ - "p", /* abbreviated parent hashes */ - "an", /* author name */ - "ae", /* author email */ - "ad", /* author date */ - "aD", /* author date, RFC2822 style */ - "ar", /* author date, relative */ - "at", /* author date, UNIX timestamp */ - "ai", /* author date, ISO 8601 */ - "cn", /* committer name */ - "ce", /* committer email */ - "cd", /* committer date */ - "cD", /* committer date, RFC2822 style */ - "cr", /* committer date, relative */ - "ct", /* committer date, UNIX timestamp */ - "ci", /* committer date, ISO 8601 */ - "e", /* encoding */ - "s", /* subject */ - "b", /* body */ - "Cred", /* red */ - "Cgreen", /* green */ - "Cblue", /* blue */ - "Creset", /* reset color */ - "n", /* newline */ - "m", /* left/right/bottom */ - NULL - }; struct format_commit_context context; memset(&context, 0, sizeof(context)); context.commit = commit; - strbuf_expand(sb, format, placeholders, format_commit_item, &context); + strbuf_expand(sb, format, format_commit_item, &context); } static void pp_header(enum cmit_fmt fmt, diff --git a/reachable.c b/reachable.c index 6383401e2d..3b1c18ff9b 100644 --- a/reachable.c +++ b/reachable.c @@ -15,6 +15,8 @@ static void process_blob(struct blob *blob, { struct object *obj = &blob->object; + if (!blob) + die("bad blob object"); if (obj->flags & SEEN) return; obj->flags |= SEEN; @@ -39,6 +41,8 @@ static void process_tree(struct tree *tree, struct name_entry entry; struct name_path me; + if (!tree) + die("bad tree object"); if (obj->flags & SEEN) return; obj->flags |= SEEN; @@ -79,7 +83,8 @@ static void process_tag(struct tag *tag, struct object_array *p, const char *nam if (parse_tag(tag) < 0) die("bad tag object %s", sha1_to_hex(obj->sha1)); - add_object(tag->tagged, p, NULL, name); + if (tag->tagged) + add_object(tag->tagged, p, NULL, name); } static void walk_commit_list(struct rev_info *revs) @@ -150,7 +155,8 @@ static int add_one_reflog(const char *path, const unsigned char *sha1, int flag, static void add_one_tree(const unsigned char *sha1, struct rev_info *revs) { struct tree *tree = lookup_tree(sha1); - add_pending_object(revs, &tree->object, ""); + if (tree) + add_pending_object(revs, &tree->object, ""); } static void add_cache_tree(struct cache_tree *it, struct rev_info *revs) @@ -176,7 +182,7 @@ static void add_cache_refs(struct rev_info *revs) * lookup_blob() on them, to avoid populating the hash table * with invalid information */ - if (S_ISGITLINK(ntohl(active_cache[i]->ce_mode))) + if (S_ISGITLINK(active_cache[i]->ce_mode)) continue; lookup_blob(active_cache[i]->sha1); @@ -215,6 +221,7 @@ void mark_reachable_objects(struct rev_info *revs, int mark_reflog) * Set up the revision walk - this will move all commits * from the pending list to the commit walking list. */ - prepare_revision_walk(revs); + if (prepare_revision_walk(revs)) + die("revision walk setup failed"); walk_commit_list(revs); } diff --git a/read-cache.c b/read-cache.c index 7db55883d6..e45f4b3d61 100644 --- a/read-cache.c +++ b/read-cache.c @@ -23,6 +23,90 @@ struct index_state the_index; +static unsigned int hash_name(const char *name, int namelen) +{ + unsigned int hash = 0x123; + + do { + unsigned char c = *name++; + hash = hash*101 + c; + } while (--namelen); + return hash; +} + +static void hash_index_entry(struct index_state *istate, struct cache_entry *ce) +{ + void **pos; + unsigned int hash = hash_name(ce->name, ce_namelen(ce)); + + pos = insert_hash(hash, ce, &istate->name_hash); + if (pos) { + ce->next = *pos; + *pos = ce; + } +} + +static void lazy_init_name_hash(struct index_state *istate) +{ + int nr; + + if (istate->name_hash_initialized) + return; + for (nr = 0; nr < istate->cache_nr; nr++) + hash_index_entry(istate, istate->cache[nr]); + istate->name_hash_initialized = 1; +} + +static void set_index_entry(struct index_state *istate, int nr, struct cache_entry *ce) +{ + istate->cache[nr] = ce; + if (istate->name_hash_initialized) + hash_index_entry(istate, ce); +} + +/* + * We don't actually *remove* it, we can just mark it invalid so that + * we won't find it in lookups. + * + * Not only would we have to search the lists (simple enough), but + * we'd also have to rehash other hash buckets in case this makes the + * hash bucket empty (common). So it's much better to just mark + * it. + */ +static void remove_hash_entry(struct index_state *istate, struct cache_entry *ce) +{ + ce->ce_flags |= CE_UNHASHED; +} + +static void replace_index_entry(struct index_state *istate, int nr, struct cache_entry *ce) +{ + struct cache_entry *old = istate->cache[nr]; + + if (ce != old) { + remove_hash_entry(istate, old); + set_index_entry(istate, nr, ce); + } + istate->cache_changed = 1; +} + +int index_name_exists(struct index_state *istate, const char *name, int namelen) +{ + unsigned int hash = hash_name(name, namelen); + struct cache_entry *ce; + + lazy_init_name_hash(istate); + ce = lookup_hash(hash, &istate->name_hash); + + while (ce) { + if (!(ce->ce_flags & CE_UNHASHED)) { + if (!cache_name_compare(name, namelen, ce->name, ce->ce_flags)) + return 1; + } + ce = ce->next; + } + return 0; +} + /* * This only updates the "non-critical" parts of the directory * cache, ie the parts that aren't tracked by GIT, and only used @@ -30,20 +114,19 @@ struct index_state the_index; */ void fill_stat_cache_info(struct cache_entry *ce, struct stat *st) { - ce->ce_ctime.sec = htonl(st->st_ctime); - ce->ce_mtime.sec = htonl(st->st_mtime); -#ifdef USE_NSEC - ce->ce_ctime.nsec = htonl(st->st_ctim.tv_nsec); - ce->ce_mtime.nsec = htonl(st->st_mtim.tv_nsec); -#endif - ce->ce_dev = htonl(st->st_dev); - ce->ce_ino = htonl(st->st_ino); - ce->ce_uid = htonl(st->st_uid); - ce->ce_gid = htonl(st->st_gid); - ce->ce_size = htonl(st->st_size); + ce->ce_ctime = st->st_ctime; + ce->ce_mtime = st->st_mtime; + ce->ce_dev = st->st_dev; + ce->ce_ino = st->st_ino; + ce->ce_uid = st->st_uid; + ce->ce_gid = st->st_gid; + ce->ce_size = st->st_size; if (assume_unchanged) - ce->ce_flags |= htons(CE_VALID); + ce->ce_flags |= CE_VALID; + + if (S_ISREG(st->st_mode)) + ce_mark_uptodate(ce); } static int ce_compare_data(struct cache_entry *ce, struct stat *st) @@ -116,7 +199,7 @@ static int ce_modified_check_fs(struct cache_entry *ce, struct stat *st) return DATA_CHANGED; break; case S_IFDIR: - if (S_ISGITLINK(ntohl(ce->ce_mode))) + if (S_ISGITLINK(ce->ce_mode)) return 0; default: return TYPE_CHANGED; @@ -128,14 +211,17 @@ static int ce_match_stat_basic(struct cache_entry *ce, struct stat *st) { unsigned int changed = 0; - switch (ntohl(ce->ce_mode) & S_IFMT) { + if (ce->ce_flags & CE_REMOVE) + return MODE_CHANGED | DATA_CHANGED | TYPE_CHANGED; + + switch (ce->ce_mode & S_IFMT) { case S_IFREG: changed |= !S_ISREG(st->st_mode) ? TYPE_CHANGED : 0; /* We consider only the owner x bit to be relevant for * "mode changes" */ if (trust_executable_bit && - (0100 & (ntohl(ce->ce_mode) ^ st->st_mode))) + (0100 & (ce->ce_mode ^ st->st_mode))) changed |= MODE_CHANGED; break; case S_IFLNK: @@ -149,32 +235,18 @@ static int ce_match_stat_basic(struct cache_entry *ce, struct stat *st) else if (ce_compare_gitlink(ce)) changed |= DATA_CHANGED; return changed; - case 0: /* Special case: unmerged file in index */ - return MODE_CHANGED | DATA_CHANGED | TYPE_CHANGED; default: - die("internal error: ce_mode is %o", ntohl(ce->ce_mode)); + die("internal error: ce_mode is %o", ce->ce_mode); } - if (ce->ce_mtime.sec != htonl(st->st_mtime)) + if (ce->ce_mtime != (unsigned int) st->st_mtime) changed |= MTIME_CHANGED; - if (ce->ce_ctime.sec != htonl(st->st_ctime)) + if (ce->ce_ctime != (unsigned int) st->st_ctime) changed |= CTIME_CHANGED; -#ifdef USE_NSEC - /* - * nsec seems unreliable - not all filesystems support it, so - * as long as it is in the inode cache you get right nsec - * but after it gets flushed, you get zero nsec. - */ - if (ce->ce_mtime.nsec != htonl(st->st_mtim.tv_nsec)) - changed |= MTIME_CHANGED; - if (ce->ce_ctime.nsec != htonl(st->st_ctim.tv_nsec)) - changed |= CTIME_CHANGED; -#endif - - if (ce->ce_uid != htonl(st->st_uid) || - ce->ce_gid != htonl(st->st_gid)) + if (ce->ce_uid != (unsigned int) st->st_uid || + ce->ce_gid != (unsigned int) st->st_gid) changed |= OWNER_CHANGED; - if (ce->ce_ino != htonl(st->st_ino)) + if (ce->ce_ino != (unsigned int) st->st_ino) changed |= INODE_CHANGED; #ifdef USE_STDEV @@ -183,16 +255,22 @@ static int ce_match_stat_basic(struct cache_entry *ce, struct stat *st) * clients will have different views of what "device" * the filesystem is on */ - if (ce->ce_dev != htonl(st->st_dev)) + if (ce->ce_dev != (unsigned int) st->st_dev) changed |= INODE_CHANGED; #endif - if (ce->ce_size != htonl(st->st_size)) + if (ce->ce_size != (unsigned int) st->st_size) changed |= DATA_CHANGED; return changed; } +static int is_racy_timestamp(struct index_state *istate, struct cache_entry *ce) +{ + return (istate->timestamp && + ((unsigned int)istate->timestamp) <= ce->ce_mtime); +} + int ie_match_stat(struct index_state *istate, struct cache_entry *ce, struct stat *st, unsigned int options) @@ -205,7 +283,7 @@ int ie_match_stat(struct index_state *istate, * If it's marked as always valid in the index, it's * valid whatever the checked-out copy says. */ - if (!ignore_valid && (ce->ce_flags & htons(CE_VALID))) + if (!ignore_valid && (ce->ce_flags & CE_VALID)) return 0; changed = ce_match_stat_basic(ce, st); @@ -226,9 +304,7 @@ int ie_match_stat(struct index_state *istate, * whose mtime are the same as the index file timestamp more * carefully than others. */ - if (!changed && - istate->timestamp && - istate->timestamp <= ntohl(ce->ce_mtime.sec)) { + if (!changed && is_racy_timestamp(istate, ce)) { if (assume_racy_is_modified) changed |= DATA_CHANGED; else @@ -257,7 +333,7 @@ int ie_modified(struct index_state *istate, * the length field is zero. For other cases the ce_size * should match the SHA1 recorded in the index entry. */ - if ((changed & DATA_CHANGED) && ce->ce_size != htonl(0)) + if ((changed & DATA_CHANGED) && ce->ce_size != 0) return changed; changed_fs = ce_modified_check_fs(ce, st); @@ -320,7 +396,7 @@ int index_name_pos(struct index_state *istate, const char *name, int namelen) while (last > first) { int next = (last + first) >> 1; struct cache_entry *ce = istate->cache[next]; - int cmp = cache_name_compare(name, namelen, ce->name, ntohs(ce->ce_flags)); + int cmp = cache_name_compare(name, namelen, ce->name, ce->ce_flags); if (!cmp) return next; if (cmp < 0) { @@ -335,6 +411,9 @@ int index_name_pos(struct index_state *istate, const char *name, int namelen) /* Remove entry, return true if there are more entries to go.. */ int remove_index_entry_at(struct index_state *istate, int pos) { + struct cache_entry *ce = istate->cache[pos]; + + remove_hash_entry(istate, ce); istate->cache_changed = 1; istate->cache_nr--; if (pos >= istate->cache_nr) @@ -405,7 +484,7 @@ int add_file_to_index(struct index_state *istate, const char *path, int verbose) size = cache_entry_size(namelen); ce = xcalloc(1, size); memcpy(ce->name, path, namelen); - ce->ce_flags = htons(namelen); + ce->ce_flags = namelen; fill_stat_cache_info(ce, &st); if (trust_executable_bit && has_symlinks) @@ -427,6 +506,7 @@ int add_file_to_index(struct index_state *istate, const char *path, int verbose) !ie_match_stat(istate, istate->cache[pos], &st, ce_option)) { /* Nothing changed, really */ free(ce); + ce_mark_uptodate(istate->cache[pos]); return 0; } @@ -583,7 +663,7 @@ static int has_file_name(struct index_state *istate, continue; if (p->name[len] != '/') continue; - if (!ce_stage(p) && !p->ce_mode) + if (p->ce_flags & CE_REMOVE) continue; retval = -1; if (!ok_to_replace) @@ -616,7 +696,7 @@ static int has_dir_name(struct index_state *istate, } len = slash - name; - pos = index_name_pos(istate, name, ntohs(create_ce_flags(len, stage))); + pos = index_name_pos(istate, name, create_ce_flags(len, stage)); if (pos >= 0) { /* * Found one, but not so fast. This could @@ -626,7 +706,7 @@ static int has_dir_name(struct index_state *istate, * it is Ok to have a directory at the same * path. */ - if (stage || istate->cache[pos]->ce_mode) { + if (!(istate->cache[pos]->ce_flags & CE_REMOVE)) { retval = -1; if (!ok_to_replace) break; @@ -648,8 +728,9 @@ static int has_dir_name(struct index_state *istate, (p->name[len] != '/') || memcmp(p->name, name, len)) break; /* not our subdirectory */ - if (ce_stage(p) == stage && (stage || p->ce_mode)) - /* p is at the same stage as our entry, and + if (ce_stage(p) == stage && !(p->ce_flags & CE_REMOVE)) + /* + * p is at the same stage as our entry, and * is a subdirectory of what we are looking * at, so we cannot have conflicts at our * level or anything shorter. @@ -679,7 +760,7 @@ static int check_file_directory_conflict(struct index_state *istate, /* * When ce is an "I am going away" entry, we allow it to be added */ - if (!ce_stage(ce) && !ce->ce_mode) + if (ce->ce_flags & CE_REMOVE) return 0; /* @@ -704,12 +785,11 @@ static int add_index_entry_with_check(struct index_state *istate, struct cache_e int skip_df_check = option & ADD_CACHE_SKIP_DFCHECK; cache_tree_invalidate_path(istate->cache_tree, ce->name); - pos = index_name_pos(istate, ce->name, ntohs(ce->ce_flags)); + pos = index_name_pos(istate, ce->name, ce->ce_flags); /* existing match? Just replace it. */ if (pos >= 0) { - istate->cache_changed = 1; - istate->cache[pos] = ce; + replace_index_entry(istate, pos, ce); return 0; } pos = -pos-1; @@ -736,7 +816,7 @@ static int add_index_entry_with_check(struct index_state *istate, struct cache_e if (!ok_to_replace) return error("'%s' appears as both a file and as a directory", ce->name); - pos = index_name_pos(istate, ce->name, ntohs(ce->ce_flags)); + pos = index_name_pos(istate, ce->name, ce->ce_flags); pos = -pos-1; } return pos + 1; @@ -769,7 +849,7 @@ int add_index_entry(struct index_state *istate, struct cache_entry *ce, int opti memmove(istate->cache + pos + 1, istate->cache + pos, (istate->cache_nr - pos - 1) * sizeof(ce)); - istate->cache[pos] = ce; + set_index_entry(istate, pos, ce); istate->cache_changed = 1; return 0; } @@ -794,6 +874,9 @@ static struct cache_entry *refresh_cache_ent(struct index_state *istate, int changed, size; int ignore_valid = options & CE_MATCH_IGNORE_VALID; + if (ce_uptodate(ce)) + return ce; + if (lstat(ce->name, &st) < 0) { if (err) *err = errno; @@ -810,10 +893,17 @@ static struct cache_entry *refresh_cache_ent(struct index_state *istate, * valid again, under "assume unchanged" mode. */ if (ignore_valid && assume_unchanged && - !(ce->ce_flags & htons(CE_VALID))) + !(ce->ce_flags & CE_VALID)) ; /* mark this one VALID again */ - else + else { + /* + * We do not mark the index itself "modified" + * because CE_UPTODATE flag is in-core only; + * we are not going to write this change out. + */ + ce_mark_uptodate(ce); return ce; + } } if (ie_modified(istate, ce, &st, options)) { @@ -826,7 +916,6 @@ static struct cache_entry *refresh_cache_ent(struct index_state *istate, updated = xmalloc(size); memcpy(updated, ce, size); fill_stat_cache_info(updated, &st); - /* * If ignore_valid is not set, we should leave CE_VALID bit * alone. Otherwise, paths marked with --no-assume-unchanged @@ -834,8 +923,8 @@ static struct cache_entry *refresh_cache_ent(struct index_state *istate, * automatically, which is not really what we want. */ if (!ignore_valid && assume_unchanged && - !(ce->ce_flags & htons(CE_VALID))) - updated->ce_flags &= ~htons(CE_VALID); + !(ce->ce_flags & CE_VALID)) + updated->ce_flags &= ~CE_VALID; return updated; } @@ -880,7 +969,7 @@ int refresh_index(struct index_state *istate, unsigned int flags, const char **p /* If we are doing --really-refresh that * means the index is not valid anymore. */ - ce->ce_flags &= ~htons(CE_VALID); + ce->ce_flags &= ~CE_VALID; istate->cache_changed = 1; } if (quiet) @@ -889,11 +978,8 @@ int refresh_index(struct index_state *istate, unsigned int flags, const char **p has_errors = 1; continue; } - istate->cache_changed = 1; - /* You can NOT just free istate->cache[i] here, since it - * might not be necessarily malloc()ed but can also come - * from mmap(). */ - istate->cache[i] = new; + + replace_index_entry(istate, i, new); } return has_errors; } @@ -942,16 +1028,58 @@ int read_index(struct index_state *istate) return read_index_from(istate, get_index_file()); } +static void convert_from_disk(struct ondisk_cache_entry *ondisk, struct cache_entry *ce) +{ + size_t len; + + ce->ce_ctime = ntohl(ondisk->ctime.sec); + ce->ce_mtime = ntohl(ondisk->mtime.sec); + ce->ce_dev = ntohl(ondisk->dev); + ce->ce_ino = ntohl(ondisk->ino); + ce->ce_mode = ntohl(ondisk->mode); + ce->ce_uid = ntohl(ondisk->uid); + ce->ce_gid = ntohl(ondisk->gid); + ce->ce_size = ntohl(ondisk->size); + /* On-disk flags are just 16 bits */ + ce->ce_flags = ntohs(ondisk->flags); + hashcpy(ce->sha1, ondisk->sha1); + + len = ce->ce_flags & CE_NAMEMASK; + if (len == CE_NAMEMASK) + len = strlen(ondisk->name); + /* + * NEEDSWORK: If the original index is crafted, this copy could + * go unchecked. + */ + memcpy(ce->name, ondisk->name, len + 1); +} + +static inline size_t estimate_cache_size(size_t ondisk_size, unsigned int entries) +{ + long per_entry; + + per_entry = sizeof(struct cache_entry) - sizeof(struct ondisk_cache_entry); + + /* + * Alignment can cause differences. This should be "alignof", but + * since that's a gcc'ism, just use the size of a pointer. + */ + per_entry += sizeof(void *); + return ondisk_size + entries*per_entry; +} + /* remember to discard_cache() before reading a different cache! */ int read_index_from(struct index_state *istate, const char *path) { int fd, i; struct stat st; - unsigned long offset; + unsigned long src_offset, dst_offset; struct cache_header *hdr; + void *mmap; + size_t mmap_size; errno = EBUSY; - if (istate->mmap) + if (istate->alloc) return istate->cache_nr; errno = ENOENT; @@ -967,31 +1095,47 @@ int read_index_from(struct index_state *istate, const char *path) die("cannot stat the open index (%s)", strerror(errno)); errno = EINVAL; - istate->mmap_size = xsize_t(st.st_size); - if (istate->mmap_size < sizeof(struct cache_header) + 20) + mmap_size = xsize_t(st.st_size); + if (mmap_size < sizeof(struct cache_header) + 20) die("index file smaller than expected"); - istate->mmap = xmmap(NULL, istate->mmap_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0); + mmap = xmmap(NULL, mmap_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0); close(fd); + if (mmap == MAP_FAILED) + die("unable to map index file"); - hdr = istate->mmap; - if (verify_hdr(hdr, istate->mmap_size) < 0) + hdr = mmap; + if (verify_hdr(hdr, mmap_size) < 0) goto unmap; istate->cache_nr = ntohl(hdr->hdr_entries); istate->cache_alloc = alloc_nr(istate->cache_nr); istate->cache = xcalloc(istate->cache_alloc, sizeof(struct cache_entry *)); - offset = sizeof(*hdr); + /* + * The disk format is actually larger than the in-memory format, + * due to space for nsec etc, so even though the in-memory one + * has room for a few more flags, we can allocate using the same + * index size + */ + istate->alloc = xmalloc(estimate_cache_size(mmap_size, istate->cache_nr)); + + src_offset = sizeof(*hdr); + dst_offset = 0; for (i = 0; i < istate->cache_nr; i++) { + struct ondisk_cache_entry *disk_ce; struct cache_entry *ce; - ce = (struct cache_entry *)((char *)(istate->mmap) + offset); - offset = offset + ce_size(ce); - istate->cache[i] = ce; + disk_ce = (struct ondisk_cache_entry *)((char *)mmap + src_offset); + ce = (struct cache_entry *)((char *)istate->alloc + dst_offset); + convert_from_disk(disk_ce, ce); + set_index_entry(istate, i, ce); + + src_offset += ondisk_ce_size(ce); + dst_offset += ce_size(ce); } istate->timestamp = st.st_mtime; - while (offset <= istate->mmap_size - 20 - 8) { + while (src_offset <= mmap_size - 20 - 8) { /* After an array of active_nr index entries, * there can be arbitrary number of extended * sections, each of which is prefixed with @@ -999,40 +1143,37 @@ int read_index_from(struct index_state *istate, const char *path) * in 4-byte network byte order. */ unsigned long extsize; - memcpy(&extsize, (char *)(istate->mmap) + offset + 4, 4); + memcpy(&extsize, (char *)mmap + src_offset + 4, 4); extsize = ntohl(extsize); if (read_index_extension(istate, - ((const char *) (istate->mmap)) + offset, - (char *) (istate->mmap) + offset + 8, + (const char *) mmap + src_offset, + (char *) mmap + src_offset + 8, extsize) < 0) goto unmap; - offset += 8; - offset += extsize; + src_offset += 8; + src_offset += extsize; } + munmap(mmap, mmap_size); return istate->cache_nr; unmap: - munmap(istate->mmap, istate->mmap_size); + munmap(mmap, mmap_size); errno = EINVAL; die("index file corrupt"); } int discard_index(struct index_state *istate) { - int ret; - istate->cache_nr = 0; istate->cache_changed = 0; istate->timestamp = 0; + free_hash(&istate->name_hash); cache_tree_free(&(istate->cache_tree)); - if (istate->mmap == NULL) - return 0; - ret = munmap(istate->mmap, istate->mmap_size); - istate->mmap = NULL; - istate->mmap_size = 0; + free(istate->alloc); + istate->alloc = NULL; /* no need to throw away allocated active_cache */ - return ret; + return 0; } #define WRITE_BUFFER_SIZE 8192 @@ -1144,10 +1285,32 @@ static void ce_smudge_racily_clean_entry(struct cache_entry *ce) * file, and never calls us, so the cached size information * for "frotz" stays 6 which does not match the filesystem. */ - ce->ce_size = htonl(0); + ce->ce_size = 0; } } +static int ce_write_entry(SHA_CTX *c, int fd, struct cache_entry *ce) +{ + int size = ondisk_ce_size(ce); + struct ondisk_cache_entry *ondisk = xcalloc(1, size); + + ondisk->ctime.sec = htonl(ce->ce_ctime); + ondisk->ctime.nsec = 0; + ondisk->mtime.sec = htonl(ce->ce_mtime); + ondisk->mtime.nsec = 0; + ondisk->dev = htonl(ce->ce_dev); + ondisk->ino = htonl(ce->ce_ino); + ondisk->mode = htonl(ce->ce_mode); + ondisk->uid = htonl(ce->ce_uid); + ondisk->gid = htonl(ce->ce_gid); + ondisk->size = htonl(ce->ce_size); + hashcpy(ondisk->sha1, ce->sha1); + ondisk->flags = htons(ce->ce_flags); + memcpy(ondisk->name, ce->name, ce_namelen(ce)); + + return ce_write(c, fd, ondisk, size); +} + int write_index(struct index_state *istate, int newfd) { SHA_CTX c; @@ -1157,7 +1320,7 @@ int write_index(struct index_state *istate, int newfd) int entries = istate->cache_nr; for (i = removed = 0; i < entries; i++) - if (!cache[i]->ce_mode) + if (cache[i]->ce_flags & CE_REMOVE) removed++; hdr.hdr_signature = htonl(CACHE_SIGNATURE); @@ -1170,12 +1333,11 @@ int write_index(struct index_state *istate, int newfd) for (i = 0; i < entries; i++) { struct cache_entry *ce = cache[i]; - if (!ce->ce_mode) + if (ce->ce_flags & CE_REMOVE) continue; - if (istate->timestamp && - istate->timestamp <= ntohl(ce->ce_mtime.sec)) + if (is_racy_timestamp(istate, ce)) ce_smudge_racily_clean_entry(ce); - if (ce_write(&c, newfd, ce, ce_size(ce)) < 0) + if (ce_write_entry(&c, newfd, ce) < 0) return -1; } diff --git a/revision.c b/revision.c index 6e85aaa3fb..d3e8658104 100644 --- a/revision.c +++ b/revision.c @@ -46,6 +46,8 @@ void add_object(struct object *obj, static void mark_blob_uninteresting(struct blob *blob) { + if (!blob) + return; if (blob->object.flags & UNINTERESTING) return; blob->object.flags |= UNINTERESTING; @@ -57,6 +59,8 @@ void mark_tree_uninteresting(struct tree *tree) struct name_entry entry; struct object *obj = &tree->object; + if (!tree) + return; if (obj->flags & UNINTERESTING) return; obj->flags |= UNINTERESTING; @@ -173,6 +177,8 @@ static struct commit *handle_commit(struct rev_info *revs, struct object *object struct tag *tag = (struct tag *) object; if (revs->tag_objects && !(flags & UNINTERESTING)) add_pending_object(revs, object, tag->tag); + if (!tag->tagged) + die("bad tag"); object = parse_object(tag->tagged->sha1); if (!object) die("bad object %s", sha1_to_hex(tag->tagged->sha1)); @@ -558,6 +564,12 @@ static void cherry_pick_list(struct commit_list *list, struct rev_info *revs) free_patch_ids(&ids); } +static void add_to_list(struct commit_list **p, struct commit *commit, struct commit_list *n) +{ + p = &commit_list_insert(commit, p)->next; + *p = n; +} + static int limit_list(struct rev_info *revs) { struct commit_list *list = revs->commits; @@ -579,9 +591,13 @@ static int limit_list(struct rev_info *revs) return -1; if (obj->flags & UNINTERESTING) { mark_parents_uninteresting(commit); - if (everybody_uninteresting(list)) + if (everybody_uninteresting(list)) { + if (revs->show_all) + add_to_list(p, commit, list); break; - continue; + } + if (!revs->show_all) + continue; } if (revs->min_age != -1 && (commit->date > revs->min_age)) continue; @@ -685,6 +701,8 @@ static int add_parents_only(struct rev_info *revs, const char *arg, int flags) it = get_reference(revs, arg, sha1, 0); if (it->type != OBJ_TAG) break; + if (!((struct tag*)it)->tagged) + return 0; hashcpy(sha1, ((struct tag*)it)->tagged->sha1); } if (it->type != OBJ_COMMIT) @@ -1055,6 +1073,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch revs->dense = 0; continue; } + if (!strcmp(arg, "--show-all")) { + revs->show_all = 1; + continue; + } if (!strcmp(arg, "--remove-empty")) { revs->remove_empty_trees = 1; continue; @@ -1438,6 +1460,8 @@ enum commit_action simplify_commit(struct rev_info *revs, struct commit *commit) return commit_ignore; if (revs->unpacked && has_sha1_pack(commit->object.sha1, revs->ignore_packed)) return commit_ignore; + if (revs->show_all) + return commit_show; if (commit->object.flags & UNINTERESTING) return commit_ignore; if (revs->min_age != -1 && (commit->date > revs->min_age)) diff --git a/revision.h b/revision.h index 8572315954..b5f01f8309 100644 --- a/revision.h +++ b/revision.h @@ -33,6 +33,7 @@ struct rev_info { prune:1, no_merges:1, no_walk:1, + show_all:1, remove_empty_trees:1, simplify_history:1, lifo:1, @@ -4,51 +4,118 @@ static int inside_git_dir = -1; static int inside_work_tree = -1; -const char *prefix_path(const char *prefix, int len, const char *path) +static int sanitary_path_copy(char *dst, const char *src) { - const char *orig = path; + char *dst0 = dst; + + if (*src == '/') { + *dst++ = '/'; + while (*src == '/') + src++; + } + for (;;) { - char c; - if (*path != '.') - break; - c = path[1]; - /* "." */ - if (!c) { - path++; - break; + char c = *src; + + /* + * A path component that begins with . could be + * special: + * (1) "." and ends -- ignore and terminate. + * (2) "./" -- ignore them, eat slash and continue. + * (3) ".." and ends -- strip one and terminate. + * (4) "../" -- strip one, eat slash and continue. + */ + if (c == '.') { + switch (src[1]) { + case '\0': + /* (1) */ + src++; + break; + case '/': + /* (2) */ + src += 2; + while (*src == '/') + src++; + continue; + case '.': + switch (src[2]) { + case '\0': + /* (3) */ + src += 2; + goto up_one; + case '/': + /* (4) */ + src += 3; + while (*src == '/') + src++; + goto up_one; + } + } } - /* "./" */ + + /* copy up to the next '/', and eat all '/' */ + while ((c = *src++) != '\0' && c != '/') + *dst++ = c; if (c == '/') { - path += 2; - continue; - } - if (c != '.') + *dst++ = c; + while (c == '/') + c = *src++; + src--; + } else if (!c) break; - c = path[2]; - if (!c) - path += 2; - else if (c == '/') - path += 3; - else - break; - /* ".." and "../" */ - /* Remove last component of the prefix */ - do { - if (!len) - die("'%s' is outside repository", orig); - len--; - } while (len && prefix[len-1] != '/'); continue; + + up_one: + /* + * dst0..dst is prefix portion, and dst[-1] is '/'; + * go up one level. + */ + dst -= 2; /* go past trailing '/' if any */ + if (dst < dst0) + return -1; + while (1) { + if (dst <= dst0) + break; + c = *dst--; + if (c == '/') { + dst += 2; + break; + } + } } - if (len) { - int speclen = strlen(path); - char *n = xmalloc(speclen + len + 1); + *dst = '\0'; + return 0; +} - memcpy(n, prefix, len); - memcpy(n + len, path, speclen+1); - path = n; +const char *prefix_path(const char *prefix, int len, const char *path) +{ + const char *orig = path; + char *sanitized = xmalloc(len + strlen(path) + 1); + if (is_absolute_path(orig)) + strcpy(sanitized, path); + else { + if (len) + memcpy(sanitized, prefix, len); + strcpy(sanitized + len, path); } - return path; + if (sanitary_path_copy(sanitized, sanitized)) + goto error_out; + if (is_absolute_path(orig)) { + const char *work_tree = get_git_work_tree(); + size_t len = strlen(work_tree); + size_t total = strlen(sanitized) + 1; + if (strncmp(sanitized, work_tree, len) || + (sanitized[len] != '\0' && sanitized[len] != '/')) { + error_out: + error("'%s' is outside repository", orig); + free(sanitized); + return NULL; + } + if (sanitized[len] == '/') + len++; + memmove(sanitized, sanitized + len, total - len); + } + return sanitized; } /* @@ -114,7 +181,7 @@ void verify_non_filename(const char *prefix, const char *arg) const char **get_pathspec(const char *prefix, const char **pathspec) { const char *entry = *pathspec; - const char **p; + const char **src, **dst; int prefixlen; if (!prefix && !entry) @@ -128,12 +195,19 @@ const char **get_pathspec(const char *prefix, const char **pathspec) } /* Otherwise we have to re-write the entries.. */ - p = pathspec; + src = pathspec; + dst = pathspec; prefixlen = prefix ? strlen(prefix) : 0; - do { - *p = prefix_path(prefix, prefixlen, entry); - } while ((entry = *++p) != NULL); - return (const char **) pathspec; + while (*src) { + const char *p = prefix_path(prefix, prefixlen, *src); + if (p) + *(dst++) = p; + src++; + } + *dst = NULL; + if (!*pathspec) + return NULL; + return pathspec; } /* diff --git a/sha1_file.c b/sha1_file.c index 66a4e00fa8..1ddb96bb82 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -1845,6 +1845,15 @@ static struct cached_object { } *cached_objects; static int cached_object_nr, cached_object_alloc; +static struct cached_object empty_tree = { + /* empty tree sha1: 4b825dc642cb6eb9a060e54bf8d69288fbee4904 */ + "\x4b\x82\x5d\xc6\x42\xcb\x6e\xb9\xa0\x60" + "\xe5\x4b\xf8\xd6\x92\x88\xfb\xee\x49\x04", + OBJ_TREE, + "", + 0 +}; + static struct cached_object *find_cached_object(const unsigned char *sha1) { int i; @@ -1854,6 +1863,8 @@ static struct cached_object *find_cached_object(const unsigned char *sha1) if (!hashcmp(co->sha1, sha1)) return co; } + if (!hashcmp(sha1, empty_tree.sha1)) + return &empty_tree; return NULL; } @@ -1943,7 +1954,8 @@ void *read_object_with_reference(const unsigned char *sha1, } ref_length = strlen(ref_type); - if (memcmp(buffer, ref_type, ref_length) || + if (ref_length + 40 > isize || + memcmp(buffer, ref_type, ref_length) || get_sha1_hex((char *) buffer + ref_length, actual_sha1)) { free(buffer); return NULL; @@ -2358,7 +2370,8 @@ int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, if ((type == OBJ_BLOB) && S_ISREG(st->st_mode)) { struct strbuf nbuf; strbuf_init(&nbuf, 0); - if (convert_to_git(path, buf, size, &nbuf)) { + if (convert_to_git(path, buf, size, &nbuf, + write_object ? safe_crlf : 0)) { munmap(buf, size); buf = strbuf_detach(&nbuf, &size); re_allocated = 1; diff --git a/sha1_name.c b/sha1_name.c index 13e11645e1..c2805e736b 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -494,8 +494,11 @@ static int peel_onion(const char *name, int len, unsigned char *sha1) return error("%.*s: expected %s type, but the object dereferences to %s type", len, name, typename(expected_type), typename(o->type)); + if (!o) + return -1; if (!o->parsed) - parse_object(o->sha1); + if (!parse_object(o->sha1)) + return -1; } } return 0; @@ -578,8 +581,11 @@ static int handle_one_ref(const char *path, struct object *object = parse_object(sha1); if (!object) return 0; - if (object->type == OBJ_TAG) + if (object->type == OBJ_TAG) { object = deref_tag(object, path, strlen(path)); + if (!object) + return 0; + } if (object->type != OBJ_COMMIT) return 0; insert_by_date((struct commit *)object, list); @@ -617,7 +623,8 @@ static int get_sha1_oneline(const char *prefix, unsigned char *sha1) unsigned long size; commit = pop_most_recent_commit(&list, ONELINE_SEEN); - parse_object(commit->object.sha1); + if (!parse_object(commit->object.sha1)) + continue; if (temp_commit_buffer) free(temp_commit_buffer); if (commit->buffer) @@ -695,7 +702,7 @@ int get_sha1_with_mode(const char *name, unsigned char *sha1, unsigned *mode) break; if (ce_stage(ce) == stage) { hashcpy(sha1, ce->sha1); - *mode = ntohl(ce->ce_mode); + *mode = ce->ce_mode; return 0; } pos++; @@ -56,7 +56,7 @@ struct commit_list *get_shallow_commits(struct object_array *heads, int depth, if (i < heads->nr) { commit = (struct commit *) deref_tag(heads->objects[i++].item, NULL, 0); - if (commit->object.type != OBJ_COMMIT) { + if (!commit || commit->object.type != OBJ_COMMIT) { commit = NULL; continue; } @@ -70,7 +70,8 @@ struct commit_list *get_shallow_commits(struct object_array *heads, int depth, cur_depth = *(int *)commit->util; } } - parse_commit(commit); + if (parse_commit(commit)) + die("invalid commit"); commit->object.flags |= not_shallow_flag; cur_depth++; for (p = commit->parents, commit = NULL; p; p = p->next) { @@ -146,11 +146,12 @@ void strbuf_addf(struct strbuf *sb, const char *fmt, ...) strbuf_setlen(sb, sb->len + len); } -void strbuf_expand(struct strbuf *sb, const char *format, - const char **placeholders, expand_fn_t fn, void *context) +void strbuf_expand(struct strbuf *sb, const char *format, expand_fn_t fn, + void *context) { for (;;) { - const char *percent, **p; + const char *percent; + size_t consumed; percent = strchrnul(format, '%'); strbuf_add(sb, format, percent - format); @@ -158,14 +159,10 @@ void strbuf_expand(struct strbuf *sb, const char *format, break; format = percent + 1; - for (p = placeholders; *p; p++) { - if (!prefixcmp(format, *p)) - break; - } - if (*p) { - fn(sb, *p, context); - format += strlen(*p); - } else + consumed = fn(sb, format, context); + if (consumed) + format += consumed; + else strbuf_addch(sb, '%'); } } @@ -103,8 +103,8 @@ static inline void strbuf_addbuf(struct strbuf *sb, struct strbuf *sb2) { } extern void strbuf_adddup(struct strbuf *sb, size_t pos, size_t len); -typedef void (*expand_fn_t) (struct strbuf *sb, const char *placeholder, void *context); -extern void strbuf_expand(struct strbuf *sb, const char *format, const char **placeholders, expand_fn_t fn, void *context); +typedef size_t (*expand_fn_t) (struct strbuf *sb, const char *placeholder, void *context); +extern void strbuf_expand(struct strbuf *sb, const char *format, expand_fn_t fn, void *context); __attribute__((format(printf,2,3))) extern void strbuf_addf(struct strbuf *sb, const char *fmt, ...); diff --git a/t/.gitattributes b/t/.gitattributes new file mode 100644 index 0000000000..562b12e16e --- /dev/null +++ b/t/.gitattributes @@ -0,0 +1 @@ +* -whitespace @@ -160,14 +160,12 @@ library for your script to use. - test_expect_failure <message> <script> - This is the opposite of test_expect_success. If <script> - yields success, test is considered a failure. - - Example: - - test_expect_failure \ - 'git-update-index without --add should fail adding.' \ - 'git-update-index should-be-empty' + This is NOT the opposite of test_expect_success, but is used + to mark a test that demonstrates a known breakage. Unlike + the usual test_expect_success tests, which say "ok" on + success and "FAIL" on failure, this will say "FIXED" on + success and "still broken" on failure. Failures from these + tests won't cause -i (immediate) to stop. - test_debug <script> diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh index 4e49d59065..92de088227 100755 --- a/t/t0000-basic.sh +++ b/t/t0000-basic.sh @@ -47,12 +47,24 @@ test_expect_success \ 'test $(wc -l < full-of-directories) = 3' ################################################################ +# Test harness +test_expect_success 'success is reported like this' ' + : +' +test_expect_failure 'pretend we have a known breakage' ' + false +' +test_expect_failure 'pretend we have fixed a known breakage' ' + : +' + +################################################################ # Basics of the basics # updating a new file without --add should fail. -test_expect_failure \ - 'git update-index without --add should fail adding.' \ - 'git update-index should-be-empty' +test_expect_success 'git update-index without --add should fail adding.' ' + ! git update-index should-be-empty +' # and with --add it should succeed, even if it is empty (it used to fail). test_expect_success \ @@ -70,9 +82,9 @@ test_expect_success \ # Removing paths. rm -f should-be-empty full-of-directories -test_expect_failure \ - 'git update-index without --remove should fail removing.' \ - 'git update-index should-be-empty' +test_expect_success 'git update-index without --remove should fail removing.' ' + ! git update-index should-be-empty +' test_expect_success \ 'git update-index with --remove should be able to remove.' \ @@ -204,9 +216,9 @@ test_expect_success \ 'put invalid objects into the index.' \ 'git update-index --index-info < badobjects' -test_expect_failure \ - 'writing this tree without --missing-ok.' \ - 'git write-tree' +test_expect_success 'writing this tree without --missing-ok.' ' + ! git write-tree +' test_expect_success \ 'writing this tree with --missing-ok.' \ @@ -297,4 +309,24 @@ test_expect_success 'absolute path works as expected' ' test "$sym" = "$(test-absolute-path $dir2/syml)" ' +test_expect_success 'very long name in the index handled sanely' ' + + a=a && # 1 + a=$a$a$a$a$a$a$a$a$a$a$a$a$a$a$a$a && # 16 + a=$a$a$a$a$a$a$a$a$a$a$a$a$a$a$a$a && # 256 + a=$a$a$a$a$a$a$a$a$a$a$a$a$a$a$a$a && # 4096 + a=${a}q && + + >path4 && + git update-index --add path4 && + ( + git ls-files -s path4 | + sed -e "s/ .*/ /" | + tr -d "\012" + echo "$a" + ) | git update-index --index-info && + len=$(git ls-files "a*" | wc -c) && + test $len = 4098 +' + test_done diff --git a/t/t0020-crlf.sh b/t/t0020-crlf.sh index 8b27aa892b..90ea081db6 100755 --- a/t/t0020-crlf.sh +++ b/t/t0020-crlf.sh @@ -8,6 +8,10 @@ q_to_nul () { tr Q '\000' } +q_to_cr () { + tr Q '\015' +} + append_cr () { sed -e 's/$/Q/' | tr Q '\015' } @@ -42,6 +46,60 @@ test_expect_success setup ' echo happy. ' +test_expect_success 'safecrlf: autocrlf=input, all CRLF' ' + + git config core.autocrlf input && + git config core.safecrlf true && + + for w in I am all CRLF; do echo $w; done | append_cr >allcrlf && + ! git add allcrlf +' + +test_expect_success 'safecrlf: autocrlf=input, mixed LF/CRLF' ' + + git config core.autocrlf input && + git config core.safecrlf true && + + for w in Oh here is CRLFQ in text; do echo $w; done | q_to_cr >mixed && + ! git add mixed +' + +test_expect_success 'safecrlf: autocrlf=true, all LF' ' + + git config core.autocrlf true && + git config core.safecrlf true && + + for w in I am all LF; do echo $w; done >alllf && + ! git add alllf +' + +test_expect_success 'safecrlf: autocrlf=true mixed LF/CRLF' ' + + git config core.autocrlf true && + git config core.safecrlf true && + + for w in Oh here is CRLFQ in text; do echo $w; done | q_to_cr >mixed && + ! git add mixed +' + +test_expect_success 'safecrlf: print warning only once' ' + + git config core.autocrlf input && + git config core.safecrlf warn && + + for w in I am all LF; do echo $w; done >doublewarn && + git add doublewarn && + git commit -m "nowarn" && + for w in Oh here is CRLFQ in text; do echo $w; done | q_to_cr >doublewarn && + test $(git add doublewarn 2>&1 | grep "CRLF will be replaced by LF" | wc -l) = 1 +' + +test_expect_success 'switch off autocrlf, safecrlf, reset HEAD' ' + git config core.autocrlf false && + git config core.safecrlf false && + git reset --hard HEAD^ +' + test_expect_success 'update with autocrlf=input' ' rm -f tmp one dir/two three && diff --git a/t/t0030-stripspace.sh b/t/t0030-stripspace.sh index cad95f35ad..818c8621f2 100755 --- a/t/t0030-stripspace.sh +++ b/t/t0030-stripspace.sh @@ -243,14 +243,14 @@ test_expect_success \ test `printf "$ttt$sss$sss$sss" | git stripspace | wc -l` -gt 0 ' -test_expect_failure \ +test_expect_success \ 'text plus spaces without newline at end should not show spaces' ' - printf "$ttt$sss" | git stripspace | grep -q " " || - printf "$ttt$ttt$sss" | git stripspace | grep -q " " || - printf "$ttt$ttt$ttt$sss" | git stripspace | grep -q " " || - printf "$ttt$sss$sss" | git stripspace | grep -q " " || - printf "$ttt$ttt$sss$sss" | git stripspace | grep -q " " || - printf "$ttt$sss$sss$sss" | git stripspace | grep -q " " + ! (printf "$ttt$sss" | git stripspace | grep -q " ") && + ! (printf "$ttt$ttt$sss" | git stripspace | grep -q " ") && + ! (printf "$ttt$ttt$ttt$sss" | git stripspace | grep -q " ") && + ! (printf "$ttt$sss$sss" | git stripspace | grep -q " ") && + ! (printf "$ttt$ttt$sss$sss" | git stripspace | grep -q " ") && + ! (printf "$ttt$sss$sss$sss" | git stripspace | grep -q " ") ' test_expect_success \ @@ -280,14 +280,14 @@ test_expect_success \ git diff expect actual ' -test_expect_failure \ +test_expect_success \ 'text plus spaces at end should not show spaces' ' - echo "$ttt$sss" | git stripspace | grep -q " " || - echo "$ttt$ttt$sss" | git stripspace | grep -q " " || - echo "$ttt$ttt$ttt$sss" | git stripspace | grep -q " " || - echo "$ttt$sss$sss" | git stripspace | grep -q " " || - echo "$ttt$ttt$sss$sss" | git stripspace | grep -q " " || - echo "$ttt$sss$sss$sss" | git stripspace | grep -q " " + ! (echo "$ttt$sss" | git stripspace | grep -q " ") && + ! (echo "$ttt$ttt$sss" | git stripspace | grep -q " ") && + ! (echo "$ttt$ttt$ttt$sss" | git stripspace | grep -q " ") && + ! (echo "$ttt$sss$sss" | git stripspace | grep -q " ") && + ! (echo "$ttt$ttt$sss$sss" | git stripspace | grep -q " ") && + ! (echo "$ttt$sss$sss$sss" | git stripspace | grep -q " ") ' test_expect_success \ @@ -339,13 +339,13 @@ test_expect_success \ git diff expect actual ' -test_expect_failure \ +test_expect_success \ 'spaces without newline at end should not show spaces' ' - printf "" | git stripspace | grep -q " " || - printf "$sss" | git stripspace | grep -q " " || - printf "$sss$sss" | git stripspace | grep -q " " || - printf "$sss$sss$sss" | git stripspace | grep -q " " || - printf "$sss$sss$sss$sss" | git stripspace | grep -q " " + ! (printf "" | git stripspace | grep -q " ") && + ! (printf "$sss" | git stripspace | grep -q " ") && + ! (printf "$sss$sss" | git stripspace | grep -q " ") && + ! (printf "$sss$sss$sss" | git stripspace | grep -q " ") && + ! (printf "$sss$sss$sss$sss" | git stripspace | grep -q " ") ' test_expect_success \ diff --git a/t/t0040-parse-options.sh b/t/t0040-parse-options.sh index 0a3b55d121..0e2933a984 100755 --- a/t/t0040-parse-options.sh +++ b/t/t0040-parse-options.sh @@ -87,9 +87,9 @@ test_expect_success 'unambiguously abbreviated option with "="' ' git diff expect output ' -test_expect_failure 'ambiguously abbreviated option' ' +test_expect_success 'ambiguously abbreviated option' ' test-parse-options --strin 123; - test $? != 129 + test $? = 129 ' cat > expect << EOF diff --git a/t/t1000-read-tree-m-3way.sh b/t/t1000-read-tree-m-3way.sh index 37add1b504..6c065bfa21 100755 --- a/t/t1000-read-tree-m-3way.sh +++ b/t/t1000-read-tree-m-3way.sh @@ -210,12 +210,12 @@ DF (file) when tree B require DF to be a directory by having DF/DF END_OF_CASE_TABLE -test_expect_failure \ - '1 - must not have an entry not in A.' \ - "rm -f .git/index XX && +test_expect_success '1 - must not have an entry not in A.' " + rm -f .git/index XX && echo XX >XX && git update-index --add XX && - git read-tree -m $tree_O $tree_A $tree_B" + ! git read-tree -m $tree_O $tree_A $tree_B +" test_expect_success \ '2 - must match B in !O && !A && B case.' \ @@ -248,13 +248,14 @@ test_expect_success \ echo extra >>AN && git read-tree -m $tree_O $tree_A $tree_B" -test_expect_failure \ - '3 (fail) - must match A in !O && A && !B case.' \ - "rm -f .git/index AN && +test_expect_success \ + '3 (fail) - must match A in !O && A && !B case.' " + rm -f .git/index AN && cp .orig-A/AN AN && echo extra >>AN && git update-index --add AN && - git read-tree -m $tree_O $tree_A $tree_B" + ! git read-tree -m $tree_O $tree_A $tree_B +" test_expect_success \ '4 - must match and be up-to-date in !O && A && B && A!=B case.' \ @@ -264,21 +265,23 @@ test_expect_success \ git read-tree -m $tree_O $tree_A $tree_B && check_result" -test_expect_failure \ - '4 (fail) - must match and be up-to-date in !O && A && B && A!=B case.' \ - "rm -f .git/index AA && +test_expect_success \ + '4 (fail) - must match and be up-to-date in !O && A && B && A!=B case.' " + rm -f .git/index AA && cp .orig-A/AA AA && git update-index --add AA && echo extra >>AA && - git read-tree -m $tree_O $tree_A $tree_B" + ! git read-tree -m $tree_O $tree_A $tree_B +" -test_expect_failure \ - '4 (fail) - must match and be up-to-date in !O && A && B && A!=B case.' \ - "rm -f .git/index AA && +test_expect_success \ + '4 (fail) - must match and be up-to-date in !O && A && B && A!=B case.' " + rm -f .git/index AA && cp .orig-A/AA AA && echo extra >>AA && git update-index --add AA && - git read-tree -m $tree_O $tree_A $tree_B" + ! git read-tree -m $tree_O $tree_A $tree_B +" test_expect_success \ '5 - must match in !O && A && B && A==B case.' \ @@ -297,34 +300,38 @@ test_expect_success \ git read-tree -m $tree_O $tree_A $tree_B && check_result" -test_expect_failure \ - '5 (fail) - must match A in !O && A && B && A==B case.' \ - "rm -f .git/index LL && +test_expect_success \ + '5 (fail) - must match A in !O && A && B && A==B case.' " + rm -f .git/index LL && cp .orig-A/LL LL && echo extra >>LL && git update-index --add LL && - git read-tree -m $tree_O $tree_A $tree_B" + ! git read-tree -m $tree_O $tree_A $tree_B +" -test_expect_failure \ - '6 - must not exist in O && !A && !B case' \ - "rm -f .git/index DD && +test_expect_success \ + '6 - must not exist in O && !A && !B case' " + rm -f .git/index DD && echo DD >DD git update-index --add DD && - git read-tree -m $tree_O $tree_A $tree_B" + ! git read-tree -m $tree_O $tree_A $tree_B +" -test_expect_failure \ - '7 - must not exist in O && !A && B && O!=B case' \ - "rm -f .git/index DM && +test_expect_success \ + '7 - must not exist in O && !A && B && O!=B case' " + rm -f .git/index DM && cp .orig-B/DM DM && git update-index --add DM && - git read-tree -m $tree_O $tree_A $tree_B" + ! git read-tree -m $tree_O $tree_A $tree_B +" -test_expect_failure \ - '8 - must not exist in O && !A && B && O==B case' \ - "rm -f .git/index DN && +test_expect_success \ + '8 - must not exist in O && !A && B && O==B case' " + rm -f .git/index DN && cp .orig-B/DN DN && git update-index --add DN && - git read-tree -m $tree_O $tree_A $tree_B" + ! git read-tree -m $tree_O $tree_A $tree_B +" test_expect_success \ '9 - must match and be up-to-date in O && A && !B && O!=A case' \ @@ -334,21 +341,23 @@ test_expect_success \ git read-tree -m $tree_O $tree_A $tree_B && check_result" -test_expect_failure \ - '9 (fail) - must match and be up-to-date in O && A && !B && O!=A case' \ - "rm -f .git/index MD && +test_expect_success \ + '9 (fail) - must match and be up-to-date in O && A && !B && O!=A case' " + rm -f .git/index MD && cp .orig-A/MD MD && git update-index --add MD && echo extra >>MD && - git read-tree -m $tree_O $tree_A $tree_B" + ! git read-tree -m $tree_O $tree_A $tree_B +" -test_expect_failure \ - '9 (fail) - must match and be up-to-date in O && A && !B && O!=A case' \ - "rm -f .git/index MD && +test_expect_success \ + '9 (fail) - must match and be up-to-date in O && A && !B && O!=A case' " + rm -f .git/index MD && cp .orig-A/MD MD && echo extra >>MD && git update-index --add MD && - git read-tree -m $tree_O $tree_A $tree_B" + ! git read-tree -m $tree_O $tree_A $tree_B +" test_expect_success \ '10 - must match and be up-to-date in O && A && !B && O==A case' \ @@ -358,21 +367,23 @@ test_expect_success \ git read-tree -m $tree_O $tree_A $tree_B && check_result" -test_expect_failure \ - '10 (fail) - must match and be up-to-date in O && A && !B && O==A case' \ - "rm -f .git/index ND && +test_expect_success \ + '10 (fail) - must match and be up-to-date in O && A && !B && O==A case' " + rm -f .git/index ND && cp .orig-A/ND ND && git update-index --add ND && echo extra >>ND && - git read-tree -m $tree_O $tree_A $tree_B" + ! git read-tree -m $tree_O $tree_A $tree_B +" -test_expect_failure \ - '10 (fail) - must match and be up-to-date in O && A && !B && O==A case' \ - "rm -f .git/index ND && +test_expect_success \ + '10 (fail) - must match and be up-to-date in O && A && !B && O==A case' " + rm -f .git/index ND && cp .orig-A/ND ND && echo extra >>ND && git update-index --add ND && - git read-tree -m $tree_O $tree_A $tree_B" + ! git read-tree -m $tree_O $tree_A $tree_B +" test_expect_success \ '11 - must match and be up-to-date in O && A && B && O!=A && O!=B && A!=B case' \ @@ -382,21 +393,23 @@ test_expect_success \ git read-tree -m $tree_O $tree_A $tree_B && check_result" -test_expect_failure \ - '11 (fail) - must match and be up-to-date in O && A && B && O!=A && O!=B && A!=B case' \ - "rm -f .git/index MM && +test_expect_success \ + '11 (fail) - must match and be up-to-date in O && A && B && O!=A && O!=B && A!=B case' " + rm -f .git/index MM && cp .orig-A/MM MM && git update-index --add MM && echo extra >>MM && - git read-tree -m $tree_O $tree_A $tree_B" + ! git read-tree -m $tree_O $tree_A $tree_B +" -test_expect_failure \ - '11 (fail) - must match and be up-to-date in O && A && B && O!=A && O!=B && A!=B case' \ - "rm -f .git/index MM && +test_expect_success \ + '11 (fail) - must match and be up-to-date in O && A && B && O!=A && O!=B && A!=B case' " + rm -f .git/index MM && cp .orig-A/MM MM && echo extra >>MM && git update-index --add MM && - git read-tree -m $tree_O $tree_A $tree_B" + ! git read-tree -m $tree_O $tree_A $tree_B +" test_expect_success \ '12 - must match A in O && A && B && O!=A && A==B case' \ @@ -415,13 +428,14 @@ test_expect_success \ git read-tree -m $tree_O $tree_A $tree_B && check_result" -test_expect_failure \ - '12 (fail) - must match A in O && A && B && O!=A && A==B case' \ - "rm -f .git/index SS && +test_expect_success \ + '12 (fail) - must match A in O && A && B && O!=A && A==B case' " + rm -f .git/index SS && cp .orig-A/SS SS && echo extra >>SS && git update-index --add SS && - git read-tree -m $tree_O $tree_A $tree_B" + ! git read-tree -m $tree_O $tree_A $tree_B +" test_expect_success \ '13 - must match A in O && A && B && O!=A && O==B case' \ @@ -457,21 +471,23 @@ test_expect_success \ git read-tree -m $tree_O $tree_A $tree_B && check_result" -test_expect_failure \ - '14 (fail) - must match and be up-to-date in O && A && B && O==A && O!=B case' \ - "rm -f .git/index NM && +test_expect_success \ + '14 (fail) - must match and be up-to-date in O && A && B && O==A && O!=B case' " + rm -f .git/index NM && cp .orig-A/NM NM && git update-index --add NM && echo extra >>NM && - git read-tree -m $tree_O $tree_A $tree_B" + ! git read-tree -m $tree_O $tree_A $tree_B +" -test_expect_failure \ - '14 (fail) - must match and be up-to-date in O && A && B && O==A && O!=B case' \ - "rm -f .git/index NM && +test_expect_success \ + '14 (fail) - must match and be up-to-date in O && A && B && O==A && O!=B case' " + rm -f .git/index NM && cp .orig-A/NM NM && echo extra >>NM && git update-index --add NM && - git read-tree -m $tree_O $tree_A $tree_B" + ! git read-tree -m $tree_O $tree_A $tree_B +" test_expect_success \ '15 - must match A in O && A && B && O==A && O==B case' \ @@ -490,13 +506,14 @@ test_expect_success \ git read-tree -m $tree_O $tree_A $tree_B && check_result" -test_expect_failure \ - '15 (fail) - must match A in O && A && B && O==A && O==B case' \ - "rm -f .git/index NN && +test_expect_success \ + '15 (fail) - must match A in O && A && B && O==A && O==B case' " + rm -f .git/index NN && cp .orig-A/NN NN && echo extra >>NN && git update-index --add NN && - git read-tree -m $tree_O $tree_A $tree_B" + ! git read-tree -m $tree_O $tree_A $tree_B +" # #16 test_expect_success \ diff --git a/t/t1200-tutorial.sh b/t/t1200-tutorial.sh index 991d3c5e9c..dcb3108c29 100755 --- a/t/t1200-tutorial.sh +++ b/t/t1200-tutorial.sh @@ -101,8 +101,8 @@ echo "Play, play, play" >>hello echo "Lots of fun" >>example git commit -m 'Some fun.' -i hello example -test_expect_failure 'git resolve now fails' ' - git merge -m "Merge work in mybranch" mybranch +test_expect_success 'git resolve now fails' ' + ! git merge -m "Merge work in mybranch" mybranch ' cat > hello << EOF @@ -156,6 +156,8 @@ test_expect_success 'git show-branch' 'cmp show-branch2.expect show-branch2.outp test_expect_success 'git repack' 'git repack' test_expect_success 'git prune-packed' 'git prune-packed' -test_expect_failure '-> only packed objects' 'find -type f .git/objects/[0-9a-f][0-9a-f]' +test_expect_success '-> only packed objects' ' + ! find -type f .git/objects/[0-9a-f][0-9a-f] +' test_done diff --git a/t/t1300-repo-config.sh b/t/t1300-repo-config.sh index d9e358e1b4..4928a57114 100755 --- a/t/t1300-repo-config.sh +++ b/t/t1300-repo-config.sh @@ -200,8 +200,9 @@ test_expect_success 'non-match' \ test_expect_success 'non-match value' \ 'test wow = $(git config --get nextsection.nonewline !for)' -test_expect_failure 'ambiguous get' \ - 'git config --get nextsection.nonewline' +test_expect_success 'ambiguous get' ' + ! git config --get nextsection.nonewline +' test_expect_success 'get multivar' \ 'git config --get-all nextsection.nonewline' @@ -221,13 +222,17 @@ EOF test_expect_success 'multivar replace' 'cmp .git/config expect' -test_expect_failure 'ambiguous value' 'git config nextsection.nonewline' +test_expect_success 'ambiguous value' ' + ! git config nextsection.nonewline +' -test_expect_failure 'ambiguous unset' \ - 'git config --unset nextsection.nonewline' +test_expect_success 'ambiguous unset' ' + ! git config --unset nextsection.nonewline +' -test_expect_failure 'invalid unset' \ - 'git config --unset somesection.nonewline' +test_expect_success 'invalid unset' ' + ! git config --unset somesection.nonewline +' git config --unset nextsection.nonewline "wow3$" @@ -243,7 +248,7 @@ EOF test_expect_success 'multivar unset' 'cmp .git/config expect' -test_expect_failure 'invalid key' 'git config inval.2key blabla' +test_expect_success 'invalid key' '! git config inval.2key blabla' test_expect_success 'correct key' 'git config 123456.a123 987' @@ -424,8 +429,9 @@ EOF test_expect_success "rename succeeded" "git diff expect .git/config" -test_expect_failure "rename non-existing section" \ - 'git config --rename-section branch."world domination" branch.drei' +test_expect_success "rename non-existing section" ' + ! git config --rename-section branch."world domination" branch.drei +' test_expect_success "rename succeeded" "git diff expect .git/config" @@ -536,14 +542,14 @@ test_expect_success bool ' done && cmp expect result' -test_expect_failure 'invalid bool (--get)' ' +test_expect_success 'invalid bool (--get)' ' git config bool.nobool foobar && - git config --bool --get bool.nobool' + ! git config --bool --get bool.nobool' -test_expect_failure 'invalid bool (set)' ' +test_expect_success 'invalid bool (set)' ' - git config --bool bool.nobool foobar' + ! git config --bool bool.nobool foobar' rm .git/config @@ -604,8 +610,9 @@ EOF test_expect_success 'quoting' 'cmp .git/config expect' -test_expect_failure 'key with newline' 'git config key.with\\\ -newline 123' +test_expect_success 'key with newline' ' + ! git config "key.with +newline" 123' test_expect_success 'value with newline' 'git config key.sub value.with\\\ newline' diff --git a/t/t1302-repo-version.sh b/t/t1302-repo-version.sh index 37fc1c8d36..9be0770e76 100755 --- a/t/t1302-repo-version.sh +++ b/t/t1302-repo-version.sh @@ -40,7 +40,8 @@ test_expect_success 'gitdir required mode on normal repos' ' (git apply --check --index test.patch && cd test && git apply --check --index ../test.patch)' -test_expect_failure 'gitdir required mode on unsupported repo' ' - (cd test2 && git apply --check --index ../test.patch)' +test_expect_success 'gitdir required mode on unsupported repo' ' + (cd test2 && ! git apply --check --index ../test.patch) +' test_done diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh index 71ab2dd0ee..78cd41245b 100755 --- a/t/t1400-update-ref.sh +++ b/t/t1400-update-ref.sh @@ -51,23 +51,23 @@ test_expect_success \ test $B"' = $(cat .git/'"$m"')' rm -f .git/$m -test_expect_failure \ - '(not) create HEAD with old sha1' \ - "git update-ref HEAD $A $B" -test_expect_failure \ - "(not) prior created .git/$m" \ - "test -f .git/$m" +test_expect_success '(not) create HEAD with old sha1' " + ! git update-ref HEAD $A $B +" +test_expect_success "(not) prior created .git/$m" " + ! test -f .git/$m +" rm -f .git/$m test_expect_success \ "create HEAD" \ "git update-ref HEAD $A" -test_expect_failure \ - '(not) change HEAD with wrong SHA1' \ - "git update-ref HEAD $B $Z" -test_expect_failure \ - "(not) changed .git/$m" \ - "test $B"' = $(cat .git/'"$m"')' +test_expect_success '(not) change HEAD with wrong SHA1' " + ! git update-ref HEAD $B $Z +" +test_expect_success "(not) changed .git/$m" " + ! test $B"' = $(cat .git/'"$m"') +' rm -f .git/$m : a repository with working tree always has reflog these days... diff --git a/t/t1500-rev-parse.sh b/t/t1500-rev-parse.sh index e474b3f1d5..38a2bf09af 100755 --- a/t/t1500-rev-parse.sh +++ b/t/t1500-rev-parse.sh @@ -33,9 +33,9 @@ test_rev_parse() { test_rev_parse toplevel false false true '' cd .git || exit 1 -test_rev_parse .git/ true true false '' +test_rev_parse .git/ false true false '' cd objects || exit 1 -test_rev_parse .git/objects/ true true false '' +test_rev_parse .git/objects/ false true false '' cd ../.. || exit 1 mkdir -p sub/dir || exit 1 diff --git a/t/t2000-checkout-cache-clash.sh b/t/t2000-checkout-cache-clash.sh index ac84335b0a..5141fab7cf 100755 --- a/t/t2000-checkout-cache-clash.sh +++ b/t/t2000-checkout-cache-clash.sh @@ -36,9 +36,9 @@ mkdir path0 date >path0/file0 date >path1 -test_expect_failure \ +test_expect_success \ 'git checkout-index without -f should fail on conflicting work tree.' \ - 'git checkout-index -a' + '! git checkout-index -a' test_expect_success \ 'git checkout-index with -f should succeed.' \ diff --git a/t/t2002-checkout-cache-u.sh b/t/t2002-checkout-cache-u.sh index f7a0055920..0f441bcef7 100755 --- a/t/t2002-checkout-cache-u.sh +++ b/t/t2002-checkout-cache-u.sh @@ -16,12 +16,12 @@ echo frotz >path0 && git update-index --add path0 && t=$(git write-tree)' -test_expect_failure \ +test_expect_success \ 'without -u, git checkout-index smudges stat information.' ' rm -f path0 && git read-tree $t && git checkout-index -f -a && -git diff-files | diff - /dev/null' +! git diff-files | diff - /dev/null' test_expect_success \ 'with -u, git checkout-index picks up stat information from new files.' ' diff --git a/t/t2008-checkout-subdir.sh b/t/t2008-checkout-subdir.sh index f78945ed8e..4a723dc0e5 100755 --- a/t/t2008-checkout-subdir.sh +++ b/t/t2008-checkout-subdir.sh @@ -67,16 +67,16 @@ test_expect_success 'checkout with simple prefix' ' ' -test_expect_failure 'relative path outside tree should fail' \ - 'git checkout HEAD -- ../../Makefile' +test_expect_success 'relative path outside tree should fail' \ + '! git checkout HEAD -- ../../Makefile' -test_expect_failure 'incorrect relative path to file should fail (1)' \ - 'git checkout HEAD -- ../file0' +test_expect_success 'incorrect relative path to file should fail (1)' \ + '! git checkout HEAD -- ../file0' -test_expect_failure 'incorrect relative path should fail (2)' \ - '( cd dir1 && git checkout HEAD -- ./file0 )' +test_expect_success 'incorrect relative path should fail (2)' \ + '( cd dir1 && ! git checkout HEAD -- ./file0 )' -test_expect_failure 'incorrect relative path should fail (3)' \ - '( cd dir1 && git checkout HEAD -- ../../file0 )' +test_expect_success 'incorrect relative path should fail (3)' \ + '( cd dir1 && ! git checkout HEAD -- ../../file0 )' test_done diff --git a/t/t2100-update-cache-badpath.sh b/t/t2100-update-cache-badpath.sh index 04a1ed1a6b..9beaecd18b 100755 --- a/t/t2100-update-cache-badpath.sh +++ b/t/t2100-update-cache-badpath.sh @@ -44,8 +44,8 @@ date >path1/file1 for p in path0/file0 path1/file1 path2 path3 do - test_expect_failure \ + test_expect_success \ "git update-index to add conflicting path $p should fail." \ - "git update-index --add -- $p" + "! git update-index --add -- $p" done test_done diff --git a/t/t3001-ls-files-others-exclude.sh b/t/t3001-ls-files-others-exclude.sh index e25b255683..b4297bacf2 100755 --- a/t/t3001-ls-files-others-exclude.sh +++ b/t/t3001-ls-files-others-exclude.sh @@ -99,4 +99,45 @@ EOF test_expect_success 'git-status honours core.excludesfile' \ 'diff -u expect output' +test_expect_success 'trailing slash in exclude allows directory match(1)' ' + + git ls-files --others --exclude=one/ >output && + if grep "^one/" output + then + echo Ooops + false + else + : happy + fi + +' + +test_expect_success 'trailing slash in exclude allows directory match (2)' ' + + git ls-files --others --exclude=one/two/ >output && + if grep "^one/two/" output + then + echo Ooops + false + else + : happy + fi + +' + +test_expect_success 'trailing slash in exclude forces directory match (1)' ' + + >two + git ls-files --others --exclude=two/ >output && + grep "^two" output + +' + +test_expect_success 'trailing slash in exclude forces directory match (2)' ' + + git ls-files --others --exclude=one/a.1/ >output && + grep "^one/a.1" output + +' + test_done diff --git a/t/t3020-ls-files-error-unmatch.sh b/t/t3020-ls-files-error-unmatch.sh index c83f820ad2..f4da869932 100755 --- a/t/t3020-ls-files-error-unmatch.sh +++ b/t/t3020-ls-files-error-unmatch.sh @@ -15,9 +15,9 @@ touch foo bar git update-index --add foo bar git-commit -m "add foo bar" -test_expect_failure \ +test_expect_success \ 'git ls-files --error-unmatch should fail with unmatched path.' \ - 'git ls-files --error-unmatch foo bar-does-not-match' + '! git ls-files --error-unmatch foo bar-does-not-match' test_expect_success \ 'git ls-files --error-unmatch should succeed eith matched paths.' \ diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index ef1eeb7d8a..d21081d0f1 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -17,10 +17,11 @@ test_expect_success \ git-commit -m "Initial commit." && HEAD=$(git rev-parse --verify HEAD)' -test_expect_failure \ - 'git branch --help should not have created a bogus branch' \ - 'git branch --help </dev/null >/dev/null 2>/dev/null || : - test -f .git/refs/heads/--help' +test_expect_success \ + 'git branch --help should not have created a bogus branch' ' + git branch --help </dev/null >/dev/null 2>/dev/null; + ! test -f .git/refs/heads/--help +' test_expect_success \ 'git branch abc should create a branch' \ @@ -71,17 +72,17 @@ test_expect_success \ git branch -m n/n n test -f .git/logs/refs/heads/n' -test_expect_failure \ - 'git branch -m o/o o should fail when o/p exists' \ - 'git branch o/o && +test_expect_success 'git branch -m o/o o should fail when o/p exists' ' + git branch o/o && git branch o/p && - git branch -m o/o o' + ! git branch -m o/o o +' -test_expect_failure \ - 'git branch -m q r/q should fail when r exists' \ - 'git branch q && - git branch r && - git branch -m q r/q' +test_expect_success 'git branch -m q r/q should fail when r exists' ' + git branch q && + git branch r && + ! git branch -m q r/q +' mv .git/config .git/config-saved @@ -108,12 +109,13 @@ test_expect_success 'config information was renamed, too' \ "test $(git config branch.s.dummy) = Hello && ! git config branch.s/s/dummy" -test_expect_failure \ - 'git branch -m u v should fail when the reflog for u is a symlink' \ - 'git branch -l u && +test_expect_success \ + 'git branch -m u v should fail when the reflog for u is a symlink' ' + git branch -l u && mv .git/logs/refs/heads/u real-u && ln -s real-u .git/logs/refs/heads/u && - git branch -m u v' + ! git branch -m u v +' test_expect_success 'test tracking setup via --track' \ 'git config remote.local.url . && diff --git a/t/t3210-pack-refs.sh b/t/t3210-pack-refs.sh index 4ddc6342a9..b64ccfbc5b 100755 --- a/t/t3210-pack-refs.sh +++ b/t/t3210-pack-refs.sh @@ -39,12 +39,12 @@ test_expect_success \ git show-ref b >result && diff expect result' -test_expect_failure \ - 'git branch c/d should barf if branch c exists' \ - 'git branch c && +test_expect_success 'git branch c/d should barf if branch c exists' ' + git branch c && git pack-refs --all && - rm .git/refs/heads/c && - git branch c/d' + rm -f .git/refs/heads/c && + ! git branch c/d +' test_expect_success \ 'see if a branch still exists after git pack-refs --prune' \ @@ -54,11 +54,11 @@ test_expect_success \ git show-ref e >result && diff expect result' -test_expect_failure \ - 'see if git pack-refs --prune remove ref files' \ - 'git branch f && +test_expect_success 'see if git pack-refs --prune remove ref files' ' + git branch f && git pack-refs --all --prune && - ls .git/refs/heads/f' + ! test -f .git/refs/heads/f +' test_expect_success \ 'git branch g should work when git branch g/h has been deleted' \ @@ -69,11 +69,11 @@ test_expect_success \ git pack-refs --all && git branch -d g' -test_expect_failure \ - 'git branch i/j/k should barf if branch i exists' \ - 'git branch i && +test_expect_success 'git branch i/j/k should barf if branch i exists' ' + git branch i && git pack-refs --all --prune && - git branch i/j/k' + ! git branch i/j/k +' test_expect_success \ 'test git branch k after branch k/l/m and k/lm have been deleted' \ diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh index 95e33b5210..496f4ec172 100755 --- a/t/t3400-rebase.sh +++ b/t/t3400-rebase.sh @@ -42,9 +42,9 @@ test_expect_success \ test_expect_success 'rebase against master' ' git rebase master' -test_expect_failure \ +test_expect_success \ 'the rebase operation should not have destroyed author information' \ - 'git log | grep "Author:" | grep "<>"' + '! git log | grep "Author:" | grep "<>"' test_expect_success 'rebase after merge master' ' git reset --hard topic && diff --git a/t/t3403-rebase-skip.sh b/t/t3403-rebase-skip.sh index 657f68104d..0a26099658 100755 --- a/t/t3403-rebase-skip.sh +++ b/t/t3403-rebase-skip.sh @@ -31,8 +31,8 @@ test_expect_success setup ' git branch skip-merge skip-reference ' -test_expect_failure 'rebase with git am -3 (default)' ' - git rebase master +test_expect_success 'rebase with git am -3 (default)' ' + ! git rebase master ' test_expect_success 'rebase --skip with am -3' ' @@ -53,7 +53,7 @@ test_expect_success 'rebase moves back to skip-reference' ' test_expect_success 'checkout skip-merge' 'git checkout -f skip-merge' -test_expect_failure 'rebase with --merge' 'git rebase --merge master' +test_expect_success 'rebase with --merge' '! git rebase --merge master' test_expect_success 'rebase --skip with --merge' ' git rebase --skip diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index e5ed74545b..62e65d704b 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -61,8 +61,8 @@ test_expect_success 'setup' ' git tag I ' -cat > fake-editor.sh <<\EOF -#!/bin/sh +echo "#!$SHELL" >fake-editor +cat >> fake-editor.sh <<\EOF case "$1" in */COMMIT_EDITMSG) test -z "$FAKE_COMMIT_MESSAGE" || echo "$FAKE_COMMIT_MESSAGE" > "$1" diff --git a/t/t3600-rm.sh b/t/t3600-rm.sh index b1ee622ef7..f542f0af41 100755 --- a/t/t3600-rm.sh +++ b/t/t3600-rm.sh @@ -59,15 +59,16 @@ test_expect_success \ echo "other content" > foo git rm --cached foo' -test_expect_failure \ - 'Test that git rm --cached foo fails if the index matches neither the file nor HEAD' \ - 'echo content > foo +test_expect_success \ + 'Test that git rm --cached foo fails if the index matches neither the file nor HEAD' ' + echo content > foo git add foo git commit -m foo echo "other content" > foo git add foo echo "yet another content" > foo - git rm --cached foo' + ! git rm --cached foo +' test_expect_success \ 'Test that git rm --cached -f foo works in case where --cached only did not' \ @@ -106,9 +107,9 @@ embedded'" if test "$test_failed_remove" = y; then chmod a-w . -test_expect_failure \ +test_expect_success \ 'Test that "git rm -f" fails if its rm fails' \ - 'git rm -f baz' + '! git rm -f baz' chmod 775 . else test_expect_success 'skipping removal failure (perhaps running as root?)' : @@ -212,8 +213,8 @@ test_expect_success 'Recursive with -r -f' ' ! test -d frotz ' -test_expect_failure 'Remove nonexistent file returns nonzero exit status' ' - git rm nonexistent +test_expect_success 'Remove nonexistent file returns nonzero exit status' ' + ! git rm nonexistent ' test_done diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh new file mode 100755 index 0000000000..c8dc1ac241 --- /dev/null +++ b/t/t3701-add-interactive.sh @@ -0,0 +1,69 @@ +#!/bin/sh + +test_description='add -i basic tests' +. ./test-lib.sh + +test_expect_success 'setup (initial)' ' + echo content >file && + git add file && + echo more >>file && + echo lines >>file +' +test_expect_success 'status works (initial)' ' + git add -i </dev/null >output && + grep "+1/-0 *+2/-0 file" output +' +cat >expected <<EOF +new file mode 100644 +index 0000000..d95f3ad +--- /dev/null ++++ b/file +@@ -0,0 +1 @@ ++content +EOF +test_expect_success 'diff works (initial)' ' + (echo d; echo 1) | git add -i >output && + sed -ne "/new file/,/content/p" <output >diff && + diff -u expected diff +' +test_expect_success 'revert works (initial)' ' + git add file && + (echo r; echo 1) | git add -i && + git ls-files >output && + ! grep . output +' + +test_expect_success 'setup (commit)' ' + echo baseline >file && + git add file && + git commit -m commit && + echo content >>file && + git add file && + echo more >>file && + echo lines >>file +' +test_expect_success 'status works (commit)' ' + git add -i </dev/null >output && + grep "+1/-0 *+2/-0 file" output +' +cat >expected <<EOF +index 180b47c..b6f2c08 100644 +--- a/file ++++ b/file +@@ -1 +1,2 @@ + baseline ++content +EOF +test_expect_success 'diff works (commit)' ' + (echo d; echo 1) | git add -i >output && + sed -ne "/^index/,/content/p" <output >diff && + diff -u expected diff +' +test_expect_success 'revert works (commit)' ' + git add file && + (echo r; echo 1) | git add -i && + git add -i </dev/null >output && + grep "unchanged *+3/-0 file" output +' + +test_done diff --git a/t/t4103-apply-binary.sh b/t/t4103-apply-binary.sh index 74f06ec730..7c25634fc2 100755 --- a/t/t4103-apply-binary.sh +++ b/t/t4103-apply-binary.sh @@ -46,21 +46,25 @@ test_expect_success 'stat binary diff (copy) -- should not fail.' \ 'git-checkout master git apply --stat --summary C.diff' -test_expect_failure 'check binary diff -- should fail.' \ - 'git-checkout master - git apply --check B.diff' - -test_expect_failure 'check binary diff (copy) -- should fail.' \ - 'git-checkout master - git apply --check C.diff' - -test_expect_failure 'check incomplete binary diff with replacement -- should fail.' \ - 'git-checkout master - git apply --check --allow-binary-replacement B.diff' +test_expect_success 'check binary diff -- should fail.' \ + 'git-checkout master && + ! git apply --check B.diff' + +test_expect_success 'check binary diff (copy) -- should fail.' \ + 'git-checkout master && + ! git apply --check C.diff' + +test_expect_success \ + 'check incomplete binary diff with replacement -- should fail.' ' + git-checkout master && + ! git apply --check --allow-binary-replacement B.diff +' -test_expect_failure 'check incomplete binary diff with replacement (copy) -- should fail.' \ - 'git-checkout master - git apply --check --allow-binary-replacement C.diff' +test_expect_success \ + 'check incomplete binary diff with replacement (copy) -- should fail.' ' + git-checkout master && + ! git apply --check --allow-binary-replacement C.diff +' test_expect_success 'check binary diff with replacement.' \ 'git-checkout master @@ -73,42 +77,42 @@ test_expect_success 'check binary diff with replacement (copy).' \ # Now we start applying them. do_reset () { - rm -f file? - git-reset --hard + rm -f file? && + git-reset --hard && git-checkout -f master } -test_expect_failure 'apply binary diff -- should fail.' \ - 'do_reset - git apply B.diff' +test_expect_success 'apply binary diff -- should fail.' \ + 'do_reset && + ! git apply B.diff' -test_expect_failure 'apply binary diff -- should fail.' \ - 'do_reset - git apply --index B.diff' +test_expect_success 'apply binary diff -- should fail.' \ + 'do_reset && + ! git apply --index B.diff' -test_expect_failure 'apply binary diff (copy) -- should fail.' \ - 'do_reset - git apply C.diff' +test_expect_success 'apply binary diff (copy) -- should fail.' \ + 'do_reset && + ! git apply C.diff' -test_expect_failure 'apply binary diff (copy) -- should fail.' \ - 'do_reset - git apply --index C.diff' +test_expect_success 'apply binary diff (copy) -- should fail.' \ + 'do_reset && + ! git apply --index C.diff' test_expect_success 'apply binary diff without replacement.' \ - 'do_reset + 'do_reset && git apply BF.diff' test_expect_success 'apply binary diff without replacement (copy).' \ - 'do_reset + 'do_reset && git apply CF.diff' test_expect_success 'apply binary diff.' \ - 'do_reset + 'do_reset && git apply --allow-binary-replacement --index BF.diff && test -z "$(git diff --name-status binary)"' test_expect_success 'apply binary diff (copy).' \ - 'do_reset + 'do_reset && git apply --allow-binary-replacement --index CF.diff && test -z "$(git diff --name-status binary)"' diff --git a/t/t4113-apply-ending.sh b/t/t4113-apply-ending.sh index 1c6bec044a..d741039882 100755 --- a/t/t4113-apply-ending.sh +++ b/t/t4113-apply-ending.sh @@ -29,8 +29,8 @@ test_expect_success setup \ # test -test_expect_failure 'apply at the end' \ - 'git apply --index test-patch' +test_expect_success 'apply at the end' \ + '! git apply --index test-patch' cat >test-patch <<\EOF diff a/file b/file @@ -47,7 +47,7 @@ b c' git update-index file -test_expect_failure 'apply at the beginning' \ - 'git apply --index test-patch' +test_expect_success 'apply at the beginning' \ + '! git apply --index test-patch' test_done diff --git a/t/t5300-pack-object.sh b/t/t5300-pack-object.sh index 6e594bf1e2..cd3c149800 100755 --- a/t/t5300-pack-object.sh +++ b/t/t5300-pack-object.sh @@ -264,8 +264,14 @@ test_expect_success \ cp -f .git/objects/9d/235ed07cd19811a6ceb342de82f190e49c9f68 \ .git/objects/c8/2de19312b6c3695c0c18f70709a6c535682a67' -test_expect_failure \ +test_expect_success \ 'make sure index-pack detects the SHA1 collision' \ - 'git-index-pack -o bad.idx test-3.pack' + '! git-index-pack -o bad.idx test-3.pack' + +test_expect_success \ + 'honor pack.packSizeLimit' \ + 'git config pack.packSizeLimit 200 && + packname_4=$(git pack-objects test-4 <obj-list) && + test 3 = $(ls test-4-*.pack | wc -l)' test_done diff --git a/t/t5302-pack-index.sh b/t/t5302-pack-index.sh index 2a2878b572..67b9a7b84a 100755 --- a/t/t5302-pack-index.sh +++ b/t/t5302-pack-index.sh @@ -42,9 +42,9 @@ test_expect_success \ 'both packs should be identical' \ 'cmp "test-1-${pack1}.pack" "test-2-${pack2}.pack"' -test_expect_failure \ +test_expect_success \ 'index v1 and index v2 should be different' \ - 'cmp "test-1-${pack1}.idx" "test-2-${pack2}.idx"' + '! cmp "test-1-${pack1}.idx" "test-2-${pack2}.idx"' test_expect_success \ 'index-pack with index version 1' \ @@ -78,9 +78,9 @@ test_expect_success \ 'git verify-pack -v "test-3-${pack3}.pack"' test "$have_64bits" && -test_expect_failure \ +test_expect_success \ '64-bit offsets: should be different from previous index v2 results' \ - 'cmp "test-2-${pack2}.idx" "test-3-${pack3}.idx"' + '! cmp "test-2-${pack2}.idx" "test-3-${pack3}.idx"' test "$have_64bits" && test_expect_success \ @@ -112,22 +112,22 @@ test_expect_success \ bs=1 count=20 conv=notrunc && git cat-file blob "$delta_sha1" > blob_2 )' -test_expect_failure \ +test_expect_success \ '[index v1] 3) corrupted delta happily returned wrong data' \ - 'cmp blob_1 blob_2' + '! cmp blob_1 blob_2' -test_expect_failure \ +test_expect_success \ '[index v1] 4) confirm that the pack is actually corrupted' \ - 'git fsck --full $commit' + '! git fsck --full $commit' test_expect_success \ '[index v1] 5) pack-objects happily reuses corrupted data' \ 'pack4=$(git pack-objects test-4 <obj-list) && test -f "test-4-${pack1}.pack"' -test_expect_failure \ +test_expect_success \ '[index v1] 6) newly created pack is BAD !' \ - 'git verify-pack -v "test-4-${pack1}.pack"' + '! git verify-pack -v "test-4-${pack1}.pack"' test_expect_success \ '[index v2] 1) stream pack to repository' \ @@ -150,16 +150,16 @@ test_expect_success \ bs=1 count=20 conv=notrunc && git cat-file blob "$delta_sha1" > blob_4 )' -test_expect_failure \ +test_expect_success \ '[index v2] 3) corrupted delta happily returned wrong data' \ - 'cmp blob_3 blob_4' + '! cmp blob_3 blob_4' -test_expect_failure \ +test_expect_success \ '[index v2] 4) confirm that the pack is actually corrupted' \ - 'git fsck --full $commit' + '! git fsck --full $commit' -test_expect_failure \ +test_expect_success \ '[index v2] 5) pack-objects refuses to reuse corrupted data' \ - 'git pack-objects test-5 <obj-list' + '! git pack-objects test-5 <obj-list' test_done diff --git a/t/t5401-update-hooks.sh b/t/t5401-update-hooks.sh index 9734fc542f..9a12024241 100755 --- a/t/t5401-update-hooks.sh +++ b/t/t5401-update-hooks.sh @@ -60,8 +60,8 @@ echo STDERR post-update >&2 EOF chmod u+x victim/.git/hooks/post-update -test_expect_failure push ' - git-send-pack --force ./victim/.git master tofail >send.out 2>send.err +test_expect_success push ' + ! git-send-pack --force ./victim/.git master tofail >send.out 2>send.err ' test_expect_success 'updated as expected' ' @@ -112,8 +112,8 @@ test_expect_success 'all *-receive hook args are empty' ' ! test -s victim/.git/post-receive.args ' -test_expect_failure 'send-pack produced no output' ' - test -s send.out +test_expect_success 'send-pack produced no output' ' + ! test -s send.out ' cat <<EOF >expect diff --git a/t/t5402-post-merge-hook.sh b/t/t5402-post-merge-hook.sh index 1c4b0b32ab..1394047a8d 100755 --- a/t/t5402-post-merge-hook.sh +++ b/t/t5402-post-merge-hook.sh @@ -30,9 +30,9 @@ EOF chmod u+x clone${clone}/.git/hooks/post-merge done -test_expect_failure 'post-merge does not run for up-to-date ' ' +test_expect_success 'post-merge does not run for up-to-date ' ' GIT_DIR=clone1/.git git merge $commit0 && - test -e clone1/.git/post-merge.args + ! test -f clone1/.git/post-merge.args ' test_expect_success 'post-merge runs as expected ' ' diff --git a/t/t5500-fetch-pack.sh b/t/t5500-fetch-pack.sh index 7b6798d8b5..788b4a5aae 100755 --- a/t/t5500-fetch-pack.sh +++ b/t/t5500-fetch-pack.sh @@ -176,7 +176,7 @@ test_expect_success "deepening fetch in shallow repo" \ test_expect_success "clone shallow object count" \ "test \"count: 18\" = \"$(grep count count.shallow)\"" -test_expect_failure "pull in shallow repo with missing merge base" \ - "(cd shallow; git pull --depth 4 .. A)" +test_expect_success "pull in shallow repo with missing merge base" \ + "(cd shallow && ! git pull --depth 4 .. A)" test_done diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh index 02882c1e4b..9b948c14e6 100755 --- a/t/t5510-fetch.sh +++ b/t/t5510-fetch.sh @@ -95,7 +95,7 @@ test_expect_success 'fetch following tags' ' ' -test_expect_failure 'fetch must not resolve short tag name' ' +test_expect_success 'fetch must not resolve short tag name' ' cd "$D" && @@ -103,11 +103,11 @@ test_expect_failure 'fetch must not resolve short tag name' ' cd five && git init && - git fetch .. anno:five + ! git fetch .. anno:five ' -test_expect_failure 'fetch must not resolve short remote name' ' +test_expect_success 'fetch must not resolve short remote name' ' cd "$D" && git-update-ref refs/remotes/six/HEAD HEAD @@ -116,7 +116,7 @@ test_expect_failure 'fetch must not resolve short remote name' ' cd six && git init && - git fetch .. six:six + ! git fetch .. six:six ' @@ -139,10 +139,10 @@ test_expect_success 'create bundle 2' ' git bundle create bundle2 master~2..master ' -test_expect_failure 'unbundle 1' ' +test_expect_success 'unbundle 1' ' cd "$D/bundle" && git checkout -b some-branch && - git fetch "$D/bundle1" master:master + ! git fetch "$D/bundle1" master:master ' test_expect_success 'bundle 1 has only 3 files ' ' diff --git a/t/t5530-upload-pack-error.sh b/t/t5530-upload-pack-error.sh index cc8949e3ef..8b05091069 100755 --- a/t/t5530-upload-pack-error.sh +++ b/t/t5530-upload-pack-error.sh @@ -26,9 +26,8 @@ test_expect_success 'setup and corrupt repository' ' ' -test_expect_failure 'fsck fails' ' - - git fsck +test_expect_success 'fsck fails' ' + ! git fsck ' test_expect_success 'upload-pack fails due to error in pack-objects' ' @@ -46,9 +45,8 @@ test_expect_success 'corrupt repo differently' ' ' -test_expect_failure 'fsck fails' ' - - git fsck +test_expect_success 'fsck fails' ' + ! git fsck ' test_expect_success 'upload-pack fails due to error in rev-list' ' @@ -66,9 +64,9 @@ test_expect_success 'create empty repository' ' ' -test_expect_failure 'fetch fails' ' +test_expect_success 'fetch fails' ' - git fetch .. master + ! git fetch .. master ' diff --git a/t/t5600-clone-fail-cleanup.sh b/t/t5600-clone-fail-cleanup.sh index 1776b377f3..acf34cec8f 100755 --- a/t/t5600-clone-fail-cleanup.sh +++ b/t/t5600-clone-fail-cleanup.sh @@ -11,13 +11,13 @@ remove the directory before attempting a clone again.' . ./test-lib.sh -test_expect_failure \ +test_expect_success \ 'clone of non-existent source should fail' \ - 'git-clone foo bar' + '! git-clone foo bar' -test_expect_failure \ +test_expect_success \ 'failed clone should not leave a directory' \ - 'cd bar' + '! test -d bar' # Need a repo to clone test_create_repo foo @@ -27,9 +27,9 @@ test_create_repo foo # source repository given to git-clone should be relative to the # current path not to the target dir -test_expect_failure \ +test_expect_success \ 'clone of non-existent (relative to $PWD) source should fail' \ - 'git-clone ../foo baz' + '! git-clone ../foo baz' test_expect_success \ 'clone should work now that source exists' \ diff --git a/t/t5710-info-alternate.sh b/t/t5710-info-alternate.sh index 1908dc8b06..910ccb4fff 100755 --- a/t/t5710-info-alternate.sh +++ b/t/t5710-info-alternate.sh @@ -87,10 +87,10 @@ test_valid_repo" cd "$base_dir" -test_expect_failure 'that info/alternates is necessary' \ +test_expect_success 'that info/alternates is necessary' \ 'cd C && -rm .git/objects/info/alternates && -test_valid_repo' +rm -f .git/objects/info/alternates && +! (test_valid_repo)' cd "$base_dir" @@ -101,9 +101,11 @@ test_valid_repo' cd "$base_dir" -test_expect_failure 'that relative alternate is only possible for current dir' \ -'cd D && -test_valid_repo' +test_expect_success \ + 'that relative alternate is only possible for current dir' ' + cd D && + ! (test_valid_repo) +' cd "$base_dir" diff --git a/t/t6009-rev-list-parent.sh b/t/t6009-rev-list-parent.sh new file mode 100755 index 0000000000..be3d238d99 --- /dev/null +++ b/t/t6009-rev-list-parent.sh @@ -0,0 +1,38 @@ +#!/bin/sh + +test_description='properly cull all ancestors' + +. ./test-lib.sh + +commit () { + test_tick && + echo $1 >file && + git commit -a -m $1 && + git tag $1 +} + +test_expect_success setup ' + + touch file && + git add file && + + commit one && + + test_tick=$(($test_tick - 2400)) + + commit two && + commit three && + commit four && + + git log --pretty=oneline --abbrev-commit +' + +test_expect_failure 'one is ancestor of others and should not be shown' ' + + git rev-list one --not four >result && + >expect && + diff -u expect result + +' + +test_done diff --git a/t/t6023-merge-file.sh b/t/t6023-merge-file.sh index ae3b6f2831..86419964b4 100755 --- a/t/t6023-merge-file.sh +++ b/t/t6023-merge-file.sh @@ -66,8 +66,8 @@ test_expect_success "merge result added missing LF" \ "git diff test.txt test2.txt" cp test.txt backup.txt -test_expect_failure "merge with conflicts" \ - "git merge-file test.txt orig.txt new3.txt" +test_expect_success "merge with conflicts" \ + "! git merge-file test.txt orig.txt new3.txt" cat > expect.txt << EOF <<<<<<< test.txt @@ -89,8 +89,8 @@ EOF test_expect_success "expected conflict markers" "git diff test.txt expect.txt" cp backup.txt test.txt -test_expect_failure "merge with conflicts, using -L" \ - "git merge-file -L 1 -L 2 test.txt orig.txt new3.txt" +test_expect_success "merge with conflicts, using -L" \ + "! git merge-file -L 1 -L 2 test.txt orig.txt new3.txt" cat > expect.txt << EOF <<<<<<< 1 @@ -113,8 +113,8 @@ test_expect_success "expected conflict markers, with -L" \ "git diff test.txt expect.txt" sed "s/ tu / TU /" < new1.txt > new5.txt -test_expect_failure "conflict in removed tail" \ - "git merge-file -p orig.txt new1.txt new5.txt > out" +test_expect_success "conflict in removed tail" \ + "! git merge-file -p orig.txt new1.txt new5.txt > out" cat > expect << EOF Dominus regit me, diff --git a/t/t6024-recursive-merge.sh b/t/t6024-recursive-merge.sh index c154f03cf5..149ea8543a 100755 --- a/t/t6024-recursive-merge.sh +++ b/t/t6024-recursive-merge.sh @@ -60,7 +60,7 @@ git update-index a1 && GIT_AUTHOR_DATE="2006-12-12 23:00:08" git commit -m F ' -test_expect_failure "combined merge conflicts" "git merge -m final G" +test_expect_success "combined merge conflicts" "! git merge -m final G" cat > expect << EOF <<<<<<< HEAD:a1 diff --git a/t/t6025-merge-symlinks.sh b/t/t6025-merge-symlinks.sh index 950c2e9b63..6004deb432 100755 --- a/t/t6025-merge-symlinks.sh +++ b/t/t6025-merge-symlinks.sh @@ -30,30 +30,29 @@ echo plain-file > symlink && git add symlink && git-commit -m b-file' -test_expect_failure \ +test_expect_success \ 'merge master into b-symlink, which has a different symbolic link' ' -! git-checkout b-symlink || -git-merge master' +git-checkout b-symlink && +! git-merge master' test_expect_success \ 'the merge result must be a file' ' test -f symlink' -test_expect_failure \ +test_expect_success \ 'merge master into b-file, which has a file instead of a symbolic link' ' -! (git-reset --hard && -git-checkout b-file) || -git-merge master' +git-reset --hard && git-checkout b-file && +! git-merge master' test_expect_success \ 'the merge result must be a file' ' test -f symlink' -test_expect_failure \ +test_expect_success \ 'merge b-file, which has a file instead of a symbolic link, into master' ' -! (git-reset --hard && -git-checkout master) || -git-merge b-file' +git-reset --hard && +git-checkout master && +! git-merge b-file' test_expect_success \ 'the merge result must be a file' ' diff --git a/t/t6101-rev-parse-parents.sh b/t/t6101-rev-parse-parents.sh index 0724864e56..2328b69947 100755 --- a/t/t6101-rev-parse-parents.sh +++ b/t/t6101-rev-parse-parents.sh @@ -26,7 +26,7 @@ test_expect_success 'final^1^1^1 = final^^^' "test $(git rev-parse final^1^1^1) test_expect_success 'final^1^2' "test $(git rev-parse start2) = $(git rev-parse final^1^2)" test_expect_success 'final^1^2 != final^1^1' "test $(git rev-parse final^1^2) != $(git rev-parse final^1^1)" test_expect_success 'final^1^3 not valid' "if git rev-parse --verify final^1^3; then false; else :; fi" -test_expect_failure '--verify start2^1' 'git rev-parse --verify start2^1' +test_expect_success '--verify start2^1' '! git rev-parse --verify start2^1' test_expect_success '--verify start2^0' 'git rev-parse --verify start2^0' test_expect_success 'repack for next test' 'git repack -a -d' diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh index 8a23aaf21b..f46ec93c83 100755 --- a/t/t6300-for-each-ref.sh +++ b/t/t6300-for-each-ref.sh @@ -43,8 +43,8 @@ test_expect_success 'Check atom names are valid' ' test -z "$bad" ' -test_expect_failure 'Check invalid atoms names are errors' ' - git-for-each-ref --format="%(INVALID)" refs/heads +test_expect_success 'Check invalid atoms names are errors' ' + ! git-for-each-ref --format="%(INVALID)" refs/heads ' test_expect_success 'Check format specifiers are ignored in naming date atoms' ' @@ -63,8 +63,8 @@ test_expect_success 'Check valid format specifiers for date fields' ' git-for-each-ref --format="%(authordate:rfc2822)" refs/heads ' -test_expect_failure 'Check invalid format specifiers are errors' ' - git-for-each-ref --format="%(authordate:INVALID)" refs/heads +test_expect_success 'Check invalid format specifiers are errors' ' + ! git-for-each-ref --format="%(authordate:INVALID)" refs/heads ' cat >expected <<\EOF diff --git a/t/t7001-mv.sh b/t/t7001-mv.sh index b730c900b1..fa382c58da 100755 --- a/t/t7001-mv.sh +++ b/t/t7001-mv.sh @@ -78,9 +78,9 @@ test_expect_success \ git diff-tree -r -M --name-status HEAD^ HEAD | \ grep "^R100..*path2/README..*path1/path2/README"' -test_expect_failure \ +test_expect_success \ 'do not move directory over existing directory' \ - 'mkdir path0 && mkdir path0/path2 && git mv path2 path0' + 'mkdir path0 && mkdir path0/path2 && ! git mv path2 path0' test_expect_success \ 'move into "."' \ @@ -118,4 +118,42 @@ test_expect_success "Sergey Vlasov's test case" ' git mv ab a ' +test_expect_success 'absolute pathname' '( + + rm -fr mine && + mkdir mine && + cd mine && + test_create_repo one && + cd one && + mkdir sub && + >sub/file && + git add sub/file && + + git mv sub "$(pwd)/in" && + ! test -d sub && + test -d in && + git ls-files --error-unmatch in/file + + +)' + +test_expect_success 'absolute pathname outside should fail' '( + + rm -fr mine && + mkdir mine && + cd mine && + out=$(pwd) && + test_create_repo one && + cd one && + mkdir sub && + >sub/file && + git add sub/file && + + ! git mv sub "$out/out" && + test -d sub && + ! test -d ../in && + git ls-files --error-unmatch sub/file + +)' + test_done diff --git a/t/t7002-grep.sh b/t/t7002-grep.sh index 68b2b92879..c8b4f65f38 100755 --- a/t/t7002-grep.sh +++ b/t/t7002-grep.sh @@ -107,8 +107,8 @@ do diff expected actual ' - test_expect_failure "grep -c $L (no /dev/null)" ' - git grep -c test $H | grep -q "/dev/null" + test_expect_success "grep -c $L (no /dev/null)" ' + ! git grep -c test $H | grep -q /dev/null ' done diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh index df496a95ff..75cd33bde8 100755 --- a/t/t7004-tag.sh +++ b/t/t7004-tag.sh @@ -26,8 +26,8 @@ test_expect_success 'listing all tags in an empty tree should output nothing' ' test `git-tag | wc -l` -eq 0 ' -test_expect_failure 'looking for a tag in an empty tree should fail' \ - 'tag_exists mytag' +test_expect_success 'looking for a tag in an empty tree should fail' \ + '! (tag_exists mytag)' test_expect_success 'creating a tag in an empty tree should fail' ' ! git-tag mynotag && @@ -83,9 +83,9 @@ test_expect_success \ # special cases for creating tags: -test_expect_failure \ +test_expect_success \ 'trying to create a tag with the name of one existing should fail' \ - 'git tag mytag' + '! git tag mytag' test_expect_success \ 'trying to create a tag with a non-valid name should fail' ' @@ -146,8 +146,8 @@ test_expect_success \ ! tag_exists myhead ' -test_expect_failure 'trying to delete an already deleted tag should fail' \ - 'git-tag -d mytag' +test_expect_success 'trying to delete an already deleted tag should fail' \ + '! git-tag -d mytag' # listing various tags with pattern matching: @@ -265,16 +265,16 @@ test_expect_success \ test $(git rev-parse non-annotated-tag) = $(git rev-parse HEAD) ' -test_expect_failure 'trying to verify an unknown tag should fail' \ - 'git-tag -v unknown-tag' +test_expect_success 'trying to verify an unknown tag should fail' \ + '! git-tag -v unknown-tag' -test_expect_failure \ +test_expect_success \ 'trying to verify a non-annotated and non-signed tag should fail' \ - 'git-tag -v non-annotated-tag' + '! git-tag -v non-annotated-tag' -test_expect_failure \ +test_expect_success \ 'trying to verify many non-annotated or unknown tags, should fail' \ - 'git-tag -v unknown-tag1 non-annotated-tag unknown-tag2' + '! git-tag -v unknown-tag1 non-annotated-tag unknown-tag2' # creating annotated tags: @@ -1027,21 +1027,21 @@ test_expect_success \ # try to sign with bad user.signingkey git config user.signingkey BobTheMouse -test_expect_failure \ +test_expect_success \ 'git-tag -s fails if gpg is misconfigured' \ - 'git tag -s -m tail tag-gpg-failure' + '! git tag -s -m tail tag-gpg-failure' git config --unset user.signingkey # try to verify without gpg: rm -rf gpghome -test_expect_failure \ +test_expect_success \ 'verify signed tag fails when public key is not present' \ - 'git-tag -v signed-tag' + '! git-tag -v signed-tag' -test_expect_failure \ +test_expect_success \ 'git-tag -a fails if tag annotation is empty' ' - GIT_EDITOR=cat git tag -a initial-comment + ! (GIT_EDITOR=cat git tag -a initial-comment) ' test_expect_success \ diff --git a/t/t7010-setup.sh b/t/t7010-setup.sh new file mode 100755 index 0000000000..e809e0e2c9 --- /dev/null +++ b/t/t7010-setup.sh @@ -0,0 +1,164 @@ +#!/bin/sh + +test_description='setup taking and sanitizing funny paths' + +. ./test-lib.sh + +test_expect_success setup ' + + mkdir -p a/b/c a/e && + D=$(pwd) && + >a/b/c/d && + >a/e/f + +' + +test_expect_success 'git add (absolute)' ' + + git add "$D/a/b/c/d" && + git ls-files >current && + echo a/b/c/d >expect && + diff -u expect current + +' + + +test_expect_success 'git add (funny relative)' ' + + rm -f .git/index && + ( + cd a/b && + git add "../e/./f" + ) && + git ls-files >current && + echo a/e/f >expect && + diff -u expect current + +' + +test_expect_success 'git rm (absolute)' ' + + rm -f .git/index && + git add a && + git rm -f --cached "$D/a/b/c/d" && + git ls-files >current && + echo a/e/f >expect && + diff -u expect current + +' + +test_expect_success 'git rm (funny relative)' ' + + rm -f .git/index && + git add a && + ( + cd a/b && + git rm -f --cached "../e/./f" + ) && + git ls-files >current && + echo a/b/c/d >expect && + diff -u expect current + +' + +test_expect_success 'git ls-files (absolute)' ' + + rm -f .git/index && + git add a && + git ls-files "$D/a/e/../b" >current && + echo a/b/c/d >expect && + diff -u expect current + +' + +test_expect_success 'git ls-files (relative #1)' ' + + rm -f .git/index && + git add a && + ( + cd a/b && + git ls-files "../b/c" + ) >current && + echo c/d >expect && + diff -u expect current + +' + +test_expect_success 'git ls-files (relative #2)' ' + + rm -f .git/index && + git add a && + ( + cd a/b && + git ls-files --full-name "../e/f" + ) >current && + echo a/e/f >expect && + diff -u expect current + +' + +test_expect_success 'git ls-files (relative #3)' ' + + rm -f .git/index && + git add a && + ( + cd a/b && + if git ls-files "../e/f" + then + echo Gaah, should have failed + exit 1 + else + : happy + fi + ) + +' + +test_expect_success 'commit using absolute path names' ' + git commit -m "foo" && + echo aa >>a/b/c/d && + git commit -m "aa" "$(pwd)/a/b/c/d" +' + +test_expect_success 'log using absolute path names' ' + echo bb >>a/b/c/d && + git commit -m "bb" $(pwd)/a/b/c/d && + + git log a/b/c/d >f1.txt && + git log "$(pwd)/a/b/c/d" >f2.txt && + diff -u f1.txt f2.txt +' + +test_expect_success 'blame using absolute path names' ' + git blame a/b/c/d >f1.txt && + git blame "$(pwd)/a/b/c/d" >f2.txt && + diff -u f1.txt f2.txt +' + +test_expect_success 'setup deeper work tree' ' + test_create_repo tester +' + +test_expect_success 'add a directory outside the work tree' '( + cd tester && + d1="$(cd .. ; pwd)" && + git add "$d1" +)' + +test_expect_success 'add a file outside the work tree, nasty case 1' '( + cd tester && + f="$(pwd)x" && + echo "$f" && + touch "$f" && + git add "$f" +)' + +test_expect_success 'add a file outside the work tree, nasty case 2' '( + cd tester && + f="$(pwd | sed "s/.$//")x" && + echo "$f" && + touch "$f" && + git add "$f" +)' + +test_done diff --git a/t/t7101-reset.sh b/t/t7101-reset.sh index 66d40430b2..0d9874bfd7 100755 --- a/t/t7101-reset.sh +++ b/t/t7101-reset.sh @@ -36,28 +36,28 @@ test_expect_success \ 'test -d path0 && test -f path0/COPYING' -test_expect_failure \ +test_expect_success \ 'checking lack of path1/path2/COPYING' \ - 'test -f path1/path2/COPYING' + '! test -f path1/path2/COPYING' -test_expect_failure \ +test_expect_success \ 'checking lack of path1/COPYING' \ - 'test -f path1/COPYING' + '! test -f path1/COPYING' -test_expect_failure \ +test_expect_success \ 'checking lack of COPYING' \ - 'test -f COPYING' + '! test -f COPYING' -test_expect_failure \ +test_expect_success \ 'checking checking lack of path1/COPYING-TOO' \ - 'test -f path0/COPYING-TOO' + '! test -f path0/COPYING-TOO' -test_expect_failure \ +test_expect_success \ 'checking lack of path1/path2' \ - 'test -d path1/path2' + '! test -d path1/path2' -test_expect_failure \ +test_expect_success \ 'checking lack of path1' \ - 'test -d path1' + '! test -d path1' test_done diff --git a/t/t7104-reset.sh b/t/t7104-reset.sh new file mode 100755 index 0000000000..f136ee7bb5 --- /dev/null +++ b/t/t7104-reset.sh @@ -0,0 +1,46 @@ +#!/bin/sh + +test_description='reset --hard unmerged' + +. ./test-lib.sh + +test_expect_success setup ' + + mkdir before later && + >before/1 && + >before/2 && + >hello && + >later/3 && + git add before hello later && + git commit -m world && + + H=$(git rev-parse :hello) && + git rm --cached hello && + echo "100644 $H 2 hello" | git update-index --index-info && + + rm -f hello && + mkdir -p hello && + >hello/world && + test "$(git ls-files -o)" = hello/world + +' + +test_expect_success 'reset --hard should restore unmerged ones' ' + + git reset --hard && + git ls-files --error-unmatch before/1 before/2 hello later/3 && + test -f hello + +' + +test_expect_success 'reset --hard did not corrupt index nor cached-tree' ' + + T=$(git write-tree) && + rm -f .git/index && + git add before hello later && + U=$(git write-tree) && + test "$T" = "$U" + +' + +test_done diff --git a/t/t7201-co.sh b/t/t7201-co.sh index 73d8a00e2c..dbf1ace29e 100755 --- a/t/t7201-co.sh +++ b/t/t7201-co.sh @@ -214,6 +214,22 @@ test_expect_success 'checkout to detach HEAD with branchname^' ' fi ' +test_expect_success 'checkout to detach HEAD with :/message' ' + + git checkout -f master && git clean -f && + git checkout ":/Initial" && + H=$(git rev-parse --verify HEAD) && + M=$(git show-ref -s --verify refs/heads/master) && + test "z$H" = "z$M" && + if git symbolic-ref HEAD >/dev/null 2>&1 + then + echo "OOPS, HEAD is still symbolic???" + false + else + : happy + fi +' + test_expect_success 'checkout to detach HEAD with HEAD^0' ' git checkout -f master && git clean -f && diff --git a/t/t7300-clean.sh b/t/t7300-clean.sh index dfd118878f..38403643a6 100755 --- a/t/t7300-clean.sh +++ b/t/t7300-clean.sh @@ -316,4 +316,14 @@ test_expect_success 'core.excludesfile' ' ' +test_expect_success 'removal failure' ' + + mkdir foo && + touch foo/bar && + chmod 0 foo && + ! git clean -f -d + +' +chmod 755 foo + test_done diff --git a/t/t7501-commit.sh b/t/t7501-commit.sh index 55043d102f..361886c3d6 100755 --- a/t/t7501-commit.sh +++ b/t/t7501-commit.sh @@ -17,49 +17,49 @@ test_expect_success \ git-add file && \ git-status | grep 'Initial commit'" -test_expect_failure \ +test_expect_success \ "fail initial amend" \ - "git-commit --amend" + "! git-commit --amend" test_expect_success \ "initial commit" \ "git-commit -m initial" -test_expect_failure \ +test_expect_success \ "invalid options 1" \ - "git-commit -m foo -m bar -F file" + "! git-commit -m foo -m bar -F file" -test_expect_failure \ +test_expect_success \ "invalid options 2" \ - "git-commit -C HEAD -m illegal" + "! git-commit -C HEAD -m illegal" -test_expect_failure \ +test_expect_success \ "using paths with -a" \ "echo King of the bongo >file && - git-commit -m foo -a file" + ! git-commit -m foo -a file" -test_expect_failure \ +test_expect_success \ "using paths with --interactive" \ "echo bong-o-bong >file && - echo 7 | git-commit -m foo --interactive file" + ! echo 7 | git-commit -m foo --interactive file" -test_expect_failure \ +test_expect_success \ "using invalid commit with -C" \ - "git-commit -C bogus" + "! git-commit -C bogus" -test_expect_failure \ +test_expect_success \ "testing nothing to commit" \ - "git-commit -m initial" + "! git-commit -m initial" test_expect_success \ "next commit" \ "echo 'bongo bongo bongo' >file \ git-commit -m next -a" -test_expect_failure \ +test_expect_success \ "commit message from non-existing file" \ "echo 'more bongo: bongo bongo bongo bongo' >file && \ - git-commit -F gah -a" + ! git-commit -F gah -a" # Empty except stray tabs and spaces on a few lines. sed -e 's/@$//' >msg <<EOF @@ -68,9 +68,9 @@ sed -e 's/@$//' >msg <<EOF @ Signed-off-by: hula EOF -test_expect_failure \ +test_expect_success \ "empty commit message" \ - "git-commit -F msg -a" + "! git-commit -F msg -a" test_expect_success \ "commit message from file" \ @@ -88,10 +88,10 @@ test_expect_success \ "amend commit" \ "VISUAL=./editor git-commit --amend" -test_expect_failure \ +test_expect_success \ "passing -m and -F" \ "echo 'enough with the bongos' >file && \ - git-commit -F msg -m amending ." + ! git-commit -F msg -m amending ." test_expect_success \ "using message from other commit" \ diff --git a/t/t7502-commit.sh b/t/t7502-commit.sh index aaf497e6a5..b780fddc08 100755 --- a/t/t7502-commit.sh +++ b/t/t7502-commit.sh @@ -154,4 +154,33 @@ test_expect_success 'cleanup commit messages (strip,-F,-e)' ' ' +pwd=`pwd` +cat >> .git/FAKE_EDITOR << EOF +#! /bin/sh +echo editor started > "$pwd/.git/result" +exit 0 +EOF +chmod +x .git/FAKE_EDITOR + +test_expect_success 'do not fire editor in the presence of conflicts' ' + + git clean + echo f>g + git add g + git commit -myes + git branch second + echo master>g + echo g>h + git add g h + git commit -mmaster + git checkout second + echo second>g + git add g + git commit -msecond + git cherry-pick -n master + echo "editor not started" > .git/result + GIT_EDITOR=`pwd`/.git/FAKE_EDITOR git commit && exit 1 # should fail + test "`cat .git/result`" = "editor not started" +' + test_done diff --git a/t/t7503-pre-commit-hook.sh b/t/t7503-pre-commit-hook.sh index d787cac2f7..2dd5a5e302 100755 --- a/t/t7503-pre-commit-hook.sh +++ b/t/t7503-pre-commit-hook.sh @@ -52,11 +52,11 @@ cat > "$HOOK" <<EOF exit 1 EOF -test_expect_failure 'with failing hook' ' +test_expect_success 'with failing hook' ' echo "another" >> file && git add file && - git commit -m "another" + ! git commit -m "another" ' diff --git a/t/t7504-commit-msg-hook.sh b/t/t7504-commit-msg-hook.sh index 751b11300b..eff36aaee3 100755 --- a/t/t7504-commit-msg-hook.sh +++ b/t/t7504-commit-msg-hook.sh @@ -98,20 +98,20 @@ cat > "$HOOK" <<EOF exit 1 EOF -test_expect_failure 'with failing hook' ' +test_expect_success 'with failing hook' ' echo "another" >> file && git add file && - git commit -m "another" + ! git commit -m "another" ' -test_expect_failure 'with failing hook (editor)' ' +test_expect_success 'with failing hook (editor)' ' echo "more another" >> file && git add file && echo "more another" > FAKE_MSG && - GIT_EDITOR="$FAKE_EDITOR" git commit + ! (GIT_EDITOR="$FAKE_EDITOR" git commit) ' diff --git a/t/t7505-prepare-commit-msg-hook.sh b/t/t7505-prepare-commit-msg-hook.sh new file mode 100755 index 0000000000..7ddec99a64 --- /dev/null +++ b/t/t7505-prepare-commit-msg-hook.sh @@ -0,0 +1,155 @@ +#!/bin/sh + +test_description='prepare-commit-msg hook' + +. ./test-lib.sh + +test_expect_success 'with no hook' ' + + echo "foo" > file && + git add file && + git commit -m "first" + +' + +# set up fake editor for interactive editing +cat > fake-editor <<'EOF' +#!/bin/sh +exit 0 +EOF +chmod +x fake-editor +FAKE_EDITOR="$(pwd)/fake-editor" +export FAKE_EDITOR + +# now install hook that always succeeds and adds a message +HOOKDIR="$(git rev-parse --git-dir)/hooks" +HOOK="$HOOKDIR/prepare-commit-msg" +mkdir -p "$HOOKDIR" +cat > "$HOOK" <<'EOF' +#!/bin/sh +if test "$2" = commit; then + source=$(git-rev-parse "$3") +else + source=${2-default} +fi +if test "$GIT_EDITOR" = :; then + sed -e "1s/.*/$source (no editor)/" "$1" > msg.tmp +else + sed -e "1s/.*/$source/" "$1" > msg.tmp +fi +mv msg.tmp "$1" +exit 0 +EOF +chmod +x "$HOOK" + +echo dummy template > "$(git rev-parse --git-dir)/template" + +test_expect_success 'with hook (-m)' ' + + echo "more" >> file && + git add file && + git commit -m "more" && + test "`git log -1 --pretty=format:%s`" = "message (no editor)" + +' + +test_expect_success 'with hook (-m editor)' ' + + echo "more" >> file && + git add file && + GIT_EDITOR="$FAKE_EDITOR" git commit -e -m "more more" && + test "`git log -1 --pretty=format:%s`" = message + +' + +test_expect_success 'with hook (-t)' ' + + echo "more" >> file && + git add file && + git commit -t "$(git rev-parse --git-dir)/template" && + test "`git log -1 --pretty=format:%s`" = template + +' + +test_expect_success 'with hook (-F)' ' + + echo "more" >> file && + git add file && + (echo more | git commit -F -) && + test "`git log -1 --pretty=format:%s`" = "message (no editor)" + +' + +test_expect_success 'with hook (-F editor)' ' + + echo "more" >> file && + git add file && + (echo more more | GIT_EDITOR="$FAKE_EDITOR" git commit -e -F -) && + test "`git log -1 --pretty=format:%s`" = message + +' + +test_expect_success 'with hook (-C)' ' + + head=`git rev-parse HEAD` && + echo "more" >> file && + git add file && + git commit -C $head && + test "`git log -1 --pretty=format:%s`" = "$head (no editor)" + +' + +test_expect_success 'with hook (editor)' ' + + echo "more more" >> file && + git add file && + GIT_EDITOR="$FAKE_EDITOR" git commit && + test "`git log -1 --pretty=format:%s`" = default + +' + +test_expect_success 'with hook (--amend)' ' + + head=`git rev-parse HEAD` && + echo "more" >> file && + git add file && + GIT_EDITOR="$FAKE_EDITOR" git commit --amend && + test "`git log -1 --pretty=format:%s`" = "$head" + +' + +test_expect_success 'with hook (-c)' ' + + head=`git rev-parse HEAD` && + echo "more" >> file && + git add file && + GIT_EDITOR="$FAKE_EDITOR" git commit -c $head && + test "`git log -1 --pretty=format:%s`" = "$head" + +' + +cat > "$HOOK" <<'EOF' +#!/bin/sh +exit 1 +EOF + +test_expect_success 'with failing hook' ' + + head=`git rev-parse HEAD` && + echo "more" >> file && + git add file && + ! GIT_EDITOR="$FAKE_EDITOR" git commit -c $head + +' + +test_expect_success 'with failing hook (--no-verify)' ' + + head=`git rev-parse HEAD` && + echo "more" >> file && + git add file && + ! GIT_EDITOR="$FAKE_EDITOR" git commit --no-verify -c $head + +' + + +test_done diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh index 08f7c3d8d7..2efaed441d 100755 --- a/t/t9001-send-email.sh +++ b/t/t9001-send-email.sh @@ -108,4 +108,25 @@ test_expect_success 'allow long lines with --no-validate' ' 2>errors ' +test_expect_success 'Invalid In-Reply-To' ' + git send-email \ + --from="Example <nobody@example.com>" \ + --to=nobody@example.com \ + --in-reply-to=" " \ + --smtp-server="$(pwd)/fake.sendmail" \ + $patches + 2>errors + ! grep "^In-Reply-To: < *>" msgtxt +' + +test_expect_success 'Valid In-Reply-To when prompting' ' + (echo "From Example <from@example.com>" + echo "To Example <to@example.com>" + echo "" + ) | env GIT_SEND_EMAIL_NOTTY=1 git send-email \ + --smtp-server="$(pwd)/fake.sendmail" \ + $patches 2>errors && + ! grep "^In-Reply-To: < *>" msgtxt +' + test_done diff --git a/t/t9100-git-svn-basic.sh b/t/t9100-git-svn-basic.sh index 614cf50d19..4e24ab3a7d 100755 --- a/t/t9100-git-svn-basic.sh +++ b/t/t9100-git-svn-basic.sh @@ -56,19 +56,19 @@ test_expect_success "$name" " name='detect node change from file to directory #1' -test_expect_failure "$name" " +test_expect_success "$name" " mkdir dir/new_file && mv dir/file dir/new_file/file && mv dir/new_file dir/file && git update-index --remove dir/file && git update-index --add dir/file/file && - git commit -m '$name' && - git-svn set-tree --find-copies-harder --rmdir \ + git commit -m '$name' && + ! git-svn set-tree --find-copies-harder --rmdir \ remotes/git-svn..mybranch" || true name='detect node change from directory to file #1' -test_expect_failure "$name" " +test_expect_success "$name" " rm -rf dir '$GIT_DIR'/index && git checkout -f -b mybranch2 remotes/git-svn && mv bar/zzz zzz && @@ -77,12 +77,12 @@ test_expect_failure "$name" " git update-index --remove -- bar/zzz && git update-index --add -- bar && git commit -m '$name' && - git-svn set-tree --find-copies-harder --rmdir \ + ! git-svn set-tree --find-copies-harder --rmdir \ remotes/git-svn..mybranch2" || true name='detect node change from file to directory #2' -test_expect_failure "$name" " +test_expect_success "$name" " rm -f '$GIT_DIR'/index && git checkout -f -b mybranch3 remotes/git-svn && rm bar/zzz && @@ -91,12 +91,12 @@ test_expect_failure "$name" " echo yyy > bar/zzz/yyy && git update-index --add bar/zzz/yyy && git commit -m '$name' && - git-svn set-tree --find-copies-harder --rmdir \ + ! git-svn set-tree --find-copies-harder --rmdir \ remotes/git-svn..mybranch3" || true name='detect node change from directory to file #2' -test_expect_failure "$name" " +test_expect_success "$name" " rm -f '$GIT_DIR'/index && git checkout -f -b mybranch4 remotes/git-svn && rm -rf dir && @@ -105,7 +105,7 @@ test_expect_failure "$name" " echo asdf > dir && git update-index --add -- dir && git commit -m '$name' && - git-svn set-tree --find-copies-harder --rmdir \ + ! git-svn set-tree --find-copies-harder --rmdir \ remotes/git-svn..mybranch4" || true @@ -213,18 +213,18 @@ EOF test_expect_success "$name" "git diff a expected" -test_expect_failure 'exit if remote refs are ambigious' " +test_expect_success 'exit if remote refs are ambigious' " git config --add svn-remote.svn.fetch \ bar:refs/remotes/git-svn && - git-svn migrate - " + ! git-svn migrate +" -test_expect_failure 'exit if init-ing a would clobber a URL' " +test_expect_success 'exit if init-ing a would clobber a URL' " svnadmin create ${PWD}/svnrepo2 && svn mkdir -m 'mkdir bar' ${svnrepo}2/bar && git config --unset svn-remote.svn.fetch \ '^bar:refs/remotes/git-svn$' && - git-svn init ${svnrepo}2/bar + ! git-svn init ${svnrepo}2/bar " test_expect_success \ diff --git a/t/t9106-git-svn-commit-diff-clobber.sh b/t/t9106-git-svn-commit-diff-clobber.sh index 79b7968eaf..f74ab1269e 100755 --- a/t/t9106-git-svn-commit-diff-clobber.sh +++ b/t/t9106-git-svn-commit-diff-clobber.sh @@ -24,11 +24,11 @@ test_expect_success 'commit change from svn side' " rm -rf t.svn " -test_expect_failure 'commit conflicting change from git' " +test_expect_success 'commit conflicting change from git' " echo second line from git >> file && git commit -a -m 'second line from git' && - git-svn commit-diff -r1 HEAD~1 HEAD $svnrepo - " || true + ! git-svn commit-diff -r1 HEAD~1 HEAD $svnrepo +" test_expect_success 'commit complementing change from git' " git reset --hard HEAD~1 && @@ -39,7 +39,7 @@ test_expect_success 'commit complementing change from git' " git-svn commit-diff -r2 HEAD~1 HEAD $svnrepo " -test_expect_failure 'dcommit fails to commit because of conflict' " +test_expect_success 'dcommit fails to commit because of conflict' " git-svn init $svnrepo && git-svn fetch && git reset --hard refs/remotes/git-svn && @@ -52,8 +52,8 @@ test_expect_failure 'dcommit fails to commit because of conflict' " rm -rf t.svn && echo 'fourth line from git' >> file && git commit -a -m 'fourth line from git' && - git-svn dcommit - " || true + ! git-svn dcommit + " test_expect_success 'dcommit does the svn equivalent of an index merge' " git reset --hard refs/remotes/git-svn && @@ -76,15 +76,15 @@ test_expect_success 'commit another change from svn side' " rm -rf t.svn " -test_expect_failure 'multiple dcommit from git-svn will not clobber svn' " +test_expect_success 'multiple dcommit from git-svn will not clobber svn' " git reset --hard refs/remotes/git-svn && echo new file >> new-file && git update-index --add new-file && git commit -a -m 'new file' && echo clobber > file && git commit -a -m 'clobber' && - git svn dcommit - " || true + ! git svn dcommit + " test_expect_success 'check that rebase really failed' 'test -d .dotest' diff --git a/t/t9106-git-svn-dcommit-clobber-series.sh b/t/t9106-git-svn-dcommit-clobber-series.sh index 745254665d..ca8a00ed0a 100755 --- a/t/t9106-git-svn-dcommit-clobber-series.sh +++ b/t/t9106-git-svn-dcommit-clobber-series.sh @@ -54,10 +54,10 @@ test_expect_success 'change file but in unrelated area' " test x\"\`sed -n -e 61p < file\`\" = x6611 " -test_expect_failure 'attempt to dcommit with a dirty index' ' +test_expect_success 'attempt to dcommit with a dirty index' ' echo foo >>file && git add file && - git svn dcommit + ! git svn dcommit ' test_done diff --git a/t/t9200-git-cvsexportcommit.sh b/t/t9200-git-cvsexportcommit.sh index a15222ced4..58c59ed5ae 100755 --- a/t/t9200-git-cvsexportcommit.sh +++ b/t/t9200-git-cvsexportcommit.sh @@ -2,7 +2,7 @@ # # Copyright (c) Robin Rosenberg # -test_description='CVS export comit. ' +test_description='Test export of commits to CVS' . ./test-lib.sh @@ -246,4 +246,55 @@ test_expect_success \ ;; esac +test_expect_success '-w option should work with relative GIT_DIR' ' + mkdir W && + echo foobar >W/file1.txt && + echo bazzle >W/file2.txt && + git add W/file1.txt && + git add W/file2.txt && + git commit -m "More updates" && + id=$(git rev-list --max-count=1 HEAD) && + (cd "$GIT_DIR" && + GIT_DIR=. git cvsexportcommit -w "$CVSWORK" -c $id && + check_entries "$CVSWORK/W" "file1.txt/1.1/|file2.txt/1.1/" && + diff -u "$CVSWORK/W/file1.txt" ../W/file1.txt && + diff -u "$CVSWORK/W/file2.txt" ../W/file2.txt + ) +' + +test_expect_success 'check files before directories' ' + + echo Notes > release-notes && + git add release-notes && + git commit -m "Add release notes" release-notes && + id=$(git rev-parse HEAD) && + git cvsexportcommit -w "$CVSWORK" -c $id && + + echo new > DS && + echo new > E/DS && + echo modified > release-notes && + git add DS E/DS release-notes && + git commit -m "Add two files with the same basename" && + id=$(git rev-parse HEAD) && + git cvsexportcommit -w "$CVSWORK" -c $id && + check_entries "$CVSWORK/E" "DS/1.1/|newfile5.txt/1.1/" && + check_entries "$CVSWORK" "DS/1.1/|release-notes/1.2/" && + diff -u "$CVSWORK/DS" DS && + diff -u "$CVSWORK/E/DS" E/DS && + diff -u "$CVSWORK/release-notes" release-notes + +' + +test_expect_success 'commit a file with leading spaces in the name' ' + + echo space > " space" && + git add " space" && + git commit -m "Add a file with a leading space" && + id=$(git rev-parse HEAD) && + git cvsexportcommit -w "$CVSWORK" -c $id && + check_entries "$CVSWORK" " space/1.1/|DS/1.1/|release-notes/1.2/" && + diff -u "$CVSWORK/ space" " space" + +' + test_done diff --git a/t/t9300-fast-import.sh b/t/t9300-fast-import.sh index 0595041af5..cceedbb2b7 100755 --- a/t/t9300-fast-import.sh +++ b/t/t9300-fast-import.sh @@ -165,9 +165,9 @@ from refs/heads/master M 755 0000000000000000000000000000000000000001 zero1 INPUT_END -test_expect_failure \ - 'B: fail on invalid blob sha1' \ - 'git-fast-import <input' +test_expect_success 'B: fail on invalid blob sha1' ' + ! git-fast-import <input +' rm -f .git/objects/pack_* .git/objects/index_* cat >input <<INPUT_END @@ -180,9 +180,9 @@ COMMIT from refs/heads/master INPUT_END -test_expect_failure \ - 'B: fail on invalid branch name ".badbranchname"' \ - 'git-fast-import <input' +test_expect_success 'B: fail on invalid branch name ".badbranchname"' ' + ! git-fast-import <input +' rm -f .git/objects/pack_* .git/objects/index_* cat >input <<INPUT_END @@ -195,9 +195,9 @@ COMMIT from refs/heads/master INPUT_END -test_expect_failure \ - 'B: fail on invalid branch name "bad[branch]name"' \ - 'git-fast-import <input' +test_expect_success 'B: fail on invalid branch name "bad[branch]name"' ' + ! git-fast-import <input +' rm -f .git/objects/pack_* .git/objects/index_* cat >input <<INPUT_END @@ -339,9 +339,9 @@ COMMIT from refs/heads/branch^0 INPUT_END -test_expect_failure \ - 'E: rfc2822 date, --date-format=raw' \ - 'git-fast-import --date-format=raw <input' +test_expect_success 'E: rfc2822 date, --date-format=raw' ' + ! git-fast-import --date-format=raw <input +' test_expect_success \ 'E: rfc2822 date, --date-format=rfc2822' \ 'git-fast-import --date-format=rfc2822 <input' diff --git a/t/t9400-git-cvsserver-server.sh b/t/t9400-git-cvsserver-server.sh index 75d1ce433d..0a20971ebb 100755 --- a/t/t9400-git-cvsserver-server.sh +++ b/t/t9400-git-cvsserver-server.sh @@ -156,15 +156,19 @@ test_expect_success 'req_Root (strict paths)' \ 'cat request-anonymous | git-cvsserver --strict-paths pserver $SERVERDIR >log 2>&1 && tail -n1 log | grep -q "^I LOVE YOU$"' -test_expect_failure 'req_Root failure (strict-paths)' \ - 'cat request-anonymous | git-cvsserver --strict-paths pserver $WORKDIR >log 2>&1' +test_expect_success 'req_Root failure (strict-paths)' ' + ! cat request-anonymous | + git-cvsserver --strict-paths pserver $WORKDIR >log 2>&1 +' test_expect_success 'req_Root (w/o strict-paths)' \ 'cat request-anonymous | git-cvsserver pserver $WORKDIR/ >log 2>&1 && tail -n1 log | grep -q "^I LOVE YOU$"' -test_expect_failure 'req_Root failure (w/o strict-paths)' \ - 'cat request-anonymous | git-cvsserver pserver $WORKDIR/gitcvs >log 2>&1' +test_expect_success 'req_Root failure (w/o strict-paths)' ' + ! cat request-anonymous | + git-cvsserver pserver $WORKDIR/gitcvs >log 2>&1 +' cat >request-base <<EOF BEGIN AUTH REQUEST @@ -179,8 +183,10 @@ test_expect_success 'req_Root (base-path)' \ 'cat request-base | git-cvsserver --strict-paths --base-path $WORKDIR/ pserver $SERVERDIR >log 2>&1 && tail -n1 log | grep -q "^I LOVE YOU$"' -test_expect_failure 'req_Root failure (base-path)' \ - 'cat request-anonymous | git-cvsserver --strict-paths --base-path $WORKDIR pserver $SERVERDIR >log 2>&1' +test_expect_success 'req_Root failure (base-path)' ' + ! cat request-anonymous | + git-cvsserver --strict-paths --base-path $WORKDIR pserver $SERVERDIR >log 2>&1 +' GIT_DIR="$SERVERDIR" git config --bool gitcvs.enabled false || exit 1 @@ -188,9 +194,8 @@ test_expect_success 'req_Root (export-all)' \ 'cat request-anonymous | git-cvsserver --export-all pserver $WORKDIR >log 2>&1 && tail -n1 log | grep -q "^I LOVE YOU$"' -test_expect_failure 'req_Root failure (export-all w/o whitelist)' \ - 'cat request-anonymous | git-cvsserver --export-all pserver >log 2>&1 || - false' +test_expect_success 'req_Root failure (export-all w/o whitelist)' \ + '! (cat request-anonymous | git-cvsserver --export-all pserver >log 2>&1 || false)' test_expect_success 'req_Root (everything together)' \ 'cat request-base | git-cvsserver --export-all --strict-paths --base-path $WORKDIR/ pserver $SERVERDIR >log 2>&1 && @@ -290,15 +295,16 @@ test_expect_success 'cvs update (update existing file)' \ cd "$WORKDIR" #TODO: cvsserver doesn't support update w/o -d -test_expect_failure "cvs update w/o -d doesn't create subdir (TODO)" \ - 'mkdir test && +test_expect_failure "cvs update w/o -d doesn't create subdir (TODO)" ' + mkdir test && echo >test/empty && git add test && git commit -q -m "Single Subdirectory" && git push gitcvs.git >/dev/null && cd cvswork && GIT_CONFIG="$git_config" cvs -Q update && - test ! -d test' + test ! -d test +' cd "$WORKDIR" test_expect_success 'cvs update (subdirectories)' \ diff --git a/t/test-lib.sh b/t/test-lib.sh index 142540e1b1..83889c4f46 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -139,6 +139,8 @@ fi test_failure=0 test_count=0 +test_fixed=0 +test_broken=0 trap 'echo >&5 "FATAL: Unexpected exit with code $?"; exit 1' exit @@ -171,6 +173,17 @@ test_failure_ () { test "$immediate" = "" || { trap - exit; exit 1; } } +test_known_broken_ok_ () { + test_count=$(expr "$test_count" + 1) + test_fixed=$(($test_fixed+1)) + say_color "" " FIXED $test_count: $@" +} + +test_known_broken_failure_ () { + test_count=$(expr "$test_count" + 1) + test_broken=$(($test_broken+1)) + say_color skip " still broken $test_count: $@" +} test_debug () { test "$debug" = "" || eval "$1" @@ -211,13 +224,13 @@ test_expect_failure () { error "bug in the test script: not 2 parameters to test-expect-failure" if ! test_skip "$@" then - say >&3 "expecting failure: $2" + say >&3 "checking known breakage: $2" test_run_ "$2" - if [ "$?" = 0 -a "$eval_ret" != 0 -a "$eval_ret" -lt 129 ] + if [ "$?" = 0 -a "$eval_ret" = 0 ] then - test_ok_ "$1" + test_known_broken_ok_ "$1" else - test_failure_ "$@" + test_known_broken_failure_ "$1" fi fi echo >&3 "" @@ -274,6 +287,18 @@ test_create_repo () { test_done () { trap - exit + + if test "$test_fixed" != 0 + then + say_color pass "fixed $test_fixed known breakage(s)" + fi + if test "$test_broken" != 0 + then + say_color error "still have $test_broken known breakage(s)" + msg="remaining $(($test_count-$test_broken)) test(s)" + else + msg="$test_count test(s)" + fi case "$test_failure" in 0) # We could: @@ -284,11 +309,11 @@ test_done () { # The Makefile provided will clean this test area so # we will leave things as they are. - say_color pass "passed all $test_count test(s)" + say_color pass "passed all $msg" exit 0 ;; *) - say_color error "failed $test_failure among $test_count test(s)" + say_color error "failed $test_failure among $msg" exit 1 ;; esac @@ -299,8 +324,11 @@ test_done () { PATH=$(pwd)/..:$PATH GIT_EXEC_PATH=$(pwd)/.. GIT_TEMPLATE_DIR=$(pwd)/../templates/blt -GIT_CONFIG=.git/config -export PATH GIT_EXEC_PATH GIT_TEMPLATE_DIR GIT_CONFIG +unset GIT_CONFIG +unset GIT_CONFIG_LOCAL +GIT_CONFIG_NOSYSTEM=1 +GIT_CONFIG_NOGLOBAL=1 +export PATH GIT_EXEC_PATH GIT_TEMPLATE_DIR GIT_CONFIG_NOSYSTEM GIT_CONFIG_NOGLOBAL GITPERLLIB=$(pwd)/../perl/blib/lib:$(pwd)/../perl/blib/arch/auto/Git export GITPERLLIB @@ -9,7 +9,10 @@ const char *tag_type = "tag"; struct object *deref_tag(struct object *o, const char *warn, int warnlen) { while (o && o->type == OBJ_TAG) - o = parse_object(((struct tag *)o)->tagged->sha1); + if (((struct tag *)o)->tagged) + o = parse_object(((struct tag *)o)->tagged->sha1); + else + o = NULL; if (!o && warn) { if (!warnlen) warnlen = strlen(warn); diff --git a/templates/hooks--commit-msg b/templates/hooks--commit-msg index c5cdb9d7ee..4ef86eb244 100644 --- a/templates/hooks--commit-msg +++ b/templates/hooks--commit-msg @@ -9,6 +9,9 @@ # To enable this hook, make this file executable. # Uncomment the below to add a Signed-off-by line to the message. +# Doing this in a hook is a bad idea in general, but the prepare-commit-msg +# hook is more suited to it. +# # SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') # grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" diff --git a/templates/hooks--prepare-commit-msg b/templates/hooks--prepare-commit-msg new file mode 100644 index 0000000000..ff0f42a1d9 --- /dev/null +++ b/templates/hooks--prepare-commit-msg @@ -0,0 +1,36 @@ +#!/bin/sh +# +# An example hook script to prepare the commit log message. +# Called by git-commit with the name of the file that has the +# commit message, followed by the description of the commit +# message's source. The hook's purpose is to edit the commit +# message file. If the hook fails with a non-zero status, +# the commit is aborted. +# +# To enable this hook, make this file executable. + +# This hook includes three examples. The first comments out the +# "Conflicts:" part of a merge commit. +# +# The second includes the output of "git diff --name-status -r" +# into the message, just before the "git status" output. It is +# commented because it doesn't cope with --amend or with squashed +# commits. +# +# The third example adds a Signed-off-by line to the message, that can +# still be edited. This is rarely a good idea. + +case "$2 $3" in + merge) + sed -i '/^Conflicts:/,/#/!b;s/^/# &/;s/^# #/#/' "$1" ;; + +# ""|template) +# perl -i -pe ' +# print "\n" . `git diff --cached --name-status -r` +# if /^#/ && $first++ == 0' "$1" ;; + + *) ;; +esac + +# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') +# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" diff --git a/transport.c b/transport.c index 497f853721..397983d115 100644 --- a/transport.c +++ b/transport.c @@ -562,6 +562,8 @@ struct git_transport_data { unsigned thin : 1; unsigned keep : 1; int depth; + struct child_process *conn; + int fd[2]; const char *uploadpack; const char *receivepack; }; @@ -592,20 +594,20 @@ static int set_git_option(struct transport *connection, return 1; } +static int connect_setup(struct transport *transport) +{ + struct git_transport_data *data = transport->data; + data->conn = git_connect(data->fd, transport->url, data->uploadpack, 0); + return 0; +} + static struct ref *get_refs_via_connect(struct transport *transport) { struct git_transport_data *data = transport->data; struct ref *refs; - int fd[2]; - char *dest = xstrdup(transport->url); - struct child_process *conn = git_connect(fd, dest, data->uploadpack, 0); - get_remote_heads(fd[0], &refs, 0, NULL, 0); - packet_flush(fd[1]); - - finish_connect(conn); - - free(dest); + connect_setup(transport); + get_remote_heads(data->fd[0], &refs, 0, NULL, 0); return refs; } @@ -616,7 +618,7 @@ static int fetch_refs_via_pack(struct transport *transport, struct git_transport_data *data = transport->data; char **heads = xmalloc(nr_heads * sizeof(*heads)); char **origh = xmalloc(nr_heads * sizeof(*origh)); - struct ref *refs; + const struct ref *refs; char *dest = xstrdup(transport->url); struct fetch_pack_args args; int i; @@ -631,13 +633,27 @@ static int fetch_refs_via_pack(struct transport *transport, for (i = 0; i < nr_heads; i++) origh[i] = heads[i] = xstrdup(to_fetch[i]->name); - refs = fetch_pack(&args, dest, nr_heads, heads, &transport->pack_lockfile); + + refs = transport_get_remote_refs(transport); + if (!data->conn) { + struct ref *refs_tmp; + connect_setup(transport); + get_remote_heads(data->fd[0], &refs_tmp, 0, NULL, 0); + free_refs(refs_tmp); + } + + refs = fetch_pack(&args, data->fd, data->conn, transport->remote_refs, + dest, nr_heads, heads, &transport->pack_lockfile); + close(data->fd[0]); + close(data->fd[1]); + if (finish_connect(data->conn)) + refs = NULL; + data->conn = NULL; for (i = 0; i < nr_heads; i++) free(origh[i]); free(origh); free(heads); - free_refs(refs); free(dest); return (refs ? 0 : -1); } @@ -660,7 +676,15 @@ static int git_transport_push(struct transport *transport, int refspec_nr, const static int disconnect_git(struct transport *transport) { - free(transport->data); + struct git_transport_data *data = transport->data; + if (data->conn) { + packet_flush(data->fd[1]); + close(data->fd[0]); + close(data->fd[1]); + finish_connect(data->conn); + } + + free(data); return 0; } @@ -720,6 +744,7 @@ struct transport *transport_get(struct remote *remote, const char *url) ret->disconnect = disconnect_git; data->thin = 1; + data->conn = NULL; data->uploadpack = "git-upload-pack"; if (remote && remote->uploadpack) data->uploadpack = remote->uploadpack; @@ -142,8 +142,8 @@ static int cmp_cache_name_compare(const void *a_, const void *b_) ce1 = *((const struct cache_entry **)a_); ce2 = *((const struct cache_entry **)b_); - return cache_name_compare(ce1->name, ntohs(ce1->ce_flags), - ce2->name, ntohs(ce2->ce_flags)); + return cache_name_compare(ce1->name, ce1->ce_flags, + ce2->name, ce2->ce_flags); } int read_tree(struct tree *tree, int stage, const char **match) diff --git a/unpack-trees.c b/unpack-trees.c index aa2513ed79..ec558f9005 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -289,7 +289,6 @@ static struct checkout state; static void check_updates(struct cache_entry **src, int nr, struct unpack_trees_options *o) { - unsigned short mask = htons(CE_UPDATE); unsigned cnt = 0, total = 0; struct progress *progress = NULL; char last_symlink[PATH_MAX]; @@ -297,7 +296,7 @@ static void check_updates(struct cache_entry **src, int nr, if (o->update && o->verbose_update) { for (total = cnt = 0; cnt < nr; cnt++) { struct cache_entry *ce = src[cnt]; - if (!ce->ce_mode || ce->ce_flags & mask) + if (ce->ce_flags & (CE_UPDATE | CE_REMOVE)) total++; } @@ -310,15 +309,15 @@ static void check_updates(struct cache_entry **src, int nr, while (nr--) { struct cache_entry *ce = *src++; - if (!ce->ce_mode || ce->ce_flags & mask) + if (ce->ce_flags & (CE_UPDATE | CE_REMOVE)) display_progress(progress, ++cnt); - if (!ce->ce_mode) { + if (ce->ce_flags & CE_REMOVE) { if (o->update) unlink_entry(ce->name, last_symlink); continue; } - if (ce->ce_flags & mask) { - ce->ce_flags &= ~mask; + if (ce->ce_flags & CE_UPDATE) { + ce->ce_flags &= ~CE_UPDATE; if (o->update) { checkout_entry(ce, &state, NULL); *last_symlink = '\0'; @@ -408,7 +407,7 @@ static void verify_uptodate(struct cache_entry *ce, * submodules that are marked to be automatically * checked out. */ - if (S_ISGITLINK(ntohl(ce->ce_mode))) + if (S_ISGITLINK(ce->ce_mode)) return; errno = 0; } @@ -450,7 +449,7 @@ static int verify_clean_subdirectory(struct cache_entry *ce, const char *action, int cnt = 0; unsigned char sha1[20]; - if (S_ISGITLINK(ntohl(ce->ce_mode)) && + if (S_ISGITLINK(ce->ce_mode) && resolve_gitlink_ref(ce->name, "HEAD", sha1) == 0) { /* If we are not going to update the submodule, then * we don't care. @@ -481,7 +480,7 @@ static int verify_clean_subdirectory(struct cache_entry *ce, const char *action, */ if (!ce_stage(ce)) { verify_uptodate(ce, o); - ce->ce_mode = 0; + ce->ce_flags |= CE_REMOVE; } cnt++; } @@ -522,8 +521,9 @@ static void verify_absent(struct cache_entry *ce, const char *action, if (!lstat(ce->name, &st)) { int cnt; + int dtype = ce_to_dtype(ce); - if (o->dir && excluded(o->dir, ce->name)) + if (o->dir && excluded(o->dir, ce->name, &dtype)) /* * ce->name is explicitly excluded, so it is Ok to * overwrite it. @@ -568,7 +568,7 @@ static void verify_absent(struct cache_entry *ce, const char *action, cnt = cache_name_pos(ce->name, strlen(ce->name)); if (0 <= cnt) { struct cache_entry *ce = active_cache[cnt]; - if (!ce_stage(ce) && !ce->ce_mode) + if (ce->ce_flags & CE_REMOVE) return; } @@ -580,7 +580,7 @@ static void verify_absent(struct cache_entry *ce, const char *action, static int merged_entry(struct cache_entry *merge, struct cache_entry *old, struct unpack_trees_options *o) { - merge->ce_flags |= htons(CE_UPDATE); + merge->ce_flags |= CE_UPDATE; if (old) { /* * See if we can re-use the old CE directly? @@ -601,7 +601,7 @@ static int merged_entry(struct cache_entry *merge, struct cache_entry *old, invalidate_ce_path(merge); } - merge->ce_flags &= ~htons(CE_STAGEMASK); + merge->ce_flags &= ~CE_STAGEMASK; add_cache_entry(merge, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE); return 1; } @@ -613,7 +613,7 @@ static int deleted_entry(struct cache_entry *ce, struct cache_entry *old, verify_uptodate(old, o); else verify_absent(ce, "removed", o); - ce->ce_mode = 0; + ce->ce_flags |= CE_REMOVE; add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE); invalidate_ce_path(ce); return 1; @@ -634,7 +634,7 @@ static void show_stage_entry(FILE *o, else fprintf(o, "%s%06o %s %d\t%s\n", label, - ntohl(ce->ce_mode), + ce->ce_mode, sha1_to_hex(ce->sha1), ce_stage(ce), ce->name); @@ -920,7 +920,7 @@ int oneway_merge(struct cache_entry **src, struct stat st; if (lstat(old->name, &st) || ce_match_stat(old, &st, CE_MATCH_IGNORE_VALID)) - old->ce_flags |= htons(CE_UPDATE); + old->ce_flags |= CE_UPDATE; } return keep_entry(old, o); } diff --git a/unpack-trees.h b/unpack-trees.h index 5517faafad..197a0044aa 100644 --- a/unpack-trees.h +++ b/unpack-trees.h @@ -25,6 +25,7 @@ struct unpack_trees_options { int merge_size; struct cache_entry *df_conflict_entry; + void *unpack_data; }; extern int unpack_trees(unsigned n, struct tree_desc *t, diff --git a/upload-pack.c b/upload-pack.c index 51e3ec49d1..b26d05331d 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -129,7 +129,8 @@ static int do_rev_list(int fd, void *create_full_pack) } setup_revisions(0, NULL, &revs, NULL); } - prepare_revision_walk(&revs); + if (prepare_revision_walk(&revs)) + die("revision walk setup failed"); mark_edges_uninteresting(revs.commits, &revs, show_edge); traverse_commit_list(&revs, show_commit, show_object); return 0; @@ -533,7 +534,8 @@ static void receive_needs(void) /* make sure the real parents are parsed */ unregister_shallow(object->sha1); object->parsed = 0; - parse_commit((struct commit *)object); + if (parse_commit((struct commit *)object)) + die("invalid commit"); parents = ((struct commit *)object)->parents; while (parents) { add_object_array(&parents->item->object, @@ -575,7 +577,8 @@ static int send_ref(const char *refname, const unsigned char *sha1, int flag, vo } if (o->type == OBJ_TAG) { o = deref_tag(o, refname, 0); - packet_write(1, "%s %s^{}\n", sha1_to_hex(o->sha1), refname); + if (o) + packet_write(1, "%s %s^{}\n", sha1_to_hex(o->sha1), refname); } return 0; } diff --git a/wt-status.c b/wt-status.c index 991e373785..32d780af1e 100644 --- a/wt-status.c +++ b/wt-status.c @@ -9,7 +9,7 @@ #include "diffcore.h" int wt_status_relative_paths = 1; -int wt_status_use_color = 0; +int wt_status_use_color = -1; static char wt_status_colors[][COLOR_MAXLEN] = { "", /* WT_STATUS_HEADER: normal */ "\033[32m", /* WT_STATUS_UPDATED: green */ @@ -40,7 +40,7 @@ static int parse_status_slot(const char *var, int offset) static const char* color(int slot) { - return wt_status_use_color ? wt_status_colors[slot] : ""; + return wt_status_use_color > 0 ? wt_status_colors[slot] : ""; } void wt_status_prepare(struct wt_status *s) @@ -217,19 +217,12 @@ static void wt_status_print_changed_cb(struct diff_queue_struct *q, wt_status_print_trailer(s); } -static void wt_read_cache(struct wt_status *s) -{ - discard_cache(); - read_cache_from(s->index_file); -} - static void wt_status_print_initial(struct wt_status *s) { int i; struct strbuf buf; strbuf_init(&buf, 0); - wt_read_cache(s); if (active_nr) { s->commitable = 1; wt_status_print_cached_header(s); @@ -256,7 +249,6 @@ static void wt_status_print_updated(struct wt_status *s) rev.diffopt.detect_rename = 1; rev.diffopt.rename_limit = 100; rev.diffopt.break_opt = 0; - wt_read_cache(s); run_diff_index(&rev, 1); } @@ -268,7 +260,6 @@ static void wt_status_print_changed(struct wt_status *s) rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK; rev.diffopt.format_callback = wt_status_print_changed_cb; rev.diffopt.format_callback_data = s; - wt_read_cache(s); run_diff_files(&rev, 0); } @@ -335,7 +326,6 @@ static void wt_status_print_verbose(struct wt_status *s) setup_revisions(0, NULL, &rev, s->reference); rev.diffopt.output_format |= DIFF_FORMAT_PATCH; rev.diffopt.detect_rename = 1; - wt_read_cache(s); run_diff_index(&rev, 1); fflush(stdout); @@ -411,5 +401,5 @@ int git_status_config(const char *k, const char *v) wt_status_relative_paths = git_config_bool(k, v); return 0; } - return git_default_config(k, v); + return git_color_default_config(k, v); } |