summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--.mailmap3
-rw-r--r--.travis.yml12
-rw-r--r--Documentation/CodingGuidelines37
-rw-r--r--Documentation/Makefile22
-rw-r--r--Documentation/RelNotes/2.11.1.txt168
-rw-r--r--Documentation/RelNotes/2.12.0.txt229
-rw-r--r--Documentation/SubmittingPatches13
-rw-r--r--Documentation/asciidoctor-extensions.rb28
-rwxr-xr-xDocumentation/cat-texi.perl21
-rw-r--r--Documentation/config.txt51
-rw-r--r--Documentation/diff-config.txt13
-rw-r--r--Documentation/diff-options.txt36
-rw-r--r--Documentation/git-bisect.txt4
-rw-r--r--Documentation/git-grep.txt14
-rw-r--r--Documentation/git-gui.txt2
-rw-r--r--Documentation/git-p4.txt2
-rw-r--r--Documentation/git-relink.txt30
-rw-r--r--Documentation/git-rev-parse.txt3
-rw-r--r--Documentation/git-tag.txt6
-rw-r--r--Documentation/git-verify-tag.txt2
-rw-r--r--Documentation/git.txt3
-rw-r--r--Documentation/giteveryday.txt17
-rw-r--r--Documentation/gitk.txt14
-rw-r--r--Documentation/technical/api-hashmap.txt4
-rw-r--r--Documentation/technical/api-in-core-index.txt21
-rw-r--r--Documentation/technical/api-setup.txt2
-rw-r--r--Documentation/texi.xsl26
-rw-r--r--Documentation/user-manual.txt8
-rw-r--r--Makefile21
-rw-r--r--abspath.c238
-rw-r--r--archive-zip.c7
-rw-r--r--builtin.h1
-rw-r--r--builtin/blame.c27
-rw-r--r--builtin/clone.c4
-rw-r--r--builtin/commit.c31
-rw-r--r--builtin/difftool.c692
-rw-r--r--builtin/fetch.c2
-rw-r--r--builtin/fsck.c164
-rw-r--r--builtin/gc.c9
-rw-r--r--builtin/grep.c386
-rw-r--r--builtin/init-db.c6
-rw-r--r--builtin/ls-tree.c16
-rw-r--r--builtin/mv.c50
-rw-r--r--builtin/push.c2
-rw-r--r--builtin/receive-pack.c3
-rw-r--r--builtin/remote.c14
-rw-r--r--builtin/repack.c9
-rw-r--r--builtin/rm.c84
-rw-r--r--builtin/show-ref.c49
-rw-r--r--builtin/submodule--helper.c15
-rw-r--r--builtin/tag.c37
-rw-r--r--builtin/verify-tag.c22
-rw-r--r--cache.h54
-rw-r--r--color.c12
-rw-r--r--command-list.txt1
-rw-r--r--compat/qsort_s.c69
-rw-r--r--compat/winansi.c11
-rw-r--r--config.c8
-rw-r--r--contrib/coccinelle/xstrdup_or_null.cocci6
-rw-r--r--contrib/convert-objects/convert-objects.c329
-rw-r--r--contrib/convert-objects/git-convert-objects.txt29
-rwxr-xr-xcontrib/examples/git-difftool.perl (renamed from git-difftool.perl)0
-rwxr-xr-xcontrib/gitview/gitview1305
-rw-r--r--contrib/gitview/gitview.txt57
-rw-r--r--diff.c8
-rw-r--r--dir.c195
-rw-r--r--environment.c2
-rw-r--r--exec_cmd.c14
-rw-r--r--fsck.c4
-rw-r--r--git-compat-util.h11
-rwxr-xr-xgit-mergetool.sh21
-rwxr-xr-xgit-p4.py6
-rw-r--r--git-rebase--interactive.sh2
-rwxr-xr-xgit-relink.perl173
-rwxr-xr-xgit-request-pull.sh3
-rwxr-xr-xgit-submodule.sh15
-rw-r--r--git.c39
-rw-r--r--gitk-git/Makefile1
-rwxr-xr-xgitk-git/gitk166
-rw-r--r--gitk-git/po/bg.po4
-rw-r--r--gitk-git/po/ca.po6
-rw-r--r--gitk-git/po/de.po4
-rw-r--r--gitk-git/po/es.po4
-rw-r--r--gitk-git/po/fr.po4
-rw-r--r--gitk-git/po/hu.po4
-rw-r--r--gitk-git/po/it.po4
-rw-r--r--gitk-git/po/ja.po13
-rw-r--r--gitk-git/po/pt_br.po4
-rw-r--r--gitk-git/po/pt_pt.po1376
-rw-r--r--gitk-git/po/ru.po670
-rw-r--r--gitk-git/po/sv.po19
-rw-r--r--gitk-git/po/vi.po4
-rw-r--r--gpg-interface.h5
-rw-r--r--graph.c40
-rw-r--r--grep.c16
-rw-r--r--grep.h1
-rw-r--r--help.c21
-rw-r--r--pathspec.c515
-rw-r--r--pathspec.h5
-rw-r--r--read-cache.c4
-rw-r--r--ref-filter.c33
-rw-r--r--ref-filter.h7
-rw-r--r--remote.c18
-rw-r--r--remote.h4
-rw-r--r--run-command.c27
-rw-r--r--run-command.h1
-rw-r--r--sequencer.c1107
-rw-r--r--sequencer.h4
-rw-r--r--setup.c13
-rw-r--r--sha1_file.c191
-rw-r--r--string-list.c13
-rw-r--r--submodule-config.c4
-rw-r--r--submodule-config.h3
-rw-r--r--submodule.c118
-rw-r--r--submodule.h60
-rw-r--r--t/helper/test-string-list.c25
-rwxr-xr-xt/lib-submodule-update.sh5
-rwxr-xr-xt/perf/p0071-sort.sh26
-rwxr-xr-xt/t0001-init.sh3
-rwxr-xr-xt/t1403-show-ref.sh42
-rwxr-xr-xt/t1450-fsck.sh160
-rwxr-xr-xt/t1514-rev-parse-push.sh6
-rwxr-xr-xt/t3404-rebase-interactive.sh16
-rwxr-xr-xt/t3510-cherry-pick-sequence.sh6
-rwxr-xr-xt/t3600-rm.sh39
-rwxr-xr-xt/t3701-add-interactive.sh14
-rwxr-xr-xt/t4026-color.sh7
-rwxr-xr-xt/t4032-diff-inter-hunk-context.sh27
-rwxr-xr-xt/t4202-log.sh22
-rwxr-xr-xt/t5003-archive-zip.sh22
-rwxr-xr-xt/t5310-pack-bitmaps.sh8
-rwxr-xr-xt/t5504-fetch-receive-strict.sh2
-rwxr-xr-xt/t5505-remote.sh7
-rwxr-xr-xt/t5516-fetch-push.sh2
-rwxr-xr-xt/t5531-deep-submodule-push.sh21
-rwxr-xr-xt/t5580-clone-push-unc.sh48
-rwxr-xr-xt/t5601-clone.sh2
-rwxr-xr-xt/t6030-bisect-porcelain.sh2
-rwxr-xr-xt/t6134-pathspec-in-submodule.sh36
-rwxr-xr-xt/t6500-gc.sh25
-rwxr-xr-xt/t7004-tag.sh148
-rwxr-xr-xt/t7030-verify-tag.sh16
-rwxr-xr-xt/t7400-submodule-basic.sh14
-rwxr-xr-xt/t7406-submodule-update.sh17
-rwxr-xr-xt/t7411-submodule-config.sh25
-rwxr-xr-xt/t7512-status-help.sh19
-rwxr-xr-xt/t7610-mergetool.sh281
-rwxr-xr-xt/t7800-difftool.sh92
-rwxr-xr-xt/t7810-grep.sh26
-rwxr-xr-xt/t7814-grep-recurse-submodules.sh241
-rwxr-xr-xt/t8002-blame.sh32
-rwxr-xr-xt/t8011-blame-split-file.sh117
-rwxr-xr-xt/t9001-send-email.sh2
-rwxr-xr-xt/t9117-git-svn-init-clone.sh12
-rwxr-xr-xt/t9813-git-p4-preserve-users.sh16
-rwxr-xr-xt/t9814-git-p4-rename.sh6
-rw-r--r--t/test-lib.sh4
-rw-r--r--tag.c5
-rw-r--r--transport.c17
-rw-r--r--transport.h31
-rw-r--r--tree-walk.c28
-rw-r--r--unpack-trees.c53
-rw-r--r--usage.c17
-rw-r--r--versioncmp.c99
-rw-r--r--worktree.c4
-rw-r--r--wt-status.c14
167 files changed, 7714 insertions, 3743 deletions
diff --git a/.gitignore b/.gitignore
index 6722f78..b1020b8 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
diff --git a/.mailmap b/.mailmap
index 9cc33e9..ab59b2f 100644
--- a/.mailmap
+++ b/.mailmap
@@ -192,6 +192,8 @@ Philippe Bruhat <book@cpan.org>
Ralf Thielow <ralf.thielow@gmail.com> <ralf.thielow@googlemail.com>
Ramsay Jones <ramsay@ramsayjones.plus.com> <ramsay@ramsay1.demon.co.uk>
René Scharfe <l.s.r@web.de> <rene.scharfe@lsrfire.ath.cx>
+Richard Hansen <rhansen@rhansen.org> <hansenr@google.com>
+Richard Hansen <rhansen@rhansen.org> <rhansen@bbn.com>
Robert Fitzsimons <robfitz@273k.net>
Robert Shearman <robertshearman@gmail.com> <rob@codeweavers.com>
Robert Zeh <robert.a.zeh@gmail.com>
@@ -223,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 3843967..9c63c8c 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 4cd95da..a4191aa 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 b43d66e..b5be2e2 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))
@@ -337,7 +348,7 @@ manpage-base-url.xsl: manpage-base-url.xsl.in
user-manual.xml: user-manual.txt user-manual.conf
$(QUIET_ASCIIDOC)$(RM) $@+ $@ && \
- $(TXT_TO_XML) -d article -o $@+ $< && \
+ $(TXT_TO_XML) -d book -o $@+ $< && \
mv $@+ $@
technical/api-index.txt: technical/api-index-skel.txt \
@@ -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
new file mode 100644
index 0000000..9cd14c8
--- /dev/null
+++ b/Documentation/RelNotes/2.11.1.txt
@@ -0,0 +1,168 @@
+Git v2.11.1 Release Notes
+=========================
+
+Fixes since v2.11
+-----------------
+
+ * The default Travis-CI configuration specifies newer P4 and GitLFS.
+
+ * The character width table has been updated to match Unicode 9.0
+
+ * Update the isatty() emulation for Windows by updating the previous
+ hack that depended on internals of (older) MSVC runtime.
+
+ * "git rev-parse --symbolic" failed with a more recent notation like
+ "HEAD^-1" and "HEAD^!".
+
+ * An empty directory in a working tree that can simply be nuked used
+ to interfere while merging or cherry-picking a change to create a
+ submodule directory there, which has been fixed..
+
+ * The code in "git push" to compute if any commit being pushed in the
+ superproject binds a commit in a submodule that hasn't been pushed
+ out was overly inefficient, making it unusable even for a small
+ project that does not have any submodule but have a reasonable
+ number of refs.
+
+ * "git push --dry-run --recurse-submodule=on-demand" wasn't
+ "--dry-run" in the submodules.
+
+ * The output from "git worktree list" was made in readdir() order,
+ and was unstable.
+
+ * mergetool.<tool>.trustExitCode configuration variable did not apply
+ to built-in tools, but now it does.
+
+ * "git p4" LFS support was broken when LFS stores an empty blob.
+
+ * Fix a corner case in merge-recursive regression that crept in
+ during 2.10 development cycle.
+
+ * Update the error messages from the dumb-http client when it fails
+ to obtain loose objects; we used to give sensible error message
+ only upon 404 but we now forbid unexpected redirects that needs to
+ be reported with something sensible.
+
+ * When diff.renames configuration is on (and with Git 2.9 and later,
+ it is enabled by default, which made it worse), "git stash"
+ misbehaved if a file is removed and another file with a very
+ similar content is added.
+
+ * "git diff --no-index" did not take "--no-abbrev" option.
+
+ * "git difftool --dir-diff" had a minor regression when started from
+ a subdirectory, which has been fixed.
+
+ * "git commit --allow-empty --only" (no pathspec) with dirty index
+ ought to be an acceptable way to create a new commit that does not
+ change any paths, but it was forbidden, perhaps because nobody
+ needed it so far.
+
+ * A pathname that begins with "//" or "\\" on Windows is special but
+ path normalization logic was unaware of it.
+
+ * "git pull --rebase", when there is no new commits on our side since
+ we forked from the upstream, should be able to fast-forward without
+ invoking "git rebase", but it didn't.
+
+ * The way to specify hotkeys to "xxdiff" that is used by "git
+ mergetool" has been modernized to match recent versions of xxdiff.
+
+ * Unlike "git am --abort", "git cherry-pick --abort" moved HEAD back
+ to where cherry-pick started while picking multiple changes, when
+ the cherry-pick stopped to ask for help from the user, and the user
+ did "git reset --hard" to a different commit in order to re-attempt
+ the operation.
+
+ * Code cleanup in shallow boundary computation.
+
+ * A recent update to receive-pack to make it easier to drop garbage
+ objects made it clear that GIT_ALTERNATE_OBJECT_DIRECTORIES cannot
+ have a pathname with a colon in it (no surprise!), and this in turn
+ made it impossible to push into a repository at such a path. This
+ has been fixed by introducing a quoting mechanism used when
+ appending such a path to the colon-separated list.
+
+ * The function usage_msg_opt() has been updated to say "fatal:"
+ before the custom message programs give, when they want to die
+ with a message about wrong command line options followed by the
+ standard usage string.
+
+ * "git index-pack --stdin" needs an access to an existing repository,
+ but "git index-pack file.pack" to generate an .idx file that
+ corresponds to a packfile does not.
+
+ * Fix for NDEBUG builds.
+
+ * A lazy "git push" without refspec did not internally use a fully
+ specified refspec to perform 'current', 'simple', or 'upstream'
+ push, causing unnecessary "ambiguous ref" errors.
+
+ * "git p4" misbehaved when swapping a directory and a symbolic link.
+
+ * Even though an fix was attempted in Git 2.9.3 days, but running
+ "git difftool --dir-diff" from a subdirectory never worked. This
+ has been fixed.
+
+ * "git p4" that tracks multile p4 paths imported a single changelist
+ that touches files in these multiple paths as one commit, followed
+ by many empty commits. This has been fixed.
+
+ * A potential but unlikely buffer overflow in Windows port has been
+ fixed.
+
+ * When the http server gives an incomplete response to a smart-http
+ rpc call, it could lead to client waiting for a full response that
+ 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 2a19064..9c3f94a 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,49 @@ 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.
+
+ * "git rm" used to refuse to remove a submodule when it has its own
+ git repository embedded in its working tree. It learned to move
+ the repository away to $GIT_DIR/modules/ of the superproject
+ instead, and allow the submodule to be deleted (as long as there
+ will be no loss of local modifications, that is).
+
+ * A recent updates to "git p4" was not usable for older p4 but it
+ could be made to work with minimum changes. Do so.
+
+ * "git diff" learned diff.interHunkContext configuration variable
+ that gives the default value for its --inter-hunk-context option.
+
+ * The prereleaseSuffix feature of version comparison that is used in
+ "git tag -l" did not correctly when two or more prereleases for the
+ 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.
@@ -75,16 +119,13 @@ Performance, Internal Implementation, Development Support etc.
code with "git interpret-trailer".
* The default Travis-CI configuration specifies newer P4 and GitLFS.
- (merge 5f703e8f02 ls/travis-update-p4-and-lfs later to maint).
* The "fast hash" that had disastrous performance issues in some
corner cases has been retired from the internal diff.
* The character width table has been updated to match Unicode 9.0
- (merge 9e6e9aefdf bb/unicode-9.0 later to maint).
* 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.
@@ -101,6 +142,28 @@ Performance, Internal Implementation, Development Support etc.
superproject to .git/modules/ (and point the latter with the former
that is turned into a "gitdir:" file) has been added.
+ * "git push \\server\share\dir" has recently regressed and then
+ fixed. A test has retroactively been added for this breakage.
+
+ * Build updates for Cygwin.
+
+ * The implementation of "real_path()" was to go there with chdir(2)
+ and call getcwd(3), but this obviously wouldn't be usable in a
+ threaded environment. Rewrite it to manually resolve relative
+ paths including symbolic links in path components.
+
+ * 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.
@@ -115,78 +178,62 @@ notes for details).
standard I/O streams are connected to a TTY, but isatty() that
comes with Windows incorrectly returned true if it is used on NUL
(i.e. an equivalent to /dev/null). This has been fixed.
- (merge cbb3f3c9b1 js/mingw-isatty later to maint).
* "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^!".
- (merge a2e7b04c44 jk/rev-parse-symbolic-parents-fix later to maint).
* An empty directory in a working tree that can simply be nuked used
to interfere while merging or cherry-picking a change to create a
submodule directory there, which has been fixed..
- (merge 5423d2e700 dt/empty-submodule-in-merge later to maint).
* The code in "git push" to compute if any commit being pushed in the
superproject binds a commit in a submodule that hasn't been pushed
out was overly inefficient, making it unusable even for a small
project that does not have any submodule but have a reasonable
number of refs.
- (merge 250ab24ab3 hv/submodule-not-yet-pushed-fix later to maint).
* "git push --dry-run --recurse-submodule=on-demand" wasn't
"--dry-run" in the submodules.
- (merge 0301c821c5 bw/push-dry-run later to maint).
* The output from "git worktree list" was made in readdir() order,
and was unstable.
- (merge 4df1d4d466 nd/worktree-list-fixup later to maint).
* mergetool.<tool>.trustExitCode configuration variable did not apply
to built-in tools, but now it does.
- (merge 2967284456 da/mergetool-trust-exit-code later to maint).
* "git p4" LFS support was broken when LFS stores an empty blob.
- (merge d5eb3cf5e7 ls/p4-empty-file-on-lfs later to maint).
* A corner case in merge-recursive regression that crept in
during 2.10 development cycle has been fixed.
- (merge 1c25d2d8ed jc/renormalize-merge-kill-safer-crlf later to maint).
* Transport with dumb http can be fooled into following foreign URLs
that the end user does not intend to, especially with the server
side redirects and http-alternates mechanism, which can lead to
security issues. Tighten the redirection and make it more obvious
to the end user when it happens.
- (merge cb4d2d35c4 jk/http-walker-limit-redirect-2.9 later to maint).
* Update the error messages from the dumb-http client when it fails
to obtain loose objects; we used to give sensible error message
only upon 404 but we now forbid unexpected redirects that needs to
be reported with something sensible.
- (merge 3680f16f9d jk/http-walker-limit-redirect later to maint).
* When diff.renames configuration is on (and with Git 2.9 and later,
it is enabled by default, which made it worse), "git stash"
misbehaved if a file is removed and another file with a very
similar content is added.
- (merge 9d4e28ead5 jk/stash-disable-renames-internally later to maint).
* "git diff --no-index" did not take "--no-abbrev" option.
- (merge 43d1948b7b jb/diff-no-index-no-abbrev later to maint).
* "git difftool --dir-diff" had a minor regression when started from
a subdirectory, which has been fixed.
- (merge 853e10c197 da/difftool-dir-diff-fix later to maint).
* "git commit --allow-empty --only" (no pathspec) with dirty index
ought to be an acceptable way to create a new commit that does not
change any paths, but it was forbidden, perhaps because nobody
needed it so far.
- (merge beb635ca9c ak/commit-only-allow-empty later to maint).
* Git 2.11 had a minor regression in "merge --ff-only" that competed
with another process that simultanously attempted to update the
@@ -196,26 +243,21 @@ notes for details).
* A pathname that begins with "//" or "\\" on Windows is special but
path normalization logic was unaware of it.
- (merge 7814fbe3f1 js/normalize-path-copy-ceil later to maint).
* "git pull --rebase", when there is no new commits on our side since
we forked from the upstream, should be able to fast-forward without
invoking "git rebase", but it didn't.
- (merge 33b842a1e9 jc/pull-rebase-ff later to maint).
* The way to specify hotkeys to "xxdiff" that is used by "git
mergetool" has been modernized to match recent versions of xxdiff.
- (merge 6cf5f6cef7 da/mergetool-xxdiff-hotkey later to maint).
* Unlike "git am --abort", "git cherry-pick --abort" moved HEAD back
to where cherry-pick started while picking multiple changes, when
the cherry-pick stopped to ask for help from the user, and the user
did "git reset --hard" to a different commit in order to re-attempt
the operation.
- (merge ce73bb22d8 sb/sequencer-abort-safety later to maint).
* Code cleanup in shallow boundary computation.
- (merge 649b0c316a nd/shallow-fixup later to maint).
* A recent update to receive-pack to make it easier to drop garbage
objects made it clear that GIT_ALTERNATE_OBJECT_DIRECTORIES cannot
@@ -223,78 +265,159 @@ notes for details).
made it impossible to push into a repository at such a path. This
has been fixed by introducing a quoting mechanism used when
appending such a path to the colon-separated list.
- (merge 5e74824fac jk/quote-env-path-list-component later to maint).
* The function usage_msg_opt() has been updated to say "fatal:"
before the custom message programs give, when they want to die
with a message about wrong command line options followed by the
standard usage string.
- (merge 87433261a4 jk/parseopt-usage-msg-opt later to maint).
* "git index-pack --stdin" needs an access to an existing repository,
but "git index-pack file.pack" to generate an .idx file that
corresponds to a packfile does not.
- (merge 29401e1575 jk/index-pack-wo-repo-from-stdin later to maint).
* Fix for NDEBUG builds.
- (merge 08414938a2 jt/mailinfo-fold-in-body-headers later to maint).
* A lazy "git push" without refspec did not internally use a fully
specified refspec to perform 'current', 'simple', or 'upstream'
push, causing unnecessary "ambiguous ref" errors.
- (merge b284495e93 jc/push-default-explicit later to maint).
* "git p4" misbehaved when swapping a directory and a symbolic link.
- (merge df8a9e86db ld/p4-compare-dir-vs-symlink later to maint).
* Even though an fix was attempted in Git 2.9.3 days, but running
"git difftool --dir-diff" from a subdirectory never worked. This
has been fixed.
- (merge ce6926974e jk/difftool-in-subdir later to maint).
* "git p4" that tracks multile p4 paths imported a single changelist
that touches files in these multiple paths as one commit, followed
by many empty commits. This has been fixed.
- (merge 9943e5b979 gv/p4-multi-path-commit-fix later to maint).
* A potential but unlikely buffer overflow in Windows port has been
fixed.
- (merge c46458e82f mk/mingw-winansi-ttyname-termination-fix later to maint).
* When the http server gives an incomplete response to a smart-http
rpc call, it could lead to client waiting for a full response that
will never come. Teach the client side to notice this condition
and abort the transfer.
- (merge f8edeaa05d dt/smart-http-detect-server-going-away later to maint).
* 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.
- (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".
+
+ * "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.
+
+ * Running "git add a/b" when "a" is a submodule correctly errored
+ out, but without a meaningful error message.
+ (merge 2d81c48fa7 sb/pathspec-errors later to maint).
+
+ * 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.
+
+ * "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
+ has been fixed.
+
+ * Retire long unused/unmaintained gitview from the contrib/ area.
+ (merge 3120925c25 sb/remove-gitview later to maint).
+
+ * Tighten a test to avoid mistaking an extended ERE regexp engine as
+ a PRE regexp engine.
+
+ * 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 fa6ca11105 nd/qsort-in-merge-recursive later to maint).
- (merge fa3142c919 ak/lazy-prereq-mktemp later to maint).
- (merge 9c48b4fb23 ls/t0021-fixup later to maint).
- (merge 584f99c87b sb/unpack-trees-grammofix later to maint).
- (merge 54471fdcc3 jk/readme-gmane-is-no-more later to maint).
- (merge 9e189f1a5c sb/t3600-cleanup later to maint).
- (merge e2c20be57c lr/doc-fix-cet later to maint).
- (merge 47437fd3bd kh/tutorial-grammofix later to maint).
(merge f2627d9b19 sb/submodule-config-cleanup later to maint).
- (merge 7eeda8b821 ls/filter-process later to maint).
- (merge 6cc823c5c1 jt/fetch-no-redundant-tag-fetch-map later to maint).
- (merge 235ec24352 mm/push-social-engineering-attack-doc later to maint).
- (merge f1350d0c12 mm/gc-safety-doc later to maint).
+ (merge 384f1a167b sb/unpack-trees-cleanup later to maint).
+ (merge 874444b704 rh/diff-orderfile-doc 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 08352de..3faf7eb 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 0000000..ec83b49
--- /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 87437f8..14d2f83 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 5064312..fc78139 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.
@@ -3113,17 +3120,39 @@ user.signingKey::
This option is passed unchanged to gpg's --local-user parameter,
so you may specify a key using any method that gpg supports.
-versionsort.prereleaseSuffix::
- When version sort is used in linkgit:git-tag[1], prerelease
- tags (e.g. "1.0-rc1") may appear after the main release
- "1.0". By specifying the suffix "-rc" in this variable,
- "1.0-rc1" will appear before "1.0".
-+
-This variable can be specified multiple times, once per suffix. The
-order of suffixes in the config file determines the sorting order
-(e.g. if "-pre" appears before "-rc" in the config file then 1.0-preXX
-is sorted before 1.0-rcXX). The sorting order between different
-suffixes is undefined if they are in multiple config files.
+versionsort.prereleaseSuffix (deprecated)::
+ Deprecated alias for `versionsort.suffix`. Ignored if
+ `versionsort.suffix` is set.
+
+versionsort.suffix::
+ Even when version sort is used in linkgit:git-tag[1], tagnames
+ with the same base version but different suffixes are still sorted
+ lexicographically, resulting e.g. in prerelease tags appearing
+ after the main release (e.g. "1.0-rc1" after "1.0"). This
+ variable can be specified to determine the sorting order of tags
+ with different suffixes.
++
+By specifying a single suffix in this variable, any tagname containing
+that suffix will appear before the corresponding main release. E.g. if
+the variable is set to "-rc", then all "1.0-rcX" tags will appear before
+"1.0". If specified multiple times, once per suffix, then the order of
+suffixes in the configuration will determine the sorting order of tagnames
+with those suffixes. E.g. if "-pre" appears before "-rc" in the
+configuration, then all "1.0-preX" tags will be listed before any
+"1.0-rcX" tags. The placement of the main release tag relative to tags
+with various suffixes can be determined by specifying the empty suffix
+among those other suffixes. E.g. if the suffixes "-rc", "", "-ck" and
+"-bfs" appear in the configuration in this order, then all "v4.8-rcX" tags
+are listed first, followed by "v4.8", then "v4.8-ckX" and finally
+"v4.8-bfsX".
++
+If more than one suffixes match the same tagname, then that tagname will
+be sorted according to the suffix which starts at the earliest position in
+the tagname. If more than one different matching suffixes start at
+that earliest position, then that tagname will be sorted according to the
+longest of those suffixes.
+The sorting order between different suffixes is undefined if they are
+in multiple config files.
web.browser::
Specify a web browser that may be used by some commands.
diff --git a/Documentation/diff-config.txt b/Documentation/diff-config.txt
index d8570f2..cbce8ec 100644
--- a/Documentation/diff-config.txt
+++ b/Documentation/diff-config.txt
@@ -60,6 +60,12 @@ diff.context::
Generate diffs with <n> lines of context instead of the default
of 3. This value is overridden by the -U option.
+diff.interHunkContext::
+ Show the context between diff hunks, up to the specified number
+ of lines, thereby fusing the hunks that are close to each other.
+ This value serves as the default for the `--inter-hunk-context`
+ command line option.
+
diff.external::
If this config variable is set, diff generation is not
performed using the internal diff machinery, but using the
@@ -99,9 +105,10 @@ diff.noprefix::
If set, 'git diff' does not show any source or destination prefix.
diff.orderFile::
- File indicating how to order files within a diff, using
- one shell glob pattern per line.
- Can be overridden by the '-O' option to linkgit:git-diff[1].
+ File indicating how to order files within a diff.
+ See the '-O' option to linkgit:git-diff[1] for details.
+ If `diff.orderFile` is a relative pathname, it is treated as
+ relative to the top of the working tree.
diff.renameLimit::
The number of files to consider when performing the copy/rename
diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt
index e6215c3..d91ddbd 100644
--- a/Documentation/diff-options.txt
+++ b/Documentation/diff-options.txt
@@ -466,11 +466,41 @@ information.
endif::git-format-patch[]
-O<orderfile>::
- Output the patch in the order specified in the
- <orderfile>, which has one shell glob pattern per line.
+ Control the order in which files appear in the output.
This overrides the `diff.orderFile` configuration variable
(see linkgit:git-config[1]). To cancel `diff.orderFile`,
use `-O/dev/null`.
++
+The output order is determined by the order of glob patterns in
+<orderfile>.
+All files with pathnames that match the first pattern are output
+first, all files with pathnames that match the second pattern (but not
+the first) are output next, and so on.
+All files with pathnames that do not match any pattern are output
+last, as if there was an implicit match-all pattern at the end of the
+file.
+If multiple pathnames have the same rank (they match the same pattern
+but no earlier patterns), their output order relative to each other is
+the normal order.
++
+<orderfile> is parsed as follows:
++
+--
+ - Blank lines are ignored, so they can be used as separators for
+ readability.
+
+ - Lines starting with a hash ("`#`") are ignored, so they can be used
+ for comments. Add a backslash ("`\`") to the beginning of the
+ pattern if it starts with a hash.
+
+ - Each other line contains a single pattern.
+--
++
+Patterns have the same syntax and semantics as patterns used for
+fnmantch(3) without the FNM_PATHNAME flag, except a pathname also
+matches a pattern if removing any number of the final pathname
+components matches the pattern. For example, the pattern "`foo*bar`"
+matches "`fooasdfbar`" and "`foo/bar/baz/asdf`" but not "`foobarx`".
ifndef::git-format-patch[]
-R::
@@ -511,6 +541,8 @@ endif::git-format-patch[]
--inter-hunk-context=<lines>::
Show the context between diff hunks, up to the specified number
of lines, thereby fusing hunks that are close to each other.
+ Defaults to `diff.interHunkContext` or 0 if the config option
+ is unset.
-W::
--function-context::
diff --git a/Documentation/git-bisect.txt b/Documentation/git-bisect.txt
index 2bb9a57..bdd915a 100644
--- a/Documentation/git-bisect.txt
+++ b/Documentation/git-bisect.txt
@@ -18,8 +18,8 @@ on the subcommand:
git bisect start [--term-{old,good}=<term> --term-{new,bad}=<term>]
[--no-checkout] [<bad> [<good>...]] [--] [<paths>...]
- git bisect (bad|new) [<rev>]
- git bisect (good|old) [<rev>...]
+ git bisect (bad|new|<term-new>) [<rev>]
+ git bisect (good|old|<term-old>) [<rev>...]
git bisect terms [--term-good | --term-bad]
git bisect skip [(<rev>|<range>)...]
git bisect reset [<commit>]
diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt
index 0ecea6e..71f32f3 100644
--- a/Documentation/git-grep.txt
+++ b/Documentation/git-grep.txt
@@ -26,6 +26,7 @@ SYNOPSIS
[--threads <num>]
[-f <file>] [-e] <pattern>
[--and|--or|--not|(|)|-e <pattern>...]
+ [--recurse-submodules] [--parent-basename <basename>]
[ [--[no-]exclude-standard] [--cached | --no-index | --untracked] | <tree>...]
[--] [<pathspec>...]
@@ -88,6 +89,19 @@ OPTIONS
mechanism. Only useful when searching files in the current directory
with `--no-index`.
+--recurse-submodules::
+ Recursively search in each submodule that has been initialized and
+ checked out in the repository. When used in combination with the
+ <tree> option the prefix of all submodule output will be the name of
+ the parent project's <tree> object.
+
+--parent-basename <basename>::
+ For internal use only. In order to produce uniform output with the
+ --recurse-submodules option, this option can be used to provide the
+ basename of a parent's <tree> object to a submodule so the submodule
+ can prefix its output with the parent's name rather than the SHA1 of
+ the submodule.
+
-a::
--text::
Process binary files as if they were text.
diff --git a/Documentation/git-gui.txt b/Documentation/git-gui.txt
index c1a3e8b..5f93f80 100644
--- a/Documentation/git-gui.txt
+++ b/Documentation/git-gui.txt
@@ -35,7 +35,7 @@ blame::
browser::
Start a tree browser showing all files in the specified
- commit (or `HEAD` by default). Files selected through the
+ commit. Files selected through the
browser are opened in the blame viewer.
citool::
diff --git a/Documentation/git-p4.txt b/Documentation/git-p4.txt
index bae862d..7436c64 100644
--- a/Documentation/git-p4.txt
+++ b/Documentation/git-p4.txt
@@ -479,6 +479,8 @@ git-p4.client::
git-p4.retries::
Specifies the number of times to retry a p4 command (notably,
'p4 sync') if the network times out. The default value is 3.
+ Set the value to 0 to disable retries or if your p4 version
+ does not support retries (pre 2012.2).
Clone and sync variables
~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/Documentation/git-relink.txt b/Documentation/git-relink.txt
deleted file mode 100644
index 3b33c99..0000000
--- 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-rev-parse.txt b/Documentation/git-rev-parse.txt
index b6c6326..7241e96 100644
--- a/Documentation/git-rev-parse.txt
+++ b/Documentation/git-rev-parse.txt
@@ -91,7 +91,8 @@ repository. For example:
----
prefix=$(git rev-parse --show-prefix)
cd "$(git rev-parse --show-toplevel)"
-eval "set -- $(git rev-parse --sq --prefix "$prefix" "$@")"
+# rev-parse provides the -- needed for 'set'
+eval "set $(git rev-parse --sq --prefix "$prefix" -- "$@")"
----
--verify::
diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt
index 76cfe40..8e70c5b 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
-----------
@@ -101,8 +101,8 @@ OPTIONS
multiple times, in which case the last key becomes the primary
key. Also supports "version:refname" or "v:refname" (tag
names are treated as versions). The "version:refname" sort
- order can also be affected by the
- "versionsort.prereleaseSuffix" configuration variable.
+ order can also be affected by the "versionsort.suffix"
+ configuration variable.
The keys supported are the same as those in `git for-each-ref`.
Sort order defaults to the value configured for the `tag.sort`
variable if it exists, or lexicographic order otherwise. See
diff --git a/Documentation/git-verify-tag.txt b/Documentation/git-verify-tag.txt
index d590edc..0b8075d 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/git.txt b/Documentation/git.txt
index ba222f6..4f208fa 100644
--- a/Documentation/git.txt
+++ b/Documentation/git.txt
@@ -44,9 +44,10 @@ unreleased) version of Git, that is available from the 'master'
branch of the `git.git` repository.
Documentation for older releases are available here:
-* link:v2.11.0/git.html[documentation for release 2.11]
+* link:v2.11.1/git.html[documentation for release 2.11.1]
* release notes for
+ link:RelNotes/2.11.1.txt[2.11.1],
link:RelNotes/2.11.0.txt[2.11].
* link:v2.10.2/git.html[documentation for release 2.10.2]
diff --git a/Documentation/giteveryday.txt b/Documentation/giteveryday.txt
index 35473ad..10c8ff9 100644
--- a/Documentation/giteveryday.txt
+++ b/Documentation/giteveryday.txt
@@ -307,9 +307,16 @@ master or exposed as a part of a stable branch.
<9> backport a critical fix.
<10> create a signed tag.
<11> make sure master was not accidentally rewound beyond that
-already pushed out. `ko` shorthand points at the Git maintainer's
+already pushed out.
+<12> In the output from `git show-branch`, `master` should have
+everything `ko/master` has, and `next` should have
+everything `ko/next` has, etc.
+<13> push out the bleeding edge, together with new tags that point
+into the pushed history.
+
+In this example, the `ko` shorthand points at the Git maintainer's
repository at kernel.org, and looks like this:
-+
+
------------
(in .git/config)
[remote "ko"]
@@ -320,12 +327,6 @@ repository at kernel.org, and looks like this:
push = +refs/heads/pu
push = refs/heads/maint
------------
-+
-<12> In the output from `git show-branch`, `master` should have
-everything `ko/master` has, and `next` should have
-everything `ko/next` has, etc.
-<13> push out the bleeding edge, together with new tags that point
-into the pushed history.
Repository Administration[[ADMINISTRATION]]
diff --git a/Documentation/gitk.txt b/Documentation/gitk.txt
index e382dd9..ca96c28 100644
--- a/Documentation/gitk.txt
+++ b/Documentation/gitk.txt
@@ -178,19 +178,21 @@ used by default. If '$XDG_CONFIG_HOME' is not set it defaults to
History
-------
Gitk was the first graphical repository browser. It's written in
-tcl/tk and started off in a separate repository but was later merged
-into the main Git repository.
+tcl/tk.
+'gitk' is actually maintained as an independent project, but stable
+versions are distributed as part of the Git suite for the convenience
+of end users.
+
+gitk-git/ comes from Paul Mackerras's gitk project:
+
+ git://ozlabs.org/~paulus/gitk
SEE ALSO
--------
'qgit(1)'::
A repository browser written in C++ using Qt.
-'gitview(1)'::
- A repository browser written in Python using Gtk. It's based on
- 'bzrk(1)' and distributed in the contrib area of the Git repository.
-
'tig(1)'::
A minimal repository browser and Git tool output highlighter written
in C using Ncurses.
diff --git a/Documentation/technical/api-hashmap.txt b/Documentation/technical/api-hashmap.txt
index 28f5a8b..a3f020c 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 adbdbf5..0000000
--- 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/technical/api-setup.txt b/Documentation/technical/api-setup.txt
index 540e455..eb1fa98 100644
--- a/Documentation/technical/api-setup.txt
+++ b/Documentation/technical/api-setup.txt
@@ -27,8 +27,6 @@ parse_pathspec(). This function takes several arguments:
- prefix and args come from cmd_* functions
-get_pathspec() is obsolete and should never be used in new code.
-
parse_pathspec() helps catch unsupported features and reject them
politely. At a lower level, different pathspec-related functions may
not support the same set of features. Such pathspec-sensitive
diff --git a/Documentation/texi.xsl b/Documentation/texi.xsl
new file mode 100644
index 0000000..0f8ff07
--- /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>
diff --git a/Documentation/user-manual.txt b/Documentation/user-manual.txt
index 5e07454..bc29298 100644
--- a/Documentation/user-manual.txt
+++ b/Documentation/user-manual.txt
@@ -4395,6 +4395,10 @@ itself!
Git Glossary
============
+[[git-explained]]
+Git explained
+-------------
+
include::glossary-content.txt[]
[[git-quick-start]]
@@ -4636,6 +4640,10 @@ $ git gc
Appendix B: Notes and todo list for this manual
===============================================
+[[todo-list]]
+Todo list
+---------
+
This is a work in progress.
The basic requirements:
diff --git a/Makefile b/Makefile
index d861bd9..8e4081e 100644
--- a/Makefile
+++ b/Makefile
@@ -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
@@ -1816,7 +1829,7 @@ $(SCRIPT_LIB) : % : %.sh GIT-SCRIPT-DEFINES
git.res: git.rc GIT-VERSION-FILE
$(QUIET_RC)$(RC) \
$(join -DMAJOR= -DMINOR=, $(wordlist 1,2,$(subst -, ,$(subst ., ,$(GIT_VERSION))))) \
- -DGIT_VERSION="\\\"$(GIT_VERSION)\\\"" $< -o $@
+ -DGIT_VERSION="\\\"$(GIT_VERSION)\\\"" -i $< -o $@
# This makes sure we depend on the NO_PERL setting itself.
$(SCRIPT_PERL_GEN): GIT-BUILD-OPTIONS
@@ -2046,7 +2059,7 @@ git-%$X: %.o GIT-LDFLAGS $(GITLIBS)
git-imap-send$X: imap-send.o $(IMAP_SEND_BUILDDEPS) GIT-LDFLAGS $(GITLIBS)
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
- $(LIBS) $(IMAP_SEND_LDFLAGS)
+ $(IMAP_SEND_LDFLAGS) $(LIBS)
git-http-fetch$X: http.o http-walker.o http-fetch.o GIT-LDFLAGS $(GITLIBS)
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
diff --git a/abspath.c b/abspath.c
index 2825de8..2f0c26e 100644
--- a/abspath.c
+++ b/abspath.c
@@ -11,46 +11,83 @@ int is_directory(const char *path)
return (!stat(path, &st) && S_ISDIR(st.st_mode));
}
+/* removes the last path component from 'path' except if 'path' is root */
+static void strip_last_component(struct strbuf *path)
+{
+ size_t offset = offset_1st_component(path->buf);
+ size_t len = path->len;
+
+ /* Find start of the last component */
+ while (offset < len && !is_dir_sep(path->buf[len - 1]))
+ len--;
+ /* Skip sequences of multiple path-separators */
+ while (offset < len && is_dir_sep(path->buf[len - 1]))
+ len--;
+
+ strbuf_setlen(path, len);
+}
+
+/* get (and remove) the next component in 'remaining' and place it in 'next' */
+static void get_next_component(struct strbuf *next, struct strbuf *remaining)
+{
+ char *start = NULL;
+ char *end = NULL;
+
+ strbuf_reset(next);
+
+ /* look for the next component */
+ /* Skip sequences of multiple path-separators */
+ for (start = remaining->buf; is_dir_sep(*start); start++)
+ ; /* nothing */
+ /* Find end of the path component */
+ for (end = start; *end && !is_dir_sep(*end); end++)
+ ; /* nothing */
+
+ strbuf_add(next, start, end - start);
+ /* remove the component from 'remaining' */
+ strbuf_remove(remaining, 0, end - remaining->buf);
+}
+
+/* copies root part from remaining to resolved, canonicalizing it on the way */
+static void get_root_part(struct strbuf *resolved, struct strbuf *remaining)
+{
+ int offset = offset_1st_component(remaining->buf);
+
+ strbuf_reset(resolved);
+ strbuf_add(resolved, remaining->buf, offset);
+#ifdef GIT_WINDOWS_NATIVE
+ convert_slashes(resolved->buf);
+#endif
+ strbuf_remove(remaining, 0, offset);
+}
+
/* We allow "recursive" symbolic links. Only within reason, though. */
-#define MAXDEPTH 5
+#ifndef MAXSYMLINKS
+#define MAXSYMLINKS 32
+#endif
/*
* Return the real path (i.e., absolute path, with symlinks resolved
* and extra slashes removed) equivalent to the specified path. (If
* you want an absolute path but don't mind links, use
- * absolute_path().) The return value is a pointer to a static
- * buffer.
+ * absolute_path().) Places the resolved realpath in the provided strbuf.
*
- * The input and all intermediate paths must be shorter than MAX_PATH.
* The directory part of path (i.e., everything up to the last
* dir_sep) must denote a valid, existing directory, but the last
* component need not exist. If die_on_error is set, then die with an
* informative error message if there is a problem. Otherwise, return
* NULL on errors (without generating any output).
- *
- * If path is our buffer, then return path, as it's already what the
- * user wants.
*/
-static const char *real_path_internal(const char *path, int die_on_error)
+char *strbuf_realpath(struct strbuf *resolved, const char *path,
+ int die_on_error)
{
- static struct strbuf sb = STRBUF_INIT;
+ struct strbuf remaining = STRBUF_INIT;
+ struct strbuf next = STRBUF_INIT;
+ struct strbuf symlink = STRBUF_INIT;
char *retval = NULL;
-
- /*
- * If we have to temporarily chdir(), store the original CWD
- * here so that we can chdir() back to it at the end of the
- * function:
- */
- struct strbuf cwd = STRBUF_INIT;
-
- int depth = MAXDEPTH;
- char *last_elem = NULL;
+ int num_symlinks = 0;
struct stat st;
- /* We've already done it */
- if (path == sb.buf)
- return path;
-
if (!*path) {
if (die_on_error)
die("The empty string is not a valid path");
@@ -58,86 +95,136 @@ static const char *real_path_internal(const char *path, int die_on_error)
goto error_out;
}
- strbuf_reset(&sb);
- strbuf_addstr(&sb, path);
-
- while (depth--) {
- if (!is_directory(sb.buf)) {
- char *last_slash = find_last_dir_sep(sb.buf);
- if (last_slash) {
- last_elem = xstrdup(last_slash + 1);
- strbuf_setlen(&sb, last_slash - sb.buf + 1);
- } else {
- last_elem = xmemdupz(sb.buf, sb.len);
- strbuf_reset(&sb);
- }
+ strbuf_addstr(&remaining, path);
+ get_root_part(resolved, &remaining);
+
+ if (!resolved->len) {
+ /* relative path; can use CWD as the initial resolved path */
+ if (strbuf_getcwd(resolved)) {
+ if (die_on_error)
+ die_errno("unable to get current working directory");
+ else
+ goto error_out;
}
+ }
- if (sb.len) {
- if (!cwd.len && strbuf_getcwd(&cwd)) {
+ /* Iterate over the remaining path components */
+ while (remaining.len > 0) {
+ get_next_component(&next, &remaining);
+
+ if (next.len == 0) {
+ continue; /* empty component */
+ } else if (next.len == 1 && !strcmp(next.buf, ".")) {
+ continue; /* '.' component */
+ } else if (next.len == 2 && !strcmp(next.buf, "..")) {
+ /* '..' component; strip the last path component */
+ strip_last_component(resolved);
+ continue;
+ }
+
+ /* append the next component and resolve resultant path */
+ if (!is_dir_sep(resolved->buf[resolved->len - 1]))
+ strbuf_addch(resolved, '/');
+ strbuf_addbuf(resolved, &next);
+
+ if (lstat(resolved->buf, &st)) {
+ /* error out unless this was the last component */
+ if (errno != ENOENT || remaining.len) {
if (die_on_error)
- die_errno("Could not get current working directory");
+ die_errno("Invalid path '%s'",
+ resolved->buf);
else
goto error_out;
}
+ } else if (S_ISLNK(st.st_mode)) {
+ ssize_t len;
+ strbuf_reset(&symlink);
+
+ if (num_symlinks++ > MAXSYMLINKS) {
+ errno = ELOOP;
- if (chdir(sb.buf)) {
if (die_on_error)
- die_errno("Could not switch to '%s'",
- sb.buf);
+ die("More than %d nested symlinks "
+ "on path '%s'", MAXSYMLINKS, path);
else
goto error_out;
}
- }
- if (strbuf_getcwd(&sb)) {
- if (die_on_error)
- die_errno("Could not get current working directory");
- else
- goto error_out;
- }
- if (last_elem) {
- if (sb.len && !is_dir_sep(sb.buf[sb.len - 1]))
- strbuf_addch(&sb, '/');
- strbuf_addstr(&sb, last_elem);
- free(last_elem);
- last_elem = NULL;
- }
-
- if (!lstat(sb.buf, &st) && S_ISLNK(st.st_mode)) {
- struct strbuf next_sb = STRBUF_INIT;
- ssize_t len = strbuf_readlink(&next_sb, sb.buf, 0);
+ len = strbuf_readlink(&symlink, resolved->buf,
+ st.st_size);
if (len < 0) {
if (die_on_error)
die_errno("Invalid symlink '%s'",
- sb.buf);
+ resolved->buf);
else
goto error_out;
}
- strbuf_swap(&sb, &next_sb);
- strbuf_release(&next_sb);
- } else
- break;
+
+ if (is_absolute_path(symlink.buf)) {
+ /* absolute symlink; set resolved to root */
+ get_root_part(resolved, &symlink);
+ } else {
+ /*
+ * relative symlink
+ * strip off the last component since it will
+ * be replaced with the contents of the symlink
+ */
+ strip_last_component(resolved);
+ }
+
+ /*
+ * if there are still remaining components to resolve
+ * then append them to symlink
+ */
+ if (remaining.len) {
+ strbuf_addch(&symlink, '/');
+ strbuf_addbuf(&symlink, &remaining);
+ }
+
+ /*
+ * use the symlink as the remaining components that
+ * need to be resloved
+ */
+ strbuf_swap(&symlink, &remaining);
+ }
}
- retval = sb.buf;
+ retval = resolved->buf;
+
error_out:
- free(last_elem);
- if (cwd.len && chdir(cwd.buf))
- die_errno("Could not change back to '%s'", cwd.buf);
- strbuf_release(&cwd);
+ strbuf_release(&remaining);
+ strbuf_release(&next);
+ strbuf_release(&symlink);
+
+ if (!retval)
+ strbuf_reset(resolved);
return retval;
}
const char *real_path(const char *path)
{
- return real_path_internal(path, 1);
+ static struct strbuf realpath = STRBUF_INIT;
+ return strbuf_realpath(&realpath, path, 1);
}
const char *real_path_if_valid(const char *path)
{
- return real_path_internal(path, 0);
+ static struct strbuf realpath = STRBUF_INIT;
+ return strbuf_realpath(&realpath, path, 0);
+}
+
+char *real_pathdup(const char *path)
+{
+ struct strbuf realpath = STRBUF_INIT;
+ char *retval = NULL;
+
+ if (strbuf_realpath(&realpath, path, 0))
+ retval = strbuf_detach(&realpath, NULL);
+
+ strbuf_release(&realpath);
+
+ return retval;
}
/*
@@ -152,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
diff --git a/archive-zip.c b/archive-zip.c
index 9db4735..b429a8d 100644
--- a/archive-zip.c
+++ b/archive-zip.c
@@ -554,11 +554,18 @@ static void dos_time(time_t *time, int *dos_date, int *dos_time)
*dos_time = t->tm_sec / 2 + t->tm_min * 32 + t->tm_hour * 2048;
}
+static int archive_zip_config(const char *var, const char *value, void *data)
+{
+ return userdiff_config(var, value);
+}
+
static int write_zip_archive(const struct archiver *ar,
struct archiver_args *args)
{
int err;
+ git_config(archive_zip_config, NULL);
+
dos_time(&args->time, &zip_date, &zip_time);
zip_dir = xmalloc(ZIP_DIRECTORY_MIN_SIZE);
diff --git a/builtin.h b/builtin.h
index b9122bc..67f8051 100644
--- a/builtin.h
+++ b/builtin.h
@@ -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/blame.c b/builtin/blame.c
index ab54a5c..126b8c9 100644
--- a/builtin/blame.c
+++ b/builtin/blame.c
@@ -1700,13 +1700,23 @@ static void get_commit_info(struct commit *commit,
}
/*
+ * Write out any suspect information which depends on the path. This must be
+ * handled separately from emit_one_suspect_detail(), because a given commit
+ * may have changes in multiple paths. So this needs to appear each time
+ * we mention a new group.
+ *
* To allow LF and other nonportable characters in pathnames,
* they are c-style quoted as needed.
*/
-static void write_filename_info(const char *path)
+static void write_filename_info(struct origin *suspect)
{
+ if (suspect->previous) {
+ struct origin *prev = suspect->previous;
+ printf("previous %s ", oid_to_hex(&prev->commit->object.oid));
+ write_name_quoted(prev->path, stdout, '\n');
+ }
printf("filename ");
- write_name_quoted(path, stdout, '\n');
+ write_name_quoted(suspect->path, stdout, '\n');
}
/*
@@ -1735,11 +1745,6 @@ static int emit_one_suspect_detail(struct origin *suspect, int repeat)
printf("summary %s\n", ci.summary.buf);
if (suspect->commit->object.flags & UNINTERESTING)
printf("boundary\n");
- if (suspect->previous) {
- struct origin *prev = suspect->previous;
- printf("previous %s ", oid_to_hex(&prev->commit->object.oid));
- write_name_quoted(prev->path, stdout, '\n');
- }
commit_info_destroy(&ci);
@@ -1760,7 +1765,7 @@ static void found_guilty_entry(struct blame_entry *ent,
oid_to_hex(&suspect->commit->object.oid),
ent->s_lno + 1, ent->lno + 1, ent->num_lines);
emit_one_suspect_detail(suspect, 0);
- write_filename_info(suspect->path);
+ write_filename_info(suspect);
maybe_flush_or_die(stdout, "stdout");
}
pi->blamed_lines += ent->num_lines;
@@ -1884,7 +1889,7 @@ static void emit_porcelain_details(struct origin *suspect, int repeat)
{
if (emit_one_suspect_detail(suspect, repeat) ||
(suspect->commit->object.flags & MORE_THAN_ONE_PATH))
- write_filename_info(suspect->path);
+ write_filename_info(suspect);
}
static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent,
@@ -2655,9 +2660,11 @@ parse_done:
} else if (show_progress < 0)
show_progress = isatty(2);
- if (0 < abbrev)
+ if (0 < abbrev && abbrev < GIT_SHA1_HEXSZ)
/* one more abbrev length is needed for the boundary commit */
abbrev++;
+ else if (!abbrev)
+ abbrev = GIT_SHA1_HEXSZ;
if (revs_file && read_ancestry(revs_file))
die_errno("reading graft file '%s' failed", revs_file);
diff --git a/builtin/clone.c b/builtin/clone.c
index 5ef8192..3f63edb 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 711f96c..2de5f6c 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 0000000..b5e85ab
--- /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 f1570e3..b5ad09d 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 f01b81e..1a5cacc 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/gc.c b/builtin/gc.c
index 069950d..331f219 100644
--- a/builtin/gc.c
+++ b/builtin/gc.c
@@ -191,6 +191,11 @@ static void add_repack_all_option(void)
}
}
+static void add_repack_incremental_option(void)
+{
+ argv_array_push(&repack, "--no-write-bitmap-index");
+}
+
static int need_to_gc(void)
{
/*
@@ -208,7 +213,9 @@ static int need_to_gc(void)
*/
if (too_many_packs())
add_repack_all_option();
- else if (!too_many_loose_objects())
+ else if (too_many_loose_objects())
+ add_repack_incremental_option();
+ else
return 0;
if (run_hook_le(NULL, "pre-auto-gc", NULL))
diff --git a/builtin/grep.c b/builtin/grep.c
index 8887b6a..2c727ef 100644
--- a/builtin/grep.c
+++ b/builtin/grep.c
@@ -18,12 +18,22 @@
#include "quote.h"
#include "dir.h"
#include "pathspec.h"
+#include "submodule.h"
+#include "submodule-config.h"
static char const * const grep_usage[] = {
N_("git grep [<options>] [-e] <pattern> [<rev>...] [[--] <path>...]"),
NULL
};
+static const char *super_prefix;
+static int recurse_submodules;
+static struct argv_array submodule_options = ARGV_ARRAY_INIT;
+static const char *parent_basename;
+
+static int grep_submodule_launch(struct grep_opt *opt,
+ const struct grep_source *gs);
+
#define GREP_NUM_THREADS_DEFAULT 8
static int num_threads;
@@ -174,7 +184,10 @@ static void *run(void *arg)
break;
opt->output_priv = w;
- hit |= grep_source(opt, &w->source);
+ if (w->source.type == GREP_SOURCE_SUBMODULE)
+ hit |= grep_submodule_launch(opt, &w->source);
+ else
+ hit |= grep_source(opt, &w->source);
grep_source_clear_data(&w->source);
work_done(w);
}
@@ -300,6 +313,10 @@ static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1,
if (opt->relative && opt->prefix_length) {
quote_path_relative(filename + tree_name_len, opt->prefix, &pathbuf);
strbuf_insert(&pathbuf, 0, filename, tree_name_len);
+ } else if (super_prefix) {
+ strbuf_add(&pathbuf, filename, tree_name_len);
+ strbuf_addstr(&pathbuf, super_prefix);
+ strbuf_addstr(&pathbuf, filename + tree_name_len);
} else {
strbuf_addstr(&pathbuf, filename);
}
@@ -328,10 +345,13 @@ static int grep_file(struct grep_opt *opt, const char *filename)
{
struct strbuf buf = STRBUF_INIT;
- if (opt->relative && opt->prefix_length)
+ if (opt->relative && opt->prefix_length) {
quote_path_relative(filename, opt->prefix, &buf);
- else
+ } else {
+ if (super_prefix)
+ strbuf_addstr(&buf, super_prefix);
strbuf_addstr(&buf, filename);
+ }
#ifndef NO_PTHREADS
if (num_threads) {
@@ -378,31 +398,310 @@ static void run_pager(struct grep_opt *opt, const char *prefix)
exit(status);
}
-static int grep_cache(struct grep_opt *opt, const struct pathspec *pathspec, int cached)
+static void compile_submodule_options(const struct grep_opt *opt,
+ const struct pathspec *pathspec,
+ int cached, int untracked,
+ int opt_exclude, int use_index,
+ int pattern_type_arg)
+{
+ struct grep_pat *pattern;
+ int i;
+
+ if (recurse_submodules)
+ argv_array_push(&submodule_options, "--recurse-submodules");
+
+ if (cached)
+ argv_array_push(&submodule_options, "--cached");
+ if (!use_index)
+ argv_array_push(&submodule_options, "--no-index");
+ if (untracked)
+ argv_array_push(&submodule_options, "--untracked");
+ if (opt_exclude > 0)
+ argv_array_push(&submodule_options, "--exclude-standard");
+
+ if (opt->invert)
+ argv_array_push(&submodule_options, "-v");
+ if (opt->ignore_case)
+ argv_array_push(&submodule_options, "-i");
+ if (opt->word_regexp)
+ argv_array_push(&submodule_options, "-w");
+ switch (opt->binary) {
+ case GREP_BINARY_NOMATCH:
+ argv_array_push(&submodule_options, "-I");
+ break;
+ case GREP_BINARY_TEXT:
+ argv_array_push(&submodule_options, "-a");
+ break;
+ default:
+ break;
+ }
+ if (opt->allow_textconv)
+ argv_array_push(&submodule_options, "--textconv");
+ if (opt->max_depth != -1)
+ argv_array_pushf(&submodule_options, "--max-depth=%d",
+ opt->max_depth);
+ if (opt->linenum)
+ argv_array_push(&submodule_options, "-n");
+ if (!opt->pathname)
+ argv_array_push(&submodule_options, "-h");
+ if (!opt->relative)
+ argv_array_push(&submodule_options, "--full-name");
+ if (opt->name_only)
+ argv_array_push(&submodule_options, "-l");
+ if (opt->unmatch_name_only)
+ argv_array_push(&submodule_options, "-L");
+ if (opt->null_following_name)
+ argv_array_push(&submodule_options, "-z");
+ if (opt->count)
+ argv_array_push(&submodule_options, "-c");
+ if (opt->file_break)
+ argv_array_push(&submodule_options, "--break");
+ if (opt->heading)
+ argv_array_push(&submodule_options, "--heading");
+ if (opt->pre_context)
+ argv_array_pushf(&submodule_options, "--before-context=%d",
+ opt->pre_context);
+ if (opt->post_context)
+ argv_array_pushf(&submodule_options, "--after-context=%d",
+ opt->post_context);
+ if (opt->funcname)
+ argv_array_push(&submodule_options, "-p");
+ if (opt->funcbody)
+ argv_array_push(&submodule_options, "-W");
+ if (opt->all_match)
+ argv_array_push(&submodule_options, "--all-match");
+ if (opt->debug)
+ argv_array_push(&submodule_options, "--debug");
+ if (opt->status_only)
+ argv_array_push(&submodule_options, "-q");
+
+ switch (pattern_type_arg) {
+ case GREP_PATTERN_TYPE_BRE:
+ argv_array_push(&submodule_options, "-G");
+ break;
+ case GREP_PATTERN_TYPE_ERE:
+ argv_array_push(&submodule_options, "-E");
+ break;
+ case GREP_PATTERN_TYPE_FIXED:
+ argv_array_push(&submodule_options, "-F");
+ break;
+ case GREP_PATTERN_TYPE_PCRE:
+ argv_array_push(&submodule_options, "-P");
+ break;
+ case GREP_PATTERN_TYPE_UNSPECIFIED:
+ break;
+ }
+
+ for (pattern = opt->pattern_list; pattern != NULL;
+ pattern = pattern->next) {
+ switch (pattern->token) {
+ case GREP_PATTERN:
+ argv_array_pushf(&submodule_options, "-e%s",
+ pattern->pattern);
+ break;
+ case GREP_AND:
+ case GREP_OPEN_PAREN:
+ case GREP_CLOSE_PAREN:
+ case GREP_NOT:
+ case GREP_OR:
+ argv_array_push(&submodule_options, pattern->pattern);
+ break;
+ /* BODY and HEAD are not used by git-grep */
+ case GREP_PATTERN_BODY:
+ case GREP_PATTERN_HEAD:
+ break;
+ }
+ }
+
+ /*
+ * Limit number of threads for child process to use.
+ * This is to prevent potential fork-bomb behavior of git-grep as each
+ * submodule process has its own thread pool.
+ */
+ argv_array_pushf(&submodule_options, "--threads=%d",
+ (num_threads + 1) / 2);
+
+ /* Add Pathspecs */
+ argv_array_push(&submodule_options, "--");
+ for (i = 0; i < pathspec->nr; i++)
+ argv_array_push(&submodule_options,
+ pathspec->items[i].original);
+}
+
+/*
+ * Launch child process to grep contents of a submodule
+ */
+static int grep_submodule_launch(struct grep_opt *opt,
+ const struct grep_source *gs)
+{
+ struct child_process cp = CHILD_PROCESS_INIT;
+ int status, i;
+ const char *end_of_base;
+ const char *name;
+ struct work_item *w = opt->output_priv;
+
+ end_of_base = strchr(gs->name, ':');
+ if (gs->identifier && end_of_base)
+ name = end_of_base + 1;
+ else
+ name = gs->name;
+
+ prepare_submodule_repo_env(&cp.env_array);
+ argv_array_push(&cp.env_array, GIT_DIR_ENVIRONMENT);
+
+ /* Add super prefix */
+ argv_array_pushf(&cp.args, "--super-prefix=%s%s/",
+ super_prefix ? super_prefix : "",
+ name);
+ argv_array_push(&cp.args, "grep");
+
+ /*
+ * Add basename of parent project
+ * When performing grep on a tree object the filename is prefixed
+ * with the object's name: 'tree-name:filename'. In order to
+ * provide uniformity of output we want to pass the name of the
+ * parent project's object name to the submodule so the submodule can
+ * prefix its output with the parent's name and not its own SHA1.
+ */
+ if (gs->identifier && end_of_base)
+ argv_array_pushf(&cp.args, "--parent-basename=%.*s",
+ (int) (end_of_base - gs->name),
+ gs->name);
+
+ /* Add options */
+ for (i = 0; i < submodule_options.argc; i++) {
+ /*
+ * If there is a tree identifier for the submodule, add the
+ * rev after adding the submodule options but before the
+ * pathspecs. To do this we listen for the '--' and insert the
+ * sha1 before pushing the '--' onto the child process argv
+ * array.
+ */
+ if (gs->identifier &&
+ !strcmp("--", submodule_options.argv[i])) {
+ argv_array_push(&cp.args, sha1_to_hex(gs->identifier));
+ }
+
+ argv_array_push(&cp.args, submodule_options.argv[i]);
+ }
+
+ cp.git_cmd = 1;
+ cp.dir = gs->path;
+
+ /*
+ * Capture output to output buffer and check the return code from the
+ * child process. A '0' indicates a hit, a '1' indicates no hit and
+ * anything else is an error.
+ */
+ status = capture_command(&cp, &w->out, 0);
+ if (status && (status != 1)) {
+ /* flush the buffer */
+ write_or_die(1, w->out.buf, w->out.len);
+ die("process for submodule '%s' failed with exit code: %d",
+ gs->name, status);
+ }
+
+ /* invert the return code to make a hit equal to 1 */
+ return !status;
+}
+
+/*
+ * Prep grep structures for a submodule grep
+ * sha1: the sha1 of the submodule or NULL if using the working tree
+ * filename: name of the submodule including tree name of parent
+ * path: location of the submodule
+ */
+static int grep_submodule(struct grep_opt *opt, const unsigned char *sha1,
+ const char *filename, const char *path)
+{
+ if (!is_submodule_initialized(path))
+ return 0;
+ if (!is_submodule_populated(path)) {
+ /*
+ * If searching history, check for the presense of the
+ * submodule's gitdir before skipping the submodule.
+ */
+ if (sha1) {
+ const struct submodule *sub =
+ submodule_from_path(null_sha1, path);
+ if (sub)
+ path = git_path("modules/%s", sub->name);
+
+ if (!(is_directory(path) && is_git_directory(path)))
+ return 0;
+ } else {
+ return 0;
+ }
+ }
+
+#ifndef NO_PTHREADS
+ if (num_threads) {
+ add_work(opt, GREP_SOURCE_SUBMODULE, filename, path, sha1);
+ return 0;
+ } else
+#endif
+ {
+ struct work_item w;
+ int hit;
+
+ grep_source_init(&w.source, GREP_SOURCE_SUBMODULE,
+ filename, path, sha1);
+ strbuf_init(&w.out, 0);
+ opt->output_priv = &w;
+ hit = grep_submodule_launch(opt, &w.source);
+
+ write_or_die(1, w.out.buf, w.out.len);
+
+ grep_source_clear(&w.source);
+ strbuf_release(&w.out);
+ return hit;
+ }
+}
+
+static int grep_cache(struct grep_opt *opt, const struct pathspec *pathspec,
+ int cached)
{
int hit = 0;
int nr;
+ struct strbuf name = STRBUF_INIT;
+ int name_base_len = 0;
+ if (super_prefix) {
+ name_base_len = strlen(super_prefix);
+ strbuf_addstr(&name, super_prefix);
+ }
+
read_cache();
for (nr = 0; nr < active_nr; nr++) {
const struct cache_entry *ce = active_cache[nr];
- if (!S_ISREG(ce->ce_mode))
- continue;
- if (!ce_path_match(ce, pathspec, NULL))
+ strbuf_setlen(&name, name_base_len);
+ strbuf_addstr(&name, ce->name);
+
+ if (S_ISREG(ce->ce_mode) &&
+ match_pathspec(pathspec, name.buf, name.len, 0, NULL,
+ S_ISDIR(ce->ce_mode) ||
+ S_ISGITLINK(ce->ce_mode))) {
+ /*
+ * If CE_VALID is on, we assume worktree file and its
+ * cache entry are identical, even if worktree file has
+ * been modified, so use cache version instead
+ */
+ if (cached || (ce->ce_flags & CE_VALID) ||
+ ce_skip_worktree(ce)) {
+ if (ce_stage(ce) || ce_intent_to_add(ce))
+ continue;
+ hit |= grep_sha1(opt, ce->oid.hash, ce->name,
+ 0, ce->name);
+ } else {
+ hit |= grep_file(opt, ce->name);
+ }
+ } else if (recurse_submodules && S_ISGITLINK(ce->ce_mode) &&
+ submodule_path_match(pathspec, name.buf, NULL)) {
+ hit |= grep_submodule(opt, NULL, ce->name, ce->name);
+ } else {
continue;
- /*
- * If CE_VALID is on, we assume worktree file and its cache entry
- * are identical, even if worktree file has been modified, so use
- * cache version instead
- */
- if (cached || (ce->ce_flags & CE_VALID) || ce_skip_worktree(ce)) {
- if (ce_stage(ce) || ce_intent_to_add(ce))
- continue;
- hit |= grep_sha1(opt, ce->oid.hash, ce->name, 0,
- ce->name);
}
- else
- hit |= grep_file(opt, ce->name);
+
if (ce_stage(ce)) {
do {
nr++;
@@ -413,6 +712,8 @@ static int grep_cache(struct grep_opt *opt, const struct pathspec *pathspec, int
if (hit && opt->status_only)
break;
}
+
+ strbuf_release(&name);
return hit;
}
@@ -424,12 +725,22 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec,
enum interesting match = entry_not_interesting;
struct name_entry entry;
int old_baselen = base->len;
+ struct strbuf name = STRBUF_INIT;
+ int name_base_len = 0;
+ if (super_prefix) {
+ strbuf_addstr(&name, super_prefix);
+ name_base_len = name.len;
+ }
while (tree_entry(tree, &entry)) {
int te_len = tree_entry_len(&entry);
if (match != all_entries_interesting) {
- match = tree_entry_interesting(&entry, base, tn_len, pathspec);
+ strbuf_addstr(&name, base->buf + tn_len);
+ match = tree_entry_interesting(&entry, &name,
+ 0, pathspec);
+ strbuf_setlen(&name, name_base_len);
+
if (match == all_entries_not_interesting)
break;
if (match == entry_not_interesting)
@@ -441,8 +752,7 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec,
if (S_ISREG(entry.mode)) {
hit |= grep_sha1(opt, entry.oid->hash, base->buf, tn_len,
check_attr ? base->buf + tn_len : NULL);
- }
- else if (S_ISDIR(entry.mode)) {
+ } else if (S_ISDIR(entry.mode)) {
enum object_type type;
struct tree_desc sub;
void *data;
@@ -458,12 +768,18 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec,
hit |= grep_tree(opt, pathspec, &sub, base, tn_len,
check_attr);
free(data);
+ } else if (recurse_submodules && S_ISGITLINK(entry.mode)) {
+ hit |= grep_submodule(opt, entry.oid->hash, base->buf,
+ base->buf + tn_len);
}
+
strbuf_setlen(base, old_baselen);
if (hit && opt->status_only)
break;
}
+
+ strbuf_release(&name);
return hit;
}
@@ -487,6 +803,10 @@ static int grep_object(struct grep_opt *opt, const struct pathspec *pathspec,
if (!data)
die(_("unable to read tree (%s)"), oid_to_hex(&obj->oid));
+ /* Use parent's name as base when recursing submodules */
+ if (recurse_submodules && parent_basename)
+ name = parent_basename;
+
len = name ? strlen(name) : 0;
strbuf_init(&base, PATH_MAX + len + 1);
if (len) {
@@ -513,6 +833,12 @@ static int grep_objects(struct grep_opt *opt, const struct pathspec *pathspec,
for (i = 0; i < nr; i++) {
struct object *real_obj;
real_obj = deref_tag(list->objects[i].item, NULL, 0);
+
+ /* load the gitmodules file for this rev */
+ if (recurse_submodules) {
+ submodule_free();
+ gitmodules_config_sha1(real_obj->oid.hash);
+ }
if (grep_object(opt, pathspec, real_obj, list->objects[i].name, list->objects[i].path)) {
hit = 1;
if (opt->status_only)
@@ -651,6 +977,11 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
N_("search in both tracked and untracked files")),
OPT_SET_INT(0, "exclude-standard", &opt_exclude,
N_("ignore files specified via '.gitignore'"), 1),
+ OPT_BOOL(0, "recurse-submodules", &recurse_submodules,
+ N_("recursivley search in each submodule")),
+ OPT_STRING(0, "parent-basename", &parent_basename,
+ N_("basename"),
+ N_("prepend parent project's basename to output")),
OPT_GROUP(""),
OPT_BOOL('v', "invert-match", &opt.invert,
N_("show non-matching lines")),
@@ -755,6 +1086,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
init_grep_defaults();
git_config(grep_cmd_config, NULL);
grep_init(&opt, prefix);
+ super_prefix = get_super_prefix();
/*
* If there is no -- then the paths must exist in the working
@@ -872,6 +1204,13 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
pathspec.max_depth = opt.max_depth;
pathspec.recursive = 1;
+ if (recurse_submodules) {
+ gitmodules_config();
+ compile_submodule_options(&opt, &pathspec, cached, untracked,
+ opt_exclude, use_index,
+ pattern_type_arg);
+ }
+
if (show_in_pager && (cached || list.nr))
die(_("--open-files-in-pager only works on the worktree"));
@@ -895,6 +1234,9 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
}
}
+ if (recurse_submodules && (!use_index || untracked))
+ die(_("option not supported with --recurse-submodules."));
+
if (!show_in_pager && !opt.status_only)
setup_pager();
diff --git a/builtin/init-db.c b/builtin/init-db.c
index 2399b97..76d68fa 100644
--- a/builtin/init-db.c
+++ b/builtin/init-db.c
@@ -338,7 +338,7 @@ int init_db(const char *git_dir, const char *real_git_dir,
{
int reinit;
int exist_ok = flags & INIT_DB_EXIST_OK;
- char *original_git_dir = xstrdup(real_path(git_dir));
+ char *original_git_dir = real_pathdup(git_dir);
if (real_git_dir) {
struct stat st;
@@ -489,7 +489,7 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
argc = parse_options(argc, argv, prefix, init_db_options, init_db_usage, 0);
if (real_git_dir && !is_absolute_path(real_git_dir))
- real_git_dir = xstrdup(real_path(real_git_dir));
+ real_git_dir = real_pathdup(real_git_dir);
if (argc == 1) {
int mkdir_tried = 0;
@@ -560,7 +560,7 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
const char *git_dir_parent = strrchr(git_dir, '/');
if (git_dir_parent) {
char *rel = xstrndup(git_dir, git_dir_parent - git_dir);
- git_work_tree_cfg = xstrdup(real_path(rel));
+ git_work_tree_cfg = real_pathdup(rel);
free(rel);
}
if (!git_work_tree_cfg)
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index 0e30d86..d7ebeb4 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -31,21 +31,18 @@ static const char * const ls_tree_usage[] = {
static int show_recursive(const char *base, int baselen, const char *pathname)
{
- const char **s;
+ int i;
if (ls_options & LS_RECURSIVE)
return 1;
- s = pathspec._raw;
- if (!s)
+ if (!pathspec.nr)
return 0;
- for (;;) {
- const char *spec = *s++;
+ for (i = 0; i < pathspec.nr; i++) {
+ const char *spec = pathspec.items[i].match;
int len, speclen;
- if (!spec)
- return 0;
if (strncmp(base, spec, baselen))
continue;
len = strlen(pathname);
@@ -59,6 +56,7 @@ static int show_recursive(const char *base, int baselen, const char *pathname)
continue;
return 1;
}
+ return 0;
}
static int show_tree(const unsigned char *sha1, struct strbuf *base,
@@ -175,8 +173,8 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
* cannot be lifted until it is converted to use
* match_pathspec() or tree_entry_interesting()
*/
- parse_pathspec(&pathspec, PATHSPEC_GLOB | PATHSPEC_ICASE |
- PATHSPEC_EXCLUDE,
+ parse_pathspec(&pathspec, PATHSPEC_ALL_MAGIC &
+ ~(PATHSPEC_FROMTOP | PATHSPEC_LITERAL),
PATHSPEC_PREFER_CWD,
prefix, argv + 1);
for (i = 0; i < pathspec.nr; i++)
diff --git a/builtin/mv.c b/builtin/mv.c
index 43adf92..61d2003 100644
--- a/builtin/mv.c
+++ b/builtin/mv.c
@@ -4,6 +4,7 @@
* Copyright (C) 2006 Johannes Schindelin
*/
#include "builtin.h"
+#include "pathspec.h"
#include "lockfile.h"
#include "dir.h"
#include "cache-tree.h"
@@ -19,31 +20,42 @@ static const char * const builtin_mv_usage[] = {
#define DUP_BASENAME 1
#define KEEP_TRAILING_SLASH 2
-static const char **internal_copy_pathspec(const char *prefix,
- const char **pathspec,
- int count, unsigned flags)
+static const char **internal_prefix_pathspec(const char *prefix,
+ const char **pathspec,
+ int count, unsigned flags)
{
int i;
const char **result;
+ int prefixlen = prefix ? strlen(prefix) : 0;
ALLOC_ARRAY(result, count + 1);
- COPY_ARRAY(result, pathspec, count);
- result[count] = NULL;
+
+ /* Create an intermediate copy of the pathspec based on the flags */
for (i = 0; i < count; i++) {
- int length = strlen(result[i]);
+ int length = strlen(pathspec[i]);
int to_copy = length;
+ char *it;
while (!(flags & KEEP_TRAILING_SLASH) &&
- to_copy > 0 && is_dir_sep(result[i][to_copy - 1]))
+ to_copy > 0 && is_dir_sep(pathspec[i][to_copy - 1]))
to_copy--;
- if (to_copy != length || flags & DUP_BASENAME) {
- char *it = xmemdupz(result[i], to_copy);
- if (flags & DUP_BASENAME) {
- result[i] = xstrdup(basename(it));
- free(it);
- } else
- result[i] = it;
+
+ it = xmemdupz(pathspec[i], to_copy);
+ if (flags & DUP_BASENAME) {
+ result[i] = xstrdup(basename(it));
+ free(it);
+ } else {
+ result[i] = it;
}
}
- return get_pathspec(prefix, result);
+ result[count] = NULL;
+
+ /* Prefix the pathspec and free the old intermediate strings */
+ for (i = 0; i < count; i++) {
+ const char *match = prefix_path(prefix, prefixlen, result[i]);
+ free((char *) result[i]);
+ result[i] = match;
+ }
+
+ return result;
}
static const char *add_slash(const char *path)
@@ -130,7 +142,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
if (read_cache() < 0)
die(_("index file corrupt"));
- source = internal_copy_pathspec(prefix, argv, argc, 0);
+ source = internal_prefix_pathspec(prefix, argv, argc, 0);
modes = xcalloc(argc, sizeof(enum update_mode));
/*
* Keep trailing slash, needed to let
@@ -140,16 +152,16 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
flags = KEEP_TRAILING_SLASH;
if (argc == 1 && is_directory(argv[0]) && !is_directory(argv[1]))
flags = 0;
- dest_path = internal_copy_pathspec(prefix, argv + argc, 1, flags);
+ dest_path = internal_prefix_pathspec(prefix, argv + argc, 1, flags);
submodule_gitfile = xcalloc(argc, sizeof(char *));
if (dest_path[0][0] == '\0')
/* special case: "." was normalized to "" */
- destination = internal_copy_pathspec(dest_path[0], argv, argc, DUP_BASENAME);
+ destination = internal_prefix_pathspec(dest_path[0], argv, argc, DUP_BASENAME);
else if (!lstat(dest_path[0], &st) &&
S_ISDIR(st.st_mode)) {
dest_path[0] = add_slash(dest_path[0]);
- destination = internal_copy_pathspec(dest_path[0], argv, argc, DUP_BASENAME);
+ destination = internal_prefix_pathspec(dest_path[0], argv, argc, DUP_BASENAME);
} else {
if (argc != 1)
die(_("destination '%s' is not a directory"), dest_path[0]);
diff --git a/builtin/push.c b/builtin/push.c
index 9307ad5..5c22e9f 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/receive-pack.c b/builtin/receive-pack.c
index 6b97cbd..1dbb8a0 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 e52cf39..5339ed6 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/repack.c b/builtin/repack.c
index 80dd06b..677bc7c 100644
--- a/builtin/repack.c
+++ b/builtin/repack.c
@@ -18,6 +18,12 @@ static const char *const git_repack_usage[] = {
NULL
};
+static const char incremental_bitmap_conflict_error[] = N_(
+"Incremental repacks are incompatible with bitmap indexes. Use\n"
+"--no-write-bitmap-index or disable the pack.writebitmaps configuration."
+);
+
+
static int repack_config(const char *var, const char *value, void *cb)
{
if (!strcmp(var, "repack.usedeltabaseoffset")) {
@@ -206,6 +212,9 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
if (pack_kept_objects < 0)
pack_kept_objects = write_bitmaps;
+ if (write_bitmaps && !(pack_everything & ALL_INTO_ONE))
+ die(_(incremental_bitmap_conflict_error));
+
packdir = mkpathdup("%s/pack", get_object_directory());
packtmp = mkpathdup("%s/.tmp-%d-pack", packdir, (int)getpid());
diff --git a/builtin/rm.c b/builtin/rm.c
index 7f15a3d..452170a 100644
--- a/builtin/rm.c
+++ b/builtin/rm.c
@@ -59,27 +59,9 @@ static void print_error_files(struct string_list *files_list,
}
}
-static void error_removing_concrete_submodules(struct string_list *files, int *errs)
-{
- print_error_files(files,
- Q_("the following submodule (or one of its nested "
- "submodules)\n"
- "uses a .git directory:",
- "the following submodules (or one of their nested "
- "submodules)\n"
- "use a .git directory:", files->nr),
- _("\n(use 'rm -rf' if you really want to remove "
- "it including all of its history)"),
- errs);
- string_list_clear(files, 0);
-}
-
-static int check_submodules_use_gitfiles(void)
+static void submodules_absorb_gitdir_if_needed(const char *prefix)
{
int i;
- int errs = 0;
- struct string_list files = STRING_LIST_INIT_NODUP;
-
for (i = 0; i < list.nr; i++) {
const char *name = list.entry[i].name;
int pos;
@@ -99,12 +81,9 @@ static int check_submodules_use_gitfiles(void)
continue;
if (!submodule_uses_gitfile(name))
- string_list_append(&files, name);
+ absorb_git_dir_into_superproject(prefix, name,
+ ABSORB_GITDIR_RECURSE_SUBMODULES);
}
-
- error_removing_concrete_submodules(&files, &errs);
-
- return errs;
}
static int check_local_mod(struct object_id *head, int index_only)
@@ -120,7 +99,6 @@ static int check_local_mod(struct object_id *head, int index_only)
int errs = 0;
struct string_list files_staged = STRING_LIST_INIT_NODUP;
struct string_list files_cached = STRING_LIST_INIT_NODUP;
- struct string_list files_submodule = STRING_LIST_INIT_NODUP;
struct string_list files_local = STRING_LIST_INIT_NODUP;
no_head = is_null_oid(head);
@@ -187,7 +165,9 @@ static int check_local_mod(struct object_id *head, int index_only)
*/
if (ce_match_stat(ce, &st, 0) ||
(S_ISGITLINK(ce->ce_mode) &&
- !ok_to_remove_submodule(ce->name)))
+ bad_to_remove_submodule(ce->name,
+ SUBMODULE_REMOVAL_DIE_ON_ERROR |
+ SUBMODULE_REMOVAL_IGNORE_IGNORED_UNTRACKED)))
local_changes = 1;
/*
@@ -217,13 +197,8 @@ static int check_local_mod(struct object_id *head, int index_only)
else if (!index_only) {
if (staged_changes)
string_list_append(&files_cached, name);
- if (local_changes) {
- if (S_ISGITLINK(ce->ce_mode) &&
- !submodule_uses_gitfile(name))
- string_list_append(&files_submodule, name);
- else
- string_list_append(&files_local, name);
- }
+ if (local_changes)
+ string_list_append(&files_local, name);
}
}
print_error_files(&files_staged,
@@ -245,8 +220,6 @@ static int check_local_mod(struct object_id *head, int index_only)
&errs);
string_list_clear(&files_cached, 0);
- error_removing_concrete_submodules(&files_submodule, &errs);
-
print_error_files(&files_local,
Q_("the following file has local modifications:",
"the following files have local modifications:",
@@ -340,6 +313,9 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
exit(0);
}
+ if (!index_only)
+ submodules_absorb_gitdir_if_needed(prefix);
+
/*
* If not forced, the file, the index and the HEAD (if exists)
* must match; but the file can already been removed, since
@@ -356,9 +332,6 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
oidclr(&oid);
if (check_local_mod(&oid, index_only))
exit(1);
- } else if (!index_only) {
- if (check_submodules_use_gitfiles())
- exit(1);
}
/*
@@ -387,32 +360,20 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
*/
if (!index_only) {
int removed = 0, gitmodules_modified = 0;
- struct strbuf buf = STRBUF_INIT;
for (i = 0; i < list.nr; i++) {
const char *path = list.entry[i].name;
if (list.entry[i].is_submodule) {
- if (is_empty_dir(path)) {
- if (!rmdir(path)) {
- removed = 1;
- if (!remove_path_from_gitmodules(path))
- gitmodules_modified = 1;
- continue;
- }
- } else {
- strbuf_reset(&buf);
- strbuf_addstr(&buf, path);
- if (!remove_dir_recursively(&buf, 0)) {
- removed = 1;
- if (!remove_path_from_gitmodules(path))
- gitmodules_modified = 1;
- strbuf_release(&buf);
- continue;
- } else if (!file_exists(path))
- /* Submodule was removed by user */
- if (!remove_path_from_gitmodules(path))
- gitmodules_modified = 1;
- /* Fallthrough and let remove_path() fail. */
- }
+ struct strbuf buf = STRBUF_INIT;
+
+ strbuf_addstr(&buf, path);
+ if (remove_dir_recursively(&buf, 0))
+ die(_("could not remove '%s'"), path);
+ strbuf_release(&buf);
+
+ removed = 1;
+ if (!remove_path_from_gitmodules(path))
+ gitmodules_modified = 1;
+ continue;
}
if (!remove_path(path)) {
removed = 1;
@@ -421,7 +382,6 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
if (!removed)
die_errno("git rm: '%s'", path);
}
- strbuf_release(&buf);
if (gitmodules_modified)
stage_updated_gitmodules();
}
diff --git a/builtin/show-ref.c b/builtin/show-ref.c
index 6d4e669..013d241 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 df0d9c1..899dc33 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -317,8 +317,12 @@ static void init_submodule(const char *path, const char *prefix, int quiet)
/* Only loads from .gitmodules, no overlay with .git/config */
gitmodules_config();
- if (prefix) {
- strbuf_addf(&sb, "%s%s", prefix, path);
+ if (prefix && get_super_prefix())
+ die("BUG: cannot have prefix and superprefix");
+ else if (prefix)
+ displaypath = xstrdup(relative_path(path, prefix, &sb));
+ else if (get_super_prefix()) {
+ strbuf_addf(&sb, "%s%s", get_super_prefix(), path);
displaypath = strbuf_detach(&sb, NULL);
} else
displaypath = xstrdup(path);
@@ -403,9 +407,6 @@ static int module_init(int argc, const char **argv, const char *prefix)
int i;
struct option module_init_options[] = {
- OPT_STRING(0, "prefix", &prefix,
- N_("path"),
- N_("alternative anchor for relative paths")),
OPT__QUIET(&quiet, N_("Suppress output for initializing a submodule")),
OPT_END()
};
@@ -625,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)) {
@@ -1144,7 +1145,7 @@ static struct cmd_struct commands[] = {
{"relative-path", resolve_relative_path, 0},
{"resolve-relative-url", resolve_relative_url, 0},
{"resolve-relative-url-test", resolve_relative_url_test, 0},
- {"init", module_init, 0},
+ {"init", module_init, SUPPORT_SUPER_PREFIX},
{"remote-branch", resolve_remote_submodule_branch, 0},
{"absorb-git-dirs", absorb_git_dirs, SUPPORT_SUPER_PREFIX},
};
diff --git a/builtin/tag.c b/builtin/tag.c
index 73df728..e40c4a9 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 99f8148..5199553 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;
}
diff --git a/cache.h b/cache.h
index 1b67f07..8c77b8c 100644
--- a/cache.h
+++ b/cache.h
@@ -514,7 +514,6 @@ extern void set_git_work_tree(const char *tree);
#define ALTERNATE_DB_ENVIRONMENT "GIT_ALTERNATE_OBJECT_DIRECTORIES"
-extern const char **get_pathspec(const char *prefix, const char **pathspec);
extern void setup_work_tree(void);
extern const char *setup_git_directory_gently(int *);
extern const char *setup_git_directory(void);
@@ -575,7 +574,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 */
@@ -584,7 +602,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
@@ -592,14 +613,24 @@ 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);
extern void set_object_name_for_intent_to_add_entry(struct cache_entry *ce);
extern int index_name_is_other(const struct index_state *, const char *, int);
-extern void *read_blob_data_from_index(struct index_state *, const char *, unsigned long *);
+extern void *read_blob_data_from_index(const struct index_state *, const char *, unsigned long *);
/* do stat comparison even if CE_VALID is true */
#define CE_MATCH_IGNORE_VALID 01
@@ -1064,9 +1095,13 @@ static inline int is_absolute_path(const char *path)
return is_dir_sep(path[0]) || has_dos_drive_prefix(path);
}
int is_directory(const char *);
+char *strbuf_realpath(struct strbuf *resolved, const char *path,
+ int die_on_error);
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);
@@ -1141,6 +1176,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.
@@ -1691,6 +1739,8 @@ extern int git_default_config(const char *, const char *, void *);
extern int git_config_from_file(config_fn_t fn, const char *, void *);
extern int git_config_from_mem(config_fn_t fn, const enum config_origin_type,
const char *name, const char *buf, size_t len, void *data);
+extern int git_config_from_blob_sha1(config_fn_t fn, const char *name,
+ const unsigned char *sha1, void *data);
extern void git_config_push_parameter(const char *text);
extern int git_config_from_parameters(config_fn_t fn, void *data);
extern void git_config(config_fn_t fn, void *);
diff --git a/color.c b/color.c
index 1b95e6b..dee6155 100644
--- a/color.c
+++ b/color.c
@@ -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 2a94137..a1fad28 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 0000000..52d1f0a
--- /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 3c9ed3c..82b89ab 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/config.c b/config.c
index 617b2e3..b680f79 100644
--- a/config.c
+++ b/config.c
@@ -1236,10 +1236,10 @@ int git_config_from_mem(config_fn_t fn, const enum config_origin_type origin_typ
return do_config_from(&top, fn, data);
}
-static int git_config_from_blob_sha1(config_fn_t fn,
- const char *name,
- const unsigned char *sha1,
- void *data)
+int git_config_from_blob_sha1(config_fn_t fn,
+ const char *name,
+ const unsigned char *sha1,
+ void *data)
{
enum object_type type;
char *buf;
diff --git a/contrib/coccinelle/xstrdup_or_null.cocci b/contrib/coccinelle/xstrdup_or_null.cocci
index 3fceef1..8e05d1c 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 f3b57bf..0000000
--- 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 f871880..0000000
--- 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 df59bdf..df59bdf 100755
--- a/git-difftool.perl
+++ b/contrib/examples/git-difftool.perl
diff --git a/contrib/gitview/gitview b/contrib/gitview/gitview
deleted file mode 100755
index 4e23c65..0000000
--- a/contrib/gitview/gitview
+++ /dev/null
@@ -1,1305 +0,0 @@
-#! /usr/bin/env python
-
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-
-""" gitview
-GUI browser for git repository
-This program is based on bzrk by Scott James Remnant <scott@ubuntu.com>
-"""
-__copyright__ = "Copyright (C) 2006 Hewlett-Packard Development Company, L.P."
-__copyright__ = "Copyright (C) 2007 Aneesh Kumar K.V <aneesh.kumar@gmail.com"
-__author__ = "Aneesh Kumar K.V <aneesh.kumar@gmail.com>"
-
-
-import sys
-import os
-import gtk
-import pygtk
-import pango
-import re
-import time
-import gobject
-import cairo
-import math
-import string
-import fcntl
-
-have_gtksourceview2 = False
-have_gtksourceview = False
-try:
- import gtksourceview2
- have_gtksourceview2 = True
-except ImportError:
- try:
- import gtksourceview
- have_gtksourceview = True
- except ImportError:
- print "Running without gtksourceview2 or gtksourceview module"
-
-re_ident = re.compile('(author|committer) (?P<ident>.*) (?P<epoch>\d+) (?P<tz>[+-]\d{4})')
-
-def list_to_string(args, skip):
- count = len(args)
- i = skip
- str_arg=" "
- while (i < count ):
- str_arg = str_arg + args[i]
- str_arg = str_arg + " "
- i = i+1
-
- return str_arg
-
-def show_date(epoch, tz):
- secs = float(epoch)
- tzsecs = float(tz[1:3]) * 3600
- tzsecs += float(tz[3:5]) * 60
- if (tz[0] == "+"):
- secs += tzsecs
- else:
- secs -= tzsecs
-
- return time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(secs))
-
-def get_source_buffer_and_view():
- if have_gtksourceview2:
- buffer = gtksourceview2.Buffer()
- slm = gtksourceview2.LanguageManager()
- gsl = slm.get_language("diff")
- buffer.set_highlight_syntax(True)
- buffer.set_language(gsl)
- view = gtksourceview2.View(buffer)
- elif have_gtksourceview:
- buffer = gtksourceview.SourceBuffer()
- slm = gtksourceview.SourceLanguagesManager()
- gsl = slm.get_language_from_mime_type("text/x-patch")
- buffer.set_highlight(True)
- buffer.set_language(gsl)
- view = gtksourceview.SourceView(buffer)
- else:
- buffer = gtk.TextBuffer()
- view = gtk.TextView(buffer)
- return (buffer, view)
-
-
-class CellRendererGraph(gtk.GenericCellRenderer):
- """Cell renderer for directed graph.
-
- This module contains the implementation of a custom GtkCellRenderer that
- draws part of the directed graph based on the lines suggested by the code
- in graph.py.
-
- Because we're shiny, we use Cairo to do this, and because we're naughty
- we cheat and draw over the bits of the TreeViewColumn that are supposed to
- just be for the background.
-
- Properties:
- node (column, colour, [ names ]) tuple to draw revision node,
- in_lines (start, end, colour) tuple list to draw inward lines,
- out_lines (start, end, colour) tuple list to draw outward lines.
- """
-
- __gproperties__ = {
- "node": ( gobject.TYPE_PYOBJECT, "node",
- "revision node instruction",
- gobject.PARAM_WRITABLE
- ),
- "in-lines": ( gobject.TYPE_PYOBJECT, "in-lines",
- "instructions to draw lines into the cell",
- gobject.PARAM_WRITABLE
- ),
- "out-lines": ( gobject.TYPE_PYOBJECT, "out-lines",
- "instructions to draw lines out of the cell",
- gobject.PARAM_WRITABLE
- ),
- }
-
- def do_set_property(self, property, value):
- """Set properties from GObject properties."""
- if property.name == "node":
- self.node = value
- elif property.name == "in-lines":
- self.in_lines = value
- elif property.name == "out-lines":
- self.out_lines = value
- else:
- raise AttributeError, "no such property: '%s'" % property.name
-
- def box_size(self, widget):
- """Calculate box size based on widget's font.
-
- Cache this as it's probably expensive to get. It ensures that we
- draw the graph at least as large as the text.
- """
- try:
- return self._box_size
- except AttributeError:
- pango_ctx = widget.get_pango_context()
- font_desc = widget.get_style().font_desc
- metrics = pango_ctx.get_metrics(font_desc)
-
- ascent = pango.PIXELS(metrics.get_ascent())
- descent = pango.PIXELS(metrics.get_descent())
-
- self._box_size = ascent + descent + 6
- return self._box_size
-
- def set_colour(self, ctx, colour, bg, fg):
- """Set the context source colour.
-
- Picks a distinct colour based on an internal wheel; the bg
- parameter provides the value that should be assigned to the 'zero'
- colours and the fg parameter provides the multiplier that should be
- applied to the foreground colours.
- """
- colours = [
- ( 1.0, 0.0, 0.0 ),
- ( 1.0, 1.0, 0.0 ),
- ( 0.0, 1.0, 0.0 ),
- ( 0.0, 1.0, 1.0 ),
- ( 0.0, 0.0, 1.0 ),
- ( 1.0, 0.0, 1.0 ),
- ]
-
- colour %= len(colours)
- red = (colours[colour][0] * fg) or bg
- green = (colours[colour][1] * fg) or bg
- blue = (colours[colour][2] * fg) or bg
-
- ctx.set_source_rgb(red, green, blue)
-
- def on_get_size(self, widget, cell_area):
- """Return the size we need for this cell.
-
- Each cell is drawn individually and is only as wide as it needs
- to be, we let the TreeViewColumn take care of making them all
- line up.
- """
- box_size = self.box_size(widget)
-
- cols = self.node[0]
- for start, end, colour in self.in_lines + self.out_lines:
- cols = int(max(cols, start, end))
-
- (column, colour, names) = self.node
- names_len = 0
- if (len(names) != 0):
- for item in names:
- names_len += len(item)
-
- width = box_size * (cols + 1 ) + names_len
- height = box_size
-
- # FIXME I have no idea how to use cell_area properly
- return (0, 0, width, height)
-
- def on_render(self, window, widget, bg_area, cell_area, exp_area, flags):
- """Render an individual cell.
-
- Draws the cell contents using cairo, taking care to clip what we
- do to within the background area so we don't draw over other cells.
- Note that we're a bit naughty there and should really be drawing
- in the cell_area (or even the exposed area), but we explicitly don't
- want any gutter.
-
- We try and be a little clever, if the line we need to draw is going
- to cross other columns we actually draw it as in the .---' style
- instead of a pure diagonal ... this reduces confusion by an
- incredible amount.
- """
- ctx = window.cairo_create()
- ctx.rectangle(bg_area.x, bg_area.y, bg_area.width, bg_area.height)
- ctx.clip()
-
- box_size = self.box_size(widget)
-
- ctx.set_line_width(box_size / 8)
- ctx.set_line_cap(cairo.LINE_CAP_SQUARE)
-
- # Draw lines into the cell
- for start, end, colour in self.in_lines:
- ctx.move_to(cell_area.x + box_size * start + box_size / 2,
- bg_area.y - bg_area.height / 2)
-
- if start - end > 1:
- ctx.line_to(cell_area.x + box_size * start, bg_area.y)
- ctx.line_to(cell_area.x + box_size * end + box_size, bg_area.y)
- elif start - end < -1:
- ctx.line_to(cell_area.x + box_size * start + box_size,
- bg_area.y)
- ctx.line_to(cell_area.x + box_size * end, bg_area.y)
-
- ctx.line_to(cell_area.x + box_size * end + box_size / 2,
- bg_area.y + bg_area.height / 2)
-
- self.set_colour(ctx, colour, 0.0, 0.65)
- ctx.stroke()
-
- # Draw lines out of the cell
- for start, end, colour in self.out_lines:
- ctx.move_to(cell_area.x + box_size * start + box_size / 2,
- bg_area.y + bg_area.height / 2)
-
- if start - end > 1:
- ctx.line_to(cell_area.x + box_size * start,
- bg_area.y + bg_area.height)
- ctx.line_to(cell_area.x + box_size * end + box_size,
- bg_area.y + bg_area.height)
- elif start - end < -1:
- ctx.line_to(cell_area.x + box_size * start + box_size,
- bg_area.y + bg_area.height)
- ctx.line_to(cell_area.x + box_size * end,
- bg_area.y + bg_area.height)
-
- ctx.line_to(cell_area.x + box_size * end + box_size / 2,
- bg_area.y + bg_area.height / 2 + bg_area.height)
-
- self.set_colour(ctx, colour, 0.0, 0.65)
- ctx.stroke()
-
- # Draw the revision node in the right column
- (column, colour, names) = self.node
- ctx.arc(cell_area.x + box_size * column + box_size / 2,
- cell_area.y + cell_area.height / 2,
- box_size / 4, 0, 2 * math.pi)
-
-
- self.set_colour(ctx, colour, 0.0, 0.5)
- ctx.stroke_preserve()
-
- self.set_colour(ctx, colour, 0.5, 1.0)
- ctx.fill_preserve()
-
- if (len(names) != 0):
- name = " "
- for item in names:
- name = name + item + " "
-
- ctx.set_font_size(13)
- if (flags & 1):
- self.set_colour(ctx, colour, 0.5, 1.0)
- else:
- self.set_colour(ctx, colour, 0.0, 0.5)
- ctx.show_text(name)
-
-class Commit(object):
- """ This represent a commit object obtained after parsing the git-rev-list
- output """
-
- __slots__ = ['children_sha1', 'message', 'author', 'date', 'committer',
- 'commit_date', 'commit_sha1', 'parent_sha1']
-
- children_sha1 = {}
-
- def __init__(self, commit_lines):
- self.message = ""
- self.author = ""
- self.date = ""
- self.committer = ""
- self.commit_date = ""
- self.commit_sha1 = ""
- self.parent_sha1 = [ ]
- self.parse_commit(commit_lines)
-
-
- def parse_commit(self, commit_lines):
-
- # First line is the sha1 lines
- line = string.strip(commit_lines[0])
- sha1 = re.split(" ", line)
- self.commit_sha1 = sha1[0]
- self.parent_sha1 = sha1[1:]
-
- #build the child list
- for parent_id in self.parent_sha1:
- try:
- Commit.children_sha1[parent_id].append(self.commit_sha1)
- except KeyError:
- Commit.children_sha1[parent_id] = [self.commit_sha1]
-
- # IF we don't have parent
- if (len(self.parent_sha1) == 0):
- self.parent_sha1 = [0]
-
- for line in commit_lines[1:]:
- m = re.match("^ ", line)
- if (m != None):
- # First line of the commit message used for short log
- if self.message == "":
- self.message = string.strip(line)
- continue
-
- m = re.match("tree", line)
- if (m != None):
- continue
-
- m = re.match("parent", line)
- if (m != None):
- continue
-
- m = re_ident.match(line)
- if (m != None):
- date = show_date(m.group('epoch'), m.group('tz'))
- if m.group(1) == "author":
- self.author = m.group('ident')
- self.date = date
- elif m.group(1) == "committer":
- self.committer = m.group('ident')
- self.commit_date = date
-
- continue
-
- def get_message(self, with_diff=0):
- if (with_diff == 1):
- message = self.diff_tree()
- else:
- fp = os.popen("git cat-file commit " + self.commit_sha1)
- message = fp.read()
- fp.close()
-
- return message
-
- def diff_tree(self):
- fp = os.popen("git diff-tree --pretty --cc -v -p --always " + self.commit_sha1)
- diff = fp.read()
- fp.close()
- return diff
-
-class AnnotateWindow(object):
- """Annotate window.
- This object represents and manages a single window containing the
- annotate information of the file
- """
-
- def __init__(self):
- self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
- self.window.set_border_width(0)
- self.window.set_title("Git repository browser annotation window")
- self.prev_read = ""
-
- # Use two thirds of the screen by default
- screen = self.window.get_screen()
- monitor = screen.get_monitor_geometry(0)
- width = int(monitor.width * 0.66)
- height = int(monitor.height * 0.66)
- self.window.set_default_size(width, height)
-
- def add_file_data(self, filename, commit_sha1, line_num):
- fp = os.popen("git cat-file blob " + commit_sha1 +":"+filename)
- i = 1;
- for line in fp.readlines():
- line = string.rstrip(line)
- self.model.append(None, ["HEAD", filename, line, i])
- i = i+1
- fp.close()
-
- # now set the cursor position
- self.treeview.set_cursor(line_num-1)
- self.treeview.grab_focus()
-
- def _treeview_cursor_cb(self, *args):
- """Callback for when the treeview cursor changes."""
- (path, col) = self.treeview.get_cursor()
- commit_sha1 = self.model[path][0]
- commit_msg = ""
- fp = os.popen("git cat-file commit " + commit_sha1)
- for line in fp.readlines():
- commit_msg = commit_msg + line
- fp.close()
-
- self.commit_buffer.set_text(commit_msg)
-
- def _treeview_row_activated(self, *args):
- """Callback for when the treeview row gets selected."""
- (path, col) = self.treeview.get_cursor()
- commit_sha1 = self.model[path][0]
- filename = self.model[path][1]
- line_num = self.model[path][3]
-
- window = AnnotateWindow();
- fp = os.popen("git rev-parse "+ commit_sha1 + "~1")
- commit_sha1 = string.strip(fp.readline())
- fp.close()
- window.annotate(filename, commit_sha1, line_num)
-
- def data_ready(self, source, condition):
- while (1):
- try :
- # A simple readline doesn't work
- # a readline bug ??
- buffer = source.read(100)
-
- except:
- # resource temporary not available
- return True
-
- if (len(buffer) == 0):
- gobject.source_remove(self.io_watch_tag)
- source.close()
- return False
-
- if (self.prev_read != ""):
- buffer = self.prev_read + buffer
- self.prev_read = ""
-
- if (buffer[len(buffer) -1] != '\n'):
- try:
- newline_index = buffer.rindex("\n")
- except ValueError:
- newline_index = 0
-
- self.prev_read = buffer[newline_index:(len(buffer))]
- buffer = buffer[0:newline_index]
-
- for buff in buffer.split("\n"):
- annotate_line = re.compile('^([0-9a-f]{40}) (.+) (.+) (.+)$')
- m = annotate_line.match(buff)
- if not m:
- annotate_line = re.compile('^(filename) (.+)$')
- m = annotate_line.match(buff)
- if not m:
- continue
- filename = m.group(2)
- else:
- self.commit_sha1 = m.group(1)
- self.source_line = int(m.group(2))
- self.result_line = int(m.group(3))
- self.count = int(m.group(4))
- #set the details only when we have the file name
- continue
-
- while (self.count > 0):
- # set at result_line + count-1 the sha1 as commit_sha1
- self.count = self.count - 1
- iter = self.model.iter_nth_child(None, self.result_line + self.count-1)
- self.model.set(iter, 0, self.commit_sha1, 1, filename, 3, self.source_line)
-
-
- def annotate(self, filename, commit_sha1, line_num):
- # verify the commit_sha1 specified has this filename
-
- fp = os.popen("git ls-tree "+ commit_sha1 + " -- " + filename)
- line = string.strip(fp.readline())
- if line == '':
- # pop up the message the file is not there as a part of the commit
- fp.close()
- dialog = gtk.MessageDialog(parent=None, flags=0,
- type=gtk.MESSAGE_WARNING, buttons=gtk.BUTTONS_CLOSE,
- message_format=None)
- dialog.set_markup("The file %s is not present in the parent commit %s" % (filename, commit_sha1))
- dialog.run()
- dialog.destroy()
- return
-
- fp.close()
-
- vpan = gtk.VPaned();
- self.window.add(vpan);
- vpan.show()
-
- scrollwin = gtk.ScrolledWindow()
- scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
- scrollwin.set_shadow_type(gtk.SHADOW_IN)
- vpan.pack1(scrollwin, True, True);
- scrollwin.show()
-
- self.model = gtk.TreeStore(str, str, str, int)
- self.treeview = gtk.TreeView(self.model)
- self.treeview.set_rules_hint(True)
- self.treeview.set_search_column(0)
- self.treeview.connect("cursor-changed", self._treeview_cursor_cb)
- self.treeview.connect("row-activated", self._treeview_row_activated)
- scrollwin.add(self.treeview)
- self.treeview.show()
-
- cell = gtk.CellRendererText()
- cell.set_property("width-chars", 10)
- cell.set_property("ellipsize", pango.ELLIPSIZE_END)
- column = gtk.TreeViewColumn("Commit")
- column.set_resizable(True)
- column.pack_start(cell, expand=True)
- column.add_attribute(cell, "text", 0)
- self.treeview.append_column(column)
-
- cell = gtk.CellRendererText()
- cell.set_property("width-chars", 20)
- cell.set_property("ellipsize", pango.ELLIPSIZE_END)
- column = gtk.TreeViewColumn("File Name")
- column.set_resizable(True)
- column.pack_start(cell, expand=True)
- column.add_attribute(cell, "text", 1)
- self.treeview.append_column(column)
-
- cell = gtk.CellRendererText()
- cell.set_property("width-chars", 20)
- cell.set_property("ellipsize", pango.ELLIPSIZE_END)
- column = gtk.TreeViewColumn("Data")
- column.set_resizable(True)
- column.pack_start(cell, expand=True)
- column.add_attribute(cell, "text", 2)
- self.treeview.append_column(column)
-
- # The commit message window
- scrollwin = gtk.ScrolledWindow()
- scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
- scrollwin.set_shadow_type(gtk.SHADOW_IN)
- vpan.pack2(scrollwin, True, True);
- scrollwin.show()
-
- commit_text = gtk.TextView()
- self.commit_buffer = gtk.TextBuffer()
- commit_text.set_buffer(self.commit_buffer)
- scrollwin.add(commit_text)
- commit_text.show()
-
- self.window.show()
-
- self.add_file_data(filename, commit_sha1, line_num)
-
- fp = os.popen("git blame --incremental -C -C -- " + filename + " " + commit_sha1)
- flags = fcntl.fcntl(fp.fileno(), fcntl.F_GETFL)
- fcntl.fcntl(fp.fileno(), fcntl.F_SETFL, flags | os.O_NONBLOCK)
- self.io_watch_tag = gobject.io_add_watch(fp, gobject.IO_IN, self.data_ready)
-
-
-class DiffWindow(object):
- """Diff window.
- This object represents and manages a single window containing the
- differences between two revisions on a branch.
- """
-
- def __init__(self):
- self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
- self.window.set_border_width(0)
- self.window.set_title("Git repository browser diff window")
-
- # Use two thirds of the screen by default
- screen = self.window.get_screen()
- monitor = screen.get_monitor_geometry(0)
- width = int(monitor.width * 0.66)
- height = int(monitor.height * 0.66)
- self.window.set_default_size(width, height)
-
-
- self.construct()
-
- def construct(self):
- """Construct the window contents."""
- vbox = gtk.VBox()
- self.window.add(vbox)
- vbox.show()
-
- menu_bar = gtk.MenuBar()
- save_menu = gtk.ImageMenuItem(gtk.STOCK_SAVE)
- save_menu.connect("activate", self.save_menu_response, "save")
- save_menu.show()
- menu_bar.append(save_menu)
- vbox.pack_start(menu_bar, expand=False, fill=True)
- menu_bar.show()
-
- hpan = gtk.HPaned()
-
- scrollwin = gtk.ScrolledWindow()
- scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
- scrollwin.set_shadow_type(gtk.SHADOW_IN)
- hpan.pack1(scrollwin, True, True)
- scrollwin.show()
-
- (self.buffer, sourceview) = get_source_buffer_and_view()
-
- sourceview.set_editable(False)
- sourceview.modify_font(pango.FontDescription("Monospace"))
- scrollwin.add(sourceview)
- sourceview.show()
-
- # The file hierarchy: a scrollable treeview
- scrollwin = gtk.ScrolledWindow()
- scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
- scrollwin.set_shadow_type(gtk.SHADOW_IN)
- scrollwin.set_size_request(20, -1)
- hpan.pack2(scrollwin, True, True)
- scrollwin.show()
-
- self.model = gtk.TreeStore(str, str, str)
- self.treeview = gtk.TreeView(self.model)
- self.treeview.set_search_column(1)
- self.treeview.connect("cursor-changed", self._treeview_clicked)
- scrollwin.add(self.treeview)
- self.treeview.show()
-
- cell = gtk.CellRendererText()
- cell.set_property("width-chars", 20)
- column = gtk.TreeViewColumn("Select to annotate")
- column.pack_start(cell, expand=True)
- column.add_attribute(cell, "text", 0)
- self.treeview.append_column(column)
-
- vbox.pack_start(hpan, expand=True, fill=True)
- hpan.show()
-
- def _treeview_clicked(self, *args):
- """Callback for when the treeview cursor changes."""
- (path, col) = self.treeview.get_cursor()
- specific_file = self.model[path][1]
- commit_sha1 = self.model[path][2]
- if specific_file == None :
- return
- elif specific_file == "" :
- specific_file = None
-
- window = AnnotateWindow();
- window.annotate(specific_file, commit_sha1, 1)
-
-
- def commit_files(self, commit_sha1, parent_sha1):
- self.model.clear()
- add = self.model.append(None, [ "Added", None, None])
- dele = self.model.append(None, [ "Deleted", None, None])
- mod = self.model.append(None, [ "Modified", None, None])
- diff_tree = re.compile('^(:.{6}) (.{6}) (.{40}) (.{40}) (A|D|M)\s(.+)$')
- fp = os.popen("git diff-tree -r --no-commit-id " + parent_sha1 + " " + commit_sha1)
- while 1:
- line = string.strip(fp.readline())
- if line == '':
- break
- m = diff_tree.match(line)
- if not m:
- continue
-
- attr = m.group(5)
- filename = m.group(6)
- if attr == "A":
- self.model.append(add, [filename, filename, commit_sha1])
- elif attr == "D":
- self.model.append(dele, [filename, filename, commit_sha1])
- elif attr == "M":
- self.model.append(mod, [filename, filename, commit_sha1])
- fp.close()
-
- self.treeview.expand_all()
-
- def set_diff(self, commit_sha1, parent_sha1, encoding):
- """Set the differences showed by this window.
- Compares the two trees and populates the window with the
- differences.
- """
- # Diff with the first commit or the last commit shows nothing
- if (commit_sha1 == 0 or parent_sha1 == 0 ):
- return
-
- fp = os.popen("git diff-tree -p " + parent_sha1 + " " + commit_sha1)
- self.buffer.set_text(unicode(fp.read(), encoding).encode('utf-8'))
- fp.close()
- self.commit_files(commit_sha1, parent_sha1)
- self.window.show()
-
- def save_menu_response(self, widget, string):
- dialog = gtk.FileChooserDialog("Save..", None, gtk.FILE_CHOOSER_ACTION_SAVE,
- (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
- gtk.STOCK_SAVE, gtk.RESPONSE_OK))
- dialog.set_default_response(gtk.RESPONSE_OK)
- response = dialog.run()
- if response == gtk.RESPONSE_OK:
- patch_buffer = self.buffer.get_text(self.buffer.get_start_iter(),
- self.buffer.get_end_iter())
- fp = open(dialog.get_filename(), "w")
- fp.write(patch_buffer)
- fp.close()
- dialog.destroy()
-
-class GitView(object):
- """ This is the main class
- """
- version = "0.9"
-
- def __init__(self, with_diff=0):
- self.with_diff = with_diff
- self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
- self.window.set_border_width(0)
- self.window.set_title("Git repository browser")
-
- self.get_encoding()
- self.get_bt_sha1()
-
- # Use three-quarters of the screen by default
- screen = self.window.get_screen()
- monitor = screen.get_monitor_geometry(0)
- width = int(monitor.width * 0.75)
- height = int(monitor.height * 0.75)
- self.window.set_default_size(width, height)
-
- # FIXME AndyFitz!
- icon = self.window.render_icon(gtk.STOCK_INDEX, gtk.ICON_SIZE_BUTTON)
- self.window.set_icon(icon)
-
- self.accel_group = gtk.AccelGroup()
- self.window.add_accel_group(self.accel_group)
- self.accel_group.connect_group(0xffc2, 0, gtk.ACCEL_LOCKED, self.refresh);
- self.accel_group.connect_group(0xffc1, 0, gtk.ACCEL_LOCKED, self.maximize);
- self.accel_group.connect_group(0xffc8, 0, gtk.ACCEL_LOCKED, self.fullscreen);
- self.accel_group.connect_group(0xffc9, 0, gtk.ACCEL_LOCKED, self.unfullscreen);
-
- self.window.add(self.construct())
-
- def refresh(self, widget, event=None, *arguments, **keywords):
- self.get_encoding()
- self.get_bt_sha1()
- Commit.children_sha1 = {}
- self.set_branch(sys.argv[without_diff:])
- self.window.show()
- return True
-
- def maximize(self, widget, event=None, *arguments, **keywords):
- self.window.maximize()
- return True
-
- def fullscreen(self, widget, event=None, *arguments, **keywords):
- self.window.fullscreen()
- return True
-
- def unfullscreen(self, widget, event=None, *arguments, **keywords):
- self.window.unfullscreen()
- return True
-
- def get_bt_sha1(self):
- """ Update the bt_sha1 dictionary with the
- respective sha1 details """
-
- self.bt_sha1 = { }
- ls_remote = re.compile('^(.{40})\trefs/([^^]+)(?:\\^(..))?$');
- fp = os.popen('git ls-remote "${GIT_DIR-.git}"')
- while 1:
- line = string.strip(fp.readline())
- if line == '':
- break
- m = ls_remote.match(line)
- if not m:
- continue
- (sha1, name) = (m.group(1), m.group(2))
- if not self.bt_sha1.has_key(sha1):
- self.bt_sha1[sha1] = []
- self.bt_sha1[sha1].append(name)
- fp.close()
-
- def get_encoding(self):
- fp = os.popen("git config --get i18n.commitencoding")
- self.encoding=string.strip(fp.readline())
- fp.close()
- if (self.encoding == ""):
- self.encoding = "utf-8"
-
-
- def construct(self):
- """Construct the window contents."""
- vbox = gtk.VBox()
- paned = gtk.VPaned()
- paned.pack1(self.construct_top(), resize=False, shrink=True)
- paned.pack2(self.construct_bottom(), resize=False, shrink=True)
- menu_bar = gtk.MenuBar()
- menu_bar.set_pack_direction(gtk.PACK_DIRECTION_RTL)
- help_menu = gtk.MenuItem("Help")
- menu = gtk.Menu()
- about_menu = gtk.MenuItem("About")
- menu.append(about_menu)
- about_menu.connect("activate", self.about_menu_response, "about")
- about_menu.show()
- help_menu.set_submenu(menu)
- help_menu.show()
- menu_bar.append(help_menu)
- menu_bar.show()
- vbox.pack_start(menu_bar, expand=False, fill=True)
- vbox.pack_start(paned, expand=True, fill=True)
- paned.show()
- vbox.show()
- return vbox
-
-
- def construct_top(self):
- """Construct the top-half of the window."""
- vbox = gtk.VBox(spacing=6)
- vbox.set_border_width(12)
- vbox.show()
-
-
- scrollwin = gtk.ScrolledWindow()
- scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
- scrollwin.set_shadow_type(gtk.SHADOW_IN)
- vbox.pack_start(scrollwin, expand=True, fill=True)
- scrollwin.show()
-
- self.treeview = gtk.TreeView()
- self.treeview.set_rules_hint(True)
- self.treeview.set_search_column(4)
- self.treeview.connect("cursor-changed", self._treeview_cursor_cb)
- scrollwin.add(self.treeview)
- self.treeview.show()
-
- cell = CellRendererGraph()
- column = gtk.TreeViewColumn()
- column.set_resizable(True)
- column.pack_start(cell, expand=True)
- column.add_attribute(cell, "node", 1)
- column.add_attribute(cell, "in-lines", 2)
- column.add_attribute(cell, "out-lines", 3)
- self.treeview.append_column(column)
-
- cell = gtk.CellRendererText()
- cell.set_property("width-chars", 65)
- cell.set_property("ellipsize", pango.ELLIPSIZE_END)
- column = gtk.TreeViewColumn("Message")
- column.set_resizable(True)
- column.pack_start(cell, expand=True)
- column.add_attribute(cell, "text", 4)
- self.treeview.append_column(column)
-
- cell = gtk.CellRendererText()
- cell.set_property("width-chars", 40)
- cell.set_property("ellipsize", pango.ELLIPSIZE_END)
- column = gtk.TreeViewColumn("Author")
- column.set_resizable(True)
- column.pack_start(cell, expand=True)
- column.add_attribute(cell, "text", 5)
- self.treeview.append_column(column)
-
- cell = gtk.CellRendererText()
- cell.set_property("ellipsize", pango.ELLIPSIZE_END)
- column = gtk.TreeViewColumn("Date")
- column.set_resizable(True)
- column.pack_start(cell, expand=True)
- column.add_attribute(cell, "text", 6)
- self.treeview.append_column(column)
-
- return vbox
-
- def about_menu_response(self, widget, string):
- dialog = gtk.AboutDialog()
- dialog.set_name("Gitview")
- dialog.set_version(GitView.version)
- dialog.set_authors(["Aneesh Kumar K.V <aneesh.kumar@gmail.com>"])
- dialog.set_website("http://www.kernel.org/pub/software/scm/git/")
- dialog.set_copyright("Use and distribute under the terms of the GNU General Public License")
- dialog.set_wrap_license(True)
- dialog.run()
- dialog.destroy()
-
-
- def construct_bottom(self):
- """Construct the bottom half of the window."""
- vbox = gtk.VBox(False, spacing=6)
- vbox.set_border_width(12)
- (width, height) = self.window.get_size()
- vbox.set_size_request(width, int(height / 2.5))
- vbox.show()
-
- self.table = gtk.Table(rows=4, columns=4)
- self.table.set_row_spacings(6)
- self.table.set_col_spacings(6)
- vbox.pack_start(self.table, expand=False, fill=True)
- self.table.show()
-
- align = gtk.Alignment(0.0, 0.5)
- label = gtk.Label()
- label.set_markup("<b>Revision:</b>")
- align.add(label)
- self.table.attach(align, 0, 1, 0, 1, gtk.FILL, gtk.FILL)
- label.show()
- align.show()
-
- align = gtk.Alignment(0.0, 0.5)
- self.revid_label = gtk.Label()
- self.revid_label.set_selectable(True)
- align.add(self.revid_label)
- self.table.attach(align, 1, 2, 0, 1, gtk.EXPAND | gtk.FILL, gtk.FILL)
- self.revid_label.show()
- align.show()
-
- align = gtk.Alignment(0.0, 0.5)
- label = gtk.Label()
- label.set_markup("<b>Committer:</b>")
- align.add(label)
- self.table.attach(align, 0, 1, 1, 2, gtk.FILL, gtk.FILL)
- label.show()
- align.show()
-
- align = gtk.Alignment(0.0, 0.5)
- self.committer_label = gtk.Label()
- self.committer_label.set_selectable(True)
- align.add(self.committer_label)
- self.table.attach(align, 1, 2, 1, 2, gtk.EXPAND | gtk.FILL, gtk.FILL)
- self.committer_label.show()
- align.show()
-
- align = gtk.Alignment(0.0, 0.5)
- label = gtk.Label()
- label.set_markup("<b>Timestamp:</b>")
- align.add(label)
- self.table.attach(align, 0, 1, 2, 3, gtk.FILL, gtk.FILL)
- label.show()
- align.show()
-
- align = gtk.Alignment(0.0, 0.5)
- self.timestamp_label = gtk.Label()
- self.timestamp_label.set_selectable(True)
- align.add(self.timestamp_label)
- self.table.attach(align, 1, 2, 2, 3, gtk.EXPAND | gtk.FILL, gtk.FILL)
- self.timestamp_label.show()
- align.show()
-
- align = gtk.Alignment(0.0, 0.5)
- label = gtk.Label()
- label.set_markup("<b>Parents:</b>")
- align.add(label)
- self.table.attach(align, 0, 1, 3, 4, gtk.FILL, gtk.FILL)
- label.show()
- align.show()
- self.parents_widgets = []
-
- align = gtk.Alignment(0.0, 0.5)
- label = gtk.Label()
- label.set_markup("<b>Children:</b>")
- align.add(label)
- self.table.attach(align, 2, 3, 3, 4, gtk.FILL, gtk.FILL)
- label.show()
- align.show()
- self.children_widgets = []
-
- scrollwin = gtk.ScrolledWindow()
- scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
- scrollwin.set_shadow_type(gtk.SHADOW_IN)
- vbox.pack_start(scrollwin, expand=True, fill=True)
- scrollwin.show()
-
- (self.message_buffer, sourceview) = get_source_buffer_and_view()
-
- sourceview.set_editable(False)
- sourceview.modify_font(pango.FontDescription("Monospace"))
- scrollwin.add(sourceview)
- sourceview.show()
-
- return vbox
-
- def _treeview_cursor_cb(self, *args):
- """Callback for when the treeview cursor changes."""
- (path, col) = self.treeview.get_cursor()
- commit = self.model[path][0]
-
- if commit.committer is not None:
- committer = commit.committer
- timestamp = commit.commit_date
- message = commit.get_message(self.with_diff)
- revid_label = commit.commit_sha1
- else:
- committer = ""
- timestamp = ""
- message = ""
- revid_label = ""
-
- self.revid_label.set_text(revid_label)
- self.committer_label.set_text(committer)
- self.timestamp_label.set_text(timestamp)
- self.message_buffer.set_text(unicode(message, self.encoding).encode('utf-8'))
-
- for widget in self.parents_widgets:
- self.table.remove(widget)
-
- self.parents_widgets = []
- self.table.resize(4 + len(commit.parent_sha1) - 1, 4)
- for idx, parent_id in enumerate(commit.parent_sha1):
- self.table.set_row_spacing(idx + 3, 0)
-
- align = gtk.Alignment(0.0, 0.0)
- self.parents_widgets.append(align)
- self.table.attach(align, 1, 2, idx + 3, idx + 4,
- gtk.EXPAND | gtk.FILL, gtk.FILL)
- align.show()
-
- hbox = gtk.HBox(False, 0)
- align.add(hbox)
- hbox.show()
-
- label = gtk.Label(parent_id)
- label.set_selectable(True)
- hbox.pack_start(label, expand=False, fill=True)
- label.show()
-
- image = gtk.Image()
- image.set_from_stock(gtk.STOCK_JUMP_TO, gtk.ICON_SIZE_MENU)
- image.show()
-
- button = gtk.Button()
- button.add(image)
- button.set_relief(gtk.RELIEF_NONE)
- button.connect("clicked", self._go_clicked_cb, parent_id)
- hbox.pack_start(button, expand=False, fill=True)
- button.show()
-
- image = gtk.Image()
- image.set_from_stock(gtk.STOCK_FIND, gtk.ICON_SIZE_MENU)
- image.show()
-
- button = gtk.Button()
- button.add(image)
- button.set_relief(gtk.RELIEF_NONE)
- button.set_sensitive(True)
- button.connect("clicked", self._show_clicked_cb,
- commit.commit_sha1, parent_id, self.encoding)
- hbox.pack_start(button, expand=False, fill=True)
- button.show()
-
- # Populate with child details
- for widget in self.children_widgets:
- self.table.remove(widget)
-
- self.children_widgets = []
- try:
- child_sha1 = Commit.children_sha1[commit.commit_sha1]
- except KeyError:
- # We don't have child
- child_sha1 = [ 0 ]
-
- if ( len(child_sha1) > len(commit.parent_sha1)):
- self.table.resize(4 + len(child_sha1) - 1, 4)
-
- for idx, child_id in enumerate(child_sha1):
- self.table.set_row_spacing(idx + 3, 0)
-
- align = gtk.Alignment(0.0, 0.0)
- self.children_widgets.append(align)
- self.table.attach(align, 3, 4, idx + 3, idx + 4,
- gtk.EXPAND | gtk.FILL, gtk.FILL)
- align.show()
-
- hbox = gtk.HBox(False, 0)
- align.add(hbox)
- hbox.show()
-
- label = gtk.Label(child_id)
- label.set_selectable(True)
- hbox.pack_start(label, expand=False, fill=True)
- label.show()
-
- image = gtk.Image()
- image.set_from_stock(gtk.STOCK_JUMP_TO, gtk.ICON_SIZE_MENU)
- image.show()
-
- button = gtk.Button()
- button.add(image)
- button.set_relief(gtk.RELIEF_NONE)
- button.connect("clicked", self._go_clicked_cb, child_id)
- hbox.pack_start(button, expand=False, fill=True)
- button.show()
-
- image = gtk.Image()
- image.set_from_stock(gtk.STOCK_FIND, gtk.ICON_SIZE_MENU)
- image.show()
-
- button = gtk.Button()
- button.add(image)
- button.set_relief(gtk.RELIEF_NONE)
- button.set_sensitive(True)
- button.connect("clicked", self._show_clicked_cb,
- child_id, commit.commit_sha1, self.encoding)
- hbox.pack_start(button, expand=False, fill=True)
- button.show()
-
- def _destroy_cb(self, widget):
- """Callback for when a window we manage is destroyed."""
- self.quit()
-
-
- def quit(self):
- """Stop the GTK+ main loop."""
- gtk.main_quit()
-
- def run(self, args):
- self.set_branch(args)
- self.window.connect("destroy", self._destroy_cb)
- self.window.show()
- gtk.main()
-
- def set_branch(self, args):
- """Fill in different windows with info from the reposiroty"""
- fp = os.popen("git rev-parse --sq --default HEAD " + list_to_string(args, 1))
- git_rev_list_cmd = fp.read()
- fp.close()
- fp = os.popen("git rev-list --header --topo-order --parents " + git_rev_list_cmd)
- self.update_window(fp)
-
- def update_window(self, fp):
- commit_lines = []
-
- self.model = gtk.ListStore(gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT,
- gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT, str, str, str)
-
- # used for cursor positioning
- self.index = {}
-
- self.colours = {}
- self.nodepos = {}
- self.incomplete_line = {}
- self.commits = []
-
- index = 0
- last_colour = 0
- last_nodepos = -1
- out_line = []
- input_line = fp.readline()
- while (input_line != ""):
- # The commit header ends with '\0'
- # This NULL is immediately followed by the sha1 of the
- # next commit
- if (input_line[0] != '\0'):
- commit_lines.append(input_line)
- input_line = fp.readline()
- continue;
-
- commit = Commit(commit_lines)
- if (commit != None ):
- self.commits.append(commit)
-
- # Skip the '\0
- commit_lines = []
- commit_lines.append(input_line[1:])
- input_line = fp.readline()
-
- fp.close()
-
- for commit in self.commits:
- (out_line, last_colour, last_nodepos) = self.draw_graph(commit,
- index, out_line,
- last_colour,
- last_nodepos)
- self.index[commit.commit_sha1] = index
- index += 1
-
- self.treeview.set_model(self.model)
- self.treeview.show()
-
- def draw_graph(self, commit, index, out_line, last_colour, last_nodepos):
- in_line=[]
-
- # | -> outline
- # X
- # |\ <- inline
-
- # Reset nodepostion
- if (last_nodepos > 5):
- last_nodepos = -1
-
- # Add the incomplete lines of the last cell in this
- try:
- colour = self.colours[commit.commit_sha1]
- except KeyError:
- self.colours[commit.commit_sha1] = last_colour+1
- last_colour = self.colours[commit.commit_sha1]
- colour = self.colours[commit.commit_sha1]
-
- try:
- node_pos = self.nodepos[commit.commit_sha1]
- except KeyError:
- self.nodepos[commit.commit_sha1] = last_nodepos+1
- last_nodepos = self.nodepos[commit.commit_sha1]
- node_pos = self.nodepos[commit.commit_sha1]
-
- #The first parent always continue on the same line
- try:
- # check we already have the value
- tmp_node_pos = self.nodepos[commit.parent_sha1[0]]
- except KeyError:
- self.colours[commit.parent_sha1[0]] = colour
- self.nodepos[commit.parent_sha1[0]] = node_pos
-
- for sha1 in self.incomplete_line.keys():
- if (sha1 != commit.commit_sha1):
- self.draw_incomplete_line(sha1, node_pos,
- out_line, in_line, index)
- else:
- del self.incomplete_line[sha1]
-
-
- for parent_id in commit.parent_sha1:
- try:
- tmp_node_pos = self.nodepos[parent_id]
- except KeyError:
- self.colours[parent_id] = last_colour+1
- last_colour = self.colours[parent_id]
- self.nodepos[parent_id] = last_nodepos+1
- last_nodepos = self.nodepos[parent_id]
-
- in_line.append((node_pos, self.nodepos[parent_id],
- self.colours[parent_id]))
- self.add_incomplete_line(parent_id)
-
- try:
- branch_tag = self.bt_sha1[commit.commit_sha1]
- except KeyError:
- branch_tag = [ ]
-
-
- node = (node_pos, colour, branch_tag)
-
- self.model.append([commit, node, out_line, in_line,
- commit.message, commit.author, commit.date])
-
- return (in_line, last_colour, last_nodepos)
-
- def add_incomplete_line(self, sha1):
- try:
- self.incomplete_line[sha1].append(self.nodepos[sha1])
- except KeyError:
- self.incomplete_line[sha1] = [self.nodepos[sha1]]
-
- def draw_incomplete_line(self, sha1, node_pos, out_line, in_line, index):
- for idx, pos in enumerate(self.incomplete_line[sha1]):
- if(pos == node_pos):
- #remove the straight line and add a slash
- if ((pos, pos, self.colours[sha1]) in out_line):
- out_line.remove((pos, pos, self.colours[sha1]))
- out_line.append((pos, pos+0.5, self.colours[sha1]))
- self.incomplete_line[sha1][idx] = pos = pos+0.5
- try:
- next_commit = self.commits[index+1]
- if (next_commit.commit_sha1 == sha1 and pos != int(pos)):
- # join the line back to the node point
- # This need to be done only if we modified it
- in_line.append((pos, pos-0.5, self.colours[sha1]))
- continue;
- except IndexError:
- pass
- in_line.append((pos, pos, self.colours[sha1]))
-
-
- def _go_clicked_cb(self, widget, revid):
- """Callback for when the go button for a parent is clicked."""
- try:
- self.treeview.set_cursor(self.index[revid])
- except KeyError:
- dialog = gtk.MessageDialog(parent=None, flags=0,
- type=gtk.MESSAGE_WARNING, buttons=gtk.BUTTONS_CLOSE,
- message_format=None)
- dialog.set_markup("Revision <b>%s</b> not present in the list" % revid)
- # revid == 0 is the parent of the first commit
- if (revid != 0 ):
- dialog.format_secondary_text("Try running gitview without any options")
- dialog.run()
- dialog.destroy()
-
- self.treeview.grab_focus()
-
- def _show_clicked_cb(self, widget, commit_sha1, parent_sha1, encoding):
- """Callback for when the show button for a parent is clicked."""
- window = DiffWindow()
- window.set_diff(commit_sha1, parent_sha1, encoding)
- self.treeview.grab_focus()
-
-without_diff = 0
-if __name__ == "__main__":
-
- if (len(sys.argv) > 1 ):
- if (sys.argv[1] == "--without-diff"):
- without_diff = 1
-
- view = GitView( without_diff != 1)
- view.run(sys.argv[without_diff:])
diff --git a/contrib/gitview/gitview.txt b/contrib/gitview/gitview.txt
deleted file mode 100644
index 7b5f900..0000000
--- a/contrib/gitview/gitview.txt
+++ /dev/null
@@ -1,57 +0,0 @@
-gitview(1)
-==========
-
-NAME
-----
-gitview - A GTK based repository browser for git
-
-SYNOPSIS
---------
-[verse]
-'gitview' [options] [args]
-
-DESCRIPTION
----------
-
-Dependencies:
-
-* Python 2.4
-* PyGTK 2.8 or later
-* PyCairo 1.0 or later
-
-OPTIONS
--------
---without-diff::
-
- If the user doesn't want to list the commit diffs in the main window.
- This may speed up the repository browsing.
-
-<args>::
-
- All the valid option for linkgit:git-rev-list[1].
-
-Key Bindings
-------------
-F4::
- To maximize the window
-
-F5::
- To reread references.
-
-F11::
- Full screen
-
-F12::
- Leave full screen
-
-EXAMPLES
---------
-
-gitview v2.6.12.. include/scsi drivers/scsi::
-
- Show as the changes since version v2.6.12 that changed any file in the
- include/scsi or drivers/scsi subdirectories
-
-gitview --since=2.weeks.ago::
-
- Show the changes during the last two weeks
diff --git a/diff.c b/diff.c
index e2eb6d6..f08cd8e 100644
--- a/diff.c
+++ b/diff.c
@@ -32,6 +32,7 @@ static int diff_rename_limit_default = 400;
static int diff_suppress_blank_empty;
static int diff_use_color_default = -1;
static int diff_context_default = 3;
+static int diff_interhunk_context_default;
static const char *diff_word_regex_cfg;
static const char *external_diff_cmd_cfg;
static const char *diff_order_file_cfg;
@@ -239,6 +240,12 @@ int git_diff_ui_config(const char *var, const char *value, void *cb)
return -1;
return 0;
}
+ if (!strcmp(var, "diff.interhunkcontext")) {
+ diff_interhunk_context_default = git_config_int(var, value);
+ if (diff_interhunk_context_default < 0)
+ return -1;
+ return 0;
+ }
if (!strcmp(var, "diff.renames")) {
diff_detect_rename_default = git_config_rename(var, value);
return 0;
@@ -3362,6 +3369,7 @@ void diff_setup(struct diff_options *options)
options->rename_limit = -1;
options->dirstat_permille = diff_dirstat_permille_default;
options->context = diff_context_default;
+ options->interhunkcontext = diff_interhunk_context_default;
options->ws_error_highlight = ws_error_highlight_default;
DIFF_OPT_SET(options, RENAME_EMPTY);
diff --git a/dir.c b/dir.c
index d872cc1..65c3e68 100644
--- a/dir.c
+++ b/dir.c
@@ -16,11 +16,6 @@
#include "varint.h"
#include "ewah/ewok.h"
-struct path_simplify {
- int len;
- const char *path;
-};
-
/*
* Tells read_directory_recursive how a file or directory should be treated.
* Values are ordered by significance, e.g. if a directory contains both
@@ -50,7 +45,7 @@ struct cached_dir {
static enum path_treatment read_directory_recursive(struct dir_struct *dir,
const char *path, int len, struct untracked_cache_dir *untracked,
- int check_only, const struct path_simplify *simplify);
+ int check_only, const struct pathspec *pathspec);
static int get_dtype(struct dirent *de, const char *path, int len);
int fspathcmp(const char *a, const char *b)
@@ -179,17 +174,21 @@ char *common_prefix(const struct pathspec *pathspec)
int fill_directory(struct dir_struct *dir, const struct pathspec *pathspec)
{
- size_t len;
+ char *prefix;
+ size_t prefix_len;
/*
* Calculate common prefix for the pathspec, and
* use that to optimize the directory walk
*/
- len = common_prefix_len(pathspec);
+ prefix = common_prefix(pathspec);
+ prefix_len = prefix ? strlen(prefix) : 0;
/* Read the directory and prune it */
- read_directory(dir, pathspec->nr ? pathspec->_raw[0] : "", len, pathspec);
- return len;
+ read_directory(dir, prefix, prefix_len, pathspec);
+
+ free(prefix);
+ return prefix_len;
}
int within_depth(const char *name, int namelen,
@@ -1312,7 +1311,7 @@ static enum exist_status directory_exists_in_index(const char *dirname, int len)
static enum path_treatment treat_directory(struct dir_struct *dir,
struct untracked_cache_dir *untracked,
const char *dirname, int len, int baselen, int exclude,
- const struct path_simplify *simplify)
+ const struct pathspec *pathspec)
{
/* The "len-1" is to strip the final '/' */
switch (directory_exists_in_index(dirname, len-1)) {
@@ -1341,7 +1340,7 @@ static enum path_treatment treat_directory(struct dir_struct *dir,
untracked = lookup_untracked(dir->untracked, untracked,
dirname + baselen, len - baselen);
return read_directory_recursive(dir, dirname, len,
- untracked, 1, simplify);
+ untracked, 1, pathspec);
}
/*
@@ -1349,24 +1348,33 @@ static enum path_treatment treat_directory(struct dir_struct *dir,
* reading - if the path cannot possibly be in the pathspec,
* return true, and we'll skip it early.
*/
-static int simplify_away(const char *path, int pathlen, const struct path_simplify *simplify)
+static int simplify_away(const char *path, int pathlen,
+ const struct pathspec *pathspec)
{
- if (simplify) {
- for (;;) {
- const char *match = simplify->path;
- int len = simplify->len;
+ int i;
- if (!match)
- break;
- if (len > pathlen)
- len = pathlen;
- if (!memcmp(path, match, len))
- return 0;
- simplify++;
- }
- return 1;
+ if (!pathspec || !pathspec->nr)
+ return 0;
+
+ GUARD_PATHSPEC(pathspec,
+ PATHSPEC_FROMTOP |
+ PATHSPEC_MAXDEPTH |
+ PATHSPEC_LITERAL |
+ PATHSPEC_GLOB |
+ PATHSPEC_ICASE |
+ PATHSPEC_EXCLUDE);
+
+ for (i = 0; i < pathspec->nr; i++) {
+ const struct pathspec_item *item = &pathspec->items[i];
+ int len = item->nowildcard_len;
+
+ if (len > pathlen)
+ len = pathlen;
+ if (!ps_strncmp(item, item->match, path, len))
+ return 0;
}
- return 0;
+
+ return 1;
}
/*
@@ -1380,19 +1388,33 @@ static int simplify_away(const char *path, int pathlen, const struct path_simpli
* 2. the path is a directory prefix of some element in the
* pathspec
*/
-static int exclude_matches_pathspec(const char *path, int len,
- const struct path_simplify *simplify)
-{
- if (simplify) {
- for (; simplify->path; simplify++) {
- if (len == simplify->len
- && !memcmp(path, simplify->path, len))
- return 1;
- if (len < simplify->len
- && simplify->path[len] == '/'
- && !memcmp(path, simplify->path, len))
- return 1;
- }
+static int exclude_matches_pathspec(const char *path, int pathlen,
+ const struct pathspec *pathspec)
+{
+ int i;
+
+ if (!pathspec || !pathspec->nr)
+ return 0;
+
+ GUARD_PATHSPEC(pathspec,
+ PATHSPEC_FROMTOP |
+ PATHSPEC_MAXDEPTH |
+ PATHSPEC_LITERAL |
+ PATHSPEC_GLOB |
+ PATHSPEC_ICASE |
+ PATHSPEC_EXCLUDE);
+
+ for (i = 0; i < pathspec->nr; i++) {
+ const struct pathspec_item *item = &pathspec->items[i];
+ int len = item->nowildcard_len;
+
+ if (len == pathlen &&
+ !ps_strncmp(item, item->match, path, pathlen))
+ return 1;
+ if (len > pathlen &&
+ item->match[pathlen] == '/' &&
+ !ps_strncmp(item, item->match, path, pathlen))
+ return 1;
}
return 0;
}
@@ -1460,7 +1482,7 @@ static enum path_treatment treat_one_path(struct dir_struct *dir,
struct untracked_cache_dir *untracked,
struct strbuf *path,
int baselen,
- const struct path_simplify *simplify,
+ const struct pathspec *pathspec,
int dtype, struct dirent *de)
{
int exclude;
@@ -1512,7 +1534,7 @@ static enum path_treatment treat_one_path(struct dir_struct *dir,
case DT_DIR:
strbuf_addch(path, '/');
return treat_directory(dir, untracked, path->buf, path->len,
- baselen, exclude, simplify);
+ baselen, exclude, pathspec);
case DT_REG:
case DT_LNK:
return exclude ? path_excluded : path_untracked;
@@ -1524,7 +1546,7 @@ static enum path_treatment treat_path_fast(struct dir_struct *dir,
struct cached_dir *cdir,
struct strbuf *path,
int baselen,
- const struct path_simplify *simplify)
+ const struct pathspec *pathspec)
{
strbuf_setlen(path, baselen);
if (!cdir->ucd) {
@@ -1541,7 +1563,7 @@ static enum path_treatment treat_path_fast(struct dir_struct *dir,
* with check_only set.
*/
return read_directory_recursive(dir, path->buf, path->len,
- cdir->ucd, 1, simplify);
+ cdir->ucd, 1, pathspec);
/*
* We get path_recurse in the first run when
* directory_exists_in_index() returns index_nonexistent. We
@@ -1556,23 +1578,23 @@ static enum path_treatment treat_path(struct dir_struct *dir,
struct cached_dir *cdir,
struct strbuf *path,
int baselen,
- const struct path_simplify *simplify)
+ const struct pathspec *pathspec)
{
int dtype;
struct dirent *de = cdir->de;
if (!de)
return treat_path_fast(dir, untracked, cdir, path,
- baselen, simplify);
+ baselen, pathspec);
if (is_dot_or_dotdot(de->d_name) || !strcmp(de->d_name, ".git"))
return path_none;
strbuf_setlen(path, baselen);
strbuf_addstr(path, de->d_name);
- if (simplify_away(path->buf, path->len, simplify))
+ if (simplify_away(path->buf, path->len, pathspec))
return path_none;
dtype = DTYPE(de);
- return treat_one_path(dir, untracked, path, baselen, simplify, dtype, de);
+ return treat_one_path(dir, untracked, path, baselen, pathspec, dtype, de);
}
static void add_untracked(struct untracked_cache_dir *dir, const char *name)
@@ -1703,7 +1725,7 @@ static void close_cached_dir(struct cached_dir *cdir)
static enum path_treatment read_directory_recursive(struct dir_struct *dir,
const char *base, int baselen,
struct untracked_cache_dir *untracked, int check_only,
- const struct path_simplify *simplify)
+ const struct pathspec *pathspec)
{
struct cached_dir cdir;
enum path_treatment state, subdir_state, dir_state = path_none;
@@ -1719,7 +1741,8 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir,
while (!read_cached_dir(&cdir)) {
/* check how the file or directory should be treated */
- state = treat_path(dir, untracked, &cdir, &path, baselen, simplify);
+ state = treat_path(dir, untracked, &cdir, &path,
+ baselen, pathspec);
if (state > dir_state)
dir_state = state;
@@ -1731,8 +1754,9 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir,
path.buf + baselen,
path.len - baselen);
subdir_state =
- read_directory_recursive(dir, path.buf, path.len,
- ud, check_only, simplify);
+ read_directory_recursive(dir, path.buf,
+ path.len, ud,
+ check_only, pathspec);
if (subdir_state > dir_state)
dir_state = subdir_state;
}
@@ -1756,7 +1780,7 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir,
else if ((dir->flags & DIR_SHOW_IGNORED_TOO) ||
((dir->flags & DIR_COLLECT_IGNORED) &&
exclude_matches_pathspec(path.buf, path.len,
- simplify)))
+ pathspec)))
dir_add_ignored(dir, path.buf, path.len);
break;
@@ -1787,36 +1811,9 @@ static int cmp_name(const void *p1, const void *p2)
return name_compare(e1->name, e1->len, e2->name, e2->len);
}
-static struct path_simplify *create_simplify(const char **pathspec)
-{
- int nr, alloc = 0;
- struct path_simplify *simplify = NULL;
-
- if (!pathspec)
- return NULL;
-
- for (nr = 0 ; ; nr++) {
- const char *match;
- ALLOC_GROW(simplify, nr + 1, alloc);
- match = *pathspec++;
- if (!match)
- break;
- simplify[nr].path = match;
- simplify[nr].len = simple_length(match);
- }
- simplify[nr].path = NULL;
- simplify[nr].len = 0;
- return simplify;
-}
-
-static void free_simplify(struct path_simplify *simplify)
-{
- free(simplify);
-}
-
static int treat_leading_path(struct dir_struct *dir,
const char *path, int len,
- const struct path_simplify *simplify)
+ const struct pathspec *pathspec)
{
struct strbuf sb = STRBUF_INIT;
int baselen, rc = 0;
@@ -1840,9 +1837,9 @@ static int treat_leading_path(struct dir_struct *dir,
strbuf_add(&sb, path, baselen);
if (!is_directory(sb.buf))
break;
- if (simplify_away(sb.buf, sb.len, simplify))
+ if (simplify_away(sb.buf, sb.len, pathspec))
break;
- if (treat_one_path(dir, NULL, &sb, baselen, simplify,
+ if (treat_one_path(dir, NULL, &sb, baselen, pathspec,
DT_DIR, NULL) == path_none)
break; /* do not recurse into it */
if (len <= baselen) {
@@ -2010,33 +2007,14 @@ static struct untracked_cache_dir *validate_untracked_cache(struct dir_struct *d
return root;
}
-int read_directory(struct dir_struct *dir, const char *path, int len, const struct pathspec *pathspec)
+int read_directory(struct dir_struct *dir, const char *path,
+ int len, const struct pathspec *pathspec)
{
- struct path_simplify *simplify;
struct untracked_cache_dir *untracked;
- /*
- * Check out create_simplify()
- */
- if (pathspec)
- GUARD_PATHSPEC(pathspec,
- PATHSPEC_FROMTOP |
- PATHSPEC_MAXDEPTH |
- PATHSPEC_LITERAL |
- PATHSPEC_GLOB |
- PATHSPEC_ICASE |
- PATHSPEC_EXCLUDE);
-
if (has_symlink_leading_path(path, len))
return dir->nr;
- /*
- * exclude patterns are treated like positive ones in
- * create_simplify. Usually exclude patterns should be a
- * subset of positive ones, which has no impacts on
- * create_simplify().
- */
- simplify = create_simplify(pathspec ? pathspec->_raw : NULL);
untracked = validate_untracked_cache(dir, len, pathspec);
if (!untracked)
/*
@@ -2044,9 +2022,8 @@ int read_directory(struct dir_struct *dir, const char *path, int len, const stru
* e.g. prep_exclude()
*/
dir->untracked = NULL;
- if (!len || treat_leading_path(dir, path, len, simplify))
- read_directory_recursive(dir, path, len, untracked, 0, simplify);
- free_simplify(simplify);
+ if (!len || treat_leading_path(dir, path, len, pathspec))
+ read_directory_recursive(dir, path, len, untracked, 0, pathspec);
QSORT(dir->entries, dir->nr, cmp_name);
QSORT(dir->ignored, dir->ignored_nr, cmp_name);
if (dir->untracked) {
@@ -2754,8 +2731,8 @@ void connect_work_tree_and_git_dir(const char *work_tree_, const char *git_dir_)
{
struct strbuf file_name = STRBUF_INIT;
struct strbuf rel_path = STRBUF_INIT;
- char *git_dir = xstrdup(real_path(git_dir_));
- char *work_tree = xstrdup(real_path(work_tree_));
+ char *git_dir = real_pathdup(git_dir_);
+ char *work_tree = real_pathdup(work_tree_);
/* Update gitfile */
strbuf_addf(&file_name, "%s/.git", work_tree);
diff --git a/environment.c b/environment.c
index 4bce3ee..8a83101 100644
--- a/environment.c
+++ b/environment.c
@@ -259,7 +259,7 @@ void set_git_work_tree(const char *new_work_tree)
return;
}
git_work_tree_initialized = 1;
- work_tree = xstrdup(real_path(new_work_tree));
+ work_tree = real_pathdup(new_work_tree);
}
const char *get_git_work_tree(void)
diff --git a/exec_cmd.c b/exec_cmd.c
index 19ac214..fb94aeb 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)
diff --git a/fsck.c b/fsck.c
index 4a3069e..9397927 100644
--- a/fsck.c
+++ b/fsck.c
@@ -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 87237b0..f46d40e 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
diff --git a/git-mergetool.sh b/git-mergetool.sh
index e52b4e4..c062e3d 100755
--- a/git-mergetool.sh
+++ b/git-mergetool.sh
@@ -421,7 +421,7 @@ main () {
prompt=true
;;
-O*)
- orderfile="$1"
+ orderfile="${1#-O}"
;;
--)
shift
@@ -454,6 +454,17 @@ main () {
merge_keep_backup="$(git config --bool mergetool.keepBackup || echo true)"
merge_keep_temporaries="$(git config --bool mergetool.keepTemporaries || echo false)"
+ prefix=$(git rev-parse --show-prefix) || exit 1
+ cd_to_toplevel
+
+ if test -n "$orderfile"
+ then
+ orderfile=$(
+ git rev-parse --prefix "$prefix" -- "$orderfile" |
+ sed -e 1d
+ )
+ fi
+
if test $# -eq 0 && test -e "$GIT_DIR/MERGE_RR"
then
set -- $(git rerere remaining)
@@ -461,13 +472,15 @@ main () {
then
print_noop_and_exit
fi
+ elif test $# -ge 0
+ then
+ # rev-parse provides the -- needed for 'set'
+ eval "set $(git rev-parse --sq --prefix "$prefix" -- "$@")"
fi
files=$(git -c core.quotePath=false \
diff --name-only --diff-filter=U \
- ${orderfile:+"$orderfile"} -- "$@")
-
- cd_to_toplevel
+ ${orderfile:+"-O$orderfile"} -- "$@")
if test -z "$files"
then
diff --git a/git-p4.py b/git-p4.py
index 22e3f57..9695d2e 100755
--- a/git-p4.py
+++ b/git-p4.py
@@ -83,7 +83,9 @@ def p4_build_cmd(cmd):
if retries is None:
# Perform 3 retries by default
retries = 3
- real_cmd += ["-r", str(retries)]
+ if retries > 0:
+ # Provide a way to not pass this option by setting git-p4.retries to 0
+ real_cmd += ["-r", str(retries)]
if isinstance(cmd,basestring):
real_cmd = ' '.join(real_cmd) + ' ' + cmd
@@ -672,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-rebase--interactive.sh b/git-rebase--interactive.sh
index b0a6f2b..4734094 100644
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -425,7 +425,7 @@ update_squash_messages () {
if test -f "$squash_msg"; then
mv "$squash_msg" "$squash_msg".bak || exit
count=$(($(sed -n \
- -e "1s/^$comment_char.*\([0-9][0-9]*\).*/\1/p" \
+ -e "1s/^$comment_char[^0-9]*\([0-9][0-9]*\).*/\1/p" \
-e "q" < "$squash_msg".bak)+1))
{
printf '%s\n' "$comment_char $(eval_ngettext \
diff --git a/git-relink.perl b/git-relink.perl
deleted file mode 100755
index 236a352..0000000
--- 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-request-pull.sh b/git-request-pull.sh
index d5500fd..eebd332 100755
--- a/git-request-pull.sh
+++ b/git-request-pull.sh
@@ -4,9 +4,6 @@
# This file is licensed under the GPL v2, or a later version
# at the discretion of Linus Torvalds.
-USAGE='<start> <url> [<end>]'
-LONG_USAGE='Summarizes the changes between two commits to the standard output,
-and includes the given URL in the generated summary.'
SUBDIRECTORY_OK='Yes'
OPTIONS_KEEPDASHDASH=
OPTIONS_STUCKLONG=
diff --git a/git-submodule.sh b/git-submodule.sh
index 554bd1c..123ac10 100755
--- a/git-submodule.sh
+++ b/git-submodule.sh
@@ -12,7 +12,8 @@ USAGE="[--quiet] add [-b <branch>] [-f|--force] [--name <name>] [--reference <re
or: $dashless [--quiet] update [--init] [--remote] [-N|--no-fetch] [-f|--force] [--checkout|--merge|--rebase] [--[no-]recommend-shallow] [--reference <repository>] [--recursive] [--] [<path>...]
or: $dashless [--quiet] summary [--cached|--files] [--summary-limit <n>] [commit] [--] [<path>...]
or: $dashless [--quiet] foreach [--recursive] <command>
- or: $dashless [--quiet] sync [--recursive] [--] [<path>...]"
+ or: $dashless [--quiet] sync [--recursive] [--] [<path>...]
+ or: $dashless [--quiet] absorbgitdirs [--] [<path>...]"
OPTIONS_SPEC=
SUBDIRECTORY_OK=Yes
. git-sh-setup
@@ -203,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
@@ -370,7 +377,7 @@ cmd_init()
shift
done
- git ${wt_prefix:+-C "$wt_prefix"} submodule--helper init ${GIT_QUIET:+--quiet} ${prefix:+--prefix "$prefix"} "$@"
+ git ${wt_prefix:+-C "$wt_prefix"} ${prefix:+--super-prefix "$prefix"} submodule--helper init ${GIT_QUIET:+--quiet} "$@"
}
#
diff --git a/git.c b/git.c
index 50e559c..c887272 100644
--- a/git.c
+++ b/git.c
@@ -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 },
@@ -434,7 +435,7 @@ static struct cmd_struct commands[] = {
{ "fsck-objects", cmd_fsck, RUN_SETUP },
{ "gc", cmd_gc, RUN_SETUP },
{ "get-tar-commit-id", cmd_get_tar_commit_id },
- { "grep", cmd_grep, RUN_SETUP_GENTLY },
+ { "grep", cmd_grep, RUN_SETUP_GENTLY | SUPPORT_SUPER_PREFIX },
{ "hash-object", cmd_hash_object },
{ "help", cmd_help },
{ "index-pack", cmd_index_pack, RUN_SETUP_GENTLY },
@@ -575,8 +576,7 @@ static void handle_builtin(int argc, const char **argv)
static void execv_dashed_external(const char **argv)
{
- struct strbuf cmd = STRBUF_INIT;
- const char *tmp;
+ struct child_process cmd = CHILD_PROCESS_INIT;
int status;
if (get_super_prefix())
@@ -586,30 +586,25 @@ static void execv_dashed_external(const char **argv)
use_pager = check_pager_config(argv[0]);
commit_pager_choice();
- strbuf_addf(&cmd, "git-%s", argv[0]);
+ argv_array_pushf(&cmd.args, "git-%s", argv[0]);
+ argv_array_pushv(&cmd.args, argv + 1);
+ cmd.clean_on_exit = 1;
+ cmd.wait_after_clean = 1;
+ cmd.silent_exec_failure = 1;
- /*
- * argv[0] must be the git command, but the argv array
- * belongs to the caller, and may be reused in
- * subsequent loop iterations. Save argv[0] and
- * restore it on error.
- */
- tmp = argv[0];
- argv[0] = cmd.buf;
-
- trace_argv_printf(argv, "trace: exec:");
+ trace_argv_printf(cmd.args.argv, "trace: exec:");
/*
- * if we fail because the command is not found, it is
- * OK to return. Otherwise, we just pass along the status code.
+ * If we fail because the command is not found, it is
+ * OK to return. Otherwise, we just pass along the status code,
+ * or our usual generic code if we were not even able to exec
+ * the program.
*/
- status = run_command_v_opt(argv, RUN_SILENT_EXEC_FAILURE | RUN_CLEAN_ON_EXIT);
- if (status >= 0 || errno != ENOENT)
+ status = run_command(&cmd);
+ if (status >= 0)
exit(status);
-
- argv[0] = tmp;
-
- strbuf_release(&cmd);
+ else if (errno != ENOENT)
+ exit(128);
}
static int run_argv(int *argcp, const char ***argv)
diff --git a/gitk-git/Makefile b/gitk-git/Makefile
index 5acdc90..5bdd52a 100644
--- a/gitk-git/Makefile
+++ b/gitk-git/Makefile
@@ -50,6 +50,7 @@ endif
all:: gitk-wish $(ALL_MSGFILES)
install:: all
+ $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(bindir_SQ)'
$(INSTALL) -m 755 gitk-wish '$(DESTDIR_SQ)$(bindir_SQ)'/gitk
$(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(msgsdir_SQ)'
$(foreach p,$(ALL_MSGFILES), $(INSTALL) -m 644 $p '$(DESTDIR_SQ)$(msgsdir_SQ)' &&) true
diff --git a/gitk-git/gitk b/gitk-git/gitk
index 805a1c7..a14d7a1 100755
--- a/gitk-git/gitk
+++ b/gitk-git/gitk
@@ -2,7 +2,7 @@
# Tcl ignores the next line -*- tcl -*- \
exec wish "$0" -- "$@"
-# Copyright © 2005-2014 Paul Mackerras. All rights reserved.
+# Copyright © 2005-2016 Paul Mackerras. All rights reserved.
# This program is free software; it may be used, copied, modified
# and distributed under the terms of the GNU General Public Licence,
# either version 2, or (at your option) any later version.
@@ -588,7 +588,7 @@ proc updatecommits {} {
proc reloadcommits {} {
global curview viewcomplete selectedline currentid thickerline
global showneartags treediffs commitinterest cached_commitrow
- global targetid
+ global targetid commitinfo
set selid {}
if {$selectedline ne {}} {
@@ -609,6 +609,7 @@ proc reloadcommits {} {
getallcommits
}
clear_display
+ unset -nocomplain commitinfo
unset -nocomplain commitinterest
unset -nocomplain cached_commitrow
unset -nocomplain targetid
@@ -1315,7 +1316,7 @@ proc commitonrow {row} {
proc closevarcs {v} {
global varctok varccommits varcid parents children
- global cmitlisted commitidx vtokmod
+ global cmitlisted commitidx vtokmod curview numcommits
set missing_parents 0
set scripts {}
@@ -1340,6 +1341,9 @@ proc closevarcs {v} {
}
lappend varccommits($v,$b) $p
incr commitidx($v)
+ if {$v == $curview} {
+ set numcommits $commitidx($v)
+ }
set scripts [check_interest $p $scripts]
}
}
@@ -2265,7 +2269,7 @@ proc makewindow {} {
set h [expr {[font metrics uifont -linespace] + 2}]
set progresscanv .tf.bar.progress
canvas $progresscanv -relief sunken -height $h -borderwidth 2
- set progressitem [$progresscanv create rect -1 0 0 $h -fill lime]
+ set progressitem [$progresscanv create rect -1 0 0 $h -fill "#00ff00"]
set fprogitem [$progresscanv create rect -1 0 0 $h -fill yellow]
set rprogitem [$progresscanv create rect -1 0 0 $h -fill red]
}
@@ -2403,7 +2407,7 @@ proc makewindow {} {
set ctext .bleft.bottom.ctext
text $ctext -background $bgcolor -foreground $fgcolor \
- -state disabled -font textfont \
+ -state disabled -undo 0 -font textfont \
-yscrollcommand scrolltext -wrap none \
-xscrollcommand ".bleft.bottom.sbhorizontal set"
if {$have_tk85} {
@@ -2664,6 +2668,7 @@ proc makewindow {} {
set headctxmenu .headctxmenu
makemenu $headctxmenu {
{mc "Check out this branch" command cobranch}
+ {mc "Rename this branch" command mvbranch}
{mc "Remove this branch" command rmbranch}
{mc "Copy branch name" command {clipboard clear; clipboard append $headmenuhead}}
}
@@ -3033,7 +3038,7 @@ proc about {} {
message $w.m -text [mc "
Gitk - a commit viewer for git
-Copyright \u00a9 2005-2014 Paul Mackerras
+Copyright \u00a9 2005-2016 Paul Mackerras
Use and redistribute under the terms of the GNU General Public License"] \
-justify center -aspect 400 -border 2 -bg $bgcolor -relief groove
@@ -3397,7 +3402,7 @@ set rectmask {
0x00, 0x00, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f,
0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0x00, 0x00};
}
-image create bitmap reficon-H -background black -foreground lime \
+image create bitmap reficon-H -background black -foreground "#00ff00" \
-data $rectdata -maskdata $rectmask
image create bitmap reficon-o -background black -foreground "#ddddff" \
-data $rectdata -maskdata $rectmask
@@ -8069,7 +8074,11 @@ proc getblobdiffline {bdf ids} {
$ctext conf -state normal
while {[incr nr] <= 1000 && [gets $bdf line] >= 0} {
if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
+ # Older diff read. Abort it.
catch {close $bdf}
+ if {$ids != $diffids} {
+ array unset blobdifffd $ids
+ }
return 0
}
parseblobdiffline $ids $line
@@ -8078,6 +8087,7 @@ proc getblobdiffline {bdf ids} {
blobdiffmaybeseehere [eof $bdf]
if {[eof $bdf]} {
catch {close $bdf}
+ array unset blobdifffd $ids
return 0
}
return [expr {$nr >= 1000? 2: 1}]
@@ -9452,26 +9462,63 @@ proc wrcomcan {} {
}
proc mkbranch {} {
- global rowmenuid mkbrtop NS
+ global NS rowmenuid
+
+ set top .branchdialog
+
+ set val(name) ""
+ set val(id) $rowmenuid
+ set val(command) [list mkbrgo $top]
+
+ set ui(title) [mc "Create branch"]
+ set ui(accept) [mc "Create"]
+
+ branchdia $top val ui
+}
+
+proc mvbranch {} {
+ global NS
+ global headmenuid headmenuhead
+
+ set top .branchdialog
+
+ set val(name) $headmenuhead
+ set val(id) $headmenuid
+ set val(command) [list mvbrgo $top $headmenuhead]
+
+ set ui(title) [mc "Rename branch %s" $headmenuhead]
+ set ui(accept) [mc "Rename"]
+
+ branchdia $top val ui
+}
+
+proc branchdia {top valvar uivar} {
+ global NS commitinfo
+ upvar $valvar val $uivar ui
- set top .makebranch
catch {destroy $top}
ttk_toplevel $top
make_transient $top .
- ${NS}::label $top.title -text [mc "Create new branch"]
+ ${NS}::label $top.title -text $ui(title)
grid $top.title - -pady 10
${NS}::label $top.id -text [mc "ID:"]
${NS}::entry $top.sha1 -width 40
- $top.sha1 insert 0 $rowmenuid
+ $top.sha1 insert 0 $val(id)
$top.sha1 conf -state readonly
grid $top.id $top.sha1 -sticky w
+ ${NS}::entry $top.head -width 60
+ $top.head insert 0 [lindex $commitinfo($val(id)) 0]
+ $top.head conf -state readonly
+ grid x $top.head -sticky ew
+ grid columnconfigure $top 1 -weight 1
${NS}::label $top.nlab -text [mc "Name:"]
${NS}::entry $top.name -width 40
+ $top.name insert 0 $val(name)
grid $top.nlab $top.name -sticky w
${NS}::frame $top.buts
- ${NS}::button $top.buts.go -text [mc "Create"] -command [list mkbrgo $top]
+ ${NS}::button $top.buts.go -text $ui(accept) -command $val(command)
${NS}::button $top.buts.can -text [mc "Cancel"] -command "catch {destroy $top}"
- bind $top <Key-Return> [list mkbrgo $top]
+ bind $top <Key-Return> $val(command)
bind $top <Key-Escape> "catch {destroy $top}"
grid $top.buts.go $top.buts.can
grid columnconfigure $top.buts 0 -weight 1 -uniform a
@@ -9526,6 +9573,46 @@ proc mkbrgo {top} {
}
}
+proc mvbrgo {top prevname} {
+ global headids idheads mainhead mainheadid
+
+ set name [$top.name get]
+ set id [$top.sha1 get]
+ set cmdargs {}
+ if {$name eq $prevname} {
+ catch {destroy $top}
+ return
+ }
+ if {$name eq {}} {
+ error_popup [mc "Please specify a new name for the branch"] $top
+ return
+ }
+ catch {destroy $top}
+ lappend cmdargs -m $prevname $name
+ nowbusy renamebranch
+ update
+ if {[catch {
+ eval exec git branch $cmdargs
+ } err]} {
+ notbusy renamebranch
+ error_popup $err
+ } else {
+ notbusy renamebranch
+ removehead $id $prevname
+ removedhead $id $prevname
+ set headids($name) $id
+ lappend idheads($id) $name
+ addedhead $id $name
+ if {$prevname eq $mainhead} {
+ set mainhead $name
+ set mainheadid $id
+ }
+ redrawtags $id
+ dispneartags 0
+ run refill_reflist
+ }
+}
+
proc exec_citool {tool_args {baseid {}}} {
global commitinfo env
@@ -9751,20 +9838,25 @@ proc readresetstat {fd} {
# context menu for a head
proc headmenu {x y id head} {
- global headmenuid headmenuhead headctxmenu mainhead
+ global headmenuid headmenuhead headctxmenu mainhead headids
stopfinding
set headmenuid $id
set headmenuhead $head
- set state normal
+ array set state {0 normal 1 normal 2 normal}
if {[string match "remotes/*" $head]} {
- set state disabled
+ set localhead [string range $head [expr [string last / $head] + 1] end]
+ if {[info exists headids($localhead)]} {
+ set state(0) disabled
+ }
+ array set state {1 disabled 2 disabled}
}
if {$head eq $mainhead} {
- set state disabled
+ array set state {0 disabled 2 disabled}
+ }
+ foreach i {0 1 2} {
+ $headctxmenu entryconfigure $i -state $state($i)
}
- $headctxmenu entryconfigure 0 -state $state
- $headctxmenu entryconfigure 1 -state $state
tk_popup $headctxmenu $x $y
}
@@ -9773,11 +9865,27 @@ proc cobranch {} {
global showlocalchanges
# check the tree is clean first??
+ set newhead $headmenuhead
+ set command [list | git checkout]
+ if {[string match "remotes/*" $newhead]} {
+ set remote $newhead
+ set newhead [string range $newhead [expr [string last / $newhead] + 1] end]
+ # The following check is redundant - the menu option should
+ # be disabled to begin with...
+ if {[info exists headids($newhead)]} {
+ error_popup [mc "A local branch named %s exists already" $newhead]
+ return
+ }
+ lappend command -b $newhead --track $remote
+ } else {
+ lappend command $newhead
+ }
+ lappend command 2>@1
nowbusy checkout [mc "Checking out"]
update
dohidelocalchanges
if {[catch {
- set fd [open [list | git checkout $headmenuhead 2>@1] r]
+ set fd [open $command r]
} err]} {
notbusy checkout
error_popup $err
@@ -9785,12 +9893,12 @@ proc cobranch {} {
dodiffindex
}
} else {
- filerun $fd [list readcheckoutstat $fd $headmenuhead $headmenuid]
+ filerun $fd [list readcheckoutstat $fd $newhead $headmenuid]
}
}
proc readcheckoutstat {fd newhead newheadid} {
- global mainhead mainheadid headids showlocalchanges progresscoords
+ global mainhead mainheadid headids idheads showlocalchanges progresscoords
global viewmainheadid curview
if {[gets $fd line] >= 0} {
@@ -9805,8 +9913,14 @@ proc readcheckoutstat {fd newhead newheadid} {
notbusy checkout
if {[catch {close $fd} err]} {
error_popup $err
+ return
}
set oldmainid $mainheadid
+ if {! [info exists headids($newhead)]} {
+ set headids($newhead) $newheadid
+ lappend idheads($newheadid) $newhead
+ addedhead $newheadid $newhead
+ }
set mainhead $newhead
set mainheadid $newheadid
set viewmainheadid($curview) $newheadid
@@ -12188,7 +12302,7 @@ if {[tk windowingsystem] eq "aqua"} {
set extdifftool "meld"
}
-set colors {lime red blue magenta darkgrey brown orange}
+set colors {"#00ff00" red blue magenta darkgrey brown orange}
if {[tk windowingsystem] eq "win32"} {
set uicolor SystemButtonFace
set uifgcolor SystemButtonText
@@ -12206,12 +12320,12 @@ if {[tk windowingsystem] eq "win32"} {
}
set diffcolors {red "#00a000" blue}
set diffcontext 3
-set mergecolors {red blue lime purple brown "#009090" magenta "#808000" "#009000" "#ff0080" cyan "#b07070" "#70b0f0" "#70f0b0" "#f0b070" "#ff70b0"}
+set mergecolors {red blue "#00ff00" purple brown "#009090" magenta "#808000" "#009000" "#ff0080" cyan "#b07070" "#70b0f0" "#70f0b0" "#f0b070" "#ff70b0"}
set ignorespace 0
set worddiff ""
set markbgcolor "#e0e0ff"
-set headbgcolor lime
+set headbgcolor "#00ff00"
set headfgcolor black
set headoutlinecolor black
set remotebgcolor #ffddaa
@@ -12226,7 +12340,7 @@ set linehoverfgcolor black
set linehoveroutlinecolor black
set mainheadcirclecolor yellow
set workingfilescirclecolor red
-set indexcirclecolor lime
+set indexcirclecolor "#00ff00"
set circlecolors {white blue gray blue blue}
set linkfgcolor blue
set circleoutlinecolor $fgcolor
diff --git a/gitk-git/po/bg.po b/gitk-git/po/bg.po
index 99aa77a..407d555 100644
--- a/gitk-git/po/bg.po
+++ b/gitk-git/po/bg.po
@@ -371,14 +371,14 @@ msgid ""
"\n"
"Gitk - a commit viewer for git\n"
"\n"
-"Copyright © 2005-2014 Paul Mackerras\n"
+"Copyright © 2005-2016 Paul Mackerras\n"
"\n"
"Use and redistribute under the terms of the GNU General Public License"
msgstr ""
"\n"
"Gitk — визуализация на подаванията в Git\n"
"\n"
-"Авторски права: © 2005-2014 Paul Mackerras\n"
+"Авторски права: © 2005-2016 Paul Mackerras\n"
"\n"
"Използвайте и разпространявайте при условията на ОПЛ на ГНУ"
diff --git a/gitk-git/po/ca.po b/gitk-git/po/ca.po
index 5ad066f..87dfc18 100644
--- a/gitk-git/po/ca.po
+++ b/gitk-git/po/ca.po
@@ -1,5 +1,5 @@
# Translation of gitk
-# Copyright (C) 2005-2014 Paul Mackerras
+# Copyright (C) 2005-2016 Paul Mackerras
# This file is distributed under the same license as the gitk package.
# Alex Henrie <alexhenrie24@gmail.com>, 2015.
#
@@ -365,14 +365,14 @@ msgid ""
"\n"
"Gitk - a commit viewer for git\n"
"\n"
-"Copyright © 2005-2014 Paul Mackerras\n"
+"Copyright © 2005-2016 Paul Mackerras\n"
"\n"
"Use and redistribute under the terms of the GNU General Public License"
msgstr ""
"\n"
"Gitk - visualitzador de comissions per al git\n"
"\n"
-"Copyright © 2005-2014 Paul Mackerras\n"
+"Copyright © 2005-2016 Paul Mackerras\n"
"\n"
"Useu-lo i redistribuïu-lo sota els termes de la Llicència Pública General GNU"
diff --git a/gitk-git/po/de.po b/gitk-git/po/de.po
index bde749e..5db3824 100644
--- a/gitk-git/po/de.po
+++ b/gitk-git/po/de.po
@@ -363,14 +363,14 @@ msgid ""
"\n"
"Gitk - a commit viewer for git\n"
"\n"
-"Copyright © 2005-2014 Paul Mackerras\n"
+"Copyright © 2005-2016 Paul Mackerras\n"
"\n"
"Use and redistribute under the terms of the GNU General Public License"
msgstr ""
"\n"
"Gitk - eine Visualisierung der Git-Historie\n"
"\n"
-"Copyright \\u00a9 2005-2014 Paul Mackerras\n"
+"Copyright \\u00a9 2005-2016 Paul Mackerras\n"
"\n"
"Benutzung und Weiterverbreitung gemäß den Bedingungen der GNU General Public "
"License"
diff --git a/gitk-git/po/es.po b/gitk-git/po/es.po
index ddcb0a5..fef3bba 100644
--- a/gitk-git/po/es.po
+++ b/gitk-git/po/es.po
@@ -370,14 +370,14 @@ msgid ""
"\n"
"Gitk - a commit viewer for git\n"
"\n"
-"Copyright © 2005-2014 Paul Mackerras\n"
+"Copyright © 2005-2016 Paul Mackerras\n"
"\n"
"Use and redistribute under the terms of the GNU General Public License"
msgstr ""
"\n"
"Gitk - un visualizador de revisiones para git\n"
"\n"
-"Copyright \\u00a9 2005-2010 Paul Mackerras\n"
+"Copyright \\u00a9 2005-2016 Paul Mackerras\n"
"\n"
"Uso y redistribución permitidos según los términos de la Licencia Pública "
"General de GNU (GNU GPL)"
diff --git a/gitk-git/po/fr.po b/gitk-git/po/fr.po
index c44f994..e4fac93 100644
--- a/gitk-git/po/fr.po
+++ b/gitk-git/po/fr.po
@@ -372,14 +372,14 @@ msgid ""
"\n"
"Gitk - a commit viewer for git\n"
"\n"
-"Copyright © 2005-2014 Paul Mackerras\n"
+"Copyright © 2005-2016 Paul Mackerras\n"
"\n"
"Use and redistribute under the terms of the GNU General Public License"
msgstr ""
"\n"
"Gitk - visualisateur de commit pour git\n"
"\n"
-"Copyright \\u00a9 2005-2014 Paul Mackerras\n"
+"Copyright \\u00a9 2005-2016 Paul Mackerras\n"
"\n"
"Utilisation et redistribution soumises aux termes de la GNU General Public License"
diff --git a/gitk-git/po/hu.po b/gitk-git/po/hu.po
index 66fd75b..79ec5a5 100644
--- a/gitk-git/po/hu.po
+++ b/gitk-git/po/hu.po
@@ -366,14 +366,14 @@ msgid ""
"\n"
"Gitk - a commit viewer for git\n"
"\n"
-"Copyright © 2005-2014 Paul Mackerras\n"
+"Copyright © 2005-2016 Paul Mackerras\n"
"\n"
"Use and redistribute under the terms of the GNU General Public License"
msgstr ""
"\n"
"Gitk - commit nézegető a githez\n"
"\n"
-"Szerzői jog \\u00a9 2005-2010 Paul Mackerras\n"
+"Szerzői jog \\u00a9 2005-2016 Paul Mackerras\n"
"\n"
"Használd és terjeszd a GNU General Public License feltételei mellett"
diff --git a/gitk-git/po/it.po b/gitk-git/po/it.po
index b5f002d..b58d23e 100644
--- a/gitk-git/po/it.po
+++ b/gitk-git/po/it.po
@@ -367,14 +367,14 @@ msgid ""
"\n"
"Gitk - a commit viewer for git\n"
"\n"
-"Copyright © 2005-2014 Paul Mackerras\n"
+"Copyright © 2005-2016 Paul Mackerras\n"
"\n"
"Use and redistribute under the terms of the GNU General Public License"
msgstr ""
"\n"
"Gitk - un visualizzatore di revisioni per git\n"
"\n"
-"Copyright \\u00a9 2005-2010 Paul Mackerras\n"
+"Copyright \\u00a9 2005-2016 Paul Mackerras\n"
"\n"
"Utilizzo e redistribuzione permessi sotto i termini della GNU General Public "
"License"
diff --git a/gitk-git/po/ja.po b/gitk-git/po/ja.po
index f143753..ca3c29b 100644
--- a/gitk-git/po/ja.po
+++ b/gitk-git/po/ja.po
@@ -2,16 +2,17 @@
# Copyright (C) 2005-2015 Paul Mackerras
# This file is distributed under the same license as the gitk package.
#
-# YOKOTA Hiroshi <yokota@netlab.cs.tsukuba.ac.jp>, 2015.
# Mizar <mizar.jp@gmail.com>, 2009.
# Junio C Hamano <gitster@pobox.com>, 2009.
+# YOKOTA Hiroshi <yokota@netlab.cs.tsukuba.ac.jp>, 2015.
+# Satoshi Yasushima <s.yasushima@gmail.com>, 2016.
msgid ""
msgstr ""
"Project-Id-Version: gitk\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-05-17 14:32+1000\n"
"PO-Revision-Date: 2015-11-12 13:00+0900\n"
-"Last-Translator: YOKOTA Hiroshi <yokota@netlab.cs.tsukuba.ac.jp>\n"
+"Last-Translator: Satoshi Yasushima <s.yasushima@gmail.com>\n"
"Language-Team: Japanese\n"
"Language: ja\n"
"MIME-Version: 1.0\n"
@@ -314,11 +315,11 @@ msgstr "マークを付けたコミットと比較する"
#: gitk:2630 gitk:2641
msgid "Diff this -> marked commit"
-msgstr "これと選択したコミットのdiffを見る"
+msgstr "これとマークを付けたコミットのdiffを見る"
#: gitk:2631 gitk:2642
msgid "Diff marked commit -> this"
-msgstr "選択したコミットとこれのdiffを見る"
+msgstr "マークを付けたコミットとこれのdiffを見る"
#: gitk:2632
msgid "Revert this commit"
@@ -373,14 +374,14 @@ msgid ""
"\n"
"Gitk - a commit viewer for git\n"
"\n"
-"Copyright © 2005-2014 Paul Mackerras\n"
+"Copyright © 2005-2016 Paul Mackerras\n"
"\n"
"Use and redistribute under the terms of the GNU General Public License"
msgstr ""
"\n"
"Gitk - gitコミットビューア\n"
"\n"
-"Copyright © 2005-2014 Paul Mackerras\n"
+"Copyright © 2005-2016 Paul Mackerras\n"
"\n"
"使用および再配布は GNU General Public License に従ってください"
diff --git a/gitk-git/po/pt_br.po b/gitk-git/po/pt_br.po
index 3f78f1b..1feb348 100644
--- a/gitk-git/po/pt_br.po
+++ b/gitk-git/po/pt_br.po
@@ -368,14 +368,14 @@ msgid ""
"\n"
"Gitk - a commit viewer for git\n"
"\n"
-"Copyright © 2005-2014 Paul Mackerras\n"
+"Copyright © 2005-2016 Paul Mackerras\n"
"\n"
"Use and redistribute under the terms of the GNU General Public License"
msgstr ""
"\n"
"Gitk - um visualizador de revisões para o git \n"
"\n"
-"Copyright ©9 2005-2010 Paul Mackerras\n"
+"Copyright ©9 2005-2016 Paul Mackerras\n"
"\n"
"Uso e distribuição segundo os termos da Licença Pública Geral GNU"
diff --git a/gitk-git/po/pt_pt.po b/gitk-git/po/pt_pt.po
new file mode 100644
index 0000000..f680ea8
--- /dev/null
+++ b/gitk-git/po/pt_pt.po
@@ -0,0 +1,1376 @@
+# Portuguese translations for gitk package.
+# Copyright (C) 2016 Paul Mackerras
+# This file is distributed under the same license as the gitk package.
+# Vasco Almeida <vascomalmeida@sapo.pt>, 2016.
+msgid ""
+msgstr ""
+"Project-Id-Version: gitk\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2016-04-15 16:52+0000\n"
+"PO-Revision-Date: 2016-05-06 15:35+0000\n"
+"Last-Translator: Vasco Almeida <vascomalmeida@sapo.pt>\n"
+"Language-Team: Portuguese\n"
+"Language: pt\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Virtaal 0.7.1\n"
+
+#: gitk:140
+msgid "Couldn't get list of unmerged files:"
+msgstr "Não foi possível obter lista de ficheiros não integrados:"
+
+#: gitk:212 gitk:2399
+msgid "Color words"
+msgstr "Colorir palavras"
+
+#: gitk:217 gitk:2399 gitk:8239 gitk:8272
+msgid "Markup words"
+msgstr "Marcar palavras"
+
+#: gitk:324
+msgid "Error parsing revisions:"
+msgstr "Erro ao analisar revisões:"
+
+#: gitk:380
+msgid "Error executing --argscmd command:"
+msgstr "Erro ao executar o comando de --argscmd:"
+
+#: gitk:393
+msgid "No files selected: --merge specified but no files are unmerged."
+msgstr ""
+"Nenhum ficheiro selecionado: --merge especificado mas não há ficheiros por "
+"integrar."
+
+#: gitk:396
+msgid ""
+"No files selected: --merge specified but no unmerged files are within file "
+"limit."
+msgstr ""
+"Nenhum ficheiro selecionado: --merge especificado mas não há ficheiros por "
+"integrar ao nível de ficheiro."
+
+#: gitk:418 gitk:566
+msgid "Error executing git log:"
+msgstr "Erro ao executar git log:"
+
+#: gitk:436 gitk:582
+msgid "Reading"
+msgstr "A ler"
+
+#: gitk:496 gitk:4544
+msgid "Reading commits..."
+msgstr "A ler commits..."
+
+#: gitk:499 gitk:1637 gitk:4547
+msgid "No commits selected"
+msgstr "Nenhum commit selecionado"
+
+#: gitk:1445 gitk:4064 gitk:12469
+msgid "Command line"
+msgstr "Linha de comandos"
+
+#: gitk:1511
+msgid "Can't parse git log output:"
+msgstr "Não é possível analisar a saída de git log:"
+
+#: gitk:1740
+msgid "No commit information available"
+msgstr "Não há informação disponível sobre o commit"
+
+#: gitk:1903 gitk:1932 gitk:4334 gitk:9702 gitk:11274 gitk:11554
+msgid "OK"
+msgstr "OK"
+
+#: gitk:1934 gitk:4336 gitk:9215 gitk:9294 gitk:9424 gitk:9473 gitk:9704
+#: gitk:11275 gitk:11555
+msgid "Cancel"
+msgstr "Cancelar"
+
+#: gitk:2083
+msgid "&Update"
+msgstr "At&ualizar"
+
+#: gitk:2084
+msgid "&Reload"
+msgstr "&Recarregar"
+
+#: gitk:2085
+msgid "Reread re&ferences"
+msgstr "Reler re&ferências"
+
+#: gitk:2086
+msgid "&List references"
+msgstr "&Listar referências"
+
+#: gitk:2088
+msgid "Start git &gui"
+msgstr "Iniciar git &gui"
+
+#: gitk:2090
+msgid "&Quit"
+msgstr "&Sair"
+
+#: gitk:2082
+msgid "&File"
+msgstr "&Ficheiro"
+
+#: gitk:2094
+msgid "&Preferences"
+msgstr "&Preferências"
+
+#: gitk:2093
+msgid "&Edit"
+msgstr "&Editar"
+
+#: gitk:2098
+msgid "&New view..."
+msgstr "&Nova vista..."
+
+#: gitk:2099
+msgid "&Edit view..."
+msgstr "&Editar vista..."
+
+#: gitk:2100
+msgid "&Delete view"
+msgstr "Elimina&r vista"
+
+#: gitk:2102
+msgid "&All files"
+msgstr "&Todos os ficheiros"
+
+#: gitk:2097
+msgid "&View"
+msgstr "&Ver"
+
+#: gitk:2107 gitk:2117
+msgid "&About gitk"
+msgstr "&Sobre gitk"
+
+#: gitk:2108 gitk:2122
+msgid "&Key bindings"
+msgstr "&Atalhos"
+
+#: gitk:2106 gitk:2121
+msgid "&Help"
+msgstr "&Ajuda"
+
+#: gitk:2199 gitk:8671
+msgid "SHA1 ID:"
+msgstr "ID SHA1:"
+
+#: gitk:2243
+msgid "Row"
+msgstr "Linha"
+
+#: gitk:2281
+msgid "Find"
+msgstr "Procurar"
+
+#: gitk:2309
+msgid "commit"
+msgstr "commit"
+
+#: gitk:2313 gitk:2315 gitk:4706 gitk:4729 gitk:4753 gitk:6774 gitk:6846
+#: gitk:6931
+msgid "containing:"
+msgstr "contendo:"
+
+#: gitk:2316 gitk:3545 gitk:3550 gitk:4782
+msgid "touching paths:"
+msgstr "altera os caminhos:"
+
+#: gitk:2317 gitk:4796
+msgid "adding/removing string:"
+msgstr "adiciona/remove a cadeia:"
+
+#: gitk:2318 gitk:4798
+msgid "changing lines matching:"
+msgstr "altera linhas com:"
+
+#: gitk:2327 gitk:2329 gitk:4785
+msgid "Exact"
+msgstr "Exato"
+
+#: gitk:2329 gitk:4873 gitk:6742
+msgid "IgnCase"
+msgstr "IgnMaiúsculas"
+
+#: gitk:2329 gitk:4755 gitk:4871 gitk:6738
+msgid "Regexp"
+msgstr "Expr. regular"
+
+#: gitk:2331 gitk:2332 gitk:4893 gitk:4923 gitk:4930 gitk:6867 gitk:6935
+msgid "All fields"
+msgstr "Todos os campos"
+
+#: gitk:2332 gitk:4890 gitk:4923 gitk:6805
+msgid "Headline"
+msgstr "Cabeçalho"
+
+#: gitk:2333 gitk:4890 gitk:6805 gitk:6935 gitk:7408
+msgid "Comments"
+msgstr "Comentários"
+
+#: gitk:2333 gitk:4890 gitk:4895 gitk:4930 gitk:6805 gitk:7343 gitk:8849
+#: gitk:8864
+msgid "Author"
+msgstr "Autor"
+
+#: gitk:2333 gitk:4890 gitk:6805 gitk:7345
+msgid "Committer"
+msgstr "Committer"
+
+#: gitk:2367
+msgid "Search"
+msgstr "Pesquisar"
+
+#: gitk:2375
+msgid "Diff"
+msgstr "Diff"
+
+#: gitk:2377
+msgid "Old version"
+msgstr "Versão antiga"
+
+#: gitk:2379
+msgid "New version"
+msgstr "Versão nova"
+
+#: gitk:2382
+msgid "Lines of context"
+msgstr "Linhas de contexto"
+
+#: gitk:2392
+msgid "Ignore space change"
+msgstr "Ignorar espaços"
+
+#: gitk:2396 gitk:2398 gitk:7978 gitk:8225
+msgid "Line diff"
+msgstr "Diff de linha"
+
+#: gitk:2463
+msgid "Patch"
+msgstr "Patch"
+
+#: gitk:2465
+msgid "Tree"
+msgstr "Árvore"
+
+#: gitk:2635 gitk:2656
+msgid "Diff this -> selected"
+msgstr "Diff este -> seleção"
+
+#: gitk:2636 gitk:2657
+msgid "Diff selected -> this"
+msgstr "Diff seleção -> este"
+
+#: gitk:2637 gitk:2658
+msgid "Make patch"
+msgstr "Gerar patch"
+
+#: gitk:2638 gitk:9273
+msgid "Create tag"
+msgstr "Criar tag"
+
+#: gitk:2639
+msgid "Copy commit summary"
+msgstr "Copiar sumário do commit"
+
+#: gitk:2640 gitk:9404
+msgid "Write commit to file"
+msgstr "Escrever commit num ficheiro"
+
+#: gitk:2641 gitk:9461
+msgid "Create new branch"
+msgstr "Criar novo ramo"
+
+#: gitk:2642
+msgid "Cherry-pick this commit"
+msgstr "Efetuar cherry-pick deste commit"
+
+#: gitk:2643
+msgid "Reset HEAD branch to here"
+msgstr "Repor ramo HEAD para aqui"
+
+#: gitk:2644
+msgid "Mark this commit"
+msgstr "Marcar este commit"
+
+#: gitk:2645
+msgid "Return to mark"
+msgstr "Voltar à marca"
+
+#: gitk:2646
+msgid "Find descendant of this and mark"
+msgstr "Encontrar descendeste deste e da marca"
+
+#: gitk:2647
+msgid "Compare with marked commit"
+msgstr "Comparar com o commit marcado"
+
+#: gitk:2648 gitk:2659
+msgid "Diff this -> marked commit"
+msgstr "Diff este -> commit marcado"
+
+#: gitk:2649 gitk:2660
+msgid "Diff marked commit -> this"
+msgstr "Diff commit marcado -> este"
+
+#: gitk:2650
+msgid "Revert this commit"
+msgstr "Reverter este commit"
+
+#: gitk:2666
+msgid "Check out this branch"
+msgstr "Extrair este ramo"
+
+#: gitk:2667
+msgid "Remove this branch"
+msgstr "Remover este ramo"
+
+#: gitk:2668
+msgid "Copy branch name"
+msgstr "Copiar nome do ramo"
+
+#: gitk:2675
+msgid "Highlight this too"
+msgstr "Realçar este também"
+
+#: gitk:2676
+msgid "Highlight this only"
+msgstr "Realçar apenas este"
+
+#: gitk:2677
+msgid "External diff"
+msgstr "Diff externo"
+
+#: gitk:2678
+msgid "Blame parent commit"
+msgstr "Culpar commit pai"
+
+#: gitk:2679
+msgid "Copy path"
+msgstr "Copiar caminho"
+
+#: gitk:2686
+msgid "Show origin of this line"
+msgstr "Mostrar origem deste ficheiro"
+
+#: gitk:2687
+msgid "Run git gui blame on this line"
+msgstr "Executar git gui blame sobre esta linha"
+
+#: gitk:3031
+msgid "About gitk"
+msgstr "Sobre gitk"
+
+#: gitk:3033
+msgid ""
+"\n"
+"Gitk - a commit viewer for git\n"
+"\n"
+"Copyright © 2005-2016 Paul Mackerras\n"
+"\n"
+"Use and redistribute under the terms of the GNU General Public License"
+msgstr ""
+"\n"
+"Gitk - um visualizador de commits do git\n"
+"\n"
+"Copyright © 2005-2016 Paul Mackerras\n"
+"\n"
+"Use e redistribua sob os termos da GNU General Public License"
+
+#: gitk:3041 gitk:3108 gitk:9890
+msgid "Close"
+msgstr "Fechar"
+
+#: gitk:3062
+msgid "Gitk key bindings"
+msgstr "Atalhos do gitk"
+
+#: gitk:3065
+msgid "Gitk key bindings:"
+msgstr "Atalhos do gitk:"
+
+#: gitk:3067
+#, tcl-format
+msgid "<%s-Q>\t\tQuit"
+msgstr "<%s-Q>\t\tSair"
+
+#: gitk:3068
+#, tcl-format
+msgid "<%s-W>\t\tClose window"
+msgstr "<%s-W>\t\tFechar janela"
+
+#: gitk:3069
+msgid "<Home>\t\tMove to first commit"
+msgstr "<Home>\t\tMover para o primeiro commit"
+
+#: gitk:3070
+msgid "<End>\t\tMove to last commit"
+msgstr "<End>\t\tMover para o último commit"
+
+#: gitk:3071
+msgid "<Up>, p, k\tMove up one commit"
+msgstr "<Cima>, p, k\tMover para o commit acima"
+
+#: gitk:3072
+msgid "<Down>, n, j\tMove down one commit"
+msgstr "<Baixo>, n, j\tMover para o commit abaixo"
+
+#: gitk:3073
+msgid "<Left>, z, h\tGo back in history list"
+msgstr "<Esquerda>, z, h\tRecuar no histórico"
+
+#: gitk:3074
+msgid "<Right>, x, l\tGo forward in history list"
+msgstr "<Direita>, x, l\tAvançar no histórico"
+
+#: gitk:3075
+#, tcl-format
+msgid "<%s-n>\tGo to n-th parent of current commit in history list"
+msgstr "<%s-n>\tIr para o n-ésimo pai do commit atual no histórico"
+
+#: gitk:3076
+msgid "<PageUp>\tMove up one page in commit list"
+msgstr "<PageUp>\tMover a lista de commits uma página para cima"
+
+#: gitk:3077
+msgid "<PageDown>\tMove down one page in commit list"
+msgstr "<PageDown>\tMover a lista de commits uma página para baixo"
+
+#: gitk:3078
+#, tcl-format
+msgid "<%s-Home>\tScroll to top of commit list"
+msgstr "<%s-Home>\tDeslocar para o topo da lista"
+
+#: gitk:3079
+#, tcl-format
+msgid "<%s-End>\tScroll to bottom of commit list"
+msgstr "<%s-End>\tDeslocar para o fim da lista"
+
+#: gitk:3080
+#, tcl-format
+msgid "<%s-Up>\tScroll commit list up one line"
+msgstr "<%s-Cima>\tDeslocar a lista de commits uma linha para cima"
+
+#: gitk:3081
+#, tcl-format
+msgid "<%s-Down>\tScroll commit list down one line"
+msgstr "<%s-Baixo>\tDeslocar a lista de commits uma linha para baixo"
+
+#: gitk:3082
+#, tcl-format
+msgid "<%s-PageUp>\tScroll commit list up one page"
+msgstr "<%s-PageUp>\tDeslocar a lista de commits uma página para cima"
+
+#: gitk:3083
+#, tcl-format
+msgid "<%s-PageDown>\tScroll commit list down one page"
+msgstr "<%s-PageDown>\tDeslocar a lista de commits uma página para baixo"
+
+#: gitk:3084
+msgid "<Shift-Up>\tFind backwards (upwards, later commits)"
+msgstr "<Shift-Cima>\tProcurar para trás (para cima, commits posteriores)"
+
+#: gitk:3085
+msgid "<Shift-Down>\tFind forwards (downwards, earlier commits)"
+msgstr "<Shift-Baixo>\tProcurar para a frente (para baixo, commits anteriores)"
+
+#: gitk:3086
+msgid "<Delete>, b\tScroll diff view up one page"
+msgstr "<Delete>, b\tDeslocar vista diff uma página para cima"
+
+#: gitk:3087
+msgid "<Backspace>\tScroll diff view up one page"
+msgstr "<Retrocesso>\tDeslocar vista diff uma página para cima"
+
+#: gitk:3088
+msgid "<Space>\t\tScroll diff view down one page"
+msgstr "<Espaço>\tDeslocar vista diff uma página para baixo"
+
+#: gitk:3089
+msgid "u\t\tScroll diff view up 18 lines"
+msgstr "u\t\tDeslocar vista diff 18 linhas para cima"
+
+#: gitk:3090
+msgid "d\t\tScroll diff view down 18 lines"
+msgstr "d\t\tDeslocar vista diff 18 linhas para baixo"
+
+#: gitk:3091
+#, tcl-format
+msgid "<%s-F>\t\tFind"
+msgstr "<%s-F>\t\tProcurar"
+
+#: gitk:3092
+#, tcl-format
+msgid "<%s-G>\t\tMove to next find hit"
+msgstr "<%s-G>\t\tMover para a ocorrência seguinte"
+
+#: gitk:3093
+msgid "<Return>\tMove to next find hit"
+msgstr "<Return>\tMover para a ocorrência seguinte"
+
+#: gitk:3094
+msgid "g\t\tGo to commit"
+msgstr "g\t\tIr para o commit"
+
+#: gitk:3095
+msgid "/\t\tFocus the search box"
+msgstr "/\t\tFocar a caixa de pesquisa"
+
+#: gitk:3096
+msgid "?\t\tMove to previous find hit"
+msgstr "?\t\tMover para a ocorrência anterior"
+
+#: gitk:3097
+msgid "f\t\tScroll diff view to next file"
+msgstr "f\t\tDeslocar vista diff para o ficheiro seguinte"
+
+#: gitk:3098
+#, tcl-format
+msgid "<%s-S>\t\tSearch for next hit in diff view"
+msgstr "<%s-S>\t\tProcurar pela ocorrência seguinte na vista diff"
+
+#: gitk:3099
+#, tcl-format
+msgid "<%s-R>\t\tSearch for previous hit in diff view"
+msgstr "<%s-R>\t\tProcurar pela ocorrência anterior na vista diff"
+
+#: gitk:3100
+#, tcl-format
+msgid "<%s-KP+>\tIncrease font size"
+msgstr "<%s-KP+>\tAumentar o tamanho da letra"
+
+#: gitk:3101
+#, tcl-format
+msgid "<%s-plus>\tIncrease font size"
+msgstr "<%s-mais>\tAumentar o tamanho da letra"
+
+#: gitk:3102
+#, tcl-format
+msgid "<%s-KP->\tDecrease font size"
+msgstr "<%s-KP->\tDiminuir o tamanho da letra"
+
+#: gitk:3103
+#, tcl-format
+msgid "<%s-minus>\tDecrease font size"
+msgstr "<%s-menos>\tDiminuir o tamanho da letra"
+
+#: gitk:3104
+msgid "<F5>\t\tUpdate"
+msgstr "<F5>\t\tAtualizar"
+
+#: gitk:3569 gitk:3578
+#, tcl-format
+msgid "Error creating temporary directory %s:"
+msgstr "Erro ao criar ficheiro temporário %s:"
+
+#: gitk:3591
+#, tcl-format
+msgid "Error getting \"%s\" from %s:"
+msgstr "Erro ao obter \"%s\" de %s:"
+
+#: gitk:3654
+msgid "command failed:"
+msgstr "o comando falhou:"
+
+#: gitk:3803
+msgid "No such commit"
+msgstr "Commit inexistente"
+
+#: gitk:3817
+msgid "git gui blame: command failed:"
+msgstr "git gui blame: o comando falhou:"
+
+#: gitk:3848
+#, tcl-format
+msgid "Couldn't read merge head: %s"
+msgstr "Não foi possível ler a cabeça de integração: %s"
+
+#: gitk:3856
+#, tcl-format
+msgid "Error reading index: %s"
+msgstr "Erro ao ler o índice: %s"
+
+#: gitk:3881
+#, tcl-format
+msgid "Couldn't start git blame: %s"
+msgstr "Não foi possível iniciar git blame: %s"
+
+#: gitk:3884 gitk:6773
+msgid "Searching"
+msgstr "A procurar"
+
+#: gitk:3916
+#, tcl-format
+msgid "Error running git blame: %s"
+msgstr "Erro ao executar git blame: %s"
+
+#: gitk:3944
+#, tcl-format
+msgid "That line comes from commit %s, which is not in this view"
+msgstr "Essa linha provém do commit %s, que não está nesta vista"
+
+#: gitk:3958
+msgid "External diff viewer failed:"
+msgstr "Visualizador diff externo falhou:"
+
+#: gitk:4062
+msgid "All files"
+msgstr "Todos os ficheiros"
+
+#: gitk:4086
+msgid "View"
+msgstr "Vista"
+
+#: gitk:4089
+msgid "Gitk view definition"
+msgstr "Definição de vistas do gitk"
+
+#: gitk:4093
+msgid "Remember this view"
+msgstr "Recordar esta vista"
+
+#: gitk:4094
+msgid "References (space separated list):"
+msgstr "Referências (lista separada por espaço):"
+
+#: gitk:4095
+msgid "Branches & tags:"
+msgstr "Ramos e tags:"
+
+#: gitk:4096
+msgid "All refs"
+msgstr "Todas as referências"
+
+#: gitk:4097
+msgid "All (local) branches"
+msgstr "Todos os ramos (locais)"
+
+#: gitk:4098
+msgid "All tags"
+msgstr "Todas as tags"
+
+#: gitk:4099
+msgid "All remote-tracking branches"
+msgstr "Todos os ramos remotos de monitorização"
+
+#: gitk:4100
+msgid "Commit Info (regular expressions):"
+msgstr "Informação Sobre o Commit (expressões regulares):"
+
+#: gitk:4101
+msgid "Author:"
+msgstr "Autor:"
+
+#: gitk:4102
+msgid "Committer:"
+msgstr "Committer:"
+
+#: gitk:4103
+msgid "Commit Message:"
+msgstr "Mensagem de Commit:"
+
+#: gitk:4104
+msgid "Matches all Commit Info criteria"
+msgstr "Corresponde a todos os critérios da Informação Sobre o Commit"
+
+#: gitk:4105
+msgid "Matches no Commit Info criteria"
+msgstr "Não corresponde a nenhum critério da Informação Sobre o Commit"
+
+#: gitk:4106
+msgid "Changes to Files:"
+msgstr "Alterações nos Ficheiros:"
+
+#: gitk:4107
+msgid "Fixed String"
+msgstr "Cadeia Fixa"
+
+#: gitk:4108
+msgid "Regular Expression"
+msgstr "Expressão Regular"
+
+#: gitk:4109
+msgid "Search string:"
+msgstr "Procurar pela cadeia:"
+
+#: gitk:4110
+msgid ""
+"Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 "
+"15:27:38\"):"
+msgstr ""
+"Datas de Commit (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 "
+"15:27:38\"):"
+
+#: gitk:4111
+msgid "Since:"
+msgstr "Desde:"
+
+#: gitk:4112
+msgid "Until:"
+msgstr "Até:"
+
+#: gitk:4113
+msgid "Limit and/or skip a number of revisions (positive integer):"
+msgstr "Limitar e/ou ignorar um número de revisões (inteiro positivo):"
+
+#: gitk:4114
+msgid "Number to show:"
+msgstr "Número a mostrar:"
+
+#: gitk:4115
+msgid "Number to skip:"
+msgstr "Número a ignorar:"
+
+#: gitk:4116
+msgid "Miscellaneous options:"
+msgstr "Opções diversas:"
+
+#: gitk:4117
+msgid "Strictly sort by date"
+msgstr "Ordenar estritamente pela data"
+
+#: gitk:4118
+msgid "Mark branch sides"
+msgstr "Marcar lado dos ramos"
+
+#: gitk:4119
+msgid "Limit to first parent"
+msgstr "Restringir ao primeiro pai"
+
+#: gitk:4120
+msgid "Simple history"
+msgstr "Histórico simples"
+
+#: gitk:4121
+msgid "Additional arguments to git log:"
+msgstr "Argumentos adicionais ao git log:"
+
+#: gitk:4122
+msgid "Enter files and directories to include, one per line:"
+msgstr "Introduza ficheiros e diretórios para incluir, um por linha:"
+
+#: gitk:4123
+msgid "Command to generate more commits to include:"
+msgstr "Comando para gerar mais commits para incluir:"
+
+#: gitk:4247
+msgid "Gitk: edit view"
+msgstr "Gitk: editar vista"
+
+#: gitk:4255
+msgid "-- criteria for selecting revisions"
+msgstr "-- critério para selecionar revisões"
+
+#: gitk:4260
+msgid "View Name"
+msgstr "Nome da Vista"
+
+#: gitk:4335
+msgid "Apply (F5)"
+msgstr "Aplicar (F5)"
+
+#: gitk:4373
+msgid "Error in commit selection arguments:"
+msgstr "Erro nos argumentos de seleção de commits:"
+
+#: gitk:4428 gitk:4481 gitk:4943 gitk:4957 gitk:6227 gitk:12410 gitk:12411
+msgid "None"
+msgstr "Nenhum"
+
+#: gitk:5040 gitk:5045
+msgid "Descendant"
+msgstr "Descendente"
+
+#: gitk:5041
+msgid "Not descendant"
+msgstr "Não descendente"
+
+#: gitk:5048 gitk:5053
+msgid "Ancestor"
+msgstr "Antecessor"
+
+#: gitk:5049
+msgid "Not ancestor"
+msgstr "Não antecessor"
+
+#: gitk:5343
+msgid "Local changes checked in to index but not committed"
+msgstr "Alterações locais preparadas no índice mas não submetidas"
+
+#: gitk:5379
+msgid "Local uncommitted changes, not checked in to index"
+msgstr "Alterações locais não submetidas, não preparadas no índice"
+
+#: gitk:7153
+msgid "and many more"
+msgstr "e muitos mais"
+
+#: gitk:7156
+msgid "many"
+msgstr "muitos"
+
+#: gitk:7347
+msgid "Tags:"
+msgstr "Tags:"
+
+#: gitk:7364 gitk:7370 gitk:8844
+msgid "Parent"
+msgstr "Pai"
+
+#: gitk:7375
+msgid "Child"
+msgstr "Filho"
+
+#: gitk:7384
+msgid "Branch"
+msgstr "Ramo"
+
+#: gitk:7387
+msgid "Follows"
+msgstr "Sucede"
+
+#: gitk:7390
+msgid "Precedes"
+msgstr "Precede"
+
+#: gitk:7985
+#, tcl-format
+msgid "Error getting diffs: %s"
+msgstr "Erro ao obter diferenças: %s"
+
+#: gitk:8669
+msgid "Goto:"
+msgstr "Ir para:"
+
+#: gitk:8690
+#, tcl-format
+msgid "Short SHA1 id %s is ambiguous"
+msgstr "O id SHA1 abreviado %s é ambíguo"
+
+#: gitk:8697
+#, tcl-format
+msgid "Revision %s is not known"
+msgstr "A revisão %s não é conhecida"
+
+#: gitk:8707
+#, tcl-format
+msgid "SHA1 id %s is not known"
+msgstr "O id SHA1 %s não é conhecido"
+
+#: gitk:8709
+#, tcl-format
+msgid "Revision %s is not in the current view"
+msgstr "A revisão %s não se encontra na vista atual"
+
+#: gitk:8851 gitk:8866
+msgid "Date"
+msgstr "Data"
+
+#: gitk:8854
+msgid "Children"
+msgstr "Filhos"
+
+#: gitk:8917
+#, tcl-format
+msgid "Reset %s branch to here"
+msgstr "Repor o ramo %s para aqui"
+
+#: gitk:8919
+msgid "Detached head: can't reset"
+msgstr "Cabeça destacada: não é possível repor"
+
+#: gitk:9024 gitk:9030
+msgid "Skipping merge commit "
+msgstr "A ignorar commit de integração "
+
+#: gitk:9039 gitk:9044
+msgid "Error getting patch ID for "
+msgstr "Erro ao obter ID de patch de "
+
+#: gitk:9040 gitk:9045
+msgid " - stopping\n"
+msgstr " - a interromper\n"
+
+#: gitk:9050 gitk:9053 gitk:9061 gitk:9075 gitk:9084
+msgid "Commit "
+msgstr "Commit "
+
+#: gitk:9054
+msgid ""
+" is the same patch as\n"
+" "
+msgstr ""
+" é o mesmo patch que\n"
+" "
+
+#: gitk:9062
+msgid ""
+" differs from\n"
+" "
+msgstr ""
+" difere de\n"
+" "
+
+#: gitk:9064
+msgid ""
+"Diff of commits:\n"
+"\n"
+msgstr ""
+"Diferença dos commits:\n"
+"\n"
+
+#: gitk:9076 gitk:9085
+#, tcl-format
+msgid " has %s children - stopping\n"
+msgstr " tem %s filhos - a interromper\n"
+
+#: gitk:9104
+#, tcl-format
+msgid "Error writing commit to file: %s"
+msgstr "Erro ao escrever commit no ficheiro: %s"
+
+#: gitk:9110
+#, tcl-format
+msgid "Error diffing commits: %s"
+msgstr "Erro ao calcular as diferenças dos commits: %s"
+
+#: gitk:9156
+msgid "Top"
+msgstr "Topo"
+
+#: gitk:9157
+msgid "From"
+msgstr "De"
+
+#: gitk:9162
+msgid "To"
+msgstr "Para"
+
+#: gitk:9186
+msgid "Generate patch"
+msgstr "Gerar patch"
+
+#: gitk:9188
+msgid "From:"
+msgstr "De:"
+
+#: gitk:9197
+msgid "To:"
+msgstr "Para:"
+
+#: gitk:9206
+msgid "Reverse"
+msgstr "Reverter"
+
+#: gitk:9208 gitk:9418
+msgid "Output file:"
+msgstr "Ficheiro de saída:"
+
+#: gitk:9214
+msgid "Generate"
+msgstr "Gerar"
+
+#: gitk:9252
+msgid "Error creating patch:"
+msgstr "Erro ao criar patch:"
+
+#: gitk:9275 gitk:9406 gitk:9463
+msgid "ID:"
+msgstr "ID:"
+
+#: gitk:9284
+msgid "Tag name:"
+msgstr "Nome da tag:"
+
+#: gitk:9287
+msgid "Tag message is optional"
+msgstr "A mensagem da tag é opcional"
+
+#: gitk:9289
+msgid "Tag message:"
+msgstr "Mensagem da tag:"
+
+#: gitk:9293 gitk:9472
+msgid "Create"
+msgstr "Criar"
+
+#: gitk:9311
+msgid "No tag name specified"
+msgstr "Nenhum nome de tag especificado"
+
+#: gitk:9315
+#, tcl-format
+msgid "Tag \"%s\" already exists"
+msgstr "A tag \"%s\" já existe"
+
+#: gitk:9325
+msgid "Error creating tag:"
+msgstr "Erro ao criar tag:"
+
+#: gitk:9415
+msgid "Command:"
+msgstr "Comando:"
+
+#: gitk:9423
+msgid "Write"
+msgstr "Escrever"
+
+#: gitk:9441
+msgid "Error writing commit:"
+msgstr "Erro ao escrever commit:"
+
+#: gitk:9468
+msgid "Name:"
+msgstr "Nome:"
+
+#: gitk:9491
+msgid "Please specify a name for the new branch"
+msgstr "Especifique um nome para o novo ramo"
+
+#: gitk:9496
+#, tcl-format
+msgid "Branch '%s' already exists. Overwrite?"
+msgstr "O ramo '%s' já existe. Substituí-lo?"
+
+#: gitk:9563
+#, tcl-format
+msgid "Commit %s is already included in branch %s -- really re-apply it?"
+msgstr "O commit %s já está incluído no ramo %s -- reaplicá-lo mesmo assim?"
+
+#: gitk:9568
+msgid "Cherry-picking"
+msgstr "A efetuar cherry-pick"
+
+#: gitk:9577
+#, tcl-format
+msgid ""
+"Cherry-pick failed because of local changes to file '%s'.\n"
+"Please commit, reset or stash your changes and try again."
+msgstr ""
+"Falha ao efetuar cherry-pick devido a alterações locais no ficheiro '%s'.\n"
+"Submeta, empilhe ou reponha as alterações e tente de novo."
+
+#: gitk:9583
+msgid ""
+"Cherry-pick failed because of merge conflict.\n"
+"Do you wish to run git citool to resolve it?"
+msgstr ""
+"Falha ao efetuar cherry-pick devido a conflito de integração.\n"
+"Deseja executar git citool para resolvê-lo?"
+
+#: gitk:9599 gitk:9657
+msgid "No changes committed"
+msgstr "Não foi submetida nenhum alteração"
+
+#: gitk:9626
+#, tcl-format
+msgid "Commit %s is not included in branch %s -- really revert it?"
+msgstr "O commit %s não está incluído no ramo %s -- revertê-lo mesmo assim?"
+
+#: gitk:9631
+msgid "Reverting"
+msgstr "A reverter"
+
+#: gitk:9639
+#, tcl-format
+msgid ""
+"Revert failed because of local changes to the following files:%s Please "
+"commit, reset or stash your changes and try again."
+msgstr ""
+"Falha ao reverter devido a alterações locais nos seguintes ficheiros:%s "
+"Submeta, empilhe ou reponha as alterações e tente de novo."
+
+#: gitk:9643
+msgid ""
+"Revert failed because of merge conflict.\n"
+" Do you wish to run git citool to resolve it?"
+msgstr ""
+"Falha ao reverter devido a conflito de integração.\n"
+"Deseja executar git citool para resolvê-lo?"
+
+#: gitk:9686
+msgid "Confirm reset"
+msgstr "Confirmar reposição"
+
+#: gitk:9688
+#, tcl-format
+msgid "Reset branch %s to %s?"
+msgstr "Repor o ramo %s para %s?"
+
+#: gitk:9690
+msgid "Reset type:"
+msgstr "Tipo de reposição:"
+
+#: gitk:9693
+msgid "Soft: Leave working tree and index untouched"
+msgstr "Suave: Deixar a árvore de trabalho e o índice intactos"
+
+#: gitk:9696
+msgid "Mixed: Leave working tree untouched, reset index"
+msgstr "Misto: Deixar a árvore de trabalho intacta, repor índice"
+
+#: gitk:9699
+msgid ""
+"Hard: Reset working tree and index\n"
+"(discard ALL local changes)"
+msgstr ""
+"Forte: Repor árvore de trabalho e índice\n"
+"(descartar TODAS as alterações locais)"
+
+#: gitk:9716
+msgid "Resetting"
+msgstr "A repor"
+
+#: gitk:9776
+msgid "Checking out"
+msgstr "A extrair"
+
+#: gitk:9829
+msgid "Cannot delete the currently checked-out branch"
+msgstr "Não é possível eliminar o ramo atual extraído"
+
+#: gitk:9835
+#, tcl-format
+msgid ""
+"The commits on branch %s aren't on any other branch.\n"
+"Really delete branch %s?"
+msgstr ""
+"Os commits no ramo %s não estão presentes em mais nenhum ramo.\n"
+"Eliminar o ramo %s mesmo assim?"
+
+#: gitk:9866
+#, tcl-format
+msgid "Tags and heads: %s"
+msgstr "Tags e cabeças: %s"
+
+#: gitk:9883
+msgid "Filter"
+msgstr "Filtrar"
+
+#: gitk:10179
+msgid ""
+"Error reading commit topology information; branch and preceding/following "
+"tag information will be incomplete."
+msgstr ""
+"Erro ao ler informação de topologia do commit; a informação do ramo e da tag "
+"precedente/seguinte ficará incompleta."
+
+#: gitk:11156
+msgid "Tag"
+msgstr "Tag"
+
+#: gitk:11160
+msgid "Id"
+msgstr "Id"
+
+#: gitk:11243
+msgid "Gitk font chooser"
+msgstr "Escolha de tipo de letra do gitk"
+
+#: gitk:11260
+msgid "B"
+msgstr "B"
+
+#: gitk:11263
+msgid "I"
+msgstr "I"
+
+#: gitk:11381
+msgid "Commit list display options"
+msgstr "Opções de visualização da lista de commits"
+
+#: gitk:11384
+msgid "Maximum graph width (lines)"
+msgstr "Largura máxima do gráfico (linhas)"
+
+#: gitk:11388
+#, no-tcl-format
+msgid "Maximum graph width (% of pane)"
+msgstr "Largura máxima do gráfico (% do painel)"
+
+#: gitk:11391
+msgid "Show local changes"
+msgstr "Mostrar alterações locais"
+
+#: gitk:11394
+msgid "Auto-select SHA1 (length)"
+msgstr "Selecionar automaticamente SHA1 (largura)"
+
+#: gitk:11398
+msgid "Hide remote refs"
+msgstr "Ocultar referências remotas"
+
+#: gitk:11402
+msgid "Diff display options"
+msgstr "Opções de visualização de diferenças"
+
+#: gitk:11404
+msgid "Tab spacing"
+msgstr "Espaçamento da tabulação"
+
+#: gitk:11407
+msgid "Display nearby tags/heads"
+msgstr "Mostrar tags/cabeças próximas"
+
+#: gitk:11410
+msgid "Maximum # tags/heads to show"
+msgstr "Nº máximo de tags/cabeças a mostrar"
+
+#: gitk:11413
+msgid "Limit diffs to listed paths"
+msgstr "Limitar diferenças aos caminhos listados"
+
+#: gitk:11416
+msgid "Support per-file encodings"
+msgstr "Suportar codificação por cada ficheiro"
+
+#: gitk:11422 gitk:11569
+msgid "External diff tool"
+msgstr "Ferramenta diff externa"
+
+#: gitk:11423
+msgid "Choose..."
+msgstr "Escolher..."
+
+#: gitk:11428
+msgid "General options"
+msgstr "Opções gerais"
+
+#: gitk:11431
+msgid "Use themed widgets"
+msgstr "Usar widgets com estilo"
+
+#: gitk:11433
+msgid "(change requires restart)"
+msgstr "(alteração exige reiniciar)"
+
+#: gitk:11435
+msgid "(currently unavailable)"
+msgstr "(não disponível de momento)"
+
+#: gitk:11446
+msgid "Colors: press to choose"
+msgstr "Cores: pressione para escolher"
+
+#: gitk:11449
+msgid "Interface"
+msgstr "Interface"
+
+#: gitk:11450
+msgid "interface"
+msgstr "interface"
+
+#: gitk:11453
+msgid "Background"
+msgstr "Fundo"
+
+#: gitk:11454 gitk:11484
+msgid "background"
+msgstr "fundo"
+
+#: gitk:11457
+msgid "Foreground"
+msgstr "Primeiro plano"
+
+#: gitk:11458
+msgid "foreground"
+msgstr "primeiro plano"
+
+#: gitk:11461
+msgid "Diff: old lines"
+msgstr "Diff: linhas antigas"
+
+#: gitk:11462
+msgid "diff old lines"
+msgstr "diff linhas antigas"
+
+#: gitk:11466
+msgid "Diff: new lines"
+msgstr "Diff: linhas novas"
+
+#: gitk:11467
+msgid "diff new lines"
+msgstr "diff linhas novas"
+
+#: gitk:11471
+msgid "Diff: hunk header"
+msgstr "Diff: cabeçalho do excerto"
+
+#: gitk:11473
+msgid "diff hunk header"
+msgstr "diff cabeçalho do excerto"
+
+#: gitk:11477
+msgid "Marked line bg"
+msgstr "Fundo da linha marcada"
+
+#: gitk:11479
+msgid "marked line background"
+msgstr "fundo da linha marcada"
+
+#: gitk:11483
+msgid "Select bg"
+msgstr "Selecionar fundo"
+
+#: gitk:11492
+msgid "Fonts: press to choose"
+msgstr "Tipo de letra: pressione para escolher"
+
+#: gitk:11494
+msgid "Main font"
+msgstr "Tipo de letra principal"
+
+#: gitk:11495
+msgid "Diff display font"
+msgstr "Tipo de letra ao mostrar diferenças"
+
+#: gitk:11496
+msgid "User interface font"
+msgstr "Tipo de letra da interface de utilizador"
+
+#: gitk:11518
+msgid "Gitk preferences"
+msgstr "Preferências do gitk"
+
+#: gitk:11527
+msgid "General"
+msgstr "Geral"
+
+#: gitk:11528
+msgid "Colors"
+msgstr "Cores"
+
+#: gitk:11529
+msgid "Fonts"
+msgstr "Tipos de letra"
+
+#: gitk:11579
+#, tcl-format
+msgid "Gitk: choose color for %s"
+msgstr "Gitk: escolher cor de %s"
+
+#: gitk:12092
+msgid ""
+"Sorry, gitk cannot run with this version of Tcl/Tk.\n"
+" Gitk requires at least Tcl/Tk 8.4."
+msgstr ""
+"Não é possível executar o gitk com esta versão do Tcl/Tk.\n"
+"O gitk requer pelo menos Tcl/Tk 8.4."
+
+#: gitk:12302
+msgid "Cannot find a git repository here."
+msgstr "Não foi encontrado nenhum repositório git aqui."
+
+#: gitk:12349
+#, tcl-format
+msgid "Ambiguous argument '%s': both revision and filename"
+msgstr "Argumento '%s' ambíguo: pode ser uma revisão ou um ficheiro"
+
+#: gitk:12361
+msgid "Bad arguments to gitk:"
+msgstr "Argumentos do gitk incorretos:"
diff --git a/gitk-git/po/ru.po b/gitk-git/po/ru.po
index 17ed026..9b08c26 100644
--- a/gitk-git/po/ru.po
+++ b/gitk-git/po/ru.po
@@ -3,15 +3,15 @@
# Translators:
# 0xAX <kuleshovmail@gmail.com>, 2014
# Alex Riesen <raa.lkml@gmail.com>, 2015
-# Dimitriy Ryazantcev <DJm00n@mail.ru>, 2015
+# Dimitriy Ryazantcev <DJm00n@mail.ru>, 2015-2016
# Dmitry Potapov <dpotapov@gmail.com>, 2009
# Skip <bsvskip@rambler.ru>, 2011
msgid ""
msgstr ""
"Project-Id-Version: Git Russian Localization Project\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2015-05-17 14:32+1000\n"
-"PO-Revision-Date: 2015-10-12 10:14+0000\n"
+"POT-Creation-Date: 2016-12-15 00:18+0200\n"
+"PO-Revision-Date: 2016-12-14 22:23+0000\n"
"Last-Translator: Dimitriy Ryazantcev <DJm00n@mail.ru>\n"
"Language-Team: Russian (http://www.transifex.com/djm00n/git-po-ru/language/ru/)\n"
"MIME-Version: 1.0\n"
@@ -24,11 +24,11 @@ msgstr ""
msgid "Couldn't get list of unmerged files:"
msgstr "Невозможно получить список файлов незавершённой операции слияния:"
-#: gitk:212 gitk:2381
+#: gitk:212 gitk:2403
msgid "Color words"
msgstr "Цветные слова"
-#: gitk:217 gitk:2381 gitk:8220 gitk:8253
+#: gitk:217 gitk:2403 gitk:8249 gitk:8282
msgid "Markup words"
msgstr "Помеченые слова"
@@ -58,1272 +58,1314 @@ msgstr "Ошибка запуска git log:"
msgid "Reading"
msgstr "Чтение"
-#: gitk:496 gitk:4525
+#: gitk:496 gitk:4549
msgid "Reading commits..."
msgstr "Чтение коммитов..."
-#: gitk:499 gitk:1637 gitk:4528
+#: gitk:499 gitk:1641 gitk:4552
msgid "No commits selected"
msgstr "Ничего не выбрано"
-#: gitk:1445 gitk:4045 gitk:12432
+#: gitk:1449 gitk:4069 gitk:12583
msgid "Command line"
msgstr "Командная строка"
-#: gitk:1511
+#: gitk:1515
msgid "Can't parse git log output:"
msgstr "Ошибка обработки вывода команды git log:"
-#: gitk:1740
+#: gitk:1744
msgid "No commit information available"
msgstr "Нет информации о коммите"
-#: gitk:1903 gitk:1932 gitk:4315 gitk:9669 gitk:11241 gitk:11521
+#: gitk:1907 gitk:1936 gitk:4339 gitk:9789 gitk:11388 gitk:11668
msgid "OK"
msgstr "Ok"
-#: gitk:1934 gitk:4317 gitk:9196 gitk:9275 gitk:9391 gitk:9440 gitk:9671
-#: gitk:11242 gitk:11522
+#: gitk:1938 gitk:4341 gitk:9225 gitk:9304 gitk:9434 gitk:9520 gitk:9791
+#: gitk:11389 gitk:11669
msgid "Cancel"
msgstr "Отмена"
-#: gitk:2069
+#: gitk:2087
msgid "&Update"
msgstr "Обновить"
-#: gitk:2070
+#: gitk:2088
msgid "&Reload"
msgstr "Перечитать"
-#: gitk:2071
+#: gitk:2089
msgid "Reread re&ferences"
msgstr "Обновить список ссылок"
-#: gitk:2072
+#: gitk:2090
msgid "&List references"
msgstr "Список ссылок"
-#: gitk:2074
+#: gitk:2092
msgid "Start git &gui"
msgstr "Запустить git gui"
-#: gitk:2076
+#: gitk:2094
msgid "&Quit"
msgstr "Завершить"
-#: gitk:2068
+#: gitk:2086
msgid "&File"
msgstr "Файл"
-#: gitk:2080
+#: gitk:2098
msgid "&Preferences"
msgstr "Настройки"
-#: gitk:2079
+#: gitk:2097
msgid "&Edit"
msgstr "Редактировать"
-#: gitk:2084
+#: gitk:2102
msgid "&New view..."
msgstr "Новое представление..."
-#: gitk:2085
+#: gitk:2103
msgid "&Edit view..."
msgstr "Редактировать представление..."
-#: gitk:2086
+#: gitk:2104
msgid "&Delete view"
msgstr "Удалить представление"
-#: gitk:2088 gitk:4043
+#: gitk:2106
msgid "&All files"
msgstr "Все файлы"
-#: gitk:2083 gitk:4067
+#: gitk:2101
msgid "&View"
msgstr "Представление"
-#: gitk:2093 gitk:2103 gitk:3012
+#: gitk:2111 gitk:2121
msgid "&About gitk"
msgstr "О gitk"
-#: gitk:2094 gitk:2108
+#: gitk:2112 gitk:2126
msgid "&Key bindings"
msgstr "Назначения клавиатуры"
-#: gitk:2092 gitk:2107
+#: gitk:2110 gitk:2125
msgid "&Help"
msgstr "Подсказка"
-#: gitk:2185 gitk:8652
+#: gitk:2203 gitk:8681
msgid "SHA1 ID:"
msgstr "SHA1 ID:"
-#: gitk:2229
+#: gitk:2247
msgid "Row"
msgstr "Строка"
-#: gitk:2267
+#: gitk:2285
msgid "Find"
msgstr "Поиск"
-#: gitk:2295
+#: gitk:2313
msgid "commit"
msgstr "коммит"
-#: gitk:2299 gitk:2301 gitk:4687 gitk:4710 gitk:4734 gitk:6755 gitk:6827
-#: gitk:6912
+#: gitk:2317 gitk:2319 gitk:4711 gitk:4734 gitk:4758 gitk:6779 gitk:6851
+#: gitk:6936
msgid "containing:"
msgstr "содержащее:"
-#: gitk:2302 gitk:3526 gitk:3531 gitk:4763
+#: gitk:2320 gitk:3550 gitk:3555 gitk:4787
msgid "touching paths:"
msgstr "касательно файлов:"
-#: gitk:2303 gitk:4777
+#: gitk:2321 gitk:4801
msgid "adding/removing string:"
msgstr "добавив/удалив строку:"
-#: gitk:2304 gitk:4779
+#: gitk:2322 gitk:4803
msgid "changing lines matching:"
msgstr "изменяя совпадающие строки:"
-#: gitk:2313 gitk:2315 gitk:4766
+#: gitk:2331 gitk:2333 gitk:4790
msgid "Exact"
msgstr "Точно"
-#: gitk:2315 gitk:4854 gitk:6723
+#: gitk:2333 gitk:4878 gitk:6747
msgid "IgnCase"
msgstr "Игнорировать большие/маленькие"
-#: gitk:2315 gitk:4736 gitk:4852 gitk:6719
+#: gitk:2333 gitk:4760 gitk:4876 gitk:6743
msgid "Regexp"
msgstr "Регулярные выражения"
-#: gitk:2317 gitk:2318 gitk:4874 gitk:4904 gitk:4911 gitk:6848 gitk:6916
+#: gitk:2335 gitk:2336 gitk:4898 gitk:4928 gitk:4935 gitk:6872 gitk:6940
msgid "All fields"
msgstr "Во всех полях"
-#: gitk:2318 gitk:4871 gitk:4904 gitk:6786
+#: gitk:2336 gitk:4895 gitk:4928 gitk:6810
msgid "Headline"
msgstr "Заголовок"
-#: gitk:2319 gitk:4871 gitk:6786 gitk:6916 gitk:7389
+#: gitk:2337 gitk:4895 gitk:6810 gitk:6940 gitk:7413
msgid "Comments"
msgstr "Комментарии"
-#: gitk:2319 gitk:4871 gitk:4876 gitk:4911 gitk:6786 gitk:7324 gitk:8830
-#: gitk:8845
+#: gitk:2337 gitk:4895 gitk:4900 gitk:4935 gitk:6810 gitk:7348 gitk:8859
+#: gitk:8874
msgid "Author"
msgstr "Автор"
-#: gitk:2319 gitk:4871 gitk:6786 gitk:7326
+#: gitk:2337 gitk:4895 gitk:6810 gitk:7350
msgid "Committer"
msgstr "Коммитер"
-#: gitk:2350
+#: gitk:2371
msgid "Search"
msgstr "Найти"
-#: gitk:2358
+#: gitk:2379
msgid "Diff"
msgstr "Сравнить"
-#: gitk:2360
+#: gitk:2381
msgid "Old version"
msgstr "Старая версия"
-#: gitk:2362
+#: gitk:2383
msgid "New version"
msgstr "Новая версия"
-#: gitk:2364
+#: gitk:2386
msgid "Lines of context"
msgstr "Строк контекста"
-#: gitk:2374
+#: gitk:2396
msgid "Ignore space change"
msgstr "Игнорировать пробелы"
-#: gitk:2378 gitk:2380 gitk:7959 gitk:8206
+#: gitk:2400 gitk:2402 gitk:7983 gitk:8235
msgid "Line diff"
msgstr "Изменения строк"
-#: gitk:2445
+#: gitk:2467
msgid "Patch"
msgstr "Патч"
-#: gitk:2447
+#: gitk:2469
msgid "Tree"
msgstr "Файлы"
-#: gitk:2617 gitk:2637
+#: gitk:2639 gitk:2660
msgid "Diff this -> selected"
msgstr "Сравнить этот коммит с выделенным"
-#: gitk:2618 gitk:2638
+#: gitk:2640 gitk:2661
msgid "Diff selected -> this"
msgstr "Сравнить выделенный с этим коммитом"
-#: gitk:2619 gitk:2639
+#: gitk:2641 gitk:2662
msgid "Make patch"
msgstr "Создать патч"
-#: gitk:2620 gitk:9254
+#: gitk:2642 gitk:9283
msgid "Create tag"
msgstr "Создать метку"
-#: gitk:2621 gitk:9371
+#: gitk:2643
+msgid "Copy commit summary"
+msgstr "Копировать информацию о коммите"
+
+#: gitk:2644 gitk:9414
msgid "Write commit to file"
msgstr "Сохранить коммит в файл"
-#: gitk:2622 gitk:9428
+#: gitk:2645
msgid "Create new branch"
msgstr "Создать ветку"
-#: gitk:2623
+#: gitk:2646
msgid "Cherry-pick this commit"
-msgstr "Отбор лучшего для этого коммита"
+msgstr "Копировать этот коммит в текущую ветку"
-#: gitk:2624
+#: gitk:2647
msgid "Reset HEAD branch to here"
msgstr "Установить HEAD на этот коммит"
-#: gitk:2625
+#: gitk:2648
msgid "Mark this commit"
msgstr "Пометить этот коммит"
-#: gitk:2626
+#: gitk:2649
msgid "Return to mark"
msgstr "Вернуться на пометку"
-#: gitk:2627
+#: gitk:2650
msgid "Find descendant of this and mark"
msgstr "Найти и пометить потомка этого коммита"
-#: gitk:2628
+#: gitk:2651
msgid "Compare with marked commit"
msgstr "Сравнить с помеченным коммитом"
-#: gitk:2629 gitk:2640
+#: gitk:2652 gitk:2663
msgid "Diff this -> marked commit"
msgstr "Сравнить выделенное с помеченным коммитом"
-#: gitk:2630 gitk:2641
+#: gitk:2653 gitk:2664
msgid "Diff marked commit -> this"
msgstr "Сравнить помеченный с этим коммитом"
-#: gitk:2631
+#: gitk:2654
msgid "Revert this commit"
-msgstr "Возврат этого коммита"
+msgstr "Обратить изменения этого коммита"
-#: gitk:2647
+#: gitk:2670
msgid "Check out this branch"
msgstr "Перейти на эту ветку"
-#: gitk:2648
+#: gitk:2671
+msgid "Rename this branch"
+msgstr "Переименовать эту ветку"
+
+#: gitk:2672
msgid "Remove this branch"
msgstr "Удалить эту ветку"
-#: gitk:2649
+#: gitk:2673
msgid "Copy branch name"
msgstr "Копировать имя ветки"
-#: gitk:2656
+#: gitk:2680
msgid "Highlight this too"
msgstr "Подсветить этот тоже"
-#: gitk:2657
+#: gitk:2681
msgid "Highlight this only"
msgstr "Подсветить только этот"
-#: gitk:2658
+#: gitk:2682
msgid "External diff"
msgstr "Программа сравнения"
-#: gitk:2659
+#: gitk:2683
msgid "Blame parent commit"
msgstr "Авторы изменений родительского коммита"
-#: gitk:2660
+#: gitk:2684
msgid "Copy path"
msgstr "Копировать путь"
-#: gitk:2667
+#: gitk:2691
msgid "Show origin of this line"
msgstr "Показать источник этой строки"
-#: gitk:2668
+#: gitk:2692
msgid "Run git gui blame on this line"
msgstr "Запустить git gui blame для этой строки"
-#: gitk:3014
+#: gitk:3036
+msgid "About gitk"
+msgstr "О gitk"
+
+#: gitk:3038
msgid ""
"\n"
"Gitk - a commit viewer for git\n"
"\n"
-"Copyright 2005-2014 Paul Mackerras\n"
+"Copyright © 2005-2016 Paul Mackerras\n"
"\n"
"Use and redistribute under the terms of the GNU General Public License"
-msgstr "\nGitk - программа просмотра истории репозиториев git\n\n© 2005-2014 Paul Mackerras\n\nИспользование и распространение согласно условиям GNU General Public License"
+msgstr "\nGitk — программа просмотра истории репозиториев git\n\n© 2005-2016 Paul Mackerras\n\nИспользование и распространение согласно условиям GNU General Public License"
-#: gitk:3022 gitk:3089 gitk:9857
+#: gitk:3046 gitk:3113 gitk:10004
msgid "Close"
msgstr "Закрыть"
-#: gitk:3043
+#: gitk:3067
msgid "Gitk key bindings"
msgstr "Назначения клавиатуры в Gitk"
-#: gitk:3046
+#: gitk:3070
msgid "Gitk key bindings:"
msgstr "Назначения клавиатуры в Gitk:"
-#: gitk:3048
+#: gitk:3072
#, tcl-format
msgid "<%s-Q>\t\tQuit"
msgstr "<%s-Q>\t\tЗавершить"
-#: gitk:3049
+#: gitk:3073
#, tcl-format
msgid "<%s-W>\t\tClose window"
msgstr "<%s-W>\t\tЗакрыть окно"
-#: gitk:3050
+#: gitk:3074
msgid "<Home>\t\tMove to first commit"
msgstr "<Home>\t\tПерейти к первому коммиту"
-#: gitk:3051
+#: gitk:3075
msgid "<End>\t\tMove to last commit"
msgstr "<End>\t\tПерейти к последнему коммиту"
-#: gitk:3052
+#: gitk:3076
msgid "<Up>, p, k\tMove up one commit"
msgstr "<Up>, p, k\tПерейти на один коммит вверх"
-#: gitk:3053
+#: gitk:3077
msgid "<Down>, n, j\tMove down one commit"
msgstr "<Down>, n, j\tПерейти на один коммит вниз"
-#: gitk:3054
+#: gitk:3078
msgid "<Left>, z, h\tGo back in history list"
msgstr "<Left>, z, h\tПоказать ранее посещённое состояние"
-#: gitk:3055
+#: gitk:3079
msgid "<Right>, x, l\tGo forward in history list"
msgstr "<Right>, x, l\tПоказать следующий посещённый коммит"
-#: gitk:3056
+#: gitk:3080
#, tcl-format
msgid "<%s-n>\tGo to n-th parent of current commit in history list"
msgstr "<%s-n>\tПерейти на n родителя от текущего коммита"
-#: gitk:3057
+#: gitk:3081
msgid "<PageUp>\tMove up one page in commit list"
msgstr "<PageUp>\tПерейти на страницу выше в списке коммитов"
-#: gitk:3058
+#: gitk:3082
msgid "<PageDown>\tMove down one page in commit list"
msgstr "<PageDown>\tПерейти на страницу ниже в списке коммитов"
-#: gitk:3059
+#: gitk:3083
#, tcl-format
msgid "<%s-Home>\tScroll to top of commit list"
msgstr "<%s-Home>\tПерейти на начало списка коммитов"
-#: gitk:3060
+#: gitk:3084
#, tcl-format
msgid "<%s-End>\tScroll to bottom of commit list"
msgstr "<%s-End>\tПерейти на конец списка коммитов"
-#: gitk:3061
+#: gitk:3085
#, tcl-format
msgid "<%s-Up>\tScroll commit list up one line"
msgstr "<%s-Up>\tПровернуть список коммитов вверх"
-#: gitk:3062
+#: gitk:3086
#, tcl-format
msgid "<%s-Down>\tScroll commit list down one line"
msgstr "<%s-Down>\tПровернуть список коммитов вниз"
-#: gitk:3063
+#: gitk:3087
#, tcl-format
msgid "<%s-PageUp>\tScroll commit list up one page"
msgstr "<%s-PageUp>\tПровернуть список коммитов на страницу вверх"
-#: gitk:3064
+#: gitk:3088
#, tcl-format
msgid "<%s-PageDown>\tScroll commit list down one page"
msgstr "<%s-PageDown>\tПровернуть список коммитов на страницу вниз"
-#: gitk:3065
+#: gitk:3089
msgid "<Shift-Up>\tFind backwards (upwards, later commits)"
msgstr "<Shift-Up>\tПоиск в обратном порядке (вверх, среди новых коммитов)"
-#: gitk:3066
+#: gitk:3090
msgid "<Shift-Down>\tFind forwards (downwards, earlier commits)"
msgstr "<Shift-Down>\tПоиск (вниз, среди старых коммитов)"
-#: gitk:3067
+#: gitk:3091
msgid "<Delete>, b\tScroll diff view up one page"
msgstr "<Delete>, b\tПрокрутить список изменений на страницу выше"
-#: gitk:3068
+#: gitk:3092
msgid "<Backspace>\tScroll diff view up one page"
msgstr "<Backspace>\tПрокрутить список изменений на страницу выше"
-#: gitk:3069
+#: gitk:3093
msgid "<Space>\t\tScroll diff view down one page"
msgstr "<Leertaste>\t\tПрокрутить список изменений на страницу ниже"
-#: gitk:3070
+#: gitk:3094
msgid "u\t\tScroll diff view up 18 lines"
msgstr "u\t\tПрокрутить список изменений на 18 строк вверх"
-#: gitk:3071
+#: gitk:3095
msgid "d\t\tScroll diff view down 18 lines"
msgstr "d\t\tПрокрутить список изменений на 18 строк вниз"
-#: gitk:3072
+#: gitk:3096
#, tcl-format
msgid "<%s-F>\t\tFind"
msgstr "<%s-F>\t\tПоиск"
-#: gitk:3073
+#: gitk:3097
#, tcl-format
msgid "<%s-G>\t\tMove to next find hit"
msgstr "<%s-G>\t\tПерейти к следующему найденному коммиту"
-#: gitk:3074
+#: gitk:3098
msgid "<Return>\tMove to next find hit"
msgstr "<Return>\tПерейти к следующему найденному коммиту"
-#: gitk:3075
+#: gitk:3099
msgid "g\t\tGo to commit"
msgstr "g\t\tПерейти на коммит"
-#: gitk:3076
+#: gitk:3100
msgid "/\t\tFocus the search box"
msgstr "/\t\tПерейти к полю поиска"
-#: gitk:3077
+#: gitk:3101
msgid "?\t\tMove to previous find hit"
msgstr "?\t\tПерейти к предыдущему найденному коммиту"
-#: gitk:3078
+#: gitk:3102
msgid "f\t\tScroll diff view to next file"
msgstr "f\t\tПрокрутить список изменений к следующему файлу"
-#: gitk:3079
+#: gitk:3103
#, tcl-format
msgid "<%s-S>\t\tSearch for next hit in diff view"
msgstr "<%s-S>\t\tПродолжить поиск в списке изменений"
-#: gitk:3080
+#: gitk:3104
#, tcl-format
msgid "<%s-R>\t\tSearch for previous hit in diff view"
msgstr "<%s-R>\t\tПерейти к предыдущему найденному тексту в списке изменений"
-#: gitk:3081
+#: gitk:3105
#, tcl-format
msgid "<%s-KP+>\tIncrease font size"
msgstr "<%s-KP+>\tУвеличить размер шрифта"
-#: gitk:3082
+#: gitk:3106
#, tcl-format
msgid "<%s-plus>\tIncrease font size"
msgstr "<%s-plus>\tУвеличить размер шрифта"
-#: gitk:3083
+#: gitk:3107
#, tcl-format
msgid "<%s-KP->\tDecrease font size"
msgstr "<%s-KP->\tУменьшить размер шрифта"
-#: gitk:3084
+#: gitk:3108
#, tcl-format
msgid "<%s-minus>\tDecrease font size"
msgstr "<%s-minus>\tУменьшить размер шрифта"
-#: gitk:3085
+#: gitk:3109
msgid "<F5>\t\tUpdate"
msgstr "<F5>\t\tОбновить"
-#: gitk:3550 gitk:3559
+#: gitk:3574 gitk:3583
#, tcl-format
msgid "Error creating temporary directory %s:"
msgstr "Ошибка создания временного каталога %s:"
-#: gitk:3572
+#: gitk:3596
#, tcl-format
msgid "Error getting \"%s\" from %s:"
msgstr "Ошибка получения «%s» из %s:"
-#: gitk:3635
+#: gitk:3659
msgid "command failed:"
msgstr "ошибка выполнения команды:"
-#: gitk:3784
+#: gitk:3808
msgid "No such commit"
msgstr "Коммит не найден"
-#: gitk:3798
+#: gitk:3822
msgid "git gui blame: command failed:"
msgstr "git gui blame: ошибка выполнения команды:"
-#: gitk:3829
+#: gitk:3853
#, tcl-format
msgid "Couldn't read merge head: %s"
msgstr "Ошибка чтения MERGE_HEAD: %s"
-#: gitk:3837
+#: gitk:3861
#, tcl-format
msgid "Error reading index: %s"
msgstr "Ошибка чтения индекса: %s"
-#: gitk:3862
+#: gitk:3886
#, tcl-format
msgid "Couldn't start git blame: %s"
msgstr "Ошибка запуска git blame: %s"
-#: gitk:3865 gitk:6754
+#: gitk:3889 gitk:6778
msgid "Searching"
msgstr "Поиск"
-#: gitk:3897
+#: gitk:3921
#, tcl-format
msgid "Error running git blame: %s"
msgstr "Ошибка выполнения git blame: %s"
-#: gitk:3925
+#: gitk:3949
#, tcl-format
msgid "That line comes from commit %s, which is not in this view"
msgstr "Эта строка принадлежит коммиту %s, который не показан в этом представлении"
-#: gitk:3939
+#: gitk:3963
msgid "External diff viewer failed:"
msgstr "Ошибка выполнения программы сравнения:"
-#: gitk:4070
+#: gitk:4067
+msgid "All files"
+msgstr "Все файлы"
+
+#: gitk:4091
+msgid "View"
+msgstr "Представление"
+
+#: gitk:4094
msgid "Gitk view definition"
msgstr "Gitk определение представлений"
-#: gitk:4074
+#: gitk:4098
msgid "Remember this view"
msgstr "Запомнить представление"
-#: gitk:4075
+#: gitk:4099
msgid "References (space separated list):"
msgstr "Ссылки (разделённые пробелом):"
-#: gitk:4076
+#: gitk:4100
msgid "Branches & tags:"
msgstr "Ветки и метки"
-#: gitk:4077
+#: gitk:4101
msgid "All refs"
msgstr "Все ссылки"
-#: gitk:4078
+#: gitk:4102
msgid "All (local) branches"
msgstr "Все (локальные) ветки"
-#: gitk:4079
+#: gitk:4103
msgid "All tags"
msgstr "Все метки"
-#: gitk:4080
+#: gitk:4104
msgid "All remote-tracking branches"
msgstr "Все внешние отслеживаемые ветки"
-#: gitk:4081
+#: gitk:4105
msgid "Commit Info (regular expressions):"
msgstr "Информация о коммите (регулярные выражения):"
-#: gitk:4082
+#: gitk:4106
msgid "Author:"
msgstr "Автор:"
-#: gitk:4083
+#: gitk:4107
msgid "Committer:"
msgstr "Коммитер:"
-#: gitk:4084
+#: gitk:4108
msgid "Commit Message:"
msgstr "Сообщение коммита:"
-#: gitk:4085
+#: gitk:4109
msgid "Matches all Commit Info criteria"
msgstr "Совпадает со всеми условиями информации о коммите"
-#: gitk:4086
+#: gitk:4110
msgid "Matches no Commit Info criteria"
msgstr "Не совпадает с условиями информации о коммите"
-#: gitk:4087
+#: gitk:4111
msgid "Changes to Files:"
msgstr "Изменения файлов:"
-#: gitk:4088
+#: gitk:4112
msgid "Fixed String"
msgstr "Обычная строка"
-#: gitk:4089
+#: gitk:4113
msgid "Regular Expression"
msgstr "Регулярное выражение:"
-#: gitk:4090
+#: gitk:4114
msgid "Search string:"
msgstr "Строка для поиска:"
-#: gitk:4091
+#: gitk:4115
msgid ""
"Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 "
"15:27:38\"):"
msgstr "Даты коммита («2 недели назад», «2009-03-17 15:27:38», «17 марта 2009 15:27:38»):"
-#: gitk:4092
+#: gitk:4116
msgid "Since:"
msgstr "С даты:"
-#: gitk:4093
+#: gitk:4117
msgid "Until:"
msgstr "По дату:"
-#: gitk:4094
+#: gitk:4118
msgid "Limit and/or skip a number of revisions (positive integer):"
msgstr "Ограничить и/или пропустить количество редакций (положительное число):"
-#: gitk:4095
+#: gitk:4119
msgid "Number to show:"
msgstr "Показать количество:"
-#: gitk:4096
+#: gitk:4120
msgid "Number to skip:"
msgstr "Пропустить количество:"
-#: gitk:4097
+#: gitk:4121
msgid "Miscellaneous options:"
msgstr "Различные опции:"
-#: gitk:4098
+#: gitk:4122
msgid "Strictly sort by date"
msgstr "Строгая сортировка по дате"
-#: gitk:4099
+#: gitk:4123
msgid "Mark branch sides"
msgstr "Отметить стороны веток"
-#: gitk:4100
+#: gitk:4124
msgid "Limit to first parent"
msgstr "Ограничить первым предком"
-#: gitk:4101
+#: gitk:4125
msgid "Simple history"
msgstr "Упрощенная история"
-#: gitk:4102
+#: gitk:4126
msgid "Additional arguments to git log:"
msgstr "Дополнительные аргументы для git log:"
-#: gitk:4103
+#: gitk:4127
msgid "Enter files and directories to include, one per line:"
msgstr "Файлы и каталоги для ограничения истории, по одному на строку:"
-#: gitk:4104
+#: gitk:4128
msgid "Command to generate more commits to include:"
msgstr "Дополнительная команда для списка коммитов:"
-#: gitk:4228
+#: gitk:4252
msgid "Gitk: edit view"
msgstr "Gitk: изменить представление"
-#: gitk:4236
+#: gitk:4260
msgid "-- criteria for selecting revisions"
msgstr "— критерий поиска редакций"
-#: gitk:4241
+#: gitk:4265
msgid "View Name"
msgstr "Имя представления"
-#: gitk:4316
+#: gitk:4340
msgid "Apply (F5)"
msgstr "Применить (F5)"
-#: gitk:4354
+#: gitk:4378
msgid "Error in commit selection arguments:"
msgstr "Ошибка в параметрах выбора коммитов:"
-#: gitk:4409 gitk:4462 gitk:4924 gitk:4938 gitk:6208 gitk:12373 gitk:12374
+#: gitk:4433 gitk:4486 gitk:4948 gitk:4962 gitk:6232 gitk:12524 gitk:12525
msgid "None"
msgstr "Ни одного"
-#: gitk:5021 gitk:5026
+#: gitk:5045 gitk:5050
msgid "Descendant"
msgstr "Порождённое"
-#: gitk:5022
+#: gitk:5046
msgid "Not descendant"
msgstr "Не порождённое"
-#: gitk:5029 gitk:5034
+#: gitk:5053 gitk:5058
msgid "Ancestor"
msgstr "Предок"
-#: gitk:5030
+#: gitk:5054
msgid "Not ancestor"
msgstr "Не предок"
-#: gitk:5324
+#: gitk:5348
msgid "Local changes checked in to index but not committed"
msgstr "Проиндексированные изменения"
-#: gitk:5360
+#: gitk:5384
msgid "Local uncommitted changes, not checked in to index"
msgstr "Непроиндексированные изменения"
-#: gitk:7134
+#: gitk:7158
msgid "and many more"
msgstr "и многое другое"
-#: gitk:7137
+#: gitk:7161
msgid "many"
msgstr "много"
-#: gitk:7328
+#: gitk:7352
msgid "Tags:"
msgstr "Метки:"
-#: gitk:7345 gitk:7351 gitk:8825
+#: gitk:7369 gitk:7375 gitk:8854
msgid "Parent"
msgstr "Предок"
-#: gitk:7356
+#: gitk:7380
msgid "Child"
msgstr "Потомок"
-#: gitk:7365
+#: gitk:7389
msgid "Branch"
msgstr "Ветка"
-#: gitk:7368
+#: gitk:7392
msgid "Follows"
msgstr "Следует за"
-#: gitk:7371
+#: gitk:7395
msgid "Precedes"
msgstr "Предшествует"
-#: gitk:7966
+#: gitk:7990
#, tcl-format
msgid "Error getting diffs: %s"
msgstr "Ошибка получения изменений: %s"
-#: gitk:8650
+#: gitk:8679
msgid "Goto:"
msgstr "Перейти к:"
-#: gitk:8671
+#: gitk:8700
#, tcl-format
msgid "Short SHA1 id %s is ambiguous"
msgstr "Сокращённый SHA1 идентификатор %s неоднозначен"
-#: gitk:8678
+#: gitk:8707
#, tcl-format
msgid "Revision %s is not known"
msgstr "Редакция %s не найдена"
-#: gitk:8688
+#: gitk:8717
#, tcl-format
msgid "SHA1 id %s is not known"
msgstr "SHA1 идентификатор %s не найден"
-#: gitk:8690
+#: gitk:8719
#, tcl-format
msgid "Revision %s is not in the current view"
msgstr "Редакция %s не найдена в текущем представлении"
-#: gitk:8832 gitk:8847
+#: gitk:8861 gitk:8876
msgid "Date"
msgstr "Дата"
-#: gitk:8835
+#: gitk:8864
msgid "Children"
msgstr "Потомки"
-#: gitk:8898
+#: gitk:8927
#, tcl-format
msgid "Reset %s branch to here"
msgstr "Сбросить ветку %s на этот коммит"
-#: gitk:8900
+#: gitk:8929
msgid "Detached head: can't reset"
msgstr "Коммит не принадлежит ни одной ветке, сбросить невозможно"
-#: gitk:9005 gitk:9011
+#: gitk:9034 gitk:9040
msgid "Skipping merge commit "
msgstr "Пропускаю коммит-слияние"
-#: gitk:9020 gitk:9025
+#: gitk:9049 gitk:9054
msgid "Error getting patch ID for "
msgstr "Не удалось получить идентификатор патча для "
-#: gitk:9021 gitk:9026
+#: gitk:9050 gitk:9055
msgid " - stopping\n"
msgstr " — останов\n"
-#: gitk:9031 gitk:9034 gitk:9042 gitk:9056 gitk:9065
+#: gitk:9060 gitk:9063 gitk:9071 gitk:9085 gitk:9094
msgid "Commit "
msgstr "Коммит"
-#: gitk:9035
+#: gitk:9064
msgid ""
" is the same patch as\n"
" "
msgstr " такой же патч, как и\n "
-#: gitk:9043
+#: gitk:9072
msgid ""
" differs from\n"
" "
msgstr " отличается от\n "
-#: gitk:9045
+#: gitk:9074
msgid ""
"Diff of commits:\n"
"\n"
msgstr "Различия коммитов:\n\n"
-#: gitk:9057 gitk:9066
+#: gitk:9086 gitk:9095
#, tcl-format
msgid " has %s children - stopping\n"
msgstr " является %s потомком — останов\n"
-#: gitk:9085
+#: gitk:9114
#, tcl-format
msgid "Error writing commit to file: %s"
msgstr "Произошла ошибка при записи коммита в файл: %s"
-#: gitk:9091
+#: gitk:9120
#, tcl-format
msgid "Error diffing commits: %s"
msgstr "Произошла ошибка при выводе различий коммитов: %s"
-#: gitk:9137
+#: gitk:9166
msgid "Top"
msgstr "Верх"
-#: gitk:9138
+#: gitk:9167
msgid "From"
msgstr "От"
-#: gitk:9143
+#: gitk:9172
msgid "To"
msgstr "До"
-#: gitk:9167
+#: gitk:9196
msgid "Generate patch"
msgstr "Создать патч"
-#: gitk:9169
+#: gitk:9198
msgid "From:"
msgstr "От:"
-#: gitk:9178
+#: gitk:9207
msgid "To:"
msgstr "До:"
-#: gitk:9187
+#: gitk:9216
msgid "Reverse"
msgstr "В обратном порядке"
-#: gitk:9189 gitk:9385
+#: gitk:9218 gitk:9428
msgid "Output file:"
msgstr "Файл для сохранения:"
-#: gitk:9195
+#: gitk:9224
msgid "Generate"
msgstr "Создать"
-#: gitk:9233
+#: gitk:9262
msgid "Error creating patch:"
msgstr "Ошибка создания патча:"
-#: gitk:9256 gitk:9373 gitk:9430
+#: gitk:9285 gitk:9416 gitk:9504
msgid "ID:"
msgstr "ID:"
-#: gitk:9265
+#: gitk:9294
msgid "Tag name:"
msgstr "Имя метки:"
-#: gitk:9268
+#: gitk:9297
msgid "Tag message is optional"
msgstr "Описание метки указывать не обязательно"
-#: gitk:9270
+#: gitk:9299
msgid "Tag message:"
msgstr "Описание метки:"
-#: gitk:9274 gitk:9439
+#: gitk:9303 gitk:9474
msgid "Create"
msgstr "Создать"
-#: gitk:9292
+#: gitk:9321
msgid "No tag name specified"
msgstr "Не задано имя метки"
-#: gitk:9296
+#: gitk:9325
#, tcl-format
msgid "Tag \"%s\" already exists"
msgstr "Метка «%s» уже существует"
-#: gitk:9306
+#: gitk:9335
msgid "Error creating tag:"
msgstr "Ошибка создания метки:"
-#: gitk:9382
+#: gitk:9425
msgid "Command:"
msgstr "Команда:"
-#: gitk:9390
+#: gitk:9433
msgid "Write"
msgstr "Запись"
-#: gitk:9408
+#: gitk:9451
msgid "Error writing commit:"
msgstr "Произошла ошибка при записи коммита:"
-#: gitk:9435
+#: gitk:9473
+msgid "Create branch"
+msgstr "Создать ветку"
+
+#: gitk:9489
+#, tcl-format
+msgid "Rename branch %s"
+msgstr "Переименовать ветку %s"
+
+#: gitk:9490
+msgid "Rename"
+msgstr "Переименовать"
+
+#: gitk:9514
msgid "Name:"
msgstr "Имя:"
-#: gitk:9458
+#: gitk:9538
msgid "Please specify a name for the new branch"
msgstr "Укажите имя для новой ветки"
-#: gitk:9463
+#: gitk:9543
#, tcl-format
msgid "Branch '%s' already exists. Overwrite?"
msgstr "Ветка «%s» уже существует. Переписать?"
-#: gitk:9530
+#: gitk:9587
+msgid "Please specify a new name for the branch"
+msgstr "Укажите имя для новой ветки"
+
+#: gitk:9650
#, tcl-format
msgid "Commit %s is already included in branch %s -- really re-apply it?"
msgstr "Коммит %s уже включён в ветку %s. Продолжить операцию?"
-#: gitk:9535
+#: gitk:9655
msgid "Cherry-picking"
-msgstr "Копирование изменений"
+msgstr "Копирование коммита"
-#: gitk:9544
+#: gitk:9664
#, tcl-format
msgid ""
"Cherry-pick failed because of local changes to file '%s'.\n"
"Please commit, reset or stash your changes and try again."
-msgstr "Отбор лучшего невозможен из-за изменений в файле «%s».\nЗакомитьте, сбросьте или спрячьте изменения и повторите операцию."
+msgstr "Копирование коммита невозможно из-за изменений в файле «%s».\nЗакоммитьте, сбросьте или спрячьте изменения и повторите операцию."
-#: gitk:9550
+#: gitk:9670
msgid ""
"Cherry-pick failed because of merge conflict.\n"
"Do you wish to run git citool to resolve it?"
msgstr "Копирование изменений невозможно из-за незавершённой операции слияния.\nЗапустить git citool для завершения этой операции?"
-#: gitk:9566 gitk:9624
+#: gitk:9686 gitk:9744
msgid "No changes committed"
msgstr "Изменения не закоммичены"
-#: gitk:9593
+#: gitk:9713
#, tcl-format
msgid "Commit %s is not included in branch %s -- really revert it?"
msgstr "Коммит %s не включён в ветку %s. Продолжить операцию?"
-#: gitk:9598
+#: gitk:9718
msgid "Reverting"
-msgstr "Возврат изменений"
+msgstr "Обращение изменений"
-#: gitk:9606
+#: gitk:9726
#, tcl-format
msgid ""
"Revert failed because of local changes to the following files:%s Please "
"commit, reset or stash your changes and try again."
-msgstr "Возврат изменений коммита не удался из-за локальных изменений в указанных файлах: %s\nЗакомитьте, сбросьте или спрячьте изменения и повторите операцию."
+msgstr "Возврат изменений коммита не удался из-за локальных изменений в указанных файлах: %s\nЗакоммитьте, сбросьте или спрячьте изменения и повторите операцию."
-#: gitk:9610
+#: gitk:9730
msgid ""
"Revert failed because of merge conflict.\n"
" Do you wish to run git citool to resolve it?"
msgstr "Возврат изменений невозможен из-за незавершённой операции слияния.\nЗапустить git citool для завершения этой операции?"
-#: gitk:9653
+#: gitk:9773
msgid "Confirm reset"
msgstr "Подтвердите операцию перехода"
-#: gitk:9655
+#: gitk:9775
#, tcl-format
msgid "Reset branch %s to %s?"
msgstr "Сбросить ветку %s на коммит %s?"
-#: gitk:9657
+#: gitk:9777
msgid "Reset type:"
msgstr "Тип операции перехода:"
-#: gitk:9660
+#: gitk:9780
msgid "Soft: Leave working tree and index untouched"
msgstr "Лёгкий: оставить рабочий каталог и индекс неизменными"
-#: gitk:9663
+#: gitk:9783
msgid "Mixed: Leave working tree untouched, reset index"
msgstr "Смешанный: оставить рабочий каталог неизменным, установить индекс"
-#: gitk:9666
+#: gitk:9786
msgid ""
"Hard: Reset working tree and index\n"
"(discard ALL local changes)"
msgstr "Жесткий: переписать индекс и рабочий каталог\n(все изменения в рабочем каталоге будут потеряны)"
-#: gitk:9683
+#: gitk:9803
msgid "Resetting"
msgstr "Сброс"
-#: gitk:9743
+#: gitk:9876
+#, tcl-format
+msgid "A local branch named %s exists already"
+msgstr "Локальная ветка с именем %s уже существует"
+
+#: gitk:9884
msgid "Checking out"
msgstr "Переход"
-#: gitk:9796
+#: gitk:9943
msgid "Cannot delete the currently checked-out branch"
msgstr "Активная ветка не может быть удалена"
-#: gitk:9802
+#: gitk:9949
#, tcl-format
msgid ""
"The commits on branch %s aren't on any other branch.\n"
"Really delete branch %s?"
msgstr "Коммиты из ветки %s не принадлежат больше никакой другой ветке.\nДействительно удалить ветку %s?"
-#: gitk:9833
+#: gitk:9980
#, tcl-format
msgid "Tags and heads: %s"
msgstr "Метки и ветки: %s"
-#: gitk:9850
+#: gitk:9997
msgid "Filter"
msgstr "Фильтровать"
-#: gitk:10146
+#: gitk:10293
msgid ""
"Error reading commit topology information; branch and preceding/following "
"tag information will be incomplete."
msgstr "Ошибка чтения истории проекта; информация о ветках и коммитах вокруг меток (до/после) может быть неполной."
-#: gitk:11123
+#: gitk:11270
msgid "Tag"
msgstr "Метка"
-#: gitk:11127
+#: gitk:11274
msgid "Id"
msgstr "Id"
-#: gitk:11210
+#: gitk:11357
msgid "Gitk font chooser"
msgstr "Шрифт Gitk"
-#: gitk:11227
+#: gitk:11374
msgid "B"
msgstr "Ж"
-#: gitk:11230
+#: gitk:11377
msgid "I"
msgstr "К"
-#: gitk:11348
+#: gitk:11495
msgid "Commit list display options"
msgstr "Параметры показа списка коммитов"
-#: gitk:11351
+#: gitk:11498
msgid "Maximum graph width (lines)"
msgstr "Макс. ширина графа (строк)"
-#: gitk:11355
+#: gitk:11502
#, no-tcl-format
msgid "Maximum graph width (% of pane)"
msgstr "Макс. ширина графа (% ширины панели)"
-#: gitk:11358
+#: gitk:11505
msgid "Show local changes"
msgstr "Показывать изменения в рабочем каталоге"
-#: gitk:11361
+#: gitk:11508
msgid "Auto-select SHA1 (length)"
msgstr "Автоматически выделить SHA1 (длинна)"
-#: gitk:11365
+#: gitk:11512
msgid "Hide remote refs"
msgstr "Скрыть внешние ссылки"
-#: gitk:11369
+#: gitk:11516
msgid "Diff display options"
msgstr "Параметры показа изменений"
-#: gitk:11371
+#: gitk:11518
msgid "Tab spacing"
msgstr "Ширина табуляции"
-#: gitk:11374
+#: gitk:11521
msgid "Display nearby tags/heads"
msgstr "Показывать близкие метки/ветки"
-#: gitk:11377
+#: gitk:11524
msgid "Maximum # tags/heads to show"
msgstr "Показывать максимальное количество меток/веток"
-#: gitk:11380
+#: gitk:11527
msgid "Limit diffs to listed paths"
msgstr "Ограничить показ изменений выбранными файлами"
-#: gitk:11383
+#: gitk:11530
msgid "Support per-file encodings"
msgstr "Поддержка кодировок в отдельных файлах"
-#: gitk:11389 gitk:11536
+#: gitk:11536 gitk:11683
msgid "External diff tool"
msgstr "Программа для показа изменений"
-#: gitk:11390
+#: gitk:11537
msgid "Choose..."
msgstr "Выберите..."
-#: gitk:11395
+#: gitk:11542
msgid "General options"
msgstr "Общие опции"
-#: gitk:11398
+#: gitk:11545
msgid "Use themed widgets"
msgstr "Использовать стили виджетов"
-#: gitk:11400
+#: gitk:11547
msgid "(change requires restart)"
msgstr "(изменение потребует перезапуск)"
-#: gitk:11402
+#: gitk:11549
msgid "(currently unavailable)"
msgstr "(недоступно в данный момент)"
-#: gitk:11413
+#: gitk:11560
msgid "Colors: press to choose"
msgstr "Цвета: нажмите для выбора"
-#: gitk:11416
+#: gitk:11563
msgid "Interface"
msgstr "Интерфейс"
-#: gitk:11417
+#: gitk:11564
msgid "interface"
msgstr "интерфейс"
-#: gitk:11420
+#: gitk:11567
msgid "Background"
msgstr "Фон"
-#: gitk:11421 gitk:11451
+#: gitk:11568 gitk:11598
msgid "background"
msgstr "фон"
-#: gitk:11424
+#: gitk:11571
msgid "Foreground"
msgstr "Передний план"
-#: gitk:11425
+#: gitk:11572
msgid "foreground"
msgstr "передний план"
-#: gitk:11428
+#: gitk:11575
msgid "Diff: old lines"
msgstr "Изменения: старый текст"
-#: gitk:11429
+#: gitk:11576
msgid "diff old lines"
msgstr "старый текст изменения"
-#: gitk:11433
+#: gitk:11580
msgid "Diff: new lines"
msgstr "Изменения: новый текст"
-#: gitk:11434
+#: gitk:11581
msgid "diff new lines"
msgstr "новый текст изменения"
-#: gitk:11438
+#: gitk:11585
msgid "Diff: hunk header"
msgstr "Изменения: заголовок блока"
-#: gitk:11440
+#: gitk:11587
msgid "diff hunk header"
msgstr "заголовок блока изменений"
-#: gitk:11444
+#: gitk:11591
msgid "Marked line bg"
msgstr "Фон выбранной строки"
-#: gitk:11446
+#: gitk:11593
msgid "marked line background"
msgstr "фон выбранной строки"
-#: gitk:11450
+#: gitk:11597
msgid "Select bg"
msgstr "Выберите фон"
-#: gitk:11459
+#: gitk:11606
msgid "Fonts: press to choose"
msgstr "Шрифт: нажмите для выбора"
-#: gitk:11461
+#: gitk:11608
msgid "Main font"
msgstr "Основной шрифт"
-#: gitk:11462
+#: gitk:11609
msgid "Diff display font"
msgstr "Шрифт показа изменений"
-#: gitk:11463
+#: gitk:11610
msgid "User interface font"
msgstr "Шрифт интерфейса"
-#: gitk:11485
+#: gitk:11632
msgid "Gitk preferences"
msgstr "Настройки Gitk"
-#: gitk:11494
+#: gitk:11641
msgid "General"
msgstr "Общие"
-#: gitk:11495
+#: gitk:11642
msgid "Colors"
msgstr "Цвета"
-#: gitk:11496
+#: gitk:11643
msgid "Fonts"
msgstr "Шрифты"
-#: gitk:11546
+#: gitk:11693
#, tcl-format
msgid "Gitk: choose color for %s"
msgstr "Gitk: выберите цвет для %s"
-#: gitk:12059
+#: gitk:12206
msgid ""
"Sorry, gitk cannot run with this version of Tcl/Tk.\n"
" Gitk requires at least Tcl/Tk 8.4."
msgstr "К сожалению gitk не может работать с этой версий Tcl/Tk.\nТребуется как минимум Tcl/Tk 8.4."
-#: gitk:12269
+#: gitk:12416
msgid "Cannot find a git repository here."
msgstr "Git-репозитарий не найден в текущем каталоге."
-#: gitk:12316
+#: gitk:12463
#, tcl-format
msgid "Ambiguous argument '%s': both revision and filename"
msgstr "Неоднозначный аргумент «%s»: существует как редакция и как имя файла"
-#: gitk:12328
+#: gitk:12475
msgid "Bad arguments to gitk:"
msgstr "Неправильные аргументы для gitk:"
diff --git a/gitk-git/po/sv.po b/gitk-git/po/sv.po
index d9d4e87..2a06fe5 100644
--- a/gitk-git/po/sv.po
+++ b/gitk-git/po/sv.po
@@ -374,14 +374,14 @@ msgid ""
"\n"
"Gitk - a commit viewer for git\n"
"\n"
-"Copyright © 2005-2014 Paul Mackerras\n"
+"Copyright © 2005-2016 Paul Mackerras\n"
"\n"
"Use and redistribute under the terms of the GNU General Public License"
msgstr ""
"\n"
"Gitk - en incheckningsvisare för git\n"
"\n"
-"Copyright © 2005-2014 Paul Mackerras\n"
+"Copyright © 2005-2016 Paul Mackerras\n"
"\n"
"Använd och vidareförmedla enligt villkoren i GNU General Public License"
@@ -1385,21 +1385,6 @@ msgstr "Felaktiga argument till gitk:"
#~ msgid "mc"
#~ msgstr "mc"
-#~ msgid ""
-#~ "\n"
-#~ "Gitk - a commit viewer for git\n"
-#~ "\n"
-#~ "Copyright © 2005-2015 Paul Mackerras\n"
-#~ "\n"
-#~ "Use and redistribute under the terms of the GNU General Public License"
-#~ msgstr ""
-#~ "\n"
-#~ "Gitk - en incheckningsvisare för git\n"
-#~ "\n"
-#~ "Copyright © 2005-2015 Paul Mackerras\n"
-#~ "\n"
-#~ "Använd och vidareförmedla enligt villkoren i GNU General Public License"
-
#~ msgid "next"
#~ msgstr "nästa"
diff --git a/gitk-git/po/vi.po b/gitk-git/po/vi.po
index 8966812..5967498 100644
--- a/gitk-git/po/vi.po
+++ b/gitk-git/po/vi.po
@@ -363,14 +363,14 @@ msgid ""
"\n"
"Gitk - a commit viewer for git\n"
"\n"
-"Copyright © 2005-2014 Paul Mackerras\n"
+"Copyright © 2005-2016 Paul Mackerras\n"
"\n"
"Use and redistribute under the terms of the GNU General Public License"
msgstr ""
"\n"
"Gitk - ứng dụng để xem các lần chuyển giao dành cho git\n"
"\n"
-"Bản quyền © 2005-2014 Paul Mackerras\n"
+"Bản quyền © 2005-2016 Paul Mackerras\n"
"\n"
"Dùng và phân phối lại phần mềm này theo các điều khoản của Giấy Phép Công GNU"
diff --git a/gpg-interface.h b/gpg-interface.h
index ea68885..d2d4fd3 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;
diff --git a/graph.c b/graph.c
index d4e8519..0649007 100644
--- a/graph.c
+++ b/graph.c
@@ -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;
diff --git a/grep.c b/grep.c
index 1194d35..0dbdc1d 100644
--- a/grep.c
+++ b/grep.c
@@ -1735,12 +1735,23 @@ void grep_source_init(struct grep_source *gs, enum grep_source_type type,
case GREP_SOURCE_FILE:
gs->identifier = xstrdup(identifier);
break;
+ case GREP_SOURCE_SUBMODULE:
+ if (!identifier) {
+ gs->identifier = NULL;
+ break;
+ }
+ /*
+ * FALL THROUGH
+ * If the identifier is non-NULL (in the submodule case) it
+ * will be a SHA1 that needs to be copied.
+ */
case GREP_SOURCE_SHA1:
gs->identifier = xmalloc(20);
hashcpy(gs->identifier, identifier);
break;
case GREP_SOURCE_BUF:
gs->identifier = NULL;
+ break;
}
}
@@ -1760,6 +1771,7 @@ void grep_source_clear_data(struct grep_source *gs)
switch (gs->type) {
case GREP_SOURCE_FILE:
case GREP_SOURCE_SHA1:
+ case GREP_SOURCE_SUBMODULE:
free(gs->buf);
gs->buf = NULL;
gs->size = 0;
@@ -1831,8 +1843,10 @@ static int grep_source_load(struct grep_source *gs)
return grep_source_load_sha1(gs);
case GREP_SOURCE_BUF:
return gs->buf ? 0 : -1;
+ case GREP_SOURCE_SUBMODULE:
+ break;
}
- die("BUG: invalid grep_source type");
+ die("BUG: invalid grep_source type to load");
}
void grep_source_load_driver(struct grep_source *gs)
diff --git a/grep.h b/grep.h
index 5856a23..267534c 100644
--- a/grep.h
+++ b/grep.h
@@ -161,6 +161,7 @@ struct grep_source {
GREP_SOURCE_SHA1,
GREP_SOURCE_FILE,
GREP_SOURCE_BUF,
+ GREP_SOURCE_SUBMODULE,
} type;
void *identifier;
diff --git a/help.c b/help.c
index 53e2a67..bc6cd19 100644
--- a/help.c
+++ b/help.c
@@ -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/pathspec.c b/pathspec.c
index 22ca74a..7ababb3 100644
--- a/pathspec.c
+++ b/pathspec.c
@@ -67,20 +67,19 @@ static struct pathspec_magic {
char mnemonic; /* this cannot be ':'! */
const char *name;
} pathspec_magic[] = {
- { PATHSPEC_FROMTOP, '/', "top" },
- { PATHSPEC_LITERAL, 0, "literal" },
- { PATHSPEC_GLOB, '\0', "glob" },
- { PATHSPEC_ICASE, '\0', "icase" },
- { PATHSPEC_EXCLUDE, '!', "exclude" },
+ { PATHSPEC_FROMTOP, '/', "top" },
+ { PATHSPEC_LITERAL, '\0', "literal" },
+ { PATHSPEC_GLOB, '\0', "glob" },
+ { PATHSPEC_ICASE, '\0', "icase" },
+ { PATHSPEC_EXCLUDE, '!', "exclude" },
};
-static void prefix_short_magic(struct strbuf *sb, int prefixlen,
- unsigned short_magic)
+static void prefix_magic(struct strbuf *sb, int prefixlen, unsigned magic)
{
int i;
strbuf_addstr(sb, ":(");
for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++)
- if (short_magic & pathspec_magic[i].bit) {
+ if (magic & pathspec_magic[i].bit) {
if (sb->buf[sb->len - 1] != '(')
strbuf_addch(sb, ',');
strbuf_addstr(sb, pathspec_magic[i].name);
@@ -88,54 +87,61 @@ static void prefix_short_magic(struct strbuf *sb, int prefixlen,
strbuf_addf(sb, ",prefix:%d)", prefixlen);
}
-/*
- * Take an element of a pathspec and check for magic signatures.
- * Append the result to the prefix. Return the magic bitmap.
- *
- * For now, we only parse the syntax and throw out anything other than
- * "top" magic.
- *
- * NEEDSWORK: This needs to be rewritten when we start migrating
- * get_pathspec() users to use the "struct pathspec" interface. For
- * example, a pathspec element may be marked as case-insensitive, but
- * the prefix part must always match literally, and a single stupid
- * string cannot express such a case.
- */
-static unsigned prefix_pathspec(struct pathspec_item *item,
- unsigned *p_short_magic,
- const char **raw, unsigned flags,
- const char *prefix, int prefixlen,
- const char *elt)
+static inline int get_literal_global(void)
{
- static int literal_global = -1;
- static int glob_global = -1;
- static int noglob_global = -1;
- static int icase_global = -1;
- unsigned magic = 0, short_magic = 0, global_magic = 0;
- const char *copyfrom = elt, *long_magic_end = NULL;
- char *match;
- int i, pathspec_prefix = -1;
+ static int literal = -1;
+
+ if (literal < 0)
+ literal = git_env_bool(GIT_LITERAL_PATHSPECS_ENVIRONMENT, 0);
+
+ return literal;
+}
+
+static inline int get_glob_global(void)
+{
+ static int glob = -1;
+
+ if (glob < 0)
+ glob = git_env_bool(GIT_GLOB_PATHSPECS_ENVIRONMENT, 0);
+
+ return glob;
+}
+
+static inline int get_noglob_global(void)
+{
+ static int noglob = -1;
+
+ if (noglob < 0)
+ noglob = git_env_bool(GIT_NOGLOB_PATHSPECS_ENVIRONMENT, 0);
+
+ return noglob;
+}
+
+static inline int get_icase_global(void)
+{
+ static int icase = -1;
+
+ if (icase < 0)
+ icase = git_env_bool(GIT_ICASE_PATHSPECS_ENVIRONMENT, 0);
+
+ return icase;
+}
+
+static int get_global_magic(int element_magic)
+{
+ int global_magic = 0;
- if (literal_global < 0)
- literal_global = git_env_bool(GIT_LITERAL_PATHSPECS_ENVIRONMENT, 0);
- if (literal_global)
+ if (get_literal_global())
global_magic |= PATHSPEC_LITERAL;
- if (glob_global < 0)
- glob_global = git_env_bool(GIT_GLOB_PATHSPECS_ENVIRONMENT, 0);
- if (glob_global)
+ /* --glob-pathspec is overridden by :(literal) */
+ if (get_glob_global() && !(element_magic & PATHSPEC_LITERAL))
global_magic |= PATHSPEC_GLOB;
- if (noglob_global < 0)
- noglob_global = git_env_bool(GIT_NOGLOB_PATHSPECS_ENVIRONMENT, 0);
-
- if (glob_global && noglob_global)
+ if (get_glob_global() && get_noglob_global())
die(_("global 'glob' and 'noglob' pathspec settings are incompatible"));
-
- if (icase_global < 0)
- icase_global = git_env_bool(GIT_ICASE_PATHSPECS_ENVIRONMENT, 0);
- if (icase_global)
+ if (get_icase_global())
global_magic |= PATHSPEC_ICASE;
if ((global_magic & PATHSPEC_LITERAL) &&
@@ -143,84 +149,198 @@ static unsigned prefix_pathspec(struct pathspec_item *item,
die(_("global 'literal' pathspec setting is incompatible "
"with all other global pathspec settings"));
- if (flags & PATHSPEC_LITERAL_PATH)
- global_magic = 0;
+ /* --noglob-pathspec adds :(literal) _unless_ :(glob) is specified */
+ if (get_noglob_global() && !(element_magic & PATHSPEC_GLOB))
+ global_magic |= PATHSPEC_LITERAL;
- if (elt[0] != ':' || literal_global ||
- (flags & PATHSPEC_LITERAL_PATH)) {
- ; /* nothing to do */
- } else if (elt[1] == '(') {
- /* longhand */
- const char *nextat;
- for (copyfrom = elt + 2;
- *copyfrom && *copyfrom != ')';
- copyfrom = nextat) {
- size_t len = strcspn(copyfrom, ",)");
- if (copyfrom[len] == ',')
- nextat = copyfrom + len + 1;
- else
- /* handle ')' and '\0' */
- nextat = copyfrom + len;
- if (!len)
- continue;
- for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++) {
- if (strlen(pathspec_magic[i].name) == len &&
- !strncmp(pathspec_magic[i].name, copyfrom, len)) {
- magic |= pathspec_magic[i].bit;
- break;
- }
- if (starts_with(copyfrom, "prefix:")) {
- char *endptr;
- pathspec_prefix = strtol(copyfrom + 7,
- &endptr, 10);
- if (endptr - copyfrom != len)
- die(_("invalid parameter for pathspec magic 'prefix'"));
- /* "i" would be wrong, but it does not matter */
- break;
- }
+ return global_magic;
+}
+
+/*
+ * Parse the pathspec element looking for long magic
+ *
+ * saves all magic in 'magic'
+ * if prefix magic is used, save the prefix length in 'prefix_len'
+ * returns the position in 'elem' after all magic has been parsed
+ */
+static const char *parse_long_magic(unsigned *magic, int *prefix_len,
+ const char *elem)
+{
+ const char *pos;
+ const char *nextat;
+
+ for (pos = elem + 2; *pos && *pos != ')'; pos = nextat) {
+ size_t len = strcspn(pos, ",)");
+ int i;
+
+ if (pos[len] == ',')
+ nextat = pos + len + 1; /* handle ',' */
+ else
+ nextat = pos + len; /* handle ')' and '\0' */
+
+ if (!len)
+ continue;
+
+ if (starts_with(pos, "prefix:")) {
+ char *endptr;
+ *prefix_len = strtol(pos + 7, &endptr, 10);
+ if (endptr - pos != len)
+ die(_("invalid parameter for pathspec magic 'prefix'"));
+ continue;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++) {
+ if (strlen(pathspec_magic[i].name) == len &&
+ !strncmp(pathspec_magic[i].name, pos, len)) {
+ *magic |= pathspec_magic[i].bit;
+ break;
}
- if (ARRAY_SIZE(pathspec_magic) <= i)
- die(_("Invalid pathspec magic '%.*s' in '%s'"),
- (int) len, copyfrom, elt);
}
- if (*copyfrom != ')')
- die(_("Missing ')' at the end of pathspec magic in '%s'"), elt);
- long_magic_end = copyfrom;
- copyfrom++;
- } else {
- /* shorthand */
- for (copyfrom = elt + 1;
- *copyfrom && *copyfrom != ':';
- copyfrom++) {
- char ch = *copyfrom;
- if (!is_pathspec_magic(ch))
+ if (ARRAY_SIZE(pathspec_magic) <= i)
+ die(_("Invalid pathspec magic '%.*s' in '%s'"),
+ (int) len, pos, elem);
+ }
+
+ if (*pos != ')')
+ die(_("Missing ')' at the end of pathspec magic in '%s'"),
+ elem);
+ pos++;
+
+ return pos;
+}
+
+/*
+ * Parse the pathspec element looking for short magic
+ *
+ * saves all magic in 'magic'
+ * returns the position in 'elem' after all magic has been parsed
+ */
+static const char *parse_short_magic(unsigned *magic, const char *elem)
+{
+ const char *pos;
+
+ for (pos = elem + 1; *pos && *pos != ':'; pos++) {
+ char ch = *pos;
+ int i;
+
+ if (!is_pathspec_magic(ch))
+ break;
+
+ for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++) {
+ if (pathspec_magic[i].mnemonic == ch) {
+ *magic |= pathspec_magic[i].bit;
break;
- for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++)
- if (pathspec_magic[i].mnemonic == ch) {
- short_magic |= pathspec_magic[i].bit;
- break;
- }
- if (ARRAY_SIZE(pathspec_magic) <= i)
- die(_("Unimplemented pathspec magic '%c' in '%s'"),
- ch, elt);
+ }
}
- if (*copyfrom == ':')
- copyfrom++;
+
+ if (ARRAY_SIZE(pathspec_magic) <= i)
+ die(_("Unimplemented pathspec magic '%c' in '%s'"),
+ ch, elem);
}
- magic |= short_magic;
- *p_short_magic = short_magic;
+ if (*pos == ':')
+ pos++;
- /* --noglob-pathspec adds :(literal) _unless_ :(glob) is specified */
- if (noglob_global && !(magic & PATHSPEC_GLOB))
- global_magic |= PATHSPEC_LITERAL;
+ return pos;
+}
- /* --glob-pathspec is overridden by :(literal) */
- if ((global_magic & PATHSPEC_GLOB) && (magic & PATHSPEC_LITERAL))
- global_magic &= ~PATHSPEC_GLOB;
+static const char *parse_element_magic(unsigned *magic, int *prefix_len,
+ const char *elem)
+{
+ if (elem[0] != ':' || get_literal_global())
+ return elem; /* nothing to do */
+ else if (elem[1] == '(')
+ /* longhand */
+ return parse_long_magic(magic, prefix_len, elem);
+ else
+ /* shorthand */
+ return parse_short_magic(magic, elem);
+}
+
+static void strip_submodule_slash_cheap(struct pathspec_item *item)
+{
+ if (item->len >= 1 && item->match[item->len - 1] == '/') {
+ int i = cache_name_pos(item->match, item->len - 1);
+
+ if (i >= 0 && S_ISGITLINK(active_cache[i]->ce_mode)) {
+ item->len--;
+ item->match[item->len] = '\0';
+ }
+ }
+}
+
+static void strip_submodule_slash_expensive(struct pathspec_item *item)
+{
+ int i;
+
+ for (i = 0; i < active_nr; i++) {
+ struct cache_entry *ce = active_cache[i];
+ int ce_len = ce_namelen(ce);
+
+ if (!S_ISGITLINK(ce->ce_mode))
+ continue;
+
+ if (item->len <= ce_len || item->match[ce_len] != '/' ||
+ memcmp(ce->name, item->match, ce_len))
+ continue;
+
+ if (item->len == ce_len + 1) {
+ /* strip trailing slash */
+ item->len--;
+ item->match[item->len] = '\0';
+ } else {
+ die(_("Pathspec '%s' is in submodule '%.*s'"),
+ item->original, ce_len, ce->name);
+ }
+ }
+}
+
+static void die_inside_submodule_path(struct pathspec_item *item)
+{
+ int i;
+
+ for (i = 0; i < active_nr; i++) {
+ struct cache_entry *ce = active_cache[i];
+ int ce_len = ce_namelen(ce);
+
+ if (!S_ISGITLINK(ce->ce_mode))
+ continue;
+
+ if (item->len < ce_len ||
+ !(item->match[ce_len] == '/' || item->match[ce_len] == '\0') ||
+ memcmp(ce->name, item->match, ce_len))
+ continue;
- magic |= global_magic;
+ die(_("Pathspec '%s' is in submodule '%.*s'"),
+ item->original, ce_len, ce->name);
+ }
+}
+
+/*
+ * Perform the initialization of a pathspec_item based on a pathspec element.
+ */
+static void init_pathspec_item(struct pathspec_item *item, unsigned flags,
+ const char *prefix, int prefixlen,
+ const char *elt)
+{
+ unsigned magic = 0, element_magic = 0;
+ const char *copyfrom = elt;
+ char *match;
+ int pathspec_prefix = -1;
+
+ /* PATHSPEC_LITERAL_PATH ignores magic */
+ if (flags & PATHSPEC_LITERAL_PATH) {
+ magic = PATHSPEC_LITERAL;
+ } else {
+ copyfrom = parse_element_magic(&element_magic,
+ &pathspec_prefix,
+ elt);
+ magic |= element_magic;
+ magic |= get_global_magic(element_magic);
+ }
+
+ item->magic = magic;
if (pathspec_prefix >= 0 &&
(prefixlen || (prefix && *prefix)))
@@ -229,6 +349,7 @@ static unsigned prefix_pathspec(struct pathspec_item *item,
if ((magic & PATHSPEC_LITERAL) && (magic & PATHSPEC_GLOB))
die(_("%s: 'literal' and 'glob' are incompatible"), elt);
+ /* Create match string which will be used for pathspec matching */
if (pathspec_prefix >= 0) {
match = xstrdup(copyfrom);
prefixlen = pathspec_prefix;
@@ -236,69 +357,47 @@ static unsigned prefix_pathspec(struct pathspec_item *item,
match = xstrdup(copyfrom);
prefixlen = 0;
} else {
- match = prefix_path_gently(prefix, prefixlen, &prefixlen, copyfrom);
+ match = prefix_path_gently(prefix, prefixlen,
+ &prefixlen, copyfrom);
if (!match)
die(_("%s: '%s' is outside repository"), elt, copyfrom);
}
- *raw = item->match = match;
+
+ item->match = match;
+ item->len = strlen(item->match);
+ item->prefix = prefixlen;
+
/*
* Prefix the pathspec (keep all magic) and assign to
* original. Useful for passing to another command.
*/
- if (flags & PATHSPEC_PREFIX_ORIGIN) {
+ if ((flags & PATHSPEC_PREFIX_ORIGIN) &&
+ prefixlen && !get_literal_global()) {
struct strbuf sb = STRBUF_INIT;
- if (prefixlen && !literal_global) {
- /* Preserve the actual prefix length of each pattern */
- if (short_magic)
- prefix_short_magic(&sb, prefixlen, short_magic);
- else if (long_magic_end) {
- strbuf_add(&sb, elt, long_magic_end - elt);
- strbuf_addf(&sb, ",prefix:%d)", prefixlen);
- } else
- strbuf_addf(&sb, ":(prefix:%d)", prefixlen);
- }
+
+ /* Preserve the actual prefix length of each pattern */
+ prefix_magic(&sb, prefixlen, element_magic);
+
strbuf_addstr(&sb, match);
item->original = strbuf_detach(&sb, NULL);
- } else
- item->original = elt;
- item->len = strlen(item->match);
- item->prefix = prefixlen;
-
- if ((flags & PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP) &&
- (item->len >= 1 && item->match[item->len - 1] == '/') &&
- (i = cache_name_pos(item->match, item->len - 1)) >= 0 &&
- S_ISGITLINK(active_cache[i]->ce_mode)) {
- item->len--;
- match[item->len] = '\0';
+ } else {
+ item->original = xstrdup(elt);
}
+ if (flags & PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP)
+ strip_submodule_slash_cheap(item);
+
if (flags & PATHSPEC_STRIP_SUBMODULE_SLASH_EXPENSIVE)
- for (i = 0; i < active_nr; i++) {
- struct cache_entry *ce = active_cache[i];
- int ce_len = ce_namelen(ce);
-
- if (!S_ISGITLINK(ce->ce_mode))
- continue;
-
- if (item->len <= ce_len || match[ce_len] != '/' ||
- memcmp(ce->name, match, ce_len))
- continue;
- if (item->len == ce_len + 1) {
- /* strip trailing slash */
- item->len--;
- match[item->len] = '\0';
- } else
- die (_("Pathspec '%s' is in submodule '%.*s'"),
- elt, ce_len, ce->name);
- }
+ strip_submodule_slash_expensive(item);
- if (magic & PATHSPEC_LITERAL)
+ if (magic & PATHSPEC_LITERAL) {
item->nowildcard_len = item->len;
- else {
+ } else {
item->nowildcard_len = simple_length(item->match);
if (item->nowildcard_len < prefixlen)
item->nowildcard_len = prefixlen;
}
+
item->flags = 0;
if (magic & PATHSPEC_GLOB) {
/*
@@ -313,9 +412,18 @@ static unsigned prefix_pathspec(struct pathspec_item *item,
}
/* sanity checks, pathspec matchers assume these are sane */
- assert(item->nowildcard_len <= item->len &&
- item->prefix <= item->len);
- return magic;
+ if (item->nowildcard_len > item->len ||
+ item->prefix > item->len) {
+ /*
+ * This case can be triggered by the user pointing us to a
+ * pathspec inside a submodule, which is an input error.
+ * Detect that here and complain, but fallback in the
+ * non-submodule case to a BUG, as we have no idea what
+ * would trigger that.
+ */
+ die_inside_submodule_path(item);
+ die ("BUG: item->nowildcard_len > item->len || item->prefix > item->len)");
+ }
}
static int pathspec_item_cmp(const void *a_, const void *b_)
@@ -328,22 +436,22 @@ static int pathspec_item_cmp(const void *a_, const void *b_)
}
static void NORETURN unsupported_magic(const char *pattern,
- unsigned magic,
- unsigned short_magic)
+ unsigned magic)
{
struct strbuf sb = STRBUF_INIT;
- int i, n;
- for (n = i = 0; i < ARRAY_SIZE(pathspec_magic); i++) {
+ int i;
+ for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++) {
const struct pathspec_magic *m = pathspec_magic + i;
if (!(magic & m->bit))
continue;
if (sb.len)
- strbuf_addch(&sb, ' ');
- if (short_magic & m->bit)
- strbuf_addf(&sb, "'%c'", m->mnemonic);
+ strbuf_addstr(&sb, ", ");
+
+ if (m->mnemonic)
+ strbuf_addf(&sb, _("'%s' (mnemonic: '%c')"),
+ m->name, m->mnemonic);
else
strbuf_addf(&sb, "'%s'", m->name);
- n++;
}
/*
* We may want to substitute "this command" with a command
@@ -381,8 +489,6 @@ void parse_pathspec(struct pathspec *pathspec,
/* No arguments with prefix -> prefix pathspec */
if (!entry) {
- static const char *raw[2];
-
if (flags & PATHSPEC_PREFER_FULL)
return;
@@ -390,14 +496,11 @@ void parse_pathspec(struct pathspec *pathspec,
die("BUG: PATHSPEC_PREFER_CWD requires arguments");
pathspec->items = item = xcalloc(1, sizeof(*item));
- item->match = prefix;
- item->original = prefix;
+ item->match = xstrdup(prefix);
+ item->original = xstrdup(prefix);
item->nowildcard_len = item->len = strlen(prefix);
item->prefix = item->len;
- raw[0] = prefix;
- raw[1] = NULL;
pathspec->nr = 1;
- pathspec->_raw = raw;
return;
}
@@ -415,25 +518,17 @@ void parse_pathspec(struct pathspec *pathspec,
pathspec->nr = n;
ALLOC_ARRAY(pathspec->items, n);
item = pathspec->items;
- pathspec->_raw = argv;
prefixlen = prefix ? strlen(prefix) : 0;
for (i = 0; i < n; i++) {
- unsigned short_magic;
entry = argv[i];
- item[i].magic = prefix_pathspec(item + i, &short_magic,
- argv + i, flags,
- prefix, prefixlen, entry);
- if ((flags & PATHSPEC_LITERAL_PATH) &&
- !(magic_mask & PATHSPEC_LITERAL))
- item[i].magic |= PATHSPEC_LITERAL;
+ init_pathspec_item(item + i, flags, prefix, prefixlen, entry);
+
if (item[i].magic & PATHSPEC_EXCLUDE)
nr_exclude++;
if (item[i].magic & magic_mask)
- unsupported_magic(entry,
- item[i].magic & magic_mask,
- short_magic);
+ unsupported_magic(entry, item[i].magic & magic_mask);
if ((flags & PATHSPEC_SYMLINK_LEADING_PATH) &&
has_symlink_leading_path(item[i].match, item[i].len)) {
@@ -457,45 +552,29 @@ void parse_pathspec(struct pathspec *pathspec,
}
}
-/*
- * N.B. get_pathspec() is deprecated in favor of the "struct pathspec"
- * based interface - see pathspec.c:parse_pathspec().
- *
- * Arguments:
- * - prefix - a path relative to the root of the working tree
- * - pathspec - a list of paths underneath the prefix path
- *
- * Iterates over pathspec, prepending each path with prefix,
- * and return the resulting list.
- *
- * If pathspec is empty, return a singleton list containing prefix.
- *
- * If pathspec and prefix are both empty, return an empty list.
- *
- * This is typically used by built-in commands such as add.c, in order
- * to normalize argv arguments provided to the built-in into a list of
- * paths to process, all relative to the root of the working tree.
- */
-const char **get_pathspec(const char *prefix, const char **pathspec)
-{
- struct pathspec ps;
- parse_pathspec(&ps,
- PATHSPEC_ALL_MAGIC &
- ~(PATHSPEC_FROMTOP | PATHSPEC_LITERAL),
- PATHSPEC_PREFER_CWD,
- prefix, pathspec);
- return ps._raw;
-}
-
void copy_pathspec(struct pathspec *dst, const struct pathspec *src)
{
+ int i;
+
*dst = *src;
ALLOC_ARRAY(dst->items, dst->nr);
COPY_ARRAY(dst->items, src->items, dst->nr);
+
+ for (i = 0; i < dst->nr; i++) {
+ dst->items[i].match = xstrdup(src->items[i].match);
+ dst->items[i].original = xstrdup(src->items[i].original);
+ }
}
void clear_pathspec(struct pathspec *pathspec)
{
+ int i;
+
+ for (i = 0; i < pathspec->nr; i++) {
+ free(pathspec->items[i].match);
+ free(pathspec->items[i].original);
+ }
free(pathspec->items);
pathspec->items = NULL;
+ pathspec->nr = 0;
}
diff --git a/pathspec.h b/pathspec.h
index 59809e4..49fd823 100644
--- a/pathspec.h
+++ b/pathspec.h
@@ -19,15 +19,14 @@
#define PATHSPEC_ONESTAR 1 /* the pathspec pattern satisfies GFNM_ONESTAR */
struct pathspec {
- const char **_raw; /* get_pathspec() result, not freed by clear_pathspec() */
int nr;
unsigned int has_wildcard:1;
unsigned int recursive:1;
unsigned magic;
int max_depth;
struct pathspec_item {
- const char *match;
- const char *original;
+ char *match;
+ char *original;
unsigned magic;
int len, prefix;
int nowildcard_len;
diff --git a/read-cache.c b/read-cache.c
index 2eca639..9054369 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];
@@ -2285,7 +2284,8 @@ int index_name_is_other(const struct index_state *istate, const char *name,
return 1;
}
-void *read_blob_data_from_index(struct index_state *istate, const char *path, unsigned long *size)
+void *read_blob_data_from_index(const struct index_state *istate,
+ const char *path, unsigned long *size)
{
int pos, len;
unsigned long sz;
diff --git a/ref-filter.c b/ref-filter.c
index 1a97840..3820b21 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 fc55fa3..7b05592 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 */
diff --git a/remote.c b/remote.c
index ad6c542..bf1bf23 100644
--- a/remote.c
+++ b/remote.c
@@ -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)
@@ -1716,9 +1724,6 @@ static const char *branch_get_push_1(struct branch *branch, struct strbuf *err)
{
struct remote *remote;
- if (!branch)
- return error_buf(err, _("HEAD does not point to a branch"));
-
remote = remote_get(pushremote_for_branch(branch, NULL));
if (!remote)
return error_buf(err,
@@ -1778,6 +1783,9 @@ static const char *branch_get_push_1(struct branch *branch, struct strbuf *err)
const char *branch_get_push(struct branch *branch, struct strbuf *err)
{
+ if (!branch)
+ return error_buf(err, _("HEAD does not point to a branch"));
+
if (!branch->push_tracking_ref)
branch->push_tracking_ref = branch_get_push_1(branch, err);
return branch->push_tracking_ref;
diff --git a/remote.h b/remote.h
index 9248811..a5bbbe0 100644
--- a/remote.h
+++ b/remote.h
@@ -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 ca905a9..5227f78 100644
--- a/run-command.c
+++ b/run-command.c
@@ -29,6 +29,8 @@ static int installed_child_cleanup_handler;
static void cleanup_children(int sig, int in_signal)
{
+ struct child_to_clean *children_to_wait_for = NULL;
+
while (children_to_clean) {
struct child_to_clean *p = children_to_clean;
children_to_clean = p->next;
@@ -45,6 +47,23 @@ static void cleanup_children(int sig, int in_signal)
}
kill(p->pid, sig);
+
+ if (p->process->wait_after_clean) {
+ p->next = children_to_wait_for;
+ children_to_wait_for = p;
+ } else {
+ if (!in_signal)
+ free(p);
+ }
+ }
+
+ while (children_to_wait_for) {
+ struct child_to_clean *p = children_to_wait_for;
+ children_to_wait_for = p->next;
+
+ while (waitpid(p->pid, NULL, 0) < 0 && errno == EINTR)
+ ; /* spin waiting for process exit or error */
+
if (!in_signal)
free(p);
}
@@ -852,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/run-command.h b/run-command.h
index dd1c78c..4fa8f65 100644
--- a/run-command.h
+++ b/run-command.h
@@ -43,6 +43,7 @@ struct child_process {
unsigned stdout_to_stderr:1;
unsigned use_shell:1;
unsigned clean_on_exit:1;
+ unsigned wait_after_clean:1;
void (*clean_on_exit_handler)(struct child_process *process);
void *clean_on_exit_handler_cbdata;
};
diff --git a/sequencer.c b/sequencer.c
index 9adb7bb..1f729b0 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 7a513c5..f885b68 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;
diff --git a/setup.c b/setup.c
index fe572b8..1b534a7 100644
--- a/setup.c
+++ b/setup.c
@@ -256,8 +256,10 @@ int get_common_dir_noenv(struct strbuf *sb, const char *gitdir)
strbuf_addbuf(&path, &data);
strbuf_addstr(sb, real_path(path.buf));
ret = 1;
- } else
+ } else {
strbuf_addstr(sb, gitdir);
+ }
+
strbuf_release(&data);
strbuf_release(&path);
return ret;
@@ -692,7 +694,7 @@ static const char *setup_discovered_git_dir(const char *gitdir,
/* --work-tree is set without --git-dir; use discovered one */
if (getenv(GIT_WORK_TREE_ENVIRONMENT) || git_work_tree_cfg) {
if (offset != cwd->len && !is_absolute_path(gitdir))
- gitdir = xstrdup(real_path(gitdir));
+ gitdir = real_pathdup(gitdir);
if (chdir(cwd->buf))
die_errno("Could not come back to cwd");
return setup_explicit_git_dir(gitdir, cwd, nongit_ok);
@@ -800,11 +802,12 @@ static int canonicalize_ceiling_entry(struct string_list_item *item,
/* Keep entry but do not canonicalize it */
return 1;
} else {
- const char *real_path = real_path_if_valid(ceil);
- if (!real_path)
+ char *real_path = real_pathdup(ceil);
+ if (!real_path) {
return 0;
+ }
free(item->string);
- item->string = xstrdup(real_path);
+ item->string = real_path;
return 1;
}
}
diff --git a/sha1_file.c b/sha1_file.c
index 1eb47f6..ec957db 100644
--- a/sha1_file.c
+++ b/sha1_file.c
@@ -284,7 +284,7 @@ static int link_alt_odb_entry(const char *entry, const char *relative_base,
struct strbuf pathbuf = STRBUF_INIT;
if (!is_absolute_path(entry) && relative_base) {
- strbuf_addstr(&pathbuf, real_path(relative_base));
+ strbuf_realpath(&pathbuf, relative_base, 1);
strbuf_addch(&pathbuf, '/');
}
strbuf_addstr(&pathbuf, entry);
@@ -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 8c83cac..45016ad 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 ec13ab5..9345390 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
@@ -379,7 +381,7 @@ static int parse_config(const char *var, const char *value, void *data)
return ret;
}
-static int gitmodule_sha1_from_commit(const unsigned char *treeish_name,
+int gitmodule_sha1_from_commit(const unsigned char *treeish_name,
unsigned char *gitmodules_sha1,
struct strbuf *rev)
{
diff --git a/submodule-config.h b/submodule-config.h
index 99df8e5..70f1936 100644
--- a/submodule-config.h
+++ b/submodule-config.h
@@ -29,6 +29,9 @@ const struct submodule *submodule_from_name(const unsigned char *commit_or_tree,
const char *name);
const struct submodule *submodule_from_path(const unsigned char *commit_or_tree,
const char *path);
+extern int gitmodule_sha1_from_commit(const unsigned char *commit_sha1,
+ unsigned char *gitmodules_sha1,
+ struct strbuf *rev);
void submodule_free(void);
#endif /* SUBMODULE_CONFIG_H */
diff --git a/submodule.c b/submodule.c
index 73521cd..4c4f033 100644
--- a/submodule.c
+++ b/submodule.c
@@ -199,6 +199,56 @@ void gitmodules_config(void)
}
}
+void gitmodules_config_sha1(const unsigned char *commit_sha1)
+{
+ struct strbuf rev = STRBUF_INIT;
+ unsigned char sha1[20];
+
+ if (gitmodule_sha1_from_commit(commit_sha1, sha1, &rev)) {
+ git_config_from_blob_sha1(submodule_config, rev.buf,
+ sha1, NULL);
+ }
+ strbuf_release(&rev);
+}
+
+/*
+ * Determine if a submodule has been initialized at a given 'path'
+ */
+int is_submodule_initialized(const char *path)
+{
+ int ret = 0;
+ const struct submodule *module = NULL;
+
+ module = submodule_from_path(null_sha1, path);
+
+ if (module) {
+ char *key = xstrfmt("submodule.%s.url", module->name);
+ char *value = NULL;
+
+ ret = !git_config_get_string(key, &value);
+
+ free(value);
+ free(key);
+ }
+
+ return ret;
+}
+
+/*
+ * Determine if a submodule has been populated at a given 'path'
+ */
+int is_submodule_populated(const char *path)
+{
+ int ret = 0;
+ char *gitdir = xstrfmt("%s/.git", path);
+
+ if (resolve_gitdir(gitdir))
+ ret = 1;
+
+ free(gitdir);
+ return ret;
+}
+
int parse_submodule_update_strategy(const char *value,
struct submodule_update_strategy *dst)
{
@@ -1093,45 +1143,64 @@ int submodule_uses_gitfile(const char *path)
return 1;
}
-int ok_to_remove_submodule(const char *path)
+/*
+ * Check if it is a bad idea to remove a submodule, i.e. if we'd lose data
+ * when doing so.
+ *
+ * Return 1 if we'd lose data, return 0 if the removal is fine,
+ * and negative values for errors.
+ */
+int bad_to_remove_submodule(const char *path, unsigned flags)
{
ssize_t len;
struct child_process cp = CHILD_PROCESS_INIT;
- const char *argv[] = {
- "status",
- "--porcelain",
- "-u",
- "--ignore-submodules=none",
- NULL,
- };
struct strbuf buf = STRBUF_INIT;
- int ok_to_remove = 1;
+ int ret = 0;
if (!file_exists(path) || is_empty_dir(path))
- return 1;
+ return 0;
if (!submodule_uses_gitfile(path))
- return 0;
+ return 1;
+
+ argv_array_pushl(&cp.args, "status", "--porcelain",
+ "--ignore-submodules=none", NULL);
+
+ if (flags & SUBMODULE_REMOVAL_IGNORE_UNTRACKED)
+ argv_array_push(&cp.args, "-uno");
+ else
+ argv_array_push(&cp.args, "-uall");
+
+ if (!(flags & SUBMODULE_REMOVAL_IGNORE_IGNORED_UNTRACKED))
+ argv_array_push(&cp.args, "--ignored");
- cp.argv = argv;
prepare_submodule_repo_env(&cp.env_array);
cp.git_cmd = 1;
cp.no_stdin = 1;
cp.out = -1;
cp.dir = path;
- if (start_command(&cp))
- die("Could not run 'git status --porcelain -uall --ignore-submodules=none' in submodule %s", path);
+ if (start_command(&cp)) {
+ if (flags & SUBMODULE_REMOVAL_DIE_ON_ERROR)
+ die(_("could not start 'git status in submodule '%s'"),
+ path);
+ ret = -1;
+ goto out;
+ }
len = strbuf_read(&buf, cp.out, 1024);
if (len > 2)
- ok_to_remove = 0;
+ ret = 1;
close(cp.out);
- if (finish_command(&cp))
- die("'git status --porcelain -uall --ignore-submodules=none' failed in submodule %s", path);
-
+ if (finish_command(&cp)) {
+ if (flags & SUBMODULE_REMOVAL_DIE_ON_ERROR)
+ die(_("could not run 'git status in submodule '%s'"),
+ path);
+ ret = -1;
+ }
+out:
strbuf_release(&buf);
- return ok_to_remove;
+ return ret;
}
static int find_first_merges(struct object_array *result, const char *path,
@@ -1310,7 +1379,8 @@ void prepare_submodule_repo_env(struct argv_array *out)
if (strcmp(*var, CONFIG_DATA_ENVIRONMENT))
argv_array_push(out, *var);
}
- argv_array_push(out, "GIT_DIR=.git");
+ argv_array_pushf(out, "%s=%s", GIT_DIR_ENVIRONMENT,
+ DEFAULT_GIT_DIR_ENVIRONMENT);
}
/*
@@ -1333,7 +1403,7 @@ static void relocate_single_git_dir_into_superproject(const char *prefix,
/* If it is an actual gitfile, it doesn't need migration. */
return;
- real_old_git_dir = xstrdup(real_path(old_git_dir));
+ real_old_git_dir = real_pathdup(old_git_dir);
sub = submodule_from_path(null_sha1, path);
if (!sub)
@@ -1342,7 +1412,7 @@ static void relocate_single_git_dir_into_superproject(const char *prefix,
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 = xstrdup(real_path(new_git_dir));
+ real_new_git_dir = real_pathdup(new_git_dir);
if (!prefix)
prefix = get_super_prefix();
@@ -1379,8 +1449,8 @@ void absorb_git_dir_into_superproject(const char *prefix,
goto out;
/* Is it already absorbed into the superprojects git dir? */
- real_sub_git_dir = xstrdup(real_path(sub_git_dir));
- real_common_git_dir = xstrdup(real_path(get_git_common_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);
diff --git a/submodule.h b/submodule.h
index b7576d6..05ab674 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,
@@ -30,52 +31,63 @@ struct submodule_update_strategy {
};
#define SUBMODULE_UPDATE_STRATEGY_INIT {SM_UPDATE_UNSPECIFIED, NULL}
-int is_staging_gitmodules_ok(void);
-int update_path_in_gitmodules(const char *oldpath, const char *newpath);
-int remove_path_from_gitmodules(const char *path);
-void stage_updated_gitmodules(void);
-void set_diffopt_flags_from_submodule_config(struct diff_options *diffopt,
+extern int is_staging_gitmodules_ok(void);
+extern int update_path_in_gitmodules(const char *oldpath, const char *newpath);
+extern int remove_path_from_gitmodules(const char *path);
+extern void stage_updated_gitmodules(void);
+extern void set_diffopt_flags_from_submodule_config(struct diff_options *,
const char *path);
-int submodule_config(const char *var, const char *value, void *cb);
-void gitmodules_config(void);
-int parse_submodule_update_strategy(const char *value,
+extern int submodule_config(const char *var, const char *value, void *cb);
+extern void gitmodules_config(void);
+extern void gitmodules_config_sha1(const unsigned char *commit_sha1);
+extern int is_submodule_initialized(const char *path);
+extern int is_submodule_populated(const char *path);
+extern int parse_submodule_update_strategy(const char *value,
struct submodule_update_strategy *dst);
-const char *submodule_strategy_to_string(const struct submodule_update_strategy *s);
-void handle_ignore_submodules_arg(struct diff_options *diffopt, const char *);
-void show_submodule_summary(FILE *f, const char *path,
+extern const char *submodule_strategy_to_string(const struct submodule_update_strategy *s);
+extern void handle_ignore_submodules_arg(struct diff_options *, const char *);
+extern void show_submodule_summary(FILE *f, const char *path,
const char *line_prefix,
struct object_id *one, struct object_id *two,
unsigned dirty_submodule, const char *meta,
const char *del, const char *add, const char *reset);
-void show_submodule_inline_diff(FILE *f, const char *path,
+extern void show_submodule_inline_diff(FILE *f, const char *path,
const char *line_prefix,
struct object_id *one, struct object_id *two,
unsigned dirty_submodule, const char *meta,
const char *del, const char *add, const char *reset,
const struct diff_options *opt);
-void set_config_fetch_recurse_submodules(int value);
-void check_for_new_submodule_commits(unsigned char new_sha1[20]);
-int fetch_populated_submodules(const struct argv_array *options,
+extern void set_config_fetch_recurse_submodules(int value);
+extern void check_for_new_submodule_commits(unsigned char new_sha1[20]);
+extern int fetch_populated_submodules(const struct argv_array *options,
const char *prefix, int command_line_option,
int quiet, int max_parallel_jobs);
-unsigned is_submodule_modified(const char *path, int ignore_untracked);
-int submodule_uses_gitfile(const char *path);
-int ok_to_remove_submodule(const char *path);
-int merge_submodule(unsigned char result[20], const char *path, const unsigned char base[20],
- const unsigned char a[20], const unsigned char b[20], int search);
-int find_unpushed_submodules(struct sha1_array *commits, const char *remotes_name,
- struct string_list *needs_pushing);
+extern unsigned is_submodule_modified(const char *path, int ignore_untracked);
+extern int submodule_uses_gitfile(const char *path);
+
+#define SUBMODULE_REMOVAL_DIE_ON_ERROR (1<<0)
+#define SUBMODULE_REMOVAL_IGNORE_UNTRACKED (1<<1)
+#define SUBMODULE_REMOVAL_IGNORE_IGNORED_UNTRACKED (1<<2)
+extern int bad_to_remove_submodule(const char *path, unsigned flags);
+extern int merge_submodule(unsigned char result[20], const char *path,
+ const unsigned char base[20],
+ const unsigned char a[20],
+ const unsigned char b[20], int search);
+extern int find_unpushed_submodules(struct sha1_array *commits,
+ const char *remotes_name,
+ struct string_list *needs_pushing);
extern int push_unpushed_submodules(struct sha1_array *commits,
const char *remotes_name,
int dry_run);
-int parallel_submodules(void);
+extern void connect_work_tree_and_git_dir(const char *work_tree, const char *git_dir);
+extern int parallel_submodules(void);
/*
* Prepare the "env_array" parameter of a "struct child_process" for executing
* a submodule by clearing any repo-specific envirionment variables, but
* retaining any config in the environment.
*/
-void prepare_submodule_repo_env(struct argv_array *out);
+extern void prepare_submodule_repo_env(struct argv_array *out);
#define ABSORB_GITDIR_RECURSE_SUBMODULES (1<<0)
extern void absorb_git_dir_into_superproject(const char *prefix,
diff --git a/t/helper/test-string-list.c b/t/helper/test-string-list.c
index 4a68967..c502fa1 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/lib-submodule-update.sh b/t/lib-submodule-update.sh
index 79cdd34..915eb4a 100755
--- a/t/lib-submodule-update.sh
+++ b/t/lib-submodule-update.sh
@@ -69,10 +69,7 @@ create_lib_submodule_repo () {
git checkout -b "replace_sub1_with_directory" "add_sub1" &&
git submodule update &&
- (
- cd sub1 &&
- git checkout modifications
- ) &&
+ git -C sub1 checkout modifications &&
git rm --cached sub1 &&
rm sub1/.git* &&
git config -f .gitmodules --remove-section "submodule.sub1" &&
diff --git a/t/perf/p0071-sort.sh b/t/perf/p0071-sort.sh
new file mode 100755
index 0000000..7c9a35a
--- /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 b8fc588..e424de5 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/t1403-show-ref.sh b/t/t1403-show-ref.sh
index 7e10bcf..30354fd 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 ee7d473..33a51c9 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/t1514-rev-parse-push.sh b/t/t1514-rev-parse-push.sh
index 7214f5b..623a32a 100755
--- a/t/t1514-rev-parse-push.sh
+++ b/t/t1514-rev-parse-push.sh
@@ -60,4 +60,10 @@ test_expect_success '@{push} with push refspecs' '
resolve topic@{push} refs/remotes/origin/magic/topic
'
+test_expect_success 'resolving @{push} fails with a detached HEAD' '
+ git checkout HEAD^0 &&
+ test_when_finished "git checkout -" &&
+ test_must_fail git rev-parse @{push}
+'
+
test_done
diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh
index c896a4c..e2f18d1 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/t3510-cherry-pick-sequence.sh b/t/t3510-cherry-pick-sequence.sh
index 372307c..0acf4b1 100755
--- a/t/t3510-cherry-pick-sequence.sh
+++ b/t/t3510-cherry-pick-sequence.sh
@@ -385,7 +385,7 @@ test_expect_success '--continue respects opts' '
git cat-file commit HEAD~1 >picked_msg &&
git cat-file commit HEAD~2 >unrelatedpick_msg &&
git cat-file commit HEAD~3 >initial_msg &&
- test_must_fail grep "cherry picked from" initial_msg &&
+ ! grep "cherry picked from" initial_msg &&
grep "cherry picked from" unrelatedpick_msg &&
grep "cherry picked from" picked_msg &&
grep "cherry picked from" anotherpick_msg
@@ -426,9 +426,9 @@ test_expect_failure '--signoff is automatically propagated to resolved conflict'
git cat-file commit HEAD~1 >picked_msg &&
git cat-file commit HEAD~2 >unrelatedpick_msg &&
git cat-file commit HEAD~3 >initial_msg &&
- test_must_fail grep "Signed-off-by:" initial_msg &&
+ ! grep "Signed-off-by:" initial_msg &&
grep "Signed-off-by:" unrelatedpick_msg &&
- test_must_fail grep "Signed-off-by:" picked_msg &&
+ ! grep "Signed-off-by:" picked_msg &&
grep "Signed-off-by:" anotherpick_msg
'
diff --git a/t/t3600-rm.sh b/t/t3600-rm.sh
index bcbb680..5aa6db5 100755
--- a/t/t3600-rm.sh
+++ b/t/t3600-rm.sh
@@ -569,26 +569,22 @@ test_expect_success 'rm of a conflicted unpopulated submodule succeeds' '
test_cmp expect actual
'
-test_expect_success 'rm of a populated submodule with a .git directory fails even when forced' '
+test_expect_success 'rm of a populated submodule with a .git directory migrates git dir' '
git checkout -f master &&
git reset --hard &&
git submodule update &&
(cd submod &&
rm .git &&
cp -R ../.git/modules/sub .git &&
- GIT_WORK_TREE=. git config --unset core.worktree
+ GIT_WORK_TREE=. git config --unset core.worktree &&
+ rm -r ../.git/modules/sub
) &&
- test_must_fail git rm submod &&
- test -d submod &&
- test -d submod/.git &&
- git status -s -uno --ignore-submodules=none >actual &&
- ! test -s actual &&
- test_must_fail git rm -f submod &&
- test -d submod &&
- test -d submod/.git &&
+ git rm submod 2>output.err &&
+ ! test -d submod &&
+ ! test -d submod/.git &&
git status -s -uno --ignore-submodules=none >actual &&
- ! test -s actual &&
- rm -rf submod
+ test -s actual &&
+ test_i18ngrep Migrating output.err
'
cat >expect.deepmodified <<EOF
@@ -667,24 +663,19 @@ test_expect_success 'rm of a populated nested submodule with a nested .git direc
git submodule update --recursive &&
(cd submod/subsubmod &&
rm .git &&
- cp -R ../../.git/modules/sub/modules/sub .git &&
+ mv ../../.git/modules/sub/modules/sub .git &&
GIT_WORK_TREE=. git config --unset core.worktree
) &&
- test_must_fail git rm submod &&
- test -d submod &&
- test -d submod/subsubmod/.git &&
- git status -s -uno --ignore-submodules=none >actual &&
- ! test -s actual &&
- test_must_fail git rm -f submod &&
- test -d submod &&
- test -d submod/subsubmod/.git &&
+ git rm submod 2>output.err &&
+ ! test -d submod &&
+ ! test -d submod/subsubmod/.git &&
git status -s -uno --ignore-submodules=none >actual &&
- ! test -s actual &&
- rm -rf submod
+ test -s actual &&
+ test_i18ngrep Migrating output.err
'
test_expect_success 'checking out a commit after submodule removal needs manual updates' '
- git commit -m "submodule removal" submod &&
+ git commit -m "submodule removal" submod .gitmodules &&
git checkout HEAD^ &&
git submodule update &&
git checkout -q HEAD^ &&
diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh
index deae948..5ffe78e 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 ec78c5e..671e951 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/t4032-diff-inter-hunk-context.sh b/t/t4032-diff-inter-hunk-context.sh
index e4e3e28..bada0cb 100755
--- a/t/t4032-diff-inter-hunk-context.sh
+++ b/t/t4032-diff-inter-hunk-context.sh
@@ -16,11 +16,15 @@ f() {
}
t() {
+ use_config=
+ git config --unset diff.interHunkContext
+
case $# in
4) hunks=$4; cmd="diff -U$3";;
5) hunks=$5; cmd="diff -U$3 --inter-hunk-context=$4";;
+ 6) hunks=$5; cmd="diff -U$3"; git config diff.interHunkContext $4; use_config="(diff.interHunkContext=$4) ";;
esac
- label="$cmd, $1 common $2"
+ label="$use_config$cmd, $1 common $2"
file=f$1
expected=expected.$file.$3.$hunks
@@ -89,4 +93,25 @@ t 9 lines 3 2
t 9 lines 3 2 2
t 9 lines 3 3 1
+# use diff.interHunkContext?
+t 1 line 0 0 2 config
+t 1 line 0 1 1 config
+t 1 line 0 2 1 config
+t 9 lines 3 3 1 config
+t 2 lines 0 0 2 config
+t 2 lines 0 1 2 config
+t 2 lines 0 2 1 config
+t 3 lines 1 0 2 config
+t 3 lines 1 1 1 config
+t 3 lines 1 2 1 config
+t 9 lines 3 2 2 config
+t 9 lines 3 3 1 config
+
+test_expect_success 'diff.interHunkContext invalid' '
+ git config diff.interHunkContext asdf &&
+ test_must_fail git diff &&
+ git config diff.interHunkContext -1 &&
+ test_must_fail git diff
+'
+
test_done
diff --git a/t/t4202-log.sh b/t/t4202-log.sh
index 1ccbd59..08ea725 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/t5003-archive-zip.sh b/t/t5003-archive-zip.sh
index 14744b2..55c7870 100755
--- a/t/t5003-archive-zip.sh
+++ b/t/t5003-archive-zip.sh
@@ -64,6 +64,12 @@ check_zip() {
test_cmp_bin $original/nodiff.crlf $extracted/nodiff.crlf &&
test_cmp_bin $original/nodiff.lf $extracted/nodiff.lf
"
+
+ test_expect_success UNZIP " validate that custom diff is unchanged " "
+ test_cmp_bin $original/custom.cr $extracted/custom.cr &&
+ test_cmp_bin $original/custom.crlf $extracted/custom.crlf &&
+ test_cmp_bin $original/custom.lf $extracted/custom.lf
+ "
}
test_expect_success \
@@ -78,6 +84,9 @@ test_expect_success \
printf "text\r" >a/nodiff.cr &&
printf "text\r\n" >a/nodiff.crlf &&
printf "text\n" >a/nodiff.lf &&
+ printf "text\r" >a/custom.cr &&
+ printf "text\r\n" >a/custom.crlf &&
+ printf "text\n" >a/custom.lf &&
printf "\0\r" >a/binary.cr &&
printf "\0\r\n" >a/binary.crlf &&
printf "\0\n" >a/binary.lf &&
@@ -112,15 +121,20 @@ test_expect_success 'add files to repository' '
test_expect_success 'setup export-subst and diff attributes' '
echo "a/nodiff.* -diff" >>.git/info/attributes &&
echo "a/diff.* diff" >>.git/info/attributes &&
+ echo "a/custom.* diff=custom" >>.git/info/attributes &&
+ git config diff.custom.binary true &&
echo "substfile?" export-subst >>.git/info/attributes &&
git log --max-count=1 "--pretty=format:A${SUBSTFORMAT}O" HEAD \
>a/substfile1
'
-test_expect_success \
- 'create bare clone' \
- 'git clone --bare . bare.git &&
- cp .git/info/attributes bare.git/info/attributes'
+test_expect_success 'create bare clone' '
+ git clone --bare . bare.git &&
+ cp .git/info/attributes bare.git/info/attributes &&
+ # Recreate our changes to .git/config rather than just copying it, as
+ # we do not want to clobber core.bare or other settings.
+ git -C bare.git config diff.custom.binary true
+'
test_expect_success \
'remove ignored file' \
diff --git a/t/t5310-pack-bitmaps.sh b/t/t5310-pack-bitmaps.sh
index b4c7a6f..424bec7 100755
--- a/t/t5310-pack-bitmaps.sh
+++ b/t/t5310-pack-bitmaps.sh
@@ -118,12 +118,10 @@ test_expect_success 'fetch (partial bitmap)' '
test_cmp expect actual
'
-test_expect_success 'incremental repack cannot create bitmaps' '
+test_expect_success 'incremental repack fails when bitmaps are requested' '
test_commit more-1 &&
- find .git/objects/pack -name "*.bitmap" >expect &&
- git repack -d &&
- find .git/objects/pack -name "*.bitmap" >actual &&
- test_cmp expect actual
+ test_must_fail git repack -d 2>err &&
+ test_i18ngrep "Incremental repacks are incompatible with bitmap" err
'
test_expect_success 'incremental repack can disable bitmaps' '
diff --git a/t/t5504-fetch-receive-strict.sh b/t/t5504-fetch-receive-strict.sh
index 9b19cff..49d3621 100755
--- a/t/t5504-fetch-receive-strict.sh
+++ b/t/t5504-fetch-receive-strict.sh
@@ -152,7 +152,7 @@ test_expect_success 'push with receive.fsck.missingEmail=warn' '
git --git-dir=dst/.git config --add \
receive.fsck.badDate warn &&
git push --porcelain dst bogus >act 2>&1 &&
- test_must_fail grep "missingEmail" act
+ ! grep "missingEmail" act
'
test_expect_success \
diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
index 8198d8e..ba46e86 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/t5516-fetch-push.sh b/t/t5516-fetch-push.sh
index 26b2caf..0fc5a7c 100755
--- a/t/t5516-fetch-push.sh
+++ b/t/t5516-fetch-push.sh
@@ -1004,7 +1004,7 @@ test_expect_success 'push --porcelain' '
test_expect_success 'push --porcelain bad url' '
mk_empty testrepo &&
test_must_fail git push >.git/bar --porcelain asdfasdfasd refs/heads/master:refs/remotes/origin/master &&
- test_must_fail grep -q Done .git/bar
+ ! grep -q Done .git/bar
'
test_expect_success 'push --porcelain rejected' '
diff --git a/t/t5531-deep-submodule-push.sh b/t/t5531-deep-submodule-push.sh
index 1524ff5..f55137f 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/t5580-clone-push-unc.sh b/t/t5580-clone-push-unc.sh
new file mode 100755
index 0000000..b195f71
--- /dev/null
+++ b/t/t5580-clone-push-unc.sh
@@ -0,0 +1,48 @@
+#!/bin/sh
+
+test_description='various UNC path tests (Windows-only)'
+. ./test-lib.sh
+
+if ! test_have_prereq MINGW; then
+ skip_all='skipping UNC path tests, requires Windows'
+ test_done
+fi
+
+UNCPATH="$(pwd)"
+case "$UNCPATH" in
+[A-Z]:*)
+ # Use administrative share e.g. \\localhost\C$\git-sdk-64\usr\src\git
+ # (we use forward slashes here because MSYS2 and Git accept them, and
+ # they are easier on the eyes)
+ UNCPATH="//localhost/${UNCPATH%%:*}\$/${UNCPATH#?:}"
+ test -d "$UNCPATH" || {
+ skip_all='could not access administrative share; skipping'
+ test_done
+ }
+ ;;
+*)
+ skip_all='skipping UNC path tests, cannot determine current path as UNC'
+ test_done
+ ;;
+esac
+
+test_expect_success setup '
+ test_commit initial
+'
+
+test_expect_success clone '
+ git clone "file://$UNCPATH" clone
+'
+
+test_expect_success push '
+ (
+ cd clone &&
+ git checkout -b to-push &&
+ test_commit to-push &&
+ git push origin HEAD
+ ) &&
+ rev="$(git -C clone rev-parse --verify refs/heads/to-push)" &&
+ test "$rev" = "$(git rev-parse --verify refs/heads/to-push)"
+'
+
+test_done
diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh
index a433394..4241ea5 100755
--- a/t/t5601-clone.sh
+++ b/t/t5601-clone.sh
@@ -151,7 +151,7 @@ test_expect_success 'clone --mirror does not repeat tags' '
git clone --mirror src mirror2 &&
(cd mirror2 &&
git show-ref 2> clone.err > clone.out) &&
- test_must_fail grep Duplicate mirror2/clone.err &&
+ ! grep Duplicate mirror2/clone.err &&
grep some-tag mirror2/clone.out
'
diff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh
index 5e5370f..8c2c6ea 100755
--- a/t/t6030-bisect-porcelain.sh
+++ b/t/t6030-bisect-porcelain.sh
@@ -407,7 +407,7 @@ test_expect_success 'good merge base when good and bad are siblings' '
test_i18ngrep "merge base must be tested" my_bisect_log.txt &&
grep $HASH4 my_bisect_log.txt &&
git bisect good > my_bisect_log.txt &&
- test_must_fail grep "merge base must be tested" my_bisect_log.txt &&
+ ! grep "merge base must be tested" my_bisect_log.txt &&
grep $HASH6 my_bisect_log.txt &&
git bisect reset
'
diff --git a/t/t6134-pathspec-in-submodule.sh b/t/t6134-pathspec-in-submodule.sh
new file mode 100755
index 0000000..fd401ca
--- /dev/null
+++ b/t/t6134-pathspec-in-submodule.sh
@@ -0,0 +1,36 @@
+#!/bin/sh
+
+test_description='test case exclude pathspec'
+
+. ./test-lib.sh
+
+test_expect_success 'setup a submodule' '
+ test_create_repo pretzel &&
+ : >pretzel/a &&
+ git -C pretzel add a &&
+ git -C pretzel commit -m "add a file" -- a &&
+ git submodule add ./pretzel sub &&
+ git commit -a -m "add submodule" &&
+ git submodule deinit --all
+'
+
+cat <<EOF >expect
+fatal: Pathspec 'sub/a' is in submodule 'sub'
+EOF
+
+test_expect_success 'error message for path inside submodule' '
+ echo a >sub/a &&
+ test_must_fail git add sub/a 2>actual &&
+ test_cmp expect actual
+'
+
+cat <<EOF >expect
+fatal: Pathspec '.' is in submodule 'sub'
+EOF
+
+test_expect_success 'error message for path inside submodule from within submodule' '
+ test_must_fail git -C sub add . 2>actual &&
+ test_cmp expect actual
+'
+
+test_done
diff --git a/t/t6500-gc.sh b/t/t6500-gc.sh
index 5d7d414..1762dfa 100755
--- a/t/t6500-gc.sh
+++ b/t/t6500-gc.sh
@@ -43,4 +43,29 @@ test_expect_success 'gc is not aborted due to a stale symref' '
)
'
+test_expect_success 'auto gc with too many loose objects does not attempt to create bitmaps' '
+ test_config gc.auto 3 &&
+ test_config gc.autodetach false &&
+ test_config pack.writebitmaps true &&
+ # We need to create two object whose sha1s start with 17
+ # since this is what git gc counts. As it happens, these
+ # two blobs will do so.
+ test_commit 263 &&
+ test_commit 410 &&
+ # Our first gc will create a pack; our second will create a second pack
+ git gc --auto &&
+ ls .git/objects/pack | sort >existing_packs &&
+ test_commit 523 &&
+ test_commit 790 &&
+
+ git gc --auto 2>err &&
+ test_i18ngrep ! "^warning:" err &&
+ ls .git/objects/pack/ | sort >post_packs &&
+ comm -1 -3 existing_packs post_packs >new &&
+ comm -2 -3 existing_packs post_packs >del &&
+ test_line_count = 0 del && # No packs are deleted
+ test_line_count = 2 new # There is one new pack and its .idx
+'
+
+
test_done
diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh
index 07869b0..b676b90 100755
--- a/t/t7004-tag.sh
+++ b/t/t7004-tag.sh
@@ -149,11 +149,11 @@ test_expect_success '--force can create a tag with the name of one existing' '
tag_exists mytag'
test_expect_success '--force is moot with a non-existing tag name' '
+ test_when_finished git tag -d newtag forcetag &&
git tag newtag >expect &&
git tag --force forcetag >actual &&
test_cmp expect actual
'
-git tag -d newtag forcetag
# deleting tags:
@@ -324,11 +324,9 @@ EOF
'
test_expect_success 'listing tags in column with column.*' '
- git config column.tag row &&
- git config column.ui dense &&
+ test_config column.tag row &&
+ test_config column.ui dense &&
COLUMNS=40 git tag -l >actual &&
- git config --unset column.ui &&
- git config --unset column.tag &&
cat >expected <<\EOF &&
a1 aa1 cba t210 t211
v0.2.1 v1.0 v1.0.1 v1.1.3
@@ -341,9 +339,8 @@ test_expect_success 'listing tag with -n --column should fail' '
'
test_expect_success 'listing tags -n in column with column.ui ignored' '
- git config column.ui "row dense" &&
+ test_config column.ui "row dense" &&
COLUMNS=40 git tag -l -n >actual &&
- git config --unset column.ui &&
cat >expected <<\EOF &&
a1 Foo
aa1 Foo
@@ -874,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
@@ -1227,11 +1240,10 @@ test_expect_success GPG,RFC1991 \
'
# try to sign with bad user.signingkey
-git config user.signingkey BobTheMouse
test_expect_success GPG \
'git tag -s fails if gpg is misconfigured (bad key)' \
- 'test_must_fail git tag -s -m tail tag-gpg-failure'
-git config --unset user.signingkey
+ 'test_config user.signingkey BobTheMouse &&
+ test_must_fail git tag -s -m tail tag-gpg-failure'
# try to produce invalid signature
test_expect_success GPG \
@@ -1511,7 +1523,7 @@ test_expect_success 'reverse lexical sort' '
'
test_expect_success 'configured lexical sort' '
- git config tag.sort "v:refname" &&
+ test_config tag.sort "v:refname" &&
git tag -l "foo*" >actual &&
cat >expect <<-\EOF &&
foo1.3
@@ -1522,6 +1534,7 @@ test_expect_success 'configured lexical sort' '
'
test_expect_success 'option override configured sort' '
+ test_config tag.sort "v:refname" &&
git tag -l --sort=-refname "foo*" >actual &&
cat >expect <<-\EOF &&
foo1.6
@@ -1536,13 +1549,12 @@ test_expect_success 'invalid sort parameter on command line' '
'
test_expect_success 'invalid sort parameter in configuratoin' '
- git config tag.sort "v:notvalid" &&
+ test_config tag.sort "v:notvalid" &&
test_must_fail git tag -l "foo*"
'
test_expect_success 'version sort with prerelease reordering' '
- git config --unset tag.sort &&
- git config versionsort.prereleaseSuffix -rc &&
+ test_config versionsort.prereleaseSuffix -rc &&
git tag foo1.6-rc1 &&
git tag foo1.6-rc2 &&
git tag -l --sort=version:refname "foo*" >actual &&
@@ -1557,6 +1569,7 @@ test_expect_success 'version sort with prerelease reordering' '
'
test_expect_success 'reverse version sort with prerelease reordering' '
+ test_config versionsort.prereleaseSuffix -rc &&
git tag -l --sort=-version:refname "foo*" >actual &&
cat >expect <<-\EOF &&
foo1.10
@@ -1568,6 +1581,103 @@ test_expect_success 'reverse version sort with prerelease reordering' '
test_cmp expect actual
'
+test_expect_success 'version sort with prerelease reordering and common leading character' '
+ test_config versionsort.prereleaseSuffix -before &&
+ git tag foo1.7-before1 &&
+ git tag foo1.7 &&
+ git tag foo1.7-after1 &&
+ git tag -l --sort=version:refname "foo1.7*" >actual &&
+ cat >expect <<-\EOF &&
+ foo1.7-before1
+ foo1.7
+ foo1.7-after1
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'version sort with prerelease reordering, multiple suffixes and common leading character' '
+ test_config versionsort.prereleaseSuffix -before &&
+ git config --add versionsort.prereleaseSuffix -after &&
+ git tag -l --sort=version:refname "foo1.7*" >actual &&
+ cat >expect <<-\EOF &&
+ foo1.7-before1
+ foo1.7-after1
+ foo1.7
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'version sort with prerelease reordering, multiple suffixes match the same tag' '
+ test_config versionsort.prereleaseSuffix -bar &&
+ git config --add versionsort.prereleaseSuffix -foo-baz &&
+ git config --add versionsort.prereleaseSuffix -foo-bar &&
+ git tag foo1.8-foo-bar &&
+ git tag foo1.8-foo-baz &&
+ git tag foo1.8 &&
+ git tag -l --sort=version:refname "foo1.8*" >actual &&
+ cat >expect <<-\EOF &&
+ foo1.8-foo-baz
+ foo1.8-foo-bar
+ foo1.8
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'version sort with prerelease reordering, multiple suffixes match starting at the same position' '
+ test_config versionsort.prereleaseSuffix -pre &&
+ git config --add versionsort.prereleaseSuffix -prerelease &&
+ git tag foo1.9-pre1 &&
+ git tag foo1.9-pre2 &&
+ git tag foo1.9-prerelease1 &&
+ git tag -l --sort=version:refname "foo1.9*" >actual &&
+ cat >expect <<-\EOF &&
+ foo1.9-pre1
+ foo1.9-pre2
+ foo1.9-prerelease1
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'version sort with general suffix reordering' '
+ test_config versionsort.suffix -alpha &&
+ git config --add versionsort.suffix -beta &&
+ git config --add versionsort.suffix "" &&
+ git config --add versionsort.suffix -gamma &&
+ git config --add versionsort.suffix -delta &&
+ git tag foo1.10-alpha &&
+ git tag foo1.10-beta &&
+ git tag foo1.10-gamma &&
+ git tag foo1.10-delta &&
+ git tag foo1.10-unlisted-suffix &&
+ git tag -l --sort=version:refname "foo1.10*" >actual &&
+ cat >expect <<-\EOF &&
+ foo1.10-alpha
+ foo1.10-beta
+ foo1.10
+ foo1.10-unlisted-suffix
+ foo1.10-gamma
+ foo1.10-delta
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'versionsort.suffix overrides versionsort.prereleaseSuffix' '
+ test_config versionsort.suffix -before &&
+ test_config versionsort.prereleaseSuffix -after &&
+ git tag -l --sort=version:refname "foo1.7*" >actual &&
+ cat >expect <<-\EOF &&
+ foo1.7-before1
+ foo1.7
+ foo1.7-after1
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'version sort with very long prerelease suffix' '
+ test_config versionsort.prereleaseSuffix -very-looooooooooooooooooooooooong-prerelease-suffix &&
+ git tag -l --sort=version:refname
+'
+
run_with_limited_stack () {
(ulimit -s 128 && "$@")
}
@@ -1596,13 +1706,11 @@ EOF"
test_expect_success '--format should list tags as per format given' '
cat >expect <<-\EOF &&
- refname : refs/tags/foo1.10
- refname : refs/tags/foo1.3
- refname : refs/tags/foo1.6
- refname : refs/tags/foo1.6-rc1
- refname : refs/tags/foo1.6-rc2
+ refname : refs/tags/v1.0
+ refname : refs/tags/v1.0.1
+ refname : refs/tags/v1.1.3
EOF
- git tag -l --format="refname : %(refname)" "foo*" >actual &&
+ git tag -l --format="refname : %(refname)" "v1*" >actual &&
test_cmp expect actual
'
diff --git a/t/t7030-verify-tag.sh b/t/t7030-verify-tag.sh
index 07079a4..d62ccbb 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 b77cce8..c09ce0d 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 64f322c..725bbed 100755
--- a/t/t7406-submodule-update.sh
+++ b/t/t7406-submodule-update.sh
@@ -140,6 +140,23 @@ test_expect_success 'submodule update --init --recursive from subdirectory' '
test_i18ncmp expect2 actual2
'
+cat <<EOF >expect2
+Submodule 'foo/sub' ($pwd/withsubs/../rebasing) registered for path 'sub'
+EOF
+
+test_expect_success 'submodule update --init from and of subdirectory' '
+ git init withsubs &&
+ (cd withsubs &&
+ mkdir foo &&
+ git submodule add "$(pwd)/../rebasing" foo/sub &&
+ (cd foo &&
+ git submodule deinit -f sub &&
+ git submodule update --init sub 2>../../actual2
+ )
+ ) &&
+ test_i18ncmp expect2 actual2
+'
+
apos="'";
test_expect_success 'submodule update does not fetch already present commits' '
(cd submodule &&
diff --git a/t/t7411-submodule-config.sh b/t/t7411-submodule-config.sh
index d389ae5..eea36f1 100755
--- a/t/t7411-submodule-config.sh
+++ b/t/t7411-submodule-config.sh
@@ -134,12 +134,33 @@ test_expect_success 'reading of local configuration' '
"" submodule \
>actual &&
test_cmp expect_local_path actual &&
- git config submodule.a.url $old_a &&
- git config submodule.submodule.url $old_submodule &&
+ git config submodule.a.url "$old_a" &&
+ git config submodule.submodule.url "$old_submodule" &&
git config --unset submodule.a.path c
)
'
+cat >super/expect_url <<EOF
+Submodule url: '../submodule' for path 'b'
+Submodule url: 'git@somewhere.else.net:submodule.git' for path 'submodule'
+EOF
+
+test_expect_success 'reading of local configuration for uninitialized submodules' '
+ (
+ cd super &&
+ git submodule deinit -f b &&
+ old_submodule=$(git config submodule.submodule.url) &&
+ git config submodule.submodule.url git@somewhere.else.net:submodule.git &&
+ test-submodule-config --url \
+ "" b \
+ "" submodule \
+ >actual &&
+ test_cmp expect_url actual &&
+ git config submodule.submodule.url "$old_submodule" &&
+ git submodule init b
+ )
+'
+
cat >super/expect_fetchrecurse_die.err <<EOF
fatal: bad submodule.submodule.fetchrecursesubmodules argument: blabla
EOF
diff --git a/t/t7512-status-help.sh b/t/t7512-status-help.sh
index 5c3db65..458608c 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/t7610-mergetool.sh b/t/t7610-mergetool.sh
index 63d36fb..381b7df 100755
--- a/t/t7610-mergetool.sh
+++ b/t/t7610-mergetool.sh
@@ -55,6 +55,22 @@ test_expect_success 'setup' '
git rm file12 &&
git commit -m "branch1 changes" &&
+ git checkout -b delete-base branch1 &&
+ mkdir -p a/a &&
+ (echo one; echo two; echo 3; echo 4) >a/a/file.txt &&
+ git add a/a/file.txt &&
+ git commit -m"base file" &&
+ git checkout -b move-to-b delete-base &&
+ mkdir -p b/b &&
+ git mv a/a/file.txt b/b/file.txt &&
+ (echo one; echo two; echo 4) >b/b/file.txt &&
+ git commit -a -m"move to b" &&
+ git checkout -b move-to-c delete-base &&
+ mkdir -p c/c &&
+ git mv a/a/file.txt c/c/file.txt &&
+ (echo one; echo two; echo 3) >c/c/file.txt &&
+ git commit -a -m"move to c" &&
+
git checkout -b stash1 master &&
echo stash1 change file11 >file11 &&
git add file11 &&
@@ -86,6 +102,23 @@ test_expect_success 'setup' '
git rm file11 &&
git commit -m "master updates" &&
+ git clean -fdx &&
+ git checkout -b order-file-start master &&
+ echo start >a &&
+ echo start >b &&
+ git add a b &&
+ git commit -m start &&
+ git checkout -b order-file-side1 order-file-start &&
+ echo side1 >a &&
+ echo side1 >b &&
+ git add a b &&
+ git commit -m side1 &&
+ git checkout -b order-file-side2 order-file-start &&
+ echo side2 >a &&
+ echo side2 >b &&
+ git add a b &&
+ git commit -m side2 &&
+
git config merge.tool mytool &&
git config mergetool.mytool.cmd "cat \"\$REMOTE\" >\"\$MERGED\"" &&
git config mergetool.mytool.trustExitCode true &&
@@ -94,7 +127,8 @@ test_expect_success 'setup' '
'
test_expect_success 'custom mergetool' '
- git checkout -b test1 branch1 &&
+ test_when_finished "git reset --hard" &&
+ git checkout -b test$test_count branch1 &&
git submodule update -N &&
test_must_fail git merge master >/dev/null 2>&1 &&
( yes "" | git mergetool both >/dev/null 2>&1 ) &&
@@ -112,8 +146,13 @@ test_expect_success 'custom mergetool' '
'
test_expect_success 'mergetool crlf' '
+ test_when_finished "git reset --hard" &&
+ # This test_config line must go after the above reset line so that
+ # core.autocrlf is unconfigured before reset runs. (The
+ # test_config command uses test_when_finished internally and
+ # test_when_finished is LIFO.)
test_config core.autocrlf true &&
- git checkout -b test2 branch1 &&
+ git checkout -b test$test_count branch1 &&
test_must_fail git merge master >/dev/null 2>&1 &&
( yes "" | git mergetool file1 >/dev/null 2>&1 ) &&
( yes "" | git mergetool file2 >/dev/null 2>&1 ) &&
@@ -128,13 +167,12 @@ test_expect_success 'mergetool crlf' '
test "$(printf x | cat subdir/file3 -)" = "$(printf "master new sub\r\nx")" &&
git submodule update -N &&
test "$(cat submod/bar)" = "master submodule" &&
- git commit -m "branch1 resolved with mergetool - autocrlf" &&
- test_config core.autocrlf false &&
- git reset --hard
+ git commit -m "branch1 resolved with mergetool - autocrlf"
'
test_expect_success 'mergetool in subdir' '
- git checkout -b test3 branch1 &&
+ test_when_finished "git reset --hard" &&
+ git checkout -b test$test_count branch1 &&
git submodule update -N &&
(
cd subdir &&
@@ -145,8 +183,13 @@ test_expect_success 'mergetool in subdir' '
'
test_expect_success 'mergetool on file in parent dir' '
+ test_when_finished "git reset --hard" &&
+ git checkout -b test$test_count branch1 &&
+ git submodule update -N &&
(
cd subdir &&
+ test_must_fail git merge master >/dev/null 2>&1 &&
+ ( yes "" | git mergetool file3 >/dev/null 2>&1 ) &&
( yes "" | git mergetool ../file1 >/dev/null 2>&1 ) &&
( yes "" | git mergetool ../file2 ../spaced\ name >/dev/null 2>&1 ) &&
( yes "" | git mergetool ../both >/dev/null 2>&1 ) &&
@@ -161,7 +204,8 @@ test_expect_success 'mergetool on file in parent dir' '
'
test_expect_success 'mergetool skips autoresolved' '
- git checkout -b test4 branch1 &&
+ test_when_finished "git reset --hard" &&
+ git checkout -b test$test_count branch1 &&
git submodule update -N &&
test_must_fail git merge master &&
test -n "$(git ls-files -u)" &&
@@ -169,11 +213,12 @@ test_expect_success 'mergetool skips autoresolved' '
( yes "d" | git mergetool file12 >/dev/null 2>&1 ) &&
( yes "l" | git mergetool submod >/dev/null 2>&1 ) &&
output="$(git mergetool --no-prompt)" &&
- test "$output" = "No files need merging" &&
- git reset --hard
+ test "$output" = "No files need merging"
'
-test_expect_success 'mergetool merges all from subdir' '
+test_expect_success 'mergetool merges all from subdir (rerere disabled)' '
+ test_when_finished "git reset --hard" &&
+ git checkout -b test$test_count branch1 &&
test_config rerere.enabled false &&
(
cd subdir &&
@@ -189,21 +234,41 @@ test_expect_success 'mergetool merges all from subdir' '
)
'
+test_expect_success 'mergetool merges all from subdir (rerere enabled)' '
+ test_when_finished "git reset --hard" &&
+ git checkout -b test$test_count branch1 &&
+ test_config rerere.enabled true &&
+ rm -rf .git/rr-cache &&
+ (
+ cd subdir &&
+ test_must_fail git merge master &&
+ ( yes "r" | git mergetool ../submod ) &&
+ ( yes "d" "d" | git mergetool --no-prompt ) &&
+ test "$(cat ../file1)" = "master updated" &&
+ test "$(cat ../file2)" = "master new" &&
+ test "$(cat file3)" = "master new sub" &&
+ ( cd .. && git submodule update -N ) &&
+ test "$(cat ../submod/bar)" = "master submodule" &&
+ git commit -m "branch2 resolved by mergetool from subdir"
+ )
+'
+
test_expect_success 'mergetool skips resolved paths when rerere is active' '
+ test_when_finished "git reset --hard" &&
test_config rerere.enabled true &&
rm -rf .git/rr-cache &&
- git checkout -b test5 branch1 &&
+ git checkout -b test$test_count branch1 &&
git submodule update -N &&
test_must_fail git merge master >/dev/null 2>&1 &&
( yes "l" | git mergetool --no-prompt submod >/dev/null 2>&1 ) &&
( yes "d" "d" | git mergetool --no-prompt >/dev/null 2>&1 ) &&
git submodule update -N &&
output="$(yes "n" | git mergetool --no-prompt)" &&
- test "$output" = "No files need merging" &&
- git reset --hard
+ test "$output" = "No files need merging"
'
test_expect_success 'conflicted stash sets up rerere' '
+ test_when_finished "git reset --hard" &&
test_config rerere.enabled true &&
git checkout stash1 &&
echo "Conflicting stash content" >file11 &&
@@ -231,67 +296,57 @@ test_expect_success 'conflicted stash sets up rerere' '
'
test_expect_success 'mergetool takes partial path' '
- git reset --hard &&
+ test_when_finished "git reset --hard" &&
test_config rerere.enabled false &&
- git checkout -b test12 branch1 &&
+ git checkout -b test$test_count branch1 &&
git submodule update -N &&
test_must_fail git merge master &&
( yes "" | git mergetool subdir ) &&
- test "$(cat subdir/file3)" = "master new sub" &&
- git reset --hard
+ test "$(cat subdir/file3)" = "master new sub"
'
test_expect_success 'mergetool delete/delete conflict' '
- git checkout -b delete-base branch1 &&
- mkdir -p a/a &&
- (echo one; echo two; echo 3; echo 4) >a/a/file.txt &&
- git add a/a/file.txt &&
- git commit -m"base file" &&
- git checkout -b move-to-b delete-base &&
- mkdir -p b/b &&
- git mv a/a/file.txt b/b/file.txt &&
- (echo one; echo two; echo 4) >b/b/file.txt &&
- git commit -a -m"move to b" &&
- git checkout -b move-to-c delete-base &&
- mkdir -p c/c &&
- git mv a/a/file.txt c/c/file.txt &&
- (echo one; echo two; echo 3) >c/c/file.txt &&
- git commit -a -m"move to c" &&
+ test_when_finished "git reset --hard" &&
+ git checkout -b test$test_count move-to-c &&
test_must_fail git merge move-to-b &&
echo d | git mergetool a/a/file.txt &&
! test -f a/a/file.txt &&
- git reset --hard HEAD &&
+ git reset --hard &&
test_must_fail git merge move-to-b &&
echo m | git mergetool a/a/file.txt &&
test -f b/b/file.txt &&
- git reset --hard HEAD &&
+ git reset --hard &&
test_must_fail git merge move-to-b &&
! echo a | git mergetool a/a/file.txt &&
- ! test -f a/a/file.txt &&
- git reset --hard HEAD
+ ! test -f a/a/file.txt
'
test_expect_success 'mergetool produces no errors when keepBackup is used' '
+ test_when_finished "git reset --hard" &&
+ git checkout -b test$test_count move-to-c &&
test_config mergetool.keepBackup true &&
test_must_fail git merge move-to-b &&
: >expect &&
echo d | git mergetool a/a/file.txt 2>actual &&
test_cmp expect actual &&
- ! test -d a &&
- git reset --hard HEAD
+ ! test -d a
'
test_expect_success 'mergetool honors tempfile config for deleted files' '
+ test_when_finished "git reset --hard" &&
+ git checkout -b test$test_count move-to-c &&
test_config mergetool.keepTemporaries false &&
test_must_fail git merge move-to-b &&
echo d | git mergetool a/a/file.txt &&
- ! test -d a &&
- git reset --hard HEAD
+ ! test -d a
'
test_expect_success 'mergetool keeps tempfiles when aborting delete/delete' '
+ test_when_finished "git reset --hard" &&
+ test_when_finished "git clean -fdx" &&
+ git checkout -b test$test_count move-to-c &&
test_config mergetool.keepTemporaries true &&
test_must_fail git merge move-to-b &&
! (echo a; echo n) | git mergetool a/a/file.txt &&
@@ -302,18 +357,17 @@ test_expect_success 'mergetool keeps tempfiles when aborting delete/delete' '
file_REMOTE_.txt
EOF
ls -1 a/a | sed -e "s/[0-9]*//g" >actual &&
- test_cmp expect actual &&
- git clean -fdx &&
- git reset --hard HEAD
+ test_cmp expect actual
'
test_expect_success 'deleted vs modified submodule' '
- git checkout -b test6 branch1 &&
+ test_when_finished "git reset --hard" &&
+ git checkout -b test$test_count branch1 &&
git submodule update -N &&
mv submod submod-movedaside &&
git rm --cached submod &&
git commit -m "Submodule deleted from branch" &&
- git checkout -b test6.a test6 &&
+ git checkout -b test$test_count.a test$test_count &&
test_must_fail git merge master &&
test -n "$(git ls-files -u)" &&
( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) &&
@@ -329,7 +383,7 @@ test_expect_success 'deleted vs modified submodule' '
git commit -m "Merge resolved by keeping module" &&
mv submod submod-movedaside &&
- git checkout -b test6.b test6 &&
+ git checkout -b test$test_count.b test$test_count &&
git submodule update -N &&
test_must_fail git merge master &&
test -n "$(git ls-files -u)" &&
@@ -343,9 +397,9 @@ test_expect_success 'deleted vs modified submodule' '
git commit -m "Merge resolved by deleting module" &&
mv submod-movedaside submod &&
- git checkout -b test6.c master &&
+ git checkout -b test$test_count.c master &&
git submodule update -N &&
- test_must_fail git merge test6 &&
+ test_must_fail git merge test$test_count &&
test -n "$(git ls-files -u)" &&
( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) &&
( yes "" | git mergetool both >/dev/null 2>&1 ) &&
@@ -359,9 +413,9 @@ test_expect_success 'deleted vs modified submodule' '
git commit -m "Merge resolved by deleting module" &&
mv submod.orig submod &&
- git checkout -b test6.d master &&
+ git checkout -b test$test_count.d master &&
git submodule update -N &&
- test_must_fail git merge test6 &&
+ test_must_fail git merge test$test_count &&
test -n "$(git ls-files -u)" &&
( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) &&
( yes "" | git mergetool both >/dev/null 2>&1 ) &&
@@ -372,19 +426,19 @@ test_expect_success 'deleted vs modified submodule' '
test "$(cat submod/bar)" = "master submodule" &&
output="$(git mergetool --no-prompt)" &&
test "$output" = "No files need merging" &&
- git commit -m "Merge resolved by keeping module" &&
- git reset --hard HEAD
+ git commit -m "Merge resolved by keeping module"
'
test_expect_success 'file vs modified submodule' '
- git checkout -b test7 branch1 &&
+ test_when_finished "git reset --hard" &&
+ git checkout -b test$test_count branch1 &&
git submodule update -N &&
mv submod submod-movedaside &&
git rm --cached submod &&
echo not a submodule >submod &&
git add submod &&
git commit -m "Submodule path becomes file" &&
- git checkout -b test7.a branch1 &&
+ git checkout -b test$test_count.a branch1 &&
test_must_fail git merge master &&
test -n "$(git ls-files -u)" &&
( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) &&
@@ -400,7 +454,7 @@ test_expect_success 'file vs modified submodule' '
git commit -m "Merge resolved by keeping module" &&
mv submod submod-movedaside &&
- git checkout -b test7.b test7 &&
+ git checkout -b test$test_count.b test$test_count &&
test_must_fail git merge master &&
test -n "$(git ls-files -u)" &&
( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) &&
@@ -413,11 +467,11 @@ test_expect_success 'file vs modified submodule' '
test "$output" = "No files need merging" &&
git commit -m "Merge resolved by keeping file" &&
- git checkout -b test7.c master &&
+ git checkout -b test$test_count.c master &&
rmdir submod && mv submod-movedaside submod &&
test ! -e submod.orig &&
git submodule update -N &&
- test_must_fail git merge test7 &&
+ test_must_fail git merge test$test_count &&
test -n "$(git ls-files -u)" &&
( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) &&
( yes "" | git mergetool both >/dev/null 2>&1 ) &&
@@ -430,10 +484,10 @@ test_expect_success 'file vs modified submodule' '
test "$output" = "No files need merging" &&
git commit -m "Merge resolved by keeping file" &&
- git checkout -b test7.d master &&
+ git checkout -b test$test_count.d master &&
rmdir submod && mv submod.orig submod &&
git submodule update -N &&
- test_must_fail git merge test7 &&
+ test_must_fail git merge test$test_count &&
test -n "$(git ls-files -u)" &&
( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) &&
( yes "" | git mergetool both>/dev/null 2>&1 ) &&
@@ -448,7 +502,8 @@ test_expect_success 'file vs modified submodule' '
'
test_expect_success 'submodule in subdirectory' '
- git checkout -b test10 branch1 &&
+ test_when_finished "git reset --hard" &&
+ git checkout -b test$test_count branch1 &&
git submodule update -N &&
(
cd subdir &&
@@ -460,56 +515,57 @@ test_expect_success 'submodule in subdirectory' '
git commit -m "add initial versions"
)
) &&
+ test_when_finished "rm -rf subdir/subdir_module" &&
git submodule add git://example.com/subsubmodule subdir/subdir_module &&
git add subdir/subdir_module &&
git commit -m "add submodule in subdirectory" &&
- git checkout -b test10.a test10 &&
+ git checkout -b test$test_count.a test$test_count &&
git submodule update -N &&
(
cd subdir/subdir_module &&
git checkout -b super10.a &&
- echo test10.a >file15 &&
+ echo test$test_count.a >file15 &&
git add file15 &&
git commit -m "on branch 10.a"
) &&
git add subdir/subdir_module &&
- git commit -m "change submodule in subdirectory on test10.a" &&
+ git commit -m "change submodule in subdirectory on test$test_count.a" &&
- git checkout -b test10.b test10 &&
+ git checkout -b test$test_count.b test$test_count &&
git submodule update -N &&
(
cd subdir/subdir_module &&
git checkout -b super10.b &&
- echo test10.b >file15 &&
+ echo test$test_count.b >file15 &&
git add file15 &&
git commit -m "on branch 10.b"
) &&
git add subdir/subdir_module &&
- git commit -m "change submodule in subdirectory on test10.b" &&
+ git commit -m "change submodule in subdirectory on test$test_count.b" &&
- test_must_fail git merge test10.a >/dev/null 2>&1 &&
+ test_must_fail git merge test$test_count.a >/dev/null 2>&1 &&
(
cd subdir &&
( yes "l" | git mergetool subdir_module )
) &&
- test "$(cat subdir/subdir_module/file15)" = "test10.b" &&
+ test "$(cat subdir/subdir_module/file15)" = "test$test_count.b" &&
git submodule update -N &&
- test "$(cat subdir/subdir_module/file15)" = "test10.b" &&
+ test "$(cat subdir/subdir_module/file15)" = "test$test_count.b" &&
git reset --hard &&
git submodule update -N &&
- test_must_fail git merge test10.a >/dev/null 2>&1 &&
+ test_must_fail git merge test$test_count.a >/dev/null 2>&1 &&
( yes "r" | git mergetool subdir/subdir_module ) &&
- test "$(cat subdir/subdir_module/file15)" = "test10.b" &&
+ test "$(cat subdir/subdir_module/file15)" = "test$test_count.b" &&
git submodule update -N &&
- test "$(cat subdir/subdir_module/file15)" = "test10.a" &&
- git commit -m "branch1 resolved with mergetool" &&
- rm -rf subdir/subdir_module
+ test "$(cat subdir/subdir_module/file15)" = "test$test_count.a" &&
+ git commit -m "branch1 resolved with mergetool"
'
test_expect_success 'directory vs modified submodule' '
- git checkout -b test11 branch1 &&
+ test_when_finished "git reset --hard" &&
+ git checkout -b test$test_count branch1 &&
mv submod submod-movedaside &&
git rm --cached submod &&
mkdir submod &&
@@ -523,7 +579,7 @@ test_expect_success 'directory vs modified submodule' '
test "$(cat submod/file16)" = "not a submodule" &&
rm -rf submod.orig &&
- git reset --hard >/dev/null 2>&1 &&
+ git reset --hard &&
test_must_fail git merge master &&
test -n "$(git ls-files -u)" &&
test ! -e submod.orig &&
@@ -535,58 +591,59 @@ test_expect_success 'directory vs modified submodule' '
( cd submod && git clean -f && git reset --hard ) &&
git submodule update -N &&
test "$(cat submod/bar)" = "master submodule" &&
- git reset --hard >/dev/null 2>&1 && rm -rf submod-movedaside &&
+ git reset --hard &&
+ rm -rf submod-movedaside &&
- git checkout -b test11.c master &&
+ git checkout -b test$test_count.c master &&
git submodule update -N &&
- test_must_fail git merge test11 &&
+ test_must_fail git merge test$test_count &&
test -n "$(git ls-files -u)" &&
( yes "l" | git mergetool submod ) &&
git submodule update -N &&
test "$(cat submod/bar)" = "master submodule" &&
- git reset --hard >/dev/null 2>&1 &&
+ git reset --hard &&
git submodule update -N &&
- test_must_fail git merge test11 &&
+ test_must_fail git merge test$test_count &&
test -n "$(git ls-files -u)" &&
test ! -e submod.orig &&
( yes "r" | git mergetool submod ) &&
test "$(cat submod/file16)" = "not a submodule" &&
- git reset --hard master >/dev/null 2>&1 &&
+ git reset --hard master &&
( cd submod && git clean -f && git reset --hard ) &&
git submodule update -N
'
test_expect_success 'file with no base' '
- git checkout -b test13 branch1 &&
+ test_when_finished "git reset --hard" &&
+ git checkout -b test$test_count branch1 &&
test_must_fail git merge master &&
git mergetool --no-prompt --tool mybase -- both &&
>expected &&
- test_cmp both expected &&
- git reset --hard master >/dev/null 2>&1
+ test_cmp both expected
'
test_expect_success 'custom commands override built-ins' '
- git checkout -b test14 branch1 &&
+ test_when_finished "git reset --hard" &&
+ git checkout -b test$test_count branch1 &&
test_config mergetool.defaults.cmd "cat \"\$REMOTE\" >\"\$MERGED\"" &&
test_config mergetool.defaults.trustExitCode true &&
test_must_fail git merge master &&
git mergetool --no-prompt --tool defaults -- both &&
echo master both added >expected &&
- test_cmp both expected &&
- git reset --hard master >/dev/null 2>&1
+ test_cmp both expected
'
test_expect_success 'filenames seen by tools start with ./' '
- git checkout -b test15 branch1 &&
+ test_when_finished "git reset --hard" &&
+ git checkout -b test$test_count branch1 &&
test_config mergetool.writeToTemp false &&
test_config mergetool.myecho.cmd "echo \"\$LOCAL\"" &&
test_config mergetool.myecho.trustExitCode true &&
test_must_fail git merge master &&
git mergetool --no-prompt --tool myecho -- both >actual &&
- grep ^\./both_LOCAL_ actual >/dev/null &&
- git reset --hard master >/dev/null 2>&1
+ grep ^\./both_LOCAL_ actual >/dev/null
'
test_lazy_prereq MKTEMP '
@@ -596,53 +653,48 @@ test_lazy_prereq MKTEMP '
'
test_expect_success MKTEMP 'temporary filenames are used with mergetool.writeToTemp' '
- git checkout -b test16 branch1 &&
+ test_when_finished "git reset --hard" &&
+ git checkout -b test$test_count branch1 &&
test_config mergetool.writeToTemp true &&
test_config mergetool.myecho.cmd "echo \"\$LOCAL\"" &&
test_config mergetool.myecho.trustExitCode true &&
test_must_fail git merge master &&
git mergetool --no-prompt --tool myecho -- both >actual &&
- test_must_fail grep ^\./both_LOCAL_ actual >/dev/null &&
- grep /both_LOCAL_ actual >/dev/null &&
- git reset --hard master >/dev/null 2>&1
+ ! grep ^\./both_LOCAL_ actual >/dev/null &&
+ grep /both_LOCAL_ actual >/dev/null
'
test_expect_success 'diff.orderFile configuration is honored' '
+ test_when_finished "git reset --hard" &&
+ git checkout -b test$test_count order-file-side2 &&
test_config diff.orderFile order-file &&
test_config mergetool.myecho.cmd "echo \"\$LOCAL\"" &&
test_config mergetool.myecho.trustExitCode true &&
echo b >order-file &&
echo a >>order-file &&
- git checkout -b order-file-start master &&
- echo start >a &&
- echo start >b &&
- git add a b &&
- git commit -m start &&
- git checkout -b order-file-side1 order-file-start &&
- echo side1 >a &&
- echo side1 >b &&
- git add a b &&
- git commit -m side1 &&
- git checkout -b order-file-side2 order-file-start &&
- echo side2 >a &&
- echo side2 >b &&
- git add a b &&
- git commit -m side2 &&
test_must_fail git merge order-file-side1 &&
cat >expect <<-\EOF &&
Merging:
b
a
EOF
+
+ # make sure "order-file" that is ambiguous between
+ # rev and path is understood correctly.
+ git branch order-file HEAD &&
+
git mergetool --no-prompt --tool myecho >output &&
git grep --no-index -h -A2 Merging: output >actual &&
- test_cmp expect actual &&
- git reset --hard >/dev/null
+ test_cmp expect actual
'
test_expect_success 'mergetool -Oorder-file is honored' '
+ test_when_finished "git reset --hard" &&
+ git checkout -b test$test_count order-file-side2 &&
test_config diff.orderFile order-file &&
test_config mergetool.myecho.cmd "echo \"\$LOCAL\"" &&
test_config mergetool.myecho.trustExitCode true &&
+ echo b >order-file &&
+ echo a >>order-file &&
test_must_fail git merge order-file-side1 &&
cat >expect <<-\EOF &&
Merging:
@@ -652,7 +704,7 @@ test_expect_success 'mergetool -Oorder-file is honored' '
git mergetool -O/dev/null --no-prompt --tool myecho >output &&
git grep --no-index -h -A2 Merging: output >actual &&
test_cmp expect actual &&
- git reset --hard >/dev/null 2>&1 &&
+ git reset --hard &&
git config --unset diff.orderFile &&
test_must_fail git merge order-file-side1 &&
@@ -663,8 +715,7 @@ test_expect_success 'mergetool -Oorder-file is honored' '
EOF
git mergetool -Oorder-file --no-prompt --tool myecho >output &&
git grep --no-index -h -A2 Merging: output >actual &&
- test_cmp expect actual &&
- git reset --hard >/dev/null 2>&1
+ test_cmp expect actual
'
test_done
diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh
index 99d4123..aa0ef02 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/t7810-grep.sh b/t/t7810-grep.sh
index de2405c..19f0108 100755
--- a/t/t7810-grep.sh
+++ b/t/t7810-grep.sh
@@ -39,6 +39,10 @@ test_expect_success setup '
echo "a+bc"
echo "abc"
} >ab &&
+ {
+ echo d &&
+ echo 0
+ } >d0 &&
echo vvv >v &&
echo ww w >w &&
echo x x xx x >x &&
@@ -1105,36 +1109,36 @@ test_expect_success 'grep pattern with grep.patternType=fixed, =basic, =extended
'
test_expect_success 'grep -G -F -P -E pattern' '
- >empty &&
- test_must_fail git grep -G -F -P -E "a\x{2b}b\x{2a}c" ab >actual &&
- test_cmp empty actual
+ echo "d0:d" >expected &&
+ git grep -G -F -P -E "[\d]" d0 >actual &&
+ test_cmp expected actual
'
test_expect_success 'grep pattern with grep.patternType=fixed, =basic, =perl, =extended' '
- >empty &&
- test_must_fail git \
+ echo "d0:d" >expected &&
+ git \
-c grep.patterntype=fixed \
-c grep.patterntype=basic \
-c grep.patterntype=perl \
-c grep.patterntype=extended \
- grep "a\x{2b}b\x{2a}c" ab >actual &&
- test_cmp empty actual
+ grep "[\d]" d0 >actual &&
+ test_cmp expected actual
'
test_expect_success LIBPCRE 'grep -G -F -E -P pattern' '
- echo "ab:a+b*c" >expected &&
- git grep -G -F -E -P "a\x{2b}b\x{2a}c" ab >actual &&
+ echo "d0:0" >expected &&
+ git grep -G -F -E -P "[\d]" d0 >actual &&
test_cmp expected actual
'
test_expect_success LIBPCRE 'grep pattern with grep.patternType=fixed, =basic, =extended, =perl' '
- echo "ab:a+b*c" >expected &&
+ echo "d0:0" >expected &&
git \
-c grep.patterntype=fixed \
-c grep.patterntype=basic \
-c grep.patterntype=extended \
-c grep.patterntype=perl \
- grep "a\x{2b}b\x{2a}c" ab >actual &&
+ grep "[\d]" d0 >actual &&
test_cmp expected actual
'
diff --git a/t/t7814-grep-recurse-submodules.sh b/t/t7814-grep-recurse-submodules.sh
new file mode 100755
index 0000000..67247a0
--- /dev/null
+++ b/t/t7814-grep-recurse-submodules.sh
@@ -0,0 +1,241 @@
+#!/bin/sh
+
+test_description='Test grep recurse-submodules feature
+
+This test verifies the recurse-submodules feature correctly greps across
+submodules.
+'
+
+. ./test-lib.sh
+
+test_expect_success 'setup directory structure and submodule' '
+ echo "foobar" >a &&
+ mkdir b &&
+ echo "bar" >b/b &&
+ git add a b &&
+ git commit -m "add a and b" &&
+ git init submodule &&
+ echo "foobar" >submodule/a &&
+ git -C submodule add a &&
+ git -C submodule commit -m "add a" &&
+ git submodule add ./submodule &&
+ git commit -m "added submodule"
+'
+
+test_expect_success 'grep correctly finds patterns in a submodule' '
+ cat >expect <<-\EOF &&
+ a:foobar
+ b/b:bar
+ submodule/a:foobar
+ EOF
+
+ git grep -e "bar" --recurse-submodules >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'grep and basic pathspecs' '
+ cat >expect <<-\EOF &&
+ submodule/a:foobar
+ EOF
+
+ git grep -e. --recurse-submodules -- submodule >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'grep and nested submodules' '
+ git init submodule/sub &&
+ echo "foobar" >submodule/sub/a &&
+ git -C submodule/sub add a &&
+ git -C submodule/sub commit -m "add a" &&
+ git -C submodule submodule add ./sub &&
+ git -C submodule add sub &&
+ git -C submodule commit -m "added sub" &&
+ git add submodule &&
+ git commit -m "updated submodule" &&
+
+ cat >expect <<-\EOF &&
+ a:foobar
+ b/b:bar
+ submodule/a:foobar
+ submodule/sub/a:foobar
+ EOF
+
+ git grep -e "bar" --recurse-submodules >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'grep and multiple patterns' '
+ cat >expect <<-\EOF &&
+ a:foobar
+ submodule/a:foobar
+ submodule/sub/a:foobar
+ EOF
+
+ git grep -e "bar" --and -e "foo" --recurse-submodules >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'grep and multiple patterns' '
+ cat >expect <<-\EOF &&
+ b/b:bar
+ EOF
+
+ git grep -e "bar" --and --not -e "foo" --recurse-submodules >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'basic grep tree' '
+ cat >expect <<-\EOF &&
+ HEAD:a:foobar
+ HEAD:b/b:bar
+ HEAD:submodule/a:foobar
+ HEAD:submodule/sub/a:foobar
+ EOF
+
+ git grep -e "bar" --recurse-submodules HEAD >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'grep tree HEAD^' '
+ cat >expect <<-\EOF &&
+ HEAD^:a:foobar
+ HEAD^:b/b:bar
+ HEAD^:submodule/a:foobar
+ EOF
+
+ git grep -e "bar" --recurse-submodules HEAD^ >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'grep tree HEAD^^' '
+ cat >expect <<-\EOF &&
+ HEAD^^:a:foobar
+ HEAD^^:b/b:bar
+ EOF
+
+ git grep -e "bar" --recurse-submodules HEAD^^ >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'grep tree and pathspecs' '
+ cat >expect <<-\EOF &&
+ HEAD:submodule/a:foobar
+ HEAD:submodule/sub/a:foobar
+ EOF
+
+ git grep -e "bar" --recurse-submodules HEAD -- submodule >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'grep tree and pathspecs' '
+ cat >expect <<-\EOF &&
+ HEAD:submodule/a:foobar
+ HEAD:submodule/sub/a:foobar
+ EOF
+
+ git grep -e "bar" --recurse-submodules HEAD -- "submodule*a" >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'grep tree and more pathspecs' '
+ cat >expect <<-\EOF &&
+ HEAD:submodule/a:foobar
+ EOF
+
+ git grep -e "bar" --recurse-submodules HEAD -- "submodul?/a" >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'grep tree and more pathspecs' '
+ cat >expect <<-\EOF &&
+ HEAD:submodule/sub/a:foobar
+ EOF
+
+ git grep -e "bar" --recurse-submodules HEAD -- "submodul*/sub/a" >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success !MINGW 'grep recurse submodule colon in name' '
+ git init parent &&
+ test_when_finished "rm -rf parent" &&
+ echo "foobar" >"parent/fi:le" &&
+ git -C parent add "fi:le" &&
+ git -C parent commit -m "add fi:le" &&
+
+ git init "su:b" &&
+ test_when_finished "rm -rf su:b" &&
+ echo "foobar" >"su:b/fi:le" &&
+ git -C "su:b" add "fi:le" &&
+ git -C "su:b" commit -m "add fi:le" &&
+
+ git -C parent submodule add "../su:b" "su:b" &&
+ git -C parent commit -m "add submodule" &&
+
+ cat >expect <<-\EOF &&
+ fi:le:foobar
+ su:b/fi:le:foobar
+ EOF
+ git -C parent grep -e "foobar" --recurse-submodules >actual &&
+ test_cmp expect actual &&
+
+ cat >expect <<-\EOF &&
+ HEAD:fi:le:foobar
+ HEAD:su:b/fi:le:foobar
+ EOF
+ git -C parent grep -e "foobar" --recurse-submodules HEAD >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'grep history with moved submoules' '
+ git init parent &&
+ test_when_finished "rm -rf parent" &&
+ echo "foobar" >parent/file &&
+ git -C parent add file &&
+ git -C parent commit -m "add file" &&
+
+ git init sub &&
+ test_when_finished "rm -rf sub" &&
+ echo "foobar" >sub/file &&
+ git -C sub add file &&
+ git -C sub commit -m "add file" &&
+
+ git -C parent submodule add ../sub dir/sub &&
+ git -C parent commit -m "add submodule" &&
+
+ cat >expect <<-\EOF &&
+ dir/sub/file:foobar
+ file:foobar
+ EOF
+ git -C parent grep -e "foobar" --recurse-submodules >actual &&
+ test_cmp expect actual &&
+
+ git -C parent mv dir/sub sub-moved &&
+ git -C parent commit -m "moved submodule" &&
+
+ cat >expect <<-\EOF &&
+ file:foobar
+ sub-moved/file:foobar
+ EOF
+ git -C parent grep -e "foobar" --recurse-submodules >actual &&
+ test_cmp expect actual &&
+
+ cat >expect <<-\EOF &&
+ HEAD^:dir/sub/file:foobar
+ HEAD^:file:foobar
+ EOF
+ git -C parent grep -e "foobar" --recurse-submodules HEAD^ >actual &&
+ test_cmp expect actual
+'
+
+test_incompatible_with_recurse_submodules ()
+{
+ test_expect_success "--recurse-submodules and $1 are incompatible" "
+ test_must_fail git grep -e. --recurse-submodules $1 2>actual &&
+ test_i18ngrep 'not supported with --recurse-submodules' actual
+ "
+}
+
+test_incompatible_with_recurse_submodules --untracked
+test_incompatible_with_recurse_submodules --no-index
+
+test_done
diff --git a/t/t8002-blame.sh b/t/t8002-blame.sh
index ab79de9..380e1c1 100755
--- a/t/t8002-blame.sh
+++ b/t/t8002-blame.sh
@@ -86,4 +86,36 @@ test_expect_success 'blame with showEmail config true' '
test_cmp expected_n result
'
+test_expect_success 'set up abbrev tests' '
+ test_commit abbrev &&
+ sha1=$(git rev-parse --verify HEAD) &&
+ check_abbrev () {
+ expect=$1; shift
+ echo $sha1 | cut -c 1-$expect >expect &&
+ git blame "$@" abbrev.t >actual &&
+ perl -lne "/[0-9a-f]+/ and print \$&" <actual >actual.sha &&
+ test_cmp expect actual.sha
+ }
+'
+
+test_expect_success 'blame --abbrev=<n> works' '
+ # non-boundary commits get +1 for alignment
+ check_abbrev 31 --abbrev=30 HEAD &&
+ check_abbrev 30 --abbrev=30 ^HEAD
+'
+
+test_expect_success 'blame -l aligns regular and boundary commits' '
+ check_abbrev 40 -l HEAD &&
+ check_abbrev 39 -l ^HEAD
+'
+
+test_expect_success 'blame --abbrev=40 behaves like -l' '
+ check_abbrev 40 --abbrev=40 HEAD &&
+ check_abbrev 39 --abbrev=40 ^HEAD
+'
+
+test_expect_success '--no-abbrev works like --abbrev=40' '
+ check_abbrev 40 --no-abbrev
+'
+
test_done
diff --git a/t/t8011-blame-split-file.sh b/t/t8011-blame-split-file.sh
new file mode 100755
index 0000000..8311250
--- /dev/null
+++ b/t/t8011-blame-split-file.sh
@@ -0,0 +1,117 @@
+#!/bin/sh
+
+test_description='
+The general idea is that we have a single file whose lines come from
+multiple other files, and those individual files were modified in the same
+commits. That means that we will see the same commit in multiple contexts,
+and each one should be attributed to the correct file.
+
+Note that we need to use "blame -C" to find the commit for all lines. We will
+not bother testing that the non-C case fails to find it. That is how blame
+behaves now, but it is not a property we want to make sure is retained.
+'
+. ./test-lib.sh
+
+# help avoid typing and reading long strings of similar lines
+# in the tests below
+generate_expect () {
+ while read nr data
+ do
+ i=0
+ while test $i -lt $nr
+ do
+ echo $data
+ i=$((i + 1))
+ done
+ done
+}
+
+test_expect_success 'setup split file case' '
+ # use lines long enough to trigger content detection
+ test_seq 1000 1010 >one &&
+ test_seq 2000 2010 >two &&
+ git add one two &&
+ test_commit base &&
+
+ sed "6s/^/modified /" <one >one.tmp &&
+ mv one.tmp one &&
+ sed "6s/^/modified /" <two >two.tmp &&
+ mv two.tmp two &&
+ git add -u &&
+ test_commit modified &&
+
+ cat one two >combined &&
+ git add combined &&
+ git rm one two &&
+ test_commit combined
+'
+
+test_expect_success 'setup simulated porcelain' '
+ # This just reads porcelain-ish output and tries
+ # to output the value of a given field for each line (either by
+ # reading the field that accompanies this line, or referencing
+ # the information found last time the commit was mentioned).
+ cat >read-porcelain.pl <<-\EOF
+ my $field = shift;
+ while (<>) {
+ if (/^[0-9a-f]{40} /) {
+ flush();
+ $hash = $&;
+ } elsif (/^$field (.*)/) {
+ $cache{$hash} = $1;
+ }
+ }
+ flush();
+
+ sub flush {
+ return unless defined $hash;
+ if (defined $cache{$hash}) {
+ print "$cache{$hash}\n";
+ } else {
+ print "NONE\n";
+ }
+ }
+ EOF
+'
+
+for output in porcelain line-porcelain
+do
+ test_expect_success "generate --$output output" '
+ git blame --root -C --$output combined >output
+ '
+
+ test_expect_success "$output output finds correct commits" '
+ generate_expect >expect <<-\EOF &&
+ 5 base
+ 1 modified
+ 10 base
+ 1 modified
+ 5 base
+ EOF
+ perl read-porcelain.pl summary <output >actual &&
+ test_cmp expect actual
+ '
+
+ test_expect_success "$output output shows correct filenames" '
+ generate_expect >expect <<-\EOF &&
+ 11 one
+ 11 two
+ EOF
+ perl read-porcelain.pl filename <output >actual &&
+ test_cmp expect actual
+ '
+
+ test_expect_success "$output output shows correct previous pointer" '
+ generate_expect >expect <<-EOF &&
+ 5 NONE
+ 1 $(git rev-parse modified^) one
+ 10 NONE
+ 1 $(git rev-parse modified^) two
+ 5 NONE
+ EOF
+ perl read-porcelain.pl previous <output >actual &&
+ test_cmp expect actual
+ '
+done
+
+test_done
diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh
index 3dc4a34..0f398dd 100755
--- a/t/t9001-send-email.sh
+++ b/t/t9001-send-email.sh
@@ -50,7 +50,7 @@ test_no_confirm () {
--smtp-server="$(pwd)/fake.sendmail" \
$@ \
$patches >stdout &&
- test_must_fail grep "Send this email" stdout &&
+ ! grep "Send this email" stdout &&
>no_confirm_okay
}
diff --git a/t/t9117-git-svn-init-clone.sh b/t/t9117-git-svn-init-clone.sh
index 69a6750..044f65e 100755
--- a/t/t9117-git-svn-init-clone.sh
+++ b/t/t9117-git-svn-init-clone.sh
@@ -55,7 +55,7 @@ test_expect_success 'clone to target directory with --stdlayout' '
test_expect_success 'init without -s/-T/-b/-t does not warn' '
test ! -d trunk &&
git svn init "$svnrepo"/project/trunk trunk 2>warning &&
- test_must_fail grep -q prefix warning &&
+ ! grep -q prefix warning &&
rm -rf trunk &&
rm -f warning
'
@@ -63,7 +63,7 @@ test_expect_success 'init without -s/-T/-b/-t does not warn' '
test_expect_success 'clone without -s/-T/-b/-t does not warn' '
test ! -d trunk &&
git svn clone "$svnrepo"/project/trunk 2>warning &&
- test_must_fail grep -q prefix warning &&
+ ! grep -q prefix warning &&
rm -rf trunk &&
rm -f warning
'
@@ -86,7 +86,7 @@ EOF
test_expect_success 'init with -s/-T/-b/-t assumes --prefix=origin/' '
test ! -d project &&
git svn init -s "$svnrepo"/project project 2>warning &&
- test_must_fail grep -q prefix warning &&
+ ! grep -q prefix warning &&
test_svn_configured_prefix "origin/" &&
rm -rf project &&
rm -f warning
@@ -95,7 +95,7 @@ test_expect_success 'init with -s/-T/-b/-t assumes --prefix=origin/' '
test_expect_success 'clone with -s/-T/-b/-t assumes --prefix=origin/' '
test ! -d project &&
git svn clone -s "$svnrepo"/project 2>warning &&
- test_must_fail grep -q prefix warning &&
+ ! grep -q prefix warning &&
test_svn_configured_prefix "origin/" &&
rm -rf project &&
rm -f warning
@@ -104,7 +104,7 @@ test_expect_success 'clone with -s/-T/-b/-t assumes --prefix=origin/' '
test_expect_success 'init with -s/-T/-b/-t and --prefix "" still works' '
test ! -d project &&
git svn init -s "$svnrepo"/project project --prefix "" 2>warning &&
- test_must_fail grep -q prefix warning &&
+ ! grep -q prefix warning &&
test_svn_configured_prefix "" &&
rm -rf project &&
rm -f warning
@@ -113,7 +113,7 @@ test_expect_success 'init with -s/-T/-b/-t and --prefix "" still works' '
test_expect_success 'clone with -s/-T/-b/-t and --prefix "" still works' '
test ! -d project &&
git svn clone -s "$svnrepo"/project --prefix "" 2>warning &&
- test_must_fail grep -q prefix warning &&
+ ! grep -q prefix warning &&
test_svn_configured_prefix "" &&
rm -rf project &&
rm -f warning
diff --git a/t/t9813-git-p4-preserve-users.sh b/t/t9813-git-p4-preserve-users.sh
index 0fe2312..bda222a 100755
--- a/t/t9813-git-p4-preserve-users.sh
+++ b/t/t9813-git-p4-preserve-users.sh
@@ -118,21 +118,21 @@ test_expect_success 'not preserving user with mixed authorship' '
make_change_by_user usernamefile3 Derek derek@example.com &&
P4EDITOR=cat P4USER=alice P4PASSWD=secret &&
export P4EDITOR P4USER P4PASSWD &&
- git p4 commit |\
- grep "git author derek@example.com does not match" &&
+ git p4 commit >actual &&
+ grep "git author derek@example.com does not match" actual &&
make_change_by_user usernamefile3 Charlie charlie@example.com &&
- git p4 commit |\
- grep "git author charlie@example.com does not match" &&
+ git p4 commit >actual &&
+ grep "git author charlie@example.com does not match" actual &&
make_change_by_user usernamefile3 alice alice@example.com &&
- git p4 commit |\
- test_must_fail grep "git author.*does not match" &&
+ git p4 commit >actual &&
+ ! grep "git author.*does not match" actual &&
git config git-p4.skipUserNameCheck true &&
make_change_by_user usernamefile3 Charlie charlie@example.com &&
- git p4 commit |\
- test_must_fail grep "git author.*does not match" &&
+ git p4 commit >actual &&
+ ! grep "git author.*does not match" actual &&
p4_check_commit_author usernamefile3 alice
)
diff --git a/t/t9814-git-p4-rename.sh b/t/t9814-git-p4-rename.sh
index c89992c..e7e0268 100755
--- a/t/t9814-git-p4-rename.sh
+++ b/t/t9814-git-p4-rename.sh
@@ -141,7 +141,7 @@ test_expect_success 'detect copies' '
git diff-tree -r -C HEAD &&
git p4 submit &&
p4 filelog //depot/file8 &&
- p4 filelog //depot/file8 | test_must_fail grep -q "branch from" &&
+ ! p4 filelog //depot/file8 | grep -q "branch from" &&
echo "file9" >>file2 &&
git commit -a -m "Differentiate file2" &&
@@ -154,7 +154,7 @@ test_expect_success 'detect copies' '
git config git-p4.detectCopies true &&
git p4 submit &&
p4 filelog //depot/file9 &&
- p4 filelog //depot/file9 | test_must_fail grep -q "branch from" &&
+ ! p4 filelog //depot/file9 | grep -q "branch from" &&
echo "file10" >>file2 &&
git commit -a -m "Differentiate file2" &&
@@ -202,7 +202,7 @@ test_expect_success 'detect copies' '
git config git-p4.detectCopies $(($level + 2)) &&
git p4 submit &&
p4 filelog //depot/file12 &&
- p4 filelog //depot/file12 | test_must_fail grep -q "branch from" &&
+ ! p4 filelog //depot/file12 | grep -q "branch from" &&
echo "file13" >>file2 &&
git commit -a -m "Differentiate file2" &&
diff --git a/t/test-lib.sh b/t/test-lib.sh
index cde7fc7..86d77c1 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
diff --git a/tag.c b/tag.c
index d1dcd18..243d1fd 100644
--- a/tag.c
+++ b/tag.c
@@ -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 3e8799a..d72e089 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);
@@ -1214,7 +1221,7 @@ static int refs_from_alternate_cb(struct alternate_object_database *e,
const struct ref *extra;
struct alternate_refs_data *cb = data;
- other = xstrdup(real_path(e->path));
+ other = real_pathdup(e->path);
len = strlen(other);
while (other[len-1] == '/')
diff --git a/transport.h b/transport.h
index 9820f10..e597b31 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/tree-walk.c b/tree-walk.c
index 828f435..ff77605 100644
--- a/tree-walk.c
+++ b/tree-walk.c
@@ -1004,6 +1004,19 @@ static enum interesting do_match(const struct name_entry *entry,
*/
if (ps->recursive && S_ISDIR(entry->mode))
return entry_interesting;
+
+ /*
+ * When matching against submodules with
+ * wildcard characters, ensure that the entry
+ * at least matches up to the first wild
+ * character. More accurate matching can then
+ * be performed in the submodule itself.
+ */
+ if (ps->recursive && S_ISGITLINK(entry->mode) &&
+ !ps_strncmp(item, match + baselen,
+ entry->path,
+ item->nowildcard_len - baselen))
+ return entry_interesting;
}
continue;
@@ -1040,6 +1053,21 @@ match_wildcards:
strbuf_setlen(base, base_offset + baselen);
return entry_interesting;
}
+
+ /*
+ * When matching against submodules with
+ * wildcard characters, ensure that the entry
+ * at least matches up to the first wild
+ * character. More accurate matching can then
+ * be performed in the submodule itself.
+ */
+ if (ps->recursive && S_ISGITLINK(entry->mode) &&
+ !ps_strncmp(item, match, base->buf + base_offset,
+ item->nowildcard_len)) {
+ strbuf_setlen(base, base_offset + baselen);
+ return entry_interesting;
+ }
+
strbuf_setlen(base, base_offset + baselen);
/*
diff --git a/unpack-trees.c b/unpack-trees.c
index eee4865..3a8ee19 100644
--- a/unpack-trees.c
+++ b/unpack-trees.c
@@ -253,29 +253,42 @@ static void unlink_entry(const struct cache_entry *ce)
schedule_dir_for_removal(ce->name, ce_namelen(ce));
}
-static int check_updates(struct unpack_trees_options *o,
- const struct checkout *state)
+static struct progress *get_progress(struct unpack_trees_options *o)
{
unsigned cnt = 0, total = 0;
+ struct index_state *index = &o->result;
+
+ if (!o->update || !o->verbose_update)
+ return NULL;
+
+ for (; cnt < index->cache_nr; cnt++) {
+ const struct cache_entry *ce = index->cache[cnt];
+ if (ce->ce_flags & (CE_UPDATE | CE_WT_REMOVE))
+ total++;
+ }
+
+ return start_progress_delay(_("Checking out files"),
+ total, 50, 1);
+}
+
+static int check_updates(struct unpack_trees_options *o)
+{
+ unsigned cnt = 0;
+ int errs = 0;
struct progress *progress = NULL;
struct index_state *index = &o->result;
+ struct checkout state = CHECKOUT_INIT;
int i;
- int errs = 0;
- if (o->update && o->verbose_update) {
- for (total = cnt = 0; cnt < index->cache_nr; cnt++) {
- const struct cache_entry *ce = index->cache[cnt];
- if (ce->ce_flags & (CE_UPDATE | CE_WT_REMOVE))
- total++;
- }
+ state.force = 1;
+ state.quiet = 1;
+ state.refresh_cache = 1;
+ state.istate = index;
- progress = start_progress_delay(_("Checking out files"),
- total, 50, 1);
- cnt = 0;
- }
+ progress = get_progress(o);
if (o->update)
- git_attr_set_direction(GIT_ATTR_CHECKOUT, &o->result);
+ git_attr_set_direction(GIT_ATTR_CHECKOUT, index);
for (i = 0; i < index->cache_nr; i++) {
const struct cache_entry *ce = index->cache[i];
@@ -283,10 +296,9 @@ static int check_updates(struct unpack_trees_options *o,
display_progress(progress, ++cnt);
if (o->update && !o->dry_run)
unlink_entry(ce);
- continue;
}
}
- remove_marked_cache_entries(&o->result);
+ remove_marked_cache_entries(index);
remove_scheduled_dirs();
for (i = 0; i < index->cache_nr; i++) {
@@ -299,7 +311,7 @@ static int check_updates(struct unpack_trees_options *o,
display_progress(progress, ++cnt);
ce->ce_flags &= ~CE_UPDATE;
if (o->update && !o->dry_run) {
- errs |= checkout_entry(ce, state, NULL);
+ errs |= checkout_entry(ce, &state, NULL);
}
}
}
@@ -1129,14 +1141,9 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
int i, ret;
static struct cache_entry *dfc;
struct exclude_list el;
- struct checkout state = CHECKOUT_INIT;
if (len > MAX_UNPACK_TREES)
die("unpack_trees takes at most %d trees", MAX_UNPACK_TREES);
- state.force = 1;
- state.quiet = 1;
- state.refresh_cache = 1;
- state.istate = &o->result;
memset(&el, 0, sizeof(el));
if (!core_apply_sparse_checkout || !o->update)
@@ -1273,7 +1280,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
}
o->src_index = NULL;
- ret = check_updates(o, &state) ? (-2) : 0;
+ ret = check_updates(o) ? (-2) : 0;
if (o->dst_index) {
if (!ret) {
if (!o->result.cache_tree)
diff --git a/usage.c b/usage.c
index 17f52c1..ad6d291 100644
--- a/usage.c
+++ b/usage.c
@@ -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/versioncmp.c b/versioncmp.c
index 80bfd10..9f81dc1 100644
--- a/versioncmp.c
+++ b/versioncmp.c
@@ -24,42 +24,83 @@
static const struct string_list *prereleases;
static int initialized;
+struct suffix_match {
+ int conf_pos;
+ int start;
+ int len;
+};
+
+static void find_better_matching_suffix(const char *tagname, const char *suffix,
+ int suffix_len, int start, int conf_pos,
+ struct suffix_match *match)
+{
+ /*
+ * A better match either starts earlier or starts at the same offset
+ * but is longer.
+ */
+ int end = match->len < suffix_len ? match->start : match->start-1;
+ int i;
+ for (i = start; i <= end; i++)
+ if (starts_with(tagname + i, suffix)) {
+ match->conf_pos = conf_pos;
+ match->start = i;
+ match->len = suffix_len;
+ break;
+ }
+}
+
/*
- * p1 and p2 point to the first different character in two strings. If
- * either p1 or p2 starts with a prerelease suffix, it will be forced
- * to be on top.
- *
- * If both p1 and p2 start with (different) suffix, the order is
- * determined by config file.
+ * off is the offset of the first different character in the two strings
+ * s1 and s2. If either s1 or s2 contains a prerelease suffix containing
+ * that offset or a suffix ends right before that offset, then that
+ * string will be forced to be on top.
*
- * Note that we don't have to deal with the situation when both p1 and
- * p2 start with the same suffix because the common part is already
- * consumed by the caller.
+ * If both s1 and s2 contain a (different) suffix around that position,
+ * their order is determined by the order of those two suffixes in the
+ * configuration.
+ * If any of the strings contains more than one different suffixes around
+ * that position, then that string is sorted according to the contained
+ * suffix which starts at the earliest offset in that string.
+ * If more than one different contained suffixes start at that earliest
+ * offset, then that string is sorted according to the longest of those
+ * suffixes.
*
* Return non-zero if *diff contains the return value for versioncmp()
*/
-static int swap_prereleases(const void *p1_,
- const void *p2_,
+static int swap_prereleases(const char *s1,
+ const char *s2,
+ int off,
int *diff)
{
- const char *p1 = p1_;
- const char *p2 = p2_;
- int i, i1 = -1, i2 = -1;
+ int i;
+ struct suffix_match match1 = { -1, off, -1 };
+ struct suffix_match match2 = { -1, off, -1 };
for (i = 0; i < prereleases->nr; i++) {
const char *suffix = prereleases->items[i].string;
- if (i1 == -1 && starts_with(p1, suffix))
- i1 = i;
- if (i2 == -1 && starts_with(p2, suffix))
- i2 = i;
+ int start, suffix_len = strlen(suffix);
+ if (suffix_len < off)
+ start = off - suffix_len;
+ else
+ start = 0;
+ find_better_matching_suffix(s1, suffix, suffix_len, start,
+ i, &match1);
+ find_better_matching_suffix(s2, suffix, suffix_len, start,
+ i, &match2);
}
- if (i1 == -1 && i2 == -1)
+ if (match1.conf_pos == -1 && match2.conf_pos == -1)
return 0;
- if (i1 >= 0 && i2 >= 0)
- *diff = i1 - i2;
- else if (i1 >= 0)
+ if (match1.conf_pos == match2.conf_pos)
+ /* Found the same suffix in both, e.g. "-rc" in "v1.0-rcX"
+ * and "v1.0-rcY": the caller should decide based on "X"
+ * and "Y". */
+ return 0;
+
+ if (match1.conf_pos >= 0 && match2.conf_pos >= 0)
+ *diff = match1.conf_pos - match2.conf_pos;
+ else if (match1.conf_pos >= 0)
*diff = -1;
- else /* if (i2 >= 0) */
+ else /* if (match2.conf_pos >= 0) */
*diff = 1;
return 1;
}
@@ -118,10 +159,18 @@ int versioncmp(const char *s1, const char *s2)
}
if (!initialized) {
+ const struct string_list *deprecated_prereleases;
initialized = 1;
- prereleases = git_config_get_value_multi("versionsort.prereleasesuffix");
+ prereleases = git_config_get_value_multi("versionsort.suffix");
+ deprecated_prereleases = git_config_get_value_multi("versionsort.prereleasesuffix");
+ if (prereleases) {
+ if (deprecated_prereleases)
+ warning("ignoring versionsort.prereleasesuffix because versionsort.suffix is set");
+ } else
+ prereleases = deprecated_prereleases;
}
- if (prereleases && swap_prereleases(p1 - 1, p2 - 1, &diff))
+ if (prereleases && swap_prereleases(s1, s2, (const char *) p1 - s1 - 1,
+ &diff))
return diff;
state = result_type[state * 3 + (((c2 == '0') + (isdigit (c2) != 0)))];
diff --git a/worktree.c b/worktree.c
index 828fd7a..d633761 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++) {
@@ -255,7 +255,7 @@ struct worktree *find_worktree(struct worktree **list,
return wt;
arg = prefix_filename(prefix, strlen(prefix), arg);
- path = xstrdup(real_path(arg));
+ path = real_pathdup(arg);
for (; *list; list++)
if (!fspathcmp(path, real_path((*list)->path)))
break;
diff --git a/wt-status.c b/wt-status.c
index a715e71..4dff0b3 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 {