Merge branch 'svn-fe' of git://repo.or.cz/git/jrn
authorJunio C Hamano <gitster@pobox.com>
Tue, 1 Mar 2011 00:33:45 +0000 (16:33 -0800)
committerJunio C Hamano <gitster@pobox.com>
Tue, 1 Mar 2011 00:33:45 +0000 (16:33 -0800)
* 'svn-fe' of git://repo.or.cz/git/jrn: (31 commits)
fast-import: make code "-Wpointer-arith" clean
vcs-svn: teach line_buffer about temporary files
vcs-svn: allow input from file descriptor
vcs-svn: allow character-oriented input
vcs-svn: add binary-safe read function
t0081 (line-buffer): add buffering tests
vcs-svn: tweak test-line-buffer to not assume line-oriented input
tests: give vcs-svn/line_buffer its own test script
vcs-svn: make test-line-buffer input format more flexible
vcs-svn: teach line_buffer to handle multiple input files
vcs-svn: collect line_buffer data in a struct
vcs-svn: replace buffer_read_string memory pool with a strbuf
vcs-svn: eliminate global byte_buffer
fast-import: add 'ls' command
vcs-svn: Allow change nodes for root of tree (/)
vcs-svn: Implement Prop-delta handling
vcs-svn: Sharpen parsing of property lines
vcs-svn: Split off function for handling of individual properties
vcs-svn: Make source easier to read on small screens
vcs-svn: More dump format sanity checks
...

72 files changed:
Documentation/CodingGuidelines
Documentation/RelNotes/1.7.5.txt [new file with mode: 0644]
Documentation/config.txt
Documentation/git-checkout.txt
Documentation/git-remote-ext.txt
Documentation/git-remote-helpers.txt
Documentation/git-svn.txt
Documentation/glossary-content.txt
RelNotes
builtin/add.c
builtin/branch.c
builtin/checkout.c
builtin/commit.c
builtin/config.c
builtin/diff-files.c
builtin/diff.c
builtin/fast-export.c
builtin/fetch.c
builtin/grep.c
builtin/hash-object.c
builtin/log.c
builtin/merge.c
builtin/notes.c
builtin/patch-id.c
builtin/push.c
builtin/read-tree.c
builtin/rerere.c
builtin/tag.c
builtin/update-index.c
cache.h
compat/mingw.c
compat/mingw.h
config.c
contrib/fast-import/git-p4
diff-lib.c
diff-no-index.c
diff.h
dir.c
dir.h
git-mergetool.sh
git.c
list-objects.c
parse-options.h
perl/Git.pm
preload-index.c
read-cache.c
remote-curl.c
rerere.c
rerere.h
revision.c
revision.h
sha1_file.c
t/t0040-parse-options.sh
t/t1007-hash-object.sh
t/t1300-repo-config.sh
t/t2019-checkout-ambiguous-ref.sh [new file with mode: 0755]
t/t2020-checkout-detach.sh [new file with mode: 0755]
t/t4010-diff-pathspec.sh
t/t4204-patch-id.sh
t/t6000-rev-list-misc.sh [new file with mode: 0755]
t/t6004-rev-list-path-optim.sh
t/t7505-prepare-commit-msg-hook.sh
t/t7610-mergetool.sh
t/t7810-grep.sh
t/t9500-gitweb-standalone-no-errors.sh
t/t9700/test.pl
t/t9800-git-p4.sh [new file with mode: 0755]
test-parse-options.c
tree-diff.c
tree-walk.c
tree-walk.h
wt-status.c
index ba2006d892ec09d606f8846588c7727396b1ee28..fe1c1e5bc26e683540cb9fe5f43320192be9185d 100644 (file)
@@ -152,7 +152,7 @@ Writing Documentation:
  when writing or modifying command usage strings and synopsis sections
  in the manual pages:
 
- Placeholders are enclosed in angle brackets:
+ Placeholders are spelled in lowercase and enclosed in angle brackets:
    <file>
    --sort=<key>
    --abbrev[=<n>]
diff --git a/Documentation/RelNotes/1.7.5.txt b/Documentation/RelNotes/1.7.5.txt
new file mode 100644 (file)
index 0000000..56c3863
--- /dev/null
@@ -0,0 +1,56 @@
+Git v1.7.5 Release Notes (draft)
+========================
+
+Updates since v1.7.4
+--------------------
+
+ * Various MinGW portability fixes.
+
+ * Various git-p4 enhancements (in contrib).
+
+ * "git config" used to be also known as "git repo-config", but the old
+   name is now officially deprecated.
+
+ * "git checkout --detach <commit>" is a more user friendly synonym for
+   "git checkout <commit>^0".
+
+ * "git cherry-pick" and "git revert" can be told to use custom merge
+   strategy, similar to "git rebase".
+
+ * "rev-list --objects $revs -- $pathspec" would limit the objects listed
+   in its output properly with the pathspec, in preparation for narrow
+   clones.
+
+ * "git log" family of commands now understand globbing pathspecs.  You
+   can say "git log -- '*.txt'" for example.
+
+ * "git rerere" learned a new subcommand "remaining", that is similar to
+   "status" that lists the paths that had conflicts that are known to
+   rerere, but excludes the paths that have already been marked as
+   resolved in the index from its output.  "git mergetool" has been
+   updated to use this facility.
+
+ * A possible value to the "push.default" configuration variable,
+   'tracking', gained a synonym that more naturally describes what it
+   does, 'upstream'.
+
+Also contains various documentation updates.
+
+
+Fixes since v1.7.4
+------------------
+
+All of the fixes in the v1.7.4.X maintenance series are included in this
+release, unless otherwise noted.
+
+ * "git merge" triggers prepare-commit-msg hook.  Earlier, only "git
+   commit" to conclude an interrupted merge triggered the hook, leading to
+   an inconsistent overall user experience (js/maint-merge-use-prepare-commit-msg-hook).
+
+
+---
+exec >/var/tmp/1
+O=v1.7.4
+O=v1.7.4.1-140-g8978166
+echo O=$(git describe 'master')
+git shortlog --no-merges ^maint ^$O master
index c5e183516a104e6efb7ed597fb4498d75560ab68..c995a1a47b3cfb99a2bebbadf4f7a5360cbe3d1a 100644 (file)
@@ -1591,7 +1591,8 @@ push.default::
 * `matching` - push all matching branches.
   All branches having the same name in both ends are considered to be
   matching. This is the default.
-* `tracking` - push the current branch to its upstream branch.
+* `upstream` - push the current branch to its upstream branch.
+* `tracking` - deprecated synonym for `upstream`.
 * `current` - push the current branch to a branch of the same name.
 
 rebase.stat::
index 880763d391d18364485edc2e1e3dfc6f52243973..396f4cc15bff745e72e137239dbd49e6584e23d2 100644 (file)
@@ -9,6 +9,7 @@ SYNOPSIS
 --------
 [verse]
 'git checkout' [-q] [-f] [-m] [<branch>]
+'git checkout' [-q] [-f] [-m] [--detach] [<commit>]
 'git checkout' [-q] [-f] [-m] [[-b|-B|--orphan] <new_branch>] [<start_point>]
 'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <paths>...
 'git checkout' --patch [<tree-ish>] [--] [<paths>...]
@@ -22,9 +23,10 @@ branch.
 
 'git checkout' [<branch>]::
 'git checkout' -b|-B <new_branch> [<start point>]::
+'git checkout' [--detach] [<commit>]::
 
        This form switches branches by updating the index, working
-       tree, and HEAD to reflect the specified branch.
+       tree, and HEAD to reflect the specified branch or commit.
 +
 If `-b` is given, a new branch is created as if linkgit:git-branch[1]
 were called and then checked out; in this case you can
@@ -115,6 +117,13 @@ explicitly give a name with '-b' in such a case.
        Create the new branch's reflog; see linkgit:git-branch[1] for
        details.
 
+--detach::
+       Rather than checking out a branch to work on it, check out a
+       commit for inspection and discardable experiments.
+       This is the default behavior of "git checkout <commit>" when
+       <commit> is not a branch name.  See the "DETACHED HEAD" section
+       below for details.
+
 --orphan::
        Create a new 'orphan' branch, named <new_branch>, started from
        <start_point> and switch to it.  The first commit made on this
@@ -204,42 +213,140 @@ leave out at most one of `A` and `B`, in which case it defaults to `HEAD`.
 
 
 
-Detached HEAD
+DETACHED HEAD
 -------------
+HEAD normally refers to a named branch (e.g. 'master'). Meanwhile, each
+branch refers to a specific commit. Let's look at a repo with three
+commits, one of them tagged, and with branch 'master' checked out:
+
+------------
+          HEAD (refers to branch 'master')
+           |
+           v
+a---b---c  branch 'master' (refers to commit 'c')
+    ^
+    |
+  tag 'v2.0' (refers to commit 'b')
+------------
 
-It is sometimes useful to be able to 'checkout' a commit that is
-not at the tip of one of your branches.  The most obvious
-example is to check out the commit at a tagged official release
-point, like this:
+When a commit is created in this state, the branch is updated to refer to
+the new commit. Specifically, 'git commit' creates a new commit 'd', whose
+parent is commit 'c', and then updates branch 'master' to refer to new
+commit 'd'. HEAD still refers to branch 'master' and so indirectly now refers
+to commit 'd':
 
 ------------
-$ git checkout v2.6.18
+$ edit; git add; git commit
+
+              HEAD (refers to branch 'master')
+               |
+               v
+a---b---c---d  branch 'master' (refers to commit 'd')
+    ^
+    |
+  tag 'v2.0' (refers to commit 'b')
 ------------
 
-Earlier versions of git did not allow this and asked you to
-create a temporary branch using the `-b` option, but starting from
-version 1.5.0, the above command 'detaches' your HEAD from the
-current branch and directly points at the commit named by the tag
-(`v2.6.18` in the example above).
+It is sometimes useful to be able to checkout a commit that is not at
+the tip of any named branch, or even to create a new commit that is not
+referenced by a named branch. Let's look at what happens when we
+checkout commit 'b' (here we show two ways this may be done):
 
-You can use all git commands while in this state.  You can use
-`git reset --hard $othercommit` to further move around, for
-example.  You can make changes and create a new commit on top of
-a detached HEAD.  You can even create a merge by using `git
-merge $othercommit`.
+------------
+$ git checkout v2.0  # or
+$ git checkout master^^
+
+   HEAD (refers to commit 'b')
+    |
+    v
+a---b---c---d  branch 'master' (refers to commit 'd')
+    ^
+    |
+  tag 'v2.0' (refers to commit 'b')
+------------
 
-The state you are in while your HEAD is detached is not recorded
-by any branch (which is natural --- you are not on any branch).
-What this means is that you can discard your temporary commits
-and merges by switching back to an existing branch (e.g. `git
-checkout master`), and a later `git prune` or `git gc` would
-garbage-collect them.  If you did this by mistake, you can ask
-the reflog for HEAD where you were, e.g.
+Notice that regardless of which checkout command we use, HEAD now refers
+directly to commit 'b'. This is known as being in detached HEAD state.
+It means simply that HEAD refers to a specific commit, as opposed to
+referring to a named branch. Let's see what happens when we create a commit:
 
 ------------
-$ git log -g -2 HEAD
+$ edit; git add; git commit
+
+     HEAD (refers to commit 'e')
+      |
+      v
+      e
+     /
+a---b---c---d  branch 'master' (refers to commit 'd')
+    ^
+    |
+  tag 'v2.0' (refers to commit 'b')
+------------
+
+There is now a new commit 'e', but it is referenced only by HEAD. We can
+of course add yet another commit in this state:
+
+------------
+$ edit; git add; git commit
+
+        HEAD (refers to commit 'f')
+         |
+         v
+      e---f
+     /
+a---b---c---d  branch 'master' (refers to commit 'd')
+    ^
+    |
+  tag 'v2.0' (refers to commit 'b')
+------------
+
+In fact, we can perform all the normal git operations. But, let's look
+at what happens when we then checkout master:
+
+------------
+$ git checkout master
+
+              HEAD (refers to branch 'master')
+      e---f     |
+     /          v
+a---b---c---d  branch 'master' (refers to commit 'd')
+    ^
+    |
+  tag 'v2.0' (refers to commit 'b')
 ------------
 
+It is important to realize that at this point nothing refers to commit
+'f'. Eventually commit 'f' (and by extension commit 'e') will be deleted
+by the routine git garbage collection process, unless we create a reference
+before that happens. If we have not yet moved away from commit 'f',
+any of these will create a reference to it:
+
+------------
+$ git checkout -b foo   <1>
+$ git branch foo        <2>
+$ git tag foo           <3>
+------------
+
+<1> creates a new branch 'foo', which refers to commit 'f', and then
+updates HEAD to refer to branch 'foo'. In other words, we'll no longer
+be in detached HEAD state after this command.
+
+<2> similarly creates a new branch 'foo', which refers to commit 'f',
+but leaves HEAD detached.
+
+<3> creates a new tag 'foo', which refers to commit 'f',
+leaving HEAD detached.
+
+If we have moved away from commit 'f', then we must first recover its object
+name (typically by using git reflog), and then we can create a reference to
+it. For example, to see the last two commits to which HEAD referred, we
+can use either of these commands:
+
+------------
+$ git reflog -2 HEAD # or
+$ git log -g -2 HEAD
+------------
 
 EXAMPLES
 --------
index 2d65cfefd5d2aeaafcbc0bb6e6705f8379b2f826..68263a6a538b3d4659ca9dc0afd2253170143fb6 100644 (file)
@@ -7,17 +7,17 @@ git-remote-ext - Bridge smart transport to external command.
 
 SYNOPSIS
 --------
-git remote add nick "ext::<command>[ <arguments>...]"
+git remote add <nick> "ext::<command>[ <arguments>...]"
 
 DESCRIPTION
 -----------
-This remote helper uses the specified 'program' to connect
+This remote helper uses the specified '<command>' to connect
 to a remote git server.
 
-Data written to stdin of this specified 'program' is assumed
+Data written to stdin of the specified '<command>' is assumed
 to be sent to a git:// server, git-upload-pack, git-receive-pack
 or git-upload-archive (depending on situation), and data read
-from stdout of this program is assumed to be received from
+from stdout of <command> is assumed to be received from
 the same service.
 
 Command and arguments are separated by an unescaped space.
@@ -40,7 +40,7 @@ The following sequences have a special meaning:
        git wants to invoke.
 
 '%G' (must be the first characters in an argument)::
-       This argument will not be passed to 'program'. Instead, it
+       This argument will not be passed to '<command>'. Instead, it
        will cause the helper to start by sending git:// service requests to
        the remote side with the service field set to an appropriate value and
        the repository field set to rest of the argument. Default is not to send
@@ -50,7 +50,7 @@ This is useful if remote side is git:// server accessed over
 some tunnel.
 
 '%V' (must be first characters in argument)::
-       This argument will not be passed to 'program'. Instead it sets
+       This argument will not be passed to '<command>'. Instead it sets
        the vhost field in the git:// service request (to rest of the argument).
        Default is not to send vhost in such request (if sent).
 
@@ -76,7 +76,7 @@ EXAMPLES:
 ---------
 This remote helper is transparently used by git when
 you use commands such as "git fetch <URL>", "git clone <URL>",
-, "git push <URL>" or "git remote add nick <URL>", where <URL>
+, "git push <URL>" or "git remote add <nick> <URL>", where <URL>
 begins with `ext::`.  Examples:
 
 "ext::ssh -i /home/foo/.ssh/somekey user&#64;host.example %S 'foo/repo'"::
index 3a23477ce72763b562258a25c3777dd57ac1a962..51de895822d6d79f970012c835d68b2cd8c8e136 100644 (file)
@@ -201,12 +201,12 @@ REF LIST ATTRIBUTES
 
 OPTIONS
 -------
-'option verbosity' <N>::
+'option verbosity' <n>::
        Changes the verbosity of messages displayed by the helper.
-       A value of 0 for N means that processes operate
+       A value of 0 for <n> means that processes operate
        quietly, and the helper produces only error output.
        1 is the default level of verbosity, and higher values
-       of N correspond to the number of -v flags passed on the
+       of <n> correspond to the number of -v flags passed on the
        command line.
 
 'option progress' \{'true'|'false'\}::
index 0ade2ce54efee3cd2241fc7e3009909eef5cf8bb..e161a40a73cb57557435e94c9444935da0828f1c 100644 (file)
@@ -66,7 +66,7 @@ COMMANDS
        Set the 'rewriteRoot' option in the [svn-remote] config.
 --rewrite-uuid=<UUID>;;
        Set the 'rewriteUUID' option in the [svn-remote] config.
---username=<USER>;;
+--username=<user>;;
        For transports that SVN handles authentication for (http,
        https, and plain svn), specify the username.  For other
        transports (eg svn+ssh://), you must include the username in
@@ -443,8 +443,8 @@ OPTIONS
        Only used with the 'init' command.
        These are passed directly to 'git init'.
 
--r <ARG>::
---revision <ARG>::
+-r <arg>::
+--revision <arg>::
           Used with the 'fetch' command.
 +
 This allows revision ranges for partial/cauterized history
index f04b48ef0d482f03f186d749768da246aabcd1db..33716a31d0c60093c14f894ef07a87bee73fafc9 100644 (file)
@@ -273,6 +273,29 @@ This commit is referred to as a "merge commit", or sometimes just a
        <<def_pack,pack>>, to assist in efficiently accessing the contents of a
        pack.
 
+[[def_pathspec]]pathspec::
+       Pattern used to specify paths.
++
+Pathspecs are used on the command line of "git ls-files", "git
+ls-tree", "git grep", "git checkout", and many other commands to
+limit the scope of operations to some subset of the tree or
+worktree.  See the documentation of each command for whether
+paths are relative to the current directory or toplevel.  The
+pathspec syntax is as follows:
+
+* any path matches itself
+* the pathspec up to the last slash represents a
+  directory prefix.  The scope of that pathspec is
+  limited to that subtree.
+* the rest of the pathspec is a pattern for the remainder
+  of the pathname.  Paths relative to the directory
+  prefix will be matched against that pattern using fnmatch(3);
+  in particular, '*' and '?' _can_ match directory separators.
++
+For example, Documentation/*.jpg will match all .jpg files
+in the Documentation subtree,
+including Documentation/chapter_1/figure_1.jpg.
+
 [[def_parent]]parent::
        A <<def_commit_object,commit object>> contains a (possibly empty) list
        of the logical predecessor(s) in the line of development, i.e. its
index b942e499449d97aeb50c73ca2bdc1c6e6d528743..540b756d2f64d8c8ebf927a707b9b2cc0a4c6151 120000 (symlink)
--- a/RelNotes
+++ b/RelNotes
@@ -1 +1 @@
-Documentation/RelNotes/1.7.4.txt
\ No newline at end of file
+Documentation/RelNotes/1.7.5.txt
\ No newline at end of file
index 42c906ea06b3d20d88ed6ba1c83d894d4e24f163..f7a17e43f6816622ab4ff836a169221289756505 100644 (file)
@@ -86,7 +86,7 @@ int add_files_to_cache(const char *prefix, const char **pathspec, int flags)
        struct rev_info rev;
        init_revisions(&rev, prefix);
        setup_revisions(0, NULL, &rev, NULL);
-       rev.prune_data = pathspec;
+       init_pathspec(&rev.prune_data, pathspec);
        rev.diffopt.output_format = DIFF_FORMAT_CALLBACK;
        rev.diffopt.format_callback = update_callback;
        data.flags = flags;
@@ -322,7 +322,7 @@ static struct option builtin_add_options[] = {
        OPT__FORCE(&ignored_too, "allow adding otherwise ignored files"),
        OPT_BOOLEAN('u', "update", &take_worktree_changes, "update tracked files"),
        OPT_BOOLEAN('N', "intent-to-add", &intent_to_add, "record only the fact that the path will be added later"),
-       OPT_BOOLEAN('A', "all", &addremove, "add all, noticing removal of tracked files"),
+       OPT_BOOLEAN('A', "all", &addremove, "add changes from all tracked and untracked files"),
        OPT_BOOLEAN( 0 , "refresh", &refresh_only, "don't add, only refresh the index"),
        OPT_BOOLEAN( 0 , "ignore-errors", &ignore_add_errors, "just skip files which cannot be added because of errors"),
        OPT_BOOLEAN( 0 , "ignore-missing", &ignore_missing, "check if - even missing - files are ignored in dry run"),
index 9e546e4a83d07dd6dd4d4b0cb7d23a02c82747fb..fe8f2fcd524cd690c8601d9370079f9d9ea21df3 100644 (file)
@@ -134,7 +134,7 @@ static int branch_merged(int kind, const char *name,
            in_merge_bases(rev, &head_rev, 1) != merged) {
                if (merged)
                        warning("deleting branch '%s' that has been merged to\n"
-                               "         '%s', but it is not yet merged to HEAD.",
+                               "         '%s', but not yet been merged to HEAD.",
                                name, reference_name);
                else
                        warning("not deleting branch '%s' that is not yet merged to\n"
index bef324e4717fbe73da852c49c664e02d70955fc9..cc97dbc30f1b064a44157897d9e325d934111cee 100644 (file)
@@ -30,6 +30,7 @@ struct checkout_opts {
        int quiet;
        int merge;
        int force;
+       int force_detach;
        int writeout_stage;
        int writeout_error;
 
@@ -541,7 +542,17 @@ static void update_refs_for_switch(struct checkout_opts *opts,
        strbuf_addf(&msg, "checkout: moving from %s to %s",
                    old_desc ? old_desc : "(invalid)", new->name);
 
-       if (new->path) {
+       if (!strcmp(new->name, "HEAD") && !new->path && !opts->force_detach) {
+               /* Nothing to do. */
+       } else if (opts->force_detach || !new->path) {  /* No longer on any branch. */
+               update_ref(msg.buf, "HEAD", new->commit->object.sha1, NULL,
+                          REF_NODEREF, DIE_ON_ERR);
+               if (!opts->quiet) {
+                       if (old->path && advice_detached_head)
+                               detach_advice(old->path, new->name);
+                       describe_detached_head("HEAD is now at", new->commit);
+               }
+       } else if (new->path) { /* Switch branches. */
                create_symref("HEAD", new->path, msg.buf);
                if (!opts->quiet) {
                        if (old->path && !strcmp(new->path, old->path))
@@ -563,18 +574,11 @@ static void update_refs_for_switch(struct checkout_opts *opts,
                        if (!file_exists(ref_file) && file_exists(log_file))
                                remove_path(log_file);
                }
-       } else if (strcmp(new->name, "HEAD")) {
-               update_ref(msg.buf, "HEAD", new->commit->object.sha1, NULL,
-                          REF_NODEREF, DIE_ON_ERR);
-               if (!opts->quiet) {
-                       if (old->path && advice_detached_head)
-                               detach_advice(old->path, new->name);
-                       describe_detached_head("HEAD is now at", new->commit);
-               }
        }
        remove_branch_state();
        strbuf_release(&msg);
-       if (!opts->quiet && (new->path || !strcmp(new->name, "HEAD")))
+       if (!opts->quiet &&
+           (new->path || (!opts->force_detach && !strcmp(new->name, "HEAD"))))
                report_tracking(new);
 }
 
@@ -675,11 +679,123 @@ static const char *unique_tracking_name(const char *name)
        return NULL;
 }
 
+static int parse_branchname_arg(int argc, const char **argv,
+                               int dwim_new_local_branch_ok,
+                               struct branch_info *new,
+                               struct tree **source_tree,
+                               unsigned char rev[20],
+                               const char **new_branch)
+{
+       int argcount = 0;
+       unsigned char branch_rev[20];
+       const char *arg;
+       int has_dash_dash;
+
+       /*
+        * case 1: git checkout <ref> -- [<paths>]
+        *
+        *   <ref> must be a valid tree, everything after the '--' must be
+        *   a path.
+        *
+        * case 2: git checkout -- [<paths>]
+        *
+        *   everything after the '--' must be paths.
+        *
+        * case 3: git checkout <something> [<paths>]
+        *
+        *   With no paths, if <something> is a commit, that is to
+        *   switch to the branch or detach HEAD at it.  As a special case,
+        *   if <something> is A...B (missing A or B means HEAD but you can
+        *   omit at most one side), and if there is a unique merge base
+        *   between A and B, A...B names that merge base.
+        *
+        *   With no paths, if <something> is _not_ a commit, no -t nor -b
+        *   was given, and there is a tracking branch whose name is
+        *   <something> in one and only one remote, then this is a short-hand
+        *   to fork local <something> from that remote-tracking branch.
+        *
+        *   Otherwise <something> shall not be ambiguous.
+        *   - If it's *only* a reference, treat it like case (1).
+        *   - If it's only a path, treat it like case (2).
+        *   - else: fail.
+        *
+        */
+       if (!argc)
+               return 0;
+
+       if (!strcmp(argv[0], "--"))     /* case (2) */
+               return 1;
+
+       arg = argv[0];
+       has_dash_dash = (argc > 1) && !strcmp(argv[1], "--");
+
+       if (!strcmp(arg, "-"))
+               arg = "@{-1}";
+
+       if (get_sha1_mb(arg, rev)) {
+               if (has_dash_dash)          /* case (1) */
+                       die("invalid reference: %s", arg);
+               if (dwim_new_local_branch_ok &&
+                   !check_filename(NULL, arg) &&
+                   argc == 1) {
+                       const char *remote = unique_tracking_name(arg);
+                       if (!remote || get_sha1(remote, rev))
+                               return argcount;
+                       *new_branch = arg;
+                       arg = remote;
+                       /* DWIMmed to create local branch */
+               } else {
+                       return argcount;
+               }
+       }
+
+       /* we can't end up being in (2) anymore, eat the argument */
+       argcount++;
+       argv++;
+       argc--;
+
+       new->name = arg;
+       setup_branch_path(new);
+
+       if (check_ref_format(new->path) == CHECK_REF_FORMAT_OK &&
+           resolve_ref(new->path, branch_rev, 1, NULL))
+               hashcpy(rev, branch_rev);
+       else
+               new->path = NULL; /* not an existing branch */
+
+       new->commit = lookup_commit_reference_gently(rev, 1);
+       if (!new->commit) {
+               /* not a commit */
+               *source_tree = parse_tree_indirect(rev);
+       } else {
+               parse_commit(new->commit);
+               *source_tree = new->commit->tree;
+       }
+
+       if (!*source_tree)                   /* case (1): want a tree */
+               die("reference is not a tree: %s", arg);
+       if (!has_dash_dash) {/* case (3 -> 1) */
+               /*
+                * Do not complain the most common case
+                *      git checkout branch
+                * even if there happen to be a file called 'branch';
+                * it would be extremely annoying.
+                */
+               if (argc)
+                       verify_non_filename(NULL, arg);
+       } else {
+               argcount++;
+               argv++;
+               argc--;
+       }
+
+       return argcount;
+}
+
 int cmd_checkout(int argc, const char **argv, const char *prefix)
 {
        struct checkout_opts opts;
        unsigned char rev[20];
-       const char *arg;
        struct branch_info new;
        struct tree *source_tree = NULL;
        char *conflict_style = NULL;
@@ -692,6 +808,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
                OPT_STRING('B', NULL, &opts.new_branch_force, "branch",
                           "create/reset and checkout a branch"),
                OPT_BOOLEAN('l', NULL, &opts.new_branch_log, "create reflog for new branch"),
+               OPT_BOOLEAN(0, "detach", &opts.force_detach, "detach the HEAD at named commit"),
                OPT_SET_INT('t', "track",  &opts.track, "set upstream info for new branch",
                        BRANCH_TRACK_EXPLICIT),
                OPT_STRING(0, "orphan", &opts.new_orphan_branch, "new branch", "new unparented branch"),
@@ -709,7 +826,6 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
                  PARSE_OPT_NOARG | PARSE_OPT_HIDDEN },
                OPT_END(),
        };
-       int has_dash_dash;
 
        memset(&opts, 0, sizeof(opts));
        memset(&new, 0, sizeof(new));
@@ -731,9 +847,15 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
                opts.new_branch = opts.new_branch_force;
 
        if (patch_mode && (opts.track > 0 || opts.new_branch
-                          || opts.new_branch_log || opts.merge || opts.force))
+                          || opts.new_branch_log || opts.merge || opts.force
+                          || opts.force_detach))
                die ("--patch is incompatible with all other options");
 
+       if (opts.force_detach && (opts.new_branch || opts.new_orphan_branch))
+               die("--detach cannot be used with -b/-B/--orphan");
+       if (opts.force_detach && 0 < opts.track)
+               die("--detach cannot be used with -t");
+
        /* --track without -b should DWIM */
        if (0 < opts.track && !opts.new_branch) {
                const char *argv0 = argv[0];
@@ -766,105 +888,30 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
                die("git checkout: -f and -m are incompatible");
 
        /*
-        * case 1: git checkout <ref> -- [<paths>]
-        *
-        *   <ref> must be a valid tree, everything after the '--' must be
-        *   a path.
-        *
-        * case 2: git checkout -- [<paths>]
-        *
-        *   everything after the '--' must be paths.
-        *
-        * case 3: git checkout <something> [<paths>]
-        *
-        *   With no paths, if <something> is a commit, that is to
-        *   switch to the branch or detach HEAD at it.  As a special case,
-        *   if <something> is A...B (missing A or B means HEAD but you can
-        *   omit at most one side), and if there is a unique merge base
-        *   between A and B, A...B names that merge base.
+        * Extract branch name from command line arguments, so
+        * all that is left is pathspecs.
         *
-        *   With no paths, if <something> is _not_ a commit, no -t nor -b
-        *   was given, and there is a remote-tracking branch whose name is
-        *   <something> in one and only one remote, then this is a short-hand
-        *   to fork local <something> from that remote-tracking branch.
+        * Handle
         *
-        *   Otherwise <something> shall not be ambiguous.
-        *   - If it's *only* a reference, treat it like case (1).
-        *   - If it's only a path, treat it like case (2).
-        *   - else: fail.
+        *  1) git checkout <tree> -- [<paths>]
+        *  2) git checkout -- [<paths>]
+        *  3) git checkout <something> [<paths>]
         *
+        * including "last branch" syntax and DWIM-ery for names of
+        * remote branches, erroring out for invalid or ambiguous cases.
         */
        if (argc) {
-               if (!strcmp(argv[0], "--")) {       /* case (2) */
-                       argv++;
-                       argc--;
-                       goto no_reference;
-               }
-
-               arg = argv[0];
-               has_dash_dash = (argc > 1) && !strcmp(argv[1], "--");
-
-               if (!strcmp(arg, "-"))
-                       arg = "@{-1}";
-
-               if (get_sha1_mb(arg, rev)) {
-                       if (has_dash_dash)          /* case (1) */
-                               die("invalid reference: %s", arg);
-                       if (!patch_mode &&
-                           dwim_new_local_branch &&
-                           opts.track == BRANCH_TRACK_UNSPECIFIED &&
-                           !opts.new_branch &&
-                           !check_filename(NULL, arg) &&
-                           argc == 1) {
-                               const char *remote = unique_tracking_name(arg);
-                               if (!remote || get_sha1(remote, rev))
-                                       goto no_reference;
-                               opts.new_branch = arg;
-                               arg = remote;
-                               /* DWIMmed to create local branch */
-                       }
-                       else
-                               goto no_reference;
-               }
-
-               /* we can't end up being in (2) anymore, eat the argument */
-               argv++;
-               argc--;
-
-               new.name = arg;
-               if ((new.commit = lookup_commit_reference_gently(rev, 1))) {
-                       setup_branch_path(&new);
-
-                       if ((check_ref_format(new.path) == CHECK_REF_FORMAT_OK) &&
-                           resolve_ref(new.path, rev, 1, NULL))
-                               ;
-                       else
-                               new.path = NULL;
-                       parse_commit(new.commit);
-                       source_tree = new.commit->tree;
-               } else
-                       source_tree = parse_tree_indirect(rev);
-
-               if (!source_tree)                   /* case (1): want a tree */
-                       die("reference is not a tree: %s", arg);
-               if (!has_dash_dash) {/* case (3 -> 1) */
-                       /*
-                        * Do not complain the most common case
-                        *      git checkout branch
-                        * even if there happen to be a file called 'branch';
-                        * it would be extremely annoying.
-                        */
-                       if (argc)
-                               verify_non_filename(NULL, arg);
-               }
-               else {
-                       argv++;
-                       argc--;
-               }
+               int dwim_ok =
+                       !patch_mode &&
+                       dwim_new_local_branch &&
+                       opts.track == BRANCH_TRACK_UNSPECIFIED &&
+                       !opts.new_branch;
+               int n = parse_branchname_arg(argc, argv, dwim_ok,
+                               &new, &source_tree, rev, &opts.new_branch);
+               argv += n;
+               argc -= n;
        }
 
-no_reference:
-
        if (opts.track == BRANCH_TRACK_UNSPECIFIED)
                opts.track = git_branch_track;
 
@@ -886,6 +933,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
                        }
                }
 
+               if (opts.force_detach)
+                       die("git checkout: --detach does not take a path argument");
+
                if (1 < !!opts.writeout_stage + !!opts.force + !!opts.merge)
                        die("git checkout: --ours/--theirs, --force and --merge are incompatible when\nchecking out of the index.");
 
index d7f55e3d46e215f94c94f1f40b5cc1ec6907701e..355b2cbca7460d40b013380c38801686e61b74f1 100644 (file)
@@ -119,13 +119,13 @@ static struct option builtin_commit_options[] = {
 
        OPT_GROUP("Commit message options"),
        OPT_FILENAME('F', "file", &logfile, "read message from file"),
-       OPT_STRING(0, "author", &force_author, "AUTHOR", "override author for commit"),
-       OPT_STRING(0, "date", &force_date, "DATE", "override date for commit"),
-       OPT_CALLBACK('m', "message", &message, "MESSAGE", "commit message", opt_parse_m),
-       OPT_STRING('c', "reedit-message", &edit_message, "COMMIT", "reuse and edit message from specified commit"),
-       OPT_STRING('C', "reuse-message", &use_message, "COMMIT", "reuse message from specified commit"),
-       OPT_STRING(0, "fixup", &fixup_message, "COMMIT", "use autosquash formatted message to fixup specified commit"),
-       OPT_STRING(0, "squash", &squash_message, "COMMIT", "use autosquash formatted message to squash specified commit"),
+       OPT_STRING(0, "author", &force_author, "author", "override author for commit"),
+       OPT_STRING(0, "date", &force_date, "date", "override date for commit"),
+       OPT_CALLBACK('m', "message", &message, "message", "commit message", opt_parse_m),
+       OPT_STRING('c', "reedit-message", &edit_message, "commit", "reuse and edit message from specified commit"),
+       OPT_STRING('C', "reuse-message", &use_message, "commit", "reuse message from specified commit"),
+       OPT_STRING(0, "fixup", &fixup_message, "commit", "use autosquash formatted message to fixup specified commit"),
+       OPT_STRING(0, "squash", &squash_message, "commit", "use autosquash formatted message to squash specified commit"),
        OPT_BOOLEAN(0, "reset-author", &renew_authorship, "the commit is authored by me now (used with -C-c/--amend)"),
        OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"),
        OPT_FILENAME('t', "template", &template_file, "use specified template file"),
index dad86fecfe8cd3b4ac5d4e1b39b74eb0f904db6b..76be0b786fd52f181f91b5ad3bdef891068946f1 100644 (file)
@@ -52,7 +52,7 @@ static struct option builtin_config_options[] = {
        OPT_BOOLEAN(0, "global", &use_global_config, "use global config file"),
        OPT_BOOLEAN(0, "system", &use_system_config, "use system config file"),
        OPT_BOOLEAN(0, "local", &use_local_config, "use repository config file"),
-       OPT_STRING('f', "file", &given_config_file, "FILE", "use given config file"),
+       OPT_STRING('f', "file", &given_config_file, "file", "use given config file"),
        OPT_GROUP("Action"),
        OPT_BIT(0, "get", &actions, "get value: name [value-regex]", ACTION_GET),
        OPT_BIT(0, "get-all", &actions, "get all values: key [value-regex]", ACTION_GET_ALL),
@@ -153,7 +153,6 @@ static int show_config(const char *key_, const char *value_, void *cb)
 static int get_value(const char *key_, const char *regex_)
 {
        int ret = -1;
-       char *tl;
        char *global = NULL, *repo_config = NULL;
        const char *system_wide = NULL, *local;
 
@@ -167,18 +166,32 @@ static int get_value(const char *key_, const char *regex_)
                        system_wide = git_etc_gitconfig();
        }
 
-       key = xstrdup(key_);
-       for (tl=key+strlen(key)-1; tl >= key && *tl != '.'; --tl)
-               *tl = tolower(*tl);
-       for (tl=key; *tl && *tl != '.'; ++tl)
-               *tl = tolower(*tl);
-
        if (use_key_regexp) {
+               char *tl;
+
+               /*
+                * NEEDSWORK: this naive pattern lowercasing obviously does not
+                * work for more complex patterns like "^[^.]*Foo.*bar".
+                * Perhaps we should deprecate this altogether someday.
+                */
+
+               key = xstrdup(key_);
+               for (tl = key + strlen(key) - 1;
+                    tl >= key && *tl != '.';
+                    tl--)
+                       *tl = tolower(*tl);
+               for (tl = key; *tl && *tl != '.'; tl++)
+                       *tl = tolower(*tl);
+
                key_regexp = (regex_t*)xmalloc(sizeof(regex_t));
                if (regcomp(key_regexp, key, REG_EXTENDED)) {
                        fprintf(stderr, "Invalid key pattern: %s\n", key_);
+                       free(key);
                        goto free_strings;
                }
+       } else {
+               if (git_config_parse_key(key_, &key, NULL))
+                       goto free_strings;
        }
 
        if (regex_) {
index 951c7c8994704543fc1784c87a17a1aa47ede257..46085f862f937b005493319cea25b93bcb10c999 100644 (file)
@@ -61,7 +61,7 @@ int cmd_diff_files(int argc, const char **argv, const char *prefix)
            (rev.diffopt.output_format & DIFF_FORMAT_PATCH))
                rev.combine_merges = rev.dense_combined_merges = 1;
 
-       if (read_cache_preload(rev.diffopt.paths) < 0) {
+       if (read_cache_preload(rev.diffopt.pathspec.raw) < 0) {
                perror("read_cache_preload");
                return -1;
        }
index 42822cd5374dbcf0e63c17078a55284c432ddc77..4c9deb28ec15d0c2adae795cc2c177b24256e585 100644 (file)
@@ -135,7 +135,7 @@ static int builtin_diff_index(struct rev_info *revs,
            revs->max_count != -1 || revs->min_age != -1 ||
            revs->max_age != -1)
                usage(builtin_diff_usage);
-       if (read_cache_preload(revs->diffopt.paths) < 0) {
+       if (read_cache_preload(revs->diffopt.pathspec.raw) < 0) {
                perror("read_cache_preload");
                return -1;
        }
@@ -237,7 +237,7 @@ static int builtin_diff_files(struct rev_info *revs, int argc, const char **argv
                revs->combine_merges = revs->dense_combined_merges = 1;
 
        setup_work_tree();
-       if (read_cache_preload(revs->diffopt.paths) < 0) {
+       if (read_cache_preload(revs->diffopt.pathspec.raw) < 0) {
                perror("read_cache_preload");
                return -1;
        }
@@ -374,14 +374,10 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
                }
                die("unhandled object '%s' given.", name);
        }
-       if (rev.prune_data) {
-               const char **pathspec = rev.prune_data;
-               while (*pathspec) {
-                       if (!path)
-                               path = *pathspec;
-                       paths++;
-                       pathspec++;
-               }
+       if (rev.prune_data.nr) {
+               if (!path)
+                       path = rev.prune_data.items[0].match;
+               paths += rev.prune_data.nr;
        }
 
        /*
index c8fd46b872780b27b09ad70316fa164d855f3220..daf19451ba7df541d1314c880679749232ccce35 100644 (file)
@@ -619,9 +619,9 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)
                OPT_CALLBACK(0, "tag-of-filtered-object", &tag_of_filtered_mode, "mode",
                             "select handling of tags that tag filtered objects",
                             parse_opt_tag_of_filtered_mode),
-               OPT_STRING(0, "export-marks", &export_filename, "FILE",
+               OPT_STRING(0, "export-marks", &export_filename, "file",
                             "Dump marks to this file"),
-               OPT_STRING(0, "import-marks", &import_filename, "FILE",
+               OPT_STRING(0, "import-marks", &import_filename, "file",
                             "Import marks from this file"),
                OPT_BOOLEAN(0, "fake-missing-tagger", &fake_missing_tagger,
                             "Fake a tagger when tags lack one"),
@@ -651,7 +651,7 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)
        if (import_filename)
                import_marks(import_filename);
 
-       if (import_filename && revs.prune_data)
+       if (import_filename && revs.prune_data.nr)
                full_tree = 1;
 
        get_tags_and_duplicates(&revs.pending, &extra_refs);
index 357f3cdbbfd601e2ce3f1261a4d6b30c1257cda4..7efecfe1cf6b14a53d619b4bcc4760c81a492747 100644 (file)
@@ -49,7 +49,7 @@ static struct option builtin_fetch_options[] = {
                    "fetch from all remotes"),
        OPT_BOOLEAN('a', "append", &append,
                    "append to .git/FETCH_HEAD instead of overwriting"),
-       OPT_STRING(0, "upload-pack", &upload_pack, "PATH",
+       OPT_STRING(0, "upload-pack", &upload_pack, "path",
                   "path to upload pack on remote end"),
        OPT__FORCE(&force, "force overwrite of local branch"),
        OPT_BOOLEAN('m', "multiple", &multiple,
@@ -69,9 +69,9 @@ static struct option builtin_fetch_options[] = {
        OPT_BOOLEAN('u', "update-head-ok", &update_head_ok,
                    "allow updating of HEAD ref"),
        OPT_BOOLEAN(0, "progress", &progress, "force progress reporting"),
-       OPT_STRING(0, "depth", &depth, "DEPTH",
+       OPT_STRING(0, "depth", &depth, "depth",
                   "deepen history of shallow clone"),
-       { OPTION_STRING, 0, "submodule-prefix", &submodule_prefix, "DIR",
+       { OPTION_STRING, 0, "submodule-prefix", &submodule_prefix, "dir",
                   "prepend this to submodule path output", PARSE_OPT_HIDDEN },
        OPT_END()
 };
index fdf7131efd25618250f382dff178ca38f9b42c77..c3af8760cc778b4eb835d4fb543f45aa333b214c 100644 (file)
@@ -329,106 +329,6 @@ static int grep_config(const char *var, const char *value, void *cb)
        return 0;
 }
 
-/*
- * Return non-zero if max_depth is negative or path has no more then max_depth
- * slashes.
- */
-static int accept_subdir(const char *path, int max_depth)
-{
-       if (max_depth < 0)
-               return 1;
-
-       while ((path = strchr(path, '/')) != NULL) {
-               max_depth--;
-               if (max_depth < 0)
-                       return 0;
-               path++;
-       }
-       return 1;
-}
-
-/*
- * Return non-zero if name is a subdirectory of match and is not too deep.
- */
-static int is_subdir(const char *name, int namelen,
-               const char *match, int matchlen, int max_depth)
-{
-       if (matchlen > namelen || strncmp(name, match, matchlen))
-               return 0;
-
-       if (name[matchlen] == '\0') /* exact match */
-               return 1;
-
-       if (!matchlen || match[matchlen-1] == '/' || name[matchlen] == '/')
-               return accept_subdir(name + matchlen + 1, max_depth);
-
-       return 0;
-}
-
-/*
- * git grep pathspecs are somewhat different from diff-tree pathspecs;
- * pathname wildcards are allowed.
- */
-static int pathspec_matches(const char **paths, const char *name, int max_depth)
-{
-       int namelen, i;
-       if (!paths || !*paths)
-               return accept_subdir(name, max_depth);
-       namelen = strlen(name);
-       for (i = 0; paths[i]; i++) {
-               const char *match = paths[i];
-               int matchlen = strlen(match);
-               const char *cp, *meta;
-
-               if (is_subdir(name, namelen, match, matchlen, max_depth))
-                       return 1;
-               if (!fnmatch(match, name, 0))
-                       return 1;
-               if (name[namelen-1] != '/')
-                       continue;
-
-               /* We are being asked if the directory ("name") is worth
-                * descending into.
-                *
-                * Find the longest leading directory name that does
-                * not have metacharacter in the pathspec; the name
-                * we are looking at must overlap with that directory.
-                */
-               for (cp = match, meta = NULL; cp - match < matchlen; cp++) {
-                       char ch = *cp;
-                       if (ch == '*' || ch == '[' || ch == '?') {
-                               meta = cp;
-                               break;
-                       }
-               }
-               if (!meta)
-                       meta = cp; /* fully literal */
-
-               if (namelen <= meta - match) {
-                       /* Looking at "Documentation/" and
-                        * the pattern says "Documentation/howto/", or
-                        * "Documentation/diff*.txt".  The name we
-                        * have should match prefix.
-                        */
-                       if (!memcmp(match, name, namelen))
-                               return 1;
-                       continue;
-               }
-
-               if (meta - match < namelen) {
-                       /* Looking at "Documentation/howto/" and
-                        * the pattern says "Documentation/h*";
-                        * match up to "Do.../h"; this avoids descending
-                        * into "Documentation/technical/".
-                        */
-                       if (!memcmp(match, name, meta - match))
-                               return 1;
-                       continue;
-               }
-       }
-       return 0;
-}
-
 static void *lock_and_read_sha1_file(const unsigned char *sha1, enum object_type *type, unsigned long *size)
 {
        void *data;
@@ -581,7 +481,7 @@ static void run_pager(struct grep_opt *opt, const char *prefix)
        free(argv);
 }
 
-static int grep_cache(struct grep_opt *opt, const char **paths, int cached)
+static int grep_cache(struct grep_opt *opt, const struct pathspec *pathspec, int cached)
 {
        int hit = 0;
        int nr;
@@ -591,7 +491,7 @@ static int grep_cache(struct grep_opt *opt, const char **paths, int cached)
                struct cache_entry *ce = active_cache[nr];
                if (!S_ISREG(ce->ce_mode))
                        continue;
-               if (!pathspec_matches(paths, ce->name, opt->max_depth))
+               if (!match_pathspec_depth(pathspec, ce->name, ce_namelen(ce), 0, NULL))
                        continue;
                /*
                 * If CE_VALID is on, we assume worktree file and its cache entry
@@ -618,44 +518,29 @@ static int grep_cache(struct grep_opt *opt, const char **paths, int cached)
        return hit;
 }
 
-static int grep_tree(struct grep_opt *opt, const char **paths,
-                    struct tree_desc *tree,
-                    const char *tree_name, const char *base)
+static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec,
+                    struct tree_desc *tree, struct strbuf *base, int tn_len)
 {
-       int len;
-       int hit = 0;
+       int hit = 0, matched = 0;
        struct name_entry entry;
-       char *down;
-       int tn_len = strlen(tree_name);
-       struct strbuf pathbuf;
-
-       strbuf_init(&pathbuf, PATH_MAX + tn_len);
-
-       if (tn_len) {
-               strbuf_add(&pathbuf, tree_name, tn_len);
-               strbuf_addch(&pathbuf, ':');
-               tn_len = pathbuf.len;
-       }
-       strbuf_addstr(&pathbuf, base);
-       len = pathbuf.len;
+       int old_baselen = base->len;
 
        while (tree_entry(tree, &entry)) {
                int te_len = tree_entry_len(entry.path, entry.sha1);
-               pathbuf.len = len;
-               strbuf_add(&pathbuf, entry.path, te_len);
-
-               if (S_ISDIR(entry.mode))
-                       /* Match "abc/" against pathspec to
-                        * decide if we want to descend into "abc"
-                        * directory.
-                        */
-                       strbuf_addch(&pathbuf, '/');
-
-               down = pathbuf.buf + tn_len;
-               if (!pathspec_matches(paths, down, opt->max_depth))
-                       ;
-               else if (S_ISREG(entry.mode))
-                       hit |= grep_sha1(opt, entry.sha1, pathbuf.buf, tn_len);
+
+               if (matched != 2) {
+                       matched = tree_entry_interesting(&entry, base, tn_len, pathspec);
+                       if (matched == -1)
+                               break; /* no more matches */
+                       if (!matched)
+                               continue;
+               }
+
+               strbuf_add(base, entry.path, te_len);
+
+               if (S_ISREG(entry.mode)) {
+                       hit |= grep_sha1(opt, entry.sha1, base->buf, tn_len);
+               }
                else if (S_ISDIR(entry.mode)) {
                        enum object_type type;
                        struct tree_desc sub;
@@ -666,18 +551,21 @@ static int grep_tree(struct grep_opt *opt, const char **paths,
                        if (!data)
                                die("unable to read tree (%s)",
                                    sha1_to_hex(entry.sha1));
+
+                       strbuf_addch(base, '/');
                        init_tree_desc(&sub, data, size);
-                       hit |= grep_tree(opt, paths, &sub, tree_name, down);
+                       hit |= grep_tree(opt, pathspec, &sub, base, tn_len);
                        free(data);
                }
+               strbuf_setlen(base, old_baselen);
+
                if (hit && opt->status_only)
                        break;
        }
-       strbuf_release(&pathbuf);
        return hit;
 }
 
-static int grep_object(struct grep_opt *opt, const char **paths,
+static int grep_object(struct grep_opt *opt, const struct pathspec *pathspec,
                       struct object *obj, const char *name)
 {
        if (obj->type == OBJ_BLOB)
@@ -686,20 +574,30 @@ static int grep_object(struct grep_opt *opt, const char **paths,
                struct tree_desc tree;
                void *data;
                unsigned long size;
-               int hit;
+               struct strbuf base;
+               int hit, len;
+
                data = read_object_with_reference(obj->sha1, tree_type,
                                                  &size, NULL);
                if (!data)
                        die("unable to read tree (%s)", sha1_to_hex(obj->sha1));
+
+               len = name ? strlen(name) : 0;
+               strbuf_init(&base, PATH_MAX + len + 1);
+               if (len) {
+                       strbuf_add(&base, name, len);
+                       strbuf_addch(&base, ':');
+               }
                init_tree_desc(&tree, data, size);
-               hit = grep_tree(opt, paths, &tree, name, "");
+               hit = grep_tree(opt, pathspec, &tree, &base, base.len);
+               strbuf_release(&base);
                free(data);
                return hit;
        }
        die("unable to grep from object of type %s", typename(obj->type));
 }
 
-static int grep_objects(struct grep_opt *opt, const char **paths,
+static int grep_objects(struct grep_opt *opt, const struct pathspec *pathspec,
                        const struct object_array *list)
 {
        unsigned int i;
@@ -709,7 +607,7 @@ static int grep_objects(struct grep_opt *opt, const char **paths,
        for (i = 0; i < nr; i++) {
                struct object *real_obj;
                real_obj = deref_tag(list->objects[i].item, NULL, 0);
-               if (grep_object(opt, paths, real_obj, list->objects[i].name)) {
+               if (grep_object(opt, pathspec, real_obj, list->objects[i].name)) {
                        hit = 1;
                        if (opt->status_only)
                                break;
@@ -718,7 +616,7 @@ static int grep_objects(struct grep_opt *opt, const char **paths,
        return hit;
 }
 
-static int grep_directory(struct grep_opt *opt, const char **paths)
+static int grep_directory(struct grep_opt *opt, const struct pathspec *pathspec)
 {
        struct dir_struct dir;
        int i, hit = 0;
@@ -726,7 +624,7 @@ static int grep_directory(struct grep_opt *opt, const char **paths)
        memset(&dir, 0, sizeof(dir));
        setup_standard_excludes(&dir);
 
-       fill_directory(&dir, paths);
+       fill_directory(&dir, pathspec->raw);
        for (i = 0; i < dir.nr; i++) {
                hit |= grep_file(opt, dir.entries[i]->name);
                if (hit && opt->status_only)
@@ -832,6 +730,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
        struct grep_opt opt;
        struct object_array list = OBJECT_ARRAY_INIT;
        const char **paths = NULL;
+       struct pathspec pathspec;
        struct string_list path_list = STRING_LIST_INIT_NODUP;
        int i;
        int dummy;
@@ -1059,6 +958,9 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
                paths[0] = prefix;
                paths[1] = NULL;
        }
+       init_pathspec(&pathspec, paths);
+       pathspec.max_depth = opt.max_depth;
+       pathspec.recursive = 1;
 
        if (show_in_pager && (cached || list.nr))
                die("--open-files-in-pager only works on the worktree");
@@ -1089,16 +991,16 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
                        die("--cached cannot be used with --no-index.");
                if (list.nr)
                        die("--no-index cannot be used with revs.");
-               hit = grep_directory(&opt, paths);
+               hit = grep_directory(&opt, &pathspec);
        } else if (!list.nr) {
                if (!cached)
                        setup_work_tree();
 
-               hit = grep_cache(&opt, paths, cached);
+               hit = grep_cache(&opt, &pathspec, cached);
        } else {
                if (cached)
                        die("both --cached and trees are given.");
-               hit = grep_objects(&opt, paths, &list);
+               hit = grep_objects(&opt, &pathspec, &list);
        }
 
        if (use_threads)
index 080af1a01b8155680faf6c04101217b60ae7b919..c90acddcb2c32ce5170a220c9b1af96b44552a41 100644 (file)
@@ -15,7 +15,7 @@ static void hash_fd(int fd, const char *type, int write_object, const char *path
        struct stat st;
        unsigned char sha1[20];
        if (fstat(fd, &st) < 0 ||
-           index_fd(sha1, fd, &st, write_object, type_from_string(type), path))
+           index_fd(sha1, fd, &st, write_object, type_from_string(type), path, 1))
                die(write_object
                    ? "Unable to add %s to database"
                    : "Unable to hash %s", path);
index d8c6c28d2fcc3bd0744f071da536215141dc0d0b..f5ed690c435423662808fe0ff7f1e256374586db 100644 (file)
@@ -89,7 +89,7 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix,
                rev->always_show_header = 0;
        if (DIFF_OPT_TST(&rev->diffopt, FOLLOW_RENAMES)) {
                rev->always_show_header = 0;
-               if (rev->diffopt.nr_paths != 1)
+               if (rev->diffopt.pathspec.nr != 1)
                        usage("git logs can only follow renames on one pathname at a time");
        }
        for (i = 1; i < argc; i++) {
index 8c58c3cc4a7cfe5eae9675f627254f61c14f7be2..a89ddbb65578182d1d0cbd9b0104889a6dc05ac8 100644 (file)
@@ -194,7 +194,7 @@ static struct option builtin_merge_options[] = {
                "merge strategy to use", option_parse_strategy),
        OPT_CALLBACK('X', "strategy-option", &xopts, "option=value",
                "option for selected merge strategy", option_parse_x),
-       OPT_CALLBACK('m', "message", &merge_msg, "MESSAGE",
+       OPT_CALLBACK('m', "message", &merge_msg, "message",
                "merge commit message (for a non-fast-forward merge)",
                option_parse_message),
        OPT__VERBOSITY(&verbosity),
@@ -797,6 +797,32 @@ static void add_strategies(const char *string, unsigned attr)
 
 }
 
+static void write_merge_msg(void)
+{
+       int fd = open(git_path("MERGE_MSG"), O_WRONLY | O_CREAT, 0666);
+       if (fd < 0)
+               die_errno("Could not open '%s' for writing",
+                         git_path("MERGE_MSG"));
+       if (write_in_full(fd, merge_msg.buf, merge_msg.len) != merge_msg.len)
+               die_errno("Could not write to '%s'", git_path("MERGE_MSG"));
+       close(fd);
+}
+
+static void read_merge_msg(void)
+{
+       strbuf_reset(&merge_msg);
+       if (strbuf_read_file(&merge_msg, git_path("MERGE_MSG"), 0) < 0)
+               die_errno("Could not read from '%s'", git_path("MERGE_MSG"));
+}
+
+static void run_prepare_commit_msg(void)
+{
+       write_merge_msg();
+       run_hook(get_index_file(), "prepare-commit-msg",
+                git_path("MERGE_MSG"), "merge", NULL, NULL);
+       read_merge_msg();
+}
+
 static int merge_trivial(void)
 {
        unsigned char result_tree[20], result_commit[20];
@@ -808,6 +834,7 @@ static int merge_trivial(void)
        parent->next = xmalloc(sizeof(*parent->next));
        parent->next->item = remoteheads->item;
        parent->next->next = NULL;
+       run_prepare_commit_msg();
        commit_tree(merge_msg.buf, result_tree, parent, result_commit, NULL);
        finish(result_commit, "In-index merge");
        drop_save();
@@ -837,6 +864,7 @@ static int finish_automerge(struct commit_list *common,
        }
        free_commit_list(remoteheads);
        strbuf_addch(&merge_msg, '\n');
+       run_prepare_commit_msg();
        commit_tree(merge_msg.buf, result_tree, parents, result_commit, NULL);
        strbuf_addf(&buf, "Merge made by %s.", wt_strategy);
        finish(result_commit, buf.buf);
@@ -1318,14 +1346,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
                        die_errno("Could not write to '%s'", git_path("MERGE_HEAD"));
                close(fd);
                strbuf_addch(&merge_msg, '\n');
-               fd = open(git_path("MERGE_MSG"), O_WRONLY | O_CREAT, 0666);
-               if (fd < 0)
-                       die_errno("Could not open '%s' for writing",
-                                 git_path("MERGE_MSG"));
-               if (write_in_full(fd, merge_msg.buf, merge_msg.len) !=
-                       merge_msg.len)
-                       die_errno("Could not write to '%s'", git_path("MERGE_MSG"));
-               close(fd);
+               write_merge_msg();
                fd = open(git_path("MERGE_MODE"), O_WRONLY | O_CREAT | O_TRUNC, 0666);
                if (fd < 0)
                        die_errno("Could not open '%s' for writing",
index 4d5556e2cb5bccaf0100d9f5019e1a1c493e35e7..0aab150c52c839512b82b5efc6e978b7a80653e3 100644 (file)
@@ -537,16 +537,16 @@ static int add(int argc, const char **argv, const char *prefix)
        const unsigned char *note;
        struct msg_arg msg = { 0, 0, STRBUF_INIT };
        struct option options[] = {
-               { OPTION_CALLBACK, 'm', "message", &msg, "MSG",
+               { OPTION_CALLBACK, 'm', "message", &msg, "msg",
                        "note contents as a string", PARSE_OPT_NONEG,
                        parse_msg_arg},
-               { OPTION_CALLBACK, 'F', "file", &msg, "FILE",
+               { OPTION_CALLBACK, 'F', "file", &msg, "file",
                        "note contents in a file", PARSE_OPT_NONEG,
                        parse_file_arg},
-               { OPTION_CALLBACK, 'c', "reedit-message", &msg, "OBJECT",
+               { OPTION_CALLBACK, 'c', "reedit-message", &msg, "object",
                        "reuse and edit specified note object", PARSE_OPT_NONEG,
                        parse_reedit_arg},
-               { OPTION_CALLBACK, 'C', "reuse-message", &msg, "OBJECT",
+               { OPTION_CALLBACK, 'C', "reuse-message", &msg, "object",
                        "reuse specified note object", PARSE_OPT_NONEG,
                        parse_reuse_arg},
                OPT__FORCE(&force, "replace existing notes"),
@@ -682,16 +682,16 @@ static int append_edit(int argc, const char **argv, const char *prefix)
        const char * const *usage;
        struct msg_arg msg = { 0, 0, STRBUF_INIT };
        struct option options[] = {
-               { OPTION_CALLBACK, 'm', "message", &msg, "MSG",
+               { OPTION_CALLBACK, 'm', "message", &msg, "msg",
                        "note contents as a string", PARSE_OPT_NONEG,
                        parse_msg_arg},
-               { OPTION_CALLBACK, 'F', "file", &msg, "FILE",
+               { OPTION_CALLBACK, 'F', "file", &msg, "file",
                        "note contents in a file", PARSE_OPT_NONEG,
                        parse_file_arg},
-               { OPTION_CALLBACK, 'c', "reedit-message", &msg, "OBJECT",
+               { OPTION_CALLBACK, 'c', "reedit-message", &msg, "object",
                        "reuse and edit specified note object", PARSE_OPT_NONEG,
                        parse_reedit_arg},
-               { OPTION_CALLBACK, 'C', "reuse-message", &msg, "OBJECT",
+               { OPTION_CALLBACK, 'C', "reuse-message", &msg, "object",
                        "reuse specified note object", PARSE_OPT_NONEG,
                        parse_reuse_arg},
                OPT_END()
index 512530022edac398f8541ee6c400c7312659e730..49a0472a9bd28274c4be1352996e700a1db4b94a 100644 (file)
@@ -73,6 +73,8 @@ int get_one_patchid(unsigned char *next_sha1, git_SHA_CTX *ctx)
                        p += 7;
                else if (!memcmp(line, "From ", 5))
                        p += 5;
+               else if (!memcmp(line, "\\ ", 2) && 12 < strlen(line))
+                       continue;
 
                if (!get_sha1_hex(p, next_sha1)) {
                        found_next = 1;
index e655eb7695faba13c4d9e8f25b9649ffec7195be..31da418cf4a9e4732edb8201c80b07f7ddadbac1 100644 (file)
@@ -64,17 +64,17 @@ static void set_refspecs(const char **refs, int nr)
        }
 }
 
-static void setup_push_tracking(void)
+static void setup_push_upstream(void)
 {
        struct strbuf refspec = STRBUF_INIT;
        struct branch *branch = branch_get(NULL);
        if (!branch)
                die("You are not currently on a branch.");
        if (!branch->merge_nr || !branch->merge)
-               die("The current branch %s is not tracking anything.",
+               die("The current branch %s has no upstream branch.",
                    branch->name);
        if (branch->merge_nr != 1)
-               die("The current branch %s is tracking multiple branches, "
+               die("The current branch %s has multiple upstream branches, "
                    "refusing to push.", branch->name);
        strbuf_addf(&refspec, "%s:%s", branch->name, branch->merge[0]->src);
        add_refspec(refspec.buf);
@@ -88,8 +88,8 @@ static void setup_default_push_refspecs(void)
                add_refspec(":");
                break;
 
-       case PUSH_DEFAULT_TRACKING:
-               setup_push_tracking();
+       case PUSH_DEFAULT_UPSTREAM:
+               setup_push_upstream();
                break;
 
        case PUSH_DEFAULT_CURRENT:
index 73c89ed15ba2fb0c470141499c3390b77cf1d81c..93c92814cf28855aee87cabf4a3b906e11a36125 100644 (file)
@@ -104,8 +104,8 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
        struct unpack_trees_options opts;
        int prefix_set = 0;
        const struct option read_tree_options[] = {
-               { OPTION_CALLBACK, 0, "index-output", NULL, "FILE",
-                 "write resulting index to <FILE>",
+               { OPTION_CALLBACK, 0, "index-output", NULL, "file",
+                 "write resulting index to <file>",
                  PARSE_OPT_NONEG, index_output_cb },
                OPT_SET_INT(0, "empty", &read_empty,
                            "only empty the index", 1),
index 642bf35587ed994948d3eeac0a189ae29e39bf11..67cbfeb152386a1c918a103a2073d83138bfdff0 100644 (file)
@@ -8,7 +8,7 @@
 #include "xdiff-interface.h"
 
 static const char * const rerere_usage[] = {
-       "git rerere [clear | status | diff | gc]",
+       "git rerere [clear | status | remaining | diff | gc]",
        NULL,
 };
 
@@ -156,7 +156,17 @@ int cmd_rerere(int argc, const char **argv, const char *prefix)
        else if (!strcmp(argv[0], "status"))
                for (i = 0; i < merge_rr.nr; i++)
                        printf("%s\n", merge_rr.items[i].string);
-       else if (!strcmp(argv[0], "diff"))
+       else if (!strcmp(argv[0], "remaining")) {
+               rerere_remaining(&merge_rr);
+               for (i = 0; i < merge_rr.nr; i++) {
+                       if (merge_rr.items[i].util != RERERE_RESOLVED)
+                               printf("%s\n", merge_rr.items[i].string);
+                       else
+                               /* prepare for later call to
+                                * string_list_clear() */
+                               merge_rr.items[i].util = NULL;
+               }
+       } else if (!strcmp(argv[0], "diff"))
                for (i = 0; i < merge_rr.nr; i++) {
                        const char *path = merge_rr.items[i].string;
                        const char *name = (const char *)merge_rr.items[i].util;
index 246a2bc72bf9e89454a27265f6dbcd904698feab..7cf48abca857f049243dfab7c95fea70d216ea93 100644 (file)
@@ -376,7 +376,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
                OPT_GROUP("Tag creation options"),
                OPT_BOOLEAN('a', NULL, &annotate,
                                        "annotated tag, needs a message"),
-               OPT_CALLBACK('m', NULL, &msg, "MESSAGE",
+               OPT_CALLBACK('m', NULL, &msg, "message",
                             "tag message", parse_msg_arg),
                OPT_FILENAME('F', NULL, &msgfile, "read message from file"),
                OPT_BOOLEAN('s', NULL, &sign, "annotated and GPG-signed tag"),
index 56baf27fb7eb18d77e8793d089f2a719d4876689..d7850c6309aec0c1a4f640999fb757d7b32ba1b8 100644 (file)
@@ -546,7 +546,10 @@ static int do_reupdate(int ac, const char **av,
         */
        int pos;
        int has_head = 1;
-       const char **pathspec = get_pathspec(prefix, av + 1);
+       const char **paths = get_pathspec(prefix, av + 1);
+       struct pathspec pathspec;
+
+       init_pathspec(&pathspec, paths);
 
        if (read_ref("HEAD", head_sha1))
                /* If there is no HEAD, that means it is an initial
@@ -559,7 +562,7 @@ static int do_reupdate(int ac, const char **av,
                struct cache_entry *old = NULL;
                int save_nr;
 
-               if (ce_stage(ce) || !ce_path_match(ce, pathspec))
+               if (ce_stage(ce) || !ce_path_match(ce, &pathspec))
                        continue;
                if (has_head)
                        old = read_one_ent(NULL, head_sha1,
@@ -578,6 +581,7 @@ static int do_reupdate(int ac, const char **av,
                if (save_nr != active_nr)
                        goto redo;
        }
+       free_pathspec(&pathspec);
        return 0;
 }
 
diff --git a/cache.h b/cache.h
index 4a758babec4b53bf8b98572128c5bbf11019beb3..08a902210c6374c65f94a1238d895a65c7cde43a 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -500,8 +500,23 @@ extern int index_name_is_other(const struct index_state *, const char *, int);
 extern int ie_match_stat(const struct index_state *, struct cache_entry *, struct stat *, unsigned int);
 extern int ie_modified(const struct index_state *, struct cache_entry *, struct stat *, unsigned int);
 
-extern int ce_path_match(const struct cache_entry *ce, const char **pathspec);
-extern int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, enum object_type type, const char *path);
+struct pathspec {
+       const char **raw; /* get_pathspec() result, not freed by free_pathspec() */
+       int nr;
+       int has_wildcard:1;
+       int recursive:1;
+       int max_depth;
+       struct pathspec_item {
+               const char *match;
+               int len;
+               int has_wildcard:1;
+       } *items;
+};
+
+extern int init_pathspec(struct pathspec *, const char **);
+extern void free_pathspec(struct pathspec *);
+extern int ce_path_match(const struct cache_entry *ce, const struct pathspec *pathspec);
+extern int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, enum object_type type, const char *path, int format_check);
 extern int index_path(unsigned char *sha1, const char *path, struct stat *st, int write_object);
 extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st);
 
@@ -608,7 +623,7 @@ enum rebase_setup_type {
 enum push_default_type {
        PUSH_DEFAULT_NOTHING = 0,
        PUSH_DEFAULT_MATCHING,
-       PUSH_DEFAULT_TRACKING,
+       PUSH_DEFAULT_UPSTREAM,
        PUSH_DEFAULT_CURRENT
 };
 
@@ -999,6 +1014,7 @@ extern int git_config_maybe_bool(const char *, const char *);
 extern int git_config_string(const char **, const char *, const char *);
 extern int git_config_pathname(const char **, const char *, const char *);
 extern int git_config_set(const char *, const char *);
+extern int git_config_parse_key(const char *, char **, int *);
 extern int git_config_set_multivar(const char *, const char *, const char *, int);
 extern int git_config_rename_section(const char *, const char *);
 extern const char *git_etc_gitconfig(void);
index bee605441910caeddd1e94eb7795f615a3d1348a..878b1de97c6c437857e7a29e1b9afc677610f1fe 100644 (file)
@@ -2,6 +2,9 @@
 #include "win32.h"
 #include <conio.h>
 #include "../strbuf.h"
+#include "../run-command.h"
+
+static const int delay[] = { 0, 1, 10, 20, 40 };
 
 int err_win_to_posix(DWORD winerr)
 {
@@ -116,6 +119,165 @@ int err_win_to_posix(DWORD winerr)
        return error;
 }
 
+static inline int is_file_in_use_error(DWORD errcode)
+{
+       switch (errcode) {
+       case ERROR_SHARING_VIOLATION:
+       case ERROR_ACCESS_DENIED:
+               return 1;
+       }
+
+       return 0;
+}
+
+static int read_yes_no_answer(void)
+{
+       char answer[1024];
+
+       if (fgets(answer, sizeof(answer), stdin)) {
+               size_t answer_len = strlen(answer);
+               int got_full_line = 0, c;
+
+               /* remove the newline */
+               if (answer_len >= 2 && answer[answer_len-2] == '\r') {
+                       answer[answer_len-2] = '\0';
+                       got_full_line = 1;
+               } else if (answer_len >= 1 && answer[answer_len-1] == '\n') {
+                       answer[answer_len-1] = '\0';
+                       got_full_line = 1;
+               }
+               /* flush the buffer in case we did not get the full line */
+               if (!got_full_line)
+                       while ((c = getchar()) != EOF && c != '\n')
+                               ;
+       } else
+               /* we could not read, return the
+                * default answer which is no */
+               return 0;
+
+       if (tolower(answer[0]) == 'y' && !answer[1])
+               return 1;
+       if (!strncasecmp(answer, "yes", sizeof(answer)))
+               return 1;
+       if (tolower(answer[0]) == 'n' && !answer[1])
+               return 0;
+       if (!strncasecmp(answer, "no", sizeof(answer)))
+               return 0;
+
+       /* did not find an answer we understand */
+       return -1;
+}
+
+static int ask_yes_no_if_possible(const char *format, ...)
+{
+       char question[4096];
+       const char *retry_hook[] = { NULL, NULL, NULL };
+       va_list args;
+
+       va_start(args, format);
+       vsnprintf(question, sizeof(question), format, args);
+       va_end(args);
+
+       if ((retry_hook[0] = getenv("GIT_ASK_YESNO"))) {
+               retry_hook[1] = question;
+               return !run_command_v_opt(retry_hook, 0);
+       }
+
+       if (!isatty(_fileno(stdin)) || !isatty(_fileno(stderr)))
+               return 0;
+
+       while (1) {
+               int answer;
+               fprintf(stderr, "%s (y/n) ", question);
+
+               if ((answer = read_yes_no_answer()) >= 0)
+                       return answer;
+
+               fprintf(stderr, "Sorry, I did not understand your answer. "
+                               "Please type 'y' or 'n'\n");
+       }
+}
+
+#undef unlink
+int mingw_unlink(const char *pathname)
+{
+       int ret, tries = 0;
+
+       /* read-only files cannot be removed */
+       chmod(pathname, 0666);
+       while ((ret = unlink(pathname)) == -1 && tries < ARRAY_SIZE(delay)) {
+               if (!is_file_in_use_error(GetLastError()))
+                       break;
+               /*
+                * We assume that some other process had the source or
+                * destination file open at the wrong moment and retry.
+                * In order to give the other process a higher chance to
+                * complete its operation, we give up our time slice now.
+                * If we have to retry again, we do sleep a bit.
+                */
+               Sleep(delay[tries]);
+               tries++;
+       }
+       while (ret == -1 && is_file_in_use_error(GetLastError()) &&
+              ask_yes_no_if_possible("Unlink of file '%s' failed. "
+                       "Should I try again?", pathname))
+              ret = unlink(pathname);
+       return ret;
+}
+
+static int is_dir_empty(const char *path)
+{
+       struct strbuf buf = STRBUF_INIT;
+       WIN32_FIND_DATAA findbuf;
+       HANDLE handle;
+
+       strbuf_addf(&buf, "%s\\*", path);
+       handle = FindFirstFileA(buf.buf, &findbuf);
+       if (handle == INVALID_HANDLE_VALUE) {
+               strbuf_release(&buf);
+               return GetLastError() == ERROR_NO_MORE_FILES;
+       }
+
+       while (!strcmp(findbuf.cFileName, ".") ||
+                       !strcmp(findbuf.cFileName, ".."))
+               if (!FindNextFile(handle, &findbuf)) {
+                       strbuf_release(&buf);
+                       return GetLastError() == ERROR_NO_MORE_FILES;
+               }
+       FindClose(handle);
+       strbuf_release(&buf);
+       return 0;
+}
+
+#undef rmdir
+int mingw_rmdir(const char *pathname)
+{
+       int ret, tries = 0;
+
+       while ((ret = rmdir(pathname)) == -1 && tries < ARRAY_SIZE(delay)) {
+               if (!is_file_in_use_error(GetLastError()))
+                       break;
+               if (!is_dir_empty(pathname)) {
+                       errno = ENOTEMPTY;
+                       break;
+               }
+               /*
+                * We assume that some other process had the source or
+                * destination file open at the wrong moment and retry.
+                * In order to give the other process a higher chance to
+                * complete its operation, we give up our time slice now.
+                * If we have to retry again, we do sleep a bit.
+                */
+               Sleep(delay[tries]);
+               tries++;
+       }
+       while (ret == -1 && is_file_in_use_error(GetLastError()) &&
+              ask_yes_no_if_possible("Deletion of directory '%s' failed. "
+                       "Should I try again?", pathname))
+              ret = rmdir(pathname);
+       return ret;
+}
+
 #undef open
 int mingw_open (const char *filename, int oflags, ...)
 {
@@ -1249,7 +1411,6 @@ int mingw_rename(const char *pold, const char *pnew)
 {
        DWORD attrs, gle;
        int tries = 0;
-       static const int delay[] = { 0, 1, 10, 20, 40 };
 
        /*
         * Try native rename() first to get errno right.
@@ -1291,6 +1452,11 @@ int mingw_rename(const char *pold, const char *pnew)
                tries++;
                goto repeat;
        }
+       if (gle == ERROR_ACCESS_DENIED &&
+              ask_yes_no_if_possible("Rename from '%s' to '%s' failed. "
+                      "Should I try again?", pold, pnew))
+               goto repeat;
+
        errno = EACCES;
        return -1;
 }
index cafc1eb08a71dd60566a7389ed606e777cf17fc8..fe6ba340437b67237afd8cfdb4cf2a20074d27f7 100644 (file)
@@ -119,14 +119,6 @@ static inline int mingw_mkdir(const char *path, int mode)
 }
 #define mkdir mingw_mkdir
 
-static inline int mingw_unlink(const char *pathname)
-{
-       /* read-only files cannot be removed */
-       chmod(pathname, 0666);
-       return unlink(pathname);
-}
-#define unlink mingw_unlink
-
 #define WNOHANG 1
 pid_t waitpid(pid_t pid, int *status, unsigned options);
 
@@ -174,6 +166,12 @@ int link(const char *oldpath, const char *newpath);
  * replacements of existing functions
  */
 
+int mingw_unlink(const char *pathname);
+#define unlink mingw_unlink
+
+int mingw_rmdir(const char *path);
+#define rmdir mingw_rmdir
+
 int mingw_open (const char *filename, int oflags, ...);
 #define open mingw_open
 
index 625e0518767712583f917762634c2fc852c4d2eb..31f778b5e8999bd898a18d8ea121c23a01fed352 100644 (file)
--- a/config.c
+++ b/config.c
@@ -737,8 +737,10 @@ static int git_default_push_config(const char *var, const char *value)
                        push_default = PUSH_DEFAULT_NOTHING;
                else if (!strcmp(value, "matching"))
                        push_default = PUSH_DEFAULT_MATCHING;
-               else if (!strcmp(value, "tracking"))
-                       push_default = PUSH_DEFAULT_TRACKING;
+               else if (!strcmp(value, "upstream"))
+                       push_default = PUSH_DEFAULT_UPSTREAM;
+               else if (!strcmp(value, "tracking")) /* deprecated */
+                       push_default = PUSH_DEFAULT_UPSTREAM;
                else if (!strcmp(value, "current"))
                        push_default = PUSH_DEFAULT_CURRENT;
                else {
@@ -1098,6 +1100,75 @@ int git_config_set(const char *key, const char *value)
        return git_config_set_multivar(key, value, NULL, 0);
 }
 
+/*
+ * Auxiliary function to sanity-check and split the key into the section
+ * identifier and variable name.
+ *
+ * Returns 0 on success, -1 when there is an invalid character in the key and
+ * -2 if there is no section name in the key.
+ *
+ * store_key - pointer to char* which will hold a copy of the key with
+ *             lowercase section and variable name
+ * baselen - pointer to int which will hold the length of the
+ *           section + subsection part, can be NULL
+ */
+int git_config_parse_key(const char *key, char **store_key, int *baselen_)
+{
+       int i, dot, baselen;
+       const char *last_dot = strrchr(key, '.');
+
+       /*
+        * Since "key" actually contains the section name and the real
+        * key name separated by a dot, we have to know where the dot is.
+        */
+
+       if (last_dot == NULL || last_dot == key) {
+               error("key does not contain a section: %s", key);
+               return -2;
+       }
+
+       if (!last_dot[1]) {
+               error("key does not contain variable name: %s", key);
+               return -2;
+       }
+
+       baselen = last_dot - key;
+       if (baselen_)
+               *baselen_ = baselen;
+
+       /*
+        * Validate the key and while at it, lower case it for matching.
+        */
+       *store_key = xmalloc(strlen(key) + 1);
+
+       dot = 0;
+       for (i = 0; key[i]; i++) {
+               unsigned char c = key[i];
+               if (c == '.')
+                       dot = 1;
+               /* Leave the extended basename untouched.. */
+               if (!dot || i > baselen) {
+                       if (!iskeychar(c) ||
+                           (i == baselen + 1 && !isalpha(c))) {
+                               error("invalid key: %s", key);
+                               goto out_free_ret_1;
+                       }
+                       c = tolower(c);
+               } else if (c == '\n') {
+                       error("invalid key (newline): %s", key);
+                       goto out_free_ret_1;
+               }
+               (*store_key)[i] = c;
+       }
+       (*store_key)[i] = 0;
+
+       return 0;
+
+out_free_ret_1:
+       free(*store_key);
+       return -1;
+}
+
 /*
  * If value==NULL, unset in (remove from) config,
  * if value_regex!=NULL, disregard key/value pairs where value does not match.
@@ -1124,59 +1195,23 @@ int git_config_set(const char *key, const char *value)
 int git_config_set_multivar(const char *key, const char *value,
        const char *value_regex, int multi_replace)
 {
-       int i, dot;
        int fd = -1, in_fd;
        int ret;
        char *config_filename;
        struct lock_file *lock = NULL;
-       const char *last_dot = strrchr(key, '.');
 
        if (config_exclusive_filename)
                config_filename = xstrdup(config_exclusive_filename);
        else
                config_filename = git_pathdup("config");
 
-       /*
-        * Since "key" actually contains the section name and the real
-        * key name separated by a dot, we have to know where the dot is.
-        */
-
-       if (last_dot == NULL) {
-               error("key does not contain a section: %s", key);
-               ret = 2;
+       /* parse-key returns negative; flip the sign to feed exit(3) */
+       ret = 0 - git_config_parse_key(key, &store.key, &store.baselen);
+       if (ret)
                goto out_free;
-       }
-       store.baselen = last_dot - key;
 
        store.multi_replace = multi_replace;
 
-       /*
-        * Validate the key and while at it, lower case it for matching.
-        */
-       store.key = xmalloc(strlen(key) + 1);
-       dot = 0;
-       for (i = 0; key[i]; i++) {
-               unsigned char c = key[i];
-               if (c == '.')
-                       dot = 1;
-               /* Leave the extended basename untouched.. */
-               if (!dot || i > store.baselen) {
-                       if (!iskeychar(c) || (i == store.baselen+1 && !isalpha(c))) {
-                               error("invalid key: %s", key);
-                               free(store.key);
-                               ret = 1;
-                               goto out_free;
-                       }
-                       c = tolower(c);
-               } else if (c == '\n') {
-                       error("invalid key (newline): %s", key);
-                       free(store.key);
-                       ret = 1;
-                       goto out_free;
-               }
-               store.key[i] = c;
-       }
-       store.key[i] = 0;
 
        /*
         * The lock serves a purpose in addition to locking: the new
index a92beb6292b49aebb12bfbb4535a94f148b88a3d..a4f440d11696caa6ee4756108bec78a85d4b04f7 100755 (executable)
@@ -543,13 +543,13 @@ class P4Submit(Command):
         self.options = [
                 optparse.make_option("--verbose", dest="verbose", action="store_true"),
                 optparse.make_option("--origin", dest="origin"),
-                optparse.make_option("-M", dest="detectRename", action="store_true"),
+                optparse.make_option("-M", dest="detectRenames", action="store_true"),
         ]
         self.description = "Submit changes from git to the perforce depot."
         self.usage += " [name of git branch to submit into perforce depot]"
         self.interactive = True
         self.origin = ""
-        self.detectRename = False
+        self.detectRenames = False
         self.verbose = False
         self.isWindows = (platform.system() == "Windows")
 
@@ -613,7 +613,22 @@ class P4Submit(Command):
 
     def applyCommit(self, id):
         print "Applying %s" % (read_pipe("git log --max-count=1 --pretty=oneline %s" % id))
-        diffOpts = ("", "-M")[self.detectRename]
+
+        if not self.detectRenames:
+            # If not explicitly set check the config variable
+            self.detectRenames = gitConfig("git-p4.detectRenames").lower() == "true"
+
+        if self.detectRenames:
+            diffOpts = "-M"
+        else:
+            diffOpts = ""
+
+        if gitConfig("git-p4.detectCopies").lower() == "true":
+            diffOpts += " -C"
+
+        if gitConfig("git-p4.detectCopiesHarder").lower() == "true":
+            diffOpts += " --find-copies-harder"
+
         diff = read_pipe_lines("git diff-tree -r %s \"%s^\" \"%s\"" % (diffOpts, id, id))
         filesToAdd = set()
         filesToDelete = set()
@@ -637,11 +652,23 @@ class P4Submit(Command):
                 filesToDelete.add(path)
                 if path in filesToAdd:
                     filesToAdd.remove(path)
+            elif modifier == "C":
+                src, dest = diff['src'], diff['dst']
+                p4_system("integrate -Dt \"%s\" \"%s\"" % (src, dest))
+                if diff['src_sha1'] != diff['dst_sha1']:
+                    p4_system("edit \"%s\"" % (dest))
+                if isModeExecChanged(diff['src_mode'], diff['dst_mode']):
+                    p4_system("edit \"%s\"" % (dest))
+                    filesToChangeExecBit[dest] = diff['dst_mode']
+                os.unlink(dest)
+                editedFiles.add(dest)
             elif modifier == "R":
                 src, dest = diff['src'], diff['dst']
                 p4_system("integrate -Dt \"%s\" \"%s\"" % (src, dest))
-                p4_system("edit \"%s\"" % (dest))
+                if diff['src_sha1'] != diff['dst_sha1']:
+                    p4_system("edit \"%s\"" % (dest))
                 if isModeExecChanged(diff['src_mode'], diff['dst_mode']):
+                    p4_system("edit \"%s\"" % (dest))
                     filesToChangeExecBit[dest] = diff['dst_mode']
                 os.unlink(dest)
                 editedFiles.add(dest)
@@ -834,6 +861,8 @@ class P4Submit(Command):
         return True
 
 class P4Sync(Command):
+    delete_actions = ( "delete", "move/delete", "purge" )
+
     def __init__(self):
         Command.__init__(self)
         self.options = [
@@ -882,6 +911,23 @@ class P4Sync(Command):
         if gitConfig("git-p4.syncFromOrigin") == "false":
             self.syncWithOrigin = False
 
+    #
+    # P4 wildcards are not allowed in filenames.  P4 complains
+    # if you simply add them, but you can force it with "-f", in
+    # which case it translates them into %xx encoding internally.
+    # Search for and fix just these four characters.  Do % last so
+    # that fixing it does not inadvertently create new %-escapes.
+    #
+    def wildcard_decode(self, path):
+        # Cannot have * in a filename in windows; untested as to
+        # what p4 would do in such a case.
+        if not self.isWindows:
+            path = path.replace("%2A", "*")
+        path = path.replace("%23", "#") \
+                   .replace("%40", "@") \
+                   .replace("%25", "%")
+        return path
+
     def extractFilesFromCommit(self, commit):
         self.cloneExclude = [re.sub(r"\.\.\.$", "", path)
                              for path in self.cloneExclude]
@@ -976,6 +1022,7 @@ class P4Sync(Command):
            return
 
         relPath = self.stripRepoPath(file['depotFile'], self.branchPrefixes)
+        relPath = self.wildcard_decode(relPath)
         if verbose:
             sys.stderr.write("%s\n" % relPath)
 
@@ -1054,10 +1101,10 @@ class P4Sync(Command):
 
             if includeFile:
                 filesForCommit.append(f)
-                if f['action'] not in ('delete', 'move/delete', 'purge'):
-                    filesToRead.append(f)
-                else:
+                if f['action'] in self.delete_actions:
                     filesToDelete.append(f)
+                else:
+                    filesToRead.append(f)
 
         # deleted files...
         for f in filesToDelete:
@@ -1143,7 +1190,7 @@ class P4Sync(Command):
 
                 cleanedFiles = {}
                 for info in files:
-                    if info["action"] in ("delete", "purge"):
+                    if info["action"] in self.delete_actions:
                         continue
                     cleanedFiles[info["depotFile"]] = info["rev"]
 
@@ -1445,7 +1492,7 @@ class P4Sync(Command):
         print "Doing initial import of %s from revision %s into %s" % (' '.join(self.depotPaths), revision, self.branch)
 
         details = { "user" : "git perforce import user", "time" : int(time.time()) }
-        details["desc"] = ("Initial import of %s from the state at revision %s"
+        details["desc"] = ("Initial import of %s from the state at revision %s\n"
                            % (' '.join(self.depotPaths), revision))
         details["change"] = revision
         newestRevision = 0
@@ -1456,9 +1503,16 @@ class P4Sync(Command):
                                            % (p, revision)
                                            for p in self.depotPaths])):
 
-            if info['code'] == 'error':
+            if 'code' in info and info['code'] == 'error':
                 sys.stderr.write("p4 returned an error: %s\n"
                                  % info['data'])
+                if info['data'].find("must refer to client") >= 0:
+                    sys.stderr.write("This particular p4 error is misleading.\n")
+                    sys.stderr.write("Perhaps the depot path was misspelled.\n");
+                    sys.stderr.write("Depot path:  %s\n" % " ".join(self.depotPaths))
+                sys.exit(1)
+            if 'p4ExitCode' in info:
+                sys.stderr.write("p4 exitcode: %s\n" % info['p4ExitCode'])
                 sys.exit(1)
 
 
@@ -1466,7 +1520,7 @@ class P4Sync(Command):
             if change > newestRevision:
                 newestRevision = change
 
-            if info["action"] in ("delete", "purge"):
+            if info["action"] in self.delete_actions:
                 # don't increase the file cnt, otherwise details["depotFile123"] will have gaps!
                 #fileCnt = fileCnt + 1
                 continue
@@ -1709,6 +1763,8 @@ class P4Sync(Command):
 
                 changes.sort()
             else:
+                if not self.p4BranchesInGit:
+                    die("No remote p4 branches.  Perhaps you never did \"git p4 clone\" in here.");
                 if self.verbose:
                     print "Getting p4 changes for %s...%s" % (', '.join(self.depotPaths),
                                                               self.changeRange)
@@ -1789,10 +1845,13 @@ class P4Clone(P4Sync):
                                  help="where to leave result of the clone"),
             optparse.make_option("-/", dest="cloneExclude",
                                  action="append", type="string",
-                                 help="exclude depot path")
+                                 help="exclude depot path"),
+            optparse.make_option("--bare", dest="cloneBare",
+                                 action="store_true", default=False),
         ]
         self.cloneDestination = None
         self.needsGit = False
+        self.cloneBare = False
 
     # This is required for the "append" cloneExclude action
     def ensure_value(self, attr, value):
@@ -1832,11 +1891,16 @@ class P4Clone(P4Sync):
             self.cloneDestination = self.defaultDestination(args)
 
         print "Importing from %s into %s" % (', '.join(depotPaths), self.cloneDestination)
+
         if not os.path.exists(self.cloneDestination):
             os.makedirs(self.cloneDestination)
         chdir(self.cloneDestination)
-        system("git init")
-        self.gitdir = os.getcwd() + "/.git"
+
+        init_cmd = [ "git", "init" ]
+        if self.cloneBare:
+            init_cmd.append("--bare")
+        subprocess.check_call(init_cmd)
+
         if not P4Sync.run(self, depotPaths):
             return False
         if self.branch != "master":
@@ -1846,7 +1910,8 @@ class P4Clone(P4Sync):
                 masterbranch = "refs/heads/p4/master"
             if gitBranchExists(masterbranch):
                 system("git branch master %s" % masterbranch)
-                system("git checkout -f")
+                if not self.cloneBare:
+                    system("git checkout -f")
             else:
                 print "Could not detect main branch. No checkout/master branch created."
 
index 392ce2bef05746cea7922d39da67bf25d1d3d192..1e22992cb10420b9dd6def16f80efc5f196ffbbb 100644 (file)
@@ -106,7 +106,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
                        DIFF_OPT_TST(&revs->diffopt, HAS_CHANGES))
                        break;
 
-               if (!ce_path_match(ce, revs->prune_data))
+               if (!ce_path_match(ce, &revs->prune_data))
                        continue;
 
                if (ce_stage(ce)) {
@@ -427,7 +427,7 @@ static int oneway_diff(struct cache_entry **src, struct unpack_trees_options *o)
        if (tree == o->df_conflict_entry)
                tree = NULL;
 
-       if (ce_path_match(idx ? idx : tree, revs->prune_data))
+       if (ce_path_match(idx ? idx : tree, &revs->prune_data))
                do_oneway_diff(o, idx, tree);
 
        return 0;
@@ -501,7 +501,7 @@ int do_diff_cache(const unsigned char *tree_sha1, struct diff_options *opt)
        active_nr = dst - active_cache;
 
        init_revisions(&revs, NULL);
-       revs.prune_data = opt->paths;
+       init_pathspec(&revs.prune_data, opt->pathspec.raw);
        tree = parse_tree_indirect(tree_sha1);
        if (!tree)
                die("bad tree object %s", sha1_to_hex(tree_sha1));
index ce9e783407437bb1e0efc6d5bc2392af26da5a41..3a36144687ae2f5bf7bb3afc914ddbada8d5ff93 100644 (file)
@@ -231,8 +231,9 @@ void diff_no_index(struct rev_info *revs,
 
        if (prefix) {
                int len = strlen(prefix);
+               const char *paths[3];
+               memset(paths, 0, sizeof(paths));
 
-               revs->diffopt.paths = xcalloc(2, sizeof(char *));
                for (i = 0; i < 2; i++) {
                        const char *p = argv[argc - 2 + i];
                        /*
@@ -242,12 +243,12 @@ void diff_no_index(struct rev_info *revs,
                        p = (strcmp(p, "-")
                             ? xstrdup(prefix_filename(prefix, len, p))
                             : p);
-                       revs->diffopt.paths[i] = p;
+                       paths[i] = p;
                }
+               diff_tree_setup_paths(paths, &revs->diffopt);
        }
        else
-               revs->diffopt.paths = argv + argc - 2;
-       revs->diffopt.nr_paths = 2;
+               diff_tree_setup_paths(argv + argc - 2, &revs->diffopt);
        revs->diffopt.skip_stat_unmatch = 1;
        if (!revs->diffopt.output_format)
                revs->diffopt.output_format = DIFF_FORMAT_PATCH;
@@ -259,8 +260,8 @@ void diff_no_index(struct rev_info *revs,
        if (diff_setup_done(&revs->diffopt) < 0)
                die("diff_setup_done failed");
 
-       if (queue_diff(&revs->diffopt, revs->diffopt.paths[0],
-                      revs->diffopt.paths[1]))
+       if (queue_diff(&revs->diffopt, revs->diffopt.pathspec.raw[0],
+                      revs->diffopt.pathspec.raw[1]))
                exit(1);
        diff_set_mnemonic_prefix(&revs->diffopt, "1/", "2/");
        diffcore_std(&revs->diffopt);
diff --git a/diff.h b/diff.h
index 0083d92438916a8188656df140ba70d6acc8c6f6..310bd6b2832ce7f874aff735bb19b4813f117d4d 100644 (file)
--- a/diff.h
+++ b/diff.h
@@ -133,9 +133,7 @@ struct diff_options {
        FILE *file;
        int close_file;
 
-       int nr_paths;
-       const char **paths;
-       int *pathlens;
+       struct pathspec pathspec;
        change_fn_t change;
        add_remove_fn_t add_remove;
        diff_format_fn_t format_callback;
diff --git a/dir.c b/dir.c
index 570b651a17520cbb0273b9247ab0fcffc0129477..168dad615230d77d7719101b76b50b4f6fe02777 100644 (file)
--- a/dir.c
+++ b/dir.c
@@ -87,6 +87,21 @@ int fill_directory(struct dir_struct *dir, const char **pathspec)
        return len;
 }
 
+int within_depth(const char *name, int namelen,
+                       int depth, int max_depth)
+{
+       const char *cp = name, *cpe = name + namelen;
+
+       while (cp < cpe) {
+               if (*cp++ != '/')
+                       continue;
+               depth++;
+               if (depth > max_depth)
+                       return 0;
+       }
+       return 1;
+}
+
 /*
  * Does 'match' match the given name?
  * A match is found if
@@ -184,6 +199,95 @@ int match_pathspec(const char **pathspec, const char *name, int namelen,
        return retval;
 }
 
+/*
+ * Does 'match' match the given name?
+ * A match is found if
+ *
+ * (1) the 'match' string is leading directory of 'name', or
+ * (2) the 'match' string is a wildcard and matches 'name', or
+ * (3) the 'match' string is exactly the same as 'name'.
+ *
+ * and the return value tells which case it was.
+ *
+ * It returns 0 when there is no match.
+ */
+static int match_pathspec_item(const struct pathspec_item *item, int prefix,
+                              const char *name, int namelen)
+{
+       /* name/namelen has prefix cut off by caller */
+       const char *match = item->match + prefix;
+       int matchlen = item->len - prefix;
+
+       /* If the match was just the prefix, we matched */
+       if (!*match)
+               return MATCHED_RECURSIVELY;
+
+       if (matchlen <= namelen && !strncmp(match, name, matchlen)) {
+               if (matchlen == namelen)
+                       return MATCHED_EXACTLY;
+
+               if (match[matchlen-1] == '/' || name[matchlen] == '/')
+                       return MATCHED_RECURSIVELY;
+       }
+
+       if (item->has_wildcard && !fnmatch(match, name, 0))
+               return MATCHED_FNMATCH;
+
+       return 0;
+}
+
+/*
+ * Given a name and a list of pathspecs, see if the name matches
+ * any of the pathspecs.  The caller is also interested in seeing
+ * all pathspec matches some names it calls this function with
+ * (otherwise the user could have mistyped the unmatched pathspec),
+ * and a mark is left in seen[] array for pathspec element that
+ * actually matched anything.
+ */
+int match_pathspec_depth(const struct pathspec *ps,
+                        const char *name, int namelen,
+                        int prefix, char *seen)
+{
+       int i, retval = 0;
+
+       if (!ps->nr) {
+               if (!ps->recursive || ps->max_depth == -1)
+                       return MATCHED_RECURSIVELY;
+
+               if (within_depth(name, namelen, 0, ps->max_depth))
+                       return MATCHED_EXACTLY;
+               else
+                       return 0;
+       }
+
+       name += prefix;
+       namelen -= prefix;
+
+       for (i = ps->nr - 1; i >= 0; i--) {
+               int how;
+               if (seen && seen[i] == MATCHED_EXACTLY)
+                       continue;
+               how = match_pathspec_item(ps->items+i, prefix, name, namelen);
+               if (ps->recursive && ps->max_depth != -1 &&
+                   how && how != MATCHED_FNMATCH) {
+                       int len = ps->items[i].len;
+                       if (name[len] == '/')
+                               len++;
+                       if (within_depth(name+len, namelen-len, 0, ps->max_depth))
+                               how = MATCHED_EXACTLY;
+                       else
+                               how = 0;
+               }
+               if (how) {
+                       if (retval < how)
+                               retval = how;
+                       if (seen && seen[i] < how)
+                               seen[i] = how;
+               }
+       }
+       return retval;
+}
+
 static int no_wildcard(const char *string)
 {
        return string[strcspn(string, "*?[{\\")] == '\0';
@@ -1151,3 +1255,50 @@ int remove_path(const char *name)
        return 0;
 }
 
+static int pathspec_item_cmp(const void *a_, const void *b_)
+{
+       struct pathspec_item *a, *b;
+
+       a = (struct pathspec_item *)a_;
+       b = (struct pathspec_item *)b_;
+       return strcmp(a->match, b->match);
+}
+
+int init_pathspec(struct pathspec *pathspec, const char **paths)
+{
+       const char **p = paths;
+       int i;
+
+       memset(pathspec, 0, sizeof(*pathspec));
+       if (!p)
+               return 0;
+       while (*p)
+               p++;
+       pathspec->raw = paths;
+       pathspec->nr = p - paths;
+       if (!pathspec->nr)
+               return 0;
+
+       pathspec->items = xmalloc(sizeof(struct pathspec_item)*pathspec->nr);
+       for (i = 0; i < pathspec->nr; i++) {
+               struct pathspec_item *item = pathspec->items+i;
+               const char *path = paths[i];
+
+               item->match = path;
+               item->len = strlen(path);
+               item->has_wildcard = !no_wildcard(path);
+               if (item->has_wildcard)
+                       pathspec->has_wildcard = 1;
+       }
+
+       qsort(pathspec->items, pathspec->nr,
+             sizeof(struct pathspec_item), pathspec_item_cmp);
+
+       return 0;
+}
+
+void free_pathspec(struct pathspec *pathspec)
+{
+       free(pathspec->items);
+       pathspec->items = NULL;
+}
diff --git a/dir.h b/dir.h
index 72a764ed84828e4a6336d2880f2167fbc9d3143a..aa511da77b0ec54cbdbd3786faad0c1285df7182 100644 (file)
--- a/dir.h
+++ b/dir.h
@@ -65,6 +65,10 @@ struct dir_struct {
 #define MATCHED_FNMATCH 2
 #define MATCHED_EXACTLY 3
 extern int match_pathspec(const char **pathspec, const char *name, int namelen, int prefix, char *seen);
+extern int match_pathspec_depth(const struct pathspec *pathspec,
+                               const char *name, int namelen,
+                               int prefix, char *seen);
+extern int within_depth(const char *name, int namelen, int depth, int max_depth);
 
 extern int fill_directory(struct dir_struct *dir, const char **pathspec);
 extern int read_directory(struct dir_struct *, const char *path, int len, const char **pathspec);
index 2f8dc441c6d575323f2fad920ecdb7b20e1445e6..bacbda2bb75854235b912903c7e45fc94d38e719 100755 (executable)
@@ -269,7 +269,7 @@ rerere=false
 files_to_merge() {
     if test "$rerere" = true
     then
-       git rerere status
+       git rerere remaining
     else
        git ls-files -u | sed -e 's/^[^ ]*      //' | sort -u
     fi
diff --git a/git.c b/git.c
index 65ed68ffc13c5e1d893a5e0d84da795d9f8f698f..ef598c3e7053b8dd2859f4d582ce2917a804fe42 100644 (file)
--- a/git.c
+++ b/git.c
@@ -313,7 +313,6 @@ static void handle_internal_command(int argc, const char **argv)
        const char *cmd = argv[0];
        static struct cmd_struct commands[] = {
                { "add", cmd_add, RUN_SETUP | NEED_WORK_TREE },
-               { "stage", cmd_add, RUN_SETUP | NEED_WORK_TREE },
                { "annotate", cmd_annotate, RUN_SETUP },
                { "apply", cmd_apply, RUN_SETUP_GENTLY },
                { "archive", cmd_archive },
@@ -322,15 +321,15 @@ static void handle_internal_command(int argc, const char **argv)
                { "branch", cmd_branch, RUN_SETUP },
                { "bundle", cmd_bundle, RUN_SETUP_GENTLY },
                { "cat-file", cmd_cat_file, RUN_SETUP },
+               { "check-attr", cmd_check_attr, RUN_SETUP },
+               { "check-ref-format", cmd_check_ref_format },
                { "checkout", cmd_checkout, RUN_SETUP | NEED_WORK_TREE },
                { "checkout-index", cmd_checkout_index,
                        RUN_SETUP | NEED_WORK_TREE},
-               { "check-ref-format", cmd_check_ref_format },
-               { "check-attr", cmd_check_attr, RUN_SETUP },
                { "cherry", cmd_cherry, RUN_SETUP },
                { "cherry-pick", cmd_cherry_pick, RUN_SETUP | NEED_WORK_TREE },
-               { "clone", cmd_clone },
                { "clean", cmd_clean, RUN_SETUP | NEED_WORK_TREE },
+               { "clone", cmd_clone },
                { "commit", cmd_commit, RUN_SETUP | NEED_WORK_TREE },
                { "commit-tree", cmd_commit_tree, RUN_SETUP },
                { "config", cmd_config, RUN_SETUP_GENTLY },
@@ -358,8 +357,8 @@ static void handle_internal_command(int argc, const char **argv)
                { "init-db", cmd_init_db },
                { "log", cmd_log, RUN_SETUP },
                { "ls-files", cmd_ls_files, RUN_SETUP },
-               { "ls-tree", cmd_ls_tree, RUN_SETUP },
                { "ls-remote", cmd_ls_remote, RUN_SETUP_GENTLY },
+               { "ls-tree", cmd_ls_tree, RUN_SETUP },
                { "mailinfo", cmd_mailinfo },
                { "mailsplit", cmd_mailsplit },
                { "merge", cmd_merge, RUN_SETUP | NEED_WORK_TREE },
@@ -379,6 +378,7 @@ static void handle_internal_command(int argc, const char **argv)
                { "notes", cmd_notes, RUN_SETUP },
                { "pack-objects", cmd_pack_objects, RUN_SETUP },
                { "pack-redundant", cmd_pack_redundant, RUN_SETUP },
+               { "pack-refs", cmd_pack_refs, RUN_SETUP },
                { "patch-id", cmd_patch_id },
                { "peek-remote", cmd_ls_remote, RUN_SETUP_GENTLY },
                { "pickaxe", cmd_blame, RUN_SETUP },
@@ -401,8 +401,10 @@ static void handle_internal_command(int argc, const char **argv)
                { "rm", cmd_rm, RUN_SETUP },
                { "send-pack", cmd_send_pack, RUN_SETUP },
                { "shortlog", cmd_shortlog, RUN_SETUP_GENTLY | USE_PAGER },
-               { "show-branch", cmd_show_branch, RUN_SETUP },
                { "show", cmd_show, RUN_SETUP },
+               { "show-branch", cmd_show_branch, RUN_SETUP },
+               { "show-ref", cmd_show_ref, RUN_SETUP },
+               { "stage", cmd_add, RUN_SETUP | NEED_WORK_TREE },
                { "status", cmd_status, RUN_SETUP | NEED_WORK_TREE },
                { "stripspace", cmd_stripspace },
                { "symbolic-ref", cmd_symbolic_ref, RUN_SETUP },
@@ -415,13 +417,11 @@ static void handle_internal_command(int argc, const char **argv)
                { "update-server-info", cmd_update_server_info, RUN_SETUP },
                { "upload-archive", cmd_upload_archive },
                { "var", cmd_var, RUN_SETUP_GENTLY },
+               { "verify-pack", cmd_verify_pack },
                { "verify-tag", cmd_verify_tag, RUN_SETUP },
                { "version", cmd_version },
                { "whatchanged", cmd_whatchanged, RUN_SETUP },
                { "write-tree", cmd_write_tree, RUN_SETUP },
-               { "verify-pack", cmd_verify_pack },
-               { "show-ref", cmd_show_ref, RUN_SETUP },
-               { "pack-refs", cmd_pack_refs, RUN_SETUP },
        };
        int i;
        static const char ext[] = STRIP_EXTENSION;
index 8953548c07bb36f20798c7ca344d07960c22618c..61f6cc98d917c3e4395ec397ac74df6a40eeb07f 100644 (file)
@@ -61,12 +61,15 @@ static void process_tree(struct rev_info *revs,
                         struct tree *tree,
                         show_object_fn show,
                         struct name_path *path,
+                        struct strbuf *base,
                         const char *name)
 {
        struct object *obj = &tree->object;
        struct tree_desc desc;
        struct name_entry entry;
        struct name_path me;
+       int all_interesting = (revs->diffopt.pathspec.nr == 0);
+       int baselen = base->len;
 
        if (!revs->tree_objects)
                return;
@@ -82,13 +85,32 @@ static void process_tree(struct rev_info *revs,
        me.elem = name;
        me.elem_len = strlen(name);
 
+       if (!all_interesting) {
+               strbuf_addstr(base, name);
+               if (base->len)
+                       strbuf_addch(base, '/');
+       }
+
        init_tree_desc(&desc, tree->buffer, tree->size);
 
        while (tree_entry(&desc, &entry)) {
+               if (!all_interesting) {
+                       int showit = tree_entry_interesting(&entry,
+                                                           base, 0,
+                                                           &revs->diffopt.pathspec);
+
+                       if (showit < 0)
+                               break;
+                       else if (!showit)
+                               continue;
+                       else if (showit == 2)
+                               all_interesting = 1;
+               }
+
                if (S_ISDIR(entry.mode))
                        process_tree(revs,
                                     lookup_tree(entry.sha1),
-                                    show, &me, entry.path);
+                                    show, &me, base, entry.path);
                else if (S_ISGITLINK(entry.mode))
                        process_gitlink(revs, entry.sha1,
                                        show, &me, entry.path);
@@ -97,6 +119,7 @@ static void process_tree(struct rev_info *revs,
                                     lookup_blob(entry.sha1),
                                     show, &me, entry.path);
        }
+       strbuf_setlen(base, baselen);
        free(tree->buffer);
        tree->buffer = NULL;
 }
@@ -146,7 +169,9 @@ void traverse_commit_list(struct rev_info *revs,
 {
        int i;
        struct commit *commit;
+       struct strbuf base;
 
+       strbuf_init(&base, PATH_MAX);
        while ((commit = get_revision(revs)) != NULL) {
                add_pending_tree(revs, commit->tree);
                show_commit(commit, data);
@@ -164,7 +189,7 @@ void traverse_commit_list(struct rev_info *revs,
                }
                if (obj->type == OBJ_TREE) {
                        process_tree(revs, (struct tree *)obj, show_object,
-                                    NULL, name);
+                                    NULL, &base, name);
                        continue;
                }
                if (obj->type == OBJ_BLOB) {
@@ -181,4 +206,5 @@ void traverse_commit_list(struct rev_info *revs,
                revs->pending.alloc = 0;
                revs->pending.objects = NULL;
        }
+       strbuf_release(&base);
 }
index 31ec5d24763b13b3e97c79f7a3edd6e7799bcd91..d1b12fe979b34ac9bbf2ff637934b96f3878a53d 100644 (file)
@@ -141,7 +141,7 @@ struct option {
        { OPTION_NUMBER, 0, NULL, (v), NULL, (h), \
          PARSE_OPT_NOARG | PARSE_OPT_NONEG, (f) }
 #define OPT_FILENAME(s, l, v, h)    { OPTION_FILENAME, (s), (l), (v), \
-                                      "FILE", (h) }
+                                      "file", (h) }
 #define OPT_COLOR_FLAG(s, l, v, h) \
        { OPTION_CALLBACK, (s), (l), (v), "when", (h), PARSE_OPT_OPTARG, \
                parse_opt_color_flag_cb, (intptr_t)"always" }
index 205e48aa3a7b912005565ac9c296205a897a1476..a86ab709c25b5e110aa2708941cea560f82c1fa8 100644 (file)
@@ -99,7 +99,7 @@ =head1 DESCRIPTION
 
 use Carp qw(carp croak); # but croak is bad - throw instead
 use Error qw(:try);
-use Cwd qw(abs_path);
+use Cwd qw(abs_path cwd);
 use IPC::Open2 qw(open2);
 use Fcntl qw(SEEK_SET SEEK_CUR);
 }
@@ -396,7 +396,16 @@ sub command_close_pipe {
 
 sub command_bidi_pipe {
        my ($pid, $in, $out);
+       my ($self) = _maybe_self(@_);
+       local %ENV = %ENV;
+       my $cwd_save = undef;
+       if ($self) {
+               shift;
+               $cwd_save = cwd();
+               _setup_git_cmd_env($self);
+       }
        $pid = open2($in, $out, 'git', @_);
+       chdir($cwd_save) if $cwd_save;
        return ($pid, $in, $out, join(' ', @_));
 }
 
@@ -843,7 +852,7 @@ sub _open_hash_and_insert_object_if_needed {
 
        ($self->{hash_object_pid}, $self->{hash_object_in},
         $self->{hash_object_out}, $self->{hash_object_ctx}) =
-               command_bidi_pipe(qw(hash-object -w --stdin-paths --no-filters));
+               $self->command_bidi_pipe(qw(hash-object -w --stdin-paths --no-filters));
 }
 
 sub _close_hash_and_insert_object {
@@ -932,7 +941,7 @@ sub _open_cat_blob_if_needed {
 
        ($self->{cat_blob_pid}, $self->{cat_blob_in},
         $self->{cat_blob_out}, $self->{cat_blob_ctx}) =
-               command_bidi_pipe(qw(cat-file --batch));
+               $self->command_bidi_pipe(qw(cat-file --batch));
 }
 
 sub _close_cat_blob {
@@ -1279,6 +1288,14 @@ sub _command_common_pipe {
 # for the given repository and execute the git command.
 sub _cmd_exec {
        my ($self, @args) = @_;
+       _setup_git_cmd_env($self);
+       _execv_git_cmd(@args);
+       die qq[exec "@args" failed: $!];
+}
+
+# set up the appropriate state for git command
+sub _setup_git_cmd_env {
+       my $self = shift;
        if ($self) {
                $self->repo_path() and $ENV{'GIT_DIR'} = $self->repo_path();
                $self->repo_path() and $self->wc_path()
@@ -1286,8 +1303,6 @@ sub _cmd_exec {
                $self->wc_path() and chdir($self->wc_path());
                $self->wc_subdir() and chdir($self->wc_subdir());
        }
-       _execv_git_cmd(@args);
-       die qq[exec "@args" failed: $!];
 }
 
 # Execute the given Git command ($_[0]) with arguments ($_[1..])
index e3d0bda31a98372eb9b6a8c2cd0fd65917a9dbde..49cb08df96faa90101bb25d8f96acaffc066f19b 100644 (file)
@@ -35,7 +35,9 @@ static void *preload_thread(void *_data)
        struct index_state *index = p->index;
        struct cache_entry **cep = index->cache + p->offset;
        struct cache_def cache;
+       struct pathspec pathspec;
 
+       init_pathspec(&pathspec, p->pathspec);
        memset(&cache, 0, sizeof(cache));
        nr = p->nr;
        if (nr + p->offset > index->cache_nr)
@@ -51,7 +53,7 @@ static void *preload_thread(void *_data)
                        continue;
                if (ce_uptodate(ce))
                        continue;
-               if (!ce_path_match(ce, p->pathspec))
+               if (!ce_path_match(ce, &pathspec))
                        continue;
                if (threaded_has_symlink_leading_path(&cache, ce->name, ce_namelen(ce)))
                        continue;
@@ -61,6 +63,7 @@ static void *preload_thread(void *_data)
                        continue;
                ce_mark_uptodate(ce);
        } while (--nr > 0);
+       free_pathspec(&pathspec);
        return NULL;
 }
 
index 15b0a73b6280b11dae91b2a1250ebdfb8a6c458f..98d526bd48d2a0e764dd0efb4c6efe4965174c9a 100644 (file)
@@ -92,7 +92,7 @@ static int ce_compare_data(struct cache_entry *ce, struct stat *st)
 
        if (fd >= 0) {
                unsigned char sha1[20];
-               if (!index_fd(sha1, fd, st, 0, OBJ_BLOB, ce->name))
+               if (!index_fd(sha1, fd, st, 0, OBJ_BLOB, ce->name, 0))
                        match = hashcmp(sha1, ce->sha1);
                /* index_fd() closed the file descriptor already */
        }
@@ -706,30 +706,9 @@ int ce_same_name(struct cache_entry *a, struct cache_entry *b)
        return ce_namelen(b) == len && !memcmp(a->name, b->name, len);
 }
 
-int ce_path_match(const struct cache_entry *ce, const char **pathspec)
+int ce_path_match(const struct cache_entry *ce, const struct pathspec *pathspec)
 {
-       const char *match, *name;
-       int len;
-
-       if (!pathspec)
-               return 1;
-
-       len = ce_namelen(ce);
-       name = ce->name;
-       while ((match = *pathspec++) != NULL) {
-               int matchlen = strlen(match);
-               if (matchlen > len)
-                       continue;
-               if (memcmp(name, match, matchlen))
-                       continue;
-               if (matchlen && name[matchlen-1] == '/')
-                       return 1;
-               if (name[matchlen] == '/' || !name[matchlen])
-                       return 1;
-               if (!matchlen)
-                       return 1;
-       }
-       return 0;
+       return match_pathspec_depth(pathspec, ce->name, ce_namelen(ce), 0, NULL);
 }
 
 /*
index 04d4813e4183c675b54aba942cd078d8ff632053..d0fb0a044a85ec6169c4623c7c2ab2143dbb5784 100644 (file)
@@ -356,14 +356,59 @@ static size_t rpc_in(const void *ptr, size_t eltsize,
        return size;
 }
 
+static int run_slot(struct active_request_slot *slot)
+{
+       int err = 0;
+       struct slot_results results;
+
+       slot->results = &results;
+       slot->curl_result = curl_easy_perform(slot->curl);
+       finish_active_slot(slot);
+
+       if (results.curl_result != CURLE_OK) {
+               err |= error("RPC failed; result=%d, HTTP code = %ld",
+                       results.curl_result, results.http_code);
+       }
+
+       return err;
+}
+
+static int probe_rpc(struct rpc_state *rpc)
+{
+       struct active_request_slot *slot;
+       struct curl_slist *headers = NULL;
+       struct strbuf buf = STRBUF_INIT;
+       int err;
+
+       slot = get_active_slot();
+
+       headers = curl_slist_append(headers, rpc->hdr_content_type);
+       headers = curl_slist_append(headers, rpc->hdr_accept);
+
+       curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 0);
+       curl_easy_setopt(slot->curl, CURLOPT_POST, 1);
+       curl_easy_setopt(slot->curl, CURLOPT_URL, rpc->service_url);
+       curl_easy_setopt(slot->curl, CURLOPT_ENCODING, "");
+       curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDS, "0000");
+       curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDSIZE, 4);
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, headers);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_FILE, &buf);
+
+       err = run_slot(slot);
+
+       curl_slist_free_all(headers);
+       strbuf_release(&buf);
+       return err;
+}
+
 static int post_rpc(struct rpc_state *rpc)
 {
        struct active_request_slot *slot;
-       struct slot_results results;
        struct curl_slist *headers = NULL;
        int use_gzip = rpc->gzip_request;
        char *gzip_body = NULL;
-       int err = 0, large_request = 0;
+       int err, large_request = 0;
 
        /* Try to load the entire request, if we can fit it into the
         * allocated buffer space we can use HTTP/1.0 and avoid the
@@ -386,8 +431,13 @@ static int post_rpc(struct rpc_state *rpc)
                rpc->len += n;
        }
 
+       if (large_request) {
+               err = probe_rpc(rpc);
+               if (err)
+                       return err;
+       }
+
        slot = get_active_slot();
-       slot->results = &results;
 
        curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 0);
        curl_easy_setopt(slot->curl, CURLOPT_POST, 1);
@@ -401,7 +451,7 @@ static int post_rpc(struct rpc_state *rpc)
                /* The request body is large and the size cannot be predicted.
                 * We must use chunked encoding to send it.
                 */
-               headers = curl_slist_append(headers, "Expect: 100-continue");
+               headers = curl_slist_append(headers, "Expect:");
                headers = curl_slist_append(headers, "Transfer-Encoding: chunked");
                rpc->initial_buffer = 1;
                curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, rpc_out);
@@ -475,13 +525,7 @@ static int post_rpc(struct rpc_state *rpc)
        curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, rpc_in);
        curl_easy_setopt(slot->curl, CURLOPT_FILE, rpc);
 
-       slot->curl_result = curl_easy_perform(slot->curl);
-       finish_active_slot(slot);
-
-       if (results.curl_result != CURLE_OK) {
-               err |= error("RPC failed; result=%d, HTTP code = %ld",
-                       results.curl_result, results.http_code);
-       }
+       err = run_slot(slot);
 
        curl_slist_free_all(headers);
        free(gzip_body);
index d2608434750c336c3f3881efda8373b7c67d4b11..22996bd08b532fb8016f24391dd0e63010706385 100644 (file)
--- a/rerere.c
+++ b/rerere.c
@@ -7,6 +7,11 @@
 #include "ll-merge.h"
 #include "attr.h"
 
+#define RESOLVED 0
+#define PUNTED 1
+#define THREE_STAGED 2
+void *RERERE_RESOLVED = &RERERE_RESOLVED;
+
 /* if rerere_enabled == -1, fall back to detection of .git/rr-cache */
 static int rerere_enabled = -1;
 
@@ -345,21 +350,74 @@ static int handle_cache(const char *path, unsigned char *sha1, const char *outpu
        return hunk_no;
 }
 
-static int find_conflict(struct string_list *conflict)
+static int check_one_conflict(int i, int *type)
 {
-       int i;
-       if (read_cache() < 0)
-               return error("Could not read index");
-       for (i = 0; i+1 < active_nr; i++) {
+       struct cache_entry *e = active_cache[i];
+
+       if (!ce_stage(e)) {
+               *type = RESOLVED;
+               return i + 1;
+       }
+
+       *type = PUNTED;
+       if (ce_stage(e) == 1) {
+               if (active_nr <= ++i)
+                       return i + 1;
+       }
+
+       /* Only handle regular files with both stages #2 and #3 */
+       if (i + 1 < active_nr) {
                struct cache_entry *e2 = active_cache[i];
-               struct cache_entry *e3 = active_cache[i+1];
+               struct cache_entry *e3 = active_cache[i + 1];
                if (ce_stage(e2) == 2 &&
                    ce_stage(e3) == 3 &&
-                   ce_same_name(e2, e3) &&
+                   ce_same_name(e, e3) &&
                    S_ISREG(e2->ce_mode) &&
-                   S_ISREG(e3->ce_mode)) {
-                       string_list_insert(conflict, (const char *)e2->name);
-                       i++; /* skip over both #2 and #3 */
+                   S_ISREG(e3->ce_mode))
+                       *type = THREE_STAGED;
+       }
+
+       /* Skip the entries with the same name */
+       while (i < active_nr && ce_same_name(e, active_cache[i]))
+               i++;
+       return i;
+}
+
+static int find_conflict(struct string_list *conflict)
+{
+       int i;
+       if (read_cache() < 0)
+               return error("Could not read index");
+
+       for (i = 0; i < active_nr;) {
+               int conflict_type;
+               struct cache_entry *e = active_cache[i];
+               i = check_one_conflict(i, &conflict_type);
+               if (conflict_type == THREE_STAGED)
+                       string_list_insert(conflict, (const char *)e->name);
+       }
+       return 0;
+}
+
+int rerere_remaining(struct string_list *merge_rr)
+{
+       int i;
+       if (read_cache() < 0)
+               return error("Could not read index");
+
+       for (i = 0; i < active_nr;) {
+               int conflict_type;
+               struct cache_entry *e = active_cache[i];
+               i = check_one_conflict(i, &conflict_type);
+               if (conflict_type == PUNTED)
+                       string_list_insert(merge_rr, (const char *)e->name);
+               else if (conflict_type == RESOLVED) {
+                       struct string_list_item *it;
+                       it = string_list_lookup(merge_rr, (const char *)e->name);
+                       if (it != NULL) {
+                               free(it->util);
+                               it->util = RERERE_RESOLVED;
+                       }
                }
        }
        return 0;
index eaa9004dcdbb2f0dfd446e1ac68237c6dff18530..595f49f701dc1003927679085ca6eea8ad1083ae 100644 (file)
--- a/rerere.h
+++ b/rerere.h
@@ -6,11 +6,19 @@
 #define RERERE_AUTOUPDATE   01
 #define RERERE_NOAUTOUPDATE 02
 
+/*
+ * Marks paths that have been hand-resolved and added to the
+ * index. Set in the util field of such paths after calling
+ * rerere_remaining.
+ */
+extern void *RERERE_RESOLVED;
+
 extern int setup_rerere(struct string_list *, int);
 extern int rerere(int);
 extern const char *rerere_path(const char *hex, const char *file);
 extern int has_rerere_resolution(const char *hex);
 extern int rerere_forget(const char **);
+extern int rerere_remaining(struct string_list *);
 
 #define OPT_RERERE_AUTOUPDATE(v) OPT_UYN(0, "rerere-autoupdate", (v), \
        "update the index with reused conflict resolution if possible")
index 7b9eaefae4ed03e994c2122453144b3c09591b9c..86d24704896d71de7bb554a3925ea88e2be3417b 100644 (file)
@@ -323,7 +323,7 @@ static int rev_compare_tree(struct rev_info *revs, struct commit *parent, struct
                 * tagged commit by specifying both --simplify-by-decoration
                 * and pathspec.
                 */
-               if (!revs->prune_data)
+               if (!revs->prune_data.nr)
                        return REV_TREE_SAME;
        }
 
@@ -553,11 +553,7 @@ static void cherry_pick_list(struct commit_list *list, struct rev_info *revs)
 
        left_first = left_count < right_count;
        init_patch_ids(&ids);
-       if (revs->diffopt.nr_paths) {
-               ids.diffopts.nr_paths = revs->diffopt.nr_paths;
-               ids.diffopts.paths = revs->diffopt.paths;
-               ids.diffopts.pathlens = revs->diffopt.pathlens;
-       }
+       ids.diffopts.pathspec = revs->diffopt.pathspec;
 
        /* Compute patch-ids for one side */
        for (p = list; p; p = p->next) {
@@ -973,7 +969,7 @@ static void prepare_show_merge(struct rev_info *revs)
                struct cache_entry *ce = active_cache[i];
                if (!ce_stage(ce))
                        continue;
-               if (ce_path_match(ce, revs->prune_data)) {
+               if (ce_path_match(ce, &revs->prune_data)) {
                        prune_num++;
                        prune = xrealloc(prune, sizeof(*prune) * prune_num);
                        prune[prune_num-2] = ce->name;
@@ -983,7 +979,8 @@ static void prepare_show_merge(struct rev_info *revs)
                       ce_same_name(ce, active_cache[i+1]))
                        i++;
        }
-       revs->prune_data = prune;
+       free_pathspec(&revs->prune_data);
+       init_pathspec(&revs->prune_data, prune);
        revs->limited = 1;
 }
 
@@ -1620,7 +1617,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
        }
 
        if (prune_data)
-               revs->prune_data = get_pathspec(revs->prefix, prune_data);
+               init_pathspec(&revs->prune_data, get_pathspec(revs->prefix, prune_data));
 
        if (revs->def == NULL)
                revs->def = opt ? opt->def : NULL;
@@ -1651,13 +1648,13 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
        if (revs->topo_order)
                revs->limited = 1;
 
-       if (revs->prune_data) {
-               diff_tree_setup_paths(revs->prune_data, &revs->pruning);
+       if (revs->prune_data.nr) {
+               diff_tree_setup_paths(revs->prune_data.raw, &revs->pruning);
                /* Can't prune commits with rename following: the paths change.. */
                if (!DIFF_OPT_TST(&revs->diffopt, FOLLOW_RENAMES))
                        revs->prune = 1;
                if (!revs->full_diff)
-                       diff_tree_setup_paths(revs->prune_data, &revs->diffopt);
+                       diff_tree_setup_paths(revs->prune_data.raw, &revs->diffopt);
        }
        if (revs->combine_merges)
                revs->ignore_merges = 0;
index 05659c64acd7fe8eb7be011cee6174e397e57baf..82509dd1d9ad7b1824971be4884409060f10aee3 100644 (file)
@@ -34,7 +34,7 @@ struct rev_info {
        /* Basic information */
        const char *prefix;
        const char *def;
-       void *prune_data;
+       struct pathspec prune_data;
        unsigned int early_output;
 
        /* Traversal flags */
index 27730c334cb433ef749a0efc5353a7f38032559c..d949b35c3308ae5c06774da7f4fecdc84778d5eb 100644 (file)
@@ -13,6 +13,7 @@
 #include "commit.h"
 #include "tag.h"
 #include "tree.h"
+#include "tree-walk.h"
 #include "refs.h"
 #include "pack-revindex.h"
 #include "sha1-lookup.h"
@@ -2479,8 +2480,37 @@ int has_sha1_file(const unsigned char *sha1)
        return has_loose_object(sha1);
 }
 
+static void check_tree(const void *buf, size_t size)
+{
+       struct tree_desc desc;
+       struct name_entry entry;
+
+       init_tree_desc(&desc, buf, size);
+       while (tree_entry(&desc, &entry))
+               /* do nothing
+                * tree_entry() will die() on malformed entries */
+               ;
+}
+
+static void check_commit(const void *buf, size_t size)
+{
+       struct commit c;
+       memset(&c, 0, sizeof(c));
+       if (parse_commit_buffer(&c, buf, size))
+               die("corrupt commit");
+}
+
+static void check_tag(const void *buf, size_t size)
+{
+       struct tag t;
+       memset(&t, 0, sizeof(t));
+       if (parse_tag_buffer(&t, buf, size))
+               die("corrupt tag");
+}
+
 static int index_mem(unsigned char *sha1, void *buf, size_t size,
-                    int write_object, enum object_type type, const char *path)
+                    int write_object, enum object_type type,
+                    const char *path, int format_check)
 {
        int ret, re_allocated = 0;
 
@@ -2498,6 +2528,14 @@ static int index_mem(unsigned char *sha1, void *buf, size_t size,
                        re_allocated = 1;
                }
        }
+       if (format_check) {
+               if (type == OBJ_TREE)
+                       check_tree(buf, size);
+               if (type == OBJ_COMMIT)
+                       check_commit(buf, size);
+               if (type == OBJ_TAG)
+                       check_tag(buf, size);
+       }
 
        if (write_object)
                ret = write_sha1_file(buf, size, typename(type), sha1);
@@ -2511,7 +2549,7 @@ static int index_mem(unsigned char *sha1, void *buf, size_t size,
 #define SMALL_FILE_SIZE (32*1024)
 
 int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object,
-            enum object_type type, const char *path)
+            enum object_type type, const char *path, int format_check)
 {
        int ret;
        size_t size = xsize_t(st->st_size);
@@ -2520,23 +2558,25 @@ int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object,
                struct strbuf sbuf = STRBUF_INIT;
                if (strbuf_read(&sbuf, fd, 4096) >= 0)
                        ret = index_mem(sha1, sbuf.buf, sbuf.len, write_object,
-                                       type, path);
+                                       type, path, format_check);
                else
                        ret = -1;
                strbuf_release(&sbuf);
        } else if (!size) {
-               ret = index_mem(sha1, NULL, size, write_object, type, path);
+               ret = index_mem(sha1, NULL, size, write_object, type, path,
+                               format_check);
        } else if (size <= SMALL_FILE_SIZE) {
                char *buf = xmalloc(size);
                if (size == read_in_full(fd, buf, size))
                        ret = index_mem(sha1, buf, size, write_object, type,
-                                       path);
+                                       path, format_check);
                else
                        ret = error("short read %s", strerror(errno));
                free(buf);
        } else {
                void *buf = xmmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
-               ret = index_mem(sha1, buf, size, write_object, type, path);
+               ret = index_mem(sha1, buf, size, write_object, type, path,
+                               format_check);
                munmap(buf, size);
        }
        close(fd);
@@ -2554,7 +2594,7 @@ int index_path(unsigned char *sha1, const char *path, struct stat *st, int write
                if (fd < 0)
                        return error("open(\"%s\"): %s", path,
                                     strerror(errno));
-               if (index_fd(sha1, fd, st, write_object, OBJ_BLOB, path) < 0)
+               if (index_fd(sha1, fd, st, write_object, OBJ_BLOB, path, 0) < 0)
                        return error("%s: failed to insert into database",
                                     path);
                break;
index 20924506af886c8d0ce28f3fcb2742214d80020f..ae266147b6db9e06abeb4dc30e4271b6c611deac 100755 (executable)
@@ -19,7 +19,7 @@ usage: test-parse-options <options>
     --set23               set integer to 23
     -t <time>             get timestamp of <time>
     -L, --length <str>    get length of <str>
-    -F, --file <FILE>     set file to <FILE>
+    -F, --file <file>     set file to <file>
 
 String options
     -s, --string <string>
index dd32432d626e4f3d192c2bbe4824772025bb08b1..6d52b824b115964c5551aaf4284205b98b885ce3 100755 (executable)
@@ -188,4 +188,17 @@ for args in "-w --stdin-paths" "--stdin-paths -w"; do
        pop_repo
 done
 
+test_expect_success 'corrupt tree' '
+       echo abc >malformed-tree
+       test_must_fail git hash-object -t tree malformed-tree
+'
+
+test_expect_success 'corrupt commit' '
+       test_must_fail git hash-object -t commit --stdin </dev/null
+'
+
+test_expect_success 'corrupt tag' '
+       test_must_fail git hash-object -t tag --stdin </dev/null
+'
+
 test_done
index d0e55465ff08698376f5d9fa86357d51bd57458c..53fb8228cf18e2b58f3ea63e98a0220fa0fd39f2 100755 (executable)
@@ -876,11 +876,25 @@ test_expect_success 'check split_cmdline return' "
        "
 
 test_expect_success 'git -c "key=value" support' '
-       test "z$(git -c name=value config name)" = zvalue &&
        test "z$(git -c core.name=value config core.name)" = zvalue &&
-       test "z$(git -c CamelCase=value config camelcase)" = zvalue &&
-       test "z$(git -c flag config --bool flag)" = ztrue &&
-       test_must_fail git -c core.name=value config name
+       test "z$(git -c foo.CamelCase=value config foo.camelcase)" = zvalue &&
+       test "z$(git -c foo.flag config --bool foo.flag)" = ztrue &&
+       test_must_fail git -c name=value config core.name
+'
+
+test_expect_success 'key sanity-checking' '
+       test_must_fail git config foo=bar &&
+       test_must_fail git config foo=.bar &&
+       test_must_fail git config foo.ba=r &&
+       test_must_fail git config foo.1bar &&
+       test_must_fail git config foo."ba
+                               z".bar &&
+       test_must_fail git config . false &&
+       test_must_fail git config .foo false &&
+       test_must_fail git config foo. false &&
+       test_must_fail git config .foo. false &&
+       git config foo.bar true &&
+       git config foo."ba =z".bar false
 '
 
 test_done
diff --git a/t/t2019-checkout-ambiguous-ref.sh b/t/t2019-checkout-ambiguous-ref.sh
new file mode 100755 (executable)
index 0000000..943541d
--- /dev/null
@@ -0,0 +1,59 @@
+#!/bin/sh
+
+test_description='checkout handling of ambiguous (branch/tag) refs'
+. ./test-lib.sh
+
+test_expect_success 'setup ambiguous refs' '
+       test_commit branch file &&
+       git branch ambiguity &&
+       git branch vagueness &&
+       test_commit tag file &&
+       git tag ambiguity &&
+       git tag vagueness HEAD:file &&
+       test_commit other file
+'
+
+test_expect_success 'checkout ambiguous ref succeeds' '
+       git checkout ambiguity >stdout 2>stderr
+'
+
+test_expect_success 'checkout produces ambiguity warning' '
+       grep "warning.*ambiguous" stderr
+'
+
+test_expect_success 'checkout chooses branch over tag' '
+       echo refs/heads/ambiguity >expect &&
+       git symbolic-ref HEAD >actual &&
+       test_cmp expect actual &&
+       echo branch >expect &&
+       test_cmp expect file
+'
+
+test_expect_success 'checkout reports switch to branch' '
+       grep "Switched to branch" stderr &&
+       ! grep "^HEAD is now at" stderr
+'
+
+test_expect_success 'checkout vague ref succeeds' '
+       git checkout vagueness >stdout 2>stderr &&
+       test_set_prereq VAGUENESS_SUCCESS
+'
+
+test_expect_success VAGUENESS_SUCCESS 'checkout produces ambiguity warning' '
+       grep "warning.*ambiguous" stderr
+'
+
+test_expect_success VAGUENESS_SUCCESS 'checkout chooses branch over tag' '
+       echo refs/heads/vagueness >expect &&
+       git symbolic-ref HEAD >actual &&
+       test_cmp expect actual &&
+       echo branch >expect &&
+       test_cmp expect file
+'
+
+test_expect_success VAGUENESS_SUCCESS 'checkout reports switch to branch' '
+       grep "Switched to branch" stderr &&
+       ! grep "^HEAD is now at" stderr
+'
+
+test_done
diff --git a/t/t2020-checkout-detach.sh b/t/t2020-checkout-detach.sh
new file mode 100755 (executable)
index 0000000..0042145
--- /dev/null
@@ -0,0 +1,95 @@
+#!/bin/sh
+
+test_description='checkout into detached HEAD state'
+. ./test-lib.sh
+
+check_detached () {
+       test_must_fail git symbolic-ref -q HEAD >/dev/null
+}
+
+check_not_detached () {
+       git symbolic-ref -q HEAD >/dev/null
+}
+
+reset () {
+       git checkout master &&
+       check_not_detached
+}
+
+test_expect_success 'setup' '
+       test_commit one &&
+       test_commit two &&
+       git branch branch &&
+       git tag tag
+'
+
+test_expect_success 'checkout branch does not detach' '
+       reset &&
+       git checkout branch &&
+       check_not_detached
+'
+
+test_expect_success 'checkout tag detaches' '
+       reset &&
+       git checkout tag &&
+       check_detached
+'
+
+test_expect_success 'checkout branch by full name detaches' '
+       reset &&
+       git checkout refs/heads/branch &&
+       check_detached
+'
+
+test_expect_success 'checkout non-ref detaches' '
+       reset &&
+       git checkout branch^ &&
+       check_detached
+'
+
+test_expect_success 'checkout ref^0 detaches' '
+       reset &&
+       git checkout branch^0 &&
+       check_detached
+'
+
+test_expect_success 'checkout --detach detaches' '
+       reset &&
+       git checkout --detach branch &&
+       check_detached
+'
+
+test_expect_success 'checkout --detach without branch name' '
+       reset &&
+       git checkout --detach &&
+       check_detached
+'
+
+test_expect_success 'checkout --detach errors out for non-commit' '
+       reset &&
+       test_must_fail git checkout --detach one^{tree} &&
+       check_not_detached
+'
+
+test_expect_success 'checkout --detach errors out for extra argument' '
+       reset &&
+       git checkout master &&
+       test_must_fail git checkout --detach tag one.t &&
+       check_not_detached
+'
+
+test_expect_success 'checkout --detached and -b are incompatible' '
+       reset &&
+       test_must_fail git checkout --detach -b newbranch tag &&
+       check_not_detached
+'
+
+test_expect_success 'checkout --detach moves HEAD' '
+       reset &&
+       git checkout one &&
+       git checkout --detach two &&
+       git diff --exit-code HEAD &&
+       git diff --exit-code two
+'
+
+test_done
index 94df7ae53a0ef47c0ef10ca6b3215ffdf38fa399..fbc8cd8f05f4debeb30b935c1b1db86e94e49f0e 100755 (executable)
@@ -70,4 +70,36 @@ test_expect_success 'diff-tree pathspec' '
        test_cmp expected current
 '
 
+EMPTY_TREE=4b825dc642cb6eb9a060e54bf8d69288fbee4904
+
+test_expect_success 'diff-tree with wildcard shows dir also matches' '
+       git diff-tree --name-only $EMPTY_TREE $tree -- "f*" >result &&
+       echo file0 >expected &&
+       test_cmp expected result
+'
+
+test_expect_success 'diff-tree -r with wildcard' '
+       git diff-tree -r --name-only $EMPTY_TREE $tree -- "*file1" >result &&
+       echo path1/file1 >expected &&
+       test_cmp expected result
+'
+
+test_expect_success 'diff-tree with wildcard shows dir also matches' '
+       git diff-tree --name-only $tree $tree2 -- "path1/f*" >result &&
+       echo path1 >expected &&
+       test_cmp expected result
+'
+
+test_expect_success 'diff-tree -r with wildcard from beginning' '
+       git diff-tree -r --name-only $tree $tree2 -- "path1/*file1" >result &&
+       echo path1/file1 >expected &&
+       test_cmp expected result
+'
+
+test_expect_success 'diff-tree -r with wildcard' '
+       git diff-tree -r --name-only $tree $tree2 -- "path1/f*" >result &&
+       echo path1/file1 >expected &&
+       test_cmp expected result
+'
+
 test_done
index 68e2652814c6a52265407b0fdfb70162eb634d53..d2c930de87f721a0e876351e511295ae0b094108 100755 (executable)
@@ -63,4 +63,40 @@ test_expect_success 'patch-id supports git-format-patch MIME output' '
        test_cmp patch-id_master patch-id_same
 '
 
+cat >nonl <<\EOF
+diff --git i/a w/a
+index e69de29..2e65efe 100644
+--- i/a
++++ w/a
+@@ -0,0 +1 @@
++a
+\ No newline at end of file
+diff --git i/b w/b
+index e69de29..6178079 100644
+--- i/b
++++ w/b
+@@ -0,0 +1 @@
++b
+EOF
+
+cat >withnl <<\EOF
+diff --git i/a w/a
+index e69de29..7898192 100644
+--- i/a
++++ w/a
+@@ -0,0 +1 @@
++a
+diff --git i/b w/b
+index e69de29..6178079 100644
+--- i/b
++++ w/b
+@@ -0,0 +1 @@
++b
+EOF
+
+test_expect_success 'patch-id handles no-nl-at-eof markers' '
+       cat nonl | calc_patch_id nonl &&
+       cat withnl | calc_patch_id withnl &&
+       test_cmp patch-id_nonl patch-id_withnl
+'
 test_done
diff --git a/t/t6000-rev-list-misc.sh b/t/t6000-rev-list-misc.sh
new file mode 100755 (executable)
index 0000000..b10685a
--- /dev/null
@@ -0,0 +1,51 @@
+#!/bin/sh
+
+test_description='miscellaneous rev-list tests'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+       echo content1 >wanted_file &&
+       echo content2 >unwanted_file &&
+       git add wanted_file unwanted_file &&
+       git commit -m one
+'
+
+test_expect_success 'rev-list --objects heeds pathspecs' '
+       git rev-list --objects HEAD -- wanted_file >output &&
+       grep wanted_file output &&
+       ! grep unwanted_file output
+'
+
+test_expect_success 'rev-list --objects with pathspecs and deeper paths' '
+       mkdir foo &&
+       >foo/file &&
+       git add foo/file &&
+       git commit -m two &&
+
+       git rev-list --objects HEAD -- foo >output &&
+       grep foo/file output &&
+
+       git rev-list --objects HEAD -- foo/file >output &&
+       grep foo/file output &&
+       ! grep unwanted_file output
+'
+
+test_expect_success 'rev-list --objects with pathspecs and copied files' '
+       git checkout --orphan junio-testcase &&
+       git rm -rf . &&
+
+       mkdir two &&
+       echo frotz >one &&
+       cp one two/three &&
+       git add one two/three &&
+       test_tick &&
+       git commit -m that &&
+
+       ONE=$(git rev-parse HEAD:one)
+       git rev-list --objects HEAD two >output &&
+       grep "$ONE two/three" output &&
+       ! grep one output
+'
+
+test_done
index 5dabf1c5e354c28cc593bd0ea8e4b0d5f0d56d67..3e8c42ee0b93cff3be39f42a411e9b32d16c5d5d 100755 (executable)
@@ -1,51 +1,96 @@
 #!/bin/sh
 
-test_description='git rev-list trivial path optimization test'
+test_description='git rev-list trivial path optimization test
+
+   d/z1
+   b0                             b1
+   o------------------------*----o master
+  /                        /
+ o---------o----o----o----o side
+ a0        c0   c1   a1   c2
+ d/f0      d/f1
+ d/z0
+
+'
 
 . ./test-lib.sh
 
 test_expect_success setup '
-echo Hello > a &&
-git add a &&
-git commit -m "Initial commit" a &&
-initial=$(git rev-parse --verify HEAD)
+       echo Hello >a &&
+       mkdir d &&
+       echo World >d/f &&
+       echo World >d/z &&
+       git add a d &&
+       test_tick &&
+       git commit -m "Initial commit" &&
+       git rev-parse --verify HEAD &&
+       git tag initial
 '
 
 test_expect_success path-optimization '
-    commit=$(echo "Unchanged tree" | git commit-tree "HEAD^{tree}" -p HEAD) &&
-    test $(git rev-list $commit | wc -l) = 2 &&
-    test $(git rev-list $commit -- . | wc -l) = 1
+       test_tick &&
+       commit=$(echo "Unchanged tree" | git commit-tree "HEAD^{tree}" -p HEAD) &&
+       test $(git rev-list $commit | wc -l) = 2 &&
+       test $(git rev-list $commit -- . | wc -l) = 1
 '
 
 test_expect_success 'further setup' '
        git checkout -b side &&
        echo Irrelevant >c &&
-       git add c &&
+       echo Irrelevant >d/f &&
+       git add c d/f &&
+       test_tick &&
        git commit -m "Side makes an irrelevant commit" &&
+       git tag side_c0 &&
        echo "More Irrelevancy" >c &&
        git add c &&
+       test_tick &&
        git commit -m "Side makes another irrelevant commit" &&
        echo Bye >a &&
        git add a &&
+       test_tick &&
        git commit -m "Side touches a" &&
-       side=$(git rev-parse --verify HEAD) &&
+       git tag side_a1 &&
        echo "Yet more Irrelevancy" >c &&
        git add c &&
+       test_tick &&
        git commit -m "Side makes yet another irrelevant commit" &&
        git checkout master &&
        echo Another >b &&
-       git add b &&
+       echo Munged >d/z &&
+       git add b d/z &&
+       test_tick &&
        git commit -m "Master touches b" &&
+       git tag master_b0 &&
        git merge side &&
        echo Touched >b &&
        git add b &&
+       test_tick &&
        git commit -m "Master touches b again"
 '
 
 test_expect_success 'path optimization 2' '
-       ( echo "$side"; echo "$initial" ) >expected &&
+       git rev-parse side_a1 initial >expected &&
        git rev-list HEAD -- a >actual &&
        test_cmp expected actual
 '
 
+test_expect_success 'pathspec with leading path' '
+       git rev-parse master^ master_b0 side_c0 initial >expected &&
+       git rev-list HEAD -- d >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'pathspec with glob (1)' '
+       git rev-parse master^ master_b0 side_c0 initial >expected &&
+       git rev-list HEAD -- "d/*" >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'pathspec with glob (2)' '
+       git rev-parse side_c0 initial >expected &&
+       git rev-list HEAD -- "d/[a-m]*" >actual &&
+       test_cmp expected actual
+'
+
 test_done
index ff189624d48fb9f68997395d121d4e7056511245..5b4b694f1801f5c2284346f882cb496df9e7d74e 100755 (executable)
@@ -132,6 +132,18 @@ test_expect_success 'with hook (-c)' '
 
 '
 
+test_expect_success 'with hook (merge)' '
+
+       head=`git rev-parse HEAD` &&
+       git checkout -b other HEAD@{1} &&
+       echo "more" >> file &&
+       git add file &&
+       git commit -m other &&
+       git checkout - &&
+       git merge other &&
+       test "`git log -1 --pretty=format:%s`" = merge
+'
+
 cat > "$HOOK" <<'EOF'
 #!/bin/sh
 exit 1
index d78bdec330cd8b9505aa92ff2e81e0f716b9a741..dc838c93bcb00a80ad56c4d1233009fe9220b90e 100755 (executable)
@@ -16,23 +16,33 @@ Testing basic merge tool invocation'
 test_expect_success 'setup' '
     git config rerere.enabled true &&
     echo master >file1 &&
+    echo master file11 >file11 &&
+    echo master file12 >file12 &&
+    echo master file13 >file13 &&
+    echo master file14 >file14 &&
     mkdir subdir &&
     echo master sub >subdir/file3 &&
-    git add file1 subdir/file3 &&
-    git commit -m "added file1" &&
+    git add file1 file1[1-4] subdir/file3 &&
+    git commit -m "add initial versions" &&
 
     git checkout -b branch1 master &&
     echo branch1 change >file1 &&
     echo branch1 newfile >file2 &&
+    echo branch1 change file11 >file11 &&
+    echo branch1 change file13 >file13 &&
     echo branch1 sub >subdir/file3 &&
-    git add file1 file2 subdir/file3 &&
+    git add file1 file11 file13 file2 subdir/file3 &&
+    git rm file12 &&
     git commit -m "branch1 changes" &&
 
     git checkout master &&
     echo master updated >file1 &&
     echo master new >file2 &&
+    echo master updated file12 >file12 &&
+    echo master updated file14 >file14 &&
     echo master new sub >subdir/file3 &&
-    git add file1 file2 subdir/file3 &&
+    git add file1 file12 file14 file2 subdir/file3 &&
+    git rm file11 &&
     git commit -m "master updates" &&
 
     git config merge.tool mytool &&
@@ -46,6 +56,8 @@ test_expect_success 'custom mergetool' '
     ( yes "" | git mergetool file1 >/dev/null 2>&1 ) &&
     ( yes "" | git mergetool file2 >/dev/null 2>&1 ) &&
     ( yes "" | git mergetool subdir/file3 >/dev/null 2>&1 ) &&
+    ( yes "d" | git mergetool file11 >/dev/null 2>&1 ) &&
+    ( yes "d" | git mergetool file12 >/dev/null 2>&1 ) &&
     test "$(cat file1)" = "master updated" &&
     test "$(cat file2)" = "master new" &&
     test "$(cat subdir/file3)" = "master new sub" &&
@@ -59,6 +71,8 @@ test_expect_success 'mergetool crlf' '
     ( yes "" | git mergetool file1 >/dev/null 2>&1 ) &&
     ( yes "" | git mergetool file2 >/dev/null 2>&1 ) &&
     ( yes "" | git mergetool subdir/file3 >/dev/null 2>&1 ) &&
+    ( yes "d" | git mergetool file11 >/dev/null 2>&1 ) &&
+    ( yes "d" | git mergetool file12 >/dev/null 2>&1 ) &&
     test "$(printf x | cat file1 -)" = "$(printf "master updated\r\nx")" &&
     test "$(printf x | cat file2 -)" = "$(printf "master new\r\nx")" &&
     test "$(printf x | cat subdir/file3 -)" = "$(printf "master new sub\r\nx")" &&
@@ -82,6 +96,8 @@ test_expect_success 'mergetool on file in parent dir' '
        cd subdir &&
        ( yes "" | git mergetool ../file1 >/dev/null 2>&1 ) &&
        ( yes "" | git mergetool ../file2 >/dev/null 2>&1 ) &&
+       ( yes "d" | git mergetool ../file11 >/dev/null 2>&1 ) &&
+       ( yes "d" | git mergetool ../file12 >/dev/null 2>&1 ) &&
        test "$(cat ../file1)" = "master updated" &&
        test "$(cat ../file2)" = "master new" &&
        git commit -m "branch1 resolved with mergetool - subdir"
@@ -92,6 +108,8 @@ test_expect_success 'mergetool skips autoresolved' '
     git checkout -b test4 branch1 &&
     test_must_fail git merge master &&
     test -n "$(git ls-files -u)" &&
+    ( yes "d" | git mergetool file11 >/dev/null 2>&1 ) &&
+    ( yes "d" | git mergetool file12 >/dev/null 2>&1 ) &&
     output="$(git mergetool --no-prompt)" &&
     test "$output" = "No files need merging" &&
     git reset --hard
@@ -102,13 +120,23 @@ test_expect_success 'mergetool merges all from subdir' '
        cd subdir &&
        git config rerere.enabled false &&
        test_must_fail git merge master &&
-       git mergetool --no-prompt &&
+       ( yes "d" "d" | git mergetool --no-prompt ) &&
        test "$(cat ../file1)" = "master updated" &&
        test "$(cat ../file2)" = "master new" &&
        test "$(cat file3)" = "master new sub" &&
-       git add ../file1 ../file2 file3 &&
        git commit -m "branch2 resolved by mergetool from subdir"
     )
 '
 
+test_expect_success 'mergetool skips resolved paths when rerere is active' '
+    git config rerere.enabled true &&
+    rm -rf .git/rr-cache &&
+    git checkout -b test5 branch1
+    test_must_fail git merge master >/dev/null 2>&1 &&
+    ( yes "d" "d" | git mergetool --no-prompt >/dev/null 2>&1 ) &&
+    output="$(yes "n" | git mergetool --no-prompt)" &&
+    test "$output" = "No files need merging" &&
+    git reset --hard
+'
+
 test_done
index c8777589ca1c89825b570cfc05405a39df39aaba..8a7788dc39f236b15e60912c384d835fd1db5a28 100755 (executable)
@@ -182,6 +182,24 @@ do
                test_cmp expected actual
        '
 
+       test_expect_success "grep --max-depth 0 -- . t $L" '
+               {
+                       echo ${HC}t/v:1:vvv
+                       echo ${HC}v:1:vvv
+               } >expected &&
+               git grep --max-depth 0 -n -e vvv $H -- . t >actual &&
+               test_cmp expected actual
+       '
+
+       test_expect_success "grep --max-depth 0 -- t . $L" '
+               {
+                       echo ${HC}t/v:1:vvv
+                       echo ${HC}v:1:vvv
+               } >expected &&
+               git grep --max-depth 0 -n -e vvv $H -- t . >actual &&
+               test_cmp expected actual
+       '
+
 done
 
 cat >expected <<EOF
index 35c151d7ead1181eb36c5a29c0bcc7aac4ec5c12..afac5b56a87f3dcc6467b8ad260e1b59d041eb03 100755 (executable)
@@ -446,6 +446,8 @@ test_expect_success \
 test_expect_success \
        'encode(commit): utf8' \
        '. "$TEST_DIRECTORY"/t3901-utf8.txt &&
+        test_when_finished "GIT_AUTHOR_NAME=\"A U Thor\"" &&
+        test_when_finished "GIT_COMMITTER_NAME=\"C O Mitter\"" &&
         echo "UTF-8" >> file &&
         git add file &&
         git commit -F "$TEST_DIRECTORY"/t3900/1-UTF-8.txt &&
@@ -454,11 +456,13 @@ test_expect_success \
 test_expect_success \
        'encode(commit): iso-8859-1' \
        '. "$TEST_DIRECTORY"/t3901-8859-1.txt &&
+        test_when_finished "GIT_AUTHOR_NAME=\"A U Thor\"" &&
+        test_when_finished "GIT_COMMITTER_NAME=\"C O Mitter\"" &&
         echo "ISO-8859-1" >> file &&
         git add file &&
         git config i18n.commitencoding ISO-8859-1 &&
+        test_when_finished "git config --unset i18n.commitencoding" &&
         git commit -F "$TEST_DIRECTORY"/t3900/ISO8859-1.txt &&
-        git config --unset i18n.commitencoding &&
         gitweb_run "p=.git;a=commit"'
 
 test_expect_success \
index c15ca2d6479a814d7649a38a671a554b6ed8b68f..13ba96e21a9295805aed40c89ecd5178db6bfae4 100755 (executable)
@@ -113,6 +113,16 @@ BEGIN
 my $dir_commit = $r2->command_oneline('log', '-n1', '--pretty=format:%H', '.');
 isnt($last_commit, $dir_commit, 'log . does not show last commit');
 
+# commands outside working tree
+chdir($abs_repo_dir . '/..');
+my $r3 = Git->repository(Directory => $abs_repo_dir);
+my $tmpfile3 = "$abs_repo_dir/file3.tmp";
+open TEMPFILE3, "+>$tmpfile3" or die "Can't open $tmpfile3: $!";
+is($r3->cat_blob($file1hash, \*TEMPFILE3), 15, "cat_blob(outside): size");
+close TEMPFILE3;
+unlink $tmpfile3;
+chdir($abs_repo_dir);
+
 printf "1..%d\n", Test::More->builder->current_test;
 
 my $is_passing = eval { Test::More->is_passing };
diff --git a/t/t9800-git-p4.sh b/t/t9800-git-p4.sh
new file mode 100755 (executable)
index 0000000..1969e6b
--- /dev/null
@@ -0,0 +1,100 @@
+#!/bin/sh
+
+test_description='git-p4 tests'
+
+. ./test-lib.sh
+
+( p4 -h && p4d -h ) >/dev/null 2>&1 || {
+       skip_all='skipping git-p4 tests; no p4 or p4d'
+       test_done
+}
+
+GITP4=$GIT_BUILD_DIR/contrib/fast-import/git-p4
+P4DPORT=10669
+
+db="$TRASH_DIRECTORY/db"
+cli="$TRASH_DIRECTORY/cli"
+git="$TRASH_DIRECTORY/git"
+
+test_debug 'echo p4d -q -d -r "$db" -p $P4DPORT'
+test_expect_success setup '
+       mkdir -p "$db" &&
+       p4d -q -d -r "$db" -p $P4DPORT &&
+       mkdir -p "$cli" &&
+       mkdir -p "$git" &&
+       export P4PORT=localhost:$P4DPORT
+'
+
+test_expect_success 'add p4 files' '
+       cd "$cli" &&
+       p4 client -i <<-EOF &&
+       Client: client
+       Description: client
+       Root: $cli
+       View: //depot/... //client/...
+       EOF
+       export P4CLIENT=client &&
+       echo file1 >file1 &&
+       p4 add file1 &&
+       p4 submit -d "file1" &&
+       cd "$TRASH_DIRECTORY"
+'
+
+test_expect_success 'basic git-p4 clone' '
+       "$GITP4" clone --dest="$git" //depot &&
+       rm -rf "$git" && mkdir "$git"
+'
+
+test_expect_success 'exit when p4 fails to produce marshaled output' '
+       badp4dir="$TRASH_DIRECTORY/badp4dir" &&
+       mkdir -p "$badp4dir" &&
+       cat >"$badp4dir"/p4 <<-EOF &&
+       #!$SHELL_PATH
+       exit 1
+       EOF
+       chmod 755 "$badp4dir"/p4 &&
+       PATH="$badp4dir:$PATH" "$GITP4" clone --dest="$git" //depot >errs 2>&1 ; retval=$? &&
+       test $retval -eq 1 &&
+       test_must_fail grep -q Traceback errs
+'
+
+test_expect_success 'add p4 files with wildcards in the names' '
+       cd "$cli" &&
+       echo file-wild-hash >file-wild#hash &&
+       echo file-wild-star >file-wild\*star &&
+       echo file-wild-at >file-wild@at &&
+       echo file-wild-percent >file-wild%percent &&
+       p4 add -f file-wild* &&
+       p4 submit -d "file wildcards" &&
+       cd "$TRASH_DIRECTORY"
+'
+
+test_expect_success 'wildcard files git-p4 clone' '
+       "$GITP4" clone --dest="$git" //depot &&
+       cd "$git" &&
+       test -f file-wild#hash &&
+       test -f file-wild\*star &&
+       test -f file-wild@at &&
+       test -f file-wild%percent &&
+       cd "$TRASH_DIRECTORY" &&
+       rm -rf "$git" && mkdir "$git"
+'
+
+test_expect_success 'clone bare' '
+       "$GITP4" clone --dest="$git" --bare //depot &&
+       cd "$git" &&
+       test ! -d .git &&
+       bare=`git config --get core.bare` &&
+       test "$bare" = true &&
+       cd "$TRASH_DIRECTORY" &&
+       rm -rf "$git" && mkdir "$git"
+'
+
+test_expect_success 'shutdown' '
+       pid=`pgrep -f p4d` &&
+       test -n "$pid" &&
+       test_debug "ps wl `echo $pid`" &&
+       kill $pid
+'
+
+test_done
index 0828592162cc7c61f0d025ba4d83fc29bc4c2067..4e3710b9a8fc868642e1e0e8f4af37eeb1ce0ce4 100644 (file)
@@ -46,7 +46,7 @@ int main(int argc, const char **argv)
                OPT_DATE('t', NULL, &timestamp, "get timestamp of <time>"),
                OPT_CALLBACK('L', "length", &integer, "str",
                        "get length of <str>", length_callback),
-               OPT_FILENAME('F', "file", &file, "set file to <FILE>"),
+               OPT_FILENAME('F', "file", &file, "set file to <file>"),
                OPT_GROUP("String options"),
                OPT_STRING('s', "string", &string, "string", "get a string"),
                OPT_STRING(0, "string2", &string, "str", "get another string"),
index 12c9a88884ec3bb70a1744e44235c578a44e08e6..3954281f509bbed9a9b095ed92d24b67275fed82 100644 (file)
@@ -6,34 +6,18 @@
 #include "diffcore.h"
 #include "tree.h"
 
-static char *malloc_base(const char *base, int baselen, const char *path, int pathlen)
-{
-       char *newbase = xmalloc(baselen + pathlen + 2);
-       memcpy(newbase, base, baselen);
-       memcpy(newbase + baselen, path, pathlen);
-       memcpy(newbase + baselen + pathlen, "/", 2);
-       return newbase;
-}
+static void show_entry(struct diff_options *opt, const char *prefix,
+                      struct tree_desc *desc, struct strbuf *base);
 
-static char *malloc_fullname(const char *base, int baselen, const char *path, int pathlen)
-{
-       char *fullname = xmalloc(baselen + pathlen + 1);
-       memcpy(fullname, base, baselen);
-       memcpy(fullname + baselen, path, pathlen);
-       fullname[baselen + pathlen] = 0;
-       return fullname;
-}
-
-static void show_entry(struct diff_options *opt, const char *prefix, struct tree_desc *desc,
-                      const char *base, int baselen);
-
-static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, const char *base, int baselen, struct diff_options *opt)
+static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2,
+                             struct strbuf *base, struct diff_options *opt)
 {
        unsigned mode1, mode2;
        const char *path1, *path2;
        const unsigned char *sha1, *sha2;
        int cmp, pathlen1, pathlen2;
-       char *fullname;
+       int old_baselen = base->len;
+       int retval = 0;
 
        sha1 = tree_entry_extract(t1, &path1, &mode1);
        sha2 = tree_entry_extract(t2, &path2, &mode2);
@@ -42,11 +26,11 @@ static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, const
        pathlen2 = tree_entry_len(path2, sha2);
        cmp = base_name_compare(path1, pathlen1, mode1, path2, pathlen2, mode2);
        if (cmp < 0) {
-               show_entry(opt, "-", t1, base, baselen);
+               show_entry(opt, "-", t1, base);
                return -1;
        }
        if (cmp > 0) {
-               show_entry(opt, "+", t2, base, baselen);
+               show_entry(opt, "+", t2, base);
                return 1;
        }
        if (!DIFF_OPT_TST(opt, FIND_COPIES_HARDER) && !hashcmp(sha1, sha2) && mode1 == mode2)
@@ -57,149 +41,29 @@ static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, const
         * file, we need to consider it a remove and an add.
         */
        if (S_ISDIR(mode1) != S_ISDIR(mode2)) {
-               show_entry(opt, "-", t1, base, baselen);
-               show_entry(opt, "+", t2, base, baselen);
+               show_entry(opt, "-", t1, base);
+               show_entry(opt, "+", t2, base);
                return 0;
        }
 
+       strbuf_add(base, path1, pathlen1);
        if (DIFF_OPT_TST(opt, RECURSIVE) && S_ISDIR(mode1)) {
-               int retval;
-               char *newbase = malloc_base(base, baselen, path1, pathlen1);
                if (DIFF_OPT_TST(opt, TREE_IN_RECURSIVE)) {
-                       newbase[baselen + pathlen1] = 0;
                        opt->change(opt, mode1, mode2,
-                                   sha1, sha2, newbase, 0, 0);
-                       newbase[baselen + pathlen1] = '/';
+                                   sha1, sha2, base->buf, 0, 0);
                }
-               retval = diff_tree_sha1(sha1, sha2, newbase, opt);
-               free(newbase);
-               return retval;
+               strbuf_addch(base, '/');
+               retval = diff_tree_sha1(sha1, sha2, base->buf, opt);
+       } else {
+               opt->change(opt, mode1, mode2, sha1, sha2, base->buf, 0, 0);
        }
-
-       fullname = malloc_fullname(base, baselen, path1, pathlen1);
-       opt->change(opt, mode1, mode2, sha1, sha2, fullname, 0, 0);
-       free(fullname);
+       strbuf_setlen(base, old_baselen);
        return 0;
 }
 
-/*
- * Is a tree entry interesting given the pathspec we have?
- *
- * Pre-condition: baselen == 0 || base[baselen-1] == '/'
- *
- * Return:
- *  - 2 for "yes, and all subsequent entries will be"
- *  - 1 for yes
- *  - zero for no
- *  - negative for "no, and no subsequent entries will be either"
- */
-static int tree_entry_interesting(struct tree_desc *desc, const char *base, int baselen, struct diff_options *opt)
-{
-       const char *path;
-       const unsigned char *sha1;
-       unsigned mode;
-       int i;
-       int pathlen;
-       int never_interesting = -1;
-
-       if (!opt->nr_paths)
-               return 2;
-
-       sha1 = tree_entry_extract(desc, &path, &mode);
-
-       pathlen = tree_entry_len(path, sha1);
-
-       for (i = 0; i < opt->nr_paths; i++) {
-               const char *match = opt->paths[i];
-               int matchlen = opt->pathlens[i];
-               int m = -1; /* signals that we haven't called strncmp() */
-
-               if (baselen >= matchlen) {
-                       /* If it doesn't match, move along... */
-                       if (strncmp(base, match, matchlen))
-                               continue;
-
-                       /*
-                        * If the base is a subdirectory of a path which
-                        * was specified, all of them are interesting.
-                        */
-                       if (!matchlen ||
-                           base[matchlen] == '/' ||
-                           match[matchlen - 1] == '/')
-                               return 2;
-
-                       /* Just a random prefix match */
-                       continue;
-               }
-
-               /* Does the base match? */
-               if (strncmp(base, match, baselen))
-                       continue;
-
-               match += baselen;
-               matchlen -= baselen;
-
-               if (never_interesting) {
-                       /*
-                        * We have not seen any match that sorts later
-                        * than the current path.
-                        */
-
-                       /*
-                        * Does match sort strictly earlier than path
-                        * with their common parts?
-                        */
-                       m = strncmp(match, path,
-                                   (matchlen < pathlen) ? matchlen : pathlen);
-                       if (m < 0)
-                               continue;
-
-                       /*
-                        * If we come here even once, that means there is at
-                        * least one pathspec that would sort equal to or
-                        * later than the path we are currently looking at.
-                        * In other words, if we have never reached this point
-                        * after iterating all pathspecs, it means all
-                        * pathspecs are either outside of base, or inside the
-                        * base but sorts strictly earlier than the current
-                        * one.  In either case, they will never match the
-                        * subsequent entries.  In such a case, we initialized
-                        * the variable to -1 and that is what will be
-                        * returned, allowing the caller to terminate early.
-                        */
-                       never_interesting = 0;
-               }
-
-               if (pathlen > matchlen)
-                       continue;
-
-               if (matchlen > pathlen) {
-                       if (match[pathlen] != '/')
-                               continue;
-                       if (!S_ISDIR(mode))
-                               continue;
-               }
-
-               if (m == -1)
-                       /*
-                        * we cheated and did not do strncmp(), so we do
-                        * that here.
-                        */
-                       m = strncmp(match, path, pathlen);
-
-               /*
-                * If common part matched earlier then it is a hit,
-                * because we rejected the case where path is not a
-                * leading directory and is shorter than match.
-                */
-               if (!m)
-                       return 1;
-       }
-       return never_interesting; /* No matches */
-}
-
 /* A whole sub-tree went away or appeared */
-static void show_tree(struct diff_options *opt, const char *prefix, struct tree_desc *desc, const char *base, int baselen)
+static void show_tree(struct diff_options *opt, const char *prefix,
+                     struct tree_desc *desc, struct strbuf *base)
 {
        int all_interesting = 0;
        while (desc->size) {
@@ -208,31 +72,32 @@ static void show_tree(struct diff_options *opt, const char *prefix, struct tree_
                if (all_interesting)
                        show = 1;
                else {
-                       show = tree_entry_interesting(desc, base, baselen,
-                                                     opt);
+                       show = tree_entry_interesting(&desc->entry, base, 0,
+                                                     &opt->pathspec);
                        if (show == 2)
                                all_interesting = 1;
                }
                if (show < 0)
                        break;
                if (show)
-                       show_entry(opt, prefix, desc, base, baselen);
+                       show_entry(opt, prefix, desc, base);
                update_tree_entry(desc);
        }
 }
 
 /* A file entry went away or appeared */
-static void show_entry(struct diff_options *opt, const char *prefix, struct tree_desc *desc,
-                      const char *base, int baselen)
+static void show_entry(struct diff_options *opt, const char *prefix,
+                      struct tree_desc *desc, struct strbuf *base)
 {
        unsigned mode;
        const char *path;
        const unsigned char *sha1 = tree_entry_extract(desc, &path, &mode);
        int pathlen = tree_entry_len(path, sha1);
+       int old_baselen = base->len;
 
+       strbuf_add(base, path, pathlen);
        if (DIFF_OPT_TST(opt, RECURSIVE) && S_ISDIR(mode)) {
                enum object_type type;
-               char *newbase = malloc_base(base, baselen, path, pathlen);
                struct tree_desc inner;
                void *tree;
                unsigned long size;
@@ -241,28 +106,25 @@ static void show_entry(struct diff_options *opt, const char *prefix, struct tree
                if (!tree || type != OBJ_TREE)
                        die("corrupt tree sha %s", sha1_to_hex(sha1));
 
-               if (DIFF_OPT_TST(opt, TREE_IN_RECURSIVE)) {
-                       newbase[baselen + pathlen] = 0;
-                       opt->add_remove(opt, *prefix, mode, sha1, newbase, 0);
-                       newbase[baselen + pathlen] = '/';
-               }
+               if (DIFF_OPT_TST(opt, TREE_IN_RECURSIVE))
+                       opt->add_remove(opt, *prefix, mode, sha1, base->buf, 0);
 
-               init_tree_desc(&inner, tree, size);
-               show_tree(opt, prefix, &inner, newbase, baselen + 1 + pathlen);
+               strbuf_addch(base, '/');
 
+               init_tree_desc(&inner, tree, size);
+               show_tree(opt, prefix, &inner, base);
                free(tree);
-               free(newbase);
-       } else {
-               char *fullname = malloc_fullname(base, baselen, path, pathlen);
-               opt->add_remove(opt, prefix[0], mode, sha1, fullname, 0);
-               free(fullname);
-       }
+       } else
+               opt->add_remove(opt, prefix[0], mode, sha1, base->buf, 0);
+
+       strbuf_setlen(base, old_baselen);
 }
 
-static void skip_uninteresting(struct tree_desc *t, const char *base, int baselen, struct diff_options *opt, int *all_interesting)
+static void skip_uninteresting(struct tree_desc *t, struct strbuf *base,
+                              struct diff_options *opt, int *all_interesting)
 {
        while (t->size) {
-               int show = tree_entry_interesting(t, base, baselen, opt);
+               int show = tree_entry_interesting(&t->entry, base, 0, &opt->pathspec);
                if (show == 2)
                        *all_interesting = 1;
                if (!show) {
@@ -276,37 +138,44 @@ static void skip_uninteresting(struct tree_desc *t, const char *base, int basele
        }
 }
 
-int diff_tree(struct tree_desc *t1, struct tree_desc *t2, const char *base, struct diff_options *opt)
+int diff_tree(struct tree_desc *t1, struct tree_desc *t2,
+             const char *base_str, struct diff_options *opt)
 {
-       int baselen = strlen(base);
+       struct strbuf base;
+       int baselen = strlen(base_str);
        int all_t1_interesting = 0;
        int all_t2_interesting = 0;
 
+       /* Enable recursion indefinitely */
+       opt->pathspec.recursive = DIFF_OPT_TST(opt, RECURSIVE);
+       opt->pathspec.max_depth = -1;
+
+       strbuf_init(&base, PATH_MAX);
+       strbuf_add(&base, base_str, baselen);
+
        for (;;) {
                if (DIFF_OPT_TST(opt, QUICK) &&
                    DIFF_OPT_TST(opt, HAS_CHANGES))
                        break;
-               if (opt->nr_paths) {
+               if (opt->pathspec.nr) {
                        if (!all_t1_interesting)
-                               skip_uninteresting(t1, base, baselen, opt,
-                                                  &all_t1_interesting);
+                               skip_uninteresting(t1, &base, opt, &all_t1_interesting);
                        if (!all_t2_interesting)
-                               skip_uninteresting(t2, base, baselen, opt,
-                                                  &all_t2_interesting);
+                               skip_uninteresting(t2, &base, opt, &all_t2_interesting);
                }
                if (!t1->size) {
                        if (!t2->size)
                                break;
-                       show_entry(opt, "+", t2, base, baselen);
+                       show_entry(opt, "+", t2, &base);
                        update_tree_entry(t2);
                        continue;
                }
                if (!t2->size) {
-                       show_entry(opt, "-", t1, base, baselen);
+                       show_entry(opt, "-", t1, &base);
                        update_tree_entry(t1);
                        continue;
                }
-               switch (compare_tree_entry(t1, t2, base, baselen, opt)) {
+               switch (compare_tree_entry(t1, t2, &base, opt)) {
                case -1:
                        update_tree_entry(t1);
                        continue;
@@ -319,6 +188,8 @@ int diff_tree(struct tree_desc *t1, struct tree_desc *t2, const char *base, stru
                }
                die("git diff-tree: internal error");
        }
+
+       strbuf_release(&base);
        return 0;
 }
 
@@ -349,7 +220,7 @@ static void try_to_follow_renames(struct tree_desc *t1, struct tree_desc *t2, co
        DIFF_OPT_SET(&diff_opts, RECURSIVE);
        DIFF_OPT_SET(&diff_opts, FIND_COPIES_HARDER);
        diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
-       diff_opts.single_follow = opt->paths[0];
+       diff_opts.single_follow = opt->pathspec.raw[0];
        diff_opts.break_opt = opt->break_opt;
        paths[0] = NULL;
        diff_tree_setup_paths(paths, &diff_opts);
@@ -369,15 +240,16 @@ static void try_to_follow_renames(struct tree_desc *t1, struct tree_desc *t2, co
                 * diff_queued_diff, we will also use that as the path in
                 * the future!
                 */
-               if ((p->status == 'R' || p->status == 'C') && !strcmp(p->two->path, opt->paths[0])) {
+               if ((p->status == 'R' || p->status == 'C') &&
+                   !strcmp(p->two->path, opt->pathspec.raw[0])) {
                        /* Switch the file-pairs around */
                        q->queue[i] = choice;
                        choice = p;
 
                        /* Update the path we use from now on.. */
                        diff_tree_release_paths(opt);
-                       opt->paths[0] = xstrdup(p->one->path);
-                       diff_tree_setup_paths(opt->paths, opt);
+                       opt->pathspec.raw[0] = xstrdup(p->one->path);
+                       diff_tree_setup_paths(opt->pathspec.raw, opt);
 
                        /*
                         * The caller expects us to return a set of vanilla
@@ -452,36 +324,12 @@ int diff_root_tree_sha1(const unsigned char *new, const char *base, struct diff_
        return retval;
 }
 
-static int count_paths(const char **paths)
-{
-       int i = 0;
-       while (*paths++)
-               i++;
-       return i;
-}
-
 void diff_tree_release_paths(struct diff_options *opt)
 {
-       free(opt->pathlens);
+       free_pathspec(&opt->pathspec);
 }
 
 void diff_tree_setup_paths(const char **p, struct diff_options *opt)
 {
-       opt->nr_paths = 0;
-       opt->pathlens = NULL;
-       opt->paths = NULL;
-
-       if (p) {
-               int i;
-
-               opt->paths = p;
-               opt->nr_paths = count_paths(p);
-               if (opt->nr_paths == 0) {
-                       opt->pathlens = NULL;
-                       return;
-               }
-               opt->pathlens = xmalloc(opt->nr_paths * sizeof(int));
-               for (i=0; i < opt->nr_paths; i++)
-                       opt->pathlens[i] = strlen(p[i]);
-       }
+       init_pathspec(&opt->pathspec, p);
 }
index a9bbf4e2354df5ce6b010873b419731343a12c7d..322becc3b4ad50b8e87fae8f6d5d38f7edc51f01 100644 (file)
@@ -1,6 +1,7 @@
 #include "cache.h"
 #include "tree-walk.h"
 #include "unpack-trees.h"
+#include "dir.h"
 #include "tree.h"
 
 static const char *get_mode(const char *str, unsigned int *modep)
@@ -455,3 +456,186 @@ int get_tree_entry(const unsigned char *tree_sha1, const char *name, unsigned ch
        free(tree);
        return retval;
 }
+
+static int match_entry(const struct name_entry *entry, int pathlen,
+                      const char *match, int matchlen,
+                      int *never_interesting)
+{
+       int m = -1; /* signals that we haven't called strncmp() */
+
+       if (*never_interesting) {
+               /*
+                * We have not seen any match that sorts later
+                * than the current path.
+                */
+
+               /*
+                * Does match sort strictly earlier than path
+                * with their common parts?
+                */
+               m = strncmp(match, entry->path,
+                           (matchlen < pathlen) ? matchlen : pathlen);
+               if (m < 0)
+                       return 0;
+
+               /*
+                * If we come here even once, that means there is at
+                * least one pathspec that would sort equal to or
+                * later than the path we are currently looking at.
+                * In other words, if we have never reached this point
+                * after iterating all pathspecs, it means all
+                * pathspecs are either outside of base, or inside the
+                * base but sorts strictly earlier than the current
+                * one.  In either case, they will never match the
+                * subsequent entries.  In such a case, we initialized
+                * the variable to -1 and that is what will be
+                * returned, allowing the caller to terminate early.
+                */
+               *never_interesting = 0;
+       }
+
+       if (pathlen > matchlen)
+               return 0;
+
+       if (matchlen > pathlen) {
+               if (match[pathlen] != '/')
+                       return 0;
+               if (!S_ISDIR(entry->mode))
+                       return 0;
+       }
+
+       if (m == -1)
+               /*
+                * we cheated and did not do strncmp(), so we do
+                * that here.
+                */
+               m = strncmp(match, entry->path, pathlen);
+
+       /*
+        * If common part matched earlier then it is a hit,
+        * because we rejected the case where path is not a
+        * leading directory and is shorter than match.
+        */
+       if (!m)
+               return 1;
+
+       return 0;
+}
+
+static int match_dir_prefix(const char *base, int baselen,
+                           const char *match, int matchlen)
+{
+       if (strncmp(base, match, matchlen))
+               return 0;
+
+       /*
+        * If the base is a subdirectory of a path which
+        * was specified, all of them are interesting.
+        */
+       if (!matchlen ||
+           base[matchlen] == '/' ||
+           match[matchlen - 1] == '/')
+               return 1;
+
+       /* Just a random prefix match */
+       return 0;
+}
+
+/*
+ * Is a tree entry interesting given the pathspec we have?
+ *
+ * Pre-condition: either baselen == base_offset (i.e. empty path)
+ * or base[baselen-1] == '/' (i.e. with trailing slash).
+ *
+ * Return:
+ *  - 2 for "yes, and all subsequent entries will be"
+ *  - 1 for yes
+ *  - zero for no
+ *  - negative for "no, and no subsequent entries will be either"
+ */
+int tree_entry_interesting(const struct name_entry *entry,
+                          struct strbuf *base, int base_offset,
+                          const struct pathspec *ps)
+{
+       int i;
+       int pathlen, baselen = base->len - base_offset;
+       int never_interesting = ps->has_wildcard ? 0 : -1;
+
+       if (!ps->nr) {
+               if (!ps->recursive || ps->max_depth == -1)
+                       return 2;
+               return !!within_depth(base->buf + base_offset, baselen,
+                                     !!S_ISDIR(entry->mode),
+                                     ps->max_depth);
+       }
+
+       pathlen = tree_entry_len(entry->path, entry->sha1);
+
+       for (i = ps->nr - 1; i >= 0; i--) {
+               const struct pathspec_item *item = ps->items+i;
+               const char *match = item->match;
+               const char *base_str = base->buf + base_offset;
+               int matchlen = item->len;
+
+               if (baselen >= matchlen) {
+                       /* If it doesn't match, move along... */
+                       if (!match_dir_prefix(base_str, baselen, match, matchlen))
+                               goto match_wildcards;
+
+                       if (!ps->recursive || ps->max_depth == -1)
+                               return 2;
+
+                       return !!within_depth(base_str + matchlen + 1,
+                                             baselen - matchlen - 1,
+                                             !!S_ISDIR(entry->mode),
+                                             ps->max_depth);
+               }
+
+               /* Does the base match? */
+               if (!strncmp(base_str, match, baselen)) {
+                       if (match_entry(entry, pathlen,
+                                       match + baselen, matchlen - baselen,
+                                       &never_interesting))
+                               return 1;
+
+                       if (ps->items[i].has_wildcard) {
+                               if (!fnmatch(match + baselen, entry->path, 0))
+                                       return 1;
+
+                               /*
+                                * Match all directories. We'll try to
+                                * match files later on.
+                                */
+                               if (ps->recursive && S_ISDIR(entry->mode))
+                                       return 1;
+                       }
+
+                       continue;
+               }
+
+match_wildcards:
+               if (!ps->items[i].has_wildcard)
+                       continue;
+
+               /*
+                * Concatenate base and entry->path into one and do
+                * fnmatch() on it.
+                */
+
+               strbuf_add(base, entry->path, pathlen);
+
+               if (!fnmatch(match, base->buf + base_offset, 0)) {
+                       strbuf_setlen(base, base_offset + baselen);
+                       return 1;
+               }
+               strbuf_setlen(base, base_offset + baselen);
+
+               /*
+                * Match all directories. We'll try to match files
+                * later on.
+                */
+               if (ps->recursive && S_ISDIR(entry->mode))
+                       return 1;
+       }
+       return never_interesting; /* No matches */
+}
index 7e3e0b5ad16710c06464726ac04d2b1c48af3708..39524b7dba6a1d0b63c4cd2b42db59a27a030b21 100644 (file)
@@ -60,4 +60,6 @@ static inline int traverse_path_len(const struct traverse_info *info, const stru
        return info->pathlen + tree_entry_len(n->path, n->sha1);
 }
 
+extern int tree_entry_interesting(const struct name_entry *, struct strbuf *, int, const struct pathspec *ps);
+
 #endif
index 123582b6cbdff90f4665fbf8db2c92d701680e4d..a82b11d341c9dddb541cc72a5568769d9f538780 100644 (file)
@@ -323,7 +323,7 @@ static void wt_status_collect_changes_worktree(struct wt_status *s)
     }
        rev.diffopt.format_callback = wt_status_collect_changed_cb;
        rev.diffopt.format_callback_data = s;
-       rev.prune_data = s->pathspec;
+       init_pathspec(&rev.prune_data, s->pathspec);
        run_diff_files(&rev, 0);
 }
 
@@ -348,20 +348,22 @@ static void wt_status_collect_changes_index(struct wt_status *s)
        rev.diffopt.detect_rename = 1;
        rev.diffopt.rename_limit = 200;
        rev.diffopt.break_opt = 0;
-       rev.prune_data = s->pathspec;
+       init_pathspec(&rev.prune_data, s->pathspec);
        run_diff_index(&rev, 1);
 }
 
 static void wt_status_collect_changes_initial(struct wt_status *s)
 {
+       struct pathspec pathspec;
        int i;
 
+       init_pathspec(&pathspec, s->pathspec);
        for (i = 0; i < active_nr; i++) {
                struct string_list_item *it;
                struct wt_status_change_data *d;
                struct cache_entry *ce = active_cache[i];
 
-               if (!ce_path_match(ce, s->pathspec))
+               if (!ce_path_match(ce, &pathspec))
                        continue;
                it = string_list_insert(&s->change, ce->name);
                d = it->util;
@@ -376,6 +378,7 @@ static void wt_status_collect_changes_initial(struct wt_status *s)
                else
                        d->index_status = DIFF_STATUS_ADDED;
        }
+       free_pathspec(&pathspec);
 }
 
 static void wt_status_collect_untracked(struct wt_status *s)