diff options
103 files changed, 2111 insertions, 602 deletions
diff --git a/Documentation/RelNotes-1.7.1.1.txt b/Documentation/RelNotes-1.7.1.1.txt new file mode 100644 index 0000000000..e0386a43f0 --- /dev/null +++ b/Documentation/RelNotes-1.7.1.1.txt @@ -0,0 +1,67 @@ +Git v1.7.1.1 Release Notes (draft) +================================== + +Fixes since v1.7.1 +------------------ + + * Authentication over http transport can now be made lazily, in that the + request can first go to a URL without username, get a 401 response and + then the client will ask for the username to use. + + * We used to mistakenly think "../work" is a subdirectory of the current + directory when we are in "../work-xyz". + + * The attribute mechanism now allows an entry that uses an attribute + macro that set/unset one attribute, immediately followed by an + overriding setting; this makes attribute macros much easier to use. + + * We didn't recognize timezone "Z" as a synonym for "UTC" (75b37e70). + + * "git am -3" did not show diagnosis when the patch in the message was corrupt. + + * After "git apply --whitespace=fix" removed trailing blank lines in an + patch in a patch series, it failed to apply later patches that depend + on the presense of such blank lines. + + * "git bundle --stdin" segfaulted. + + * "git checkout" and "git rebase" overwrote paths that are marked "assume + unchanged". + + * "git describe" did not tie-break tags that point at the same commit + correctly; newer ones are preferred by paying attention to the + tagger date now. + + * "git diff" used to tell underlying xdiff machinery to work very hard to + minimize the output, but this often was spending too many extra cycles + for very little gain. + + * "git diff --color" did not paint extended diff headers per line + (i.e. the coloring escape sequence didn't end at the end of line), + which confused "less -R". + + * "git fetch" over HTTP verifies the downloaded packfiles more robustly. + + * The memory usage by "git index-pack" (run during "git fetch" and "git + push") got leaner. + + * "GIT_DIR=foo.git git init --bare bar.git" created foo.git instead of bar.git. + + * "git log --abbrev=$num --format='%h' ignored --abbrev=$num. + + * "git pull" accepted "--dry-run", gave it to underlying "git fetch" but + ignored the option itself, resulting in a bogus attempt to merge + unrelated commit. + + * "git send-email" lacked a way to specify the domainname used in the + EHLO/HELO exchange, causing rejected connection from picky servers. + It learned --smtp-domain option to solve this issue. + + * "git stash" incorrectly lost paths in the working tree that were + previously removed from the index. + + * "git status" stopped refreshing the index by mistake in 1.7.1. + + * "git status" showed excess "hints" even when advice.statusHints is set to false. + +And other minor fixes and documentation updates. diff --git a/Documentation/SubmittingPatches b/Documentation/SubmittingPatches index 84248daa58..eb53e0636e 100644 --- a/Documentation/SubmittingPatches +++ b/Documentation/SubmittingPatches @@ -54,6 +54,34 @@ But the patch submission requirements are a lot more relaxed here on the technical/contents front, because the core GIT is thousand times smaller ;-). So here is only the relevant bits. +(0) Decide what to base your work on. + +In general, always base your work on the oldest branch that your +change is relevant to. + + - A bugfix should be based on 'maint' in general. If the bug is not + present in 'maint', base it on 'master'. For a bug that's not yet + in 'master', find the topic that introduces the regression, and + base your work on the tip of the topic. + + - A new feature should be based on 'master' in general. If the new + feature depends on a topic that is in 'pu', but not in 'master', + base your work on the tip of that topic. + + - Corrections and enhancements to a topic not yet in 'master' should + be based on the tip of that topic. If the topic has not been merged + to 'next', it's alright to add a note to squash minor corrections + into the series. + + - In the exceptional case that a new feature depends on several topics + not in 'master', start working on 'next' or 'pu' privately and send + out patches for discussion. Before the final merge, you may have to + wait until some of the dependent topics graduate to 'master', and + rebase your work. + +To find the tip of a topic branch, run "git log --first-parent +master..pu" and look for the merge commit. The second parent of this +commit is the tip of the topic branch. (1) Make separate commits for logically separate changes. @@ -171,17 +199,16 @@ patch, format it as "multipart/signed", not a text/plain message that starts with '-----BEGIN PGP SIGNED MESSAGE-----'. That is not a text/plain, it's something else. -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 for -inclusion. - -Also note that your maintainer does not actively involve himself in -maintaining what are in contrib/ hierarchy. When you send fixes and -enhancements to them, do not forget to "cc: " the person who primarily -worked on that hierarchy in contrib/. +Unless your patch is a very trivial and an obviously correct one, +first send it with "To:" set to the mailing list, with "cc:" listing +people who are involved in the area you are touching (the output from +"git blame $path" and "git shortlog --no-merges $path" would help to +identify them), to solicit comments and reviews. After the list +reached a consensus that it is a good idea to apply the patch, re-send +it with "To:" set to the maintainer and optionally "cc:" the list for +inclusion. Do not forget to add trailers such as "Acked-by:", +"Reviewed-by:" and "Tested-by:" after your "Signed-off-by:" line as +necessary. (4) Sign your work diff --git a/Documentation/blame-options.txt b/Documentation/blame-options.txt index d8205691c6..16e3c68576 100644 --- a/Documentation/blame-options.txt +++ b/Documentation/blame-options.txt @@ -90,9 +90,9 @@ of lines before or after the line given by <start>. running extra passes of inspection. + <num> is optional but it is the lower bound on the number of -alphanumeric characters that git must detect as moving +alphanumeric characters that git must detect as moving/copying within a file for it to associate those lines with the parent -commit. +commit. The default value is 20. -C|<num>|:: In addition to `-M`, detect lines moved or copied from other @@ -105,9 +105,11 @@ commit. looks for copies from other files in any commit. + <num> is optional but it is the lower bound on the number of -alphanumeric characters that git must detect as moving +alphanumeric characters that git must detect as moving/copying between files for it to associate those lines with the parent -commit. +commit. And the default value is 40. If there are more than one +`-C` options given, the <num> argument of the last `-C` will +take effect. -h:: --help:: diff --git a/Documentation/config.txt b/Documentation/config.txt index bcf3705759..ae174c99db 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -518,18 +518,12 @@ check that makes sure that existing object files will not get overwritten. core.notesRef:: When showing commit messages, also show notes which are stored in - the given ref. This ref is expected to contain files named - after the full SHA-1 of the commit they annotate. The ref - must be fully qualified. + the given ref. The ref must be fully qualified. If the given + ref does not exist, it is not an error but means that no + notes should be printed. + -If such a file exists in the given ref, the referenced blob is read, and -appended to the commit message, separated by a "Notes (<refname>):" -line (shortened to "Notes:" in the case of "refs/notes/commits"). If the -given ref itself does not exist, it is not an error, but means that no -notes should be printed. -+ -This setting defaults to "refs/notes/commits", and can be overridden by -the `GIT_NOTES_REF` environment variable. +This setting defaults to "refs/notes/commits", and it can be overridden by +the 'GIT_NOTES_REF' environment variable. See linkgit:git-notes[1]. core.sparseCheckout:: Enable "sparse checkout" feature. See section "Sparse checkout" in @@ -1642,6 +1636,7 @@ sendemail.smtppass:: sendemail.suppresscc:: sendemail.suppressfrom:: sendemail.to:: +sendemail.smtpdomain:: sendemail.smtpserver:: sendemail.smtpserverport:: sendemail.smtpuser:: diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index c9c6c2b1cb..3070dddfe2 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -177,7 +177,14 @@ endif::git-format-patch[] Break complete rewrite changes into pairs of delete and create. -M:: +ifndef::git-log[] Detect renames. +endif::git-log[] +ifdef::git-log[] + If generating diffs, detect and report renames for each commit. + For following files across renames while traversing history, see + `--follow`. +endif::git-log[] -C:: Detect copies as well as renames. See also `--find-copies-harder`. diff --git a/Documentation/git-add.txt b/Documentation/git-add.txt index 51cbeb7032..74741a42f4 100644 --- a/Documentation/git-add.txt +++ b/Documentation/git-add.txt @@ -266,9 +266,9 @@ patch:: y - stage this hunk n - do not stage this hunk - q - quit, do not stage this hunk nor any of the remaining ones - a - stage this and all the remaining hunks in the file - d - do not stage this hunk nor any of the remaining hunks in the file + q - quit; do not stage this hunk nor any of the remaining ones + a - stage this hunk and all later hunks in the file + d - do not stage this hunk nor any of the later hunks in the file g - select a hunk to go to / - search for a hunk matching the given regex j - leave this hunk undecided, see next undecided hunk diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt index a3a87fa7fd..7146288ad6 100644 --- a/Documentation/git-checkout.txt +++ b/Documentation/git-checkout.txt @@ -15,33 +15,41 @@ SYNOPSIS DESCRIPTION ----------- - -When <paths> are not given, this command switches branches by -updating the index, working tree, and HEAD to reflect the specified +Updates files in the working tree to match the version in the index +or the specified tree. If no paths are given, 'git checkout' will +also update `HEAD` to set the specified branch as the current branch. -If `-b` is given, a new branch is created and checked out, as if -linkgit:git-branch[1] were called; in this case you can -use the --track or --no-track options, which will be passed to `git -branch`. As a convenience, --track without `-b` implies branch -creation; see the description of --track below. - -When <paths> or --patch are given, this command does *not* switch -branches. It updates the named paths in the working tree from -the index file, or from a named <tree-ish> (most often a commit). In -this case, the `-b` and `--track` options are meaningless and giving -either of them results in an error. The <tree-ish> argument can be -used to specify a specific tree-ish (i.e. commit, tag or tree) -to update the index for the given paths before updating the -working tree. - -The index may contain unmerged entries after a failed merge. By -default, if you try to check out such an entry from the index, the +'git checkout' [<branch>]:: +'git checkout' -b <new branch> [<start point>]:: + + This form switches branches by updating the index, working + tree, and HEAD to reflect the specified branch. ++ +If `-b` is given, a new branch is created as if linkgit:git-branch[1] +were called and then checked out; in this case you can +use the `--track` or `--no-track` options, which will be passed to +'git branch'. As a convenience, `--track` without `-b` implies branch +creation; see the description of `--track` below. + +'git checkout' [--patch] [<tree-ish>] [--] <pathspec>...:: + + When <paths> or `--patch` are given, 'git checkout' *not* switch + branches. It updates the named paths in the working tree from + the index file or from a named <tree-ish> (most often a commit). In + this case, the `-b` and `--track` options are meaningless and giving + either of them results in an error. The <tree-ish> argument can be + used to specify a specific tree-ish (i.e. commit, tag or tree) + to update the index for the given paths before updating the + working tree. ++ +The index may contain unmerged entries because of a previous failed merge. +By default, if you try to check out such an entry from the index, the checkout operation will fail and nothing will be checked out. -Using -f will ignore these unmerged entries. The contents from a +Using `-f` will ignore these unmerged entries. The contents from a specific side of the merge can be checked out of the index by -using --ours or --theirs. With -m, changes made to the working tree -file can be discarded to recreate the original conflicted merge result. +using `--ours` or `--theirs`. With `-m`, changes made to the working tree +file can be discarded to re-create the original conflicted merge result. OPTIONS ------- diff --git a/Documentation/git-log.txt b/Documentation/git-log.txt index fb184ba186..d7f6a9cc3e 100644 --- a/Documentation/git-log.txt +++ b/Documentation/git-log.txt @@ -132,6 +132,48 @@ Discussion include::i18n.txt[] +Configuration +------------- + +See linkgit:git-config[1] for core variables and linkgit:git-diff[1] +for settings related to diff generation. + +format.pretty:: + Default for the `--format` option. (See "PRETTY FORMATS" above.) + Defaults to "medium". + +i18n.logOutputEncoding:: + Encoding to use when displaying logs. (See "Discussion", above.) + Defaults to the value of `i18n.commitEncoding` if set, UTF-8 + otherwise. + +log.date:: + Default format for human-readable dates. (Compare the + `--date` option.) Defaults to "default", which means to write + dates like `Sat May 8 19:35:34 2010 -0500`. + +log.showroot:: + If `false`, 'git log' and related commands will not treat the + initial commit as a big creation event. Any root commits in + `git log -p` output would be shown without a diff attached. + The default is `true`. + +mailmap.file:: + See linkgit:git-shortlog[1]. + +notes.displayRef:: + Which refs, in addition to the default set by `core.notesRef` + or 'GIT_NOTES_REF', to read notes from when showing commit + messages with the 'log' family of commands. See + linkgit:git-notes[1]. ++ +May be an unabbreviated ref name or a glob and may be specified +multiple times. A warning will be issued for refs that do not exist, +but a glob that does not match any refs is silently ignored. ++ +This setting can be disabled by the `--no-standard-notes` option, +overridden by the 'GIT_NOTES_DISPLAY_REF' environment variable, +and supplemented by the `--show-notes` option. Author ------ diff --git a/Documentation/git-mailinfo.txt b/Documentation/git-mailinfo.txt index e3d58cbac3..3ea5aad56c 100644 --- a/Documentation/git-mailinfo.txt +++ b/Documentation/git-mailinfo.txt @@ -40,16 +40,16 @@ OPTIONS -u:: The commit log message, author name and author email are taken from the e-mail, and after minimally decoding MIME - transfer encoding, re-coded in UTF-8 by transliterating + transfer encoding, re-coded in the charset specified by + i18n.commitencoding (defaulting to UTF-8) by transliterating them. This used to be optional but now it is the default. + Note that the patch is always used as-is without charset conversion, even with this flag. --encoding=<encoding>:: - Similar to -u but if the local convention is different - from what is specified by i18n.commitencoding, this flag - can be used to override it. + Similar to -u. But when re-coding, the charset specified here is + used instead of the one specified by i18n.commitencoding or UTF-8. -n:: Disable all charset re-coding of the metadata. diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt index c2325ef90e..84043cc5b2 100644 --- a/Documentation/git-merge.txt +++ b/Documentation/git-merge.txt @@ -58,7 +58,12 @@ include::merge-options.txt[] -m <msg>:: Set the commit message to be used for the merge commit (in - case one is created). The 'git fmt-merge-msg' command can be + case one is created). + + If `--log` is specified, a shortlog of the commits being merged + will be appended to the specified message. + + The 'git fmt-merge-msg' command can be used to give a good default for automated 'git merge' invocations. diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt index 4e5113b837..de63ef0745 100644 --- a/Documentation/git-notes.txt +++ b/Documentation/git-notes.txt @@ -3,7 +3,7 @@ git-notes(1) NAME ---- -git-notes - Add/inspect object notes +git-notes - Add or inspect object notes SYNOPSIS -------- @@ -20,24 +20,26 @@ SYNOPSIS DESCRIPTION ----------- -This command allows you to add/remove notes to/from objects, without -changing the objects themselves. +Adds, removes, or reads notes attached to objects, without touching +the objects themselves. -A typical use of notes is to extend a commit message without having -to change the commit itself. Such commit notes can be shown by `git log` -along with the original commit message. To discern these notes from the +By default, notes are saved to and read from `refs/notes/commits`, but +this default can be overridden. See the OPTIONS, CONFIGURATION, and +ENVIRONMENT sections below. If this ref does not exist, it will be +quietly created when it is first needed to store a note. + +A typical use of notes is to supplement a commit message without +changing the commit itself. Notes can be shown by 'git log' along with +the original commit message. To distinguish these notes from the message stored in the commit object, the notes are indented like the message, after an unindented line saying "Notes (<refname>):" (or -"Notes:" for the default setting). +"Notes:" for `refs/notes/commits`). -This command always manipulates the notes specified in "core.notesRef" -(see linkgit:git-config[1]), which can be overridden by GIT_NOTES_REF. -To change which notes are shown by 'git-log', see the -"notes.displayRef" configuration. +To change which notes are shown by 'git log', see the +"notes.displayRef" configuration in linkgit:git-log[1]. -See the description of "notes.rewrite.<command>" in -linkgit:git-config[1] for a way of carrying your notes across commands -that rewrite commits. +See the "notes.rewrite.<command>" configuration for a way to carry +notes across commands that rewrite commits. SUBCOMMANDS @@ -101,15 +103,20 @@ OPTIONS Use the given note message (instead of prompting). If multiple `-m` options are given, their values are concatenated as separate paragraphs. + Lines starting with `#` and empty lines other than a + single line between paragraphs will be stripped out. -F <file>:: --file=<file>:: Take the note message from the given file. Use '-' to read the note message from the standard input. + Lines starting with `#` and empty lines other than a + single line between paragraphs will be stripped out. -C <object>:: --reuse-message=<object>:: - Reuse the note message from the given note object. + Take the note message from the given blob object (for + example, another note). -c <object>:: --reedit-message=<object>:: @@ -117,22 +124,144 @@ OPTIONS the user can further edit the note message. --ref <ref>:: - Manipulate the notes tree in <ref>. This overrides both - GIT_NOTES_REF and the "core.notesRef" configuration. The ref + Manipulate the notes tree in <ref>. This overrides + 'GIT_NOTES_REF' and the "core.notesRef" configuration. The ref is taken to be in `refs/notes/` if it is not qualified. -NOTES ------ +DISCUSSION +---------- + +Commit notes are blobs containing extra information about an object +(usually information to supplement a commit's message). These blobs +are taken from notes refs. A notes ref is usually a branch which +contains "files" whose paths are the object names for the objects +they describe, with some directory separators included for performance +reasons footnote:[Permitted pathnames have the form +'ab'`/`'cd'`/`'ef'`/`'...'`/`'abcdef...': a sequence of directory +names of two hexadecimal digits each followed by a filename with the +rest of the object ID.]. Every notes change creates a new commit at the specified notes ref. You can therefore inspect the history of the notes by invoking, e.g., -`git log -p notes/commits`. +`git log -p notes/commits`. Currently the commit message only records +which operation triggered the update, and the commit authorship is +determined according to the usual rules (see linkgit:git-commit[1]). +These details may change in the future. + +It is also permitted for a notes ref to point directly to a tree +object, in which case the history of the notes can be read with +`git log -p -g <refname>`. + + +EXAMPLES +-------- + +You can use notes to add annotations with information that was not +available at the time a commit was written. + +------------ +$ git notes add -m 'Tested-by: Johannes Sixt <j6t@kdbg.org>' 72a144e2 +$ git show -s 72a144e +[...] + Signed-off-by: Junio C Hamano <gitster@pobox.com> + +Notes: + Tested-by: Johannes Sixt <j6t@kdbg.org> +------------ + +In principle, a note is a regular Git blob, and any kind of +(non-)format is accepted. You can binary-safely create notes from +arbitrary files using 'git hash-object': + +------------ +$ cc *.c +$ blob=$(git hash-object -w a.out) +$ git notes --ref=built add -C "$blob" HEAD +------------ + +Of course, it doesn't make much sense to display non-text-format notes +with 'git log', so if you use such notes, you'll probably need to write +some special-purpose tools to do something useful with them. + + +CONFIGURATION +------------- + +core.notesRef:: + Notes ref to read and manipulate instead of + `refs/notes/commits`. Must be an unabbreviated ref name. + This setting can be overridden through the environment and + command line. -Currently the commit message only records which operation triggered -the update, and the commit authorship is determined according to the -usual rules (see linkgit:git-commit[1]). These details may change in -the future. +notes.displayRef:: + Which ref (or refs, if a glob or specified more than once), in + addition to the default set by `core.notesRef` or + 'GIT_NOTES_REF', to read notes from when showing commit + messages with the 'git log' family of commands. + This setting can be overridden on the command line or by the + 'GIT_NOTES_DISPLAY_REF' environment variable. + See linkgit:git-log[1]. + +notes.rewrite.<command>:: + When rewriting commits with <command> (currently `amend` or + `rebase`), if this variable is `false`, git will not copy + notes from the original to the rewritten commit. Defaults to + `true`. See also "`notes.rewriteRef`" below. ++ +This setting can be overridden by the 'GIT_NOTES_REWRITE_REF' +environment variable. + +notes.rewriteMode:: + When copying notes during a rewrite, what to do if the target + commit already has a note. Must be one of `overwrite`, + `concatenate`, and `ignore`. Defaults to `concatenate`. ++ +This setting can be overridden with the `GIT_NOTES_REWRITE_MODE` +environment variable. + +notes.rewriteRef:: + When copying notes during a rewrite, specifies the (fully + qualified) ref whose notes should be copied. May be a glob, + in which case notes in all matching refs will be copied. You + may also specify this configuration several times. ++ +Does not have a default value; you must configure this variable to +enable note rewriting. ++ +Can be overridden with the 'GIT_NOTES_REWRITE_REF' environment variable. + + +ENVIRONMENT +----------- + +'GIT_NOTES_REF':: + Which ref to manipulate notes from, instead of `refs/notes/commits`. + This overrides the `core.notesRef` setting. + +'GIT_NOTES_DISPLAY_REF':: + Colon-delimited list of refs or globs indicating which refs, + in addition to the default from `core.notesRef` or + 'GIT_NOTES_REF', to read notes from when showing commit + messages. + This overrides the `notes.displayRef` setting. ++ +A warning will be issued for refs that do not exist, but a glob that +does not match any refs is silently ignored. + +'GIT_NOTES_REWRITE_MODE':: + When copying notes during a rewrite, what to do if the target + commit already has a note. + Must be one of `overwrite`, `concatenate`, and `ignore`. + This overrides the `core.rewriteMode` setting. + +'GIT_NOTES_REWRITE_REF':: + When rewriting commits, which notes to copy from the original + to the rewritten commit. Must be a colon-delimited list of + refs or globs. ++ +If not set in the environment, the list of notes to copy depends +on the `notes.rewrite.<command>` and `notes.rewriteRef` settings. Author diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index 50ba2e469f..be23ad2359 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -310,6 +310,11 @@ link:howto/revert-a-faulty-merge.txt[revert-a-faulty-merge How-To] for details). -p:: --preserve-merges:: Instead of ignoring merges, try to recreate them. ++ +This uses the `--interactive` machinery internally, but combining it +with the `--interactive` option explicitly is generally not a good +idea unless you know what you are doing (see BUGS below). + --root:: Rebase all commits reachable from <branch>, instead of @@ -611,6 +616,28 @@ The ripple effect of a "hard case" recovery is especially bad: case" recovery too! +BUGS +---- +The todo list presented by `--preserve-merges --interactive` does not +represent the topology of the revision graph. Editing commits and +rewording their commit messages should work fine, but attempts to +reorder commits tend to produce counterintuitive results. + +For example, an attempt to rearrange +------------ +1 --- 2 --- 3 --- 4 --- 5 +------------ +to +------------ +1 --- 2 --- 4 --- 3 --- 5 +------------ +by moving the "pick 4" line will result in the following history: +------------ + 3 + / +1 --- 2 --- 4 --- 5 +------------ + Authors ------ Written by Junio C Hamano <gitster@pobox.com> and diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt index 3dfdc7cca6..12622fc49a 100644 --- a/Documentation/git-send-email.txt +++ b/Documentation/git-send-email.txt @@ -119,6 +119,13 @@ Sending value reverts to plain SMTP. Default is the value of 'sendemail.smtpencryption'. +--smtp-domain=<FQDN>:: + Specifies the Fully Qualified Domain Name (FQDN) used in the + HELO/EHLO command to the SMTP server. Some servers require the + FQDN to match your IP address. If not set, git send-email attempts + to determine your FQDN automatically. Default is the value of + 'sendemail.smtpdomain'. + --smtp-pass[=<password>]:: Password for SMTP-AUTH. The argument is optional: If no argument is specified, then the empty string is used as diff --git a/Documentation/git-shortlog.txt b/Documentation/git-shortlog.txt index dfd4d0c223..bc1ac77495 100644 --- a/Documentation/git-shortlog.txt +++ b/Documentation/git-shortlog.txt @@ -9,7 +9,7 @@ SYNOPSIS -------- [verse] git log --pretty=short | 'git shortlog' [-h] [-n] [-s] [-e] [-w] -'git shortlog' [-n|--numbered] [-s|--summary] [-e|--email] [-w[<width>[,<indent1>[,<indent2>]]]] [<committish>...] +'git shortlog' [-n|--numbered] [-s|--summary] [-e|--email] [-w[<width>[,<indent1>[,<indent2>]]]] <commit>... DESCRIPTION ----------- @@ -19,6 +19,11 @@ the first line of the commit message will be shown. Additionally, "[PATCH]" will be stripped from the commit description. +If no revisions are passed on the command line and either standard input +is not a terminal or there is no current branch, 'git shortlog' will +output a summary of the log read from standard input, without +reference to the current repository. + OPTIONS ------- @@ -39,6 +44,14 @@ OPTIONS --email:: Show the email address of each author. +--format[='<format>']:: + Instead of the commit subject, use some other information to + describe each commit. '<format>' can be any string accepted + by the `--format` option of 'git log', such as '{asterisk} [%h] %s'. + (See the "PRETTY FORMATS" section of linkgit:git-log[1].) + + Each pretty-printed commit will be rewrapped before it is shown. + -w[<width>[,<indent1>[,<indent2>]]]:: Linewrap the output by wrapping each line at `width`. The first line of each entry is indented by `indent1` spaces, and the second diff --git a/Documentation/git-update-index.txt b/Documentation/git-update-index.txt index 68dc1879fe..765d4b312e 100644 --- a/Documentation/git-update-index.txt +++ b/Documentation/git-update-index.txt @@ -93,8 +93,6 @@ OPTIONS This option can be also used as a coarse file-level mechanism to ignore uncommitted changes in tracked files (akin to what `.gitignore` does for untracked files). -You should remember that an explicit 'git add' operation will -still cause the file to be refreshed from the working tree. Git will fail (gracefully) in case it needs to modify this file in the index e.g. when merging in a commit; thus, in case the assumed-untracked file is changed upstream, diff --git a/Documentation/gitignore.txt b/Documentation/gitignore.txt index 98c459dc82..e10fa88b8c 100644 --- a/Documentation/gitignore.txt +++ b/Documentation/gitignore.txt @@ -83,16 +83,20 @@ Patterns have the following format: - 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. + pathname relative to the location of the `.gitignore` file + (relative to the toplevel of the work tree if not from a + `.gitignore` file). - Otherwise, git treats the pattern as a shell glob suitable for consumption by fnmatch(3) with the FNM_PATHNAME flag: wildcards in the pattern will not match a / in the pathname. For example, "Documentation/\*.html" matches - "Documentation/git.html" but not - "Documentation/ppc/ppc.html". A leading slash matches the - beginning of the pathname; for example, "/*.c" matches - "cat-file.c" but not "mozilla-sha1/sha1.c". + "Documentation/git.html" but not "Documentation/ppc/ppc.html" + or "tools/perf/Documentation/perf.html". + + - A leading slash matches the beginning of the pathname. + For example, "/*.c" matches "cat-file.c" but not + "mozilla-sha1/sha1.c". An example: @@ -31,6 +31,9 @@ all:: # Define EXPATDIR=/foo/bar if your expat header and library files are in # /foo/bar/include and /foo/bar/lib directories. # +# Define HAVE_PATHS_H if you have paths.h and want to use the default PATH +# it specifies. +# # Define NO_D_INO_IN_DIRENT if you don't have d_ino in your struct dirent. # # Define NO_D_TYPE_IN_DIRENT if your platform defines DT_UNKNOWN but lacks @@ -735,10 +738,12 @@ EXTLIBS = ifeq ($(uname_S),Linux) NO_STRLCPY = YesPlease NO_MKSTEMPS = YesPlease + HAVE_PATHS_H = YesPlease endif ifeq ($(uname_S),GNU/kFreeBSD) NO_STRLCPY = YesPlease NO_MKSTEMPS = YesPlease + HAVE_PATHS_H = YesPlease endif ifeq ($(uname_S),UnixWare) CC = cc @@ -867,6 +872,7 @@ ifeq ($(uname_S),FreeBSD) NO_STRTOUMAX = YesPlease endif PYTHON_PATH = /usr/local/bin/python + HAVE_PATHS_H = YesPlease endif ifeq ($(uname_S),OpenBSD) NO_STRCASESTR = YesPlease @@ -875,6 +881,7 @@ ifeq ($(uname_S),OpenBSD) NEEDS_LIBICONV = YesPlease BASIC_CFLAGS += -I/usr/local/include BASIC_LDFLAGS += -L/usr/local/lib + HAVE_PATHS_H = YesPlease endif ifeq ($(uname_S),NetBSD) ifeq ($(shell expr "$(uname_R)" : '[01]\.'),2) @@ -884,8 +891,10 @@ ifeq ($(uname_S),NetBSD) BASIC_LDFLAGS += -L/usr/pkg/lib $(CC_LD_DYNPATH)/usr/pkg/lib USE_ST_TIMESPEC = YesPlease NO_MKSTEMPS = YesPlease + HAVE_PATHS_H = YesPlease endif ifeq ($(uname_S),AIX) + DEFAULT_PAGER = more NO_STRCASESTR=YesPlease NO_MEMMEM = YesPlease NO_MKDTEMP = YesPlease @@ -904,6 +913,7 @@ ifeq ($(uname_S),GNU) # GNU/Hurd NO_STRLCPY=YesPlease NO_MKSTEMPS = YesPlease + HAVE_PATHS_H = YesPlease endif ifeq ($(uname_S),IRIX) NO_SETENV = YesPlease @@ -1353,6 +1363,10 @@ else LIB_OBJS += thread-utils.o endif +ifdef HAVE_PATHS_H + BASIC_CFLAGS += -DHAVE_PATHS_H +endif + ifdef DIR_HAS_BSD_GROUP_SEMANTICS COMPAT_CFLAGS += -DDIR_HAS_BSD_GROUP_SEMANTICS endif @@ -1 +1 @@ -Documentation/RelNotes-1.7.1.txt
\ No newline at end of file +Documentation/RelNotes-1.7.1.1.txt
\ No newline at end of file @@ -594,20 +594,25 @@ static int path_matches(const char *pathname, int pathlen, return fnmatch(pattern, pathname + baselen, FNM_PATHNAME) == 0; } +static int macroexpand_one(int attr_nr, int rem); + static int fill_one(const char *what, struct match_attr *a, int rem) { struct git_attr_check *check = check_all_attr; int i; - for (i = 0; 0 < rem && i < a->num_attr; i++) { + for (i = a->num_attr - 1; 0 < rem && 0 <= i; i--) { struct git_attr *attr = a->state[i].attr; const char **n = &(check[attr->attr_nr].value); const char *v = a->state[i].setto; if (*n == ATTR__UNKNOWN) { - debug_set(what, a->u.pattern, attr, v); + debug_set(what, + a->is_macro ? a->u.attr->name : a->u.pattern, + attr, v); *n = v; rem--; + rem = macroexpand_one(attr->attr_nr, rem); } } return rem; @@ -629,19 +634,27 @@ static int fill(const char *path, int pathlen, struct attr_stack *stk, int rem) return rem; } -static int macroexpand(struct attr_stack *stk, int rem) +static int macroexpand_one(int attr_nr, int rem) { + struct attr_stack *stk; + struct match_attr *a = NULL; int i; - struct git_attr_check *check = check_all_attr; - for (i = stk->num_matches - 1; 0 < rem && 0 <= i; i--) { - struct match_attr *a = stk->attrs[i]; - if (!a->is_macro) - continue; - if (check[a->u.attr->attr_nr].value != ATTR__TRUE) - continue; + if (check_all_attr[attr_nr].value != ATTR__TRUE) + return rem; + + for (stk = attr_stack; !a && stk; stk = stk->prev) + for (i = stk->num_matches - 1; !a && 0 <= i; i--) { + struct match_attr *ma = stk->attrs[i]; + if (!ma->is_macro) + continue; + if (ma->u.attr->attr_nr == attr_nr) + a = ma; + } + + if (a) rem = fill_one("expand", a, rem); - } + return rem; } @@ -666,9 +679,6 @@ int git_checkattr(const char *path, int num, struct git_attr_check *check) for (stk = attr_stack; 0 < rem && stk; stk = stk->prev) rem = fill(path, pathlen, stk, rem); - for (stk = attr_stack; 0 < rem && stk; stk = stk->prev) - rem = macroexpand(stk, rem); - for (i = 0; i < num; i++) { const char *value = check_all_attr[check[i].attr->attr_nr].value; if (value == ATTR__UNKNOWN) @@ -16,6 +16,7 @@ extern const char *help_unknown_cmd(const char *cmd); extern void prune_packed_objects(int); extern int fmt_merge_msg(int merge_summary, struct strbuf *in, struct strbuf *out); +extern int fmt_merge_msg_shortlog(struct strbuf *in, struct strbuf *out); extern int commit_tree(const char *msg, unsigned char *tree, struct commit_list *parents, unsigned char *ret, const char *author); diff --git a/builtin/apply.c b/builtin/apply.c index 771c972c55..59bbcdb132 100644 --- a/builtin/apply.c +++ b/builtin/apply.c @@ -1864,13 +1864,13 @@ static int match_fragment(struct image *img, if (match_end && (preimage->nr + try_lno != img->nr)) return 0; } else if (ws_error_action == correct_ws_error && - (ws_rule & WS_BLANK_AT_EOF) && match_end) { + (ws_rule & WS_BLANK_AT_EOF)) { /* - * This hunk that matches at the end extends beyond - * the end of img, and we are removing blank lines - * at the end of the file. This many lines from the - * beginning of the preimage must match with img, and - * the remainder of the preimage must be blank. + * This hunk extends beyond the end of img, and we are + * removing blank lines at the end of the file. This + * many lines from the beginning of the preimage must + * match with img, and the remainder of the preimage + * must be blank. */ preimage_limit = img->nr - try_lno; } else { @@ -3141,11 +3141,7 @@ static void remove_file(struct patch *patch, int rmdir_empty) die("unable to remove %s from index", patch->old_name); } if (!cached) { - if (S_ISGITLINK(patch->old_mode)) { - if (rmdir(patch->old_name)) - warning("unable to remove submodule %s", - patch->old_name); - } else if (!unlink_or_warn(patch->old_name) && rmdir_empty) { + if (!remove_or_warn(patch->old_mode, patch->old_name) && rmdir_empty) { remove_path(patch->old_name); } } diff --git a/builtin/blame.c b/builtin/blame.c index 4dd4c3f494..8506286dd2 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -39,7 +39,7 @@ static int show_root; static int reverse; static int blank_boundary; static int incremental; -static int xdl_opts = XDF_NEED_MINIMAL; +static int xdl_opts; static enum date_mode blame_date_mode = DATE_ISO8601; static size_t blame_date_width; diff --git a/builtin/clone.c b/builtin/clone.c index 0bedde41f0..3a3625b2ad 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -464,7 +464,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix) set_git_dir(make_absolute_path(git_dir)); if (0 <= option_verbosity) - printf("Cloning into %s...\n", get_git_dir()); + printf("Cloning into %s%s...\n", + option_bare ? "bare repository " : "", dir); init_db(option_template, INIT_DB_QUIET); /* diff --git a/builtin/commit.c b/builtin/commit.c index 216ecc91d1..9d59af0344 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -460,15 +460,21 @@ static void determine_author_info(void) if (!a) die("invalid commit: %s", use_message); - lb = strstr(a + 8, " <"); - rb = strstr(a + 8, "> "); - eol = strchr(a + 8, '\n'); - if (!lb || !rb || !eol) + lb = strchrnul(a + strlen("\nauthor "), '<'); + rb = strchrnul(lb, '>'); + eol = strchrnul(rb, '\n'); + if (!*lb || !*rb || !*eol) die("invalid commit: %s", use_message); - name = xstrndup(a + 8, lb - (a + 8)); - email = xstrndup(lb + 2, rb - (lb + 2)); - date = xstrndup(rb + 2, eol - (rb + 2)); + if (lb == a + strlen("\nauthor ")) + /* \nauthor <foo@example.com> */ + name = xcalloc(1, 1); + else + name = xmemdupz(a + strlen("\nauthor "), + (lb - strlen(" ") - + (a + strlen("\nauthor ")))); + email = xmemdupz(lb + strlen("<"), rb - (lb + strlen("<"))); + date = xmemdupz(rb + strlen("> "), eol - (rb + strlen("> "))); } if (force_author) { @@ -1024,6 +1030,7 @@ static int git_status_config(const char *k, const char *v, void *cb) int cmd_status(int argc, const char **argv, const char *prefix) { struct wt_status s; + int fd; unsigned char sha1[20]; static struct option builtin_status_options[] = { OPT__VERBOSE(&verbose), @@ -1057,6 +1064,14 @@ int cmd_status(int argc, const char **argv, const char *prefix) read_cache_preload(s.pathspec); refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, s.pathspec, NULL, NULL); + + fd = hold_locked_index(&index_lock, 0); + if (0 <= fd) { + if (!write_cache(fd, active_cache, active_nr)) + commit_locked_index(&index_lock); + rollback_lock_file(&index_lock); + } + s.is_initial = get_sha1(s.reference, sha1) ? 1 : 0; s.in_merge = in_merge; wt_status_collect(&s); @@ -1145,13 +1160,11 @@ static void print_summary(const char *prefix, const unsigned char *sha1) initial_commit ? " (root-commit)" : ""); if (!log_tree_commit(&rev, commit)) { - struct pretty_print_context ctx = {0}; - struct strbuf buf = STRBUF_INIT; - ctx.date_mode = DATE_NORMAL; - format_commit_message(commit, format.buf + 7, &buf, &ctx); - printf("%s\n", buf.buf); - strbuf_release(&buf); + rev.always_show_header = 1; + rev.use_terminator = 1; + log_tree_commit(&rev, commit); } + strbuf_release(&format); } diff --git a/builtin/describe.c b/builtin/describe.c index 71be2a9364..43caff2ffe 100644 --- a/builtin/describe.c +++ b/builtin/describe.c @@ -35,7 +35,8 @@ static const char *diff_index_args[] = { struct commit_name { struct tag *tag; - int prio; /* annotated tag = 2, tag = 1, head = 0 */ + unsigned prio:2; /* annotated tag = 2, tag = 1, head = 0 */ + unsigned name_checked:1; unsigned char sha1[20]; char path[FLEX_ARRAY]; /* more */ }; @@ -43,18 +44,53 @@ static const char *prio_names[] = { "head", "lightweight", "annotated", }; +static int replace_name(struct commit_name *e, + int prio, + const unsigned char *sha1, + struct tag **tag) +{ + if (!e || e->prio < prio) + return 1; + + if (e->prio == 2 && prio == 2) { + /* Multiple annotated tags point to the same commit. + * Select one to keep based upon their tagger date. + */ + struct tag *t; + + if (!e->tag) { + t = lookup_tag(e->sha1); + if (!t || parse_tag(t)) + return 1; + e->tag = t; + } + + t = lookup_tag(sha1); + if (!t || parse_tag(t)) + return 0; + *tag = t; + + if (e->tag->date < t->date) + return 1; + } + + return 0; +} + static void add_to_known_names(const char *path, struct commit *commit, int prio, const unsigned char *sha1) { struct commit_name *e = commit->util; - if (!e || e->prio < prio) { + struct tag *tag = NULL; + if (replace_name(e, prio, sha1, &tag)) { size_t len = strlen(path)+1; free(e); e = xmalloc(sizeof(struct commit_name) + len); - e->tag = NULL; + e->tag = tag; e->prio = prio; + e->name_checked = 0; hashcpy(e->sha1, sha1); memcpy(e->path, path, len); commit->util = e; @@ -165,10 +201,15 @@ static void display_name(struct commit_name *n) { if (n->prio == 2 && !n->tag) { n->tag = lookup_tag(n->sha1); - if (!n->tag || parse_tag(n->tag) || !n->tag->tag) + if (!n->tag || parse_tag(n->tag)) die("annotated tag %s not available", n->path); + } + if (n->tag && !n->name_checked) { + if (!n->tag->tag) + die("annotated tag %s has no embedded name", n->path); if (strcmp(n->tag->tag, all ? n->path + 5 : n->path)) warning("tag '%s' is really '%s' here", n->tag->tag, n->path); + n->name_checked = 1; } if (n->tag) diff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c index 379a03131f..44204257c7 100644 --- a/builtin/fmt-merge-msg.c +++ b/builtin/fmt-merge-msg.c @@ -202,35 +202,10 @@ static void shortlog(const char *name, unsigned char *sha1, string_list_clear(&subjects, 0); } -int fmt_merge_msg(int merge_summary, struct strbuf *in, struct strbuf *out) { - int limit = 20, i = 0, pos = 0; +static void do_fmt_merge_msg_title(struct strbuf *out, + const char *current_branch) { + int i = 0; char *sep = ""; - unsigned char head_sha1[20]; - const char *current_branch; - - /* get current branch */ - current_branch = resolve_ref("HEAD", head_sha1, 1, NULL); - if (!current_branch) - die("No current branch"); - if (!prefixcmp(current_branch, "refs/heads/")) - current_branch += 11; - - /* get a line */ - while (pos < in->len) { - int len; - char *newline, *p = in->buf + pos; - - newline = strchr(p, '\n'); - len = newline ? newline - p : strlen(p); - pos += len + !!newline; - i++; - p[len] = 0; - if (handle_line(p)) - die ("Error in line %d: %.*s", i, len, p); - } - - if (!srcs.nr) - return 0; strbuf_addstr(out, "Merge "); for (i = 0; i < srcs.nr; i++) { @@ -278,6 +253,40 @@ int fmt_merge_msg(int merge_summary, struct strbuf *in, struct strbuf *out) { strbuf_addch(out, '\n'); else strbuf_addf(out, " into %s\n", current_branch); +} + +static int do_fmt_merge_msg(int merge_title, int merge_summary, + struct strbuf *in, struct strbuf *out) { + int limit = 20, i = 0, pos = 0; + unsigned char head_sha1[20]; + const char *current_branch; + + /* get current branch */ + current_branch = resolve_ref("HEAD", head_sha1, 1, NULL); + if (!current_branch) + die("No current branch"); + if (!prefixcmp(current_branch, "refs/heads/")) + current_branch += 11; + + /* get a line */ + while (pos < in->len) { + int len; + char *newline, *p = in->buf + pos; + + newline = strchr(p, '\n'); + len = newline ? newline - p : strlen(p); + pos += len + !!newline; + i++; + p[len] = 0; + if (handle_line(p)) + die ("Error in line %d: %.*s", i, len, p); + } + + if (!srcs.nr) + return 0; + + if (merge_title) + do_fmt_merge_msg_title(out, current_branch); if (merge_summary) { struct commit *head; @@ -289,6 +298,9 @@ int fmt_merge_msg(int merge_summary, struct strbuf *in, struct strbuf *out) { rev.ignore_merges = 1; rev.limited = 1; + if (suffixcmp(out->buf, "\n")) + strbuf_addch(out, '\n'); + for (i = 0; i < origins.nr; i++) shortlog(origins.items[i].string, origins.items[i].util, head, &rev, limit, out); @@ -296,6 +308,14 @@ int fmt_merge_msg(int merge_summary, struct strbuf *in, struct strbuf *out) { return 0; } +int fmt_merge_msg(int merge_summary, struct strbuf *in, struct strbuf *out) { + return do_fmt_merge_msg(1, merge_summary, in, out); +} + +int fmt_merge_msg_shortlog(struct strbuf *in, struct strbuf *out) { + return do_fmt_merge_msg(0, 1, in, out); +} + int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix) { const char *inpath = NULL; diff --git a/builtin/grep.c b/builtin/grep.c index 8e928e2170..b194ea3cea 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -17,8 +17,8 @@ #include "dir.h" #ifndef NO_PTHREADS -#include "thread-utils.h" #include <pthread.h> +#include "thread-utils.h" #endif static char const * const grep_usage[] = { diff --git a/builtin/index-pack.c b/builtin/index-pack.c index 03d0cd2f47..a89ae831dd 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -266,26 +266,23 @@ static void unlink_base_data(struct base_data *c) static void *unpack_entry_data(unsigned long offset, unsigned long size) { + int status; z_stream stream; void *buf = xmalloc(size); memset(&stream, 0, sizeof(stream)); + git_inflate_init(&stream); stream.next_out = buf; stream.avail_out = size; - stream.next_in = fill(1); - stream.avail_in = input_len; - git_inflate_init(&stream); - for (;;) { - int ret = git_inflate(&stream, 0); - use(input_len - stream.avail_in); - if (stream.total_out == size && ret == Z_STREAM_END) - break; - if (ret != Z_OK) - bad_object(offset, "inflate returned %d", ret); + do { stream.next_in = fill(1); stream.avail_in = input_len; - } + status = git_inflate(&stream, 0); + use(input_len - stream.avail_in); + } while (status == Z_OK); + if (stream.total_out != size || status != Z_STREAM_END) + bad_object(offset, "inflate returned %d", status); git_inflate_end(&stream); return buf; } @@ -359,34 +356,38 @@ static void *get_data_from_pack(struct object_entry *obj) { off_t from = obj[0].idx.offset + obj[0].hdr_size; unsigned long len = obj[1].idx.offset - from; - unsigned long rdy = 0; - unsigned char *src, *data; + unsigned char *data, *inbuf; z_stream stream; - int st; + int status; - src = xmalloc(len); - data = src; - do { - ssize_t n = pread(pack_fd, data + rdy, len - rdy, from + rdy); - if (n < 0) - die_errno("cannot pread pack file"); - if (!n) - die("premature end of pack file, %lu bytes missing", - len - rdy); - rdy += n; - } while (rdy < len); data = xmalloc(obj->size); + inbuf = xmalloc((len < 64*1024) ? len : 64*1024); + memset(&stream, 0, sizeof(stream)); + git_inflate_init(&stream); stream.next_out = data; stream.avail_out = obj->size; - stream.next_in = src; - stream.avail_in = len; - git_inflate_init(&stream); - while ((st = git_inflate(&stream, Z_FINISH)) == Z_OK); - git_inflate_end(&stream); - if (st != Z_STREAM_END || stream.total_out != obj->size) + + do { + ssize_t n = (len < 64*1024) ? len : 64*1024; + n = pread(pack_fd, inbuf, n, from); + if (n < 0) + die_errno("cannot pread pack file"); + if (!n) + die("premature end of pack file, %lu bytes missing", len); + from += n; + len -= n; + stream.next_in = inbuf; + stream.avail_in = n; + status = git_inflate(&stream, 0); + } while (len && status == Z_OK && !stream.avail_in); + + /* This has been inflated OK when first encountered, so... */ + if (status != Z_STREAM_END || stream.total_out != obj->size) die("serious inflate inconsistency"); - free(src); + + git_inflate_end(&stream); + free(inbuf); return data; } @@ -668,25 +669,25 @@ static void parse_pack_objects(unsigned char *sha1) static int write_compressed(struct sha1file *f, void *in, unsigned int size) { z_stream stream; - unsigned long maxsize; - void *out; + int status; + unsigned char outbuf[4096]; memset(&stream, 0, sizeof(stream)); deflateInit(&stream, zlib_compression_level); - maxsize = deflateBound(&stream, size); - out = xmalloc(maxsize); - - /* Compress it */ stream.next_in = in; stream.avail_in = size; - stream.next_out = out; - stream.avail_out = maxsize; - while (deflate(&stream, Z_FINISH) == Z_OK); - deflateEnd(&stream); + do { + stream.next_out = outbuf; + stream.avail_out = sizeof(outbuf); + status = deflate(&stream, Z_FINISH); + sha1write(f, outbuf, sizeof(outbuf) - stream.avail_out); + } while (status == Z_OK); + + if (status != Z_STREAM_END) + die("unable to deflate appended object (%d)", status); size = stream.total_out; - sha1write(f, out, size); - free(out); + deflateEnd(&stream); return size; } diff --git a/builtin/ls-files.c b/builtin/ls-files.c index c0fbcdcf4f..0804047693 100644 --- a/builtin/ls-files.c +++ b/builtin/ls-files.c @@ -26,8 +26,9 @@ static int show_killed; static int show_valid_bit; static int line_terminator = '\n'; +static const char *prefix; +static int max_prefix_len; static int prefix_len; -static int prefix_offset; static const char **pathspec; static int error_unmatch; static char *ps_matched; @@ -43,10 +44,15 @@ static const char *tag_modified = ""; static const char *tag_skip_worktree = ""; static const char *tag_resolve_undo = ""; +static void write_name(const char* name, size_t len) +{ + write_name_quoted_relative(name, len, prefix, prefix_len, stdout, + line_terminator); +} + static void show_dir_entry(const char *tag, struct dir_entry *ent) { - int len = prefix_len; - int offset = prefix_offset; + int len = max_prefix_len; if (len >= ent->len) die("git ls-files: internal error - directory entry not superset of prefix"); @@ -55,7 +61,7 @@ static void show_dir_entry(const char *tag, struct dir_entry *ent) return; fputs(tag, stdout); - write_name_quoted(ent->name + offset, stdout, line_terminator); + write_name(ent->name, ent->len); } static void show_other_files(struct dir_struct *dir) @@ -121,8 +127,7 @@ static void show_killed_files(struct dir_struct *dir) static void show_ce_entry(const char *tag, struct cache_entry *ce) { - int len = prefix_len; - int offset = prefix_offset; + int len = max_prefix_len; if (len >= ce_namelen(ce)) die("git ls-files: internal error - cache entry not superset of prefix"); @@ -156,20 +161,19 @@ static void show_ce_entry(const char *tag, struct cache_entry *ce) find_unique_abbrev(ce->sha1,abbrev), ce_stage(ce)); } - write_name_quoted(ce->name + offset, stdout, line_terminator); + write_name(ce->name, ce_namelen(ce)); } static int show_one_ru(struct string_list_item *item, void *cbdata) { - int offset = prefix_offset; const char *path = item->string; struct resolve_undo_info *ui = item->util; int i, len; len = strlen(path); - if (len < prefix_len) + if (len < max_prefix_len) return 0; /* outside of the prefix */ - if (!match_pathspec(pathspec, path, len, prefix_len, ps_matched)) + if (!match_pathspec(pathspec, path, len, max_prefix_len, ps_matched)) return 0; /* uninterested */ for (i = 0; i < 3; i++) { if (!ui->mode[i]) @@ -177,19 +181,19 @@ static int show_one_ru(struct string_list_item *item, void *cbdata) printf("%s%06o %s %d\t", tag_resolve_undo, ui->mode[i], find_unique_abbrev(ui->sha1[i], abbrev), i + 1); - write_name_quoted(path + offset, stdout, line_terminator); + write_name(path, len); } return 0; } -static void show_ru_info(const char *prefix) +static void show_ru_info(void) { if (!the_index.resolve_undo) return; for_each_string_list(show_one_ru, the_index.resolve_undo, NULL); } -static void show_files(struct dir_struct *dir, const char *prefix) +static void show_files(struct dir_struct *dir) { int i; @@ -243,7 +247,7 @@ static void show_files(struct dir_struct *dir, const char *prefix) */ static void prune_cache(const char *prefix) { - int pos = cache_name_pos(prefix, prefix_len); + int pos = cache_name_pos(prefix, max_prefix_len); unsigned int first, last; if (pos < 0) @@ -256,7 +260,7 @@ static void prune_cache(const char *prefix) while (last > first) { int next = (last + first) >> 1; struct cache_entry *ce = active_cache[next]; - if (!strncmp(ce->name, prefix, prefix_len)) { + if (!strncmp(ce->name, prefix, max_prefix_len)) { first = next+1; continue; } @@ -265,11 +269,16 @@ static void prune_cache(const char *prefix) active_nr = last; } -static const char *verify_pathspec(const char *prefix) +static const char *pathspec_prefix(const char *prefix) { const char **p, *n, *prev; unsigned long max; + if (!pathspec) { + max_prefix_len = prefix ? strlen(prefix) : 0; + return prefix; + } + prev = NULL; max = PATH_MAX; for (p = pathspec; (n = *p) != NULL; p++) { @@ -291,10 +300,7 @@ static const char *verify_pathspec(const char *prefix) } } - if (prefix_offset > max || memcmp(prev, prefix, prefix_offset)) - die("git ls-files: cannot generate relative filenames containing '..'"); - - prefix_len = max; + max_prefix_len = max; return max ? xmemdupz(prev, max) : NULL; } @@ -374,7 +380,7 @@ void overlay_tree_on_cache(const char *tree_name, const char *prefix) } } -int report_path_error(const char *ps_matched, const char **pathspec, int prefix_offset) +int report_path_error(const char *ps_matched, const char **pathspec, int prefix_len) { /* * Make sure all pathspec matched; otherwise it is an error. @@ -404,7 +410,7 @@ int report_path_error(const char *ps_matched, const char **pathspec, int prefix_ continue; error("pathspec '%s' did not match any file(s) known to git.", - pathspec[num] + prefix_offset); + pathspec[num] + prefix_len); errors++; } return errors; @@ -456,9 +462,10 @@ static int option_parse_exclude_standard(const struct option *opt, return 0; } -int cmd_ls_files(int argc, const char **argv, const char *prefix) +int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) { int require_work_tree = 0, show_tag = 0; + const char *max_prefix; struct dir_struct dir; struct option builtin_ls_files_options[] = { { OPTION_CALLBACK, 'z', NULL, NULL, NULL, @@ -504,7 +511,7 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) { OPTION_CALLBACK, 0, "exclude-standard", &dir, NULL, "add the standard git exclusions", PARSE_OPT_NOARG, option_parse_exclude_standard }, - { OPTION_SET_INT, 0, "full-name", &prefix_offset, NULL, + { OPTION_SET_INT, 0, "full-name", &prefix_len, NULL, "make the output relative to the project top directory", PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL }, OPT_BOOLEAN(0, "error-unmatch", &error_unmatch, @@ -516,8 +523,9 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) }; memset(&dir, 0, sizeof(dir)); + prefix = cmd_prefix; if (prefix) - prefix_offset = strlen(prefix); + prefix_len = strlen(prefix); git_config(git_default_config, NULL); if (read_cache() < 0) @@ -555,9 +563,8 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) if (pathspec) strip_trailing_slash_from_submodules(); - /* Verify that the pathspec matches the prefix */ - if (pathspec) - prefix = verify_pathspec(prefix); + /* Find common prefix for all pathspec's */ + max_prefix = pathspec_prefix(prefix); /* Treat unmatching pathspec elements as errors */ if (pathspec && error_unmatch) { @@ -575,8 +582,8 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) show_killed | show_modified | show_resolve_undo)) show_cached = 1; - if (prefix) - prune_cache(prefix); + if (max_prefix) + prune_cache(max_prefix); if (with_tree) { /* * Basic sanity check; show-stages and show-unmerged @@ -584,15 +591,15 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) */ if (show_stage || show_unmerged) die("ls-files --with-tree is incompatible with -s or -u"); - overlay_tree_on_cache(with_tree, prefix); + overlay_tree_on_cache(with_tree, max_prefix); } - show_files(&dir, prefix); + show_files(&dir); if (show_resolve_undo) - show_ru_info(prefix); + show_ru_info(); if (ps_matched) { int bad; - bad = report_path_error(ps_matched, pathspec, prefix_offset); + bad = report_path_error(ps_matched, pathspec, prefix_len); if (bad) fprintf(stderr, "Did you forget to 'git add'?\n"); diff --git a/builtin/merge-file.c b/builtin/merge-file.c index 610849a653..b8e9e5ba01 100644 --- a/builtin/merge-file.c +++ b/builtin/merge-file.c @@ -25,7 +25,7 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) const char *names[3] = { NULL, NULL, NULL }; mmfile_t mmfs[3]; mmbuffer_t result = {NULL, 0}; - xmparam_t xmp = {{XDF_NEED_MINIMAL}}; + xmparam_t xmp = {{0}}; int ret = 0, i = 0, to_stdout = 0; int quiet = 0; int nongit; diff --git a/builtin/merge-tree.c b/builtin/merge-tree.c index a4a4f2ce4c..fc00d794d6 100644 --- a/builtin/merge-tree.c +++ b/builtin/merge-tree.c @@ -106,7 +106,7 @@ static void show_diff(struct merge_list *entry) xdemitconf_t xecfg; xdemitcb_t ecb; - xpp.flags = XDF_NEED_MINIMAL; + xpp.flags = 0; memset(&xecfg, 0, sizeof(xecfg)); xecfg.ctxlen = 3; ecb.outf = show_outf; diff --git a/builtin/merge.c b/builtin/merge.c index c043066845..cae1cbee26 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -973,7 +973,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) reset_hard(remote_head->sha1, 0); return 0; } else { - struct strbuf msg = STRBUF_INIT; + struct strbuf merge_names = STRBUF_INIT; /* We are invoked directly as the first-class UI. */ head_arg = "HEAD"; @@ -986,13 +986,17 @@ int cmd_merge(int argc, const char **argv, const char *prefix) * codepath so we discard the error in this * loop. */ - if (!have_message) { - for (i = 0; i < argc; i++) - merge_name(argv[i], &msg); - fmt_merge_msg(option_log, &msg, &merge_msg); - if (merge_msg.len) - strbuf_setlen(&merge_msg, merge_msg.len-1); - } + for (i = 0; i < argc; i++) + merge_name(argv[i], &merge_names); + + if (have_message && option_log) + fmt_merge_msg_shortlog(&merge_names, &merge_msg); + else if (!have_message) + fmt_merge_msg(option_log, &merge_names, &merge_msg); + + + if (!(have_message && !option_log) && merge_msg.len) + strbuf_setlen(&merge_msg, merge_msg.len-1); } if (head_invalid || !argc) diff --git a/builtin/notes.c b/builtin/notes.c index 52b72fca68..26617546c8 100644 --- a/builtin/notes.c +++ b/builtin/notes.c @@ -416,7 +416,7 @@ int notes_copy_from_stdin(int force, const char *rewrite_cmd) { struct strbuf buf = STRBUF_INIT; struct notes_rewrite_cfg *c = NULL; - struct notes_tree *t; + struct notes_tree *t = NULL; int ret = 0; if (rewrite_cmd) { diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index 97802585ea..214d7ef2b1 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -18,8 +18,8 @@ #include "refs.h" #ifndef NO_PTHREADS -#include "thread-utils.h" #include <pthread.h> +#include "thread-utils.h" #endif static const char pack_usage[] = @@ -1522,6 +1522,13 @@ static void find_deltas(struct object_entry **list, unsigned *list_size, #ifndef NO_PTHREADS +static void try_to_free_from_threads(size_t size) +{ + read_lock(); + release_pack_memory(size, -1); + read_unlock(); +} + /* * The main thread waits on the condition that (at least) one of the workers * has stopped working (which is indicated in the .working member of @@ -1552,14 +1559,16 @@ static pthread_cond_t progress_cond; */ static void init_threaded_search(void) { - pthread_mutex_init(&read_mutex, NULL); + init_recursive_mutex(&read_mutex); pthread_mutex_init(&cache_mutex, NULL); pthread_mutex_init(&progress_mutex, NULL); pthread_cond_init(&progress_cond, NULL); + set_try_to_free_routine(try_to_free_from_threads); } static void cleanup_threaded_search(void) { + set_try_to_free_routine(NULL); pthread_cond_destroy(&progress_cond); pthread_mutex_destroy(&read_mutex); pthread_mutex_destroy(&cache_mutex); diff --git a/builtin/rerere.c b/builtin/rerere.c index 34f9acee91..0048f9ef7f 100644 --- a/builtin/rerere.c +++ b/builtin/rerere.c @@ -89,7 +89,7 @@ static int diff_two(const char *file1, const char *label1, printf("--- a/%s\n+++ b/%s\n", label1, label2); fflush(stdout); memset(&xpp, 0, sizeof(xpp)); - xpp.flags = XDF_NEED_MINIMAL; + xpp.flags = 0; memset(&xecfg, 0, sizeof(xecfg)); xecfg.ctxlen = 3; ecb.outf = outf; diff --git a/builtin/shortlog.c b/builtin/shortlog.c index 06320f5285..5089502800 100644 --- a/builtin/shortlog.c +++ b/builtin/shortlog.c @@ -162,7 +162,7 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit) sha1_to_hex(commit->object.sha1)); if (log->user_format) { struct pretty_print_context ctx = {0}; - ctx.abbrev = DEFAULT_ABBREV; + ctx.abbrev = log->abbrev; ctx.subject = ""; ctx.after_subject = ""; ctx.date_mode = DATE_NORMAL; @@ -290,6 +290,7 @@ parse_done: } log.user_format = rev.commit_format == CMIT_FMT_USERFORMAT; + log.abbrev = rev.abbrev; /* assume HEAD if from a tty */ if (!nongit && !rev.pending.nr && isatty(0)) @@ -905,7 +905,7 @@ struct extra_have_objects { extern struct ref **get_remote_heads(int in, struct ref **list, int nr_match, char **match, unsigned int flags, struct extra_have_objects *); extern int server_supports(const char *feature); -extern struct packed_git *parse_pack_index(unsigned char *sha1); +extern struct packed_git *parse_pack_index(unsigned char *sha1, const char *idx_path); extern void prepare_packed_git(void); extern void reprepare_packed_git(void); @@ -916,6 +916,7 @@ extern struct packed_git *find_sha1_pack(const unsigned char *sha1, extern void pack_report(void); extern int open_pack_index(struct packed_git *); +extern void close_pack_index(struct packed_git *); extern unsigned char *use_pack(struct packed_git *, struct pack_window **, off_t, unsigned int *); extern void close_pack_windows(struct packed_git *); extern void unuse_pack(struct pack_window **); diff --git a/combine-diff.c b/combine-diff.c index 7557136c82..655fa89d8a 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -220,7 +220,7 @@ static void combine_diff(const unsigned char *parent, unsigned int mode, parent_file.ptr = grab_blob(parent, mode, &sz); parent_file.size = sz; memset(&xpp, 0, sizeof(xpp)); - xpp.flags = XDF_NEED_MINIMAL; + xpp.flags = 0; memset(&xecfg, 0, sizeof(xecfg)); memset(&state, 0, sizeof(state)); state.nmask = nmask; diff --git a/compat/win32/pthread.h b/compat/win32/pthread.h index c72f100f40..a45f8d66df 100644 --- a/compat/win32/pthread.h +++ b/compat/win32/pthread.h @@ -18,11 +18,17 @@ */ #define pthread_mutex_t CRITICAL_SECTION -#define pthread_mutex_init(a,b) InitializeCriticalSection((a)) +#define pthread_mutex_init(a,b) (InitializeCriticalSection((a)), 0) #define pthread_mutex_destroy(a) DeleteCriticalSection((a)) #define pthread_mutex_lock EnterCriticalSection #define pthread_mutex_unlock LeaveCriticalSection +typedef int pthread_mutexattr_t; +#define pthread_mutexattr_init(a) (*(a) = 0) +#define pthread_mutexattr_destroy(a) do {} while (0) +#define pthread_mutexattr_settype(a, t) 0 +#define PTHREAD_MUTEX_RECURSIVE 0 + /* * Implement simple condition variable for Windows threads, based on ACE * implementation. diff --git a/config.mak.in b/config.mak.in index 6008ac9f1b..0d4b64d076 100644 --- a/config.mak.in +++ b/config.mak.in @@ -31,6 +31,7 @@ NO_OPENSSL=@NO_OPENSSL@ NO_CURL=@NO_CURL@ NO_EXPAT=@NO_EXPAT@ NO_LIBGEN_H=@NO_LIBGEN_H@ +HAVE_PATHS_H=@HAVE_PATHS_H@ NEEDS_LIBICONV=@NEEDS_LIBICONV@ NEEDS_SOCKET=@NEEDS_SOCKET@ NEEDS_RESOLV=@NEEDS_RESOLV@ diff --git a/configure.ac b/configure.ac index f4d7372ef8..71038fcf1c 100644 --- a/configure.ac +++ b/configure.ac @@ -724,6 +724,12 @@ AC_CHECK_HEADER([libgen.h], [NO_LIBGEN_H=YesPlease]) AC_SUBST(NO_LIBGEN_H) # +# Define HAVE_PATHS_H if you have paths.h. +AC_CHECK_HEADER([paths.h], +[HAVE_PATHS_H=YesPlease], +[HAVE_PATHS_H=]) +AC_SUBST(HAVE_PATHS_H) +# # Define NO_STRCASESTR if you don't have strcasestr. GIT_CHECK_FUNC(strcasestr, [NO_STRCASESTR=], @@ -229,6 +229,7 @@ static const struct { { "GMT", 0, 0, }, /* Greenwich Mean */ { "UTC", 0, 0, }, /* Universal (Coordinated) */ + { "Z", 0, 0, }, /* Zulu, alias for UTC */ { "WET", 0, 0, }, /* Western European */ { "BST", 0, 1, }, /* British Summer */ @@ -305,7 +306,7 @@ static int match_alpha(const char *date, struct tm *tm, int *offset) for (i = 0; i < ARRAY_SIZE(timezone_names); i++) { int match = match_string(date, timezone_names[i].name); - if (match >= 3) { + if (match >= 3 || match == strlen(timezone_names[i].name)) { int off = timezone_names[i].offset; /* This is bogus, but we like summer */ @@ -717,7 +717,7 @@ static void diff_words_show(struct diff_words_data *diff_words) memset(&xecfg, 0, sizeof(xecfg)); diff_words_fill(&diff_words->minus, &minus, diff_words->word_regex); diff_words_fill(&diff_words->plus, &plus, diff_words->word_regex); - xpp.flags = XDF_NEED_MINIMAL; + xpp.flags = 0; /* as only the hunk header will be parsed, we need a 0-context */ xecfg.ctxlen = 0; xdi_diff_outf(&minus, &plus, fn_out_diff_words_aux, diff_words, @@ -1600,6 +1600,7 @@ static void builtin_diff(const char *name_a, struct diff_filespec *one, struct diff_filespec *two, const char *xfrm_msg, + int must_show_header, struct diff_options *o, int complete_rewrite) { @@ -1649,21 +1650,24 @@ static void builtin_diff(const char *name_a, if (lbl[0][0] == '/') { /* /dev/null */ strbuf_addf(&header, "%snew file mode %06o%s\n", set, two->mode, reset); - if (xfrm_msg && xfrm_msg[0]) - strbuf_addf(&header, "%s%s%s\n", set, xfrm_msg, reset); + if (xfrm_msg) + strbuf_addstr(&header, xfrm_msg); + must_show_header = 1; } else if (lbl[1][0] == '/') { strbuf_addf(&header, "%sdeleted file mode %06o%s\n", set, one->mode, reset); - if (xfrm_msg && xfrm_msg[0]) - strbuf_addf(&header, "%s%s%s\n", set, xfrm_msg, reset); + if (xfrm_msg) + strbuf_addstr(&header, xfrm_msg); + must_show_header = 1; } else { if (one->mode != two->mode) { strbuf_addf(&header, "%sold mode %06o%s\n", set, one->mode, reset); strbuf_addf(&header, "%snew mode %06o%s\n", set, two->mode, reset); + must_show_header = 1; } - if (xfrm_msg && xfrm_msg[0]) - strbuf_addf(&header, "%s%s%s\n", set, xfrm_msg, reset); + if (xfrm_msg) + strbuf_addstr(&header, xfrm_msg); /* * we do not run diff between different kind @@ -1691,8 +1695,11 @@ static void builtin_diff(const char *name_a, (diff_filespec_is_binary(two) && !textconv_two) )) { /* Quite common confusing case */ if (mf1.size == mf2.size && - !memcmp(mf1.ptr, mf2.ptr, mf1.size)) + !memcmp(mf1.ptr, mf2.ptr, mf1.size)) { + if (must_show_header) + fprintf(o->file, "%s", header.buf); goto free_ab_and_return; + } fprintf(o->file, "%s", header.buf); strbuf_reset(&header); if (DIFF_OPT_TST(o, BINARY)) @@ -1710,7 +1717,7 @@ static void builtin_diff(const char *name_a, struct emit_callback ecbdata; const struct userdiff_funcname *pe; - if (!DIFF_XDL_TST(o, WHITESPACE_FLAGS)) { + if (!DIFF_XDL_TST(o, WHITESPACE_FLAGS) || must_show_header) { fprintf(o->file, "%s", header.buf); strbuf_reset(&header); } @@ -1745,7 +1752,7 @@ static void builtin_diff(const char *name_a, check_blank_at_eof(&mf1, &mf2, &ecbdata); ecbdata.file = o->file; ecbdata.header = header.len ? &header : NULL; - xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts; + xpp.flags = o->xdl_opts; xecfg.ctxlen = o->context; xecfg.interhunkctxlen = o->interhunkcontext; xecfg.flags = XDL_EMIT_FUNCNAMES; @@ -1834,7 +1841,7 @@ static void builtin_diffstat(const char *name_a, const char *name_b, memset(&xpp, 0, sizeof(xpp)); memset(&xecfg, 0, sizeof(xecfg)); - xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts; + xpp.flags = o->xdl_opts; xdi_diff_outf(&mf1, &mf2, diffstat_consume, diffstat, &xpp, &xecfg); } @@ -1882,7 +1889,7 @@ static void builtin_checkdiff(const char *name_a, const char *name_b, memset(&xpp, 0, sizeof(xpp)); memset(&xecfg, 0, sizeof(xecfg)); xecfg.ctxlen = 1; /* at least one context line */ - xpp.flags = XDF_NEED_MINIMAL; + xpp.flags = 0; xdi_diff_outf(&mf1, &mf2, checkdiff_consume, &data, &xpp, &xecfg); @@ -2319,36 +2326,43 @@ static void fill_metainfo(struct strbuf *msg, struct diff_filespec *one, struct diff_filespec *two, struct diff_options *o, - struct diff_filepair *p) + struct diff_filepair *p, + int *must_show_header, + int use_color) { + const char *set = diff_get_color(use_color, DIFF_METAINFO); + const char *reset = diff_get_color(use_color, DIFF_RESET); + + *must_show_header = 1; strbuf_init(msg, PATH_MAX * 2 + 300); switch (p->status) { case DIFF_STATUS_COPIED: - strbuf_addf(msg, "similarity index %d%%", similarity_index(p)); - strbuf_addstr(msg, "\ncopy from "); + strbuf_addf(msg, "%ssimilarity index %d%%", + set, similarity_index(p)); + strbuf_addf(msg, "%s\n%scopy from ", reset, set); quote_c_style(name, msg, NULL, 0); - strbuf_addstr(msg, "\ncopy to "); + strbuf_addf(msg, "%s\n%scopy to ", reset, set); quote_c_style(other, msg, NULL, 0); - strbuf_addch(msg, '\n'); + strbuf_addf(msg, "%s\n", reset); break; case DIFF_STATUS_RENAMED: - strbuf_addf(msg, "similarity index %d%%", similarity_index(p)); - strbuf_addstr(msg, "\nrename from "); + strbuf_addf(msg, "%ssimilarity index %d%%", + set, similarity_index(p)); + strbuf_addf(msg, "%s\n%srename from ", reset, set); quote_c_style(name, msg, NULL, 0); - strbuf_addstr(msg, "\nrename to "); + strbuf_addf(msg, "%s\n%srename to ", reset, set); quote_c_style(other, msg, NULL, 0); - strbuf_addch(msg, '\n'); + strbuf_addf(msg, "%s\n", reset); break; case DIFF_STATUS_MODIFIED: if (p->score) { - strbuf_addf(msg, "dissimilarity index %d%%\n", - similarity_index(p)); + strbuf_addf(msg, "%sdissimilarity index %d%%%s\n", + set, similarity_index(p), reset); break; } /* fallthru */ default: - /* nothing */ - ; + *must_show_header = 0; } if (one && two && hashcmp(one->sha1, two->sha1)) { int abbrev = DIFF_OPT_TST(o, FULL_INDEX) ? 40 : DEFAULT_ABBREV; @@ -2359,15 +2373,13 @@ static void fill_metainfo(struct strbuf *msg, (!fill_mmfile(&mf, two) && diff_filespec_is_binary(two))) abbrev = 40; } - strbuf_addf(msg, "index %.*s..%.*s", + strbuf_addf(msg, "%sindex %.*s..%.*s", set, abbrev, sha1_to_hex(one->sha1), abbrev, sha1_to_hex(two->sha1)); if (one->mode == two->mode) strbuf_addf(msg, " %06o", one->mode); - strbuf_addch(msg, '\n'); + strbuf_addf(msg, "%s\n", reset); } - if (msg->len) - strbuf_setlen(msg, msg->len - 1); } static void run_diff_cmd(const char *pgm, @@ -2382,11 +2394,7 @@ static void run_diff_cmd(const char *pgm, { const char *xfrm_msg = NULL; int complete_rewrite = (p->status == DIFF_STATUS_MODIFIED) && p->score; - - if (msg) { - fill_metainfo(msg, name, other, one, two, o, p); - xfrm_msg = msg->len ? msg->buf : NULL; - } + int must_show_header = 0; if (!DIFF_OPT_TST(o, ALLOW_EXTERNAL)) pgm = NULL; @@ -2396,6 +2404,17 @@ static void run_diff_cmd(const char *pgm, pgm = drv->external; } + if (msg) { + /* + * don't use colors when the header is intended for an + * external diff driver + */ + fill_metainfo(msg, name, other, one, two, o, p, + &must_show_header, + DIFF_OPT_TST(o, COLOR_DIFF) && !pgm); + xfrm_msg = msg->len ? msg->buf : NULL; + } + if (pgm) { run_external_diff(pgm, name, other, one, two, xfrm_msg, complete_rewrite); @@ -2403,7 +2422,8 @@ static void run_diff_cmd(const char *pgm, } if (one && two) builtin_diff(name, other ? other : name, - one, two, xfrm_msg, o, complete_rewrite); + one, two, xfrm_msg, must_show_header, + o, complete_rewrite); else fprintf(o->file, "* Unmerged path %s\n", name); } @@ -3436,7 +3456,7 @@ static int diff_get_patch_id(struct diff_options *options, unsigned char *sha1) len2, p->two->path); git_SHA1_Update(&ctx, buffer, len1); - xpp.flags = XDF_NEED_MINIMAL; + xpp.flags = 0; xecfg.ctxlen = 3; xecfg.flags = XDL_EMIT_FUNCNAMES; xdi_diff_outf(&mf1, &mf2, patch_id_consume, &data, diff --git a/exec_cmd.c b/exec_cmd.c index b2c07c70ce..bf225706ee 100644 --- a/exec_cmd.c +++ b/exec_cmd.c @@ -107,7 +107,7 @@ void setup_path(void) if (old_path) strbuf_addstr(&new_path, old_path); else - strbuf_addstr(&new_path, "/usr/local/bin:/usr/bin:/bin"); + strbuf_addstr(&new_path, _PATH_DEFPATH); setenv("PATH", new_path.buf, 1); diff --git a/git-add--interactive.perl b/git-add--interactive.perl index 21f1330a5b..27fc79347a 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -1111,9 +1111,9 @@ sub help_patch_cmd { print colored $help_color, <<EOF ; y - $verb this hunk$target n - do not $verb this hunk$target -q - quit, do not $verb this hunk nor any of the remaining ones -a - $verb this and all the remaining hunks in the file -d - do not $verb this hunk nor any of the remaining hunks in the file +q - quit; do not $verb this hunk nor any of the remaining ones +a - $verb this hunk and all later hunks in the file +d - do not $verb this hunk nor any of the later hunks in the file g - select a hunk to go to / - search for a hunk matching the given regex j - leave this hunk undecided, see next undecided hunk @@ -740,7 +740,7 @@ do ;; esac - if test $apply_status = 1 && test "$threeway" = t + if test $apply_status != 0 && test "$threeway" = t then if (fall_back_3way) then diff --git a/git-compat-util.h b/git-compat-util.h index b96912b5d3..c0198dde4c 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -164,6 +164,13 @@ extern char *gitbasename(char *); #define PATH_SEP ':' #endif +#ifdef HAVE_PATHS_H +#include <paths.h> +#endif +#ifndef _PATH_DEFPATH +#define _PATH_DEFPATH "/usr/local/bin:/usr/bin:/bin" +#endif + #ifndef STRIP_EXTENSION #define STRIP_EXTENSION "" #endif @@ -356,6 +363,8 @@ static inline void *gitmempcpy(void *dest, const void *src, size_t n) extern void release_pack_memory(size_t, int); +extern void set_try_to_free_routine(void (*routine)(size_t)); + extern char *xstrdup(const char *str); extern void *xmalloc(size_t size); extern void *xmallocz(size_t size); @@ -479,5 +488,14 @@ void git_qsort(void *base, size_t nmemb, size_t size, * Always returns the return value of unlink(2). */ int unlink_or_warn(const char *path); +/* + * Likewise for rmdir(2). + */ +int rmdir_or_warn(const char *path); +/* + * Calls the correct function out of {unlink,rmdir}_or_warn based on + * the supplied file mode. + */ +int remove_or_warn(unsigned int mode, const char *path); #endif diff --git a/git-pull.sh b/git-pull.sh index 1a4729f7bb..a09a44ec4c 100755 --- a/git-pull.sh +++ b/git-pull.sh @@ -43,6 +43,7 @@ merge_args= curr_branch=$(git symbolic-ref -q HEAD) curr_branch_short="${curr_branch#refs/heads/}" rebase=$(git config --bool branch.$curr_branch_short.rebase) +dry_run= while : do case "$1" in @@ -104,6 +105,9 @@ do --no-r|--no-re|--no-reb|--no-reba|--no-rebas|--no-rebase) rebase=false ;; + --d|--dr|--dry|--dry-|--dry-r|--dry-ru|--dry-run) + dry_run=--dry-run + ;; -h|--h|--he|--hel|--help) usage ;; @@ -216,7 +220,8 @@ test true = "$rebase" && { done } orig_head=$(git rev-parse -q --verify HEAD) -git fetch $verbosity $progress --update-head-ok "$@" || exit 1 +git fetch $verbosity $progress $dry_run --update-head-ok "$@" || exit 1 +test -z "$dry_run" || exit 0 curr_head=$(git rev-parse -q --verify HEAD) if test -n "$orig_head" && test "$curr_head" != "$orig_head" diff --git a/git-send-email.perl b/git-send-email.perl index ce569a9c8f..111c981229 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -132,8 +132,6 @@ my $have_email_valid = eval { require Email::Valid; 1 }; my $have_mail_address = eval { require Mail::Address; 1 }; my $smtp; my $auth; -my $mail_domain_default = "localhost.localdomain"; -my $mail_domain; sub unique_email_list(@); sub cleanup_compose_files(); @@ -190,7 +188,7 @@ sub do_edit { # Variables with corresponding config settings my ($thread, $chain_reply_to, $suppress_from, $signed_off_by_cc, $cc_cmd); my ($smtp_server, $smtp_server_port, $smtp_authuser, $smtp_encryption); -my ($identity, $aliasfiletype, @alias_files, @smtp_host_parts); +my ($identity, $aliasfiletype, @alias_files, @smtp_host_parts, $smtp_domain); my ($validate, $confirm); my (@suppress_cc); @@ -212,6 +210,7 @@ my %config_settings = ( "smtpserverport" => \$smtp_server_port, "smtpuser" => \$smtp_authuser, "smtppass" => \$smtp_authpass, + "smtpdomain" => \$smtp_domain, "to" => \@to, "cc" => \@initial_cc, "cccmd" => \$cc_cmd, @@ -283,7 +282,7 @@ my $rc = GetOptions("sender|from=s" => \$sender, "smtp-ssl" => sub { $smtp_encryption = 'ssl' }, "smtp-encryption=s" => \$smtp_encryption, "smtp-debug:i" => \$debug_net_smtp, - "smtp-domain:s" => \$mail_domain, + "smtp-domain:s" => \$smtp_domain, "identity=s" => \$identity, "annotate" => \$annotate, "compose" => \$compose, @@ -761,8 +760,7 @@ sub extract_valid_address { # We'll setup a template for the message id, using the "from" address: my ($message_id_stamp, $message_id_serial); -sub make_message_id -{ +sub make_message_id { my $uniq; if (!defined $message_id_stamp) { $message_id_stamp = sprintf("%s-%s", time, $$); @@ -817,8 +815,7 @@ sub is_rfc2047_quoted { } # use the simplest quoting being able to handle the recipient -sub sanitize_address -{ +sub sanitize_address { my ($recipient) = @_; my ($recipient_name, $recipient_addr) = ($recipient =~ /^(.*?)\s*(<.*)/); @@ -863,21 +860,23 @@ sub sanitize_address # This maildomain*() code is based on ideas in Perl library Test::Reporter # /usr/share/perl5/Test/Reporter/Mail/Util.pm ==> sub _maildomain () -sub maildomain_net -{ +sub valid_fqdn { + my $domain = shift; + return !($^O eq 'darwin' && $domain =~ /\.local$/) && $domain =~ /\./; +} + +sub maildomain_net { my $maildomain; if (eval { require Net::Domain; 1 }) { my $domain = Net::Domain::domainname(); - $maildomain = $domain - unless $^O eq 'darwin' && $domain =~ /\.local$/; + $maildomain = $domain if valid_fqdn($domain); } return $maildomain; } -sub maildomain_mta -{ +sub maildomain_mta { my $maildomain; if (eval { require Net::SMTP; 1 }) { @@ -887,8 +886,7 @@ sub maildomain_mta my $domain = $smtp->domain; $smtp->quit; - $maildomain = $domain - unless $^O eq 'darwin' && $domain =~ /\.local$/; + $maildomain = $domain if valid_fqdn($domain); last if $maildomain; } @@ -898,17 +896,15 @@ sub maildomain_mta return $maildomain; } -sub maildomain -{ - return maildomain_net() || maildomain_mta() || $mail_domain_default; +sub maildomain { + return maildomain_net() || maildomain_mta() || 'localhost.localdomain'; } # Returns 1 if the message was sent, and 0 otherwise. # In actuality, the whole program dies when there # is an error sending a message. -sub send_message -{ +sub send_message { my @recipients = unique_email_list(@to); @cc = (grep { my $cc = extract_valid_address($_); not grep { $cc eq $_ } @recipients @@ -1005,18 +1001,18 @@ X-Mailer: git-send-email $gitversion if ($smtp_encryption eq 'ssl') { $smtp_server_port ||= 465; # ssmtp require Net::SMTP::SSL; - $mail_domain ||= maildomain(); + $smtp_domain ||= maildomain(); $smtp ||= Net::SMTP::SSL->new($smtp_server, - Hello => $mail_domain, + Hello => $smtp_domain, Port => $smtp_server_port); } else { require Net::SMTP; - $mail_domain ||= maildomain(); + $smtp_domain ||= maildomain(); $smtp ||= Net::SMTP->new((defined $smtp_server_port) ? "$smtp_server:$smtp_server_port" : $smtp_server, - Hello => $mail_domain, + Hello => $smtp_domain, Debug => $debug_net_smtp); if ($smtp_encryption eq 'tls' && $smtp) { require Net::SMTP::SSL; @@ -1039,7 +1035,7 @@ X-Mailer: git-send-email $gitversion die "Unable to initialize SMTP properly. Check config and use --smtp-debug. ", "VALUES: server=$smtp_server ", "encryption=$smtp_encryption ", - "maildomain=$mail_domain", + "hello=$smtp_domain", defined $smtp_server_port ? "port=$smtp_server_port" : ""; } diff --git a/git-stash.sh b/git-stash.sh index 59db3dc38e..0f858d334c 100755 --- a/git-stash.sh +++ b/git-stash.sh @@ -86,7 +86,7 @@ create_stash () { GIT_INDEX_FILE="$TMP-index" && export GIT_INDEX_FILE && git read-tree -m $i_tree && - git add -u && + git diff --name-only -z HEAD | git update-index -z --add --remove --stdin && git write-tree && rm -f "$TMP-index" ) ) || diff --git a/gitweb/Makefile b/gitweb/Makefile index f2e1d92fbb..e7dd252773 100644 --- a/gitweb/Makefile +++ b/gitweb/Makefile @@ -91,7 +91,7 @@ ifdef CSSMIN GITWEB_CSS = gitweb.min.css all:: gitweb.min.css gitweb.min.css: gitweb.css GITWEB-BUILD-OPTIONS - $(QUIET_GEN)$(CSSMIN) <$ >$@ + $(QUIET_GEN)$(CSSMIN) <$< >$@ endif GITWEB_REPLACE = \ diff --git a/http-walker.c b/http-walker.c index ef99ae647a..8ca76d0507 100644 --- a/http-walker.c +++ b/http-walker.c @@ -510,7 +510,7 @@ static int fetch_object(struct walker *walker, struct alt_base *repo, unsigned c ret = error("File %s has bad hash", hex); } else if (req->rename < 0) { ret = error("unable to write sha1 filename %s", - req->filename); + sha1_file_name(req->sha1)); } release_http_object_request(req); @@ -1,6 +1,7 @@ #include "http.h" #include "pack.h" #include "sideband.h" +#include "run-command.h" int data_received; int active_requests; @@ -720,7 +721,7 @@ static inline int hex(int v) return 'A' + v - 10; } -static void end_url_with_slash(struct strbuf *buf, const char *url) +void end_url_with_slash(struct strbuf *buf, const char *url) { strbuf_addstr(buf, url); if (buf->len && buf->buf[buf->len - 1] != '/') @@ -815,7 +816,21 @@ static int http_request(const char *url, void *result, int target, int options) ret = HTTP_OK; else if (missing_target(&results)) ret = HTTP_MISSING_TARGET; - else + else if (results.http_code == 401) { + if (user_name) { + ret = HTTP_NOAUTH; + } else { + /* + * git_getpass is needed here because its very likely stdin/stdout are + * pipes to our parent process. So we instead need to use /dev/tty, + * but that is non-portable. Using git_getpass() can at least be stubbed + * on other platforms with a different implementation if/when necessary. + */ + user_name = xstrdup(git_getpass("Username: ")); + init_curl_http_auth(slot->curl); + ret = HTTP_REAUTH; + } + } else ret = HTTP_ERROR; } else { error("Unable to start HTTP request for %s", url); @@ -831,7 +846,11 @@ static int http_request(const char *url, void *result, int target, int options) int http_get_strbuf(const char *url, struct strbuf *result, int options) { - return http_request(url, result, HTTP_REQUEST_STRBUF, options); + int http_ret = http_request(url, result, HTTP_REQUEST_STRBUF, options); + if (http_ret == HTTP_REAUTH) { + http_ret = http_request(url, result, HTTP_REQUEST_STRBUF, options); + } + return http_ret; } /* @@ -896,47 +915,67 @@ int http_fetch_ref(const char *base, struct ref *ref) } /* Helpers for fetching packs */ -static int fetch_pack_index(unsigned char *sha1, const char *base_url) +static char *fetch_pack_index(unsigned char *sha1, const char *base_url) { - int ret = 0; - char *hex = xstrdup(sha1_to_hex(sha1)); - char *filename; - char *url = NULL; + char *url, *tmp; struct strbuf buf = STRBUF_INIT; - if (has_pack_index(sha1)) { - ret = 0; - goto cleanup; - } - if (http_is_verbose) - fprintf(stderr, "Getting index for pack %s\n", hex); + fprintf(stderr, "Getting index for pack %s\n", sha1_to_hex(sha1)); end_url_with_slash(&buf, base_url); - strbuf_addf(&buf, "objects/pack/pack-%s.idx", hex); + strbuf_addf(&buf, "objects/pack/pack-%s.idx", sha1_to_hex(sha1)); url = strbuf_detach(&buf, NULL); - filename = sha1_pack_index_name(sha1); - if (http_get_file(url, filename, 0) != HTTP_OK) - ret = error("Unable to get pack index %s\n", url); + strbuf_addf(&buf, "%s.temp", sha1_pack_index_name(sha1)); + tmp = strbuf_detach(&buf, NULL); + + if (http_get_file(url, tmp, 0) != HTTP_OK) { + error("Unable to get pack index %s\n", url); + free(tmp); + tmp = NULL; + } -cleanup: - free(hex); free(url); - return ret; + return tmp; } static int fetch_and_setup_pack_index(struct packed_git **packs_head, unsigned char *sha1, const char *base_url) { struct packed_git *new_pack; + char *tmp_idx = NULL; + int ret; - if (fetch_pack_index(sha1, base_url)) + if (has_pack_index(sha1)) { + new_pack = parse_pack_index(sha1, NULL); + if (!new_pack) + return -1; /* parse_pack_index() already issued error message */ + goto add_pack; + } + + tmp_idx = fetch_pack_index(sha1, base_url); + if (!tmp_idx) return -1; - new_pack = parse_pack_index(sha1); - if (!new_pack) + new_pack = parse_pack_index(sha1, tmp_idx); + if (!new_pack) { + unlink(tmp_idx); + free(tmp_idx); + return -1; /* parse_pack_index() already issued error message */ + } + + ret = verify_pack_index(new_pack); + if (!ret) { + close_pack_index(new_pack); + ret = move_temp_to_file(tmp_idx, sha1_pack_index_name(sha1)); + } + free(tmp_idx); + if (ret) + return -1; + +add_pack: new_pack->next = *packs_head; *packs_head = new_pack; return 0; @@ -1000,37 +1039,62 @@ void release_http_pack_request(struct http_pack_request *preq) int finish_http_pack_request(struct http_pack_request *preq) { - int ret; struct packed_git **lst; + struct packed_git *p = preq->target; + char *tmp_idx; + struct child_process ip; + const char *ip_argv[8]; - preq->target->pack_size = ftell(preq->packfile); - - if (preq->packfile != NULL) { - fclose(preq->packfile); - preq->packfile = NULL; - preq->slot->local = NULL; - } + close_pack_index(p); - ret = move_temp_to_file(preq->tmpfile, preq->filename); - if (ret) - return ret; + fclose(preq->packfile); + preq->packfile = NULL; + preq->slot->local = NULL; lst = preq->lst; - while (*lst != preq->target) + while (*lst != p) lst = &((*lst)->next); *lst = (*lst)->next; - if (verify_pack(preq->target)) + tmp_idx = xstrdup(preq->tmpfile); + strcpy(tmp_idx + strlen(tmp_idx) - strlen(".pack.temp"), + ".idx.temp"); + + ip_argv[0] = "index-pack"; + ip_argv[1] = "-o"; + ip_argv[2] = tmp_idx; + ip_argv[3] = preq->tmpfile; + ip_argv[4] = NULL; + + memset(&ip, 0, sizeof(ip)); + ip.argv = ip_argv; + ip.git_cmd = 1; + ip.no_stdin = 1; + ip.no_stdout = 1; + + if (run_command(&ip)) { + unlink(preq->tmpfile); + unlink(tmp_idx); + free(tmp_idx); + return -1; + } + + unlink(sha1_pack_index_name(p->sha1)); + + if (move_temp_to_file(preq->tmpfile, sha1_pack_name(p->sha1)) + || move_temp_to_file(tmp_idx, sha1_pack_index_name(p->sha1))) { + free(tmp_idx); return -1; - install_packed_git(preq->target); + } + install_packed_git(p); + free(tmp_idx); return 0; } struct http_pack_request *new_http_pack_request( struct packed_git *target, const char *base_url) { - char *filename; long prev_posn = 0; char range[RANGE_HEADER_SIZE]; struct strbuf buf = STRBUF_INIT; @@ -1045,9 +1109,8 @@ struct http_pack_request *new_http_pack_request( sha1_to_hex(target->sha1)); preq->url = strbuf_detach(&buf, NULL); - filename = sha1_pack_name(target->sha1); - snprintf(preq->filename, sizeof(preq->filename), "%s", filename); - snprintf(preq->tmpfile, sizeof(preq->tmpfile), "%s.temp", filename); + snprintf(preq->tmpfile, sizeof(preq->tmpfile), "%s.temp", + sha1_pack_name(target->sha1)); preq->packfile = fopen(preq->tmpfile, "a"); if (!preq->packfile) { error("Unable to open local file %s for pack", @@ -1082,7 +1145,6 @@ struct http_pack_request *new_http_pack_request( return preq; abort: - free(filename); free(preq->url); free(preq); return NULL; @@ -1137,7 +1199,6 @@ struct http_object_request *new_http_object_request(const char *base_url, freq->localfile = -1; filename = sha1_file_name(sha1); - snprintf(freq->filename, sizeof(freq->filename), "%s", filename); snprintf(freq->tmpfile, sizeof(freq->tmpfile), "%s.temp", filename); @@ -1166,8 +1227,8 @@ struct http_object_request *new_http_object_request(const char *base_url, } if (freq->localfile < 0) { - error("Couldn't create temporary file %s for %s: %s", - freq->tmpfile, freq->filename, strerror(errno)); + error("Couldn't create temporary file %s: %s", + freq->tmpfile, strerror(errno)); goto abort; } @@ -1214,8 +1275,8 @@ struct http_object_request *new_http_object_request(const char *base_url, prev_posn = 0; lseek(freq->localfile, 0, SEEK_SET); if (ftruncate(freq->localfile, 0) < 0) { - error("Couldn't truncate temporary file %s for %s: %s", - freq->tmpfile, freq->filename, strerror(errno)); + error("Couldn't truncate temporary file %s: %s", + freq->tmpfile, strerror(errno)); goto abort; } } @@ -1291,7 +1352,7 @@ int finish_http_object_request(struct http_object_request *freq) return -1; } freq->rename = - move_temp_to_file(freq->tmpfile, freq->filename); + move_temp_to_file(freq->tmpfile, sha1_file_name(freq->sha1)); return freq->rename; } @@ -117,6 +117,7 @@ extern void append_remote_object_url(struct strbuf *buf, const char *url, int only_two_digit_prefix); extern char *get_remote_object_url(const char *url, const char *hex, int only_two_digit_prefix); +extern void end_url_with_slash(struct strbuf *buf, const char *url); /* Options for http_request_*() */ #define HTTP_NO_CACHE 1 @@ -126,6 +127,8 @@ extern char *get_remote_object_url(const char *url, const char *hex, #define HTTP_MISSING_TARGET 1 #define HTTP_ERROR 2 #define HTTP_START_FAILED 3 +#define HTTP_REAUTH 4 +#define HTTP_NOAUTH 5 /* * Requests an url and stores the result in a strbuf. @@ -152,7 +155,6 @@ struct http_pack_request struct packed_git *target; struct packed_git **lst; FILE *packfile; - char filename[PATH_MAX]; char tmpfile[PATH_MAX]; struct curl_slist *range_header; struct active_request_slot *slot; @@ -167,7 +169,6 @@ extern void release_http_pack_request(struct http_pack_request *preq); struct http_object_request { char *url; - char filename[PATH_MAX]; char tmpfile[PATH_MAX]; int localfile; CURLcode curl_result; diff --git a/merge-file.c b/merge-file.c index c336c93c01..db4d0d50d3 100644 --- a/merge-file.c +++ b/merge-file.c @@ -66,7 +66,7 @@ static int generate_common_file(mmfile_t *res, mmfile_t *f1, mmfile_t *f2) xdemitcb_t ecb; memset(&xpp, 0, sizeof(xpp)); - xpp.flags = XDF_NEED_MINIMAL; + xpp.flags = 0; memset(&xecfg, 0, sizeof(xecfg)); xecfg.ctxlen = 3; xecfg.flags = XDL_EMIT_COMMON; diff --git a/merge-recursive.c b/merge-recursive.c index 917397ca7a..206c103635 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -409,7 +409,7 @@ static int remove_file(struct merge_options *o, int clean, return -1; } if (update_working_directory) { - if (remove_path(path) && errno != ENOENT) + if (remove_path(path)) return -1; } return 0; @@ -252,10 +252,10 @@ void add_object_array_with_mode(struct object *obj, const char *name, struct obj void object_array_remove_duplicates(struct object_array *array) { - int ref, src, dst; + unsigned int ref, src, dst; struct object_array_entry *objects = array->objects; - for (ref = 0; ref < array->nr - 1; ref++) { + for (ref = 0; ref + 1 < array->nr; ref++) { for (src = ref + 1, dst = src; src < array->nr; src++) { diff --git a/pack-check.c b/pack-check.c index 166ca703c1..395fb9527a 100644 --- a/pack-check.c +++ b/pack-check.c @@ -133,14 +133,13 @@ static int verify_packfile(struct packed_git *p, return err; } -int verify_pack(struct packed_git *p) +int verify_pack_index(struct packed_git *p) { off_t index_size; const unsigned char *index_base; git_SHA_CTX ctx; unsigned char sha1[20]; int err = 0; - struct pack_window *w_curs = NULL; if (open_pack_index(p)) return error("packfile %s index not opened", p->pack_name); @@ -154,8 +153,18 @@ int verify_pack(struct packed_git *p) if (hashcmp(sha1, index_base + index_size - 20)) err = error("Packfile index for %s SHA1 mismatch", p->pack_name); + return err; +} + +int verify_pack(struct packed_git *p) +{ + int err = 0; + struct pack_window *w_curs = NULL; + + err |= verify_pack_index(p); + if (!p->index_data) + return -1; - /* Verify pack file */ err |= verify_packfile(p, &w_curs); unuse_pack(&w_curs); @@ -57,6 +57,7 @@ struct pack_idx_entry { extern const char *write_idx_file(const char *index_name, struct pack_idx_entry **objects, int nr_objects, unsigned char *sha1); extern int check_pack_crc(struct packed_git *p, struct pack_window **w_curs, off_t offset, off_t len, unsigned int nr); +extern int verify_pack_index(struct packed_git *); extern int verify_pack(struct packed_git *); extern void fixup_pack_header_footer(int, unsigned char *, const char *, uint32_t, unsigned char *, off_t); extern char *index_pack_lockfile(int fd); @@ -716,7 +716,7 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder, if (add_again(sb, &c->abbrev_commit_hash)) return 1; strbuf_addstr(sb, find_unique_abbrev(commit->object.sha1, - DEFAULT_ABBREV)); + c->pretty_ctx->abbrev)); c->abbrev_commit_hash.len = sb->len - c->abbrev_commit_hash.off; return 1; case 'T': /* tree hash */ @@ -726,7 +726,7 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder, if (add_again(sb, &c->abbrev_tree_hash)) return 1; strbuf_addstr(sb, find_unique_abbrev(commit->tree->object.sha1, - DEFAULT_ABBREV)); + c->pretty_ctx->abbrev)); c->abbrev_tree_hash.len = sb->len - c->abbrev_tree_hash.off; return 1; case 'P': /* parent hashes */ @@ -743,7 +743,8 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder, if (p != commit->parents) strbuf_addch(sb, ' '); strbuf_addstr(sb, find_unique_abbrev( - p->item->object.sha1, DEFAULT_ABBREV)); + p->item->object.sha1, + c->pretty_ctx->abbrev)); } c->abbrev_parent_hashes.len = sb->len - c->abbrev_parent_hashes.off; @@ -295,42 +295,75 @@ void write_name_quotedpfx(const char *pfx, size_t pfxlen, fputc(terminator, fp); } -/* quote path as relative to the given prefix */ -char *quote_path_relative(const char *in, int len, - struct strbuf *out, const char *prefix) +static const char *path_relative(const char *in, int len, + struct strbuf *sb, const char *prefix, + int prefix_len); + +void write_name_quoted_relative(const char *name, size_t len, + const char *prefix, size_t prefix_len, + FILE *fp, int terminator) { - int needquote; + struct strbuf sb = STRBUF_INIT; + + name = path_relative(name, len, &sb, prefix, prefix_len); + write_name_quoted(name, fp, terminator); + + strbuf_release(&sb); +} + +/* + * Give path as relative to prefix. + * + * The strbuf may or may not be used, so do not assume it contains the + * returned path. + */ +static const char *path_relative(const char *in, int len, + struct strbuf *sb, const char *prefix, + int prefix_len) +{ + int off, i; if (len < 0) len = strlen(in); + if (prefix && prefix_len < 0) + prefix_len = strlen(prefix); + + off = 0; + i = 0; + while (i < prefix_len && i < len && prefix[i] == in[i]) { + if (prefix[i] == '/') + off = i + 1; + i++; + } + in += off; + len -= off; + + if (i >= prefix_len) + return in; - /* "../" prefix itself does not need quoting, but "in" might. */ - needquote = next_quote_pos(in, len) < len; - strbuf_setlen(out, 0); - strbuf_grow(out, len); - - if (needquote) - strbuf_addch(out, '"'); - if (prefix) { - int off = 0; - while (prefix[off] && off < len && prefix[off] == in[off]) - if (prefix[off] == '/') { - prefix += off + 1; - in += off + 1; - len -= off + 1; - off = 0; - } else - off++; - - for (; *prefix; prefix++) - if (*prefix == '/') - strbuf_addstr(out, "../"); + strbuf_reset(sb); + strbuf_grow(sb, len); + + while (i < prefix_len) { + if (prefix[i] == '/') + strbuf_addstr(sb, "../"); + i++; } + strbuf_add(sb, in, len); + + return sb->buf; +} - quote_c_style_counted (in, len, out, NULL, 1); +/* quote path as relative to the given prefix */ +char *quote_path_relative(const char *in, int len, + struct strbuf *out, const char *prefix) +{ + struct strbuf sb = STRBUF_INIT; + const char *rel = path_relative(in, len, &sb, prefix, -1); + strbuf_reset(out); + quote_c_style_counted(rel, strlen(rel), out, NULL, 0); + strbuf_release(&sb); - if (needquote) - strbuf_addch(out, '"'); if (!out->len) strbuf_addstr(out, "./"); @@ -54,9 +54,12 @@ extern void quote_two_c_style(struct strbuf *, const char *, const char *, int); extern void write_name_quoted(const char *name, FILE *, int terminator); extern void write_name_quotedpfx(const char *pfx, size_t pfxlen, const char *name, FILE *, int terminator); +extern void write_name_quoted_relative(const char *name, size_t len, + const char *prefix, size_t prefix_len, + FILE *fp, int terminator); /* quote path as relative to the given prefix */ -char *quote_path_relative(const char *in, int len, +extern char *quote_path_relative(const char *in, int len, struct strbuf *out, const char *prefix); /* quoting as a string literal for other languages */ diff --git a/remote-curl.c b/remote-curl.c index b76bfcb3d3..24fbb9a9b9 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -9,7 +9,7 @@ #include "sideband.h" static struct remote *remote; -static const char *url; +static const char *url; /* always ends with a trailing slash */ struct options { int verbosity; @@ -101,7 +101,7 @@ static struct discovery* discover_refs(const char *service) return last; free_discovery(last); - strbuf_addf(&buffer, "%s/info/refs", url); + strbuf_addf(&buffer, "%sinfo/refs", url); if (!prefixcmp(url, "http://") || !prefixcmp(url, "https://")) { is_http = 1; if (!strchr(url, '?')) @@ -120,7 +120,7 @@ static struct discovery* discover_refs(const char *service) strbuf_reset(&buffer); proto_git_candidate = 0; - strbuf_addf(&buffer, "%s/info/refs", url); + strbuf_addf(&buffer, "%sinfo/refs", url); refs_url = strbuf_detach(&buffer, NULL); http_ret = http_get_strbuf(refs_url, &buffer, HTTP_NO_CACHE); @@ -132,6 +132,8 @@ static struct discovery* discover_refs(const char *service) case HTTP_MISSING_TARGET: die("%s not found: did you run git update-server-info on the" " server?", refs_url); + case HTTP_NOAUTH: + die("Authentication failed"); default: http_error(refs_url, http_ret); die("HTTP request failed"); @@ -509,7 +511,7 @@ static int rpc_service(struct rpc_state *rpc, struct discovery *heads) rpc->out = client.out; strbuf_init(&rpc->result, 0); - strbuf_addf(&buf, "%s/%s", url, svc); + strbuf_addf(&buf, "%s%s", url, svc); rpc->service_url = strbuf_detach(&buf, NULL); strbuf_addf(&buf, "Content-Type: application/x-%s-request", svc); @@ -798,11 +800,13 @@ int main(int argc, const char **argv) remote = remote_get(argv[1]); if (argc > 2) { - url = argv[2]; + end_url_with_slash(&buf, argv[2]); } else { - url = remote->url[0]; + end_url_with_slash(&buf, remote->url[0]); } + url = strbuf_detach(&buf, NULL); + http_init(remote); do { diff --git a/sha1_file.c b/sha1_file.c index 1b551e4609..72de38909e 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -102,20 +102,22 @@ static void fill_sha1_path(char *pathbuf, const unsigned char *sha1) */ char *sha1_file_name(const unsigned char *sha1) { - static char *name, *base; + static char buf[PATH_MAX]; + const char *objdir; + int len; - if (!base) { - const char *sha1_file_directory = get_object_directory(); - int len = strlen(sha1_file_directory); - base = xmalloc(len + 60); - memcpy(base, sha1_file_directory, len); - memset(base+len, 0, 60); - base[len] = '/'; - base[len+3] = '/'; - name = base + len + 1; - } - fill_sha1_path(name, sha1); - return base; + objdir = get_object_directory(); + len = strlen(objdir); + + /* '/' + sha1(2) + '/' + sha1(38) + '\0' */ + if (len + 43 > PATH_MAX) + die("insanely long object directory %s", objdir); + memcpy(buf, objdir, len); + buf[len] = '/'; + buf[len+3] = '/'; + buf[len+42] = '\0'; + fill_sha1_path(buf + len + 1, sha1); + return buf; } static char *sha1_get_pack_name(const unsigned char *sha1, @@ -599,6 +601,14 @@ void unuse_pack(struct pack_window **w_cursor) } } +void close_pack_index(struct packed_git *p) +{ + if (p->index_data) { + munmap((void *)p->index_data, p->index_size); + p->index_data = NULL; + } +} + /* * This is used by git-repack in case a newly created pack happens to * contain the same set of objects as an existing one. In that case @@ -620,8 +630,7 @@ void free_pack_by_name(const char *pack_name) close_pack_windows(p); if (p->pack_fd != -1) close(p->pack_fd); - if (p->index_data) - munmap((void *)p->index_data, p->index_size); + close_pack_index(p); free(p->bad_object_sha1); *pp = p->next; free(p); @@ -831,9 +840,8 @@ struct packed_git *add_packed_git(const char *path, int path_len, int local) return p; } -struct packed_git *parse_pack_index(unsigned char *sha1) +struct packed_git *parse_pack_index(unsigned char *sha1, const char *idx_path) { - const char *idx_path = sha1_pack_index_name(sha1); const char *path = sha1_pack_name(sha1); struct packed_git *p = alloc_packed_git(strlen(path) + 1); diff --git a/shortlog.h b/shortlog.h index bc02cc29ef..de4f86fb97 100644 --- a/shortlog.h +++ b/shortlog.h @@ -12,6 +12,7 @@ struct shortlog { int in1; int in2; int user_format; + int abbrev; char *common_repo_prefix; int email; @@ -84,6 +84,12 @@ appropriately before running "make". implied by other options like --valgrind and GIT_TEST_INSTALLED. +--root=<directory>:: + Create "trash" directories used to store all temporary data during + testing under <directory>, instead of the t/ directory. + Using this option with a RAM-based filesystem (such as tmpfs) + can massively speed up the test suite. + You can also set the GIT_TEST_INSTALLED environment variable to the bindir of an existing git installation to test that installation. You still need to have built this git sandbox, from which various diff --git a/t/t6000lib.sh b/t/lib-t6000.sh index 985d517a1c..985d517a1c 100644 --- a/t/t6000lib.sh +++ b/t/lib-t6000.sh diff --git a/t/t0003-attributes.sh b/t/t0003-attributes.sh index 1c77192eb3..53bd7fcc4a 100755 --- a/t/t0003-attributes.sh +++ b/t/t0003-attributes.sh @@ -20,8 +20,12 @@ test_expect_success 'setup' ' mkdir -p a/b/d a/c && ( + echo "[attr]notest !test" echo "f test=f" echo "a/i test=a/i" + echo "onoff test -test" + echo "offon -test test" + echo "no notest" ) >.gitattributes && ( echo "g test=a/g" && @@ -30,6 +34,7 @@ test_expect_success 'setup' ' ( echo "h test=a/b/h" && echo "d/* test=a/b/d/*" + echo "d/yes notest" ) >a/b/.gitattributes ' @@ -44,6 +49,11 @@ test_expect_success 'attribute test' ' attr_check b/g unspecified && attr_check a/b/h a/b/h && attr_check a/b/d/g "a/b/d/*" + attr_check onoff unset + attr_check offon set + attr_check no unspecified + attr_check a/b/d/no "a/b/d/*" + attr_check a/b/d/yes unspecified ' @@ -58,6 +68,11 @@ a/b/g: test: a/b/g b/g: test: unspecified a/b/h: test: a/b/h a/b/d/g: test: a/b/d/* +onoff: test: unset +offon: test: set +no: test: unspecified +a/b/d/no: test: a/b/d/* +a/b/d/yes: test: unspecified EOF sed -e "s/:.*//" < expect | git check-attr --stdin test > actual && diff --git a/t/t2007-checkout-symlink.sh b/t/t2007-checkout-symlink.sh index 20f33436d0..27e2127afe 100755 --- a/t/t2007-checkout-symlink.sh +++ b/t/t2007-checkout-symlink.sh @@ -44,8 +44,10 @@ test_expect_success 'switch from symlink to dir' ' ' -rm -fr frotz xyzzy nitfol && -git checkout -f master || exit +test_expect_success 'Remove temporary directories & switch to master' ' + rm -fr frotz xyzzy nitfol && + git checkout -f master +' test_expect_success 'switch from dir to symlink' ' diff --git a/t/t2106-update-index-assume-unchanged.sh b/t/t2106-update-index-assume-unchanged.sh new file mode 100755 index 0000000000..99d858c6b7 --- /dev/null +++ b/t/t2106-update-index-assume-unchanged.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +test_description='git update-index --assume-unchanged test. +' + +. ./test-lib.sh + +test_expect_success 'setup' \ + ': >file && + git add file && + git commit -m initial && + git branch other && + echo upstream >file && + git add file && + git commit -m upstream' + +test_expect_success 'do not switch branches with dirty file' \ + 'git reset --hard && + git checkout other && + echo dirt >file && + git update-index --assume-unchanged file && + test_must_fail git checkout master' + +test_done diff --git a/t/t3307-notes-man.sh b/t/t3307-notes-man.sh new file mode 100755 index 0000000000..3269f2eebd --- /dev/null +++ b/t/t3307-notes-man.sh @@ -0,0 +1,38 @@ +#!/bin/sh + +test_description='Examples from the git-notes man page + +Make sure the manual is not full of lies.' + +. ./test-lib.sh + +test_expect_success 'setup' ' + test_commit A && + test_commit B && + test_commit C +' + +test_expect_success 'example 1: notes to add an Acked-by line' ' + cat <<-\EOF >expect && + B + + Notes: + Acked-by: A C Ker <acker@example.com> + EOF + git notes add -m "Acked-by: A C Ker <acker@example.com>" B && + git show -s B^{commit} >log && + tail -n 4 log >actual && + test_cmp expect actual +' + +test_expect_success 'example 2: binary notes' ' + cp "$TEST_DIRECTORY"/test4012.png . + git checkout B && + blob=$(git hash-object -w test4012.png) && + git notes --ref=logo add -C "$blob" && + git notes --ref=logo copy B C && + git notes --ref=logo show C >actual && + test_cmp test4012.png actual +' + +test_done diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index f20ea38411..6668907ec5 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -181,6 +181,12 @@ test_expect_success '-p handles "no changes" gracefully' ' test $HEAD = $(git rev-parse HEAD) ' +test_expect_failure 'exchange two commits with -p' ' + FAKE_LINES="2 1" git rebase -i -p HEAD~2 && + test H = $(git cat-file commit HEAD^ | sed -ne \$p) && + test G = $(git cat-file commit HEAD | sed -ne \$p) +' + test_expect_success 'preserve merges with -p' ' git checkout -b to-be-preserved master^ && : > unrelated-file && diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh index 476e5ec038..8fe14ccc54 100755 --- a/t/t3903-stash.sh +++ b/t/t3903-stash.sh @@ -228,4 +228,154 @@ test_expect_success 'stash --invalid-option' ' test bar,bar2 = $(cat file),$(cat file2) ' +test_expect_success 'stash an added file' ' + git reset --hard && + echo new >file3 && + git add file3 && + git stash save "added file" && + ! test -r file3 && + git stash apply && + test new = "$(cat file3)" +' + +test_expect_success 'stash rm then recreate' ' + git reset --hard && + git rm file && + echo bar7 >file && + git stash save "rm then recreate" && + test bar = "$(cat file)" && + git stash apply && + test bar7 = "$(cat file)" +' + +test_expect_success 'stash rm and ignore' ' + git reset --hard && + git rm file && + echo file >.gitignore && + git stash save "rm and ignore" && + test bar = "$(cat file)" && + test file = "$(cat .gitignore)" + git stash apply && + ! test -r file && + test file = "$(cat .gitignore)" +' + +test_expect_success 'stash rm and ignore (stage .gitignore)' ' + git reset --hard && + git rm file && + echo file >.gitignore && + git add .gitignore && + git stash save "rm and ignore (stage .gitignore)" && + test bar = "$(cat file)" && + ! test -r .gitignore + git stash apply && + ! test -r file && + test file = "$(cat .gitignore)" +' + +test_expect_success SYMLINKS 'stash file to symlink' ' + git reset --hard && + rm file && + ln -s file2 file && + git stash save "file to symlink" && + test -f file && + test bar = "$(cat file)" && + git stash apply && + case "$(ls -l file)" in *" file -> file2") :;; *) false;; esac +' + +test_expect_success SYMLINKS 'stash file to symlink (stage rm)' ' + git reset --hard && + git rm file && + ln -s file2 file && + git stash save "file to symlink (stage rm)" && + test -f file && + test bar = "$(cat file)" && + git stash apply && + case "$(ls -l file)" in *" file -> file2") :;; *) false;; esac +' + +test_expect_success SYMLINKS 'stash file to symlink (full stage)' ' + git reset --hard && + rm file && + ln -s file2 file && + git add file && + git stash save "file to symlink (full stage)" && + test -f file && + test bar = "$(cat file)" && + git stash apply && + case "$(ls -l file)" in *" file -> file2") :;; *) false;; esac +' + +# This test creates a commit with a symlink used for the following tests + +test_expect_success SYMLINKS 'stash symlink to file' ' + git reset --hard && + ln -s file filelink && + git add filelink && + git commit -m "Add symlink" && + rm filelink && + cp file filelink && + git stash save "symlink to file" && + test -h filelink && + case "$(ls -l filelink)" in *" filelink -> file") :;; *) false;; esac && + git stash apply && + ! test -h filelink && + test bar = "$(cat file)" +' + +test_expect_success SYMLINKS 'stash symlink to file (stage rm)' ' + git reset --hard && + git rm filelink && + cp file filelink && + git stash save "symlink to file (stage rm)" && + test -h filelink && + case "$(ls -l filelink)" in *" filelink -> file") :;; *) false;; esac && + git stash apply && + ! test -h filelink && + test bar = "$(cat file)" +' + +test_expect_success SYMLINKS 'stash symlink to file (full stage)' ' + git reset --hard && + rm filelink && + cp file filelink && + git add filelink && + git stash save "symlink to file (full stage)" && + test -h filelink && + case "$(ls -l filelink)" in *" filelink -> file") :;; *) false;; esac && + git stash apply && + ! test -h filelink && + test bar = "$(cat file)" +' + +test_expect_failure 'stash directory to file' ' + git reset --hard && + mkdir dir && + echo foo >dir/file && + git add dir/file && + git commit -m "Add file in dir" && + rm -fr dir && + echo bar >dir && + git stash save "directory to file" && + test -d dir && + test foo = "$(cat dir/file)" && + test_must_fail git stash apply && + test bar = "$(cat dir)" && + git reset --soft HEAD^ +' + +test_expect_failure 'stash file to directory' ' + git reset --hard && + rm file && + mkdir file && + echo foo >file/file && + git stash save "file to directory" && + test -f file && + test bar = "$(cat file)" && + git stash apply && + test -f file/file && + test foo = "$(cat file/file)" +' + test_done diff --git a/t/t4015-diff-whitespace.sh b/t/t4015-diff-whitespace.sh index 90f3342373..7e78851a11 100755 --- a/t/t4015-diff-whitespace.sh +++ b/t/t4015-diff-whitespace.sh @@ -396,6 +396,43 @@ test_expect_success 'whitespace-only changes not reported' ' test_cmp expect actual ' +cat <<EOF >expect +diff --git a/x b/z +similarity index NUM% +rename from x +rename to z +index 380c32a..a97b785 100644 +EOF +test_expect_success 'whitespace-only changes reported across renames' ' + git reset --hard && + for i in 1 2 3 4 5 6 7 8 9; do echo "$i$i$i$i$i$i"; done >x && + git add x && + git commit -m "base" && + sed -e "5s/^/ /" x >z && + git rm x && + git add z && + git diff -w -M --cached | + sed -e "/^similarity index /s/[0-9][0-9]*/NUM/" >actual && + test_cmp expect actual +' + +cat >expected <<\EOF +diff --git a/empty b/void +similarity index 100% +rename from empty +rename to void +EOF + +test_expect_success 'rename empty' ' + git reset --hard && + >empty && + git add empty && + git commit -m empty && + git mv empty void && + git diff -w --cached -M >current && + test_cmp expected current +' + test_expect_success 'combined diff with autocrlf conversion' ' git reset --hard && diff --git a/t/t4043-diff-rename-binary.sh b/t/t4043-diff-rename-binary.sh new file mode 100755 index 0000000000..06012811a1 --- /dev/null +++ b/t/t4043-diff-rename-binary.sh @@ -0,0 +1,45 @@ +#!/bin/sh +# +# Copyright (c) 2010 Jakub Narebski, Christian Couder +# + +test_description='Move a binary file' + +. ./test-lib.sh + + +test_expect_success 'prepare repository' ' + git init && + echo foo > foo && + echo "barQ" | q_to_nul > bar && + git add . && + git commit -m "Initial commit" +' + +test_expect_success 'move the files into a "sub" directory' ' + mkdir sub && + git mv bar foo sub/ && + git commit -m "Moved to sub/" +' + +cat > expected <<\EOF + bar => sub/bar | Bin 5 -> 5 bytes + foo => sub/foo | 0 + 2 files changed, 0 insertions(+), 0 deletions(-) + +diff --git a/bar b/sub/bar +similarity index 100% +rename from bar +rename to sub/bar +diff --git a/foo b/sub/foo +similarity index 100% +rename from foo +rename to sub/foo +EOF + +test_expect_success 'git show -C -C report renames' ' + git show -C -C --raw --binary --stat | tail -n 12 > current && + test_cmp expected current +' + +test_done diff --git a/t/t4124-apply-ws-rule.sh b/t/t4124-apply-ws-rule.sh index fb9ad247bf..451d75e3fb 100755 --- a/t/t4124-apply-ws-rule.sh +++ b/t/t4124-apply-ws-rule.sh @@ -325,6 +325,18 @@ test_expect_success 'two missing blank lines at end with --whitespace=fix' ' test_cmp one expect ' +test_expect_success 'missing blank line at end, insert before end, --whitespace=fix' ' + { echo a; echo; } >one && + git add one && + { echo b; echo a; echo; } >one && + cp one expect && + git diff -- one >patch && + echo a >one && + test_must_fail git apply patch && + git apply --whitespace=fix patch && + test_cmp one expect +' + test_expect_success 'shrink file with tons of missing blanks at end of file' ' { echo a; echo b; echo c; } >one && cp one no-blank-lines && diff --git a/t/t4134-apply-submodule.sh b/t/t4134-apply-submodule.sh new file mode 100755 index 0000000000..1b82f93cff --- /dev/null +++ b/t/t4134-apply-submodule.sh @@ -0,0 +1,38 @@ +#!/bin/sh +# +# Copyright (c) 2010 Peter Collingbourne +# + +test_description='git apply submodule tests' + +. ./test-lib.sh + +test_expect_success setup ' + cat > create-sm.patch <<EOF +diff --git a/dir/sm b/dir/sm +new file mode 160000 +index 0000000..0123456 +--- /dev/null ++++ b/dir/sm +@@ -0,0 +1 @@ ++Subproject commit 0123456789abcdef0123456789abcdef01234567 +EOF + cat > remove-sm.patch <<EOF +diff --git a/dir/sm b/dir/sm +deleted file mode 160000 +index 0123456..0000000 +--- a/dir/sm ++++ /dev/null +@@ -1 +0,0 @@ +-Subproject commit 0123456789abcdef0123456789abcdef01234567 +EOF +' + +test_expect_success 'removing a submodule also removes all leading subdirectories' ' + git apply --index create-sm.patch && + test -d dir/sm && + git apply --index remove-sm.patch && + test \! -d dir +' + +test_done diff --git a/t/t4201-shortlog.sh b/t/t4201-shortlog.sh index a01e55bf6b..cdb70b4b33 100755 --- a/t/t4201-shortlog.sh +++ b/t/t4201-shortlog.sh @@ -8,30 +8,93 @@ test_description='git shortlog . ./test-lib.sh -echo 1 > a1 -git add a1 -tree=$(git write-tree) -commit=$( (echo "Test"; echo) | git commit-tree $tree ) -git update-ref HEAD $commit +test_expect_success 'setup' ' + echo 1 >a1 && + git add a1 && + tree=$(git write-tree) && + commit=$(printf "%s\n" "Test" "" | git commit-tree "$tree") && + git update-ref HEAD "$commit" && + + echo 2 >a1 && + git commit --quiet -m "This is a very, very long first line for the commit message to see if it is wrapped correctly" a1 && + + # test if the wrapping is still valid + # when replacing all is by treble clefs. + echo 3 >a1 && + git commit --quiet -m "$( + echo "This is a very, very long first line for the commit message to see if it is wrapped correctly" | + sed "s/i/1234/g" | + tr 1234 "\360\235\204\236")" a1 && + + # now fsck up the utf8 + git config i18n.commitencoding non-utf-8 && + echo 4 >a1 && + git commit --quiet -m "$( + echo "This is a very, very long first line for the commit message to see if it is wrapped correctly" | + sed "s/i/1234/g" | + tr 1234 "\370\235\204\236")" a1 && + + echo 5 >a1 && + git commit --quiet -m "a 12 34 56 78" a1 + + echo 6 >a1 && + git commit --quiet -m "Commit by someone else" \ + --author="Someone else <not!me>" a1 && + + cat >expect.template <<-\EOF + A U Thor (5): + SUBJECT + SUBJECT + SUBJECT + SUBJECT + SUBJECT + + Someone else (1): + SUBJECT + + EOF +' -echo 2 > a1 -git commit --quiet -m "This is a very, very long first line for the commit message to see if it is wrapped correctly" a1 +fuzz() { + file=$1 && + sed " + s/$_x40/OBJECT_NAME/g + s/$_x05/OBJID/g + s/^ \{6\}[CTa].*/ SUBJECT/g + s/^ \{8\}[^ ].*/ CONTINUATION/g + " <"$file" >"$file.fuzzy" && + sed "/CONTINUATION/ d" <"$file.fuzzy" +} -# test if the wrapping is still valid when replacing all i's by treble clefs. -echo 3 > a1 -git commit --quiet -m "$(echo "This is a very, very long first line for the commit message to see if it is wrapped correctly" | sed "s/i/1234/g" | tr 1234 '\360\235\204\236')" a1 +test_expect_success 'default output format' ' + git shortlog HEAD >log && + fuzz log >log.predictable && + test_cmp expect.template log.predictable +' -# now fsck up the utf8 -git config i18n.commitencoding non-utf-8 -echo 4 > a1 -git commit --quiet -m "$(echo "This is a very, very long first line for the commit message to see if it is wrapped correctly" | sed "s/i/1234/g" | tr 1234 '\370\235\204\236')" a1 +test_expect_success 'pretty format' ' + sed s/SUBJECT/OBJECT_NAME/ expect.template >expect && + git shortlog --format="%H" HEAD >log && + fuzz log >log.predictable && + test_cmp expect log.predictable +' -echo 5 > a1 -git commit --quiet -m "a 12 34 56 78" a1 +test_expect_success '--abbrev' ' + sed s/SUBJECT/OBJID/ expect.template >expect && + git shortlog --format="%h" --abbrev=5 HEAD >log && + fuzz log >log.predictable && + test_cmp expect log.predictable +' -git shortlog -w HEAD > out +test_expect_success 'output from user-defined format is re-wrapped' ' + sed "s/SUBJECT/two lines/" expect.template >expect && + git shortlog --format="two%nlines" HEAD >log && + fuzz log >log.predictable && + test_cmp expect log.predictable +' -cat > expect << EOF +test_expect_success 'shortlog wrapping' ' + cat >expect <<\EOF && A U Thor (5): Test This is a very, very long first line for the commit message to see if @@ -43,14 +106,19 @@ A U Thor (5): a 12 34 56 78 -EOF - -test_expect_success 'shortlog wrapping' 'test_cmp expect out' +Someone else (1): + Commit by someone else -git log HEAD > log -GIT_DIR=non-existing git shortlog -w < log > out +EOF + git shortlog -w HEAD >out && + test_cmp expect out +' -test_expect_success 'shortlog from non-git directory' 'test_cmp expect out' +test_expect_success 'shortlog from non-git directory' ' + git log HEAD >log && + GIT_DIR=non-existing git shortlog -w <log >out && + test_cmp expect out +' iconvfromutf8toiso88591() { printf "%s" "$*" | iconv -f UTF-8 -t ISO8859-1 diff --git a/t/t5541-http-push.sh b/t/t5541-http-push.sh index 795dc2bcdf..17e1bdc5a8 100755 --- a/t/t5541-http-push.sh +++ b/t/t5541-http-push.sh @@ -34,8 +34,34 @@ test_expect_success 'setup remote repository' ' mv test_repo.git "$HTTPD_DOCUMENT_ROOT_PATH" ' -test_expect_success 'clone remote repository' ' +cat >exp <<EOF +GET /smart/test_repo.git/info/refs?service=git-upload-pack HTTP/1.1 200 +POST /smart/test_repo.git/git-upload-pack HTTP/1.1 200 +EOF +test_expect_success 'no empty path components' ' + # In the URL, add a trailing slash, and see if git appends yet another + # slash. cd "$ROOT_PATH" && + git clone $HTTPD_URL/smart/test_repo.git/ test_repo_clone && + + sed -e " + s/^.* \"// + s/\"// + s/ [1-9][0-9]*\$// + s/^GET /GET / + " >act <"$HTTPD_ROOT_PATH"/access.log && + + # Clear the log, so that it does not affect the "used receive-pack + # service" test which reads the log too. + # + # We do this before the actual comparison to ensure the log is cleared. + echo > "$HTTPD_ROOT_PATH"/access.log && + + test_cmp exp act +' + +test_expect_success 'clone remote repository' ' + rm -rf test_repo_clone && git clone $HTTPD_URL/smart/test_repo.git test_repo_clone ' @@ -68,6 +94,7 @@ test_expect_success 'create and delete remote branch' ' ' cat >exp <<EOF + GET /smart/test_repo.git/info/refs?service=git-upload-pack HTTP/1.1 200 POST /smart/test_repo.git/git-upload-pack HTTP/1.1 200 GET /smart/test_repo.git/info/refs?service=git-receive-pack HTTP/1.1 200 diff --git a/t/t5550-http-fetch.sh b/t/t5550-http-fetch.sh index 8cfce969bc..fc675b50ad 100755 --- a/t/t5550-http-fetch.sh +++ b/t/t5550-http-fetch.sh @@ -55,12 +55,43 @@ test_expect_success 'http remote detects correct HEAD' ' test_expect_success 'fetch packed objects' ' cp -R "$HTTPD_DOCUMENT_ROOT_PATH"/repo.git "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git && - cd "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git && - git --bare repack && - git --bare prune-packed && + (cd "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git && + git --bare repack && + git --bare prune-packed + ) && git clone $HTTPD_URL/dumb/repo_pack.git ' +test_expect_success 'fetch notices corrupt pack' ' + cp -R "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git "$HTTPD_DOCUMENT_ROOT_PATH"/repo_bad1.git && + (cd "$HTTPD_DOCUMENT_ROOT_PATH"/repo_bad1.git && + p=`ls objects/pack/pack-*.pack` && + chmod u+w $p && + printf %0256d 0 | dd of=$p bs=256 count=1 seek=1 conv=notrunc + ) && + mkdir repo_bad1.git && + (cd repo_bad1.git && + git --bare init && + test_must_fail git --bare fetch $HTTPD_URL/dumb/repo_bad1.git && + test 0 = `ls objects/pack/pack-*.pack | wc -l` + ) +' + +test_expect_success 'fetch notices corrupt idx' ' + cp -R "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git "$HTTPD_DOCUMENT_ROOT_PATH"/repo_bad2.git && + (cd "$HTTPD_DOCUMENT_ROOT_PATH"/repo_bad2.git && + p=`ls objects/pack/pack-*.idx` && + chmod u+w $p && + printf %0256d 0 | dd of=$p bs=256 count=1 seek=1 conv=notrunc + ) && + mkdir repo_bad2.git && + (cd repo_bad2.git && + git --bare init && + test_must_fail git --bare fetch $HTTPD_URL/dumb/repo_bad2.git && + test 0 = `ls objects/pack | wc -l` + ) +' + test_expect_success 'did not use upload-pack service' ' grep '/git-upload-pack' <"$HTTPD_ROOT_PATH"/access.log >act : >exp diff --git a/t/t5704-bundle.sh b/t/t5704-bundle.sh index a8f4419e61..ddc3dc52f4 100755 --- a/t/t5704-bundle.sh +++ b/t/t5704-bundle.sh @@ -30,4 +30,20 @@ test_expect_success 'tags can be excluded by rev-list options' ' ' +test_expect_failure 'bundle --stdin' ' + + echo master | git bundle create stdin-bundle.bdl --stdin && + git ls-remote stdin-bundle.bdl >output && + grep master output + +' + +test_expect_failure 'bundle --stdin <rev-list options>' ' + + echo master | git bundle create hybrid-bundle.bdl --stdin tag && + git ls-remote hybrid-bundle.bdl >output && + grep master output + +' + test_done diff --git a/t/t6002-rev-list-bisect.sh b/t/t6002-rev-list-bisect.sh index b4e8fbaa5e..fb07536a0f 100755 --- a/t/t6002-rev-list-bisect.sh +++ b/t/t6002-rev-list-bisect.sh @@ -5,7 +5,7 @@ test_description='Tests git rev-list --bisect functionality' . ./test-lib.sh -. "$TEST_DIRECTORY"/t6000lib.sh # t6xxx specific functions +. "$TEST_DIRECTORY"/lib-t6000.sh # t6xxx specific functions # usage: test_bisection max-diff bisect-option head ^prune... # diff --git a/t/t6003-rev-list-topo-order.sh b/t/t6003-rev-list-topo-order.sh index 2c73f2da7b..e4c52b0214 100755 --- a/t/t6003-rev-list-topo-order.sh +++ b/t/t6003-rev-list-topo-order.sh @@ -6,7 +6,7 @@ test_description='Tests git rev-list --topo-order functionality' . ./test-lib.sh -. "$TEST_DIRECTORY"/t6000lib.sh # t6xxx specific functions +. "$TEST_DIRECTORY"/lib-t6000.sh # t6xxx specific functions list_duplicates() { diff --git a/t/t6006-rev-list-format.sh b/t/t6006-rev-list-format.sh index a49b7c5722..e8fde5c19c 100755 --- a/t/t6006-rev-list-format.sh +++ b/t/t6006-rev-list-format.sh @@ -191,6 +191,31 @@ test_expect_success 'add LF before non-empty (2)' ' grep "^$" actual ' +test_expect_success '--abbrev' ' + echo SHORT SHORT SHORT >expect2 && + echo LONG LONG LONG >expect3 && + git log -1 --format="%h %h %h" HEAD >actual1 && + git log -1 --abbrev=5 --format="%h %h %h" HEAD >actual2 && + git log -1 --abbrev=5 --format="%H %H %H" HEAD >actual3 && + sed -e "s/$_x40/LONG/g" -e "s/$_x05/SHORT/g" <actual2 >fuzzy2 && + sed -e "s/$_x40/LONG/g" -e "s/$_x05/SHORT/g" <actual3 >fuzzy3 && + test_cmp expect2 fuzzy2 && + test_cmp expect3 fuzzy3 && + ! test_cmp actual1 actual2 +' + +test_expect_success '%H is not affected by --abbrev-commit' ' + git log -1 --format=%H --abbrev-commit --abbrev=20 HEAD >actual && + len=$(wc -c <actual) && + test $len = 41 +' + +test_expect_success '%h is not affected by --abbrev-commit' ' + git log -1 --format=%h --abbrev-commit --abbrev=20 HEAD >actual && + len=$(wc -c <actual) && + test $len = 21 +' + test_expect_success '"%h %gD: %gs" is same as git-reflog' ' git reflog >expect && git log -g --format="%h %gD: %gs" >actual && @@ -203,6 +228,12 @@ test_expect_success '"%h %gD: %gs" is same as git-reflog (with date)' ' test_cmp expect actual ' +test_expect_success '"%h %gD: %gs" is same as git-reflog (with --abbrev)' ' + git reflog --abbrev=13 --date=raw >expect && + git log -g --abbrev=13 --format="%h %gD: %gs" --date=raw >actual && + test_cmp expect actual +' + test_expect_success '%gd shortens ref name' ' echo "master@{0}" >expect.gd-short && git log -g -1 --format=%gd refs/heads/master >actual.gd-short && diff --git a/t/t6101-rev-parse-parents.sh b/t/t6101-rev-parse-parents.sh index f105fab98e..e673c25e94 100755 --- a/t/t6101-rev-parse-parents.sh +++ b/t/t6101-rev-parse-parents.sh @@ -6,7 +6,7 @@ test_description='Test git rev-parse with different parent options' . ./test-lib.sh -. "$TEST_DIRECTORY"/t6000lib.sh # t6xxx specific functions +. "$TEST_DIRECTORY"/lib-t6000.sh # t6xxx specific functions date >path0 git update-index --add path0 diff --git a/t/t6120-describe.sh b/t/t6120-describe.sh index 065deadc29..876d1ab743 100755 --- a/t/t6120-describe.sh +++ b/t/t6120-describe.sh @@ -8,7 +8,7 @@ test_description='test describe o----o----o----o----o----. / \ A c / .------------o---o---o - D e + D,R e ' . ./test-lib.sh @@ -68,6 +68,8 @@ test_expect_success setup ' echo D >another && git add another && git commit -m D && test_tick && git tag -a -m D D && + test_tick && + git tag -a -m R R && test_tick && echo DD >another && git commit -a -m another && @@ -89,10 +91,10 @@ test_expect_success setup ' check_describe A-* HEAD check_describe A-* HEAD^ -check_describe D-* HEAD^^ +check_describe R-* HEAD^^ check_describe A-* HEAD^^2 check_describe B HEAD^^2^ -check_describe D-* HEAD^^^ +check_describe R-* HEAD^^^ check_describe c-* --tags HEAD check_describe c-* --tags HEAD^ diff --git a/t/t7005-editor.sh b/t/t7005-editor.sh index 5257f4d261..6355698c6b 100755 --- a/t/t7005-editor.sh +++ b/t/t7005-editor.sh @@ -13,7 +13,7 @@ test_expect_success 'determine default editor' ' ' -if ! expr "$vi" : '^[a-z]*$' >/dev/null +if ! expr "$vi" : '[a-z]*$' >/dev/null then vi= fi diff --git a/t/t7006-pager.sh b/t/t7006-pager.sh index d9202d5af5..0f6b367832 100755 --- a/t/t7006-pager.sh +++ b/t/t7006-pager.sh @@ -4,17 +4,24 @@ test_description='Test automatic use of a pager.' . ./test-lib.sh -rm -f stdout_is_tty +cleanup_fail() { + echo >&2 cleanup failed + (exit 1) +} + test_expect_success 'set up terminal for tests' ' + rm -f stdout_is_tty || + cleanup_fail && + if test -t 1 then - : > stdout_is_tty + >stdout_is_tty elif test_have_prereq PERL && "$PERL_PATH" "$TEST_DIRECTORY"/t7006/test-terminal.perl \ sh -c "test -t 1" then - : > test_terminal_works + >test_terminal_works fi ' @@ -32,53 +39,68 @@ else say no usable terminal, so skipping some tests fi -unset GIT_PAGER GIT_PAGER_IN_USE -git config --unset core.pager -PAGER='cat > paginated.out' -export PAGER - test_expect_success 'setup' ' + unset GIT_PAGER GIT_PAGER_IN_USE && + test_might_fail git config --unset core.pager && + + PAGER="cat >paginated.out" && + export PAGER && + test_commit initial ' -rm -f paginated.out test_expect_success TTY 'some commands use a pager' ' + rm -f paginated.out || + cleanup_fail && + test_terminal git log && test -e paginated.out ' -rm -f paginated.out test_expect_success TTY 'some commands do not use a pager' ' + rm -f paginated.out || + cleanup_fail && + test_terminal git rev-list HEAD && ! test -e paginated.out ' -rm -f paginated.out test_expect_success 'no pager when stdout is a pipe' ' + rm -f paginated.out || + cleanup_fail && + git log | cat && ! test -e paginated.out ' -rm -f paginated.out test_expect_success 'no pager when stdout is a regular file' ' - git log > file && + rm -f paginated.out || + cleanup_fail && + + git log >file && ! test -e paginated.out ' -rm -f paginated.out test_expect_success TTY 'git --paginate rev-list uses a pager' ' + rm -f paginated.out || + cleanup_fail && + test_terminal git --paginate rev-list HEAD && test -e paginated.out ' -rm -f file paginated.out test_expect_success 'no pager even with --paginate when stdout is a pipe' ' + rm -f file paginated.out || + cleanup_fail && + git --paginate log | cat && ! test -e paginated.out ' -rm -f paginated.out test_expect_success TTY 'no pager with --no-pager' ' + rm -f paginated.out || + cleanup_fail && + test_terminal git --no-pager log && ! test -e paginated.out ' @@ -86,88 +108,119 @@ test_expect_success TTY 'no pager with --no-pager' ' # A colored commit log will begin with an appropriate ANSI escape # for the first color; the text "commit" comes later. colorful() { - read firstline < $1 - ! expr "$firstline" : "^[a-zA-Z]" >/dev/null + read firstline <$1 + ! expr "$firstline" : "[a-zA-Z]" >/dev/null } -rm -f colorful.log colorless.log test_expect_success 'tests can detect color' ' - git log --no-color > colorless.log && - git log --color > colorful.log && + rm -f colorful.log colorless.log || + cleanup_fail && + + git log --no-color >colorless.log && + git log --color >colorful.log && ! colorful colorless.log && colorful colorful.log ' -rm -f colorless.log -git config color.ui auto test_expect_success 'no color when stdout is a regular file' ' - git log > colorless.log && + rm -f colorless.log && + git config color.ui auto || + cleanup_fail && + + git log >colorless.log && ! colorful colorless.log ' -rm -f paginated.out -git config color.ui auto test_expect_success TTY 'color when writing to a pager' ' - TERM=vt100 test_terminal git log && + rm -f paginated.out && + git config color.ui auto || + cleanup_fail && + + ( + TERM=vt100 && + export TERM && + test_terminal git log + ) && colorful paginated.out ' -rm -f colorful.log -git config color.ui auto test_expect_success 'color when writing to a file intended for a pager' ' - TERM=vt100 GIT_PAGER_IN_USE=true git log > colorful.log && + rm -f colorful.log && + git config color.ui auto || + cleanup_fail && + + ( + TERM=vt100 && + GIT_PAGER_IN_USE=true && + export TERM GIT_PAGER_IN_USE && + git log >colorful.log + ) && colorful colorful.log ' -unset PAGER GIT_PAGER -git config --unset core.pager test_expect_success 'determine default pager' ' + unset PAGER GIT_PAGER && + test_might_fail git config --unset core.pager || + cleanup_fail && + less=$(git var GIT_PAGER) && test -n "$less" ' -if expr "$less" : '^[a-z]*$' > /dev/null && test_have_prereq TTY +if expr "$less" : '[a-z][a-z]*$' >/dev/null && test_have_prereq TTY then test_set_prereq SIMPLEPAGER fi -unset PAGER GIT_PAGER -git config --unset core.pager -rm -f default_pager_used test_expect_success SIMPLEPAGER 'default pager is used by default' ' - cat > $less <<-EOF && - #!$SHELL_PATH - wc > default_pager_used + unset PAGER GIT_PAGER && + test_might_fail git config --unset core.pager && + rm -f default_pager_used || + cleanup_fail && + + cat >$less <<-\EOF && + #!/bin/sh + wc >default_pager_used EOF chmod +x $less && - PATH=.:$PATH test_terminal git log && + ( + PATH=.:$PATH && + export PATH && + test_terminal git log + ) && test -e default_pager_used ' -unset GIT_PAGER -git config --unset core.pager -rm -f PAGER_used test_expect_success TTY 'PAGER overrides default pager' ' - PAGER="wc > PAGER_used" && + unset GIT_PAGER && + test_might_fail git config --unset core.pager && + rm -f PAGER_used || + cleanup_fail && + + PAGER="wc >PAGER_used" && export PAGER && test_terminal git log && test -e PAGER_used ' -unset GIT_PAGER -rm -f core.pager_used test_expect_success TTY 'core.pager overrides PAGER' ' + unset GIT_PAGER && + rm -f core.pager_used || + cleanup_fail && + PAGER=wc && export PAGER && - git config core.pager "wc > core.pager_used" && + git config core.pager "wc >core.pager_used" && test_terminal git log && test -e core.pager_used ' -rm -f GIT_PAGER_used test_expect_success TTY 'GIT_PAGER overrides core.pager' ' + rm -f GIT_PAGER_used || + cleanup_fail && + git config core.pager wc && - GIT_PAGER="wc > GIT_PAGER_used" && + GIT_PAGER="wc >GIT_PAGER_used" && export GIT_PAGER && test_terminal git log && test -e GIT_PAGER_used diff --git a/t/t7010-setup.sh b/t/t7010-setup.sh index d8a7c79852..0335a9a158 100755 --- a/t/t7010-setup.sh +++ b/t/t7010-setup.sh @@ -103,14 +103,10 @@ test_expect_success 'git ls-files (relative #3)' ' git add a && ( cd a/b && - if git ls-files "../e/f" - then - echo Gaah, should have failed - exit 1 - else - : happy - fi - ) + git ls-files "../e/f" + ) >current && + echo ../e/f >expect && + test_cmp expect current ' diff --git a/t/t7502-commit.sh b/t/t7502-commit.sh index 95044668ee..ac2e187a57 100755 --- a/t/t7502-commit.sh +++ b/t/t7502-commit.sh @@ -4,8 +4,76 @@ test_description='git commit porcelain-ish' . ./test-lib.sh +# Arguments: [<prefix] [<commit message>] [<commit options>] +check_summary_oneline() { + test_tick && + git commit ${3+"$3"} -m "$2" | head -1 > act && + + # branch name + SUMMARY_PREFIX="$(git name-rev --name-only HEAD)" && + + # append the "special" prefix, like "root-commit", "detached HEAD" + if test -n "$1" + then + SUMMARY_PREFIX="$SUMMARY_PREFIX ($1)" + fi + + # abbrev SHA-1 + SUMMARY_POSTFIX="$(git log -1 --pretty='format:%h')" + echo "[$SUMMARY_PREFIX $SUMMARY_POSTFIX] $2" >exp && + + test_cmp exp act +} + +test_expect_success 'output summary format' ' + + echo new >file1 && + git add file1 && + check_summary_oneline "root-commit" "initial" && + + echo change >>file1 && + git add file1 && + check_summary_oneline "" "a change" +' + +test_expect_success 'output summary format for commit with an empty diff' ' + + check_summary_oneline "" "empty" "--allow-empty" +' + +test_expect_success 'output summary format for merges' ' + + git checkout -b recursive-base && + test_commit base file1 && + + git checkout -b recursive-a recursive-base && + test_commit commit-a file1 && + + git checkout -b recursive-b recursive-base && + test_commit commit-b file1 && + + # conflict + git checkout recursive-a && + test_must_fail git merge recursive-b && + # resolve the conflict + echo commit-a > file1 && + git add file1 && + check_summary_oneline "" "Merge" +' + +output_tests_cleanup() { + # this is needed for "do not fire editor in the presence of conflicts" + git checkout master && + + # this is needed for the "partial removal" test to pass + git rm file1 && + git commit -m "cleanup" +} + test_expect_success 'the basics' ' + output_tests_cleanup && + echo doing partial >"commit is" && mkdir not && echo very much encouraged but we should >not/forbid && diff --git a/t/t7508-status.sh b/t/t7508-status.sh index 556d0faa77..008d5711b8 100755 --- a/t/t7508-status.sh +++ b/t/t7508-status.sh @@ -69,6 +69,34 @@ test_expect_success 'status (2)' ' ' cat >expect <<\EOF +# On branch master +# Changes to be committed: +# new file: dir2/added +# +# Changed but not updated: +# modified: dir1/modified +# +# Untracked files: +# dir1/untracked +# dir2/modified +# dir2/untracked +# expect +# output +# untracked +EOF + +git config advice.statusHints false + +test_expect_success 'status (advice.statusHints false)' ' + + git status >output && + test_cmp expect output + +' + +git config --unset advice.statusHints + +cat >expect <<\EOF M dir1/modified A dir2/added ?? dir1/untracked @@ -115,6 +143,23 @@ test_expect_success 'status (status.showUntrackedFiles no)' ' test_cmp expect output ' +cat >expect <<EOF +# On branch master +# Changes to be committed: +# new file: dir2/added +# +# Changed but not updated: +# modified: dir1/modified +# +# Untracked files not listed +EOF +git config advice.statusHints false +test_expect_success 'status -uno (advice.statusHints false)' ' + git status -uno >output && + test_cmp expect output +' +git config --unset advice.statusHints + cat >expect << EOF M dir1/modified A dir2/added @@ -496,6 +541,16 @@ test_expect_success 'dry-run of partial commit excluding new file in index' ' test_cmp expect output ' +cat >expect <<EOF +:100644 100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0000000000000000000000000000000000000000 M dir1/modified +EOF +test_expect_success 'status refreshes the index' ' + touch dir2/added && + git status && + git diff-files >output && + test_cmp expect output +' + test_expect_success 'setup status submodule summary' ' test_create_repo sm && ( cd sm && @@ -693,4 +748,19 @@ test_expect_success 'commit --dry-run submodule summary (--amend)' ' test_cmp expect output ' +test_expect_success POSIXPERM 'status succeeds in a read-only repository' ' + ( + chmod a-w .git && + # make dir1/tracked stat-dirty + >dir1/tracked1 && mv -f dir1/tracked1 dir1/tracked && + git status -s >output && + ! grep dir1/tracked output && + # make sure "status" succeeded without writing index out + git diff-files | grep dir1/tracked + ) + status=$? + chmod 775 .git + (exit $status) +' + test_done diff --git a/t/t7509-commit.sh b/t/t7509-commit.sh index d52c060b06..3ea33db6c7 100755 --- a/t/t7509-commit.sh +++ b/t/t7509-commit.sh @@ -83,6 +83,52 @@ test_expect_success '--amend option copies authorship' ' test_cmp expect actual ' +sha1_file() { + echo "$*" | sed "s#..#.git/objects/&/#" +} +remove_object() { + rm -f $(sha1_file "$*") +} +no_reflog() { + cp .git/config .git/config.saved && + echo "[core] logallrefupdates = false" >>.git/config && + test_when_finished "mv -f .git/config.saved .git/config" && + + if test -e .git/logs + then + mv .git/logs . && + test_when_finished "mv logs .git/" + fi +} + +test_expect_success '--amend option with empty author' ' + git cat-file commit Initial >tmp && + sed "s/author [^<]* </author </" tmp >empty-author && + no_reflog && + sha=$(git hash-object -t commit -w empty-author) && + test_when_finished "remove_object $sha" && + git checkout $sha && + test_when_finished "git checkout Initial" && + echo "Empty author test" >>foo && + test_tick && + ! git commit -a -m "empty author" --amend 2>err && + grep "empty ident" err +' + +test_expect_success '--amend option with missing author' ' + git cat-file commit Initial >tmp && + sed "s/author [^<]* </author </" tmp >malformed && + no_reflog && + sha=$(git hash-object -t commit -w malformed) && + test_when_finished "remove_object $sha" && + git checkout $sha && + test_when_finished "git checkout Initial" && + echo "Missing author test" >>foo && + test_tick && + ! git commit -a -m "malformed author" --amend 2>err && + grep "empty ident" err +' + test_expect_success '--reset-author makes the commit ours even with --amend option' ' git checkout Initial && echo "Test 6" >>foo && diff --git a/t/t7604-merge-custom-message.sh b/t/t7604-merge-custom-message.sh index 269cfdf267..9114785ef7 100755 --- a/t/t7604-merge-custom-message.sh +++ b/t/t7604-merge-custom-message.sh @@ -6,6 +6,15 @@ Testing merge when using a custom message for the merge commit.' . ./test-lib.sh +create_merge_msgs() { + echo >exp.subject "custom message" + + cp exp.subject exp.log && + echo >>exp.log "" && + echo >>exp.log "* commit 'c2':" && + echo >>exp.log " c2" +} + test_expect_success 'setup' ' echo c0 > c0.c && git add c0.c && @@ -19,16 +28,23 @@ test_expect_success 'setup' ' echo c2 > c2.c && git add c2.c && git commit -m c2 && - git tag c2 + git tag c2 && + create_merge_msgs ' test_expect_success 'merge c2 with a custom message' ' git reset --hard c1 && - echo >expected "custom message" && - git merge -m "custom message" c2 && + git merge -m "$(cat exp.subject)" c2 && + git cat-file commit HEAD | sed -e "1,/^$/d" >actual && + test_cmp exp.subject actual +' + +test_expect_success 'merge --log appends to custom message' ' + git reset --hard c1 && + git merge --log -m "$(cat exp.subject)" c2 && git cat-file commit HEAD | sed -e "1,/^$/d" >actual && - test_cmp expected actual + test_cmp exp.log actual ' test_done diff --git a/t/t9129-git-svn-i18n-commitencoding.sh b/t/t9129-git-svn-i18n-commitencoding.sh index b9224bdb20..1e9a2eb12b 100755 --- a/t/t9129-git-svn-i18n-commitencoding.sh +++ b/t/t9129-git-svn-i18n-commitencoding.sh @@ -14,10 +14,22 @@ compare_git_head_with () { test_cmp current "$1" } +a_utf8_locale=$(locale -a | sed -n '/\.[uU][tT][fF]-*8$/{ + p + q +}') + +if test -n "$a_utf8_locale" +then + test_set_prereq UTF8 +else + say "UTF-8 locale not available, some tests are skipped" +fi + compare_svn_head_with () { # extract just the log message and strip out committer info. # don't use --limit here since svn 1.1.x doesn't have it, - LC_ALL=en_US.UTF-8 svn log `git svn info --url` | perl -w -e ' + LC_ALL="$a_utf8_locale" svn log `git svn info --url` | perl -w -e ' use bytes; $/ = ("-"x72) . "\n"; my @x = <STDIN>; @@ -69,12 +81,6 @@ do ' done -if locale -a |grep -q en_US.utf8; then - test_set_prereq UTF8 -else - say "UTF-8 locale not available, test skipped" -fi - test_expect_success UTF8 'ISO-8859-1 should match UTF-8 in svn' ' ( cd ISO8859-1 && diff --git a/t/test-lib.sh b/t/test-lib.sh index 7422bba47e..9bfa14be7f 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -530,6 +530,22 @@ test_must_fail () { test $? -gt 0 -a $? -le 129 -o $? -gt 192 } +# Similar to test_must_fail, but tolerates success, too. This is +# meant to be used in contexts like: +# +# test_expect_success 'some command works without configuration' ' +# test_might_fail git config --unset all.configuration && +# do something +# ' +# +# Writing "git config --unset all.configuration || :" would be wrong, +# because we want to notice if it fails due to segv. + +test_might_fail () { + "$@" + test $? -ge 0 -a $? -le 129 -o $? -gt 192 +} + # test_cmp is a helper function to compare actual and expected output. # You can use it like: # @@ -36,43 +36,50 @@ struct tag *lookup_tag(const unsigned char *sha1) return (struct tag *) obj; } +static unsigned long parse_tag_date(const char *buf, const char *tail) +{ + const char *dateptr; + + while (buf < tail && *buf++ != '>') + /* nada */; + if (buf >= tail) + return 0; + dateptr = buf; + while (buf < tail && *buf++ != '\n') + /* nada */; + if (buf >= tail) + return 0; + /* dateptr < buf && buf[-1] == '\n', so strtoul will stop at buf-1 */ + return strtoul(dateptr, NULL, 10); +} + int parse_tag_buffer(struct tag *item, void *data, unsigned long size) { - int typelen, taglen; unsigned char sha1[20]; - const char *type_line, *tag_line, *sig_line; char type[20]; - const char *start = data; + const char *bufptr = data; + const char *tail = bufptr + size; + const char *nl; - if (item->object.parsed) - return 0; - item->object.parsed = 1; + if (item->object.parsed) + return 0; + item->object.parsed = 1; if (size < 64) return -1; - if (memcmp("object ", data, 7) || get_sha1_hex((char *) data + 7, sha1)) + if (memcmp("object ", bufptr, 7) || get_sha1_hex(bufptr + 7, sha1) || bufptr[47] != '\n') return -1; + bufptr += 48; /* "object " + sha1 + "\n" */ - type_line = (char *) data + 48; - if (memcmp("\ntype ", type_line-1, 6)) + if (prefixcmp(bufptr, "type ")) return -1; - - tag_line = memchr(type_line, '\n', size - (type_line - start)); - if (!tag_line || memcmp("tag ", ++tag_line, 4)) + bufptr += 5; + nl = memchr(bufptr, '\n', tail - bufptr); + if (!nl || sizeof(type) <= (nl - bufptr)) return -1; - - sig_line = memchr(tag_line, '\n', size - (tag_line - start)); - if (!sig_line) - return -1; - sig_line++; - - typelen = tag_line - type_line - strlen("type \n"); - if (typelen >= 20) - return -1; - memcpy(type, type_line + 5, typelen); - type[typelen] = '\0'; - taglen = sig_line - tag_line - strlen("tag \n"); - item->tag = xmemdupz(tag_line + 4, taglen); + strncpy(type, bufptr, nl - bufptr); + type[nl - bufptr] = '\0'; + bufptr = nl + 1; if (!strcmp(type, blob_type)) { item->tagged = &lookup_blob(sha1)->object; @@ -87,6 +94,20 @@ int parse_tag_buffer(struct tag *item, void *data, unsigned long size) item->tagged = NULL; } + if (prefixcmp(bufptr, "tag ")) + return -1; + bufptr += 4; + nl = memchr(bufptr, '\n', tail - bufptr); + if (!nl) + return -1; + item->tag = xmemdupz(bufptr, nl - bufptr); + bufptr = nl + 1; + + if (!prefixcmp(bufptr, "tagger ")) + item->date = parse_tag_date(bufptr, tail); + else + item->date = 0; + return 0; } @@ -9,7 +9,7 @@ struct tag { struct object object; struct object *tagged; char *tag; - char *signature; /* not actually implemented */ + unsigned long date; }; extern struct tag *lookup_tag(const unsigned char *sha1); diff --git a/thread-utils.c b/thread-utils.c index 4f9c829c2d..589f838f82 100644 --- a/thread-utils.c +++ b/thread-utils.c @@ -1,4 +1,5 @@ #include "cache.h" +#include <pthread.h> #if defined(hpux) || defined(__hpux) || defined(_hpux) # include <sys/pstat.h> @@ -43,3 +44,18 @@ int online_cpus(void) return 1; } + +int init_recursive_mutex(pthread_mutex_t *m) +{ + pthread_mutexattr_t a; + int ret; + + ret = pthread_mutexattr_init(&a); + if (!ret) { + ret = pthread_mutexattr_settype(&a, PTHREAD_MUTEX_RECURSIVE); + if (!ret) + ret = pthread_mutex_init(m, &a); + pthread_mutexattr_destroy(&a); + } + return ret; +} diff --git a/thread-utils.h b/thread-utils.h index cce4b77bd6..1727a03333 100644 --- a/thread-utils.h +++ b/thread-utils.h @@ -2,5 +2,6 @@ #define THREAD_COMPAT_H extern int online_cpus(void); +extern int init_recursive_mutex(pthread_mutex_t*); #endif /* THREAD_COMPAT_H */ diff --git a/unpack-trees.c b/unpack-trees.c index 75f54cac97..490cd5f6f4 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -67,16 +67,8 @@ static void unlink_entry(struct cache_entry *ce) { if (has_symlink_or_noent_leading_path(ce->name, ce_namelen(ce))) return; - if (S_ISGITLINK(ce->ce_mode)) { - if (rmdir(ce->name)) { - warning("unable to rmdir %s: %s", - ce->name, strerror(errno)); - return; - } - } - else - if (unlink_or_warn(ce->name)) - return; + if (remove_or_warn(ce->ce_mode, ce->name)) + return; schedule_dir_for_removal(ce->name, ce_namelen(ce)); } @@ -862,7 +854,7 @@ static int verify_uptodate_1(struct cache_entry *ce, { struct stat st; - if (o->index_only || (!ce_skip_worktree(ce) && (o->reset || ce_uptodate(ce)))) + if (o->index_only || (!((ce->ce_flags & CE_VALID) || ce_skip_worktree(ce)) && (o->reset || ce_uptodate(ce)))) return 0; if (!lstat(ce->name, &st)) { diff --git a/userdiff.c b/userdiff.c index df992490d5..38563daa3c 100644 --- a/userdiff.c +++ b/userdiff.c @@ -44,7 +44,9 @@ PATTERNS("pascal", "|[-+0-9.e]+|0[xXbB]?[0-9a-fA-F]+" "|<>|<=|>=|:=|\\.\\." "|[^[:space:]]|[\x80-\xff]+"), -PATTERNS("php", "^[\t ]*((function|class).*)", +PATTERNS("php", + "^[\t ]*(((public|protected|private|static)[\t ]+)*function.*)$\n" + "^[\t ]*(class.*)$", /* -- */ "[a-zA-Z_][a-zA-Z0-9_]*" "|[-+0-9.e]+|0[xXbB]?[0-9a-fA-F]+" @@ -3,11 +3,23 @@ */ #include "cache.h" +static void try_to_free_builtin(size_t size) +{ + release_pack_memory(size, -1); +} + +static void (*try_to_free_routine)(size_t size) = try_to_free_builtin; + +void set_try_to_free_routine(void (*routine)(size_t)) +{ + try_to_free_routine = (routine) ? routine : try_to_free_builtin; +} + char *xstrdup(const char *str) { char *ret = strdup(str); if (!ret) { - release_pack_memory(strlen(str) + 1, -1); + try_to_free_routine(strlen(str) + 1); ret = strdup(str); if (!ret) die("Out of memory, strdup failed"); @@ -21,7 +33,7 @@ void *xmalloc(size_t size) if (!ret && !size) ret = malloc(1); if (!ret) { - release_pack_memory(size, -1); + try_to_free_routine(size); ret = malloc(size); if (!ret && !size) ret = malloc(1); @@ -67,7 +79,7 @@ void *xrealloc(void *ptr, size_t size) if (!ret && !size) ret = realloc(ptr, 1); if (!ret) { - release_pack_memory(size, -1); + try_to_free_routine(size); ret = realloc(ptr, size); if (!ret && !size) ret = realloc(ptr, 1); @@ -83,7 +95,7 @@ void *xcalloc(size_t nmemb, size_t size) if (!ret && (!nmemb || !size)) ret = calloc(1, 1); if (!ret) { - release_pack_memory(nmemb * size, -1); + try_to_free_routine(nmemb * size); ret = calloc(nmemb, size); if (!ret && (!nmemb || !size)) ret = calloc(1, 1); @@ -311,18 +323,30 @@ int odb_pack_keep(char *name, size_t namesz, unsigned char *sha1) return open(name, O_RDWR|O_CREAT|O_EXCL, 0600); } -int unlink_or_warn(const char *file) +static int warn_if_unremovable(const char *op, const char *file, int rc) { - int rc = unlink(file); - if (rc < 0) { int err = errno; if (ENOENT != err) { - warning("unable to unlink %s: %s", - file, strerror(errno)); + warning("unable to %s %s: %s", + op, file, strerror(errno)); errno = err; } } return rc; } +int unlink_or_warn(const char *file) +{ + return warn_if_unremovable("unlink", file, unlink(file)); +} + +int rmdir_or_warn(const char *file) +{ + return warn_if_unremovable("rmdir", file, rmdir(file)); +} + +int remove_or_warn(unsigned int mode, const char *file) +{ + return S_ISGITLINK(mode) ? rmdir_or_warn(file) : unlink_or_warn(file); +} diff --git a/wt-status.c b/wt-status.c index 8ca59a2d2a..d44486c826 100644 --- a/wt-status.c +++ b/wt-status.c @@ -625,7 +625,9 @@ void wt_status_print(struct wt_status *s) if (s->show_untracked_files) wt_status_print_untracked(s); else if (s->commitable) - fprintf(s->fp, "# Untracked files not listed (use -u option to show untracked files)\n"); + fprintf(s->fp, "# Untracked files not listed%s\n", + advice_status_hints + ? " (use -u option to show untracked files)" : ""); if (s->verbose) wt_status_print_verbose(s); @@ -635,15 +637,22 @@ void wt_status_print(struct wt_status *s) else if (s->nowarn) ; /* nothing */ else if (s->workdir_dirty) - printf("no changes added to commit (use \"git add\" and/or \"git commit -a\")\n"); + printf("no changes added to commit%s\n", + advice_status_hints + ? " (use \"git add\" and/or \"git commit -a\")" : ""); else if (s->untracked.nr) - printf("nothing added to commit but untracked files present (use \"git add\" to track)\n"); + printf("nothing added to commit but untracked files present%s\n", + advice_status_hints + ? " (use \"git add\" to track)" : ""); else if (s->is_initial) - printf("nothing to commit (create/copy files and use \"git add\" to track)\n"); + printf("nothing to commit%s\n", advice_status_hints + ? " (create/copy files and use \"git add\" to track)" : ""); else if (!s->show_untracked_files) - printf("nothing to commit (use -u to show untracked files)\n"); + printf("nothing to commit%s\n", advice_status_hints + ? " (use -u to show untracked files)" : ""); else - printf("nothing to commit (working directory clean)\n"); + printf("nothing to commit%s\n", advice_status_hints + ? " (working directory clean)" : ""); } } |