diff options
55 files changed, 2527 insertions, 262 deletions
diff --git a/.gitignore b/.gitignore index 9ec40fa9f..a05241916 100644 --- a/.gitignore +++ b/.gitignore @@ -74,6 +74,7 @@ /git-index-pack /git-init /git-init-db +/git-interpret-trailers /git-instaweb /git-log /git-ls-files diff --git a/Documentation/Makefile b/Documentation/Makefile index cea0e7ae3..8d0f70938 100644 --- a/Documentation/Makefile +++ b/Documentation/Makefile @@ -5,6 +5,7 @@ MAN7_TXT = TECH_DOCS = ARTICLES = SP_ARTICLES = +OBSOLETE_HTML = MAN1_TXT += $(filter-out \ $(addsuffix .txt, $(ARTICLES) $(SP_ARTICLES)), \ @@ -26,6 +27,7 @@ MAN7_TXT += gitcore-tutorial.txt MAN7_TXT += gitcredentials.txt MAN7_TXT += gitcvs-migration.txt MAN7_TXT += gitdiffcore.txt +MAN7_TXT += giteveryday.txt MAN7_TXT += gitglossary.txt MAN7_TXT += gitnamespaces.txt MAN7_TXT += gitrevisions.txt @@ -37,11 +39,11 @@ MAN_TXT = $(MAN1_TXT) $(MAN5_TXT) $(MAN7_TXT) MAN_XML = $(patsubst %.txt,%.xml,$(MAN_TXT)) MAN_HTML = $(patsubst %.txt,%.html,$(MAN_TXT)) -OBSOLETE_HTML = git-remote-helpers.html +OBSOLETE_HTML += everyday.html +OBSOLETE_HTML += git-remote-helpers.html DOC_HTML = $(MAN_HTML) $(OBSOLETE_HTML) ARTICLES += howto-index -ARTICLES += everyday ARTICLES += git-tools ARTICLES += git-bisect-lk2009 # with their own formatting rules. diff --git a/Documentation/RelNotes/2.2.0.txt b/Documentation/RelNotes/2.2.0.txt index 95891765a..af876e2ce 100644 --- a/Documentation/RelNotes/2.2.0.txt +++ b/Documentation/RelNotes/2.2.0.txt @@ -58,6 +58,14 @@ UI, Workflows & Features public repository really point the commits the pusher wanted to, without having to "trust" the server. + * "git interpret-trailers" is a new filter to programatically edit + the tail end of the commit log messages. + + * "git help everyday" shows the "Everyday Git in 20 commands or so" + document, whose contents have been updated to more modern Git + practice. + + Performance, Internal Implementation, etc. * The API to manipulate the "refs" is currently undergoing a revamp @@ -124,6 +132,10 @@ Performance, Internal Implementation, etc. original before feeding the filter. Instead, stream the file contents directly to the filter and process its output. + * The scripts in the test suite can be run with "-x" option to show + a shell-trace of each command run in them. + + Also contains various documentation updates and code clean-ups. @@ -138,11 +150,6 @@ notes for details). mean the more obvious "No output whatsoever" but "Use default format", which was counterintuitive. - * Implementations of "tar" that do not understand an extended pax - header would extract the contents of it in a regular file; make - sure the permission bits of this file follows the same tar.umask - configuration setting. - * "git -c section.var command" and "git -c section.var= command" should pass the configuration differently (the former should be a boolean true, the latter should be an empty string). @@ -236,3 +243,8 @@ notes for details). * A few documentation pages had example sections marked up not quite correctly, which passed AsciiDoc but failed with AsciiDoctor. (merge c30c43c bc/asciidoc-pretty-formats-fix later to maint). + (merge f8a48af bc/asciidoc later to maint). + + * "gitweb" used deprecated CGI::startfrom, which was removed from + CGI.pm as of 4.04; use CGI::start_from instead. + (merge 4750f4b rm/gitweb-start-form later to maint). diff --git a/Documentation/everyday.txto b/Documentation/everyday.txto new file mode 100644 index 000000000..c5047d8f9 --- /dev/null +++ b/Documentation/everyday.txto @@ -0,0 +1,9 @@ +Everyday Git With 20 Commands Or So +=================================== + +This document has been moved to linkgit:giteveryday[1]. + +Please let the owners of the referring site know so that they can update the +link you clicked to get here. + +Thanks. diff --git a/Documentation/git-imap-send.txt b/Documentation/git-imap-send.txt index 7d991d919..c7c0d2142 100644 --- a/Documentation/git-imap-send.txt +++ b/Documentation/git-imap-send.txt @@ -97,7 +97,7 @@ Using direct mode: host = imap://imap.example.com user = bob pass = p4ssw0rd -.......................... +......................... Using direct mode with SSL: @@ -109,7 +109,7 @@ Using direct mode with SSL: pass = p4ssw0rd port = 123 sslverify = false -.......................... +......................... EXAMPLE diff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt new file mode 100644 index 000000000..81fac3d26 --- /dev/null +++ b/Documentation/git-interpret-trailers.txt @@ -0,0 +1,314 @@ +git-interpret-trailers(1) +========================= + +NAME +---- +git-interpret-trailers - help add stuctured information into commit messages + +SYNOPSIS +-------- +[verse] +'git interpret-trailers' [--trim-empty] [(--trailer <token>[(=|:)<value>])...] [<file>...] + +DESCRIPTION +----------- +Help adding 'trailers' lines, that look similar to RFC 822 e-mail +headers, at the end of the otherwise free-form part of a commit +message. + +This command reads some patches or commit messages from either the +<file> arguments or the standard input if no <file> is specified. Then +this command applies the arguments passed using the `--trailer` +option, if any, to the commit message part of each input file. The +result is emitted on the standard output. + +Some configuration variables control the way the `--trailer` arguments +are applied to each commit message and the way any existing trailer in +the commit message is changed. They also make it possible to +automatically add some trailers. + +By default, a '<token>=<value>' or '<token>:<value>' argument given +using `--trailer` will be appended after the existing trailers only if +the last trailer has a different (<token>, <value>) pair (or if there +is no existing trailer). The <token> and <value> parts will be trimmed +to remove starting and trailing whitespace, and the resulting trimmed +<token> and <value> will appear in the message like this: + +------------------------------------------------ +token: value +------------------------------------------------ + +This means that the trimmed <token> and <value> will be separated by +`': '` (one colon followed by one space). + +By default the new trailer will appear at the end of all the existing +trailers. If there is no existing trailer, the new trailer will appear +after the commit message part of the ouput, and, if there is no line +with only spaces at the end of the commit message part, one blank line +will be added before the new trailer. + +Existing trailers are extracted from the input message by looking for +a group of one or more lines that contain a colon (by default), where +the group is preceded by one or more empty (or whitespace-only) lines. +The group must either be at the end of the message or be the last +non-whitespace lines before a line that starts with '---'. Such three +minus signs start the patch part of the message. + +When reading trailers, there can be whitespaces before and after the +token, the separator and the value. There can also be whitespaces +indide the token and the value. + +Note that 'trailers' do not follow and are not intended to follow many +rules for RFC 822 headers. For example they do not follow the line +folding rules, the encoding rules and probably many other rules. + +OPTIONS +------- +--trim-empty:: + If the <value> part of any trailer contains only whitespace, + the whole trailer will be removed from the resulting message. + This apply to existing trailers as well as new trailers. + +--trailer <token>[(=|:)<value>]:: + Specify a (<token>, <value>) pair that should be applied as a + trailer to the input messages. See the description of this + command. + +CONFIGURATION VARIABLES +----------------------- + +trailer.separators:: + This option tells which characters are recognized as trailer + separators. By default only ':' is recognized as a trailer + separator, except that '=' is always accepted on the command + line for compatibility with other git commands. ++ +The first character given by this option will be the default character +used when another separator is not specified in the config for this +trailer. ++ +For example, if the value for this option is "%=$", then only lines +using the format '<token><sep><value>' with <sep> containing '%', '=' +or '$' and then spaces will be considered trailers. And '%' will be +the default separator used, so by default trailers will appear like: +'<token>% <value>' (one percent sign and one space will appear between +the token and the value). + +trailer.where:: + This option tells where a new trailer will be added. ++ +This can be `end`, which is the default, `start`, `after` or `before`. ++ +If it is `end`, then each new trailer will appear at the end of the +existing trailers. ++ +If it is `start`, then each new trailer will appear at the start, +instead of the end, of the existing trailers. ++ +If it is `after`, then each new trailer will appear just after the +last trailer with the same <token>. ++ +If it is `before`, then each new trailer will appear just before the +first trailer with the same <token>. + +trailer.ifexists:: + This option makes it possible to choose what action will be + performed when there is already at least one trailer with the + same <token> in the message. ++ +The valid values for this option are: `addIfDifferentNeighbor` (this +is the default), `addIfDifferent`, `add`, `overwrite` or `doNothing`. ++ +With `addIfDifferentNeighbor`, a new trailer will be added only if no +trailer with the same (<token>, <value>) pair is above or below the line +where the new trailer will be added. ++ +With `addIfDifferent`, a new trailer will be added only if no trailer +with the same (<token>, <value>) pair is already in the message. ++ +With `add`, a new trailer will be added, even if some trailers with +the same (<token>, <value>) pair are already in the message. ++ +With `replace`, an existing trailer with the same <token> will be +deleted and the new trailer will be added. The deleted trailer will be +the closest one (with the same <token>) to the place where the new one +will be added. ++ +With `doNothing`, nothing will be done; that is no new trailer will be +added if there is already one with the same <token> in the message. + +trailer.ifmissing:: + This option makes it possible to choose what action will be + performed when there is not yet any trailer with the same + <token> in the message. ++ +The valid values for this option are: `add` (this is the default) and +`doNothing`. ++ +With `add`, a new trailer will be added. ++ +With `doNothing`, nothing will be done. + +trailer.<token>.key:: + This `key` will be used instead of <token> in the trailer. At + the end of this key, a separator can appear and then some + space characters. By default the only valid separator is ':', + but this can be changed using the `trailer.separators` config + variable. ++ +If there is a separator, then the key will be used instead of both the +<token> and the default separator when adding the trailer. + +trailer.<token>.where:: + This option takes the same values as the 'trailer.where' + configuration variable and it overrides what is specified by + that option for trailers with the specified <token>. + +trailer.<token>.ifexist:: + This option takes the same values as the 'trailer.ifexist' + configuration variable and it overrides what is specified by + that option for trailers with the specified <token>. + +trailer.<token>.ifmissing:: + This option takes the same values as the 'trailer.ifmissing' + configuration variable and it overrides what is specified by + that option for trailers with the specified <token>. + +trailer.<token>.command:: + This option can be used to specify a shell command that will + be called to automatically add or modify a trailer with the + specified <token>. ++ +When this option is specified, the behavior is as if a special +'<token>=<value>' argument were added at the beginning of the command +line, where <value> is taken to be the standard output of the +specified command with any leading and trailing whitespace trimmed +off. ++ +If the command contains the `$ARG` string, this string will be +replaced with the <value> part of an existing trailer with the same +<token>, if any, before the command is launched. ++ +If some '<token>=<value>' arguments are also passed on the command +line, when a 'trailer.<token>.command' is configured, the command will +also be executed for each of these arguments. And the <value> part of +these arguments, if any, will be used to replace the `$ARG` string in +the command. + +EXAMPLES +-------- + +* Configure a 'sign' trailer with a 'Signed-off-by' key, and then + add two of these trailers to a message: ++ +------------ +$ git config trailer.sign.key "Signed-off-by" +$ cat msg.txt +subject + +message +$ cat msg.txt | git interpret-trailers --trailer 'sign: Alice <alice@example.com>' --trailer 'sign: Bob <bob@example.com>' +subject + +message + +Signed-off-by: Alice <alice@example.com> +Signed-off-by: Bob <bob@example.com> +------------ + +* Extract the last commit as a patch, and add a 'Cc' and a + 'Reviewed-by' trailer to it: ++ +------------ +$ git format-patch -1 +0001-foo.patch +$ git interpret-trailers --trailer 'Cc: Alice <alice@example.com>' --trailer 'Reviewed-by: Bob <bob@example.com>' 0001-foo.patch >0001-bar.patch +------------ + +* Configure a 'sign' trailer with a command to automatically add a + 'Signed-off-by: ' with the author information only if there is no + 'Signed-off-by: ' already, and show how it works: ++ +------------ +$ git config trailer.sign.key "Signed-off-by: " +$ git config trailer.sign.ifmissing add +$ git config trailer.sign.ifexists doNothing +$ git config trailer.sign.command 'echo "$(git config user.name) <$(git config user.email)>"' +$ git interpret-trailers <<EOF +> EOF + +Signed-off-by: Bob <bob@example.com> +$ git interpret-trailers <<EOF +> Signed-off-by: Alice <alice@example.com> +> EOF + +Signed-off-by: Alice <alice@example.com> +------------ + +* Configure a 'fix' trailer with a key that contains a '#' and no + space after this character, and show how it works: ++ +------------ +$ git config trailer.separators ":#" +$ git config trailer.fix.key "Fix #" +$ echo "subject" | git interpret-trailers --trailer fix=42 +subject + +Fix #42 +------------ + +* Configure a 'see' trailer with a command to show the subject of a + commit that is related, and show how it works: ++ +------------ +$ git config trailer.see.key "See-also: " +$ git config trailer.see.ifExists "replace" +$ git config trailer.see.ifMissing "doNothing" +$ git config trailer.see.command "git log -1 --oneline --format=\"%h (%s)\" --abbrev-commit --abbrev=14 \$ARG" +$ git interpret-trailers <<EOF +> subject +> +> message +> +> see: HEAD~2 +> EOF +subject + +message + +See-also: fe3187489d69c4 (subject of related commit) +------------ + +* Configure a commit template with some trailers with empty values + (using sed to show and keep the trailing spaces at the end of the + trailers), then configure a commit-msg hook that uses + 'git interpret-trailers' to remove trailers with empty values and + to add a 'git-version' trailer: ++ +------------ +$ sed -e 's/ Z$/ /' >commit_template.txt <<EOF +> ***subject*** +> +> ***message*** +> +> Fixes: Z +> Cc: Z +> Reviewed-by: Z +> Signed-off-by: Z +> EOF +$ git config commit.template commit_template.txt +$ cat >.git/hooks/commit-msg <<EOF +> #!/bin/sh +> git interpret-trailers --trim-empty --trailer "git-version: \$(git describe)" "\$1" > "\$1.new" +> mv "\$1.new" "\$1" +> EOF +$ chmod +x .git/hooks/commit-msg +------------ + +SEE ALSO +-------- +linkgit:git-commit[1], linkgit:git-format-patch[1], linkgit:git-config[1] + +GIT +--- +Part of the linkgit:git[1] suite diff --git a/Documentation/git-prune-packed.txt b/Documentation/git-prune-packed.txt index 6738055bd..9fed59a31 100644 --- a/Documentation/git-prune-packed.txt +++ b/Documentation/git-prune-packed.txt @@ -1,5 +1,5 @@ git-prune-packed(1) -===================== +=================== NAME ---- diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt index b17283ab7..21b3f29c3 100644 --- a/Documentation/git-push.txt +++ b/Documentation/git-push.txt @@ -34,7 +34,7 @@ When the command line does not specify what to push with `<refspec>...` arguments or `--all`, `--mirror`, `--tags` options, the command finds the default `<refspec>` by consulting `remote.*.push` configuration, and if it is not found, honors `push.default` configuration to decide -what to push (See linkgit:git-config[1] for the meaning of `push.default`). +what to push (See gitlink:git-config[1] for the meaning of `push.default`). OPTIONS[[OPTIONS]] diff --git a/Documentation/git-quiltimport.txt b/Documentation/git-quiltimport.txt index a35619658..d64388cb8 100644 --- a/Documentation/git-quiltimport.txt +++ b/Documentation/git-quiltimport.txt @@ -1,5 +1,5 @@ git-quiltimport(1) -================ +================== NAME ---- diff --git a/Documentation/git-stage.txt b/Documentation/git-stage.txt index ba3fe0d7f..25bcda936 100644 --- a/Documentation/git-stage.txt +++ b/Documentation/git-stage.txt @@ -1,5 +1,5 @@ git-stage(1) -============== +============ NAME ---- diff --git a/Documentation/git.txt b/Documentation/git.txt index c6175d45e..9e0a42ce5 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -22,7 +22,7 @@ unusually rich command set that provides both high-level operations and full access to internals. See linkgit:gittutorial[7] to get started, then see -link:everyday.html[Everyday Git] for a useful minimum set of +linkgit:giteveryday[7] for a useful minimum set of commands. The link:user-manual.html[Git User's Manual] has a more in-depth introduction. @@ -1098,7 +1098,7 @@ subscribed to the list to send a message there. SEE ALSO -------- linkgit:gittutorial[7], linkgit:gittutorial-2[7], -link:everyday.html[Everyday Git], linkgit:gitcvs-migration[7], +linkgit:giteveryday[7], linkgit:gitcvs-migration[7], linkgit:gitglossary[7], linkgit:gitcore-tutorial[7], linkgit:gitcli[7], link:user-manual.html[The Git User's Manual], linkgit:gitworkflows[7] diff --git a/Documentation/gitcore-tutorial.txt b/Documentation/gitcore-tutorial.txt index d2d7c213d..8475c0793 100644 --- a/Documentation/gitcore-tutorial.txt +++ b/Documentation/gitcore-tutorial.txt @@ -1667,7 +1667,7 @@ linkgit:gittutorial[7], linkgit:gittutorial-2[7], linkgit:gitcvs-migration[7], linkgit:git-help[1], -link:everyday.html[Everyday git], +linkgit:giteveryday[7], link:user-manual.html[The Git User's Manual] GIT diff --git a/Documentation/gitcvs-migration.txt b/Documentation/gitcvs-migration.txt index 5f4e89005..b06e852a8 100644 --- a/Documentation/gitcvs-migration.txt +++ b/Documentation/gitcvs-migration.txt @@ -194,7 +194,7 @@ linkgit:gittutorial[7], linkgit:gittutorial-2[7], linkgit:gitcore-tutorial[7], linkgit:gitglossary[7], -link:everyday.html[Everyday Git], +linkgit:giteveryday[7], link:user-manual.html[The Git User's Manual] GIT diff --git a/Documentation/everyday.txt b/Documentation/giteveryday.txt index b2548ef4e..7be6e6484 100644 --- a/Documentation/everyday.txt +++ b/Documentation/giteveryday.txt @@ -1,22 +1,37 @@ +giteveryday(7) +=============== + +NAME +---- +giteveryday - A useful minimum set of commands for Everyday Git + +SYNOPSIS +-------- + Everyday Git With 20 Commands Or So -=================================== -<<Individual Developer (Standalone)>> commands are essential for -anybody who makes a commit, even for somebody who works alone. +DESCRIPTION +----------- -If you work with other people, you will need commands listed in -the <<Individual Developer (Participant)>> section as well. +Git users can broadly be grouped into four categories for the purposes of +describing here a small set of useful command for everyday Git. -People who play the <<Integrator>> role need to learn some more -commands in addition to the above. +* <<STANDALONE,Individual Developer (Standalone)>> commands are essential + for anybody who makes a commit, even for somebody who works alone. -<<Repository Administration>> commands are for system -administrators who are responsible for the care and feeding -of Git repositories. +* If you work with other people, you will need commands listed in + the <<PARTICIPANT,Individual Developer (Participant)>> section as well. +* People who play the <<INTEGRATOR,Integrator>> role need to learn some + more commands in addition to the above. -Individual Developer (Standalone)[[Individual Developer (Standalone)]] ----------------------------------------------------------------------- +* <<ADMINISTRATION,Repository Administration>> commands are for system + administrators who are responsible for the care and feeding + of Git repositories. + + +Individual Developer (Standalone)[[STANDALONE]] +----------------------------------------------- A standalone individual developer does not exchange patches with other people, and works alone in a single repository, using the @@ -24,8 +39,6 @@ following commands. * linkgit:git-init[1] to create a new repository. - * linkgit:git-show-branch[1] to see where you are. - * linkgit:git-log[1] to see what happened. * linkgit:git-checkout[1] and linkgit:git-branch[1] to switch @@ -45,7 +58,7 @@ following commands. * linkgit:git-rebase[1] to maintain topic branches. - * linkgit:git-tag[1] to mark known point. + * linkgit:git-tag[1] to mark a known point. Examples ~~~~~~~~ @@ -75,14 +88,12 @@ $ edit/compile/test $ git diff HEAD <4> $ git commit -a -s <5> $ edit/compile/test -$ git reset --soft HEAD^ <6> -$ edit/compile/test -$ git diff ORIG_HEAD <7> -$ git commit -a -c ORIG_HEAD <8> -$ git checkout master <9> -$ git merge alsa-audio <10> -$ git log --since='3 days ago' <11> -$ git log v2.43.. curses/ <12> +$ git diff HEAD^ <6> +$ git commit -a --amend <7> +$ git checkout master <8> +$ git merge alsa-audio <9> +$ git log --since='3 days ago' <10> +$ git log v2.43.. curses/ <11> ------------ + <1> create a new topic branch. @@ -90,22 +101,21 @@ $ git log v2.43.. curses/ <12> <3> you need to tell Git if you added a new file; removal and modification will be caught if you do `git commit -a` later. <4> to see what changes you are committing. -<5> commit everything as you have tested, with your sign-off. -<6> take the last commit back, keeping what is in the working tree. -<7> look at the changes since the premature commit we took back. -<8> redo the commit undone in the previous step, using the message -you originally wrote. -<9> switch to the master branch. -<10> merge a topic branch into your master branch. -<11> review commit logs; other forms to limit output can be -combined and include `--max-count=10` (show 10 commits), +<5> commit everything, as you have tested, with your sign-off. +<6> look at all your changes including the previous commit. +<7> amend the previous commit, adding all your new changes, +using your original message. +<8> switch to the master branch. +<9> merge a topic branch into your master branch. +<10> review commit logs; other forms to limit output can be +combined and include `-10` (to show up to 10 commits), `--until=2005-12-10`, etc. -<12> view only the changes that touch what's in `curses/` +<11> view only the changes that touch what's in `curses/` directory, since `v2.43` tag. -Individual Developer (Participant)[[Individual Developer (Participant)]] ------------------------------------------------------------------------- +Individual Developer (Participant)[[PARTICIPANT]] +------------------------------------------------- A developer working as a participant in a group project needs to learn how to communicate with others, and uses these commands in @@ -123,6 +133,13 @@ addition to the ones needed by a standalone developer. * linkgit:git-format-patch[1] to prepare e-mail submission, if you adopt Linux kernel-style public forum workflow. + * linkgit:git-send-email[1] to send your e-mail submission without + corruption by your MUA. + + * linkgit:git-request-pull[1] to create a summary of changes + for your upstream to pull. + + Examples ~~~~~~~~ @@ -131,28 +148,34 @@ Clone the upstream and work on it. Feed changes to upstream.:: ------------ $ git clone git://git.kernel.org/pub/scm/.../torvalds/linux-2.6 my2.6 $ cd my2.6 -$ edit/compile/test; git commit -a -s <1> -$ git format-patch origin <2> -$ git pull <3> -$ git log -p ORIG_HEAD.. arch/i386 include/asm-i386 <4> -$ git pull git://git.kernel.org/pub/.../jgarzik/libata-dev.git ALL <5> -$ git reset --hard ORIG_HEAD <6> -$ git gc <7> -$ git fetch --tags <8> +$ git checkout -b mine master <1> +$ edit/compile/test; git commit -a -s <2> +$ git format-patch master <3> +$ git send-email --to="person <email@example.com>" 00*.patch <4> +$ git checkout master <5> +$ git pull <6> +$ git log -p ORIG_HEAD.. arch/i386 include/asm-i386 <7> +$ git ls-remote --heads http://git.kernel.org/.../jgarzik/libata-dev.git <8> +$ git pull git://git.kernel.org/pub/.../jgarzik/libata-dev.git ALL <9> +$ git reset --hard ORIG_HEAD <10> +$ git gc <11> ------------ + -<1> repeat as needed. -<2> extract patches from your branch for e-mail submission. -<3> `git pull` fetches from `origin` by default and merges into the +<1> checkout a new branch `mine` from master. +<2> repeat as needed. +<3> extract patches from your branch, relative to master, +<4> and email them. +<5> return to `master`, ready to see what's new +<6> `git pull` fetches from `origin` by default and merges into the current branch. -<4> immediately after pulling, look at the changes done upstream +<7> immediately after pulling, look at the changes done upstream since last time we checked, only in the area we are interested in. -<5> fetch from a specific branch from a specific repository and merge. -<6> revert the pull. -<7> garbage collect leftover objects from reverted pull. -<8> from time to time, obtain official tags from the `origin` -and store them under `.git/refs/tags/`. +<8> check the branch names in an external repository (if not known). +<9> fetch from a specific branch `ALL` from a specific repository +and merge it. +<10> revert the pull. +<11> garbage collect leftover objects from reverted pull. Push into another repository.:: @@ -166,7 +189,7 @@ remote.origin.fetch refs/heads/*:refs/remotes/origin/* branch.master.remote origin branch.master.merge refs/heads/master satellite$ git config remote.origin.push \ - master:refs/remotes/satellite/master <3> + +refs/heads/*:refs/remotes/satellite/* <3> satellite$ edit/compile/test/commit satellite$ git push origin <4> @@ -181,11 +204,12 @@ machine. <2> clone sets these configuration variables by default. It arranges `git pull` to fetch and store the branches of mothership machine to local `remotes/origin/*` remote-tracking branches. -<3> arrange `git push` to push local `master` branch to -`remotes/satellite/master` branch of the mothership machine. -<4> push will stash our work away on `remotes/satellite/master` -remote-tracking branch on the mothership machine. You could use this -as a back-up method. +<3> arrange `git push` to push all local branches to +their corresponding branch of the mothership machine. +<4> push will stash all our work away on `remotes/satellite/*` +remote-tracking branches on the mothership machine. You could use this +as a back-up method. Likewise, you can pretend that mothership +"fetched" from you (useful when access is one sided). <5> on mothership machine, merge the work done on the satellite machine into the master branch. @@ -195,17 +219,22 @@ Branch off of a specific tag.:: $ git checkout -b private2.6.14 v2.6.14 <1> $ edit/compile/test; git commit -a $ git checkout master -$ git format-patch -k -m --stdout v2.6.14..private2.6.14 | - git am -3 -k <2> +$ git cherry-pick v2.6.14..private2.6.14 <2> ------------ + <1> create a private branch based on a well known (but somewhat behind) tag. <2> forward port all changes in `private2.6.14` branch to `master` branch -without a formal "merging". +without a formal "merging". Or longhand + +`git format-patch -k -m --stdout v2.6.14..private2.6.14 | + git am -3 -k` +An alternate participant submission mechanism is using the +`git request-pull` or pull-request mechanisms (e.g as used on +GitHub (www.github.com) to notify your upstream of your +contribution. -Integrator[[Integrator]] +Integrator[[INTEGRATOR]] ------------------------ A fairly central person acting as the integrator in a group @@ -213,6 +242,13 @@ project receives changes made by others, reviews and integrates them and publishes the result for others to use, using these commands in addition to the ones needed by participants. +This section can also be used by those who respond to `git +request-pull` or pull-request on GitHub (www.github.com) to +integrate the work of others into their history. An sub-area +lieutenant for a repository will act both as a participant and +as an integrator. + + * linkgit:git-am[1] to apply patches e-mailed in from your contributors. @@ -229,19 +265,19 @@ commands in addition to the ones needed by participants. Examples ~~~~~~~~ -My typical Git day.:: +A typical integrator's Git day.:: + ------------ $ git status <1> -$ git show-branch <2> +$ git branch --no-merged master <2> $ mailx <3> & s 2 3 4 5 ./+to-apply & s 7 8 ./+hold-linus & q $ git checkout -b topic/one master -$ git am -3 -i -s -u ./+to-apply <4> +$ git am -3 -i -s ./+to-apply <4> $ compile/test -$ git checkout -b hold/linus && git am -3 -i -s -u ./+hold-linus <5> +$ git checkout -b hold/linus && git am -3 -i -s ./+hold-linus <5> $ git checkout topic/one && git rebase master <6> $ git checkout pu && git reset --hard next <7> $ git merge topic/one topic/two && git merge hold/linus <8> @@ -249,51 +285,51 @@ $ git checkout maint $ git cherry-pick master~4 <9> $ compile/test $ git tag -s -m "GIT 0.99.9x" v0.99.9x <10> -$ git fetch ko && git show-branch master maint 'tags/ko-*' <11> -$ git push ko <12> -$ git push ko v0.99.9x <13> +$ git fetch ko && for branch in master maint next pu <11> + do + git show-branch ko/$branch $branch <12> + done +$ git push --follow-tags ko <13> ------------ + -<1> see what I was in the middle of doing, if any. -<2> see what topic branches I have and think about how ready -they are. +<1> see what you were in the middle of doing, if anything. +<2> see which branches haven't been merged into `master` yet. +Likewise for any other integration branches e.g. `maint`, `next` +and `pu` (potential updates). <3> read mails, save ones that are applicable, and save others -that are not quite ready. -<4> apply them, interactively, with my sign-offs. -<5> create topic branch as needed and apply, again with my -sign-offs. +that are not quite ready (other mail readers are available). +<4> apply them, interactively, with your sign-offs. +<5> create topic branch as needed and apply, again with sign-offs. <6> rebase internal topic branch that has not been merged to the master or exposed as a part of a stable branch. <7> restart `pu` every time from the next. <8> and bundle topic branches still cooking. <9> backport a critical fix. <10> create a signed tag. -<11> make sure I did not accidentally rewind master beyond what I -already pushed out. `ko` shorthand points at the repository I have -at kernel.org, and looks like this: +<11> make sure master was not accidentally rewound beyond that +already pushed out. `ko` shorthand points at the Git maintainer's +repository at kernel.org, and looks like this: + ------------ -$ cat .git/remotes/ko -URL: kernel.org:/pub/scm/git/git.git -Pull: master:refs/tags/ko-master -Pull: next:refs/tags/ko-next -Pull: maint:refs/tags/ko-maint -Push: master -Push: next -Push: +pu -Push: maint +(in .git/config) +[remote "ko"] + url = kernel.org:/pub/scm/git/git.git + fetch = refs/heads/*:refs/remotes/ko/* + push = refs/heads/master + push = refs/heads/next + push = +refs/heads/pu + push = refs/heads/maint ------------ + -In the output from `git show-branch`, `master` should have -everything `ko-master` has, and `next` should have -everything `ko-next` has. - -<12> push out the bleeding edge. -<13> push the tag out, too. +<12> In the output from `git show-branch`, `master` should have +everything `ko/master` has, and `next` should have +everything `ko/next` has, etc. +<13> push out the bleeding edge, together with new tags that point +into the pushed history. -Repository Administration[[Repository Administration]] ------------------------------------------------------- +Repository Administration[[ADMINISTRATION]] +------------------------------------------- A repository administrator uses the following tools to set up and maintain access to the repository by developers. @@ -304,9 +340,19 @@ and maintain access to the repository by developers. * linkgit:git-shell[1] can be used as a 'restricted login shell' for shared central repository users. + * linkgit:git-http-backend[1] provides a server side implementation + of Git-over-HTTP ("Smart http") allowing both fetch and push services. + + * linkgit:gitweb[1] provides a web front-end to Git repositories, + which can be set-up using the linkgit:git-instaweb[1] script. + link:howto/update-hook-example.html[update hook howto] has a good example of managing a shared central repository. +In addition there are a number of other widely deployed hosting, browsing +and reviewing solutions such as: + + * gitolite, gerrit code review, cgit and others. Examples ~~~~~~~~ @@ -335,22 +381,25 @@ $ cat /etc/xinetd.d/git-daemon # description: The Git server offers access to Git repositories service git { - disable = no - type = UNLISTED - port = 9418 - socket_type = stream - wait = no - user = nobody - server = /usr/bin/git-daemon - server_args = --inetd --export-all --base-path=/pub/scm - log_on_failure += USERID + disable = no + type = UNLISTED + port = 9418 + socket_type = stream + wait = no + user = nobody + server = /usr/bin/git-daemon + server_args = --inetd --export-all --base-path=/pub/scm + log_on_failure += USERID } ------------ + Check your xinetd(8) documentation and setup, this is from a Fedora system. Others might be different. -Give push/pull only access to developers.:: +Give push/pull only access to developers using git-over-ssh.:: + +e.g. those using: +`$ git push/pull ssh://host.xz/pub/scm/project` + ------------ $ grep git /etc/passwd <1> @@ -363,8 +412,8 @@ $ grep git /etc/shells <2> ------------ + <1> log-in shell is set to /usr/bin/git-shell, which does not -allow anything but `git push` and `git pull`. The users should -get an ssh access to the machine. +allow anything but `git push` and `git pull`. The users require +ssh access to the machine. <2> in many distributions /etc/shells needs to list what is used as the login shell. @@ -401,13 +450,6 @@ for branch policy control. david is the release manager and is the only person who can create and push version tags. -HTTP server to support dumb protocol transfer.:: -+ ------------- -dev$ git update-server-info <1> -dev$ ftp user@isp.example.com <2> -ftp> cp -r .git /home/user/myproject.git ------------- -+ -<1> make sure your info/refs and objects/info/packs are up-to-date -<2> upload to public HTTP server hosted by your ISP. +GIT +--- +Part of the linkgit:git[1] suite diff --git a/Documentation/gitglossary.txt b/Documentation/gitglossary.txt index e52de7dbb..212e254ad 100644 --- a/Documentation/gitglossary.txt +++ b/Documentation/gitglossary.txt @@ -19,7 +19,7 @@ SEE ALSO linkgit:gittutorial[7], linkgit:gittutorial-2[7], linkgit:gitcvs-migration[7], -link:everyday.html[Everyday Git], +linkgit:giteveryday[7], link:user-manual.html[The Git User's Manual] GIT diff --git a/Documentation/gittutorial-2.txt b/Documentation/gittutorial-2.txt index 3109ea8aa..f6fbf814f 100644 --- a/Documentation/gittutorial-2.txt +++ b/Documentation/gittutorial-2.txt @@ -403,7 +403,7 @@ What next? At this point you should know everything necessary to read the man pages for any of the git commands; one good place to start would be -with the commands mentioned in link:everyday.html[Everyday Git]. You +with the commands mentioned in linkgit:giteveryday[7]. You should be able to find any unknown jargon in linkgit:gitglossary[7]. The link:user-manual.html[Git User's Manual] provides a more @@ -427,7 +427,7 @@ linkgit:gitcvs-migration[7], linkgit:gitcore-tutorial[7], linkgit:gitglossary[7], linkgit:git-help[1], -link:everyday.html[Everyday Git], +linkgit:giteveryday[7], link:user-manual.html[The Git User's Manual] GIT diff --git a/Documentation/gittutorial.txt b/Documentation/gittutorial.txt index 826219631..af9f709cc 100644 --- a/Documentation/gittutorial.txt +++ b/Documentation/gittutorial.txt @@ -656,7 +656,7 @@ digressions that may be interesting at this point are: * linkgit:gitworkflows[7]: Gives an overview of recommended workflows. - * link:everyday.html[Everyday Git with 20 Commands Or So] + * linkgit:giteveryday[7]: Everyday Git with 20 Commands Or So. * linkgit:gitcvs-migration[7]: Git for CVS users. @@ -668,7 +668,7 @@ linkgit:gitcore-tutorial[7], linkgit:gitglossary[7], linkgit:git-help[1], linkgit:gitworkflows[7], -link:everyday.html[Everyday Git], +linkgit:giteveryday[7], link:user-manual.html[The Git User's Manual] GIT @@ -764,6 +764,7 @@ LIB_OBJS += submodule.o LIB_OBJS += symlinks.o LIB_OBJS += tag.o LIB_OBJS += trace.o +LIB_OBJS += trailer.o LIB_OBJS += transport.o LIB_OBJS += transport-helper.o LIB_OBJS += tree-diff.o @@ -828,6 +829,7 @@ BUILTIN_OBJS += builtin/hash-object.o BUILTIN_OBJS += builtin/help.o BUILTIN_OBJS += builtin/index-pack.o BUILTIN_OBJS += builtin/init-db.o +BUILTIN_OBJS += builtin/interpret-trailers.o BUILTIN_OBJS += builtin/log.o BUILTIN_OBJS += builtin/ls-files.o BUILTIN_OBJS += builtin/ls-remote.o @@ -27,7 +27,7 @@ Torvalds with help of a group of hackers around the net. Please read the file INSTALL for installation instructions. See Documentation/gittutorial.txt to get started, then see -Documentation/everyday.txt for a useful minimum set of commands, and +Documentation/giteveryday.txt for a useful minimum set of commands, and Documentation/git-commandname.txt for documentation of each command. If git has been correctly installed, then the tutorial can also be read with "man gittutorial" or "git help tutorial", and the diff --git a/archive-tar.c b/archive-tar.c index df2f4c8a6..0d1e6bd75 100644 --- a/archive-tar.c +++ b/archive-tar.c @@ -192,7 +192,7 @@ static int write_extended_header(struct archiver_args *args, unsigned int mode; memset(&header, 0, sizeof(header)); *header.typeflag = TYPEFLAG_EXT_HEADER; - mode = 0100666 & ~tar_umask; + mode = 0100666; sprintf(header.name, "%s.paxheader", sha1_to_hex(sha1)); prepare_header(args, &header, mode, size); write_blocked(&header, sizeof(header)); @@ -300,7 +300,7 @@ static int write_global_extended_header(struct archiver_args *args) strbuf_append_ext_header(&ext_header, "comment", sha1_to_hex(sha1), 40); memset(&header, 0, sizeof(header)); *header.typeflag = TYPEFLAG_GLOBAL_HEADER; - mode = 0100666 & ~tar_umask; + mode = 0100666; strcpy(header.name, "pax_global_header"); prepare_header(args, &header, mode, ext_header.len); write_blocked(&header, sizeof(header)); @@ -73,6 +73,7 @@ extern int cmd_hash_object(int argc, const char **argv, const char *prefix); extern int cmd_help(int argc, const char **argv, const char *prefix); extern int cmd_index_pack(int argc, const char **argv, const char *prefix); extern int cmd_init_db(int argc, const char **argv, const char *prefix); +extern int cmd_interpret_trailers(int argc, const char **argv, const char *prefix); extern int cmd_log(int argc, const char **argv, const char *prefix); extern int cmd_log_reflog(int argc, const char **argv, const char *prefix); extern int cmd_ls_files(int argc, const char **argv, const char *prefix); diff --git a/builtin/branch.c b/builtin/branch.c index 7e113d6c7..3b79c5087 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -62,19 +62,19 @@ static unsigned char merge_filter_ref[20]; static struct string_list output = STRING_LIST_INIT_DUP; static unsigned int colopts; -static int parse_branch_color_slot(const char *var, int ofs) +static int parse_branch_color_slot(const char *slot) { - if (!strcasecmp(var+ofs, "plain")) + if (!strcasecmp(slot, "plain")) return BRANCH_COLOR_PLAIN; - if (!strcasecmp(var+ofs, "reset")) + if (!strcasecmp(slot, "reset")) return BRANCH_COLOR_RESET; - if (!strcasecmp(var+ofs, "remote")) + if (!strcasecmp(slot, "remote")) return BRANCH_COLOR_REMOTE; - if (!strcasecmp(var+ofs, "local")) + if (!strcasecmp(slot, "local")) return BRANCH_COLOR_LOCAL; - if (!strcasecmp(var+ofs, "current")) + if (!strcasecmp(slot, "current")) return BRANCH_COLOR_CURRENT; - if (!strcasecmp(var+ofs, "upstream")) + if (!strcasecmp(slot, "upstream")) return BRANCH_COLOR_UPSTREAM; return -1; } @@ -90,13 +90,12 @@ static int git_branch_config(const char *var, const char *value, void *cb) return 0; } if (skip_prefix(var, "color.branch.", &slot_name)) { - int slot = parse_branch_color_slot(var, slot_name - var); + int slot = parse_branch_color_slot(slot_name); if (slot < 0) return 0; if (!value) return config_error_nonbool(var); - color_parse(value, var, branch_colors[slot]); - return 0; + return color_parse(value, branch_colors[slot]); } return git_color_default_config(var, value, cb); } diff --git a/builtin/clean.c b/builtin/clean.c index c35505ee6..77846762b 100644 --- a/builtin/clean.c +++ b/builtin/clean.c @@ -117,8 +117,7 @@ static int git_clean_config(const char *var, const char *value, void *cb) return 0; if (!value) return config_error_nonbool(var); - color_parse(value, var, clean_colors[slot]); - return 0; + return color_parse(value, clean_colors[slot]); } if (!strcmp(var, "clean.requireforce")) { diff --git a/builtin/commit.c b/builtin/commit.c index aa0940e38..e108c5301 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -1272,22 +1272,21 @@ static int dry_run_commit(int argc, const char **argv, const char *prefix, return commitable ? 0 : 1; } -static int parse_status_slot(const char *var, int offset) +static int parse_status_slot(const char *slot) { - if (!strcasecmp(var+offset, "header")) + if (!strcasecmp(slot, "header")) return WT_STATUS_HEADER; - if (!strcasecmp(var+offset, "branch")) + if (!strcasecmp(slot, "branch")) return WT_STATUS_ONBRANCH; - if (!strcasecmp(var+offset, "updated") - || !strcasecmp(var+offset, "added")) + if (!strcasecmp(slot, "updated") || !strcasecmp(slot, "added")) return WT_STATUS_UPDATED; - if (!strcasecmp(var+offset, "changed")) + if (!strcasecmp(slot, "changed")) return WT_STATUS_CHANGED; - if (!strcasecmp(var+offset, "untracked")) + if (!strcasecmp(slot, "untracked")) return WT_STATUS_UNTRACKED; - if (!strcasecmp(var+offset, "nobranch")) + if (!strcasecmp(slot, "nobranch")) return WT_STATUS_NOBRANCH; - if (!strcasecmp(var+offset, "unmerged")) + if (!strcasecmp(slot, "unmerged")) return WT_STATUS_UNMERGED; return -1; } @@ -1327,13 +1326,12 @@ static int git_status_config(const char *k, const char *v, void *cb) } if (skip_prefix(k, "status.color.", &slot_name) || skip_prefix(k, "color.status.", &slot_name)) { - int slot = parse_status_slot(k, slot_name - k); + int slot = parse_status_slot(slot_name); if (slot < 0) return 0; if (!v) return config_error_nonbool(k); - color_parse(v, k, s->color_palette[slot]); - return 0; + return color_parse(v, s->color_palette[slot]); } if (!strcmp(k, "status.relativepaths")) { s->relative_paths = git_config_bool(k, v); diff --git a/builtin/config.c b/builtin/config.c index 37305e93e..8cc260406 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -296,7 +296,8 @@ static int git_get_color_config(const char *var, const char *value, void *cb) if (!strcmp(var, get_color_slot)) { if (!value) config_error_nonbool(var); - color_parse(value, var, parsed_color); + if (color_parse(value, parsed_color) < 0) + return -1; get_color_found = 1; } return 0; @@ -309,8 +310,10 @@ static void get_color(const char *def_color) git_config_with_options(git_get_color_config, NULL, &given_config_source, respect_includes); - if (!get_color_found && def_color) - color_parse(def_color, "command line", parsed_color); + if (!get_color_found && def_color) { + if (color_parse(def_color, parsed_color) < 0) + die(_("unable to parse default color value")); + } fputs(parsed_color, stdout); } diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c index 3ee22b96a..603a90e29 100644 --- a/builtin/for-each-ref.c +++ b/builtin/for-each-ref.c @@ -672,7 +672,8 @@ static void populate_value(struct refinfo *ref) } else if (starts_with(name, "color:")) { char color[COLOR_MAXLEN] = ""; - color_parse(name + 6, "--format", color); + if (color_parse(name + 6, color) < 0) + die(_("unable to parse format")); v->s = xstrdup(color); continue; } else if (!strcmp(name, "flag")) { @@ -1011,7 +1012,8 @@ static void show_ref(struct refinfo *info, const char *format, int quote_style) struct atom_value resetv; char color[COLOR_MAXLEN] = ""; - color_parse("reset", "--format", color); + if (color_parse("reset", color) < 0) + die("BUG: couldn't parse 'reset' as a color"); resetv.s = color; print_value(&resetv, quote_style); } diff --git a/builtin/help.c b/builtin/help.c index 8343b4027..b3c818ee0 100644 --- a/builtin/help.c +++ b/builtin/help.c @@ -421,6 +421,7 @@ static struct { const char *help; } common_guides[] = { { "attributes", N_("Defining attributes per path") }, + { "everyday", N_("Everyday Git With 20 Commands Or So") }, { "glossary", N_("A Git glossary") }, { "ignore", N_("Specifies intentionally untracked files to ignore") }, { "modules", N_("Defining submodule properties") }, diff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c new file mode 100644 index 000000000..46838d24a --- /dev/null +++ b/builtin/interpret-trailers.c @@ -0,0 +1,44 @@ +/* + * Builtin "git interpret-trailers" + * + * Copyright (c) 2013, 2014 Christian Couder <chriscool@tuxfamily.org> + * + */ + +#include "cache.h" +#include "builtin.h" +#include "parse-options.h" +#include "string-list.h" +#include "trailer.h" + +static const char * const git_interpret_trailers_usage[] = { + N_("git interpret-trailers [--trim-empty] [(--trailer <token>[(=|:)<value>])...] [<file>...]"), + NULL +}; + +int cmd_interpret_trailers(int argc, const char **argv, const char *prefix) +{ + int trim_empty = 0; + struct string_list trailers = STRING_LIST_INIT_DUP; + + struct option options[] = { + OPT_BOOL(0, "trim-empty", &trim_empty, N_("trim empty trailers")), + OPT_STRING_LIST(0, "trailer", &trailers, N_("trailer"), + N_("trailer(s) to add")), + OPT_END() + }; + + argc = parse_options(argc, argv, prefix, options, + git_interpret_trailers_usage, 0); + + if (argc) { + int i; + for (i = 0; i < argc; i++) + process_trailers(argv[i], trim_empty, &trailers); + } else + process_trailers(NULL, trim_empty, &trailers); + + string_list_clear(&trailers, 0); + + return 0; +} diff --git a/builtin/log.c b/builtin/log.c index 83c0b9270..734aab3a7 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -391,7 +391,7 @@ static int git_log_config(const char *var, const char *value, void *cb) return 0; } if (skip_prefix(var, "color.decorate.", &slot_name)) - return parse_decorate_color_config(var, slot_name - var, value); + return parse_decorate_color_config(var, slot_name, value); if (!strcmp(var, "log.mailmap")) { use_mailmap_config = git_config_bool(var, value); return 0; diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index 27e9dc2c5..916315ff5 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -1231,7 +1231,6 @@ static const char *pack_lockfile; static const char *unpack(int err_fd, struct shallow_info *si) { struct pack_header hdr; - struct argv_array av = ARGV_ARRAY_INIT; const char *hdr_err; int status; char hdr_arg[38]; @@ -1254,16 +1253,16 @@ static const char *unpack(int err_fd, struct shallow_info *si) if (si->nr_ours || si->nr_theirs) { alt_shallow_file = setup_temporary_shallow(si->shallow); - argv_array_pushl(&av, "--shallow-file", alt_shallow_file, NULL); + argv_array_push(&child.args, "--shallow-file"); + argv_array_push(&child.args, alt_shallow_file); } if (ntohl(hdr.hdr_entries) < unpack_limit) { - argv_array_pushl(&av, "unpack-objects", hdr_arg, NULL); + argv_array_pushl(&child.args, "unpack-objects", hdr_arg, NULL); if (quiet) - argv_array_push(&av, "-q"); + argv_array_push(&child.args, "-q"); if (fsck_objects) - argv_array_push(&av, "--strict"); - child.argv = av.argv; + argv_array_push(&child.args, "--strict"); child.no_stdout = 1; child.err = err_fd; child.git_cmd = 1; @@ -1278,13 +1277,12 @@ static const char *unpack(int err_fd, struct shallow_info *si) if (gethostname(keep_arg + s, sizeof(keep_arg) - s)) strcpy(keep_arg + s, "localhost"); - argv_array_pushl(&av, "index-pack", + argv_array_pushl(&child.args, "index-pack", "--stdin", hdr_arg, keep_arg, NULL); if (fsck_objects) - argv_array_push(&av, "--strict"); + argv_array_push(&child.args, "--strict"); if (fix_thin) - argv_array_push(&av, "--fix-thin"); - child.argv = av.argv; + argv_array_push(&child.args, "--fix-thin"); child.out = -1; child.err = err_fd; child.git_cmd = 1; @@ -60,13 +60,12 @@ static int parse_attr(const char *name, int len) return -1; } -void color_parse(const char *value, const char *var, char *dst) +int color_parse(const char *value, char *dst) { - color_parse_mem(value, strlen(value), var, dst); + return color_parse_mem(value, strlen(value), dst); } -void color_parse_mem(const char *value, int value_len, const char *var, - char *dst) +int color_parse_mem(const char *value, int value_len, char *dst) { const char *ptr = value; int len = value_len; @@ -76,7 +75,7 @@ void color_parse_mem(const char *value, int value_len, const char *var, if (!strncasecmp(value, "reset", len)) { strcpy(dst, GIT_COLOR_RESET); - return; + return 0; } /* [fg [bg]] [attr]... */ @@ -153,9 +152,9 @@ void color_parse_mem(const char *value, int value_len, const char *var, *dst++ = 'm'; } *dst = 0; - return; + return 0; bad: - die("bad color value '%.*s' for variable '%s'", value_len, value, var); + return error(_("invalid color value: %.*s"), value_len, value); } int git_config_colorbool(const char *var, const char *value) @@ -77,8 +77,8 @@ int git_color_default_config(const char *var, const char *value, void *cb); int git_config_colorbool(const char *var, const char *value); int want_color(int var); -void color_parse(const char *value, const char *var, char *dst); -void color_parse_mem(const char *value, int len, const char *var, char *dst); +int color_parse(const char *value, char *dst); +int color_parse_mem(const char *value, int len, char *dst); __attribute__((format (printf, 3, 4))) int color_fprintf(FILE *fp, const char *color, const char *fmt, ...); __attribute__((format (printf, 3, 4))) diff --git a/command-list.txt b/command-list.txt index a3ff0c9e6..f1eae0810 100644 --- a/command-list.txt +++ b/command-list.txt @@ -62,6 +62,7 @@ git-imap-send foreignscminterface git-index-pack plumbingmanipulators git-init mainporcelain common git-instaweb ancillaryinterrogators +git-interpret-trailers purehelpers gitk mainporcelain git-log mainporcelain common git-ls-files plumbinginterrogators diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 2ed230a86..d548e99eb 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -281,16 +281,12 @@ __gitcomp_file () # argument, and using the options specified in the second argument. __git_ls_files_helper () { - ( - test -n "${CDPATH+set}" && unset CDPATH - cd "$1" - if [ "$2" == "--committable" ]; then - git diff-index --name-only --relative HEAD - else - # NOTE: $2 is not quoted in order to support multiple options - git ls-files --exclude-standard $2 - fi - ) 2>/dev/null + if [ "$2" == "--committable" ]; then + git -C "$1" diff-index --name-only --relative HEAD + else + # NOTE: $2 is not quoted in order to support multiple options + git -C "$1" ls-files --exclude-standard $2 + fi 2>/dev/null } @@ -522,7 +518,7 @@ __git_complete_index_file () ;; esac - __gitcomp_file "$(__git_index_files "$1" "$pfx")" "$pfx" "$cur_" + __gitcomp_file "$(__git_index_files "$1" ${pfx:+"$pfx"})" "$pfx" "$cur_" } __git_complete_file () diff --git a/contrib/contacts/.gitignore b/contrib/contacts/.gitignore new file mode 100644 index 000000000..f385ee643 --- /dev/null +++ b/contrib/contacts/.gitignore @@ -0,0 +1,3 @@ +git-contacts.1 +git-contacts.html +git-contacts.xml diff --git a/contrib/contacts/Makefile b/contrib/contacts/Makefile new file mode 100644 index 000000000..a2990f0dc --- /dev/null +++ b/contrib/contacts/Makefile @@ -0,0 +1,71 @@ +# The default target of this Makefile is... +all:: + +-include ../../config.mak.autogen +-include ../../config.mak + +prefix ?= /usr/local +gitexecdir ?= $(prefix)/libexec/git-core +mandir ?= $(prefix)/share/man +man1dir ?= $(mandir)/man1 +htmldir ?= $(prefix)/share/doc/git-doc + +../../GIT-VERSION-FILE: FORCE + $(MAKE) -C ../../ GIT-VERSION-FILE + +-include ../../GIT-VERSION-FILE + +# this should be set to a 'standard' bsd-type install program +INSTALL ?= install +RM ?= rm -f + +ASCIIDOC = asciidoc +XMLTO = xmlto + +ifndef SHELL_PATH + SHELL_PATH = /bin/sh +endif +SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) + +ASCIIDOC_CONF = ../../Documentation/asciidoc.conf +MANPAGE_XSL = ../../Documentation/manpage-normal.xsl + +GIT_CONTACTS := git-contacts + +GIT_CONTACTS_DOC := git-contacts.1 +GIT_CONTACTS_XML := git-contacts.xml +GIT_CONTACTS_TXT := git-contacts.txt +GIT_CONTACTS_HTML := git-contacts.html + +doc: $(GIT_CONTACTS_DOC) $(GIT_CONTACTS_HTML) + +install: $(GIT_CONTACTS) + $(INSTALL) -d -m 755 $(DESTDIR)$(gitexecdir) + $(INSTALL) -m 755 $(GIT_CONTACTS) $(DESTDIR)$(gitexecdir) + +install-doc: install-man install-html + +install-man: $(GIT_CONTACTS_DOC) + $(INSTALL) -d -m 755 $(DESTDIR)$(man1dir) + $(INSTALL) -m 644 $^ $(DESTDIR)$(man1dir) + +install-html: $(GIT_CONTACTS_HTML) + $(INSTALL) -d -m 755 $(DESTDIR)$(htmldir) + $(INSTALL) -m 644 $^ $(DESTDIR)$(htmldir) + +$(GIT_CONTACTS_DOC): $(GIT_CONTACTS_XML) + $(XMLTO) -m $(MANPAGE_XSL) man $^ + +$(GIT_CONTACTS_XML): $(GIT_CONTACTS_TXT) + $(ASCIIDOC) -b docbook -d manpage -f $(ASCIIDOC_CONF) \ + -agit_version=$(GIT_VERSION) $^ + +$(GIT_CONTACTS_HTML): $(GIT_CONTACTS_TXT) + $(ASCIIDOC) -b xhtml11 -d manpage -f $(ASCIIDOC_CONF) \ + -agit_version=$(GIT_VERSION) $^ + +clean: + $(RM) $(GIT_CONTACTS) + $(RM) *.xml *.html *.1 + +.PHONY: FORCE diff --git a/contrib/subtree/.gitignore b/contrib/subtree/.gitignore index 91360a3d7..0b9381abc 100644 --- a/contrib/subtree/.gitignore +++ b/contrib/subtree/.gitignore @@ -1,6 +1,7 @@ *~ git-subtree -git-subtree.xml git-subtree.1 +git-subtree.html +git-subtree.xml mainline subproj diff --git a/contrib/subtree/Makefile b/contrib/subtree/Makefile index c2bd703ee..3071baf49 100644 --- a/contrib/subtree/Makefile +++ b/contrib/subtree/Makefile @@ -5,9 +5,10 @@ all:: -include ../../config.mak prefix ?= /usr/local -mandir ?= $(prefix)/share/man gitexecdir ?= $(prefix)/libexec/git-core +mandir ?= $(prefix)/share/man man1dir ?= $(mandir)/man1 +htmldir ?= $(prefix)/share/doc/git-doc ../../GIT-VERSION-FILE: FORCE $(MAKE) -C ../../ GIT-VERSION-FILE @@ -49,12 +50,16 @@ install: $(GIT_SUBTREE) $(INSTALL) -d -m 755 $(DESTDIR)$(gitexecdir) $(INSTALL) -m 755 $(GIT_SUBTREE) $(DESTDIR)$(gitexecdir) -install-doc: install-man +install-doc: install-man install-html install-man: $(GIT_SUBTREE_DOC) $(INSTALL) -d -m 755 $(DESTDIR)$(man1dir) $(INSTALL) -m 644 $^ $(DESTDIR)$(man1dir) +install-html: $(GIT_SUBTREE_HTML) + $(INSTALL) -d -m 755 $(DESTDIR)$(htmldir) + $(INSTALL) -m 644 $^ $(DESTDIR)$(htmldir) + $(GIT_SUBTREE_DOC): $(GIT_SUBTREE_XML) $(XMLTO) -m $(MANPAGE_XSL) man $^ @@ -248,8 +248,7 @@ int git_diff_basic_config(const char *var, const char *value, void *cb) return 0; if (!value) return config_error_nonbool(var); - color_parse(value, var, diff_colors[slot]); - return 0; + return color_parse(value, diff_colors[slot]); } /* like GNU diff's --suppress-blank-empty option */ @@ -417,6 +417,7 @@ static struct cmd_struct commands[] = { { "index-pack", cmd_index_pack, RUN_SETUP_GENTLY }, { "init", cmd_init_db, NO_SETUP }, { "init-db", cmd_init_db, NO_SETUP }, + { "interpret-trailers", cmd_interpret_trailers, RUN_SETUP }, { "log", cmd_log, RUN_SETUP }, { "ls-files", cmd_ls_files, RUN_SETUP }, { "ls-remote", cmd_ls_remote, RUN_SETUP_GENTLY }, diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index a9f57d6f9..ccf75169d 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -4100,7 +4100,7 @@ sub print_search_form { if ($use_pathinfo) { $action .= "/".esc_url($project); } - print $cgi->startform(-method => "get", -action => $action) . + print $cgi->start_form(-method => "get", -action => $action) . "<div class=\"search\">\n" . (!$use_pathinfo && $cgi->input({-name=>"p", -value=>$project, -type=>"hidden"}) . "\n") . @@ -5510,7 +5510,7 @@ sub git_project_search_form { } print "<div class=\"projsearch\">\n"; - print $cgi->startform(-method => 'get', -action => $my_uri) . + print $cgi->start_form(-method => 'get', -action => $my_uri) . $cgi->hidden(-name => 'a', -value => 'project_list') . "\n"; print $cgi->hidden(-name => 'pf', -value => $project_filter). "\n" if (defined $project_filter); @@ -111,7 +111,7 @@ int grep_config(const char *var, const char *value, void *cb) if (color) { if (!value) return config_error_nonbool(var); - color_parse(value, var, color); + return color_parse(value, color); } return 0; } diff --git a/log-tree.c b/log-tree.c index cff7ac1db..7f0890e4a 100644 --- a/log-tree.c +++ b/log-tree.c @@ -56,15 +56,14 @@ static int parse_decorate_color_slot(const char *slot) return -1; } -int parse_decorate_color_config(const char *var, const int ofs, const char *value) +int parse_decorate_color_config(const char *var, const char *slot_name, const char *value) { - int slot = parse_decorate_color_slot(var + ofs); + int slot = parse_decorate_color_slot(slot_name); if (slot < 0) return 0; if (!value) return config_error_nonbool(var); - color_parse(value, var, decoration_colors[slot]); - return 0; + return color_parse(value, decoration_colors[slot]); } /* diff --git a/log-tree.h b/log-tree.h index b26160c4d..c8116e60c 100644 --- a/log-tree.h +++ b/log-tree.h @@ -7,7 +7,7 @@ struct log_info { struct commit *commit, *parent; }; -int parse_decorate_color_config(const char *var, const int ofs, const char *value); +int parse_decorate_color_config(const char *var, const char *slot_name, const char *value); void init_log_tree_opt(struct rev_info *); int log_tree_diff_flush(struct rev_info *); int log_tree_commit(struct rev_info *, struct commit *); @@ -964,9 +964,8 @@ static size_t parse_color(struct strbuf *sb, /* in UTF-8 */ if (!want_color(c->pretty_ctx->color)) return end - placeholder + 1; } - color_parse_mem(begin, - end - begin, - "--pretty format", color); + if (color_parse_mem(begin, end - begin, color) < 0) + die(_("unable to parse --pretty format")); strbuf_addstr(sb, color); return end - placeholder + 1; } @@ -82,6 +82,12 @@ appropriately before running "make". numbers matching <pattern>. The number matched against is simply the running count of the test within the file. +-x:: + Turn on shell tracing (i.e., `set -x`) during the tests + themselves. Implies `--verbose`. Note that this can cause + failures in some tests which redirect and test the + output of shell functions. Use with caution. + -d:: --debug:: This may help the person who is developing a new test. diff --git a/t/t1308-config-set.sh b/t/t1308-config-set.sh index ea0bce2dc..91235b76b 100755 --- a/t/t1308-config-set.sh +++ b/t/t1308-config-set.sh @@ -23,7 +23,7 @@ check_config () { } test_expect_success 'setup default config' ' - cat >.git/config <<\EOF + cat >.git/config <<-\EOF [case] penguin = very blue Movie = BadPhysics @@ -195,7 +195,7 @@ test_expect_success 'proper error on error in default config files' ' cp .git/config .git/config.old && test_when_finished "mv .git/config.old .git/config" && echo "[" >>.git/config && - echo "fatal: bad config file line 35 in .git/config" >expect && + echo "fatal: bad config file line 34 in .git/config" >expect && test_expect_code 128 test-config get_value foo.bar 2>actual && test_cmp expect actual ' diff --git a/t/t5004-archive-corner-cases.sh b/t/t5004-archive-corner-cases.sh index 83d20c4ba..305bcac6b 100755 --- a/t/t5004-archive-corner-cases.sh +++ b/t/t5004-archive-corner-cases.sh @@ -113,9 +113,4 @@ test_expect_success 'archive empty subtree by direct pathspec' ' check_dir extract sub ' -test_expect_success 'archive applies umask even for pax headers' ' - git archive --format=tar HEAD >archive.tar && - ! grep 0666 archive.tar -' - test_done diff --git a/t/t5304-prune.sh b/t/t5304-prune.sh index 01c6a3fc1..e32e46dee 100755 --- a/t/t5304-prune.sh +++ b/t/t5304-prune.sh @@ -13,8 +13,8 @@ add_blob() { before=$(git count-objects | sed "s/ .*//") && BLOB=$(echo aleph_0 | git hash-object -w --stdin) && BLOB_FILE=.git/objects/$(echo $BLOB | sed "s/^../&\//") && - test $((1 + $before)) = $(git count-objects | sed "s/ .*//") && - test -f $BLOB_FILE && + verbose test $((1 + $before)) = $(git count-objects | sed "s/ .*//") && + test_path_is_file $BLOB_FILE && test-chmtime =+0 $BLOB_FILE } @@ -35,9 +35,9 @@ test_expect_success 'prune stale packs' ' : > .git/objects/tmp_2.pack && test-chmtime =-86501 .git/objects/tmp_1.pack && git prune --expire 1.day && - test -f $orig_pack && - test -f .git/objects/tmp_2.pack && - ! test -f .git/objects/tmp_1.pack + test_path_is_file $orig_pack && + test_path_is_file .git/objects/tmp_2.pack && + test_path_is_missing .git/objects/tmp_1.pack ' @@ -45,12 +45,12 @@ test_expect_success 'prune --expire' ' add_blob && git prune --expire=1.hour.ago && - test $((1 + $before)) = $(git count-objects | sed "s/ .*//") && - test -f $BLOB_FILE && + verbose test $((1 + $before)) = $(git count-objects | sed "s/ .*//") && + test_path_is_file $BLOB_FILE && test-chmtime =-86500 $BLOB_FILE && git prune --expire 1.day && - test $before = $(git count-objects | sed "s/ .*//") && - ! test -f $BLOB_FILE + verbose test $before = $(git count-objects | sed "s/ .*//") && + test_path_is_missing $BLOB_FILE ' @@ -59,12 +59,12 @@ test_expect_success 'gc: implicit prune --expire' ' add_blob && test-chmtime =-$((2*$week-30)) $BLOB_FILE && git gc && - test $((1 + $before)) = $(git count-objects | sed "s/ .*//") && - test -f $BLOB_FILE && + verbose test $((1 + $before)) = $(git count-objects | sed "s/ .*//") && + test_path_is_file $BLOB_FILE && test-chmtime =-$((2*$week+1)) $BLOB_FILE && git gc && - test $before = $(git count-objects | sed "s/ .*//") && - ! test -f $BLOB_FILE + verbose test $before = $(git count-objects | sed "s/ .*//") && + test_path_is_missing $BLOB_FILE ' @@ -110,7 +110,7 @@ test_expect_success 'prune: do not prune detached HEAD with no reflog' ' git commit --allow-empty -m "detached commit" && # verify that there is no reflogs # (should be removed and disabled by previous test) - test ! -e .git/logs && + test_path_is_missing .git/logs && git prune -n >prune_actual && : >prune_expected && test_cmp prune_actual prune_expected @@ -144,8 +144,8 @@ test_expect_success 'gc --no-prune' ' test-chmtime =-$((5001*$day)) $BLOB_FILE && git config gc.pruneExpire 2.days.ago && git gc --no-prune && - test 1 = $(git count-objects | sed "s/ .*//") && - test -f $BLOB_FILE + verbose test 1 = $(git count-objects | sed "s/ .*//") && + test_path_is_file $BLOB_FILE ' @@ -153,10 +153,10 @@ test_expect_success 'gc respects gc.pruneExpire' ' git config gc.pruneExpire 5002.days.ago && git gc && - test -f $BLOB_FILE && + test_path_is_file $BLOB_FILE && git config gc.pruneExpire 5000.days.ago && git gc && - test ! -f $BLOB_FILE + test_path_is_missing $BLOB_FILE ' @@ -165,9 +165,9 @@ test_expect_success 'gc --prune=<date>' ' add_blob && test-chmtime =-$((5001*$day)) $BLOB_FILE && git gc --prune=5002.days.ago && - test -f $BLOB_FILE && + test_path_is_file $BLOB_FILE && git gc --prune=5000.days.ago && - test ! -f $BLOB_FILE + test_path_is_missing $BLOB_FILE ' @@ -175,9 +175,9 @@ test_expect_success 'gc --prune=never' ' add_blob && git gc --prune=never && - test -f $BLOB_FILE && + test_path_is_file $BLOB_FILE && git gc --prune=now && - test ! -f $BLOB_FILE + test_path_is_missing $BLOB_FILE ' @@ -186,10 +186,10 @@ test_expect_success 'gc respects gc.pruneExpire=never' ' git config gc.pruneExpire never && add_blob && git gc && - test -f $BLOB_FILE && + test_path_is_file $BLOB_FILE && git config gc.pruneExpire now && git gc && - test ! -f $BLOB_FILE + test_path_is_missing $BLOB_FILE ' @@ -197,9 +197,9 @@ test_expect_success 'prune --expire=never' ' add_blob && git prune --expire=never && - test -f $BLOB_FILE && + test_path_is_file $BLOB_FILE && git prune && - test ! -f $BLOB_FILE + test_path_is_missing $BLOB_FILE ' @@ -209,11 +209,11 @@ test_expect_success 'gc: prune old objects after local clone' ' git clone --no-hardlinks . aclone && ( cd aclone && - test 1 = $(git count-objects | sed "s/ .*//") && - test -f $BLOB_FILE && + verbose test 1 = $(git count-objects | sed "s/ .*//") && + test_path_is_file $BLOB_FILE && git gc --prune && - test 0 = $(git count-objects | sed "s/ .*//") && - ! test -f $BLOB_FILE + verbose test 0 = $(git count-objects | sed "s/ .*//") && + test_path_is_missing $BLOB_FILE ) ' @@ -250,7 +250,7 @@ test_expect_success 'prune .git/shallow' ' grep $SHA1 .git/shallow && grep $SHA1 out && git prune && - ! test -f .git/shallow + test_path_is_missing .git/shallow ' test_done diff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh new file mode 100755 index 000000000..1efb88051 --- /dev/null +++ b/t/t7513-interpret-trailers.sh @@ -0,0 +1,863 @@ +#!/bin/sh +# +# Copyright (c) 2013, 2014 Christian Couder +# + +test_description='git interpret-trailers' + +. ./test-lib.sh + +# When we want one trailing space at the end of each line, let's use sed +# to make sure that these spaces are not removed by any automatic tool. + +test_expect_success 'setup' ' + : >empty && + cat >basic_message <<-\EOF && + subject + + body + EOF + cat >complex_message_body <<-\EOF && + my subject + + my body which is long + and contains some special + chars like : = ? ! + + EOF + sed -e "s/ Z\$/ /" >complex_message_trailers <<-\EOF && + Fixes: Z + Acked-by: Z + Reviewed-by: Z + Signed-off-by: Z + EOF + cat >basic_patch <<-\EOF + --- + foo.txt | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + + diff --git a/foo.txt b/foo.txt + index 0353767..1d91aa1 100644 + --- a/foo.txt + +++ b/foo.txt + @@ -1,3 +1,3 @@ + + -bar + +baz + + -- + 1.9.rc0.11.ga562ddc + + EOF +' + +test_expect_success 'without config' ' + sed -e "s/ Z\$/ /" >expected <<-\EOF && + + ack: Peff + Reviewed-by: Z + Acked-by: Johan + EOF + git interpret-trailers --trailer "ack = Peff" --trailer "Reviewed-by" \ + --trailer "Acked-by: Johan" empty >actual && + test_cmp expected actual +' + +test_expect_success 'without config in another order' ' + sed -e "s/ Z\$/ /" >expected <<-\EOF && + + Acked-by: Johan + Reviewed-by: Z + ack: Peff + EOF + git interpret-trailers --trailer "Acked-by: Johan" --trailer "Reviewed-by" \ + --trailer "ack = Peff" empty >actual && + test_cmp expected actual +' + +test_expect_success '--trim-empty without config' ' + cat >expected <<-\EOF && + + ack: Peff + Acked-by: Johan + EOF + git interpret-trailers --trim-empty --trailer ack=Peff \ + --trailer "Reviewed-by" --trailer "Acked-by: Johan" \ + --trailer "sob:" empty >actual && + test_cmp expected actual +' + +test_expect_success 'with config option on the command line' ' + cat >expected <<-\EOF && + + Acked-by: Johan + Reviewed-by: Peff + EOF + echo "Acked-by: Johan" | + git -c "trailer.Acked-by.ifexists=addifdifferent" interpret-trailers \ + --trailer "Reviewed-by: Peff" --trailer "Acked-by: Johan" >actual && + test_cmp expected actual +' + +test_expect_success 'with config setup' ' + git config trailer.ack.key "Acked-by: " && + cat >expected <<-\EOF && + + Acked-by: Peff + EOF + git interpret-trailers --trim-empty --trailer "ack = Peff" empty >actual && + test_cmp expected actual && + git interpret-trailers --trim-empty --trailer "Acked-by = Peff" empty >actual && + test_cmp expected actual && + git interpret-trailers --trim-empty --trailer "Acked-by :Peff" empty >actual && + test_cmp expected actual +' + +test_expect_success 'with config setup and ":=" as separators' ' + git config trailer.separators ":=" && + git config trailer.ack.key "Acked-by= " && + cat >expected <<-\EOF && + + Acked-by= Peff + EOF + git interpret-trailers --trim-empty --trailer "ack = Peff" empty >actual && + test_cmp expected actual && + git interpret-trailers --trim-empty --trailer "Acked-by= Peff" empty >actual && + test_cmp expected actual && + git interpret-trailers --trim-empty --trailer "Acked-by : Peff" empty >actual && + test_cmp expected actual +' + +test_expect_success 'with config setup and "%" as separators' ' + git config trailer.separators "%" && + cat >expected <<-\EOF && + + bug% 42 + count% 10 + bug% 422 + EOF + git interpret-trailers --trim-empty --trailer "bug = 42" \ + --trailer count%10 --trailer "test: stuff" \ + --trailer "bug % 422" empty >actual && + test_cmp expected actual +' + +test_expect_success 'with "%" as separators and a message with trailers' ' + cat >special_message <<-\EOF && + Special Message + + bug% 42 + count% 10 + bug% 422 + EOF + cat >expected <<-\EOF && + Special Message + + bug% 42 + count% 10 + bug% 422 + count% 100 + EOF + git interpret-trailers --trailer count%100 \ + special_message >actual && + test_cmp expected actual +' + +test_expect_success 'with config setup and ":=#" as separators' ' + git config trailer.separators ":=#" && + git config trailer.bug.key "Bug #" && + cat >expected <<-\EOF && + + Bug #42 + EOF + git interpret-trailers --trim-empty --trailer "bug = 42" empty >actual && + test_cmp expected actual +' + +test_expect_success 'with commit basic message' ' + cat basic_message >expected && + echo >>expected && + git interpret-trailers <basic_message >actual && + test_cmp expected actual +' + +test_expect_success 'with basic patch' ' + cat basic_message >input && + cat basic_patch >>input && + cat basic_message >expected && + echo >>expected && + cat basic_patch >>expected && + git interpret-trailers <input >actual && + test_cmp expected actual +' + +test_expect_success 'with commit complex message as argument' ' + cat complex_message_body complex_message_trailers >complex_message && + cat complex_message_body >expected && + sed -e "s/ Z\$/ /" >>expected <<-\EOF && + Fixes: Z + Acked-by= Z + Reviewed-by: Z + Signed-off-by: Z + EOF + git interpret-trailers complex_message >actual && + test_cmp expected actual +' + +test_expect_success 'with 2 files arguments' ' + cat basic_message >>expected && + echo >>expected && + cat basic_patch >>expected && + git interpret-trailers complex_message input >actual && + test_cmp expected actual +' + +test_expect_success 'with message that has comments' ' + cat basic_message >>message_with_comments && + sed -e "s/ Z\$/ /" >>message_with_comments <<-\EOF && + # comment + + # other comment + Cc: Z + # yet another comment + Reviewed-by: Johan + Reviewed-by: Z + # last comment + + EOF + cat basic_patch >>message_with_comments && + cat basic_message >expected && + cat >>expected <<-\EOF && + # comment + + Reviewed-by: Johan + Cc: Peff + EOF + cat basic_patch >>expected && + git interpret-trailers --trim-empty --trailer "Cc: Peff" message_with_comments >actual && + test_cmp expected actual +' + +test_expect_success 'with commit complex message and trailer args' ' + cat complex_message_body >expected && + sed -e "s/ Z\$/ /" >>expected <<-\EOF && + Fixes: Z + Acked-by= Z + Reviewed-by: Z + Signed-off-by: Z + Acked-by= Peff + Bug #42 + EOF + git interpret-trailers --trailer "ack: Peff" \ + --trailer "bug: 42" <complex_message >actual && + test_cmp expected actual +' + +test_expect_success 'with complex patch, args and --trim-empty' ' + cat complex_message >complex_patch && + cat basic_patch >>complex_patch && + cat complex_message_body >expected && + cat >>expected <<-\EOF && + Acked-by= Peff + Bug #42 + EOF + cat basic_patch >>expected && + git interpret-trailers --trim-empty --trailer "ack: Peff" \ + --trailer "bug: 42" <complex_patch >actual && + test_cmp expected actual +' + +test_expect_success 'using "where = before"' ' + git config trailer.bug.where "before" && + cat complex_message_body >expected && + sed -e "s/ Z\$/ /" >>expected <<-\EOF && + Bug #42 + Fixes: Z + Acked-by= Z + Reviewed-by: Z + Signed-off-by: Z + Acked-by= Peff + EOF + git interpret-trailers --trailer "ack: Peff" \ + --trailer "bug: 42" complex_message >actual && + test_cmp expected actual +' + +test_expect_success 'using "where = after"' ' + git config trailer.ack.where "after" && + cat complex_message_body >expected && + sed -e "s/ Z\$/ /" >>expected <<-\EOF && + Bug #42 + Fixes: Z + Acked-by= Z + Acked-by= Peff + Reviewed-by: Z + Signed-off-by: Z + EOF + git interpret-trailers --trailer "ack: Peff" \ + --trailer "bug: 42" complex_message >actual && + test_cmp expected actual +' + +test_expect_success 'using "where = end"' ' + git config trailer.review.key "Reviewed-by" && + git config trailer.review.where "end" && + cat complex_message_body >expected && + sed -e "s/ Z\$/ /" >>expected <<-\EOF && + Fixes: Z + Acked-by= Z + Acked-by= Peff + Reviewed-by: Z + Signed-off-by: Z + Reviewed-by: Junio + Reviewed-by: Johannes + EOF + git interpret-trailers --trailer "ack: Peff" \ + --trailer "Reviewed-by: Junio" --trailer "Reviewed-by: Johannes" \ + complex_message >actual && + test_cmp expected actual +' + +test_expect_success 'using "where = start"' ' + git config trailer.review.key "Reviewed-by" && + git config trailer.review.where "start" && + cat complex_message_body >expected && + sed -e "s/ Z\$/ /" >>expected <<-\EOF && + Reviewed-by: Johannes + Reviewed-by: Junio + Fixes: Z + Acked-by= Z + Acked-by= Peff + Reviewed-by: Z + Signed-off-by: Z + EOF + git interpret-trailers --trailer "ack: Peff" \ + --trailer "Reviewed-by: Junio" --trailer "Reviewed-by: Johannes" \ + complex_message >actual && + test_cmp expected actual +' + +test_expect_success 'using "where = before" for a token in the middle of the message' ' + git config trailer.review.key "Reviewed-by:" && + git config trailer.review.where "before" && + cat complex_message_body >expected && + sed -e "s/ Z\$/ /" >>expected <<-\EOF && + Bug #42 + Fixes: Z + Acked-by= Z + Acked-by= Peff + Reviewed-by:Johan + Reviewed-by: + Signed-off-by: Z + EOF + git interpret-trailers --trailer "ack: Peff" --trailer "bug: 42" \ + --trailer "review: Johan" <complex_message >actual && + test_cmp expected actual +' + +test_expect_success 'using "where = before" and --trim-empty' ' + cat complex_message_body >expected && + cat >>expected <<-\EOF && + Bug #46 + Bug #42 + Acked-by= Peff + Reviewed-by:Johan + EOF + git interpret-trailers --trim-empty --trailer "ack: Peff" \ + --trailer "bug: 42" --trailer "review: Johan" \ + --trailer "Bug: 46" <complex_message >actual && + test_cmp expected actual +' + +test_expect_success 'the default is "ifExists = addIfDifferentNeighbor"' ' + cat complex_message_body >expected && + sed -e "s/ Z\$/ /" >>expected <<-\EOF && + Bug #42 + Fixes: Z + Acked-by= Z + Acked-by= Peff + Acked-by= Junio + Acked-by= Peff + Reviewed-by: + Signed-off-by: Z + EOF + git interpret-trailers --trailer "ack: Peff" --trailer "review:" \ + --trailer "ack: Junio" --trailer "bug: 42" --trailer "ack: Peff" \ + --trailer "ack: Peff" <complex_message >actual && + test_cmp expected actual +' + +test_expect_success 'default "ifExists" is now "addIfDifferent"' ' + git config trailer.ifexists "addIfDifferent" && + cat complex_message_body >expected && + sed -e "s/ Z\$/ /" >>expected <<-\EOF && + Bug #42 + Fixes: Z + Acked-by= Z + Acked-by= Peff + Acked-by= Junio + Reviewed-by: + Signed-off-by: Z + EOF + git interpret-trailers --trailer "ack: Peff" --trailer "review:" \ + --trailer "ack: Junio" --trailer "bug: 42" --trailer "ack: Peff" \ + --trailer "ack: Peff" <complex_message >actual && + test_cmp expected actual +' + +test_expect_success 'using "ifExists = addIfDifferent" with "where = end"' ' + git config trailer.ack.ifExists "addIfDifferent" && + git config trailer.ack.where "end" && + cat complex_message_body >expected && + sed -e "s/ Z\$/ /" >>expected <<-\EOF && + Bug #42 + Fixes: Z + Acked-by= Z + Reviewed-by: + Signed-off-by: Z + Acked-by= Peff + EOF + git interpret-trailers --trailer "ack: Peff" --trailer "review:" \ + --trailer "bug: 42" --trailer "ack: Peff" \ + <complex_message >actual && + test_cmp expected actual +' + +test_expect_success 'using "ifExists = addIfDifferent" with "where = before"' ' + git config trailer.ack.ifExists "addIfDifferent" && + git config trailer.ack.where "before" && + cat complex_message_body >expected && + sed -e "s/ Z\$/ /" >>expected <<-\EOF && + Bug #42 + Fixes: Z + Acked-by= Peff + Acked-by= Z + Reviewed-by: + Signed-off-by: Z + EOF + git interpret-trailers --trailer "ack: Peff" --trailer "review:" \ + --trailer "bug: 42" --trailer "ack: Peff" \ + <complex_message >actual && + test_cmp expected actual +' + +test_expect_success 'using "ifExists = addIfDifferentNeighbor" with "where = end"' ' + git config trailer.ack.ifExists "addIfDifferentNeighbor" && + git config trailer.ack.where "end" && + cat complex_message_body >expected && + sed -e "s/ Z\$/ /" >>expected <<-\EOF && + Bug #42 + Fixes: Z + Acked-by= Z + Reviewed-by: + Signed-off-by: Z + Acked-by= Peff + Acked-by= Junio + Tested-by: Jakub + Acked-by= Junio + Acked-by= Peff + EOF + git interpret-trailers --trailer "ack: Peff" --trailer "review:" \ + --trailer "ack: Junio" --trailer "bug: 42" \ + --trailer "Tested-by: Jakub" --trailer "ack: Junio" \ + --trailer "ack: Junio" --trailer "ack: Peff" <complex_message >actual && + test_cmp expected actual +' + +test_expect_success 'using "ifExists = addIfDifferentNeighbor" with "where = after"' ' + git config trailer.ack.ifExists "addIfDifferentNeighbor" && + git config trailer.ack.where "after" && + cat complex_message_body >expected && + sed -e "s/ Z\$/ /" >>expected <<-\EOF && + Bug #42 + Fixes: Z + Acked-by= Z + Acked-by= Peff + Acked-by= Junio + Acked-by= Peff + Reviewed-by: + Signed-off-by: Z + Tested-by: Jakub + EOF + git interpret-trailers --trailer "ack: Peff" --trailer "review:" \ + --trailer "ack: Junio" --trailer "bug: 42" \ + --trailer "Tested-by: Jakub" --trailer "ack: Junio" \ + --trailer "ack: Junio" --trailer "ack: Peff" <complex_message >actual && + test_cmp expected actual +' + +test_expect_success 'using "ifExists = addIfDifferentNeighbor" and --trim-empty' ' + git config trailer.ack.ifExists "addIfDifferentNeighbor" && + cat complex_message_body >expected && + cat >>expected <<-\EOF && + Bug #42 + Acked-by= Peff + Acked-by= Junio + Acked-by= Peff + EOF + git interpret-trailers --trim-empty --trailer "ack: Peff" \ + --trailer "Acked-by= Peff" --trailer "review:" \ + --trailer "ack: Junio" --trailer "bug: 42" \ + --trailer "ack: Peff" <complex_message >actual && + test_cmp expected actual +' + +test_expect_success 'using "ifExists = add" with "where = end"' ' + git config trailer.ack.ifExists "add" && + git config trailer.ack.where "end" && + cat complex_message_body >expected && + sed -e "s/ Z\$/ /" >>expected <<-\EOF && + Bug #42 + Fixes: Z + Acked-by= Z + Reviewed-by: + Signed-off-by: Z + Acked-by= Peff + Acked-by= Peff + Tested-by: Jakub + Acked-by= Junio + Tested-by: Johannes + Acked-by= Peff + EOF + git interpret-trailers --trailer "ack: Peff" \ + --trailer "Acked-by= Peff" --trailer "review:" \ + --trailer "Tested-by: Jakub" --trailer "ack: Junio" \ + --trailer "bug: 42" --trailer "Tested-by: Johannes" \ + --trailer "ack: Peff" <complex_message >actual && + test_cmp expected actual +' + +test_expect_success 'using "ifExists = add" with "where = after"' ' + git config trailer.ack.ifExists "add" && + git config trailer.ack.where "after" && + cat complex_message_body >expected && + sed -e "s/ Z\$/ /" >>expected <<-\EOF && + Bug #42 + Fixes: Z + Acked-by= Z + Acked-by= Peff + Acked-by= Peff + Acked-by= Junio + Acked-by= Peff + Reviewed-by: + Signed-off-by: Z + EOF + git interpret-trailers --trailer "ack: Peff" \ + --trailer "Acked-by= Peff" --trailer "review:" \ + --trailer "ack: Junio" --trailer "bug: 42" \ + --trailer "ack: Peff" <complex_message >actual && + test_cmp expected actual +' + +test_expect_success 'using "ifExists = replace"' ' + git config trailer.fix.key "Fixes: " && + git config trailer.fix.ifExists "replace" && + cat complex_message_body >expected && + sed -e "s/ Z\$/ /" >>expected <<-\EOF && + Bug #42 + Acked-by= Z + Acked-by= Junio + Acked-by= Peff + Reviewed-by: + Signed-off-by: Z + Fixes: 22 + EOF + git interpret-trailers --trailer "review:" \ + --trailer "fix=53" --trailer "ack: Junio" --trailer "fix=22" \ + --trailer "bug: 42" --trailer "ack: Peff" \ + <complex_message >actual && + test_cmp expected actual +' + +test_expect_success 'using "ifExists = replace" with "where = after"' ' + git config trailer.fix.where "after" && + cat complex_message_body >expected && + sed -e "s/ Z\$/ /" >>expected <<-\EOF && + Bug #42 + Fixes: 22 + Acked-by= Z + Acked-by= Junio + Acked-by= Peff + Reviewed-by: + Signed-off-by: Z + EOF + git interpret-trailers --trailer "review:" \ + --trailer "fix=53" --trailer "ack: Junio" --trailer "fix=22" \ + --trailer "bug: 42" --trailer "ack: Peff" \ + <complex_message >actual && + test_cmp expected actual +' + +test_expect_success 'using "ifExists = doNothing"' ' + git config trailer.fix.ifExists "doNothing" && + cat complex_message_body >expected && + sed -e "s/ Z\$/ /" >>expected <<-\EOF && + Bug #42 + Fixes: Z + Acked-by= Z + Acked-by= Junio + Acked-by= Peff + Reviewed-by: + Signed-off-by: Z + EOF + git interpret-trailers --trailer "review:" --trailer "fix=53" \ + --trailer "ack: Junio" --trailer "fix=22" \ + --trailer "bug: 42" --trailer "ack: Peff" \ + <complex_message >actual && + test_cmp expected actual +' + +test_expect_success 'the default is "ifMissing = add"' ' + git config trailer.cc.key "Cc: " && + git config trailer.cc.where "before" && + cat complex_message_body >expected && + sed -e "s/ Z\$/ /" >>expected <<-\EOF && + Bug #42 + Cc: Linus + Fixes: Z + Acked-by= Z + Acked-by= Junio + Acked-by= Peff + Reviewed-by: + Signed-off-by: Z + EOF + git interpret-trailers --trailer "review:" --trailer "fix=53" \ + --trailer "cc=Linus" --trailer "ack: Junio" \ + --trailer "fix=22" --trailer "bug: 42" --trailer "ack: Peff" \ + <complex_message >actual && + test_cmp expected actual +' + +test_expect_success 'when default "ifMissing" is "doNothing"' ' + git config trailer.ifmissing "doNothing" && + cat complex_message_body >expected && + sed -e "s/ Z\$/ /" >>expected <<-\EOF && + Fixes: Z + Acked-by= Z + Acked-by= Junio + Acked-by= Peff + Reviewed-by: + Signed-off-by: Z + EOF + git interpret-trailers --trailer "review:" --trailer "fix=53" \ + --trailer "cc=Linus" --trailer "ack: Junio" \ + --trailer "fix=22" --trailer "bug: 42" --trailer "ack: Peff" \ + <complex_message >actual && + test_cmp expected actual && + git config trailer.ifmissing "add" +' + +test_expect_success 'using "ifMissing = add" with "where = end"' ' + git config trailer.cc.key "Cc: " && + git config trailer.cc.where "end" && + git config trailer.cc.ifMissing "add" && + cat complex_message_body >expected && + sed -e "s/ Z\$/ /" >>expected <<-\EOF && + Bug #42 + Fixes: Z + Acked-by= Z + Acked-by= Junio + Acked-by= Peff + Reviewed-by: + Signed-off-by: Z + Cc: Linus + EOF + git interpret-trailers --trailer "review:" --trailer "fix=53" \ + --trailer "ack: Junio" --trailer "fix=22" \ + --trailer "bug: 42" --trailer "cc=Linus" --trailer "ack: Peff" \ + <complex_message >actual && + test_cmp expected actual +' + +test_expect_success 'using "ifMissing = add" with "where = before"' ' + git config trailer.cc.key "Cc: " && + git config trailer.cc.where "before" && + git config trailer.cc.ifMissing "add" && + cat complex_message_body >expected && + sed -e "s/ Z\$/ /" >>expected <<-\EOF && + Cc: Linus + Bug #42 + Fixes: Z + Acked-by= Z + Acked-by= Junio + Acked-by= Peff + Reviewed-by: + Signed-off-by: Z + EOF + git interpret-trailers --trailer "review:" --trailer "fix=53" \ + --trailer "ack: Junio" --trailer "fix=22" \ + --trailer "bug: 42" --trailer "cc=Linus" --trailer "ack: Peff" \ + <complex_message >actual && + test_cmp expected actual +' + +test_expect_success 'using "ifMissing = doNothing"' ' + git config trailer.cc.ifMissing "doNothing" && + cat complex_message_body >expected && + sed -e "s/ Z\$/ /" >>expected <<-\EOF && + Bug #42 + Fixes: Z + Acked-by= Z + Acked-by= Junio + Acked-by= Peff + Reviewed-by: + Signed-off-by: Z + EOF + git interpret-trailers --trailer "review:" --trailer "fix=53" \ + --trailer "cc=Linus" --trailer "ack: Junio" \ + --trailer "fix=22" --trailer "bug: 42" --trailer "ack: Peff" \ + <complex_message >actual && + test_cmp expected actual +' + +test_expect_success 'default "where" is now "after"' ' + git config trailer.where "after" && + git config --unset trailer.ack.where && + cat complex_message_body >expected && + sed -e "s/ Z\$/ /" >>expected <<-\EOF && + Bug #42 + Fixes: Z + Acked-by= Z + Acked-by= Peff + Acked-by= Peff + Acked-by= Junio + Acked-by= Peff + Reviewed-by: + Signed-off-by: Z + Tested-by: Jakub + Tested-by: Johannes + EOF + git interpret-trailers --trailer "ack: Peff" \ + --trailer "Acked-by= Peff" --trailer "review:" \ + --trailer "Tested-by: Jakub" --trailer "ack: Junio" \ + --trailer "bug: 42" --trailer "Tested-by: Johannes" \ + --trailer "ack: Peff" <complex_message >actual && + test_cmp expected actual +' + +test_expect_success 'with simple command' ' + git config trailer.sign.key "Signed-off-by: " && + git config trailer.sign.where "after" && + git config trailer.sign.ifExists "addIfDifferentNeighbor" && + git config trailer.sign.command "echo \"A U Thor <author@example.com>\"" && + cat complex_message_body >expected && + sed -e "s/ Z\$/ /" >>expected <<-\EOF && + Fixes: Z + Acked-by= Z + Reviewed-by: + Signed-off-by: Z + Signed-off-by: A U Thor <author@example.com> + EOF + git interpret-trailers --trailer "review:" --trailer "fix=22" \ + <complex_message >actual && + test_cmp expected actual +' + +test_expect_success 'with command using commiter information' ' + git config trailer.sign.ifExists "addIfDifferent" && + git config trailer.sign.command "echo \"\$GIT_COMMITTER_NAME <\$GIT_COMMITTER_EMAIL>\"" && + cat complex_message_body >expected && + sed -e "s/ Z\$/ /" >>expected <<-\EOF && + Fixes: Z + Acked-by= Z + Reviewed-by: + Signed-off-by: Z + Signed-off-by: C O Mitter <committer@example.com> + EOF + git interpret-trailers --trailer "review:" --trailer "fix=22" \ + <complex_message >actual && + test_cmp expected actual +' + +test_expect_success 'with command using author information' ' + git config trailer.sign.key "Signed-off-by: " && + git config trailer.sign.where "after" && + git config trailer.sign.ifExists "addIfDifferentNeighbor" && + git config trailer.sign.command "echo \"\$GIT_AUTHOR_NAME <\$GIT_AUTHOR_EMAIL>\"" && + cat complex_message_body >expected && + sed -e "s/ Z\$/ /" >>expected <<-\EOF && + Fixes: Z + Acked-by= Z + Reviewed-by: + Signed-off-by: Z + Signed-off-by: A U Thor <author@example.com> + EOF + git interpret-trailers --trailer "review:" --trailer "fix=22" \ + <complex_message >actual && + test_cmp expected actual +' + +test_expect_success 'setup a commit' ' + echo "Content of the first commit." > a.txt && + git add a.txt && + git commit -m "Add file a.txt" +' + +test_expect_success 'with command using $ARG' ' + git config trailer.fix.ifExists "replace" && + git config trailer.fix.command "git log -1 --oneline --format=\"%h (%s)\" --abbrev-commit --abbrev=14 \$ARG" && + FIXED=$(git log -1 --oneline --format="%h (%s)" --abbrev-commit --abbrev=14 HEAD) && + cat complex_message_body >expected && + sed -e "s/ Z\$/ /" >>expected <<-EOF && + Fixes: $FIXED + Acked-by= Z + Reviewed-by: + Signed-off-by: Z + Signed-off-by: A U Thor <author@example.com> + EOF + git interpret-trailers --trailer "review:" --trailer "fix=HEAD" \ + <complex_message >actual && + test_cmp expected actual +' + +test_expect_success 'with failing command using $ARG' ' + git config trailer.fix.ifExists "replace" && + git config trailer.fix.command "false \$ARG" && + cat complex_message_body >expected && + sed -e "s/ Z\$/ /" >>expected <<-EOF && + Fixes: Z + Acked-by= Z + Reviewed-by: + Signed-off-by: Z + Signed-off-by: A U Thor <author@example.com> + EOF + git interpret-trailers --trailer "review:" --trailer "fix=HEAD" \ + <complex_message >actual && + test_cmp expected actual +' + +test_expect_success 'with empty tokens' ' + git config --unset trailer.fix.command && + cat >expected <<-EOF && + + Signed-off-by: A U Thor <author@example.com> + EOF + git interpret-trailers --trailer ":" --trailer ":test" >actual <<-EOF && + EOF + test_cmp expected actual +' + +test_expect_success 'with command but no key' ' + git config --unset trailer.sign.key && + cat >expected <<-EOF && + + sign: A U Thor <author@example.com> + EOF + git interpret-trailers >actual <<-EOF && + EOF + test_cmp expected actual +' + +test_expect_success 'with no command and no key' ' + git config --unset trailer.review.key && + cat >expected <<-EOF && + + review: Junio + sign: A U Thor <author@example.com> + EOF + git interpret-trailers --trailer "review:Junio" >actual <<-EOF && + EOF + test_cmp expected actual +' + +test_done diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh index dafd6ad21..b7957b87b 100644 --- a/t/test-lib-functions.sh +++ b/t/test-lib-functions.sh @@ -634,6 +634,15 @@ test_cmp_bin() { cmp "$@" } +# Call any command "$@" but be more verbose about its +# failure. This is handy for commands like "test" which do +# not output anything when they fail. +verbose () { + "$@" && return 0 + echo >&2 "command failed: $(git rev-parse --sq-quote "$@")" + return 1 +} + # Check if the file expected to be empty is indeed empty, and barfs # otherwise. diff --git a/t/test-lib.sh b/t/test-lib.sh index 0f4a67bfc..cf19339cc 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -233,6 +233,10 @@ do --root=*) root=$(expr "z$1" : 'z[^=]*=\(.*\)') shift ;; + -x) + trace=t + verbose=t + shift ;; *) echo "error: unknown test option '$1'" >&2; exit 1 ;; esac @@ -517,10 +521,39 @@ maybe_setup_valgrind () { fi } +# This is a separate function because some tests use +# "return" to end a test_expect_success block early +# (and we want to make sure we run any cleanup like +# "set +x"). +test_eval_inner_ () { + # Do not add anything extra (including LF) after '$*' + eval " + test \"$trace\" = t && set -x + $*" +} + test_eval_ () { - # This is a separate function because some tests use - # "return" to end a test_expect_success block early. - eval </dev/null >&3 2>&4 "$*" + # We run this block with stderr redirected to avoid extra cruft + # during a "-x" trace. Once in "set -x" mode, we cannot prevent + # the shell from printing the "set +x" to turn it off (nor the saving + # of $? before that). But we can make sure that the output goes to + # /dev/null. + # + # The test itself is run with stderr put back to &4 (so either to + # /dev/null, or to the original stderr if --verbose was used). + { + test_eval_inner_ "$@" </dev/null >&3 2>&4 + test_eval_ret_=$? + if test "$trace" = t + then + set +x + if test "$test_eval_ret_" != 0 + then + say_color error >&4 "error: last command exited with \$?=$test_eval_ret_" + fi + fi + } 2>/dev/null + return $test_eval_ret_ } test_run_ () { @@ -531,7 +564,8 @@ test_run_ () { eval_ret=$? teardown_malloc_check - if test -z "$immediate" || test $eval_ret = 0 || test -n "$expecting_failure" + if test -z "$immediate" || test $eval_ret = 0 || + test -n "$expecting_failure" && test "$test_cleanup" != ":" then setup_malloc_check test_eval_ "$test_cleanup" @@ -385,7 +385,7 @@ static inline uint64_t gettimeofday_nanos(void) * Returns nanoseconds since the epoch (01/01/1970), for performance tracing * (i.e. favoring high precision over wall clock time accuracy). */ -inline uint64_t getnanotime(void) +uint64_t getnanotime(void) { static uint64_t offset; if (offset > 1) { diff --git a/trailer.c b/trailer.c new file mode 100644 index 000000000..851456656 --- /dev/null +++ b/trailer.c @@ -0,0 +1,851 @@ +#include "cache.h" +#include "string-list.h" +#include "run-command.h" +#include "string-list.h" +#include "trailer.h" +/* + * Copyright (c) 2013, 2014 Christian Couder <chriscool@tuxfamily.org> + */ + +enum action_where { WHERE_END, WHERE_AFTER, WHERE_BEFORE, WHERE_START }; +enum action_if_exists { EXISTS_ADD_IF_DIFFERENT_NEIGHBOR, EXISTS_ADD_IF_DIFFERENT, + EXISTS_ADD, EXISTS_REPLACE, EXISTS_DO_NOTHING }; +enum action_if_missing { MISSING_ADD, MISSING_DO_NOTHING }; + +struct conf_info { + char *name; + char *key; + char *command; + enum action_where where; + enum action_if_exists if_exists; + enum action_if_missing if_missing; +}; + +static struct conf_info default_conf_info; + +struct trailer_item { + struct trailer_item *previous; + struct trailer_item *next; + const char *token; + const char *value; + struct conf_info conf; +}; + +static struct trailer_item *first_conf_item; + +static char *separators = ":"; + +#define TRAILER_ARG_STRING "$ARG" + +static int after_or_end(enum action_where where) +{ + return (where == WHERE_AFTER) || (where == WHERE_END); +} + +/* + * Return the length of the string not including any final + * punctuation. E.g., the input "Signed-off-by:" would return + * 13, stripping the trailing punctuation but retaining + * internal punctuation. + */ +static size_t token_len_without_separator(const char *token, size_t len) +{ + while (len > 0 && !isalnum(token[len - 1])) + len--; + return len; +} + +static int same_token(struct trailer_item *a, struct trailer_item *b) +{ + size_t a_len = token_len_without_separator(a->token, strlen(a->token)); + size_t b_len = token_len_without_separator(b->token, strlen(b->token)); + size_t min_len = (a_len > b_len) ? b_len : a_len; + + return !strncasecmp(a->token, b->token, min_len); +} + +static int same_value(struct trailer_item *a, struct trailer_item *b) +{ + return !strcasecmp(a->value, b->value); +} + +static int same_trailer(struct trailer_item *a, struct trailer_item *b) +{ + return same_token(a, b) && same_value(a, b); +} + +static inline int contains_only_spaces(const char *str) +{ + const char *s = str; + while (*s && isspace(*s)) + s++; + return !*s; +} + +static inline void strbuf_replace(struct strbuf *sb, const char *a, const char *b) +{ + const char *ptr = strstr(sb->buf, a); + if (ptr) + strbuf_splice(sb, ptr - sb->buf, strlen(a), b, strlen(b)); +} + +static void free_trailer_item(struct trailer_item *item) +{ + free(item->conf.name); + free(item->conf.key); + free(item->conf.command); + free((char *)item->token); + free((char *)item->value); + free(item); +} + +static char last_non_space_char(const char *s) +{ + int i; + for (i = strlen(s) - 1; i >= 0; i--) + if (!isspace(s[i])) + return s[i]; + return '\0'; +} + +static void print_tok_val(const char *tok, const char *val) +{ + char c = last_non_space_char(tok); + if (!c) + return; + if (strchr(separators, c)) + printf("%s%s\n", tok, val); + else + printf("%s%c %s\n", tok, separators[0], val); +} + +static void print_all(struct trailer_item *first, int trim_empty) +{ + struct trailer_item *item; + for (item = first; item; item = item->next) { + if (!trim_empty || strlen(item->value) > 0) + print_tok_val(item->token, item->value); + } +} + +static void update_last(struct trailer_item **last) +{ + if (*last) + while ((*last)->next != NULL) + *last = (*last)->next; +} + +static void update_first(struct trailer_item **first) +{ + if (*first) + while ((*first)->previous != NULL) + *first = (*first)->previous; +} + +static void add_arg_to_input_list(struct trailer_item *on_tok, + struct trailer_item *arg_tok, + struct trailer_item **first, + struct trailer_item **last) +{ + if (after_or_end(arg_tok->conf.where)) { + arg_tok->next = on_tok->next; + on_tok->next = arg_tok; + arg_tok->previous = on_tok; + if (arg_tok->next) + arg_tok->next->previous = arg_tok; + update_last(last); + } else { + arg_tok->previous = on_tok->previous; + on_tok->previous = arg_tok; + arg_tok->next = on_tok; + if (arg_tok->previous) + arg_tok->previous->next = arg_tok; + update_first(first); + } +} + +static int check_if_different(struct trailer_item *in_tok, + struct trailer_item *arg_tok, + int check_all) +{ + enum action_where where = arg_tok->conf.where; + do { + if (!in_tok) + return 1; + if (same_trailer(in_tok, arg_tok)) + return 0; + /* + * if we want to add a trailer after another one, + * we have to check those before this one + */ + in_tok = after_or_end(where) ? in_tok->previous : in_tok->next; + } while (check_all); + return 1; +} + +static void remove_from_list(struct trailer_item *item, + struct trailer_item **first, + struct trailer_item **last) +{ + struct trailer_item *next = item->next; + struct trailer_item *previous = item->previous; + + if (next) { + item->next->previous = previous; + item->next = NULL; + } else if (last) + *last = previous; + + if (previous) { + item->previous->next = next; + item->previous = NULL; + } else if (first) + *first = next; +} + +static struct trailer_item *remove_first(struct trailer_item **first) +{ + struct trailer_item *item = *first; + *first = item->next; + if (item->next) { + item->next->previous = NULL; + item->next = NULL; + } + return item; +} + +static int read_from_command(struct child_process *cp, struct strbuf *buf) +{ + if (run_command(cp)) + return error("running trailer command '%s' failed", cp->argv[0]); + if (strbuf_read(buf, cp->out, 1024) < 1) + return error("reading from trailer command '%s' failed", cp->argv[0]); + strbuf_trim(buf); + return 0; +} + +static const char *apply_command(const char *command, const char *arg) +{ + struct strbuf cmd = STRBUF_INIT; + struct strbuf buf = STRBUF_INIT; + struct child_process cp; + const char *argv[] = {NULL, NULL}; + const char *result; + + strbuf_addstr(&cmd, command); + if (arg) + strbuf_replace(&cmd, TRAILER_ARG_STRING, arg); + + argv[0] = cmd.buf; + memset(&cp, 0, sizeof(cp)); + cp.argv = argv; + cp.env = local_repo_env; + cp.no_stdin = 1; + cp.out = -1; + cp.use_shell = 1; + + if (read_from_command(&cp, &buf)) { + strbuf_release(&buf); + result = xstrdup(""); + } else + result = strbuf_detach(&buf, NULL); + + strbuf_release(&cmd); + return result; +} + +static void apply_item_command(struct trailer_item *in_tok, struct trailer_item *arg_tok) +{ + if (arg_tok->conf.command) { + const char *arg; + if (arg_tok->value && arg_tok->value[0]) { + arg = arg_tok->value; + } else { + if (in_tok && in_tok->value) + arg = xstrdup(in_tok->value); + else + arg = xstrdup(""); + } + arg_tok->value = apply_command(arg_tok->conf.command, arg); + free((char *)arg); + } +} + +static void apply_arg_if_exists(struct trailer_item *in_tok, + struct trailer_item *arg_tok, + struct trailer_item *on_tok, + struct trailer_item **in_tok_first, + struct trailer_item **in_tok_last) +{ + switch (arg_tok->conf.if_exists) { + case EXISTS_DO_NOTHING: + free_trailer_item(arg_tok); + break; + case EXISTS_REPLACE: + apply_item_command(in_tok, arg_tok); + add_arg_to_input_list(on_tok, arg_tok, + in_tok_first, in_tok_last); + remove_from_list(in_tok, in_tok_first, in_tok_last); + free_trailer_item(in_tok); + break; + case EXISTS_ADD: + apply_item_command(in_tok, arg_tok); + add_arg_to_input_list(on_tok, arg_tok, + in_tok_first, in_tok_last); + break; + case EXISTS_ADD_IF_DIFFERENT: + apply_item_command(in_tok, arg_tok); + if (check_if_different(in_tok, arg_tok, 1)) + add_arg_to_input_list(on_tok, arg_tok, + in_tok_first, in_tok_last); + else + free_trailer_item(arg_tok); + break; + case EXISTS_ADD_IF_DIFFERENT_NEIGHBOR: + apply_item_command(in_tok, arg_tok); + if (check_if_different(on_tok, arg_tok, 0)) + add_arg_to_input_list(on_tok, arg_tok, + in_tok_first, in_tok_last); + else + free_trailer_item(arg_tok); + break; + } +} + +static void apply_arg_if_missing(struct trailer_item **in_tok_first, + struct trailer_item **in_tok_last, + struct trailer_item *arg_tok) +{ + struct trailer_item **in_tok; + enum action_where where; + + switch (arg_tok->conf.if_missing) { + case MISSING_DO_NOTHING: + free_trailer_item(arg_tok); + break; + case MISSING_ADD: + where = arg_tok->conf.where; + in_tok = after_or_end(where) ? in_tok_last : in_tok_first; + apply_item_command(NULL, arg_tok); + if (*in_tok) { + add_arg_to_input_list(*in_tok, arg_tok, + in_tok_first, in_tok_last); + } else { + *in_tok_first = arg_tok; + *in_tok_last = arg_tok; + } + break; + } +} + +static int find_same_and_apply_arg(struct trailer_item **in_tok_first, + struct trailer_item **in_tok_last, + struct trailer_item *arg_tok) +{ + struct trailer_item *in_tok; + struct trailer_item *on_tok; + struct trailer_item *following_tok; + + enum action_where where = arg_tok->conf.where; + int middle = (where == WHERE_AFTER) || (where == WHERE_BEFORE); + int backwards = after_or_end(where); + struct trailer_item *start_tok = backwards ? *in_tok_last : *in_tok_first; + + for (in_tok = start_tok; in_tok; in_tok = following_tok) { + following_tok = backwards ? in_tok->previous : in_tok->next; + if (!same_token(in_tok, arg_tok)) + continue; + on_tok = middle ? in_tok : start_tok; + apply_arg_if_exists(in_tok, arg_tok, on_tok, + in_tok_first, in_tok_last); + return 1; + } + return 0; +} + +static void process_trailers_lists(struct trailer_item **in_tok_first, + struct trailer_item **in_tok_last, + struct trailer_item **arg_tok_first) +{ + struct trailer_item *arg_tok; + struct trailer_item *next_arg; + + if (!*arg_tok_first) + return; + + for (arg_tok = *arg_tok_first; arg_tok; arg_tok = next_arg) { + int applied = 0; + + next_arg = arg_tok->next; + remove_from_list(arg_tok, arg_tok_first, NULL); + + applied = find_same_and_apply_arg(in_tok_first, + in_tok_last, + arg_tok); + + if (!applied) + apply_arg_if_missing(in_tok_first, + in_tok_last, + arg_tok); + } +} + +static int set_where(struct conf_info *item, const char *value) +{ + if (!strcasecmp("after", value)) + item->where = WHERE_AFTER; + else if (!strcasecmp("before", value)) + item->where = WHERE_BEFORE; + else if (!strcasecmp("end", value)) + item->where = WHERE_END; + else if (!strcasecmp("start", value)) + item->where = WHERE_START; + else + return -1; + return 0; +} + +static int set_if_exists(struct conf_info *item, const char *value) +{ + if (!strcasecmp("addIfDifferent", value)) + item->if_exists = EXISTS_ADD_IF_DIFFERENT; + else if (!strcasecmp("addIfDifferentNeighbor", value)) + item->if_exists = EXISTS_ADD_IF_DIFFERENT_NEIGHBOR; + else if (!strcasecmp("add", value)) + item->if_exists = EXISTS_ADD; + else if (!strcasecmp("replace", value)) + item->if_exists = EXISTS_REPLACE; + else if (!strcasecmp("doNothing", value)) + item->if_exists = EXISTS_DO_NOTHING; + else + return -1; + return 0; +} + +static int set_if_missing(struct conf_info *item, const char *value) +{ + if (!strcasecmp("doNothing", value)) + item->if_missing = MISSING_DO_NOTHING; + else if (!strcasecmp("add", value)) + item->if_missing = MISSING_ADD; + else + return -1; + return 0; +} + +static void duplicate_conf(struct conf_info *dst, struct conf_info *src) +{ + *dst = *src; + if (src->name) + dst->name = xstrdup(src->name); + if (src->key) + dst->key = xstrdup(src->key); + if (src->command) + dst->command = xstrdup(src->command); +} + +static struct trailer_item *get_conf_item(const char *name) +{ + struct trailer_item *item; + struct trailer_item *previous; + + /* Look up item with same name */ + for (previous = NULL, item = first_conf_item; + item; + previous = item, item = item->next) { + if (!strcasecmp(item->conf.name, name)) + return item; + } + + /* Item does not already exists, create it */ + item = xcalloc(sizeof(struct trailer_item), 1); + duplicate_conf(&item->conf, &default_conf_info); + item->conf.name = xstrdup(name); + + if (!previous) + first_conf_item = item; + else { + previous->next = item; + item->previous = previous; + } + + return item; +} + +enum trailer_info_type { TRAILER_KEY, TRAILER_COMMAND, TRAILER_WHERE, + TRAILER_IF_EXISTS, TRAILER_IF_MISSING }; + +static struct { + const char *name; + enum trailer_info_type type; +} trailer_config_items[] = { + { "key", TRAILER_KEY }, + { "command", TRAILER_COMMAND }, + { "where", TRAILER_WHERE }, + { "ifexists", TRAILER_IF_EXISTS }, + { "ifmissing", TRAILER_IF_MISSING } +}; + +static int git_trailer_default_config(const char *conf_key, const char *value, void *cb) +{ + const char *trailer_item, *variable_name; + + if (!skip_prefix(conf_key, "trailer.", &trailer_item)) + return 0; + + variable_name = strrchr(trailer_item, '.'); + if (!variable_name) { + if (!strcmp(trailer_item, "where")) { + if (set_where(&default_conf_info, value) < 0) + warning(_("unknown value '%s' for key '%s'"), + value, conf_key); + } else if (!strcmp(trailer_item, "ifexists")) { + if (set_if_exists(&default_conf_info, value) < 0) + warning(_("unknown value '%s' for key '%s'"), + value, conf_key); + } else if (!strcmp(trailer_item, "ifmissing")) { + if (set_if_missing(&default_conf_info, value) < 0) + warning(_("unknown value '%s' for key '%s'"), + value, conf_key); + } else if (!strcmp(trailer_item, "separators")) { + separators = xstrdup(value); + } + } + return 0; +} + +static int git_trailer_config(const char *conf_key, const char *value, void *cb) +{ + const char *trailer_item, *variable_name; + struct trailer_item *item; + struct conf_info *conf; + char *name = NULL; + enum trailer_info_type type; + int i; + + if (!skip_prefix(conf_key, "trailer.", &trailer_item)) + return 0; + + variable_name = strrchr(trailer_item, '.'); + if (!variable_name) + return 0; + + variable_name++; + for (i = 0; i < ARRAY_SIZE(trailer_config_items); i++) { + if (strcmp(trailer_config_items[i].name, variable_name)) + continue; + name = xstrndup(trailer_item, variable_name - trailer_item - 1); + type = trailer_config_items[i].type; + break; + } + + if (!name) + return 0; + + item = get_conf_item(name); + conf = &item->conf; + free(name); + + switch (type) { + case TRAILER_KEY: + if (conf->key) + warning(_("more than one %s"), conf_key); + conf->key = xstrdup(value); + break; + case TRAILER_COMMAND: + if (conf->command) + warning(_("more than one %s"), conf_key); + conf->command = xstrdup(value); + break; + case TRAILER_WHERE: + if (set_where(conf, value)) + warning(_("unknown value '%s' for key '%s'"), value, conf_key); + break; + case TRAILER_IF_EXISTS: + if (set_if_exists(conf, value)) + warning(_("unknown value '%s' for key '%s'"), value, conf_key); + break; + case TRAILER_IF_MISSING: + if (set_if_missing(conf, value)) + warning(_("unknown value '%s' for key '%s'"), value, conf_key); + break; + default: + die("internal bug in trailer.c"); + } + return 0; +} + +static int parse_trailer(struct strbuf *tok, struct strbuf *val, const char *trailer) +{ + size_t len; + struct strbuf seps = STRBUF_INIT; + strbuf_addstr(&seps, separators); + strbuf_addch(&seps, '='); + len = strcspn(trailer, seps.buf); + strbuf_release(&seps); + if (len == 0) + return error(_("empty trailer token in trailer '%s'"), trailer); + if (len < strlen(trailer)) { + strbuf_add(tok, trailer, len); + strbuf_trim(tok); + strbuf_addstr(val, trailer + len + 1); + strbuf_trim(val); + } else { + strbuf_addstr(tok, trailer); + strbuf_trim(tok); + } + return 0; +} + +static const char *token_from_item(struct trailer_item *item, char *tok) +{ + if (item->conf.key) + return item->conf.key; + if (tok) + return tok; + return item->conf.name; +} + +static struct trailer_item *new_trailer_item(struct trailer_item *conf_item, + char *tok, char *val) +{ + struct trailer_item *new = xcalloc(sizeof(*new), 1); + new->value = val ? val : xstrdup(""); + + if (conf_item) { + duplicate_conf(&new->conf, &conf_item->conf); + new->token = xstrdup(token_from_item(conf_item, tok)); + free(tok); + } else { + duplicate_conf(&new->conf, &default_conf_info); + new->token = tok; + } + + return new; +} + +static int token_matches_item(const char *tok, struct trailer_item *item, int tok_len) +{ + if (!strncasecmp(tok, item->conf.name, tok_len)) + return 1; + return item->conf.key ? !strncasecmp(tok, item->conf.key, tok_len) : 0; +} + +static struct trailer_item *create_trailer_item(const char *string) +{ + struct strbuf tok = STRBUF_INIT; + struct strbuf val = STRBUF_INIT; + struct trailer_item *item; + int tok_len; + + if (parse_trailer(&tok, &val, string)) + return NULL; + + tok_len = token_len_without_separator(tok.buf, tok.len); + + /* Lookup if the token matches something in the config */ + for (item = first_conf_item; item; item = item->next) { + if (token_matches_item(tok.buf, item, tok_len)) + return new_trailer_item(item, + strbuf_detach(&tok, NULL), + strbuf_detach(&val, NULL)); + } + + return new_trailer_item(NULL, + strbuf_detach(&tok, NULL), + strbuf_detach(&val, NULL)); +} + +static void add_trailer_item(struct trailer_item **first, + struct trailer_item **last, + struct trailer_item *new) +{ + if (!new) + return; + if (!*last) { + *first = new; + *last = new; + } else { + (*last)->next = new; + new->previous = *last; + *last = new; + } +} + +static struct trailer_item *process_command_line_args(struct string_list *trailers) +{ + struct trailer_item *arg_tok_first = NULL; + struct trailer_item *arg_tok_last = NULL; + struct string_list_item *tr; + struct trailer_item *item; + + /* Add a trailer item for each configured trailer with a command */ + for (item = first_conf_item; item; item = item->next) { + if (item->conf.command) { + struct trailer_item *new = new_trailer_item(item, NULL, NULL); + add_trailer_item(&arg_tok_first, &arg_tok_last, new); + } + } + + /* Add a trailer item for each trailer on the command line */ + for_each_string_list_item(tr, trailers) { + struct trailer_item *new = create_trailer_item(tr->string); + add_trailer_item(&arg_tok_first, &arg_tok_last, new); + } + + return arg_tok_first; +} + +static struct strbuf **read_input_file(const char *file) +{ + struct strbuf **lines; + struct strbuf sb = STRBUF_INIT; + + if (file) { + if (strbuf_read_file(&sb, file, 0) < 0) + die_errno(_("could not read input file '%s'"), file); + } else { + if (strbuf_read(&sb, fileno(stdin), 0) < 0) + die_errno(_("could not read from stdin")); + } + + lines = strbuf_split(&sb, '\n'); + + strbuf_release(&sb); + + return lines; +} + +/* + * Return the (0 based) index of the start of the patch or the line + * count if there is no patch in the message. + */ +static int find_patch_start(struct strbuf **lines, int count) +{ + int i; + + /* Get the start of the patch part if any */ + for (i = 0; i < count; i++) { + if (starts_with(lines[i]->buf, "---")) + return i; + } + + return count; +} + +/* + * Return the (0 based) index of the first trailer line or count if + * there are no trailers. Trailers are searched only in the lines from + * index (count - 1) down to index 0. + */ +static int find_trailer_start(struct strbuf **lines, int count) +{ + int start, only_spaces = 1; + + /* + * Get the start of the trailers by looking starting from the end + * for a line with only spaces before lines with one separator. + */ + for (start = count - 1; start >= 0; start--) { + if (lines[start]->buf[0] == comment_line_char) + continue; + if (contains_only_spaces(lines[start]->buf)) { + if (only_spaces) + continue; + return start + 1; + } + if (strcspn(lines[start]->buf, separators) < lines[start]->len) { + if (only_spaces) + only_spaces = 0; + continue; + } + return count; + } + + return only_spaces ? count : 0; +} + +static int has_blank_line_before(struct strbuf **lines, int start) +{ + for (;start >= 0; start--) { + if (lines[start]->buf[0] == comment_line_char) + continue; + return contains_only_spaces(lines[start]->buf); + } + return 0; +} + +static void print_lines(struct strbuf **lines, int start, int end) +{ + int i; + for (i = start; lines[i] && i < end; i++) + printf("%s", lines[i]->buf); +} + +static int process_input_file(struct strbuf **lines, + struct trailer_item **in_tok_first, + struct trailer_item **in_tok_last) +{ + int count = 0; + int patch_start, trailer_start, i; + + /* Get the line count */ + while (lines[count]) + count++; + + patch_start = find_patch_start(lines, count); + trailer_start = find_trailer_start(lines, patch_start); + + /* Print lines before the trailers as is */ + print_lines(lines, 0, trailer_start); + + if (!has_blank_line_before(lines, trailer_start - 1)) + printf("\n"); + + /* Parse trailer lines */ + for (i = trailer_start; i < patch_start; i++) { + struct trailer_item *new = create_trailer_item(lines[i]->buf); + add_trailer_item(in_tok_first, in_tok_last, new); + } + + return patch_start; +} + +static void free_all(struct trailer_item **first) +{ + while (*first) { + struct trailer_item *item = remove_first(first); + free_trailer_item(item); + } +} + +void process_trailers(const char *file, int trim_empty, struct string_list *trailers) +{ + struct trailer_item *in_tok_first = NULL; + struct trailer_item *in_tok_last = NULL; + struct trailer_item *arg_tok_first; + struct strbuf **lines; + int patch_start; + + /* Default config must be setup first */ + git_config(git_trailer_default_config, NULL); + git_config(git_trailer_config, NULL); + + lines = read_input_file(file); + + /* Print the lines before the trailers */ + patch_start = process_input_file(lines, &in_tok_first, &in_tok_last); + + arg_tok_first = process_command_line_args(trailers); + + process_trailers_lists(&in_tok_first, &in_tok_last, &arg_tok_first); + + print_all(in_tok_first, trim_empty); + + free_all(&in_tok_first); + + /* Print the lines after the trailers as is */ + print_lines(lines, patch_start, INT_MAX); + + strbuf_list_free(lines); +} diff --git a/trailer.h b/trailer.h new file mode 100644 index 000000000..8eb25d565 --- /dev/null +++ b/trailer.h @@ -0,0 +1,6 @@ +#ifndef TRAILER_H +#define TRAILER_H + +void process_trailers(const char *file, int trim_empty, struct string_list *trailers); + +#endif /* TRAILER_H */ |