Merge branch 'mh/loose-refs-race-with-pack-ref'
authorJunio C Hamano <gitster@pobox.com>
Sun, 30 Jun 2013 22:39:47 +0000 (15:39 -0700)
committerJunio C Hamano <gitster@pobox.com>
Sun, 30 Jun 2013 22:39:47 +0000 (15:39 -0700)
We read loose and packed rerferences in two steps, but after
deciding to read a loose ref but before actually opening it to read
it, another process racing with us can unlink it, which would cause
us to barf. Update the codepath to retry when such a race is
detected.

* mh/loose-refs-race-with-pack-ref:
resolve_ref_unsafe(): close race condition reading loose refs
resolve_ref_unsafe(): handle the case of an SHA-1 within loop
resolve_ref_unsafe(): extract function handle_missing_loose_ref()

171 files changed:
.gitignore
Documentation/Makefile
Documentation/RelNotes/1.8.3.2.txt [new file with mode: 0644]
Documentation/RelNotes/1.8.4.txt
Documentation/config.txt
Documentation/git-config.txt
Documentation/git-diff.txt
Documentation/git-log.txt
Documentation/git-merge.txt
Documentation/git-name-rev.txt
Documentation/git-push.txt
Documentation/git-rev-parse.txt
Documentation/git-stash.txt
Documentation/git-web--browse.txt
Documentation/git.txt
Documentation/rev-list-options.txt
Documentation/technical/api-builtin.txt
Documentation/technical/api-parse-options.txt
Documentation/user-manual.txt
GIT-VERSION-GEN
Makefile
advice.c
advice.h
bisect.c
builtin.h
builtin/apply.c
builtin/checkout.c
builtin/commit.c
builtin/config.c
builtin/describe.c
builtin/diff.c
builtin/fetch.c
builtin/fsck.c
builtin/help.c
builtin/ls-files.c
builtin/merge-base.c
builtin/name-rev.c
builtin/notes.c
builtin/replace.c
builtin/rev-parse.c
builtin/rm.c
builtin/show-ref.c
bundle.c
cache.h
color.c
config.c
configure.ac
contrib/blameview/README [deleted file]
contrib/blameview/blameview.perl [deleted file]
contrib/completion/git-completion.bash
contrib/completion/git-prompt.sh
contrib/continuous/cidaemon [deleted file]
contrib/continuous/post-receive-cinotify [deleted file]
contrib/mw-to-git/.perlcriticrc [new file with mode: 0644]
contrib/mw-to-git/Makefile
contrib/mw-to-git/git-remote-mediawiki.perl
contrib/mw-to-git/t/test-gitmw-lib.sh
contrib/mw-to-git/t/test.config
contrib/patches/docbook-xsl-manpages-charmap.patch [deleted file]
decorate.c
environment.c
git-add--interactive.perl
git-am.sh
git-gui/GIT-VERSION-GEN
git-gui/Makefile
git-gui/git-gui.sh
git-gui/lib/choose_repository.tcl
git-gui/lib/diff.tcl
git-gui/lib/mergetool.tcl
git-gui/lib/remote.tcl
git-gui/po/fr.po
git-mergetool--lib.sh
git-p4.py
git-pull.sh
git-rebase.sh
git-send-email.perl
git-sh-setup.sh
git-stash.sh
git-submodule.sh
git-svn.perl
git-web--browse.sh
http-backend.c
http.c
match-trees.c
notes-merge.c
notes-merge.h
notes-utils.c [new file with mode: 0644]
notes-utils.h [new file with mode: 0644]
notes.c
object.c
object.h
read-cache.c
refs.h
revision.c
revision.h
sequencer.c
sha1_file.c
submodule.c
t/README
t/lib-httpd/apache.conf
t/lib-rebase.sh
t/perf/p0002-read-cache.sh [new file with mode: 0755]
t/t0000-basic.sh
t/t0040-parse-options.sh
t/t1004-read-tree-m-u-wf.sh
t/t1513-rev-parse-prefix.sh [new file with mode: 0755]
t/t2001-checkout-cache-clash.sh
t/t2004-checkout-cache-temp.sh
t/t2007-checkout-symlink.sh
t/t2021-checkout-overwrite.sh
t/t2200-add-update.sh
t/t3010-ls-files-killed-modified.sh
t/t3030-merge-recursive.sh
t/t3100-ls-tree-restrict.sh
t/t3400-rebase.sh
t/t3401-rebase-partial.sh [deleted file]
t/t3404-rebase-interactive.sh
t/t3406-rebase-message.sh
t/t3409-rebase-preserve-merges.sh
t/t3420-rebase-autostash.sh
t/t3421-rebase-topology-linear.sh [new file with mode: 0755]
t/t3425-rebase-topology-merges.sh [new file with mode: 0755]
t/t3509-cherry-pick-merge-df.sh
t/t3600-rm.sh
t/t3700-add.sh
t/t3903-stash.sh
t/t4008-diff-break-rewrite.sh
t/t4011-diff-symlink.sh
t/t4023-diff-rename-typechange.sh
t/t4030-diff-textconv.sh
t/t4111-apply-subdir.sh
t/t4114-apply-typechange.sh
t/t4115-apply-symlink.sh
t/t4122-apply-symlink-inside.sh
t/t4150-am.sh
t/t5150-request-pull.sh
t/t5303-pack-corruption-resilience.sh
t/t5520-pull.sh
t/t5521-pull-options.sh
t/t5702-clone-options.sh
t/t5801-remote-helpers.sh
t/t6012-rev-list-simplify.sh
t/t6019-rev-list-ancestry-path.sh
t/t6035-merge-dir-to-symlink.sh
t/t6111-rev-list-treesame.sh [new file with mode: 0755]
t/t7001-mv.sh
t/t7102-reset.sh
t/t7400-submodule-basic.sh
t/t7401-submodule-summary.sh
t/t7403-submodule-sync.sh
t/t7406-submodule-update.sh
t/t7407-submodule-foreach.sh
t/t7512-status-help.sh
t/t7600-merge.sh
t/t7607-merge-overwrite.sh
t/t8006-blame-textconv.sh
t/t8007-cat-file-textconv.sh
t/t9001-send-email.sh
t/t9350-fast-export.sh
t/t9402-git-cvsserver-refs.sh
t/t9500-gitweb-standalone-no-errors.sh
t/t9802-git-p4-filetype.sh
t/test-lib-functions.sh
test-chmtime.c
test-read-cache.c [new file with mode: 0644]
transport-helper.c
transport.c
transport.h
tree-walk.h
unpack-trees.c
wt-status.c
index 1640c3ad005adbeecf4b90f92e5d3369c6490697..c0e00eb37bb370ce9deee72468fb419bc25cfd62 100644 (file)
 /test-mktemp
 /test-parse-options
 /test-path-utils
+/test-read-cache
 /test-regex
 /test-revision-walking
 /test-run-command
index 62dbd9ac7c93e8df4359b00b7c3a59b614bd8d57..0cfdc36b44ee51d827136c2bdd75c3d977f03742 100644 (file)
@@ -31,11 +31,11 @@ MAN7_TXT += gittutorial.txt
 MAN7_TXT += gitworkflows.txt
 
 MAN_TXT = $(MAN1_TXT) $(MAN5_TXT) $(MAN7_TXT)
-MAN_XML=$(patsubst %.txt,%.xml,$(MAN_TXT))
-MAN_HTML=$(patsubst %.txt,%.html,$(MAN_TXT))
+MAN_XML = $(patsubst %.txt,%.xml,$(MAN_TXT))
+MAN_HTML = $(patsubst %.txt,%.html,$(MAN_TXT))
 
 OBSOLETE_HTML = git-remote-helpers.html
-DOC_HTML=$(MAN_HTML) $(OBSOLETE_HTML)
+DOC_HTML = $(MAN_HTML) $(OBSOLETE_HTML)
 
 ARTICLES = howto-index
 ARTICLES += everyday
@@ -74,35 +74,35 @@ SP_ARTICLES += technical/api-index
 
 DOC_HTML += $(patsubst %,%.html,$(ARTICLES) $(SP_ARTICLES))
 
-DOC_MAN1=$(patsubst %.txt,%.1,$(MAN1_TXT))
-DOC_MAN5=$(patsubst %.txt,%.5,$(MAN5_TXT))
-DOC_MAN7=$(patsubst %.txt,%.7,$(MAN7_TXT))
-
-prefix?=$(HOME)
-bindir?=$(prefix)/bin
-htmldir?=$(prefix)/share/doc/git-doc
-pdfdir?=$(prefix)/share/doc/git-doc
-mandir?=$(prefix)/share/man
-man1dir=$(mandir)/man1
-man5dir=$(mandir)/man5
-man7dir=$(mandir)/man7
-# DESTDIR=
+DOC_MAN1 = $(patsubst %.txt,%.1,$(MAN1_TXT))
+DOC_MAN5 = $(patsubst %.txt,%.5,$(MAN5_TXT))
+DOC_MAN7 = $(patsubst %.txt,%.7,$(MAN7_TXT))
+
+prefix ?= $(HOME)
+bindir ?= $(prefix)/bin
+htmldir ?= $(prefix)/share/doc/git-doc
+infodir ?= $(prefix)/share/info
+pdfdir ?= $(prefix)/share/doc/git-doc
+mandir ?= $(prefix)/share/man
+man1dir = $(mandir)/man1
+man5dir = $(mandir)/man5
+man7dir = $(mandir)/man7
+# DESTDIR =
 
 ASCIIDOC = asciidoc
 ASCIIDOC_EXTRA =
 MANPAGE_XSL = manpage-normal.xsl
 XMLTO = xmlto
 XMLTO_EXTRA =
-INSTALL?=install
+INSTALL ?= install
 RM ?= rm -f
 MAN_REPO = ../../git-manpages
 HTML_REPO = ../../git-htmldocs
 
-infodir?=$(prefix)/share/info
-MAKEINFO=makeinfo
-INSTALL_INFO=install-info
-DOCBOOK2X_TEXI=docbook2x-texi
-DBLATEX=dblatex
+MAKEINFO = makeinfo
+INSTALL_INFO = install-info
+DOCBOOK2X_TEXI = docbook2x-texi
+DBLATEX = dblatex
 ifndef PERL_PATH
        PERL_PATH = /usr/bin/perl
 endif
diff --git a/Documentation/RelNotes/1.8.3.2.txt b/Documentation/RelNotes/1.8.3.2.txt
new file mode 100644 (file)
index 0000000..26ae142
--- /dev/null
@@ -0,0 +1,59 @@
+Git v1.8.3.2 Release Notes
+==========================
+
+Fixes since v1.8.3.1
+--------------------
+
+ * Cloning with "git clone --depth N" while fetch.fsckobjects (or
+   transfer.fsckobjects) is set to true did not tell the cut-off
+   points of the shallow history to the process that validates the
+   objects and the history received, causing the validation to fail.
+
+ * "git checkout foo" DWIMs the intended "upstream" and turns it into
+   "git checkout -t -b foo remotes/origin/foo". This codepath has been
+   updated to correctly take existing remote definitions into account.
+
+ * "git fetch" into a shallow repository from a repository that does
+   not know about the shallow boundary commits (e.g. a different fork
+   from the repository the current shallow repository was cloned from)
+   did not work correctly.
+
+ * "git subtree" (in contrib/) had one codepath with loose error
+   checks to lose data at the remote side.
+
+ * "git log --ancestry-path A...B" did not work as expected, as it did
+   not pay attention to the fact that the merge base between A and B
+   was the bottom of the range being specified.
+
+ * "git diff -c -p" was not showing a deleted line from a hunk when
+   another hunk immediately begins where the earlier one ends.
+
+ * "git merge @{-1}~22" was rewritten to "git merge frotz@{1}~22"
+   incorrectly when your previous branch was "frotz" (it should be
+   rewritten to "git merge frotz~22" instead).
+
+ * "git commit --allow-empty-message -m ''" should not start an
+   editor.
+
+ * "git push --[no-]verify" was not documented.
+
+ * An entry for "file://" scheme in the enumeration of URL types Git
+   can take in the HTML documentation was made into a clickable link
+   by mistake.
+
+ * zsh prompt script that borrowed from bash prompt script did not
+   work due to slight differences in array variable notation between
+   these two shells.
+
+ * The bash prompt code (in contrib/) displayed the name of the branch
+   being rebased when "rebase -i/-m/-p" modes are in use, but not the
+   plain vanilla "rebase".
+
+ * "git push $there HEAD:branch" did not resolve HEAD early enough, so
+   it was easy to flip it around while push is still going on and push
+   out a branch that the user did not originally intended when the
+   command was started.
+
+ * "difftool --dir-diff" did not copy back changes made by the
+   end-user in the diff tool backend to the working tree in some
+   cases.
index b974d492dc49f3c047f94ed681811c7a102e6886..1854d1723de1e042819e8b248590d9bdcf95c2be 100644 (file)
@@ -4,7 +4,12 @@ Git v1.8.4 Release Notes
 Updates since v1.8.3
 --------------------
 
-Foreign interfaces and ports.
+Foreign interfaces, subsystems and ports.
+
+ * Git-gui has been updated to its 0.18.0 version.
+
+ * MediaWiki remote helper (in contrib/) has been updated to use the
+   credential helper interface from Git.pm.
 
  * Update build for Cygwin 1.[57].  Torsten Bögershausen reports that
    this is fine with Cygwin 1.7 ($gmane/225824) so let's try moving it
@@ -30,6 +35,23 @@ Foreign interfaces and ports.
 
 UI, Workflows & Features
 
+ * "git rm" gives a single message followed by list of paths to report
+   multiple paths that cannot be removed.
+
+ * "git rebase" can be told with ":/look for this string" syntax commits
+   to replay the changes onto and where the work to be replayed begins.
+
+ * Many tutorials teach users to set "color.ui" to "auto" as the first
+   thing after you set "user.name/email" to introduce yourselves to
+   Git.  Now the variable defaults to "auto".
+
+ * On Cygwin, "cygstart" is now recognised as a possible way to start
+   a web browser (used in "help -w" and "instaweb" among others).
+
+### * "git status" learned status.branch and status.short configuration
+###   variables to use --branch and --short options by default (override
+###   with --no-branch and --no-short options from the command line).
+
  * "git cmd <name>", when <name> happens to be a 40-hex string,
    directly uses the 40-hex string as an object name, even if a ref
    "refs/<some hierarchy>/<name>" exists.  This disambiguation order
@@ -39,7 +61,8 @@ UI, Workflows & Features
 
  * "git rebase" learned "--[no-]autostash" option to save local
    changes instead of refusing to run (to which people's normal
-   response was to stash them and re-run).
+   response was to stash them and re-run).  This introduced a corner
+   case breakage to "git am --abort" but it has been fixed.
 
  * Instead of typing four capital letters "HEAD", you can say "@" now,
    e.g. "git log @".
@@ -85,6 +108,18 @@ UI, Workflows & Features
 
 Performance, Internal Implementation, etc.
 
+ * Uses of the platform fnmatch(3) function (many places in the code,
+   matching pathspec, .gitignore and .gitattributes to name a few)
+   have been replaced with wildmatch, allowing "foo/**/bar" that would
+   match foo/bar, foo/a/bar, foo/a/b/bar, etc.
+
+ * Memory ownership and lifetime rules for what for-each-ref feeds to
+   its callbacks have been clarified (in short, "you do not own it, so
+   make a copy if you want to keep it").
+
+ * The revision traversal logic to improve culling of irrelevant
+   parents while traversing a mergy history has been updated.
+
  * Some leaks in unpack-trees (used in merge, cherry-pick and other
    codepaths) have been plugged.
 
@@ -122,6 +157,40 @@ Unless otherwise noted, all the fixes since v1.8.3 in the maintenance
 track are contained in this release (see release notes to them for
 details).
 
+ * "git apply" parsed patches that add new files, generated by
+   programs other than Git, incorrectly.  This is an old breakage in
+   v1.7.11 and will need to be merged down to the maintanance tracks.
+   (merge 212eb96 tr/maint-apply-non-git-patch-parsefix later to maint).
+
+ * Older cURL wanted piece of memory we call it with to be stable, but
+   we updated the auth material after handing it to a call.
+   (merge a94cf2c bc/http-keep-memory-given-to-curl later to maint).
+
+ * "git pull" into nothing trashed "local changes" that were in the
+   index, and this avoids it.
+   (merge b4dc085 jk/pull-into-dirty-unborn later to maint).
+
+ * Many "git submodule" operations do not work on a submodule at a
+   path whose name is not in ASCII.
+   (merge bed9470 fg/submodule-non-ascii-path later to maint).
+
+ * "cherry-pick" had a small leak in an error codepath.
+   (merge 706728a fc/sequencer-plug-leak later to maint).
+
+ * Logic used by git-send-email to suppress cc mishandled names like
+   "A U. Thor" <author@example.xz>, where the human readable part
+   needs to be quoted (the user input may not have the double quotes
+   around the name, and comparison was done between quoted and
+   unquoted strings).  It also mishandled names that need RFC2047
+   quoting.
+   (merge 1495266 mt/send-email-cc-match-fix later to maint).
+
+ * Call to discard_cache/discard_index (used when we use different
+   contents of the index in-core, in many operations like commit,
+   apply, and merge) used to leak memory that held the array of index
+   entries, which has been plugged.
+   (merge a0fc4db rs/discard-index-discard-array later to maint).
+
  * "gitweb" forgot to clear a global variable $search_regexp upon each
    request, mistakenly carrying over the previous search to a new one
    when used as a persistent CGI.
index 7fd4035cb52dfb746fad496fd27347f538c31006..1153585aa2e2d19dd41f8cf81465e8b6d858a028 100644 (file)
@@ -199,6 +199,9 @@ advice.*::
        amWorkDir::
                Advice that shows the location of the patch file when
                linkgit:git-am[1] fails to apply it.
+       rmHints::
+               In case of failure in the output of linkgit:git-rm[1],
+               show directions on how to proceed from the current state.
 --
 
 core.fileMode::
@@ -919,11 +922,12 @@ color.ui::
        as `color.diff` and `color.grep` that control the use of color
        per command family. Its scope will expand as more commands learn
        configuration to set a default for the `--color` option.  Set it
-       to `always` if you want all output not intended for machine
-       consumption to use color, to `true` or `auto` if you want such
-       output to use color when written to the terminal, or to `false` or
-       `never` if you prefer Git commands not to use color unless enabled
-       explicitly with some other configuration or the `--color` option.
+       to `false` or `never` if you prefer Git commands not to use
+       color unless enabled explicitly with some other configuration
+       or the `--color` option. Set it to `always` if you want all
+       output not intended for machine consumption to use color, to
+       `true` or `auto` (this is the default since Git 1.8.4) if you
+       want such output to use color when written to the terminal.
 
 column.ui::
        Specify whether supported commands should output in columns.
index d88a6fcb29395ac200930191518fdbceca78bbd4..19a7be085693f7f9386fd3856fa38ebeed364f99 100644 (file)
@@ -114,6 +114,15 @@ rather than from all available files.
 +
 See also <<FILES>>.
 
+--local::
+       For writing options: write to the repository .git/config file.
+       This is the default behavior.
++
+For reading options: read only from the repository .git/config rather than
+from all available files.
++
+See also <<FILES>>.
+
 -f config-file::
 --file config-file::
        Use the given config file instead of the one specified by GIT_CONFIG.
index a7b46208f62c81b63ef4b8fdc5cf3ac67bd8b541..78d6d50489502638013509fbf72c811b50bfee86 100644 (file)
@@ -18,8 +18,8 @@ SYNOPSIS
 DESCRIPTION
 -----------
 Show changes between the working tree and the index or a tree, changes
-between the index and a tree, changes between two trees, or changes
-between two files on disk.
+between the index and a tree, changes between two trees, changes between
+two blob objects, or changes between two files on disk.
 
 'git diff' [--options] [--] [<path>...]::
 
@@ -56,11 +56,6 @@ directories. This behavior can be forced by --no-index.
        This is to view the changes between two arbitrary
        <commit>.
 
-'git diff' [options] <blob> <blob>::
-
-       This form is to view the differences between the raw
-       contents of two blob objects.
-
 'git diff' [--options] <commit>..<commit> [--] [<path>...]::
 
        This is synonymous to the previous form.  If <commit> on
@@ -87,6 +82,11 @@ and the range notations ("<commit>..<commit>" and
 "<commit>\...<commit>") do not mean a range as defined in the
 "SPECIFYING RANGES" section in linkgit:gitrevisions[7].
 
+'git diff' [options] <blob> <blob>::
+
+       This form is to view the differences between the raw
+       contents of two blob objects.
+
 OPTIONS
 -------
 :git-diff: 1
index 4687fe8192594b358f7058dcdec985ab30c2a3fb..2ea79ba16819929b53b5c27f242ae0997a7603a4 100644 (file)
@@ -128,9 +128,9 @@ Examples
        in the "release" branch, along with the list of paths
        each commit modifies.
 
-`git log --follow builtin-rev-list.c`::
+`git log --follow builtin/rev-list.c`::
 
-       Shows the commits that changed builtin-rev-list.c, including
+       Shows the commits that changed builtin/rev-list.c, including
        those commits that occurred before the file was given its
        present name.
 
index 67ca99cd92e371ae7518d4574e2a8507d78e0823..8c7f2f66d86e5d41113d6158b496068cc6b72bce 100644 (file)
@@ -56,8 +56,8 @@ especially if those changes were further modified after the merge
 was started), 'git merge --abort' will in some cases be unable to
 reconstruct the original (pre-merge) changes. Therefore:
 
-*Warning*: Running 'git merge' with uncommitted changes is
-discouraged: while possible, it leaves you in a state that is hard to
+*Warning*: Running 'git merge' with non-trivial uncommitted changes is
+discouraged: while possible, it may leave you in a state that is hard to
 back out of in the case of a conflict.
 
 
index ad1d1468c9936d206701d379003177e7457c884e..6b0f1ba75fc98dedf7b940355274ad25d3b458c5 100644 (file)
@@ -25,7 +25,8 @@ OPTIONS
        Do not use branch names, but only tags to name the commits
 
 --refs=<pattern>::
-       Only use refs whose names match a given shell pattern.
+       Only use refs whose names match a given shell pattern.  The pattern
+       can be one of branch name, tag name or fully qualified ref name.
 
 --all::
        List all commits reachable from all refs
index df5be268ba69f34fe5e91c3148f8af8c1e0091d6..f7dfe48d2813324e1a699efa530afe8974d7c400 100644 (file)
@@ -136,6 +136,15 @@ already exists on the remote side.
        not an ancestor of the local ref used to overwrite it.
        This flag disables the check.  This can cause the
        remote repository to lose commits; use it with care.
+       Note that `--force` applies to all the refs that are pushed,
+       hence using it with `push.default` set to `matching` or with
+       multiple push destinations configured with `remote.*.push`
+       may overwrite refs other than the current branch (including
+       local refs that are strictly behind their remote counterpart).
+       To force a push to only one branch, use a `+` in front of the
+       refspec to push (e.g `git push origin +master` to force a push
+       to the `master` branch). See the `<refspec>...` section above
+       for details.
 
 --repo=<repository>::
        This option is only relevant if no <repository> argument is
index 947d62fd25e4b326af44a6f6933e98d9cc3e7153..993903c9f123831fdfb25c8e22e213fd6c6a7c96 100644 (file)
@@ -59,6 +59,22 @@ OPTIONS
        If there is no parameter given by the user, use `<arg>`
        instead.
 
+--prefix <arg>::
+       Behave as if 'git rev-parse' was invoked from the `<arg>`
+       subdirectory of the working tree.  Any relative filenames are
+       resolved as if they are prefixed by `<arg>` and will be printed
+       in that form.
++
+This can be used to convert arguments to a command run in a subdirectory
+so that they can still be used after moving to the top-level of the
+repository.  For example:
++
+----
+prefix=$(git rev-parse --show-prefix)
+cd "$(git rev-parse --show-toplevel)"
+eval "set -- $(git rev-parse --sq --prefix "$prefix" "$@")"
+----
+
 --verify::
        Verify that exactly one parameter is provided, and that it
        can be turned into a raw 20-byte SHA-1 that can be used to
index 711ffe17a7574511de6c20cb85b6cdd5a4fff5a5..db7e803038090510609a6534ab7d086245c15270 100644 (file)
@@ -13,10 +13,11 @@ SYNOPSIS
 'git stash' drop [-q|--quiet] [<stash>]
 'git stash' ( pop | apply ) [--index] [-q|--quiet] [<stash>]
 'git stash' branch <branchname> [<stash>]
-'git stash' [save [--patch] [-k|--[no-]keep-index] [-q|--quiet]
+'git stash' [save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]
             [-u|--include-untracked] [-a|--all] [<message>]]
 'git stash' clear
-'git stash' create
+'git stash' create [<message>]
+'git stash' store [-m|--message <message>] [-q|--quiet] <commit>
 
 DESCRIPTION
 -----------
@@ -151,7 +152,15 @@ create::
 
        Create a stash (which is a regular commit object) and return its
        object name, without storing it anywhere in the ref namespace.
+       This is intended to be useful for scripts.  It is probably not
+       the command you want to use; see "save" above.
 
+store::
+
+       Store a given stash created via 'git stash create' (which is a
+       dangling merge commit) in the stash ref, updating the stash
+       reflog.  This is intended to be useful for scripts.  It is
+       probably not the command you want to use; see "save" above.
 
 DISCUSSION
 ----------
index ba79cb4f35a1f525c98c0bc6b95acf4c46ddc1dc..5aec4ecffb8c9dd58c444319d24064c8fee1770e 100644 (file)
@@ -34,6 +34,7 @@ The following browsers (or commands) are currently supported:
 * dillo
 * open (this is the default under Mac OS X GUI)
 * start (this is the default under MinGW)
+* cygstart (this is the default under Cygwin)
 
 Custom commands may also be specified.
 
index 2e23cbb224854c3090ecd73cb36e437978d0d063..b738a40e6b0fe740910aa4a919c46ce74f303a69 100644 (file)
@@ -43,9 +43,10 @@ unreleased) version of Git, that is available from 'master'
 branch of the `git.git` repository.
 Documentation for older releases are available here:
 
-* link:v1.8.3.1/git.html[documentation for release 1.8.3.1]
+* link:v1.8.3.2/git.html[documentation for release 1.8.3.2]
 
 * release notes for
+  link:RelNotes/1.8.3.2.txt[1.8.3.2],
   link:RelNotes/1.8.3.1.txt[1.8.3.1],
   link:RelNotes/1.8.3.txt[1.8.3].
 
@@ -837,6 +838,19 @@ for further details.
        as a file path and will try to write the trace messages
        into it.
 
+'GIT_TRACE_PACK_ACCESS'::
+       If this variable is set to a path, a file will be created at
+       the given path logging all accesses to any packs. For each
+       access, the pack file name and an offset in the pack is
+       recorded. This may be helpful for troubleshooting some
+       pack-related performance problems.
+
+'GIT_TRACE_PACKET'::
+       If this variable is set, it shows a trace of all packets
+       coming in or out of a given program. This can help with
+       debugging object negotiation or other protocol issues. Tracing
+       is turned off at a packet starting with "PACK".
+
 GIT_LITERAL_PATHSPECS::
        Setting this variable to `1` will cause Git to treat all
        pathspecs literally, rather than as glob patterns. For example,
index 3bdbf5e856b68692745a91304d80b09186d487b4..b462f17f62d4c0066b1178b405b011963643cdd9 100644 (file)
@@ -271,8 +271,8 @@ See also linkgit:git-reflog[1].
 
 --boundary::
 
-       Output uninteresting commits at the boundary, which are usually
-       not shown.
+       Output excluded boundary commits. Boundary commits are
+       prefixed with `-`.
 
 --
 
@@ -342,13 +342,13 @@ In the following, we will always refer to the same example history to
 illustrate the differences between simplification settings.  We assume
 that you are filtering for a file `foo` in this commit graph:
 -----------------------------------------------------------------------
-         .-A---M---N---O---P
-        /     /   /   /   /
-       I     B   C   D   E
-        \   /   /   /   /
-         `-------------'
+         .-A---M---N---O---P---Q
+        /     /   /   /   /   /
+       I     B   C   D   E   Y
+        \   /   /   /   /   /
+         `-------------'   X
 -----------------------------------------------------------------------
-The horizontal line of history A---P is taken to be the first parent of
+The horizontal line of history A---Q is taken to be the first parent of
 each merge.  The commits are:
 
 * `I` is the initial commit, in which `foo` exists with contents
@@ -367,8 +367,11 @@ each merge.  The commits are:
   `N` and `D` to "foobarbaz"; i.e., it is not TREESAME to any parent.
 
 * `E` changes `quux` to "xyzzy", and its merge `P` combines the
-  strings to "quux xyzzy".  Despite appearing interesting, `P` is
-  TREESAME to all parents.
+  strings to "quux xyzzy".  `P` is TREESAME to `O`, but not to `E`.
+
+* `X` is an indpendent root commit that added a new file `side`, and `Y`
+  modified it. `Y` is TREESAME to `X`. Its merge `Q` added `side` to `P`, and
+  `Q` is TREESAME to `P`, but not to `Y`.
 
 'rev-list' walks backwards through history, including or excluding
 commits based on whether '\--full-history' and/or parent rewriting
@@ -410,10 +413,10 @@ parent lines.
        the example, we get
 +
 -----------------------------------------------------------------------
-       I  A  B  N  D  O
+       I  A  B  N  D  O  P  Q
 -----------------------------------------------------------------------
 +
-`P` and `M` were excluded because they are TREESAME to a parent.  `E`,
+`M` was excluded because it is TREESAME to both parents.  `E`,
 `C` and `B` were all walked, but only `B` was !TREESAME, so the others
 do not appear.
 +
@@ -431,7 +434,7 @@ Along each parent, prune away commits that are not included
 themselves.  This results in
 +
 -----------------------------------------------------------------------
-         .-A---M---N---O---P
+         .-A---M---N---O---P---Q
         /     /   /   /   /
        I     B   /   D   /
         \   /   /   /   /
@@ -441,7 +444,7 @@ themselves.  This results in
 Compare to '\--full-history' without rewriting above.  Note that `E`
 was pruned away because it is TREESAME, but the parent list of P was
 rewritten to contain `E`'s parent `I`.  The same happened for `C` and
-`N`.  Note also that `P` was included despite being TREESAME.
+`N`, and `X`, `Y` and `Q`.
 
 In addition to the above settings, you can change whether TREESAME
 affects inclusion:
@@ -471,8 +474,9 @@ history according to the following rules:
 * Set `C'` to `C`.
 +
 * Replace each parent `P` of `C'` with its simplification `P'`.  In
-  the process, drop parents that are ancestors of other parents, and
-  remove duplicates.
+  the process, drop parents that are ancestors of other parents or that are
+  root commits TREESAME to an empty tree, and remove duplicates, but take care
+  to never drop all parents that we are TREESAME to.
 +
 * If after this parent rewriting, `C'` is a root or merge commit (has
   zero or >1 parents), a boundary commit, or !TREESAME, it remains.
@@ -490,7 +494,7 @@ The effect of this is best shown by way of comparing to
          `---------'
 -----------------------------------------------------------------------
 +
-Note the major differences in `N` and `P` over '--full-history':
+Note the major differences in `N`, `P` and `Q` over '--full-history':
 +
 --
 * `N`'s parent list had `I` removed, because it is an ancestor of the
@@ -498,6 +502,10 @@ Note the major differences in `N` and `P` over '--full-history':
 +
 * `P`'s parent list similarly had `I` removed.  `P` was then
   removed completely, because it had one parent and is TREESAME.
++
+* `Q`'s parent list had `Y` simplified to `X`. `X` was then removed, because it
+  was a TREESAME root. `Q` was then removed completely, because it had one
+  parent and is TREESAME.
 --
 
 Finally, there is a fifth simplification mode available:
index 4a4228b896df1c06b565f330b6d7eb9d2cd9b10c..f3c1357b7cc037681b5d0f28974b199e326f8c2d 100644 (file)
@@ -39,7 +39,7 @@ where options is the bitwise-or of:
        on bare repositories.
        This only makes sense when `RUN_SETUP` is also set.
 
-. Add `builtin-foo.o` to `BUILTIN_OBJS` in `Makefile`.
+. Add `builtin/foo.o` to `BUILTIN_OBJS` in `Makefile`.
 
 Additionally, if `foo` is a new command, there are 3 more things to do:
 
index 1317db4d6ca798c0ffa5150a7373c37b5599ecd9..0be2b5159f1b349f0c263eb9e8ba696e2a57ebc0 100644 (file)
@@ -275,10 +275,10 @@ Examples
 --------
 
 See `test-parse-options.c` and
-`builtin-add.c`,
-`builtin-clone.c`,
-`builtin-commit.c`,
-`builtin-fetch.c`,
-`builtin-fsck.c`,
-`builtin-rm.c`
+`builtin/add.c`,
+`builtin/clone.c`,
+`builtin/commit.c`,
+`builtin/fetch.c`,
+`builtin/fsck.c`,
+`builtin/rm.c`
 for real-world examples.
index e831cc2020b2b728d972c9e33f7fef8041381547..644acce079df81ecd7c2cf8774b3e7fcba9c49d5 100644 (file)
@@ -57,10 +57,10 @@ download a copy of an existing repository.  If you don't already have a
 project in mind, here are some interesting examples:
 
 ------------------------------------------------
-       # Git itself (approx. 10MB download):
+       # Git itself (approx. 40MB download):
 $ git clone git://git.kernel.org/pub/scm/git/git.git
-       # the Linux kernel (approx. 150MB download):
-$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git
+       # the Linux kernel (approx. 640MB download):
+$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
 ------------------------------------------------
 
 The initial clone may be time-consuming for a large project, but you
@@ -4256,15 +4256,16 @@ no longer need to call `setup_pager()` directly).
 Nowadays, `git log` is a builtin, which means that it is _contained_ in the
 command `git`.  The source side of a builtin is
 
-- a function called `cmd_<bla>`, typically defined in `builtin-<bla>.c`,
-  and declared in `builtin.h`,
+- a function called `cmd_<bla>`, typically defined in `builtin/<bla.c>`
+  (note that older versions of Git used to have it in `builtin-<bla>.c`
+  instead), and declared in `builtin.h`.
 
 - an entry in the `commands[]` array in `git.c`, and
 
 - an entry in `BUILTIN_OBJECTS` in the `Makefile`.
 
 Sometimes, more than one builtin is contained in one source file.  For
-example, `cmd_whatchanged()` and `cmd_log()` both reside in `builtin-log.c`,
+example, `cmd_whatchanged()` and `cmd_log()` both reside in `builtin/log.c`,
 since they share quite a bit of code.  In that case, the commands which are
 _not_ named like the `.c` file in which they live have to be listed in
 `BUILT_INS` in the `Makefile`.
@@ -4287,10 +4288,10 @@ For the sake of clarity, let's stay with `git cat-file`, because it
 - is plumbing, and
 
 - was around even in the initial commit (it literally went only through
-  some 20 revisions as `cat-file.c`, was renamed to `builtin-cat-file.c`
+  some 20 revisions as `cat-file.c`, was renamed to `builtin/cat-file.c`
   when made a builtin, and then saw less than 10 versions).
 
-So, look into `builtin-cat-file.c`, search for `cmd_cat_file()` and look what
+So, look into `builtin/cat-file.c`, search for `cmd_cat_file()` and look what
 it does.
 
 ------------------------------------------------------------------
@@ -4366,7 +4367,7 @@ Another example: Find out what to do in order to make some script a
 builtin:
 
 -------------------------------------------------
-$ git log --no-merges --diff-filter=A builtin-*.c
+$ git log --no-merges --diff-filter=A builtin/*.c
 -------------------------------------------------
 
 You see, Git is actually the best tool to find out about the source of Git
index 390782fa124114e902d1d7ba254bb14323f943a1..b4d4e5045fc78173c77de6de3fb9330de49a9628 100755 (executable)
@@ -11,7 +11,7 @@ LF='
 if test -f version
 then
        VN=$(cat version) || VN="$DEF_VER"
-elif test -d .git -o -f .git &&
+elif test -d ${GIT_DIR:-.git} -o -f .git &&
        VN=$(git describe --match "v[0-9]*" --abbrev=7 HEAD 2>/dev/null) &&
        case "$VN" in
        *$LF*) (exit 1) ;;
index 03524d022c3bea71a71d736214c07a8667d625d8..e1583761dfa58c3ede93b2076e4aa0963f2aa379 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -109,7 +109,7 @@ all::
 # Define NO_FNMATCH_CASEFOLD if your fnmatch function doesn't have the
 # FNM_CASEFOLD GNU extension.
 #
-# Define USE_WILDMATCH if you want to use Git's wildmatch
+# Define NO_WILDMATCH if you do not want to use Git's wildmatch
 # implementation as fnmatch
 #
 # Define NO_GECOS_IN_PWENT if you don't have pw_gecos in struct passwd
@@ -491,6 +491,7 @@ SCRIPT_PERL += git-svn.perl
 SCRIPT_PYTHON += git-remote-testpy.py
 SCRIPT_PYTHON += git-p4.py
 
+NO_INSTALL += git-remote-testgit
 NO_INSTALL += git-remote-testpy
 
 # Generated files for scripts
@@ -568,6 +569,7 @@ TEST_PROGRAMS_NEED_X += test-mergesort
 TEST_PROGRAMS_NEED_X += test-mktemp
 TEST_PROGRAMS_NEED_X += test-parse-options
 TEST_PROGRAMS_NEED_X += test-path-utils
+TEST_PROGRAMS_NEED_X += test-read-cache
 TEST_PROGRAMS_NEED_X += test-regex
 TEST_PROGRAMS_NEED_X += test-revision-walking
 TEST_PROGRAMS_NEED_X += test-run-command
@@ -694,6 +696,7 @@ LIB_H += merge-recursive.h
 LIB_H += mergesort.h
 LIB_H += notes-cache.h
 LIB_H += notes-merge.h
+LIB_H += notes-utils.h
 LIB_H += notes.h
 LIB_H += object.h
 LIB_H += pack-revindex.h
@@ -828,6 +831,7 @@ LIB_OBJS += name-hash.o
 LIB_OBJS += notes.o
 LIB_OBJS += notes-cache.o
 LIB_OBJS += notes-merge.o
+LIB_OBJS += notes-utils.o
 LIB_OBJS += object.o
 LIB_OBJS += pack-check.o
 LIB_OBJS += pack-revindex.o
@@ -1278,7 +1282,7 @@ ifdef NO_FNMATCH_CASEFOLD
        COMPAT_OBJS += compat/fnmatch/fnmatch.o
 endif
 endif
-ifdef USE_WILDMATCH
+ifndef NO_WILDMATCH
        COMPAT_CFLAGS += -DUSE_WILDMATCH
 endif
 ifdef NO_SETENV
@@ -2067,13 +2071,13 @@ $(REMOTE_CURL_PRIMARY): remote-curl.o http.o http-walker.o GIT-LDFLAGS $(GITLIBS
                $(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT)
 
 $(LIB_FILE): $(LIB_OBJS)
-       $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $(LIB_OBJS)
+       $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $^
 
 $(XDIFF_LIB): $(XDIFF_OBJS)
-       $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $(XDIFF_OBJS)
+       $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $^
 
 $(VCSSVN_LIB): $(VCSSVN_OBJS)
-       $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $(VCSSVN_OBJS)
+       $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $^
 
 export DEFAULT_EDITOR DEFAULT_PAGER
 
@@ -2233,6 +2237,7 @@ endif
 test_bindir_programs := $(patsubst %,bin-wrappers/%,$(BINDIR_PROGRAMS_NEED_X) $(BINDIR_PROGRAMS_NO_X) $(TEST_PROGRAMS_NEED_X))
 
 all:: $(TEST_PROGRAMS) $(test_bindir_programs)
+all:: $(NO_INSTALL)
 
 bin-wrappers/%: wrap-for-bin.sh
        @mkdir -p bin-wrappers
@@ -2482,7 +2487,7 @@ clean: profile-clean coverage-clean
        $(RM) *.o *.res block-sha1/*.o ppc/*.o compat/*.o compat/*/*.o xdiff/*.o vcs-svn/*.o \
                builtin/*.o $(LIB_FILE) $(XDIFF_LIB) $(VCSSVN_LIB)
        $(RM) $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) git$X
-       $(RM) $(TEST_PROGRAMS)
+       $(RM) $(TEST_PROGRAMS) $(NO_INSTALL)
        $(RM) -r bin-wrappers $(dep_dirs)
        $(RM) -r po/build/
        $(RM) *.spec *.pyc *.pyo */*.pyc */*.pyo common-cmds.h $(ETAGS_TARGET) tags cscope*
index 54315cbd0a554bdeb1ace160a5df7cf6c485e559..2a52098a29dd1c2192edb6fff8ca6979383e77c6 100644 (file)
--- a/advice.c
+++ b/advice.c
@@ -15,6 +15,7 @@ int advice_implicit_identity = 1;
 int advice_detached_head = 1;
 int advice_set_upstream_failure = 1;
 int advice_object_name_warning = 1;
+int advice_rm_hints = 1;
 
 static struct {
        const char *name;
@@ -35,6 +36,7 @@ static struct {
        { "detachedhead", &advice_detached_head },
        { "setupstreamfailure", &advice_set_upstream_failure },
        { "object_name_warning", &advice_object_name_warning },
+       { "rmhints", &advice_rm_hints },
 
        /* make this an alias for backward compatibility */
        { "pushnonfastforward", &advice_push_update_rejected }
index fefe39ac5caf5a1e8b95683a4c8dcc443df16d32..93a7d110ea94b055476644fd5ecbec43bb0aaf92 100644 (file)
--- a/advice.h
+++ b/advice.h
@@ -18,6 +18,7 @@ extern int advice_implicit_identity;
 extern int advice_detached_head;
 extern int advice_set_upstream_failure;
 extern int advice_object_name_warning;
+extern int advice_rm_hints;
 
 int git_default_advice_config(const char *var, const char *value);
 void advise(const char *advice, ...);
index 374d9e24bd0a18b0453f3e948db93251a859ba18..71c19581daccffc87e5128c61d9adfae0be3e7de 100644 (file)
--- a/bisect.c
+++ b/bisect.c
@@ -15,7 +15,7 @@
 static struct sha1_array good_revs;
 static struct sha1_array skipped_revs;
 
-static const unsigned char *current_bad_sha1;
+static unsigned char *current_bad_sha1;
 
 static const char *argv_checkout[] = {"checkout", "-q", NULL, "--", NULL};
 static const char *argv_show_branch[] = {"show-branch", NULL, NULL};
@@ -404,7 +404,8 @@ static int register_ref(const char *refname, const unsigned char *sha1,
                        int flags, void *cb_data)
 {
        if (!strcmp(refname, "bad")) {
-               current_bad_sha1 = sha1;
+               current_bad_sha1 = xmalloc(20);
+               hashcpy(current_bad_sha1, sha1);
        } else if (!prefixcmp(refname, "good-")) {
                sha1_array_append(&good_revs, sha1);
        } else if (!prefixcmp(refname, "skip-")) {
index 64bab6bf54df4ae9e6e3695abb7bd5fd36ddfea6..1ed8edb0cbd83ef1819c0d56defe97c10ade9c0d 100644 (file)
--- a/builtin.h
+++ b/builtin.h
@@ -5,7 +5,6 @@
 #include "strbuf.h"
 #include "cache.h"
 #include "commit.h"
-#include "notes.h"
 
 #define DEFAULT_MERGE_LOG_LEN 20
 
@@ -26,21 +25,6 @@ struct fmt_merge_msg_opts {
 extern int fmt_merge_msg(struct strbuf *in, struct strbuf *out,
                         struct fmt_merge_msg_opts *);
 
-struct notes_rewrite_cfg {
-       struct notes_tree **trees;
-       const char *cmd;
-       int enabled;
-       combine_notes_fn combine;
-       struct string_list *refs;
-       int refs_from_env;
-       int mode_from_env;
-};
-
-struct notes_rewrite_cfg *init_copy_notes_for_rewrite(const char *cmd);
-int copy_note_for_rewrite(struct notes_rewrite_cfg *c,
-                         const unsigned char *from_obj, const unsigned char *to_obj);
-void finish_copy_notes_for_rewrite(struct notes_rewrite_cfg *c);
-
 extern int textconv_object(const char *path, unsigned mode, const unsigned char *sha1, int sha1_valid, char **buf, unsigned long *buf_size);
 
 extern int cmd_add(int argc, const char **argv, const char *prefix);
index 30eefc3c7b390800e9452a06da18208d2fef4f11..0e9b631db6db422b1e938d6168b608e0a2ae8618 100644 (file)
@@ -906,7 +906,7 @@ static void parse_traditional_patch(const char *first, const char *second, struc
                        patch->old_name = name;
                } else {
                        patch->old_name = name;
-                       patch->new_name = xstrdup(name);
+                       patch->new_name = null_strdup(name);
                }
        }
        if (!name)
@@ -3525,7 +3525,7 @@ static int check_patch(struct patch *patch)
                ok_if_exists = 0;
 
        if (new_name &&
-           ((0 < patch->is_new) | (0 < patch->is_rename) | patch->is_copy)) {
+           ((0 < patch->is_new) || patch->is_rename || patch->is_copy)) {
                int err = check_to_create(new_name, ok_if_exists);
 
                if (err && threeway) {
index f5b50e520feb42c50cd1783871eb63d009e0a0e0..3be00188216f503ea1f9269acd312aebdb72a0fa 100644 (file)
@@ -838,13 +838,16 @@ static int check_tracking_name(struct remote *remote, void *cb_data)
        memset(&query, 0, sizeof(struct refspec));
        query.src = cb->src_ref;
        if (remote_find_tracking(remote, &query) ||
-           get_sha1(query.dst, cb->dst_sha1))
+           get_sha1(query.dst, cb->dst_sha1)) {
+               free(query.dst);
                return 0;
+       }
        if (cb->dst_ref) {
+               free(query.dst);
                cb->unique = 0;
                return 0;
        }
-       cb->dst_ref = xstrdup(query.dst);
+       cb->dst_ref = query.dst;
        return 0;
 }
 
index 1621dfcd4008fc5853fc078a32bf9eefb6c52023..6b693c16d8842b70b0e1c7bd7df5095947ed0a64 100644 (file)
@@ -29,6 +29,7 @@
 #include "gpg-interface.h"
 #include "column.h"
 #include "sequencer.h"
+#include "notes-utils.h"
 
 static const char * const builtin_commit_usage[] = {
        N_("git commit [options] [--] <pathspec>..."),
@@ -1593,7 +1594,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
                if (cfg) {
                        /* we are amending, so current_head is not NULL */
                        copy_note_for_rewrite(cfg, current_head->object.sha1, sha1);
-                       finish_copy_notes_for_rewrite(cfg);
+                       finish_copy_notes_for_rewrite(cfg, "Notes added by 'git commit --amend'");
                }
                run_rewrite_hook(current_head->object.sha1, sha1);
        }
index 19ffcaf18776e8b100ee3f57c7ca7447436c5476..7759671eb8d44ad341831eeffc96c4469d0652e8 100644 (file)
@@ -329,6 +329,7 @@ static int get_colorbool(int print)
 {
        get_colorbool_found = -1;
        get_diff_color_found = -1;
+       get_color_ui_found = -1;
        git_config_with_options(git_get_colorbool_config, NULL,
                                given_config_file, respect_includes);
 
@@ -339,6 +340,10 @@ static int get_colorbool(int print)
                        get_colorbool_found = get_color_ui_found;
        }
 
+       if (get_colorbool_found < 0)
+               /* default value if none found in config */
+               get_colorbool_found = GIT_COLOR_AUTO;
+
        get_colorbool_found = want_color(get_colorbool_found);
 
        if (print) {
index ad8471626ac4c6f4f4eda413114665fb4fcca018..4e675c3d0d7b1daeb7f12ef425514a11a92a5c3f 100644 (file)
@@ -43,7 +43,7 @@ struct commit_name {
        unsigned prio:2; /* annotated tag = 2, tag = 1, head = 0 */
        unsigned name_checked:1;
        unsigned char sha1[20];
-       const char *path;
+       char *path;
 };
 static const char *prio_names[] = {
        "head", "lightweight", "annotated",
@@ -127,12 +127,14 @@ static void add_to_known_names(const char *path,
                        } else {
                                e->next = NULL;
                        }
+                       e->path = NULL;
                }
                e->tag = tag;
                e->prio = prio;
                e->name_checked = 0;
                hashcpy(e->sha1, sha1);
-               e->path = path;
+               free(e->path);
+               e->path = xstrdup(path);
        }
 }
 
index 8c2af6cb43ca62f253e07903a8e3cce1080e335b..9fc273d8cd78d53a55047e17d44eb89291741167 100644 (file)
@@ -153,7 +153,8 @@ static int builtin_diff_index(struct rev_info *revs,
 
 static int builtin_diff_tree(struct rev_info *revs,
                             int argc, const char **argv,
-                            struct object_array_entry *ent)
+                            struct object_array_entry *ent0,
+                            struct object_array_entry *ent1)
 {
        const unsigned char *(sha1[2]);
        int swap = 0;
@@ -161,13 +162,14 @@ static int builtin_diff_tree(struct rev_info *revs,
        if (argc > 1)
                usage(builtin_diff_usage);
 
-       /* We saw two trees, ent[0] and ent[1].
-        * if ent[1] is uninteresting, they are swapped
+       /*
+        * We saw two trees, ent0 and ent1.  If ent1 is uninteresting,
+        * swap them.
         */
-       if (ent[1].item->flags & UNINTERESTING)
+       if (ent1->item->flags & UNINTERESTING)
                swap = 1;
-       sha1[swap] = ent[0].item->sha1;
-       sha1[1-swap] = ent[1].item->sha1;
+       sha1[swap] = ent0->item->sha1;
+       sha1[1-swap] = ent1->item->sha1;
        diff_tree_sha1(sha1[0], sha1[1], "", &revs->diffopt);
        log_tree_diff_flush(revs);
        return 0;
@@ -251,8 +253,8 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
 {
        int i;
        struct rev_info rev;
-       struct object_array_entry ent[100];
-       int ents = 0, blobs = 0, paths = 0;
+       struct object_array ent = OBJECT_ARRAY_INIT;
+       int blobs = 0, paths = 0;
        const char *path = NULL;
        struct blobinfo blob[2];
        int nongit;
@@ -337,9 +339,9 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
        }
 
        for (i = 0; i < rev.pending.nr; i++) {
-               struct object_array_entry *list = rev.pending.objects+i;
-               struct object *obj = list->item;
-               const char *name = list->name;
+               struct object_array_entry *entry = &rev.pending.objects[i];
+               struct object *obj = entry->item;
+               const char *name = entry->name;
                int flags = (obj->flags & UNINTERESTING);
                if (!obj->parsed)
                        obj = parse_object(obj->sha1);
@@ -348,27 +350,21 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
                        die(_("invalid object '%s' given."), name);
                if (obj->type == OBJ_COMMIT)
                        obj = &((struct commit *)obj)->tree->object;
+
                if (obj->type == OBJ_TREE) {
-                       if (ARRAY_SIZE(ent) <= ents)
-                               die(_("more than %d trees given: '%s'"),
-                                   (int) ARRAY_SIZE(ent), name);
                        obj->flags |= flags;
-                       ent[ents].item = obj;
-                       ent[ents].name = name;
-                       ents++;
-                       continue;
-               }
-               if (obj->type == OBJ_BLOB) {
+                       add_object_array(obj, name, &ent);
+               } else if (obj->type == OBJ_BLOB) {
                        if (2 <= blobs)
                                die(_("more than two blobs given: '%s'"), name);
                        hashcpy(blob[blobs].sha1, obj->sha1);
                        blob[blobs].name = name;
-                       blob[blobs].mode = list->mode;
+                       blob[blobs].mode = entry->mode;
                        blobs++;
-                       continue;
 
+               } else {
+                       die(_("unhandled object '%s' given."), name);
                }
-               die(_("unhandled object '%s' given."), name);
        }
        if (rev.prune_data.nr) {
                if (!path)
@@ -379,7 +375,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
        /*
         * Now, do the arguments look reasonable?
         */
-       if (!ents) {
+       if (!ent.nr) {
                switch (blobs) {
                case 0:
                        result = builtin_diff_files(&rev, argc, argv);
@@ -400,23 +396,26 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
        }
        else if (blobs)
                usage(builtin_diff_usage);
-       else if (ents == 1)
+       else if (ent.nr == 1)
                result = builtin_diff_index(&rev, argc, argv);
-       else if (ents == 2)
-               result = builtin_diff_tree(&rev, argc, argv, ent);
-       else if (ent[0].item->flags & UNINTERESTING) {
+       else if (ent.nr == 2)
+               result = builtin_diff_tree(&rev, argc, argv,
+                                          &ent.objects[0], &ent.objects[1]);
+       else if (ent.objects[0].item->flags & UNINTERESTING) {
                /*
                 * diff A...B where there is at least one merge base
-                * between A and B.  We have ent[0] == merge-base,
-                * ent[ents-2] == A, and ent[ents-1] == B.  Show diff
-                * between the base and B.  Note that we pick one
-                * merge base at random if there are more than one.
+                * between A and B.  We have ent.objects[0] ==
+                * merge-base, ent.objects[ents-2] == A, and
+                * ent.objects[ents-1] == B.  Show diff between the
+                * base and B.  Note that we pick one merge base at
+                * random if there are more than one.
                 */
-               ent[1] = ent[ents-1];
-               result = builtin_diff_tree(&rev, argc, argv, ent);
+               result = builtin_diff_tree(&rev, argc, argv,
+                                          &ent.objects[0],
+                                          &ent.objects[ent.nr-1]);
        } else
                result = builtin_diff_combined(&rev, argc, argv,
-                                              ent, ents);
+                                              ent.objects, ent.nr);
        result = diff_result_code(&rev.diffopt, result);
        if (1 < rev.diffopt.skip_stat_unmatch)
                refresh_index_quietly();
index d15a7343d8384d05784aa5739dc758262f750a25..d784b2e6947f1b5ace0016d92258e29377e9d1a7 100644 (file)
@@ -600,7 +600,8 @@ static int add_existing(const char *refname, const unsigned char *sha1,
 {
        struct string_list *list = (struct string_list *)cbdata;
        struct string_list_item *item = string_list_insert(list, refname);
-       item->util = (void *)sha1;
+       item->util = xmalloc(20);
+       hashcpy(item->util, sha1);
        return 0;
 }
 
@@ -619,7 +620,7 @@ static void find_non_local_tags(struct transport *transport,
                        struct ref **head,
                        struct ref ***tail)
 {
-       struct string_list existing_refs = STRING_LIST_INIT_NODUP;
+       struct string_list existing_refs = STRING_LIST_INIT_DUP;
        struct string_list remote_refs = STRING_LIST_INIT_NODUP;
        const struct ref *ref;
        struct string_list_item *item = NULL;
@@ -665,7 +666,7 @@ static void find_non_local_tags(struct transport *transport,
                item = string_list_insert(&remote_refs, ref->name);
                item->util = (void *)ref->old_sha1;
        }
-       string_list_clear(&existing_refs, 0);
+       string_list_clear(&existing_refs, 1);
 
        /*
         * We may have a final lightweight tag that needs to be
@@ -722,11 +723,11 @@ static int truncate_fetch_head(void)
 static int do_fetch(struct transport *transport,
                    struct refspec *refs, int ref_count)
 {
-       struct string_list existing_refs = STRING_LIST_INIT_NODUP;
-       struct string_list_item *peer_item = NULL;
+       struct string_list existing_refs = STRING_LIST_INIT_DUP;
        struct ref *ref_map;
        struct ref *rm;
        int autotags = (transport->remote->fetch_tags == 1);
+       int retcode = 0;
 
        for_each_ref(add_existing, &existing_refs);
 
@@ -742,9 +743,9 @@ static int do_fetch(struct transport *transport,
 
        /* if not appending, truncate FETCH_HEAD */
        if (!append && !dry_run) {
-               int errcode = truncate_fetch_head();
-               if (errcode)
-                       return errcode;
+               retcode = truncate_fetch_head();
+               if (retcode)
+                       goto cleanup;
        }
 
        ref_map = get_ref_map(transport, refs, ref_count, tags, &autotags);
@@ -753,8 +754,9 @@ static int do_fetch(struct transport *transport,
 
        for (rm = ref_map; rm; rm = rm->next) {
                if (rm->peer_ref) {
-                       peer_item = string_list_lookup(&existing_refs,
-                                                      rm->peer_ref->name);
+                       struct string_list_item *peer_item =
+                               string_list_lookup(&existing_refs,
+                                                  rm->peer_ref->name);
                        if (peer_item)
                                hashcpy(rm->peer_ref->old_sha1,
                                        peer_item->util);
@@ -765,7 +767,8 @@ static int do_fetch(struct transport *transport,
                transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, "1");
        if (fetch_refs(transport, ref_map)) {
                free_refs(ref_map);
-               return 1;
+               retcode = 1;
+               goto cleanup;
        }
        if (prune) {
                /* If --tags was specified, pretend the user gave us the canonical tags refspec */
@@ -808,7 +811,9 @@ static int do_fetch(struct transport *transport,
                free_refs(ref_map);
        }
 
-       return 0;
+ cleanup:
+       string_list_clear(&existing_refs, 1);
+       return retcode;
 }
 
 static void set_option(const char *name, const char *value)
index bb9a2cd44722dc27d54aa5451278a10f512becb8..9909b6d519f0a7d3d3b53313e7886c9482d98ba5 100644 (file)
@@ -112,7 +112,7 @@ static int mark_object(struct object *obj, int type, void *data)
                return 1;
        }
 
-       add_object_array(obj, (void *) parent, &pending);
+       add_object_array(obj, NULL, &pending);
        return 0;
 }
 
index 062957f629af09db81d78b9cb0be1ce60fd75ec4..f1e236b9124f8b7bfd9aa5e926d525c6f4568268 100644 (file)
@@ -1,6 +1,4 @@
 /*
- * builtin-help.c
- *
  * Builtin help command
  */
 #include "cache.h"
index 22020729cbc35dc5fe11d2a3194fe5994fe67ff9..87f3b331ca68715f074b2a5396dda77eea3c3154 100644 (file)
@@ -219,7 +219,7 @@ static void show_files(struct dir_struct *dir)
                if (show_killed)
                        show_killed_files(dir);
        }
-       if (show_cached | show_stage) {
+       if (show_cached || show_stage) {
                for (i = 0; i < active_nr; i++) {
                        struct cache_entry *ce = active_cache[i];
                        if ((dir->flags & DIR_SHOW_IGNORED) &&
@@ -233,7 +233,7 @@ static void show_files(struct dir_struct *dir)
                                (ce_skip_worktree(ce) ? tag_skip_worktree : tag_cached), ce);
                }
        }
-       if (show_deleted | show_modified) {
+       if (show_deleted || show_modified) {
                for (i = 0; i < active_nr; i++) {
                        struct cache_entry *ce = active_cache[i];
                        struct stat st;
@@ -571,8 +571,8 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
                die("ls-files --ignored needs some exclude pattern");
 
        /* With no flags, we default to showing the cached files */
-       if (!(show_stage | show_deleted | show_others | show_unmerged |
-             show_killed | show_modified | show_resolve_undo))
+       if (!(show_stage || show_deleted || show_others || show_unmerged ||
+             show_killed || show_modified || show_resolve_undo))
                show_cached = 1;
 
        if (max_prefix)
index 1bc79910481e6ee04b470620da5609c1c6fbcac4..0c4cd2f9f792cdf47cecf3401eb2bf7c676f7f6e 100644 (file)
@@ -107,7 +107,7 @@ int cmd_merge_base(int argc, const char **argv, const char *prefix)
        argc = parse_options(argc, argv, prefix, options, merge_base_usage, 0);
        if (!octopus && !reduce && argc < 2)
                usage_with_options(merge_base_usage, options);
-       if (is_ancestor && (show_all | octopus | reduce))
+       if (is_ancestor && (show_all || octopus || reduce))
                die("--is-ancestor cannot be used with other options");
        if (is_ancestor)
                return handle_is_ancestor(argc, argv);
index 62382479749909d449b718fc2f3bef36906caa4b..87d485496fd5556cbd0f24793ed7e48f11e9b292 100644 (file)
@@ -82,6 +82,20 @@ static void name_rev(struct commit *commit,
        }
 }
 
+static int subpath_matches(const char *path, const char *filter)
+{
+       const char *subpath = path;
+
+       while (subpath) {
+               if (!fnmatch(filter, subpath, 0))
+                       return subpath - path;
+               subpath = strchr(subpath, '/');
+               if (subpath)
+                       subpath++;
+       }
+       return -1;
+}
+
 struct name_ref_data {
        int tags_only;
        int name_only;
@@ -92,13 +106,23 @@ static int name_ref(const char *path, const unsigned char *sha1, int flags, void
 {
        struct object *o = parse_object(sha1);
        struct name_ref_data *data = cb_data;
+       int can_abbreviate_output = data->tags_only && data->name_only;
        int deref = 0;
 
        if (data->tags_only && prefixcmp(path, "refs/tags/"))
                return 0;
 
-       if (data->ref_filter && fnmatch(data->ref_filter, path, 0))
-               return 0;
+       if (data->ref_filter) {
+               switch (subpath_matches(path, data->ref_filter)) {
+               case -1: /* did not match */
+                       return 0;
+               case 0:  /* matched fully */
+                       break;
+               default: /* matched subpath */
+                       can_abbreviate_output = 1;
+                       break;
+               }
+       }
 
        while (o && o->type == OBJ_TAG) {
                struct tag *t = (struct tag *) o;
@@ -110,12 +134,10 @@ static int name_ref(const char *path, const unsigned char *sha1, int flags, void
        if (o && o->type == OBJ_COMMIT) {
                struct commit *commit = (struct commit *)o;
 
-               if (!prefixcmp(path, "refs/heads/"))
+               if (can_abbreviate_output)
+                       path = shorten_unambiguous_ref(path, 0);
+               else if (!prefixcmp(path, "refs/heads/"))
                        path = path + 11;
-               else if (data->tags_only
-                   && data->name_only
-                   && !prefixcmp(path, "refs/tags/"))
-                       path = path + 10;
                else if (!prefixcmp(path, "refs/"))
                        path = path + 5;
 
index 57748a6fb67820c15ee6a8a92b32d4a0392dd933..e4100c49820306b881a20db4bb3af7c13f12c019 100644 (file)
@@ -4,7 +4,7 @@
  * Copyright (c) 2010 Johan Herland <johan@herland.net>
  *
  * Based on git-notes.sh by Johannes Schindelin,
- * and builtin-tag.c by Kristian Høgsberg and Carlos Rica.
+ * and builtin/tag.c by Kristian Høgsberg and Carlos Rica.
  */
 
 #include "cache.h"
@@ -18,9 +18,7 @@
 #include "parse-options.h"
 #include "string-list.h"
 #include "notes-merge.h"
-
-static void commit_notes(struct notes_tree *t, const char *msg);
-static combine_notes_fn parse_combine_notes_fn(const char *v);
+#include "notes-utils.h"
 
 static const char * const git_notes_usage[] = {
        N_("git notes [--ref <notes_ref>] [list [<object>]]"),
@@ -287,139 +285,13 @@ static int parse_reedit_arg(const struct option *opt, const char *arg, int unset
        return parse_reuse_arg(opt, arg, unset);
 }
 
-static void commit_notes(struct notes_tree *t, const char *msg)
-{
-       struct strbuf buf = STRBUF_INIT;
-       unsigned char commit_sha1[20];
-
-       if (!t)
-               t = &default_notes_tree;
-       if (!t->initialized || !t->ref || !*t->ref)
-               die(_("Cannot commit uninitialized/unreferenced notes tree"));
-       if (!t->dirty)
-               return; /* don't have to commit an unchanged tree */
-
-       /* Prepare commit message and reflog message */
-       strbuf_addstr(&buf, msg);
-       if (buf.buf[buf.len - 1] != '\n')
-               strbuf_addch(&buf, '\n'); /* Make sure msg ends with newline */
-
-       create_notes_commit(t, NULL, &buf, commit_sha1);
-       strbuf_insert(&buf, 0, "notes: ", 7); /* commit message starts at index 7 */
-       update_ref(buf.buf, t->ref, commit_sha1, NULL, 0, DIE_ON_ERR);
-
-       strbuf_release(&buf);
-}
-
-static combine_notes_fn parse_combine_notes_fn(const char *v)
-{
-       if (!strcasecmp(v, "overwrite"))
-               return combine_notes_overwrite;
-       else if (!strcasecmp(v, "ignore"))
-               return combine_notes_ignore;
-       else if (!strcasecmp(v, "concatenate"))
-               return combine_notes_concatenate;
-       else if (!strcasecmp(v, "cat_sort_uniq"))
-               return combine_notes_cat_sort_uniq;
-       else
-               return NULL;
-}
-
-static int notes_rewrite_config(const char *k, const char *v, void *cb)
-{
-       struct notes_rewrite_cfg *c = cb;
-       if (!prefixcmp(k, "notes.rewrite.") && !strcmp(k+14, c->cmd)) {
-               c->enabled = git_config_bool(k, v);
-               return 0;
-       } else if (!c->mode_from_env && !strcmp(k, "notes.rewritemode")) {
-               if (!v)
-                       config_error_nonbool(k);
-               c->combine = parse_combine_notes_fn(v);
-               if (!c->combine) {
-                       error(_("Bad notes.rewriteMode value: '%s'"), v);
-                       return 1;
-               }
-               return 0;
-       } else if (!c->refs_from_env && !strcmp(k, "notes.rewriteref")) {
-               /* note that a refs/ prefix is implied in the
-                * underlying for_each_glob_ref */
-               if (!prefixcmp(v, "refs/notes/"))
-                       string_list_add_refs_by_glob(c->refs, v);
-               else
-                       warning(_("Refusing to rewrite notes in %s"
-                               " (outside of refs/notes/)"), v);
-               return 0;
-       }
-
-       return 0;
-}
-
-
-struct notes_rewrite_cfg *init_copy_notes_for_rewrite(const char *cmd)
-{
-       struct notes_rewrite_cfg *c = xmalloc(sizeof(struct notes_rewrite_cfg));
-       const char *rewrite_mode_env = getenv(GIT_NOTES_REWRITE_MODE_ENVIRONMENT);
-       const char *rewrite_refs_env = getenv(GIT_NOTES_REWRITE_REF_ENVIRONMENT);
-       c->cmd = cmd;
-       c->enabled = 1;
-       c->combine = combine_notes_concatenate;
-       c->refs = xcalloc(1, sizeof(struct string_list));
-       c->refs->strdup_strings = 1;
-       c->refs_from_env = 0;
-       c->mode_from_env = 0;
-       if (rewrite_mode_env) {
-               c->mode_from_env = 1;
-               c->combine = parse_combine_notes_fn(rewrite_mode_env);
-               if (!c->combine)
-                       /* TRANSLATORS: The first %s is the name of the
-                          environment variable, the second %s is its value */
-                       error(_("Bad %s value: '%s'"), GIT_NOTES_REWRITE_MODE_ENVIRONMENT,
-                                       rewrite_mode_env);
-       }
-       if (rewrite_refs_env) {
-               c->refs_from_env = 1;
-               string_list_add_refs_from_colon_sep(c->refs, rewrite_refs_env);
-       }
-       git_config(notes_rewrite_config, c);
-       if (!c->enabled || !c->refs->nr) {
-               string_list_clear(c->refs, 0);
-               free(c->refs);
-               free(c);
-               return NULL;
-       }
-       c->trees = load_notes_trees(c->refs);
-       string_list_clear(c->refs, 0);
-       free(c->refs);
-       return c;
-}
-
-int copy_note_for_rewrite(struct notes_rewrite_cfg *c,
-                         const unsigned char *from_obj, const unsigned char *to_obj)
-{
-       int ret = 0;
-       int i;
-       for (i = 0; c->trees[i]; i++)
-               ret = copy_note(c->trees[i], from_obj, to_obj, 1, c->combine) || ret;
-       return ret;
-}
-
-void finish_copy_notes_for_rewrite(struct notes_rewrite_cfg *c)
-{
-       int i;
-       for (i = 0; c->trees[i]; i++) {
-               commit_notes(c->trees[i], "Notes added by 'git notes copy'");
-               free_notes(c->trees[i]);
-       }
-       free(c->trees);
-       free(c);
-}
-
 static int notes_copy_from_stdin(int force, const char *rewrite_cmd)
 {
        struct strbuf buf = STRBUF_INIT;
        struct notes_rewrite_cfg *c = NULL;
        struct notes_tree *t = NULL;
        int ret = 0;
+       const char *msg = "Notes added by 'git notes copy'";
 
        if (rewrite_cmd) {
                c = init_copy_notes_for_rewrite(rewrite_cmd);
@@ -461,10 +333,10 @@ static int notes_copy_from_stdin(int force, const char *rewrite_cmd)
        }
 
        if (!rewrite_cmd) {
-               commit_notes(t, "Notes added by 'git notes copy'");
+               commit_notes(t, msg);
                free_notes(t);
        } else {
-               finish_copy_notes_for_rewrite(c);
+               finish_copy_notes_for_rewrite(c, msg);
        }
        return ret;
 }
index 398ccd5eaa243045c5d21af1e37462846665568a..59d31152d0068dd4a542d2c8e076c629d508500f 100644 (file)
@@ -3,7 +3,7 @@
  *
  * Copyright (c) 2008 Christian Couder <chriscool@tuxfamily.org>
  *
- * Based on builtin-tag.c by Kristian Høgsberg <krh@redhat.com>
+ * Based on builtin/tag.c by Kristian Høgsberg <krh@redhat.com>
  * and Carlos Rica <jasampler@gmail.com> that was itself based on
  * git-tag.sh and mktag.c by Linus Torvalds.
  */
index f267a1d3b57c1f1ab44c83a197f46051f4f13060..de894c75773c78afb2c0f1ea4ea5aff8ec7de6bc 100644 (file)
@@ -212,11 +212,17 @@ static void show_datestring(const char *flag, const char *datestr)
        show(buffer);
 }
 
-static int show_file(const char *arg)
+static int show_file(const char *arg, int output_prefix)
 {
        show_default();
        if ((filter & (DO_NONFLAGS|DO_NOREV)) == (DO_NONFLAGS|DO_NOREV)) {
-               show(arg);
+               if (output_prefix) {
+                       const char *prefix = startup_info->prefix;
+                       show(prefix_filename(prefix,
+                                            prefix ? strlen(prefix) : 0,
+                                            arg));
+               } else
+                       show(arg);
                return 1;
        }
        return 0;
@@ -470,6 +476,7 @@ N_("git rev-parse --parseopt [options] -- [<args>...]\n"
 int cmd_rev_parse(int argc, const char **argv, const char *prefix)
 {
        int i, as_is = 0, verify = 0, quiet = 0, revs_count = 0, type = 0;
+       int output_prefix = 0;
        unsigned char sha1[20];
        const char *name = NULL;
 
@@ -503,7 +510,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
                const char *arg = argv[i];
 
                if (as_is) {
-                       if (show_file(arg) && as_is < 2)
+                       if (show_file(arg, output_prefix) && as_is < 2)
                                verify_filename(prefix, arg, 0);
                        continue;
                }
@@ -527,7 +534,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
                                as_is = 2;
                                /* Pass on the "--" if we show anything but files.. */
                                if (filter & (DO_FLAGS | DO_REVS))
-                                       show_file(arg);
+                                       show_file(arg, 0);
                                continue;
                        }
                        if (!strcmp(arg, "--default")) {
@@ -535,6 +542,13 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
                                i++;
                                continue;
                        }
+                       if (!strcmp(arg, "--prefix")) {
+                               prefix = argv[i+1];
+                               startup_info->prefix = prefix;
+                               output_prefix = 1;
+                               i++;
+                               continue;
+                       }
                        if (!strcmp(arg, "--revs-only")) {
                                filter &= ~DO_NOREV;
                                continue;
@@ -754,7 +768,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
                if (verify)
                        die_no_single_rev(quiet);
                as_is = 1;
-               if (!show_file(arg))
+               if (!show_file(arg, output_prefix))
                        continue;
                verify_filename(prefix, arg, 1);
        }
index 7b91d52f39004dbfcc9b0dc5b8aadf3df5c6d320..06025a2e751fbc2b2de3b024919c381cd2176c51 100644 (file)
@@ -9,6 +9,7 @@
 #include "cache-tree.h"
 #include "tree-walk.h"
 #include "parse-options.h"
+#include "string-list.h"
 #include "submodule.h"
 
 static const char * const builtin_rm_usage[] = {
@@ -36,10 +37,32 @@ static int get_ours_cache_pos(const char *path, int pos)
        return -1;
 }
 
+static void print_error_files(struct string_list *files_list,
+                             const char *main_msg,
+                             const char *hints_msg,
+                             int *errs)
+{
+       if (files_list->nr) {
+               int i;
+               struct strbuf err_msg = STRBUF_INIT;
+
+               strbuf_addstr(&err_msg, main_msg);
+               for (i = 0; i < files_list->nr; i++)
+                       strbuf_addf(&err_msg,
+                                   "\n    %s",
+                                   files_list->items[i].string);
+               if (advice_rm_hints)
+                       strbuf_addstr(&err_msg, hints_msg);
+               *errs = error("%s", err_msg.buf);
+               strbuf_release(&err_msg);
+       }
+}
+
 static int check_submodules_use_gitfiles(void)
 {
        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;
@@ -61,11 +84,18 @@ static int check_submodules_use_gitfiles(void)
                        continue;
 
                if (!submodule_uses_gitfile(name))
-                       errs = error(_("submodule '%s' (or one of its nested "
-                                    "submodules) uses a .git directory\n"
-                                    "(use 'rm -rf' if you really want to remove "
-                                    "it including all of its history)"), name);
+                       string_list_append(&files, name);
        }
+       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 its 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);
 
        return errs;
 }
@@ -81,6 +111,10 @@ static int check_local_mod(unsigned char *head, int index_only)
         */
        int i, no_head;
        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_sha1(head);
        for (i = 0; i < list.nr; i++) {
@@ -171,29 +205,58 @@ static int check_local_mod(unsigned char *head, int index_only)
                 */
                if (local_changes && staged_changes) {
                        if (!index_only || !(ce->ce_flags & CE_INTENT_TO_ADD))
-                               errs = error(_("'%s' has staged content different "
-                                            "from both the file and the HEAD\n"
-                                            "(use -f to force removal)"), name);
+                               string_list_append(&files_staged, name);
                }
                else if (!index_only) {
                        if (staged_changes)
-                               errs = error(_("'%s' has changes staged in the index\n"
-                                            "(use --cached to keep the file, "
-                                            "or -f to force removal)"), name);
+                               string_list_append(&files_cached, name);
                        if (local_changes) {
                                if (S_ISGITLINK(ce->ce_mode) &&
-                                   !submodule_uses_gitfile(name)) {
-                                       errs = error(_("submodule '%s' (or one of its nested "
-                                                    "submodules) uses a .git directory\n"
-                                                    "(use 'rm -rf' if you really want to remove "
-                                                    "it including all of its history)"), name);
-                               } else
-                                       errs = error(_("'%s' has local modifications\n"
-                                                    "(use --cached to keep the file, "
-                                                    "or -f to force removal)"), name);
+                                   !submodule_uses_gitfile(name))
+                                       string_list_append(&files_submodule, name);
+                               else
+                                       string_list_append(&files_local, name);
                        }
                }
        }
+       print_error_files(&files_staged,
+                         Q_("the following file has staged content different "
+                            "from both the\nfile and the HEAD:",
+                            "the following files have staged content different"
+                            " from both the\nfile and the HEAD:",
+                            files_staged.nr),
+                         _("\n(use -f to force removal)"),
+                         &errs);
+       string_list_clear(&files_staged, 0);
+       print_error_files(&files_cached,
+                         Q_("the following file has changes "
+                            "staged in the index:",
+                            "the following files have changes "
+                            "staged in the index:", files_cached.nr),
+                         _("\n(use --cached to keep the file,"
+                           " or -f to force removal)"),
+                         &errs);
+       string_list_clear(&files_cached, 0);
+       print_error_files(&files_submodule,
+                         Q_("the following submodule (or one of its nested "
+                            "submodule)\nuses a .git directory:",
+                            "the following submodules (or one of its nested "
+                            "submodule)\nuse a .git directory:",
+                            files_submodule.nr),
+                         _("\n(use 'rm -rf' if you really "
+                           "want to remove it including all "
+                           "of its history)"),
+                         &errs);
+       string_list_clear(&files_submodule, 0);
+       print_error_files(&files_local,
+                         Q_("the following file has local modifications:",
+                            "the following files have local modifications:",
+                            files_local.nr),
+                         _("\n(use --cached to keep the file,"
+                           " or -f to force removal)"),
+                         &errs);
+       string_list_clear(&files_local, 0);
+
        return errs;
 }
 
index 8d9b76a02fd115c0285216c27c5232f345d0582d..4a0310da371cfc851bf1d9b763a927764be68476 100644 (file)
@@ -103,7 +103,7 @@ static int add_existing(const char *refname, const unsigned char *sha1, int flag
  */
 static int exclude_existing(const char *match)
 {
-       static struct string_list existing_refs = STRING_LIST_INIT_NODUP;
+       static struct string_list existing_refs = STRING_LIST_INIT_DUP;
        char buf[1024];
        int matchlen = match ? strlen(match) : 0;
 
index 4b0e5cd51b82f916c26d3776f81f22cdb3045fa6..3d6431137336f9748247dfad625555d0a546b26b 100644 (file)
--- a/bundle.c
+++ b/bundle.c
@@ -281,7 +281,7 @@ int create_bundle(struct bundle_header *header, const char *path,
                        if (!get_sha1_hex(buf.buf + 1, sha1)) {
                                struct object *object = parse_object_or_die(sha1, buf.buf);
                                object->flags |= UNINTERESTING;
-                               add_pending_object(&revs, object, xstrdup(buf.buf));
+                               add_pending_object(&revs, object, buf.buf);
                        }
                } else if (!get_sha1_hex(buf.buf, sha1)) {
                        struct object *object = parse_object_or_die(sha1, buf.buf);
diff --git a/cache.h b/cache.h
index 820aa05c7a6f42aa3a3599c48f560a1074132596..ec8240f62a77cf718436d3de62f8ab7f66d4a322 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -774,9 +774,6 @@ extern int parse_sha1_header(const char *hdr, unsigned long *sizep);
 /* global flag to enable extra checks when accessing packed objects */
 extern int do_check_packed_object_crc;
 
-/* for development: log offset of pack access */
-extern const char *log_pack_access;
-
 extern int check_sha1_signature(const unsigned char *sha1, void *buf, unsigned long size, const char *type);
 
 extern int move_temp_to_file(const char *tmpfile, const char *filename);
diff --git a/color.c b/color.c
index e8e26818b3b1f2ffce1374e2edf88b40c575c3dd..f672885b71acef4108c8cefc9cf887c220f614d3 100644 (file)
--- a/color.c
+++ b/color.c
@@ -1,7 +1,7 @@
 #include "cache.h"
 #include "color.h"
 
-static int git_use_color_default = 0;
+static int git_use_color_default = GIT_COLOR_AUTO;
 int color_stdout_is_tty = -1;
 
 /*
index 7a85ebdbae79cf13305ebc821fafcd21a67c16d4..d04e8157abc415c6956bb8a237c8a17bd9624f8c 100644 (file)
--- a/config.c
+++ b/config.c
@@ -688,9 +688,6 @@ static int git_default_core_config(const char *var, const char *value)
                return 0;
        }
 
-       if (!strcmp(var, "core.logpackaccess"))
-               return git_config_string(&log_pack_access, var, value);
-
        if (!strcmp(var, "core.autocrlf")) {
                if (value && !strcasecmp(value, "input")) {
                        if (core_eol == EOL_CRLF)
index f3462d9c81f12ea07229128f6bef7a4ff3fcfe10..2f433939dc9d9001c7ace83bfeadf567eb90c30c 100644 (file)
@@ -193,7 +193,7 @@ AC_ARG_ENABLE([pthreads],
   [FLAGS is the value to pass to the compiler to enable POSIX Threads.]
   [The default if FLAGS is not specified is to try first -pthread]
   [and then -lpthread.]
-  [--without-pthreads will disable threading.])],
+  [--disable-pthreads will disable threading.])],
 [
 if test "x$enableval" = "xyes"; then
    AC_MSG_NOTICE([Will try -pthread then -lpthread to enable POSIX Threads])
diff --git a/contrib/blameview/README b/contrib/blameview/README
deleted file mode 100644 (file)
index fada5ce..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-This is a sample program to use 'git-blame --incremental', based
-on this message.
-
-From: Jeff King <peff@peff.net>
-Subject: Re: More precise tag following
-To: Linus Torvalds <torvalds@linux-foundation.org>
-Cc: git@vger.kernel.org
-Date: Sat, 27 Jan 2007 18:52:38 -0500
-Message-ID: <20070127235238.GA28706@coredump.intra.peff.net>
diff --git a/contrib/blameview/blameview.perl b/contrib/blameview/blameview.perl
deleted file mode 100755 (executable)
index 1dec001..0000000
+++ /dev/null
@@ -1,155 +0,0 @@
-#!/usr/bin/perl
-
-use Gtk2 -init;
-use Gtk2::SimpleList;
-
-my $hash;
-my $fn;
-if ( @ARGV == 1 ) {
-       $hash = "HEAD";
-       $fn = shift;
-} elsif ( @ARGV == 2 ) {
-       $hash = shift;
-       $fn = shift;
-} else {
-       die "Usage blameview [<rev>] <filename>";
-}
-
-Gtk2::Rc->parse_string(<<'EOS');
-style "treeview_style"
-{
-  GtkTreeView::vertical-separator = 0
-}
-class "GtkTreeView" style "treeview_style"
-EOS
-
-my $window = Gtk2::Window->new('toplevel');
-$window->signal_connect(destroy => sub { Gtk2->main_quit });
-my $vpan = Gtk2::VPaned->new();
-$window->add($vpan);
-my $scrolled_window = Gtk2::ScrolledWindow->new;
-$vpan->pack1($scrolled_window, 1, 1);
-my $fileview = Gtk2::SimpleList->new(
-    'Commit' => 'text',
-    'FileLine' => 'text',
-    'Data' => 'text'
-);
-$scrolled_window->add($fileview);
-$fileview->get_column(0)->set_spacing(0);
-$fileview->set_size_request(1024, 768);
-$fileview->set_rules_hint(1);
-$fileview->signal_connect (row_activated => sub {
-               my ($sl, $path, $column) = @_;
-               my $row_ref = $sl->get_row_data_from_path ($path);
-               system("blameview @$row_ref[0]~1 $fn &");
-               });
-
-my $commitwindow = Gtk2::ScrolledWindow->new();
-$commitwindow->set_policy ('GTK_POLICY_AUTOMATIC','GTK_POLICY_AUTOMATIC');
-$vpan->pack2($commitwindow, 1, 1);
-my $commit_text = Gtk2::TextView->new();
-my $commit_buffer = Gtk2::TextBuffer->new();
-$commit_text->set_buffer($commit_buffer);
-$commitwindow->add($commit_text);
-
-$fileview->signal_connect (cursor_changed => sub {
-               my ($sl) = @_;
-               my ($path, $focus_column) = $sl->get_cursor();
-               my $row_ref = $sl->get_row_data_from_path ($path);
-               my $c_fh;
-               open($c_fh,  '-|', "git cat-file commit @$row_ref[0]")
-                                       or die "unable to find commit @$row_ref[0]";
-               my @buffer = <$c_fh>;
-               $commit_buffer->set_text("@buffer");
-               close($c_fh);
-               });
-
-my $fh;
-open($fh, '-|', "git cat-file blob $hash:$fn")
-  or die "unable to open $fn: $!";
-
-while(<$fh>) {
-  chomp;
-  $fileview->{data}->[$.] = ['HEAD', "$fn:$.", $_];
-}
-
-my $blame;
-open($blame, '-|', qw(git blame --incremental --), $fn, $hash)
-    or die "cannot start git-blame $fn";
-
-Glib::IO->add_watch(fileno($blame), 'in', \&read_blame_line);
-
-$window->show_all;
-Gtk2->main;
-exit 0;
-
-my %commitinfo = ();
-
-sub flush_blame_line {
-       my ($attr) = @_;
-
-       return unless defined $attr;
-
-       my ($commit, $s_lno, $lno, $cnt) =
-           @{$attr}{qw(COMMIT S_LNO LNO CNT)};
-
-       my ($filename, $author, $author_time, $author_tz) =
-           @{$commitinfo{$commit}}{qw(FILENAME AUTHOR AUTHOR-TIME AUTHOR-TZ)};
-       my $info = $author . ' ' . format_time($author_time, $author_tz);
-
-       for(my $i = 0; $i < $cnt; $i++) {
-               @{$fileview->{data}->[$lno+$i-1]}[0,1,2] =
-               (substr($commit, 0, 8), $filename . ':' . ($s_lno+$i));
-       }
-}
-
-my $buf;
-my $current;
-sub read_blame_line {
-
-       my $r = sysread($blame, $buf, 1024, length($buf));
-       die "I/O error" unless defined $r;
-
-       if ($r == 0) {
-               flush_blame_line($current);
-               $current = undef;
-               return 0;
-       }
-
-       while ($buf =~ s/([^\n]*)\n//) {
-               my $line = $1;
-
-               if (($commit, $s_lno, $lno, $cnt) =
-                   ($line =~ /^([0-9a-f]{40}) (\d+) (\d+) (\d+)$/)) {
-                       flush_blame_line($current);
-                       $current = +{
-                               COMMIT => $1,
-                               S_LNO => $2,
-                               LNO => $3,
-                               CNT => $4,
-                       };
-                       next;
-               }
-
-               # extended attribute values
-               if ($line =~ /^(author|author-mail|author-time|author-tz|committer|committer-mail|committer-time|committer-tz|summary|filename) (.*)$/) {
-                       my $commit = $current->{COMMIT};
-                       $commitinfo{$commit}{uc($1)} = $2;
-                       next;
-               }
-       }
-       return 1;
-}
-
-sub format_time {
-  my $time = shift;
-  my $tz = shift;
-
-  my $minutes = $tz < 0 ? 0-$tz : $tz;
-  $minutes = ($minutes / 100)*60 + ($minutes % 100);
-  $minutes = $tz < 0 ? 0-$minutes : $minutes;
-  $time += $minutes * 60;
-  my @t = gmtime($time);
-  return sprintf('%04d-%02d-%02d %02d:%02d:%02d %s',
-                $t[5] + 1900, @t[4,3,2,1,0], $tz);
-}
index 56c52c6654dfe0d320515ad151c6afc292ec70f9..6c3bafeea569601c4b55de165eb7f56ac3be1d31 100644 (file)
@@ -1163,7 +1163,7 @@ __git_diff_common_options="--stat --numstat --shortstat --summary
                        --no-prefix --src-prefix= --dst-prefix=
                        --inter-hunk-context=
                        --patience --histogram --minimal
-                       --raw
+                       --raw --word-diff
                        --dirstat --dirstat= --dirstat-by-file
                        --dirstat-by-file= --cumulative
                        --diff-algorithm=
@@ -1211,7 +1211,7 @@ _git_difftool ()
                return
                ;;
        esac
-       __git_complete_file
+       __git_complete_revlist_file
 }
 
 __git_fetch_options="
@@ -2277,7 +2277,7 @@ _git_show ()
                return
                ;;
        esac
-       __git_complete_file
+       __git_complete_revlist_file
 }
 
 _git_show_branch ()
index 86a4f3fa49cf68e14a6b0de13cb7f63fe8a2624d..07a6218d10679f5102c3649794bfd98e1883da75 100644 (file)
@@ -347,9 +347,9 @@ __git_ps1 ()
                local step=""
                local total=""
                if [ -d "$g/rebase-merge" ]; then
-                       b="$(cat "$g/rebase-merge/head-name")"
-                       step=$(cat "$g/rebase-merge/msgnum")
-                       total=$(cat "$g/rebase-merge/end")
+                       b="$(cat "$g/rebase-merge/head-name" 2>/dev/null)"
+                       step=$(cat "$g/rebase-merge/msgnum" 2>/dev/null)
+                       total=$(cat "$g/rebase-merge/end" 2>/dev/null)
                        if [ -f "$g/rebase-merge/interactive" ]; then
                                r="|REBASE-i"
                        else
@@ -357,10 +357,10 @@ __git_ps1 ()
                        fi
                else
                        if [ -d "$g/rebase-apply" ]; then
-                               step=$(cat "$g/rebase-apply/next")
-                               total=$(cat "$g/rebase-apply/last")
+                               step=$(cat "$g/rebase-apply/next" 2>/dev/null)
+                               total=$(cat "$g/rebase-apply/last" 2>/dev/null)
                                if [ -f "$g/rebase-apply/rebasing" ]; then
-                                       b="$(cat "$g/rebase-apply/head-name")"
+                                       b="$(cat "$g/rebase-apply/head-name" 2>/dev/null)"
                                        r="|REBASE"
                                elif [ -f "$g/rebase-apply/applying" ]; then
                                        r="|AM"
diff --git a/contrib/continuous/cidaemon b/contrib/continuous/cidaemon
deleted file mode 100644 (file)
index 4009a15..0000000
+++ /dev/null
@@ -1,503 +0,0 @@
-#!/usr/bin/perl
-#
-# A daemon that waits for update events sent by its companion
-# post-receive-cinotify hook, checks out a new copy of source,
-# compiles it, and emails the guilty parties if the compile
-# (and optionally test suite) fails.
-#
-# To use this daemon, configure it and run it.  It will disconnect
-# from your terminal and fork into the background.  The daemon must
-# have local filesystem access to the source repositories, as it
-# uses objects/info/alternates to avoid copying objects.
-#
-# Add its companion post-receive-cinotify hook as the post-receive
-# hook to each repository that the daemon should monitor.  Yes, a
-# single daemon can monitor more than one repository.
-#
-# To use multiple daemons on the same system, give them each a
-# unique queue file and tmpdir.
-#
-# Global Config
-# -------------
-# Reads from a Git style configuration file.  This will be
-# ~/.gitconfig by default but can be overridden by setting
-# the GIT_CONFIG_FILE environment variable before starting.
-#
-# cidaemon.smtpHost
-#   Hostname of the SMTP server the daemon will send email
-#   through.  Defaults to 'localhost'.
-#
-# cidaemon.smtpUser
-#   Username to authenticate to the SMTP server as.  This
-#   variable is optional; if it is not supplied then no
-#   authentication will be performed.
-#
-# cidaemon.smtpPassword
-#   Password to authenticate to the SMTP server as.  This
-#   variable is optional.  If not supplied but smtpUser was,
-#   the daemon prompts for the password before forking into
-#   the background.
-#
-# cidaemon.smtpAuth
-#   Type of authentication to perform with the SMTP server.
-#   If set to 'login' and smtpUser was defined, this will
-#   use the AUTH LOGIN command, which is suitable for use
-#   with at least one version of Microsoft Exchange Server.
-#   If not set the daemon will use whatever auth methods
-#   are supported by your version of Net::SMTP.
-#
-# cidaemon.email
-#   Email address that daemon generated emails will be sent
-#   from.  This should be a useful email address within your
-#   organization.  Required.
-#
-# cidaemon.name
-#   Human friendly name that the daemon will send emails as.
-#   Defaults to 'cidaemon'.
-#
-# cidaemon.scanDelay
-#   Number of seconds to sleep between polls of the queue file.
-#   Defaults to 60.
-#
-# cidaemon.recentCache
-#   Number of recent commit SHA-1s per repository to cache and
-#   skip building if they appear again.  This is useful to avoid
-#   rebuilding the same commit multiple times just because it was
-#   pushed into more than one branch.  Defaults to 100.
-#
-# cidaemon.tmpdir
-#   Scratch directory to create the builds within.  The daemon
-#   makes a new subdirectory for each build, then deletes it when
-#   the build has finished.  The pid file is also placed here.
-#   Defaults to '/tmp'.
-#
-# cidaemon.queue
-#   Path to the queue file that the post-receive-cinotify hook
-#   appends events to.  This file is polled by the daemon.  It
-#   must not be on an NFS mount (uses flock).  Required.
-#
-# cidaemon.nocc
-#   Perl regex patterns to match against author and committer
-#   lines.  If a pattern matches, that author or committer will
-#   not be notified of a build failure.
-#
-# Per Repository Config
-# ----------------------
-# Read from the source repository's config file.
-#
-# builder.command
-#   Shell command to execute the build.  This command must
-#   return 0 on "success" and non-zero on failure.  If you
-#   also want to run a test suite, make sure your command
-#   does that too.  Required.
-#
-# builder.queue
-#   Queue file to notify the cidaemon through.  Should match
-#   cidaemon.queue.  If not set the hook will not notify the
-#   cidaemon.
-#
-# builder.skip
-#   Perl regex patterns of refs that should not be sent to
-#   cidaemon.  Updates of these refs will be ignored.
-#
-# builder.newBranchBase
-#   Glob patterns of refs that should be used to form the
-#   'old' revions of a newly created ref.  This should set
-#   to be globs that match your 'mainline' branches.  This
-#   way a build failure of a brand new topic branch does not
-#   attempt to email everyone since the beginning of time;
-#   instead it only emails those authors of commits not in
-#   these 'mainline' branches.
-
-local $ENV{PATH} = join ':', qw(
-       /opt/git/bin
-       /usr/bin
-       /bin
-       );
-
-use strict;
-use warnings;
-use FindBin qw($RealBin);
-use File::Spec;
-use lib File::Spec->catfile($RealBin, '..', 'perl5');
-use Storable qw(retrieve nstore);
-use Fcntl ':flock';
-use POSIX qw(strftime);
-use Getopt::Long qw(:config no_auto_abbrev auto_help);
-
-sub git_config ($;$)
-{
-       my $var = shift;
-       my $required = shift || 0;
-       local *GIT;
-       open GIT, '-|','git','config','--get',$var;
-       my $r = <GIT>;
-       chop $r if $r;
-       close GIT;
-       die "error: $var not set.\n" if ($required && !$r);
-       return $r;
-}
-
-package EXCHANGE_NET_SMTP;
-
-# Microsoft Exchange Server requires an 'AUTH LOGIN'
-# style of authentication.  This is different from
-# the default supported by Net::SMTP so we subclass
-# and override the auth method to support that.
-
-use Net::SMTP;
-use Net::Cmd;
-use MIME::Base64 qw(encode_base64);
-our @ISA = qw(Net::SMTP);
-our $auth_type = ::git_config 'cidaemon.smtpAuth';
-
-sub new
-{
-       my $self = shift;
-       my $type = ref($self) || $self;
-       $type->SUPER::new(@_);
-}
-
-sub auth
-{
-       my $self = shift;
-       return $self->SUPER::auth(@_) unless $auth_type eq 'login';
-
-       my $user = encode_base64 shift, '';
-       my $pass = encode_base64 shift, '';
-       return 0 unless CMD_MORE == $self->command("AUTH LOGIN")->response;
-       return 0 unless CMD_MORE == $self->command($user)->response;
-       CMD_OK == $self->command($pass)->response;
-}
-
-package main;
-
-my ($debug_flag, %recent);
-
-my $ex_host = git_config('cidaemon.smtpHost') || 'localhost';
-my $ex_user = git_config('cidaemon.smtpUser');
-my $ex_pass = git_config('cidaemon.smtpPassword');
-
-my $ex_from_addr = git_config('cidaemon.email', 1);
-my $ex_from_name = git_config('cidaemon.name') || 'cidaemon';
-
-my $scan_delay = git_config('cidaemon.scanDelay') || 60;
-my $recent_size = git_config('cidaemon.recentCache') || 100;
-my $tmpdir = git_config('cidaemon.tmpdir') || '/tmp';
-my $queue_name = git_config('cidaemon.queue', 1);
-my $queue_lock = "$queue_name.lock";
-
-my @nocc_list;
-open GIT,'git config --get-all cidaemon.nocc|';
-while (<GIT>) {
-       chop;
-       push @nocc_list, $_;
-}
-close GIT;
-
-sub nocc_author ($)
-{
-       local $_ = shift;
-       foreach my $pat (@nocc_list) {
-               return 1 if /$pat/;
-       }
-       0;
-}
-
-sub input_echo ($)
-{
-       my $prompt = shift;
-
-       local $| = 1;
-       print $prompt;
-       my $input = <STDIN>;
-       chop $input;
-       return $input;
-}
-
-sub input_noecho ($)
-{
-       my $prompt = shift;
-
-       my $end = sub {system('stty','echo');print "\n";exit};
-       local $SIG{TERM} = $end;
-       local $SIG{INT} = $end;
-       system('stty','-echo');
-
-       local $| = 1;
-       print $prompt;
-       my $input = <STDIN>;
-       system('stty','echo');
-       print "\n";
-       chop $input;
-       return $input;
-}
-
-sub rfc2822_date ()
-{
-        strftime("%a, %d %b %Y %H:%M:%S %Z", localtime);
-}
-
-sub send_email ($$$)
-{
-       my ($subj, $body, $to) = @_;
-       my $now = rfc2822_date;
-       my $to_str = '';
-       my @rcpt_to;
-       foreach (@$to) {
-               my $s = $_;
-               $s =~ s/^/"/;
-               $s =~ s/(\s+<)/"$1/;
-               $to_str .= ', ' if $to_str;
-               $to_str .= $s;
-               push @rcpt_to, $1 if $s =~ /<(.*)>/;
-       }
-       die "Nobody to send to.\n" unless @rcpt_to;
-       my $msg = <<EOF;
-From: "$ex_from_name" <$ex_from_addr>
-To: $to_str
-Date: $now
-Subject: $subj
-
-$body
-EOF
-
-       my $smtp = EXCHANGE_NET_SMTP->new(Host => $ex_host)
-               or die "Cannot connect to $ex_host: $!\n";
-       if ($ex_user && $ex_pass) {
-               $smtp->auth($ex_user,$ex_pass)
-                       or die "$ex_host rejected $ex_user\n";
-       }
-       $smtp->mail($ex_from_addr)
-               or die "$ex_host rejected $ex_from_addr\n";
-       scalar($smtp->recipient(@rcpt_to, { SkipBad => 1 }))
-               or die "$ex_host did not accept any addresses.\n";
-       $smtp->data($msg)
-               or die "$ex_host rejected message data\n";
-       $smtp->quit;
-}
-
-sub pop_queue ()
-{
-       open LOCK, ">$queue_lock" or die "Can't open $queue_lock: $!";
-       flock LOCK, LOCK_EX;
-
-       my $queue = -f $queue_name ? retrieve $queue_name : [];
-       my $ent = shift @$queue;
-       nstore $queue, $queue_name;
-
-       flock LOCK, LOCK_UN;
-       close LOCK;
-       $ent;
-}
-
-sub git_exec (@)
-{
-       system('git',@_) == 0 or die "Cannot git " . join(' ', @_) . "\n";
-}
-
-sub git_val (@)
-{
-       open(C, '-|','git',@_);
-       my $r = <C>;
-       chop $r if $r;
-       close C;
-       $r;
-}
-
-sub do_build ($$)
-{
-       my ($git_dir, $new) = @_;
-
-       my $tmp = File::Spec->catfile($tmpdir, "builder$$");
-       system('rm','-rf',$tmp) == 0 or die "Cannot clear $tmp\n";
-       die "Cannot clear $tmp.\n" if -e $tmp;
-
-       my $result = 1;
-       eval {
-               my $command;
-               {
-                       local $ENV{GIT_DIR} = $git_dir;
-                       $command = git_val 'config','builder.command';
-               }
-               die "No builder.command for $git_dir.\n" unless $command;
-
-               git_exec 'clone','-n','-l','-s',$git_dir,$tmp;
-               chmod 0700, $tmp or die "Cannot lock $tmp\n";
-               chdir $tmp or die "Cannot enter $tmp\n";
-
-               git_exec 'update-ref','HEAD',$new;
-               git_exec 'read-tree','-m','-u','HEAD','HEAD';
-               system $command;
-               if ($? == -1) {
-                       print STDERR "failed to execute '$command': $!\n";
-                       $result = 1;
-               } elsif ($? & 127) {
-                       my $sig = $? & 127;
-                       print STDERR "'$command' died from signal $sig\n";
-                       $result = 1;
-               } else {
-                       my $r = $? >> 8;
-                       print STDERR "'$command' exited with $r\n" if $r;
-                       $result = $r;
-               }
-       };
-       if ($@) {
-               $result = 2;
-               print STDERR "$@\n";
-       }
-
-       chdir '/';
-       system('rm','-rf',$tmp);
-       rmdir $tmp;
-       $result;
-}
-
-sub build_failed ($$$$$)
-{
-       my ($git_dir, $ref, $old, $new, $msg) = @_;
-
-       $git_dir =~ m,/([^/]+)$,;
-       my $repo_name = $1;
-       $ref =~ s,^refs/(heads|tags)/,,;
-
-       my %authors;
-       my $shortlog;
-       my $revstr;
-       {
-               local $ENV{GIT_DIR} = $git_dir;
-               my @revs = ($new);
-               push @revs, '--not', @$old if @$old;
-               open LOG,'-|','git','rev-list','--pretty=raw',@revs;
-               while (<LOG>) {
-                       if (s/^(author|committer) //) {
-                               chomp;
-                               s/>.*$/>/;
-                               $authors{$_} = 1 unless nocc_author $_;
-                       }
-               }
-               close LOG;
-               open LOG,'-|','git','shortlog',@revs;
-               $shortlog .= $_ while <LOG>;
-               close LOG;
-               $revstr = join(' ', @revs);
-       }
-
-       my @to = sort keys %authors;
-       unless (@to) {
-               print STDERR "error: No authors in $revstr\n";
-               return;
-       }
-
-       my $subject = "[$repo_name] $ref : Build Failed";
-       my $body = <<EOF;
-Project: $git_dir
-Branch:  $ref
-Commits: $revstr
-
-$shortlog
-Build Output:
---------------------------------------------------------------
-$msg
-EOF
-       send_email($subject, $body, \@to);
-}
-
-sub run_build ($$$$)
-{
-       my ($git_dir, $ref, $old, $new) = @_;
-
-       if ($debug_flag) {
-               my @revs = ($new);
-               push @revs, '--not', @$old if @$old;
-               print "BUILDING $git_dir\n";
-               print "  BRANCH: $ref\n";
-               print "  COMMITS: ", join(' ', @revs), "\n";
-       }
-
-       local(*R, *W);
-       pipe R, W or die "cannot pipe builder: $!";
-
-       my $builder = fork();
-       if (!defined $builder) {
-               die "cannot fork builder: $!";
-       } elsif (0 == $builder) {
-               close R;
-               close STDIN;open(STDIN, '/dev/null');
-               open(STDOUT, '>&W');
-               open(STDERR, '>&W');
-               exit do_build $git_dir, $new;
-       } else {
-               close W;
-               my $out = '';
-               $out .= $_ while <R>;
-               close R;
-               waitpid $builder, 0;
-               build_failed $git_dir, $ref, $old, $new, $out if $?;
-       }
-
-       print "DONE\n\n" if $debug_flag;
-}
-
-sub daemon_loop ()
-{
-       my $run = 1;
-       my $stop_sub = sub {$run = 0};
-       $SIG{HUP} = $stop_sub;
-       $SIG{INT} = $stop_sub;
-       $SIG{TERM} = $stop_sub;
-
-       mkdir $tmpdir, 0755;
-       my $pidfile = File::Spec->catfile($tmpdir, "cidaemon.pid");
-       open(O, ">$pidfile"); print O "$$\n"; close O;
-
-       while ($run) {
-               my $ent = pop_queue;
-               if ($ent) {
-                       my ($git_dir, $ref, $old, $new) = @$ent;
-
-                       $ent = $recent{$git_dir};
-                       $recent{$git_dir} = $ent = [[], {}] unless $ent;
-                       my ($rec_arr, $rec_hash) = @$ent;
-                       next if $rec_hash->{$new}++;
-                       while (@$rec_arr >= $recent_size) {
-                               my $to_kill = shift @$rec_arr;
-                               delete $rec_hash->{$to_kill};
-                       }
-                       push @$rec_arr, $new;
-
-                       run_build $git_dir, $ref, $old, $new;
-               } else {
-                       sleep $scan_delay;
-               }
-       }
-
-       unlink $pidfile;
-}
-
-$debug_flag = 0;
-GetOptions(
-       'debug|d' => \$debug_flag,
-       'smtp-user=s' => \$ex_user,
-) or die "usage: $0 [--debug] [--smtp-user=user]\n";
-
-$ex_pass = input_noecho("$ex_user SMTP password: ")
-       if ($ex_user && !$ex_pass);
-
-if ($debug_flag) {
-       daemon_loop;
-       exit 0;
-}
-
-my $daemon = fork();
-if (!defined $daemon) {
-       die "cannot fork daemon: $!";
-} elsif (0 == $daemon) {
-       close STDIN;open(STDIN, '/dev/null');
-       close STDOUT;open(STDOUT, '>/dev/null');
-       close STDERR;open(STDERR, '>/dev/null');
-       daemon_loop;
-       exit 0;
-} else {
-       print "Daemon $daemon running in the background.\n";
-}
diff --git a/contrib/continuous/post-receive-cinotify b/contrib/continuous/post-receive-cinotify
deleted file mode 100644 (file)
index b8f5a60..0000000
+++ /dev/null
@@ -1,104 +0,0 @@
-#!/usr/bin/perl
-#
-# A hook that notifies its companion cidaemon through a simple
-# queue file that a ref has been updated via a push (actually
-# by a receive-pack running on the server).
-#
-# See cidaemon for per-repository configuration details.
-#
-# To use this hook, add it as the post-receive hook, make it
-# executable, and set its configuration options.
-#
-
-local $ENV{PATH} = '/opt/git/bin';
-
-use strict;
-use warnings;
-use File::Spec;
-use Storable qw(retrieve nstore);
-use Fcntl ':flock';
-
-my $git_dir = File::Spec->rel2abs($ENV{GIT_DIR});
-my $queue_name = `git config --get builder.queue`;chop $queue_name;
-$queue_name =~ m,^([^\s]+)$,; $queue_name = $1; # untaint
-unless ($queue_name) {
-       1 while <STDIN>;
-       print STDERR "\nerror: builder.queue not set.  Not enqueing.\n\n";
-       exit;
-}
-my $queue_lock = "$queue_name.lock";
-
-my @skip;
-open S, "git config --get-all builder.skip|";
-while (<S>) {
-       chop;
-       push @skip, $_;
-}
-close S;
-
-my @new_branch_base;
-open S, "git config --get-all builder.newBranchBase|";
-while (<S>) {
-       chop;
-       push @new_branch_base, $_;
-}
-close S;
-
-sub skip ($)
-{
-       local $_ = shift;
-       foreach my $p (@skip) {
-               return 1 if /^$p/;
-       }
-       0;
-}
-
-open LOCK, ">$queue_lock" or die "Can't open $queue_lock: $!";
-flock LOCK, LOCK_EX;
-
-my $queue = -f $queue_name ? retrieve $queue_name : [];
-my %existing;
-foreach my $r (@$queue) {
-       my ($gd, $ref) = @$r;
-       $existing{$gd}{$ref} = $r;
-}
-
-my @new_branch_commits;
-my $loaded_new_branch_commits = 0;
-
-while (<STDIN>) {
-       chop;
-       my ($old, $new, $ref) = split / /, $_, 3;
-
-       next if $old eq $new;
-       next if $new =~ /^0{40}$/;
-       next if skip $ref;
-
-       my $r = $existing{$git_dir}{$ref};
-       if ($r) {
-               $r->[3] = $new;
-       } else {
-               if ($old =~ /^0{40}$/) {
-                       if (!$loaded_new_branch_commits && @new_branch_base) {
-                               open M,'-|','git','show-ref',@new_branch_base;
-                               while (<M>) {
-                                       ($_) = split / /, $_;
-                                       push @new_branch_commits, $_;
-                               }
-                               close M;
-                               $loaded_new_branch_commits = 1;
-                       }
-                       $old = [@new_branch_commits];
-               } else {
-                       $old = [$old];
-               }
-
-               $r = [$git_dir, $ref, $old, $new];
-               $existing{$git_dir}{$ref} = $r;
-               push @$queue, $r;
-       }
-}
-nstore $queue, $queue_name;
-
-flock LOCK, LOCK_UN;
-close LOCK;
diff --git a/contrib/mw-to-git/.perlcriticrc b/contrib/mw-to-git/.perlcriticrc
new file mode 100644 (file)
index 0000000..5a9955d
--- /dev/null
@@ -0,0 +1,28 @@
+# These 3 rules demand to add the s, m and x flag to *every* regexp. This is
+# overkill and would be harmful for readability.
+[-RegularExpressions::RequireExtendedFormatting]
+[-RegularExpressions::RequireDotMatchAnything]
+[-RegularExpressions::RequireLineBoundaryMatching]
+
+# This rule says that builtin functions should not be called with parentheses
+# e.g.: (taken from CPAN's documentation)
+# open($handle, '>', $filename); #not ok
+# open $handle, '>', $filename;  #ok
+# Applying such a rule would mean modifying a huge number of lines for a
+# question of style.
+[-CodeLayout::ProhibitParensWithBuiltins]
+
+# This rule states that each system call should have its return value checked
+# The problem is that it includes the print call. Checking every print call's
+# return value would be harmful to the code readabilty.
+# This configuration keeps all default function but print.
+[InputOutput::RequireCheckedSyscalls]
+functions = open say close
+
+# This rules demands to add a dependancy for the Readonly module. This is not
+# wished.
+[-ValuesAndExpressions::ProhibitConstantPragma]
+
+# This rule is not really useful (rather a question of style) and produces many
+# warnings among the code.
+[-ValuesAndExpressions::ProhibitNoisyQuotes]
index f14971987c3d14892ad212485c4b1662d73165f7..1fb24244817b7b61d91316ced5f583605ff72f36 100644 (file)
@@ -15,3 +15,5 @@ all: build
 build install clean:
        $(MAKE) -C $(GIT_ROOT_DIR) SCRIPT_PERL=$(SCRIPT_PERL_FULL) \
                 $@-perl-script
+perlcritic:
+       perlcritic -2 *.perl
index f0c348cd2878e94ecb5a05500efebb970f221843..71baf8ace8882e96eddab19e4b11448044b5190e 100755 (executable)
 
 use strict;
 use MediaWiki::API;
+use Git;
 use DateTime::Format::ISO8601;
+use warnings;
 
 # By default, use UTF-8 to communicate with Git and the user
-binmode STDERR, ":utf8";
-binmode STDOUT, ":utf8";
+binmode STDERR, ':encoding(UTF-8)';
+binmode STDOUT, ':encoding(UTF-8)';
 
 use URI::Escape;
-use IPC::Open2;
-
-use warnings;
 
 # Mediawiki filenames can contain forward slashes. This variable decides by which pattern they should be replaced
-use constant SLASH_REPLACEMENT => "%2F";
+use constant SLASH_REPLACEMENT => '%2F';
 
 # It's not always possible to delete pages (may require some
 # privileges). Deleted pages are replaced with this content.
 use constant EMPTY_CONTENT => "<!-- empty page -->\n";
 
 # used to reflect file creation or deletion in diff.
-use constant NULL_SHA1 => "0000000000000000000000000000000000000000";
+use constant NULL_SHA1 => '0000000000000000000000000000000000000000';
 
 # Used on Git's side to reflect empty edit messages on the wiki
 use constant EMPTY_MESSAGE => '*Empty MediaWiki Message*';
 
+use constant EMPTY => q{};
+
+# Number of pages taken into account at once in submodule get_mw_page_list
+use constant SLICE_SIZE => 50;
+
+# Number of linked mediafile to get at once in get_linked_mediafiles
+# The query is split in small batches because of the MW API limit of
+# the number of links to be returned (500 links max).
+use constant BATCH_SIZE => 10;
+
+use constant HTTP_CODE_OK => 200;
+
+if (@ARGV != 2) {
+       exit_error_usage();
+}
+
 my $remotename = $ARGV[0];
 my $url = $ARGV[1];
 
 # Accept both space-separated and multiple keys in config file.
 # Spaces should be written as _ anyway because we'll use chomp.
-my @tracked_pages = split(/[ \n]/, run_git("config --get-all remote.". $remotename .".pages"));
+my @tracked_pages = split(/[ \n]/, run_git("config --get-all remote.${remotename}.pages"));
 chomp(@tracked_pages);
 
 # Just like @tracked_pages, but for MediaWiki categories.
-my @tracked_categories = split(/[ \n]/, run_git("config --get-all remote.". $remotename .".categories"));
+my @tracked_categories = split(/[ \n]/, run_git("config --get-all remote.${remotename}.categories"));
 chomp(@tracked_categories);
 
 # Import media files on pull
-my $import_media = run_git("config --get --bool remote.". $remotename .".mediaimport");
+my $import_media = run_git("config --get --bool remote.${remotename}.mediaimport");
 chomp($import_media);
-$import_media = ($import_media eq "true");
+$import_media = ($import_media eq 'true');
 
 # Export media files on push
-my $export_media = run_git("config --get --bool remote.". $remotename .".mediaexport");
+my $export_media = run_git("config --get --bool remote.${remotename}.mediaexport");
 chomp($export_media);
-$export_media = !($export_media eq "false");
+$export_media = !($export_media eq 'false');
 
-my $wiki_login = run_git("config --get remote.". $remotename .".mwLogin");
+my $wiki_login = run_git("config --get remote.${remotename}.mwLogin");
 # Note: mwPassword is discourraged. Use the credential system instead.
-my $wiki_passwd = run_git("config --get remote.". $remotename .".mwPassword");
-my $wiki_domain = run_git("config --get remote.". $remotename .".mwDomain");
+my $wiki_passwd = run_git("config --get remote.${remotename}.mwPassword");
+my $wiki_domain = run_git("config --get remote.${remotename}.mwDomain");
 chomp($wiki_login);
 chomp($wiki_passwd);
 chomp($wiki_domain);
 
 # Import only last revisions (both for clone and fetch)
-my $shallow_import = run_git("config --get --bool remote.". $remotename .".shallow");
+my $shallow_import = run_git("config --get --bool remote.${remotename}.shallow");
 chomp($shallow_import);
-$shallow_import = ($shallow_import eq "true");
+$shallow_import = ($shallow_import eq 'true');
 
 # Fetch (clone and pull) by revisions instead of by pages. This behavior
 # is more efficient when we have a wiki with lots of pages and we fetch
 # Possible values:
 # - by_rev: perform one query per new revision on the remote wiki
 # - by_page: query each tracked page for new revision
-my $fetch_strategy = run_git("config --get remote.$remotename.fetchStrategy");
-unless ($fetch_strategy) {
-       $fetch_strategy = run_git("config --get mediawiki.fetchStrategy");
+my $fetch_strategy = run_git("config --get remote.${remotename}.fetchStrategy");
+if (!$fetch_strategy) {
+       $fetch_strategy = run_git('config --get mediawiki.fetchStrategy');
 }
 chomp($fetch_strategy);
-unless ($fetch_strategy) {
-       $fetch_strategy = "by_page";
+if (!$fetch_strategy) {
+       $fetch_strategy = 'by_page';
 }
 
+# Remember the timestamp corresponding to a revision id.
+my %basetimestamps;
+
 # Dumb push: don't update notes and mediawiki ref to reflect the last push.
 #
 # Configurable with mediawiki.dumbPush, or per-remote with
 # will get the history with information lost). If the import is
 # deterministic, this means everybody gets the same sha1 for each
 # MediaWiki revision.
-my $dumb_push = run_git("config --get --bool remote.$remotename.dumbPush");
-unless ($dumb_push) {
-       $dumb_push = run_git("config --get --bool mediawiki.dumbPush");
+my $dumb_push = run_git("config --get --bool remote.${remotename}.dumbPush");
+if (!$dumb_push) {
+       $dumb_push = run_git('config --get --bool mediawiki.dumbPush');
 }
 chomp($dumb_push);
-$dumb_push = ($dumb_push eq "true");
+$dumb_push = ($dumb_push eq 'true');
 
 my $wiki_name = $url;
-$wiki_name =~ s/[^\/]*:\/\///;
+$wiki_name =~ s{[^/]*://}{};
 # If URL is like http://user:password@example.com/, we clearly don't
 # want the password in $wiki_name. While we're there, also remove user
 # and '@' sign, to avoid author like MWUser@HTTPUser@host.com
 $wiki_name =~ s/^.*@//;
 
 # Commands parser
-my $entry;
-my @cmd;
 while (<STDIN>) {
        chomp;
-       @cmd = split(/ /);
-       if (defined($cmd[0])) {
-               # Line not blank
-               if ($cmd[0] eq "capabilities") {
-                       die("Too many arguments for capabilities") unless (!defined($cmd[1]));
-                       mw_capabilities();
-               } elsif ($cmd[0] eq "list") {
-                       die("Too many arguments for list") unless (!defined($cmd[2]));
-                       mw_list($cmd[1]);
-               } elsif ($cmd[0] eq "import") {
-                       die("Invalid arguments for import") unless ($cmd[1] ne "" && !defined($cmd[2]));
-                       mw_import($cmd[1]);
-               } elsif ($cmd[0] eq "option") {
-                       die("Too many arguments for option") unless ($cmd[1] ne "" && $cmd[2] ne "" && !defined($cmd[3]));
-                       mw_option($cmd[1],$cmd[2]);
-               } elsif ($cmd[0] eq "push") {
-                       mw_push($cmd[1]);
-               } else {
-                       print STDERR "Unknown command. Aborting...\n";
-                       last;
-               }
-       } else {
-               # blank line: we should terminate
+
+       if (!parse_command($_)) {
                last;
        }
 
 
 ########################## Functions ##############################
 
-## credential API management (generic functions)
-
-sub credential_read {
-       my %credential;
-       my $reader = shift;
-       my $op = shift;
-       while (<$reader>) {
-               my ($key, $value) = /([^=]*)=(.*)/;
-               if (not defined $key) {
-                       die "ERROR receiving response from git credential $op:\n$_\n";
-               }
-               $credential{$key} = $value;
-       }
-       return %credential;
+## error handling
+sub exit_error_usage {
+       die "ERROR: git-remote-mediawiki module was not called with a correct number of\n" .
+           "parameters\n" .
+           "You may obtain this error because you attempted to run the git-remote-mediawiki\n" .
+            "module directly.\n" .
+           "This module can be used the following way:\n" .
+           "\tgit clone mediawiki://<address of a mediawiki>\n" .
+           "Then, use git commit, push and pull as with every normal git repository.\n";
 }
 
-sub credential_write {
-       my $credential = shift;
-       my $writer = shift;
-       # url overwrites other fields, so it must come first
-       print $writer "url=$credential->{url}\n" if exists $credential->{url};
-       while (my ($key, $value) = each(%$credential) ) {
-               if (length $value && $key ne 'url') {
-                       print $writer "$key=$value\n";
-               }
+sub parse_command {
+       my ($line) = @_;
+       my @cmd = split(/ /, $line);
+       if (!defined $cmd[0]) {
+               return 0;
        }
-}
-
-sub credential_run {
-       my $op = shift;
-       my $credential = shift;
-       my $pid = open2(my $reader, my $writer, "git credential $op");
-       credential_write($credential, $writer);
-       print $writer "\n";
-       close($writer);
-
-       if ($op eq "fill") {
-               %$credential = credential_read($reader, $op);
+       if ($cmd[0] eq 'capabilities') {
+               die("Too many arguments for capabilities\n")
+                   if (defined($cmd[1]));
+               mw_capabilities();
+       } elsif ($cmd[0] eq 'list') {
+               die("Too many arguments for list\n") if (defined($cmd[2]));
+               mw_list($cmd[1]);
+       } elsif ($cmd[0] eq 'import') {
+               die("Invalid argument for import\n")
+                   if ($cmd[1] eq EMPTY);
+               die("Too many arguments for import\n")
+                   if (defined($cmd[2]));
+               mw_import($cmd[1]);
+       } elsif ($cmd[0] eq 'option') {
+               die("Invalid arguments for option\n")
+                   if ($cmd[1] eq EMPTY || $cmd[2] eq EMPTY);
+               die("Too many arguments for option\n")
+                   if (defined($cmd[3]));
+               mw_option($cmd[1],$cmd[2]);
+       } elsif ($cmd[0] eq 'push') {
+               mw_push($cmd[1]);
        } else {
-               if (<$reader>) {
-                       die "ERROR while running git credential $op:\n$_";
-               }
-       }
-       close($reader);
-       waitpid($pid, 0);
-       my $child_exit_status = $? >> 8;
-       if ($child_exit_status != 0) {
-               die "'git credential $op' failed with code $child_exit_status.";
+               print {*STDERR} "Unknown command. Aborting...\n";
+               return 0;
        }
+       return 1;
 }
 
 # MediaWiki API instance, created lazily.
@@ -215,27 +204,30 @@ sub mw_connect_maybe {
                return;
        }
        $mediawiki = MediaWiki::API->new;
-       $mediawiki->{config}->{api_url} = "$url/api.php";
+       $mediawiki->{config}->{api_url} = "${url}/api.php";
        if ($wiki_login) {
-               my %credential = (url => $url);
-               $credential{username} = $wiki_login;
-               $credential{password} = $wiki_passwd;
-               credential_run("fill", \%credential);
+               my %credential = (
+                       'url' => $url,
+                       'username' => $wiki_login,
+                       'password' => $wiki_passwd
+               );
+               Git::credential(\%credential);
                my $request = {lgname => $credential{username},
                               lgpassword => $credential{password},
                               lgdomain => $wiki_domain};
                if ($mediawiki->login($request)) {
-                       credential_run("approve", \%credential);
-                       print STDERR "Logged in mediawiki user \"$credential{username}\".\n";
+                       Git::credential(\%credential, 'approve');
+                       print {*STDERR} qq(Logged in mediawiki user "$credential{username}".\n);
                } else {
-                       print STDERR "Failed to log in mediawiki user \"$credential{username}\" on $url\n";
-                       print STDERR "  (error " .
+                       print {*STDERR} qq(Failed to log in mediawiki user "$credential{username}" on ${url}\n);
+                       print {*STDERR} '  (error ' .
                                $mediawiki->{error}->{code} . ': ' .
                                $mediawiki->{error}->{details} . ")\n";
-                       credential_run("reject", \%credential);
+                       Git::credential(\%credential, 'reject');
                        exit 1;
                }
        }
+       return;
 }
 
 sub fatal_mw_error {
@@ -258,21 +250,23 @@ sub fatal_mw_error {
 sub get_mw_tracked_pages {
        my $pages = shift;
        get_mw_page_list(\@tracked_pages, $pages);
+       return;
 }
 
 sub get_mw_page_list {
        my $page_list = shift;
        my $pages = shift;
-       my @some_pages = @$page_list;
+       my @some_pages = @{$page_list};
        while (@some_pages) {
-               my $last = 50;
-               if ($#some_pages < $last) {
-                       $last = $#some_pages;
+               my $last_page = SLICE_SIZE;
+               if ($#some_pages < $last_page) {
+                       $last_page = $#some_pages;
                }
-               my @slice = @some_pages[0..$last];
+               my @slice = @some_pages[0..$last_page];
                get_mw_first_pages(\@slice, $pages);
-               @some_pages = @some_pages[51..$#some_pages];
+               @some_pages = @some_pages[(SLICE_SIZE + 1)..$#some_pages];
        }
+       return;
 }
 
 sub get_mw_tracked_categories {
@@ -282,7 +276,7 @@ sub get_mw_tracked_categories {
                        # Mediawiki requires the Category
                        # prefix, but let's not force the user
                        # to specify it.
-                       $category = "Category:" . $category;
+                       $category = "Category:${category}";
                }
                my $mw_pages = $mediawiki->list( {
                        action => 'query',
@@ -290,11 +284,12 @@ sub get_mw_tracked_categories {
                        cmtitle => $category,
                        cmlimit => 'max' } )
                        || die $mediawiki->{error}->{code} . ': '
-                               . $mediawiki->{error}->{details};
+                               . $mediawiki->{error}->{details} . "\n";
                foreach my $page (@{$mw_pages}) {
                        $pages->{$page->{title}} = $page;
                }
        }
+       return;
 }
 
 sub get_mw_all_pages {
@@ -311,6 +306,7 @@ sub get_mw_all_pages {
        foreach my $page (@{$mw_pages}) {
                $pages->{$page->{title}} = $page;
        }
+       return;
 }
 
 # queries the wiki for a set of pages. Meant to be used within a loop
@@ -333,18 +329,19 @@ sub get_mw_first_pages {
        }
        while (my ($id, $page) = each(%{$mw_pages->{query}->{pages}})) {
                if ($id < 0) {
-                       print STDERR "Warning: page $page->{title} not found on wiki\n";
+                       print {*STDERR} "Warning: page $page->{title} not found on wiki\n";
                } else {
                        $pages->{$page->{title}} = $page;
                }
        }
+       return;
 }
 
 # Get the list of pages to be fetched according to configuration.
 sub get_mw_pages {
        mw_connect_maybe();
 
-       print STDERR "Listing pages on remote wiki...\n";
+       print {*STDERR} "Listing pages on remote wiki...\n";
 
        my %pages; # hash on page titles to avoid duplicates
        my $user_defined;
@@ -362,14 +359,14 @@ sub get_mw_pages {
                get_mw_all_pages(\%pages);
        }
        if ($import_media) {
-               print STDERR "Getting media files for selected pages...\n";
+               print {*STDERR} "Getting media files for selected pages...\n";
                if ($user_defined) {
                        get_linked_mediafiles(\%pages);
                } else {
                        get_all_mediafiles(\%pages);
                }
        }
-       print STDERR (scalar keys %pages) . " pages found.\n";
+       print {*STDERR} (scalar keys %pages) . " pages found.\n";
        return %pages;
 }
 
@@ -377,9 +374,13 @@ sub get_mw_pages {
 #        $out = run_git("command args", "raw"); # don't interpret output as UTF-8.
 sub run_git {
        my $args = shift;
-       my $encoding = (shift || "encoding(UTF-8)");
-       open(my $git, "-|:$encoding", "git " . $args);
-       my $res = do { local $/; <$git> };
+       my $encoding = (shift || 'encoding(UTF-8)');
+       open(my $git, "-|:${encoding}", "git ${args}")
+           or die "Unable to fork: $!\n";
+       my $res = do {
+               local $/ = undef;
+               <$git>
+       };
        close($git);
 
        return $res;
@@ -394,27 +395,26 @@ sub get_all_mediafiles {
        my $mw_pages = $mediawiki->list({
                action => 'query',
                list => 'allpages',
-               apnamespace => get_mw_namespace_id("File"),
+               apnamespace => get_mw_namespace_id('File'),
                aplimit => 'max'
        });
        if (!defined($mw_pages)) {
-               print STDERR "fatal: could not get the list of pages for media files.\n";
-               print STDERR "fatal: '$url' does not appear to be a mediawiki\n";
-               print STDERR "fatal: make sure '$url/api.php' is a valid page.\n";
+               print {*STDERR} "fatal: could not get the list of pages for media files.\n";
+               print {*STDERR} "fatal: '$url' does not appear to be a mediawiki\n";
+               print {*STDERR} "fatal: make sure '$url/api.php' is a valid page.\n";
                exit 1;
        }
        foreach my $page (@{$mw_pages}) {
                $pages->{$page->{title}} = $page;
        }
+       return;
 }
 
 sub get_linked_mediafiles {
        my $pages = shift;
-       my @titles = map $_->{title}, values(%{$pages});
+       my @titles = map { $_->{title} } values(%{$pages});
 
-       # The query is split in small batches because of the MW API limit of
-       # the number of links to be returned (500 links max).
-       my $batch = 10;
+       my $batch = BATCH_SIZE;
        while (@titles) {
                if ($#titles < $batch) {
                        $batch = $#titles;
@@ -430,7 +430,7 @@ sub get_linked_mediafiles {
                        action => 'query',
                        prop => 'links|images',
                        titles => $mw_titles,
-                       plnamespace => get_mw_namespace_id("File"),
+                       plnamespace => get_mw_namespace_id('File'),
                        pllimit => 'max'
                };
                my $result = $mediawiki->api($query);
@@ -438,11 +438,13 @@ sub get_linked_mediafiles {
                while (my ($id, $page) = each(%{$result->{query}->{pages}})) {
                        my @media_titles;
                        if (defined($page->{links})) {
-                               my @link_titles = map $_->{title}, @{$page->{links}};
+                               my @link_titles
+                                   = map { $_->{title} } @{$page->{links}};
                                push(@media_titles, @link_titles);
                        }
                        if (defined($page->{images})) {
-                               my @image_titles = map $_->{title}, @{$page->{images}};
+                               my @image_titles
+                                   = map { $_->{title} } @{$page->{images}};
                                push(@media_titles, @image_titles);
                        }
                        if (@media_titles) {
@@ -452,6 +454,7 @@ sub get_linked_mediafiles {
 
                @titles = @titles[($batch+1)..$#titles];
        }
+       return;
 }
 
 sub get_mw_mediafile_for_page_revision {
@@ -465,7 +468,7 @@ sub get_mw_mediafile_for_page_revision {
        my $query = {
                action => 'query',
                prop => 'imageinfo',
-               titles => "File:" . $filename,
+               titles => "File:${filename}",
                iistart => $timestamp,
                iiend => $timestamp,
                iiprop => 'timestamp|archivename|url',
@@ -483,47 +486,44 @@ sub get_mw_mediafile_for_page_revision {
                $mediafile{timestamp} = $fileinfo->{timestamp};
                # Mediawiki::API's download function doesn't support https URLs
                # and can't download old versions of files.
-               print STDERR "\tDownloading file $mediafile{title}, version $mediafile{timestamp}\n";
+               print {*STDERR} "\tDownloading file $mediafile{title}, version $mediafile{timestamp}\n";
                $mediafile{content} = download_mw_mediafile($fileinfo->{url});
        }
        return %mediafile;
 }
 
 sub download_mw_mediafile {
-       my $url = shift;
+       my $download_url = shift;
 
-       my $response = $mediawiki->{ua}->get($url);
-       if ($response->code == 200) {
+       my $response = $mediawiki->{ua}->get($download_url);
+       if ($response->code == HTTP_CODE_OK) {
                return $response->decoded_content;
        } else {
-               print STDERR "Error downloading mediafile from :\n";
-               print STDERR "URL: $url\n";
-               print STDERR "Server response: " . $response->code . " " . $response->message . "\n";
+               print {*STDERR} "Error downloading mediafile from :\n";
+               print {*STDERR} "URL: ${download_url}\n";
+               print {*STDERR} 'Server response: ' . $response->code . q{ } . $response->message . "\n";
                exit 1;
        }
 }
 
 sub get_last_local_revision {
        # Get note regarding last mediawiki revision
-       my $note = run_git("notes --ref=$remotename/mediawiki show refs/mediawiki/$remotename/master 2>/dev/null");
+       my $note = run_git("notes --ref=${remotename}/mediawiki show refs/mediawiki/${remotename}/master 2>/dev/null");
        my @note_info = split(/ /, $note);
 
        my $lastrevision_number;
-       if (!(defined($note_info[0]) && $note_info[0] eq "mediawiki_revision:")) {
-               print STDERR "No previous mediawiki revision found";
+       if (!(defined($note_info[0]) && $note_info[0] eq 'mediawiki_revision:')) {
+               print {*STDERR} 'No previous mediawiki revision found';
                $lastrevision_number = 0;
        } else {
                # Notes are formatted : mediawiki_revision: #number
                $lastrevision_number = $note_info[1];
                chomp($lastrevision_number);
-               print STDERR "Last local mediawiki revision found is $lastrevision_number";
+               print {*STDERR} "Last local mediawiki revision found is ${lastrevision_number}";
        }
        return $lastrevision_number;
 }
 
-# Remember the timestamp corresponding to a revision id.
-my %basetimestamps;
-
 # Get the last remote revision without taking in account which pages are
 # tracked or not. This function makes a single request to the wiki thus
 # avoid a loop onto all tracked pages. This is useful for the fetch-by-rev
@@ -552,7 +552,7 @@ sub get_last_remote_revision {
 
        my $max_rev_num = 0;
 
-       print STDERR "Getting last revision id on tracked pages...\n";
+       print {*STDERR} "Getting last revision id on tracked pages...\n";
 
        foreach my $page (@pages) {
                my $id = $page->{pageid};
@@ -573,7 +573,7 @@ sub get_last_remote_revision {
                $max_rev_num = ($lastrev->{revid} > $max_rev_num ? $lastrev->{revid} : $max_rev_num);
        }
 
-       print STDERR "Last remote revision found is $max_rev_num.\n";
+       print {*STDERR} "Last remote revision found is $max_rev_num.\n";
        return $max_rev_num;
 }
 
@@ -584,7 +584,7 @@ sub mediawiki_clean {
        # Mediawiki does not allow blank space at the end of a page and ends with a single \n.
        # This function right trims a string and adds a \n at the end to follow this rule
        $string =~ s/\s+$//;
-       if ($string eq "" && $page_created) {
+       if ($string eq EMPTY && $page_created) {
                # Creating empty pages is forbidden.
                $string = EMPTY_CONTENT;
        }
@@ -595,15 +595,15 @@ sub mediawiki_clean {
 sub mediawiki_smudge {
        my $string = shift;
        if ($string eq EMPTY_CONTENT) {
-               $string = "";
+               $string = EMPTY;
        }
        # This \n is important. This is due to mediawiki's way to handle end of files.
-       return $string."\n";
+       return "${string}\n";
 }
 
 sub mediawiki_clean_filename {
        my $filename = shift;
-       $filename =~ s/@{[SLASH_REPLACEMENT]}/\//g;
+       $filename =~ s{@{[SLASH_REPLACEMENT]}}{/}g;
        # [, ], |, {, and } are forbidden by MediaWiki, even URL-encoded.
        # Do a variant of URL-encoding, i.e. looks like URL-encoding,
        # but with _ added to prevent MediaWiki from thinking this is
@@ -617,16 +617,17 @@ sub mediawiki_clean_filename {
 
 sub mediawiki_smudge_filename {
        my $filename = shift;
-       $filename =~ s/\//@{[SLASH_REPLACEMENT]}/g;
+       $filename =~ s{/}{@{[SLASH_REPLACEMENT]}}g;
        $filename =~ s/ /_/g;
        # Decode forbidden characters encoded in mediawiki_clean_filename
-       $filename =~ s/_%_([0-9a-fA-F][0-9a-fA-F])/sprintf("%c", hex($1))/ge;
+       $filename =~ s/_%_([0-9a-fA-F][0-9a-fA-F])/sprintf('%c', hex($1))/ge;
        return $filename;
 }
 
 sub literal_data {
        my ($content) = @_;
-       print STDOUT "data ", bytes::length($content), "\n", $content;
+       print {*STDOUT} 'data ', bytes::length($content), "\n", $content;
+       return;
 }
 
 sub literal_data_raw {
@@ -634,33 +635,37 @@ sub literal_data_raw {
        my ($content) = @_;
        # Avoid confusion between size in bytes and in characters
        utf8::downgrade($content);
-       binmode STDOUT, ":raw";
-       print STDOUT "data ", bytes::length($content), "\n", $content;
-       binmode STDOUT, ":utf8";
+       binmode {*STDOUT}, ':raw';
+       print {*STDOUT} 'data ', bytes::length($content), "\n", $content;
+       binmode {*STDOUT}, ':encoding(UTF-8)';
+       return;
 }
 
 sub mw_capabilities {
        # Revisions are imported to the private namespace
        # refs/mediawiki/$remotename/ by the helper and fetched into
        # refs/remotes/$remotename later by fetch.
-       print STDOUT "refspec refs/heads/*:refs/mediawiki/$remotename/*\n";
-       print STDOUT "import\n";
-       print STDOUT "list\n";
-       print STDOUT "push\n";
-       print STDOUT "\n";
+       print {*STDOUT} "refspec refs/heads/*:refs/mediawiki/${remotename}/*\n";
+       print {*STDOUT} "import\n";
+       print {*STDOUT} "list\n";
+       print {*STDOUT} "push\n";
+       print {*STDOUT} "\n";
+       return;
 }
 
 sub mw_list {
        # MediaWiki do not have branches, we consider one branch arbitrarily
        # called master, and HEAD pointing to it.
-       print STDOUT "? refs/heads/master\n";
-       print STDOUT "\@refs/heads/master HEAD\n";
-       print STDOUT "\n";
+       print {*STDOUT} "? refs/heads/master\n";
+       print {*STDOUT} "\@refs/heads/master HEAD\n";
+       print {*STDOUT} "\n";
+       return;
 }
 
 sub mw_option {
-       print STDERR "remote-helper command 'option $_[0]' not yet implemented\n";
-       print STDOUT "unsupported\n";
+       print {*STDERR} "remote-helper command 'option $_[0]' not yet implemented\n";
+       print {*STDOUT} "unsupported\n";
+       return;
 }
 
 sub fetch_mw_revisions_for_page {
@@ -691,15 +696,15 @@ sub fetch_mw_revisions_for_page {
                        push(@page_revs, $page_rev_ids);
                        $revnum++;
                }
-               last unless $result->{'query-continue'};
+               last if (!$result->{'query-continue'});
                $query->{rvstartid} = $result->{'query-continue'}->{revisions}->{rvstartid};
        }
        if ($shallow_import && @page_revs) {
-               print STDERR "  Found 1 revision (shallow import).\n";
+               print {*STDERR} "  Found 1 revision (shallow import).\n";
                @page_revs = sort {$b->{revid} <=> $a->{revid}} (@page_revs);
                return $page_revs[0];
        }
-       print STDERR "  Found ", $revnum, " revision(s).\n";
+       print {*STDERR} "  Found ${revnum} revision(s).\n";
        return @page_revs;
 }
 
@@ -711,8 +716,7 @@ sub fetch_mw_revisions {
        my $n = 1;
        foreach my $page (@pages) {
                my $id = $page->{pageid};
-
-               print STDERR "page $n/", scalar(@pages), ": ". $page->{title} ."\n";
+               print {*STDERR} "page ${n}/", scalar(@pages), ': ', $page->{title}, "\n";
                $n++;
                my @page_revs = fetch_mw_revisions_for_page($page, $id, $fetch_from);
                @revisions = (@page_revs, @revisions);
@@ -726,7 +730,7 @@ sub fe_escape_path {
     $path =~ s/\\/\\\\/g;
     $path =~ s/"/\\"/g;
     $path =~ s/\n/\\n/g;
-    return '"' . $path . '"';
+    return qq("${path}");
 }
 
 sub import_file_revision {
@@ -746,42 +750,43 @@ sub import_file_revision {
        my $author = $commit{author};
        my $date = $commit{date};
 
-       print STDOUT "commit refs/mediawiki/$remotename/master\n";
-       print STDOUT "mark :$n\n";
-       print STDOUT "committer $author <$author\@$wiki_name> ", $date->epoch, " +0000\n";
+       print {*STDOUT} "commit refs/mediawiki/${remotename}/master\n";
+       print {*STDOUT} "mark :${n}\n";
+       print {*STDOUT} "committer ${author} <${author}\@${wiki_name}> " . $date->epoch . " +0000\n";
        literal_data($comment);
 
        # If it's not a clone, we need to know where to start from
        if (!$full_import && $n == 1) {
-               print STDOUT "from refs/mediawiki/$remotename/master^0\n";
+               print {*STDOUT} "from refs/mediawiki/${remotename}/master^0\n";
        }
        if ($content ne DELETED_CONTENT) {
-               print STDOUT "M 644 inline " .
-                   fe_escape_path($title . ".mw") . "\n";
+               print {*STDOUT} 'M 644 inline ' .
+                   fe_escape_path("${title}.mw") . "\n";
                literal_data($content);
                if (%mediafile) {
-                       print STDOUT "M 644 inline "
+                       print {*STDOUT} 'M 644 inline '
                            . fe_escape_path($mediafile{title}) . "\n";
                        literal_data_raw($mediafile{content});
                }
-               print STDOUT "\n\n";
+               print {*STDOUT} "\n\n";
        } else {
-               print STDOUT "D " . fe_escape_path($title . ".mw") . "\n";
+               print {*STDOUT} 'D ' . fe_escape_path("${title}.mw") . "\n";
        }
 
        # mediawiki revision number in the git note
        if ($full_import && $n == 1) {
-               print STDOUT "reset refs/notes/$remotename/mediawiki\n";
+               print {*STDOUT} "reset refs/notes/${remotename}/mediawiki\n";
        }
-       print STDOUT "commit refs/notes/$remotename/mediawiki\n";
-       print STDOUT "committer $author <$author\@$wiki_name> ", $date->epoch, " +0000\n";
-       literal_data("Note added by git-mediawiki during import");
+       print {*STDOUT} "commit refs/notes/${remotename}/mediawiki\n";
+       print {*STDOUT} "committer ${author} <${author}\@${wiki_name}> " . $date->epoch . " +0000\n";
+       literal_data('Note added by git-mediawiki during import');
        if (!$full_import && $n == 1) {
-               print STDOUT "from refs/notes/$remotename/mediawiki^0\n";
+               print {*STDOUT} "from refs/notes/${remotename}/mediawiki^0\n";
        }
-       print STDOUT "N inline :$n\n";
-       literal_data("mediawiki_revision: " . $commit{mw_revision});
-       print STDOUT "\n\n";
+       print {*STDOUT} "N inline :${n}\n";
+       literal_data("mediawiki_revision: $commit{mw_revision}");
+       print {*STDOUT} "\n\n";
+       return;
 }
 
 # parse a sequence of
@@ -794,23 +799,25 @@ sub get_more_refs {
        my @refs;
        while (1) {
                my $line = <STDIN>;
-               if ($line =~ m/^$cmd (.*)$/) {
+               if ($line =~ /^$cmd (.*)$/) {
                        push(@refs, $1);
                } elsif ($line eq "\n") {
                        return @refs;
                } else {
-                       die("Invalid command in a '$cmd' batch: ". $_);
+                       die("Invalid command in a '$cmd' batch: $_\n");
                }
        }
+       return;
 }
 
 sub mw_import {
        # multiple import commands can follow each other.
-       my @refs = (shift, get_more_refs("import"));
+       my @refs = (shift, get_more_refs('import'));
        foreach my $ref (@refs) {
                mw_import_ref($ref);
        }
-       print STDOUT "done\n";
+       print {*STDOUT} "done\n";
+       return;
 }
 
 sub mw_import_ref {
@@ -820,40 +827,41 @@ sub mw_import_ref {
        # Since HEAD is a symbolic ref to master (by convention,
        # followed by the output of the command "list" that we gave),
        # we don't need to do anything in this case.
-       if ($ref eq "HEAD") {
+       if ($ref eq 'HEAD') {
                return;
        }
 
        mw_connect_maybe();
 
-       print STDERR "Searching revisions...\n";
+       print {*STDERR} "Searching revisions...\n";
        my $last_local = get_last_local_revision();
        my $fetch_from = $last_local + 1;
        if ($fetch_from == 1) {
-               print STDERR ", fetching from beginning.\n";
+               print {*STDERR} ", fetching from beginning.\n";
        } else {
-               print STDERR ", fetching from here.\n";
+               print {*STDERR} ", fetching from here.\n";
        }
 
        my $n = 0;
-       if ($fetch_strategy eq "by_rev") {
-               print STDERR "Fetching & writing export data by revs...\n";
+       if ($fetch_strategy eq 'by_rev') {
+               print {*STDERR} "Fetching & writing export data by revs...\n";
                $n = mw_import_ref_by_revs($fetch_from);
-       } elsif ($fetch_strategy eq "by_page") {
-               print STDERR "Fetching & writing export data by pages...\n";
+       } elsif ($fetch_strategy eq 'by_page') {
+               print {*STDERR} "Fetching & writing export data by pages...\n";
                $n = mw_import_ref_by_pages($fetch_from);
        } else {
-               print STDERR "fatal: invalid fetch strategy \"$fetch_strategy\".\n";
-               print STDERR "Check your configuration variables remote.$remotename.fetchStrategy and mediawiki.fetchStrategy\n";
+               print {*STDERR} qq(fatal: invalid fetch strategy "${fetch_strategy}".\n);
+               print {*STDERR} "Check your configuration variables remote.${remotename}.fetchStrategy and mediawiki.fetchStrategy\n";
                exit 1;
        }
 
        if ($fetch_from == 1 && $n == 0) {
-               print STDERR "You appear to have cloned an empty MediaWiki.\n";
+               print {*STDERR} "You appear to have cloned an empty MediaWiki.\n";
                # Something has to be done remote-helper side. If nothing is done, an error is
                # thrown saying that HEAD is referring to unknown object 0000000000000000000
                # and the clone fails.
        }
+       return;
 }
 
 sub mw_import_ref_by_pages {
@@ -865,7 +873,7 @@ sub mw_import_ref_by_pages {
        my ($n, @revisions) = fetch_mw_revisions(\@pages, $fetch_from);
 
        @revisions = sort {$a->{revid} <=> $b->{revid}} @revisions;
-       my @revision_ids = map $_->{revid}, @revisions;
+       my @revision_ids = map { $_->{revid} } @revisions;
 
        return mw_import_revids($fetch_from, \@revision_ids, \%pages_hash);
 }
@@ -892,7 +900,7 @@ sub mw_import_revids {
        my $n_actual = 0;
        my $last_timestamp = 0; # Placeholer in case $rev->timestamp is undefined
 
-       foreach my $pagerevid (@$revision_ids) {
+       foreach my $pagerevid (@{$revision_ids}) {
                # Count page even if we skip it, since we display
                # $n/$total and $total includes skipped pages.
                $n++;
@@ -908,7 +916,7 @@ sub mw_import_revids {
                my $result = $mediawiki->api($query);
 
                if (!$result) {
-                       die "Failed to retrieve modified page for revision $pagerevid";
+                       die "Failed to retrieve modified page for revision $pagerevid\n";
                }
 
                if (defined($result->{query}->{badrevids}->{$pagerevid})) {
@@ -917,7 +925,7 @@ sub mw_import_revids {
                }
 
                if (!defined($result->{query}->{pages})) {
-                       die "Invalid revision $pagerevid.";
+                       die "Invalid revision ${pagerevid}.\n";
                }
 
                my @result_pages = values(%{$result->{query}->{pages}});
@@ -927,8 +935,8 @@ sub mw_import_revids {
                my $page_title = $result_page->{title};
 
                if (!exists($pages->{$page_title})) {
-                       print STDERR "$n/", scalar(@$revision_ids),
-                               ": Skipping revision #$rev->{revid} of $page_title\n";
+                       print {*STDERR} "${n}/", scalar(@{$revision_ids}),
+                               ": Skipping revision #$rev->{revid} of ${page_title}\n";
                        next;
                }
 
@@ -953,14 +961,14 @@ sub mw_import_revids {
                my %mediafile;
                if ($namespace) {
                        my $id = get_mw_namespace_id($namespace);
-                       if ($id && $id == get_mw_namespace_id("File")) {
+                       if ($id && $id == get_mw_namespace_id('File')) {
                                %mediafile = get_mw_mediafile_for_page_revision($filename, $rev->{timestamp});
                        }
                }
                # If this is a revision of the media page for new version
                # of a file do one common commit for both file and media page.
                # Else do commit only for that page.
-               print STDERR "$n/", scalar(@$revision_ids), ": Revision #$rev->{revid} of $commit{title}\n";
+               print {*STDERR} "${n}/", scalar(@{$revision_ids}), ": Revision #$rev->{revid} of $commit{title}\n";
                import_file_revision(\%commit, ($fetch_from == 1), $n_actual, \%mediafile);
        }
 
@@ -968,17 +976,17 @@ sub mw_import_revids {
 }
 
 sub error_non_fast_forward {
-       my $advice = run_git("config --bool advice.pushNonFastForward");
+       my $advice = run_git('config --bool advice.pushNonFastForward');
        chomp($advice);
-       if ($advice ne "false") {
+       if ($advice ne 'false') {
                # Native git-push would show this after the summary.
                # We can't ask it to display it cleanly, so print it
                # ourselves before.
-               print STDERR "To prevent you from losing history, non-fast-forward updates were rejected\n";
-               print STDERR "Merge the remote changes (e.g. 'git pull') before pushing again. See the\n";
-               print STDERR "'Note about fast-forwards' section of 'git push --help' for details.\n";
+               print {*STDERR} "To prevent you from losing history, non-fast-forward updates were rejected\n";
+               print {*STDERR} "Merge the remote changes (e.g. 'git pull') before pushing again. See the\n";
+               print {*STDERR} "'Note about fast-forwards' section of 'git push --help' for details.\n";
        }
-       print STDOUT "error $_[0] \"non-fast-forward\"\n";
+       print {*STDOUT} qq(error $_[0] "non-fast-forward"\n);
        return 0;
 }
 
@@ -989,11 +997,11 @@ sub mw_upload_file {
        my $file_deleted = shift;
        my $summary = shift;
        my $newrevid;
-       my $path = "File:" . $complete_file_name;
+       my $path = "File:${complete_file_name}";
        my %hashFiles = get_allowed_file_extensions();
        if (!exists($hashFiles{$extension})) {
-               print STDERR "$complete_file_name is not a permitted file on this wiki.\n";
-               print STDERR "Check the configuration of file uploads in your mediawiki.\n";
+               print {*STDERR} "${complete_file_name} is not a permitted file on this wiki.\n";
+               print {*STDERR} "Check the configuration of file uploads in your mediawiki.\n";
                return $newrevid;
        }
        # Deleting and uploading a file requires a priviledged user
@@ -1005,18 +1013,18 @@ sub mw_upload_file {
                        reason => $summary
                };
                if (!$mediawiki->edit($query)) {
-                       print STDERR "Failed to delete file on remote wiki\n";
-                       print STDERR "Check your permissions on the remote site. Error code:\n";
-                       print STDERR $mediawiki->{error}->{code} . ':' . $mediawiki->{error}->{details};
+                       print {*STDERR} "Failed to delete file on remote wiki\n";
+                       print {*STDERR} "Check your permissions on the remote site. Error code:\n";
+                       print {*STDERR} $mediawiki->{error}->{code} . ':' . $mediawiki->{error}->{details};
                        exit 1;
                }
        } else {
                # Don't let perl try to interpret file content as UTF-8 => use "raw"
-               my $content = run_git("cat-file blob $new_sha1", "raw");
-               if ($content ne "") {
+               my $content = run_git("cat-file blob ${new_sha1}", 'raw');
+               if ($content ne EMPTY) {
                        mw_connect_maybe();
                        $mediawiki->{config}->{upload_url} =
-                               "$url/index.php/Special:Upload";
+                               "${url}/index.php/Special:Upload";
                        $mediawiki->edit({
                                action => 'upload',
                                filename => $complete_file_name,
@@ -1028,12 +1036,12 @@ sub mw_upload_file {
                        }, {
                                skip_encoding => 1
                        } ) || die $mediawiki->{error}->{code} . ':'
-                                . $mediawiki->{error}->{details};
+                                . $mediawiki->{error}->{details} . "\n";
                        my $last_file_page = $mediawiki->get_page({title => $path});
                        $newrevid = $last_file_page->{revid};
-                       print STDERR "Pushed file: $new_sha1 - $complete_file_name.\n";
+                       print {*STDERR} "Pushed file: ${new_sha1} - ${complete_file_name}.\n";
                } else {
-                       print STDERR "Empty file $complete_file_name not pushed.\n";
+                       print {*STDERR} "Empty file ${complete_file_name} not pushed.\n";
                }
        }
        return $newrevid;
@@ -1055,7 +1063,7 @@ sub mw_push_file {
        my $newrevid;
 
        if ($summary eq EMPTY_MESSAGE) {
-               $summary = '';
+               $summary = EMPTY;
        }
 
        my $new_sha1 = $diff_info_split[3];
@@ -1066,13 +1074,13 @@ sub mw_push_file {
 
        my ($title, $extension) = $complete_file_name =~ /^(.*)\.([^\.]*)$/;
        if (!defined($extension)) {
-               $extension = "";
+               $extension = EMPTY;
        }
-       if ($extension eq "mw") {
+       if ($extension eq 'mw') {
                my $ns = get_mw_namespace_id_for_page($complete_file_name);
-               if ($ns && $ns == get_mw_namespace_id("File") && (!$export_media)) {
-                       print STDERR "Ignoring media file related page: $complete_file_name\n";
-                       return ($oldrevid, "ok");
+               if ($ns && $ns == get_mw_namespace_id('File') && (!$export_media)) {
+                       print {*STDERR} "Ignoring media file related page: ${complete_file_name}\n";
+                       return ($oldrevid, 'ok');
                }
                my $file_content;
                if ($page_deleted) {
@@ -1082,7 +1090,7 @@ sub mw_push_file {
                        # with this content instead:
                        $file_content = DELETED_CONTENT;
                } else {
-                       $file_content = run_git("cat-file blob $new_sha1");
+                       $file_content = run_git("cat-file blob ${new_sha1}");
                }
 
                mw_connect_maybe();
@@ -1099,49 +1107,49 @@ sub mw_push_file {
                if (!$result) {
                        if ($mediawiki->{error}->{code} == 3) {
                                # edit conflicts, considered as non-fast-forward
-                               print STDERR 'Warning: Error ' .
+                               print {*STDERR} 'Warning: Error ' .
                                    $mediawiki->{error}->{code} .
-                                   ' from mediwiki: ' . $mediawiki->{error}->{details} .
+                                   ' from mediawiki: ' . $mediawiki->{error}->{details} .
                                    ".\n";
-                               return ($oldrevid, "non-fast-forward");
+                               return ($oldrevid, 'non-fast-forward');
                        } else {
                                # Other errors. Shouldn't happen => just die()
                                die 'Fatal: Error ' .
                                    $mediawiki->{error}->{code} .
-                                   ' from mediwiki: ' . $mediawiki->{error}->{details};
+                                   ' from mediawiki: ' . $mediawiki->{error}->{details} . "\n";
                        }
                }
                $newrevid = $result->{edit}->{newrevid};
-               print STDERR "Pushed file: $new_sha1 - $title\n";
+               print {*STDERR} "Pushed file: ${new_sha1} - ${title}\n";
        } elsif ($export_media) {
                $newrevid = mw_upload_file($complete_file_name, $new_sha1,
                                           $extension, $page_deleted,
                                           $summary);
        } else {
-               print STDERR "Ignoring media file $title\n";
+               print {*STDERR} "Ignoring media file ${title}\n";
        }
        $newrevid = ($newrevid or $oldrevid);
-       return ($newrevid, "ok");
+       return ($newrevid, 'ok');
 }
 
 sub mw_push {
        # multiple push statements can follow each other
-       my @refsspecs = (shift, get_more_refs("push"));
+       my @refsspecs = (shift, get_more_refs('push'));
        my $pushed;
        for my $refspec (@refsspecs) {
                my ($force, $local, $remote) = $refspec =~ /^(\+)?([^:]*):([^:]*)$/
-                   or die("Invalid refspec for push. Expected <src>:<dst> or +<src>:<dst>");
+                   or die("Invalid refspec for push. Expected <src>:<dst> or +<src>:<dst>\n");
                if ($force) {
-                       print STDERR "Warning: forced push not allowed on a MediaWiki.\n";
+                       print {*STDERR} "Warning: forced push not allowed on a MediaWiki.\n";
                }
-               if ($local eq "") {
-                       print STDERR "Cannot delete remote branch on a MediaWiki\n";
-                       print STDOUT "error $remote cannot delete\n";
+               if ($local eq EMPTY) {
+                       print {*STDERR} "Cannot delete remote branch on a MediaWiki\n";
+                       print {*STDOUT} "error ${remote} cannot delete\n";
                        next;
                }
-               if ($remote ne "refs/heads/master") {
-                       print STDERR "Only push to the branch 'master' is supported on a MediaWiki\n";
-                       print STDOUT "error $remote only master allowed\n";
+               if ($remote ne 'refs/heads/master') {
+                       print {*STDERR} "Only push to the branch 'master' is supported on a MediaWiki\n";
+                       print {*STDOUT} "error ${remote} only master allowed\n";
                        next;
                }
                if (mw_push_revision($local, $remote)) {
@@ -1150,30 +1158,32 @@ sub mw_push {
        }
 
        # Notify Git that the push is done
-       print STDOUT "\n";
+       print {*STDOUT} "\n";
 
        if ($pushed && $dumb_push) {
-               print STDERR "Just pushed some revisions to MediaWiki.\n";
-               print STDERR "The pushed revisions now have to be re-imported, and your current branch\n";
-               print STDERR "needs to be updated with these re-imported commits. You can do this with\n";
-               print STDERR "\n";
-               print STDERR "  git pull --rebase\n";
-               print STDERR "\n";
+               print {*STDERR} "Just pushed some revisions to MediaWiki.\n";
+               print {*STDERR} "The pushed revisions now have to be re-imported, and your current branch\n";
+               print {*STDERR} "needs to be updated with these re-imported commits. You can do this with\n";
+               print {*STDERR} "\n";
+               print {*STDERR} "  git pull --rebase\n";
+               print {*STDERR} "\n";
        }
+       return;
 }
 
 sub mw_push_revision {
        my $local = shift;
        my $remote = shift; # actually, this has to be "refs/heads/master" at this point.
        my $last_local_revid = get_last_local_revision();
-       print STDERR ".\n"; # Finish sentence started by get_last_local_revision()
+       print {*STDERR} ".\n"; # Finish sentence started by get_last_local_revision()
        my $last_remote_revid = get_last_remote_revision();
        my $mw_revision = $last_remote_revid;
 
        # Get sha1 of commit pointed by local HEAD
-       my $HEAD_sha1 = run_git("rev-parse $local 2>/dev/null"); chomp($HEAD_sha1);
+       my $HEAD_sha1 = run_git("rev-parse ${local} 2>/dev/null");
+       chomp($HEAD_sha1);
        # Get sha1 of commit pointed by remotes/$remotename/master
-       my $remoteorigin_sha1 = run_git("rev-parse refs/remotes/$remotename/master 2>/dev/null");
+       my $remoteorigin_sha1 = run_git("rev-parse refs/remotes/${remotename}/master 2>/dev/null");
        chomp($remoteorigin_sha1);
 
        if ($last_local_revid > 0 &&
@@ -1192,22 +1202,22 @@ sub mw_push_revision {
        if ($last_local_revid > 0) {
                my $parsed_sha1 = $remoteorigin_sha1;
                # Find a path from last MediaWiki commit to pushed commit
-               print STDERR "Computing path from local to remote ...\n";
-               my @local_ancestry = split(/\n/, run_git("rev-list --boundary --parents $local ^$parsed_sha1"));
+               print {*STDERR} "Computing path from local to remote ...\n";
+               my @local_ancestry = split(/\n/, run_git("rev-list --boundary --parents ${local} ^${parsed_sha1}"));
                my %local_ancestry;
                foreach my $line (@local_ancestry) {
-                       if (my ($child, $parents) = $line =~ m/^-?([a-f0-9]+) ([a-f0-9 ]+)/) {
-                               foreach my $parent (split(' ', $parents)) {
+                       if (my ($child, $parents) = $line =~ /^-?([a-f0-9]+) ([a-f0-9 ]+)/) {
+                               foreach my $parent (split(/ /, $parents)) {
                                        $local_ancestry{$parent} = $child;
                                }
-                       } elsif (!$line =~ m/^([a-f0-9]+)/) {
-                               die "Unexpected output from git rev-list: $line";
+                       } elsif (!$line =~ /^([a-f0-9]+)/) {
+                               die "Unexpected output from git rev-list: ${line}\n";
                        }
                }
                while ($parsed_sha1 ne $HEAD_sha1) {
                        my $child = $local_ancestry{$parsed_sha1};
                        if (!$child) {
-                               printf STDERR "Cannot find a path in history from remote commit to last commit\n";
+                               print {*STDERR} "Cannot find a path in history from remote commit to last commit\n";
                                return error_non_fast_forward($remote);
                        }
                        push(@commit_pairs, [$parsed_sha1, $child]);
@@ -1216,12 +1226,12 @@ sub mw_push_revision {
        } else {
                # No remote mediawiki revision. Export the whole
                # history (linearized with --first-parent)
-               print STDERR "Warning: no common ancestor, pushing complete history\n";
-               my $history = run_git("rev-list --first-parent --children $local");
-               my @history = split('\n', $history);
+               print {*STDERR} "Warning: no common ancestor, pushing complete history\n";
+               my $history = run_git("rev-list --first-parent --children ${local}");
+               my @history = split(/\n/, $history);
                @history = @history[1..$#history];
                foreach my $line (reverse @history) {
-                       my @commit_info_split = split(/ |\n/, $line);
+                       my @commit_info_split = split(/[ \n]/, $line);
                        push(@commit_pairs, \@commit_info_split);
                }
        }
@@ -1229,12 +1239,12 @@ sub mw_push_revision {
        foreach my $commit_info_split (@commit_pairs) {
                my $sha1_child = @{$commit_info_split}[0];
                my $sha1_commit = @{$commit_info_split}[1];
-               my $diff_infos = run_git("diff-tree -r --raw -z $sha1_child $sha1_commit");
+               my $diff_infos = run_git("diff-tree -r --raw -z ${sha1_child} ${sha1_commit}");
                # TODO: we could detect rename, and encode them with a #redirect on the wiki.
                # TODO: for now, it's just a delete+add
                my @diff_info_list = split(/\0/, $diff_infos);
                # Keep the subject line of the commit message as mediawiki comment for the revision
-               my $commit_msg = run_git("log --no-walk --format=\"%s\" $sha1_commit");
+               my $commit_msg = run_git(qq(log --no-walk --format="%s" ${sha1_commit}));
                chomp($commit_msg);
                # Push every blob
                while (@diff_info_list) {
@@ -1246,7 +1256,7 @@ sub mw_push_revision {
                        my $info = shift(@diff_info_list);
                        my $file = shift(@diff_info_list);
                        ($mw_revision, $status) = mw_push_file($info, $file, $commit_msg, $mw_revision);
-                       if ($status eq "non-fast-forward") {
+                       if ($status eq 'non-fast-forward') {
                                # we may already have sent part of the
                                # commit to MediaWiki, but it's too
                                # late to cancel it. Stop the push in
@@ -1254,17 +1264,17 @@ sub mw_push_revision {
                                # accurate error message.
                                return error_non_fast_forward($remote);
                        }
-                       if ($status ne "ok") {
-                               die("Unknown error from mw_push_file()");
+                       if ($status ne 'ok') {
+                               die("Unknown error from mw_push_file()\n");
                        }
                }
-               unless ($dumb_push) {
-                       run_git("notes --ref=$remotename/mediawiki add -f -m \"mediawiki_revision: $mw_revision\" $sha1_commit");
-                       run_git("update-ref -m \"Git-MediaWiki push\" refs/mediawiki/$remotename/master $sha1_commit $sha1_child");
+               if (!$dumb_push) {
+                       run_git(qq(notes --ref=${remotename}/mediawiki add -f -m "mediawiki_revision: ${mw_revision}" ${sha1_commit}));
+                       run_git(qq(update-ref -m "Git-MediaWiki push" refs/mediawiki/${remotename}/master ${sha1_commit} ${sha1_child}));
                }
        }
 
-       print STDOUT "ok $remote\n";
+       print {*STDOUT} "ok ${remote}\n";
        return 1;
 }
 
@@ -1277,8 +1287,8 @@ sub get_allowed_file_extensions {
                siprop => 'fileextensions'
                };
        my $result = $mediawiki->api($query);
-       my @file_extensions= map $_->{ext},@{$result->{query}->{fileextensions}};
-       my %hashFile = map {$_ => 1}@file_extensions;
+       my @file_extensions = map { $_->{ext}} @{$result->{query}->{fileextensions}};
+       my %hashFile = map { $_ => 1 } @file_extensions;
 
        return %hashFile;
 }
@@ -1300,8 +1310,8 @@ sub get_mw_namespace_id {
                # Look at configuration file, if the record for that namespace is
                # already cached. Namespaces are stored in form:
                # "Name_of_namespace:Id_namespace", ex.: "File:6".
-               my @temp = split(/[\n]/, run_git("config --get-all remote."
-                                               . $remotename .".namespaceCache"));
+               my @temp = split(/\n/,
+                                run_git("config --get-all remote.${remotename}.namespaceCache"));
                chomp(@temp);
                foreach my $ns (@temp) {
                        my ($n, $id) = split(/:/, $ns);
@@ -1315,7 +1325,7 @@ sub get_mw_namespace_id {
        }
 
        if (!exists $namespace_id{$name}) {
-               print STDERR "Namespace $name not found in cache, querying the wiki ...\n";
+               print {*STDERR} "Namespace ${name} not found in cache, querying the wiki ...\n";
                # NS not found => get namespace id from MW and store it in
                # configuration file.
                my $query = {
@@ -1339,8 +1349,8 @@ sub get_mw_namespace_id {
        my $ns = $namespace_id{$name};
        my $id;
 
-       unless (defined $ns) {
-               print STDERR "No such namespace $name on MediaWiki.\n";
+       if (!defined $ns) {
+               print {*STDERR} "No such namespace ${name} on MediaWiki.\n";
                $ns = {is_namespace => 0};
                $namespace_id{$name} = $ns;
        }
@@ -1354,15 +1364,15 @@ sub get_mw_namespace_id {
 
        # Store explicitely requested namespaces on disk
        if (!exists $cached_mw_namespace_id{$name}) {
-               run_git("config --add remote.". $remotename
-                       .".namespaceCache \"". $name .":". $store_id ."\"");
+               run_git(qq(config --add remote.${remotename}.namespaceCache "${name}:${store_id}"));
                $cached_mw_namespace_id{$name} = 1;
        }
        return $id;
 }
 
 sub get_mw_namespace_id_for_page {
-       if (my ($namespace) = $_[0] =~ /^([^:]*):/) {
+       my $namespace = shift;
+       if ($namespace =~ /^([^:]*):/) {
                return get_mw_namespace_id($namespace);
        } else {
                return;
index 3b2cfacf512b0476c157e475b3ad6ea15cadeb2b..bb76cee3798ab2b4670b6fcc57a94a3199a9f528 100755 (executable)
@@ -336,20 +336,21 @@ wiki_install () {
        fi
 
        # Fetch MediaWiki's archive if not already present in the TMP directory
+       MW_FILENAME="mediawiki-$MW_VERSION_MAJOR.$MW_VERSION_MINOR.tar.gz"
        cd "$TMP"
-       if [ ! -f "$MW_VERSION.tar.gz" ] ; then
-               echo "Downloading $MW_VERSION sources ..."
-               wget "http://download.wikimedia.org/mediawiki/1.19/mediawiki-1.19.0.tar.gz" ||
+       if [ ! -f $MW_FILENAME ] ; then
+               echo "Downloading $MW_VERSION_MAJOR.$MW_VERSION_MINOR sources ..."
+               wget "http://download.wikimedia.org/mediawiki/$MW_VERSION_MAJOR/$MW_FILENAME" ||
                        error "Unable to download "\
-                       "http://download.wikimedia.org/mediawiki/1.19/"\
-                       "mediawiki-1.19.0.tar.gz. "\
+                       "http://download.wikimedia.org/mediawiki/$MW_VERSION_MAJOR/"\
+                       "$MW_FILENAME. "\
                        "Please fix your connection and launch the script again."
-               echo "$MW_VERSION.tar.gz downloaded in `pwd`. "\
+               echo "$MW_FILENAME downloaded in `pwd`. "\
                        "You can delete it later if you want."
        else
-               echo "Reusing existing $MW_VERSION.tar.gz downloaded in `pwd`."
+               echo "Reusing existing $MW_FILENAME downloaded in `pwd`."
        fi
-       archive_abs_path=$(pwd)/"$MW_VERSION.tar.gz"
+       archive_abs_path=$(pwd)/$MW_FILENAME
        cd "$WIKI_DIR_INST/$WIKI_DIR_NAME/" ||
                error "can't cd to $WIKI_DIR_INST/$WIKI_DIR_NAME/"
        tar xzf "$archive_abs_path" --strip-components=1 ||
@@ -431,5 +432,5 @@ wiki_delete () {
        # Delete the wiki's SQLite database
        rm -f "$TMP/$DB_FILE" || error "Database $TMP/$DB_FILE could not be deleted."
        rm -f "$FILES_FOLDER/$DB_FILE"
-       rm -rf "$TMP/$MW_VERSION"
+       rm -rf "$TMP/mediawiki-$MW_VERSION_MAJOR.$MW_VERSION_MINOR.tar.gz"
 }
index 958b37b4a701b9fada02eaa6abdeb32e3591a436..4cfebe9c69bda210ba8de6882a3ed269415de559 100644 (file)
@@ -30,6 +30,8 @@ WEB_WWW=$WEB/www
 
 # The variables below are used by the script to install a wiki.
 # You should not modify these unless you are modifying the script itself.
-MW_VERSION=mediawiki-1.19.0
+# tested versions: 1.19.X -> 1.21.1
+MW_VERSION_MAJOR=1.21
+MW_VERSION_MINOR=1
 FILES_FOLDER=install-wiki
 DB_INSTALL_SCRIPT=db_install.php
diff --git a/contrib/patches/docbook-xsl-manpages-charmap.patch b/contrib/patches/docbook-xsl-manpages-charmap.patch
deleted file mode 100644 (file)
index f2b08b4..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-From: Ismail Dönmez <ismail@pardus.org.tr>
-
-Trying to build the documentation with docbook-xsl 1.73 may result in
-the following error.  This patch fixes it.
-
-$ xmlto -m callouts.xsl man git-add.xml
-runtime error: file
-file:///usr/share/sgml/docbook/xsl-stylesheets-1.73.0/manpages/other.xsl line
-129 element call-template
-The called template 'read-character-map' was not found.
-
---- docbook-xsl-1.73.0/manpages/docbook.xsl.manpages-charmap   2007-07-23 16:24:23.000000000 +0100
-+++ docbook-xsl-1.73.0/manpages/docbook.xsl    2007-07-23 16:25:16.000000000 +0100
-@@ -37,6 +37,7 @@
-   <xsl:include href="lists.xsl"/>
-   <xsl:include href="endnotes.xsl"/>
-   <xsl:include href="table.xsl"/>
-+  <xsl:include href="../common/charmap.xsl"/>
-
-   <!-- * we rename the following just to avoid using params with "man" -->
-   <!-- * prefixes in the table.xsl stylesheet (because that stylesheet -->
index 2f8a63e38881587fe29fcb72a5272ef54b9efa6e..7cb5d29a89a43ecf7e433db513b66184df3accb8 100644 (file)
@@ -49,7 +49,7 @@ static void grow_decoration(struct decoration *n)
                const struct object *base = old_hash[i].base;
                void *decoration = old_hash[i].decoration;
 
-               if (!base)
+               if (!decoration)
                        continue;
                insert_decoration(n, base, decoration);
        }
index e2e75c16602d8e5841c2461defaba7b9866115d8..0cb67b22cf5a8754daf429b3dbe45ef3e663650b 100644 (file)
@@ -37,7 +37,6 @@ size_t packed_git_window_size = DEFAULT_PACKED_GIT_WINDOW_SIZE;
 size_t packed_git_limit = DEFAULT_PACKED_GIT_LIMIT;
 size_t delta_base_cache_limit = 16 * 1024 * 1024;
 unsigned long big_file_threshold = 512 * 1024 * 1024;
-const char *log_pack_access;
 const char *pager_program;
 int pager_use_color = 1;
 const char *editor_program;
index d2c4ce6e1e88f543bd06cd8347e8949fef11a381..75a991f7ec8a2e4926cb925ad9ff23be2f6aedeb 100755 (executable)
@@ -44,6 +44,8 @@
 
 my $normal_color = $repo->get_color("", "reset");
 
+my $diff_algorithm = $repo->config('diff.algorithm');
+
 my $use_readkey = 0;
 my $use_termcap = 0;
 my %term_escapes;
@@ -731,6 +733,9 @@ sub run_git_apply {
 sub parse_diff {
        my ($path) = @_;
        my @diff_cmd = split(" ", $patch_mode_flavour{DIFF});
+       if (defined $diff_algorithm) {
+               splice @diff_cmd, 1, 0, "--diff-algorithm=${diff_algorithm}";
+       }
        if (defined $patch_mode_revision) {
                push @diff_cmd, $patch_mode_revision;
        }
index 1cf3d1dacfb2084e9fcc360a6296900b6f9a7d03..9f4450916cd28578002d04a45b4e28e09cca738c 100755 (executable)
--- a/git-am.sh
+++ b/git-am.sh
@@ -506,6 +506,23 @@ then
        esac
        rm -f "$dotest/dirtyindex"
 else
+       # Possible stray $dotest directory in the independent-run
+       # case; in the --rebasing case, it is upto the caller
+       # (git-rebase--am) to take care of stray directories.
+       if test -d "$dotest" && test -z "$rebasing"
+       then
+               case "$skip,$resolved,$abort" in
+               ,,t)
+                       rm -fr "$dotest"
+                       exit 0
+                       ;;
+               *)
+                       die "$(eval_gettext "Stray \$dotest directory found.
+Use \"git am --abort\" to remove it.")"
+                       ;;
+               esac
+       fi
+
        # Make sure we are not given --skip, --resolved, nor --abort
        test "$skip$resolved$abort" = "" ||
                die "$(gettext "Resolve operation not in progress, we are not resuming.")"
index 49b62ea6ebc61c4af16d7d740e5b423e5787b915..0a1f8d4f8a9187a0d5decf20f9c926dbd570a8db 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=0.17.GITGUI
+DEF_VER=0.18.GITGUI
 
 LF='
 '
index e22ba5c321dc47fdf512f30c5e6c741c41fb99eb..e9c2bc347dc77fb08bc6515a604a744fc4ee5795 100644 (file)
@@ -254,7 +254,7 @@ lib/tclIndex: $(ALL_LIBFILES) GIT-GUI-VARS
          auto_mkindex lib '*.tcl' \
        | $(TCL_PATH) $(QUIET_2DEVNULL); then : ok; \
        else \
-        echo 1>&2 "    * $(TCL_PATH) failed; using unoptimized loading"; \
+        echo >&2 "    * $(TCL_PATH) failed; using unoptimized loading"; \
         rm -f $@ ; \
         echo '# Autogenerated by git-gui Makefile' >$@ && \
         echo >>$@ && \
@@ -274,8 +274,8 @@ TRACK_VARS = \
 GIT-GUI-VARS: FORCE
        @VARS='$(TRACK_VARS)'; \
        if test x"$$VARS" != x"`cat $@ 2>/dev/null`" ; then \
-               echo 1>&2 "    * new locations or Tcl/Tk interpreter"; \
-               echo 1>$@ "$$VARS"; \
+               echo >&2 "    * new locations or Tcl/Tk interpreter"; \
+               echo >$@ "$$VARS"; \
        fi
 
 ifdef GITGUI_MACOSXAPP
index 89f636f4963cf7933639f6a23b3be2e8fd4c0964..153f85da068294917f5e60afca4fb6715f3f32ab 100755 (executable)
@@ -135,6 +135,20 @@ proc strcat {args} {
 ::msgcat::mcload $oguimsg
 unset oguimsg
 
+######################################################################
+##
+## On Mac, bring the current Wish process window to front
+
+if {[tk windowingsystem] eq "aqua"} {
+       catch {
+               exec osascript -e [format {
+                       tell application "System Events"
+                               set frontmost of processes whose unix id is %d to true
+                       end tell
+               } [pid]]
+       }
+}
+
 ######################################################################
 ##
 ## read only globals
@@ -3003,18 +3017,11 @@ blame {
        set jump_spec {}
        set is_path 0
        foreach a $argv {
-               if {[file exists $a]} {
-                       if {$path ne {}} usage
-                       set path [normalize_relpath $a]
-                       break
-               } elseif {[file exists $_prefix$a]} {
-                       if {$path ne {}} usage
-                       set path [normalize_relpath $_prefix$a]
-                       break
-               }
+               set p [file join $_prefix $a]
 
-               if {$is_path} {
+               if {$is_path || [file exists $p]} {
                        if {$path ne {}} usage
+                       set path [normalize_relpath $p]
                        break
                } elseif {$a eq {--}} {
                        if {$path ne {}} {
index 657f7d5dc19b13f36a31b833f04407b8ca0b2901..ee58981f539bfd27dbba99db47d6f85102b0e7a6 100644 (file)
@@ -286,7 +286,9 @@ method _next {action} {
        destroy $w_body
        if {![winfo exists $w_next]} {
                ${NS}::button $w_next -default active
-               pack $w_next -side right -padx 5 -before $w_quit
+               set pos -before
+               if {[tk windowingsystem] eq "win32"} { set pos -after }
+               pack $w_next -side right -padx 5 $pos $w_quit
        }
        _do_$action $this
 }
index ec4405567a9b86a9bfd8bf50815b3c9c14e2df40..30d9a797769b49cdd9d42fba925d8fa8ecbffb21 100644 (file)
@@ -764,8 +764,15 @@ proc apply_range_or_line {x y} {
                                # context line
                                set ln [$ui_diff get $i_l $next_l]
                                set patch "$patch$pre_context$ln"
-                               set n [expr $n+1]
-                               set m [expr $m+1]
+                               # Skip the "\ No newline at end of
+                               # file". Depending on the locale setting
+                               # we don't know what this line looks
+                               # like exactly. The only thing we do
+                               # know is that it starts with "\ "
+                               if {![string match {\\ *} $ln]} {
+                                       set n [expr $n+1]
+                                       set m [expr $m+1]
+                               }
                                set pre_context {}
                        } elseif {$c1 eq $to_context} {
                                # turn change line into context line
index 3c8e73bcebea652c11e328169f63a6d9950d6dc2..120bc4064b6f88023fb067a7aedaa67e13802f2c 100644 (file)
@@ -189,9 +189,9 @@ proc merge_resolve_tool2 {} {
        }
        bc3 {
                if {$base_stage ne {}} {
-                       set cmdline [list "$merge_tool_path" "$LOCAL" "$REMOTE" "$BASE" -mergeoutput="$MERGED"]
+                       set cmdline [list "$merge_tool_path" "$LOCAL" "$REMOTE" "$BASE" "-mergeoutput=$MERGED"]
                } else {
-                       set cmdline [list "$merge_tool_path" "$LOCAL" "$REMOTE" -mergeoutput="$MERGED"]
+                       set cmdline [list "$merge_tool_path" "$LOCAL" "$REMOTE" "-mergeoutput=$MERGED"]
                }
        }
        ecmerge {
index 5e4e7f4c83952ac2ec4c60eb79426248071ae54e..4e5c7844188ee2eab0c9f6e00835b99c69bbdfc2 100644 (file)
@@ -245,7 +245,8 @@ proc update_all_remotes_menu_entry {} {
        set prune_m $remote_m.prune
        if {$have_remote > 1} {
                make_sure_remote_submenues_exist $remote_m
-               if {[$fetch_m entrycget end -label] ne "All"} {
+               if {[$fetch_m type end] eq "command" \
+                       && [$fetch_m entrycget end -label] ne "All"} {
 
                        $fetch_m insert end separator
                        $fetch_m insert end command \
@@ -259,7 +260,8 @@ proc update_all_remotes_menu_entry {} {
                }
        } else {
                if {[winfo exists $fetch_m]} {
-                       if {[$fetch_m entrycget end -label] eq "All"} {
+                       if {[$fetch_m type end] eq "command" \
+                               && [$fetch_m entrycget end -label] eq "All"} {
 
                                delete_from_menu $fetch_m end
                                delete_from_menu $fetch_m end
index 40441dbb0da4fe5a62776054aa2d5d2d30d9af8f..0aff18691d168d7826995d5288e37d8421009141 100644 (file)
@@ -1139,7 +1139,7 @@ msgstr "Standard (rapide, semi-redondant, liens durs)"
 
 #: lib/choose_repository.tcl:514
 msgid "Full Copy (Slower, Redundant Backup)"
-msgstr "Copy complète (plus lent, sauvegarde redondante)"
+msgstr "Copie complète (plus lent, sauvegarde redondante)"
 
 #: lib/choose_repository.tcl:520
 msgid "Shared (Fastest, Not Recommended, No Backup)"
index e338be5e57e32f52cfa818fd44a6364c00c24cdd..6a721064c288609fde50fc84e963519ceb00723e 100644 (file)
@@ -114,6 +114,33 @@ valid_tool () {
        test -n "$cmd"
 }
 
+setup_user_tool () {
+       merge_tool_cmd=$(get_merge_tool_cmd "$tool")
+       test -n "$merge_tool_cmd" || return 1
+
+       diff_cmd () {
+               ( eval $merge_tool_cmd )
+               status=$?
+               return $status
+       }
+
+       merge_cmd () {
+               trust_exit_code=$(git config --bool \
+                       "mergetool.$1.trustExitCode" || echo false)
+               if test "$trust_exit_code" = "false"
+               then
+                       touch "$BACKUP"
+                       ( eval $merge_tool_cmd )
+                       status=$?
+                       check_unchanged
+               else
+                       ( eval $merge_tool_cmd )
+                       status=$?
+               fi
+               return $status
+       }
+}
+
 setup_tool () {
        tool="$1"
 
@@ -142,15 +169,15 @@ setup_tool () {
 
        if ! test -f "$MERGE_TOOLS_DIR/$tool"
        then
-               # Use a special return code for this case since we want to
-               # source "defaults" even when an explicit tool path is
-               # configured since the user can use that to override the
-               # default path in the scriptlet.
-               return 2
+               setup_user_tool
+               return $?
        fi
 
        # Load the redefined functions
        . "$MERGE_TOOLS_DIR/$tool"
+       # Now let the user override the default command for the tool.  If
+       # they have not done so then this will return 1 which we ignore.
+       setup_user_tool
 
        if merge_mode && ! can_merge
        then
@@ -187,20 +214,7 @@ run_merge_tool () {
        status=0
 
        # Bring tool-specific functions into scope
-       setup_tool "$1"
-       exitcode=$?
-       case $exitcode in
-       0)
-               :
-               ;;
-       2)
-               # The configured tool is not a built-in tool.
-               test -n "$merge_tool_path" || return 1
-               ;;
-       *)
-               return $exitcode
-               ;;
-       esac
+       setup_tool "$1" || return 1
 
        if merge_mode
        then
@@ -213,38 +227,12 @@ run_merge_tool () {
 
 # Run a either a configured or built-in diff tool
 run_diff_cmd () {
-       merge_tool_cmd=$(get_merge_tool_cmd "$1")
-       if test -n "$merge_tool_cmd"
-       then
-               ( eval $merge_tool_cmd )
-               status=$?
-               return $status
-       else
-               diff_cmd "$1"
-       fi
+       diff_cmd "$1"
 }
 
 # Run a either a configured or built-in merge tool
 run_merge_cmd () {
-       merge_tool_cmd=$(get_merge_tool_cmd "$1")
-       if test -n "$merge_tool_cmd"
-       then
-               trust_exit_code=$(git config --bool \
-                       "mergetool.$1.trustExitCode" || echo false)
-               if test "$trust_exit_code" = "false"
-               then
-                       touch "$BACKUP"
-                       ( eval $merge_tool_cmd )
-                       status=$?
-                       check_unchanged
-               else
-                       ( eval $merge_tool_cmd )
-                       status=$?
-               fi
-               return $status
-       else
-               merge_cmd "$1"
-       fi
+       merge_cmd "$1"
 }
 
 list_merge_tool_candidates () {
index 911bbce6c5d4e19243e50d5916005872ac672961..88fcf232e5739b14a60f8b2c2a45add4a48e764b 100755 (executable)
--- a/git-p4.py
+++ b/git-p4.py
@@ -3168,7 +3168,7 @@ def rebase(self):
         if os.system("git update-index --refresh") != 0:
             die("Some files in your working directory are modified and different than what is in your index. You can use git update-index <filename> to bring the index up-to-date or stash away all your changes with git stash.");
         if len(read_pipe("git diff-index HEAD --")) > 0:
-            die("You have uncommited changes. Please commit them before rebasing or stash them away with git stash.");
+            die("You have uncommitted changes. Please commit them before rebasing or stash them away with git stash.");
 
         [upstream, settings] = findUpstreamBranchPoint()
         if len(upstream) == 0:
index 638aabb7b347e2afeb9bf327902de9e3702cd9d4..6828e2c7157fd80905e864b475c11ccc7a330e03 100755 (executable)
@@ -266,10 +266,17 @@ case "$merge_head" in
        ;;
 esac
 
+# Pulling into unborn branch: a shorthand for branching off
+# FETCH_HEAD, for lazy typers.
 if test -z "$orig_head"
 then
-       git update-ref -m "initial pull" HEAD $merge_head "$curr_head" &&
-       git read-tree -m -u HEAD || exit 1
+       # Two-way merge: we claim the index is based on an empty tree,
+       # and try to fast-forward to HEAD.  This ensures we will not
+       # lose index/worktree changes that the user already made on
+       # the unborn branch.
+       empty_tree=4b825dc642cb6eb9a060e54bf8d69288fbee4904
+       git read-tree -m -u $empty_tree $merge_head &&
+       git update-ref -m "initial pull" HEAD $merge_head "$curr_head"
        exit
 fi
 
index d0c11a910a69603ac19c83a5d29af9f32f780267..81b0346a5da2386c94f72ac777f11887fef7fa70 100755 (executable)
@@ -84,6 +84,8 @@ keep_empty=
 test "$(git config --bool rebase.autosquash)" = "true" && autosquash=t
 
 read_basic_state () {
+       test -f "$state_dir/head-name" &&
+       test -f "$state_dir/onto" &&
        head_name=$(cat "$state_dir"/head-name) &&
        onto=$(cat "$state_dir"/onto) &&
        # We always write to orig-head, but interactive rebase used to write to
@@ -153,11 +155,8 @@ finish_rebase () {
                then
                        echo "$(gettext 'Applied autostash.')"
                else
-                       ref_stash=refs/stash &&
-                       >>"$GIT_DIR/logs/$ref_stash" &&
-                       git update-ref -m "autostash" $ref_stash $stash_sha1 ||
-                       die "$(eval_gettext 'Cannot store $stash_sha1')"
-
+                       git stash store -m "autostash" -q $stash_sha1 ||
+                       die "$(eval_gettext "Cannot store \$stash_sha1")"
                        gettext 'Applying autostash resulted in conflicts.
 Your changes are safe in the stash.
 You can run "git stash pop" or "git stash drop" it at any time.
@@ -434,7 +433,7 @@ then
                shift
                ;;
        esac
-       upstream=`git rev-parse --verify "${upstream_name}^0"` ||
+       upstream=$(peel_committish "${upstream_name}") ||
        die "$(eval_gettext "invalid upstream \$upstream_name")"
        upstream_arg="$upstream_name"
 else
@@ -470,7 +469,7 @@ case "$onto_name" in
        fi
        ;;
 *)
-       onto=$(git rev-parse --verify "${onto_name}^0") ||
+       onto=$(peel_committish "$onto_name") ||
        die "$(eval_gettext "Does not point to a valid commit: \$onto_name")"
        ;;
 esac
@@ -545,6 +544,7 @@ then
                # Lazily switch to the target branch if needed...
                test -z "$switch_to" || git checkout "$switch_to" --
                say "$(eval_gettext "Current branch \$branch_name is up to date.")"
+               finish_rebase
                exit 0
        else
                say "$(eval_gettext "Current branch \$branch_name is up to date, rebase forced.")"
@@ -577,6 +577,7 @@ if test "$mb" = "$orig_head"
 then
        say "$(eval_gettext "Fast-forwarded \$branch_name to \$onto_name.")"
        move_to_original_branch
+       finish_rebase
        exit 0
 fi
 
index ec1d6ce4ebfd4810d46c727e99af9a1b54074ec4..ecbf56f693f7049fd87267ec1830f9020e60e720 100755 (executable)
@@ -745,6 +745,11 @@ sub file_declares_8bit_cte {
        $sender = $repoauthor || $repocommitter || '';
 }
 
+# $sender could be an already sanitized address
+# (e.g. sendemail.from could be manually sanitized by user).
+# But it's a no-op to run sanitize_address on an already sanitized address.
+$sender = sanitize_address($sender);
+
 my $prompting = 0;
 if (!@initial_to && !defined $to_cmd) {
        my $to = ask("Who should the emails be sent to (if any)? ",
@@ -1098,10 +1103,9 @@ sub send_message {
        if ($cc ne '') {
                $ccline = "\nCc: $cc";
        }
-       my $sanitized_sender = sanitize_address($sender);
        make_message_id() unless defined($message_id);
 
-       my $header = "From: $sanitized_sender
+       my $header = "From: $sender
 To: $to${ccline}
 Subject: $subject
 Date: $date
@@ -1118,7 +1122,7 @@ sub send_message {
        }
 
        my @sendmail_parameters = ('-i', @recipients);
-       my $raw_from = $sanitized_sender;
+       my $raw_from = $sender;
        if (defined $envelope_sender && $envelope_sender ne "auto") {
                $raw_from = $envelope_sender;
        }
@@ -1255,6 +1259,7 @@ sub send_message {
        open my $fh, "<", $t or die "can't open file $t";
 
        my $author = undef;
+       my $sauthor = undef;
        my $author_encoding;
        my $has_content_type;
        my $body_encoding;
@@ -1293,8 +1298,9 @@ sub send_message {
                        }
                        elsif (/^From:\s+(.*)$/i) {
                                ($author, $author_encoding) = unquote_rfc2047($1);
+                               $sauthor = sanitize_address($author);
                                next if $suppress_cc{'author'};
-                               next if $suppress_cc{'self'} and $author eq $sender;
+                               next if $suppress_cc{'self'} and $sauthor eq $sender;
                                printf("(mbox) Adding cc: %s from line '%s'\n",
                                        $1, $_) unless $quiet;
                                push @cc, $1;
@@ -1308,7 +1314,9 @@ sub send_message {
                        }
                        elsif (/^Cc:\s+(.*)$/i) {
                                foreach my $addr (parse_address_line($1)) {
-                                       if (unquote_rfc2047($addr) eq $sender) {
+                                       my $qaddr = unquote_rfc2047($addr);
+                                       my $saddr = sanitize_address($qaddr);
+                                       if ($saddr eq $sender) {
                                                next if ($suppress_cc{'self'});
                                        } else {
                                                next if ($suppress_cc{'cc'});
@@ -1355,7 +1363,8 @@ sub send_message {
                        chomp;
                        my ($what, $c) = ($1, $2);
                        chomp $c;
-                       if ($c eq $sender) {
+                       my $sc = sanitize_address($c);
+                       if ($sc eq $sender) {
                                next if ($suppress_cc{'self'});
                        } else {
                                next if $suppress_cc{'sob'} and $what =~ /Signed-off-by/i;
@@ -1385,7 +1394,7 @@ sub send_message {
                $subject = quote_subject($subject, $auto_8bit_encoding);
        }
 
-       if (defined $author and $author ne $sender) {
+       if (defined $sauthor and $sauthor ne $sender) {
                $message = "From: $author\n\n$message";
                if (defined $author_encoding) {
                        if ($has_content_type) {
@@ -1439,7 +1448,6 @@ sub send_message {
 sub recipients_cmd {
        my ($prefix, $what, $cmd, $file) = @_;
 
-       my $sanitized_sender = sanitize_address($sender);
        my @addresses = ();
        open my $fh, "-|", "$cmd \Q$file\E"
            or die "($prefix) Could not execute '$cmd'";
@@ -1447,7 +1455,7 @@ sub recipients_cmd {
                $address =~ s/^\s*//g;
                $address =~ s/\s*$//g;
                $address = sanitize_address($address);
-               next if ($address eq $sanitized_sender and $suppress_from);
+               next if ($address eq $sender and $suppress_cc{'self'});
                push @addresses, $address;
                printf("($prefix) Adding %s: %s from: '%s'\n",
                       $what, $address, $cmd) unless $quiet;
index 2f7835941ecaa48be17b6dd64e64fb0df3db37e2..7a964ad2ff70a8746c52ab30d945ebff95246edd 100644 (file)
@@ -313,3 +313,15 @@ then
        }
        : ${GIT_OBJECT_DIRECTORY="$GIT_DIR/objects"}
 fi
+
+peel_committish () {
+       case "$1" in
+       :/*)
+               peeltmp=$(git rev-parse --verify "$1") &&
+               git rev-parse --verify "${peeltmp}^0"
+               ;;
+       *)
+               git rev-parse --verify "${1}^0"
+               ;;
+       esac
+}
index bbefdf64244063f4708f381b8a772466d64e90e5..1e541a21257c70fa67c0fee552ccb05fc5b6a280 100755 (executable)
@@ -156,6 +156,41 @@ create_stash () {
        die "$(gettext "Cannot record working tree state")"
 }
 
+store_stash () {
+       while test $# != 0
+       do
+               case "$1" in
+               -m|--message)
+                       shift
+                       stash_msg="$1"
+                       ;;
+               -q|--quiet)
+                       quiet=t
+                       ;;
+               *)
+                       break
+                       ;;
+               esac
+               shift
+       done
+       test $# = 1 ||
+       die "$(eval_gettext "\"$dashless store\" requires one <commit> argument")"
+
+       w_commit="$1"
+       if test -z "$stash_msg"
+       then
+               stash_msg="Created via \"git stash store\"."
+       fi
+
+       # Make sure the reflog for stash is kept.
+       : >>"$GIT_DIR/logs/$ref_stash"
+       git update-ref -m "$stash_msg" $ref_stash $w_commit
+       ret=$?
+       test $ret != 0 && test -z $quiet &&
+       die "$(eval_gettext "Cannot update \$ref_stash with \$w_commit")"
+       return $ret
+}
+
 save_stash () {
        keep_index=
        patch_mode=
@@ -227,12 +262,8 @@ save_stash () {
                clear_stash || die "$(gettext "Cannot initialize stash")"
 
        create_stash "$stash_msg" $untracked
-
-       # Make sure the reflog for stash is kept.
-       : >>"$GIT_DIR/logs/$ref_stash"
-
-       git update-ref -m "$stash_msg" $ref_stash $w_commit ||
-               die "$(gettext "Cannot save the current status")"
+       store_stash -m "$stash_msg" -q $w_commit ||
+       die "$(gettext "Cannot save the current status")"
        say Saved working directory and index state "$stash_msg"
 
        if test -z "$patch_mode"
@@ -546,12 +577,13 @@ clear)
        clear_stash "$@"
        ;;
 create)
-       if test $# -gt 0 && test "$1" = create
-       then
-               shift
-       fi
+       shift
        create_stash "$*" && echo "$w_commit"
        ;;
+store)
+       shift
+       store_stash "$@"
+       ;;
 drop)
        shift
        drop_stash "$@"
index 79bfaac9d4cb9a04e5e1fd7675d740cf9fc27e87..945e296d304a1d1f108571946a6d50fbf238302f 100755 (executable)
@@ -14,10 +14,13 @@ USAGE="[--quiet] add [-b <branch>] [-f|--force] [--name <name>] [--reference <re
    or: $dashless [--quiet] foreach [--recursive] <command>
    or: $dashless [--quiet] sync [--recursive] [--] [<path>...]"
 OPTIONS_SPEC=
+SUBDIRECTORY_OK=Yes
 . git-sh-setup
 . git-sh-i18n
 . git-parse-remote
 require_work_tree
+wt_prefix=$(git rev-parse --show-prefix)
+cd_to_toplevel
 
 command=
 branch=
@@ -106,14 +109,50 @@ resolve_relative_url ()
        echo "${is_relative:+${up_path}}${remoteurl#./}"
 }
 
+# Resolve a path to be relative to another path.  This is intended for
+# converting submodule paths when git-submodule is run in a subdirectory
+# and only handles paths where the directory separator is '/'.
+#
+# The output is the first argument as a path relative to the second argument,
+# which defaults to $wt_prefix if it is omitted.
+relative_path ()
+{
+       local target curdir result
+       target=$1
+       curdir=${2-$wt_prefix}
+       curdir=${curdir%/}
+       result=
+
+       while test -n "$curdir"
+       do
+               case "$target" in
+               "$curdir/"*)
+                       target=${target#"$curdir"/}
+                       break
+                       ;;
+               esac
+
+               result="${result}../"
+               if test "$curdir" = "${curdir%/*}"
+               then
+                       curdir=
+               else
+                       curdir="${curdir%/*}"
+               fi
+       done
+
+       echo "$result$target"
+}
+
 #
 # Get submodule info for registered submodules
 # $@ = path to limit submodule list
 #
 module_list()
 {
+       eval "set $(git rev-parse --sq --prefix "$wt_prefix" -- "$@")"
        (
-               git ls-files --error-unmatch --stage -- "$@" ||
+               git ls-files -z --error-unmatch --stage -- "$@" ||
                echo "unmatched pathspec exists"
        ) |
        perl -e '
@@ -121,6 +160,7 @@ module_list()
        my ($null_sha1) = ("0" x 40);
        my @out = ();
        my $unmatched = 0;
+       $/ = "\0";
        while (<STDIN>) {
                if (/^unmatched pathspec/) {
                        $unmatched = 1;
@@ -282,6 +322,7 @@ isnumber()
 cmd_add()
 {
        # parse $args after "submodule ... add".
+       reference_path=
        while test $# -ne 0
        do
                case "$1" in
@@ -298,11 +339,11 @@ cmd_add()
                        ;;
                --reference)
                        case "$2" in '') usage ;; esac
-                       reference="--reference=$2"
+                       reference_path=$2
                        shift
                        ;;
                --reference=*)
-                       reference="$1"
+                       reference_path="${1#--reference=}"
                        ;;
                --name)
                        case "$2" in '') usage ;; esac
@@ -323,6 +364,14 @@ cmd_add()
                shift
        done
 
+       if test -n "$reference_path"
+       then
+               is_absolute_path "$reference_path" ||
+               reference_path="$wt_prefix$reference_path"
+
+               reference="--reference=$reference_path"
+       fi
+
        repo=$1
        sm_path=$2
 
@@ -335,9 +384,14 @@ cmd_add()
                usage
        fi
 
+       is_absolute_path "$sm_path" || sm_path="$wt_prefix$sm_path"
+
        # assure repo is absolute or relative to parent
        case "$repo" in
        ./*|../*)
+               test -z "$wt_prefix" ||
+               die "$(gettext "Relative path can only be used from the toplevel of the working tree")"
+
                # dereference source url relative to parent's url
                realrepo=$(resolve_relative_url "$repo") || exit
                ;;
@@ -471,21 +525,23 @@ cmd_foreach()
                die_if_unmatched "$mode"
                if test -e "$sm_path"/.git
                then
-                       say "$(eval_gettext "Entering '\$prefix\$sm_path'")"
+                       displaypath=$(relative_path "$sm_path")
+                       say "$(eval_gettext "Entering '\$prefix\$displaypath'")"
                        name=$(module_name "$sm_path")
                        (
                                prefix="$prefix$sm_path/"
                                clear_local_git_env
-                               # we make $path available to scripts ...
-                               path=$sm_path
                                cd "$sm_path" &&
+                               sm_path=$(relative_path "$sm_path") &&
+                               # we make $path available to scripts ...
+                               path=$sm_path &&
                                eval "$@" &&
                                if test -n "$recursive"
                                then
                                        cmd_foreach "--recursive" "$@"
                                fi
                        ) <&3 3<&- ||
-                       die "$(eval_gettext "Stopping at '\$sm_path'; script returned non-zero status.")"
+                       die "$(eval_gettext "Stopping at '\$prefix\$displaypath'; script returned non-zero status.")"
                fi
        done
 }
@@ -524,12 +580,14 @@ cmd_init()
                die_if_unmatched "$mode"
                name=$(module_name "$sm_path") || exit
 
+               displaypath=$(relative_path "$sm_path")
+
                # Copy url setting when it is not set yet
                if test -z "$(git config "submodule.$name.url")"
                then
                        url=$(git config -f .gitmodules submodule."$name".url)
                        test -z "$url" &&
-                       die "$(eval_gettext "No url found for submodule path '\$sm_path' in .gitmodules")"
+                       die "$(eval_gettext "No url found for submodule path '\$displaypath' in .gitmodules")"
 
                        # Possibly a url relative to parent
                        case "$url" in
@@ -538,9 +596,9 @@ cmd_init()
                                ;;
                        esac
                        git config submodule."$name".url "$url" ||
-                       die "$(eval_gettext "Failed to register url for submodule path '\$sm_path'")"
+                       die "$(eval_gettext "Failed to register url for submodule path '\$displaypath'")"
 
-                       say "$(eval_gettext "Submodule '\$name' (\$url) registered for path '\$sm_path'")"
+                       say "$(eval_gettext "Submodule '\$name' (\$url) registered for path '\$displaypath'")"
                fi
 
                # Copy "update" setting when it is not set yet
@@ -548,7 +606,7 @@ cmd_init()
                test -z "$upd" ||
                test -n "$(git config submodule."$name".update)" ||
                git config submodule."$name".update "$upd" ||
-               die "$(eval_gettext "Failed to register update mode for submodule path '\$sm_path'")"
+               die "$(eval_gettext "Failed to register update mode for submodule path '\$displaypath'")"
        done
 }
 
@@ -594,27 +652,29 @@ cmd_deinit()
                die_if_unmatched "$mode"
                name=$(module_name "$sm_path") || exit
 
+               displaypath=$(relative_path "$sm_path")
+
                # Remove the submodule work tree (unless the user already did it)
                if test -d "$sm_path"
                then
                        # Protect submodules containing a .git directory
                        if test -d "$sm_path/.git"
                        then
-                               echo >&2 "$(eval_gettext "Submodule work tree '\$sm_path' contains a .git directory")"
+                               echo >&2 "$(eval_gettext "Submodule work tree '\$displaypath' contains a .git directory")"
                                die "$(eval_gettext "(use 'rm -rf' if you really want to remove it including all of its history)")"
                        fi
 
                        if test -z "$force"
                        then
                                git rm -qn "$sm_path" ||
-                               die "$(eval_gettext "Submodule work tree '\$sm_path' contains local modifications; use '-f' to discard them")"
+                               die "$(eval_gettext "Submodule work tree '\$displaypath' contains local modifications; use '-f' to discard them")"
                        fi
                        rm -rf "$sm_path" &&
-                       say "$(eval_gettext "Cleared directory '\$sm_path'")" ||
-                       say "$(eval_gettext "Could not remove submodule work tree '\$sm_path'")"
+                       say "$(eval_gettext "Cleared directory '\$displaypath'")" ||
+                       say "$(eval_gettext "Could not remove submodule work tree '\$displaypath'")"
                fi
 
-               mkdir "$sm_path" || say "$(eval_gettext "Could not create empty submodule directory '\$sm_path'")"
+               mkdir "$sm_path" || say "$(eval_gettext "Could not create empty submodule directory '\$displaypath'")"
 
                # Remove the .git/config entries (unless the user already did it)
                if test -n "$(git config --get-regexp submodule."$name\.")"
@@ -623,7 +683,7 @@ cmd_deinit()
                        # the user later decides to init this submodule again
                        url=$(git config submodule."$name".url)
                        git config --remove-section submodule."$name" 2>/dev/null &&
-                       say "$(eval_gettext "Submodule '\$name' (\$url) unregistered for path '\$sm_path'")"
+                       say "$(eval_gettext "Submodule '\$name' (\$url) unregistered for path '\$displaypath'")"
                fi
        done
 }
@@ -717,9 +777,11 @@ cmd_update()
                        update_module=$(git config submodule."$name".update)
                fi
 
+               displaypath=$(relative_path "$prefix$sm_path")
+
                if test "$update_module" = "none"
                then
-                       echo "Skipping submodule '$prefix$sm_path'"
+                       echo "Skipping submodule '$displaypath'"
                        continue
                fi
 
@@ -728,7 +790,7 @@ cmd_update()
                        # Only mention uninitialized submodules when its
                        # path have been specified
                        test "$#" != "0" &&
-                       say "$(eval_gettext "Submodule path '\$prefix\$sm_path' not initialized
+                       say "$(eval_gettext "Submodule path '\$displaypath' not initialized
 Maybe you want to use 'update --init'?")"
                        continue
                fi
@@ -741,7 +803,7 @@ Maybe you want to use 'update --init'?")"
                else
                        subsha1=$(clear_local_git_env; cd "$sm_path" &&
                                git rev-parse --verify HEAD) ||
-                       die "$(eval_gettext "Unable to find current revision in submodule path '\$prefix\$sm_path'")"
+                       die "$(eval_gettext "Unable to find current revision in submodule path '\$displaypath'")"
                fi
 
                if test -n "$remote"
@@ -774,7 +836,7 @@ Maybe you want to use 'update --init'?")"
                                (clear_local_git_env; cd "$sm_path" &&
                                        ( (rev=$(git rev-list -n 1 $sha1 --not --all 2>/dev/null) &&
                                         test -z "$rev") || git-fetch)) ||
-                               die "$(eval_gettext "Unable to fetch in submodule path '\$prefix\$sm_path'")"
+                               die "$(eval_gettext "Unable to fetch in submodule path '\$displaypath'")"
                        fi
 
                        # Is this something we just cloned?
@@ -788,20 +850,20 @@ Maybe you want to use 'update --init'?")"
                        case "$update_module" in
                        rebase)
                                command="git rebase"
-                               die_msg="$(eval_gettext "Unable to rebase '\$sha1' in submodule path '\$prefix\$sm_path'")"
-                               say_msg="$(eval_gettext "Submodule path '\$prefix\$sm_path': rebased into '\$sha1'")"
+                               die_msg="$(eval_gettext "Unable to rebase '\$sha1' in submodule path '\$displaypath'")"
+                               say_msg="$(eval_gettext "Submodule path '\$displaypath': rebased into '\$sha1'")"
                                must_die_on_failure=yes
                                ;;
                        merge)
                                command="git merge"
-                               die_msg="$(eval_gettext "Unable to merge '\$sha1' in submodule path '\$prefix\$sm_path'")"
-                               say_msg="$(eval_gettext "Submodule path '\$prefix\$sm_path': merged in '\$sha1'")"
+                               die_msg="$(eval_gettext "Unable to merge '\$sha1' in submodule path '\$displaypath'")"
+                               say_msg="$(eval_gettext "Submodule path '\$displaypath': merged in '\$sha1'")"
                                must_die_on_failure=yes
                                ;;
                        *)
                                command="git checkout $subforce -q"
-                               die_msg="$(eval_gettext "Unable to checkout '\$sha1' in submodule path '\$prefix\$sm_path'")"
-                               say_msg="$(eval_gettext "Submodule path '\$prefix\$sm_path': checked out '\$sha1'")"
+                               die_msg="$(eval_gettext "Unable to checkout '\$sha1' in submodule path '\$displaypath'")"
+                               say_msg="$(eval_gettext "Submodule path '\$displaypath': checked out '\$sha1'")"
                                ;;
                        esac
 
@@ -828,7 +890,7 @@ Maybe you want to use 'update --init'?")"
                        res=$?
                        if test $res -gt 0
                        then
-                               die_msg="$(eval_gettext "Failed to recurse into submodule path '\$prefix\$sm_path'")"
+                               die_msg="$(eval_gettext "Failed to recurse into submodule path '\$displaypath'")"
                                if test $res -eq 1
                                then
                                        err="${err};$die_msg"
@@ -942,6 +1004,7 @@ cmd_summary() {
        fi
 
        cd_to_toplevel
+       eval "set $(git rev-parse --sq --prefix "$wt_prefix" -- "$@")"
        # Get modified modules cared by user
        modules=$(git $diff_cmd $cached --ignore-submodules=dirty --raw $head -- "$@" |
                sane_egrep '^:([0-7]* )?160000' |
@@ -991,16 +1054,18 @@ cmd_summary() {
                ! GIT_DIR="$name/.git" git-rev-parse -q --verify $sha1_dst^0 >/dev/null &&
                missing_dst=t
 
+               display_name=$(relative_path "$name")
+
                total_commits=
                case "$missing_src,$missing_dst" in
                t,)
-                       errmsg="$(eval_gettext "  Warn: \$name doesn't contain commit \$sha1_src")"
+                       errmsg="$(eval_gettext "  Warn: \$display_name doesn't contain commit \$sha1_src")"
                        ;;
                ,t)
-                       errmsg="$(eval_gettext "  Warn: \$name doesn't contain commit \$sha1_dst")"
+                       errmsg="$(eval_gettext "  Warn: \$display_name doesn't contain commit \$sha1_dst")"
                        ;;
                t,t)
-                       errmsg="$(eval_gettext "  Warn: \$name doesn't contain commits \$sha1_src and \$sha1_dst")"
+                       errmsg="$(eval_gettext "  Warn: \$display_name doesn't contain commits \$sha1_src and \$sha1_dst")"
                        ;;
                *)
                        errmsg=
@@ -1029,12 +1094,12 @@ cmd_summary() {
                        submodule="$(gettext "submodule")"
                        if test $mod_dst = 160000
                        then
-                               echo "* $name $sha1_abbr_src($blob)->$sha1_abbr_dst($submodule)$total_commits:"
+                               echo "* $display_name $sha1_abbr_src($blob)->$sha1_abbr_dst($submodule)$total_commits:"
                        else
-                               echo "* $name $sha1_abbr_src($submodule)->$sha1_abbr_dst($blob)$total_commits:"
+                               echo "* $display_name $sha1_abbr_src($submodule)->$sha1_abbr_dst($blob)$total_commits:"
                        fi
                else
-                       echo "* $name $sha1_abbr_src...$sha1_abbr_dst$total_commits:"
+                       echo "* $display_name $sha1_abbr_src...$sha1_abbr_dst$total_commits:"
                fi
                if test -n "$errmsg"
                then
@@ -1118,7 +1183,7 @@ cmd_status()
                die_if_unmatched "$mode"
                name=$(module_name "$sm_path") || exit
                url=$(git config submodule."$name".url)
-               displaypath="$prefix$sm_path"
+               displaypath=$(relative_path "$prefix$sm_path")
                if test "$stage" = U
                then
                        say "U$sha1 $displaypath"
@@ -1129,16 +1194,16 @@ cmd_status()
                        say "-$sha1 $displaypath"
                        continue;
                fi
-               set_name_rev "$sm_path" "$sha1"
                if git diff-files --ignore-submodules=dirty --quiet -- "$sm_path"
                then
+                       set_name_rev "$sm_path" "$sha1"
                        say " $sha1 $displaypath$revname"
                else
                        if test -z "$cached"
                        then
                                sha1=$(clear_local_git_env; cd "$sm_path" && git rev-parse --verify HEAD)
-                               set_name_rev "$sm_path" "$sha1"
                        fi
+                       set_name_rev "$sm_path" "$sha1"
                        say "+$sha1 $displaypath$revname"
                fi
 
@@ -1213,7 +1278,8 @@ cmd_sync()
 
                if git config "submodule.$name.url" >/dev/null 2>/dev/null
                then
-                       say "$(eval_gettext "Synchronizing submodule url for '\$prefix\$sm_path'")"
+                       displaypath=$(relative_path "$prefix$sm_path")
+                       say "$(eval_gettext "Synchronizing submodule url for '\$displaypath'")"
                        git config submodule."$name".url "$super_config_url"
 
                        if test -e "$sm_path"/.git
index d070de012c3a04ca621782f047425318ff246ca6..ff1ce3d351c8e4e175321d45b7284070f9ce691a 100755 (executable)
@@ -831,7 +831,7 @@ sub dcommit_rebase {
 sub cmd_dcommit {
        my $head = shift;
        command_noisy(qw/update-index --refresh/);
-       git_cmd_try { command_oneline(qw/diff-index --quiet HEAD/) }
+       git_cmd_try { command_oneline(qw/diff-index --quiet HEAD --/) }
                'Cannot dcommit with a dirty index.  Commit your changes first, '
                . "or stash them with `git stash'.\n";
        $head ||= 'HEAD';
@@ -1246,7 +1246,7 @@ sub cmd_rebase {
                return;
        }
        if (command(qw/diff-index HEAD --/)) {
-               print STDERR "Cannot rebase with uncommited changes:\n";
+               print STDERR "Cannot rebase with uncommitted changes:\n";
                command_noisy('status');
                exit 1;
        }
@@ -1932,7 +1932,7 @@ sub cmt_sha2rev_batch {
 sub working_head_info {
        my ($head, $refs) = @_;
        my @args = qw/rev-list --first-parent --pretty=medium/;
-       my ($fh, $ctx) = command_output_pipe(@args, $head);
+       my ($fh, $ctx) = command_output_pipe(@args, $head, "--");
        my $hash;
        my %max;
        while (<$fh>) {
index 9f446798d473cee7dedc82d5d2c820cb0967b375..1d72ec760e9d69e0fc29a699fa60be945f872a93 100755 (executable)
@@ -32,8 +32,9 @@ valid_custom_tool()
 valid_tool() {
        case "$1" in
        firefox | iceweasel | seamonkey | iceape | \
-       chrome | google-chrome | chromium | chromium-browser |\
-       konqueror | opera | w3m | elinks | links | lynx | dillo | open | start)
+       chrome | google-chrome | chromium | chromium-browser | \
+       konqueror | opera | w3m | elinks | links | lynx | dillo | open | \
+       start | cygstart)
                ;; # happy
        *)
                valid_custom_tool "$1" || return 1
@@ -127,6 +128,10 @@ if test -z "$browser" ; then
        if test -x /bin/start; then
                browser_candidates="start $browser_candidates"
        fi
+       # /usr/bin/cygstart indicates Cygwin
+       if test -x /usr/bin/cygstart; then
+               browser_candidates="cygstart $browser_candidates"
+       fi
 
        for i in $browser_candidates; do
                init_browser_path $i
@@ -174,7 +179,7 @@ konqueror)
                ;;
        esac
        ;;
-w3m|elinks|links|lynx|open)
+w3m|elinks|links|lynx|open|cygstart)
        "$browser_path" "$@"
        ;;
 start)
index 6b85ffac271596cf469984a06d27423af7df2b35..03244172977ba44399383c5b22f90877c94da964 100644 (file)
@@ -410,14 +410,14 @@ static void get_info_refs(char *arg)
        strbuf_release(&buf);
 }
 
-static int show_head_ref(const char *name, const unsigned char *sha1,
+static int show_head_ref(const char *refname, const unsigned char *sha1,
        int flag, void *cb_data)
 {
        struct strbuf *buf = cb_data;
 
        if (flag & REF_ISSYMREF) {
-               unsigned char sha1[20];
-               const char *target = resolve_ref_unsafe(name, sha1, 1, NULL);
+               unsigned char unused[20];
+               const char *target = resolve_ref_unsafe(refname, unused, 1, NULL);
                const char *target_nons = strip_namespace(target);
 
                strbuf_addf(buf, "ref: %s\n", target_nons);
diff --git a/http.c b/http.c
index 92aba59082a4ed9aa0c92d8522793abcd332e279..2d086aedfac9a8ea384c53f3b1963149b880ed31 100644 (file)
--- a/http.c
+++ b/http.c
@@ -228,9 +228,15 @@ static void init_curl_http_auth(CURL *result)
 #else
        {
                static struct strbuf up = STRBUF_INIT;
-               strbuf_reset(&up);
-               strbuf_addf(&up, "%s:%s",
-                           http_auth.username, http_auth.password);
+               /*
+                * Note that we assume we only ever have a single set of
+                * credentials in a given program run, so we do not have
+                * to worry about updating this buffer, only setting its
+                * initial value.
+                */
+               if (!up.len)
+                       strbuf_addf(&up, "%s:%s",
+                               http_auth.username, http_auth.password);
                curl_easy_setopt(result, CURLOPT_USERPWD, up.buf);
        }
 #endif
index 2bb734d51cbe59e3eed7c82e7522c594d5efa579..7873cdec581eea66feab06c22b83ef7c27c7c5b6 100644 (file)
@@ -47,6 +47,22 @@ static int score_matches(unsigned mode1, unsigned mode2, const char *path)
        return score;
 }
 
+static void *fill_tree_desc_strict(struct tree_desc *desc,
+                                  const unsigned char *hash)
+{
+       void *buffer;
+       enum object_type type;
+       unsigned long size;
+
+       buffer = read_sha1_file(hash, &type, &size);
+       if (!buffer)
+               die("unable to read tree (%s)", sha1_to_hex(hash));
+       if (type != OBJ_TREE)
+               die("%s is not a tree", sha1_to_hex(hash));
+       init_tree_desc(desc, buffer, size);
+       return buffer;
+}
+
 static int base_name_entries_compare(const struct name_entry *a,
                                     const struct name_entry *b)
 {
@@ -61,23 +77,10 @@ static int score_trees(const unsigned char *hash1, const unsigned char *hash2)
 {
        struct tree_desc one;
        struct tree_desc two;
-       void *one_buf, *two_buf;
+       void *one_buf = fill_tree_desc_strict(&one, hash1);
+       void *two_buf = fill_tree_desc_strict(&two, hash2);
        int score = 0;
-       enum object_type type;
-       unsigned long size;
 
-       one_buf = read_sha1_file(hash1, &type, &size);
-       if (!one_buf)
-               die("unable to read tree (%s)", sha1_to_hex(hash1));
-       if (type != OBJ_TREE)
-               die("%s is not a tree", sha1_to_hex(hash1));
-       init_tree_desc(&one, one_buf, size);
-       two_buf = read_sha1_file(hash2, &type, &size);
-       if (!two_buf)
-               die("unable to read tree (%s)", sha1_to_hex(hash2));
-       if (type != OBJ_TREE)
-               die("%s is not a tree", sha1_to_hex(hash2));
-       init_tree_desc(&two, two_buf, size);
        for (;;) {
                struct name_entry e1, e2;
                int got_entry_from_one = tree_entry(&one, &e1);
@@ -124,16 +127,7 @@ static void match_trees(const unsigned char *hash1,
                        int recurse_limit)
 {
        struct tree_desc one;
-       void *one_buf;
-       enum object_type type;
-       unsigned long size;
-
-       one_buf = read_sha1_file(hash1, &type, &size);
-       if (!one_buf)
-               die("unable to read tree (%s)", sha1_to_hex(hash1));
-       if (type != OBJ_TREE)
-               die("%s is not a tree", sha1_to_hex(hash1));
-       init_tree_desc(&one, one_buf, size);
+       void *one_buf = fill_tree_desc_strict(&one, hash1);
 
        while (one.size) {
                const char *path;
index 0f67bd3f9605aa7a3c98b88b4ae4aae037ace3d7..ab1885707403e06fe058ece2e0455e25e194224c 100644 (file)
@@ -9,6 +9,7 @@
 #include "notes.h"
 #include "notes-merge.h"
 #include "strbuf.h"
+#include "notes-utils.h"
 
 struct notes_merge_pair {
        unsigned char obj[20], base[20], local[20], remote[20];
@@ -530,32 +531,6 @@ static int merge_from_diffs(struct notes_merge_options *o,
        return conflicts ? -1 : 1;
 }
 
-void create_notes_commit(struct notes_tree *t, struct commit_list *parents,
-                        const struct strbuf *msg, unsigned char *result_sha1)
-{
-       unsigned char tree_sha1[20];
-
-       assert(t->initialized);
-
-       if (write_notes_tree(t, tree_sha1))
-               die("Failed to write notes tree to database");
-
-       if (!parents) {
-               /* Deduce parent commit from t->ref */
-               unsigned char parent_sha1[20];
-               if (!read_ref(t->ref, parent_sha1)) {
-                       struct commit *parent = lookup_commit(parent_sha1);
-                       if (!parent || parse_commit(parent))
-                               die("Failed to find/parse commit %s", t->ref);
-                       commit_list_insert(parent, &parents);
-               }
-               /* else: t->ref points to nothing, assume root/orphan commit */
-       }
-
-       if (commit_tree(msg, tree_sha1, parents, result_sha1, NULL, NULL))
-               die("Failed to commit notes tree to database");
-}
-
 int notes_merge(struct notes_merge_options *o,
                struct notes_tree *local_tree,
                unsigned char *result_sha1)
index 0c11b173a1e38ddeb702e022ebdd571ed2ad12f3..1d01f6aacf54b27a498d6071b5ec80fee5c35a47 100644 (file)
@@ -25,20 +25,6 @@ struct notes_merge_options {
 
 void init_notes_merge_options(struct notes_merge_options *o);
 
-/*
- * Create new notes commit from the given notes tree
- *
- * Properties of the created commit:
- * - tree: the result of converting t to a tree object with write_notes_tree().
- * - parents: the given parents OR (if NULL) the commit referenced by t->ref.
- * - author/committer: the default determined by commmit_tree().
- * - commit message: msg
- *
- * The resulting commit SHA1 is stored in result_sha1.
- */
-void create_notes_commit(struct notes_tree *t, struct commit_list *parents,
-                        const struct strbuf *msg, unsigned char *result_sha1);
-
 /*
  * Merge notes from o->remote_ref into o->local_ref
  *
diff --git a/notes-utils.c b/notes-utils.c
new file mode 100644 (file)
index 0000000..9107c37
--- /dev/null
@@ -0,0 +1,157 @@
+#include "cache.h"
+#include "commit.h"
+#include "refs.h"
+#include "notes-utils.h"
+
+void create_notes_commit(struct notes_tree *t, struct commit_list *parents,
+                        const struct strbuf *msg, unsigned char *result_sha1)
+{
+       unsigned char tree_sha1[20];
+
+       assert(t->initialized);
+
+       if (write_notes_tree(t, tree_sha1))
+               die("Failed to write notes tree to database");
+
+       if (!parents) {
+               /* Deduce parent commit from t->ref */
+               unsigned char parent_sha1[20];
+               if (!read_ref(t->ref, parent_sha1)) {
+                       struct commit *parent = lookup_commit(parent_sha1);
+                       if (!parent || parse_commit(parent))
+                               die("Failed to find/parse commit %s", t->ref);
+                       commit_list_insert(parent, &parents);
+               }
+               /* else: t->ref points to nothing, assume root/orphan commit */
+       }
+
+       if (commit_tree(msg, tree_sha1, parents, result_sha1, NULL, NULL))
+               die("Failed to commit notes tree to database");
+}
+
+void commit_notes(struct notes_tree *t, const char *msg)
+{
+       struct strbuf buf = STRBUF_INIT;
+       unsigned char commit_sha1[20];
+
+       if (!t)
+               t = &default_notes_tree;
+       if (!t->initialized || !t->ref || !*t->ref)
+               die(_("Cannot commit uninitialized/unreferenced notes tree"));
+       if (!t->dirty)
+               return; /* don't have to commit an unchanged tree */
+
+       /* Prepare commit message and reflog message */
+       strbuf_addstr(&buf, msg);
+       if (buf.buf[buf.len - 1] != '\n')
+               strbuf_addch(&buf, '\n'); /* Make sure msg ends with newline */
+
+       create_notes_commit(t, NULL, &buf, commit_sha1);
+       strbuf_insert(&buf, 0, "notes: ", 7); /* commit message starts at index 7 */
+       update_ref(buf.buf, t->ref, commit_sha1, NULL, 0, DIE_ON_ERR);
+
+       strbuf_release(&buf);
+}
+
+static combine_notes_fn parse_combine_notes_fn(const char *v)
+{
+       if (!strcasecmp(v, "overwrite"))
+               return combine_notes_overwrite;
+       else if (!strcasecmp(v, "ignore"))
+               return combine_notes_ignore;
+       else if (!strcasecmp(v, "concatenate"))
+               return combine_notes_concatenate;
+       else if (!strcasecmp(v, "cat_sort_uniq"))
+               return combine_notes_cat_sort_uniq;
+       else
+               return NULL;
+}
+
+static int notes_rewrite_config(const char *k, const char *v, void *cb)
+{
+       struct notes_rewrite_cfg *c = cb;
+       if (!prefixcmp(k, "notes.rewrite.") && !strcmp(k+14, c->cmd)) {
+               c->enabled = git_config_bool(k, v);
+               return 0;
+       } else if (!c->mode_from_env && !strcmp(k, "notes.rewritemode")) {
+               if (!v)
+                       config_error_nonbool(k);
+               c->combine = parse_combine_notes_fn(v);
+               if (!c->combine) {
+                       error(_("Bad notes.rewriteMode value: '%s'"), v);
+                       return 1;
+               }
+               return 0;
+       } else if (!c->refs_from_env && !strcmp(k, "notes.rewriteref")) {
+               /* note that a refs/ prefix is implied in the
+                * underlying for_each_glob_ref */
+               if (!prefixcmp(v, "refs/notes/"))
+                       string_list_add_refs_by_glob(c->refs, v);
+               else
+                       warning(_("Refusing to rewrite notes in %s"
+                               " (outside of refs/notes/)"), v);
+               return 0;
+       }
+
+       return 0;
+}
+
+
+struct notes_rewrite_cfg *init_copy_notes_for_rewrite(const char *cmd)
+{
+       struct notes_rewrite_cfg *c = xmalloc(sizeof(struct notes_rewrite_cfg));
+       const char *rewrite_mode_env = getenv(GIT_NOTES_REWRITE_MODE_ENVIRONMENT);
+       const char *rewrite_refs_env = getenv(GIT_NOTES_REWRITE_REF_ENVIRONMENT);
+       c->cmd = cmd;
+       c->enabled = 1;
+       c->combine = combine_notes_concatenate;
+       c->refs = xcalloc(1, sizeof(struct string_list));
+       c->refs->strdup_strings = 1;
+       c->refs_from_env = 0;
+       c->mode_from_env = 0;
+       if (rewrite_mode_env) {
+               c->mode_from_env = 1;
+               c->combine = parse_combine_notes_fn(rewrite_mode_env);
+               if (!c->combine)
+                       /* TRANSLATORS: The first %s is the name of the
+                          environment variable, the second %s is its value */
+                       error(_("Bad %s value: '%s'"), GIT_NOTES_REWRITE_MODE_ENVIRONMENT,
+                                       rewrite_mode_env);
+       }
+       if (rewrite_refs_env) {
+               c->refs_from_env = 1;
+               string_list_add_refs_from_colon_sep(c->refs, rewrite_refs_env);
+       }
+       git_config(notes_rewrite_config, c);
+       if (!c->enabled || !c->refs->nr) {
+               string_list_clear(c->refs, 0);
+               free(c->refs);
+               free(c);
+               return NULL;
+       }
+       c->trees = load_notes_trees(c->refs);
+       string_list_clear(c->refs, 0);
+       free(c->refs);
+       return c;
+}
+
+int copy_note_for_rewrite(struct notes_rewrite_cfg *c,
+                         const unsigned char *from_obj, const unsigned char *to_obj)
+{
+       int ret = 0;
+       int i;
+       for (i = 0; c->trees[i]; i++)
+               ret = copy_note(c->trees[i], from_obj, to_obj, 1, c->combine) || ret;
+       return ret;
+}
+
+void finish_copy_notes_for_rewrite(struct notes_rewrite_cfg *c, const char *msg)
+{
+       int i;
+       for (i = 0; c->trees[i]; i++) {
+               commit_notes(c->trees[i], msg);
+               free_notes(c->trees[i]);
+       }
+       free(c->trees);
+       free(c);
+}
diff --git a/notes-utils.h b/notes-utils.h
new file mode 100644 (file)
index 0000000..b4cb1bf
--- /dev/null
@@ -0,0 +1,37 @@
+#ifndef NOTES_UTILS_H
+#define NOTES_UTILS_H
+
+#include "notes.h"
+
+/*
+ * Create new notes commit from the given notes tree
+ *
+ * Properties of the created commit:
+ * - tree: the result of converting t to a tree object with write_notes_tree().
+ * - parents: the given parents OR (if NULL) the commit referenced by t->ref.
+ * - author/committer: the default determined by commmit_tree().
+ * - commit message: msg
+ *
+ * The resulting commit SHA1 is stored in result_sha1.
+ */
+void create_notes_commit(struct notes_tree *t, struct commit_list *parents,
+                        const struct strbuf *msg, unsigned char *result_sha1);
+
+void commit_notes(struct notes_tree *t, const char *msg);
+
+struct notes_rewrite_cfg {
+       struct notes_tree **trees;
+       const char *cmd;
+       int enabled;
+       combine_notes_fn combine;
+       struct string_list *refs;
+       int refs_from_env;
+       int mode_from_env;
+};
+
+struct notes_rewrite_cfg *init_copy_notes_for_rewrite(const char *cmd);
+int copy_note_for_rewrite(struct notes_rewrite_cfg *c,
+                         const unsigned char *from_obj, const unsigned char *to_obj);
+void finish_copy_notes_for_rewrite(struct notes_rewrite_cfg *c, const char *msg);
+
+#endif
diff --git a/notes.c b/notes.c
index f63fd572d6db125559e68556a8fc152a53700644..b69c0b82577e0d4958ddf3efbb4aec5a9c5fa99e 100644 (file)
--- a/notes.c
+++ b/notes.c
@@ -918,17 +918,21 @@ int combine_notes_cat_sort_uniq(unsigned char *cur_sha1,
        return ret;
 }
 
-static int string_list_add_one_ref(const char *path, const unsigned char *sha1,
+static int string_list_add_one_ref(const char *refname, const unsigned char *sha1,
                                   int flag, void *cb)
 {
        struct string_list *refs = cb;
-       if (!unsorted_string_list_has_string(refs, path))
-               string_list_append(refs, path);
+       if (!unsorted_string_list_has_string(refs, refname))
+               string_list_append(refs, refname);
        return 0;
 }
 
+/*
+ * The list argument must have strdup_strings set on it.
+ */
 void string_list_add_refs_by_glob(struct string_list *list, const char *glob)
 {
+       assert(list->strdup_strings);
        if (has_glob_specials(glob)) {
                for_each_glob_ref(string_list_add_one_ref, glob, list);
        } else {
index 88d0bece8116862681ba9fee3f9e0dfdec7f8b17..cbc7333a7ec083556ba9b46e1adafb695f83e0af 100644 (file)
--- a/object.c
+++ b/object.c
@@ -270,11 +270,18 @@ void add_object_array(struct object *obj, const char *name, struct object_array
        add_object_array_with_mode(obj, name, array, S_IFINVALID);
 }
 
+/*
+ * A zero-length string to which object_array_entry::name can be
+ * initialized without requiring a malloc/free.
+ */
+static char object_array_slopbuf[1];
+
 void add_object_array_with_mode(struct object *obj, const char *name, struct object_array *array, unsigned mode)
 {
        unsigned nr = array->nr;
        unsigned alloc = array->alloc;
        struct object_array_entry *objects = array->objects;
+       struct object_array_entry *entry;
 
        if (nr >= alloc) {
                alloc = (alloc + 32) * 2;
@@ -282,28 +289,67 @@ void add_object_array_with_mode(struct object *obj, const char *name, struct obj
                array->alloc = alloc;
                array->objects = objects;
        }
-       objects[nr].item = obj;
-       objects[nr].name = name;
-       objects[nr].mode = mode;
+       entry = &objects[nr];
+       entry->item = obj;
+       if (!name)
+               entry->name = NULL;
+       else if (!*name)
+               /* Use our own empty string instead of allocating one: */
+               entry->name = object_array_slopbuf;
+       else
+               entry->name = xstrdup(name);
+       entry->mode = mode;
        array->nr = ++nr;
 }
 
-void object_array_remove_duplicates(struct object_array *array)
+void object_array_filter(struct object_array *array,
+                        object_array_each_func_t want, void *cb_data)
 {
-       unsigned int ref, src, dst;
+       unsigned nr = array->nr, src, dst;
        struct object_array_entry *objects = array->objects;
 
-       for (ref = 0; ref + 1 < array->nr; ref++) {
-               for (src = ref + 1, dst = src;
-                    src < array->nr;
-                    src++) {
-                       if (!strcmp(objects[ref].name, objects[src].name))
-                               continue;
+       for (src = dst = 0; src < nr; src++) {
+               if (want(&objects[src], cb_data)) {
                        if (src != dst)
                                objects[dst] = objects[src];
                        dst++;
+               } else {
+                       if (objects[src].name != object_array_slopbuf)
+                               free(objects[src].name);
+               }
+       }
+       array->nr = dst;
+}
+
+/*
+ * Return true iff array already contains an entry with name.
+ */
+static int contains_name(struct object_array *array, const char *name)
+{
+       unsigned nr = array->nr, i;
+       struct object_array_entry *object = array->objects;
+
+       for (i = 0; i < nr; i++, object++)
+               if (!strcmp(object->name, name))
+                       return 1;
+       return 0;
+}
+
+void object_array_remove_duplicates(struct object_array *array)
+{
+       unsigned nr = array->nr, src;
+       struct object_array_entry *objects = array->objects;
+
+       array->nr = 0;
+       for (src = 0; src < nr; src++) {
+               if (!contains_name(array, objects[src].name)) {
+                       if (src != array->nr)
+                               objects[array->nr] = objects[src];
+                       array->nr++;
+               } else {
+                       if (objects[src].name != object_array_slopbuf)
+                               free(objects[src].name);
                }
-               array->nr = dst;
        }
 }
 
index 97d384b80a5dbf0cac88b59cbb8a2d333d2b9582..2ff68c52dd48842a188eb8f0c9b1e12ff27fae37 100644 (file)
--- a/object.h
+++ b/object.h
@@ -11,7 +11,13 @@ struct object_array {
        unsigned int alloc;
        struct object_array_entry {
                struct object *item;
-               const char *name;
+               /*
+                * name or NULL.  If non-NULL, the memory pointed to
+                * is owned by this object *except* if it points at
+                * object_array_slopbuf, which is a static copy of the
+                * empty string.
+                */
+               char *name;
                unsigned mode;
        } *objects;
 };
@@ -85,7 +91,22 @@ int object_list_contains(struct object_list *list, struct object *obj);
 /* Object array handling .. */
 void add_object_array(struct object *obj, const char *name, struct object_array *array);
 void add_object_array_with_mode(struct object *obj, const char *name, struct object_array *array, unsigned mode);
-void object_array_remove_duplicates(struct object_array *);
+
+typedef int (*object_array_each_func_t)(struct object_array_entry *, void *);
+
+/*
+ * Apply want to each entry in array, retaining only the entries for
+ * which the function returns true.  Preserve the order of the entries
+ * that are retained.
+ */
+void object_array_filter(struct object_array *array,
+                        object_array_each_func_t want, void *cb_data);
+
+/*
+ * Remove from array all but the first entry with a given name.
+ * Warning: this function uses an O(N^2) algorithm.
+ */
+void object_array_remove_duplicates(struct object_array *array);
 
 void clear_object_flags(unsigned flags);
 
index 5e30746886d477a0dc69853efd42283f2ac8c712..b297addb576dec45fa9b82ef4a0ffb350f9cfc6c 100644 (file)
@@ -1520,8 +1520,9 @@ int discard_index(struct index_state *istate)
        free_name_hash(istate);
        cache_tree_free(&(istate->cache_tree));
        istate->initialized = 0;
-
-       /* no need to throw away allocated active_cache */
+       free(istate->cache);
+       istate->cache = NULL;
+       istate->cache_alloc = 0;
        return 0;
 }
 
diff --git a/refs.h b/refs.h
index 8060ed831308fdde3a39c837f8b0953ea35baaef..246bf6096d222a0d822d621de519ca5c49b0f641 100644 (file)
--- a/refs.h
+++ b/refs.h
@@ -28,13 +28,23 @@ struct ref_lock {
 #define REF_ISBROKEN 0x04
 
 /*
- * Calls the specified function for each ref file until it returns
- * nonzero, and returns the value.  Please note that it is not safe to
- * modify references while an iteration is in progress, unless the
- * same callback function invocation that modifies the reference also
- * returns a nonzero value to immediately stop the iteration.
+ * The signature for the callback function for the for_each_*()
+ * functions below.  The memory pointed to by the refname and sha1
+ * arguments is only guaranteed to be valid for the duration of a
+ * single callback invocation.
+ */
+typedef int each_ref_fn(const char *refname,
+                       const unsigned char *sha1, int flags, void *cb_data);
+
+/*
+ * The following functions invoke the specified callback function for
+ * each reference indicated.  If the function ever returns a nonzero
+ * value, stop the iteration and return that value.  Please note that
+ * it is not safe to modify references while an iteration is in
+ * progress, unless the same callback function invocation that
+ * modifies the reference also returns a nonzero value to immediately
+ * stop the iteration.
  */
-typedef int each_ref_fn(const char *refname, const unsigned char *sha1, int flags, void *cb_data);
 extern int head_ref(each_ref_fn, void *);
 extern int for_each_ref(each_ref_fn, void *);
 extern int for_each_ref_in(const char *, each_ref_fn, void *);
index 518cd086939f306f8fc6d1228bd6e61ea6d0bc22..f1bb731fd71eb874344faa5376e7c375ac2fcf7f 100644 (file)
@@ -71,7 +71,8 @@ static int show_path_truncated(FILE *out, const struct name_path *path)
        return ours || emitted;
 }
 
-void show_object_with_name(FILE *out, struct object *obj, const struct name_path *path, const char *component)
+void show_object_with_name(FILE *out, struct object *obj,
+                          const struct name_path *path, const char *component)
 {
        struct name_path leaf;
        leaf.up = (struct name_path *)path;
@@ -88,7 +89,9 @@ void add_object(struct object *obj,
                struct name_path *path,
                const char *name)
 {
-       add_object_array(obj, path_name(path, name), p);
+       char *pn = path_name(path, name);
+       add_object_array(obj, pn, p);
+       free(pn);
 }
 
 static void mark_blob_uninteresting(struct blob *blob)
@@ -187,7 +190,9 @@ void mark_parents_uninteresting(struct commit *commit)
        }
 }
 
-static void add_pending_object_with_mode(struct rev_info *revs, struct object *obj, const char *name, unsigned mode)
+static void add_pending_object_with_mode(struct rev_info *revs,
+                                        struct object *obj,
+                                        const char *name, unsigned mode)
 {
        if (!obj)
                return;
@@ -210,7 +215,8 @@ static void add_pending_object_with_mode(struct rev_info *revs, struct object *o
        add_object_array_with_mode(obj, name, &revs->pending, mode);
 }
 
-void add_pending_object(struct rev_info *revs, struct object *obj, const char *name)
+void add_pending_object(struct rev_info *revs,
+                       struct object *obj, const char *name)
 {
        add_pending_object_with_mode(revs, obj, name, S_IFINVALID);
 }
@@ -227,7 +233,9 @@ void add_head_to_pending(struct rev_info *revs)
        add_pending_object(revs, obj, "HEAD");
 }
 
-static struct object *get_reference(struct rev_info *revs, const char *name, const unsigned char *sha1, unsigned int flags)
+static struct object *get_reference(struct rev_info *revs, const char *name,
+                                   const unsigned char *sha1,
+                                   unsigned int flags)
 {
        struct object *object;
 
@@ -248,7 +256,8 @@ void add_pending_sha1(struct rev_info *revs, const char *name,
        add_pending_object(revs, object, name);
 }
 
-static struct commit *handle_commit(struct rev_info *revs, struct object *object, const char *name)
+static struct commit *handle_commit(struct rev_info *revs,
+                                   struct object *object, const char *name)
 {
        unsigned long flags = object->flags;
 
@@ -333,6 +342,80 @@ static int everybody_uninteresting(struct commit_list *orig)
        return 1;
 }
 
+/*
+ * A definition of "relevant" commit that we can use to simplify limited graphs
+ * by eliminating side branches.
+ *
+ * A "relevant" commit is one that is !UNINTERESTING (ie we are including it
+ * in our list), or that is a specified BOTTOM commit. Then after computing
+ * a limited list, during processing we can generally ignore boundary merges
+ * coming from outside the graph, (ie from irrelevant parents), and treat
+ * those merges as if they were single-parent. TREESAME is defined to consider
+ * only relevant parents, if any. If we are TREESAME to our on-graph parents,
+ * we don't care if we were !TREESAME to non-graph parents.
+ *
+ * Treating bottom commits as relevant ensures that a limited graph's
+ * connection to the actual bottom commit is not viewed as a side branch, but
+ * treated as part of the graph. For example:
+ *
+ *   ....Z...A---X---o---o---B
+ *        .     /
+ *         W---Y
+ *
+ * When computing "A..B", the A-X connection is at least as important as
+ * Y-X, despite A being flagged UNINTERESTING.
+ *
+ * And when computing --ancestry-path "A..B", the A-X connection is more
+ * important than Y-X, despite both A and Y being flagged UNINTERESTING.
+ */
+static inline int relevant_commit(struct commit *commit)
+{
+       return (commit->object.flags & (UNINTERESTING | BOTTOM)) != UNINTERESTING;
+}
+
+/*
+ * Return a single relevant commit from a parent list. If we are a TREESAME
+ * commit, and this selects one of our parents, then we can safely simplify to
+ * that parent.
+ */
+static struct commit *one_relevant_parent(const struct rev_info *revs,
+                                         struct commit_list *orig)
+{
+       struct commit_list *list = orig;
+       struct commit *relevant = NULL;
+
+       if (!orig)
+               return NULL;
+
+       /*
+        * For 1-parent commits, or if first-parent-only, then return that
+        * first parent (even if not "relevant" by the above definition).
+        * TREESAME will have been set purely on that parent.
+        */
+       if (revs->first_parent_only || !orig->next)
+               return orig->item;
+
+       /*
+        * For multi-parent commits, identify a sole relevant parent, if any.
+        * If we have only one relevant parent, then TREESAME will be set purely
+        * with regard to that parent, and we can simplify accordingly.
+        *
+        * If we have more than one relevant parent, or no relevant parents
+        * (and multiple irrelevant ones), then we can't select a parent here
+        * and return NULL.
+        */
+       while (list) {
+               struct commit *commit = list->item;
+               list = list->next;
+               if (relevant_commit(commit)) {
+                       if (relevant)
+                               return NULL;
+                       relevant = commit;
+               }
+       }
+       return relevant;
+}
+
 /*
  * The goal is to get REV_TREE_NEW as the result only if the
  * diff consists of all '+' (and no other changes), REV_TREE_OLD
@@ -369,7 +452,8 @@ static void file_change(struct diff_options *options,
        DIFF_OPT_SET(options, HAS_CHANGES);
 }
 
-static int rev_compare_tree(struct rev_info *revs, struct commit *parent, struct commit *commit)
+static int rev_compare_tree(struct rev_info *revs,
+                           struct commit *parent, struct commit *commit)
 {
        struct tree *t1 = parent->tree;
        struct tree *t2 = commit->tree;
@@ -430,10 +514,125 @@ static int rev_same_tree_as_empty(struct rev_info *revs, struct commit *commit)
        return retval >= 0 && (tree_difference == REV_TREE_SAME);
 }
 
+struct treesame_state {
+       unsigned int nparents;
+       unsigned char treesame[FLEX_ARRAY];
+};
+
+static struct treesame_state *initialise_treesame(struct rev_info *revs, struct commit *commit)
+{
+       unsigned n = commit_list_count(commit->parents);
+       struct treesame_state *st = xcalloc(1, sizeof(*st) + n);
+       st->nparents = n;
+       add_decoration(&revs->treesame, &commit->object, st);
+       return st;
+}
+
+/*
+ * Must be called immediately after removing the nth_parent from a commit's
+ * parent list, if we are maintaining the per-parent treesame[] decoration.
+ * This does not recalculate the master TREESAME flag - update_treesame()
+ * should be called to update it after a sequence of treesame[] modifications
+ * that may have affected it.
+ */
+static int compact_treesame(struct rev_info *revs, struct commit *commit, unsigned nth_parent)
+{
+       struct treesame_state *st;
+       int old_same;
+
+       if (!commit->parents) {
+               /*
+                * Have just removed the only parent from a non-merge.
+                * Different handling, as we lack decoration.
+                */
+               if (nth_parent != 0)
+                       die("compact_treesame %u", nth_parent);
+               old_same = !!(commit->object.flags & TREESAME);
+               if (rev_same_tree_as_empty(revs, commit))
+                       commit->object.flags |= TREESAME;
+               else
+                       commit->object.flags &= ~TREESAME;
+               return old_same;
+       }
+
+       st = lookup_decoration(&revs->treesame, &commit->object);
+       if (!st || nth_parent >= st->nparents)
+               die("compact_treesame %u", nth_parent);
+
+       old_same = st->treesame[nth_parent];
+       memmove(st->treesame + nth_parent,
+               st->treesame + nth_parent + 1,
+               st->nparents - nth_parent - 1);
+
+       /*
+        * If we've just become a non-merge commit, update TREESAME
+        * immediately, and remove the no-longer-needed decoration.
+        * If still a merge, defer update until update_treesame().
+        */
+       if (--st->nparents == 1) {
+               if (commit->parents->next)
+                       die("compact_treesame parents mismatch");
+               if (st->treesame[0] && revs->dense)
+                       commit->object.flags |= TREESAME;
+               else
+                       commit->object.flags &= ~TREESAME;
+               free(add_decoration(&revs->treesame, &commit->object, NULL));
+       }
+
+       return old_same;
+}
+
+static unsigned update_treesame(struct rev_info *revs, struct commit *commit)
+{
+       if (commit->parents && commit->parents->next) {
+               unsigned n;
+               struct treesame_state *st;
+               struct commit_list *p;
+               unsigned relevant_parents;
+               unsigned relevant_change, irrelevant_change;
+
+               st = lookup_decoration(&revs->treesame, &commit->object);
+               if (!st)
+                       die("update_treesame %s", sha1_to_hex(commit->object.sha1));
+               relevant_parents = 0;
+               relevant_change = irrelevant_change = 0;
+               for (p = commit->parents, n = 0; p; n++, p = p->next) {
+                       if (relevant_commit(p->item)) {
+                               relevant_change |= !st->treesame[n];
+                               relevant_parents++;
+                       } else
+                               irrelevant_change |= !st->treesame[n];
+               }
+               if (relevant_parents ? relevant_change : irrelevant_change)
+                       commit->object.flags &= ~TREESAME;
+               else
+                       commit->object.flags |= TREESAME;
+       }
+
+       return commit->object.flags & TREESAME;
+}
+
+static inline int limiting_can_increase_treesame(const struct rev_info *revs)
+{
+       /*
+        * TREESAME is irrelevant unless prune && dense;
+        * if simplify_history is set, we can't have a mixture of TREESAME and
+        *    !TREESAME INTERESTING parents (and we don't have treesame[]
+        *    decoration anyway);
+        * if first_parent_only is set, then the TREESAME flag is locked
+        *    against the first parent (and again we lack treesame[] decoration).
+        */
+       return revs->prune && revs->dense &&
+              !revs->simplify_history &&
+              !revs->first_parent_only;
+}
+
 static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
 {
        struct commit_list **pp, *parent;
-       int tree_changed = 0, tree_same = 0, nth_parent = 0;
+       struct treesame_state *ts = NULL;
+       int relevant_change = 0, irrelevant_change = 0;
+       int relevant_parents, nth_parent;
 
        /*
         * If we don't do pruning, everything is interesting
@@ -457,33 +656,54 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
        if (!revs->dense && !commit->parents->next)
                return;
 
-       pp = &commit->parents;
-       while ((parent = *pp) != NULL) {
+       for (pp = &commit->parents, nth_parent = 0, relevant_parents = 0;
+            (parent = *pp) != NULL;
+            pp = &parent->next, nth_parent++) {
                struct commit *p = parent->item;
+               if (relevant_commit(p))
+                       relevant_parents++;
 
-               /*
-                * Do not compare with later parents when we care only about
-                * the first parent chain, in order to avoid derailing the
-                * traversal to follow a side branch that brought everything
-                * in the path we are limited to by the pathspec.
-                */
-               if (revs->first_parent_only && nth_parent++)
-                       break;
+               if (nth_parent == 1) {
+                       /*
+                        * This our second loop iteration - so we now know
+                        * we're dealing with a merge.
+                        *
+                        * Do not compare with later parents when we care only about
+                        * the first parent chain, in order to avoid derailing the
+                        * traversal to follow a side branch that brought everything
+                        * in the path we are limited to by the pathspec.
+                        */
+                       if (revs->first_parent_only)
+                               break;
+                       /*
+                        * If this will remain a potentially-simplifiable
+                        * merge, remember per-parent treesame if needed.
+                        * Initialise the array with the comparison from our
+                        * first iteration.
+                        */
+                       if (revs->treesame.name &&
+                           !revs->simplify_history &&
+                           !(commit->object.flags & UNINTERESTING)) {
+                               ts = initialise_treesame(revs, commit);
+                               if (!(irrelevant_change || relevant_change))
+                                       ts->treesame[0] = 1;
+                       }
+               }
                if (parse_commit(p) < 0)
                        die("cannot simplify commit %s (because of %s)",
                            sha1_to_hex(commit->object.sha1),
                            sha1_to_hex(p->object.sha1));
                switch (rev_compare_tree(revs, p, commit)) {
                case REV_TREE_SAME:
-                       tree_same = 1;
-                       if (!revs->simplify_history || (p->object.flags & UNINTERESTING)) {
+                       if (!revs->simplify_history || !relevant_commit(p)) {
                                /* Even if a merge with an uninteresting
                                 * side branch brought the entire change
                                 * we are interested in, we do not want
                                 * to lose the other branches of this
                                 * merge, so we just keep going.
                                 */
-                               pp = &parent->next;
+                               if (ts)
+                                       ts->treesame[nth_parent] = 1;
                                continue;
                        }
                        parent->next = NULL;
@@ -511,15 +731,27 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
                /* fallthrough */
                case REV_TREE_OLD:
                case REV_TREE_DIFFERENT:
-                       tree_changed = 1;
-                       pp = &parent->next;
+                       if (relevant_commit(p))
+                               relevant_change = 1;
+                       else
+                               irrelevant_change = 1;
                        continue;
                }
                die("bad tree compare for commit %s", sha1_to_hex(commit->object.sha1));
        }
-       if (tree_changed && !tree_same)
-               return;
-       commit->object.flags |= TREESAME;
+
+       /*
+        * TREESAME is straightforward for single-parent commits. For merge
+        * commits, it is most useful to define it so that "irrelevant"
+        * parents cannot make us !TREESAME - if we have any relevant
+        * parents, then we only consider TREESAMEness with respect to them,
+        * allowing irrelevant merges from uninteresting branches to be
+        * simplified away. Only if we have only irrelevant parents do we
+        * base TREESAME on them. Note that this logic is replicated in
+        * update_treesame, which should be kept in sync.
+        */
+       if (relevant_parents ? !relevant_change : !irrelevant_change)
+               commit->object.flags |= TREESAME;
 }
 
 static void commit_list_insert_by_date_cached(struct commit *p, struct commit_list **head,
@@ -802,16 +1034,12 @@ static void limit_to_ancestry(struct commit_list *bottom, struct commit_list *li
  * to filter the result of "A..B" further to the ones that can actually
  * reach A.
  */
-static struct commit_list *collect_bottom_commits(struct rev_info *revs)
+static struct commit_list *collect_bottom_commits(struct commit_list *list)
 {
-       struct commit_list *bottom = NULL;
-       int i;
-       for (i = 0; i < revs->cmdline.nr; i++) {
-               struct rev_cmdline_entry *elem = &revs->cmdline.rev[i];
-               if ((elem->flags & UNINTERESTING) &&
-                   elem->item->type == OBJ_COMMIT)
-                       commit_list_insert((struct commit *)elem->item, &bottom);
-       }
+       struct commit_list *elem, *bottom = NULL;
+       for (elem = list; elem; elem = elem->next)
+               if (elem->item->object.flags & BOTTOM)
+                       commit_list_insert(elem->item, &bottom);
        return bottom;
 }
 
@@ -842,7 +1070,7 @@ static int limit_list(struct rev_info *revs)
        struct commit_list *bottom = NULL;
 
        if (revs->ancestry_path) {
-               bottom = collect_bottom_commits(revs);
+               bottom = collect_bottom_commits(list);
                if (!bottom)
                        die("--ancestry-path given but there are no bottom commits");
        }
@@ -895,10 +1123,26 @@ static int limit_list(struct rev_info *revs)
                free_commit_list(bottom);
        }
 
+       /*
+        * Check if any commits have become TREESAME by some of their parents
+        * becoming UNINTERESTING.
+        */
+       if (limiting_can_increase_treesame(revs))
+               for (list = newlist; list; list = list->next) {
+                       struct commit *c = list->item;
+                       if (c->object.flags & (UNINTERESTING | TREESAME))
+                               continue;
+                       update_treesame(revs, c);
+               }
+
        revs->commits = newlist;
        return 0;
 }
 
+/*
+ * Add an entry to refs->cmdline with the specified information.
+ * *name is copied.
+ */
 static void add_rev_cmdline(struct rev_info *revs,
                            struct object *item,
                            const char *name,
@@ -910,7 +1154,7 @@ static void add_rev_cmdline(struct rev_info *revs,
 
        ALLOC_GROW(info->rev, nr + 1, info->alloc);
        info->rev[nr].item = item;
-       info->rev[nr].name = name;
+       info->rev[nr].name = xstrdup(name);
        info->rev[nr].whence = whence;
        info->rev[nr].flags = flags;
        info->nr++;
@@ -1014,7 +1258,7 @@ static int add_parents_only(struct rev_info *revs, const char *arg_, int flags)
        const char *arg = arg_;
 
        if (*arg == '^') {
-               flags ^= UNINTERESTING;
+               flags ^= UNINTERESTING | BOTTOM;
                arg++;
        }
        if (get_sha1_committish(arg, sha1))
@@ -1106,8 +1350,8 @@ static void prepare_show_merge(struct rev_info *revs)
        add_pending_object(revs, &head->object, "HEAD");
        add_pending_object(revs, &other->object, "MERGE_HEAD");
        bases = get_merge_bases(head, other, 1);
-       add_rev_cmdline_list(revs, bases, REV_CMD_MERGE_BASE, UNINTERESTING);
-       add_pending_commit_list(revs, bases, UNINTERESTING);
+       add_rev_cmdline_list(revs, bases, REV_CMD_MERGE_BASE, UNINTERESTING | BOTTOM);
+       add_pending_commit_list(revs, bases, UNINTERESTING | BOTTOM);
        free_commit_list(bases);
        head->object.flags |= SYMMETRIC_LEFT;
 
@@ -1143,13 +1387,15 @@ int handle_revision_arg(const char *arg_, struct rev_info *revs, int flags, unsi
        int cant_be_filename = revarg_opt & REVARG_CANNOT_BE_FILENAME;
        unsigned get_sha1_flags = 0;
 
+       flags = flags & UNINTERESTING ? flags | BOTTOM : flags & ~BOTTOM;
+
        dotdot = strstr(arg, "..");
        if (dotdot) {
                unsigned char from_sha1[20];
                const char *next = dotdot + 2;
                const char *this = arg;
                int symmetric = *next == '.';
-               unsigned int flags_exclude = flags ^ UNINTERESTING;
+               unsigned int flags_exclude = flags ^ (UNINTERESTING | BOTTOM);
                static const char head_by_default[] = "HEAD";
                unsigned int a_flags;
 
@@ -1225,13 +1471,13 @@ int handle_revision_arg(const char *arg_, struct rev_info *revs, int flags, unsi
        dotdot = strstr(arg, "^!");
        if (dotdot && !dotdot[2]) {
                *dotdot = 0;
-               if (!add_parents_only(revs, arg, flags ^ UNINTERESTING))
+               if (!add_parents_only(revs, arg, flags ^ (UNINTERESTING | BOTTOM)))
                        *dotdot = '^';
        }
 
        local_flags = 0;
        if (*arg == '^') {
-               local_flags = UNINTERESTING;
+               local_flags = UNINTERESTING | BOTTOM;
                arg++;
        }
 
@@ -1294,7 +1540,7 @@ static void read_revisions_from_stdin(struct rev_info *revs,
                        }
                        die("options not supported in --stdin mode");
                }
-               if (handle_revision_arg(xstrdup(sb.buf), revs, 0,
+               if (handle_revision_arg(sb.buf, revs, 0,
                                        REVARG_CANNOT_BE_FILENAME))
                        die("bad revision '%s'", sb.buf);
        }
@@ -1708,7 +1954,7 @@ static int handle_revision_pseudo_opt(const char *submodule,
                handle_refs(submodule, revs, *flags, for_each_branch_ref_submodule);
        } else if (!strcmp(arg, "--bisect")) {
                handle_refs(submodule, revs, *flags, for_each_bad_bisect_ref);
-               handle_refs(submodule, revs, *flags ^ UNINTERESTING, for_each_good_bisect_ref);
+               handle_refs(submodule, revs, *flags ^ (UNINTERESTING | BOTTOM), for_each_good_bisect_ref);
                revs->bisect = 1;
        } else if (!strcmp(arg, "--tags")) {
                handle_refs(submodule, revs, *flags, for_each_tag_ref_submodule);
@@ -1734,7 +1980,7 @@ static int handle_revision_pseudo_opt(const char *submodule,
        } else if (!strcmp(arg, "--reflog")) {
                handle_reflog(revs, *flags);
        } else if (!strcmp(arg, "--not")) {
-               *flags ^= UNINTERESTING;
+               *flags ^= UNINTERESTING | BOTTOM;
        } else if (!strcmp(arg, "--no-walk")) {
                revs->no_walk = REVISION_WALK_NO_WALK_SORTED;
        } else if (!prefixcmp(arg, "--no-walk=")) {
@@ -1954,28 +2200,32 @@ static void add_child(struct rev_info *revs, struct commit *parent, struct commi
        l->next = add_decoration(&revs->children, &parent->object, l);
 }
 
-static int remove_duplicate_parents(struct commit *commit)
+static int remove_duplicate_parents(struct rev_info *revs, struct commit *commit)
 {
+       struct treesame_state *ts = lookup_decoration(&revs->treesame, &commit->object);
        struct commit_list **pp, *p;
        int surviving_parents;
 
        /* Examine existing parents while marking ones we have seen... */
        pp = &commit->parents;
+       surviving_parents = 0;
        while ((p = *pp) != NULL) {
                struct commit *parent = p->item;
                if (parent->object.flags & TMP_MARK) {
                        *pp = p->next;
+                       if (ts)
+                               compact_treesame(revs, commit, surviving_parents);
                        continue;
                }
                parent->object.flags |= TMP_MARK;
+               surviving_parents++;
                pp = &p->next;
        }
-       /* count them while clearing the temporary mark */
-       surviving_parents = 0;
+       /* clear the temporary mark */
        for (p = commit->parents; p; p = p->next) {
                p->item->object.flags &= ~TMP_MARK;
-               surviving_parents++;
        }
+       /* no update_treesame() - removing duplicates can't affect TREESAME */
        return surviving_parents;
 }
 
@@ -1995,9 +2245,157 @@ static struct merge_simplify_state *locate_simplify_state(struct rev_info *revs,
        return st;
 }
 
+static int mark_redundant_parents(struct rev_info *revs, struct commit *commit)
+{
+       struct commit_list *h = reduce_heads(commit->parents);
+       int i = 0, marked = 0;
+       struct commit_list *po, *pn;
+
+       /* Want these for sanity-checking only */
+       int orig_cnt = commit_list_count(commit->parents);
+       int cnt = commit_list_count(h);
+
+       /*
+        * Not ready to remove items yet, just mark them for now, based
+        * on the output of reduce_heads(). reduce_heads outputs the reduced
+        * set in its original order, so this isn't too hard.
+        */
+       po = commit->parents;
+       pn = h;
+       while (po) {
+               if (pn && po->item == pn->item) {
+                       pn = pn->next;
+                       i++;
+               } else {
+                       po->item->object.flags |= TMP_MARK;
+                       marked++;
+               }
+               po=po->next;
+       }
+
+       if (i != cnt || cnt+marked != orig_cnt)
+               die("mark_redundant_parents %d %d %d %d", orig_cnt, cnt, i, marked);
+
+       free_commit_list(h);
+
+       return marked;
+}
+
+static int mark_treesame_root_parents(struct rev_info *revs, struct commit *commit)
+{
+       struct commit_list *p;
+       int marked = 0;
+
+       for (p = commit->parents; p; p = p->next) {
+               struct commit *parent = p->item;
+               if (!parent->parents && (parent->object.flags & TREESAME)) {
+                       parent->object.flags |= TMP_MARK;
+                       marked++;
+               }
+       }
+
+       return marked;
+}
+
+/*
+ * Awkward naming - this means one parent we are TREESAME to.
+ * cf mark_treesame_root_parents: root parents that are TREESAME (to an
+ * empty tree). Better name suggestions?
+ */
+static int leave_one_treesame_to_parent(struct rev_info *revs, struct commit *commit)
+{
+       struct treesame_state *ts = lookup_decoration(&revs->treesame, &commit->object);
+       struct commit *unmarked = NULL, *marked = NULL;
+       struct commit_list *p;
+       unsigned n;
+
+       for (p = commit->parents, n = 0; p; p = p->next, n++) {
+               if (ts->treesame[n]) {
+                       if (p->item->object.flags & TMP_MARK) {
+                               if (!marked)
+                                       marked = p->item;
+                       } else {
+                               if (!unmarked) {
+                                       unmarked = p->item;
+                                       break;
+                               }
+                       }
+               }
+       }
+
+       /*
+        * If we are TREESAME to a marked-for-deletion parent, but not to any
+        * unmarked parents, unmark the first TREESAME parent. This is the
+        * parent that the default simplify_history==1 scan would have followed,
+        * and it doesn't make sense to omit that path when asking for a
+        * simplified full history. Retaining it improves the chances of
+        * understanding odd missed merges that took an old version of a file.
+        *
+        * Example:
+        *
+        *   I--------*X       A modified the file, but mainline merge X used
+        *    \       /        "-s ours", so took the version from I. X is
+        *     `-*A--'         TREESAME to I and !TREESAME to A.
+        *
+        * Default log from X would produce "I". Without this check,
+        * --full-history --simplify-merges would produce "I-A-X", showing
+        * the merge commit X and that it changed A, but not making clear that
+        * it had just taken the I version. With this check, the topology above
+        * is retained.
+        *
+        * Note that it is possible that the simplification chooses a different
+        * TREESAME parent from the default, in which case this test doesn't
+        * activate, and we _do_ drop the default parent. Example:
+        *
+        *   I------X         A modified the file, but it was reverted in B,
+        *    \    /          meaning mainline merge X is TREESAME to both
+        *    *A-*B           parents.
+        *
+        * Default log would produce "I" by following the first parent;
+        * --full-history --simplify-merges will produce "I-A-B". But this is a
+        * reasonable result - it presents a logical full history leading from
+        * I to X, and X is not an important merge.
+        */
+       if (!unmarked && marked) {
+               marked->object.flags &= ~TMP_MARK;
+               return 1;
+       }
+
+       return 0;
+}
+
+static int remove_marked_parents(struct rev_info *revs, struct commit *commit)
+{
+       struct commit_list **pp, *p;
+       int nth_parent, removed = 0;
+
+       pp = &commit->parents;
+       nth_parent = 0;
+       while ((p = *pp) != NULL) {
+               struct commit *parent = p->item;
+               if (parent->object.flags & TMP_MARK) {
+                       parent->object.flags &= ~TMP_MARK;
+                       *pp = p->next;
+                       free(p);
+                       removed++;
+                       compact_treesame(revs, commit, nth_parent);
+                       continue;
+               }
+               pp = &p->next;
+               nth_parent++;
+       }
+
+       /* Removing parents can only increase TREESAMEness */
+       if (removed && !(commit->object.flags & TREESAME))
+               update_treesame(revs, commit);
+
+       return nth_parent;
+}
+
 static struct commit_list **simplify_one(struct rev_info *revs, struct commit *commit, struct commit_list **tail)
 {
        struct commit_list *p;
+       struct commit *parent;
        struct merge_simplify_state *st, *pst;
        int cnt;
 
@@ -2039,7 +2437,9 @@ static struct commit_list **simplify_one(struct rev_info *revs, struct commit *c
        }
 
        /*
-        * Rewrite our list of parents.
+        * Rewrite our list of parents. Note that this cannot
+        * affect our TREESAME flags in any way - a commit is
+        * always TREESAME to its simplification.
         */
        for (p = commit->parents; p; p = p->next) {
                pst = locate_simplify_state(revs, p->item);
@@ -2051,43 +2451,53 @@ static struct commit_list **simplify_one(struct rev_info *revs, struct commit *c
        if (revs->first_parent_only)
                cnt = 1;
        else
-               cnt = remove_duplicate_parents(commit);
+               cnt = remove_duplicate_parents(revs, commit);
 
        /*
         * It is possible that we are a merge and one side branch
         * does not have any commit that touches the given paths;
-        * in such a case, the immediate parents will be rewritten
-        * to different commits.
+        * in such a case, the immediate parent from that branch
+        * will be rewritten to be the merge base.
         *
         *      o----X          X: the commit we are looking at;
         *     /    /           o: a commit that touches the paths;
         * ---o----'
         *
-        * Further reduce the parents by removing redundant parents.
+        * Further, a merge of an independent branch that doesn't
+        * touch the path will reduce to a treesame root parent:
+        *
+        *  ----o----X          X: the commit we are looking at;
+        *          /           o: a commit that touches the paths;
+        *         r            r: a root commit not touching the paths
+        *
+        * Detect and simplify both cases.
         */
        if (1 < cnt) {
-               struct commit_list *h = reduce_heads(commit->parents);
-               cnt = commit_list_count(h);
-               free_commit_list(commit->parents);
-               commit->parents = h;
+               int marked = mark_redundant_parents(revs, commit);
+               marked += mark_treesame_root_parents(revs, commit);
+               if (marked)
+                       marked -= leave_one_treesame_to_parent(revs, commit);
+               if (marked)
+                       cnt = remove_marked_parents(revs, commit);
        }
 
        /*
         * A commit simplifies to itself if it is a root, if it is
         * UNINTERESTING, if it touches the given paths, or if it is a
-        * merge and its parents simplifies to more than one commits
+        * merge and its parents don't simplify to one relevant commit
         * (the first two cases are already handled at the beginning of
         * this function).
         *
-        * Otherwise, it simplifies to what its sole parent simplifies to.
+        * Otherwise, it simplifies to what its sole relevant parent
+        * simplifies to.
         */
        if (!cnt ||
            (commit->object.flags & UNINTERESTING) ||
            !(commit->object.flags & TREESAME) ||
-           (1 < cnt))
+           (parent = one_relevant_parent(revs, commit->parents)) == NULL)
                st->simplified = commit;
        else {
-               pst = locate_simplify_state(revs, commit->parents->item);
+               pst = locate_simplify_state(revs, parent);
                st->simplified = pst->simplified;
        }
        return tail;
@@ -2183,6 +2593,11 @@ int prepare_revision_walk(struct rev_info *revs)
        if (!revs->leak_pending)
                free(list);
 
+       /* Signal whether we need per-parent treesame decoration */
+       if (revs->simplify_merges ||
+           (revs->limited && limiting_can_increase_treesame(revs)))
+               revs->treesame.name = "treesame";
+
        if (revs->no_walk != REVISION_WALK_NO_WALK_UNSORTED)
                commit_list_sort_by_date(&revs->commits);
        if (revs->no_walk)
@@ -2210,15 +2625,15 @@ static enum rewrite_result rewrite_one(struct rev_info *revs, struct commit **pp
                if (!revs->limited)
                        if (add_parents_to_list(revs, p, &revs->commits, &cache) < 0)
                                return rewrite_one_error;
-               if (p->parents && p->parents->next)
-                       return rewrite_one_ok;
                if (p->object.flags & UNINTERESTING)
                        return rewrite_one_ok;
                if (!(p->object.flags & TREESAME))
                        return rewrite_one_ok;
                if (!p->parents)
                        return rewrite_one_noparents;
-               *pp = p->parents->item;
+               if ((p = one_relevant_parent(revs, p->parents)) == NULL)
+                       return rewrite_one_ok;
+               *pp = p;
        }
 }
 
@@ -2239,7 +2654,7 @@ int rewrite_parents(struct rev_info *revs, struct commit *commit,
                }
                pp = &parent->next;
        }
-       remove_duplicate_parents(commit);
+       remove_duplicate_parents(revs, commit);
        return 0;
 }
 
@@ -2363,10 +2778,7 @@ enum commit_action get_commit_action(struct rev_info *revs, struct commit *commi
        if (revs->min_age != -1 && (commit->date > revs->min_age))
                return commit_ignore;
        if (revs->min_parents || (revs->max_parents >= 0)) {
-               int n = 0;
-               struct commit_list *p;
-               for (p = commit->parents; p; p = p->next)
-                       n++;
+               int n = commit_list_count(commit->parents);
                if ((n < revs->min_parents) ||
                    ((revs->max_parents >= 0) && (n > revs->max_parents)))
                        return commit_ignore;
@@ -2376,12 +2788,23 @@ enum commit_action get_commit_action(struct rev_info *revs, struct commit *commi
        if (revs->prune && revs->dense) {
                /* Commit without changes? */
                if (commit->object.flags & TREESAME) {
+                       int n;
+                       struct commit_list *p;
                        /* drop merges unless we want parenthood */
                        if (!want_ancestry(revs))
                                return commit_ignore;
-                       /* non-merge - always ignore it */
-                       if (!commit->parents || !commit->parents->next)
-                               return commit_ignore;
+                       /*
+                        * If we want ancestry, then need to keep any merges
+                        * between relevant commits to tie together topology.
+                        * For consistency with TREESAME and simplification
+                        * use "relevant" here rather than just INTERESTING,
+                        * to treat bottom commit(s) as part of the topology.
+                        */
+                       for (n = 0, p = commit->parents; p; p = p->next)
+                               if (relevant_commit(p->item))
+                                       if (++n >= 2)
+                                               return commit_show;
+                       return commit_ignore;
                }
        }
        return commit_show;
@@ -2444,25 +2867,23 @@ static struct commit *get_revision_1(struct rev_info *revs)
        return NULL;
 }
 
-static void gc_boundary(struct object_array *array)
+/*
+ * Return true for entries that have not yet been shown.  (This is an
+ * object_array_each_func_t.)
+ */
+static int entry_unshown(struct object_array_entry *entry, void *cb_data_unused)
 {
-       unsigned nr = array->nr;
-       unsigned alloc = array->alloc;
-       struct object_array_entry *objects = array->objects;
+       return !(entry->item->flags & SHOWN);
+}
 
-       if (alloc <= nr) {
-               unsigned i, j;
-               for (i = j = 0; i < nr; i++) {
-                       if (objects[i].item->flags & SHOWN)
-                               continue;
-                       if (i != j)
-                               objects[j] = objects[i];
-                       j++;
-               }
-               for (i = j; i < nr; i++)
-                       objects[i].item = NULL;
-               array->nr = j;
-       }
+/*
+ * If array is on the verge of a realloc, garbage-collect any entries
+ * that have already been shown to try to free up some space.
+ */
+static void gc_boundary(struct object_array *array)
+{
+       if (array->nr == array->alloc)
+               object_array_filter(array, entry_unshown, NULL);
 }
 
 static void create_boundary_commit_list(struct rev_info *revs)
index a313a1340536321080c44d502a2bb6bc105940d5..eeea6fba3c5408d5567eec238d0a331bf64ddc04 100644 (file)
@@ -15,7 +15,8 @@
 #define ADDED          (1u<<7) /* Parents already parsed and added? */
 #define SYMMETRIC_LEFT (1u<<8)
 #define PATCHSAME      (1u<<9)
-#define ALL_REV_FLAGS  ((1u<<10)-1)
+#define BOTTOM         (1u<<10)
+#define ALL_REV_FLAGS  ((1u<<11)-1)
 
 #define DECORATE_SHORT_REFS    1
 #define DECORATE_FULL_REFS     2
@@ -169,6 +170,7 @@ struct rev_info {
        struct reflog_walk_info *reflog_info;
        struct decoration children;
        struct decoration merge_simplification;
+       struct decoration treesame;
 
        /* notes-specific options: which refs to show */
        struct display_notes_opt notes_opt;
@@ -200,19 +202,23 @@ struct setup_revision_opt {
 };
 
 extern void init_revisions(struct rev_info *revs, const char *prefix);
-extern int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct setup_revision_opt *);
+extern int setup_revisions(int argc, const char **argv, struct rev_info *revs,
+                          struct setup_revision_opt *);
 extern void parse_revision_opt(struct rev_info *revs, struct parse_opt_ctx_t *ctx,
-                                const struct option *options,
-                                const char * const usagestr[]);
+                              const struct option *options,
+                              const char * const usagestr[]);
 #define REVARG_CANNOT_BE_FILENAME 01
 #define REVARG_COMMITTISH 02
-extern int handle_revision_arg(const char *arg, struct rev_info *revs, int flags, unsigned revarg_opt);
+extern int handle_revision_arg(const char *arg, struct rev_info *revs,
+                              int flags, unsigned revarg_opt);
 
 extern void reset_revision_walk(void);
 extern int prepare_revision_walk(struct rev_info *revs);
 extern struct commit *get_revision(struct rev_info *revs);
-extern char *get_revision_mark(const struct rev_info *revs, const struct commit *commit);
-extern void put_revision_mark(const struct rev_info *revs, const struct commit *commit);
+extern char *get_revision_mark(const struct rev_info *revs,
+                              const struct commit *commit);
+extern void put_revision_mark(const struct rev_info *revs,
+                             const struct commit *commit);
 
 extern void mark_parents_uninteresting(struct commit *commit);
 extern void mark_tree_uninteresting(struct tree *tree);
@@ -225,15 +231,19 @@ struct name_path {
 
 char *path_name(const struct name_path *path, const char *name);
 
-extern void show_object_with_name(FILE *, struct object *, const struct name_path *, const char *);
+extern void show_object_with_name(FILE *, struct object *,
+                                 const struct name_path *, const char *);
 
 extern void add_object(struct object *obj,
                       struct object_array *p,
                       struct name_path *path,
                       const char *name);
 
-extern void add_pending_object(struct rev_info *revs, struct object *obj, const char *name);
-extern void add_pending_sha1(struct rev_info *revs, const char *name, const unsigned char *sha1, unsigned int flags);
+extern void add_pending_object(struct rev_info *revs,
+                              struct object *obj, const char *name);
+extern void add_pending_sha1(struct rev_info *revs,
+                            const char *name, const unsigned char *sha1,
+                            unsigned int flags);
 
 extern void add_head_to_pending(struct rev_info *);
 
@@ -243,8 +253,10 @@ enum commit_action {
        commit_error
 };
 
-extern enum commit_action get_commit_action(struct rev_info *revs, struct commit *commit);
-extern enum commit_action simplify_commit(struct rev_info *revs, struct commit *commit);
+extern enum commit_action get_commit_action(struct rev_info *revs,
+                                           struct commit *commit);
+extern enum commit_action simplify_commit(struct rev_info *revs,
+                                         struct commit *commit);
 
 enum rewrite_result {
        rewrite_one_ok,
index ab6f8a722d1dfa8f9633d8c046aad26ef4d0404e..425207ad5f1068b17daa50199b6880ef6009db79 100644 (file)
@@ -270,15 +270,20 @@ static int error_dirty_index(struct replay_opts *opts)
 }
 
 static int fast_forward_to(const unsigned char *to, const unsigned char *from,
-                          int unborn)
+                       int unborn, struct replay_opts *opts)
 {
        struct ref_lock *ref_lock;
+       struct strbuf sb = STRBUF_INIT;
+       int ret;
 
        read_cache();
        if (checkout_fast_forward(from, to, 1))
                exit(1); /* the callee should have complained already */
        ref_lock = lock_any_ref_for_update("HEAD", unborn ? null_sha1 : from, 0);
-       return write_ref_sha1(ref_lock, to, "cherry-pick");
+       strbuf_addf(&sb, "%s: fast-forward", action_name(opts));
+       ret = write_ref_sha1(ref_lock, to, sb.buf);
+       strbuf_release(&sb);
+       return ret;
 }
 
 static int do_recursive_merge(struct commit *base, struct commit *next,
@@ -474,7 +479,7 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
        struct commit_message msg = { NULL, NULL, NULL, NULL, NULL };
        char *defmsg = NULL;
        struct strbuf msgbuf = STRBUF_INIT;
-       int res, unborn = 0;
+       int res, unborn = 0, allow;
 
        if (opts->no_commit) {
                /*
@@ -523,7 +528,7 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
        if (opts->allow_ff &&
            ((parent && !hashcmp(parent->object.sha1, head)) ||
             (!parent && unborn)))
-            return fast_forward_to(commit->object.sha1, head, unborn);
+               return fast_forward_to(commit->object.sha1, head, unborn, opts);
 
        if (parent && parse_commit(parent) < 0)
                /* TRANSLATORS: The first %s will be "revert" or
@@ -624,14 +629,18 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
                      msg.subject);
                print_advice(res == 1, opts);
                rerere(opts->allow_rerere_auto);
-       } else {
-               int allow = allow_empty(opts, commit);
-               if (allow < 0)
-                       return allow;
-               if (!opts->no_commit)
-                       res = run_git_commit(defmsg, opts, allow);
+               goto leave;
+       }
+
+       allow = allow_empty(opts, commit);
+       if (allow < 0) {
+               res = allow;
+               goto leave;
        }
+       if (!opts->no_commit)
+               res = run_git_commit(defmsg, opts, allow);
 
+leave:
        free_message(&msg);
        free(defmsg);
 
index 5c08701ae85d7bc5a4465ed138fd80b3f3480052..0af19c00f19b56b23bd7d4ad0f27fdd45cd06d90 100644 (file)
@@ -36,6 +36,9 @@ static inline uintmax_t sz_fmt(size_t s) { return s; }
 
 const unsigned char null_sha1[20];
 
+static const char *no_log_pack_access = "no_log_pack_access";
+static const char *log_pack_access;
+
 /*
  * This is meant to hold a *small* number of objects that you would
  * want read_sha1_file() to be able to return, but yet you do not want
@@ -1956,12 +1959,19 @@ static void write_pack_access_log(struct packed_git *p, off_t obj_offset)
 {
        static FILE *log_file;
 
+       if (!log_pack_access)
+               log_pack_access = getenv("GIT_TRACE_PACK_ACCESS");
+       if (!log_pack_access)
+               log_pack_access = no_log_pack_access;
+       if (log_pack_access == no_log_pack_access)
+               return;
+
        if (!log_file) {
                log_file = fopen(log_pack_access, "w");
                if (!log_file) {
                        error("cannot open pack access log '%s' for writing: %s",
                              log_pack_access, strerror(errno));
-                       log_pack_access = NULL;
+                       log_pack_access = no_log_pack_access;
                        return;
                }
        }
@@ -1992,7 +2002,7 @@ void *unpack_entry(struct packed_git *p, off_t obj_offset,
        int delta_stack_nr = 0, delta_stack_alloc = UNPACK_ENTRY_STACK_PREALLOC;
        int base_from_cache = 0;
 
-       if (log_pack_access)
+       if (log_pack_access != no_log_pack_access)
                write_pack_access_log(p, obj_offset);
 
        /* PHASE 1: drill down to the innermost base object */
@@ -2135,8 +2145,17 @@ void *unpack_entry(struct packed_git *p, off_t obj_offset,
                data = patch_delta(base, base_size,
                                   delta_data, delta_size,
                                   &size);
+
+               /*
+                * We could not apply the delta; warn the user, but keep going.
+                * Our failure will be noticed either in the next iteration of
+                * the loop, or if this is the final delta, in the caller when
+                * we return NULL. Those code paths will take care of making
+                * a more explicit warning and retrying with another copy of
+                * the object.
+                */
                if (!data)
-                       die("failed to apply delta");
+                       error("failed to apply delta");
 
                free(delta_data);
        }
index 1821a5b3160bd3a3e60f328b50b56a0208b2df9c..86854248983be78b3702f5045a659dfe83bf4446 100644 (file)
@@ -845,7 +845,7 @@ static int find_first_merges(struct object_array *result, const char *path,
                struct commit *a, struct commit *b)
 {
        int i, j;
-       struct object_array merges;
+       struct object_array merges = OBJECT_ARRAY_INIT;
        struct commit *commit;
        int contains_another;
 
@@ -855,7 +855,6 @@ static int find_first_merges(struct object_array *result, const char *path,
        struct rev_info revs;
        struct setup_revision_opt rev_opts;
 
-       memset(&merges, 0, sizeof(merges));
        memset(result, 0, sizeof(struct object_array));
        memset(&rev_opts, 0, sizeof(rev_opts));
 
@@ -893,8 +892,7 @@ static int find_first_merges(struct object_array *result, const char *path,
                }
 
                if (!contains_another)
-                       add_object_array(merges.objects[i].item,
-                                        merges.objects[i].name, result);
+                       add_object_array(merges.objects[i].item, NULL, result);
        }
 
        free(merges.objects);
index 35b3c5c2faec0ed543dde460a30de63c48e5f1e7..ec5246886132f919ec4b6b05bfedd65eb2011690 100644 (file)
--- a/t/README
+++ b/t/README
@@ -595,6 +595,20 @@ library for your script to use.
                test_cmp expected actual
        '
 
+ - test_ln_s_add <path1> <path2>
+
+   This function helps systems whose filesystem does not support symbolic
+   links. Use it to add a symbolic link entry to the index when it is not
+   important that the file system entry is a symbolic link, i.e., instead
+   of the sequence
+
+       ln -s foo bar &&
+       git add bar
+
+   Sometimes it is possible to split a test in a part that does not need
+   the symbolic link in the file system and a part that does; then only
+   the latter part need be protected by a SYMLINKS prerequisite (see below).
+
 Prerequisites
 -------------
 
index b5bce459b61160653fcf45023f1e3c65e91f89be..dd17e3a09d728a08c8038c5ce8187ea63112cdf3 100644 (file)
@@ -1,5 +1,4 @@
 ServerName dummy
-LockFile accept.lock
 PidFile httpd.pid
 DocumentRoot www
 LogFormat "%h %l %u %t \"%r\" %>s %b" common
@@ -24,6 +23,10 @@ ErrorLog error.log
        LoadModule version_module modules/mod_version.so
 </IfModule>
 
+<IfVersion < 2.4>
+LockFile accept.lock
+</IfVersion>
+
 <IfVersion < 2.1>
 <IfModule !mod_auth.c>
        LoadModule auth_module modules/mod_auth.so
@@ -45,6 +48,21 @@ ErrorLog error.log
 </IfModule>
 </IfVersion>
 
+<IfVersion >= 2.4>
+<IfModule !mod_authn_core.c>
+       LoadModule authn_core_module modules/mod_authn_core.so
+</IfModule>
+<IfModule !mod_authz_core.c>
+       LoadModule authz_core_module modules/mod_authz_core.so
+</IfModule>
+<IfModule !mod_access_compat.c>
+       LoadModule access_compat_module modules/mod_access_compat.so
+</IfModule>
+<IfModule !mod_mpm_prefork.c>
+       LoadModule mpm_prefork_module modules/mod_mpm_prefork.so
+</IfModule>
+</IfVersion>
+
 PassEnv GIT_VALGRIND
 PassEnv GIT_VALGRIND_OPTIONS
 
index 6ccf7970916b58748aedcce7e583eed2dee782d3..4b74ae460b8943c176b6563d374e1663f89a1d7b 100644 (file)
@@ -65,3 +65,36 @@ EOF
        test_set_editor "$(pwd)/fake-editor.sh"
        chmod a+x fake-editor.sh
 }
+
+# checks that the revisions in "$2" represent a linear range with the
+# subjects in "$1"
+test_linear_range () {
+       revlist_merges=$(git rev-list --merges "$2") &&
+       test -z "$revlist_merges" &&
+       expected=$1
+       set -- $(git log --reverse --format=%s "$2")
+       test "$expected" = "$*"
+}
+
+reset_rebase () {
+       test_might_fail git rebase --abort &&
+       git reset --hard &&
+       git clean -f
+}
+
+cherry_pick () {
+       git cherry-pick -n "$2" &&
+       git commit -m "$1" &&
+       git tag "$1"
+}
+
+revert () {
+       git revert -n "$2" &&
+       git commit -m "$1" &&
+       git tag "$1"
+}
+
+make_empty () {
+       git commit --allow-empty -m "$1" &&
+       git tag "$1"
+}
diff --git a/t/perf/p0002-read-cache.sh b/t/perf/p0002-read-cache.sh
new file mode 100755 (executable)
index 0000000..9180ae9
--- /dev/null
@@ -0,0 +1,14 @@
+#!/bin/sh
+
+test_description="Tests performance of reading the index"
+
+. ./perf-lib.sh
+
+test_perf_default_repo
+
+count=1000
+test_perf "read_cache/discard_cache $count times" "
+       test-read-cache $count
+"
+
+test_done
index cefe33d6d1976e4017569507a6f76ad89c83e4ff..0f1318056cd06d8bcae615be5e573149ac5971d1 100755 (executable)
@@ -367,22 +367,6 @@ test_expect_success 'validate object ID of a known tree' '
 
 # Various types of objects
 
-# Some filesystems do not support symblic links; on such systems
-# some expected values are different
-if test_have_prereq SYMLINKS
-then
-       expectfilter=cat
-       expectedtree=087704a96baf1c2d1c869a8b084481e121c88b5b
-       expectedptree1=21ae8269cacbe57ae09138dcc3a2887f904d02b3
-       expectedptree2=3c5e5399f3a333eddecce7a9b9465b63f65f51e2
-else
-       expectfilter='grep -v sym'
-       expectedtree=8e18edf7d7edcf4371a3ac6ae5f07c2641db7c46
-       expectedptree1=cfb8591b2f65de8b8cc1020cd7d9e67e7793b325
-       expectedptree2=ce580448f0148b985a513b693fdf7d802cacb44f
-fi
-
-
 test_expect_success 'adding various types of objects with git update-index --add' '
        mkdir path2 path3 path3/subp3 &&
        paths="path0 path2/file2 path3/file3 path3/subp3/file3" &&
@@ -390,10 +374,7 @@ test_expect_success 'adding various types of objects with git update-index --add
                for p in $paths
                do
                        echo "hello $p" >$p || exit 1
-                       if test_have_prereq SYMLINKS
-                       then
-                               ln -s "hello $p" ${p}sym || exit 1
-                       fi
+                       test_ln_s_add "hello $p" ${p}sym || exit 1
                done
        ) &&
        find path* ! -type d -print | xargs git update-index --add
@@ -405,7 +386,7 @@ test_expect_success 'showing stage with git ls-files --stage' '
 '
 
 test_expect_success 'validate git ls-files output for a known tree' '
-       $expectfilter >expected <<-\EOF &&
+       cat >expected <<-\EOF &&
        100644 f87290f8eb2cbbea7857214459a0739927eab154 0       path0
        120000 15a98433ae33114b085f3eb3bb03b832b3180a01 0       path0sym
        100644 3feff949ed00a62d9f7af97c15cd8a30595e7ac7 0       path2/file2
@@ -423,14 +404,14 @@ test_expect_success 'writing tree out with git write-tree' '
 '
 
 test_expect_success 'validate object ID for a known tree' '
-       test "$tree" = "$expectedtree"
+       test "$tree" = 087704a96baf1c2d1c869a8b084481e121c88b5b
 '
 
 test_expect_success 'showing tree with git ls-tree' '
     git ls-tree $tree >current
 '
 
-test_expect_success SYMLINKS 'git ls-tree output for a known tree' '
+test_expect_success 'git ls-tree output for a known tree' '
        cat >expected <<-\EOF &&
        100644 blob f87290f8eb2cbbea7857214459a0739927eab154    path0
        120000 blob 15a98433ae33114b085f3eb3bb03b832b3180a01    path0sym
@@ -447,7 +428,7 @@ test_expect_success 'showing tree with git ls-tree -r' '
 '
 
 test_expect_success 'git ls-tree -r output for a known tree' '
-       $expectfilter >expected <<-\EOF &&
+       cat >expected <<-\EOF &&
        100644 blob f87290f8eb2cbbea7857214459a0739927eab154    path0
        120000 blob 15a98433ae33114b085f3eb3bb03b832b3180a01    path0sym
        100644 blob 3feff949ed00a62d9f7af97c15cd8a30595e7ac7    path2/file2
@@ -465,7 +446,7 @@ test_expect_success 'showing tree with git ls-tree -r -t' '
        git ls-tree -r -t $tree >current
 '
 
-test_expect_success SYMLINKS 'git ls-tree -r output for a known tree' '
+test_expect_success 'git ls-tree -r output for a known tree' '
        cat >expected <<-\EOF &&
        100644 blob f87290f8eb2cbbea7857214459a0739927eab154    path0
        120000 blob 15a98433ae33114b085f3eb3bb03b832b3180a01    path0sym
@@ -487,7 +468,7 @@ test_expect_success 'writing partial tree out with git write-tree --prefix' '
 '
 
 test_expect_success 'validate object ID for a known tree' '
-       test "$ptree" = "$expectedptree1"
+       test "$ptree" = 21ae8269cacbe57ae09138dcc3a2887f904d02b3
 '
 
 test_expect_success 'writing partial tree out with git write-tree --prefix' '
@@ -495,7 +476,7 @@ test_expect_success 'writing partial tree out with git write-tree --prefix' '
 '
 
 test_expect_success 'validate object ID for a known tree' '
-       test "$ptree" = "$expectedptree2"
+       test "$ptree" = 3c5e5399f3a333eddecce7a9b9465b63f65f51e2
 '
 
 test_expect_success 'put invalid objects into the index' '
@@ -529,7 +510,7 @@ test_expect_success 'git read-tree followed by write-tree should be idempotent'
 '
 
 test_expect_success 'validate git diff-files output for a know cache/work tree state' '
-       $expectfilter >expected <<\EOF &&
+       cat >expected <<\EOF &&
 :100644 100644 f87290f8eb2cbbea7857214459a0739927eab154 0000000000000000000000000000000000000000 M     path0
 :120000 120000 15a98433ae33114b085f3eb3bb03b832b3180a01 0000000000000000000000000000000000000000 M     path0sym
 :100644 100644 3feff949ed00a62d9f7af97c15cd8a30595e7ac7 0000000000000000000000000000000000000000 M     path2/file2
@@ -553,7 +534,7 @@ test_expect_success 'no diff after checkout and git update-index --refresh' '
 '
 
 ################################################################
-P=$expectedtree
+P=087704a96baf1c2d1c869a8b084481e121c88b5b
 
 test_expect_success 'git commit-tree records the correct tree in a commit' '
        commit0=$(echo NO | git commit-tree $P) &&
index 244a43c9201a3d81d0931f5f127bd2dcfd068eba..65606df3ed8b87256273dc85056f57a404b71c99 100755 (executable)
@@ -50,7 +50,7 @@ EOF
 
 test_expect_success 'test help' '
        test_must_fail test-parse-options -h > output 2> output.err &&
-       test ! -s output.err &&
+       test_must_be_empty output.err &&
        test_i18ncmp expect output
 '
 
@@ -75,7 +75,7 @@ check() {
        shift &&
        sed "s/^$what .*/$what $expect/" <expect.template >expect &&
        test-parse-options $* >output 2>output.err &&
-       test ! -s output.err &&
+       test_must_be_empty output.err &&
        test_cmp expect output
 }
 
@@ -86,7 +86,7 @@ check_i18n() {
        shift &&
        sed "s/^$what .*/$what $expect/" <expect.template >expect &&
        test-parse-options $* >output 2>output.err &&
-       test ! -s output.err &&
+       test_must_be_empty output.err &&
        test_i18ncmp expect output
 }
 
@@ -99,7 +99,7 @@ check_unknown() {
        esac &&
        cat expect.err >>expect &&
        test_must_fail test-parse-options $* >output 2>output.err &&
-       test ! -s output &&
+       test_must_be_empty output &&
        test_cmp expect output.err
 }
 
@@ -112,7 +112,7 @@ check_unknown_i18n() {
        esac &&
        cat expect.err >>expect &&
        test_must_fail test-parse-options $* >output 2>output.err &&
-       test ! -s output &&
+       test_must_be_empty output &&
        test_i18ncmp expect output.err
 }
 
@@ -149,7 +149,7 @@ test_expect_success 'short options' '
        test-parse-options -s123 -b -i 1729 -b -vv -n -F my.file \
        > output 2> output.err &&
        test_cmp expect output &&
-       test ! -s output.err
+       test_must_be_empty output.err
 '
 
 cat > expect << EOF
@@ -168,7 +168,7 @@ test_expect_success 'long options' '
        test-parse-options --boolean --integer 1729 --boolean --string2=321 \
                --verbose --verbose --no-dry-run --abbrev=10 --file fi.le\
                --obsolete > output 2> output.err &&
-       test ! -s output.err &&
+       test_must_be_empty output.err &&
        test_cmp expect output
 '
 
@@ -199,7 +199,7 @@ EOF
 test_expect_success 'intermingled arguments' '
        test-parse-options a1 --string 123 b1 --boolean -j 13 -- --boolean \
                > output 2> output.err &&
-       test ! -s output.err &&
+       test_must_be_empty output.err &&
        test_cmp expect output
 '
 
@@ -217,13 +217,13 @@ EOF
 
 test_expect_success 'unambiguously abbreviated option' '
        test-parse-options --int 2 --boolean --no-bo > output 2> output.err &&
-       test ! -s output.err &&
+       test_must_be_empty output.err &&
        test_cmp expect output
 '
 
 test_expect_success 'unambiguously abbreviated option with "="' '
        test-parse-options --int=2 > output 2> output.err &&
-       test ! -s output.err &&
+       test_must_be_empty output.err &&
        test_cmp expect output
 '
 
@@ -246,7 +246,7 @@ EOF
 
 test_expect_success 'non ambiguous option (after two options it abbreviates)' '
        test-parse-options --st 123 > output 2> output.err &&
-       test ! -s output.err &&
+       test_must_be_empty output.err &&
        test_cmp expect output
 '
 
@@ -256,7 +256,7 @@ EOF
 
 test_expect_success 'detect possible typos' '
        test_must_fail test-parse-options -boolean > output 2> output.err &&
-       test ! -s output &&
+       test_must_be_empty output &&
        test_cmp typo.err output.err
 '
 
@@ -266,7 +266,7 @@ EOF
 
 test_expect_success 'detect possible typos' '
        test_must_fail test-parse-options -ambiguous > output 2> output.err &&
-       test ! -s output &&
+       test_must_be_empty output &&
        test_cmp typo.err output.err
 '
 
@@ -285,7 +285,7 @@ EOF
 
 test_expect_success 'keep some options as arguments' '
        test-parse-options --quux > output 2> output.err &&
-        test ! -s output.err &&
+       test_must_be_empty output.err &&
         test_cmp expect output
 '
 
@@ -305,7 +305,7 @@ EOF
 test_expect_success 'OPT_DATE() and OPT_SET_PTR() work' '
        test-parse-options -t "1970-01-01 00:00:01 +0000" --default-string \
                foo -q > output 2> output.err &&
-       test ! -s output.err &&
+       test_must_be_empty output.err &&
        test_cmp expect output
 '
 
@@ -324,7 +324,7 @@ EOF
 
 test_expect_success 'OPT_CALLBACK() and OPT_BIT() work' '
        test-parse-options --length=four -b -4 > output 2> output.err &&
-       test ! -s output.err &&
+       test_must_be_empty output.err &&
        test_cmp expect output
 '
 
@@ -352,13 +352,13 @@ EOF
 
 test_expect_success 'OPT_BIT() and OPT_SET_INT() work' '
        test-parse-options --set23 -bbbbb --no-or4 > output 2> output.err &&
-       test ! -s output.err &&
+       test_must_be_empty output.err &&
        test_cmp expect output
 '
 
 test_expect_success 'OPT_NEGBIT() and OPT_SET_INT() work' '
        test-parse-options --set23 -bbbbb --neg-or4 > output 2> output.err &&
-       test ! -s output.err &&
+       test_must_be_empty output.err &&
        test_cmp expect output
 '
 
@@ -376,19 +376,19 @@ EOF
 
 test_expect_success 'OPT_BIT() works' '
        test-parse-options -bb --or4 > output 2> output.err &&
-       test ! -s output.err &&
+       test_must_be_empty output.err &&
        test_cmp expect output
 '
 
 test_expect_success 'OPT_NEGBIT() works' '
        test-parse-options -bb --no-neg-or4 > output 2> output.err &&
-       test ! -s output.err &&
+       test_must_be_empty output.err &&
        test_cmp expect output
 '
 
 test_expect_success 'OPT_COUNTUP() with PARSE_OPT_NODASH works' '
        test-parse-options + + + + + + > output 2> output.err &&
-       test ! -s output.err &&
+       test_must_be_empty output.err &&
        test_cmp expect output
 '
 
@@ -406,7 +406,7 @@ EOF
 
 test_expect_success 'OPT_NUMBER_CALLBACK() works' '
        test-parse-options -12345 > output 2> output.err &&
-       test ! -s output.err &&
+       test_must_be_empty output.err &&
        test_cmp expect output
 '
 
@@ -424,7 +424,7 @@ EOF
 
 test_expect_success 'negation of OPT_NONEG flags is not ambiguous' '
        test-parse-options --no-ambig >output 2>output.err &&
-       test ! -s output.err &&
+       test_must_be_empty output.err &&
        test_cmp expect output
 '
 
index b3ae7d52c6c76895434c1d2e8dfb9a31b12ebc62..3e72aff470f8d9e5e79689de459904d1f3271d0d 100755 (executable)
@@ -158,7 +158,7 @@ test_expect_success '3-way not overwriting local changes (their side)' '
 
 '
 
-test_expect_success SYMLINKS 'funny symlink in work tree' '
+test_expect_success 'funny symlink in work tree' '
 
        git reset --hard &&
        git checkout -b sym-b side-b &&
@@ -170,15 +170,14 @@ test_expect_success SYMLINKS 'funny symlink in work tree' '
        rm -fr a &&
        git checkout -b sym-a side-a &&
        mkdir -p a &&
-       ln -s ../b a/b &&
-       git add a/b &&
+       test_ln_s_add ../b a/b &&
        git commit -m "we add a/b" &&
 
        read_tree_u_must_succeed -m -u sym-a sym-a sym-b
 
 '
 
-test_expect_success SYMLINKS,SANITY 'funny symlink in work tree, un-unlink-able' '
+test_expect_success SANITY 'funny symlink in work tree, un-unlink-able' '
 
        rm -fr a b &&
        git reset --hard &&
diff --git a/t/t1513-rev-parse-prefix.sh b/t/t1513-rev-parse-prefix.sh
new file mode 100755 (executable)
index 0000000..87ec3ae
--- /dev/null
@@ -0,0 +1,96 @@
+#!/bin/sh
+
+test_description='Tests for rev-parse --prefix'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+       mkdir -p sub1/sub2 &&
+       echo top >top &&
+       echo file1 >sub1/file1 &&
+       echo file2 >sub1/sub2/file2 &&
+       git add top sub1/file1 sub1/sub2/file2 &&
+       git commit -m commit
+'
+
+test_expect_success 'empty prefix -- file' '
+       git rev-parse --prefix "" -- top sub1/file1 >actual &&
+       cat <<-\EOF >expected &&
+       --
+       top
+       sub1/file1
+       EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'valid prefix -- file' '
+       git rev-parse --prefix sub1/ -- file1 sub2/file2 >actual &&
+       cat <<-\EOF >expected &&
+       --
+       sub1/file1
+       sub1/sub2/file2
+       EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'valid prefix -- ../file' '
+       git rev-parse --prefix sub1/ -- ../top sub2/file2 >actual &&
+       cat <<-\EOF >expected &&
+       --
+       sub1/../top
+       sub1/sub2/file2
+       EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'empty prefix HEAD:./path' '
+       git rev-parse --prefix "" HEAD:./top >actual &&
+       git rev-parse HEAD:top >expected &&
+       test_cmp expected actual
+'
+
+test_expect_success 'valid prefix HEAD:./path' '
+       git rev-parse --prefix sub1/ HEAD:./file1 >actual &&
+       git rev-parse HEAD:sub1/file1 >expected &&
+       test_cmp expected actual
+'
+
+test_expect_success 'valid prefix HEAD:../path' '
+       git rev-parse --prefix sub1/ HEAD:../top >actual &&
+       git rev-parse HEAD:top >expected &&
+       test_cmp expected actual
+'
+
+test_expect_success 'prefix ignored with HEAD:top' '
+       git rev-parse --prefix sub1/ HEAD:top >actual &&
+       git rev-parse HEAD:top >expected &&
+       test_cmp expected actual
+'
+
+test_expect_success 'disambiguate path with valid prefix' '
+       git rev-parse --prefix sub1/ file1 >actual &&
+       cat <<-\EOF >expected &&
+       sub1/file1
+       EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'file and refs with prefix' '
+       git rev-parse --prefix sub1/ master file1 >actual &&
+       cat <<-EOF >expected &&
+       $(git rev-parse master)
+       sub1/file1
+       EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'two-levels deep' '
+       git rev-parse --prefix sub1/sub2/ -- file2 >actual &&
+       cat <<-\EOF >expected &&
+       --
+       sub1/sub2/file2
+       EOF
+       test_cmp expected actual
+'
+
+test_done
index 98aa73e8239355eba098253edfd156d4ea254be2..1fc8e634b737db766dcaf195af9cb8a5d14ef04f 100755 (executable)
@@ -59,10 +59,9 @@ test_expect_success \
     'git read-tree -m $tree1 && git checkout-index -f -a'
 test_debug 'show_files $tree1'
 
-test_expect_success SYMLINKS \
-    'git update-index --add a symlink.' \
-    'ln -s path0 path1 &&
-     git update-index --add path1'
+test_expect_success \
+    'add a symlink' \
+    'test_ln_s_add path0 path1'
 test_expect_success \
     'writing tree out with git write-tree' \
     'tree3=$(git write-tree)'
index 0f4b2896af8b73edcd5bd60631405a430803a40f..f171a5578b6b81e4d7cb03ef0b6b9272ef610ffc 100755 (executable)
@@ -194,11 +194,10 @@ test_expect_success \
  test $(cat ../$s1) = tree1asubdir/path5)
 )'
 
-test_expect_success SYMLINKS \
+test_expect_success \
 'checkout --temp symlink' '
 rm -f path* .merge_* out .git/index &&
-ln -s b a &&
-git update-index --add a &&
+test_ln_s_add b a &&
 t4=$(git write-tree) &&
 rm -f .git/index &&
 git read-tree $t4 &&
index e6f59f1914667f0001fe990656a66bb76e14a41d..fc9aad530e9f9078e95d99da5c1d0849e489d4cf 100755 (executable)
@@ -6,7 +6,7 @@ test_description='git checkout to switch between branches with symlink<->dir'
 
 . ./test-lib.sh
 
-test_expect_success SYMLINKS setup '
+test_expect_success setup '
 
        mkdir frotz &&
        echo hello >frotz/filfre &&
@@ -25,25 +25,25 @@ test_expect_success SYMLINKS setup '
 
        git rm --cached frotz/filfre &&
        mv frotz xyzzy &&
-       ln -s xyzzy frotz &&
-       git add xyzzy/filfre frotz &&
+       test_ln_s_add xyzzy frotz &&
+       git add xyzzy/filfre &&
        test_tick &&
        git commit -m "side moves frotz/ to xyzzy/ and adds frotz->xyzzy/"
 
 '
 
-test_expect_success SYMLINKS 'switch from symlink to dir' '
+test_expect_success 'switch from symlink to dir' '
 
        git checkout master
 
 '
 
-test_expect_success SYMLINKS 'Remove temporary directories & switch to master' '
+test_expect_success 'Remove temporary directories & switch to master' '
        rm -fr frotz xyzzy nitfol &&
        git checkout -f master
 '
 
-test_expect_success SYMLINKS 'switch from dir to symlink' '
+test_expect_success 'switch from dir to symlink' '
 
        git checkout side
 
index 5da63e9fa267af4517024307f4bdeef73caeccf2..c2ada7de37312bffd4b05ff7ac7a9c9e0c4da395 100755 (executable)
@@ -29,21 +29,25 @@ test_expect_success 'checkout commit with dir must not remove untracked a/b' '
        test -f a/b
 '
 
-test_expect_success SYMLINKS 'create a commit where dir a/b changed to symlink' '
+test_expect_success 'create a commit where dir a/b changed to symlink' '
 
        rm -rf a/b &&   # cleanup if previous test failed
        git checkout -f -b symlink start &&
        rm -rf a/b &&
-       ln -s foo a/b &&
        git add -A &&
+       test_ln_s_add foo a/b &&
        git commit -m "dir to symlink"
 '
 
-test_expect_success SYMLINKS 'checkout commit with dir must not remove untracked a/b' '
+test_expect_success 'checkout commit with dir must not remove untracked a/b' '
 
        git rm --cached a/b &&
        git commit -m "un-track the symlink" &&
-       test_must_fail git checkout start &&
+       test_must_fail git checkout start
+'
+
+test_expect_success SYMLINKS 'the symlink remained' '
+
        test -h a/b
 '
 
index b2bd41918ee7f8b19e3ef906f27796f331ed9ea5..9bf2bdffd24b773a2eec636efdd534269f45e74b 100755 (executable)
@@ -96,11 +96,10 @@ test_expect_success 'non-limited update in subdir leaves root alone' '
        test_cmp expect actual
 '
 
-test_expect_success SYMLINKS 'replace a file with a symlink' '
+test_expect_success 'replace a file with a symlink' '
 
        rm foo &&
-       ln -s top foo &&
-       git add -u -- foo
+       test_ln_s_add top foo
 
 '
 
index 95671c205364a12bea02173b33d0d427d5c546fe..262e61744563b2ce4eb14df0e54f71d56c157007 100755 (executable)
@@ -37,71 +37,65 @@ modified without reporting path9 and path10.
 '
 . ./test-lib.sh
 
-date >path0
-if test_have_prereq SYMLINKS
-then
-       ln -s xyzzy path1
-else
-       date > path1
-fi
-mkdir path2 path3
-date >path2/file2
-date >path3/file3
-: >path7
-date >path8
-: >path9
-date >path10
-test_expect_success \
-    'git update-index --add to add various paths.' \
-    "git update-index --add -- path0 path1 path?/file? path7 path8 path9 path10"
-
-rm -fr path? ;# leave path10 alone
-date >path2
-if test_have_prereq SYMLINKS
-then
-       ln -s frotz path3
-       ln -s nitfol path5
-else
-       date > path3
-       date > path5
-fi
-mkdir path0 path1 path6
-date >path0/file0
-date >path1/file1
-date >path6/file6
-date >path7
-: >path8
-: >path9
-touch path10
+test_expect_success 'git update-index --add to add various paths.' '
+       date >path0 &&
+       test_ln_s_add xyzzy path1 &&
+       mkdir path2 path3 &&
+       date >path2/file2 &&
+       date >path3/file3 &&
+       : >path7 &&
+       date >path8 &&
+       : >path9 &&
+       date >path10 &&
+       git update-index --add -- path0 path?/file? path7 path8 path9 path10 &&
+       rm -fr path?    # leave path10 alone
+'
 
-test_expect_success \
-    'git ls-files -k to show killed files.' \
-    'git ls-files -k >.output'
-cat >.expected <<EOF
-path0/file0
-path1/file1
-path2
-path3
-EOF
+test_expect_success 'git ls-files -k to show killed files.' '
+       date >path2 &&
+       if test_have_prereq SYMLINKS
+       then
+               ln -s frotz path3 &&
+               ln -s nitfol path5
+       else
+               date >path3 &&
+               date >path5
+       fi &&
+       mkdir path0 path1 path6 &&
+       date >path0/file0 &&
+       date >path1/file1 &&
+       date >path6/file6 &&
+       date >path7 &&
+       : >path8 &&
+       : >path9 &&
+       touch path10 &&
+       git ls-files -k >.output
+'
 
-test_expect_success \
-    'validate git ls-files -k output.' \
-    'test_cmp .expected .output'
+test_expect_success 'validate git ls-files -k output.' '
+       cat >.expected <<-\EOF &&
+       path0/file0
+       path1/file1
+       path2
+       path3
+       EOF
+       test_cmp .expected .output
+'
 
-test_expect_success \
-    'git ls-files -m to show modified files.' \
-    'git ls-files -m >.output'
-cat >.expected <<EOF
-path0
-path1
-path2/file2
-path3/file3
-path7
-path8
-EOF
+test_expect_success 'git ls-files -m to show modified files.' '
+       git ls-files -m >.output
+'
 
-test_expect_success \
-    'validate git ls-files -m output.' \
-    'test_cmp .expected .output'
+test_expect_success 'validate git ls-files -m output.' '
+       cat >.expected <<-\EOF &&
+       path0
+       path1
+       path2/file2
+       path3/file3
+       path7
+       path8
+       EOF
+       test_cmp .expected .output
+'
 
 test_done
index a5e3da7e419e6f13ea0960722b4c1c712a995112..2f96100a5f655bbea859bf596ff9835c9abb11a2 100755 (executable)
@@ -25,10 +25,7 @@ test_expect_success 'setup 1' '
        git branch submod &&
        git branch copy &&
        git branch rename &&
-       if test_have_prereq SYMLINKS
-       then
-               git branch rename-ln
-       fi &&
+       git branch rename-ln &&
 
        echo hello >>a &&
        cp a d/e &&
@@ -260,16 +257,12 @@ test_expect_success 'setup 8' '
        git add e &&
        test_tick &&
        git commit -m "rename a->e" &&
-       if test_have_prereq SYMLINKS
-       then
-               git checkout rename-ln &&
-               git mv a e &&
-               ln -s e a &&
-               git add a e &&
-               test_tick &&
-               git commit -m "rename a->e, symlink a->e" &&
-               oln=`printf e | git hash-object --stdin`
-       fi
+       git checkout rename-ln &&
+       git mv a e &&
+       test_ln_s_add e a &&
+       test_tick &&
+       git commit -m "rename a->e, symlink a->e" &&
+       oln=`printf e | git hash-object --stdin`
 '
 
 test_expect_success 'setup 9' '
@@ -569,28 +562,25 @@ test_expect_success 'merge-recursive copy vs. rename' '
        test_cmp expected actual
 '
 
-if test_have_prereq SYMLINKS
-then
-       test_expect_failure 'merge-recursive rename vs. rename/symlink' '
-
-               git checkout -f rename &&
-               git merge rename-ln &&
-               ( git ls-tree -r HEAD ; git ls-files -s ) >actual &&
-               (
-                       echo "120000 blob $oln  a"
-                       echo "100644 blob $o0   b"
-                       echo "100644 blob $o0   c"
-                       echo "100644 blob $o0   d/e"
-                       echo "100644 blob $o0   e"
-                       echo "120000 $oln 0     a"
-                       echo "100644 $o0 0      b"
-                       echo "100644 $o0 0      c"
-                       echo "100644 $o0 0      d/e"
-                       echo "100644 $o0 0      e"
-               ) >expected &&
-               test_cmp expected actual
-       '
-fi
+test_expect_failure 'merge-recursive rename vs. rename/symlink' '
+
+       git checkout -f rename &&
+       git merge rename-ln &&
+       ( git ls-tree -r HEAD ; git ls-files -s ) >actual &&
+       (
+               echo "120000 blob $oln  a"
+               echo "100644 blob $o0   b"
+               echo "100644 blob $o0   c"
+               echo "100644 blob $o0   d/e"
+               echo "100644 blob $o0   e"
+               echo "120000 $oln 0     a"
+               echo "100644 $o0 0      b"
+               echo "100644 $o0 0      c"
+               echo "100644 $o0 0      d/e"
+               echo "100644 $o0 0      e"
+       ) >expected &&
+       test_cmp expected actual
+'
 
 
 test_done
index 81d90b66c50e75323a44aaf5e2e067d5a3569e6a..eb73c06a4e1ee826684ee84efa134ee5517023a6 100755 (executable)
@@ -22,20 +22,8 @@ test_expect_success \
     'setup' \
     'mkdir path2 path2/baz &&
      echo Hi >path0 &&
-     if test_have_prereq SYMLINKS
-     then
-       ln -s path0 path1 &&
-       ln -s ../path1 path2/bazbo
-       make_expected () {
-               cat >expected
-       }
-     else
-       printf path0 > path1 &&
-       printf ../path1 > path2/bazbo
-       make_expected () {
-               sed -e "s/120000 /100644 /" >expected
-       }
-     fi &&
+     test_ln_s_add path0 path1 &&
+     test_ln_s_add ../path1 path2/bazbo &&
      echo Lo >path2/foo &&
      echo Mi >path2/baz/b &&
      find path? \( -type f -o -type l \) -print |
@@ -51,7 +39,7 @@ test_output () {
 test_expect_success \
     'ls-tree plain' \
     'git ls-tree $tree >current &&
-     make_expected <<\EOF &&
+     cat >expected <<\EOF &&
 100644 blob X  path0
 120000 blob X  path1
 040000 tree X  path2
@@ -61,7 +49,7 @@ EOF
 test_expect_success \
     'ls-tree recursive' \
     'git ls-tree -r $tree >current &&
-     make_expected <<\EOF &&
+     cat >expected <<\EOF &&
 100644 blob X  path0
 120000 blob X  path1
 100644 blob X  path2/baz/b
@@ -73,7 +61,7 @@ EOF
 test_expect_success \
     'ls-tree recursive with -t' \
     'git ls-tree -r -t $tree >current &&
-     make_expected <<\EOF &&
+     cat >expected <<\EOF &&
 100644 blob X  path0
 120000 blob X  path1
 040000 tree X  path2
@@ -87,7 +75,7 @@ EOF
 test_expect_success \
     'ls-tree recursive with -d' \
     'git ls-tree -r -d $tree >current &&
-     make_expected <<\EOF &&
+     cat >expected <<\EOF &&
 040000 tree X  path2
 040000 tree X  path2/baz
 EOF
@@ -96,7 +84,7 @@ EOF
 test_expect_success \
     'ls-tree filtered with path' \
     'git ls-tree $tree path >current &&
-     make_expected <<\EOF &&
+     cat >expected <<\EOF &&
 EOF
      test_output'
 
@@ -106,7 +94,7 @@ EOF
 test_expect_success \
     'ls-tree filtered with path1 path0' \
     'git ls-tree $tree path1 path0 >current &&
-     make_expected <<\EOF &&
+     cat >expected <<\EOF &&
 100644 blob X  path0
 120000 blob X  path1
 EOF
@@ -115,7 +103,7 @@ EOF
 test_expect_success \
     'ls-tree filtered with path0/' \
     'git ls-tree $tree path0/ >current &&
-     make_expected <<\EOF &&
+     cat >expected <<\EOF &&
 EOF
      test_output'
 
@@ -124,7 +112,7 @@ EOF
 test_expect_success \
     'ls-tree filtered with path2' \
     'git ls-tree $tree path2 >current &&
-     make_expected <<\EOF &&
+     cat >expected <<\EOF &&
 040000 tree X  path2
 EOF
      test_output'
@@ -133,7 +121,7 @@ EOF
 test_expect_success \
     'ls-tree filtered with path2/' \
     'git ls-tree $tree path2/ >current &&
-     make_expected <<\EOF &&
+     cat >expected <<\EOF &&
 040000 tree X  path2/baz
 120000 blob X  path2/bazbo
 100644 blob X  path2/foo
@@ -145,7 +133,7 @@ EOF
 test_expect_success \
     'ls-tree filtered with path2/baz' \
     'git ls-tree $tree path2/baz >current &&
-     make_expected <<\EOF &&
+     cat >expected <<\EOF &&
 040000 tree X  path2/baz
 EOF
      test_output'
@@ -153,14 +141,14 @@ EOF
 test_expect_success \
     'ls-tree filtered with path2/bak' \
     'git ls-tree $tree path2/bak >current &&
-     make_expected <<\EOF &&
+     cat >expected <<\EOF &&
 EOF
      test_output'
 
 test_expect_success \
     'ls-tree -t filtered with path2/bak' \
     'git ls-tree -t $tree path2/bak >current &&
-     make_expected <<\EOF &&
+     cat >expected <<\EOF &&
 040000 tree X  path2
 EOF
      test_output'
@@ -168,7 +156,7 @@ EOF
 test_expect_success \
     'ls-tree with one path a prefix of the other' \
     'git ls-tree $tree path2/baz path2/bazbo >current &&
-     make_expected <<\EOF &&
+     cat >expected <<\EOF &&
 040000 tree X  path2/baz
 120000 blob X  path2/bazbo
 EOF
index b58fa1a23243e435f119afb9966b0a80cbc1e4d4..ebf93b0695dfac37fc276e1f07377d858eb3e1da 100755 (executable)
@@ -40,13 +40,6 @@ test_expect_success 'prepare repository with topic branches' '
        echo Side >>C &&
        git add C &&
        git commit -m "Add C" &&
-       git checkout -b nonlinear my-topic-branch &&
-       echo Edit >>B &&
-       git add B &&
-       git commit -m "Modify B" &&
-       git merge side &&
-       git checkout -b upstream-merged-nonlinear &&
-       git merge master &&
        git checkout -f my-topic-branch &&
        git tag topic
 '
@@ -66,26 +59,15 @@ test_expect_success 'rebase against master' '
        git rebase master
 '
 
-test_expect_success 'rebase against master twice' '
-       git rebase master >out &&
-       test_i18ngrep "Current branch my-topic-branch is up to date" out
-'
-
-test_expect_success 'rebase against master twice with --force' '
-       git rebase --force-rebase master >out &&
-       test_i18ngrep "Current branch my-topic-branch is up to date, rebase forced" out
-'
-
-test_expect_success 'rebase against master twice from another branch' '
-       git checkout my-topic-branch^ &&
-       git rebase master my-topic-branch >out &&
-       test_i18ngrep "Current branch my-topic-branch is up to date" out
-'
-
-test_expect_success 'rebase fast-forward to master' '
-       git checkout my-topic-branch^ &&
-       git rebase my-topic-branch >out &&
-       test_i18ngrep "Fast-forwarded HEAD to my-topic-branch" out
+test_expect_success 'rebase, with <onto> and <upstream> specified as :/quuxery' '
+       test_when_finished "git branch -D torebase" &&
+       git checkout -b torebase my-topic-branch^ &&
+       upstream=$(git rev-parse ":/Add B") &&
+       onto=$(git rev-parse ":/Add A") &&
+       git rebase --onto $onto $upstream &&
+       git reset --hard my-topic-branch^ &&
+       git rebase --onto ":/Add A" ":/Add B" &&
+       git checkout my-topic-branch
 '
 
 test_expect_success 'the rebase operation should not have destroyed author information' '
@@ -106,31 +88,9 @@ test_expect_success 'rebase from ambiguous branch name' '
        git rebase master
 '
 
-test_expect_success 'rebase after merge master' '
-       git checkout --detach refs/tags/topic &&
-       git branch -D topic &&
-       git reset --hard topic &&
-       git merge master &&
-       git rebase master &&
-       ! (git show | grep "^Merge:")
-'
-
-test_expect_success 'rebase of history with merges is linearized' '
-       git checkout nonlinear &&
-       test 4 = $(git rev-list master.. | wc -l) &&
-       git rebase master &&
-       test 3 = $(git rev-list master.. | wc -l)
-'
-
-test_expect_success 'rebase of history with merges after upstream merge is linearized' '
-       git checkout upstream-merged-nonlinear &&
-       test 5 = $(git rev-list master.. | wc -l) &&
-       git rebase master &&
-       test 3 = $(git rev-list master.. | wc -l)
-'
-
 test_expect_success 'rebase a single mode change' '
        git checkout master &&
+       git branch -D topic &&
        echo 1 >X &&
        git add X &&
        test_tick &&
@@ -185,7 +145,7 @@ test_expect_success 'default to @{upstream} when upstream arg is missing' '
 test_expect_success 'rebase -q is quiet' '
        git checkout -b quiet topic &&
        git rebase -q master >output.out 2>&1 &&
-       test ! -s output.out
+       test_must_be_empty output.out
 '
 
 test_expect_success 'Rebase a commit that sprinkles CRs in' '
diff --git a/t/t3401-rebase-partial.sh b/t/t3401-rebase-partial.sh
deleted file mode 100755 (executable)
index 58f4823..0000000
+++ /dev/null
@@ -1,69 +0,0 @@
-#!/bin/sh
-#
-# Copyright (c) 2006 Yann Dirson, based on t3400 by Amos Waterland
-#
-
-test_description='git rebase should detect patches integrated upstream
-
-This test cherry-picks one local change of two into master branch, and
-checks that git rebase succeeds with only the second patch in the
-local branch.
-'
-. ./test-lib.sh
-
-test_expect_success 'prepare repository with topic branch' '
-       test_commit A &&
-       git checkout -b my-topic-branch &&
-       test_commit B &&
-       test_commit C &&
-       git checkout -f master &&
-       test_commit A2 A.t
-'
-
-test_expect_success 'pick top patch from topic branch into master' '
-       git cherry-pick C &&
-       git checkout -f my-topic-branch
-'
-
-test_debug '
-       git cherry master &&
-       git format-patch -k --stdout --full-index master >/dev/null &&
-       gitk --all & sleep 1
-'
-
-test_expect_success 'rebase topic branch against new master and check git am did not get halted' '
-       git rebase master &&
-       test_path_is_missing .git/rebase-apply
-'
-
-test_expect_success 'rebase --merge topic branch that was partially merged upstream' '
-       git reset --hard C &&
-       git rebase --merge master &&
-       test_path_is_missing .git/rebase-merge
-'
-
-test_expect_success 'rebase ignores empty commit' '
-       git reset --hard A &&
-       git commit --allow-empty -m empty &&
-       test_commit D &&
-       git rebase C &&
-       test "$(git log --format=%s C..)" = "D"
-'
-
-test_expect_success 'rebase --keep-empty' '
-       git reset --hard D &&
-       git rebase --keep-empty C &&
-       test "$(git log --format=%s C..)" = "D
-empty"
-'
-
-test_expect_success 'rebase --keep-empty keeps empty even if already in upstream' '
-       git reset --hard A &&
-       git commit --allow-empty -m also-empty &&
-       git rebase --keep-empty D &&
-       test "$(git log --format=%s A..)" = "also-empty
-D
-empty"
-'
-
-test_done
index 79e8d3c5966bad34b317c6e35c301ecfaa3ab648..d6b414377306de88e8e294dd5eaadfcb3a234ec2 100755 (executable)
@@ -477,19 +477,11 @@ test_expect_success 'interrupted squash works as expected (case 2)' '
        test $one = $(git rev-parse HEAD~2)
 '
 
-test_expect_success 'ignore patch if in upstream' '
-       HEAD=$(git rev-parse HEAD) &&
-       git checkout -b has-cherry-picked HEAD^ &&
+test_expect_success '--continue tries to commit, even for "edit"' '
        echo unrelated > file7 &&
        git add file7 &&
        test_tick &&
        git commit -m "unrelated change" &&
-       git cherry-pick $HEAD &&
-       EXPECT_COUNT=1 git rebase -i $HEAD &&
-       test $HEAD = $(git rev-parse HEAD^)
-'
-
-test_expect_success '--continue tries to commit, even for "edit"' '
        parent=$(git rev-parse HEAD^) &&
        test_tick &&
        FAKE_LINES="edit 1" git rebase -i HEAD^ &&
@@ -947,4 +939,15 @@ test_expect_success 'rebase -i respects core.commentchar' '
        test B = $(git cat-file commit HEAD^ | sed -ne \$p)
 '
 
+test_expect_success 'rebase -i, with <onto> and <upstream> specified as :/quuxery' '
+       test_when_finished "git branch -D torebase" &&
+       git checkout -b torebase branch1 &&
+       upstream=$(git rev-parse ":/J") &&
+       onto=$(git rev-parse ":/A") &&
+       git rebase --onto $onto $upstream &&
+       git reset --hard branch1 &&
+       git rebase --onto ":/A" ":/J" &&
+       git checkout branch1
+'
+
 test_done
index e6a9a0d436f3a28a7de6eaadd7f1f223ae740c64..0392e36d2364c8149f2a628a0901f113b7a5875f 100755 (executable)
@@ -4,27 +4,17 @@ test_description='messages from rebase operation'
 
 . ./test-lib.sh
 
-quick_one () {
-       echo "$1" >"file$1" &&
-       git add "file$1" &&
-       test_tick &&
-       git commit -m "$1"
-}
+test_expect_success 'setup' '
+       test_commit O fileO &&
+       test_commit X fileX &&
+       test_commit A fileA &&
+       test_commit B fileB &&
+       test_commit Y fileY &&
 
-test_expect_success setup '
-       quick_one O &&
-       git branch topic &&
-       quick_one X &&
-       quick_one A &&
-       quick_one B &&
-       quick_one Y &&
-
-       git checkout topic &&
-       quick_one A &&
-       quick_one B &&
-       quick_one Z &&
+       git checkout -b topic O &&
+       git cherry-pick A B &&
+       test_commit Z fileZ &&
        git tag start
-
 '
 
 cat >expect <<\EOF
@@ -34,12 +24,32 @@ Committed: 0003 Z
 EOF
 
 test_expect_success 'rebase -m' '
-
        git rebase -m master >report &&
        sed -n -e "/^Already applied: /p" \
                -e "/^Committed: /p" report >actual &&
        test_cmp expect actual
+'
+
+test_expect_success 'rebase against master twice' '
+       git rebase master >out &&
+       test_i18ngrep "Current branch topic is up to date" out
+'
+
+test_expect_success 'rebase against master twice with --force' '
+       git rebase --force-rebase master >out &&
+       test_i18ngrep "Current branch topic is up to date, rebase forced" out
+'
+
+test_expect_success 'rebase against master twice from another branch' '
+       git checkout topic^ &&
+       git rebase master topic >out &&
+       test_i18ngrep "Current branch topic is up to date" out
+'
 
+test_expect_success 'rebase fast-forward to master' '
+       git checkout topic^ &&
+       git rebase topic >out &&
+       test_i18ngrep "Fast-forwarded HEAD to topic" out
 '
 
 test_expect_success 'rebase --stat' '
index 6de4e2263f9ec65d2de2e28af6a12d5c1065f686..2e0c36415fe525663fdcc64675fd081c642732ed 100755 (executable)
@@ -11,14 +11,6 @@ Run "git rebase -p" and check that merges are properly carried along
 GIT_AUTHOR_EMAIL=bogus_email_address
 export GIT_AUTHOR_EMAIL
 
-# Clone 1 (trivial merge):
-#
-# A1--A2  <-- origin/master
-#  \   \
-#   B1--M  <-- topic
-#    \
-#     B2  <-- origin/topic
-#
 # Clone 2 (conflicting merge):
 #
 # A1--A2--B3   <-- origin/master
@@ -36,16 +28,6 @@ export GIT_AUTHOR_EMAIL
 #     \--A3    <-- topic2
 #      \
 #       B2     <-- origin/topic
-#
-# Clone 4 (merge using second parent as base):
-#
-# A1--A2--B3   <-- origin/master
-#  \
-#   B1--A3--M  <-- topic
-#    \     /
-#     \--A4    <-- topic2
-#      \
-#       B2     <-- origin/topic
 
 test_expect_success 'setup for merge-preserving rebase' \
        'echo First > A &&
@@ -58,20 +40,6 @@ test_expect_success 'setup for merge-preserving rebase' \
        git checkout -f master &&
        echo Third >> A &&
        git commit -a -m "Modify A2" &&
-
-       git clone ./. clone1 &&
-       (cd clone1 &&
-       git checkout -b topic origin/topic &&
-       git merge origin/master
-       ) &&
-
-       git clone ./. clone4 &&
-       (
-               cd clone4 &&
-               git checkout -b topic origin/topic &&
-               git merge origin/master
-       ) &&
-
        echo Fifth > B &&
        git add B &&
        git commit -m "Add different B" &&
@@ -101,16 +69,6 @@ test_expect_success 'setup for merge-preserving rebase' \
        git commit -a -m "Modify B2"
 '
 
-test_expect_success 'rebase -p fakes interactive rebase' '
-       (
-       cd clone1 &&
-       git fetch &&
-       git rebase -p origin/topic &&
-       test 1 = $(git rev-list --all --pretty=oneline | grep "Modify A" | wc -l) &&
-       test 1 = $(git rev-list --all --pretty=oneline | grep "Merge remote-tracking branch " | wc -l)
-       )
-'
-
 test_expect_success '--continue works after a conflict' '
        (
        cd clone2 &&
@@ -138,15 +96,4 @@ test_expect_success 'rebase -p preserves no-ff merges' '
        )
 '
 
-test_expect_success 'rebase -p works when base inside second parent' '
-       (
-       cd clone4 &&
-       git fetch &&
-       git rebase -p HEAD^2 &&
-       test 1 = $(git rev-list --all --pretty=oneline | grep "Modify A" | wc -l) &&
-       test 1 = $(git rev-list --all --pretty=oneline | grep "Modify B" | wc -l) &&
-       test 1 = $(git rev-list --all --pretty=oneline | grep "Merge remote-tracking branch " | wc -l)
-       )
-'
-
 test_done
index 479cbb215fc90e5d5741fca830c62b8b4e8121e1..90eb26493cd309da34687dffc939166069cd88c6 100755 (executable)
@@ -141,6 +141,28 @@ testrebase() {
        '
 }
 
+test_expect_success "rebase: fast-forward rebase" '
+       test_config rebase.autostash true &&
+       git reset --hard &&
+       git checkout -b behind-feature-branch feature-branch~1 &&
+       test_when_finished git branch -D behind-feature-branch &&
+       echo dirty >>file1 &&
+       git rebase feature-branch &&
+       grep dirty file1 &&
+       git checkout feature-branch
+'
+
+test_expect_success "rebase: noop rebase" '
+       test_config rebase.autostash true &&
+       git reset --hard &&
+       git checkout -b same-feature-branch feature-branch &&
+       test_when_finished git branch -D same-feature-branch &&
+       echo dirty >>file1 &&
+       git rebase feature-branch &&
+       grep dirty file1 &&
+       git checkout feature-branch
+'
+
 testrebase "" .git/rebase-apply
 testrebase " --merge" .git/rebase-merge
 testrebase " --interactive" .git/rebase-merge
diff --git a/t/t3421-rebase-topology-linear.sh b/t/t3421-rebase-topology-linear.sh
new file mode 100755 (executable)
index 0000000..9c55cba
--- /dev/null
@@ -0,0 +1,350 @@
+#!/bin/sh
+
+test_description='basic rebase topology tests'
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-rebase.sh
+
+# a---b---c
+#      \
+#       d---e
+test_expect_success 'setup' '
+       test_commit a &&
+       test_commit b &&
+       test_commit c &&
+       git checkout b &&
+       test_commit d &&
+       test_commit e
+'
+
+test_run_rebase () {
+       result=$1
+       shift
+       test_expect_$result "simple rebase $*" "
+               reset_rebase &&
+               git rebase $* c e &&
+               test_cmp_rev c HEAD~2 &&
+               test_linear_range 'd e' c..
+       "
+}
+test_run_rebase success ''
+test_run_rebase success -m
+test_run_rebase success -i
+test_run_rebase success -p
+
+test_run_rebase () {
+       result=$1
+       shift
+       test_expect_$result "rebase $* is no-op if upstream is an ancestor" "
+               reset_rebase &&
+               git rebase $* b e &&
+               test_cmp_rev e HEAD
+       "
+}
+test_run_rebase success ''
+test_run_rebase success -m
+test_run_rebase success -i
+test_run_rebase success -p
+
+test_run_rebase () {
+       result=$1
+       shift
+       test_expect_$result "rebase $* -f rewrites even if upstream is an ancestor" "
+               reset_rebase &&
+               git rebase $* -f b e &&
+               ! test_cmp_rev e HEAD &&
+               test_cmp_rev b HEAD~2 &&
+               test_linear_range 'd e' b..
+       "
+}
+test_run_rebase success ''
+test_run_rebase success -m
+test_run_rebase success -i
+test_run_rebase failure -p
+
+test_run_rebase () {
+       result=$1
+       shift
+       test_expect_$result "rebase $* fast-forwards from ancestor of upstream" "
+               reset_rebase &&
+               git rebase $* e b &&
+               test_cmp_rev e HEAD
+       "
+}
+test_run_rebase success ''
+test_run_rebase success -m
+test_run_rebase success -i
+test_run_rebase success -p
+
+#       f
+#      /
+# a---b---c---g---h
+#      \
+#       d---gp--i
+#
+# gp = cherry-picked g
+# h = reverted g
+#
+# Reverted patches are there for tests to be able to check if a commit
+# that introduced the same change as another commit is
+# dropped. Without reverted commits, we could get false positives
+# because applying the patch succeeds, but simply results in no
+# changes.
+test_expect_success 'setup of linear history for range selection tests' '
+       git checkout c &&
+       test_commit g &&
+       revert h g &&
+       git checkout d &&
+       cherry_pick gp g &&
+       test_commit i &&
+       git checkout b &&
+       test_commit f
+'
+
+test_run_rebase () {
+       result=$1
+       shift
+       test_expect_$result "rebase $* drops patches in upstream" "
+               reset_rebase &&
+               git rebase $* h i &&
+               test_cmp_rev h HEAD~2 &&
+               test_linear_range 'd i' h..
+       "
+}
+test_run_rebase success ''
+test_run_rebase failure -m
+test_run_rebase success -i
+test_run_rebase success -p
+
+test_run_rebase () {
+       result=$1
+       shift
+       test_expect_$result "rebase $* can drop last patch if in upstream" "
+               reset_rebase &&
+               git rebase $* h gp &&
+               test_cmp_rev h HEAD^ &&
+               test_linear_range 'd' h..
+       "
+}
+test_run_rebase success ''
+test_run_rebase failure -m
+test_run_rebase success -i
+test_run_rebase success -p
+
+test_run_rebase () {
+       result=$1
+       shift
+       test_expect_$result "rebase $* --onto drops patches in upstream" "
+               reset_rebase &&
+               git rebase $* --onto f h i &&
+               test_cmp_rev f HEAD~2 &&
+               test_linear_range 'd i' f..
+       "
+}
+test_run_rebase success ''
+test_run_rebase failure -m
+test_run_rebase success -i
+test_run_rebase success -p
+
+test_run_rebase () {
+       result=$1
+       shift
+       test_expect_$result "rebase $* --onto does not drop patches in onto" "
+               reset_rebase &&
+               git rebase $* --onto h f i &&
+               test_cmp_rev h HEAD~3 &&
+               test_linear_range 'd gp i' h..
+       "
+}
+test_run_rebase success ''
+test_run_rebase success -m
+test_run_rebase success -i
+test_run_rebase success -p
+
+# a---b---c---j!
+#      \
+#       d---k!--l
+#
+# ! = empty
+test_expect_success 'setup of linear history for empty commit tests' '
+       git checkout c &&
+       make_empty j &&
+       git checkout d &&
+       make_empty k &&
+       test_commit l
+'
+
+test_run_rebase () {
+       result=$1
+       shift
+       test_expect_$result "rebase $* drops empty commit" "
+               reset_rebase &&
+               git rebase $* c l &&
+               test_cmp_rev c HEAD~2 &&
+               test_linear_range 'd l' c..
+       "
+}
+test_run_rebase success ''
+test_run_rebase success -m
+test_run_rebase success -i
+test_run_rebase success -p
+
+test_run_rebase () {
+       result=$1
+       shift
+       test_expect_$result "rebase $* --keep-empty" "
+               reset_rebase &&
+               git rebase $* --keep-empty c l &&
+               test_cmp_rev c HEAD~3 &&
+               test_linear_range 'd k l' c..
+       "
+}
+test_run_rebase success ''
+test_run_rebase failure -m
+test_run_rebase success -i
+test_run_rebase failure -p
+
+test_run_rebase () {
+       result=$1
+       shift
+       test_expect_$result "rebase $* --keep-empty keeps empty even if already in upstream" "
+               reset_rebase &&
+               git rebase $* --keep-empty j l &&
+               test_cmp_rev j HEAD~3 &&
+               test_linear_range 'd k l' j..
+       "
+}
+test_run_rebase success ''
+test_run_rebase failure -m
+test_run_rebase failure -i
+test_run_rebase failure -p
+
+#       m
+#      /
+# a---b---c---g
+#
+# x---y---bp
+#
+# bp = cherry-picked b
+# m = reverted b
+#
+# Reverted patches are there for tests to be able to check if a commit
+# that introduced the same change as another commit is
+# dropped. Without reverted commits, we could get false positives
+# because applying the patch succeeds, but simply results in no
+# changes.
+test_expect_success 'setup of linear history for test involving root' '
+       git checkout b &&
+       revert m b &&
+       git checkout --orphan disjoint &&
+       git rm -rf . &&
+       test_commit x &&
+       test_commit y &&
+       cherry_pick bp b
+'
+
+test_run_rebase () {
+       result=$1
+       shift
+       test_expect_$result "rebase $* --onto --root" "
+               reset_rebase &&
+               git rebase $* --onto c --root y &&
+               test_cmp_rev c HEAD~2 &&
+               test_linear_range 'x y' c..
+       "
+}
+test_run_rebase success ''
+test_run_rebase failure -m
+test_run_rebase success -i
+test_run_rebase success -p
+
+test_run_rebase () {
+       result=$1
+       shift
+       test_expect_$result "rebase $* without --onto --root with disjoint history" "
+               reset_rebase &&
+               git rebase $* c y &&
+               test_cmp_rev c HEAD~2 &&
+               test_linear_range 'x y' c..
+       "
+}
+test_run_rebase success ''
+test_run_rebase failure -m
+test_run_rebase success -i
+test_run_rebase failure -p
+
+test_run_rebase () {
+       result=$1
+       shift
+       test_expect_$result "rebase $* --onto --root drops patch in onto" "
+               reset_rebase &&
+               git rebase $* --onto m --root bp &&
+               test_cmp_rev m HEAD~2 &&
+               test_linear_range 'x y' m..
+       "
+}
+test_run_rebase success ''
+test_run_rebase failure -m
+test_run_rebase success -i
+test_run_rebase success -p
+
+test_run_rebase () {
+       result=$1
+       shift
+       test_expect_$result "rebase $* --onto --root with merge-base does not go to root" "
+               reset_rebase &&
+               git rebase $* --onto m --root g &&
+               test_cmp_rev m HEAD~2 &&
+               test_linear_range 'c g' m..
+       "
+}
+
+test_run_rebase success ''
+test_run_rebase success -m
+test_run_rebase success -i
+test_run_rebase failure -p
+
+test_run_rebase () {
+       result=$1
+       shift
+       test_expect_$result "rebase $* without --onto --root with disjoint history drops patch in onto" "
+               reset_rebase &&
+               git rebase $* m bp &&
+               test_cmp_rev m HEAD~2 &&
+               test_linear_range 'x y' m..
+       "
+}
+test_run_rebase success ''
+test_run_rebase failure -m
+test_run_rebase success -i
+test_run_rebase failure -p
+
+test_run_rebase () {
+       result=$1
+       shift
+       test_expect_$result "rebase $* --root on linear history is a no-op" "
+               reset_rebase &&
+               git rebase $* --root c &&
+               test_cmp_rev c HEAD
+       "
+}
+test_run_rebase failure ''
+test_run_rebase failure -m
+test_run_rebase failure -i
+test_run_rebase failure -p
+
+test_run_rebase () {
+       result=$1
+       shift
+       test_expect_$result "rebase $* -f --root on linear history causes re-write" "
+               reset_rebase &&
+               git rebase $* -f --root c &&
+               ! test_cmp_rev a HEAD~2 &&
+               test_linear_range 'a b c' HEAD
+       "
+}
+test_run_rebase success ''
+test_run_rebase success -m
+test_run_rebase success -i
+test_run_rebase success -p
+
+test_done
diff --git a/t/t3425-rebase-topology-merges.sh b/t/t3425-rebase-topology-merges.sh
new file mode 100755 (executable)
index 0000000..1d195fb
--- /dev/null
@@ -0,0 +1,258 @@
+#!/bin/sh
+
+test_description='rebase topology tests with merges'
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-rebase.sh
+
+test_revision_subjects () {
+       expected="$1"
+       shift
+       set -- $(git log --format=%s --no-walk=unsorted "$@")
+       test "$expected" = "$*"
+}
+
+# a---b-----------c
+#      \           \
+#       d-------e   \
+#        \       \   \
+#         n---o---w---v
+#              \
+#               z
+test_expect_success 'setup of non-linear-history' '
+       test_commit a &&
+       test_commit b &&
+       test_commit c &&
+       git checkout b &&
+       test_commit d &&
+       test_commit e
+
+       git checkout c &&
+       test_commit g &&
+       revert h g &&
+       git checkout d &&
+       cherry_pick gp g &&
+       test_commit i &&
+       git checkout b &&
+       test_commit f
+
+       git checkout d &&
+       test_commit n &&
+       test_commit o &&
+       test_merge w e &&
+       test_merge v c &&
+       git checkout o &&
+       test_commit z
+'
+
+test_run_rebase () {
+       result=$1
+       shift
+       test_expect_$result "rebase $* after merge from upstream" "
+               reset_rebase &&
+               git rebase $* e w &&
+               test_cmp_rev e HEAD~2 &&
+               test_linear_range 'n o' e..
+       "
+}
+test_run_rebase success ''
+test_run_rebase success -m
+test_run_rebase success -i
+
+test_run_rebase () {
+       result=$1
+       shift
+       expected=$1
+       shift
+       test_expect_$result "rebase $* of non-linear history is linearized in place" "
+               reset_rebase &&
+               git rebase $* d w &&
+               test_cmp_rev d HEAD~3 &&
+               test_linear_range "\'"$expected"\'" d..
+       "
+}
+#TODO: make order consistent across all flavors of rebase
+test_run_rebase success 'e n o' ''
+test_run_rebase success 'e n o' -m
+test_run_rebase success 'n o e' -i
+
+test_run_rebase () {
+       result=$1
+       shift
+       expected=$1
+       shift
+       test_expect_$result "rebase $* of non-linear history is linearized upstream" "
+               reset_rebase &&
+               git rebase $* c w &&
+               test_cmp_rev c HEAD~4 &&
+               test_linear_range "\'"$expected"\'" c..
+       "
+}
+#TODO: make order consistent across all flavors of rebase
+test_run_rebase success 'd e n o' ''
+test_run_rebase success 'd e n o' -m
+test_run_rebase success 'd n o e' -i
+
+test_run_rebase () {
+       result=$1
+       shift
+       expected=$1
+       shift
+       test_expect_$result "rebase $* of non-linear history with merges after upstream merge is linearized" "
+               reset_rebase &&
+               git rebase $* c v &&
+               test_cmp_rev c HEAD~4 &&
+               test_linear_range "\'"$expected"\'" c..
+       "
+}
+#TODO: make order consistent across all flavors of rebase
+test_run_rebase success 'd e n o' ''
+test_run_rebase success 'd e n o' -m
+test_run_rebase success 'd n o e' -i
+
+test_expect_success "rebase -p is no-op in non-linear history" "
+       reset_rebase &&
+       git rebase -p d w &&
+       test_cmp_rev w HEAD
+"
+
+test_expect_success "rebase -p is no-op when base inside second parent" "
+       reset_rebase &&
+       git rebase -p e w &&
+       test_cmp_rev w HEAD
+"
+
+test_expect_failure "rebase -p --root on non-linear history is a no-op" "
+       reset_rebase &&
+       git rebase -p --root w &&
+       test_cmp_rev w HEAD
+"
+
+test_expect_success "rebase -p re-creates merge from side branch" "
+       reset_rebase &&
+       git rebase -p z w &&
+       test_cmp_rev z HEAD^ &&
+       test_cmp_rev w^2 HEAD^2
+"
+
+test_expect_success "rebase -p re-creates internal merge" "
+       reset_rebase &&
+       git rebase -p c w &&
+       test_cmp_rev c HEAD~4 &&
+       test_cmp_rev HEAD^2^ HEAD~3 &&
+       test_revision_subjects 'd n e o w' HEAD~3 HEAD~2 HEAD^2 HEAD^ HEAD
+"
+
+test_expect_success "rebase -p can re-create two branches on onto" "
+       reset_rebase &&
+       git rebase -p --onto c d w &&
+       test_cmp_rev c HEAD~3 &&
+       test_cmp_rev c HEAD^2^ &&
+       test_revision_subjects 'n e o w' HEAD~2 HEAD^2 HEAD^ HEAD
+"
+
+#       f
+#      /
+# a---b---c---g---h
+#      \
+#       d---gp--i
+#        \       \
+#         e-------u
+#
+# gp = cherry-picked g
+# h = reverted g
+test_expect_success 'setup of non-linear-history for patch-equivalence tests' '
+       git checkout e &&
+       test_merge u i
+'
+
+test_expect_success "rebase -p re-creates history around dropped commit matching upstream" "
+       reset_rebase &&
+       git rebase -p h u &&
+       test_cmp_rev h HEAD~3 &&
+       test_cmp_rev HEAD^2^ HEAD~2 &&
+       test_revision_subjects 'd i e u' HEAD~2 HEAD^2 HEAD^ HEAD
+"
+
+test_expect_success "rebase -p --onto in merged history drops patches in upstream" "
+       reset_rebase &&
+       git rebase -p --onto f h u &&
+       test_cmp_rev f HEAD~3 &&
+       test_cmp_rev HEAD^2^ HEAD~2 &&
+       test_revision_subjects 'd i e u' HEAD~2 HEAD^2 HEAD^ HEAD
+"
+
+test_expect_success "rebase -p --onto in merged history does not drop patches in onto" "
+       reset_rebase &&
+       git rebase -p --onto h f u &&
+       test_cmp_rev h HEAD~3 &&
+       test_cmp_rev HEAD^2~2 HEAD~2 &&
+       test_revision_subjects 'd gp i e u' HEAD~2 HEAD^2^ HEAD^2 HEAD^ HEAD
+"
+
+# a---b---c---g---h
+#      \
+#       d---gp--s
+#        \   \ /
+#         \   X
+#          \ / \
+#           e---t
+#
+# gp = cherry-picked g
+# h = reverted g
+test_expect_success 'setup of non-linear-history for dropping whole side' '
+       git checkout gp &&
+       test_merge s e &&
+       git checkout e &&
+       test_merge t gp
+'
+
+test_expect_failure "rebase -p drops merge commit when entire first-parent side is dropped" "
+       reset_rebase &&
+       git rebase -p h s &&
+       test_cmp_rev h HEAD~2 &&
+       test_linear_range 'd e' h..
+"
+
+test_expect_success "rebase -p drops merge commit when entire second-parent side is dropped" "
+       reset_rebase &&
+       git rebase -p h t &&
+       test_cmp_rev h HEAD~2 &&
+       test_linear_range 'd e' h..
+"
+
+# a---b---c
+#      \
+#       d---e
+#        \   \
+#         n---r
+#          \
+#           o
+#
+# r = tree-same with n
+test_expect_success 'setup of non-linear-history for empty commits' '
+       git checkout n &&
+       git merge --no-commit e &&
+       git reset n . &&
+       git commit -m r &&
+       git reset --hard &&
+       git clean -f &&
+       git tag r
+'
+
+test_expect_success "rebase -p re-creates empty internal merge commit" "
+       reset_rebase &&
+       git rebase -p c r &&
+       test_cmp_rev c HEAD~3 &&
+       test_cmp_rev HEAD^2^ HEAD~2 &&
+       test_revision_subjects 'd e n r' HEAD~2 HEAD^2 HEAD^ HEAD
+"
+
+test_expect_success "rebase -p re-creates empty merge commit" "
+       reset_rebase &&
+       git rebase -p o r &&
+       test_cmp_rev e HEAD^2 &&
+       test_cmp_rev o HEAD^ &&
+       test_revision_subjects 'r' HEAD
+"
+
+test_done
index df921d1f33df34dd2b2631580f8d53b18270662a..a5b6a5f3310316d289aee0961fcb6a2ae26fca99 100755 (executable)
@@ -10,17 +10,15 @@ test_expect_success 'Initialize repository' '
        git commit -m a
 '
 
-test_expect_success SYMLINKS 'Setup rename across paths each below D/F conflicts' '
+test_expect_success 'Setup rename across paths each below D/F conflicts' '
        mkdir b &&
-       ln -s ../a b/a &&
-       git add b &&
+       test_ln_s_add ../a b/a &&
        git commit -m b &&
 
        git checkout -b branch &&
        rm b/a &&
-       mv a b/a &&
-       ln -s b/a a &&
-       git add . &&
+       git mv a b/a &&
+       test_ln_s_add b/a a &&
        git commit -m swap &&
 
        >f1 &&
@@ -28,7 +26,7 @@ test_expect_success SYMLINKS 'Setup rename across paths each below D/F conflicts
        git commit -m f1
 '
 
-test_expect_success SYMLINKS 'Cherry-pick succeeds with rename across D/F conflicts' '
+test_expect_success 'Cherry-pick succeeds with rename across D/F conflicts' '
        git reset --hard &&
        git checkout master^0 &&
        git cherry-pick branch
index 0c44e9f5d04af8d038a1e78ed5fdf892ce4dd5b5..5c87b55645e249929585c2ed0e7bcf98a1b62da3 100755 (executable)
@@ -687,4 +687,100 @@ test_expect_failure SYMLINKS 'rm across a symlinked leading path (w/ index)' '
        test_path_is_file e/f
 '
 
+test_expect_success 'setup for testing rm messages' '
+       >bar.txt &&
+       >foo.txt &&
+       git add bar.txt foo.txt
+'
+
+test_expect_success 'rm files with different staged content' '
+       cat >expect <<-\EOF &&
+       error: the following files have staged content different from both the
+       file and the HEAD:
+           bar.txt
+           foo.txt
+       (use -f to force removal)
+       EOF
+       echo content1 >foo.txt &&
+       echo content1 >bar.txt &&
+       test_must_fail git rm foo.txt bar.txt 2>actual &&
+       test_i18ncmp expect actual
+'
+
+test_expect_success 'rm files with different staged content without hints' '
+       cat >expect <<-\EOF &&
+       error: the following files have staged content different from both the
+       file and the HEAD:
+           bar.txt
+           foo.txt
+       EOF
+       echo content2 >foo.txt &&
+       echo content2 >bar.txt &&
+       test_must_fail git -c advice.rmhints=false rm foo.txt bar.txt 2>actual &&
+       test_i18ncmp expect actual
+'
+
+test_expect_success 'rm file with local modification' '
+       cat >expect <<-\EOF &&
+       error: the following file has local modifications:
+           foo.txt
+       (use --cached to keep the file, or -f to force removal)
+       EOF
+       git commit -m "testing rm 3" &&
+       echo content3 >foo.txt &&
+       test_must_fail git rm foo.txt 2>actual &&
+       test_i18ncmp expect actual
+'
+
+test_expect_success 'rm file with local modification without hints' '
+       cat >expect <<-\EOF &&
+       error: the following file has local modifications:
+           bar.txt
+       EOF
+       echo content4 >bar.txt &&
+       test_must_fail git -c advice.rmhints=false rm bar.txt 2>actual &&
+       test_i18ncmp expect actual
+'
+
+test_expect_success 'rm file with changes in the index' '
+       cat >expect <<-\EOF &&
+       error: the following file has changes staged in the index:
+           foo.txt
+       (use --cached to keep the file, or -f to force removal)
+       EOF
+       git reset --hard &&
+       echo content5 >foo.txt &&
+       git add foo.txt &&
+       test_must_fail git rm foo.txt 2>actual &&
+       test_i18ncmp expect actual
+'
+
+test_expect_success 'rm file with changes in the index without hints' '
+       cat >expect <<-\EOF &&
+       error: the following file has changes staged in the index:
+           foo.txt
+       EOF
+       test_must_fail git -c advice.rmhints=false rm foo.txt 2>actual &&
+       test_i18ncmp expect actual
+'
+
+test_expect_success 'rm files with two different errors' '
+       cat >expect <<-\EOF &&
+       error: the following file has staged content different from both the
+       file and the HEAD:
+           foo1.txt
+       (use -f to force removal)
+       error: the following file has changes staged in the index:
+           bar1.txt
+       (use --cached to keep the file, or -f to force removal)
+       EOF
+       echo content >foo1.txt &&
+       git add foo1.txt &&
+       echo content6 >foo1.txt &&
+       echo content6 >bar1.txt &&
+       git add bar1.txt &&
+       test_must_fail git rm bar1.txt foo1.txt 2>actual &&
+       test_i18ncmp expect actual
+'
+
 test_done
index 874b3a6444ac64182eecf2b6aca4411fc90097e2..aab86e838b806f9bb2289f536f4ba9324d75da0a 100755 (executable)
@@ -30,10 +30,9 @@ test_expect_success \
         *) echo fail; git ls-files --stage xfoo1; (exit 1);;
         esac'
 
-test_expect_success SYMLINKS 'git add: filemode=0 should not get confused by symlink' '
+test_expect_success 'git add: filemode=0 should not get confused by symlink' '
        rm -f xfoo1 &&
-       ln -s foo xfoo1 &&
-       git add xfoo1 &&
+       test_ln_s_add foo xfoo1 &&
        case "`git ls-files --stage xfoo1`" in
        120000" "*xfoo1) echo pass;;
        *) echo fail; git ls-files --stage xfoo1; (exit 1);;
@@ -51,21 +50,19 @@ test_expect_success \
         *) echo fail; git ls-files --stage xfoo2; (exit 1);;
         esac'
 
-test_expect_success SYMLINKS 'git add: filemode=0 should not get confused by symlink' '
+test_expect_success 'git add: filemode=0 should not get confused by symlink' '
        rm -f xfoo2 &&
-       ln -s foo xfoo2 &&
-       git update-index --add xfoo2 &&
+       test_ln_s_add foo xfoo2 &&
        case "`git ls-files --stage xfoo2`" in
        120000" "*xfoo2) echo pass;;
        *) echo fail; git ls-files --stage xfoo2; (exit 1);;
        esac
 '
 
-test_expect_success SYMLINKS \
+test_expect_success \
        'git update-index --add: Test that executable bit is not used...' \
        'git config core.filemode 0 &&
-        ln -s xfoo2 xfoo3 &&
-        git update-index --add xfoo3 &&
+        test_ln_s_add xfoo2 xfoo3 &&   # runs git update-index --add
         case "`git ls-files --stage xfoo3`" in
         120000" "*xfoo3) echo pass;;
         *) echo fail; git ls-files --stage xfoo3; (exit 1);;
index 5dfbda7491aceaa64215ac94bdd416f9b93f55d3..debda7a678277e1198ba6620210b66984caf03a2 100755 (executable)
@@ -200,17 +200,17 @@ test_expect_success 'apply -q is quiet' '
        echo foo > file &&
        git stash &&
        git stash apply -q > output.out 2>&1 &&
-       test ! -s output.out
+       test_must_be_empty output.out
 '
 
 test_expect_success 'save -q is quiet' '
        git stash save --quiet > output.out 2>&1 &&
-       test ! -s output.out
+       test_must_be_empty output.out
 '
 
 test_expect_success 'pop -q is quiet' '
        git stash pop -q > output.out 2>&1 &&
-       test ! -s output.out
+       test_must_be_empty output.out
 '
 
 test_expect_success 'pop -q --index works and is quiet' '
@@ -219,13 +219,13 @@ test_expect_success 'pop -q --index works and is quiet' '
        git stash save --quiet &&
        git stash pop -q --index > output.out 2>&1 &&
        test foo = "$(git show :file)" &&
-       test ! -s output.out
+       test_must_be_empty output.out
 '
 
 test_expect_success 'drop -q is quiet' '
        git stash &&
        git stash drop -q > output.out 2>&1 &&
-       test ! -s output.out
+       test_must_be_empty output.out
 '
 
 test_expect_success 'stash -k' '
@@ -336,41 +336,58 @@ test_expect_success SYMLINKS 'stash file to symlink (full stage)' '
 
 # This test creates a commit with a symlink used for the following tests
 
-test_expect_success SYMLINKS 'stash symlink to file' '
+test_expect_success 'stash symlink to file' '
        git reset --hard &&
-       ln -s file filelink &&
-       git add filelink &&
+       test_ln_s_add file filelink &&
        git commit -m "Add symlink" &&
        rm filelink &&
        cp file filelink &&
-       git stash save "symlink to file" &&
+       git stash save "symlink to file"
+'
+
+test_expect_success SYMLINKS 'this must have re-created the symlink' '
        test -h filelink &&
-       case "$(ls -l filelink)" in *" filelink -> file") :;; *) false;; esac &&
+       case "$(ls -l filelink)" in *" filelink -> file") :;; *) false;; esac
+'
+
+test_expect_success 'unstash must re-create the file' '
        git stash apply &&
        ! test -h filelink &&
        test bar = "$(cat file)"
 '
 
-test_expect_success SYMLINKS 'stash symlink to file (stage rm)' '
+test_expect_success 'stash symlink to file (stage rm)' '
        git reset --hard &&
        git rm filelink &&
        cp file filelink &&
-       git stash save "symlink to file (stage rm)" &&
+       git stash save "symlink to file (stage rm)"
+'
+
+test_expect_success SYMLINKS 'this must have re-created the symlink' '
        test -h filelink &&
-       case "$(ls -l filelink)" in *" filelink -> file") :;; *) false;; esac &&
+       case "$(ls -l filelink)" in *" filelink -> file") :;; *) false;; esac
+'
+
+test_expect_success 'unstash must re-create the file' '
        git stash apply &&
        ! test -h filelink &&
        test bar = "$(cat file)"
 '
 
-test_expect_success SYMLINKS 'stash symlink to file (full stage)' '
+test_expect_success 'stash symlink to file (full stage)' '
        git reset --hard &&
        rm filelink &&
        cp file filelink &&
        git add filelink &&
-       git stash save "symlink to file (full stage)" &&
+       git stash save "symlink to file (full stage)"
+'
+
+test_expect_success SYMLINKS 'this must have re-created the symlink' '
        test -h filelink &&
-       case "$(ls -l filelink)" in *" filelink -> file") :;; *) false;; esac &&
+       case "$(ls -l filelink)" in *" filelink -> file") :;; *) false;; esac
+'
+
+test_expect_success 'unstash must re-create the file' '
        git stash apply &&
        ! test -h filelink &&
        test bar = "$(cat file)"
@@ -637,4 +654,23 @@ test_expect_success 'stash where working directory contains "HEAD" file' '
        test_cmp output expect
 '
 
+test_expect_success 'store called with invalid commit' '
+       test_must_fail git stash store foo
+'
+
+test_expect_success 'store updates stash ref and reflog' '
+       git stash clear &&
+       git reset --hard &&
+       echo quux >bazzy &&
+       git add bazzy &&
+       STASH_ID=$(git stash create) &&
+       git reset --hard &&
+       ! grep quux bazzy &&
+       git stash store -m quuxery $STASH_ID &&
+       test $(cat .git/refs/stash) = $STASH_ID &&
+       grep $STASH_ID .git/logs/refs/stash &&
+       git stash pop &&
+       grep quux bazzy
+'
+
 test_done
index 73b4a24f5ef676b8fa9fb3dc83183437710853c3..27e98a8f9d6c858468904542c500236b9e9f99c2 100755 (executable)
@@ -99,11 +99,11 @@ test_expect_success \
     'validate result of -B -M (#4)' \
     'compare_diff_raw expected current'
 
-test_expect_success SYMLINKS \
+test_expect_success \
     'make file0 into something completely different' \
     'rm -f file0 &&
-     ln -s frotz file0 &&
-     git update-index file0 file1'
+     test_ln_s_add frotz file0 &&
+     git update-index file1'
 
 test_expect_success \
     'run diff with -B' \
@@ -114,7 +114,7 @@ cat >expected <<\EOF
 :100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 M100  file1
 EOF
 
-test_expect_success SYMLINKS \
+test_expect_success \
     'validate result of -B (#5)' \
     'compare_diff_raw expected current'
 
@@ -129,7 +129,7 @@ cat >expected <<\EOF
 :100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 R     file0   file1
 EOF
 
-test_expect_success SYMLINKS \
+test_expect_success \
     'validate result of -B -M (#6)' \
     'compare_diff_raw expected current'
 
@@ -144,7 +144,7 @@ cat >expected <<\EOF
 :100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 M     file1
 EOF
 
-test_expect_success SYMLINKS \
+test_expect_success \
     'validate result of -M (#7)' \
     'compare_diff_raw expected current'
 
index f0d5041c11581ec2d711c0ea3bfd479019814b78..13e7f621ab79f95cc7c3057d9de5710813049102 100755 (executable)
@@ -9,7 +9,7 @@ test_description='Test diff of symlinks.
 . ./test-lib.sh
 . "$TEST_DIRECTORY"/diff-lib.sh
 
-test_expect_success SYMLINKS 'diff new symlink and file' '
+test_expect_success 'diff new symlink and file' '
        cat >expected <<-\EOF &&
        diff --git a/frotz b/frotz
        new file mode 120000
@@ -27,22 +27,25 @@ test_expect_success SYMLINKS 'diff new symlink and file' '
        @@ -0,0 +1 @@
        +xyzzy
        EOF
-       ln -s xyzzy frotz &&
-       echo xyzzy >nitfol &&
+
+       # the empty tree
        git update-index &&
        tree=$(git write-tree) &&
-       git update-index --add frotz nitfol &&
+
+       test_ln_s_add xyzzy frotz &&
+       echo xyzzy >nitfol &&
+       git update-index --add nitfol &&
        GIT_DIFF_OPTS=--unified=0 git diff-index -M -p $tree >current &&
        compare_diff_patch expected current
 '
 
-test_expect_success SYMLINKS 'diff unchanged symlink and file'  '
+test_expect_success 'diff unchanged symlink and file'  '
        tree=$(git write-tree) &&
        git update-index frotz nitfol &&
        test -z "$(git diff-index --name-only $tree)"
 '
 
-test_expect_success SYMLINKS 'diff removed symlink and file' '
+test_expect_success 'diff removed symlink and file' '
        cat >expected <<-\EOF &&
        diff --git a/frotz b/frotz
        deleted file mode 120000
@@ -66,12 +69,18 @@ test_expect_success SYMLINKS 'diff removed symlink and file' '
        compare_diff_patch expected current
 '
 
-test_expect_success SYMLINKS 'diff identical, but newly created symlink and file' '
+test_expect_success 'diff identical, but newly created symlink and file' '
        >expected &&
        rm -f frotz nitfol &&
        echo xyzzy >nitfol &&
        test-chmtime +10 nitfol &&
-       ln -s xyzzy frotz &&
+       if test_have_prereq SYMLINKS
+       then
+               ln -s xyzzy frotz
+       else
+               printf xyzzy >frotz
+               # the symlink property propagates from the index
+       fi &&
        git diff-index -M -p $tree >current &&
        compare_diff_patch expected current &&
 
@@ -80,7 +89,7 @@ test_expect_success SYMLINKS 'diff identical, but newly created symlink and file
        compare_diff_patch expected current
 '
 
-test_expect_success SYMLINKS 'diff different symlink and file' '
+test_expect_success 'diff different symlink and file' '
        cat >expected <<-\EOF &&
        diff --git a/frotz b/frotz
        index 7c465af..df1db54 120000
@@ -100,7 +109,13 @@ test_expect_success SYMLINKS 'diff different symlink and file' '
        +yxyyz
        EOF
        rm -f frotz &&
-       ln -s yxyyz frotz &&
+       if test_have_prereq SYMLINKS
+       then
+               ln -s yxyyz frotz
+       else
+               printf yxyyz >frotz
+               # the symlink property propagates from the index
+       fi &&
        echo yxyyz >nitfol &&
        git diff-index -M -p $tree >current &&
        compare_diff_patch expected current
index 5d20acf436558da6c11214f431beb503bc89459a..55d549fcf441be927bc43b2c41107a90aa614c2a 100755 (executable)
@@ -4,44 +4,44 @@ test_description='typechange rename detection'
 
 . ./test-lib.sh
 
-test_expect_success SYMLINKS setup '
+test_expect_success setup '
 
        rm -f foo bar &&
        cat "$TEST_DIRECTORY"/../COPYING >foo &&
-       ln -s linklink bar &&
-       git add foo bar &&
+       test_ln_s_add linklink bar &&
+       git add foo &&
        git commit -a -m Initial &&
        git tag one &&
 
-       rm -f foo bar &&
+       git rm -f foo bar &&
        cat "$TEST_DIRECTORY"/../COPYING >bar &&
-       ln -s linklink foo &&
-       git add foo bar &&
+       test_ln_s_add linklink foo &&
+       git add bar &&
        git commit -a -m Second &&
        git tag two &&
 
-       rm -f foo bar &&
+       git rm -f foo bar &&
        cat "$TEST_DIRECTORY"/../COPYING >foo &&
        git add foo &&
        git commit -a -m Third &&
        git tag three &&
 
        mv foo bar &&
-       ln -s linklink foo &&
-       git add foo bar &&
+       test_ln_s_add linklink foo &&
+       git add bar &&
        git commit -a -m Fourth &&
        git tag four &&
 
        # This is purely for sanity check
 
-       rm -f foo bar &&
+       git rm -f foo bar &&
        cat "$TEST_DIRECTORY"/../COPYING >foo &&
        cat "$TEST_DIRECTORY"/../Makefile >bar &&
        git add foo bar &&
        git commit -a -m Fifth &&
        git tag five &&
 
-       rm -f foo bar &&
+       git rm -f foo bar &&
        cat "$TEST_DIRECTORY"/../Makefile >foo &&
        cat "$TEST_DIRECTORY"/../COPYING >bar &&
        git add foo bar &&
@@ -50,7 +50,7 @@ test_expect_success SYMLINKS setup '
 
 '
 
-test_expect_success SYMLINKS 'cross renames to be detected for regular files' '
+test_expect_success 'cross renames to be detected for regular files' '
 
        git diff-tree five six -r --name-status -B -M | sort >actual &&
        {
@@ -61,7 +61,7 @@ test_expect_success SYMLINKS 'cross renames to be detected for regular files' '
 
 '
 
-test_expect_success SYMLINKS 'cross renames to be detected for typechange' '
+test_expect_success 'cross renames to be detected for typechange' '
 
        git diff-tree one two -r --name-status -B -M | sort >actual &&
        {
@@ -72,7 +72,7 @@ test_expect_success SYMLINKS 'cross renames to be detected for typechange' '
 
 '
 
-test_expect_success SYMLINKS 'moves and renames' '
+test_expect_success 'moves and renames' '
 
        git diff-tree three four -r --name-status -B -M | sort >actual &&
        {
index 53ec330ce8a09cfde5b3b5e7661bb6dd457ba0db..f75f46f92d22451522d4676ff4ed709b49419481 100755 (executable)
@@ -139,12 +139,10 @@ index 0000000..67be421
 +frotz
 \ No newline at end of file
 EOF
-# make a symlink the hard way that works on symlink-challenged file systems
+
 test_expect_success 'textconv does not act on symlinks' '
-       printf frotz > file &&
-       git add file &&
-       git ls-files -s | sed -e s/100644/120000/ |
-               git update-index --index-info &&
+       rm -f file &&
+       test_ln_s_add frotz file &&
        git commit -m typechange &&
        git show >diff &&
        find_diff <diff >actual &&
index 7c398432bad761c1b38c6d15548bed0c389221fd..1618a6dbc7c718677cf669b5f6fc96f2682992fb 100755 (executable)
@@ -86,6 +86,20 @@ test_expect_success 'apply --index from subdir of toplevel' '
        test_cmp expected sub/dir/file
 '
 
+test_expect_success 'apply half-broken patch from subdir of toplevel' '
+       (
+               cd sub/dir &&
+               test_must_fail git apply <<-EOF
+               --- sub/dir/file
+               +++ sub/dir/file
+               @@ -1,0 +1,0 @@
+               --- file_in_root
+               +++ file_in_root
+               @@ -1,0 +1,0 @@
+               EOF
+       )
+'
+
 test_expect_success 'apply from .git dir' '
        cp postimage expected &&
        cp preimage .git/file &&
index f12826fb09729d5753f3f6c9511dab5062f0aae1..ebadbc347fc4fd9d435a366e46e3e1cebba81b83 100755 (executable)
@@ -9,20 +9,19 @@ test_description='git apply should not get confused with type changes.
 
 . ./test-lib.sh
 
-test_expect_success SYMLINKS 'setup repository and commits' '
+test_expect_success 'setup repository and commits' '
        echo "hello world" > foo &&
        echo "hi planet" > bar &&
        git update-index --add foo bar &&
        git commit -m initial &&
        git branch initial &&
        rm -f foo &&
-       ln -s bar foo &&
-       git update-index foo &&
+       test_ln_s_add bar foo &&
        git commit -m "foo symlinked to bar" &&
        git branch foo-symlinked-to-bar &&
-       rm -f foo &&
+       git rm -f foo &&
        echo "how far is the sun?" > foo &&
-       git update-index foo &&
+       git update-index --add foo &&
        git commit -m "foo back to file" &&
        git branch foo-back-to-file &&
        printf "\0" > foo &&
@@ -42,7 +41,7 @@ test_expect_success SYMLINKS 'setup repository and commits' '
        git branch foo-baz-renamed-from-foo
        '
 
-test_expect_success SYMLINKS 'file renamed from foo to foo/baz' '
+test_expect_success 'file renamed from foo to foo/baz' '
        git checkout -f initial &&
        git diff-tree -M -p HEAD foo-baz-renamed-from-foo > patch &&
        git apply --index < patch
@@ -50,7 +49,7 @@ test_expect_success SYMLINKS 'file renamed from foo to foo/baz' '
 test_debug 'cat patch'
 
 
-test_expect_success SYMLINKS 'file renamed from foo/baz to foo' '
+test_expect_success 'file renamed from foo/baz to foo' '
        git checkout -f foo-baz-renamed-from-foo &&
        git diff-tree -M -p HEAD initial > patch &&
        git apply --index < patch
@@ -58,7 +57,7 @@ test_expect_success SYMLINKS 'file renamed from foo/baz to foo' '
 test_debug 'cat patch'
 
 
-test_expect_success SYMLINKS 'directory becomes file' '
+test_expect_success 'directory becomes file' '
        git checkout -f foo-becomes-a-directory &&
        git diff-tree -p HEAD initial > patch &&
        git apply --index < patch
@@ -66,7 +65,7 @@ test_expect_success SYMLINKS 'directory becomes file' '
 test_debug 'cat patch'
 
 
-test_expect_success SYMLINKS 'file becomes directory' '
+test_expect_success 'file becomes directory' '
        git checkout -f initial &&
        git diff-tree -p HEAD foo-becomes-a-directory > patch &&
        git apply --index < patch
@@ -74,7 +73,7 @@ test_expect_success SYMLINKS 'file becomes directory' '
 test_debug 'cat patch'
 
 
-test_expect_success SYMLINKS 'file becomes symlink' '
+test_expect_success 'file becomes symlink' '
        git checkout -f initial &&
        git diff-tree -p HEAD foo-symlinked-to-bar > patch &&
        git apply --index < patch
@@ -82,21 +81,21 @@ test_expect_success SYMLINKS 'file becomes symlink' '
 test_debug 'cat patch'
 
 
-test_expect_success SYMLINKS 'symlink becomes file' '
+test_expect_success 'symlink becomes file' '
        git checkout -f foo-symlinked-to-bar &&
        git diff-tree -p HEAD foo-back-to-file > patch &&
        git apply --index < patch
        '
 test_debug 'cat patch'
 
-test_expect_success SYMLINKS 'binary file becomes symlink' '
+test_expect_success 'binary file becomes symlink' '
        git checkout -f foo-becomes-binary &&
        git diff-tree -p --binary HEAD foo-symlinked-to-bar > patch &&
        git apply --index < patch
        '
 test_debug 'cat patch'
 
-test_expect_success SYMLINKS 'symlink becomes binary file' '
+test_expect_success 'symlink becomes binary file' '
        git checkout -f foo-symlinked-to-bar &&
        git diff-tree -p --binary HEAD foo-becomes-binary > patch &&
        git apply --index < patch
@@ -104,7 +103,7 @@ test_expect_success SYMLINKS 'symlink becomes binary file' '
 test_debug 'cat patch'
 
 
-test_expect_success SYMLINKS 'symlink becomes directory' '
+test_expect_success 'symlink becomes directory' '
        git checkout -f foo-symlinked-to-bar &&
        git diff-tree -p HEAD foo-becomes-a-directory > patch &&
        git apply --index < patch
@@ -112,7 +111,7 @@ test_expect_success SYMLINKS 'symlink becomes directory' '
 test_debug 'cat patch'
 
 
-test_expect_success SYMLINKS 'directory becomes symlink' '
+test_expect_success 'directory becomes symlink' '
        git checkout -f foo-becomes-a-directory &&
        git diff-tree -p HEAD foo-symlinked-to-bar > patch &&
        git apply --index < patch
index 7674dd2ec9d6f14a0f2181ebf415378521f90fa4..872fcda6cb6dce98ec360c41cb6ae1220193ca48 100755 (executable)
@@ -9,18 +9,16 @@ test_description='git apply symlinks and partial files
 
 . ./test-lib.sh
 
-test_expect_success SYMLINKS setup '
+test_expect_success setup '
 
-       ln -s path1/path2/path3/path4/path5 link1 &&
-       git add link? &&
+       test_ln_s_add path1/path2/path3/path4/path5 link1 &&
        git commit -m initial &&
 
        git branch side &&
 
        rm -f link? &&
 
-       ln -s htap6 link1 &&
-       git update-index link? &&
+       test_ln_s_add htap6 link1 &&
        git commit -m second &&
 
        git diff-tree -p HEAD^ HEAD >patch  &&
@@ -37,7 +35,7 @@ test_expect_success SYMLINKS 'apply symlink patch' '
 
 '
 
-test_expect_success SYMLINKS 'apply --index symlink patch' '
+test_expect_success 'apply --index symlink patch' '
 
        git checkout -f side &&
        git apply --index patch &&
index 39407376ba7da1cb8256bcd3041a83147cd9eee5..70b3a06e1dc7a92ac5a0806854e100af02c2346b 100755 (executable)
@@ -10,11 +10,11 @@ lecho () {
        done
 }
 
-test_expect_success SYMLINKS setup '
+test_expect_success setup '
 
        mkdir -p arch/i386/boot arch/x86_64 &&
        lecho 1 2 3 4 5 >arch/i386/boot/Makefile &&
-       ln -s ../i386/boot arch/x86_64/boot &&
+       test_ln_s_add ../i386/boot arch/x86_64/boot &&
        git add . &&
        test_tick &&
        git commit -m initial &&
@@ -31,7 +31,7 @@ test_expect_success SYMLINKS setup '
 
 '
 
-test_expect_success SYMLINKS apply '
+test_expect_success apply '
 
        git checkout test &&
        git diff --exit-code test &&
@@ -40,7 +40,7 @@ test_expect_success SYMLINKS apply '
 
 '
 
-test_expect_success SYMLINKS 'check result' '
+test_expect_success 'check result' '
 
        git diff --exit-code master &&
        git diff --exit-code --cached master &&
index 12f6b027acbccae3fe524906970ff139ddccfed0..5edb79a058f407f47353041afb8e7851b066a517 100755 (executable)
@@ -147,7 +147,7 @@ test_expect_success 'am applies patch correctly' '
        git checkout first &&
        test_tick &&
        git am <patch1 &&
-       ! test -d .git/rebase-apply &&
+       test_path_is_missing .git/rebase-apply &&
        git diff --exit-code second &&
        test "$(git rev-parse second)" = "$(git rev-parse HEAD)" &&
        test "$(git rev-parse second^)" = "$(git rev-parse HEAD^)"
@@ -158,7 +158,7 @@ test_expect_success 'am applies patch e-mail not in a mbox' '
        git reset --hard &&
        git checkout first &&
        git am patch1.eml &&
-       ! test -d .git/rebase-apply &&
+       test_path_is_missing .git/rebase-apply &&
        git diff --exit-code second &&
        test "$(git rev-parse second)" = "$(git rev-parse HEAD)" &&
        test "$(git rev-parse second^)" = "$(git rev-parse HEAD^)"
@@ -169,7 +169,7 @@ test_expect_success 'am applies patch e-mail not in a mbox with CRLF' '
        git reset --hard &&
        git checkout first &&
        git am patch1-crlf.eml &&
-       ! test -d .git/rebase-apply &&
+       test_path_is_missing .git/rebase-apply &&
        git diff --exit-code second &&
        test "$(git rev-parse second)" = "$(git rev-parse HEAD)" &&
        test "$(git rev-parse second^)" = "$(git rev-parse HEAD^)"
@@ -180,7 +180,7 @@ test_expect_success 'am applies patch e-mail with preceding whitespace' '
        git reset --hard &&
        git checkout first &&
        git am patch1-ws.eml &&
-       ! test -d .git/rebase-apply &&
+       test_path_is_missing .git/rebase-apply &&
        git diff --exit-code second &&
        test "$(git rev-parse second)" = "$(git rev-parse HEAD)" &&
        test "$(git rev-parse second^)" = "$(git rev-parse HEAD^)"
@@ -206,7 +206,7 @@ test_expect_success 'am changes committer and keeps author' '
        git reset --hard &&
        git checkout first &&
        git am patch2 &&
-       ! test -d .git/rebase-apply &&
+       test_path_is_missing .git/rebase-apply &&
        test "$(git rev-parse master^^)" = "$(git rev-parse HEAD^^)" &&
        git diff --exit-code master..HEAD &&
        git diff --exit-code master^..HEAD^ &&
@@ -258,7 +258,7 @@ test_expect_success 'am --keep really keeps the subject' '
        git reset --hard &&
        git checkout HEAD^ &&
        git am --keep patch4 &&
-       ! test -d .git/rebase-apply &&
+       test_path_is_missing .git/rebase-apply &&
        git cat-file commit HEAD >actual &&
        grep "Re: Re: Re: \[PATCH 1/5 v2\] \[foo\] third" actual
 '
@@ -268,7 +268,7 @@ test_expect_success 'am --keep-non-patch really keeps the non-patch part' '
        git reset --hard &&
        git checkout HEAD^ &&
        git am --keep-non-patch patch4 &&
-       ! test -d .git/rebase-apply &&
+       test_path_is_missing .git/rebase-apply &&
        git cat-file commit HEAD >actual &&
        grep "^\[foo\] third" actual
 '
@@ -283,7 +283,7 @@ test_expect_success 'am -3 falls back to 3-way merge' '
        test_tick &&
        git commit -m "copied stuff" &&
        git am -3 lorem-move.patch &&
-       ! test -d .git/rebase-apply &&
+       test_path_is_missing .git/rebase-apply &&
        git diff --exit-code lorem
 '
 
@@ -297,7 +297,7 @@ test_expect_success 'am -3 -p0 can read --no-prefix patch' '
        test_tick &&
        git commit -m "copied stuff" &&
        git am -3 -p0 lorem-zero.patch &&
-       ! test -d .git/rebase-apply &&
+       test_path_is_missing .git/rebase-apply &&
        git diff --exit-code lorem
 '
 
@@ -307,7 +307,7 @@ test_expect_success 'am can rename a file' '
        git reset --hard &&
        git checkout lorem^0 &&
        git am rename.patch &&
-       ! test -d .git/rebase-apply &&
+       test_path_is_missing .git/rebase-apply &&
        git update-index --refresh &&
        git diff --exit-code rename
 '
@@ -318,7 +318,7 @@ test_expect_success 'am -3 can rename a file' '
        git reset --hard &&
        git checkout lorem^0 &&
        git am -3 rename.patch &&
-       ! test -d .git/rebase-apply &&
+       test_path_is_missing .git/rebase-apply &&
        git update-index --refresh &&
        git diff --exit-code rename
 '
@@ -329,7 +329,7 @@ test_expect_success 'am -3 can rename a file after falling back to 3-way merge'
        git reset --hard &&
        git checkout lorem^0 &&
        git am -3 rename-add.patch &&
-       ! test -d .git/rebase-apply &&
+       test_path_is_missing .git/rebase-apply &&
        git update-index --refresh &&
        git diff --exit-code rename
 '
@@ -358,11 +358,17 @@ test_expect_success 'am pauses on conflict' '
 test_expect_success 'am --skip works' '
        echo goodbye >expected &&
        git am --skip &&
-       ! test -d .git/rebase-apply &&
+       test_path_is_missing .git/rebase-apply &&
        git diff --exit-code lorem2^^ -- file &&
        test_cmp expected another
 '
 
+test_expect_success 'am --abort removes a stray directory' '
+       mkdir .git/rebase-apply &&
+       git am --abort &&
+       test_path_is_missing .git/rebase-apply
+'
+
 test_expect_success 'am --resolved works' '
        echo goodbye >expected &&
        rm -fr .git/rebase-apply &&
@@ -373,7 +379,7 @@ test_expect_success 'am --resolved works' '
        echo resolved >>file &&
        git add file &&
        git am --resolved &&
-       ! test -d .git/rebase-apply &&
+       test_path_is_missing .git/rebase-apply &&
        test_cmp expected another
 '
 
@@ -382,7 +388,7 @@ test_expect_success 'am takes patches from a Pine mailbox' '
        git reset --hard &&
        git checkout first &&
        cat pine patch1 | git am &&
-       ! test -d .git/rebase-apply &&
+       test_path_is_missing .git/rebase-apply &&
        git diff --exit-code master^..HEAD
 '
 
@@ -391,7 +397,7 @@ test_expect_success 'am fails on mail without patch' '
        git reset --hard &&
        test_must_fail git am <failmail &&
        git am --abort &&
-       ! test -d .git/rebase-apply
+       test_path_is_missing .git/rebase-apply
 '
 
 test_expect_success 'am fails on empty patch' '
@@ -400,7 +406,7 @@ test_expect_success 'am fails on empty patch' '
        echo "---" >>failmail &&
        test_must_fail git am <failmail &&
        git am --skip &&
-       ! test -d .git/rebase-apply
+       test_path_is_missing .git/rebase-apply
 '
 
 test_expect_success 'am works from stdin in subdirectory' '
index 432f98c357601057cb89f9dd6bfbe1ab02e9477a..1afa0d5c44e00a401cb32ec4bca13c4c6882fd07 100755 (executable)
@@ -80,12 +80,12 @@ test_expect_success 'setup: two scripts for reading pull requests' '
 
        cat <<-EOT >fuzz.sed
        #!/bin/sed -nf
+       s/$downstream_url_for_sed/URL/g
        s/$_x40/OBJECT_NAME/g
        s/A U Thor/AUTHOR/g
        s/[-0-9]\{10\} [:0-9]\{8\} [-+][0-9]\{4\}/DATE/g
        s/        [^ ].*/        SUBJECT/g
        s/  [^ ].* (DATE)/  SUBJECT (DATE)/g
-       s/$downstream_url_for_sed/URL/g
        s/for-upstream/BRANCH/g
        s/mnemonic.txt/FILENAME/g
        s/^version [0-9]/VERSION/
index 5b1250f0d2d69369fccacbefa3fa24488e9c4b4e..35926debe3df81dc997e99f5edbf74ab5f9ec479 100755 (executable)
@@ -51,7 +51,7 @@ do_corrupt_object() {
     ofs=`git show-index < ${pack}.idx | grep $1 | cut -f1 -d" "` &&
     ofs=$(($ofs + $2)) &&
     chmod +w ${pack}.pack &&
-    dd of=${pack}.pack count=1 bs=1 conv=notrunc seek=$ofs &&
+    dd of=${pack}.pack bs=1 conv=notrunc seek=$ofs &&
     test_must_fail git verify-pack ${pack}.pack
 }
 
@@ -275,6 +275,33 @@ test_expect_success \
      git cat-file blob $blob_2 > /dev/null &&
      git cat-file blob $blob_3 > /dev/null'
 
+test_expect_success \
+    'corruption of delta base reference pointing to wrong object' \
+    'create_new_pack --delta-base-offset &&
+     git prune-packed &&
+     printf "\220\033" | do_corrupt_object $blob_3 2 &&
+     git cat-file blob $blob_1 >/dev/null &&
+     git cat-file blob $blob_2 >/dev/null &&
+     test_must_fail git cat-file blob $blob_3 >/dev/null'
+
+test_expect_success \
+    '... but having a loose copy allows for full recovery' \
+    'mv ${pack}.idx tmp &&
+     git hash-object -t blob -w file_3 &&
+     mv tmp ${pack}.idx &&
+     git cat-file blob $blob_1 > /dev/null &&
+     git cat-file blob $blob_2 > /dev/null &&
+     git cat-file blob $blob_3 > /dev/null'
+
+test_expect_success \
+    '... and then a repack "clears" the corruption' \
+    'do_repack --delta-base-offset --no-reuse-delta &&
+     git prune-packed &&
+     git verify-pack ${pack}.pack &&
+     git cat-file blob $blob_1 > /dev/null &&
+     git cat-file blob $blob_2 > /dev/null &&
+     git cat-file blob $blob_3 > /dev/null'
+
 test_expect_success \
     'corrupting header to have too small output buffer fails unpack' \
     'create_new_pack &&
index 6af6c63350383e049082e61d3c8133af79d5a749..ed4d9c83181bc45456074f053ed5a0f137fc7e27 100755 (executable)
@@ -57,6 +57,35 @@ test_expect_success 'pulling into void does not overwrite untracked files' '
        )
 '
 
+test_expect_success 'pulling into void does not overwrite staged files' '
+       git init cloned-staged-colliding &&
+       (
+               cd cloned-staged-colliding &&
+               echo "alternate content" >file &&
+               git add file &&
+               test_must_fail git pull .. master &&
+               echo "alternate content" >expect &&
+               test_cmp expect file &&
+               git cat-file blob :file >file.index &&
+               test_cmp expect file.index
+       )
+'
+
+
+test_expect_success 'pulling into void does not remove new staged files' '
+       git init cloned-staged-new &&
+       (
+               cd cloned-staged-new &&
+               echo "new tracked file" >newfile &&
+               git add newfile &&
+               git pull .. master &&
+               echo "new tracked file" >expect &&
+               test_cmp expect newfile &&
+               git cat-file blob :newfile >newfile.index &&
+               test_cmp expect newfile.index
+       )
+'
+
 test_expect_success 'test . as a remote' '
 
        git branch copy master &&
index aa31abe32b3abc1b43d8111f6503313aa8237455..453aba53f45e34f6b17bcad7628f4224d6c2de4c 100755 (executable)
@@ -15,19 +15,19 @@ test_expect_success 'git pull -q' '
        mkdir clonedq &&
        (cd clonedq && git init &&
        git pull -q "../parent" >out 2>err &&
-       test ! -s err &&
-       test ! -s out)
+       test_must_be_empty err &&
+       test_must_be_empty out)
 '
 
 test_expect_success 'git pull -q --rebase' '
        mkdir clonedqrb &&
        (cd clonedqrb && git init &&
        git pull -q --rebase "../parent" >out 2>err &&
-       test ! -s err &&
-       test ! -s out &&
+       test_must_be_empty err &&
+       test_must_be_empty out &&
        git pull -q --rebase "../parent" >out 2>err &&
-       test ! -s err &&
-       test ! -s out)
+       test_must_be_empty err &&
+       test_must_be_empty out)
 '
 
 test_expect_success 'git pull' '
@@ -35,7 +35,7 @@ test_expect_success 'git pull' '
        (cd cloned && git init &&
        git pull "../parent" >out 2>err &&
        test -s err &&
-       test ! -s out)
+       test_must_be_empty out)
 '
 
 test_expect_success 'git pull --rebase' '
@@ -43,7 +43,7 @@ test_expect_success 'git pull --rebase' '
        (cd clonedrb && git init &&
        git pull --rebase "../parent" >out 2>err &&
        test -s err &&
-       test ! -s out)
+       test_must_be_empty out)
 '
 
 test_expect_success 'git pull -v' '
@@ -51,7 +51,7 @@ test_expect_success 'git pull -v' '
        (cd clonedv && git init &&
        git pull -v "../parent" >out 2>err &&
        test -s err &&
-       test ! -s out)
+       test_must_be_empty out)
 '
 
 test_expect_success 'git pull -v --rebase' '
@@ -59,22 +59,22 @@ test_expect_success 'git pull -v --rebase' '
        (cd clonedvrb && git init &&
        git pull -v --rebase "../parent" >out 2>err &&
        test -s err &&
-       test ! -s out)
+       test_must_be_empty out)
 '
 
 test_expect_success 'git pull -v -q' '
        mkdir clonedvq &&
        (cd clonedvq && git init &&
        git pull -v -q "../parent" >out 2>err &&
-       test ! -s out &&
-       test ! -s err)
+       test_must_be_empty out &&
+       test_must_be_empty err)
 '
 
 test_expect_success 'git pull -q -v' '
        mkdir clonedqv &&
        (cd clonedqv && git init &&
        git pull -q -v "../parent" >out 2>err &&
-       test ! -s out &&
+       test_must_be_empty out &&
        test -s err)
 '
 
index 02cb02472322e8fd6c31548d30950c937e301e5f..85cadfad6d8aeee79e0f05530376ca7dc6f085f0 100755 (executable)
@@ -22,7 +22,7 @@ test_expect_success 'clone -o' '
 test_expect_success 'redirected clone' '
 
        git clone "file://$(pwd)/parent" clone-redirected >out 2>err &&
-       test ! -s err
+       test_must_be_empty err
 
 '
 test_expect_success 'redirected clone -v' '
index 4899af3f7abfb567017e0d64c7961c22beb5b749..8c4c5396a8447fc39d6f0c697af761631b08b3f7 100755 (executable)
@@ -210,9 +210,7 @@ test_expect_success 'proper failure checks for pushing' '
        (GIT_REMOTE_TESTGIT_FAILURE=1 &&
        export GIT_REMOTE_TESTGIT_FAILURE &&
        cd local &&
-       test_must_fail git push --all 2> error &&
-       cat error &&
-       grep -q "Reading from helper .git-remote-testgit. failed" error
+       test_must_fail git push --all
        )
 '
 
index dd6dc844e787549ab7622fb4fbec4f89b52e22ab..57ce2395d6738617797d0ba270874abd55a3b899 100755 (executable)
@@ -14,21 +14,24 @@ unnote () {
 
 test_expect_success setup '
        echo "Hi there" >file &&
-       git add file &&
-       test_tick && git commit -m "Initial file" &&
+       echo "initial" >lost &&
+       git add file lost &&
+       test_tick && git commit -m "Initial file and lost" &&
        note A &&
 
        git branch other-branch &&
 
        echo "Hello" >file &&
-       git add file &&
-       test_tick && git commit -m "Modified file" &&
+       echo "second" >lost &&
+       git add file lost &&
+       test_tick && git commit -m "Modified file and lost" &&
        note B &&
 
        git checkout other-branch &&
 
        echo "Hello" >file &&
-       git add file &&
+       >lost &&
+       git add file lost &&
        test_tick && git commit -m "Modified the file identically" &&
        note C &&
 
@@ -37,7 +40,9 @@ test_expect_success setup '
        test_tick && git commit -m "Add another file" &&
        note D &&
 
-       test_tick && git merge -m "merge" master &&
+       test_tick &&
+       test_must_fail git merge -m "merge" master &&
+       >lost && git commit -a -m "merge" &&
        note E &&
 
        echo "Yet another" >elif &&
@@ -105,9 +110,21 @@ check_result 'L K J I H G F E D C B A' --full-history
 check_result 'K I H E C B A' --full-history -- file
 check_result 'K I H E C B A' --full-history --topo-order -- file
 check_result 'K I H E C B A' --full-history --date-order -- file
-check_outcome failure 'I E C B A' --simplify-merges -- file
+check_result 'I E C B A' --simplify-merges -- file
 check_result 'I B A' -- file
 check_result 'I B A' --topo-order -- file
 check_result 'H' --first-parent -- another-file
 
+check_result 'E C B A' --full-history E -- lost
+test_expect_success 'full history simplification without parent' '
+       printf "%s\n" E C B A >expect &&
+       git log --pretty="$FMT" --full-history E -- lost |
+       unnote >actual &&
+       sed -e "s/^.*   \([^ ]*\) .*/\1/" >check <actual &&
+       test_cmp expect check || {
+               cat actual
+               false
+       }
+'
+
 test_done
index dd5b0e55d26b0f9258ae47425050083abe385fc0..dabebaee0b7b474387d4bc9b466b969624822d6d 100755 (executable)
@@ -16,6 +16,10 @@ test_description='--ancestry-path'
 #
 #  F...I                 == F G H I
 #  --ancestry-path F...I == F H I
+#
+#  G..M -- G.t                 == [nothing - was dropped in "-s ours" merge L]
+#  --ancestry-path G..M -- G.t == L
+#  --ancestry-path --simplify-merges G^..M -- G.t == G L
 
 . ./test-lib.sh
 
@@ -89,6 +93,29 @@ test_expect_success 'rev-list --ancestry-path F...I' '
        test_cmp expect actual
 '
 
+# G.t is dropped in an "-s ours" merge
+test_expect_success 'rev-list G..M -- G.t' '
+       >expect &&
+       git rev-list --format=%s G..M -- G.t |
+       sed -e "/^commit /d" >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'rev-list --ancestry-path G..M -- G.t' '
+       echo L >expect &&
+       git rev-list --ancestry-path --format=%s G..M -- G.t |
+       sed -e "/^commit /d" >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'rev-list --ancestry-path --simplify-merges G^..M -- G.t' '
+       for c in G L; do echo $c; done >expect &&
+       git rev-list --ancestry-path --simplify-merges --format=%s G^..M -- G.t |
+       sed -e "/^commit /d" |
+       sort >actual &&
+       test_cmp expect actual
+'
+
 #   b---bc
 #  / \ /
 # a   X
index 2599ae50eb94051f66b4dc1de603aaf8cd1f79eb..9324ea44162130c0eea4dcc08d21866354e3c1da 100755 (executable)
@@ -3,7 +3,7 @@
 test_description='merging when a directory was replaced with a symlink'
 . ./test-lib.sh
 
-test_expect_success SYMLINKS 'create a commit where dir a/b changed to symlink' '
+test_expect_success 'create a commit where dir a/b changed to symlink' '
        mkdir -p a/b/c a/b-2/c &&
        > a/b/c/d &&
        > a/b-2/c/d &&
@@ -12,12 +12,12 @@ test_expect_success SYMLINKS 'create a commit where dir a/b changed to symlink'
        git commit -m base &&
        git tag start &&
        rm -rf a/b &&
-       ln -s b-2 a/b &&
        git add -A &&
+       test_ln_s_add b-2 a/b &&
        git commit -m "dir to symlink"
 '
 
-test_expect_success SYMLINKS 'checkout does not clobber untracked symlink' '
+test_expect_success 'checkout does not clobber untracked symlink' '
        git checkout HEAD^0 &&
        git reset --hard master &&
        git rm --cached a/b &&
@@ -25,7 +25,7 @@ test_expect_success SYMLINKS 'checkout does not clobber untracked symlink' '
        test_must_fail git checkout start^0
 '
 
-test_expect_success SYMLINKS 'a/b-2/c/d is kept when clobbering symlink b' '
+test_expect_success 'a/b-2/c/d is kept when clobbering symlink b' '
        git checkout HEAD^0 &&
        git reset --hard master &&
        git rm --cached a/b &&
@@ -34,14 +34,14 @@ test_expect_success SYMLINKS 'a/b-2/c/d is kept when clobbering symlink b' '
        test -f a/b-2/c/d
 '
 
-test_expect_success SYMLINKS 'checkout should not have deleted a/b-2/c/d' '
+test_expect_success 'checkout should not have deleted a/b-2/c/d' '
        git checkout HEAD^0 &&
        git reset --hard master &&
         git checkout start^0 &&
         test -f a/b-2/c/d
 '
 
-test_expect_success SYMLINKS 'setup for merge test' '
+test_expect_success 'setup for merge test' '
        git reset --hard &&
        test -f a/b-2/c/d &&
        echo x > a/x &&
@@ -50,39 +50,51 @@ test_expect_success SYMLINKS 'setup for merge test' '
        git tag baseline
 '
 
-test_expect_success SYMLINKS 'Handle D/F conflict, do not lose a/b-2/c/d in merge (resolve)' '
+test_expect_success 'Handle D/F conflict, do not lose a/b-2/c/d in merge (resolve)' '
        git reset --hard &&
        git checkout baseline^0 &&
        git merge -s resolve master &&
-       test -h a/b &&
        test -f a/b-2/c/d
 '
 
-test_expect_success SYMLINKS 'Handle D/F conflict, do not lose a/b-2/c/d in merge (recursive)' '
+test_expect_success SYMLINKS 'a/b was resolved as symlink' '
+       test -h a/b
+'
+
+test_expect_success 'Handle D/F conflict, do not lose a/b-2/c/d in merge (recursive)' '
        git reset --hard &&
        git checkout baseline^0 &&
        git merge -s recursive master &&
-       test -h a/b &&
        test -f a/b-2/c/d
 '
 
-test_expect_success SYMLINKS 'Handle F/D conflict, do not lose a/b-2/c/d in merge (resolve)' '
+test_expect_success SYMLINKS 'a/b was resolved as symlink' '
+       test -h a/b
+'
+
+test_expect_success 'Handle F/D conflict, do not lose a/b-2/c/d in merge (resolve)' '
        git reset --hard &&
        git checkout master^0 &&
        git merge -s resolve baseline^0 &&
-       test -h a/b &&
        test -f a/b-2/c/d
 '
 
-test_expect_success SYMLINKS 'Handle F/D conflict, do not lose a/b-2/c/d in merge (recursive)' '
+test_expect_success SYMLINKS 'a/b was resolved as symlink' '
+       test -h a/b
+'
+
+test_expect_success 'Handle F/D conflict, do not lose a/b-2/c/d in merge (recursive)' '
        git reset --hard &&
        git checkout master^0 &&
        git merge -s recursive baseline^0 &&
-       test -h a/b &&
        test -f a/b-2/c/d
 '
 
-test_expect_failure SYMLINKS 'do not lose untracked in merge (resolve)' '
+test_expect_success SYMLINKS 'a/b was resolved as symlink' '
+       test -h a/b
+'
+
+test_expect_failure 'do not lose untracked in merge (resolve)' '
        git reset --hard &&
        git checkout baseline^0 &&
        >a/b/c/e &&
@@ -91,7 +103,7 @@ test_expect_failure SYMLINKS 'do not lose untracked in merge (resolve)' '
        test -f a/b-2/c/d
 '
 
-test_expect_success SYMLINKS 'do not lose untracked in merge (recursive)' '
+test_expect_success 'do not lose untracked in merge (recursive)' '
        git reset --hard &&
        git checkout baseline^0 &&
        >a/b/c/e &&
@@ -100,52 +112,61 @@ test_expect_success SYMLINKS 'do not lose untracked in merge (recursive)' '
        test -f a/b-2/c/d
 '
 
-test_expect_success SYMLINKS 'do not lose modifications in merge (resolve)' '
+test_expect_success 'do not lose modifications in merge (resolve)' '
        git reset --hard &&
        git checkout baseline^0 &&
        echo more content >>a/b/c/d &&
        test_must_fail git merge -s resolve master
 '
 
-test_expect_success SYMLINKS 'do not lose modifications in merge (recursive)' '
+test_expect_success 'do not lose modifications in merge (recursive)' '
        git reset --hard &&
        git checkout baseline^0 &&
        echo more content >>a/b/c/d &&
        test_must_fail git merge -s recursive master
 '
 
-test_expect_success SYMLINKS 'setup a merge where dir a/b-2 changed to symlink' '
+test_expect_success 'setup a merge where dir a/b-2 changed to symlink' '
        git reset --hard &&
        git checkout start^0 &&
        rm -rf a/b-2 &&
-       ln -s b a/b-2 &&
        git add -A &&
+       test_ln_s_add b a/b-2 &&
        git commit -m "dir a/b-2 to symlink" &&
        git tag test2
 '
 
-test_expect_success SYMLINKS 'merge should not have D/F conflicts (resolve)' '
+test_expect_success 'merge should not have D/F conflicts (resolve)' '
        git reset --hard &&
        git checkout baseline^0 &&
        git merge -s resolve test2 &&
-       test -h a/b-2 &&
        test -f a/b/c/d
 '
 
-test_expect_success SYMLINKS 'merge should not have D/F conflicts (recursive)' '
+test_expect_success SYMLINKS 'a/b-2 was resolved as symlink' '
+       test -h a/b-2
+'
+
+test_expect_success 'merge should not have D/F conflicts (recursive)' '
        git reset --hard &&
        git checkout baseline^0 &&
        git merge -s recursive test2 &&
-       test -h a/b-2 &&
        test -f a/b/c/d
 '
 
-test_expect_success SYMLINKS 'merge should not have F/D conflicts (recursive)' '
+test_expect_success SYMLINKS 'a/b-2 was resolved as symlink' '
+       test -h a/b-2
+'
+
+test_expect_success 'merge should not have F/D conflicts (recursive)' '
        git reset --hard &&
        git checkout -b foo test2 &&
        git merge -s recursive baseline^0 &&
-       test -h a/b-2 &&
        test -f a/b/c/d
 '
 
+test_expect_success SYMLINKS 'a/b-2 was resolved as symlink' '
+       test -h a/b-2
+'
+
 test_done
diff --git a/t/t6111-rev-list-treesame.sh b/t/t6111-rev-list-treesame.sh
new file mode 100755 (executable)
index 0000000..88b84df
--- /dev/null
@@ -0,0 +1,196 @@
+#!/bin/sh
+#
+#        ,---E--.   *H----------.             * marks !TREESAME parent paths
+#       /        \ /             \*
+# *A--*B---D--*F-*G---------K-*L-*M
+#   \     /*       \       /
+#    `-C-'          `-*I-*J
+#
+# A creates "file", B and F change it.
+# Odd merge G takes the old version from B.
+# I changes it, but J reverts it, so K is TREESAME to both parents.
+# H and L both change "file", and M merges those changes.
+
+test_description='TREESAME and limiting'
+
+. ./test-lib.sh
+
+note () {
+       git tag "$1"
+}
+
+unnote () {
+       git name-rev --tags --stdin | sed -e "s|$_x40 (tags/\([^)]*\))\([       ]\)|\1\2|g"
+}
+
+test_expect_success setup '
+       test_commit "Initial file" file "Hi there" A &&
+       git branch other-branch &&
+
+       test_commit "file=Hello" file "Hello" B &&
+       git branch third-branch &&
+
+       git checkout other-branch &&
+       test_commit "Added other" other "Hello" C &&
+
+       git checkout master &&
+       test_merge D other-branch &&
+
+       git checkout third-branch &&
+       test_commit "Third file" third "Nothing" E &&
+
+       git checkout master &&
+       test_commit "file=Blah" file "Blah" F &&
+
+       test_tick && git merge --no-commit third-branch &&
+       git checkout third-branch file &&
+       git commit &&
+       note G &&
+       git branch fiddler-branch &&
+
+       git checkout -b part2-branch &&
+       test_commit "file=Part 2" file "Part 2" H &&
+
+       git checkout fiddler-branch &&
+       test_commit "Bad commit" file "Silly" I &&
+
+       test_tick && git revert I && note J &&
+
+       git checkout master &&
+       test_tick && git merge --no-ff fiddler-branch &&
+       note K
+
+       test_commit "file=Part 1" file "Part 1" L &&
+
+       test_tick && test_must_fail git merge part2-branch &&
+       test_commit M file "Parts 1+2"
+'
+
+check_outcome () {
+       outcome=$1
+       shift
+
+       case "$1" in
+       *"("*)
+               FMT="%P %H | %s"
+               munge_actual="
+                       s/^\([^ ]*\)    \([^ ]*\) .*/(\1)\2/
+                       s/ //g
+                       s/()//
+               "
+               ;;
+       *)
+               FMT="%H | %s"
+               munge_actual="s/^\([^ ]*\) .*/\1/"
+               ;;
+       esac &&
+       printf "%s\n" $1 >expect &&
+       shift
+
+       param="$*" &&
+       test_expect_$outcome "log $param" '
+               git log --format="$FMT" $param |
+               unnote >actual &&
+               sed -e "$munge_actual" <actual >check &&
+               test_cmp expect check || {
+                       cat actual
+                       false
+               }
+       '
+}
+
+check_result () {
+       check_outcome success "$@"
+}
+
+# Odd merge G drops a change in F. Important that G is listed in all
+# except the most basic list. Achieving this means normal merge D will also be
+# shown in normal full-history, as we can't distinguish unless we do a
+# simplification pass. After simplification, D is dropped but G remains.
+# Also, merge simplification of G should not drop the parent B that the default
+# simple history follows.
+check_result 'M L K J I H G F E D C B A'
+check_result '(LH)M (K)L (GJ)K (I)J (G)I (G)H (FE)G (D)F (B)E (BC)D (A)C (A)B A'
+check_result 'M H L K J I G E F D C B A' --topo-order
+check_result 'M L H B A' -- file
+check_result '(LH)M (B)L (B)H (A)B A' --parents -- file
+check_result 'M L J I H G F D B A' --full-history -- file
+check_result '(LH)M (K)L (GJ)K (I)J (G)I (G)H (FB)G (D)F (BA)D (A)B A' --full-history --parents -- file
+check_result '(LH)M (G)H (J)L (I)J (G)I (FB)G (B)F (A)B A' --simplify-merges -- file
+check_result 'M L K G F D B A' --first-parent
+check_result 'M L G F B A' --first-parent -- file
+
+# Check that odd merge G remains shown when F is the bottom.
+check_result 'M L K J I H G E' F..M
+check_result 'M H L K J I G E' F..M --topo-order
+check_result 'M L H' F..M -- file
+check_result '(LH)M (B)L (B)H' --parents F..M -- file
+check_result 'M L J I H G' F..M --full-history -- file
+check_result '(LH)M (K)L (GJ)K (I)J (G)I (G)H (FB)G' F..M --full-history --parents -- file
+check_result '(LH)M (G)H (J)L (I)J (G)I (FB)G' F..M --simplify-merges -- file
+check_result 'M L K J I H G' F..M --ancestry-path
+check_result 'M L J I H G' F..M --ancestry-path -- file
+check_result '(LH)M (K)L (GJ)K (I)J (G)I (G)H (FE)G' F..M --ancestry-path --parents -- file
+check_result '(LH)M (G)H (J)L (I)J (G)I (FE)G' F..M --ancestry-path --simplify-merges -- file
+check_result 'M L K G' F..M --first-parent
+check_result 'M L G' F..M --first-parent -- file
+
+# Note that G is pruned when E is the bottom, even if it's the same commit list
+# If we want history since E, then we're quite happy to ignore G that took E.
+check_result 'M L K J I H G' E..M --ancestry-path
+check_result 'M L J I H' E..M --ancestry-path -- file
+check_result '(LH)M (K)L (EJ)K (I)J (E)I (E)H' E..M --ancestry-path --parents -- file
+check_result '(LH)M (E)H (J)L (I)J (E)I' E..M --ancestry-path --simplify-merges -- file
+
+# Should still be able to ignore I-J branch in simple log, despite limiting
+# to G.
+check_result 'M L K J I H' G..M
+check_result 'M H L K J I' G..M --topo-order
+check_result 'M L H' G..M -- file
+check_result '(LH)M (G)L (G)H' G..M --parents -- file
+check_result 'M L J I H' G..M --full-history -- file
+check_result 'M L K J I H' G..M --full-history --parents -- file
+check_result 'M H L J I' G..M --simplify-merges -- file
+check_result 'M L K J I H' G..M --ancestry-path
+check_result 'M L J I H' G..M --ancestry-path -- file
+check_result 'M L K J I H' G..M --ancestry-path --parents -- file
+check_result 'M H L J I' G..M --ancestry-path --simplify-merges -- file
+
+# B..F should be able to simplify the merge D from irrelevant side branch C.
+# Default log should also be free to follow B-D, and ignore C.
+# But --full-history shouldn't drop D on its own - without simplification,
+# we can't decide if the merge from INTERESTING commit C was sensible.
+check_result 'F D C' B..F
+check_result 'F' B..F -- file
+check_result '(B)F' B..F --parents -- file
+check_result 'F D' B..F --full-history -- file
+check_result '(D)F (BA)D' B..F --full-history --parents -- file
+check_result '(B)F' B..F --simplify-merges -- file
+check_result 'F D' B..F --ancestry-path
+check_result 'F' B..F --ancestry-path -- file
+check_result 'F' B..F --ancestry-path --parents -- file
+check_result 'F' B..F --ancestry-path --simplify-merges -- file
+check_result 'F D' B..F --first-parent
+check_result 'F' B..F --first-parent -- file
+
+# E...F should be equivalent to E F ^B, and be able to drop D as above.
+check_result 'F' E F ^B -- file # includes D
+check_result 'F' E...F -- file # includes D
+
+# Any sort of full history of C..F should show D, as it's the connection to C,
+# and it differs from it.
+check_result 'F D B' C..F
+check_result 'F B' C..F -- file
+check_result '(B)F (A)B' C..F --parents -- file
+check_result 'F D B' C..F --full-history -- file
+check_result '(D)F (BC)D (A)B' C..F --full-history --parents -- file
+check_result '(D)F (BC)D (A)B' C..F --simplify-merges -- file
+check_result 'F D' C..F --ancestry-path
+check_result 'F D' C..F --ancestry-path -- file
+check_result 'F D' C..F --ancestry-path --parents -- file
+check_result 'F D' C..F --ancestry-path --simplify-merges -- file
+check_result 'F D B' C..F --first-parent
+check_result 'F B' C..F --first-parent -- file
+
+
+test_done
index a845b154e4db50b52eb67eaefefcc42403c52c0e..101816e718d6149a0e8ee500026455f04a8384ed 100755 (executable)
@@ -218,13 +218,13 @@ test_expect_success 'git mv should not change sha1 of moved cache entry' '
 
 rm -f dirty dirty2
 
-test_expect_success SYMLINKS 'git mv should overwrite symlink to a file' '
+test_expect_success 'git mv should overwrite symlink to a file' '
 
        rm -fr .git &&
        git init &&
        echo 1 >moved &&
-       ln -s moved symlink &&
-       git add moved symlink &&
+       test_ln_s_add moved symlink &&
+       git add moved &&
        test_must_fail git mv moved symlink &&
        git mv -f moved symlink &&
        ! test -e moved &&
@@ -237,22 +237,26 @@ test_expect_success SYMLINKS 'git mv should overwrite symlink to a file' '
 
 rm -f moved symlink
 
-test_expect_success SYMLINKS 'git mv should overwrite file with a symlink' '
+test_expect_success 'git mv should overwrite file with a symlink' '
 
        rm -fr .git &&
        git init &&
        echo 1 >moved &&
-       ln -s moved symlink &&
-       git add moved symlink &&
+       test_ln_s_add moved symlink &&
+       git add moved &&
        test_must_fail git mv symlink moved &&
        git mv -f symlink moved &&
        ! test -e symlink &&
-       test -h moved &&
        git update-index --refresh &&
        git diff-files --quiet
 
 '
 
+test_expect_success SYMLINKS 'check moved symlink' '
+
+       test -h moved
+'
+
 rm -f moved symlink
 
 test_done
index df82ec9ddae30acc5cad23886cbf6ff2de287e7f..300be86c385a93588e987f591357c57421b9cbae 100755 (executable)
@@ -457,7 +457,7 @@ test_expect_success 'disambiguation (1)' '
        test_must_fail git diff --quiet -- secondfile &&
        test -z "$(git diff --cached --name-only)" &&
        test -f secondfile &&
-       test ! -s secondfile
+       test_must_be_empty secondfile
 
 '
 
index ff265353a375d02bb578fed29ea00f07dc08a6fc..50e6ad7458c48842408e2afc20f84c117120c262 100755 (executable)
@@ -78,7 +78,7 @@ test_expect_success 'submodule add' '
        (
                cd addtest &&
                git submodule add -q "$submodurl" submod >actual &&
-               test ! -s actual &&
+               test_must_be_empty actual &&
                echo "gitdir: ../.git/modules/submod" >expect &&
                test_cmp expect submod/.git &&
                (
@@ -212,6 +212,32 @@ test_expect_success 'submodule add with ./, /.. and // in path' '
        test_cmp empty untracked
 '
 
+test_expect_success 'submodule add in subdirectory' '
+       echo "refs/heads/master" >expect &&
+       >empty &&
+
+       mkdir addtest/sub &&
+       (
+               cd addtest/sub &&
+               git submodule add "$submodurl" ../realsubmod3 &&
+               git submodule init
+       ) &&
+
+       rm -f heads head untracked &&
+       inspect addtest/realsubmod3 ../.. &&
+       test_cmp expect heads &&
+       test_cmp expect head &&
+       test_cmp empty untracked
+'
+
+test_expect_success 'submodule add in subdirectory with relative path should fail' '
+       (
+               cd addtest/sub &&
+               test_must_fail git submodule add ../../ submod3 2>../../output.err
+       ) &&
+       test_i18ngrep toplevel output.err
+'
+
 test_expect_success 'setup - add an example entry to .gitmodules' '
        GIT_CONFIG=.gitmodules \
        git config submodule.example.url git://example.com/init.git
@@ -308,7 +334,7 @@ test_expect_success 'update should work when path is an empty dir' '
 
        mkdir init &&
        git submodule update -q >update.out &&
-       test ! -s update.out &&
+       test_must_be_empty update.out &&
 
        inspect init &&
        test_cmp expect head-sha1
@@ -319,6 +345,26 @@ test_expect_success 'status should be "up-to-date" after update' '
        grep "^ $rev1" list
 '
 
+test_expect_success 'status "up-to-date" from subdirectory' '
+       mkdir -p sub &&
+       (
+               cd sub &&
+               git submodule status >../list
+       ) &&
+       grep "^ $rev1" list &&
+       grep "\\.\\./init" list
+'
+
+test_expect_success 'status "up-to-date" from subdirectory with path' '
+       mkdir -p sub &&
+       (
+               cd sub &&
+               git submodule status ../init >../list
+       ) &&
+       grep "^ $rev1" list &&
+       grep "\\.\\./init" list
+'
+
 test_expect_success 'status should be "modified" after submodule commit' '
        (
                cd init &&
@@ -399,6 +445,25 @@ test_expect_success 'update --init' '
        git rev-parse --resolve-git-dir init/.git
 '
 
+test_expect_success 'update --init from subdirectory' '
+       mv init init2 &&
+       git config -f .gitmodules submodule.example.url "$(pwd)/init2" &&
+       git config --remove-section submodule.example &&
+       test_must_fail git config submodule.example.url &&
+
+       mkdir -p sub &&
+       (
+               cd sub &&
+               git submodule update ../init >update.out &&
+               cat update.out &&
+               test_i18ngrep "not initialized" update.out &&
+               test_must_fail git rev-parse --resolve-git-dir ../init/.git &&
+
+               git submodule update --init ../init
+       ) &&
+       git rev-parse --resolve-git-dir init/.git
+'
+
 test_expect_success 'do not add files from a submodule' '
 
        git reset --hard &&
@@ -696,7 +761,7 @@ test_expect_success 'submodule add --name allows to replace a submodule with ano
                rm -rf repo &&
                git rm repo &&
                git submodule add -q --name repo_new "$submodurl/bare.git" repo >actual &&
-               test ! -s actual &&
+               test_must_be_empty actual &&
                echo "gitdir: ../.git/modules/submod" >expect &&
                test_cmp expect submod/.git &&
                (
@@ -772,6 +837,21 @@ test_expect_success 'submodule deinit should remove the whole submodule section
        rmdir init
 '
 
+test_expect_success 'submodule deinit from subdirectory' '
+       git submodule update --init &&
+       git config submodule.example.foo bar &&
+       mkdir -p sub &&
+       (
+               cd sub &&
+               git submodule deinit ../init >../output
+       ) &&
+       grep "\\.\\./init" output &&
+       test -z "$(git config --get-regexp "submodule\.example\.")" &&
+       test -n "$(git config --get-regexp "submodule\.example2\.")" &&
+       test -f example2/.git &&
+       rmdir init
+'
+
 test_expect_success 'submodule deinit . deinits all initialized submodules' '
        git submodule update --init &&
        git config submodule.example.foo bar &&
@@ -868,4 +948,19 @@ test_expect_success 'submodule deinit fails when submodule has a .git directory
        test -n "$(git config --get-regexp "submodule\.example\.")"
 '
 
+test_expect_success 'submodule with UTF-8 name' '
+       svname=$(printf "\303\245 \303\244\303\266") &&
+       mkdir "$svname" &&
+       (
+               cd "$svname" &&
+               git init &&
+               >sub &&
+               git add sub &&
+               git commit -m "init sub"
+       ) &&
+       test_config core.precomposeunicode true &&
+       git submodule add ./"$svname" &&
+       git submodule >&2 &&
+       test -n "$(git submodule | grep "$svname")"
+'
 test_done
index 30b429e7dcbcd56af21ded67128aa18901133275..ac2434c0dbdaf837d8dd7f896e680fbb7b6ab954 100755 (executable)
@@ -45,6 +45,42 @@ EOF
        test_cmp expected actual
 "
 
+test_expect_success 'added submodule (subdirectory)' "
+       mkdir sub &&
+       (
+               cd sub &&
+               git submodule summary >../actual
+       ) &&
+       cat >expected <<-EOF &&
+* ../sm1 0000000...$head1 (2):
+  > Add foo2
+
+EOF
+       test_cmp expected actual
+"
+
+test_expect_success 'added submodule (subdirectory only)' "
+       (
+               cd sub &&
+               git submodule summary . >../actual
+       ) &&
+       >expected &&
+       test_cmp expected actual
+"
+
+test_expect_success 'added submodule (subdirectory with explicit path)' "
+       (
+               cd sub &&
+               git submodule summary ../sm1 >../actual
+       ) &&
+       cat >expected <<-EOF &&
+* ../sm1 0000000...$head1 (2):
+  > Add foo2
+
+EOF
+       test_cmp expected actual
+"
+
 commit_file sm1 &&
 head2=$(add_file sm1 foo3)
 
@@ -76,8 +112,8 @@ head3=$(
 )
 
 test_expect_success 'modified submodule(backward)' "
-    git submodule summary >actual &&
-    cat >expected <<-EOF &&
+       git submodule summary >actual &&
+       cat >expected <<-EOF &&
 * sm1 $head2...$head3 (2):
   < Add foo3
   < Add foo2
@@ -89,8 +125,8 @@ EOF
 head4=$(add_file sm1 foo4 foo5) &&
 head4_full=$(GIT_DIR=sm1/.git git rev-parse --verify HEAD)
 test_expect_success 'modified submodule(backward and forward)' "
-    git submodule summary >actual &&
-    cat >expected <<-EOF &&
+       git submodule summary >actual &&
+       cat >expected <<-EOF &&
 * sm1 $head2...$head4 (4):
   > Add foo5
   > Add foo4
@@ -102,15 +138,15 @@ EOF
 "
 
 test_expect_success '--summary-limit' "
-    git submodule summary -n 3 >actual &&
-    cat >expected <<-EOF &&
+       git submodule summary -n 3 >actual &&
+       cat >expected <<-EOF &&
 * sm1 $head2...$head4 (4):
   > Add foo5
   > Add foo4
   < Add foo3
 
 EOF
-    test_cmp expected actual
+       test_cmp expected actual
 "
 
 commit_file sm1 &&
@@ -122,8 +158,8 @@ rm -f sm1 &&
 mv sm1-bak sm1
 
 test_expect_success 'typechanged submodule(submodule->blob), --cached' "
-    git submodule summary --cached >actual &&
-    cat >expected <<-EOF &&
+       git submodule summary --cached >actual &&
+       cat >expected <<-EOF &&
 * sm1 $head4(submodule)->$head5(blob) (3):
   < Add foo5
 
@@ -132,59 +168,59 @@ EOF
 "
 
 test_expect_success 'typechanged submodule(submodule->blob), --files' "
-    git submodule summary --files >actual &&
-    cat >expected <<-EOF &&
+       git submodule summary --files >actual &&
+       cat >expected <<-EOF &&
 * sm1 $head5(blob)->$head4(submodule) (3):
   > Add foo5
 
 EOF
-    test_i18ncmp actual expected
+       test_i18ncmp actual expected
 "
 
 rm -rf sm1 &&
 git checkout-index sm1
 test_expect_success 'typechanged submodule(submodule->blob)' "
-    git submodule summary >actual &&
-    cat >expected <<-EOF &&
+       git submodule summary >actual &&
+       cat >expected <<-EOF &&
 * sm1 $head4(submodule)->$head5(blob):
 
 EOF
-    test_i18ncmp actual expected
+       test_i18ncmp actual expected
 "
 
 rm -f sm1 &&
 test_create_repo sm1 &&
 head6=$(add_file sm1 foo6 foo7)
 test_expect_success 'nonexistent commit' "
-    git submodule summary >actual &&
-    cat >expected <<-EOF &&
+       git submodule summary >actual &&
+       cat >expected <<-EOF &&
 * sm1 $head4...$head6:
   Warn: sm1 doesn't contain commit $head4_full
 
 EOF
-    test_i18ncmp actual expected
+       test_i18ncmp actual expected
 "
 
 commit_file
 test_expect_success 'typechanged submodule(blob->submodule)' "
-    git submodule summary >actual &&
-    cat >expected <<-EOF &&
+       git submodule summary >actual &&
+       cat >expected <<-EOF &&
 * sm1 $head5(blob)->$head6(submodule) (2):
   > Add foo7
 
 EOF
-    test_i18ncmp expected actual
+       test_i18ncmp expected actual
 "
 
 commit_file sm1 &&
 rm -rf sm1
 test_expect_success 'deleted submodule' "
-    git submodule summary >actual &&
-    cat >expected <<-EOF &&
+       git submodule summary >actual &&
+       cat >expected <<-EOF &&
 * sm1 $head6...0000000:
 
 EOF
-    test_cmp expected actual
+       test_cmp expected actual
 "
 
 test_create_repo sm2 &&
@@ -192,43 +228,43 @@ head7=$(add_file sm2 foo8 foo9) &&
 git add sm2
 
 test_expect_success 'multiple submodules' "
-    git submodule summary >actual &&
-    cat >expected <<-EOF &&
+       git submodule summary >actual &&
+       cat >expected <<-EOF &&
 * sm1 $head6...0000000:
 
 * sm2 0000000...$head7 (2):
   > Add foo9
 
 EOF
-    test_cmp expected actual
+       test_cmp expected actual
 "
 
 test_expect_success 'path filter' "
-    git submodule summary sm2 >actual &&
-    cat >expected <<-EOF &&
+       git submodule summary sm2 >actual &&
+       cat >expected <<-EOF &&
 * sm2 0000000...$head7 (2):
   > Add foo9
 
 EOF
-    test_cmp expected actual
+       test_cmp expected actual
 "
 
 commit_file sm2
 test_expect_success 'given commit' "
-    git submodule summary HEAD^ >actual &&
-    cat >expected <<-EOF &&
+       git submodule summary HEAD^ >actual &&
+       cat >expected <<-EOF &&
 * sm1 $head6...0000000:
 
 * sm2 0000000...$head7 (2):
   > Add foo9
 
 EOF
-    test_cmp expected actual
+       test_cmp expected actual
 "
 
 test_expect_success '--for-status' "
-    git submodule summary --for-status HEAD^ >actual &&
-    test_i18ncmp actual - <<EOF
+       git submodule summary --for-status HEAD^ >actual &&
+       test_i18ncmp actual - <<EOF
 # Submodule changes to be committed:
 #
 # * sm1 $head6...0000000:
@@ -240,14 +276,14 @@ EOF
 "
 
 test_expect_success 'fail when using --files together with --cached' "
-    test_must_fail git submodule summary --files --cached
+       test_must_fail git submodule summary --files --cached
 "
 
 test_expect_success 'should not fail in an empty repo' "
-    git init xyzzy &&
-    cd xyzzy &&
-    git submodule summary >output 2>&1 &&
-    test_cmp output /dev/null
+       git init xyzzy &&
+       cd xyzzy &&
+       git submodule summary >output 2>&1 &&
+       test_cmp output /dev/null
 "
 
 test_done
index 94e26c47eae37062ca0d7d24d30104fb46d06743..79bc135bf69ea5813f0a372db6bbefa0f9a92753 100755 (executable)
@@ -11,216 +11,338 @@ These tests exercise the "git submodule sync" subcommand.
 . ./test-lib.sh
 
 test_expect_success setup '
-       echo file > file &&
+       echo file >file &&
        git add file &&
        test_tick &&
        git commit -m upstream &&
        git clone . super &&
        git clone super submodule &&
-       (cd submodule &&
-        git submodule add ../submodule sub-submodule &&
-        test_tick &&
-        git commit -m "sub-submodule"
+       (
+               cd submodule &&
+               git submodule add ../submodule sub-submodule &&
+               test_tick &&
+               git commit -m "sub-submodule"
        ) &&
-       (cd super &&
-        git submodule add ../submodule submodule &&
-        test_tick &&
-        git commit -m "submodule"
+       (
+               cd super &&
+               git submodule add ../submodule submodule &&
+               test_tick &&
+               git commit -m "submodule"
        ) &&
        git clone super super-clone &&
-       (cd super-clone && git submodule update --init --recursive) &&
+       (
+               cd super-clone &&
+               git submodule update --init --recursive
+       ) &&
        git clone super empty-clone &&
-       (cd empty-clone && git submodule init) &&
+       (
+               cd empty-clone &&
+               git submodule init
+       ) &&
        git clone super top-only-clone &&
        git clone super relative-clone &&
-       (cd relative-clone && git submodule update --init --recursive) &&
+       (
+               cd relative-clone &&
+               git submodule update --init --recursive
+       ) &&
        git clone super recursive-clone &&
-       (cd recursive-clone && git submodule update --init --recursive)
+       (
+               cd recursive-clone &&
+               git submodule update --init --recursive
+       )
 '
 
 test_expect_success 'change submodule' '
-       (cd submodule &&
-        echo second line >> file &&
-        test_tick &&
-        git commit -a -m "change submodule"
+       (
+               cd submodule &&
+               echo second line >>file &&
+               test_tick &&
+               git commit -a -m "change submodule"
        )
 '
 
+reset_submodule_urls () {
+       local root
+       root=$(pwd) &&
+       (
+               cd super-clone/submodule &&
+               git config remote.origin.url "$root/submodule"
+       ) &&
+       (
+               cd super-clone/submodule/sub-submodule &&
+               git config remote.origin.url "$root/submodule"
+       )
+}
+
 test_expect_success 'change submodule url' '
-       (cd super &&
-        cd submodule &&
-        git checkout master &&
-        git pull
+       (
+               cd super &&
+               cd submodule &&
+               git checkout master &&
+               git pull
        ) &&
        mv submodule moved-submodule &&
-       (cd moved-submodule &&
-        git config -f .gitmodules submodule.sub-submodule.url ../moved-submodule &&
-        test_tick &&
-        git commit -a -m moved-sub-submodule
+       (
+               cd moved-submodule &&
+               git config -f .gitmodules submodule.sub-submodule.url ../moved-submodule &&
+               test_tick &&
+               git commit -a -m moved-sub-submodule
        ) &&
-       (cd super &&
-        git config -f .gitmodules submodule.submodule.url ../moved-submodule &&
-        test_tick &&
-        git commit -a -m moved-submodule
+       (
+               cd super &&
+               git config -f .gitmodules submodule.submodule.url ../moved-submodule &&
+               test_tick &&
+               git commit -a -m moved-submodule
        )
 '
 
 test_expect_success '"git submodule sync" should update submodule URLs' '
-       (cd super-clone &&
-        git pull --no-recurse-submodules &&
-        git submodule sync
+       (
+               cd super-clone &&
+               git pull --no-recurse-submodules &&
+               git submodule sync
        ) &&
-       test -d "$(cd super-clone/submodule &&
-        git config remote.origin.url
+       test -d "$(
+               cd super-clone/submodule &&
+               git config remote.origin.url
        )" &&
-       test ! -d "$(cd super-clone/submodule/sub-submodule &&
-        git config remote.origin.url
+       test ! -d "$(
+               cd super-clone/submodule/sub-submodule &&
+               git config remote.origin.url
        )" &&
-       (cd super-clone/submodule &&
-        git checkout master &&
-        git pull
+       (
+               cd super-clone/submodule &&
+               git checkout master &&
+               git pull
        ) &&
-       (cd super-clone &&
-        test -d "$(git config submodule.submodule.url)"
+       (
+               cd super-clone &&
+               test -d "$(git config submodule.submodule.url)"
        )
 '
 
 test_expect_success '"git submodule sync --recursive" should update all submodule URLs' '
-       (cd super-clone &&
-        (cd submodule &&
-         git pull --no-recurse-submodules
-        ) &&
-        git submodule sync --recursive
+       (
+               cd super-clone &&
+               (
+                       cd submodule &&
+                       git pull --no-recurse-submodules
+               ) &&
+               git submodule sync --recursive
+       ) &&
+       test -d "$(
+               cd super-clone/submodule &&
+               git config remote.origin.url
+       )" &&
+       test -d "$(
+               cd super-clone/submodule/sub-submodule &&
+               git config remote.origin.url
+       )" &&
+       (
+               cd super-clone/submodule/sub-submodule &&
+               git checkout master &&
+               git pull
+       )
+'
+
+test_expect_success 'reset submodule URLs' '
+       reset_submodule_urls super-clone
+'
+
+test_expect_success '"git submodule sync" should update submodule URLs - subdirectory' '
+       (
+               cd super-clone &&
+               git pull --no-recurse-submodules &&
+               mkdir -p sub &&
+               cd sub &&
+               git submodule sync >../../output
+       ) &&
+       grep "\\.\\./submodule" output &&
+       test -d "$(
+               cd super-clone/submodule &&
+               git config remote.origin.url
+       )" &&
+       test ! -d "$(
+               cd super-clone/submodule/sub-submodule &&
+               git config remote.origin.url
+       )" &&
+       (
+               cd super-clone/submodule &&
+               git checkout master &&
+               git pull
+       ) &&
+       (
+               cd super-clone &&
+               test -d "$(git config submodule.submodule.url)"
+       )
+'
+
+test_expect_success '"git submodule sync --recursive" should update all submodule URLs - subdirectory' '
+       (
+               cd super-clone &&
+               (
+                       cd submodule &&
+                       git pull --no-recurse-submodules
+               ) &&
+               mkdir -p sub &&
+               cd sub &&
+               git submodule sync --recursive >../../output
        ) &&
-       test -d "$(cd super-clone/submodule &&
-        git config remote.origin.url
+       grep "\\.\\./submodule/sub-submodule" output &&
+       test -d "$(
+               cd super-clone/submodule &&
+               git config remote.origin.url
        )" &&
-       test -d "$(cd super-clone/submodule/sub-submodule &&
-        git config remote.origin.url
+       test -d "$(
+               cd super-clone/submodule/sub-submodule &&
+               git config remote.origin.url
        )" &&
-       (cd super-clone/submodule/sub-submodule &&
-        git checkout master &&
-        git pull
+       (
+               cd super-clone/submodule/sub-submodule &&
+               git checkout master &&
+               git pull
        )
 '
 
 test_expect_success '"git submodule sync" should update known submodule URLs' '
-       (cd empty-clone &&
-        git pull &&
-        git submodule sync &&
-        test -d "$(git config submodule.submodule.url)"
+       (
+               cd empty-clone &&
+               git pull &&
+               git submodule sync &&
+               test -d "$(git config submodule.submodule.url)"
        )
 '
 
 test_expect_success '"git submodule sync" should not vivify uninteresting submodule' '
-       (cd top-only-clone &&
-        git pull &&
-        git submodule sync &&
-        test -z "$(git config submodule.submodule.url)" &&
-        git submodule sync submodule &&
-        test -z "$(git config submodule.submodule.url)"
+       (
+               cd top-only-clone &&
+               git pull &&
+               git submodule sync &&
+               test -z "$(git config submodule.submodule.url)" &&
+               git submodule sync submodule &&
+               test -z "$(git config submodule.submodule.url)"
        )
 '
 
 test_expect_success '"git submodule sync" handles origin URL of the form foo' '
-       (cd relative-clone &&
-        git remote set-url origin foo &&
-        git submodule sync &&
-       (cd submodule &&
-        #actual fails with: "cannot strip off url foo
-        test "$(git config remote.origin.url)" = "../submodule"
-       )
+       (
+               cd relative-clone &&
+               git remote set-url origin foo &&
+               git submodule sync &&
+               (
+                       cd submodule &&
+                       #actual fails with: "cannot strip off url foo
+                       test "$(git config remote.origin.url)" = "../submodule"
+               )
        )
 '
 
 test_expect_success '"git submodule sync" handles origin URL of the form foo/bar' '
-       (cd relative-clone &&
-        git remote set-url origin foo/bar &&
-        git submodule sync &&
-       (cd submodule &&
-        #actual foo/submodule
-        test "$(git config remote.origin.url)" = "../foo/submodule"
-       )
-       (cd submodule/sub-submodule &&
-        test "$(git config remote.origin.url)" != "../../foo/submodule"
-       )
+       (
+               cd relative-clone &&
+               git remote set-url origin foo/bar &&
+               git submodule sync &&
+               (
+                       cd submodule &&
+                       #actual foo/submodule
+                       test "$(git config remote.origin.url)" = "../foo/submodule"
+               ) &&
+               (
+                       cd submodule/sub-submodule &&
+                       test "$(git config remote.origin.url)" != "../../foo/submodule"
+               )
        )
 '
 
 test_expect_success '"git submodule sync --recursive" propagates changes in origin' '
-       (cd recursive-clone &&
-        git remote set-url origin foo/bar &&
-        git submodule sync --recursive &&
-       (cd submodule &&
-        #actual foo/submodule
-        test "$(git config remote.origin.url)" = "../foo/submodule"
-       )
-       (cd submodule/sub-submodule &&
-        test "$(git config remote.origin.url)" = "../../foo/submodule"
-       )
+       (
+               cd recursive-clone &&
+               git remote set-url origin foo/bar &&
+               git submodule sync --recursive &&
+               (
+                       cd submodule &&
+                       #actual foo/submodule
+                       test "$(git config remote.origin.url)" = "../foo/submodule"
+               ) &&
+               (
+                       cd submodule/sub-submodule &&
+                       test "$(git config remote.origin.url)" = "../../foo/submodule"
+               )
        )
 '
 
 test_expect_success '"git submodule sync" handles origin URL of the form ./foo' '
-       (cd relative-clone &&
-        git remote set-url origin ./foo &&
-        git submodule sync &&
-       (cd submodule &&
-        #actual ./submodule
-        test "$(git config remote.origin.url)" = "../submodule"
-       )
+       (
+               cd relative-clone &&
+               git remote set-url origin ./foo &&
+               git submodule sync &&
+               (
+                       cd submodule &&
+                       #actual ./submodule
+                       test "$(git config remote.origin.url)" = "../submodule"
+               )
        )
 '
 
 test_expect_success '"git submodule sync" handles origin URL of the form ./foo/bar' '
-       (cd relative-clone &&
-        git remote set-url origin ./foo/bar &&
-        git submodule sync &&
-       (cd submodule &&
-        #actual ./foo/submodule
-        test "$(git config remote.origin.url)" = "../foo/submodule"
-       )
+       (
+               cd relative-clone &&
+               git remote set-url origin ./foo/bar &&
+               git submodule sync &&
+               (
+                       cd submodule &&
+                       #actual ./foo/submodule
+                       test "$(git config remote.origin.url)" = "../foo/submodule"
+               )
        )
 '
 
 test_expect_success '"git submodule sync" handles origin URL of the form ../foo' '
-       (cd relative-clone &&
-        git remote set-url origin ../foo &&
-        git submodule sync &&
-       (cd submodule &&
-        #actual ../submodule
-        test "$(git config remote.origin.url)" = "../../submodule"
-       )
+       (
+               cd relative-clone &&
+               git remote set-url origin ../foo &&
+               git submodule sync &&
+               (
+                       cd submodule &&
+                       #actual ../submodule
+                       test "$(git config remote.origin.url)" = "../../submodule"
+               )
        )
 '
 
 test_expect_success '"git submodule sync" handles origin URL of the form ../foo/bar' '
-       (cd relative-clone &&
-        git remote set-url origin ../foo/bar &&
-        git submodule sync &&
-       (cd submodule &&
-        #actual ../foo/submodule
-        test "$(git config remote.origin.url)" = "../../foo/submodule"
-       )
+       (
+               cd relative-clone &&
+               git remote set-url origin ../foo/bar &&
+               git submodule sync &&
+               (
+                       cd submodule &&
+                       #actual ../foo/submodule
+                       test "$(git config remote.origin.url)" = "../../foo/submodule"
+               )
        )
 '
 
 test_expect_success '"git submodule sync" handles origin URL of the form ../foo/bar with deeply nested submodule' '
-       (cd relative-clone &&
-        git remote set-url origin ../foo/bar &&
-        mkdir -p a/b/c &&
-        ( cd a/b/c &&
-          git init &&
-          :> .gitignore &&
-          git add .gitignore &&
-          test_tick &&
-          git commit -m "initial commit" ) &&
-        git submodule add ../bar/a/b/c ./a/b/c &&
-        git submodule sync &&
-       (cd a/b/c &&
-        #actual ../foo/bar/a/b/c
-        test "$(git config remote.origin.url)" = "../../../../foo/bar/a/b/c"
-       )
+       (
+               cd relative-clone &&
+               git remote set-url origin ../foo/bar &&
+               mkdir -p a/b/c &&
+               (
+                       cd a/b/c &&
+                       git init &&
+                       >.gitignore &&
+                       git add .gitignore &&
+                       test_tick &&
+                       git commit -m "initial commit"
+               ) &&
+               git submodule add ../bar/a/b/c ./a/b/c &&
+               git submodule sync &&
+               (
+                       cd a/b/c &&
+                       #actual ../foo/bar/a/b/c
+                       test "$(git config remote.origin.url)" = "../../../../foo/bar/a/b/c"
+               )
        )
 '
 
index a4ffea0dbe743a1bbe4c5512037a6b1ffde1c313..cdb0538392feaffa69675dcd40e7ecf4094f136a 100755 (executable)
@@ -80,6 +80,21 @@ test_expect_success 'submodule update detaching the HEAD ' '
        )
 '
 
+test_expect_success 'submodule update from subdirectory' '
+       (cd super/submodule &&
+        git reset --hard HEAD~1
+       ) &&
+       mkdir super/sub &&
+       (cd super/sub &&
+        (cd ../submodule &&
+         compare_head
+        ) &&
+        git submodule update ../submodule &&
+        cd ../submodule &&
+        ! compare_head
+       )
+'
+
 apos="'";
 test_expect_success 'submodule update does not fetch already present commits' '
        (cd submodule &&
index 107b4b7c455dc0be6400eb1d462868b940b1247d..91d4fd1facf8dc6f97ecd77cdfcdd03b7e1d8927 100755 (executable)
@@ -80,6 +80,22 @@ test_expect_success 'test basic "submodule foreach" usage' '
        test_i18ncmp expect actual
 '
 
+cat >expect <<EOF
+Entering '../sub1'
+$pwd/clone-foo1-../sub1-$sub1sha1
+Entering '../sub3'
+$pwd/clone-foo3-../sub3-$sub3sha1
+EOF
+
+test_expect_success 'test "submodule foreach" from subdirectory' '
+       mkdir clone/sub &&
+       (
+               cd clone/sub &&
+               git submodule foreach "echo \$toplevel-\$name-\$sm_path-\$sha1" >../../actual
+       ) &&
+       test_i18ncmp expect actual
+'
+
 test_expect_success 'setup nested submodules' '
        git clone submodule nested1 &&
        git clone submodule nested2 &&
index bf08d4e098f1bdc3adc5ec3df37b90d90b0e5c8f..4f09beca90ec18c9e541f1f880471f9f2de0de8a 100755 (executable)
@@ -632,7 +632,8 @@ test_expect_success 'status when cherry-picking before resolving conflicts' '
        cat >expected <<-\EOF &&
        # On branch cherry_branch
        # You are currently cherry-picking.
-       #   (fix conflicts and run "git commit")
+       #   (fix conflicts and run "git cherry-pick --continue")
+       #   (use "git cherry-pick --abort" to cancel the cherry-pick operation)
        #
        # Unmerged paths:
        #   (use "git add <file>..." to mark resolution)
@@ -655,7 +656,8 @@ test_expect_success 'status when cherry-picking after resolving conflicts' '
        cat >expected <<-\EOF &&
        # On branch cherry_branch
        # You are currently cherry-picking.
-       #   (all conflicts fixed: run "git commit")
+       #   (all conflicts fixed: run "git cherry-pick --continue")
+       #   (use "git cherry-pick --abort" to cancel the cherry-pick operation)
        #
        # Changes to be committed:
        #
index 2f70433568e3134b09d7e1eb10cc5c8995c1a106..460d8ebf48f9ab3826e58e2e9285f6faecd79b78 100755 (executable)
@@ -316,7 +316,7 @@ test_expect_success 'merge c1 with c2 (squash)' '
 
 test_debug 'git log --graph --decorate --oneline --all'
 
-test_expect_success 'unsuccesful merge of c1 with c2 (squash, ff-only)' '
+test_expect_success 'unsuccessful merge of c1 with c2 (squash, ff-only)' '
        git reset --hard c1 &&
        test_must_fail git merge --squash --ff-only c2
 '
index 6547eb8f5459d4d95113469d338e1879f86b79ea..758a623cdbb5f7e367bcfce5c239d79258c46e84 100755 (executable)
@@ -141,11 +141,10 @@ test_expect_success SYMLINKS 'will not overwrite untracked symlink in leading pa
        test_path_is_missing .git/MERGE_HEAD
 '
 
-test_expect_success SYMLINKS 'will not be confused by symlink in leading path' '
+test_expect_success 'will not be confused by symlink in leading path' '
        git reset --hard c0 &&
        rm -rf sub &&
-       ln -s sub2 sub &&
-       git add sub &&
+       test_ln_s_add sub2 sub &&
        git commit -m ln &&
        git checkout sub
 '
index bf6caa4dc3d42230757526dd215ab777f77ae369..7683515155497d9303f319b31c3a49fb6610706f 100755 (executable)
@@ -18,17 +18,13 @@ test_expect_success 'setup ' '
        echo "bin: test number 0" >zero.bin &&
        echo "bin: test 1" >one.bin &&
        echo "bin: test number 2" >two.bin &&
-       if test_have_prereq SYMLINKS; then
-               ln -s one.bin symlink.bin
-       fi &&
+       test_ln_s_add one.bin symlink.bin &&
        git add . &&
        GIT_AUTHOR_NAME=Number1 git commit -a -m First --date="2010-01-01 18:00:00" &&
        echo "bin: test 1 version 2" >one.bin &&
        echo "bin: test number 2 version 2" >>two.bin &&
-       if test_have_prereq SYMLINKS; then
-               rm symlink.bin &&
-               ln -s two.bin symlink.bin
-       fi &&
+       rm -f symlink.bin &&
+       test_ln_s_add two.bin symlink.bin &&
        GIT_AUTHOR_NAME=Number2 git commit -a -m Second --date="2010-01-01 20:00:00"
 '
 
@@ -135,7 +131,7 @@ test_expect_success SYMLINKS 'blame --textconv (on symlink)' '
 
 # cp two.bin three.bin  and make small tweak
 # (this will direct blame -C -C three.bin to consider two.bin and symlink.bin)
-test_expect_success SYMLINKS 'make another new commit' '
+test_expect_success 'make another new commit' '
        cat >three.bin <<\EOF &&
 bin: test number 2
 bin: test number 2 version 2
@@ -146,7 +142,7 @@ EOF
        GIT_AUTHOR_NAME=Number4 git commit -a -m Fourth --date="2010-01-01 23:00:00"
 '
 
-test_expect_success SYMLINKS 'blame on last commit (-C -C, symlink)' '
+test_expect_success 'blame on last commit (-C -C, symlink)' '
        git blame -C -C three.bin >blame &&
        find_blame <blame >result &&
        cat >expected <<\EOF &&
index 78a0085e648b8fa6773b47e86959853cf29ccdf9..b95e102891db65c184a2ee3137f1b3e20cdab2db 100755 (executable)
@@ -12,9 +12,7 @@ chmod +x helper
 
 test_expect_success 'setup ' '
        echo "bin: test" >one.bin &&
-       if test_have_prereq SYMLINKS; then
-               ln -s one.bin symlink.bin
-       fi &&
+       test_ln_s_add one.bin symlink.bin &&
        git add . &&
        GIT_AUTHOR_NAME=Number1 git commit -a -m First --date="2010-01-01 18:00:00" &&
        echo "bin: test version 2" >one.bin &&
@@ -72,14 +70,14 @@ test_expect_success 'cat-file --textconv on previous commit' '
        test_cmp expected result
 '
 
-test_expect_success SYMLINKS 'cat-file without --textconv (symlink)' '
+test_expect_success 'cat-file without --textconv (symlink)' '
        git cat-file blob :symlink.bin >result &&
        printf "%s" "one.bin" >expected
        test_cmp expected result
 '
 
 
-test_expect_success SYMLINKS 'cat-file --textconv on index (symlink)' '
+test_expect_success 'cat-file --textconv on index (symlink)' '
        ! git cat-file --textconv :symlink.bin 2>result &&
        cat >expected <<\EOF &&
 fatal: git cat-file --textconv: unable to run textconv on :symlink.bin
@@ -87,7 +85,7 @@ EOF
        test_cmp expected result
 '
 
-test_expect_success SYMLINKS 'cat-file --textconv on HEAD (symlink)' '
+test_expect_success 'cat-file --textconv on HEAD (symlink)' '
        ! git cat-file --textconv HEAD:symlink.bin 2>result &&
        cat >expected <<EOF &&
 fatal: git cat-file --textconv: unable to run textconv on HEAD:symlink.bin
index 5d2dbe98d5e01e8b18b5d9fe26ce5ed6f2bf171d..2813aa9a61dc52d6d72f684c51fe6894e623754d 100755 (executable)
@@ -171,6 +171,81 @@ Result: OK
 EOF
 "
 
+test_suppress_self () {
+       test_commit $3 &&
+       test_when_finished "git reset --hard HEAD^" &&
+
+       write_script cccmd-sed <<-EOF &&
+               sed -n -e s/^cccmd--//p "\$1"
+       EOF
+
+       git commit --amend --author="$1 <$2>" -F - &&
+       clean_fake_sendmail &&
+       git format-patch --stdout -1 >"suppress-self-$3.patch" &&
+
+       git send-email --from="$1 <$2>" \
+               --to=nobody@example.com \
+               --cc-cmd=./cccmd-sed \
+               --suppress-cc=self \
+               --smtp-server="$(pwd)/fake.sendmail" \
+               suppress-self-$3.patch &&
+
+       mv msgtxt1 msgtxt1-$3 &&
+       sed -e '/^$/q' msgtxt1-$3 >"msghdr1-$3" &&
+       >"expected-no-cc-$3" &&
+
+       (grep '^Cc:' msghdr1-$3 >"actual-no-cc-$3";
+        test_cmp expected-no-cc-$3 actual-no-cc-$3)
+}
+
+test_suppress_self_unquoted () {
+       test_suppress_self "$1" "$2" "unquoted-$3" <<-EOF
+               test suppress-cc.self unquoted-$3 with name $1 email $2
+
+               unquoted-$3
+
+               cccmd--$1 <$2>
+
+               Cc: $1 <$2>
+               Signed-off-by: $1 <$2>
+       EOF
+}
+
+test_suppress_self_quoted () {
+       test_suppress_self "$1" "$2" "quoted-$3" <<-EOF
+               test suppress-cc.self quoted-$3 with name $1 email $2
+
+               quoted-$3
+
+               cccmd--"$1" <$2>
+
+               Cc: $1 <$2>
+               Cc: "$1" <$2>
+               Signed-off-by: $1 <$2>
+               Signed-off-by: "$1" <$2>
+       EOF
+}
+
+test_expect_success $PREREQ 'self name is suppressed' "
+       test_suppress_self_unquoted 'A U Thor' 'author@example.com' \
+               'self_name_suppressed'
+"
+
+test_expect_success $PREREQ 'self name with dot is suppressed' "
+       test_suppress_self_quoted 'A U. Thor' 'author@example.com' \
+               'self_name_dot_suppressed'
+"
+
+test_expect_success $PREREQ 'non-ascii self name is suppressed' "
+       test_suppress_self_quoted 'Füñný Nâmé' 'odd_?=mail@example.com' \
+               'non_ascii_self_suppressed'
+"
+
+test_expect_success $PREREQ 'sanitized self name is suppressed' "
+       test_suppress_self_unquoted '\"A U. Thor\"' 'author@example.com' \
+               'self_name_sanitized_suppressed'
+"
+
 test_expect_success $PREREQ 'Show all headers' '
        git send-email \
                --dry-run \
@@ -881,6 +956,20 @@ test_expect_success $PREREQ 'utf8 author is correctly passed on' '
        grep "^From: Füñný Nâmé <odd_?=mail@example.com>" msgtxt1
 '
 
+test_expect_success $PREREQ 'utf8 sender is not duplicated' '
+       clean_fake_sendmail &&
+       test_commit weird_sender &&
+       test_when_finished "git reset --hard HEAD^" &&
+       git commit --amend --author "Füñný Nâmé <odd_?=mail@example.com>" &&
+       git format-patch --stdout -1 >funny_name.patch &&
+       git send-email --from="Füñný Nâmé <odd_?=mail@example.com>" \
+         --to=nobody@example.com \
+         --smtp-server="$(pwd)/fake.sendmail" \
+         funny_name.patch &&
+       grep "^From: " msgtxt1 >msgfrom &&
+       test_line_count = 1 msgfrom
+'
+
 test_expect_success $PREREQ 'sendemail.composeencoding works' '
        clean_fake_sendmail &&
        git config sendemail.composeencoding iso-8859-1 &&
index 2471bc6777df58c222a96d4fc6e77d77e9e38703..34c2d8f49ab6fd02a19e4b9f95e31838c8de3ece 100755 (executable)
@@ -396,7 +396,7 @@ test_expect_success 'tree_tag-obj'    'git fast-export tree_tag-obj'
 test_expect_success 'tag-obj_tag'     'git fast-export tag-obj_tag'
 test_expect_success 'tag-obj_tag-obj' 'git fast-export tag-obj_tag-obj'
 
-test_expect_success SYMLINKS 'directory becomes symlink'        '
+test_expect_success 'directory becomes symlink'        '
        git init dirtosymlink &&
        git init result &&
        (
@@ -408,8 +408,7 @@ test_expect_success SYMLINKS 'directory becomes symlink'        '
                git add foo/world bar/world &&
                git commit -q -mone &&
                git rm -r foo &&
-               ln -s bar foo &&
-               git add foo &&
+               test_ln_s_add bar foo &&
                git commit -q -mtwo
        ) &&
        (
index 735a018eccf934ef1e0bc96b905142d4823a2814..db69af2cff72733bda515446a48b5ec8dcee23fd 100755 (executable)
@@ -330,7 +330,7 @@ test_expect_success 'validate result of edits [cvswork2]' '
 
 test_expect_success 'validate basic diffs saved during above cvswork2 edits' '
        test $(grep Index: cvsEdit1.diff | wc -l) = 1 &&
-       test ! -s cvsEdit2-empty.diff &&
+       test_must_be_empty cvsEdit2-empty.diff &&
        test $(grep Index: cvsEdit2-N.diff | wc -l) = 1 &&
        test $(grep Index: cvsEdit3.diff | wc -l) = 3 &&
        rm -rf diffSandbox &&
@@ -456,20 +456,20 @@ test_expect_success 'cvs up -r $(git rev-parse v1)' '
 
 test_expect_success 'cvs diff -r v1 -u' '
        ( cd cvswork && cvs -f diff -r v1 -u ) >cvsDiff.out 2>cvs.log &&
-       test ! -s cvsDiff.out &&
-       test ! -s cvs.log
+       test_must_be_empty cvsDiff.out &&
+       test_must_be_empty cvs.log
 '
 
 test_expect_success 'cvs diff -N -r v2 -u' '
        ( cd cvswork && ! cvs -f diff -N -r v2 -u ) >cvsDiff.out 2>cvs.log &&
-       test ! -s cvs.log &&
+       test_must_be_empty cvs.log &&
        test -s cvsDiff.out &&
        check_diff cvsDiff.out v2 v1 >check_diff.out 2>&1
 '
 
 test_expect_success 'cvs diff -N -r v2 -r v1.2' '
        ( cd cvswork && ! cvs -f diff -N -r v2 -r v1.2 -u ) >cvsDiff.out 2>cvs.log &&
-       test ! -s cvs.log &&
+       test_must_be_empty cvs.log &&
        test -s cvsDiff.out &&
        check_diff cvsDiff.out v2 v1.2 >check_diff.out 2>&1
 '
@@ -488,7 +488,7 @@ test_expect_success 'apply early [cvswork3] diff to b3' '
 
 test_expect_success 'check [cvswork3] diff' '
        ( cd cvswork3 && ! cvs -f diff -N -u ) >"$WORKDIR/cvsDiff.out" 2>cvs.log &&
-       test ! -s cvs.log &&
+       test_must_be_empty cvs.log &&
        test -s cvsDiff.out &&
        test $(grep Index: cvsDiff.out | wc -l) = 3 &&
        test_cmp cvsDiff.out cvswork3edit.diff &&
index 6783c14c1ad9af0f5aa3d16e13250e9a1a292b28..6fca19353d7308f88e63806f7187d9d379412d18 100755 (executable)
@@ -156,10 +156,10 @@ test_expect_success \
         git commit -a -m "File renamed." &&
         gitweb_run "p=.git;a=commitdiff"'
 
-test_expect_success SYMLINKS \
+test_expect_success \
        'commitdiff(0): file to symlink' \
        'rm renamed_file &&
-        ln -s file renamed_file &&
+        test_ln_s_add file renamed_file &&
         git commit -a -m "File to symlink." &&
         gitweb_run "p=.git;a=commitdiff"'
 
@@ -212,15 +212,14 @@ test_expect_success \
 # ----------------------------------------------------------------------
 # commitdiff testing (taken from t4114-apply-typechange.sh)
 
-test_expect_success SYMLINKS 'setup typechange commits' '
+test_expect_success 'setup typechange commits' '
        echo "hello world" > foo &&
        echo "hi planet" > bar &&
        git update-index --add foo bar &&
        git commit -m initial &&
        git branch initial &&
        rm -f foo &&
-       ln -s bar foo &&
-       git update-index foo &&
+       test_ln_s_add bar foo &&
        git commit -m "foo symlinked to bar" &&
        git branch foo-symlinked-to-bar &&
        rm -f foo &&
@@ -361,11 +360,7 @@ test_expect_success \
         echo "Changed" >> 04-rename-to &&
         test_chmod +x 05-mode-change &&
         rm -f 06-file-or-symlink &&
-        if test_have_prereq SYMLINKS; then
-               ln -s 01-change 06-file-or-symlink
-        else
-               printf %s 01-change > 06-file-or-symlink
-        fi &&
+        test_ln_s_add 01-change 06-file-or-symlink &&
         echo "Changed and have mode changed" > 07-change-mode-change   &&
         test_chmod +x 07-change-mode-change &&
         git commit -a -m "Large commit" &&
index eeefa6790454d83bfb4ce740f7b1843c6a71c79c..b0d1d94bf417a7c3cdd4f1b07334416aacbee18d 100755 (executable)
@@ -95,7 +95,7 @@ test_expect_success 'gitattributes setting eol=lf produces lf newlines' '
                git init &&
                echo "* eol=lf" >.gitattributes &&
                git p4 sync //depot@all &&
-               git checkout master &&
+               git checkout -b master p4/master &&
                test_cmp "$cli"/f-unix-orig f-unix &&
                test_cmp "$cli"/f-win-as-lf f-win
        )
@@ -109,7 +109,7 @@ test_expect_success 'gitattributes setting eol=crlf produces crlf newlines' '
                git init &&
                echo "* eol=crlf" >.gitattributes &&
                git p4 sync //depot@all &&
-               git checkout master &&
+               git checkout -b master p4/master &&
                test_cmp "$cli"/f-unix-as-crlf f-unix &&
                test_cmp "$cli"/f-win-orig f-win
        )
index 52510094add59b508e1581ffebfa555e7249561c..8828ff78f184a451fb43709771cc39bf17186cfb 100644 (file)
@@ -609,6 +609,18 @@ test_cmp() {
        $GIT_TEST_CMP "$@"
 }
 
+# Check if the file expected to be empty is indeed empty, and barfs
+# otherwise.
+
+test_must_be_empty () {
+       if test -s "$1"
+       then
+               echo "'$1' is not empty, it contains:"
+               cat "$1"
+               return 1
+       fi
+}
+
 # Tests that its two parameters refer to the same revision
 test_cmp_rev () {
        git rev-parse --verify "$1" >expect.rev &&
@@ -679,3 +691,20 @@ test_create_repo () {
                mv .git/hooks .git/hooks-disabled
        ) || exit
 }
+
+# This function helps on symlink challenged file systems when it is not
+# important that the file system entry is a symbolic link.
+# Use test_ln_s_add instead of "ln -s x y && git add y" to add a
+# symbolic link entry y to the index.
+
+test_ln_s_add () {
+       if test_have_prereq SYMLINKS
+       then
+               ln -s "$1" "$2" &&
+               git update-index --add "$2"
+       else
+               printf '%s' "$1" >"$2" &&
+               ln_s_obj=$(git hash-object -w "$2") &&
+               git update-index --add --cacheinfo 120000 $ln_s_obj "$2"
+       fi
+}
index 923bf327f8ac8243c59292879a60e1f7364b6070..0df748784bb7f6b6f286dd37ada293df17cffeee 100644 (file)
@@ -84,7 +84,7 @@ int main(int argc, char *argv[])
                if (stat(argv[i], &sb) < 0) {
                        fprintf(stderr, "Failed to stat %s: %s\n",
                                argv[i], strerror(errno));
-                       return -1;
+                       return 1;
                }
 
 #ifdef GIT_WINDOWS_NATIVE
@@ -92,7 +92,7 @@ int main(int argc, char *argv[])
                                chmod(argv[i], sb.st_mode | S_IWUSR)) {
                        fprintf(stderr, "Could not make user-writable %s: %s",
                                argv[i], strerror(errno));
-                       return -1;
+                       return 1;
                }
 #endif
 
@@ -107,7 +107,7 @@ int main(int argc, char *argv[])
                if (utb.modtime != sb.st_mtime && utime(argv[i], &utb) < 0) {
                        fprintf(stderr, "Failed to modify time on %s: %s\n",
                                argv[i], strerror(errno));
-                       return -1;
+                       return 1;
                }
        }
 
@@ -115,5 +115,5 @@ int main(int argc, char *argv[])
 
 usage:
        fprintf(stderr, "usage: %s %s\n", argv[0], usage_str);
-       return -1;
+       return 1;
 }
diff --git a/test-read-cache.c b/test-read-cache.c
new file mode 100644 (file)
index 0000000..b25bcf1
--- /dev/null
@@ -0,0 +1,13 @@
+#include "cache.h"
+
+int main (int argc, char **argv)
+{
+       int i, cnt = 1;
+       if (argc == 2)
+               cnt = strtol(argv[1], NULL, 0);
+       for (i = 0; i < cnt; i++) {
+               read_cache();
+               discard_cache();
+       }
+       return 0;
+}
index 06c08a1786391e7a04fa566af0cf42c659ed8aa9..db9bd182984f88f512cf7983ea734de4c415d21c 100644 (file)
@@ -56,7 +56,7 @@ static int recvline_fh(FILE *helper, struct strbuf *buffer, const char *name)
        if (strbuf_getline(buffer, helper, '\n') == EOF) {
                if (debug)
                        fprintf(stderr, "Debug: Remote helper quit.\n");
-               die("Reading from helper 'git-remote-%s' failed", name);
+               exit(128);
        }
 
        if (debug)
index 359a671c8ca9232944cd6e92a2ac0a9eda7b429d..e15db9808c85a3918a8de3c409500aaa78d1328e 100644 (file)
@@ -1232,7 +1232,7 @@ int transport_fetch_refs(struct transport *transport, struct ref *refs)
                 * then local and remote refs are likely to still be equal.
                 * Just feed them all to the fetch method in that case.
                 * This condition shouldn't be met in a non-deepening fetch
-                * (see builtin-fetch.c:quickfetch()).
+                * (see builtin/fetch.c:quickfetch()).
                 */
                heads = xmalloc(nr_refs * sizeof(*heads));
                for (rm = refs; rm; rm = rm->next)
index 4edebc535583f655cc120d169677ea221af554b3..ea70ea7e4a1b61c3006c614712ddd1aa41205ac6 100644 (file)
@@ -170,7 +170,7 @@ int transport_connect(struct transport *transport, const char *name,
 int transport_helper_init(struct transport *transport, const char *name);
 int bidirectional_transfer_loop(int input, int output);
 
-/* common methods used by transport.c and builtin-send-pack.c */
+/* common methods used by transport.c and builtin/send-pack.c */
 void transport_verify_remote_names(int nr_heads, const char **heads);
 
 void transport_update_tracking_ref(struct remote *remote, struct ref *ref, int verbose);
index 2bf0db9814a5c9f77fe00c97fa4f723986a7aac1..ae04b6417de0ff06391cc7e2d98fcd1b7d5aa633 100644 (file)
@@ -46,7 +46,7 @@ struct traverse_info {
        int pathlen;
        struct pathspec *pathspec;
 
-       unsigned long conflicts;
+       unsigned long df_conflicts;
        traverse_callback_t fn;
        void *data;
        int show_all_errors;
index 57b40743a1ae047de6837d70124298a49dc668e7..b27f2a62e8c462a95229486342323500de4d5ca0 100644 (file)
@@ -464,7 +464,7 @@ static int traverse_trees_recursive(int n, unsigned long dirmask,
        newinfo.pathspec = info->pathspec;
        newinfo.name = *p;
        newinfo.pathlen += tree_entry_len(p) + 1;
-       newinfo.conflicts |= df_conflicts;
+       newinfo.df_conflicts |= df_conflicts;
 
        for (i = 0; i < n; i++, dirmask >>= 1) {
                const unsigned char *sha1 = NULL;
@@ -565,17 +565,12 @@ static int unpack_nondirectories(int n, unsigned long mask,
 {
        int i;
        struct unpack_trees_options *o = info->data;
-       unsigned long conflicts;
+       unsigned long conflicts = info->df_conflicts | dirmask;
 
        /* Do we have *only* directories? Nothing to do */
        if (mask == dirmask && !src[0])
                return 0;
 
-       conflicts = info->conflicts;
-       if (o->merge)
-               conflicts >>= 1;
-       conflicts |= dirmask;
-
        /*
         * Ok, we've filled in up to any potential index entry in src[0],
         * now do the rest.
@@ -807,13 +802,6 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str
 
        /* Now handle any directories.. */
        if (dirmask) {
-               unsigned long conflicts = mask & ~dirmask;
-               if (o->merge) {
-                       conflicts <<= 1;
-                       if (src[0])
-                               conflicts |= 1;
-               }
-
                /* special case: "diff-index --cached" looking at a tree */
                if (o->diff_index_cached &&
                    n == 1 && dirmask == 1 && S_ISDIR(names->mode)) {
@@ -832,7 +820,7 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str
                        }
                }
 
-               if (traverse_trees_recursive(n, dirmask, conflicts,
+               if (traverse_trees_recursive(n, dirmask, mask & ~dirmask,
                                             names, info) < 0)
                        return -1;
                return mask;
index bf84a86ee3d6b136bd83115f0bf15a55835817fc..438a40d63739d9397829e83d7af241054f729235 100644 (file)
@@ -955,10 +955,12 @@ static void show_cherry_pick_in_progress(struct wt_status *s,
        if (advice_status_hints) {
                if (has_unmerged(s))
                        status_printf_ln(s, color,
-                               _("  (fix conflicts and run \"git commit\")"));
+                               _("  (fix conflicts and run \"git cherry-pick --continue\")"));
                else
                        status_printf_ln(s, color,
-                               _("  (all conflicts fixed: run \"git commit\")"));
+                               _("  (all conflicts fixed: run \"git cherry-pick --continue\")"));
+               status_printf_ln(s, color,
+                       _("  (use \"git cherry-pick --abort\" to cancel the cherry-pick operation)"));
        }
        wt_status_print_trailer(s);
 }