diff options
311 files changed, 8920 insertions, 2637 deletions
diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..6b9c715d2 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +* whitespace=!indent,trail,space +*.[ch] whitespace diff --git a/.gitignore b/.gitignore index 7f8421dcd..165b256e2 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> @@ -43,6 +44,7 @@ Steven Grimm <koreth@midwinter.com> Theodore Ts'o <tytso@mit.edu> Tony Luck <tony.luck@intel.com> Uwe Kleine-König <Uwe_Zeisberger@digi.com> +Uwe Kleine-König <Uwe.Kleine-Koenig@digi.com> Uwe Kleine-König <ukleinek@informatik.uni-freiburg.de> Uwe Kleine-König <uzeisberger@io.fsforth.de> Uwe Kleine-König <zeisberg@informatik.uni-freiburg.de> diff --git a/Documentation/.gitattributes b/Documentation/.gitattributes new file mode 100644 index 000000000..ddb030137 --- /dev/null +++ b/Documentation/.gitattributes @@ -0,0 +1 @@ +*.txt whitespace diff --git a/Documentation/Makefile b/Documentation/Makefile index 7a325462e..43781fb24 100644 --- a/Documentation/Makefile +++ b/Documentation/Makefile @@ -45,6 +45,7 @@ man7dir=$(mandir)/man7 ASCIIDOC=asciidoc ASCIIDOC_EXTRA = +MANPAGE_XSL = callouts.xsl INSTALL?=install RM ?= rm -f DOC_REF = origin/man @@ -65,6 +66,7 @@ ASCIIDOC_EXTRA += -a asciidoc7compatible endif ifdef DOCBOOK_XSL_172 ASCIIDOC_EXTRA += -a docbook-xsl-172 +MANPAGE_XSL = manpage-1.72.xsl endif # @@ -159,7 +161,7 @@ $(MAN_HTML): %.html : %.txt %.1 %.5 %.7 : %.xml $(RM) $@ - xmlto -m callouts.xsl man $< + xmlto -m $(MANPAGE_XSL) man $< %.xml : %.txt $(RM) $@+ $@ diff --git a/Documentation/RelNotes-1.5.3.3.txt b/Documentation/RelNotes-1.5.3.3.txt index 2a7bfdd5c..d21384695 100644 --- a/Documentation/RelNotes-1.5.3.3.txt +++ b/Documentation/RelNotes-1.5.3.3.txt @@ -12,7 +12,7 @@ Fixes since v1.5.3.2 * The default shell on some FreeBSD did not execute the argument parsing code correctly and made git unusable. - * git-svn incorrectly spawned pager even when the user user + * git-svn incorrectly spawned pager even when the user explicitly asked not to. * sample post-receive hook overquoted the envelope sender diff --git a/Documentation/RelNotes-1.5.4.1.txt b/Documentation/RelNotes-1.5.4.1.txt new file mode 100644 index 000000000..d4e44b8b0 --- /dev/null +++ b/Documentation/RelNotes-1.5.4.1.txt @@ -0,0 +1,17 @@ +GIT v1.5.4.1 Release Notes +========================== + +Fixes since v1.5.4 +------------------ + + * "git-commit -C $tag" used to work but rewrite in C done in + 1.5.4 broke it. + + * An entry in the .gitattributes file that names a pattern in a + subdirectory of the directory it is in did not match + correctly (e.g. pattern "b/*.c" in "a/.gitattributes" should + match "a/b/foo.c" but it didn't). + + * Customized color specification was parsed incorrectly when + numeric color values are used. This was fixed in 1.5.4.1. + diff --git a/Documentation/RelNotes-1.5.4.2.txt b/Documentation/RelNotes-1.5.4.2.txt new file mode 100644 index 000000000..21d0df59f --- /dev/null +++ b/Documentation/RelNotes-1.5.4.2.txt @@ -0,0 +1,43 @@ +GIT v1.5.4.2 Release Notes +========================== + +Fixes since v1.5.4 +------------------ + + * The configuration parser was not prepared to see string + valued variables misspelled as boolean and segfaulted. + + * Temporary files left behind due to interrupted object + transfers were not cleaned up with "git prune". + + * "git config --unset" was confused when the unset variables + were spelled with continuation lines in the config file. + + * The merge message detection in "git cvsimport" did not catch + a message that began with "Merge...". + + * "git status" suggests "git rm --cached" for unstaging the + earlier "git add" before the initial commit. + + * "git status" output was incorrect during a partial commit. + + * "git bisect" refused to start when the HEAD was detached. + + * "git bisect" allowed a wildcard character in the commit + message expanded while writing its log file. + + * Manual pages were not formatted correctly with docbook xsl + 1.72; added a workaround. + + * "git-commit -C $tag" used to work but rewrite in C done in + 1.5.4 broke it. This was fixed in 1.5.4.1. + + * An entry in the .gitattributes file that names a pattern in a + subdirectory of the directory it is in did not match + correctly (e.g. pattern "b/*.c" in "a/.gitattributes" should + match "a/b/foo.c" but it didn't). This was fixed in 1.5.4.1. + + * Customized color specification was parsed incorrectly when + numeric color values are used. This was fixed in 1.5.4.1. + + * http transport misbehaved when linked with curl-gnutls. diff --git a/Documentation/RelNotes-1.5.4.3.txt b/Documentation/RelNotes-1.5.4.3.txt new file mode 100644 index 000000000..b0fc67fb2 --- /dev/null +++ b/Documentation/RelNotes-1.5.4.3.txt @@ -0,0 +1,27 @@ +GIT v1.5.4.3 Release Notes +========================== + +Fixes since v1.5.4.2 +-------------------- + + * RPM spec used to pull in everything with 'git'. This has been + changed so that 'git' package contains just the core parts, + and we now supply 'git-all' metapackage to slurp in everything. + This should match end user's expectation better. + + * When some refs failed to update, git-push reported "failure" + which was unclear if some other refs were updated or all of + them failed atomically (the answer is the former). Reworded + the message to clarify this. + + * "git clone" from a repository whose HEAD was misconfigured + did not set up the remote properly. Now it tries to do + better. + + * Updated git-push documentation to clarify what "matching" + means, in order to reduce user confusion. + + * Updated git-add documentation to clarify "add -u" operates in + the current subdirectory you are in, just like other commands. + + * git-gui updates to work on OSX and Windows better. diff --git a/Documentation/RelNotes-1.5.4.txt b/Documentation/RelNotes-1.5.4.txt index 9c864c9de..f1323b617 100644 --- a/Documentation/RelNotes-1.5.4.txt +++ b/Documentation/RelNotes-1.5.4.txt @@ -10,22 +10,32 @@ Removal * As git-commit and git-status have been rewritten, "git runstatus" helper script lost all its users and has been removed. - * Curl library older than 7.10 is not supported by "git http-push", - as it does not work without CURLM. + +Temporarily disabled +-------------------- + + * "git http-push" is known not to work well with cURL library older + than 7.16, and we had reports of repository corruption. It is + disabled on such platforms for now. Unfortunately, 1.5.3.8 shares + the same issue. In other words, this does not mean you will be + fine if you stick to an older git release. For now, please do not + use http-push from older git with cURL older than 7.16 if you + value your data. A proper fix will hopefully materialize in + later versions. Deprecation notices ------------------- - * The next feature release of git (this change is scheduled for v1.6.0) - will by default install dashed form of commands (e.g. "git-commit") - outside of users' normal $PATH, and will install only selected - commands ("git" itself, and "gitk") in $PATH. This implies: + * From v1.6.0, git will by default install dashed form of commands + (e.g. "git-commit") outside of users' normal $PATH, and will install + only selected commands ("git" itself, and "gitk") in $PATH. This + implies: - Using dashed forms of git commands (e.g. "git-commit") from the command line has been informally deprecated since early 2006, but now it officially is, and will be removed in the future. Use - dashless forms (e.g. "git commit") instead. + dash-less forms (e.g. "git commit") instead. - Using dashed forms from your scripts, without first prepending the return value from "git --exec-path" to the scripts' PATH, has been @@ -34,8 +44,8 @@ Deprecation notices - Use of dashed forms with "PATH=$(git --exec-path):$PATH; export PATH" early in your script is not deprecated with this change. - Users are strongly encouraged to adjust their habits and scripts now - to prepare for this. + Users are strongly encouraged to adjust their habits and scripts now + to prepare for this change. * The post-receive hook was introduced in March 2007 to supersede the post-update hook, primarily to overcome the command line length @@ -70,7 +80,7 @@ Updates since v1.5.3 * Comes with much improved gitk, with i18n. - * Comes with "git gui" 0.9.1 with i18n. + * Comes with git-gui 0.9.2 with i18n. * gitk is now merged as a subdirectory of git.git project, in preparation for its i18n. @@ -156,7 +166,7 @@ Updates since v1.5.3 command line in the generated log message, when told to cherry-pick a commit by naming a tag that points at it. It does not anymore. - * "git for-each-ref" learned %(xxxdate:<dateformat>) syntax to show the + * "git for-each-ref" learned %(xxxdate:<date-format>) syntax to show the various date fields in different formats. * "git gc --auto" is a low-impact way to automatically run a variant of @@ -211,7 +221,7 @@ Updates since v1.5.3 * "git pull --rebase" is a different way to integrate what you fetched into your current branch. - * "git fast-export" produces datastream that can be fed to fast-import + * "git fast-export" produces data-stream that can be fed to fast-import to reproduce the history recorded in a git repository. * "git add -i" takes pathspecs to limit the set of files to work on. @@ -232,8 +242,8 @@ Updates since v1.5.3 from its first parent. * "git commit" used to unconditionally strip comment lines that - began with '#' and removed excess blank lines. This - behaviour has been made configurable. + began with '#' and removed excess blank lines. This behavior has + been made configurable. * "git commit" has been rewritten in C. @@ -317,7 +327,7 @@ Updates since v1.5.3 * "git status" from a subdirectory now shows relative paths, which makes copy-and-pasting for git-checkout/git-add/git-rm easier. The - traditional behaviour to show the full path relative to the top of + traditional behavior to show the full path relative to the top of the work tree can be had by setting status.relativepaths configuration variable to false. @@ -332,7 +342,7 @@ Updates since v1.5.3 * "git help" learned "-w" option to show documentation in browsers. - * In addition there are quite a few internal clean-ups. Notably + * In addition there are quite a few internal clean-ups. Notably: - many fork/exec have been replaced with run-command API, brought from the msysgit effort. @@ -341,6 +351,7 @@ Updates since v1.5.3 - enhancement and more use of the strbuf API. + * Makefile tweaks to support HP-UX is in. Fixes since v1.5.3 ------------------ @@ -354,7 +365,7 @@ series. * The way "git diff --check" behaves is much more consistent with the way "git apply --whitespace=warn" works. - * "git svn" talking with the SVN over http will correctly quote branch + * "git svn" talking with the SVN over HTTP will correctly quote branch and project names. * "git config" did not work correctly on platforms that define @@ -364,9 +375,3 @@ series. documentation; a workaround has been implemented. * "git diff --color-words" colored context lines in a wrong color. - --- -exec >/var/tmp/1 -O=v1.5.4-rc4 -echo O=`git describe refs/heads/master` -git shortlog --no-merges $O..refs/heads/master ^refs/heads/maint diff --git a/Documentation/RelNotes-1.5.5.txt b/Documentation/RelNotes-1.5.5.txt new file mode 100644 index 000000000..c8b4f72c2 --- /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 de08d094e..0e155c936 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/blame-options.txt b/Documentation/blame-options.txt index ea1007bfb..c11bb7d36 100644 --- a/Documentation/blame-options.txt +++ b/Documentation/blame-options.txt @@ -52,7 +52,7 @@ of lines before or after the line given by <start>. When <rev> is not specified, the command annotates the changes starting backwards from the working tree copy. This flag makes the command pretend as if the working - tree copy has the contents of he named file (specify + tree copy has the contents of the named file (specify `-` to make the command read from the standard input). -M|<num>|:: diff --git a/Documentation/config.txt b/Documentation/config.txt index 877eda960..8e361a1e7 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 @@ -308,6 +353,10 @@ core.whitespace:: error (enabled by default). * `indent-with-non-tab` treats a line that is indented with 8 or more space characters as an error (not enabled by default). +* `cr-at-eol` treats a carriage-return at the end of line as + part of the line terminator, i.e. with it, `trailing-space` + does not trigger if the character before such a carriage-return + is not a whitespace (not enabled by default). alias.*:: Command aliases for the linkgit:git[1] command wrapper - e.g. @@ -333,7 +382,7 @@ branch.autosetupmerge:: so that linkgit:git-pull[1] will appropriately merge from that remote branch. Note that even if this option is not set, this behavior can be chosen per-branch using the `--track` - and `--no-track` options. This option defaults to false. + and `--no-track` options. This option defaults to true. branch.<name>.remote:: When in branch <name>, it tells `git fetch` which remote to fetch. @@ -367,6 +416,11 @@ branch.<name>.rebase:: it unless you understand the implications (see linkgit:git-rebase[1] for details). +browser.<tool>.path:: + Override the path for the given tool that may be used to + browse HTML help (see '-w' option in linkgit:git-help[1]) or a + working repository in gitweb (see linkgit:git-instaweb[1]). + clean.requireForce:: A boolean to make git-clean do nothing unless given -f or -n. Defaults to true. @@ -439,6 +493,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. @@ -751,6 +812,8 @@ pack.threads:: warning. This is meant to reduce packing time on multiprocessor machines. The required amount of memory for the delta search window is however multiplied by the number of threads. + Specifying 0 will cause git to auto-detect the number of CPU's + and set the number of threads accordingly. pack.indexVersion:: Specify the default pack index version. Valid values are 1 for @@ -761,6 +824,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. @@ -830,6 +899,17 @@ tar.umask:: archiving user's umask will be used instead. See umask(2) and linkgit:git-archive[1]. +url.<base>.insteadOf:: + Any URL that starts with this value will be rewritten to + start, instead, with <base>. In cases where some site serves a + large number of repositories, and serves them with multiple + access methods, and some users need to use different access + methods, this feature allows people to specify any of the + equivalent URLs and have git automatically rewrite the URL to + the best alternative for the particular user, even for a + never-before-seen repository on the site. When more than one + insteadOf strings match a given URL, the longest match is used. + user.email:: Your email address to be recorded in any newly created commits. Can be overridden by the 'GIT_AUTHOR_EMAIL', 'GIT_COMMITTER_EMAIL', and diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index 8d35cbd60..8dc5b001c 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -170,6 +170,14 @@ endif::git-format-patch[] Swap two inputs; that is, show differences from index or on-disk file to tree contents. +--relative[=<path>]:: + When run from a subdirectory of the project, it can be + told to exclude changes outside the directory and show + pathnames relative to it with this option. When you are + not in a subdirectory (e.g. in a bare repository), you + can name which subdirectory to make the output relative + to by giving a <path> as an argument. + --text:: Treat all files as text. diff --git a/Documentation/fetch-options.txt b/Documentation/fetch-options.txt index 61e48ccf0..b67591148 100644 --- a/Documentation/fetch-options.txt +++ b/Documentation/fetch-options.txt @@ -23,11 +23,15 @@ fetches is a descendant of `<lbranch>`. This option overrides that check. +ifdef::git-pull[] +\--no-tags:: +endif::git-pull[] +ifndef::git-pull[] -n, \--no-tags:: - By default, `git-fetch` fetches tags that point at - objects that are downloaded from the remote repository - and stores them locally. This option disables this - automatic tag following. +endif::git-pull[] + By default, tags that point at objects that are downloaded + from the remote repository are fetched and stored locally. + This option disables this automatic tag following. -t, \--tags:: Most of the tags are fetched automatically as branch diff --git a/Documentation/git-add.txt b/Documentation/git-add.txt index 9d2ac865d..47799097c 100644 --- a/Documentation/git-add.txt +++ b/Documentation/git-add.txt @@ -74,8 +74,8 @@ OPTIONS Update only files that git already knows about. This is similar to what "git commit -a" does in preparation for making a commit, except that the update is limited to paths specified on the - command line. If no paths are specified, all tracked files are - updated. + command line. If no paths are specified, all tracked files in the + current directory and its subdirectories are updated. \--refresh:: Don't add the file(s), but only refresh their stat() diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt index 2ffba2102..e640fc75c 100644 --- a/Documentation/git-am.txt +++ b/Documentation/git-am.txt @@ -138,7 +138,7 @@ aborts in the middle,. You can recover from this in one of two ways: The command refuses to process new mailboxes while `.dotest` directory exists, so if you decide to start over from scratch, -run `rm -f .dotest` before running the command with mailbox +run `rm -f -r .dotest` before running the command with mailbox names. diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt index f920c04cc..7e8874aca 100644 --- a/Documentation/git-branch.txt +++ b/Documentation/git-branch.txt @@ -34,11 +34,11 @@ Note that this will create the new branch, but it will not switch the working tree to it; use "git checkout <newbranch>" to switch to the new branch. -When a local branch is started off a remote branch, git can setup the +When a local branch is started off a remote branch, git sets up the branch so that linkgit:git-pull[1] will appropriately merge from that -remote branch. If this behavior is desired, it is possible to make it -the default using the global `branch.autosetupmerge` configuration -flag. Otherwise, it can be chosen per-branch using the `--track` +remote branch. If this behavior is not desired, it is possible to +disable it using the global `branch.autosetupmerge` configuration +flag. That setting can be overridden by using the `--track` and `--no-track` options. With a '-m' or '-M' option, <oldbranch> will be renamed to <newbranch>. @@ -108,10 +108,11 @@ OPTIONS Set up configuration so that git-pull will automatically retrieve data from the remote branch. Use this if you always pull from the same remote branch into the new branch, or if you - don't want to use "git pull <repository> <refspec>" explicitly. Set the - branch.autosetupmerge configuration variable to true if you + don't want to use "git pull <repository> <refspec>" explicitly. + This behavior is the default. Set the + branch.autosetupmerge configuration variable to false if you want git-checkout and git-branch to always behave as if - '--track' were given. + '--no-track' were given. --no-track:: When a branch is created off a remote branch, diff --git a/Documentation/git-bundle.txt b/Documentation/git-bundle.txt index 72f080a97..505ac056e 100644 --- a/Documentation/git-bundle.txt +++ b/Documentation/git-bundle.txt @@ -99,36 +99,62 @@ Assume two repositories exist as R1 on machine A, and R2 on machine B. For whatever reason, direct connection between A and B is not allowed, but we can move data from A to B via some mechanism (CD, email, etc). We want to update R2 with developments made on branch master in R1. + +To create the bundle you have to specify the basis. You have some options: + +- Without basis. ++ +This is useful when sending the whole history. + +------------ +$ git bundle create mybundle master +------------ + +- Using temporally tags. ++ We set a tag in R1 (lastR2bundle) after the previous such transport, and move it afterwards to help build the bundle. -in R1 on A: - ------------ $ git-bundle create mybundle master ^lastR2bundle $ git tag -f lastR2bundle master ------------ -(move mybundle from A to B by some mechanism) +- Using a tag present in both repositories + +------------ +$ git bundle create mybundle master ^v1.0.0 +------------ + +- A basis based on time. + +------------ +$ git bundle create mybundle master --since=10.days.ago +------------ -in R2 on B: +- With a limit on the number of commits ------------ -$ git-bundle verify mybundle -$ git-fetch mybundle refspec +$ git bundle create mybundle master -n 10 ------------ -where refspec is refInBundle:localRef +Then you move mybundle from A to B, and in R2 on B: +------------ +$ git-bundle verify mybundle +$ git-fetch mybundle master:localRef +------------ -Also, with something like this in your config: +With something like this in the config in R2: +------------------------ [remote "bundle"] url = /home/me/tmp/file.bdl fetch = refs/heads/*:refs/remotes/origin/* +------------------------ You can first sneakernet the bundle file to ~/tmp/file.bdl and -then these commands: +then these commands on machine B: ------------ $ git ls-remote bundle diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt index 584359ff3..b4cfa044b 100644 --- a/Documentation/git-checkout.txt +++ b/Documentation/git-checkout.txt @@ -52,10 +52,11 @@ OPTIONS set up configuration so that git-pull will automatically retrieve data from the remote branch. Use this if you always pull from the same remote branch into the new branch, or if you - don't want to use "git pull <repository> <refspec>" explicitly. Set the - branch.autosetupmerge configuration variable to true if you + don't want to use "git pull <repository> <refspec>" explicitly. + This behavior is the default. Set the + branch.autosetupmerge configuration variable to false if you want git-checkout and git-branch to always behave as if - '--track' were given. + '--no-track' were given. --no-track:: When -b is given and a branch is created off a remote branch, diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt index fdccbd4cb..975824301 100644 --- a/Documentation/git-clone.txt +++ b/Documentation/git-clone.txt @@ -62,6 +62,15 @@ OPTIONS .git/objects/info/alternates to share the objects with the source repository. The resulting repository starts out without any object of its own. ++ +*NOTE*: this is a possibly dangerous operation; do *not* use +it unless you understand what it does. If you clone your +repository using this option, then delete branches in the +source repository and then run linkgit:git-gc[1] using the +'--prune' option in the source repository, it may remove +objects which are referenced by the cloned repository. + + --reference <repository>:: If the reference repository is on the local machine diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt index c3725b2ed..b4ae61ff4 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-config.txt b/Documentation/git-config.txt index e4d0e4752..fa161718d 100644 --- a/Documentation/git-config.txt +++ b/Documentation/git-config.txt @@ -130,7 +130,7 @@ See also <<FILES>>. -z, --null:: For all options that output values and/or keys, always - end values with with the null character (instead of a + end values with the null character (instead of a newline). Use newline instead as a delimiter between key and value. This allows for secure parsing of the output without getting confused e.g. by values that diff --git a/Documentation/git-cvsimport.txt b/Documentation/git-cvsimport.txt index dbce50369..6f91b9ea2 100644 --- a/Documentation/git-cvsimport.txt +++ b/Documentation/git-cvsimport.txt @@ -107,8 +107,8 @@ If you need to pass multiple options, separate them with a comma. -M <regex>:: Attempt to detect merges based on the commit message with a custom - regex. It can be used with '-m' to also see the default regexes. - You must escape forward slashes. + regex. It can be used with '-m' to enable the default regexes + as well. You must escape forward slashes. -S <regex>:: Skip paths matching the regex. diff --git a/Documentation/git-cvsserver.txt b/Documentation/git-cvsserver.txt index d75e40134..d3e99931d 100644 --- a/Documentation/git-cvsserver.txt +++ b/Documentation/git-cvsserver.txt @@ -183,7 +183,7 @@ access method and requested operation. That means that even if you offer only read access (e.g. by using the pserver method), git-cvsserver should have write access to the database to work reliably (otherwise you need to make sure -that the database if up-to-date all the time git-cvsserver is run). +that the database is up-to-date any time git-cvsserver is executed). By default it uses SQLite databases in the git directory, named `gitcvs.<module_name>.sqlite`. Note that the SQLite backend creates @@ -204,7 +204,7 @@ about `DBI->connect()`. gitcvs.dbname:: Database name. The exact meaning depends on the - used database driver, for SQLite this is a filename. + selected database driver, for SQLite this is a filename. Supports variable substitution (see below). May not contain semicolons (`;`). Default: '%Ggitcvs.%m.sqlite' @@ -215,7 +215,7 @@ gitcvs.dbdriver:: with 'DBD::SQLite', reported to work with 'DBD::Pg', and reported *not* to work with 'DBD::mysql'. Please regard this as an experimental feature. May not - contain double colons (`:`). + contain colons (`:`). Default: 'SQLite' gitcvs.dbuser:: diff --git a/Documentation/git-describe.txt b/Documentation/git-describe.txt index 0742152b8..fbb40a291 100644 --- a/Documentation/git-describe.txt +++ b/Documentation/git-describe.txt @@ -45,12 +45,21 @@ OPTIONS candidates to describe the input committish consider up to <n> candidates. Increasing <n> above 10 will take slightly longer but may produce a more accurate result. + An <n> of 0 will cause only exact matches to be output. + +--exact-match:: + Only output exact matches (a tag directly references the + supplied commit). This is a synonym for --candidates=0. --debug:: Verbosely display information about the searching strategy 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 bd625abab..96f676707 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-filter-branch.txt b/Documentation/git-filter-branch.txt index e22dfa580..543a1cf10 100644 --- a/Documentation/git-filter-branch.txt +++ b/Documentation/git-filter-branch.txt @@ -56,7 +56,9 @@ notable exception of the commit filter, for technical reasons). Prior to that, the $GIT_COMMIT environment variable will be set to contain the id of the commit being rewritten. Also, GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, GIT_AUTHOR_DATE, GIT_COMMITTER_NAME, GIT_COMMITTER_EMAIL, -and GIT_COMMITTER_DATE are set according to the current commit. +and GIT_COMMITTER_DATE are set according to the current commit. If any +evaluation of <command> returns a non-zero exit status, the whole operation +will be aborted. A 'map' function is available that takes an "original sha1 id" argument and outputs a "rewritten sha1 id" if the commit has been already @@ -197,7 +199,7 @@ happened). If this is not the case, use: -------------------------------------------------------------------------- git filter-branch --parent-filter \ - 'cat; test $GIT_COMMIT = <commit-id> && echo "-p <graft-id>"' HEAD + 'test $GIT_COMMIT = <commit-id> && echo "-p <graft-id>" || cat' HEAD -------------------------------------------------------------------------- or even simpler: @@ -240,6 +242,15 @@ committed a merge between P1 and P2, it will be propagated properly and all children of the merge will become merge commits with P1,P2 as their parents instead of the merge commit. +You can rewrite the commit log messages using `--message-filter`. For +example, `git-svn-id` strings in a repository created by `git-svn` can +be removed this way: + +------------------------------------------------------- +git filter-branch --message-filter ' + sed -e "/^git-svn-id:/d" +' +------------------------------------------------------- To restrict rewriting to only part of the history, specify a revision range in addition to the new branch name. The new branch name will diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt index 651efe6ca..b5207b760 100644 --- a/Documentation/git-format-patch.txt +++ b/Documentation/git-format-patch.txt @@ -10,13 +10,15 @@ SYNOPSIS -------- [verse] 'git-format-patch' [-k] [-o <dir> | --stdout] [--thread] - [--attach[=<boundary>] | --inline[=<boundary>]] - [-s | --signoff] [<common diff options>] - [-n | --numbered | -N | --no-numbered] - [--start-number <n>] [--numbered-files] - [--in-reply-to=Message-Id] [--suffix=.<sfx>] - [--ignore-if-in-upstream] - [--subject-prefix=Subject-Prefix] + [--attach[=<boundary>] | --inline[=<boundary>]] + [-s | --signoff] [<common diff options>] + [-n | --numbered | -N | --no-numbered] + [--start-number <n>] [--numbered-files] + [--in-reply-to=Message-Id] [--suffix=.<sfx>] + [--ignore-if-in-upstream] + [--subject-prefix=Subject-Prefix] + [--cc=<email>] + [--cover-letter] [ <since> | <revision range> ] DESCRIPTION @@ -135,6 +137,15 @@ include::diff-options.txt[] allows for useful naming of a patch series, and can be combined with the --numbered option. +--cc=<email>:: + Add a "Cc:" header to the email headers. This is in addition + to any configured headers, and may be used multiple times. + +--cover-letter:: + Generate a cover letter template. You still have to fill in + a description, but the shortlog and the diffstat will be + generated for you. + --suffix=.<sfx>:: Instead of using `.patch` as the suffix for generated filenames, use specified suffix. A common alternative is diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt index f3cb24f25..71a73354f 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 09904c75c..0926dc12b 100644 --- a/Documentation/git-help.txt +++ b/Documentation/git-help.txt @@ -47,17 +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. +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 ----------------------- @@ -74,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-http-push.txt b/Documentation/git-http-push.txt index cca77f10d..0b8272234 100644 --- a/Documentation/git-http-push.txt +++ b/Documentation/git-http-push.txt @@ -15,6 +15,9 @@ DESCRIPTION Sends missing objects to remote repository, and updates the remote branch. +*NOTE*: This command is temporarily disabled if your cURL +library is older than 7.16, as the combination has been reported +not to work and sometimes corrupts repository. OPTIONS ------- diff --git a/Documentation/git-instaweb.txt b/Documentation/git-instaweb.txt index 841e8fac7..51f1532ef 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 5d816d0d8..19ee017ae 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 74cc7c1cb..5c1bd3b08 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 @@ -176,6 +177,8 @@ base-name:: This is meant to reduce packing time on multiprocessor machines. The required amount of memory for the delta search window is however multiplied by the number of threads. + Specifying 0 will cause git to auto-detect the number of CPU's + and set the number of threads accordingly. --index-version=<version>[,<offset>]:: This is intended to be used by the test suite only. It allows diff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt index 77fdaf146..737894390 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 @@ -25,6 +26,24 @@ OPTIONS ------- include::merge-options.txt[] +:git-pull: 1 + +\--rebase:: + Instead of a merge, perform a rebase after fetching. If + there is a remote ref for the upstream branch, and this branch + was rebased since last fetched, the rebase uses that information + to avoid rebasing non-local changes. To make this the default + for branch `<name>`, set configuration `branch.<name>.rebase` + to `true`. ++ +*NOTE:* This is a potentially _dangerous_ mode of operation. +It rewrites history, which does not bode well when you +published that history already. Do *not* use this option +unless you have read linkgit:git-rebase[1] carefully. + +\--no-rebase:: + Override earlier \--rebase. + include::fetch-options.txt[] include::pull-fetch-param.txt[] @@ -33,16 +52,6 @@ include::urls-remotes.txt[] include::merge-strategies.txt[] -\--rebase:: - Instead of a merge, perform a rebase after fetching. - *NOTE:* This is a potentially _dangerous_ mode of operation. - It rewrites history, which does not bode well when you - published that history already. Do *not* use this option - unless you have read linkgit:git-rebase[1] carefully. - -\--no-rebase:: - Override earlier \--rebase. - DEFAULT BEHAVIOUR ----------------- diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt index 5f2494495..3128170bc 100644 --- a/Documentation/git-push.txt +++ b/Documentation/git-push.txt @@ -47,9 +47,9 @@ even if it does not result in a fast forward update. + Note: If no explicit refspec is found, (that is neither on the command line nor in any Push line of the -corresponding remotes file---see below), then all the -heads that exist both on the local side and on the remote -side are updated. +corresponding remotes file---see below), then "matching" heads are +pushed: for every head that exists on the local side, the remote side is +updated if a head of the same name already exists on the remote side. + `tag <tag>` means the same as `refs/tags/<tag>:refs/tags/<tag>`. + @@ -108,6 +108,55 @@ the remote repository. include::urls-remotes.txt[] +OUTPUT +------ + +The output of "git push" depends on the transport method used; this +section describes the output when pushing over the git protocol (either +locally or via ssh). + +The status of the push is output in tabular form, with each line +representing the status of a single ref. Each line is of the form: + +------------------------------- + <flag> <summary> <from> -> <to> (<reason>) +------------------------------- + +flag:: + A single character indicating the status of the ref. This is + blank for a successfully pushed ref, `!` for a ref that was + rejected or failed to push, and '=' for a ref that was up to + date and did not need pushing (note that the status of up to + date refs is shown only when `git push` is running verbosely). + +summary:: + For a successfully pushed ref, the summary shows the old and new + values of the ref in a form suitable for using as an argument to + `git log` (this is `<old>..<new>` in most cases, and + `<old>...<new>` for forced non-fast forward updates). For a + failed update, more details are given for the failure. + The string `rejected` indicates that git did not try to send the + ref at all (typically because it is not a fast forward). The + string `remote rejected` indicates that the remote end refused + the update; this rejection is typically caused by a hook on the + remote side. The string `remote failure` indicates that the + remote end did not report the successful update of the ref + (perhaps because of a temporary error on the remote side, a + break in the network connection, or other transient error). + +from:: + The name of the local ref being pushed, minus its + `refs/<type>/` prefix. In the case of deletion, the + name of the local ref is omitted. + +to:: + The name of the remote ref being updated, minus its + `refs/<type>/` prefix. + +reason:: + A human-readable explanation. In the case of successfully pushed + refs, no explanation is needed. For a failed ref, the reason for + failure is described. Examples -------- diff --git a/Documentation/git-remote.txt b/Documentation/git-remote.txt index 10f6fa58b..2cbd1f764 100644 --- a/Documentation/git-remote.txt +++ b/Documentation/git-remote.txt @@ -10,7 +10,7 @@ SYNOPSIS -------- [verse] 'git-remote' -'git-remote' add [-t <branch>] [-m <branch>] [-f] [--mirror] <name> <url> +'git-remote' add [-t <branch>] [-m <master>] [-f] [--mirror] <name> <url> 'git-remote' rm <name> 'git-remote' show <name> 'git-remote' prune <name> diff --git a/Documentation/git-reset.txt b/Documentation/git-reset.txt index a4e0a779d..fac59c972 100644 --- a/Documentation/git-reset.txt +++ b/Documentation/git-reset.txt @@ -8,8 +8,8 @@ git-reset - Reset current HEAD to the specified state SYNOPSIS -------- [verse] -'git-reset' [--mixed | --soft | --hard] [-q] [<commit>] -'git-reset' [--mixed] [-q] [<commit>] [--] <paths>... +'git reset' [--mixed | --soft | --hard] [-q] [<commit>] +'git reset' [-q] [<commit>] [--] <paths>... DESCRIPTION ----------- @@ -37,7 +37,7 @@ OPTIONS --soft:: Does not touch the index file nor the working tree at all, but requires them to be in a good order. This leaves all your changed - files "Added but not yet committed", as linkgit:git-status[1] would + files "Changes to be committed", as linkgit:git-status[1] would put it. --hard:: @@ -176,6 +176,23 @@ $ git reset <3> committed as 'snapshot WIP'. This updates the index to show your WIP files as uncommitted. +Reset a single file in the index:: ++ +Suppose you have added a file to your index, but later decide you do not +want to add it to your commit. You can remove the file from the index +while keeping your changes with git reset. ++ +------------ +$ git reset -- frotz.c <1> +$ git commit -m "Commit files in index" <2> +$ git add frotz.c <3> +------------ ++ +<1> This removes the file from the index while keeping it in the working + directory. +<2> This commits all other changes in the index. +<3> Adds the file to the index again. + Author ------ Written by Junio C Hamano <junkio@cox.net> and Linus Torvalds <torvalds@osdl.org> diff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt index 5b96eabfc..a8d489f9f 100644 --- a/Documentation/git-rev-list.txt +++ b/Documentation/git-rev-list.txt @@ -31,6 +31,7 @@ SYNOPSIS [ \--(author|committer|grep)=<pattern> ] [ \--regexp-ignore-case | \-i ] [ \--extended-regexp | \-E ] + [ \--fixed-strings | \-F ] [ \--date={local|relative|default|iso|rfc|short} ] [ [\--objects | \--objects-edge] [ \--unpacked ] ] [ \--pretty | \--header ] diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt index 5d9c36985..f02f6bbb4 100644 --- a/Documentation/git-rev-parse.txt +++ b/Documentation/git-rev-parse.txt @@ -229,13 +229,13 @@ blobs contained in a commit. * A colon, optionally followed by a stage number (0 to 3) and a colon, followed by a path; this names a blob object in the index at the given path. Missing stage number (and the colon - that follows it) names an stage 0 entry. During a merge, stage + that follows it) names a stage 0 entry. During a merge, stage 1 is the common ancestor, stage 2 is the target branch's version (typically the current branch), and stage 3 is the version from the branch being merged. -Here is an illustration, by Jon Loeliger. Both node B and C are -a commit parents of commit node A. Parent commits are ordered +Here is an illustration, by Jon Loeliger. Both commit nodes B +and C are parents of commit node A. Parent commits are ordered left-to-right. G H I J @@ -291,7 +291,7 @@ and its parent commits exists. `r1{caret}@` notation means all parents of `r1`. `r1{caret}!` includes commit `r1` but excludes its all parents. -Here are a handful examples: +Here are a handful of examples: D G H D D F G H I J D F diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt index 4b8ec8a20..336d797e8 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 @@ -137,6 +177,8 @@ The --cc option must be repeated for each user you want on the cc list. Specify the primary recipient of the emails generated. Generally, this will be the upstream maintainer of the project involved. + Default is the value of the 'sendemail.to' configuration value; + if that is unspecified, this will be prompted for. + The --to option must be repeated for each user you want on the to list. diff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt index 9889806a5..48e6f5a3f 100644 --- a/Documentation/git-stash.txt +++ b/Documentation/git-stash.txt @@ -9,7 +9,7 @@ SYNOPSIS -------- [verse] 'git-stash' (list | show [<stash>] | apply [<stash>] | clear) -'git-stash' [save] [message...] +'git-stash' [save [<message>]] DESCRIPTION ----------- @@ -36,13 +36,14 @@ is also possible). OPTIONS ------- -save:: +save [<message>]:: Save your local modifications to a new 'stash', and run `git-reset --hard` to revert them. This is the default action when no - subcommand is given. + subcommand is given. The <message> part is optional and gives + the description along with the stashed state. -list:: +list [<options>]:: List the stashes that you currently have. Each 'stash' is listed with its name (e.g. `stash@\{0}` is the latest stash, `stash@\{1}` is @@ -54,6 +55,9 @@ list:: stash@{0}: WIP on submit: 6ebd0e2... Update git-stash documentation stash@{1}: On master: 9cc0589... Add git-stash ---------------------------------------------------------------- ++ +The command takes options applicable to the linkgit:git-log[1] +command to control what is shown and how. show [<stash>]:: diff --git a/Documentation/git-submodule.txt b/Documentation/git-submodule.txt index cffc6d48f..e818e6e78 100644 --- a/Documentation/git-submodule.txt +++ b/Documentation/git-submodule.txt @@ -9,8 +9,9 @@ git-submodule - Initialize, update or inspect submodules SYNOPSIS -------- [verse] -'git-submodule' [--quiet] [-b branch] add <repository> [<path>] -'git-submodule' [--quiet] [--cached] [status|init|update] [--] [<path>...] +'git-submodule' [--quiet] add [-b branch] [--] <repository> [<path>] +'git-submodule' [--quiet] status [--cached] [--] [<path>...] +'git-submodule' [--quiet] [init|update] [--] [<path>...] COMMANDS diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index e1a1d46a9..340f1be02 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 @@ -456,10 +463,13 @@ have each person clone that repository with 'git clone': ------------------------------------------------------------------------ # Do the initial import on a server ssh server "cd /pub && git-svn clone http://svn.foo.org/project -# Clone locally - git clone server:/pub/project -# Tell git-svn which branch contains the Subversion commits - git update-ref refs/remotes/git-svn origin/master +# Clone locally - make sure the refs/remotes/ space matches the server + mkdir project + cd project + git-init + git remote add origin server:/pub/project + git config --add remote.origin.fetch=+refs/remotes/*:refs/remotes/* + git fetch # Initialize git-svn locally (be sure to use the same URL and -T/-b/-t options as were used on server) git-svn init http://svn.foo.org/project # Pull the latest changes from Subversion diff --git a/Documentation/git-web--browse.txt b/Documentation/git-web--browse.txt new file mode 100644 index 000000000..df57d010e --- /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 4ece40d1e..741ae0e4c 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -43,6 +43,14 @@ 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.3/git.html[documentation for release 1.5.4.3] + +* release notes for + link:RelNotes-1.5.4.3.txt[1.5.4.3], + 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] * release notes for diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt index 35a29fd60..84ec9623a 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 08373f52b..e847b3ba6 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 f110162b0..76b8d7746 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/manpage-1.72.xsl b/Documentation/manpage-1.72.xsl new file mode 100644 index 000000000..fe3cd72d6 --- /dev/null +++ b/Documentation/manpage-1.72.xsl @@ -0,0 +1,17 @@ +<!-- callout.xsl: converts asciidoc callouts to man page format --> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> +<xsl:template match="co"> + <xsl:value-of select="concat('▓fB(',substring-after(@id,'-'),')▓fR')"/> +</xsl:template> +<xsl:template match="calloutlist"> + <xsl:text>⌂sp </xsl:text> + <xsl:apply-templates/> + <xsl:text> </xsl:text> +</xsl:template> +<xsl:template match="callout"> + <xsl:value-of select="concat('▓fB',substring-after(@arearefs,'-'),'. ▓fR')"/> + <xsl:apply-templates/> + <xsl:text>⌂br </xsl:text> +</xsl:template> + +</xsl:stylesheet> diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt index a8138e27a..259072c07 100644 --- a/Documentation/rev-list-options.txt +++ b/Documentation/rev-list-options.txt @@ -153,6 +153,11 @@ limiting may be applied. Consider the limiting patterns to be extended regular expressions instead of the default basic regular expressions. +-F, --fixed-strings:: + + Consider the limiting patterns to be fixed strings (don't interpret + pattern as a regular expression). + --remove-empty:: Stop when a given path disappears from the tree. diff --git a/Documentation/technical/api-remote.txt b/Documentation/technical/api-remote.txt new file mode 100644 index 000000000..073b22bd8 --- /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 19d2f64f7..dfbf9ac5d 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/Documentation/technical/racy-git.txt b/Documentation/technical/racy-git.txt index 5030d9f2f..6bdf034b3 100644 --- a/Documentation/technical/racy-git.txt +++ b/Documentation/technical/racy-git.txt @@ -184,7 +184,7 @@ In a large project where raciness avoidance cost really matters, however, the initial computation of all object names in the index takes more than one second, and the index file is written out after all that happens. Therefore the timestamp of the -index file will be more than one seconds later than the the +index file will be more than one seconds later than the youngest file in the working tree. This means that in these cases there actually will not be any racily clean entry in the resulting index. diff --git a/Documentation/urls.txt b/Documentation/urls.txt index 81ac17f32..fa34c6747 100644 --- a/Documentation/urls.txt +++ b/Documentation/urls.txt @@ -44,3 +44,26 @@ endif::git-clone[] ifdef::git-clone[] They are equivalent, except the former implies --local option. endif::git-clone[] + + +If there are a large number of similarly-named remote repositories and +you want to use a different format for them (such that the URLs you +use will be rewritten into URLs that work), you can create a +configuration section of the form: + +------------ + [url "<actual url base>"] + insteadOf = <other url base> +------------ + +For example, with this: + +------------ + [url "git://git.host.xz/"] + insteadOf = host.xz:/path/to/ + insteadOf = work: +------------ + +a URL like "work:repo.git" or like "host.xz:/path/to/repo.git" will be +rewritten in any context that takes a URL to be "git://git.host.xz/repo.git". + diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 2ba142a11..6ddf04d21 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v1.5.4-rc3.GIT +DEF_VER=v1.5.4.GIT LF=' ' @@ -15,7 +15,9 @@ elif test -d .git && VN=$(git describe --abbrev=4 HEAD 2>/dev/null) && case "$VN" in *$LF*) (exit 1) ;; - v[0-9]*) : happy ;; + v[0-9]*) + test -z "$(git diff-index --name-only HEAD)" || + VN="$VN-dirty" ;; esac then VN=$(echo "$VN" | sed -e 's/-/./g'); @@ -25,14 +27,6 @@ fi VN=$(expr "$VN" : v*'\(.*\)') -dirty=$(sh -c 'git diff-index --name-only HEAD' 2>/dev/null) || dirty= -case "$dirty" in -'') - ;; -*) - VN="$VN-dirty" ;; -esac - if test -r $GVF then VC=$(sed -e 's/^GIT_VERSION = //' <$GVF) @@ -79,8 +79,8 @@ Issues of note: - "perl" and POSIX-compliant shells are needed to use most of the barebone Porcelainish scripts. - - "cpio" is used by git-merge for saving and restoring the index, - and by git-clone when doing a local (possibly hardlinked) clone. + - "cpio" is used by git-clone when doing a local (possibly + hardlinked) clone. - Some platform specific issues are dealt with Makefile rules, but depending on your specific installation, you may not @@ -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. # @@ -42,6 +45,8 @@ all:: # # Define NO_MKDTEMP if you don't have mkdtemp in the C library. # +# Define NO_SYS_SELECT_H if you don't have sys/select.h. +# # Define NO_SYMLINK_HEAD if you never want .git/HEAD to be a symbolic link. # Enable it on Windows. By default, symrefs are still used. # @@ -135,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 @@ -229,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 \ @@ -318,7 +327,8 @@ LIB_OBJS = \ alloc.o merge-file.o path-list.o help.o unpack-trees.o $(DIFF_OBJS) \ color.o wt-status.o archive-zip.o archive-tar.o shallow.o utf8.o \ convert.o attr.o decorate.o progress.o mailmap.o symlinks.o remote.o \ - transport.o bundle.o walker.o parse-options.o ws.o archive.o branch.o + transport.o bundle.o walker.o parse-options.o ws.o archive.o branch.o \ + alias.o BUILTIN_OBJS = \ builtin-add.o \ @@ -505,6 +515,17 @@ ifeq ($(uname_S),IRIX64) # for now, build 32-bit version BASIC_LDFLAGS += -L/usr/lib32 endif +ifeq ($(uname_S),HP-UX) + NO_IPV6=YesPlease + NO_SETENV=YesPlease + NO_STRCASESTR=YesPlease + NO_MEMMEM = YesPlease + NO_STRLCPY = YesPlease + NO_MKDTEMP = YesPlease + NO_UNSETENV = YesPlease + NO_HSTRERROR = YesPlease + NO_SYS_SELECT_H = YesPlease +endif ifneq (,$(findstring arm,$(uname_M))) ARM_SHA1 = YesPlease endif @@ -607,6 +628,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 @@ -637,6 +662,9 @@ ifdef NO_UNSETENV COMPAT_CFLAGS += -DNO_UNSETENV COMPAT_OBJS += compat/unsetenv.o endif +ifdef NO_SYS_SELECT_H + BASIC_CFLAGS += -DNO_SYS_SELECT_H +endif ifdef NO_MMAP COMPAT_CFLAGS += -DNO_MMAP COMPAT_OBJS += compat/mmap.o @@ -708,10 +736,15 @@ 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 EXTLIBS += -lpthread + LIB_OBJS += thread-utils.o endif ifeq ($(TCLTK_PATH),) @@ -805,6 +838,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)"' $< @@ -822,7 +856,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 $@+ $@ @@ -927,7 +960,7 @@ git-%$X: %.o $(GITLIBS) git-imap-send$X: imap-send.o $(LIB_FILE) -http.o http-walker.o http-push.o: http.h +http.o http-walker.o http-push.o transport.o: http.h git-http-push$X: revision.o http.o http-push.o $(GITLIBS) $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ @@ -1070,7 +1103,7 @@ git.spec: git.spec.in mv $@+ $@ GIT_TARNAME=git-$(GIT_VERSION) -dist: git.spec git-archive configure +dist: git.spec git-archive$(X) configure ./git-archive --format=tar \ --prefix=$(GIT_TARNAME)/ HEAD^{tree} > $(GIT_TARNAME).tar @mkdir -p $(GIT_TARNAME) @@ -1 +1 @@ -Documentation/RelNotes-1.5.4.txt
\ No newline at end of file +Documentation/RelNotes-1.5.5.txt
\ No newline at end of file diff --git a/alias.c b/alias.c new file mode 100644 index 000000000..116cac87c --- /dev/null +++ b/alias.c @@ -0,0 +1,22 @@ +#include "cache.h" + +static const char *alias_key; +static char *alias_val; +static int alias_lookup_cb(const char *k, const char *v) +{ + if (!prefixcmp(k, "alias.") && !strcmp(k+6, alias_key)) { + if (!v) + return config_error_nonbool(k); + alias_val = xstrdup(v); + return 0; + } + return 0; +} + +char *alias_lookup(const char *alias) +{ + alias_key = alias; + alias_val = NULL; + git_config(alias_lookup_cb); + return alias_val; +} diff --git a/archive-tar.c b/archive-tar.c index e1bced560..30aa2e23f 100644 --- a/archive-tar.c +++ b/archive-tar.c @@ -222,7 +222,7 @@ static void write_global_extended_header(const unsigned char *sha1) static int git_tar_config(const char *var, const char *value) { if (!strcmp(var, "tar.umask")) { - if (!strcmp(value, "user")) { + if (value && !strcmp(value, "user")) { tar_umask = umask(0); umask(tar_umask); } else { @@ -406,7 +406,7 @@ static void debug_info(const char *what, struct attr_stack *elem) { fprintf(stderr, "%s: %s\n", what, elem->origin ? elem->origin : "()"); } -static void debug_set(const char *what, const char *match, struct git_attr *attr, void *v) +static void debug_set(const char *what, const char *match, struct git_attr *attr, const void *v) { const char *value = v; @@ -543,10 +543,10 @@ static int path_matches(const char *pathname, int pathlen, if (*pattern == '/') pattern++; if (pathlen < baselen || - (baselen && pathname[baselen - 1] != '/') || + (baselen && pathname[baselen] != '/') || strncmp(pathname, base, baselen)) return 0; - return fnmatch(pattern, pathname + baselen, FNM_PATHNAME) == 0; + return fnmatch(pattern, pathname + baselen + 1, FNM_PATHNAME) == 0; } static int fill_one(const char *what, struct match_attr *a, int rem) diff --git a/builtin-add.c b/builtin-add.c index 4a91e3eb1..820110e08 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 30d86f219..a3f075df4 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -161,6 +161,84 @@ struct patch { struct patch *next; }; +/* + * A line in a file, len-bytes long (includes the terminating LF, + * except for an incomplete line at the end if the file ends with + * one), and its contents hashes to 'hash'. + */ +struct line { + size_t len; + unsigned hash : 24; + unsigned flag : 8; +#define LINE_COMMON 1 +}; + +/* + * This represents a "file", which is an array of "lines". + */ +struct image { + char *buf; + size_t len; + size_t nr; + size_t alloc; + struct line *line_allocated; + struct line *line; +}; + +static uint32_t hash_line(const char *cp, size_t len) +{ + size_t i; + uint32_t h; + for (i = 0, h = 0; i < len; i++) { + if (!isspace(cp[i])) { + h = h * 3 + (cp[i] & 0xff); + } + } + return h; +} + +static void add_line_info(struct image *img, const char *bol, size_t len, unsigned flag) +{ + ALLOC_GROW(img->line_allocated, img->nr + 1, img->alloc); + img->line_allocated[img->nr].len = len; + img->line_allocated[img->nr].hash = hash_line(bol, len); + img->line_allocated[img->nr].flag = flag; + img->nr++; +} + +static void prepare_image(struct image *image, char *buf, size_t len, + int prepare_linetable) +{ + const char *cp, *ep; + + memset(image, 0, sizeof(*image)); + image->buf = buf; + image->len = len; + + if (!prepare_linetable) + return; + + ep = image->buf + image->len; + cp = image->buf; + while (cp < ep) { + const char *next; + for (next = cp; next < ep && *next != '\n'; next++) + ; + if (next < ep) + next++; + add_line_info(image, cp, next - cp, 0); + cp = next; + } + image->line = image->line_allocated; +} + +static void clear_image(struct image *image) +{ + free(image->buf); + image->buf = NULL; + image->len = 0; +} + static void say_patch_name(FILE *output, const char *pre, struct patch *patch, const char *post) { @@ -1430,234 +1508,345 @@ 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; } } -static int find_offset(const char *buf, unsigned long size, - const char *fragment, unsigned long fragsize, - int line, int *lines) +static void update_pre_post_images(struct image *preimage, + struct image *postimage, + char *buf, + size_t len) { - int i; - unsigned long start, backwards, forwards; + int i, ctx; + char *new, *old, *fixed; + struct image fixed_preimage; - if (fragsize > size) - return -1; + /* + * Update the preimage with whitespace fixes. Note that we + * are not losing preimage->buf -- apply_one_fragment() will + * free "oldlines". + */ + prepare_image(&fixed_preimage, buf, len, 1); + assert(fixed_preimage.nr == preimage->nr); + for (i = 0; i < preimage->nr; i++) + fixed_preimage.line[i].flag = preimage->line[i].flag; + free(preimage->line_allocated); + *preimage = fixed_preimage; - start = 0; - if (line > 1) { - unsigned long offset = 0; - i = line-1; - while (offset + fragsize <= size) { - if (buf[offset++] == '\n') { - start = offset; - if (!--i) - break; - } + /* + * Adjust the common context lines in postimage, in place. + * This is possible because whitespace fixing does not make + * the string grow. + */ + new = old = postimage->buf; + fixed = preimage->buf; + for (i = ctx = 0; i < postimage->nr; i++) { + size_t len = postimage->line[i].len; + if (!(postimage->line[i].flag & LINE_COMMON)) { + /* an added line -- no counterparts in preimage */ + memmove(new, old, len); + old += len; + new += len; + continue; } + + /* a common context -- skip it in the original postimage */ + old += len; + + /* and find the corresponding one in the fixed preimage */ + while (ctx < preimage->nr && + !(preimage->line[ctx].flag & LINE_COMMON)) { + fixed += preimage->line[ctx].len; + ctx++; + } + if (preimage->nr <= ctx) + die("oops"); + + /* and copy it in, while fixing the line length */ + len = preimage->line[ctx].len; + memcpy(new, fixed, len); + new += len; + fixed += len; + postimage->line[i].len = len; + ctx++; } - /* Exact line number? */ - if ((start + fragsize <= size) && - !memcmp(buf + start, fragment, fragsize)) - return start; + /* Fix the length of the whole thing */ + postimage->len = new - postimage->buf; +} + +static int match_fragment(struct image *img, + struct image *preimage, + struct image *postimage, + unsigned long try, + int try_lno, + unsigned ws_rule, + int match_beginning, int match_end) +{ + int i; + char *fixed_buf, *buf, *orig, *target; + + if (preimage->nr + try_lno > img->nr) + return 0; + + if (match_beginning && try_lno) + return 0; + + if (match_end && preimage->nr + try_lno != img->nr) + return 0; + + /* Quick hash check */ + for (i = 0; i < preimage->nr; i++) + if (preimage->line[i].hash != img->line[try_lno + i].hash) + return 0; + + /* + * Do we have an exact match? If we were told to match + * at the end, size must be exactly at try+fragsize, + * otherwise try+fragsize must be still within the preimage, + * and either case, the old piece should match the preimage + * exactly. + */ + if ((match_end + ? (try + preimage->len == img->len) + : (try + preimage->len <= img->len)) && + !memcmp(img->buf + try, preimage->buf, preimage->len)) + return 1; + + if (ws_error_action != correct_ws_error) + return 0; + + /* + * The hunk does not apply byte-by-byte, but the hash says + * it might with whitespace fuzz. + */ + fixed_buf = xmalloc(preimage->len + 1); + buf = fixed_buf; + orig = preimage->buf; + target = img->buf + try; + for (i = 0; i < preimage->nr; i++) { + size_t fixlen; /* length after fixing the preimage */ + size_t oldlen = preimage->line[i].len; + size_t tgtlen = img->line[try_lno + i].len; + size_t tgtfixlen; /* length after fixing the target line */ + char tgtfixbuf[1024], *tgtfix; + int match; + + /* Try fixing the line in the preimage */ + fixlen = ws_fix_copy(buf, orig, oldlen, ws_rule, NULL); + + /* Try fixing the line in the target */ + if (sizeof(tgtfixbuf) < tgtlen) + tgtfix = tgtfixbuf; + else + tgtfix = xmalloc(tgtlen); + tgtfixlen = ws_fix_copy(tgtfix, target, tgtlen, ws_rule, NULL); + + /* + * If they match, either the preimage was based on + * a version before our tree fixed whitespace breakage, + * or we are lacking a whitespace-fix patch the tree + * the preimage was based on already had (i.e. target + * has whitespace breakage, the preimage doesn't). + * In either case, we are fixing the whitespace breakages + * so we might as well take the fix together with their + * real change. + */ + match = (tgtfixlen == fixlen && !memcmp(tgtfix, buf, fixlen)); + + if (tgtfix != tgtfixbuf) + free(tgtfix); + if (!match) + goto unmatch_exit; + + orig += oldlen; + buf += fixlen; + target += tgtlen; + } + + /* + * Yes, the preimage is based on an older version that still + * has whitespace breakages unfixed, and fixing them makes the + * hunk match. Update the context lines in the postimage. + */ + update_pre_post_images(preimage, postimage, + fixed_buf, buf - fixed_buf); + return 1; + + unmatch_exit: + free(fixed_buf); + return 0; +} + +static int find_pos(struct image *img, + struct image *preimage, + struct image *postimage, + int line, + unsigned ws_rule, + int match_beginning, int match_end) +{ + int i; + unsigned long backwards, forwards, try; + int backwards_lno, forwards_lno, try_lno; + + if (preimage->nr > img->nr) + return -1; + + /* + * If match_begining or match_end is specified, there is no + * point starting from a wrong line that will never match and + * wander around and wait for a match at the specified end. + */ + if (match_beginning) + line = 0; + else if (match_end) + line = img->nr - preimage->nr; + + if (line > img->nr) + line = img->nr; + + try = 0; + for (i = 0; i < line; i++) + try += img->line[i].len; /* * There's probably some smart way to do this, but I'll leave * that to the smart and beautiful people. I'm simple and stupid. */ - backwards = start; - forwards = start; + backwards = try; + backwards_lno = line; + forwards = try; + forwards_lno = line; + try_lno = line; + for (i = 0; ; i++) { - unsigned long try; - int n; + if (match_fragment(img, preimage, postimage, + try, try_lno, ws_rule, + match_beginning, match_end)) + return try_lno; + + again: + if (backwards_lno == 0 && forwards_lno == img->nr) + break; - /* "backward" */ if (i & 1) { - if (!backwards) { - if (forwards + fragsize > size) - break; - continue; + if (backwards_lno == 0) { + i++; + goto again; } - do { - --backwards; - } while (backwards && buf[backwards-1] != '\n'); + backwards_lno--; + backwards -= img->line[backwards_lno].len; try = backwards; + try_lno = backwards_lno; } else { - while (forwards + fragsize <= size) { - if (buf[forwards++] == '\n') - break; + if (forwards_lno == img->nr) { + i++; + goto again; } + forwards += img->line[forwards_lno].len; + forwards_lno++; try = forwards; + try_lno = forwards_lno; } - if (try + fragsize > size) - continue; - if (memcmp(buf + try, fragment, fragsize)) - continue; - n = (i >> 1)+1; - if (i & 1) - n = -n; - *lines = n; - return try; } - - /* - * We should start searching forward and backward. - */ return -1; } -static void remove_first_line(const char **rbuf, int *rsize) +static void remove_first_line(struct image *img) { - const char *buf = *rbuf; - int size = *rsize; - unsigned long offset; - offset = 0; - while (offset <= size) { - if (buf[offset++] == '\n') - break; - } - *rsize = size - offset; - *rbuf = buf + offset; + img->buf += img->line[0].len; + img->len -= img->line[0].len; + img->line++; + img->nr--; } -static void remove_last_line(const char **rbuf, int *rsize) +static void remove_last_line(struct image *img) { - const char *buf = *rbuf; - int size = *rsize; - unsigned long offset; - offset = size - 1; - while (offset > 0) { - if (buf[--offset] == '\n') - break; - } - *rsize = offset + 1; + img->len -= img->line[--img->nr].len; } -static int apply_line(char *output, const char *patch, int plen, - unsigned ws_rule) +static void update_image(struct image *img, + int applied_pos, + struct image *preimage, + struct image *postimage) { /* - * plen is number of bytes to be copied from patch, - * starting at patch+1 (patch[0] is '+'). Typically - * patch[plen] is '\n', unless this is the incomplete - * last line. + * remove the copy of preimage at offset in img + * and replace it with postimage */ - int i; - int add_nl_to_tail = 0; - int fixed = 0; - int last_tab_in_indent = 0; - int last_space_in_indent = 0; - int need_fix_leading_space = 0; - char *buf; - - if ((ws_error_action != correct_ws_error) || !whitespace_error || - *patch != '+') { - memcpy(output, patch + 1, plen); - return plen; - } - - /* - * Strip trailing whitespace - */ - if ((ws_rule & WS_TRAILING_SPACE) && - (1 < plen && isspace(patch[plen-1]))) { - if (patch[plen] == '\n') - add_nl_to_tail = 1; - plen--; - while (0 < plen && isspace(patch[plen])) - plen--; - fixed = 1; - } - - /* - * Check leading whitespaces (indent) - */ - for (i = 1; i < plen; i++) { - char ch = patch[i]; - if (ch == '\t') { - last_tab_in_indent = i; - if ((ws_rule & WS_SPACE_BEFORE_TAB) && - 0 < last_space_in_indent) - need_fix_leading_space = 1; - } else if (ch == ' ') { - last_space_in_indent = i; - if ((ws_rule & WS_INDENT_WITH_NON_TAB) && - 8 <= i - last_tab_in_indent) - need_fix_leading_space = 1; - } - else - break; - } - - buf = output; - if (need_fix_leading_space) { - int consecutive_spaces = 0; - int last = last_tab_in_indent + 1; - - if (ws_rule & WS_INDENT_WITH_NON_TAB) { - /* have "last" point at one past the indent */ - if (last_tab_in_indent < last_space_in_indent) - last = last_space_in_indent + 1; - else - last = last_tab_in_indent + 1; - } + int i, nr; + size_t remove_count, insert_count, applied_at = 0; + char *result; + for (i = 0; i < applied_pos; i++) + applied_at += img->line[i].len; + + remove_count = 0; + for (i = 0; i < preimage->nr; i++) + remove_count += img->line[applied_pos + i].len; + insert_count = postimage->len; + + /* Adjust the contents */ + result = xmalloc(img->len + insert_count - remove_count + 1); + memcpy(result, img->buf, applied_at); + memcpy(result + applied_at, postimage->buf, postimage->len); + memcpy(result + applied_at + postimage->len, + img->buf + (applied_at + remove_count), + img->len - (applied_at + remove_count)); + free(img->buf); + img->buf = result; + img->len += insert_count - remove_count; + result[img->len] = '\0'; + + /* Adjust the line table */ + nr = img->nr + postimage->nr - preimage->nr; + if (preimage->nr < postimage->nr) { /* - * between patch[1..last], strip the funny spaces, - * updating them to tab as needed. + * NOTE: this knows that we never call remove_first_line() + * on anything other than pre/post image. */ - for (i = 1; i < last; i++, plen--) { - char ch = patch[i]; - if (ch != ' ') { - consecutive_spaces = 0; - *output++ = ch; - } else { - consecutive_spaces++; - if (consecutive_spaces == 8) { - *output++ = '\t'; - consecutive_spaces = 0; - } - } - } - while (0 < consecutive_spaces--) - *output++ = ' '; - fixed = 1; - i = last; + img->line = xrealloc(img->line, nr * sizeof(*img->line)); + img->line_allocated = img->line; } - else - i = 1; - - memcpy(output, patch + i, plen); - if (add_nl_to_tail) - output[plen++] = '\n'; - if (fixed) - applied_after_fixing_ws++; - return output + plen - buf; + if (preimage->nr != postimage->nr) + memmove(img->line + applied_pos + postimage->nr, + img->line + applied_pos + preimage->nr, + (img->nr - (applied_pos + preimage->nr)) * + sizeof(*img->line)); + memcpy(img->line + applied_pos, + postimage->line, + postimage->nr * sizeof(*img->line)); + img->nr = nr; } -static int apply_one_fragment(struct strbuf *buf, struct fragment *frag, +static int apply_one_fragment(struct image *img, struct fragment *frag, int inaccurate_eof, unsigned ws_rule) { int match_beginning, match_end; const char *patch = frag->patch; - int offset, size = frag->size; - char *old = xmalloc(size); - char *new = xmalloc(size); - const char *oldlines, *newlines; - int oldsize = 0, newsize = 0; + int size = frag->size; + char *old, *new, *oldlines, *newlines; int new_blank_lines_at_end = 0; unsigned long leading, trailing; - int pos, lines; + int pos, applied_pos; + struct image preimage; + struct image postimage; + + memset(&preimage, 0, sizeof(preimage)); + memset(&postimage, 0, sizeof(postimage)); + oldlines = xmalloc(size); + newlines = xmalloc(size); + old = oldlines; + new = newlines; while (size > 0) { char first; int len = linelen(patch, size); - int plen; + int plen, added; int added_blank_line = 0; if (!len) @@ -1670,7 +1859,7 @@ static int apply_one_fragment(struct strbuf *buf, struct fragment *frag, * followed by "\ No newline", then we also remove the * last one (which is the newline, of course). */ - plen = len-1; + plen = len - 1; if (len < size && patch[len] == '\\') plen--; first = *patch; @@ -1687,25 +1876,40 @@ static int apply_one_fragment(struct strbuf *buf, struct fragment *frag, if (plen < 0) /* ... followed by '\No newline'; nothing */ break; - old[oldsize++] = '\n'; - new[newsize++] = '\n'; + *old++ = '\n'; + *new++ = '\n'; + add_line_info(&preimage, "\n", 1, LINE_COMMON); + add_line_info(&postimage, "\n", 1, LINE_COMMON); break; case ' ': case '-': - memcpy(old + oldsize, patch + 1, plen); - oldsize += plen; + memcpy(old, patch + 1, plen); + add_line_info(&preimage, old, plen, + (first == ' ' ? LINE_COMMON : 0)); + old += plen; if (first == '-') break; /* Fall-through for ' ' */ case '+': - if (first != '+' || !no_add) { - int added = apply_line(new + newsize, patch, - plen, ws_rule); - newsize += added; - if (first == '+' && - added == 1 && new[newsize-1] == '\n') - added_blank_line = 1; + /* --no-add does not add new lines */ + if (first == '+' && no_add) + break; + + if (first != '+' || + !whitespace_error || + ws_error_action != correct_ws_error) { + memcpy(new, patch + 1, plen); + added = plen; } + else { + added = ws_fix_copy(new, patch + 1, plen, ws_rule, &applied_after_fixing_ws); + } + add_line_info(&postimage, new, added, + (first == '+' ? 0 : LINE_COMMON)); + new += added; + if (first == '+' && + added == 1 && new[-1] == '\n') + added_blank_line = 1; break; case '@': case '\\': /* Ignore it, we already handled it */ @@ -1722,16 +1926,13 @@ static int apply_one_fragment(struct strbuf *buf, struct fragment *frag, patch += len; size -= len; } - if (inaccurate_eof && - oldsize > 0 && old[oldsize - 1] == '\n' && - newsize > 0 && new[newsize - 1] == '\n') { - oldsize--; - newsize--; + old > oldlines && old[-1] == '\n' && + new > newlines && new[-1] == '\n') { + old--; + new--; } - oldlines = old; - newlines = new; leading = frag->leading; trailing = frag->trailing; @@ -1752,33 +1953,21 @@ static int apply_one_fragment(struct strbuf *buf, struct fragment *frag, match_end = !trailing; } - lines = 0; - pos = frag->newpos; + pos = frag->newpos ? (frag->newpos - 1) : 0; + preimage.buf = oldlines; + preimage.len = old - oldlines; + postimage.buf = newlines; + postimage.len = new - newlines; + preimage.line = preimage.line_allocated; + postimage.line = postimage.line_allocated; + for (;;) { - offset = find_offset(buf->buf, buf->len, - oldlines, oldsize, pos, &lines); - if (match_end && offset + oldsize != buf->len) - offset = -1; - if (match_beginning && offset) - offset = -1; - if (offset >= 0) { - if (ws_error_action == correct_ws_error && - (buf->len - oldsize - offset == 0)) /* end of file? */ - newsize -= new_blank_lines_at_end; - - /* Warn if it was necessary to reduce the number - * of context lines. - */ - if ((leading != frag->leading) || - (trailing != frag->trailing)) - fprintf(stderr, "Context reduced to (%ld/%ld)" - " to apply fragment at %d\n", - leading, trailing, pos + lines); - - strbuf_splice(buf, offset, oldsize, newlines, newsize); - offset = 0; + + applied_pos = find_pos(img, &preimage, &postimage, pos, + ws_rule, match_beginning, match_end); + + if (applied_pos >= 0) break; - } /* Am I at my context limits? */ if ((leading <= p_context) && (trailing <= p_context)) @@ -1787,33 +1976,64 @@ static int apply_one_fragment(struct strbuf *buf, struct fragment *frag, match_beginning = match_end = 0; continue; } + /* * Reduce the number of context lines; reduce both * leading and trailing if they are equal otherwise * just reduce the larger context. */ if (leading >= trailing) { - remove_first_line(&oldlines, &oldsize); - remove_first_line(&newlines, &newsize); + remove_first_line(&preimage); + remove_first_line(&postimage); pos--; leading--; } if (trailing > leading) { - remove_last_line(&oldlines, &oldsize); - remove_last_line(&newlines, &newsize); + remove_last_line(&preimage); + remove_last_line(&postimage); trailing--; } } - if (offset && apply_verbosely) - error("while searching for:\n%.*s", oldsize, oldlines); + if (applied_pos >= 0) { + if (ws_error_action == correct_ws_error && + new_blank_lines_at_end && + postimage.nr + applied_pos == img->nr) { + /* + * If the patch application adds blank lines + * at the end, and if the patch applies at the + * end of the image, remove those added blank + * lines. + */ + while (new_blank_lines_at_end--) + remove_last_line(&postimage); + } - free(old); - free(new); - return offset; + /* + * Warn if it was necessary to reduce the number + * of context lines. + */ + if ((leading != frag->leading) || + (trailing != frag->trailing)) + fprintf(stderr, "Context reduced to (%ld/%ld)" + " to apply fragment at %d\n", + leading, trailing, applied_pos+1); + update_image(img, applied_pos, &preimage, &postimage); + } else { + if (apply_verbosely) + error("while searching for:\n%.*s", + (int)(old - oldlines), oldlines); + } + + free(oldlines); + free(newlines); + free(preimage.line_allocated); + free(postimage.line_allocated); + + return (applied_pos < 0); } -static int apply_binary_fragment(struct strbuf *buf, struct patch *patch) +static int apply_binary_fragment(struct image *img, struct patch *patch) { struct fragment *fragment = patch->fragments; unsigned long len; @@ -1830,22 +2050,26 @@ static int apply_binary_fragment(struct strbuf *buf, struct patch *patch) } switch (fragment->binary_patch_method) { case BINARY_DELTA_DEFLATED: - dst = patch_delta(buf->buf, buf->len, fragment->patch, + dst = patch_delta(img->buf, img->len, fragment->patch, fragment->size, &len); if (!dst) return -1; - /* XXX patch_delta NUL-terminates */ - strbuf_attach(buf, dst, len, len + 1); + clear_image(img); + img->buf = dst; + img->len = len; return 0; case BINARY_LITERAL_DEFLATED: - strbuf_reset(buf); - strbuf_add(buf, fragment->patch, fragment->size); + clear_image(img); + img->len = fragment->size; + img->buf = xmalloc(img->len+1); + memcpy(img->buf, fragment->patch, img->len); + img->buf[img->len] = '\0'; return 0; } return -1; } -static int apply_binary(struct strbuf *buf, struct patch *patch) +static int apply_binary(struct image *img, struct patch *patch) { const char *name = patch->old_name ? patch->old_name : patch->new_name; unsigned char sha1[20]; @@ -1866,7 +2090,7 @@ static int apply_binary(struct strbuf *buf, struct patch *patch) * See if the old one matches what the patch * applies to. */ - hash_sha1_file(buf->buf, buf->len, blob_type, sha1); + hash_sha1_file(img->buf, img->len, blob_type, sha1); if (strcmp(sha1_to_hex(sha1), patch->old_sha1_prefix)) return error("the patch applies to '%s' (%s), " "which does not match the " @@ -1875,14 +2099,14 @@ static int apply_binary(struct strbuf *buf, struct patch *patch) } else { /* Otherwise, the old one must be empty. */ - if (buf->len) + if (img->len) return error("the patch applies to an empty " "'%s' but it is not empty", name); } get_sha1_hex(patch->new_sha1_prefix, sha1); if (is_null_sha1(sha1)) { - strbuf_release(buf); + clear_image(img); return 0; /* deletion patch */ } @@ -1897,20 +2121,21 @@ static int apply_binary(struct strbuf *buf, struct patch *patch) return error("the necessary postimage %s for " "'%s' cannot be read", patch->new_sha1_prefix, name); - /* XXX read_sha1_file NUL-terminates */ - strbuf_attach(buf, result, size, size + 1); + clear_image(img); + img->buf = result; + img->len = size; } else { /* * We have verified buf matches the preimage; * apply the patch data to it, which is stored * in the patch->fragments->{patch,size}. */ - if (apply_binary_fragment(buf, patch)) + if (apply_binary_fragment(img, patch)) return error("binary patch does not apply to '%s'", name); /* verify that the result matches */ - hash_sha1_file(buf->buf, buf->len, blob_type, sha1); + hash_sha1_file(img->buf, img->len, blob_type, sha1); if (strcmp(sha1_to_hex(sha1), patch->new_sha1_prefix)) return error("binary patch to '%s' creates incorrect result (expecting %s, got %s)", name, patch->new_sha1_prefix, sha1_to_hex(sha1)); @@ -1919,7 +2144,7 @@ static int apply_binary(struct strbuf *buf, struct patch *patch) return 0; } -static int apply_fragments(struct strbuf *buf, struct patch *patch) +static int apply_fragments(struct image *img, struct patch *patch) { struct fragment *frag = patch->fragments; const char *name = patch->old_name ? patch->old_name : patch->new_name; @@ -1927,10 +2152,10 @@ static int apply_fragments(struct strbuf *buf, struct patch *patch) unsigned inaccurate_eof = patch->inaccurate_eof; if (patch->is_binary) - return apply_binary(buf, patch); + return apply_binary(img, patch); while (frag) { - if (apply_one_fragment(buf, frag, inaccurate_eof, ws_rule)) { + if (apply_one_fragment(img, frag, inaccurate_eof, ws_rule)) { error("patch failed: %s:%ld", name, frag->oldpos); if (!apply_with_reject) return -1; @@ -1966,6 +2191,9 @@ static int read_file_or_gitlink(struct cache_entry *ce, struct strbuf *buf) static int apply_data(struct patch *patch, struct stat *st, struct cache_entry *ce) { struct strbuf buf; + struct image image; + size_t len; + char *img; strbuf_init(&buf, 0); if (cached) { @@ -1988,9 +2216,14 @@ static int apply_data(struct patch *patch, struct stat *st, struct cache_entry * } } - if (apply_fragments(&buf, patch) < 0) + img = strbuf_detach(&buf, &len); + prepare_image(&image, img, len, !patch->is_binary); + + if (apply_fragments(&image, patch) < 0) return -1; /* note with --reject this succeeds. */ - patch->result = strbuf_detach(&buf, &patch->resultsize); + patch->result = image.buf; + patch->resultsize = image.len; + free(image.line_allocated); if (0 < patch->is_delete && patch->resultsize) return error("removal patch leaves file contents"); @@ -2746,6 +2979,8 @@ static int apply_patch(int fd, const char *filename, int inaccurate_eof) static int git_apply_config(const char *var, const char *value) { if (!strcmp(var, "apply.whitespace")) { + if (!value) + return config_error_nonbool(var); apply_default_whitespace = xstrdup(value); return 0; } diff --git a/builtin-blame.c b/builtin-blame.c index c7e68874e..59d7237f2 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); @@ -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 1e0c9dea3..7e991030c 100644 --- a/builtin-branch.c +++ b/builtin-branch.c @@ -32,7 +32,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) */ @@ -71,18 +71,21 @@ static int git_branch_config(const char *var, const char *value) } if (!prefixcmp(var, "color.branch.")) { int slot = parse_branch_color_slot(var, 13); + if (!value) + return config_error_nonbool(var); color_parse(value, var, branch_colors[slot]); return 0; } - if (!strcmp(var, "branch.autosetupmerge")) - branch_track = git_config_bool(var, value); - - return git_default_config(var, value); + if (!strcmp(var, "branch.autosetupmerge")) { + branch_track = git_config_bool(var, value); + return 0; + } + 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 ""; } @@ -451,6 +454,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 eb853a37c..3b220d506 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 5b5b7c0f4..f49c22e64 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" @@ -122,19 +123,23 @@ static void rollback_index_files(void) } } -static void commit_index_files(void) +static int commit_index_files(void) { + int err = 0; + switch (commit_style) { case COMMIT_AS_IS: break; /* nothing to do */ case COMMIT_NORMAL: - commit_lock_file(&index_lock); + err = commit_lock_file(&index_lock); break; case COMMIT_PARTIAL: - commit_lock_file(&index_lock); + err = commit_lock_file(&index_lock); rollback_lock_file(&false_lock); break; } + + return err; } /* @@ -314,6 +319,10 @@ static char *prepare_index(int argc, const char **argv, const char *prefix) if (write_cache(fd, active_cache, active_nr) || close_lock_file(&false_lock)) die("unable to write temporary index file"); + + discard_cache(); + read_cache_from(false_lock.filename); + return false_lock.filename; } @@ -340,45 +349,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)); @@ -410,13 +481,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"); @@ -424,48 +520,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; - return commitable; + 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), NULL)) { + return 0; + } + + return 1; } /* @@ -562,6 +670,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; @@ -598,7 +708,7 @@ static int parse_and_validate_options(int argc, const char *argv[], if (get_sha1(use_message, sha1)) die("could not lookup commit %s", use_message); - commit = lookup_commit(sha1); + commit = lookup_commit_reference(sha1); if (!commit || parse_commit(commit)) die("could not parse commit %s", use_message); @@ -663,6 +773,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); @@ -674,31 +787,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; @@ -740,6 +828,8 @@ static void print_summary(const char *prefix, const unsigned char *sha1) int git_commit_config(const char *k, const char *v) { if (!strcmp(k, "commit.template")) { + if (!v) + return config_error_nonbool(v); template_file = xstrdup(v); return 0; } @@ -747,14 +837,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" @@ -786,33 +868,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 */ @@ -864,19 +926,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"); @@ -926,8 +977,12 @@ int cmd_commit(int argc, const char **argv, const char *prefix) unlink(git_path("MERGE_HEAD")); unlink(git_path("MERGE_MSG")); + unlink(git_path("SQUASH_MSG")); - commit_index_files(); + if (commit_index_files()) + die ("Repository has been updated, but unable to write\n" + "new_index file. Check that disk is not full or quota is\n" + "not exceeded, and then \"git reset HEAD\" to recover."); rerere(); run_hook(get_index_file(), "post-commit", NULL); diff --git a/builtin-config.c b/builtin-config.c index e4a12e316..2b9a4261d 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_); @@ -168,6 +169,8 @@ static char parsed_color[COLOR_MAXLEN]; static int git_get_color_config(const char *var, const char *value) { if (!strcmp(var, get_color_slot)) { + if (!value) + config_error_nonbool(var); color_parse(value, var, parsed_color); get_color_found = 1; } diff --git a/builtin-describe.c b/builtin-describe.c index 7a148a2c2..05e309f5a 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 */ @@ -45,21 +46,38 @@ static void add_to_known_names(const char *path, static int get_name(const char *path, const unsigned char *sha1, int flag, void *cb_data) { - struct commit *commit = lookup_commit_reference_gently(sha1, 1); + int might_be_tag = !prefixcmp(path, "refs/tags/"); + struct commit *commit; struct object *object; - int prio; + unsigned char peeled[20]; + int is_tag, prio; - if (!commit) + if (!all && !might_be_tag) return 0; - object = parse_object(sha1); + + if (!peel_ref(path, peeled) && !is_null_sha1(peeled)) { + commit = lookup_commit_reference_gently(peeled, 1); + if (!commit) + return 0; + is_tag = !!hashcmp(sha1, commit->object.sha1); + } else { + commit = lookup_commit_reference_gently(sha1, 1); + object = parse_object(sha1); + if (!commit || !object) + return 0; + is_tag = object->type == OBJ_TAG; + } + /* If --all, then any refs are used. * If --tags, then any tags are used. * Otherwise only annotated tags are used. */ - if (!prefixcmp(path, "refs/tags/")) { - if (object->type == OBJ_TAG) + if (might_be_tag) { + if (is_tag) { prio = 2; - else + if (pattern && fnmatch(pattern, path + 10, 0)) + prio = 0; + } else prio = 1; } else @@ -156,6 +174,8 @@ static void describe(const char *arg, int last_one) return; } + if (!max_candidates) + die("no tag exactly matches '%s'", sha1_to_hex(cmit->object.sha1)); if (debug) fprintf(stderr, "searching to describe %s\n", arg); @@ -252,26 +272,37 @@ int cmd_describe(int argc, const char **argv, const char *prefix) OPT_BOOLEAN(0, "all", &all, "use any ref in .git/refs"), OPT_BOOLEAN(0, "tags", &tags, "use any tag in .git/refs/tags"), OPT__ABBREV(&abbrev), + OPT_SET_INT(0, "exact-match", &max_candidates, + "only output exact matches", 0), 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(), }; argc = parse_options(argc, argv, options, describe_usage, 0); - if (max_candidates < 1) - max_candidates = 1; + if (max_candidates < 0) + max_candidates = 0; else if (max_candidates > MAX_TAGS) max_candidates = MAX_TAGS; 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 8d7a5697f..444ff2fd9 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" @@ -43,12 +44,17 @@ static void stuff_change(struct diff_options *opt, tmp_u = old_sha1; old_sha1 = new_sha1; new_sha1 = tmp_u; tmp_c = old_name; old_name = new_name; new_name = tmp_c; } + + if (opt->prefix && + (strncmp(old_name, opt->prefix, opt->prefix_length) || + strncmp(new_name, opt->prefix, opt->prefix_length))) + return; + one = alloc_filespec(old_name); two = alloc_filespec(new_name); fill_filespec(one, old_sha1, old_mode); fill_filespec(two, new_sha1, new_mode); - /* NEEDSWORK: shouldn't this part of diffopt??? */ diff_queue(&diff_queued_diff, one, two); } @@ -229,6 +235,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; @@ -241,6 +251,10 @@ int cmd_diff(int argc, const char **argv, const char *prefix) if (diff_setup_done(&rev.diffopt) < 0) die("diff_setup_done failed"); } + if (rev.diffopt.prefix && nongit) { + rev.diffopt.prefix = NULL; + rev.diffopt.prefix_length = 0; + } DIFF_OPT_SET(&rev.diffopt, ALLOW_EXTERNAL); DIFF_OPT_SET(&rev.diffopt, RECURSIVE); diff --git a/builtin-fast-export.c b/builtin-fast-export.c index ef27eee71..4bd1356d5 100755 --- a/builtin-fast-export.c +++ b/builtin-fast-export.c @@ -123,7 +123,7 @@ static void show_filemodify(struct diff_queue_struct *q, printf("D %s\n", spec->path); else { struct object *object = lookup_object(spec->sha1); - printf("M 0%06o :%d %s\n", spec->mode, + printf("M %06o :%d %s\n", spec->mode, get_object_mark(object), spec->path); } } @@ -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 e68e01592..5ea48ca7d 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; @@ -537,8 +538,10 @@ static int get_pack(int xd[2], char **pack_lockfile) cmd.git_cmd = 1; if (start_command(&cmd)) die("fetch-pack: unable to fork off %s", argv[0]); - if (do_keep && pack_lockfile) + if (do_keep && pack_lockfile) { *pack_lockfile = index_pack_lockfile(cmd.out); + close(cmd.out); + } if (finish_command(&cmd)) die("%s failed", argv[0]); @@ -548,14 +551,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 +576,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 +649,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 +707,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 +744,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 +761,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 +797,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 320e23568..ac335f20f 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 6163bd497..ebb3f37cf 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-for-each-ref.c b/builtin-for-each-ref.c index f36a43c26..07d9c5721 100644 --- a/builtin-for-each-ref.c +++ b/builtin-for-each-ref.c @@ -165,7 +165,7 @@ static int verify_format(const char *format) for (cp = format; *cp && (sp = find_next(cp)); ) { const char *ep = strchr(sp, ')'); if (!ep) - return error("malformatted format string %s", sp); + return error("malformed format string %s", sp); /* sp points at "%(" and ep points at the closing ")" */ parse_atom(sp + 2, ep); cp = ep + 1; diff --git a/builtin-fsck.c b/builtin-fsck.c index 6fc9525e0..cc7524be8 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; } diff --git a/builtin-gc.c b/builtin-gc.c index ac34788c8..ad4a75eed 100644 --- a/builtin-gc.c +++ b/builtin-gc.c @@ -37,7 +37,7 @@ static const char *argv_rerere[] = {"rerere", "gc", NULL}; static int gc_config(const char *var, const char *value) { if (!strcmp(var, "gc.packrefs")) { - if (!strcmp(value, "notbare")) + if (value && !strcmp(value, "notbare")) pack_refs = -1; else pack_refs = git_config_bool(var, value); diff --git a/builtin-grep.c b/builtin-grep.c index 9180b39e3..f4f4ecb11 100644 --- a/builtin-grep.c +++ b/builtin-grep.c @@ -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 e1393b8d1..79eaf8d6e 100644 --- a/builtin-init-db.c +++ b/builtin-init-db.c @@ -29,27 +29,6 @@ static void safe_create_dir(const char *dir, int share) die("Could not make %s writable by group\n", dir); } -static int copy_file(const char *dst, const char *src, int mode) -{ - int fdi, fdo, status; - - mode = (mode & 0111) ? 0777 : 0666; - if ((fdi = open(src, O_RDONLY)) < 0) - return fdi; - if ((fdo = open(dst, O_WRONLY | O_CREAT | O_EXCL, mode)) < 0) { - close(fdi); - return fdo; - } - status = copy_fd(fdi, fdo); - if (close(fdo) != 0) - return error("%s: write error: %s", dst, strerror(errno)); - - if (!status && adjust_shared_perm(dst)) - return -1; - - return status; -} - static void copy_templates_1(char *path, int baselen, char *template, int template_baselen, DIR *dir) @@ -141,9 +120,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 dcc9f8179..3209ea5c6 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" @@ -14,6 +15,8 @@ #include "reflog-walk.h" #include "patch-ids.h" #include "refs.h" +#include "run-command.h" +#include "shortlog.h" static int default_show_root = 1; static const char *fmt_patch_subject_prefix = "PATCH"; @@ -197,7 +200,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); @@ -219,7 +223,7 @@ static int git_log_config(const char *var, const char *value) { if (!strcmp(var, "format.subjectprefix")) { if (!value) - die("format.subjectprefix without value"); + config_error_nonbool(var); fmt_patch_subject_prefix = xstrdup(value); return 0; } @@ -235,6 +239,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 +315,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 +379,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 +411,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); @@ -410,29 +430,52 @@ static int istitlechar(char c) (c >= '0' && c <= '9') || c == '.' || c == '_'; } -static char *extra_headers = NULL; -static int extra_headers_size = 0; static const char *fmt_patch_suffix = ".patch"; static int numbered = 0; static int auto_number = 0; +static char **extra_hdr; +static int extra_hdr_nr; +static int extra_hdr_alloc; + +static char **extra_to; +static int extra_to_nr; +static int extra_to_alloc; + +static char **extra_cc; +static int extra_cc_nr; +static int extra_cc_alloc; + +static void add_header(const char *value) +{ + int len = strlen(value); + while (value[len - 1] == '\n') + len--; + if (!strncasecmp(value, "to: ", 4)) { + ALLOC_GROW(extra_to, extra_to_nr + 1, extra_to_alloc); + extra_to[extra_to_nr++] = xstrndup(value + 4, len - 4); + return; + } + if (!strncasecmp(value, "cc: ", 4)) { + ALLOC_GROW(extra_cc, extra_cc_nr + 1, extra_cc_alloc); + extra_cc[extra_cc_nr++] = xstrndup(value + 4, len - 4); + return; + } + ALLOC_GROW(extra_hdr, extra_hdr_nr + 1, extra_hdr_alloc); + extra_hdr[extra_hdr_nr++] = xstrndup(value, len); +} + static int git_format_config(const char *var, const char *value) { if (!strcmp(var, "format.headers")) { - int len; - if (!value) die("format.headers without value"); - len = strlen(value); - extra_headers_size += len + 1; - extra_headers = xrealloc(extra_headers, extra_headers_size); - extra_headers[extra_headers_size - len - 1] = 0; - strcat(extra_headers, value); + add_header(value); return 0; } if (!strcmp(var, "format.suffix")) { if (!value) - die("format.suffix without value"); + return config_error_nonbool(var); fmt_patch_suffix = xstrdup(value); return 0; } @@ -440,11 +483,10 @@ static int git_format_config(const char *var, const char *value) return 0; } if (!strcmp(var, "format.numbered")) { - if (!strcasecmp(value, "auto")) { + if (value && !strcasecmp(value, "auto")) { auto_number = 1; return 0; } - numbered = git_config_bool(var, value); return 0; } @@ -453,74 +495,81 @@ static int git_format_config(const char *var, const char *value) } +static const char *get_oneline_for_filename(struct commit *commit, + int keep_subject) +{ + static char filename[PATH_MAX]; + char *sol; + int len = 0; + int suffix_len = strlen(fmt_patch_suffix) + 1; + + sol = strstr(commit->buffer, "\n\n"); + if (!sol) + filename[0] = '\0'; + else { + int j, space = 0; + + sol += 2; + /* strip [PATCH] or [PATCH blabla] */ + if (!keep_subject && !prefixcmp(sol, "[PATCH")) { + char *eos = strchr(sol + 6, ']'); + if (eos) { + while (isspace(*eos)) + eos++; + sol = eos; + } + } + + for (j = 0; + j < FORMAT_PATCH_NAME_MAX - suffix_len - 5 && + len < sizeof(filename) - suffix_len && + sol[j] && sol[j] != '\n'; + j++) { + if (istitlechar(sol[j])) { + if (space) { + filename[len++] = '-'; + space = 0; + } + filename[len++] = sol[j]; + if (sol[j] == '.') + while (sol[j + 1] == '.') + j++; + } else + space = 1; + } + while (filename[len - 1] == '.' + || filename[len - 1] == '-') + len--; + filename[len] = '\0'; + } + return filename; +} + static FILE *realstdout = NULL; static const char *output_directory = NULL; -static int reopen_stdout(struct commit *commit, int nr, int keep_subject, - int numbered_files) +static int reopen_stdout(const char *oneline, int nr, int total) { char filename[PATH_MAX]; - char *sol; int len = 0; int suffix_len = strlen(fmt_patch_suffix) + 1; if (output_directory) { - if (strlen(output_directory) >= + len = snprintf(filename, sizeof(filename), "%s", + output_directory); + if (len >= sizeof(filename) - FORMAT_PATCH_NAME_MAX - suffix_len) return error("name of output directory is too long"); - strlcpy(filename, output_directory, sizeof(filename) - suffix_len); - len = strlen(filename); if (filename[len - 1] != '/') filename[len++] = '/'; } - if (numbered_files) { - sprintf(filename + len, "%d", nr); - len = strlen(filename); - - } else { - sprintf(filename + len, "%04d", nr); - len = strlen(filename); - - sol = strstr(commit->buffer, "\n\n"); - if (sol) { - int j, space = 1; - - sol += 2; - /* strip [PATCH] or [PATCH blabla] */ - if (!keep_subject && !prefixcmp(sol, "[PATCH")) { - char *eos = strchr(sol + 6, ']'); - if (eos) { - while (isspace(*eos)) - eos++; - sol = eos; - } - } - - for (j = 0; - j < FORMAT_PATCH_NAME_MAX - suffix_len - 5 && - len < sizeof(filename) - suffix_len && - sol[j] && sol[j] != '\n'; - j++) { - if (istitlechar(sol[j])) { - if (space) { - filename[len++] = '-'; - space = 0; - } - filename[len++] = sol[j]; - if (sol[j] == '.') - while (sol[j + 1] == '.') - j++; - } else - space = 1; - } - while (filename[len - 1] == '.' - || filename[len - 1] == '-') - len--; - filename[len] = 0; - } - if (len + suffix_len >= sizeof(filename)) - return error("Patch pathname too long"); + if (!oneline) + len += sprintf(filename + len, "%d", nr); + else { + len += sprintf(filename + len, "%04d-", nr); + len += snprintf(filename + len, sizeof(filename) - len - 1 + - suffix_len, "%s", oneline); strcpy(filename + len, fmt_patch_suffix); } @@ -557,7 +606,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 */ @@ -576,16 +626,89 @@ static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids, const cha o2->flags = flags2; } -static void gen_message_id(char *dest, unsigned int length, char *base) +static void gen_message_id(struct rev_info *info, char *base) { const char *committer = git_committer_info(IDENT_WARN_ON_NO_NAME); const char *email_start = strrchr(committer, '<'); const char *email_end = strrchr(committer, '>'); - if(!email_start || !email_end || email_start > email_end - 1) + struct strbuf buf; + if (!email_start || !email_end || email_start > email_end - 1) die("Could not extract email from committer identity."); - snprintf(dest, length, "%s.%lu.git.%.*s", base, - (unsigned long) time(NULL), - (int)(email_end - email_start - 1), email_start + 1); + strbuf_init(&buf, 0); + strbuf_addf(&buf, "%s.%lu.git.%.*s", base, + (unsigned long) time(NULL), + (int)(email_end - email_start - 1), email_start + 1); + info->message_id = strbuf_detach(&buf, NULL); +} + +static void make_cover_letter(struct rev_info *rev, int use_stdout, + int numbered, int numbered_files, + struct commit *origin, + int nr, struct commit **list, struct commit *head) +{ + const char *committer; + const char *origin_sha1, *head_sha1; + const char *argv[7]; + const char *subject_start = NULL; + const char *body = "*** SUBJECT HERE ***\n\n*** BLURB HERE ***\n"; + const char *msg; + const char *extra_headers = rev->extra_headers; + struct shortlog log; + struct strbuf sb; + int i; + const char *encoding = "utf-8"; + + if (rev->commit_format != CMIT_FMT_EMAIL) + die("Cover letter needs email format"); + + if (!use_stdout && reopen_stdout(numbered_files ? + NULL : "cover-letter", 0, rev->total)) + return; + + head_sha1 = sha1_to_hex(head->object.sha1); + + log_write_email_headers(rev, head_sha1, &subject_start, &extra_headers); + + committer = git_committer_info(0); + + msg = body; + strbuf_init(&sb, 0); + pp_user_info(NULL, CMIT_FMT_EMAIL, &sb, committer, DATE_RFC2822, + encoding); + pp_title_line(CMIT_FMT_EMAIL, &msg, &sb, subject_start, extra_headers, + encoding, 0); + pp_remainder(CMIT_FMT_EMAIL, &msg, &sb, 0); + printf("%s\n", sb.buf); + + strbuf_release(&sb); + + shortlog_init(&log); + for (i = 0; i < nr; i++) + shortlog_add_commit(&log, list[i]); + + shortlog_output(&log); + + /* + * We can only do diffstat with a unique reference point + */ + if (!origin) + return; + + origin_sha1 = sha1_to_hex(origin->object.sha1); + + argv[0] = "diff"; + argv[1] = "--stat"; + argv[2] = "--summary"; + argv[3] = head_sha1; + argv[4] = "--not"; + argv[5] = origin_sha1; + argv[6] = "--"; + argv[7] = NULL; + fflush(stdout); + run_command_v_opt(argv, RUN_GIT_CMD); + + fflush(stdout); + printf("\n"); } static const char *clean_message_id(const char *msg_id) @@ -623,11 +746,13 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) int subject_prefix = 0; int ignore_if_in_upstream = 0; int thread = 0; + int cover_letter = 0; + int boundary_count = 0; + struct commit *origin = NULL, *head = NULL; const char *in_reply_to = NULL; struct patch_ids ids; char *add_signoff = NULL; - char message_id[1024]; - char ref_message_id[1024]; + struct strbuf buf; git_config(git_format_config); init_revisions(&rev, prefix); @@ -640,7 +765,6 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) DIFF_OPT_SET(&rev.diffopt, RECURSIVE); rev.subject_prefix = fmt_patch_subject_prefix; - rev.extra_headers = extra_headers; /* * Parse the arguments before setup_revisions(), or something @@ -668,6 +792,10 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) die("Need a number for --start-number"); start_number = strtol(argv[i], NULL, 10); } + else if (!prefixcmp(argv[i], "--cc=")) { + ALLOC_GROW(extra_cc, extra_cc_nr + 1, extra_cc_alloc); + extra_cc[extra_cc_nr++] = xstrdup(argv[i] + 5); + } else if (!strcmp(argv[i], "-k") || !strcmp(argv[i], "--keep-subject")) { keep_subject = 1; @@ -724,11 +852,44 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) rev.subject_prefix = argv[i] + 17; } else if (!prefixcmp(argv[i], "--suffix=")) fmt_patch_suffix = argv[i] + 9; + else if (!strcmp(argv[i], "--cover-letter")) + cover_letter = 1; else argv[j++] = argv[i]; } argc = j; + strbuf_init(&buf, 0); + + for (i = 0; i < extra_hdr_nr; i++) { + strbuf_addstr(&buf, extra_hdr[i]); + strbuf_addch(&buf, '\n'); + } + + if (extra_to_nr) + strbuf_addstr(&buf, "To: "); + for (i = 0; i < extra_to_nr; i++) { + if (i) + strbuf_addstr(&buf, " "); + strbuf_addstr(&buf, extra_to[i]); + if (i + 1 < extra_to_nr) + strbuf_addch(&buf, ','); + strbuf_addch(&buf, '\n'); + } + + if (extra_cc_nr) + strbuf_addstr(&buf, "Cc: "); + for (i = 0; i < extra_cc_nr; i++) { + if (i) + strbuf_addstr(&buf, " "); + strbuf_addstr(&buf, extra_cc[i]); + if (i + 1 < extra_cc_nr) + strbuf_addch(&buf, ','); + strbuf_addch(&buf, '\n'); + } + + rev.extra_headers = strbuf_detach(&buf, 0); + if (start_number < 0) start_number = 1; if (numbered && keep_subject) @@ -775,6 +936,18 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) * get_revision() to do the usual traversal. */ } + if (cover_letter) { + /* remember the range */ + int i; + for (i = 0; i < rev.pending.nr; i++) { + struct object *o = rev.pending.objects[i].item; + if (!(o->flags & UNINTERESTING)) + head = (struct commit *)o; + } + /* We can't generate a cover letter without any patches */ + if (!head) + return 0; + } if (ignore_if_in_upstream) get_patch_ids(&rev, &ids, prefix); @@ -782,8 +955,17 @@ 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"); + rev.boundary = 1; while ((commit = get_revision(&rev)) != NULL) { + if (commit->object.flags & BOUNDARY) { + fprintf(stderr, "Boundary %s\n", sha1_to_hex(commit->object.sha1)); + boundary_count++; + origin = (boundary_count == 1) ? commit : NULL; + continue; + } + /* ignore merges */ if (commit->parents && commit->parents->next) continue; @@ -801,29 +983,42 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) numbered = 1; if (numbered) rev.total = total + start_number - 1; - rev.add_signoff = add_signoff; if (in_reply_to) rev.ref_message_id = clean_message_id(in_reply_to); + if (cover_letter) { + if (thread) + gen_message_id(&rev, "cover"); + make_cover_letter(&rev, use_stdout, numbered, numbered_files, + origin, nr, list, head); + total++; + start_number--; + } + rev.add_signoff = add_signoff; while (0 <= --nr) { int shown; commit = list[nr]; rev.nr = total - nr + (start_number - 1); /* Make the second and subsequent mails replies to the first */ if (thread) { - if (nr == (total - 2)) { - strncpy(ref_message_id, message_id, - sizeof(ref_message_id)); - ref_message_id[sizeof(ref_message_id)-1]='\0'; - rev.ref_message_id = ref_message_id; + /* Have we already had a message ID? */ + if (rev.message_id) { + /* + * If we've got the ID to be a reply + * to, discard the current ID; + * otherwise, make everything a reply + * to that. + */ + if (rev.ref_message_id) + free(rev.message_id); + else + rev.ref_message_id = rev.message_id; } - gen_message_id(message_id, sizeof(message_id), - sha1_to_hex(commit->object.sha1)); - rev.message_id = message_id; + gen_message_id(&rev, sha1_to_hex(commit->object.sha1)); } - if (!use_stdout) - if (reopen_stdout(commit, rev.nr, keep_subject, - numbered_files)) - die("Failed to create output files"); + if (!use_stdout && reopen_stdout(numbered_files ? NULL : + get_oneline_for_filename(commit, keep_subject), + rev.nr, rev.total)) + die("Failed to create output files"); shown = log_tree_commit(&rev, commit); free(commit->buffer); commit->buffer = NULL; @@ -924,7 +1119,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 d56e33e25..25dbfb449 100644 --- a/builtin-ls-files.c +++ b/builtin-ls-files.c @@ -238,7 +238,8 @@ 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; @@ -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) @@ -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 6dd31d1dd..023754986 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-mailinfo.c b/builtin-mailinfo.c index 260084797..11f154b31 100644 --- a/builtin-mailinfo.c +++ b/builtin-mailinfo.c @@ -818,6 +818,7 @@ static void handle_body(void) switch (transfer_encoding) { case TE_BASE64: + case TE_QP: { char *op = line; diff --git a/builtin-merge-file.c b/builtin-merge-file.c index 58deb62ac..adce6d463 100644 --- a/builtin-merge-file.c +++ b/builtin-merge-file.c @@ -46,7 +46,7 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) } ret = xdl_merge(mmfs + 1, mmfs + 0, names[0], mmfs + 2, names[2], - &xpp, XDL_MERGE_ZEALOUS, &result); + &xpp, XDL_MERGE_ZEALOUS_ALNUM, &result); for (i = 0; i < 3; i++) free(mmfs[i].ptr); diff --git a/builtin-merge-recursive.c b/builtin-merge-recursive.c index 558a58e4d..6fe4102c0 100644 --- a/builtin-merge-recursive.c +++ b/builtin-merge-recursive.c @@ -835,8 +835,9 @@ static int read_merge_config(const char *var, const char *value) int namelen; if (!strcmp(var, "merge.default")) { - if (value) - default_ll_merge = strdup(value); + if (!value) + return config_error_nonbool(var); + default_ll_merge = strdup(value); return 0; } @@ -869,14 +870,14 @@ static int read_merge_config(const char *var, const char *value) if (!strcmp("name", ep)) { if (!value) - return error("%s: lacks value", var); + return config_error_nonbool(var); fn->description = strdup(value); return 0; } if (!strcmp("driver", ep)) { if (!value) - return error("%s: lacks value", var); + return config_error_nonbool(var); /* * merge.<name>.driver specifies the command line: * @@ -899,7 +900,7 @@ static int read_merge_config(const char *var, const char *value) if (!strcmp("recursive", ep)) { if (!value) - return error("%s: lacks value", var); + return config_error_nonbool(var); fn->recursive = strdup(value); return 0; } @@ -1663,6 +1664,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)); diff --git a/builtin-mv.c b/builtin-mv.c index 990e21355..68aa2a68b 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 a0c89a827..f22c8b5f5 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 ec10238e4..586ae1197 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -16,6 +16,7 @@ #include "progress.h" #ifdef THREADED_DELTA_SEARCH +#include "thread-utils.h" #include <pthread.h> #endif @@ -68,7 +69,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; @@ -1672,7 +1673,8 @@ static void ll_find_deltas(struct object_entry **list, unsigned list_size, p[i].data_ready = 0; /* try to split chunks on "path" boundaries */ - while (sub_size < list_size && list[sub_size]->hash && + while (sub_size && sub_size < list_size && + list[sub_size]->hash && list[sub_size]->hash == list[sub_size-1]->hash) sub_size++; @@ -1851,11 +1853,11 @@ static int git_pack_config(const char *k, const char *v) } if (!strcmp(k, "pack.threads")) { delta_search_threads = git_config_int(k, v); - if (delta_search_threads < 1) + if (delta_search_threads < 0) die("invalid number of threads specified (%d)", delta_search_threads); #ifndef THREADED_DELTA_SEARCH - if (delta_search_threads > 1) + if (delta_search_threads != 1) warning("no threads support, ignoring %s", k); #endif return 0; @@ -1866,6 +1868,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); } @@ -2028,7 +2034,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); @@ -2095,6 +2102,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); @@ -2115,10 +2123,10 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) if (!prefixcmp(arg, "--threads=")) { char *end; delta_search_threads = strtoul(arg+10, &end, 0); - if (!arg[10] || *end || delta_search_threads < 1) + if (!arg[10] || *end || delta_search_threads < 0) usage(pack_usage); #ifndef THREADED_DELTA_SEARCH - if (delta_search_threads > 1) + if (delta_search_threads != 1) warning("no threads support, " "ignoring %s", arg); #endif @@ -2219,12 +2227,20 @@ 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."); if (!pack_to_stdout && thin) die("--thin cannot be used to build an indexable pack."); +#ifdef THREADED_DELTA_SEARCH + if (!delta_search_threads) /* --threads=0 means autodetect */ + delta_search_threads = online_cpus(); +#endif + prepare_packed_git(); if (progress) diff --git a/builtin-prune.c b/builtin-prune.c index b5e768421..bb8ead92c 100644 --- a/builtin-prune.c +++ b/builtin-prune.c @@ -83,6 +83,44 @@ static void prune_object_dir(const char *path) } } +/* + * Write errors (particularly out of space) can result in + * failed temporary packs (and more rarely indexes and other + * files begining with "tmp_") accumulating in the + * object directory. + */ +static void remove_temporary_files(void) +{ + DIR *dir; + struct dirent *de; + char* dirname=get_object_directory(); + + dir = opendir(dirname); + if (!dir) { + fprintf(stderr, "Unable to open object directory %s\n", + dirname); + return; + } + while ((de = readdir(dir)) != NULL) { + if (!prefixcmp(de->d_name, "tmp_")) { + char name[PATH_MAX]; + int c = snprintf(name, PATH_MAX, "%s/%s", + dirname, de->d_name); + if (c < 0 || c >= PATH_MAX) + continue; + if (expire) { + struct stat st; + if (stat(name, &st) != 0 || st.st_mtime >= expire) + continue; + } + printf("Removing stale temporary file %s\n", name); + if (!show_only) + unlink(name); + } + } + closedir(dir); +} + int cmd_prune(int argc, const char **argv, const char *prefix) { int i; @@ -115,5 +153,6 @@ int cmd_prune(int argc, const char **argv, const char *prefix) sync(); prune_packed_objects(show_only); + remove_temporary_files(); return 0; } diff --git a/builtin-push.c b/builtin-push.c index c8cb63e23..b68c6813b 100644 --- a/builtin-push.c +++ b/builtin-push.c @@ -44,15 +44,6 @@ static void set_refspecs(const char **refs, int nr) strcat(tag, refs[i]); ref = tag; } - if (!strcmp("HEAD", ref)) { - unsigned char sha1_dummy[20]; - ref = resolve_ref(ref, sha1_dummy, 1, NULL); - if (!ref) - die("HEAD cannot be resolved."); - if (prefixcmp(ref, "refs/heads/")) - die("HEAD cannot be resolved to branch."); - ref = xstrdup(ref + 11); - } add_refspec(ref); } } @@ -90,7 +81,7 @@ static int do_push(const char *repo, int flags) if (!err) continue; - error("failed to push to '%s'", remote->url[i]); + error("failed to push some refs to '%s'", remote->url[i]); errs++; } return !!errs; diff --git a/builtin-read-tree.c b/builtin-read-tree.c index 1d9d125b9..0138f5a91 100644 --- a/builtin-read-tree.c +++ b/builtin-read-tree.c @@ -41,11 +41,12 @@ static int read_cache_unmerged(void) for (i = 0; i < active_nr; i++) { struct cache_entry *ce = active_cache[i]; if (ce_stage(ce)) { + remove_index_entry(ce); if (last && !strcmp(ce->name, last->name)) continue; cache_tree_invalidate_path(active_cache_tree, ce->name); last = ce; - ce->ce_flags |= CE_REMOVE; + continue; } *dst++ = ce; } diff --git a/builtin-reflog.c b/builtin-reflog.c index ce093cad7..ab53c8cb7 100644 --- a/builtin-reflog.c +++ b/builtin-reflog.c @@ -34,6 +34,16 @@ struct expire_reflog_cb { struct cmd_reflog_expire_cb *cmd; }; +struct collected_reflog { + unsigned char sha1[20]; + char reflog[FLEX_ARRAY]; +}; +struct collect_reflog_cb { + struct collected_reflog **e; + int alloc; + int nr; +}; + #define INCOMPLETE (1u<<10) #define STUDYING (1u<<11) @@ -266,10 +276,11 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused, for_each_reflog_ent(ref, expire_reflog_ent, &cb); finish: if (cb.newlog) { - if (fclose(cb.newlog)) + if (fclose(cb.newlog)) { status |= error("%s: %s", strerror(errno), newlog_path); - if (rename(newlog_path, log_file)) { + unlink(newlog_path); + } else if (rename(newlog_path, log_file)) { status |= error("cannot rename %s to %s", newlog_path, log_file); unlink(newlog_path); @@ -281,15 +292,35 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused, return status; } +static int collect_reflog(const char *ref, const unsigned char *sha1, int unused, void *cb_data) +{ + struct collected_reflog *e; + struct collect_reflog_cb *cb = cb_data; + size_t namelen = strlen(ref); + + e = xmalloc(sizeof(*e) + namelen + 1); + hashcpy(e->sha1, sha1); + memcpy(e->reflog, ref, namelen + 1); + ALLOC_GROW(cb->e, cb->nr + 1, cb->alloc); + cb->e[cb->nr++] = e; + return 0; +} + static int reflog_expire_config(const char *var, const char *value) { - if (!strcmp(var, "gc.reflogexpire")) + if (!strcmp(var, "gc.reflogexpire")) { + if (!value) + config_error_nonbool(var); default_reflog_expire = approxidate(value); - else if (!strcmp(var, "gc.reflogexpireunreachable")) + return 0; + } + if (!strcmp(var, "gc.reflogexpireunreachable")) { + if (!value) + config_error_nonbool(var); default_reflog_expire_unreachable = approxidate(value); - else - return git_default_config(var, value); - return 0; + return 0; + } + return git_default_config(var, value); } static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) @@ -349,8 +380,20 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) putchar('\n'); } - if (do_all) - status |= for_each_reflog(expire_reflog, &cb); + if (do_all) { + struct collect_reflog_cb collected; + int i; + + memset(&collected, 0, sizeof(collected)); + for_each_reflog(collect_reflog, &collected); + for (i = 0; i < collected.nr; i++) { + struct collected_reflog *e = collected.e[i]; + status |= expire_reflog(e->reflog, e->sha1, 0, &cb); + free(e); + } + free(collected.e); + } + while (i < argc) { const char *ref = argv[i++]; unsigned char sha1[20]; diff --git a/builtin-rerere.c b/builtin-rerere.c index b0c17bde8..c607aade6 100644 --- a/builtin-rerere.c +++ b/builtin-rerere.c @@ -267,23 +267,6 @@ static int diff_two(const char *file1, const char *label1, return 0; } -static int copy_file(const char *src, const char *dest) -{ - FILE *in, *out; - char buffer[32768]; - int count; - - if (!(in = fopen(src, "r"))) - return error("Could not open %s", src); - if (!(out = fopen(dest, "w"))) - return error("Could not open %s", dest); - while ((count = fread(buffer, 1, sizeof(buffer), in))) - fwrite(buffer, 1, count, out); - fclose(in); - fclose(out); - return 0; -} - static int do_plain_rerere(struct path_list *rr, int fd) { struct path_list conflict = { NULL, 0, 0, 1 }; @@ -343,7 +326,7 @@ static int do_plain_rerere(struct path_list *rr, int fd) continue; fprintf(stderr, "Recorded resolution for '%s'.\n", path); - copy_file(path, rr_path(name, "postimage")); + copy_file(rr_path(name, "postimage"), path, 0666); tail_optimization: if (i < rr->nr - 1) memmove(rr->items + i, diff --git a/builtin-rev-list.c b/builtin-rev-list.c index de80158fd..6f7d5f821 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-rev-parse.c b/builtin-rev-parse.c index b9af1a5a5..90dbb9d7c 100644 --- a/builtin-rev-parse.c +++ b/builtin-rev-parse.c @@ -315,7 +315,7 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix) s = strchr(sb.buf, ' '); if (!s || *sb.buf == ' ') { o->type = OPTION_GROUP; - o->help = xstrdup(skipspaces(s)); + o->help = xstrdup(skipspaces(sb.buf)); continue; } diff --git a/builtin-revert.c b/builtin-revert.c index 358af5374..e219859f9 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-send-pack.c b/builtin-send-pack.c index 8afb1d0bc..b0cfae83f 100644 --- a/builtin-send-pack.c +++ b/builtin-send-pack.c @@ -71,6 +71,7 @@ static int pack_objects(int fd, struct ref *refs) refs = refs->next; } + close(po.in); if (finish_command(&po)) return error("pack-objects died with strange error"); return 0; @@ -403,12 +404,15 @@ static int do_send_pack(int in, int out, struct remote *remote, const char *dest if (!remote_tail) remote_tail = &remote_refs; if (match_refs(local_refs, remote_refs, &remote_tail, - nr_refspec, refspec, flags)) + nr_refspec, refspec, flags)) { + close(out); return -1; + } if (!remote_refs) { fprintf(stderr, "No refs in common and none specified; doing nothing.\n" "Perhaps you should specify a branch such as 'master'.\n"); + close(out); return 0; } @@ -495,12 +499,11 @@ static int do_send_pack(int in, int out, struct remote *remote, const char *dest packet_flush(out); if (new_refs && !args.dry_run) { - if (pack_objects(out, remote_refs) < 0) { - close(out); + if (pack_objects(out, remote_refs) < 0) return -1; - } } - close(out); + else + close(out); if (expect_status_report) ret = receive_status(in, remote_refs); @@ -648,7 +651,7 @@ int send_pack(struct send_pack_args *my_args, conn = git_connect(fd, dest, args.receivepack, args.verbose ? CONNECT_VERBOSE : 0); ret = do_send_pack(fd[0], fd[1], remote, dest, nr_heads, heads); close(fd[0]); - close(fd[1]); + /* do_send_pack always closes fd[1] */ ret |= finish_connect(conn); return !!ret; } diff --git a/builtin-shortlog.c b/builtin-shortlog.c index fa8bc7d02..af31abaaf 100644 --- a/builtin-shortlog.c +++ b/builtin-shortlog.c @@ -6,13 +6,11 @@ #include "revision.h" #include "utf8.h" #include "mailmap.h" +#include "shortlog.h" static const char shortlog_usage[] = "git-shortlog [-n] [-s] [-e] [<commit-id>... ]"; -static char *common_repo_prefix; -static int email; - static int compare_by_number(const void *a1, const void *a2) { const struct path_list_item *i1 = a1, *i2 = a2; @@ -26,13 +24,11 @@ static int compare_by_number(const void *a1, const void *a2) return -1; } -static struct path_list mailmap = {NULL, 0, 0, 0}; - -static void insert_one_record(struct path_list *list, +static void insert_one_record(struct shortlog *log, const char *author, const char *oneline) { - const char *dot3 = common_repo_prefix; + const char *dot3 = log->common_repo_prefix; char *buffer, *p; struct path_list_item *item; struct path_list *onelines; @@ -47,7 +43,7 @@ static void insert_one_record(struct path_list *list, eoemail = strchr(boemail, '>'); if (!eoemail) return; - if (!map_email(&mailmap, boemail+1, namebuf, sizeof(namebuf))) { + if (!map_email(&log->mailmap, boemail+1, namebuf, sizeof(namebuf))) { while (author < boemail && isspace(*author)) author++; for (len = 0; @@ -61,14 +57,14 @@ static void insert_one_record(struct path_list *list, else len = strlen(namebuf); - if (email) { + if (log->email) { size_t room = sizeof(namebuf) - len - 1; int maillen = eoemail - boemail + 1; snprintf(namebuf + len, room, " %.*s", maillen, boemail); } buffer = xstrdup(namebuf); - item = path_list_insert(buffer, list); + item = path_list_insert(buffer, &log->list); if (item->util == NULL) item->util = xcalloc(1, sizeof(struct path_list)); else @@ -114,7 +110,7 @@ static void insert_one_record(struct path_list *list, onelines->items[onelines->nr++].path = buffer; } -static void read_from_stdin(struct path_list *list) +static void read_from_stdin(struct shortlog *log) { char author[1024], oneline[1024]; @@ -128,38 +124,43 @@ static void read_from_stdin(struct path_list *list) while (fgets(oneline, sizeof(oneline), stdin) && oneline[0] == '\n') ; /* discard blanks */ - insert_one_record(list, author + 8, oneline); + insert_one_record(log, author + 8, oneline); } } -static void get_from_rev(struct rev_info *rev, struct path_list *list) +void shortlog_add_commit(struct shortlog *log, struct commit *commit) { - struct commit *commit; - - prepare_revision_walk(rev); - while ((commit = get_revision(rev)) != NULL) { - const char *author = NULL, *buffer; + const char *author = NULL, *buffer; - buffer = commit->buffer; - while (*buffer && *buffer != '\n') { - const char *eol = strchr(buffer, '\n'); + buffer = commit->buffer; + while (*buffer && *buffer != '\n') { + const char *eol = strchr(buffer, '\n'); - if (eol == NULL) - eol = buffer + strlen(buffer); - else - eol++; + if (eol == NULL) + eol = buffer + strlen(buffer); + else + eol++; - if (!prefixcmp(buffer, "author ")) - author = buffer + 7; - buffer = eol; - } - if (!author) - die("Missing author: %s", - sha1_to_hex(commit->object.sha1)); - if (*buffer) - buffer++; - insert_one_record(list, author, !*buffer ? "<none>" : buffer); + if (!prefixcmp(buffer, "author ")) + author = buffer + 7; + buffer = eol; } + if (!author) + die("Missing author: %s", + sha1_to_hex(commit->object.sha1)); + if (*buffer) + buffer++; + insert_one_record(log, author, !*buffer ? "<none>" : buffer); +} + +static void get_from_rev(struct rev_info *rev, struct shortlog *log) +{ + struct commit *commit; + + if (prepare_revision_walk(rev)) + die("revision walk setup failed"); + while ((commit = get_revision(rev)) != NULL) + shortlog_add_commit(log, commit); } static int parse_uint(char const **arg, int comma) @@ -211,29 +212,38 @@ static void parse_wrap_args(const char *arg, int *in1, int *in2, int *wrap) die(wrap_arg_usage); } +void shortlog_init(struct shortlog *log) +{ + memset(log, 0, sizeof(*log)); + + read_mailmap(&log->mailmap, ".mailmap", &log->common_repo_prefix); + + log->list.strdup_paths = 1; + log->wrap = DEFAULT_WRAPLEN; + log->in1 = DEFAULT_INDENT1; + log->in2 = DEFAULT_INDENT2; +} + int cmd_shortlog(int argc, const char **argv, const char *prefix) { + struct shortlog log; struct rev_info rev; - struct path_list list = { NULL, 0, 0, 1 }; - int i, j, sort_by_number = 0, summary = 0; - int wrap_lines = 0; - int wrap = DEFAULT_WRAPLEN; - int in1 = DEFAULT_INDENT1; - int in2 = DEFAULT_INDENT2; + + shortlog_init(&log); /* since -n is a shadowed rev argument, parse our args first */ while (argc > 1) { if (!strcmp(argv[1], "-n") || !strcmp(argv[1], "--numbered")) - sort_by_number = 1; + log.sort_by_number = 1; else if (!strcmp(argv[1], "-s") || !strcmp(argv[1], "--summary")) - summary = 1; + log.summary = 1; else if (!strcmp(argv[1], "-e") || !strcmp(argv[1], "--email")) - email = 1; + log.email = 1; else if (!prefixcmp(argv[1], "-w")) { - wrap_lines = 1; - parse_wrap_args(argv[1], &in1, &in2, &wrap); + log.wrap_lines = 1; + parse_wrap_args(argv[1], &log.in1, &log.in2, &log.wrap); } else if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) usage(shortlog_usage); @@ -247,34 +257,38 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix) if (argc > 1) die ("unrecognized argument: %s", argv[1]); - read_mailmap(&mailmap, ".mailmap", &common_repo_prefix); - /* assume HEAD if from a tty */ if (!rev.pending.nr && isatty(0)) add_head_to_pending(&rev); if (rev.pending.nr == 0) { - read_from_stdin(&list); + read_from_stdin(&log); } else - get_from_rev(&rev, &list); + get_from_rev(&rev, &log); - if (sort_by_number) - qsort(list.items, list.nr, sizeof(struct path_list_item), - compare_by_number); + shortlog_output(&log); + return 0; +} - for (i = 0; i < list.nr; i++) { - struct path_list *onelines = list.items[i].util; +void shortlog_output(struct shortlog *log) +{ + int i, j; + if (log->sort_by_number) + qsort(log->list.items, log->list.nr, sizeof(struct path_list_item), + compare_by_number); + for (i = 0; i < log->list.nr; i++) { + struct path_list *onelines = log->list.items[i].util; - if (summary) { - printf("%6d\t%s\n", onelines->nr, list.items[i].path); + if (log->summary) { + printf("%6d\t%s\n", onelines->nr, log->list.items[i].path); } else { - printf("%s (%d):\n", list.items[i].path, onelines->nr); + printf("%s (%d):\n", log->list.items[i].path, onelines->nr); for (j = onelines->nr - 1; j >= 0; j--) { const char *msg = onelines->items[j].path; - if (wrap_lines) { - int col = print_wrapped_text(msg, in1, in2, wrap); - if (col != wrap) + if (log->wrap_lines) { + int col = print_wrapped_text(msg, log->in1, log->in2, log->wrap); + if (col != log->wrap) putchar('\n'); } else @@ -286,13 +300,11 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix) onelines->strdup_paths = 1; path_list_clear(onelines, 1); free(onelines); - list.items[i].util = NULL; + log->list.items[i].util = NULL; } - list.strdup_paths = 1; - path_list_clear(&list, 1); - mailmap.strdup_paths = 1; - path_list_clear(&mailmap, 1); - - return 0; + log->list.strdup_paths = 1; + path_list_clear(&log->list, 1); + log->mailmap.strdup_paths = 1; + path_list_clear(&log->mailmap, 1); } diff --git a/builtin-show-branch.c b/builtin-show-branch.c index 6dc835d30..019abd352 100644 --- a/builtin-show-branch.c +++ b/builtin-show-branch.c @@ -536,6 +536,8 @@ static void append_one_rev(const char *av) static int git_show_branch_config(const char *var, const char *value) { if (!strcmp(var, "showbranch.default")) { + if (!value) + return config_error_nonbool(var); if (default_alloc <= default_num + 1) { default_alloc = default_alloc * 3 / 2 + 20; default_arg = xrealloc(default_arg, sizeof *default_arg * default_alloc); diff --git a/builtin-show-ref.c b/builtin-show-ref.c index 65051d14f..a323633e2 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 03e70155f..28c36fdcd 100644 --- a/builtin-tag.c +++ b/builtin-tag.c @@ -226,19 +226,17 @@ static int do_sign(struct strbuf *buffer) if (write_in_full(gpg.in, buffer->buf, buffer->len) != buffer->len) { close(gpg.in); + close(gpg.out); finish_command(&gpg); return error("gpg did not accept the tag data"); } close(gpg.in); - gpg.close_in = 0; len = strbuf_read(buffer, gpg.out, 1024); + close(gpg.out); 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; } @@ -258,7 +256,7 @@ static int git_tag_config(const char *var, const char *value) { if (!strcmp(var, "user.signingkey")) { if (!value) - die("user.signingkey without value"); + return config_error_nonbool(value); set_signingkey(value); return 0; } diff --git a/builtin-verify-tag.c b/builtin-verify-tag.c index cc4c55d7e..f3ef11fa2 100644 --- a/builtin-verify-tag.c +++ b/builtin-verify-tag.c @@ -45,14 +45,12 @@ static int run_gpg_verify(const char *buf, unsigned long size, int verbose) memset(&gpg, 0, sizeof(gpg)); gpg.argv = args_gpg; gpg.in = -1; - gpg.out = 1; args_gpg[2] = path; if (start_command(&gpg)) return error("could not run gpg."); write_in_full(gpg.in, buf, len); close(gpg.in); - gpg.close_in = 0; ret = finish_command(&gpg); unlink(path); diff --git a/builtin-write-tree.c b/builtin-write-tree.c index d16b9ed00..e838d0123 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))) @@ -332,10 +333,12 @@ int create_bundle(struct bundle_header *header, const char *path, write_or_die(rls.in, sha1_to_hex(object->sha1), 40); write_or_die(rls.in, "\n", 1); } + close(rls.in); if (finish_command(&rls)) return error ("pack-objects died"); - - return bundle_to_stdout ? close(bundle_fd) : commit_lock_file(&lock); + if (!bundle_to_stdout) + commit_lock_file(&lock); + return 0; } int unbundle(struct bundle_header *header, int bundle_fd) diff --git a/cache-tree.c b/cache-tree.c index bfc95d2dc..39da54d1e 100644 --- a/cache-tree.c +++ b/cache-tree.c @@ -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 8243228e4..44aad426d 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 @@ -110,7 +110,6 @@ struct ondisk_cache_entry { }; struct cache_entry { - struct cache_entry *next; unsigned int ce_ctime; unsigned int ce_mtime; unsigned int ce_dev; @@ -121,6 +120,7 @@ struct cache_entry { unsigned int ce_size; unsigned int ce_flags; unsigned char sha1[20]; + struct cache_entry *next; char name[FLEX_ARRAY]; /* more */ }; @@ -133,7 +133,39 @@ struct cache_entry { #define CE_UPDATE (0x10000) #define CE_REMOVE (0x20000) #define CE_UPTODATE (0x40000) -#define CE_UNHASHED (0x80000) + +#define CE_HASHED (0x100000) +#define CE_UNHASHED (0x200000) + +/* + * Copy the sha1 and stat state of a cache entry from one to + * another. But we never change the name, or the hash state! + */ +#define CE_STATE_MASK (CE_HASHED | CE_UNHASHED) +static inline void copy_cache_entry(struct cache_entry *dst, struct cache_entry *src) +{ + unsigned int state = dst->ce_flags & CE_STATE_MASK; + + /* Don't copy hash chain and name */ + memcpy(dst, src, offsetof(struct cache_entry, next)); + + /* Restore the hash state */ + dst->ce_flags = (dst->ce_flags & ~CE_STATE_MASK) | state; +} + +/* + * 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 inline void remove_index_entry(struct cache_entry *ce) +{ + ce->ce_flags |= CE_UNHASHED; +} static inline unsigned create_ce_flags(size_t len, unsigned stage) { @@ -178,6 +210,18 @@ static inline unsigned int ce_mode_from_stat(struct cache_entry *ce, unsigned in } 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) @@ -373,6 +417,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); @@ -627,11 +679,16 @@ extern int git_parse_ulong(const char *, unsigned long *); extern int git_config_int(const char *, const char *); extern unsigned long git_config_ulong(const char *, const char *); extern int git_config_bool(const char *, const char *); +extern int git_config_string(const char **, const char *, const char *); extern int git_config_set(const char *, const char *); 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) extern char git_default_email[MAX_GITNAME]; @@ -643,6 +700,7 @@ extern const char *git_log_output_encoding; /* IO helper functions */ extern void maybe_flush_or_die(FILE *, const char *); extern int copy_fd(int ifd, int ofd); +extern int copy_file(const char *dst, const char *src, int mode); extern int read_in_full(int fd, void *buf, size_t count); extern int write_in_full(int fd, const void *buf, size_t count); extern void write_or_die(int fd, const void *buf, size_t count); @@ -651,12 +709,12 @@ extern int write_or_whine_pipe(int fd, const void *buf, size_t count, const char /* pager.c */ extern void setup_pager(void); -extern char *pager_program; +extern const char *pager_program; extern int pager_in_use(void); extern int pager_use_color; -extern char *editor_program; -extern char *excludes_file; +extern const char *editor_program; +extern const char *excludes_file; /* base85 */ int decode_85(char *dst, const char *line, int linelen); @@ -676,7 +734,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 */ @@ -695,6 +754,7 @@ void shift_tree(const unsigned char *, const unsigned char *, unsigned char *, i #define WS_TRAILING_SPACE 01 #define WS_SPACE_BEFORE_TAB 02 #define WS_INDENT_WITH_NON_TAB 04 +#define WS_CR_AT_EOL 010 #define WS_DEFAULT_RULE (WS_TRAILING_SPACE|WS_SPACE_BEFORE_TAB) extern unsigned whitespace_rule_cfg; extern unsigned whitespace_rule(const char *); @@ -703,10 +763,13 @@ extern unsigned check_and_emit_line(const char *line, int len, unsigned ws_rule, FILE *stream, const char *set, const char *reset, const char *ws); extern char *whitespace_error_string(unsigned ws); +extern int ws_fix_copy(char *, const char *, int, unsigned, int *); /* ls-files */ int pathspec_match(const char **spec, char *matched, const char *filename, int skiplen); int report_path_error(const char *ps_matched, const char **pathspec, int prefix_offset); void overlay_tree_on_cache(const char *tree_name, const char *prefix); +char *alias_lookup(const char *alias); + #endif /* CACHE_H */ @@ -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[] = { @@ -17,7 +19,7 @@ static int parse_color(const char *name, int len) return i - 1; } i = strtol(name, &end, 10); - if (*name && !*end && i >= -1 && i <= 255) + if (end - name == len && i >= -1 && i <= 255) return i; return -2; } @@ -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); } @@ -71,6 +71,21 @@ extern void pretty_print_commit(enum cmit_fmt fmt, const struct commit*, int abbrev, const char *subject, const char *after_subject, enum date_mode, int non_ascii_present); +void pp_user_info(const char *what, enum cmit_fmt fmt, struct strbuf *sb, + const char *line, enum date_mode dmode, + const char *encoding); +void pp_title_line(enum cmit_fmt fmt, + const char **msg_p, + struct strbuf *sb, + const char *subject, + const char *after_subject, + const char *encoding, + int plain_non_ascii); +void pp_remainder(enum cmit_fmt fmt, + const char **msg_p, + struct strbuf *sb, + int indent); + /** Removes the first commit from a list sorted by date, and adds all * of its parents. diff --git a/compat/fopen.c b/compat/fopen.c new file mode 100644 index 000000000..ccb9e89fa --- /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 000000000..d93dce2cf --- /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; } @@ -309,6 +316,14 @@ int git_config_bool(const char *name, const char *value) return git_config_int(name, value) != 0; } +int git_config_string(const char **dest, const char *var, const char *value) +{ + if (!value) + return config_error_nonbool(var); + *dest = xstrdup(value); + return 0; +} + int git_default_config(const char *var, const char *value) { /* This needs a better name */ @@ -407,50 +422,52 @@ 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); strlcpy(git_default_name, value, sizeof(git_default_name)); return 0; } if (!strcmp(var, "user.email")) { + if (!value) + return config_error_nonbool(var); strlcpy(git_default_email, value, sizeof(git_default_email)); return 0; } - if (!strcmp(var, "i18n.commitencoding")) { - git_commit_encoding = xstrdup(value); - return 0; - } - - if (!strcmp(var, "i18n.logoutputencoding")) { - git_log_output_encoding = xstrdup(value); - return 0; - } + if (!strcmp(var, "i18n.commitencoding")) + return git_config_string(&git_commit_encoding, var, value); + if (!strcmp(var, "i18n.logoutputencoding")) + return git_config_string(&git_log_output_encoding, var, value); if (!strcmp(var, "pager.color") || !strcmp(var, "color.pager")) { pager_use_color = git_config_bool(var,value); return 0; } - if (!strcmp(var, "core.pager")) { - pager_program = xstrdup(value); - return 0; - } + if (!strcmp(var, "core.pager")) + return git_config_string(&pager_program, var, value); - if (!strcmp(var, "core.editor")) { - editor_program = xstrdup(value); - return 0; - } + if (!strcmp(var, "core.editor")) + return git_config_string(&editor_program, var, value); - if (!strcmp(var, "core.excludesfile")) { - if (!value) - die("core.excludesfile without value"); - excludes_file = xstrdup(value); - return 0; - } + if (!strcmp(var, "core.excludesfile")) + return git_config_string(&excludes_file, var, value); if (!strcmp(var, "core.whitespace")) { + if (!value) + return config_error_nonbool(var); whitespace_rule_cfg = parse_whitespace_rule(value); return 0; } @@ -484,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; @@ -504,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); @@ -512,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); @@ -701,12 +734,17 @@ static ssize_t find_beginning_of_line(const char* contents, size_t size, size_t equal_offset = size, bracket_offset = size; ssize_t offset; +contline: for (offset = offset_-2; offset > 0 && contents[offset] != '\n'; offset--) switch (contents[offset]) { case '=': equal_offset = offset; break; case ']': bracket_offset = offset; break; } + if (offset > 0 && contents[offset-1] == '\\') { + offset_ = offset; + goto contline; + } if (bracket_offset < equal_offset) { *found_bracket = 1; offset = bracket_offset+1; @@ -1074,3 +1112,12 @@ int git_config_rename_section(const char *old_name, const char *new_name) free(config_filename); return ret; } + +/* + * Call this to report error for your variable that should not + * get a boolean value (i.e. "[my] var" means "true"). + */ +int config_error_nonbool(const char *var) +{ + return error("Missing value for '%s'", var); +} diff --git a/config.mak.in b/config.mak.in index 40b14d985..ee6c33df0 100644 --- a/config.mak.in +++ b/config.mak.in @@ -30,6 +30,7 @@ NO_CURL=@NO_CURL@ NO_EXPAT=@NO_EXPAT@ NEEDS_LIBICONV=@NEEDS_LIBICONV@ NEEDS_SOCKET=@NEEDS_SOCKET@ +NO_SYS_SELECT_H=@NO_SYS_SELECT_H@ NO_D_INO_IN_DIRENT=@NO_D_INO_IN_DIRENT@ NO_D_TYPE_IN_DIRENT=@NO_D_TYPE_IN_DIRENT@ NO_SOCKADDR_STORAGE=@NO_SOCKADDR_STORAGE@ diff --git a/configure.ac b/configure.ac index af177fdb4..85d7ef570 100644 --- a/configure.ac +++ b/configure.ac @@ -235,6 +235,12 @@ test -n "$NEEDS_SOCKET" && LIBS="$LIBS -lsocket" ## Checks for header files. AC_MSG_NOTICE([CHECKS for header files]) # +# Define NO_SYS_SELECT_H if you don't have sys/select.h. +AC_CHECK_HEADER([sys/select.h], +[NO_SYS_SELECT_H=], +[NO_SYS_SELECT_H=UnfortunatelyYes]) +AC_SUBST(NO_SYS_SELECT_H) +# # Define OLD_ICONV if your library has an old iconv(), where the second # (input buffer pointer) parameter is declared with type (const char **). AC_DEFUN([OLDICONVTEST_SRC], [[ @@ -370,6 +370,8 @@ static int git_proxy_command_options(const char *var, const char *value) if (git_proxy_command) return 0; + if (!value) + return config_error_nonbool(var); /* [core] * ;# matches www.kernel.org as well * gitproxy = netcatter-1 for kernel.org @@ -472,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) @@ -577,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)); @@ -635,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 0d33f9a3d..8722a6879 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -64,12 +64,44 @@ __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 + if ! b="$(git describe --exact-match HEAD 2>/dev/null)" + then + b="$(cut -c1-7 $g/HEAD)..." + fi + 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 bb671d561..9f92cd250 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 d8a06381f..f69b697f8 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/examples/git-checkout.sh b/contrib/examples/git-checkout.sh index 5621c69d8..1a7689a48 100755 --- a/contrib/examples/git-checkout.sh +++ b/contrib/examples/git-checkout.sh @@ -71,7 +71,8 @@ while test $# != 0; do done arg="$1" -if rev=$(git rev-parse --verify "$arg^0" 2>/dev/null) +rev=$(git rev-parse --verify "$arg" 2>/dev/null) +if rev=$(git rev-parse --verify "$rev^0" 2>/dev/null) then [ -z "$rev" ] && die "unknown flag $arg" new_name="$arg" @@ -82,11 +83,11 @@ then fi new="$rev" shift -elif rev=$(git rev-parse --verify "$arg^{tree}" 2>/dev/null) +elif rev=$(git rev-parse --verify "$rev^{tree}" 2>/dev/null) then # checking out selected paths from a tree-ish. new="$rev" - new_name="$arg^{tree}" + new_name="$rev^{tree}" shift fi [ "$1" = "--" ] && shift @@ -209,11 +210,14 @@ then git read-tree $v --reset -u $new else git update-index --refresh >/dev/null - merge_error=$(git read-tree -m -u --exclude-per-directory=.gitignore $old $new 2>&1) || ( - case "$merge" in - '') - echo >&2 "$merge_error" + git read-tree $v -m -u --exclude-per-directory=.gitignore $old $new || ( + case "$merge,$v" in + ,*) exit 1 ;; + 1,) + ;; # quiet + *) + echo >&2 "Falling back to 3-way merge..." ;; esac # Match the index to the working tree, and do a three-way. diff --git a/contrib/fast-import/git-p4 b/contrib/fast-import/git-p4 index c80a6da25..781a0cbbb 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] diff --git a/contrib/hg-to-git/hg-to-git.py b/contrib/hg-to-git/hg-to-git.py index c35b15860..d72ffbb77 100755 --- a/contrib/hg-to-git/hg-to-git.py +++ b/contrib/hg-to-git/hg-to-git.py @@ -111,7 +111,7 @@ hgparents["0"] = (None, None) hgbranch["0"] = "master" for cset in range(1, int(tip) + 1): hgchildren[str(cset)] = () - prnts = os.popen('hg log -r %d --template "{parents}"' % cset).read().split(' ') + prnts = os.popen('hg log -r %d --template "{parents}"' % cset).read().strip().split(' ') prnts = map(lambda x: x[:x.find(':')], prnts) if prnts[0] != '': parent = prnts[0].strip() @@ -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); @@ -326,14 +360,14 @@ static int read_convert_config(const char *var, const char *value) if (!strcmp("smudge", ep)) { if (!value) - return error("%s: lacks value", var); + return config_error_nonbool(var); drv->smudge = strdup(value); return 0; } if (!strcmp("clean", ep)) { if (!value) - return error("%s: lacks value", var); + return config_error_nonbool(var); drv->clean = strdup(value); return 0; } @@ -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; @@ -34,3 +34,24 @@ int copy_fd(int ifd, int ofd) close(ifd); return 0; } + +int copy_file(const char *dst, const char *src, int mode) +{ + int fdi, fdo, status; + + mode = (mode & 0111) ? 0777 : 0666; + if ((fdi = open(src, O_RDONLY)) < 0) + return fdi; + if ((fdo = open(dst, O_WRONLY | O_CREAT | O_EXCL, mode)) < 0) { + close(fdi); + return fdo; + } + status = copy_fd(fdi, fdo); + if (close(fdo) != 0) + return error("%s: write error: %s", dst, strerror(errno)); + + if (!status && adjust_shared_perm(dst)) + return -1; + + return status; +} @@ -213,9 +213,9 @@ static const struct { { "EAST", +10, 0, }, /* Eastern Australian Standard */ { "EADT", +10, 1, }, /* Eastern Australian Daylight */ { "GST", +10, 0, }, /* Guam Standard, USSR Zone 9 */ - { "NZT", +11, 0, }, /* New Zealand */ - { "NZST", +11, 0, }, /* New Zealand Standard */ - { "NZDT", +11, 1, }, /* New Zealand Daylight */ + { "NZT", +12, 0, }, /* New Zealand */ + { "NZST", +12, 0, }, /* New Zealand Standard */ + { "NZDT", +12, 1, }, /* New Zealand Daylight */ { "IDLE", +12, 0, }, /* International Date Line East */ }; @@ -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; @@ -57,7 +57,7 @@ static int parse_diff_color_slot(const char *var, int ofs) static struct ll_diff_driver { const char *name; struct ll_diff_driver *next; - char *cmd; + const char *cmd; } *user_diff, **user_diff_tail; /* @@ -86,10 +86,7 @@ static int parse_lldiff_command(const char *var, const char *ep, const char *val user_diff_tail = &(drv->next); } - if (!value) - return error("%s: lacks value", var); - drv->cmd = strdup(value); - return 0; + return git_config_string(&(drv->cmd), var, value); } /* @@ -158,16 +155,16 @@ int git_diff_ui_config(const char *var, const char *value) return 0; } if (!strcmp(var, "diff.external")) { + if (!value) + return config_error_nonbool(var); external_diff_cmd_cfg = xstrdup(value); return 0; } if (!prefixcmp(var, "diff.")) { const char *ep = strrchr(var, '.'); - if (ep != var + 4) { - if (!strcmp(ep, ".command")) - return parse_lldiff_command(var, ep, value); - } + if (ep != var + 4 && !strcmp(ep, ".command")) + return parse_lldiff_command(var, ep, value); } return git_diff_basic_config(var, value); @@ -177,6 +174,8 @@ int git_diff_basic_config(const char *var, const char *value) { if (!prefixcmp(var, "diff.color.") || !prefixcmp(var, "color.diff.")) { int slot = parse_diff_color_slot(var, 11); + if (!value) + return config_error_nonbool(var); color_parse(value, var, diff_colors[slot]); return 0; } @@ -184,12 +183,15 @@ int git_diff_basic_config(const char *var, const char *value) if (!prefixcmp(var, "diff.")) { const char *ep = strrchr(var, '.'); if (ep != var + 4) { - if (!strcmp(ep, ".funcname")) + if (!strcmp(ep, ".funcname")) { + if (!value) + return config_error_nonbool(var); return parse_funcname_pattern(var, ep, value); + } } } - return git_default_config(var, value); + return git_color_default_config(var, value); } static char *quote_two(const char *one, const char *two) @@ -270,8 +272,8 @@ static void print_line_count(int count) } } -static void copy_file(int prefix, const char *data, int size, - const char *set, const char *reset) +static void copy_file_with_prefix(int prefix, const char *data, int size, + const char *set, const char *reset) { int ch, nl_just_seen = 1; while (0 < size--) { @@ -329,9 +331,9 @@ static void emit_rewrite_diff(const char *name_a, print_line_count(lc_b); printf(" @@%s\n", reset); if (lc_a) - copy_file('-', one->data, one->size, old, reset); + copy_file_with_prefix('-', one->data, one->size, old, reset); if (lc_b) - copy_file('+', two->data, two->size, new, reset); + copy_file_with_prefix('+', two->data, two->size, new, reset); } static int fill_mmfile(mmfile_t *mf, struct diff_filespec *one) @@ -980,6 +982,90 @@ static void show_numstat(struct diffstat_t* data, struct diff_options *options) } } +struct diffstat_dir { + struct diffstat_file **files; + int nr, percent, cumulative; +}; + +static long gather_dirstat(struct diffstat_dir *dir, unsigned long changed, const char *base, int baselen) +{ + unsigned long this_dir = 0; + unsigned int sources = 0; + + while (dir->nr) { + struct diffstat_file *f = *dir->files; + int namelen = strlen(f->name); + unsigned long this; + char *slash; + + if (namelen < baselen) + break; + if (memcmp(f->name, base, baselen)) + break; + slash = strchr(f->name + baselen, '/'); + if (slash) { + int newbaselen = slash + 1 - f->name; + this = gather_dirstat(dir, changed, f->name, newbaselen); + sources++; + } else { + if (f->is_unmerged || f->is_binary) + this = 0; + else + this = f->added + f->deleted; + dir->files++; + dir->nr--; + sources += 2; + } + this_dir += this; + } + + /* + * We don't report dirstat's for + * - the top level + * - or cases where everything came from a single directory + * under this directory (sources == 1). + */ + if (baselen && sources != 1) { + int permille = this_dir * 1000 / changed; + if (permille) { + int percent = permille / 10; + if (percent >= dir->percent) { + printf("%4d.%01d%% %.*s\n", percent, permille % 10, baselen, base); + if (!dir->cumulative) + return 0; + } + } + } + return this_dir; +} + +static void show_dirstat(struct diffstat_t *data, struct diff_options *options) +{ + int i; + unsigned long changed; + struct diffstat_dir dir; + + /* Calculate total changes */ + changed = 0; + for (i = 0; i < data->nr; i++) { + if (data->files[i]->is_binary || data->files[i]->is_unmerged) + continue; + changed += data->files[i]->added; + changed += data->files[i]->deleted; + } + + /* This can happen even with many files, if everything was renames */ + if (!changed) + return; + + /* Show all directories with more than x% of the changes */ + dir.files = data->files; + dir.nr = data->nr; + dir.percent = options->dirstat_percent; + dir.cumulative = options->output_format & DIFF_FORMAT_CUMULATIVE; + gather_dirstat(&dir, changed, "", 0); +} + static void free_diffstat_info(struct diffstat_t *diffstat) { int i; @@ -1011,6 +1097,7 @@ static void checkdiff_consume(void *priv, char *line, unsigned long len) char *err; if (line[0] == '+') { + data->lineno++; data->status = check_and_emit_line(line + 1, len - 1, data->ws_rule, NULL, NULL, NULL, NULL); if (!data->status) @@ -1021,13 +1108,12 @@ static void checkdiff_consume(void *priv, char *line, unsigned long len) emit_line(set, reset, line, 1); (void)check_and_emit_line(line + 1, len - 1, data->ws_rule, stdout, set, reset, ws); - data->lineno++; } else if (line[0] == ' ') data->lineno++; else if (line[0] == '@') { char *plus = strchr(line, '+'); if (plus) - data->lineno = strtol(plus, NULL, 10); + data->lineno = strtol(plus, NULL, 10) - 1; else die("invalid diff"); } @@ -1197,7 +1283,7 @@ static struct builtin_funcname_pattern { "new\\|return\\|switch\\|throw\\|while\\)\n" "^[ ]*\\(\\([ ]*" "[A-Za-z_][A-Za-z_0-9]*\\)\\{2,\\}" - "[ ]*([^;]*$\\)" }, + "[ ]*([^;]*\\)$" }, { "tex", "^\\(\\\\\\(sub\\)*section{.*\\)$" }, }; @@ -1397,6 +1483,7 @@ static void builtin_diffstat(const char *name_a, const char *name_b, } static void builtin_checkdiff(const char *name_a, const char *name_b, + const char *attr_path, struct diff_filespec *one, struct diff_filespec *two, struct diff_options *o) { @@ -1411,7 +1498,7 @@ static void builtin_checkdiff(const char *name_a, const char *name_b, data.filename = name_b ? name_b : name_a; data.lineno = 0; data.color_diff = DIFF_OPT_TST(o, COLOR_DIFF); - data.ws_rule = whitespace_rule(data.filename); + data.ws_rule = whitespace_rule(attr_path); if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0) die("unable to read files to diff"); @@ -1629,7 +1716,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; @@ -1836,6 +1923,9 @@ static const char *external_diff_attr(const char *name) { struct git_attr_check attr_diff_check; + if (!name) + return NULL; + setup_diff_attr_check(&attr_diff_check); if (!git_checkattr(name, 1, &attr_diff_check)) { const char *value = attr_diff_check.value; @@ -1855,6 +1945,7 @@ static const char *external_diff_attr(const char *name) static void run_diff_cmd(const char *pgm, const char *name, const char *other, + const char *attr_path, struct diff_filespec *one, struct diff_filespec *two, const char *xfrm_msg, @@ -1864,7 +1955,7 @@ static void run_diff_cmd(const char *pgm, if (!DIFF_OPT_TST(o, ALLOW_EXTERNAL)) pgm = NULL; else { - const char *cmd = external_diff_attr(name); + const char *cmd = external_diff_attr(attr_path); if (cmd) pgm = cmd; } @@ -1905,6 +1996,15 @@ static int similarity_index(struct diff_filepair *p) return p->score * 100 / MAX_SCORE; } +static void strip_prefix(int prefix_length, const char **namep, const char **otherp) +{ + /* Strip the prefix but do not molest /dev/null and absolute paths */ + if (*namep && **namep != '/') + *namep += prefix_length; + if (*otherp && **otherp != '/') + *otherp += prefix_length; +} + static void run_diff(struct diff_filepair *p, struct diff_options *o) { const char *pgm = external_diff(); @@ -1914,16 +2014,21 @@ static void run_diff(struct diff_filepair *p, struct diff_options *o) struct diff_filespec *two = p->two; const char *name; const char *other; + const char *attr_path; int complete_rewrite = 0; + name = p->one->path; + other = (strcmp(name, p->two->path) ? p->two->path : NULL); + attr_path = name; + if (o->prefix_length) + strip_prefix(o->prefix_length, &name, &other); if (DIFF_PAIR_UNMERGED(p)) { - run_diff_cmd(pgm, p->one->path, NULL, NULL, NULL, NULL, o, 0); + run_diff_cmd(pgm, name, NULL, attr_path, + NULL, NULL, NULL, o, 0); return; } - name = p->one->path; - other = (strcmp(name, p->two->path) ? p->two->path : NULL); diff_fill_sha1_info(one); diff_fill_sha1_info(two); @@ -1986,15 +2091,17 @@ static void run_diff(struct diff_filepair *p, struct diff_options *o) * needs to be split into deletion and creation. */ struct diff_filespec *null = alloc_filespec(two->path); - run_diff_cmd(NULL, name, other, one, null, xfrm_msg, o, 0); + run_diff_cmd(NULL, name, other, attr_path, + one, null, xfrm_msg, o, 0); free(null); null = alloc_filespec(one->path); - run_diff_cmd(NULL, name, other, null, two, xfrm_msg, o, 0); + run_diff_cmd(NULL, name, other, attr_path, + null, two, xfrm_msg, o, 0); free(null); } else - run_diff_cmd(pgm, name, other, one, two, xfrm_msg, o, - complete_rewrite); + run_diff_cmd(pgm, name, other, attr_path, + one, two, xfrm_msg, o, complete_rewrite); strbuf_release(&msg); } @@ -2015,6 +2122,9 @@ static void run_diffstat(struct diff_filepair *p, struct diff_options *o, name = p->one->path; other = (strcmp(name, p->two->path) ? p->two->path : NULL); + if (o->prefix_length) + strip_prefix(o->prefix_length, &name, &other); + diff_fill_sha1_info(p->one); diff_fill_sha1_info(p->two); @@ -2027,6 +2137,7 @@ static void run_checkdiff(struct diff_filepair *p, struct diff_options *o) { const char *name; const char *other; + const char *attr_path; if (DIFF_PAIR_UNMERGED(p)) { /* unmerged */ @@ -2035,11 +2146,15 @@ static void run_checkdiff(struct diff_filepair *p, struct diff_options *o) name = p->one->path; other = (strcmp(name, p->two->path) ? p->two->path : NULL); + attr_path = other ? other : name; + + if (o->prefix_length) + strip_prefix(o->prefix_length, &name, &other); diff_fill_sha1_info(p->one); diff_fill_sha1_info(p->two); - builtin_checkdiff(name, other, p->one, p->two, o); + builtin_checkdiff(name, other, attr_path, p->one, p->two, o); } void diff_setup(struct diff_options *options) @@ -2048,12 +2163,13 @@ void diff_setup(struct diff_options *options) options->line_termination = '\n'; options->break_opt = -1; options->rename_limit = -1; + options->dirstat_percent = 3; options->context = 3; options->msg_sep = ""; 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); @@ -2081,6 +2197,13 @@ int diff_setup_done(struct diff_options *options) if (DIFF_OPT_TST(options, FIND_COPIES_HARDER)) options->detect_rename = DIFF_DETECT_COPY; + if (!DIFF_OPT_TST(options, RELATIVE_NAME)) + options->prefix = NULL; + if (options->prefix) + options->prefix_length = strlen(options->prefix); + else + options->prefix_length = 0; + if (options->output_format & (DIFF_FORMAT_NAME | DIFF_FORMAT_NAME_STATUS | DIFF_FORMAT_CHECKDIFF | @@ -2089,6 +2212,7 @@ int diff_setup_done(struct diff_options *options) DIFF_FORMAT_NUMSTAT | DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_SHORTSTAT | + DIFF_FORMAT_DIRSTAT | DIFF_FORMAT_SUMMARY | DIFF_FORMAT_PATCH); @@ -2100,6 +2224,7 @@ int diff_setup_done(struct diff_options *options) DIFF_FORMAT_NUMSTAT | DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_SHORTSTAT | + DIFF_FORMAT_DIRSTAT | DIFF_FORMAT_SUMMARY | DIFF_FORMAT_CHECKDIFF)) DIFF_OPT_SET(options, RECURSIVE); @@ -2210,6 +2335,10 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) options->output_format |= DIFF_FORMAT_NUMSTAT; else if (!strcmp(arg, "--shortstat")) options->output_format |= DIFF_FORMAT_SHORTSTAT; + else if (opt_arg(arg, 'X', "dirstat", &options->dirstat_percent)) + options->output_format |= DIFF_FORMAT_DIRSTAT; + else if (!strcmp(arg, "--cumulative")) + options->output_format |= DIFF_FORMAT_CUMULATIVE; else if (!strcmp(arg, "--check")) options->output_format |= DIFF_FORMAT_CHECKDIFF; else if (!strcmp(arg, "--summary")) @@ -2269,6 +2398,12 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) } else if (!strcmp(arg, "--no-renames")) options->detect_rename = 0; + else if (!strcmp(arg, "--relative")) + DIFF_OPT_SET(options, RELATIVE_NAME); + else if (!prefixcmp(arg, "--relative=")) { + DIFF_OPT_SET(options, RELATIVE_NAME); + options->prefix = arg + 11; + } /* xdiff options */ else if (!strcmp(arg, "-w") || !strcmp(arg, "--ignore-all-space")) @@ -2480,12 +2615,20 @@ static void diff_flush_raw(struct diff_filepair *p, struct diff_options *opt) printf("%c%c", p->status, inter_name_termination); } - if (p->status == DIFF_STATUS_COPIED || p->status == DIFF_STATUS_RENAMED) { - write_name_quoted(p->one->path, stdout, inter_name_termination); - write_name_quoted(p->two->path, stdout, line_termination); + if (p->status == DIFF_STATUS_COPIED || + p->status == DIFF_STATUS_RENAMED) { + const char *name_a, *name_b; + name_a = p->one->path; + name_b = p->two->path; + strip_prefix(opt->prefix_length, &name_a, &name_b); + write_name_quoted(name_a, stdout, inter_name_termination); + write_name_quoted(name_b, stdout, line_termination); } else { - const char *path = p->one->mode ? p->one->path : p->two->path; - write_name_quoted(path, stdout, line_termination); + const char *name_a, *name_b; + name_a = p->one->mode ? p->one->path : p->two->path; + name_b = NULL; + strip_prefix(opt->prefix_length, &name_a, &name_b); + write_name_quoted(name_a, stdout, line_termination); } } @@ -2682,8 +2825,13 @@ static void flush_one_pair(struct diff_filepair *p, struct diff_options *opt) diff_flush_checkdiff(p, opt); else if (fmt & (DIFF_FORMAT_RAW | DIFF_FORMAT_NAME_STATUS)) diff_flush_raw(p, opt); - else if (fmt & DIFF_FORMAT_NAME) - write_name_quoted(p->two->path, stdout, opt->line_termination); + else if (fmt & DIFF_FORMAT_NAME) { + const char *name_a, *name_b; + name_a = p->two->path; + name_b = NULL; + strip_prefix(opt->prefix_length, &name_a, &name_b); + write_name_quoted(name_a, stdout, opt->line_termination); + } } static void show_file_mode_name(const char *newdelete, struct diff_filespec *fs) @@ -2928,7 +3076,7 @@ void diff_flush(struct diff_options *options) separator++; } - if (output_format & (DIFF_FORMAT_DIFFSTAT|DIFF_FORMAT_SHORTSTAT|DIFF_FORMAT_NUMSTAT)) { + if (output_format & (DIFF_FORMAT_DIFFSTAT|DIFF_FORMAT_SHORTSTAT|DIFF_FORMAT_NUMSTAT|DIFF_FORMAT_DIRSTAT)) { struct diffstat_t diffstat; memset(&diffstat, 0, sizeof(struct diffstat_t)); @@ -2938,6 +3086,8 @@ void diff_flush(struct diff_options *options) if (check_pair_status(p)) diff_flush_stat(p, options, &diffstat); } + if (output_format & DIFF_FORMAT_DIRSTAT) + show_dirstat(&diffstat, options); if (output_format & DIFF_FORMAT_NUMSTAT) show_numstat(&diffstat, options); if (output_format & DIFF_FORMAT_DIFFSTAT) @@ -3169,6 +3319,11 @@ void diff_addremove(struct diff_options *options, if (!path) path = ""; sprintf(concatpath, "%s%s", base, path); + + if (options->prefix && + strncmp(concatpath, options->prefix, options->prefix_length)) + return; + one = alloc_filespec(concatpath); two = alloc_filespec(concatpath); @@ -3198,6 +3353,11 @@ void diff_change(struct diff_options *options, } if (!path) path = ""; sprintf(concatpath, "%s%s", base, path); + + if (options->prefix && + strncmp(concatpath, options->prefix, options->prefix_length)) + return; + one = alloc_filespec(concatpath); two = alloc_filespec(concatpath); fill_filespec(one, old_sha1, old_mode); @@ -3212,6 +3372,11 @@ void diff_unmerge(struct diff_options *options, unsigned mode, const unsigned char *sha1) { struct diff_filespec *one, *two; + + if (options->prefix && + strncmp(path, options->prefix, options->prefix_length)) + return; + one = alloc_filespec(path); two = alloc_filespec(path); fill_filespec(one, sha1, mode); @@ -30,6 +30,8 @@ typedef void (*diff_format_fn_t)(struct diff_queue_struct *q, #define DIFF_FORMAT_SUMMARY 0x0008 #define DIFF_FORMAT_PATCH 0x0010 #define DIFF_FORMAT_SHORTSTAT 0x0020 +#define DIFF_FORMAT_DIRSTAT 0x0040 +#define DIFF_FORMAT_CUMULATIVE 0x0080 /* These override all above */ #define DIFF_FORMAT_NAME 0x0100 @@ -60,6 +62,7 @@ typedef void (*diff_format_fn_t)(struct diff_queue_struct *q, #define DIFF_OPT_EXIT_WITH_STATUS (1 << 14) #define DIFF_OPT_REVERSE_DIFF (1 << 15) #define DIFF_OPT_CHECK_FAILED (1 << 16) +#define DIFF_OPT_RELATIVE_NAME (1 << 17) #define DIFF_OPT_TST(opts, flag) ((opts)->flags & DIFF_OPT_##flag) #define DIFF_OPT_SET(opts, flag) ((opts)->flags |= DIFF_OPT_##flag) #define DIFF_OPT_CLR(opts, flag) ((opts)->flags &= ~DIFF_OPT_##flag) @@ -80,8 +83,11 @@ struct diff_options { int pickaxe_opts; int rename_score; int rename_limit; + int dirstat_percent; int setup; int abbrev; + const char *prefix; + int prefix_length; const char *msg_sep; const char *stat_sep; long xdl_opts; @@ -174,6 +180,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: @@ -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); diff --git a/environment.c b/environment.c index 18a1c4eec..3527f1663 100644 --- a/environment.c +++ b/environment.c @@ -30,11 +30,12 @@ int core_compression_seen; size_t packed_git_window_size = DEFAULT_PACKED_GIT_WINDOW_SIZE; size_t packed_git_limit = DEFAULT_PACKED_GIT_LIMIT; size_t delta_base_cache_limit = 16 * 1024 * 1024; -char *pager_program; +const char *pager_program; int pager_use_color = 1; -char *editor_program; -char *excludes_file; +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 45b4edf36..0d3449f2c 100644 --- a/fast-import.c +++ b/fast-import.c @@ -275,6 +275,8 @@ struct recent_command static unsigned long max_depth = 10; static off_t max_packsize = (1LL << 32) - 1; static int force_update; +static int pack_compression_level = Z_DEFAULT_COMPRESSION; +static int pack_compression_seen; /* Stats and misc. counters */ static uintmax_t alloc_count; @@ -370,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()); @@ -428,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; @@ -447,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); } @@ -1038,7 +1070,7 @@ static int store_object( delta = NULL; memset(&s, 0, sizeof(s)); - deflateInit(&s, zlib_compression_level); + deflateInit(&s, pack_compression_level); if (delta) { s.next_in = delta; s.avail_in = deltalen; @@ -1066,7 +1098,7 @@ static int store_object( delta = NULL; memset(&s, 0, sizeof(s)); - deflateInit(&s, zlib_compression_level); + deflateInit(&s, pack_compression_level); s.next_in = (void *)dat->buf; s.avail_in = dat->len; s.avail_out = deflateBound(&s, s.avail_in); @@ -1123,6 +1155,24 @@ static int store_object( return 0; } +/* All calls must be guarded by find_object() or find_mark() to + * ensure the 'struct object_entry' passed was written by this + * process instance. We unpack the entry by the offset, avoiding + * the need for the corresponding .idx file. This unpacking rule + * works because we only use OBJ_REF_DELTA within the packfiles + * created by fast-import. + * + * oe must not be NULL. Such an oe usually comes from giving + * an unknown SHA-1 to find_object() or an undefined mark to + * find_mark(). Callers must test for this condition and use + * the standard read_sha1_file() when it happens. + * + * oe->pack_id must not be MAX_PACK_ID. Such an oe is usually from + * find_mark(), where the mark was reloaded from an existing marks + * file and is referencing an object that this fast-import process + * instance did not write out to a packfile. Callers must test for + * this condition and use read_sha1_file() instead. + */ static void *gfi_unpack_entry( struct object_entry *oe, unsigned long *sizep) @@ -1130,7 +1180,22 @@ static void *gfi_unpack_entry( enum object_type type; struct packed_git *p = all_packs[oe->pack_id]; if (p == pack_data && p->pack_size < (pack_size + 20)) { + /* The object is stored in the packfile we are writing to + * and we have modified it since the last time we scanned + * back to read a previously written object. If an old + * window covered [p->pack_size, p->pack_size + 20) its + * data is stale and is not valid. Closing all windows + * and updating the packfile length ensures we can read + * the newly written data. + */ close_pack_windows(p); + + /* We have to offer 20 bytes additional on the end of + * the packfile as the core unpacker code assumes the + * footer is present at the file end and must promise + * at least 20 bytes within any window it maps. But + * we don't actually create the footer here. + */ p->pack_size = pack_size + 20; } return unpack_entry(p, oe->offset, &type, sizep); @@ -1169,6 +1234,8 @@ static void load_tree(struct tree_entry *root) die("Not a tree: %s", sha1_to_hex(sha1)); t->delta_depth = myoe->depth; buf = gfi_unpack_entry(myoe, &size); + if (!buf) + die("Can't load tree %s", sha1_to_hex(sha1)); } else { enum object_type type; buf = read_sha1_file(sha1, &type, &size); @@ -2282,6 +2349,27 @@ static void import_marks(const char *input_file) fclose(f); } +static int git_pack_config(const char *k, const char *v) +{ + if (!strcmp(k, "pack.depth")) { + max_depth = git_config_int(k, v); + if (max_depth > MAX_DEPTH) + max_depth = MAX_DEPTH; + return 0; + } + if (!strcmp(k, "pack.compression")) { + int level = git_config_int(k, v); + if (level == -1) + level = Z_DEFAULT_COMPRESSION; + else if (level < 0 || level > Z_BEST_COMPRESSION) + die("bad pack compression level %d", level); + pack_compression_level = level; + pack_compression_seen = 1; + return 0; + } + return git_default_config(k, v); +} + static const char fast_import_usage[] = "git-fast-import [--date-format=f] [--max-pack-size=n] [--depth=n] [--active-branches=n] [--export-marks=marks.file]"; @@ -2289,7 +2377,10 @@ int main(int argc, const char **argv) { unsigned int i, show_stats = 1; - git_config(git_default_config); + git_config(git_pack_config); + if (!pack_compression_seen && core_compression_seen) + pack_compression_level = core_compression_level; + alloc_objects(object_entry_alloc); strbuf_init(&command_buf, 0); atom_table = xcalloc(atom_table_sz, sizeof(struct atom_str*)); diff --git a/fetch-pack.h b/fetch-pack.h index a7888ea30..8d35ef60b 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 17ca5b84f..a0a81f134 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 { @@ -14,7 +14,7 @@ b,binary pass --allo-binary-replacement to git-apply 3,3way allow fall back on 3way merging if needed s,signoff add a Signed-off-by line to the commit message u,utf8 recode into utf8 (default) -k,keep pass -k flagg to git-mailinfo +k,keep pass -k flag to git-mailinfo whitespace= pass it through git-apply C= pass it through git-apply p= pass it through git-apply diff --git a/git-bisect.sh b/git-bisect.sh index 538524989..2c32d0b9e 100755 --- a/git-bisect.sh +++ b/git-bisect.sh @@ -26,6 +26,9 @@ OPTIONS_SPEC= . git-sh-setup require_work_tree +_x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]' +_x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40" + sq() { @@PERL@@ -e ' for (@ARGV) { @@ -60,19 +63,22 @@ bisect_start() { # top-of-line master first! # head=$(GIT_DIR="$GIT_DIR" git symbolic-ref HEAD) || - die "Bad HEAD - I need a symbolic ref" + head=$(GIT_DIR="$GIT_DIR" git rev-parse --verify HEAD) || + die "Bad HEAD - I need a HEAD" case "$head" in refs/heads/bisect) - if [ -s "$GIT_DIR/head-name" ]; then - branch=`cat "$GIT_DIR/head-name"` + if [ -s "$GIT_DIR/BISECT_START" ]; then + branch=`cat "$GIT_DIR/BISECT_START"` else branch=master fi git checkout $branch || exit ;; - refs/heads/*) + refs/heads/*|$_x40) + # This error message should only be triggered by cogito usage, + # and cogito users should understand it relates to cg-seek. [ -s "$GIT_DIR/head-name" ] && die "won't bisect on seeked tree" - echo "${head#refs/heads/}" >"$GIT_DIR/head-name" + echo "${head#refs/heads/}" >"$GIT_DIR/BISECT_START" ;; *) die "Bad HEAD - strange symbolic ref" @@ -131,7 +137,7 @@ bisect_write() { *) die "Bad bisect_write argument: $state" ;; esac git update-ref "refs/bisect/$tag" "$rev" - echo "# $state: "$(git show-branch $rev) >>"$GIT_DIR/BISECT_LOG" + echo "# $state: $(git show-branch $rev)" >>"$GIT_DIR/BISECT_LOG" test -z "$nolog" && echo "git-bisect $state $rev" >>"$GIT_DIR/BISECT_LOG" } @@ -327,9 +333,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 @@ -349,8 +355,8 @@ bisect_reset() { return } case "$#" in - 0) if [ -s "$GIT_DIR/head-name" ]; then - branch=`cat "$GIT_DIR/head-name"` + 0) if [ -s "$GIT_DIR/BISECT_START" ]; then + branch=`cat "$GIT_DIR/BISECT_START"` else branch=master fi ;; @@ -361,7 +367,9 @@ bisect_reset() { usage ;; esac if git checkout "$branch"; then + # Cleanup head-name if it got left by an old version of git-bisect rm -f "$GIT_DIR/head-name" + rm -f "$GIT_DIR/BISECT_START" bisect_clean_state fi } diff --git a/git-clone.sh b/git-clone.sh index b4e858c38..0d686c3a0 100755 --- a/git-clone.sh +++ b/git-clone.sh @@ -409,11 +409,12 @@ else cd "$D" || exit fi -if test -z "$bare" && test -f "$GIT_DIR/REMOTE_HEAD" +if test -z "$bare" then # a non-bare repository is always in separate-remote layout remote_top="refs/remotes/$origin" - head_sha1=`cat "$GIT_DIR/REMOTE_HEAD"` + head_sha1= + test ! -r "$GIT_DIR/REMOTE_HEAD" || head_sha1=`cat "$GIT_DIR/REMOTE_HEAD"` case "$head_sha1" in 'ref: refs/'*) # Uh-oh, the remote told us (http transport done against @@ -470,9 +471,16 @@ then git config branch."$head_points_at".merge "refs/heads/$head_points_at" ;; '') - # Source had detached HEAD pointing nowhere - git update-ref --no-deref HEAD "$head_sha1" && - rm -f "refs/remotes/$origin/HEAD" + if test -z "$head_sha1" + then + # Source had nonexistent ref in HEAD + echo >&2 "Warning: Remote HEAD refers to nonexistent ref, unable to checkout." + no_checkout=t + else + # Source had detached HEAD pointing nowhere + git update-ref --no-deref HEAD "$head_sha1" && + rm -f "refs/remotes/$origin/HEAD" + fi ;; esac diff --git a/git-compat-util.h b/git-compat-util.h index b6ef5442b..2a40703c8 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -68,7 +68,9 @@ #include <sys/poll.h> #include <sys/socket.h> #include <sys/ioctl.h> +#ifndef NO_SYS_SELECT_H #include <sys/select.h> +#endif #include <assert.h> #include <regex.h> #include <netinet/in.h> @@ -202,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 @@ -424,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 d2e50c342..b6036bd4d 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-cvsimport.perl b/git-cvsimport.perl index 5694978c4..951624233 100755 --- a/git-cvsimport.perl +++ b/git-cvsimport.perl @@ -164,7 +164,7 @@ if ($#ARGV == 0) { our @mergerx = (); if ($opt_m) { - @mergerx = ( qr/\W(?:from|of|merge|merging|merged) (\w+)/i ); + @mergerx = ( qr/\b(?:from|of|merge|merging|merged) (\w+)/i ); } if ($opt_M) { push (@mergerx, qr/$opt_M/); diff --git a/git-cvsserver.perl b/git-cvsserver.perl index ecded3b9c..afe3d0b7f 100755 --- a/git-cvsserver.perl +++ b/git-cvsserver.perl @@ -2543,8 +2543,15 @@ sub update if ($parent eq $lastpicked) { next; } - my $base = safe_pipe_capture('git-merge-base', + my $base = eval { + safe_pipe_capture('git-merge-base', $lastpicked, $parent); + }; + # The two branches may not be related at all, + # in which case merge base simply fails to find + # any, but that's Ok. + next if ($@); + chomp $base; if ($base) { my @merged; diff --git a/git-filter-branch.sh b/git-filter-branch.sh index ebf05ca60..49e13f0bb 100755 --- a/git-filter-branch.sh +++ b/git-filter-branch.sh @@ -114,7 +114,6 @@ orig_namespace=refs/original/ force= while : do - test $# = 0 && usage case "$1" in --) shift @@ -189,6 +188,9 @@ cd "$tempdir/t" && workdir="$(pwd)" || die "" +# Remove tempdir on exit +trap 'cd ../..; rm -rf "$tempdir"' 0 + # Make sure refs/original is empty git for-each-ref > "$tempdir"/backup-refs while read sha1 type name @@ -210,7 +212,7 @@ GIT_WORK_TREE=. export GIT_DIR GIT_WORK_TREE # The refs should be updated if their heads were rewritten -git rev-parse --no-flags --revs-only --symbolic-full-name "$@" | +git rev-parse --no-flags --revs-only --symbolic-full-name --default HEAD "$@" | sed -e '/^^/d' >"$tempdir"/heads test -s "$tempdir"/heads || @@ -274,10 +276,11 @@ while read commit parents; do eval "$filter_tree" < /dev/null || die "tree filter failed: $filter_tree" - git diff-index -r $commit | cut -f 2- | tr '\012' '\000' | \ - xargs -0 git update-index --add --replace --remove - git ls-files -z --others | \ - xargs -0 git update-index --add --replace --remove + ( + git diff-index -r --name-only $commit + git ls-files --others + ) | + git update-index --add --replace --remove --stdin fi eval "$filter_index" < /dev/null || @@ -406,6 +409,8 @@ fi cd ../.. rm -rf "$tempdir" +trap - 0 + unset GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE test -z "$ORIG_GIT_DIR" || GIT_DIR="$ORIG_GIT_DIR" && export GIT_DIR test -z "$ORIG_GIT_WORK_TREE" || GIT_WORK_TREE="$ORIG_GIT_WORK_TREE" && diff --git a/git-gui/Makefile b/git-gui/Makefile index 1baf4b086..01e0a46ba 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 @@ -67,7 +68,7 @@ ifndef V QUIET_GEN = $(QUIET)echo ' ' GEN '$@' && QUIET_INDEX = $(QUIET)echo ' ' INDEX $(dir $@) && QUIET_MSGFMT0 = $(QUIET)printf ' MSGFMT %12s ' $@ && v=` - QUIET_MSGFMT1 = 2>&1` && echo "$$v" | sed -e 's/fuzzy translations/fuzzy/' | sed -e 's/ messages//g' + QUIET_MSGFMT1 = 2>&1` && echo "$$v" | sed -e 's/fuzzy translations/fuzzy/' | sed -e 's/ messages*//g' QUIET_2DEVNULL = 2>/dev/null INSTALL_D0 = dir= @@ -91,9 +92,20 @@ ifndef V REMOVE_F1 = && echo ' ' REMOVE `basename "$$dst"` && $(RM_RF) "$$dst" endif -TCL_PATH ?= tclsh TCLTK_PATH ?= wish -TKFRAMEWORK = /Library/Frameworks/Tk.framework/Resources/Wish.app +ifeq (./,$(dir $(TCLTK_PATH))) + TCL_PATH ?= $(subst wish,tclsh,$(TCLTK_PATH)) +else + TCL_PATH ?= $(dir $(TCLTK_PATH))$(notdir $(subst wish,tclsh,$(TCLTK_PATH))) +endif + +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 = @@ -119,7 +131,17 @@ GITGUI_MACOSXAPP := ifeq ($(uname_O),Cygwin) GITGUI_SCRIPT := `cygpath --windows --absolute "$(GITGUI_SCRIPT)"` - gg_libdir_sed_in := $(shell cygpath --windows --absolute "$(gg_libdir)") + + # Is this a Cygwin Tcl/Tk binary? If so it knows how to do + # POSIX path translation just like cygpath does and we must + # keep libdir in POSIX format so Cygwin packages of git-gui + # work no matter where the user installs them. + # + ifeq ($(shell echo 'puts [file normalize /]' | '$(TCL_PATH_SQ)'),$(shell cygpath --mixed --absolute /)) + gg_libdir_sed_in := $(gg_libdir) + else + gg_libdir_sed_in := $(shell cygpath --windows --absolute "$(gg_libdir)") + endif else ifeq ($(exedir),$(gg_libdir)) GITGUI_RELATIVE := 1 @@ -147,7 +169,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 +179,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)|' \ @@ -198,6 +221,9 @@ ifdef NO_MSGFMT MSGFMT ?= $(TCL_PATH) po/po2msg.sh else MSGFMT ?= msgfmt + ifeq ($(shell $(MSGFMT) >/dev/null 2>&1 || echo $$?),127) + MSGFMT := $(TCL_PATH) po/po2msg.sh + endif endif msgsdir = $(gg_libdir)/msgs diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh index fcb2ab2fb..238a2393f 100755 --- a/git-gui/git-gui.sh +++ b/git-gui/git-gui.sh @@ -438,6 +438,34 @@ proc git_write {args} { return [open [concat $opt $cmdp $args] w] } +proc githook_read {hook_name args} { + set pchook [gitdir hooks $hook_name] + lappend args 2>@1 + + # On Cygwin [file executable] might lie so we need to ask + # the shell if the hook is executable. Yes that's annoying. + # + if {[is_Cygwin]} { + upvar #0 _sh interp + if {![info exists interp]} { + set interp [_which sh] + } + if {$interp eq {}} { + error "hook execution requires sh (not in PATH)" + } + + set scr {if test -x "$1";then exec "$@";fi} + set sh_c [list | $interp -c $scr $interp $pchook] + return [_open_stdout_stderr [concat $sh_c $args]] + } + + if {[file executable $pchook]} { + return [_open_stdout_stderr [concat [list | $pchook] $args]] + } + + return {} +} + proc sq {value} { regsub -all ' $value "'\\''" value return "'$value'" @@ -584,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 { @@ -634,7 +663,7 @@ if {![regsub {^git version } $_git_version {} _git_version]} { } set _real_git_version $_git_version -regsub -- {-dirty$} $_git_version {} _git_version +regsub -- {[\-\.]dirty$} $_git_version {} _git_version regsub {\.[0-9]+\.g[0-9a-f]+$} $_git_version {} _git_version regsub {\.rc[0-9]+$} $_git_version {} _git_version regsub {\.GIT$} $_git_version {} _git_version @@ -1655,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 @@ -1682,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 @@ -2426,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 # @@ -2829,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 719fc547b..47be8eb97 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 f24396692..6e1411711 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/choose_repository.tcl b/git-gui/lib/choose_repository.tcl index 86faf24cc..0adcf9d95 100644 --- a/git-gui/lib/choose_repository.tcl +++ b/git-gui/lib/choose_repository.tcl @@ -11,6 +11,7 @@ field w_quit ; # Quit button field o_cons ; # Console object (if active) field w_types ; # List of type buttons in clone field w_recentlist ; # Listbox containing recent repositories +field w_localpath ; # Entry widget bound to local_path field done 0 ; # Finished picking the repository? field local_path {} ; # Where this repository is locally @@ -385,6 +386,7 @@ method _do_new {} { button $w_body.where.b \ -text [mc "Browse"] \ -command [cb _new_local_path] + set w_localpath $w_body.where.t pack $w_body.where.b -side right pack $w_body.where.l -side left @@ -416,6 +418,7 @@ method _new_local_path {} { return } set local_path $p + $w_localpath icursor end } method _do_new2 {} { @@ -481,6 +484,7 @@ method _do_clone {} { -text [mc "Browse"] \ -command [cb _new_local_path] grid $args.where_l $args.where_t $args.where_b -sticky ew + set w_localpath $args.where_t label $args.type_l -text [mc "Clone Type:"] frame $args.type_f diff --git a/git-gui/lib/choose_rev.tcl b/git-gui/lib/choose_rev.tcl index a063c5bc4..c8821c146 100644 --- a/git-gui/lib/choose_rev.tcl +++ b/git-gui/lib/choose_rev.tcl @@ -451,7 +451,8 @@ method _sb_set {sb orient first last} { focus $old_focus } } - $sb set $first $last + + catch {$sb set $first $last} } method _show_tooltip {pos} { diff --git a/git-gui/lib/commit.tcl b/git-gui/lib/commit.tcl index 1c0586c40..40a710355 100644 --- a/git-gui/lib/commit.tcl +++ b/git-gui/lib/commit.tcl @@ -192,45 +192,52 @@ A good commit message has the following format: return } - # -- Run the pre-commit hook. + # -- Build the message file. # - set pchook [gitdir hooks pre-commit] + set msg_p [gitdir GITGUI_EDITMSG] + set msg_wt [open $msg_p w] + fconfigure $msg_wt -translation lf + if {[catch {set enc $repo_config(i18n.commitencoding)}]} { + set enc utf-8 + } + set use_enc [tcl_encoding $enc] + if {$use_enc ne {}} { + fconfigure $msg_wt -encoding $use_enc + } else { + puts stderr [mc "warning: Tcl does not support encoding '%s'." $enc] + fconfigure $msg_wt -encoding utf-8 + } + puts $msg_wt $msg + close $msg_wt - # On Cygwin [file executable] might lie so we need to ask - # the shell if the hook is executable. Yes that's annoying. + # -- Run the pre-commit hook. # - if {[is_Cygwin] && [file isfile $pchook]} { - set pchook [list sh -c [concat \ - "if test -x \"$pchook\";" \ - "then exec \"$pchook\" 2>&1;" \ - "fi"]] - } elseif {[file executable $pchook]} { - set pchook [list $pchook |& cat] - } else { - commit_writetree $curHEAD $msg + set fd_ph [githook_read pre-commit] + if {$fd_ph eq {}} { + commit_commitmsg $curHEAD $msg_p return } - ui_status {Calling pre-commit hook...} + ui_status [mc "Calling pre-commit hook..."] set pch_error {} - set fd_ph [open "| $pchook" r] fconfigure $fd_ph -blocking 0 -translation binary -eofchar {} fileevent $fd_ph readable \ - [list commit_prehook_wait $fd_ph $curHEAD $msg] + [list commit_prehook_wait $fd_ph $curHEAD $msg_p] } -proc commit_prehook_wait {fd_ph curHEAD msg} { +proc commit_prehook_wait {fd_ph curHEAD msg_p} { global pch_error append pch_error [read $fd_ph] fconfigure $fd_ph -blocking 1 if {[eof $fd_ph]} { if {[catch {close $fd_ph}]} { - ui_status {Commit declined by pre-commit hook.} + catch {file delete $msg_p} + ui_status [mc "Commit declined by pre-commit hook."] hook_failed_popup pre-commit $pch_error unlock_index } else { - commit_writetree $curHEAD $msg + commit_commitmsg $curHEAD $msg_p } set pch_error {} return @@ -238,14 +245,52 @@ proc commit_prehook_wait {fd_ph curHEAD msg} { fconfigure $fd_ph -blocking 0 } -proc commit_writetree {curHEAD msg} { - ui_status {Committing changes...} +proc commit_commitmsg {curHEAD msg_p} { + global pch_error + + # -- Run the commit-msg hook. + # + set fd_ph [githook_read commit-msg $msg_p] + if {$fd_ph eq {}} { + commit_writetree $curHEAD $msg_p + return + } + + ui_status [mc "Calling commit-msg hook..."] + set pch_error {} + fconfigure $fd_ph -blocking 0 -translation binary -eofchar {} + fileevent $fd_ph readable \ + [list commit_commitmsg_wait $fd_ph $curHEAD $msg_p] +} + +proc commit_commitmsg_wait {fd_ph curHEAD msg_p} { + global pch_error + + append pch_error [read $fd_ph] + fconfigure $fd_ph -blocking 1 + if {[eof $fd_ph]} { + if {[catch {close $fd_ph}]} { + catch {file delete $msg_p} + ui_status [mc "Commit declined by commit-msg hook."] + hook_failed_popup commit-msg $pch_error + unlock_index + } else { + commit_writetree $curHEAD $msg_p + } + set pch_error {} + return + } + fconfigure $fd_ph -blocking 0 +} + +proc commit_writetree {curHEAD msg_p} { + ui_status [mc "Committing changes..."] set fd_wt [git_read write-tree] fileevent $fd_wt readable \ - [list commit_committree $fd_wt $curHEAD $msg] + [list commit_committree $fd_wt $curHEAD $msg_p] } -proc commit_committree {fd_wt curHEAD msg} { +proc commit_committree {fd_wt curHEAD msg_p} { global HEAD PARENT MERGE_HEAD commit_type global current_branch global ui_comm selected_commit_type @@ -254,8 +299,9 @@ proc commit_committree {fd_wt curHEAD msg} { gets $fd_wt tree_id 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 } @@ -276,6 +322,7 @@ proc commit_committree {fd_wt curHEAD msg} { } if {$tree_id eq $old_tree} { + catch {file delete $msg_p} info_popup [mc "No changes to commit. No files were modified by this commit and it was not a merge commit. @@ -288,24 +335,6 @@ A rescan will be automatically started now. } } - # -- Build the message. - # - set msg_p [gitdir COMMIT_EDITMSG] - set msg_wt [open $msg_p w] - fconfigure $msg_wt -translation lf - if {[catch {set enc $repo_config(i18n.commitencoding)}]} { - set enc utf-8 - } - set use_enc [tcl_encoding $enc] - if {$use_enc ne {}} { - fconfigure $msg_wt -encoding $use_enc - } else { - puts stderr [mc "warning: Tcl does not support encoding '%s'." $enc] - fconfigure $msg_wt -encoding utf-8 - } - puts $msg_wt $msg - close $msg_wt - # -- Create the commit. # set cmd [list commit-tree $tree_id] @@ -314,8 +343,9 @@ A rescan will be automatically started now. } lappend cmd <$msg_p 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 } @@ -326,18 +356,16 @@ A rescan will be automatically started now. if {$commit_type ne {normal}} { append reflogm " ($commit_type)" } - set i [string first "\n" $msg] - if {$i >= 0} { - set subject [string range $msg 0 [expr {$i - 1}]] - } else { - set subject $msg - } + set msg_fd [open $msg_p r] + gets $msg_fd subject + close $msg_fd append reflogm {: } $subject if {[catch { git update-ref -m $reflogm HEAD $cmt_id $curHEAD } 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 } @@ -363,17 +391,13 @@ A rescan will be automatically started now. # -- Run the post-commit hook. # - set pchook [gitdir hooks post-commit] - if {[is_Cygwin] && [file isfile $pchook]} { - set pchook [list sh -c [concat \ - "if test -x \"$pchook\";" \ - "then exec \"$pchook\";" \ - "fi"]] - } elseif {![file executable $pchook]} { - set pchook {} - } - if {$pchook ne {}} { - catch {exec $pchook &} + set fd_ph [githook_read post-commit] + if {$fd_ph ne {}} { + upvar #0 pch_error$cmt_id pc_err + set pc_err {} + fconfigure $fd_ph -blocking 0 -translation binary -eofchar {} + fileevent $fd_ph readable \ + [list commit_postcommit_wait $fd_ph $cmt_id] } $ui_comm delete 0.0 end @@ -429,3 +453,18 @@ A rescan will be automatically started now. reshow_diff ui_status [mc "Created commit %s: %s" [string range $cmt_id 0 7] $subject] } + +proc commit_postcommit_wait {fd_ph cmt_id} { + upvar #0 pch_error$cmt_id pch_error + + append pch_error [read $fd_ph] + fconfigure $fd_ph -blocking 1 + if {[eof $fd_ph]} { + if {[catch {close $fd_ph}]} { + hook_failed_popup post-commit $pch_error 0 + } + unset pch_error + return + } + fconfigure $fd_ph -blocking 0 +} diff --git a/git-gui/lib/error.tcl b/git-gui/lib/error.tcl index 13565b7ab..08a24622c 100644 --- a/git-gui/lib/error.tcl +++ b/git-gui/lib/error.tcl @@ -1,6 +1,14 @@ # git-gui branch (create/delete) support # Copyright (C) 2006, 2007 Shawn Pearce +proc _error_parent {} { + set p [grab current .] + if {$p eq {}} { + return . + } + return $p +} + proc error_popup {msg} { set title [appname] if {[reponame] ne {}} { @@ -11,8 +19,8 @@ proc error_popup {msg} { -type ok \ -title [append "$title: " [mc "error"]] \ -message $msg] - if {[winfo ismapped .]} { - lappend cmd -parent . + if {[winfo ismapped [_error_parent]]} { + lappend cmd -parent [_error_parent] } eval $cmd } @@ -27,13 +35,13 @@ proc warn_popup {msg} { -type ok \ -title [append "$title: " [mc "warning"]] \ -message $msg] - if {[winfo ismapped .]} { - lappend cmd -parent . + if {[winfo ismapped [_error_parent]]} { + lappend cmd -parent [_error_parent] } eval $cmd } -proc info_popup {msg {parent .}} { +proc info_popup {msg} { set title [appname] if {[reponame] ne {}} { append title " ([reponame])" @@ -56,13 +64,13 @@ proc ask_popup {msg} { -type yesno \ -title $title \ -message $msg] - if {[winfo ismapped .]} { - lappend cmd -parent . + if {[winfo ismapped [_error_parent]]} { + lappend cmd -parent [_error_parent] } eval $cmd } -proc hook_failed_popup {hook msg} { +proc hook_failed_popup {hook msg {is_fatal 1}} { set w .hookfail toplevel $w @@ -77,14 +85,16 @@ proc hook_failed_popup {hook msg} { -width 80 -height 10 \ -font font_diff \ -yscrollcommand [list $w.m.sby set] - label $w.m.l2 \ - -text [mc "You must correct the above errors before committing."] \ - -anchor w \ - -justify left \ - -font font_uibold scrollbar $w.m.sby -command [list $w.m.t yview] pack $w.m.l1 -side top -fill x - pack $w.m.l2 -side bottom -fill x + if {$is_fatal} { + label $w.m.l2 \ + -text [mc "You must correct the above errors before committing."] \ + -anchor w \ + -justify left \ + -font font_uibold + pack $w.m.l2 -side bottom -fill x + } pack $w.m.sby -side right -fill y pack $w.m.t -side left -fill both -expand 1 pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10 @@ -99,6 +109,6 @@ proc hook_failed_popup {hook msg} { bind $w <Visibility> "grab $w; focus $w" bind $w <Key-Return> "destroy $w" - wm title $w [append "[appname] ([reponame]): " [mc "error"]] + wm title $w [strcat "[appname] ([reponame]): " [mc "error"]] tkwait window $w } diff --git a/git-gui/lib/index.tcl b/git-gui/lib/index.tcl index 30a244cc1..3c1fce747 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 63e14279c..cc26b0780 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 f812e5e89..ea80df009 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 000000000..7f018e400 --- /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 99913ec57..b3bf15fa1 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 2dfe07e06..d7c38f9c7 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 dfa48ae26..3f139da6c 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-gui/po/glossary/fr.po b/git-gui/po/glossary/fr.po index bb2feaf13..27c006abb 100644 --- a/git-gui/po/glossary/fr.po +++ b/git-gui/po/glossary/fr.po @@ -34,7 +34,7 @@ msgstr "branche" #. "" msgid "branch [verb]" -msgstr "créer une branche" +msgstr "créer une branche" #. "" msgid "checkout [noun]" @@ -58,7 +58,7 @@ msgstr "commiter" #. "" msgid "diff [noun]" -msgstr "différence" +msgstr "différence" #. "" msgid "diff [verb]" @@ -70,11 +70,11 @@ msgstr "fusion par avance rapide" #. "Fetching a branch means to get the branch's head from a remote repository, to find out which objects are missing from the local object database, and to get them, too." msgid "fetch" -msgstr "récupérer" +msgstr "récupérer" #. "A collection of files. The index is a stored version of your working tree." msgid "index (in git-gui: staging area)" -msgstr "pré-commit" +msgstr "pré-commit" #. "A successful merge results in the creation of a new commit representing the result of the merge." msgid "merge [noun]" @@ -106,15 +106,15 @@ msgstr "refaire" #. "An other repository ('remote'). One might have a set of remotes whose branches one tracks." msgid "remote" -msgstr "référentiel distant" +msgstr "référentiel distant" #. "A collection of refs (?) together with an object database containing all objects which are reachable from the refs... (oops, you've lost me here. Again, please an explanation for mere mortals?)" msgid "repository" -msgstr "référentiel" +msgstr "référentiel" #. "" msgid "reset" -msgstr "réinitialiser" +msgstr "réinitialiser" #. "" msgid "revert" @@ -122,7 +122,7 @@ msgstr "inverser" #. "A particular state of files and directories which was stored in the object database." msgid "revision" -msgstr "révision" +msgstr "révision" #. "" msgid "sign off" @@ -130,11 +130,11 @@ msgstr "signer" #. "" msgid "staging area" -msgstr "pré-commit" +msgstr "pré-commit" #. "" msgid "status" -msgstr "état" +msgstr "état" #. "A ref pointing to a tag or commit object" msgid "tag [noun]" @@ -150,15 +150,15 @@ msgstr "branche de suivi" #. "" msgid "undo" -msgstr "défaire" +msgstr "défaire" #. "" msgid "update" -msgstr "mise à jour" +msgstr "mise à jour" #. "" msgid "verify" -msgstr "vérifier" +msgstr "vérifier" #. "The tree of actual checked out files." msgid "working copy, working tree" diff --git a/git-gui/po/po2msg.sh b/git-gui/po/po2msg.sh index c63248e37..b7c4bf3fd 100644 --- a/git-gui/po/po2msg.sh +++ b/git-gui/po/po2msg.sh @@ -127,7 +127,26 @@ foreach file $files { } if {$show_statistics} { - puts [concat "$translated_count translated messages, " \ - "$fuzzy_count fuzzy ones, " \ - "$not_translated_count untranslated ones."] + set str "" + + append str "$translated_count translated message" + if {$translated_count != 1} { + append str s + } + + if {$fuzzy_count > 1} { + append str ", $fuzzy_count fuzzy translation" + if {$fuzzy_count != 1} { + append str s + } + } + if {$not_translated_count > 0} { + append str ", $not_translated_count untranslated message" + if {$not_translated_count != 1} { + append str s + } + } + + append str . + puts $str } diff --git a/git-instaweb.sh b/git-instaweb.sh index ad0723ccc..6f91c8f84 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 @@ -276,4 +271,9 @@ esac start_httpd url=http://127.0.0.1:$port -test -n "$browser" && "$browser" $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-pull.sh b/git-pull.sh index fa97b0f35..3ce32b5f2 100755 --- a/git-pull.sh +++ b/git-pull.sh @@ -106,6 +106,15 @@ error_on_no_merge_candidates () { exit 1 } +test true = "$rebase" && { + . git-parse-remote && + origin="$1" + test -z "$origin" && origin=$(get_default_remote) + reflist="$(get_remote_refs_for_fetch "$@" 2>/dev/null | + sed "s|refs/heads/\(.*\):|\1|")" && + oldremoteref="$(git rev-parse --verify \ + "refs/remotes/$origin/$reflist" 2>/dev/null)" +} orig_head=$(git rev-parse --verify HEAD 2>/dev/null) git-fetch --update-head-ok "$@" || exit 1 @@ -164,6 +173,8 @@ then fi merge_name=$(git fmt-merge-msg <"$GIT_DIR/FETCH_HEAD") || exit -test true = "$rebase" && exec git-rebase $merge_head +test true = "$rebase" && + exec git-rebase $strategy_args --onto $merge_head \ + ${oldremoteref:-$merge_head} exec git-merge $no_summary $no_commit $squash $no_ff $strategy_args \ "$merge_name" HEAD $merge_head diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 402ff3782..fb12b03b2 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -432,7 +432,7 @@ do shift ;; esac ;; - --merge) + -m|--merge) # we use merge anyway ;; -C*) diff --git a/git-relink.perl b/git-relink.perl index f6b4f6a2f..15fb93202 100755 --- a/git-relink.perl +++ b/git-relink.perl @@ -40,7 +40,7 @@ my $master_dir = pop @dirs; opendir(D,$master_dir . "objects/") or die "Failed to open $master_dir/objects/ : $!"; -my @hashdirs = grep !/^\.{1,2}$/, readdir(D); +my @hashdirs = grep { ($_ eq 'pack') || /^[0-9a-f]{2}$/ } readdir(D); foreach my $repo (@dirs) { $linked = 0; diff --git a/git-remote.perl b/git-remote.perl index d13e4c1fe..5cd69513c 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 6c72952fc..29b1105c4 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 && $_ ne "") { - $initial_reply_to =~ s/^\s*<?/</; - $initial_reply_to =~ s/>?\s*$/>/; +if (defined $initial_reply_to) { + $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,16 +525,18 @@ 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(); exit(0); } - @files = ($compose_filename . ".final"); + @files = ($compose_filename . ".final", @files); } # Variables we set as part of the loop over 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 aae14090b..a44b1c74a 100755 --- a/git-sh-setup.sh +++ b/git-sh-setup.sh @@ -119,7 +119,7 @@ get_author_ident_from_commit () { } ' encoding=$(git config i18n.commitencoding || echo UTF-8) - git show -s --pretty=raw --encoding="$encoding" "$1" | + git show -s --pretty=raw --encoding="$encoding" "$1" -- | LANG=C LC_ALL=C sed -ne "$pick_author_script" } @@ -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-submodule.sh b/git-submodule.sh index ad9fe628f..a6aaf40b0 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -9,11 +9,8 @@ OPTIONS_SPEC= . git-sh-setup require_work_tree -add= +command= branch= -init= -update= -status= quiet= cached= @@ -86,9 +83,9 @@ module_name() # # Clone a submodule # -# Prior to calling, modules_update checks that a possibly existing +# Prior to calling, cmd_update checks that a possibly existing # path is not a git repository. -# Likewise, module_add checks that path does not exist at all, +# Likewise, cmd_add checks that path does not exist at all, # since it is the location of a new submodule. # module_clone() @@ -121,8 +118,34 @@ module_clone() # # optional branch is stored in global branch variable # -module_add() +cmd_add() { + # parse $args after "submodule ... add". + while test $# -ne 0 + do + case "$1" in + -b | --branch) + case "$2" in '') usage ;; esac + branch=$2 + shift + ;; + -q|--quiet) + quiet=1 + ;; + --) + shift + break + ;; + -*) + usage + ;; + *) + break + ;; + esac + shift + done + repo=$1 path=$2 @@ -174,8 +197,29 @@ module_add() # # $@ = requested paths (default to all) # -modules_init() +cmd_init() { + # parse $args after "submodule ... init". + while test $# -ne 0 + do + case "$1" in + -q|--quiet) + quiet=1 + ;; + --) + shift + break + ;; + -*) + usage + ;; + *) + break + ;; + esac + shift + done + git ls-files --stage -- "$@" | grep -e '^160000 ' | while read mode sha1 stage path do @@ -207,8 +251,29 @@ modules_init() # # $@ = requested paths (default to all) # -modules_update() +cmd_update() { + # parse $args after "submodule ... update". + while test $# -ne 0 + do + case "$1" in + -q|--quiet) + quiet=1 + ;; + --) + shift + break + ;; + -*) + usage + ;; + *) + break + ;; + esac + shift + done + git ls-files --stage -- "$@" | grep -e '^160000 ' | while read mode sha1 stage path do @@ -266,8 +331,32 @@ set_name_rev () { # # $@ = requested paths (default to all) # -modules_list() +cmd_status() { + # parse $args after "submodule ... status". + while test $# -ne 0 + do + case "$1" in + -q|--quiet) + quiet=1 + ;; + --cached) + cached=1 + ;; + --) + shift + break + ;; + -*) + usage + ;; + *) + break + ;; + esac + shift + done + git ls-files --stage -- "$@" | grep -e '^160000 ' | while read mode sha1 stage path do @@ -293,20 +382,17 @@ modules_list() done } -while test $# != 0 +# This loop parses the command line arguments to find the +# subcommand name to dispatch. Parsing of the subcommand specific +# options are primarily done by the subcommand implementations. +# Subcommand specific options such as --branch and --cached are +# parsed here as well, for backward compatibility. + +while test $# != 0 && test -z "$command" do case "$1" in - add) - add=1 - ;; - init) - init=1 - ;; - update) - update=1 - ;; - status) - status=1 + add | init | update | status) + command=$1 ;; -q|--quiet) quiet=1 @@ -335,30 +421,19 @@ do shift done -case "$add,$branch" in -1,*) - ;; -,) - ;; -,*) +# No command word defaults to "status" +test -n "$command" || command=status + +# "-b branch" is accepted only by "add" +if test -n "$branch" && test "$command" != add +then usage - ;; -esac - -case "$add,$init,$update,$status,$cached" in -1,,,,) - module_add "$@" - ;; -,1,,,) - modules_init "$@" - ;; -,,1,,) - modules_update "$@" - ;; -,,,*,*) - modules_list "$@" - ;; -*) +fi + +# "--cached" is accepted only by "status" +if test -n "$cached" && test "$command" != status +then usage - ;; -esac +fi + +"cmd_$command" "$@" diff --git a/git-svn.perl b/git-svn.perl index 9f2b587b2..05fb3582d 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,10 +1412,9 @@ sub read_all_remotes { } sub init_vars { - if (defined $_repack) { - $_repack = 1000 if ($_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"; } } @@ -2098,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; @@ -2151,12 +2158,9 @@ sub do_git_commit { 0, $self->svm_uuid); } print " = $commit ($self->{ref_id})\n"; - if (defined $_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; } @@ -2228,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) { @@ -3985,6 +3994,7 @@ sub gs_fetch_loop_common { $max += $inc; $max = $head if ($max > $head); } + Git::SVN::gc(); } sub match_globs { @@ -4441,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 10b0a36a3..1023b9085 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 @@ -87,17 +87,6 @@ static int handle_options(const char*** argv, int* argc, int* envchanged) return handled; } -static const char *alias_command; -static char *alias_string; - -static int git_alias_config(const char *var, const char *value) -{ - if (!prefixcmp(var, "alias.") && !strcmp(var + 6, alias_command)) { - alias_string = xstrdup(value); - } - return 0; -} - static int split_cmdline(char *cmdline, const char ***argv) { int src, dst, count = 0, size = 16; @@ -157,11 +146,13 @@ static int handle_alias(int *argcp, const char ***argv) const char *subdir; int count, option_count; const char** new_argv; + const char *alias_command; + char *alias_string; subdir = setup_git_directory_gently(&nongit); alias_command = (*argv)[0]; - git_config(git_alias_config); + alias_string = alias_lookup(alias_command); if (alias_string) { if (alias_string[0] == '!') { if (*argcp > 1) { diff --git a/git.spec.in b/git.spec.in index 659f05881..97a26be29 100644 --- a/git.spec.in +++ b/git.spec.in @@ -3,88 +3,94 @@ Name: git Version: @@VERSION@@ Release: 1%{?dist} -Summary: Git core and tools +Summary: Core git tools License: GPL Group: Development/Tools URL: http://kernel.org/pub/software/scm/git/ Source: http://kernel.org/pub/software/scm/git/%{name}-%{version}.tar.gz -BuildRequires: zlib-devel >= 1.2, openssl-devel, curl-devel, expat-devel %{!?_without_docs:, xmlto, asciidoc > 6.0.3} +BuildRequires: zlib-devel >= 1.2, openssl-devel, curl-devel, expat-devel, gettext %{!?_without_docs:, xmlto, asciidoc > 6.0.3} BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) -Requires: git-core = %{version}-%{release} -Requires: git-svn = %{version}-%{release} -Requires: git-cvs = %{version}-%{release} -Requires: git-arch = %{version}-%{release} -Requires: git-email = %{version}-%{release} -Requires: gitk = %{version}-%{release} -Requires: git-gui = %{version}-%{release} Requires: perl-Git = %{version}-%{release} +Requires: zlib >= 1.2, rsync, curl, less, openssh-clients, expat +Provides: git-core = %{version}-%{release} +Obsoletes: git-core <= 1.5.4.2 +Obsoletes: git-p4 %description Git is a fast, scalable, distributed revision control system with an unusually rich command set that provides both high-level operations and full access to internals. -This is a dummy package which brings in all subpackages. +The git rpm installs the core tools with minimal dependencies. To +install all git packages, including tools for integrating with other +SCMs, install the git-all meta-package. -%package core -Summary: Core git tools +%package all +Summary: Meta-package to pull in all git tools Group: Development/Tools -Requires: zlib >= 1.2, rsync, curl, less, openssh-clients, expat -Obsoletes: git-p4 -%description core +Requires: git = %{version}-%{release} +Requires: git-svn = %{version}-%{release} +Requires: git-cvs = %{version}-%{release} +Requires: git-arch = %{version}-%{release} +Requires: git-email = %{version}-%{release} +Requires: gitk = %{version}-%{release} +Requires: git-gui = %{version}-%{release} +Obsoletes: git <= 1.5.4.2 + +%description all Git is a fast, scalable, distributed revision control system with an unusually rich command set that provides both high-level operations and full access to internals. -These are the core tools with minimal dependencies. +This is a dummy package which brings in all subpackages. %package svn Summary: Git tools for importing Subversion repositories Group: Development/Tools -Requires: git-core = %{version}-%{release}, subversion +Requires: git = %{version}-%{release}, subversion %description svn Git tools for importing Subversion repositories. %package cvs Summary: Git tools for importing CVS repositories Group: Development/Tools -Requires: git-core = %{version}-%{release}, cvs, cvsps +Requires: git = %{version}-%{release}, cvs, cvsps %description cvs Git tools for importing CVS repositories. %package arch Summary: Git tools for importing Arch repositories Group: Development/Tools -Requires: git-core = %{version}-%{release}, tla +Requires: git = %{version}-%{release}, tla %description arch Git tools for importing Arch repositories. %package email Summary: Git tools for sending email Group: Development/Tools -Requires: git-core = %{version}-%{release} +Requires: git = %{version}-%{release} %description email Git tools for sending email. %package gui Summary: Git GUI tool Group: Development/Tools -Requires: git-core = %{version}-%{release}, tk >= 8.4 +Requires: git = %{version}-%{release}, tk >= 8.4 %description gui Git GUI tool %package -n gitk Summary: Git revision tree visualiser ('gitk') Group: Development/Tools -Requires: git-core = %{version}-%{release}, tk >= 8.4 +Requires: git = %{version}-%{release}, tk >= 8.4 %description -n gitk Git revision tree visualiser ('gitk') %package -n perl-Git Summary: Perl interface to Git Group: Development/Libraries -Requires: git-core = %{version}-%{release} +Requires: git = %{version}-%{release} Requires: perl(:MODULE_COMPAT_%(eval "`%{__perl} -V:version`"; echo $version)) BuildRequires: perl(Error) @@ -121,8 +127,12 @@ rm -rf $RPM_BUILD_ROOT%{_mandir} %clean rm -rf $RPM_BUILD_ROOT -%files -# These are no files in the root package +%files -f bin-man-doc-files +%defattr(-,root,root) +%{_datadir}/git-core/ +%doc README COPYING Documentation/*.txt +%{!?_without_docs: %doc Documentation/*.html Documentation/howto} +%{!?_without_docs: %doc Documentation/technical} %files svn %defattr(-,root,root) @@ -173,14 +183,16 @@ rm -rf $RPM_BUILD_ROOT %files -n perl-Git -f perl-files %defattr(-,root,root) -%files core -f bin-man-doc-files -%defattr(-,root,root) -%{_datadir}/git-core/ -%doc README COPYING Documentation/*.txt -%{!?_without_docs: %doc Documentation/*.html Documentation/howto} -%{!?_without_docs: %doc Documentation/technical} +%files all +# No files for you! %changelog +* Fri Feb 15 2008 Kristian Høgsberg <krh@redhat.com> +- Rename git-core to just git and rename meta package from git to git-all. + +* Sun Feb 03 2008 James Bowes <jbowes@dangerouslyinc.com> +- Add a BuildRequires for gettext + * Fri Jan 11 2008 Junio C Hamano <gitster@pobox.com> - Include gitk message files diff --git a/gitk-git/gitk b/gitk-git/gitk index 5560e4dc5..f1f21e97b 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 b28f59f57..216307104 100644 --- a/gitweb/README +++ b/gitweb/README @@ -129,17 +129,30 @@ descriptions. Gitweb config file variables ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -You can set, among others, the following variables in gitweb config files: +You can set, among others, the following variables in gitweb config files +(with the exception of $projectroot and $projects_list this list does +not include variables usually directly set during build): * $GIT Cure git executable to use. By default set to "$GIT_BINDIR/git", which in turn is by default set to "$(bindir)/git". If you use git from binary package, set this to "/usr/bin/git". This can just be "git" if your webserver has a sensible PATH. If you have multiple git versions - installed it is / can be used to choose which one to use. + installed it can be used to choose which one to use. * $version Gitweb version, set automatically when creating gitweb.cgi from gitweb.perl. You might want to modify it if you are running modified gitweb. + * $projectroot + Absolute filesystem path which will be prepended to project path; + the path to repository is $projectroot/$project. Set to + $GITWEB_PROJECTROOT during installation. This variable have to be + set correctly for gitweb to find repositories. + * $projects_list + Source of projects list, either directory to scan, or text file + with list of repositories (in the "<URI-encoded repository path> SPC + <URI-encoded repository owner>" format). Set to $GITWEB_LIST + during installation. If empty, $projectroot is used to scan for + repositories. * $my_url, $my_uri URL and absolute URL of gitweb script; you might need to set those variables if you are using 'pathinfo' feature: see also below. @@ -220,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 6256641ac..fc95e2ca8 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; @@ -837,37 +848,78 @@ sub project_in_list { ## ---------------------------------------------------------------------- ## HTML aware string manipulation +# Try to chop given string on a word boundary between position +# $len and $len+$add_len. If there is no word boundary there, +# chop at $len+$add_len. Do not chop if chopped part plus ellipsis +# (marking chopped part) would be longer than given string. sub chop_str { my $str = shift; my $len = shift; my $add_len = shift || 10; + my $where = shift || 'right'; # 'left' | 'center' | 'right' # allow only $len chars, but don't cut a word if it would fit in $add_len # if it doesn't fit, cut it if it's still longer than the dots we would add - $str =~ m/^(.{0,$len}[^ \/\-_:\.@]{0,$add_len})(.*)/; - my $body = $1; - my $tail = $2; - if (length($tail) > 4) { - $tail = " ..."; - $body =~ s/&[^;]*$//; # remove chopped character entities + # remove chopped character entities entirely + + # when chopping in the middle, distribute $len into left and right part + # return early if chopping wouldn't make string shorter + if ($where eq 'center') { + return $str if ($len + 5 >= length($str)); # filler is length 5 + $len = int($len/2); + } else { + return $str if ($len + 4 >= length($str)); # filler is length 4 + } + + # regexps: ending and beginning with word part up to $add_len + my $endre = qr/.{$len}\w{0,$add_len}/; + my $begre = qr/\w{0,$add_len}.{$len}/; + + if ($where eq 'left') { + $str =~ m/^(.*?)($begre)$/; + my ($lead, $body) = ($1, $2); + if (length($lead) > 4) { + $body =~ s/^[^;]*;// if ($lead =~ m/&[^;]*$/); + $lead = " ..."; + } + return "$lead$body"; + + } elsif ($where eq 'center') { + $str =~ m/^($endre)(.*)$/; + my ($left, $str) = ($1, $2); + $str =~ m/^(.*?)($begre)$/; + my ($mid, $right) = ($1, $2); + if (length($mid) > 5) { + $left =~ s/&[^;]*$//; + $right =~ s/^[^;]*;// if ($mid =~ m/&[^;]*$/); + $mid = " ... "; + } + return "$left$mid$right"; + + } else { + $str =~ m/^($endre)(.*)$/; + my $body = $1; + my $tail = $2; + if (length($tail) > 4) { + $body =~ s/&[^;]*$//; + $tail = "... "; + } + return "$body$tail"; } - return "$body$tail"; } # takes the same arguments as chop_str, but also wraps a <span> around the # result with a title attribute if it does get chopped. Additionally, the # string is HTML-escaped. sub chop_and_escape_str { - my $str = shift; - my $len = shift; - my $add_len = shift || 10; + my ($str) = @_; - my $chopped = chop_str($str, $len, $add_len); + my $chopped = chop_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)); } } @@ -1606,7 +1658,7 @@ sub git_get_project_description { my $path = shift; $git_dir = "$projectroot/$path"; - open my $fd, "$projectroot/$path/description" + open my $fd, "$git_dir/description" or return git_get_project_config('description'); my $descr = <$fd>; close $fd; @@ -1620,7 +1672,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 +1811,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 +1820,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 +3825,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, 'center'); + my $contextlen = int((80 - length($match))/2); + $contextlen = 30 if ($contextlen > 30); + $lead = chop_str($lead, $contextlen, 10, 'left'); + $trail = chop_str($trail, $contextlen, 10, 'right'); + + $lead = esc_html($lead); + $match = esc_html($match); + $trail = esc_html($trail); + + print "$lead<span class=\"match\">$match</span>$trail<br />"; } } print "</td>\n" . @@ -5048,16 +5110,15 @@ sub git_commitdiff { -expires => $expires, -content_disposition => 'inline; filename="' . "$filename" . '"'); my %ad = parse_date($co{'author_epoch'}, $co{'author_tz'}); - print <<TEXT; -From: $co{'author'} -Date: $ad{'rfc2822'} ($ad{'tz_local'}) -Subject: $co{'title'} -TEXT + print "From: " . to_utf8($co{'author'}) . "\n"; + print "Date: $ad{'rfc2822'} ($ad{'tz_local'})\n"; + print "Subject: " . to_utf8($co{'title'}) . "\n"; + print "X-Git-Tag: $tagname\n" if $tagname; print "X-Git-Url: " . $cgi->self_url() . "\n\n"; foreach my $line (@{$co{'comment'}}) { - print "$line\n"; + print to_utf8($line) . "\n"; } print "---\n\n"; } @@ -5566,7 +5627,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" . diff --git a/hash-object.c b/hash-object.c index 0a58f3f12..61e7160b3 100644 --- a/hash-object.c +++ b/hash-object.c @@ -41,6 +41,7 @@ int main(int argc, char **argv) const char *prefix = NULL; int prefix_length = -1; int no_more_flags = 0; + int hashstdin = 0; git_config(git_default_config); @@ -65,13 +66,20 @@ int main(int argc, char **argv) else if (!strcmp(argv[i], "--help")) usage(hash_object_usage); else if (!strcmp(argv[i], "--stdin")) { - hash_stdin(type, write_object); + if (hashstdin) + die("Multiple --stdin arguments are not supported"); + hashstdin = 1; } else usage(hash_object_usage); } else { const char *arg = argv[i]; + + if (hashstdin) { + hash_stdin(type, write_object); + hashstdin = 0; + } if (0 <= prefix_length) arg = prefix_filename(prefix, prefix_length, arg); @@ -79,5 +87,7 @@ int main(int argc, char **argv) no_more_flags = 1; } } + if (hashstdin) + hash_stdin(type, write_object); return 0; } @@ -70,7 +70,7 @@ void *lookup_hash(unsigned int hash, struct hash_table *table) { if (!table->array) return NULL; - return &lookup_hash_entry(hash, table)->ptr; + return lookup_hash_entry(hash, table)->ptr; } void **insert_hash(unsigned int hash, void *ptr, struct hash_table *table) @@ -7,40 +7,47 @@ #include "builtin.h" #include "exec_cmd.h" #include "common-cmds.h" - -static const char *help_default_format; - -static enum help_format { - man_format, - info_format, - web_format, -} help_format = man_format; - -static void parse_help_format(const char *format) +#include "parse-options.h" + +enum help_format { + HELP_FORMAT_MAN, + HELP_FORMAT_INFO, + HELP_FORMAT_WEB, +}; + +static int show_all = 0; +static enum help_format help_format = HELP_FORMAT_MAN; +static struct option builtin_help_options[] = { + OPT_BOOLEAN('a', "all", &show_all, "print all available commands"), + OPT_SET_INT('m', "man", &help_format, "show man page", HELP_FORMAT_MAN), + OPT_SET_INT('w', "web", &help_format, "show manual in web browser", + HELP_FORMAT_WEB), + OPT_SET_INT('i', "info", &help_format, "show info page", + HELP_FORMAT_INFO), +}; + +static const char * const builtin_help_usage[] = { + "git-help [--all] [--man|--web|--info] [command]", + NULL +}; + +static enum help_format parse_help_format(const char *format) { - if (!format) { - help_format = man_format; - return; - } - if (!strcmp(format, "man")) { - help_format = man_format; - return; - } - if (!strcmp(format, "info")) { - help_format = info_format; - return; - } - if (!strcmp(format, "web") || !strcmp(format, "html")) { - help_format = web_format; - return; - } + if (!strcmp(format, "man")) + return HELP_FORMAT_MAN; + if (!strcmp(format, "info")) + return HELP_FORMAT_INFO; + if (!strcmp(format, "web") || !strcmp(format, "html")) + return HELP_FORMAT_WEB; die("unrecognized help format '%s'", format); } static int git_help_config(const char *var, const char *value) { if (!strcmp(var, "help.format")) { - help_default_format = xstrdup(value); + if (!value) + return config_error_nonbool(var); + help_format = parse_help_format(value); return 0; } return git_default_config(var, value); @@ -203,7 +210,7 @@ static unsigned int list_commands_in_dir(struct cmdnames *cmds, return longest; } -static void list_commands(void) +static unsigned int load_command_list(void) { unsigned int longest = 0; unsigned int len; @@ -243,6 +250,14 @@ static void list_commands(void) uniq(&other_cmds); exclude_cmds(&other_cmds, &main_cmds); + return longest; +} + +static void list_commands(void) +{ + unsigned int longest = load_command_list(); + const char *exec_path = git_exec_path(); + if (main_cmds.cnt) { printf("available git commands in '%s'\n", exec_path); printf("----------------------------"); @@ -277,6 +292,22 @@ void list_common_cmds_help(void) } } +static int is_in_cmdlist(struct cmdnames *c, const char *s) +{ + int i; + for (i = 0; i < c->cnt; i++) + if (!strcmp(s, c->names[i]->name)) + return 1; + return 0; +} + +static int is_git_command(const char *s) +{ + load_command_list(); + return is_in_cmdlist(&main_cmds, s) || + is_in_cmdlist(&other_cmds, s); +} + static const char *cmd_to_page(const char *git_cmd) { if (!git_cmd) @@ -328,10 +359,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) @@ -348,50 +395,43 @@ int cmd_version(int argc, const char **argv, const char *prefix) int cmd_help(int argc, const char **argv, const char *prefix) { - const char *help_cmd = argv[1]; + int nongit; + const char *alias; - if (argc < 2) { - printf("usage: %s\n\n", git_usage_string); - list_common_cmds_help(); - exit(0); - } + setup_git_directory_gently(&nongit); + git_config(git_help_config); - if (!strcmp(help_cmd, "--all") || !strcmp(help_cmd, "-a")) { + argc = parse_options(argc, argv, builtin_help_options, + builtin_help_usage, 0); + + if (show_all) { printf("usage: %s\n\n", git_usage_string); list_commands(); + return 0; } - else if (!strcmp(help_cmd, "--web") || !strcmp(help_cmd, "-w")) { - show_html_page(argc > 2 ? argv[2] : NULL); - } - - else if (!strcmp(help_cmd, "--info") || !strcmp(help_cmd, "-i")) { - show_info_page(argc > 2 ? argv[2] : NULL); + if (!argv[0]) { + printf("usage: %s\n\n", git_usage_string); + list_common_cmds_help(); + return 0; } - else if (!strcmp(help_cmd, "--man") || !strcmp(help_cmd, "-m")) { - show_man_page(argc > 2 ? argv[2] : NULL); + alias = alias_lookup(argv[0]); + if (alias && !is_git_command(argv[0])) { + printf("`git %s' is aliased to `%s'\n", argv[0], alias); + return 0; } - else { - int nongit; - - setup_git_directory_gently(&nongit); - git_config(git_help_config); - if (help_default_format) - parse_help_format(help_default_format); - - switch (help_format) { - case man_format: - show_man_page(help_cmd); - break; - case info_format: - show_info_page(help_cmd); - break; - case web_format: - show_html_page(help_cmd); - break; - } + switch (help_format) { + case HELP_FORMAT_MAN: + show_man_page(argv[0]); + break; + case HELP_FORMAT_INFO: + show_info_page(argv[0]); + break; + case HELP_FORMAT_WEB: + show_html_page(argv[0]); + break; } return 0; diff --git a/http-push.c b/http-push.c index b2b410df9..0beb7406c 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); @@ -101,16 +101,18 @@ static int http_options(const char *var, const char *value) if (!strcmp("http.sslcert", var)) { if (ssl_cert == NULL) { - ssl_cert = xmalloc(strlen(value)+1); - strcpy(ssl_cert, value); + if (!value) + return config_error_nonbool(var); + ssl_cert = xstrdup(value); } return 0; } #if LIBCURL_VERSION_NUM >= 0x070902 if (!strcmp("http.sslkey", var)) { if (ssl_key == NULL) { - ssl_key = xmalloc(strlen(value)+1); - strcpy(ssl_key, value); + if (!value) + return config_error_nonbool(var); + ssl_key = xstrdup(value); } return 0; } @@ -118,16 +120,18 @@ static int http_options(const char *var, const char *value) #if LIBCURL_VERSION_NUM >= 0x070908 if (!strcmp("http.sslcapath", var)) { if (ssl_capath == NULL) { - ssl_capath = xmalloc(strlen(value)+1); - strcpy(ssl_capath, value); + if (!value) + return config_error_nonbool(var); + ssl_capath = xstrdup(value); } return 0; } #endif if (!strcmp("http.sslcainfo", var)) { if (ssl_cainfo == NULL) { - ssl_cainfo = xmalloc(strlen(value)+1); - strcpy(ssl_cainfo, value); + if (!value) + return config_error_nonbool(var); + ssl_cainfo = xstrdup(value); } return 0; } @@ -157,8 +161,9 @@ static int http_options(const char *var, const char *value) } if (!strcmp("http.proxy", var)) { if (curl_http_proxy == NULL) { - curl_http_proxy = xmalloc(strlen(value)+1); - strcpy(curl_http_proxy, value); + if (!value) + return config_error_nonbool(var); + curl_http_proxy = xstrdup(value); } return 0; } @@ -8,6 +8,14 @@ #include "strbuf.h" +/* + * We detect based on the cURL version if multi-transfer is + * usable in this implementation and define this symbol accordingly. + * This is not something Makefile should set nor users should pass + * via CFLAGS. + */ +#undef USE_CURL_MULTI + #if LIBCURL_VERSION_NUM >= 0x071000 #define USE_CURL_MULTI #define DEFAULT_MAX_REQUESTS 5 diff --git a/imap-send.c b/imap-send.c index a429a76a6..9025d9aa3 100644 --- a/imap-send.c +++ b/imap-send.c @@ -1254,6 +1254,10 @@ git_imap_config(const char *key, const char *val) if (strncmp( key, imap_key, sizeof imap_key - 1 )) return 0; + + if (!val) + return config_error_nonbool(key); + key += sizeof imap_key - 1; if (!strcmp( "folder", key )) { diff --git a/list-objects.c b/list-objects.c index 4ef58e7ec..c8b8375e4 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 1f3fcf16a..608f697cf 100644 --- a/log-tree.c +++ b/log-tree.c @@ -137,6 +137,72 @@ static int has_non_ascii(const char *s) return 0; } +void log_write_email_headers(struct rev_info *opt, const char *name, + const char **subject_p, const char **extra_headers_p) +{ + const char *subject = NULL; + const char *extra_headers = opt->extra_headers; + if (opt->total > 0) { + static char buffer[64]; + snprintf(buffer, sizeof(buffer), + "Subject: [%s %0*d/%d] ", + opt->subject_prefix, + digits_in_number(opt->total), + opt->nr, opt->total); + subject = buffer; + } else if (opt->total == 0 && opt->subject_prefix && *opt->subject_prefix) { + static char buffer[256]; + snprintf(buffer, sizeof(buffer), + "Subject: [%s] ", + opt->subject_prefix); + subject = buffer; + } else { + subject = "Subject: "; + } + + printf("From %s Mon Sep 17 00:00:00 2001\n", name); + if (opt->message_id) + printf("Message-Id: <%s>\n", opt->message_id); + if (opt->ref_message_id) + printf("In-Reply-To: <%s>\nReferences: <%s>\n", + opt->ref_message_id, opt->ref_message_id); + if (opt->mime_boundary) { + static char subject_buffer[1024]; + static char buffer[1024]; + snprintf(subject_buffer, sizeof(subject_buffer) - 1, + "%s" + "MIME-Version: 1.0\n" + "Content-Type: multipart/mixed;" + " boundary=\"%s%s\"\n" + "\n" + "This is a multi-part message in MIME " + "format.\n" + "--%s%s\n" + "Content-Type: text/plain; " + "charset=UTF-8; format=fixed\n" + "Content-Transfer-Encoding: 8bit\n\n", + extra_headers ? extra_headers : "", + mime_boundary_leader, opt->mime_boundary, + mime_boundary_leader, opt->mime_boundary); + extra_headers = subject_buffer; + + snprintf(buffer, sizeof(buffer) - 1, + "--%s%s\n" + "Content-Type: text/x-patch;" + " name=\"%s.diff\"\n" + "Content-Transfer-Encoding: 8bit\n" + "Content-Disposition: %s;" + " filename=\"%s.diff\"\n\n", + mime_boundary_leader, opt->mime_boundary, + name, + opt->no_inline ? "attachment" : "inline", + name); + opt->diffopt.stat_sep = buffer; + } + *subject_p = subject; + *extra_headers_p = extra_headers; +} + void show_log(struct rev_info *opt, const char *sep) { struct strbuf msgbuf; @@ -149,10 +215,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('>'); @@ -186,70 +254,16 @@ void show_log(struct rev_info *opt, const char *sep) */ if (opt->commit_format == CMIT_FMT_EMAIL) { - char *sha1 = sha1_to_hex(commit->object.sha1); - if (opt->total > 0) { - static char buffer[64]; - snprintf(buffer, sizeof(buffer), - "Subject: [%s %0*d/%d] ", - opt->subject_prefix, - digits_in_number(opt->total), - opt->nr, opt->total); - subject = buffer; - } else if (opt->total == 0 && opt->subject_prefix && *opt->subject_prefix) { - static char buffer[256]; - snprintf(buffer, sizeof(buffer), - "Subject: [%s] ", - opt->subject_prefix); - subject = buffer; - } else { - subject = "Subject: "; - } - - printf("From %s Mon Sep 17 00:00:00 2001\n", sha1); - if (opt->message_id) - printf("Message-Id: <%s>\n", opt->message_id); - if (opt->ref_message_id) - printf("In-Reply-To: <%s>\nReferences: <%s>\n", - opt->ref_message_id, opt->ref_message_id); - if (opt->mime_boundary) { - static char subject_buffer[1024]; - static char buffer[1024]; - snprintf(subject_buffer, sizeof(subject_buffer) - 1, - "%s" - "MIME-Version: 1.0\n" - "Content-Type: multipart/mixed;" - " boundary=\"%s%s\"\n" - "\n" - "This is a multi-part message in MIME " - "format.\n" - "--%s%s\n" - "Content-Type: text/plain; " - "charset=UTF-8; format=fixed\n" - "Content-Transfer-Encoding: 8bit\n\n", - extra_headers ? extra_headers : "", - mime_boundary_leader, opt->mime_boundary, - mime_boundary_leader, opt->mime_boundary); - extra_headers = subject_buffer; - - snprintf(buffer, sizeof(buffer) - 1, - "--%s%s\n" - "Content-Type: text/x-patch;" - " name=\"%s.diff\"\n" - "Content-Transfer-Encoding: 8bit\n" - "Content-Disposition: %s;" - " filename=\"%s.diff\"\n\n", - mime_boundary_leader, opt->mime_boundary, - sha1, - opt->no_inline ? "attachment" : "inline", - sha1); - opt->diffopt.stat_sep = buffer; - } + log_write_email_headers(opt, sha1_to_hex(commit->object.sha1), + &subject, &extra_headers); } else if (opt->commit_format != CMIT_FMT_USERFORMAT) { fputs(diff_get_color_opt(&opt->diffopt, DIFF_COMMIT), stdout); if (opt->commit_format != CMIT_FMT_ONELINE) 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 +292,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/log-tree.h b/log-tree.h index b33f7cd7a..0cc9344ea 100644 --- a/log-tree.h +++ b/log-tree.h @@ -13,5 +13,7 @@ int log_tree_commit(struct rev_info *, struct commit *); int log_tree_opt_parse(struct rev_info *, const char **, int); void show_log(struct rev_info *opt, const char *sep); void show_decorations(struct commit *commit); +void log_write_email_headers(struct rev_info *opt, const char *name, + const char **subject_p, const char **extra_headers_p); #endif diff --git a/merge-index.c b/merge-index.c index bbb700b54..7491c56ad 100644 --- a/merge-index.c +++ b/merge-index.c @@ -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(); @@ -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; diff --git a/parse-options.c b/parse-options.c index 7a08a0c64..d9562ba50 100644 --- a/parse-options.c +++ b/parse-options.c @@ -216,6 +216,26 @@ is_abbreviated: return error("unknown option `%s'", arg); } +void check_typos(const char *arg, const struct option *options) +{ + if (strlen(arg) < 3) + return; + + if (!prefixcmp(arg, "no-")) { + error ("did you mean `--%s` (with two dashes ?)", arg); + exit(129); + } + + for (; options->type != OPTION_END; options++) { + if (!options->long_name) + continue; + if (!prefixcmp(options->long_name, arg)) { + error ("did you mean `--%s` (with two dashes ?)", arg); + exit(129); + } + } +} + static NORETURN void usage_with_options_internal(const char * const *, const struct option *, int); @@ -235,12 +255,18 @@ int parse_options(int argc, const char **argv, const struct option *options, if (arg[1] != '-') { args.opt = arg + 1; - do { + if (*args.opt == 'h') + usage_with_options(usagestr, options); + if (parse_short_opt(&args, options) < 0) + usage_with_options(usagestr, options); + if (args.opt) + check_typos(arg + 1, options); + while (args.opt) { if (*args.opt == 'h') usage_with_options(usagestr, options); if (parse_short_opt(&args, options) < 0) usage_with_options(usagestr, options); - } while (args.opt); + } continue; } @@ -110,9 +110,9 @@ needquote: strbuf_addstr(sb, "?="); } -static void add_user_info(const char *what, enum cmit_fmt fmt, struct strbuf *sb, - const char *line, enum date_mode dmode, - const char *encoding) +void pp_user_info(const char *what, enum cmit_fmt fmt, struct strbuf *sb, + const char *line, enum date_mode dmode, + const char *encoding) { char *date; int namelen; @@ -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, @@ -643,23 +621,23 @@ static void pp_header(enum cmit_fmt fmt, */ if (!memcmp(line, "author ", 7)) { strbuf_grow(sb, linelen + 80); - add_user_info("Author", fmt, sb, line + 7, dmode, encoding); + pp_user_info("Author", fmt, sb, line + 7, dmode, encoding); } if (!memcmp(line, "committer ", 10) && (fmt == CMIT_FMT_FULL || fmt == CMIT_FMT_FULLER)) { strbuf_grow(sb, linelen + 80); - add_user_info("Commit", fmt, sb, line + 10, dmode, encoding); + pp_user_info("Commit", fmt, sb, line + 10, dmode, encoding); } } } -static void pp_title_line(enum cmit_fmt fmt, - const char **msg_p, - struct strbuf *sb, - const char *subject, - const char *after_subject, - const char *encoding, - int plain_non_ascii) +void pp_title_line(enum cmit_fmt fmt, + const char **msg_p, + struct strbuf *sb, + const char *subject, + const char *after_subject, + const char *encoding, + int plain_non_ascii) { struct strbuf title; @@ -708,10 +686,10 @@ static void pp_title_line(enum cmit_fmt fmt, strbuf_release(&title); } -static void pp_remainder(enum cmit_fmt fmt, - const char **msg_p, - struct strbuf *sb, - int indent) +void pp_remainder(enum cmit_fmt fmt, + const char **msg_p, + struct strbuf *sb, + int indent) { int first = 1; for (;;) { diff --git a/reachable.c b/reachable.c index 00f289f2f..3b1c18ff9 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) @@ -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 22d7b4624..657f0c589 100644 --- a/read-cache.c +++ b/read-cache.c @@ -37,8 +37,13 @@ static unsigned int hash_name(const char *name, int namelen) 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)); + unsigned int hash; + if (ce->ce_flags & CE_HASHED) + return; + ce->ce_flags |= CE_HASHED; + ce->next = NULL; + hash = hash_name(ce->name, ce_namelen(ce)); pos = insert_hash(hash, ce, &istate->name_hash); if (pos) { ce->next = *pos; @@ -59,33 +64,18 @@ static void lazy_init_name_hash(struct index_state *istate) static void set_index_entry(struct index_state *istate, int nr, struct cache_entry *ce) { + ce->ce_flags &= ~CE_UNHASHED; 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); - } + remove_index_entry(old); + set_index_entry(istate, nr, ce); istate->cache_changed = 1; } @@ -413,7 +403,7 @@ int remove_index_entry_at(struct index_state *istate, int pos) { struct cache_entry *ce = istate->cache[pos]; - remove_hash_entry(istate, ce); + remove_index_entry(ce); istate->cache_changed = 1; istate->cache_nr--; if (pos >= istate->cache_nr) diff --git a/receive-pack.c b/receive-pack.c index 326749583..a971433db 100644 --- a/receive-pack.c +++ b/receive-pack.c @@ -132,6 +132,7 @@ static int run_hook(const char *hook_name) break; } } + close(proc.in); return hook_status(finish_command(&proc), hook_name); } @@ -414,6 +415,7 @@ static const char *unpack(void) if (start_command(&ip)) return "index-pack fork failed"; pack_lockfile = index_pack_lockfile(ip.out); + close(ip.out); status = finish_command(&ip); if (!status) { reprepare_packed_git(); @@ -157,6 +157,7 @@ static struct cached_refs { struct ref_list *loose; struct ref_list *packed; } cached_refs; +static struct ref_list *current_ref; static void free_ref_list(struct ref_list *list) { @@ -476,6 +477,7 @@ static int do_one_ref(const char *base, each_ref_fn fn, int trim, error("%s does not point to a valid object!", entry->name); return 0; } + current_ref = entry; return fn(entry->name + trim, entry->sha1, entry->flag, cb_data); } @@ -485,6 +487,16 @@ int peel_ref(const char *ref, unsigned char *sha1) unsigned char base[20]; struct object *o; + if (current_ref && (current_ref->name == ref + || !strcmp(current_ref->name, ref))) { + if (current_ref->flag & REF_KNOWS_PEELED) { + hashcpy(sha1, current_ref->peeled); + return 0; + } + hashcpy(base, current_ref->sha1); + goto fallback; + } + if (!resolve_ref(ref, base, 1, &flag)) return -1; @@ -504,9 +516,9 @@ int peel_ref(const char *ref, unsigned char *sha1) } } - /* fallback - callers should not call this for unpacked refs */ +fallback: o = parse_object(base); - if (o->type == OBJ_TAG) { + if (o && o->type == OBJ_TAG) { o = deref_tag(o, ref, 0); if (o) { hashcpy(sha1, o->sha1); @@ -519,7 +531,7 @@ int peel_ref(const char *ref, unsigned char *sha1) static int do_for_each_ref(const char *base, each_ref_fn fn, int trim, void *cb_data) { - int retval; + int retval = 0; struct ref_list *packed = get_packed_refs(); struct ref_list *loose = get_loose_refs(); @@ -539,15 +551,18 @@ static int do_for_each_ref(const char *base, each_ref_fn fn, int trim, } retval = do_one_ref(base, fn, trim, cb_data, entry); if (retval) - return retval; + goto end_each; } for (packed = packed ? packed : loose; packed; packed = packed->next) { retval = do_one_ref(base, fn, trim, cb_data, packed); if (retval) - return retval; + goto end_each; } - return 0; + +end_each: + current_ref = NULL; + return retval; } int head_ref(each_ref_fn fn, void *cb_data) @@ -2,123 +2,184 @@ #include "remote.h" #include "refs.h" +struct counted_string { + size_t len; + const char *s; +}; +struct rewrite { + const char *base; + size_t baselen; + struct counted_string *instead_of; + int instead_of_nr; + int instead_of_alloc; +}; + static struct remote **remotes; -static int allocated_remotes; +static int remotes_alloc; +static int remotes_nr; static struct branch **branches; -static int allocated_branches; +static int branches_alloc; +static int branches_nr; static struct branch *current_branch; static const char *default_remote_name; +static struct rewrite **rewrite; +static int rewrite_alloc; +static int rewrite_nr; + #define BUF_SIZE (2048) static char buffer[BUF_SIZE]; +static const char *alias_url(const char *url) +{ + int i, j; + char *ret; + struct counted_string *longest; + int longest_i; + + longest = NULL; + longest_i = -1; + for (i = 0; i < rewrite_nr; i++) { + if (!rewrite[i]) + continue; + for (j = 0; j < rewrite[i]->instead_of_nr; j++) { + if (!prefixcmp(url, rewrite[i]->instead_of[j].s) && + (!longest || + longest->len < rewrite[i]->instead_of[j].len)) { + longest = &(rewrite[i]->instead_of[j]); + longest_i = i; + } + } + } + if (!longest) + return url; + + ret = malloc(rewrite[longest_i]->baselen + + (strlen(url) - longest->len) + 1); + strcpy(ret, rewrite[longest_i]->base); + strcpy(ret + rewrite[longest_i]->baselen, url + longest->len); + return ret; +} + static void add_push_refspec(struct remote *remote, const char *ref) { - int nr = remote->push_refspec_nr + 1; - remote->push_refspec = - xrealloc(remote->push_refspec, nr * sizeof(char *)); - remote->push_refspec[nr-1] = ref; - remote->push_refspec_nr = nr; + ALLOC_GROW(remote->push_refspec, + remote->push_refspec_nr + 1, + remote->push_refspec_alloc); + remote->push_refspec[remote->push_refspec_nr++] = ref; } static void add_fetch_refspec(struct remote *remote, const char *ref) { - int nr = remote->fetch_refspec_nr + 1; - remote->fetch_refspec = - xrealloc(remote->fetch_refspec, nr * sizeof(char *)); - remote->fetch_refspec[nr-1] = ref; - remote->fetch_refspec_nr = nr; + ALLOC_GROW(remote->fetch_refspec, + remote->fetch_refspec_nr + 1, + remote->fetch_refspec_alloc); + remote->fetch_refspec[remote->fetch_refspec_nr++] = ref; } static void add_url(struct remote *remote, const char *url) { - int nr = remote->url_nr + 1; - remote->url = - xrealloc(remote->url, nr * sizeof(char *)); - remote->url[nr-1] = url; - remote->url_nr = nr; + ALLOC_GROW(remote->url, remote->url_nr + 1, remote->url_alloc); + remote->url[remote->url_nr++] = url; +} + +static void add_url_alias(struct remote *remote, const char *url) +{ + add_url(remote, alias_url(url)); } static struct remote *make_remote(const char *name, int len) { - int i, empty = -1; + struct remote *ret; + int i; - for (i = 0; i < allocated_remotes; i++) { - if (!remotes[i]) { - if (empty < 0) - empty = i; - } else { - if (len ? (!strncmp(name, remotes[i]->name, len) && - !remotes[i]->name[len]) : - !strcmp(name, remotes[i]->name)) - return remotes[i]; - } + for (i = 0; i < remotes_nr; i++) { + if (len ? (!strncmp(name, remotes[i]->name, len) && + !remotes[i]->name[len]) : + !strcmp(name, remotes[i]->name)) + return remotes[i]; } - if (empty < 0) { - empty = allocated_remotes; - allocated_remotes += allocated_remotes ? allocated_remotes : 1; - remotes = xrealloc(remotes, - sizeof(*remotes) * allocated_remotes); - memset(remotes + empty, 0, - (allocated_remotes - empty) * sizeof(*remotes)); - } - remotes[empty] = xcalloc(1, sizeof(struct remote)); + ret = xcalloc(1, sizeof(struct remote)); + ALLOC_GROW(remotes, remotes_nr + 1, remotes_alloc); + remotes[remotes_nr++] = ret; if (len) - remotes[empty]->name = xstrndup(name, len); + ret->name = xstrndup(name, len); else - remotes[empty]->name = xstrdup(name); - return remotes[empty]; + ret->name = xstrdup(name); + return ret; } static void add_merge(struct branch *branch, const char *name) { - int nr = branch->merge_nr + 1; - branch->merge_name = - xrealloc(branch->merge_name, nr * sizeof(char *)); - branch->merge_name[nr-1] = name; - branch->merge_nr = nr; + ALLOC_GROW(branch->merge_name, branch->merge_nr + 1, + branch->merge_alloc); + branch->merge_name[branch->merge_nr++] = name; } static struct branch *make_branch(const char *name, int len) { - int i, empty = -1; + struct branch *ret; + int i; char *refname; - for (i = 0; i < allocated_branches; i++) { - if (!branches[i]) { - if (empty < 0) - empty = i; - } else { - if (len ? (!strncmp(name, branches[i]->name, len) && - !branches[i]->name[len]) : - !strcmp(name, branches[i]->name)) - return branches[i]; - } + for (i = 0; i < branches_nr; i++) { + if (len ? (!strncmp(name, branches[i]->name, len) && + !branches[i]->name[len]) : + !strcmp(name, branches[i]->name)) + return branches[i]; } - if (empty < 0) { - empty = allocated_branches; - allocated_branches += allocated_branches ? allocated_branches : 1; - branches = xrealloc(branches, - sizeof(*branches) * allocated_branches); - memset(branches + empty, 0, - (allocated_branches - empty) * sizeof(*branches)); - } - branches[empty] = xcalloc(1, sizeof(struct branch)); + ALLOC_GROW(branches, branches_nr + 1, branches_alloc); + ret = xcalloc(1, sizeof(struct branch)); + branches[branches_nr++] = ret; if (len) - branches[empty]->name = xstrndup(name, len); + ret->name = xstrndup(name, len); else - branches[empty]->name = xstrdup(name); + ret->name = xstrdup(name); refname = malloc(strlen(name) + strlen("refs/heads/") + 1); strcpy(refname, "refs/heads/"); - strcpy(refname + strlen("refs/heads/"), - branches[empty]->name); - branches[empty]->refname = refname; + strcpy(refname + strlen("refs/heads/"), ret->name); + ret->refname = refname; + + return ret; +} - return branches[empty]; +static struct rewrite *make_rewrite(const char *base, int len) +{ + struct rewrite *ret; + int i; + + for (i = 0; i < rewrite_nr; i++) { + if (len + ? (len == rewrite[i]->baselen && + !strncmp(base, rewrite[i]->base, len)) + : !strcmp(base, rewrite[i]->base)) + return rewrite[i]; + } + + ALLOC_GROW(rewrite, rewrite_nr + 1, rewrite_alloc); + ret = xcalloc(1, sizeof(struct rewrite)); + rewrite[rewrite_nr++] = ret; + if (len) { + ret->base = xstrndup(base, len); + ret->baselen = len; + } + else { + ret->base = xstrdup(base); + ret->baselen = strlen(base); + } + return ret; +} + +static void add_instead_of(struct rewrite *rewrite, const char *instead_of) +{ + ALLOC_GROW(rewrite->instead_of, rewrite->instead_of_nr + 1, rewrite->instead_of_alloc); + rewrite->instead_of[rewrite->instead_of_nr].s = instead_of; + rewrite->instead_of[rewrite->instead_of_nr].len = strlen(instead_of); + rewrite->instead_of_nr++; } static void read_remotes_file(struct remote *remote) @@ -154,7 +215,7 @@ static void read_remotes_file(struct remote *remote) switch (value_list) { case 0: - add_url(remote, xstrdup(s)); + add_url_alias(remote, xstrdup(s)); break; case 1: add_push_refspec(remote, xstrdup(s)); @@ -206,7 +267,7 @@ static void read_branches_file(struct remote *remote) } else { branch = "refs/heads/master"; } - add_url(remote, p); + add_url_alias(remote, p); add_fetch_refspec(remote, branch); remote->fetch_tags = 1; /* always auto-follow */ } @@ -222,17 +283,33 @@ static int handle_config(const char *key, const char *value) subkey = strrchr(name, '.'); if (!subkey) return 0; - if (!value) - return 0; branch = make_branch(name, subkey - name); if (!strcmp(subkey, ".remote")) { + if (!value) + return config_error_nonbool(key); branch->remote_name = xstrdup(value); if (branch == current_branch) default_remote_name = branch->remote_name; - } else if (!strcmp(subkey, ".merge")) + } else if (!strcmp(subkey, ".merge")) { + if (!value) + return config_error_nonbool(key); add_merge(branch, xstrdup(value)); + } return 0; } + if (!prefixcmp(key, "url.")) { + struct rewrite *rewrite; + name = key + 5; + subkey = strrchr(name, '.'); + if (!subkey) + return 0; + rewrite = make_rewrite(name, subkey - name); + if (!strcmp(subkey, ".insteadof")) { + if (!value) + return config_error_nonbool(key); + add_instead_of(rewrite, xstrdup(value)); + } + } if (prefixcmp(key, "remote.")) return 0; name = key + 7; @@ -284,6 +361,18 @@ static int handle_config(const char *key, const char *value) return 0; } +static void alias_all_urls(void) +{ + int i, j; + for (i = 0; i < remotes_nr; i++) { + if (!remotes[i]) + continue; + for (j = 0; j < remotes[i]->url_nr; j++) { + remotes[i]->url[j] = alias_url(remotes[i]->url[j]); + } + } +} + static void read_config(void) { unsigned char sha1[20]; @@ -300,6 +389,7 @@ static void read_config(void) make_branch(head_ref + strlen("refs/heads/"), 0); } git_config(handle_config); + alias_all_urls(); } struct refspec *parse_ref_spec(int nr_refspec, const char **refspec) @@ -340,6 +430,16 @@ struct refspec *parse_ref_spec(int nr_refspec, const char **refspec) return rs; } +static int valid_remote_nick(const char *name) +{ + if (!name[0] || /* not empty */ + (name[0] == '.' && /* not "." */ + (!name[1] || /* not ".." */ + (name[1] == '.' && !name[2])))) + return 0; + return !strchr(name, '/'); /* no slash */ +} + struct remote *remote_get(const char *name) { struct remote *ret; @@ -348,14 +448,14 @@ struct remote *remote_get(const char *name) if (!name) name = default_remote_name; ret = make_remote(name, 0); - if (name[0] != '/') { + if (valid_remote_nick(name)) { if (!ret->url) read_remotes_file(ret); if (!ret->url) read_branches_file(ret); } if (!ret->url) - add_url(ret, name); + add_url_alias(ret, name); if (!ret->url) return NULL; ret->fetch = parse_ref_spec(ret->fetch_refspec_nr, ret->fetch_refspec); @@ -367,7 +467,7 @@ int for_each_remote(each_remote_fn fn, void *priv) { int i, result = 0; read_config(); - for (i = 0; i < allocated_remotes && !result; i++) { + for (i = 0; i < remotes_nr && !result; i++) { struct remote *r = remotes[i]; if (!r) continue; @@ -630,9 +730,17 @@ static int match_explicit(struct ref *src, struct ref *dst, errs = 1; if (!dst_value) { + unsigned char sha1[20]; + int flag; + if (!matched_src) return errs; - dst_value = matched_src->name; + dst_value = resolve_ref(matched_src->name, sha1, 1, &flag); + if (!dst_value || + ((flag & REF_ISSYMREF) && + prefixcmp(dst_value, "refs/heads/"))) + die("%s cannot be resolved to branch.", + matched_src->name); } switch (count_refspec_match(dst_value, dst, &matched_dst)) { @@ -6,14 +6,17 @@ struct remote { const char **url; int url_nr; + int url_alloc; const char **push_refspec; struct refspec *push; int push_refspec_nr; + int push_refspec_alloc; const char **fetch_refspec; struct refspec *fetch; int fetch_refspec_nr; + int fetch_refspec_alloc; /* * -1 to never fetch tags @@ -100,6 +103,7 @@ struct branch { const char **merge_name; struct refspec **merge; int merge_nr; + int merge_alloc; }; struct branch *branch_get(const char *name); diff --git a/revision.c b/revision.c index 6e85aaa3f..84fbdd3af 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) @@ -720,6 +738,10 @@ void init_revisions(struct rev_info *revs, const char *prefix) revs->commit_format = CMIT_FMT_DEFAULT; diff_setup(&revs->diffopt); + if (prefix && !revs->diffopt.prefix) { + revs->diffopt.prefix = prefix; + revs->diffopt.prefix_length = strlen(prefix); + } } static void add_pending_commit_list(struct rev_info *revs, @@ -924,6 +946,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch int left = 1; int all_match = 0; int regflags = 0; + int fixed = 0; /* First, search for "--" */ seen_dashdash = 0; @@ -1055,6 +1078,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; @@ -1216,6 +1243,11 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch regflags |= REG_ICASE; continue; } + if (!strcmp(arg, "--fixed-strings") || + !strcmp(arg, "-F")) { + fixed = 1; + continue; + } if (!strcmp(arg, "--all-match")) { all_match = 1; continue; @@ -1271,8 +1303,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch } } - if (revs->grep_filter) + if (revs->grep_filter) { revs->grep_filter->regflags |= regflags; + revs->grep_filter->fixed = fixed; + } if (show_merge) prepare_show_merge(revs); @@ -1438,6 +1472,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 857231595..c8b3b948e 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, @@ -74,7 +75,7 @@ struct rev_info { struct log_info *loginfo; int nr, total; const char *mime_boundary; - const char *message_id; + char *message_id; const char *ref_message_id; const char *add_signoff; const char *extra_headers; diff --git a/run-command.c b/run-command.c index 476d00c21..743757c36 100644 --- a/run-command.c +++ b/run-command.c @@ -20,12 +20,19 @@ int start_command(struct child_process *cmd) int need_in, need_out, need_err; int fdin[2], fdout[2], fderr[2]; + /* + * In case of errors we must keep the promise to close FDs + * that have been passed in via ->in and ->out. + */ + need_in = !cmd->no_stdin && cmd->in < 0; if (need_in) { - if (pipe(fdin) < 0) + if (pipe(fdin) < 0) { + if (cmd->out > 0) + close(cmd->out); return -ERR_RUN_COMMAND_PIPE; + } cmd->in = fdin[1]; - cmd->close_in = 1; } need_out = !cmd->no_stdout @@ -35,10 +42,11 @@ int start_command(struct child_process *cmd) if (pipe(fdout) < 0) { if (need_in) close_pair(fdin); + else if (cmd->in) + close(cmd->in); return -ERR_RUN_COMMAND_PIPE; } cmd->out = fdout[0]; - cmd->close_out = 1; } need_err = !cmd->no_stderr && cmd->err < 0; @@ -46,8 +54,12 @@ int start_command(struct child_process *cmd) if (pipe(fderr) < 0) { if (need_in) close_pair(fdin); + else if (cmd->in) + close(cmd->in); if (need_out) close_pair(fdout); + else if (cmd->out) + close(cmd->out); return -ERR_RUN_COMMAND_PIPE; } cmd->err = fderr[0]; @@ -57,8 +69,12 @@ int start_command(struct child_process *cmd) if (cmd->pid < 0) { if (need_in) close_pair(fdin); + else if (cmd->in) + close(cmd->in); if (need_out) close_pair(fdout); + else if (cmd->out) + close(cmd->out); if (need_err) close_pair(fderr); return -ERR_RUN_COMMAND_FORK; @@ -120,7 +136,7 @@ int start_command(struct child_process *cmd) if (need_out) close(fdout[1]); - else if (cmd->out > 1) + else if (cmd->out) close(cmd->out); if (need_err) @@ -157,10 +173,6 @@ static int wait_or_whine(pid_t pid) int finish_command(struct child_process *cmd) { - if (cmd->close_in) - close(cmd->in); - if (cmd->close_out) - close(cmd->out); return wait_or_whine(cmd->pid); } diff --git a/run-command.h b/run-command.h index 1fc781d76..debe3074b 100644 --- a/run-command.h +++ b/run-command.h @@ -14,13 +14,29 @@ enum { struct child_process { const char **argv; pid_t pid; + /* + * Using .in, .out, .err: + * - Specify 0 for no redirections (child inherits stdin, stdout, + * stderr from parent). + * - Specify -1 to have a pipe allocated as follows: + * .in: returns the writable pipe end; parent writes to it, + * the readable pipe end becomes child's stdin + * .out, .err: returns the readable pipe end; parent reads from + * it, the writable pipe end becomes child's stdout/stderr + * The caller of start_command() must close the returned FDs + * after it has completed reading from/writing to it! + * - Specify > 0 to set a channel to a particular FD as follows: + * .in: a readable FD, becomes child's stdin + * .out: a writable FD, becomes child's stdout/stderr + * .err > 0 not supported + * The specified FD is closed by start_command(), even in case + * of errors! + */ int in; int out; int err; const char *dir; const char *const *env; - unsigned close_in:1; - unsigned close_out:1; unsigned no_stdin:1; unsigned no_stdout:1; unsigned no_stderr: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; } /* @@ -372,6 +446,8 @@ int check_repository_format_version(const char *var, const char *value) if (is_bare_repository_cfg == 1) inside_work_tree = -1; } else if (strcmp(var, "core.worktree") == 0) { + if (!value) + return config_error_nonbool(var); if (git_work_tree_cfg) free(git_work_tree_cfg); git_work_tree_cfg = xstrdup(value); diff --git a/sha1_file.c b/sha1_file.c index 66a4e00fa..1ddb96bb8 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 be8489e4e..c2805e736 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) @@ -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) { diff --git a/shortlog.h b/shortlog.h new file mode 100644 index 000000000..31ff491b7 --- /dev/null +++ b/shortlog.h @@ -0,0 +1,26 @@ +#ifndef SHORTLOG_H +#define SHORTLOG_H + +#include "path-list.h" + +struct shortlog { + struct path_list list; + int summary; + int wrap_lines; + int sort_by_number; + int wrap; + int in1; + int in2; + + char *common_repo_prefix; + int email; + struct path_list mailmap; +}; + +void shortlog_init(struct shortlog *log); + +void shortlog_add_commit(struct shortlog *log, struct commit *commit); + +void shortlog_output(struct shortlog *log); + +#endif @@ -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 000000000..562b12e16 --- /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 9f84b8d3a..92de08822 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.' \ diff --git a/t/t0003-attributes.sh b/t/t0003-attributes.sh new file mode 100755 index 000000000..47f08a46c --- /dev/null +++ b/t/t0003-attributes.sh @@ -0,0 +1,49 @@ +#!/bin/sh + +test_description=gitattributes + +. ./test-lib.sh + +attr_check () { + + path="$1" + expect="$2" + + git check-attr test -- "$path" >actual && + echo "$path: test: $2" >expect && + diff -u expect actual + +} + + +test_expect_success 'setup' ' + + mkdir -p a/b/d a/c && + ( + echo "f test=f" + ) >.gitattributes && + ( + echo "g test=a/g" && + echo "b/g test=a/b/g" + ) >a/.gitattributes && + ( + echo "h test=a/b/h" && + echo "d/* test=a/b/d/*" + ) >a/b/.gitattributes + +' + +test_expect_success 'attribute test' ' + + attr_check f f && + attr_check a/f f && + attr_check a/c/f f && + attr_check a/g a/g && + attr_check a/b/g a/b/g && + attr_check b/g unspecified && + attr_check a/b/h a/b/h && + attr_check a/b/d/g "a/b/d/*" + +' + +test_done diff --git a/t/t0020-crlf.sh b/t/t0020-crlf.sh index 8b27aa892..90ea081db 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 cad95f35a..818c8621f 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 462fdf262..0e2933a98 100755 --- a/t/t0040-parse-options.sh +++ b/t/t0040-parse-options.sh @@ -19,6 +19,7 @@ string options get a string --string2 <str> get another string --st <st> get another string (pervert ordering) + -o <str> get another string EOF @@ -86,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 @@ -103,4 +104,14 @@ test_expect_success 'non ambiguous option (after two options it abbreviates)' ' git diff expect output ' +cat > expect.err << EOF +error: did you mean \`--boolean\` (with two dashes ?) +EOF + +test_expect_success 'detect possible typos' ' + ! test-parse-options -boolean > output 2> output.err && + test ! -s output && + git diff expect.err output.err +' + test_done diff --git a/t/t0050-filesystem.sh b/t/t0050-filesystem.sh new file mode 100755 index 000000000..cd088b37f --- /dev/null +++ b/t/t0050-filesystem.sh @@ -0,0 +1,93 @@ +#!/bin/sh + +test_description='Various filesystem issues' + +. ./test-lib.sh + +auml=`perl -CO -e 'print pack("U",0x00E4)'` +aumlcdiar=`perl -CO -e 'print pack("U",0x0061).pack("U",0x0308)'` + +test_expect_success 'see if we expect ' ' + + test_case=test_expect_success + test_unicode=test_expect_success + mkdir junk && + echo good >junk/CamelCase && + echo bad >junk/camelcase && + if test "$(cat junk/CamelCase)" != good + then + test_case=test_expect_failure + say "will test on a case insensitive filesystem" + fi && + rm -fr junk && + mkdir junk && + >junk/"$auml" && + case "$(cd junk && echo *)" in + "$aumlcdiar") + test_unicode=test_expect_failure + say "will test on a unicode corrupting filesystem" + ;; + *) ;; + esac && + rm -fr junk +' + +test_expect_success "setup case tests" ' + + touch camelcase && + git add camelcase && + git commit -m "initial" && + git tag initial && + git checkout -b topic && + git mv camelcase tmp && + git mv tmp CamelCase && + git commit -m "rename" && + git checkout -f master + +' + +$test_case 'rename (case change)' ' + + git mv camelcase CamelCase && + git commit -m "rename" + +' + +$test_case 'merge (case change)' ' + + git reset --hard initial && + git merge topic + +' + +test_expect_success "setup unicode normalization tests" ' + + test_create_repo unicode && + cd unicode && + touch "$aumlcdiar" && + git add "$aumlcdiar" && + git commit -m initial + git tag initial && + git checkout -b topic && + git mv $aumlcdiar tmp && + git mv tmp "$auml" && + git commit -m rename && + git checkout -f master + +' + +$test_unicode 'rename (silent unicode normalization)' ' + + git mv "$aumlcdiar" "$auml" && + git commit -m rename + +' + +$test_unicode 'merge (silent unicode normalization)' ' + + git reset --hard initial && + git merge topic + +' + +test_done diff --git a/t/t1000-read-tree-m-3way.sh b/t/t1000-read-tree-m-3way.sh index 37add1b50..6c065bfa2 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 991d3c5e9..dcb3108c2 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 42eac2a7c..4928a5711 100755 --- a/t/t1300-repo-config.sh +++ b/t/t1300-repo-config.sh @@ -71,6 +71,25 @@ EOF test_expect_success 'non-match result' 'cmp .git/config expect' +cat > .git/config <<\EOF +[alpha] +bar = foo +[beta] +baz = multiple \ +lines +EOF + +test_expect_success 'unset with cont. lines' \ + 'git config --unset beta.baz' + +cat > expect <<\EOF +[alpha] +bar = foo +[beta] +EOF + +test_expect_success 'unset with cont. lines is correct' 'cmp .git/config expect' + cat > .git/config << EOF [beta] ; silly comment # another comment noIndent= sillyValue ; 'nother silly comment @@ -181,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' @@ -202,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$" @@ -224,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' @@ -278,17 +302,40 @@ test_expect_success '--add' \ cat > .git/config << EOF [novalue] variable +[emptyvalue] + variable = EOF test_expect_success 'get variable with no value' \ 'git config --get novalue.variable ^$' +test_expect_success 'get variable with empty value' \ + 'git config --get emptyvalue.variable ^$' + echo novalue.variable > expect test_expect_success 'get-regexp variable with no value' \ 'git config --get-regexp novalue > output && cmp output expect' +echo 'emptyvalue.variable ' > expect + +test_expect_success 'get-regexp variable with empty value' \ + 'git config --get-regexp emptyvalue > output && + cmp output expect' + +echo true > expect + +test_expect_success 'get bool variable with no value' \ + 'git config --bool novalue.variable > output && + cmp output expect' + +echo false > expect + +test_expect_success 'get bool variable with empty value' \ + 'git config --bool emptyvalue.variable > output && + cmp output expect' + git config > output 2>&1 test_expect_success 'no arguments, but no crash' \ @@ -382,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" @@ -494,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 @@ -562,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 37fc1c8d3..9be0770e7 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 71ab2dd0e..78cd41245 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 e474b3f1d..38a2bf09a 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/t1502-rev-parse-parseopt.sh b/t/t1502-rev-parse-parseopt.sh new file mode 100755 index 000000000..762af5faf --- /dev/null +++ b/t/t1502-rev-parse-parseopt.sh @@ -0,0 +1,43 @@ +#!/bin/sh + +test_description='test git rev-parse --parseopt' +. ./test-lib.sh + +cat > expect.err <<EOF +usage: some-command [options] <args>... + + some-command does foo and bar! + + -h, --help show the help + --foo some nifty option --foo + --bar ... some cool option --bar with an argument + +An option group Header + -C [...] option C with an optional argument + +Extras + --extra1 line above used to cause a segfault but no longer does + +EOF + +test_expect_success 'test --parseopt help output' ' + git rev-parse --parseopt -- -h 2> output.err <<EOF +some-command [options] <args>... + +some-command does foo and bar! +-- +h,help show the help + +foo some nifty option --foo +bar= some cool option --bar with an argument + + An option group Header +C? option C with an optional argument + +Extras +extra1 line above used to cause a segfault but no longer does +EOF + git diff expect.err output.err +' + +test_done diff --git a/t/t2000-checkout-cache-clash.sh b/t/t2000-checkout-cache-clash.sh index ac84335b0..5141fab7c 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 f7a005592..0f441bcef 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 f78945ed8..4a723dc0e 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 04a1ed1a6..9beaecd18 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 e25b25568..b4297bacf 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 c83f820ad..f4da86993 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 ef1eeb7d8..d21081d0f 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 4ddc6342a..b64ccfbc5 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 95e33b521..496f4ec17 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 657f68104..0a2609965 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 74a7eb30f..62e65d704 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" @@ -324,4 +324,42 @@ test_expect_success 'rebase a detached HEAD' ' test $grandparent = $(git rev-parse HEAD~2) ' +test_expect_success 'rebase a commit violating pre-commit' ' + + mkdir -p .git/hooks && + PRE_COMMIT=.git/hooks/pre-commit && + echo "#!/bin/sh" > $PRE_COMMIT && + echo "test -z \"\$(git diff --cached --check)\"" >> $PRE_COMMIT && + chmod a+x $PRE_COMMIT && + echo "monde! " >> file1 && + test_tick && + ! git commit -m doesnt-verify file1 && + git commit -m doesnt-verify --no-verify file1 && + test_tick && + FAKE_LINES=2 git rebase -i HEAD~2 + +' + +test_expect_success 'rebase with a file named HEAD in worktree' ' + + rm -fr .git/hooks && + git reset --hard && + git checkout -b branch3 A && + + ( + GIT_AUTHOR_NAME="Squashed Away" && + export GIT_AUTHOR_NAME && + >HEAD && + git add HEAD && + git commit -m "Add head" && + >BODY && + git add BODY && + git commit -m "Add body" + ) && + + FAKE_LINES="1 squash 2" git rebase -i to-be-rebased && + test "$(git show -s --pretty=format:%an)" = "Squashed Away" + +' + test_done diff --git a/t/t3600-rm.sh b/t/t3600-rm.sh index b1ee622ef..f542f0af4 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 000000000..c8dc1ac24 --- /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/t4013-diff-various.sh b/t/t4013-diff-various.sh index 9eec75422..6b4d1c52b 100755 --- a/t/t4013-diff-various.sh +++ b/t/t4013-diff-various.sh @@ -245,6 +245,7 @@ format-patch --inline --stdout initial..master format-patch --inline --stdout --subject-prefix=TESTCASE initial..master config format.subjectprefix DIFFERENT_PREFIX format-patch --inline --stdout initial..master^^ +format-patch --stdout --cover-letter -n initial..master^ diff --abbrev initial..side diff -r initial..side diff --git a/t/t4013/diff.format-patch_--stdout_--cover-letter_-n_initial..master^ b/t/t4013/diff.format-patch_--stdout_--cover-letter_-n_initial..master^ new file mode 100644 index 000000000..0151453b7 --- /dev/null +++ b/t/t4013/diff.format-patch_--stdout_--cover-letter_-n_initial..master^ @@ -0,0 +1,100 @@ +$ git format-patch --stdout --cover-letter -n initial..master^ +From 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0 Mon Sep 17 00:00:00 2001 +From: C O Mitter <committer@example.com> +Date: Mon, 26 Jun 2006 00:05:00 +0000 +Subject: [DIFFERENT_PREFIX 0/2] *** SUBJECT HERE *** + +*** BLURB HERE *** + +A U Thor (2): + Second + Third + + dir/sub | 4 ++++ + file0 | 3 +++ + file1 | 3 +++ + file2 | 3 --- + 4 files changed, 10 insertions(+), 3 deletions(-) + create mode 100644 file1 + delete mode 100644 file2 + +From 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44 Mon Sep 17 00:00:00 2001 +From: A U Thor <author@example.com> +Date: Mon, 26 Jun 2006 00:01:00 +0000 +Subject: [DIFFERENT_PREFIX 1/2] Second + +This is the second commit. +--- + dir/sub | 2 ++ + file0 | 3 +++ + file2 | 3 --- + 3 files changed, 5 insertions(+), 3 deletions(-) + delete mode 100644 file2 + +diff --git a/dir/sub b/dir/sub +index 35d242b..8422d40 100644 +--- a/dir/sub ++++ b/dir/sub +@@ -1,2 +1,4 @@ + A + B ++C ++D +diff --git a/file0 b/file0 +index 01e79c3..b414108 100644 +--- a/file0 ++++ b/file0 +@@ -1,3 +1,6 @@ + 1 + 2 + 3 ++4 ++5 ++6 +diff --git a/file2 b/file2 +deleted file mode 100644 +index 01e79c3..0000000 +--- a/file2 ++++ /dev/null +@@ -1,3 +0,0 @@ +-1 +-2 +-3 +-- +g-i-t--v-e-r-s-i-o-n + + +From 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0 Mon Sep 17 00:00:00 2001 +From: A U Thor <author@example.com> +Date: Mon, 26 Jun 2006 00:02:00 +0000 +Subject: [DIFFERENT_PREFIX 2/2] Third + +--- + dir/sub | 2 ++ + file1 | 3 +++ + 2 files changed, 5 insertions(+), 0 deletions(-) + create mode 100644 file1 + +diff --git a/dir/sub b/dir/sub +index 8422d40..cead32e 100644 +--- a/dir/sub ++++ b/dir/sub +@@ -2,3 +2,5 @@ A + B + C + D ++E ++F +diff --git a/file1 b/file1 +new file mode 100644 +index 0000000..b1e6722 +--- /dev/null ++++ b/file1 +@@ -0,0 +1,3 @@ ++A ++B ++C +-- +g-i-t--v-e-r-s-i-o-n + +$ diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh index 0a6fe5337..16aa99dc0 100755 --- a/t/t4014-format-patch.sh +++ b/t/t4014-format-patch.sh @@ -88,4 +88,117 @@ test_expect_success 'replay did not screw up the log message' ' ' +test_expect_success 'extra headers' ' + + git config format.headers "To: R. E. Cipient <rcipient@example.com> +" && + git config --add format.headers "Cc: S. E. Cipient <scipient@example.com> +" && + git format-patch --stdout master..side > patch2 && + sed -e "/^$/q" patch2 > hdrs2 && + grep "^To: R. E. Cipient <rcipient@example.com>$" hdrs2 && + grep "^Cc: S. E. Cipient <scipient@example.com>$" hdrs2 + +' + +test_expect_success 'extra headers without newlines' ' + + git config --replace-all format.headers "To: R. E. Cipient <rcipient@example.com>" && + git config --add format.headers "Cc: S. E. Cipient <scipient@example.com>" && + git format-patch --stdout master..side >patch3 && + sed -e "/^$/q" patch3 > hdrs3 && + grep "^To: R. E. Cipient <rcipient@example.com>$" hdrs3 && + grep "^Cc: S. E. Cipient <scipient@example.com>$" hdrs3 + +' + +test_expect_success 'extra headers with multiple To:s' ' + + git config --replace-all format.headers "To: R. E. Cipient <rcipient@example.com>" && + git config --add format.headers "To: S. E. Cipient <scipient@example.com>" && + git format-patch --stdout master..side > patch4 && + sed -e "/^$/q" patch4 > hdrs4 && + grep "^To: R. E. Cipient <rcipient@example.com>,$" hdrs4 && + grep "^ *S. E. Cipient <scipient@example.com>$" hdrs4 +' + +test_expect_success 'additional command line cc' ' + + git config --replace-all format.headers "Cc: R. E. Cipient <rcipient@example.com>" && + git format-patch --cc="S. E. Cipient <scipient@example.com>" --stdout master..side | sed -e "/^$/q" >patch5 && + grep "^Cc: R. E. Cipient <rcipient@example.com>,$" patch5 && + grep "^ *S. E. Cipient <scipient@example.com>$" patch5 +' + +test_expect_success 'multiple files' ' + + rm -rf patches/ && + git checkout side && + git format-patch -o patches/ master && + ls patches/0001-Side-changes-1.patch patches/0002-Side-changes-2.patch patches/0003-Side-changes-3-with-n-backslash-n-in-it.patch +' + +test_expect_success 'thread' ' + + rm -rf patches/ && + git checkout side && + git format-patch --thread -o patches/ master && + FIRST_MID=$(grep "Message-Id:" patches/0001-* | sed "s/^[^<]*\(<[^>]*>\).*$/\1/") && + for i in patches/0002-* patches/0003-* + do + grep "References: $FIRST_MID" $i && + grep "In-Reply-To: $FIRST_MID" $i + done +' + +test_expect_success 'thread in-reply-to' ' + + rm -rf patches/ && + git checkout side && + git format-patch --in-reply-to="<test.message>" --thread -o patches/ master && + FIRST_MID="<test.message>" && + for i in patches/* + do + grep "References: $FIRST_MID" $i && + grep "In-Reply-To: $FIRST_MID" $i + done +' + +test_expect_success 'thread cover-letter' ' + + rm -rf patches/ && + git checkout side && + git format-patch --cover-letter --thread -o patches/ master && + FIRST_MID=$(grep "Message-Id:" patches/0000-* | sed "s/^[^<]*\(<[^>]*>\).*$/\1/") && + for i in patches/0001-* patches/0002-* patches/0003-* + do + grep "References: $FIRST_MID" $i && + grep "In-Reply-To: $FIRST_MID" $i + done +' + +test_expect_success 'thread cover-letter in-reply-to' ' + + rm -rf patches/ && + git checkout side && + git format-patch --cover-letter --in-reply-to="<test.message>" --thread -o patches/ master && + FIRST_MID="<test.message>" && + for i in patches/* + do + grep "References: $FIRST_MID" $i && + grep "In-Reply-To: $FIRST_MID" $i + done +' + +test_expect_success 'excessive subject' ' + + rm -rf patches/ && + git checkout side && + for i in 5 6 1 2 3 A 4 B C 7 8 9 10 D E F; do echo "$i"; done >>file && + git update-index file && + git commit -m "This is an excessively long subject line for a message due to the habit some projects have of not having a short, one-line subject at the start of the commit message, but rather sticking a whole paragraph right at the start as the only thing in the commit message. It had better not become the filename for the patch." && + git format-patch -o patches/ master..side && + ls patches/0004-This-is-an-excessively-long-subject-line-for-a-messa.patch +' + test_done diff --git a/t/t4015-diff-whitespace.sh b/t/t4015-diff-whitespace.sh index d30169fbd..83c54b747 100755 --- a/t/t4015-diff-whitespace.sh +++ b/t/t4015-diff-whitespace.sh @@ -326,4 +326,13 @@ test_expect_success 'check tabs and spaces as indentation (indent-with-non-tab: ! git diff --check ' + +test_expect_success 'line numbers in --check output are correct' ' + + echo "" > x && + echo "foo(); " >> x && + git diff --check | grep "x:2:" + +' + test_done diff --git a/t/t4019-diff-wserror.sh b/t/t4019-diff-wserror.sh index 67e080bdb..0d9cbb626 100755 --- a/t/t4019-diff-wserror.sh +++ b/t/t4019-diff-wserror.sh @@ -12,6 +12,7 @@ test_expect_success setup ' echo " Eight SP indent" >>F && echo " HT and SP indent" >>F && echo "With trailing SP " >>F && + echo "Carriage ReturnQ" | tr Q "\015" >>F && echo "No problem" >>F ' @@ -27,6 +28,7 @@ test_expect_success default ' grep Eight normal >/dev/null && grep HT error >/dev/null && grep With error >/dev/null && + grep Return error >/dev/null && grep No normal >/dev/null ' @@ -41,6 +43,7 @@ test_expect_success 'without -trail' ' grep Eight normal >/dev/null && grep HT error >/dev/null && grep With normal >/dev/null && + grep Return normal >/dev/null && grep No normal >/dev/null ' @@ -56,6 +59,7 @@ test_expect_success 'without -trail (attribute)' ' grep Eight normal >/dev/null && grep HT error >/dev/null && grep With normal >/dev/null && + grep Return normal >/dev/null && grep No normal >/dev/null ' @@ -71,6 +75,7 @@ test_expect_success 'without -space' ' grep Eight normal >/dev/null && grep HT normal >/dev/null && grep With error >/dev/null && + grep Return error >/dev/null && grep No normal >/dev/null ' @@ -86,6 +91,7 @@ test_expect_success 'without -space (attribute)' ' grep Eight normal >/dev/null && grep HT normal >/dev/null && grep With error >/dev/null && + grep Return error >/dev/null && grep No normal >/dev/null ' @@ -101,6 +107,7 @@ test_expect_success 'with indent-non-tab only' ' grep Eight error >/dev/null && grep HT normal >/dev/null && grep With normal >/dev/null && + grep Return normal >/dev/null && grep No normal >/dev/null ' @@ -116,6 +123,39 @@ test_expect_success 'with indent-non-tab only (attribute)' ' grep Eight error >/dev/null && grep HT normal >/dev/null && grep With normal >/dev/null && + grep Return normal >/dev/null && + grep No normal >/dev/null + +' + +test_expect_success 'with cr-at-eol' ' + + rm -f .gitattributes + git config core.whitespace cr-at-eol + git diff --color >output + grep "$blue_grep" output >error + grep -v "$blue_grep" output >normal + + grep Eight normal >/dev/null && + grep HT error >/dev/null && + grep With error >/dev/null && + grep Return normal >/dev/null && + grep No normal >/dev/null + +' + +test_expect_success 'with cr-at-eol (attribute)' ' + + git config --unset core.whitespace + echo "F whitespace=trailing,cr-at-eol" >.gitattributes + git diff --color >output + grep "$blue_grep" output >error + grep -v "$blue_grep" output >normal + + grep Eight normal >/dev/null && + grep HT error >/dev/null && + grep With error >/dev/null && + grep Return normal >/dev/null && grep No normal >/dev/null ' diff --git a/t/t4026-color.sh b/t/t4026-color.sh new file mode 100755 index 000000000..b61e5169f --- /dev/null +++ b/t/t4026-color.sh @@ -0,0 +1,69 @@ +#!/bin/sh +# +# Copyright (c) 2008 Timo Hirvonen +# + +test_description='Test diff/status color escape codes' +. ./test-lib.sh + +color() +{ + git config diff.color.new "$1" && + test "`git config --get-color diff.color.new`" = "$2" +} + +invalid_color() +{ + git config diff.color.new "$1" && + test -z "`git config --get-color diff.color.new 2>/dev/null`" +} + +test_expect_success 'reset' ' + color "reset" "[m" +' + +test_expect_success 'attribute before color name' ' + color "bold red" "[1;31m" +' + +test_expect_success 'color name before attribute' ' + color "red bold" "[1;31m" +' + +test_expect_success 'attr fg bg' ' + color "ul blue red" "[4;34;41m" +' + +test_expect_success 'fg attr bg' ' + color "blue ul red" "[4;34;41m" +' + +test_expect_success 'fg bg attr' ' + color "blue red ul" "[4;34;41m" +' + +test_expect_success '256 colors' ' + color "254 bold 255" "[1;38;5;254;48;5;255m" +' + +test_expect_success 'color too small' ' + invalid_color "-2" +' + +test_expect_success 'color too big' ' + invalid_color "256" +' + +test_expect_success 'extra character after color number' ' + invalid_color "3X" +' + +test_expect_success 'extra character after color name' ' + invalid_color "redX" +' + +test_expect_success 'extra character after attribute' ' + invalid_color "dimX" +' + +test_done diff --git a/t/t4103-apply-binary.sh b/t/t4103-apply-binary.sh index 74f06ec73..7c25634fc 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/t4105-apply-fuzz.sh b/t/t4105-apply-fuzz.sh new file mode 100755 index 000000000..0e8d25f18 --- /dev/null +++ b/t/t4105-apply-fuzz.sh @@ -0,0 +1,57 @@ +#!/bin/sh + +test_description='apply with fuzz and offset' + +. ./test-lib.sh + +dotest () { + name="$1" && shift && + test_expect_success "$name" " + git checkout-index -f -q -u file && + git apply $* && + diff -u expect file + " +} + +test_expect_success setup ' + + for i in 1 2 3 4 5 6 7 8 9 10 11 12 + do + echo $i + done >file && + git update-index --add file && + for i in 1 2 3 4 5 6 7 a b c d e 8 9 10 11 12 + do + echo $i + done >file && + cat file >expect && + git diff >O0.diff && + + sed -e "s/@@ -5,6 +5,11 @@/@@ -2,6 +2,11 @@/" >O1.diff O0.diff && + sed -e "s/@@ -5,6 +5,11 @@/@@ -7,6 +7,11 @@/" >O2.diff O0.diff && + sed -e "s/@@ -5,6 +5,11 @@/@@ -19,6 +19,11 @@/" >O3.diff O0.diff && + + sed -e "s/^ 5/ S/" >F0.diff O0.diff && + sed -e "s/^ 5/ S/" >F1.diff O1.diff && + sed -e "s/^ 5/ S/" >F2.diff O2.diff && + sed -e "s/^ 5/ S/" >F3.diff O3.diff + +' + +dotest 'unmodified patch' O0.diff + +dotest 'minus offset' O1.diff + +dotest 'plus offset' O2.diff + +dotest 'big offset' O3.diff + +dotest 'fuzz with no offset' -C2 F0.diff + +dotest 'fuzz with minus offset' -C2 F1.diff + +dotest 'fuzz with plus offset' -C2 F2.diff + +dotest 'fuzz with big offset' -C2 F3.diff + +test_done diff --git a/t/t4113-apply-ending.sh b/t/t4113-apply-ending.sh index 1c6bec044..d74103988 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/t4125-apply-ws-fuzz.sh b/t/t4125-apply-ws-fuzz.sh new file mode 100755 index 000000000..d6f15be67 --- /dev/null +++ b/t/t4125-apply-ws-fuzz.sh @@ -0,0 +1,103 @@ +#!/bin/sh + +test_description='applying patch that has broken whitespaces in context' + +. ./test-lib.sh + +test_expect_success setup ' + + >file && + git add file && + + # file-0 is full of whitespace breakages + for l in a bb c d eeee f ggg h + do + echo "$l " + done >file-0 && + + # patch-0 creates a whitespace broken file + cat file-0 >file && + git diff >patch-0 && + git add file && + + # file-1 is still full of whitespace breakages, + # but has one line updated, without fixing any + # whitespaces. + # patch-1 records that change. + sed -e "s/d/D/" file-0 >file-1 && + cat file-1 >file && + git diff >patch-1 && + + # patch-all is the effect of both patch-0 and patch-1 + >file && + git add file && + cat file-1 >file && + git diff >patch-all && + + # patch-2 is the same as patch-1 but is based + # on a version that already has whitespace fixed, + # and does not introduce whitespace breakages. + sed -e "s/ $//" patch-1 >patch-2 && + + # If all whitespace breakages are fixed the contents + # should look like file-fixed + sed -e "s/ $//" file-1 >file-fixed + +' + +test_expect_success nofix ' + + >file && + git add file && + + # Baseline. Applying without fixing any whitespace + # breakages. + git apply --whitespace=nowarn patch-0 && + git apply --whitespace=nowarn patch-1 && + + # The result should obviously match. + diff -u file-1 file +' + +test_expect_success 'withfix (forward)' ' + + >file && + git add file && + + # The first application will munge the context lines + # the second patch depends on. We should be able to + # adjust and still apply. + git apply --whitespace=fix patch-0 && + git apply --whitespace=fix patch-1 && + + diff -u file-fixed file +' + +test_expect_success 'withfix (backward)' ' + + >file && + git add file && + + # Now we have a whitespace breakages on our side. + git apply --whitespace=nowarn patch-0 && + + # And somebody sends in a patch based on image + # with whitespace already fixed. + git apply --whitespace=fix patch-2 && + + # The result should accept the whitespace fixed + # postimage. But the line with "h" is beyond context + # horizon and left unfixed. + + sed -e /h/d file-fixed >fixed-head && + sed -e /h/d file >file-head && + diff -u fixed-head file-head && + + sed -n -e /h/p file-fixed >fixed-tail && + sed -n -e /h/p file >file-tail && + + ! diff -u fixed-tail file-tail + +' + +test_done diff --git a/t/t5100-mailinfo.sh b/t/t5100-mailinfo.sh index 9b1a74542..d6c55c115 100755 --- a/t/t5100-mailinfo.sh +++ b/t/t5100-mailinfo.sh @@ -11,7 +11,7 @@ test_expect_success 'split sample box' \ 'git mailsplit -o. ../t5100/sample.mbox >last && last=`cat last` && echo total is $last && - test `cat last` = 8' + test `cat last` = 9' for mail in `echo 00*` do diff --git a/t/t5100/info0009 b/t/t5100/info0009 new file mode 100644 index 000000000..2a66321c8 --- /dev/null +++ b/t/t5100/info0009 @@ -0,0 +1,5 @@ +Author: F U Bar +Email: f.u.bar@example.com +Subject: updates +Date: Mon, 17 Sep 2001 00:00:00 +0900 + diff --git a/t/t5100/msg0009 b/t/t5100/msg0009 new file mode 100644 index 000000000..9ffe13148 --- /dev/null +++ b/t/t5100/msg0009 @@ -0,0 +1,2 @@ +This is to fix diff-format documentation. + diff --git a/t/t5100/patch0009 b/t/t5100/patch0009 new file mode 100644 index 000000000..65615c34a --- /dev/null +++ b/t/t5100/patch0009 @@ -0,0 +1,13 @@ +diff --git a/Documentation/diff-format.txt b/Documentation/diff-format.txt +index b426a14..97756ec 100644 +--- a/Documentation/diff-format.txt ++++ b/Documentation/diff-format.txt +@@ -81,7 +81,7 @@ The "diff" formatting options can be customized via the + environment variable 'GIT_DIFF_OPTS'. For example, if you + prefer context diff: + +- GIT_DIFF_OPTS=-c git-diff-index -p $(cat .git/HEAD) ++ GIT_DIFF_OPTS=-c git-diff-index -p HEAD + + + 2. When the environment variable 'GIT_EXTERNAL_DIFF' is set, the diff --git a/t/t5100/sample.mbox b/t/t5100/sample.mbox index 070c1661b..0476b96c3 100644 --- a/t/t5100/sample.mbox +++ b/t/t5100/sample.mbox @@ -407,3 +407,26 @@ Subject: [PATCH] another patch Hey you forgot the patch! +From nobody Mon Sep 17 00:00:00 2001 +From: A U Thor <a.u.thor@example.com> +Date: Mon, 17 Sep 2001 00:00:00 +0900 +Mime-Version: 1.0 +Content-Type: Text/Plain; charset=us-ascii +Content-Transfer-Encoding: Quoted-Printable + +=0A=0AFrom: F U Bar <f.u.bar@example.com> +Subject: [PATCH] updates=0A=0AThis is to fix diff-format documentation. + +diff --git a/Documentation/diff-format.txt b/Documentation/diff-format.txt +index b426a14..97756ec 100644 +--- a/Documentation/diff-format.txt ++++ b/Documentation/diff-format.txt +@@ -81,7 +81,7 @@ The "diff" formatting options can be customized via the + environment variable 'GIT_DIFF_OPTS'. For example, if you + prefer context diff: +=20 +- GIT_DIFF_OPTS=3D-c git-diff-index -p $(cat .git/HEAD) ++ GIT_DIFF_OPTS=3D-c git-diff-index -p HEAD +=20 +=20 + 2. When the environment variable 'GIT_EXTERNAL_DIFF' is set, the diff --git a/t/t5300-pack-object.sh b/t/t5300-pack-object.sh index 6e594bf1e..cd3c14980 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 2a2878b57..67b9a7b84 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/t5303-hash-object.sh b/t/t5303-hash-object.sh new file mode 100755 index 000000000..543c0784b --- /dev/null +++ b/t/t5303-hash-object.sh @@ -0,0 +1,35 @@ +#!/bin/sh + +test_description=git-hash-object + +. ./test-lib.sh + +test_expect_success \ + 'git hash-object -w --stdin saves the object' \ + 'obname=$(echo foo | git hash-object -w --stdin) && + obpath=$(echo $obname | sed -e "s/\(..\)/\1\//") && + test -r .git/objects/"$obpath" && + rm -f .git/objects/"$obpath"' + +test_expect_success \ + 'git hash-object --stdin -w saves the object' \ + 'obname=$(echo foo | git hash-object --stdin -w) && + obpath=$(echo $obname | sed -e "s/\(..\)/\1\//") && + test -r .git/objects/"$obpath" && + rm -f .git/objects/"$obpath"' + +test_expect_success \ + 'git hash-object --stdin file1 <file0 first operates on file0, then file1' \ + 'echo foo > file1 && + obname0=$(echo bar | git hash-object --stdin) && + obname1=$(git hash-object file1) && + obname0new=$(echo bar | git hash-object --stdin file1 | sed -n -e 1p) && + obname1new=$(echo bar | git hash-object --stdin file1 | sed -n -e 2p) && + test "$obname0" = "$obname0new" && + test "$obname1" = "$obname1new"' + +test_expect_success \ + 'git hash-object refuses multiple --stdin arguments' \ + '! git hash-object --stdin --stdin < file1' + +test_done diff --git a/t/t5304-prune.sh b/t/t5304-prune.sh new file mode 100644 index 000000000..6560af756 --- /dev/null +++ b/t/t5304-prune.sh @@ -0,0 +1,32 @@ +#!/bin/sh +# +# Copyright (c) 2008 Johannes E. Schindelin +# + +test_description='prune' +. ./test-lib.sh + +test_expect_success setup ' + + : > file && + git add file && + test_tick && + git commit -m initial && + git gc + +' + +test_expect_success 'prune stale packs' ' + + orig_pack=$(echo .git/objects/pack/*.pack) && + : > .git/objects/tmp_1.pack && + : > .git/objects/tmp_2.pack && + test-chmtime -86501 .git/objects/tmp_1.pack && + git prune --expire 1.day && + test -f $orig_pack && + test -f .git/objects/tmp_2.pack && + ! test -f .git/objects/tmp_1.pack + +' + +test_done diff --git a/t/t5401-update-hooks.sh b/t/t5401-update-hooks.sh index 3eea3069e..9a1202424 100755 --- a/t/t5401-update-hooks.sh +++ b/t/t5401-update-hooks.sh @@ -25,7 +25,7 @@ test_expect_success setup ' cat >victim/.git/hooks/pre-receive <<'EOF' #!/bin/sh -printf "$@" >>$GIT_DIR/pre-receive.args +printf %s "$@" >>$GIT_DIR/pre-receive.args cat - >$GIT_DIR/pre-receive.stdin echo STDOUT pre-receive echo STDERR pre-receive >&2 @@ -35,7 +35,7 @@ chmod u+x victim/.git/hooks/pre-receive cat >victim/.git/hooks/update <<'EOF' #!/bin/sh echo "$@" >>$GIT_DIR/update.args -read x; printf "$x" >$GIT_DIR/update.stdin +read x; printf %s "$x" >$GIT_DIR/update.stdin echo STDOUT update $1 echo STDERR update $1 >&2 test "$1" = refs/heads/master || exit @@ -44,7 +44,7 @@ chmod u+x victim/.git/hooks/update cat >victim/.git/hooks/post-receive <<'EOF' #!/bin/sh -printf "$@" >>$GIT_DIR/post-receive.args +printf %s "$@" >>$GIT_DIR/post-receive.args cat - >$GIT_DIR/post-receive.stdin echo STDOUT post-receive echo STDERR post-receive >&2 @@ -54,14 +54,14 @@ chmod u+x victim/.git/hooks/post-receive cat >victim/.git/hooks/post-update <<'EOF' #!/bin/sh echo "$@" >>$GIT_DIR/post-update.args -read x; printf "$x" >$GIT_DIR/post-update.stdin +read x; printf %s "$x" >$GIT_DIR/post-update.stdin echo STDOUT post-update 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 1c4b0b32a..1394047a8 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 7b6798d8b..788b4a5aa 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 02882c1e4..9b948c14e 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/t5516-fetch-push.sh b/t/t5516-fetch-push.sh index 9d2dc33cb..793ffc660 100755 --- a/t/t5516-fetch-push.sh +++ b/t/t5516-fetch-push.sh @@ -100,6 +100,23 @@ test_expect_success 'fetch with wildcard' ' ) ' +test_expect_success 'fetch with insteadOf' ' + mk_empty && + ( + TRASH=$(pwd) && + cd testrepo && + git config url./$TRASH/.insteadOf trash/ + git config remote.up.url trash/. && + git config remote.up.fetch "refs/heads/*:refs/remotes/origin/*" && + git fetch up && + + r=$(git show-ref -s --verify refs/remotes/origin/master) && + test "z$r" = "z$the_commit" && + + test 1 = $(git for-each-ref refs/remotes/origin | wc -l) + ) +' + test_expect_success 'push without wildcard' ' mk_empty && @@ -126,6 +143,20 @@ test_expect_success 'push with wildcard' ' ) ' +test_expect_success 'push with insteadOf' ' + mk_empty && + TRASH=$(pwd) && + git config url./$TRASH/.insteadOf trash/ && + git push trash/testrepo refs/heads/master:refs/remotes/origin/master && + ( + cd testrepo && + r=$(git show-ref -s --verify refs/remotes/origin/master) && + test "z$r" = "z$the_commit" && + + test 1 = $(git for-each-ref refs/remotes/origin | wc -l) + ) +' + test_expect_success 'push with matching heads' ' mk_test heads/master && @@ -271,6 +302,49 @@ test_expect_success 'push with HEAD nonexisting at remote' ' check_push_result $the_commit heads/local ' +test_expect_success 'push with +HEAD' ' + + mk_test heads/master && + git checkout master && + git branch -D local && + git checkout -b local && + git push testrepo master local && + check_push_result $the_commit heads/master && + check_push_result $the_commit heads/local && + + # Without force rewinding should fail + git reset --hard HEAD^ && + ! git push testrepo HEAD && + check_push_result $the_commit heads/local && + + # With force rewinding should succeed + git push testrepo +HEAD && + check_push_result $the_first_commit heads/local + +' + +test_expect_success 'push with config remote.*.push = HEAD' ' + + mk_test heads/local && + git checkout master && + git branch -f local $the_commit && + ( + cd testrepo && + git checkout local && + git reset --hard $the_first_commit + ) && + git config remote.there.url testrepo && + git config remote.there.push HEAD && + git config branch.master.remote there && + git push && + check_push_result $the_commit heads/master && + check_push_result $the_first_commit heads/local +' + +# clean up the cruft left with the previous one +git config --remove-section remote.there +git config --remove-section branch.master + test_expect_success 'push with dry-run' ' mk_test heads/master && diff --git a/t/t5520-pull.sh b/t/t5520-pull.sh index 52b3a0c6d..9484129ca 100755 --- a/t/t5520-pull.sh +++ b/t/t5520-pull.sh @@ -71,8 +71,25 @@ test_expect_success 'branch.to-rebase.rebase' ' git reset --hard before-rebase && git config branch.to-rebase.rebase 1 && git pull . copy && + git config branch.to-rebase.rebase 0 && test $(git rev-parse HEAD^) = $(git rev-parse copy) && test new = $(git show HEAD:file2) ' +test_expect_success '--rebase with rebased upstream' ' + + git remote add -f me . && + git checkout copy && + git reset --hard HEAD^ && + echo conflicting modification > file && + git commit -m conflict file && + git checkout to-rebase && + echo file > file2 && + git commit -m to-rebase file2 && + git pull --rebase me copy && + test "conflicting modification" = "$(cat file)" && + test file = $(cat file2) + +' + test_done diff --git a/t/t5530-upload-pack-error.sh b/t/t5530-upload-pack-error.sh index cc8949e3e..8b0509106 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 1776b377f..acf34cec8 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/t5701-clone-local.sh b/t/t5701-clone-local.sh index 822ac8c28..59a165a6d 100755 --- a/t/t5701-clone-local.sh +++ b/t/t5701-clone-local.sh @@ -63,4 +63,12 @@ test_expect_success 'Even without -l, local will make a hardlink' ' test 0 = $copied ' +test_expect_success 'local clone of repo with nonexistent ref in HEAD' ' + cd "$D" && + echo "ref: refs/heads/nonexistent" > a.git/HEAD && + git clone a d && + cd d && + git fetch && + test ! -e .git/refs/remotes/origin/HEAD' + test_done diff --git a/t/t5710-info-alternate.sh b/t/t5710-info-alternate.sh index 699df6ebd..910ccb4ff 100755 --- a/t/t5710-info-alternate.sh +++ b/t/t5710-info-alternate.sh @@ -53,14 +53,18 @@ git prune' cd "$base_dir" -test_expect_failure 'creating too deep nesting' \ +test_expect_success 'creating too deep nesting' \ 'git clone -l -s C D && git clone -l -s D E && git clone -l -s E F && git clone -l -s F G && -git clone -l -s G H && -cd H && -test_valid_repo' +git clone -l -s G H' + +test_expect_success 'invalidity of deepest repository' \ +'cd H && { + test_valid_repo + test $? -ne 0 +}' cd "$base_dir" @@ -83,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" @@ -97,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 000000000..be3d238d9 --- /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 ae3b6f283..79dc58b2c 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, @@ -139,4 +139,24 @@ test_expect_success 'binary files cannot be merged' ' grep "Cannot merge binary files" merge.err ' +sed -e "s/deerit.$/deerit;/" -e "s/me;$/me./" < new5.txt > new6.txt +sed -e "s/deerit.$/deerit,/" -e "s/me;$/me,/" < new5.txt > new7.txt + +test_expect_success 'MERGE_ZEALOUS simplifies non-conflicts' ' + + ! git merge-file -p new6.txt new5.txt new7.txt > output && + test 1 = $(grep ======= < output | wc -l) + +' + +sed -e 's/deerit./&\n\n\n\n/' -e "s/locavit,/locavit;/" < new6.txt > new8.txt +sed -e 's/deerit./&\n\n\n\n/' -e "s/locavit,/locavit --/" < new7.txt > new9.txt + +test_expect_success 'ZEALOUS_ALNUM' ' + + ! git merge-file -p new8.txt new5.txt new9.txt > merge.out && + test 1 = $(grep ======= < merge.out | wc -l) + +' + test_done diff --git a/t/t6024-recursive-merge.sh b/t/t6024-recursive-merge.sh index c154f03cf..149ea8543 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 950c2e9b6..6004deb43 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/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh index 2ba4b00e5..4908e878f 100755 --- a/t/t6030-bisect-porcelain.sh +++ b/t/t6030-bisect-porcelain.sh @@ -254,6 +254,18 @@ test_expect_success 'bisect run & skip: find first bad' ' grep "$HASH6 is first bad commit" my_bisect_log.txt ' +test_expect_success 'bisect starting with a detached HEAD' ' + + git bisect reset && + git checkout master^ && + HEAD=$(git rev-parse --verify HEAD) && + git bisect start && + test $HEAD = $(cat .git/BISECT_START) && + git bisect reset && + test $HEAD = $(git rev-parse --verify HEAD) + +' + # # test_done diff --git a/t/t6101-rev-parse-parents.sh b/t/t6101-rev-parse-parents.sh index 0724864e5..2328b6994 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 8a23aaf21..f46ec93c8 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 b730c900b..fa382c58d 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 68b2b9287..c8b4f65f3 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/t7003-filter-branch.sh b/t/t7003-filter-branch.sh index 5f60b22d8..868babc4b 100755 --- a/t/t7003-filter-branch.sh +++ b/t/t7003-filter-branch.sh @@ -165,4 +165,18 @@ test_expect_success '"map" works in commit filter' ' git rev-parse --verify master ' +test_expect_success 'Name needing quotes' ' + + git checkout -b rerere A && + mkdir foo && + name="れれれ" && + >foo/$name && + git add foo && + git commit -m "Adding a file" && + git filter-branch --tree-filter "rm -fr foo" && + ! git ls-files --error-unmatch "foo/$name" && + test $(git rev-parse --verify rerere) != $(git rev-parse --verify A) + +' + test_done diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh index df496a95f..75cd33bde 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 000000000..e809e0e2c --- /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 66d40430b..0d9874bfd 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 000000000..f136ee7bb --- /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 0fa94678a..724adef0b 100755 --- a/t/t7201-co.sh +++ b/t/t7201-co.sh @@ -207,6 +207,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 dfd118878..38403643a 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/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh index 4fe3a41f0..2ef85a869 100755 --- a/t/t7400-submodule-basic.sh +++ b/t/t7400-submodule-basic.sh @@ -13,11 +13,11 @@ subcommands of git-submodule. # # Test setup: -# -create a repository in directory lib +# -create a repository in directory init # -add a couple of files -# -add directory lib to 'superproject', this creates a DIRLINK entry +# -add directory init to 'superproject', this creates a DIRLINK entry # -add a couple of regular files to enable testing of submodule filtering -# -mv lib subrepo +# -mv init subrepo # -add an entry to .gitmodules for submodule 'example' # test_expect_success 'Prepare submodule testing' ' @@ -25,8 +25,8 @@ test_expect_success 'Prepare submodule testing' ' git-add t && git-commit -m "initial commit" && git branch initial HEAD && - mkdir lib && - cd lib && + mkdir init && + cd init && git init && echo a >a && git add a && @@ -41,10 +41,10 @@ test_expect_success 'Prepare submodule testing' ' cd .. && echo a >a && echo z >z && - git add a lib z && + git add a init z && git-commit -m "super commit 1" && - mv lib .subrepo && - GIT_CONFIG=.gitmodules git config submodule.example.url git://example.com/lib.git + mv init .subrepo && + GIT_CONFIG=.gitmodules git config submodule.example.url git://example.com/init.git ' test_expect_success 'status should fail for unmapped paths' ' @@ -52,7 +52,7 @@ test_expect_success 'status should fail for unmapped paths' ' then echo "[OOPS] submodule status succeeded" false - elif ! GIT_CONFIG=.gitmodules git config submodule.example.path lib + elif ! GIT_CONFIG=.gitmodules git config submodule.example.path init then echo "[OOPS] git config failed to update .gitmodules" false @@ -71,7 +71,7 @@ test_expect_success 'status should initially be "missing"' ' test_expect_success 'init should register submodule url in .git/config' ' git-submodule init && url=$(git config submodule.example.url) && - if test "$url" != "git://example.com/lib.git" + if test "$url" != "git://example.com/init.git" then echo "[OOPS] init succeeded but submodule url is wrong" false @@ -83,41 +83,41 @@ test_expect_success 'init should register submodule url in .git/config' ' ' test_expect_success 'update should fail when path is used by a file' ' - echo "hello" >lib && + echo "hello" >init && if git-submodule update then echo "[OOPS] update should have failed" false - elif test "$(cat lib)" != "hello" + elif test "$(cat init)" != "hello" then - echo "[OOPS] update failed but lib file was molested" + echo "[OOPS] update failed but init file was molested" false else - rm lib + rm init fi ' test_expect_success 'update should fail when path is used by a nonempty directory' ' - mkdir lib && - echo "hello" >lib/a && + mkdir init && + echo "hello" >init/a && if git-submodule update then echo "[OOPS] update should have failed" false - elif test "$(cat lib/a)" != "hello" + elif test "$(cat init/a)" != "hello" then - echo "[OOPS] update failed but lib/a was molested" + echo "[OOPS] update failed but init/a was molested" false else - rm lib/a + rm init/a fi ' test_expect_success 'update should work when path is an empty dir' ' - rm -rf lib && - mkdir lib && + rm -rf init && + mkdir init && git-submodule update && - head=$(cd lib && git rev-parse HEAD) && + head=$(cd init && git rev-parse HEAD) && if test -z "$head" then echo "[OOPS] Failed to obtain submodule head" @@ -134,7 +134,7 @@ test_expect_success 'status should be "up-to-date" after update' ' ' test_expect_success 'status should be "modified" after submodule commit' ' - cd lib && + cd init && echo b >b && git add b && git-commit -m "submodule commit 2" && @@ -157,8 +157,8 @@ test_expect_success 'git diff should report the SHA1 of the new submodule commit ' test_expect_success 'update should checkout rev1' ' - git-submodule update && - head=$(cd lib && git rev-parse HEAD) && + git-submodule update init && + head=$(cd init && git rev-parse HEAD) && if test -z "$head" then echo "[OOPS] submodule git rev-parse returned nothing" @@ -182,13 +182,13 @@ test_expect_success 'checkout superproject with subproject already present' ' test_expect_success 'apply submodule diff' ' git branch second && ( - cd lib && + cd init && echo s >s && git add s && git commit -m "change subproject" ) && - git update-index --add lib && - git-commit -m "change lib" && + git update-index --add init && + git-commit -m "change init" && git-format-patch -1 --stdout >P.diff && git checkout second && git apply --index P.diff && diff --git a/t/t7501-commit.sh b/t/t7501-commit.sh index d1a415a12..361886c3d 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" \ @@ -327,4 +327,22 @@ test_expect_success 'amend using the message from another commit' ' ' +test_expect_success 'amend using the message from a commit named with tag' ' + + git reset --hard && + test_tick && + git commit --allow-empty -m "old commit" && + old=$(git rev-parse --verify HEAD) && + git tag -a -m "tag on old" tagged-old HEAD && + test_tick && + git commit --allow-empty -m "new commit" && + new=$(git rev-parse --verify HEAD) && + test_tick && + git commit --allow-empty --amend -C tagged-old && + git show --pretty="format:%ad %s" "$old" >expected && + git show --pretty="format:%ad %s" HEAD >actual && + diff -u expected actual + +' + test_done diff --git a/t/t7502-commit.sh b/t/t7502-commit.sh index aaf497e6a..b780fddc0 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/t7502-status.sh b/t/t7502-status.sh index 9ce50cade..e00607490 100755 --- a/t/t7502-status.sh +++ b/t/t7502-status.sh @@ -17,6 +17,9 @@ test_expect_success 'setup' ' : > dir1/tracked && : > dir1/modified && git add . && + + git status >output && + test_tick && git commit -m initial && : > untracked && @@ -28,6 +31,12 @@ test_expect_success 'setup' ' git add dir2/added ' +test_expect_success 'status (1)' ' + + grep -e "use \"git rm --cached <file>\.\.\.\" to unstage" output + +' + cat > expect << \EOF # On branch master # Changes to be committed: @@ -51,7 +60,7 @@ cat > expect << \EOF # untracked EOF -test_expect_success 'status' ' +test_expect_success 'status (2)' ' git status > output && git diff expect output @@ -119,4 +128,25 @@ test_expect_success 'status without relative paths' ' ' +cat <<EOF >expect +# On branch master +# Changes to be committed: +# (use "git reset HEAD <file>..." to unstage) +# +# modified: dir1/modified +# +# Untracked files: +# (use "git add <file>..." to include in what will be committed) +# +# dir1/untracked +# dir2/ +# expect +# output +# untracked +EOF +test_expect_success 'status of partial commit excluding new file in index' ' + git status dir1/modified >output && + diff -u expect output +' + test_done diff --git a/t/t7503-pre-commit-hook.sh b/t/t7503-pre-commit-hook.sh index d787cac2f..2dd5a5e30 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 751b11300..eff36aaee 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 000000000..7ddec99a6 --- /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 4f6822f2c..cbbfa9cb4 100755 --- a/t/t9001-send-email.sh +++ b/t/t9001-send-email.sh @@ -8,23 +8,29 @@ test_expect_success \ 'prepare reference tree' \ 'echo "1A quick brown fox jumps over the" >file && echo "lazy dog" >>file && - git add file + git add file && GIT_AUTHOR_NAME="A" git commit -a -m "Initial."' test_expect_success \ 'Setup helper tool' \ '(echo "#!/bin/sh" echo shift + echo output=1 + echo "while test -f commandline\$output; do output=\$((\$output+1)); done" echo for a echo do echo " echo \"!\$a!\"" - echo "done >commandline" - echo "cat > msgtxt" - ) >fake.sendmail - chmod +x ./fake.sendmail - git add fake.sendmail + echo "done >commandline\$output" + echo "cat > msgtxt\$output" + ) >fake.sendmail && + chmod +x ./fake.sendmail && + git add fake.sendmail && GIT_AUTHOR_NAME="A" git commit -a -m "Second."' +clean_fake_sendmail() { + rm -f commandline* msgtxt* +} + test_expect_success 'Extract patches' ' patches=`git format-patch -n HEAD^1` ' @@ -39,7 +45,7 @@ cat >expected <<\EOF EOF test_expect_success \ 'Verify commandline' \ - 'diff commandline expected' + 'diff commandline1 expected' cat >expected-show-all-headers <<\EOF 0001-Second.patch @@ -82,7 +88,7 @@ z8=zzzzzzzz z64=$z8$z8$z8$z8$z8$z8$z8$z8 z512=$z64$z64$z64$z64$z64$z64$z64$z64 test_expect_success 'reject long lines' ' - rm -f commandline && + clean_fake_sendmail && cp $patches longline.patch && echo $z512$z512 >>longline.patch && ! git send-email \ @@ -95,7 +101,7 @@ test_expect_success 'reject long lines' ' ' test_expect_success 'no patch was sent' ' - ! test -e commandline + ! test -e commandline1 ' test_expect_success 'allow long lines with --no-validate' ' @@ -108,4 +114,56 @@ test_expect_success 'allow long lines with --no-validate' ' 2>errors ' +test_expect_success 'Invalid In-Reply-To' ' + clean_fake_sendmail && + 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: < *>" msgtxt1 +' + +test_expect_success 'Valid In-Reply-To when prompting' ' + clean_fake_sendmail && + (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: < *>" msgtxt1 +' + +test_expect_success 'setup fake editor' ' + (echo "#!/bin/sh" && + echo "echo fake edit >>\$1" + ) >fake-editor && + chmod +x fake-editor +' + +test_expect_success '--compose works' ' + clean_fake_sendmail && + echo y | \ + GIT_EDITOR=$(pwd)/fake-editor \ + GIT_SEND_EMAIL_NOTTY=1 \ + git send-email \ + --compose --subject foo \ + --from="Example <nobody@example.com>" \ + --to=nobody@example.com \ + --smtp-server="$(pwd)/fake.sendmail" \ + $patches \ + 2>errors +' + +test_expect_success 'first message is compose text' ' + grep "^fake edit" msgtxt1 +' + +test_expect_success 'second message is patch' ' + grep "Subject:.*Second" msgtxt2 +' + test_done diff --git a/t/t9100-git-svn-basic.sh b/t/t9100-git-svn-basic.sh index 614cf50d1..4e24ab3a7 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 79b7968ea..f74ab1269 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 745254665..ca8a00ed0 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 a15222ced..58c59ed5a 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 0595041af..cceedbb2b 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 641303e0a..0a20971eb 100755 --- a/t/t9400-git-cvsserver-server.sh +++ b/t/t9400-git-cvsserver-server.sh @@ -33,19 +33,28 @@ CVS_SERVER=git-cvsserver export CVSROOT CVS_SERVER rm -rf "$CVSWORK" "$SERVERDIR" -echo >empty && +test_expect_success 'setup' ' + echo >empty && git add empty && git commit -q -m "First Commit" && + mkdir secondroot && + ( cd secondroot && + git init && + touch secondrootfile && + git add secondrootfile && + git commit -m "second root") && + git pull secondroot master && git clone -q --local --bare "$WORKDIR/.git" "$SERVERDIR" >/dev/null 2>&1 && GIT_DIR="$SERVERDIR" git config --bool gitcvs.enabled true && - GIT_DIR="$SERVERDIR" git config gitcvs.logfile "$SERVERDIR/gitcvs.log" || - exit 1 + GIT_DIR="$SERVERDIR" git config gitcvs.logfile "$SERVERDIR/gitcvs.log" +' # note that cvs doesn't accept absolute pathnames # as argument to co -d test_expect_success 'basic checkout' \ 'GIT_CONFIG="$git_config" cvs -Q co -d cvswork master && - test "$(echo $(grep -v ^D cvswork/CVS/Entries|cut -d/ -f2,3,5))" = "empty/1.1/"' + test "$(echo $(grep -v ^D cvswork/CVS/Entries|cut -d/ -f2,3,5 | head -n 1))" = "empty/1.1/" + test "$(echo $(grep -v ^D cvswork/CVS/Entries|cut -d/ -f2,3,5 | tail -n 1))" = "secondrootfile/1.1/"' #------------------------ # PSERVER AUTHENTICATION @@ -147,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 @@ -170,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 @@ -179,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 && @@ -281,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 90b6844d0..83889c4f4 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 "" @@ -257,7 +270,7 @@ test_expect_code () { echo >&3 "" } -# Most tests can use the created repository, but some amy need to create more. +# Most tests can use the created repository, but some may need to create more. # Usage: test_create_repo <directory> test_create_repo () { test "$#" = 1 || @@ -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 c5cdb9d7e..4ef86eb24 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 000000000..ff0f42a1d --- /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/test-parse-options.c b/test-parse-options.c index 4d3e2ec39..eed8a02c6 100644 --- a/test-parse-options.c +++ b/test-parse-options.c @@ -19,6 +19,7 @@ int main(int argc, const char **argv) OPT_STRING('s', "string", &string, "string", "get a string"), OPT_STRING(0, "string2", &string, "str", "get another string"), OPT_STRING(0, "st", &string, "st", "get another string (pervert ordering)"), + OPT_STRING('o', NULL, &string, "str", "get another string"), OPT_END(), }; int i; diff --git a/thread-utils.c b/thread-utils.c new file mode 100644 index 000000000..55e7e2904 --- /dev/null +++ b/thread-utils.c @@ -0,0 +1,48 @@ +#include "cache.h" + +#ifdef _WIN32 +# define WIN32_LEAN_AND_MEAN +# include <windows.h> +#elif defined(hpux) || defined(__hpux) || defined(_hpux) +# include <sys/pstat.h> +#endif + +/* + * By doing this in two steps we can at least get + * the function to be somewhat coherent, even + * with this disgusting nest of #ifdefs. + */ +#ifndef _SC_NPROCESSORS_ONLN +# ifdef _SC_NPROC_ONLN +# define _SC_NPROCESSORS_ONLN _SC_NPROC_ONLN +# elif defined _SC_CRAY_NCPU +# define _SC_NPROCESSORS_ONLN _SC_CRAY_NCPU +# endif +#endif + +int online_cpus(void) +{ +#ifdef _SC_NPROCESSORS_ONLN + long ncpus; +#endif + +#ifdef _WIN32 + SYSTEM_INFO info; + GetSystemInfo(&info); + + if ((int)info.dwNumberOfProcessors > 0) + return (int)info.dwNumberOfProcessors; +#elif defined(hpux) || defined(__hpux) || defined(_hpux) + struct pst_dynamic psd; + + if (!pstat_getdynamic(&psd, sizeof(psd), (size_t)1, 0)) + return (int)psd.psd_proc_cnt; +#endif + +#ifdef _SC_NPROCESSORS_ONLN + if ((ncpus = (long)sysconf(_SC_NPROCESSORS_ONLN)) > 0) + return (int)ncpus; +#endif + + return 1; +} diff --git a/thread-utils.h b/thread-utils.h new file mode 100644 index 000000000..cce4b77bd --- /dev/null +++ b/thread-utils.h @@ -0,0 +1,6 @@ +#ifndef THREAD_COMPAT_H +#define THREAD_COMPAT_H + +extern int online_cpus(void); + +#endif /* THREAD_COMPAT_H */ diff --git a/transport.c b/transport.c index babaa2139..397983d11 100644 --- a/transport.c +++ b/transport.c @@ -441,11 +441,12 @@ static struct ref *get_refs_via_curl(struct transport *transport) struct ref *ref = NULL; struct ref *last_ref = NULL; + if (!transport->data) + transport->data = get_http_walker(transport->url); + refs_url = xmalloc(strlen(transport->url) + 11); sprintf(refs_url, "%s/info/refs", transport->url); - http_init(); - slot = get_active_slot(); slot->results = &results; curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer); @@ -473,8 +474,6 @@ static struct ref *get_refs_via_curl(struct transport *transport) return NULL; } - http_cleanup(); - data = buffer.buf; start = NULL; mid = data; @@ -563,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; }; @@ -593,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; } @@ -617,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; @@ -632,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); } @@ -661,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; } @@ -721,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; diff --git a/unpack-trees.c b/unpack-trees.c index 470fa02e0..3e448d897 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -318,7 +318,7 @@ static void check_updates(struct unpack_trees_options *o) } progress = start_progress_delay("Checking out files", - total, 50, 2); + total, 50, 1); cnt = 0; } @@ -554,8 +554,9 @@ static int 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. @@ -624,7 +625,7 @@ static int merged_entry(struct cache_entry *merge, struct cache_entry *old, * a match. */ if (same(old, merge)) { - memcpy(merge, old, offsetof(struct cache_entry, name)); + copy_cache_entry(merge, old); } else { if (verify_uptodate(old, o)) return -1; diff --git a/upload-pack.c b/upload-pack.c index 7e0431102..b26d05331 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; } @@ -620,6 +623,9 @@ int main(int argc, char **argv) if (i != argc-1) usage(upload_pack_usage); + + setup_path(NULL); + dir = argv[i]; if (!enter_repo(dir, strict)) @@ -14,6 +14,7 @@ static struct whitespace_rule { { "trailing-space", WS_TRAILING_SPACE }, { "space-before-tab", WS_SPACE_BEFORE_TAB }, { "indent-with-non-tab", WS_INDENT_WITH_NON_TAB }, + { "cr-at-eol", WS_CR_AT_EOL }, }; unsigned parse_whitespace_rule(const char *string) @@ -124,6 +125,7 @@ unsigned check_and_emit_line(const char *line, int len, unsigned ws_rule, int written = 0; int trailing_whitespace = -1; int trailing_newline = 0; + int trailing_carriage_return = 0; int i; /* Logic is simpler if we temporarily ignore the trailing newline. */ @@ -131,6 +133,11 @@ unsigned check_and_emit_line(const char *line, int len, unsigned ws_rule, trailing_newline = 1; len--; } + if ((ws_rule & WS_CR_AT_EOL) && + len > 0 && line[len - 1] == '\r') { + trailing_carriage_return = 1; + len--; + } /* Check for trailing whitespace. */ if (ws_rule & WS_TRAILING_SPACE) { @@ -176,8 +183,10 @@ unsigned check_and_emit_line(const char *line, int len, unsigned ws_rule, } if (stream) { - /* Now the rest of the line starts at written. - * The non-highlighted part ends at trailing_whitespace. */ + /* + * Now the rest of the line starts at "written". + * The non-highlighted part ends at "trailing_whitespace". + */ if (trailing_whitespace == -1) trailing_whitespace = len; @@ -196,8 +205,114 @@ unsigned check_and_emit_line(const char *line, int len, unsigned ws_rule, len - trailing_whitespace, 1, stream); fputs(reset, stream); } + if (trailing_carriage_return) + fputc('\r', stream); if (trailing_newline) fputc('\n', stream); } return result; } + +/* Copy the line to the buffer while fixing whitespaces */ +int ws_fix_copy(char *dst, const char *src, int len, unsigned ws_rule, int *error_count) +{ + /* + * len is number of bytes to be copied from src, starting + * at src. Typically src[len-1] is '\n', unless this is + * the incomplete last line. + */ + int i; + int add_nl_to_tail = 0; + int add_cr_to_tail = 0; + int fixed = 0; + int last_tab_in_indent = -1; + int last_space_in_indent = -1; + int need_fix_leading_space = 0; + char *buf; + + /* + * Strip trailing whitespace + */ + if ((ws_rule & WS_TRAILING_SPACE) && + (2 <= len && isspace(src[len-2]))) { + if (src[len - 1] == '\n') { + add_nl_to_tail = 1; + len--; + if (1 < len && src[len - 1] == '\r') { + add_cr_to_tail = !!(ws_rule & WS_CR_AT_EOL); + len--; + } + } + if (0 < len && isspace(src[len - 1])) { + while (0 < len && isspace(src[len-1])) + len--; + fixed = 1; + } + } + + /* + * Check leading whitespaces (indent) + */ + for (i = 0; i < len; i++) { + char ch = src[i]; + if (ch == '\t') { + last_tab_in_indent = i; + if ((ws_rule & WS_SPACE_BEFORE_TAB) && + 0 <= last_space_in_indent) + need_fix_leading_space = 1; + } else if (ch == ' ') { + last_space_in_indent = i; + if ((ws_rule & WS_INDENT_WITH_NON_TAB) && + 8 <= i - last_tab_in_indent) + need_fix_leading_space = 1; + } else + break; + } + + buf = dst; + if (need_fix_leading_space) { + /* Process indent ourselves */ + int consecutive_spaces = 0; + int last = last_tab_in_indent + 1; + + if (ws_rule & WS_INDENT_WITH_NON_TAB) { + /* have "last" point at one past the indent */ + if (last_tab_in_indent < last_space_in_indent) + last = last_space_in_indent + 1; + else + last = last_tab_in_indent + 1; + } + + /* + * between src[0..last-1], strip the funny spaces, + * updating them to tab as needed. + */ + for (i = 0; i < last; i++) { + char ch = src[i]; + if (ch != ' ') { + consecutive_spaces = 0; + *dst++ = ch; + } else { + consecutive_spaces++; + if (consecutive_spaces == 8) { + *dst++ = '\t'; + consecutive_spaces = 0; + } + } + } + while (0 < consecutive_spaces--) + *dst++ = ' '; + len -= last; + src += last; + fixed = 1; + } + + memcpy(dst, src, len); + if (add_cr_to_tail) + dst[len++] = '\r'; + if (add_nl_to_tail) + dst[len++] = '\n'; + if (fixed && error_count) + (*error_count)++; + return dst + len - buf; +} diff --git a/wt-status.c b/wt-status.c index 27b946d55..32d780af1 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) @@ -60,7 +60,7 @@ static void wt_status_print_cached_header(struct wt_status *s) { const char *c = color(WT_STATUS_HEADER); color_fprintf_ln(s->fp, c, "# Changes to be committed:"); - if (s->reference) { + if (!s->is_initial) { color_fprintf_ln(s->fp, c, "# (use \"git reset %s <file>...\" to unstage)", s->reference); } else { color_fprintf_ln(s->fp, c, "# (use \"git rm --cached <file>...\" to unstage)"); @@ -392,6 +392,8 @@ int git_status_config(const char *k, const char *v) } if (!prefixcmp(k, "status.color.") || !prefixcmp(k, "color.status.")) { int slot = parse_status_slot(k, 13); + if (!v) + return config_error_nonbool(k); color_parse(v, k, wt_status_colors[slot]); return 0; } @@ -399,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); } diff --git a/xdiff/xdiff.h b/xdiff/xdiff.h index c00ddaa6e..413082e1f 100644 --- a/xdiff/xdiff.h +++ b/xdiff/xdiff.h @@ -53,6 +53,7 @@ extern "C" { #define XDL_MERGE_MINIMAL 0 #define XDL_MERGE_EAGER 1 #define XDL_MERGE_ZEALOUS 2 +#define XDL_MERGE_ZEALOUS_ALNUM 3 typedef struct s_mmfile { char *ptr; diff --git a/xdiff/xmerge.c b/xdiff/xmerge.c index b83b3348c..82b3573e7 100644 --- a/xdiff/xmerge.c +++ b/xdiff/xmerge.c @@ -248,10 +248,76 @@ static int xdl_refine_conflicts(xdfenv_t *xe1, xdfenv_t *xe2, xdmerge_t *m, return 0; } +static int line_contains_alnum(const char *ptr, long size) +{ + while (size--) + if (isalnum(*(ptr++))) + return 1; + return 0; +} + +static int lines_contain_alnum(xdfenv_t *xe, int i, int chg) +{ + for (; chg; chg--, i++) + if (line_contains_alnum(xe->xdf2.recs[i]->ptr, + xe->xdf2.recs[i]->size)) + return 1; + return 0; +} + +/* + * This function merges m and m->next, marking everything between those hunks + * as conflicting, too. + */ +static void xdl_merge_two_conflicts(xdmerge_t *m) +{ + xdmerge_t *next_m = m->next; + m->chg1 = next_m->i1 + next_m->chg1 - m->i1; + m->chg2 = next_m->i2 + next_m->chg2 - m->i2; + m->next = next_m->next; + free(next_m); +} + +/* + * If there are less than 3 non-conflicting lines between conflicts, + * it appears simpler -- because it takes up less (or as many) lines -- + * if the lines are moved into the conflicts. + */ +static int xdl_simplify_non_conflicts(xdfenv_t *xe1, xdmerge_t *m, + int simplify_if_no_alnum) +{ + int result = 0; + + if (!m) + return result; + for (;;) { + xdmerge_t *next_m = m->next; + int begin, end; + + if (!next_m) + return result; + + begin = m->i1 + m->chg1; + end = next_m->i1; + + if (m->mode != 0 || next_m->mode != 0 || + (end - begin > 3 && + (!simplify_if_no_alnum || + lines_contain_alnum(xe1, begin, end - begin)))) { + m = next_m; + } else { + result++; + xdl_merge_two_conflicts(m); + } + } +} + /* * level == 0: mark all overlapping changes as conflict * level == 1: mark overlapping changes as conflict only if not identical * level == 2: analyze non-identical changes for minimal conflict set + * level == 3: analyze non-identical changes for minimal conflict set, but + * treat hunks not containing any letter or number as conflicting * * returns < 0 on error, == 0 for no conflicts, else number of conflicts */ @@ -355,7 +421,9 @@ static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, const char *name1, if (!changes) changes = c; /* refine conflicts */ - if (level > 1 && xdl_refine_conflicts(xe1, xe2, changes, xpp) < 0) { + if (level > 1 && + (xdl_refine_conflicts(xe1, xe2, changes, xpp) < 0 || + xdl_simplify_non_conflicts(xe1, changes, level > 2) < 0)) { xdl_cleanup_merge(changes); return -1; } |