Merge branch 'lt/diff' into next
authorJunio C Hamano <junkio@cox.net>
Sun, 14 May 2006 23:39:14 +0000 (16:39 -0700)
committerJunio C Hamano <junkio@cox.net>
Sun, 14 May 2006 23:39:14 +0000 (16:39 -0700)
* lt/diff:
git diff: support "-U" and "--unified" options properly
include header to define uint32_t, necessary on Mac OS X

34 files changed:
.gitignore
Documentation/git-grep.txt
Documentation/git-read-tree.txt
Documentation/git-write-tree.txt
Makefile
apply.c
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
commit.c
commit.h
config.c
contrib/remotes2config.sh [new file with mode: 0644]
date.c
dump-cache-tree.c [new file with mode: 0644]
fsck-objects.c
git-parse-remote.sh
git.c
log-tree.c
read-cache.c
read-tree.c
repo-config.c
rev-list.c
revision.h
show-branch.c
t/t0000-basic.sh
t/t1300-repo-config.sh
update-index.c
write-tree.c
index b5959d63116aa1e3e900f43a137295b47376e1e0..7906909b306f80ed74d6e53bb8514a889057194c 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 d55456ae93f2596717cc2468af066dce8c11f2c6..74102b7944fd7153602ea5d9ae0969c150cd945f 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 844cfda8d23e216a090ef94c9b85c186f2d31399..1f21d95684a476a7e771ef166536415b5fe81a3c 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 77e12cb949201445ab33992f9ff155f647ce167c..c85fa89c30e25ed026d2e95a2b537798f878e50f 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 37fbe789d34e03fac4cc11de4f1eefb5f480d893..070c478bf291898060faf02ea86d416b765b39f1 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -205,7 +205,7 @@ 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 \
@@ -215,7 +215,8 @@ LIB_OBJS = \
        $(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
 
 GITLIBS = $(LIB_FILE) $(XDIFF_LIB)
 LIBS = $(GITLIBS) -lz
@@ -609,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 7c8146a7f317d9d1743e6a780eec823be72905a6..ea6fb4c8806203a0268261a4aaa4a07dcdefdca2 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"
@@ -1893,6 +1894,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);
        }
        unlink(patch->old_name);
 }
@@ -1989,8 +1991,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)
index d3ac581f291dae630478248133344e2dd6a80b06..71742aa10b3b8e2621eecd4be929946b0a308deb 100644 (file)
@@ -231,7 +231,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..fead356
--- /dev/null
@@ -0,0 +1,757 @@
+/*
+ * 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>
+
+/*
+ * 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 grep_cache(struct grep_opt *opt, const char **paths, int cached)
+{
+       int hit = 0;
+       int nr;
+       read_cache();
+
+       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 69f2911cb4739ba211ac692ded2aa0a6d31cb989..d5bbc1cc06931974de46078b5cac17e315177e40 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 bb63f0729a4a05e60ef3e609b971bdf57b60b06d..97e24644687d819fddf57beaaddf4bdda731bb82 100644 (file)
--- a/builtin.h
+++ b/builtin.h
@@ -20,8 +20,10 @@ 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);
 
 #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 4b7a4392531dd4064694cd346fef902a2b1930e9..b1300cd989437e4631d6e4a58b1c32941b5c3b6b 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 cc3a745c149b38ae250ac5656bb4a4843a95f6a4..9876af6fd60a81d871e6be30060cc93d0e0259c7 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 2717dd81c346d89bf5d6727e3aa1f5b65ff39aca..93b3903ea78fac0b5a40074ec64d0c98bb8cfa94 100644 (file)
--- a/commit.c
+++ b/commit.c
@@ -36,6 +36,8 @@ enum cmit_fmt get_commit_format(const char *arg)
                return CMIT_FMT_FULL;
        if (!strcmp(arg, "=fuller"))
                return CMIT_FMT_FULLER;
+       if (!strcmp(arg, "=email"))
+               return CMIT_FMT_EMAIL;
        if (!strcmp(arg, "=oneline"))
                return CMIT_FMT_ONELINE;
        die("invalid --pretty format");
@@ -428,6 +430,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 +441,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 +455,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 +469,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 +489,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 +522,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 +560,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 de142afe733a6ecd3bcec2abd9f71a358b4ae09d..8d7514cd0012377195ef8a5022e98e887fe6a2e1 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 0f518c957cda48bb4fdc3aa7b472bb8a1c26a3bf..0248c6d8a58eedff72352022404e765d31adc5de 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 034d7228bfefff9ba94ed3fd19fe751f924d7a55..365dc3b14e47826dd7becf19a76e29bfaa6b2eb5 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/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 59b25904cb0f6e87594e2bad20db3ee2aa726aef..1922b6d84c51588b22553141f608c9a07182560b 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 c9b899e3d73152a92523ed460f4d90fdab3d6f33..187f0883c9136772677088ddf61228291d4b41d1 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
diff --git a/git.c b/git.c
index 49ba5189d923493aaed40a6a0fccc2ead040b06a..84803a62e6c4dc6b64aaf5d1fda9991be535048a 100644 (file)
--- a/git.c
+++ b/git.c
@@ -47,8 +47,10 @@ 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 },
        };
        int i;
 
index b90ba6762ad0aa469a19c42cf8d53f7a79db02d5..526d578e98eef5a099be43bf39a300b33ac1991c 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 a917ab0cfe1547d1070f2567a41295c96f97b249..ed0da38e07cdb3d0a05a12bdebf10d833e53bd08 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 e926e4c880363469c5abf766127acc466fb5e438..c25385d79f605c74ae9e9cde06225f0a4c834f00 100644 (file)
@@ -9,6 +9,7 @@
 
 #include "object.h"
 #include "tree.h"
+#include "cache-tree.h"
 #include <sys/time.h>
 #include <signal.h>
 
@@ -20,6 +21,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 +370,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;
        }
 
@@ -421,6 +424,12 @@ static void verify_uptodate(struct cache_entry *ce)
        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);
+}
+
 static int merged_entry(struct cache_entry *merge, struct cache_entry *old)
 {
        merge->ce_flags |= htons(CE_UPDATE);
@@ -436,8 +445,11 @@ static int merged_entry(struct cache_entry *merge, struct cache_entry *old)
                        *merge = *old;
                } else {
                        verify_uptodate(old);
+                       invalidate_ce_path(old);
                }
        }
+       else
+               invalidate_ce_path(merge);
        merge->ce_flags &= ~htons(CE_STAGEMASK);
        add_cache_entry(merge, ADD_CACHE_OK_TO_ADD);
        return 1;
@@ -449,6 +461,7 @@ static int deleted_entry(struct cache_entry *ce, struct cache_entry *old)
                verify_uptodate(old);
        ce->ce_mode = 0;
        add_cache_entry(ce, ADD_CACHE_OK_TO_ADD);
+       invalidate_ce_path(ce);
        return 1;
 }
 
@@ -668,6 +681,28 @@ static int twoway_merge(struct cache_entry **src)
                return deleted_entry(oldtree, current);
 }
 
+/*
+ * Bind merge.
+ *
+ * Keep the index entries at stage0, collapse stage1 but make sure
+ * stage0 does not have anything in prefix.
+ */
+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)
+               return merged_entry(old, NULL);
+       if (old)
+               die("Entry '%s' overlaps.  Cannot bind.", a->name);
+
+       return merged_entry(a, NULL);
+}
+
 /*
  * One-way merge.
  *
@@ -683,8 +718,10 @@ static int oneway_merge(struct cache_entry **src)
                return error("Cannot do a oneway merge of %d trees",
                             merge_size);
 
-       if (!a)
+       if (!a) {
+               invalidate_ce_path(old);
                return 0;
+       }
        if (old && same(old, a)) {
                return keep_entry(old);
        }
@@ -703,6 +740,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,7 +751,40 @@ 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;
 
@@ -758,9 +829,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 +867,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 +889,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 +932,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");
index 63eda1bb783fcb9ff72843e74383abb0f0581441..127afd784c1fcdb531c1d3435f724d0041151e90 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 8b0ec388fa0afe5ffb28c94f2e75544612ab7265..235ae4c7e13b7dd26af100c6a1411ebfdeaef354 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 48d7b4ca94f3fd00f7a1f6a3fb57ebed934ffc0d..62759f7bc05c0133fd900bca15f01c4d17487589 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 268c57b180627f7d8ba3a3a754210a37d420fb0d..bbe26c2e7af7515d07af990a4db67c62ee14eb36 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 cf33989b5687e171bcef159f74af9a37973c1285..2c9bbb59b03d97ea668ab3010eaca95eb36f64e8 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 7090ea92c1ace40079493447d12fc61da416cb9c..8260d57b63d7f988a4c236d99ce2c9931a67c5e3 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 3d7e02db2cc37dd22952f0e86e844eac3f5c1cc4..f6b09a4800b01f3c1675e87a91ea458e7172f169 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 dcad6e66708f7cf9cae682da6a9a22086381ecec..895e7a359d108312918c27d98c7b529f2e25e300 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;
 }