Merge branch 'kb/am-deprecate-resolved'
authorJunio C Hamano <gitster@pobox.com>
Mon, 1 Jul 2013 19:41:48 +0000 (12:41 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 1 Jul 2013 19:41:48 +0000 (12:41 -0700)
Promote "git am --continue" over "git am --resolved" for UI
consistency.

* kb/am-deprecate-resolved:
am: replace uses of --resolved with --continue

77 files changed:
.gitignore
.mailmap
Documentation/RelNotes/1.8.3.2.txt [new file with mode: 0644]
Documentation/RelNotes/1.8.3.3.txt [new file with mode: 0644]
Documentation/RelNotes/1.8.4.txt
Documentation/config.txt
Documentation/diff-options.txt
Documentation/git-check-ignore.txt
Documentation/git-clone.txt
Documentation/git-fast-export.txt
Documentation/git-name-rev.txt
Documentation/git-remote.txt
Documentation/git-rev-parse.txt
Documentation/git-stash.txt
Documentation/git.txt
Documentation/rev-list-options.txt
Documentation/technical/racy-git.txt
Documentation/urls.txt
Documentation/user-manual.txt
Makefile
builtin/checkout.c
builtin/clone.c
builtin/log.c
builtin/ls-files.c
builtin/name-rev.c
builtin/rev-parse.c
builtin/show-branch.c
cache.h
commit-slab.h [new file with mode: 0644]
commit.c
commit.h
configure.ac
contrib/completion/git-completion.bash
contrib/completion/git-prompt.sh
diff.c
git-add--interactive.perl
git-pull.sh
git-rebase.sh
git-send-email.perl
git-stash.sh
git-submodule.sh
http.c
prio-queue.c [new file with mode: 0644]
prio-queue.h [new file with mode: 0644]
read-cache.c
refs.c
refs.h
revision.c
revision.h
sequencer.c
t/lib-t6000.sh
t/perf/README
t/t0009-prio-queue.sh [new file with mode: 0755]
t/t1513-rev-parse-prefix.sh [new file with mode: 0755]
t/t3903-stash.sh
t/t4015-diff-whitespace.sh
t/t5520-pull.sh
t/t6002-rev-list-bisect.sh
t/t6003-rev-list-topo-order.sh
t/t7011-skip-worktree-reading.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/t9001-send-email.sh
t/t9802-git-p4-filetype.sh
t/t9902-completion.sh
t/t9903-bash-prompt.sh
test-prio-queue.c [new file with mode: 0644]
xdiff/xdiff.h
xdiff/xdiffi.c
xdiff/xdiffi.h
xdiff/xemit.c
xdiff/xemit.h
xdiff/xutils.c
xdiff/xutils.h
index c0e00eb37bb370ce9deee72468fb419bc25cfd62..efa8db00358772852e637af8758f8f76719dec01 100644 (file)
 /test-mktemp
 /test-parse-options
 /test-path-utils
+/test-prio-queue
 /test-read-cache
 /test-regex
 /test-revision-walking
index 48d7acf013c611d4885cddffdf516bb5d0ea5a86..345cce698a3bfe1575db0f439d2879d3bf9c05b2 100644 (file)
--- a/.mailmap
+++ b/.mailmap
@@ -24,6 +24,7 @@ Eric S. Raymond <esr@thyrsus.com>
 Erik Faye-Lund <kusmabite@gmail.com> <kusmabite@googlemail.com>
 Fredrik Kuivinen <freku045@student.liu.se>
 Frédéric Heitzmann <frederic.heitzmann@gmail.com>
+H. Merijn Brand <h.m.brand@xs4all.nl> H.Merijn Brand <h.m.brand@xs4all.nl>
 H. Peter Anvin <hpa@bonde.sc.orionmulti.com>
 H. Peter Anvin <hpa@tazenda.sc.orionmulti.com>
 H. Peter Anvin <hpa@trantor.hos.anvin.org>
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.
diff --git a/Documentation/RelNotes/1.8.3.3.txt b/Documentation/RelNotes/1.8.3.3.txt
new file mode 100644 (file)
index 0000000..58a570e
--- /dev/null
@@ -0,0 +1,11 @@
+Git v1.8.3.3 Release Notes
+==========================
+
+Fixes since v1.8.3.2
+--------------------
+
+ * Mac OS X does not like to write(2) more than INT_MAX number of
+   bytes; work it around by chopping write(2) into smaller pieces.
+
+ * Newer MacOS X encourages the programs to compile and link with
+   their CommonCrypto, not with OpenSSL.
index 10699fa2265143eab0e670613951a9a0ab15bacf..5f440b8a6b1ce0d7b0808bffcdc46d3fc9298fac 100644 (file)
@@ -35,6 +35,14 @@ Foreign interfaces, subsystems and ports.
 
 UI, Workflows & Features
 
+ * Various subcommands of "git submodule" refused to run from anywhere
+   other than the top of the working tree of the superproject, but
+   they have been taught to let you run from a subdirectory.
+
+ * "git diff" learned a mode that ignores hunks whose change consists
+   only of additions and removals of blank lines, which is the same as
+   "diff -B" (ignore blank lines) of GNU diff.
+
  * "git rm" gives a single message followed by list of paths to report
    multiple paths that cannot be removed.
 
@@ -108,6 +116,16 @@ UI, Workflows & Features
 
 Performance, Internal Implementation, etc.
 
+ * "git pack-refs" that races with new ref creation or deletion have
+   been susceptible to lossage of refs under right conditions, which
+   has been tightened up.
+
+ * 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.  The codepath has been updated to retry when such a
+   race is detected, instead of outright failing.
+
  * 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
@@ -157,11 +175,24 @@ 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 name-rev --refs=tags/v*" were forbidden, which was a bit
+   inconvenient (you had to give a pattern to match refs fully, like
+   --refs=refs/tags/v*).
+   (merge 98c5c4a nk/name-rev-abbreviated-refs later to maint).
+
  * "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).
@@ -173,7 +204,8 @@ details).
    "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).
+   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
index 1153585aa2e2d19dd41f8cf81465e8b6d858a028..b4d4887bd79d66635fdc7a7c3b5992aea30308b7 100644 (file)
@@ -934,6 +934,9 @@ column.ui::
        This variable consists of a list of tokens separated by spaces
        or commas:
 +
+These options control when the feature should be enabled
+(defaults to 'never'):
++
 --
 `always`;;
        always show in columns
@@ -941,19 +944,30 @@ column.ui::
        never show in columns
 `auto`;;
        show in columns if the output is to the terminal
+--
++
+These options control layout (defaults to 'column').  Setting any
+of these implies 'always' if none of 'always', 'never', or 'auto' are
+specified.
++
+--
 `column`;;
-       fill columns before rows (default)
+       fill columns before rows
 `row`;;
        fill rows before columns
 `plain`;;
        show in one column
+--
++
+Finally, these options can be combined with a layout option (defaults
+to 'nodense'):
++
+--
 `dense`;;
        make unequal size columns to utilize more space
 `nodense`;;
        make equal size columns
 --
-+
-This option defaults to 'never'.
 
 column.branch::
        Specify whether to output branch listing in `git branch` in columns.
index a85288f23ef13512042a783460c5dfcd347b83f1..19f78a7d5cca0c4a08681605d31a57158a869298 100644 (file)
@@ -461,6 +461,9 @@ endif::git-format-patch[]
        differences even if one line has whitespace where the other
        line has none.
 
+--ignore-blank-lines::
+       Ignore changes whose lines are all blank.
+
 --inter-hunk-context=<lines>::
        Show the context between diff hunks, up to the specified number
        of lines, thereby fusing hunks that are close to each other.
index 8e1f7ab7ea93af4b7c496efc2251469ed553a55f..d2df487aa20a89264afee08bcdddf681d7701fda 100644 (file)
@@ -102,7 +102,7 @@ SEE ALSO
 --------
 linkgit:gitignore[5]
 linkgit:gitconfig[5]
-linkgit:git-ls-files[5]
+linkgit:git-ls-files[1]
 
 GIT
 ---
index a0727d7759aefef4dede0fb40008b7b1ee957b2d..450f158779fbfda31c0b3f85d4768a294ef782ef 100644 (file)
@@ -239,8 +239,8 @@ Examples
 * Clone from upstream:
 +
 ------------
-$ git clone git://git.kernel.org/pub/scm/.../linux-2.6 my2.6
-$ cd my2.6
+$ git clone git://git.kernel.org/pub/scm/.../linux.git my-linux
+$ cd my-linux
 $ make
 ------------
 
@@ -257,10 +257,10 @@ $ git show-branch
 * Clone from upstream while borrowing from an existing local directory:
 +
 ------------
-$ git clone --reference my2.6 \
-       git://git.kernel.org/pub/scm/.../linux-2.7 \
-       my2.7
-$ cd my2.7
+$ git clone --reference /git/linux.git \
+       git://git.kernel.org/pub/scm/.../linux.git \
+       my-linux
+$ cd my-linux
 ------------
 
 
@@ -271,13 +271,6 @@ $ git clone --bare -l /home/proj/.git /pub/scm/proj.git
 ------------
 
 
-* Create a repository on the kernel.org machine that borrows from Linus:
-+
-------------
-$ git clone --bare -l -s /pub/scm/.../torvalds/linux-2.6.git \
-    /pub/scm/.../me/subsys-2.6.git
-------------
-
 GIT
 ---
 Part of the linkgit:git[1] suite
index efb03806f507789877219f87217841d6deb5cd26..85f1f30fdf5c0a741c3c575bfa393fc7d88d308f 100644 (file)
@@ -141,7 +141,7 @@ Limitations
 -----------
 
 Since 'git fast-import' cannot tag trees, you will not be
-able to export the linux-2.6.git repository completely, as it contains
+able to export the linux.git repository completely, as it contains
 a tag referencing a tree instead of a commit.
 
 GIT
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 581bb4c413fb70956430bb7359bbee3bbc1a24e6..9c3e3bf83a2721678a2c7a430ec88bee0d39dd0a 100644 (file)
@@ -187,18 +187,25 @@ Examples
 $ git remote
 origin
 $ git branch -r
-origin/master
-$ git remote add linux-nfs git://linux-nfs.org/pub/linux/nfs-2.6.git
+  origin/HEAD -> origin/master
+  origin/master
+$ git remote add staging git://git.kernel.org/.../gregkh/staging.git
 $ git remote
-linux-nfs
 origin
-$ git fetch
-* refs/remotes/linux-nfs/master: storing branch 'master' ...
-  commit: bf81b46
+staging
+$ git fetch staging
+...
+From git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/staging
+ * [new branch]      master     -> staging/master
+ * [new branch]      staging-linus -> staging/staging-linus
+ * [new branch]      staging-next -> staging/staging-next
 $ git branch -r
-origin/master
-linux-nfs/master
-$ git checkout -b nfs linux-nfs/master
+  origin/HEAD -> origin/master
+  origin/master
+  staging/master
+  staging/staging-linus
+  staging/staging-next
+$ git checkout -b staging staging/master
 ...
 ------------
 
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 894454609fab8f3773afff7e1e3fc4aadfb825f0..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].
 
index b462f17f62d4c0066b1178b405b011963643cdd9..e157ec3fe7e6bc72e271337f6cb0b4c1d791a2eb 100644 (file)
@@ -625,6 +625,10 @@ By default, the commits are shown in reverse chronological order.
        Show no parents before all of its children are shown, but
        otherwise show commits in the commit timestamp order.
 
+--author-date-order::
+       Show no parents before all of its children are shown, but
+       otherwise show commits in the author timestamp order.
+
 --topo-order::
        Show no parents before all of its children are shown, and
        avoid showing commits on multiple lines of history
index 6dc82ca5a8a8b744fad1aad6ee7578c3b02495d1..f716d6d97f1cbb66b36a1e06ec2c268abf0a5ed1 100644 (file)
@@ -135,9 +135,9 @@ them, and give the same timestamp to the index file:
   $ git ls-files | git update-index --stdin
   $ touch -r .datestamp .git/index
 
-This will make all index entries racily clean.  The linux-2.6
-project, for example, there are over 20,000 files in the working
-tree.  On my Athlon 64 X2 3800+, after the above:
+This will make all index entries racily clean.  The linux project, for
+example, there are over 20,000 files in the working tree.  On my
+Athlon 64 X2 3800+, after the above:
 
   $ /usr/bin/time git diff-files
   1.68user 0.54system 0:02.22elapsed 100%CPU (0avgtext+0avgdata 0maxresident)k
index 5e185f87c20a2124a7fb9bf2046f6035942b4f92..9ccb24677e95b8aff605c9020bc7a0537e23f751 100644 (file)
@@ -11,6 +11,9 @@ and ftps can be used for fetching and rsync can be used for fetching
 and pushing, but these are inefficient and deprecated; do not use
 them).
 
+The native transport (i.e. git:// URL) does no authentication and
+should be used with caution on unsecured networks.
+
 The following syntaxes may be used with them:
 
 - ssh://{startsb}user@{endsb}host.xz{startsb}:port{endsb}/path/to/repo.git/
index faa7251c11d8745b7a646a9bf6a13200fe598ddf..e364007d7c2e5f9b1aa5472c7968cb248eb8cf3f 100644 (file)
@@ -66,8 +66,8 @@ $ 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
 will only need to clone once.
 
-The clone command creates a new directory named after the project (`git`
-or `linux-2.6` in the examples above).  After you cd into this
+The clone command creates a new directory named after the project
+(`git` or `linux` in the examples above).  After you cd into this
 directory, you will see that it contains a copy of the project files,
 called the <<def_working_tree,working tree>>, together with a special
 top-level directory named `.git`, which contains all the information
@@ -431,19 +431,25 @@ You can also track branches from repositories other than the one you
 cloned from, using linkgit:git-remote[1]:
 
 -------------------------------------------------
-$ git remote add linux-nfs git://linux-nfs.org/pub/nfs-2.6.git
-$ git fetch linux-nfs
-* refs/remotes/linux-nfs/master: storing branch 'master' ...
-  commit: bf81b46
+$ git remote add staging git://git.kernel.org/.../gregkh/staging.git
+$ git fetch staging
+...
+From git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/staging
+ * [new branch]      master     -> staging/master
+ * [new branch]      staging-linus -> staging/staging-linus
+ * [new branch]      staging-next -> staging/staging-next
 -------------------------------------------------
 
 New remote-tracking branches will be stored under the shorthand name
-that you gave `git remote add`, in this case `linux-nfs`:
+that you gave `git remote add`, in this case `staging`:
 
 -------------------------------------------------
 $ git branch -r
-linux-nfs/master
-origin/master
+  origin/HEAD -> origin/master
+  origin/master
+  staging/master
+  staging/staging-linus
+  staging/staging-next
 -------------------------------------------------
 
 If you run `git fetch <remote>` later, the remote-tracking branches
@@ -455,9 +461,9 @@ a new stanza:
 -------------------------------------------------
 $ cat .git/config
 ...
-[remote "linux-nfs"]
-       url = git://linux-nfs.org/pub/nfs-2.6.git
-       fetch = +refs/heads/*:refs/remotes/linux-nfs/*
+[remote "staging"]
+       url = git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/staging.git
+       fetch = +refs/heads/*:refs/remotes/staging/*
 ...
 -------------------------------------------------
 
@@ -2156,7 +2162,7 @@ To set this up, first create your work tree by cloning Linus's public
 tree:
 
 -------------------------------------------------
-$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git work
+$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git work
 $ cd work
 -------------------------------------------------
 
@@ -2198,7 +2204,7 @@ make it easy to push both branches to your public tree.  (See
 -------------------------------------------------
 $ cat >> .git/config <<EOF
 [remote "mytree"]
-       url =  master.kernel.org:/pub/scm/linux/kernel/git/aegl/linux-2.6.git
+       url =  master.kernel.org:/pub/scm/linux/kernel/git/aegl/linux.git
        push = release
        push = test
 EOF
index e1583761dfa58c3ede93b2076e4aa0963f2aa379..5a68fe5431043d2b1b98493434c0c7989d36757c 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -569,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-prio-queue
 TEST_PROGRAMS_NEED_X += test-read-cache
 TEST_PROGRAMS_NEED_X += test-regex
 TEST_PROGRAMS_NEED_X += test-revision-walking
@@ -705,6 +706,7 @@ LIB_H += parse-options.h
 LIB_H += patch-ids.h
 LIB_H += pathspec.h
 LIB_H += pkt-line.h
+LIB_H += prio-queue.h
 LIB_H += progress.h
 LIB_H += prompt.h
 LIB_H += quote.h
@@ -846,6 +848,7 @@ LIB_OBJS += pathspec.o
 LIB_OBJS += pkt-line.o
 LIB_OBJS += preload-index.o
 LIB_OBJS += pretty.o
+LIB_OBJS += prio-queue.o
 LIB_OBJS += progress.o
 LIB_OBJS += prompt.o
 LIB_OBJS += quote.o
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 66bff5700f1e33c009c69fc44ba327dff1acb135..14b13235682add18daf625295d746ca5dea7ebcc 100644 (file)
@@ -493,13 +493,16 @@ static void write_remote_refs(const struct ref *local_refs)
 {
        const struct ref *r;
 
+       lock_packed_refs(LOCK_DIE_ON_ERROR);
+
        for (r = local_refs; r; r = r->next) {
                if (!r->peer_ref)
                        continue;
                add_packed_ref(r->peer_ref->name, r->old_sha1);
        }
 
-       pack_refs(PACK_REFS_ALL);
+       if (commit_packed_refs())
+               die_errno("unable to overwrite old ref-pack file");
 }
 
 static void write_followtags(const struct ref *refs, const char *msg)
index 9e2123295ffa80ecab83977099ed6a43fd4a93f9..e3222ed9f979e3140cbfc88ad2839c573c89bf06 100644 (file)
@@ -237,7 +237,7 @@ static void log_show_early(struct rev_info *revs, struct commit_list *list)
        int i = revs->early_output;
        int show_header = 1;
 
-       sort_in_topological_order(&list, revs->lifo);
+       sort_in_topological_order(&list, revs->sort_order);
        while (list && i) {
                struct commit *commit = list->item;
                switch (simplify_commit(revs, commit)) {
index 87f3b331ca68715f074b2a5396dda77eea3c3154..3a410c35d99ae030a75c4d3065b7023467e0d1c6 100644 (file)
@@ -165,11 +165,13 @@ static void show_ce_entry(const char *tag, struct cache_entry *ce)
        }
        write_name(ce->name, ce_namelen(ce));
        if (debug_mode) {
-               printf("  ctime: %d:%d\n", ce->ce_ctime.sec, ce->ce_ctime.nsec);
-               printf("  mtime: %d:%d\n", ce->ce_mtime.sec, ce->ce_mtime.nsec);
-               printf("  dev: %d\tino: %d\n", ce->ce_dev, ce->ce_ino);
-               printf("  uid: %d\tgid: %d\n", ce->ce_uid, ce->ce_gid);
-               printf("  size: %d\tflags: %x\n", ce->ce_size, ce->ce_flags);
+               struct stat_data *sd = &ce->ce_stat_data;
+
+               printf("  ctime: %d:%d\n", sd->sd_ctime.sec, sd->sd_ctime.nsec);
+               printf("  mtime: %d:%d\n", sd->sd_mtime.sec, sd->sd_mtime.nsec);
+               printf("  dev: %d\tino: %d\n", sd->sd_dev, sd->sd_ino);
+               printf("  uid: %d\tgid: %d\n", sd->sd_uid, sd->sd_gid);
+               printf("  size: %d\tflags: %x\n", sd->sd_size, ce->ce_flags);
        }
 }
 
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 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 90fc6b1b9d2a397ae43dfc4c0aba6bc192c9823c..99ec4af224464f5c6c82599dcc4ca18b6abaa7f7 100644 (file)
@@ -630,7 +630,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
        int num_rev, i, extra = 0;
        int all_heads = 0, all_remotes = 0;
        int all_mask, all_revs;
-       int lifo = 1;
+       enum rev_sort_order sort_order = REV_SORT_IN_GRAPH_ORDER;
        char head[128];
        const char *head_p;
        int head_len;
@@ -665,15 +665,17 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
                            N_("show possible merge bases")),
                OPT_BOOLEAN(0, "independent", &independent,
                            N_("show refs unreachable from any other ref")),
-               OPT_BOOLEAN(0, "topo-order", &lifo,
-                           N_("show commits in topological order")),
+               OPT_SET_INT(0, "topo-order", &sort_order,
+                           N_("show commits in topological order"),
+                           REV_SORT_IN_GRAPH_ORDER),
                OPT_BOOLEAN(0, "topics", &topics,
                            N_("show only commits not on the first branch")),
                OPT_SET_INT(0, "sparse", &dense,
                            N_("show merges reachable from only one tip"), 0),
-               OPT_SET_INT(0, "date-order", &lifo,
+               OPT_SET_INT(0, "date-order", &sort_order,
                            N_("show commits where no parent comes before its "
-                              "children"), 0),
+                              "children"),
+                           REV_SORT_BY_COMMIT_DATE),
                { OPTION_CALLBACK, 'g', "reflog", &reflog_base, N_("<n>[,<base>]"),
                            N_("show <n> most recent ref-log entries starting at "
                               "base"),
@@ -900,7 +902,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
                exit(0);
 
        /* Sort topologically */
-       sort_in_topological_order(&seen, lifo);
+       sort_in_topological_order(&seen, sort_order);
 
        /* Give names to commits */
        if (!sha1_name && !no_name)
diff --git a/cache.h b/cache.h
index ec8240f62a77cf718436d3de62f8ab7f66d4a322..dd0fb33a1520edf2a243555474d1875818294598 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -119,15 +119,19 @@ struct cache_time {
        unsigned int nsec;
 };
 
+struct stat_data {
+       struct cache_time sd_ctime;
+       struct cache_time sd_mtime;
+       unsigned int sd_dev;
+       unsigned int sd_ino;
+       unsigned int sd_uid;
+       unsigned int sd_gid;
+       unsigned int sd_size;
+};
+
 struct cache_entry {
-       struct cache_time ce_ctime;
-       struct cache_time ce_mtime;
-       unsigned int ce_dev;
-       unsigned int ce_ino;
+       struct stat_data ce_stat_data;
        unsigned int ce_mode;
-       unsigned int ce_uid;
-       unsigned int ce_gid;
-       unsigned int ce_size;
        unsigned int ce_flags;
        unsigned int ce_namelen;
        unsigned char sha1[20];
@@ -511,6 +515,21 @@ extern int limit_pathspec_to_literal(void);
 #define HASH_FORMAT_CHECK 2
 extern int index_fd(unsigned char *sha1, int fd, struct stat *st, enum object_type type, const char *path, unsigned flags);
 extern int index_path(unsigned char *sha1, const char *path, struct stat *st, unsigned flags);
+
+/*
+ * Record to sd the data from st that we use to check whether a file
+ * might have changed.
+ */
+extern void fill_stat_data(struct stat_data *sd, struct stat *st);
+
+/*
+ * Return 0 if st is consistent with a file not having been changed
+ * since sd was filled.  If there are differences, return a
+ * combination of MTIME_CHANGED, CTIME_CHANGED, OWNER_CHANGED,
+ * INODE_CHANGED, and DATA_CHANGED.
+ */
+extern int match_stat_data(const struct stat_data *sd, struct stat *st);
+
 extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st);
 
 #define REFRESH_REALLY         0x0001  /* ignore_valid */
@@ -1338,4 +1357,31 @@ int checkout_fast_forward(const unsigned char *from,
 
 int sane_execvp(const char *file, char *const argv[]);
 
+/*
+ * A struct to encapsulate the concept of whether a file has changed
+ * since we last checked it. This uses criteria similar to those used
+ * for the index.
+ */
+struct stat_validity {
+       struct stat_data *sd;
+};
+
+void stat_validity_clear(struct stat_validity *sv);
+
+/*
+ * Returns 1 if the path is a regular file (or a symlink to a regular
+ * file) and matches the saved stat_validity, 0 otherwise.  A missing
+ * or inaccessible file is considered a match if the struct was just
+ * initialized, or if the previous update found an inaccessible file.
+ */
+int stat_validity_check(struct stat_validity *sv, const char *path);
+
+/*
+ * Update the stat_validity from a file opened at descriptor fd. If
+ * the file is missing, inaccessible, or not a regular file, then
+ * future calls to stat_validity_check will match iff one of those
+ * conditions continues to be true.
+ */
+void stat_validity_update(struct stat_validity *sv, int fd);
+
 #endif /* CACHE_H */
diff --git a/commit-slab.h b/commit-slab.h
new file mode 100644 (file)
index 0000000..7d48163
--- /dev/null
@@ -0,0 +1,98 @@
+#ifndef COMMIT_SLAB_H
+#define COMMIT_SLAB_H
+
+/*
+ * define_commit_slab(slabname, elemtype) creates boilerplate code to define
+ * a new struct (struct slabname) that is used to associate a piece of data
+ * of elemtype to commits, and a few functions to use that struct.
+ *
+ * After including this header file, using:
+ *
+ * define_commit_slab(indegee, int);
+ *
+ * will let you call the following functions:
+ *
+ * - int *indegree_at(struct indegree *, struct commit *);
+ *
+ *   This function locates the data associated with the given commit in
+ *   the indegree slab, and returns the pointer to it.
+ *
+ * - void init_indegree(struct indegree *);
+ *   void init_indegree_with_stride(struct indegree *, int);
+ *
+ *   Initializes the indegree slab that associates an array of integers
+ *   to each commit. 'stride' specifies how big each array is.  The slab
+ *   that id initialied by the variant without "_with_stride" associates
+ *   each commit with an array of one integer.
+ */
+
+/* allocate ~512kB at once, allowing for malloc overhead */
+#ifndef COMMIT_SLAB_SIZE
+#define COMMIT_SLAB_SIZE (512*1024-32)
+#endif
+
+#define define_commit_slab(slabname, elemtype)                                 \
+                                                                       \
+struct slabname {                                                      \
+       unsigned slab_size;                                             \
+       unsigned stride;                                                \
+       unsigned slab_count;                                            \
+       elemtype **slab;                                                \
+};                                                                     \
+static int stat_ ##slabname## realloc;                                 \
+                                                                       \
+static void init_ ##slabname## _with_stride(struct slabname *s,                \
+                                           unsigned stride)            \
+{                                                                      \
+       unsigned int elem_size;                                         \
+       if (!stride)                                                    \
+               stride = 1;                                             \
+       s->stride = stride;                                             \
+       elem_size = sizeof(struct slabname) * stride;                   \
+       s->slab_size = COMMIT_SLAB_SIZE / elem_size;                    \
+       s->slab_count = 0;                                              \
+       s->slab = NULL;                                                 \
+}                                                                      \
+                                                                       \
+static void init_ ##slabname(struct slabname *s)                       \
+{                                                                      \
+       init_ ##slabname## _with_stride(s, 1);                          \
+}                                                                      \
+                                                                       \
+static void clear_ ##slabname(struct slabname *s)                      \
+{                                                                      \
+       int i;                                                          \
+       for (i = 0; i < s->slab_count; i++)                             \
+               free(s->slab[i]);                                       \
+       s->slab_count = 0;                                              \
+       free(s->slab);                                                  \
+       s->slab = NULL;                                                 \
+}                                                                      \
+                                                                       \
+static elemtype *slabname## _at(struct slabname *s,                    \
+                               const struct commit *c)                 \
+{                                                                      \
+       int nth_slab, nth_slot, ix;                                     \
+                                                                       \
+       ix = c->index * s->stride;                                      \
+       nth_slab = ix / s->slab_size;                                   \
+       nth_slot = ix % s->slab_size;                                   \
+                                                                       \
+       if (s->slab_count <= nth_slab) {                                \
+               int i;                                                  \
+               s->slab = xrealloc(s->slab,                             \
+                                  (nth_slab + 1) * sizeof(s->slab));   \
+               stat_ ##slabname## realloc++;                           \
+               for (i = s->slab_count; i <= nth_slab; i++)             \
+                       s->slab[i] = NULL;                              \
+               s->slab_count = nth_slab + 1;                           \
+       }                                                               \
+       if (!s->slab[nth_slab])                                         \
+               s->slab[nth_slab] = xcalloc(s->slab_size,               \
+                                           sizeof(**s->slab));         \
+       return &s->slab[nth_slab][nth_slot];                            \
+}                                                                      \
+                                                                       \
+static int stat_ ##slabname## realloc
+
+#endif /* COMMIT_SLAB_H */
index 888e02ae2f65ab566555465e5d88d02bbe52420f..521e49c3094acc06340e15f7be27722be9b03ee8 100644 (file)
--- a/commit.c
+++ b/commit.c
@@ -8,12 +8,15 @@
 #include "notes.h"
 #include "gpg-interface.h"
 #include "mergesort.h"
+#include "commit-slab.h"
+#include "prio-queue.h"
 
 static struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len, const char **);
 
 int save_commit_buffer = 1;
 
 const char *commit_type = "commit";
+static int commit_count;
 
 static struct commit *check_commit(struct object *obj,
                                   const unsigned char *sha1,
@@ -58,8 +61,11 @@ struct commit *lookup_commit_or_die(const unsigned char *sha1, const char *ref_n
 struct commit *lookup_commit(const unsigned char *sha1)
 {
        struct object *obj = lookup_object(sha1);
-       if (!obj)
-               return create_object(sha1, OBJ_COMMIT, alloc_commit_node());
+       if (!obj) {
+               struct commit *c = alloc_commit_node();
+               c->index = commit_count++;
+               return create_object(sha1, OBJ_COMMIT, c);
+       }
        if (!obj->type)
                obj->type = OBJ_COMMIT;
        return check_commit(obj, sha1, 0);
@@ -506,33 +512,137 @@ struct commit *pop_commit(struct commit_list **stack)
        return item;
 }
 
+/*
+ * Topological sort support
+ */
+
+/* count number of children that have not been emitted */
+define_commit_slab(indegree_slab, int);
+
+/* record author-date for each commit object */
+define_commit_slab(author_date_slab, unsigned long);
+
+static void record_author_date(struct author_date_slab *author_date,
+                              struct commit *commit)
+{
+       const char *buf, *line_end;
+       char *buffer = NULL;
+       struct ident_split ident;
+       char *date_end;
+       unsigned long date;
+
+       if (!commit->buffer) {
+               unsigned long size;
+               enum object_type type;
+               buffer = read_sha1_file(commit->object.sha1, &type, &size);
+               if (!buffer)
+                       return;
+       }
+
+       for (buf = commit->buffer ? commit->buffer : buffer;
+            buf;
+            buf = line_end + 1) {
+               line_end = strchrnul(buf, '\n');
+               if (prefixcmp(buf, "author ")) {
+                       if (!line_end[0] || line_end[1] == '\n')
+                               return; /* end of header */
+                       continue;
+               }
+               if (split_ident_line(&ident,
+                                    buf + strlen("author "),
+                                    line_end - (buf + strlen("author "))) ||
+                   !ident.date_begin || !ident.date_end)
+                       goto fail_exit; /* malformed "author" line */
+               break;
+       }
+
+       date = strtoul(ident.date_begin, &date_end, 10);
+       if (date_end != ident.date_end)
+               goto fail_exit; /* malformed date */
+       *(author_date_slab_at(author_date, commit)) = date;
+
+fail_exit:
+       free(buffer);
+}
+
+static int compare_commits_by_author_date(const void *a_, const void *b_,
+                                         void *cb_data)
+{
+       const struct commit *a = a_, *b = b_;
+       struct author_date_slab *author_date = cb_data;
+       unsigned long a_date = *(author_date_slab_at(author_date, a));
+       unsigned long b_date = *(author_date_slab_at(author_date, b));
+
+       /* newer commits with larger date first */
+       if (a_date < b_date)
+               return 1;
+       else if (a_date > b_date)
+               return -1;
+       return 0;
+}
+
+static int compare_commits_by_commit_date(const void *a_, const void *b_, void *unused)
+{
+       const struct commit *a = a_, *b = b_;
+       /* newer commits with larger date first */
+       if (a->date < b->date)
+               return 1;
+       else if (a->date > b->date)
+               return -1;
+       return 0;
+}
+
 /*
  * Performs an in-place topological sort on the list supplied.
  */
-void sort_in_topological_order(struct commit_list ** list, int lifo)
+void sort_in_topological_order(struct commit_list **list, enum rev_sort_order sort_order)
 {
        struct commit_list *next, *orig = *list;
-       struct commit_list *work, **insert;
        struct commit_list **pptr;
+       struct indegree_slab indegree;
+       struct prio_queue queue;
+       struct commit *commit;
+       struct author_date_slab author_date;
 
        if (!orig)
                return;
        *list = NULL;
 
+       init_indegree_slab(&indegree);
+       memset(&queue, '\0', sizeof(queue));
+
+       switch (sort_order) {
+       default: /* REV_SORT_IN_GRAPH_ORDER */
+               queue.compare = NULL;
+               break;
+       case REV_SORT_BY_COMMIT_DATE:
+               queue.compare = compare_commits_by_commit_date;
+               break;
+       case REV_SORT_BY_AUTHOR_DATE:
+               init_author_date_slab(&author_date);
+               queue.compare = compare_commits_by_author_date;
+               queue.cb_data = &author_date;
+               break;
+       }
+
        /* Mark them and clear the indegree */
        for (next = orig; next; next = next->next) {
                struct commit *commit = next->item;
-               commit->indegree = 1;
+               *(indegree_slab_at(&indegree, commit)) = 1;
+               /* also record the author dates, if needed */
+               if (sort_order == REV_SORT_BY_AUTHOR_DATE)
+                       record_author_date(&author_date, commit);
        }
 
        /* update the indegree */
        for (next = orig; next; next = next->next) {
-               struct commit_list * parents = next->item->parents;
+               struct commit_list *parents = next->item->parents;
                while (parents) {
                        struct commit *parent = parents->item;
+                       int *pi = indegree_slab_at(&indegree, parent);
 
-                       if (parent->indegree)
-                               parent->indegree++;
+                       if (*pi)
+                               (*pi)++;
                        parents = parents->next;
                }
        }
@@ -544,34 +654,33 @@ void sort_in_topological_order(struct commit_list ** list, int lifo)
         *
         * the tips serve as a starting set for the work queue.
         */
-       work = NULL;
-       insert = &work;
        for (next = orig; next; next = next->next) {
                struct commit *commit = next->item;
 
-               if (commit->indegree == 1)
-                       insert = &commit_list_insert(commit, insert)->next;
+               if (*(indegree_slab_at(&indegree, commit)) == 1)
+                       prio_queue_put(&queue, commit);
        }
 
-       /* process the list in topological order */
-       if (!lifo)
-               commit_list_sort_by_date(&work);
+       /*
+        * This is unfortunate; the initial tips need to be shown
+        * in the order given from the revision traversal machinery.
+        */
+       if (sort_order == REV_SORT_IN_GRAPH_ORDER)
+               prio_queue_reverse(&queue);
+
+       /* We no longer need the commit list */
+       free_commit_list(orig);
 
        pptr = list;
        *list = NULL;
-       while (work) {
-               struct commit *commit;
-               struct commit_list *parents, *work_item;
-
-               work_item = work;
-               work = work_item->next;
-               work_item->next = NULL;
+       while ((commit = prio_queue_get(&queue)) != NULL) {
+               struct commit_list *parents;
 
-               commit = work_item->item;
                for (parents = commit->parents; parents ; parents = parents->next) {
                        struct commit *parent = parents->item;
+                       int *pi = indegree_slab_at(&indegree, parent);
 
-                       if (!parent->indegree)
+                       if (!*pi)
                                continue;
 
                        /*
@@ -579,21 +688,22 @@ void sort_in_topological_order(struct commit_list ** list, int lifo)
                         * when all their children have been emitted thereby
                         * guaranteeing topological order.
                         */
-                       if (--parent->indegree == 1) {
-                               if (!lifo)
-                                       commit_list_insert_by_date(parent, &work);
-                               else
-                                       commit_list_insert(parent, &work);
-                       }
+                       if (--(*pi) == 1)
+                               prio_queue_put(&queue, parent);
                }
                /*
-                * work_item is a commit all of whose children
-                * have already been emitted. we can emit it now.
+                * all children of commit have already been
+                * emitted. we can emit it now.
                 */
-               commit->indegree = 0;
-               *pptr = work_item;
-               pptr = &work_item->next;
+               *(indegree_slab_at(&indegree, commit)) = 0;
+
+               pptr = &commit_list_insert(commit, pptr)->next;
        }
+
+       clear_indegree_slab(&indegree);
+       clear_prio_queue(&queue);
+       if (sort_order == REV_SORT_BY_AUTHOR_DATE)
+               clear_author_date_slab(&author_date);
 }
 
 /* merge-base stuff */
index 6e9c7cd9d5da7d24d4810b36039681f184325932..4d452dc96db3d5590878ab6f257022fe76b4130b 100644 (file)
--- a/commit.h
+++ b/commit.h
@@ -15,7 +15,7 @@ struct commit_list {
 struct commit {
        struct object object;
        void *util;
-       unsigned int indegree;
+       unsigned int index;
        unsigned long date;
        struct commit_list *parents;
        struct tree *tree;
@@ -142,15 +142,24 @@ void clear_commit_marks(struct commit *commit, unsigned int mark);
 void clear_commit_marks_many(int nr, struct commit **commit, unsigned int mark);
 void clear_commit_marks_for_object_array(struct object_array *a, unsigned mark);
 
+
+enum rev_sort_order {
+       REV_SORT_IN_GRAPH_ORDER = 0,
+       REV_SORT_BY_COMMIT_DATE,
+       REV_SORT_BY_AUTHOR_DATE
+};
+
 /*
  * Performs an in-place topological sort of list supplied.
  *
  *   invariant of resulting list is:
  *      a reachable from b => ord(b) < ord(a)
- *   in addition, when lifo == 0, commits on parallel tracks are
- *   sorted in the dates order.
+ *   sort_order further specifies:
+ *   REV_SORT_IN_GRAPH_ORDER: try to show a commit on a single-parent
+ *                            chain together.
+ *   REV_SORT_BY_COMMIT_DATE: show eligible commits in committer-date order.
  */
-void sort_in_topological_order(struct commit_list ** list, int lifo);
+void sort_in_topological_order(struct commit_list **, enum rev_sort_order);
 
 struct commit_graft {
        unsigned char sha1[20];
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])
index 6c3bafeea569601c4b55de165eb7f56ac3be1d31..ebc40d4845586bcdd7df8f02c34db9b157cfe149 100644 (file)
@@ -33,8 +33,6 @@ esac
 # returns location of .git repo
 __gitdir ()
 {
-       # Note: this function is duplicated in git-prompt.sh
-       # When updating it, make sure you update the other one to match.
        if [ -z "${1-}" ]; then
                if [ -n "${__git_dir-}" ]; then
                        echo "$__git_dir"
index 07a6218d10679f5102c3649794bfd98e1883da75..daed6a1d039cc8b2f5adb1d089e36ea7011e0536 100644 (file)
 #        Bash: PS1='[\u@\h \W$(__git_ps1 " (%s)")]\$ '
 #        ZSH:  PS1='[%n@%m %c$(__git_ps1 " (%s)")]\$ '
 #        the optional argument will be used as format string.
-#    3b) Alternatively, if you are using bash, __git_ps1 can be
-#        used for PROMPT_COMMAND with two parameters, <pre> and
-#        <post>, which are strings you would put in $PS1 before
-#        and after the status string generated by the git-prompt
-#        machinery.  e.g.
+#    3b) Alternatively, for a slighly faster prompt, if you are
+#        using bash, __git_ps1 can be used for PROMPT_COMMAND
+#        with two parameters, <pre> and <post>, which are strings
+#        you would put in $PS1 before and after the status string
+#        generated by the git-prompt machinery.  e.g.
 #        Bash: PROMPT_COMMAND='__git_ps1 "\u@\h:\w" "\\\$ "'
 #        ZSH:  precmd () { __git_ps1 "%n" ":%~$ " "|%s" }
 #        will show username, at-sign, host, colon, cwd, then
 # GIT_PS1_SHOWCOLORHINTS to a nonempty value. The colors are based on
 # the colored output of "git status -sb".
 
-# __gitdir accepts 0 or 1 arguments (i.e., location)
-# returns location of .git repo
-__gitdir ()
-{
-       # Note: this function is duplicated in git-completion.bash
-       # When updating it, make sure you update the other one to match.
-       if [ -z "${1-}" ]; then
-               if [ -n "${__git_dir-}" ]; then
-                       echo "$__git_dir"
-               elif [ -n "${GIT_DIR-}" ]; then
-                       test -d "${GIT_DIR-}" || return 1
-                       echo "$GIT_DIR"
-               elif [ -d .git ]; then
-                       echo .git
-               else
-                       git rev-parse --git-dir 2>/dev/null
-               fi
-       elif [ -d "$1/.git" ]; then
-               echo "$1/.git"
-       else
-               echo "$1"
-       fi
-}
-
 # stores the divergence from upstream in $p
 # used by GIT_PS1_SHOWUPSTREAM
 __git_ps1_show_upstream ()
@@ -335,50 +311,83 @@ __git_ps1 ()
                ;;
        esac
 
-       local g="$(__gitdir)"
-       if [ -z "$g" ]; then
+       local repo_info rev_parse_exit_code
+       repo_info="$(git rev-parse --git-dir --is-inside-git-dir \
+               --is-bare-repository --is-inside-work-tree \
+               --short HEAD 2>/dev/null)"
+       rev_parse_exit_code="$?"
+
+       if [ -z "$repo_info" ]; then
                if [ $pcmode = yes ]; then
                        #In PC mode PS1 always needs to be set
                        PS1="$ps1pc_start$ps1pc_end"
                fi
+               return
+       fi
+
+       local short_sha
+       if [ "$rev_parse_exit_code" = "0" ]; then
+               short_sha="${repo_info##*$'\n'}"
+               repo_info="${repo_info%$'\n'*}"
+       fi
+       local inside_worktree="${repo_info##*$'\n'}"
+       repo_info="${repo_info%$'\n'*}"
+       local bare_repo="${repo_info##*$'\n'}"
+       repo_info="${repo_info%$'\n'*}"
+       local inside_gitdir="${repo_info##*$'\n'}"
+       local g="${repo_info%$'\n'*}"
+
+       local r=""
+       local b=""
+       local step=""
+       local total=""
+       if [ -d "$g/rebase-merge" ]; then
+               read b 2>/dev/null <"$g/rebase-merge/head-name"
+               read step 2>/dev/null <"$g/rebase-merge/msgnum"
+               read total 2>/dev/null <"$g/rebase-merge/end"
+               if [ -f "$g/rebase-merge/interactive" ]; then
+                       r="|REBASE-i"
+               else
+                       r="|REBASE-m"
+               fi
        else
-               local r=""
-               local b=""
-               local step=""
-               local total=""
-               if [ -d "$g/rebase-merge" ]; then
-                       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"
+               if [ -d "$g/rebase-apply" ]; then
+                       read step 2>/dev/null <"$g/rebase-apply/next"
+                       read total 2>/dev/null <"$g/rebase-apply/last"
+                       if [ -f "$g/rebase-apply/rebasing" ]; then
+                               read b 2>/dev/null <"$g/rebase-apply/head-name"
+                               r="|REBASE"
+                       elif [ -f "$g/rebase-apply/applying" ]; then
+                               r="|AM"
                        else
-                               r="|REBASE-m"
+                               r="|AM/REBASE"
                        fi
+               elif [ -f "$g/MERGE_HEAD" ]; then
+                       r="|MERGING"
+               elif [ -f "$g/CHERRY_PICK_HEAD" ]; then
+                       r="|CHERRY-PICKING"
+               elif [ -f "$g/REVERT_HEAD" ]; then
+                       r="|REVERTING"
+               elif [ -f "$g/BISECT_LOG" ]; then
+                       r="|BISECTING"
+               fi
+
+               if [ -n "$b" ]; then
+                       :
+               elif [ -h "$g/HEAD" ]; then
+                       # symlink symbolic ref
+                       b="$(git symbolic-ref HEAD 2>/dev/null)"
                else
-                       if [ -d "$g/rebase-apply" ]; then
-                               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" 2>/dev/null)"
-                                       r="|REBASE"
-                               elif [ -f "$g/rebase-apply/applying" ]; then
-                                       r="|AM"
-                               else
-                                       r="|AM/REBASE"
+                       local head=""
+                       if ! read head 2>/dev/null <"$g/HEAD"; then
+                               if [ $pcmode = yes ]; then
+                                       PS1="$ps1pc_start$ps1pc_end"
                                fi
-                       elif [ -f "$g/MERGE_HEAD" ]; then
-                               r="|MERGING"
-                       elif [ -f "$g/CHERRY_PICK_HEAD" ]; then
-                               r="|CHERRY-PICKING"
-                       elif [ -f "$g/REVERT_HEAD" ]; then
-                               r="|REVERTING"
-                       elif [ -f "$g/BISECT_LOG" ]; then
-                               r="|BISECTING"
+                               return
                        fi
-
-                       test -n "$b" ||
-                       b="$(git symbolic-ref HEAD 2>/dev/null)" || {
+                       # is it a symbolic ref?
+                       b="${head#ref: }"
+                       if [ "$head" = "$b" ]; then
                                detached=yes
                                b="$(
                                case "${GIT_PS1_DESCRIBE_STYLE-}" in
@@ -392,70 +401,74 @@ __git_ps1 ()
                                        git describe --tags --exact-match HEAD ;;
                                esac 2>/dev/null)" ||
 
-                               b="$(cut -c1-7 "$g/HEAD" 2>/dev/null)..." ||
-                               b="unknown"
+                               b="$short_sha..."
                                b="($b)"
-                       }
+                       fi
                fi
+       fi
 
-               if [ -n "$step" ] && [ -n "$total" ]; then
-                       r="$r $step/$total"
-               fi
+       if [ -n "$step" ] && [ -n "$total" ]; then
+               r="$r $step/$total"
+       fi
 
-               local w=""
-               local i=""
-               local s=""
-               local u=""
-               local c=""
-               local p=""
+       local w=""
+       local i=""
+       local s=""
+       local u=""
+       local c=""
+       local p=""
 
-               if [ "true" = "$(git rev-parse --is-inside-git-dir 2>/dev/null)" ]; then
-                       if [ "true" = "$(git rev-parse --is-bare-repository 2>/dev/null)" ]; then
-                               c="BARE:"
+       if [ "true" = "$inside_gitdir" ]; then
+               if [ "true" = "$bare_repo" ]; then
+                       c="BARE:"
+               else
+                       b="GIT_DIR!"
+               fi
+       elif [ "true" = "$inside_worktree" ]; then
+               if [ -n "${GIT_PS1_SHOWDIRTYSTATE-}" ] &&
+                  [ "$(git config --bool bash.showDirtyState)" != "false" ]
+               then
+                       git diff --no-ext-diff --quiet --exit-code || w="*"
+                       if [ -n "$short_sha" ]; then
+                               git diff-index --cached --quiet HEAD -- || i="+"
                        else
-                               b="GIT_DIR!"
-                       fi
-               elif [ "true" = "$(git rev-parse --is-inside-work-tree 2>/dev/null)" ]; then
-                       if [ -n "${GIT_PS1_SHOWDIRTYSTATE-}" ] &&
-                          [ "$(git config --bool bash.showDirtyState)" != "false" ]
-                       then
-                               git diff --no-ext-diff --quiet --exit-code || w="*"
-                               if git rev-parse --quiet --verify HEAD >/dev/null; then
-                                       git diff-index --cached --quiet HEAD -- || i="+"
-                               else
-                                       i="#"
-                               fi
-                       fi
-                       if [ -n "${GIT_PS1_SHOWSTASHSTATE-}" ]; then
-                               git rev-parse --verify refs/stash >/dev/null 2>&1 && s="$"
+                               i="#"
                        fi
+               fi
+               if [ -n "${GIT_PS1_SHOWSTASHSTATE-}" ] &&
+                  [ -r "$g/refs/stash" ]; then
+                       s="$"
+               fi
 
-                       if [ -n "${GIT_PS1_SHOWUNTRACKEDFILES-}" ] &&
-                          [ "$(git config --bool bash.showUntrackedFiles)" != "false" ] &&
-                          [ -n "$(git ls-files --others --exclude-standard)" ]
-                       then
-                               u="%${ZSH_VERSION+%}"
-                       fi
+               if [ -n "${GIT_PS1_SHOWUNTRACKEDFILES-}" ] &&
+                  [ "$(git config --bool bash.showUntrackedFiles)" != "false" ] &&
+                  git ls-files --others --exclude-standard --error-unmatch -- '*' >/dev/null 2>/dev/null
+               then
+                       u="%${ZSH_VERSION+%}"
+               fi
 
-                       if [ -n "${GIT_PS1_SHOWUPSTREAM-}" ]; then
-                               __git_ps1_show_upstream
-                       fi
+               if [ -n "${GIT_PS1_SHOWUPSTREAM-}" ]; then
+                       __git_ps1_show_upstream
                fi
+       fi
 
-               local z="${GIT_PS1_STATESEPARATOR-" "}"
-               local f="$w$i$s$u"
-               if [ $pcmode = yes ]; then
-                       local gitstring=
-                       if [ -n "${GIT_PS1_SHOWCOLORHINTS-}" ]; then
-                               __git_ps1_colorize_gitstring
-                       else
-                               gitstring="$c${b##refs/heads/}${f:+$z$f}$r$p"
-                       fi
+       local z="${GIT_PS1_STATESEPARATOR-" "}"
+       local f="$w$i$s$u"
+       if [ $pcmode = yes ]; then
+               local gitstring=
+               if [ -n "${GIT_PS1_SHOWCOLORHINTS-}" ]; then
+                       __git_ps1_colorize_gitstring
+               else
+                       gitstring="$c${b##refs/heads/}${f:+$z$f}$r$p"
+               fi
+               if [[ -n ${ZSH_VERSION-} ]]; then
                        gitstring=$(printf -- "$printf_format" "$gitstring")
-                       PS1="$ps1pc_start$gitstring$ps1pc_end"
                else
-                       # NO color option unless in PROMPT_COMMAND mode
-                       printf -- "$printf_format" "$c${b##refs/heads/}${f:+$z$f}$r$p"
+                       printf -v gitstring -- "$printf_format" "$gitstring"
                fi
+               PS1="$ps1pc_start$gitstring$ps1pc_end"
+       else
+               # NO color option unless in PROMPT_COMMAND mode
+               printf -- "$printf_format" "$c${b##refs/heads/}${f:+$z$f}$r$p"
        fi
 }
diff --git a/diff.c b/diff.c
index f0b3e7cfe34a99c0e5fea85fcd00ec54e9b578d8..208094f6b7d8b6e18459fc1cd57ac8554723d8d4 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -3593,6 +3593,8 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
                DIFF_XDL_SET(options, IGNORE_WHITESPACE_CHANGE);
        else if (!strcmp(arg, "--ignore-space-at-eol"))
                DIFF_XDL_SET(options, IGNORE_WHITESPACE_AT_EOL);
+       else if (!strcmp(arg, "--ignore-blank-lines"))
+               DIFF_XDL_SET(options, IGNORE_BLANK_LINES);
        else if (!strcmp(arg, "--patience"))
                options->xdl_opts = DIFF_WITH_ALG(options, PATIENCE_DIFF);
        else if (!strcmp(arg, "--histogram"))
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 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 54015e3eaf5e091a387841d9a6e7cdd3085174f8..81b0346a5da2386c94f72ac777f11887fef7fa70 100755 (executable)
@@ -155,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.
index 671762b93031e66cba2ca837ddf71ed765f7a204..ecbf56f693f7049fd87267ec1830f9020e60e720 100755 (executable)
@@ -1259,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;
@@ -1297,7 +1298,7 @@ sub send_message {
                        }
                        elsif (/^From:\s+(.*)$/i) {
                                ($author, $author_encoding) = unquote_rfc2047($1);
-                               my $sauthor = sanitize_address($author);
+                               $sauthor = sanitize_address($author);
                                next if $suppress_cc{'author'};
                                next if $suppress_cc{'self'} and $sauthor eq $sender;
                                printf("(mbox) Adding cc: %s from line '%s'\n",
@@ -1393,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) {
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 eb58c8e89d28626fed56604533ffb4b87991a6af..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,12 +109,48 @@ 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 -z --error-unmatch --stage -- "$@" ||
                echo "unmatched pathspec exists"
@@ -283,6 +322,7 @@ isnumber()
 cmd_add()
 {
        # parse $args after "submodule ... add".
+       reference_path=
        while test $# -ne 0
        do
                case "$1" in
@@ -299,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
@@ -324,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
 
@@ -336,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
                ;;
@@ -472,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
 }
@@ -525,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
@@ -539,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
@@ -549,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
 }
 
@@ -595,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\.")"
@@ -624,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
 }
@@ -718,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
 
@@ -729,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
@@ -742,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"
@@ -775,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?
@@ -789,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
 
@@ -829,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"
@@ -943,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' |
@@ -992,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=
@@ -1030,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
@@ -1119,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"
@@ -1214,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
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
diff --git a/prio-queue.c b/prio-queue.c
new file mode 100644 (file)
index 0000000..c9f8c6d
--- /dev/null
@@ -0,0 +1,84 @@
+#include "cache.h"
+#include "commit.h"
+#include "prio-queue.h"
+
+void prio_queue_reverse(struct prio_queue *queue)
+{
+       int i, j;
+
+       if (queue->compare != NULL)
+               die("BUG: prio_queue_reverse() on non-LIFO queue");
+       for (i = 0; i <= (j = (queue->nr - 1) - i); i++) {
+               struct commit *swap = queue->array[i];
+               queue->array[i] = queue->array[j];
+               queue->array[j] = swap;
+       }
+}
+
+void clear_prio_queue(struct prio_queue *queue)
+{
+       free(queue->array);
+       queue->nr = 0;
+       queue->alloc = 0;
+       queue->array = NULL;
+}
+
+void prio_queue_put(struct prio_queue *queue, void *thing)
+{
+       prio_queue_compare_fn compare = queue->compare;
+       int ix, parent;
+
+       /* Append at the end */
+       ALLOC_GROW(queue->array, queue->nr + 1, queue->alloc);
+       queue->array[queue->nr++] = thing;
+       if (!compare)
+               return; /* LIFO */
+
+       /* Bubble up the new one */
+       for (ix = queue->nr - 1; ix; ix = parent) {
+               parent = (ix - 1) / 2;
+               if (compare(queue->array[parent], queue->array[ix],
+                           queue->cb_data) <= 0)
+                       break;
+
+               thing = queue->array[parent];
+               queue->array[parent] = queue->array[ix];
+               queue->array[ix] = thing;
+       }
+}
+
+void *prio_queue_get(struct prio_queue *queue)
+{
+       void *result, *swap;
+       int ix, child;
+       prio_queue_compare_fn compare = queue->compare;
+
+       if (!queue->nr)
+               return NULL;
+       if (!compare)
+               return queue->array[--queue->nr]; /* LIFO */
+
+       result = queue->array[0];
+       if (!--queue->nr)
+               return result;
+
+       queue->array[0] = queue->array[queue->nr];
+
+       /* Push down the one at the root */
+       for (ix = 0; ix * 2 + 1 < queue->nr; ix = child) {
+               child = ix * 2 + 1; /* left */
+               if ((child + 1 < queue->nr) &&
+                   (compare(queue->array[child], queue->array[child + 1],
+                            queue->cb_data) >= 0))
+                       child++; /* use right child */
+
+               if (compare(queue->array[ix], queue->array[child],
+                           queue->cb_data) <= 0)
+                       break;
+
+               swap = queue->array[child];
+               queue->array[child] = queue->array[ix];
+               queue->array[ix] = swap;
+       }
+       return result;
+}
diff --git a/prio-queue.h b/prio-queue.h
new file mode 100644 (file)
index 0000000..9c3cd1f
--- /dev/null
@@ -0,0 +1,48 @@
+#ifndef PRIO_QUEUE_H
+#define PRIO_QUEUE_H
+
+/*
+ * A priority queue implementation, primarily for keeping track of
+ * commits in the 'date-order' so that we process them from new to old
+ * as they are discovered, but can be used to hold any pointer to
+ * struct.  The caller is responsible for supplying a function to
+ * compare two "things".
+ *
+ * Alternatively, this data structure can also be used as a LIFO stack
+ * by specifying NULL as the comparison function.
+ */
+
+/*
+ * Compare two "things", one and two; the third parameter is cb_data
+ * in the prio_queue structure.  The result is returned as a sign of
+ * the return value, being the same as the sign of the result of
+ * subtracting "two" from "one" (i.e. negative if "one" sorts earlier
+ * than "two").
+ */
+typedef int (*prio_queue_compare_fn)(const void *one, const void *two, void *cb_data);
+
+struct prio_queue {
+       prio_queue_compare_fn compare;
+       void *cb_data;
+       int alloc, nr;
+       void **array;
+};
+
+/*
+ * Add the "thing" to the queue.
+ */
+extern void prio_queue_put(struct prio_queue *, void *thing);
+
+/*
+ * Extract the "thing" that compares the smallest out of the queue,
+ * or NULL.  If compare function is NULL, the queue acts as a LIFO
+ * stack.
+ */
+extern void *prio_queue_get(struct prio_queue *);
+
+extern void clear_prio_queue(struct prio_queue *);
+
+/* Reverse the LIFO elements */
+extern void prio_queue_reverse(struct prio_queue *);
+
+#endif /* PRIO_QUEUE_H */
index b297addb576dec45fa9b82ef4a0ffb350f9cfc6c..d5201f9b06c3f5ac53b8a6f5396fbf3392f1b3b2 100644 (file)
@@ -67,6 +67,61 @@ void rename_index_entry_at(struct index_state *istate, int nr, const char *new_n
        add_index_entry(istate, new, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE);
 }
 
+void fill_stat_data(struct stat_data *sd, struct stat *st)
+{
+       sd->sd_ctime.sec = (unsigned int)st->st_ctime;
+       sd->sd_mtime.sec = (unsigned int)st->st_mtime;
+       sd->sd_ctime.nsec = ST_CTIME_NSEC(*st);
+       sd->sd_mtime.nsec = ST_MTIME_NSEC(*st);
+       sd->sd_dev = st->st_dev;
+       sd->sd_ino = st->st_ino;
+       sd->sd_uid = st->st_uid;
+       sd->sd_gid = st->st_gid;
+       sd->sd_size = st->st_size;
+}
+
+int match_stat_data(const struct stat_data *sd, struct stat *st)
+{
+       int changed = 0;
+
+       if (sd->sd_mtime.sec != (unsigned int)st->st_mtime)
+               changed |= MTIME_CHANGED;
+       if (trust_ctime && check_stat &&
+           sd->sd_ctime.sec != (unsigned int)st->st_ctime)
+               changed |= CTIME_CHANGED;
+
+#ifdef USE_NSEC
+       if (check_stat && sd->sd_mtime.nsec != ST_MTIME_NSEC(*st))
+               changed |= MTIME_CHANGED;
+       if (trust_ctime && check_stat &&
+           sd->sd_ctime.nsec != ST_CTIME_NSEC(*st))
+               changed |= CTIME_CHANGED;
+#endif
+
+       if (check_stat) {
+               if (sd->sd_uid != (unsigned int) st->st_uid ||
+                       sd->sd_gid != (unsigned int) st->st_gid)
+                       changed |= OWNER_CHANGED;
+               if (sd->sd_ino != (unsigned int) st->st_ino)
+                       changed |= INODE_CHANGED;
+       }
+
+#ifdef USE_STDEV
+       /*
+        * st_dev breaks on network filesystems where different
+        * clients will have different views of what "device"
+        * the filesystem is on
+        */
+       if (check_stat && sd->sd_dev != (unsigned int) st->st_dev)
+                       changed |= INODE_CHANGED;
+#endif
+
+       if (sd->sd_size != (unsigned int) st->st_size)
+               changed |= DATA_CHANGED;
+
+       return changed;
+}
+
 /*
  * This only updates the "non-critical" parts of the directory
  * cache, ie the parts that aren't tracked by GIT, and only used
@@ -74,15 +129,7 @@ void rename_index_entry_at(struct index_state *istate, int nr, const char *new_n
  */
 void fill_stat_cache_info(struct cache_entry *ce, struct stat *st)
 {
-       ce->ce_ctime.sec = (unsigned int)st->st_ctime;
-       ce->ce_mtime.sec = (unsigned int)st->st_mtime;
-       ce->ce_ctime.nsec = ST_CTIME_NSEC(*st);
-       ce->ce_mtime.nsec = ST_MTIME_NSEC(*st);
-       ce->ce_dev = st->st_dev;
-       ce->ce_ino = st->st_ino;
-       ce->ce_uid = st->st_uid;
-       ce->ce_gid = st->st_gid;
-       ce->ce_size = st->st_size;
+       fill_stat_data(&ce->ce_stat_data, st);
 
        if (assume_unchanged)
                ce->ce_flags |= CE_VALID;
@@ -195,43 +242,11 @@ static int ce_match_stat_basic(const struct cache_entry *ce, struct stat *st)
        default:
                die("internal error: ce_mode is %o", ce->ce_mode);
        }
-       if (ce->ce_mtime.sec != (unsigned int)st->st_mtime)
-               changed |= MTIME_CHANGED;
-       if (trust_ctime && check_stat &&
-           ce->ce_ctime.sec != (unsigned int)st->st_ctime)
-               changed |= CTIME_CHANGED;
-
-#ifdef USE_NSEC
-       if (check_stat && ce->ce_mtime.nsec != ST_MTIME_NSEC(*st))
-               changed |= MTIME_CHANGED;
-       if (trust_ctime && check_stat &&
-           ce->ce_ctime.nsec != ST_CTIME_NSEC(*st))
-               changed |= CTIME_CHANGED;
-#endif
-
-       if (check_stat) {
-               if (ce->ce_uid != (unsigned int) st->st_uid ||
-                       ce->ce_gid != (unsigned int) st->st_gid)
-                       changed |= OWNER_CHANGED;
-               if (ce->ce_ino != (unsigned int) st->st_ino)
-                       changed |= INODE_CHANGED;
-       }
 
-#ifdef USE_STDEV
-       /*
-        * st_dev breaks on network filesystems where different
-        * clients will have different views of what "device"
-        * the filesystem is on
-        */
-       if (check_stat && ce->ce_dev != (unsigned int) st->st_dev)
-                       changed |= INODE_CHANGED;
-#endif
-
-       if (ce->ce_size != (unsigned int) st->st_size)
-               changed |= DATA_CHANGED;
+       changed |= match_stat_data(&ce->ce_stat_data, st);
 
        /* Racily smudged entry? */
-       if (!ce->ce_size) {
+       if (!ce->ce_stat_data.sd_size) {
                if (!is_empty_blob_sha1(ce->sha1))
                        changed |= DATA_CHANGED;
        }
@@ -246,11 +261,11 @@ static int is_racy_timestamp(const struct index_state *istate,
                istate->timestamp.sec &&
 #ifdef USE_NSEC
                 /* nanosecond timestamped files can also be racy! */
-               (istate->timestamp.sec < ce->ce_mtime.sec ||
-                (istate->timestamp.sec == ce->ce_mtime.sec &&
-                 istate->timestamp.nsec <= ce->ce_mtime.nsec))
+               (istate->timestamp.sec < ce->ce_stat_data.sd_mtime.sec ||
+                (istate->timestamp.sec == ce->ce_stat_data.sd_mtime.sec &&
+                 istate->timestamp.nsec <= ce->ce_stat_data.sd_mtime.nsec))
 #else
-               istate->timestamp.sec <= ce->ce_mtime.sec
+               istate->timestamp.sec <= ce->ce_stat_data.sd_mtime.sec
 #endif
                 );
 }
@@ -342,7 +357,7 @@ int ie_modified(const struct index_state *istate,
         * then we know it is.
         */
        if ((changed & DATA_CHANGED) &&
-           (S_ISGITLINK(ce->ce_mode) || ce->ce_size != 0))
+           (S_ISGITLINK(ce->ce_mode) || ce->ce_stat_data.sd_size != 0))
                return changed;
 
        changed_fs = ce_modified_check_fs(ce, st);
@@ -1324,16 +1339,16 @@ static struct cache_entry *cache_entry_from_ondisk(struct ondisk_cache_entry *on
 {
        struct cache_entry *ce = xmalloc(cache_entry_size(len));
 
-       ce->ce_ctime.sec = ntoh_l(ondisk->ctime.sec);
-       ce->ce_mtime.sec = ntoh_l(ondisk->mtime.sec);
-       ce->ce_ctime.nsec = ntoh_l(ondisk->ctime.nsec);
-       ce->ce_mtime.nsec = ntoh_l(ondisk->mtime.nsec);
-       ce->ce_dev   = ntoh_l(ondisk->dev);
-       ce->ce_ino   = ntoh_l(ondisk->ino);
+       ce->ce_stat_data.sd_ctime.sec = ntoh_l(ondisk->ctime.sec);
+       ce->ce_stat_data.sd_mtime.sec = ntoh_l(ondisk->mtime.sec);
+       ce->ce_stat_data.sd_ctime.nsec = ntoh_l(ondisk->ctime.nsec);
+       ce->ce_stat_data.sd_mtime.nsec = ntoh_l(ondisk->mtime.nsec);
+       ce->ce_stat_data.sd_dev   = ntoh_l(ondisk->dev);
+       ce->ce_stat_data.sd_ino   = ntoh_l(ondisk->ino);
        ce->ce_mode  = ntoh_l(ondisk->mode);
-       ce->ce_uid   = ntoh_l(ondisk->uid);
-       ce->ce_gid   = ntoh_l(ondisk->gid);
-       ce->ce_size  = ntoh_l(ondisk->size);
+       ce->ce_stat_data.sd_uid   = ntoh_l(ondisk->uid);
+       ce->ce_stat_data.sd_gid   = ntoh_l(ondisk->gid);
+       ce->ce_stat_data.sd_size  = ntoh_l(ondisk->size);
        ce->ce_flags = flags & ~CE_NAMEMASK;
        ce->ce_namelen = len;
        hashcpy(ce->sha1, ondisk->sha1);
@@ -1611,7 +1626,7 @@ static void ce_smudge_racily_clean_entry(struct cache_entry *ce)
         * The only thing we care about in this function is to smudge the
         * falsely clean entry due to touch-update-touch race, so we leave
         * everything else as they are.  We are called for entries whose
-        * ce_mtime match the index file mtime.
+        * ce_stat_data.sd_mtime match the index file mtime.
         *
         * Note that this actually does not do much for gitlinks, for
         * which ce_match_stat_basic() always goes to the actual
@@ -1650,7 +1665,7 @@ static void ce_smudge_racily_clean_entry(struct cache_entry *ce)
                 * file, and never calls us, so the cached size information
                 * for "frotz" stays 6 which does not match the filesystem.
                 */
-               ce->ce_size = 0;
+               ce->ce_stat_data.sd_size = 0;
        }
 }
 
@@ -1660,16 +1675,16 @@ static char *copy_cache_entry_to_ondisk(struct ondisk_cache_entry *ondisk,
 {
        short flags;
 
-       ondisk->ctime.sec = htonl(ce->ce_ctime.sec);
-       ondisk->mtime.sec = htonl(ce->ce_mtime.sec);
-       ondisk->ctime.nsec = htonl(ce->ce_ctime.nsec);
-       ondisk->mtime.nsec = htonl(ce->ce_mtime.nsec);
-       ondisk->dev  = htonl(ce->ce_dev);
-       ondisk->ino  = htonl(ce->ce_ino);
+       ondisk->ctime.sec = htonl(ce->ce_stat_data.sd_ctime.sec);
+       ondisk->mtime.sec = htonl(ce->ce_stat_data.sd_mtime.sec);
+       ondisk->ctime.nsec = htonl(ce->ce_stat_data.sd_ctime.nsec);
+       ondisk->mtime.nsec = htonl(ce->ce_stat_data.sd_mtime.nsec);
+       ondisk->dev  = htonl(ce->ce_stat_data.sd_dev);
+       ondisk->ino  = htonl(ce->ce_stat_data.sd_ino);
        ondisk->mode = htonl(ce->ce_mode);
-       ondisk->uid  = htonl(ce->ce_uid);
-       ondisk->gid  = htonl(ce->ce_gid);
-       ondisk->size = htonl(ce->ce_size);
+       ondisk->uid  = htonl(ce->ce_stat_data.sd_uid);
+       ondisk->gid  = htonl(ce->ce_stat_data.sd_gid);
+       ondisk->size = htonl(ce->ce_stat_data.sd_size);
        hashcpy(ondisk->sha1, ce->sha1);
 
        flags = ce->ce_flags;
@@ -1936,3 +1951,33 @@ void *read_blob_data_from_index(struct index_state *istate, const char *path, un
                *size = sz;
        return data;
 }
+
+void stat_validity_clear(struct stat_validity *sv)
+{
+       free(sv->sd);
+       sv->sd = NULL;
+}
+
+int stat_validity_check(struct stat_validity *sv, const char *path)
+{
+       struct stat st;
+
+       if (stat(path, &st) < 0)
+               return sv->sd == NULL;
+       if (!sv->sd)
+               return 0;
+       return S_ISREG(st.st_mode) && !match_stat_data(sv->sd, &st);
+}
+
+void stat_validity_update(struct stat_validity *sv, int fd)
+{
+       struct stat st;
+
+       if (fstat(fd, &st) < 0 || !S_ISREG(st.st_mode))
+               stat_validity_clear(sv);
+       else {
+               if (!sv->sd)
+                       sv->sd = xcalloc(1, sizeof(struct stat_data));
+               fill_stat_data(sv->sd, &st);
+       }
+}
diff --git a/refs.c b/refs.c
index 42a7e17f6bdf6f03f56a012c87c137b80452ea17..43022066499ffaf828ac31e82b77f53539bc232d 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -749,6 +749,21 @@ static int do_for_each_entry_in_dirs(struct ref_dir *dir1,
        }
 }
 
+/*
+ * Load all of the refs from the dir into our in-memory cache. The hard work
+ * of loading loose refs is done by get_ref_dir(), so we just need to recurse
+ * through all of the sub-directories. We do not even need to care about
+ * sorting, as traversal order does not matter to us.
+ */
+static void prime_ref_dir(struct ref_dir *dir)
+{
+       int i;
+       for (i = 0; i < dir->nr; i++) {
+               struct ref_entry *entry = dir->entries[i];
+               if (entry->flag & REF_DIR)
+                       prime_ref_dir(get_ref_dir(entry));
+       }
+}
 /*
  * Return true iff refname1 and refname2 conflict with each other.
  * Two reference names conflict if one of them exactly matches the
@@ -806,6 +821,30 @@ static int is_refname_available(const char *refname, const char *oldrefname,
        return 1;
 }
 
+struct packed_ref_cache {
+       struct ref_entry *root;
+
+       /*
+        * Count of references to the data structure in this instance,
+        * including the pointer from ref_cache::packed if any.  The
+        * data will not be freed as long as the reference count is
+        * nonzero.
+        */
+       unsigned int referrers;
+
+       /*
+        * Iff the packed-refs file associated with this instance is
+        * currently locked for writing, this points at the associated
+        * lock (which is owned by somebody else).  The referrer count
+        * is also incremented when the file is locked and decremented
+        * when it is unlocked.
+        */
+       struct lock_file *lock;
+
+       /* The metadata from when this packed-refs cache was read */
+       struct stat_validity validity;
+};
+
 /*
  * Future: need to be in "struct repository"
  * when doing a full libification.
@@ -813,7 +852,7 @@ static int is_refname_available(const char *refname, const char *oldrefname,
 static struct ref_cache {
        struct ref_cache *next;
        struct ref_entry *loose;
-       struct ref_entry *packed;
+       struct packed_ref_cache *packed;
        /*
         * The submodule name, or "" for the main repo.  We allocate
         * length 1 rather than FLEX_ARRAY so that the main ref_cache
@@ -822,11 +861,42 @@ static struct ref_cache {
        char name[1];
 } ref_cache, *submodule_ref_caches;
 
+/* Lock used for the main packed-refs file: */
+static struct lock_file packlock;
+
+/*
+ * Increment the reference count of *packed_refs.
+ */
+static void acquire_packed_ref_cache(struct packed_ref_cache *packed_refs)
+{
+       packed_refs->referrers++;
+}
+
+/*
+ * Decrease the reference count of *packed_refs.  If it goes to zero,
+ * free *packed_refs and return true; otherwise return false.
+ */
+static int release_packed_ref_cache(struct packed_ref_cache *packed_refs)
+{
+       if (!--packed_refs->referrers) {
+               free_ref_entry(packed_refs->root);
+               stat_validity_clear(&packed_refs->validity);
+               free(packed_refs);
+               return 1;
+       } else {
+               return 0;
+       }
+}
+
 static void clear_packed_ref_cache(struct ref_cache *refs)
 {
        if (refs->packed) {
-               free_ref_entry(refs->packed);
+               struct packed_ref_cache *packed_refs = refs->packed;
+
+               if (packed_refs->lock)
+                       die("internal error: packed-ref cache cleared while locked");
                refs->packed = NULL;
+               release_packed_ref_cache(packed_refs);
        }
 }
 
@@ -996,29 +1066,57 @@ static void read_packed_refs(FILE *f, struct ref_dir *dir)
        }
 }
 
-static struct ref_dir *get_packed_refs(struct ref_cache *refs)
+/*
+ * Get the packed_ref_cache for the specified ref_cache, creating it
+ * if necessary.
+ */
+static struct packed_ref_cache *get_packed_ref_cache(struct ref_cache *refs)
 {
+       const char *packed_refs_file;
+
+       if (*refs->name)
+               packed_refs_file = git_path_submodule(refs->name, "packed-refs");
+       else
+               packed_refs_file = git_path("packed-refs");
+
+       if (refs->packed &&
+           !stat_validity_check(&refs->packed->validity, packed_refs_file))
+               clear_packed_ref_cache(refs);
+
        if (!refs->packed) {
-               const char *packed_refs_file;
                FILE *f;
 
-               refs->packed = create_dir_entry(refs, "", 0, 0);
-               if (*refs->name)
-                       packed_refs_file = git_path_submodule(refs->name, "packed-refs");
-               else
-                       packed_refs_file = git_path("packed-refs");
+               refs->packed = xcalloc(1, sizeof(*refs->packed));
+               acquire_packed_ref_cache(refs->packed);
+               refs->packed->root = create_dir_entry(refs, "", 0, 0);
                f = fopen(packed_refs_file, "r");
                if (f) {
-                       read_packed_refs(f, get_ref_dir(refs->packed));
+                       stat_validity_update(&refs->packed->validity, fileno(f));
+                       read_packed_refs(f, get_ref_dir(refs->packed->root));
                        fclose(f);
                }
        }
-       return get_ref_dir(refs->packed);
+       return refs->packed;
+}
+
+static struct ref_dir *get_packed_ref_dir(struct packed_ref_cache *packed_ref_cache)
+{
+       return get_ref_dir(packed_ref_cache->root);
+}
+
+static struct ref_dir *get_packed_refs(struct ref_cache *refs)
+{
+       return get_packed_ref_dir(get_packed_ref_cache(refs));
 }
 
 void add_packed_ref(const char *refname, const unsigned char *sha1)
 {
-       add_ref(get_packed_refs(&ref_cache),
+       struct packed_ref_cache *packed_ref_cache =
+               get_packed_ref_cache(&ref_cache);
+
+       if (!packed_ref_cache->lock)
+               die("internal error: packed refs not locked");
+       add_ref(get_packed_ref_dir(packed_ref_cache),
                create_ref_entry(refname, sha1, REF_ISPACKED, 1));
 }
 
@@ -1197,6 +1295,37 @@ static struct ref_entry *get_packed_ref(const char *refname)
        return find_ref(get_packed_refs(&ref_cache), refname);
 }
 
+/*
+ * A loose ref file doesn't exist; check for a packed ref.  The
+ * options are forwarded from resolve_safe_unsafe().
+ */
+static const char *handle_missing_loose_ref(const char *refname,
+                                           unsigned char *sha1,
+                                           int reading,
+                                           int *flag)
+{
+       struct ref_entry *entry;
+
+       /*
+        * The loose reference file does not exist; check for a packed
+        * reference.
+        */
+       entry = get_packed_ref(refname);
+       if (entry) {
+               hashcpy(sha1, entry->u.value.sha1);
+               if (flag)
+                       *flag |= REF_ISPACKED;
+               return refname;
+       }
+       /* The reference is not a packed reference, either. */
+       if (reading) {
+               return NULL;
+       } else {
+               hashclr(sha1);
+               return refname;
+       }
+}
+
 const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int reading, int *flag)
 {
        int depth = MAXDEPTH;
@@ -1221,36 +1350,34 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea
 
                git_snpath(path, sizeof(path), "%s", refname);
 
+               /*
+                * We might have to loop back here to avoid a race
+                * condition: first we lstat() the file, then we try
+                * to read it as a link or as a file.  But if somebody
+                * changes the type of the file (file <-> directory
+                * <-> symlink) between the lstat() and reading, then
+                * we don't want to report that as an error but rather
+                * try again starting with the lstat().
+                */
+       stat_ref:
                if (lstat(path, &st) < 0) {
-                       struct ref_entry *entry;
-
-                       if (errno != ENOENT)
+                       if (errno == ENOENT)
+                               return handle_missing_loose_ref(refname, sha1,
+                                                               reading, flag);
+                       else
                                return NULL;
-                       /*
-                        * The loose reference file does not exist;
-                        * check for a packed reference.
-                        */
-                       entry = get_packed_ref(refname);
-                       if (entry) {
-                               hashcpy(sha1, entry->u.value.sha1);
-                               if (flag)
-                                       *flag |= REF_ISPACKED;
-                               return refname;
-                       }
-                       /* The reference is not a packed reference, either. */
-                       if (reading) {
-                               return NULL;
-                       } else {
-                               hashclr(sha1);
-                               return refname;
-                       }
                }
 
                /* Follow "normalized" - ie "refs/.." symlinks by hand */
                if (S_ISLNK(st.st_mode)) {
                        len = readlink(path, buffer, sizeof(buffer)-1);
-                       if (len < 0)
-                               return NULL;
+                       if (len < 0) {
+                               if (errno == ENOENT || errno == EINVAL)
+                                       /* inconsistent with lstat; retry */
+                                       goto stat_ref;
+                               else
+                                       return NULL;
+                       }
                        buffer[len] = 0;
                        if (!prefixcmp(buffer, "refs/") &&
                                        !check_refname_format(buffer, 0)) {
@@ -1273,8 +1400,13 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea
                 * a ref
                 */
                fd = open(path, O_RDONLY);
-               if (fd < 0)
-                       return NULL;
+               if (fd < 0) {
+                       if (errno == ENOENT)
+                               /* inconsistent with lstat; retry */
+                               goto stat_ref;
+                       else
+                               return NULL;
+               }
                len = read_in_full(fd, buffer, sizeof(buffer)-1);
                close(fd);
                if (len < 0)
@@ -1286,8 +1418,19 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea
                /*
                 * Is it a symbolic ref?
                 */
-               if (prefixcmp(buffer, "ref:"))
-                       break;
+               if (prefixcmp(buffer, "ref:")) {
+                       /*
+                        * Please note that FETCH_HEAD has a second
+                        * line containing other data.
+                        */
+                       if (get_sha1_hex(buffer, sha1) ||
+                           (buffer[40] != '\0' && !isspace(buffer[40]))) {
+                               if (flag)
+                                       *flag |= REF_ISBROKEN;
+                               return NULL;
+                       }
+                       return refname;
+               }
                if (flag)
                        *flag |= REF_ISSYMREF;
                buf = buffer + 4;
@@ -1300,13 +1443,6 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea
                }
                refname = strcpy(refname_buffer, buf);
        }
-       /* Please note that FETCH_HEAD has a second line containing other data. */
-       if (get_sha1_hex(buffer, sha1) || (buffer[40] != '\0' && !isspace(buffer[40]))) {
-               if (flag)
-                       *flag |= REF_ISBROKEN;
-               return NULL;
-       }
-       return refname;
 }
 
 char *resolve_refdup(const char *ref, unsigned char *sha1, int reading, int *flag)
@@ -1520,14 +1656,32 @@ void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refname)
 static int do_for_each_entry(struct ref_cache *refs, const char *base,
                             each_ref_entry_fn fn, void *cb_data)
 {
-       struct ref_dir *packed_dir = get_packed_refs(refs);
-       struct ref_dir *loose_dir = get_loose_refs(refs);
+       struct packed_ref_cache *packed_ref_cache;
+       struct ref_dir *loose_dir;
+       struct ref_dir *packed_dir;
        int retval = 0;
 
+       /*
+        * We must make sure that all loose refs are read before accessing the
+        * packed-refs file; this avoids a race condition in which loose refs
+        * are migrated to the packed-refs file by a simultaneous process, but
+        * our in-memory view is from before the migration. get_packed_ref_cache()
+        * takes care of making sure our view is up to date with what is on
+        * disk.
+        */
+       loose_dir = get_loose_refs(refs);
        if (base && *base) {
-               packed_dir = find_containing_dir(packed_dir, base, 0);
                loose_dir = find_containing_dir(loose_dir, base, 0);
        }
+       if (loose_dir)
+               prime_ref_dir(loose_dir);
+
+       packed_ref_cache = get_packed_ref_cache(refs);
+       acquire_packed_ref_cache(packed_ref_cache);
+       packed_dir = get_packed_ref_dir(packed_ref_cache);
+       if (base && *base) {
+               packed_dir = find_containing_dir(packed_dir, base, 0);
+       }
 
        if (packed_dir && loose_dir) {
                sort_ref_dir(packed_dir);
@@ -1544,6 +1698,7 @@ static int do_for_each_entry(struct ref_cache *refs, const char *base,
                                loose_dir, 0, fn, cb_data);
        }
 
+       release_packed_ref_cache(packed_ref_cache);
        return retval;
 }
 
@@ -1998,6 +2153,73 @@ static void write_packed_entry(int fd, char *refname, unsigned char *sha1,
        }
 }
 
+/*
+ * An each_ref_entry_fn that writes the entry to a packed-refs file.
+ */
+static int write_packed_entry_fn(struct ref_entry *entry, void *cb_data)
+{
+       int *fd = cb_data;
+       enum peel_status peel_status = peel_entry(entry, 0);
+
+       if (peel_status != PEEL_PEELED && peel_status != PEEL_NON_TAG)
+               error("internal error: %s is not a valid packed reference!",
+                     entry->name);
+       write_packed_entry(*fd, entry->name, entry->u.value.sha1,
+                          peel_status == PEEL_PEELED ?
+                          entry->u.value.peeled : NULL);
+       return 0;
+}
+
+int lock_packed_refs(int flags)
+{
+       struct packed_ref_cache *packed_ref_cache;
+
+       /* Discard the old cache because it might be invalid: */
+       clear_packed_ref_cache(&ref_cache);
+       if (hold_lock_file_for_update(&packlock, git_path("packed-refs"), flags) < 0)
+               return -1;
+       /* Read the current packed-refs while holding the lock: */
+       packed_ref_cache = get_packed_ref_cache(&ref_cache);
+       packed_ref_cache->lock = &packlock;
+       /* Increment the reference count to prevent it from being freed: */
+       acquire_packed_ref_cache(packed_ref_cache);
+       return 0;
+}
+
+int commit_packed_refs(void)
+{
+       struct packed_ref_cache *packed_ref_cache =
+               get_packed_ref_cache(&ref_cache);
+       int error = 0;
+
+       if (!packed_ref_cache->lock)
+               die("internal error: packed-refs not locked");
+       write_or_die(packed_ref_cache->lock->fd,
+                    PACKED_REFS_HEADER, strlen(PACKED_REFS_HEADER));
+
+       do_for_each_entry_in_dir(get_packed_ref_dir(packed_ref_cache),
+                                0, write_packed_entry_fn,
+                                &packed_ref_cache->lock->fd);
+       if (commit_lock_file(packed_ref_cache->lock))
+               error = -1;
+       packed_ref_cache->lock = NULL;
+       release_packed_ref_cache(packed_ref_cache);
+       return error;
+}
+
+void rollback_packed_refs(void)
+{
+       struct packed_ref_cache *packed_ref_cache =
+               get_packed_ref_cache(&ref_cache);
+
+       if (!packed_ref_cache->lock)
+               die("internal error: packed-refs not locked");
+       rollback_lock_file(packed_ref_cache->lock);
+       packed_ref_cache->lock = NULL;
+       release_packed_ref_cache(packed_ref_cache);
+       clear_packed_ref_cache(&ref_cache);
+}
+
 struct ref_to_prune {
        struct ref_to_prune *next;
        unsigned char sha1[20];
@@ -2006,35 +2228,50 @@ struct ref_to_prune {
 
 struct pack_refs_cb_data {
        unsigned int flags;
+       struct ref_dir *packed_refs;
        struct ref_to_prune *ref_to_prune;
-       int fd;
 };
 
-static int pack_one_ref(struct ref_entry *entry, void *cb_data)
+/*
+ * An each_ref_entry_fn that is run over loose references only.  If
+ * the loose reference can be packed, add an entry in the packed ref
+ * cache.  If the reference should be pruned, also add it to
+ * ref_to_prune in the pack_refs_cb_data.
+ */
+static int pack_if_possible_fn(struct ref_entry *entry, void *cb_data)
 {
        struct pack_refs_cb_data *cb = cb_data;
        enum peel_status peel_status;
+       struct ref_entry *packed_entry;
        int is_tag_ref = !prefixcmp(entry->name, "refs/tags/");
 
-       /* ALWAYS pack refs that were already packed or are tags */
-       if (!(cb->flags & PACK_REFS_ALL) && !is_tag_ref &&
-           !(entry->flag & REF_ISPACKED))
+       /* ALWAYS pack tags */
+       if (!(cb->flags & PACK_REFS_ALL) && !is_tag_ref)
                return 0;
 
        /* Do not pack symbolic or broken refs: */
        if ((entry->flag & REF_ISSYMREF) || !ref_resolves_to_object(entry))
                return 0;
 
+       /* Add a packed ref cache entry equivalent to the loose entry. */
        peel_status = peel_entry(entry, 1);
        if (peel_status != PEEL_PEELED && peel_status != PEEL_NON_TAG)
                die("internal error peeling reference %s (%s)",
                    entry->name, sha1_to_hex(entry->u.value.sha1));
-       write_packed_entry(cb->fd, entry->name, entry->u.value.sha1,
-                          peel_status == PEEL_PEELED ?
-                          entry->u.value.peeled : NULL);
+       packed_entry = find_ref(cb->packed_refs, entry->name);
+       if (packed_entry) {
+               /* Overwrite existing packed entry with info from loose entry */
+               packed_entry->flag = REF_ISPACKED | REF_KNOWS_PEELED;
+               hashcpy(packed_entry->u.value.sha1, entry->u.value.sha1);
+       } else {
+               packed_entry = create_ref_entry(entry->name, entry->u.value.sha1,
+                                               REF_ISPACKED | REF_KNOWS_PEELED, 0);
+               add_ref(cb->packed_refs, packed_entry);
+       }
+       hashcpy(packed_entry->u.value.peeled, entry->u.value.peeled);
 
-       /* If the ref was already packed, there is no need to prune it. */
-       if ((cb->flags & PACK_REFS_PRUNE) && !(entry->flag & REF_ISPACKED)) {
+       /* Schedule the loose reference for pruning if requested. */
+       if ((cb->flags & PACK_REFS_PRUNE)) {
                int namelen = strlen(entry->name) + 1;
                struct ref_to_prune *n = xcalloc(1, sizeof(*n) + namelen);
                hashcpy(n->sha1, entry->u.value.sha1);
@@ -2096,8 +2333,6 @@ static void prune_refs(struct ref_to_prune *r)
        }
 }
 
-static struct lock_file packlock;
-
 int pack_refs(unsigned int flags)
 {
        struct pack_refs_cb_data cbdata;
@@ -2105,26 +2340,38 @@ int pack_refs(unsigned int flags)
        memset(&cbdata, 0, sizeof(cbdata));
        cbdata.flags = flags;
 
-       cbdata.fd = hold_lock_file_for_update(&packlock, git_path("packed-refs"),
-                                             LOCK_DIE_ON_ERROR);
+       lock_packed_refs(LOCK_DIE_ON_ERROR);
+       cbdata.packed_refs = get_packed_refs(&ref_cache);
 
-       write_or_die(cbdata.fd, PACKED_REFS_HEADER, strlen(PACKED_REFS_HEADER));
+       do_for_each_entry_in_dir(get_loose_refs(&ref_cache), 0,
+                                pack_if_possible_fn, &cbdata);
 
-       do_for_each_entry(&ref_cache, "", pack_one_ref, &cbdata);
-       if (commit_lock_file(&packlock) < 0)
+       if (commit_packed_refs())
                die_errno("unable to overwrite old ref-pack file");
+
        prune_refs(cbdata.ref_to_prune);
        return 0;
 }
 
-static int repack_ref_fn(struct ref_entry *entry, void *cb_data)
+/*
+ * If entry is no longer needed in packed-refs, add it to the string
+ * list pointed to by cb_data.  Reasons for deleting entries:
+ *
+ * - Entry is broken.
+ * - Entry is overridden by a loose ref.
+ * - Entry does not point at a valid object.
+ *
+ * In the first and third cases, also emit an error message because these
+ * are indications of repository corruption.
+ */
+static int curate_packed_ref_fn(struct ref_entry *entry, void *cb_data)
 {
-       int *fd = cb_data;
-       enum peel_status peel_status;
+       struct string_list *refs_to_delete = cb_data;
 
        if (entry->flag & REF_ISBROKEN) {
                /* This shouldn't happen to packed refs. */
                error("%s is broken!", entry->name);
+               string_list_append(refs_to_delete, entry->name);
                return 0;
        }
        if (!has_sha1_file(entry->u.value.sha1)) {
@@ -2134,7 +2381,7 @@ static int repack_ref_fn(struct ref_entry *entry, void *cb_data)
                if (read_ref_full(entry->name, sha1, 0, &flags))
                        /* We should at least have found the packed ref. */
                        die("Internal error");
-               if ((flags & REF_ISSYMREF) || !(flags & REF_ISPACKED))
+               if ((flags & REF_ISSYMREF) || !(flags & REF_ISPACKED)) {
                        /*
                         * This packed reference is overridden by a
                         * loose reference, so it is OK that its value
@@ -2143,9 +2390,11 @@ static int repack_ref_fn(struct ref_entry *entry, void *cb_data)
                         * collected.  For this purpose we don't even
                         * care whether the loose reference itself is
                         * invalid, broken, symbolic, etc.  Silently
-                        * omit the packed reference from the output.
+                        * remove the packed reference.
                         */
+                       string_list_append(refs_to_delete, entry->name);
                        return 0;
+               }
                /*
                 * There is no overriding loose reference, so the fact
                 * that this reference doesn't refer to a valid object
@@ -2154,44 +2403,47 @@ static int repack_ref_fn(struct ref_entry *entry, void *cb_data)
                 * the output.
                 */
                error("%s does not point to a valid object!", entry->name);
+               string_list_append(refs_to_delete, entry->name);
                return 0;
        }
 
-       peel_status = peel_entry(entry, 0);
-       write_packed_entry(*fd, entry->name, entry->u.value.sha1,
-                          peel_status == PEEL_PEELED ?
-                          entry->u.value.peeled : NULL);
-
        return 0;
 }
 
 static int repack_without_ref(const char *refname)
 {
-       int fd;
        struct ref_dir *packed;
+       struct string_list refs_to_delete = STRING_LIST_INIT_DUP;
+       struct string_list_item *ref_to_delete;
 
        if (!get_packed_ref(refname))
                return 0; /* refname does not exist in packed refs */
 
-       fd = hold_lock_file_for_update(&packlock, git_path("packed-refs"), 0);
-       if (fd < 0) {
+       if (lock_packed_refs(0)) {
                unable_to_lock_error(git_path("packed-refs"), errno);
                return error("cannot delete '%s' from packed refs", refname);
        }
-       clear_packed_ref_cache(&ref_cache);
        packed = get_packed_refs(&ref_cache);
-       /* Remove refname from the cache. */
+
+       /* Remove refname from the cache: */
        if (remove_entry(packed, refname) == -1) {
                /*
                 * The packed entry disappeared while we were
                 * acquiring the lock.
                 */
-               rollback_lock_file(&packlock);
+               rollback_packed_refs();
                return 0;
        }
-       write_or_die(fd, PACKED_REFS_HEADER, strlen(PACKED_REFS_HEADER));
-       do_for_each_entry_in_dir(packed, 0, repack_ref_fn, &fd);
-       return commit_lock_file(&packlock);
+
+       /* Remove any other accumulated cruft: */
+       do_for_each_entry_in_dir(packed, 0, curate_packed_ref_fn, &refs_to_delete);
+       for_each_string_list_item(ref_to_delete, &refs_to_delete) {
+               if (remove_entry(packed, ref_to_delete->string) == -1)
+                       die("internal error");
+       }
+
+       /* Write what remains: */
+       return commit_packed_refs();
 }
 
 int delete_ref(const char *refname, const unsigned char *sha1, int delopt)
diff --git a/refs.h b/refs.h
index 246bf6096d222a0d822d621de519ca5c49b0f641..9e5db3ae26ec7898d8ee9083c37562d3af05c0ab 100644 (file)
--- a/refs.h
+++ b/refs.h
@@ -77,11 +77,33 @@ extern int for_each_rawref(each_ref_fn, void *);
 extern void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refname);
 
 /*
- * Add a reference to the in-memory packed reference cache.  To actually
- * write the reference to the packed-refs file, call pack_refs().
+ * Lock the packed-refs file for writing.  Flags is passed to
+ * hold_lock_file_for_update().  Return 0 on success.
+ */
+extern int lock_packed_refs(int flags);
+
+/*
+ * Add a reference to the in-memory packed reference cache.  This may
+ * only be called while the packed-refs file is locked (see
+ * lock_packed_refs()).  To actually write the packed-refs file, call
+ * commit_packed_refs().
  */
 extern void add_packed_ref(const char *refname, const unsigned char *sha1);
 
+/*
+ * Write the current version of the packed refs cache from memory to
+ * disk.  The packed-refs file must already be locked for writing (see
+ * lock_packed_refs()).  Return zero on success.
+ */
+extern int commit_packed_refs(void);
+
+/*
+ * Rollback the lockfile for the packed-refs file, and discard the
+ * in-memory packed reference cache.  (The packed-refs file will be
+ * read anew if it is needed again after this function is called.)
+ */
+extern void rollback_packed_refs(void);
+
 /*
  * Flags for controlling behaviour of pack_refs()
  * PACK_REFS_PRUNE: Prune loose refs after packing
index f1bb731fd71eb874344faa5376e7c375ac2fcf7f..2f0142f22e21d7cfb1c74ca7035ee962e16dbd9e 100644 (file)
@@ -1296,7 +1296,7 @@ void init_revisions(struct rev_info *revs, const char *prefix)
        DIFF_OPT_SET(&revs->pruning, QUICK);
        revs->pruning.add_remove = file_add_remove;
        revs->pruning.change = file_change;
-       revs->lifo = 1;
+       revs->sort_order = REV_SORT_IN_GRAPH_ORDER;
        revs->dense = 1;
        revs->prefix = prefix;
        revs->max_age = -1;
@@ -1638,7 +1638,7 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
        } else if (!strcmp(arg, "--merge")) {
                revs->show_merge = 1;
        } else if (!strcmp(arg, "--topo-order")) {
-               revs->lifo = 1;
+               revs->sort_order = REV_SORT_IN_GRAPH_ORDER;
                revs->topo_order = 1;
        } else if (!strcmp(arg, "--simplify-merges")) {
                revs->simplify_merges = 1;
@@ -1656,7 +1656,10 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
                revs->prune = 1;
                load_ref_decorations(DECORATE_SHORT_REFS);
        } else if (!strcmp(arg, "--date-order")) {
-               revs->lifo = 0;
+               revs->sort_order = REV_SORT_BY_COMMIT_DATE;
+               revs->topo_order = 1;
+       } else if (!strcmp(arg, "--author-date-order")) {
+               revs->sort_order = REV_SORT_BY_AUTHOR_DATE;
                revs->topo_order = 1;
        } else if (!prefixcmp(arg, "--early-output")) {
                int count = 100;
@@ -2606,7 +2609,7 @@ int prepare_revision_walk(struct rev_info *revs)
                if (limit_list(revs) < 0)
                        return -1;
        if (revs->topo_order)
-               sort_in_topological_order(&revs->commits, revs->lifo);
+               sort_in_topological_order(&revs->commits, revs->sort_order);
        if (revs->line_level_traverse)
                line_log_filter(revs);
        if (revs->simplify_merges)
@@ -2924,7 +2927,7 @@ static void create_boundary_commit_list(struct rev_info *revs)
         * If revs->topo_order is set, sort the boundary commits
         * in topological order
         */
-       sort_in_topological_order(&revs->commits, revs->lifo);
+       sort_in_topological_order(&revs->commits, revs->sort_order);
 }
 
 static struct commit *get_revision_internal(struct rev_info *revs)
index eeea6fba3c5408d5567eec238d0a331bf64ddc04..92d6614af6da62160219b0df40d0caf56335ffce 100644 (file)
@@ -4,6 +4,7 @@
 #include "parse-options.h"
 #include "grep.h"
 #include "notes.h"
+#include "commit.h"
 
 #define SEEN           (1u<<0)
 #define UNINTERESTING   (1u<<1)
@@ -62,6 +63,10 @@ struct rev_info {
        const char *prefix;
        const char *def;
        struct pathspec prune_data;
+
+       /* topo-sort */
+       enum rev_sort_order sort_order;
+
        unsigned int    early_output:1,
                        ignore_missing:1;
 
@@ -72,7 +77,6 @@ struct rev_info {
                        show_all:1,
                        remove_empty_trees:1,
                        simplify_history:1,
-                       lifo:1,
                        topo_order:1,
                        simplify_merges:1,
                        simplify_by_decoration:1,
index f7be7d8be66992567fe2140c6aecdd3cc6140d38..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,
@@ -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
index ea25dd89e505525507c16c62d91fb07c092b064a..4ffd90127edb360142e492f6562c3c15c002d286 100644 (file)
@@ -1,55 +1,50 @@
 : included from 6002 and others
 
-[ -d .git/refs/tags ] || mkdir -p .git/refs/tags
+mkdir -p .git/refs/tags
 
-:> sed.script
+>sed.script
 
-# Answer the sha1 has associated with the tag. The tag must exist in .git or .git/refs/tags
-tag()
-{
+# Answer the sha1 has associated with the tag. The tag must exist in .git/refs/tags
+tag () {
        _tag=$1
-       [ -f .git/refs/tags/$_tag ] || error "tag: \"$_tag\" does not exist"
-       cat .git/refs/tags/$_tag
+       test -f ".git/refs/tags/$_tag" || error "tag: \"$_tag\" does not exist"
+       cat ".git/refs/tags/$_tag"
 }
 
 # Generate a commit using the text specified to make it unique and the tree
 # named by the tag specified.
-unique_commit()
-{
+unique_commit () {
        _text=$1
-        _tree=$2
+       _tree=$2
        shift 2
-       echo $_text | git commit-tree $(tag $_tree) "$@"
+       echo "$_text" | git commit-tree $(tag "$_tree") "$@"
 }
 
 # Save the output of a command into the tag specified. Prepend
 # a substitution script for the tag onto the front of sed.script
-save_tag()
-{
+save_tag () {
        _tag=$1
-       [ -n "$_tag" ] || error "usage: save_tag tag commit-args ..."
+       test -n "$_tag" || error "usage: save_tag tag commit-args ..."
        shift 1
-       "$@" >.git/refs/tags/$_tag
+       "$@" >".git/refs/tags/$_tag"
 
-        echo "s/$(tag $_tag)/$_tag/g" > sed.script.tmp
-       cat sed.script >> sed.script.tmp
+       echo "s/$(tag $_tag)/$_tag/g" >sed.script.tmp
+       cat sed.script >>sed.script.tmp
        rm sed.script
        mv sed.script.tmp sed.script
 }
 
 # Replace unhelpful sha1 hashses with their symbolic equivalents
-entag()
-{
+entag () {
        sed -f sed.script
 }
 
 # Execute a command after first saving, then setting the GIT_AUTHOR_EMAIL
 # tag to a specified value. Restore the original value on return.
-as_author()
-{
+as_author () {
        _author=$1
        shift 1
-        _save=$GIT_AUTHOR_EMAIL
+       _save=$GIT_AUTHOR_EMAIL
 
        GIT_AUTHOR_EMAIL="$_author"
        export GIT_AUTHOR_EMAIL
@@ -63,45 +58,58 @@ as_author()
        fi
 }
 
-commit_date()
-{
-        _commit=$1
-       git cat-file commit $_commit | sed -n "s/^committer .*> \([0-9]*\) .*/\1/p"
+commit_date () {
+       _commit=$1
+       git cat-file commit $_commit |
+       sed -n "s/^committer .*> \([0-9]*\) .*/\1/p"
 }
 
-on_committer_date()
-{
-    _date=$1
-    shift 1
-    GIT_COMMITTER_DATE="$_date"
-    export GIT_COMMITTER_DATE
-    "$@"
-    unset GIT_COMMITTER_DATE
+# Assign the value of fake date to a variable, but
+# allow fairly common "1971-08-16 00:00" to be omittd
+assign_fake_date () {
+       case "$2" in
+       ??:??:??)       eval "$1='1971-08-16 $2'" ;;
+       ??:??)          eval "$1='1971-08-16 00:$2'" ;;
+       ??)             eval "$1='1971-08-16 00:00:$2'" ;;
+       *)              eval "$1='$2'" ;;
+       esac
+}
+
+on_committer_date () {
+       assign_fake_date GIT_COMMITTER_DATE "$1"
+       export GIT_COMMITTER_DATE
+       shift 1
+       "$@"
+}
+
+on_dates () {
+       assign_fake_date GIT_COMMITTER_DATE "$1"
+       assign_fake_date GIT_AUTHOR_DATE "$2"
+       export GIT_COMMITTER_DATE GIT_AUTHOR_DATE
+       shift 2
+       "$@"
 }
 
 # Execute a command and suppress any error output.
-hide_error()
-{
+hide_error () {
        "$@" 2>/dev/null
 }
 
-check_output()
-{
+check_output () {
        _name=$1
        shift 1
-       if eval "$*" | entag > $_name.actual
+       if eval "$*" | entag >"$_name.actual"
        then
-               test_cmp $_name.expected $_name.actual
+               test_cmp "$_name.expected" "$_name.actual"
        else
-               return 1;
+               return 1
        fi
 }
 
 # Turn a reasonable test description into a reasonable test name.
 # All alphanums translated into -'s which are then compressed and stripped
 # from front and back.
-name_from_description()
-{
+name_from_description () {
        perl -pe '
                s/[^A-Za-z0-9.]/-/g;
                s/-+/-/g;
@@ -119,9 +127,11 @@ name_from_description()
 test_output_expect_success()
 {
        _description=$1
-        _test=$2
-        [ $# -eq 2 ] || error "usage: test_output_expect_success description test <<EOF ... EOF"
-        _name=$(echo $_description | name_from_description)
-       cat > $_name.expected
+       _test=$2
+       test $# -eq 2 ||
+       error "usage: test_output_expect_success description test <<EOF ... EOF"
+
+       _name=$(echo $_description | name_from_description)
+       cat >"$_name.expected"
        test_expect_success "$_description" "check_output $_name \"$_test\""
 }
index c552f561bffd9800d53064772ea640677b20a3ae..8848c1461909199a5c0bd587df8d9ef652c650ac 100644 (file)
@@ -66,7 +66,7 @@ You can set the following variables (also in your config.mak):
     GIT_PERF_LARGE_REPO
        Repositories to copy for the performance tests.  The normal
        repo should be at least git.git size.  The large repo should
-       probably be about linux-2.6.git size for optimal results.
+       probably be about linux.git size for optimal results.
        Both default to the git.git you are running from.
 
 You can also pass the options taken by ordinary git tests; the most
diff --git a/t/t0009-prio-queue.sh b/t/t0009-prio-queue.sh
new file mode 100755 (executable)
index 0000000..94045c3
--- /dev/null
@@ -0,0 +1,50 @@
+#!/bin/sh
+
+test_description='basic tests for priority queue implementation'
+. ./test-lib.sh
+
+cat >expect <<'EOF'
+1
+2
+3
+4
+5
+5
+6
+7
+8
+9
+10
+EOF
+test_expect_success 'basic ordering' '
+       test-prio-queue 2 6 3 10 9 5 7 4 5 8 1 dump >actual &&
+       test_cmp expect actual
+'
+
+cat >expect <<'EOF'
+2
+3
+4
+1
+5
+6
+EOF
+test_expect_success 'mixed put and get' '
+       test-prio-queue 6 2 4 get 5 3 get get 1 dump >actual &&
+       test_cmp expect actual
+'
+
+cat >expect <<'EOF'
+1
+2
+NULL
+1
+2
+NULL
+EOF
+test_expect_success 'notice empty queue' '
+       test-prio-queue 1 2 get get get 1 2 get get get >actual &&
+       test_cmp expect actual
+'
+
+test_done
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 634b2b74f49558781f612e3044d99cbc6fa3e657..debda7a678277e1198ba6620210b66984caf03a2 100755 (executable)
@@ -654,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 cc3db1304ef202a376e84360986568bb7408c701..3fb4b976a277e69cdbf73172caf335748be609a3 100755 (executable)
@@ -142,6 +142,351 @@ EOF
 git diff --ignore-space-at-eol > out
 test_expect_success 'another test, with --ignore-space-at-eol' 'test_cmp expect out'
 
+test_expect_success 'ignore-blank-lines: only new lines' '
+       test_seq 5 >x &&
+       git update-index x &&
+       test_seq 5 | sed "/3/i \\
+" >x &&
+       git diff --ignore-blank-lines >out &&
+       >expect &&
+       test_cmp out expect
+'
+
+test_expect_success 'ignore-blank-lines: only new lines with space' '
+       test_seq 5 >x &&
+       git update-index x &&
+       test_seq 5 | sed "/3/i \ " >x &&
+       git diff -w --ignore-blank-lines >out &&
+       >expect &&
+       test_cmp out expect
+'
+
+test_expect_success 'ignore-blank-lines: after change' '
+       cat <<-\EOF >x &&
+       1
+       2
+
+       3
+       4
+       5
+
+       6
+       7
+       EOF
+       git update-index x &&
+       cat <<-\EOF >x &&
+       change
+
+       1
+       2
+       3
+       4
+       5
+       6
+
+       7
+       EOF
+       git diff --inter-hunk-context=100 --ignore-blank-lines >out.tmp &&
+       cat <<-\EOF >expected &&
+       diff --git a/x b/x
+       --- a/x
+       +++ b/x
+       @@ -1,6 +1,7 @@
+       +change
+       +
+        1
+        2
+       -
+        3
+        4
+        5
+       EOF
+       compare_diff_patch expected out.tmp
+'
+
+test_expect_success 'ignore-blank-lines: before change' '
+       cat <<-\EOF >x &&
+       1
+       2
+
+       3
+       4
+       5
+       6
+       7
+       EOF
+       git update-index x &&
+       cat <<-\EOF >x &&
+
+       1
+       2
+       3
+       4
+       5
+
+       6
+       7
+       change
+       EOF
+       git diff --inter-hunk-context=100 --ignore-blank-lines >out.tmp &&
+       cat <<-\EOF >expected &&
+       diff --git a/x b/x
+       --- a/x
+       +++ b/x
+       @@ -4,5 +4,7 @@
+        3
+        4
+        5
+       +
+        6
+        7
+       +change
+       EOF
+       compare_diff_patch expected out.tmp
+'
+
+test_expect_success 'ignore-blank-lines: between changes' '
+       cat <<-\EOF >x &&
+       1
+       2
+       3
+       4
+       5
+
+
+       6
+       7
+       8
+       9
+       10
+       EOF
+       git update-index x &&
+       cat <<-\EOF >x &&
+       change
+       1
+       2
+
+       3
+       4
+       5
+       6
+       7
+       8
+
+       9
+       10
+       change
+       EOF
+       git diff --ignore-blank-lines >out.tmp &&
+       cat <<-\EOF >expected &&
+       diff --git a/x b/x
+       --- a/x
+       +++ b/x
+       @@ -1,5 +1,7 @@
+       +change
+        1
+        2
+       +
+        3
+        4
+        5
+       @@ -8,5 +8,7 @@
+        6
+        7
+        8
+       +
+        9
+        10
+       +change
+       EOF
+       compare_diff_patch expected out.tmp
+'
+
+test_expect_success 'ignore-blank-lines: between changes (with interhunkctx)' '
+       test_seq 10 >x &&
+       git update-index x &&
+       cat <<-\EOF >x &&
+       change
+       1
+       2
+
+       3
+       4
+       5
+
+       6
+       7
+       8
+       9
+
+       10
+       change
+       EOF
+       git diff --inter-hunk-context=2 --ignore-blank-lines >out.tmp &&
+       cat <<-\EOF >expected &&
+       diff --git a/x b/x
+       --- a/x
+       +++ b/x
+       @@ -1,10 +1,15 @@
+       +change
+        1
+        2
+       +
+        3
+        4
+        5
+       +
+        6
+        7
+        8
+        9
+       +
+        10
+       +change
+       EOF
+       compare_diff_patch expected out.tmp
+'
+
+test_expect_success 'ignore-blank-lines: scattered spaces' '
+       test_seq 10 >x &&
+       git update-index x &&
+       cat <<-\EOF >x &&
+       change
+       1
+       2
+       3
+
+       4
+
+       5
+
+       6
+
+
+       7
+
+       8
+       9
+       10
+       change
+       EOF
+       git diff --inter-hunk-context=4 --ignore-blank-lines >out.tmp &&
+       cat <<-\EOF >expected &&
+       diff --git a/x b/x
+       --- a/x
+       +++ b/x
+       @@ -1,3 +1,4 @@
+       +change
+        1
+        2
+        3
+       @@ -8,3 +15,4 @@
+        8
+        9
+        10
+       +change
+       EOF
+       compare_diff_patch expected out.tmp
+'
+
+test_expect_success 'ignore-blank-lines: spaces coalesce' '
+       test_seq 6 >x &&
+       git update-index x &&
+       cat <<-\EOF >x &&
+       change
+       1
+       2
+       3
+
+       4
+
+       5
+
+       6
+       change
+       EOF
+       git diff --inter-hunk-context=4 --ignore-blank-lines >out.tmp &&
+       cat <<-\EOF >expected &&
+       diff --git a/x b/x
+       --- a/x
+       +++ b/x
+       @@ -1,6 +1,11 @@
+       +change
+        1
+        2
+        3
+       +
+        4
+       +
+        5
+       +
+        6
+       +change
+       EOF
+       compare_diff_patch expected out.tmp
+'
+
+test_expect_success 'ignore-blank-lines: mix changes and blank lines' '
+       test_seq 16 >x &&
+       git update-index x &&
+       cat <<-\EOF >x &&
+       change
+       1
+       2
+
+       3
+       4
+       5
+       change
+       6
+       7
+       8
+
+       9
+       10
+       11
+       change
+       12
+       13
+       14
+
+       15
+       16
+       change
+       EOF
+       git diff --ignore-blank-lines >out.tmp &&
+       cat <<-\EOF >expected &&
+       diff --git a/x b/x
+       --- a/x
+       +++ b/x
+       @@ -1,8 +1,11 @@
+       +change
+        1
+        2
+       +
+        3
+        4
+        5
+       +change
+        6
+        7
+        8
+       @@ -9,8 +13,11 @@
+        9
+        10
+        11
+       +change
+        12
+        13
+        14
+       +
+        15
+        16
+       +change
+       EOF
+       compare_diff_patch expected out.tmp
+'
+
 test_expect_success 'check mixed spaces and tabs in indent' '
 
        # This is indented with SP HT SP.
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 fb07536a0f5b7e1000cf4945f55be3305df88e90..43ad7724845cd0e1523aadd60845f048a6b92ce5 100755 (executable)
@@ -39,25 +39,25 @@ test_bisection_diff()
 date >path0
 git update-index --add path0
 save_tag tree git write-tree
-on_committer_date "1971-08-16 00:00:00" hide_error save_tag root unique_commit root tree
-on_committer_date "1971-08-16 00:00:01" save_tag l0 unique_commit l0 tree -p root
-on_committer_date "1971-08-16 00:00:02" save_tag l1 unique_commit l1 tree -p l0
-on_committer_date "1971-08-16 00:00:03" save_tag l2 unique_commit l2 tree -p l1
-on_committer_date "1971-08-16 00:00:04" save_tag a0 unique_commit a0 tree -p l2
-on_committer_date "1971-08-16 00:00:05" save_tag a1 unique_commit a1 tree -p a0
-on_committer_date "1971-08-16 00:00:06" save_tag b1 unique_commit b1 tree -p a0
-on_committer_date "1971-08-16 00:00:07" save_tag c1 unique_commit c1 tree -p b1
-on_committer_date "1971-08-16 00:00:08" save_tag b2 unique_commit b2 tree -p b1
-on_committer_date "1971-08-16 00:00:09" save_tag b3 unique_commit b2 tree -p b2
-on_committer_date "1971-08-16 00:00:10" save_tag c2 unique_commit c2 tree -p c1 -p b2
-on_committer_date "1971-08-16 00:00:11" save_tag c3 unique_commit c3 tree -p c2
-on_committer_date "1971-08-16 00:00:12" save_tag a2 unique_commit a2 tree -p a1
-on_committer_date "1971-08-16 00:00:13" save_tag a3 unique_commit a3 tree -p a2
-on_committer_date "1971-08-16 00:00:14" save_tag b4 unique_commit b4 tree -p b3 -p a3
-on_committer_date "1971-08-16 00:00:15" save_tag a4 unique_commit a4 tree -p a3 -p b4 -p c3
-on_committer_date "1971-08-16 00:00:16" save_tag l3 unique_commit l3 tree -p a4
-on_committer_date "1971-08-16 00:00:17" save_tag l4 unique_commit l4 tree -p l3
-on_committer_date "1971-08-16 00:00:18" save_tag l5 unique_commit l5 tree -p l4
+on_committer_date "00:00" hide_error save_tag root unique_commit root tree
+on_committer_date "00:01" save_tag l0 unique_commit l0 tree -p root
+on_committer_date "00:02" save_tag l1 unique_commit l1 tree -p l0
+on_committer_date "00:03" save_tag l2 unique_commit l2 tree -p l1
+on_committer_date "00:04" save_tag a0 unique_commit a0 tree -p l2
+on_committer_date "00:05" save_tag a1 unique_commit a1 tree -p a0
+on_committer_date "00:06" save_tag b1 unique_commit b1 tree -p a0
+on_committer_date "00:07" save_tag c1 unique_commit c1 tree -p b1
+on_committer_date "00:08" save_tag b2 unique_commit b2 tree -p b1
+on_committer_date "00:09" save_tag b3 unique_commit b2 tree -p b2
+on_committer_date "00:10" save_tag c2 unique_commit c2 tree -p c1 -p b2
+on_committer_date "00:11" save_tag c3 unique_commit c3 tree -p c2
+on_committer_date "00:12" save_tag a2 unique_commit a2 tree -p a1
+on_committer_date "00:13" save_tag a3 unique_commit a3 tree -p a2
+on_committer_date "00:14" save_tag b4 unique_commit b4 tree -p b3 -p a3
+on_committer_date "00:15" save_tag a4 unique_commit a4 tree -p a3 -p b4 -p c3
+on_committer_date "00:16" save_tag l3 unique_commit l3 tree -p a4
+on_committer_date "00:17" save_tag l4 unique_commit l4 tree -p l3
+on_committer_date "00:18" save_tag l5 unique_commit l5 tree -p l4
 git update-ref HEAD $(tag l5)
 
 
@@ -90,29 +90,29 @@ git update-ref HEAD $(tag l5)
 #     F
 
 
-on_committer_date "1971-08-16 00:00:00" hide_error save_tag F unique_commit F tree
-on_committer_date "1971-08-16 00:00:01" save_tag e8 unique_commit e8 tree -p F
-on_committer_date "1971-08-16 00:00:02" save_tag e7 unique_commit e7 tree -p e8
-on_committer_date "1971-08-16 00:00:03" save_tag e6 unique_commit e6 tree -p e7
-on_committer_date "1971-08-16 00:00:04" save_tag e5 unique_commit e5 tree -p e6
-on_committer_date "1971-08-16 00:00:05" save_tag f4 unique_commit f4 tree -p F
-on_committer_date "1971-08-16 00:00:06" save_tag f3 unique_commit f3 tree -p f4
-on_committer_date "1971-08-16 00:00:07" save_tag f2 unique_commit f2 tree -p f3
-on_committer_date "1971-08-16 00:00:08" save_tag f1 unique_commit f1 tree -p f2
-on_committer_date "1971-08-16 00:00:09" save_tag e4 unique_commit e4 tree -p e5
-on_committer_date "1971-08-16 00:00:10" save_tag e3 unique_commit e3 tree -p e4
-on_committer_date "1971-08-16 00:00:11" save_tag e2 unique_commit e2 tree -p e3
-on_committer_date "1971-08-16 00:00:12" save_tag e1 unique_commit e1 tree -p e2
-on_committer_date "1971-08-16 00:00:13" save_tag E unique_commit E tree -p e1 -p f1
-
-on_committer_date "1971-08-16 00:00:00" hide_error save_tag U unique_commit U tree
-on_committer_date "1971-08-16 00:00:01" save_tag u0 unique_commit u0 tree -p U
-on_committer_date "1971-08-16 00:00:01" save_tag u1 unique_commit u1 tree -p u0
-on_committer_date "1971-08-16 00:00:02" save_tag u2 unique_commit u2 tree -p u0
-on_committer_date "1971-08-16 00:00:03" save_tag u3 unique_commit u3 tree -p u0
-on_committer_date "1971-08-16 00:00:04" save_tag u4 unique_commit u4 tree -p u0
-on_committer_date "1971-08-16 00:00:05" save_tag u5 unique_commit u5 tree -p u0
-on_committer_date "1971-08-16 00:00:06" save_tag V unique_commit V tree -p u1 -p u2 -p u3 -p u4 -p u5
+on_committer_date "00:00" hide_error save_tag F unique_commit F tree
+on_committer_date "00:01" save_tag e8 unique_commit e8 tree -p F
+on_committer_date "00:02" save_tag e7 unique_commit e7 tree -p e8
+on_committer_date "00:03" save_tag e6 unique_commit e6 tree -p e7
+on_committer_date "00:04" save_tag e5 unique_commit e5 tree -p e6
+on_committer_date "00:05" save_tag f4 unique_commit f4 tree -p F
+on_committer_date "00:06" save_tag f3 unique_commit f3 tree -p f4
+on_committer_date "00:07" save_tag f2 unique_commit f2 tree -p f3
+on_committer_date "00:08" save_tag f1 unique_commit f1 tree -p f2
+on_committer_date "00:09" save_tag e4 unique_commit e4 tree -p e5
+on_committer_date "00:10" save_tag e3 unique_commit e3 tree -p e4
+on_committer_date "00:11" save_tag e2 unique_commit e2 tree -p e3
+on_committer_date "00:12" save_tag e1 unique_commit e1 tree -p e2
+on_committer_date "00:13" save_tag E unique_commit E tree -p e1 -p f1
+
+on_committer_date "00:00" hide_error save_tag U unique_commit U tree
+on_committer_date "00:01" save_tag u0 unique_commit u0 tree -p U
+on_committer_date "00:01" save_tag u1 unique_commit u1 tree -p u0
+on_committer_date "00:02" save_tag u2 unique_commit u2 tree -p u0
+on_committer_date "00:03" save_tag u3 unique_commit u3 tree -p u0
+on_committer_date "00:04" save_tag u4 unique_commit u4 tree -p u0
+on_committer_date "00:05" save_tag u5 unique_commit u5 tree -p u0
+on_committer_date "00:06" save_tag V unique_commit V tree -p u1 -p u2 -p u3 -p u4 -p u5
 
 test_sequence()
 {
index e4c52b0214b5028e8c3db035dc96ca3285e61506..24d1836f417d67a224f0d9bef619952a39ed28eb 100755 (executable)
@@ -16,39 +16,34 @@ list_duplicates()
 date >path0
 git update-index --add path0
 save_tag tree git write-tree
-on_committer_date "1971-08-16 00:00:00" hide_error save_tag root unique_commit root tree
-on_committer_date "1971-08-16 00:00:01" save_tag l0 unique_commit l0 tree -p root
-on_committer_date "1971-08-16 00:00:02" save_tag l1 unique_commit l1 tree -p l0
-on_committer_date "1971-08-16 00:00:03" save_tag l2 unique_commit l2 tree -p l1
-on_committer_date "1971-08-16 00:00:04" save_tag a0 unique_commit a0 tree -p l2
-on_committer_date "1971-08-16 00:00:05" save_tag a1 unique_commit a1 tree -p a0
-on_committer_date "1971-08-16 00:00:06" save_tag b1 unique_commit b1 tree -p a0
-on_committer_date "1971-08-16 00:00:07" save_tag c1 unique_commit c1 tree -p b1
-on_committer_date "1971-08-16 00:00:08" as_author foobar@example.com save_tag b2 unique_commit b2 tree -p b1
-on_committer_date "1971-08-16 00:00:09" save_tag b3 unique_commit b3 tree -p b2
-on_committer_date "1971-08-16 00:00:10" save_tag c2 unique_commit c2 tree -p c1 -p b2
-on_committer_date "1971-08-16 00:00:11" save_tag c3 unique_commit c3 tree -p c2
-on_committer_date "1971-08-16 00:00:12" save_tag a2 unique_commit a2 tree -p a1
-on_committer_date "1971-08-16 00:00:13" save_tag a3 unique_commit a3 tree -p a2
-on_committer_date "1971-08-16 00:00:14" save_tag b4 unique_commit b4 tree -p b3 -p a3
-on_committer_date "1971-08-16 00:00:15" save_tag a4 unique_commit a4 tree -p a3 -p b4 -p c3
-on_committer_date "1971-08-16 00:00:16" save_tag l3 unique_commit l3 tree -p a4
-on_committer_date "1971-08-16 00:00:17" save_tag l4 unique_commit l4 tree -p l3
-on_committer_date "1971-08-16 00:00:18" save_tag l5 unique_commit l5 tree -p l4
-on_committer_date "1971-08-16 00:00:19" save_tag m1 unique_commit m1 tree -p a4 -p c3
-on_committer_date "1971-08-16 00:00:20" save_tag m2 unique_commit m2 tree -p c3 -p a4
-on_committer_date "1971-08-16 00:00:21" hide_error save_tag alt_root unique_commit alt_root tree
-on_committer_date "1971-08-16 00:00:22" save_tag r0 unique_commit r0 tree -p alt_root
-on_committer_date "1971-08-16 00:00:23" save_tag r1 unique_commit r1 tree -p r0
-on_committer_date "1971-08-16 00:00:24" save_tag l5r1 unique_commit l5r1 tree -p l5 -p r1
-on_committer_date "1971-08-16 00:00:25" save_tag r1l5 unique_commit r1l5 tree -p r1 -p l5
+on_dates "00:00" "00:00" hide_error save_tag root unique_commit root tree
+on_dates "00:01" "00:01" save_tag l0 unique_commit l0 tree -p root
+on_dates "00:02" "00:02" save_tag l1 unique_commit l1 tree -p l0
+on_dates "00:03" "00:03" save_tag l2 unique_commit l2 tree -p l1
+on_dates "00:04" "00:04" save_tag a0 unique_commit a0 tree -p l2
+on_dates "00:05" "00:05" save_tag a1 unique_commit a1 tree -p a0
+on_dates "00:06" "00:06" save_tag b1 unique_commit b1 tree -p a0
+on_dates "00:07" "00:07" save_tag c1 unique_commit c1 tree -p b1
+on_dates "00:08" "00:08" as_author foobar@example.com save_tag b2 unique_commit b2 tree -p b1
+on_dates "00:09" "00:09" save_tag b3 unique_commit b3 tree -p b2
+on_dates "00:10" "00:10" save_tag c2 unique_commit c2 tree -p c1 -p b2
+on_dates "00:11" "00:11" save_tag c3 unique_commit c3 tree -p c2
+on_dates "00:12" "00:00" save_tag a2 unique_commit a2 tree -p a1
+on_dates "00:13" "00:01" save_tag a3 unique_commit a3 tree -p a2
+on_dates "00:14" "00:14" save_tag b4 unique_commit b4 tree -p b3 -p a3
+on_dates "00:15" "00:15" save_tag a4 unique_commit a4 tree -p a3 -p b4 -p c3
+on_dates "00:16" "00:16" save_tag l3 unique_commit l3 tree -p a4
+on_dates "00:17" "00:17" save_tag l4 unique_commit l4 tree -p l3
+on_dates "00:18" "00:18" save_tag l5 unique_commit l5 tree -p l4
+on_dates "00:19" "00:19" save_tag m1 unique_commit m1 tree -p a4 -p c3
+on_dates "00:20" "00:20" save_tag m2 unique_commit m2 tree -p c3 -p a4
+on_dates "00:21" "00:21" hide_error save_tag alt_root unique_commit alt_root tree
+on_dates "00:22" "00:22" save_tag r0 unique_commit r0 tree -p alt_root
+on_dates "00:23" "00:23" save_tag r1 unique_commit r1 tree -p r0
+on_dates "00:24" "00:24" save_tag l5r1 unique_commit l5r1 tree -p l5 -p r1
+on_dates "00:25" "00:25" save_tag r1l5 unique_commit r1l5 tree -p r1 -p l5
 
 
-#
-# note: as of 20/6, it isn't possible to create duplicate parents, so this
-# can't be tested.
-#
-#on_committer_date "1971-08-16 00:00:20" save_tag m3 unique_commit m3 tree -p c3 -p a4 -p c3
 hide_error save_tag e1 as_author e@example.com unique_commit e1 tree
 save_tag e2 as_author e@example.com unique_commit e2 tree -p e1
 save_tag f1 as_author f@example.com unique_commit f1 tree -p e1
@@ -105,6 +100,50 @@ l0
 root
 EOF
 
+test_output_expect_success 'simple date order' 'git rev-list --date-order  HEAD' <<EOF
+l5
+l4
+l3
+a4
+b4
+a3
+a2
+c3
+c2
+b3
+b2
+c1
+b1
+a1
+a0
+l2
+l1
+l0
+root
+EOF
+
+test_output_expect_success 'simple author-date order' 'git rev-list --author-date-order  HEAD' <<EOF
+l5
+l4
+l3
+a4
+b4
+c3
+c2
+b3
+b2
+c1
+b1
+a3
+a2
+a1
+a0
+l2
+l1
+l0
+root
+EOF
+
 test_output_expect_success 'two diamonds topo order (g6)' 'git rev-list --topo-order  g4' <<EOF
 g4
 h2
index 8f3b54d826f5a9c8e7b08df7f6a58398b5a62480..88d60c1ce2933141c0800a76fdebca9037803258 100755 (executable)
@@ -91,12 +91,12 @@ test_expect_success 'update-index --remove' '
        test_cmp expected 1
 '
 
-test_expect_success 'ls-files --delete' '
+test_expect_success 'ls-files --deleted' '
        setup_absent &&
        test -z "$(git ls-files -d)"
 '
 
-test_expect_success 'ls-files --delete' '
+test_expect_success 'ls-files --deleted' '
        setup_dirty &&
        test -z "$(git ls-files -d)"
 '
index 71a42f00865944339187995bee63bab508f03b9c..50e6ad7458c48842408e2afc20f84c117120c262 100755 (executable)
@@ -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
@@ -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 &&
@@ -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 &&
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 9f46f22ca87be40e217c2ffb199368f0afbf9548..2813aa9a61dc52d6d72f684c51fe6894e623754d 100755 (executable)
@@ -956,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 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 81a1657efb18e88a448886aed60bb7e5fdb36212..5469dee8d1f39e6cae4205501a774d36913fff73 100755 (executable)
@@ -122,6 +122,140 @@ test_gitcomp_nl ()
 
 invalid_variable_name='${foo.bar}'
 
+actual="$TRASH_DIRECTORY/actual"
+
+test_expect_success 'setup for __gitdir tests' '
+       mkdir -p subdir/subsubdir &&
+       git init otherrepo
+'
+
+test_expect_success '__gitdir - from command line (through $__git_dir)' '
+       echo "$TRASH_DIRECTORY/otherrepo/.git" >expected &&
+       (
+               __git_dir="$TRASH_DIRECTORY/otherrepo/.git" &&
+               __gitdir >"$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success '__gitdir - repo as argument' '
+       echo "otherrepo/.git" >expected &&
+       __gitdir "otherrepo" >"$actual" &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success '__gitdir - remote as argument' '
+       echo "remote" >expected &&
+       __gitdir "remote" >"$actual" &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success '__gitdir - .git directory in cwd' '
+       echo ".git" >expected &&
+       __gitdir >"$actual" &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success '__gitdir - .git directory in parent' '
+       echo "$(pwd -P)/.git" >expected &&
+       (
+               cd subdir/subsubdir &&
+               __gitdir >"$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success '__gitdir - cwd is a .git directory' '
+       echo "." >expected &&
+       (
+               cd .git &&
+               __gitdir >"$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success '__gitdir - parent is a .git directory' '
+       echo "$(pwd -P)/.git" >expected &&
+       (
+               cd .git/refs/heads &&
+               __gitdir >"$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success '__gitdir - $GIT_DIR set while .git directory in cwd' '
+       echo "$TRASH_DIRECTORY/otherrepo/.git" >expected &&
+       (
+               GIT_DIR="$TRASH_DIRECTORY/otherrepo/.git" &&
+               export GIT_DIR &&
+               __gitdir >"$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success '__gitdir - $GIT_DIR set while .git directory in parent' '
+       echo "$TRASH_DIRECTORY/otherrepo/.git" >expected &&
+       (
+               GIT_DIR="$TRASH_DIRECTORY/otherrepo/.git" &&
+               export GIT_DIR &&
+               cd subdir &&
+               __gitdir >"$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success '__gitdir - non-existing $GIT_DIR' '
+       (
+               GIT_DIR="$TRASH_DIRECTORY/non-existing" &&
+               export GIT_DIR &&
+               test_must_fail __gitdir
+       )
+'
+
+test_expect_success '__gitdir - gitfile in cwd' '
+       echo "$(pwd -P)/otherrepo/.git" >expected &&
+       echo "gitdir: $TRASH_DIRECTORY/otherrepo/.git" >subdir/.git &&
+       test_when_finished "rm -f subdir/.git" &&
+       (
+               cd subdir &&
+               __gitdir >"$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success '__gitdir - gitfile in parent' '
+       echo "$(pwd -P)/otherrepo/.git" >expected &&
+       echo "gitdir: $TRASH_DIRECTORY/otherrepo/.git" >subdir/.git &&
+       test_when_finished "rm -f subdir/.git" &&
+       (
+               cd subdir/subsubdir &&
+               __gitdir >"$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success SYMLINKS '__gitdir - resulting path avoids symlinks' '
+       echo "$(pwd -P)/otherrepo/.git" >expected &&
+       mkdir otherrepo/dir &&
+       test_when_finished "rm -rf otherrepo/dir" &&
+       ln -s otherrepo/dir link &&
+       test_when_finished "rm -f link" &&
+       (
+               cd link &&
+               __gitdir >"$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success '__gitdir - not a git repository' '
+       (
+               cd subdir/subsubdir &&
+               GIT_CEILING_DIRECTORIES="$TRASH_DIRECTORY" &&
+               export GIT_CEILING_DIRECTORIES &&
+               test_must_fail __gitdir
+       )
+'
+
 test_expect_success '__gitcomp - trailing space - options' '
        test_gitcomp "--re" "--dry-run --reuse-message= --reedit-message=
                --reset-author" <<-EOF
index 15521cc4f9be68ccd105916cee1322041682e8d5..c05458cbe671b4881af3596d9b06a120674519d7 100755 (executable)
@@ -12,526 +12,415 @@ test_description='test git-specific bash prompt functions'
 actual="$TRASH_DIRECTORY/actual"
 
 test_expect_success 'setup for prompt tests' '
-       mkdir -p subdir/subsubdir &&
        git init otherrepo &&
-       echo 1 > file &&
+       echo 1 >file &&
        git add file &&
        test_tick &&
        git commit -m initial &&
        git tag -a -m msg1 t1 &&
        git checkout -b b1 &&
-       echo 2 > file &&
+       echo 2 >file &&
        git commit -m "second b1" file &&
-       echo 3 > file &&
+       echo 3 >file &&
        git commit -m "third b1" file &&
        git tag -a -m msg2 t2 &&
        git checkout -b b2 master &&
-       echo 0 > file &&
+       echo 0 >file &&
        git commit -m "second b2" file &&
-       echo 00 > file &&
+       echo 00 >file &&
        git commit -m "another b2" file &&
-       echo 000 > file &&
+       echo 000 >file &&
        git commit -m "yet another b2" file &&
        git checkout master
 '
 
-test_expect_success 'gitdir - from command line (through $__git_dir)' '
-       echo "$TRASH_DIRECTORY/otherrepo/.git" > expected &&
-       (
-               __git_dir="$TRASH_DIRECTORY/otherrepo/.git" &&
-               __gitdir > "$actual"
-       ) &&
-       test_cmp expected "$actual"
-'
-
-test_expect_success 'gitdir - repo as argument' '
-       echo "otherrepo/.git" > expected &&
-       __gitdir "otherrepo" > "$actual" &&
-       test_cmp expected "$actual"
-'
-
-test_expect_success 'gitdir - remote as argument' '
-       echo "remote" > expected &&
-       __gitdir "remote" > "$actual" &&
-       test_cmp expected "$actual"
-'
-
-test_expect_success 'gitdir - .git directory in cwd' '
-       echo ".git" > expected &&
-       __gitdir > "$actual" &&
-       test_cmp expected "$actual"
-'
-
-test_expect_success 'gitdir - .git directory in parent' '
-       echo "$(pwd -P)/.git" > expected &&
-       (
-               cd subdir/subsubdir &&
-               __gitdir > "$actual"
-       ) &&
-       test_cmp expected "$actual"
-'
-
-test_expect_success 'gitdir - cwd is a .git directory' '
-       echo "." > expected &&
-       (
-               cd .git &&
-               __gitdir > "$actual"
-       ) &&
-       test_cmp expected "$actual"
-'
-
-test_expect_success 'gitdir - parent is a .git directory' '
-       echo "$(pwd -P)/.git" > expected &&
-       (
-               cd .git/refs/heads &&
-               __gitdir > "$actual"
-       ) &&
-       test_cmp expected "$actual"
-'
-
-test_expect_success 'gitdir - $GIT_DIR set while .git directory in cwd' '
-       echo "$TRASH_DIRECTORY/otherrepo/.git" > expected &&
-       (
-               GIT_DIR="$TRASH_DIRECTORY/otherrepo/.git" &&
-               export GIT_DIR &&
-               __gitdir > "$actual"
-       ) &&
-       test_cmp expected "$actual"
-'
-
-test_expect_success 'gitdir - $GIT_DIR set while .git directory in parent' '
-       echo "$TRASH_DIRECTORY/otherrepo/.git" > expected &&
-       (
-               GIT_DIR="$TRASH_DIRECTORY/otherrepo/.git" &&
-               export GIT_DIR &&
-               cd subdir &&
-               __gitdir > "$actual"
-       ) &&
-       test_cmp expected "$actual"
-'
-
-test_expect_success 'gitdir - non-existing $GIT_DIR' '
-       (
-               GIT_DIR="$TRASH_DIRECTORY/non-existing" &&
-               export GIT_DIR &&
-               test_must_fail __gitdir
-       )
-'
-
-test_expect_success 'gitdir - gitfile in cwd' '
-       echo "$(pwd -P)/otherrepo/.git" > expected &&
-       echo "gitdir: $TRASH_DIRECTORY/otherrepo/.git" > subdir/.git &&
-       test_when_finished "rm -f subdir/.git" &&
-       (
-               cd subdir &&
-               __gitdir > "$actual"
-       ) &&
-       test_cmp expected "$actual"
-'
-
-test_expect_success 'gitdir - gitfile in parent' '
-       echo "$(pwd -P)/otherrepo/.git" > expected &&
-       echo "gitdir: $TRASH_DIRECTORY/otherrepo/.git" > subdir/.git &&
-       test_when_finished "rm -f subdir/.git" &&
-       (
-               cd subdir/subsubdir &&
-               __gitdir > "$actual"
-       ) &&
+test_expect_success 'prompt - branch name' '
+       printf " (master)" >expected &&
+       __git_ps1 >"$actual" &&
        test_cmp expected "$actual"
 '
 
-test_expect_success SYMLINKS 'gitdir - resulting path avoids symlinks' '
-       echo "$(pwd -P)/otherrepo/.git" > expected &&
-       mkdir otherrepo/dir &&
-       test_when_finished "rm -rf otherrepo/dir" &&
-       ln -s otherrepo/dir link &&
-       test_when_finished "rm -f link" &&
-       (
-               cd link &&
-               __gitdir > "$actual"
-       ) &&
+test_expect_success SYMLINKS 'prompt - branch name - symlink symref' '
+       printf " (master)" >expected &&
+       test_when_finished "git checkout master" &&
+       test_config core.preferSymlinkRefs true &&
+       git checkout master &&
+       __git_ps1 >"$actual" &&
        test_cmp expected "$actual"
 '
 
-test_expect_success 'gitdir - not a git repository' '
-       (
-               cd subdir/subsubdir &&
-               GIT_CEILING_DIRECTORIES="$TRASH_DIRECTORY" &&
-               export GIT_CEILING_DIRECTORIES &&
-               test_must_fail __gitdir
-       )
-'
-
-test_expect_success 'prompt - branch name' '
-       printf " (master)" > expected &&
-       __git_ps1 > "$actual" &&
+test_expect_success 'prompt - unborn branch' '
+       printf " (unborn)" >expected &&
+       git checkout --orphan unborn &&
+       test_when_finished "git checkout master" &&
+       __git_ps1 >"$actual" &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - detached head' '
-       printf " ((%s...))" $(git log -1 --format="%h" b1^) > expected &&
+       printf " ((%s...))" $(git log -1 --format="%h" --abbrev=13 b1^) >expected &&
+       test_config core.abbrev 13 &&
        git checkout b1^ &&
        test_when_finished "git checkout master" &&
-       __git_ps1 > "$actual" &&
+       __git_ps1 >"$actual" &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - describe detached head - contains' '
-       printf " ((t2~1))" > expected &&
+       printf " ((t2~1))" >expected &&
        git checkout b1^ &&
        test_when_finished "git checkout master" &&
        (
                GIT_PS1_DESCRIBE_STYLE=contains &&
-               __git_ps1 > "$actual"
+               __git_ps1 >"$actual"
        ) &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - describe detached head - branch' '
-       printf " ((b1~1))" > expected &&
+       printf " ((b1~1))" >expected &&
        git checkout b1^ &&
        test_when_finished "git checkout master" &&
        (
                GIT_PS1_DESCRIBE_STYLE=branch &&
-               __git_ps1 > "$actual"
+               __git_ps1 >"$actual"
        ) &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - describe detached head - describe' '
-       printf " ((t1-1-g%s))" $(git log -1 --format="%h" b1^) > expected &&
+       printf " ((t1-1-g%s))" $(git log -1 --format="%h" b1^) >expected &&
        git checkout b1^ &&
        test_when_finished "git checkout master" &&
        (
                GIT_PS1_DESCRIBE_STYLE=describe &&
-               __git_ps1 > "$actual"
+               __git_ps1 >"$actual"
        ) &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - describe detached head - default' '
-       printf " ((t2))" > expected &&
+       printf " ((t2))" >expected &&
        git checkout --detach b1 &&
        test_when_finished "git checkout master" &&
-       __git_ps1 > "$actual" &&
+       __git_ps1 >"$actual" &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - inside .git directory' '
-       printf " (GIT_DIR!)" > expected &&
+       printf " (GIT_DIR!)" >expected &&
        (
                cd .git &&
-               __git_ps1 > "$actual"
+               __git_ps1 >"$actual"
        ) &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - deep inside .git directory' '
-       printf " (GIT_DIR!)" > expected &&
+       printf " (GIT_DIR!)" >expected &&
        (
                cd .git/refs/heads &&
-               __git_ps1 > "$actual"
+               __git_ps1 >"$actual"
        ) &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - inside bare repository' '
-       printf " (BARE:master)" > expected &&
+       printf " (BARE:master)" >expected &&
        git init --bare bare.git &&
        test_when_finished "rm -rf bare.git" &&
        (
                cd bare.git &&
-               __git_ps1 > "$actual"
+               __git_ps1 >"$actual"
        ) &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - interactive rebase' '
-       printf " (b1|REBASE-i 2/3)" > expected
-       echo "#!$SHELL_PATH" >fake_editor.sh &&
-       cat >>fake_editor.sh <<\EOF &&
-echo "exec echo" > "$1"
-echo "edit $(git log -1 --format="%h")" >> "$1"
-echo "exec echo" >> "$1"
-EOF
+       printf " (b1|REBASE-i 2/3)" >expected
+       write_script fake_editor.sh <<-\EOF &&
+               echo "exec echo" >"$1"
+               echo "edit $(git log -1 --format="%h")" >>"$1"
+               echo "exec echo" >>"$1"
+       EOF
        test_when_finished "rm -f fake_editor.sh" &&
-       chmod a+x fake_editor.sh &&
        test_set_editor "$TRASH_DIRECTORY/fake_editor.sh" &&
        git checkout b1 &&
        test_when_finished "git checkout master" &&
        git rebase -i HEAD^ &&
        test_when_finished "git rebase --abort"
-       __git_ps1 > "$actual" &&
+       __git_ps1 >"$actual" &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - rebase merge' '
-       printf " (b2|REBASE-m 1/3)" > expected &&
+       printf " (b2|REBASE-m 1/3)" >expected &&
        git checkout b2 &&
        test_when_finished "git checkout master" &&
        test_must_fail git rebase --merge b1 b2 &&
        test_when_finished "git rebase --abort" &&
-       __git_ps1 > "$actual" &&
+       __git_ps1 >"$actual" &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - rebase' '
-       printf " (b2|REBASE 1/3)" > expected &&
+       printf " (b2|REBASE 1/3)" >expected &&
        git checkout b2 &&
        test_when_finished "git checkout master" &&
        test_must_fail git rebase b1 b2 &&
        test_when_finished "git rebase --abort" &&
-       __git_ps1 > "$actual" &&
+       __git_ps1 >"$actual" &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - merge' '
-       printf " (b1|MERGING)" > expected &&
+       printf " (b1|MERGING)" >expected &&
        git checkout b1 &&
        test_when_finished "git checkout master" &&
        test_must_fail git merge b2 &&
        test_when_finished "git reset --hard" &&
-       __git_ps1 > "$actual" &&
+       __git_ps1 >"$actual" &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - cherry-pick' '
-       printf " (master|CHERRY-PICKING)" > expected &&
+       printf " (master|CHERRY-PICKING)" >expected &&
        test_must_fail git cherry-pick b1 &&
        test_when_finished "git reset --hard" &&
-       __git_ps1 > "$actual" &&
+       __git_ps1 >"$actual" &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - bisect' '
-       printf " (master|BISECTING)" > expected &&
+       printf " (master|BISECTING)" >expected &&
        git bisect start &&
        test_when_finished "git bisect reset" &&
-       __git_ps1 > "$actual" &&
+       __git_ps1 >"$actual" &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - dirty status indicator - clean' '
-       printf " (master)" > expected &&
+       printf " (master)" >expected &&
        (
                GIT_PS1_SHOWDIRTYSTATE=y &&
-               __git_ps1 > "$actual"
+               __git_ps1 >"$actual"
        ) &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - dirty status indicator - dirty worktree' '
-       printf " (master *)" > expected &&
-       echo "dirty" > file &&
+       printf " (master *)" >expected &&
+       echo "dirty" >file &&
        test_when_finished "git reset --hard" &&
        (
                GIT_PS1_SHOWDIRTYSTATE=y &&
-               __git_ps1 > "$actual"
+               __git_ps1 >"$actual"
        ) &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - dirty status indicator - dirty index' '
-       printf " (master +)" > expected &&
-       echo "dirty" > file &&
+       printf " (master +)" >expected &&
+       echo "dirty" >file &&
        test_when_finished "git reset --hard" &&
        git add -u &&
        (
                GIT_PS1_SHOWDIRTYSTATE=y &&
-               __git_ps1 > "$actual"
+               __git_ps1 >"$actual"
        ) &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - dirty status indicator - dirty index and worktree' '
-       printf " (master *+)" > expected &&
-       echo "dirty index" > file &&
+       printf " (master *+)" >expected &&
+       echo "dirty index" >file &&
        test_when_finished "git reset --hard" &&
        git add -u &&
-       echo "dirty worktree" > file &&
+       echo "dirty worktree" >file &&
        (
                GIT_PS1_SHOWDIRTYSTATE=y &&
-               __git_ps1 > "$actual"
+               __git_ps1 >"$actual"
        ) &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - dirty status indicator - before root commit' '
-       printf " (master #)" > expected &&
+       printf " (master #)" >expected &&
        (
                GIT_PS1_SHOWDIRTYSTATE=y &&
                cd otherrepo &&
-               __git_ps1 > "$actual"
+               __git_ps1 >"$actual"
        ) &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - dirty status indicator - shell variable unset with config disabled' '
-       printf " (master)" > expected &&
-       echo "dirty" > file &&
+       printf " (master)" >expected &&
+       echo "dirty" >file &&
        test_when_finished "git reset --hard" &&
        test_config bash.showDirtyState false &&
        (
                sane_unset GIT_PS1_SHOWDIRTYSTATE &&
-               __git_ps1 > "$actual"
+               __git_ps1 >"$actual"
        ) &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - dirty status indicator - shell variable unset with config enabled' '
-       printf " (master)" > expected &&
-       echo "dirty" > file &&
+       printf " (master)" >expected &&
+       echo "dirty" >file &&
        test_when_finished "git reset --hard" &&
        test_config bash.showDirtyState true &&
        (
                sane_unset GIT_PS1_SHOWDIRTYSTATE &&
-               __git_ps1 > "$actual"
+               __git_ps1 >"$actual"
        ) &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - dirty status indicator - shell variable set with config disabled' '
-       printf " (master)" > expected &&
-       echo "dirty" > file &&
+       printf " (master)" >expected &&
+       echo "dirty" >file &&
        test_when_finished "git reset --hard" &&
        test_config bash.showDirtyState false &&
        (
                GIT_PS1_SHOWDIRTYSTATE=y &&
-               __git_ps1 > "$actual"
+               __git_ps1 >"$actual"
        ) &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - dirty status indicator - shell variable set with config enabled' '
-       printf " (master *)" > expected &&
-       echo "dirty" > file &&
+       printf " (master *)" >expected &&
+       echo "dirty" >file &&
        test_when_finished "git reset --hard" &&
        test_config bash.showDirtyState true &&
        (
                GIT_PS1_SHOWDIRTYSTATE=y &&
-               __git_ps1 > "$actual"
+               __git_ps1 >"$actual"
        ) &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - dirty status indicator - not shown inside .git directory' '
-       printf " (GIT_DIR!)" > expected &&
-       echo "dirty" > file &&
+       printf " (GIT_DIR!)" >expected &&
+       echo "dirty" >file &&
        test_when_finished "git reset --hard" &&
        (
                GIT_PS1_SHOWDIRTYSTATE=y &&
                cd .git &&
-               __git_ps1 > "$actual"
+               __git_ps1 >"$actual"
        ) &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - stash status indicator - no stash' '
-       printf " (master)" > expected &&
+       printf " (master)" >expected &&
        (
                GIT_PS1_SHOWSTASHSTATE=y &&
-               __git_ps1 > "$actual"
+               __git_ps1 >"$actual"
        ) &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - stash status indicator - stash' '
-       printf " (master $)" > expected &&
+       printf " (master $)" >expected &&
        echo 2 >file &&
        git stash &&
        test_when_finished "git stash drop" &&
+       git pack-refs --all &&
        (
                GIT_PS1_SHOWSTASHSTATE=y &&
-               __git_ps1 > "$actual"
+               __git_ps1 >"$actual"
        ) &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - stash status indicator - not shown inside .git directory' '
-       printf " (GIT_DIR!)" > expected &&
+       printf " (GIT_DIR!)" >expected &&
        echo 2 >file &&
        git stash &&
        test_when_finished "git stash drop" &&
        (
                GIT_PS1_SHOWSTASHSTATE=y &&
                cd .git &&
-               __git_ps1 > "$actual"
+               __git_ps1 >"$actual"
        ) &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - untracked files status indicator - no untracked files' '
-       printf " (master)" > expected &&
+       printf " (master)" >expected &&
        (
                GIT_PS1_SHOWUNTRACKEDFILES=y &&
                cd otherrepo &&
-               __git_ps1 > "$actual"
+               __git_ps1 >"$actual"
        ) &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - untracked files status indicator - untracked files' '
-       printf " (master %%)" > expected &&
+       printf " (master %%)" >expected &&
        (
                GIT_PS1_SHOWUNTRACKEDFILES=y &&
-               __git_ps1 > "$actual"
+               __git_ps1 >"$actual"
        ) &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - untracked files status indicator - shell variable unset with config disabled' '
-       printf " (master)" > expected &&
+       printf " (master)" >expected &&
        test_config bash.showUntrackedFiles false &&
        (
                sane_unset GIT_PS1_SHOWUNTRACKEDFILES &&
-               __git_ps1 > "$actual"
+               __git_ps1 >"$actual"
        ) &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - untracked files status indicator - shell variable unset with config enabled' '
-       printf " (master)" > expected &&
+       printf " (master)" >expected &&
        test_config bash.showUntrackedFiles true &&
        (
                sane_unset GIT_PS1_SHOWUNTRACKEDFILES &&
-               __git_ps1 > "$actual"
+               __git_ps1 >"$actual"
        ) &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - untracked files status indicator - shell variable set with config disabled' '
-       printf " (master)" > expected &&
+       printf " (master)" >expected &&
        test_config bash.showUntrackedFiles false &&
        (
                GIT_PS1_SHOWUNTRACKEDFILES=y &&
-               __git_ps1 > "$actual"
+               __git_ps1 >"$actual"
        ) &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - untracked files status indicator - shell variable set with config enabled' '
-       printf " (master %%)" > expected &&
+       printf " (master %%)" >expected &&
        test_config bash.showUntrackedFiles true &&
        (
                GIT_PS1_SHOWUNTRACKEDFILES=y &&
-               __git_ps1 > "$actual"
+               __git_ps1 >"$actual"
        ) &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - untracked files status indicator - not shown inside .git directory' '
-       printf " (GIT_DIR!)" > expected &&
+       printf " (GIT_DIR!)" >expected &&
        (
                GIT_PS1_SHOWUNTRACKEDFILES=y &&
                cd .git &&
-               __git_ps1 > "$actual"
+               __git_ps1 >"$actual"
        ) &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - format string starting with dash' '
-       printf -- "-master" > expected &&
-       __git_ps1 "-%s" > "$actual" &&
+       printf -- "-master" >expected &&
+       __git_ps1 "-%s" >"$actual" &&
        test_cmp expected "$actual"
 '
 
diff --git a/test-prio-queue.c b/test-prio-queue.c
new file mode 100644 (file)
index 0000000..7be72f0
--- /dev/null
@@ -0,0 +1,39 @@
+#include "cache.h"
+#include "prio-queue.h"
+
+static int intcmp(const void *va, const void *vb, void *data)
+{
+       const int *a = va, *b = vb;
+       return *a - *b;
+}
+
+static void show(int *v)
+{
+       if (!v)
+               printf("NULL\n");
+       else
+               printf("%d\n", *v);
+       free(v);
+}
+
+int main(int argc, char **argv)
+{
+       struct prio_queue pq = { intcmp };
+
+       while (*++argv) {
+               if (!strcmp(*argv, "get"))
+                       show(prio_queue_get(&pq));
+               else if (!strcmp(*argv, "dump")) {
+                       int *v;
+                       while ((v = prio_queue_get(&pq)))
+                              show(v);
+               }
+               else {
+                       int *v = malloc(sizeof(*v));
+                       *v = atoi(*argv);
+                       prio_queue_put(&pq, v);
+               }
+       }
+
+       return 0;
+}
index 219a3bbca613192d8b127659e4bfc0d60f38cf10..c0339919ccf78c4fe7d78123f9c3e891075d0602 100644 (file)
@@ -39,6 +39,8 @@ extern "C" {
 #define XDF_DIFF_ALGORITHM_MASK (XDF_PATIENCE_DIFF | XDF_HISTOGRAM_DIFF)
 #define XDF_DIFF_ALG(x) ((x) & XDF_DIFF_ALGORITHM_MASK)
 
+#define XDF_IGNORE_BLANK_LINES (1 << 7)
+
 #define XDL_EMIT_FUNCNAMES (1 << 0)
 #define XDL_EMIT_COMMON (1 << 1)
 #define XDL_EMIT_FUNCCONTEXT (1 << 2)
index b2eb6db2c851aea7eb08b17d802155876bef211c..2358a2d6326e54308413cb8a5e6b61eba06324e9 100644 (file)
@@ -394,6 +394,7 @@ static xdchange_t *xdl_add_change(xdchange_t *xscr, long i1, long i2, long chg1,
        xch->i2 = i2;
        xch->chg1 = chg1;
        xch->chg2 = chg2;
+       xch->ignore = 0;
 
        return xch;
 }
@@ -544,7 +545,9 @@ static int xdl_call_hunk_func(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
        xdchange_t *xch, *xche;
 
        for (xch = xscr; xch; xch = xche->next) {
-               xche = xdl_get_hunk(xch, xecfg);
+               xche = xdl_get_hunk(&xch, xecfg);
+               if (!xch)
+                       break;
                if (xecfg->hunk_func(xch->i1, xche->i1 + xche->chg1 - xch->i1,
                                     xch->i2, xche->i2 + xche->chg2 - xch->i2,
                                     ecb->priv) < 0)
@@ -553,6 +556,27 @@ static int xdl_call_hunk_func(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
        return 0;
 }
 
+static void xdl_mark_ignorable(xdchange_t *xscr, xdfenv_t *xe, long flags)
+{
+       xdchange_t *xch;
+
+       for (xch = xscr; xch; xch = xch->next) {
+               int ignore = 1;
+               xrecord_t **rec;
+               long i;
+
+               rec = &xe->xdf1.recs[xch->i1];
+               for (i = 0; i < xch->chg1 && ignore; i++)
+                       ignore = xdl_blankline(rec[i]->ptr, rec[i]->size, flags);
+
+               rec = &xe->xdf2.recs[xch->i2];
+               for (i = 0; i < xch->chg2 && ignore; i++)
+                       ignore = xdl_blankline(rec[i]->ptr, rec[i]->size, flags);
+
+               xch->ignore = ignore;
+       }
+}
+
 int xdl_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
             xdemitconf_t const *xecfg, xdemitcb_t *ecb) {
        xdchange_t *xscr;
@@ -571,6 +595,9 @@ int xdl_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
                return -1;
        }
        if (xscr) {
+               if (xpp->flags & XDF_IGNORE_BLANK_LINES)
+                       xdl_mark_ignorable(xscr, &xe, xpp->flags);
+
                if (ef(&xe, xscr, ecb, xecfg) < 0) {
 
                        xdl_free_script(xscr);
index 7a92ea9c4d84a559ae1d0bd90ebe667828d8f9cb..8b81206c9af0767bd91c4b9e453f7c5c2bde47b1 100644 (file)
@@ -41,6 +41,7 @@ typedef struct s_xdchange {
        struct s_xdchange *next;
        long i1, i2;
        long chg1, chg2;
+       int ignore;
 } xdchange_t;
 
 
index d11dbf9f13c13db16fa05f7539170862d6da9795..4d8645867e7d0103d49882877a7e4a8abacd24ea 100644 (file)
@@ -56,16 +56,51 @@ static int xdl_emit_record(xdfile_t *xdf, long ri, char const *pre, xdemitcb_t *
 /*
  * Starting at the passed change atom, find the latest change atom to be included
  * inside the differential hunk according to the specified configuration.
+ * Also advance xscr if the first changes must be discarded.
  */
-xdchange_t *xdl_get_hunk(xdchange_t *xscr, xdemitconf_t const *xecfg) {
-       xdchange_t *xch, *xchp;
+xdchange_t *xdl_get_hunk(xdchange_t **xscr, xdemitconf_t const *xecfg)
+{
+       xdchange_t *xch, *xchp, *lxch;
        long max_common = 2 * xecfg->ctxlen + xecfg->interhunkctxlen;
+       long max_ignorable = xecfg->ctxlen;
+       unsigned long ignored = 0; /* number of ignored blank lines */
+
+       /* remove ignorable changes that are too far before other changes */
+       for (xchp = *xscr; xchp && xchp->ignore; xchp = xchp->next) {
+               xch = xchp->next;
+
+               if (xch == NULL ||
+                   xch->i1 - (xchp->i1 + xchp->chg1) >= max_ignorable)
+                       *xscr = xch;
+       }
+
+       if (*xscr == NULL)
+               return NULL;
+
+       lxch = *xscr;
 
-       for (xchp = xscr, xch = xscr->next; xch; xchp = xch, xch = xch->next)
-               if (xch->i1 - (xchp->i1 + xchp->chg1) > max_common)
+       for (xchp = *xscr, xch = xchp->next; xch; xchp = xch, xch = xch->next) {
+               long distance = xch->i1 - (xchp->i1 + xchp->chg1);
+               if (distance > max_common)
                        break;
 
-       return xchp;
+               if (distance < max_ignorable && (!xch->ignore || lxch == xchp)) {
+                       lxch = xch;
+                       ignored = 0;
+               } else if (distance < max_ignorable && xch->ignore) {
+                       ignored += xch->chg2;
+               } else if (lxch != xchp &&
+                          xch->i1 + ignored - (lxch->i1 + lxch->chg1) > max_common) {
+                       break;
+               } else if (!xch->ignore) {
+                       lxch = xch;
+                       ignored = 0;
+               } else {
+                       ignored += xch->chg2;
+               }
+       }
+
+       return lxch;
 }
 
 
@@ -139,7 +174,9 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
                return xdl_emit_common(xe, xscr, ecb, xecfg);
 
        for (xch = xscr; xch; xch = xche->next) {
-               xche = xdl_get_hunk(xch, xecfg);
+               xche = xdl_get_hunk(&xch, xecfg);
+               if (!xch)
+                       break;
 
                s1 = XDL_MAX(xch->i1 - xecfg->ctxlen, 0);
                s2 = XDL_MAX(xch->i2 - xecfg->ctxlen, 0);
index c2e2e830273782dc597606ddbb0401c04dce8f8f..d29710770ce40bafa6e9eb2b2ea7c9c8ba43c727 100644 (file)
@@ -27,7 +27,7 @@
 typedef int (*emit_func_t)(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
                           xdemitconf_t const *xecfg);
 
-xdchange_t *xdl_get_hunk(xdchange_t *xscr, xdemitconf_t const *xecfg);
+xdchange_t *xdl_get_hunk(xdchange_t **xscr, xdemitconf_t const *xecfg);
 int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
                  xdemitconf_t const *xecfg);
 
index 9504eaecb8ac45248ea22b0779aa7492cd15d83e..62cb23dfd37743e4985655998ccabd56db160233 100644 (file)
@@ -143,6 +143,19 @@ long xdl_guess_lines(mmfile_t *mf, long sample) {
        return nl + 1;
 }
 
+int xdl_blankline(const char *line, long size, long flags)
+{
+       long i;
+
+       if (!(flags & XDF_WHITESPACE_FLAGS))
+               return (size <= 1);
+
+       for (i = 0; i < size && XDL_ISSPACE(line[i]); i++)
+               ;
+
+       return (i == size);
+}
+
 int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags)
 {
        int i1, i2;
index ad1428ed699383bf6f736b28033aeb9dbe114b76..4646ce575251b07053f20285be99422d6576603e 100644 (file)
@@ -32,6 +32,7 @@ int xdl_cha_init(chastore_t *cha, long isize, long icount);
 void xdl_cha_free(chastore_t *cha);
 void *xdl_cha_alloc(chastore_t *cha);
 long xdl_guess_lines(mmfile_t *mf, long sample);
+int xdl_blankline(const char *line, long size, long flags);
 int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags);
 unsigned long xdl_hash_record(char const **data, char const *top, long flags);
 unsigned int xdl_hashbits(unsigned int size);