summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--Documentation/git-read-tree.txt11
-rw-r--r--Documentation/git-write-tree.txt8
-rw-r--r--Makefile33
-rw-r--r--builtin-add.c189
-rw-r--r--builtin-apply.c5
-rw-r--r--builtin-diff.c2
-rw-r--r--builtin-log.c172
-rw-r--r--builtin-ls-files.c381
-rw-r--r--builtin-read-tree.c134
-rw-r--r--builtin-rev-list.c2
-rw-r--r--builtin-rm.c152
-rw-r--r--builtin-show-branch.c2
-rw-r--r--builtin.h3
-rw-r--r--cache-tree.c557
-rw-r--r--cache-tree.h33
-rw-r--r--cache.h3
-rw-r--r--checkout-index.c1
-rw-r--r--commit.c134
-rw-r--r--commit.h3
-rw-r--r--connect.c2
-rw-r--r--date.c29
-rw-r--r--diff.c6
-rw-r--r--diff.h3
-rw-r--r--dir.c401
-rw-r--r--dir.h51
-rw-r--r--dump-cache-tree.c64
-rw-r--r--fetch-pack.c66
-rw-r--r--fsck-objects.c25
-rwxr-xr-xgit-add.sh56
-rwxr-xr-xgit-format-patch.sh344
-rwxr-xr-xgit-rebase.sh2
-rwxr-xr-xgit-rm.sh70
-rw-r--r--git.c3
-rw-r--r--http-fetch.c4
-rw-r--r--log-tree.c74
-rw-r--r--mailinfo.c7
-rw-r--r--read-cache.c138
-rw-r--r--revision.h2
-rwxr-xr-xt/t0000-basic.sh14
-rw-r--r--update-index.c77
-rw-r--r--write-tree.c166
42 files changed, 2340 insertions, 1090 deletions
diff --git a/.gitignore b/.gitignore
index 199cc310a2..18a7295e6c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -124,6 +124,7 @@ git-write-tree
git-core-*/?*
test-date
test-delta
+test-dump-cache-tree
common-cmds.h
*.tar.gz
*.dsc
diff --git a/Documentation/git-read-tree.txt b/Documentation/git-read-tree.txt
index 844cfda8d2..1f21d95684 100644
--- a/Documentation/git-read-tree.txt
+++ b/Documentation/git-read-tree.txt
@@ -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.
diff --git a/Documentation/git-write-tree.txt b/Documentation/git-write-tree.txt
index 77e12cb949..c85fa89c30 100644
--- a/Documentation/git-write-tree.txt
+++ b/Documentation/git-write-tree.txt
@@ -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>
diff --git a/Makefile b/Makefile
index 14873fa359..355e0ccece 100644
--- 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 \
@@ -168,12 +168,13 @@ 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-ls-files$X git-ls-tree$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 \
+ git-ls-files$X git-ls-tree$X \
git-read-tree$X git-commit-tree$X \
git-apply$X git-show-branch$X git-diff-files$X \
- git-diff-index$X git-diff-stages$X git-diff-tree$X \
- git-tar-tree$X git-upload-tar$X
+ git-diff-index$X git-diff-stages$X git-diff-tree$X
# what 'all' will build and 'install' will install, in gitexecdir
ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS)
@@ -202,7 +203,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 \
@@ -210,10 +211,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 \
@@ -221,12 +222,13 @@ 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-ls-files.o builtin-ls-tree.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 \
+ builtin-ls-files.o builtin-ls-tree.o \
builtin-read-tree.o builtin-commit-tree.o \
builtin-apply.o builtin-show-branch.o builtin-diff-files.o \
- builtin-diff-index.o builtin-diff-stages.o builtin-diff-tree.o \
- builtin-tar-tree.o builtin-upload-tar.o
+ builtin-diff-index.o builtin-diff-stages.o builtin-diff-tree.o
GITLIBS = $(LIB_FILE) $(XDIFF_LIB)
LIBS = $(GITLIBS) -lz
@@ -618,6 +620,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/builtin-add.c b/builtin-add.c
new file mode 100644
index 0000000000..02fe38b0c4
--- /dev/null
+++ b/builtin-add.c
@@ -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;
+}
diff --git a/builtin-apply.c b/builtin-apply.c
index 4056b9d67b..f16c753bee 100644
--- a/builtin-apply.c
+++ b/builtin-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"
@@ -1918,6 +1919,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);
@@ -2019,8 +2021,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-diff.c b/builtin-diff.c
index de81b05e32..27451d5613 100644
--- a/builtin-diff.c
+++ b/builtin-diff.c
@@ -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;
diff --git a/builtin-log.c b/builtin-log.c
index c4ceee0f98..c8feb0f795 100644
--- a/builtin-log.c
+++ b/builtin-log.c
@@ -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;
+}
+
diff --git a/builtin-ls-files.c b/builtin-ls-files.c
index 3a0c5f2150..8dae9f70e2 100644
--- a/builtin-ls-files.c
+++ b/builtin-ls-files.c
@@ -5,24 +5,21 @@
*
* Copyright (C) Linus Torvalds, 2005
*/
-#include <dirent.h>
#include <fnmatch.h>
#include "cache.h"
#include "quote.h"
+#include "dir.h"
#include "builtin.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';
@@ -39,309 +36,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
@@ -378,7 +72,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;
@@ -394,14 +88,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)
@@ -417,11 +111,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;
@@ -462,7 +156,7 @@ static void show_killed_files(void)
}
}
if (killed)
- show_dir_entry(tag_killed, dir[i]);
+ show_dir_entry(tag_killed, dir->entries[i]);
}
}
@@ -513,7 +207,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;
@@ -522,39 +216,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;
@@ -566,7 +239,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)
@@ -653,7 +326,9 @@ int cmd_ls_files(int argc, const char **argv, char** envp)
{
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);
@@ -698,7 +373,7 @@ int cmd_ls_files(int argc, const char **argv, char** envp)
continue;
}
if (!strcmp(arg, "-i") || !strcmp(arg, "--ignored")) {
- show_ignored = 1;
+ dir.show_ignored = 1;
continue;
}
if (!strcmp(arg, "-s") || !strcmp(arg, "--stage")) {
@@ -710,11 +385,11 @@ int cmd_ls_files(int argc, const char **argv, char** envp)
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")) {
@@ -727,27 +402,27 @@ int cmd_ls_files(int argc, const char **argv, char** envp)
}
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")) {
@@ -789,7 +464,7 @@ int cmd_ls_files(int argc, const char **argv, char** envp)
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);
@@ -803,7 +478,7 @@ int cmd_ls_files(int argc, const char **argv, char** envp)
read_cache();
if (prefix)
prune_cache();
- show_files();
+ show_files(&dir);
if (ps_matched) {
/* We need to make sure all pathspec matched otherwise
diff --git a/builtin-read-tree.c b/builtin-read-tree.c
index ec40d013c4..629d151639 100644
--- a/builtin-read-tree.c
+++ b/builtin-read-tree.c
@@ -9,6 +9,7 @@
#include "object.h"
#include "tree.h"
+#include "cache-tree.h"
#include <sys/time.h>
#include <signal.h>
#include "builtin.h"
@@ -22,6 +23,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;
@@ -370,7 +372,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;
}
@@ -427,6 +430,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.
@@ -457,10 +466,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);
@@ -475,6 +487,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;
}
@@ -710,6 +723,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:
@@ -724,8 +759,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;
@@ -750,6 +787,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)
@@ -760,7 +798,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;
@@ -805,9 +876,24 @@ int cmd_read_tree(int argc, const char **argv, char **envp)
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;
@@ -828,7 +914,7 @@ int cmd_read_tree(int argc, const char **argv, char **envp)
/* "-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");
@@ -850,21 +936,39 @@ int cmd_read_tree(int argc, const char **argv, char **envp)
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;
}
@@ -875,6 +979,18 @@ int cmd_read_tree(int argc, const char **argv, char **envp)
}
unpack_trees(fn);
+
+ /*
+ * When reading only one tree (either the most basic form,
+ * "-m ent" or "--reset ent" form), we can obtain a fully
+ * valid cache-tree because the index must match exactly
+ * what came from the tree.
+ */
+ if (trees && trees->item && (!merge || (stage == 2))) {
+ cache_tree_free(&active_cache_tree);
+ prime_cache_tree();
+ }
+
if (write_cache(newfd, active_cache, active_nr) ||
commit_index_file(&cache_file))
die("unable to write new index file");
diff --git a/builtin-rev-list.c b/builtin-rev-list.c
index 446802d377..f11dbd65c1 100644
--- a/builtin-rev-list.c
+++ b/builtin-rev-list.c
@@ -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
index 0000000000..e7793a20f5
--- /dev/null
+++ b/builtin-rm.c
@@ -0,0 +1,152 @@
+/*
+ * "git rm" builtin command
+ *
+ * Copyright (C) Linus Torvalds 2006
+ */
+#include "cache.h"
+#include "builtin.h"
+#include "dir.h"
+#include "cache-tree.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-show-branch.c b/builtin-show-branch.c
index 3af24e767b..2895140915 100644
--- a/builtin-show-branch.c
+++ b/builtin-show-branch.c
@@ -260,7 +260,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))
diff --git a/builtin.h b/builtin.h
index 2249c5fae0..714b97578c 100644
--- a/builtin.h
+++ b/builtin.h
@@ -20,10 +20,13 @@ 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);
diff --git a/cache-tree.c b/cache-tree.c
new file mode 100644
index 0000000000..d9f7e1e3dd
--- /dev/null
+++ b/cache-tree.c
@@ -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
index 0000000000..119407e3a1
--- /dev/null
+++ b/cache-tree.h
@@ -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 afa8e4f0ac..31755c85a0 100644
--- 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 *);
diff --git a/checkout-index.c b/checkout-index.c
index cc3a745c14..9876af6fd6 100644
--- a/checkout-index.c
+++ b/checkout-index.c
@@ -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;
diff --git a/commit.c b/commit.c
index 4a26070c13..94f470b75c 100644
--- 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--;
diff --git a/commit.h b/commit.h
index de142afe73..c9de1677e9 100644
--- 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.
diff --git a/connect.c b/connect.c
index 6a8f8a6a24..54f7bf7915 100644
--- 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 034d7228bf..365dc3b14e 100644
--- 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 3a2a175c7e..a7bb9b95bd 100644
--- 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 c672277df2..4fc597c594 100644
--- 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
index 0000000000..d778ecd890
--- /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
index 0000000000..56a1b7fce2
--- /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
index 0000000000..1ccaf51773
--- /dev/null
+++ b/dump-cache-tree.c
@@ -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, "");
+}
diff --git a/fetch-pack.c b/fetch-pack.c
index a3bcad016f..8daa93d024 100644
--- a/fetch-pack.c
+++ b/fetch-pack.c
@@ -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)
diff --git a/fsck-objects.c b/fsck-objects.c
index 59b25904cb..1922b6d84c 100644
--- a/fsck-objects.c
+++ b/fsck-objects.c
@@ -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
index d6a4bc7d09..0000000000
--- a/git-add.sh
+++ /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
index 8a16eadfbd..0000000000
--- a/git-format-patch.sh
+++ /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
diff --git a/git-rebase.sh b/git-rebase.sh
index 6ff6088d18..e6b57b8ab9 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -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
index fda4541c76..0000000000
--- 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 38e84618dc..5a884bb07a 100644
--- a/git.c
+++ b/git.c
@@ -47,9 +47,12 @@ 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 },
diff --git a/http-fetch.c b/http-fetch.c
index 861644b27e..178f1ee311 100644
--- a/http-fetch.c
+++ b/http-fetch.c
@@ -1269,10 +1269,10 @@ int main(int argc, char **argv)
if (pull(commit_id))
rc = 1;
- curl_slist_free_all(no_pragma_header);
-
http_cleanup();
+ curl_slist_free_all(no_pragma_header);
+
if (corrupt_object_found) {
fprintf(stderr,
"Some loose object were found to be corrupt, but they might be just\n"
diff --git a/log-tree.c b/log-tree.c
index b90ba6762a..7e23e42788 100644
--- a/log-tree.c
+++ b/log-tree.c
@@ -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;
}
diff --git a/mailinfo.c b/mailinfo.c
index b27651935d..a133e6d08a 100644
--- a/mailinfo.c
+++ b/mailinfo.c
@@ -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;
diff --git a/read-cache.c b/read-cache.c
index b95edcc14c..36bd4ea0be 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -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);
}
diff --git a/revision.h b/revision.h
index 48d7b4ca94..bdbdd235d8 100644
--- a/revision.h
+++ b/revision.h
@@ -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;
diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh
index cf33989b56..2c9bbb59b0 100755
--- a/t/t0000-basic.sh
+++ b/t/t0000-basic.sh
@@ -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/update-index.c b/update-index.c
index 7d6de821e2..956b6b34f9 100644
--- a/update-index.c
+++ b/update-index.c
@@ -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);
diff --git a/write-tree.c b/write-tree.c
index dcad6e6670..895e7a359d 100644
--- a/write-tree.c
+++ b/write-tree.c
@@ -5,154 +5,68 @@
*/
#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;
}