diff options
39 files changed, 2127 insertions, 617 deletions
diff --git a/.gitignore b/.gitignore index b5959d6311..7906909b30 100644 --- a/.gitignore +++ b/.gitignore @@ -123,6 +123,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> @@ -199,7 +199,7 @@ LIB_H = \ blob.h cache.h commit.h csum-file.h delta.h \ diff.h object.h pack.h pkt-line.h quote.h refs.h \ run-command.h strbuf.h tag.h tree.h git-compat-util.h revision.h \ - tree-walk.h log-tree.h + tree-walk.h log-tree.h dir.h DIFF_OBJS = \ diff.o diff-lib.o diffcore-break.o diffcore-order.o \ @@ -207,10 +207,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 \ @@ -218,7 +218,7 @@ LIB_OBJS = \ BUILTIN_OBJS = \ builtin-log.o builtin-help.o builtin-count.o builtin-diff.o builtin-push.o \ - builtin-grep.o + builtin-grep.o builtin-add.o GITLIBS = $(LIB_FILE) $(XDIFF_LIB) LIBS = $(GITLIBS) -lz @@ -610,6 +610,9 @@ test-date$X: test-date.c date.o ctype.o test-delta$X: test-delta.c diff-delta.o patch-delta.o $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $^ +test-dump-cache-tree$X: dump-cache-tree.o $(GITLIBS) + $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS) + check: for i in *.c; do sparse $(ALL_CFLAGS) $(SPARSE_FLAGS) $$i || exit; done @@ -8,6 +8,7 @@ */ #include <fnmatch.h> #include "cache.h" +#include "cache-tree.h" #include "quote.h" #include "blob.h" #include "delta.h" @@ -17,6 +18,8 @@ // --stat does just a diffstat, and doesn't actually apply // --numstat does numeric diffstat, and doesn't actually apply // --index-info shows the old and new index info for paths if available. +// --index updates the cache as well. +// --cached updates only the cache without ever touching the working tree. // static const char *prefix; static int prefix_length = -1; @@ -26,6 +29,7 @@ static int p_value = 1; static int allow_binary_replacement = 0; static int check_index = 0; static int write_index = 0; +static int cached = 0; static int diffstat = 0; static int numstat = 0; static int summary = 0; @@ -36,7 +40,7 @@ static int show_index_info = 0; static int line_termination = '\n'; static unsigned long p_context = -1; static const char apply_usage[] = -"git-apply [--stat] [--numstat] [--summary] [--check] [--index] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [-z] [-pNUM] [-CNUM] [--whitespace=<nowarn|warn|error|error-all|strip>] <patch>..."; +"git-apply [--stat] [--numstat] [--summary] [--check] [--index] [--cached] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [-z] [-pNUM] [-CNUM] [--whitespace=<nowarn|warn|error|error-all|strip>] <patch>..."; static enum whitespace_eol { nowarn_whitespace, @@ -1600,7 +1604,7 @@ static int apply_fragments(struct buffer_desc *desc, struct patch *patch) return 0; } -static int apply_data(struct patch *patch, struct stat *st) +static int apply_data(struct patch *patch, struct stat *st, struct cache_entry *ce) { char *buf; unsigned long size, alloc; @@ -1609,7 +1613,17 @@ static int apply_data(struct patch *patch, struct stat *st) size = 0; alloc = 0; buf = NULL; - if (patch->old_name) { + if (cached) { + if (ce) { + char type[20]; + buf = read_sha1_file(ce->sha1, type, &size); + if (!buf) + return error("read of %s failed", + patch->old_name); + alloc = size; + } + } + else if (patch->old_name) { size = st->st_size; alloc = size + 8192; buf = xmalloc(alloc); @@ -1637,16 +1651,21 @@ static int check_patch(struct patch *patch) const char *old_name = patch->old_name; const char *new_name = patch->new_name; const char *name = old_name ? old_name : new_name; + struct cache_entry *ce = NULL; if (old_name) { - int changed; - int stat_ret = lstat(old_name, &st); + int changed = 0; + int stat_ret = 0; + unsigned st_mode = 0; + if (!cached) + stat_ret = lstat(old_name, &st); if (check_index) { int pos = cache_name_pos(old_name, strlen(old_name)); if (pos < 0) return error("%s: does not exist in index", old_name); + ce = active_cache[pos]; if (stat_ret < 0) { struct checkout costate; if (errno != ENOENT) @@ -1659,40 +1678,46 @@ static int check_patch(struct patch *patch) costate.quiet = 0; costate.not_new = 0; costate.refresh_cache = 1; - if (checkout_entry(active_cache[pos], + if (checkout_entry(ce, &costate, NULL) || lstat(old_name, &st)) return -1; } - - changed = ce_match_stat(active_cache[pos], &st, 1); + if (!cached) + changed = ce_match_stat(ce, &st, 1); if (changed) return error("%s: does not match index", old_name); + if (cached) + st_mode = ntohl(ce->ce_mode); } else if (stat_ret < 0) return error("%s: %s", old_name, strerror(errno)); + if (!cached) + st_mode = ntohl(create_ce_mode(st.st_mode)); + if (patch->is_new < 0) patch->is_new = 0; - st.st_mode = ntohl(create_ce_mode(st.st_mode)); if (!patch->old_mode) - patch->old_mode = st.st_mode; - if ((st.st_mode ^ patch->old_mode) & S_IFMT) + patch->old_mode = st_mode; + if ((st_mode ^ patch->old_mode) & S_IFMT) return error("%s: wrong type", old_name); - if (st.st_mode != patch->old_mode) + if (st_mode != patch->old_mode) fprintf(stderr, "warning: %s has type %o, expected %o\n", - old_name, st.st_mode, patch->old_mode); + old_name, st_mode, patch->old_mode); } if (new_name && (patch->is_new | patch->is_rename | patch->is_copy)) { if (check_index && cache_name_pos(new_name, strlen(new_name)) >= 0) return error("%s: already exists in index", new_name); - if (!lstat(new_name, &st)) - return error("%s: already exists in working directory", new_name); - if (errno != ENOENT) - return error("%s: %s", new_name, strerror(errno)); + if (!cached) { + if (!lstat(new_name, &st)) + return error("%s: already exists in working directory", new_name); + if (errno != ENOENT) + return error("%s: %s", new_name, strerror(errno)); + } if (!patch->new_mode) { if (patch->is_new) patch->new_mode = S_IFREG | 0644; @@ -1709,9 +1734,9 @@ static int check_patch(struct patch *patch) return error("new mode (%o) of %s does not match old mode (%o)%s%s", patch->new_mode, new_name, patch->old_mode, same ? "" : " of ", same ? "" : old_name); - } + } - if (apply_data(patch, &st) < 0) + if (apply_data(patch, &st, ce) < 0) return error("%s: patch does not apply", name); return 0; } @@ -1778,7 +1803,7 @@ static void numstat_patch_list(struct patch *patch) { for ( ; patch; patch = patch->next) { const char *name; - name = patch->old_name ? patch->old_name : patch->new_name; + name = patch->new_name ? patch->new_name : patch->old_name; printf("%d\t%d\t", patch->lines_added, patch->lines_deleted); if (line_termination && quote_c_style(name, NULL, NULL, 0)) quote_c_style(name, NULL, stdout, 0); @@ -1893,8 +1918,10 @@ static void remove_file(struct patch *patch) if (write_index) { if (remove_file_from_cache(patch->old_name) < 0) die("unable to remove %s from index", patch->old_name); + cache_tree_invalidate_path(active_cache_tree, patch->old_name); } - unlink(patch->old_name); + if (!cached) + unlink(patch->old_name); } static void add_index_file(const char *path, unsigned mode, void *buf, unsigned long size) @@ -1911,9 +1938,11 @@ static void add_index_file(const char *path, unsigned mode, void *buf, unsigned memcpy(ce->name, path, namelen); ce->ce_mode = create_ce_mode(mode); ce->ce_flags = htons(namelen); - if (lstat(path, &st) < 0) - die("unable to stat newly created file %s", path); - fill_stat_cache_info(ce, &st); + if (!cached) { + if (lstat(path, &st) < 0) + die("unable to stat newly created file %s", path); + fill_stat_cache_info(ce, &st); + } if (write_sha1_file(buf, size, blob_type, ce->sha1) < 0) die("unable to create backing store for newly created file %s", path); if (add_cache_entry(ce, ADD_CACHE_OK_TO_ADD) < 0) @@ -1950,6 +1979,8 @@ static int try_create_file(const char *path, unsigned int mode, const char *buf, */ static void create_one_file(char *path, unsigned mode, const char *buf, unsigned long size) { + if (cached) + return; if (!try_create_file(path, mode, buf, size)) return; @@ -1989,8 +2020,9 @@ static void create_file(struct patch *patch) if (!mode) mode = S_IFREG | 0644; - create_one_file(path, mode, buf, size); + create_one_file(path, mode, buf, size); add_index_file(path, mode, buf, size); + cache_tree_invalidate_path(active_cache_tree, path); } static void write_out_one_result(struct patch *patch) @@ -2182,6 +2214,11 @@ int main(int argc, char **argv) check_index = 1; continue; } + if (!strcmp(arg, "--cached")) { + check_index = 1; + cached = 1; + continue; + } if (!strcmp(arg, "--apply")) { apply = 1; continue; diff --git a/builtin-add.c b/builtin-add.c new file mode 100644 index 0000000000..7083820f7b --- /dev/null +++ b/builtin-add.c @@ -0,0 +1,269 @@ +/* + * "git add" builtin command + * + * Copyright (C) 2006 Linus Torvalds + */ +#include <fnmatch.h> + +#include "cache.h" +#include "builtin.h" +#include "dir.h" +#include "cache-tree.h" + +static const char builtin_add_usage[] = +"git-add [-n] [-v] <filepattern>..."; + +static int common_prefix(const char **pathspec) +{ + const char *path, *slash, *next; + int prefix; + + if (!pathspec) + return 0; + + path = *pathspec; + slash = strrchr(path, '/'); + if (!slash) + return 0; + + prefix = slash - path + 1; + while ((next = *++pathspec) != NULL) { + int len = strlen(next); + if (len >= prefix && !memcmp(path, next, len)) + continue; + for (;;) { + if (!len) + return 0; + if (next[--len] != '/') + continue; + if (memcmp(path, next, len+1)) + continue; + prefix = len + 1; + break; + } + } + return prefix; +} + +static int match_one(const char *match, const char *name, int namelen) +{ + int matchlen; + + /* If the match was just the prefix, we matched */ + matchlen = strlen(match); + if (!matchlen) + return 1; + + /* + * If we don't match the matchstring exactly, + * we need to match by fnmatch + */ + if (strncmp(match, name, matchlen)) + return !fnmatch(match, name, 0); + + /* + * If we did match the string exactly, we still + * need to make sure that it happened on a path + * component boundary (ie either the last character + * of the match was '/', or the next character of + * the name was '/' or the terminating NUL. + */ + return match[matchlen-1] == '/' || + name[matchlen] == '/' || + !name[matchlen]; +} + +static int match(const char **pathspec, const char *name, int namelen, int prefix, char *seen) +{ + int retval; + const char *match; + + name += prefix; + namelen -= prefix; + + for (retval = 0; (match = *pathspec++) != NULL; seen++) { + if (retval & *seen) + continue; + match += prefix; + if (match_one(match, name, namelen)) { + retval = 1; + *seen = 1; + } + } + return retval; +} + +static void prune_directory(struct dir_struct *dir, const char **pathspec, int prefix) +{ + char *seen; + int i, specs; + struct dir_entry **src, **dst; + + for (specs = 0; pathspec[specs]; specs++) + /* nothing */; + seen = xmalloc(specs); + memset(seen, 0, specs); + + src = dst = dir->entries; + i = dir->nr; + while (--i >= 0) { + struct dir_entry *entry = *src++; + if (!match(pathspec, entry->name, entry->len, prefix, seen)) { + free(entry); + continue; + } + *dst++ = entry; + } + dir->nr = dst - dir->entries; + + for (i = 0; i < specs; i++) { + struct stat st; + const char *match; + if (seen[i]) + continue; + + /* Existing file? We must have ignored it */ + match = pathspec[i]; + if (!lstat(match, &st)) + continue; + die("pathspec '%s' did not match any files", match); + } +} + +static void fill_directory(struct dir_struct *dir, const char **pathspec) +{ + const char *path, *base; + int baselen; + + /* Set up the default git porcelain excludes */ + memset(dir, 0, sizeof(*dir)); + dir->exclude_per_dir = ".gitignore"; + path = git_path("info/exclude"); + if (!access(path, R_OK)) + add_excludes_from_file(dir, path); + + /* + * Calculate common prefix for the pathspec, and + * use that to optimize the directory walk + */ + baselen = common_prefix(pathspec); + path = "."; + base = ""; + if (baselen) { + char *common = xmalloc(baselen + 1); + common = xmalloc(baselen + 1); + memcpy(common, *pathspec, baselen); + common[baselen] = 0; + path = base = common; + } + + /* Read the directory and prune it */ + read_directory(dir, path, base, baselen); + if (pathspec) + prune_directory(dir, pathspec, baselen); +} + +static int add_file_to_index(const char *path, int verbose) +{ + int size, namelen; + struct stat st; + struct cache_entry *ce; + + if (lstat(path, &st)) + die("%s: unable to stat (%s)", path, strerror(errno)); + + if (!S_ISREG(st.st_mode) && !S_ISLNK(st.st_mode)) + die("%s: can only add regular files or symbolic links", path); + + namelen = strlen(path); + size = cache_entry_size(namelen); + ce = xcalloc(1, size); + memcpy(ce->name, path, namelen); + ce->ce_flags = htons(namelen); + fill_stat_cache_info(ce, &st); + + ce->ce_mode = create_ce_mode(st.st_mode); + if (!trust_executable_bit) { + /* If there is an existing entry, pick the mode bits + * from it. + */ + int pos = cache_name_pos(path, namelen); + if (pos >= 0) + ce->ce_mode = active_cache[pos]->ce_mode; + } + + if (index_path(ce->sha1, path, &st, 1)) + die("unable to index file %s", path); + if (add_cache_entry(ce, ADD_CACHE_OK_TO_ADD)) + die("unable to add %s to index",path); + if (verbose) + printf("add '%s'\n", path); + cache_tree_invalidate_path(active_cache_tree, path); + return 0; +} + +static struct cache_file cache_file; + +int cmd_add(int argc, const char **argv, char **envp) +{ + int i, newfd; + int verbose = 0, show_only = 0; + const char *prefix = setup_git_directory(); + const char **pathspec; + struct dir_struct dir; + + git_config(git_default_config); + + newfd = hold_index_file_for_update(&cache_file, get_index_file()); + if (newfd < 0) + die("unable to create new cachefile"); + + if (read_cache() < 0) + die("index file corrupt"); + + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + + if (arg[0] != '-') + break; + if (!strcmp(arg, "--")) { + i++; + break; + } + if (!strcmp(arg, "-n")) { + show_only = 1; + continue; + } + if (!strcmp(arg, "-v")) { + verbose = 1; + continue; + } + die(builtin_add_usage); + } + git_config(git_default_config); + pathspec = get_pathspec(prefix, argv + i); + + fill_directory(&dir, pathspec); + + if (show_only) { + const char *sep = "", *eof = ""; + for (i = 0; i < dir.nr; i++) { + printf("%s%s", sep, dir.entries[i]->name); + sep = " "; + eof = "\n"; + } + fputs(eof, stdout); + return 0; + } + + for (i = 0; i < dir.nr; i++) + add_file_to_index(dir.entries[i]->name, verbose); + + if (active_cache_changed) { + if (write_cache(newfd, active_cache, active_nr) || + commit_index_file(&cache_file)) + die("Unable to write new index file"); + } + + return 0; +} diff --git a/builtin-diff.c b/builtin-diff.c index b0c9cade02..95d2c699b9 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-grep.c b/builtin-grep.c index 66111de514..d09ddf0485 100644 --- a/builtin-grep.c +++ b/builtin-grep.c @@ -453,7 +453,6 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached) len = nr = 0; push_arg("grep"); - push_arg("-H"); if (opt->fixed) push_arg("-F"); if (opt->linenum) @@ -503,17 +502,35 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached) push_arg("-e"); push_arg(p->pattern); } - push_arg("--"); + + /* + * To make sure we get the header printed out when we want it, + * add /dev/null to the paths to grep. This is unnecessary + * (and wrong) with "-l" or "-L", which always print out the + * name anyway. + * + * GNU grep has "-H", but this is portable. + */ + if (!opt->name_only && !opt->unmatch_name_only) + push_arg("/dev/null"); hit = 0; argc = nr; for (i = 0; i < active_nr; i++) { struct cache_entry *ce = active_cache[i]; + const char *name; if (ce_stage(ce) || !S_ISREG(ntohl(ce->ce_mode))) continue; if (!pathspec_matches(paths, ce->name)) continue; - argv[argc++] = ce->name; + name = ce->name; + if (name[0] == '-') { + int len = ce_namelen(ce); + name = xmalloc(len + 3); + memcpy(name, "./", 2); + memcpy(name + 2, ce->name, len + 1); + } + argv[argc++] = name; if (argc < MAXARGS) continue; hit += exec_grep(argc, argv); diff --git a/builtin-log.c b/builtin-log.c index 69f2911cb4..d5bbc1cc06 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) @@ -67,3 +71,160 @@ int cmd_log(int argc, const char **argv, char **envp) rev.diffopt.recursive = 1; return cmd_log_wc(argc, argv, envp, &rev); } + +static int istitlechar(char c) +{ + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9') || c == '.' || c == '_'; +} + +static FILE *realstdout = NULL; +static char *output_directory = NULL; + +static void reopen_stdout(struct commit *commit, int nr, int keep_subject) +{ + char filename[1024]; + char *sol; + int len = 0; + + if (output_directory) { + strncpy(filename, output_directory, 1010); + len = strlen(filename); + if (filename[len - 1] != '/') + filename[len++] = '/'; + } + + sprintf(filename + len, "%04d", nr); + len = strlen(filename); + + sol = strstr(commit->buffer, "\n\n"); + if (sol) { + int j, space = 1; + + sol += 2; + /* strip [PATCH] or [PATCH blabla] */ + if (!keep_subject && !strncmp(sol, "[PATCH", 6)) { + char *eos = strchr(sol + 6, ']'); + if (eos) { + while (isspace(*eos)) + eos++; + sol = eos; + } + } + + for (j = 0; len < 1024 - 6 && sol[j] && sol[j] != '\n'; j++) { + if (istitlechar(sol[j])) { + if (space) { + filename[len++] = '-'; + space = 0; + } + filename[len++] = sol[j]; + if (sol[j] == '.') + while (sol[j + 1] == '.') + j++; + } else + space = 1; + } + while (filename[len - 1] == '.' || filename[len - 1] == '-') + len--; + } + strcpy(filename + len, ".txt"); + fprintf(realstdout, "%s\n", filename); + freopen(filename, "w", stdout); +} + +int cmd_format_patch(int argc, const char **argv, char **envp) +{ + struct commit *commit; + struct commit **list = NULL; + struct rev_info rev; + int nr = 0, total, i, j; + int use_stdout = 0; + int numbered = 0; + int keep_subject = 0; + + init_revisions(&rev); + rev.commit_format = CMIT_FMT_EMAIL; + rev.verbose_header = 1; + rev.diff = 1; + rev.diffopt.with_raw = 0; + rev.diffopt.with_stat = 1; + rev.combine_merges = 0; + rev.ignore_merges = 1; + rev.diffopt.output_format = DIFF_FORMAT_PATCH; + + /* + * Parse the arguments before setup_revisions(), or something + * like "git fmt-patch -o a123 HEAD^.." may fail; a123 is + * possibly a valid SHA1. + */ + for (i = 1, j = 1; i < argc; i++) { + if (!strcmp(argv[i], "--stdout")) + use_stdout = 1; + else if (!strcmp(argv[i], "-n") || + !strcmp(argv[i], "--numbered")) + numbered = 1; + else if (!strcmp(argv[i], "-k") || + !strcmp(argv[i], "--keep-subject")) { + keep_subject = 1; + rev.total = -1; + } else if (!strcmp(argv[i], "-o")) { + if (argc < 3) + die ("Which directory?"); + if (mkdir(argv[i + 1], 0777) < 0 && errno != EEXIST) + die("Could not create directory %s", + argv[i + 1]); + output_directory = strdup(argv[i + 1]); + i++; + } else + argv[j++] = argv[i]; + } + argc = j; + + if (numbered && keep_subject < 0) + die ("-n and -k are mutually exclusive."); + + argc = setup_revisions(argc, argv, &rev, "HEAD"); + if (argc > 1) + die ("unrecognized argument: %s", argv[1]); + + if (rev.pending_objects && rev.pending_objects->next == NULL) { + rev.pending_objects->item->flags |= UNINTERESTING; + add_head(&rev); + } + + if (!use_stdout) + realstdout = fdopen(dup(1), "w"); + + prepare_revision_walk(&rev); + while ((commit = get_revision(&rev)) != NULL) { + /* ignore merges */ + if (commit->parents && commit->parents->next) + continue; + nr++; + list = realloc(list, nr * sizeof(list[0])); + list[nr - 1] = commit; + } + total = nr; + if (numbered) + rev.total = total; + while (0 <= --nr) { + int shown; + commit = list[nr]; + rev.nr = total - nr; + if (!use_stdout) + reopen_stdout(commit, rev.nr, keep_subject); + shown = log_tree_commit(&rev, commit); + free(commit->buffer); + commit->buffer = NULL; + if (shown) + printf("-- \n%s\n\n", git_version_string); + if (!use_stdout) + fclose(stdout); + } + if (output_directory) + free(output_directory); + free(list); + return 0; +} + @@ -20,9 +20,11 @@ extern int cmd_whatchanged(int argc, const char **argv, char **envp); extern int cmd_show(int argc, const char **argv, char **envp); extern int cmd_log(int argc, const char **argv, char **envp); extern int cmd_diff(int argc, const char **argv, char **envp); +extern int cmd_format_patch(int argc, const char **argv, char **envp); extern int cmd_count_objects(int argc, const char **argv, char **envp); extern int cmd_push(int argc, const char **argv, char **envp); extern int cmd_grep(int argc, const char **argv, char **envp); +extern int cmd_add(int argc, const char **argv, char **envp); #endif diff --git a/cache-tree.c b/cache-tree.c new file mode 100644 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 @@ -114,6 +114,7 @@ static inline unsigned int create_ce_mode(unsigned int mode) extern struct cache_entry **active_cache; extern unsigned int active_nr, active_alloc, active_cache_changed; +extern struct cache_tree *active_cache_tree; #define GIT_DIR_ENVIRONMENT "GIT_DIR" #define DEFAULT_GIT_DIR_ENVIRONMENT ".git" @@ -251,6 +252,7 @@ extern void *read_object_with_reference(const unsigned char *sha1, unsigned char *sha1_ret); const char *show_date(unsigned long time, int timezone); +const char *show_rfc2822_date(unsigned long time, int timezone); int parse_date(const char *date, char *buf, int bufsize); void datestamp(char *buf, int bufsize); unsigned long approxidate(const char *); 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; @@ -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 }, @@ -438,6 +439,10 @@ static int add_user_info(const char *what, enum cmit_fmt fmt, char *buf, const c time = strtoul(date, &date, 10); tz = strtol(date, NULL, 10); + if (fmt == CMIT_FMT_EMAIL) { + what = "From"; + filler = ""; + } ret = sprintf(buf, "%s: %.*s%.*s\n", what, (fmt == CMIT_FMT_FULLER) ? 4 : 0, filler, namelen, line); @@ -445,6 +450,10 @@ static int add_user_info(const char *what, enum cmit_fmt fmt, char *buf, const c case CMIT_FMT_MEDIUM: ret += sprintf(buf + ret, "Date: %s\n", show_date(time, tz)); break; + case CMIT_FMT_EMAIL: + ret += sprintf(buf + ret, "Date: %s\n", + show_rfc2822_date(time, tz)); + break; case CMIT_FMT_FULLER: ret += sprintf(buf + ret, "%sDate: %s\n", what, show_date(time, tz)); break; @@ -455,10 +464,12 @@ static int add_user_info(const char *what, enum cmit_fmt fmt, char *buf, const c return ret; } -static int is_empty_line(const char *line, int len) +static int is_empty_line(const char *line, int *len_p) { + int len = *len_p; while (len && isspace(line[len-1])) len--; + *len_p = len; return !len; } @@ -467,7 +478,8 @@ static int add_merge_info(enum cmit_fmt fmt, char *buf, const struct commit *com struct commit_list *parent = commit->parents; int offset; - if ((fmt == CMIT_FMT_ONELINE) || !parent || !parent->next) + if ((fmt == CMIT_FMT_ONELINE) || (fmt == CMIT_FMT_EMAIL) || + !parent || !parent->next) return 0; offset = sprintf(buf, "Merge:"); @@ -486,14 +498,17 @@ static int add_merge_info(enum cmit_fmt fmt, char *buf, const struct commit *com return offset; } -unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit, unsigned long len, char *buf, unsigned long space, int abbrev) +unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit, unsigned long len, char *buf, unsigned long space, int abbrev, const char *subject) { int hdr = 1, body = 0; unsigned long offset = 0; - int indent = (fmt == CMIT_FMT_ONELINE) ? 0 : 4; + int indent = 4; int parents_shown = 0; const char *msg = commit->buffer; + if (fmt == CMIT_FMT_ONELINE || fmt == CMIT_FMT_EMAIL) + indent = 0; + for (;;) { const char *line = msg; int linelen = get_one_line(msg, len); @@ -516,7 +531,7 @@ unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit if (hdr) { if (linelen == 1) { hdr = 0; - if (fmt != CMIT_FMT_ONELINE) + if ((fmt != CMIT_FMT_ONELINE) && !subject) buf[offset++] = '\n'; continue; } @@ -554,20 +569,29 @@ unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit continue; } - if (is_empty_line(line, linelen)) { + if (is_empty_line(line, &linelen)) { if (!body) continue; + if (subject) + continue; if (fmt == CMIT_FMT_SHORT) break; } else { body = 1; } + if (subject) { + int slen = strlen(subject); + memcpy(buf + offset, subject, slen); + offset += slen; + } memset(buf + offset, ' ', indent); memcpy(buf + offset + indent, line, linelen); offset += linelen + indent; + buf[offset++] = '\n'; if (fmt == CMIT_FMT_ONELINE) break; + subject = NULL; } while (offset && isspace(buf[offset-1])) offset--; @@ -45,12 +45,13 @@ enum cmit_fmt { CMIT_FMT_FULL, CMIT_FMT_FULLER, CMIT_FMT_ONELINE, + CMIT_FMT_EMAIL, CMIT_FMT_UNSPECIFIED, }; extern enum cmit_fmt get_commit_format(const char *arg); -extern unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *, unsigned long len, char *buf, unsigned long space, int abbrev); +extern unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *, unsigned long len, char *buf, unsigned long space, int abbrev, const char *subject); /** Removes the first commit from a list sorted by date, and adds all * of its parents. diff --git a/contrib/remotes2config.sh b/contrib/remotes2config.sh new file mode 100644 index 0000000000..25901e2b3b --- /dev/null +++ b/contrib/remotes2config.sh @@ -0,0 +1,35 @@ +#!/bin/sh + +# Use this tool to rewrite your .git/remotes/ files into the config. + +. git-sh-setup + +if [ -d "$GIT_DIR"/remotes ]; then + echo "Rewriting $GIT_DIR/remotes" >&2 + error=0 + # rewrite into config + { + cd "$GIT_DIR"/remotes + ls | while read f; do + name=$(echo -n "$f" | tr -c "A-Za-z0-9" ".") + sed -n \ + -e "s/^URL: \(.*\)$/remote.$name.url \1 ./p" \ + -e "s/^Pull: \(.*\)$/remote.$name.fetch \1 ^$ /p" \ + -e "s/^Push: \(.*\)$/remote.$name.push \1 ^$ /p" \ + < "$f" + done + echo done + } | while read key value regex; do + case $key in + done) + if [ $error = 0 ]; then + mv "$GIT_DIR"/remotes "$GIT_DIR"/remotes.old + fi ;; + *) + echo "git-repo-config $key "$value" $regex" + git-repo-config $key "$value" $regex || error=1 ;; + esac + done +fi + + @@ -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. * @@ -0,0 +1,321 @@ +/* + * This handles recursive filename detection with exclude + * files, index knowledge etc.. + * + * Copyright (C) Linus Torvalds, 2005-2006 + * Junio Hamano, 2005-2006 + */ +#include <dirent.h> +#include <fnmatch.h> + +#include "cache.h" +#include "dir.h" + +void add_exclude(const char *string, const char *base, + int baselen, struct exclude_list *which) +{ + struct exclude *x = xmalloc(sizeof (*x)); + + x->pattern = string; + x->base = base; + x->baselen = baselen; + if (which->nr == which->alloc) { + which->alloc = alloc_nr(which->alloc); + which->excludes = realloc(which->excludes, + which->alloc * sizeof(x)); + } + which->excludes[which->nr++] = x; +} + +static int add_excludes_from_file_1(const char *fname, + const char *base, + int baselen, + struct exclude_list *which) +{ + int fd, i; + long size; + char *buf, *entry; + + fd = open(fname, O_RDONLY); + if (fd < 0) + goto err; + size = lseek(fd, 0, SEEK_END); + if (size < 0) + goto err; + lseek(fd, 0, SEEK_SET); + if (size == 0) { + close(fd); + return 0; + } + buf = xmalloc(size+1); + if (read(fd, buf, size) != size) + goto err; + close(fd); + + buf[size++] = '\n'; + entry = buf; + for (i = 0; i < size; i++) { + if (buf[i] == '\n') { + if (entry != buf + i && entry[0] != '#') { + buf[i - (i && buf[i-1] == '\r')] = 0; + add_exclude(entry, base, baselen, which); + } + entry = buf + i + 1; + } + } + return 0; + + err: + if (0 <= fd) + close(fd); + return -1; +} + +void add_excludes_from_file(struct dir_struct *dir, const char *fname) +{ + if (add_excludes_from_file_1(fname, "", 0, + &dir->exclude_list[EXC_FILE]) < 0) + die("cannot use %s as an exclude file", fname); +} + +static int push_exclude_per_directory(struct dir_struct *dir, const char *base, int baselen) +{ + char exclude_file[PATH_MAX]; + struct exclude_list *el = &dir->exclude_list[EXC_DIRS]; + int current_nr = el->nr; + + if (dir->exclude_per_dir) { + memcpy(exclude_file, base, baselen); + strcpy(exclude_file + baselen, dir->exclude_per_dir); + add_excludes_from_file_1(exclude_file, base, baselen, el); + } + return current_nr; +} + +static void pop_exclude_per_directory(struct dir_struct *dir, int stk) +{ + struct exclude_list *el = &dir->exclude_list[EXC_DIRS]; + + while (stk < el->nr) + free(el->excludes[--el->nr]); +} + +/* Scan the list and let the last match determines the fate. + * Return 1 for exclude, 0 for include and -1 for undecided. + */ +static int excluded_1(const char *pathname, + int pathlen, + struct exclude_list *el) +{ + int i; + + if (el->nr) { + for (i = el->nr - 1; 0 <= i; i--) { + struct exclude *x = el->excludes[i]; + const char *exclude = x->pattern; + int to_exclude = 1; + + if (*exclude == '!') { + to_exclude = 0; + exclude++; + } + + if (!strchr(exclude, '/')) { + /* match basename */ + const char *basename = strrchr(pathname, '/'); + basename = (basename) ? basename+1 : pathname; + if (fnmatch(exclude, basename, 0) == 0) + return to_exclude; + } + else { + /* match with FNM_PATHNAME: + * exclude has base (baselen long) implicitly + * in front of it. + */ + int baselen = x->baselen; + if (*exclude == '/') + exclude++; + + if (pathlen < baselen || + (baselen && pathname[baselen-1] != '/') || + strncmp(pathname, x->base, baselen)) + continue; + + if (fnmatch(exclude, pathname+baselen, + FNM_PATHNAME) == 0) + return to_exclude; + } + } + } + return -1; /* undecided */ +} + +int excluded(struct dir_struct *dir, const char *pathname) +{ + int pathlen = strlen(pathname); + int st; + + for (st = EXC_CMDL; st <= EXC_FILE; st++) { + switch (excluded_1(pathname, pathlen, &dir->exclude_list[st])) { + case 0: + return 0; + case 1: + return 1; + } + } + return 0; +} + +static void add_name(struct dir_struct *dir, const char *pathname, int len) +{ + struct dir_entry *ent; + + if (cache_name_pos(pathname, len) >= 0) + return; + + if (dir->nr == dir->alloc) { + int alloc = alloc_nr(dir->alloc); + dir->alloc = alloc; + dir->entries = xrealloc(dir->entries, alloc*sizeof(ent)); + } + ent = xmalloc(sizeof(*ent) + len + 1); + ent->len = len; + memcpy(ent->name, pathname, len); + ent->name[len] = 0; + dir->entries[dir->nr++] = ent; +} + +static int dir_exists(const char *dirname, int len) +{ + int pos = cache_name_pos(dirname, len); + if (pos >= 0) + return 1; + pos = -pos-1; + if (pos >= active_nr) /* can't */ + return 0; + return !strncmp(active_cache[pos]->name, dirname, len); +} + +/* + * Read a directory tree. We currently ignore anything but + * directories, regular files and symlinks. That's because git + * doesn't handle them at all yet. Maybe that will change some + * day. + * + * Also, we ignore the name ".git" (even if it is not a directory). + * That likely will not change. + */ +static int read_directory_recursive(struct dir_struct *dir, const char *path, const char *base, int baselen) +{ + DIR *fdir = opendir(path); + int contents = 0; + + if (fdir) { + int exclude_stk; + struct dirent *de; + char fullname[MAXPATHLEN + 1]; + memcpy(fullname, base, baselen); + + exclude_stk = push_exclude_per_directory(dir, base, baselen); + + while ((de = readdir(fdir)) != NULL) { + int len; + + if ((de->d_name[0] == '.') && + (de->d_name[1] == 0 || + !strcmp(de->d_name + 1, ".") || + !strcmp(de->d_name + 1, "git"))) + continue; + len = strlen(de->d_name); + memcpy(fullname + baselen, de->d_name, len+1); + if (excluded(dir, fullname) != dir->show_ignored) { + if (!dir->show_ignored || DTYPE(de) != DT_DIR) { + continue; + } + } + + switch (DTYPE(de)) { + struct stat st; + int subdir, rewind_base; + default: + continue; + case DT_UNKNOWN: + if (lstat(fullname, &st)) + continue; + if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) + break; + if (!S_ISDIR(st.st_mode)) + continue; + /* fallthrough */ + case DT_DIR: + memcpy(fullname + baselen + len, "/", 2); + len++; + rewind_base = dir->nr; + subdir = read_directory_recursive(dir, fullname, fullname, + baselen + len); + if (dir->show_other_directories && + (subdir || !dir->hide_empty_directories) && + !dir_exists(fullname, baselen + len)) { + // Rewind the read subdirectory + while (dir->nr > rewind_base) + free(dir->entries[--dir->nr]); + break; + } + contents += subdir; + continue; + case DT_REG: + case DT_LNK: + break; + } + add_name(dir, fullname, baselen + len); + contents++; + } + closedir(fdir); + + pop_exclude_per_directory(dir, exclude_stk); + } + + return contents; +} + +static int cmp_name(const void *p1, const void *p2) +{ + const struct dir_entry *e1 = *(const struct dir_entry **)p1; + const struct dir_entry *e2 = *(const struct dir_entry **)p2; + + return cache_name_compare(e1->name, e1->len, + e2->name, e2->len); +} + +int read_directory(struct dir_struct *dir, const char *path, const char *base, int baselen) +{ + /* + * Make sure to do the per-directory exclude for all the + * directories leading up to our base. + */ + if (baselen) { + if (dir->exclude_per_dir) { + char *p, *pp = xmalloc(baselen+1); + memcpy(pp, base, baselen+1); + p = pp; + while (1) { + char save = *p; + *p = 0; + push_exclude_per_directory(dir, pp, p-pp); + *p++ = save; + if (!save) + break; + p = strchr(p, '/'); + if (p) + p++; + else + p = pp + baselen; + } + free(pp); + } + } + + read_directory_recursive(dir, path, base, baselen); + qsort(dir->entries, dir->nr, sizeof(struct dir_entry *), cmp_name); + return dir->nr; +} @@ -0,0 +1,48 @@ +#ifndef DIR_H +#define DIR_H + +/* + * We maintain three exclude pattern lists: + * EXC_CMDL lists patterns explicitly given on the command line. + * EXC_DIRS lists patterns obtained from per-directory ignore files. + * EXC_FILE lists patterns from fallback ignore files. + */ +#define EXC_CMDL 0 +#define EXC_DIRS 1 +#define EXC_FILE 2 + + +struct dir_entry { + int len; + char name[FLEX_ARRAY]; /* more */ +}; + +struct exclude_list { + int nr; + int alloc; + struct exclude { + const char *pattern; + const char *base; + int baselen; + } **excludes; +}; + +struct dir_struct { + int nr, alloc; + unsigned int show_ignored:1, + show_other_directories:1, + hide_empty_directories:1; + struct dir_entry **entries; + + /* Exclude info */ + const char *exclude_per_dir; + struct exclude_list exclude_list[3]; +}; + +extern int read_directory(struct dir_struct *, const char *path, const char *base, int baselen); +extern int excluded(struct dir_struct *, const char *); +extern void add_excludes_from_file(struct dir_struct *, const char *fname); +extern void add_exclude(const char *string, const char *base, + int baselen, struct exclude_list *which); + +#endif diff --git a/dump-cache-tree.c b/dump-cache-tree.c new file mode 100644 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/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(); @@ -59,46 +59,12 @@ fall_back_3way () { GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \ git-write-tree >"$dotest/patch-merge-base+" && # index has the base tree now. - ( - cd "$dotest/patch-merge-tmp-dir" && - GIT_INDEX_FILE="../patch-merge-tmp-index" \ - GIT_OBJECT_DIRECTORY="$O_OBJECT" \ - git-apply $binary --index <../patch - ) + GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \ + git-apply $binary --cached <"$dotest/patch" then echo Using index info to reconstruct a base tree... mv "$dotest/patch-merge-base+" "$dotest/patch-merge-base" mv "$dotest/patch-merge-tmp-index" "$dotest/patch-merge-index" - else - # Otherwise, try nearby trees that can be used to apply the - # patch. - ( - N=10 - - # Hoping the patch is against our recent commits... - git-rev-list --max-count=$N HEAD - - # or hoping the patch is against known tags... - git-ls-remote --tags . - ) | - while read base junk - do - # See if we have it as a tree... - git-cat-file tree "$base" >/dev/null 2>&1 || continue - - rm -fr "$dotest"/patch-merge-* && - mkdir "$dotest/patch-merge-tmp-dir" || break - ( - cd "$dotest/patch-merge-tmp-dir" && - GIT_INDEX_FILE=../patch-merge-tmp-index && - GIT_OBJECT_DIRECTORY="$O_OBJECT" && - export GIT_INDEX_FILE GIT_OBJECT_DIRECTORY && - git-read-tree "$base" && - git-apply $binary --index && - mv ../patch-merge-tmp-index ../patch-merge-index && - echo "$base" >../patch-merge-base - ) <"$dotest/patch" 2>/dev/null && break - done fi test -f "$dotest/patch-merge-index" && diff --git a/git-parse-remote.sh b/git-parse-remote.sh index c9b899e3d7..187f0883c9 100755 --- a/git-parse-remote.sh +++ b/git-parse-remote.sh @@ -10,7 +10,10 @@ get_data_source () { # Not so fast. This could be the partial URL shorthand... token=$(expr "z$1" : 'z\([^/]*\)/') remainder=$(expr "z$1" : 'z[^/]*/\(.*\)') - if test -f "$GIT_DIR/branches/$token" + if test "$(git-repo-config --get "remote.$token.url")" + then + echo config-partial + elif test -f "$GIT_DIR/branches/$token" then echo branches-partial else @@ -18,7 +21,10 @@ get_data_source () { fi ;; *) - if test -f "$GIT_DIR/remotes/$1" + if test "$(git-repo-config --get "remote.$1.url")" + then + echo config + elif test -f "$GIT_DIR/remotes/$1" then echo remotes elif test -f "$GIT_DIR/branches/$1" @@ -35,6 +41,15 @@ get_remote_url () { case "$data_source" in '') echo "$1" ;; + config-partial) + token=$(expr "z$1" : 'z\([^/]*\)/') + remainder=$(expr "z$1" : 'z[^/]*/\(.*\)') + url=$(git-repo-config --get "remote.$token.url") + echo "$url/$remainder" + ;; + config) + git-repo-config --get "remote.$1.url" + ;; remotes) sed -ne '/^URL: */{ s///p @@ -56,8 +71,10 @@ get_remote_url () { get_remote_default_refs_for_push () { data_source=$(get_data_source "$1") case "$data_source" in - '' | branches | branches-partial) + '' | config-partial | branches | branches-partial) ;; # no default push mapping, just send matching refs. + config) + git-repo-config --get-all "remote.$1.push" ;; remotes) sed -ne '/^Push: */{ s///p @@ -111,8 +128,11 @@ canon_refs_list_for_fetch () { get_remote_default_refs_for_fetch () { data_source=$(get_data_source "$1") case "$data_source" in - '' | branches-partial) + '' | config-partial | branches-partial) echo "HEAD:" ;; + config) + canon_refs_list_for_fetch \ + $(git-repo-config --get-all "remote.$1.fetch") ;; branches) remote_branch=$(sed -ne '/#/s/.*#//p' "$GIT_DIR/branches/$1") case "$remote_branch" in '') remote_branch=master ;; esac @@ -47,9 +47,11 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "whatchanged", cmd_whatchanged }, { "show", cmd_show }, { "push", cmd_push }, + { "fmt-patch", cmd_format_patch }, { "count-objects", cmd_count_objects }, { "diff", cmd_diff }, { "grep", cmd_grep }, + { "add", cmd_add }, }; int i; diff --git a/log-tree.c b/log-tree.c index b90ba6762a..526d578e98 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; opt->loginfo = NULL; if (!opt->verbose_header) { @@ -49,19 +50,38 @@ void show_log(struct rev_info *opt, struct log_info *log, const char *sep) /* * Print header line of header.. */ - printf("%s%s", - opt->commit_format == CMIT_FMT_ONELINE ? "" : "commit ", - diff_unique_abbrev(commit->object.sha1, abbrev_commit)); - if (opt->parents) - show_parents(commit, abbrev_commit); - if (parent) - printf(" (from %s)", diff_unique_abbrev(parent->object.sha1, abbrev_commit)); - putchar(opt->commit_format == CMIT_FMT_ONELINE ? ' ' : '\n'); + + if (opt->commit_format == CMIT_FMT_EMAIL) { + if (opt->total > 0) { + static char buffer[64]; + snprintf(buffer, sizeof(buffer), + "Subject: [PATCH %d/%d] ", + opt->nr, opt->total); + subject = buffer; + } else if (opt->total == 0) + subject = "Subject: [PATCH] "; + else + subject = "Subject: "; + + printf("From %s Thu Apr 7 15:13:13 2005\n", + sha1_to_hex(commit->object.sha1)); + } else { + printf("%s%s", + opt->commit_format == CMIT_FMT_ONELINE ? "" : "commit ", + diff_unique_abbrev(commit->object.sha1, abbrev_commit)); + if (opt->parents) + show_parents(commit, abbrev_commit); + if (parent) + printf(" (from %s)", + diff_unique_abbrev(parent->object.sha1, + abbrev_commit)); + putchar(opt->commit_format == CMIT_FMT_ONELINE ? ' ' : '\n'); + } /* * And then the pretty-printed message itself */ - len = pretty_print_commit(opt->commit_format, commit, ~0u, this_header, sizeof(this_header), abbrev); + len = pretty_print_commit(opt->commit_format, commit, ~0u, this_header, sizeof(this_header), abbrev, subject); printf("%s%s%s", this_header, extra, sep); } @@ -166,15 +186,18 @@ static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log int log_tree_commit(struct rev_info *opt, struct commit *commit) { struct log_info log; + int shown; log.commit = commit; log.parent = NULL; opt->loginfo = &log; - if (!log_tree_diff(opt, commit, &log) && opt->loginfo && opt->always_show_header) { + shown = log_tree_diff(opt, commit, &log); + if (!shown && opt->loginfo && opt->always_show_header) { log.parent = NULL; show_log(opt, opt->loginfo, ""); + shown = 1; } opt->loginfo = NULL; - return 0; + return shown; } diff --git a/ls-files.c b/ls-files.c index 4a4af1ca3b..dfe1481a35 100644 --- a/ls-files.c +++ b/ls-files.c @@ -5,23 +5,20 @@ * * Copyright (C) Linus Torvalds, 2005 */ -#include <dirent.h> #include <fnmatch.h> #include "cache.h" #include "quote.h" +#include "dir.h" static int abbrev = 0; static int show_deleted = 0; static int show_cached = 0; static int show_others = 0; -static int show_ignored = 0; static int show_stage = 0; static int show_unmerged = 0; static int show_modified = 0; static int show_killed = 0; -static int show_other_directories = 0; -static int hide_empty_directories = 0; static int show_valid_bit = 0; static int line_terminator = '\n'; @@ -38,309 +35,6 @@ static const char *tag_other = ""; static const char *tag_killed = ""; static const char *tag_modified = ""; -static const char *exclude_per_dir = NULL; - -/* We maintain three exclude pattern lists: - * EXC_CMDL lists patterns explicitly given on the command line. - * EXC_DIRS lists patterns obtained from per-directory ignore files. - * EXC_FILE lists patterns from fallback ignore files. - */ -#define EXC_CMDL 0 -#define EXC_DIRS 1 -#define EXC_FILE 2 -static struct exclude_list { - int nr; - int alloc; - struct exclude { - const char *pattern; - const char *base; - int baselen; - } **excludes; -} exclude_list[3]; - -static void add_exclude(const char *string, const char *base, - int baselen, struct exclude_list *which) -{ - struct exclude *x = xmalloc(sizeof (*x)); - - x->pattern = string; - x->base = base; - x->baselen = baselen; - if (which->nr == which->alloc) { - which->alloc = alloc_nr(which->alloc); - which->excludes = realloc(which->excludes, - which->alloc * sizeof(x)); - } - which->excludes[which->nr++] = x; -} - -static int add_excludes_from_file_1(const char *fname, - const char *base, - int baselen, - struct exclude_list *which) -{ - int fd, i; - long size; - char *buf, *entry; - - fd = open(fname, O_RDONLY); - if (fd < 0) - goto err; - size = lseek(fd, 0, SEEK_END); - if (size < 0) - goto err; - lseek(fd, 0, SEEK_SET); - if (size == 0) { - close(fd); - return 0; - } - buf = xmalloc(size+1); - if (read(fd, buf, size) != size) - goto err; - close(fd); - - buf[size++] = '\n'; - entry = buf; - for (i = 0; i < size; i++) { - if (buf[i] == '\n') { - if (entry != buf + i && entry[0] != '#') { - buf[i - (i && buf[i-1] == '\r')] = 0; - add_exclude(entry, base, baselen, which); - } - entry = buf + i + 1; - } - } - return 0; - - err: - if (0 <= fd) - close(fd); - return -1; -} - -static void add_excludes_from_file(const char *fname) -{ - if (add_excludes_from_file_1(fname, "", 0, - &exclude_list[EXC_FILE]) < 0) - die("cannot use %s as an exclude file", fname); -} - -static int push_exclude_per_directory(const char *base, int baselen) -{ - char exclude_file[PATH_MAX]; - struct exclude_list *el = &exclude_list[EXC_DIRS]; - int current_nr = el->nr; - - if (exclude_per_dir) { - memcpy(exclude_file, base, baselen); - strcpy(exclude_file + baselen, exclude_per_dir); - add_excludes_from_file_1(exclude_file, base, baselen, el); - } - return current_nr; -} - -static void pop_exclude_per_directory(int stk) -{ - struct exclude_list *el = &exclude_list[EXC_DIRS]; - - while (stk < el->nr) - free(el->excludes[--el->nr]); -} - -/* Scan the list and let the last match determines the fate. - * Return 1 for exclude, 0 for include and -1 for undecided. - */ -static int excluded_1(const char *pathname, - int pathlen, - struct exclude_list *el) -{ - int i; - - if (el->nr) { - for (i = el->nr - 1; 0 <= i; i--) { - struct exclude *x = el->excludes[i]; - const char *exclude = x->pattern; - int to_exclude = 1; - - if (*exclude == '!') { - to_exclude = 0; - exclude++; - } - - if (!strchr(exclude, '/')) { - /* match basename */ - const char *basename = strrchr(pathname, '/'); - basename = (basename) ? basename+1 : pathname; - if (fnmatch(exclude, basename, 0) == 0) - return to_exclude; - } - else { - /* match with FNM_PATHNAME: - * exclude has base (baselen long) implicitly - * in front of it. - */ - int baselen = x->baselen; - if (*exclude == '/') - exclude++; - - if (pathlen < baselen || - (baselen && pathname[baselen-1] != '/') || - strncmp(pathname, x->base, baselen)) - continue; - - if (fnmatch(exclude, pathname+baselen, - FNM_PATHNAME) == 0) - return to_exclude; - } - } - } - return -1; /* undecided */ -} - -static int excluded(const char *pathname) -{ - int pathlen = strlen(pathname); - int st; - - for (st = EXC_CMDL; st <= EXC_FILE; st++) { - switch (excluded_1(pathname, pathlen, &exclude_list[st])) { - case 0: - return 0; - case 1: - return 1; - } - } - return 0; -} - -struct nond_on_fs { - int len; - char name[FLEX_ARRAY]; /* more */ -}; - -static struct nond_on_fs **dir; -static int nr_dir; -static int dir_alloc; - -static void add_name(const char *pathname, int len) -{ - struct nond_on_fs *ent; - - if (cache_name_pos(pathname, len) >= 0) - return; - - if (nr_dir == dir_alloc) { - dir_alloc = alloc_nr(dir_alloc); - dir = xrealloc(dir, dir_alloc*sizeof(ent)); - } - ent = xmalloc(sizeof(*ent) + len + 1); - ent->len = len; - memcpy(ent->name, pathname, len); - ent->name[len] = 0; - dir[nr_dir++] = ent; -} - -static int dir_exists(const char *dirname, int len) -{ - int pos = cache_name_pos(dirname, len); - if (pos >= 0) - return 1; - pos = -pos-1; - if (pos >= active_nr) /* can't */ - return 0; - return !strncmp(active_cache[pos]->name, dirname, len); -} - -/* - * Read a directory tree. We currently ignore anything but - * directories, regular files and symlinks. That's because git - * doesn't handle them at all yet. Maybe that will change some - * day. - * - * Also, we ignore the name ".git" (even if it is not a directory). - * That likely will not change. - */ -static int read_directory(const char *path, const char *base, int baselen) -{ - DIR *fdir = opendir(path); - int contents = 0; - - if (fdir) { - int exclude_stk; - struct dirent *de; - char fullname[MAXPATHLEN + 1]; - memcpy(fullname, base, baselen); - - exclude_stk = push_exclude_per_directory(base, baselen); - - while ((de = readdir(fdir)) != NULL) { - int len; - - if ((de->d_name[0] == '.') && - (de->d_name[1] == 0 || - !strcmp(de->d_name + 1, ".") || - !strcmp(de->d_name + 1, "git"))) - continue; - len = strlen(de->d_name); - memcpy(fullname + baselen, de->d_name, len+1); - if (excluded(fullname) != show_ignored) { - if (!show_ignored || DTYPE(de) != DT_DIR) { - continue; - } - } - - switch (DTYPE(de)) { - struct stat st; - int subdir, rewind_base; - default: - continue; - case DT_UNKNOWN: - if (lstat(fullname, &st)) - continue; - if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) - break; - if (!S_ISDIR(st.st_mode)) - continue; - /* fallthrough */ - case DT_DIR: - memcpy(fullname + baselen + len, "/", 2); - len++; - rewind_base = nr_dir; - subdir = read_directory(fullname, fullname, - baselen + len); - if (show_other_directories && - (subdir || !hide_empty_directories) && - !dir_exists(fullname, baselen + len)) { - // Rewind the read subdirectory - while (nr_dir > rewind_base) - free(dir[--nr_dir]); - break; - } - contents += subdir; - continue; - case DT_REG: - case DT_LNK: - break; - } - add_name(fullname, baselen + len); - contents++; - } - closedir(fdir); - - pop_exclude_per_directory(exclude_stk); - } - - return contents; -} - -static int cmp_name(const void *p1, const void *p2) -{ - const struct nond_on_fs *e1 = *(const struct nond_on_fs **)p1; - const struct nond_on_fs *e2 = *(const struct nond_on_fs **)p2; - - return cache_name_compare(e1->name, e1->len, - e2->name, e2->len); -} /* * Match a pathspec against a filename. The first "len" characters @@ -377,7 +71,7 @@ static int match(const char **spec, char *ps_matched, return 0; } -static void show_dir_entry(const char *tag, struct nond_on_fs *ent) +static void show_dir_entry(const char *tag, struct dir_entry *ent) { int len = prefix_len; int offset = prefix_offset; @@ -393,14 +87,14 @@ static void show_dir_entry(const char *tag, struct nond_on_fs *ent) putchar(line_terminator); } -static void show_other_files(void) +static void show_other_files(struct dir_struct *dir) { int i; - for (i = 0; i < nr_dir; i++) { + for (i = 0; i < dir->nr; i++) { /* We should not have a matching entry, but we * may have an unmerged entry for this path. */ - struct nond_on_fs *ent = dir[i]; + struct dir_entry *ent = dir->entries[i]; int pos = cache_name_pos(ent->name, ent->len); struct cache_entry *ce; if (0 <= pos) @@ -416,11 +110,11 @@ static void show_other_files(void) } } -static void show_killed_files(void) +static void show_killed_files(struct dir_struct *dir) { int i; - for (i = 0; i < nr_dir; i++) { - struct nond_on_fs *ent = dir[i]; + for (i = 0; i < dir->nr; i++) { + struct dir_entry *ent = dir->entries[i]; char *cp, *sp; int pos, len, killed = 0; @@ -461,7 +155,7 @@ static void show_killed_files(void) } } if (killed) - show_dir_entry(tag_killed, dir[i]); + show_dir_entry(tag_killed, dir->entries[i]); } } @@ -512,7 +206,7 @@ static void show_ce_entry(const char *tag, struct cache_entry *ce) } } -static void show_files(void) +static void show_files(struct dir_struct *dir) { int i; @@ -521,39 +215,18 @@ static void show_files(void) const char *path = ".", *base = ""; int baselen = prefix_len; - if (baselen) { + if (baselen) path = base = prefix; - if (exclude_per_dir) { - char *p, *pp = xmalloc(baselen+1); - memcpy(pp, prefix, baselen+1); - p = pp; - while (1) { - char save = *p; - *p = 0; - push_exclude_per_directory(pp, p-pp); - *p++ = save; - if (!save) - break; - p = strchr(p, '/'); - if (p) - p++; - else - p = pp + baselen; - } - free(pp); - } - } - read_directory(path, base, baselen); - qsort(dir, nr_dir, sizeof(struct nond_on_fs *), cmp_name); + read_directory(dir, path, base, baselen); if (show_others) - show_other_files(); + show_other_files(dir); if (show_killed) - show_killed_files(); + show_killed_files(dir); } if (show_cached | show_stage) { for (i = 0; i < active_nr; i++) { struct cache_entry *ce = active_cache[i]; - if (excluded(ce->name) != show_ignored) + if (excluded(dir, ce->name) != dir->show_ignored) continue; if (show_unmerged && !ce_stage(ce)) continue; @@ -565,7 +238,7 @@ static void show_files(void) struct cache_entry *ce = active_cache[i]; struct stat st; int err; - if (excluded(ce->name) != show_ignored) + if (excluded(dir, ce->name) != dir->show_ignored) continue; err = lstat(ce->name, &st); if (show_deleted && err) @@ -652,7 +325,9 @@ int main(int argc, const char **argv) { int i; int exc_given = 0; + struct dir_struct dir; + memset(&dir, 0, sizeof(dir)); prefix = setup_git_directory(); if (prefix) prefix_offset = strlen(prefix); @@ -697,7 +372,7 @@ int main(int argc, const char **argv) continue; } if (!strcmp(arg, "-i") || !strcmp(arg, "--ignored")) { - show_ignored = 1; + dir.show_ignored = 1; continue; } if (!strcmp(arg, "-s") || !strcmp(arg, "--stage")) { @@ -709,11 +384,11 @@ int main(int argc, const char **argv) continue; } if (!strcmp(arg, "--directory")) { - show_other_directories = 1; + dir.show_other_directories = 1; continue; } if (!strcmp(arg, "--no-empty-directory")) { - hide_empty_directories = 1; + dir.hide_empty_directories = 1; continue; } if (!strcmp(arg, "-u") || !strcmp(arg, "--unmerged")) { @@ -726,27 +401,27 @@ int main(int argc, const char **argv) } if (!strcmp(arg, "-x") && i+1 < argc) { exc_given = 1; - add_exclude(argv[++i], "", 0, &exclude_list[EXC_CMDL]); + add_exclude(argv[++i], "", 0, &dir.exclude_list[EXC_CMDL]); continue; } if (!strncmp(arg, "--exclude=", 10)) { exc_given = 1; - add_exclude(arg+10, "", 0, &exclude_list[EXC_CMDL]); + add_exclude(arg+10, "", 0, &dir.exclude_list[EXC_CMDL]); continue; } if (!strcmp(arg, "-X") && i+1 < argc) { exc_given = 1; - add_excludes_from_file(argv[++i]); + add_excludes_from_file(&dir, argv[++i]); continue; } if (!strncmp(arg, "--exclude-from=", 15)) { exc_given = 1; - add_excludes_from_file(arg+15); + add_excludes_from_file(&dir, arg+15); continue; } if (!strncmp(arg, "--exclude-per-directory=", 24)) { exc_given = 1; - exclude_per_dir = arg + 24; + dir.exclude_per_dir = arg + 24; continue; } if (!strcmp(arg, "--full-name")) { @@ -788,7 +463,7 @@ int main(int argc, const char **argv) ps_matched = xcalloc(1, num); } - if (show_ignored && !exc_given) { + if (dir.show_ignored && !exc_given) { fprintf(stderr, "%s: --ignored needs some exclude pattern\n", argv[0]); exit(1); @@ -802,7 +477,7 @@ int main(int argc, const char **argv) read_cache(); if (prefix) prune_cache(); - show_files(); + show_files(&dir); if (ps_matched) { /* We need to make sure all pathspec matched otherwise diff --git a/read-cache.c b/read-cache.c index a917ab0cfe..ed0da38e07 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 @@ -513,6 +528,22 @@ static int verify_hdr(struct cache_header *hdr, unsigned long size) return 0; } +static int read_index_extension(const char *ext, void *data, unsigned long sz) +{ + switch (CACHE_EXT(ext)) { + case CACHE_EXT_TREE: + active_cache_tree = cache_tree_read(data, sz); + break; + default: + if (*ext < 'A' || 'Z' < *ext) + return error("index uses %.4s extension, which we do not understand", + ext); + fprintf(stderr, "ignoring %.4s extension\n", ext); + break; + } + return 0; +} + int read_cache(void) { int fd, i; @@ -561,6 +592,22 @@ int read_cache(void) active_cache[i] = ce; } index_file_timestamp = st.st_mtime; + while (offset <= size - 20 - 8) { + /* After an array of active_nr index entries, + * there can be arbitrary number of extended + * sections, each of which is prefixed with + * extension name (4-byte) and section length + * in 4-byte network byte order. + */ + unsigned long extsize; + memcpy(&extsize, map + offset + 4, 4); + extsize = ntohl(extsize); + if (read_index_extension(map + offset, + map + offset + 8, extsize) < 0) + goto unmap; + offset += 8; + offset += extsize; + } return active_nr; unmap: @@ -595,6 +642,17 @@ static int ce_write(SHA_CTX *context, int fd, void *data, unsigned int len) return 0; } +static int write_index_ext_header(SHA_CTX *context, int fd, + unsigned long ext, unsigned long sz) +{ + ext = htonl(ext); + sz = htonl(sz); + if ((ce_write(context, fd, &ext, 4) < 0) || + (ce_write(context, fd, &sz, 4) < 0)) + return -1; + return 0; +} + static int ce_flush(SHA_CTX *context, int fd) { unsigned int left = write_buffer_len; @@ -691,5 +749,19 @@ int write_cache(int newfd, struct cache_entry **cache, int entries) if (ce_write(&c, newfd, ce, ce_size(ce)) < 0) return -1; } + + /* Write extension data here */ + if (active_cache_tree) { + unsigned long sz; + void *data = cache_tree_write(active_cache_tree, &sz); + if (data && + !write_index_ext_header(&c, newfd, CACHE_EXT_TREE, sz) && + !ce_write(&c, newfd, data, sz)) + ; + else { + free(data); + return -1; + } + } return ce_flush(&c, newfd); } diff --git a/read-tree.c b/read-tree.c index e16e91b173..a3ada55c15 100644 --- a/read-tree.c +++ b/read-tree.c @@ -9,6 +9,7 @@ #include "object.h" #include "tree.h" +#include "cache-tree.h" #include <sys/time.h> #include <signal.h> @@ -21,6 +22,7 @@ static int trivial_merges_only = 0; static int aggressive = 0; static int verbose_update = 0; static volatile int progress_update = 0; +static const char *prefix = NULL; static int head_idx = -1; static int merge_size = 0; @@ -369,7 +371,8 @@ static int unpack_trees(merge_fn_t fn) posns[i] = ((struct tree *) posn->item)->entries; posn = posn->next; } - if (unpack_trees_rec(posns, len, "", fn, &indpos)) + if (unpack_trees_rec(posns, len, prefix ? prefix : "", + fn, &indpos)) return -1; } @@ -408,7 +411,7 @@ static void verify_uptodate(struct cache_entry *ce) { struct stat st; - if (index_only) + if (index_only || reset) return; if (!lstat(ce->name, &st)) { @@ -426,6 +429,27 @@ 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. + */ +static void verify_absent(const char *path, const char *action) +{ + struct stat st; + + if (index_only || reset || !update) + return; + if (!lstat(path, &st)) + die("Untracked working tree file '%s' " + "would be %s by merge.", path, action); +} + static int merged_entry(struct cache_entry *merge, struct cache_entry *old) { merge->ce_flags |= htons(CE_UPDATE); @@ -441,8 +465,14 @@ static int merged_entry(struct cache_entry *merge, struct cache_entry *old) *merge = *old; } else { verify_uptodate(old); + invalidate_ce_path(old); } } + else { + verify_absent(merge->name, "overwritten"); + invalidate_ce_path(merge); + } + merge->ce_flags &= ~htons(CE_STAGEMASK); add_cache_entry(merge, ADD_CACHE_OK_TO_ADD); return 1; @@ -452,8 +482,11 @@ static int deleted_entry(struct cache_entry *ce, struct cache_entry *old) { if (old) verify_uptodate(old); + else + verify_absent(ce->name, "removed"); ce->ce_mode = 0; add_cache_entry(ce, ADD_CACHE_OK_TO_ADD); + invalidate_ce_path(ce); return 1; } @@ -487,6 +520,7 @@ static int threeway_merge(struct cache_entry **stages) int count; int head_match = 0; int remote_match = 0; + const char *path = NULL; int df_conflict_head = 0; int df_conflict_remote = 0; @@ -498,8 +532,11 @@ static int threeway_merge(struct cache_entry **stages) for (i = 1; i < head_idx; i++) { if (!stages[i]) any_anc_missing = 1; - else + else { + if (!path) + path = stages[i]->name; no_anc_exists = 0; + } } index = stages[0]; @@ -515,8 +552,15 @@ static int threeway_merge(struct cache_entry **stages) remote = NULL; } + if (!path && index) + path = index->name; + if (!path && head) + path = head->name; + if (!path && remote) + path = remote->name; + /* First, if there's a #16 situation, note that to prevent #13 - * and #14. + * and #14. */ if (!same(remote, head)) { for (i = 1; i < head_idx; i++) { @@ -575,6 +619,8 @@ static int threeway_merge(struct cache_entry **stages) (remote_deleted && head && head_match)) { if (index) return deleted_entry(index, index); + else if (path) + verify_absent(path, "removed"); return 0; } /* @@ -592,6 +638,8 @@ static int threeway_merge(struct cache_entry **stages) if (index) { verify_uptodate(index); } + else if (path) + verify_absent(path, "overwritten"); nontrivial_merge = 1; @@ -674,6 +722,28 @@ static int twoway_merge(struct cache_entry **src) } /* + * Bind merge. + * + * Keep the index entries at stage0, collapse stage1 but make sure + * stage0 does not have anything there. + */ +static int bind_merge(struct cache_entry **src) +{ + struct cache_entry *old = src[0]; + struct cache_entry *a = src[1]; + + if (merge_size != 1) + return error("Cannot do a bind merge of %d trees\n", + merge_size); + if (a && old) + die("Entry '%s' overlaps. Cannot bind.", a->name); + if (!a) + return keep_entry(old); + else + return merged_entry(a, NULL); +} + +/* * One-way merge. * * The rule is: @@ -688,8 +758,10 @@ static int oneway_merge(struct cache_entry **src) return error("Cannot do a oneway merge of %d trees", merge_size); - if (!a) - return deleted_entry(old, NULL); + if (!a) { + invalidate_ce_path(old); + return deleted_entry(old, old); + } if (old && same(old, a)) { if (reset) { struct stat st; @@ -699,7 +771,7 @@ static int oneway_merge(struct cache_entry **src) } return keep_entry(old); } - return merged_entry(a, NULL); + return merged_entry(a, old); } static int read_cache_unmerged(void) @@ -714,6 +786,7 @@ static int read_cache_unmerged(void) struct cache_entry *ce = active_cache[i]; if (ce_stage(ce)) { deleted++; + invalidate_ce_path(ce); continue; } if (deleted) @@ -724,7 +797,40 @@ static int read_cache_unmerged(void) return deleted; } -static const char read_tree_usage[] = "git-read-tree (<sha> | -m [--aggressive] [-u | -i] <sha1> [<sha2> [<sha3>]])"; +static void prime_cache_tree_rec(struct cache_tree *it, struct tree *tree) +{ + struct tree_entry_list *ent; + int cnt; + + memcpy(it->sha1, tree->object.sha1, 20); + for (cnt = 0, ent = tree->entries; ent; ent = ent->next) { + if (!ent->directory) + cnt++; + else { + struct cache_tree_sub *sub; + struct tree *subtree = (struct tree *)ent->item.tree; + if (!subtree->object.parsed) + parse_tree(subtree); + sub = cache_tree_sub(it, ent->name); + sub->cache_tree = cache_tree(); + prime_cache_tree_rec(sub->cache_tree, subtree); + cnt += sub->cache_tree->entry_count; + } + } + it->entry_count = cnt; +} + +static void prime_cache_tree(void) +{ + struct tree *tree = (struct tree *)trees->item; + if (!tree) + return; + active_cache_tree = cache_tree(); + prime_cache_tree_rec(active_cache_tree, tree); + +} + +static const char read_tree_usage[] = "git-read-tree (<sha> | [[-m [--aggressive] | --reset | --prefix=<prefix>] [-u | -i]] <sha1> [<sha2> [<sha3>]])"; static struct cache_file cache_file; @@ -769,9 +875,24 @@ int main(int argc, char **argv) continue; } + /* "--prefix=<subdirectory>/" means keep the current index + * entries and put the entries from the tree under the + * given subdirectory. + */ + if (!strncmp(arg, "--prefix=", 9)) { + if (stage || merge || prefix) + usage(read_tree_usage); + prefix = arg + 9; + merge = 1; + stage = 1; + if (read_cache_unmerged()) + die("you need to resolve your current index first"); + continue; + } + /* This differs from "-m" in that we'll silently ignore unmerged entries */ if (!strcmp(arg, "--reset")) { - if (stage || merge) + if (stage || merge || prefix) usage(read_tree_usage); reset = 1; merge = 1; @@ -792,7 +913,7 @@ int main(int argc, char **argv) /* "-m" stands for "merge", meaning we start in stage 1 */ if (!strcmp(arg, "-m")) { - if (stage || merge) + if (stage || merge || prefix) usage(read_tree_usage); if (read_cache_unmerged()) die("you need to resolve your current index first"); @@ -814,21 +935,39 @@ int main(int argc, char **argv) if ((update||index_only) && !merge) usage(read_tree_usage); + if (prefix) { + int pfxlen = strlen(prefix); + int pos; + if (prefix[pfxlen-1] != '/') + die("prefix must end with /"); + if (stage != 2) + die("binding merge takes only one tree"); + pos = cache_name_pos(prefix, pfxlen); + if (0 <= pos) + die("corrupt index file"); + pos = -pos-1; + if (pos < active_nr && + !strncmp(active_cache[pos]->name, prefix, pfxlen)) + die("subdirectory '%s' already exists.", prefix); + pos = cache_name_pos(prefix, pfxlen-1); + if (0 <= pos) + die("file '%.*s' already exists.", pfxlen-1, prefix); + } + if (merge) { if (stage < 2) die("just how do you expect me to merge %d trees?", stage-1); switch (stage - 1) { case 1: - fn = oneway_merge; + fn = prefix ? bind_merge : oneway_merge; break; case 2: fn = twoway_merge; break; case 3: - fn = threeway_merge; - break; default: fn = threeway_merge; + cache_tree_free(&active_cache_tree); break; } @@ -839,6 +978,18 @@ int main(int argc, char **argv) } unpack_trees(fn); + + /* + * When reading only one tree (either the most basic form, + * "-m ent" or "--reset ent" form), we can obtain a fully + * valid cache-tree because the index must match exactly + * what came from the tree. + */ + if (trees && trees->item && (!merge || (stage == 2))) { + cache_tree_free(&active_cache_tree); + prime_cache_tree(); + } + if (write_cache(newfd, active_cache, active_nr) || commit_index_file(&cache_file)) die("unable to write new index file"); diff --git a/rev-list.c b/rev-list.c index 8b0ec388fa..235ae4c7e1 100644 --- a/rev-list.c +++ b/rev-list.c @@ -84,7 +84,7 @@ static void show_commit(struct commit *commit) static char pretty_header[16384]; pretty_print_commit(revs.commit_format, commit, ~0, pretty_header, sizeof(pretty_header), - revs.abbrev); + revs.abbrev, NULL); printf("%s%c", pretty_header, hdr_termination); } fflush(stdout); diff --git a/revision.h b/revision.h index 48d7b4ca94..62759f7bc0 100644 --- a/revision.h +++ b/revision.h @@ -58,6 +58,7 @@ struct rev_info { unsigned int abbrev; enum cmit_fmt commit_format; struct log_info *loginfo; + int nr, total; /* special limits */ int max_count; diff --git a/show-branch.c b/show-branch.c index 268c57b180..bbe26c2e7a 100644 --- a/show-branch.c +++ b/show-branch.c @@ -259,7 +259,7 @@ static void show_one_commit(struct commit *commit, int no_name) struct commit_name *name = commit->object.util; if (commit->object.parsed) pretty_print_commit(CMIT_FMT_ONELINE, commit, ~0, - pretty, sizeof(pretty), 0); + pretty, sizeof(pretty), 0, NULL); else strcpy(pretty, "(unavailable)"); if (!strncmp(pretty, "[PATCH] ", 8)) 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/t/t1002-read-tree-m-u-2way.sh b/t/t1002-read-tree-m-u-2way.sh index 4d175d8ea1..8335a63e2e 100755 --- a/t/t1002-read-tree-m-u-2way.sh +++ b/t/t1002-read-tree-m-u-2way.sh @@ -39,7 +39,6 @@ test_expect_success \ echo nitfol >nitfol && echo bozbar >bozbar && echo rezrov >rezrov && - echo yomin >yomin && git-update-index --add nitfol bozbar rezrov && treeH=`git-write-tree` && echo treeH $treeH && @@ -56,7 +55,8 @@ test_expect_success \ test_expect_success \ '1, 2, 3 - no carry forward' \ - 'rm -f .git/index && + 'rm -f .git/index nitfol bozbar rezrov frotz && + git-read-tree --reset -u $treeH && git-read-tree -m -u $treeH $treeM && git-ls-files --stage >1-3.out && cmp M.out 1-3.out && @@ -66,11 +66,12 @@ test_expect_success \ check_cache_at frotz clean && check_cache_at nitfol clean' -echo '+100644 X 0 yomin' >expected - test_expect_success \ '4 - carry forward local addition.' \ - 'rm -f .git/index && + 'rm -f .git/index nitfol bozbar rezrov frotz && + git-read-tree --reset -u $treeH && + echo "+100644 X 0 yomin" >expected && + echo yomin >yomin && git-update-index --add yomin && git-read-tree -m -u $treeH $treeM && git-ls-files --stage >4.out || return 1 @@ -85,7 +86,9 @@ test_expect_success \ test_expect_success \ '5 - carry forward local addition.' \ - 'rm -f .git/index && + 'rm -f .git/index nitfol bozbar rezrov frotz && + git-read-tree --reset -u $treeH && + git-read-tree -m -u $treeH && echo yomin >yomin && git-update-index --add yomin && echo yomin yomin >yomin && @@ -103,7 +106,9 @@ test_expect_success \ test_expect_success \ '6 - local addition already has the same.' \ - 'rm -f .git/index && + 'rm -f .git/index nitfol bozbar rezrov frotz && + git-read-tree --reset -u $treeH && + echo frotz >frotz && git-update-index --add frotz && git-read-tree -m -u $treeH $treeM && git-ls-files --stage >6.out && @@ -117,7 +122,8 @@ test_expect_success \ test_expect_success \ '7 - local addition already has the same.' \ - 'rm -f .git/index && + 'rm -f .git/index nitfol bozbar rezrov frotz && + git-read-tree --reset -u $treeH && echo frotz >frotz && git-update-index --add frotz && echo frotz frotz >frotz && @@ -134,14 +140,16 @@ test_expect_success \ test_expect_success \ '8 - conflicting addition.' \ - 'rm -f .git/index && + 'rm -f .git/index nitfol bozbar rezrov frotz && + git-read-tree --reset -u $treeH && echo frotz frotz >frotz && git-update-index --add frotz && if git-read-tree -m -u $treeH $treeM; then false; else :; fi' test_expect_success \ '9 - conflicting addition.' \ - 'rm -f .git/index && + 'rm -f .git/index nitfol bozbar rezrov frotz && + git-read-tree --reset -u $treeH && echo frotz frotz >frotz && git-update-index --add frotz && echo frotz >frotz && @@ -149,7 +157,8 @@ test_expect_success \ test_expect_success \ '10 - path removed.' \ - 'rm -f .git/index && + 'rm -f .git/index nitfol bozbar rezrov frotz && + git-read-tree --reset -u $treeH && echo rezrov >rezrov && git-update-index --add rezrov && git-read-tree -m -u $treeH $treeM && @@ -160,7 +169,8 @@ test_expect_success \ test_expect_success \ '11 - dirty path removed.' \ - 'rm -f .git/index && + 'rm -f .git/index nitfol bozbar rezrov frotz && + git-read-tree --reset -u $treeH && echo rezrov >rezrov && git-update-index --add rezrov && echo rezrov rezrov >rezrov && @@ -168,14 +178,16 @@ test_expect_success \ test_expect_success \ '12 - unmatching local changes being removed.' \ - 'rm -f .git/index && + 'rm -f .git/index nitfol bozbar rezrov frotz && + git-read-tree --reset -u $treeH && echo rezrov rezrov >rezrov && git-update-index --add rezrov && if git-read-tree -m -u $treeH $treeM; then false; else :; fi' test_expect_success \ '13 - unmatching local changes being removed.' \ - 'rm -f .git/index && + 'rm -f .git/index nitfol bozbar rezrov frotz && + git-read-tree --reset -u $treeH && echo rezrov rezrov >rezrov && git-update-index --add rezrov && echo rezrov >rezrov && @@ -188,7 +200,8 @@ EOF test_expect_success \ '14 - unchanged in two heads.' \ - 'rm -f .git/index && + 'rm -f .git/index nitfol bozbar rezrov frotz && + git-read-tree --reset -u $treeH && echo nitfol nitfol >nitfol && git-update-index --add nitfol && git-read-tree -m -u $treeH $treeM && @@ -207,7 +220,8 @@ test_expect_success \ test_expect_success \ '15 - unchanged in two heads.' \ - 'rm -f .git/index && + 'rm -f .git/index nitfol bozbar rezrov frotz && + git-read-tree --reset -u $treeH && echo nitfol nitfol >nitfol && git-update-index --add nitfol && echo nitfol nitfol nitfol >nitfol && @@ -227,14 +241,16 @@ test_expect_success \ test_expect_success \ '16 - conflicting local change.' \ - 'rm -f .git/index && + 'rm -f .git/index nitfol bozbar rezrov frotz && + git-read-tree --reset -u $treeH && echo bozbar bozbar >bozbar && git-update-index --add bozbar && if git-read-tree -m -u $treeH $treeM; then false; else :; fi' test_expect_success \ '17 - conflicting local change.' \ - 'rm -f .git/index && + 'rm -f .git/index nitfol bozbar rezrov frotz && + git-read-tree --reset -u $treeH && echo bozbar bozbar >bozbar && git-update-index --add bozbar && echo bozbar bozbar bozbar >bozbar && @@ -242,7 +258,8 @@ test_expect_success \ test_expect_success \ '18 - local change already having a good result.' \ - 'rm -f .git/index && + 'rm -f .git/index nitfol bozbar rezrov frotz && + git-read-tree --reset -u $treeH && echo gnusto >bozbar && git-update-index --add bozbar && git-read-tree -m -u $treeH $treeM && @@ -254,7 +271,8 @@ test_expect_success \ test_expect_success \ '19 - local change already having a good result, further modified.' \ - 'rm -f .git/index && + 'rm -f .git/index nitfol bozbar rezrov frotz && + git-read-tree --reset -u $treeH && echo gnusto >bozbar && git-update-index --add bozbar && echo gnusto gnusto >bozbar && @@ -273,7 +291,8 @@ test_expect_success \ test_expect_success \ '20 - no local change, use new tree.' \ - 'rm -f .git/index && + 'rm -f .git/index nitfol bozbar rezrov frotz && + git-read-tree --reset -u $treeH && echo bozbar >bozbar && git-update-index --add bozbar && git-read-tree -m -u $treeH $treeM && @@ -285,7 +304,8 @@ test_expect_success \ test_expect_success \ '21 - no local change, dirty cache.' \ - 'rm -f .git/index && + 'rm -f .git/index nitfol bozbar rezrov frotz && + git-read-tree --reset -u $treeH && echo bozbar >bozbar && git-update-index --add bozbar && echo gnusto gnusto >bozbar && @@ -294,7 +314,7 @@ test_expect_success \ # Also make sure we did not break DF vs DF/DF case. test_expect_success \ 'DF vs DF/DF case setup.' \ - 'rm -f .git/index && + 'rm -f .git/index echo DF >DF && git-update-index --add DF && treeDF=`git-write-tree` && diff --git a/t/t3500-cherry.sh b/t/t3500-cherry.sh index b141f89de2..e83bbee074 100755 --- a/t/t3500-cherry.sh +++ b/t/t3500-cherry.sh @@ -30,6 +30,7 @@ test_expect_success \ git-commit -m "Add C." && git-checkout -f master && + rm -f B C && echo Third >> A && git-update-index A && diff --git a/t/t4002-diff-basic.sh b/t/t4002-diff-basic.sh index 769274aa51..56eda63fc2 100755 --- a/t/t4002-diff-basic.sh +++ b/t/t4002-diff-basic.sh @@ -191,7 +191,7 @@ test_expect_success \ 'rm -fr Z [A-Z][A-Z] && git-read-tree $tree_A && git-checkout-index -f -a && - git-read-tree -m $tree_O || return 1 + git-read-tree --reset $tree_O || return 1 git-update-index --refresh >/dev/null ;# this can exit non-zero git-diff-files >.test-a && cmp_diff_files_output .test-a .test-recursive-OA' @@ -201,7 +201,7 @@ test_expect_success \ 'rm -fr Z [A-Z][A-Z] && git-read-tree $tree_B && git-checkout-index -f -a && - git-read-tree -m $tree_O || return 1 + git-read-tree --reset $tree_O || return 1 git-update-index --refresh >/dev/null ;# this can exit non-zero git-diff-files >.test-a && cmp_diff_files_output .test-a .test-recursive-OB' @@ -211,7 +211,7 @@ test_expect_success \ 'rm -fr Z [A-Z][A-Z] && git-read-tree $tree_B && git-checkout-index -f -a && - git-read-tree -m $tree_A || return 1 + git-read-tree --reset $tree_A || return 1 git-update-index --refresh >/dev/null ;# this can exit non-zero git-diff-files >.test-a && cmp_diff_files_output .test-a .test-recursive-AB' diff --git a/t/t6022-merge-rename.sh b/t/t6022-merge-rename.sh index a2d24b5ca9..5ac25647ab 100755 --- a/t/t6022-merge-rename.sh +++ b/t/t6022-merge-rename.sh @@ -111,6 +111,7 @@ test_expect_success 'pull renaming branch into unrenaming one' \ test_expect_success 'pull renaming branch into another renaming one' \ ' + rm -f B git reset --hard git checkout red git pull . white && { diff --git a/update-index.c b/update-index.c index 3d7e02db2c..f6b09a4800 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" /* @@ -71,6 +72,7 @@ static int mark_valid(const char *path) active_cache[pos]->ce_flags &= ~htons(CE_VALID); break; } + cache_tree_invalidate_path(active_cache_tree, path); active_cache_changed = 1; return 0; } @@ -84,6 +86,12 @@ static int add_file_to_cache(const char *path) struct stat st; status = lstat(path, &st); + + /* We probably want to do this in remove_file_from_cache() and + * add_cache_entry() instead... + */ + cache_tree_invalidate_path(active_cache_tree, path); + if (status < 0 || S_ISDIR(st.st_mode)) { /* When we used to have "path" and now we want to add * "path/file", we need a way to remove "path" before @@ -326,6 +334,7 @@ static int add_cacheinfo(unsigned int mode, const unsigned char *sha1, return error("%s: cannot add to the index - missing --add option?", path); report("add '%s'", path); + cache_tree_invalidate_path(active_cache_tree, path); return 0; } @@ -350,6 +359,7 @@ static void chmod_path(int flip, const char *path) default: goto fail; } + cache_tree_invalidate_path(active_cache_tree, path); active_cache_changed = 1; report("chmod %cx '%s'", flip, path); return; @@ -371,6 +381,7 @@ static void update_one(const char *path, const char *prefix, int prefix_length) die("Unable to mark file %s", path); goto free_return; } + cache_tree_invalidate_path(active_cache_tree, path); if (force_remove) { if (remove_file_from_cache(p)) @@ -449,6 +460,7 @@ static void read_index_info(int line_termination) free(path_name); continue; } + cache_tree_invalidate_path(active_cache_tree, path_name); if (!mode) { /* mode == 0 means there is no such path -- remove */ @@ -555,6 +567,7 @@ static int unresolve_one(const char *path) goto free_return; } + cache_tree_invalidate_path(active_cache_tree, path); remove_file_from_cache(path); if (add_cache_entry(ce_2, ADD_CACHE_OK_TO_ADD)) { error("%s: cannot add our version to the index.", path); 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; } |