diff options
94 files changed, 3993 insertions, 1745 deletions
diff --git a/.gitignore b/.gitignore index 6722f78f9a..b1020b875f 100644 --- a/.gitignore +++ b/.gitignore @@ -118,7 +118,6 @@ /git-rebase--merge /git-receive-pack /git-reflog -/git-relink /git-remote /git-remote-http /git-remote-https @@ -225,6 +225,7 @@ Steven Walter <stevenrwalter@gmail.com> <swalter@lexmark.com> Steven Walter <stevenrwalter@gmail.com> <swalter@lpdev.prtdev.lexmark.com> Sven Verdoolaege <skimo@kotnet.org> <Sven.Verdoolaege@cs.kuleuven.ac.be> Sven Verdoolaege <skimo@kotnet.org> <skimo@liacs.nl> +SZEDER Gábor <szeder.dev@gmail.com> <szeder@ira.uka.de> Tay Ray Chuan <rctay89@gmail.com> Ted Percival <ted@midg3t.net> <ted.percival@quest.com> Theodore Ts'o <tytso@mit.edu> diff --git a/.travis.yml b/.travis.yml index 3843967a69..9c63c8c3f6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -75,20 +75,12 @@ before_install: popd ;; osx) - brew_force_set_latest_binary_hash () { - FORMULA=$1 - SHA=$(brew fetch --force $FORMULA 2>&1 | grep ^SHA256: | cut -d ' ' -f 2) - sed -E -i.bak "s/sha256 \"[0-9a-f]{64}\"/sha256 \"$SHA\"/g" \ - "$(brew --repository homebrew/homebrew-binary)/$FORMULA.rb" - } brew update --quiet - brew tap homebrew/binary --quiet - brew_force_set_latest_binary_hash perforce - brew_force_set_latest_binary_hash perforce-server # Uncomment this if you want to run perf tests: # brew install gnu-time - brew install git-lfs perforce-server perforce gettext + brew install git-lfs gettext brew link --force gettext + brew install caskroom/cask/perforce ;; esac; echo "$(tput setaf 6)Perforce Server Version$(tput sgr0)"; diff --git a/Documentation/CodingGuidelines b/Documentation/CodingGuidelines index 4cd95da6b1..a4191aa388 100644 --- a/Documentation/CodingGuidelines +++ b/Documentation/CodingGuidelines @@ -206,11 +206,38 @@ For C programs: x = 1; } - is frowned upon. A gray area is when the statement extends - over a few lines, and/or you have a lengthy comment atop of - it. Also, like in the Linux kernel, if there is a long list - of "else if" statements, it can make sense to add braces to - single line blocks. + is frowned upon. But there are a few exceptions: + + - When the statement extends over a few lines (e.g., a while loop + with an embedded conditional, or a comment). E.g.: + + while (foo) { + if (x) + one(); + else + two(); + } + + if (foo) { + /* + * This one requires some explanation, + * so we're better off with braces to make + * it obvious that the indentation is correct. + */ + doit(); + } + + - When there are multiple arms to a conditional and some of them + require braces, enclose even a single line block in braces for + consistency. E.g.: + + if (foo) { + doit(); + } else { + one(); + two(); + three(); + } - We try to avoid assignments in the condition of an "if" statement. diff --git a/Documentation/Makefile b/Documentation/Makefile index a9fb497b83..b5be2e2d3f 100644 --- a/Documentation/Makefile +++ b/Documentation/Makefile @@ -120,6 +120,7 @@ INSTALL_INFO = install-info DOCBOOK2X_TEXI = docbook2x-texi DBLATEX = dblatex ASCIIDOC_DBLATEX_DIR = /etc/asciidoc/dblatex +DBLATEX_COMMON = -p $(ASCIIDOC_DBLATEX_DIR)/asciidoc-dblatex.xsl -s $(ASCIIDOC_DBLATEX_DIR)/asciidoc-dblatex.sty ifndef PERL_PATH PERL_PATH = /usr/bin/perl endif @@ -173,6 +174,16 @@ ifdef GNU_ROFF XMLTO_EXTRA += -m manpage-quote-apos.xsl endif +ifdef USE_ASCIIDOCTOR +ASCIIDOC = asciidoctor +ASCIIDOC_CONF = +ASCIIDOC_HTML = xhtml5 +ASCIIDOC_DOCBOOK = docbook45 +ASCIIDOC_EXTRA += -I. -rasciidoctor-extensions +ASCIIDOC_EXTRA += -alitdd='&\#x2d;&\#x2d;' +DBLATEX_COMMON = +endif + SHELL_PATH ?= $(SHELL) # Shell quote; SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) @@ -368,13 +379,14 @@ user-manual.texi: user-manual.xml user-manual.pdf: user-manual.xml $(QUIET_DBLATEX)$(RM) $@+ $@ && \ - $(DBLATEX) -o $@+ -p $(ASCIIDOC_DBLATEX_DIR)/asciidoc-dblatex.xsl -s $(ASCIIDOC_DBLATEX_DIR)/asciidoc-dblatex.sty $< && \ + $(DBLATEX) -o $@+ $(DBLATEX_COMMON) $< && \ mv $@+ $@ -gitman.texi: $(MAN_XML) cat-texi.perl +gitman.texi: $(MAN_XML) cat-texi.perl texi.xsl $(QUIET_DB2TEXI)$(RM) $@+ $@ && \ - ($(foreach xml,$(MAN_XML),$(DOCBOOK2X_TEXI) --encoding=UTF-8 \ - --to-stdout $(xml) &&) true) > $@++ && \ + ($(foreach xml,$(sort $(MAN_XML)),xsltproc -o $(xml)+ texi.xsl $(xml) && \ + $(DOCBOOK2X_TEXI) --encoding=UTF-8 --to-stdout $(xml)+ && \ + rm $(xml)+ &&) true) > $@++ && \ $(PERL_PATH) cat-texi.perl $@ <$@++ >$@+ && \ rm $@++ && \ mv $@+ $@ diff --git a/Documentation/RelNotes/2.11.1.txt b/Documentation/RelNotes/2.11.1.txt index 74b193f1a8..9cd14c8197 100644 --- a/Documentation/RelNotes/2.11.1.txt +++ b/Documentation/RelNotes/2.11.1.txt @@ -116,5 +116,53 @@ Fixes since v2.11 will never come. Teach the client side to notice this condition and abort the transfer. + * Some platforms no longer understand "latin-1" that is still seen in + the wild in e-mail headers; replace them with "iso-8859-1" that is + more widely known when conversion fails from/to it. + + * Update the procedure to generate "tags" for developer support. + + * Update the definition of the MacOSX test environment used by + TravisCI. + + * A few git-svn updates. + + * Compression setting for producing packfiles were spread across + three codepaths, one of which did not honor any configuration. + Unify these so that all of them honor core.compression and + pack.compression variables the same way. + + * "git fast-import" sometimes mishandled while rebalancing notes + tree, which has been fixed. + + * Recent update to the default abbreviation length that auto-scales + lacked documentation update, which has been corrected. + + * Leakage of lockfiles in the config subsystem has been fixed. + + * It is natural that "git gc --auto" may not attempt to pack + everything into a single pack, and there is no point in warning + when the user has configured the system to use the pack bitmap, + leading to disabling further "gc". + + * "git archive" did not read the standard configuration files, and + failed to notice a file that is marked as binary via the userdiff + driver configuration. + + * "git blame --porcelain" misidentified the "previous" <commit, path> + pair (aka "source") when contents came from two or more files. + + * "git rebase -i" with a recent update started showing an incorrect + count when squashing more than 10 commits. + + * "git <cmd> @{push}" on a detached HEAD used to segfault; it has + been corrected to error out with a message. + + * Tighten a test to avoid mistaking an extended ERE regexp engine as + a PRE regexp engine. + + * Typing ^C to pager, which usually does not kill it, killed Git and + took the pager down as a collateral damage in certain process-tree + structure. This has been fixed. Also contains various documentation updates and code clean-ups. diff --git a/Documentation/RelNotes/2.12.0.txt b/Documentation/RelNotes/2.12.0.txt index 17409fa147..9c3f94ae2d 100644 --- a/Documentation/RelNotes/2.12.0.txt +++ b/Documentation/RelNotes/2.12.0.txt @@ -11,8 +11,10 @@ Backward compatibility notes. is not scheduled to happen in the upcoming release (yet). * The historical argument order "git merge <msg> HEAD <commit>..." - has been deprecated for quite some time, and will be removed in the - upcoming release. + has been deprecated for quite some time, and will be removed in a + future release. + + * An ancient script "git relink" has been removed. Updates since v2.11 @@ -64,7 +66,6 @@ UI, Workflows & Features * Some platforms no longer understand "latin-1" that is still seen in the wild in e-mail headers; replace them with "iso-8859-1" that is more widely known when conversion fails from/to it. - (merge df3755888b jc/latin-1 later to maint). * "git grep" has been taught to optionally recurse into submodules. @@ -85,6 +86,30 @@ UI, Workflows & Features same release were present (e.g. when 2.0, 2.0-beta1, and 2.0-beta2 are there and the code needs to compare 2.0-beta1 and 2.0-beta2). + * "git submodule push" learned "--recurse-submodules=only option to + push submodules out without pushing the top-level superproject. + + * "git tag" and "git verify-tag" learned to put GPG verification + status in their "--format=<placeholders>" output format. + + * An ancient repository conversion tool left in contrib/ has been + removed. + + * "git show-ref HEAD" used with "--verify" because the user is not + interested in seeing refs/remotes/origin/HEAD, and used with + "--head" because the user does not want HEAD to be filtered out, + i.e. "git show-ref --head --verify HEAD", did not work as expected. + + * "git submodule add" used to be confused and refused to add a + locally created repository; users can now use "--force" option + to add them. + (merge 619acfc78c sb/submodule-add-force later to maint). + + * Some people feel the default set of colors used by "git log --graph" + rather limiting. A mechanism to customize the set of colors has + been introduced. + (merge 512aba261a nd/log-graph-configurable-colors later to maint). + Performance, Internal Implementation, Development Support etc. @@ -101,7 +126,6 @@ Performance, Internal Implementation, Development Support etc. * The character width table has been updated to match Unicode 9.0 * Update the procedure to generate "tags" for developer support. - (merge 046e4c1c09 jk/make-tags-find-sources-tweak later to maint). * The codeflow of setting NOATIME and CLOEXEC on file descriptors Git opens has been simplified. @@ -131,6 +155,15 @@ Performance, Internal Implementation, Development Support etc. * Adjust documentation to help AsciiDoctor render better while not breaking the rendering done by AsciiDoc. + * The sequencer machinery has been further enhanced so that a later + set of patches can start using it to reimplement "rebase -i". + + * Update the definition of the MacOSX test environment used by + TravisCI. + + * Rewrite a scripted porcelain "git difftool" in C. + (merge 94d3997ecc js/difftool-builtin later to maint). + Also contains various documentation updates and code clean-ups. @@ -148,7 +181,6 @@ notes for details). * "git svn" did not work well with path components that are "0", and some configuration variable it uses were not documented. - (merge ea9a93dcc2 ew/svn-fixes later to maint). * "git rev-parse --symbolic" failed with a more recent notation like "HEAD^-1" and "HEAD^!". @@ -271,41 +303,32 @@ notes for details). three codepaths, one of which did not honor any configuration. Unify these so that all of them honor core.compression and pack.compression variables the same way. - (merge 8de7eeb54b jc/compression-config later to maint). * "git fast-import" sometimes mishandled while rebalancing notes tree, which has been fixed. - (merge 405d7f4af6 mh/fast-import-notes-fix-new later to maint). * Recent update to the default abbreviation length that auto-scales lacked documentation update, which has been corrected. - (merge 48d5014dd4 jc/abbrev-autoscale-config later to maint). * Leakage of lockfiles in the config subsystem has been fixed. - (merge c06fa62dfc nd/config-misc-fixes later to maint). * It is natural that "git gc --auto" may not attempt to pack everything into a single pack, and there is no point in warning when the user has configured the system to use the pack bitmap, leading to disabling further "gc". - (merge 1c409a705c dt/disable-bitmap-in-auto-gc later to maint). * "git archive" did not read the standard configuration files, and failed to notice a file that is marked as binary via the userdiff driver configuration. - (merge 965cba2e7e jk/archive-zip-userdiff-config later to maint). * "git blame --porcelain" misidentified the "previous" <commit, path> pair (aka "source") when contents came from two or more files. - (merge 4e76832984 jk/blame-fixes later to maint). * "git rebase -i" with a recent update started showing an incorrect count when squashing more than 10 commits. - (merge 356b8ecff1 jk/rebase-i-squash-count-fix later to maint). * "git <cmd> @{push}" on a detached HEAD used to segfault; it has been corrected to error out with a message. - (merge b10731f43d km/branch-get-push-while-detached later to maint). * Running "git add a/b" when "a" is a submodule correctly errored out, but without a meaningful error message. @@ -314,7 +337,6 @@ notes for details). * Typing ^C to pager, which usually does not kill it, killed Git and took the pager down as a collateral damage in certain process-tree structure. This has been fixed. - (merge 46df6906f3 jk/execv-dashed-external later to maint). * "git mergetool" without any pathspec on the command line that is run from a subdirectory became no-op in Git v2.11 by mistake, which @@ -325,11 +347,77 @@ notes for details). * Tighten a test to avoid mistaking an extended ERE regexp engine as a PRE regexp engine. - (merge 7675c7bd01 jk/grep-e-could-be-extended-beyond-posix later to maint). + + * An error message with an ASCII control character like '\r' in it + can alter the message to hide its early part, which is problematic + when a remote side gives such an error message that the local side + will relay with a "remote: " prefix. + (merge f290089879 jk/vreport-sanitize later to maint). + + * "git fsck" inspects loose objects more carefully now. + (merge cce044df7f jk/loose-object-fsck later to maint). + + * A crashing bug introduced in v2.11 timeframe has been found (it is + triggerable only in fast-import) and fixed. + (merge abd5a00268 jk/clear-delta-base-cache-fix later to maint). + + * With an anticipatory tweak for remotes defined in ~/.gitconfig + (e.g. "remote.origin.prune" set to true, even though there may or + may not actually be "origin" remote defined in a particular Git + repository), "git remote rename" and other commands misinterpreted + and behaved as if such a non-existing remote actually existed. + (merge e459b073fb js/remote-rename-with-half-configured-remote later to maint). + + * A few codepaths had to rely on a global variable when sorting + elements of an array because sort(3) API does not allow extra data + to be passed to the comparison function. Use qsort_s() when + natively available, and a fallback implementation of it when not, + to eliminate the need, which is a prerequisite for making the + codepath reentrant. + (merge 83fc4d64fe rs/qsort-s later to maint). + + * "git fsck --connectivity-check" was not working at all. + (merge a2b22854bd jk/fsck-connectivity-check-fix later to maint). + + * After starting "git rebase -i", which first opens the user's editor + to edit the series of patches to apply, but before saving the + contents of that file, "git status" failed to show the current + state (i.e. you are in an interactive rebase session, but you have + applied no steps yet) correctly. + (merge df9ded4984 js/status-pre-rebase-i later to maint). + + * Test tweak for FreeBSD where /usr/bin/unzip is unsuitable to run + our tests but /usr/local/bin/unzip is usable. + (merge d98b2c5fce js/unzip-in-usr-bin-workaround later to maint). + + * "git p4" did not work well with multiple git-p4.mapUser entries on + Windows. + (merge c3c2b05776 gv/mingw-p4-mapuser later to maint). + + * "git help" enumerates executable files in $PATH; the implementation + of "is this file executable?" on Windows has been optimized. + (merge c755015f79 hv/mingw-help-is-executable later to maint). + + * Test tweaks for those who have default ACL in their git source tree + that interfere with the umask test. + (merge d549d21307 mm/reset-facl-before-umask-test later to maint). + + * Names of the various hook scripts must be spelled exactly, but on + Windows, an .exe binary must be named with .exe suffix; notice + $GIT_DIR/hooks/<hookname>.exe as a valid <hookname> hook. + (merge 235be51fbe js/mingw-hooks-with-exe-suffix later to maint). + + * Asciidoctor, an alternative reimplementation of AsciiDoc, still + needs some changes to work with documents meant to be formatted + with AsciiDoc. "make USE_ASCIIDOCTOR=YesPlease" to use it out of + the box to document our pages is getting closer to reality. + (merge 55d2d812e4 bc/use-asciidoctor-opt later to maint). * Other minor doc, test and build updates and code cleanups. (merge f2627d9b19 sb/submodule-config-cleanup later to maint). (merge 384f1a167b sb/unpack-trees-cleanup later to maint). - (merge 3f05402ac0 ad/bisect-terms later to maint). (merge 874444b704 rh/diff-orderfile-doc later to maint). - (merge c68d2d7c2b ws/request-pull-code-cleanup later to maint). + (merge eafd5d9483 cw/doc-sign-off later to maint). + (merge 0aaad415bc rs/absolute-pathdup later to maint). + (merge 4432dd6b5b rs/receive-pack-cleanup later to maint). + (merge 540a398e9c sg/mailmap-self later to maint). diff --git a/Documentation/SubmittingPatches b/Documentation/SubmittingPatches index 08352deaae..3faf7eb884 100644 --- a/Documentation/SubmittingPatches +++ b/Documentation/SubmittingPatches @@ -216,12 +216,11 @@ that it will be postponed. Exception: If your mailer is mangling patches then someone may ask you to re-send them using MIME, that is OK. -Do not PGP sign your patch, at least for now. Most likely, your -maintainer or other people on the list would not have your PGP -key and would not bother obtaining it anyway. Your patch is not -judged by who you are; a good patch from an unknown origin has a -far better chance of being accepted than a patch from a known, -respected origin that is done poorly or does incorrect things. +Do not PGP sign your patch. Most likely, your maintainer or other people on the +list would not have your PGP key and would not bother obtaining it anyway. +Your patch is not judged by who you are; a good patch from an unknown origin +has a far better chance of being accepted than a patch from a known, respected +origin that is done poorly or does incorrect things. If you really really really really want to do a PGP signed patch, format it as "multipart/signed", not a text/plain message @@ -246,7 +245,7 @@ patch. *2* The mailing list: git@vger.kernel.org -(5) Sign your work +(5) Certify your work by adding your "Signed-off-by: " line To improve tracking of who did what, we've borrowed the "sign-off" procedure from the Linux kernel project on patches diff --git a/Documentation/asciidoctor-extensions.rb b/Documentation/asciidoctor-extensions.rb new file mode 100644 index 0000000000..ec83b4959e --- /dev/null +++ b/Documentation/asciidoctor-extensions.rb @@ -0,0 +1,28 @@ +require 'asciidoctor' +require 'asciidoctor/extensions' + +module Git + module Documentation + class LinkGitProcessor < Asciidoctor::Extensions::InlineMacroProcessor + use_dsl + + named :chrome + + def process(parent, target, attrs) + if parent.document.basebackend? 'html' + prefix = parent.document.attr('git-relative-html-prefix') + %(<a href="#{prefix}#{target}.html">#{target}(#{attrs[1]})</a>\n) + elsif parent.document.basebackend? 'docbook' + "<citerefentry>\n" \ + "<refentrytitle>#{target}</refentrytitle>" \ + "<manvolnum>#{attrs[1]}</manvolnum>\n" \ + "</citerefentry>\n" + end + end + end + end +end + +Asciidoctor::Extensions.register do + inline_macro Git::Documentation::LinkGitProcessor, :linkgit +end diff --git a/Documentation/cat-texi.perl b/Documentation/cat-texi.perl index 87437f8a95..14d2f83415 100755 --- a/Documentation/cat-texi.perl +++ b/Documentation/cat-texi.perl @@ -1,9 +1,12 @@ #!/usr/bin/perl -w +use strict; +use warnings; + my @menu = (); my $output = $ARGV[0]; -open TMP, '>', "$output.tmp"; +open my $tmp, '>', "$output.tmp"; while (<STDIN>) { next if (/^\\input texinfo/../\@node Top/); @@ -11,13 +14,13 @@ while (<STDIN>) { if (s/^\@top (.*)/\@node $1,,,Top/) { push @menu, $1; } - s/\(\@pxref{\[(URLS|REMOTES)\]}\)//; + s/\(\@pxref\{\[(URLS|REMOTES)\]}\)//; s/\@anchor\{[^{}]*\}//g; - print TMP; + print $tmp $_; } -close TMP; +close $tmp; -printf '\input texinfo +print '\input texinfo @setfilename gitman.info @documentencoding UTF-8 @dircategory Development @@ -28,16 +31,16 @@ printf '\input texinfo @top Git Manual Pages @documentlanguage en @menu -', $menu[0]; +'; for (@menu) { print "* ${_}::\n"; } print "\@end menu\n"; -open TMP, '<', "$output.tmp"; -while (<TMP>) { +open $tmp, '<', "$output.tmp"; +while (<$tmp>) { print; } -close TMP; +close $tmp; print "\@bye\n"; unlink "$output.tmp"; diff --git a/Documentation/config.txt b/Documentation/config.txt index af2ae4cc02..fc78139c21 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -170,6 +170,9 @@ The position of any attributes with respect to the colors be turned off by prefixing them with `no` or `no-` (e.g., `noreverse`, `no-ul`, etc). + +An empty color string produces no color effect at all. This can be used +to avoid coloring specific elements without disabling color entirely. ++ For git's pre-defined color slots, the attributes are meant to be reset at the beginning of each item in the colored output. So setting `color.decorate.branch` to `black` will paint that branch name in a @@ -2036,6 +2039,10 @@ log.follow:: i.e. it cannot be used to follow multiple files and does not work well on non-linear history. +log.graphColors:: + A list of colors, separated by commas, that can be used to draw + history lines in `git log --graph`. + log.showRoot:: If true, the initial commit will be shown as a big creation event. This is equivalent to a diff against an empty tree. diff --git a/Documentation/git-relink.txt b/Documentation/git-relink.txt deleted file mode 100644 index 3b33c99510..0000000000 --- a/Documentation/git-relink.txt +++ /dev/null @@ -1,30 +0,0 @@ -git-relink(1) -============= - -NAME ----- -git-relink - Hardlink common objects in local repositories - -SYNOPSIS --------- -[verse] -'git relink' [--safe] <dir>... <master_dir> - -DESCRIPTION ------------ -This will scan 1 or more object repositories and look for objects in common -with a master repository. Objects not already hardlinked to the master -repository will be replaced with a hardlink to the master repository. - -OPTIONS -------- ---safe:: - Stops if two objects with the same hash exist but have different sizes. - Default is to warn and continue. - -<dir>:: - Directories containing a .git/objects/ subdirectory. - -GIT ---- -Part of the linkgit:git[1] suite diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt index 5055a96823..8e70c5b6a4 100644 --- a/Documentation/git-tag.txt +++ b/Documentation/git-tag.txt @@ -15,7 +15,7 @@ SYNOPSIS 'git tag' [-n[<num>]] -l [--contains <commit>] [--points-at <object>] [--column[=<options>] | --no-column] [--create-reflog] [--sort=<key>] [--format=<format>] [--[no-]merged [<commit>]] [<pattern>...] -'git tag' -v <tagname>... +'git tag' -v [--format=<format>] <tagname>... DESCRIPTION ----------- diff --git a/Documentation/git-verify-tag.txt b/Documentation/git-verify-tag.txt index d590edcebd..0b8075dad9 100644 --- a/Documentation/git-verify-tag.txt +++ b/Documentation/git-verify-tag.txt @@ -8,7 +8,7 @@ git-verify-tag - Check the GPG signature of tags SYNOPSIS -------- [verse] -'git verify-tag' <tag>... +'git verify-tag' [--format=<format>] <tag>... DESCRIPTION ----------- diff --git a/Documentation/technical/api-hashmap.txt b/Documentation/technical/api-hashmap.txt index 28f5a8b715..a3f020cd9e 100644 --- a/Documentation/technical/api-hashmap.txt +++ b/Documentation/technical/api-hashmap.txt @@ -188,7 +188,9 @@ Returns the removed entry, or NULL if not found. `void *hashmap_iter_next(struct hashmap_iter *iter)`:: `void *hashmap_iter_first(struct hashmap *map, struct hashmap_iter *iter)`:: - Used to iterate over all entries of a hashmap. + Used to iterate over all entries of a hashmap. Note that it is + not safe to add or remove entries to the hashmap while + iterating. + `hashmap_iter_init` initializes a `hashmap_iter` structure. + diff --git a/Documentation/technical/api-in-core-index.txt b/Documentation/technical/api-in-core-index.txt deleted file mode 100644 index adbdbf5d75..0000000000 --- a/Documentation/technical/api-in-core-index.txt +++ /dev/null @@ -1,21 +0,0 @@ -in-core index API -================= - -Talk about <read-cache.c> and <cache-tree.c>, things like: - -* cache -> the_index macros -* read_index() -* write_index() -* ie_match_stat() and ie_modified(); how they are different and when to - use which. -* index_name_pos() -* remove_index_entry_at() -* remove_file_from_index() -* add_file_to_index() -* add_index_entry() -* refresh_index() -* discard_index() -* cache_tree_invalidate_path() -* cache_tree_update() - -(JC, Linus) diff --git a/Documentation/texi.xsl b/Documentation/texi.xsl new file mode 100644 index 0000000000..0f8ff07eca --- /dev/null +++ b/Documentation/texi.xsl @@ -0,0 +1,26 @@ +<!-- texi.xsl: + convert refsection elements into refsect elements that docbook2texi can + understand --> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + version="1.0"> + +<xsl:output method="xml" + encoding="UTF-8" + doctype-public="-//OASIS//DTD DocBook XML V4.5//EN" + doctype-system="http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" /> + +<xsl:template match="//refsection"> + <xsl:variable name="element">refsect<xsl:value-of select="count(ancestor-or-self::refsection)" /></xsl:variable> + <xsl:element name="{$element}"> + <xsl:apply-templates select="@*|node()" /> + </xsl:element> +</xsl:template> + +<!-- Copy all other nodes through. --> +<xsl:template match="node()|@*"> + <xsl:copy> + <xsl:apply-templates select="@*|node()" /> + </xsl:copy> +</xsl:template> + +</xsl:stylesheet> @@ -250,6 +250,12 @@ all:: # apostrophes to be ASCII so that cut&pasting examples to the shell # will work. # +# Define USE_ASCIIDOCTOR to use Asciidoctor instead of AsciiDoc to build the +# documentation. +# +# Define ASCIIDOCTOR_EXTENSIONS_LAB to point to the location of the Asciidoctor +# Extensions Lab if you have it available. +# # Define PERL_PATH to the path of your Perl binary (usually /usr/bin/perl). # # Define NO_PERL_MAKEMAKER if you cannot use Makefiles generated by perl's @@ -279,6 +285,9 @@ all:: # is a simplified version of the merge sort used in glibc. This is # recommended if Git triggers O(n^2) behavior in your platform's qsort(). # +# Define HAVE_ISO_QSORT_S if your platform provides a qsort_s() that's +# compatible with the one described in C11 Annex K. +# # Define UNRELIABLE_FSTAT if your system's fstat does not return the same # information on a not yet closed file that lstat would return for the same # file after it was closed. @@ -522,12 +531,10 @@ SCRIPT_LIB += git-sh-setup SCRIPT_LIB += git-sh-i18n SCRIPT_PERL += git-add--interactive.perl -SCRIPT_PERL += git-difftool.perl SCRIPT_PERL += git-archimport.perl SCRIPT_PERL += git-cvsexportcommit.perl SCRIPT_PERL += git-cvsimport.perl SCRIPT_PERL += git-cvsserver.perl -SCRIPT_PERL += git-relink.perl SCRIPT_PERL += git-send-email.perl SCRIPT_PERL += git-svn.perl @@ -883,6 +890,7 @@ BUILTIN_OBJS += builtin/diff-files.o BUILTIN_OBJS += builtin/diff-index.o BUILTIN_OBJS += builtin/diff-tree.o BUILTIN_OBJS += builtin/diff.o +BUILTIN_OBJS += builtin/difftool.o BUILTIN_OBJS += builtin/fast-export.o BUILTIN_OBJS += builtin/fetch-pack.o BUILTIN_OBJS += builtin/fetch.o @@ -1418,6 +1426,11 @@ ifdef INTERNAL_QSORT COMPAT_CFLAGS += -DINTERNAL_QSORT COMPAT_OBJS += compat/qsort.o endif +ifdef HAVE_ISO_QSORT_S + COMPAT_CFLAGS += -DHAVE_ISO_QSORT_S +else + COMPAT_OBJS += compat/qsort_s.o +endif ifdef RUNTIME_PREFIX COMPAT_CFLAGS += -DRUNTIME_PREFIX endif @@ -239,6 +239,13 @@ const char *absolute_path(const char *path) return sb.buf; } +char *absolute_pathdup(const char *path) +{ + struct strbuf sb = STRBUF_INIT; + strbuf_add_absolute_path(&sb, path); + return strbuf_detach(&sb, NULL); +} + /* * Unlike prefix_path, this should be used if the named file does * not have to interact with index entry; i.e. name of a random file @@ -60,6 +60,7 @@ extern int cmd_diff_files(int argc, const char **argv, const char *prefix); extern int cmd_diff_index(int argc, const char **argv, const char *prefix); extern int cmd_diff(int argc, const char **argv, const char *prefix); extern int cmd_diff_tree(int argc, const char **argv, const char *prefix); +extern int cmd_difftool(int argc, const char **argv, const char *prefix); extern int cmd_fast_export(int argc, const char **argv, const char *prefix); extern int cmd_fetch(int argc, const char **argv, const char *prefix); extern int cmd_fetch_pack(int argc, const char **argv, const char *prefix); diff --git a/builtin/clone.c b/builtin/clone.c index 5ef81927a6..3f63edbbf9 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -170,7 +170,7 @@ static char *get_repo_path(const char *repo, int *is_bundle) strbuf_addstr(&path, repo); raw = get_repo_path_1(&path, is_bundle); - canon = raw ? xstrdup(absolute_path(raw)) : NULL; + canon = raw ? absolute_pathdup(raw) : NULL; strbuf_release(&path); return canon; } @@ -894,7 +894,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) path = get_repo_path(repo_name, &is_bundle); if (path) - repo = xstrdup(absolute_path(repo_name)); + repo = absolute_pathdup(repo_name); else if (!strchr(repo_name, ':')) die(_("repository '%s' does not exist"), repo_name); else diff --git a/builtin/commit.c b/builtin/commit.c index 711f96cc43..2de5f6cc64 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -960,15 +960,15 @@ static int prepare_to_commit(const char *index_file, const char *prefix, return 0; if (use_editor) { - char index[PATH_MAX]; - const char *env[2] = { NULL }; - env[0] = index; - snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", index_file); - if (launch_editor(git_path_commit_editmsg(), NULL, env)) { + struct argv_array env = ARGV_ARRAY_INIT; + + argv_array_pushf(&env, "GIT_INDEX_FILE=%s", index_file); + if (launch_editor(git_path_commit_editmsg(), NULL, env.argv)) { fprintf(stderr, _("Please supply the message using either -m or -F option.\n")); exit(1); } + argv_array_clear(&env); } if (!no_verify && @@ -1525,12 +1525,10 @@ static int git_commit_config(const char *k, const char *v, void *cb) static int run_rewrite_hook(const unsigned char *oldsha1, const unsigned char *newsha1) { - /* oldsha1 SP newsha1 LF NUL */ - static char buf[2*40 + 3]; struct child_process proc = CHILD_PROCESS_INIT; const char *argv[3]; int code; - size_t n; + struct strbuf sb = STRBUF_INIT; argv[0] = find_hook("post-rewrite"); if (!argv[0]) @@ -1546,34 +1544,33 @@ static int run_rewrite_hook(const unsigned char *oldsha1, code = start_command(&proc); if (code) return code; - n = snprintf(buf, sizeof(buf), "%s %s\n", - sha1_to_hex(oldsha1), sha1_to_hex(newsha1)); + strbuf_addf(&sb, "%s %s\n", sha1_to_hex(oldsha1), sha1_to_hex(newsha1)); sigchain_push(SIGPIPE, SIG_IGN); - write_in_full(proc.in, buf, n); + write_in_full(proc.in, sb.buf, sb.len); close(proc.in); + strbuf_release(&sb); sigchain_pop(SIGPIPE); return finish_command(&proc); } int run_commit_hook(int editor_is_used, const char *index_file, const char *name, ...) { - const char *hook_env[3] = { NULL }; - char index[PATH_MAX]; + struct argv_array hook_env = ARGV_ARRAY_INIT; va_list args; int ret; - snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", index_file); - hook_env[0] = index; + argv_array_pushf(&hook_env, "GIT_INDEX_FILE=%s", index_file); /* * Let the hook know that no editor will be launched. */ if (!editor_is_used) - hook_env[1] = "GIT_EDITOR=:"; + argv_array_push(&hook_env, "GIT_EDITOR=:"); va_start(args, name); - ret = run_hook_ve(hook_env, name, args); + ret = run_hook_ve(hook_env.argv,name, args); va_end(args); + argv_array_clear(&hook_env); return ret; } diff --git a/builtin/difftool.c b/builtin/difftool.c new file mode 100644 index 0000000000..b5e85ab079 --- /dev/null +++ b/builtin/difftool.c @@ -0,0 +1,692 @@ +/* + * "git difftool" builtin command + * + * This is a wrapper around the GIT_EXTERNAL_DIFF-compatible + * git-difftool--helper script. + * + * This script exports GIT_EXTERNAL_DIFF and GIT_PAGER for use by git. + * The GIT_DIFF* variables are exported for use by git-difftool--helper. + * + * Any arguments that are unknown to this script are forwarded to 'git diff'. + * + * Copyright (C) 2016 Johannes Schindelin + */ +#include "cache.h" +#include "builtin.h" +#include "run-command.h" +#include "exec_cmd.h" +#include "parse-options.h" +#include "argv-array.h" +#include "strbuf.h" +#include "lockfile.h" +#include "dir.h" + +static char *diff_gui_tool; +static int trust_exit_code; + +static const char *const builtin_difftool_usage[] = { + N_("git difftool [<options>] [<commit> [<commit>]] [--] [<path>...]"), + NULL +}; + +static int difftool_config(const char *var, const char *value, void *cb) +{ + if (!strcmp(var, "diff.guitool")) { + diff_gui_tool = xstrdup(value); + return 0; + } + + if (!strcmp(var, "difftool.trustexitcode")) { + trust_exit_code = git_config_bool(var, value); + return 0; + } + + return git_default_config(var, value, cb); +} + +static int print_tool_help(void) +{ + const char *argv[] = { "mergetool", "--tool-help=diff", NULL }; + return run_command_v_opt(argv, RUN_GIT_CMD); +} + +static int parse_index_info(char *p, int *mode1, int *mode2, + struct object_id *oid1, struct object_id *oid2, + char *status) +{ + if (*p != ':') + return error("expected ':', got '%c'", *p); + *mode1 = (int)strtol(p + 1, &p, 8); + if (*p != ' ') + return error("expected ' ', got '%c'", *p); + *mode2 = (int)strtol(p + 1, &p, 8); + if (*p != ' ') + return error("expected ' ', got '%c'", *p); + if (get_oid_hex(++p, oid1)) + return error("expected object ID, got '%s'", p + 1); + p += GIT_SHA1_HEXSZ; + if (*p != ' ') + return error("expected ' ', got '%c'", *p); + if (get_oid_hex(++p, oid2)) + return error("expected object ID, got '%s'", p + 1); + p += GIT_SHA1_HEXSZ; + if (*p != ' ') + return error("expected ' ', got '%c'", *p); + *status = *++p; + if (!*status) + return error("missing status"); + if (p[1] && !isdigit(p[1])) + return error("unexpected trailer: '%s'", p + 1); + return 0; +} + +/* + * Remove any trailing slash from $workdir + * before starting to avoid double slashes in symlink targets. + */ +static void add_path(struct strbuf *buf, size_t base_len, const char *path) +{ + strbuf_setlen(buf, base_len); + if (buf->len && buf->buf[buf->len - 1] != '/') + strbuf_addch(buf, '/'); + strbuf_addstr(buf, path); +} + +/* + * Determine whether we can simply reuse the file in the worktree. + */ +static int use_wt_file(const char *workdir, const char *name, + struct object_id *oid) +{ + struct strbuf buf = STRBUF_INIT; + struct stat st; + int use = 0; + + strbuf_addstr(&buf, workdir); + add_path(&buf, buf.len, name); + + if (!lstat(buf.buf, &st) && !S_ISLNK(st.st_mode)) { + struct object_id wt_oid; + int fd = open(buf.buf, O_RDONLY); + + if (fd >= 0 && + !index_fd(wt_oid.hash, fd, &st, OBJ_BLOB, name, 0)) { + if (is_null_oid(oid)) { + oidcpy(oid, &wt_oid); + use = 1; + } else if (!oidcmp(oid, &wt_oid)) + use = 1; + } + } + + strbuf_release(&buf); + + return use; +} + +struct working_tree_entry { + struct hashmap_entry entry; + char path[FLEX_ARRAY]; +}; + +static int working_tree_entry_cmp(struct working_tree_entry *a, + struct working_tree_entry *b, void *keydata) +{ + return strcmp(a->path, b->path); +} + +/* + * The `left` and `right` entries hold paths for the symlinks hashmap, + * and a SHA-1 surrounded by brief text for submodules. + */ +struct pair_entry { + struct hashmap_entry entry; + char left[PATH_MAX], right[PATH_MAX]; + const char path[FLEX_ARRAY]; +}; + +static int pair_cmp(struct pair_entry *a, struct pair_entry *b, void *keydata) +{ + return strcmp(a->path, b->path); +} + +static void add_left_or_right(struct hashmap *map, const char *path, + const char *content, int is_right) +{ + struct pair_entry *e, *existing; + + FLEX_ALLOC_STR(e, path, path); + hashmap_entry_init(e, strhash(path)); + existing = hashmap_get(map, e, NULL); + if (existing) { + free(e); + e = existing; + } else { + e->left[0] = e->right[0] = '\0'; + hashmap_add(map, e); + } + strlcpy(is_right ? e->right : e->left, content, PATH_MAX); +} + +struct path_entry { + struct hashmap_entry entry; + char path[FLEX_ARRAY]; +}; + +static int path_entry_cmp(struct path_entry *a, struct path_entry *b, void *key) +{ + return strcmp(a->path, key ? key : b->path); +} + +static void changed_files(struct hashmap *result, const char *index_path, + const char *workdir) +{ + struct child_process update_index = CHILD_PROCESS_INIT; + struct child_process diff_files = CHILD_PROCESS_INIT; + struct strbuf index_env = STRBUF_INIT, buf = STRBUF_INIT; + const char *git_dir = absolute_path(get_git_dir()), *env[] = { + NULL, NULL + }; + FILE *fp; + + strbuf_addf(&index_env, "GIT_INDEX_FILE=%s", index_path); + env[0] = index_env.buf; + + argv_array_pushl(&update_index.args, + "--git-dir", git_dir, "--work-tree", workdir, + "update-index", "--really-refresh", "-q", + "--unmerged", NULL); + update_index.no_stdin = 1; + update_index.no_stdout = 1; + update_index.no_stderr = 1; + update_index.git_cmd = 1; + update_index.use_shell = 0; + update_index.clean_on_exit = 1; + update_index.dir = workdir; + update_index.env = env; + /* Ignore any errors of update-index */ + run_command(&update_index); + + argv_array_pushl(&diff_files.args, + "--git-dir", git_dir, "--work-tree", workdir, + "diff-files", "--name-only", "-z", NULL); + diff_files.no_stdin = 1; + diff_files.git_cmd = 1; + diff_files.use_shell = 0; + diff_files.clean_on_exit = 1; + diff_files.out = -1; + diff_files.dir = workdir; + diff_files.env = env; + if (start_command(&diff_files)) + die("could not obtain raw diff"); + fp = xfdopen(diff_files.out, "r"); + while (!strbuf_getline_nul(&buf, fp)) { + struct path_entry *entry; + FLEX_ALLOC_STR(entry, path, buf.buf); + hashmap_entry_init(entry, strhash(buf.buf)); + hashmap_add(result, entry); + } + if (finish_command(&diff_files)) + die("diff-files did not exit properly"); + strbuf_release(&index_env); + strbuf_release(&buf); +} + +static NORETURN void exit_cleanup(const char *tmpdir, int exit_code) +{ + struct strbuf buf = STRBUF_INIT; + strbuf_addstr(&buf, tmpdir); + remove_dir_recursively(&buf, 0); + if (exit_code) + warning(_("failed: %d"), exit_code); + exit(exit_code); +} + +static int ensure_leading_directories(char *path) +{ + switch (safe_create_leading_directories(path)) { + case SCLD_OK: + case SCLD_EXISTS: + return 0; + default: + return error(_("could not create leading directories " + "of '%s'"), path); + } +} + +static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix, + int argc, const char **argv) +{ + char tmpdir[PATH_MAX]; + struct strbuf info = STRBUF_INIT, lpath = STRBUF_INIT; + struct strbuf rpath = STRBUF_INIT, buf = STRBUF_INIT; + struct strbuf ldir = STRBUF_INIT, rdir = STRBUF_INIT; + struct strbuf wtdir = STRBUF_INIT; + size_t ldir_len, rdir_len, wtdir_len; + struct cache_entry *ce = xcalloc(1, sizeof(ce) + PATH_MAX + 1); + const char *workdir, *tmp; + int ret = 0, i; + FILE *fp; + struct hashmap working_tree_dups, submodules, symlinks2; + struct hashmap_iter iter; + struct pair_entry *entry; + enum object_type type; + unsigned long size; + struct index_state wtindex; + struct checkout lstate, rstate; + int rc, flags = RUN_GIT_CMD, err = 0; + struct child_process child = CHILD_PROCESS_INIT; + const char *helper_argv[] = { "difftool--helper", NULL, NULL, NULL }; + struct hashmap wt_modified, tmp_modified; + int indices_loaded = 0; + + workdir = get_git_work_tree(); + + /* Setup temp directories */ + tmp = getenv("TMPDIR"); + xsnprintf(tmpdir, sizeof(tmpdir), "%s/git-difftool.XXXXXX", tmp ? tmp : "/tmp"); + if (!mkdtemp(tmpdir)) + return error("could not create '%s'", tmpdir); + strbuf_addf(&ldir, "%s/left/", tmpdir); + strbuf_addf(&rdir, "%s/right/", tmpdir); + strbuf_addstr(&wtdir, workdir); + if (!wtdir.len || !is_dir_sep(wtdir.buf[wtdir.len - 1])) + strbuf_addch(&wtdir, '/'); + mkdir(ldir.buf, 0700); + mkdir(rdir.buf, 0700); + + memset(&wtindex, 0, sizeof(wtindex)); + + memset(&lstate, 0, sizeof(lstate)); + lstate.base_dir = ldir.buf; + lstate.base_dir_len = ldir.len; + lstate.force = 1; + memset(&rstate, 0, sizeof(rstate)); + rstate.base_dir = rdir.buf; + rstate.base_dir_len = rdir.len; + rstate.force = 1; + + ldir_len = ldir.len; + rdir_len = rdir.len; + wtdir_len = wtdir.len; + + hashmap_init(&working_tree_dups, + (hashmap_cmp_fn)working_tree_entry_cmp, 0); + hashmap_init(&submodules, (hashmap_cmp_fn)pair_cmp, 0); + hashmap_init(&symlinks2, (hashmap_cmp_fn)pair_cmp, 0); + + child.no_stdin = 1; + child.git_cmd = 1; + child.use_shell = 0; + child.clean_on_exit = 1; + child.dir = prefix; + child.out = -1; + argv_array_pushl(&child.args, "diff", "--raw", "--no-abbrev", "-z", + NULL); + for (i = 0; i < argc; i++) + argv_array_push(&child.args, argv[i]); + if (start_command(&child)) + die("could not obtain raw diff"); + fp = xfdopen(child.out, "r"); + + /* Build index info for left and right sides of the diff */ + i = 0; + while (!strbuf_getline_nul(&info, fp)) { + int lmode, rmode; + struct object_id loid, roid; + char status; + const char *src_path, *dst_path; + size_t src_path_len, dst_path_len; + + if (starts_with(info.buf, "::")) + die(N_("combined diff formats('-c' and '--cc') are " + "not supported in\n" + "directory diff mode('-d' and '--dir-diff').")); + + if (parse_index_info(info.buf, &lmode, &rmode, &loid, &roid, + &status)) + break; + if (strbuf_getline_nul(&lpath, fp)) + break; + src_path = lpath.buf; + src_path_len = lpath.len; + + i++; + if (status != 'C' && status != 'R') { + dst_path = src_path; + dst_path_len = src_path_len; + } else { + if (strbuf_getline_nul(&rpath, fp)) + break; + dst_path = rpath.buf; + dst_path_len = rpath.len; + } + + if (S_ISGITLINK(lmode) || S_ISGITLINK(rmode)) { + strbuf_reset(&buf); + strbuf_addf(&buf, "Subproject commit %s", + oid_to_hex(&loid)); + add_left_or_right(&submodules, src_path, buf.buf, 0); + strbuf_reset(&buf); + strbuf_addf(&buf, "Subproject commit %s", + oid_to_hex(&roid)); + if (!oidcmp(&loid, &roid)) + strbuf_addstr(&buf, "-dirty"); + add_left_or_right(&submodules, dst_path, buf.buf, 1); + continue; + } + + if (S_ISLNK(lmode)) { + char *content = read_sha1_file(loid.hash, &type, &size); + add_left_or_right(&symlinks2, src_path, content, 0); + free(content); + } + + if (S_ISLNK(rmode)) { + char *content = read_sha1_file(roid.hash, &type, &size); + add_left_or_right(&symlinks2, dst_path, content, 1); + free(content); + } + + if (lmode && status != 'C') { + ce->ce_mode = lmode; + oidcpy(&ce->oid, &loid); + strcpy(ce->name, src_path); + ce->ce_namelen = src_path_len; + if (checkout_entry(ce, &lstate, NULL)) + return error("could not write '%s'", src_path); + } + + if (rmode) { + struct working_tree_entry *entry; + + /* Avoid duplicate working_tree entries */ + FLEX_ALLOC_STR(entry, path, dst_path); + hashmap_entry_init(entry, strhash(dst_path)); + if (hashmap_get(&working_tree_dups, entry, NULL)) { + free(entry); + continue; + } + hashmap_add(&working_tree_dups, entry); + + if (!use_wt_file(workdir, dst_path, &roid)) { + ce->ce_mode = rmode; + oidcpy(&ce->oid, &roid); + strcpy(ce->name, dst_path); + ce->ce_namelen = dst_path_len; + if (checkout_entry(ce, &rstate, NULL)) + return error("could not write '%s'", + dst_path); + } else if (!is_null_oid(&roid)) { + /* + * Changes in the working tree need special + * treatment since they are not part of the + * index. + */ + struct cache_entry *ce2 = + make_cache_entry(rmode, roid.hash, + dst_path, 0, 0); + + add_index_entry(&wtindex, ce2, + ADD_CACHE_JUST_APPEND); + + add_path(&rdir, rdir_len, dst_path); + if (ensure_leading_directories(rdir.buf)) + return error("could not create " + "directory for '%s'", + dst_path); + add_path(&wtdir, wtdir_len, dst_path); + if (symlinks) { + if (symlink(wtdir.buf, rdir.buf)) { + ret = error_errno("could not symlink '%s' to '%s'", wtdir.buf, rdir.buf); + goto finish; + } + } else { + struct stat st; + if (stat(wtdir.buf, &st)) + st.st_mode = 0644; + if (copy_file(rdir.buf, wtdir.buf, + st.st_mode)) { + ret = error("could not copy '%s' to '%s'", wtdir.buf, rdir.buf); + goto finish; + } + } + } + } + } + + if (finish_command(&child)) { + ret = error("error occurred running diff --raw"); + goto finish; + } + + if (!i) + return 0; + + /* + * Changes to submodules require special treatment.This loop writes a + * temporary file to both the left and right directories to show the + * change in the recorded SHA1 for the submodule. + */ + hashmap_iter_init(&submodules, &iter); + while ((entry = hashmap_iter_next(&iter))) { + if (*entry->left) { + add_path(&ldir, ldir_len, entry->path); + ensure_leading_directories(ldir.buf); + write_file(ldir.buf, "%s", entry->left); + } + if (*entry->right) { + add_path(&rdir, rdir_len, entry->path); + ensure_leading_directories(rdir.buf); + write_file(rdir.buf, "%s", entry->right); + } + } + + /* + * Symbolic links require special treatment.The standard "git diff" + * shows only the link itself, not the contents of the link target. + * This loop replicates that behavior. + */ + hashmap_iter_init(&symlinks2, &iter); + while ((entry = hashmap_iter_next(&iter))) { + if (*entry->left) { + add_path(&ldir, ldir_len, entry->path); + ensure_leading_directories(ldir.buf); + write_file(ldir.buf, "%s", entry->left); + } + if (*entry->right) { + add_path(&rdir, rdir_len, entry->path); + ensure_leading_directories(rdir.buf); + write_file(rdir.buf, "%s", entry->right); + } + } + + strbuf_release(&buf); + + strbuf_setlen(&ldir, ldir_len); + helper_argv[1] = ldir.buf; + strbuf_setlen(&rdir, rdir_len); + helper_argv[2] = rdir.buf; + + if (extcmd) { + helper_argv[0] = extcmd; + flags = 0; + } else + setenv("GIT_DIFFTOOL_DIRDIFF", "true", 1); + rc = run_command_v_opt(helper_argv, flags); + + /* + * If the diff includes working copy files and those + * files were modified during the diff, then the changes + * should be copied back to the working tree. + * Do not copy back files when symlinks are used and the + * external tool did not replace the original link with a file. + * + * These hashes are loaded lazily since they aren't needed + * in the common case of --symlinks and the difftool updating + * files through the symlink. + */ + hashmap_init(&wt_modified, (hashmap_cmp_fn)path_entry_cmp, + wtindex.cache_nr); + hashmap_init(&tmp_modified, (hashmap_cmp_fn)path_entry_cmp, + wtindex.cache_nr); + + for (i = 0; i < wtindex.cache_nr; i++) { + struct hashmap_entry dummy; + const char *name = wtindex.cache[i]->name; + struct stat st; + + add_path(&rdir, rdir_len, name); + if (lstat(rdir.buf, &st)) + continue; + + if ((symlinks && S_ISLNK(st.st_mode)) || !S_ISREG(st.st_mode)) + continue; + + if (!indices_loaded) { + static struct lock_file lock; + strbuf_reset(&buf); + strbuf_addf(&buf, "%s/wtindex", tmpdir); + if (hold_lock_file_for_update(&lock, buf.buf, 0) < 0 || + write_locked_index(&wtindex, &lock, COMMIT_LOCK)) { + ret = error("could not write %s", buf.buf); + rollback_lock_file(&lock); + goto finish; + } + changed_files(&wt_modified, buf.buf, workdir); + strbuf_setlen(&rdir, rdir_len); + changed_files(&tmp_modified, buf.buf, rdir.buf); + add_path(&rdir, rdir_len, name); + indices_loaded = 1; + } + + hashmap_entry_init(&dummy, strhash(name)); + if (hashmap_get(&tmp_modified, &dummy, name)) { + add_path(&wtdir, wtdir_len, name); + if (hashmap_get(&wt_modified, &dummy, name)) { + warning(_("both files modified: '%s' and '%s'."), + wtdir.buf, rdir.buf); + warning(_("working tree file has been left.")); + warning("%s", ""); + err = 1; + } else if (unlink(wtdir.buf) || + copy_file(wtdir.buf, rdir.buf, st.st_mode)) + warning_errno(_("could not copy '%s' to '%s'"), + rdir.buf, wtdir.buf); + } + } + + if (err) { + warning(_("temporary files exist in '%s'."), tmpdir); + warning(_("you may want to cleanup or recover these.")); + exit(1); + } else + exit_cleanup(tmpdir, rc); + +finish: + free(ce); + strbuf_release(&ldir); + strbuf_release(&rdir); + strbuf_release(&wtdir); + strbuf_release(&buf); + + return ret; +} + +static int run_file_diff(int prompt, const char *prefix, + int argc, const char **argv) +{ + struct argv_array args = ARGV_ARRAY_INIT; + const char *env[] = { + "GIT_PAGER=", "GIT_EXTERNAL_DIFF=git-difftool--helper", NULL, + NULL + }; + int ret = 0, i; + + if (prompt > 0) + env[2] = "GIT_DIFFTOOL_PROMPT=true"; + else if (!prompt) + env[2] = "GIT_DIFFTOOL_NO_PROMPT=true"; + + + argv_array_push(&args, "diff"); + for (i = 0; i < argc; i++) + argv_array_push(&args, argv[i]); + ret = run_command_v_opt_cd_env(args.argv, RUN_GIT_CMD, prefix, env); + exit(ret); +} + +int cmd_difftool(int argc, const char **argv, const char *prefix) +{ + int use_gui_tool = 0, dir_diff = 0, prompt = -1, symlinks = 0, + tool_help = 0; + static char *difftool_cmd = NULL, *extcmd = NULL; + struct option builtin_difftool_options[] = { + OPT_BOOL('g', "gui", &use_gui_tool, + N_("use `diff.guitool` instead of `diff.tool`")), + OPT_BOOL('d', "dir-diff", &dir_diff, + N_("perform a full-directory diff")), + { OPTION_SET_INT, 'y', "no-prompt", &prompt, NULL, + N_("do not prompt before launching a diff tool"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 0}, + { OPTION_SET_INT, 0, "prompt", &prompt, NULL, NULL, + PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN, + NULL, 1 }, + OPT_BOOL(0, "symlinks", &symlinks, + N_("use symlinks in dir-diff mode")), + OPT_STRING('t', "tool", &difftool_cmd, N_("<tool>"), + N_("use the specified diff tool")), + OPT_BOOL(0, "tool-help", &tool_help, + N_("print a list of diff tools that may be used with " + "`--tool`")), + OPT_BOOL(0, "trust-exit-code", &trust_exit_code, + N_("make 'git-difftool' exit when an invoked diff " + "tool returns a non - zero exit code")), + OPT_STRING('x', "extcmd", &extcmd, N_("<command>"), + N_("specify a custom command for viewing diffs")), + OPT_END() + }; + + /* NEEDSWORK: once we no longer spawn anything, remove this */ + setenv(GIT_DIR_ENVIRONMENT, absolute_path(get_git_dir()), 1); + setenv(GIT_WORK_TREE_ENVIRONMENT, absolute_path(get_git_work_tree()), 1); + + git_config(difftool_config, NULL); + symlinks = has_symlinks; + + argc = parse_options(argc, argv, prefix, builtin_difftool_options, + builtin_difftool_usage, PARSE_OPT_KEEP_UNKNOWN | + PARSE_OPT_KEEP_DASHDASH); + + if (tool_help) + return print_tool_help(); + + if (use_gui_tool && diff_gui_tool && *diff_gui_tool) + setenv("GIT_DIFF_TOOL", diff_gui_tool, 1); + else if (difftool_cmd) { + if (*difftool_cmd) + setenv("GIT_DIFF_TOOL", difftool_cmd, 1); + else + die(_("no <tool> given for --tool=<tool>")); + } + + if (extcmd) { + if (*extcmd) + setenv("GIT_DIFFTOOL_EXTCMD", extcmd, 1); + else + die(_("no <cmd> given for --extcmd=<cmd>")); + } + + setenv("GIT_DIFFTOOL_TRUST_EXIT_CODE", + trust_exit_code ? "true" : "false", 1); + + /* + * In directory diff mode, 'git-difftool--helper' is called once + * to compare the a / b directories. In file diff mode, 'git diff' + * will invoke a separate instance of 'git-difftool--helper' for + * each file that changed. + */ + if (dir_diff) + return run_dir_diff(extcmd, symlinks, prefix, argc, argv); + return run_file_diff(prompt, prefix, argc, argv); +} diff --git a/builtin/fetch.c b/builtin/fetch.c index f1570e3464..b5ad09d046 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -1177,7 +1177,7 @@ static int add_remote_or_group(const char *name, struct string_list *list) git_config(get_remote_group, &g); if (list->nr == prev_nr) { struct remote *remote = remote_get(name); - if (!remote_is_configured(remote)) + if (!remote_is_configured(remote, 0)) return 0; string_list_append(list, remote->name); } diff --git a/builtin/fsck.c b/builtin/fsck.c index f01b81eebf..1a5caccd0f 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -56,6 +56,23 @@ static const char *describe_object(struct object *obj) return buf.buf; } +static const char *printable_type(struct object *obj) +{ + const char *ret; + + if (obj->type == OBJ_NONE) { + enum object_type type = sha1_object_info(obj->oid.hash, NULL); + if (type > 0) + object_as_type(obj, type, 0); + } + + ret = typename(obj->type); + if (!ret) + ret = "unknown"; + + return ret; +} + static int fsck_config(const char *var, const char *value, void *cb) { if (strcmp(var, "fsck.skiplist") == 0) { @@ -83,7 +100,7 @@ static void objreport(struct object *obj, const char *msg_type, const char *err) { fprintf(stderr, "%s in %s %s: %s\n", - msg_type, typename(obj->type), describe_object(obj), err); + msg_type, printable_type(obj), describe_object(obj), err); } static int objerror(struct object *obj, const char *err) @@ -114,7 +131,7 @@ static int mark_object(struct object *obj, int type, void *data, struct fsck_opt if (!obj) { /* ... these references to parent->fld are safe here */ printf("broken link from %7s %s\n", - typename(parent->type), describe_object(parent)); + printable_type(parent), describe_object(parent)); printf("broken link from %7s %s\n", (type == OBJ_ANY ? "unknown" : typename(type)), "unknown"); errors_found |= ERROR_REACHABLE; @@ -131,9 +148,9 @@ static int mark_object(struct object *obj, int type, void *data, struct fsck_opt if (!(obj->flags & HAS_OBJ)) { if (parent && !has_object_file(&obj->oid)) { printf("broken link from %7s %s\n", - typename(parent->type), describe_object(parent)); + printable_type(parent), describe_object(parent)); printf(" to %7s %s\n", - typename(obj->type), describe_object(obj)); + printable_type(obj), describe_object(obj)); errors_found |= ERROR_REACHABLE; } return 1; @@ -205,9 +222,7 @@ static void check_reachable_object(struct object *obj) if (!(obj->flags & HAS_OBJ)) { if (has_sha1_pack(obj->oid.hash)) return; /* it is in pack - forget about it */ - if (connectivity_only && has_object_file(&obj->oid)) - return; - printf("missing %s %s\n", typename(obj->type), + printf("missing %s %s\n", printable_type(obj), describe_object(obj)); errors_found |= ERROR_REACHABLE; return; @@ -225,7 +240,7 @@ static void check_unreachable_object(struct object *obj) * to complain about it being unreachable (since it does * not exist). */ - if (!obj->parsed) + if (!(obj->flags & HAS_OBJ)) return; /* @@ -233,7 +248,7 @@ static void check_unreachable_object(struct object *obj) * since this is something that is prunable. */ if (show_unreachable) { - printf("unreachable %s %s\n", typename(obj->type), + printf("unreachable %s %s\n", printable_type(obj), describe_object(obj)); return; } @@ -252,7 +267,7 @@ static void check_unreachable_object(struct object *obj) */ if (!obj->used) { if (show_dangling) - printf("dangling %s %s\n", typename(obj->type), + printf("dangling %s %s\n", printable_type(obj), describe_object(obj)); if (write_lost_and_found) { char *filename = git_pathdup("lost-found/%s/%s", @@ -326,7 +341,7 @@ static int fsck_obj(struct object *obj) if (verbose) fprintf(stderr, "Checking %s %s\n", - typename(obj->type), describe_object(obj)); + printable_type(obj), describe_object(obj)); if (fsck_walk(obj, NULL, &fsck_obj_options)) objerror(obj, "broken links"); @@ -352,7 +367,7 @@ static int fsck_obj(struct object *obj) struct tag *tag = (struct tag *) obj; if (show_tags && tag->tagged) { - printf("tagged %s %s", typename(tag->tagged->type), + printf("tagged %s %s", printable_type(tag->tagged), describe_object(tag->tagged)); printf(" (%s) in %s\n", tag->tag, describe_object(&tag->object)); @@ -362,18 +377,6 @@ static int fsck_obj(struct object *obj) return 0; } -static int fsck_sha1(const unsigned char *sha1) -{ - struct object *obj = parse_object(sha1); - if (!obj) { - errors_found |= ERROR_OBJECT; - return error("%s: object corrupt or missing", - sha1_to_hex(sha1)); - } - obj->flags |= HAS_OBJ; - return fsck_obj(obj); -} - static int fsck_obj_buffer(const unsigned char *sha1, enum object_type type, unsigned long size, void *buffer, int *eaten) { @@ -400,7 +403,7 @@ static void fsck_handle_reflog_sha1(const char *refname, unsigned char *sha1, if (!is_null_sha1(sha1)) { obj = lookup_object(sha1); - if (obj) { + if (obj && (obj->flags & HAS_OBJ)) { if (timestamp && name_objects) add_decoration(fsck_walk_options.object_names, obj, @@ -488,9 +491,41 @@ static void get_default_heads(void) } } +static struct object *parse_loose_object(const unsigned char *sha1, + const char *path) +{ + struct object *obj; + void *contents; + enum object_type type; + unsigned long size; + int eaten; + + if (read_loose_object(path, sha1, &type, &size, &contents) < 0) + return NULL; + + if (!contents && type != OBJ_BLOB) + die("BUG: read_loose_object streamed a non-blob"); + + obj = parse_object_buffer(sha1, type, size, contents, &eaten); + + if (!eaten) + free(contents); + return obj; +} + static int fsck_loose(const unsigned char *sha1, const char *path, void *data) { - if (fsck_sha1(sha1)) + struct object *obj = parse_loose_object(sha1, path); + + if (!obj) { + errors_found |= ERROR_OBJECT; + error("%s: object corrupt or missing: %s", + sha1_to_hex(sha1), path); + return 0; /* keep checking other objects */ + } + + obj->flags = HAS_OBJ; + if (fsck_obj(obj)) errors_found |= ERROR_OBJECT; return 0; } @@ -584,6 +619,29 @@ static int fsck_cache_tree(struct cache_tree *it) return err; } +static void mark_object_for_connectivity(const unsigned char *sha1) +{ + struct object *obj = lookup_unknown_object(sha1); + obj->flags |= HAS_OBJ; +} + +static int mark_loose_for_connectivity(const unsigned char *sha1, + const char *path, + void *data) +{ + mark_object_for_connectivity(sha1); + return 0; +} + +static int mark_packed_for_connectivity(const unsigned char *sha1, + struct packed_git *pack, + uint32_t pos, + void *data) +{ + mark_object_for_connectivity(sha1); + return 0; +} + static char const * const fsck_usage[] = { N_("git fsck [<options>] [<object>...]"), NULL @@ -640,38 +698,41 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) git_config(fsck_config, NULL); fsck_head_link(); - if (!connectivity_only) { + if (connectivity_only) { + for_each_loose_object(mark_loose_for_connectivity, NULL, 0); + for_each_packed_object(mark_packed_for_connectivity, NULL, 0); + } else { fsck_object_dir(get_object_directory()); prepare_alt_odb(); for (alt = alt_odb_list; alt; alt = alt->next) fsck_object_dir(alt->path); - } - if (check_full) { - struct packed_git *p; - uint32_t total = 0, count = 0; - struct progress *progress = NULL; + if (check_full) { + struct packed_git *p; + uint32_t total = 0, count = 0; + struct progress *progress = NULL; - prepare_packed_git(); + prepare_packed_git(); - if (show_progress) { + if (show_progress) { + for (p = packed_git; p; p = p->next) { + if (open_pack_index(p)) + continue; + total += p->num_objects; + } + + progress = start_progress(_("Checking objects"), total); + } for (p = packed_git; p; p = p->next) { - if (open_pack_index(p)) - continue; - total += p->num_objects; + /* verify gives error messages itself */ + if (verify_pack(p, fsck_obj_buffer, + progress, count)) + errors_found |= ERROR_PACK; + count += p->num_objects; } - - progress = start_progress(_("Checking objects"), total); + stop_progress(&progress); } - for (p = packed_git; p; p = p->next) { - /* verify gives error messages itself */ - if (verify_pack(p, fsck_obj_buffer, - progress, count)) - errors_found |= ERROR_PACK; - count += p->num_objects; - } - stop_progress(&progress); } heads = 0; @@ -681,9 +742,11 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) if (!get_sha1(arg, sha1)) { struct object *obj = lookup_object(sha1); - /* Error is printed by lookup_object(). */ - if (!obj) + if (!obj || !(obj->flags & HAS_OBJ)) { + error("%s: object missing", sha1_to_hex(sha1)); + errors_found |= ERROR_OBJECT; continue; + } obj->used = 1; if (name_objects) @@ -694,6 +757,7 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) continue; } error("invalid parameter: expected sha1, got '%s'", arg); + errors_found |= ERROR_OBJECT; } /* @@ -701,7 +765,7 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) * default ones from .git/refs. We also consider the index file * in this case (ie this implies --cache). */ - if (!heads) { + if (!argc) { get_default_heads(); keep_cache_objects = 1; } diff --git a/builtin/push.c b/builtin/push.c index 9307ad56a9..5c22e9f2e5 100644 --- a/builtin/push.c +++ b/builtin/push.c @@ -568,6 +568,8 @@ int cmd_push(int argc, const char **argv, const char *prefix) flags |= TRANSPORT_RECURSE_SUBMODULES_CHECK; else if (recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND) flags |= TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND; + else if (recurse_submodules == RECURSE_SUBMODULES_ONLY) + flags |= TRANSPORT_RECURSE_SUBMODULES_ONLY; if (tags) add_refspec("refs/tags/*"); diff --git a/builtin/read-tree.c b/builtin/read-tree.c index fa6edb35b2..8ba64bc785 100644 --- a/builtin/read-tree.c +++ b/builtin/read-tree.c @@ -109,34 +109,34 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) { OPTION_CALLBACK, 0, "index-output", NULL, N_("file"), N_("write resulting index to <file>"), PARSE_OPT_NONEG, index_output_cb }, - OPT_SET_INT(0, "empty", &read_empty, - N_("only empty the index"), 1), + OPT_BOOL(0, "empty", &read_empty, + N_("only empty the index")), OPT__VERBOSE(&opts.verbose_update, N_("be verbose")), OPT_GROUP(N_("Merging")), - OPT_SET_INT('m', NULL, &opts.merge, - N_("perform a merge in addition to a read"), 1), - OPT_SET_INT(0, "trivial", &opts.trivial_merges_only, - N_("3-way merge if no file level merging required"), 1), - OPT_SET_INT(0, "aggressive", &opts.aggressive, - N_("3-way merge in presence of adds and removes"), 1), - OPT_SET_INT(0, "reset", &opts.reset, - N_("same as -m, but discard unmerged entries"), 1), + OPT_BOOL('m', NULL, &opts.merge, + N_("perform a merge in addition to a read")), + OPT_BOOL(0, "trivial", &opts.trivial_merges_only, + N_("3-way merge if no file level merging required")), + OPT_BOOL(0, "aggressive", &opts.aggressive, + N_("3-way merge in presence of adds and removes")), + OPT_BOOL(0, "reset", &opts.reset, + N_("same as -m, but discard unmerged entries")), { OPTION_STRING, 0, "prefix", &opts.prefix, N_("<subdirectory>/"), N_("read the tree into the index under <subdirectory>/"), PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP }, - OPT_SET_INT('u', NULL, &opts.update, - N_("update working tree with merge result"), 1), + OPT_BOOL('u', NULL, &opts.update, + N_("update working tree with merge result")), { OPTION_CALLBACK, 0, "exclude-per-directory", &opts, N_("gitignore"), N_("allow explicitly ignored files to be overwritten"), PARSE_OPT_NONEG, exclude_per_directory_cb }, - OPT_SET_INT('i', NULL, &opts.index_only, - N_("don't check the working tree after merging"), 1), + OPT_BOOL('i', NULL, &opts.index_only, + N_("don't check the working tree after merging")), OPT__DRY_RUN(&opts.dry_run, N_("don't update the index or the work tree")), - OPT_SET_INT(0, "no-sparse-checkout", &opts.skip_sparse_checkout, - N_("skip applying sparse checkout filter"), 1), - OPT_SET_INT(0, "debug-unpack", &opts.debug_unpack, - N_("debug unpack-trees"), 1), + OPT_BOOL(0, "no-sparse-checkout", &opts.skip_sparse_checkout, + N_("skip applying sparse checkout filter")), + OPT_BOOL(0, "debug-unpack", &opts.debug_unpack, + N_("debug unpack-trees")), OPT_END() }; diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index 6b97cbdbe9..1dbb8a0692 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -1942,8 +1942,7 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) run_receive_hook(commands, "post-receive", 1, &push_options); run_update_post_hook(commands); - if (push_options.nr) - string_list_clear(&push_options, 0); + string_list_clear(&push_options, 0); if (auto_gc) { const char *argv_gc_auto[] = { "gc", "--auto", "--quiet", NULL, diff --git a/builtin/remote.c b/builtin/remote.c index e52cf3925b..5339ed6ad1 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -186,7 +186,7 @@ static int add(int argc, const char **argv) url = argv[1]; remote = remote_get(name); - if (remote_is_configured(remote)) + if (remote_is_configured(remote, 1)) die(_("remote %s already exists."), name); strbuf_addf(&buf2, "refs/heads/test:refs/remotes/%s/test", name); @@ -618,14 +618,14 @@ static int mv(int argc, const char **argv) rename.remote_branches = &remote_branches; oldremote = remote_get(rename.old); - if (!remote_is_configured(oldremote)) + if (!remote_is_configured(oldremote, 1)) die(_("No such remote: %s"), rename.old); if (!strcmp(rename.old, rename.new) && oldremote->origin != REMOTE_CONFIG) return migrate_file(oldremote); newremote = remote_get(rename.new); - if (remote_is_configured(newremote)) + if (remote_is_configured(newremote, 1)) die(_("remote %s already exists."), rename.new); strbuf_addf(&buf, "refs/heads/test:refs/remotes/%s/test", rename.new); @@ -753,7 +753,7 @@ static int rm(int argc, const char **argv) usage_with_options(builtin_remote_rm_usage, options); remote = remote_get(argv[1]); - if (!remote_is_configured(remote)) + if (!remote_is_configured(remote, 1)) die(_("No such remote: %s"), argv[1]); known_remotes.to_delete = remote; @@ -1415,7 +1415,7 @@ static int set_remote_branches(const char *remotename, const char **branches, strbuf_addf(&key, "remote.%s.fetch", remotename); remote = remote_get(remotename); - if (!remote_is_configured(remote)) + if (!remote_is_configured(remote, 1)) die(_("No such remote '%s'"), remotename); if (!add_mode && remove_all_fetch_refspecs(remotename, key.buf)) { @@ -1469,7 +1469,7 @@ static int get_url(int argc, const char **argv) remotename = argv[0]; remote = remote_get(remotename); - if (!remote_is_configured(remote)) + if (!remote_is_configured(remote, 1)) die(_("No such remote '%s'"), remotename); url_nr = 0; @@ -1537,7 +1537,7 @@ static int set_url(int argc, const char **argv) oldurl = newurl; remote = remote_get(remotename); - if (!remote_is_configured(remote)) + if (!remote_is_configured(remote, 1)) die(_("No such remote '%s'"), remotename); if (push_mode) { diff --git a/builtin/show-ref.c b/builtin/show-ref.c index 6d4e669002..013d241abc 100644 --- a/builtin/show-ref.c +++ b/builtin/show-ref.c @@ -19,19 +19,34 @@ static const char *exclude_existing_arg; static void show_one(const char *refname, const struct object_id *oid) { - const char *hex = find_unique_abbrev(oid->hash, abbrev); + const char *hex; + struct object_id peeled; + + if (!has_sha1_file(oid->hash)) + die("git show-ref: bad ref %s (%s)", refname, + oid_to_hex(oid)); + + if (quiet) + return; + + hex = find_unique_abbrev(oid->hash, abbrev); if (hash_only) printf("%s\n", hex); else printf("%s %s\n", hex, refname); + + if (!deref_tags) + return; + + if (!peel_ref(refname, peeled.hash)) { + hex = find_unique_abbrev(peeled.hash, abbrev); + printf("%s %s^{}\n", hex, refname); + } } static int show_ref(const char *refname, const struct object_id *oid, int flag, void *cbdata) { - const char *hex; - struct object_id peeled; - if (show_head && !strcmp(refname, "HEAD")) goto match; @@ -54,9 +69,6 @@ static int show_ref(const char *refname, const struct object_id *oid, continue; if (len == reflen) goto match; - /* "--verify" requires an exact match */ - if (verify) - continue; if (refname[reflen - len - 1] == '/') goto match; } @@ -66,26 +78,8 @@ static int show_ref(const char *refname, const struct object_id *oid, match: found_match++; - /* This changes the semantics slightly that even under quiet we - * detect and return error if the repository is corrupt and - * ref points at a nonexistent object. - */ - if (!has_sha1_file(oid->hash)) - die("git show-ref: bad ref %s (%s)", refname, - oid_to_hex(oid)); - - if (quiet) - return 0; - show_one(refname, oid); - if (!deref_tags) - return 0; - - if (!peel_ref(refname, peeled.hash)) { - hex = find_unique_abbrev(peeled.hash, abbrev); - printf("%s %s^{}\n", hex, refname); - } return 0; } @@ -202,10 +196,9 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix) while (*pattern) { struct object_id oid; - if (starts_with(*pattern, "refs/") && + if ((starts_with(*pattern, "refs/") || !strcmp(*pattern, "HEAD")) && !read_ref(*pattern, oid.hash)) { - if (!quiet) - show_one(*pattern, &oid); + show_one(*pattern, &oid); } else if (!quiet) die("'%s' - not a valid ref", *pattern); diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index 74614a951e..899dc334e3 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -626,7 +626,7 @@ static int module_clone(int argc, const char **argv, const char *prefix) module_clone_options); strbuf_addf(&sb, "%s/modules/%s", get_git_dir(), name); - sm_gitdir = xstrdup(absolute_path(sb.buf)); + sm_gitdir = absolute_pathdup(sb.buf); strbuf_reset(&sb); if (!is_absolute_path(path)) { diff --git a/builtin/tag.c b/builtin/tag.c index 73df728114..e40c4a9676 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -24,7 +24,7 @@ static const char * const git_tag_usage[] = { N_("git tag -d <tagname>..."), N_("git tag -l [-n[<num>]] [--contains <commit>] [--points-at <object>]" "\n\t\t[--format=<format>] [--[no-]merged [<commit>]] [<pattern>...]"), - N_("git tag -v <tagname>..."), + N_("git tag -v [--format=<format>] <tagname>..."), NULL }; @@ -66,9 +66,10 @@ static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting, con } typedef int (*each_tag_name_fn)(const char *name, const char *ref, - const unsigned char *sha1); + const unsigned char *sha1, const void *cb_data); -static int for_each_tag_name(const char **argv, each_tag_name_fn fn) +static int for_each_tag_name(const char **argv, each_tag_name_fn fn, + const void *cb_data) { const char **p; char ref[PATH_MAX]; @@ -87,14 +88,14 @@ static int for_each_tag_name(const char **argv, each_tag_name_fn fn) had_error = 1; continue; } - if (fn(*p, ref, sha1)) + if (fn(*p, ref, sha1, cb_data)) had_error = 1; } return had_error; } static int delete_tag(const char *name, const char *ref, - const unsigned char *sha1) + const unsigned char *sha1, const void *cb_data) { if (delete_ref(ref, sha1, 0)) return 1; @@ -103,9 +104,22 @@ static int delete_tag(const char *name, const char *ref, } static int verify_tag(const char *name, const char *ref, - const unsigned char *sha1) + const unsigned char *sha1, const void *cb_data) { - return gpg_verify_tag(sha1, name, GPG_VERIFY_VERBOSE); + int flags; + const char *fmt_pretty = cb_data; + flags = GPG_VERIFY_VERBOSE; + + if (fmt_pretty) + flags = GPG_VERIFY_OMIT_STATUS; + + if (gpg_verify_tag(sha1, name, flags)) + return -1; + + if (fmt_pretty) + pretty_print_ref(name, sha1, fmt_pretty); + + return 0; } static int do_sign(struct strbuf *buffer) @@ -428,9 +442,12 @@ int cmd_tag(int argc, const char **argv, const char *prefix) if (filter.merge_commit) die(_("--merged and --no-merged option are only allowed with -l")); if (cmdmode == 'd') - return for_each_tag_name(argv, delete_tag); - if (cmdmode == 'v') - return for_each_tag_name(argv, verify_tag); + return for_each_tag_name(argv, delete_tag, NULL); + if (cmdmode == 'v') { + if (format) + verify_ref_format(format); + return for_each_tag_name(argv, verify_tag, format); + } if (msg.given || msgfile) { if (msg.given && msgfile) diff --git a/builtin/verify-tag.c b/builtin/verify-tag.c index 99f8148cf7..5199553d91 100644 --- a/builtin/verify-tag.c +++ b/builtin/verify-tag.c @@ -12,9 +12,10 @@ #include <signal.h> #include "parse-options.h" #include "gpg-interface.h" +#include "ref-filter.h" static const char * const verify_tag_usage[] = { - N_("git verify-tag [-v | --verbose] <tag>..."), + N_("git verify-tag [-v | --verbose] [--format=<format>] <tag>..."), NULL }; @@ -30,9 +31,11 @@ int cmd_verify_tag(int argc, const char **argv, const char *prefix) { int i = 1, verbose = 0, had_error = 0; unsigned flags = 0; + char *fmt_pretty = NULL; const struct option verify_tag_options[] = { OPT__VERBOSE(&verbose, N_("print tag contents")), OPT_BIT(0, "raw", &flags, N_("print raw gpg status output"), GPG_VERIFY_RAW), + OPT_STRING( 0 , "format", &fmt_pretty, N_("format"), N_("format to use for the output")), OPT_END() }; @@ -46,13 +49,26 @@ int cmd_verify_tag(int argc, const char **argv, const char *prefix) if (verbose) flags |= GPG_VERIFY_VERBOSE; + if (fmt_pretty) { + verify_ref_format(fmt_pretty); + flags |= GPG_VERIFY_OMIT_STATUS; + } + while (i < argc) { unsigned char sha1[20]; const char *name = argv[i++]; - if (get_sha1(name, sha1)) + if (get_sha1(name, sha1)) { had_error = !!error("tag '%s' not found.", name); - else if (gpg_verify_tag(sha1, name, flags)) + continue; + } + + if (gpg_verify_tag(sha1, name, flags)) { had_error = 1; + continue; + } + + if (fmt_pretty) + pretty_print_ref(name, sha1, fmt_pretty); } return had_error; } @@ -507,9 +507,12 @@ extern int is_nonbare_repository_dir(struct strbuf *path); #define READ_GITFILE_ERR_NO_PATH 6 #define READ_GITFILE_ERR_NOT_A_REPO 7 #define READ_GITFILE_ERR_TOO_LARGE 8 +extern void read_gitfile_error_die(int error_code, const char *path, const char *dir); extern const char *read_gitfile_gently(const char *path, int *return_error_code); #define read_gitfile(path) read_gitfile_gently((path), NULL) -extern const char *resolve_gitdir(const char *suspect); +extern const char *resolve_gitdir_gently(const char *suspect, int *return_error_code); +#define resolve_gitdir(path) resolve_gitdir_gently((path), NULL) + extern void set_git_work_tree(const char *tree); #define ALTERNATE_DB_ENVIRONMENT "GIT_ALTERNATE_OBJECT_DIRECTORIES" @@ -574,7 +577,26 @@ extern int verify_path(const char *path); extern int index_dir_exists(struct index_state *istate, const char *name, int namelen); extern void adjust_dirname_case(struct index_state *istate, char *name); extern struct cache_entry *index_file_exists(struct index_state *istate, const char *name, int namelen, int igncase); + +/* + * Searches for an entry defined by name and namelen in the given index. + * If the return value is positive (including 0) it is the position of an + * exact match. If the return value is negative, the negated value minus 1 + * is the position where the entry would be inserted. + * Example: The current index consists of these files and its stages: + * + * b#0, d#0, f#1, f#3 + * + * index_name_pos(&index, "a", 1) -> -1 + * index_name_pos(&index, "b", 1) -> 0 + * index_name_pos(&index, "c", 1) -> -2 + * index_name_pos(&index, "d", 1) -> 1 + * index_name_pos(&index, "e", 1) -> -3 + * index_name_pos(&index, "f", 1) -> -3 + * index_name_pos(&index, "g", 1) -> -5 + */ extern int index_name_pos(const struct index_state *, const char *name, int namelen); + #define ADD_CACHE_OK_TO_ADD 1 /* Ok to add */ #define ADD_CACHE_OK_TO_REPLACE 2 /* Ok to replace file/directory */ #define ADD_CACHE_SKIP_DFCHECK 4 /* Ok to skip DF conflict checks */ @@ -583,7 +605,10 @@ extern int index_name_pos(const struct index_state *, const char *name, int name #define ADD_CACHE_KEEP_CACHE_TREE 32 /* Do not invalidate cache-tree */ extern int add_index_entry(struct index_state *, struct cache_entry *ce, int option); extern void rename_index_entry_at(struct index_state *, int pos, const char *new_name); + +/* Remove entry, return true if there are more entries to go. */ extern int remove_index_entry_at(struct index_state *, int pos); + extern void remove_marked_cache_entries(struct index_state *istate); extern int remove_file_from_index(struct index_state *, const char *path); #define ADD_CACHE_VERBOSE 1 @@ -591,8 +616,18 @@ extern int remove_file_from_index(struct index_state *, const char *path); #define ADD_CACHE_IGNORE_ERRORS 4 #define ADD_CACHE_IGNORE_REMOVAL 8 #define ADD_CACHE_INTENT 16 +/* + * These two are used to add the contents of the file at path + * to the index, marking the working tree up-to-date by storing + * the cached stat info in the resulting cache entry. A caller + * that has already run lstat(2) on the path can call + * add_to_index(), and all others can call add_file_to_index(); + * the latter will do necessary lstat(2) internally before + * calling the former. + */ extern int add_to_index(struct index_state *, const char *path, struct stat *, int flags); extern int add_file_to_index(struct index_state *, const char *path, int flags); + extern struct cache_entry *make_cache_entry(unsigned int mode, const unsigned char *sha1, const char *path, int stage, unsigned int refresh_options); extern int chmod_index_entry(struct index_state *, struct cache_entry *ce, char flip); extern int ce_same_name(const struct cache_entry *a, const struct cache_entry *b); @@ -1069,6 +1104,7 @@ const char *real_path(const char *path); const char *real_path_if_valid(const char *path); char *real_pathdup(const char *path); const char *absolute_path(const char *path); +char *absolute_pathdup(const char *path); const char *remove_leading_path(const char *in, const char *prefix); const char *relative_path(const char *in, const char *prefix, struct strbuf *sb); int normalize_path_copy_len(char *dst, const char *src, int *prefix_len); @@ -1143,6 +1179,19 @@ extern int finalize_object_file(const char *tmpfile, const char *filename); extern int has_sha1_pack(const unsigned char *sha1); /* + * Open the loose object at path, check its sha1, and return the contents, + * type, and size. If the object is a blob, then "contents" may return NULL, + * to allow streaming of large blobs. + * + * Returns 0 on success, negative on error (details may be written to stderr). + */ +int read_loose_object(const char *path, + const unsigned char *expected_sha1, + enum object_type *type, + unsigned long *size, + void **contents); + +/* * Return true iff we have an object named sha1, whether local or in * an alternate object database, and whether packed or loose. This * function does not respect replace references. @@ -207,7 +207,17 @@ int color_parse_mem(const char *value, int value_len, char *dst) struct color fg = { COLOR_UNSPECIFIED }; struct color bg = { COLOR_UNSPECIFIED }; - if (!strncasecmp(value, "reset", len)) { + while (len > 0 && isspace(*ptr)) { + ptr++; + len--; + } + + if (!len) { + dst[0] = '\0'; + return 0; + } + + if (!strncasecmp(ptr, "reset", len)) { xsnprintf(dst, end - dst, GIT_COLOR_RESET); return 0; } diff --git a/command-list.txt b/command-list.txt index 2a94137bbb..a1fad28fd8 100644 --- a/command-list.txt +++ b/command-list.txt @@ -107,7 +107,6 @@ git-read-tree plumbingmanipulators git-rebase mainporcelain history git-receive-pack synchelpers git-reflog ancillarymanipulators -git-relink ancillarymanipulators git-remote ancillarymanipulators git-repack ancillarymanipulators git-replace ancillarymanipulators diff --git a/compat/qsort_s.c b/compat/qsort_s.c new file mode 100644 index 0000000000..52d1f0a73d --- /dev/null +++ b/compat/qsort_s.c @@ -0,0 +1,69 @@ +#include "../git-compat-util.h" + +/* + * A merge sort implementation, simplified from the qsort implementation + * by Mike Haertel, which is a part of the GNU C Library. + * Added context pointer, safety checks and return value. + */ + +static void msort_with_tmp(void *b, size_t n, size_t s, + int (*cmp)(const void *, const void *, void *), + char *t, void *ctx) +{ + char *tmp; + char *b1, *b2; + size_t n1, n2; + + if (n <= 1) + return; + + n1 = n / 2; + n2 = n - n1; + b1 = b; + b2 = (char *)b + (n1 * s); + + msort_with_tmp(b1, n1, s, cmp, t, ctx); + msort_with_tmp(b2, n2, s, cmp, t, ctx); + + tmp = t; + + while (n1 > 0 && n2 > 0) { + if (cmp(b1, b2, ctx) <= 0) { + memcpy(tmp, b1, s); + tmp += s; + b1 += s; + --n1; + } else { + memcpy(tmp, b2, s); + tmp += s; + b2 += s; + --n2; + } + } + if (n1 > 0) + memcpy(tmp, b1, n1 * s); + memcpy(b, t, (n - n2) * s); +} + +int git_qsort_s(void *b, size_t n, size_t s, + int (*cmp)(const void *, const void *, void *), void *ctx) +{ + const size_t size = st_mult(n, s); + char buf[1024]; + + if (!n) + return 0; + if (!b || !cmp) + return -1; + + if (size < sizeof(buf)) { + /* The temporary array fits on the small on-stack buffer. */ + msort_with_tmp(b, n, s, cmp, buf, ctx); + } else { + /* It's somewhat large, so malloc it. */ + char *tmp = xmalloc(size); + msort_with_tmp(b, n, s, cmp, tmp, ctx); + free(tmp); + } + return 0; +} diff --git a/compat/winansi.c b/compat/winansi.c index 3c9ed3cfe0..82b89ab137 100644 --- a/compat/winansi.c +++ b/compat/winansi.c @@ -494,19 +494,16 @@ static HANDLE swap_osfhnd(int fd, HANDLE new_handle) * It is because of this implicit close() that we created the * copy of the original. * - * Note that the OS can recycle HANDLE (numbers) just like it - * recycles fd (numbers), so we must update the cached value - * of "console". You can use GetFileType() to see that - * handle and _get_osfhandle(fd) may have the same number - * value, but they refer to different actual files now. + * Note that we need to update the cached console handle to the + * duplicated one because the dup2() call will implicitly close + * the original one. * * Note that dup2() when given target := {0,1,2} will also * call SetStdHandle(), so we don't need to worry about that. */ - dup2(new_fd, fd); if (console == handle) console = duplicate; - handle = INVALID_HANDLE_VALUE; + dup2(new_fd, fd); /* Close the temp fd. This explicitly closes "new_handle" * (because it has been associated with it). diff --git a/contrib/coccinelle/xstrdup_or_null.cocci b/contrib/coccinelle/xstrdup_or_null.cocci index 3fceef132b..8e05d1ca4b 100644 --- a/contrib/coccinelle/xstrdup_or_null.cocci +++ b/contrib/coccinelle/xstrdup_or_null.cocci @@ -5,3 +5,9 @@ expression V; - if (E) - V = xstrdup(E); + V = xstrdup_or_null(E); + +@@ +expression E; +@@ +- xstrdup(absolute_path(E)) ++ absolute_pathdup(E) diff --git a/contrib/convert-objects/convert-objects.c b/contrib/convert-objects/convert-objects.c deleted file mode 100644 index f3b57bf1d2..0000000000 --- a/contrib/convert-objects/convert-objects.c +++ /dev/null @@ -1,329 +0,0 @@ -#include "cache.h" -#include "blob.h" -#include "commit.h" -#include "tree.h" - -struct entry { - unsigned char old_sha1[20]; - unsigned char new_sha1[20]; - int converted; -}; - -#define MAXOBJECTS (1000000) - -static struct entry *convert[MAXOBJECTS]; -static int nr_convert; - -static struct entry * convert_entry(unsigned char *sha1); - -static struct entry *insert_new(unsigned char *sha1, int pos) -{ - struct entry *new = xcalloc(1, sizeof(struct entry)); - hashcpy(new->old_sha1, sha1); - memmove(convert + pos + 1, convert + pos, (nr_convert - pos) * sizeof(struct entry *)); - convert[pos] = new; - nr_convert++; - if (nr_convert == MAXOBJECTS) - die("you're kidding me - hit maximum object limit"); - return new; -} - -static struct entry *lookup_entry(unsigned char *sha1) -{ - int low = 0, high = nr_convert; - - while (low < high) { - int next = (low + high) / 2; - struct entry *n = convert[next]; - int cmp = hashcmp(sha1, n->old_sha1); - if (!cmp) - return n; - if (cmp < 0) { - high = next; - continue; - } - low = next+1; - } - return insert_new(sha1, low); -} - -static void convert_binary_sha1(void *buffer) -{ - struct entry *entry = convert_entry(buffer); - hashcpy(buffer, entry->new_sha1); -} - -static void convert_ascii_sha1(void *buffer) -{ - unsigned char sha1[20]; - struct entry *entry; - - if (get_sha1_hex(buffer, sha1)) - die("expected sha1, got '%s'", (char *) buffer); - entry = convert_entry(sha1); - memcpy(buffer, sha1_to_hex(entry->new_sha1), 40); -} - -static unsigned int convert_mode(unsigned int mode) -{ - unsigned int newmode; - - newmode = mode & S_IFMT; - if (S_ISREG(mode)) - newmode |= (mode & 0100) ? 0755 : 0644; - return newmode; -} - -static int write_subdirectory(void *buffer, unsigned long size, const char *base, int baselen, unsigned char *result_sha1) -{ - char *new = xmalloc(size); - unsigned long newlen = 0; - unsigned long used; - - used = 0; - while (size) { - int len = 21 + strlen(buffer); - char *path = strchr(buffer, ' '); - unsigned char *sha1; - unsigned int mode; - char *slash, *origpath; - - if (!path || strtoul_ui(buffer, 8, &mode)) - die("bad tree conversion"); - mode = convert_mode(mode); - path++; - if (memcmp(path, base, baselen)) - break; - origpath = path; - path += baselen; - slash = strchr(path, '/'); - if (!slash) { - newlen += sprintf(new + newlen, "%o %s", mode, path); - new[newlen++] = '\0'; - hashcpy((unsigned char *)new + newlen, (unsigned char *) buffer + len - 20); - newlen += 20; - - used += len; - size -= len; - buffer = (char *) buffer + len; - continue; - } - - newlen += sprintf(new + newlen, "%o %.*s", S_IFDIR, (int)(slash - path), path); - new[newlen++] = 0; - sha1 = (unsigned char *)(new + newlen); - newlen += 20; - - len = write_subdirectory(buffer, size, origpath, slash-origpath+1, sha1); - - used += len; - size -= len; - buffer = (char *) buffer + len; - } - - write_sha1_file(new, newlen, tree_type, result_sha1); - free(new); - return used; -} - -static void convert_tree(void *buffer, unsigned long size, unsigned char *result_sha1) -{ - void *orig_buffer = buffer; - unsigned long orig_size = size; - - while (size) { - size_t len = 1+strlen(buffer); - - convert_binary_sha1((char *) buffer + len); - - len += 20; - if (len > size) - die("corrupt tree object"); - size -= len; - buffer = (char *) buffer + len; - } - - write_subdirectory(orig_buffer, orig_size, "", 0, result_sha1); -} - -static unsigned long parse_oldstyle_date(const char *buf) -{ - char c, *p; - char buffer[100]; - struct tm tm; - const char *formats[] = { - "%c", - "%a %b %d %T", - "%Z", - "%Y", - " %Y", - NULL - }; - /* We only ever did two timezones in the bad old format .. */ - const char *timezones[] = { - "PDT", "PST", "CEST", NULL - }; - const char **fmt = formats; - - p = buffer; - while (isspace(c = *buf)) - buf++; - while ((c = *buf++) != '\n') - *p++ = c; - *p++ = 0; - buf = buffer; - memset(&tm, 0, sizeof(tm)); - do { - const char *next = strptime(buf, *fmt, &tm); - if (next) { - if (!*next) - return mktime(&tm); - buf = next; - } else { - const char **p = timezones; - while (isspace(*buf)) - buf++; - while (*p) { - if (!memcmp(buf, *p, strlen(*p))) { - buf += strlen(*p); - break; - } - p++; - } - } - fmt++; - } while (*buf && *fmt); - printf("left: %s\n", buf); - return mktime(&tm); -} - -static int convert_date_line(char *dst, void **buf, unsigned long *sp) -{ - unsigned long size = *sp; - char *line = *buf; - char *next = strchr(line, '\n'); - char *date = strchr(line, '>'); - int len; - - if (!next || !date) - die("missing or bad author/committer line %s", line); - next++; date += 2; - - *buf = next; - *sp = size - (next - line); - - len = date - line; - memcpy(dst, line, len); - dst += len; - - /* Is it already in new format? */ - if (isdigit(*date)) { - int datelen = next - date; - memcpy(dst, date, datelen); - return len + datelen; - } - - /* - * Hacky hacky: one of the sparse old-style commits does not have - * any date at all, but we can fake it by using the committer date. - */ - if (*date == '\n' && strchr(next, '>')) - date = strchr(next, '>')+2; - - return len + sprintf(dst, "%lu -0700\n", parse_oldstyle_date(date)); -} - -static void convert_date(void *buffer, unsigned long size, unsigned char *result_sha1) -{ - char *new = xmalloc(size + 100); - unsigned long newlen = 0; - - /* "tree <sha1>\n" */ - memcpy(new + newlen, buffer, 46); - newlen += 46; - buffer = (char *) buffer + 46; - size -= 46; - - /* "parent <sha1>\n" */ - while (!memcmp(buffer, "parent ", 7)) { - memcpy(new + newlen, buffer, 48); - newlen += 48; - buffer = (char *) buffer + 48; - size -= 48; - } - - /* "author xyz <xyz> date" */ - newlen += convert_date_line(new + newlen, &buffer, &size); - /* "committer xyz <xyz> date" */ - newlen += convert_date_line(new + newlen, &buffer, &size); - - /* Rest */ - memcpy(new + newlen, buffer, size); - newlen += size; - - write_sha1_file(new, newlen, commit_type, result_sha1); - free(new); -} - -static void convert_commit(void *buffer, unsigned long size, unsigned char *result_sha1) -{ - void *orig_buffer = buffer; - unsigned long orig_size = size; - - if (memcmp(buffer, "tree ", 5)) - die("Bad commit '%s'", (char *) buffer); - convert_ascii_sha1((char *) buffer + 5); - buffer = (char *) buffer + 46; /* "tree " + "hex sha1" + "\n" */ - while (!memcmp(buffer, "parent ", 7)) { - convert_ascii_sha1((char *) buffer + 7); - buffer = (char *) buffer + 48; - } - convert_date(orig_buffer, orig_size, result_sha1); -} - -static struct entry * convert_entry(unsigned char *sha1) -{ - struct entry *entry = lookup_entry(sha1); - enum object_type type; - void *buffer, *data; - unsigned long size; - - if (entry->converted) - return entry; - data = read_sha1_file(sha1, &type, &size); - if (!data) - die("unable to read object %s", sha1_to_hex(sha1)); - - buffer = xmalloc(size); - memcpy(buffer, data, size); - - if (type == OBJ_BLOB) { - write_sha1_file(buffer, size, blob_type, entry->new_sha1); - } else if (type == OBJ_TREE) - convert_tree(buffer, size, entry->new_sha1); - else if (type == OBJ_COMMIT) - convert_commit(buffer, size, entry->new_sha1); - else - die("unknown object type %d in %s", type, sha1_to_hex(sha1)); - entry->converted = 1; - free(buffer); - free(data); - return entry; -} - -int main(int argc, char **argv) -{ - unsigned char sha1[20]; - struct entry *entry; - - setup_git_directory(); - - if (argc != 2) - usage("git-convert-objects <sha1>"); - if (get_sha1(argv[1], sha1)) - die("Not a valid object name %s", argv[1]); - - entry = convert_entry(sha1); - printf("new sha1: %s\n", sha1_to_hex(entry->new_sha1)); - return 0; -} diff --git a/contrib/convert-objects/git-convert-objects.txt b/contrib/convert-objects/git-convert-objects.txt deleted file mode 100644 index f871880cfb..0000000000 --- a/contrib/convert-objects/git-convert-objects.txt +++ /dev/null @@ -1,29 +0,0 @@ -git-convert-objects(1) -====================== - -NAME ----- -git-convert-objects - Converts old-style git repository - - -SYNOPSIS --------- -[verse] -'git-convert-objects' - -DESCRIPTION ------------ -Converts old-style git repository to the latest format - - -Author ------- -Written by Linus Torvalds <torvalds@osdl.org> - -Documentation --------------- -Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>. - -GIT ---- -Part of the linkgit:git[7] suite diff --git a/git-difftool.perl b/contrib/examples/git-difftool.perl index df59bdfe97..df59bdfe97 100755 --- a/git-difftool.perl +++ b/contrib/examples/git-difftool.perl diff --git a/exec_cmd.c b/exec_cmd.c index 19ac2146d0..fb94aeba9c 100644 --- a/exec_cmd.c +++ b/exec_cmd.c @@ -64,17 +64,19 @@ void git_set_argv_exec_path(const char *exec_path) /* Returns the highest-priority, location to look for git programs. */ const char *git_exec_path(void) { - const char *env; + static char *cached_exec_path; if (argv_exec_path) return argv_exec_path; - env = getenv(EXEC_PATH_ENVIRONMENT); - if (env && *env) { - return env; + if (!cached_exec_path) { + const char *env = getenv(EXEC_PATH_ENVIRONMENT); + if (env && *env) + cached_exec_path = xstrdup(env); + else + cached_exec_path = system_path(GIT_EXEC_PATH); } - - return system_path(GIT_EXEC_PATH); + return cached_exec_path; } static void add_path(struct strbuf *out, const char *path) @@ -458,6 +458,10 @@ int fsck_walk(struct object *obj, void *data, struct fsck_options *options) { if (!obj) return -1; + + if (obj->type == OBJ_NONE) + parse_object(obj->oid.hash); + switch (obj->type) { case OBJ_BLOB: return 0; diff --git a/git-compat-util.h b/git-compat-util.h index 87237b092b..f46d40e4a4 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -988,6 +988,17 @@ static inline void sane_qsort(void *base, size_t nmemb, size_t size, qsort(base, nmemb, size, compar); } +#ifndef HAVE_ISO_QSORT_S +int git_qsort_s(void *base, size_t nmemb, size_t size, + int (*compar)(const void *, const void *, void *), void *ctx); +#define qsort_s git_qsort_s +#endif + +#define QSORT_S(base, n, compar, ctx) do { \ + if (qsort_s((base), (n), sizeof(*(base)), compar, ctx)) \ + die("BUG: qsort_s() failed"); \ +} while (0) + #ifndef REG_STARTEND #error "Git requires REG_STARTEND support. Compile with NO_REGEX=NeedsStartEnd" #endif @@ -674,7 +674,7 @@ def gitConfigInt(key): def gitConfigList(key): if not _gitConfig.has_key(key): s = read_pipe(["git", "config", "--get-all", key], ignore_error=True) - _gitConfig[key] = s.strip().split(os.linesep) + _gitConfig[key] = s.strip().splitlines() if _gitConfig[key] == ['']: _gitConfig[key] = [] return _gitConfig[key] diff --git a/git-relink.perl b/git-relink.perl deleted file mode 100755 index 236a3521a1..0000000000 --- a/git-relink.perl +++ /dev/null @@ -1,173 +0,0 @@ -#!/usr/bin/perl -# Copyright 2005, Ryan Anderson <ryan@michonline.com> -# Distribution permitted under the GPL v2, as distributed -# by the Free Software Foundation. -# Later versions of the GPL at the discretion of Linus Torvalds -# -# Scan two git object-trees, and hardlink any common objects between them. - -use 5.008; -use strict; -use warnings; -use Getopt::Long; - -sub get_canonical_form($); -sub do_scan_directory($$$); -sub compare_two_files($$); -sub usage(); -sub link_two_files($$); - -# stats -my $total_linked = 0; -my $total_already = 0; -my ($linked,$already); - -my $fail_on_different_sizes = 0; -my $help = 0; -GetOptions("safe" => \$fail_on_different_sizes, - "help" => \$help); - -usage() if $help; - -my (@dirs) = @ARGV; - -usage() if (!defined $dirs[0] || !defined $dirs[1]); - -$_ = get_canonical_form($_) foreach (@dirs); - -my $master_dir = pop @dirs; - -opendir(D,$master_dir . "objects/") - or die "Failed to open $master_dir/objects/ : $!"; - -my @hashdirs = grep { ($_ eq 'pack') || /^[0-9a-f]{2}$/ } readdir(D); - -foreach my $repo (@dirs) { - $linked = 0; - $already = 0; - printf("Searching '%s' and '%s' for common objects and hardlinking them...\n", - $master_dir,$repo); - - foreach my $hashdir (@hashdirs) { - do_scan_directory($master_dir, $hashdir, $repo); - } - - printf("Linked %d files, %d were already linked.\n",$linked, $already); - - $total_linked += $linked; - $total_already += $already; -} - -printf("Totals: Linked %d files, %d were already linked.\n", - $total_linked, $total_already); - - -sub do_scan_directory($$$) { - my ($srcdir, $subdir, $dstdir) = @_; - - my $sfulldir = sprintf("%sobjects/%s/",$srcdir,$subdir); - my $dfulldir = sprintf("%sobjects/%s/",$dstdir,$subdir); - - opendir(S,$sfulldir) - or die "Failed to opendir $sfulldir: $!"; - - foreach my $file (grep(!/\.{1,2}$/, readdir(S))) { - my $sfilename = $sfulldir . $file; - my $dfilename = $dfulldir . $file; - - compare_two_files($sfilename,$dfilename); - - } - closedir(S); -} - -sub compare_two_files($$) { - my ($sfilename, $dfilename) = @_; - - # Perl's stat returns relevant information as follows: - # 0 = dev number - # 1 = inode number - # 7 = size - my @sstatinfo = stat($sfilename); - my @dstatinfo = stat($dfilename); - - if (@sstatinfo == 0 && @dstatinfo == 0) { - die sprintf("Stat of both %s and %s failed: %s\n",$sfilename, $dfilename, $!); - - } elsif (@dstatinfo == 0) { - return; - } - - if ( ($sstatinfo[0] == $dstatinfo[0]) && - ($sstatinfo[1] != $dstatinfo[1])) { - if ($sstatinfo[7] == $dstatinfo[7]) { - link_two_files($sfilename, $dfilename); - - } else { - my $err = sprintf("ERROR: File sizes are not the same, cannot relink %s to %s.\n", - $sfilename, $dfilename); - if ($fail_on_different_sizes) { - die $err; - } else { - warn $err; - } - } - - } elsif ( ($sstatinfo[0] == $dstatinfo[0]) && - ($sstatinfo[1] == $dstatinfo[1])) { - $already++; - } -} - -sub get_canonical_form($) { - my $dir = shift; - my $original = $dir; - - die "$dir is not a directory." unless -d $dir; - - $dir .= "/" unless $dir =~ m#/$#; - $dir .= ".git/" unless $dir =~ m#\.git/$#; - - die "$original does not have a .git/ subdirectory.\n" unless -d $dir; - - return $dir; -} - -sub link_two_files($$) { - my ($sfilename, $dfilename) = @_; - my $tmpdname = sprintf("%s.old",$dfilename); - rename($dfilename,$tmpdname) - or die sprintf("Failure renaming %s to %s: %s", - $dfilename, $tmpdname, $!); - - if (! link($sfilename,$dfilename)) { - my $failtxt = ""; - unless (rename($tmpdname,$dfilename)) { - $failtxt = sprintf( - "Git Repository containing %s is probably corrupted, " . - "please copy '%s' to '%s' to fix.\n", - $tmpdname, $dfilename); - } - - die sprintf("Failed to link %s to %s: %s\n%s" . - $sfilename, $dfilename, - $!, $dfilename, $failtxt); - } - - unlink($tmpdname) - or die sprintf("Unlink of %s failed: %s\n", - $dfilename, $!); - - $linked++; -} - - -sub usage() { - print("usage: git relink [--safe] <dir>... <master_dir> \n"); - print("All directories should contain a .git/objects/ subdirectory.\n"); - print("Options\n"); - print("\t--safe\t" . - "Stops if two objects with the same hash exist but " . - "have different sizes. Default is to warn and continue.\n"); - exit(1); -} diff --git a/git-submodule.sh b/git-submodule.sh index 9788175979..136e26a2c8 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -204,8 +204,14 @@ cmd_add() tstart s|/*$|| ') - git ls-files --error-unmatch "$sm_path" > /dev/null 2>&1 && - die "$(eval_gettext "'\$sm_path' already exists in the index")" + if test -z "$force" + then + git ls-files --error-unmatch "$sm_path" > /dev/null 2>&1 && + die "$(eval_gettext "'\$sm_path' already exists in the index")" + else + git ls-files -s "$sm_path" | sane_grep -v "^160000" > /dev/null 2>&1 && + die "$(eval_gettext "'\$sm_path' already exists in the index and is not a submodule")" + fi if test -z "$force" && ! git add --dry-run --ignore-missing "$sm_path" > /dev/null 2>&1 then @@ -607,7 +613,10 @@ cmd_update() if test $just_cloned -eq 1 then subsha1= - update_module=checkout + case "$update_module" in + merge | rebase | none) + update_module=checkout ;; + esac else subsha1=$(sanitize_submodule_env; cd "$sm_path" && git rev-parse --verify HEAD) || @@ -424,6 +424,7 @@ static struct cmd_struct commands[] = { { "diff-files", cmd_diff_files, RUN_SETUP | NEED_WORK_TREE }, { "diff-index", cmd_diff_index, RUN_SETUP }, { "diff-tree", cmd_diff_tree, RUN_SETUP }, + { "difftool", cmd_difftool, RUN_SETUP | NEED_WORK_TREE }, { "fast-export", cmd_fast_export, RUN_SETUP }, { "fetch", cmd_fetch, RUN_SETUP }, { "fetch-pack", cmd_fetch_pack, RUN_SETUP }, @@ -471,7 +472,7 @@ static struct cmd_struct commands[] = { { "prune-packed", cmd_prune_packed, RUN_SETUP }, { "pull", cmd_pull, RUN_SETUP | NEED_WORK_TREE }, { "push", cmd_push, RUN_SETUP }, - { "read-tree", cmd_read_tree, RUN_SETUP }, + { "read-tree", cmd_read_tree, RUN_SETUP | SUPPORT_SUPER_PREFIX}, { "receive-pack", cmd_receive_pack }, { "reflog", cmd_reflog, RUN_SETUP }, { "remote", cmd_remote, RUN_SETUP }, diff --git a/gpg-interface.h b/gpg-interface.h index ea68885ad5..d2d4fd3a65 100644 --- a/gpg-interface.h +++ b/gpg-interface.h @@ -1,8 +1,9 @@ #ifndef GPG_INTERFACE_H #define GPG_INTERFACE_H -#define GPG_VERIFY_VERBOSE 1 -#define GPG_VERIFY_RAW 2 +#define GPG_VERIFY_VERBOSE 1 +#define GPG_VERIFY_RAW 2 +#define GPG_VERIFY_OMIT_STATUS 4 struct signature_check { char *payload; @@ -3,6 +3,7 @@ #include "color.h" #include "graph.h" #include "revision.h" +#include "argv-array.h" /* Internal API */ @@ -79,6 +80,26 @@ static void graph_show_line_prefix(const struct diff_options *diffopt) static const char **column_colors; static unsigned short column_colors_max; +static void parse_graph_colors_config(struct argv_array *colors, const char *string) +{ + const char *end, *start; + + start = string; + end = string + strlen(string); + while (start < end) { + const char *comma = strchrnul(start, ','); + char color[COLOR_MAXLEN]; + + if (!color_parse_mem(start, comma - start, color)) + argv_array_push(colors, color); + else + warning(_("ignore invalid color '%.*s' in log.graphColors"), + (int)(comma - start), start); + start = comma + 1; + } + argv_array_push(colors, GIT_COLOR_RESET); +} + void graph_set_column_colors(const char **colors, unsigned short colors_max) { column_colors = colors; @@ -238,9 +259,22 @@ struct git_graph *graph_init(struct rev_info *opt) { struct git_graph *graph = xmalloc(sizeof(struct git_graph)); - if (!column_colors) - graph_set_column_colors(column_colors_ansi, - column_colors_ansi_max); + if (!column_colors) { + char *string; + if (git_config_get_string("log.graphcolors", &string)) { + /* not configured -- use default */ + graph_set_column_colors(column_colors_ansi, + column_colors_ansi_max); + } else { + static struct argv_array custom_colors = ARGV_ARRAY_INIT; + argv_array_clear(&custom_colors); + parse_graph_colors_config(&custom_colors, string); + free(string); + /* graph_set_column_colors takes a max-index, not a count */ + graph_set_column_colors(custom_colors.argv, + custom_colors.argc - 1); + } + } graph->commit = NULL; graph->revs = opt; @@ -105,7 +105,22 @@ static int is_executable(const char *name) return 0; #if defined(GIT_WINDOWS_NATIVE) -{ /* cannot trust the executable bit, peek into the file instead */ + /* + * On Windows there is no executable bit. The file extension + * indicates whether it can be run as an executable, and Git + * has special-handling to detect scripts and launch them + * through the indicated script interpreter. We test for the + * file extension first because virus scanners may make + * it quite expensive to open many files. + */ + if (ends_with(name, ".exe")) + return S_IXUSR; + +{ + /* + * Now that we know it does not have an executable extension, + * peek into the file instead. + */ char buf[3] = { 0 }; int n; int fd = open(name, O_RDONLY); @@ -113,8 +128,8 @@ static int is_executable(const char *name) if (fd >= 0) { n = read(fd, buf, 2); if (n == 2) - /* DOS executables start with "MZ" */ - if (!strcmp(buf, "#!") || !strcmp(buf, "MZ")) + /* look for a she-bang */ + if (!strcmp(buf, "#!")) st.st_mode |= S_IXUSR; close(fd); } diff --git a/read-cache.c b/read-cache.c index 7a9a7de91e..9054369dd0 100644 --- a/read-cache.c +++ b/read-cache.c @@ -503,7 +503,6 @@ int index_name_pos(const struct index_state *istate, const char *name, int namel return index_name_stage_pos(istate, name, namelen, 0); } -/* Remove entry, return true if there are more entries to go.. */ int remove_index_entry_at(struct index_state *istate, int pos) { struct cache_entry *ce = istate->cache[pos]; diff --git a/ref-filter.c b/ref-filter.c index 1a978405e6..3820b21cc7 100644 --- a/ref-filter.c +++ b/ref-filter.c @@ -1361,7 +1361,7 @@ static struct ref_array_item *new_ref_array_item(const char *refname, return ref; } -static int filter_ref_kind(struct ref_filter *filter, const char *refname) +static int ref_kind_from_refname(const char *refname) { unsigned int i; @@ -1374,11 +1374,7 @@ static int filter_ref_kind(struct ref_filter *filter, const char *refname) { "refs/tags/", FILTER_REFS_TAGS} }; - if (filter->kind == FILTER_REFS_BRANCHES || - filter->kind == FILTER_REFS_REMOTES || - filter->kind == FILTER_REFS_TAGS) - return filter->kind; - else if (!strcmp(refname, "HEAD")) + if (!strcmp(refname, "HEAD")) return FILTER_REFS_DETACHED_HEAD; for (i = 0; i < ARRAY_SIZE(ref_kind); i++) { @@ -1389,6 +1385,15 @@ static int filter_ref_kind(struct ref_filter *filter, const char *refname) return FILTER_REFS_OTHERS; } +static int filter_ref_kind(struct ref_filter *filter, const char *refname) +{ + if (filter->kind == FILTER_REFS_BRANCHES || + filter->kind == FILTER_REFS_REMOTES || + filter->kind == FILTER_REFS_TAGS) + return filter->kind; + return ref_kind_from_refname(refname); +} + /* * A call-back given to for_each_ref(). Filter refs and keep them for * later object processing. @@ -1589,8 +1594,7 @@ static int cmp_ref_sorting(struct ref_sorting *s, struct ref_array_item *a, stru return (s->reverse) ? -cmp : cmp; } -static struct ref_sorting *ref_sorting; -static int compare_refs(const void *a_, const void *b_) +static int compare_refs(const void *a_, const void *b_, void *ref_sorting) { struct ref_array_item *a = *((struct ref_array_item **)a_); struct ref_array_item *b = *((struct ref_array_item **)b_); @@ -1606,8 +1610,7 @@ static int compare_refs(const void *a_, const void *b_) void ref_array_sort(struct ref_sorting *sorting, struct ref_array *array) { - ref_sorting = sorting; - QSORT(array->items, array->nr, compare_refs); + QSORT_S(array->items, array->nr, compare_refs, sorting); } static void append_literal(const char *cp, const char *ep, struct ref_formatting_state *state) @@ -1671,6 +1674,16 @@ void show_ref_array_item(struct ref_array_item *info, const char *format, int qu putchar('\n'); } +void pretty_print_ref(const char *name, const unsigned char *sha1, + const char *format) +{ + struct ref_array_item *ref_item; + ref_item = new_ref_array_item(name, sha1, 0); + ref_item->kind = ref_kind_from_refname(name); + show_ref_array_item(ref_item, format, 0); + free_array_item(ref_item); +} + /* If no sorting option is given, use refname to sort as default */ struct ref_sorting *ref_default_sorting(void) { diff --git a/ref-filter.h b/ref-filter.h index fc55fa3574..7b05592baf 100644 --- a/ref-filter.h +++ b/ref-filter.h @@ -109,4 +109,11 @@ struct ref_sorting *ref_default_sorting(void); /* Function to parse --merged and --no-merged options */ int parse_opt_merge_filter(const struct option *opt, const char *arg, int unset); +/* + * Print a single ref, outside of any ref-filter. Note that the + * name must be a fully qualified refname. + */ +void pretty_print_ref(const char *name, const unsigned char *sha1, + const char *format); + #endif /* REF_FILTER_H */ @@ -255,6 +255,7 @@ static void read_remotes_file(struct remote *remote) if (!f) return; + remote->configured_in_repo = 1; remote->origin = REMOTE_REMOTES; while (strbuf_getline(&buf, f) != EOF) { const char *v; @@ -289,6 +290,7 @@ static void read_branches_file(struct remote *remote) return; } + remote->configured_in_repo = 1; remote->origin = REMOTE_BRANCHES; /* @@ -371,6 +373,8 @@ static int handle_config(const char *key, const char *value, void *cb) } remote = make_remote(name, namelen); remote->origin = REMOTE_CONFIG; + if (current_config_scope() == CONFIG_SCOPE_REPO) + remote->configured_in_repo = 1; if (!strcmp(subkey, "mirror")) remote->mirror = git_config_bool(key, value); else if (!strcmp(subkey, "skipdefaultupdate")) @@ -714,9 +718,13 @@ struct remote *pushremote_get(const char *name) return remote_get_1(name, pushremote_for_branch); } -int remote_is_configured(struct remote *remote) +int remote_is_configured(struct remote *remote, int in_repo) { - return remote && remote->origin; + if (!remote) + return 0; + if (in_repo) + return remote->configured_in_repo; + return !!remote->origin; } int for_each_remote(each_remote_fn fn, void *priv) @@ -15,7 +15,7 @@ struct remote { struct hashmap_entry ent; /* must be first */ const char *name; - int origin; + int origin, configured_in_repo; const char *foreign_vcs; @@ -60,7 +60,7 @@ struct remote { struct remote *remote_get(const char *name); struct remote *pushremote_get(const char *name); -int remote_is_configured(struct remote *remote); +int remote_is_configured(struct remote *remote, int in_repo); typedef int each_remote_fn(struct remote *remote, void *priv); int for_each_remote(each_remote_fn fn, void *priv); diff --git a/run-command.c b/run-command.c index 73bfba7ef9..5227f78aea 100644 --- a/run-command.c +++ b/run-command.c @@ -871,8 +871,14 @@ const char *find_hook(const char *name) strbuf_reset(&path); strbuf_git_path(&path, "hooks/%s", name); - if (access(path.buf, X_OK) < 0) + if (access(path.buf, X_OK) < 0) { +#ifdef STRIP_EXTENSION + strbuf_addstr(&path, STRIP_EXTENSION); + if (access(path.buf, X_OK) >= 0) + return path.buf; +#endif return NULL; + } return path.buf; } diff --git a/sequencer.c b/sequencer.c index 9adb7bbf1d..1f729b053b 100644 --- a/sequencer.c +++ b/sequencer.c @@ -17,6 +17,8 @@ #include "argv-array.h" #include "quote.h" #include "trailer.h" +#include "log-tree.h" +#include "wt-status.h" #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION" @@ -30,6 +32,59 @@ static GIT_PATH_FUNC(git_path_opts_file, "sequencer/opts") static GIT_PATH_FUNC(git_path_head_file, "sequencer/head") static GIT_PATH_FUNC(git_path_abort_safety_file, "sequencer/abort-safety") +static GIT_PATH_FUNC(rebase_path, "rebase-merge") +/* + * The file containing rebase commands, comments, and empty lines. + * This file is created by "git rebase -i" then edited by the user. As + * the lines are processed, they are removed from the front of this + * file and written to the tail of 'done'. + */ +static GIT_PATH_FUNC(rebase_path_todo, "rebase-merge/git-rebase-todo") +/* + * The rebase command lines that have already been processed. A line + * is moved here when it is first handled, before any associated user + * actions. + */ +static GIT_PATH_FUNC(rebase_path_done, "rebase-merge/done") +/* + * The file to keep track of how many commands were already processed (e.g. + * for the prompt). + */ +static GIT_PATH_FUNC(rebase_path_msgnum, "rebase-merge/msgnum"); +/* + * The file to keep track of how many commands are to be processed in total + * (e.g. for the prompt). + */ +static GIT_PATH_FUNC(rebase_path_msgtotal, "rebase-merge/end"); +/* + * The commit message that is planned to be used for any changes that + * need to be committed following a user interaction. + */ +static GIT_PATH_FUNC(rebase_path_message, "rebase-merge/message") +/* + * The file into which is accumulated the suggested commit message for + * squash/fixup commands. When the first of a series of squash/fixups + * is seen, the file is created and the commit message from the + * previous commit and from the first squash/fixup commit are written + * to it. The commit message for each subsequent squash/fixup commit + * is appended to the file as it is processed. + * + * The first line of the file is of the form + * # This is a combination of $count commits. + * where $count is the number of commits whose messages have been + * written to the file so far (including the initial "pick" commit). + * Each time that a commit message is processed, this line is read and + * updated. It is deleted just before the combined commit is made. + */ +static GIT_PATH_FUNC(rebase_path_squash_msg, "rebase-merge/message-squash") +/* + * If the current series of squash/fixups has not yet included a squash + * command, then this file exists and holds the commit message of the + * original "pick" commit. (If the series ends without a "squash" + * command, then this can be used as the commit message of the combined + * commit without opening the editor.) + */ +static GIT_PATH_FUNC(rebase_path_fixup_msg, "rebase-merge/message-fixup") /* * A script to set the GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and * GIT_AUTHOR_DATE that will be used for the commit that is currently @@ -37,24 +92,57 @@ static GIT_PATH_FUNC(git_path_abort_safety_file, "sequencer/abort-safety") */ static GIT_PATH_FUNC(rebase_path_author_script, "rebase-merge/author-script") /* + * When an "edit" rebase command is being processed, the SHA1 of the + * commit to be edited is recorded in this file. When "git rebase + * --continue" is executed, if there are any staged changes then they + * will be amended to the HEAD commit, but only provided the HEAD + * commit is still the commit to be edited. When any other rebase + * command is processed, this file is deleted. + */ +static GIT_PATH_FUNC(rebase_path_amend, "rebase-merge/amend") +/* + * When we stop at a given patch via the "edit" command, this file contains + * the abbreviated commit name of the corresponding patch. + */ +static GIT_PATH_FUNC(rebase_path_stopped_sha, "rebase-merge/stopped-sha") +/* + * For the post-rewrite hook, we make a list of rewritten commits and + * their new sha1s. The rewritten-pending list keeps the sha1s of + * commits that have been processed, but not committed yet, + * e.g. because they are waiting for a 'squash' command. + */ +static GIT_PATH_FUNC(rebase_path_rewritten_list, "rebase-merge/rewritten-list") +static GIT_PATH_FUNC(rebase_path_rewritten_pending, + "rebase-merge/rewritten-pending") +/* * The following files are written by git-rebase just after parsing the * command-line (and are only consumed, not modified, by the sequencer). */ static GIT_PATH_FUNC(rebase_path_gpg_sign_opt, "rebase-merge/gpg_sign_opt") +static GIT_PATH_FUNC(rebase_path_orig_head, "rebase-merge/orig-head") +static GIT_PATH_FUNC(rebase_path_verbose, "rebase-merge/verbose") +static GIT_PATH_FUNC(rebase_path_head_name, "rebase-merge/head-name") +static GIT_PATH_FUNC(rebase_path_onto, "rebase-merge/onto") +static GIT_PATH_FUNC(rebase_path_autostash, "rebase-merge/autostash") +static GIT_PATH_FUNC(rebase_path_strategy, "rebase-merge/strategy") +static GIT_PATH_FUNC(rebase_path_strategy_opts, "rebase-merge/strategy_opts") -/* We will introduce the 'interactive rebase' mode later */ static inline int is_rebase_i(const struct replay_opts *opts) { - return 0; + return opts->action == REPLAY_INTERACTIVE_REBASE; } static const char *get_dir(const struct replay_opts *opts) { + if (is_rebase_i(opts)) + return rebase_path(); return git_path_seq_dir(); } static const char *get_todo_path(const struct replay_opts *opts) { + if (is_rebase_i(opts)) + return rebase_path_todo(); return git_path_todo_file(); } @@ -122,7 +210,15 @@ int sequencer_remove_state(struct replay_opts *opts) static const char *action_name(const struct replay_opts *opts) { - return opts->action == REPLAY_REVERT ? N_("revert") : N_("cherry-pick"); + switch (opts->action) { + case REPLAY_REVERT: + return N_("revert"); + case REPLAY_PICK: + return N_("cherry-pick"); + case REPLAY_INTERACTIVE_REBASE: + return N_("rebase -i"); + } + die(_("Unknown action: %d"), opts->action); } struct commit_message { @@ -347,6 +443,8 @@ static int do_recursive_merge(struct commit *base, struct commit *next, o.ancestor = base ? base_label : "(empty tree)"; o.branch1 = "HEAD"; o.branch2 = next ? next_label : "(empty tree)"; + if (is_rebase_i(opts)) + o.buffer_output = 2; head_tree = parse_tree_indirect(head); next_tree = next ? next->tree : empty_tree(); @@ -358,13 +456,17 @@ static int do_recursive_merge(struct commit *base, struct commit *next, clean = merge_trees(&o, head_tree, next_tree, base_tree, &result); + if (is_rebase_i(opts) && clean <= 0) + fputs(o.obuf.buf, stdout); strbuf_release(&o.obuf); if (clean < 0) return clean; if (active_cache_changed && write_locked_index(&the_index, &index_lock, COMMIT_LOCK)) - /* TRANSLATORS: %s will be "revert" or "cherry-pick" */ + /* TRANSLATORS: %s will be "revert", "cherry-pick" or + * "rebase -i". + */ return error(_("%s: Unable to write new index file"), _(action_name(opts))); rollback_lock_file(&index_lock); @@ -409,19 +511,64 @@ static int is_index_unchanged(void) return !hashcmp(active_cache_tree->sha1, head_commit->tree->object.oid.hash); } +static int write_author_script(const char *message) +{ + struct strbuf buf = STRBUF_INIT; + const char *eol; + int res; + + for (;;) + if (!*message || starts_with(message, "\n")) { +missing_author: + /* Missing 'author' line? */ + unlink(rebase_path_author_script()); + return 0; + } else if (skip_prefix(message, "author ", &message)) + break; + else if ((eol = strchr(message, '\n'))) + message = eol + 1; + else + goto missing_author; + + strbuf_addstr(&buf, "GIT_AUTHOR_NAME='"); + while (*message && *message != '\n' && *message != '\r') + if (skip_prefix(message, " <", &message)) + break; + else if (*message != '\'') + strbuf_addch(&buf, *(message++)); + else + strbuf_addf(&buf, "'\\\\%c'", *(message++)); + strbuf_addstr(&buf, "'\nGIT_AUTHOR_EMAIL='"); + while (*message && *message != '\n' && *message != '\r') + if (skip_prefix(message, "> ", &message)) + break; + else if (*message != '\'') + strbuf_addch(&buf, *(message++)); + else + strbuf_addf(&buf, "'\\\\%c'", *(message++)); + strbuf_addstr(&buf, "'\nGIT_AUTHOR_DATE='@"); + while (*message && *message != '\n' && *message != '\r') + if (*message != '\'') + strbuf_addch(&buf, *(message++)); + else + strbuf_addf(&buf, "'\\\\%c'", *(message++)); + res = write_message(buf.buf, buf.len, rebase_path_author_script(), 1); + strbuf_release(&buf); + return res; +} + /* - * Read the author-script file into an environment block, ready for use in - * run_command(), that can be free()d afterwards. + * Read a list of environment variable assignments (such as the author-script + * file) into an environment block. Returns -1 on error, 0 otherwise. */ -static char **read_author_script(void) +static int read_env_script(struct argv_array *env) { struct strbuf script = STRBUF_INIT; int i, count = 0; - char *p, *p2, **env; - size_t env_size; + char *p, *p2; if (strbuf_read_file(&script, rebase_path_author_script(), 256) <= 0) - return NULL; + return -1; for (p = script.buf; *p; p++) if (skip_prefix(p, "'\\\\''", (const char **)&p2)) @@ -433,19 +580,12 @@ static char **read_author_script(void) count++; } - env_size = (count + 1) * sizeof(*env); - strbuf_grow(&script, env_size); - memmove(script.buf + env_size, script.buf, script.len); - p = script.buf + env_size; - env = (char **)strbuf_detach(&script, NULL); - - for (i = 0; i < count; i++) { - env[i] = p; + for (i = 0, p = script.buf; i < count; i++) { + argv_array_push(env, p); p += strlen(p) + 1; } - env[count] = NULL; - return env; + return 0; } static const char staged_changes_advice[] = @@ -478,14 +618,18 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts, int allow_empty, int edit, int amend, int cleanup_commit_message) { - char **env = NULL; - struct argv_array array; - int rc; + struct child_process cmd = CHILD_PROCESS_INIT; const char *value; + cmd.git_cmd = 1; + if (is_rebase_i(opts)) { - env = read_author_script(); - if (!env) { + if (!edit) { + cmd.stdout_to_stderr = 1; + cmd.err = -1; + } + + if (read_env_script(&cmd.env_array)) { const char *gpg_opt = gpg_sign_opt_quoted(opts); return error(_(staged_changes_advice), @@ -493,39 +637,47 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts, } } - argv_array_init(&array); - argv_array_push(&array, "commit"); - argv_array_push(&array, "-n"); + argv_array_push(&cmd.args, "commit"); + argv_array_push(&cmd.args, "-n"); if (amend) - argv_array_push(&array, "--amend"); + argv_array_push(&cmd.args, "--amend"); if (opts->gpg_sign) - argv_array_pushf(&array, "-S%s", opts->gpg_sign); + argv_array_pushf(&cmd.args, "-S%s", opts->gpg_sign); if (opts->signoff) - argv_array_push(&array, "-s"); + argv_array_push(&cmd.args, "-s"); if (defmsg) - argv_array_pushl(&array, "-F", defmsg, NULL); + argv_array_pushl(&cmd.args, "-F", defmsg, NULL); if (cleanup_commit_message) - argv_array_push(&array, "--cleanup=strip"); + argv_array_push(&cmd.args, "--cleanup=strip"); if (edit) - argv_array_push(&array, "-e"); + argv_array_push(&cmd.args, "-e"); else if (!cleanup_commit_message && !opts->signoff && !opts->record_origin && git_config_get_value("commit.cleanup", &value)) - argv_array_push(&array, "--cleanup=verbatim"); + argv_array_push(&cmd.args, "--cleanup=verbatim"); if (allow_empty) - argv_array_push(&array, "--allow-empty"); + argv_array_push(&cmd.args, "--allow-empty"); if (opts->allow_empty_message) - argv_array_push(&array, "--allow-empty-message"); + argv_array_push(&cmd.args, "--allow-empty-message"); - rc = run_command_v_opt_cd_env(array.argv, RUN_GIT_CMD, NULL, - (const char *const *)env); - argv_array_clear(&array); - free(env); + if (cmd.err == -1) { + /* hide stderr on success */ + struct strbuf buf = STRBUF_INIT; + int rc = pipe_command(&cmd, + NULL, 0, + /* stdout is already redirected */ + NULL, 0, + &buf, 0); + if (rc) + fputs(buf.buf, stderr); + strbuf_release(&buf); + return rc; + } - return rc; + return run_command(&cmd); } static int is_original_commit_empty(struct commit *commit) @@ -586,33 +738,202 @@ static int allow_empty(struct replay_opts *opts, struct commit *commit) return 1; } +/* + * Note that ordering matters in this enum. Not only must it match the mapping + * below, it is also divided into several sections that matter. When adding + * new commands, make sure you add it in the right section. + */ enum todo_command { + /* commands that handle commits */ TODO_PICK = 0, - TODO_REVERT + TODO_REVERT, + TODO_EDIT, + TODO_REWORD, + TODO_FIXUP, + TODO_SQUASH, + /* commands that do something else than handling a single commit */ + TODO_EXEC, + /* commands that do nothing but are counted for reporting progress */ + TODO_NOOP, + TODO_DROP, + /* comments (not counted for reporting progress) */ + TODO_COMMENT }; -static const char *todo_command_strings[] = { - "pick", - "revert" +static struct { + char c; + const char *str; +} todo_command_info[] = { + { 'p', "pick" }, + { 0, "revert" }, + { 'e', "edit" }, + { 'r', "reword" }, + { 'f', "fixup" }, + { 's', "squash" }, + { 'x', "exec" }, + { 0, "noop" }, + { 'd', "drop" }, + { 0, NULL } }; static const char *command_to_string(const enum todo_command command) { - if ((size_t)command < ARRAY_SIZE(todo_command_strings)) - return todo_command_strings[command]; + if (command < TODO_COMMENT) + return todo_command_info[command].str; die("Unknown command: %d", command); } +static int is_noop(const enum todo_command command) +{ + return TODO_NOOP <= command; +} + +static int is_fixup(enum todo_command command) +{ + return command == TODO_FIXUP || command == TODO_SQUASH; +} + +static int update_squash_messages(enum todo_command command, + struct commit *commit, struct replay_opts *opts) +{ + struct strbuf buf = STRBUF_INIT; + int count, res; + const char *message, *body; + + if (file_exists(rebase_path_squash_msg())) { + struct strbuf header = STRBUF_INIT; + char *eol, *p; + + if (strbuf_read_file(&buf, rebase_path_squash_msg(), 2048) <= 0) + return error(_("could not read '%s'"), + rebase_path_squash_msg()); + + p = buf.buf + 1; + eol = strchrnul(buf.buf, '\n'); + if (buf.buf[0] != comment_line_char || + (p += strcspn(p, "0123456789\n")) == eol) + return error(_("unexpected 1st line of squash message:" + "\n\n\t%.*s"), + (int)(eol - buf.buf), buf.buf); + count = strtol(p, NULL, 10); + + if (count < 1) + return error(_("invalid 1st line of squash message:\n" + "\n\t%.*s"), + (int)(eol - buf.buf), buf.buf); + + strbuf_addf(&header, "%c ", comment_line_char); + strbuf_addf(&header, + _("This is a combination of %d commits."), ++count); + strbuf_splice(&buf, 0, eol - buf.buf, header.buf, header.len); + strbuf_release(&header); + } else { + unsigned char head[20]; + struct commit *head_commit; + const char *head_message, *body; + + if (get_sha1("HEAD", head)) + return error(_("need a HEAD to fixup")); + if (!(head_commit = lookup_commit_reference(head))) + return error(_("could not read HEAD")); + if (!(head_message = get_commit_buffer(head_commit, NULL))) + return error(_("could not read HEAD's commit message")); + + find_commit_subject(head_message, &body); + if (write_message(body, strlen(body), + rebase_path_fixup_msg(), 0)) { + unuse_commit_buffer(head_commit, head_message); + return error(_("cannot write '%s'"), + rebase_path_fixup_msg()); + } + + count = 2; + strbuf_addf(&buf, "%c ", comment_line_char); + strbuf_addf(&buf, _("This is a combination of %d commits."), + count); + strbuf_addf(&buf, "\n%c ", comment_line_char); + strbuf_addstr(&buf, _("This is the 1st commit message:")); + strbuf_addstr(&buf, "\n\n"); + strbuf_addstr(&buf, body); + + unuse_commit_buffer(head_commit, head_message); + } + + if (!(message = get_commit_buffer(commit, NULL))) + return error(_("could not read commit message of %s"), + oid_to_hex(&commit->object.oid)); + find_commit_subject(message, &body); + + if (command == TODO_SQUASH) { + unlink(rebase_path_fixup_msg()); + strbuf_addf(&buf, "\n%c ", comment_line_char); + strbuf_addf(&buf, _("This is the commit message #%d:"), count); + strbuf_addstr(&buf, "\n\n"); + strbuf_addstr(&buf, body); + } else if (command == TODO_FIXUP) { + strbuf_addf(&buf, "\n%c ", comment_line_char); + strbuf_addf(&buf, _("The commit message #%d will be skipped:"), + count); + strbuf_addstr(&buf, "\n\n"); + strbuf_add_commented_lines(&buf, body, strlen(body)); + } else + return error(_("unknown command: %d"), command); + unuse_commit_buffer(commit, message); + + res = write_message(buf.buf, buf.len, rebase_path_squash_msg(), 0); + strbuf_release(&buf); + return res; +} + +static void flush_rewritten_pending(void) { + struct strbuf buf = STRBUF_INIT; + unsigned char newsha1[20]; + FILE *out; + + if (strbuf_read_file(&buf, rebase_path_rewritten_pending(), 82) > 0 && + !get_sha1("HEAD", newsha1) && + (out = fopen(rebase_path_rewritten_list(), "a"))) { + char *bol = buf.buf, *eol; + + while (*bol) { + eol = strchrnul(bol, '\n'); + fprintf(out, "%.*s %s\n", (int)(eol - bol), + bol, sha1_to_hex(newsha1)); + if (!*eol) + break; + bol = eol + 1; + } + fclose(out); + unlink(rebase_path_rewritten_pending()); + } + strbuf_release(&buf); +} + +static void record_in_rewritten(struct object_id *oid, + enum todo_command next_command) { + FILE *out = fopen(rebase_path_rewritten_pending(), "a"); + + if (!out) + return; + + fprintf(out, "%s\n", oid_to_hex(oid)); + fclose(out); + + if (!is_fixup(next_command)) + flush_rewritten_pending(); +} static int do_pick_commit(enum todo_command command, struct commit *commit, - struct replay_opts *opts) + struct replay_opts *opts, int final_fixup) { + int edit = opts->edit, cleanup_commit_message = 0; + const char *msg_file = edit ? NULL : git_path_merge_msg(); unsigned char head[20]; struct commit *base, *next, *parent; const char *base_label, *next_label; struct commit_message msg = { NULL, NULL, NULL, NULL }; struct strbuf msgbuf = STRBUF_INIT; - int res, unborn = 0, allow; + int res, unborn = 0, amend = 0, allow = 0; if (opts->no_commit) { /* @@ -632,9 +953,8 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, } discard_cache(); - if (!commit->parents) { + if (!commit->parents) parent = NULL; - } else if (commit->parents->next) { /* Reverting or cherry-picking a merge commit */ int cnt; @@ -658,11 +978,23 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, else parent = commit->parents->item; - if (opts->allow_ff && - ((parent && !hashcmp(parent->object.oid.hash, head)) || - (!parent && unborn))) - return fast_forward_to(commit->object.oid.hash, head, unborn, opts); + if (get_message(commit, &msg) != 0) + return error(_("cannot get commit message for %s"), + oid_to_hex(&commit->object.oid)); + if (opts->allow_ff && !is_fixup(command) && + ((parent && !hashcmp(parent->object.oid.hash, head)) || + (!parent && unborn))) { + if (is_rebase_i(opts)) + write_author_script(msg.message); + res = fast_forward_to(commit->object.oid.hash, head, unborn, + opts); + if (res || command != TODO_REWORD) + goto leave; + edit = amend = 1; + msg_file = NULL; + goto fast_forward_edit; + } if (parent && parse_commit(parent) < 0) /* TRANSLATORS: The first %s will be a "todo" command like "revert" or "pick", the second %s a SHA1. */ @@ -670,10 +1002,6 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, command_to_string(command), oid_to_hex(&parent->object.oid)); - if (get_message(commit, &msg) != 0) - return error(_("cannot get commit message for %s"), - oid_to_hex(&commit->object.oid)); - /* * "commit" is an existing commit. We would want to apply * the difference it introduces since its first parent "prev" @@ -704,14 +1032,9 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, next = commit; next_label = msg.label; - /* - * Append the commit log message to msgbuf; it starts - * after the tree, parent, author, committer - * information followed by "\n\n". - */ - p = strstr(msg.message, "\n\n"); - if (p) - strbuf_addstr(&msgbuf, skip_blank_lines(p + 2)); + /* Append the commit log message to msgbuf. */ + if (find_commit_subject(msg.message, &p)) + strbuf_addstr(&msgbuf, p); if (opts->record_origin) { if (!has_conforming_footer(&msgbuf, NULL, 0)) @@ -722,7 +1045,32 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, } } - if (!opts->strategy || !strcmp(opts->strategy, "recursive") || command == TODO_REVERT) { + if (command == TODO_REWORD) + edit = 1; + else if (is_fixup(command)) { + if (update_squash_messages(command, commit, opts)) + return -1; + amend = 1; + if (!final_fixup) + msg_file = rebase_path_squash_msg(); + else if (file_exists(rebase_path_fixup_msg())) { + cleanup_commit_message = 1; + msg_file = rebase_path_fixup_msg(); + } else { + const char *dest = git_path("SQUASH_MSG"); + unlink(dest); + if (copy_file(dest, rebase_path_squash_msg(), 0666)) + return error(_("could not rename '%s' to '%s'"), + rebase_path_squash_msg(), dest); + unlink(git_path("MERGE_MSG")); + msg_file = dest; + edit = 1; + } + } + + if (is_rebase_i(opts) && write_author_script(msg.message) < 0) + res = -1; + else if (!opts->strategy || !strcmp(opts->strategy, "recursive") || command == TODO_REVERT) { res = do_recursive_merge(base, next, base_label, next_label, head, &msgbuf, opts); if (res < 0) @@ -777,8 +1125,14 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, goto leave; } if (!opts->no_commit) - res = run_git_commit(opts->edit ? NULL : git_path_merge_msg(), - opts, allow, opts->edit, 0, 0); +fast_forward_edit: + res = run_git_commit(msg_file, opts, allow, edit, amend, + cleanup_commit_message); + + if (!res && final_fixup) { + unlink(rebase_path_fixup_msg()); + unlink(rebase_path_squash_msg()); + } leave: free_message(commit, &msg); @@ -837,6 +1191,7 @@ struct todo_list { struct strbuf buf; struct todo_item *items; int nr, alloc, current; + int done_nr, total_nr; }; #define TODO_LIST_INIT { STRBUF_INIT } @@ -864,20 +1219,45 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol) /* left-trim */ bol += strspn(bol, " \t"); - for (i = 0; i < ARRAY_SIZE(todo_command_strings); i++) - if (skip_prefix(bol, todo_command_strings[i], &bol)) { + if (bol == eol || *bol == '\r' || *bol == comment_line_char) { + item->command = TODO_COMMENT; + item->commit = NULL; + item->arg = bol; + item->arg_len = eol - bol; + return 0; + } + + for (i = 0; i < TODO_COMMENT; i++) + if (skip_prefix(bol, todo_command_info[i].str, &bol)) { + item->command = i; + break; + } else if (bol[1] == ' ' && *bol == todo_command_info[i].c) { + bol++; item->command = i; break; } - if (i >= ARRAY_SIZE(todo_command_strings)) + if (i >= TODO_COMMENT) return -1; + if (item->command == TODO_NOOP) { + item->commit = NULL; + item->arg = bol; + item->arg_len = eol - bol; + return 0; + } + /* Eat up extra spaces/ tabs before object name */ padding = strspn(bol, " \t"); if (!padding) return -1; bol += padding; + if (item->command == TODO_EXEC) { + item->arg = bol; + item->arg_len = (int)(eol - bol); + return 0; + } + end_of_object_name = (char *) bol + strcspn(bol, " \t\n"); saved = *end_of_object_name; *end_of_object_name = '\0'; @@ -898,7 +1278,7 @@ static int parse_insn_buffer(char *buf, struct todo_list *todo_list) { struct todo_item *item; char *p = buf, *next_p; - int i, res = 0; + int i, res = 0, fixup_okay = file_exists(rebase_path_done()); for (i = 1; *p; i++, p = next_p) { char *eol = strchrnul(p, '\n'); @@ -913,14 +1293,32 @@ static int parse_insn_buffer(char *buf, struct todo_list *todo_list) if (parse_insn_line(item, p, eol)) { res = error(_("invalid line %d: %.*s"), i, (int)(eol - p), p); - item->command = -1; + item->command = TODO_NOOP; } + + if (fixup_okay) + ; /* do nothing */ + else if (is_fixup(item->command)) + return error(_("cannot '%s' without a previous commit"), + command_to_string(item->command)); + else if (!is_noop(item->command)) + fixup_okay = 1; } - if (!todo_list->nr) - return error(_("no commits parsed.")); + return res; } +static int count_commands(struct todo_list *todo_list) +{ + int count = 0, i; + + for (i = 0; i < todo_list->nr; i++) + if (todo_list->items[i].command != TODO_COMMENT) + count++; + + return count; +} + static int read_populate_todo(struct todo_list *todo_list, struct replay_opts *opts) { @@ -938,8 +1336,16 @@ static int read_populate_todo(struct todo_list *todo_list, close(fd); res = parse_insn_buffer(todo_list->buf.buf, todo_list); - if (res) + if (res) { + if (is_rebase_i(opts)) + return error(_("please fix this using " + "'git rebase --edit-todo'.")); return error(_("unusable instruction sheet: '%s'"), todo_file); + } + + if (!todo_list->nr && + (!is_rebase_i(opts) || !file_exists(rebase_path_done()))) + return error(_("no commits parsed.")); if (!is_rebase_i(opts)) { enum todo_command valid = @@ -955,6 +1361,26 @@ static int read_populate_todo(struct todo_list *todo_list, return error(_("cannot revert during a cherry-pick.")); } + if (is_rebase_i(opts)) { + struct todo_list done = TODO_LIST_INIT; + FILE *f = fopen(rebase_path_msgtotal(), "w"); + + if (strbuf_read_file(&done.buf, rebase_path_done(), 0) > 0 && + !parse_insn_buffer(done.buf.buf, &done)) + todo_list->done_nr = count_commands(&done); + else + todo_list->done_nr = 0; + + todo_list->total_nr = todo_list->done_nr + + count_commands(todo_list); + todo_list_release(&done); + + if (f) { + fprintf(f, "%d\n", todo_list->total_nr); + fclose(f); + } + } + return 0; } @@ -1003,6 +1429,26 @@ static int populate_opts_cb(const char *key, const char *value, void *data) return 0; } +static void read_strategy_opts(struct replay_opts *opts, struct strbuf *buf) +{ + int i; + + strbuf_reset(buf); + if (!read_oneliner(buf, rebase_path_strategy(), 0)) + return; + opts->strategy = strbuf_detach(buf, NULL); + if (!read_oneliner(buf, rebase_path_strategy_opts(), 0)) + return; + + opts->xopts_nr = split_cmdline(buf->buf, (const char ***)&opts->xopts); + for (i = 0; i < opts->xopts_nr; i++) { + const char *arg = opts->xopts[i]; + + skip_prefix(arg, "--", &arg); + opts->xopts[i] = xstrdup(arg); + } +} + static int read_populate_opts(struct replay_opts *opts) { if (is_rebase_i(opts)) { @@ -1016,6 +1462,11 @@ static int read_populate_opts(struct replay_opts *opts) opts->gpg_sign = xstrdup(buf.buf + 2); } } + + if (file_exists(rebase_path_verbose())) + opts->verbose = 1; + + read_strategy_opts(opts, &buf); strbuf_release(&buf); return 0; @@ -1040,7 +1491,7 @@ static int walk_revs_populate_todo(struct todo_list *todo_list, { enum todo_command command = opts->action == REPLAY_PICK ? TODO_PICK : TODO_REVERT; - const char *command_string = todo_command_strings[command]; + const char *command_string = todo_command_info[command].str; struct commit *commit; if (prepare_revs(opts)) @@ -1071,8 +1522,7 @@ static int create_seq_dir(void) error(_("a cherry-pick or revert is already in progress")); advise(_("try \"git cherry-pick (--continue | --quit | --abort)\"")); return -1; - } - else if (mkdir(git_path_seq_dir(), 0777) < 0) + } else if (mkdir(git_path_seq_dir(), 0777) < 0) return error_errno(_("could not create sequencer directory '%s'"), git_path_seq_dir()); return 0; @@ -1205,6 +1655,13 @@ static int save_todo(struct todo_list *todo_list, struct replay_opts *opts) const char *todo_path = get_todo_path(opts); int next = todo_list->current, offset, fd; + /* + * rebase -i writes "git-rebase-todo" without the currently executing + * command, appending it to "done" instead. + */ + if (is_rebase_i(opts)) + next++; + fd = hold_lock_file_for_update(&todo_lock, todo_path, 0); if (fd < 0) return error_errno(_("could not lock '%s'"), todo_path); @@ -1215,6 +1672,23 @@ static int save_todo(struct todo_list *todo_list, struct replay_opts *opts) return error_errno(_("could not write to '%s'"), todo_path); if (commit_lock_file(&todo_lock) < 0) return error(_("failed to finalize '%s'."), todo_path); + + if (is_rebase_i(opts)) { + const char *done_path = rebase_path_done(); + int fd = open(done_path, O_CREAT | O_WRONLY | O_APPEND, 0666); + int prev_offset = !next ? 0 : + todo_list->items[next - 1].offset_in_buf; + + if (fd >= 0 && offset > prev_offset && + write_in_full(fd, todo_list->buf.buf + prev_offset, + offset - prev_offset) < 0) { + close(fd); + return error_errno(_("could not write to '%s'"), + done_path); + } + if (fd >= 0) + close(fd); + } return 0; } @@ -1253,9 +1727,228 @@ static int save_opts(struct replay_opts *opts) return res; } +static int make_patch(struct commit *commit, struct replay_opts *opts) +{ + struct strbuf buf = STRBUF_INIT; + struct rev_info log_tree_opt; + const char *subject, *p; + int res = 0; + + p = short_commit_name(commit); + if (write_message(p, strlen(p), rebase_path_stopped_sha(), 1) < 0) + return -1; + + strbuf_addf(&buf, "%s/patch", get_dir(opts)); + memset(&log_tree_opt, 0, sizeof(log_tree_opt)); + init_revisions(&log_tree_opt, NULL); + log_tree_opt.abbrev = 0; + log_tree_opt.diff = 1; + log_tree_opt.diffopt.output_format = DIFF_FORMAT_PATCH; + log_tree_opt.disable_stdin = 1; + log_tree_opt.no_commit_id = 1; + log_tree_opt.diffopt.file = fopen(buf.buf, "w"); + log_tree_opt.diffopt.use_color = GIT_COLOR_NEVER; + if (!log_tree_opt.diffopt.file) + res |= error_errno(_("could not open '%s'"), buf.buf); + else { + res |= log_tree_commit(&log_tree_opt, commit); + fclose(log_tree_opt.diffopt.file); + } + strbuf_reset(&buf); + + strbuf_addf(&buf, "%s/message", get_dir(opts)); + if (!file_exists(buf.buf)) { + const char *commit_buffer = get_commit_buffer(commit, NULL); + find_commit_subject(commit_buffer, &subject); + res |= write_message(subject, strlen(subject), buf.buf, 1); + unuse_commit_buffer(commit, commit_buffer); + } + strbuf_release(&buf); + + return res; +} + +static int intend_to_amend(void) +{ + unsigned char head[20]; + char *p; + + if (get_sha1("HEAD", head)) + return error(_("cannot read HEAD")); + + p = sha1_to_hex(head); + return write_message(p, strlen(p), rebase_path_amend(), 1); +} + +static int error_with_patch(struct commit *commit, + const char *subject, int subject_len, + struct replay_opts *opts, int exit_code, int to_amend) +{ + if (make_patch(commit, opts)) + return -1; + + if (to_amend) { + if (intend_to_amend()) + return -1; + + fprintf(stderr, "You can amend the commit now, with\n" + "\n" + " git commit --amend %s\n" + "\n" + "Once you are satisfied with your changes, run\n" + "\n" + " git rebase --continue\n", gpg_sign_opt_quoted(opts)); + } else if (exit_code) + fprintf(stderr, "Could not apply %s... %.*s\n", + short_commit_name(commit), subject_len, subject); + + return exit_code; +} + +static int error_failed_squash(struct commit *commit, + struct replay_opts *opts, int subject_len, const char *subject) +{ + if (rename(rebase_path_squash_msg(), rebase_path_message())) + return error(_("could not rename '%s' to '%s'"), + rebase_path_squash_msg(), rebase_path_message()); + unlink(rebase_path_fixup_msg()); + unlink(git_path("MERGE_MSG")); + if (copy_file(git_path("MERGE_MSG"), rebase_path_message(), 0666)) + return error(_("could not copy '%s' to '%s'"), + rebase_path_message(), git_path("MERGE_MSG")); + return error_with_patch(commit, subject, subject_len, opts, 1, 0); +} + +static int do_exec(const char *command_line) +{ + const char *child_argv[] = { NULL, NULL }; + int dirty, status; + + fprintf(stderr, "Executing: %s\n", command_line); + child_argv[0] = command_line; + status = run_command_v_opt(child_argv, RUN_USING_SHELL); + + /* force re-reading of the cache */ + if (discard_cache() < 0 || read_cache() < 0) + return error(_("could not read index")); + + dirty = require_clean_work_tree("rebase", NULL, 1, 1); + + if (status) { + warning(_("execution failed: %s\n%s" + "You can fix the problem, and then run\n" + "\n" + " git rebase --continue\n" + "\n"), + command_line, + dirty ? N_("and made changes to the index and/or the " + "working tree\n") : ""); + if (status == 127) + /* command not found */ + status = 1; + } else if (dirty) { + warning(_("execution succeeded: %s\nbut " + "left changes to the index and/or the working tree\n" + "Commit or stash your changes, and then run\n" + "\n" + " git rebase --continue\n" + "\n"), command_line); + status = 1; + } + + return status; +} + +static int is_final_fixup(struct todo_list *todo_list) +{ + int i = todo_list->current; + + if (!is_fixup(todo_list->items[i].command)) + return 0; + + while (++i < todo_list->nr) + if (is_fixup(todo_list->items[i].command)) + return 0; + else if (!is_noop(todo_list->items[i].command)) + break; + return 1; +} + +static enum todo_command peek_command(struct todo_list *todo_list, int offset) +{ + int i; + + for (i = todo_list->current + offset; i < todo_list->nr; i++) + if (!is_noop(todo_list->items[i].command)) + return todo_list->items[i].command; + + return -1; +} + +static int apply_autostash(struct replay_opts *opts) +{ + struct strbuf stash_sha1 = STRBUF_INIT; + struct child_process child = CHILD_PROCESS_INIT; + int ret = 0; + + if (!read_oneliner(&stash_sha1, rebase_path_autostash(), 1)) { + strbuf_release(&stash_sha1); + return 0; + } + strbuf_trim(&stash_sha1); + + child.git_cmd = 1; + argv_array_push(&child.args, "stash"); + argv_array_push(&child.args, "apply"); + argv_array_push(&child.args, stash_sha1.buf); + if (!run_command(&child)) + printf(_("Applied autostash.")); + else { + struct child_process store = CHILD_PROCESS_INIT; + + store.git_cmd = 1; + argv_array_push(&store.args, "stash"); + argv_array_push(&store.args, "store"); + argv_array_push(&store.args, "-m"); + argv_array_push(&store.args, "autostash"); + argv_array_push(&store.args, "-q"); + argv_array_push(&store.args, stash_sha1.buf); + if (run_command(&store)) + ret = error(_("cannot store %s"), stash_sha1.buf); + else + printf(_("Applying autostash resulted in conflicts.\n" + "Your changes are safe in the stash.\n" + "You can run \"git stash pop\" or" + " \"git stash drop\" at any time.\n")); + } + + strbuf_release(&stash_sha1); + return ret; +} + +static const char *reflog_message(struct replay_opts *opts, + const char *sub_action, const char *fmt, ...) +{ + va_list ap; + static struct strbuf buf = STRBUF_INIT; + + va_start(ap, fmt); + strbuf_reset(&buf); + strbuf_addstr(&buf, action_name(opts)); + if (sub_action) + strbuf_addf(&buf, " (%s)", sub_action); + if (fmt) { + strbuf_addstr(&buf, ": "); + strbuf_vaddf(&buf, fmt, ap); + } + va_end(ap); + + return buf.buf; +} + static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) { - int res; + int res = 0; setenv(GIT_REFLOG_ACTION, action_name(opts), 0); if (opts->allow_ff) @@ -1268,12 +1961,179 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) struct todo_item *item = todo_list->items + todo_list->current; if (save_todo(todo_list, opts)) return -1; - res = do_pick_commit(item->command, item->commit, opts); + if (is_rebase_i(opts)) { + if (item->command != TODO_COMMENT) { + FILE *f = fopen(rebase_path_msgnum(), "w"); + + todo_list->done_nr++; + + if (f) { + fprintf(f, "%d\n", todo_list->done_nr); + fclose(f); + } + fprintf(stderr, "Rebasing (%d/%d)%s", + todo_list->done_nr, + todo_list->total_nr, + opts->verbose ? "\n" : "\r"); + } + unlink(rebase_path_message()); + unlink(rebase_path_author_script()); + unlink(rebase_path_stopped_sha()); + unlink(rebase_path_amend()); + } + if (item->command <= TODO_SQUASH) { + if (is_rebase_i(opts)) + setenv("GIT_REFLOG_ACTION", reflog_message(opts, + command_to_string(item->command), NULL), + 1); + res = do_pick_commit(item->command, item->commit, + opts, is_final_fixup(todo_list)); + if (is_rebase_i(opts) && res < 0) { + /* Reschedule */ + todo_list->current--; + if (save_todo(todo_list, opts)) + return -1; + } + if (item->command == TODO_EDIT) { + struct commit *commit = item->commit; + if (!res) + warning(_("stopped at %s... %.*s"), + short_commit_name(commit), + item->arg_len, item->arg); + return error_with_patch(commit, + item->arg, item->arg_len, opts, res, + !res); + } + if (is_rebase_i(opts) && !res) + record_in_rewritten(&item->commit->object.oid, + peek_command(todo_list, 1)); + if (res && is_fixup(item->command)) { + if (res == 1) + intend_to_amend(); + return error_failed_squash(item->commit, opts, + item->arg_len, item->arg); + } else if (res && is_rebase_i(opts)) + return res | error_with_patch(item->commit, + item->arg, item->arg_len, opts, res, + item->command == TODO_REWORD); + } else if (item->command == TODO_EXEC) { + char *end_of_arg = (char *)(item->arg + item->arg_len); + int saved = *end_of_arg; + + *end_of_arg = '\0'; + res = do_exec(item->arg); + *end_of_arg = saved; + } else if (!is_noop(item->command)) + return error(_("unknown command %d"), item->command); + todo_list->current++; if (res) return res; } + if (is_rebase_i(opts)) { + struct strbuf head_ref = STRBUF_INIT, buf = STRBUF_INIT; + struct stat st; + + /* Stopped in the middle, as planned? */ + if (todo_list->current < todo_list->nr) + return 0; + + if (read_oneliner(&head_ref, rebase_path_head_name(), 0) && + starts_with(head_ref.buf, "refs/")) { + const char *msg; + unsigned char head[20], orig[20]; + int res; + + if (get_sha1("HEAD", head)) { + res = error(_("cannot read HEAD")); +cleanup_head_ref: + strbuf_release(&head_ref); + strbuf_release(&buf); + return res; + } + if (!read_oneliner(&buf, rebase_path_orig_head(), 0) || + get_sha1_hex(buf.buf, orig)) { + res = error(_("could not read orig-head")); + goto cleanup_head_ref; + } + if (!read_oneliner(&buf, rebase_path_onto(), 0)) { + res = error(_("could not read 'onto'")); + goto cleanup_head_ref; + } + msg = reflog_message(opts, "finish", "%s onto %s", + head_ref.buf, buf.buf); + if (update_ref(msg, head_ref.buf, head, orig, + REF_NODEREF, UPDATE_REFS_MSG_ON_ERR)) { + res = error(_("could not update %s"), + head_ref.buf); + goto cleanup_head_ref; + } + msg = reflog_message(opts, "finish", "returning to %s", + head_ref.buf); + if (create_symref("HEAD", head_ref.buf, msg)) { + res = error(_("could not update HEAD to %s"), + head_ref.buf); + goto cleanup_head_ref; + } + strbuf_reset(&buf); + } + + if (opts->verbose) { + struct rev_info log_tree_opt; + struct object_id orig, head; + + memset(&log_tree_opt, 0, sizeof(log_tree_opt)); + init_revisions(&log_tree_opt, NULL); + log_tree_opt.diff = 1; + log_tree_opt.diffopt.output_format = + DIFF_FORMAT_DIFFSTAT; + log_tree_opt.disable_stdin = 1; + + if (read_oneliner(&buf, rebase_path_orig_head(), 0) && + !get_sha1(buf.buf, orig.hash) && + !get_sha1("HEAD", head.hash)) { + diff_tree_sha1(orig.hash, head.hash, + "", &log_tree_opt.diffopt); + log_tree_diff_flush(&log_tree_opt); + } + } + flush_rewritten_pending(); + if (!stat(rebase_path_rewritten_list(), &st) && + st.st_size > 0) { + struct child_process child = CHILD_PROCESS_INIT; + const char *post_rewrite_hook = + find_hook("post-rewrite"); + + child.in = open(rebase_path_rewritten_list(), O_RDONLY); + child.git_cmd = 1; + argv_array_push(&child.args, "notes"); + argv_array_push(&child.args, "copy"); + argv_array_push(&child.args, "--for-rewrite=rebase"); + /* we don't care if this copying failed */ + run_command(&child); + + if (post_rewrite_hook) { + struct child_process hook = CHILD_PROCESS_INIT; + + hook.in = open(rebase_path_rewritten_list(), + O_RDONLY); + hook.stdout_to_stderr = 1; + argv_array_push(&hook.args, post_rewrite_hook); + argv_array_push(&hook.args, "rebase"); + /* we don't care if this hook failed */ + run_command(&hook); + } + } + apply_autostash(opts); + + fprintf(stderr, "Successfully rebased and updated %s.\n", + head_ref.buf); + + strbuf_release(&buf); + strbuf_release(&head_ref); + } + /* * Sequence of picks finished successfully; cleanup by * removing the .git/sequencer directory @@ -1291,6 +2151,47 @@ static int continue_single_pick(void) return run_command_v_opt(argv, RUN_GIT_CMD); } +static int commit_staged_changes(struct replay_opts *opts) +{ + int amend = 0; + + if (has_unstaged_changes(1)) + return error(_("cannot rebase: You have unstaged changes.")); + if (!has_uncommitted_changes(0)) { + const char *cherry_pick_head = git_path("CHERRY_PICK_HEAD"); + + if (file_exists(cherry_pick_head) && unlink(cherry_pick_head)) + return error(_("could not remove CHERRY_PICK_HEAD")); + return 0; + } + + if (file_exists(rebase_path_amend())) { + struct strbuf rev = STRBUF_INIT; + unsigned char head[20], to_amend[20]; + + if (get_sha1("HEAD", head)) + return error(_("cannot amend non-existing commit")); + if (!read_oneliner(&rev, rebase_path_amend(), 0)) + return error(_("invalid file: '%s'"), rebase_path_amend()); + if (get_sha1_hex(rev.buf, to_amend)) + return error(_("invalid contents: '%s'"), + rebase_path_amend()); + if (hashcmp(head, to_amend)) + return error(_("\nYou have uncommitted changes in your " + "working tree. Please, commit them\n" + "first and then run 'git rebase " + "--continue' again.")); + + strbuf_release(&rev); + amend = 1; + } + + if (run_git_commit(rebase_path_message(), opts, 1, 1, amend, 0)) + return error(_("could not commit staged changes.")); + unlink(rebase_path_amend()); + return 0; +} + int sequencer_continue(struct replay_opts *opts) { struct todo_list todo_list = TODO_LIST_INIT; @@ -1299,25 +2200,39 @@ int sequencer_continue(struct replay_opts *opts) if (read_and_refresh_cache(opts)) return -1; - if (!file_exists(get_todo_path(opts))) + if (is_rebase_i(opts)) { + if (commit_staged_changes(opts)) + return -1; + } else if (!file_exists(get_todo_path(opts))) return continue_single_pick(); if (read_populate_opts(opts)) return -1; if ((res = read_populate_todo(&todo_list, opts))) goto release_todo_list; - /* Verify that the conflict has been resolved */ - if (file_exists(git_path_cherry_pick_head()) || - file_exists(git_path_revert_head())) { - res = continue_single_pick(); - if (res) + if (!is_rebase_i(opts)) { + /* Verify that the conflict has been resolved */ + if (file_exists(git_path_cherry_pick_head()) || + file_exists(git_path_revert_head())) { + res = continue_single_pick(); + if (res) + goto release_todo_list; + } + if (index_differs_from("HEAD", 0, 0)) { + res = error_dirty_index(opts); goto release_todo_list; + } + todo_list.current++; + } else if (file_exists(rebase_path_stopped_sha())) { + struct strbuf buf = STRBUF_INIT; + struct object_id oid; + + if (read_oneliner(&buf, rebase_path_stopped_sha(), 1) && + !get_sha1_committish(buf.buf, oid.hash)) + record_in_rewritten(&oid, peek_command(&todo_list, 0)); + strbuf_release(&buf); } - if (index_differs_from("HEAD", 0, 0)) { - res = error_dirty_index(opts); - goto release_todo_list; - } - todo_list.current++; + res = pick_commits(&todo_list, opts); release_todo_list: todo_list_release(&todo_list); @@ -1328,7 +2243,7 @@ static int single_pick(struct commit *cmit, struct replay_opts *opts) { setenv(GIT_REFLOG_ACTION, action_name(opts), 0); return do_pick_commit(opts->action == REPLAY_PICK ? - TODO_PICK : TODO_REVERT, cmit, opts); + TODO_PICK : TODO_REVERT, cmit, opts, 0); } int sequencer_pick_revisions(struct replay_opts *opts) diff --git a/sequencer.h b/sequencer.h index 7a513c576b..f885b68395 100644 --- a/sequencer.h +++ b/sequencer.h @@ -7,7 +7,8 @@ const char *git_path_seq_dir(void); enum replay_action { REPLAY_REVERT, - REPLAY_PICK + REPLAY_PICK, + REPLAY_INTERACTIVE_REBASE }; struct replay_opts { @@ -23,6 +24,7 @@ struct replay_opts { int allow_empty; int allow_empty_message; int keep_redundant_commits; + int verbose; int mainline; @@ -486,6 +486,30 @@ int verify_repository_format(const struct repository_format *format, return 0; } +void read_gitfile_error_die(int error_code, const char *path, const char *dir) +{ + switch (error_code) { + case READ_GITFILE_ERR_STAT_FAILED: + case READ_GITFILE_ERR_NOT_A_FILE: + /* non-fatal; follow return path */ + break; + case READ_GITFILE_ERR_OPEN_FAILED: + die_errno("Error opening '%s'", path); + case READ_GITFILE_ERR_TOO_LARGE: + die("Too large to be a .git file: '%s'", path); + case READ_GITFILE_ERR_READ_FAILED: + die("Error reading %s", path); + case READ_GITFILE_ERR_INVALID_FORMAT: + die("Invalid gitfile format: %s", path); + case READ_GITFILE_ERR_NO_PATH: + die("No path in gitfile: %s", path); + case READ_GITFILE_ERR_NOT_A_REPO: + die("Not a git repository: %s", dir); + default: + die("BUG: unknown error code"); + } +} + /* * Try to read the location of the git directory from the .git file, * return path to git directory if found. @@ -559,28 +583,8 @@ const char *read_gitfile_gently(const char *path, int *return_error_code) cleanup_return: if (return_error_code) *return_error_code = error_code; - else if (error_code) { - switch (error_code) { - case READ_GITFILE_ERR_STAT_FAILED: - case READ_GITFILE_ERR_NOT_A_FILE: - /* non-fatal; follow return path */ - break; - case READ_GITFILE_ERR_OPEN_FAILED: - die_errno("Error opening '%s'", path); - case READ_GITFILE_ERR_TOO_LARGE: - die("Too large to be a .git file: '%s'", path); - case READ_GITFILE_ERR_READ_FAILED: - die("Error reading %s", path); - case READ_GITFILE_ERR_INVALID_FORMAT: - die("Invalid gitfile format: %s", path); - case READ_GITFILE_ERR_NO_PATH: - die("No path in gitfile: %s", path); - case READ_GITFILE_ERR_NOT_A_REPO: - die("Not a git repository: %s", dir); - default: - assert(0); - } - } + else if (error_code) + read_gitfile_error_die(error_code, path, dir); free(buf); return error_code ? NULL : path; @@ -1017,11 +1021,11 @@ const char *setup_git_directory(void) return setup_git_directory_gently(NULL); } -const char *resolve_gitdir(const char *suspect) +const char *resolve_gitdir_gently(const char *suspect, int *return_error_code) { if (is_git_directory(suspect)) return suspect; - return read_gitfile(suspect); + return read_gitfile_gently(suspect, return_error_code); } /* if any standard file descriptor is missing open it to /dev/null */ diff --git a/sha1_file.c b/sha1_file.c index b5e827ac9e..ec957db5e1 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -1630,39 +1630,54 @@ int git_open_cloexec(const char *name, int flags) return fd; } -static int stat_sha1_file(const unsigned char *sha1, struct stat *st) +/* + * Find "sha1" as a loose object in the local repository or in an alternate. + * Returns 0 on success, negative on failure. + * + * The "path" out-parameter will give the path of the object we found (if any). + * Note that it may point to static storage and is only valid until another + * call to sha1_file_name(), etc. + */ +static int stat_sha1_file(const unsigned char *sha1, struct stat *st, + const char **path) { struct alternate_object_database *alt; - if (!lstat(sha1_file_name(sha1), st)) + *path = sha1_file_name(sha1); + if (!lstat(*path, st)) return 0; prepare_alt_odb(); errno = ENOENT; for (alt = alt_odb_list; alt; alt = alt->next) { - const char *path = alt_sha1_path(alt, sha1); - if (!lstat(path, st)) + *path = alt_sha1_path(alt, sha1); + if (!lstat(*path, st)) return 0; } return -1; } -static int open_sha1_file(const unsigned char *sha1) +/* + * Like stat_sha1_file(), but actually open the object and return the + * descriptor. See the caveats on the "path" parameter above. + */ +static int open_sha1_file(const unsigned char *sha1, const char **path) { int fd; struct alternate_object_database *alt; int most_interesting_errno; - fd = git_open(sha1_file_name(sha1)); + *path = sha1_file_name(sha1); + fd = git_open(*path); if (fd >= 0) return fd; most_interesting_errno = errno; prepare_alt_odb(); for (alt = alt_odb_list; alt; alt = alt->next) { - const char *path = alt_sha1_path(alt, sha1); - fd = git_open(path); + *path = alt_sha1_path(alt, sha1); + fd = git_open(*path); if (fd >= 0) return fd; if (most_interesting_errno == ENOENT) @@ -1672,12 +1687,21 @@ static int open_sha1_file(const unsigned char *sha1) return -1; } -void *map_sha1_file(const unsigned char *sha1, unsigned long *size) +/* + * Map the loose object at "path" if it is not NULL, or the path found by + * searching for a loose object named "sha1". + */ +static void *map_sha1_file_1(const char *path, + const unsigned char *sha1, + unsigned long *size) { void *map; int fd; - fd = open_sha1_file(sha1); + if (path) + fd = git_open(path); + else + fd = open_sha1_file(sha1, &path); map = NULL; if (fd >= 0) { struct stat st; @@ -1686,7 +1710,7 @@ void *map_sha1_file(const unsigned char *sha1, unsigned long *size) *size = xsize_t(st.st_size); if (!*size) { /* mmap() is forbidden on empty files */ - error("object file %s is empty", sha1_file_name(sha1)); + error("object file %s is empty", path); return NULL; } map = xmmap(NULL, *size, PROT_READ, MAP_PRIVATE, fd, 0); @@ -1696,6 +1720,11 @@ void *map_sha1_file(const unsigned char *sha1, unsigned long *size) return map; } +void *map_sha1_file(const unsigned char *sha1, unsigned long *size) +{ + return map_sha1_file_1(NULL, sha1, size); +} + unsigned long unpack_object_header_buffer(const unsigned char *buf, unsigned long len, enum object_type *type, unsigned long *sizep) { @@ -2342,11 +2371,10 @@ static inline void release_delta_base_cache(struct delta_base_cache_entry *ent) void clear_delta_base_cache(void) { - struct hashmap_iter iter; - struct delta_base_cache_entry *entry; - for (entry = hashmap_iter_first(&delta_base_cache, &iter); - entry; - entry = hashmap_iter_next(&iter)) { + struct list_head *lru, *tmp; + list_for_each_safe(lru, tmp, &delta_base_cache_lru) { + struct delta_base_cache_entry *entry = + list_entry(lru, struct delta_base_cache_entry, lru); release_delta_base_cache(entry); } } @@ -2806,8 +2834,9 @@ static int sha1_loose_object_info(const unsigned char *sha1, * object even exists. */ if (!oi->typep && !oi->typename && !oi->sizep) { + const char *path; struct stat st; - if (stat_sha1_file(sha1, &st) < 0) + if (stat_sha1_file(sha1, &st, &path) < 0) return -1; if (oi->disk_sizep) *oi->disk_sizep = st.st_size; @@ -3003,6 +3032,8 @@ void *read_sha1_file_extended(const unsigned char *sha1, { void *data; const struct packed_git *p; + const char *path; + struct stat st; const unsigned char *repl = lookup_replace_object_extended(sha1, flag); errno = 0; @@ -3018,12 +3049,9 @@ void *read_sha1_file_extended(const unsigned char *sha1, die("replacement %s not found for %s", sha1_to_hex(repl), sha1_to_hex(sha1)); - if (has_loose_object(repl)) { - const char *path = sha1_file_name(sha1); - + if (!stat_sha1_file(repl, &st, &path)) die("loose object %s (stored in %s) is corrupt", sha1_to_hex(repl), path); - } if ((p = has_packed_and_bad(repl)) != NULL) die("packed object %s (stored in %s) is corrupt", @@ -3793,3 +3821,122 @@ int for_each_packed_object(each_packed_object_fn cb, void *data, unsigned flags) } return r ? r : pack_errors; } + +static int check_stream_sha1(git_zstream *stream, + const char *hdr, + unsigned long size, + const char *path, + const unsigned char *expected_sha1) +{ + git_SHA_CTX c; + unsigned char real_sha1[GIT_SHA1_RAWSZ]; + unsigned char buf[4096]; + unsigned long total_read; + int status = Z_OK; + + git_SHA1_Init(&c); + git_SHA1_Update(&c, hdr, stream->total_out); + + /* + * We already read some bytes into hdr, but the ones up to the NUL + * do not count against the object's content size. + */ + total_read = stream->total_out - strlen(hdr) - 1; + + /* + * This size comparison must be "<=" to read the final zlib packets; + * see the comment in unpack_sha1_rest for details. + */ + while (total_read <= size && + (status == Z_OK || status == Z_BUF_ERROR)) { + stream->next_out = buf; + stream->avail_out = sizeof(buf); + if (size - total_read < stream->avail_out) + stream->avail_out = size - total_read; + status = git_inflate(stream, Z_FINISH); + git_SHA1_Update(&c, buf, stream->next_out - buf); + total_read += stream->next_out - buf; + } + git_inflate_end(stream); + + if (status != Z_STREAM_END) { + error("corrupt loose object '%s'", sha1_to_hex(expected_sha1)); + return -1; + } + if (stream->avail_in) { + error("garbage at end of loose object '%s'", + sha1_to_hex(expected_sha1)); + return -1; + } + + git_SHA1_Final(real_sha1, &c); + if (hashcmp(expected_sha1, real_sha1)) { + error("sha1 mismatch for %s (expected %s)", path, + sha1_to_hex(expected_sha1)); + return -1; + } + + return 0; +} + +int read_loose_object(const char *path, + const unsigned char *expected_sha1, + enum object_type *type, + unsigned long *size, + void **contents) +{ + int ret = -1; + int fd = -1; + void *map = NULL; + unsigned long mapsize; + git_zstream stream; + char hdr[32]; + + *contents = NULL; + + map = map_sha1_file_1(path, NULL, &mapsize); + if (!map) { + error_errno("unable to mmap %s", path); + goto out; + } + + if (unpack_sha1_header(&stream, map, mapsize, hdr, sizeof(hdr)) < 0) { + error("unable to unpack header of %s", path); + goto out; + } + + *type = parse_sha1_header(hdr, size); + if (*type < 0) { + error("unable to parse header of %s", path); + git_inflate_end(&stream); + goto out; + } + + if (*type == OBJ_BLOB) { + if (check_stream_sha1(&stream, hdr, *size, path, expected_sha1) < 0) + goto out; + } else { + *contents = unpack_sha1_rest(&stream, hdr, *size, expected_sha1); + if (!*contents) { + error("unable to unpack contents of %s", path); + git_inflate_end(&stream); + goto out; + } + if (check_sha1_signature(expected_sha1, *contents, + *size, typename(*type))) { + error("sha1 mismatch for %s (expected %s)", path, + sha1_to_hex(expected_sha1)); + free(*contents); + goto out; + } + } + + ret = 0; /* everything checks out */ + +out: + if (map) + munmap(map, mapsize); + if (fd >= 0) + close(fd); + return ret; +} diff --git a/string-list.c b/string-list.c index 8c83cac189..45016ad86d 100644 --- a/string-list.c +++ b/string-list.c @@ -211,21 +211,18 @@ struct string_list_item *string_list_append(struct string_list *list, list->strdup_strings ? xstrdup(string) : (char *)string); } -/* Yuck */ -static compare_strings_fn compare_for_qsort; - -/* Only call this from inside string_list_sort! */ -static int cmp_items(const void *a, const void *b) +static int cmp_items(const void *a, const void *b, void *ctx) { + compare_strings_fn cmp = ctx; const struct string_list_item *one = a; const struct string_list_item *two = b; - return compare_for_qsort(one->string, two->string); + return cmp(one->string, two->string); } void string_list_sort(struct string_list *list) { - compare_for_qsort = list->cmp ? list->cmp : strcmp; - QSORT(list->items, list->nr, cmp_items); + QSORT_S(list->items, list->nr, cmp_items, + list->cmp ? list->cmp : strcmp); } struct string_list_item *unsorted_string_list_lookup(struct string_list *list, diff --git a/submodule-config.c b/submodule-config.c index 4bf50f398a..93453909cf 100644 --- a/submodule-config.c +++ b/submodule-config.c @@ -251,6 +251,8 @@ static int parse_push_recurse(const char *opt, const char *arg, return RECURSE_SUBMODULES_ON_DEMAND; else if (!strcmp(arg, "check")) return RECURSE_SUBMODULES_CHECK; + else if (!strcmp(arg, "only")) + return RECURSE_SUBMODULES_ONLY; else if (die_on_error) die("bad %s argument: %s", opt, arg); else diff --git a/submodule.c b/submodule.c index 4c4f033e8a..3b98766a6b 100644 --- a/submodule.c +++ b/submodule.c @@ -1437,22 +1437,57 @@ void absorb_git_dir_into_superproject(const char *prefix, const char *path, unsigned flags) { - const char *sub_git_dir, *v; - char *real_sub_git_dir = NULL, *real_common_git_dir = NULL; + int err_code; + const char *sub_git_dir; struct strbuf gitdir = STRBUF_INIT; - strbuf_addf(&gitdir, "%s/.git", path); - sub_git_dir = resolve_gitdir(gitdir.buf); + sub_git_dir = resolve_gitdir_gently(gitdir.buf, &err_code); /* Not populated? */ - if (!sub_git_dir) - goto out; + if (!sub_git_dir) { + char *real_new_git_dir; + const char *new_git_dir; + const struct submodule *sub; + + if (err_code == READ_GITFILE_ERR_STAT_FAILED) { + /* unpopulated as expected */ + strbuf_release(&gitdir); + return; + } + + if (err_code != READ_GITFILE_ERR_NOT_A_REPO) + /* We don't know what broke here. */ + read_gitfile_error_die(err_code, path, NULL); + + /* + * Maybe populated, but no git directory was found? + * This can happen if the superproject is a submodule + * itself and was just absorbed. The absorption of the + * superproject did not rewrite the git file links yet, + * fix it now. + */ + sub = submodule_from_path(null_sha1, path); + if (!sub) + die(_("could not lookup name for submodule '%s'"), path); + new_git_dir = git_path("modules/%s", sub->name); + if (safe_create_leading_directories_const(new_git_dir) < 0) + die(_("could not create directory '%s'"), new_git_dir); + real_new_git_dir = real_pathdup(new_git_dir); + connect_work_tree_and_git_dir(path, real_new_git_dir); + + free(real_new_git_dir); + } else { + /* Is it already absorbed into the superprojects git dir? */ + char *real_sub_git_dir = real_pathdup(sub_git_dir); + char *real_common_git_dir = real_pathdup(get_git_common_dir()); - /* Is it already absorbed into the superprojects git dir? */ - real_sub_git_dir = real_pathdup(sub_git_dir); - real_common_git_dir = real_pathdup(get_git_common_dir()); - if (!skip_prefix(real_sub_git_dir, real_common_git_dir, &v)) - relocate_single_git_dir_into_superproject(prefix, path); + if (!starts_with(real_sub_git_dir, real_common_git_dir)) + relocate_single_git_dir_into_superproject(prefix, path); + + free(real_sub_git_dir); + free(real_common_git_dir); + } + strbuf_release(&gitdir); if (flags & ABSORB_GITDIR_RECURSE_SUBMODULES) { struct child_process cp = CHILD_PROCESS_INIT; @@ -1478,9 +1513,4 @@ void absorb_git_dir_into_superproject(const char *prefix, strbuf_release(&sb); } - -out: - strbuf_release(&gitdir); - free(real_sub_git_dir); - free(real_common_git_dir); } diff --git a/submodule.h b/submodule.h index b7fe4d2027..05ab674f06 100644 --- a/submodule.h +++ b/submodule.h @@ -6,6 +6,7 @@ struct argv_array; struct sha1_array; enum { + RECURSE_SUBMODULES_ONLY = -5, RECURSE_SUBMODULES_CHECK = -4, RECURSE_SUBMODULES_ERROR = -3, RECURSE_SUBMODULES_NONE = -2, diff --git a/t/helper/test-string-list.c b/t/helper/test-string-list.c index 4a68967bd1..c502fa16d3 100644 --- a/t/helper/test-string-list.c +++ b/t/helper/test-string-list.c @@ -97,6 +97,31 @@ int cmd_main(int argc, const char **argv) return 0; } + if (argc == 2 && !strcmp(argv[1], "sort")) { + struct string_list list = STRING_LIST_INIT_NODUP; + struct strbuf sb = STRBUF_INIT; + struct string_list_item *item; + + strbuf_read(&sb, 0, 0); + + /* + * Split by newline, but don't create a string_list item + * for the empty string after the last separator. + */ + if (sb.buf[sb.len - 1] == '\n') + strbuf_setlen(&sb, sb.len - 1); + string_list_split_in_place(&list, sb.buf, '\n', -1); + + string_list_sort(&list); + + for_each_string_list_item(item, &list) + puts(item->string); + + string_list_clear(&list, 0); + strbuf_release(&sb); + return 0; + } + fprintf(stderr, "%s: unknown function name: %s\n", argv[0], argv[1] ? argv[1] : "(there was none)"); return 1; diff --git a/t/perf/p0071-sort.sh b/t/perf/p0071-sort.sh new file mode 100755 index 0000000000..7c9a35a646 --- /dev/null +++ b/t/perf/p0071-sort.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +test_description='Basic sort performance tests' +. ./perf-lib.sh + +test_perf_default_repo + +test_expect_success 'setup' ' + git ls-files --stage "*.[ch]" "*.sh" | + cut -f2 -d" " | + git cat-file --batch >unsorted +' + +test_perf 'sort(1)' ' + sort <unsorted >expect +' + +test_perf 'string_list_sort()' ' + test-string-list sort <unsorted >actual +' + +test_expect_success 'string_list_sort() sorts like sort(1)' ' + test_cmp_bin expect actual +' + +test_done diff --git a/t/t0001-init.sh b/t/t0001-init.sh index b8fc588b19..e424de5363 100755 --- a/t/t0001-init.sh +++ b/t/t0001-init.sh @@ -258,6 +258,9 @@ test_expect_success POSIXPERM 'init creates a new deep directory (umask vs. shar ( # Leading directories should honor umask while # the repository itself should follow "shared" + mkdir newdir && + # Remove a default ACL if possible. + (setfacl -k newdir 2>/dev/null || true) && umask 002 && git init --bare --shared=0660 newdir/a/b/c && test_path_is_dir newdir/a/b/c/refs && diff --git a/t/t1000-read-tree-m-3way.sh b/t/t1000-read-tree-m-3way.sh index a0b79b4839..3c4d2d6045 100755 --- a/t/t1000-read-tree-m-3way.sh +++ b/t/t1000-read-tree-m-3way.sh @@ -128,29 +128,29 @@ cat >expected <<\EOF EOF check_result () { - git ls-files --stage | sed -e 's/ '"$_x40"' / X /' >current && - test_cmp expected current + git ls-files --stage | sed -e 's/ '"$_x40"' / X /' >current && + test_cmp expected current } # This is done on an empty work directory, which is the normal # merge person behaviour. -test_expect_success \ - '3-way merge with git read-tree -m, empty cache' \ - "rm -fr [NDMALTS][NDMALTSF] Z && - rm .git/index && - read_tree_must_succeed -m $tree_O $tree_A $tree_B && - check_result" +test_expect_success '3-way merge with git read-tree -m, empty cache' ' + rm -fr [NDMALTS][NDMALTSF] Z && + rm .git/index && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && + check_result +' # This starts out with the first head, which is the normal # patch submitter behaviour. -test_expect_success \ - '3-way merge with git read-tree -m, match H' \ - "rm -fr [NDMALTS][NDMALTSF] Z && - rm .git/index && - read_tree_must_succeed $tree_A && - git checkout-index -f -u -a && - read_tree_must_succeed -m $tree_O $tree_A $tree_B && - check_result" +test_expect_success '3-way merge with git read-tree -m, match H' ' + rm -fr [NDMALTS][NDMALTSF] Z && + rm .git/index && + read_tree_must_succeed $tree_A && + git checkout-index -f -u -a && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && + check_result +' : <<\END_OF_CASE_TABLE @@ -208,322 +208,304 @@ DF (file) when tree B require DF to be a directory by having DF/DF END_OF_CASE_TABLE -test_expect_success '1 - must not have an entry not in A.' " - rm -f .git/index XX && - echo XX >XX && - git update-index --add XX && - read_tree_must_fail -m $tree_O $tree_A $tree_B -" - -test_expect_success \ - '2 - must match B in !O && !A && B case.' \ - "rm -f .git/index NA && - cp .orig-B/NA NA && - git update-index --add NA && - read_tree_must_succeed -m $tree_O $tree_A $tree_B" - -test_expect_success \ - '2 - matching B alone is OK in !O && !A && B case.' \ - "rm -f .git/index NA && - cp .orig-B/NA NA && - git update-index --add NA && - echo extra >>NA && - read_tree_must_succeed -m $tree_O $tree_A $tree_B" - -test_expect_success \ - '3 - must match A in !O && A && !B case.' \ - "rm -f .git/index AN && - cp .orig-A/AN AN && - git update-index --add AN && - read_tree_must_succeed -m $tree_O $tree_A $tree_B && - check_result" - -test_expect_success \ - '3 - matching A alone is OK in !O && A && !B case.' \ - "rm -f .git/index AN && - cp .orig-A/AN AN && - git update-index --add AN && - echo extra >>AN && - read_tree_must_succeed -m $tree_O $tree_A $tree_B" - -test_expect_success \ - '3 (fail) - must match A in !O && A && !B case.' " - rm -f .git/index AN && - cp .orig-A/AN AN && - echo extra >>AN && - git update-index --add AN && - read_tree_must_fail -m $tree_O $tree_A $tree_B -" - -test_expect_success \ - '4 - must match and be up-to-date in !O && A && B && A!=B case.' \ - "rm -f .git/index AA && - cp .orig-A/AA AA && - git update-index --add AA && - read_tree_must_succeed -m $tree_O $tree_A $tree_B && - check_result" - -test_expect_success \ - '4 (fail) - must match and be up-to-date in !O && A && B && A!=B case.' " - rm -f .git/index AA && - cp .orig-A/AA AA && - git update-index --add AA && - echo extra >>AA && - read_tree_must_fail -m $tree_O $tree_A $tree_B -" - -test_expect_success \ - '4 (fail) - must match and be up-to-date in !O && A && B && A!=B case.' " - rm -f .git/index AA && - cp .orig-A/AA AA && - echo extra >>AA && - git update-index --add AA && - read_tree_must_fail -m $tree_O $tree_A $tree_B -" - -test_expect_success \ - '5 - must match in !O && A && B && A==B case.' \ - "rm -f .git/index LL && - cp .orig-A/LL LL && - git update-index --add LL && - read_tree_must_succeed -m $tree_O $tree_A $tree_B && - check_result" - -test_expect_success \ - '5 - must match in !O && A && B && A==B case.' \ - "rm -f .git/index LL && - cp .orig-A/LL LL && - git update-index --add LL && - echo extra >>LL && - read_tree_must_succeed -m $tree_O $tree_A $tree_B && - check_result" - -test_expect_success \ - '5 (fail) - must match A in !O && A && B && A==B case.' " - rm -f .git/index LL && - cp .orig-A/LL LL && - echo extra >>LL && - git update-index --add LL && - read_tree_must_fail -m $tree_O $tree_A $tree_B -" - -test_expect_success \ - '6 - must not exist in O && !A && !B case' " - rm -f .git/index DD && - echo DD >DD && - git update-index --add DD && - read_tree_must_fail -m $tree_O $tree_A $tree_B -" - -test_expect_success \ - '7 - must not exist in O && !A && B && O!=B case' " - rm -f .git/index DM && - cp .orig-B/DM DM && - git update-index --add DM && - read_tree_must_fail -m $tree_O $tree_A $tree_B -" - -test_expect_success \ - '8 - must not exist in O && !A && B && O==B case' " - rm -f .git/index DN && - cp .orig-B/DN DN && - git update-index --add DN && - read_tree_must_fail -m $tree_O $tree_A $tree_B -" - -test_expect_success \ - '9 - must match and be up-to-date in O && A && !B && O!=A case' \ - "rm -f .git/index MD && - cp .orig-A/MD MD && - git update-index --add MD && - read_tree_must_succeed -m $tree_O $tree_A $tree_B && - check_result" - -test_expect_success \ - '9 (fail) - must match and be up-to-date in O && A && !B && O!=A case' " - rm -f .git/index MD && - cp .orig-A/MD MD && - git update-index --add MD && - echo extra >>MD && - read_tree_must_fail -m $tree_O $tree_A $tree_B -" - -test_expect_success \ - '9 (fail) - must match and be up-to-date in O && A && !B && O!=A case' " - rm -f .git/index MD && - cp .orig-A/MD MD && - echo extra >>MD && - git update-index --add MD && - read_tree_must_fail -m $tree_O $tree_A $tree_B -" - -test_expect_success \ - '10 - must match and be up-to-date in O && A && !B && O==A case' \ - "rm -f .git/index ND && - cp .orig-A/ND ND && - git update-index --add ND && - read_tree_must_succeed -m $tree_O $tree_A $tree_B && - check_result" - -test_expect_success \ - '10 (fail) - must match and be up-to-date in O && A && !B && O==A case' " - rm -f .git/index ND && - cp .orig-A/ND ND && - git update-index --add ND && - echo extra >>ND && - read_tree_must_fail -m $tree_O $tree_A $tree_B -" - -test_expect_success \ - '10 (fail) - must match and be up-to-date in O && A && !B && O==A case' " - rm -f .git/index ND && - cp .orig-A/ND ND && - echo extra >>ND && - git update-index --add ND && - read_tree_must_fail -m $tree_O $tree_A $tree_B -" - -test_expect_success \ - '11 - must match and be up-to-date in O && A && B && O!=A && O!=B && A!=B case' \ - "rm -f .git/index MM && - cp .orig-A/MM MM && - git update-index --add MM && - read_tree_must_succeed -m $tree_O $tree_A $tree_B && - check_result" - -test_expect_success \ - '11 (fail) - must match and be up-to-date in O && A && B && O!=A && O!=B && A!=B case' " - rm -f .git/index MM && - cp .orig-A/MM MM && - git update-index --add MM && - echo extra >>MM && - read_tree_must_fail -m $tree_O $tree_A $tree_B -" - -test_expect_success \ - '11 (fail) - must match and be up-to-date in O && A && B && O!=A && O!=B && A!=B case' " - rm -f .git/index MM && - cp .orig-A/MM MM && - echo extra >>MM && - git update-index --add MM && - read_tree_must_fail -m $tree_O $tree_A $tree_B -" - -test_expect_success \ - '12 - must match A in O && A && B && O!=A && A==B case' \ - "rm -f .git/index SS && - cp .orig-A/SS SS && - git update-index --add SS && - read_tree_must_succeed -m $tree_O $tree_A $tree_B && - check_result" - -test_expect_success \ - '12 - must match A in O && A && B && O!=A && A==B case' \ - "rm -f .git/index SS && - cp .orig-A/SS SS && - git update-index --add SS && - echo extra >>SS && - read_tree_must_succeed -m $tree_O $tree_A $tree_B && - check_result" - -test_expect_success \ - '12 (fail) - must match A in O && A && B && O!=A && A==B case' " - rm -f .git/index SS && - cp .orig-A/SS SS && - echo extra >>SS && - git update-index --add SS && - read_tree_must_fail -m $tree_O $tree_A $tree_B -" - -test_expect_success \ - '13 - must match A in O && A && B && O!=A && O==B case' \ - "rm -f .git/index MN && - cp .orig-A/MN MN && - git update-index --add MN && - read_tree_must_succeed -m $tree_O $tree_A $tree_B && - check_result" - -test_expect_success \ - '13 - must match A in O && A && B && O!=A && O==B case' \ - "rm -f .git/index MN && - cp .orig-A/MN MN && - git update-index --add MN && - echo extra >>MN && - read_tree_must_succeed -m $tree_O $tree_A $tree_B && - check_result" - -test_expect_success \ - '14 - must match and be up-to-date in O && A && B && O==A && O!=B case' \ - "rm -f .git/index NM && - cp .orig-A/NM NM && - git update-index --add NM && - read_tree_must_succeed -m $tree_O $tree_A $tree_B && - check_result" - -test_expect_success \ - '14 - may match B in O && A && B && O==A && O!=B case' \ - "rm -f .git/index NM && - cp .orig-B/NM NM && - git update-index --add NM && - echo extra >>NM && - read_tree_must_succeed -m $tree_O $tree_A $tree_B && - check_result" - -test_expect_success \ - '14 (fail) - must match and be up-to-date in O && A && B && O==A && O!=B case' " - rm -f .git/index NM && - cp .orig-A/NM NM && - git update-index --add NM && - echo extra >>NM && - read_tree_must_fail -m $tree_O $tree_A $tree_B -" - -test_expect_success \ - '14 (fail) - must match and be up-to-date in O && A && B && O==A && O!=B case' " - rm -f .git/index NM && - cp .orig-A/NM NM && - echo extra >>NM && - git update-index --add NM && - read_tree_must_fail -m $tree_O $tree_A $tree_B -" - -test_expect_success \ - '15 - must match A in O && A && B && O==A && O==B case' \ - "rm -f .git/index NN && - cp .orig-A/NN NN && - git update-index --add NN && - read_tree_must_succeed -m $tree_O $tree_A $tree_B && - check_result" - -test_expect_success \ - '15 - must match A in O && A && B && O==A && O==B case' \ - "rm -f .git/index NN && - cp .orig-A/NN NN && - git update-index --add NN && - echo extra >>NN && - read_tree_must_succeed -m $tree_O $tree_A $tree_B && - check_result" - -test_expect_success \ - '15 (fail) - must match A in O && A && B && O==A && O==B case' " - rm -f .git/index NN && - cp .orig-A/NN NN && - echo extra >>NN && - git update-index --add NN && - read_tree_must_fail -m $tree_O $tree_A $tree_B -" - -# #16 -test_expect_success \ - '16 - A matches in one and B matches in another.' \ - 'rm -f .git/index F16 && - echo F16 >F16 && - git update-index --add F16 && - tree0=$(git write-tree) && - echo E16 >F16 && - git update-index F16 && - tree1=$(git write-tree) && - read_tree_must_succeed -m $tree0 $tree1 $tree1 $tree0 && - git ls-files --stage' +test_expect_success '1 - must not have an entry not in A.' ' + rm -f .git/index XX && + echo XX >XX && + git update-index --add XX && + read_tree_must_fail -m $tree_O $tree_A $tree_B +' + +test_expect_success '2 - must match B in !O && !A && B case.' ' + rm -f .git/index NA && + cp .orig-B/NA NA && + git update-index --add NA && + read_tree_must_succeed -m $tree_O $tree_A $tree_B +' + +test_expect_success '2 - matching B alone is OK in !O && !A && B case.' ' + rm -f .git/index NA && + cp .orig-B/NA NA && + git update-index --add NA && + echo extra >>NA && + read_tree_must_succeed -m $tree_O $tree_A $tree_B +' + +test_expect_success '3 - must match A in !O && A && !B case.' ' + rm -f .git/index AN && + cp .orig-A/AN AN && + git update-index --add AN && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && + check_result +' + +test_expect_success '3 - matching A alone is OK in !O && A && !B case.' ' + rm -f .git/index AN && + cp .orig-A/AN AN && + git update-index --add AN && + echo extra >>AN && + read_tree_must_succeed -m $tree_O $tree_A $tree_B +' + +test_expect_success '3 (fail) - must match A in !O && A && !B case.' ' + rm -f .git/index AN && + cp .orig-A/AN AN && + echo extra >>AN && + git update-index --add AN && + read_tree_must_fail -m $tree_O $tree_A $tree_B +' + +test_expect_success '4 - must match and be up-to-date in !O && A && B && A!=B case.' ' + rm -f .git/index AA && + cp .orig-A/AA AA && + git update-index --add AA && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && + check_result +' + +test_expect_success '4 (fail) - must match and be up-to-date in !O && A && B && A!=B case.' ' + rm -f .git/index AA && + cp .orig-A/AA AA && + git update-index --add AA && + echo extra >>AA && + read_tree_must_fail -m $tree_O $tree_A $tree_B +' + +test_expect_success '4 (fail) - must match and be up-to-date in !O && A && B && A!=B case.' ' + rm -f .git/index AA && + cp .orig-A/AA AA && + echo extra >>AA && + git update-index --add AA && + read_tree_must_fail -m $tree_O $tree_A $tree_B +' + +test_expect_success '5 - must match in !O && A && B && A==B case.' ' + rm -f .git/index LL && + cp .orig-A/LL LL && + git update-index --add LL && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && + check_result +' + +test_expect_success '5 - must match in !O && A && B && A==B case.' ' + rm -f .git/index LL && + cp .orig-A/LL LL && + git update-index --add LL && + echo extra >>LL && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && + check_result +' + +test_expect_success '5 (fail) - must match A in !O && A && B && A==B case.' ' + rm -f .git/index LL && + cp .orig-A/LL LL && + echo extra >>LL && + git update-index --add LL && + read_tree_must_fail -m $tree_O $tree_A $tree_B +' + +test_expect_success '6 - must not exist in O && !A && !B case' ' + rm -f .git/index DD && + echo DD >DD && + git update-index --add DD && + read_tree_must_fail -m $tree_O $tree_A $tree_B +' + +test_expect_success '7 - must not exist in O && !A && B && O!=B case' ' + rm -f .git/index DM && + cp .orig-B/DM DM && + git update-index --add DM && + read_tree_must_fail -m $tree_O $tree_A $tree_B +' + +test_expect_success '8 - must not exist in O && !A && B && O==B case' ' + rm -f .git/index DN && + cp .orig-B/DN DN && + git update-index --add DN && + read_tree_must_fail -m $tree_O $tree_A $tree_B +' + +test_expect_success '9 - must match and be up-to-date in O && A && !B && O!=A case' ' + rm -f .git/index MD && + cp .orig-A/MD MD && + git update-index --add MD && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && + check_result +' + +test_expect_success '9 (fail) - must match and be up-to-date in O && A && !B && O!=A case' ' + rm -f .git/index MD && + cp .orig-A/MD MD && + git update-index --add MD && + echo extra >>MD && + read_tree_must_fail -m $tree_O $tree_A $tree_B +' + +test_expect_success '9 (fail) - must match and be up-to-date in O && A && !B && O!=A case' ' + rm -f .git/index MD && + cp .orig-A/MD MD && + echo extra >>MD && + git update-index --add MD && + read_tree_must_fail -m $tree_O $tree_A $tree_B +' + +test_expect_success '10 - must match and be up-to-date in O && A && !B && O==A case' ' + rm -f .git/index ND && + cp .orig-A/ND ND && + git update-index --add ND && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && + check_result +' + +test_expect_success '10 (fail) - must match and be up-to-date in O && A && !B && O==A case' ' + rm -f .git/index ND && + cp .orig-A/ND ND && + git update-index --add ND && + echo extra >>ND && + read_tree_must_fail -m $tree_O $tree_A $tree_B +' + +test_expect_success '10 (fail) - must match and be up-to-date in O && A && !B && O==A case' ' + rm -f .git/index ND && + cp .orig-A/ND ND && + echo extra >>ND && + git update-index --add ND && + read_tree_must_fail -m $tree_O $tree_A $tree_B +' + +test_expect_success '11 - must match and be up-to-date in O && A && B && O!=A && O!=B && A!=B case' ' + rm -f .git/index MM && + cp .orig-A/MM MM && + git update-index --add MM && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && + check_result +' + +test_expect_success '11 (fail) - must match and be up-to-date in O && A && B && O!=A && O!=B && A!=B case' ' + rm -f .git/index MM && + cp .orig-A/MM MM && + git update-index --add MM && + echo extra >>MM && + read_tree_must_fail -m $tree_O $tree_A $tree_B +' + +test_expect_success '11 (fail) - must match and be up-to-date in O && A && B && O!=A && O!=B && A!=B case' ' + rm -f .git/index MM && + cp .orig-A/MM MM && + echo extra >>MM && + git update-index --add MM && + read_tree_must_fail -m $tree_O $tree_A $tree_B +' + +test_expect_success '12 - must match A in O && A && B && O!=A && A==B case' ' + rm -f .git/index SS && + cp .orig-A/SS SS && + git update-index --add SS && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && + check_result +' + +test_expect_success '12 - must match A in O && A && B && O!=A && A==B case' ' + rm -f .git/index SS && + cp .orig-A/SS SS && + git update-index --add SS && + echo extra >>SS && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && + check_result +' + +test_expect_success '12 (fail) - must match A in O && A && B && O!=A && A==B case' ' + rm -f .git/index SS && + cp .orig-A/SS SS && + echo extra >>SS && + git update-index --add SS && + read_tree_must_fail -m $tree_O $tree_A $tree_B +' + +test_expect_success '13 - must match A in O && A && B && O!=A && O==B case' ' + rm -f .git/index MN && + cp .orig-A/MN MN && + git update-index --add MN && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && + check_result +' + +test_expect_success '13 - must match A in O && A && B && O!=A && O==B case' ' + rm -f .git/index MN && + cp .orig-A/MN MN && + git update-index --add MN && + echo extra >>MN && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && + check_result +' + +test_expect_success '14 - must match and be up-to-date in O && A && B && O==A && O!=B case' ' + rm -f .git/index NM && + cp .orig-A/NM NM && + git update-index --add NM && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && + check_result +' + +test_expect_success '14 - may match B in O && A && B && O==A && O!=B case' ' + rm -f .git/index NM && + cp .orig-B/NM NM && + git update-index --add NM && + echo extra >>NM && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && + check_result +' + +test_expect_success '14 (fail) - must match and be up-to-date in O && A && B && O==A && O!=B case' ' + rm -f .git/index NM && + cp .orig-A/NM NM && + git update-index --add NM && + echo extra >>NM && + read_tree_must_fail -m $tree_O $tree_A $tree_B +' + +test_expect_success '14 (fail) - must match and be up-to-date in O && A && B && O==A && O!=B case' ' + rm -f .git/index NM && + cp .orig-A/NM NM && + echo extra >>NM && + git update-index --add NM && + read_tree_must_fail -m $tree_O $tree_A $tree_B +' + +test_expect_success '15 - must match A in O && A && B && O==A && O==B case' ' + rm -f .git/index NN && + cp .orig-A/NN NN && + git update-index --add NN && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && + check_result +' + +test_expect_success '15 - must match A in O && A && B && O==A && O==B case' ' + rm -f .git/index NN && + cp .orig-A/NN NN && + git update-index --add NN && + echo extra >>NN && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && + check_result +' + +test_expect_success '15 (fail) - must match A in O && A && B && O==A && O==B case' ' + rm -f .git/index NN && + cp .orig-A/NN NN && + echo extra >>NN && + git update-index --add NN && + read_tree_must_fail -m $tree_O $tree_A $tree_B +' + +test_expect_success '16 - A matches in one and B matches in another.' ' + rm -f .git/index F16 && + echo F16 >F16 && + git update-index --add F16 && + tree0=$(git write-tree) && + echo E16 >F16 && + git update-index F16 && + tree1=$(git write-tree) && + read_tree_must_succeed -m $tree0 $tree1 $tree1 $tree0 && + git ls-files --stage +' test_done diff --git a/t/t1001-read-tree-m-2way.sh b/t/t1001-read-tree-m-2way.sh index db1b6f5cf4..5ededd8e40 100755 --- a/t/t1001-read-tree-m-2way.sh +++ b/t/t1001-read-tree-m-2way.sh @@ -14,10 +14,10 @@ all the combinations described in the two-tree merge "carry forward" rules, found in <Documentation/git read-tree.txt>. In the test, these paths are used: - bozbar - in H, stays in M, modified from bozbar to gnusto - frotz - not in H added in M - nitfol - in H, stays in M unmodified - rezrov - in H, deleted in M + bozbar - in H, stays in M, modified from bozbar to gnusto + frotz - not in H added in M + nitfol - in H, stays in M unmodified + rezrov - in H, deleted in M yomin - not in H or M ' . ./test-lib.sh @@ -60,336 +60,343 @@ EOF sed -e 's/bozbar/gnusto (earlier bozbar)/' bozbar-old >bozbar-new -test_expect_success \ - setup \ - 'echo frotz >frotz && - echo nitfol >nitfol && - cat bozbar-old >bozbar && - echo rezrov >rezrov && - echo yomin >yomin && - git update-index --add nitfol bozbar rezrov && - treeH=$(git write-tree) && - echo treeH $treeH && - git ls-tree $treeH && - - cat bozbar-new >bozbar && - git update-index --add frotz bozbar --force-remove rezrov && - git ls-files --stage >M.out && - treeM=$(git write-tree) && - echo treeM $treeM && - git ls-tree $treeM && - git diff-tree $treeH $treeM' - -test_expect_success \ - '1, 2, 3 - no carry forward' \ - 'rm -f .git/index && - read_tree_twoway $treeH $treeM && - git ls-files --stage >1-3.out && - test_cmp M.out 1-3.out && - check_cache_at bozbar dirty && - check_cache_at frotz dirty && - check_cache_at nitfol dirty' +test_expect_success 'setup' ' + echo frotz >frotz && + echo nitfol >nitfol && + cat bozbar-old >bozbar && + echo rezrov >rezrov && + echo yomin >yomin && + git update-index --add nitfol bozbar rezrov && + treeH=$(git write-tree) && + echo treeH $treeH && + git ls-tree $treeH && + + cat bozbar-new >bozbar && + git update-index --add frotz bozbar --force-remove rezrov && + git ls-files --stage >M.out && + treeM=$(git write-tree) && + echo treeM $treeM && + git ls-tree $treeM && + git diff-tree $treeH $treeM +' +test_expect_success '1, 2, 3 - no carry forward' ' + rm -f .git/index && + read_tree_twoway $treeH $treeM && + git ls-files --stage >1-3.out && + test_cmp M.out 1-3.out && + check_cache_at bozbar dirty && + check_cache_at frotz dirty && + check_cache_at nitfol dirty +' echo '+100644 X 0 yomin' >expected -test_expect_success \ - '4 - carry forward local addition.' \ - 'rm -f .git/index && - read_tree_must_succeed $treeH && - git checkout-index -u -f -q -a && - git update-index --add yomin && - read_tree_twoway $treeH $treeM && - git ls-files --stage >4.out && - test_must_fail git diff --no-index M.out 4.out >4diff.out && - compare_change 4diff.out expected && - check_cache_at yomin clean' - -test_expect_success \ - '5 - carry forward local addition.' \ - 'rm -f .git/index && - read_tree_must_succeed $treeH && - git checkout-index -u -f -q -a && - echo yomin >yomin && - git update-index --add yomin && - echo yomin yomin >yomin && - read_tree_twoway $treeH $treeM && - git ls-files --stage >5.out && - test_must_fail git diff --no-index M.out 5.out >5diff.out && - compare_change 5diff.out expected && - check_cache_at yomin dirty' - -test_expect_success \ - '6 - local addition already has the same.' \ - 'rm -f .git/index && - read_tree_must_succeed $treeH && - git checkout-index -u -f -q -a && - git update-index --add frotz && - read_tree_twoway $treeH $treeM && - git ls-files --stage >6.out && - test_cmp M.out 6.out && - check_cache_at frotz clean' - -test_expect_success \ - '7 - local addition already has the same.' \ - 'rm -f .git/index && - read_tree_must_succeed $treeH && - git checkout-index -u -f -q -a && - echo frotz >frotz && - git update-index --add frotz && - echo frotz frotz >frotz && - read_tree_twoway $treeH $treeM && - git ls-files --stage >7.out && - test_cmp M.out 7.out && - check_cache_at frotz dirty' - -test_expect_success \ - '8 - conflicting addition.' \ - 'rm -f .git/index && - read_tree_must_succeed $treeH && - git checkout-index -u -f -q -a && - echo frotz frotz >frotz && - git update-index --add frotz && - if read_tree_twoway $treeH $treeM; then false; else :; fi' - -test_expect_success \ - '9 - conflicting addition.' \ - 'rm -f .git/index && - read_tree_must_succeed $treeH && - git checkout-index -u -f -q -a && - echo frotz frotz >frotz && - git update-index --add frotz && - echo frotz >frotz && - if read_tree_twoway $treeH $treeM; then false; else :; fi' - -test_expect_success \ - '10 - path removed.' \ - 'rm -f .git/index && - read_tree_must_succeed $treeH && - git checkout-index -u -f -q -a && - echo rezrov >rezrov && - git update-index --add rezrov && - read_tree_twoway $treeH $treeM && - git ls-files --stage >10.out && - test_cmp M.out 10.out' - -test_expect_success \ - '11 - dirty path removed.' \ - 'rm -f .git/index && - read_tree_must_succeed $treeH && - git checkout-index -u -f -q -a && - echo rezrov >rezrov && - git update-index --add rezrov && - echo rezrov rezrov >rezrov && - if read_tree_twoway $treeH $treeM; then false; else :; fi' - -test_expect_success \ - '12 - unmatching local changes being removed.' \ - 'rm -f .git/index && - read_tree_must_succeed $treeH && - git checkout-index -u -f -q -a && - echo rezrov rezrov >rezrov && - git update-index --add rezrov && - if read_tree_twoway $treeH $treeM; then false; else :; fi' - -test_expect_success \ - '13 - unmatching local changes being removed.' \ - 'rm -f .git/index && - read_tree_must_succeed $treeH && - git checkout-index -u -f -q -a && - echo rezrov rezrov >rezrov && - git update-index --add rezrov && - echo rezrov >rezrov && - if read_tree_twoway $treeH $treeM; then false; else :; fi' +test_expect_success '4 - carry forward local addition.' ' + rm -f .git/index && + read_tree_must_succeed $treeH && + git checkout-index -u -f -q -a && + git update-index --add yomin && + read_tree_twoway $treeH $treeM && + git ls-files --stage >4.out && + test_must_fail git diff --no-index M.out 4.out >4diff.out && + compare_change 4diff.out expected && + check_cache_at yomin clean +' + +test_expect_success '5 - carry forward local addition.' ' + rm -f .git/index && + read_tree_must_succeed $treeH && + git checkout-index -u -f -q -a && + echo yomin >yomin && + git update-index --add yomin && + echo yomin yomin >yomin && + read_tree_twoway $treeH $treeM && + git ls-files --stage >5.out && + test_must_fail git diff --no-index M.out 5.out >5diff.out && + compare_change 5diff.out expected && + check_cache_at yomin dirty +' + +test_expect_success '6 - local addition already has the same.' ' + rm -f .git/index && + read_tree_must_succeed $treeH && + git checkout-index -u -f -q -a && + git update-index --add frotz && + read_tree_twoway $treeH $treeM && + git ls-files --stage >6.out && + test_cmp M.out 6.out && + check_cache_at frotz clean +' + +test_expect_success '7 - local addition already has the same.' ' + rm -f .git/index && + read_tree_must_succeed $treeH && + git checkout-index -u -f -q -a && + echo frotz >frotz && + git update-index --add frotz && + echo frotz frotz >frotz && + read_tree_twoway $treeH $treeM && + git ls-files --stage >7.out && + test_cmp M.out 7.out && + check_cache_at frotz dirty +' + +test_expect_success '8 - conflicting addition.' ' + rm -f .git/index && + read_tree_must_succeed $treeH && + git checkout-index -u -f -q -a && + echo frotz frotz >frotz && + git update-index --add frotz && + if read_tree_twoway $treeH $treeM; then false; else :; fi +' + +test_expect_success '9 - conflicting addition.' ' + rm -f .git/index && + read_tree_must_succeed $treeH && + git checkout-index -u -f -q -a && + echo frotz frotz >frotz && + git update-index --add frotz && + echo frotz >frotz && + if read_tree_twoway $treeH $treeM; then false; else :; fi +' + +test_expect_success '10 - path removed.' ' + rm -f .git/index && + read_tree_must_succeed $treeH && + git checkout-index -u -f -q -a && + echo rezrov >rezrov && + git update-index --add rezrov && + read_tree_twoway $treeH $treeM && + git ls-files --stage >10.out && + test_cmp M.out 10.out +' + +test_expect_success '11 - dirty path removed.' ' + rm -f .git/index && + read_tree_must_succeed $treeH && + git checkout-index -u -f -q -a && + echo rezrov >rezrov && + git update-index --add rezrov && + echo rezrov rezrov >rezrov && + if read_tree_twoway $treeH $treeM; then false; else :; fi +' + +test_expect_success '12 - unmatching local changes being removed.' ' + rm -f .git/index && + read_tree_must_succeed $treeH && + git checkout-index -u -f -q -a && + echo rezrov rezrov >rezrov && + git update-index --add rezrov && + if read_tree_twoway $treeH $treeM; then false; else :; fi +' + +test_expect_success '13 - unmatching local changes being removed.' ' + rm -f .git/index && + read_tree_must_succeed $treeH && + git checkout-index -u -f -q -a && + echo rezrov rezrov >rezrov && + git update-index --add rezrov && + echo rezrov >rezrov && + if read_tree_twoway $treeH $treeM; then false; else :; fi +' cat >expected <<EOF -100644 X 0 nitfol +100644 X 0 nitfol EOF -test_expect_success \ - '14 - unchanged in two heads.' \ - 'rm -f .git/index && - read_tree_must_succeed $treeH && - git checkout-index -u -f -q -a && - echo nitfol nitfol >nitfol && - git update-index --add nitfol && - read_tree_twoway $treeH $treeM && - git ls-files --stage >14.out && - test_must_fail git diff --no-index M.out 14.out >14diff.out && - compare_change 14diff.out expected && - check_cache_at nitfol clean' - -test_expect_success \ - '15 - unchanged in two heads.' \ - 'rm -f .git/index && - read_tree_must_succeed $treeH && - git checkout-index -u -f -q -a && - echo nitfol nitfol >nitfol && - git update-index --add nitfol && - echo nitfol nitfol nitfol >nitfol && - read_tree_twoway $treeH $treeM && - git ls-files --stage >15.out && - test_must_fail git diff --no-index M.out 15.out >15diff.out && - compare_change 15diff.out expected && - check_cache_at nitfol dirty' - -test_expect_success \ - '16 - conflicting local change.' \ - 'rm -f .git/index && - read_tree_must_succeed $treeH && - git checkout-index -u -f -q -a && - echo bozbar bozbar >bozbar && - git update-index --add bozbar && - if read_tree_twoway $treeH $treeM; then false; else :; fi' - -test_expect_success \ - '17 - conflicting local change.' \ - 'rm -f .git/index && - read_tree_must_succeed $treeH && - git checkout-index -u -f -q -a && - echo bozbar bozbar >bozbar && - git update-index --add bozbar && - echo bozbar bozbar bozbar >bozbar && - if read_tree_twoway $treeH $treeM; then false; else :; fi' - -test_expect_success \ - '18 - local change already having a good result.' \ - 'rm -f .git/index && - read_tree_must_succeed $treeH && - git checkout-index -u -f -q -a && - cat bozbar-new >bozbar && - git update-index --add bozbar && - read_tree_twoway $treeH $treeM && - git ls-files --stage >18.out && - test_cmp M.out 18.out && - check_cache_at bozbar clean' - -test_expect_success \ - '19 - local change already having a good result, further modified.' \ - 'rm -f .git/index && - read_tree_must_succeed $treeH && - git checkout-index -u -f -q -a && - cat bozbar-new >bozbar && - git update-index --add bozbar && - echo gnusto gnusto >bozbar && - read_tree_twoway $treeH $treeM && - git ls-files --stage >19.out && - test_cmp M.out 19.out && - check_cache_at bozbar dirty' - -test_expect_success \ - '20 - no local change, use new tree.' \ - 'rm -f .git/index && - read_tree_must_succeed $treeH && - git checkout-index -u -f -q -a && - cat bozbar-old >bozbar && - git update-index --add bozbar && - read_tree_twoway $treeH $treeM && - git ls-files --stage >20.out && - test_cmp M.out 20.out && - check_cache_at bozbar dirty' - -test_expect_success \ - '21 - no local change, dirty cache.' \ - 'rm -f .git/index && - read_tree_must_succeed $treeH && - git checkout-index -u -f -q -a && - cat bozbar-old >bozbar && - git update-index --add bozbar && - echo gnusto gnusto >bozbar && - if read_tree_twoway $treeH $treeM; then false; else :; fi' +test_expect_success '14 - unchanged in two heads.' ' + rm -f .git/index && + read_tree_must_succeed $treeH && + git checkout-index -u -f -q -a && + echo nitfol nitfol >nitfol && + git update-index --add nitfol && + read_tree_twoway $treeH $treeM && + git ls-files --stage >14.out && + test_must_fail git diff --no-index M.out 14.out >14diff.out && + compare_change 14diff.out expected && + check_cache_at nitfol clean +' + +test_expect_success '15 - unchanged in two heads.' ' + rm -f .git/index && + read_tree_must_succeed $treeH && + git checkout-index -u -f -q -a && + echo nitfol nitfol >nitfol && + git update-index --add nitfol && + echo nitfol nitfol nitfol >nitfol && + read_tree_twoway $treeH $treeM && + git ls-files --stage >15.out && + test_must_fail git diff --no-index M.out 15.out >15diff.out && + compare_change 15diff.out expected && + check_cache_at nitfol dirty +' + +test_expect_success '16 - conflicting local change.' ' + rm -f .git/index && + read_tree_must_succeed $treeH && + git checkout-index -u -f -q -a && + echo bozbar bozbar >bozbar && + git update-index --add bozbar && + if read_tree_twoway $treeH $treeM; then false; else :; fi +' + +test_expect_success '17 - conflicting local change.' ' + rm -f .git/index && + read_tree_must_succeed $treeH && + git checkout-index -u -f -q -a && + echo bozbar bozbar >bozbar && + git update-index --add bozbar && + echo bozbar bozbar bozbar >bozbar && + if read_tree_twoway $treeH $treeM; then false; else :; fi +' + +test_expect_success '18 - local change already having a good result.' ' + rm -f .git/index && + read_tree_must_succeed $treeH && + git checkout-index -u -f -q -a && + cat bozbar-new >bozbar && + git update-index --add bozbar && + read_tree_twoway $treeH $treeM && + git ls-files --stage >18.out && + test_cmp M.out 18.out && + check_cache_at bozbar clean +' + +test_expect_success '19 - local change already having a good result, further modified.' ' + rm -f .git/index && + read_tree_must_succeed $treeH && + git checkout-index -u -f -q -a && + cat bozbar-new >bozbar && + git update-index --add bozbar && + echo gnusto gnusto >bozbar && + read_tree_twoway $treeH $treeM && + git ls-files --stage >19.out && + test_cmp M.out 19.out && + check_cache_at bozbar dirty +' + +test_expect_success '20 - no local change, use new tree.' ' + rm -f .git/index && + read_tree_must_succeed $treeH && + git checkout-index -u -f -q -a && + cat bozbar-old >bozbar && + git update-index --add bozbar && + read_tree_twoway $treeH $treeM && + git ls-files --stage >20.out && + test_cmp M.out 20.out && + check_cache_at bozbar dirty +' + +test_expect_success '21 - no local change, dirty cache.' ' + rm -f .git/index && + read_tree_must_succeed $treeH && + git checkout-index -u -f -q -a && + cat bozbar-old >bozbar && + git update-index --add bozbar && + echo gnusto gnusto >bozbar && + if read_tree_twoway $treeH $treeM; then false; else :; fi +' # This fails with straight two-way fast-forward. -test_expect_success \ - '22 - local change cache updated.' \ - 'rm -f .git/index && - read_tree_must_succeed $treeH && - git checkout-index -u -f -q -a && - sed -e "s/such as/SUCH AS/" bozbar-old >bozbar && - git update-index --add bozbar && - if read_tree_twoway $treeH $treeM; then false; else :; fi' +test_expect_success '22 - local change cache updated.' ' + rm -f .git/index && + read_tree_must_succeed $treeH && + git checkout-index -u -f -q -a && + sed -e "s/such as/SUCH AS/" bozbar-old >bozbar && + git update-index --add bozbar && + if read_tree_twoway $treeH $treeM; then false; else :; fi +' # Also make sure we did not break DF vs DF/DF case. -test_expect_success \ - 'DF vs DF/DF case setup.' \ - 'rm -f .git/index && - echo DF >DF && - git update-index --add DF && - treeDF=$(git write-tree) && - echo treeDF $treeDF && - git ls-tree $treeDF && - - rm -f DF && - mkdir DF && - echo DF/DF >DF/DF && - git update-index --add --remove DF DF/DF && - treeDFDF=$(git write-tree) && - echo treeDFDF $treeDFDF && - git ls-tree $treeDFDF && - git ls-files --stage >DFDF.out' - -test_expect_success \ - 'DF vs DF/DF case test.' \ - 'rm -f .git/index && - rm -fr DF && - echo DF >DF && - git update-index --add DF && - read_tree_twoway $treeDF $treeDFDF && - git ls-files --stage >DFDFcheck.out && - test_cmp DFDF.out DFDFcheck.out && - check_cache_at DF/DF dirty && - :' - -test_expect_success \ - 'a/b (untracked) vs a case setup.' \ - 'rm -f .git/index && - : >a && - git update-index --add a && - treeM=$(git write-tree) && - echo treeM $treeM && - git ls-tree $treeM && - git ls-files --stage >treeM.out && - - rm -f a && - git update-index --remove a && - mkdir a && - : >a/b && - treeH=$(git write-tree) && - echo treeH $treeH && - git ls-tree $treeH' - -test_expect_success \ - 'a/b (untracked) vs a, plus c/d case test.' \ - 'read_tree_u_must_fail -u -m "$treeH" "$treeM" && - git ls-files --stage && - test -f a/b' - -test_expect_success \ - 'a/b vs a, plus c/d case setup.' \ - 'rm -f .git/index && - rm -fr a && - : >a && - mkdir c && - : >c/d && - git update-index --add a c/d && - treeM=$(git write-tree) && - echo treeM $treeM && - git ls-tree $treeM && - git ls-files --stage >treeM.out && - - rm -f a && - mkdir a && - : >a/b && - git update-index --add --remove a a/b && - treeH=$(git write-tree) && - echo treeH $treeH && - git ls-tree $treeH' - -test_expect_success \ - 'a/b vs a, plus c/d case test.' \ - 'read_tree_u_must_succeed -u -m "$treeH" "$treeM" && - git ls-files --stage | tee >treeMcheck.out && - test_cmp treeM.out treeMcheck.out' +test_expect_success 'DF vs DF/DF case setup.' ' + rm -f .git/index && + echo DF >DF && + git update-index --add DF && + treeDF=$(git write-tree) && + echo treeDF $treeDF && + git ls-tree $treeDF && + + rm -f DF && + mkdir DF && + echo DF/DF >DF/DF && + git update-index --add --remove DF DF/DF && + treeDFDF=$(git write-tree) && + echo treeDFDF $treeDFDF && + git ls-tree $treeDFDF && + git ls-files --stage >DFDF.out +' + +test_expect_success 'DF vs DF/DF case test.' ' + rm -f .git/index && + rm -fr DF && + echo DF >DF && + git update-index --add DF && + read_tree_twoway $treeDF $treeDFDF && + git ls-files --stage >DFDFcheck.out && + test_cmp DFDF.out DFDFcheck.out && + check_cache_at DF/DF dirty && + : +' + +test_expect_success 'a/b (untracked) vs a case setup.' ' + rm -f .git/index && + : >a && + git update-index --add a && + treeM=$(git write-tree) && + echo treeM $treeM && + git ls-tree $treeM && + git ls-files --stage >treeM.out && + + rm -f a && + git update-index --remove a && + mkdir a && + : >a/b && + treeH=$(git write-tree) && + echo treeH $treeH && + git ls-tree $treeH +' + +test_expect_success 'a/b (untracked) vs a, plus c/d case test.' ' + read_tree_u_must_fail -u -m "$treeH" "$treeM" && + git ls-files --stage && + test -f a/b +' + +test_expect_success 'read-tree supports the super-prefix' ' + cat <<-EOF >expect && + error: Updating '\''fictional/a'\'' would lose untracked files in it + EOF + test_must_fail git --super-prefix fictional/ read-tree -u -m "$treeH" "$treeM" 2>actual && + test_cmp expect actual +' + +test_expect_success 'a/b vs a, plus c/d case setup.' ' + rm -f .git/index && + rm -fr a && + : >a && + mkdir c && + : >c/d && + git update-index --add a c/d && + treeM=$(git write-tree) && + echo treeM $treeM && + git ls-tree $treeM && + git ls-files --stage >treeM.out && + + rm -f a && + mkdir a && + : >a/b && + git update-index --add --remove a a/b && + treeH=$(git write-tree) && + echo treeH $treeH && + git ls-tree $treeH +' + +test_expect_success 'a/b vs a, plus c/d case test.' ' + read_tree_u_must_succeed -u -m "$treeH" "$treeM" && + git ls-files --stage | tee >treeMcheck.out && + test_cmp treeM.out treeMcheck.out +' test_expect_success '-m references the correct modified tree' ' echo >file-a && diff --git a/t/t1403-show-ref.sh b/t/t1403-show-ref.sh index 7e10bcfe39..30354fd26c 100755 --- a/t/t1403-show-ref.sh +++ b/t/t1403-show-ref.sh @@ -97,6 +97,9 @@ test_expect_success 'show-ref -d' ' git show-ref -d refs/tags/A refs/tags/C >actual && test_cmp expect actual && + git show-ref --verify -d refs/tags/A refs/tags/C >actual && + test_cmp expect actual && + echo $(git rev-parse refs/heads/master) refs/heads/master >expect && git show-ref -d master >actual && test_cmp expect actual && @@ -116,6 +119,12 @@ test_expect_success 'show-ref -d' ' test_cmp expect actual && test_must_fail git show-ref -d --verify heads/master >actual && + test_cmp expect actual && + + test_must_fail git show-ref --verify -d A C >actual && + test_cmp expect actual && + + test_must_fail git show-ref --verify -d tags/A tags/C >actual && test_cmp expect actual ' @@ -164,4 +173,37 @@ test_expect_success 'show-ref --heads, --tags, --head, pattern' ' test_cmp expect actual ' +test_expect_success 'show-ref --verify HEAD' ' + echo $(git rev-parse HEAD) HEAD >expect && + git show-ref --verify HEAD >actual && + test_cmp expect actual && + + >expect && + + git show-ref --verify -q HEAD >actual && + test_cmp expect actual +' + +test_expect_success 'show-ref --verify with dangling ref' ' + sha1_file() { + echo "$*" | sed "s#..#.git/objects/&/#" + } && + + remove_object() { + file=$(sha1_file "$*") && + test -e "$file" && + rm -f "$file" + } && + + test_when_finished "rm -rf dangling" && + ( + git init dangling && + cd dangling && + test_commit dangling && + sha=$(git rev-parse refs/tags/dangling) && + remove_object $sha && + test_must_fail git show-ref --verify refs/tags/dangling + ) +' + test_done diff --git a/t/t1450-fsck.sh b/t/t1450-fsck.sh index ee7d4736db..33a51c9a67 100755 --- a/t/t1450-fsck.sh +++ b/t/t1450-fsck.sh @@ -43,13 +43,13 @@ test_expect_success 'HEAD is part of refs, valid objects appear valid' ' test_expect_success 'setup: helpers for corruption tests' ' sha1_file() { - echo "$*" | sed "s#..#.git/objects/&/#" + remainder=${1#??} && + firsttwo=${1%$remainder} && + echo ".git/objects/$firsttwo/$remainder" } && remove_object() { - file=$(sha1_file "$*") && - test -e "$file" && - rm -f "$file" + rm "$(sha1_file "$1")" } ' @@ -189,14 +189,16 @@ test_expect_success 'commit with NUL in header' ' ' test_expect_success 'tree object with duplicate entries' ' - test_when_finished "remove_object \$T" && + test_when_finished "for i in \$T; do remove_object \$i; done" && T=$( GIT_INDEX_FILE=test-index && export GIT_INDEX_FILE && rm -f test-index && >x && git add x && + git rev-parse :x && T=$(git write-tree) && + echo $T && ( git cat-file tree $T && git cat-file tree $T @@ -521,9 +523,21 @@ test_expect_success 'fsck --connectivity-only' ' touch empty && git add empty && test_commit empty && + + # Drop the index now; we want to be sure that we + # recursively notice the broken objects + # because they are reachable from refs, not because + # they are in the index. + rm -f .git/index && + + # corrupt the blob, but in a way that we can still identify + # its type. That lets us see that --connectivity-only is + # not actually looking at the contents, but leaves it + # free to examine the type if it chooses. empty=.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 && - rm -f $empty && - echo invalid >$empty && + blob=$(echo unrelated | git hash-object -w --stdin) && + mv -f $(sha1_file $blob) $empty && + test_must_fail git fsck --strict && git fsck --strict --connectivity-only && tree=$(git rev-parse HEAD:) && @@ -535,12 +549,18 @@ test_expect_success 'fsck --connectivity-only' ' ) ' -remove_loose_object () { - sha1="$(git rev-parse "$1")" && - remainder=${sha1#??} && - firsttwo=${sha1%$remainder} && - rm .git/objects/$firsttwo/$remainder -} +test_expect_success 'fsck --connectivity-only with explicit head' ' + rm -rf connectivity-only && + git init connectivity-only && + ( + cd connectivity-only && + test_commit foo && + rm -f .git/index && + tree=$(git rev-parse HEAD^{tree}) && + remove_object $(git rev-parse HEAD:foo.t) && + test_must_fail git fsck --connectivity-only $tree + ) +' test_expect_success 'fsck --name-objects' ' rm -rf name-objects && @@ -550,11 +570,123 @@ test_expect_success 'fsck --name-objects' ' test_commit julius caesar.t && test_commit augustus && test_commit caesar && - remove_loose_object $(git rev-parse julius:caesar.t) && + remove_object $(git rev-parse julius:caesar.t) && test_must_fail git fsck --name-objects >out && tree=$(git rev-parse --verify julius:) && grep "$tree (\(refs/heads/master\|HEAD\)@{[0-9]*}:" out ) ' +test_expect_success 'alternate objects are correctly blamed' ' + test_when_finished "rm -rf alt.git .git/objects/info/alternates" && + git init --bare alt.git && + echo "../../alt.git/objects" >.git/objects/info/alternates && + mkdir alt.git/objects/12 && + >alt.git/objects/12/34567890123456789012345678901234567890 && + test_must_fail git fsck >out 2>&1 && + grep alt.git out +' + +test_expect_success 'fsck errors in packed objects' ' + git cat-file commit HEAD >basis && + sed "s/</one/" basis >one && + sed "s/</foo/" basis >two && + one=$(git hash-object -t commit -w one) && + two=$(git hash-object -t commit -w two) && + pack=$( + { + echo $one && + echo $two + } | git pack-objects .git/objects/pack/pack + ) && + test_when_finished "rm -f .git/objects/pack/pack-$pack.*" && + remove_object $one && + remove_object $two && + test_must_fail git fsck 2>out && + grep "error in commit $one.* - bad name" out && + grep "error in commit $two.* - bad name" out && + ! grep corrupt out +' + +test_expect_success 'fsck finds problems in duplicate loose objects' ' + rm -rf broken-duplicate && + git init broken-duplicate && + ( + cd broken-duplicate && + test_commit duplicate && + # no "-d" here, so we end up with duplicates + git repack && + # now corrupt the loose copy + file=$(sha1_file "$(git rev-parse HEAD)") && + rm "$file" && + echo broken >"$file" && + test_must_fail git fsck + ) +' + +test_expect_success 'fsck detects trailing loose garbage (commit)' ' + git cat-file commit HEAD >basis && + echo bump-commit-sha1 >>basis && + commit=$(git hash-object -w -t commit basis) && + file=$(sha1_file $commit) && + test_when_finished "remove_object $commit" && + chmod +w "$file" && + echo garbage >>"$file" && + test_must_fail git fsck 2>out && + test_i18ngrep "garbage.*$commit" out +' + +test_expect_success 'fsck detects trailing loose garbage (blob)' ' + blob=$(echo trailing | git hash-object -w --stdin) && + file=$(sha1_file $blob) && + test_when_finished "remove_object $blob" && + chmod +w "$file" && + echo garbage >>"$file" && + test_must_fail git fsck 2>out && + test_i18ngrep "garbage.*$blob" out +' + +# for each of type, we have one version which is referenced by another object +# (and so while unreachable, not dangling), and another variant which really is +# dangling. +test_expect_success 'fsck notices dangling objects' ' + git init dangling && + ( + cd dangling && + blob=$(echo not-dangling | git hash-object -w --stdin) && + dblob=$(echo dangling | git hash-object -w --stdin) && + tree=$(printf "100644 blob %s\t%s\n" $blob one | git mktree) && + dtree=$(printf "100644 blob %s\t%s\n" $blob two | git mktree) && + commit=$(git commit-tree $tree) && + dcommit=$(git commit-tree -p $commit $tree) && + + cat >expect <<-EOF && + dangling blob $dblob + dangling commit $dcommit + dangling tree $dtree + EOF + + git fsck >actual && + # the output order is non-deterministic, as it comes from a hash + sort <actual >actual.sorted && + test_cmp expect actual.sorted + ) +' + +test_expect_success 'fsck $name notices bogus $name' ' + test_must_fail git fsck bogus && + test_must_fail git fsck $_z40 +' + +test_expect_success 'bogus head does not fallback to all heads' ' + # set up a case that will cause a reachability complaint + echo to-be-deleted >foo && + git add foo && + blob=$(git rev-parse :foo) && + test_when_finished "git rm --cached foo" && + remove_object $blob && + test_must_fail git fsck $_z40 >out 2>&1 && + ! grep $blob out +' + test_done diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index c896a4c106..e2f18d11f6 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -237,6 +237,22 @@ test_expect_success 'retain authorship' ' git show HEAD | grep "^Author: Twerp Snog" ' +test_expect_success 'retain authorship w/ conflicts' ' + git reset --hard twerp && + test_commit a conflict a conflict-a && + git reset --hard twerp && + GIT_AUTHOR_NAME=AttributeMe \ + test_commit b conflict b conflict-b && + set_fake_editor && + test_must_fail git rebase -i conflict-a && + echo resolved >conflict && + git add conflict && + git rebase --continue && + test $(git rev-parse conflict-a^0) = $(git rev-parse HEAD^) && + git show >out && + grep AttributeMe out +' + test_expect_success 'squash' ' git reset --hard twerp && echo B > file7 && diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh index deae948c76..5ffe78e920 100755 --- a/t/t3701-add-interactive.sh +++ b/t/t3701-add-interactive.sh @@ -380,4 +380,18 @@ test_expect_success 'patch mode ignores unmerged entries' ' test_cmp expected diff ' +test_expect_success 'diffs can be colorized' ' + git reset --hard && + + # force color even though the test script has no terminal + test_config color.ui always && + + echo content >test && + printf y | git add -p >output 2>&1 && + + # We do not want to depend on the exact coloring scheme + # git uses for diffs, so just check that we saw some kind of color. + grep "$(printf "\\033")" output +' + test_done diff --git a/t/t4026-color.sh b/t/t4026-color.sh index ec78c5e3ac..671e951ee5 100755 --- a/t/t4026-color.sh +++ b/t/t4026-color.sh @@ -6,10 +6,11 @@ test_description='Test diff/status color escape codes' . ./test-lib.sh +ESC=$(printf '\033') color() { actual=$(git config --get-color no.such.slot "$1") && - test "$actual" = "$2" + test "$actual" = "${2:+$ESC}$2" } invalid_color() @@ -21,6 +22,10 @@ test_expect_success 'reset' ' color "reset" "[m" ' +test_expect_success 'empty color is empty' ' + color "" "" +' + test_expect_success 'attribute before color name' ' color "bold red" "[1;31m" ' diff --git a/t/t4202-log.sh b/t/t4202-log.sh index 1ccbd5948a..08ea725de3 100755 --- a/t/t4202-log.sh +++ b/t/t4202-log.sh @@ -359,6 +359,28 @@ test_expect_success 'log --graph --line-prefix="| | | " with merge' ' test_cmp expect actual ' +cat > expect.colors <<\EOF +* Merge branch 'side' +<BLUE>|<RESET><CYAN>\<RESET> +<BLUE>|<RESET> * side-2 +<BLUE>|<RESET> * side-1 +* <CYAN>|<RESET> Second +* <CYAN>|<RESET> sixth +* <CYAN>|<RESET> fifth +* <CYAN>|<RESET> fourth +<CYAN>|<RESET><CYAN>/<RESET> +* third +* second +* initial +EOF + +test_expect_success 'log --graph with merge with log.graphColors' ' + test_config log.graphColors " blue,invalid-color, cyan, red , " && + git log --color=always --graph --date-order --pretty=tformat:%s | + test_decode_color | sed "s/ *\$//" >actual && + test_cmp expect.colors actual +' + test_expect_success 'log --raw --graph -m with merge' ' git log --raw --graph --oneline -m master | head -n 500 >actual && grep "initial" actual diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh index 8198d8eb05..ba46e86de0 100755 --- a/t/t5505-remote.sh +++ b/t/t5505-remote.sh @@ -764,6 +764,13 @@ test_expect_success 'rename a remote with name prefix of other remote' ' ) ' +test_expect_success 'rename succeeds with existing remote.<target>.prune' ' + git clone one four.four && + test_when_finished git config --global --unset remote.upstream.prune && + git config --global remote.upstream.prune true && + git -C four.four remote rename origin upstream +' + cat >remotes_origin <<EOF URL: $(pwd)/one Push: refs/heads/master:refs/heads/upstream diff --git a/t/t5531-deep-submodule-push.sh b/t/t5531-deep-submodule-push.sh index 1524ff5ba6..f55137f76f 100755 --- a/t/t5531-deep-submodule-push.sh +++ b/t/t5531-deep-submodule-push.sh @@ -454,4 +454,25 @@ test_expect_success 'push --dry-run does not recursively update submodules' ' test_cmp expected_submodule actual_submodule ' +test_expect_success 'push --dry-run does not recursively update submodules' ' + git -C work push --dry-run --recurse-submodules=only ../pub.git master && + + git -C submodule.git rev-parse master >actual_submodule && + git -C pub.git rev-parse master >actual_pub && + test_cmp expected_pub actual_pub && + test_cmp expected_submodule actual_submodule +' + +test_expect_success 'push only unpushed submodules recursively' ' + git -C work/gar/bage rev-parse master >expected_submodule && + git -C pub.git rev-parse master >expected_pub && + + git -C work push --recurse-submodules=only ../pub.git master && + + git -C submodule.git rev-parse master >actual_submodule && + git -C pub.git rev-parse master >actual_pub && + test_cmp expected_submodule actual_submodule && + test_cmp expected_pub actual_pub +' + test_done diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh index 1cfa8a21d2..b676b90c7d 100755 --- a/t/t7004-tag.sh +++ b/t/t7004-tag.sh @@ -871,6 +871,22 @@ test_expect_success GPG 'verifying a forged tag should fail' ' test_must_fail git tag -v forged-tag ' +test_expect_success 'verifying a proper tag with --format pass and format accordingly' ' + cat >expect <<-\EOF + tagname : signed-tag + EOF && + git tag -v --format="tagname : %(tag)" "signed-tag" >actual && + test_cmp expect actual +' + +test_expect_success 'verifying a forged tag with --format fail and format accordingly' ' + cat >expect <<-\EOF + tagname : forged-tag + EOF && + test_must_fail git tag -v --format="tagname : %(tag)" "forged-tag" >actual && + test_cmp expect actual +' + # blank and empty messages for signed tags: get_tag_header empty-signed-tag $commit commit $time >expect diff --git a/t/t7030-verify-tag.sh b/t/t7030-verify-tag.sh index 07079a41c4..d62ccbb98e 100755 --- a/t/t7030-verify-tag.sh +++ b/t/t7030-verify-tag.sh @@ -125,4 +125,20 @@ test_expect_success GPG 'verify multiple tags' ' test_cmp expect.stderr actual.stderr ' +test_expect_success 'verifying tag with --format' ' + cat >expect <<-\EOF + tagname : fourth-signed + EOF && + git verify-tag --format="tagname : %(tag)" "fourth-signed" >actual && + test_cmp expect actual +' + +test_expect_success 'verifying a forged tag with --format fail and format accordingly' ' + cat >expect <<-\EOF + tagname : 7th forged-signed + EOF && + test_must_fail git verify-tag --format="tagname : %(tag)" $(cat forged1.tag) >actual-forged && + test_cmp expect actual-forged +' + test_done diff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh index b77cce8e40..c09ce0d4c1 100755 --- a/t/t7400-submodule-basic.sh +++ b/t/t7400-submodule-basic.sh @@ -152,6 +152,20 @@ test_expect_success 'submodule add to .gitignored path with --force' ' ) ' +test_expect_success 'submodule add to reconfigure existing submodule with --force' ' + ( + cd addtest-ignore && + git submodule add --force bogus-url submod && + git submodule add -b initial "$submodurl" submod-branch && + test "bogus-url" = "$(git config -f .gitmodules submodule.submod.url)" && + test "bogus-url" = "$(git config submodule.submod.url)" && + # Restore the url + git submodule add --force "$submodurl" submod + test "$submodurl" = "$(git config -f .gitmodules submodule.submod.url)" && + test "$submodurl" = "$(git config submodule.submod.url)" + ) +' + test_expect_success 'submodule add --branch' ' echo "refs/heads/initial" >expect-head && cat <<-\EOF >expect-heads && diff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh index 725bbed1f8..347857fa7c 100755 --- a/t/t7406-submodule-update.sh +++ b/t/t7406-submodule-update.sh @@ -441,6 +441,16 @@ test_expect_success 'submodule update - command in .git/config catches failure - test_i18ncmp actual expect ' +test_expect_success 'submodule update - command run for initial population of submodule' ' + cat <<-\ EOF >expect + Execution of '\''false $submodulesha1'\'' failed in submodule path '\''submodule'\'' + EOF && + rm -rf super/submodule && + test_must_fail git -C super submodule update >../actual && + test_cmp expect actual && + git -C super submodule update --checkout +' + cat << EOF >expect Execution of 'false $submodulesha1' failed in submodule path '../super/submodule' Failed to recurse into submodule path '../super' @@ -493,6 +503,7 @@ test_expect_success 'submodule init picks up merge' ' ' test_expect_success 'submodule update --merge - ignores --merge for new submodules' ' + test_config -C super submodule.submodule.update checkout && (cd super && rm -rf submodule && git submodule update submodule && @@ -505,6 +516,7 @@ test_expect_success 'submodule update --merge - ignores --merge for new submod ' test_expect_success 'submodule update --rebase - ignores --rebase for new submodules' ' + test_config -C super submodule.submodule.update checkout && (cd super && rm -rf submodule && git submodule update submodule && diff --git a/t/t7412-submodule-absorbgitdirs.sh b/t/t7412-submodule-absorbgitdirs.sh index 1c47780e2b..e2bbb449b6 100755 --- a/t/t7412-submodule-absorbgitdirs.sh +++ b/t/t7412-submodule-absorbgitdirs.sh @@ -64,6 +64,33 @@ test_expect_success 'absorb the git dir in a nested submodule' ' test_cmp expect.2 actual.2 ' +test_expect_success 're-setup nested submodule' ' + # un-absorb the direct submodule, to test if the nested submodule + # is still correct (needs a rewrite of the gitfile only) + rm -rf sub1/.git && + mv .git/modules/sub1 sub1/.git && + GIT_WORK_TREE=. git -C sub1 config --unset core.worktree && + # fixup the nested submodule + echo "gitdir: ../.git/modules/nested" >sub1/nested/.git && + GIT_WORK_TREE=../../../nested git -C sub1/.git/modules/nested config \ + core.worktree "../../../nested" && + # make sure this re-setup is correct + git status --ignore-submodules=none +' + +test_expect_success 'absorb the git dir in a nested submodule' ' + git status >expect.1 && + git -C sub1/nested rev-parse HEAD >expect.2 && + git submodule absorbgitdirs && + test -f sub1/.git && + test -f sub1/nested/.git && + test -d .git/modules/sub1/modules/nested && + git status >actual.1 && + git -C sub1/nested rev-parse HEAD >actual.2 && + test_cmp expect.1 actual.1 && + test_cmp expect.2 actual.2 +' + test_expect_success 'setup a gitlink with missing .gitmodules entry' ' git init sub2 && test_commit -C sub2 first && diff --git a/t/t7512-status-help.sh b/t/t7512-status-help.sh index 5c3db656df..458608cc1e 100755 --- a/t/t7512-status-help.sh +++ b/t/t7512-status-help.sh @@ -944,4 +944,23 @@ EOF test_i18ncmp expected actual ' +test_expect_success 'status: handle not-yet-started rebase -i gracefully' ' + ONTO=$(git rev-parse --short HEAD^) && + COMMIT=$(git rev-parse --short HEAD) && + EDITOR="git status --untracked-files=no >actual" git rebase -i HEAD^ && + cat >expected <<EOF && +On branch several_commits +No commands done. +Next command to do (1 remaining command): + pick $COMMIT four_commit + (use "git rebase --edit-todo" to view and edit) +You are currently editing a commit while rebasing branch '\''several_commits'\'' on '\''$ONTO'\''. + (use "git commit --amend" to amend the current commit) + (use "git rebase --continue" once you are satisfied with your changes) + +nothing to commit (use -u to show untracked files) +EOF + test_i18ncmp expected actual +' + test_done diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh index 99d4123461..aa0ef02597 100755 --- a/t/t7800-difftool.sh +++ b/t/t7800-difftool.sh @@ -24,7 +24,7 @@ prompt_given () } # Create a file on master and change it on branch -test_expect_success PERL 'setup' ' +test_expect_success 'setup' ' echo master >file && git add file && git commit -m "added file" && @@ -36,7 +36,7 @@ test_expect_success PERL 'setup' ' ' # Configure a custom difftool.<tool>.cmd and use it -test_expect_success PERL 'custom commands' ' +test_expect_success 'custom commands' ' difftool_test_setup && test_config difftool.test-tool.cmd "cat \"\$REMOTE\"" && echo master >expect && @@ -49,21 +49,21 @@ test_expect_success PERL 'custom commands' ' test_cmp expect actual ' -test_expect_success PERL 'custom tool commands override built-ins' ' +test_expect_success 'custom tool commands override built-ins' ' test_config difftool.vimdiff.cmd "cat \"\$REMOTE\"" && echo master >expect && git difftool --tool vimdiff --no-prompt branch >actual && test_cmp expect actual ' -test_expect_success PERL 'difftool ignores bad --tool values' ' +test_expect_success 'difftool ignores bad --tool values' ' : >expect && test_must_fail \ git difftool --no-prompt --tool=bad-tool branch >actual && test_cmp expect actual ' -test_expect_success PERL 'difftool forwards arguments to diff' ' +test_expect_success 'difftool forwards arguments to diff' ' difftool_test_setup && >for-diff && git add for-diff && @@ -76,40 +76,40 @@ test_expect_success PERL 'difftool forwards arguments to diff' ' rm for-diff ' -test_expect_success PERL 'difftool ignores exit code' ' +test_expect_success 'difftool ignores exit code' ' test_config difftool.error.cmd false && git difftool -y -t error branch ' -test_expect_success PERL 'difftool forwards exit code with --trust-exit-code' ' +test_expect_success 'difftool forwards exit code with --trust-exit-code' ' test_config difftool.error.cmd false && test_must_fail git difftool -y --trust-exit-code -t error branch ' -test_expect_success PERL 'difftool forwards exit code with --trust-exit-code for built-ins' ' +test_expect_success 'difftool forwards exit code with --trust-exit-code for built-ins' ' test_config difftool.vimdiff.path false && test_must_fail git difftool -y --trust-exit-code -t vimdiff branch ' -test_expect_success PERL 'difftool honors difftool.trustExitCode = true' ' +test_expect_success 'difftool honors difftool.trustExitCode = true' ' test_config difftool.error.cmd false && test_config difftool.trustExitCode true && test_must_fail git difftool -y -t error branch ' -test_expect_success PERL 'difftool honors difftool.trustExitCode = false' ' +test_expect_success 'difftool honors difftool.trustExitCode = false' ' test_config difftool.error.cmd false && test_config difftool.trustExitCode false && git difftool -y -t error branch ' -test_expect_success PERL 'difftool ignores exit code with --no-trust-exit-code' ' +test_expect_success 'difftool ignores exit code with --no-trust-exit-code' ' test_config difftool.error.cmd false && test_config difftool.trustExitCode true && git difftool -y --no-trust-exit-code -t error branch ' -test_expect_success PERL 'difftool stops on error with --trust-exit-code' ' +test_expect_success 'difftool stops on error with --trust-exit-code' ' test_when_finished "rm -f for-diff .git/fail-right-file" && test_when_finished "git reset -- for-diff" && write_script .git/fail-right-file <<-\EOF && @@ -124,13 +124,13 @@ test_expect_success PERL 'difftool stops on error with --trust-exit-code' ' test_cmp expect actual ' -test_expect_success PERL 'difftool honors exit status if command not found' ' +test_expect_success 'difftool honors exit status if command not found' ' test_config difftool.nonexistent.cmd i-dont-exist && test_config difftool.trustExitCode false && test_must_fail git difftool -y -t nonexistent branch ' -test_expect_success PERL 'difftool honors --gui' ' +test_expect_success 'difftool honors --gui' ' difftool_test_setup && test_config merge.tool bogus-tool && test_config diff.tool bogus-tool && @@ -141,7 +141,7 @@ test_expect_success PERL 'difftool honors --gui' ' test_cmp expect actual ' -test_expect_success PERL 'difftool --gui last setting wins' ' +test_expect_success 'difftool --gui last setting wins' ' difftool_test_setup && : >expect && git difftool --no-prompt --gui --no-gui >actual && @@ -155,7 +155,7 @@ test_expect_success PERL 'difftool --gui last setting wins' ' test_cmp expect actual ' -test_expect_success PERL 'difftool --gui works without configured diff.guitool' ' +test_expect_success 'difftool --gui works without configured diff.guitool' ' difftool_test_setup && echo branch >expect && git difftool --no-prompt --gui branch >actual && @@ -163,7 +163,7 @@ test_expect_success PERL 'difftool --gui works without configured diff.guitool' ' # Specify the diff tool using $GIT_DIFF_TOOL -test_expect_success PERL 'GIT_DIFF_TOOL variable' ' +test_expect_success 'GIT_DIFF_TOOL variable' ' difftool_test_setup && git config --unset diff.tool && echo branch >expect && @@ -173,7 +173,7 @@ test_expect_success PERL 'GIT_DIFF_TOOL variable' ' # Test the $GIT_*_TOOL variables and ensure # that $GIT_DIFF_TOOL always wins unless --tool is specified -test_expect_success PERL 'GIT_DIFF_TOOL overrides' ' +test_expect_success 'GIT_DIFF_TOOL overrides' ' difftool_test_setup && test_config diff.tool bogus-tool && test_config merge.tool bogus-tool && @@ -191,7 +191,7 @@ test_expect_success PERL 'GIT_DIFF_TOOL overrides' ' # Test that we don't have to pass --no-prompt to difftool # when $GIT_DIFFTOOL_NO_PROMPT is true -test_expect_success PERL 'GIT_DIFFTOOL_NO_PROMPT variable' ' +test_expect_success 'GIT_DIFFTOOL_NO_PROMPT variable' ' difftool_test_setup && echo branch >expect && GIT_DIFFTOOL_NO_PROMPT=true git difftool branch >actual && @@ -200,7 +200,7 @@ test_expect_success PERL 'GIT_DIFFTOOL_NO_PROMPT variable' ' # git-difftool supports the difftool.prompt variable. # Test that GIT_DIFFTOOL_PROMPT can override difftool.prompt = false -test_expect_success PERL 'GIT_DIFFTOOL_PROMPT variable' ' +test_expect_success 'GIT_DIFFTOOL_PROMPT variable' ' difftool_test_setup && test_config difftool.prompt false && echo >input && @@ -210,7 +210,7 @@ test_expect_success PERL 'GIT_DIFFTOOL_PROMPT variable' ' ' # Test that we don't have to pass --no-prompt when difftool.prompt is false -test_expect_success PERL 'difftool.prompt config variable is false' ' +test_expect_success 'difftool.prompt config variable is false' ' difftool_test_setup && test_config difftool.prompt false && echo branch >expect && @@ -219,7 +219,7 @@ test_expect_success PERL 'difftool.prompt config variable is false' ' ' # Test that we don't have to pass --no-prompt when mergetool.prompt is false -test_expect_success PERL 'difftool merge.prompt = false' ' +test_expect_success 'difftool merge.prompt = false' ' difftool_test_setup && test_might_fail git config --unset difftool.prompt && test_config mergetool.prompt false && @@ -229,7 +229,7 @@ test_expect_success PERL 'difftool merge.prompt = false' ' ' # Test that the -y flag can override difftool.prompt = true -test_expect_success PERL 'difftool.prompt can overridden with -y' ' +test_expect_success 'difftool.prompt can overridden with -y' ' difftool_test_setup && test_config difftool.prompt true && echo branch >expect && @@ -238,7 +238,7 @@ test_expect_success PERL 'difftool.prompt can overridden with -y' ' ' # Test that the --prompt flag can override difftool.prompt = false -test_expect_success PERL 'difftool.prompt can overridden with --prompt' ' +test_expect_success 'difftool.prompt can overridden with --prompt' ' difftool_test_setup && test_config difftool.prompt false && echo >input && @@ -248,7 +248,7 @@ test_expect_success PERL 'difftool.prompt can overridden with --prompt' ' ' # Test that the last flag passed on the command-line wins -test_expect_success PERL 'difftool last flag wins' ' +test_expect_success 'difftool last flag wins' ' difftool_test_setup && echo branch >expect && git difftool --prompt --no-prompt branch >actual && @@ -261,7 +261,7 @@ test_expect_success PERL 'difftool last flag wins' ' # git-difftool falls back to git-mergetool config variables # so test that behavior here -test_expect_success PERL 'difftool + mergetool config variables' ' +test_expect_success 'difftool + mergetool config variables' ' test_config merge.tool test-tool && test_config mergetool.test-tool.cmd "cat \$LOCAL" && echo branch >expect && @@ -275,49 +275,49 @@ test_expect_success PERL 'difftool + mergetool config variables' ' test_cmp expect actual ' -test_expect_success PERL 'difftool.<tool>.path' ' +test_expect_success 'difftool.<tool>.path' ' test_config difftool.tkdiff.path echo && git difftool --tool=tkdiff --no-prompt branch >output && lines=$(grep file output | wc -l) && test "$lines" -eq 1 ' -test_expect_success PERL 'difftool --extcmd=cat' ' +test_expect_success 'difftool --extcmd=cat' ' echo branch >expect && echo master >>expect && git difftool --no-prompt --extcmd=cat branch >actual && test_cmp expect actual ' -test_expect_success PERL 'difftool --extcmd cat' ' +test_expect_success 'difftool --extcmd cat' ' echo branch >expect && echo master >>expect && git difftool --no-prompt --extcmd=cat branch >actual && test_cmp expect actual ' -test_expect_success PERL 'difftool -x cat' ' +test_expect_success 'difftool -x cat' ' echo branch >expect && echo master >>expect && git difftool --no-prompt -x cat branch >actual && test_cmp expect actual ' -test_expect_success PERL 'difftool --extcmd echo arg1' ' +test_expect_success 'difftool --extcmd echo arg1' ' echo file >expect && git difftool --no-prompt \ --extcmd sh\ -c\ \"echo\ \$1\" branch >actual && test_cmp expect actual ' -test_expect_success PERL 'difftool --extcmd cat arg1' ' +test_expect_success 'difftool --extcmd cat arg1' ' echo master >expect && git difftool --no-prompt \ --extcmd sh\ -c\ \"cat\ \$1\" branch >actual && test_cmp expect actual ' -test_expect_success PERL 'difftool --extcmd cat arg2' ' +test_expect_success 'difftool --extcmd cat arg2' ' echo branch >expect && git difftool --no-prompt \ --extcmd sh\ -c\ \"cat\ \$2\" branch >actual && @@ -325,7 +325,7 @@ test_expect_success PERL 'difftool --extcmd cat arg2' ' ' # Create a second file on master and a different version on branch -test_expect_success PERL 'setup with 2 files different' ' +test_expect_success 'setup with 2 files different' ' echo m2 >file2 && git add file2 && git commit -m "added file2" && @@ -337,7 +337,7 @@ test_expect_success PERL 'setup with 2 files different' ' git checkout master ' -test_expect_success PERL 'say no to the first file' ' +test_expect_success 'say no to the first file' ' (echo n && echo) >input && git difftool -x cat branch <input >output && grep m2 output && @@ -346,7 +346,7 @@ test_expect_success PERL 'say no to the first file' ' ! grep branch output ' -test_expect_success PERL 'say no to the second file' ' +test_expect_success 'say no to the second file' ' (echo && echo n) >input && git difftool -x cat branch <input >output && grep master output && @@ -355,7 +355,7 @@ test_expect_success PERL 'say no to the second file' ' ! grep br2 output ' -test_expect_success PERL 'ending prompt input with EOF' ' +test_expect_success 'ending prompt input with EOF' ' git difftool -x cat branch </dev/null >output && ! grep master output && ! grep branch output && @@ -363,12 +363,12 @@ test_expect_success PERL 'ending prompt input with EOF' ' ! grep br2 output ' -test_expect_success PERL 'difftool --tool-help' ' +test_expect_success 'difftool --tool-help' ' git difftool --tool-help >output && grep tool output ' -test_expect_success PERL 'setup change in subdirectory' ' +test_expect_success 'setup change in subdirectory' ' git checkout master && mkdir sub && echo master >sub/sub && @@ -382,11 +382,11 @@ test_expect_success PERL 'setup change in subdirectory' ' ' run_dir_diff_test () { - test_expect_success PERL "$1 --no-symlinks" " + test_expect_success "$1 --no-symlinks" " symlinks=--no-symlinks && $2 " - test_expect_success PERL,SYMLINKS "$1 --symlinks" " + test_expect_success SYMLINKS "$1 --symlinks" " symlinks=--symlinks && $2 " @@ -508,7 +508,7 @@ do done >actual EOF -test_expect_success PERL,SYMLINKS 'difftool --dir-diff --symlink without unstaged changes' ' +test_expect_success SYMLINKS 'difftool --dir-diff --symlink without unstaged changes' ' cat >expect <<-EOF && file $PWD/file @@ -545,7 +545,7 @@ write_script modify-file <<\EOF echo "new content" >file EOF -test_expect_success PERL 'difftool --no-symlinks does not overwrite working tree file ' ' +test_expect_success 'difftool --no-symlinks does not overwrite working tree file ' ' echo "orig content" >file && git difftool --dir-diff --no-symlinks --extcmd "$PWD/modify-file" branch && echo "new content" >expect && @@ -558,7 +558,7 @@ echo "tmp content" >"$2/file" && echo "$2" >tmpdir EOF -test_expect_success PERL 'difftool --no-symlinks detects conflict ' ' +test_expect_success 'difftool --no-symlinks detects conflict ' ' ( TMPDIR=$TRASH_DIRECTORY && export TMPDIR && @@ -571,7 +571,7 @@ test_expect_success PERL 'difftool --no-symlinks detects conflict ' ' ) ' -test_expect_success PERL 'difftool properly honors gitlink and core.worktree' ' +test_expect_success 'difftool properly honors gitlink and core.worktree' ' git submodule add ./. submod/ule && test_config -C submod/ule diff.tool checktrees && test_config -C submod/ule difftool.checktrees.cmd '\'' @@ -585,7 +585,7 @@ test_expect_success PERL 'difftool properly honors gitlink and core.worktree' ' ) ' -test_expect_success PERL,SYMLINKS 'difftool --dir-diff symlinked directories' ' +test_expect_success SYMLINKS 'difftool --dir-diff symlinked directories' ' git init dirlinks && ( cd dirlinks && diff --git a/t/test-lib.sh b/t/test-lib.sh index cde7fc7fcf..86d77c16dd 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -966,7 +966,8 @@ yes () { } # Fix some commands on Windows -case $(uname -s) in +uname_s=$(uname -s) +case $uname_s in *MINGW*) # Windows has its own (incompatible) sort and find sort () { @@ -1141,6 +1142,7 @@ test_lazy_prereq SANITY ' return $status ' +test FreeBSD != $uname_s || GIT_UNZIP=${GIT_UNZIP:-/usr/local/bin/unzip} GIT_UNZIP=${GIT_UNZIP:-unzip} test_lazy_prereq UNZIP ' "$GIT_UNZIP" -v @@ -3,6 +3,7 @@ #include "commit.h" #include "tree.h" #include "blob.h" +#include "gpg-interface.h" const char *tag_type = "tag"; @@ -24,7 +25,9 @@ static int run_gpg_verify(const char *buf, unsigned long size, unsigned flags) ret = check_signature(buf, payload_size, buf + payload_size, size - payload_size, &sigc); - print_signature_buffer(&sigc, flags); + + if (!(flags & GPG_VERIFY_OMIT_STATUS)) + print_signature_buffer(&sigc, flags); signature_check_clear(&sigc); return ret; diff --git a/transport.c b/transport.c index c86ba2eb89..d72e089484 100644 --- a/transport.c +++ b/transport.c @@ -1015,7 +1015,9 @@ int transport_push(struct transport *transport, if (run_pre_push_hook(transport, remote_refs)) return -1; - if ((flags & TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND) && !is_bare_repository()) { + if ((flags & (TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND | + TRANSPORT_RECURSE_SUBMODULES_ONLY)) && + !is_bare_repository()) { struct ref *ref = remote_refs; struct sha1_array commits = SHA1_ARRAY_INIT; @@ -1033,7 +1035,8 @@ int transport_push(struct transport *transport, } if (((flags & TRANSPORT_RECURSE_SUBMODULES_CHECK) || - ((flags & TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND) && + ((flags & (TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND | + TRANSPORT_RECURSE_SUBMODULES_ONLY)) && !pretend)) && !is_bare_repository()) { struct ref *ref = remote_refs; struct string_list needs_pushing = STRING_LIST_INIT_DUP; @@ -1052,7 +1055,10 @@ int transport_push(struct transport *transport, sha1_array_clear(&commits); } - push_ret = transport->push_refs(transport, remote_refs, flags); + if (!(flags & TRANSPORT_RECURSE_SUBMODULES_ONLY)) + push_ret = transport->push_refs(transport, remote_refs, flags); + else + push_ret = 0; err = push_had_errors(remote_refs); ret = push_ret | err; @@ -1064,7 +1070,8 @@ int transport_push(struct transport *transport, if (flags & TRANSPORT_PUSH_SET_UPSTREAM) set_upstreams(transport, remote_refs, pretend); - if (!(flags & TRANSPORT_PUSH_DRY_RUN)) { + if (!(flags & (TRANSPORT_PUSH_DRY_RUN | + TRANSPORT_RECURSE_SUBMODULES_ONLY))) { struct ref *ref; for (ref = remote_refs; ref; ref = ref->next) transport_update_tracking_ref(transport->remote, ref, verbose); diff --git a/transport.h b/transport.h index 9820f10b8e..e597b31b38 100644 --- a/transport.h +++ b/transport.h @@ -131,21 +131,22 @@ struct transport { enum transport_family family; }; -#define TRANSPORT_PUSH_ALL 1 -#define TRANSPORT_PUSH_FORCE 2 -#define TRANSPORT_PUSH_DRY_RUN 4 -#define TRANSPORT_PUSH_MIRROR 8 -#define TRANSPORT_PUSH_PORCELAIN 16 -#define TRANSPORT_PUSH_SET_UPSTREAM 32 -#define TRANSPORT_RECURSE_SUBMODULES_CHECK 64 -#define TRANSPORT_PUSH_PRUNE 128 -#define TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND 256 -#define TRANSPORT_PUSH_NO_HOOK 512 -#define TRANSPORT_PUSH_FOLLOW_TAGS 1024 -#define TRANSPORT_PUSH_CERT_ALWAYS 2048 -#define TRANSPORT_PUSH_CERT_IF_ASKED 4096 -#define TRANSPORT_PUSH_ATOMIC 8192 -#define TRANSPORT_PUSH_OPTIONS 16384 +#define TRANSPORT_PUSH_ALL (1<<0) +#define TRANSPORT_PUSH_FORCE (1<<1) +#define TRANSPORT_PUSH_DRY_RUN (1<<2) +#define TRANSPORT_PUSH_MIRROR (1<<3) +#define TRANSPORT_PUSH_PORCELAIN (1<<4) +#define TRANSPORT_PUSH_SET_UPSTREAM (1<<5) +#define TRANSPORT_RECURSE_SUBMODULES_CHECK (1<<6) +#define TRANSPORT_PUSH_PRUNE (1<<7) +#define TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND (1<<8) +#define TRANSPORT_PUSH_NO_HOOK (1<<9) +#define TRANSPORT_PUSH_FOLLOW_TAGS (1<<10) +#define TRANSPORT_PUSH_CERT_ALWAYS (1<<11) +#define TRANSPORT_PUSH_CERT_IF_ASKED (1<<12) +#define TRANSPORT_PUSH_ATOMIC (1<<13) +#define TRANSPORT_PUSH_OPTIONS (1<<14) +#define TRANSPORT_RECURSE_SUBMODULES_ONLY (1<<15) extern int transport_summary_width(const struct ref *refs); diff --git a/unpack-trees.c b/unpack-trees.c index 129d49ff45..3a8ee19fe8 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -52,6 +52,41 @@ static const char *unpack_plumbing_errors[NB_UNPACK_TREES_ERROR_TYPES] = { ? ((o)->msgs[(type)]) \ : (unpack_plumbing_errors[(type)]) ) +static const char *super_prefixed(const char *path) +{ + /* + * It is necessary and sufficient to have two static buffers + * here, as the return value of this function is fed to + * error() using the unpack_*_errors[] templates we see above. + */ + static struct strbuf buf[2] = {STRBUF_INIT, STRBUF_INIT}; + static int super_prefix_len = -1; + static unsigned idx = ARRAY_SIZE(buf) - 1; + + if (super_prefix_len < 0) { + const char *super_prefix = get_super_prefix(); + if (!super_prefix) { + super_prefix_len = 0; + } else { + int i; + for (i = 0; i < ARRAY_SIZE(buf); i++) + strbuf_addstr(&buf[i], super_prefix); + super_prefix_len = buf[0].len; + } + } + + if (!super_prefix_len) + return path; + + if (++idx >= ARRAY_SIZE(buf)) + idx = 0; + + strbuf_setlen(&buf[idx], super_prefix_len); + strbuf_addstr(&buf[idx], path); + + return buf[idx].buf; +} + void setup_unpack_trees_porcelain(struct unpack_trees_options *opts, const char *cmd) { @@ -172,7 +207,7 @@ static int add_rejected_path(struct unpack_trees_options *o, const char *path) { if (!o->show_all_errors) - return error(ERRORMSG(o, e), path); + return error(ERRORMSG(o, e), super_prefixed(path)); /* * Otherwise, insert in a list for future display by @@ -196,7 +231,7 @@ static void display_error_msgs(struct unpack_trees_options *o) something_displayed = 1; for (i = 0; i < rejects->nr; i++) strbuf_addf(&path, "\t%s\n", rejects->items[i].string); - error(ERRORMSG(o, e), path.buf); + error(ERRORMSG(o, e), super_prefixed(path.buf)); strbuf_release(&path); } string_list_clear(rejects, 0); @@ -1925,7 +1960,9 @@ int bind_merge(const struct cache_entry * const *src, o->merge_size); if (a && old) return o->gently ? -1 : - error(ERRORMSG(o, ERROR_BIND_OVERLAP), a->name, old->name); + error(ERRORMSG(o, ERROR_BIND_OVERLAP), + super_prefixed(a->name), + super_prefixed(old->name)); if (!a) return keep_entry(old, o); else @@ -7,21 +7,19 @@ #include "cache.h" static FILE *error_handle; -static int tweaked_error_buffering; void vreportf(const char *prefix, const char *err, va_list params) { + char msg[4096]; FILE *fh = error_handle ? error_handle : stderr; + char *p; - fflush(fh); - if (!tweaked_error_buffering) { - setvbuf(fh, NULL, _IOLBF, 0); - tweaked_error_buffering = 1; + vsnprintf(msg, sizeof(msg), err, params); + for (p = msg; *p; p++) { + if (iscntrl(*p) && *p != '\t' && *p != '\n') + *p = '?'; } - - fputs(prefix, fh); - vfprintf(fh, err, params); - fputc('\n', fh); + fprintf(fh, "%s%s\n", prefix, msg); } static NORETURN void usage_builtin(const char *err, va_list params) @@ -93,7 +91,6 @@ void set_die_is_recursing_routine(int (*routine)(void)) void set_error_handle(FILE *fh) { error_handle = fh; - tweaked_error_buffering = 0; } void NORETURN usagef(const char *err, ...) diff --git a/worktree.c b/worktree.c index 53b4771c04..d633761575 100644 --- a/worktree.c +++ b/worktree.c @@ -145,7 +145,7 @@ done: static void mark_current_worktree(struct worktree **worktrees) { - char *git_dir = xstrdup(absolute_path(get_git_dir())); + char *git_dir = absolute_pathdup(get_git_dir()); int i; for (i = 0; worktrees[i]; i++) { diff --git a/wt-status.c b/wt-status.c index a715e71906..4dff0b3e21 100644 --- a/wt-status.c +++ b/wt-status.c @@ -1135,14 +1135,17 @@ static void abbrev_sha1_in_line(struct strbuf *line) strbuf_list_free(split); } -static void read_rebase_todolist(const char *fname, struct string_list *lines) +static int read_rebase_todolist(const char *fname, struct string_list *lines) { struct strbuf line = STRBUF_INIT; FILE *f = fopen(git_path("%s", fname), "r"); - if (!f) + if (!f) { + if (errno == ENOENT) + return -1; die_errno("Could not open file %s for reading", git_path("%s", fname)); + } while (!strbuf_getline_lf(&line, f)) { if (line.len && line.buf[0] == comment_line_char) continue; @@ -1152,6 +1155,7 @@ static void read_rebase_todolist(const char *fname, struct string_list *lines) abbrev_sha1_in_line(&line); string_list_append(lines, line.buf); } + return 0; } static void show_rebase_information(struct wt_status *s, @@ -1166,8 +1170,10 @@ static void show_rebase_information(struct wt_status *s, struct string_list yet_to_do = STRING_LIST_INIT_DUP; read_rebase_todolist("rebase-merge/done", &have_done); - read_rebase_todolist("rebase-merge/git-rebase-todo", &yet_to_do); - + if (read_rebase_todolist("rebase-merge/git-rebase-todo", + &yet_to_do)) + status_printf_ln(s, color, + _("git-rebase-todo is missing.")); if (have_done.nr == 0) status_printf_ln(s, color, _("No commands done.")); else { |