diff options
156 files changed, 4927 insertions, 1545 deletions
diff --git a/.gitignore b/.gitignore index 5bfb234591..a05241916c 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 @@ -199,6 +200,7 @@ /test-revision-walking /test-run-command /test-sha1 +/test-sha1-array /test-sigchain /test-string-list /test-subprocess @@ -205,6 +205,7 @@ Shawn O. Pearce <spearce@spearce.org> Simon Hausmann <hausmann@kde.org> <simon@lst.de> Simon Hausmann <hausmann@kde.org> <shausman@trolltech.com> Stefan Beller <stefanbeller@gmail.com> <stefanbeller@googlemail.com> +Stefan Beller <stefanbeller@gmail.com> <sbeller@google.com> Stefan Naewe <stefan.naewe@gmail.com> <stefan.naewe@atlas-elektronik.com> Stefan Naewe <stefan.naewe@gmail.com> <stefan.naewe@googlemail.com> Stefan Sperling <stsp@elego.de> <stsp@stsp.name> diff --git a/Documentation/Makefile b/Documentation/Makefile index cea0e7ae3d..2f6b6aabd7 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. @@ -97,6 +99,13 @@ man7dir = $(mandir)/man7 ASCIIDOC = asciidoc ASCIIDOC_EXTRA = +ASCIIDOC_HTML = xhtml11 +ASCIIDOC_DOCBOOK = docbook +ASCIIDOC_CONF = -f asciidoc.conf +ASCIIDOC_COMMON = $(ASCIIDOC) $(ASCIIDOC_EXTRA) $(ASCIIDOC_CONF) \ + -agit-version=$(GIT_VERSION) +TXT_TO_HTML = $(ASCIIDOC_COMMON) -b $(ASCIIDOC_HTML) +TXT_TO_XML = $(ASCIIDOC_COMMON) -b $(ASCIIDOC_DOCBOOK) MANPAGE_XSL = manpage-normal.xsl XMLTO = xmlto XMLTO_EXTRA = @@ -304,14 +313,12 @@ clean: $(MAN_HTML): %.html : %.txt asciidoc.conf $(QUIET_ASCIIDOC)$(RM) $@+ $@ && \ - $(ASCIIDOC) -b xhtml11 -d manpage -f asciidoc.conf \ - $(ASCIIDOC_EXTRA) -agit_version=$(GIT_VERSION) -o $@+ $< && \ + $(TXT_TO_HTML) -d manpage -o $@+ $< && \ mv $@+ $@ $(OBSOLETE_HTML): %.html : %.txto asciidoc.conf $(QUIET_ASCIIDOC)$(RM) $@+ $@ && \ - $(ASCIIDOC) -b xhtml11 -f asciidoc.conf \ - $(ASCIIDOC_EXTRA) -agit_version=$(GIT_VERSION) -o $@+ $< && \ + $(TXT_TO_HTML) -o $@+ $< && \ mv $@+ $@ manpage-base-url.xsl: manpage-base-url.xsl.in @@ -323,13 +330,12 @@ manpage-base-url.xsl: manpage-base-url.xsl.in %.xml : %.txt asciidoc.conf $(QUIET_ASCIIDOC)$(RM) $@+ $@ && \ - $(ASCIIDOC) -b docbook -d manpage -f asciidoc.conf \ - $(ASCIIDOC_EXTRA) -agit_version=$(GIT_VERSION) -o $@+ $< && \ + $(TXT_TO_XML) -d manpage -o $@+ $< && \ mv $@+ $@ user-manual.xml: user-manual.txt user-manual.conf $(QUIET_ASCIIDOC)$(RM) $@+ $@ && \ - $(ASCIIDOC) $(ASCIIDOC_EXTRA) -b docbook -d article -o $@+ $< && \ + $(TXT_TO_XML) -d article -o $@+ $< && \ mv $@+ $@ technical/api-index.txt: technical/api-index-skel.txt \ @@ -338,8 +344,7 @@ technical/api-index.txt: technical/api-index-skel.txt \ technical/%.html: ASCIIDOC_EXTRA += -a git-relative-html-prefix=../ $(patsubst %,%.html,$(API_DOCS) technical/api-index $(TECH_DOCS)): %.html : %.txt asciidoc.conf - $(QUIET_ASCIIDOC)$(ASCIIDOC) -b xhtml11 -f asciidoc.conf \ - $(ASCIIDOC_EXTRA) -agit_version=$(GIT_VERSION) $*.txt + $(QUIET_ASCIIDOC)$(TXT_TO_HTML) $*.txt XSLT = docbook.xsl XSLTOPTS = --xinclude --stringparam html.stylesheet docbook-xsl.css @@ -386,14 +391,15 @@ howto-index.txt: howto-index.sh $(wildcard howto/*.txt) mv $@+ $@ $(patsubst %,%.html,$(ARTICLES)) : %.html : %.txt - $(QUIET_ASCIIDOC)$(ASCIIDOC) $(ASCIIDOC_EXTRA) -b xhtml11 $*.txt + $(QUIET_ASCIIDOC)$(TXT_TO_HTML) $*.txt WEBDOC_DEST = /pub/software/scm/git/docs howto/%.html: ASCIIDOC_EXTRA += -a git-relative-html-prefix=../ $(patsubst %.txt,%.html,$(wildcard howto/*.txt)): %.html : %.txt $(QUIET_ASCIIDOC)$(RM) $@+ $@ && \ - sed -e '1,/^$$/d' $< | $(ASCIIDOC) $(ASCIIDOC_EXTRA) -b xhtml11 - >$@+ && \ + sed -e '1,/^$$/d' $< | \ + $(TXT_TO_HTML) - >$@+ && \ mv $@+ $@ install-webdoc : html diff --git a/Documentation/RelNotes/2.2.0.txt b/Documentation/RelNotes/2.2.0.txt index 5f0c421365..b8017a6140 100644 --- a/Documentation/RelNotes/2.2.0.txt +++ b/Documentation/RelNotes/2.2.0.txt @@ -9,6 +9,8 @@ Ports * Building on older MacOS X systems automatically sets the necessary NO_APPLE_COMMON_CRYPTO build-time option. + * The support to build with NO_PTHREADS has been resurrected. + UI, Workflows & Features @@ -42,6 +44,15 @@ UI, Workflows & Features forgot to remove higher stage entries, or if it wanted to unresolve and forgot to remove the stage#0 entry). + * The temporary files "git mergetool" uses are named to avoid too + many dots in them (e.g. a temporary file for "hello.c" used to be + named e.g. "hello.BASE.4321.c" but now uses underscore instead, + e.g. "hello_BASE_4321.c"). + + * The temporary files "git mergetools" uses can be placed in a newly + creted temporary directory, instead of the current directory, by + setting the mergetool.writeToTemp configuration variable. + * The "pre-receive" and "post-receive" hooks are no longer required to consume their input fully (not following this requirement used to result in intermittent errors in "git push"). @@ -58,12 +69,22 @@ 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 - to make it more transactional, with the eventual goal to allow - all-or-none atomic updates and migrating the storage to something - other than the traditional filesystem based one (e.g. databases). + * The API to manipulate the "refs" has been restructured to make it + more transactional, with the eventual goal to allow all-or-none + atomic updates and migrating the storage to something other than + the traditional filesystem based one (e.g. databases). + + * The lockfile API and its users have been cleaned up. * We no longer attempt to keep track of individual dependencies to the header files in the build procedure, relying on automated @@ -122,6 +143,14 @@ 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. + + * The "run-command" API learned to manage the argv and environment + array for child process, alleviating the need for the callers to + allocate and deallocate them. + + Also contains various documentation updates and code clean-ups. @@ -136,11 +165,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). @@ -164,6 +188,11 @@ notes for details). * "git checkout -m" did not switch to another branch while carrying the local changes forward when a path was deleted from the index. + * "git daemon" (with NO_IPV6 build configuration) used to incorrectly + use the hostname even when gethostbyname() reported that the given + hostname is not found. + (merge 107efbe rs/daemon-fixes later to maint). + * With sufficiently long refnames, "git fast-import" could have overflown an on-stack buffer. @@ -215,3 +244,31 @@ notes for details). * "rev-parse --verify --quiet $name" is meant to quietly exit with a non-zero status when $name is not a valid object name, but still gave error messages in some cases. + + * A handful of C source files have been updated to include + "git-compat-util.h" as the first thing, to conform better to our + coding guidelines. + (merge 1c4b660 da/include-compat-util-first-in-c later to maint). + + * t7004 test, which tried to run Git with small stack space, has been + updated to give a bit larger stack to avoid false breakage on some + platforms. + (merge b9a1907 sk/tag-contains-wo-recursion later to maint). + + * 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). + + * Newer versions of 'meld' breaks the auto-detection we use to see if + they are new enough to support the `--output` option. + (merge b12d045 da/mergetool-meld later to maint). + + * "git pack-objects" forgot to disable the codepath to generate + object recheability bitmap when it needs to split the resulting + pack. + (merge 2113471 jk/pack-objects-no-bitmap-when-splitting later to maint). diff --git a/Documentation/config.txt b/Documentation/config.txt index 04a1e2f37e..8b49813d80 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -204,13 +204,26 @@ advice.*:: -- core.fileMode:: - If false, the executable bit differences between the index and - the working tree are ignored; useful on broken filesystems like FAT. - See linkgit:git-update-index[1]. + Tells Git if the executable bit of files in the working tree + is to be honored. + -The default is true, except linkgit:git-clone[1] or linkgit:git-init[1] -will probe and set core.fileMode false if appropriate when the -repository is created. +Some filesystems lose the executable bit when a file that is +marked as executable is checked out, or checks out an +non-executable file with executable bit on. +linkgit:git-clone[1] or linkgit:git-init[1] probe the filesystem +to see if it handles the executable bit correctly +and this variable is automatically set as necessary. ++ +A repository, however, may be on a filesystem that handles +the filemode correctly, and this variable is set to 'true' +when created, but later may be made accessible from another +environment that loses the filemode (e.g. exporting ext4 via +CIFS mount, visiting a Cygwin created repository with +Git for Windows or Eclipse). +In such a case it may be necessary to set this variable to 'false'. +See linkgit:git-update-index[1]. ++ +The default is true (when core.filemode is not specified in the config file). core.ignorecase:: If true, this option enables various workarounds to enable @@ -1755,6 +1768,15 @@ mergetool.<tool>.trustExitCode:: if the file has been updated, otherwise the user is prompted to indicate the success of the merge. +mergetool.meld.hasOutput:: + Older versions of `meld` do not support the `--output` option. + Git will attempt to detect whether `meld` supports `--output` + by inspecting the output of `meld --help`. Configuring + `mergetool.meld.hasOutput` will make Git skip these checks and + use the configured value instead. Setting `mergetool.meld.hasOutput` + to `true` tells Git to unconditionally use the `--output` option, + and `false` avoids using `--output`. + mergetool.keepBackup:: After performing a merge, the original file with conflict markers can be saved as a file with a `.orig` extension. If this variable @@ -1768,6 +1790,12 @@ mergetool.keepTemporaries:: preserved, otherwise they will be removed after the tool has exited. Defaults to `false`. +mergetool.writeToTemp:: + Git writes temporary 'BASE', 'LOCAL', and 'REMOTE' versions of + conflicting files in the worktree by default. Git will attempt + to use a temporary directory for these files when set `true`. + Defaults to `false`. + mergetool.prompt:: Prompt before each invocation of the merge resolution program. diff --git a/Documentation/everyday.txto b/Documentation/everyday.txto new file mode 100644 index 0000000000..c5047d8f9b --- /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 7d991d919c..c7c0d21429 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 0000000000..81fac3d26d --- /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 6738055bd3..9fed59a317 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 b17283ab7a..21b3f29c3b 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 a356196586..d64388cb8e 100644 --- a/Documentation/git-quiltimport.txt +++ b/Documentation/git-quiltimport.txt @@ -1,5 +1,5 @@ git-quiltimport(1) -================ +================== NAME ---- diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index 4138554912..924827dc2e 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -21,15 +21,17 @@ If <branch> is specified, 'git rebase' will perform an automatic it remains on the current branch. If <upstream> is not specified, the upstream configured in -branch.<name>.remote and branch.<name>.merge options will be used; see -linkgit:git-config[1] for details. If you are currently not on any -branch or if the current branch does not have a configured upstream, -the rebase will abort. +branch.<name>.remote and branch.<name>.merge options will be used (see +linkgit:git-config[1] for details) and the `--fork-point` option is +assumed. If you are currently not on any branch or if the current +branch does not have a configured upstream, the rebase will abort. All changes made by commits in the current branch but that are not in <upstream> are saved to a temporary area. This is the same set -of commits that would be shown by `git log <upstream>..HEAD` (or -`git log HEAD`, if --root is specified). +of commits that would be shown by `git log <upstream>..HEAD`; or by +`git log 'fork_point'..HEAD`, if `--fork-point` is active (see the +description on `--fork-point` below); or by `git log HEAD`, if the +`--root` option is specified. The current branch is reset to <upstream>, or <newbase> if the --onto option was supplied. This has the exact same effect as @@ -327,13 +329,18 @@ link:howto/revert-a-faulty-merge.html[revert-a-faulty-merge How-To] for details) --fork-point:: --no-fork-point:: - Use 'git merge-base --fork-point' to find a better common ancestor - between `upstream` and `branch` when calculating which commits have - have been introduced by `branch` (see linkgit:git-merge-base[1]). + Use reflog to find a better common ancestor between <upstream> + and <branch> when calculating which commits have been + introduced by <branch>. + -If no non-option arguments are given on the command line, then the default is -`--fork-point @{u}` otherwise the `upstream` argument is interpreted literally -unless the `--fork-point` option is specified. +When --fork-point is active, 'fork_point' will be used instead of +<upstream> to calculate the set of commits to rebase, where +'fork_point' is the result of `git merge-base --fork-point <upstream> +<branch>` command (see linkgit:git-merge-base[1]). If 'fork_point' +ends up being empty, the <upstream> will be used as a fallback. ++ +If either <upstream> or --root is given on the command line, then the +default is `--no-fork-point`, otherwise the default is `--fork-point`. --ignore-whitespace:: --whitespace=<option>:: diff --git a/Documentation/git-stage.txt b/Documentation/git-stage.txt index ba3fe0d7f5..25bcda936d 100644 --- a/Documentation/git-stage.txt +++ b/Documentation/git-stage.txt @@ -1,5 +1,5 @@ git-stage(1) -============== +============ NAME ---- diff --git a/Documentation/git-status.txt b/Documentation/git-status.txt index def635f578..4d8d530d35 100644 --- a/Documentation/git-status.txt +++ b/Documentation/git-status.txt @@ -116,7 +116,7 @@ In the short-format, the status of each path is shown as where `PATH1` is the path in the `HEAD`, and the " `-> PATH2`" part is shown only when `PATH1` corresponds to a different path in the -index/worktree (i.e. the file is renamed). The 'XY' is a two-letter +index/worktree (i.e. the file is renamed). The `XY` is a two-letter status code. The fields (including the `->`) are separated from each other by a @@ -125,7 +125,7 @@ characters, that field will be quoted in the manner of a C string literal: surrounded by ASCII double quote (34) characters, and with interior special characters backslash-escaped. -For paths with merge conflicts, `X` and 'Y' show the modification +For paths with merge conflicts, `X` and `Y` show the modification states of each side of the merge. For paths that do not have merge conflicts, `X` shows the status of the index, and `Y` shows the status of the work tree. For untracked paths, `XY` are `??`. Other status diff --git a/Documentation/git.txt b/Documentation/git.txt index c6175d45e4..9e0a42ce56 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 d2d7c213dd..8475c07932 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 5f4e89005c..b06e852a85 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 b2548ef4e6..7be6e64846 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 e52de7dbb4..212e254adc 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 3109ea8aad..f6fbf814fb 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 8262196318..af9f709ccf 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 diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt index eecc39dec9..dcf7429a47 100644 --- a/Documentation/pretty-formats.txt +++ b/Documentation/pretty-formats.txt @@ -95,7 +95,7 @@ would show something like this: The author of fe6e0ee was Junio C Hamano, 23 hours ago The title was >>t4119: test autocomputing -p<n> for traditional diff input.<< --------- +------- + The placeholders are: diff --git a/Documentation/technical/api-lockfile.txt b/Documentation/technical/api-lockfile.txt index dd894043ae..93b5f23e4c 100644 --- a/Documentation/technical/api-lockfile.txt +++ b/Documentation/technical/api-lockfile.txt @@ -3,20 +3,132 @@ lockfile API The lockfile API serves two purposes: -* Mutual exclusion. When we write out a new index file, first - we create a new file `$GIT_DIR/index.lock`, write the new - contents into it, and rename it to the final destination - `$GIT_DIR/index`. We try to create the `$GIT_DIR/index.lock` - file with O_EXCL so that we can notice and fail when somebody - else is already trying to update the index file. - -* Automatic cruft removal. After we create the "lock" file, we - may decide to `die()`, and we would want to make sure that we - remove the file that has not been committed to its final - destination. This is done by remembering the lockfiles we - created in a linked list and cleaning them up from an - `atexit(3)` handler. Outstanding lockfiles are also removed - when the program dies on a signal. +* Mutual exclusion and atomic file updates. When we want to change a + file, we create a lockfile `<filename>.lock`, write the new file + contents into it, and then rename the lockfile to its final + destination `<filename>`. We create the `<filename>.lock` file with + `O_CREAT|O_EXCL` so that we can notice and fail if somebody else has + already locked the file, then atomically rename the lockfile to its + final destination to commit the changes and unlock the file. + +* Automatic cruft removal. If the program exits after we lock a file + but before the changes have been committed, we want to make sure + that we remove the lockfile. This is done by remembering the + lockfiles we have created in a linked list and setting up an + `atexit(3)` handler and a signal handler that clean up the + lockfiles. This mechanism ensures that outstanding lockfiles are + cleaned up if the program exits (including when `die()` is called) + or if the program dies on a signal. + +Please note that lockfiles only block other writers. Readers do not +block, but they are guaranteed to see either the old contents of the +file or the new contents of the file (assuming that the filesystem +implements `rename(2)` atomically). + + +Calling sequence +---------------- + +The caller: + +* Allocates a `struct lock_file` either as a static variable or on the + heap, initialized to zeros. Once you use the structure to call the + `hold_lock_file_*` family of functions, it belongs to the lockfile + subsystem and its storage must remain valid throughout the life of + the program (i.e. you cannot use an on-stack variable to hold this + structure). + +* Attempts to create a lockfile by passing that variable and the path + of the final destination (e.g. `$GIT_DIR/index`) to + `hold_lock_file_for_update` or `hold_lock_file_for_append`. + +* Writes new content for the destination file by either: + + * writing to the file descriptor returned by the `hold_lock_file_*` + functions (also available via `lock->fd`). + + * calling `fdopen_lock_file` to get a `FILE` pointer for the open + file and writing to the file using stdio. + +When finished writing, the caller can: + +* Close the file descriptor and rename the lockfile to its final + destination by calling `commit_lock_file` or `commit_lock_file_to`. + +* Close the file descriptor and remove the lockfile by calling + `rollback_lock_file`. + +* Close the file descriptor without removing or renaming the lockfile + by calling `close_lock_file`, and later call `commit_lock_file`, + `commit_lock_file_to`, `rollback_lock_file`, or `reopen_lock_file`. + +Even after the lockfile is committed or rolled back, the `lock_file` +object must not be freed or altered by the caller. However, it may be +reused; just pass it to another call of `hold_lock_file_for_update` or +`hold_lock_file_for_append`. + +If the program exits before you have called one of `commit_lock_file`, +`commit_lock_file_to`, `rollback_lock_file`, or `close_lock_file`, an +`atexit(3)` handler will close and remove the lockfile, rolling back +any uncommitted changes. + +If you need to close the file descriptor you obtained from a +`hold_lock_file_*` function yourself, do so by calling +`close_lock_file`. You should never call `close(2)` or `fclose(3)` +yourself! Otherwise the `struct lock_file` structure would still think +that the file descriptor needs to be closed, and a commit or rollback +would result in duplicate calls to `close(2)`. Worse yet, if you close +and then later open another file descriptor for a completely different +purpose, then a commit or rollback might close that unrelated file +descriptor. + + +Error handling +-------------- + +The `hold_lock_file_*` functions return a file descriptor on success +or -1 on failure (unless `LOCK_DIE_ON_ERROR` is used; see below). On +errors, `errno` describes the reason for failure. Errors can be +reported by passing `errno` to one of the following helper functions: + +unable_to_lock_message:: + + Append an appropriate error message to a `strbuf`. + +unable_to_lock_error:: + + Emit an appropriate error message using `error()`. + +unable_to_lock_die:: + + Emit an appropriate error message and `die()`. + +Similarly, `commit_lock_file`, `commit_lock_file_to`, and +`close_lock_file` return 0 on success. On failure they set `errno` +appropriately, do their best to roll back the lockfile, and return -1. + + +Flags +----- + +The following flags can be passed to `hold_lock_file_for_update` or +`hold_lock_file_for_append`: + +LOCK_NO_DEREF:: + + Usually symbolic links in the destination path are resolved + and the lockfile is created by adding ".lock" to the resolved + path. If `LOCK_NO_DEREF` is set, then the lockfile is created + by adding ".lock" to the path argument itself. This option is + used, for example, when locking a symbolic reference, which + for backwards-compatibility reasons can be a symbolic link + containing the name of the referred-to-reference. + +LOCK_DIE_ON_ERROR:: + + If a lock is already taken for the file, `die()` with an error + message. If this option is not specified, trying to lock a + file that is already locked returns -1 to the caller. The functions @@ -24,51 +136,85 @@ The functions hold_lock_file_for_update:: - Take a pointer to `struct lock_file`, the filename of - the final destination (e.g. `$GIT_DIR/index`) and a flag - `die_on_error`. Attempt to create a lockfile for the - destination and return the file descriptor for writing - to the file. If `die_on_error` flag is true, it dies if - a lock is already taken for the file; otherwise it - returns a negative integer to the caller on failure. + Take a pointer to `struct lock_file`, the path of the file to + be locked (e.g. `$GIT_DIR/index`) and a flags argument (see + above). Attempt to create a lockfile for the destination and + return the file descriptor for writing to the file. + +hold_lock_file_for_append:: + + Like `hold_lock_file_for_update`, but before returning copy + the existing contents of the file (if any) to the lockfile and + position its write pointer at the end of the file. + +fdopen_lock_file:: + + Associate a stdio stream with the lockfile. Return NULL + (*without* rolling back the lockfile) on error. The stream is + closed automatically when `close_lock_file` is called or when + the file is committed or rolled back. + +get_locked_file_path:: + + Return the path of the file that is locked by the specified + lock_file object. The caller must free the memory. commit_lock_file:: - Take a pointer to the `struct lock_file` initialized - with an earlier call to `hold_lock_file_for_update()`, - close the file descriptor and rename the lockfile to its - final destination. Returns 0 upon success, a negative - value on failure to close(2) or rename(2). + Take a pointer to the `struct lock_file` initialized with an + earlier call to `hold_lock_file_for_update` or + `hold_lock_file_for_append`, close the file descriptor, and + rename the lockfile to its final destination. Return 0 upon + success. On failure, roll back the lock file and return -1, + with `errno` set to the value from the failing call to + `close(2)` or `rename(2)`. It is a bug to call + `commit_lock_file` for a `lock_file` object that is not + currently locked. + +commit_lock_file_to:: + + Like `commit_lock_file()`, except that it takes an explicit + `path` argument to which the lockfile should be renamed. The + `path` must be on the same filesystem as the lock file. rollback_lock_file:: - Take a pointer to the `struct lock_file` initialized - with an earlier call to `hold_lock_file_for_update()`, - close the file descriptor and remove the lockfile. + Take a pointer to the `struct lock_file` initialized with an + earlier call to `hold_lock_file_for_update` or + `hold_lock_file_for_append`, close the file descriptor and + remove the lockfile. It is a NOOP to call + `rollback_lock_file()` for a `lock_file` object that has + already been committed or rolled back. close_lock_file:: - Take a pointer to the `struct lock_file` initialized - with an earlier call to `hold_lock_file_for_update()`, - and close the file descriptor. Returns 0 upon success, - a negative value on failure to close(2). - -Because the structure is used in an `atexit(3)` handler, its -storage has to stay throughout the life of the program. It -cannot be an auto variable allocated on the stack. - -Call `commit_lock_file()` or `rollback_lock_file()` when you are -done writing to the file descriptor. If you do not call either -and simply `exit(3)` from the program, an `atexit(3)` handler -will close and remove the lockfile. - -If you need to close the file descriptor you obtained from -`hold_lock_file_for_update` function yourself, do so by calling -`close_lock_file()`. You should never call `close(2)` yourself! -Otherwise the `struct -lock_file` structure still remembers that the file descriptor -needs to be closed, and a later call to `commit_lock_file()` or -`rollback_lock_file()` will result in duplicate calls to -`close(2)`. Worse yet, if you `close(2)`, open another file -descriptor for completely different purpose, and then call -`commit_lock_file()` or `rollback_lock_file()`, they may close -that unrelated file descriptor. + + Take a pointer to the `struct lock_file` initialized with an + earlier call to `hold_lock_file_for_update` or + `hold_lock_file_for_append`. Close the file descriptor (and + the file pointer if it has been opened using + `fdopen_lock_file`). Return 0 upon success. On failure to + `close(2)`, return a negative value and roll back the lock + file. Usually `commit_lock_file`, `commit_lock_file_to`, or + `rollback_lock_file` should eventually be called if + `close_lock_file` succeeds. + +reopen_lock_file:: + + Re-open a lockfile that has been closed (using + `close_lock_file`) but not yet committed or rolled back. This + can be used to implement a sequence of operations like the + following: + + * Lock file. + + * Write new contents to lockfile, then `close_lock_file` to + cause the contents to be written to disk. + + * Pass the name of the lockfile to another program to allow it + (and nobody else) to inspect the contents you wrote, while + still holding the lock yourself. + + * `reopen_lock_file` to reopen the lockfile. Make further + updates to the contents. + + * `commit_lock_file` to make the final version permanent. diff --git a/Documentation/technical/api-run-command.txt b/Documentation/technical/api-run-command.txt index 842b8389eb..3f12fcdd4c 100644 --- a/Documentation/technical/api-run-command.txt +++ b/Documentation/technical/api-run-command.txt @@ -169,6 +169,11 @@ string pointers (NULL terminated) in .env: . If the string does not contain '=', it names an environment variable that will be removed from the child process's environment. +If the .env member is NULL, `start_command` will point it at the +.env_array `argv_array` (so you may use one or the other, but not both). +The memory in .env_array will be cleaned up automatically during +`finish_command` (or during `start_command` when it is unsuccessful). + To specify a new initial working directory for the sub-process, specify it in the .dir member. @@ -568,6 +568,7 @@ TEST_PROGRAMS_NEED_X += test-revision-walking TEST_PROGRAMS_NEED_X += test-run-command TEST_PROGRAMS_NEED_X += test-scrap-cache-tree TEST_PROGRAMS_NEED_X += test-sha1 +TEST_PROGRAMS_NEED_X += test-sha1-array TEST_PROGRAMS_NEED_X += test-sigchain TEST_PROGRAMS_NEED_X += test-string-list TEST_PROGRAMS_NEED_X += test-subprocess @@ -763,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 @@ -827,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 df2f4c8a64..0d1e6bd754 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)); @@ -170,7 +170,7 @@ int validate_new_branchname(const char *name, struct strbuf *ref, const char *head; unsigned char sha1[20]; - head = resolve_ref_unsafe("HEAD", sha1, 0, NULL); + head = resolve_ref_unsafe("HEAD", 0, sha1, NULL); if (!is_bare_repository() && head && !strcmp(head, ref->buf)) die(_("Cannot force update the current branch.")); } @@ -285,8 +285,8 @@ void create_branch(const char *head, transaction = ref_transaction_begin(&err); if (!transaction || ref_transaction_update(transaction, ref.buf, sha1, - null_sha1, 0, !forcing, &err) || - ref_transaction_commit(transaction, msg, &err)) + null_sha1, 0, !forcing, msg, &err) || + ref_transaction_commit(transaction, &err)) die("%s", err.buf); ref_transaction_free(transaction); strbuf_release(&err); @@ -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/add.c b/builtin/add.c index 352b85e8db..ae6d3e262b 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -5,6 +5,7 @@ */ #include "cache.h" #include "builtin.h" +#include "lockfile.h" #include "dir.h" #include "pathspec.h" #include "exec_cmd.h" diff --git a/builtin/apply.c b/builtin/apply.c index 8714a88720..6696ea4c3f 100644 --- a/builtin/apply.c +++ b/builtin/apply.c @@ -7,6 +7,7 @@ * */ #include "cache.h" +#include "lockfile.h" #include "cache-tree.h" #include "quote.h" #include "blob.h" @@ -435,7 +436,7 @@ static unsigned long linelen(const char *buffer, unsigned long size) static int is_dev_null(const char *str) { - return !memcmp("/dev/null", str, 9) && isspace(str[9]); + return skip_prefix(str, "/dev/null", &str) && isspace(*str); } #define TERM_SPACE 1 diff --git a/builtin/blame.c b/builtin/blame.c index 3838be2b02..303e217ae9 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -2286,7 +2286,7 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt, commit->date = now; parent_tail = &commit->parents; - if (!resolve_ref_unsafe("HEAD", head_sha1, 1, NULL)) + if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, head_sha1, NULL)) die("no such ref: HEAD"); parent_tail = append_parent(parent_tail, head_sha1); diff --git a/builtin/branch.c b/builtin/branch.c index 9e4666f0c5..3b79c5087f 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -62,39 +62,40 @@ 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; } static int git_branch_config(const char *var, const char *value, void *cb) { + const char *slot_name; + if (starts_with(var, "column.")) return git_column_config(var, value, "branch", &colopts); if (!strcmp(var, "color.branch")) { branch_use_color = git_config_colorbool(var, value); return 0; } - if (starts_with(var, "color.branch.")) { - int slot = parse_branch_color_slot(var, 13); + if (skip_prefix(var, "color.branch.", &slot_name)) { + 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); } @@ -129,7 +130,8 @@ static int branch_merged(int kind, const char *name, branch->merge[0] && branch->merge[0]->dst && (reference_name = reference_name_to_free = - resolve_refdup(branch->merge[0]->dst, sha1, 1, NULL)) != NULL) + resolve_refdup(branch->merge[0]->dst, RESOLVE_REF_READING, + sha1, NULL)) != NULL) reference_rev = lookup_commit_reference(sha1); } if (!reference_rev) @@ -233,9 +235,12 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, free(name); name = mkpathdup(fmt, bname.buf); - target = resolve_ref_unsafe(name, sha1, 0, &flags); - if (!target || - (!(flags & REF_ISSYMREF) && is_null_sha1(sha1))) { + target = resolve_ref_unsafe(name, + RESOLVE_REF_READING + | RESOLVE_REF_NO_RECURSE + | RESOLVE_REF_ALLOW_BAD_NAME, + sha1, &flags); + if (!target) { error(remote_branch ? _("remote branch '%s' not found.") : _("branch '%s' not found."), bname.buf); @@ -243,7 +248,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, continue; } - if (!(flags & REF_ISSYMREF) && + if (!(flags & (REF_ISSYMREF|REF_ISBROKEN)) && check_branch_commit(bname.buf, name, sha1, head_rev, kinds, force)) { ret = 1; @@ -263,8 +268,8 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, ? _("Deleted remote branch %s (was %s).\n") : _("Deleted branch %s (was %s).\n"), bname.buf, - (flags & REF_ISSYMREF) - ? target + (flags & REF_ISBROKEN) ? "broken" + : (flags & REF_ISSYMREF) ? target : find_unique_abbrev(sha1, DEFAULT_ABBREV)); } delete_branch_config(bname.buf); @@ -297,7 +302,7 @@ static char *resolve_symref(const char *src, const char *prefix) int flag; const char *dst; - dst = resolve_ref_unsafe(src, sha1, 0, &flag); + dst = resolve_ref_unsafe(src, 0, sha1, &flag); if (!(dst && (flag & REF_ISSYMREF))) return NULL; if (prefix) @@ -335,20 +340,18 @@ static int append_ref(const char *refname, const unsigned char *sha1, int flags, static struct { int kind; const char *prefix; - int pfxlen; } ref_kind[] = { - { REF_LOCAL_BRANCH, "refs/heads/", 11 }, - { REF_REMOTE_BRANCH, "refs/remotes/", 13 }, + { REF_LOCAL_BRANCH, "refs/heads/" }, + { REF_REMOTE_BRANCH, "refs/remotes/" }, }; /* Detect kind */ for (i = 0; i < ARRAY_SIZE(ref_kind); i++) { prefix = ref_kind[i].prefix; - if (strncmp(refname, prefix, ref_kind[i].pfxlen)) - continue; - kind = ref_kind[i].kind; - refname += ref_kind[i].pfxlen; - break; + if (skip_prefix(refname, prefix, &refname)) { + kind = ref_kind[i].kind; + break; + } } if (ARRAY_SIZE(ref_kind) <= i) return 0; @@ -869,16 +872,13 @@ int cmd_branch(int argc, const char **argv, const char *prefix) track = git_branch_track; - head = resolve_refdup("HEAD", head_sha1, 0, NULL); + head = resolve_refdup("HEAD", 0, head_sha1, NULL); if (!head) die(_("Failed to resolve HEAD as a valid ref.")); - if (!strcmp(head, "HEAD")) { + if (!strcmp(head, "HEAD")) detached = 1; - } else { - if (!starts_with(head, "refs/heads/")) - die(_("HEAD not found below refs/heads!")); - head += 11; - } + else if (!skip_prefix(head, "refs/heads/", &head)) + die(_("HEAD not found below refs/heads!")); hashcpy(merge_filter_ref, head_sha1); diff --git a/builtin/cat-file.c b/builtin/cat-file.c index 707330499f..f8d81291b9 100644 --- a/builtin/cat-file.c +++ b/builtin/cat-file.c @@ -82,8 +82,9 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name) enum object_type type; unsigned long size; char *buffer = read_sha1_file(sha1, &type, &size); - if (memcmp(buffer, "object ", 7) || - get_sha1_hex(buffer + 7, blob_sha1)) + const char *target; + if (!skip_prefix(buffer, "object ", &target) || + get_sha1_hex(target, blob_sha1)) die("%s not a valid tag", sha1_to_hex(sha1)); free(buffer); } else diff --git a/builtin/checkout-index.c b/builtin/checkout-index.c index 05edd9e1df..383dccf93e 100644 --- a/builtin/checkout-index.c +++ b/builtin/checkout-index.c @@ -5,7 +5,7 @@ * */ #include "builtin.h" -#include "cache.h" +#include "lockfile.h" #include "quote.h" #include "cache-tree.h" #include "parse-options.h" diff --git a/builtin/checkout.c b/builtin/checkout.c index 8afdf2b5c4..5410dacea0 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -1,5 +1,5 @@ -#include "cache.h" #include "builtin.h" +#include "lockfile.h" #include "parse-options.h" #include "refs.h" #include "commit.h" @@ -355,7 +355,7 @@ static int checkout_paths(const struct checkout_opts *opts, if (write_locked_index(&the_index, lock_file, COMMIT_LOCK)) die(_("unable to write new index file")); - read_ref_full("HEAD", rev, 0, &flag); + read_ref_full("HEAD", 0, rev, &flag); head = lookup_commit_reference_gently(rev, 1); errs |= post_checkout_hook(head, head, 0); @@ -775,7 +775,7 @@ static int switch_branches(const struct checkout_opts *opts, unsigned char rev[20]; int flag, writeout_error = 0; memset(&old, 0, sizeof(old)); - old.path = path_to_free = resolve_refdup("HEAD", rev, 0, &flag); + old.path = path_to_free = resolve_refdup("HEAD", 0, rev, &flag); old.commit = lookup_commit_reference_gently(rev, 1); if (!(flag & REF_ISSYMREF)) old.path = NULL; @@ -1072,7 +1072,7 @@ static int checkout_branch(struct checkout_opts *opts, unsigned char rev[20]; int flag; - if (!read_ref_full("HEAD", rev, 0, &flag) && + if (!read_ref_full("HEAD", 0, rev, &flag) && (flag & REF_ISSYMREF) && is_null_sha1(rev)) return switch_unborn_to_new_branch(opts); } @@ -1150,10 +1150,8 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) const char *argv0 = argv[0]; if (!argc || !strcmp(argv0, "--")) die (_("--track needs a branch name")); - if (starts_with(argv0, "refs/")) - argv0 += 5; - if (starts_with(argv0, "remotes/")) - argv0 += 8; + skip_prefix(argv0, "refs/", &argv0); + skip_prefix(argv0, "remotes/", &argv0); argv0 = strchr(argv0, '/'); if (!argv0 || !argv0[1]) die (_("Missing branch name; try -b")); diff --git a/builtin/clean.c b/builtin/clean.c index 3beeea6ec0..77846762b5 100644 --- a/builtin/clean.c +++ b/builtin/clean.c @@ -100,6 +100,8 @@ static int parse_clean_color_slot(const char *var) static int git_clean_config(const char *var, const char *value, void *cb) { + const char *slot_name; + if (starts_with(var, "column.")) return git_column_config(var, value, "clean", &colopts); @@ -109,15 +111,13 @@ static int git_clean_config(const char *var, const char *value, void *cb) clean_use_color = git_config_colorbool(var, value); return 0; } - if (starts_with(var, "color.interactive.")) { - int slot = parse_clean_color_slot(var + - strlen("color.interactive.")); + if (skip_prefix(var, "color.interactive.", &slot_name)) { + int slot = parse_clean_color_slot(slot_name); if (slot < 0) 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/clone.c b/builtin/clone.c index 3927edfb6e..d5e7532105 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -9,6 +9,7 @@ */ #include "builtin.h" +#include "lockfile.h" #include "parse-options.h" #include "fetch-pack.h" #include "refs.h" @@ -390,7 +391,6 @@ static void clone_local(const char *src_repo, const char *dest_repo) static const char *junk_work_tree; static const char *junk_git_dir; -static pid_t junk_pid; static enum { JUNK_LEAVE_NONE, JUNK_LEAVE_REPO, @@ -417,8 +417,6 @@ static void remove_junk(void) break; } - if (getpid() != junk_pid) - return; if (junk_git_dir) { strbuf_addstr(&sb, junk_git_dir); remove_dir_recursively(&sb, 0); @@ -622,7 +620,7 @@ static int checkout(void) if (option_no_checkout) return 0; - head = resolve_refdup("HEAD", sha1, 1, NULL); + head = resolve_refdup("HEAD", RESOLVE_REF_READING, sha1, NULL); if (!head) { warning(_("remote HEAD refers to nonexistent ref, " "unable to checkout.\n")); @@ -759,8 +757,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix) struct refspec *refspec; const char *fetch_pattern; - junk_pid = getpid(); - packet_trace_identity("clone"); argc = parse_options(argc, argv, prefix, builtin_clone_options, builtin_clone_usage, 0); diff --git a/builtin/commit.c b/builtin/commit.c index b0fe7847d3..e108c53015 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -6,6 +6,7 @@ */ #include "cache.h" +#include "lockfile.h" #include "cache-tree.h" #include "color.h" #include "dir.h" @@ -315,8 +316,8 @@ static void refresh_cache_or_die(int refresh_flags) die_resolve_conflict("commit"); } -static char *prepare_index(int argc, const char **argv, const char *prefix, - const struct commit *current_head, int is_status) +static const char *prepare_index(int argc, const char **argv, const char *prefix, + const struct commit *current_head, int is_status) { struct string_list partial; struct pathspec pathspec; @@ -341,7 +342,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, die(_("unable to create temporary index")); old_index_env = getenv(INDEX_ENVIRONMENT); - setenv(INDEX_ENVIRONMENT, index_lock.filename, 1); + setenv(INDEX_ENVIRONMENT, index_lock.filename.buf, 1); if (interactive_add(argc, argv, prefix, patch_interactive) != 0) die(_("interactive add failed")); @@ -352,7 +353,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, unsetenv(INDEX_ENVIRONMENT); discard_cache(); - read_cache_from(index_lock.filename); + read_cache_from(index_lock.filename.buf); if (update_main_cache_tree(WRITE_TREE_SILENT) == 0) { if (reopen_lock_file(&index_lock) < 0) die(_("unable to write index file")); @@ -362,7 +363,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, warning(_("Failed to update main cache tree")); commit_style = COMMIT_NORMAL; - return index_lock.filename; + return index_lock.filename.buf; } /* @@ -385,7 +386,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, if (write_locked_index(&the_index, &index_lock, CLOSE_LOCK)) die(_("unable to write new_index file")); commit_style = COMMIT_NORMAL; - return index_lock.filename; + return index_lock.filename.buf; } /* @@ -472,9 +473,9 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, die(_("unable to write temporary index file")); discard_cache(); - read_cache_from(false_lock.filename); + read_cache_from(false_lock.filename.buf); - return false_lock.filename; + return false_lock.filename.buf; } static int run_status(FILE *fp, const char *index_file, const char *prefix, int nowarn, @@ -1271,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; } @@ -1294,6 +1294,7 @@ static int parse_status_slot(const char *var, int offset) static int git_status_config(const char *k, const char *v, void *cb) { struct wt_status *s = cb; + const char *slot_name; if (starts_with(k, "column.")) return git_column_config(k, v, "status", &s->colopts); @@ -1323,14 +1324,14 @@ static int git_status_config(const char *k, const char *v, void *cb) s->display_comment_prefix = git_config_bool(k, v); return 0; } - if (starts_with(k, "status.color.") || starts_with(k, "color.status.")) { - int slot = parse_status_slot(k, 13); + if (skip_prefix(k, "status.color.", &slot_name) || + skip_prefix(k, "color.status.", &slot_name)) { + 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); @@ -1512,14 +1513,12 @@ static void print_summary(const char *prefix, const unsigned char *sha1, rev.diffopt.break_opt = 0; diff_setup_done(&rev.diffopt); - head = resolve_ref_unsafe("HEAD", junk_sha1, 0, NULL); - printf("[%s%s ", - starts_with(head, "refs/heads/") ? - head + 11 : - !strcmp(head, "HEAD") ? - _("detached HEAD") : - head, - initial_commit ? _(" (root-commit)") : ""); + head = resolve_ref_unsafe("HEAD", 0, junk_sha1, NULL); + if (!strcmp(head, "HEAD")) + head = _("detached HEAD"); + else + skip_prefix(head, "refs/heads/", &head); + printf("[%s%s ", head, initial_commit ? _(" (root-commit)") : ""); if (!log_tree_commit(&rev, commit)) { rev.always_show_header = 1; @@ -1810,8 +1809,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix) ref_transaction_update(transaction, "HEAD", sha1, current_head ? current_head->object.sha1 : NULL, - 0, !!current_head, &err) || - ref_transaction_commit(transaction, sb.buf, &err)) { + 0, !!current_head, sb.buf, &err) || + ref_transaction_commit(transaction, &err)) { rollback_index_files(); die("%s", err.buf); } diff --git a/builtin/config.c b/builtin/config.c index 37305e93e9..8cc2604069 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/describe.c b/builtin/describe.c index ee6a3b998f..9103193b4f 100644 --- a/builtin/describe.c +++ b/builtin/describe.c @@ -1,4 +1,5 @@ #include "cache.h" +#include "lockfile.h" #include "commit.h" #include "tag.h" #include "refs.h" diff --git a/builtin/diff.c b/builtin/diff.c index 0f247d2400..4326fa56bf 100644 --- a/builtin/diff.c +++ b/builtin/diff.c @@ -4,6 +4,7 @@ * Copyright (c) 2006 Junio C Hamano */ #include "cache.h" +#include "lockfile.h" #include "color.h" #include "commit.h" #include "blob.h" diff --git a/builtin/fetch.c b/builtin/fetch.c index 159fb7e916..6ffd02388b 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -404,23 +404,37 @@ static int s_update_ref(const char *action, { char msg[1024]; char *rla = getenv("GIT_REFLOG_ACTION"); - static struct ref_lock *lock; + struct ref_transaction *transaction; + struct strbuf err = STRBUF_INIT; + int ret, df_conflict = 0; if (dry_run) return 0; if (!rla) rla = default_rla.buf; snprintf(msg, sizeof(msg), "%s: %s", rla, action); - lock = lock_any_ref_for_update(ref->name, - check_old ? ref->old_sha1 : NULL, - 0, NULL); - if (!lock) - return errno == ENOTDIR ? STORE_REF_ERROR_DF_CONFLICT : - STORE_REF_ERROR_OTHER; - if (write_ref_sha1(lock, ref->new_sha1, msg) < 0) - return errno == ENOTDIR ? STORE_REF_ERROR_DF_CONFLICT : - STORE_REF_ERROR_OTHER; + + transaction = ref_transaction_begin(&err); + if (!transaction || + ref_transaction_update(transaction, ref->name, ref->new_sha1, + ref->old_sha1, 0, check_old, msg, &err)) + goto fail; + + ret = ref_transaction_commit(transaction, &err); + if (ret) { + df_conflict = (ret == TRANSACTION_NAME_CONFLICT); + goto fail; + } + + ref_transaction_free(transaction); + strbuf_release(&err); return 0; +fail: + ref_transaction_free(transaction); + error("%s", err.buf); + strbuf_release(&err); + return df_conflict ? STORE_REF_ERROR_DF_CONFLICT + : STORE_REF_ERROR_OTHER; } #define REFCOL_WIDTH 10 diff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c index 79df05ef52..37177c6c29 100644 --- a/builtin/fmt-merge-msg.c +++ b/builtin/fmt-merge-msg.c @@ -602,7 +602,7 @@ int fmt_merge_msg(struct strbuf *in, struct strbuf *out, /* get current branch */ current_branch = current_branch_to_free = - resolve_refdup("HEAD", head_sha1, 1, NULL); + resolve_refdup("HEAD", RESOLVE_REF_READING, head_sha1, NULL); if (!current_branch) die("No current branch"); if (starts_with(current_branch, "refs/heads/")) diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c index fda0f04712..603a90e29b 100644 --- a/builtin/for-each-ref.c +++ b/builtin/for-each-ref.c @@ -635,7 +635,8 @@ static void populate_value(struct refinfo *ref) if (need_symref && (ref->flag & REF_ISSYMREF) && !ref->symref) { unsigned char unused1[20]; - ref->symref = resolve_refdup(ref->refname, unused1, 1, NULL); + ref->symref = resolve_refdup(ref->refname, RESOLVE_REF_READING, + unused1, NULL); if (!ref->symref) ref->symref = ""; } @@ -671,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")) { @@ -693,7 +695,8 @@ static void populate_value(struct refinfo *ref) const char *head; unsigned char sha1[20]; - head = resolve_ref_unsafe("HEAD", sha1, 1, NULL); + head = resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, + sha1, NULL); if (!strcmp(ref->refname, head)) v->s = "*"; else @@ -837,6 +840,11 @@ static int grab_single_ref(const char *refname, const unsigned char *sha1, int f struct refinfo *ref; int cnt; + if (flag & REF_BAD_NAME) { + warning("ignoring ref with broken name %s", refname); + return 0; + } + if (*cb->grab_pattern) { const char **pattern; int namelen = strlen(refname); @@ -1004,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/fsck.c b/builtin/fsck.c index e9ba576c1f..a27515aeaa 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -556,7 +556,7 @@ static int fsck_head_link(void) if (verbose) fprintf(stderr, "Checking HEAD link\n"); - head_points_at = resolve_ref_unsafe("HEAD", head_sha1, 0, &flag); + head_points_at = resolve_ref_unsafe("HEAD", 0, head_sha1, &flag); if (!head_points_at) return error("Invalid HEAD"); if (!strcmp(head_points_at, "HEAD")) diff --git a/builtin/gc.c b/builtin/gc.c index ced1456e1e..005adbebea 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -11,7 +11,7 @@ */ #include "builtin.h" -#include "cache.h" +#include "lockfile.h" #include "parse-options.h" #include "run-command.h" #include "sigchain.h" diff --git a/builtin/get-tar-commit-id.c b/builtin/get-tar-commit-id.c index aa72596083..6f4147ad02 100644 --- a/builtin/get-tar-commit-id.c +++ b/builtin/get-tar-commit-id.c @@ -19,6 +19,7 @@ int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix) char buffer[HEADERSIZE]; struct ustar_header *header = (struct ustar_header *)buffer; char *content = buffer + RECORDSIZE; + const char *comment; ssize_t n; if (argc != 1) @@ -29,10 +30,10 @@ int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix) die("git get-tar-commit-id: read error"); if (header->typeflag[0] != 'g') return 1; - if (memcmp(content, "52 comment=", 11)) + if (!skip_prefix(content, "52 comment=", &comment)) return 1; - n = write_in_full(1, content + 11, 41); + n = write_in_full(1, comment, 41); if (n < 41) die_errno("git get-tar-commit-id: write error"); diff --git a/builtin/help.c b/builtin/help.c index 8343b4027d..b3c818ee01 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/index-pack.c b/builtin/index-pack.c index 792c66ca59..a369f55353 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -185,6 +185,9 @@ static void cleanup_thread(void) #define deepest_delta_lock() #define deepest_delta_unlock() +#define type_cas_lock() +#define type_cas_unlock() + #endif diff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c new file mode 100644 index 0000000000..46838d24a9 --- /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 2fb34c7de9..734aab3a73 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -368,6 +368,8 @@ static int cmd_log_walk(struct rev_info *rev) static int git_log_config(const char *var, const char *value, void *cb) { + const char *slot_name; + if (!strcmp(var, "format.pretty")) return git_config_string(&fmt_pretty, var, value); if (!strcmp(var, "format.subjectprefix")) @@ -388,8 +390,8 @@ static int git_log_config(const char *var, const char *value, void *cb) default_show_root = git_config_bool(var, value); return 0; } - if (starts_with(var, "color.decorate.")) - return parse_decorate_color_config(var, 15, value); + if (skip_prefix(var, "color.decorate.", &slot_name)) + return parse_decorate_color_config(var, slot_name, value); if (!strcmp(var, "log.mailmap")) { use_mailmap_config = git_config_bool(var, value); return 0; @@ -1398,7 +1400,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) if (check_head) { unsigned char sha1[20]; const char *ref, *v; - ref = resolve_ref_unsafe("HEAD", sha1, 1, NULL); + ref = resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, + sha1, NULL); if (ref && skip_prefix(ref, "refs/heads/", &v)) branch_name = xstrdup(v); else diff --git a/builtin/mailsplit.c b/builtin/mailsplit.c index 763cda098c..8e02ea109a 100644 --- a/builtin/mailsplit.c +++ b/builtin/mailsplit.c @@ -59,7 +59,6 @@ static int split_one(FILE *mbox, const char *name, int allow_bare) int is_bare = !is_from_line(buf.buf, buf.len); if (is_bare && !allow_bare) { - unlink(name); fprintf(stderr, "corrupt mailbox\n"); exit(1); } diff --git a/builtin/merge.c b/builtin/merge.c index dff043dac3..bebbe5b308 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -9,6 +9,7 @@ #include "cache.h" #include "parse-options.h" #include "builtin.h" +#include "lockfile.h" #include "run-command.h" #include "diff.h" #include "refs.h" @@ -656,19 +657,18 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, struct commit_list *remoteheads, struct commit *head, const char *head_arg) { - struct lock_file *lock = xcalloc(1, sizeof(struct lock_file)); + static struct lock_file lock; - hold_locked_index(lock, 1); + hold_locked_index(&lock, 1); refresh_cache(REFRESH_QUIET); if (active_cache_changed && - write_locked_index(&the_index, lock, COMMIT_LOCK)) + write_locked_index(&the_index, &lock, COMMIT_LOCK)) return error(_("Unable to write index.")); - rollback_lock_file(lock); + rollback_lock_file(&lock); if (!strcmp(strategy, "recursive") || !strcmp(strategy, "subtree")) { int clean, x; struct commit *result; - struct lock_file *lock = xcalloc(1, sizeof(struct lock_file)); struct commit_list *reversed = NULL; struct merge_options o; struct commit_list *j; @@ -696,13 +696,13 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, for (j = common; j; j = j->next) commit_list_insert(j->item, &reversed); - hold_locked_index(lock, 1); + hold_locked_index(&lock, 1); clean = merge_recursive(&o, head, remoteheads->item, reversed, &result); if (active_cache_changed && - write_locked_index(&the_index, lock, COMMIT_LOCK)) + write_locked_index(&the_index, &lock, COMMIT_LOCK)) die (_("unable to write %s"), get_index_file()); - rollback_lock_file(lock); + rollback_lock_file(&lock); return clean ? 0 : 1; } else { return try_merge_command(strategy, xopts_nr, xopts, @@ -1101,7 +1101,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) * Check if we are _not_ on a detached HEAD, i.e. if there is a * current branch. */ - branch = branch_to_free = resolve_refdup("HEAD", head_sha1, 0, &flag); + branch = branch_to_free = resolve_refdup("HEAD", 0, head_sha1, &flag); if (branch && starts_with(branch, "refs/heads/")) branch += 11; if (!branch || is_null_sha1(head_sha1)) diff --git a/builtin/mv.c b/builtin/mv.c index 8883baa903..563d05ba1a 100644 --- a/builtin/mv.c +++ b/builtin/mv.c @@ -3,8 +3,8 @@ * * Copyright (C) 2006 Johannes Schindelin */ -#include "cache.h" #include "builtin.h" +#include "lockfile.h" #include "dir.h" #include "cache-tree.h" #include "string-list.h" diff --git a/builtin/notes.c b/builtin/notes.c index 67d0bb14f8..68b6cd8cc1 100644 --- a/builtin/notes.c +++ b/builtin/notes.c @@ -702,7 +702,7 @@ static int merge_commit(struct notes_merge_options *o) init_notes(t, "NOTES_MERGE_PARTIAL", combine_notes_overwrite, 0); o->local_ref = local_ref_to_free = - resolve_refdup("NOTES_MERGE_REF", sha1, 0, NULL); + resolve_refdup("NOTES_MERGE_REF", 0, sha1, NULL); if (!o->local_ref) die("Failed to resolve NOTES_MERGE_REF"); diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index 64123d4222..3f9f5c7760 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -814,6 +814,7 @@ static void write_pack_file(void) fixup_pack_header_footer(fd, sha1, pack_tmp_name, nr_written, sha1, offset); close(fd); + write_bitmap_index = 0; } if (!pack_to_stdout) { @@ -1975,8 +1976,6 @@ static void ll_find_deltas(struct object_entry **list, unsigned list_size, init_threaded_search(); - if (!delta_search_threads) /* --threads=0 means autodetect */ - delta_search_threads = online_cpus(); if (delta_search_threads <= 1) { find_deltas(list, &list_size, window, depth, processed); cleanup_threaded_search(); @@ -2739,6 +2738,10 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) pack_compression_level = Z_DEFAULT_COMPRESSION; else if (pack_compression_level < 0 || pack_compression_level > Z_BEST_COMPRESSION) die("bad pack compression level %d", pack_compression_level); + + if (!delta_search_threads) /* --threads=0 means autodetect */ + delta_search_threads = online_cpus(); + #ifdef NO_PTHREADS if (delta_search_threads != 1) warning("no threads support, ignoring --threads"); diff --git a/builtin/push.c b/builtin/push.c index ae56f73a66..a076b1964d 100644 --- a/builtin/push.c +++ b/builtin/push.c @@ -471,6 +471,17 @@ static int option_parse_recurse_submodules(const struct option *opt, return 0; } +static int git_push_config(const char *k, const char *v, void *cb) +{ + struct wt_status *s = cb; + int status; + + status = git_gpg_config(k, v, NULL); + if (status) + return status; + return git_default_config(k, v, s); +} + int cmd_push(int argc, const char **argv, const char *prefix) { int flags = 0; @@ -511,7 +522,7 @@ int cmd_push(int argc, const char **argv, const char *prefix) }; packet_trace_identity("push"); - git_config(git_default_config, NULL); + git_config(git_push_config, NULL); argc = parse_options(argc, argv, prefix, options, push_usage, 0); if (deleterefs && (tags || (flags & (TRANSPORT_PUSH_ALL | TRANSPORT_PUSH_MIRROR)))) diff --git a/builtin/read-tree.c b/builtin/read-tree.c index e7e1c33a7f..43b47f72f1 100644 --- a/builtin/read-tree.c +++ b/builtin/read-tree.c @@ -5,6 +5,7 @@ */ #include "cache.h" +#include "lockfile.h" #include "object.h" #include "tree.h" #include "tree-walk.h" diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index a01ac2096a..fc0393779c 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -1,4 +1,5 @@ #include "builtin.h" +#include "lockfile.h" #include "pack.h" #include "refs.h" #include "pkt-line.h" @@ -452,7 +453,6 @@ leave: static void prepare_push_cert_sha1(struct child_process *proc) { static int already_done; - struct argv_array env = ARGV_ARRAY_INIT; if (!push_cert.len) return; @@ -486,20 +486,26 @@ static void prepare_push_cert_sha1(struct child_process *proc) nonce_status = check_nonce(push_cert.buf, bogs); } if (!is_null_sha1(push_cert_sha1)) { - argv_array_pushf(&env, "GIT_PUSH_CERT=%s", sha1_to_hex(push_cert_sha1)); - argv_array_pushf(&env, "GIT_PUSH_CERT_SIGNER=%s", + argv_array_pushf(&proc->env_array, "GIT_PUSH_CERT=%s", + sha1_to_hex(push_cert_sha1)); + argv_array_pushf(&proc->env_array, "GIT_PUSH_CERT_SIGNER=%s", sigcheck.signer ? sigcheck.signer : ""); - argv_array_pushf(&env, "GIT_PUSH_CERT_KEY=%s", + argv_array_pushf(&proc->env_array, "GIT_PUSH_CERT_KEY=%s", sigcheck.key ? sigcheck.key : ""); - argv_array_pushf(&env, "GIT_PUSH_CERT_STATUS=%c", sigcheck.result); + argv_array_pushf(&proc->env_array, "GIT_PUSH_CERT_STATUS=%c", + sigcheck.result); if (push_cert_nonce) { - argv_array_pushf(&env, "GIT_PUSH_CERT_NONCE=%s", push_cert_nonce); - argv_array_pushf(&env, "GIT_PUSH_CERT_NONCE_STATUS=%s", nonce_status); + argv_array_pushf(&proc->env_array, + "GIT_PUSH_CERT_NONCE=%s", + push_cert_nonce); + argv_array_pushf(&proc->env_array, + "GIT_PUSH_CERT_NONCE_STATUS=%s", + nonce_status); if (nonce_status == NONCE_SLOP) - argv_array_pushf(&env, "GIT_PUSH_CERT_NONCE_SLOP=%ld", + argv_array_pushf(&proc->env_array, + "GIT_PUSH_CERT_NONCE_SLOP=%ld", nonce_stamp_slop); } - proc->env = env.argv; } } @@ -841,8 +847,9 @@ static const char *update(struct command *cmd, struct shallow_info *si) transaction = ref_transaction_begin(&err); if (!transaction || ref_transaction_update(transaction, namespaced_name, - new_sha1, old_sha1, 0, 1, &err) || - ref_transaction_commit(transaction, "push", &err)) { + new_sha1, old_sha1, 0, 1, "push", + &err) || + ref_transaction_commit(transaction, &err)) { ref_transaction_free(transaction); rp_error("%s", err.buf); @@ -907,7 +914,7 @@ static void check_aliased_update(struct command *cmd, struct string_list *list) int flag; strbuf_addf(&buf, "%s%s", get_git_namespace(), cmd->ref_name); - dst_name = resolve_ref_unsafe(buf.buf, sha1, 0, &flag); + dst_name = resolve_ref_unsafe(buf.buf, 0, sha1, &flag); strbuf_release(&buf); if (!(flag & REF_ISSYMREF)) @@ -1068,7 +1075,7 @@ static void execute_commands(struct command *commands, check_aliased_updates(commands); free(head_name_to_free); - head_name = head_name_to_free = resolve_refdup("HEAD", sha1, 0, NULL); + head_name = head_name_to_free = resolve_refdup("HEAD", 0, sha1, NULL); checked_connectivity = 1; for (cmd = commands; cmd; cmd = cmd->next) { @@ -1229,7 +1236,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]; @@ -1252,16 +1258,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; @@ -1276,13 +1282,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; diff --git a/builtin/reflog.c b/builtin/reflog.c index 80bddc259b..2d85d260ca 100644 --- a/builtin/reflog.c +++ b/builtin/reflog.c @@ -1,5 +1,5 @@ -#include "cache.h" #include "builtin.h" +#include "lockfile.h" #include "commit.h" #include "refs.h" #include "dir.h" @@ -431,7 +431,7 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused, write_str_in_full(lock->lock_fd, "\n") != 1 || close_ref(lock) < 0)) { status |= error("Couldn't write %s", - lock->lk->filename); + lock->lk->filename.buf); unlink(newlog_path); } else if (rename(newlog_path, log_file)) { status |= error("cannot rename %s to %s", diff --git a/builtin/remote-ext.c b/builtin/remote-ext.c index d699d28e98..3b8c22cc75 100644 --- a/builtin/remote-ext.c +++ b/builtin/remote-ext.c @@ -30,16 +30,14 @@ static char *strip_escapes(const char *str, const char *service, size_t rpos = 0; int escape = 0; char special = 0; - size_t psoff = 0; + const char *service_noprefix = service; struct strbuf ret = STRBUF_INIT; - /* Calculate prefix length for \s and lengths for \s and \S */ - if (!strncmp(service, "git-", 4)) - psoff = 4; + skip_prefix(service_noprefix, "git-", &service_noprefix); /* Pass the service to command. */ setenv("GIT_EXT_SERVICE", service, 1); - setenv("GIT_EXT_SERVICE_NOPREFIX", service + psoff, 1); + setenv("GIT_EXT_SERVICE_NOPREFIX", service_noprefix, 1); /* Scan the length of argument. */ while (str[rpos] && (escape || str[rpos] != ' ')) { @@ -85,7 +83,7 @@ static char *strip_escapes(const char *str, const char *service, strbuf_addch(&ret, str[rpos]); break; case 's': - strbuf_addstr(&ret, service + psoff); + strbuf_addstr(&ret, service_noprefix); break; case 'S': strbuf_addstr(&ret, service); diff --git a/builtin/remote.c b/builtin/remote.c index 9a4640dbf0..7f28f92a37 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -567,7 +567,8 @@ static int read_remote_branches(const char *refname, strbuf_addf(&buf, "refs/remotes/%s/", rename->old); if (starts_with(refname, buf.buf)) { item = string_list_append(rename->remote_branches, xstrdup(refname)); - symref = resolve_ref_unsafe(refname, orig_sha1, 1, &flag); + symref = resolve_ref_unsafe(refname, RESOLVE_REF_READING, + orig_sha1, &flag); if (flag & REF_ISSYMREF) item->util = xstrdup(symref); else @@ -703,7 +704,7 @@ static int mv(int argc, const char **argv) int flag = 0; unsigned char sha1[20]; - read_ref_full(item->string, sha1, 1, &flag); + read_ref_full(item->string, RESOLVE_REF_READING, sha1, &flag); if (!(flag & REF_ISSYMREF)) continue; if (delete_ref(item->string, NULL, REF_NODEREF)) @@ -748,13 +749,16 @@ static int mv(int argc, const char **argv) static int remove_branches(struct string_list *branches) { + struct strbuf err = STRBUF_INIT; const char **branch_names; int i, result = 0; branch_names = xmalloc(branches->nr * sizeof(*branch_names)); for (i = 0; i < branches->nr; i++) branch_names[i] = branches->items[i].string; - result |= repack_without_refs(branch_names, branches->nr, NULL); + if (repack_without_refs(branch_names, branches->nr, &err)) + result |= error("%s", err.buf); + strbuf_release(&err); free(branch_names); for (i = 0; i < branches->nr; i++) { @@ -1331,9 +1335,13 @@ static int prune_remote(const char *remote, int dry_run) delete_refs = xmalloc(states.stale.nr * sizeof(*delete_refs)); for (i = 0; i < states.stale.nr; i++) delete_refs[i] = states.stale.items[i].util; - if (!dry_run) - result |= repack_without_refs(delete_refs, - states.stale.nr, NULL); + if (!dry_run) { + struct strbuf err = STRBUF_INIT; + if (repack_without_refs(delete_refs, states.stale.nr, + &err)) + result |= error("%s", err.buf); + strbuf_release(&err); + } free(delete_refs); } diff --git a/builtin/replace.c b/builtin/replace.c index 8020db8500..85d39b58d8 100644 --- a/builtin/replace.c +++ b/builtin/replace.c @@ -171,8 +171,9 @@ static int replace_object_sha1(const char *object_ref, transaction = ref_transaction_begin(&err); if (!transaction || - ref_transaction_update(transaction, ref, repl, prev, 0, 1, &err) || - ref_transaction_commit(transaction, NULL, &err)) + ref_transaction_update(transaction, ref, repl, prev, + 0, 1, NULL, &err) || + ref_transaction_commit(transaction, &err)) die("%s", err.buf); ref_transaction_free(transaction); diff --git a/builtin/reset.c b/builtin/reset.c index 855d478e3b..4c08ddc1ca 100644 --- a/builtin/reset.c +++ b/builtin/reset.c @@ -8,6 +8,7 @@ * Copyright (c) 2005, 2006 Linus Torvalds and Junio C Hamano */ #include "builtin.h" +#include "lockfile.h" #include "tag.h" #include "object.h" #include "commit.h" diff --git a/builtin/rm.c b/builtin/rm.c index 2b61d3bd41..d8a9c86dd1 100644 --- a/builtin/rm.c +++ b/builtin/rm.c @@ -3,8 +3,8 @@ * * Copyright (C) Linus Torvalds 2006 */ -#include "cache.h" #include "builtin.h" +#include "lockfile.h" #include "dir.h" #include "cache-tree.h" #include "tree-walk.h" diff --git a/builtin/show-branch.c b/builtin/show-branch.c index 199b081e9b..270e39c6c1 100644 --- a/builtin/show-branch.c +++ b/builtin/show-branch.c @@ -728,7 +728,9 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) if (ac == 0) { static const char *fake_av[2]; - fake_av[0] = resolve_refdup("HEAD", sha1, 1, NULL); + fake_av[0] = resolve_refdup("HEAD", + RESOLVE_REF_READING, + sha1, NULL); fake_av[1] = NULL; av = fake_av; ac = 1; @@ -789,7 +791,8 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) } } - head_p = resolve_ref_unsafe("HEAD", head_sha1, 1, NULL); + head_p = resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, + head_sha1, NULL); if (head_p) { head_len = strlen(head_p); memcpy(head, head_p, head_len + 1); diff --git a/builtin/symbolic-ref.c b/builtin/symbolic-ref.c index b6a711d319..29fb3f1c20 100644 --- a/builtin/symbolic-ref.c +++ b/builtin/symbolic-ref.c @@ -13,7 +13,7 @@ static int check_symref(const char *HEAD, int quiet, int shorten, int print) { unsigned char sha1[20]; int flag; - const char *refname = resolve_ref_unsafe(HEAD, sha1, 0, &flag); + const char *refname = resolve_ref_unsafe(HEAD, 0, sha1, &flag); if (!refname) die("No such ref: %s", HEAD); diff --git a/builtin/tag.c b/builtin/tag.c index a81b9e4174..e633f4efdb 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -733,8 +733,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix) transaction = ref_transaction_begin(&err); if (!transaction || ref_transaction_update(transaction, ref.buf, object, prev, - 0, 1, &err) || - ref_transaction_commit(transaction, NULL, &err)) + 0, 1, NULL, &err) || + ref_transaction_commit(transaction, &err)) die("%s", err.buf); ref_transaction_free(transaction); if (force && !is_null_sha1(prev) && hashcmp(prev, object)) diff --git a/builtin/update-index.c b/builtin/update-index.c index e8c7fd4d49..b0e3dc9105 100644 --- a/builtin/update-index.c +++ b/builtin/update-index.c @@ -4,6 +4,7 @@ * Copyright (C) Linus Torvalds, 2005 */ #include "cache.h" +#include "lockfile.h" #include "quote.h" #include "cache-tree.h" #include "tree-walk.h" @@ -942,7 +943,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) if (newfd < 0) { if (refresh_args.flags & REFRESH_QUIET) exit(128); - unable_to_lock_index_die(get_index_file(), lock_error); + unable_to_lock_die(get_index_file(), lock_error); } if (write_locked_index(&the_index, lock_file, COMMIT_LOCK)) die("Unable to write new index file"); diff --git a/builtin/update-ref.c b/builtin/update-ref.c index 54a48c0cfa..6c9be05128 100644 --- a/builtin/update-ref.c +++ b/builtin/update-ref.c @@ -14,6 +14,7 @@ static const char * const git_update_ref_usage[] = { static char line_termination = '\n'; static int update_flags; +static const char *msg; /* * Parse one whitespace- or NUL-terminated, possibly C-quoted argument @@ -198,7 +199,7 @@ static const char *parse_cmd_update(struct ref_transaction *transaction, die("update %s: extra input: %s", refname, next); if (ref_transaction_update(transaction, refname, new_sha1, old_sha1, - update_flags, have_old, &err)) + update_flags, have_old, msg, &err)) die("%s", err.buf); update_flags = 0; @@ -229,7 +230,7 @@ static const char *parse_cmd_create(struct ref_transaction *transaction, die("create %s: extra input: %s", refname, next); if (ref_transaction_create(transaction, refname, new_sha1, - update_flags, &err)) + update_flags, msg, &err)) die("%s", err.buf); update_flags = 0; @@ -264,7 +265,7 @@ static const char *parse_cmd_delete(struct ref_transaction *transaction, die("delete %s: extra input: %s", refname, next); if (ref_transaction_delete(transaction, refname, old_sha1, - update_flags, have_old, &err)) + update_flags, have_old, msg, &err)) die("%s", err.buf); update_flags = 0; @@ -300,7 +301,7 @@ static const char *parse_cmd_verify(struct ref_transaction *transaction, die("verify %s: extra input: %s", refname, next); if (ref_transaction_update(transaction, refname, new_sha1, old_sha1, - update_flags, have_old, &err)) + update_flags, have_old, msg, &err)) die("%s", err.buf); update_flags = 0; @@ -354,7 +355,7 @@ static void update_refs_stdin(struct ref_transaction *transaction) int cmd_update_ref(int argc, const char **argv, const char *prefix) { - const char *refname, *oldval, *msg = NULL; + const char *refname, *oldval; unsigned char sha1[20], oldsha1[20]; int delete = 0, no_deref = 0, read_stdin = 0, end_null = 0, flags = 0; struct option options[] = { @@ -385,7 +386,7 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix) if (end_null) line_termination = '\0'; update_refs_stdin(transaction); - if (ref_transaction_commit(transaction, msg, &err)) + if (ref_transaction_commit(transaction, &err)) die("%s", err.buf); ref_transaction_free(transaction); strbuf_release(&err); diff --git a/bulk-checkin.c b/bulk-checkin.c index 98e651c284..0c4b8a7cad 100644 --- a/bulk-checkin.c +++ b/bulk-checkin.c @@ -1,6 +1,7 @@ /* * Copyright (c) 2011, Google Inc. */ +#include "cache.h" #include "bulk-checkin.h" #include "csum-file.h" #include "pack.h" diff --git a/bulk-checkin.h b/bulk-checkin.h index 4f599f8841..fbd40fc98c 100644 --- a/bulk-checkin.h +++ b/bulk-checkin.h @@ -4,8 +4,6 @@ #ifndef BULK_CHECKIN_H #define BULK_CHECKIN_H -#include "cache.h" - extern int index_bulk_checkin(unsigned char sha1[], int fd, size_t size, enum object_type type, const char *path, unsigned flags); @@ -1,4 +1,5 @@ #include "cache.h" +#include "lockfile.h" #include "bundle.h" #include "object.h" #include "commit.h" @@ -209,26 +210,29 @@ static int is_tag_in_date_range(struct object *tag, struct rev_info *revs) { unsigned long size; enum object_type type; - char *buf, *line, *lineend; + char *buf = NULL, *line, *lineend; unsigned long date; + int result = 1; if (revs->max_age == -1 && revs->min_age == -1) - return 1; + goto out; buf = read_sha1_file(tag->sha1, &type, &size); if (!buf) - return 1; + goto out; line = memmem(buf, size, "\ntagger ", 8); if (!line++) - return 1; + goto out; lineend = memchr(line, '\n', buf + size - line); line = memchr(line, '>', lineend ? lineend - line : buf + size - line); if (!line++) - return 1; + goto out; date = strtoul(line, NULL, 10); - free(buf); - return (revs->max_age == -1 || revs->max_age < date) && + result = (revs->max_age == -1 || revs->max_age < date) && (revs->min_age == -1 || revs->min_age > date); +out: + free(buf); + return result; } int create_bundle(struct bundle_header *header, const char *path, @@ -306,7 +310,7 @@ int create_bundle(struct bundle_header *header, const char *path, continue; if (dwim_ref(e->name, strlen(e->name), sha1, &ref) != 1) continue; - if (read_ref_full(e->name, sha1, 1, &flag)) + if (read_ref_full(e->name, RESOLVE_REF_READING, sha1, &flag)) flag = 0; display_ref = (flag & REF_ISSYMREF) ? e->name : ref; diff --git a/cache-tree.c b/cache-tree.c index 75a54fdc72..215202c42d 100644 --- a/cache-tree.c +++ b/cache-tree.c @@ -1,4 +1,5 @@ #include "cache.h" +#include "lockfile.h" #include "tree.h" #include "tree-walk.h" #include "cache-tree.h" @@ -570,29 +570,11 @@ extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st); #define REFRESH_IN_PORCELAIN 0x0020 /* user friendly output, not "needs update" */ extern int refresh_index(struct index_state *, unsigned int flags, const struct pathspec *pathspec, char *seen, const char *header_msg); -struct lock_file { - struct lock_file *next; - int fd; - pid_t owner; - char on_list; - char filename[PATH_MAX]; -}; -#define LOCK_DIE_ON_ERROR 1 -#define LOCK_NODEREF 2 -extern int unable_to_lock_error(const char *path, int err); -extern void unable_to_lock_message(const char *path, int err, - struct strbuf *buf); -extern NORETURN void unable_to_lock_index_die(const char *path, int err); -extern int hold_lock_file_for_update(struct lock_file *, const char *path, int); -extern int hold_lock_file_for_append(struct lock_file *, const char *path, int); -extern int commit_lock_file(struct lock_file *); -extern int reopen_lock_file(struct lock_file *); extern void update_index_if_able(struct index_state *, struct lock_file *); extern int hold_locked_index(struct lock_file *, int); extern void set_alternate_index_output(const char *); -extern int close_lock_file(struct lock_file *); -extern void rollback_lock_file(struct lock_file *); + extern int delete_ref(const char *, const unsigned char *sha1, int delopt); /* Environment bits from configuration mechanism */ @@ -968,8 +950,8 @@ extern int for_each_abbrev(const char *prefix, each_abbrev_fn, void *); extern int get_sha1_hex(const char *hex, unsigned char *sha1); extern char *sha1_to_hex(const unsigned char *sha1); /* static buffer result! */ -extern int read_ref_full(const char *refname, unsigned char *sha1, - int reading, int *flags); +extern int read_ref_full(const char *refname, int resolve_flags, + unsigned char *sha1, int *flags); extern int read_ref(const char *refname, unsigned char *sha1); /* @@ -981,29 +963,49 @@ extern int read_ref(const char *refname, unsigned char *sha1); * or the input ref. * * If the reference cannot be resolved to an object, the behavior - * depends on the "reading" argument: + * depends on the RESOLVE_REF_READING flag: * - * - If reading is set, return NULL. + * - If RESOLVE_REF_READING is set, return NULL. * - * - If reading is not set, clear sha1 and return the name of the last - * reference name in the chain, which will either be a non-symbolic + * - If RESOLVE_REF_READING is not set, clear sha1 and return the name of + * the last reference name in the chain, which will either be a non-symbolic * reference or an undefined reference. If this is a prelude to * "writing" to the ref, the return value is the name of the ref * that will actually be created or changed. * - * If flag is non-NULL, set the value that it points to the + * If the RESOLVE_REF_NO_RECURSE flag is passed, only resolves one + * level of symbolic reference. The value stored in sha1 for a symbolic + * reference will always be null_sha1 in this case, and the return + * value is the reference that the symref refers to directly. + * + * If flags is non-NULL, set the value that it points to the * combination of REF_ISPACKED (if the reference was found among the - * packed references) and REF_ISSYMREF (if the initial reference was a - * symbolic reference). + * packed references), REF_ISSYMREF (if the initial reference was a + * symbolic reference), REF_BAD_NAME (if the reference name is ill + * formed --- see RESOLVE_REF_ALLOW_BAD_NAME below), and REF_ISBROKEN + * (if the ref is malformed or has a bad name). See refs.h for more detail + * on each flag. * * If ref is not a properly-formatted, normalized reference, return * NULL. If more than MAXDEPTH recursive symbolic lookups are needed, * give up and return NULL. * - * errno is set to something meaningful on error. + * RESOLVE_REF_ALLOW_BAD_NAME allows resolving refs even when their + * name is invalid according to git-check-ref-format(1). If the name + * is bad then the value stored in sha1 will be null_sha1 and the two + * flags REF_ISBROKEN and REF_BAD_NAME will be set. + * + * Even with RESOLVE_REF_ALLOW_BAD_NAME, names that escape the refs/ + * directory and do not consist of all caps and underscores cannot be + * resolved. The function returns NULL for such ref names. + * Caps and underscores refers to the special refs, such as HEAD, + * FETCH_HEAD and friends, that all live outside of the refs/ directory. */ -extern const char *resolve_ref_unsafe(const char *ref, unsigned char *sha1, int reading, int *flag); -extern char *resolve_refdup(const char *ref, unsigned char *sha1, int reading, int *flag); +#define RESOLVE_REF_READING 0x01 +#define RESOLVE_REF_NO_RECURSE 0x02 +#define RESOLVE_REF_ALLOW_BAD_NAME 0x04 +extern const char *resolve_ref_unsafe(const char *ref, int resolve_flags, unsigned char *sha1, int *flags); +extern char *resolve_refdup(const char *ref, int resolve_flags, unsigned char *sha1, int *flags); extern int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref); extern int dwim_log(const char *str, int len, unsigned char *sha1, char **ref); @@ -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 a3ff0c9e60..f1eae0810d 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 @@ -6,6 +6,7 @@ * */ #include "cache.h" +#include "lockfile.h" #include "exec_cmd.h" #include "strbuf.h" #include "quote.h" @@ -2040,9 +2041,9 @@ int git_config_set_multivar_in_file(const char *config_filename, MAP_PRIVATE, in_fd, 0); close(in_fd); - if (chmod(lock->filename, st.st_mode & 07777) < 0) { + if (chmod(lock->filename.buf, st.st_mode & 07777) < 0) { error("chmod on %s failed: %s", - lock->filename, strerror(errno)); + lock->filename.buf, strerror(errno)); ret = CONFIG_NO_WRITE; goto out_free; } @@ -2099,6 +2100,7 @@ int git_config_set_multivar_in_file(const char *config_filename, if (commit_lock_file(lock) < 0) { error("could not commit config file %s", config_filename); ret = CONFIG_NO_WRITE; + lock = NULL; goto out_free; } @@ -2121,7 +2123,7 @@ out_free: return ret; write_err_out: - ret = write_error(lock->filename); + ret = write_error(lock->filename.buf); goto out_free; } @@ -2222,9 +2224,9 @@ int git_config_rename_section_in_file(const char *config_filename, fstat(fileno(config_file), &st); - if (chmod(lock->filename, st.st_mode & 07777) < 0) { + if (chmod(lock->filename.buf, st.st_mode & 07777) < 0) { ret = error("chmod on %s failed: %s", - lock->filename, strerror(errno)); + lock->filename.buf, strerror(errno)); goto out; } @@ -2245,7 +2247,7 @@ int git_config_rename_section_in_file(const char *config_filename, } store.baselen = strlen(new_name); if (!store_write_section(out_fd, new_name)) { - ret = write_error(lock->filename); + ret = write_error(lock->filename.buf); goto out; } /* @@ -2271,7 +2273,7 @@ int git_config_rename_section_in_file(const char *config_filename, continue; length = strlen(output); if (write_in_full(out_fd, output, length) != length) { - ret = write_error(lock->filename); + ret = write_error(lock->filename.buf); goto out; } } diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 5ea5b82d2b..8704451e52 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 } @@ -388,7 +384,8 @@ __git_refs () ;; *) echo "HEAD" - git for-each-ref --format="%(refname:short)" -- "refs/remotes/$dir/" | sed -e "s#^$dir/##" + git for-each-ref --format="%(refname:short)" -- \ + "refs/remotes/$dir/" 2>/dev/null | sed -e "s#^$dir/##" ;; esac } @@ -522,7 +519,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 () @@ -1467,6 +1464,7 @@ _git_log () --abbrev-commit --abbrev= --relative-date --date= --pretty= --format= --oneline + --show-signature --cherry-pick --graph --decorate --decorate= @@ -2344,6 +2342,7 @@ _git_show () ;; --*) __gitcomp "--pretty= --format= --abbrev-commit --oneline + --show-signature $__git_diff_common_options " return diff --git a/contrib/contacts/.gitignore b/contrib/contacts/.gitignore new file mode 100644 index 0000000000..f385ee643c --- /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 0000000000..a2990f0dcb --- /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 91360a3d7f..0b9381abca 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 c2bd703ee3..3071baf493 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 $^ diff --git a/credential-store.c b/credential-store.c index f9146e576f..d435514cbe 100644 --- a/credential-store.c +++ b/credential-store.c @@ -1,4 +1,5 @@ #include "cache.h" +#include "lockfile.h" #include "credential.h" #include "string-list.h" #include "parse-options.h" @@ -553,20 +553,21 @@ static void parse_host_arg(char *extra_args, int buflen) static char addrbuf[HOST_NAME_MAX + 1]; hent = gethostbyname(hostname); + if (hent) { + ap = hent->h_addr_list; + memset(&sa, 0, sizeof sa); + sa.sin_family = hent->h_addrtype; + sa.sin_port = htons(0); + memcpy(&sa.sin_addr, *ap, hent->h_length); + + inet_ntop(hent->h_addrtype, &sa.sin_addr, + addrbuf, sizeof(addrbuf)); - ap = hent->h_addr_list; - memset(&sa, 0, sizeof sa); - sa.sin_family = hent->h_addrtype; - sa.sin_port = htons(0); - memcpy(&sa.sin_addr, *ap, hent->h_length); - - inet_ntop(hent->h_addrtype, &sa.sin_addr, - addrbuf, sizeof(addrbuf)); - - free(canon_hostname); - canon_hostname = xstrdup(hent->h_name); - free(ip_address); - ip_address = xstrdup(addrbuf); + free(canon_hostname); + canon_hostname = xstrdup(hent->h_name); + free(ip_address); + ip_address = xstrdup(addrbuf); + } #endif } } @@ -814,7 +815,6 @@ static const char *ip2str(int family, struct sockaddr *sin, socklen_t len) static int setup_named_sock(char *listen_addr, int listen_port, struct socketlist *socklist) { int socknum = 0; - int maxfd = -1; char pbuf[NI_MAXSERV]; struct addrinfo hints, *ai0, *ai; int gai; @@ -882,9 +882,6 @@ static int setup_named_sock(char *listen_addr, int listen_port, struct socketlis ALLOC_GROW(socklist->list, socklist->nr + 1, socklist->alloc); socklist->list[socklist->nr++] = sockfd; socknum++; - - if (maxfd < sockfd) - maxfd = sockfd; } freeaddrinfo(ai0); @@ -923,7 +920,7 @@ static int setup_named_sock(char *listen_addr, int listen_port, struct socketlis } if ( bind(sockfd, (struct sockaddr *)&sin, sizeof sin) < 0 ) { - logerror("Could not listen to %s: %s", + logerror("Could not bind to %s: %s", ip2str(AF_INET, (struct sockaddr *)&sin, sizeof(sin)), strerror(errno)); close(sockfd); @@ -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 */ @@ -826,9 +826,9 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen) current = stk ? stk->baselen : -1; strbuf_setlen(&dir->basebuf, current < 0 ? 0 : current); while (current < baselen) { - struct exclude_stack *stk = xcalloc(1, sizeof(*stk)); const char *cp; + stk = xcalloc(1, sizeof(*stk)); if (current < 0) { cp = base; current = 0; diff --git a/fast-import.c b/fast-import.c index 96b0f4236a..d0bd285a16 100644 --- a/fast-import.c +++ b/fast-import.c @@ -153,6 +153,7 @@ Format of STDIN stream: #include "builtin.h" #include "cache.h" +#include "lockfile.h" #include "object.h" #include "blob.h" #include "tree.h" @@ -1715,8 +1716,8 @@ static int update_branch(struct branch *b) transaction = ref_transaction_begin(&err); if (!transaction || ref_transaction_update(transaction, b->name, b->sha1, old_sha1, - 0, 1, &err) || - ref_transaction_commit(transaction, msg, &err)) { + 0, 1, msg, &err) || + ref_transaction_commit(transaction, &err)) { ref_transaction_free(transaction); error("%s", err.buf); strbuf_release(&err); @@ -1756,12 +1757,12 @@ static void dump_tags(void) strbuf_addf(&ref_name, "refs/tags/%s", t->name); if (ref_transaction_update(transaction, ref_name.buf, t->sha1, - NULL, 0, 0, &err)) { + NULL, 0, 0, msg, &err)) { failure |= error("%s", err.buf); goto cleanup; } } - if (ref_transaction_commit(transaction, msg, &err)) + if (ref_transaction_commit(transaction, &err)) failure |= error("%s", err.buf); cleanup: @@ -1793,20 +1794,18 @@ static void dump_marks_helper(FILE *f, static void dump_marks(void) { static struct lock_file mark_lock; - int mark_fd; FILE *f; if (!export_marks_file) return; - mark_fd = hold_lock_file_for_update(&mark_lock, export_marks_file, 0); - if (mark_fd < 0) { + if (hold_lock_file_for_update(&mark_lock, export_marks_file, 0) < 0) { failure |= error("Unable to write marks file %s: %s", export_marks_file, strerror(errno)); return; } - f = fdopen(mark_fd, "w"); + f = fdopen_lock_file(&mark_lock, "w"); if (!f) { int saved_errno = errno; rollback_lock_file(&mark_lock); @@ -1815,27 +1814,10 @@ static void dump_marks(void) return; } - /* - * Since the lock file was fdopen()'ed, it should not be close()'ed. - * Assign -1 to the lock file descriptor so that commit_lock_file() - * won't try to close() it. - */ - mark_lock.fd = -1; - dump_marks_helper(f, 0, marks); - if (ferror(f) || fclose(f)) { - int saved_errno = errno; - rollback_lock_file(&mark_lock); - failure |= error("Unable to write marks file %s: %s", - export_marks_file, strerror(saved_errno)); - return; - } - if (commit_lock_file(&mark_lock)) { - int saved_errno = errno; - rollback_lock_file(&mark_lock); failure |= error("Unable to commit marks file %s: %s", - export_marks_file, strerror(saved_errno)); + export_marks_file, strerror(errno)); return; } } diff --git a/fetch-pack.c b/fetch-pack.c index 7487aa7306..655ee64256 100644 --- a/fetch-pack.c +++ b/fetch-pack.c @@ -1,4 +1,5 @@ #include "cache.h" +#include "lockfile.h" #include "refs.h" #include "pkt-line.h" #include "commit.h" diff --git a/git-compat-util.h b/git-compat-util.h index 44890d5b18..fc83339bd7 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -326,6 +326,8 @@ static inline char *git_find_last_dir_sep(const char *path) #include "wildmatch.h" +struct strbuf; + /* General helper functions */ extern void vreportf(const char *prefix, const char *err, va_list params); extern void vwritef(int fd, const char *prefix, const char *err, va_list params); @@ -594,6 +596,11 @@ int inet_pton(int af, const char *src, void *dst); const char *inet_ntop(int af, const void *src, char *dst, size_t size); #endif +#ifdef NO_PTHREADS +#define atexit git_atexit +extern int git_atexit(void (*handler)(void)); +#endif + extern void release_pack_memory(size_t); typedef void (*try_to_free_t)(size_t); @@ -777,11 +784,21 @@ void git_qsort(void *base, size_t nmemb, size_t size, /* * Preserves errno, prints a message, but gives no warning for ENOENT. - * Always returns the return value of unlink(2). + * Returns 0 on success, which includes trying to unlink an object that does + * not exist. */ int unlink_or_warn(const char *path); + /* + * Tries to unlink file. Returns 0 if unlink succeeded + * or the file already didn't exist. Returns -1 and + * appends a message to err suitable for + * 'error("%s", err->buf)' on error. + */ +int unlink_or_msg(const char *file, struct strbuf *err); /* - * Likewise for rmdir(2). + * Preserves errno, prints a message, but gives no warning for ENOENT. + * Returns 0 on success, which includes trying to remove a directory that does + * not exist. */ int rmdir_or_warn(const char *path); /* diff --git a/git-difftool.perl b/git-difftool.perl index 18ca61e8d0..598fcc23b9 100755 --- a/git-difftool.perl +++ b/git-difftool.perl @@ -47,13 +47,9 @@ sub find_worktree sub print_tool_help { - my $cmd = 'TOOL_MODE=diff'; - $cmd .= ' && . "$(git --exec-path)/git-mergetool--lib"'; - $cmd .= ' && show_tool_help'; - # See the comment at the bottom of file_diff() for the reason behind # using system() followed by exit() instead of exec(). - my $rc = system('sh', '-c', $cmd); + my $rc = system(qw(git mergetool --tool-help=diff)); exit($rc | ($rc >> 8)); } diff --git a/git-mergetool.sh b/git-mergetool.sh index 9a046b75d1..ff050e58ff 100755 --- a/git-mergetool.sh +++ b/git-mergetool.sh @@ -10,11 +10,11 @@ USAGE='[--tool=tool] [--tool-help] [-y|--no-prompt|--prompt] [file to merge] ...' SUBDIRECTORY_OK=Yes +NONGIT_OK=Yes OPTIONS_SPEC= TOOL_MODE=merge . git-sh-setup . git-mergetool--lib -require_work_tree # Returns true if the mode reflects a symlink is_symlink () { @@ -37,6 +37,19 @@ base_present () { test -n "$base_mode" } +mergetool_tmpdir_init () { + if test "$(git config --bool mergetool.writeToTemp)" != true + then + MERGETOOL_TMPDIR=. + return 0 + fi + if MERGETOOL_TMPDIR=$(mktemp -d -t "git-mergetool-XXXXXX" 2>/dev/null) + then + return 0 + fi + die "error: mktemp is needed when 'mergetool.writeToTemp' is true" +} + cleanup_temp_files () { if test "$1" = --save-backup then @@ -46,6 +59,10 @@ cleanup_temp_files () { else rm -f -- "$LOCAL" "$REMOTE" "$BASE" "$BACKUP" fi + if test "$MERGETOOL_TMPDIR" != "." + then + rmdir "$MERGETOOL_TMPDIR" + fi } describe_file () { @@ -228,11 +245,27 @@ merge_file () { return 1 fi - ext="$$$(expr "$MERGED" : '.*\(\.[^/]*\)$')" - BACKUP="./$MERGED.BACKUP.$ext" - LOCAL="./$MERGED.LOCAL.$ext" - REMOTE="./$MERGED.REMOTE.$ext" - BASE="./$MERGED.BASE.$ext" + if BASE=$(expr "$MERGED" : '\(.*\)\.[^/]*$') + then + ext=$(expr "$MERGED" : '.*\(\.[^/]*\)$') + else + BASE=$MERGED + ext= + fi + + mergetool_tmpdir_init + + if test "$MERGETOOL_TMPDIR" != "." + then + # If we're using a temporary directory then write to the + # top-level of that directory. + BASE=${BASE##*/} + fi + + BACKUP="$MERGETOOL_TMPDIR/${BASE}_BACKUP_$$$ext" + LOCAL="$MERGETOOL_TMPDIR/${BASE}_LOCAL_$$$ext" + REMOTE="$MERGETOOL_TMPDIR/${BASE}_REMOTE_$$$ext" + BASE="$MERGETOOL_TMPDIR/${BASE}_BASE_$$$ext" base_mode=$(git ls-files -u -- "$MERGED" | awk '{if ($3==1) print $1;}') local_mode=$(git ls-files -u -- "$MERGED" | awk '{if ($3==2) print $1;}') @@ -321,6 +354,10 @@ guessed_merge_tool=false while test $# != 0 do case "$1" in + --tool-help=*) + TOOL_MODE=${1#--tool-help=} + show_tool_help + ;; --tool-help) show_tool_help ;; @@ -372,6 +409,9 @@ prompt_after_failed_merge () { done } +git_dir_init +require_work_tree + if test -z "$merge_tool" then # Check if a merge tool has been configured diff --git a/git-sh-setup.sh b/git-sh-setup.sh index 9447980330..d968760139 100644 --- a/git-sh-setup.sh +++ b/git-sh-setup.sh @@ -330,8 +330,7 @@ esac # Make sure we are in a valid repository of a vintage we understand, # if we require to be in a git repository. -if test -z "$NONGIT_OK" -then +git_dir_init () { GIT_DIR=$(git rev-parse --git-dir) || exit if [ -z "$SUBDIRECTORY_OK" ] then @@ -346,6 +345,11 @@ then exit 1 } : ${GIT_OBJECT_DIRECTORY="$GIT_DIR/objects"} +} + +if test -z "$NONGIT_OK" +then + git_dir_init fi peel_committish () { @@ -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 a9f57d6f90..ccf75169dd 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/http-backend.c b/http-backend.c index 404e682593..b6c0484fb2 100644 --- a/http-backend.c +++ b/http-backend.c @@ -314,7 +314,6 @@ static void run_service(const char **argv) const char *encoding = getenv("HTTP_CONTENT_ENCODING"); const char *user = getenv("REMOTE_USER"); const char *host = getenv("REMOTE_ADDR"); - struct argv_array env = ARGV_ARRAY_INIT; int gzipped_request = 0; struct child_process cld = CHILD_PROCESS_INIT; @@ -329,13 +328,12 @@ static void run_service(const char **argv) host = "(none)"; if (!getenv("GIT_COMMITTER_NAME")) - argv_array_pushf(&env, "GIT_COMMITTER_NAME=%s", user); + argv_array_pushf(&cld.env_array, "GIT_COMMITTER_NAME=%s", user); if (!getenv("GIT_COMMITTER_EMAIL")) - argv_array_pushf(&env, "GIT_COMMITTER_EMAIL=%s@http.%s", - user, host); + argv_array_pushf(&cld.env_array, + "GIT_COMMITTER_EMAIL=%s@http.%s", user, host); cld.argv = argv; - cld.env = env.argv; if (gzipped_request) cld.in = -1; cld.git_cmd = 1; @@ -350,7 +348,6 @@ static void run_service(const char **argv) if (finish_command(&cld)) exit(1); - argv_array_clear(&env); } static int show_text_ref(const char *name, const unsigned char *sha1, @@ -412,7 +409,9 @@ static int show_head_ref(const char *refname, const unsigned char *sha1, if (flag & REF_ISSYMREF) { unsigned char unused[20]; - const char *target = resolve_ref_unsafe(refname, unused, 1, NULL); + const char *target = resolve_ref_unsafe(refname, + RESOLVE_REF_READING, + unused, NULL); const char *target_nons = strip_namespace(target); strbuf_addf(buf, "ref: %s\n", target_nons); @@ -1,3 +1,4 @@ +#include "git-compat-util.h" #include "http.h" #include "pack.h" #include "sideband.h" diff --git a/lockfile.c b/lockfile.c index d34a96df4f..4f16ee78ce 100644 --- a/lockfile.c +++ b/lockfile.c @@ -2,59 +2,61 @@ * Copyright (c) 2005, Junio C Hamano */ #include "cache.h" +#include "lockfile.h" #include "sigchain.h" -static struct lock_file *lock_file_list; +static struct lock_file *volatile lock_file_list; -static void remove_lock_file(void) +static void remove_lock_files(int skip_fclose) { pid_t me = getpid(); while (lock_file_list) { - if (lock_file_list->owner == me && - lock_file_list->filename[0]) { - if (lock_file_list->fd >= 0) - close(lock_file_list->fd); - unlink_or_warn(lock_file_list->filename); + if (lock_file_list->owner == me) { + /* fclose() is not safe to call in a signal handler */ + if (skip_fclose) + lock_file_list->fp = NULL; + rollback_lock_file(lock_file_list); } lock_file_list = lock_file_list->next; } } -static void remove_lock_file_on_signal(int signo) +static void remove_lock_files_on_exit(void) { - remove_lock_file(); + remove_lock_files(0); +} + +static void remove_lock_files_on_signal(int signo) +{ + remove_lock_files(1); sigchain_pop(signo); raise(signo); } /* - * p = absolute or relative path name + * path = absolute or relative path name * - * Return a pointer into p showing the beginning of the last path name - * element. If p is empty or the root directory ("/"), just return p. + * Remove the last path name element from path (leaving the preceding + * "/", if any). If path is empty or the root directory ("/"), set + * path to the empty string. */ -static char *last_path_elm(char *p) +static void trim_last_path_component(struct strbuf *path) { - /* r starts pointing to null at the end of the string */ - char *r = strchr(p, '\0'); - - if (r == p) - return p; /* just return empty string */ - - r--; /* back up to last non-null character */ + int i = path->len; /* back up past trailing slashes, if any */ - while (r > p && *r == '/') - r--; + while (i && path->buf[i - 1] == '/') + i--; /* - * then go backwards until I hit a slash, or the beginning of - * the string + * then go backwards until a slash, or the beginning of the + * string */ - while (r > p && *(r-1) != '/') - r--; - return r; + while (i && path->buf[i - 1] != '/') + i--; + + strbuf_setlen(path, i); } @@ -62,103 +64,88 @@ static char *last_path_elm(char *p) #define MAXDEPTH 5 /* - * p = path that may be a symlink - * s = full size of p + * path contains a path that might be a symlink. * - * If p is a symlink, attempt to overwrite p with a path to the real - * file or directory (which may or may not exist), following a chain of - * symlinks if necessary. Otherwise, leave p unmodified. + * If path is a symlink, attempt to overwrite it with a path to the + * real file or directory (which may or may not exist), following a + * chain of symlinks if necessary. Otherwise, leave path unmodified. * - * This is a best-effort routine. If an error occurs, p will either be - * left unmodified or will name a different symlink in a symlink chain - * that started with p's initial contents. - * - * Always returns p. + * This is a best-effort routine. If an error occurs, path will + * either be left unmodified or will name a different symlink in a + * symlink chain that started with the original path. */ - -static char *resolve_symlink(char *p, size_t s) +static void resolve_symlink(struct strbuf *path) { int depth = MAXDEPTH; + static struct strbuf link = STRBUF_INIT; while (depth--) { - char link[PATH_MAX]; - int link_len = readlink(p, link, sizeof(link)); - if (link_len < 0) { - /* not a symlink anymore */ - return p; - } - else if (link_len < sizeof(link)) - /* readlink() never null-terminates */ - link[link_len] = '\0'; - else { - warning("%s: symlink too long", p); - return p; - } + if (strbuf_readlink(&link, path->buf, path->len) < 0) + break; - if (is_absolute_path(link)) { + if (is_absolute_path(link.buf)) /* absolute path simply replaces p */ - if (link_len < s) - strcpy(p, link); - else { - warning("%s: symlink too long", p); - return p; - } - } else { + strbuf_reset(path); + else /* - * link is a relative path, so I must replace the + * link is a relative path, so replace the * last element of p with it. */ - char *r = (char *)last_path_elm(p); - if (r - p + link_len < s) - strcpy(r, link); - else { - warning("%s: symlink too long", p); - return p; - } - } + trim_last_path_component(path); + + strbuf_addbuf(path, &link); } - return p; + strbuf_reset(&link); } /* Make sure errno contains a meaningful value on error */ static int lock_file(struct lock_file *lk, const char *path, int flags) { - /* - * subtract 5 from size to make sure there's room for adding - * ".lock" for the lock file name - */ - static const size_t max_path_len = sizeof(lk->filename) - 5; + size_t pathlen = strlen(path); + + if (!lock_file_list) { + /* One-time initialization */ + sigchain_push_common(remove_lock_files_on_signal); + atexit(remove_lock_files_on_exit); + } - if (strlen(path) >= max_path_len) { - errno = ENAMETOOLONG; + if (lk->active) + die("BUG: cannot lock_file(\"%s\") using active struct lock_file", + path); + if (!lk->on_list) { + /* Initialize *lk and add it to lock_file_list: */ + lk->fd = -1; + lk->fp = NULL; + lk->active = 0; + lk->owner = 0; + strbuf_init(&lk->filename, pathlen + LOCK_SUFFIX_LEN); + lk->next = lock_file_list; + lock_file_list = lk; + lk->on_list = 1; + } else if (lk->filename.len) { + /* This shouldn't happen, but better safe than sorry. */ + die("BUG: lock_file(\"%s\") called with improperly-reset lock_file object", + path); + } + + strbuf_add(&lk->filename, path, pathlen); + if (!(flags & LOCK_NO_DEREF)) + resolve_symlink(&lk->filename); + strbuf_addstr(&lk->filename, LOCK_SUFFIX); + lk->fd = open(lk->filename.buf, O_RDWR | O_CREAT | O_EXCL, 0666); + if (lk->fd < 0) { + strbuf_reset(&lk->filename); return -1; } - strcpy(lk->filename, path); - if (!(flags & LOCK_NODEREF)) - resolve_symlink(lk->filename, max_path_len); - strcat(lk->filename, ".lock"); - lk->fd = open(lk->filename, O_RDWR | O_CREAT | O_EXCL, 0666); - if (0 <= lk->fd) { - if (!lock_file_list) { - sigchain_push_common(remove_lock_file_on_signal); - atexit(remove_lock_file); - } - lk->owner = getpid(); - if (!lk->on_list) { - lk->next = lock_file_list; - lock_file_list = lk; - lk->on_list = 1; - } - if (adjust_shared_perm(lk->filename)) { - int save_errno = errno; - error("cannot fix permission bits on %s", - lk->filename); - errno = save_errno; - return -1; - } + lk->owner = getpid(); + lk->active = 1; + if (adjust_shared_perm(lk->filename.buf)) { + int save_errno = errno; + error("cannot fix permission bits on %s", lk->filename.buf); + rollback_lock_file(lk); + errno = save_errno; + return -1; } - else - lk->filename[0] = 0; return lk->fd; } @@ -175,17 +162,7 @@ void unable_to_lock_message(const char *path, int err, struct strbuf *buf) absolute_path(path), strerror(err)); } -int unable_to_lock_error(const char *path, int err) -{ - struct strbuf buf = STRBUF_INIT; - - unable_to_lock_message(path, err, &buf); - error("%s", buf.buf); - strbuf_release(&buf); - return -1; -} - -NORETURN void unable_to_lock_index_die(const char *path, int err) +NORETURN void unable_to_lock_die(const char *path, int err) { struct strbuf buf = STRBUF_INIT; @@ -198,7 +175,7 @@ int hold_lock_file_for_update(struct lock_file *lk, const char *path, int flags) { int fd = lock_file(lk, path, flags); if (fd < 0 && (flags & LOCK_DIE_ON_ERROR)) - unable_to_lock_index_die(path, errno); + unable_to_lock_die(path, errno); return fd; } @@ -209,23 +186,30 @@ int hold_lock_file_for_append(struct lock_file *lk, const char *path, int flags) fd = lock_file(lk, path, flags); if (fd < 0) { if (flags & LOCK_DIE_ON_ERROR) - unable_to_lock_index_die(path, errno); + unable_to_lock_die(path, errno); return fd; } orig_fd = open(path, O_RDONLY); if (orig_fd < 0) { if (errno != ENOENT) { + int save_errno = errno; + if (flags & LOCK_DIE_ON_ERROR) die("cannot open '%s' for copying", path); - close(fd); - return error("cannot open '%s' for copying", path); + rollback_lock_file(lk); + error("cannot open '%s' for copying", path); + errno = save_errno; + return -1; } } else if (copy_fd(orig_fd, fd)) { + int save_errno = errno; + if (flags & LOCK_DIE_ON_ERROR) exit(128); close(orig_fd); - close(fd); + rollback_lock_file(lk); + errno = save_errno; return -1; } else { close(orig_fd); @@ -233,52 +217,116 @@ int hold_lock_file_for_append(struct lock_file *lk, const char *path, int flags) return fd; } +FILE *fdopen_lock_file(struct lock_file *lk, const char *mode) +{ + if (!lk->active) + die("BUG: fdopen_lock_file() called for unlocked object"); + if (lk->fp) + die("BUG: fdopen_lock_file() called twice for file '%s'", lk->filename.buf); + + lk->fp = fdopen(lk->fd, mode); + return lk->fp; +} + +char *get_locked_file_path(struct lock_file *lk) +{ + if (!lk->active) + die("BUG: get_locked_file_path() called for unlocked object"); + if (lk->filename.len <= LOCK_SUFFIX_LEN) + die("BUG: get_locked_file_path() called for malformed lock object"); + return xmemdupz(lk->filename.buf, lk->filename.len - LOCK_SUFFIX_LEN); +} + int close_lock_file(struct lock_file *lk) { int fd = lk->fd; + FILE *fp = lk->fp; + int err; + + if (fd < 0) + return 0; + lk->fd = -1; - return close(fd); + if (fp) { + lk->fp = NULL; + + /* + * Note: no short-circuiting here; we want to fclose() + * in any case! + */ + err = ferror(fp) | fclose(fp); + } else { + err = close(fd); + } + + if (err) { + int save_errno = errno; + rollback_lock_file(lk); + errno = save_errno; + return -1; + } + + return 0; } int reopen_lock_file(struct lock_file *lk) { if (0 <= lk->fd) die(_("BUG: reopen a lockfile that is still open")); - if (!lk->filename[0]) + if (!lk->active) die(_("BUG: reopen a lockfile that has been committed")); - lk->fd = open(lk->filename, O_WRONLY); + lk->fd = open(lk->filename.buf, O_WRONLY); return lk->fd; } -int commit_lock_file(struct lock_file *lk) +int commit_lock_file_to(struct lock_file *lk, const char *path) { - char result_file[PATH_MAX]; - size_t i; - if (lk->fd >= 0 && close_lock_file(lk)) + if (!lk->active) + die("BUG: attempt to commit unlocked object to \"%s\"", path); + + if (close_lock_file(lk)) return -1; - strcpy(result_file, lk->filename); - i = strlen(result_file) - 5; /* .lock */ - result_file[i] = 0; - if (rename(lk->filename, result_file)) + + if (rename(lk->filename.buf, path)) { + int save_errno = errno; + rollback_lock_file(lk); + errno = save_errno; return -1; - lk->filename[0] = 0; + } + + lk->active = 0; + strbuf_reset(&lk->filename); return 0; } -int hold_locked_index(struct lock_file *lk, int die_on_error) +int commit_lock_file(struct lock_file *lk) { - return hold_lock_file_for_update(lk, get_index_file(), - die_on_error - ? LOCK_DIE_ON_ERROR - : 0); + static struct strbuf result_file = STRBUF_INIT; + int err; + + if (!lk->active) + die("BUG: attempt to commit unlocked object"); + + if (lk->filename.len <= LOCK_SUFFIX_LEN || + strcmp(lk->filename.buf + lk->filename.len - LOCK_SUFFIX_LEN, LOCK_SUFFIX)) + die("BUG: lockfile filename corrupt"); + + /* remove ".lock": */ + strbuf_add(&result_file, lk->filename.buf, + lk->filename.len - LOCK_SUFFIX_LEN); + err = commit_lock_file_to(lk, result_file.buf); + strbuf_reset(&result_file); + return err; } void rollback_lock_file(struct lock_file *lk) { - if (lk->filename[0]) { - if (lk->fd >= 0) - close(lk->fd); - unlink_or_warn(lk->filename); + if (!lk->active) + return; + + if (!close_lock_file(lk)) { + unlink_or_warn(lk->filename.buf); + lk->active = 0; + strbuf_reset(&lk->filename); } - lk->filename[0] = 0; } diff --git a/lockfile.h b/lockfile.h new file mode 100644 index 0000000000..cd2ec95d30 --- /dev/null +++ b/lockfile.h @@ -0,0 +1,87 @@ +#ifndef LOCKFILE_H +#define LOCKFILE_H + +/* + * File write-locks as used by Git. + * + * For an overview of how to use the lockfile API, please see + * + * Documentation/technical/api-lockfile.txt + * + * This module keeps track of all locked files in lock_file_list for + * use at cleanup. This list and the lock_file objects that comprise + * it must be kept in self-consistent states at all time, because the + * program can be interrupted any time by a signal, in which case the + * signal handler will walk through the list attempting to clean up + * any open lock files. + * + * A lockfile is owned by the process that created it. The lock_file + * object has an "owner" field that records its owner. This field is + * used to prevent a forked process from closing a lockfile created by + * its parent. + * + * The possible states of a lock_file object are as follows: + * + * - Uninitialized. In this state the object's on_list field must be + * zero but the rest of its contents need not be initialized. As + * soon as the object is used in any way, it is irrevocably + * registered in the lock_file_list, and on_list is set. + * + * - Locked, lockfile open (after hold_lock_file_for_update(), + * hold_lock_file_for_append(), or reopen_lock_file()). In this + * state: + * - the lockfile exists + * - active is set + * - filename holds the filename of the lockfile + * - fd holds a file descriptor open for writing to the lockfile + * - fp holds a pointer to an open FILE object if and only if + * fdopen_lock_file() has been called on the object + * - owner holds the PID of the process that locked the file + * + * - Locked, lockfile closed (after successful close_lock_file()). + * Same as the previous state, except that the lockfile is closed + * and fd is -1. + * + * - Unlocked (after commit_lock_file(), commit_lock_file_to(), + * rollback_lock_file(), a failed attempt to lock, or a failed + * close_lock_file()). In this state: + * - active is unset + * - filename is empty (usually, though there are transitory + * states in which this condition doesn't hold). Client code should + * *not* rely on the filename being empty in this state. + * - fd is -1 + * - the object is left registered in the lock_file_list, and + * on_list is set. + */ + +struct lock_file { + struct lock_file *volatile next; + volatile sig_atomic_t active; + volatile int fd; + FILE *volatile fp; + volatile pid_t owner; + char on_list; + struct strbuf filename; +}; + +/* String appended to a filename to derive the lockfile name: */ +#define LOCK_SUFFIX ".lock" +#define LOCK_SUFFIX_LEN 5 + +#define LOCK_DIE_ON_ERROR 1 +#define LOCK_NO_DEREF 2 + +extern void unable_to_lock_message(const char *path, int err, + struct strbuf *buf); +extern NORETURN void unable_to_lock_die(const char *path, int err); +extern int hold_lock_file_for_update(struct lock_file *, const char *path, int); +extern int hold_lock_file_for_append(struct lock_file *, const char *path, int); +extern FILE *fdopen_lock_file(struct lock_file *, const char *mode); +extern char *get_locked_file_path(struct lock_file *); +extern int commit_lock_file_to(struct lock_file *, const char *path); +extern int commit_lock_file(struct lock_file *); +extern int reopen_lock_file(struct lock_file *); +extern int close_lock_file(struct lock_file *); +extern void rollback_lock_file(struct lock_file *); + +#endif /* LOCKFILE_H */ diff --git a/log-tree.c b/log-tree.c index cff7ac1dbd..7f0890e4ac 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 b26160c4d6..c8116e60cd 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 *); diff --git a/merge-recursive.c b/merge-recursive.c index 8ad4be897d..fdb7d0f10b 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -3,8 +3,9 @@ * Fredrik Kuivinen. * The thieves were Alex Riesen and Johannes Schindelin, in June/July 2006 */ -#include "advice.h" #include "cache.h" +#include "advice.h" +#include "lockfile.h" #include "cache-tree.h" #include "commit.h" #include "blob.h" @@ -1,4 +1,5 @@ #include "cache.h" +#include "lockfile.h" #include "commit.h" #include "run-command.h" #include "resolve-undo.h" diff --git a/mergetools/meld b/mergetools/meld index cb672a5519..83ebdfb4c3 100644 --- a/mergetools/meld +++ b/mergetools/meld @@ -18,13 +18,18 @@ merge_cmd () { check_unchanged } -# Check whether 'meld --output <file>' is supported +# Check whether we should use 'meld --output <file>' check_meld_for_output_version () { meld_path="$(git config mergetool.meld.path)" meld_path="${meld_path:-meld}" - if "$meld_path" --help 2>&1 | grep -e --output >/dev/null + if meld_has_output_option=$(git config --bool mergetool.meld.hasOutput) then + : use configured value + elif "$meld_path" --help 2>&1 | + grep -e '--output=' -e '\[OPTION\.\.\.\]' >/dev/null + then + : old ones mention --output and new ones just say OPTION... meld_has_output_option=true else meld_has_output_option=false diff --git a/notes-merge.c b/notes-merge.c index fd5fae255d..7eb9d7a010 100644 --- a/notes-merge.c +++ b/notes-merge.c @@ -549,7 +549,7 @@ int notes_merge(struct notes_merge_options *o, o->local_ref, o->remote_ref); /* Dereference o->local_ref into local_sha1 */ - if (read_ref_full(o->local_ref, local_sha1, 0, NULL)) + if (read_ref_full(o->local_ref, 0, local_sha1, NULL)) die("Failed to resolve local notes ref '%s'", o->local_ref); else if (!check_refname_format(o->local_ref, 0) && is_null_sha1(local_sha1)) @@ -74,17 +74,10 @@ void setup_pager(void) pager_process.use_shell = 1; pager_process.argv = pager_argv; pager_process.in = -1; - if (!getenv("LESS") || !getenv("LV")) { - static const char *env[3]; - int i = 0; - - if (!getenv("LESS")) - env[i++] = "LESS=FRX"; - if (!getenv("LV")) - env[i++] = "LV=-c"; - env[i] = NULL; - pager_process.env = env; - } + if (!getenv("LESS")) + argv_array_push(&pager_process.env_array, "LESS=FRX"); + if (!getenv("LV")) + argv_array_push(&pager_process.env_array, "LV=-c"); if (start_command(&pager_process)) return; @@ -73,10 +73,9 @@ static int git_pretty_formats_config(const char *var, const char *value, void *c if (git_config_string(&fmt, var, value)) return -1; - if (starts_with(fmt, "format:") || starts_with(fmt, "tformat:")) { - commit_format->is_tformat = fmt[0] == 't'; - fmt = strchr(fmt, ':') + 1; - } else if (strchr(fmt, '%')) + if (skip_prefix(fmt, "format:", &fmt)) + commit_format->is_tformat = 0; + else if (skip_prefix(fmt, "tformat:", &fmt) || strchr(fmt, '%')) commit_format->is_tformat = 1; else commit_format->is_alias = 1; @@ -157,12 +156,12 @@ void get_commit_format(const char *arg, struct rev_info *rev) rev->commit_format = CMIT_FMT_DEFAULT; return; } - if (starts_with(arg, "format:") || starts_with(arg, "tformat:")) { - save_user_format(rev, strchr(arg, ':') + 1, arg[0] == 't'); + if (skip_prefix(arg, "format:", &arg)) { + save_user_format(rev, arg, 0); return; } - if (!*arg || strchr(arg, '%')) { + if (!*arg || skip_prefix(arg, "tformat:", &arg) || strchr(arg, '%')) { save_user_format(rev, arg, 1); return; } @@ -809,18 +808,19 @@ static void parse_commit_header(struct format_commit_context *context) int i; for (i = 0; msg[i]; i++) { + const char *name; int eol; for (eol = i; msg[eol] && msg[eol] != '\n'; eol++) ; /* do nothing */ if (i == eol) { break; - } else if (starts_with(msg + i, "author ")) { - context->author.off = i + 7; - context->author.len = eol - i - 7; - } else if (starts_with(msg + i, "committer ")) { - context->committer.off = i + 10; - context->committer.len = eol - i - 10; + } else if (skip_prefix(msg + i, "author ", &name)) { + context->author.off = name - msg; + context->author.len = msg + eol - name; + } else if (skip_prefix(msg + i, "committer ", &name)) { + context->committer.off = name - msg; + context->committer.len = msg + eol - name; } i = eol; } @@ -951,6 +951,8 @@ static size_t parse_color(struct strbuf *sb, /* in UTF-8 */ const char *placeholder, struct format_commit_context *c) { + const char *rest = placeholder; + if (placeholder[1] == '(') { const char *begin = placeholder + 2; const char *end = strchr(begin, ')'); @@ -958,31 +960,24 @@ static size_t parse_color(struct strbuf *sb, /* in UTF-8 */ if (!end) return 0; - if (starts_with(begin, "auto,")) { + if (skip_prefix(begin, "auto,", &begin)) { if (!want_color(c->pretty_ctx->color)) return end - placeholder + 1; - begin += 5; } - 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; } - if (starts_with(placeholder + 1, "red")) { + if (skip_prefix(placeholder + 1, "red", &rest)) strbuf_addstr(sb, GIT_COLOR_RED); - return 4; - } else if (starts_with(placeholder + 1, "green")) { + else if (skip_prefix(placeholder + 1, "green", &rest)) strbuf_addstr(sb, GIT_COLOR_GREEN); - return 6; - } else if (starts_with(placeholder + 1, "blue")) { + else if (skip_prefix(placeholder + 1, "blue", &rest)) strbuf_addstr(sb, GIT_COLOR_BLUE); - return 5; - } else if (starts_with(placeholder + 1, "reset")) { + else if (skip_prefix(placeholder + 1, "reset", &rest)) strbuf_addstr(sb, GIT_COLOR_RESET); - return 6; - } else - return 0; + return rest - placeholder; } static size_t parse_padding_placeholder(struct strbuf *sb, @@ -1522,7 +1517,7 @@ static void pp_header(struct pretty_print_context *pp, int parents_shown = 0; for (;;) { - const char *line = *msg_p; + const char *name, *line = *msg_p; int linelen = get_one_line(*msg_p); if (!linelen) @@ -1557,14 +1552,14 @@ static void pp_header(struct pretty_print_context *pp, * FULL shows both authors but not dates. * FULLER shows both authors and dates. */ - if (starts_with(line, "author ")) { + if (skip_prefix(line, "author ", &name)) { strbuf_grow(sb, linelen + 80); - pp_user_info(pp, "Author", sb, line + 7, encoding); + pp_user_info(pp, "Author", sb, name, encoding); } - if (starts_with(line, "committer ") && + if (skip_prefix(line, "committer ", &name) && (pp->fmt == CMIT_FMT_FULL || pp->fmt == CMIT_FMT_FULLER)) { strbuf_grow(sb, linelen + 80); - pp_user_info(pp, "Commit", sb, line + 10, encoding); + pp_user_info(pp, "Commit", sb, name, encoding); } } } diff --git a/read-cache.c b/read-cache.c index 2fc1182f22..8f3e9eb314 100644 --- a/read-cache.c +++ b/read-cache.c @@ -5,6 +5,7 @@ */ #define NO_THE_INDEX_COMPATIBILITY_MACROS #include "cache.h" +#include "lockfile.h" #include "cache-tree.h" #include "refs.h" #include "dir.h" @@ -1367,6 +1368,14 @@ static int read_index_extension(struct index_state *istate, return 0; } +int hold_locked_index(struct lock_file *lk, int die_on_error) +{ + return hold_lock_file_for_update(lk, get_index_file(), + die_on_error + ? LOCK_DIE_ON_ERROR + : 0); +} + int read_index(struct index_state *istate) { return read_index_from(istate, get_index_file()); @@ -2041,16 +2050,10 @@ void set_alternate_index_output(const char *name) static int commit_locked_index(struct lock_file *lk) { - if (alternate_index_output) { - if (lk->fd >= 0 && close_lock_file(lk)) - return -1; - if (rename(lk->filename, alternate_index_output)) - return -1; - lk->filename[0] = 0; - return 0; - } else { + if (alternate_index_output) + return commit_lock_file_to(lk, alternate_index_output); + else return commit_lock_file(lk); - } } static int do_write_locked_index(struct index_state *istate, struct lock_file *lock, diff --git a/reflog-walk.c b/reflog-walk.c index 0e5174b605..222de762eb 100644 --- a/reflog-walk.c +++ b/reflog-walk.c @@ -48,7 +48,8 @@ static struct complete_reflogs *read_complete_reflog(const char *ref) unsigned char sha1[20]; const char *name; void *name_to_free; - name = name_to_free = resolve_refdup(ref, sha1, 1, NULL); + name = name_to_free = resolve_refdup(ref, RESOLVE_REF_READING, + sha1, NULL); if (name) { for_each_reflog_ent(name, read_one_reflog, reflogs); free(name_to_free); @@ -174,7 +175,7 @@ int add_reflog_for_walk(struct reflog_walk_info *info, if (*branch == '\0') { unsigned char sha1[20]; free(branch); - branch = resolve_refdup("HEAD", sha1, 0, NULL); + branch = resolve_refdup("HEAD", 0, sha1, NULL); if (!branch) die ("No current branch"); @@ -1,4 +1,5 @@ #include "cache.h" +#include "lockfile.h" #include "refs.h" #include "object.h" #include "tag.h" @@ -69,17 +70,10 @@ static int check_refname_component(const char *refname, int flags) out: if (cp == refname) return 0; /* Component has zero length. */ - if (refname[0] == '.') { - if (!(flags & REFNAME_DOT_COMPONENT)) - return -1; /* Component starts with '.'. */ - /* - * Even if leading dots are allowed, don't allow "." - * as a component (".." is prevented by a rule above). - */ - if (refname[1] == '\0') - return -1; /* Component equals ".". */ - } - if (cp - refname >= 5 && !memcmp(cp - 5, ".lock", 5)) + if (refname[0] == '.') + return -1; /* Component starts with '.'. */ + if (cp - refname >= LOCK_SUFFIX_LEN && + !memcmp(cp - LOCK_SUFFIX_LEN, LOCK_SUFFIX, LOCK_SUFFIX_LEN)) return -1; /* Refname ends with ".lock". */ return cp - refname; } @@ -193,8 +187,8 @@ struct ref_dir { /* * Bit values for ref_entry::flag. REF_ISSYMREF=0x01, - * REF_ISPACKED=0x02, and REF_ISBROKEN=0x04 are public values; see - * refs.h. + * REF_ISPACKED=0x02, REF_ISBROKEN=0x04 and REF_BAD_NAME=0x08 are + * public values; see refs.h. */ /* @@ -202,16 +196,16 @@ struct ref_dir { * the correct peeled value for the reference, which might be * null_sha1 if the reference is not a tag or if it is broken. */ -#define REF_KNOWS_PEELED 0x08 +#define REF_KNOWS_PEELED 0x10 /* ref_entry represents a directory of references */ -#define REF_DIR 0x10 +#define REF_DIR 0x20 /* * Entry has not yet been read from disk (used only for REF_DIR * entries representing loose references) */ -#define REF_INCOMPLETE 0x20 +#define REF_INCOMPLETE 0x40 /* * A ref_entry represents either a reference or a "subdirectory" of @@ -280,6 +274,39 @@ static struct ref_dir *get_ref_dir(struct ref_entry *entry) return dir; } +/* + * Check if a refname is safe. + * For refs that start with "refs/" we consider it safe as long they do + * not try to resolve to outside of refs/. + * + * For all other refs we only consider them safe iff they only contain + * upper case characters and '_' (like "HEAD" AND "MERGE_HEAD", and not like + * "config"). + */ +static int refname_is_safe(const char *refname) +{ + if (starts_with(refname, "refs/")) { + char *buf; + int result; + + buf = xmalloc(strlen(refname) + 1); + /* + * Does the refname try to escape refs/? + * For example: refs/foo/../bar is safe but refs/foo/../../bar + * is not. + */ + result = !normalize_path_copy(buf, refname + strlen("refs/")); + free(buf); + return result; + } + while (*refname) { + if (!isupper(*refname) && *refname != '_') + return 0; + refname++; + } + return 1; +} + static struct ref_entry *create_ref_entry(const char *refname, const unsigned char *sha1, int flag, int check_name) @@ -288,8 +315,10 @@ static struct ref_entry *create_ref_entry(const char *refname, struct ref_entry *ref; if (check_name && - check_refname_format(refname, REFNAME_ALLOW_ONELEVEL|REFNAME_DOT_COMPONENT)) + check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) die("Reference has invalid format: '%s'", refname); + if (!check_name && !refname_is_safe(refname)) + die("Reference has invalid name: '%s'", refname); len = strlen(refname) + 1; ref = xmalloc(sizeof(struct ref_entry) + len); hashcpy(ref->u.value.sha1, sha1); @@ -785,13 +814,13 @@ static void prime_ref_dir(struct ref_dir *dir) } } -static int entry_matches(struct ref_entry *entry, const char *refname) +static int entry_matches(struct ref_entry *entry, const struct string_list *list) { - return refname && !strcmp(entry->name, refname); + return list && string_list_has_string(list, entry->name); } struct nonmatching_ref_data { - const char *skip; + const struct string_list *skip; struct ref_entry *found; }; @@ -815,16 +844,19 @@ static void report_refname_conflict(struct ref_entry *entry, /* * Return true iff a reference named refname could be created without * conflicting with the name of an existing reference in dir. If - * oldrefname is non-NULL, ignore potential conflicts with oldrefname - * (e.g., because oldrefname is scheduled for deletion in the same + * skip is non-NULL, ignore potential conflicts with refs in skip + * (e.g., because they are scheduled for deletion in the same * operation). * * Two reference names conflict if one of them exactly matches the * leading components of the other; e.g., "foo/bar" conflicts with * both "foo" and with "foo/bar/baz" but not with "foo/bar" or * "foo/barbados". + * + * skip must be sorted. */ -static int is_refname_available(const char *refname, const char *oldrefname, +static int is_refname_available(const char *refname, + const struct string_list *skip, struct ref_dir *dir) { const char *slash; @@ -838,12 +870,12 @@ static int is_refname_available(const char *refname, const char *oldrefname, * looking for a conflict with a leaf entry. * * If we find one, we still must make sure it is - * not "oldrefname". + * not in "skip". */ pos = search_ref_dir(dir, refname, slash - refname); if (pos >= 0) { struct ref_entry *entry = dir->entries[pos]; - if (entry_matches(entry, oldrefname)) + if (entry_matches(entry, skip)) return 1; report_refname_conflict(entry, refname); return 0; @@ -876,13 +908,13 @@ static int is_refname_available(const char *refname, const char *oldrefname, /* * We found a directory named "refname". It is a * problem iff it contains any ref that is not - * "oldrefname". + * in "skip". */ struct ref_entry *entry = dir->entries[pos]; struct ref_dir *dir = get_ref_dir(entry); struct nonmatching_ref_data data; - data.skip = oldrefname; + data.skip = skip; sort_ref_dir(dir); if (!do_for_each_entry_in_dir(dir, 0, nonmatching_ref_fn, &data)) return 1; @@ -1114,7 +1146,13 @@ static void read_packed_refs(FILE *f, struct ref_dir *dir) refname = parse_ref_line(refline, sha1); if (refname) { - last = create_ref_entry(refname, sha1, REF_ISPACKED, 1); + int flag = REF_ISPACKED; + + if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) { + hashclr(sha1); + flag |= REF_BAD_NAME | REF_ISBROKEN; + } + last = create_ref_entry(refname, sha1, flag, 0); if (peeled == PEELED_FULLY || (peeled == PEELED_TAGS && starts_with(refname, "refs/tags/"))) last->flag |= REF_KNOWS_PEELED; @@ -1246,12 +1284,19 @@ static void read_loose_refs(const char *dirname, struct ref_dir *dir) hashclr(sha1); flag |= REF_ISBROKEN; } - } else if (read_ref_full(refname.buf, sha1, 1, &flag)) { + } else if (read_ref_full(refname.buf, + RESOLVE_REF_READING, + sha1, &flag)) { hashclr(sha1); flag |= REF_ISBROKEN; } + if (check_refname_format(refname.buf, + REFNAME_ALLOW_ONELEVEL)) { + hashclr(sha1); + flag |= REF_BAD_NAME | REF_ISBROKEN; + } add_entry_to_dir(dir, - create_ref_entry(refname.buf, sha1, flag, 1)); + create_ref_entry(refname.buf, sha1, flag, 0)); } strbuf_setlen(&refname, dirnamelen); } @@ -1370,10 +1415,10 @@ static struct ref_entry *get_packed_ref(const char *refname) * A loose ref file doesn't exist; check for a packed ref. The * options are forwarded from resolve_safe_unsafe(). */ -static const char *handle_missing_loose_ref(const char *refname, - unsigned char *sha1, - int reading, - int *flag) +static int resolve_missing_loose_ref(const char *refname, + int resolve_flags, + unsigned char *sha1, + int *flags) { struct ref_entry *entry; @@ -1384,35 +1429,51 @@ static const char *handle_missing_loose_ref(const char *refname, entry = get_packed_ref(refname); if (entry) { hashcpy(sha1, entry->u.value.sha1); - if (flag) - *flag |= REF_ISPACKED; - return refname; + if (flags) + *flags |= REF_ISPACKED; + return 0; } /* The reference is not a packed reference, either. */ - if (reading) { - return NULL; + if (resolve_flags & RESOLVE_REF_READING) { + errno = ENOENT; + return -1; } else { hashclr(sha1); - return refname; + return 0; } } /* This function needs to return a meaningful errno on failure */ -const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int reading, int *flag) +const char *resolve_ref_unsafe(const char *refname, int resolve_flags, unsigned char *sha1, int *flags) { int depth = MAXDEPTH; ssize_t len; char buffer[256]; static char refname_buffer[256]; + int bad_name = 0; - if (flag) - *flag = 0; + if (flags) + *flags = 0; if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) { - errno = EINVAL; - return NULL; - } + if (flags) + *flags |= REF_BAD_NAME; + if (!(resolve_flags & RESOLVE_REF_ALLOW_BAD_NAME) || + !refname_is_safe(refname)) { + errno = EINVAL; + return NULL; + } + /* + * dwim_ref() uses REF_ISBROKEN to distinguish between + * missing refs and refs that were present but invalid, + * to complain about the latter to stderr. + * + * We don't know whether the ref exists, so don't set + * REF_ISBROKEN yet. + */ + bad_name = 1; + } for (;;) { char path[PATH_MAX]; struct stat st; @@ -1437,11 +1498,17 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea */ stat_ref: if (lstat(path, &st) < 0) { - if (errno == ENOENT) - return handle_missing_loose_ref(refname, sha1, - reading, flag); - else + if (errno != ENOENT) + return NULL; + if (resolve_missing_loose_ref(refname, resolve_flags, + sha1, flags)) return NULL; + if (bad_name) { + hashclr(sha1); + if (flags) + *flags |= REF_ISBROKEN; + } + return refname; } /* Follow "normalized" - ie "refs/.." symlinks by hand */ @@ -1459,8 +1526,12 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea !check_refname_format(buffer, 0)) { strcpy(refname_buffer, buffer); refname = refname_buffer; - if (flag) - *flag |= REF_ISSYMREF; + if (flags) + *flags |= REF_ISSYMREF; + if (resolve_flags & RESOLVE_REF_NO_RECURSE) { + hashclr(sha1); + return refname; + } continue; } } @@ -1505,31 +1576,45 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea */ if (get_sha1_hex(buffer, sha1) || (buffer[40] != '\0' && !isspace(buffer[40]))) { - if (flag) - *flag |= REF_ISBROKEN; + if (flags) + *flags |= REF_ISBROKEN; errno = EINVAL; return NULL; } + if (bad_name) { + hashclr(sha1); + if (flags) + *flags |= REF_ISBROKEN; + } return refname; } - if (flag) - *flag |= REF_ISSYMREF; + if (flags) + *flags |= REF_ISSYMREF; buf = buffer + 4; while (isspace(*buf)) buf++; + refname = strcpy(refname_buffer, buf); + if (resolve_flags & RESOLVE_REF_NO_RECURSE) { + hashclr(sha1); + return refname; + } if (check_refname_format(buf, REFNAME_ALLOW_ONELEVEL)) { - if (flag) - *flag |= REF_ISBROKEN; - errno = EINVAL; - return NULL; + if (flags) + *flags |= REF_ISBROKEN; + + if (!(resolve_flags & RESOLVE_REF_ALLOW_BAD_NAME) || + !refname_is_safe(buf)) { + errno = EINVAL; + return NULL; + } + bad_name = 1; } - refname = strcpy(refname_buffer, buf); } } -char *resolve_refdup(const char *ref, unsigned char *sha1, int reading, int *flag) +char *resolve_refdup(const char *ref, int resolve_flags, unsigned char *sha1, int *flags) { - const char *ret = resolve_ref_unsafe(ref, sha1, reading, flag); + const char *ret = resolve_ref_unsafe(ref, resolve_flags, sha1, flags); return ret ? xstrdup(ret) : NULL; } @@ -1540,22 +1625,22 @@ struct ref_filter { void *cb_data; }; -int read_ref_full(const char *refname, unsigned char *sha1, int reading, int *flags) +int read_ref_full(const char *refname, int resolve_flags, unsigned char *sha1, int *flags) { - if (resolve_ref_unsafe(refname, sha1, reading, flags)) + if (resolve_ref_unsafe(refname, resolve_flags, sha1, flags)) return 0; return -1; } int read_ref(const char *refname, unsigned char *sha1) { - return read_ref_full(refname, sha1, 1, NULL); + return read_ref_full(refname, RESOLVE_REF_READING, sha1, NULL); } int ref_exists(const char *refname) { unsigned char sha1[20]; - return !!resolve_ref_unsafe(refname, sha1, 1, NULL); + return !!resolve_ref_unsafe(refname, RESOLVE_REF_READING, sha1, NULL); } static int filter_refs(const char *refname, const unsigned char *sha1, int flags, @@ -1668,7 +1753,7 @@ int peel_ref(const char *refname, unsigned char *sha1) return 0; } - if (read_ref_full(refname, base, 1, &flag)) + if (read_ref_full(refname, RESOLVE_REF_READING, base, &flag)) return -1; /* @@ -1709,7 +1794,7 @@ static int warn_if_dangling_symref(const char *refname, const unsigned char *sha if (!(flags & REF_ISSYMREF)) return 0; - resolves_to = resolve_ref_unsafe(refname, junk, 0, NULL); + resolves_to = resolve_ref_unsafe(refname, 0, junk, NULL); if (!resolves_to || (d->refname ? strcmp(resolves_to, d->refname) @@ -1834,7 +1919,7 @@ static int do_head_ref(const char *submodule, each_ref_fn fn, void *cb_data) return 0; } - if (!read_ref_full("HEAD", sha1, 1, &flag)) + if (!read_ref_full("HEAD", RESOLVE_REF_READING, sha1, &flag)) return fn("HEAD", sha1, flag, cb_data); return 0; @@ -1914,7 +1999,7 @@ int head_ref_namespaced(each_ref_fn fn, void *cb_data) int flag; strbuf_addf(&buf, "%sHEAD", get_git_namespace()); - if (!read_ref_full(buf.buf, sha1, 1, &flag)) + if (!read_ref_full(buf.buf, RESOLVE_REF_READING, sha1, &flag)) ret = fn(buf.buf, sha1, flag, cb_data); strbuf_release(&buf); @@ -2009,7 +2094,9 @@ int refname_match(const char *abbrev_name, const char *full_name) static struct ref_lock *verify_lock(struct ref_lock *lock, const unsigned char *old_sha1, int mustexist) { - if (read_ref_full(lock->ref_name, lock->old_sha1, mustexist, NULL)) { + if (read_ref_full(lock->ref_name, + mustexist ? RESOLVE_REF_READING : 0, + lock->old_sha1, NULL)) { int save_errno = errno; error("Can't verify ref %s", lock->ref_name); unlock_ref(lock); @@ -2082,7 +2169,8 @@ int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref) this_result = refs_found ? sha1_from_ref : sha1; mksnpath(fullref, sizeof(fullref), *p, len, str); - r = resolve_ref_unsafe(fullref, this_result, 1, &flag); + r = resolve_ref_unsafe(fullref, RESOLVE_REF_READING, + this_result, &flag); if (r) { if (!refs_found++) *ref = xstrdup(r); @@ -2111,7 +2199,8 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log) const char *ref, *it; mksnpath(path, sizeof(path), *p, len, str); - ref = resolve_ref_unsafe(path, hash, 1, NULL); + ref = resolve_ref_unsafe(path, RESOLVE_REF_READING, + hash, NULL); if (!ref) continue; if (reflog_exists(path)) @@ -2132,11 +2221,12 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log) } /* - * Locks a "refs/" ref returning the lock on success and NULL on failure. + * Locks a ref returning the lock on success and NULL on failure. * On failure errno is set to something meaningful. */ static struct ref_lock *lock_ref_sha1_basic(const char *refname, const unsigned char *old_sha1, + const struct string_list *skip, int flags, int *type_p) { char *ref_file; @@ -2145,13 +2235,23 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname, int last_errno = 0; int type, lflags; int mustexist = (old_sha1 && !is_null_sha1(old_sha1)); + int resolve_flags = 0; int missing = 0; int attempts_remaining = 3; lock = xcalloc(1, sizeof(struct ref_lock)); lock->lock_fd = -1; - refname = resolve_ref_unsafe(refname, lock->old_sha1, mustexist, &type); + if (mustexist) + resolve_flags |= RESOLVE_REF_READING; + if (flags & REF_DELETING) { + resolve_flags |= RESOLVE_REF_ALLOW_BAD_NAME; + if (flags & REF_NODEREF) + resolve_flags |= RESOLVE_REF_NO_RECURSE; + } + + refname = resolve_ref_unsafe(refname, resolve_flags, + lock->old_sha1, &type); if (!refname && errno == EISDIR) { /* we are trying to lock foo but we used to * have foo/bar which now does not exist; @@ -2164,7 +2264,8 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname, error("there are still refs under '%s'", orig_refname); goto error_return; } - refname = resolve_ref_unsafe(orig_refname, lock->old_sha1, mustexist, &type); + refname = resolve_ref_unsafe(orig_refname, resolve_flags, + lock->old_sha1, &type); } if (type_p) *type_p = type; @@ -2181,7 +2282,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname, * name is a proper prefix of our refname. */ if (missing && - !is_refname_available(refname, NULL, get_packed_refs(&ref_cache))) { + !is_refname_available(refname, skip, get_packed_refs(&ref_cache))) { last_errno = ENOTDIR; goto error_return; } @@ -2191,7 +2292,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname, lflags = 0; if (flags & REF_NODEREF) { refname = orig_refname; - lflags |= LOCK_NODEREF; + lflags |= LOCK_NO_DEREF; } lock->ref_name = xstrdup(refname); lock->orig_ref_name = xstrdup(orig_refname); @@ -2225,7 +2326,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname, */ goto retry; else - unable_to_lock_index_die(ref_file, errno); + unable_to_lock_die(ref_file, errno); } return old_sha1 ? verify_lock(lock, old_sha1, mustexist) : lock; @@ -2239,9 +2340,7 @@ struct ref_lock *lock_any_ref_for_update(const char *refname, const unsigned char *old_sha1, int flags, int *type_p) { - if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) - return NULL; - return lock_ref_sha1_basic(refname, old_sha1, flags, type_p); + return lock_ref_sha1_basic(refname, old_sha1, NULL, flags, type_p); } /* @@ -2307,16 +2406,13 @@ int commit_packed_refs(void) if (!packed_ref_cache->lock) die("internal error: packed-refs not locked"); - out = fdopen(packed_ref_cache->lock->fd, "w"); + out = fdopen_lock_file(packed_ref_cache->lock, "w"); if (!out) die_errno("unable to fdopen packed-refs descriptor"); fprintf_or_die(out, "%s", PACKED_REFS_HEADER); do_for_each_entry_in_dir(get_packed_ref_dir(packed_ref_cache), 0, write_packed_entry_fn, out); - if (fclose(out)) - die_errno("write error"); - packed_ref_cache->lock->fd = -1; if (commit_lock_file(packed_ref_cache->lock)) { save_errno = errno; @@ -2446,8 +2542,8 @@ static void prune_ref(struct ref_to_prune *r) transaction = ref_transaction_begin(&err); if (!transaction || ref_transaction_delete(transaction, r->name, r->sha1, - REF_ISPRUNING, 1, &err) || - ref_transaction_commit(transaction, NULL, &err)) { + REF_ISPRUNING, 1, NULL, &err) || + ref_transaction_commit(transaction, &err)) { ref_transaction_free(transaction); error("%s", err.buf); strbuf_release(&err); @@ -2511,7 +2607,7 @@ static int curate_packed_ref_fn(struct ref_entry *entry, void *cb_data) unsigned char sha1[20]; int flags; - if (read_ref_full(entry->name, sha1, 0, &flags)) + if (read_ref_full(entry->name, 0, sha1, &flags)) /* We should at least have found the packed ref. */ die("Internal error"); if ((flags & REF_ISSYMREF) || !(flags & REF_ISPACKED)) { @@ -2550,6 +2646,8 @@ int repack_without_refs(const char **refnames, int n, struct strbuf *err) struct string_list_item *ref_to_delete; int i, ret, removed = 0; + assert(err); + /* Look for a packed ref */ for (i = 0; i < n; i++) if (get_packed_ref(refnames[i])) @@ -2560,13 +2658,8 @@ int repack_without_refs(const char **refnames, int n, struct strbuf *err) return 0; /* no refname exists in packed refs */ if (lock_packed_refs(0)) { - if (err) { - unable_to_lock_message(git_path("packed-refs"), errno, - err); - return -1; - } - unable_to_lock_error(git_path("packed-refs"), errno); - return error("cannot delete '%s' from packed refs", refnames[i]); + unable_to_lock_message(git_path("packed-refs"), errno, err); + return -1; } packed = get_packed_refs(&ref_cache); @@ -2592,22 +2685,25 @@ int repack_without_refs(const char **refnames, int n, struct strbuf *err) /* Write what remains */ ret = commit_packed_refs(); - if (ret && err) + if (ret) strbuf_addf(err, "unable to overwrite old ref-pack file: %s", strerror(errno)); return ret; } -static int delete_ref_loose(struct ref_lock *lock, int flag) +static int delete_ref_loose(struct ref_lock *lock, int flag, struct strbuf *err) { - if (!(flag & REF_ISPACKED) || flag & REF_ISSYMREF) { - /* loose */ - int err, i = strlen(lock->lk->filename) - 5; /* .lock */ + assert(err); - lock->lk->filename[i] = 0; - err = unlink_or_warn(lock->lk->filename); - lock->lk->filename[i] = '.'; - if (err && errno != ENOENT) + if (!(flag & REF_ISPACKED) || flag & REF_ISSYMREF) { + /* + * loose. The loose file name is the same as the + * lockfile name, minus ".lock": + */ + char *loose_filename = get_locked_file_path(lock->lk); + int res = unlink_or_msg(loose_filename, err); + free(loose_filename); + if (res) return 1; } return 0; @@ -2621,8 +2717,8 @@ int delete_ref(const char *refname, const unsigned char *sha1, int delopt) transaction = ref_transaction_begin(&err); if (!transaction || ref_transaction_delete(transaction, refname, sha1, delopt, - sha1 && !is_null_sha1(sha1), &err) || - ref_transaction_commit(transaction, NULL, &err)) { + sha1 && !is_null_sha1(sha1), NULL, &err) || + ref_transaction_commit(transaction, &err)) { error("%s", err.buf); ref_transaction_free(transaction); strbuf_release(&err); @@ -2687,6 +2783,21 @@ static int rename_tmp_log(const char *newrefname) return 0; } +static int rename_ref_available(const char *oldname, const char *newname) +{ + struct string_list skip = STRING_LIST_INIT_NODUP; + int ret; + + string_list_insert(&skip, oldname); + ret = is_refname_available(newname, &skip, get_packed_refs(&ref_cache)) + && is_refname_available(newname, &skip, get_loose_refs(&ref_cache)); + string_list_clear(&skip, 0); + return ret; +} + +static int write_ref_sha1(struct ref_lock *lock, const unsigned char *sha1, + const char *logmsg); + int rename_ref(const char *oldrefname, const char *newrefname, const char *logmsg) { unsigned char sha1[20], orig_sha1[20]; @@ -2699,17 +2810,15 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms if (log && S_ISLNK(loginfo.st_mode)) return error("reflog for %s is a symlink", oldrefname); - symref = resolve_ref_unsafe(oldrefname, orig_sha1, 1, &flag); + symref = resolve_ref_unsafe(oldrefname, RESOLVE_REF_READING, + orig_sha1, &flag); if (flag & REF_ISSYMREF) return error("refname %s is a symbolic ref, renaming it is not supported", oldrefname); if (!symref) return error("refname %s not found", oldrefname); - if (!is_refname_available(newrefname, oldrefname, get_packed_refs(&ref_cache))) - return 1; - - if (!is_refname_available(newrefname, oldrefname, get_loose_refs(&ref_cache))) + if (!rename_ref_available(oldrefname, newrefname)) return 1; if (log && rename(git_path("logs/%s", oldrefname), git_path(TMP_RENAMED_LOG))) @@ -2721,7 +2830,7 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms goto rollback; } - if (!read_ref_full(newrefname, sha1, 1, &flag) && + if (!read_ref_full(newrefname, RESOLVE_REF_READING, sha1, NULL) && delete_ref(newrefname, sha1, REF_NODEREF)) { if (errno==EISDIR) { if (remove_empty_directories(git_path("%s", newrefname))) { @@ -2739,7 +2848,7 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms logmoved = log; - lock = lock_ref_sha1_basic(newrefname, NULL, 0, NULL); + lock = lock_ref_sha1_basic(newrefname, NULL, NULL, 0, NULL); if (!lock) { error("unable to lock %s for update", newrefname); goto rollback; @@ -2754,7 +2863,7 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms return 0; rollback: - lock = lock_ref_sha1_basic(oldrefname, NULL, 0, NULL); + lock = lock_ref_sha1_basic(oldrefname, NULL, NULL, 0, NULL); if (!lock) { error("unable to lock %s for rollback", oldrefname); goto rollbacklog; @@ -2934,8 +3043,11 @@ int is_branch(const char *refname) return !strcmp(refname, "HEAD") || starts_with(refname, "refs/heads/"); } -/* This function must return a meaningful errno */ -int write_ref_sha1(struct ref_lock *lock, +/* + * Write sha1 into the ref specified by the lock. Make sure that errno + * is sane on error. + */ +static int write_ref_sha1(struct ref_lock *lock, const unsigned char *sha1, const char *logmsg) { static char term = '\n'; @@ -2968,7 +3080,7 @@ int write_ref_sha1(struct ref_lock *lock, write_in_full(lock->lock_fd, &term, 1) != 1 || close_ref(lock) < 0) { int save_errno = errno; - error("Couldn't write %s", lock->lk->filename); + error("Couldn't write %s", lock->lk->filename.buf); unlock_ref(lock); errno = save_errno; return -1; @@ -2996,7 +3108,8 @@ int write_ref_sha1(struct ref_lock *lock, unsigned char head_sha1[20]; int head_flag; const char *head_ref; - head_ref = resolve_ref_unsafe("HEAD", head_sha1, 1, &head_flag); + head_ref = resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, + head_sha1, &head_flag); if (head_ref && (head_flag & REF_ISSYMREF) && !strcmp(head_ref, lock->ref_name)) log_ref_write("HEAD", lock->old_sha1, sha1, logmsg); @@ -3367,7 +3480,7 @@ static int do_for_each_reflog(struct strbuf *name, each_ref_fn fn, void *cb_data retval = do_for_each_reflog(name, fn, cb_data); } else { unsigned char sha1[20]; - if (read_ref_full(name->buf, sha1, 0, NULL)) + if (read_ref_full(name->buf, 0, sha1, NULL)) retval = error("bad ref for %s", name->buf); else retval = fn(name->buf, sha1, 0, cb_data); @@ -3404,6 +3517,7 @@ struct ref_update { int have_old; /* 1 if old_sha1 is valid, 0 otherwise */ struct ref_lock *lock; int type; + char *msg; const char refname[FLEX_ARRAY]; }; @@ -3436,6 +3550,8 @@ struct ref_transaction { struct ref_transaction *ref_transaction_begin(struct strbuf *err) { + assert(err); + return xcalloc(1, sizeof(struct ref_transaction)); } @@ -3446,9 +3562,10 @@ void ref_transaction_free(struct ref_transaction *transaction) if (!transaction) return; - for (i = 0; i < transaction->nr; i++) + for (i = 0; i < transaction->nr; i++) { + free(transaction->updates[i]->msg); free(transaction->updates[i]); - + } free(transaction->updates); free(transaction); } @@ -3469,57 +3586,80 @@ int ref_transaction_update(struct ref_transaction *transaction, const char *refname, const unsigned char *new_sha1, const unsigned char *old_sha1, - int flags, int have_old, + int flags, int have_old, const char *msg, struct strbuf *err) { struct ref_update *update; + assert(err); + if (transaction->state != REF_TRANSACTION_OPEN) die("BUG: update called for transaction that is not open"); if (have_old && !old_sha1) die("BUG: have_old is true but old_sha1 is NULL"); + if (!is_null_sha1(new_sha1) && + check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) { + strbuf_addf(err, "refusing to update ref with bad name %s", + refname); + return -1; + } + update = add_update(transaction, refname); hashcpy(update->new_sha1, new_sha1); update->flags = flags; update->have_old = have_old; if (have_old) hashcpy(update->old_sha1, old_sha1); + if (msg) + update->msg = xstrdup(msg); return 0; } int ref_transaction_create(struct ref_transaction *transaction, const char *refname, const unsigned char *new_sha1, - int flags, + int flags, const char *msg, struct strbuf *err) { struct ref_update *update; + assert(err); + if (transaction->state != REF_TRANSACTION_OPEN) die("BUG: create called for transaction that is not open"); if (!new_sha1 || is_null_sha1(new_sha1)) die("BUG: create ref with null new_sha1"); + if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) { + strbuf_addf(err, "refusing to create ref with bad name %s", + refname); + return -1; + } + update = add_update(transaction, refname); hashcpy(update->new_sha1, new_sha1); hashclr(update->old_sha1); update->flags = flags; update->have_old = 1; + if (msg) + update->msg = xstrdup(msg); return 0; } int ref_transaction_delete(struct ref_transaction *transaction, const char *refname, const unsigned char *old_sha1, - int flags, int have_old, + int flags, int have_old, const char *msg, struct strbuf *err) { struct ref_update *update; + assert(err); + if (transaction->state != REF_TRANSACTION_OPEN) die("BUG: delete called for transaction that is not open"); @@ -3533,6 +3673,8 @@ int ref_transaction_delete(struct ref_transaction *transaction, assert(!is_null_sha1(old_sha1)); hashcpy(update->old_sha1, old_sha1); } + if (msg) + update->msg = xstrdup(msg); return 0; } @@ -3546,8 +3688,8 @@ int update_ref(const char *action, const char *refname, t = ref_transaction_begin(&err); if (!t || ref_transaction_update(t, refname, sha1, oldval, flags, - !!oldval, &err) || - ref_transaction_commit(t, action, &err)) { + !!oldval, action, &err) || + ref_transaction_commit(t, &err)) { const char *str = "update_ref failed for ref '%s': %s"; ref_transaction_free(t); @@ -3580,26 +3722,29 @@ static int ref_update_reject_duplicates(struct ref_update **updates, int n, struct strbuf *err) { int i; + + assert(err); + for (i = 1; i < n; i++) if (!strcmp(updates[i - 1]->refname, updates[i]->refname)) { - const char *str = - "Multiple updates for ref '%s' not allowed."; - if (err) - strbuf_addf(err, str, updates[i]->refname); - + strbuf_addf(err, + "Multiple updates for ref '%s' not allowed.", + updates[i]->refname); return 1; } return 0; } int ref_transaction_commit(struct ref_transaction *transaction, - const char *msg, struct strbuf *err) + struct strbuf *err) { int ret = 0, delnum = 0, i; const char **delnames; int n = transaction->nr; struct ref_update **updates = transaction->updates; + assert(err); + if (transaction->state != REF_TRANSACTION_OPEN) die("BUG: commit called for transaction that is not open"); @@ -3613,25 +3758,31 @@ int ref_transaction_commit(struct ref_transaction *transaction, /* Copy, sort, and reject duplicate refs */ qsort(updates, n, sizeof(*updates), ref_update_compare); - ret = ref_update_reject_duplicates(updates, n, err); - if (ret) + if (ref_update_reject_duplicates(updates, n, err)) { + ret = TRANSACTION_GENERIC_ERROR; goto cleanup; + } /* Acquire all locks while verifying old values */ for (i = 0; i < n; i++) { struct ref_update *update = updates[i]; - - update->lock = lock_any_ref_for_update(update->refname, - (update->have_old ? - update->old_sha1 : - NULL), - update->flags, - &update->type); + int flags = update->flags; + + if (is_null_sha1(update->new_sha1)) + flags |= REF_DELETING; + update->lock = lock_ref_sha1_basic(update->refname, + (update->have_old ? + update->old_sha1 : + NULL), + NULL, + flags, + &update->type); if (!update->lock) { - if (err) - strbuf_addf(err, "Cannot lock the ref '%s'.", - update->refname); - ret = 1; + ret = (errno == ENOTDIR) + ? TRANSACTION_NAME_CONFLICT + : TRANSACTION_GENERIC_ERROR; + strbuf_addf(err, "Cannot lock the ref '%s'.", + update->refname); goto cleanup; } } @@ -3641,15 +3792,15 @@ int ref_transaction_commit(struct ref_transaction *transaction, struct ref_update *update = updates[i]; if (!is_null_sha1(update->new_sha1)) { - ret = write_ref_sha1(update->lock, update->new_sha1, - msg); - update->lock = NULL; /* freed by write_ref_sha1 */ - if (ret) { - if (err) - strbuf_addf(err, "Cannot update the ref '%s'.", - update->refname); + if (write_ref_sha1(update->lock, update->new_sha1, + update->msg)) { + update->lock = NULL; /* freed by write_ref_sha1 */ + strbuf_addf(err, "Cannot update the ref '%s'.", + update->refname); + ret = TRANSACTION_GENERIC_ERROR; goto cleanup; } + update->lock = NULL; /* freed by write_ref_sha1 */ } } @@ -3658,13 +3809,20 @@ int ref_transaction_commit(struct ref_transaction *transaction, struct ref_update *update = updates[i]; if (update->lock) { - ret |= delete_ref_loose(update->lock, update->type); + if (delete_ref_loose(update->lock, update->type, err)) { + ret = TRANSACTION_GENERIC_ERROR; + goto cleanup; + } + if (!(update->flags & REF_ISPRUNING)) delnames[delnum++] = update->lock->ref_name; } } - ret |= repack_without_refs(delnames, delnum, err); + if (repack_without_refs(delnames, delnum, err)) { + ret = TRANSACTION_GENERIC_ERROR; + goto cleanup; + } for (i = 0; i < delnum; i++) unlink_or_warn(git_path("logs/%s", delnames[i])); clear_loose_ref_cache(&ref_cache); @@ -56,12 +56,20 @@ struct ref_transaction; /* * Reference cannot be resolved to an object name: dangling symbolic - * reference (directly or indirectly), corrupt reference file, or - * symbolic reference refers to ill-formatted reference name. + * reference (directly or indirectly), corrupt reference file, + * reference exists but name is bad, or symbolic reference refers to + * ill-formatted reference name. */ #define REF_ISBROKEN 0x04 /* + * Reference name is not well formed. + * + * See git-check-ref-format(1) for the definition of well formed ref names. + */ +#define REF_BAD_NAME 0x08 + +/* * The signature for the callback function for the for_each_*() * functions below. The memory pointed to by the refname and sha1 * arguments is only guaranteed to be valid for the duration of a @@ -177,10 +185,12 @@ extern int peel_ref(const char *refname, unsigned char *sha1); * ref_transaction_create(), etc. * REF_NODEREF: act on the ref directly, instead of dereferencing * symbolic references. + * REF_DELETING: tolerate broken refs * * Flags >= 0x100 are reserved for internal use. */ #define REF_NODEREF 0x01 +#define REF_DELETING 0x02 /* * This function sets errno to something meaningful on failure. */ @@ -197,9 +207,6 @@ extern int commit_ref(struct ref_lock *lock); /** Release any lock taken but not written. **/ extern void unlock_ref(struct ref_lock *lock); -/** Writes sha1 into the ref specified by the lock. **/ -extern int write_ref_sha1(struct ref_lock *lock, const unsigned char *sha1, const char *msg); - /* * Setup reflog before using. Set errno to something meaningful on failure. */ @@ -230,7 +237,6 @@ extern int for_each_reflog(each_ref_fn, void *); #define REFNAME_ALLOW_ONELEVEL 1 #define REFNAME_REFSPEC_PATTERN 2 -#define REFNAME_DOT_COMPONENT 4 /* * Return 0 iff refname has the correct format for a refname according @@ -238,10 +244,7 @@ extern int for_each_reflog(each_ref_fn, void *); * If REFNAME_ALLOW_ONELEVEL is set in flags, then accept one-level * reference names. If REFNAME_REFSPEC_PATTERN is set in flags, then * allow a "*" wildcard character in place of one of the name - * components. No leading or repeated slashes are accepted. If - * REFNAME_DOT_COMPONENT is set in flags, then allow refname - * components to start with "." (but not a whole component equal to - * "." or ".."). + * components. No leading or repeated slashes are accepted. */ extern int check_refname_format(const char *refname, int flags); @@ -274,8 +277,8 @@ struct ref_transaction *ref_transaction_begin(struct strbuf *err); * The following functions add a reference check or update to a * ref_transaction. In all of them, refname is the name of the * reference to be affected. The functions make internal copies of - * refname, so the caller retains ownership of the parameter. flags - * can be REF_NODEREF; it is passed to update_ref_lock(). + * refname and msg, so the caller retains ownership of these parameters. + * flags can be REF_NODEREF; it is passed to update_ref_lock(). */ /* @@ -292,7 +295,7 @@ int ref_transaction_update(struct ref_transaction *transaction, const char *refname, const unsigned char *new_sha1, const unsigned char *old_sha1, - int flags, int have_old, + int flags, int have_old, const char *msg, struct strbuf *err); /* @@ -307,7 +310,7 @@ int ref_transaction_update(struct ref_transaction *transaction, int ref_transaction_create(struct ref_transaction *transaction, const char *refname, const unsigned char *new_sha1, - int flags, + int flags, const char *msg, struct strbuf *err); /* @@ -321,16 +324,21 @@ int ref_transaction_create(struct ref_transaction *transaction, int ref_transaction_delete(struct ref_transaction *transaction, const char *refname, const unsigned char *old_sha1, - int flags, int have_old, + int flags, int have_old, const char *msg, struct strbuf *err); /* * Commit all of the changes that have been queued in transaction, as - * atomically as possible. Return a nonzero value if there is a - * problem. + * atomically as possible. + * + * Returns 0 for success, or one of the below error codes for errors. */ +/* Naming conflict (for example, the ref names A and A/B conflict). */ +#define TRANSACTION_NAME_CONFLICT -1 +/* All other errors. */ +#define TRANSACTION_GENERIC_ERROR -2 int ref_transaction_commit(struct ref_transaction *transaction, - const char *msg, struct strbuf *err); + struct strbuf *err); /* * Free an existing transaction and all associated data. @@ -508,7 +508,7 @@ static void read_config(void) return; default_remote_name = "origin"; current_branch = NULL; - head_ref = resolve_ref_unsafe("HEAD", sha1, 0, &flag); + head_ref = resolve_ref_unsafe("HEAD", 0, sha1, &flag); if (head_ref && (flag & REF_ISSYMREF) && skip_prefix(head_ref, "refs/heads/", &head_ref)) { current_branch = make_branch(head_ref, 0); @@ -1138,7 +1138,8 @@ static char *guess_ref(const char *name, struct ref *peer) struct strbuf buf = STRBUF_INIT; unsigned char sha1[20]; - const char *r = resolve_ref_unsafe(peer->name, sha1, 1, NULL); + const char *r = resolve_ref_unsafe(peer->name, RESOLVE_REF_READING, + sha1, NULL); if (!r) return NULL; @@ -1199,7 +1200,9 @@ static int match_explicit(struct ref *src, struct ref *dst, unsigned char sha1[20]; int flag; - dst_value = resolve_ref_unsafe(matched_src->name, sha1, 1, &flag); + dst_value = resolve_ref_unsafe(matched_src->name, + RESOLVE_REF_READING, + sha1, &flag); if (!dst_value || ((flag & REF_ISSYMREF) && !starts_with(dst_value, "refs/heads/"))) @@ -1673,7 +1676,7 @@ static int ignore_symref_update(const char *refname) unsigned char sha1[20]; int flag; - if (!resolve_ref_unsafe(refname, sha1, 0, &flag)) + if (!resolve_ref_unsafe(refname, 0, sha1, &flag)) return 0; /* non-existing refs are OK */ return (flag & REF_ISSYMREF); } @@ -1,4 +1,5 @@ #include "cache.h" +#include "lockfile.h" #include "string-list.h" #include "rerere.h" #include "xdiff-interface.h" diff --git a/run-command.c b/run-command.c index 761f0fde40..79a0a763ec 100644 --- a/run-command.c +++ b/run-command.c @@ -12,6 +12,7 @@ void child_process_init(struct child_process *child) { memset(child, 0, sizeof(*child)); argv_array_init(&child->args); + argv_array_init(&child->env_array); } struct child_to_clean { @@ -287,6 +288,8 @@ int start_command(struct child_process *cmd) if (!cmd->argv) cmd->argv = cmd->args.argv; + if (!cmd->env) + cmd->env = cmd->env_array.argv; /* * In case of errors we must keep the promise to close FDs @@ -338,6 +341,7 @@ fail_pipe: error("cannot create %s pipe for %s: %s", str, cmd->argv[0], strerror(failed_errno)); argv_array_clear(&cmd->args); + argv_array_clear(&cmd->env_array); errno = failed_errno; return -1; } @@ -524,6 +528,7 @@ fail_pipe: else if (cmd->err) close(cmd->err); argv_array_clear(&cmd->args); + argv_array_clear(&cmd->env_array); errno = failed_errno; return -1; } @@ -550,6 +555,7 @@ int finish_command(struct child_process *cmd) { int ret = wait_or_whine(cmd->pid, cmd->argv[0]); argv_array_clear(&cmd->args); + argv_array_clear(&cmd->env_array); return ret; } @@ -620,6 +626,45 @@ static int async_die_is_recursing(void) return ret != NULL; } +#else + +static struct { + void (**handlers)(void); + size_t nr; + size_t alloc; +} git_atexit_hdlrs; + +static int git_atexit_installed; + +static void git_atexit_dispatch() +{ + size_t i; + + for (i=git_atexit_hdlrs.nr ; i ; i--) + git_atexit_hdlrs.handlers[i-1](); +} + +static void git_atexit_clear() +{ + free(git_atexit_hdlrs.handlers); + memset(&git_atexit_hdlrs, 0, sizeof(git_atexit_hdlrs)); + git_atexit_installed = 0; +} + +#undef atexit +int git_atexit(void (*handler)(void)) +{ + ALLOC_GROW(git_atexit_hdlrs.handlers, git_atexit_hdlrs.nr + 1, git_atexit_hdlrs.alloc); + git_atexit_hdlrs.handlers[git_atexit_hdlrs.nr++] = handler; + if (!git_atexit_installed) { + if (atexit(&git_atexit_dispatch)) + return -1; + git_atexit_installed = 1; + } + return 0; +} +#define atexit git_atexit + #endif int start_async(struct async *async) @@ -678,6 +723,7 @@ int start_async(struct async *async) close(fdin[1]); if (need_out) close(fdout[0]); + git_atexit_clear(); exit(!!async->proc(proc_in, proc_out, async->data)); } diff --git a/run-command.h b/run-command.h index 1b135d1c96..2137315ee4 100644 --- a/run-command.h +++ b/run-command.h @@ -10,6 +10,7 @@ struct child_process { const char **argv; struct argv_array args; + struct argv_array env_array; pid_t pid; /* * Using .in, .out, .err: @@ -44,7 +45,7 @@ struct child_process { unsigned clean_on_exit:1; }; -#define CHILD_PROCESS_INIT { NULL, ARGV_ARRAY_INIT } +#define CHILD_PROCESS_INIT { NULL, ARGV_ARRAY_INIT, ARGV_ARRAY_INIT } void child_process_init(struct child_process *); int start_command(struct child_process *); diff --git a/sequencer.c b/sequencer.c index 5e8a207474..a03d4fa252 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1,4 +1,5 @@ #include "cache.h" +#include "lockfile.h" #include "sequencer.h" #include "dir.h" #include "object.h" @@ -251,8 +252,8 @@ static int fast_forward_to(const unsigned char *to, const unsigned char *from, if (!transaction || ref_transaction_update(transaction, "HEAD", to, unborn ? null_sha1 : from, - 0, 1, &err) || - ref_transaction_commit(transaction, sb.buf, &err)) { + 0, 1, sb.buf, &err) || + ref_transaction_commit(transaction, &err)) { ref_transaction_free(transaction); error("%s", err.buf); strbuf_release(&sb); @@ -330,7 +331,7 @@ static int is_index_unchanged(void) unsigned char head_sha1[20]; struct commit *head_commit; - if (!resolve_ref_unsafe("HEAD", head_sha1, 1, NULL)) + if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, head_sha1, NULL)) return error(_("Could not resolve HEAD commit\n")); head_commit = lookup_commit(head_sha1); @@ -870,7 +871,7 @@ static int rollback_single_pick(void) if (!file_exists(git_path("CHERRY_PICK_HEAD")) && !file_exists(git_path("REVERT_HEAD"))) return error(_("no cherry-pick or revert in progress")); - if (read_ref_full("HEAD", head_sha1, 0, NULL)) + if (read_ref_full("HEAD", 0, head_sha1, NULL)) return error(_("cannot resolve HEAD")); if (is_null_sha1(head_sha1)) return error(_("cannot abort from a branch yet to be born")); diff --git a/sha1-lookup.c b/sha1-lookup.c index 2dd851598a..5f069214d9 100644 --- a/sha1-lookup.c +++ b/sha1-lookup.c @@ -84,8 +84,6 @@ int sha1_pos(const unsigned char *sha1, void *table, size_t nr, die("BUG: assertion failed in binary search"); } } - if (18 <= ofs) - die("cannot happen -- lo and hi are identical"); } do { diff --git a/sha1_file.c b/sha1_file.c index c63264198e..d7f1838c13 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -8,6 +8,7 @@ */ #include "cache.h" #include "string-list.h" +#include "lockfile.h" #include "delta.h" #include "pack.h" #include "blob.h" @@ -1,4 +1,5 @@ #include "cache.h" +#include "lockfile.h" #include "commit.h" #include "tag.h" #include "pkt-line.h" @@ -226,7 +227,6 @@ static void remove_temporary_shallow_on_signal(int signo) const char *setup_temporary_shallow(const struct sha1_array *extra) { - static int installed_handler; struct strbuf sb = STRBUF_INIT; int fd; @@ -237,10 +237,8 @@ const char *setup_temporary_shallow(const struct sha1_array *extra) strbuf_addstr(&temporary_shallow, git_path("shallow_XXXXXX")); fd = xmkstemp(temporary_shallow.buf); - if (!installed_handler) { - atexit(remove_temporary_shallow); - sigchain_push_common(remove_temporary_shallow_on_signal); - } + atexit(remove_temporary_shallow); + sigchain_push_common(remove_temporary_shallow_on_signal); if (write_in_full(fd, sb.buf, sb.len) != sb.len) die_errno("failed to write to %s", @@ -269,8 +267,8 @@ void setup_alternate_shallow(struct lock_file *shallow_lock, if (write_shallow_commits(&sb, 0, extra)) { if (write_in_full(fd, sb.buf, sb.len) != sb.len) die_errno("failed to write to %s", - shallow_lock->filename); - *alternate_shallow_file = shallow_lock->filename; + shallow_lock->filename.buf); + *alternate_shallow_file = shallow_lock->filename.buf; } else /* * is_repository_shallow() sees empty string as "no @@ -316,7 +314,7 @@ void prune_shallow(int show_only) if (write_shallow_commits_1(&sb, 0, NULL, SEEN_ONLY)) { if (write_in_full(fd, sb.buf, sb.len) != sb.len) die_errno("failed to write to %s", - shallow_lock.filename); + shallow_lock.filename.buf); commit_lock_file(&shallow_lock); } else { unlink(git_path("shallow")); diff --git a/sigchain.c b/sigchain.c index 1118b99e57..faa375d5d8 100644 --- a/sigchain.c +++ b/sigchain.c @@ -1,5 +1,5 @@ -#include "sigchain.h" #include "cache.h" +#include "sigchain.h" #define SIGCHAIN_MAX_SIGNALS 32 diff --git a/t/t0064-sha1-array.sh b/t/t0064-sha1-array.sh new file mode 100755 index 0000000000..50b31ffe75 --- /dev/null +++ b/t/t0064-sha1-array.sh @@ -0,0 +1,94 @@ +#!/bin/sh + +test_description='basic tests for the SHA1 array implementation' +. ./test-lib.sh + +echo20 () { + prefix="${1:+$1 }" + shift + while test $# -gt 0 + do + echo "$prefix$1$1$1$1$1$1$1$1$1$1$1$1$1$1$1$1$1$1$1$1" + shift + done +} + +test_expect_success 'ordered enumeration' ' + echo20 "" 44 55 88 aa >expect && + { + echo20 append 88 44 aa 55 && + echo for_each_unique + } | test-sha1-array >actual && + test_cmp expect actual +' + +test_expect_success 'ordered enumeration with duplicate suppression' ' + echo20 "" 44 55 88 aa >expect && + { + echo20 append 88 44 aa 55 && + echo20 append 88 44 aa 55 && + echo for_each_unique + } | test-sha1-array >actual && + test_cmp expect actual +' + +test_expect_success 'lookup' ' + { + echo20 append 88 44 aa 55 && + echo20 lookup 55 + } | test-sha1-array >actual && + n=$(cat actual) && + test "$n" -eq 1 +' + +test_expect_success 'lookup non-existing entry' ' + { + echo20 append 88 44 aa 55 && + echo20 lookup 33 + } | test-sha1-array >actual && + n=$(cat actual) && + test "$n" -lt 0 +' + +test_expect_success 'lookup with duplicates' ' + { + echo20 append 88 44 aa 55 && + echo20 append 88 44 aa 55 && + echo20 lookup 55 + } | test-sha1-array >actual && + n=$(cat actual) && + test "$n" -ge 2 && + test "$n" -le 3 +' + +test_expect_success 'lookup non-existing entry with duplicates' ' + { + echo20 append 88 44 aa 55 && + echo20 append 88 44 aa 55 && + echo20 lookup 66 + } | test-sha1-array >actual && + n=$(cat actual) && + test "$n" -lt 0 +' + +test_expect_success 'lookup with almost duplicate values' ' + { + echo "append 5555555555555555555555555555555555555555" && + echo "append 555555555555555555555555555555555555555f" && + echo20 lookup 55 + } | test-sha1-array >actual && + n=$(cat actual) && + test "$n" -eq 0 +' + +test_expect_success 'lookup with single duplicate value' ' + { + echo20 append 55 55 && + echo20 lookup 55 + } | test-sha1-array >actual && + n=$(cat actual) && + test "$n" -ge 0 && + test "$n" -le 1 +' + +test_done diff --git a/t/t0090-cache-tree.sh b/t/t0090-cache-tree.sh index f9648a8642..158cf4f03b 100755 --- a/t/t0090-cache-tree.sh +++ b/t/t0090-cache-tree.sh @@ -22,7 +22,7 @@ generate_expected_cache_tree_rec () { # ls-files might have foo/bar, foo/bar/baz, and foo/bar/quux # We want to count only foo because it's the only direct child subtrees=$(git ls-files|grep /|cut -d / -f 1|uniq) && - subtree_count=$(echo "$subtrees"|awk '$1 {++c} END {print c}') && + subtree_count=$(echo "$subtrees"|awk -v c=0 '$1 {++c} END {print c}') && entries=$(git ls-files|wc -l) && printf "SHA $dir (%d entries, %d subtrees)\n" "$entries" "$subtree_count" && for subtree in $subtrees diff --git a/t/t1304-default-acl.sh b/t/t1304-default-acl.sh index 79045abb51..f5422f1d33 100755 --- a/t/t1304-default-acl.sh +++ b/t/t1304-default-acl.sh @@ -26,7 +26,7 @@ test_expect_success 'checking for a working acl setup' ' if test -z "$LOGNAME" then - LOGNAME=$USER + LOGNAME="${USER:-$(id -u -n)}" fi check_perms_and_acl () { diff --git a/t/t1308-config-set.sh b/t/t1308-config-set.sh index ea0bce2dc6..91235b76ba 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/t1400-update-ref.sh b/t/t1400-update-ref.sh index 0218e96366..7b4707b776 100755 --- a/t/t1400-update-ref.sh +++ b/t/t1400-update-ref.sh @@ -110,6 +110,32 @@ test_expect_success "delete symref without dereference when the referred ref is cp -f .git/HEAD.orig .git/HEAD git update-ref -d $m +test_expect_success 'update-ref -d is not confused by self-reference' ' + git symbolic-ref refs/heads/self refs/heads/self && + test_when_finished "rm -f .git/refs/heads/self" && + test_path_is_file .git/refs/heads/self && + test_must_fail git update-ref -d refs/heads/self && + test_path_is_file .git/refs/heads/self +' + +test_expect_success 'update-ref --no-deref -d can delete self-reference' ' + git symbolic-ref refs/heads/self refs/heads/self && + test_when_finished "rm -f .git/refs/heads/self" && + test_path_is_file .git/refs/heads/self && + git update-ref --no-deref -d refs/heads/self && + test_path_is_missing .git/refs/heads/self +' + +test_expect_success 'update-ref --no-deref -d can delete reference to bad ref' ' + >.git/refs/heads/bad && + test_when_finished "rm -f .git/refs/heads/bad" && + git symbolic-ref refs/heads/ref-to-bad refs/heads/bad && + test_when_finished "rm -f .git/refs/heads/ref-to-bad" && + test_path_is_file .git/refs/heads/ref-to-bad && + git update-ref --no-deref -d refs/heads/ref-to-bad && + test_path_is_missing .git/refs/heads/ref-to-bad +' + test_expect_success '(not) create HEAD with old sha1' " test_must_fail git update-ref HEAD $A $B " @@ -374,12 +400,6 @@ test_expect_success 'stdin fails create with no ref' ' grep "fatal: create: missing <ref>" err ' -test_expect_success 'stdin fails create with bad ref name' ' - echo "create ~a $m" >stdin && - test_must_fail git update-ref --stdin <stdin 2>err && - grep "fatal: invalid ref format: ~a" err -' - test_expect_success 'stdin fails create with no new value' ' echo "create $a" >stdin && test_must_fail git update-ref --stdin <stdin 2>err && @@ -398,12 +418,6 @@ test_expect_success 'stdin fails update with no ref' ' grep "fatal: update: missing <ref>" err ' -test_expect_success 'stdin fails update with bad ref name' ' - echo "update ~a $m" >stdin && - test_must_fail git update-ref --stdin <stdin 2>err && - grep "fatal: invalid ref format: ~a" err -' - test_expect_success 'stdin fails update with no new value' ' echo "update $a" >stdin && test_must_fail git update-ref --stdin <stdin 2>err && @@ -422,12 +436,6 @@ test_expect_success 'stdin fails delete with no ref' ' grep "fatal: delete: missing <ref>" err ' -test_expect_success 'stdin fails delete with bad ref name' ' - echo "delete ~a $m" >stdin && - test_must_fail git update-ref --stdin <stdin 2>err && - grep "fatal: invalid ref format: ~a" err -' - test_expect_success 'stdin fails delete with too many arguments' ' echo "delete $a $m $m" >stdin && test_must_fail git update-ref --stdin <stdin 2>err && @@ -700,12 +708,6 @@ test_expect_success 'stdin -z fails create with no ref' ' grep "fatal: create: missing <ref>" err ' -test_expect_success 'stdin -z fails create with bad ref name' ' - printf $F "create ~a " "$m" >stdin && - test_must_fail git update-ref -z --stdin <stdin 2>err && - grep "fatal: invalid ref format: ~a " err -' - test_expect_success 'stdin -z fails create with no new value' ' printf $F "create $a" >stdin && test_must_fail git update-ref -z --stdin <stdin 2>err && @@ -730,12 +732,6 @@ test_expect_success 'stdin -z fails update with too few args' ' grep "fatal: update $a: unexpected end of input when reading <oldvalue>" err ' -test_expect_success 'stdin -z fails update with bad ref name' ' - printf $F "update ~a" "$m" "" >stdin && - test_must_fail git update-ref -z --stdin <stdin 2>err && - grep "fatal: invalid ref format: ~a" err -' - test_expect_success 'stdin -z emits warning with empty new value' ' git update-ref $a $m && printf $F "update $a" "" "" >stdin && @@ -768,12 +764,6 @@ test_expect_success 'stdin -z fails delete with no ref' ' grep "fatal: delete: missing <ref>" err ' -test_expect_success 'stdin -z fails delete with bad ref name' ' - printf $F "delete ~a" "$m" >stdin && - test_must_fail git update-ref -z --stdin <stdin 2>err && - grep "fatal: invalid ref format: ~a" err -' - test_expect_success 'stdin -z fails delete with no old value' ' printf $F "delete $a" >stdin && test_must_fail git update-ref -z --stdin <stdin 2>err && diff --git a/t/t1413-reflog-detach.sh b/t/t1413-reflog-detach.sh new file mode 100755 index 0000000000..c730600d8a --- /dev/null +++ b/t/t1413-reflog-detach.sh @@ -0,0 +1,70 @@ +#!/bin/sh + +test_description='Test reflog interaction with detached HEAD' +. ./test-lib.sh + +reset_state () { + git checkout master && + cp saved_reflog .git/logs/HEAD +} + +test_expect_success setup ' + test_tick && + git commit --allow-empty -m initial && + git branch side && + test_tick && + git commit --allow-empty -m second && + cat .git/logs/HEAD >saved_reflog +' + +test_expect_success baseline ' + reset_state && + git rev-parse master master^ >expect && + git log -g --format=%H >actual && + test_cmp expect actual +' + +test_expect_success 'switch to branch' ' + reset_state && + git rev-parse side master master^ >expect && + git checkout side && + git log -g --format=%H >actual && + test_cmp expect actual +' + +test_expect_success 'detach to other' ' + reset_state && + git rev-parse master side master master^ >expect && + git checkout side && + git checkout master^0 && + git log -g --format=%H >actual && + test_cmp expect actual +' + +test_expect_success 'detach to self' ' + reset_state && + git rev-parse master master master^ >expect && + git checkout master^0 && + git log -g --format=%H >actual && + test_cmp expect actual +' + +test_expect_success 'attach to self' ' + reset_state && + git rev-parse master master master master^ >expect && + git checkout master^0 && + git checkout master && + git log -g --format=%H >actual && + test_cmp expect actual +' + +test_expect_success 'attach to other' ' + reset_state && + git rev-parse side master master master^ >expect && + git checkout master^0 && + git checkout side && + git log -g --format=%H >actual && + test_cmp expect actual +' + +test_done diff --git a/t/t1430-bad-ref-name.sh b/t/t1430-bad-ref-name.sh new file mode 100755 index 0000000000..468e85621a --- /dev/null +++ b/t/t1430-bad-ref-name.sh @@ -0,0 +1,207 @@ +#!/bin/sh + +test_description='Test handling of ref names that check-ref-format rejects' +. ./test-lib.sh + +test_expect_success setup ' + test_commit one && + test_commit two +' + +test_expect_success 'fast-import: fail on invalid branch name ".badbranchname"' ' + test_when_finished "rm -f .git/objects/pack_* .git/objects/index_*" && + cat >input <<-INPUT_END && + commit .badbranchname + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + corrupt + COMMIT + + from refs/heads/master + + INPUT_END + test_must_fail git fast-import <input +' + +test_expect_success 'fast-import: fail on invalid branch name "bad[branch]name"' ' + test_when_finished "rm -f .git/objects/pack_* .git/objects/index_*" && + cat >input <<-INPUT_END && + commit bad[branch]name + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + corrupt + COMMIT + + from refs/heads/master + + INPUT_END + test_must_fail git fast-import <input +' + +test_expect_success 'git branch shows badly named ref' ' + cp .git/refs/heads/master .git/refs/heads/broken...ref && + test_when_finished "rm -f .git/refs/heads/broken...ref" && + git branch >output && + grep -e "broken\.\.\.ref" output +' + +test_expect_success 'branch -d can delete badly named ref' ' + cp .git/refs/heads/master .git/refs/heads/broken...ref && + test_when_finished "rm -f .git/refs/heads/broken...ref" && + git branch -d broken...ref && + git branch >output && + ! grep -e "broken\.\.\.ref" output +' + +test_expect_success 'branch -D can delete badly named ref' ' + cp .git/refs/heads/master .git/refs/heads/broken...ref && + test_when_finished "rm -f .git/refs/heads/broken...ref" && + git branch -D broken...ref && + git branch >output && + ! grep -e "broken\.\.\.ref" output +' + +test_expect_success 'branch -D cannot delete non-ref in .git dir' ' + echo precious >.git/my-private-file && + echo precious >expect && + test_must_fail git branch -D ../../my-private-file && + test_cmp expect .git/my-private-file +' + +test_expect_success 'branch -D cannot delete absolute path' ' + git branch -f extra && + test_must_fail git branch -D "$(pwd)/.git/refs/heads/extra" && + test_cmp_rev HEAD extra +' + +test_expect_success 'git branch cannot create a badly named ref' ' + test_when_finished "rm -f .git/refs/heads/broken...ref" && + test_must_fail git branch broken...ref && + git branch >output && + ! grep -e "broken\.\.\.ref" output +' + +test_expect_success 'branch -m cannot rename to a bad ref name' ' + test_when_finished "rm -f .git/refs/heads/broken...ref" && + test_might_fail git branch -D goodref && + git branch goodref && + test_must_fail git branch -m goodref broken...ref && + test_cmp_rev master goodref && + git branch >output && + ! grep -e "broken\.\.\.ref" output +' + +test_expect_failure 'branch -m can rename from a bad ref name' ' + cp .git/refs/heads/master .git/refs/heads/broken...ref && + test_when_finished "rm -f .git/refs/heads/broken...ref" && + git branch -m broken...ref renamed && + test_cmp_rev master renamed && + git branch >output && + ! grep -e "broken\.\.\.ref" output +' + +test_expect_success 'push cannot create a badly named ref' ' + test_when_finished "rm -f .git/refs/heads/broken...ref" && + test_must_fail git push "file://$(pwd)" HEAD:refs/heads/broken...ref && + git branch >output && + ! grep -e "broken\.\.\.ref" output +' + +test_expect_failure 'push --mirror can delete badly named ref' ' + top=$(pwd) && + git init src && + git init dest && + + ( + cd src && + test_commit one + ) && + ( + cd dest && + test_commit two && + git checkout --detach && + cp .git/refs/heads/master .git/refs/heads/broken...ref + ) && + git -C src push --mirror "file://$top/dest" && + git -C dest branch >output && + ! grep -e "broken\.\.\.ref" output +' + +test_expect_success 'rev-parse skips symref pointing to broken name' ' + test_when_finished "rm -f .git/refs/heads/broken...ref" && + git branch shadow one && + cp .git/refs/heads/master .git/refs/heads/broken...ref && + git symbolic-ref refs/tags/shadow refs/heads/broken...ref && + + git rev-parse --verify one >expect && + git rev-parse --verify shadow >actual 2>err && + test_cmp expect actual && + test_i18ngrep "ignoring.*refs/tags/shadow" err +' + +test_expect_success 'update-ref --no-deref -d can delete reference to broken name' ' + git symbolic-ref refs/heads/badname refs/heads/broken...ref && + test_when_finished "rm -f .git/refs/heads/badname" && + test_path_is_file .git/refs/heads/badname && + git update-ref --no-deref -d refs/heads/badname && + test_path_is_missing .git/refs/heads/badname +' + +test_expect_success 'update-ref -d can delete broken name' ' + cp .git/refs/heads/master .git/refs/heads/broken...ref && + test_when_finished "rm -f .git/refs/heads/broken...ref" && + git update-ref -d refs/heads/broken...ref && + git branch >output && + ! grep -e "broken\.\.\.ref" output +' + +test_expect_success 'update-ref -d cannot delete non-ref in .git dir' ' + echo precious >.git/my-private-file && + echo precious >expect && + test_must_fail git update-ref -d my-private-file && + test_cmp expect .git/my-private-file +' + +test_expect_success 'update-ref -d cannot delete absolute path' ' + git branch -f extra && + test_must_fail git update-ref -d "$(pwd)/.git/refs/heads/extra" && + test_cmp_rev HEAD extra +' + +test_expect_success 'update-ref --stdin fails create with bad ref name' ' + echo "create ~a refs/heads/master" >stdin && + test_must_fail git update-ref --stdin <stdin 2>err && + grep "fatal: invalid ref format: ~a" err +' + +test_expect_success 'update-ref --stdin fails update with bad ref name' ' + echo "update ~a refs/heads/master" >stdin && + test_must_fail git update-ref --stdin <stdin 2>err && + grep "fatal: invalid ref format: ~a" err +' + +test_expect_success 'update-ref --stdin fails delete with bad ref name' ' + echo "delete ~a refs/heads/master" >stdin && + test_must_fail git update-ref --stdin <stdin 2>err && + grep "fatal: invalid ref format: ~a" err +' + +test_expect_success 'update-ref --stdin -z fails create with bad ref name' ' + printf "%s\0" "create ~a " refs/heads/master >stdin && + test_must_fail git update-ref -z --stdin <stdin 2>err && + grep "fatal: invalid ref format: ~a " err +' + +test_expect_success 'update-ref --stdin -z fails update with bad ref name' ' + printf "%s\0" "update ~a" refs/heads/master "" >stdin && + test_must_fail git update-ref -z --stdin <stdin 2>err && + grep "fatal: invalid ref format: ~a" err +' + +test_expect_success 'update-ref --stdin -z fails delete with bad ref name' ' + printf "%s\0" "delete ~a" refs/heads/master >stdin && + test_must_fail git update-ref -z --stdin <stdin 2>err && + grep "fatal: invalid ref format: ~a" err +' + +test_done diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index ac31b711f2..432921b6b8 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -285,6 +285,15 @@ test_expect_success 'deleting a dangling symref' ' test_i18ncmp expect actual ' +test_expect_success 'deleting a self-referential symref' ' + git symbolic-ref refs/heads/self-reference refs/heads/self-reference && + test_path_is_file .git/refs/heads/self-reference && + echo "Deleted branch self-reference (was refs/heads/self-reference)." >expect && + git branch -d self-reference >actual && + test_path_is_missing .git/refs/heads/self-reference && + test_i18ncmp expect actual +' + test_expect_success 'renaming a symref is not allowed' ' git symbolic-ref refs/heads/master2 refs/heads/master && test_must_fail git branch -m master2 master3 && diff --git a/t/t5004-archive-corner-cases.sh b/t/t5004-archive-corner-cases.sh index 83d20c4ba9..305bcac6b7 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/t5310-pack-bitmaps.sh b/t/t5310-pack-bitmaps.sh index 0580258c91..6003490192 100755 --- a/t/t5310-pack-bitmaps.sh +++ b/t/t5310-pack-bitmaps.sh @@ -170,4 +170,13 @@ test_expect_success JGIT 'jgit can read our bitmaps' ' ) ' +test_expect_success 'splitting packs does not generate bogus bitmaps' ' + test-genrandom foo $((1024 * 1024)) >rand && + git add rand && + git commit -m "commit with big file" && + git -c pack.packSizeLimit=500k repack -adb && + git init --bare no-bitmaps.git && + git -C no-bitmaps.git fetch .. HEAD +' + test_done diff --git a/t/t5534-push-signed.sh b/t/t5534-push-signed.sh index 2786346f9a..ecb8d446a5 100755 --- a/t/t5534-push-signed.sh +++ b/t/t5534-push-signed.sh @@ -124,4 +124,48 @@ test_expect_success GPG 'signed push sends push certificate' ' test_cmp expect dst/push-cert-status ' +test_expect_success GPG 'fail without key and heed user.signingkey' ' + prepare_dst && + mkdir -p dst/.git/hooks && + git -C dst config receive.certnonceseed sekrit && + write_script dst/.git/hooks/post-receive <<-\EOF && + # discard the update list + cat >/dev/null + # record the push certificate + if test -n "${GIT_PUSH_CERT-}" + then + git cat-file blob $GIT_PUSH_CERT >../push-cert + fi && + + cat >../push-cert-status <<E_O_F + SIGNER=${GIT_PUSH_CERT_SIGNER-nobody} + KEY=${GIT_PUSH_CERT_KEY-nokey} + STATUS=${GIT_PUSH_CERT_STATUS-nostatus} + NONCE_STATUS=${GIT_PUSH_CERT_NONCE_STATUS-nononcestatus} + NONCE=${GIT_PUSH_CERT_NONCE-nononce} + E_O_F + + EOF + + unset GIT_COMMITTER_EMAIL && + git config user.email hasnokey@nowhere.com && + test_must_fail git push --signed dst noop ff +noff && + git config user.signingkey committer@example.com && + git push --signed dst noop ff +noff && + + ( + cat <<-\EOF && + SIGNER=C O Mitter <committer@example.com> + KEY=13B6F51ECDDE430D + STATUS=G + NONCE_STATUS=OK + EOF + sed -n -e "s/^nonce /NONCE=/p" -e "/^$/q" dst/push-cert + ) >expect && + + grep "$(git rev-parse noop ff) refs/heads/ff" dst/push-cert && + grep "$(git rev-parse noop noff) refs/heads/noff" dst/push-cert && + test_cmp expect dst/push-cert-status +' + test_done diff --git a/t/t7001-mv.sh b/t/t7001-mv.sh index 54d78079e8..69f11bd40d 100755 --- a/t/t7001-mv.sh +++ b/t/t7001-mv.sh @@ -350,10 +350,11 @@ test_expect_success 'git mv moves a submodule with a .git directory and .gitmodu ' test_expect_success 'git mv moves a submodule with gitfile' ' - rm -rf mod/sub && + rm -rf mod && git reset --hard && git submodule update && entry="$(git ls-files --stage sub | cut -f 1)" && + mkdir mod && ( cd mod && git mv ../sub/ . @@ -372,11 +373,12 @@ test_expect_success 'git mv moves a submodule with gitfile' ' ' test_expect_success 'mv does not complain when no .gitmodules file is found' ' - rm -rf mod/sub && + rm -rf mod && git reset --hard && git submodule update && git rm .gitmodules && entry="$(git ls-files --stage sub | cut -f 1)" && + mkdir mod && git mv sub mod/sub 2>actual.err && ! test -s actual.err && ! test -e sub && @@ -390,11 +392,12 @@ test_expect_success 'mv does not complain when no .gitmodules file is found' ' ' test_expect_success 'mv will error out on a modified .gitmodules file unless staged' ' - rm -rf mod/sub && + rm -rf mod && git reset --hard && git submodule update && git config -f .gitmodules foo.bar true && entry="$(git ls-files --stage sub | cut -f 1)" && + mkdir mod && test_must_fail git mv sub mod/sub 2>actual.err && test -s actual.err && test -e sub && @@ -413,13 +416,14 @@ test_expect_success 'mv will error out on a modified .gitmodules file unless sta ' test_expect_success 'mv issues a warning when section is not found in .gitmodules' ' - rm -rf mod/sub && + rm -rf mod && git reset --hard && git submodule update && git config -f .gitmodules --remove-section submodule.sub && git add .gitmodules && entry="$(git ls-files --stage sub | cut -f 1)" && echo "warning: Could not find section in .gitmodules where path=sub" >expect.err && + mkdir mod && git mv sub mod/sub 2>actual.err && test_i18ncmp expect.err actual.err && ! test -e sub && @@ -433,9 +437,10 @@ test_expect_success 'mv issues a warning when section is not found in .gitmodule ' test_expect_success 'mv --dry-run does not touch the submodule or .gitmodules' ' - rm -rf mod/sub && + rm -rf mod && git reset --hard && git submodule update && + mkdir mod && git mv -n sub mod/sub 2>actual.err && test -f sub/.git && git diff-index --exit-code HEAD && diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh index 0366653088..796e9f79ea 100755 --- a/t/t7004-tag.sh +++ b/t/t7004-tag.sh @@ -1460,7 +1460,7 @@ test_expect_success 'invalid sort parameter in configuratoin' ' ' run_with_limited_stack () { - (ulimit -s 64 && "$@") + (ulimit -s 128 && "$@") } test_lazy_prereq ULIMIT 'run_with_limited_stack true' @@ -1469,7 +1469,7 @@ test_lazy_prereq ULIMIT 'run_with_limited_stack true' test_expect_success ULIMIT '--contains works in a deep repo' ' >expect && i=1 && - while test $i -lt 4000 + while test $i -lt 8000 do echo "commit refs/heads/master committer A U Thor <author@example.com> $((1000000000 + $i * 100)) +0200 diff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh new file mode 100755 index 0000000000..1efb88051a --- /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/t7610-mergetool.sh b/t/t7610-mergetool.sh index 05d9db090d..7eeb207b32 100755 --- a/t/t7610-mergetool.sh +++ b/t/t7610-mergetool.sh @@ -14,512 +14,527 @@ Testing basic merge tool invocation' # running mergetool test_expect_success 'setup' ' - git config rerere.enabled true && - echo master >file1 && - echo master spaced >"spaced name" && - echo master file11 >file11 && - echo master file12 >file12 && - echo master file13 >file13 && - echo master file14 >file14 && - mkdir subdir && - echo master sub >subdir/file3 && - test_create_repo submod && - ( - cd submod && - : >foo && - git add foo && - git commit -m "Add foo" - ) && - git submodule add git://example.com/submod submod && - git add file1 "spaced name" file1[1-4] subdir/file3 .gitmodules submod && - git commit -m "add initial versions" && - - git checkout -b branch1 master && - git submodule update -N && - echo branch1 change >file1 && - echo branch1 newfile >file2 && - echo branch1 spaced >"spaced name" && - echo branch1 both added >both && - echo branch1 change file11 >file11 && - echo branch1 change file13 >file13 && - echo branch1 sub >subdir/file3 && - ( - cd submod && - echo branch1 submodule >bar && - git add bar && - git commit -m "Add bar on branch1" && - git checkout -b submod-branch1 - ) && - git add file1 "spaced name" file11 file13 file2 subdir/file3 submod && - git add both && - git rm file12 && - git commit -m "branch1 changes" && - - git checkout -b stash1 master && - echo stash1 change file11 >file11 && - git add file11 && - git commit -m "stash1 changes" && - - git checkout -b stash2 master && - echo stash2 change file11 >file11 && - git add file11 && - git commit -m "stash2 changes" && - - git checkout master && - git submodule update -N && - echo master updated >file1 && - echo master new >file2 && - echo master updated spaced >"spaced name" && - echo master both added >both && - echo master updated file12 >file12 && - echo master updated file14 >file14 && - echo master new sub >subdir/file3 && - ( - cd submod && - echo master submodule >bar && - git add bar && - git commit -m "Add bar on master" && - git checkout -b submod-master - ) && - git add file1 "spaced name" file12 file14 file2 subdir/file3 submod && - git add both && - git rm file11 && - git commit -m "master updates" && - - git config merge.tool mytool && - git config mergetool.mytool.cmd "cat \"\$REMOTE\" >\"\$MERGED\"" && - git config mergetool.mytool.trustExitCode true && - git config mergetool.mybase.cmd "cat \"\$BASE\" >\"\$MERGED\"" && - git config mergetool.mybase.trustExitCode true + test_config rerere.enabled true && + echo master >file1 && + echo master spaced >"spaced name" && + echo master file11 >file11 && + echo master file12 >file12 && + echo master file13 >file13 && + echo master file14 >file14 && + mkdir subdir && + echo master sub >subdir/file3 && + test_create_repo submod && + ( + cd submod && + : >foo && + git add foo && + git commit -m "Add foo" + ) && + git submodule add git://example.com/submod submod && + git add file1 "spaced name" file1[1-4] subdir/file3 .gitmodules submod && + git commit -m "add initial versions" && + + git checkout -b branch1 master && + git submodule update -N && + echo branch1 change >file1 && + echo branch1 newfile >file2 && + echo branch1 spaced >"spaced name" && + echo branch1 both added >both && + echo branch1 change file11 >file11 && + echo branch1 change file13 >file13 && + echo branch1 sub >subdir/file3 && + ( + cd submod && + echo branch1 submodule >bar && + git add bar && + git commit -m "Add bar on branch1" && + git checkout -b submod-branch1 + ) && + git add file1 "spaced name" file11 file13 file2 subdir/file3 submod && + git add both && + git rm file12 && + git commit -m "branch1 changes" && + + git checkout -b stash1 master && + echo stash1 change file11 >file11 && + git add file11 && + git commit -m "stash1 changes" && + + git checkout -b stash2 master && + echo stash2 change file11 >file11 && + git add file11 && + git commit -m "stash2 changes" && + + git checkout master && + git submodule update -N && + echo master updated >file1 && + echo master new >file2 && + echo master updated spaced >"spaced name" && + echo master both added >both && + echo master updated file12 >file12 && + echo master updated file14 >file14 && + echo master new sub >subdir/file3 && + ( + cd submod && + echo master submodule >bar && + git add bar && + git commit -m "Add bar on master" && + git checkout -b submod-master + ) && + git add file1 "spaced name" file12 file14 file2 subdir/file3 submod && + git add both && + git rm file11 && + git commit -m "master updates" && + + git config merge.tool mytool && + git config mergetool.mytool.cmd "cat \"\$REMOTE\" >\"\$MERGED\"" && + git config mergetool.mytool.trustExitCode true && + git config mergetool.mybase.cmd "cat \"\$BASE\" >\"\$MERGED\"" && + git config mergetool.mybase.trustExitCode true ' test_expect_success 'custom mergetool' ' - git checkout -b test1 branch1 && - git submodule update -N && - test_must_fail git merge master >/dev/null 2>&1 && - ( yes "" | git mergetool both >/dev/null 2>&1 ) && - ( yes "" | git mergetool file1 file1 ) && - ( yes "" | git mergetool file2 "spaced name" >/dev/null 2>&1 ) && - ( yes "" | git mergetool subdir/file3 >/dev/null 2>&1 ) && - ( yes "d" | git mergetool file11 >/dev/null 2>&1 ) && - ( yes "d" | git mergetool file12 >/dev/null 2>&1 ) && - ( yes "l" | git mergetool submod >/dev/null 2>&1 ) && - test "$(cat file1)" = "master updated" && - test "$(cat file2)" = "master new" && - test "$(cat subdir/file3)" = "master new sub" && - test "$(cat submod/bar)" = "branch1 submodule" && - git commit -m "branch1 resolved with mergetool" + git checkout -b test1 branch1 && + git submodule update -N && + test_must_fail git merge master >/dev/null 2>&1 && + ( yes "" | git mergetool both >/dev/null 2>&1 ) && + ( yes "" | git mergetool file1 file1 ) && + ( yes "" | git mergetool file2 "spaced name" >/dev/null 2>&1 ) && + ( yes "" | git mergetool subdir/file3 >/dev/null 2>&1 ) && + ( yes "d" | git mergetool file11 >/dev/null 2>&1 ) && + ( yes "d" | git mergetool file12 >/dev/null 2>&1 ) && + ( yes "l" | git mergetool submod >/dev/null 2>&1 ) && + test "$(cat file1)" = "master updated" && + test "$(cat file2)" = "master new" && + test "$(cat subdir/file3)" = "master new sub" && + test "$(cat submod/bar)" = "branch1 submodule" && + git commit -m "branch1 resolved with mergetool" ' test_expect_success 'mergetool crlf' ' - git config core.autocrlf true && - git checkout -b test2 branch1 && - test_must_fail git merge master >/dev/null 2>&1 && - ( yes "" | git mergetool file1 >/dev/null 2>&1 ) && - ( yes "" | git mergetool file2 >/dev/null 2>&1 ) && - ( yes "" | git mergetool "spaced name" >/dev/null 2>&1 ) && - ( yes "" | git mergetool both >/dev/null 2>&1 ) && - ( yes "" | git mergetool subdir/file3 >/dev/null 2>&1 ) && - ( yes "d" | git mergetool file11 >/dev/null 2>&1 ) && - ( yes "d" | git mergetool file12 >/dev/null 2>&1 ) && - ( yes "r" | git mergetool submod >/dev/null 2>&1 ) && - test "$(printf x | cat file1 -)" = "$(printf "master updated\r\nx")" && - test "$(printf x | cat file2 -)" = "$(printf "master new\r\nx")" && - test "$(printf x | cat subdir/file3 -)" = "$(printf "master new sub\r\nx")" && - git submodule update -N && - test "$(cat submod/bar)" = "master submodule" && - git commit -m "branch1 resolved with mergetool - autocrlf" && - git config core.autocrlf false && - git reset --hard + test_config core.autocrlf true && + git checkout -b test2 branch1 && + test_must_fail git merge master >/dev/null 2>&1 && + ( yes "" | git mergetool file1 >/dev/null 2>&1 ) && + ( yes "" | git mergetool file2 >/dev/null 2>&1 ) && + ( yes "" | git mergetool "spaced name" >/dev/null 2>&1 ) && + ( yes "" | git mergetool both >/dev/null 2>&1 ) && + ( yes "" | git mergetool subdir/file3 >/dev/null 2>&1 ) && + ( yes "d" | git mergetool file11 >/dev/null 2>&1 ) && + ( yes "d" | git mergetool file12 >/dev/null 2>&1 ) && + ( yes "r" | git mergetool submod >/dev/null 2>&1 ) && + test "$(printf x | cat file1 -)" = "$(printf "master updated\r\nx")" && + test "$(printf x | cat file2 -)" = "$(printf "master new\r\nx")" && + test "$(printf x | cat subdir/file3 -)" = "$(printf "master new sub\r\nx")" && + git submodule update -N && + test "$(cat submod/bar)" = "master submodule" && + git commit -m "branch1 resolved with mergetool - autocrlf" && + test_config core.autocrlf false && + git reset --hard ' test_expect_success 'mergetool in subdir' ' - git checkout -b test3 branch1 && - git submodule update -N && - ( - cd subdir && - test_must_fail git merge master >/dev/null 2>&1 && - ( yes "" | git mergetool file3 >/dev/null 2>&1 ) && - test "$(cat file3)" = "master new sub" - ) + git checkout -b test3 branch1 && + git submodule update -N && + ( + cd subdir && + test_must_fail git merge master >/dev/null 2>&1 && + ( yes "" | git mergetool file3 >/dev/null 2>&1 ) && + test "$(cat file3)" = "master new sub" + ) ' test_expect_success 'mergetool on file in parent dir' ' - ( - cd subdir && - ( yes "" | git mergetool ../file1 >/dev/null 2>&1 ) && - ( yes "" | git mergetool ../file2 ../spaced\ name >/dev/null 2>&1 ) && - ( yes "" | git mergetool ../both >/dev/null 2>&1 ) && - ( yes "d" | git mergetool ../file11 >/dev/null 2>&1 ) && - ( yes "d" | git mergetool ../file12 >/dev/null 2>&1 ) && - ( yes "l" | git mergetool ../submod >/dev/null 2>&1 ) && - test "$(cat ../file1)" = "master updated" && - test "$(cat ../file2)" = "master new" && - test "$(cat ../submod/bar)" = "branch1 submodule" && - git commit -m "branch1 resolved with mergetool - subdir" - ) + ( + cd subdir && + ( yes "" | git mergetool ../file1 >/dev/null 2>&1 ) && + ( yes "" | git mergetool ../file2 ../spaced\ name >/dev/null 2>&1 ) && + ( yes "" | git mergetool ../both >/dev/null 2>&1 ) && + ( yes "d" | git mergetool ../file11 >/dev/null 2>&1 ) && + ( yes "d" | git mergetool ../file12 >/dev/null 2>&1 ) && + ( yes "l" | git mergetool ../submod >/dev/null 2>&1 ) && + test "$(cat ../file1)" = "master updated" && + test "$(cat ../file2)" = "master new" && + test "$(cat ../submod/bar)" = "branch1 submodule" && + git commit -m "branch1 resolved with mergetool - subdir" + ) ' test_expect_success 'mergetool skips autoresolved' ' - git checkout -b test4 branch1 && - git submodule update -N && - test_must_fail git merge master && - test -n "$(git ls-files -u)" && - ( yes "d" | git mergetool file11 >/dev/null 2>&1 ) && - ( yes "d" | git mergetool file12 >/dev/null 2>&1 ) && - ( yes "l" | git mergetool submod >/dev/null 2>&1 ) && - output="$(git mergetool --no-prompt)" && - test "$output" = "No files need merging" && - git reset --hard + git checkout -b test4 branch1 && + git submodule update -N && + test_must_fail git merge master && + test -n "$(git ls-files -u)" && + ( yes "d" | git mergetool file11 >/dev/null 2>&1 ) && + ( yes "d" | git mergetool file12 >/dev/null 2>&1 ) && + ( yes "l" | git mergetool submod >/dev/null 2>&1 ) && + output="$(git mergetool --no-prompt)" && + test "$output" = "No files need merging" && + git reset --hard ' test_expect_success 'mergetool merges all from subdir' ' - ( - cd subdir && - git config rerere.enabled false && - test_must_fail git merge master && - ( yes "r" | git mergetool ../submod ) && - ( yes "d" "d" | git mergetool --no-prompt ) && - test "$(cat ../file1)" = "master updated" && - test "$(cat ../file2)" = "master new" && - test "$(cat file3)" = "master new sub" && - ( cd .. && git submodule update -N ) && - test "$(cat ../submod/bar)" = "master submodule" && - git commit -m "branch2 resolved by mergetool from subdir" - ) + ( + cd subdir && + test_config rerere.enabled false && + test_must_fail git merge master && + ( yes "r" | git mergetool ../submod ) && + ( yes "d" "d" | git mergetool --no-prompt ) && + test "$(cat ../file1)" = "master updated" && + test "$(cat ../file2)" = "master new" && + test "$(cat file3)" = "master new sub" && + ( cd .. && git submodule update -N ) && + test "$(cat ../submod/bar)" = "master submodule" && + git commit -m "branch2 resolved by mergetool from subdir" + ) ' test_expect_success 'mergetool skips resolved paths when rerere is active' ' - git config rerere.enabled true && - rm -rf .git/rr-cache && - git checkout -b test5 branch1 - git submodule update -N && - test_must_fail git merge master >/dev/null 2>&1 && - ( yes "l" | git mergetool --no-prompt submod >/dev/null 2>&1 ) && - ( yes "d" "d" | git mergetool --no-prompt >/dev/null 2>&1 ) && - git submodule update -N && - output="$(yes "n" | git mergetool --no-prompt)" && - test "$output" = "No files need merging" && - git reset --hard + test_config rerere.enabled true && + rm -rf .git/rr-cache && + git checkout -b test5 branch1 && + git submodule update -N && + test_must_fail git merge master >/dev/null 2>&1 && + ( yes "l" | git mergetool --no-prompt submod >/dev/null 2>&1 ) && + ( yes "d" "d" | git mergetool --no-prompt >/dev/null 2>&1 ) && + git submodule update -N && + output="$(yes "n" | git mergetool --no-prompt)" && + test "$output" = "No files need merging" && + git reset --hard ' test_expect_success 'conflicted stash sets up rerere' ' - git config rerere.enabled true && - git checkout stash1 && - echo "Conflicting stash content" >file11 && - git stash && - - git checkout --detach stash2 && - test_must_fail git stash apply && - - test -n "$(git ls-files -u)" && - conflicts="$(git rerere remaining)" && - test "$conflicts" = "file11" && - output="$(git mergetool --no-prompt)" && - test "$output" != "No files need merging" && - - git commit -am "save the stash resolution" && - - git reset --hard stash2 && - test_must_fail git stash apply && - - test -n "$(git ls-files -u)" && - conflicts="$(git rerere remaining)" && - test -z "$conflicts" && - output="$(git mergetool --no-prompt)" && - test "$output" = "No files need merging" + test_config rerere.enabled true && + git checkout stash1 && + echo "Conflicting stash content" >file11 && + git stash && + + git checkout --detach stash2 && + test_must_fail git stash apply && + + test -n "$(git ls-files -u)" && + conflicts="$(git rerere remaining)" && + test "$conflicts" = "file11" && + output="$(git mergetool --no-prompt)" && + test "$output" != "No files need merging" && + + git commit -am "save the stash resolution" && + + git reset --hard stash2 && + test_must_fail git stash apply && + + test -n "$(git ls-files -u)" && + conflicts="$(git rerere remaining)" && + test -z "$conflicts" && + output="$(git mergetool --no-prompt)" && + test "$output" = "No files need merging" ' test_expect_success 'mergetool takes partial path' ' - git reset --hard - git config rerere.enabled false && - git checkout -b test12 branch1 && - git submodule update -N && - test_must_fail git merge master && - - #should not need these lines - #( yes "d" | git mergetool file11 >/dev/null 2>&1 ) && - #( yes "d" | git mergetool file12 >/dev/null 2>&1 ) && - #( yes "l" | git mergetool submod >/dev/null 2>&1 ) && - #( yes "" | git mergetool file1 file2 >/dev/null 2>&1 ) && - - ( yes "" | git mergetool subdir ) && - - test "$(cat subdir/file3)" = "master new sub" && - git reset --hard + git reset --hard && + test_config rerere.enabled false && + git checkout -b test12 branch1 && + git submodule update -N && + test_must_fail git merge master && + + ( yes "" | git mergetool subdir ) && + + test "$(cat subdir/file3)" = "master new sub" && + git reset --hard ' test_expect_success 'deleted vs modified submodule' ' - git checkout -b test6 branch1 && - git submodule update -N && - mv submod submod-movedaside && - git rm --cached submod && - git commit -m "Submodule deleted from branch" && - git checkout -b test6.a test6 && - test_must_fail git merge master && - test -n "$(git ls-files -u)" && - ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) && - ( yes "" | git mergetool both >/dev/null 2>&1 ) && - ( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) && - ( yes "r" | git mergetool submod ) && - rmdir submod && mv submod-movedaside submod && - test "$(cat submod/bar)" = "branch1 submodule" && - git submodule update -N && - test "$(cat submod/bar)" = "master submodule" && - output="$(git mergetool --no-prompt)" && - test "$output" = "No files need merging" && - git commit -m "Merge resolved by keeping module" && - - mv submod submod-movedaside && - git checkout -b test6.b test6 && - git submodule update -N && - test_must_fail git merge master && - test -n "$(git ls-files -u)" && - ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) && - ( yes "" | git mergetool both >/dev/null 2>&1 ) && - ( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) && - ( yes "l" | git mergetool submod ) && - test ! -e submod && - output="$(git mergetool --no-prompt)" && - test "$output" = "No files need merging" && - git commit -m "Merge resolved by deleting module" && - - mv submod-movedaside submod && - git checkout -b test6.c master && - git submodule update -N && - test_must_fail git merge test6 && - test -n "$(git ls-files -u)" && - ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) && - ( yes "" | git mergetool both >/dev/null 2>&1 ) && - ( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) && - ( yes "r" | git mergetool submod ) && - test ! -e submod && - test -d submod.orig && - git submodule update -N && - output="$(git mergetool --no-prompt)" && - test "$output" = "No files need merging" && - git commit -m "Merge resolved by deleting module" && - mv submod.orig submod && - - git checkout -b test6.d master && - git submodule update -N && - test_must_fail git merge test6 && - test -n "$(git ls-files -u)" && - ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) && - ( yes "" | git mergetool both >/dev/null 2>&1 ) && - ( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) && - ( yes "l" | git mergetool submod ) && - test "$(cat submod/bar)" = "master submodule" && - git submodule update -N && - test "$(cat submod/bar)" = "master submodule" && - output="$(git mergetool --no-prompt)" && - test "$output" = "No files need merging" && - git commit -m "Merge resolved by keeping module" && - git reset --hard HEAD + git checkout -b test6 branch1 && + git submodule update -N && + mv submod submod-movedaside && + git rm --cached submod && + git commit -m "Submodule deleted from branch" && + git checkout -b test6.a test6 && + test_must_fail git merge master && + test -n "$(git ls-files -u)" && + ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) && + ( yes "" | git mergetool both >/dev/null 2>&1 ) && + ( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) && + ( yes "r" | git mergetool submod ) && + rmdir submod && mv submod-movedaside submod && + test "$(cat submod/bar)" = "branch1 submodule" && + git submodule update -N && + test "$(cat submod/bar)" = "master submodule" && + output="$(git mergetool --no-prompt)" && + test "$output" = "No files need merging" && + git commit -m "Merge resolved by keeping module" && + + mv submod submod-movedaside && + git checkout -b test6.b test6 && + git submodule update -N && + test_must_fail git merge master && + test -n "$(git ls-files -u)" && + ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) && + ( yes "" | git mergetool both >/dev/null 2>&1 ) && + ( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) && + ( yes "l" | git mergetool submod ) && + test ! -e submod && + output="$(git mergetool --no-prompt)" && + test "$output" = "No files need merging" && + git commit -m "Merge resolved by deleting module" && + + mv submod-movedaside submod && + git checkout -b test6.c master && + git submodule update -N && + test_must_fail git merge test6 && + test -n "$(git ls-files -u)" && + ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) && + ( yes "" | git mergetool both >/dev/null 2>&1 ) && + ( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) && + ( yes "r" | git mergetool submod ) && + test ! -e submod && + test -d submod.orig && + git submodule update -N && + output="$(git mergetool --no-prompt)" && + test "$output" = "No files need merging" && + git commit -m "Merge resolved by deleting module" && + mv submod.orig submod && + + git checkout -b test6.d master && + git submodule update -N && + test_must_fail git merge test6 && + test -n "$(git ls-files -u)" && + ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) && + ( yes "" | git mergetool both >/dev/null 2>&1 ) && + ( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) && + ( yes "l" | git mergetool submod ) && + test "$(cat submod/bar)" = "master submodule" && + git submodule update -N && + test "$(cat submod/bar)" = "master submodule" && + output="$(git mergetool --no-prompt)" && + test "$output" = "No files need merging" && + git commit -m "Merge resolved by keeping module" && + git reset --hard HEAD ' test_expect_success 'file vs modified submodule' ' - git checkout -b test7 branch1 && - git submodule update -N && - mv submod submod-movedaside && - git rm --cached submod && - echo not a submodule >submod && - git add submod && - git commit -m "Submodule path becomes file" && - git checkout -b test7.a branch1 && - test_must_fail git merge master && - test -n "$(git ls-files -u)" && - ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) && - ( yes "" | git mergetool both >/dev/null 2>&1 ) && - ( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) && - ( yes "r" | git mergetool submod ) && - rmdir submod && mv submod-movedaside submod && - test "$(cat submod/bar)" = "branch1 submodule" && - git submodule update -N && - test "$(cat submod/bar)" = "master submodule" && - output="$(git mergetool --no-prompt)" && - test "$output" = "No files need merging" && - git commit -m "Merge resolved by keeping module" && - - mv submod submod-movedaside && - git checkout -b test7.b test7 && - test_must_fail git merge master && - test -n "$(git ls-files -u)" && - ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) && - ( yes "" | git mergetool both >/dev/null 2>&1 ) && - ( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) && - ( yes "l" | git mergetool submod ) && - git submodule update -N && - test "$(cat submod)" = "not a submodule" && - output="$(git mergetool --no-prompt)" && - test "$output" = "No files need merging" && - git commit -m "Merge resolved by keeping file" && - - git checkout -b test7.c master && - rmdir submod && mv submod-movedaside submod && - test ! -e submod.orig && - git submodule update -N && - test_must_fail git merge test7 && - test -n "$(git ls-files -u)" && - ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) && - ( yes "" | git mergetool both >/dev/null 2>&1 ) && - ( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) && - ( yes "r" | git mergetool submod ) && - test -d submod.orig && - git submodule update -N && - test "$(cat submod)" = "not a submodule" && - output="$(git mergetool --no-prompt)" && - test "$output" = "No files need merging" && - git commit -m "Merge resolved by keeping file" && - - git checkout -b test7.d master && - rmdir submod && mv submod.orig submod && - git submodule update -N && - test_must_fail git merge test7 && - test -n "$(git ls-files -u)" && - ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) && - ( yes "" | git mergetool both>/dev/null 2>&1 ) && - ( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) && - ( yes "l" | git mergetool submod ) && - test "$(cat submod/bar)" = "master submodule" && - git submodule update -N && - test "$(cat submod/bar)" = "master submodule" && - output="$(git mergetool --no-prompt)" && - test "$output" = "No files need merging" && - git commit -m "Merge resolved by keeping module" + git checkout -b test7 branch1 && + git submodule update -N && + mv submod submod-movedaside && + git rm --cached submod && + echo not a submodule >submod && + git add submod && + git commit -m "Submodule path becomes file" && + git checkout -b test7.a branch1 && + test_must_fail git merge master && + test -n "$(git ls-files -u)" && + ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) && + ( yes "" | git mergetool both >/dev/null 2>&1 ) && + ( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) && + ( yes "r" | git mergetool submod ) && + rmdir submod && mv submod-movedaside submod && + test "$(cat submod/bar)" = "branch1 submodule" && + git submodule update -N && + test "$(cat submod/bar)" = "master submodule" && + output="$(git mergetool --no-prompt)" && + test "$output" = "No files need merging" && + git commit -m "Merge resolved by keeping module" && + + mv submod submod-movedaside && + git checkout -b test7.b test7 && + test_must_fail git merge master && + test -n "$(git ls-files -u)" && + ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) && + ( yes "" | git mergetool both >/dev/null 2>&1 ) && + ( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) && + ( yes "l" | git mergetool submod ) && + git submodule update -N && + test "$(cat submod)" = "not a submodule" && + output="$(git mergetool --no-prompt)" && + test "$output" = "No files need merging" && + git commit -m "Merge resolved by keeping file" && + + git checkout -b test7.c master && + rmdir submod && mv submod-movedaside submod && + test ! -e submod.orig && + git submodule update -N && + test_must_fail git merge test7 && + test -n "$(git ls-files -u)" && + ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) && + ( yes "" | git mergetool both >/dev/null 2>&1 ) && + ( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) && + ( yes "r" | git mergetool submod ) && + test -d submod.orig && + git submodule update -N && + test "$(cat submod)" = "not a submodule" && + output="$(git mergetool --no-prompt)" && + test "$output" = "No files need merging" && + git commit -m "Merge resolved by keeping file" && + + git checkout -b test7.d master && + rmdir submod && mv submod.orig submod && + git submodule update -N && + test_must_fail git merge test7 && + test -n "$(git ls-files -u)" && + ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) && + ( yes "" | git mergetool both>/dev/null 2>&1 ) && + ( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) && + ( yes "l" | git mergetool submod ) && + test "$(cat submod/bar)" = "master submodule" && + git submodule update -N && + test "$(cat submod/bar)" = "master submodule" && + output="$(git mergetool --no-prompt)" && + test "$output" = "No files need merging" && + git commit -m "Merge resolved by keeping module" ' test_expect_success 'submodule in subdirectory' ' - git checkout -b test10 branch1 && - git submodule update -N && - ( - cd subdir && - test_create_repo subdir_module && + git checkout -b test10 branch1 && + git submodule update -N && + ( + cd subdir && + test_create_repo subdir_module && + ( + cd subdir_module && + : >file15 && + git add file15 && + git commit -m "add initial versions" + ) + ) && + git submodule add git://example.com/subsubmodule subdir/subdir_module && + git add subdir/subdir_module && + git commit -m "add submodule in subdirectory" && + + git checkout -b test10.a test10 && + git submodule update -N && ( - cd subdir_module && - : >file15 && - git add file15 && - git commit -m "add initial versions" - ) - ) && - git submodule add git://example.com/subsubmodule subdir/subdir_module && - git add subdir/subdir_module && - git commit -m "add submodule in subdirectory" && - - git checkout -b test10.a test10 && - git submodule update -N && - ( - cd subdir/subdir_module && - git checkout -b super10.a && - echo test10.a >file15 && - git add file15 && - git commit -m "on branch 10.a" - ) && - git add subdir/subdir_module && - git commit -m "change submodule in subdirectory on test10.a" && - - git checkout -b test10.b test10 && - git submodule update -N && - ( cd subdir/subdir_module && - git checkout -b super10.b && - echo test10.b >file15 && - git add file15 && - git commit -m "on branch 10.b" - ) && - git add subdir/subdir_module && - git commit -m "change submodule in subdirectory on test10.b" && - - test_must_fail git merge test10.a >/dev/null 2>&1 && - ( - cd subdir && - ( yes "l" | git mergetool subdir_module ) - ) && - test "$(cat subdir/subdir_module/file15)" = "test10.b" && - git submodule update -N && - test "$(cat subdir/subdir_module/file15)" = "test10.b" && - git reset --hard && - git submodule update -N && - - test_must_fail git merge test10.a >/dev/null 2>&1 && - ( yes "r" | git mergetool subdir/subdir_module ) && - test "$(cat subdir/subdir_module/file15)" = "test10.b" && - git submodule update -N && - test "$(cat subdir/subdir_module/file15)" = "test10.a" && - git commit -m "branch1 resolved with mergetool" && - rm -rf subdir/subdir_module + git checkout -b super10.a && + echo test10.a >file15 && + git add file15 && + git commit -m "on branch 10.a" + ) && + git add subdir/subdir_module && + git commit -m "change submodule in subdirectory on test10.a" && + + git checkout -b test10.b test10 && + git submodule update -N && + ( + cd subdir/subdir_module && + git checkout -b super10.b && + echo test10.b >file15 && + git add file15 && + git commit -m "on branch 10.b" + ) && + git add subdir/subdir_module && + git commit -m "change submodule in subdirectory on test10.b" && + + test_must_fail git merge test10.a >/dev/null 2>&1 && + ( + cd subdir && + ( yes "l" | git mergetool subdir_module ) + ) && + test "$(cat subdir/subdir_module/file15)" = "test10.b" && + git submodule update -N && + test "$(cat subdir/subdir_module/file15)" = "test10.b" && + git reset --hard && + git submodule update -N && + + test_must_fail git merge test10.a >/dev/null 2>&1 && + ( yes "r" | git mergetool subdir/subdir_module ) && + test "$(cat subdir/subdir_module/file15)" = "test10.b" && + git submodule update -N && + test "$(cat subdir/subdir_module/file15)" = "test10.a" && + git commit -m "branch1 resolved with mergetool" && + rm -rf subdir/subdir_module ' test_expect_success 'directory vs modified submodule' ' - git checkout -b test11 branch1 && - mv submod submod-movedaside && - git rm --cached submod && - mkdir submod && - echo not a submodule >submod/file16 && - git add submod/file16 && - git commit -m "Submodule path becomes directory" && - - test_must_fail git merge master && - test -n "$(git ls-files -u)" && - ( yes "l" | git mergetool submod ) && - test "$(cat submod/file16)" = "not a submodule" && - rm -rf submod.orig && - - git reset --hard >/dev/null 2>&1 && - test_must_fail git merge master && - test -n "$(git ls-files -u)" && - test ! -e submod.orig && - ( yes "r" | git mergetool submod ) && - test -d submod.orig && - test "$(cat submod.orig/file16)" = "not a submodule" && - rm -r submod.orig && - mv submod-movedaside/.git submod && - ( cd submod && git clean -f && git reset --hard ) && - git submodule update -N && - test "$(cat submod/bar)" = "master submodule" && - git reset --hard >/dev/null 2>&1 && rm -rf submod-movedaside && - - git checkout -b test11.c master && - git submodule update -N && - test_must_fail git merge test11 && - test -n "$(git ls-files -u)" && - ( yes "l" | git mergetool submod ) && - git submodule update -N && - test "$(cat submod/bar)" = "master submodule" && - - git reset --hard >/dev/null 2>&1 && - git submodule update -N && - test_must_fail git merge test11 && - test -n "$(git ls-files -u)" && - test ! -e submod.orig && - ( yes "r" | git mergetool submod ) && - test "$(cat submod/file16)" = "not a submodule" && - - git reset --hard master >/dev/null 2>&1 && - ( cd submod && git clean -f && git reset --hard ) && - git submodule update -N + git checkout -b test11 branch1 && + mv submod submod-movedaside && + git rm --cached submod && + mkdir submod && + echo not a submodule >submod/file16 && + git add submod/file16 && + git commit -m "Submodule path becomes directory" && + + test_must_fail git merge master && + test -n "$(git ls-files -u)" && + ( yes "l" | git mergetool submod ) && + test "$(cat submod/file16)" = "not a submodule" && + rm -rf submod.orig && + + git reset --hard >/dev/null 2>&1 && + test_must_fail git merge master && + test -n "$(git ls-files -u)" && + test ! -e submod.orig && + ( yes "r" | git mergetool submod ) && + test -d submod.orig && + test "$(cat submod.orig/file16)" = "not a submodule" && + rm -r submod.orig && + mv submod-movedaside/.git submod && + ( cd submod && git clean -f && git reset --hard ) && + git submodule update -N && + test "$(cat submod/bar)" = "master submodule" && + git reset --hard >/dev/null 2>&1 && rm -rf submod-movedaside && + + git checkout -b test11.c master && + git submodule update -N && + test_must_fail git merge test11 && + test -n "$(git ls-files -u)" && + ( yes "l" | git mergetool submod ) && + git submodule update -N && + test "$(cat submod/bar)" = "master submodule" && + + git reset --hard >/dev/null 2>&1 && + git submodule update -N && + test_must_fail git merge test11 && + test -n "$(git ls-files -u)" && + test ! -e submod.orig && + ( yes "r" | git mergetool submod ) && + test "$(cat submod/file16)" = "not a submodule" && + + git reset --hard master >/dev/null 2>&1 && + ( cd submod && git clean -f && git reset --hard ) && + git submodule update -N ' test_expect_success 'file with no base' ' - git checkout -b test13 branch1 && - test_must_fail git merge master && - git mergetool --no-prompt --tool mybase -- both && - >expected && - test_cmp both expected && - git reset --hard master >/dev/null 2>&1 + git checkout -b test13 branch1 && + test_must_fail git merge master && + git mergetool --no-prompt --tool mybase -- both && + >expected && + test_cmp both expected && + git reset --hard master >/dev/null 2>&1 ' test_expect_success 'custom commands override built-ins' ' - git checkout -b test14 branch1 && - git config mergetool.defaults.cmd "cat \"\$REMOTE\" >\"\$MERGED\"" && - git config mergetool.defaults.trustExitCode true && - test_must_fail git merge master && - git mergetool --no-prompt --tool defaults -- both && - echo master both added >expected && - test_cmp both expected && - git config --unset mergetool.defaults.cmd && - git config --unset mergetool.defaults.trustExitCode && - git reset --hard master >/dev/null 2>&1 + git checkout -b test14 branch1 && + test_config mergetool.defaults.cmd "cat \"\$REMOTE\" >\"\$MERGED\"" && + test_config mergetool.defaults.trustExitCode true && + test_must_fail git merge master && + git mergetool --no-prompt --tool defaults -- both && + echo master both added >expected && + test_cmp both expected && + git reset --hard master >/dev/null 2>&1 +' + +test_expect_success 'filenames seen by tools start with ./' ' + git checkout -b test15 branch1 && + test_config mergetool.writeToTemp false && + test_config mergetool.myecho.cmd "echo \"\$LOCAL\"" && + test_config mergetool.myecho.trustExitCode true && + test_must_fail git merge master && + git mergetool --no-prompt --tool myecho -- both >actual && + grep ^\./both_LOCAL_ actual >/dev/null && + git reset --hard master >/dev/null 2>&1 +' + +test_expect_success 'temporary filenames are used with mergetool.writeToTemp' ' + git checkout -b test16 branch1 && + test_config mergetool.writeToTemp true && + test_config mergetool.myecho.cmd "echo \"\$LOCAL\"" && + test_config mergetool.myecho.trustExitCode true && + test_must_fail git merge master && + git mergetool --no-prompt --tool myecho -- both >actual && + test_must_fail grep ^\./both_LOCAL_ actual >/dev/null && + grep /both_LOCAL_ actual >/dev/null && + git reset --hard master >/dev/null 2>&1 ' test_done diff --git a/t/t9300-fast-import.sh b/t/t9300-fast-import.sh index 8df0445a84..37c2d633f0 100755 --- a/t/t9300-fast-import.sh +++ b/t/t9300-fast-import.sh @@ -347,36 +347,6 @@ test_expect_success 'B: fail on invalid blob sha1' ' rm -f .git/objects/pack_* .git/objects/index_* cat >input <<INPUT_END -commit .badbranchname -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -corrupt -COMMIT - -from refs/heads/master - -INPUT_END -test_expect_success 'B: fail on invalid branch name ".badbranchname"' ' - test_must_fail git fast-import <input -' -rm -f .git/objects/pack_* .git/objects/index_* - -cat >input <<INPUT_END -commit bad[branch]name -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -corrupt -COMMIT - -from refs/heads/master - -INPUT_END -test_expect_success 'B: fail on invalid branch name "bad[branch]name"' ' - test_must_fail git fast-import <input -' -rm -f .git/objects/pack_* .git/objects/index_* - -cat >input <<INPUT_END commit TEMP_TAG committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE data <<COMMIT diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh index b7957b87bb..0d93e33de4 100644 --- a/t/test-lib-functions.sh +++ b/t/test-lib-functions.sh @@ -413,7 +413,7 @@ test_external () { # test_run_, but keep its stdout on our stdout even in # non-verbose mode. "$@" 2>&4 - if [ "$?" = 0 ] + if test "$?" = 0 then if test $test_external_has_tap -eq 0; then test_ok_ "$descr" @@ -440,11 +440,12 @@ test_external_without_stderr () { tmp=${TMPDIR:-/tmp} stderr="$tmp/git-external-stderr.$$.tmp" test_external "$@" 4> "$stderr" - [ -f "$stderr" ] || error "Internal error: $stderr disappeared." + test -f "$stderr" || error "Internal error: $stderr disappeared." descr="no stderr: $1" shift say >&3 "# expecting no stderr from previous command" - if [ ! -s "$stderr" ]; then + if test ! -s "$stderr" + then rm "$stderr" if test $test_external_has_tap -eq 0; then @@ -454,8 +455,9 @@ test_external_without_stderr () { test_success=$(($test_success + 1)) fi else - if [ "$verbose" = t ]; then - output=`echo; echo "# Stderr is:"; cat "$stderr"` + if test "$verbose" = t + then + output=$(echo; echo "# Stderr is:"; cat "$stderr") else output= fi @@ -474,7 +476,7 @@ test_external_without_stderr () { # The commands test the existence or non-existence of $1. $2 can be # given to provide a more precise diagnosis. test_path_is_file () { - if ! [ -f "$1" ] + if ! test -f "$1" then echo "File $1 doesn't exist. $*" false @@ -482,7 +484,7 @@ test_path_is_file () { } test_path_is_dir () { - if ! [ -d "$1" ] + if ! test -d "$1" then echo "Directory $1 doesn't exist. $*" false @@ -501,11 +503,12 @@ test_dir_is_empty () { } test_path_is_missing () { - if [ -e "$1" ] + if test -e "$1" then echo "Path exists:" ls -ld "$1" - if [ $# -ge 1 ]; then + if test $# -ge 1 + then echo "$*" fi false @@ -666,9 +669,12 @@ test_cmp_rev () { # similar to GNU seq(1), but the latter might not be available # everywhere (and does not do letters). It may be used like: # -# for i in `test_seq 100`; do -# for j in `test_seq 10 20`; do -# for k in `test_seq a z`; do +# for i in $(test_seq 100) +# do +# for j in $(test_seq 10 20) +# do +# for k in $(test_seq a z) +# do # echo $i-$j-$k # done # done diff --git a/test-regex.c b/test-regex.c index b5bfd54139..0dc598ecdc 100644 --- a/test-regex.c +++ b/test-regex.c @@ -1,4 +1,4 @@ -#include <git-compat-util.h> +#include "git-compat-util.h" int main(int argc, char **argv) { diff --git a/test-scrap-cache-tree.c b/test-scrap-cache-tree.c index 9ebcbca9d2..6efee31a48 100644 --- a/test-scrap-cache-tree.c +++ b/test-scrap-cache-tree.c @@ -1,4 +1,5 @@ #include "cache.h" +#include "lockfile.h" #include "tree.h" #include "cache-tree.h" diff --git a/test-sha1-array.c b/test-sha1-array.c new file mode 100644 index 0000000000..ddc491eff9 --- /dev/null +++ b/test-sha1-array.c @@ -0,0 +1,34 @@ +#include "cache.h" +#include "sha1-array.h" + +static void print_sha1(const unsigned char sha1[20], void *data) +{ + puts(sha1_to_hex(sha1)); +} + +int main(int argc, char **argv) +{ + struct sha1_array array = SHA1_ARRAY_INIT; + struct strbuf line = STRBUF_INIT; + + while (strbuf_getline(&line, stdin, '\n') != EOF) { + const char *arg; + unsigned char sha1[20]; + + if (skip_prefix(line.buf, "append ", &arg)) { + if (get_sha1_hex(arg, sha1)) + die("not a hexadecimal SHA1: %s", arg); + sha1_array_append(&array, sha1); + } else if (skip_prefix(line.buf, "lookup ", &arg)) { + if (get_sha1_hex(arg, sha1)) + die("not a hexadecimal SHA1: %s", arg); + printf("%d\n", sha1_array_lookup(&array, sha1)); + } else if (!strcmp(line.buf, "clear")) + sha1_array_clear(&array); + else if (!strcmp(line.buf, "for_each_unique")) + sha1_array_for_each_unique(&array, print_sha1, NULL); + else + die("unknown command: %s", line.buf); + } + return 0; +} diff --git a/test-sigchain.c b/test-sigchain.c index 42db234e87..e499fce60f 100644 --- a/test-sigchain.c +++ b/test-sigchain.c @@ -1,5 +1,5 @@ -#include "sigchain.h" #include "cache.h" +#include "sigchain.h" #define X(f) \ static void f(int sig) { \ diff --git a/thread-utils.h b/thread-utils.h index 6fb98c333c..d9a769d190 100644 --- a/thread-utils.h +++ b/thread-utils.h @@ -7,5 +7,9 @@ extern int online_cpus(void); extern int init_recursive_mutex(pthread_mutex_t*); +#else + +#define online_cpus() 1 + #endif #endif /* THREAD_COMPAT_H */ @@ -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 0000000000..8514566564 --- /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 0000000000..8eb25d565e --- /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 */ diff --git a/transport-helper.c b/transport-helper.c index 2b24d51a24..6cd9dd1f9f 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -108,12 +108,6 @@ static struct child_process *get_helper(struct transport *transport) int refspec_alloc = 0; int duped; int code; - char git_dir_buf[sizeof(GIT_DIR_ENVIRONMENT) + PATH_MAX + 1]; - const char *helper_env[] = { - git_dir_buf, - NULL - }; - if (data->helper) return data->helper; @@ -129,8 +123,8 @@ static struct child_process *get_helper(struct transport *transport) helper->git_cmd = 0; helper->silent_exec_failure = 1; - snprintf(git_dir_buf, sizeof(git_dir_buf), "%s=%s", GIT_DIR_ENVIRONMENT, get_git_dir()); - helper->env = helper_env; + argv_array_pushf(&helper->env_array, "%s=%s", GIT_DIR_ENVIRONMENT, + get_git_dir()); code = start_command(helper); if (code < 0 && errno == ENOENT) @@ -897,7 +891,10 @@ static int push_refs_with_export(struct transport *transport, int flag; /* Follow symbolic refs (mainly for HEAD). */ - name = resolve_ref_unsafe(ref->peer_ref->name, sha1, 1, &flag); + name = resolve_ref_unsafe( + ref->peer_ref->name, + RESOLVE_REF_READING, + sha1, &flag); if (!name || !(flag & REF_ISSYMREF)) name = ref->peer_ref->name; diff --git a/transport.c b/transport.c index 055d2a27d9..70d38e4c4b 100644 --- a/transport.c +++ b/transport.c @@ -168,7 +168,8 @@ static void set_upstreams(struct transport *transport, struct ref *refs, /* Follow symbolic refs (mainly for HEAD). */ localname = ref->peer_ref->name; remotename = ref->name; - tmp = resolve_ref_unsafe(localname, sha, 1, &flag); + tmp = resolve_ref_unsafe(localname, RESOLVE_REF_READING, + sha, &flag); if (tmp && flag & REF_ISSYMREF && starts_with(tmp, "refs/heads/")) localname = tmp; @@ -743,7 +744,7 @@ void transport_print_push_status(const char *dest, struct ref *refs, unsigned char head_sha1[20]; char *head; - head = resolve_refdup("HEAD", head_sha1, 1, NULL); + head = resolve_refdup("HEAD", RESOLVE_REF_READING, head_sha1, NULL); if (verbose) { for (ref = refs; ref; ref = ref->next) @@ -774,6 +775,7 @@ void transport_print_push_status(const char *dest, struct ref *refs, *reject_reasons |= REJECT_NEEDS_FORCE; } } + free(head); } void transport_verify_remote_names(int nr_heads, const char **heads) diff --git a/upload-pack.c b/upload-pack.c index c789ec0050..ac9ac1592d 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -744,7 +744,7 @@ static int find_symref(const char *refname, const unsigned char *sha1, int flag, if ((flag & REF_ISSYMREF) == 0) return 0; - symref_target = resolve_ref_unsafe(refname, unused, 0, &flag); + symref_target = resolve_ref_unsafe(refname, 0, unused, &flag); if (!symref_target || (flag & REF_ISSYMREF) == 0) die("'%s' is a symref but it is not?", refname); item = string_list_append(cb_data, refname); @@ -1,3 +1,4 @@ +#include "git-compat-util.h" #include "varint.h" uintmax_t decode_varint(const unsigned char **bufp) @@ -1,8 +1,6 @@ #ifndef VARINT_H #define VARINT_H -#include "git-compat-util.h" - extern int encode_varint(uintmax_t, unsigned char *); extern uintmax_t decode_varint(const unsigned char **); @@ -300,14 +300,13 @@ int walker_fetch(struct walker *walker, int targets, char **target, strbuf_addf(&refname, "refs/%s", write_ref[i]); if (ref_transaction_update(transaction, refname.buf, &sha1[20 * i], NULL, 0, 0, + msg ? msg : "fetch (unknown)", &err)) { error("%s", err.buf); goto done; } } - if (ref_transaction_commit(transaction, - msg ? msg : "fetch (unknown)", - &err)) { + if (ref_transaction_commit(transaction, &err)) { error("%s", err.buf); goto done; } @@ -466,17 +466,29 @@ int xmkstemp_mode(char *template, int mode) static int warn_if_unremovable(const char *op, const char *file, int rc) { - if (rc < 0) { - int err = errno; - if (ENOENT != err) { - warning("unable to %s %s: %s", - op, file, strerror(errno)); - errno = err; - } - } + int err; + if (!rc || errno == ENOENT) + return 0; + err = errno; + warning("unable to %s %s: %s", op, file, strerror(errno)); + errno = err; return rc; } +int unlink_or_msg(const char *file, struct strbuf *err) +{ + int rc = unlink(file); + + assert(err); + + if (!rc || errno == ENOENT) + return 0; + + strbuf_addf(err, "unable to unlink %s: %s", + file, strerror(errno)); + return -1; +} + int unlink_or_warn(const char *file) { return warn_if_unremovable("unlink", file, unlink(file)); diff --git a/wt-status.c b/wt-status.c index 1bf5d72545..cdbc8d798a 100644 --- a/wt-status.c +++ b/wt-status.c @@ -128,7 +128,7 @@ void wt_status_prepare(struct wt_status *s) s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES; s->use_color = -1; s->relative_paths = 1; - s->branch = resolve_refdup("HEAD", sha1, 0, NULL); + s->branch = resolve_refdup("HEAD", 0, sha1, NULL); s->reference = "HEAD"; s->fp = stdout; s->index_file = get_index_file(); @@ -726,14 +726,14 @@ static void wt_status_print_changed(struct wt_status *s) static void wt_status_print_submodule_summary(struct wt_status *s, int uncommitted) { struct child_process sm_summary = CHILD_PROCESS_INIT; - struct argv_array env = ARGV_ARRAY_INIT; struct argv_array argv = ARGV_ARRAY_INIT; struct strbuf cmd_stdout = STRBUF_INIT; struct strbuf summary = STRBUF_INIT; char *summary_content; size_t len; - argv_array_pushf(&env, "GIT_INDEX_FILE=%s", s->index_file); + argv_array_pushf(&sm_summary.env_array, "GIT_INDEX_FILE=%s", + s->index_file); argv_array_push(&argv, "submodule"); argv_array_push(&argv, "summary"); @@ -745,14 +745,12 @@ static void wt_status_print_submodule_summary(struct wt_status *s, int uncommitt argv_array_push(&argv, s->amend ? "HEAD^" : "HEAD"); sm_summary.argv = argv.argv; - sm_summary.env = env.argv; sm_summary.git_cmd = 1; sm_summary.no_stdin = 1; fflush(s->fp); sm_summary.out = -1; run_command(&sm_summary); - argv_array_clear(&env); argv_array_clear(&argv); len = strbuf_read(&cmd_stdout, sm_summary.out, 1024); |