Merge branch 'se/http' into next
authorJunio C Hamano <junkio@cox.net>
Tue, 23 May 2006 01:54:19 +0000 (18:54 -0700)
committerJunio C Hamano <junkio@cox.net>
Tue, 23 May 2006 01:54:19 +0000 (18:54 -0700)
* se/http:
  Remove possible segfault in http-fetch.
  Install git builtins into gitexecdir rather than bindir.
  Change GIT-VERSION-GEN to call git commands with "git" not "git-".
  cvsimport: replace anonymous sub ref with a normal sub
  cvsimport: minor fixups
  Problem: 'trap...exit' causes error message when /bin/sh is ash.
  Avoid segfault in diff --stat rename output.
  Tutorial #2: broken link fix.
  git help: remove whatchanged from list of common commands

44 files changed:
.gitignore
Documentation/git-read-tree.txt
Documentation/git-write-tree.txt
Makefile
apply.c
builtin-add.c [new file with mode: 0644]
builtin-diff.c
builtin-log.c
builtin-rev-list.c
builtin-rm.c [new file with mode: 0644]
builtin-tar-tree.c [new file with mode: 0644]
builtin-upload-tar.c [new file with mode: 0644]
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
connect.c
date.c
diff.c
diff.h
dir.c [new file with mode: 0644]
dir.h [new file with mode: 0644]
dump-cache-tree.c [new file with mode: 0644]
fetch-pack.c
fsck-objects.c
git-add.sh [deleted file]
git-format-patch.sh [deleted file]
git-rebase.sh
git-rm.sh [deleted file]
git.c
log-tree.c
ls-files.c
mailinfo.c
read-cache.c
read-tree.c
revision.h
show-branch.c
t/t0000-basic.sh
tar-tree.c [deleted file]
update-index.c
write-tree.c

index b5959d6..7906909 100644 (file)
@@ -123,6 +123,7 @@ git-write-tree
 git-core-*/?*
 test-date
 test-delta
+test-dump-cache-tree
 common-cmds.h
 *.tar.gz
 *.dsc
index 844cfda..1f21d95 100644 (file)
@@ -8,7 +8,7 @@ git-read-tree - Reads tree information into the index
 
 SYNOPSIS
 --------
-'git-read-tree' (<tree-ish> | [[-m [--aggressive]| --reset] [-u | -i]] <tree-ish1> [<tree-ish2> [<tree-ish3>]])
+'git-read-tree' (<tree-ish> | [[-m [--aggressive] | --reset | --prefix=<prefix>] [-u | -i]] <tree-ish1> [<tree-ish2> [<tree-ish3>]])
 
 
 DESCRIPTION
@@ -63,6 +63,15 @@ OPTIONS
 * when both sides adds a path identically.  The resolution
   is to add that path.
 
+--prefix=<prefix>/::
+       Keep the current index contents, and read the contents
+       of named tree-ish under directory at `<prefix>`.  The
+       original index file cannot have anything at the path
+       `<prefix>` itself, and have nothing in `<prefix>/`
+       directory.  Note that the `<prefix>/` value must end
+       with a slash.
+
+
 <tree-ish#>::
        The id of the tree object(s) to be read/merged.
 
index 77e12cb..c85fa89 100644 (file)
@@ -8,7 +8,7 @@ git-write-tree - Creates a tree object from the current index
 
 SYNOPSIS
 --------
-'git-write-tree' [--missing-ok]
+'git-write-tree' [--missing-ok] [--prefix=<prefix>/]
 
 DESCRIPTION
 -----------
@@ -30,6 +30,12 @@ OPTIONS
        directory exist in the object database.  This option disables this
        check.
 
+--prefix=<prefix>/::
+       Writes a tree object that represents a subdirectory
+       `<prefix>`.  This can be used to write the tree object
+       for a subproject that is in the named subdirectory.
+
+
 Author
 ------
 Written by Linus Torvalds <torvalds@osdl.org>
index 5423b7a..677bc13 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -113,14 +113,14 @@ SPARSE_FLAGS = -D__BIG_ENDIAN__ -D__powerpc__
 ### --- END CONFIGURATION SECTION ---
 
 SCRIPT_SH = \
-       git-add.sh git-bisect.sh git-branch.sh git-checkout.sh \
+       git-bisect.sh git-branch.sh git-checkout.sh \
        git-cherry.sh git-clean.sh git-clone.sh git-commit.sh \
        git-fetch.sh \
-       git-format-patch.sh git-ls-remote.sh \
+       git-ls-remote.sh \
        git-merge-one-file.sh git-parse-remote.sh \
        git-prune.sh git-pull.sh git-rebase.sh \
        git-repack.sh git-request-pull.sh git-reset.sh \
-       git-resolve.sh git-revert.sh git-rm.sh git-sh-setup.sh \
+       git-resolve.sh git-revert.sh git-sh-setup.sh \
        git-tag.sh git-verify-tag.sh \
        git-applymbox.sh git-applypatch.sh git-am.sh \
        git-merge.sh git-merge-stupid.sh git-merge-octopus.sh \
@@ -161,7 +161,7 @@ PROGRAMS = \
        git-receive-pack$X git-rev-parse$X \
        git-send-pack$X git-show-branch$X git-shell$X \
        git-show-index$X git-ssh-fetch$X \
-       git-ssh-upload$X git-tar-tree$X git-unpack-file$X \
+       git-ssh-upload$X git-unpack-file$X \
        git-unpack-objects$X git-update-index$X git-update-server-info$X \
        git-upload-pack$X git-verify-pack$X git-write-tree$X \
        git-update-ref$X git-symbolic-ref$X \
@@ -170,8 +170,9 @@ PROGRAMS = \
 
 BUILT_INS = git-log$X git-whatchanged$X git-show$X \
        git-count-objects$X git-diff$X git-push$X \
-       git-grep$X git-rev-list$X git-check-ref-format$X \
-       git-init-db$X
+       git-grep$X git-add$X git-rm$X git-rev-list$X \
+       git-check-ref-format$X \
+       git-init-db$X git-tar-tree$X git-upload-tar$X git-format-patch$X
 
 # what 'all' will build and 'install' will install, in gitexecdir
 ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS)
@@ -200,7 +201,7 @@ LIB_H = \
        blob.h cache.h commit.h csum-file.h delta.h \
        diff.h object.h pack.h pkt-line.h quote.h refs.h \
        run-command.h strbuf.h tag.h tree.h git-compat-util.h revision.h \
-       tree-walk.h log-tree.h
+       tree-walk.h log-tree.h dir.h
 
 DIFF_OBJS = \
        diff.o diff-lib.o diffcore-break.o diffcore-order.o \
@@ -208,10 +209,10 @@ DIFF_OBJS = \
        diffcore-delta.o log-tree.o
 
 LIB_OBJS = \
-       blob.o commit.o connect.o csum-file.o base85.o \
+       blob.o commit.o connect.o csum-file.o cache-tree.o base85.o \
        date.o diff-delta.o entry.o exec_cmd.o ident.o index.o \
        object.o pack-check.o patch-delta.o path.o pkt-line.o \
-       quote.o read-cache.o refs.o run-command.o \
+       quote.o read-cache.o refs.o run-command.o dir.o \
        server-info.o setup.o sha1_file.o sha1_name.o strbuf.o \
        tag.o tree.o usage.o config.o environment.o ctype.o copy.o \
        fetch-clone.o revision.o pager.o tree-walk.o xdiff-interface.o \
@@ -219,8 +220,8 @@ LIB_OBJS = \
 
 BUILTIN_OBJS = \
        builtin-log.o builtin-help.o builtin-count.o builtin-diff.o builtin-push.o \
-       builtin-grep.o builtin-rev-list.o builtin-check-ref-format.o \
-       builtin-init-db.o
+       builtin-grep.o builtin-add.o builtin-rev-list.o builtin-check-ref-format.o \
+       builtin-rm.o builtin-init-db.o builtin-tar-tree.o builtin-upload-tar.o
 
 GITLIBS = $(LIB_FILE) $(XDIFF_LIB)
 LIBS = $(GITLIBS) -lz
@@ -612,6 +613,9 @@ test-date$X: test-date.c date.o ctype.o
 test-delta$X: test-delta.c diff-delta.o patch-delta.o
        $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $^
 
+test-dump-cache-tree$X: dump-cache-tree.o $(GITLIBS)
+       $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
+
 check:
        for i in *.c; do sparse $(ALL_CFLAGS) $(SPARSE_FLAGS) $$i || exit; done
 
diff --git a/apply.c b/apply.c
index 0ed9d13..5341e30 100644 (file)
--- a/apply.c
+++ b/apply.c
@@ -8,6 +8,7 @@
  */
 #include <fnmatch.h>
 #include "cache.h"
+#include "cache-tree.h"
 #include "quote.h"
 #include "blob.h"
 #include "delta.h"
@@ -1917,6 +1918,7 @@ static void remove_file(struct patch *patch)
        if (write_index) {
                if (remove_file_from_cache(patch->old_name) < 0)
                        die("unable to remove %s from index", patch->old_name);
+               cache_tree_invalidate_path(active_cache_tree, patch->old_name);
        }
        if (!cached)
                unlink(patch->old_name);
@@ -2018,8 +2020,9 @@ static void create_file(struct patch *patch)
 
        if (!mode)
                mode = S_IFREG | 0644;
-       create_one_file(path, mode, buf, size); 
+       create_one_file(path, mode, buf, size);
        add_index_file(path, mode, buf, size);
+       cache_tree_invalidate_path(active_cache_tree, path);
 }
 
 static void write_out_one_result(struct patch *patch)
diff --git a/builtin-add.c b/builtin-add.c
new file mode 100644 (file)
index 0000000..02fe38b
--- /dev/null
@@ -0,0 +1,189 @@
+/*
+ * "git add" builtin command
+ *
+ * Copyright (C) 2006 Linus Torvalds
+ */
+#include <fnmatch.h>
+
+#include "cache.h"
+#include "builtin.h"
+#include "dir.h"
+#include "cache-tree.h"
+
+static const char builtin_add_usage[] =
+"git-add [-n] [-v] <filepattern>...";
+
+static void prune_directory(struct dir_struct *dir, const char **pathspec, int prefix)
+{
+       char *seen;
+       int i, specs;
+       struct dir_entry **src, **dst;
+
+       for (specs = 0; pathspec[specs];  specs++)
+               /* nothing */;
+       seen = xmalloc(specs);
+       memset(seen, 0, specs);
+
+       src = dst = dir->entries;
+       i = dir->nr;
+       while (--i >= 0) {
+               struct dir_entry *entry = *src++;
+               if (!match_pathspec(pathspec, entry->name, entry->len, prefix, seen)) {
+                       free(entry);
+                       continue;
+               }
+               *dst++ = entry;
+       }
+       dir->nr = dst - dir->entries;
+
+       for (i = 0; i < specs; i++) {
+               struct stat st;
+               const char *match;
+               if (seen[i])
+                       continue;
+
+               /* Existing file? We must have ignored it */
+               match = pathspec[i];
+               if (!match[0] || !lstat(match, &st))
+                       continue;
+               die("pathspec '%s' did not match any files", match);
+       }
+}
+
+static void fill_directory(struct dir_struct *dir, const char **pathspec)
+{
+       const char *path, *base;
+       int baselen;
+
+       /* Set up the default git porcelain excludes */
+       memset(dir, 0, sizeof(*dir));
+       dir->exclude_per_dir = ".gitignore";
+       path = git_path("info/exclude");
+       if (!access(path, R_OK))
+               add_excludes_from_file(dir, path);
+
+       /*
+        * Calculate common prefix for the pathspec, and
+        * use that to optimize the directory walk
+        */
+       baselen = common_prefix(pathspec);
+       path = ".";
+       base = "";
+       if (baselen) {
+               char *common = xmalloc(baselen + 1);
+               common = xmalloc(baselen + 1);
+               memcpy(common, *pathspec, baselen);
+               common[baselen] = 0;
+               path = base = common;
+       }
+
+       /* Read the directory and prune it */
+       read_directory(dir, path, base, baselen);
+       if (pathspec)
+               prune_directory(dir, pathspec, baselen);
+}
+
+static int add_file_to_index(const char *path, int verbose)
+{
+       int size, namelen;
+       struct stat st;
+       struct cache_entry *ce;
+
+       if (lstat(path, &st))
+               die("%s: unable to stat (%s)", path, strerror(errno));
+
+       if (!S_ISREG(st.st_mode) && !S_ISLNK(st.st_mode))
+               die("%s: can only add regular files or symbolic links", path);
+
+       namelen = strlen(path);
+       size = cache_entry_size(namelen);
+       ce = xcalloc(1, size);
+       memcpy(ce->name, path, namelen);
+       ce->ce_flags = htons(namelen);
+       fill_stat_cache_info(ce, &st);
+
+       ce->ce_mode = create_ce_mode(st.st_mode);
+       if (!trust_executable_bit) {
+               /* If there is an existing entry, pick the mode bits
+                * from it.
+                */
+               int pos = cache_name_pos(path, namelen);
+               if (pos >= 0)
+                       ce->ce_mode = active_cache[pos]->ce_mode;
+       }
+
+       if (index_path(ce->sha1, path, &st, 1))
+               die("unable to index file %s", path);
+       if (add_cache_entry(ce, ADD_CACHE_OK_TO_ADD))
+               die("unable to add %s to index",path);
+       if (verbose)
+               printf("add '%s'\n", path);
+       cache_tree_invalidate_path(active_cache_tree, path);
+       return 0;
+}
+
+static struct cache_file cache_file;
+
+int cmd_add(int argc, const char **argv, char **envp)
+{
+       int i, newfd;
+       int verbose = 0, show_only = 0;
+       const char *prefix = setup_git_directory();
+       const char **pathspec;
+       struct dir_struct dir;
+
+       git_config(git_default_config);
+
+       newfd = hold_index_file_for_update(&cache_file, get_index_file());
+       if (newfd < 0)
+               die("unable to create new cachefile");
+
+       if (read_cache() < 0)
+               die("index file corrupt");
+
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+
+               if (arg[0] != '-')
+                       break;
+               if (!strcmp(arg, "--")) {
+                       i++;
+                       break;
+               }
+               if (!strcmp(arg, "-n")) {
+                       show_only = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "-v")) {
+                       verbose = 1;
+                       continue;
+               }
+               die(builtin_add_usage);
+       }
+       git_config(git_default_config);
+       pathspec = get_pathspec(prefix, argv + i);
+
+       fill_directory(&dir, pathspec);
+
+       if (show_only) {
+               const char *sep = "", *eof = "";
+               for (i = 0; i < dir.nr; i++) {
+                       printf("%s%s", sep, dir.entries[i]->name);
+                       sep = " ";
+                       eof = "\n";
+               }
+               fputs(eof, stdout);
+               return 0;
+       }
+
+       for (i = 0; i < dir.nr; i++)
+               add_file_to_index(dir.entries[i]->name, verbose);
+
+       if (active_cache_changed) {
+               if (write_cache(newfd, active_cache, active_nr) ||
+                   commit_index_file(&cache_file))
+                       die("Unable to write new index file");
+       }
+
+       return 0;
+}
index de81b05..27451d5 100644 (file)
@@ -233,7 +233,7 @@ static int builtin_diff_combined(struct rev_info *revs,
        return 0;
 }
 
-static void add_head(struct rev_info *revs)
+void add_head(struct rev_info *revs)
 {
        unsigned char sha1[20];
        struct object *obj;
index c4ceee0..c8feb0f 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)
@@ -74,3 +78,171 @@ 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 if (!strcmp(argv[i], "--attach"))
+                       rev.mime_boundary = git_version_string;
+               else if (!strncmp(argv[i], "--attach=", 9))
+                       rev.mime_boundary = argv[i] + 9;
+               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) {
+                       if (rev.mime_boundary)
+                               printf("\n--%s%s--\n\n\n",
+                                      mime_boundary_leader,
+                                      rev.mime_boundary);
+                       else
+                               printf("-- \n%s\n\n", git_version_string);
+               }
+               if (!use_stdout)
+                       fclose(stdout);
+       }
+       if (output_directory)
+               free(output_directory);
+       free(list);
+       return 0;
+}
+
index 446802d..f11dbd6 100644 (file)
@@ -85,7 +85,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, NULL);
                printf("%s%c", pretty_header, hdr_termination);
        }
        fflush(stdout);
diff --git a/builtin-rm.c b/builtin-rm.c
new file mode 100644 (file)
index 0000000..0beb86d
--- /dev/null
@@ -0,0 +1,151 @@
+/*
+ * "git rm" builtin command
+ *
+ * Copyright (C) Linus Torvalds 2006
+ */
+#include "cache.h"
+#include "builtin.h"
+#include "dir.h"
+
+static const char builtin_rm_usage[] =
+"git-rm [-n] [-v] [-f] <filepattern>...";
+
+static struct {
+       int nr, alloc;
+       const char **name;
+} list;
+
+static void add_list(const char *name)
+{
+       if (list.nr >= list.alloc) {
+               list.alloc = alloc_nr(list.alloc);
+               list.name = xrealloc(list.name, list.alloc * sizeof(const char *));
+       }
+       list.name[list.nr++] = name;
+}
+
+static int remove_file(const char *name)
+{
+       int ret;
+       char *slash;
+
+       ret = unlink(name);
+       if (!ret && (slash = strrchr(name, '/'))) {
+               char *n = strdup(name);
+               do {
+                       n[slash - name] = 0;
+                       name = n;
+               } while (!rmdir(name) && (slash = strrchr(name, '/')));
+       }
+       return ret;
+}
+
+static struct cache_file cache_file;
+
+int cmd_rm(int argc, const char **argv, char **envp)
+{
+       int i, newfd;
+       int verbose = 0, show_only = 0, force = 0;
+       const char *prefix = setup_git_directory();
+       const char **pathspec;
+       char *seen;
+
+       git_config(git_default_config);
+
+       newfd = hold_index_file_for_update(&cache_file, get_index_file());
+       if (newfd < 0)
+               die("unable to create new index file");
+
+       if (read_cache() < 0)
+               die("index file corrupt");
+
+       for (i = 1 ; i < argc ; i++) {
+               const char *arg = argv[i];
+
+               if (*arg != '-')
+                       break;
+               if (!strcmp(arg, "--")) {
+                       i++;
+                       break;
+               }
+               if (!strcmp(arg, "-n")) {
+                       show_only = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "-v")) {
+                       verbose = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "-f")) {
+                       force = 1;
+                       continue;
+               }
+               die(builtin_rm_usage);
+       }
+       pathspec = get_pathspec(prefix, argv + i);
+
+       seen = NULL;
+       if (pathspec) {
+               for (i = 0; pathspec[i] ; i++)
+                       /* nothing */;
+               seen = xmalloc(i);
+               memset(seen, 0, i);
+       }
+
+       for (i = 0; i < active_nr; i++) {
+               struct cache_entry *ce = active_cache[i];
+               if (!match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, seen))
+                       continue;
+               add_list(ce->name);
+       }
+
+       if (pathspec) {
+               const char *match;
+               for (i = 0; (match = pathspec[i]) != NULL ; i++) {
+                       if (*match && !seen[i])
+                               die("pathspec '%s' did not match any files", match);
+               }
+       }
+
+       /*
+        * First remove the names from the index: we won't commit
+        * the index unless all of them succeed
+        */
+       for (i = 0; i < list.nr; i++) {
+               const char *path = list.name[i];
+               printf("rm '%s'\n", path);
+
+               if (remove_file_from_cache(path))
+                       die("git rm: unable to remove %s", path);
+               cache_tree_invalidate_path(active_cache_tree, path);
+       }
+
+       /*
+        * Then, if we used "-f", remove the filenames from the
+        * workspace. If we fail to remove the first one, we
+        * abort the "git rm" (but once we've successfully removed
+        * any file at all, we'll go ahead and commit to it all:
+        * by then we've already committed ourself and can't fail
+        * in the middle)
+        */
+       if (force) {
+               int removed = 0;
+               for (i = 0; i < list.nr; i++) {
+                       const char *path = list.name[i];
+                       if (!remove_file(path)) {
+                               removed = 1;
+                               continue;
+                       }
+                       if (!removed)
+                               die("git rm: %s: %s", path, strerror(errno));
+               }
+       }
+
+       if (active_cache_changed) {
+               if (write_cache(newfd, active_cache, active_nr) ||
+                   commit_index_file(&cache_file))
+                       die("Unable to write new index file");
+       }
+
+       return 0;
+}
diff --git a/builtin-tar-tree.c b/builtin-tar-tree.c
new file mode 100644 (file)
index 0000000..e97e0af
--- /dev/null
@@ -0,0 +1,408 @@
+/*
+ * Copyright (c) 2005, 2006 Rene Scharfe
+ */
+#include <time.h>
+#include "cache.h"
+#include "tree-walk.h"
+#include "commit.h"
+#include "strbuf.h"
+#include "tar.h"
+#include "builtin.h"
+#include "pkt-line.h"
+
+#define RECORDSIZE     (512)
+#define BLOCKSIZE      (RECORDSIZE * 20)
+
+static const char tar_tree_usage[] =
+"git-tar-tree [--remote=<repo>] <ent> [basedir]";
+
+static char block[BLOCKSIZE];
+static unsigned long offset;
+
+static time_t archive_time;
+
+/* tries hard to write, either succeeds or dies in the attempt */
+static void reliable_write(void *buf, unsigned long size)
+{
+       while (size > 0) {
+               long ret = xwrite(1, buf, size);
+               if (ret < 0) {
+                       if (errno == EPIPE)
+                               exit(0);
+                       die("git-tar-tree: %s", strerror(errno));
+               } else if (!ret) {
+                       die("git-tar-tree: disk full?");
+               }
+               size -= ret;
+               buf += ret;
+       }
+}
+
+/* writes out the whole block, but only if it is full */
+static void write_if_needed(void)
+{
+       if (offset == BLOCKSIZE) {
+               reliable_write(block, BLOCKSIZE);
+               offset = 0;
+       }
+}
+
+/* acquire the next record from the buffer; user must call write_if_needed() */
+static char *get_record(void)
+{
+       char *p = block + offset;
+       memset(p, 0, RECORDSIZE);
+       offset += RECORDSIZE;
+       return p;
+}
+
+/*
+ * The end of tar archives is marked by 1024 nul bytes and after that
+ * follows the rest of the block (if any).
+ */
+static void write_trailer(void)
+{
+       get_record();
+       write_if_needed();
+       get_record();
+       write_if_needed();
+       while (offset) {
+               get_record();
+               write_if_needed();
+       }
+}
+
+/*
+ * queues up writes, so that all our write(2) calls write exactly one
+ * full block; pads writes to RECORDSIZE
+ */
+static void write_blocked(void *buf, unsigned long size)
+{
+       unsigned long tail;
+
+       if (offset) {
+               unsigned long chunk = BLOCKSIZE - offset;
+               if (size < chunk)
+                       chunk = size;
+               memcpy(block + offset, buf, chunk);
+               size -= chunk;
+               offset += chunk;
+               buf += chunk;
+               write_if_needed();
+       }
+       while (size >= BLOCKSIZE) {
+               reliable_write(buf, BLOCKSIZE);
+               size -= BLOCKSIZE;
+               buf += BLOCKSIZE;
+       }
+       if (size) {
+               memcpy(block + offset, buf, size);
+               offset += size;
+       }
+       tail = offset % RECORDSIZE;
+       if (tail)  {
+               memset(block + offset, 0, RECORDSIZE - tail);
+               offset += RECORDSIZE - tail;
+       }
+       write_if_needed();
+}
+
+static void strbuf_append_string(struct strbuf *sb, const char *s)
+{
+       int slen = strlen(s);
+       int total = sb->len + slen;
+       if (total > sb->alloc) {
+               sb->buf = xrealloc(sb->buf, total);
+               sb->alloc = total;
+       }
+       memcpy(sb->buf + sb->len, s, slen);
+       sb->len = total;
+}
+
+/*
+ * pax extended header records have the format "%u %s=%s\n".  %u contains
+ * the size of the whole string (including the %u), the first %s is the
+ * keyword, the second one is the value.  This function constructs such a
+ * string and appends it to a struct strbuf.
+ */
+static void strbuf_append_ext_header(struct strbuf *sb, const char *keyword,
+                                     const char *value, unsigned int valuelen)
+{
+       char *p;
+       int len, total, tmp;
+
+       /* "%u %s=%s\n" */
+       len = 1 + 1 + strlen(keyword) + 1 + valuelen + 1;
+       for (tmp = len; tmp > 9; tmp /= 10)
+               len++;
+
+       total = sb->len + len;
+       if (total > sb->alloc) {
+               sb->buf = xrealloc(sb->buf, total);
+               sb->alloc = total;
+       }
+
+       p = sb->buf;
+       p += sprintf(p, "%u %s=", len, keyword);
+       memcpy(p, value, valuelen);
+       p += valuelen;
+       *p = '\n';
+       sb->len = total;
+}
+
+static unsigned int ustar_header_chksum(const struct ustar_header *header)
+{
+       char *p = (char *)header;
+       unsigned int chksum = 0;
+       while (p < header->chksum)
+               chksum += *p++;
+       chksum += sizeof(header->chksum) * ' ';
+       p += sizeof(header->chksum);
+       while (p < (char *)header + sizeof(struct ustar_header))
+               chksum += *p++;
+       return chksum;
+}
+
+static int get_path_prefix(const struct strbuf *path, int maxlen)
+{
+       int i = path->len;
+       if (i > maxlen)
+               i = maxlen;
+       while (i > 0 && path->buf[i] != '/')
+               i--;
+       return i;
+}
+
+static void write_entry(const unsigned char *sha1, struct strbuf *path,
+                        unsigned int mode, void *buffer, unsigned long size)
+{
+       struct ustar_header header;
+       struct strbuf ext_header;
+
+       memset(&header, 0, sizeof(header));
+       ext_header.buf = NULL;
+       ext_header.len = ext_header.alloc = 0;
+
+       if (!sha1) {
+               *header.typeflag = TYPEFLAG_GLOBAL_HEADER;
+               mode = 0100666;
+               strcpy(header.name, "pax_global_header");
+       } else if (!path) {
+               *header.typeflag = TYPEFLAG_EXT_HEADER;
+               mode = 0100666;
+               sprintf(header.name, "%s.paxheader", sha1_to_hex(sha1));
+       } else {
+               if (S_ISDIR(mode)) {
+                       *header.typeflag = TYPEFLAG_DIR;
+                       mode |= 0777;
+               } else if (S_ISLNK(mode)) {
+                       *header.typeflag = TYPEFLAG_LNK;
+                       mode |= 0777;
+               } else if (S_ISREG(mode)) {
+                       *header.typeflag = TYPEFLAG_REG;
+                       mode |= (mode & 0100) ? 0777 : 0666;
+               } else {
+                       error("unsupported file mode: 0%o (SHA1: %s)",
+                             mode, sha1_to_hex(sha1));
+                       return;
+               }
+               if (path->len > sizeof(header.name)) {
+                       int plen = get_path_prefix(path, sizeof(header.prefix));
+                       int rest = path->len - plen - 1;
+                       if (plen > 0 && rest <= sizeof(header.name)) {
+                               memcpy(header.prefix, path->buf, plen);
+                               memcpy(header.name, path->buf + plen + 1, rest);
+                       } else {
+                               sprintf(header.name, "%s.data",
+                                       sha1_to_hex(sha1));
+                               strbuf_append_ext_header(&ext_header, "path",
+                                                        path->buf, path->len);
+                       }
+               } else
+                       memcpy(header.name, path->buf, path->len);
+       }
+
+       if (S_ISLNK(mode) && buffer) {
+               if (size > sizeof(header.linkname)) {
+                       sprintf(header.linkname, "see %s.paxheader",
+                               sha1_to_hex(sha1));
+                       strbuf_append_ext_header(&ext_header, "linkpath",
+                                                buffer, size);
+               } else
+                       memcpy(header.linkname, buffer, size);
+       }
+
+       sprintf(header.mode, "%07o", mode & 07777);
+       sprintf(header.size, "%011lo", S_ISREG(mode) ? size : 0);
+       sprintf(header.mtime, "%011lo", archive_time);
+
+       /* XXX: should we provide more meaningful info here? */
+       sprintf(header.uid, "%07o", 0);
+       sprintf(header.gid, "%07o", 0);
+       strncpy(header.uname, "git", 31);
+       strncpy(header.gname, "git", 31);
+       sprintf(header.devmajor, "%07o", 0);
+       sprintf(header.devminor, "%07o", 0);
+
+       memcpy(header.magic, "ustar", 6);
+       memcpy(header.version, "00", 2);
+
+       sprintf(header.chksum, "%07o", ustar_header_chksum(&header));
+
+       if (ext_header.len > 0) {
+               write_entry(sha1, NULL, 0, ext_header.buf, ext_header.len);
+               free(ext_header.buf);
+       }
+       write_blocked(&header, sizeof(header));
+       if (S_ISREG(mode) && buffer && size > 0)
+               write_blocked(buffer, size);
+}
+
+static void write_global_extended_header(const unsigned char *sha1)
+{
+       struct strbuf ext_header;
+       ext_header.buf = NULL;
+       ext_header.len = ext_header.alloc = 0;
+       strbuf_append_ext_header(&ext_header, "comment", sha1_to_hex(sha1), 40);
+       write_entry(NULL, NULL, 0, ext_header.buf, ext_header.len);
+       free(ext_header.buf);
+}
+
+static void traverse_tree(struct tree_desc *tree, struct strbuf *path)
+{
+       int pathlen = path->len;
+
+       while (tree->size) {
+               const char *name;
+               const unsigned char *sha1;
+               unsigned mode;
+               void *eltbuf;
+               char elttype[20];
+               unsigned long eltsize;
+
+               sha1 = tree_entry_extract(tree, &name, &mode);
+               update_tree_entry(tree);
+
+               eltbuf = read_sha1_file(sha1, elttype, &eltsize);
+               if (!eltbuf)
+                       die("cannot read %s", sha1_to_hex(sha1));
+
+               path->len = pathlen;
+               strbuf_append_string(path, name);
+               if (S_ISDIR(mode))
+                       strbuf_append_string(path, "/");
+
+               write_entry(sha1, path, mode, eltbuf, eltsize);
+
+               if (S_ISDIR(mode)) {
+                       struct tree_desc subtree;
+                       subtree.buf = eltbuf;
+                       subtree.size = eltsize;
+                       traverse_tree(&subtree, path);
+               }
+               free(eltbuf);
+       }
+}
+
+int generate_tar(int argc, const char **argv)
+{
+       unsigned char sha1[20], tree_sha1[20];
+       struct commit *commit;
+       struct tree_desc tree;
+       struct strbuf current_path;
+
+       current_path.buf = xmalloc(PATH_MAX);
+       current_path.alloc = PATH_MAX;
+       current_path.len = current_path.eof = 0;
+
+       setup_git_directory();
+       git_config(git_default_config);
+
+       switch (argc) {
+       case 3:
+               strbuf_append_string(&current_path, argv[2]);
+               strbuf_append_string(&current_path, "/");
+               /* FALLTHROUGH */
+       case 2:
+               if (get_sha1(argv[1], sha1))
+                       die("Not a valid object name %s", argv[1]);
+               break;
+       default:
+               usage(tar_tree_usage);
+       }
+
+       commit = lookup_commit_reference_gently(sha1, 1);
+       if (commit) {
+               write_global_extended_header(commit->object.sha1);
+               archive_time = commit->date;
+       } else
+               archive_time = time(NULL);
+
+       tree.buf = read_object_with_reference(sha1, tree_type, &tree.size,
+                                             tree_sha1);
+       if (!tree.buf)
+               die("not a reference to a tag, commit or tree object: %s",
+                   sha1_to_hex(sha1));
+
+       if (current_path.len > 0)
+               write_entry(tree_sha1, &current_path, 040777, NULL, 0);
+       traverse_tree(&tree, &current_path);
+       write_trailer();
+       free(current_path.buf);
+       return 0;
+}
+
+static const char *exec = "git-upload-tar";
+
+static int remote_tar(int argc, const char **argv)
+{
+       int fd[2], ret, len;
+       pid_t pid;
+       char buf[1024];
+       char *url;
+
+       if (argc < 3 || 4 < argc)
+               usage(tar_tree_usage);
+
+       /* --remote=<repo> */
+       url = strdup(argv[1]+9);
+       pid = git_connect(fd, url, exec);
+       if (pid < 0)
+               return 1;
+
+       packet_write(fd[1], "want %s\n", argv[2]);
+       if (argv[3])
+               packet_write(fd[1], "base %s\n", argv[3]);
+       packet_flush(fd[1]);
+
+       len = packet_read_line(fd[0], buf, sizeof(buf));
+       if (!len)
+               die("git-tar-tree: expected ACK/NAK, got EOF");
+       if (buf[len-1] == '\n')
+               buf[--len] = 0;
+       if (strcmp(buf, "ACK")) {
+               if (5 < len && !strncmp(buf, "NACK ", 5))
+                       die("git-tar-tree: NACK %s", buf + 5);
+               die("git-tar-tree: protocol error");
+       }
+       /* expect a flush */
+       len = packet_read_line(fd[0], buf, sizeof(buf));
+       if (len)
+               die("git-tar-tree: expected a flush");
+
+       /* Now, start reading from fd[0] and spit it out to stdout */
+       ret = copy_fd(fd[0], 1);
+       close(fd[0]);
+
+       ret |= finish_connect(pid);
+       return !!ret;
+}
+
+int cmd_tar_tree(int argc, const char **argv, char **envp)
+{
+       if (argc < 2)
+               usage(tar_tree_usage);
+       if (!strncmp("--remote=", argv[1], 9))
+               return remote_tar(argc, argv);
+       return generate_tar(argc, argv);
+}
diff --git a/builtin-upload-tar.c b/builtin-upload-tar.c
new file mode 100644 (file)
index 0000000..d4fa7b5
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2006 Junio C Hamano
+ */
+#include "cache.h"
+#include "pkt-line.h"
+#include "exec_cmd.h"
+#include "builtin.h"
+
+static const char upload_tar_usage[] = "git-upload-tar <repo>";
+
+static int nak(const char *reason)
+{
+       packet_write(1, "NACK %s\n", reason);
+       packet_flush(1);
+       return 1;
+}
+
+int cmd_upload_tar(int argc, const char **argv, char **envp)
+{
+       int len;
+       const char *dir = argv[1];
+       char buf[8192];
+       unsigned char sha1[20];
+       char *base = NULL;
+       char hex[41];
+       int ac;
+       const char *av[4];
+
+       if (argc != 2)
+               usage(upload_tar_usage);
+       if (strlen(dir) < sizeof(buf)-1)
+               strcpy(buf, dir); /* enter-repo smudges its argument */
+       else
+               packet_write(1, "NACK insanely long repository name %s\n", dir);
+       if (!enter_repo(buf, 0)) {
+               packet_write(1, "NACK not a git archive %s\n", dir);
+               packet_flush(1);
+               return 1;
+       }
+
+       len = packet_read_line(0, buf, sizeof(buf));
+       if (len < 5 || strncmp("want ", buf, 5))
+               return nak("expected want");
+       if (buf[len-1] == '\n')
+               buf[--len] = 0;
+       if (get_sha1(buf + 5, sha1))
+               return nak("expected sha1");
+        strcpy(hex, sha1_to_hex(sha1));
+
+       len = packet_read_line(0, buf, sizeof(buf));
+       if (len) {
+               if (len < 5 || strncmp("base ", buf, 5))
+                       return nak("expected (optional) base");
+               if (buf[len-1] == '\n')
+                       buf[--len] = 0;
+               base = strdup(buf + 5);
+               len = packet_read_line(0, buf, sizeof(buf));
+       }
+       if (len)
+               return nak("expected flush");
+
+       packet_write(1, "ACK\n");
+       packet_flush(1);
+
+       ac = 0;
+       av[ac++] = "tar-tree";
+       av[ac++] = hex;
+       if (base)
+               av[ac++] = base;
+       av[ac++] = NULL;
+       execv_git_cmd(av);
+       /* should it return that is an error */
+       return 1;
+}
index 6054126..c92e702 100644 (file)
--- a/builtin.h
+++ b/builtin.h
@@ -20,12 +20,17 @@ extern int cmd_whatchanged(int argc, const char **argv, char **envp);
 extern int cmd_show(int argc, const char **argv, char **envp);
 extern int cmd_log(int argc, const char **argv, char **envp);
 extern int cmd_diff(int argc, const char **argv, char **envp);
+extern int cmd_format_patch(int argc, const char **argv, char **envp);
 extern int cmd_count_objects(int argc, const char **argv, char **envp);
 
 extern int cmd_push(int argc, const char **argv, char **envp);
 extern int cmd_grep(int argc, const char **argv, char **envp);
+extern int cmd_rm(int argc, const char **argv, char **envp);
+extern int cmd_add(int argc, const char **argv, char **envp);
 extern int cmd_rev_list(int argc, const char **argv, char **envp);
 extern int cmd_check_ref_format(int argc, const char **argv, char **envp);
 extern int cmd_init_db(int argc, const char **argv, char **envp);
+extern int cmd_tar_tree(int argc, const char **argv, char **envp);
+extern int cmd_upload_tar(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 afa8e4f..31755c8 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"
@@ -142,6 +143,7 @@ extern void verify_non_filename(const char *prefix, const char *name);
 /* Initialize and use the cache information */
 extern int read_cache(void);
 extern int write_cache(int newfd, struct cache_entry **cache, int entries);
+extern int verify_path(const char *path);
 extern int cache_name_pos(const char *name, int namelen);
 #define ADD_CACHE_OK_TO_ADD 1          /* Ok to add */
 #define ADD_CACHE_OK_TO_REPLACE 2      /* Ok to replace file/directory */
@@ -257,6 +259,7 @@ extern void *read_object_with_reference(const unsigned char *sha1,
                                        unsigned char *sha1_ret);
 
 const char *show_date(unsigned long time, int timezone);
+const char *show_rfc2822_date(unsigned long time, int timezone);
 int parse_date(const char *date, char *buf, int bufsize);
 void datestamp(char *buf, int bufsize);
 unsigned long approxidate(const char *);
index cc3a745..9876af6 100644 (file)
@@ -39,6 +39,7 @@
 #include "cache.h"
 #include "strbuf.h"
 #include "quote.h"
+#include "cache-tree.h"
 
 #define CHECKOUT_ALL 4
 static const char *prefix;
index 4a26070..94f470b 100644 (file)
--- a/commit.c
+++ b/commit.c
@@ -30,6 +30,7 @@ struct cmt_fmt_map {
        { "raw",        1,      CMIT_FMT_RAW },
        { "medium",     1,      CMIT_FMT_MEDIUM },
        { "short",      1,      CMIT_FMT_SHORT },
+       { "email",      1,      CMIT_FMT_EMAIL },
        { "full",       5,      CMIT_FMT_FULL },
        { "fuller",     5,      CMIT_FMT_FULLER },
        { "oneline",    1,      CMIT_FMT_ONELINE },
@@ -421,6 +422,46 @@ static int get_one_line(const char *msg, unsigned long len)
        return ret;
 }
 
+static int is_rfc2047_special(char ch)
+{
+       return ((ch & 0x80) || (ch == '=') || (ch == '?') || (ch == '_'));
+}
+
+static int add_rfc2047(char *buf, const char *line, int len)
+{
+       char *bp = buf;
+       int i, needquote;
+       static const char q_utf8[] = "=?utf-8?q?";
+
+       for (i = needquote = 0; !needquote && i < len; i++) {
+               unsigned ch = line[i];
+               if (ch & 0x80)
+                       needquote++;
+               if ((i + 1 < len) &&
+                   (ch == '=' && line[i+1] == '?'))
+                       needquote++;
+       }
+       if (!needquote)
+               return sprintf(buf, "%.*s", len, line);
+
+       memcpy(bp, q_utf8, sizeof(q_utf8)-1);
+       bp += sizeof(q_utf8)-1;
+       for (i = 0; i < len; i++) {
+               unsigned ch = line[i];
+               if (is_rfc2047_special(ch)) {
+                       sprintf(bp, "=%02X", ch);
+                       bp += 3;
+               }
+               else if (ch == ' ')
+                       *bp++ = '_';
+               else
+                       *bp++ = ch;
+       }
+       memcpy(bp, "?=", 2);
+       bp += 2;
+       return bp - buf;
+}
+
 static int add_user_info(const char *what, enum cmit_fmt fmt, char *buf, const char *line)
 {
        char *date;
@@ -438,13 +479,35 @@ 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);
 
-       ret = sprintf(buf, "%s: %.*s%.*s\n", what,
-                     (fmt == CMIT_FMT_FULLER) ? 4 : 0,
-                     filler, namelen, line);
+       if (fmt == CMIT_FMT_EMAIL) {
+               char *name_tail = strchr(line, '<');
+               int display_name_length;
+               if (!name_tail)
+                       return 0;
+               while (line < name_tail && isspace(name_tail[-1]))
+                       name_tail--;
+               display_name_length = name_tail - line;
+               filler = "";
+               strcpy(buf, "From: ");
+               ret = strlen(buf);
+               ret += add_rfc2047(buf + ret, line, display_name_length);
+               memcpy(buf + ret, name_tail, namelen - display_name_length);
+               ret += namelen - display_name_length;
+               buf[ret++] = '\n';
+       }
+       else {
+               ret = sprintf(buf, "%s: %.*s%.*s\n", what,
+                             (fmt == CMIT_FMT_FULLER) ? 4 : 0,
+                             filler, namelen, line);
+       }
        switch (fmt) {
        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;
@@ -455,10 +518,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;
 }
 
@@ -467,7 +532,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:");
@@ -486,13 +552,30 @@ 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, const char *after_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;
+       int plain_non_ascii = 0;
+
+       if (fmt == CMIT_FMT_ONELINE || fmt == CMIT_FMT_EMAIL)
+               indent = 0;
+
+       /* After-subject is used to pass in Content-Type: multipart
+        * MIME header; in that case we do not have to do the
+        * plaintext content type even if the commit message has
+        * non 7-bit ASCII character.  Otherwise, check if we need
+        * to say this is not a 7-bit ASCII.
+        */
+       if (fmt == CMIT_FMT_EMAIL && !after_subject) {
+               int i;
+               for (i = 0; !plain_non_ascii && msg[i] && i < len; i++)
+                       if (msg[i] & 0x80)
+                               plain_non_ascii = 1;
+       }
 
        for (;;) {
                const char *line = msg;
@@ -516,7 +599,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;
                        }
@@ -554,20 +637,47 @@ 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;
                }
 
-               memset(buf + offset, ' ', indent);
-               memcpy(buf + offset + indent, line, linelen);
-               offset += linelen + indent;
+               if (subject) {
+                       int slen = strlen(subject);
+                       memcpy(buf + offset, subject, slen);
+                       offset += slen;
+                       offset += add_rfc2047(buf + offset, line, linelen);
+               }
+               else {
+                       memset(buf + offset, ' ', indent);
+                       memcpy(buf + offset + indent, line, linelen);
+                       offset += linelen + indent;
+               }
+               buf[offset++] = '\n';
                if (fmt == CMIT_FMT_ONELINE)
                        break;
+               if (subject && plain_non_ascii) {
+                       static const char header[] =
+                               "Content-Type: text/plain; charset=UTF-8\n"
+                               "Content-Transfer-Encoding: 8bit\n";
+                       memcpy(buf + offset, header, sizeof(header)-1);
+                       offset += sizeof(header)-1;
+               }
+               if (after_subject) {
+                       int slen = strlen(after_subject);
+                       if (slen > space - offset - 1)
+                               slen = space - offset - 1;
+                       memcpy(buf + offset, after_subject, slen);
+                       offset += slen;
+                       after_subject = NULL;
+               }
+               subject = NULL;
        }
        while (offset && isspace(buf[offset-1]))
                offset--;
index de142af..c9de167 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, const char *after_subject);
 
 /** Removes the first commit from a list sorted by date, and adds all
  * of its parents.
index 6a8f8a6..54f7bf7 100644 (file)
--- a/connect.c
+++ b/connect.c
@@ -100,7 +100,7 @@ int path_match(const char *path, int nr, char **match)
                if (pathlen > len && path[pathlen - len - 1] != '/')
                        continue;
                *s = 0;
-               return 1;
+               return (i + 1);
        }
        return 0;
 }
diff --git a/date.c b/date.c
index 034d722..365dc3b 100644 (file)
--- a/date.c
+++ b/date.c
@@ -42,18 +42,24 @@ static const char *weekday_names[] = {
  * thing, which means that tz -0100 is passed in as the integer -100,
  * even though it means "sixty minutes off"
  */
-const char *show_date(unsigned long time, int tz)
+static struct tm *time_to_tm(unsigned long time, int tz)
 {
-       struct tm *tm;
        time_t t;
-       static char timebuf[200];
        int minutes;
 
        minutes = tz < 0 ? -tz : tz;
        minutes = (minutes / 100)*60 + (minutes % 100);
        minutes = tz < 0 ? -minutes : minutes;
        t = time + minutes * 60;
-       tm = gmtime(&t);
+       return gmtime(&t);
+}
+
+const char *show_date(unsigned long time, int tz)
+{
+       struct tm *tm;
+       static char timebuf[200];
+
+       tm = time_to_tm(time, tz);
        if (!tm)
                return NULL;
        sprintf(timebuf, "%.3s %.3s %d %02d:%02d:%02d %d %+05d",
@@ -65,6 +71,21 @@ const char *show_date(unsigned long time, int tz)
        return timebuf;
 }
 
+const char *show_rfc2822_date(unsigned long time, int tz)
+{
+       struct tm *tm;
+       static char timebuf[200];
+
+       tm = time_to_tm(time, tz);
+       if (!tm)
+               return NULL;
+       sprintf(timebuf, "%.3s, %d %.3s %d %02d:%02d:%02d %+05d",
+               weekday_names[tm->tm_wday], tm->tm_mday,
+               month_names[tm->tm_mon], tm->tm_year + 1900,
+               tm->tm_hour, tm->tm_min, tm->tm_sec, tz);
+       return timebuf;
+}
+
 /*
  * Check these. And note how it doesn't do the summer-time conversion.
  *
diff --git a/diff.c b/diff.c
index 3a2a175..a7bb9b9 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -299,6 +299,7 @@ static void diffstat_consume(void *priv, char *line, unsigned long len)
 
 static const char pluses[] = "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++";
 static const char minuses[]= "----------------------------------------------------------------------";
+const char mime_boundary_leader[] = "------------";
 
 static void show_stats(struct diffstat_t* data)
 {
@@ -1988,7 +1989,10 @@ void diff_flush(struct diff_options *options)
                show_stats(diffstat);
                free(diffstat);
                diffstat = NULL;
-               putchar(options->line_termination);
+               if (options->stat_sep)
+                       fputs(options->stat_sep, stdout);
+               else
+                       putchar(options->line_termination);
        }
        for (i = 0; i < q->nr; i++) {
                struct diff_filepair *p = q->queue[i];
diff --git a/diff.h b/diff.h
index c672277..4fc597c 100644 (file)
--- a/diff.h
+++ b/diff.h
@@ -44,6 +44,7 @@ struct diff_options {
        int rename_limit;
        int setup;
        int abbrev;
+       const char *stat_sep;
 
        int nr_paths;
        const char **paths;
@@ -52,6 +53,8 @@ struct diff_options {
        add_remove_fn_t add_remove;
 };
 
+extern const char mime_boundary_leader[];
+
 extern void diff_tree_setup_paths(const char **paths, struct diff_options *);
 extern void diff_tree_release_paths(struct diff_options *);
 extern int diff_tree(struct tree_desc *t1, struct tree_desc *t2,
diff --git a/dir.c b/dir.c
new file mode 100644 (file)
index 0000000..d778ecd
--- /dev/null
+++ b/dir.c
@@ -0,0 +1,401 @@
+/*
+ * This handles recursive filename detection with exclude
+ * files, index knowledge etc..
+ *
+ * Copyright (C) Linus Torvalds, 2005-2006
+ *              Junio Hamano, 2005-2006
+ */
+#include <dirent.h>
+#include <fnmatch.h>
+
+#include "cache.h"
+#include "dir.h"
+
+int common_prefix(const char **pathspec)
+{
+       const char *path, *slash, *next;
+       int prefix;
+
+       if (!pathspec)
+               return 0;
+
+       path = *pathspec;
+       slash = strrchr(path, '/');
+       if (!slash)
+               return 0;
+
+       prefix = slash - path + 1;
+       while ((next = *++pathspec) != NULL) {
+               int len = strlen(next);
+               if (len >= prefix && !memcmp(path, next, len))
+                       continue;
+               for (;;) {
+                       if (!len)
+                               return 0;
+                       if (next[--len] != '/')
+                               continue;
+                       if (memcmp(path, next, len+1))
+                               continue;
+                       prefix = len + 1;
+                       break;
+               }
+       }
+       return prefix;
+}
+
+static int match_one(const char *match, const char *name, int namelen)
+{
+       int matchlen;
+
+       /* If the match was just the prefix, we matched */
+       matchlen = strlen(match);
+       if (!matchlen)
+               return 1;
+
+       /*
+        * If we don't match the matchstring exactly,
+        * we need to match by fnmatch
+        */
+       if (strncmp(match, name, matchlen))
+               return !fnmatch(match, name, 0);
+
+       /*
+        * If we did match the string exactly, we still
+        * need to make sure that it happened on a path
+        * component boundary (ie either the last character
+        * of the match was '/', or the next character of
+        * the name was '/' or the terminating NUL.
+        */
+       return  match[matchlen-1] == '/' ||
+               name[matchlen] == '/' ||
+               !name[matchlen];
+}
+
+int match_pathspec(const char **pathspec, const char *name, int namelen, int prefix, char *seen)
+{
+       int retval;
+       const char *match;
+
+       name += prefix;
+       namelen -= prefix;
+
+       for (retval = 0; (match = *pathspec++) != NULL; seen++) {
+               if (retval & *seen)
+                       continue;
+               match += prefix;
+               if (match_one(match, name, namelen)) {
+                       retval = 1;
+                       *seen = 1;
+               }
+       }
+       return retval;
+}
+
+void add_exclude(const char *string, const char *base,
+                int baselen, struct exclude_list *which)
+{
+       struct exclude *x = xmalloc(sizeof (*x));
+
+       x->pattern = string;
+       x->base = base;
+       x->baselen = baselen;
+       if (which->nr == which->alloc) {
+               which->alloc = alloc_nr(which->alloc);
+               which->excludes = realloc(which->excludes,
+                                         which->alloc * sizeof(x));
+       }
+       which->excludes[which->nr++] = x;
+}
+
+static int add_excludes_from_file_1(const char *fname,
+                                   const char *base,
+                                   int baselen,
+                                   struct exclude_list *which)
+{
+       int fd, i;
+       long size;
+       char *buf, *entry;
+
+       fd = open(fname, O_RDONLY);
+       if (fd < 0)
+               goto err;
+       size = lseek(fd, 0, SEEK_END);
+       if (size < 0)
+               goto err;
+       lseek(fd, 0, SEEK_SET);
+       if (size == 0) {
+               close(fd);
+               return 0;
+       }
+       buf = xmalloc(size+1);
+       if (read(fd, buf, size) != size)
+               goto err;
+       close(fd);
+
+       buf[size++] = '\n';
+       entry = buf;
+       for (i = 0; i < size; i++) {
+               if (buf[i] == '\n') {
+                       if (entry != buf + i && entry[0] != '#') {
+                               buf[i - (i && buf[i-1] == '\r')] = 0;
+                               add_exclude(entry, base, baselen, which);
+                       }
+                       entry = buf + i + 1;
+               }
+       }
+       return 0;
+
+ err:
+       if (0 <= fd)
+               close(fd);
+       return -1;
+}
+
+void add_excludes_from_file(struct dir_struct *dir, const char *fname)
+{
+       if (add_excludes_from_file_1(fname, "", 0,
+                                    &dir->exclude_list[EXC_FILE]) < 0)
+               die("cannot use %s as an exclude file", fname);
+}
+
+static int push_exclude_per_directory(struct dir_struct *dir, const char *base, int baselen)
+{
+       char exclude_file[PATH_MAX];
+       struct exclude_list *el = &dir->exclude_list[EXC_DIRS];
+       int current_nr = el->nr;
+
+       if (dir->exclude_per_dir) {
+               memcpy(exclude_file, base, baselen);
+               strcpy(exclude_file + baselen, dir->exclude_per_dir);
+               add_excludes_from_file_1(exclude_file, base, baselen, el);
+       }
+       return current_nr;
+}
+
+static void pop_exclude_per_directory(struct dir_struct *dir, int stk)
+{
+       struct exclude_list *el = &dir->exclude_list[EXC_DIRS];
+
+       while (stk < el->nr)
+               free(el->excludes[--el->nr]);
+}
+
+/* Scan the list and let the last match determines the fate.
+ * Return 1 for exclude, 0 for include and -1 for undecided.
+ */
+static int excluded_1(const char *pathname,
+                     int pathlen,
+                     struct exclude_list *el)
+{
+       int i;
+
+       if (el->nr) {
+               for (i = el->nr - 1; 0 <= i; i--) {
+                       struct exclude *x = el->excludes[i];
+                       const char *exclude = x->pattern;
+                       int to_exclude = 1;
+
+                       if (*exclude == '!') {
+                               to_exclude = 0;
+                               exclude++;
+                       }
+
+                       if (!strchr(exclude, '/')) {
+                               /* match basename */
+                               const char *basename = strrchr(pathname, '/');
+                               basename = (basename) ? basename+1 : pathname;
+                               if (fnmatch(exclude, basename, 0) == 0)
+                                       return to_exclude;
+                       }
+                       else {
+                               /* match with FNM_PATHNAME:
+                                * exclude has base (baselen long) implicitly
+                                * in front of it.
+                                */
+                               int baselen = x->baselen;
+                               if (*exclude == '/')
+                                       exclude++;
+
+                               if (pathlen < baselen ||
+                                   (baselen && pathname[baselen-1] != '/') ||
+                                   strncmp(pathname, x->base, baselen))
+                                   continue;
+
+                               if (fnmatch(exclude, pathname+baselen,
+                                           FNM_PATHNAME) == 0)
+                                       return to_exclude;
+                       }
+               }
+       }
+       return -1; /* undecided */
+}
+
+int excluded(struct dir_struct *dir, const char *pathname)
+{
+       int pathlen = strlen(pathname);
+       int st;
+
+       for (st = EXC_CMDL; st <= EXC_FILE; st++) {
+               switch (excluded_1(pathname, pathlen, &dir->exclude_list[st])) {
+               case 0:
+                       return 0;
+               case 1:
+                       return 1;
+               }
+       }
+       return 0;
+}
+
+static void add_name(struct dir_struct *dir, const char *pathname, int len)
+{
+       struct dir_entry *ent;
+
+       if (cache_name_pos(pathname, len) >= 0)
+               return;
+
+       if (dir->nr == dir->alloc) {
+               int alloc = alloc_nr(dir->alloc);
+               dir->alloc = alloc;
+               dir->entries = xrealloc(dir->entries, alloc*sizeof(ent));
+       }
+       ent = xmalloc(sizeof(*ent) + len + 1);
+       ent->len = len;
+       memcpy(ent->name, pathname, len);
+       ent->name[len] = 0;
+       dir->entries[dir->nr++] = ent;
+}
+
+static int dir_exists(const char *dirname, int len)
+{
+       int pos = cache_name_pos(dirname, len);
+       if (pos >= 0)
+               return 1;
+       pos = -pos-1;
+       if (pos >= active_nr) /* can't */
+               return 0;
+       return !strncmp(active_cache[pos]->name, dirname, len);
+}
+
+/*
+ * Read a directory tree. We currently ignore anything but
+ * directories, regular files and symlinks. That's because git
+ * doesn't handle them at all yet. Maybe that will change some
+ * day.
+ *
+ * Also, we ignore the name ".git" (even if it is not a directory).
+ * That likely will not change.
+ */
+static int read_directory_recursive(struct dir_struct *dir, const char *path, const char *base, int baselen)
+{
+       DIR *fdir = opendir(path);
+       int contents = 0;
+
+       if (fdir) {
+               int exclude_stk;
+               struct dirent *de;
+               char fullname[MAXPATHLEN + 1];
+               memcpy(fullname, base, baselen);
+
+               exclude_stk = push_exclude_per_directory(dir, base, baselen);
+
+               while ((de = readdir(fdir)) != NULL) {
+                       int len;
+
+                       if ((de->d_name[0] == '.') &&
+                           (de->d_name[1] == 0 ||
+                            !strcmp(de->d_name + 1, ".") ||
+                            !strcmp(de->d_name + 1, "git")))
+                               continue;
+                       len = strlen(de->d_name);
+                       memcpy(fullname + baselen, de->d_name, len+1);
+                       if (excluded(dir, fullname) != dir->show_ignored) {
+                               if (!dir->show_ignored || DTYPE(de) != DT_DIR) {
+                                       continue;
+                               }
+                       }
+
+                       switch (DTYPE(de)) {
+                       struct stat st;
+                       int subdir, rewind_base;
+                       default:
+                               continue;
+                       case DT_UNKNOWN:
+                               if (lstat(fullname, &st))
+                                       continue;
+                               if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))
+                                       break;
+                               if (!S_ISDIR(st.st_mode))
+                                       continue;
+                               /* fallthrough */
+                       case DT_DIR:
+                               memcpy(fullname + baselen + len, "/", 2);
+                               len++;
+                               rewind_base = dir->nr;
+                               subdir = read_directory_recursive(dir, fullname, fullname,
+                                                       baselen + len);
+                               if (dir->show_other_directories &&
+                                   (subdir || !dir->hide_empty_directories) &&
+                                   !dir_exists(fullname, baselen + len)) {
+                                       // Rewind the read subdirectory
+                                       while (dir->nr > rewind_base)
+                                               free(dir->entries[--dir->nr]);
+                                       break;
+                               }
+                               contents += subdir;
+                               continue;
+                       case DT_REG:
+                       case DT_LNK:
+                               break;
+                       }
+                       add_name(dir, fullname, baselen + len);
+                       contents++;
+               }
+               closedir(fdir);
+
+               pop_exclude_per_directory(dir, exclude_stk);
+       }
+
+       return contents;
+}
+
+static int cmp_name(const void *p1, const void *p2)
+{
+       const struct dir_entry *e1 = *(const struct dir_entry **)p1;
+       const struct dir_entry *e2 = *(const struct dir_entry **)p2;
+
+       return cache_name_compare(e1->name, e1->len,
+                                 e2->name, e2->len);
+}
+
+int read_directory(struct dir_struct *dir, const char *path, const char *base, int baselen)
+{
+       /*
+        * Make sure to do the per-directory exclude for all the
+        * directories leading up to our base.
+        */
+       if (baselen) {
+               if (dir->exclude_per_dir) {
+                       char *p, *pp = xmalloc(baselen+1);
+                       memcpy(pp, base, baselen+1);
+                       p = pp;
+                       while (1) {
+                               char save = *p;
+                               *p = 0;
+                               push_exclude_per_directory(dir, pp, p-pp);
+                               *p++ = save;
+                               if (!save)
+                                       break;
+                               p = strchr(p, '/');
+                               if (p)
+                                       p++;
+                               else
+                                       p = pp + baselen;
+                       }
+                       free(pp);
+               }
+       }
+
+       read_directory_recursive(dir, path, base, baselen);
+       qsort(dir->entries, dir->nr, sizeof(struct dir_entry *), cmp_name);
+       return dir->nr;
+}
diff --git a/dir.h b/dir.h
new file mode 100644 (file)
index 0000000..56a1b7f
--- /dev/null
+++ b/dir.h
@@ -0,0 +1,51 @@
+#ifndef DIR_H
+#define DIR_H
+
+/*
+ * We maintain three exclude pattern lists:
+ * EXC_CMDL lists patterns explicitly given on the command line.
+ * EXC_DIRS lists patterns obtained from per-directory ignore files.
+ * EXC_FILE lists patterns from fallback ignore files.
+ */
+#define EXC_CMDL 0
+#define EXC_DIRS 1
+#define EXC_FILE 2
+
+
+struct dir_entry {
+       int len;
+       char name[FLEX_ARRAY]; /* more */
+};
+
+struct exclude_list {
+       int nr;
+       int alloc;
+       struct exclude {
+               const char *pattern;
+               const char *base;
+               int baselen;
+       } **excludes;
+};
+
+struct dir_struct {
+       int nr, alloc;
+       unsigned int show_ignored:1,
+                    show_other_directories:1,
+                    hide_empty_directories:1;
+       struct dir_entry **entries;
+
+       /* Exclude info */
+       const char *exclude_per_dir;
+       struct exclude_list exclude_list[3];
+};
+
+extern int common_prefix(const char **pathspec);
+extern int match_pathspec(const char **pathspec, const char *name, int namelen, int prefix, char *seen);
+
+extern int read_directory(struct dir_struct *, const char *path, const char *base, int baselen);
+extern int excluded(struct dir_struct *, const char *);
+extern void add_excludes_from_file(struct dir_struct *, const char *fname);
+extern void add_exclude(const char *string, const char *base,
+                       int baselen, struct exclude_list *which);
+
+#endif
diff --git a/dump-cache-tree.c b/dump-cache-tree.c
new file mode 100644 (file)
index 0000000..1ccaf51
--- /dev/null
@@ -0,0 +1,64 @@
+#include "cache.h"
+#include "tree.h"
+#include "cache-tree.h"
+
+
+static void dump_one(struct cache_tree *it, const char *pfx, const char *x)
+{
+       if (it->entry_count < 0)
+               printf("%-40s %s%s (%d subtrees)\n",
+                      "invalid", x, pfx, it->subtree_nr);
+       else
+               printf("%s %s%s (%d entries, %d subtrees)\n",
+                      sha1_to_hex(it->sha1), x, pfx,
+                      it->entry_count, it->subtree_nr);
+}
+
+static int dump_cache_tree(struct cache_tree *it,
+                          struct cache_tree *ref,
+                          const char *pfx)
+{
+       int i;
+       int errs = 0;
+
+       if (!it || !ref)
+               /* missing in either */
+               return 0;
+
+       if (it->entry_count < 0) {
+               dump_one(it, pfx, "");
+               dump_one(ref, pfx, "#(ref) ");
+               if (it->subtree_nr != ref->subtree_nr)
+                       errs = 1;
+       }
+       else {
+               dump_one(it, pfx, "");
+               if (memcmp(it->sha1, ref->sha1, 20) ||
+                   ref->entry_count != it->entry_count ||
+                   ref->subtree_nr != it->subtree_nr) {
+                       dump_one(ref, pfx, "#(ref) ");
+                       errs = 1;
+               }
+       }
+
+       for (i = 0; i < it->subtree_nr; i++) {
+               char path[PATH_MAX];
+               struct cache_tree_sub *down = it->down[i];
+               struct cache_tree_sub *rdwn;
+
+               rdwn = cache_tree_sub(ref, down->name);
+               sprintf(path, "%s%.*s/", pfx, down->namelen, down->name);
+               if (dump_cache_tree(down->cache_tree, rdwn->cache_tree, path))
+                       errs = 1;
+       }
+       return errs;
+}
+
+int main(int ac, char **av)
+{
+       struct cache_tree *another = cache_tree();
+       if (read_cache() < 0)
+               die("unable to read index file");
+       cache_tree_update(another, active_cache, active_nr, 0, 1);
+       return dump_cache_tree(active_cache_tree, another, "");
+}
index a3bcad0..8daa93d 100644 (file)
@@ -262,22 +262,58 @@ static void mark_recent_complete_commits(unsigned long cutoff)
 
 static void filter_refs(struct ref **refs, int nr_match, char **match)
 {
-       struct ref *prev, *current, *next;
-
-       for (prev = NULL, current = *refs; current; current = next) {
-               next = current->next;
-               if ((!memcmp(current->name, "refs/", 5) &&
-                    check_ref_format(current->name + 5)) ||
-                   (!fetch_all &&
-                    !path_match(current->name, nr_match, match))) {
-                       if (prev == NULL)
-                               *refs = next;
-                       else
-                               prev->next = next;
-                       free(current);
-               } else
-                       prev = current;
+       struct ref **return_refs;
+       struct ref *newlist = NULL;
+       struct ref **newtail = &newlist;
+       struct ref *ref, *next;
+       struct ref *fastarray[32];
+
+       if (nr_match && !fetch_all) {
+               if (ARRAY_SIZE(fastarray) < nr_match)
+                       return_refs = xcalloc(nr_match, sizeof(struct ref *));
+               else {
+                       return_refs = fastarray;
+                       memset(return_refs, 0, sizeof(struct ref *) * nr_match);
+               }
+       }
+       else
+               return_refs = NULL;
+
+       for (ref = *refs; ref; ref = next) {
+               next = ref->next;
+               if (!memcmp(ref->name, "refs/", 5) &&
+                   check_ref_format(ref->name + 5))
+                       ; /* trash */
+               else if (fetch_all) {
+                       *newtail = ref;
+                       ref->next = NULL;
+                       newtail = &ref->next;
+                       continue;
+               }
+               else {
+                       int order = path_match(ref->name, nr_match, match);
+                       if (order) {
+                               return_refs[order-1] = ref;
+                               continue; /* we will link it later */
+                       }
+               }
+               free(ref);
+       }
+
+       if (!fetch_all) {
+               int i;
+               for (i = 0; i < nr_match; i++) {
+                       ref = return_refs[i];
+                       if (ref) {
+                               *newtail = ref;
+                               ref->next = NULL;
+                               newtail = &ref->next;
+                       }
+               }
+               if (return_refs != fastarray)
+                       free(return_refs);
        }
+       *refs = newlist;
 }
 
 static int everything_local(struct ref **refs, int nr_match, char **match)
index 59b2590..1922b6d 100644 (file)
@@ -8,6 +8,7 @@
 #include "tag.h"
 #include "refs.h"
 #include "pack.h"
+#include "cache-tree.h"
 
 #define REACHABLE 0x0001
 
@@ -438,6 +439,28 @@ static int fsck_head_link(void)
        return 0;
 }
 
+static int fsck_cache_tree(struct cache_tree *it)
+{
+       int i;
+       int err = 0;
+
+       if (0 <= it->entry_count) {
+               struct object *obj = parse_object(it->sha1);
+               if (!obj) {
+                       error("%s: invalid sha1 pointer in cache-tree",
+                             sha1_to_hex(it->sha1));
+                       return 1;
+               }
+               mark_reachable(obj, REACHABLE);
+               obj->used = 1;
+               if (obj->type != tree_type)
+                       err |= objerror(obj, "non-tree in cache-tree");
+       }
+       for (i = 0; i < it->subtree_nr; i++)
+               err |= fsck_cache_tree(it->down[i]->cache_tree);
+       return err;
+}
+
 int main(int argc, char **argv)
 {
        int i, heads;
@@ -547,6 +570,8 @@ int main(int argc, char **argv)
                        obj->used = 1;
                        mark_reachable(obj, REACHABLE);
                }
+               if (active_cache_tree)
+                       fsck_cache_tree(active_cache_tree);
        }
 
        check_connectivity();
diff --git a/git-add.sh b/git-add.sh
deleted file mode 100755 (executable)
index d6a4bc7..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-#!/bin/sh
-
-USAGE='[-n] [-v] <file>...'
-SUBDIRECTORY_OK='Yes'
-. git-sh-setup
-
-show_only=
-verbose=
-while : ; do
-  case "$1" in
-    -n)
-       show_only=true
-       ;;
-    -v)
-       verbose=--verbose
-       ;;
-    --)
-       shift
-       break
-       ;;
-    -*)
-       usage
-       ;;
-    *)
-       break
-       ;;
-  esac
-  shift
-done
-
-# Check misspelled pathspec
-case "$#" in
-0)     ;;
-*)
-       git-ls-files --error-unmatch --others --cached -- "$@" >/dev/null || {
-               echo >&2 "Maybe you misspelled it?"
-               exit 1
-       }
-       ;;
-esac
-
-if test -f "$GIT_DIR/info/exclude"
-then
-       git-ls-files -z \
-       --exclude-from="$GIT_DIR/info/exclude" \
-       --others --exclude-per-directory=.gitignore -- "$@"
-else
-       git-ls-files -z \
-       --others --exclude-per-directory=.gitignore -- "$@"
-fi |
-case "$show_only" in
-true)
-       xargs -0 echo ;;
-*)
-       git-update-index --add $verbose -z --stdin ;;
-esac
diff --git a/git-format-patch.sh b/git-format-patch.sh
deleted file mode 100755 (executable)
index 8a16ead..0000000
+++ /dev/null
@@ -1,344 +0,0 @@
-#!/bin/sh
-#
-# Copyright (c) 2005 Junio C Hamano
-#
-
-USAGE='[-n | -k] [-o <dir> | --stdout] [--signoff] [--check] [--diff-options] [--attach] <his> [<mine>]'
-LONG_USAGE='Prepare each commit with its patch since <mine> head forked from
-<his> head, one file per patch formatted to resemble UNIX mailbox
-format, for e-mail submission or use with git-am.
-
-Each output file is numbered sequentially from 1, and uses the
-first line of the commit message (massaged for pathname safety)
-as the filename.
-
-When -o is specified, output files are created in <dir>; otherwise
-they are created in the current working directory.  This option
-is ignored if --stdout is specified.
-
-When -n is specified, instead of "[PATCH] Subject", the first
-line is formatted as "[PATCH N/M] Subject", unless you have only
-one patch.
-
-When --attach is specified, patches are attached, not inlined.'
-
-. git-sh-setup
-
-# Force diff to run in C locale.
-LANG=C LC_ALL=C
-export LANG LC_ALL
-
-diff_opts=
-LF='
-'
-
-outdir=./
-while case "$#" in 0) break;; esac
-do
-    case "$1" in
-    -c|--c|--ch|--che|--chec|--check)
-    check=t ;;
-    -a|--a|--au|--aut|--auth|--autho|--author|\
-    -d|--d|--da|--dat|--date|\
-    -m|--m|--mb|--mbo|--mbox) # now noop
-    ;;
-    --at|--att|--atta|--attac|--attach)
-    attach=t ;;
-    -k|--k|--ke|--kee|--keep|--keep-|--keep-s|--keep-su|--keep-sub|\
-    --keep-subj|--keep-subje|--keep-subjec|--keep-subject)
-    keep_subject=t ;;
-    -n|--n|--nu|--num|--numb|--numbe|--number|--numbere|--numbered)
-    numbered=t ;;
-    -s|--s|--si|--sig|--sign|--signo|--signof|--signoff)
-    signoff=t ;;
-    --st|--std|--stdo|--stdou|--stdout)
-    stdout=t ;;
-    -o=*|--o=*|--ou=*|--out=*|--outp=*|--outpu=*|--output=*|--output-=*|\
-    --output-d=*|--output-di=*|--output-dir=*|--output-dire=*|\
-    --output-direc=*|--output-direct=*|--output-directo=*|\
-    --output-director=*|--output-directory=*)
-    outdir=`expr "$1" : '-[^=]*=\(.*\)'` ;;
-    -o|--o|--ou|--out|--outp|--outpu|--output|--output-|--output-d|\
-    --output-di|--output-dir|--output-dire|--output-direc|--output-direct|\
-    --output-directo|--output-director|--output-directory)
-    case "$#" in 1) usage ;; esac; shift
-    outdir="$1" ;;
-    -h|--h|--he|--hel|--help)
-        usage
-       ;;
-    -*' '* | -*"$LF"* | -*'    '*)
-       # Ignore diff option that has whitespace for now.
-       ;;
-    -*)        diff_opts="$diff_opts$1 " ;;
-    *) break ;;
-    esac
-    shift
-done
-
-case "$keep_subject$numbered" in
-tt)
-       die '--keep-subject and --numbered are incompatible.' ;;
-esac
-
-tmp=.tmp-series$$
-trap 'rm -f $tmp-*' 0 1 2 3 15
-
-series=$tmp-series
-commsg=$tmp-commsg
-filelist=$tmp-files
-
-# Backward compatible argument parsing hack.
-#
-# Historically, we supported:
-# 1. "rev1"            is equivalent to "rev1..HEAD"
-# 2. "rev1..rev2"
-# 3. "rev1" "rev2      is equivalent to "rev1..rev2"
-#
-# We want to take a sequence of "rev1..rev2" in general.
-# Also, "rev1.." should mean "rev1..HEAD"; git-diff users are
-# familiar with that syntax.
-
-case "$#,$1$2" in
-1,?*..?*)
-       # single "rev1..rev2"
-       ;;
-1,?*..)
-       # single "rev1.." should mean "rev1..HEAD"
-       set x "$1"HEAD
-       shift
-       ;;
-1,*)
-       # single rev1
-       set x "$1..HEAD"
-       shift
-       ;;
-2,?*..?*)
-       # not traditional "rev1" "rev2"
-       ;;
-2,*)
-       set x "$1..$2"
-       shift
-       ;;
-esac
-
-# Now we have what we want in $@
-for revpair
-do
-       case "$revpair" in
-       ?*..?*)
-               rev1=`expr "z$revpair" : 'z\(.*\)\.\.'`
-               rev2=`expr "z$revpair" : 'z.*\.\.\(.*\)'`
-               ;;
-       *)
-               rev1="$revpair^"
-               rev2="$revpair"
-               ;;
-       esac
-       git-rev-parse --verify "$rev1^0" >/dev/null 2>&1 ||
-               die "Not a valid rev $rev1 ($revpair)"
-       git-rev-parse --verify "$rev2^0" >/dev/null 2>&1 ||
-               die "Not a valid rev $rev2 ($revpair)"
-       git-cherry -v "$rev1" "$rev2" |
-       while read sign rev comment
-       do
-               case "$sign" in
-               '-')
-                       echo >&2 "Merged already: $comment"
-                       ;;
-               *)
-                       echo $rev
-                       ;;
-               esac
-       done
-done >$series
-
-me=`git-var GIT_AUTHOR_IDENT | sed -e 's/>.*/>/'`
-headers=`git-repo-config --get format.headers`
-case "$attach" in
-"") ;;
-*)
-       mimemagic="050802040500080604070107"
-esac
-
-case "$outdir" in
-*/) ;;
-*) outdir="$outdir/" ;;
-esac
-test -d "$outdir" || mkdir -p "$outdir" || exit
-
-titleScript='
-       /./d
-       /^$/n
-       s/^\[PATCH[^]]*\] *//
-       s/[^-a-z.A-Z_0-9]/-/g
-        s/\.\.\.*/\./g
-       s/\.*$//
-       s/--*/-/g
-       s/^-//
-       s/-$//
-       s/$/./
-       p
-       q
-'
-
-process_one () {
-       perl -w -e '
-my ($keep_subject, $num, $signoff, $headers, $mimemagic, $commsg) = @ARGV;
-my ($signoff_pattern, $done_header, $done_subject, $done_separator, $signoff_seen,
-    $last_was_signoff);
-
-if ($signoff) {
-       $signoff = "Signed-off-by: " . `git-var GIT_COMMITTER_IDENT`;
-       $signoff =~ s/>.*/>/;
-       $signoff_pattern = quotemeta($signoff);
-}
-
-my @weekday_names = qw(Sun Mon Tue Wed Thu Fri Sat);
-my @month_names = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
-
-sub show_date {
-    my ($time, $tz) = @_;
-    my $minutes = abs($tz);
-    $minutes = int($minutes / 100) * 60 + ($minutes % 100);
-    if ($tz < 0) {
-        $minutes = -$minutes;
-    }
-    my $t = $time + $minutes * 60;
-    my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday) = gmtime($t);
-    return sprintf("%s, %d %s %d %02d:%02d:%02d %+05d",
-                  $weekday_names[$wday], $mday,
-                  $month_names[$mon], $year+1900,
-                  $hour, $min, $sec, $tz);
-}
-
-print "From nobody Mon Sep 17 00:00:00 2001\n";
-open FH, "git stripspace <$commsg |" or die "open $commsg pipe";
-while (<FH>) {
-    unless ($done_header) {
-       if (/^$/) {
-           $done_header = 1;
-       }
-       elsif (/^author (.*>) (.*)$/) {
-           my ($author_ident, $author_date) = ($1, $2);
-           my ($utc, $off) = ($author_date =~ /^(\d+) ([-+]?\d+)$/);
-           $author_date = show_date($utc, $off);
-
-           print "From: $author_ident\n";
-           print "Date: $author_date\n";
-       }
-       next;
-    }
-    unless ($done_subject) {
-       unless ($keep_subject) {
-           s/^\[PATCH[^]]*\]\s*//;
-           s/^/[PATCH$num] /;
-       }
-       if ($headers) {
-           print "$headers\n";
-       }
-        print "Subject: $_";
-       if ($mimemagic) {
-           print "MIME-Version: 1.0\n";
-           print "Content-Type: multipart/mixed;\n";
-           print " boundary=\"------------$mimemagic\"\n";
-           print "\n";
-           print "This is a multi-part message in MIME format.\n";
-           print "--------------$mimemagic\n";
-           print "Content-Type: text/plain; charset=UTF-8; format=fixed\n";
-           print "Content-Transfer-Encoding: 8bit\n";
-       }
-       $done_subject = 1;
-       next;
-    }
-    unless ($done_separator) {
-        print "\n";
-        $done_separator = 1;
-        next if (/^$/);
-    }
-
-    $last_was_signoff = 0;
-    if (/Signed-off-by:/i) {
-        if ($signoff ne "" && /Signed-off-by:\s*$signoff_pattern$/i) {
-           $signoff_seen = 1;
-       }
-    }
-    print $_;
-}
-if (!$signoff_seen && $signoff ne "") {
-    if (!$last_was_signoff) {
-        print "\n";
-    }
-    print "$signoff\n";
-}
-print "\n---\n\n";
-close FH or die "close $commsg pipe";
-' "$keep_subject" "$num" "$signoff" "$headers" "$mimemagic" $commsg
-
-       git-diff-tree -p --stat --summary $diff_opts "$commit"
-       echo
-       case "$mimemagic" in
-       '');;
-       *)
-               echo "--------------$mimemagic"
-               echo "Content-Type: text/x-patch;"
-               echo " name=\"$commit.diff\""
-               echo "Content-Transfer-Encoding: 8bit"
-               echo "Content-Disposition: inline;"
-               echo " filename=\"$commit.diff\""
-               echo
-       esac
-       git-diff-tree -p $diff_opts "$commit"
-       case "$mimemagic" in
-       '')
-               echo "-- "
-               echo "@@GIT_VERSION@@"
-               ;;
-       *)
-               echo
-               echo "--------------$mimemagic--"
-               echo
-               ;;
-       esac
-       echo
-}
-
-total=`wc -l <$series | tr -dc "[0-9]"`
-case "$total,$numbered" in
-1,*)
-       numfmt='' ;;
-*,t)
-       numfmt=`echo "$total" | wc -c`
-       numfmt=$(($numfmt-1))
-       numfmt=" %0${numfmt}d/$total"
-esac
-
-i=1
-while read commit
-do
-    git-cat-file commit "$commit" | git-stripspace >$commsg
-    title=`sed -ne "$titleScript" <$commsg`
-    case "$numbered" in
-    '') num= ;;
-    *)
-        num=`printf "$numfmt" $i` ;;
-    esac
-
-    file=`printf '%04d-%stxt' $i "$title"`
-    if test '' = "$stdout"
-    then
-           echo "$file"
-           process_one >"$outdir$file"
-           if test t = "$check"
-           then
-               # This is slightly modified from Andrew Morton's Perfect Patch.
-               # Lines you introduce should not have trailing whitespace.
-               # Also check for an indentation that has SP before a TAB.
-               grep -n '^+\([  ]*      .*\|.*[         ]\)$' "$outdir$file"
-               :
-           fi
-    else
-           echo >&2 "$file"
-           process_one
-    fi
-    i=`expr "$i" + 1`
-done <$series
index 6ff6088..e6b57b8 100755 (executable)
@@ -152,6 +152,6 @@ then
        exit 0
 fi
 
-git-format-patch -k --stdout --full-index "$upstream" ORIG_HEAD |
+git-format-patch -k --stdout --full-index "$upstream"..ORIG_HEAD |
 git am --binary -3 -k --resolvemsg="$RESOLVEMSG"
 
diff --git a/git-rm.sh b/git-rm.sh
deleted file mode 100755 (executable)
index fda4541..0000000
--- a/git-rm.sh
+++ /dev/null
@@ -1,70 +0,0 @@
-#!/bin/sh
-
-USAGE='[-f] [-n] [-v] [--] <file>...'
-SUBDIRECTORY_OK='Yes'
-. git-sh-setup
-
-remove_files=
-show_only=
-verbose=
-while : ; do
-  case "$1" in
-    -f)
-       remove_files=true
-       ;;
-    -n)
-       show_only=true
-       ;;
-    -v)
-       verbose=--verbose
-       ;;
-    --)
-       shift; break
-       ;;
-    -*)
-       usage
-       ;;
-    *)
-       break
-       ;;
-  esac
-  shift
-done
-
-# This is typo-proofing. If some paths match and some do not, we want
-# to do nothing.
-case "$#" in
-0)     ;;
-*)
-       git-ls-files --error-unmatch -- "$@" >/dev/null || {
-               echo >&2 "Maybe you misspelled it?"
-               exit 1
-       }
-       ;;
-esac
-
-if test -f "$GIT_DIR/info/exclude"
-then
-       git-ls-files -z \
-       --exclude-from="$GIT_DIR/info/exclude" \
-       --exclude-per-directory=.gitignore -- "$@"
-else
-       git-ls-files -z \
-       --exclude-per-directory=.gitignore -- "$@"
-fi |
-case "$show_only,$remove_files" in
-true,*)
-       xargs -0 echo
-       ;;
-*,true)
-       xargs -0 sh -c "
-               while [ \$# -gt 0 ]; do
-                       file=\$1; shift
-                       rm -- \"\$file\" && git-update-index --remove $verbose \"\$file\"
-               done
-       " inline
-       ;;
-*)
-       git-update-index --force-remove $verbose -z --stdin
-       ;;
-esac
diff --git a/git.c b/git.c
index 3216d31..a4ddac7 100644 (file)
--- a/git.c
+++ b/git.c
@@ -47,11 +47,16 @@ static void handle_internal_command(int argc, const char **argv, char **envp)
                { "whatchanged", cmd_whatchanged },
                { "show", cmd_show },
                { "push", cmd_push },
+               { "format-patch", cmd_format_patch },
                { "count-objects", cmd_count_objects },
                { "diff", cmd_diff },
                { "grep", cmd_grep },
+               { "rm", cmd_rm },
+               { "add", cmd_add },
                { "rev-list", cmd_rev_list },
                { "init-db", cmd_init_db },
+               { "tar-tree", cmd_tar_tree },
+               { "upload-tar", cmd_upload_tar },
                { "check-ref-format", cmd_check_ref_format }
        };
        int i;
index b90ba67..7e23e42 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, *after_subject = NULL;
 
        opt->loginfo = NULL;
        if (!opt->verbose_header) {
@@ -49,19 +50,67 @@ 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) {
+               char *sha1 = sha1_to_hex(commit->object.sha1);
+               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 Mon Sep 17 00:00:00 2001\n", sha1);
+               if (opt->mime_boundary) {
+                       static char subject_buffer[1024];
+                       static char buffer[1024];
+                       snprintf(subject_buffer, sizeof(subject_buffer) - 1,
+                                "MIME-Version: 1.0\n"
+                                "Content-Type: multipart/mixed;\n"
+                                " boundary=\"%s%s\"\n"
+                                "\n"
+                                "This is a multi-part message in MIME "
+                                "format.\n"
+                                "--%s%s\n"
+                                "Content-Type: text/plain; "
+                                "charset=UTF-8; format=fixed\n"
+                                "Content-Transfer-Encoding: 8bit\n\n",
+                                mime_boundary_leader, opt->mime_boundary,
+                                mime_boundary_leader, opt->mime_boundary);
+                       after_subject = subject_buffer;
+
+                       snprintf(buffer, sizeof(buffer) - 1,
+                                "--%s%s\n"
+                                "Content-Type: text/x-patch;\n"
+                                " name=\"%s.diff\"\n"
+                                "Content-Transfer-Encoding: 8bit\n"
+                                "Content-Disposition: inline;\n"
+                                " filename=\"%s.diff\"\n\n",
+                                mime_boundary_leader, opt->mime_boundary,
+                                sha1, sha1);
+                       opt->diffopt.stat_sep = buffer;
+               }
+       } 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, after_subject);
        printf("%s%s%s", this_header, extra, sep);
 }
 
@@ -166,15 +215,18 @@ static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log
 int log_tree_commit(struct rev_info *opt, struct commit *commit)
 {
        struct log_info log;
+       int shown;
 
        log.commit = commit;
        log.parent = NULL;
        opt->loginfo = &log;
 
-       if (!log_tree_diff(opt, commit, &log) && opt->loginfo && opt->always_show_header) {
+       shown = log_tree_diff(opt, commit, &log);
+       if (!shown && opt->loginfo && opt->always_show_header) {
                log.parent = NULL;
                show_log(opt, opt->loginfo, "");
+               shown = 1;
        }
        opt->loginfo = NULL;
-       return 0;
+       return shown;
 }
index 4a4af1c..dfe1481 100644 (file)
@@ -5,23 +5,20 @@
  *
  * Copyright (C) Linus Torvalds, 2005
  */
-#include <dirent.h>
 #include <fnmatch.h>
 
 #include "cache.h"
 #include "quote.h"
+#include "dir.h"
 
 static int abbrev = 0;
 static int show_deleted = 0;
 static int show_cached = 0;
 static int show_others = 0;
-static int show_ignored = 0;
 static int show_stage = 0;
 static int show_unmerged = 0;
 static int show_modified = 0;
 static int show_killed = 0;
-static int show_other_directories = 0;
-static int hide_empty_directories = 0;
 static int show_valid_bit = 0;
 static int line_terminator = '\n';
 
@@ -38,309 +35,6 @@ static const char *tag_other = "";
 static const char *tag_killed = "";
 static const char *tag_modified = "";
 
-static const char *exclude_per_dir = NULL;
-
-/* We maintain three exclude pattern lists:
- * EXC_CMDL lists patterns explicitly given on the command line.
- * EXC_DIRS lists patterns obtained from per-directory ignore files.
- * EXC_FILE lists patterns from fallback ignore files.
- */
-#define EXC_CMDL 0
-#define EXC_DIRS 1
-#define EXC_FILE 2
-static struct exclude_list {
-       int nr;
-       int alloc;
-       struct exclude {
-               const char *pattern;
-               const char *base;
-               int baselen;
-       } **excludes;
-} exclude_list[3];
-
-static void add_exclude(const char *string, const char *base,
-                       int baselen, struct exclude_list *which)
-{
-       struct exclude *x = xmalloc(sizeof (*x));
-
-       x->pattern = string;
-       x->base = base;
-       x->baselen = baselen;
-       if (which->nr == which->alloc) {
-               which->alloc = alloc_nr(which->alloc);
-               which->excludes = realloc(which->excludes,
-                                         which->alloc * sizeof(x));
-       }
-       which->excludes[which->nr++] = x;
-}
-
-static int add_excludes_from_file_1(const char *fname,
-                                   const char *base,
-                                   int baselen,
-                                   struct exclude_list *which)
-{
-       int fd, i;
-       long size;
-       char *buf, *entry;
-
-       fd = open(fname, O_RDONLY);
-       if (fd < 0)
-               goto err;
-       size = lseek(fd, 0, SEEK_END);
-       if (size < 0)
-               goto err;
-       lseek(fd, 0, SEEK_SET);
-       if (size == 0) {
-               close(fd);
-               return 0;
-       }
-       buf = xmalloc(size+1);
-       if (read(fd, buf, size) != size)
-               goto err;
-       close(fd);
-
-       buf[size++] = '\n';
-       entry = buf;
-       for (i = 0; i < size; i++) {
-               if (buf[i] == '\n') {
-                       if (entry != buf + i && entry[0] != '#') {
-                               buf[i - (i && buf[i-1] == '\r')] = 0;
-                               add_exclude(entry, base, baselen, which);
-                       }
-                       entry = buf + i + 1;
-               }
-       }
-       return 0;
-
- err:
-       if (0 <= fd)
-               close(fd);
-       return -1;
-}
-
-static void add_excludes_from_file(const char *fname)
-{
-       if (add_excludes_from_file_1(fname, "", 0,
-                                    &exclude_list[EXC_FILE]) < 0)
-               die("cannot use %s as an exclude file", fname);
-}
-
-static int push_exclude_per_directory(const char *base, int baselen)
-{
-       char exclude_file[PATH_MAX];
-       struct exclude_list *el = &exclude_list[EXC_DIRS];
-       int current_nr = el->nr;
-
-       if (exclude_per_dir) {
-               memcpy(exclude_file, base, baselen);
-               strcpy(exclude_file + baselen, exclude_per_dir);
-               add_excludes_from_file_1(exclude_file, base, baselen, el);
-       }
-       return current_nr;
-}
-
-static void pop_exclude_per_directory(int stk)
-{
-       struct exclude_list *el = &exclude_list[EXC_DIRS];
-
-       while (stk < el->nr)
-               free(el->excludes[--el->nr]);
-}
-
-/* Scan the list and let the last match determines the fate.
- * Return 1 for exclude, 0 for include and -1 for undecided.
- */
-static int excluded_1(const char *pathname,
-                     int pathlen,
-                     struct exclude_list *el)
-{
-       int i;
-
-       if (el->nr) {
-               for (i = el->nr - 1; 0 <= i; i--) {
-                       struct exclude *x = el->excludes[i];
-                       const char *exclude = x->pattern;
-                       int to_exclude = 1;
-
-                       if (*exclude == '!') {
-                               to_exclude = 0;
-                               exclude++;
-                       }
-
-                       if (!strchr(exclude, '/')) {
-                               /* match basename */
-                               const char *basename = strrchr(pathname, '/');
-                               basename = (basename) ? basename+1 : pathname;
-                               if (fnmatch(exclude, basename, 0) == 0)
-                                       return to_exclude;
-                       }
-                       else {
-                               /* match with FNM_PATHNAME:
-                                * exclude has base (baselen long) implicitly
-                                * in front of it.
-                                */
-                               int baselen = x->baselen;
-                               if (*exclude == '/')
-                                       exclude++;
-
-                               if (pathlen < baselen ||
-                                   (baselen && pathname[baselen-1] != '/') ||
-                                   strncmp(pathname, x->base, baselen))
-                                   continue;
-
-                               if (fnmatch(exclude, pathname+baselen,
-                                           FNM_PATHNAME) == 0)
-                                       return to_exclude;
-                       }
-               }
-       }
-       return -1; /* undecided */
-}
-
-static int excluded(const char *pathname)
-{
-       int pathlen = strlen(pathname);
-       int st;
-
-       for (st = EXC_CMDL; st <= EXC_FILE; st++) {
-               switch (excluded_1(pathname, pathlen, &exclude_list[st])) {
-               case 0:
-                       return 0;
-               case 1:
-                       return 1;
-               }
-       }
-       return 0;
-}
-
-struct nond_on_fs {
-       int len;
-       char name[FLEX_ARRAY]; /* more */
-};
-
-static struct nond_on_fs **dir;
-static int nr_dir;
-static int dir_alloc;
-
-static void add_name(const char *pathname, int len)
-{
-       struct nond_on_fs *ent;
-
-       if (cache_name_pos(pathname, len) >= 0)
-               return;
-
-       if (nr_dir == dir_alloc) {
-               dir_alloc = alloc_nr(dir_alloc);
-               dir = xrealloc(dir, dir_alloc*sizeof(ent));
-       }
-       ent = xmalloc(sizeof(*ent) + len + 1);
-       ent->len = len;
-       memcpy(ent->name, pathname, len);
-       ent->name[len] = 0;
-       dir[nr_dir++] = ent;
-}
-
-static int dir_exists(const char *dirname, int len)
-{
-       int pos = cache_name_pos(dirname, len);
-       if (pos >= 0)
-               return 1;
-       pos = -pos-1;
-       if (pos >= active_nr) /* can't */
-               return 0;
-       return !strncmp(active_cache[pos]->name, dirname, len);
-}
-
-/*
- * Read a directory tree. We currently ignore anything but
- * directories, regular files and symlinks. That's because git
- * doesn't handle them at all yet. Maybe that will change some
- * day.
- *
- * Also, we ignore the name ".git" (even if it is not a directory).
- * That likely will not change.
- */
-static int read_directory(const char *path, const char *base, int baselen)
-{
-       DIR *fdir = opendir(path);
-       int contents = 0;
-
-       if (fdir) {
-               int exclude_stk;
-               struct dirent *de;
-               char fullname[MAXPATHLEN + 1];
-               memcpy(fullname, base, baselen);
-
-               exclude_stk = push_exclude_per_directory(base, baselen);
-
-               while ((de = readdir(fdir)) != NULL) {
-                       int len;
-
-                       if ((de->d_name[0] == '.') &&
-                           (de->d_name[1] == 0 ||
-                            !strcmp(de->d_name + 1, ".") ||
-                            !strcmp(de->d_name + 1, "git")))
-                               continue;
-                       len = strlen(de->d_name);
-                       memcpy(fullname + baselen, de->d_name, len+1);
-                       if (excluded(fullname) != show_ignored) {
-                               if (!show_ignored || DTYPE(de) != DT_DIR) {
-                                       continue;
-                               }
-                       }
-
-                       switch (DTYPE(de)) {
-                       struct stat st;
-                       int subdir, rewind_base;
-                       default:
-                               continue;
-                       case DT_UNKNOWN:
-                               if (lstat(fullname, &st))
-                                       continue;
-                               if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))
-                                       break;
-                               if (!S_ISDIR(st.st_mode))
-                                       continue;
-                               /* fallthrough */
-                       case DT_DIR:
-                               memcpy(fullname + baselen + len, "/", 2);
-                               len++;
-                               rewind_base = nr_dir;
-                               subdir = read_directory(fullname, fullname,
-                                                       baselen + len);
-                               if (show_other_directories &&
-                                   (subdir || !hide_empty_directories) &&
-                                   !dir_exists(fullname, baselen + len)) {
-                                       // Rewind the read subdirectory
-                                       while (nr_dir > rewind_base)
-                                               free(dir[--nr_dir]);
-                                       break;
-                               }
-                               contents += subdir;
-                               continue;
-                       case DT_REG:
-                       case DT_LNK:
-                               break;
-                       }
-                       add_name(fullname, baselen + len);
-                       contents++;
-               }
-               closedir(fdir);
-
-               pop_exclude_per_directory(exclude_stk);
-       }
-
-       return contents;
-}
-
-static int cmp_name(const void *p1, const void *p2)
-{
-       const struct nond_on_fs *e1 = *(const struct nond_on_fs **)p1;
-       const struct nond_on_fs *e2 = *(const struct nond_on_fs **)p2;
-
-       return cache_name_compare(e1->name, e1->len,
-                                 e2->name, e2->len);
-}
 
 /*
  * Match a pathspec against a filename. The first "len" characters
@@ -377,7 +71,7 @@ static int match(const char **spec, char *ps_matched,
        return 0;
 }
 
-static void show_dir_entry(const char *tag, struct nond_on_fs *ent)
+static void show_dir_entry(const char *tag, struct dir_entry *ent)
 {
        int len = prefix_len;
        int offset = prefix_offset;
@@ -393,14 +87,14 @@ static void show_dir_entry(const char *tag, struct nond_on_fs *ent)
        putchar(line_terminator);
 }
 
-static void show_other_files(void)
+static void show_other_files(struct dir_struct *dir)
 {
        int i;
-       for (i = 0; i < nr_dir; i++) {
+       for (i = 0; i < dir->nr; i++) {
                /* We should not have a matching entry, but we
                 * may have an unmerged entry for this path.
                 */
-               struct nond_on_fs *ent = dir[i];
+               struct dir_entry *ent = dir->entries[i];
                int pos = cache_name_pos(ent->name, ent->len);
                struct cache_entry *ce;
                if (0 <= pos)
@@ -416,11 +110,11 @@ static void show_other_files(void)
        }
 }
 
-static void show_killed_files(void)
+static void show_killed_files(struct dir_struct *dir)
 {
        int i;
-       for (i = 0; i < nr_dir; i++) {
-               struct nond_on_fs *ent = dir[i];
+       for (i = 0; i < dir->nr; i++) {
+               struct dir_entry *ent = dir->entries[i];
                char *cp, *sp;
                int pos, len, killed = 0;
 
@@ -461,7 +155,7 @@ static void show_killed_files(void)
                        }
                }
                if (killed)
-                       show_dir_entry(tag_killed, dir[i]);
+                       show_dir_entry(tag_killed, dir->entries[i]);
        }
 }
 
@@ -512,7 +206,7 @@ static void show_ce_entry(const char *tag, struct cache_entry *ce)
        }
 }
 
-static void show_files(void)
+static void show_files(struct dir_struct *dir)
 {
        int i;
 
@@ -521,39 +215,18 @@ static void show_files(void)
                const char *path = ".", *base = "";
                int baselen = prefix_len;
 
-               if (baselen) {
+               if (baselen)
                        path = base = prefix;
-                       if (exclude_per_dir) {
-                               char *p, *pp = xmalloc(baselen+1);
-                               memcpy(pp, prefix, baselen+1);
-                               p = pp;
-                               while (1) {
-                                       char save = *p;
-                                       *p = 0;
-                                       push_exclude_per_directory(pp, p-pp);
-                                       *p++ = save;
-                                       if (!save)
-                                               break;
-                                       p = strchr(p, '/');
-                                       if (p)
-                                               p++;
-                                       else
-                                               p = pp + baselen;
-                               }
-                               free(pp);
-                       }
-               }
-               read_directory(path, base, baselen);
-               qsort(dir, nr_dir, sizeof(struct nond_on_fs *), cmp_name);
+               read_directory(dir, path, base, baselen);
                if (show_others)
-                       show_other_files();
+                       show_other_files(dir);
                if (show_killed)
-                       show_killed_files();
+                       show_killed_files(dir);
        }
        if (show_cached | show_stage) {
                for (i = 0; i < active_nr; i++) {
                        struct cache_entry *ce = active_cache[i];
-                       if (excluded(ce->name) != show_ignored)
+                       if (excluded(dir, ce->name) != dir->show_ignored)
                                continue;
                        if (show_unmerged && !ce_stage(ce))
                                continue;
@@ -565,7 +238,7 @@ static void show_files(void)
                        struct cache_entry *ce = active_cache[i];
                        struct stat st;
                        int err;
-                       if (excluded(ce->name) != show_ignored)
+                       if (excluded(dir, ce->name) != dir->show_ignored)
                                continue;
                        err = lstat(ce->name, &st);
                        if (show_deleted && err)
@@ -652,7 +325,9 @@ int main(int argc, const char **argv)
 {
        int i;
        int exc_given = 0;
+       struct dir_struct dir;
 
+       memset(&dir, 0, sizeof(dir));
        prefix = setup_git_directory();
        if (prefix)
                prefix_offset = strlen(prefix);
@@ -697,7 +372,7 @@ int main(int argc, const char **argv)
                        continue;
                }
                if (!strcmp(arg, "-i") || !strcmp(arg, "--ignored")) {
-                       show_ignored = 1;
+                       dir.show_ignored = 1;
                        continue;
                }
                if (!strcmp(arg, "-s") || !strcmp(arg, "--stage")) {
@@ -709,11 +384,11 @@ int main(int argc, const char **argv)
                        continue;
                }
                if (!strcmp(arg, "--directory")) {
-                       show_other_directories = 1;
+                       dir.show_other_directories = 1;
                        continue;
                }
                if (!strcmp(arg, "--no-empty-directory")) {
-                       hide_empty_directories = 1;
+                       dir.hide_empty_directories = 1;
                        continue;
                }
                if (!strcmp(arg, "-u") || !strcmp(arg, "--unmerged")) {
@@ -726,27 +401,27 @@ int main(int argc, const char **argv)
                }
                if (!strcmp(arg, "-x") && i+1 < argc) {
                        exc_given = 1;
-                       add_exclude(argv[++i], "", 0, &exclude_list[EXC_CMDL]);
+                       add_exclude(argv[++i], "", 0, &dir.exclude_list[EXC_CMDL]);
                        continue;
                }
                if (!strncmp(arg, "--exclude=", 10)) {
                        exc_given = 1;
-                       add_exclude(arg+10, "", 0, &exclude_list[EXC_CMDL]);
+                       add_exclude(arg+10, "", 0, &dir.exclude_list[EXC_CMDL]);
                        continue;
                }
                if (!strcmp(arg, "-X") && i+1 < argc) {
                        exc_given = 1;
-                       add_excludes_from_file(argv[++i]);
+                       add_excludes_from_file(&dir, argv[++i]);
                        continue;
                }
                if (!strncmp(arg, "--exclude-from=", 15)) {
                        exc_given = 1;
-                       add_excludes_from_file(arg+15);
+                       add_excludes_from_file(&dir, arg+15);
                        continue;
                }
                if (!strncmp(arg, "--exclude-per-directory=", 24)) {
                        exc_given = 1;
-                       exclude_per_dir = arg + 24;
+                       dir.exclude_per_dir = arg + 24;
                        continue;
                }
                if (!strcmp(arg, "--full-name")) {
@@ -788,7 +463,7 @@ int main(int argc, const char **argv)
                ps_matched = xcalloc(1, num);
        }
 
-       if (show_ignored && !exc_given) {
+       if (dir.show_ignored && !exc_given) {
                fprintf(stderr, "%s: --ignored needs some exclude pattern\n",
                        argv[0]);
                exit(1);
@@ -802,7 +477,7 @@ int main(int argc, const char **argv)
        read_cache();
        if (prefix)
                prune_cache();
-       show_files();
+       show_files(&dir);
 
        if (ps_matched) {
                /* We need to make sure all pathspec matched otherwise
index b276519..a133e6d 100644 (file)
@@ -237,10 +237,17 @@ static int eatspace(char *line)
 #define SEEN_FROM 01
 #define SEEN_DATE 02
 #define SEEN_SUBJECT 04
+#define SEEN_BOGUS_UNIX_FROM 010
 
 /* First lines of body can have From:, Date:, and Subject: */
 static int handle_inbody_header(int *seen, char *line)
 {
+       if (!memcmp(">From", line, 5) && isspace(line[5])) {
+               if (!(*seen & SEEN_BOGUS_UNIX_FROM)) {
+                       *seen |= SEEN_BOGUS_UNIX_FROM;
+                       return 1;
+               }
+       }
        if (!memcmp("From:", line, 5) && isspace(line[5])) {
                if (!(*seen & SEEN_FROM) && handle_from(line+6)) {
                        *seen |= SEEN_FROM;
index b95edcc..36bd4ea 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
@@ -332,6 +347,70 @@ int ce_path_match(const struct cache_entry *ce, const char **pathspec)
 }
 
 /*
+ * We fundamentally don't like some paths: we don't want
+ * dot or dot-dot anywhere, and for obvious reasons don't
+ * want to recurse into ".git" either.
+ *
+ * Also, we don't want double slashes or slashes at the
+ * end that can make pathnames ambiguous.
+ */
+static int verify_dotfile(const char *rest)
+{
+       /*
+        * The first character was '.', but that
+        * has already been discarded, we now test
+        * the rest.
+        */
+       switch (*rest) {
+       /* "." is not allowed */
+       case '\0': case '/':
+               return 0;
+
+       /*
+        * ".git" followed by  NUL or slash is bad. This
+        * shares the path end test with the ".." case.
+        */
+       case 'g':
+               if (rest[1] != 'i')
+                       break;
+               if (rest[2] != 't')
+                       break;
+               rest += 2;
+       /* fallthrough */
+       case '.':
+               if (rest[1] == '\0' || rest[1] == '/')
+                       return 0;
+       }
+       return 1;
+}
+
+int verify_path(const char *path)
+{
+       char c;
+
+       goto inside;
+       for (;;) {
+               if (!c)
+                       return 1;
+               if (c == '/') {
+inside:
+                       c = *path++;
+                       switch (c) {
+                       default:
+                               continue;
+                       case '/': case '\0':
+                               break;
+                       case '.':
+                               if (verify_dotfile(path))
+                                       continue;
+                       }
+                       return 0;
+               }
+               c = *path++;
+       }
+}
+
+/*
  * Do we have another file that has the beginning components being a
  * proper superset of the name we're trying to add?
  */
@@ -472,6 +551,8 @@ int add_cache_entry(struct cache_entry *ce, int option)
 
        if (!ok_to_add)
                return -1;
+       if (!verify_path(ce->name))
+               return -1;
 
        if (!skip_df_check &&
            check_file_directory_conflict(ce, pos, ok_to_replace)) {
@@ -630,6 +711,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;
@@ -678,6 +775,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:
@@ -712,6 +825,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;
@@ -808,5 +932,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 82e2a9a..55f7f87 100644 (file)
@@ -9,6 +9,7 @@
 
 #include "object.h"
 #include "tree.h"
+#include "cache-tree.h"
 #include <sys/time.h>
 #include <signal.h>
 
@@ -21,6 +22,7 @@ static int trivial_merges_only = 0;
 static int aggressive = 0;
 static int verbose_update = 0;
 static volatile int progress_update = 0;
+static const char *prefix = NULL;
 
 static int head_idx = -1;
 static int merge_size = 0;
@@ -369,7 +371,8 @@ static int unpack_trees(merge_fn_t fn)
                        posns[i] = ((struct tree *) posn->item)->entries;
                        posn = posn->next;
                }
-               if (unpack_trees_rec(posns, len, "", fn, &indpos))
+               if (unpack_trees_rec(posns, len, prefix ? prefix : "",
+                                    fn, &indpos))
                        return -1;
        }
 
@@ -426,6 +429,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);
+}
+
 /*
  * We do not want to remove or overwrite a working tree file that
  * is not tracked.
@@ -456,10 +465,13 @@ static int merged_entry(struct cache_entry *merge, struct cache_entry *old)
                        *merge = *old;
                } else {
                        verify_uptodate(old);
+                       invalidate_ce_path(old);
                }
        }
-       else
+       else {
                verify_absent(merge->name, "overwritten");
+               invalidate_ce_path(merge);
+       }
 
        merge->ce_flags &= ~htons(CE_STAGEMASK);
        add_cache_entry(merge, ADD_CACHE_OK_TO_ADD);
@@ -474,6 +486,7 @@ static int deleted_entry(struct cache_entry *ce, struct cache_entry *old)
                verify_absent(ce->name, "removed");
        ce->ce_mode = 0;
        add_cache_entry(ce, ADD_CACHE_OK_TO_ADD);
+       invalidate_ce_path(ce);
        return 1;
 }
 
@@ -709,6 +722,28 @@ static int twoway_merge(struct cache_entry **src)
 }
 
 /*
+ * Bind merge.
+ *
+ * Keep the index entries at stage0, collapse stage1 but make sure
+ * stage0 does not have anything there.
+ */
+static int bind_merge(struct cache_entry **src)
+{
+       struct cache_entry *old = src[0];
+       struct cache_entry *a = src[1];
+
+       if (merge_size != 1)
+               return error("Cannot do a bind merge of %d trees\n",
+                            merge_size);
+       if (a && old)
+               die("Entry '%s' overlaps.  Cannot bind.", a->name);
+       if (!a)
+               return keep_entry(old);
+       else
+               return merged_entry(a, NULL);
+}
+
+/*
  * One-way merge.
  *
  * The rule is:
@@ -723,8 +758,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 deleted_entry(old, old);
+       }
        if (old && same(old, a)) {
                if (reset) {
                        struct stat st;
@@ -749,6 +786,7 @@ static int read_cache_unmerged(void)
                struct cache_entry *ce = active_cache[i];
                if (ce_stage(ce)) {
                        deleted++;
+                       invalidate_ce_path(ce);
                        continue;
                }
                if (deleted)
@@ -759,7 +797,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;
 
@@ -804,9 +875,24 @@ int main(int argc, char **argv)
                        continue;
                }
 
+               /* "--prefix=<subdirectory>/" means keep the current index
+                *  entries and put the entries from the tree under the
+                * given subdirectory.
+                */
+               if (!strncmp(arg, "--prefix=", 9)) {
+                       if (stage || merge || prefix)
+                               usage(read_tree_usage);
+                       prefix = arg + 9;
+                       merge = 1;
+                       stage = 1;
+                       if (read_cache_unmerged())
+                               die("you need to resolve your current index first");
+                       continue;
+               }
+
                /* This differs from "-m" in that we'll silently ignore unmerged entries */
                if (!strcmp(arg, "--reset")) {
-                       if (stage || merge)
+                       if (stage || merge || prefix)
                                usage(read_tree_usage);
                        reset = 1;
                        merge = 1;
@@ -827,7 +913,7 @@ int main(int argc, char **argv)
 
                /* "-m" stands for "merge", meaning we start in stage 1 */
                if (!strcmp(arg, "-m")) {
-                       if (stage || merge)
+                       if (stage || merge || prefix)
                                usage(read_tree_usage);
                        if (read_cache_unmerged())
                                die("you need to resolve your current index first");
@@ -849,21 +935,39 @@ int main(int argc, char **argv)
        if ((update||index_only) && !merge)
                usage(read_tree_usage);
 
+       if (prefix) {
+               int pfxlen = strlen(prefix);
+               int pos;
+               if (prefix[pfxlen-1] != '/')
+                       die("prefix must end with /");
+               if (stage != 2)
+                       die("binding merge takes only one tree");
+               pos = cache_name_pos(prefix, pfxlen);
+               if (0 <= pos)
+                       die("corrupt index file");
+               pos = -pos-1;
+               if (pos < active_nr &&
+                   !strncmp(active_cache[pos]->name, prefix, pfxlen))
+                       die("subdirectory '%s' already exists.", prefix);
+               pos = cache_name_pos(prefix, pfxlen-1);
+               if (0 <= pos)
+                       die("file '%.*s' already exists.", pfxlen-1, prefix);
+       }
+
        if (merge) {
                if (stage < 2)
                        die("just how do you expect me to merge %d trees?", stage-1);
                switch (stage - 1) {
                case 1:
-                       fn = oneway_merge;
+                       fn = prefix ? bind_merge : oneway_merge;
                        break;
                case 2:
                        fn = twoway_merge;
                        break;
                case 3:
-                       fn = threeway_merge;
-                       break;
                default:
                        fn = threeway_merge;
+                       cache_tree_free(&active_cache_tree);
                        break;
                }
 
@@ -874,6 +978,18 @@ int main(int argc, char **argv)
        }
 
        unpack_trees(fn);
+
+       /*
+        * When reading only one tree (either the most basic form,
+        * "-m ent" or "--reset ent" form), we can obtain a fully
+        * valid cache-tree because the index must match exactly
+        * what came from the tree.
+        */
+       if (trees && trees->item && (!merge || (stage == 2))) {
+               cache_tree_free(&active_cache_tree);
+               prime_cache_tree();
+       }
+
        if (write_cache(newfd, active_cache, active_nr) ||
            commit_index_file(&cache_file))
                die("unable to write new index file");
index 48d7b4c..bdbdd23 100644 (file)
@@ -58,6 +58,8 @@ struct rev_info {
        unsigned int    abbrev;
        enum cmit_fmt   commit_format;
        struct log_info *loginfo;
+       int             nr, total;
+       const char      *mime_boundary;
 
        /* special limits */
        int max_count;
index 268c57b..684ffd1 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, NULL);
        else
                strcpy(pretty, "(unavailable)");
        if (!strncmp(pretty, "[PATCH] ", 8))
index cf33989..2c9bbb5 100755 (executable)
@@ -195,6 +195,20 @@ test_expect_success \
     'git-ls-tree -r output for a known tree.' \
     'diff current expected'
 
+test_expect_success \
+    'writing partial tree out with git-write-tree --prefix.' \
+    'ptree=$(git-write-tree --prefix=path3)'
+test_expect_success \
+    'validate object ID for a known tree.' \
+    'test "$ptree" = 21ae8269cacbe57ae09138dcc3a2887f904d02b3'
+
+test_expect_success \
+    'writing partial tree out with git-write-tree --prefix.' \
+    'ptree=$(git-write-tree --prefix=path3/subp3)'
+test_expect_success \
+    'validate object ID for a known tree.' \
+    'test "$ptree" = 3c5e5399f3a333eddecce7a9b9465b63f65f51e2'
+
 ################################################################
 rm .git/index
 test_expect_success \
diff --git a/tar-tree.c b/tar-tree.c
deleted file mode 100644 (file)
index 3308736..0000000
+++ /dev/null
@@ -1,350 +0,0 @@
-/*
- * Copyright (c) 2005, 2006 Rene Scharfe
- */
-#include <time.h>
-#include "cache.h"
-#include "tree-walk.h"
-#include "commit.h"
-#include "strbuf.h"
-#include "tar.h"
-
-#define RECORDSIZE     (512)
-#define BLOCKSIZE      (RECORDSIZE * 20)
-
-static const char tar_tree_usage[] = "git-tar-tree <key> [basedir]";
-
-static char block[BLOCKSIZE];
-static unsigned long offset;
-
-static time_t archive_time;
-
-/* tries hard to write, either succeeds or dies in the attempt */
-static void reliable_write(void *buf, unsigned long size)
-{
-       while (size > 0) {
-               long ret = xwrite(1, buf, size);
-               if (ret < 0) {
-                       if (errno == EPIPE)
-                               exit(0);
-                       die("git-tar-tree: %s", strerror(errno));
-               } else if (!ret) {
-                       die("git-tar-tree: disk full?");
-               }
-               size -= ret;
-               buf += ret;
-       }
-}
-
-/* writes out the whole block, but only if it is full */
-static void write_if_needed(void)
-{
-       if (offset == BLOCKSIZE) {
-               reliable_write(block, BLOCKSIZE);
-               offset = 0;
-       }
-}
-
-/* acquire the next record from the buffer; user must call write_if_needed() */
-static char *get_record(void)
-{
-       char *p = block + offset;
-       memset(p, 0, RECORDSIZE);
-       offset += RECORDSIZE;
-       return p;
-}
-
-/*
- * The end of tar archives is marked by 1024 nul bytes and after that
- * follows the rest of the block (if any).
- */
-static void write_trailer(void)
-{
-       get_record();
-       write_if_needed();
-       get_record();
-       write_if_needed();
-       while (offset) {
-               get_record();
-               write_if_needed();
-       }
-}
-
-/*
- * queues up writes, so that all our write(2) calls write exactly one
- * full block; pads writes to RECORDSIZE
- */
-static void write_blocked(void *buf, unsigned long size)
-{
-       unsigned long tail;
-
-       if (offset) {
-               unsigned long chunk = BLOCKSIZE - offset;
-               if (size < chunk)
-                       chunk = size;
-               memcpy(block + offset, buf, chunk);
-               size -= chunk;
-               offset += chunk;
-               buf += chunk;
-               write_if_needed();
-       }
-       while (size >= BLOCKSIZE) {
-               reliable_write(buf, BLOCKSIZE);
-               size -= BLOCKSIZE;
-               buf += BLOCKSIZE;
-       }
-       if (size) {
-               memcpy(block + offset, buf, size);
-               offset += size;
-       }
-       tail = offset % RECORDSIZE;
-       if (tail)  {
-               memset(block + offset, 0, RECORDSIZE - tail);
-               offset += RECORDSIZE - tail;
-       }
-       write_if_needed();
-}
-
-static void strbuf_append_string(struct strbuf *sb, const char *s)
-{
-       int slen = strlen(s);
-       int total = sb->len + slen;
-       if (total > sb->alloc) {
-               sb->buf = xrealloc(sb->buf, total);
-               sb->alloc = total;
-       }
-       memcpy(sb->buf + sb->len, s, slen);
-       sb->len = total;
-}
-
-/*
- * pax extended header records have the format "%u %s=%s\n".  %u contains
- * the size of the whole string (including the %u), the first %s is the
- * keyword, the second one is the value.  This function constructs such a
- * string and appends it to a struct strbuf.
- */
-static void strbuf_append_ext_header(struct strbuf *sb, const char *keyword,
-                                     const char *value, unsigned int valuelen)
-{
-       char *p;
-       int len, total, tmp;
-
-       /* "%u %s=%s\n" */
-       len = 1 + 1 + strlen(keyword) + 1 + valuelen + 1;
-       for (tmp = len; tmp > 9; tmp /= 10)
-               len++;
-
-       total = sb->len + len;
-       if (total > sb->alloc) {
-               sb->buf = xrealloc(sb->buf, total);
-               sb->alloc = total;
-       }
-
-       p = sb->buf;
-       p += sprintf(p, "%u %s=", len, keyword);
-       memcpy(p, value, valuelen);
-       p += valuelen;
-       *p = '\n';
-       sb->len = total;
-}
-
-static unsigned int ustar_header_chksum(const struct ustar_header *header)
-{
-       char *p = (char *)header;
-       unsigned int chksum = 0;
-       while (p < header->chksum)
-               chksum += *p++;
-       chksum += sizeof(header->chksum) * ' ';
-       p += sizeof(header->chksum);
-       while (p < (char *)header + sizeof(struct ustar_header))
-               chksum += *p++;
-       return chksum;
-}
-
-static int get_path_prefix(const struct strbuf *path, int maxlen)
-{
-       int i = path->len;
-       if (i > maxlen)
-               i = maxlen;
-       while (i > 0 && path->buf[i] != '/')
-               i--;
-       return i;
-}
-
-static void write_entry(const unsigned char *sha1, struct strbuf *path,
-                        unsigned int mode, void *buffer, unsigned long size)
-{
-       struct ustar_header header;
-       struct strbuf ext_header;
-
-       memset(&header, 0, sizeof(header));
-       ext_header.buf = NULL;
-       ext_header.len = ext_header.alloc = 0;
-
-       if (!sha1) {
-               *header.typeflag = TYPEFLAG_GLOBAL_HEADER;
-               mode = 0100666;
-               strcpy(header.name, "pax_global_header");
-       } else if (!path) {
-               *header.typeflag = TYPEFLAG_EXT_HEADER;
-               mode = 0100666;
-               sprintf(header.name, "%s.paxheader", sha1_to_hex(sha1));
-       } else {
-               if (S_ISDIR(mode)) {
-                       *header.typeflag = TYPEFLAG_DIR;
-                       mode |= 0777;
-               } else if (S_ISLNK(mode)) {
-                       *header.typeflag = TYPEFLAG_LNK;
-                       mode |= 0777;
-               } else if (S_ISREG(mode)) {
-                       *header.typeflag = TYPEFLAG_REG;
-                       mode |= (mode & 0100) ? 0777 : 0666;
-               } else {
-                       error("unsupported file mode: 0%o (SHA1: %s)",
-                             mode, sha1_to_hex(sha1));
-                       return;
-               }
-               if (path->len > sizeof(header.name)) {
-                       int plen = get_path_prefix(path, sizeof(header.prefix));
-                       int rest = path->len - plen - 1;
-                       if (plen > 0 && rest <= sizeof(header.name)) {
-                               memcpy(header.prefix, path->buf, plen);
-                               memcpy(header.name, path->buf + plen + 1, rest);
-                       } else {
-                               sprintf(header.name, "%s.data",
-                                       sha1_to_hex(sha1));
-                               strbuf_append_ext_header(&ext_header, "path",
-                                                        path->buf, path->len);
-                       }
-               } else
-                       memcpy(header.name, path->buf, path->len);
-       }
-
-       if (S_ISLNK(mode) && buffer) {
-               if (size > sizeof(header.linkname)) {
-                       sprintf(header.linkname, "see %s.paxheader",
-                               sha1_to_hex(sha1));
-                       strbuf_append_ext_header(&ext_header, "linkpath",
-                                                buffer, size);
-               } else
-                       memcpy(header.linkname, buffer, size);
-       }
-
-       sprintf(header.mode, "%07o", mode & 07777);
-       sprintf(header.size, "%011lo", S_ISREG(mode) ? size : 0);
-       sprintf(header.mtime, "%011lo", archive_time);
-
-       /* XXX: should we provide more meaningful info here? */
-       sprintf(header.uid, "%07o", 0);
-       sprintf(header.gid, "%07o", 0);
-       strncpy(header.uname, "git", 31);
-       strncpy(header.gname, "git", 31);
-       sprintf(header.devmajor, "%07o", 0);
-       sprintf(header.devminor, "%07o", 0);
-
-       memcpy(header.magic, "ustar", 6);
-       memcpy(header.version, "00", 2);
-
-       sprintf(header.chksum, "%07o", ustar_header_chksum(&header));
-
-       if (ext_header.len > 0) {
-               write_entry(sha1, NULL, 0, ext_header.buf, ext_header.len);
-               free(ext_header.buf);
-       }
-       write_blocked(&header, sizeof(header));
-       if (S_ISREG(mode) && buffer && size > 0)
-               write_blocked(buffer, size);
-}
-
-static void write_global_extended_header(const unsigned char *sha1)
-{
-       struct strbuf ext_header;
-       ext_header.buf = NULL;
-       ext_header.len = ext_header.alloc = 0;
-       strbuf_append_ext_header(&ext_header, "comment", sha1_to_hex(sha1), 40);
-       write_entry(NULL, NULL, 0, ext_header.buf, ext_header.len);
-       free(ext_header.buf);
-}
-
-static void traverse_tree(struct tree_desc *tree, struct strbuf *path)
-{
-       int pathlen = path->len;
-
-       while (tree->size) {
-               const char *name;
-               const unsigned char *sha1;
-               unsigned mode;
-               void *eltbuf;
-               char elttype[20];
-               unsigned long eltsize;
-
-               sha1 = tree_entry_extract(tree, &name, &mode);
-               update_tree_entry(tree);
-
-               eltbuf = read_sha1_file(sha1, elttype, &eltsize);
-               if (!eltbuf)
-                       die("cannot read %s", sha1_to_hex(sha1));
-
-               path->len = pathlen;
-               strbuf_append_string(path, name);
-               if (S_ISDIR(mode))
-                       strbuf_append_string(path, "/");
-
-               write_entry(sha1, path, mode, eltbuf, eltsize);
-
-               if (S_ISDIR(mode)) {
-                       struct tree_desc subtree;
-                       subtree.buf = eltbuf;
-                       subtree.size = eltsize;
-                       traverse_tree(&subtree, path);
-               }
-               free(eltbuf);
-       }
-}
-
-int main(int argc, char **argv)
-{
-       unsigned char sha1[20], tree_sha1[20];
-       struct commit *commit;
-       struct tree_desc tree;
-       struct strbuf current_path;
-
-       current_path.buf = xmalloc(PATH_MAX);
-       current_path.alloc = PATH_MAX;
-       current_path.len = current_path.eof = 0;
-
-       setup_git_directory();
-       git_config(git_default_config);
-
-       switch (argc) {
-       case 3:
-               strbuf_append_string(&current_path, argv[2]);
-               strbuf_append_string(&current_path, "/");
-               /* FALLTHROUGH */
-       case 2:
-               if (get_sha1(argv[1], sha1))
-                       die("Not a valid object name %s", argv[1]);
-               break;
-       default:
-               usage(tar_tree_usage);
-       }
-
-       commit = lookup_commit_reference_gently(sha1, 1);
-       if (commit) {
-               write_global_extended_header(commit->object.sha1);
-               archive_time = commit->date;
-       } else
-               archive_time = time(NULL);
-
-       tree.buf = read_object_with_reference(sha1, tree_type, &tree.size,
-                                             tree_sha1);
-       if (!tree.buf)
-               die("not a reference to a tag, commit or tree object: %s",
-                   sha1_to_hex(sha1));
-
-       if (current_path.len > 0)
-               write_entry(tree_sha1, &current_path, 040777, NULL, 0);
-       traverse_tree(&tree, &current_path);
-       write_trailer();
-       free(current_path.buf);
-       return 0;
-}
index 7d6de82..956b6b3 100644 (file)
@@ -6,6 +6,7 @@
 #include "cache.h"
 #include "strbuf.h"
 #include "quote.h"
+#include "cache-tree.h"
 #include "tree-walk.h"
 
 /*
@@ -51,6 +52,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;
        }
@@ -64,6 +66,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
@@ -120,70 +128,6 @@ static int add_file_to_cache(const char *path)
        return 0;
 }
 
-/*
- * We fundamentally don't like some paths: we don't want
- * dot or dot-dot anywhere, and for obvious reasons don't
- * want to recurse into ".git" either.
- *
- * Also, we don't want double slashes or slashes at the
- * end that can make pathnames ambiguous.
- */
-static int verify_dotfile(const char *rest)
-{
-       /*
-        * The first character was '.', but that
-        * has already been discarded, we now test
-        * the rest.
-        */
-       switch (*rest) {
-       /* "." is not allowed */
-       case '\0': case '/':
-               return 0;
-
-       /*
-        * ".git" followed by  NUL or slash is bad. This
-        * shares the path end test with the ".." case.
-        */
-       case 'g':
-               if (rest[1] != 'i')
-                       break;
-               if (rest[2] != 't')
-                       break;
-               rest += 2;
-       /* fallthrough */
-       case '.':
-               if (rest[1] == '\0' || rest[1] == '/')
-                       return 0;
-       }
-       return 1;
-}
-
-static int verify_path(const char *path)
-{
-       char c;
-
-       goto inside;
-       for (;;) {
-               if (!c)
-                       return 1;
-               if (c == '/') {
-inside:
-                       c = *path++;
-                       switch (c) {
-                       default:
-                               continue;
-                       case '/': case '\0':
-                               break;
-                       case '.':
-                               if (verify_dotfile(path))
-                                       continue;
-                       }
-                       return 0;
-               }
-               c = *path++;
-       }
-}
-
 static int add_cacheinfo(unsigned int mode, const unsigned char *sha1,
                         const char *path, int stage)
 {
@@ -209,6 +153,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;
 }
 
@@ -233,6 +178,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;
@@ -254,6 +200,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))
@@ -332,6 +279,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 */
@@ -438,6 +386,7 @@ static int unresolve_one(const char *path)
                goto free_return;
        }
 
+       cache_tree_invalidate_path(active_cache_tree, path);
        remove_file_from_cache(path);
        if (add_cache_entry(ce_2, ADD_CACHE_OK_TO_ADD)) {
                error("%s: cannot add our version to the index.", path);
index dcad6e6..895e7a3 100644 (file)
  */
 #include "cache.h"
 #include "tree.h"
+#include "cache-tree.h"
 
 static int missing_ok = 0;
+static char *prefix = NULL;
 
-static int check_valid_sha1(unsigned char *sha1)
-{
-       int ret;
-
-       /* If we were anal, we'd check that the sha1 of the contents actually matches */
-       ret = has_sha1_file(sha1);
-       if (ret == 0)
-               perror(sha1_file_name(sha1));
-       return ret ? 0 : -1;
-}
-
-static int write_tree(struct cache_entry **cachep, int maxentries, const char *base, int baselen, unsigned char *returnsha1)
-{
-       unsigned char subdir_sha1[20];
-       unsigned long size, offset;
-       char *buffer;
-       int nr;
-
-       /* Guess at some random initial size */
-       size = 8192;
-       buffer = xmalloc(size);
-       offset = 0;
-
-       nr = 0;
-       while (nr < maxentries) {
-               struct cache_entry *ce = cachep[nr];
-               const char *pathname = ce->name, *filename, *dirname;
-               int pathlen = ce_namelen(ce), entrylen;
-               unsigned char *sha1;
-               unsigned int mode;
-
-               /* Did we hit the end of the directory? Return how many we wrote */
-               if (baselen >= pathlen || memcmp(base, pathname, baselen))
-                       break;
-
-               sha1 = ce->sha1;
-               mode = ntohl(ce->ce_mode);
-
-               /* Do we have _further_ subdirectories? */
-               filename = pathname + baselen;
-               dirname = strchr(filename, '/');
-               if (dirname) {
-                       int subdir_written;
-
-                       subdir_written = write_tree(cachep + nr, maxentries - nr, pathname, dirname-pathname+1, subdir_sha1);
-                       nr += subdir_written;
-
-                       /* Now we need to write out the directory entry into this tree.. */
-                       mode = S_IFDIR;
-                       pathlen = dirname - pathname;
-
-                       /* ..but the directory entry doesn't count towards the total count */
-                       nr--;
-                       sha1 = subdir_sha1;
-               }
+static const char write_tree_usage[] =
+"git-write-tree [--missing-ok] [--prefix=<prefix>/]";
 
-               if (!missing_ok && check_valid_sha1(sha1) < 0)
-                       exit(1);
-
-               entrylen = pathlen - baselen;
-               if (offset + entrylen + 100 > size) {
-                       size = alloc_nr(offset + entrylen + 100);
-                       buffer = xrealloc(buffer, size);
-               }
-               offset += sprintf(buffer + offset, "%o %.*s", mode, entrylen, filename);
-               buffer[offset++] = 0;
-               memcpy(buffer + offset, sha1, 20);
-               offset += 20;
-               nr++;
-       }
-
-       write_sha1_file(buffer, offset, tree_type, returnsha1);
-       free(buffer);
-       return nr;
-}
-
-static const char write_tree_usage[] = "git-write-tree [--missing-ok]";
+static struct cache_file cache_file;
 
 int main(int argc, char **argv)
 {
-       int i, funny;
-       int entries;
-       unsigned char sha1[20];
-       
+       int entries, was_valid, newfd;
+
        setup_git_directory();
 
+       newfd = hold_index_file_for_update(&cache_file, get_index_file());
        entries = read_cache();
-       if (argc == 2) {
-               if (!strcmp(argv[1], "--missing-ok"))
+
+       while (1 < argc) {
+               char *arg = argv[1];
+               if (!strcmp(arg, "--missing-ok"))
                        missing_ok = 1;
+               else if (!strncmp(arg, "--prefix=", 9))
+                       prefix = arg + 9;
                else
                        die(write_tree_usage);
+               argc--; argv++;
        }
-       
+
        if (argc > 2)
                die("too many options");
 
        if (entries < 0)
                die("git-write-tree: error reading cache");
 
-       /* Verify that the tree is merged */
-       funny = 0;
-       for (i = 0; i < entries; i++) {
-               struct cache_entry *ce = active_cache[i];
-               if (ce_stage(ce)) {
-                       if (10 < ++funny) {
-                               fprintf(stderr, "...\n");
-                               break;
-                       }
-                       fprintf(stderr, "%s: unmerged (%s)\n", ce->name, sha1_to_hex(ce->sha1));
+       if (!active_cache_tree)
+               active_cache_tree = cache_tree();
+
+       was_valid = cache_tree_fully_valid(active_cache_tree);
+       if (!was_valid) {
+               if (cache_tree_update(active_cache_tree,
+                                     active_cache, active_nr,
+                                     missing_ok, 0) < 0)
+                       die("git-write-tree: error building trees");
+               if (0 <= newfd) {
+                       if (!write_cache(newfd, active_cache, active_nr))
+                               commit_index_file(&cache_file);
                }
-       }
-       if (funny)
-               die("git-write-tree: not able to write tree");
-
-       /* Also verify that the cache does not have path and path/file
-        * at the same time.  At this point we know the cache has only
-        * stage 0 entries.
-        */
-       funny = 0;
-       for (i = 0; i < entries - 1; i++) {
-               /* path/file always comes after path because of the way
-                * the cache is sorted.  Also path can appear only once,
-                * which means conflicting one would immediately follow.
+               /* Not being able to write is fine -- we are only interested
+                * in updating the cache-tree part, and if the next caller
+                * ends up using the old index with unupdated cache-tree part
+                * it misses the work we did here, but that is just a
+                * performance penalty and not a big deal.
                 */
-               const char *this_name = active_cache[i]->name;
-               const char *next_name = active_cache[i+1]->name;
-               int this_len = strlen(this_name);
-               if (this_len < strlen(next_name) &&
-                   strncmp(this_name, next_name, this_len) == 0 &&
-                   next_name[this_len] == '/') {
-                       if (10 < ++funny) {
-                               fprintf(stderr, "...\n");
-                               break;
-                       }
-                       fprintf(stderr, "You have both %s and %s\n",
-                               this_name, next_name);
-               }
        }
-       if (funny)
-               die("git-write-tree: not able to write tree");
-
-       /* Ok, write it out */
-       if (write_tree(active_cache, entries, "", 0, sha1) != entries)
-               die("git-write-tree: internal error");
-       printf("%s\n", sha1_to_hex(sha1));
+       if (prefix) {
+               struct cache_tree *subtree =
+                       cache_tree_find(active_cache_tree, prefix);
+               printf("%s\n", sha1_to_hex(subtree->sha1));
+       }
+       else
+               printf("%s\n", sha1_to_hex(active_cache_tree->sha1));
        return 0;
 }