diff options
53 files changed, 2574 insertions, 255 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/diff-options.txt b/Documentation/diff-options.txt index c183dc9da0..f523ec2fbe 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -10,6 +10,10 @@ --stat:: Generate a diffstat instead of a patch. +--summary:: + Output a condensed summary of extended header information + such as creations, renames and mode changes. + --patch-with-stat:: Generate patch and prepend its diffstat. diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt index d55456ae93..74102b7944 100644 --- a/Documentation/git-grep.txt +++ b/Documentation/git-grep.txt @@ -8,43 +8,82 @@ git-grep - Print lines matching a pattern SYNOPSIS -------- -'git-grep' [<option>...] [-e] <pattern> [--] [<path>...] +[verse] +'git-grep' [--cached] + [-a | --text] [-I] [-i | --ignore-case] [-w | --word-regexp] + [-v | --invert-match] + [-E | --extended-regexp] [-G | --basic-regexp] [-F | --fixed-strings] + [-n] [-l | --files-with-matches] [-L | --files-without-match] + [-c | --count] + [-A <post-context>] [-B <pre-context>] [-C <context>] + [-f <file>] [-e <pattern>] + [<tree>...] + [--] [<path>...] DESCRIPTION ----------- -Searches list of files `git-ls-files` produces for lines -containing a match to the given pattern. +Look for specified patterns in the working tree files, blobs +registered in the index file, or given tree objects. OPTIONS ------- -`--`:: - Signals the end of options; the rest of the parameters - are <path> limiters. +--cached:: + Instead of searching in the working tree files, check + the blobs registerd in the index file. + +-a | --text:: + Process binary files as if they were text. + +-i | --ignore-case:: + Ignore case differences between the patterns and the + files. + +-w | --word-regexp:: + Match the pattern only at word boundary (either begin at the + beginning of a line, or preceded by a non-word character; end at + the end of a line or followed by a non-word character). + +-v | --invert-match:: + Select non-matching lines. + +-E | --extended-regexp | -G | --basic-regexp:: + Use POSIX extended/basic regexp for patterns. Default + is to use basic regexp. -<option>...:: - Either an option to pass to `grep` or `git-ls-files`. -+ -The following are the specific `git-ls-files` options -that may be given: `-o`, `--cached`, `--deleted`, `--others`, -`--killed`, `--ignored`, `--modified`, `--exclude=\*`, -`--exclude-from=\*`, and `--exclude-per-directory=\*`. -+ -All other options will be passed to `grep`. +-n:: + Prefix the line number to matching lines. -<pattern>:: - The pattern to look for. The first non option is taken - as the pattern; if your pattern begins with a dash, use - `-e <pattern>`. +-l | --files-with-matches | -L | --files-without-match:: + Instead of showing every matched line, show only the + names of files that contain (or do not contain) matches. -<path>...:: - Optional paths to limit the set of files to be searched; - passed to `git-ls-files`. +-c | --count:: + Instead of showing every matched line, show the number of + lines that match. + +-[ABC] <context>:: + Show `context` trailing (`A` -- after), or leading (`B` + -- before), or both (`C` -- context) lines, and place a + line containing `--` between continguous groups of + matches. + +-f <file>:: + Read patterns from <file>, one per line. + +`<tree>...`:: + Search blobs in the trees for specified patterns. + +`--`:: + Signals the end of options; the rest of the parameters + are <path> limiters. Author ------ -Written by Linus Torvalds <torvalds@osdl.org> +Originally written by Linus Torvalds <torvalds@osdl.org>, later +revamped by Junio C Hamano. + Documentation -------------- 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-rebase.txt b/Documentation/git-rebase.txt index 1b482abecd..08ee4aabaf 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -9,9 +9,7 @@ SYNOPSIS -------- 'git-rebase' [--onto <newbase>] <upstream> [<branch>] -'git-rebase' --continue - -'git-rebase' --abort +'git-rebase' --continue | --skip | --abort DESCRIPTION ----------- @@ -23,9 +21,10 @@ not exist in the <upstream> branch. It is possible that a merge failure will prevent this process from being completely automatic. You will have to resolve any such merge failure -and run `git rebase --continue`. If you can not resolve the merge -failure, running `git rebase --abort` will restore the original <branch> -and remove the working files found in the .dotest directory. +and run `git rebase --continue`. Another option is to bypass the commit +that caused the merge failure with `git rebase --skip`. To restore the +original <branch> and remove the .dotest working files, use the command +`git rebase --abort` instead. Note that if <branch> is not specified on the command line, the currently checked out branch is used. diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt index 8b95df0c6e..ab896fcf83 100644 --- a/Documentation/git-rev-parse.txt +++ b/Documentation/git-rev-parse.txt @@ -67,6 +67,15 @@ OPTIONS --all:: Show all refs found in `$GIT_DIR/refs`. +--branches:: + Show branch refs found in `$GIT_DIR/refs/heads`. + +--tags:: + Show tag refs found in `$GIT_DIR/refs/tags`. + +--remotes:: + Show tag refs found in `$GIT_DIR/refs/remotes`. + --show-prefix:: When the command is invoked from a subdirectory, show the path of the current directory relative to the top-level 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> @@ -131,7 +131,8 @@ SCRIPT_PERL = \ git-archimport.perl git-cvsimport.perl git-relink.perl \ git-shortlog.perl git-fmt-merge-msg.perl git-rerere.perl \ git-annotate.perl git-cvsserver.perl \ - git-svnimport.perl git-mv.perl git-cvsexportcommit.perl + git-svnimport.perl git-mv.perl git-cvsexportcommit.perl \ + git-send-email.perl SCRIPT_PYTHON = \ git-merge-recursive.py @@ -205,7 +206,7 @@ DIFF_OBJS = \ diffcore-delta.o log-tree.o LIB_OBJS = \ - blob.o commit.o connect.o csum-file.o base85.o \ + blob.o commit.o connect.o csum-file.o cache-tree.o base85.o \ date.o diff-delta.o entry.o exec_cmd.o ident.o index.o \ object.o pack-check.o patch-delta.o path.o pkt-line.o \ quote.o read-cache.o refs.o run-command.o \ @@ -215,7 +216,8 @@ LIB_OBJS = \ $(DIFF_OBJS) BUILTIN_OBJS = \ - builtin-log.o builtin-help.o builtin-count.o builtin-diff.o builtin-push.o + builtin-log.o builtin-help.o builtin-count.o builtin-diff.o builtin-push.o \ + builtin-grep.o GITLIBS = $(LIB_FILE) $(XDIFF_LIB) LIBS = $(GITLIBS) -lz @@ -319,10 +321,6 @@ else endif endif -ifdef WITH_SEND_EMAIL - SCRIPT_PERL += git-send-email.perl -endif - ifndef NO_CURL ifdef CURLDIR # This is still problematic -- gcc does not always want -R. @@ -609,6 +607,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" @@ -1778,7 +1779,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,6 +1894,7 @@ static void remove_file(struct patch *patch) if (write_index) { if (remove_file_from_cache(patch->old_name) < 0) die("unable to remove %s from index", patch->old_name); + cache_tree_invalidate_path(active_cache_tree, patch->old_name); } unlink(patch->old_name); } @@ -1989,8 +1991,9 @@ static void create_file(struct patch *patch) if (!mode) mode = S_IFREG | 0644; - create_one_file(path, mode, buf, size); + create_one_file(path, mode, buf, size); add_index_file(path, mode, buf, size); + cache_tree_invalidate_path(active_cache_tree, path); } static void write_out_one_result(struct patch *patch) diff --git a/builtin-diff.c b/builtin-diff.c index d3ac581f29..71742aa10b 100644 --- a/builtin-diff.c +++ b/builtin-diff.c @@ -231,7 +231,7 @@ static int builtin_diff_combined(struct rev_info *revs, return 0; } -static void add_head(struct rev_info *revs) +void add_head(struct rev_info *revs) { unsigned char sha1[20]; struct object *obj; diff --git a/builtin-grep.c b/builtin-grep.c new file mode 100644 index 0000000000..14471db7cb --- /dev/null +++ b/builtin-grep.c @@ -0,0 +1,836 @@ +/* + * Builtin "git grep" + * + * Copyright (c) 2006 Junio C Hamano + */ +#include "cache.h" +#include "blob.h" +#include "tree.h" +#include "commit.h" +#include "tag.h" +#include "tree-walk.h" +#include "builtin.h" +#include <regex.h> +#include <fnmatch.h> +#include <sys/wait.h> + +/* + * git grep pathspecs are somewhat different from diff-tree pathspecs; + * pathname wildcards are allowed. + */ +static int pathspec_matches(const char **paths, const char *name) +{ + int namelen, i; + if (!paths || !*paths) + return 1; + namelen = strlen(name); + for (i = 0; paths[i]; i++) { + const char *match = paths[i]; + int matchlen = strlen(match); + const char *cp, *meta; + + if ((matchlen <= namelen) && + !strncmp(name, match, matchlen) && + (match[matchlen-1] == '/' || + name[matchlen] == '\0' || name[matchlen] == '/')) + return 1; + if (!fnmatch(match, name, 0)) + return 1; + if (name[namelen-1] != '/') + continue; + + /* We are being asked if the directory ("name") is worth + * descending into. + * + * Find the longest leading directory name that does + * not have metacharacter in the pathspec; the name + * we are looking at must overlap with that directory. + */ + for (cp = match, meta = NULL; cp - match < matchlen; cp++) { + char ch = *cp; + if (ch == '*' || ch == '[' || ch == '?') { + meta = cp; + break; + } + } + if (!meta) + meta = cp; /* fully literal */ + + if (namelen <= meta - match) { + /* Looking at "Documentation/" and + * the pattern says "Documentation/howto/", or + * "Documentation/diff*.txt". The name we + * have should match prefix. + */ + if (!memcmp(match, name, namelen)) + return 1; + continue; + } + + if (meta - match < namelen) { + /* Looking at "Documentation/howto/" and + * the pattern says "Documentation/h*"; + * match up to "Do.../h"; this avoids descending + * into "Documentation/technical/". + */ + if (!memcmp(match, name, meta - match)) + return 1; + continue; + } + } + return 0; +} + +struct grep_pat { + struct grep_pat *next; + const char *origin; + int no; + const char *pattern; + regex_t regexp; +}; + +struct grep_opt { + struct grep_pat *pattern_list; + struct grep_pat **pattern_tail; + regex_t regexp; + unsigned linenum:1; + unsigned invert:1; + unsigned name_only:1; + unsigned unmatch_name_only:1; + unsigned count:1; + unsigned word_regexp:1; + unsigned fixed:1; +#define GREP_BINARY_DEFAULT 0 +#define GREP_BINARY_NOMATCH 1 +#define GREP_BINARY_TEXT 2 + unsigned binary:2; + int regflags; + unsigned pre_context; + unsigned post_context; +}; + +static void add_pattern(struct grep_opt *opt, const char *pat, + const char *origin, int no) +{ + struct grep_pat *p = xcalloc(1, sizeof(*p)); + p->pattern = pat; + p->origin = origin; + p->no = no; + *opt->pattern_tail = p; + opt->pattern_tail = &p->next; + p->next = NULL; +} + +static void compile_patterns(struct grep_opt *opt) +{ + struct grep_pat *p; + for (p = opt->pattern_list; p; p = p->next) { + int err = regcomp(&p->regexp, p->pattern, opt->regflags); + if (err) { + char errbuf[1024]; + char where[1024]; + if (p->no) + sprintf(where, "In '%s' at %d, ", + p->origin, p->no); + else if (p->origin) + sprintf(where, "%s, ", p->origin); + else + where[0] = 0; + regerror(err, &p->regexp, errbuf, 1024); + regfree(&p->regexp); + die("%s'%s': %s", where, p->pattern, errbuf); + } + } +} + +static char *end_of_line(char *cp, unsigned long *left) +{ + unsigned long l = *left; + while (l && *cp != '\n') { + l--; + cp++; + } + *left = l; + return cp; +} + +static int word_char(char ch) +{ + return isalnum(ch) || ch == '_'; +} + +static void show_line(struct grep_opt *opt, const char *bol, const char *eol, + const char *name, unsigned lno, char sign) +{ + printf("%s%c", name, sign); + if (opt->linenum) + printf("%d%c", lno, sign); + printf("%.*s\n", (int)(eol-bol), bol); +} + +/* + * NEEDSWORK: share code with diff.c + */ +#define FIRST_FEW_BYTES 8000 +static int buffer_is_binary(const char *ptr, unsigned long size) +{ + if (FIRST_FEW_BYTES < size) + size = FIRST_FEW_BYTES; + if (memchr(ptr, 0, size)) + return 1; + return 0; +} + +static int fixmatch(const char *pattern, char *line, regmatch_t *match) +{ + char *hit = strstr(line, pattern); + if (!hit) { + match->rm_so = match->rm_eo = -1; + return REG_NOMATCH; + } + else { + match->rm_so = hit - line; + match->rm_eo = match->rm_so + strlen(pattern); + return 0; + } +} + +static int grep_buffer(struct grep_opt *opt, const char *name, + char *buf, unsigned long size) +{ + char *bol = buf; + unsigned long left = size; + unsigned lno = 1; + struct pre_context_line { + char *bol; + char *eol; + } *prev = NULL, *pcl; + unsigned last_hit = 0; + unsigned last_shown = 0; + int binary_match_only = 0; + const char *hunk_mark = ""; + unsigned count = 0; + + if (buffer_is_binary(buf, size)) { + switch (opt->binary) { + case GREP_BINARY_DEFAULT: + binary_match_only = 1; + break; + case GREP_BINARY_NOMATCH: + return 0; /* Assume unmatch */ + break; + default: + break; + } + } + + if (opt->pre_context) + prev = xcalloc(opt->pre_context, sizeof(*prev)); + if (opt->pre_context || opt->post_context) + hunk_mark = "--\n"; + + while (left) { + regmatch_t pmatch[10]; + char *eol, ch; + int hit = 0; + struct grep_pat *p; + + eol = end_of_line(bol, &left); + ch = *eol; + *eol = 0; + + for (p = opt->pattern_list; p; p = p->next) { + if (!opt->fixed) { + regex_t *exp = &p->regexp; + hit = !regexec(exp, bol, ARRAY_SIZE(pmatch), + pmatch, 0); + } + else { + hit = !fixmatch(p->pattern, bol, pmatch); + } + + if (hit && opt->word_regexp) { + /* Match beginning must be either + * beginning of the line, or at word + * boundary (i.e. the last char must + * not be alnum or underscore). + */ + if ((pmatch[0].rm_so < 0) || + (eol - bol) <= pmatch[0].rm_so || + (pmatch[0].rm_eo < 0) || + (eol - bol) < pmatch[0].rm_eo) + die("regexp returned nonsense"); + if (pmatch[0].rm_so != 0 && + word_char(bol[pmatch[0].rm_so-1])) + hit = 0; + if (pmatch[0].rm_eo != (eol-bol) && + word_char(bol[pmatch[0].rm_eo])) + hit = 0; + } + if (hit) + break; + } + /* "grep -v -e foo -e bla" should list lines + * that do not have either, so inversion should + * be done outside. + */ + if (opt->invert) + hit = !hit; + if (opt->unmatch_name_only) { + if (hit) + return 0; + goto next_line; + } + if (hit) { + count++; + if (binary_match_only) { + printf("Binary file %s matches\n", name); + return 1; + } + if (opt->name_only) { + printf("%s\n", name); + return 1; + } + /* Hit at this line. If we haven't shown the + * pre-context lines, we would need to show them. + * When asked to do "count", this still show + * the context which is nonsense, but the user + * deserves to get that ;-). + */ + if (opt->pre_context) { + unsigned from; + if (opt->pre_context < lno) + from = lno - opt->pre_context; + else + from = 1; + if (from <= last_shown) + from = last_shown + 1; + if (last_shown && from != last_shown + 1) + printf(hunk_mark); + while (from < lno) { + pcl = &prev[lno-from-1]; + show_line(opt, pcl->bol, pcl->eol, + name, from, '-'); + from++; + } + last_shown = lno-1; + } + if (last_shown && lno != last_shown + 1) + printf(hunk_mark); + if (!opt->count) + show_line(opt, bol, eol, name, lno, ':'); + last_shown = last_hit = lno; + } + else if (last_hit && + lno <= last_hit + opt->post_context) { + /* If the last hit is within the post context, + * we need to show this line. + */ + if (last_shown && lno != last_shown + 1) + printf(hunk_mark); + show_line(opt, bol, eol, name, lno, '-'); + last_shown = lno; + } + if (opt->pre_context) { + memmove(prev+1, prev, + (opt->pre_context-1) * sizeof(*prev)); + prev->bol = bol; + prev->eol = eol; + } + + next_line: + *eol = ch; + bol = eol + 1; + if (!left) + break; + left--; + lno++; + } + + if (opt->unmatch_name_only) { + /* We did not see any hit, so we want to show this */ + printf("%s\n", name); + return 1; + } + + /* NEEDSWORK: + * The real "grep -c foo *.c" gives many "bar.c:0" lines, + * which feels mostly useless but sometimes useful. Maybe + * make it another option? For now suppress them. + */ + if (opt->count && count) + printf("%s:%u\n", name, count); + return !!last_hit; +} + +static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1, const char *name) +{ + unsigned long size; + char *data; + char type[20]; + int hit; + data = read_sha1_file(sha1, type, &size); + if (!data) { + error("'%s': unable to read %s", name, sha1_to_hex(sha1)); + return 0; + } + hit = grep_buffer(opt, name, data, size); + free(data); + return hit; +} + +static int grep_file(struct grep_opt *opt, const char *filename) +{ + struct stat st; + int i; + char *data; + if (lstat(filename, &st) < 0) { + err_ret: + if (errno != ENOENT) + error("'%s': %s", filename, strerror(errno)); + return 0; + } + if (!st.st_size) + return 0; /* empty file -- no grep hit */ + if (!S_ISREG(st.st_mode)) + return 0; + i = open(filename, O_RDONLY); + if (i < 0) + goto err_ret; + data = xmalloc(st.st_size + 1); + if (st.st_size != xread(i, data, st.st_size)) { + error("'%s': short read %s", filename, strerror(errno)); + close(i); + free(data); + return 0; + } + close(i); + i = grep_buffer(opt, filename, data, st.st_size); + free(data); + return i; +} + +static int exec_grep(int argc, const char **argv) +{ + pid_t pid; + int status; + + argv[argc] = NULL; + pid = fork(); + if (pid < 0) + return pid; + if (!pid) { + execvp("grep", (char **) argv); + exit(255); + } + while (waitpid(pid, &status, 0) < 0) { + if (errno == EINTR) + continue; + return -1; + } + if (WIFEXITED(status)) { + if (!WEXITSTATUS(status)) + return 1; + return 0; + } + return -1; +} + +#define MAXARGS 1000 + +static int external_grep(struct grep_opt *opt, const char **paths, int cached) +{ + int i, nr, argc, hit; + const char *argv[MAXARGS+1]; + struct grep_pat *p; + + nr = 0; + argv[nr++] = "grep"; + if (opt->word_regexp) + argv[nr++] = "-w"; + if (opt->name_only) + argv[nr++] = "-l"; + for (p = opt->pattern_list; p; p = p->next) { + argv[nr++] = "-e"; + argv[nr++] = p->pattern; + } + argv[nr++] = "--"; + + hit = 0; + argc = nr; + for (i = 0; i < active_nr; i++) { + struct cache_entry *ce = active_cache[i]; + if (ce_stage(ce) || !S_ISREG(ntohl(ce->ce_mode))) + continue; + if (!pathspec_matches(paths, ce->name)) + continue; + argv[argc++] = ce->name; + if (argc < MAXARGS) + continue; + hit += exec_grep(argc, argv); + argc = nr; + } + if (argc > nr) + hit += exec_grep(argc, argv); + return 0; +} + +static int grep_cache(struct grep_opt *opt, const char **paths, int cached) +{ + int hit = 0; + int nr; + read_cache(); + +#ifdef __unix__ + /* + * Use the external "grep" command for the case where + * we grep through the checked-out files. It tends to + * be a lot more optimized + */ + if (!cached) { + hit = external_grep(opt, paths, cached); + if (hit >= 0) + return hit; + } +#endif + + for (nr = 0; nr < active_nr; nr++) { + struct cache_entry *ce = active_cache[nr]; + if (ce_stage(ce) || !S_ISREG(ntohl(ce->ce_mode))) + continue; + if (!pathspec_matches(paths, ce->name)) + continue; + if (cached) + hit |= grep_sha1(opt, ce->sha1, ce->name); + else + hit |= grep_file(opt, ce->name); + } + return hit; +} + +static int grep_tree(struct grep_opt *opt, const char **paths, + struct tree_desc *tree, + const char *tree_name, const char *base) +{ + unsigned mode; + int len; + int hit = 0; + const char *path; + const unsigned char *sha1; + char *down; + char *path_buf = xmalloc(PATH_MAX + strlen(tree_name) + 100); + + if (tree_name[0]) { + int offset = sprintf(path_buf, "%s:", tree_name); + down = path_buf + offset; + strcat(down, base); + } + else { + down = path_buf; + strcpy(down, base); + } + len = strlen(path_buf); + + while (tree->size) { + int pathlen; + sha1 = tree_entry_extract(tree, &path, &mode); + pathlen = strlen(path); + strcpy(path_buf + len, path); + + if (S_ISDIR(mode)) + /* Match "abc/" against pathspec to + * decide if we want to descend into "abc" + * directory. + */ + strcpy(path_buf + len + pathlen, "/"); + + if (!pathspec_matches(paths, down)) + ; + else if (S_ISREG(mode)) + hit |= grep_sha1(opt, sha1, path_buf); + else if (S_ISDIR(mode)) { + char type[20]; + struct tree_desc sub; + void *data; + data = read_sha1_file(sha1, type, &sub.size); + if (!data) + die("unable to read tree (%s)", + sha1_to_hex(sha1)); + sub.buf = data; + hit |= grep_tree(opt, paths, &sub, tree_name, down); + free(data); + } + update_tree_entry(tree); + } + return hit; +} + +static int grep_object(struct grep_opt *opt, const char **paths, + struct object *obj, const char *name) +{ + if (!strcmp(obj->type, blob_type)) + return grep_sha1(opt, obj->sha1, name); + if (!strcmp(obj->type, commit_type) || + !strcmp(obj->type, tree_type)) { + struct tree_desc tree; + void *data; + int hit; + data = read_object_with_reference(obj->sha1, tree_type, + &tree.size, NULL); + if (!data) + die("unable to read tree (%s)", sha1_to_hex(obj->sha1)); + tree.buf = data; + hit = grep_tree(opt, paths, &tree, name, ""); + free(data); + return hit; + } + die("unable to grep from object of type %s", obj->type); +} + +static const char builtin_grep_usage[] = +"git-grep <option>* <rev>* [-e] <pattern> [<path>...]"; + +int cmd_grep(int argc, const char **argv, char **envp) +{ + int hit = 0; + int cached = 0; + int seen_dashdash = 0; + struct grep_opt opt; + struct object_list *list, **tail, *object_list = NULL; + const char *prefix = setup_git_directory(); + const char **paths = NULL; + int i; + + memset(&opt, 0, sizeof(opt)); + opt.pattern_tail = &opt.pattern_list; + opt.regflags = REG_NEWLINE; + + /* + * If there is no -- then the paths must exist in the working + * tree. If there is no explicit pattern specified with -e or + * -f, we take the first unrecognized non option to be the + * pattern, but then what follows it must be zero or more + * valid refs up to the -- (if exists), and then existing + * paths. If there is an explicit pattern, then the first + * unrecocnized non option is the beginning of the refs list + * that continues up to the -- (if exists), and then paths. + */ + + tail = &object_list; + while (1 < argc) { + const char *arg = argv[1]; + argc--; argv++; + if (!strcmp("--cached", arg)) { + cached = 1; + continue; + } + if (!strcmp("-a", arg) || + !strcmp("--text", arg)) { + opt.binary = GREP_BINARY_TEXT; + continue; + } + if (!strcmp("-i", arg) || + !strcmp("--ignore-case", arg)) { + opt.regflags |= REG_ICASE; + continue; + } + if (!strcmp("-I", arg)) { + opt.binary = GREP_BINARY_NOMATCH; + continue; + } + if (!strcmp("-v", arg) || + !strcmp("--invert-match", arg)) { + opt.invert = 1; + continue; + } + if (!strcmp("-E", arg) || + !strcmp("--extended-regexp", arg)) { + opt.regflags |= REG_EXTENDED; + continue; + } + if (!strcmp("-F", arg) || + !strcmp("--fixed-strings", arg)) { + opt.fixed = 1; + continue; + } + if (!strcmp("-G", arg) || + !strcmp("--basic-regexp", arg)) { + opt.regflags &= ~REG_EXTENDED; + continue; + } + if (!strcmp("-n", arg)) { + opt.linenum = 1; + continue; + } + if (!strcmp("-H", arg)) { + /* We always show the pathname, so this + * is a noop. + */ + continue; + } + if (!strcmp("-l", arg) || + !strcmp("--files-with-matches", arg)) { + opt.name_only = 1; + continue; + } + if (!strcmp("-L", arg) || + !strcmp("--files-without-match", arg)) { + opt.unmatch_name_only = 1; + continue; + } + if (!strcmp("-c", arg) || + !strcmp("--count", arg)) { + opt.count = 1; + continue; + } + if (!strcmp("-w", arg) || + !strcmp("--word-regexp", arg)) { + opt.word_regexp = 1; + continue; + } + if (!strncmp("-A", arg, 2) || + !strncmp("-B", arg, 2) || + !strncmp("-C", arg, 2) || + (arg[0] == '-' && '1' <= arg[1] && arg[1] <= '9')) { + unsigned num; + const char *scan; + switch (arg[1]) { + case 'A': case 'B': case 'C': + if (!arg[2]) { + if (argc <= 1) + usage(builtin_grep_usage); + scan = *++argv; + argc--; + } + else + scan = arg + 2; + break; + default: + scan = arg + 1; + break; + } + if (sscanf(scan, "%u", &num) != 1) + usage(builtin_grep_usage); + switch (arg[1]) { + case 'A': + opt.post_context = num; + break; + default: + case 'C': + opt.post_context = num; + case 'B': + opt.pre_context = num; + break; + } + continue; + } + if (!strcmp("-f", arg)) { + FILE *patterns; + int lno = 0; + char buf[1024]; + if (argc <= 1) + usage(builtin_grep_usage); + patterns = fopen(argv[1], "r"); + if (!patterns) + die("'%s': %s", argv[1], strerror(errno)); + while (fgets(buf, sizeof(buf), patterns)) { + int len = strlen(buf); + if (buf[len-1] == '\n') + buf[len-1] = 0; + /* ignore empty line like grep does */ + if (!buf[0]) + continue; + add_pattern(&opt, strdup(buf), argv[1], ++lno); + } + fclose(patterns); + argv++; + argc--; + continue; + } + if (!strcmp("-e", arg)) { + if (1 < argc) { + add_pattern(&opt, argv[1], "-e option", 0); + argv++; + argc--; + continue; + } + usage(builtin_grep_usage); + } + if (!strcmp("--", arg)) + break; + if (*arg == '-') + usage(builtin_grep_usage); + + /* First unrecognized non-option token */ + if (!opt.pattern_list) { + add_pattern(&opt, arg, "command line", 0); + break; + } + else { + /* We are looking at the first path or rev; + * it is found at argv[1] after leaving the + * loop. + */ + argc++; argv--; + break; + } + } + + if (!opt.pattern_list) + die("no pattern given."); + if ((opt.regflags != REG_NEWLINE) && opt.fixed) + die("cannot mix --fixed-strings and regexp"); + if (!opt.fixed) + compile_patterns(&opt); + + /* Check revs and then paths */ + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + unsigned char sha1[20]; + /* Is it a rev? */ + if (!get_sha1(arg, sha1)) { + struct object *object = parse_object(sha1); + struct object_list *elem; + if (!object) + die("bad object %s", arg); + elem = object_list_insert(object, tail); + elem->name = arg; + tail = &elem->next; + continue; + } + if (!strcmp(arg, "--")) { + i++; + seen_dashdash = 1; + } + break; + } + + /* The rest are paths */ + if (!seen_dashdash) { + int j; + for (j = i; j < argc; j++) + verify_filename(prefix, argv[j]); + } + + if (i < argc) + paths = get_pathspec(prefix, argv + i); + else if (prefix) { + paths = xcalloc(2, sizeof(const char *)); + paths[0] = prefix; + paths[1] = NULL; + } + + if (!object_list) + return !grep_cache(&opt, paths, cached); + + if (cached) + die("both --cached and trees are given."); + + for (list = object_list; list; list = list->next) { + struct object *real_obj; + real_obj = deref_tag(list->item, NULL, 0); + if (grep_object(&opt, paths, real_obj, list->name)) + hit = 1; + } + return !hit; +} 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,8 +20,10 @@ extern int cmd_whatchanged(int argc, const char **argv, char **envp); extern int cmd_show(int argc, const char **argv, char **envp); extern int cmd_log(int argc, const char **argv, char **envp); extern int cmd_diff(int argc, const char **argv, char **envp); +extern int cmd_format_patch(int argc, const char **argv, char **envp); extern int cmd_count_objects(int argc, const char **argv, char **envp); extern int cmd_push(int argc, const char **argv, char **envp); +extern int cmd_grep(int argc, const char **argv, char **envp); #endif diff --git a/cache-tree.c b/cache-tree.c new file mode 100644 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; diff --git a/combine-diff.c b/combine-diff.c index 8a8fe3863a..64b20cce24 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -608,6 +608,7 @@ static int show_patch_diff(struct combine_diff_path *elem, int num_parent, int abbrev = opt->full_index ? 40 : DEFAULT_ABBREV; mmfile_t result_file; + context = opt->context; /* Read the result of merge first */ if (!working_tree_file) result = grab_blob(elem->sha1, &result_size); @@ -36,6 +36,8 @@ enum cmit_fmt get_commit_format(const char *arg) return CMIT_FMT_FULL; if (!strcmp(arg, "=fuller")) return CMIT_FMT_FULLER; + if (!strcmp(arg, "=email")) + return CMIT_FMT_EMAIL; if (!strcmp(arg, "=oneline")) return CMIT_FMT_ONELINE; die("invalid --pretty format"); @@ -428,6 +430,10 @@ static int add_user_info(const char *what, enum cmit_fmt fmt, char *buf, const c time = strtoul(date, &date, 10); tz = strtol(date, NULL, 10); + if (fmt == CMIT_FMT_EMAIL) { + what = "From"; + filler = ""; + } ret = sprintf(buf, "%s: %.*s%.*s\n", what, (fmt == CMIT_FMT_FULLER) ? 4 : 0, filler, namelen, line); @@ -435,6 +441,10 @@ static int add_user_info(const char *what, enum cmit_fmt fmt, char *buf, const c case CMIT_FMT_MEDIUM: ret += sprintf(buf + ret, "Date: %s\n", show_date(time, tz)); break; + case CMIT_FMT_EMAIL: + ret += sprintf(buf + ret, "Date: %s\n", + show_rfc2822_date(time, tz)); + break; case CMIT_FMT_FULLER: ret += sprintf(buf + ret, "%sDate: %s\n", what, show_date(time, tz)); break; @@ -445,10 +455,12 @@ static int add_user_info(const char *what, enum cmit_fmt fmt, char *buf, const c return ret; } -static int is_empty_line(const char *line, int len) +static int is_empty_line(const char *line, int *len_p) { + int len = *len_p; while (len && isspace(line[len-1])) len--; + *len_p = len; return !len; } @@ -457,7 +469,8 @@ static int add_merge_info(enum cmit_fmt fmt, char *buf, const struct commit *com struct commit_list *parent = commit->parents; int offset; - if ((fmt == CMIT_FMT_ONELINE) || !parent || !parent->next) + if ((fmt == CMIT_FMT_ONELINE) || (fmt == CMIT_FMT_EMAIL) || + !parent || !parent->next) return 0; offset = sprintf(buf, "Merge:"); @@ -476,14 +489,17 @@ static int add_merge_info(enum cmit_fmt fmt, char *buf, const struct commit *com return offset; } -unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit, unsigned long len, char *buf, unsigned long space, int abbrev) +unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit, unsigned long len, char *buf, unsigned long space, int abbrev, const char *subject) { int hdr = 1, body = 0; unsigned long offset = 0; - int indent = (fmt == CMIT_FMT_ONELINE) ? 0 : 4; + int indent = 4; int parents_shown = 0; const char *msg = commit->buffer; + if (fmt == CMIT_FMT_ONELINE || fmt == CMIT_FMT_EMAIL) + indent = 0; + for (;;) { const char *line = msg; int linelen = get_one_line(msg, len); @@ -506,7 +522,7 @@ unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit if (hdr) { if (linelen == 1) { hdr = 0; - if (fmt != CMIT_FMT_ONELINE) + if ((fmt != CMIT_FMT_ONELINE) && !subject) buf[offset++] = '\n'; continue; } @@ -544,20 +560,29 @@ unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit continue; } - if (is_empty_line(line, linelen)) { + if (is_empty_line(line, &linelen)) { if (!body) continue; + if (subject) + continue; if (fmt == CMIT_FMT_SHORT) break; } else { body = 1; } + if (subject) { + int slen = strlen(subject); + memcpy(buf + offset, subject, slen); + offset += slen; + } memset(buf + offset, ' ', indent); memcpy(buf + offset + indent, line, linelen); offset += linelen + indent; + buf[offset++] = '\n'; if (fmt == CMIT_FMT_ONELINE) break; + subject = NULL; } while (offset && isspace(buf[offset-1])) offset--; @@ -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. @@ -134,6 +134,41 @@ static int get_value(config_fn_t fn, char *name, unsigned int len) return fn(name, value); } +static int get_extended_base_var(char *name, int baselen, int c) +{ + do { + if (c == '\n') + return -1; + c = get_next_char(); + } while (isspace(c)); + + /* We require the format to be '[base "extension"]' */ + if (c != '"') + return -1; + name[baselen++] = '.'; + + for (;;) { + int c = get_next_char(); + if (c == '\n') + return -1; + if (c == '"') + break; + if (c == '\\') { + c = get_next_char(); + if (c == '\n') + return -1; + } + name[baselen++] = c; + if (baselen > MAXNAME / 2) + return -1; + } + + /* Final ']' */ + if (get_next_char() != ']') + return -1; + return baselen; +} + static int get_base_var(char *name) { int baselen = 0; @@ -144,6 +179,8 @@ static int get_base_var(char *name) return -1; if (c == ']') return baselen; + if (isspace(c)) + return get_extended_base_var(name, baselen, c); if (!isalnum(c) && c != '.') return -1; if (baselen > MAXNAME / 2) @@ -335,10 +372,12 @@ static int store_aux(const char* key, const char* value) store.offset[store.seen] = ftell(config_file); store.state = KEY_SEEN; store.seen++; - } else if (strrchr(key, '.') - key == store.baselen && + } else { + if (strrchr(key, '.') - key == store.baselen && !strncmp(key, store.key, store.baselen)) { store.state = SECTION_SEEN; store.offset[store.seen] = ftell(config_file); + } } } return 0; @@ -346,8 +385,30 @@ static int store_aux(const char* key, const char* value) static void store_write_section(int fd, const char* key) { + const char *dot = strchr(key, '.'); + int len1 = store.baselen, len2 = -1; + + dot = strchr(key, '.'); + if (dot) { + int dotlen = dot - key; + if (dotlen < len1) { + len2 = len1 - dotlen - 1; + len1 = dotlen; + } + } + write(fd, "[", 1); - write(fd, key, store.baselen); + write(fd, key, len1); + if (len2 >= 0) { + write(fd, " \"", 2); + while (--len2 >= 0) { + unsigned char c = *++dot; + if (c == '"') + write(fd, "\\", 1); + write(fd, &c, 1); + } + write(fd, "\"", 1); + } write(fd, "]\n", 2); } @@ -421,7 +482,7 @@ int git_config_set(const char* key, const char* value) int git_config_set_multivar(const char* key, const char* value, const char* value_regex, int multi_replace) { - int i; + int i, dot; int fd = -1, in_fd; int ret; char* config_filename = strdup(git_path("config")); @@ -446,16 +507,23 @@ int git_config_set_multivar(const char* key, const char* value, * Validate the key and while at it, lower case it for matching. */ store.key = (char*)malloc(strlen(key)+1); - for (i = 0; key[i]; i++) - if (i != store.baselen && - ((!isalnum(key[i]) && key[i] != '.') || - (i == store.baselen+1 && !isalpha(key[i])))) { - fprintf(stderr, "invalid key: %s\n", key); - free(store.key); - ret = 1; - goto out_free; - } else - store.key[i] = tolower(key[i]); + dot = 0; + for (i = 0; key[i]; i++) { + unsigned char c = key[i]; + if (c == '.') + dot = 1; + /* Leave the extended basename untouched.. */ + if (!dot || i > store.baselen) { + if (!isalnum(c) || (i == store.baselen+1 && !isalpha(c))) { + fprintf(stderr, "invalid key: %s\n", key); + free(store.key); + ret = 1; + goto out_free; + } + c = tolower(c); + } + store.key[i] = c; + } store.key[i] = 0; /* diff --git a/contrib/remotes2config.sh b/contrib/remotes2config.sh new file mode 100644 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. * @@ -232,11 +232,16 @@ static char *pprint_rename(const char *a, const char *b) * name-a => name-b */ if (pfx_length + sfx_length) { + int a_midlen = len_a - pfx_length - sfx_length; + int b_midlen = len_b - pfx_length - sfx_length; + if (a_midlen < 0) a_midlen = 0; + if (b_midlen < 0) b_midlen = 0; + name = xmalloc(len_a + len_b - pfx_length - sfx_length + 7); sprintf(name, "%.*s{%.*s => %.*s}%s", pfx_length, a, - len_a - pfx_length - sfx_length, a + pfx_length, - len_b - pfx_length - sfx_length, b + pfx_length, + a_midlen, a + pfx_length, + b_midlen, b + pfx_length, a + len_a - sfx_length); } else { @@ -558,7 +563,7 @@ static void builtin_diff(const char *name_a, ecbdata.label_path = lbl; xpp.flags = XDF_NEED_MINIMAL; - xecfg.ctxlen = 3; + xecfg.ctxlen = o->context; xecfg.flags = XDL_EMIT_FUNCNAMES; if (!diffopts) ; @@ -1182,6 +1187,7 @@ void diff_setup(struct diff_options *options) options->line_termination = '\n'; options->break_opt = -1; options->rename_limit = -1; + options->context = 3; options->change = diff_change; options->add_remove = diff_addremove; @@ -1222,17 +1228,68 @@ int diff_setup_done(struct diff_options *options) return 0; } +int opt_arg(const char *arg, int arg_short, const char *arg_long, int *val) +{ + char c, *eq; + int len; + + if (*arg != '-') + return 0; + c = *++arg; + if (!c) + return 0; + if (c == arg_short) { + c = *++arg; + if (!c) + return 1; + if (val && isdigit(c)) { + char *end; + int n = strtoul(arg, &end, 10); + if (*end) + return 0; + *val = n; + return 1; + } + return 0; + } + if (c != '-') + return 0; + arg++; + eq = strchr(arg, '='); + if (eq) + len = eq - arg; + else + len = strlen(arg); + if (!len || strncmp(arg, arg_long, len)) + return 0; + if (eq) { + int n; + char *end; + if (!isdigit(*++eq)) + return 0; + n = strtoul(eq, &end, 10); + if (*end) + return 0; + *val = n; + } + return 1; +} + int diff_opt_parse(struct diff_options *options, const char **av, int ac) { const char *arg = av[0]; if (!strcmp(arg, "-p") || !strcmp(arg, "-u")) options->output_format = DIFF_FORMAT_PATCH; + else if (opt_arg(arg, 'U', "unified", &options->context)) + options->output_format = DIFF_FORMAT_PATCH; else if (!strcmp(arg, "--patch-with-raw")) { options->output_format = DIFF_FORMAT_PATCH; options->with_raw = 1; } else if (!strcmp(arg, "--stat")) options->output_format = DIFF_FORMAT_DIFFSTAT; + else if (!strcmp(arg, "--summary")) + options->summary = 1; else if (!strcmp(arg, "--patch-with-stat")) { options->output_format = DIFF_FORMAT_PATCH; options->with_stat = 1; @@ -1703,6 +1760,85 @@ static void flush_one_pair(struct diff_filepair *p, } } +static void show_file_mode_name(const char *newdelete, struct diff_filespec *fs) +{ + if (fs->mode) + printf(" %s mode %06o %s\n", newdelete, fs->mode, fs->path); + else + printf(" %s %s\n", newdelete, fs->path); +} + + +static void show_mode_change(struct diff_filepair *p, int show_name) +{ + if (p->one->mode && p->two->mode && p->one->mode != p->two->mode) { + if (show_name) + printf(" mode change %06o => %06o %s\n", + p->one->mode, p->two->mode, p->two->path); + else + printf(" mode change %06o => %06o\n", + p->one->mode, p->two->mode); + } +} + +static void show_rename_copy(const char *renamecopy, struct diff_filepair *p) +{ + const char *old, *new; + + /* Find common prefix */ + old = p->one->path; + new = p->two->path; + while (1) { + const char *slash_old, *slash_new; + slash_old = strchr(old, '/'); + slash_new = strchr(new, '/'); + if (!slash_old || + !slash_new || + slash_old - old != slash_new - new || + memcmp(old, new, slash_new - new)) + break; + old = slash_old + 1; + new = slash_new + 1; + } + /* p->one->path thru old is the common prefix, and old and new + * through the end of names are renames + */ + if (old != p->one->path) + printf(" %s %.*s{%s => %s} (%d%%)\n", renamecopy, + (int)(old - p->one->path), p->one->path, + old, new, (int)(0.5 + p->score * 100.0/MAX_SCORE)); + else + printf(" %s %s => %s (%d%%)\n", renamecopy, + p->one->path, p->two->path, + (int)(0.5 + p->score * 100.0/MAX_SCORE)); + show_mode_change(p, 0); +} + +static void diff_summary(struct diff_filepair *p) +{ + switch(p->status) { + case DIFF_STATUS_DELETED: + show_file_mode_name("delete", p->one); + break; + case DIFF_STATUS_ADDED: + show_file_mode_name("create", p->two); + break; + case DIFF_STATUS_COPIED: + show_rename_copy("copy", p); + break; + case DIFF_STATUS_RENAMED: + show_rename_copy("rename", p); + break; + default: + if (p->score) { + printf(" rewrite %s (%d%%)\n", p->two->path, + (int)(0.5 + p->score * 100.0/MAX_SCORE)); + show_mode_change(p, 0); + } else show_mode_change(p, 1); + break; + } +} + void diff_flush(struct diff_options *options) { struct diff_queue_struct *q = &diff_queued_diff; @@ -1736,7 +1872,6 @@ void diff_flush(struct diff_options *options) for (i = 0; i < q->nr; i++) { struct diff_filepair *p = q->queue[i]; flush_one_pair(p, diff_output_format, options, diffstat); - diff_free_filepair(p); } if (diffstat) { @@ -1744,6 +1879,12 @@ void diff_flush(struct diff_options *options) free(diffstat); } + for (i = 0; i < q->nr; i++) { + if (options->summary) + diff_summary(q->queue[i]); + diff_free_filepair(q->queue[i]); + } + free(q->queue); q->queue = NULL; q->nr = q->alloc = 0; @@ -31,7 +31,9 @@ struct diff_options { binary:1, full_index:1, silent_on_remove:1, - find_copies_harder:1; + find_copies_harder:1, + summary:1; + int context; int break_opt; int detect_rename; int line_termination; diff --git a/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(); @@ -15,6 +15,10 @@ stop_here () { } stop_here_user_resolve () { + if [ -n "$resolvemsg" ]; then + echo "$resolvemsg" + stop_here $1 + fi cmdline=$(basename $0) if test '' != "$interactive" then @@ -121,7 +125,7 @@ fall_back_3way () { } prec=4 -dotest=.dotest sign= utf8= keep= skip= interactive= resolved= binary= ws= +dotest=.dotest sign= utf8= keep= skip= interactive= resolved= binary= ws= resolvemsg= while case "$#" in 0) break;; esac do @@ -157,6 +161,9 @@ do --whitespace=*) ws=$1; shift ;; + --resolvemsg=*) + resolvemsg=$(echo "$1" | sed -e "s/^--resolvemsg=//"); shift ;; + --) shift; break ;; -*) @@ -185,7 +192,7 @@ then else # Make sure we are not given --skip nor --resolved test ",$skip,$resolved," = ,,, || - die "we are not resuming." + die "Resolve operation not in progress, we are not resuming." # Start afresh. mkdir -p "$dotest" || exit diff --git a/git-branch.sh b/git-branch.sh index ebcc8989d8..134e68cf7f 100755 --- a/git-branch.sh +++ b/git-branch.sh @@ -82,8 +82,7 @@ done case "$#" in 0) - git-rev-parse --symbolic --all | - sed -ne 's|^refs/heads/||p' | + git-rev-parse --symbolic --branches | sort | while read ref do diff --git a/git-commit.sh b/git-commit.sh index 26cd7ca54d..6ef1a9dedc 100755 --- a/git-commit.sh +++ b/git-commit.sh @@ -640,6 +640,8 @@ case "$no_edit" in exit 1 ;; esac + git-var GIT_AUTHOR_IDENT > /dev/null || die + git-var GIT_COMMITTER_IDENT > /dev/null || die ${VISUAL:-${EDITOR:-vi}} "$GIT_DIR/COMMIT_EDITMSG" ;; esac diff --git a/git-format-patch.sh b/git-format-patch.sh index c077f44ca1..8a16eadfbd 100755 --- a/git-format-patch.sh +++ b/git-format-patch.sh @@ -274,7 +274,7 @@ print "\n---\n\n"; close FH or die "close $commsg pipe"; ' "$keep_subject" "$num" "$signoff" "$headers" "$mimemagic" $commsg - git-diff-tree -p $diff_opts "$commit" | git-apply --stat --summary + git-diff-tree -p --stat --summary $diff_opts "$commit" echo case "$mimemagic" in '');; diff --git a/git-merge.sh b/git-merge.sh index b834e79c98..af1f25b3c5 100755 --- a/git-merge.sh +++ b/git-merge.sh @@ -55,8 +55,7 @@ finish () { case "$no_summary" in '') - git-diff-tree -p -M "$head" "$1" | - git-apply --stat --summary + git-diff-tree -p --stat --summary -M "$head" "$1" ;; esac } 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 diff --git a/git-rebase.sh b/git-rebase.sh index 9e259028e0..6ff6088d18 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -12,9 +12,10 @@ It then attempts to create a new commit for each commit from the original It is possible that a merge failure will prevent this process from being completely automatic. You will have to resolve any such merge failure -and run git-rebase --continue. If you can not resolve the merge failure, -running git-rebase --abort will restore the original <branch> and remove -the working files found in the .dotest directory. +and run git rebase --continue. Another option is to bypass the commit +that caused the merge failure with git rebase --skip. To restore the +original <branch> and remove the .dotest working files, use the command +git rebase --abort instead. Note that if <branch> is not specified on the command line, the currently checked out branch is used. You must be in the top @@ -28,6 +29,11 @@ Example: git-rebase master~1 topic ' . git-sh-setup +RESOLVEMSG=" +When you have resolved this problem run \"git rebase --continue\". +If you would prefer to skip this patch, instead run \"git rebase --skip\". +To restore the original branch and stop rebasing run \"git rebase --abort\". +" unset newbase while case "$#" in 0) break ;; esac do @@ -40,7 +46,11 @@ do exit 1 ;; esac - git am --resolved --3way + git am --resolved --3way --resolvemsg="$RESOLVEMSG" + exit + ;; + --skip) + git am -3 --skip --resolvemsg="$RESOLVEMSG" exit ;; --abort) @@ -143,4 +153,5 @@ then fi git-format-patch -k --stdout --full-index "$upstream" ORIG_HEAD | -git am --binary -3 -k +git am --binary -3 -k --resolvemsg="$RESOLVEMSG" + diff --git a/git-request-pull.sh b/git-request-pull.sh index 2c48bfb299..4319e35c62 100755 --- a/git-request-pull.sh +++ b/git-request-pull.sh @@ -30,4 +30,4 @@ echo " $url" echo git log $baserev..$headrev | git-shortlog ; -git diff $baserev..$headrev | git-apply --stat --summary +git diff --stat --summary $baserev..$headrev diff --git a/git-send-email.perl b/git-send-email.perl index 703dd1ff9e..d8c4b1f892 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -89,6 +89,41 @@ sub gitvar_ident { my ($author) = gitvar_ident('GIT_AUTHOR_IDENT'); my ($committer) = gitvar_ident('GIT_COMMITTER_IDENT'); +my %aliases; +chomp(my @alias_files = `git-repo-config --get-all sendemail.aliasesfile`); +chomp(my $aliasfiletype = `git-repo-config sendemail.aliasfiletype`); +my %parse_alias = ( + # multiline formats can be supported in the future + mutt => sub { my $fh = shift; while (<$fh>) { + if (/^alias\s+(\S+)\s+(.*)$/) { + my ($alias, $addr) = ($1, $2); + $addr =~ s/#.*$//; # mutt allows # comments + # commas delimit multiple addresses + $aliases{$alias} = [ split(/\s*,\s*/, $addr) ]; + }}}, + mailrc => sub { my $fh = shift; while (<$fh>) { + if (/^alias\s+(\S+)\s+(.*)$/) { + # spaces delimit multiple addresses + $aliases{$1} = [ split(/\s+/, $2) ]; + }}}, + pine => sub { my $fh = shift; while (<$fh>) { + if (/^(\S+)\s+(.*)$/) { + $aliases{$1} = [ split(/\s*,\s*/, $2) ]; + }}}, + gnus => sub { my $fh = shift; while (<$fh>) { + if (/\(define-mail-alias\s+"(\S+?)"\s+"(\S+?)"\)/) { + $aliases{$1} = [ $2 ]; + }}} +); + +if (@alias_files && defined $parse_alias{$aliasfiletype}) { + foreach my $file (@alias_files) { + open my $fh, '<', $file or die "opening $file: $!\n"; + $parse_alias{$aliasfiletype}->($fh); + close $fh; + } +} + my $prompting = 0; if (!defined $from) { $from = $author || $committer; @@ -112,6 +147,19 @@ if (!@to) { $prompting++; } +sub expand_aliases { + my @cur = @_; + my @last; + do { + @last = @cur; + @cur = map { $aliases{$_} ? @{$aliases{$_}} : $_ } @last; + } while (join(',',@cur) ne join(',',@last)); + return @cur; +} + +@to = expand_aliases(@to); +@initial_cc = expand_aliases(@initial_cc); + if (!defined $initial_subject && $compose) { do { $_ = $term->readline("What subject should the emails start with? ", diff --git a/git-tag.sh b/git-tag.sh index dc6aa95767..a0afa25821 100755 --- a/git-tag.sh +++ b/git-tag.sh @@ -25,14 +25,12 @@ do force=1 ;; -l) - cd "$GIT_DIR/refs" && case "$#" in 1) - find tags -type f -print ;; - *) - shift - find tags -type f -print | grep "$@" ;; + set x . ;; esac + shift + git rev-parse --symbolic --tags | sort | grep "$@" exit $? ;; -m) @@ -47,8 +47,10 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "whatchanged", cmd_whatchanged }, { "show", cmd_show }, { "push", cmd_push }, + { "fmt-patch", cmd_format_patch }, { "count-objects", cmd_count_objects }, { "diff", cmd_diff }, + { "grep", cmd_grep }, }; int i; diff --git a/git.spec.in b/git.spec.in index 96dfc1de55..8ccd2564e7 100644 --- a/git.spec.in +++ b/git.spec.in @@ -74,12 +74,12 @@ Git revision tree visualiser ('gitk') %setup -q %build -make %{_smp_mflags} CFLAGS="$RPM_OPT_FLAGS" WITH_OWN_SUBPROCESS_PY=YesPlease WITH_SEND_EMAIL=1 \ +make %{_smp_mflags} CFLAGS="$RPM_OPT_FLAGS" WITH_OWN_SUBPROCESS_PY=YesPlease \ prefix=%{_prefix} all %{!?_without_docs: doc} %install rm -rf $RPM_BUILD_ROOT -make %{_smp_mflags} DESTDIR=$RPM_BUILD_ROOT WITH_OWN_SUBPROCESS_PY=YesPlease WITH_SEND_EMAIL=1 \ +make %{_smp_mflags} DESTDIR=$RPM_BUILD_ROOT WITH_OWN_SUBPROCESS_PY=YesPlease \ prefix=%{_prefix} mandir=%{_mandir} \ install %{!?_without_docs: install-doc} 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/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..aa6172b52e 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; } @@ -426,6 +429,12 @@ static void verify_uptodate(struct cache_entry *ce) die("Entry '%s' not uptodate. Cannot merge.", ce->name); } +static void invalidate_ce_path(struct cache_entry *ce) +{ + if (ce) + cache_tree_invalidate_path(active_cache_tree, ce->name); +} + static int merged_entry(struct cache_entry *merge, struct cache_entry *old) { merge->ce_flags |= htons(CE_UPDATE); @@ -441,8 +450,11 @@ static int merged_entry(struct cache_entry *merge, struct cache_entry *old) *merge = *old; } else { verify_uptodate(old); + invalidate_ce_path(old); } } + else + invalidate_ce_path(merge); merge->ce_flags &= ~htons(CE_STAGEMASK); add_cache_entry(merge, ADD_CACHE_OK_TO_ADD); return 1; @@ -454,6 +466,7 @@ static int deleted_entry(struct cache_entry *ce, struct cache_entry *old) verify_uptodate(old); ce->ce_mode = 0; add_cache_entry(ce, ADD_CACHE_OK_TO_ADD); + invalidate_ce_path(ce); return 1; } @@ -674,6 +687,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 in prefix. + */ +static int bind_merge(struct cache_entry **src) +{ + struct cache_entry *old = src[0]; + struct cache_entry *a = src[1]; + + if (merge_size != 1) + return error("Cannot do a bind merge of %d trees\n", + merge_size); + if (!a) + return merged_entry(old, NULL); + if (old) + die("Entry '%s' overlaps. Cannot bind.", a->name); + + return merged_entry(a, NULL); +} + +/* * One-way merge. * * The rule is: @@ -688,8 +723,10 @@ static int oneway_merge(struct cache_entry **src) return error("Cannot do a oneway merge of %d trees", merge_size); - if (!a) + if (!a) { + invalidate_ce_path(old); return deleted_entry(old, NULL); + } if (old && same(old, a)) { if (reset) { struct stat st; @@ -714,6 +751,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 +762,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 +840,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 +878,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 +900,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 +943,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"); @@ -114,7 +114,7 @@ int read_ref(const char *filename, unsigned char *sha1) return -1; } -static int do_for_each_ref(const char *base, int (*fn)(const char *path, const unsigned char *sha1)) +static int do_for_each_ref(const char *base, int (*fn)(const char *path, const unsigned char *sha1), int trim) { int retval = 0; DIR *dir = opendir(git_path("%s", base)); @@ -146,7 +146,7 @@ static int do_for_each_ref(const char *base, int (*fn)(const char *path, const u if (stat(git_path("%s", path), &st) < 0) continue; if (S_ISDIR(st.st_mode)) { - retval = do_for_each_ref(path, fn); + retval = do_for_each_ref(path, fn, trim); if (retval) break; continue; @@ -160,7 +160,7 @@ static int do_for_each_ref(const char *base, int (*fn)(const char *path, const u "commit object!", path); continue; } - retval = fn(path, sha1); + retval = fn(path + trim, sha1); if (retval) break; } @@ -180,7 +180,22 @@ int head_ref(int (*fn)(const char *path, const unsigned char *sha1)) int for_each_ref(int (*fn)(const char *path, const unsigned char *sha1)) { - return do_for_each_ref("refs", fn); + return do_for_each_ref("refs", fn, 0); +} + +int for_each_tag_ref(int (*fn)(const char *path, const unsigned char *sha1)) +{ + return do_for_each_ref("refs/tags", fn, 10); +} + +int for_each_branch_ref(int (*fn)(const char *path, const unsigned char *sha1)) +{ + return do_for_each_ref("refs/heads", fn, 11); +} + +int for_each_remote_ref(int (*fn)(const char *path, const unsigned char *sha1)) +{ + return do_for_each_ref("refs/remotes", fn, 13); } static char *ref_file_name(const char *ref) @@ -7,6 +7,9 @@ */ extern int head_ref(int (*fn)(const char *path, const unsigned char *sha1)); extern int for_each_ref(int (*fn)(const char *path, const unsigned char *sha1)); +extern int for_each_tag_ref(int (*fn)(const char *path, const unsigned char *sha1)); +extern int for_each_branch_ref(int (*fn)(const char *path, const unsigned char *sha1)); +extern int for_each_remote_ref(int (*fn)(const char *path, const unsigned char *sha1)); /** Reads the refs file specified into sha1 **/ extern int get_ref_sha1(const char *ref, unsigned char *sha1); diff --git a/repo-config.c b/repo-config.c index 63eda1bb78..127afd784c 100644 --- a/repo-config.c +++ b/repo-config.c @@ -64,12 +64,13 @@ static int show_config(const char* key_, const char* value_) static int get_value(const char* key_, const char* regex_) { - int i; + char *tl; - key = malloc(strlen(key_)+1); - for (i = 0; key_[i]; i++) - key[i] = tolower(key_[i]); - key[i] = 0; + key = strdup(key_); + for (tl=key+strlen(key)-1; tl >= key && *tl != '.'; --tl) + *tl = tolower(*tl); + for (tl=key; *tl && *tl != '.'; ++tl) + *tl = tolower(*tl); if (use_key_regexp) { key_regexp = (regex_t*)malloc(sizeof(regex_t)); 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/rev-parse.c b/rev-parse.c index 62e16af33c..4e2d9fbdf6 100644 --- a/rev-parse.c +++ b/rev-parse.c @@ -36,6 +36,7 @@ static int is_rev_argument(const char *arg) "--all", "--bisect", "--dense", + "--branches", "--header", "--max-age=", "--max-count=", @@ -45,7 +46,9 @@ static int is_rev_argument(const char *arg) "--objects-edge", "--parents", "--pretty", + "--remotes", "--sparse", + "--tags", "--topo-order", "--date-order", "--unpacked", @@ -165,7 +168,7 @@ int main(int argc, char **argv) int i, as_is = 0, verify = 0; unsigned char sha1[20]; const char *prefix = setup_git_directory(); - + git_config(git_default_config); for (i = 1; i < argc; i++) { @@ -255,6 +258,18 @@ int main(int argc, char **argv) for_each_ref(show_reference); continue; } + if (!strcmp(arg, "--branches")) { + for_each_branch_ref(show_reference); + continue; + } + if (!strcmp(arg, "--tags")) { + for_each_tag_ref(show_reference); + continue; + } + if (!strcmp(arg, "--remotes")) { + for_each_remote_ref(show_reference); + continue; + } if (!strcmp(arg, "--show-prefix")) { if (prefix) puts(prefix); 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/t1300-repo-config.sh b/t/t1300-repo-config.sh index 7090ea92c1..8260d57b63 100755 --- a/t/t1300-repo-config.sh +++ b/t/t1300-repo-config.sh @@ -229,7 +229,7 @@ test_expect_failure 'invalid key' 'git-repo-config inval.2key blabla' test_expect_success 'correct key' 'git-repo-config 123456.a123 987' test_expect_success 'hierarchical section' \ - 'git-repo-config 1.2.3.alpha beta' + 'git-repo-config Version.1.2.3eX.Alpha beta' cat > expect << EOF [beta] ; silly comment # another comment @@ -241,8 +241,8 @@ noIndent= sillyValue ; 'nother silly comment NoNewLine = wow2 for me [123456] a123 = 987 -[1.2.3] - alpha = beta +[Version "1.2.3eX"] + Alpha = beta EOF test_expect_success 'hierarchical section value' 'cmp .git/config expect' @@ -251,7 +251,7 @@ cat > expect << EOF beta.noindent=sillyValue nextsection.nonewline=wow2 for me 123456.a123=987 -1.2.3.alpha=beta +version.1.2.3eX.alpha=beta EOF test_expect_success 'working --list' \ 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; } |