Merge branch 'jc/apply' into next
authorJunio C Hamano <junkio@cox.net>
Wed, 17 May 2006 23:56:20 +0000 (16:56 -0700)
committerJunio C Hamano <junkio@cox.net>
Wed, 17 May 2006 23:56:20 +0000 (16:56 -0700)
* jc/apply:
  apply --cached: do not check newly added file in the working tree

66 files changed:
.gitignore
Documentation/diff-options.txt
Documentation/git-grep.txt
Documentation/git-merge-base.txt
Documentation/git-read-tree.txt
Documentation/git-rebase.txt
Documentation/git-rev-parse.txt
Documentation/git-write-tree.txt
Makefile
apply.c
builtin-add.c [new file with mode: 0644]
builtin-diff.c
builtin-grep.c [new file with mode: 0644]
builtin-log.c
builtin.h
cache-tree.c [new file with mode: 0644]
cache-tree.h [new file with mode: 0644]
cache.h
checkout-index.c
combine-diff.c
commit.c
commit.h
config.c
contrib/remotes2config.sh [new file with mode: 0644]
date.c
delta.h
diff.c
diff.h
dir.c [new file with mode: 0644]
dir.h [new file with mode: 0644]
dump-cache-tree.c [new file with mode: 0644]
fsck-objects.c
git-am.sh
git-branch.sh
git-format-patch.sh
git-grep.sh [deleted file]
git-merge.sh
git-parse-remote.sh
git-rebase.sh
git-request-pull.sh
git-reset.sh
git-send-email.perl
git-tag.sh
git.c
log-tree.c
ls-files.c
merge-base.c
pack-objects.c
read-cache.c
read-tree.c
refs.c
refs.h
repo-config.c
rev-list.c
rev-parse.c
revision.h
sha1_file.c
show-branch.c
t/t0000-basic.sh
t/t1002-read-tree-m-u-2way.sh
t/t1300-repo-config.sh
t/t3500-cherry.sh
t/t4002-diff-basic.sh
t/t6022-merge-rename.sh
update-index.c
write-tree.c

index b5959d6..7906909 100644 (file)
@@ -123,6 +123,7 @@ git-write-tree
 git-core-*/?*
 test-date
 test-delta
+test-dump-cache-tree
 common-cmds.h
 *.tar.gz
 *.dsc
index c183dc9..f523ec2 100644 (file)
 --stat::
        Generate a diffstat instead of a patch.
 
+--summary::
+       Output a condensed summary of extended header information
+       such as creations, renames and mode changes.
+
 --patch-with-stat::
        Generate patch and prepend its diffstat.
 
index d55456a..74102b7 100644 (file)
@@ -8,43 +8,82 @@ git-grep - Print lines matching a pattern
 
 SYNOPSIS
 --------
-'git-grep' [<option>...] [-e] <pattern> [--] [<path>...]
+[verse]
+'git-grep' [--cached]
+          [-a | --text] [-I] [-i | --ignore-case] [-w | --word-regexp]
+          [-v | --invert-match]
+          [-E | --extended-regexp] [-G | --basic-regexp] [-F | --fixed-strings]
+          [-n] [-l | --files-with-matches] [-L | --files-without-match]
+          [-c | --count]
+          [-A <post-context>] [-B <pre-context>] [-C <context>]
+          [-f <file>] [-e <pattern>]
+          [<tree>...]
+          [--] [<path>...]
 
 DESCRIPTION
 -----------
-Searches list of files `git-ls-files` produces for lines
-containing a match to the given pattern.
+Look for specified patterns in the working tree files, blobs
+registered in the index file, or given tree objects.
 
 
 OPTIONS
 -------
-`--`::
-       Signals the end of options; the rest of the parameters
-       are <path> limiters.
+--cached::
+       Instead of searching in the working tree files, check
+       the blobs registerd in the index file.
+
+-a | --text::
+       Process binary files as if they were text.
+
+-i | --ignore-case::
+       Ignore case differences between the patterns and the
+       files.
+
+-w | --word-regexp::
+       Match the pattern only at word boundary (either begin at the
+       beginning of a line, or preceded by a non-word character; end at
+       the end of a line or followed by a non-word character).
+
+-v | --invert-match::
+       Select non-matching lines.
+
+-E | --extended-regexp | -G | --basic-regexp::
+       Use POSIX extended/basic regexp for patterns.  Default
+       is to use basic regexp.
 
-<option>...::
-       Either an option to pass to `grep` or `git-ls-files`.
-+
-The following are the specific `git-ls-files` options
-that may be given: `-o`, `--cached`, `--deleted`, `--others`,
-`--killed`, `--ignored`, `--modified`, `--exclude=\*`,
-`--exclude-from=\*`, and `--exclude-per-directory=\*`.
-+
-All other options will be passed to `grep`.
+-n::
+       Prefix the line number to matching lines.
 
-<pattern>::
-       The pattern to look for.  The first non option is taken
-       as the pattern; if your pattern begins with a dash, use
-       `-e <pattern>`.
+-l | --files-with-matches | -L | --files-without-match::
+       Instead of showing every matched line, show only the
+       names of files that contain (or do not contain) matches.
 
-<path>...::
-       Optional paths to limit the set of files to be searched;
-       passed to `git-ls-files`.
+-c | --count::
+       Instead of showing every matched line, show the number of
+       lines that match.
+
+-[ABC] <context>::
+       Show `context` trailing (`A` -- after), or leading (`B`
+       -- before), or both (`C` -- context) lines, and place a
+       line containing `--` between continguous groups of
+       matches.
+
+-f <file>::
+       Read patterns from <file>, one per line.
+
+`<tree>...`::
+       Search blobs in the trees for specified patterns.
+
+`--`::
+       Signals the end of options; the rest of the parameters
+       are <path> limiters.
 
 
 Author
 ------
-Written by Linus Torvalds <torvalds@osdl.org>
+Originally written by Linus Torvalds <torvalds@osdl.org>, later
+revamped by Junio C Hamano.
+
 
 Documentation
 --------------
index d1d56f1..6099be2 100644 (file)
@@ -8,16 +8,26 @@ git-merge-base - Finds as good a common ancestor as possible for a merge
 
 SYNOPSIS
 --------
-'git-merge-base' <commit> <commit>
+'git-merge-base' [--all] <commit> <commit>
 
 DESCRIPTION
 -----------
-"git-merge-base" finds as good a common ancestor as possible. Given a
-selection of equally good common ancestors it should not be relied on
-to decide in any particular way.
+
+"git-merge-base" finds as good a common ancestor as possible between
+the two commits. That is, given two commits A and B 'git-merge-base A
+B' will output a commit which is reachable from both A and B through
+the parent relationship.
+
+Given a selection of equally good common ancestors it should not be
+relied on to decide in any particular way.
 
 The "git-merge-base" algorithm is still in flux - use the source...
 
+OPTIONS
+-------
+--all::
+       Output all common ancestors for the two commits instead of
+       just one.
 
 Author
 ------
index 844cfda..1f21d95 100644 (file)
@@ -8,7 +8,7 @@ git-read-tree - Reads tree information into the index
 
 SYNOPSIS
 --------
-'git-read-tree' (<tree-ish> | [[-m [--aggressive]| --reset] [-u | -i]] <tree-ish1> [<tree-ish2> [<tree-ish3>]])
+'git-read-tree' (<tree-ish> | [[-m [--aggressive] | --reset | --prefix=<prefix>] [-u | -i]] <tree-ish1> [<tree-ish2> [<tree-ish3>]])
 
 
 DESCRIPTION
@@ -63,6 +63,15 @@ OPTIONS
 * when both sides adds a path identically.  The resolution
   is to add that path.
 
+--prefix=<prefix>/::
+       Keep the current index contents, and read the contents
+       of named tree-ish under directory at `<prefix>`.  The
+       original index file cannot have anything at the path
+       `<prefix>` itself, and have nothing in `<prefix>/`
+       directory.  Note that the `<prefix>/` value must end
+       with a slash.
+
+
 <tree-ish#>::
        The id of the tree object(s) to be read/merged.
 
index 1b482ab..08ee4aa 100644 (file)
@@ -9,9 +9,7 @@ SYNOPSIS
 --------
 'git-rebase' [--onto <newbase>] <upstream> [<branch>]
 
-'git-rebase' --continue
-
-'git-rebase' --abort
+'git-rebase' --continue | --skip | --abort
 
 DESCRIPTION
 -----------
@@ -23,9 +21,10 @@ not exist in the <upstream> branch.
 
 It is possible that a merge failure will prevent this process from being
 completely automatic.  You will have to resolve any such merge failure
-and run `git rebase --continue`.  If you can not resolve the merge
-failure, running `git rebase --abort` will restore the original <branch>
-and remove the working files found in the .dotest directory.
+and run `git rebase --continue`.  Another option is to bypass the commit
+that caused the merge failure with `git rebase --skip`.  To restore the
+original <branch> and remove the .dotest working files, use the command
+`git rebase --abort` instead.
 
 Note that if <branch> is not specified on the command line, the currently
 checked out branch is used.
index 8b95df0..ab896fc 100644 (file)
@@ -67,6 +67,15 @@ OPTIONS
 --all::
        Show all refs found in `$GIT_DIR/refs`.
 
+--branches::
+       Show branch refs found in `$GIT_DIR/refs/heads`.
+
+--tags::
+       Show tag refs found in `$GIT_DIR/refs/tags`.
+
+--remotes::
+       Show tag refs found in `$GIT_DIR/refs/remotes`.
+
 --show-prefix::
        When the command is invoked from a subdirectory, show the
        path of the current directory relative to the top-level
index 77e12cb..c85fa89 100644 (file)
@@ -8,7 +8,7 @@ git-write-tree - Creates a tree object from the current index
 
 SYNOPSIS
 --------
-'git-write-tree' [--missing-ok]
+'git-write-tree' [--missing-ok] [--prefix=<prefix>/]
 
 DESCRIPTION
 -----------
@@ -30,6 +30,12 @@ OPTIONS
        directory exist in the object database.  This option disables this
        check.
 
+--prefix=<prefix>/::
+       Writes a tree object that represents a subdirectory
+       `<prefix>`.  This can be used to write the tree object
+       for a subproject that is in the named subdirectory.
+
+
 Author
 ------
 Written by Linus Torvalds <torvalds@osdl.org>
index 93918a0..a1d2e08 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -124,7 +124,7 @@ SCRIPT_SH = \
        git-tag.sh git-verify-tag.sh \
        git-applymbox.sh git-applypatch.sh git-am.sh \
        git-merge.sh git-merge-stupid.sh git-merge-octopus.sh \
-       git-merge-resolve.sh git-merge-ours.sh git-grep.sh \
+       git-merge-resolve.sh git-merge-ours.sh \
        git-lost-found.sh
 
 SCRIPT_PERL = \
@@ -169,7 +169,8 @@ PROGRAMS = \
        git-describe$X git-merge-tree$X git-blame$X git-imap-send$X
 
 BUILT_INS = git-log$X git-whatchanged$X git-show$X \
-       git-count-objects$X git-diff$X git-push$X
+       git-count-objects$X git-diff$X git-push$X \
+       git-grep$X
 
 # what 'all' will build and 'install' will install, in gitexecdir
 ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS)
@@ -198,7 +199,7 @@ LIB_H = \
        blob.h cache.h commit.h csum-file.h delta.h \
        diff.h object.h pack.h pkt-line.h quote.h refs.h \
        run-command.h strbuf.h tag.h tree.h git-compat-util.h revision.h \
-       tree-walk.h log-tree.h
+       tree-walk.h log-tree.h dir.h
 
 DIFF_OBJS = \
        diff.o diff-lib.o diffcore-break.o diffcore-order.o \
@@ -206,17 +207,18 @@ DIFF_OBJS = \
        diffcore-delta.o log-tree.o
 
 LIB_OBJS = \
-       blob.o commit.o connect.o csum-file.o base85.o \
+       blob.o commit.o connect.o csum-file.o cache-tree.o base85.o \
        date.o diff-delta.o entry.o exec_cmd.o ident.o index.o \
        object.o pack-check.o patch-delta.o path.o pkt-line.o \
-       quote.o read-cache.o refs.o run-command.o \
+       quote.o read-cache.o refs.o run-command.o dir.o \
        server-info.o setup.o sha1_file.o sha1_name.o strbuf.o \
        tag.o tree.o usage.o config.o environment.o ctype.o copy.o \
        fetch-clone.o revision.o pager.o tree-walk.o xdiff-interface.o \
        $(DIFF_OBJS)
 
 BUILTIN_OBJS = \
-       builtin-log.o builtin-help.o builtin-count.o builtin-diff.o builtin-push.o
+       builtin-log.o builtin-help.o builtin-count.o builtin-diff.o builtin-push.o \
+       builtin-grep.o builtin-add.o
 
 GITLIBS = $(LIB_FILE) $(XDIFF_LIB)
 LIBS = $(GITLIBS) -lz
@@ -286,7 +288,9 @@ ifeq ($(uname_S),OpenBSD)
        ALL_LDFLAGS += -L/usr/local/lib
 endif
 ifeq ($(uname_S),NetBSD)
-       NEEDS_LIBICONV = YesPlease
+       ifeq ($(shell expr "$(uname_R)" : '[01]\.'),2)
+               NEEDS_LIBICONV = YesPlease
+       endif
        ALL_CFLAGS += -I/usr/pkg/include
        ALL_LDFLAGS += -L/usr/pkg/lib -Wl,-rpath,/usr/pkg/lib
 endif
@@ -606,6 +610,9 @@ test-date$X: test-date.c date.o ctype.o
 test-delta$X: test-delta.c diff-delta.o patch-delta.o
        $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $^
 
+test-dump-cache-tree$X: dump-cache-tree.o $(GITLIBS)
+       $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
+
 check:
        for i in *.c; do sparse $(ALL_CFLAGS) $(SPARSE_FLAGS) $$i || exit; done
 
diff --git a/apply.c b/apply.c
index 0ed9d13..5341e30 100644 (file)
--- a/apply.c
+++ b/apply.c
@@ -8,6 +8,7 @@
  */
 #include <fnmatch.h>
 #include "cache.h"
+#include "cache-tree.h"
 #include "quote.h"
 #include "blob.h"
 #include "delta.h"
@@ -1917,6 +1918,7 @@ static void remove_file(struct patch *patch)
        if (write_index) {
                if (remove_file_from_cache(patch->old_name) < 0)
                        die("unable to remove %s from index", patch->old_name);
+               cache_tree_invalidate_path(active_cache_tree, patch->old_name);
        }
        if (!cached)
                unlink(patch->old_name);
@@ -2018,8 +2020,9 @@ static void create_file(struct patch *patch)
 
        if (!mode)
                mode = S_IFREG | 0644;
-       create_one_file(path, mode, buf, size); 
+       create_one_file(path, mode, buf, size);
        add_index_file(path, mode, buf, size);
+       cache_tree_invalidate_path(active_cache_tree, path);
 }
 
 static void write_out_one_result(struct patch *patch)
diff --git a/builtin-add.c b/builtin-add.c
new file mode 100644 (file)
index 0000000..7083820
--- /dev/null
@@ -0,0 +1,269 @@
+/*
+ * "git add" builtin command
+ *
+ * Copyright (C) 2006 Linus Torvalds
+ */
+#include <fnmatch.h>
+
+#include "cache.h"
+#include "builtin.h"
+#include "dir.h"
+#include "cache-tree.h"
+
+static const char builtin_add_usage[] =
+"git-add [-n] [-v] <filepattern>...";
+
+static int common_prefix(const char **pathspec)
+{
+       const char *path, *slash, *next;
+       int prefix;
+
+       if (!pathspec)
+               return 0;
+
+       path = *pathspec;
+       slash = strrchr(path, '/');
+       if (!slash)
+               return 0;
+
+       prefix = slash - path + 1;
+       while ((next = *++pathspec) != NULL) {
+               int len = strlen(next);
+               if (len >= prefix && !memcmp(path, next, len))
+                       continue;
+               for (;;) {
+                       if (!len)
+                               return 0;
+                       if (next[--len] != '/')
+                               continue;
+                       if (memcmp(path, next, len+1))
+                               continue;
+                       prefix = len + 1;
+                       break;
+               }
+       }
+       return prefix;
+}
+
+static int match_one(const char *match, const char *name, int namelen)
+{
+       int matchlen;
+
+       /* If the match was just the prefix, we matched */
+       matchlen = strlen(match);
+       if (!matchlen)
+               return 1;
+
+       /*
+        * If we don't match the matchstring exactly,
+        * we need to match by fnmatch
+        */
+       if (strncmp(match, name, matchlen))
+               return !fnmatch(match, name, 0);
+
+       /*
+        * If we did match the string exactly, we still
+        * need to make sure that it happened on a path
+        * component boundary (ie either the last character
+        * of the match was '/', or the next character of
+        * the name was '/' or the terminating NUL.
+        */
+       return  match[matchlen-1] == '/' ||
+               name[matchlen] == '/' ||
+               !name[matchlen];
+}
+
+static int match(const char **pathspec, const char *name, int namelen, int prefix, char *seen)
+{
+       int retval;
+       const char *match;
+
+       name += prefix;
+       namelen -= prefix;
+
+       for (retval = 0; (match = *pathspec++) != NULL; seen++) {
+               if (retval & *seen)
+                       continue;
+               match += prefix;
+               if (match_one(match, name, namelen)) {
+                       retval = 1;
+                       *seen = 1;
+               }
+       }
+       return retval;
+}
+
+static void prune_directory(struct dir_struct *dir, const char **pathspec, int prefix)
+{
+       char *seen;
+       int i, specs;
+       struct dir_entry **src, **dst;
+
+       for (specs = 0; pathspec[specs];  specs++)
+               /* nothing */;
+       seen = xmalloc(specs);
+       memset(seen, 0, specs);
+
+       src = dst = dir->entries;
+       i = dir->nr;
+       while (--i >= 0) {
+               struct dir_entry *entry = *src++;
+               if (!match(pathspec, entry->name, entry->len, prefix, seen)) {
+                       free(entry);
+                       continue;
+               }
+               *dst++ = entry;
+       }
+       dir->nr = dst - dir->entries;
+
+       for (i = 0; i < specs; i++) {
+               struct stat st;
+               const char *match;
+               if (seen[i])
+                       continue;
+
+               /* Existing file? We must have ignored it */
+               match = pathspec[i];
+               if (!lstat(match, &st))
+                       continue;
+               die("pathspec '%s' did not match any files", match);
+       }
+}
+
+static void fill_directory(struct dir_struct *dir, const char **pathspec)
+{
+       const char *path, *base;
+       int baselen;
+
+       /* Set up the default git porcelain excludes */
+       memset(dir, 0, sizeof(*dir));
+       dir->exclude_per_dir = ".gitignore";
+       path = git_path("info/exclude");
+       if (!access(path, R_OK))
+               add_excludes_from_file(dir, path);
+
+       /*
+        * Calculate common prefix for the pathspec, and
+        * use that to optimize the directory walk
+        */
+       baselen = common_prefix(pathspec);
+       path = ".";
+       base = "";
+       if (baselen) {
+               char *common = xmalloc(baselen + 1);
+               common = xmalloc(baselen + 1);
+               memcpy(common, *pathspec, baselen);
+               common[baselen] = 0;
+               path = base = common;
+       }
+
+       /* Read the directory and prune it */
+       read_directory(dir, path, base, baselen);
+       if (pathspec)
+               prune_directory(dir, pathspec, baselen);
+}
+
+static int add_file_to_index(const char *path, int verbose)
+{
+       int size, namelen;
+       struct stat st;
+       struct cache_entry *ce;
+
+       if (lstat(path, &st))
+               die("%s: unable to stat (%s)", path, strerror(errno));
+
+       if (!S_ISREG(st.st_mode) && !S_ISLNK(st.st_mode))
+               die("%s: can only add regular files or symbolic links", path);
+
+       namelen = strlen(path);
+       size = cache_entry_size(namelen);
+       ce = xcalloc(1, size);
+       memcpy(ce->name, path, namelen);
+       ce->ce_flags = htons(namelen);
+       fill_stat_cache_info(ce, &st);
+
+       ce->ce_mode = create_ce_mode(st.st_mode);
+       if (!trust_executable_bit) {
+               /* If there is an existing entry, pick the mode bits
+                * from it.
+                */
+               int pos = cache_name_pos(path, namelen);
+               if (pos >= 0)
+                       ce->ce_mode = active_cache[pos]->ce_mode;
+       }
+
+       if (index_path(ce->sha1, path, &st, 1))
+               die("unable to index file %s", path);
+       if (add_cache_entry(ce, ADD_CACHE_OK_TO_ADD))
+               die("unable to add %s to index",path);
+       if (verbose)
+               printf("add '%s'\n", path);
+       cache_tree_invalidate_path(active_cache_tree, path);
+       return 0;
+}
+
+static struct cache_file cache_file;
+
+int cmd_add(int argc, const char **argv, char **envp)
+{
+       int i, newfd;
+       int verbose = 0, show_only = 0;
+       const char *prefix = setup_git_directory();
+       const char **pathspec;
+       struct dir_struct dir;
+
+       git_config(git_default_config);
+
+       newfd = hold_index_file_for_update(&cache_file, get_index_file());
+       if (newfd < 0)
+               die("unable to create new cachefile");
+
+       if (read_cache() < 0)
+               die("index file corrupt");
+
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+
+               if (arg[0] != '-')
+                       break;
+               if (!strcmp(arg, "--")) {
+                       i++;
+                       break;
+               }
+               if (!strcmp(arg, "-n")) {
+                       show_only = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "-v")) {
+                       verbose = 1;
+                       continue;
+               }
+               die(builtin_add_usage);
+       }
+       git_config(git_default_config);
+       pathspec = get_pathspec(prefix, argv + i);
+
+       fill_directory(&dir, pathspec);
+
+       if (show_only) {
+               const char *sep = "", *eof = "";
+               for (i = 0; i < dir.nr; i++) {
+                       printf("%s%s", sep, dir.entries[i]->name);
+                       sep = " ";
+                       eof = "\n";
+               }
+               fputs(eof, stdout);
+               return 0;
+       }
+
+       for (i = 0; i < dir.nr; i++)
+               add_file_to_index(dir.entries[i]->name, verbose);
+
+       if (active_cache_changed) {
+               if (write_cache(newfd, active_cache, active_nr) ||
+                   commit_index_file(&cache_file))
+                       die("Unable to write new index file");
+       }
+
+       return 0;
+}
index d3ac581..95d2c69 100644 (file)
@@ -132,7 +132,9 @@ static int builtin_diff_blobs(struct rev_info *revs,
                              int argc, const char **argv,
                              struct blobinfo *blob)
 {
-       /* Blobs */
+       /* Blobs: the arguments are reversed when setup_revisions()
+        * picked them up.
+        */
        unsigned mode = canon_mode(S_IFREG | 0644);
 
        while (1 < argc) {
@@ -145,8 +147,8 @@ static int builtin_diff_blobs(struct rev_info *revs,
        }
        stuff_change(&revs->diffopt,
                     mode, mode,
-                    blob[0].sha1, blob[1].sha1,
-                    blob[1].name, blob[1].name);
+                    blob[1].sha1, blob[0].sha1,
+                    blob[0].name, blob[0].name);
        diffcore_std(&revs->diffopt);
        diff_flush(&revs->diffopt);
        return 0;
@@ -231,7 +233,7 @@ static int builtin_diff_combined(struct rev_info *revs,
        return 0;
 }
 
-static void add_head(struct rev_info *revs)
+void add_head(struct rev_info *revs)
 {
        unsigned char sha1[20];
        struct object *obj;
diff --git a/builtin-grep.c b/builtin-grep.c
new file mode 100644 (file)
index 0000000..d09ddf0
--- /dev/null
@@ -0,0 +1,902 @@
+/*
+ * Builtin "git grep"
+ *
+ * Copyright (c) 2006 Junio C Hamano
+ */
+#include "cache.h"
+#include "blob.h"
+#include "tree.h"
+#include "commit.h"
+#include "tag.h"
+#include "tree-walk.h"
+#include "builtin.h"
+#include <regex.h>
+#include <fnmatch.h>
+#include <sys/wait.h>
+
+/*
+ * 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 namelen, i;
+       if (!paths || !*paths)
+               return 1;
+       namelen = strlen(name);
+       for (i = 0; paths[i]; i++) {
+               const char *match = paths[i];
+               int matchlen = strlen(match);
+               const char *cp, *meta;
+
+               if ((matchlen <= namelen) &&
+                   !strncmp(name, match, matchlen) &&
+                   (match[matchlen-1] == '/' ||
+                    name[matchlen] == '\0' || name[matchlen] == '/'))
+                       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;
+}
+
+struct grep_pat {
+       struct grep_pat *next;
+       const char *origin;
+       int no;
+       const char *pattern;
+       regex_t regexp;
+};
+
+struct grep_opt {
+       struct grep_pat *pattern_list;
+       struct grep_pat **pattern_tail;
+       regex_t regexp;
+       unsigned linenum:1;
+       unsigned invert:1;
+       unsigned name_only:1;
+       unsigned unmatch_name_only:1;
+       unsigned count:1;
+       unsigned word_regexp:1;
+       unsigned fixed:1;
+#define GREP_BINARY_DEFAULT    0
+#define GREP_BINARY_NOMATCH    1
+#define GREP_BINARY_TEXT       2
+       unsigned binary:2;
+       int regflags;
+       unsigned pre_context;
+       unsigned post_context;
+};
+
+static void add_pattern(struct grep_opt *opt, const char *pat,
+                       const char *origin, int no)
+{
+       struct grep_pat *p = xcalloc(1, sizeof(*p));
+       p->pattern = pat;
+       p->origin = origin;
+       p->no = no;
+       *opt->pattern_tail = p;
+       opt->pattern_tail = &p->next;
+       p->next = NULL;
+}
+
+static void compile_patterns(struct grep_opt *opt)
+{
+       struct grep_pat *p;
+       for (p = opt->pattern_list; p; p = p->next) {
+               int err = regcomp(&p->regexp, p->pattern, opt->regflags);
+               if (err) {
+                       char errbuf[1024];
+                       char where[1024];
+                       if (p->no)
+                               sprintf(where, "In '%s' at %d, ",
+                                       p->origin, p->no);
+                       else if (p->origin)
+                               sprintf(where, "%s, ", p->origin);
+                       else
+                               where[0] = 0;
+                       regerror(err, &p->regexp, errbuf, 1024);
+                       regfree(&p->regexp);
+                       die("%s'%s': %s", where, p->pattern, errbuf);
+               }
+       }
+}
+
+static char *end_of_line(char *cp, unsigned long *left)
+{
+       unsigned long l = *left;
+       while (l && *cp != '\n') {
+               l--;
+               cp++;
+       }
+       *left = l;
+       return cp;
+}
+
+static int word_char(char ch)
+{
+       return isalnum(ch) || ch == '_';
+}
+
+static void show_line(struct grep_opt *opt, const char *bol, const char *eol,
+                     const char *name, unsigned lno, char sign)
+{
+       printf("%s%c", name, sign);
+       if (opt->linenum)
+               printf("%d%c", lno, sign);
+       printf("%.*s\n", (int)(eol-bol), bol);
+}
+
+/*
+ * NEEDSWORK: share code with diff.c
+ */
+#define FIRST_FEW_BYTES 8000
+static int buffer_is_binary(const char *ptr, unsigned long size)
+{
+       if (FIRST_FEW_BYTES < size)
+               size = FIRST_FEW_BYTES;
+       if (memchr(ptr, 0, size))
+               return 1;
+       return 0;
+}
+
+static int fixmatch(const char *pattern, char *line, regmatch_t *match)
+{
+       char *hit = strstr(line, pattern);
+       if (!hit) {
+               match->rm_so = match->rm_eo = -1;
+               return REG_NOMATCH;
+       }
+       else {
+               match->rm_so = hit - line;
+               match->rm_eo = match->rm_so + strlen(pattern);
+               return 0;
+       }
+}
+
+static int grep_buffer(struct grep_opt *opt, const char *name,
+                      char *buf, unsigned long size)
+{
+       char *bol = buf;
+       unsigned long left = size;
+       unsigned lno = 1;
+       struct pre_context_line {
+               char *bol;
+               char *eol;
+       } *prev = NULL, *pcl;
+       unsigned last_hit = 0;
+       unsigned last_shown = 0;
+       int binary_match_only = 0;
+       const char *hunk_mark = "";
+       unsigned count = 0;
+
+       if (buffer_is_binary(buf, size)) {
+               switch (opt->binary) {
+               case GREP_BINARY_DEFAULT:
+                       binary_match_only = 1;
+                       break;
+               case GREP_BINARY_NOMATCH:
+                       return 0; /* Assume unmatch */
+                       break;
+               default:
+                       break;
+               }
+       }
+
+       if (opt->pre_context)
+               prev = xcalloc(opt->pre_context, sizeof(*prev));
+       if (opt->pre_context || opt->post_context)
+               hunk_mark = "--\n";
+
+       while (left) {
+               regmatch_t pmatch[10];
+               char *eol, ch;
+               int hit = 0;
+               struct grep_pat *p;
+
+               eol = end_of_line(bol, &left);
+               ch = *eol;
+               *eol = 0;
+
+               for (p = opt->pattern_list; p; p = p->next) {
+                       if (!opt->fixed) {
+                               regex_t *exp = &p->regexp;
+                               hit = !regexec(exp, bol, ARRAY_SIZE(pmatch),
+                                              pmatch, 0);
+                       }
+                       else {
+                               hit = !fixmatch(p->pattern, bol, pmatch);
+                       }
+
+                       if (hit && opt->word_regexp) {
+                               /* Match beginning must be either
+                                * beginning of the line, or at word
+                                * boundary (i.e. the last char must
+                                * not be alnum or underscore).
+                                */
+                               if ((pmatch[0].rm_so < 0) ||
+                                   (eol - bol) <= pmatch[0].rm_so ||
+                                   (pmatch[0].rm_eo < 0) ||
+                                   (eol - bol) < pmatch[0].rm_eo)
+                                       die("regexp returned nonsense");
+                               if (pmatch[0].rm_so != 0 &&
+                                   word_char(bol[pmatch[0].rm_so-1]))
+                                       hit = 0;
+                               if (pmatch[0].rm_eo != (eol-bol) &&
+                                   word_char(bol[pmatch[0].rm_eo]))
+                                       hit = 0;
+                       }
+                       if (hit)
+                               break;
+               }
+               /* "grep -v -e foo -e bla" should list lines
+                * that do not have either, so inversion should
+                * be done outside.
+                */
+               if (opt->invert)
+                       hit = !hit;
+               if (opt->unmatch_name_only) {
+                       if (hit)
+                               return 0;
+                       goto next_line;
+               }
+               if (hit) {
+                       count++;
+                       if (binary_match_only) {
+                               printf("Binary file %s matches\n", name);
+                               return 1;
+                       }
+                       if (opt->name_only) {
+                               printf("%s\n", name);
+                               return 1;
+                       }
+                       /* Hit at this line.  If we haven't shown the
+                        * pre-context lines, we would need to show them.
+                        * When asked to do "count", this still show
+                        * the context which is nonsense, but the user
+                        * deserves to get that ;-).
+                        */
+                       if (opt->pre_context) {
+                               unsigned from;
+                               if (opt->pre_context < lno)
+                                       from = lno - opt->pre_context;
+                               else
+                                       from = 1;
+                               if (from <= last_shown)
+                                       from = last_shown + 1;
+                               if (last_shown && from != last_shown + 1)
+                                       printf(hunk_mark);
+                               while (from < lno) {
+                                       pcl = &prev[lno-from-1];
+                                       show_line(opt, pcl->bol, pcl->eol,
+                                                 name, from, '-');
+                                       from++;
+                               }
+                               last_shown = lno-1;
+                       }
+                       if (last_shown && lno != last_shown + 1)
+                               printf(hunk_mark);
+                       if (!opt->count)
+                               show_line(opt, bol, eol, name, lno, ':');
+                       last_shown = last_hit = lno;
+               }
+               else if (last_hit &&
+                        lno <= last_hit + opt->post_context) {
+                       /* If the last hit is within the post context,
+                        * we need to show this line.
+                        */
+                       if (last_shown && lno != last_shown + 1)
+                               printf(hunk_mark);
+                       show_line(opt, bol, eol, name, lno, '-');
+                       last_shown = lno;
+               }
+               if (opt->pre_context) {
+                       memmove(prev+1, prev,
+                               (opt->pre_context-1) * sizeof(*prev));
+                       prev->bol = bol;
+                       prev->eol = eol;
+               }
+
+       next_line:
+               *eol = ch;
+               bol = eol + 1;
+               if (!left)
+                       break;
+               left--;
+               lno++;
+       }
+
+       if (opt->unmatch_name_only) {
+               /* We did not see any hit, so we want to show this */
+               printf("%s\n", name);
+               return 1;
+       }
+
+       /* NEEDSWORK:
+        * The real "grep -c foo *.c" gives many "bar.c:0" lines,
+        * which feels mostly useless but sometimes useful.  Maybe
+        * make it another option?  For now suppress them.
+        */
+       if (opt->count && count)
+               printf("%s:%u\n", name, count);
+       return !!last_hit;
+}
+
+static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1, const char *name)
+{
+       unsigned long size;
+       char *data;
+       char type[20];
+       int hit;
+       data = read_sha1_file(sha1, type, &size);
+       if (!data) {
+               error("'%s': unable to read %s", name, sha1_to_hex(sha1));
+               return 0;
+       }
+       hit = grep_buffer(opt, name, data, size);
+       free(data);
+       return hit;
+}
+
+static int grep_file(struct grep_opt *opt, const char *filename)
+{
+       struct stat st;
+       int i;
+       char *data;
+       if (lstat(filename, &st) < 0) {
+       err_ret:
+               if (errno != ENOENT)
+                       error("'%s': %s", filename, strerror(errno));
+               return 0;
+       }
+       if (!st.st_size)
+               return 0; /* empty file -- no grep hit */
+       if (!S_ISREG(st.st_mode))
+               return 0;
+       i = open(filename, O_RDONLY);
+       if (i < 0)
+               goto err_ret;
+       data = xmalloc(st.st_size + 1);
+       if (st.st_size != xread(i, data, st.st_size)) {
+               error("'%s': short read %s", filename, strerror(errno));
+               close(i);
+               free(data);
+               return 0;
+       }
+       close(i);
+       i = grep_buffer(opt, filename, data, st.st_size);
+       free(data);
+       return i;
+}
+
+static int exec_grep(int argc, const char **argv)
+{
+       pid_t pid;
+       int status;
+
+       argv[argc] = NULL;
+       pid = fork();
+       if (pid < 0)
+               return pid;
+       if (!pid) {
+               execvp("grep", (char **) argv);
+               exit(255);
+       }
+       while (waitpid(pid, &status, 0) < 0) {
+               if (errno == EINTR)
+                       continue;
+               return -1;
+       }
+       if (WIFEXITED(status)) {
+               if (!WEXITSTATUS(status))
+                       return 1;
+               return 0;
+       }
+       return -1;
+}
+
+#define MAXARGS 1000
+#define ARGBUF 4096
+#define push_arg(a) do { \
+       if (nr < MAXARGS) argv[nr++] = (a); \
+       else die("maximum number of args exceeded"); \
+       } while (0)
+
+static int external_grep(struct grep_opt *opt, const char **paths, int cached)
+{
+       int i, nr, argc, hit, len;
+       const char *argv[MAXARGS+1];
+       char randarg[ARGBUF];
+       char *argptr = randarg;
+       struct grep_pat *p;
+
+       len = nr = 0;
+       push_arg("grep");
+       if (opt->fixed)
+               push_arg("-F");
+       if (opt->linenum)
+               push_arg("-n");
+       if (opt->regflags & REG_EXTENDED)
+               push_arg("-E");
+       if (opt->word_regexp)
+               push_arg("-w");
+       if (opt->name_only)
+               push_arg("-l");
+       if (opt->unmatch_name_only)
+               push_arg("-L");
+       if (opt->count)
+               push_arg("-c");
+       if (opt->post_context || opt->pre_context) {
+               if (opt->post_context != opt->pre_context) {
+                       if (opt->pre_context) {
+                               push_arg("-B");
+                               len += snprintf(argptr, sizeof(randarg)-len,
+                                               "%u", opt->pre_context);
+                               if (sizeof(randarg) <= len)
+                                       die("maximum length of args exceeded");
+                               push_arg(argptr);
+                               argptr += len;
+                       }
+                       if (opt->post_context) {
+                               push_arg("-A");
+                               len += snprintf(argptr, sizeof(randarg)-len,
+                                               "%u", opt->post_context);
+                               if (sizeof(randarg) <= len)
+                                       die("maximum length of args exceeded");
+                               push_arg(argptr);
+                               argptr += len;
+                       }
+               }
+               else {
+                       push_arg("-C");
+                       len += snprintf(argptr, sizeof(randarg)-len,
+                                       "%u", opt->post_context);
+                       if (sizeof(randarg) <= len)
+                               die("maximum length of args exceeded");
+                       push_arg(argptr);
+                       argptr += len;
+               }
+       }
+       for (p = opt->pattern_list; p; p = p->next) {
+               push_arg("-e");
+               push_arg(p->pattern);
+       }
+
+       /*
+        * To make sure we get the header printed out when we want it,
+        * add /dev/null to the paths to grep.  This is unnecessary
+        * (and wrong) with "-l" or "-L", which always print out the
+        * name anyway.
+        *
+        * GNU grep has "-H", but this is portable.
+        */
+       if (!opt->name_only && !opt->unmatch_name_only)
+               push_arg("/dev/null");
+
+       hit = 0;
+       argc = nr;
+       for (i = 0; i < active_nr; i++) {
+               struct cache_entry *ce = active_cache[i];
+               const char *name;
+               if (ce_stage(ce) || !S_ISREG(ntohl(ce->ce_mode)))
+                       continue;
+               if (!pathspec_matches(paths, ce->name))
+                       continue;
+               name = ce->name;
+               if (name[0] == '-') {
+                       int len = ce_namelen(ce);
+                       name = xmalloc(len + 3);
+                       memcpy(name, "./", 2);
+                       memcpy(name + 2, ce->name, len + 1);
+               }
+               argv[argc++] = name;
+               if (argc < MAXARGS)
+                       continue;
+               hit += exec_grep(argc, argv);
+               argc = nr;
+       }
+       if (argc > nr)
+               hit += exec_grep(argc, argv);
+       return 0;
+}
+
+static int grep_cache(struct grep_opt *opt, const char **paths, int cached)
+{
+       int hit = 0;
+       int nr;
+       read_cache();
+
+#ifdef __unix__
+       /*
+        * Use the external "grep" command for the case where
+        * we grep through the checked-out files. It tends to
+        * be a lot more optimized
+        */
+       if (!cached) {
+               hit = external_grep(opt, paths, cached);
+               if (hit >= 0)
+                       return hit;
+       }
+#endif
+
+       for (nr = 0; nr < active_nr; nr++) {
+               struct cache_entry *ce = active_cache[nr];
+               if (ce_stage(ce) || !S_ISREG(ntohl(ce->ce_mode)))
+                       continue;
+               if (!pathspec_matches(paths, ce->name))
+                       continue;
+               if (cached)
+                       hit |= grep_sha1(opt, ce->sha1, ce->name);
+               else
+                       hit |= grep_file(opt, ce->name);
+       }
+       return hit;
+}
+
+static int grep_tree(struct grep_opt *opt, const char **paths,
+                    struct tree_desc *tree,
+                    const char *tree_name, const char *base)
+{
+       unsigned mode;
+       int len;
+       int hit = 0;
+       const char *path;
+       const unsigned char *sha1;
+       char *down;
+       char *path_buf = xmalloc(PATH_MAX + strlen(tree_name) + 100);
+
+       if (tree_name[0]) {
+               int offset = sprintf(path_buf, "%s:", tree_name);
+               down = path_buf + offset;
+               strcat(down, base);
+       }
+       else {
+               down = path_buf;
+               strcpy(down, base);
+       }
+       len = strlen(path_buf);
+
+       while (tree->size) {
+               int pathlen;
+               sha1 = tree_entry_extract(tree, &path, &mode);
+               pathlen = strlen(path);
+               strcpy(path_buf + len, path);
+
+               if (S_ISDIR(mode))
+                       /* Match "abc/" against pathspec to
+                        * decide if we want to descend into "abc"
+                        * directory.
+                        */
+                       strcpy(path_buf + len + pathlen, "/");
+
+               if (!pathspec_matches(paths, down))
+                       ;
+               else if (S_ISREG(mode))
+                       hit |= grep_sha1(opt, sha1, path_buf);
+               else if (S_ISDIR(mode)) {
+                       char type[20];
+                       struct tree_desc sub;
+                       void *data;
+                       data = read_sha1_file(sha1, type, &sub.size);
+                       if (!data)
+                               die("unable to read tree (%s)",
+                                   sha1_to_hex(sha1));
+                       sub.buf = data;
+                       hit |= grep_tree(opt, paths, &sub, tree_name, down);
+                       free(data);
+               }
+               update_tree_entry(tree);
+       }
+       return hit;
+}
+
+static int grep_object(struct grep_opt *opt, const char **paths,
+                      struct object *obj, const char *name)
+{
+       if (!strcmp(obj->type, blob_type))
+               return grep_sha1(opt, obj->sha1, name);
+       if (!strcmp(obj->type, commit_type) ||
+           !strcmp(obj->type, tree_type)) {
+               struct tree_desc tree;
+               void *data;
+               int hit;
+               data = read_object_with_reference(obj->sha1, tree_type,
+                                                 &tree.size, NULL);
+               if (!data)
+                       die("unable to read tree (%s)", sha1_to_hex(obj->sha1));
+               tree.buf = data;
+               hit = grep_tree(opt, paths, &tree, name, "");
+               free(data);
+               return hit;
+       }
+       die("unable to grep from object of type %s", obj->type);
+}
+
+static const char builtin_grep_usage[] =
+"git-grep <option>* <rev>* [-e] <pattern> [<path>...]";
+
+int cmd_grep(int argc, const char **argv, char **envp)
+{
+       int hit = 0;
+       int cached = 0;
+       int seen_dashdash = 0;
+       struct grep_opt opt;
+       struct object_list *list, **tail, *object_list = NULL;
+       const char *prefix = setup_git_directory();
+       const char **paths = NULL;
+       int i;
+
+       memset(&opt, 0, sizeof(opt));
+       opt.pattern_tail = &opt.pattern_list;
+       opt.regflags = REG_NEWLINE;
+
+       /*
+        * If there is no -- then the paths must exist in the working
+        * tree.  If there is no explicit pattern specified with -e or
+        * -f, we take the first unrecognized non option to be the
+        * pattern, but then what follows it must be zero or more
+        * valid refs up to the -- (if exists), and then existing
+        * paths.  If there is an explicit pattern, then the first
+        * unrecocnized non option is the beginning of the refs list
+        * that continues up to the -- (if exists), and then paths.
+        */
+
+       tail = &object_list;
+       while (1 < argc) {
+               const char *arg = argv[1];
+               argc--; argv++;
+               if (!strcmp("--cached", arg)) {
+                       cached = 1;
+                       continue;
+               }
+               if (!strcmp("-a", arg) ||
+                   !strcmp("--text", arg)) {
+                       opt.binary = GREP_BINARY_TEXT;
+                       continue;
+               }
+               if (!strcmp("-i", arg) ||
+                   !strcmp("--ignore-case", arg)) {
+                       opt.regflags |= REG_ICASE;
+                       continue;
+               }
+               if (!strcmp("-I", arg)) {
+                       opt.binary = GREP_BINARY_NOMATCH;
+                       continue;
+               }
+               if (!strcmp("-v", arg) ||
+                   !strcmp("--invert-match", arg)) {
+                       opt.invert = 1;
+                       continue;
+               }
+               if (!strcmp("-E", arg) ||
+                   !strcmp("--extended-regexp", arg)) {
+                       opt.regflags |= REG_EXTENDED;
+                       continue;
+               }
+               if (!strcmp("-F", arg) ||
+                   !strcmp("--fixed-strings", arg)) {
+                       opt.fixed = 1;
+                       continue;
+               }
+               if (!strcmp("-G", arg) ||
+                   !strcmp("--basic-regexp", arg)) {
+                       opt.regflags &= ~REG_EXTENDED;
+                       continue;
+               }
+               if (!strcmp("-n", arg)) {
+                       opt.linenum = 1;
+                       continue;
+               }
+               if (!strcmp("-H", arg)) {
+                       /* We always show the pathname, so this
+                        * is a noop.
+                        */
+                       continue;
+               }
+               if (!strcmp("-l", arg) ||
+                   !strcmp("--files-with-matches", arg)) {
+                       opt.name_only = 1;
+                       continue;
+               }
+               if (!strcmp("-L", arg) ||
+                   !strcmp("--files-without-match", arg)) {
+                       opt.unmatch_name_only = 1;
+                       continue;
+               }
+               if (!strcmp("-c", arg) ||
+                   !strcmp("--count", arg)) {
+                       opt.count = 1;
+                       continue;
+               }
+               if (!strcmp("-w", arg) ||
+                   !strcmp("--word-regexp", arg)) {
+                       opt.word_regexp = 1;
+                       continue;
+               }
+               if (!strncmp("-A", arg, 2) ||
+                   !strncmp("-B", arg, 2) ||
+                   !strncmp("-C", arg, 2) ||
+                   (arg[0] == '-' && '1' <= arg[1] && arg[1] <= '9')) {
+                       unsigned num;
+                       const char *scan;
+                       switch (arg[1]) {
+                       case 'A': case 'B': case 'C':
+                               if (!arg[2]) {
+                                       if (argc <= 1)
+                                               usage(builtin_grep_usage);
+                                       scan = *++argv;
+                                       argc--;
+                               }
+                               else
+                                       scan = arg + 2;
+                               break;
+                       default:
+                               scan = arg + 1;
+                               break;
+                       }
+                       if (sscanf(scan, "%u", &num) != 1)
+                               usage(builtin_grep_usage);
+                       switch (arg[1]) {
+                       case 'A':
+                               opt.post_context = num;
+                               break;
+                       default:
+                       case 'C':
+                               opt.post_context = num;
+                       case 'B':
+                               opt.pre_context = num;
+                               break;
+                       }
+                       continue;
+               }
+               if (!strcmp("-f", arg)) {
+                       FILE *patterns;
+                       int lno = 0;
+                       char buf[1024];
+                       if (argc <= 1)
+                               usage(builtin_grep_usage);
+                       patterns = fopen(argv[1], "r");
+                       if (!patterns)
+                               die("'%s': %s", argv[1], strerror(errno));
+                       while (fgets(buf, sizeof(buf), patterns)) {
+                               int len = strlen(buf);
+                               if (buf[len-1] == '\n')
+                                       buf[len-1] = 0;
+                               /* ignore empty line like grep does */
+                               if (!buf[0])
+                                       continue;
+                               add_pattern(&opt, strdup(buf), argv[1], ++lno);
+                       }
+                       fclose(patterns);
+                       argv++;
+                       argc--;
+                       continue;
+               }
+               if (!strcmp("-e", arg)) {
+                       if (1 < argc) {
+                               add_pattern(&opt, argv[1], "-e option", 0);
+                               argv++;
+                               argc--;
+                               continue;
+                       }
+                       usage(builtin_grep_usage);
+               }
+               if (!strcmp("--", arg))
+                       break;
+               if (*arg == '-')
+                       usage(builtin_grep_usage);
+
+               /* First unrecognized non-option token */
+               if (!opt.pattern_list) {
+                       add_pattern(&opt, arg, "command line", 0);
+                       break;
+               }
+               else {
+                       /* We are looking at the first path or rev;
+                        * it is found at argv[1] after leaving the
+                        * loop.
+                        */
+                       argc++; argv--;
+                       break;
+               }
+       }
+
+       if (!opt.pattern_list)
+               die("no pattern given.");
+       if ((opt.regflags != REG_NEWLINE) && opt.fixed)
+               die("cannot mix --fixed-strings and regexp");
+       if (!opt.fixed)
+               compile_patterns(&opt);
+
+       /* Check revs and then paths */
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+               unsigned char sha1[20];
+               /* Is it a rev? */
+               if (!get_sha1(arg, sha1)) {
+                       struct object *object = parse_object(sha1);
+                       struct object_list *elem;
+                       if (!object)
+                               die("bad object %s", arg);
+                       elem = object_list_insert(object, tail);
+                       elem->name = arg;
+                       tail = &elem->next;
+                       continue;
+               }
+               if (!strcmp(arg, "--")) {
+                       i++;
+                       seen_dashdash = 1;
+               }
+               break;
+       }
+
+       /* The rest are paths */
+       if (!seen_dashdash) {
+               int j;
+               for (j = i; j < argc; j++)
+                       verify_filename(prefix, argv[j]);
+       }
+
+       if (i < argc)
+               paths = get_pathspec(prefix, argv + i);
+       else if (prefix) {
+               paths = xcalloc(2, sizeof(const char *));
+               paths[0] = prefix;
+               paths[1] = NULL;
+       }
+
+       if (!object_list)
+               return !grep_cache(&opt, paths, cached);
+
+       if (cached)
+               die("both --cached and trees are given.");
+
+       for (list = object_list; list; list = list->next) {
+               struct object *real_obj;
+               real_obj = deref_tag(list->item, NULL, 0);
+               if (grep_object(&opt, paths, real_obj, list->name))
+                       hit = 1;
+       }
+       return !hit;
+}
index 69f2911..d5bbc1c 100644 (file)
@@ -9,6 +9,10 @@
 #include "diff.h"
 #include "revision.h"
 #include "log-tree.h"
+#include "builtin.h"
+
+/* this is in builtin-diff.c */
+void add_head(struct rev_info *revs);
 
 static int cmd_log_wc(int argc, const char **argv, char **envp,
                      struct rev_info *rev)
@@ -67,3 +71,160 @@ int cmd_log(int argc, const char **argv, char **envp)
        rev.diffopt.recursive = 1;
        return cmd_log_wc(argc, argv, envp, &rev);
 }
+
+static int istitlechar(char c)
+{
+       return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
+               (c >= '0' && c <= '9') || c == '.' || c == '_';
+}
+
+static FILE *realstdout = NULL;
+static char *output_directory = NULL;
+
+static void reopen_stdout(struct commit *commit, int nr, int keep_subject)
+{
+       char filename[1024];
+       char *sol;
+       int len = 0;
+
+       if (output_directory) {
+               strncpy(filename, output_directory, 1010);
+               len = strlen(filename);
+               if (filename[len - 1] != '/')
+                       filename[len++] = '/';
+       }
+
+       sprintf(filename + len, "%04d", nr);
+       len = strlen(filename);
+
+       sol = strstr(commit->buffer, "\n\n");
+       if (sol) {
+               int j, space = 1;
+
+               sol += 2;
+               /* strip [PATCH] or [PATCH blabla] */
+               if (!keep_subject && !strncmp(sol, "[PATCH", 6)) {
+                       char *eos = strchr(sol + 6, ']');
+                       if (eos) {
+                               while (isspace(*eos))
+                                       eos++;
+                               sol = eos;
+                       }
+               }
+
+               for (j = 0; len < 1024 - 6 && sol[j] && sol[j] != '\n'; j++) {
+                       if (istitlechar(sol[j])) {
+                               if (space) {
+                                       filename[len++] = '-';
+                                       space = 0;
+                               }
+                               filename[len++] = sol[j];
+                               if (sol[j] == '.')
+                                       while (sol[j + 1] == '.')
+                                               j++;
+                       } else
+                               space = 1;
+               }
+               while (filename[len - 1] == '.' || filename[len - 1] == '-')
+                       len--;
+       }
+       strcpy(filename + len, ".txt");
+       fprintf(realstdout, "%s\n", filename);
+       freopen(filename, "w", stdout);
+}
+
+int cmd_format_patch(int argc, const char **argv, char **envp)
+{
+       struct commit *commit;
+       struct commit **list = NULL;
+       struct rev_info rev;
+       int nr = 0, total, i, j;
+       int use_stdout = 0;
+       int numbered = 0;
+       int keep_subject = 0;
+
+       init_revisions(&rev);
+       rev.commit_format = CMIT_FMT_EMAIL;
+       rev.verbose_header = 1;
+       rev.diff = 1;
+       rev.diffopt.with_raw = 0;
+       rev.diffopt.with_stat = 1;
+       rev.combine_merges = 0;
+       rev.ignore_merges = 1;
+       rev.diffopt.output_format = DIFF_FORMAT_PATCH;
+
+       /*
+        * Parse the arguments before setup_revisions(), or something
+        * like "git fmt-patch -o a123 HEAD^.." may fail; a123 is
+        * possibly a valid SHA1.
+        */
+       for (i = 1, j = 1; i < argc; i++) {
+               if (!strcmp(argv[i], "--stdout"))
+                       use_stdout = 1;
+               else if (!strcmp(argv[i], "-n") ||
+                               !strcmp(argv[i], "--numbered"))
+                       numbered = 1;
+               else if (!strcmp(argv[i], "-k") ||
+                               !strcmp(argv[i], "--keep-subject")) {
+                       keep_subject = 1;
+                       rev.total = -1;
+               } else if (!strcmp(argv[i], "-o")) {
+                       if (argc < 3)
+                               die ("Which directory?");
+                       if (mkdir(argv[i + 1], 0777) < 0 && errno != EEXIST)
+                               die("Could not create directory %s",
+                                               argv[i + 1]);
+                       output_directory = strdup(argv[i + 1]);
+                       i++;
+               } else
+                       argv[j++] = argv[i];
+       }
+       argc = j;
+
+       if (numbered && keep_subject < 0)
+               die ("-n and -k are mutually exclusive.");
+
+       argc = setup_revisions(argc, argv, &rev, "HEAD");
+       if (argc > 1)
+               die ("unrecognized argument: %s", argv[1]);
+
+       if (rev.pending_objects && rev.pending_objects->next == NULL) {
+               rev.pending_objects->item->flags |= UNINTERESTING;
+               add_head(&rev);
+       }
+
+       if (!use_stdout)
+               realstdout = fdopen(dup(1), "w");
+
+       prepare_revision_walk(&rev);
+       while ((commit = get_revision(&rev)) != NULL) {
+               /* ignore merges */
+               if (commit->parents && commit->parents->next)
+                       continue;
+               nr++;
+               list = realloc(list, nr * sizeof(list[0]));
+               list[nr - 1] = commit;
+       }
+       total = nr;
+       if (numbered)
+               rev.total = total;
+       while (0 <= --nr) {
+               int shown;
+               commit = list[nr];
+               rev.nr = total - nr;
+               if (!use_stdout)
+                       reopen_stdout(commit, rev.nr, keep_subject);
+               shown = log_tree_commit(&rev, commit);
+               free(commit->buffer);
+               commit->buffer = NULL;
+               if (shown)
+                       printf("-- \n%s\n\n", git_version_string);
+               if (!use_stdout)
+                       fclose(stdout);
+       }
+       if (output_directory)
+               free(output_directory);
+       free(list);
+       return 0;
+}
+
index bb63f07..ccd0e31 100644 (file)
--- a/builtin.h
+++ b/builtin.h
@@ -20,8 +20,11 @@ extern int cmd_whatchanged(int argc, const char **argv, char **envp);
 extern int cmd_show(int argc, const char **argv, char **envp);
 extern int cmd_log(int argc, const char **argv, char **envp);
 extern int cmd_diff(int argc, const char **argv, char **envp);
+extern int cmd_format_patch(int argc, const char **argv, char **envp);
 extern int cmd_count_objects(int argc, const char **argv, char **envp);
 
 extern int cmd_push(int argc, const char **argv, char **envp);
+extern int cmd_grep(int argc, const char **argv, char **envp);
+extern int cmd_add(int argc, const char **argv, char **envp);
 
 #endif
diff --git a/cache-tree.c b/cache-tree.c
new file mode 100644 (file)
index 0000000..d9f7e1e
--- /dev/null
@@ -0,0 +1,557 @@
+#include "cache.h"
+#include "tree.h"
+#include "cache-tree.h"
+
+#define DEBUG 0
+
+struct cache_tree *cache_tree(void)
+{
+       struct cache_tree *it = xcalloc(1, sizeof(struct cache_tree));
+       it->entry_count = -1;
+       return it;
+}
+
+void cache_tree_free(struct cache_tree **it_p)
+{
+       int i;
+       struct cache_tree *it = *it_p;
+
+       if (!it)
+               return;
+       for (i = 0; i < it->subtree_nr; i++)
+               if (it->down[i])
+                       cache_tree_free(&it->down[i]->cache_tree);
+       free(it->down);
+       free(it);
+       *it_p = NULL;
+}
+
+static int subtree_name_cmp(const char *one, int onelen,
+                           const char *two, int twolen)
+{
+       if (onelen < twolen)
+               return -1;
+       if (twolen < onelen)
+               return 1;
+       return memcmp(one, two, onelen);
+}
+
+static int subtree_pos(struct cache_tree *it, const char *path, int pathlen)
+{
+       struct cache_tree_sub **down = it->down;
+       int lo, hi;
+       lo = 0;
+       hi = it->subtree_nr;
+       while (lo < hi) {
+               int mi = (lo + hi) / 2;
+               struct cache_tree_sub *mdl = down[mi];
+               int cmp = subtree_name_cmp(path, pathlen,
+                                          mdl->name, mdl->namelen);
+               if (!cmp)
+                       return mi;
+               if (cmp < 0)
+                       hi = mi;
+               else
+                       lo = mi + 1;
+       }
+       return -lo-1;
+}
+
+static struct cache_tree_sub *find_subtree(struct cache_tree *it,
+                                          const char *path,
+                                          int pathlen,
+                                          int create)
+{
+       struct cache_tree_sub *down;
+       int pos = subtree_pos(it, path, pathlen);
+       if (0 <= pos)
+               return it->down[pos];
+       if (!create)
+               return NULL;
+
+       pos = -pos-1;
+       if (it->subtree_alloc <= it->subtree_nr) {
+               it->subtree_alloc = alloc_nr(it->subtree_alloc);
+               it->down = xrealloc(it->down, it->subtree_alloc *
+                                   sizeof(*it->down));
+       }
+       it->subtree_nr++;
+
+       down = xmalloc(sizeof(*down) + pathlen + 1);
+       down->cache_tree = NULL;
+       down->namelen = pathlen;
+       memcpy(down->name, path, pathlen);
+       down->name[pathlen] = 0;
+
+       if (pos < it->subtree_nr)
+               memmove(it->down + pos + 1,
+                       it->down + pos,
+                       sizeof(down) * (it->subtree_nr - pos - 1));
+       it->down[pos] = down;
+       return down;
+}
+
+struct cache_tree_sub *cache_tree_sub(struct cache_tree *it, const char *path)
+{
+       int pathlen = strlen(path);
+       return find_subtree(it, path, pathlen, 1);
+}
+
+void cache_tree_invalidate_path(struct cache_tree *it, const char *path)
+{
+       /* a/b/c
+        * ==> invalidate self
+        * ==> find "a", have it invalidate "b/c"
+        * a
+        * ==> invalidate self
+        * ==> if "a" exists as a subtree, remove it.
+        */
+       const char *slash;
+       int namelen;
+       struct cache_tree_sub *down;
+
+#if DEBUG
+       fprintf(stderr, "cache-tree invalidate <%s>\n", path);
+#endif
+
+       if (!it)
+               return;
+       slash = strchr(path, '/');
+       it->entry_count = -1;
+       if (!slash) {
+               int pos;
+               namelen = strlen(path);
+               pos = subtree_pos(it, path, namelen);
+               if (0 <= pos) {
+                       cache_tree_free(&it->down[pos]->cache_tree);
+                       free(it->down[pos]);
+                       /* 0 1 2 3 4 5
+                        *       ^     ^subtree_nr = 6
+                        *       pos
+                        * move 4 and 5 up one place (2 entries)
+                        * 2 = 6 - 3 - 1 = subtree_nr - pos - 1
+                        */
+                       memmove(it->down+pos, it->down+pos+1,
+                               sizeof(struct cache_tree_sub *) *
+                               (it->subtree_nr - pos - 1));
+                       it->subtree_nr--;
+               }
+               return;
+       }
+       namelen = slash - path;
+       down = find_subtree(it, path, namelen, 0);
+       if (down)
+               cache_tree_invalidate_path(down->cache_tree, slash + 1);
+}
+
+static int verify_cache(struct cache_entry **cache,
+                       int entries)
+{
+       int i, funny;
+
+       /* Verify that the tree is merged */
+       funny = 0;
+       for (i = 0; i < entries; i++) {
+               struct cache_entry *ce = cache[i];
+               if (ce_stage(ce)) {
+                       if (10 < ++funny) {
+                               fprintf(stderr, "...\n");
+                               break;
+                       }
+                       fprintf(stderr, "%s: unmerged (%s)\n",
+                               ce->name, sha1_to_hex(ce->sha1));
+               }
+       }
+       if (funny)
+               return -1;
+
+       /* Also verify that the cache does not have path and path/file
+        * at the same time.  At this point we know the cache has only
+        * stage 0 entries.
+        */
+       funny = 0;
+       for (i = 0; i < entries - 1; i++) {
+               /* path/file always comes after path because of the way
+                * the cache is sorted.  Also path can appear only once,
+                * which means conflicting one would immediately follow.
+                */
+               const char *this_name = cache[i]->name;
+               const char *next_name = cache[i+1]->name;
+               int this_len = strlen(this_name);
+               if (this_len < strlen(next_name) &&
+                   strncmp(this_name, next_name, this_len) == 0 &&
+                   next_name[this_len] == '/') {
+                       if (10 < ++funny) {
+                               fprintf(stderr, "...\n");
+                               break;
+                       }
+                       fprintf(stderr, "You have both %s and %s\n",
+                               this_name, next_name);
+               }
+       }
+       if (funny)
+               return -1;
+       return 0;
+}
+
+static void discard_unused_subtrees(struct cache_tree *it)
+{
+       struct cache_tree_sub **down = it->down;
+       int nr = it->subtree_nr;
+       int dst, src;
+       for (dst = src = 0; src < nr; src++) {
+               struct cache_tree_sub *s = down[src];
+               if (s->used)
+                       down[dst++] = s;
+               else {
+                       cache_tree_free(&s->cache_tree);
+                       free(s);
+                       it->subtree_nr--;
+               }
+       }
+}
+
+int cache_tree_fully_valid(struct cache_tree *it)
+{
+       int i;
+       if (!it)
+               return 0;
+       if (it->entry_count < 0 || !has_sha1_file(it->sha1))
+               return 0;
+       for (i = 0; i < it->subtree_nr; i++) {
+               if (!cache_tree_fully_valid(it->down[i]->cache_tree))
+                       return 0;
+       }
+       return 1;
+}
+
+static int update_one(struct cache_tree *it,
+                     struct cache_entry **cache,
+                     int entries,
+                     const char *base,
+                     int baselen,
+                     int missing_ok,
+                     int dryrun)
+{
+       unsigned long size, offset;
+       char *buffer;
+       int i;
+
+       if (0 <= it->entry_count && has_sha1_file(it->sha1))
+               return it->entry_count;
+
+       /*
+        * We first scan for subtrees and update them; we start by
+        * marking existing subtrees -- the ones that are unmarked
+        * should not be in the result.
+        */
+       for (i = 0; i < it->subtree_nr; i++)
+               it->down[i]->used = 0;
+
+       /*
+        * Find the subtrees and update them.
+        */
+       for (i = 0; i < entries; i++) {
+               struct cache_entry *ce = cache[i];
+               struct cache_tree_sub *sub;
+               const char *path, *slash;
+               int pathlen, sublen, subcnt;
+
+               path = ce->name;
+               pathlen = ce_namelen(ce);
+               if (pathlen <= baselen || memcmp(base, path, baselen))
+                       break; /* at the end of this level */
+
+               slash = strchr(path + baselen, '/');
+               if (!slash)
+                       continue;
+               /*
+                * a/bbb/c (base = a/, slash = /c)
+                * ==>
+                * path+baselen = bbb/c, sublen = 3
+                */
+               sublen = slash - (path + baselen);
+               sub = find_subtree(it, path + baselen, sublen, 1);
+               if (!sub->cache_tree)
+                       sub->cache_tree = cache_tree();
+               subcnt = update_one(sub->cache_tree,
+                                   cache + i, entries - i,
+                                   path,
+                                   baselen + sublen + 1,
+                                   missing_ok,
+                                   dryrun);
+               i += subcnt - 1;
+               sub->used = 1;
+       }
+
+       discard_unused_subtrees(it);
+
+       /*
+        * Then write out the tree object for this level.
+        */
+       size = 8192;
+       buffer = xmalloc(size);
+       offset = 0;
+
+       for (i = 0; i < entries; i++) {
+               struct cache_entry *ce = cache[i];
+               struct cache_tree_sub *sub;
+               const char *path, *slash;
+               int pathlen, entlen;
+               const unsigned char *sha1;
+               unsigned mode;
+
+               path = ce->name;
+               pathlen = ce_namelen(ce);
+               if (pathlen <= baselen || memcmp(base, path, baselen))
+                       break; /* at the end of this level */
+
+               slash = strchr(path + baselen, '/');
+               if (slash) {
+                       entlen = slash - (path + baselen);
+                       sub = find_subtree(it, path + baselen, entlen, 0);
+                       if (!sub)
+                               die("cache-tree.c: '%.*s' in '%s' not found",
+                                   entlen, path + baselen, path);
+                       i += sub->cache_tree->entry_count - 1;
+                       sha1 = sub->cache_tree->sha1;
+                       mode = S_IFDIR;
+               }
+               else {
+                       sha1 = ce->sha1;
+                       mode = ntohl(ce->ce_mode);
+                       entlen = pathlen - baselen;
+               }
+               if (!missing_ok && !has_sha1_file(sha1))
+                       return error("invalid object %s", sha1_to_hex(sha1));
+
+               if (!ce->ce_mode)
+                       continue; /* entry being removed */
+
+               if (size < offset + entlen + 100) {
+                       size = alloc_nr(offset + entlen + 100);
+                       buffer = xrealloc(buffer, size);
+               }
+               offset += sprintf(buffer + offset,
+                                 "%o %.*s", mode, entlen, path + baselen);
+               buffer[offset++] = 0;
+               memcpy(buffer + offset, sha1, 20);
+               offset += 20;
+
+#if DEBUG
+               fprintf(stderr, "cache-tree update-one %o %.*s\n",
+                       mode, entlen, path + baselen);
+#endif
+       }
+
+       if (dryrun) {
+               unsigned char hdr[200];
+               int hdrlen;
+               write_sha1_file_prepare(buffer, offset, tree_type, it->sha1,
+                                       hdr, &hdrlen);
+       }
+       else
+               write_sha1_file(buffer, offset, tree_type, it->sha1);
+       free(buffer);
+       it->entry_count = i;
+#if DEBUG
+       fprintf(stderr, "cache-tree update-one (%d ent, %d subtree) %s\n",
+               it->entry_count, it->subtree_nr,
+               sha1_to_hex(it->sha1));
+#endif
+       return i;
+}
+
+int cache_tree_update(struct cache_tree *it,
+                     struct cache_entry **cache,
+                     int entries,
+                     int missing_ok,
+                     int dryrun)
+{
+       int i;
+       i = verify_cache(cache, entries);
+       if (i)
+               return i;
+       i = update_one(it, cache, entries, "", 0, missing_ok, dryrun);
+       if (i < 0)
+               return i;
+       return 0;
+}
+
+static void *write_one(struct cache_tree *it,
+                      char *path,
+                      int pathlen,
+                      char *buffer,
+                      unsigned long *size,
+                      unsigned long *offset)
+{
+       int i;
+
+       /* One "cache-tree" entry consists of the following:
+        * path (NUL terminated)
+        * entry_count, subtree_nr ("%d %d\n")
+        * tree-sha1 (missing if invalid)
+        * subtree_nr "cache-tree" entries for subtrees.
+        */
+       if (*size < *offset + pathlen + 100) {
+               *size = alloc_nr(*offset + pathlen + 100);
+               buffer = xrealloc(buffer, *size);
+       }
+       *offset += sprintf(buffer + *offset, "%.*s%c%d %d\n",
+                          pathlen, path, 0,
+                          it->entry_count, it->subtree_nr);
+
+#if DEBUG
+       if (0 <= it->entry_count)
+               fprintf(stderr, "cache-tree <%.*s> (%d ent, %d subtree) %s\n",
+                       pathlen, path, it->entry_count, it->subtree_nr,
+                       sha1_to_hex(it->sha1));
+       else
+               fprintf(stderr, "cache-tree <%.*s> (%d subtree) invalid\n",
+                       pathlen, path, it->subtree_nr);
+#endif
+
+       if (0 <= it->entry_count) {
+               memcpy(buffer + *offset, it->sha1, 20);
+               *offset += 20;
+       }
+       for (i = 0; i < it->subtree_nr; i++) {
+               struct cache_tree_sub *down = it->down[i];
+               if (i) {
+                       struct cache_tree_sub *prev = it->down[i-1];
+                       if (subtree_name_cmp(down->name, down->namelen,
+                                            prev->name, prev->namelen) <= 0)
+                               die("fatal - unsorted cache subtree");
+               }
+               buffer = write_one(down->cache_tree, down->name, down->namelen,
+                                  buffer, size, offset);
+       }
+       return buffer;
+}
+
+void *cache_tree_write(struct cache_tree *root, unsigned long *size_p)
+{
+       char path[PATH_MAX];
+       unsigned long size = 8192;
+       char *buffer = xmalloc(size);
+
+       *size_p = 0;
+       path[0] = 0;
+       return write_one(root, path, 0, buffer, &size, size_p);
+}
+
+static struct cache_tree *read_one(const char **buffer, unsigned long *size_p)
+{
+       const char *buf = *buffer;
+       unsigned long size = *size_p;
+       const char *cp;
+       char *ep;
+       struct cache_tree *it;
+       int i, subtree_nr;
+
+       it = NULL;
+       /* skip name, but make sure name exists */
+       while (size && *buf) {
+               size--;
+               buf++;
+       }
+       if (!size)
+               goto free_return;
+       buf++; size--;
+       it = cache_tree();
+
+       cp = buf;
+       it->entry_count = strtol(cp, &ep, 10);
+       if (cp == ep)
+               goto free_return;
+       cp = ep;
+       subtree_nr = strtol(cp, &ep, 10);
+       if (cp == ep)
+               goto free_return;
+       while (size && *buf && *buf != '\n') {
+               size--;
+               buf++;
+       }
+       if (!size)
+               goto free_return;
+       buf++; size--;
+       if (0 <= it->entry_count) {
+               if (size < 20)
+                       goto free_return;
+               memcpy(it->sha1, buf, 20);
+               buf += 20;
+               size -= 20;
+       }
+
+#if DEBUG
+       if (0 <= it->entry_count)
+               fprintf(stderr, "cache-tree <%s> (%d ent, %d subtree) %s\n",
+                       *buffer, it->entry_count, subtree_nr,
+                       sha1_to_hex(it->sha1));
+       else
+               fprintf(stderr, "cache-tree <%s> (%d subtrees) invalid\n",
+                       *buffer, subtree_nr);
+#endif
+
+       /*
+        * Just a heuristic -- we do not add directories that often but
+        * we do not want to have to extend it immediately when we do,
+        * hence +2.
+        */
+       it->subtree_alloc = subtree_nr + 2;
+       it->down = xcalloc(it->subtree_alloc, sizeof(struct cache_tree_sub *));
+       for (i = 0; i < subtree_nr; i++) {
+               /* read each subtree */
+               struct cache_tree *sub;
+               struct cache_tree_sub *subtree;
+               const char *name = buf;
+
+               sub = read_one(&buf, &size);
+               if (!sub)
+                       goto free_return;
+               subtree = cache_tree_sub(it, name);
+               subtree->cache_tree = sub;
+       }
+       if (subtree_nr != it->subtree_nr)
+               die("cache-tree: internal error");
+       *buffer = buf;
+       *size_p = size;
+       return it;
+
+ free_return:
+       cache_tree_free(&it);
+       return NULL;
+}
+
+struct cache_tree *cache_tree_read(const char *buffer, unsigned long size)
+{
+       if (buffer[0])
+               return NULL; /* not the whole tree */
+       return read_one(&buffer, &size);
+}
+
+struct cache_tree *cache_tree_find(struct cache_tree *it, const char *path)
+{
+       while (*path) {
+               const char *slash;
+               struct cache_tree_sub *sub;
+
+               slash = strchr(path, '/');
+               if (!slash)
+                       slash = path + strlen(path);
+               /* between path and slash is the name of the
+                * subtree to look for.
+                */
+               sub = find_subtree(it, path, slash - path, 0);
+               if (!sub)
+                       return NULL;
+               it = sub->cache_tree;
+               if (slash)
+                       while (*slash && *slash == '/')
+                               slash++;
+               if (!slash || !*slash)
+                       return it; /* prefix ended with slashes */
+               path = slash;
+       }
+       return it;
+}
diff --git a/cache-tree.h b/cache-tree.h
new file mode 100644 (file)
index 0000000..119407e
--- /dev/null
@@ -0,0 +1,33 @@
+#ifndef CACHE_TREE_H
+#define CACHE_TREE_H
+
+struct cache_tree;
+struct cache_tree_sub {
+       struct cache_tree *cache_tree;
+       int namelen;
+       int used;
+       char name[FLEX_ARRAY];
+};
+
+struct cache_tree {
+       int entry_count; /* negative means "invalid" */
+       unsigned char sha1[20];
+       int subtree_nr;
+       int subtree_alloc;
+       struct cache_tree_sub **down;
+};
+
+struct cache_tree *cache_tree(void);
+void cache_tree_free(struct cache_tree **);
+void cache_tree_invalidate_path(struct cache_tree *, const char *);
+struct cache_tree_sub *cache_tree_sub(struct cache_tree *, const char *);
+
+void *cache_tree_write(struct cache_tree *root, unsigned long *size_p);
+struct cache_tree *cache_tree_read(const char *buffer, unsigned long size);
+
+int cache_tree_fully_valid(struct cache_tree *);
+int cache_tree_update(struct cache_tree *, struct cache_entry **, int, int, int);
+
+struct cache_tree *cache_tree_find(struct cache_tree *, const char *);
+
+#endif
diff --git a/cache.h b/cache.h
index 4b7a439..b1300cd 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -114,6 +114,7 @@ static inline unsigned int create_ce_mode(unsigned int mode)
 
 extern struct cache_entry **active_cache;
 extern unsigned int active_nr, active_alloc, active_cache_changed;
+extern struct cache_tree *active_cache_tree;
 
 #define GIT_DIR_ENVIRONMENT "GIT_DIR"
 #define DEFAULT_GIT_DIR_ENVIRONMENT ".git"
@@ -251,6 +252,7 @@ extern void *read_object_with_reference(const unsigned char *sha1,
                                        unsigned char *sha1_ret);
 
 const char *show_date(unsigned long time, int timezone);
+const char *show_rfc2822_date(unsigned long time, int timezone);
 int parse_date(const char *date, char *buf, int bufsize);
 void datestamp(char *buf, int bufsize);
 unsigned long approxidate(const char *);
index cc3a745..9876af6 100644 (file)
@@ -39,6 +39,7 @@
 #include "cache.h"
 #include "strbuf.h"
 #include "quote.h"
+#include "cache-tree.h"
 
 #define CHECKOUT_ALL 4
 static const char *prefix;
index 8a8fe38..64b20cc 100644 (file)
@@ -608,6 +608,7 @@ static int show_patch_diff(struct combine_diff_path *elem, int num_parent,
        int abbrev = opt->full_index ? 40 : DEFAULT_ABBREV;
        mmfile_t result_file;
 
+       context = opt->context;
        /* Read the result of merge first */
        if (!working_tree_file)
                result = grab_blob(elem->sha1, &result_size);
index 2717dd8..84558ba 100644 (file)
--- a/commit.c
+++ b/commit.c
@@ -22,23 +22,34 @@ struct sort_node
 
 const char *commit_type = "commit";
 
+struct cmt_fmt_map {
+       const char *n;
+       size_t cmp_len;
+       enum cmit_fmt v;
+} cmt_fmts[] = {
+       { "raw",        1,      CMIT_FMT_RAW },
+       { "medium",     1,      CMIT_FMT_MEDIUM },
+       { "short",      1,      CMIT_FMT_SHORT },
+       { "email",      1,      CMIT_FMT_EMAIL },
+       { "full",       5,      CMIT_FMT_FULL },
+       { "fuller",     5,      CMIT_FMT_FULLER },
+       { "oneline",    1,      CMIT_FMT_ONELINE },
+};
+
 enum cmit_fmt get_commit_format(const char *arg)
 {
-       if (!*arg)
+       int i;
+
+       if (!arg || !*arg)
                return CMIT_FMT_DEFAULT;
-       if (!strcmp(arg, "=raw"))
-               return CMIT_FMT_RAW;
-       if (!strcmp(arg, "=medium"))
-               return CMIT_FMT_MEDIUM;
-       if (!strcmp(arg, "=short"))
-               return CMIT_FMT_SHORT;
-       if (!strcmp(arg, "=full"))
-               return CMIT_FMT_FULL;
-       if (!strcmp(arg, "=fuller"))
-               return CMIT_FMT_FULLER;
-       if (!strcmp(arg, "=oneline"))
-               return CMIT_FMT_ONELINE;
-       die("invalid --pretty format");
+       if (*arg == '=')
+               arg++;
+       for (i = 0; i < ARRAY_SIZE(cmt_fmts); i++) {
+               if (!strncmp(arg, cmt_fmts[i].n, cmt_fmts[i].cmp_len))
+                       return cmt_fmts[i].v;
+       }
+
+       die("invalid --pretty format: %s", arg);
 }
 
 static struct commit *check_commit(struct object *obj,
@@ -428,6 +439,10 @@ static int add_user_info(const char *what, enum cmit_fmt fmt, char *buf, const c
        time = strtoul(date, &date, 10);
        tz = strtol(date, NULL, 10);
 
+       if (fmt == CMIT_FMT_EMAIL) {
+               what = "From";
+               filler = "";
+       }
        ret = sprintf(buf, "%s: %.*s%.*s\n", what,
                      (fmt == CMIT_FMT_FULLER) ? 4 : 0,
                      filler, namelen, line);
@@ -435,6 +450,10 @@ static int add_user_info(const char *what, enum cmit_fmt fmt, char *buf, const c
        case CMIT_FMT_MEDIUM:
                ret += sprintf(buf + ret, "Date:   %s\n", show_date(time, tz));
                break;
+       case CMIT_FMT_EMAIL:
+               ret += sprintf(buf + ret, "Date: %s\n",
+                              show_rfc2822_date(time, tz));
+               break;
        case CMIT_FMT_FULLER:
                ret += sprintf(buf + ret, "%sDate: %s\n", what, show_date(time, tz));
                break;
@@ -445,10 +464,12 @@ static int add_user_info(const char *what, enum cmit_fmt fmt, char *buf, const c
        return ret;
 }
 
-static int is_empty_line(const char *line, int len)
+static int is_empty_line(const char *line, int *len_p)
 {
+       int len = *len_p;
        while (len && isspace(line[len-1]))
                len--;
+       *len_p = len;
        return !len;
 }
 
@@ -457,7 +478,8 @@ static int add_merge_info(enum cmit_fmt fmt, char *buf, const struct commit *com
        struct commit_list *parent = commit->parents;
        int offset;
 
-       if ((fmt == CMIT_FMT_ONELINE) || !parent || !parent->next)
+       if ((fmt == CMIT_FMT_ONELINE) || (fmt == CMIT_FMT_EMAIL) ||
+           !parent || !parent->next)
                return 0;
 
        offset = sprintf(buf, "Merge:");
@@ -476,14 +498,17 @@ static int add_merge_info(enum cmit_fmt fmt, char *buf, const struct commit *com
        return offset;
 }
 
-unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit, unsigned long len, char *buf, unsigned long space, int abbrev)
+unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit, unsigned long len, char *buf, unsigned long space, int abbrev, const char *subject)
 {
        int hdr = 1, body = 0;
        unsigned long offset = 0;
-       int indent = (fmt == CMIT_FMT_ONELINE) ? 0 : 4;
+       int indent = 4;
        int parents_shown = 0;
        const char *msg = commit->buffer;
 
+       if (fmt == CMIT_FMT_ONELINE || fmt == CMIT_FMT_EMAIL)
+               indent = 0;
+
        for (;;) {
                const char *line = msg;
                int linelen = get_one_line(msg, len);
@@ -506,7 +531,7 @@ unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit
                if (hdr) {
                        if (linelen == 1) {
                                hdr = 0;
-                               if (fmt != CMIT_FMT_ONELINE)
+                               if ((fmt != CMIT_FMT_ONELINE) && !subject)
                                        buf[offset++] = '\n';
                                continue;
                        }
@@ -544,20 +569,29 @@ unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit
                        continue;
                }
 
-               if (is_empty_line(line, linelen)) {
+               if (is_empty_line(line, &linelen)) {
                        if (!body)
                                continue;
+                       if (subject)
+                               continue;
                        if (fmt == CMIT_FMT_SHORT)
                                break;
                } else {
                        body = 1;
                }
 
+               if (subject) {
+                       int slen = strlen(subject);
+                       memcpy(buf + offset, subject, slen);
+                       offset += slen;
+               }
                memset(buf + offset, ' ', indent);
                memcpy(buf + offset + indent, line, linelen);
                offset += linelen + indent;
+               buf[offset++] = '\n';
                if (fmt == CMIT_FMT_ONELINE)
                        break;
+               subject = NULL;
        }
        while (offset && isspace(buf[offset-1]))
                offset--;
index de142af..8d7514c 100644 (file)
--- a/commit.h
+++ b/commit.h
@@ -45,12 +45,13 @@ enum cmit_fmt {
        CMIT_FMT_FULL,
        CMIT_FMT_FULLER,
        CMIT_FMT_ONELINE,
+       CMIT_FMT_EMAIL,
 
        CMIT_FMT_UNSPECIFIED,
 };
 
 extern enum cmit_fmt get_commit_format(const char *arg);
-extern unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *, unsigned long len, char *buf, unsigned long space, int abbrev);
+extern unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *, unsigned long len, char *buf, unsigned long space, int abbrev, const char *subject);
 
 /** Removes the first commit from a list sorted by date, and adds all
  * of its parents.
index 0f518c9..0248c6d 100644 (file)
--- a/config.c
+++ b/config.c
@@ -134,6 +134,41 @@ static int get_value(config_fn_t fn, char *name, unsigned int len)
        return fn(name, value);
 }
 
+static int get_extended_base_var(char *name, int baselen, int c)
+{
+       do {
+               if (c == '\n')
+                       return -1;
+               c = get_next_char();
+       } while (isspace(c));
+
+       /* We require the format to be '[base "extension"]' */
+       if (c != '"')
+               return -1;
+       name[baselen++] = '.';
+
+       for (;;) {
+               int c = get_next_char();
+               if (c == '\n')
+                       return -1;
+               if (c == '"')
+                       break;
+               if (c == '\\') {
+                       c = get_next_char();
+                       if (c == '\n')
+                               return -1;
+               }
+               name[baselen++] = c;
+               if (baselen > MAXNAME / 2)
+                       return -1;
+       }
+
+       /* Final ']' */
+       if (get_next_char() != ']')
+               return -1;
+       return baselen;
+}
+
 static int get_base_var(char *name)
 {
        int baselen = 0;
@@ -144,6 +179,8 @@ static int get_base_var(char *name)
                        return -1;
                if (c == ']')
                        return baselen;
+               if (isspace(c))
+                       return get_extended_base_var(name, baselen, c);
                if (!isalnum(c) && c != '.')
                        return -1;
                if (baselen > MAXNAME / 2)
@@ -335,10 +372,12 @@ static int store_aux(const char* key, const char* value)
                        store.offset[store.seen] = ftell(config_file);
                        store.state = KEY_SEEN;
                        store.seen++;
-               } else if (strrchr(key, '.') - key == store.baselen &&
+               } else {
+                       if (strrchr(key, '.') - key == store.baselen &&
                              !strncmp(key, store.key, store.baselen)) {
                                        store.state = SECTION_SEEN;
                                        store.offset[store.seen] = ftell(config_file);
+                       }
                }
        }
        return 0;
@@ -346,8 +385,30 @@ static int store_aux(const char* key, const char* value)
 
 static void store_write_section(int fd, const char* key)
 {
+       const char *dot = strchr(key, '.');
+       int len1 = store.baselen, len2 = -1;
+
+       dot = strchr(key, '.');
+       if (dot) {
+               int dotlen = dot - key;
+               if (dotlen < len1) {
+                       len2 = len1 - dotlen - 1;
+                       len1 = dotlen;
+               }
+       }
+
        write(fd, "[", 1);
-       write(fd, key, store.baselen);
+       write(fd, key, len1);
+       if (len2 >= 0) {
+               write(fd, " \"", 2);
+               while (--len2 >= 0) {
+                       unsigned char c = *++dot;
+                       if (c == '"')
+                               write(fd, "\\", 1);
+                       write(fd, &c, 1);
+               }
+               write(fd, "\"", 1);
+       }
        write(fd, "]\n", 2);
 }
 
@@ -421,7 +482,7 @@ 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;
+       int i, dot;
        int fd = -1, in_fd;
        int ret;
        char* config_filename = strdup(git_path("config"));
@@ -446,16 +507,23 @@ int git_config_set_multivar(const char* key, const char* value,
         * Validate the key and while at it, lower case it for matching.
         */
        store.key = (char*)malloc(strlen(key)+1);
-       for (i = 0; key[i]; i++)
-               if (i != store.baselen &&
-                               ((!isalnum(key[i]) && key[i] != '.') ||
-                                (i == store.baselen+1 && !isalpha(key[i])))) {
-                       fprintf(stderr, "invalid key: %s\n", key);
-                       free(store.key);
-                       ret = 1;
-                       goto out_free;
-               } else
-                       store.key[i] = tolower(key[i]);
+       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 (!isalnum(c) || (i == store.baselen+1 && !isalpha(c))) {
+                               fprintf(stderr, "invalid key: %s\n", key);
+                               free(store.key);
+                               ret = 1;
+                               goto out_free;
+                       }
+                       c = tolower(c);
+               }
+               store.key[i] = c;
+       }
        store.key[i] = 0;
 
        /*
diff --git a/contrib/remotes2config.sh b/contrib/remotes2config.sh
new file mode 100644 (file)
index 0000000..25901e2
--- /dev/null
@@ -0,0 +1,35 @@
+#!/bin/sh
+
+# Use this tool to rewrite your .git/remotes/ files into the config.
+
+. git-sh-setup
+
+if [ -d "$GIT_DIR"/remotes ]; then
+       echo "Rewriting $GIT_DIR/remotes" >&2
+       error=0
+       # rewrite into config
+       {
+               cd "$GIT_DIR"/remotes
+               ls | while read f; do
+                       name=$(echo -n "$f" | tr -c "A-Za-z0-9" ".")
+                       sed -n \
+                       -e "s/^URL: \(.*\)$/remote.$name.url \1 ./p" \
+                       -e "s/^Pull: \(.*\)$/remote.$name.fetch \1 ^$ /p" \
+                       -e "s/^Push: \(.*\)$/remote.$name.push \1 ^$ /p" \
+                       < "$f"
+               done
+               echo done
+       } | while read key value regex; do
+               case $key in
+               done)
+                       if [ $error = 0 ]; then
+                               mv "$GIT_DIR"/remotes "$GIT_DIR"/remotes.old
+                       fi ;;
+               *)
+                       echo "git-repo-config $key "$value" $regex"
+                       git-repo-config $key "$value" $regex || error=1 ;;
+               esac
+       done
+fi
+
+
diff --git a/date.c b/date.c
index 034d722..365dc3b 100644 (file)
--- a/date.c
+++ b/date.c
@@ -42,18 +42,24 @@ static const char *weekday_names[] = {
  * thing, which means that tz -0100 is passed in as the integer -100,
  * even though it means "sixty minutes off"
  */
-const char *show_date(unsigned long time, int tz)
+static struct tm *time_to_tm(unsigned long time, int tz)
 {
-       struct tm *tm;
        time_t t;
-       static char timebuf[200];
        int minutes;
 
        minutes = tz < 0 ? -tz : tz;
        minutes = (minutes / 100)*60 + (minutes % 100);
        minutes = tz < 0 ? -minutes : minutes;
        t = time + minutes * 60;
-       tm = gmtime(&t);
+       return gmtime(&t);
+}
+
+const char *show_date(unsigned long time, int tz)
+{
+       struct tm *tm;
+       static char timebuf[200];
+
+       tm = time_to_tm(time, tz);
        if (!tm)
                return NULL;
        sprintf(timebuf, "%.3s %.3s %d %02d:%02d:%02d %d %+05d",
@@ -65,6 +71,21 @@ const char *show_date(unsigned long time, int tz)
        return timebuf;
 }
 
+const char *show_rfc2822_date(unsigned long time, int tz)
+{
+       struct tm *tm;
+       static char timebuf[200];
+
+       tm = time_to_tm(time, tz);
+       if (!tm)
+               return NULL;
+       sprintf(timebuf, "%.3s, %d %.3s %d %02d:%02d:%02d %+05d",
+               weekday_names[tm->tm_wday], tm->tm_mday,
+               month_names[tm->tm_mon], tm->tm_year + 1900,
+               tm->tm_hour, tm->tm_min, tm->tm_sec, tz);
+       return timebuf;
+}
+
 /*
  * Check these. And note how it doesn't do the summer-time conversion.
  *
diff --git a/delta.h b/delta.h
index 727ae30..7b3f86d 100644 (file)
--- a/delta.h
+++ b/delta.h
@@ -18,6 +18,8 @@ create_delta_index(const void *buf, unsigned long bufsize);
 
 /*
  * free_delta_index: free the index created by create_delta_index()
+ *
+ * Given pointer must be what create_delta_index() returned, or NULL.
  */
 extern void free_delta_index(struct delta_index *index);
 
diff --git a/diff.c b/diff.c
index 5285c03..e16e0bf 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -563,7 +563,7 @@ static void builtin_diff(const char *name_a,
 
                ecbdata.label_path = lbl;
                xpp.flags = XDF_NEED_MINIMAL;
-               xecfg.ctxlen = 3;
+               xecfg.ctxlen = o->context;
                xecfg.flags = XDL_EMIT_FUNCNAMES;
                if (!diffopts)
                        ;
@@ -1187,6 +1187,7 @@ void diff_setup(struct diff_options *options)
        options->line_termination = '\n';
        options->break_opt = -1;
        options->rename_limit = -1;
+       options->context = 3;
 
        options->change = diff_change;
        options->add_remove = diff_addremove;
@@ -1227,17 +1228,68 @@ int diff_setup_done(struct diff_options *options)
        return 0;
 }
 
+int opt_arg(const char *arg, int arg_short, const char *arg_long, int *val)
+{
+       char c, *eq;
+       int len;
+
+       if (*arg != '-')
+               return 0;
+       c = *++arg;
+       if (!c)
+               return 0;
+       if (c == arg_short) {
+               c = *++arg;
+               if (!c)
+                       return 1;
+               if (val && isdigit(c)) {
+                       char *end;
+                       int n = strtoul(arg, &end, 10);
+                       if (*end)
+                               return 0;
+                       *val = n;
+                       return 1;
+               }
+               return 0;
+       }
+       if (c != '-')
+               return 0;
+       arg++;
+       eq = strchr(arg, '=');
+       if (eq)
+               len = eq - arg;
+       else
+               len = strlen(arg);
+       if (!len || strncmp(arg, arg_long, len))
+               return 0;
+       if (eq) {
+               int n;
+               char *end;
+               if (!isdigit(*++eq))
+                       return 0;
+               n = strtoul(eq, &end, 10);
+               if (*end)
+                       return 0;
+               *val = n;
+       }
+       return 1;
+}
+
 int diff_opt_parse(struct diff_options *options, const char **av, int ac)
 {
        const char *arg = av[0];
        if (!strcmp(arg, "-p") || !strcmp(arg, "-u"))
                options->output_format = DIFF_FORMAT_PATCH;
+       else if (opt_arg(arg, 'U', "unified", &options->context))
+               options->output_format = DIFF_FORMAT_PATCH;
        else if (!strcmp(arg, "--patch-with-raw")) {
                options->output_format = DIFF_FORMAT_PATCH;
                options->with_raw = 1;
        }
        else if (!strcmp(arg, "--stat"))
                options->output_format = DIFF_FORMAT_DIFFSTAT;
+       else if (!strcmp(arg, "--summary"))
+               options->summary = 1;
        else if (!strcmp(arg, "--patch-with-stat")) {
                options->output_format = DIFF_FORMAT_PATCH;
                options->with_stat = 1;
@@ -1708,6 +1760,85 @@ static void flush_one_pair(struct diff_filepair *p,
        }
 }
 
+static void show_file_mode_name(const char *newdelete, struct diff_filespec *fs)
+{
+       if (fs->mode)
+               printf(" %s mode %06o %s\n", newdelete, fs->mode, fs->path);
+       else
+               printf(" %s %s\n", newdelete, fs->path);
+}
+
+
+static void show_mode_change(struct diff_filepair *p, int show_name)
+{
+       if (p->one->mode && p->two->mode && p->one->mode != p->two->mode) {
+               if (show_name)
+                       printf(" mode change %06o => %06o %s\n",
+                              p->one->mode, p->two->mode, p->two->path);
+               else
+                       printf(" mode change %06o => %06o\n",
+                              p->one->mode, p->two->mode);
+       }
+}
+
+static void show_rename_copy(const char *renamecopy, struct diff_filepair *p)
+{
+       const char *old, *new;
+
+       /* Find common prefix */
+       old = p->one->path;
+       new = p->two->path;
+       while (1) {
+               const char *slash_old, *slash_new;
+               slash_old = strchr(old, '/');
+               slash_new = strchr(new, '/');
+               if (!slash_old ||
+                   !slash_new ||
+                   slash_old - old != slash_new - new ||
+                   memcmp(old, new, slash_new - new))
+                       break;
+               old = slash_old + 1;
+               new = slash_new + 1;
+       }
+       /* p->one->path thru old is the common prefix, and old and new
+        * through the end of names are renames
+        */
+       if (old != p->one->path)
+               printf(" %s %.*s{%s => %s} (%d%%)\n", renamecopy,
+                      (int)(old - p->one->path), p->one->path,
+                      old, new, (int)(0.5 + p->score * 100.0/MAX_SCORE));
+       else
+               printf(" %s %s => %s (%d%%)\n", renamecopy,
+                      p->one->path, p->two->path,
+                      (int)(0.5 + p->score * 100.0/MAX_SCORE));
+       show_mode_change(p, 0);
+}
+
+static void diff_summary(struct diff_filepair *p)
+{
+       switch(p->status) {
+       case DIFF_STATUS_DELETED:
+               show_file_mode_name("delete", p->one);
+               break;
+       case DIFF_STATUS_ADDED:
+               show_file_mode_name("create", p->two);
+               break;
+       case DIFF_STATUS_COPIED:
+               show_rename_copy("copy", p);
+               break;
+       case DIFF_STATUS_RENAMED:
+               show_rename_copy("rename", p);
+               break;
+       default:
+               if (p->score) {
+                       printf(" rewrite %s (%d%%)\n", p->two->path,
+                               (int)(0.5 + p->score * 100.0/MAX_SCORE));
+                       show_mode_change(p, 0);
+               } else  show_mode_change(p, 1);
+               break;
+       }
+}
+
 void diff_flush(struct diff_options *options)
 {
        struct diff_queue_struct *q = &diff_queued_diff;
@@ -1741,7 +1872,6 @@ void diff_flush(struct diff_options *options)
        for (i = 0; i < q->nr; i++) {
                struct diff_filepair *p = q->queue[i];
                flush_one_pair(p, diff_output_format, options, diffstat);
-               diff_free_filepair(p);
        }
 
        if (diffstat) {
@@ -1749,6 +1879,12 @@ void diff_flush(struct diff_options *options)
                free(diffstat);
        }
 
+       for (i = 0; i < q->nr; i++) {
+               if (options->summary)
+                       diff_summary(q->queue[i]);
+               diff_free_filepair(q->queue[i]);
+       }
+
        free(q->queue);
        q->queue = NULL;
        q->nr = q->alloc = 0;
diff --git a/diff.h b/diff.h
index d052608..3027974 100644 (file)
--- a/diff.h
+++ b/diff.h
@@ -31,7 +31,9 @@ struct diff_options {
                 binary:1,
                 full_index:1,
                 silent_on_remove:1,
-                find_copies_harder:1;
+                find_copies_harder:1,
+                summary:1;
+       int context;
        int break_opt;
        int detect_rename;
        int line_termination;
diff --git a/dir.c b/dir.c
new file mode 100644 (file)
index 0000000..d40b62e
--- /dev/null
+++ b/dir.c
@@ -0,0 +1,321 @@
+/*
+ * This handles recursive filename detection with exclude
+ * files, index knowledge etc..
+ *
+ * Copyright (C) Linus Torvalds, 2005-2006
+ *              Junio Hamano, 2005-2006
+ */
+#include <dirent.h>
+#include <fnmatch.h>
+
+#include "cache.h"
+#include "dir.h"
+
+void add_exclude(const char *string, const char *base,
+                int baselen, struct exclude_list *which)
+{
+       struct exclude *x = xmalloc(sizeof (*x));
+
+       x->pattern = string;
+       x->base = base;
+       x->baselen = baselen;
+       if (which->nr == which->alloc) {
+               which->alloc = alloc_nr(which->alloc);
+               which->excludes = realloc(which->excludes,
+                                         which->alloc * sizeof(x));
+       }
+       which->excludes[which->nr++] = x;
+}
+
+static int add_excludes_from_file_1(const char *fname,
+                                   const char *base,
+                                   int baselen,
+                                   struct exclude_list *which)
+{
+       int fd, i;
+       long size;
+       char *buf, *entry;
+
+       fd = open(fname, O_RDONLY);
+       if (fd < 0)
+               goto err;
+       size = lseek(fd, 0, SEEK_END);
+       if (size < 0)
+               goto err;
+       lseek(fd, 0, SEEK_SET);
+       if (size == 0) {
+               close(fd);
+               return 0;
+       }
+       buf = xmalloc(size+1);
+       if (read(fd, buf, size) != size)
+               goto err;
+       close(fd);
+
+       buf[size++] = '\n';
+       entry = buf;
+       for (i = 0; i < size; i++) {
+               if (buf[i] == '\n') {
+                       if (entry != buf + i && entry[0] != '#') {
+                               buf[i - (i && buf[i-1] == '\r')] = 0;
+                               add_exclude(entry, base, baselen, which);
+                       }
+                       entry = buf + i + 1;
+               }
+       }
+       return 0;
+
+ err:
+       if (0 <= fd)
+               close(fd);
+       return -1;
+}
+
+void add_excludes_from_file(struct dir_struct *dir, const char *fname)
+{
+       if (add_excludes_from_file_1(fname, "", 0,
+                                    &dir->exclude_list[EXC_FILE]) < 0)
+               die("cannot use %s as an exclude file", fname);
+}
+
+static int push_exclude_per_directory(struct dir_struct *dir, const char *base, int baselen)
+{
+       char exclude_file[PATH_MAX];
+       struct exclude_list *el = &dir->exclude_list[EXC_DIRS];
+       int current_nr = el->nr;
+
+       if (dir->exclude_per_dir) {
+               memcpy(exclude_file, base, baselen);
+               strcpy(exclude_file + baselen, dir->exclude_per_dir);
+               add_excludes_from_file_1(exclude_file, base, baselen, el);
+       }
+       return current_nr;
+}
+
+static void pop_exclude_per_directory(struct dir_struct *dir, int stk)
+{
+       struct exclude_list *el = &dir->exclude_list[EXC_DIRS];
+
+       while (stk < el->nr)
+               free(el->excludes[--el->nr]);
+}
+
+/* Scan the list and let the last match determines the fate.
+ * Return 1 for exclude, 0 for include and -1 for undecided.
+ */
+static int excluded_1(const char *pathname,
+                     int pathlen,
+                     struct exclude_list *el)
+{
+       int i;
+
+       if (el->nr) {
+               for (i = el->nr - 1; 0 <= i; i--) {
+                       struct exclude *x = el->excludes[i];
+                       const char *exclude = x->pattern;
+                       int to_exclude = 1;
+
+                       if (*exclude == '!') {
+                               to_exclude = 0;
+                               exclude++;
+                       }
+
+                       if (!strchr(exclude, '/')) {
+                               /* match basename */
+                               const char *basename = strrchr(pathname, '/');
+                               basename = (basename) ? basename+1 : pathname;
+                               if (fnmatch(exclude, basename, 0) == 0)
+                                       return to_exclude;
+                       }
+                       else {
+                               /* match with FNM_PATHNAME:
+                                * exclude has base (baselen long) implicitly
+                                * in front of it.
+                                */
+                               int baselen = x->baselen;
+                               if (*exclude == '/')
+                                       exclude++;
+
+                               if (pathlen < baselen ||
+                                   (baselen && pathname[baselen-1] != '/') ||
+                                   strncmp(pathname, x->base, baselen))
+                                   continue;
+
+                               if (fnmatch(exclude, pathname+baselen,
+                                           FNM_PATHNAME) == 0)
+                                       return to_exclude;
+                       }
+               }
+       }
+       return -1; /* undecided */
+}
+
+int excluded(struct dir_struct *dir, const char *pathname)
+{
+       int pathlen = strlen(pathname);
+       int st;
+
+       for (st = EXC_CMDL; st <= EXC_FILE; st++) {
+               switch (excluded_1(pathname, pathlen, &dir->exclude_list[st])) {
+               case 0:
+                       return 0;
+               case 1:
+                       return 1;
+               }
+       }
+       return 0;
+}
+
+static void add_name(struct dir_struct *dir, const char *pathname, int len)
+{
+       struct dir_entry *ent;
+
+       if (cache_name_pos(pathname, len) >= 0)
+               return;
+
+       if (dir->nr == dir->alloc) {
+               int alloc = alloc_nr(dir->alloc);
+               dir->alloc = alloc;
+               dir->entries = xrealloc(dir->entries, alloc*sizeof(ent));
+       }
+       ent = xmalloc(sizeof(*ent) + len + 1);
+       ent->len = len;
+       memcpy(ent->name, pathname, len);
+       ent->name[len] = 0;
+       dir->entries[dir->nr++] = ent;
+}
+
+static int dir_exists(const char *dirname, int len)
+{
+       int pos = cache_name_pos(dirname, len);
+       if (pos >= 0)
+               return 1;
+       pos = -pos-1;
+       if (pos >= active_nr) /* can't */
+               return 0;
+       return !strncmp(active_cache[pos]->name, dirname, len);
+}
+
+/*
+ * Read a directory tree. We currently ignore anything but
+ * directories, regular files and symlinks. That's because git
+ * doesn't handle them at all yet. Maybe that will change some
+ * day.
+ *
+ * Also, we ignore the name ".git" (even if it is not a directory).
+ * That likely will not change.
+ */
+static int read_directory_recursive(struct dir_struct *dir, const char *path, const char *base, int baselen)
+{
+       DIR *fdir = opendir(path);
+       int contents = 0;
+
+       if (fdir) {
+               int exclude_stk;
+               struct dirent *de;
+               char fullname[MAXPATHLEN + 1];
+               memcpy(fullname, base, baselen);
+
+               exclude_stk = push_exclude_per_directory(dir, base, baselen);
+
+               while ((de = readdir(fdir)) != NULL) {
+                       int len;
+
+                       if ((de->d_name[0] == '.') &&
+                           (de->d_name[1] == 0 ||
+                            !strcmp(de->d_name + 1, ".") ||
+                            !strcmp(de->d_name + 1, "git")))
+                               continue;
+                       len = strlen(de->d_name);
+                       memcpy(fullname + baselen, de->d_name, len+1);
+                       if (excluded(dir, fullname) != dir->show_ignored) {
+                               if (!dir->show_ignored || DTYPE(de) != DT_DIR) {
+                                       continue;
+                               }
+                       }
+
+                       switch (DTYPE(de)) {
+                       struct stat st;
+                       int subdir, rewind_base;
+                       default:
+                               continue;
+                       case DT_UNKNOWN:
+                               if (lstat(fullname, &st))
+                                       continue;
+                               if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))
+                                       break;
+                               if (!S_ISDIR(st.st_mode))
+                                       continue;
+                               /* fallthrough */
+                       case DT_DIR:
+                               memcpy(fullname + baselen + len, "/", 2);
+                               len++;
+                               rewind_base = dir->nr;
+                               subdir = read_directory_recursive(dir, fullname, fullname,
+                                                       baselen + len);
+                               if (dir->show_other_directories &&
+                                   (subdir || !dir->hide_empty_directories) &&
+                                   !dir_exists(fullname, baselen + len)) {
+                                       // Rewind the read subdirectory
+                                       while (dir->nr > rewind_base)
+                                               free(dir->entries[--dir->nr]);
+                                       break;
+                               }
+                               contents += subdir;
+                               continue;
+                       case DT_REG:
+                       case DT_LNK:
+                               break;
+                       }
+                       add_name(dir, fullname, baselen + len);
+                       contents++;
+               }
+               closedir(fdir);
+
+               pop_exclude_per_directory(dir, exclude_stk);
+       }
+
+       return contents;
+}
+
+static int cmp_name(const void *p1, const void *p2)
+{
+       const struct dir_entry *e1 = *(const struct dir_entry **)p1;
+       const struct dir_entry *e2 = *(const struct dir_entry **)p2;
+
+       return cache_name_compare(e1->name, e1->len,
+                                 e2->name, e2->len);
+}
+
+int read_directory(struct dir_struct *dir, const char *path, const char *base, int baselen)
+{
+       /*
+        * Make sure to do the per-directory exclude for all the
+        * directories leading up to our base.
+        */
+       if (baselen) {
+               if (dir->exclude_per_dir) {
+                       char *p, *pp = xmalloc(baselen+1);
+                       memcpy(pp, base, baselen+1);
+                       p = pp;
+                       while (1) {
+                               char save = *p;
+                               *p = 0;
+                               push_exclude_per_directory(dir, pp, p-pp);
+                               *p++ = save;
+                               if (!save)
+                                       break;
+                               p = strchr(p, '/');
+                               if (p)
+                                       p++;
+                               else
+                                       p = pp + baselen;
+                       }
+                       free(pp);
+               }
+       }
+
+       read_directory_recursive(dir, path, base, baselen);
+       qsort(dir->entries, dir->nr, sizeof(struct dir_entry *), cmp_name);
+       return dir->nr;
+}
diff --git a/dir.h b/dir.h
new file mode 100644 (file)
index 0000000..4f65f57
--- /dev/null
+++ b/dir.h
@@ -0,0 +1,48 @@
+#ifndef DIR_H
+#define DIR_H
+
+/*
+ * We maintain three exclude pattern lists:
+ * EXC_CMDL lists patterns explicitly given on the command line.
+ * EXC_DIRS lists patterns obtained from per-directory ignore files.
+ * EXC_FILE lists patterns from fallback ignore files.
+ */
+#define EXC_CMDL 0
+#define EXC_DIRS 1
+#define EXC_FILE 2
+
+
+struct dir_entry {
+       int len;
+       char name[FLEX_ARRAY]; /* more */
+};
+
+struct exclude_list {
+       int nr;
+       int alloc;
+       struct exclude {
+               const char *pattern;
+               const char *base;
+               int baselen;
+       } **excludes;
+};
+
+struct dir_struct {
+       int nr, alloc;
+       unsigned int show_ignored:1,
+                    show_other_directories:1,
+                    hide_empty_directories:1;
+       struct dir_entry **entries;
+
+       /* Exclude info */
+       const char *exclude_per_dir;
+       struct exclude_list exclude_list[3];
+};
+
+extern int read_directory(struct dir_struct *, const char *path, const char *base, int baselen);
+extern int excluded(struct dir_struct *, const char *);
+extern void add_excludes_from_file(struct dir_struct *, const char *fname);
+extern void add_exclude(const char *string, const char *base,
+                       int baselen, struct exclude_list *which);
+
+#endif
diff --git a/dump-cache-tree.c b/dump-cache-tree.c
new file mode 100644 (file)
index 0000000..1ccaf51
--- /dev/null
@@ -0,0 +1,64 @@
+#include "cache.h"
+#include "tree.h"
+#include "cache-tree.h"
+
+
+static void dump_one(struct cache_tree *it, const char *pfx, const char *x)
+{
+       if (it->entry_count < 0)
+               printf("%-40s %s%s (%d subtrees)\n",
+                      "invalid", x, pfx, it->subtree_nr);
+       else
+               printf("%s %s%s (%d entries, %d subtrees)\n",
+                      sha1_to_hex(it->sha1), x, pfx,
+                      it->entry_count, it->subtree_nr);
+}
+
+static int dump_cache_tree(struct cache_tree *it,
+                          struct cache_tree *ref,
+                          const char *pfx)
+{
+       int i;
+       int errs = 0;
+
+       if (!it || !ref)
+               /* missing in either */
+               return 0;
+
+       if (it->entry_count < 0) {
+               dump_one(it, pfx, "");
+               dump_one(ref, pfx, "#(ref) ");
+               if (it->subtree_nr != ref->subtree_nr)
+                       errs = 1;
+       }
+       else {
+               dump_one(it, pfx, "");
+               if (memcmp(it->sha1, ref->sha1, 20) ||
+                   ref->entry_count != it->entry_count ||
+                   ref->subtree_nr != it->subtree_nr) {
+                       dump_one(ref, pfx, "#(ref) ");
+                       errs = 1;
+               }
+       }
+
+       for (i = 0; i < it->subtree_nr; i++) {
+               char path[PATH_MAX];
+               struct cache_tree_sub *down = it->down[i];
+               struct cache_tree_sub *rdwn;
+
+               rdwn = cache_tree_sub(ref, down->name);
+               sprintf(path, "%s%.*s/", pfx, down->namelen, down->name);
+               if (dump_cache_tree(down->cache_tree, rdwn->cache_tree, path))
+                       errs = 1;
+       }
+       return errs;
+}
+
+int main(int ac, char **av)
+{
+       struct cache_tree *another = cache_tree();
+       if (read_cache() < 0)
+               die("unable to read index file");
+       cache_tree_update(another, active_cache, active_nr, 0, 1);
+       return dump_cache_tree(active_cache_tree, another, "");
+}
index 59b2590..1922b6d 100644 (file)
@@ -8,6 +8,7 @@
 #include "tag.h"
 #include "refs.h"
 #include "pack.h"
+#include "cache-tree.h"
 
 #define REACHABLE 0x0001
 
@@ -438,6 +439,28 @@ static int fsck_head_link(void)
        return 0;
 }
 
+static int fsck_cache_tree(struct cache_tree *it)
+{
+       int i;
+       int err = 0;
+
+       if (0 <= it->entry_count) {
+               struct object *obj = parse_object(it->sha1);
+               if (!obj) {
+                       error("%s: invalid sha1 pointer in cache-tree",
+                             sha1_to_hex(it->sha1));
+                       return 1;
+               }
+               mark_reachable(obj, REACHABLE);
+               obj->used = 1;
+               if (obj->type != tree_type)
+                       err |= objerror(obj, "non-tree in cache-tree");
+       }
+       for (i = 0; i < it->subtree_nr; i++)
+               err |= fsck_cache_tree(it->down[i]->cache_tree);
+       return err;
+}
+
 int main(int argc, char **argv)
 {
        int i, heads;
@@ -547,6 +570,8 @@ int main(int argc, char **argv)
                        obj->used = 1;
                        mark_reachable(obj, REACHABLE);
                }
+               if (active_cache_tree)
+                       fsck_cache_tree(active_cache_tree);
        }
 
        check_connectivity();
index f50dff2..97ec2d0 100755 (executable)
--- a/git-am.sh
+++ b/git-am.sh
@@ -15,6 +15,10 @@ stop_here () {
 }
 
 stop_here_user_resolve () {
+    if [ -n "$resolvemsg" ]; then
+           echo "$resolvemsg"
+           stop_here $1
+    fi
     cmdline=$(basename $0)
     if test '' != "$interactive"
     then
@@ -87,7 +91,7 @@ fall_back_3way () {
 }
 
 prec=4
-dotest=.dotest sign= utf8= keep= skip= interactive= resolved= binary= ws=
+dotest=.dotest sign= utf8= keep= skip= interactive= resolved= binary= ws= resolvemsg=
 
 while case "$#" in 0) break;; esac
 do
@@ -123,6 +127,9 @@ do
        --whitespace=*)
        ws=$1; shift ;;
 
+       --resolvemsg=*)
+       resolvemsg=$(echo "$1" | sed -e "s/^--resolvemsg=//"); shift ;;
+
        --)
        shift; break ;;
        -*)
@@ -151,7 +158,7 @@ then
 else
        # Make sure we are not given --skip nor --resolved
        test ",$skip,$resolved," = ,,, ||
-               die "we are not resuming."
+               die "Resolve operation not in progress, we are not resuming."
 
        # Start afresh.
        mkdir -p "$dotest" || exit
index ebcc898..134e68c 100755 (executable)
@@ -82,8 +82,7 @@ done
 
 case "$#" in
 0)
-       git-rev-parse --symbolic --all |
-       sed -ne 's|^refs/heads/||p' |
+       git-rev-parse --symbolic --branches |
        sort |
        while read ref
        do
index c077f44..8a16ead 100755 (executable)
@@ -274,7 +274,7 @@ print "\n---\n\n";
 close FH or die "close $commsg pipe";
 ' "$keep_subject" "$num" "$signoff" "$headers" "$mimemagic" $commsg
 
-       git-diff-tree -p $diff_opts "$commit" | git-apply --stat --summary
+       git-diff-tree -p --stat --summary $diff_opts "$commit"
        echo
        case "$mimemagic" in
        '');;
diff --git a/git-grep.sh b/git-grep.sh
deleted file mode 100755 (executable)
index ad4f2fe..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-#!/bin/sh
-#
-# Copyright (c) Linus Torvalds, 2005
-#
-
-USAGE='[<option>...] [-e] <pattern> [<path>...]'
-SUBDIRECTORY_OK='Yes'
-. git-sh-setup
-
-got_pattern () {
-       if [ -z "$no_more_patterns" ]
-       then
-               pattern="$1" no_more_patterns=yes
-       else
-               die "git-grep: do not specify more than one pattern"
-       fi
-}
-
-no_more_patterns=
-pattern=
-flags=()
-git_flags=()
-while : ; do
-       case "$1" in
-       -o|--cached|--deleted|--others|--killed|\
-       --ignored|--modified|--exclude=*|\
-       --exclude-from=*|\--exclude-per-directory=*)
-               git_flags=("${git_flags[@]}" "$1")
-               ;;
-       -e)
-               got_pattern "$2"
-               shift
-               ;;
-       -A|-B|-C|-D|-d|-f|-m)
-               flags=("${flags[@]}" "$1" "$2")
-               shift
-               ;;
-       --)
-               # The rest are git-ls-files paths
-               shift
-               break
-               ;;
-       -*)
-               flags=("${flags[@]}" "$1")
-               ;;
-       *)
-               if [ -z "$no_more_patterns" ]
-               then
-                       got_pattern "$1"
-                       shift
-               fi
-               [ "$1" = -- ] && shift
-               break
-               ;;
-       esac
-       shift
-done
-[ "$pattern" ] || {
-       usage
-}
-git-ls-files -z "${git_flags[@]}" -- "$@" |
-       xargs -0 grep "${flags[@]}" -e "$pattern" --
index b834e79..af1f25b 100755 (executable)
@@ -55,8 +55,7 @@ finish () {
 
        case "$no_summary" in
        '')
-               git-diff-tree -p -M "$head" "$1" |
-               git-apply --stat --summary
+               git-diff-tree -p --stat --summary -M "$head" "$1"
                ;;
        esac
 }
index c9b899e..187f088 100755 (executable)
@@ -10,7 +10,10 @@ get_data_source () {
                # Not so fast.  This could be the partial URL shorthand...
                token=$(expr "z$1" : 'z\([^/]*\)/')
                remainder=$(expr "z$1" : 'z[^/]*/\(.*\)')
-               if test -f "$GIT_DIR/branches/$token"
+               if test "$(git-repo-config --get "remote.$token.url")"
+               then
+                       echo config-partial
+               elif test -f "$GIT_DIR/branches/$token"
                then
                        echo branches-partial
                else
@@ -18,7 +21,10 @@ get_data_source () {
                fi
                ;;
        *)
-               if test -f "$GIT_DIR/remotes/$1"
+               if test "$(git-repo-config --get "remote.$1.url")"
+               then
+                       echo config
+               elif test -f "$GIT_DIR/remotes/$1"
                then
                        echo remotes
                elif test -f "$GIT_DIR/branches/$1"
@@ -35,6 +41,15 @@ get_remote_url () {
        case "$data_source" in
        '')
                echo "$1" ;;
+       config-partial)
+               token=$(expr "z$1" : 'z\([^/]*\)/')
+               remainder=$(expr "z$1" : 'z[^/]*/\(.*\)')
+               url=$(git-repo-config --get "remote.$token.url")
+               echo "$url/$remainder"
+               ;;
+       config)
+               git-repo-config --get "remote.$1.url"
+               ;;
        remotes)
                sed -ne '/^URL: */{
                        s///p
@@ -56,8 +71,10 @@ get_remote_url () {
 get_remote_default_refs_for_push () {
        data_source=$(get_data_source "$1")
        case "$data_source" in
-       '' | branches | branches-partial)
+       '' | config-partial | branches | branches-partial)
                ;; # no default push mapping, just send matching refs.
+       config)
+               git-repo-config --get-all "remote.$1.push" ;;
        remotes)
                sed -ne '/^Push: */{
                        s///p
@@ -111,8 +128,11 @@ canon_refs_list_for_fetch () {
 get_remote_default_refs_for_fetch () {
        data_source=$(get_data_source "$1")
        case "$data_source" in
-       '' | branches-partial)
+       '' | config-partial | branches-partial)
                echo "HEAD:" ;;
+       config)
+               canon_refs_list_for_fetch \
+                       $(git-repo-config --get-all "remote.$1.fetch") ;;
        branches)
                remote_branch=$(sed -ne '/#/s/.*#//p' "$GIT_DIR/branches/$1")
                case "$remote_branch" in '') remote_branch=master ;; esac
index 9e25902..6ff6088 100755 (executable)
@@ -12,9 +12,10 @@ It then attempts to create a new commit for each commit from the original
 
 It is possible that a merge failure will prevent this process from being
 completely automatic.  You will have to resolve any such merge failure
-and run git-rebase --continue.  If you can not resolve the merge failure,
-running git-rebase --abort will restore the original <branch> and remove
-the working files found in the .dotest directory.
+and run git rebase --continue.  Another option is to bypass the commit
+that caused the merge failure with git rebase --skip.  To restore the
+original <branch> and remove the .dotest working files, use the command
+git rebase --abort instead.
 
 Note that if <branch> is not specified on the command line, the
 currently checked out branch is used.  You must be in the top
@@ -28,6 +29,11 @@ Example:       git-rebase master~1 topic
 '
 . git-sh-setup
 
+RESOLVEMSG="
+When you have resolved this problem run \"git rebase --continue\".
+If you would prefer to skip this patch, instead run \"git rebase --skip\".
+To restore the original branch and stop rebasing run \"git rebase --abort\".
+"
 unset newbase
 while case "$#" in 0) break ;; esac
 do
@@ -40,7 +46,11 @@ do
                        exit 1
                        ;;
                esac
-               git am --resolved --3way
+               git am --resolved --3way --resolvemsg="$RESOLVEMSG"
+               exit
+               ;;
+       --skip)
+               git am -3 --skip --resolvemsg="$RESOLVEMSG"
                exit
                ;;
        --abort)
@@ -143,4 +153,5 @@ then
 fi
 
 git-format-patch -k --stdout --full-index "$upstream" ORIG_HEAD |
-git am --binary -3 -k
+git am --binary -3 -k --resolvemsg="$RESOLVEMSG"
+
index 2c48bfb..4319e35 100755 (executable)
@@ -30,4 +30,4 @@ echo "  $url"
 echo
 
 git log  $baserev..$headrev | git-shortlog ;
-git diff $baserev..$headrev | git-apply --stat --summary
+git diff --stat --summary $baserev..$headrev
index 6cb073c..0ee3e3e 100755 (executable)
@@ -6,6 +6,7 @@ USAGE='[--mixed | --soft | --hard]  [<commit-ish>]'
 tmp=${GIT_DIR}/reset.$$
 trap 'rm -f $tmp-*' 0 1 2 3 15
 
+update=
 reset_type=--mixed
 case "$1" in
 --mixed | --soft | --hard)
@@ -23,24 +24,7 @@ rev=$(git-rev-parse --verify $rev^0) || exit
 # behind before a hard reset, so that we can remove them.
 if test "$reset_type" = "--hard"
 then
-       {
-               git-ls-files --stage -z
-               git-rev-parse --verify HEAD 2>/dev/null &&
-               git-ls-tree -r -z HEAD
-       } | perl -e '
-           use strict;
-           my %seen;
-           $/ = "\0";
-           while (<>) {
-               chomp;
-               my ($info, $path) = split(/\t/, $_);
-               next if ($info =~ / tree /);
-               if (!$seen{$path}) {
-                       $seen{$path} = 1;
-                       print "$path\0";
-               }
-           }
-       ' >$tmp-exists
+       update=-u
 fi
 
 # Soft reset does not touch the index file nor the working tree
@@ -54,7 +38,7 @@ then
                die "Cannot do a soft reset in the middle of a merge."
        fi
 else
-       git-read-tree --reset "$rev" || exit
+       git-read-tree --reset $update "$rev" || exit
 fi
 
 # Any resets update HEAD to the head being switched to.
@@ -68,33 +52,7 @@ git-update-ref HEAD "$rev"
 
 case "$reset_type" in
 --hard )
-       # Hard reset matches the working tree to that of the tree
-       # being switched to.
-       git-checkout-index -f -u -q -a
-       git-ls-files --cached -z |
-       perl -e '
-               use strict;
-               my (%keep, $fh);
-               $/ = "\0";
-               while (<STDIN>) {
-                       chomp;
-                       $keep{$_} = 1;
-               }
-               open $fh, "<", $ARGV[0]
-                       or die "cannot open $ARGV[0]";
-               while (<$fh>) {
-                       chomp;
-                       if (! exists $keep{$_}) {
-                               # it is ok if this fails -- it may already
-                               # have been culled by checkout-index.
-                               unlink $_;
-                               while (s|/[^/]*$||) {
-                                       rmdir($_) or last;
-                               }
-                       }
-               }
-       ' $tmp-exists
-       ;;
+       ;; # Nothing else to do
 --soft )
        ;; # Nothing else to do
 --mixed )
index d8c4b1f..312a4ea 100755 (executable)
@@ -40,7 +40,8 @@ my $compose_filename = ".msg.$$";
 my (@to,@cc,@initial_cc,$initial_reply_to,$initial_subject,@files,$from,$compose,$time);
 
 # Behavior modification variables
-my ($chain_reply_to, $smtp_server, $quiet, $suppress_from, $no_signed_off_cc) = (1, "localhost", 0, 0, 0);
+my ($chain_reply_to, $quiet, $suppress_from, $no_signed_off_cc) = (1, 0, 0, 0);
+my $smtp_server;
 
 # Example reply to:
 #$initial_reply_to = ''; #<20050203173208.GA23964@foobar.com>';
@@ -179,8 +180,14 @@ if (!defined $initial_reply_to && $prompting) {
        $initial_reply_to =~ s/(^\s+|\s+$)//g;
 }
 
-if (!defined $smtp_server) {
-       $smtp_server = "localhost";
+if (!$smtp_server) {
+       foreach (qw( /usr/sbin/sendmail /usr/lib/sendmail )) {
+               if (-x $_) {
+                       $smtp_server = $_;
+                       last;
+               }
+       }
+       $smtp_server ||= 'localhost'; # could be 127.0.0.1, too... *shrug*
 }
 
 if ($compose) {
@@ -300,6 +307,10 @@ our ($message_id, $cc, %mail, $subject, $reply_to, $message);
 
 sub extract_valid_address {
        my $address = shift;
+
+       # check for a local address:
+       return $address if ($address =~ /^([\w\-]+)$/);
+
        if ($have_email_valid) {
                return Email::Valid->address($address);
        } else {
@@ -358,26 +369,39 @@ X-Mailer: git-send-email $gitversion
 ";
        $header .= "In-Reply-To: $reply_to\n" if $reply_to;
 
-       $smtp ||= Net::SMTP->new( $smtp_server );
-       $smtp->mail( $from ) or die $smtp->message;
-       $smtp->to( @recipients ) or die $smtp->message;
-       $smtp->data or die $smtp->message;
-       $smtp->datasend("$header\n$message") or die $smtp->message;
-       $smtp->dataend() or die $smtp->message;
-       $smtp->ok or die "Failed to send $subject\n".$smtp->message;
-
+       if ($smtp_server =~ m#^/#) {
+               my $pid = open my $sm, '|-';
+               defined $pid or die $!;
+               if (!$pid) {
+                       exec($smtp_server,'-i',@recipients) or die $!;
+               }
+               print $sm "$header\n$message";
+               close $sm or die $?;
+       } else {
+               $smtp ||= Net::SMTP->new( $smtp_server );
+               $smtp->mail( $from ) or die $smtp->message;
+               $smtp->to( @recipients ) or die $smtp->message;
+               $smtp->data or die $smtp->message;
+               $smtp->datasend("$header\n$message") or die $smtp->message;
+               $smtp->dataend() or die $smtp->message;
+               $smtp->ok or die "Failed to send $subject\n".$smtp->message;
+       }
        if ($quiet) {
                printf "Sent %s\n", $subject;
        } else {
-               print "OK. Log says:
-Date: $date
-Server: $smtp_server Port: 25
-From: $from
-Subject: $subject
-Cc: $cc
-To: $to
-
-Result: ", $smtp->code, ' ', ($smtp->message =~ /\n([^\n]+\n)$/s), "\n";
+               print "OK. Log says:\nDate: $date\n";
+               if ($smtp) {
+                       print "Server: $smtp_server\n";
+               } else {
+                       print "Sendmail: $smtp_server\n";
+               }
+               print "From: $from\nSubject: $subject\nCc: $cc\nTo: $to\n\n";
+               if ($smtp) {
+                       print "Result: ", $smtp->code, ' ',
+                               ($smtp->message =~ /\n([^\n]+\n)$/s), "\n";
+               } else {
+                       print "Result: OK\n";
+               }
        }
 }
 
@@ -478,9 +502,14 @@ sub unique_email_list(@) {
        my @emails;
 
        foreach my $entry (@_) {
-               my $clean = extract_valid_address($entry);
-               next if $seen{$clean}++;
-               push @emails, $entry;
+               if (my $clean = extract_valid_address($entry)) {
+                       $seen{$clean} ||= 0;
+                       next if $seen{$clean}++;
+                       push @emails, $entry;
+               } else {
+                       print STDERR "W: unable to extract a valid address",
+                                       " from: $entry\n";
+               }
        }
        return @emails;
 }
index dc6aa95..a0afa25 100755 (executable)
@@ -25,14 +25,12 @@ do
        force=1
        ;;
     -l)
-        cd "$GIT_DIR/refs" &&
        case "$#" in
        1)
-               find tags -type f -print ;;
-       *)
-               shift
-               find tags -type f -print | grep "$@" ;;
+               set x . ;;
        esac
+       shift
+       git rev-parse --symbolic --tags | sort | grep "$@"
        exit $?
        ;;
     -m)
diff --git a/git.c b/git.c
index 49ba518..6a470cf 100644 (file)
--- a/git.c
+++ b/git.c
@@ -47,8 +47,11 @@ static void handle_internal_command(int argc, const char **argv, char **envp)
                { "whatchanged", cmd_whatchanged },
                { "show", cmd_show },
                { "push", cmd_push },
+               { "fmt-patch", cmd_format_patch },
                { "count-objects", cmd_count_objects },
                { "diff", cmd_diff },
+               { "grep", cmd_grep },
+               { "add", cmd_add },
        };
        int i;
 
index b90ba67..526d578 100644 (file)
@@ -20,6 +20,7 @@ void show_log(struct rev_info *opt, struct log_info *log, const char *sep)
        int abbrev_commit = opt->abbrev_commit ? opt->abbrev : 40;
        const char *extra;
        int len;
+       char* subject = NULL;
 
        opt->loginfo = NULL;
        if (!opt->verbose_header) {
@@ -49,19 +50,38 @@ void show_log(struct rev_info *opt, struct log_info *log, const char *sep)
        /*
         * Print header line of header..
         */
-       printf("%s%s",
-               opt->commit_format == CMIT_FMT_ONELINE ? "" : "commit ",
-               diff_unique_abbrev(commit->object.sha1, abbrev_commit));
-       if (opt->parents)
-               show_parents(commit, abbrev_commit);
-       if (parent)
-               printf(" (from %s)", diff_unique_abbrev(parent->object.sha1, abbrev_commit));
-       putchar(opt->commit_format == CMIT_FMT_ONELINE ? ' ' : '\n');
+
+       if (opt->commit_format == CMIT_FMT_EMAIL) {
+               if (opt->total > 0) {
+                       static char buffer[64];
+                       snprintf(buffer, sizeof(buffer),
+                                       "Subject: [PATCH %d/%d] ",
+                                       opt->nr, opt->total);
+                       subject = buffer;
+               } else if (opt->total == 0)
+                       subject = "Subject: [PATCH] ";
+               else
+                       subject = "Subject: ";
+
+               printf("From %s  Thu Apr 7 15:13:13 2005\n",
+                      sha1_to_hex(commit->object.sha1));
+       } else {
+               printf("%s%s",
+                      opt->commit_format == CMIT_FMT_ONELINE ? "" : "commit ",
+                      diff_unique_abbrev(commit->object.sha1, abbrev_commit));
+               if (opt->parents)
+                       show_parents(commit, abbrev_commit);
+               if (parent) 
+                       printf(" (from %s)",
+                              diff_unique_abbrev(parent->object.sha1,
+                                                 abbrev_commit));
+               putchar(opt->commit_format == CMIT_FMT_ONELINE ? ' ' : '\n');
+       }
 
        /*
         * And then the pretty-printed message itself
         */
-       len = pretty_print_commit(opt->commit_format, commit, ~0u, this_header, sizeof(this_header), abbrev);
+       len = pretty_print_commit(opt->commit_format, commit, ~0u, this_header, sizeof(this_header), abbrev, subject);
        printf("%s%s%s", this_header, extra, sep);
 }
 
@@ -166,15 +186,18 @@ static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log
 int log_tree_commit(struct rev_info *opt, struct commit *commit)
 {
        struct log_info log;
+       int shown;
 
        log.commit = commit;
        log.parent = NULL;
        opt->loginfo = &log;
 
-       if (!log_tree_diff(opt, commit, &log) && opt->loginfo && opt->always_show_header) {
+       shown = log_tree_diff(opt, commit, &log);
+       if (!shown && opt->loginfo && opt->always_show_header) {
                log.parent = NULL;
                show_log(opt, opt->loginfo, "");
+               shown = 1;
        }
        opt->loginfo = NULL;
-       return 0;
+       return shown;
 }
index 4a4af1c..dfe1481 100644 (file)
@@ -5,23 +5,20 @@
  *
  * Copyright (C) Linus Torvalds, 2005
  */
-#include <dirent.h>
 #include <fnmatch.h>
 
 #include "cache.h"
 #include "quote.h"
+#include "dir.h"
 
 static int abbrev = 0;
 static int show_deleted = 0;
 static int show_cached = 0;
 static int show_others = 0;
-static int show_ignored = 0;
 static int show_stage = 0;
 static int show_unmerged = 0;
 static int show_modified = 0;
 static int show_killed = 0;
-static int show_other_directories = 0;
-static int hide_empty_directories = 0;
 static int show_valid_bit = 0;
 static int line_terminator = '\n';
 
@@ -38,309 +35,6 @@ static const char *tag_other = "";
 static const char *tag_killed = "";
 static const char *tag_modified = "";
 
-static const char *exclude_per_dir = NULL;
-
-/* We maintain three exclude pattern lists:
- * EXC_CMDL lists patterns explicitly given on the command line.
- * EXC_DIRS lists patterns obtained from per-directory ignore files.
- * EXC_FILE lists patterns from fallback ignore files.
- */
-#define EXC_CMDL 0
-#define EXC_DIRS 1
-#define EXC_FILE 2
-static struct exclude_list {
-       int nr;
-       int alloc;
-       struct exclude {
-               const char *pattern;
-               const char *base;
-               int baselen;
-       } **excludes;
-} exclude_list[3];
-
-static void add_exclude(const char *string, const char *base,
-                       int baselen, struct exclude_list *which)
-{
-       struct exclude *x = xmalloc(sizeof (*x));
-
-       x->pattern = string;
-       x->base = base;
-       x->baselen = baselen;
-       if (which->nr == which->alloc) {
-               which->alloc = alloc_nr(which->alloc);
-               which->excludes = realloc(which->excludes,
-                                         which->alloc * sizeof(x));
-       }
-       which->excludes[which->nr++] = x;
-}
-
-static int add_excludes_from_file_1(const char *fname,
-                                   const char *base,
-                                   int baselen,
-                                   struct exclude_list *which)
-{
-       int fd, i;
-       long size;
-       char *buf, *entry;
-
-       fd = open(fname, O_RDONLY);
-       if (fd < 0)
-               goto err;
-       size = lseek(fd, 0, SEEK_END);
-       if (size < 0)
-               goto err;
-       lseek(fd, 0, SEEK_SET);
-       if (size == 0) {
-               close(fd);
-               return 0;
-       }
-       buf = xmalloc(size+1);
-       if (read(fd, buf, size) != size)
-               goto err;
-       close(fd);
-
-       buf[size++] = '\n';
-       entry = buf;
-       for (i = 0; i < size; i++) {
-               if (buf[i] == '\n') {
-                       if (entry != buf + i && entry[0] != '#') {
-                               buf[i - (i && buf[i-1] == '\r')] = 0;
-                               add_exclude(entry, base, baselen, which);
-                       }
-                       entry = buf + i + 1;
-               }
-       }
-       return 0;
-
- err:
-       if (0 <= fd)
-               close(fd);
-       return -1;
-}
-
-static void add_excludes_from_file(const char *fname)
-{
-       if (add_excludes_from_file_1(fname, "", 0,
-                                    &exclude_list[EXC_FILE]) < 0)
-               die("cannot use %s as an exclude file", fname);
-}
-
-static int push_exclude_per_directory(const char *base, int baselen)
-{
-       char exclude_file[PATH_MAX];
-       struct exclude_list *el = &exclude_list[EXC_DIRS];
-       int current_nr = el->nr;
-
-       if (exclude_per_dir) {
-               memcpy(exclude_file, base, baselen);
-               strcpy(exclude_file + baselen, exclude_per_dir);
-               add_excludes_from_file_1(exclude_file, base, baselen, el);
-       }
-       return current_nr;
-}
-
-static void pop_exclude_per_directory(int stk)
-{
-       struct exclude_list *el = &exclude_list[EXC_DIRS];
-
-       while (stk < el->nr)
-               free(el->excludes[--el->nr]);
-}
-
-/* Scan the list and let the last match determines the fate.
- * Return 1 for exclude, 0 for include and -1 for undecided.
- */
-static int excluded_1(const char *pathname,
-                     int pathlen,
-                     struct exclude_list *el)
-{
-       int i;
-
-       if (el->nr) {
-               for (i = el->nr - 1; 0 <= i; i--) {
-                       struct exclude *x = el->excludes[i];
-                       const char *exclude = x->pattern;
-                       int to_exclude = 1;
-
-                       if (*exclude == '!') {
-                               to_exclude = 0;
-                               exclude++;
-                       }
-
-                       if (!strchr(exclude, '/')) {
-                               /* match basename */
-                               const char *basename = strrchr(pathname, '/');
-                               basename = (basename) ? basename+1 : pathname;
-                               if (fnmatch(exclude, basename, 0) == 0)
-                                       return to_exclude;
-                       }
-                       else {
-                               /* match with FNM_PATHNAME:
-                                * exclude has base (baselen long) implicitly
-                                * in front of it.
-                                */
-                               int baselen = x->baselen;
-                               if (*exclude == '/')
-                                       exclude++;
-
-                               if (pathlen < baselen ||
-                                   (baselen && pathname[baselen-1] != '/') ||
-                                   strncmp(pathname, x->base, baselen))
-                                   continue;
-
-                               if (fnmatch(exclude, pathname+baselen,
-                                           FNM_PATHNAME) == 0)
-                                       return to_exclude;
-                       }
-               }
-       }
-       return -1; /* undecided */
-}
-
-static int excluded(const char *pathname)
-{
-       int pathlen = strlen(pathname);
-       int st;
-
-       for (st = EXC_CMDL; st <= EXC_FILE; st++) {
-               switch (excluded_1(pathname, pathlen, &exclude_list[st])) {
-               case 0:
-                       return 0;
-               case 1:
-                       return 1;
-               }
-       }
-       return 0;
-}
-
-struct nond_on_fs {
-       int len;
-       char name[FLEX_ARRAY]; /* more */
-};
-
-static struct nond_on_fs **dir;
-static int nr_dir;
-static int dir_alloc;
-
-static void add_name(const char *pathname, int len)
-{
-       struct nond_on_fs *ent;
-
-       if (cache_name_pos(pathname, len) >= 0)
-               return;
-
-       if (nr_dir == dir_alloc) {
-               dir_alloc = alloc_nr(dir_alloc);
-               dir = xrealloc(dir, dir_alloc*sizeof(ent));
-       }
-       ent = xmalloc(sizeof(*ent) + len + 1);
-       ent->len = len;
-       memcpy(ent->name, pathname, len);
-       ent->name[len] = 0;
-       dir[nr_dir++] = ent;
-}
-
-static int dir_exists(const char *dirname, int len)
-{
-       int pos = cache_name_pos(dirname, len);
-       if (pos >= 0)
-               return 1;
-       pos = -pos-1;
-       if (pos >= active_nr) /* can't */
-               return 0;
-       return !strncmp(active_cache[pos]->name, dirname, len);
-}
-
-/*
- * Read a directory tree. We currently ignore anything but
- * directories, regular files and symlinks. That's because git
- * doesn't handle them at all yet. Maybe that will change some
- * day.
- *
- * Also, we ignore the name ".git" (even if it is not a directory).
- * That likely will not change.
- */
-static int read_directory(const char *path, const char *base, int baselen)
-{
-       DIR *fdir = opendir(path);
-       int contents = 0;
-
-       if (fdir) {
-               int exclude_stk;
-               struct dirent *de;
-               char fullname[MAXPATHLEN + 1];
-               memcpy(fullname, base, baselen);
-
-               exclude_stk = push_exclude_per_directory(base, baselen);
-
-               while ((de = readdir(fdir)) != NULL) {
-                       int len;
-
-                       if ((de->d_name[0] == '.') &&
-                           (de->d_name[1] == 0 ||
-                            !strcmp(de->d_name + 1, ".") ||
-                            !strcmp(de->d_name + 1, "git")))
-                               continue;
-                       len = strlen(de->d_name);
-                       memcpy(fullname + baselen, de->d_name, len+1);
-                       if (excluded(fullname) != show_ignored) {
-                               if (!show_ignored || DTYPE(de) != DT_DIR) {
-                                       continue;
-                               }
-                       }
-
-                       switch (DTYPE(de)) {
-                       struct stat st;
-                       int subdir, rewind_base;
-                       default:
-                               continue;
-                       case DT_UNKNOWN:
-                               if (lstat(fullname, &st))
-                                       continue;
-                               if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))
-                                       break;
-                               if (!S_ISDIR(st.st_mode))
-                                       continue;
-                               /* fallthrough */
-                       case DT_DIR:
-                               memcpy(fullname + baselen + len, "/", 2);
-                               len++;
-                               rewind_base = nr_dir;
-                               subdir = read_directory(fullname, fullname,
-                                                       baselen + len);
-                               if (show_other_directories &&
-                                   (subdir || !hide_empty_directories) &&
-                                   !dir_exists(fullname, baselen + len)) {
-                                       // Rewind the read subdirectory
-                                       while (nr_dir > rewind_base)
-                                               free(dir[--nr_dir]);
-                                       break;
-                               }
-                               contents += subdir;
-                               continue;
-                       case DT_REG:
-                       case DT_LNK:
-                               break;
-                       }
-                       add_name(fullname, baselen + len);
-                       contents++;
-               }
-               closedir(fdir);
-
-               pop_exclude_per_directory(exclude_stk);
-       }
-
-       return contents;
-}
-
-static int cmp_name(const void *p1, const void *p2)
-{
-       const struct nond_on_fs *e1 = *(const struct nond_on_fs **)p1;
-       const struct nond_on_fs *e2 = *(const struct nond_on_fs **)p2;
-
-       return cache_name_compare(e1->name, e1->len,
-                                 e2->name, e2->len);
-}
 
 /*
  * Match a pathspec against a filename. The first "len" characters
@@ -377,7 +71,7 @@ static int match(const char **spec, char *ps_matched,
        return 0;
 }
 
-static void show_dir_entry(const char *tag, struct nond_on_fs *ent)
+static void show_dir_entry(const char *tag, struct dir_entry *ent)
 {
        int len = prefix_len;
        int offset = prefix_offset;
@@ -393,14 +87,14 @@ static void show_dir_entry(const char *tag, struct nond_on_fs *ent)
        putchar(line_terminator);
 }
 
-static void show_other_files(void)
+static void show_other_files(struct dir_struct *dir)
 {
        int i;
-       for (i = 0; i < nr_dir; i++) {
+       for (i = 0; i < dir->nr; i++) {
                /* We should not have a matching entry, but we
                 * may have an unmerged entry for this path.
                 */
-               struct nond_on_fs *ent = dir[i];
+               struct dir_entry *ent = dir->entries[i];
                int pos = cache_name_pos(ent->name, ent->len);
                struct cache_entry *ce;
                if (0 <= pos)
@@ -416,11 +110,11 @@ static void show_other_files(void)
        }
 }
 
-static void show_killed_files(void)
+static void show_killed_files(struct dir_struct *dir)
 {
        int i;
-       for (i = 0; i < nr_dir; i++) {
-               struct nond_on_fs *ent = dir[i];
+       for (i = 0; i < dir->nr; i++) {
+               struct dir_entry *ent = dir->entries[i];
                char *cp, *sp;
                int pos, len, killed = 0;
 
@@ -461,7 +155,7 @@ static void show_killed_files(void)
                        }
                }
                if (killed)
-                       show_dir_entry(tag_killed, dir[i]);
+                       show_dir_entry(tag_killed, dir->entries[i]);
        }
 }
 
@@ -512,7 +206,7 @@ static void show_ce_entry(const char *tag, struct cache_entry *ce)
        }
 }
 
-static void show_files(void)
+static void show_files(struct dir_struct *dir)
 {
        int i;
 
@@ -521,39 +215,18 @@ static void show_files(void)
                const char *path = ".", *base = "";
                int baselen = prefix_len;
 
-               if (baselen) {
+               if (baselen)
                        path = base = prefix;
-                       if (exclude_per_dir) {
-                               char *p, *pp = xmalloc(baselen+1);
-                               memcpy(pp, prefix, baselen+1);
-                               p = pp;
-                               while (1) {
-                                       char save = *p;
-                                       *p = 0;
-                                       push_exclude_per_directory(pp, p-pp);
-                                       *p++ = save;
-                                       if (!save)
-                                               break;
-                                       p = strchr(p, '/');
-                                       if (p)
-                                               p++;
-                                       else
-                                               p = pp + baselen;
-                               }
-                               free(pp);
-                       }
-               }
-               read_directory(path, base, baselen);
-               qsort(dir, nr_dir, sizeof(struct nond_on_fs *), cmp_name);
+               read_directory(dir, path, base, baselen);
                if (show_others)
-                       show_other_files();
+                       show_other_files(dir);
                if (show_killed)
-                       show_killed_files();
+                       show_killed_files(dir);
        }
        if (show_cached | show_stage) {
                for (i = 0; i < active_nr; i++) {
                        struct cache_entry *ce = active_cache[i];
-                       if (excluded(ce->name) != show_ignored)
+                       if (excluded(dir, ce->name) != dir->show_ignored)
                                continue;
                        if (show_unmerged && !ce_stage(ce))
                                continue;
@@ -565,7 +238,7 @@ static void show_files(void)
                        struct cache_entry *ce = active_cache[i];
                        struct stat st;
                        int err;
-                       if (excluded(ce->name) != show_ignored)
+                       if (excluded(dir, ce->name) != dir->show_ignored)
                                continue;
                        err = lstat(ce->name, &st);
                        if (show_deleted && err)
@@ -652,7 +325,9 @@ int main(int argc, const char **argv)
 {
        int i;
        int exc_given = 0;
+       struct dir_struct dir;
 
+       memset(&dir, 0, sizeof(dir));
        prefix = setup_git_directory();
        if (prefix)
                prefix_offset = strlen(prefix);
@@ -697,7 +372,7 @@ int main(int argc, const char **argv)
                        continue;
                }
                if (!strcmp(arg, "-i") || !strcmp(arg, "--ignored")) {
-                       show_ignored = 1;
+                       dir.show_ignored = 1;
                        continue;
                }
                if (!strcmp(arg, "-s") || !strcmp(arg, "--stage")) {
@@ -709,11 +384,11 @@ int main(int argc, const char **argv)
                        continue;
                }
                if (!strcmp(arg, "--directory")) {
-                       show_other_directories = 1;
+                       dir.show_other_directories = 1;
                        continue;
                }
                if (!strcmp(arg, "--no-empty-directory")) {
-                       hide_empty_directories = 1;
+                       dir.hide_empty_directories = 1;
                        continue;
                }
                if (!strcmp(arg, "-u") || !strcmp(arg, "--unmerged")) {
@@ -726,27 +401,27 @@ int main(int argc, const char **argv)
                }
                if (!strcmp(arg, "-x") && i+1 < argc) {
                        exc_given = 1;
-                       add_exclude(argv[++i], "", 0, &exclude_list[EXC_CMDL]);
+                       add_exclude(argv[++i], "", 0, &dir.exclude_list[EXC_CMDL]);
                        continue;
                }
                if (!strncmp(arg, "--exclude=", 10)) {
                        exc_given = 1;
-                       add_exclude(arg+10, "", 0, &exclude_list[EXC_CMDL]);
+                       add_exclude(arg+10, "", 0, &dir.exclude_list[EXC_CMDL]);
                        continue;
                }
                if (!strcmp(arg, "-X") && i+1 < argc) {
                        exc_given = 1;
-                       add_excludes_from_file(argv[++i]);
+                       add_excludes_from_file(&dir, argv[++i]);
                        continue;
                }
                if (!strncmp(arg, "--exclude-from=", 15)) {
                        exc_given = 1;
-                       add_excludes_from_file(arg+15);
+                       add_excludes_from_file(&dir, arg+15);
                        continue;
                }
                if (!strncmp(arg, "--exclude-per-directory=", 24)) {
                        exc_given = 1;
-                       exclude_per_dir = arg + 24;
+                       dir.exclude_per_dir = arg + 24;
                        continue;
                }
                if (!strcmp(arg, "--full-name")) {
@@ -788,7 +463,7 @@ int main(int argc, const char **argv)
                ps_matched = xcalloc(1, num);
        }
 
-       if (show_ignored && !exc_given) {
+       if (dir.show_ignored && !exc_given) {
                fprintf(stderr, "%s: --ignored needs some exclude pattern\n",
                        argv[0]);
                exit(1);
@@ -802,7 +477,7 @@ int main(int argc, const char **argv)
        read_cache();
        if (prefix)
                prune_cache();
-       show_files();
+       show_files(&dir);
 
        if (ps_matched) {
                /* We need to make sure all pathspec matched otherwise
index f0dc06e..4856ca0 100644 (file)
@@ -82,8 +82,9 @@ static struct commit *interesting(struct commit_list *list)
  * commit B.
  *
  *
- * Another pathological example how this thing can fail to mark an ancestor
- * of a merge base as UNINTERESTING without the postprocessing phase.
+ * Another pathological example how this thing used to fail to mark an
+ * ancestor of a merge base as UNINTERESTING before we introduced the
+ * postprocessing phase (mark_reachable_commits).
  *
  *               2
  *               H
@@ -118,7 +119,9 @@ static struct commit *interesting(struct commit_list *list)
  *      D7                     2 3 7 7 3 2 1 2
  *      E7                     2 3 7 7 7 2 1 2
  *
- * and we end up showing E as an interesting merge base.
+ * and we ended up showing E as an interesting merge base.
+ * The postprocessing phase re-injects C and continues traversal
+ * to contaminate D and E.
  */
 
 static int show_all = 0;
index 5466b15..77284cf 100644 (file)
@@ -10,7 +10,6 @@
 #include "tree-walk.h"
 #include <sys/time.h>
 #include <signal.h>
-#include <stdint.h>
 
 static const char pack_usage[] = "git-pack-objects [-q] [--no-reuse-delta] [--non-empty] [--local] [--incremental] [--window=N] [--depth=N] {--stdout | base-name} < object-list";
 
@@ -157,7 +156,7 @@ static void prepare_pack_revindex(struct pack_revindex *rix)
 
        rix->revindex = xmalloc(sizeof(unsigned long) * (num_ent + 1));
        for (i = 0; i < num_ent; i++) {
-               uint32_t hl = *((uint32_t *)(index + 24 * i));
+               unsigned int hl = *((unsigned int *)(index + 24 * i));
                rix->revindex[i] = ntohl(hl);
        }
        /* This knows the pack format -- the 20-byte trailer
@@ -1037,10 +1036,13 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,
        if (src_entry->depth >= max_depth)
                return 0;
 
-       /* Now some size filtering euristics. */
+       /* Now some size filtering heuristics. */
        size = trg_entry->size;
-       max_size = size / 2 - 20;
-       if (trg_entry->delta)
+       max_size = size/2 - 20;
+       max_size = max_size * (max_depth - src_entry->depth) / max_depth;
+       if (max_size == 0)
+               return 0;
+       if (trg_entry->delta && trg_entry->delta_size <= max_size)
                max_size = trg_entry->delta_size-1;
        src_size = src_entry->size;
        sizediff = src_size < size ? size - src_size : 0;
@@ -1105,17 +1107,14 @@ static void find_deltas(struct object_entry **list, int window, int depth)
 
                if (entry->size < 50)
                        continue;
-               if (n->index)
-                       free_delta_index(n->index);
+               free_delta_index(n->index);
+               n->index = NULL;
                free(n->data);
                n->entry = entry;
                n->data = read_sha1_file(entry->sha1, type, &size);
                if (size != entry->size)
                        die("object %s inconsistent object length (%lu vs %lu)",
                            sha1_to_hex(entry->sha1), size, entry->size);
-               n->index = create_delta_index(n->data, size);
-               if (!n->index)
-                       die("out of memory");
 
                j = window;
                while (--j > 0) {
@@ -1129,15 +1128,17 @@ static void find_deltas(struct object_entry **list, int window, int depth)
                        if (try_delta(n, m, m->index, depth) < 0)
                                break;
                }
-#if 0
                /* if we made n a delta, and if n is already at max
                 * depth, leaving it in the window is pointless.  we
                 * should evict it first.
-                * ... in theory only; somehow this makes things worse.
                 */
                if (entry->delta && depth <= entry->depth)
                        continue;
-#endif
+
+               n->index = create_delta_index(n->data, size);
+               if (!n->index)
+                       die("out of memory");
+
                idx++;
                if (idx >= window)
                        idx = 0;
@@ -1147,8 +1148,7 @@ static void find_deltas(struct object_entry **list, int window, int depth)
                fputc('\n', stderr);
 
        for (i = 0; i < window; ++i) {
-               if (array[i].index)
-                       free_delta_index(array[i].index);
+               free_delta_index(array[i].index);
                free(array[i].data);
        }
        free(array);
index a917ab0..ed0da38 100644 (file)
@@ -4,11 +4,26 @@
  * Copyright (C) Linus Torvalds, 2005
  */
 #include "cache.h"
+#include "cache-tree.h"
+
+/* Index extensions.
+ *
+ * The first letter should be 'A'..'Z' for extensions that are not
+ * necessary for a correct operation (i.e. optimization data).
+ * When new extensions are added that _needs_ to be understood in
+ * order to correctly interpret the index file, pick character that
+ * is outside the range, to cause the reader to abort.
+ */
+
+#define CACHE_EXT(s) ( (s[0]<<24)|(s[1]<<16)|(s[2]<<8)|(s[3]) )
+#define CACHE_EXT_TREE 0x54524545      /* "TREE" */
 
 struct cache_entry **active_cache = NULL;
 static time_t index_file_timestamp;
 unsigned int active_nr = 0, active_alloc = 0, active_cache_changed = 0;
 
+struct cache_tree *active_cache_tree = NULL;
+
 /*
  * This only updates the "non-critical" parts of the directory
  * cache, ie the parts that aren't tracked by GIT, and only used
@@ -513,6 +528,22 @@ static int verify_hdr(struct cache_header *hdr, unsigned long size)
        return 0;
 }
 
+static int read_index_extension(const char *ext, void *data, unsigned long sz)
+{
+       switch (CACHE_EXT(ext)) {
+       case CACHE_EXT_TREE:
+               active_cache_tree = cache_tree_read(data, sz);
+               break;
+       default:
+               if (*ext < 'A' || 'Z' < *ext)
+                       return error("index uses %.4s extension, which we do not understand",
+                                    ext);
+               fprintf(stderr, "ignoring %.4s extension\n", ext);
+               break;
+       }
+       return 0;
+}
+
 int read_cache(void)
 {
        int fd, i;
@@ -561,6 +592,22 @@ int read_cache(void)
                active_cache[i] = ce;
        }
        index_file_timestamp = st.st_mtime;
+       while (offset <= size - 20 - 8) {
+               /* After an array of active_nr index entries,
+                * there can be arbitrary number of extended
+                * sections, each of which is prefixed with
+                * extension name (4-byte) and section length
+                * in 4-byte network byte order.
+                */
+               unsigned long extsize;
+               memcpy(&extsize, map + offset + 4, 4);
+               extsize = ntohl(extsize);
+               if (read_index_extension(map + offset,
+                                        map + offset + 8, extsize) < 0)
+                       goto unmap;
+               offset += 8;
+               offset += extsize;
+       }
        return active_nr;
 
 unmap:
@@ -595,6 +642,17 @@ static int ce_write(SHA_CTX *context, int fd, void *data, unsigned int len)
        return 0;
 }
 
+static int write_index_ext_header(SHA_CTX *context, int fd,
+                                 unsigned long ext, unsigned long sz)
+{
+       ext = htonl(ext);
+       sz = htonl(sz);
+       if ((ce_write(context, fd, &ext, 4) < 0) ||
+           (ce_write(context, fd, &sz, 4) < 0))
+               return -1;
+       return 0;
+}
+
 static int ce_flush(SHA_CTX *context, int fd)
 {
        unsigned int left = write_buffer_len;
@@ -691,5 +749,19 @@ int write_cache(int newfd, struct cache_entry **cache, int entries)
                if (ce_write(&c, newfd, ce, ce_size(ce)) < 0)
                        return -1;
        }
+
+       /* Write extension data here */
+       if (active_cache_tree) {
+               unsigned long sz;
+               void *data = cache_tree_write(active_cache_tree, &sz);
+               if (data &&
+                   !write_index_ext_header(&c, newfd, CACHE_EXT_TREE, sz) &&
+                   !ce_write(&c, newfd, data, sz))
+                       ;
+               else {
+                       free(data);
+                       return -1;
+               }
+       }
        return ce_flush(&c, newfd);
 }
index e926e4c..a3ada55 100644 (file)
@@ -9,9 +9,11 @@
 
 #include "object.h"
 #include "tree.h"
+#include "cache-tree.h"
 #include <sys/time.h>
 #include <signal.h>
 
+static int reset = 0;
 static int merge = 0;
 static int update = 0;
 static int index_only = 0;
@@ -20,6 +22,7 @@ static int trivial_merges_only = 0;
 static int aggressive = 0;
 static int verbose_update = 0;
 static volatile int progress_update = 0;
+static const char *prefix = NULL;
 
 static int head_idx = -1;
 static int merge_size = 0;
@@ -368,7 +371,8 @@ static int unpack_trees(merge_fn_t fn)
                        posns[i] = ((struct tree *) posn->item)->entries;
                        posn = posn->next;
                }
-               if (unpack_trees_rec(posns, len, "", fn, &indpos))
+               if (unpack_trees_rec(posns, len, prefix ? prefix : "",
+                                    fn, &indpos))
                        return -1;
        }
 
@@ -407,7 +411,7 @@ static void verify_uptodate(struct cache_entry *ce)
 {
        struct stat st;
 
-       if (index_only)
+       if (index_only || reset)
                return;
 
        if (!lstat(ce->name, &st)) {
@@ -416,11 +420,36 @@ static void verify_uptodate(struct cache_entry *ce)
                        return;
                errno = 0;
        }
+       if (reset) {
+               ce->ce_flags |= htons(CE_UPDATE);
+               return;
+       }
        if (errno == ENOENT)
                return;
        die("Entry '%s' not uptodate. Cannot merge.", ce->name);
 }
 
+static void invalidate_ce_path(struct cache_entry *ce)
+{
+       if (ce)
+               cache_tree_invalidate_path(active_cache_tree, ce->name);
+}
+
+/*
+ * We do not want to remove or overwrite a working tree file that
+ * is not tracked.
+ */
+static void verify_absent(const char *path, const char *action)
+{
+       struct stat st;
+
+       if (index_only || reset || !update)
+               return;
+       if (!lstat(path, &st))
+               die("Untracked working tree file '%s' "
+                   "would be %s by merge.", path, action);
+}
+
 static int merged_entry(struct cache_entry *merge, struct cache_entry *old)
 {
        merge->ce_flags |= htons(CE_UPDATE);
@@ -436,8 +465,14 @@ static int merged_entry(struct cache_entry *merge, struct cache_entry *old)
                        *merge = *old;
                } else {
                        verify_uptodate(old);
+                       invalidate_ce_path(old);
                }
        }
+       else {
+               verify_absent(merge->name, "overwritten");
+               invalidate_ce_path(merge);
+       }
+
        merge->ce_flags &= ~htons(CE_STAGEMASK);
        add_cache_entry(merge, ADD_CACHE_OK_TO_ADD);
        return 1;
@@ -447,8 +482,11 @@ static int deleted_entry(struct cache_entry *ce, struct cache_entry *old)
 {
        if (old)
                verify_uptodate(old);
+       else
+               verify_absent(ce->name, "removed");
        ce->ce_mode = 0;
        add_cache_entry(ce, ADD_CACHE_OK_TO_ADD);
+       invalidate_ce_path(ce);
        return 1;
 }
 
@@ -482,6 +520,7 @@ static int threeway_merge(struct cache_entry **stages)
        int count;
        int head_match = 0;
        int remote_match = 0;
+       const char *path = NULL;
 
        int df_conflict_head = 0;
        int df_conflict_remote = 0;
@@ -493,8 +532,11 @@ static int threeway_merge(struct cache_entry **stages)
        for (i = 1; i < head_idx; i++) {
                if (!stages[i])
                        any_anc_missing = 1;
-               else
+               else {
+                       if (!path)
+                               path = stages[i]->name;
                        no_anc_exists = 0;
+               }
        }
 
        index = stages[0];
@@ -510,8 +552,15 @@ static int threeway_merge(struct cache_entry **stages)
                remote = NULL;
        }
 
+       if (!path && index)
+               path = index->name;
+       if (!path && head)
+               path = head->name;
+       if (!path && remote)
+               path = remote->name;
+
        /* First, if there's a #16 situation, note that to prevent #13
-        * and #14. 
+        * and #14.
         */
        if (!same(remote, head)) {
                for (i = 1; i < head_idx; i++) {
@@ -570,6 +619,8 @@ static int threeway_merge(struct cache_entry **stages)
                    (remote_deleted && head && head_match)) {
                        if (index)
                                return deleted_entry(index, index);
+                       else if (path)
+                               verify_absent(path, "removed");
                        return 0;
                }
                /*
@@ -587,6 +638,8 @@ static int threeway_merge(struct cache_entry **stages)
        if (index) {
                verify_uptodate(index);
        }
+       else if (path)
+               verify_absent(path, "overwritten");
 
        nontrivial_merge = 1;
 
@@ -669,6 +722,28 @@ static int twoway_merge(struct cache_entry **src)
 }
 
 /*
+ * Bind merge.
+ *
+ * Keep the index entries at stage0, collapse stage1 but make sure
+ * stage0 does not have anything there.
+ */
+static int bind_merge(struct cache_entry **src)
+{
+       struct cache_entry *old = src[0];
+       struct cache_entry *a = src[1];
+
+       if (merge_size != 1)
+               return error("Cannot do a bind merge of %d trees\n",
+                            merge_size);
+       if (a && old)
+               die("Entry '%s' overlaps.  Cannot bind.", a->name);
+       if (!a)
+               return keep_entry(old);
+       else
+               return merged_entry(a, NULL);
+}
+
+/*
  * One-way merge.
  *
  * The rule is:
@@ -683,12 +758,20 @@ static int oneway_merge(struct cache_entry **src)
                return error("Cannot do a oneway merge of %d trees",
                             merge_size);
 
-       if (!a)
-               return 0;
+       if (!a) {
+               invalidate_ce_path(old);
+               return deleted_entry(old, old);
+       }
        if (old && same(old, a)) {
+               if (reset) {
+                       struct stat st;
+                       if (lstat(old->name, &st) ||
+                           ce_match_stat(old, &st, 1))
+                               old->ce_flags |= htons(CE_UPDATE);
+               }
                return keep_entry(old);
        }
-       return merged_entry(a, NULL);
+       return merged_entry(a, old);
 }
 
 static int read_cache_unmerged(void)
@@ -703,6 +786,7 @@ static int read_cache_unmerged(void)
                struct cache_entry *ce = active_cache[i];
                if (ce_stage(ce)) {
                        deleted++;
+                       invalidate_ce_path(ce);
                        continue;
                }
                if (deleted)
@@ -713,13 +797,46 @@ static int read_cache_unmerged(void)
        return deleted;
 }
 
-static const char read_tree_usage[] = "git-read-tree (<sha> | -m [--aggressive] [-u | -i] <sha1> [<sha2> [<sha3>]])";
+static void prime_cache_tree_rec(struct cache_tree *it, struct tree *tree)
+{
+       struct tree_entry_list *ent;
+       int cnt;
+       
+       memcpy(it->sha1, tree->object.sha1, 20);
+       for (cnt = 0, ent = tree->entries; ent; ent = ent->next) {
+               if (!ent->directory)
+                       cnt++;
+               else {
+                       struct cache_tree_sub *sub;
+                       struct tree *subtree = (struct tree *)ent->item.tree;
+                       if (!subtree->object.parsed)
+                               parse_tree(subtree);
+                       sub = cache_tree_sub(it, ent->name);
+                       sub->cache_tree = cache_tree();
+                       prime_cache_tree_rec(sub->cache_tree, subtree);
+                       cnt += sub->cache_tree->entry_count;
+               }
+       }
+       it->entry_count = cnt;
+}
+
+static void prime_cache_tree(void)
+{
+       struct tree *tree = (struct tree *)trees->item;
+       if (!tree)
+               return;
+       active_cache_tree = cache_tree();
+       prime_cache_tree_rec(active_cache_tree, tree);
+
+}
+
+static const char read_tree_usage[] = "git-read-tree (<sha> | [[-m [--aggressive] | --reset | --prefix=<prefix>] [-u | -i]] <sha1> [<sha2> [<sha3>]])";
 
 static struct cache_file cache_file;
 
 int main(int argc, char **argv)
 {
-       int i, newfd, reset, stage = 0;
+       int i, newfd, stage = 0;
        unsigned char sha1[20];
        merge_fn_t fn = NULL;
 
@@ -758,9 +875,24 @@ int main(int argc, char **argv)
                        continue;
                }
 
+               /* "--prefix=<subdirectory>/" means keep the current index
+                *  entries and put the entries from the tree under the
+                * given subdirectory.
+                */
+               if (!strncmp(arg, "--prefix=", 9)) {
+                       if (stage || merge || prefix)
+                               usage(read_tree_usage);
+                       prefix = arg + 9;
+                       merge = 1;
+                       stage = 1;
+                       if (read_cache_unmerged())
+                               die("you need to resolve your current index first");
+                       continue;
+               }
+
                /* This differs from "-m" in that we'll silently ignore unmerged entries */
                if (!strcmp(arg, "--reset")) {
-                       if (stage || merge)
+                       if (stage || merge || prefix)
                                usage(read_tree_usage);
                        reset = 1;
                        merge = 1;
@@ -781,7 +913,7 @@ int main(int argc, char **argv)
 
                /* "-m" stands for "merge", meaning we start in stage 1 */
                if (!strcmp(arg, "-m")) {
-                       if (stage || merge)
+                       if (stage || merge || prefix)
                                usage(read_tree_usage);
                        if (read_cache_unmerged())
                                die("you need to resolve your current index first");
@@ -803,21 +935,39 @@ int main(int argc, char **argv)
        if ((update||index_only) && !merge)
                usage(read_tree_usage);
 
+       if (prefix) {
+               int pfxlen = strlen(prefix);
+               int pos;
+               if (prefix[pfxlen-1] != '/')
+                       die("prefix must end with /");
+               if (stage != 2)
+                       die("binding merge takes only one tree");
+               pos = cache_name_pos(prefix, pfxlen);
+               if (0 <= pos)
+                       die("corrupt index file");
+               pos = -pos-1;
+               if (pos < active_nr &&
+                   !strncmp(active_cache[pos]->name, prefix, pfxlen))
+                       die("subdirectory '%s' already exists.", prefix);
+               pos = cache_name_pos(prefix, pfxlen-1);
+               if (0 <= pos)
+                       die("file '%.*s' already exists.", pfxlen-1, prefix);
+       }
+
        if (merge) {
                if (stage < 2)
                        die("just how do you expect me to merge %d trees?", stage-1);
                switch (stage - 1) {
                case 1:
-                       fn = oneway_merge;
+                       fn = prefix ? bind_merge : oneway_merge;
                        break;
                case 2:
                        fn = twoway_merge;
                        break;
                case 3:
-                       fn = threeway_merge;
-                       break;
                default:
                        fn = threeway_merge;
+                       cache_tree_free(&active_cache_tree);
                        break;
                }
 
@@ -828,6 +978,18 @@ int main(int argc, char **argv)
        }
 
        unpack_trees(fn);
+
+       /*
+        * When reading only one tree (either the most basic form,
+        * "-m ent" or "--reset ent" form), we can obtain a fully
+        * valid cache-tree because the index must match exactly
+        * what came from the tree.
+        */
+       if (trees && trees->item && (!merge || (stage == 2))) {
+               cache_tree_free(&active_cache_tree);
+               prime_cache_tree();
+       }
+
        if (write_cache(newfd, active_cache, active_nr) ||
            commit_index_file(&cache_file))
                die("unable to write new index file");
diff --git a/refs.c b/refs.c
index 275b914..6c91ae6 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -114,7 +114,7 @@ int read_ref(const char *filename, unsigned char *sha1)
        return -1;
 }
 
-static int do_for_each_ref(const char *base, int (*fn)(const char *path, const unsigned char *sha1))
+static int do_for_each_ref(const char *base, int (*fn)(const char *path, const unsigned char *sha1), int trim)
 {
        int retval = 0;
        DIR *dir = opendir(git_path("%s", base));
@@ -146,7 +146,7 @@ static int do_for_each_ref(const char *base, int (*fn)(const char *path, const u
                        if (stat(git_path("%s", path), &st) < 0)
                                continue;
                        if (S_ISDIR(st.st_mode)) {
-                               retval = do_for_each_ref(path, fn);
+                               retval = do_for_each_ref(path, fn, trim);
                                if (retval)
                                        break;
                                continue;
@@ -160,7 +160,7 @@ static int do_for_each_ref(const char *base, int (*fn)(const char *path, const u
                                      "commit object!", path);
                                continue;
                        }
-                       retval = fn(path, sha1);
+                       retval = fn(path + trim, sha1);
                        if (retval)
                                break;
                }
@@ -180,7 +180,22 @@ int head_ref(int (*fn)(const char *path, const unsigned char *sha1))
 
 int for_each_ref(int (*fn)(const char *path, const unsigned char *sha1))
 {
-       return do_for_each_ref("refs", fn);
+       return do_for_each_ref("refs", fn, 0);
+}
+
+int for_each_tag_ref(int (*fn)(const char *path, const unsigned char *sha1))
+{
+       return do_for_each_ref("refs/tags", fn, 10);
+}
+
+int for_each_branch_ref(int (*fn)(const char *path, const unsigned char *sha1))
+{
+       return do_for_each_ref("refs/heads", fn, 11);
+}
+
+int for_each_remote_ref(int (*fn)(const char *path, const unsigned char *sha1))
+{
+       return do_for_each_ref("refs/remotes", fn, 13);
 }
 
 static char *ref_file_name(const char *ref)
diff --git a/refs.h b/refs.h
index 2625596..fa816c1 100644 (file)
--- a/refs.h
+++ b/refs.h
@@ -7,6 +7,9 @@
  */
 extern int head_ref(int (*fn)(const char *path, const unsigned char *sha1));
 extern int for_each_ref(int (*fn)(const char *path, const unsigned char *sha1));
+extern int for_each_tag_ref(int (*fn)(const char *path, const unsigned char *sha1));
+extern int for_each_branch_ref(int (*fn)(const char *path, const unsigned char *sha1));
+extern int for_each_remote_ref(int (*fn)(const char *path, const unsigned char *sha1));
 
 /** Reads the refs file specified into sha1 **/
 extern int get_ref_sha1(const char *ref, unsigned char *sha1);
index 63eda1b..127afd7 100644 (file)
@@ -64,12 +64,13 @@ static int show_config(const char* key_, const char* value_)
 
 static int get_value(const char* key_, const char* regex_)
 {
-       int i;
+       char *tl;
 
-       key = malloc(strlen(key_)+1);
-       for (i = 0; key_[i]; i++)
-               key[i] = tolower(key_[i]);
-       key[i] = 0;
+       key = strdup(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) {
                key_regexp = (regex_t*)malloc(sizeof(regex_t));
index 8b0ec38..235ae4c 100644 (file)
@@ -84,7 +84,7 @@ static void show_commit(struct commit *commit)
                static char pretty_header[16384];
                pretty_print_commit(revs.commit_format, commit, ~0,
                                    pretty_header, sizeof(pretty_header),
-                                   revs.abbrev);
+                                   revs.abbrev, NULL);
                printf("%s%c", pretty_header, hdr_termination);
        }
        fflush(stdout);
index 62e16af..4e2d9fb 100644 (file)
@@ -36,6 +36,7 @@ static int is_rev_argument(const char *arg)
                "--all",
                "--bisect",
                "--dense",
+               "--branches",
                "--header",
                "--max-age=",
                "--max-count=",
@@ -45,7 +46,9 @@ static int is_rev_argument(const char *arg)
                "--objects-edge",
                "--parents",
                "--pretty",
+               "--remotes",
                "--sparse",
+               "--tags",
                "--topo-order",
                "--date-order",
                "--unpacked",
@@ -165,7 +168,7 @@ int main(int argc, char **argv)
        int i, as_is = 0, verify = 0;
        unsigned char sha1[20];
        const char *prefix = setup_git_directory();
-       
+
        git_config(git_default_config);
 
        for (i = 1; i < argc; i++) {
@@ -255,6 +258,18 @@ int main(int argc, char **argv)
                                for_each_ref(show_reference);
                                continue;
                        }
+                       if (!strcmp(arg, "--branches")) {
+                               for_each_branch_ref(show_reference);
+                               continue;
+                       }
+                       if (!strcmp(arg, "--tags")) {
+                               for_each_tag_ref(show_reference);
+                               continue;
+                       }
+                       if (!strcmp(arg, "--remotes")) {
+                               for_each_remote_ref(show_reference);
+                               continue;
+                       }
                        if (!strcmp(arg, "--show-prefix")) {
                                if (prefix)
                                        puts(prefix);
index 48d7b4c..62759f7 100644 (file)
@@ -58,6 +58,7 @@ struct rev_info {
        unsigned int    abbrev;
        enum cmit_fmt   commit_format;
        struct log_info *loginfo;
+       int             nr, total;
 
        /* special limits */
        int max_count;
index 3372ebc..2230010 100644 (file)
@@ -13,7 +13,6 @@
 #include "commit.h"
 #include "tag.h"
 #include "tree.h"
-#include <stdint.h>
 
 #ifndef O_NOATIME
 #if defined(__linux__) && (defined(__i386__) || defined(__PPC__))
@@ -1162,7 +1161,7 @@ int find_pack_entry_one(const unsigned char *sha1,
                int mi = (lo + hi) / 2;
                int cmp = memcmp(index + 24 * mi + 4, sha1, 20);
                if (!cmp) {
-                       e->offset = ntohl(*((uint32_t *)(index + 24 * mi)));
+                       e->offset = ntohl(*((unsigned int *)(index + 24 * mi)));
                        memcpy(e->sha1, sha1, 20);
                        e->p = p;
                        return 1;
index 268c57b..bbe26c2 100644 (file)
@@ -259,7 +259,7 @@ static void show_one_commit(struct commit *commit, int no_name)
        struct commit_name *name = commit->object.util;
        if (commit->object.parsed)
                pretty_print_commit(CMIT_FMT_ONELINE, commit, ~0,
-                                   pretty, sizeof(pretty), 0);
+                                   pretty, sizeof(pretty), 0, NULL);
        else
                strcpy(pretty, "(unavailable)");
        if (!strncmp(pretty, "[PATCH] ", 8))
index cf33989..2c9bbb5 100755 (executable)
@@ -195,6 +195,20 @@ test_expect_success \
     'git-ls-tree -r output for a known tree.' \
     'diff current expected'
 
+test_expect_success \
+    'writing partial tree out with git-write-tree --prefix.' \
+    'ptree=$(git-write-tree --prefix=path3)'
+test_expect_success \
+    'validate object ID for a known tree.' \
+    'test "$ptree" = 21ae8269cacbe57ae09138dcc3a2887f904d02b3'
+
+test_expect_success \
+    'writing partial tree out with git-write-tree --prefix.' \
+    'ptree=$(git-write-tree --prefix=path3/subp3)'
+test_expect_success \
+    'validate object ID for a known tree.' \
+    'test "$ptree" = 3c5e5399f3a333eddecce7a9b9465b63f65f51e2'
+
 ################################################################
 rm .git/index
 test_expect_success \
index 4d175d8..8335a63 100755 (executable)
@@ -39,7 +39,6 @@ test_expect_success \
      echo nitfol >nitfol &&
      echo bozbar >bozbar &&
      echo rezrov >rezrov &&
-     echo yomin >yomin &&
      git-update-index --add nitfol bozbar rezrov &&
      treeH=`git-write-tree` &&
      echo treeH $treeH &&
@@ -56,7 +55,8 @@ test_expect_success \
 
 test_expect_success \
     '1, 2, 3 - no carry forward' \
-    'rm -f .git/index &&
+    'rm -f .git/index nitfol bozbar rezrov frotz &&
+     git-read-tree --reset -u $treeH &&
      git-read-tree -m -u $treeH $treeM &&
      git-ls-files --stage >1-3.out &&
      cmp M.out 1-3.out &&
@@ -66,11 +66,12 @@ test_expect_success \
      check_cache_at frotz clean &&
      check_cache_at nitfol clean'
 
-echo '+100644 X 0      yomin' >expected
-
 test_expect_success \
     '4 - carry forward local addition.' \
-    'rm -f .git/index &&
+    'rm -f .git/index nitfol bozbar rezrov frotz &&
+     git-read-tree --reset -u $treeH &&
+     echo "+100644 X 0 yomin" >expected &&
+     echo yomin >yomin &&
      git-update-index --add yomin &&
      git-read-tree -m -u $treeH $treeM &&
      git-ls-files --stage >4.out || return 1
@@ -85,7 +86,9 @@ test_expect_success \
 
 test_expect_success \
     '5 - carry forward local addition.' \
-    'rm -f .git/index &&
+    'rm -f .git/index nitfol bozbar rezrov frotz &&
+     git-read-tree --reset -u $treeH &&
+     git-read-tree -m -u $treeH &&
      echo yomin >yomin &&
      git-update-index --add yomin &&
      echo yomin yomin >yomin &&
@@ -103,7 +106,9 @@ test_expect_success \
 
 test_expect_success \
     '6 - local addition already has the same.' \
-    'rm -f .git/index &&
+    'rm -f .git/index nitfol bozbar rezrov frotz &&
+     git-read-tree --reset -u $treeH &&
+     echo frotz >frotz &&
      git-update-index --add frotz &&
      git-read-tree -m -u $treeH $treeM &&
      git-ls-files --stage >6.out &&
@@ -117,7 +122,8 @@ test_expect_success \
 
 test_expect_success \
     '7 - local addition already has the same.' \
-    'rm -f .git/index &&
+    'rm -f .git/index nitfol bozbar rezrov frotz &&
+     git-read-tree --reset -u $treeH &&
      echo frotz >frotz &&
      git-update-index --add frotz &&
      echo frotz frotz >frotz &&
@@ -134,14 +140,16 @@ test_expect_success \
 
 test_expect_success \
     '8 - conflicting addition.' \
-    'rm -f .git/index &&
+    'rm -f .git/index nitfol bozbar rezrov frotz &&
+     git-read-tree --reset -u $treeH &&
      echo frotz frotz >frotz &&
      git-update-index --add frotz &&
      if git-read-tree -m -u $treeH $treeM; then false; else :; fi'
 
 test_expect_success \
     '9 - conflicting addition.' \
-    'rm -f .git/index &&
+    'rm -f .git/index nitfol bozbar rezrov frotz &&
+     git-read-tree --reset -u $treeH &&
      echo frotz frotz >frotz &&
      git-update-index --add frotz &&
      echo frotz >frotz &&
@@ -149,7 +157,8 @@ test_expect_success \
 
 test_expect_success \
     '10 - path removed.' \
-    'rm -f .git/index &&
+    'rm -f .git/index nitfol bozbar rezrov frotz &&
+     git-read-tree --reset -u $treeH &&
      echo rezrov >rezrov &&
      git-update-index --add rezrov &&
      git-read-tree -m -u $treeH $treeM &&
@@ -160,7 +169,8 @@ test_expect_success \
 
 test_expect_success \
     '11 - dirty path removed.' \
-    'rm -f .git/index &&
+    'rm -f .git/index nitfol bozbar rezrov frotz &&
+     git-read-tree --reset -u $treeH &&
      echo rezrov >rezrov &&
      git-update-index --add rezrov &&
      echo rezrov rezrov >rezrov &&
@@ -168,14 +178,16 @@ test_expect_success \
 
 test_expect_success \
     '12 - unmatching local changes being removed.' \
-    'rm -f .git/index &&
+    'rm -f .git/index nitfol bozbar rezrov frotz &&
+     git-read-tree --reset -u $treeH &&
      echo rezrov rezrov >rezrov &&
      git-update-index --add rezrov &&
      if git-read-tree -m -u $treeH $treeM; then false; else :; fi'
 
 test_expect_success \
     '13 - unmatching local changes being removed.' \
-    'rm -f .git/index &&
+    'rm -f .git/index nitfol bozbar rezrov frotz &&
+     git-read-tree --reset -u $treeH &&
      echo rezrov rezrov >rezrov &&
      git-update-index --add rezrov &&
      echo rezrov >rezrov &&
@@ -188,7 +200,8 @@ EOF
 
 test_expect_success \
     '14 - unchanged in two heads.' \
-    'rm -f .git/index &&
+    'rm -f .git/index nitfol bozbar rezrov frotz &&
+     git-read-tree --reset -u $treeH &&
      echo nitfol nitfol >nitfol &&
      git-update-index --add nitfol &&
      git-read-tree -m -u $treeH $treeM &&
@@ -207,7 +220,8 @@ test_expect_success \
 
 test_expect_success \
     '15 - unchanged in two heads.' \
-    'rm -f .git/index &&
+    'rm -f .git/index nitfol bozbar rezrov frotz &&
+     git-read-tree --reset -u $treeH &&
      echo nitfol nitfol >nitfol &&
      git-update-index --add nitfol &&
      echo nitfol nitfol nitfol >nitfol &&
@@ -227,14 +241,16 @@ test_expect_success \
 
 test_expect_success \
     '16 - conflicting local change.' \
-    'rm -f .git/index &&
+    'rm -f .git/index nitfol bozbar rezrov frotz &&
+     git-read-tree --reset -u $treeH &&
      echo bozbar bozbar >bozbar &&
      git-update-index --add bozbar &&
      if git-read-tree -m -u $treeH $treeM; then false; else :; fi'
 
 test_expect_success \
     '17 - conflicting local change.' \
-    'rm -f .git/index &&
+    'rm -f .git/index nitfol bozbar rezrov frotz &&
+     git-read-tree --reset -u $treeH &&
      echo bozbar bozbar >bozbar &&
      git-update-index --add bozbar &&
      echo bozbar bozbar bozbar >bozbar &&
@@ -242,7 +258,8 @@ test_expect_success \
 
 test_expect_success \
     '18 - local change already having a good result.' \
-    'rm -f .git/index &&
+    'rm -f .git/index nitfol bozbar rezrov frotz &&
+     git-read-tree --reset -u $treeH &&
      echo gnusto >bozbar &&
      git-update-index --add bozbar &&
      git-read-tree -m -u $treeH $treeM &&
@@ -254,7 +271,8 @@ test_expect_success \
 
 test_expect_success \
     '19 - local change already having a good result, further modified.' \
-    'rm -f .git/index &&
+    'rm -f .git/index nitfol bozbar rezrov frotz &&
+     git-read-tree --reset -u $treeH &&
      echo gnusto >bozbar &&
      git-update-index --add bozbar &&
      echo gnusto gnusto >bozbar &&
@@ -273,7 +291,8 @@ test_expect_success \
 
 test_expect_success \
     '20 - no local change, use new tree.' \
-    'rm -f .git/index &&
+    'rm -f .git/index nitfol bozbar rezrov frotz &&
+     git-read-tree --reset -u $treeH &&
      echo bozbar >bozbar &&
      git-update-index --add bozbar &&
      git-read-tree -m -u $treeH $treeM &&
@@ -285,7 +304,8 @@ test_expect_success \
 
 test_expect_success \
     '21 - no local change, dirty cache.' \
-    'rm -f .git/index &&
+    'rm -f .git/index nitfol bozbar rezrov frotz &&
+     git-read-tree --reset -u $treeH &&
      echo bozbar >bozbar &&
      git-update-index --add bozbar &&
      echo gnusto gnusto >bozbar &&
@@ -294,7 +314,7 @@ test_expect_success \
 # Also make sure we did not break DF vs DF/DF case.
 test_expect_success \
     'DF vs DF/DF case setup.' \
-    'rm -f .git/index &&
+    'rm -f .git/index
      echo DF >DF &&
      git-update-index --add DF &&
      treeDF=`git-write-tree` &&
index 7090ea9..8260d57 100755 (executable)
@@ -229,7 +229,7 @@ test_expect_failure 'invalid key' 'git-repo-config inval.2key blabla'
 test_expect_success 'correct key' 'git-repo-config 123456.a123 987'
 
 test_expect_success 'hierarchical section' \
-       'git-repo-config 1.2.3.alpha beta'
+       'git-repo-config Version.1.2.3eX.Alpha beta'
 
 cat > expect << EOF
 [beta] ; silly comment # another comment
@@ -241,8 +241,8 @@ noIndent= sillyValue ; 'nother silly comment
        NoNewLine = wow2 for me
 [123456]
        a123 = 987
-[1.2.3]
-       alpha = beta
+[Version "1.2.3eX"]
+       Alpha = beta
 EOF
 
 test_expect_success 'hierarchical section value' 'cmp .git/config expect'
@@ -251,7 +251,7 @@ cat > expect << EOF
 beta.noindent=sillyValue
 nextsection.nonewline=wow2 for me
 123456.a123=987
-1.2.3.alpha=beta
+version.1.2.3eX.alpha=beta
 EOF
 
 test_expect_success 'working --list' \
index b141f89..e83bbee 100755 (executable)
@@ -30,6 +30,7 @@ test_expect_success \
      git-commit -m "Add C." &&
 
      git-checkout -f master &&
+     rm -f B C &&
 
      echo Third >> A &&
      git-update-index A &&
index 769274a..56eda63 100755 (executable)
@@ -191,7 +191,7 @@ test_expect_success \
     'rm -fr Z [A-Z][A-Z] &&
      git-read-tree $tree_A &&
      git-checkout-index -f -a &&
-     git-read-tree -m $tree_O || return 1
+     git-read-tree --reset $tree_O || return 1
      git-update-index --refresh >/dev/null ;# this can exit non-zero
      git-diff-files >.test-a &&
      cmp_diff_files_output .test-a .test-recursive-OA'
@@ -201,7 +201,7 @@ test_expect_success \
     'rm -fr Z [A-Z][A-Z] &&
      git-read-tree $tree_B &&
      git-checkout-index -f -a &&
-     git-read-tree -m $tree_O || return 1
+     git-read-tree --reset $tree_O || return 1
      git-update-index --refresh >/dev/null ;# this can exit non-zero
      git-diff-files >.test-a &&
      cmp_diff_files_output .test-a .test-recursive-OB'
@@ -211,7 +211,7 @@ test_expect_success \
     'rm -fr Z [A-Z][A-Z] &&
      git-read-tree $tree_B &&
      git-checkout-index -f -a &&
-     git-read-tree -m $tree_A || return 1
+     git-read-tree --reset $tree_A || return 1
      git-update-index --refresh >/dev/null ;# this can exit non-zero
      git-diff-files >.test-a &&
      cmp_diff_files_output .test-a .test-recursive-AB'
index a2d24b5..5ac2564 100755 (executable)
@@ -111,6 +111,7 @@ test_expect_success 'pull renaming branch into unrenaming one' \
 
 test_expect_success 'pull renaming branch into another renaming one' \
 '
+       rm -f B
        git reset --hard
        git checkout red
        git pull . white && {
index 3d7e02d..f6b09a4 100644 (file)
@@ -6,6 +6,7 @@
 #include "cache.h"
 #include "strbuf.h"
 #include "quote.h"
+#include "cache-tree.h"
 #include "tree-walk.h"
 
 /*
@@ -71,6 +72,7 @@ static int mark_valid(const char *path)
                        active_cache[pos]->ce_flags &= ~htons(CE_VALID);
                        break;
                }
+               cache_tree_invalidate_path(active_cache_tree, path);
                active_cache_changed = 1;
                return 0;
        }
@@ -84,6 +86,12 @@ static int add_file_to_cache(const char *path)
        struct stat st;
 
        status = lstat(path, &st);
+
+       /* We probably want to do this in remove_file_from_cache() and
+        * add_cache_entry() instead...
+        */
+       cache_tree_invalidate_path(active_cache_tree, path);
+
        if (status < 0 || S_ISDIR(st.st_mode)) {
                /* When we used to have "path" and now we want to add
                 * "path/file", we need a way to remove "path" before
@@ -326,6 +334,7 @@ static int add_cacheinfo(unsigned int mode, const unsigned char *sha1,
                return error("%s: cannot add to the index - missing --add option?",
                             path);
        report("add '%s'", path);
+       cache_tree_invalidate_path(active_cache_tree, path);
        return 0;
 }
 
@@ -350,6 +359,7 @@ static void chmod_path(int flip, const char *path)
        default:
                goto fail;
        }
+       cache_tree_invalidate_path(active_cache_tree, path);
        active_cache_changed = 1;
        report("chmod %cx '%s'", flip, path);
        return;
@@ -371,6 +381,7 @@ static void update_one(const char *path, const char *prefix, int prefix_length)
                        die("Unable to mark file %s", path);
                goto free_return;
        }
+       cache_tree_invalidate_path(active_cache_tree, path);
 
        if (force_remove) {
                if (remove_file_from_cache(p))
@@ -449,6 +460,7 @@ static void read_index_info(int line_termination)
                                free(path_name);
                        continue;
                }
+               cache_tree_invalidate_path(active_cache_tree, path_name);
 
                if (!mode) {
                        /* mode == 0 means there is no such path -- remove */
@@ -555,6 +567,7 @@ static int unresolve_one(const char *path)
                goto free_return;
        }
 
+       cache_tree_invalidate_path(active_cache_tree, path);
        remove_file_from_cache(path);
        if (add_cache_entry(ce_2, ADD_CACHE_OK_TO_ADD)) {
                error("%s: cannot add our version to the index.", path);
index dcad6e6..895e7a3 100644 (file)
  */
 #include "cache.h"
 #include "tree.h"
+#include "cache-tree.h"
 
 static int missing_ok = 0;
+static char *prefix = NULL;
 
-static int check_valid_sha1(unsigned char *sha1)
-{
-       int ret;
-
-       /* If we were anal, we'd check that the sha1 of the contents actually matches */
-       ret = has_sha1_file(sha1);
-       if (ret == 0)
-               perror(sha1_file_name(sha1));
-       return ret ? 0 : -1;
-}
-
-static int write_tree(struct cache_entry **cachep, int maxentries, const char *base, int baselen, unsigned char *returnsha1)
-{
-       unsigned char subdir_sha1[20];
-       unsigned long size, offset;
-       char *buffer;
-       int nr;
-
-       /* Guess at some random initial size */
-       size = 8192;
-       buffer = xmalloc(size);
-       offset = 0;
-
-       nr = 0;
-       while (nr < maxentries) {
-               struct cache_entry *ce = cachep[nr];
-               const char *pathname = ce->name, *filename, *dirname;
-               int pathlen = ce_namelen(ce), entrylen;
-               unsigned char *sha1;
-               unsigned int mode;
-
-               /* Did we hit the end of the directory? Return how many we wrote */
-               if (baselen >= pathlen || memcmp(base, pathname, baselen))
-                       break;
-
-               sha1 = ce->sha1;
-               mode = ntohl(ce->ce_mode);
-
-               /* Do we have _further_ subdirectories? */
-               filename = pathname + baselen;
-               dirname = strchr(filename, '/');
-               if (dirname) {
-                       int subdir_written;
-
-                       subdir_written = write_tree(cachep + nr, maxentries - nr, pathname, dirname-pathname+1, subdir_sha1);
-                       nr += subdir_written;
-
-                       /* Now we need to write out the directory entry into this tree.. */
-                       mode = S_IFDIR;
-                       pathlen = dirname - pathname;
-
-                       /* ..but the directory entry doesn't count towards the total count */
-                       nr--;
-                       sha1 = subdir_sha1;
-               }
+static const char write_tree_usage[] =
+"git-write-tree [--missing-ok] [--prefix=<prefix>/]";
 
-               if (!missing_ok && check_valid_sha1(sha1) < 0)
-                       exit(1);
-
-               entrylen = pathlen - baselen;
-               if (offset + entrylen + 100 > size) {
-                       size = alloc_nr(offset + entrylen + 100);
-                       buffer = xrealloc(buffer, size);
-               }
-               offset += sprintf(buffer + offset, "%o %.*s", mode, entrylen, filename);
-               buffer[offset++] = 0;
-               memcpy(buffer + offset, sha1, 20);
-               offset += 20;
-               nr++;
-       }
-
-       write_sha1_file(buffer, offset, tree_type, returnsha1);
-       free(buffer);
-       return nr;
-}
-
-static const char write_tree_usage[] = "git-write-tree [--missing-ok]";
+static struct cache_file cache_file;
 
 int main(int argc, char **argv)
 {
-       int i, funny;
-       int entries;
-       unsigned char sha1[20];
-       
+       int entries, was_valid, newfd;
+
        setup_git_directory();
 
+       newfd = hold_index_file_for_update(&cache_file, get_index_file());
        entries = read_cache();
-       if (argc == 2) {
-               if (!strcmp(argv[1], "--missing-ok"))
+
+       while (1 < argc) {
+               char *arg = argv[1];
+               if (!strcmp(arg, "--missing-ok"))
                        missing_ok = 1;
+               else if (!strncmp(arg, "--prefix=", 9))
+                       prefix = arg + 9;
                else
                        die(write_tree_usage);
+               argc--; argv++;
        }
-       
+
        if (argc > 2)
                die("too many options");
 
        if (entries < 0)
                die("git-write-tree: error reading cache");
 
-       /* Verify that the tree is merged */
-       funny = 0;
-       for (i = 0; i < entries; i++) {
-               struct cache_entry *ce = active_cache[i];
-               if (ce_stage(ce)) {
-                       if (10 < ++funny) {
-                               fprintf(stderr, "...\n");
-                               break;
-                       }
-                       fprintf(stderr, "%s: unmerged (%s)\n", ce->name, sha1_to_hex(ce->sha1));
+       if (!active_cache_tree)
+               active_cache_tree = cache_tree();
+
+       was_valid = cache_tree_fully_valid(active_cache_tree);
+       if (!was_valid) {
+               if (cache_tree_update(active_cache_tree,
+                                     active_cache, active_nr,
+                                     missing_ok, 0) < 0)
+                       die("git-write-tree: error building trees");
+               if (0 <= newfd) {
+                       if (!write_cache(newfd, active_cache, active_nr))
+                               commit_index_file(&cache_file);
                }
-       }
-       if (funny)
-               die("git-write-tree: not able to write tree");
-
-       /* Also verify that the cache does not have path and path/file
-        * at the same time.  At this point we know the cache has only
-        * stage 0 entries.
-        */
-       funny = 0;
-       for (i = 0; i < entries - 1; i++) {
-               /* path/file always comes after path because of the way
-                * the cache is sorted.  Also path can appear only once,
-                * which means conflicting one would immediately follow.
+               /* Not being able to write is fine -- we are only interested
+                * in updating the cache-tree part, and if the next caller
+                * ends up using the old index with unupdated cache-tree part
+                * it misses the work we did here, but that is just a
+                * performance penalty and not a big deal.
                 */
-               const char *this_name = active_cache[i]->name;
-               const char *next_name = active_cache[i+1]->name;
-               int this_len = strlen(this_name);
-               if (this_len < strlen(next_name) &&
-                   strncmp(this_name, next_name, this_len) == 0 &&
-                   next_name[this_len] == '/') {
-                       if (10 < ++funny) {
-                               fprintf(stderr, "...\n");
-                               break;
-                       }
-                       fprintf(stderr, "You have both %s and %s\n",
-                               this_name, next_name);
-               }
        }
-       if (funny)
-               die("git-write-tree: not able to write tree");
-
-       /* Ok, write it out */
-       if (write_tree(active_cache, entries, "", 0, sha1) != entries)
-               die("git-write-tree: internal error");
-       printf("%s\n", sha1_to_hex(sha1));
+       if (prefix) {
+               struct cache_tree *subtree =
+                       cache_tree_find(active_cache_tree, prefix);
+               printf("%s\n", sha1_to_hex(subtree->sha1));
+       }
+       else
+               printf("%s\n", sha1_to_hex(active_cache_tree->sha1));
        return 0;
 }