From: Junio C Hamano Date: Tue, 23 May 2006 21:52:45 +0000 (-0700) Subject: Merge branch 'jc/builtin-n-tar-tree' into next X-Git-Url: https://git.verplant.org/?a=commitdiff_plain;h=e96b6c4bf6dab94fd3833c31adc5105589a59e7a;p=git.git Merge branch 'jc/builtin-n-tar-tree' into next * jc/builtin-n-tar-tree: Builtin git-diff-files, git-diff-index, git-diff-stages, and git-diff-tree. Builtin git-show-branch. Builtin git-apply. Builtin git-commit-tree. Builtin git-read-tree. Builtin git-tar-tree. Builtin git-ls-tree. Builtin git-ls-files. --- e96b6c4bf6dab94fd3833c31adc5105589a59e7a diff --cc Makefile index 677bc133,14873fa3..355e0cce --- a/Makefile +++ b/Makefile @@@ -170,9 -168,12 +168,13 @@@ PROGRAMS = BUILT_INS = git-log$X git-whatchanged$X git-show$X \ git-count-objects$X git-diff$X git-push$X \ - git-grep$X git-rev-list$X git-check-ref-format$X \ - git-init-db$X git-ls-files$X git-ls-tree$X \ + git-grep$X git-add$X git-rm$X git-rev-list$X \ + git-check-ref-format$X \ - git-init-db$X git-tar-tree$X git-upload-tar$X git-format-patch$X ++ git-init-db$X git-tar-tree$X git-upload-tar$X git-format-patch$X \ ++ git-ls-files$X git-ls-tree$X \ + git-read-tree$X git-commit-tree$X \ + git-apply$X git-show-branch$X git-diff-files$X \ - git-diff-index$X git-diff-stages$X git-diff-tree$X \ - git-tar-tree$X git-upload-tar$X ++ git-diff-index$X git-diff-stages$X git-diff-tree$X # what 'all' will build and 'install' will install, in gitexecdir ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS) @@@ -220,8 -221,12 +222,13 @@@ LIB_OBJS = BUILTIN_OBJS = \ builtin-log.o builtin-help.o builtin-count.o builtin-diff.o builtin-push.o \ - builtin-grep.o builtin-rev-list.o builtin-check-ref-format.o \ - builtin-init-db.o builtin-ls-files.o builtin-ls-tree.o \ + builtin-grep.o builtin-add.o builtin-rev-list.o builtin-check-ref-format.o \ - builtin-rm.o builtin-init-db.o builtin-tar-tree.o builtin-upload-tar.o ++ builtin-rm.o builtin-init-db.o \ ++ builtin-tar-tree.o builtin-upload-tar.o \ ++ builtin-ls-files.o builtin-ls-tree.o \ + builtin-read-tree.o builtin-commit-tree.o \ + builtin-apply.o builtin-show-branch.o builtin-diff-files.o \ - builtin-diff-index.o builtin-diff-stages.o builtin-diff-tree.o \ - builtin-tar-tree.o builtin-upload-tar.o ++ builtin-diff-index.o builtin-diff-stages.o builtin-diff-tree.o GITLIBS = $(LIB_FILE) $(XDIFF_LIB) LIBS = $(GITLIBS) -lz diff --cc builtin-apply.c index 00000000,4056b9d6..f16c753b mode 000000,100644..100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@@ -1,0 -1,2300 +1,2303 @@@ + /* + * apply.c + * + * Copyright (C) Linus Torvalds, 2005 + * + * This applies patches on top of some (arbitrary) version of the SCM. + * + */ + #include + #include "cache.h" ++#include "cache-tree.h" + #include "quote.h" + #include "blob.h" + #include "delta.h" + #include "builtin.h" + + // --check turns on checking that the working tree matches the + // files that are being modified, but doesn't apply the patch + // --stat does just a diffstat, and doesn't actually apply + // --numstat does numeric diffstat, and doesn't actually apply + // --index-info shows the old and new index info for paths if available. + // --index updates the cache as well. + // --cached updates only the cache without ever touching the working tree. + // + static const char *prefix; + static int prefix_length = -1; + static int newfd = -1; + + static int p_value = 1; + static int allow_binary_replacement = 0; + static int check_index = 0; + static int write_index = 0; + static int cached = 0; + static int diffstat = 0; + static int numstat = 0; + static int summary = 0; + static int check = 0; + static int apply = 1; + static int no_add = 0; + static int show_index_info = 0; + static int line_termination = '\n'; + static unsigned long p_context = -1; + static const char apply_usage[] = + "git-apply [--stat] [--numstat] [--summary] [--check] [--index] [--cached] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [-z] [-pNUM] [-CNUM] [--whitespace=] ..."; + + static enum whitespace_eol { + nowarn_whitespace, + warn_on_whitespace, + error_on_whitespace, + strip_whitespace, + } new_whitespace = warn_on_whitespace; + static int whitespace_error = 0; + static int squelch_whitespace_errors = 5; + static int applied_after_stripping = 0; + static const char *patch_input_file = NULL; + + static void parse_whitespace_option(const char *option) + { + if (!option) { + new_whitespace = warn_on_whitespace; + return; + } + if (!strcmp(option, "warn")) { + new_whitespace = warn_on_whitespace; + return; + } + if (!strcmp(option, "nowarn")) { + new_whitespace = nowarn_whitespace; + return; + } + if (!strcmp(option, "error")) { + new_whitespace = error_on_whitespace; + return; + } + if (!strcmp(option, "error-all")) { + new_whitespace = error_on_whitespace; + squelch_whitespace_errors = 0; + return; + } + if (!strcmp(option, "strip")) { + new_whitespace = strip_whitespace; + return; + } + die("unrecognized whitespace option '%s'", option); + } + + static void set_default_whitespace_mode(const char *whitespace_option) + { + if (!whitespace_option && !apply_default_whitespace) { + new_whitespace = (apply + ? warn_on_whitespace + : nowarn_whitespace); + } + } + + /* + * For "diff-stat" like behaviour, we keep track of the biggest change + * we've seen, and the longest filename. That allows us to do simple + * scaling. + */ + static int max_change, max_len; + + /* + * Various "current state", notably line numbers and what + * file (and how) we're patching right now.. The "is_xxxx" + * things are flags, where -1 means "don't know yet". + */ + static int linenr = 1; + + struct fragment { + unsigned long leading, trailing; + unsigned long oldpos, oldlines; + unsigned long newpos, newlines; + const char *patch; + int size; + struct fragment *next; + }; + + struct patch { + char *new_name, *old_name, *def_name; + unsigned int old_mode, new_mode; + int is_rename, is_copy, is_new, is_delete, is_binary; + #define BINARY_DELTA_DEFLATED 1 + #define BINARY_LITERAL_DEFLATED 2 + unsigned long deflate_origlen; + int lines_added, lines_deleted; + int score; + struct fragment *fragments; + char *result; + unsigned long resultsize; + char old_sha1_prefix[41]; + char new_sha1_prefix[41]; + struct patch *next; + }; + + #define CHUNKSIZE (8192) + #define SLOP (16) + + static void *read_patch_file(int fd, unsigned long *sizep) + { + unsigned long size = 0, alloc = CHUNKSIZE; + void *buffer = xmalloc(alloc); + + for (;;) { + int nr = alloc - size; + if (nr < 1024) { + alloc += CHUNKSIZE; + buffer = xrealloc(buffer, alloc); + nr = alloc - size; + } + nr = xread(fd, buffer + size, nr); + if (!nr) + break; + if (nr < 0) + die("git-apply: read returned %s", strerror(errno)); + size += nr; + } + *sizep = size; + + /* + * Make sure that we have some slop in the buffer + * so that we can do speculative "memcmp" etc, and + * see to it that it is NUL-filled. + */ + if (alloc < size + SLOP) + buffer = xrealloc(buffer, size + SLOP); + memset(buffer + size, 0, SLOP); + return buffer; + } + + static unsigned long linelen(const char *buffer, unsigned long size) + { + unsigned long len = 0; + while (size--) { + len++; + if (*buffer++ == '\n') + break; + } + return len; + } + + static int is_dev_null(const char *str) + { + return !memcmp("/dev/null", str, 9) && isspace(str[9]); + } + + #define TERM_SPACE 1 + #define TERM_TAB 2 + + static int name_terminate(const char *name, int namelen, int c, int terminate) + { + if (c == ' ' && !(terminate & TERM_SPACE)) + return 0; + if (c == '\t' && !(terminate & TERM_TAB)) + return 0; + + return 1; + } + + static char * find_name(const char *line, char *def, int p_value, int terminate) + { + int len; + const char *start = line; + char *name; + + if (*line == '"') { + /* Proposed "new-style" GNU patch/diff format; see + * http://marc.theaimsgroup.com/?l=git&m=112927316408690&w=2 + */ + name = unquote_c_style(line, NULL); + if (name) { + char *cp = name; + while (p_value) { + cp = strchr(name, '/'); + if (!cp) + break; + cp++; + p_value--; + } + if (cp) { + /* name can later be freed, so we need + * to memmove, not just return cp + */ + memmove(name, cp, strlen(cp) + 1); + free(def); + return name; + } + else { + free(name); + name = NULL; + } + } + } + + for (;;) { + char c = *line; + + if (isspace(c)) { + if (c == '\n') + break; + if (name_terminate(start, line-start, c, terminate)) + break; + } + line++; + if (c == '/' && !--p_value) + start = line; + } + if (!start) + return def; + len = line - start; + if (!len) + return def; + + /* + * Generally we prefer the shorter name, especially + * if the other one is just a variation of that with + * something else tacked on to the end (ie "file.orig" + * or "file~"). + */ + if (def) { + int deflen = strlen(def); + if (deflen < len && !strncmp(start, def, deflen)) + return def; + } + + name = xmalloc(len + 1); + memcpy(name, start, len); + name[len] = 0; + free(def); + return name; + } + + /* + * Get the name etc info from the --/+++ lines of a traditional patch header + * + * NOTE! This hardcodes "-p1" behaviour in filename detection. + * + * FIXME! The end-of-filename heuristics are kind of screwy. For existing + * files, we can happily check the index for a match, but for creating a + * new file we should try to match whatever "patch" does. I have no idea. + */ + static void parse_traditional_patch(const char *first, const char *second, struct patch *patch) + { + char *name; + + first += 4; // skip "--- " + second += 4; // skip "+++ " + if (is_dev_null(first)) { + patch->is_new = 1; + patch->is_delete = 0; + name = find_name(second, NULL, p_value, TERM_SPACE | TERM_TAB); + patch->new_name = name; + } else if (is_dev_null(second)) { + patch->is_new = 0; + patch->is_delete = 1; + name = find_name(first, NULL, p_value, TERM_SPACE | TERM_TAB); + patch->old_name = name; + } else { + name = find_name(first, NULL, p_value, TERM_SPACE | TERM_TAB); + name = find_name(second, name, p_value, TERM_SPACE | TERM_TAB); + patch->old_name = patch->new_name = name; + } + if (!name) + die("unable to find filename in patch at line %d", linenr); + } + + static int gitdiff_hdrend(const char *line, struct patch *patch) + { + return -1; + } + + /* + * We're anal about diff header consistency, to make + * sure that we don't end up having strange ambiguous + * patches floating around. + * + * As a result, gitdiff_{old|new}name() will check + * their names against any previous information, just + * to make sure.. + */ + static char *gitdiff_verify_name(const char *line, int isnull, char *orig_name, const char *oldnew) + { + if (!orig_name && !isnull) + return find_name(line, NULL, 1, 0); + + if (orig_name) { + int len; + const char *name; + char *another; + name = orig_name; + len = strlen(name); + if (isnull) + die("git-apply: bad git-diff - expected /dev/null, got %s on line %d", name, linenr); + another = find_name(line, NULL, 1, 0); + if (!another || memcmp(another, name, len)) + die("git-apply: bad git-diff - inconsistent %s filename on line %d", oldnew, linenr); + free(another); + return orig_name; + } + else { + /* expect "/dev/null" */ + if (memcmp("/dev/null", line, 9) || line[9] != '\n') + die("git-apply: bad git-diff - expected /dev/null on line %d", linenr); + return NULL; + } + } + + static int gitdiff_oldname(const char *line, struct patch *patch) + { + patch->old_name = gitdiff_verify_name(line, patch->is_new, patch->old_name, "old"); + return 0; + } + + static int gitdiff_newname(const char *line, struct patch *patch) + { + patch->new_name = gitdiff_verify_name(line, patch->is_delete, patch->new_name, "new"); + return 0; + } + + static int gitdiff_oldmode(const char *line, struct patch *patch) + { + patch->old_mode = strtoul(line, NULL, 8); + return 0; + } + + static int gitdiff_newmode(const char *line, struct patch *patch) + { + patch->new_mode = strtoul(line, NULL, 8); + return 0; + } + + static int gitdiff_delete(const char *line, struct patch *patch) + { + patch->is_delete = 1; + patch->old_name = patch->def_name; + return gitdiff_oldmode(line, patch); + } + + static int gitdiff_newfile(const char *line, struct patch *patch) + { + patch->is_new = 1; + patch->new_name = patch->def_name; + return gitdiff_newmode(line, patch); + } + + static int gitdiff_copysrc(const char *line, struct patch *patch) + { + patch->is_copy = 1; + patch->old_name = find_name(line, NULL, 0, 0); + return 0; + } + + static int gitdiff_copydst(const char *line, struct patch *patch) + { + patch->is_copy = 1; + patch->new_name = find_name(line, NULL, 0, 0); + return 0; + } + + static int gitdiff_renamesrc(const char *line, struct patch *patch) + { + patch->is_rename = 1; + patch->old_name = find_name(line, NULL, 0, 0); + return 0; + } + + static int gitdiff_renamedst(const char *line, struct patch *patch) + { + patch->is_rename = 1; + patch->new_name = find_name(line, NULL, 0, 0); + return 0; + } + + static int gitdiff_similarity(const char *line, struct patch *patch) + { + if ((patch->score = strtoul(line, NULL, 10)) == ULONG_MAX) + patch->score = 0; + return 0; + } + + static int gitdiff_dissimilarity(const char *line, struct patch *patch) + { + if ((patch->score = strtoul(line, NULL, 10)) == ULONG_MAX) + patch->score = 0; + return 0; + } + + static int gitdiff_index(const char *line, struct patch *patch) + { + /* index line is N hexadecimal, "..", N hexadecimal, + * and optional space with octal mode. + */ + const char *ptr, *eol; + int len; + + ptr = strchr(line, '.'); + if (!ptr || ptr[1] != '.' || 40 < ptr - line) + return 0; + len = ptr - line; + memcpy(patch->old_sha1_prefix, line, len); + patch->old_sha1_prefix[len] = 0; + + line = ptr + 2; + ptr = strchr(line, ' '); + eol = strchr(line, '\n'); + + if (!ptr || eol < ptr) + ptr = eol; + len = ptr - line; + + if (40 < len) + return 0; + memcpy(patch->new_sha1_prefix, line, len); + patch->new_sha1_prefix[len] = 0; + if (*ptr == ' ') + patch->new_mode = patch->old_mode = strtoul(ptr+1, NULL, 8); + return 0; + } + + /* + * This is normal for a diff that doesn't change anything: we'll fall through + * into the next diff. Tell the parser to break out. + */ + static int gitdiff_unrecognized(const char *line, struct patch *patch) + { + return -1; + } + + static const char *stop_at_slash(const char *line, int llen) + { + int i; + + for (i = 0; i < llen; i++) { + int ch = line[i]; + if (ch == '/') + return line + i; + } + return NULL; + } + + /* This is to extract the same name that appears on "diff --git" + * line. We do not find and return anything if it is a rename + * patch, and it is OK because we will find the name elsewhere. + * We need to reliably find name only when it is mode-change only, + * creation or deletion of an empty file. In any of these cases, + * both sides are the same name under a/ and b/ respectively. + */ + static char *git_header_name(char *line, int llen) + { + int len; + const char *name; + const char *second = NULL; + + line += strlen("diff --git "); + llen -= strlen("diff --git "); + + if (*line == '"') { + const char *cp; + char *first = unquote_c_style(line, &second); + if (!first) + return NULL; + + /* advance to the first slash */ + cp = stop_at_slash(first, strlen(first)); + if (!cp || cp == first) { + /* we do not accept absolute paths */ + free_first_and_fail: + free(first); + return NULL; + } + len = strlen(cp+1); + memmove(first, cp+1, len+1); /* including NUL */ + + /* second points at one past closing dq of name. + * find the second name. + */ + while ((second < line + llen) && isspace(*second)) + second++; + + if (line + llen <= second) + goto free_first_and_fail; + if (*second == '"') { + char *sp = unquote_c_style(second, NULL); + if (!sp) + goto free_first_and_fail; + cp = stop_at_slash(sp, strlen(sp)); + if (!cp || cp == sp) { + free_both_and_fail: + free(sp); + goto free_first_and_fail; + } + /* They must match, otherwise ignore */ + if (strcmp(cp+1, first)) + goto free_both_and_fail; + free(sp); + return first; + } + + /* unquoted second */ + cp = stop_at_slash(second, line + llen - second); + if (!cp || cp == second) + goto free_first_and_fail; + cp++; + if (line + llen - cp != len + 1 || + memcmp(first, cp, len)) + goto free_first_and_fail; + return first; + } + + /* unquoted first name */ + name = stop_at_slash(line, llen); + if (!name || name == line) + return NULL; + + name++; + + /* since the first name is unquoted, a dq if exists must be + * the beginning of the second name. + */ + for (second = name; second < line + llen; second++) { + if (*second == '"') { + const char *cp = second; + const char *np; + char *sp = unquote_c_style(second, NULL); + + if (!sp) + return NULL; + np = stop_at_slash(sp, strlen(sp)); + if (!np || np == sp) { + free_second_and_fail: + free(sp); + return NULL; + } + np++; + len = strlen(np); + if (len < cp - name && + !strncmp(np, name, len) && + isspace(name[len])) { + /* Good */ + memmove(sp, np, len + 1); + return sp; + } + goto free_second_and_fail; + } + } + + /* + * Accept a name only if it shows up twice, exactly the same + * form. + */ + for (len = 0 ; ; len++) { + char c = name[len]; + + switch (c) { + default: + continue; + case '\n': + return NULL; + case '\t': case ' ': + second = name+len; + for (;;) { + char c = *second++; + if (c == '\n') + return NULL; + if (c == '/') + break; + } + if (second[len] == '\n' && !memcmp(name, second, len)) { + char *ret = xmalloc(len + 1); + memcpy(ret, name, len); + ret[len] = 0; + return ret; + } + } + } + return NULL; + } + + /* Verify that we recognize the lines following a git header */ + static int parse_git_header(char *line, int len, unsigned int size, struct patch *patch) + { + unsigned long offset; + + /* A git diff has explicit new/delete information, so we don't guess */ + patch->is_new = 0; + patch->is_delete = 0; + + /* + * Some things may not have the old name in the + * rest of the headers anywhere (pure mode changes, + * or removing or adding empty files), so we get + * the default name from the header. + */ + patch->def_name = git_header_name(line, len); + + line += len; + size -= len; + linenr++; + for (offset = len ; size > 0 ; offset += len, size -= len, line += len, linenr++) { + static const struct opentry { + const char *str; + int (*fn)(const char *, struct patch *); + } optable[] = { + { "@@ -", gitdiff_hdrend }, + { "--- ", gitdiff_oldname }, + { "+++ ", gitdiff_newname }, + { "old mode ", gitdiff_oldmode }, + { "new mode ", gitdiff_newmode }, + { "deleted file mode ", gitdiff_delete }, + { "new file mode ", gitdiff_newfile }, + { "copy from ", gitdiff_copysrc }, + { "copy to ", gitdiff_copydst }, + { "rename old ", gitdiff_renamesrc }, + { "rename new ", gitdiff_renamedst }, + { "rename from ", gitdiff_renamesrc }, + { "rename to ", gitdiff_renamedst }, + { "similarity index ", gitdiff_similarity }, + { "dissimilarity index ", gitdiff_dissimilarity }, + { "index ", gitdiff_index }, + { "", gitdiff_unrecognized }, + }; + int i; + + len = linelen(line, size); + if (!len || line[len-1] != '\n') + break; + for (i = 0; i < ARRAY_SIZE(optable); i++) { + const struct opentry *p = optable + i; + int oplen = strlen(p->str); + if (len < oplen || memcmp(p->str, line, oplen)) + continue; + if (p->fn(line + oplen, patch) < 0) + return offset; + break; + } + } + + return offset; + } + + static int parse_num(const char *line, unsigned long *p) + { + char *ptr; + + if (!isdigit(*line)) + return 0; + *p = strtoul(line, &ptr, 10); + return ptr - line; + } + + static int parse_range(const char *line, int len, int offset, const char *expect, + unsigned long *p1, unsigned long *p2) + { + int digits, ex; + + if (offset < 0 || offset >= len) + return -1; + line += offset; + len -= offset; + + digits = parse_num(line, p1); + if (!digits) + return -1; + + offset += digits; + line += digits; + len -= digits; + + *p2 = 1; + if (*line == ',') { + digits = parse_num(line+1, p2); + if (!digits) + return -1; + + offset += digits+1; + line += digits+1; + len -= digits+1; + } + + ex = strlen(expect); + if (ex > len) + return -1; + if (memcmp(line, expect, ex)) + return -1; + + return offset + ex; + } + + /* + * Parse a unified diff fragment header of the + * form "@@ -a,b +c,d @@" + */ + static int parse_fragment_header(char *line, int len, struct fragment *fragment) + { + int offset; + + if (!len || line[len-1] != '\n') + return -1; + + /* Figure out the number of lines in a fragment */ + offset = parse_range(line, len, 4, " +", &fragment->oldpos, &fragment->oldlines); + offset = parse_range(line, len, offset, " @@", &fragment->newpos, &fragment->newlines); + + return offset; + } + + static int find_header(char *line, unsigned long size, int *hdrsize, struct patch *patch) + { + unsigned long offset, len; + + patch->is_rename = patch->is_copy = 0; + patch->is_new = patch->is_delete = -1; + patch->old_mode = patch->new_mode = 0; + patch->old_name = patch->new_name = NULL; + for (offset = 0; size > 0; offset += len, size -= len, line += len, linenr++) { + unsigned long nextlen; + + len = linelen(line, size); + if (!len) + break; + + /* Testing this early allows us to take a few shortcuts.. */ + if (len < 6) + continue; + + /* + * Make sure we don't find any unconnected patch fragmants. + * That's a sign that we didn't find a header, and that a + * patch has become corrupted/broken up. + */ + if (!memcmp("@@ -", line, 4)) { + struct fragment dummy; + if (parse_fragment_header(line, len, &dummy) < 0) + continue; + error("patch fragment without header at line %d: %.*s", linenr, (int)len-1, line); + } + + if (size < len + 6) + break; + + /* + * Git patch? It might not have a real patch, just a rename + * or mode change, so we handle that specially + */ + if (!memcmp("diff --git ", line, 11)) { + int git_hdr_len = parse_git_header(line, len, size, patch); + if (git_hdr_len <= len) + continue; + if (!patch->old_name && !patch->new_name) { + if (!patch->def_name) + die("git diff header lacks filename information (line %d)", linenr); + patch->old_name = patch->new_name = patch->def_name; + } + *hdrsize = git_hdr_len; + return offset; + } + + /** --- followed by +++ ? */ + if (memcmp("--- ", line, 4) || memcmp("+++ ", line + len, 4)) + continue; + + /* + * We only accept unified patches, so we want it to + * at least have "@@ -a,b +c,d @@\n", which is 14 chars + * minimum + */ + nextlen = linelen(line + len, size - len); + if (size < nextlen + 14 || memcmp("@@ -", line + len + nextlen, 4)) + continue; + + /* Ok, we'll consider it a patch */ + parse_traditional_patch(line, line+len, patch); + *hdrsize = len + nextlen; + linenr += 2; + return offset; + } + return -1; + } + + /* + * Parse a unified diff. Note that this really needs + * to parse each fragment separately, since the only + * way to know the difference between a "---" that is + * part of a patch, and a "---" that starts the next + * patch is to look at the line counts.. + */ + static int parse_fragment(char *line, unsigned long size, struct patch *patch, struct fragment *fragment) + { + int added, deleted; + int len = linelen(line, size), offset; + unsigned long oldlines, newlines; + unsigned long leading, trailing; + + offset = parse_fragment_header(line, len, fragment); + if (offset < 0) + return -1; + oldlines = fragment->oldlines; + newlines = fragment->newlines; + leading = 0; + trailing = 0; + + if (patch->is_new < 0) { + patch->is_new = !oldlines; + if (!oldlines) + patch->old_name = NULL; + } + if (patch->is_delete < 0) { + patch->is_delete = !newlines; + if (!newlines) + patch->new_name = NULL; + } + + if (patch->is_new && oldlines) + return error("new file depends on old contents"); + if (patch->is_delete != !newlines) { + if (newlines) + return error("deleted file still has contents"); + fprintf(stderr, "** warning: file %s becomes empty but is not deleted\n", patch->new_name); + } + + /* Parse the thing.. */ + line += len; + size -= len; + linenr++; + added = deleted = 0; + for (offset = len; size > 0; offset += len, size -= len, line += len, linenr++) { + if (!oldlines && !newlines) + break; + len = linelen(line, size); + if (!len || line[len-1] != '\n') + return -1; + switch (*line) { + default: + return -1; + case ' ': + oldlines--; + newlines--; + if (!deleted && !added) + leading++; + trailing++; + break; + case '-': + deleted++; + oldlines--; + trailing = 0; + break; + case '+': + /* + * We know len is at least two, since we have a '+' and + * we checked that the last character was a '\n' above. + * That is, an addition of an empty line would check + * the '+' here. Sneaky... + */ + if ((new_whitespace != nowarn_whitespace) && + isspace(line[len-2])) { + whitespace_error++; + if (squelch_whitespace_errors && + squelch_whitespace_errors < + whitespace_error) + ; + else { + fprintf(stderr, "Adds trailing whitespace.\n%s:%d:%.*s\n", + patch_input_file, + linenr, len-2, line+1); + } + } + added++; + newlines--; + trailing = 0; + break; + + /* We allow "\ No newline at end of file". Depending + * on locale settings when the patch was produced we + * don't know what this line looks like. The only + * thing we do know is that it begins with "\ ". + * Checking for 12 is just for sanity check -- any + * l10n of "\ No newline..." is at least that long. + */ + case '\\': + if (len < 12 || memcmp(line, "\\ ", 2)) + return -1; + break; + } + } + if (oldlines || newlines) + return -1; + fragment->leading = leading; + fragment->trailing = trailing; + + /* If a fragment ends with an incomplete line, we failed to include + * it in the above loop because we hit oldlines == newlines == 0 + * before seeing it. + */ + if (12 < size && !memcmp(line, "\\ ", 2)) + offset += linelen(line, size); + + patch->lines_added += added; + patch->lines_deleted += deleted; + return offset; + } + + static int parse_single_patch(char *line, unsigned long size, struct patch *patch) + { + unsigned long offset = 0; + struct fragment **fragp = &patch->fragments; + + while (size > 4 && !memcmp(line, "@@ -", 4)) { + struct fragment *fragment; + int len; + + fragment = xcalloc(1, sizeof(*fragment)); + len = parse_fragment(line, size, patch, fragment); + if (len <= 0) + die("corrupt patch at line %d", linenr); + + fragment->patch = line; + fragment->size = len; + + *fragp = fragment; + fragp = &fragment->next; + + offset += len; + line += len; + size -= len; + } + return offset; + } + + static inline int metadata_changes(struct patch *patch) + { + return patch->is_rename > 0 || + patch->is_copy > 0 || + patch->is_new > 0 || + patch->is_delete || + (patch->old_mode && patch->new_mode && + patch->old_mode != patch->new_mode); + } + + static int parse_binary(char *buffer, unsigned long size, struct patch *patch) + { + /* We have read "GIT binary patch\n"; what follows is a line + * that says the patch method (currently, either "deflated + * literal" or "deflated delta") and the length of data before + * deflating; a sequence of 'length-byte' followed by base-85 + * encoded data follows. + * + * Each 5-byte sequence of base-85 encodes up to 4 bytes, + * and we would limit the patch line to 66 characters, + * so one line can fit up to 13 groups that would decode + * to 52 bytes max. The length byte 'A'-'Z' corresponds + * to 1-26 bytes, and 'a'-'z' corresponds to 27-52 bytes. + * The end of binary is signalled with an empty line. + */ + int llen, used; + struct fragment *fragment; + char *data = NULL; + + patch->fragments = fragment = xcalloc(1, sizeof(*fragment)); + + /* Grab the type of patch */ + llen = linelen(buffer, size); + used = llen; + linenr++; + + if (!strncmp(buffer, "delta ", 6)) { + patch->is_binary = BINARY_DELTA_DEFLATED; + patch->deflate_origlen = strtoul(buffer + 6, NULL, 10); + } + else if (!strncmp(buffer, "literal ", 8)) { + patch->is_binary = BINARY_LITERAL_DEFLATED; + patch->deflate_origlen = strtoul(buffer + 8, NULL, 10); + } + else + return error("unrecognized binary patch at line %d: %.*s", + linenr-1, llen-1, buffer); + buffer += llen; + while (1) { + int byte_length, max_byte_length, newsize; + llen = linelen(buffer, size); + used += llen; + linenr++; + if (llen == 1) + break; + /* Minimum line is "A00000\n" which is 7-byte long, + * and the line length must be multiple of 5 plus 2. + */ + if ((llen < 7) || (llen-2) % 5) + goto corrupt; + max_byte_length = (llen - 2) / 5 * 4; + byte_length = *buffer; + if ('A' <= byte_length && byte_length <= 'Z') + byte_length = byte_length - 'A' + 1; + else if ('a' <= byte_length && byte_length <= 'z') + byte_length = byte_length - 'a' + 27; + else + goto corrupt; + /* if the input length was not multiple of 4, we would + * have filler at the end but the filler should never + * exceed 3 bytes + */ + if (max_byte_length < byte_length || + byte_length <= max_byte_length - 4) + goto corrupt; + newsize = fragment->size + byte_length; + data = xrealloc(data, newsize); + if (decode_85(data + fragment->size, + buffer + 1, + byte_length)) + goto corrupt; + fragment->size = newsize; + buffer += llen; + size -= llen; + } + fragment->patch = data; + return used; + corrupt: + return error("corrupt binary patch at line %d: %.*s", + linenr-1, llen-1, buffer); + } + + static int parse_chunk(char *buffer, unsigned long size, struct patch *patch) + { + int hdrsize, patchsize; + int offset = find_header(buffer, size, &hdrsize, patch); + + if (offset < 0) + return offset; + + patchsize = parse_single_patch(buffer + offset + hdrsize, size - offset - hdrsize, patch); + + if (!patchsize) { + static const char *binhdr[] = { + "Binary files ", + "Files ", + NULL, + }; + static const char git_binary[] = "GIT binary patch\n"; + int i; + int hd = hdrsize + offset; + unsigned long llen = linelen(buffer + hd, size - hd); + + if (llen == sizeof(git_binary) - 1 && + !memcmp(git_binary, buffer + hd, llen)) { + int used; + linenr++; + used = parse_binary(buffer + hd + llen, + size - hd - llen, patch); + if (used) + patchsize = used + llen; + else + patchsize = 0; + } + else if (!memcmp(" differ\n", buffer + hd + llen - 8, 8)) { + for (i = 0; binhdr[i]; i++) { + int len = strlen(binhdr[i]); + if (len < size - hd && + !memcmp(binhdr[i], buffer + hd, len)) { + linenr++; + patch->is_binary = 1; + patchsize = llen; + break; + } + } + } + + /* Empty patch cannot be applied if: + * - it is a binary patch and we do not do binary_replace, or + * - text patch without metadata change + */ + if ((apply || check) && + (patch->is_binary + ? !allow_binary_replacement + : !metadata_changes(patch))) + die("patch with only garbage at line %d", linenr); + } + + return offset + hdrsize + patchsize; + } + + static const char pluses[] = "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"; + static const char minuses[]= "----------------------------------------------------------------------"; + + static void show_stats(struct patch *patch) + { + const char *prefix = ""; + char *name = patch->new_name; + char *qname = NULL; + int len, max, add, del, total; + + if (!name) + name = patch->old_name; + + if (0 < (len = quote_c_style(name, NULL, NULL, 0))) { + qname = xmalloc(len + 1); + quote_c_style(name, qname, NULL, 0); + name = qname; + } + + /* + * "scale" the filename + */ + len = strlen(name); + max = max_len; + if (max > 50) + max = 50; + if (len > max) { + char *slash; + prefix = "..."; + max -= 3; + name += len - max; + slash = strchr(name, '/'); + if (slash) + name = slash; + } + len = max; + + /* + * scale the add/delete + */ + max = max_change; + if (max + len > 70) + max = 70 - len; + + add = patch->lines_added; + del = patch->lines_deleted; + total = add + del; + + if (max_change > 0) { + total = (total * max + max_change / 2) / max_change; + add = (add * max + max_change / 2) / max_change; + del = total - add; + } + if (patch->is_binary) + printf(" %s%-*s | Bin\n", prefix, len, name); + else + printf(" %s%-*s |%5d %.*s%.*s\n", prefix, + len, name, patch->lines_added + patch->lines_deleted, + add, pluses, del, minuses); + if (qname) + free(qname); + } + + static int read_old_data(struct stat *st, const char *path, void *buf, unsigned long size) + { + int fd; + unsigned long got; + + switch (st->st_mode & S_IFMT) { + case S_IFLNK: + return readlink(path, buf, size); + case S_IFREG: + fd = open(path, O_RDONLY); + if (fd < 0) + return error("unable to open %s", path); + got = 0; + for (;;) { + int ret = xread(fd, buf + got, size - got); + if (ret <= 0) + break; + got += ret; + } + close(fd); + return got; + + default: + return -1; + } + } + + static int find_offset(const char *buf, unsigned long size, const char *fragment, unsigned long fragsize, int line, int *lines) + { + int i; + unsigned long start, backwards, forwards; + + if (fragsize > size) + return -1; + + start = 0; + if (line > 1) { + unsigned long offset = 0; + i = line-1; + while (offset + fragsize <= size) { + if (buf[offset++] == '\n') { + start = offset; + if (!--i) + break; + } + } + } + + /* Exact line number? */ + if (!memcmp(buf + start, fragment, fragsize)) + return start; + + /* + * There's probably some smart way to do this, but I'll leave + * that to the smart and beautiful people. I'm simple and stupid. + */ + backwards = start; + forwards = start; + for (i = 0; ; i++) { + unsigned long try; + int n; + + /* "backward" */ + if (i & 1) { + if (!backwards) { + if (forwards + fragsize > size) + break; + continue; + } + do { + --backwards; + } while (backwards && buf[backwards-1] != '\n'); + try = backwards; + } else { + while (forwards + fragsize <= size) { + if (buf[forwards++] == '\n') + break; + } + try = forwards; + } + + if (try + fragsize > size) + continue; + if (memcmp(buf + try, fragment, fragsize)) + continue; + n = (i >> 1)+1; + if (i & 1) + n = -n; + *lines = n; + return try; + } + + /* + * We should start searching forward and backward. + */ + return -1; + } + + static void remove_first_line(const char **rbuf, int *rsize) + { + const char *buf = *rbuf; + int size = *rsize; + unsigned long offset; + offset = 0; + while (offset <= size) { + if (buf[offset++] == '\n') + break; + } + *rsize = size - offset; + *rbuf = buf + offset; + } + + static void remove_last_line(const char **rbuf, int *rsize) + { + const char *buf = *rbuf; + int size = *rsize; + unsigned long offset; + offset = size - 1; + while (offset > 0) { + if (buf[--offset] == '\n') + break; + } + *rsize = offset + 1; + } + + struct buffer_desc { + char *buffer; + unsigned long size; + unsigned long alloc; + }; + + static int apply_line(char *output, const char *patch, int plen) + { + /* plen is number of bytes to be copied from patch, + * starting at patch+1 (patch[0] is '+'). Typically + * patch[plen] is '\n'. + */ + int add_nl_to_tail = 0; + if ((new_whitespace == strip_whitespace) && + 1 < plen && isspace(patch[plen-1])) { + if (patch[plen] == '\n') + add_nl_to_tail = 1; + plen--; + while (0 < plen && isspace(patch[plen])) + plen--; + applied_after_stripping++; + } + memcpy(output, patch + 1, plen); + if (add_nl_to_tail) + output[plen++] = '\n'; + return plen; + } + + static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag) + { + char *buf = desc->buffer; + const char *patch = frag->patch; + int offset, size = frag->size; + char *old = xmalloc(size); + char *new = xmalloc(size); + const char *oldlines, *newlines; + int oldsize = 0, newsize = 0; + unsigned long leading, trailing; + int pos, lines; + + while (size > 0) { + int len = linelen(patch, size); + int plen; + + if (!len) + break; + + /* + * "plen" is how much of the line we should use for + * the actual patch data. Normally we just remove the + * first character on the line, but if the line is + * followed by "\ No newline", then we also remove the + * last one (which is the newline, of course). + */ + plen = len-1; + if (len < size && patch[len] == '\\') + plen--; + switch (*patch) { + case ' ': + case '-': + memcpy(old + oldsize, patch + 1, plen); + oldsize += plen; + if (*patch == '-') + break; + /* Fall-through for ' ' */ + case '+': + if (*patch != '+' || !no_add) + newsize += apply_line(new + newsize, patch, + plen); + break; + case '@': case '\\': + /* Ignore it, we already handled it */ + break; + default: + return -1; + } + patch += len; + size -= len; + } + + #ifdef NO_ACCURATE_DIFF + if (oldsize > 0 && old[oldsize - 1] == '\n' && + newsize > 0 && new[newsize - 1] == '\n') { + oldsize--; + newsize--; + } + #endif + + oldlines = old; + newlines = new; + leading = frag->leading; + trailing = frag->trailing; + lines = 0; + pos = frag->newpos; + for (;;) { + offset = find_offset(buf, desc->size, oldlines, oldsize, pos, &lines); + if (offset >= 0) { + int diff = newsize - oldsize; + unsigned long size = desc->size + diff; + unsigned long alloc = desc->alloc; + + /* Warn if it was necessary to reduce the number + * of context lines. + */ + if ((leading != frag->leading) || (trailing != frag->trailing)) + fprintf(stderr, "Context reduced to (%ld/%ld) to apply fragment at %d\n", + leading, trailing, pos + lines); + + if (size > alloc) { + alloc = size + 8192; + desc->alloc = alloc; + buf = xrealloc(buf, alloc); + desc->buffer = buf; + } + desc->size = size; + memmove(buf + offset + newsize, buf + offset + oldsize, size - offset - newsize); + memcpy(buf + offset, newlines, newsize); + offset = 0; + + break; + } + + /* Am I at my context limits? */ + if ((leading <= p_context) && (trailing <= p_context)) + break; + /* Reduce the number of context lines + * Reduce both leading and trailing if they are equal + * otherwise just reduce the larger context. + */ + if (leading >= trailing) { + remove_first_line(&oldlines, &oldsize); + remove_first_line(&newlines, &newsize); + pos--; + leading--; + } + if (trailing > leading) { + remove_last_line(&oldlines, &oldsize); + remove_last_line(&newlines, &newsize); + trailing--; + } + } + + free(old); + free(new); + return offset; + } + + static char *inflate_it(const void *data, unsigned long size, + unsigned long inflated_size) + { + z_stream stream; + void *out; + int st; + + memset(&stream, 0, sizeof(stream)); + + stream.next_in = (unsigned char *)data; + stream.avail_in = size; + stream.next_out = out = xmalloc(inflated_size); + stream.avail_out = inflated_size; + inflateInit(&stream); + st = inflate(&stream, Z_FINISH); + if ((st != Z_STREAM_END) || stream.total_out != inflated_size) { + free(out); + return NULL; + } + return out; + } + + static int apply_binary_fragment(struct buffer_desc *desc, struct patch *patch) + { + unsigned long dst_size; + struct fragment *fragment = patch->fragments; + void *data; + void *result; + + data = inflate_it(fragment->patch, fragment->size, + patch->deflate_origlen); + if (!data) + return error("corrupt patch data"); + switch (patch->is_binary) { + case BINARY_DELTA_DEFLATED: + result = patch_delta(desc->buffer, desc->size, + data, + patch->deflate_origlen, + &dst_size); + free(desc->buffer); + desc->buffer = result; + free(data); + break; + case BINARY_LITERAL_DEFLATED: + free(desc->buffer); + desc->buffer = data; + dst_size = patch->deflate_origlen; + break; + } + if (!desc->buffer) + return -1; + desc->size = desc->alloc = dst_size; + return 0; + } + + static int apply_binary(struct buffer_desc *desc, struct patch *patch) + { + const char *name = patch->old_name ? patch->old_name : patch->new_name; + unsigned char sha1[20]; + unsigned char hdr[50]; + int hdrlen; + + if (!allow_binary_replacement) + return error("cannot apply binary patch to '%s' " + "without --allow-binary-replacement", + name); + + /* For safety, we require patch index line to contain + * full 40-byte textual SHA1 for old and new, at least for now. + */ + if (strlen(patch->old_sha1_prefix) != 40 || + strlen(patch->new_sha1_prefix) != 40 || + get_sha1_hex(patch->old_sha1_prefix, sha1) || + get_sha1_hex(patch->new_sha1_prefix, sha1)) + return error("cannot apply binary patch to '%s' " + "without full index line", name); + + if (patch->old_name) { + /* See if the old one matches what the patch + * applies to. + */ + write_sha1_file_prepare(desc->buffer, desc->size, + blob_type, sha1, hdr, &hdrlen); + if (strcmp(sha1_to_hex(sha1), patch->old_sha1_prefix)) + return error("the patch applies to '%s' (%s), " + "which does not match the " + "current contents.", + name, sha1_to_hex(sha1)); + } + else { + /* Otherwise, the old one must be empty. */ + if (desc->size) + return error("the patch applies to an empty " + "'%s' but it is not empty", name); + } + + get_sha1_hex(patch->new_sha1_prefix, sha1); + if (!memcmp(sha1, null_sha1, 20)) { + free(desc->buffer); + desc->alloc = desc->size = 0; + desc->buffer = NULL; + return 0; /* deletion patch */ + } + + if (has_sha1_file(sha1)) { + /* We already have the postimage */ + char type[10]; + unsigned long size; + + free(desc->buffer); + desc->buffer = read_sha1_file(sha1, type, &size); + if (!desc->buffer) + return error("the necessary postimage %s for " + "'%s' cannot be read", + patch->new_sha1_prefix, name); + desc->alloc = desc->size = size; + } + else { + /* We have verified desc matches the preimage; + * apply the patch data to it, which is stored + * in the patch->fragments->{patch,size}. + */ + if (apply_binary_fragment(desc, patch)) + return error("binary patch does not apply to '%s'", + name); + + /* verify that the result matches */ + write_sha1_file_prepare(desc->buffer, desc->size, blob_type, + sha1, hdr, &hdrlen); + if (strcmp(sha1_to_hex(sha1), patch->new_sha1_prefix)) + return error("binary patch to '%s' creates incorrect result", name); + } + + return 0; + } + + static int apply_fragments(struct buffer_desc *desc, struct patch *patch) + { + struct fragment *frag = patch->fragments; + const char *name = patch->old_name ? patch->old_name : patch->new_name; + + if (patch->is_binary) + return apply_binary(desc, patch); + + while (frag) { + if (apply_one_fragment(desc, frag) < 0) + return error("patch failed: %s:%ld", + name, frag->oldpos); + frag = frag->next; + } + return 0; + } + + static int apply_data(struct patch *patch, struct stat *st, struct cache_entry *ce) + { + char *buf; + unsigned long size, alloc; + struct buffer_desc desc; + + size = 0; + alloc = 0; + buf = NULL; + if (cached) { + if (ce) { + char type[20]; + buf = read_sha1_file(ce->sha1, type, &size); + if (!buf) + return error("read of %s failed", + patch->old_name); + alloc = size; + } + } + else if (patch->old_name) { + size = st->st_size; + alloc = size + 8192; + buf = xmalloc(alloc); + if (read_old_data(st, patch->old_name, buf, alloc) != size) + return error("read of %s failed", patch->old_name); + } + + desc.size = size; + desc.alloc = alloc; + desc.buffer = buf; + if (apply_fragments(&desc, patch) < 0) + return -1; + patch->result = desc.buffer; + patch->resultsize = desc.size; + + if (patch->is_delete && patch->resultsize) + return error("removal patch leaves file contents"); + + return 0; + } + + static int check_patch(struct patch *patch) + { + struct stat st; + const char *old_name = patch->old_name; + const char *new_name = patch->new_name; + const char *name = old_name ? old_name : new_name; + struct cache_entry *ce = NULL; + + if (old_name) { + int changed = 0; + int stat_ret = 0; + unsigned st_mode = 0; + + if (!cached) + stat_ret = lstat(old_name, &st); + if (check_index) { + int pos = cache_name_pos(old_name, strlen(old_name)); + if (pos < 0) + return error("%s: does not exist in index", + old_name); + ce = active_cache[pos]; + if (stat_ret < 0) { + struct checkout costate; + if (errno != ENOENT) + return error("%s: %s", old_name, + strerror(errno)); + /* checkout */ + costate.base_dir = ""; + costate.base_dir_len = 0; + costate.force = 0; + costate.quiet = 0; + costate.not_new = 0; + costate.refresh_cache = 1; + if (checkout_entry(ce, + &costate, + NULL) || + lstat(old_name, &st)) + return -1; + } + if (!cached) + changed = ce_match_stat(ce, &st, 1); + if (changed) + return error("%s: does not match index", + old_name); + if (cached) + st_mode = ntohl(ce->ce_mode); + } + else if (stat_ret < 0) + return error("%s: %s", old_name, strerror(errno)); + + if (!cached) + st_mode = ntohl(create_ce_mode(st.st_mode)); + + if (patch->is_new < 0) + patch->is_new = 0; + if (!patch->old_mode) + patch->old_mode = st_mode; + if ((st_mode ^ patch->old_mode) & S_IFMT) + return error("%s: wrong type", old_name); + if (st_mode != patch->old_mode) + fprintf(stderr, "warning: %s has type %o, expected %o\n", + old_name, st_mode, patch->old_mode); + } + + if (new_name && (patch->is_new | patch->is_rename | patch->is_copy)) { + if (check_index && cache_name_pos(new_name, strlen(new_name)) >= 0) + return error("%s: already exists in index", new_name); + if (!cached) { + if (!lstat(new_name, &st)) + return error("%s: already exists in working directory", new_name); + if (errno != ENOENT) + return error("%s: %s", new_name, strerror(errno)); + } + if (!patch->new_mode) { + if (patch->is_new) + patch->new_mode = S_IFREG | 0644; + else + patch->new_mode = patch->old_mode; + } + } + + if (new_name && old_name) { + int same = !strcmp(old_name, new_name); + if (!patch->new_mode) + patch->new_mode = patch->old_mode; + if ((patch->old_mode ^ patch->new_mode) & S_IFMT) + return error("new mode (%o) of %s does not match old mode (%o)%s%s", + patch->new_mode, new_name, patch->old_mode, + same ? "" : " of ", same ? "" : old_name); + } + + if (apply_data(patch, &st, ce) < 0) + return error("%s: patch does not apply", name); + return 0; + } + + static int check_patch_list(struct patch *patch) + { + int error = 0; + + for (;patch ; patch = patch->next) + error |= check_patch(patch); + return error; + } + + static inline int is_null_sha1(const unsigned char *sha1) + { + return !memcmp(sha1, null_sha1, 20); + } + + static void show_index_list(struct patch *list) + { + struct patch *patch; + + /* Once we start supporting the reverse patch, it may be + * worth showing the new sha1 prefix, but until then... + */ + for (patch = list; patch; patch = patch->next) { + const unsigned char *sha1_ptr; + unsigned char sha1[20]; + const char *name; + + name = patch->old_name ? patch->old_name : patch->new_name; + if (patch->is_new) + sha1_ptr = null_sha1; + else if (get_sha1(patch->old_sha1_prefix, sha1)) + die("sha1 information is lacking or useless (%s).", + name); + else + sha1_ptr = sha1; + + printf("%06o %s ",patch->old_mode, sha1_to_hex(sha1_ptr)); + if (line_termination && quote_c_style(name, NULL, NULL, 0)) + quote_c_style(name, NULL, stdout, 0); + else + fputs(name, stdout); + putchar(line_termination); + } + } + + static void stat_patch_list(struct patch *patch) + { + int files, adds, dels; + + for (files = adds = dels = 0 ; patch ; patch = patch->next) { + files++; + adds += patch->lines_added; + dels += patch->lines_deleted; + show_stats(patch); + } + + printf(" %d files changed, %d insertions(+), %d deletions(-)\n", files, adds, dels); + } + + static void numstat_patch_list(struct patch *patch) + { + for ( ; patch; patch = patch->next) { + const char *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); + else + fputs(name, stdout); + putchar('\n'); + } + } + + static void show_file_mode_name(const char *newdelete, unsigned int mode, const char *name) + { + if (mode) + printf(" %s mode %06o %s\n", newdelete, mode, name); + else + printf(" %s %s\n", newdelete, name); + } + + static void show_mode_change(struct patch *p, int show_name) + { + if (p->old_mode && p->new_mode && p->old_mode != p->new_mode) { + if (show_name) + printf(" mode change %06o => %06o %s\n", + p->old_mode, p->new_mode, p->new_name); + else + printf(" mode change %06o => %06o\n", + p->old_mode, p->new_mode); + } + } + + static void show_rename_copy(struct patch *p) + { + const char *renamecopy = p->is_rename ? "rename" : "copy"; + const char *old, *new; + + /* Find common prefix */ + old = p->old_name; + new = p->new_name; + 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->old_name thru old is the common prefix, and old and new + * through the end of names are renames + */ + if (old != p->old_name) + printf(" %s %.*s{%s => %s} (%d%%)\n", renamecopy, + (int)(old - p->old_name), p->old_name, + old, new, p->score); + else + printf(" %s %s => %s (%d%%)\n", renamecopy, + p->old_name, p->new_name, p->score); + show_mode_change(p, 0); + } + + static void summary_patch_list(struct patch *patch) + { + struct patch *p; + + for (p = patch; p; p = p->next) { + if (p->is_new) + show_file_mode_name("create", p->new_mode, p->new_name); + else if (p->is_delete) + show_file_mode_name("delete", p->old_mode, p->old_name); + else { + if (p->is_rename || p->is_copy) + show_rename_copy(p); + else { + if (p->score) { + printf(" rewrite %s (%d%%)\n", + p->new_name, p->score); + show_mode_change(p, 0); + } + else + show_mode_change(p, 1); + } + } + } + } + + static void patch_stats(struct patch *patch) + { + int lines = patch->lines_added + patch->lines_deleted; + + if (lines > max_change) + max_change = lines; + if (patch->old_name) { + int len = quote_c_style(patch->old_name, NULL, NULL, 0); + if (!len) + len = strlen(patch->old_name); + if (len > max_len) + max_len = len; + } + if (patch->new_name) { + int len = quote_c_style(patch->new_name, NULL, NULL, 0); + if (!len) + len = strlen(patch->new_name); + if (len > max_len) + max_len = len; + } + } + + static void remove_file(struct patch *patch) + { + if (write_index) { + if (remove_file_from_cache(patch->old_name) < 0) + die("unable to remove %s from index", patch->old_name); ++ cache_tree_invalidate_path(active_cache_tree, patch->old_name); + } + if (!cached) + unlink(patch->old_name); + } + + static void add_index_file(const char *path, unsigned mode, void *buf, unsigned long size) + { + struct stat st; + struct cache_entry *ce; + int namelen = strlen(path); + unsigned ce_size = cache_entry_size(namelen); + + if (!write_index) + return; + + ce = xcalloc(1, ce_size); + memcpy(ce->name, path, namelen); + ce->ce_mode = create_ce_mode(mode); + ce->ce_flags = htons(namelen); + if (!cached) { + if (lstat(path, &st) < 0) + die("unable to stat newly created file %s", path); + fill_stat_cache_info(ce, &st); + } + if (write_sha1_file(buf, size, blob_type, ce->sha1) < 0) + die("unable to create backing store for newly created file %s", path); + if (add_cache_entry(ce, ADD_CACHE_OK_TO_ADD) < 0) + die("unable to add cache entry for %s", path); + } + + static int try_create_file(const char *path, unsigned int mode, const char *buf, unsigned long size) + { + int fd; + + if (S_ISLNK(mode)) + return symlink(buf, path); + fd = open(path, O_CREAT | O_EXCL | O_WRONLY, (mode & 0100) ? 0777 : 0666); + if (fd < 0) + return -1; + while (size) { + int written = xwrite(fd, buf, size); + if (written < 0) + die("writing file %s: %s", path, strerror(errno)); + if (!written) + die("out of space writing file %s", path); + buf += written; + size -= written; + } + if (close(fd) < 0) + die("closing file %s: %s", path, strerror(errno)); + return 0; + } + + /* + * We optimistically assume that the directories exist, + * which is true 99% of the time anyway. If they don't, + * we create them and try again. + */ + static void create_one_file(char *path, unsigned mode, const char *buf, unsigned long size) + { + if (cached) + return; + if (!try_create_file(path, mode, buf, size)) + return; + + if (errno == ENOENT) { + if (safe_create_leading_directories(path)) + return; + if (!try_create_file(path, mode, buf, size)) + return; + } + + if (errno == EEXIST) { + unsigned int nr = getpid(); + + for (;;) { + const char *newpath; + newpath = mkpath("%s~%u", path, nr); + if (!try_create_file(newpath, mode, buf, size)) { + if (!rename(newpath, path)) + return; + unlink(newpath); + break; + } + if (errno != EEXIST) + break; + ++nr; + } + } + die("unable to write file %s mode %o", path, mode); + } + + static void create_file(struct patch *patch) + { + char *path = patch->new_name; + unsigned mode = patch->new_mode; + unsigned long size = patch->resultsize; + char *buf = patch->result; + + 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) + { + if (patch->is_delete > 0) { + remove_file(patch); + return; + } + if (patch->is_new > 0 || patch->is_copy) { + create_file(patch); + return; + } + /* + * Rename or modification boils down to the same + * thing: remove the old, write the new + */ + remove_file(patch); + create_file(patch); + } + + static void write_out_results(struct patch *list, int skipped_patch) + { + if (!list && !skipped_patch) + die("No changes"); + + while (list) { + write_out_one_result(list); + list = list->next; + } + } + + static struct cache_file cache_file; + + static struct excludes { + struct excludes *next; + const char *path; + } *excludes; + + static int use_patch(struct patch *p) + { + const char *pathname = p->new_name ? p->new_name : p->old_name; + struct excludes *x = excludes; + while (x) { + if (fnmatch(x->path, pathname, 0) == 0) + return 0; + x = x->next; + } + if (0 < prefix_length) { + int pathlen = strlen(pathname); + if (pathlen <= prefix_length || + memcmp(prefix, pathname, prefix_length)) + return 0; + } + return 1; + } + + static int apply_patch(int fd, const char *filename) + { + unsigned long offset, size; + char *buffer = read_patch_file(fd, &size); + struct patch *list = NULL, **listp = &list; + int skipped_patch = 0; + + patch_input_file = filename; + if (!buffer) + return -1; + offset = 0; + while (size > 0) { + struct patch *patch; + int nr; + + patch = xcalloc(1, sizeof(*patch)); + nr = parse_chunk(buffer + offset, size, patch); + if (nr < 0) + break; + if (use_patch(patch)) { + patch_stats(patch); + *listp = patch; + listp = &patch->next; + } else { + /* perhaps free it a bit better? */ + free(patch); + skipped_patch++; + } + offset += nr; + size -= nr; + } + + if (whitespace_error && (new_whitespace == error_on_whitespace)) + apply = 0; + + write_index = check_index && apply; + if (write_index && newfd < 0) + newfd = hold_index_file_for_update(&cache_file, get_index_file()); + if (check_index) { + if (read_cache() < 0) + die("unable to read index file"); + } + + if ((check || apply) && check_patch_list(list) < 0) + exit(1); + + if (apply) + write_out_results(list, skipped_patch); + + if (show_index_info) + show_index_list(list); + + if (diffstat) + stat_patch_list(list); + + if (numstat) + numstat_patch_list(list); + + if (summary) + summary_patch_list(list); + + free(buffer); + return 0; + } + + static int git_apply_config(const char *var, const char *value) + { + if (!strcmp(var, "apply.whitespace")) { + apply_default_whitespace = strdup(value); + return 0; + } + return git_default_config(var, value); + } + + + int cmd_apply(int argc, const char **argv, char **envp) + { + int i; + int read_stdin = 1; + const char *whitespace_option = NULL; + + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + char *end; + int fd; + + if (!strcmp(arg, "-")) { + apply_patch(0, ""); + read_stdin = 0; + continue; + } + if (!strncmp(arg, "--exclude=", 10)) { + struct excludes *x = xmalloc(sizeof(*x)); + x->path = arg + 10; + x->next = excludes; + excludes = x; + continue; + } + if (!strncmp(arg, "-p", 2)) { + p_value = atoi(arg + 2); + continue; + } + if (!strcmp(arg, "--no-add")) { + no_add = 1; + continue; + } + if (!strcmp(arg, "--stat")) { + apply = 0; + diffstat = 1; + continue; + } + if (!strcmp(arg, "--allow-binary-replacement") || + !strcmp(arg, "--binary")) { + allow_binary_replacement = 1; + continue; + } + if (!strcmp(arg, "--numstat")) { + apply = 0; + numstat = 1; + continue; + } + if (!strcmp(arg, "--summary")) { + apply = 0; + summary = 1; + continue; + } + if (!strcmp(arg, "--check")) { + apply = 0; + check = 1; + continue; + } + if (!strcmp(arg, "--index")) { + check_index = 1; + continue; + } + if (!strcmp(arg, "--cached")) { + check_index = 1; + cached = 1; + continue; + } + if (!strcmp(arg, "--apply")) { + apply = 1; + continue; + } + if (!strcmp(arg, "--index-info")) { + apply = 0; + show_index_info = 1; + continue; + } + if (!strcmp(arg, "-z")) { + line_termination = 0; + continue; + } + if (!strncmp(arg, "-C", 2)) { + p_context = strtoul(arg + 2, &end, 0); + if (*end != '\0') + die("unrecognized context count '%s'", arg + 2); + continue; + } + if (!strncmp(arg, "--whitespace=", 13)) { + whitespace_option = arg + 13; + parse_whitespace_option(arg + 13); + continue; + } + + if (check_index && prefix_length < 0) { + prefix = setup_git_directory(); + prefix_length = prefix ? strlen(prefix) : 0; + git_config(git_apply_config); + if (!whitespace_option && apply_default_whitespace) + parse_whitespace_option(apply_default_whitespace); + } + if (0 < prefix_length) + arg = prefix_filename(prefix, prefix_length, arg); + + fd = open(arg, O_RDONLY); + if (fd < 0) + usage(apply_usage); + read_stdin = 0; + set_default_whitespace_mode(whitespace_option); + apply_patch(fd, arg); + close(fd); + } + set_default_whitespace_mode(whitespace_option); + if (read_stdin) + apply_patch(0, ""); + if (whitespace_error) { + if (squelch_whitespace_errors && + squelch_whitespace_errors < whitespace_error) { + int squelched = + whitespace_error - squelch_whitespace_errors; + fprintf(stderr, "warning: squelched %d whitespace error%s\n", + squelched, + squelched == 1 ? "" : "s"); + } + if (new_whitespace == error_on_whitespace) + die("%d line%s add%s trailing whitespaces.", + whitespace_error, + whitespace_error == 1 ? "" : "s", + whitespace_error == 1 ? "s" : ""); + if (applied_after_stripping) + fprintf(stderr, "warning: %d line%s applied after" + " stripping trailing whitespaces.\n", + applied_after_stripping, + applied_after_stripping == 1 ? "" : "s"); + else if (whitespace_error) + fprintf(stderr, "warning: %d line%s add%s trailing" + " whitespaces.\n", + whitespace_error, + whitespace_error == 1 ? "" : "s", + whitespace_error == 1 ? "s" : ""); + } + + if (write_index) { + if (write_cache(newfd, active_cache, active_nr) || + commit_index_file(&cache_file)) + die("Unable to write new cachefile"); + } + + return 0; + } diff --cc builtin-ls-files.c index 00000000,3a0c5f21..8dae9f70 mode 000000,100644..100644 --- a/builtin-ls-files.c +++ b/builtin-ls-files.c @@@ -1,0 -1,824 +1,499 @@@ + /* + * This merges the file listing in the directory cache index + * with the actual working directory list, and shows different + * combinations of the two. + * + * Copyright (C) Linus Torvalds, 2005 + */ -#include + #include + + #include "cache.h" + #include "quote.h" ++#include "dir.h" + #include "builtin.h" + + static int abbrev = 0; + static int show_deleted = 0; + static int show_cached = 0; + static int show_others = 0; -static int show_ignored = 0; + static int show_stage = 0; + static int show_unmerged = 0; + static int show_modified = 0; + static int show_killed = 0; -static int show_other_directories = 0; -static int hide_empty_directories = 0; + static int show_valid_bit = 0; + static int line_terminator = '\n'; + + static int prefix_len = 0, prefix_offset = 0; + static const char *prefix = NULL; + static const char **pathspec = NULL; + static int error_unmatch = 0; + static char *ps_matched = NULL; + + static const char *tag_cached = ""; + static const char *tag_unmerged = ""; + static const char *tag_removed = ""; + static const char *tag_other = ""; + static const char *tag_killed = ""; + static const char *tag_modified = ""; + -static const char *exclude_per_dir = NULL; - -/* We maintain three exclude pattern lists: - * EXC_CMDL lists patterns explicitly given on the command line. - * EXC_DIRS lists patterns obtained from per-directory ignore files. - * EXC_FILE lists patterns from fallback ignore files. - */ -#define EXC_CMDL 0 -#define EXC_DIRS 1 -#define EXC_FILE 2 -static struct exclude_list { - int nr; - int alloc; - struct exclude { - const char *pattern; - const char *base; - int baselen; - } **excludes; -} exclude_list[3]; - -static void add_exclude(const char *string, const char *base, - int baselen, struct exclude_list *which) -{ - struct exclude *x = xmalloc(sizeof (*x)); - - x->pattern = string; - x->base = base; - x->baselen = baselen; - if (which->nr == which->alloc) { - which->alloc = alloc_nr(which->alloc); - which->excludes = realloc(which->excludes, - which->alloc * sizeof(x)); - } - which->excludes[which->nr++] = x; -} - -static int add_excludes_from_file_1(const char *fname, - const char *base, - int baselen, - struct exclude_list *which) -{ - int fd, i; - long size; - char *buf, *entry; - - fd = open(fname, O_RDONLY); - if (fd < 0) - goto err; - size = lseek(fd, 0, SEEK_END); - if (size < 0) - goto err; - lseek(fd, 0, SEEK_SET); - if (size == 0) { - close(fd); - return 0; - } - buf = xmalloc(size+1); - if (read(fd, buf, size) != size) - goto err; - close(fd); - - buf[size++] = '\n'; - entry = buf; - for (i = 0; i < size; i++) { - if (buf[i] == '\n') { - if (entry != buf + i && entry[0] != '#') { - buf[i - (i && buf[i-1] == '\r')] = 0; - add_exclude(entry, base, baselen, which); - } - entry = buf + i + 1; - } - } - return 0; - - err: - if (0 <= fd) - close(fd); - return -1; -} - -static void add_excludes_from_file(const char *fname) -{ - if (add_excludes_from_file_1(fname, "", 0, - &exclude_list[EXC_FILE]) < 0) - die("cannot use %s as an exclude file", fname); -} - -static int push_exclude_per_directory(const char *base, int baselen) -{ - char exclude_file[PATH_MAX]; - struct exclude_list *el = &exclude_list[EXC_DIRS]; - int current_nr = el->nr; - - if (exclude_per_dir) { - memcpy(exclude_file, base, baselen); - strcpy(exclude_file + baselen, exclude_per_dir); - add_excludes_from_file_1(exclude_file, base, baselen, el); - } - return current_nr; -} - -static void pop_exclude_per_directory(int stk) -{ - struct exclude_list *el = &exclude_list[EXC_DIRS]; - - while (stk < el->nr) - free(el->excludes[--el->nr]); -} - -/* Scan the list and let the last match determines the fate. - * Return 1 for exclude, 0 for include and -1 for undecided. - */ -static int excluded_1(const char *pathname, - int pathlen, - struct exclude_list *el) -{ - int i; - - if (el->nr) { - for (i = el->nr - 1; 0 <= i; i--) { - struct exclude *x = el->excludes[i]; - const char *exclude = x->pattern; - int to_exclude = 1; - - if (*exclude == '!') { - to_exclude = 0; - exclude++; - } - - if (!strchr(exclude, '/')) { - /* match basename */ - const char *basename = strrchr(pathname, '/'); - basename = (basename) ? basename+1 : pathname; - if (fnmatch(exclude, basename, 0) == 0) - return to_exclude; - } - else { - /* match with FNM_PATHNAME: - * exclude has base (baselen long) implicitly - * in front of it. - */ - int baselen = x->baselen; - if (*exclude == '/') - exclude++; - - if (pathlen < baselen || - (baselen && pathname[baselen-1] != '/') || - strncmp(pathname, x->base, baselen)) - continue; - - if (fnmatch(exclude, pathname+baselen, - FNM_PATHNAME) == 0) - return to_exclude; - } - } - } - return -1; /* undecided */ -} - -static int excluded(const char *pathname) -{ - int pathlen = strlen(pathname); - int st; - - for (st = EXC_CMDL; st <= EXC_FILE; st++) { - switch (excluded_1(pathname, pathlen, &exclude_list[st])) { - case 0: - return 0; - case 1: - return 1; - } - } - return 0; -} - -struct nond_on_fs { - int len; - char name[FLEX_ARRAY]; /* more */ -}; - -static struct nond_on_fs **dir; -static int nr_dir; -static int dir_alloc; - -static void add_name(const char *pathname, int len) -{ - struct nond_on_fs *ent; - - if (cache_name_pos(pathname, len) >= 0) - return; - - if (nr_dir == dir_alloc) { - dir_alloc = alloc_nr(dir_alloc); - dir = xrealloc(dir, dir_alloc*sizeof(ent)); - } - ent = xmalloc(sizeof(*ent) + len + 1); - ent->len = len; - memcpy(ent->name, pathname, len); - ent->name[len] = 0; - dir[nr_dir++] = ent; -} - -static int dir_exists(const char *dirname, int len) -{ - int pos = cache_name_pos(dirname, len); - if (pos >= 0) - return 1; - pos = -pos-1; - if (pos >= active_nr) /* can't */ - return 0; - return !strncmp(active_cache[pos]->name, dirname, len); -} - -/* - * Read a directory tree. We currently ignore anything but - * directories, regular files and symlinks. That's because git - * doesn't handle them at all yet. Maybe that will change some - * day. - * - * Also, we ignore the name ".git" (even if it is not a directory). - * That likely will not change. - */ -static int read_directory(const char *path, const char *base, int baselen) -{ - DIR *fdir = opendir(path); - int contents = 0; - - if (fdir) { - int exclude_stk; - struct dirent *de; - char fullname[MAXPATHLEN + 1]; - memcpy(fullname, base, baselen); - - exclude_stk = push_exclude_per_directory(base, baselen); - - while ((de = readdir(fdir)) != NULL) { - int len; - - if ((de->d_name[0] == '.') && - (de->d_name[1] == 0 || - !strcmp(de->d_name + 1, ".") || - !strcmp(de->d_name + 1, "git"))) - continue; - len = strlen(de->d_name); - memcpy(fullname + baselen, de->d_name, len+1); - if (excluded(fullname) != show_ignored) { - if (!show_ignored || DTYPE(de) != DT_DIR) { - continue; - } - } - - switch (DTYPE(de)) { - struct stat st; - int subdir, rewind_base; - default: - continue; - case DT_UNKNOWN: - if (lstat(fullname, &st)) - continue; - if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) - break; - if (!S_ISDIR(st.st_mode)) - continue; - /* fallthrough */ - case DT_DIR: - memcpy(fullname + baselen + len, "/", 2); - len++; - rewind_base = nr_dir; - subdir = read_directory(fullname, fullname, - baselen + len); - if (show_other_directories && - (subdir || !hide_empty_directories) && - !dir_exists(fullname, baselen + len)) { - // Rewind the read subdirectory - while (nr_dir > rewind_base) - free(dir[--nr_dir]); - break; - } - contents += subdir; - continue; - case DT_REG: - case DT_LNK: - break; - } - add_name(fullname, baselen + len); - contents++; - } - closedir(fdir); - - pop_exclude_per_directory(exclude_stk); - } - - return contents; -} - -static int cmp_name(const void *p1, const void *p2) -{ - const struct nond_on_fs *e1 = *(const struct nond_on_fs **)p1; - const struct nond_on_fs *e2 = *(const struct nond_on_fs **)p2; - - return cache_name_compare(e1->name, e1->len, - e2->name, e2->len); -} + + /* + * Match a pathspec against a filename. The first "len" characters + * are the common prefix + */ + static int match(const char **spec, char *ps_matched, + const char *filename, int len) + { + const char *m; + + while ((m = *spec++) != NULL) { + int matchlen = strlen(m + len); + + if (!matchlen) + goto matched; + if (!strncmp(m + len, filename + len, matchlen)) { + if (m[len + matchlen - 1] == '/') + goto matched; + switch (filename[len + matchlen]) { + case '/': case '\0': + goto matched; + } + } + if (!fnmatch(m + len, filename + len, 0)) + goto matched; + if (ps_matched) + ps_matched++; + continue; + matched: + if (ps_matched) + *ps_matched = 1; + return 1; + } + return 0; + } + -static void show_dir_entry(const char *tag, struct nond_on_fs *ent) ++static void show_dir_entry(const char *tag, struct dir_entry *ent) + { + int len = prefix_len; + int offset = prefix_offset; + + if (len >= ent->len) + die("git-ls-files: internal error - directory entry not superset of prefix"); + + if (pathspec && !match(pathspec, ps_matched, ent->name, len)) + return; + + fputs(tag, stdout); + write_name_quoted("", 0, ent->name + offset, line_terminator, stdout); + putchar(line_terminator); + } + -static void show_other_files(void) ++static void show_other_files(struct dir_struct *dir) + { + int i; - for (i = 0; i < nr_dir; i++) { ++ for (i = 0; i < dir->nr; i++) { + /* We should not have a matching entry, but we + * may have an unmerged entry for this path. + */ - struct nond_on_fs *ent = dir[i]; ++ struct dir_entry *ent = dir->entries[i]; + int pos = cache_name_pos(ent->name, ent->len); + struct cache_entry *ce; + if (0 <= pos) + die("bug in show-other-files"); + pos = -pos - 1; + if (pos < active_nr) { + ce = active_cache[pos]; + if (ce_namelen(ce) == ent->len && + !memcmp(ce->name, ent->name, ent->len)) + continue; /* Yup, this one exists unmerged */ + } + show_dir_entry(tag_other, ent); + } + } + -static void show_killed_files(void) ++static void show_killed_files(struct dir_struct *dir) + { + int i; - for (i = 0; i < nr_dir; i++) { - struct nond_on_fs *ent = dir[i]; ++ for (i = 0; i < dir->nr; i++) { ++ struct dir_entry *ent = dir->entries[i]; + char *cp, *sp; + int pos, len, killed = 0; + + for (cp = ent->name; cp - ent->name < ent->len; cp = sp + 1) { + sp = strchr(cp, '/'); + if (!sp) { + /* If ent->name is prefix of an entry in the + * cache, it will be killed. + */ + pos = cache_name_pos(ent->name, ent->len); + if (0 <= pos) + die("bug in show-killed-files"); + pos = -pos - 1; + while (pos < active_nr && + ce_stage(active_cache[pos])) + pos++; /* skip unmerged */ + if (active_nr <= pos) + break; + /* pos points at a name immediately after + * ent->name in the cache. Does it expect + * ent->name to be a directory? + */ + len = ce_namelen(active_cache[pos]); + if ((ent->len < len) && + !strncmp(active_cache[pos]->name, + ent->name, ent->len) && + active_cache[pos]->name[ent->len] == '/') + killed = 1; + break; + } + if (0 <= cache_name_pos(ent->name, sp - ent->name)) { + /* If any of the leading directories in + * ent->name is registered in the cache, + * ent->name will be killed. + */ + killed = 1; + break; + } + } + if (killed) - show_dir_entry(tag_killed, dir[i]); ++ show_dir_entry(tag_killed, dir->entries[i]); + } + } + + static void show_ce_entry(const char *tag, struct cache_entry *ce) + { + int len = prefix_len; + int offset = prefix_offset; + + if (len >= ce_namelen(ce)) + die("git-ls-files: internal error - cache entry not superset of prefix"); + + if (pathspec && !match(pathspec, ps_matched, ce->name, len)) + return; + + if (tag && *tag && show_valid_bit && + (ce->ce_flags & htons(CE_VALID))) { + static char alttag[4]; + memcpy(alttag, tag, 3); + if (isalpha(tag[0])) + alttag[0] = tolower(tag[0]); + else if (tag[0] == '?') + alttag[0] = '!'; + else { + alttag[0] = 'v'; + alttag[1] = tag[0]; + alttag[2] = ' '; + alttag[3] = 0; + } + tag = alttag; + } + + if (!show_stage) { + fputs(tag, stdout); + write_name_quoted("", 0, ce->name + offset, + line_terminator, stdout); + putchar(line_terminator); + } + else { + printf("%s%06o %s %d\t", + tag, + ntohl(ce->ce_mode), + abbrev ? find_unique_abbrev(ce->sha1,abbrev) + : sha1_to_hex(ce->sha1), + ce_stage(ce)); + write_name_quoted("", 0, ce->name + offset, + line_terminator, stdout); + putchar(line_terminator); + } + } + -static void show_files(void) ++static void show_files(struct dir_struct *dir) + { + int i; + + /* For cached/deleted files we don't need to even do the readdir */ + if (show_others || show_killed) { + const char *path = ".", *base = ""; + int baselen = prefix_len; + - if (baselen) { ++ if (baselen) + path = base = prefix; - if (exclude_per_dir) { - char *p, *pp = xmalloc(baselen+1); - memcpy(pp, prefix, baselen+1); - p = pp; - while (1) { - char save = *p; - *p = 0; - push_exclude_per_directory(pp, p-pp); - *p++ = save; - if (!save) - break; - p = strchr(p, '/'); - if (p) - p++; - else - p = pp + baselen; - } - free(pp); - } - } - read_directory(path, base, baselen); - qsort(dir, nr_dir, sizeof(struct nond_on_fs *), cmp_name); ++ read_directory(dir, path, base, baselen); + if (show_others) - show_other_files(); ++ show_other_files(dir); + if (show_killed) - show_killed_files(); ++ show_killed_files(dir); + } + if (show_cached | show_stage) { + for (i = 0; i < active_nr; i++) { + struct cache_entry *ce = active_cache[i]; - if (excluded(ce->name) != show_ignored) ++ if (excluded(dir, ce->name) != dir->show_ignored) + continue; + if (show_unmerged && !ce_stage(ce)) + continue; + show_ce_entry(ce_stage(ce) ? tag_unmerged : tag_cached, ce); + } + } + if (show_deleted | show_modified) { + for (i = 0; i < active_nr; i++) { + struct cache_entry *ce = active_cache[i]; + struct stat st; + int err; - if (excluded(ce->name) != show_ignored) ++ if (excluded(dir, ce->name) != dir->show_ignored) + continue; + err = lstat(ce->name, &st); + if (show_deleted && err) + show_ce_entry(tag_removed, ce); + if (show_modified && ce_modified(ce, &st, 0)) + show_ce_entry(tag_modified, ce); + } + } + } + + /* + * Prune the index to only contain stuff starting with "prefix" + */ + static void prune_cache(void) + { + int pos = cache_name_pos(prefix, prefix_len); + unsigned int first, last; + + if (pos < 0) + pos = -pos-1; + active_cache += pos; + active_nr -= pos; + first = 0; + last = active_nr; + while (last > first) { + int next = (last + first) >> 1; + struct cache_entry *ce = active_cache[next]; + if (!strncmp(ce->name, prefix, prefix_len)) { + first = next+1; + continue; + } + last = next; + } + active_nr = last; + } + + static void verify_pathspec(void) + { + const char **p, *n, *prev; + char *real_prefix; + unsigned long max; + + prev = NULL; + max = PATH_MAX; + for (p = pathspec; (n = *p) != NULL; p++) { + int i, len = 0; + for (i = 0; i < max; i++) { + char c = n[i]; + if (prev && prev[i] != c) + break; + if (!c || c == '*' || c == '?') + break; + if (c == '/') + len = i+1; + } + prev = n; + if (len < max) { + max = len; + if (!max) + break; + } + } + + if (prefix_offset > max || memcmp(prev, prefix, prefix_offset)) + die("git-ls-files: cannot generate relative filenames containing '..'"); + + real_prefix = NULL; + prefix_len = max; + if (max) { + real_prefix = xmalloc(max + 1); + memcpy(real_prefix, prev, max); + real_prefix[max] = 0; + } + prefix = real_prefix; + } + + static const char ls_files_usage[] = + "git-ls-files [-z] [-t] [-v] (--[cached|deleted|others|stage|unmerged|killed|modified])* " + "[ --ignored ] [--exclude=] [--exclude-from=] " + "[ --exclude-per-directory= ] [--full-name] [--abbrev] " + "[--] []*"; + + int cmd_ls_files(int argc, const char **argv, char** envp) + { + int i; + int exc_given = 0; ++ struct dir_struct dir; + ++ memset(&dir, 0, sizeof(dir)); + prefix = setup_git_directory(); + if (prefix) + prefix_offset = strlen(prefix); + git_config(git_default_config); + + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + + if (!strcmp(arg, "--")) { + i++; + break; + } + if (!strcmp(arg, "-z")) { + line_terminator = 0; + continue; + } + if (!strcmp(arg, "-t") || !strcmp(arg, "-v")) { + tag_cached = "H "; + tag_unmerged = "M "; + tag_removed = "R "; + tag_modified = "C "; + tag_other = "? "; + tag_killed = "K "; + if (arg[1] == 'v') + show_valid_bit = 1; + continue; + } + if (!strcmp(arg, "-c") || !strcmp(arg, "--cached")) { + show_cached = 1; + continue; + } + if (!strcmp(arg, "-d") || !strcmp(arg, "--deleted")) { + show_deleted = 1; + continue; + } + if (!strcmp(arg, "-m") || !strcmp(arg, "--modified")) { + show_modified = 1; + continue; + } + if (!strcmp(arg, "-o") || !strcmp(arg, "--others")) { + show_others = 1; + continue; + } + if (!strcmp(arg, "-i") || !strcmp(arg, "--ignored")) { - show_ignored = 1; ++ dir.show_ignored = 1; + continue; + } + if (!strcmp(arg, "-s") || !strcmp(arg, "--stage")) { + show_stage = 1; + continue; + } + if (!strcmp(arg, "-k") || !strcmp(arg, "--killed")) { + show_killed = 1; + continue; + } + if (!strcmp(arg, "--directory")) { - show_other_directories = 1; ++ dir.show_other_directories = 1; + continue; + } + if (!strcmp(arg, "--no-empty-directory")) { - hide_empty_directories = 1; ++ dir.hide_empty_directories = 1; + continue; + } + if (!strcmp(arg, "-u") || !strcmp(arg, "--unmerged")) { + /* There's no point in showing unmerged unless + * you also show the stage information. + */ + show_stage = 1; + show_unmerged = 1; + continue; + } + if (!strcmp(arg, "-x") && i+1 < argc) { + exc_given = 1; - add_exclude(argv[++i], "", 0, &exclude_list[EXC_CMDL]); ++ add_exclude(argv[++i], "", 0, &dir.exclude_list[EXC_CMDL]); + continue; + } + if (!strncmp(arg, "--exclude=", 10)) { + exc_given = 1; - add_exclude(arg+10, "", 0, &exclude_list[EXC_CMDL]); ++ add_exclude(arg+10, "", 0, &dir.exclude_list[EXC_CMDL]); + continue; + } + if (!strcmp(arg, "-X") && i+1 < argc) { + exc_given = 1; - add_excludes_from_file(argv[++i]); ++ add_excludes_from_file(&dir, argv[++i]); + continue; + } + if (!strncmp(arg, "--exclude-from=", 15)) { + exc_given = 1; - add_excludes_from_file(arg+15); ++ add_excludes_from_file(&dir, arg+15); + continue; + } + if (!strncmp(arg, "--exclude-per-directory=", 24)) { + exc_given = 1; - exclude_per_dir = arg + 24; ++ dir.exclude_per_dir = arg + 24; + continue; + } + if (!strcmp(arg, "--full-name")) { + prefix_offset = 0; + continue; + } + if (!strcmp(arg, "--error-unmatch")) { + error_unmatch = 1; + continue; + } + if (!strncmp(arg, "--abbrev=", 9)) { + abbrev = strtoul(arg+9, NULL, 10); + if (abbrev && abbrev < MINIMUM_ABBREV) + abbrev = MINIMUM_ABBREV; + else if (abbrev > 40) + abbrev = 40; + continue; + } + if (!strcmp(arg, "--abbrev")) { + abbrev = DEFAULT_ABBREV; + continue; + } + if (*arg == '-') + usage(ls_files_usage); + break; + } + + pathspec = get_pathspec(prefix, argv + i); + + /* Verify that the pathspec matches the prefix */ + if (pathspec) + verify_pathspec(); + + /* Treat unmatching pathspec elements as errors */ + if (pathspec && error_unmatch) { + int num; + for (num = 0; pathspec[num]; num++) + ; + ps_matched = xcalloc(1, num); + } + - if (show_ignored && !exc_given) { ++ if (dir.show_ignored && !exc_given) { + fprintf(stderr, "%s: --ignored needs some exclude pattern\n", + argv[0]); + exit(1); + } + + /* With no flags, we default to showing the cached files */ + if (!(show_stage | show_deleted | show_others | show_unmerged | + show_killed | show_modified)) + show_cached = 1; + + read_cache(); + if (prefix) + prune_cache(); - show_files(); ++ show_files(&dir); + + if (ps_matched) { + /* We need to make sure all pathspec matched otherwise + * it is an error. + */ + int num, errors = 0; + for (num = 0; pathspec[num]; num++) { + if (ps_matched[num]) + continue; + error("pathspec '%s' did not match any.", + pathspec[num] + prefix_offset); + errors++; + } + return errors ? 1 : 0; + } + + return 0; + } diff --cc builtin-read-tree.c index 00000000,ec40d013..629d1516 mode 000000,100644..100644 --- a/builtin-read-tree.c +++ b/builtin-read-tree.c @@@ -1,0 -1,882 +1,998 @@@ + /* + * GIT - The information manager from hell + * + * Copyright (C) Linus Torvalds, 2005 + */ + #define DBRT_DEBUG 1 + + #include "cache.h" + + #include "object.h" + #include "tree.h" ++#include "cache-tree.h" + #include + #include + #include "builtin.h" + + static int reset = 0; + static int merge = 0; + static int update = 0; + static int index_only = 0; + static int nontrivial_merge = 0; + 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; + + static struct object_list *trees = NULL; + + static struct cache_entry df_conflict_entry = { + }; + + static struct tree_entry_list df_conflict_list = { + .name = NULL, + .next = &df_conflict_list + }; + + typedef int (*merge_fn_t)(struct cache_entry **src); + + static int entcmp(char *name1, int dir1, char *name2, int dir2) + { + int len1 = strlen(name1); + int len2 = strlen(name2); + int len = len1 < len2 ? len1 : len2; + int ret = memcmp(name1, name2, len); + unsigned char c1, c2; + if (ret) + return ret; + c1 = name1[len]; + c2 = name2[len]; + if (!c1 && dir1) + c1 = '/'; + if (!c2 && dir2) + c2 = '/'; + ret = (c1 < c2) ? -1 : (c1 > c2) ? 1 : 0; + if (c1 && c2 && !ret) + ret = len1 - len2; + return ret; + } + + static int unpack_trees_rec(struct tree_entry_list **posns, int len, + const char *base, merge_fn_t fn, int *indpos) + { + int baselen = strlen(base); + int src_size = len + 1; + do { + int i; + char *first; + int firstdir = 0; + int pathlen; + unsigned ce_size; + struct tree_entry_list **subposns; + struct cache_entry **src; + int any_files = 0; + int any_dirs = 0; + char *cache_name; + int ce_stage; + + /* Find the first name in the input. */ + + first = NULL; + cache_name = NULL; + + /* Check the cache */ + if (merge && *indpos < active_nr) { + /* This is a bit tricky: */ + /* If the index has a subdirectory (with + * contents) as the first name, it'll get a + * filename like "foo/bar". But that's after + * "foo", so the entry in trees will get + * handled first, at which point we'll go into + * "foo", and deal with "bar" from the index, + * because the base will be "foo/". The only + * way we can actually have "foo/bar" first of + * all the things is if the trees don't + * contain "foo" at all, in which case we'll + * handle "foo/bar" without going into the + * directory, but that's fine (and will return + * an error anyway, with the added unknown + * file case. + */ + + cache_name = active_cache[*indpos]->name; + if (strlen(cache_name) > baselen && + !memcmp(cache_name, base, baselen)) { + cache_name += baselen; + first = cache_name; + } else { + cache_name = NULL; + } + } + + #if DBRT_DEBUG > 1 + if (first) + printf("index %s\n", first); + #endif + for (i = 0; i < len; i++) { + if (!posns[i] || posns[i] == &df_conflict_list) + continue; + #if DBRT_DEBUG > 1 + printf("%d %s\n", i + 1, posns[i]->name); + #endif + if (!first || entcmp(first, firstdir, + posns[i]->name, + posns[i]->directory) > 0) { + first = posns[i]->name; + firstdir = posns[i]->directory; + } + } + /* No name means we're done */ + if (!first) + return 0; + + pathlen = strlen(first); + ce_size = cache_entry_size(baselen + pathlen); + + src = xcalloc(src_size, sizeof(struct cache_entry *)); + + subposns = xcalloc(len, sizeof(struct tree_list_entry *)); + + if (cache_name && !strcmp(cache_name, first)) { + any_files = 1; + src[0] = active_cache[*indpos]; + remove_cache_entry_at(*indpos); + } + + for (i = 0; i < len; i++) { + struct cache_entry *ce; + + if (!posns[i] || + (posns[i] != &df_conflict_list && + strcmp(first, posns[i]->name))) { + continue; + } + + if (posns[i] == &df_conflict_list) { + src[i + merge] = &df_conflict_entry; + continue; + } + + if (posns[i]->directory) { + any_dirs = 1; + parse_tree(posns[i]->item.tree); + subposns[i] = posns[i]->item.tree->entries; + posns[i] = posns[i]->next; + src[i + merge] = &df_conflict_entry; + continue; + } + + if (!merge) + ce_stage = 0; + else if (i + 1 < head_idx) + ce_stage = 1; + else if (i + 1 > head_idx) + ce_stage = 3; + else + ce_stage = 2; + + ce = xcalloc(1, ce_size); + ce->ce_mode = create_ce_mode(posns[i]->mode); + ce->ce_flags = create_ce_flags(baselen + pathlen, + ce_stage); + memcpy(ce->name, base, baselen); + memcpy(ce->name + baselen, first, pathlen + 1); + + any_files = 1; + + memcpy(ce->sha1, posns[i]->item.any->sha1, 20); + src[i + merge] = ce; + subposns[i] = &df_conflict_list; + posns[i] = posns[i]->next; + } + if (any_files) { + if (merge) { + int ret; + + #if DBRT_DEBUG > 1 + printf("%s:\n", first); + for (i = 0; i < src_size; i++) { + printf(" %d ", i); + if (src[i]) + printf("%s\n", sha1_to_hex(src[i]->sha1)); + else + printf("\n"); + } + #endif + ret = fn(src); + + #if DBRT_DEBUG > 1 + printf("Added %d entries\n", ret); + #endif + *indpos += ret; + } else { + for (i = 0; i < src_size; i++) { + if (src[i]) { + add_cache_entry(src[i], ADD_CACHE_OK_TO_ADD|ADD_CACHE_SKIP_DFCHECK); + } + } + } + } + if (any_dirs) { + char *newbase = xmalloc(baselen + 2 + pathlen); + memcpy(newbase, base, baselen); + memcpy(newbase + baselen, first, pathlen); + newbase[baselen + pathlen] = '/'; + newbase[baselen + pathlen + 1] = '\0'; + if (unpack_trees_rec(subposns, len, newbase, fn, + indpos)) + return -1; + free(newbase); + } + free(subposns); + free(src); + } while (1); + } + + static void reject_merge(struct cache_entry *ce) + { + die("Entry '%s' would be overwritten by merge. Cannot merge.", + ce->name); + } + + /* Unlink the last component and attempt to remove leading + * directories, in case this unlink is the removal of the + * last entry in the directory -- empty directories are removed. + */ + static void unlink_entry(char *name) + { + char *cp, *prev; + + if (unlink(name)) + return; + prev = NULL; + while (1) { + int status; + cp = strrchr(name, '/'); + if (prev) + *prev = '/'; + if (!cp) + break; + + *cp = 0; + status = rmdir(name); + if (status) { + *cp = '/'; + break; + } + prev = cp; + } + } + + static void progress_interval(int signum) + { + progress_update = 1; + } + + static void setup_progress_signal(void) + { + struct sigaction sa; + struct itimerval v; + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = progress_interval; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + sigaction(SIGALRM, &sa, NULL); + + v.it_interval.tv_sec = 1; + v.it_interval.tv_usec = 0; + v.it_value = v.it_interval; + setitimer(ITIMER_REAL, &v, NULL); + } + + static void check_updates(struct cache_entry **src, int nr) + { + static struct checkout state = { + .base_dir = "", + .force = 1, + .quiet = 1, + .refresh_cache = 1, + }; + unsigned short mask = htons(CE_UPDATE); + unsigned last_percent = 200, cnt = 0, total = 0; + + if (update && verbose_update) { + for (total = cnt = 0; cnt < nr; cnt++) { + struct cache_entry *ce = src[cnt]; + if (!ce->ce_mode || ce->ce_flags & mask) + total++; + } + + /* Don't bother doing this for very small updates */ + if (total < 250) + total = 0; + + if (total) { + fprintf(stderr, "Checking files out...\n"); + setup_progress_signal(); + progress_update = 1; + } + cnt = 0; + } + + while (nr--) { + struct cache_entry *ce = *src++; + + if (total) { + if (!ce->ce_mode || ce->ce_flags & mask) { + unsigned percent; + cnt++; + percent = (cnt * 100) / total; + if (percent != last_percent || + progress_update) { + fprintf(stderr, "%4u%% (%u/%u) done\r", + percent, cnt, total); + last_percent = percent; + } + } + } + if (!ce->ce_mode) { + if (update) + unlink_entry(ce->name); + continue; + } + if (ce->ce_flags & mask) { + ce->ce_flags &= ~mask; + if (update) + checkout_entry(ce, &state, NULL); + } + } + if (total) { + signal(SIGALRM, SIG_IGN); + fputc('\n', stderr); + } + } + + static int unpack_trees(merge_fn_t fn) + { + int indpos = 0; + unsigned len = object_list_length(trees); + struct tree_entry_list **posns; + int i; + struct object_list *posn = trees; + merge_size = len; + + if (len) { + posns = xmalloc(len * sizeof(struct tree_entry_list *)); + for (i = 0; i < len; i++) { + 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; + } + + if (trivial_merges_only && nontrivial_merge) + die("Merge requires file-level merging"); + + check_updates(active_cache, active_nr); + return 0; + } + + static int list_tree(unsigned char *sha1) + { + struct tree *tree = parse_tree_indirect(sha1); + if (!tree) + return -1; + object_list_append(&tree->object, &trees); + return 0; + } + + static int same(struct cache_entry *a, struct cache_entry *b) + { + if (!!a != !!b) + return 0; + if (!a && !b) + return 1; + return a->ce_mode == b->ce_mode && + !memcmp(a->sha1, b->sha1, 20); + } + + + /* + * When a CE gets turned into an unmerged entry, we + * want it to be up-to-date + */ + static void verify_uptodate(struct cache_entry *ce) + { + struct stat st; + + if (index_only || reset) + return; + + if (!lstat(ce->name, &st)) { + unsigned changed = ce_match_stat(ce, &st, 1); + if (!changed) + return; + errno = 0; + } + if (reset) { + ce->ce_flags |= htons(CE_UPDATE); + return; + } + if (errno == ENOENT) + return; + die("Entry '%s' not uptodate. Cannot merge.", ce->name); + } + ++static void invalidate_ce_path(struct cache_entry *ce) ++{ ++ if (ce) ++ cache_tree_invalidate_path(active_cache_tree, ce->name); ++} ++ + /* + * We do not want to remove or overwrite a working tree file that + * is not tracked. + */ + static void verify_absent(const char *path, const char *action) + { + struct stat st; + + if (index_only || reset || !update) + return; + if (!lstat(path, &st)) + die("Untracked working tree file '%s' " + "would be %s by merge.", path, action); + } + + static int merged_entry(struct cache_entry *merge, struct cache_entry *old) + { + merge->ce_flags |= htons(CE_UPDATE); + if (old) { + /* + * See if we can re-use the old CE directly? + * That way we get the uptodate stat info. + * + * This also removes the UPDATE flag on + * a match. + */ + if (same(old, merge)) { + *merge = *old; + } else { + verify_uptodate(old); ++ invalidate_ce_path(old); + } + } - else ++ else { + verify_absent(merge->name, "overwritten"); ++ invalidate_ce_path(merge); ++ } + + merge->ce_flags &= ~htons(CE_STAGEMASK); + add_cache_entry(merge, ADD_CACHE_OK_TO_ADD); + return 1; + } + + static int deleted_entry(struct cache_entry *ce, struct cache_entry *old) + { + if (old) + verify_uptodate(old); + else + verify_absent(ce->name, "removed"); + ce->ce_mode = 0; + add_cache_entry(ce, ADD_CACHE_OK_TO_ADD); ++ invalidate_ce_path(ce); + return 1; + } + + static int keep_entry(struct cache_entry *ce) + { + add_cache_entry(ce, ADD_CACHE_OK_TO_ADD); + return 1; + } + + #if DBRT_DEBUG + static void show_stage_entry(FILE *o, + const char *label, const struct cache_entry *ce) + { + if (!ce) + fprintf(o, "%s (missing)\n", label); + else + fprintf(o, "%s%06o %s %d\t%s\n", + label, + ntohl(ce->ce_mode), + sha1_to_hex(ce->sha1), + ce_stage(ce), + ce->name); + } + #endif + + static int threeway_merge(struct cache_entry **stages) + { + struct cache_entry *index; + struct cache_entry *head; + struct cache_entry *remote = stages[head_idx + 1]; + int count; + int head_match = 0; + int remote_match = 0; + const char *path = NULL; + + int df_conflict_head = 0; + int df_conflict_remote = 0; + + int any_anc_missing = 0; + int no_anc_exists = 1; + int i; + + for (i = 1; i < head_idx; i++) { + if (!stages[i]) + any_anc_missing = 1; + else { + if (!path) + path = stages[i]->name; + no_anc_exists = 0; + } + } + + index = stages[0]; + head = stages[head_idx]; + + if (head == &df_conflict_entry) { + df_conflict_head = 1; + head = NULL; + } + + if (remote == &df_conflict_entry) { + df_conflict_remote = 1; + remote = NULL; + } + + if (!path && index) + path = index->name; + if (!path && head) + path = head->name; + if (!path && remote) + path = remote->name; + + /* First, if there's a #16 situation, note that to prevent #13 + * and #14. + */ + if (!same(remote, head)) { + for (i = 1; i < head_idx; i++) { + if (same(stages[i], head)) { + head_match = i; + } + if (same(stages[i], remote)) { + remote_match = i; + } + } + } + + /* We start with cases where the index is allowed to match + * something other than the head: #14(ALT) and #2ALT, where it + * is permitted to match the result instead. + */ + /* #14, #14ALT, #2ALT */ + if (remote && !df_conflict_head && head_match && !remote_match) { + if (index && !same(index, remote) && !same(index, head)) + reject_merge(index); + return merged_entry(remote, index); + } + /* + * If we have an entry in the index cache, then we want to + * make sure that it matches head. + */ + if (index && !same(index, head)) { + reject_merge(index); + } + + if (head) { + /* #5ALT, #15 */ + if (same(head, remote)) + return merged_entry(head, index); + /* #13, #3ALT */ + if (!df_conflict_remote && remote_match && !head_match) + return merged_entry(head, index); + } + + /* #1 */ + if (!head && !remote && any_anc_missing) + return 0; + + /* Under the new "aggressive" rule, we resolve mostly trivial + * cases that we historically had git-merge-one-file resolve. + */ + if (aggressive) { + int head_deleted = !head && !df_conflict_head; + int remote_deleted = !remote && !df_conflict_remote; + /* + * Deleted in both. + * Deleted in one and unchanged in the other. + */ + if ((head_deleted && remote_deleted) || + (head_deleted && remote && remote_match) || + (remote_deleted && head && head_match)) { + if (index) + return deleted_entry(index, index); + else if (path) + verify_absent(path, "removed"); + return 0; + } + /* + * Added in both, identically. + */ + if (no_anc_exists && head && remote && same(head, remote)) + return merged_entry(head, index); + + } + + /* Below are "no merge" cases, which require that the index be + * up-to-date to avoid the files getting overwritten with + * conflict resolution files. + */ + if (index) { + verify_uptodate(index); + } + else if (path) + verify_absent(path, "overwritten"); + + nontrivial_merge = 1; + + /* #2, #3, #4, #6, #7, #9, #11. */ + count = 0; + if (!head_match || !remote_match) { + for (i = 1; i < head_idx; i++) { + if (stages[i]) { + keep_entry(stages[i]); + count++; + break; + } + } + } + #if DBRT_DEBUG + else { + fprintf(stderr, "read-tree: warning #16 detected\n"); + show_stage_entry(stderr, "head ", stages[head_match]); + show_stage_entry(stderr, "remote ", stages[remote_match]); + } + #endif + if (head) { count += keep_entry(head); } + if (remote) { count += keep_entry(remote); } + return count; + } + + /* + * Two-way merge. + * + * The rule is to "carry forward" what is in the index without losing + * information across a "fast forward", favoring a successful merge + * over a merge failure when it makes sense. For details of the + * "carry forward" rule, please see . + * + */ + static int twoway_merge(struct cache_entry **src) + { + struct cache_entry *current = src[0]; + struct cache_entry *oldtree = src[1], *newtree = src[2]; + + if (merge_size != 2) + return error("Cannot do a twoway merge of %d trees", + merge_size); + + if (current) { + if ((!oldtree && !newtree) || /* 4 and 5 */ + (!oldtree && newtree && + same(current, newtree)) || /* 6 and 7 */ + (oldtree && newtree && + same(oldtree, newtree)) || /* 14 and 15 */ + (oldtree && newtree && + !same(oldtree, newtree) && /* 18 and 19*/ + same(current, newtree))) { + return keep_entry(current); + } + else if (oldtree && !newtree && same(current, oldtree)) { + /* 10 or 11 */ + return deleted_entry(oldtree, current); + } + else if (oldtree && newtree && + same(current, oldtree) && !same(current, newtree)) { + /* 20 or 21 */ + return merged_entry(newtree, current); + } + else { + /* all other failures */ + if (oldtree) + reject_merge(oldtree); + if (current) + reject_merge(current); + if (newtree) + reject_merge(newtree); + return -1; + } + } + else if (newtree) + return merged_entry(newtree, current); + else + return deleted_entry(oldtree, current); + } + + /* ++ * Bind merge. ++ * ++ * Keep the index entries at stage0, collapse stage1 but make sure ++ * stage0 does not have anything there. ++ */ ++static int bind_merge(struct cache_entry **src) ++{ ++ struct cache_entry *old = src[0]; ++ struct cache_entry *a = src[1]; ++ ++ if (merge_size != 1) ++ return error("Cannot do a bind merge of %d trees\n", ++ merge_size); ++ if (a && old) ++ die("Entry '%s' overlaps. Cannot bind.", a->name); ++ if (!a) ++ return keep_entry(old); ++ else ++ return merged_entry(a, NULL); ++} ++ ++/* + * One-way merge. + * + * The rule is: + * - take the stat information from stage0, take the data from stage1 + */ + static int oneway_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 oneway merge of %d trees", + merge_size); + - if (!a) ++ if (!a) { ++ invalidate_ce_path(old); + return deleted_entry(old, old); ++ } + if (old && same(old, a)) { + if (reset) { + struct stat st; + if (lstat(old->name, &st) || + ce_match_stat(old, &st, 1)) + old->ce_flags |= htons(CE_UPDATE); + } + return keep_entry(old); + } + return merged_entry(a, old); + } + + static int read_cache_unmerged(void) + { + int i, deleted; + struct cache_entry **dst; + + read_cache(); + dst = active_cache; + deleted = 0; + for (i = 0; i < active_nr; i++) { + struct cache_entry *ce = active_cache[i]; + if (ce_stage(ce)) { + deleted++; ++ invalidate_ce_path(ce); + continue; + } + if (deleted) + *dst = ce; + dst++; + } + active_nr -= deleted; + return deleted; + } + -static const char read_tree_usage[] = "git-read-tree ( | -m [--aggressive] [-u | -i] [ []])"; ++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 ( | [[-m [--aggressive] | --reset | --prefix=] [-u | -i]] [ []])"; + + static struct cache_file cache_file; + + int cmd_read_tree(int argc, const char **argv, char **envp) + { + int i, newfd, stage = 0; + unsigned char sha1[20]; + merge_fn_t fn = NULL; + + setup_git_directory(); + git_config(git_default_config); + + newfd = hold_index_file_for_update(&cache_file, get_index_file()); + if (newfd < 0) + die("unable to create new cachefile"); + + git_config(git_default_config); + + merge = 0; + reset = 0; + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + + /* "-u" means "update", meaning that a merge will update + * the working tree. + */ + if (!strcmp(arg, "-u")) { + update = 1; + continue; + } + + if (!strcmp(arg, "-v")) { + verbose_update = 1; + continue; + } + + /* "-i" means "index only", meaning that a merge will + * not even look at the working tree. + */ + if (!strcmp(arg, "-i")) { + index_only = 1; + continue; + } + ++ /* "--prefix=/" 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; + stage = 1; + read_cache_unmerged(); + continue; + } + + if (!strcmp(arg, "--trivial")) { + trivial_merges_only = 1; + continue; + } + + if (!strcmp(arg, "--aggressive")) { + aggressive = 1; + continue; + } + + /* "-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"); + stage = 1; + merge = 1; + continue; + } + + /* using -u and -i at the same time makes no sense */ + if (1 < index_only + update) + usage(read_tree_usage); + + if (get_sha1(arg, sha1)) + die("Not a valid object name %s", arg); + if (list_tree(sha1) < 0) + die("failed to unpack tree object %s", arg); + stage++; + } + 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; + } + + if (stage - 1 >= 3) + head_idx = stage - 2; + else + head_idx = 1; + } + + 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"); + return 0; + } diff --cc builtin-show-branch.c index 00000000,3af24e76..28951409 mode 000000,100644..100644 --- a/builtin-show-branch.c +++ b/builtin-show-branch.c @@@ -1,0 -1,789 +1,789 @@@ + #include + #include + #include "cache.h" + #include "commit.h" + #include "refs.h" + #include "builtin.h" + + static const char show_branch_usage[] = + "git-show-branch [--dense] [--current] [--all] [--heads] [--tags] [--topo-order] [--more=count | --list | --independent | --merge-base ] [--topics] [...]"; + + static int default_num = 0; + static int default_alloc = 0; + static const char **default_arg = NULL; + + #define UNINTERESTING 01 + + #define REV_SHIFT 2 + #define MAX_REVS 29 /* should not exceed bits_per_int - REV_SHIFT */ + + static struct commit *interesting(struct commit_list *list) + { + while (list) { + struct commit *commit = list->item; + list = list->next; + if (commit->object.flags & UNINTERESTING) + continue; + return commit; + } + return NULL; + } + + static struct commit *pop_one_commit(struct commit_list **list_p) + { + struct commit *commit; + struct commit_list *list; + list = *list_p; + commit = list->item; + *list_p = list->next; + free(list); + return commit; + } + + struct commit_name { + const char *head_name; /* which head's ancestor? */ + int generation; /* how many parents away from head_name */ + }; + + /* Name the commit as nth generation ancestor of head_name; + * we count only the first-parent relationship for naming purposes. + */ + static void name_commit(struct commit *commit, const char *head_name, int nth) + { + struct commit_name *name; + if (!commit->object.util) + commit->object.util = xmalloc(sizeof(struct commit_name)); + name = commit->object.util; + name->head_name = head_name; + name->generation = nth; + } + + /* Parent is the first parent of the commit. We may name it + * as (n+1)th generation ancestor of the same head_name as + * commit is nth generation ancestor of, if that generation + * number is better than the name it already has. + */ + static void name_parent(struct commit *commit, struct commit *parent) + { + struct commit_name *commit_name = commit->object.util; + struct commit_name *parent_name = parent->object.util; + if (!commit_name) + return; + if (!parent_name || + commit_name->generation + 1 < parent_name->generation) + name_commit(parent, commit_name->head_name, + commit_name->generation + 1); + } + + static int name_first_parent_chain(struct commit *c) + { + int i = 0; + while (c) { + struct commit *p; + if (!c->object.util) + break; + if (!c->parents) + break; + p = c->parents->item; + if (!p->object.util) { + name_parent(c, p); + i++; + } + c = p; + } + return i; + } + + static void name_commits(struct commit_list *list, + struct commit **rev, + char **ref_name, + int num_rev) + { + struct commit_list *cl; + struct commit *c; + int i; + + /* First give names to the given heads */ + for (cl = list; cl; cl = cl->next) { + c = cl->item; + if (c->object.util) + continue; + for (i = 0; i < num_rev; i++) { + if (rev[i] == c) { + name_commit(c, ref_name[i], 0); + break; + } + } + } + + /* Then commits on the first parent ancestry chain */ + do { + i = 0; + for (cl = list; cl; cl = cl->next) { + i += name_first_parent_chain(cl->item); + } + } while (i); + + /* Finally, any unnamed commits */ + do { + i = 0; + for (cl = list; cl; cl = cl->next) { + struct commit_list *parents; + struct commit_name *n; + int nth; + c = cl->item; + if (!c->object.util) + continue; + n = c->object.util; + parents = c->parents; + nth = 0; + while (parents) { + struct commit *p = parents->item; + char newname[1000], *en; + parents = parents->next; + nth++; + if (p->object.util) + continue; + en = newname; + switch (n->generation) { + case 0: + en += sprintf(en, "%s", n->head_name); + break; + case 1: + en += sprintf(en, "%s^", n->head_name); + break; + default: + en += sprintf(en, "%s~%d", + n->head_name, n->generation); + break; + } + if (nth == 1) + en += sprintf(en, "^"); + else + en += sprintf(en, "^%d", nth); + name_commit(p, strdup(newname), 0); + i++; + name_first_parent_chain(p); + } + } + } while (i); + } + + static int mark_seen(struct commit *commit, struct commit_list **seen_p) + { + if (!commit->object.flags) { + insert_by_date(commit, seen_p); + return 1; + } + return 0; + } + + static void join_revs(struct commit_list **list_p, + struct commit_list **seen_p, + int num_rev, int extra) + { + int all_mask = ((1u << (REV_SHIFT + num_rev)) - 1); + int all_revs = all_mask & ~((1u << REV_SHIFT) - 1); + + while (*list_p) { + struct commit_list *parents; + int still_interesting = !!interesting(*list_p); + struct commit *commit = pop_one_commit(list_p); + int flags = commit->object.flags & all_mask; + + if (!still_interesting && extra <= 0) + break; + + mark_seen(commit, seen_p); + if ((flags & all_revs) == all_revs) + flags |= UNINTERESTING; + parents = commit->parents; + + while (parents) { + struct commit *p = parents->item; + int this_flag = p->object.flags; + parents = parents->next; + if ((this_flag & flags) == flags) + continue; + if (!p->object.parsed) + parse_commit(p); + if (mark_seen(p, seen_p) && !still_interesting) + extra--; + p->object.flags |= flags; + insert_by_date(p, list_p); + } + } + + /* + * Postprocess to complete well-poisoning. + * + * At this point we have all the commits we have seen in + * seen_p list (which happens to be sorted chronologically but + * it does not really matter). Mark anything that can be + * reached from uninteresting commits not interesting. + */ + for (;;) { + int changed = 0; + struct commit_list *s; + for (s = *seen_p; s; s = s->next) { + struct commit *c = s->item; + struct commit_list *parents; + + if (((c->object.flags & all_revs) != all_revs) && + !(c->object.flags & UNINTERESTING)) + continue; + + /* The current commit is either a merge base or + * already uninteresting one. Mark its parents + * as uninteresting commits _only_ if they are + * already parsed. No reason to find new ones + * here. + */ + parents = c->parents; + while (parents) { + struct commit *p = parents->item; + parents = parents->next; + if (!(p->object.flags & UNINTERESTING)) { + p->object.flags |= UNINTERESTING; + changed = 1; + } + } + } + if (!changed) + break; + } + } + + static void show_one_commit(struct commit *commit, int no_name) + { + char pretty[256], *cp; + struct commit_name *name = commit->object.util; + if (commit->object.parsed) + pretty_print_commit(CMIT_FMT_ONELINE, commit, ~0, - pretty, sizeof(pretty), 0); ++ pretty, sizeof(pretty), 0, NULL, NULL); + else + strcpy(pretty, "(unavailable)"); + if (!strncmp(pretty, "[PATCH] ", 8)) + cp = pretty + 8; + else + cp = pretty; + + if (!no_name) { + if (name && name->head_name) { + printf("[%s", name->head_name); + if (name->generation) { + if (name->generation == 1) + printf("^"); + else + printf("~%d", name->generation); + } + printf("] "); + } + else + printf("[%s] ", + find_unique_abbrev(commit->object.sha1, 7)); + } + puts(cp); + } + + static char *ref_name[MAX_REVS + 1]; + static int ref_name_cnt; + + static const char *find_digit_prefix(const char *s, int *v) + { + const char *p; + int ver; + char ch; + + for (p = s, ver = 0; + '0' <= (ch = *p) && ch <= '9'; + p++) + ver = ver * 10 + ch - '0'; + *v = ver; + return p; + } + + + static int version_cmp(const char *a, const char *b) + { + while (1) { + int va, vb; + + a = find_digit_prefix(a, &va); + b = find_digit_prefix(b, &vb); + if (va != vb) + return va - vb; + + while (1) { + int ca = *a; + int cb = *b; + if ('0' <= ca && ca <= '9') + ca = 0; + if ('0' <= cb && cb <= '9') + cb = 0; + if (ca != cb) + return ca - cb; + if (!ca) + break; + a++; + b++; + } + if (!*a && !*b) + return 0; + } + } + + static int compare_ref_name(const void *a_, const void *b_) + { + const char * const*a = a_, * const*b = b_; + return version_cmp(*a, *b); + } + + static void sort_ref_range(int bottom, int top) + { + qsort(ref_name + bottom, top - bottom, sizeof(ref_name[0]), + compare_ref_name); + } + + static int append_ref(const char *refname, const unsigned char *sha1) + { + struct commit *commit = lookup_commit_reference_gently(sha1, 1); + int i; + + if (!commit) + return 0; + /* Avoid adding the same thing twice */ + for (i = 0; i < ref_name_cnt; i++) + if (!strcmp(refname, ref_name[i])) + return 0; + + if (MAX_REVS <= ref_name_cnt) { + fprintf(stderr, "warning: ignoring %s; " + "cannot handle more than %d refs\n", + refname, MAX_REVS); + return 0; + } + ref_name[ref_name_cnt++] = strdup(refname); + ref_name[ref_name_cnt] = NULL; + return 0; + } + + static int append_head_ref(const char *refname, const unsigned char *sha1) + { + unsigned char tmp[20]; + int ofs = 11; + if (strncmp(refname, "refs/heads/", ofs)) + return 0; + /* If both heads/foo and tags/foo exists, get_sha1 would + * get confused. + */ + if (get_sha1(refname + ofs, tmp) || memcmp(tmp, sha1, 20)) + ofs = 5; + return append_ref(refname + ofs, sha1); + } + + static int append_tag_ref(const char *refname, const unsigned char *sha1) + { + if (strncmp(refname, "refs/tags/", 10)) + return 0; + return append_ref(refname + 5, sha1); + } + + static const char *match_ref_pattern = NULL; + static int match_ref_slash = 0; + static int count_slash(const char *s) + { + int cnt = 0; + while (*s) + if (*s++ == '/') + cnt++; + return cnt; + } + + static int append_matching_ref(const char *refname, const unsigned char *sha1) + { + /* we want to allow pattern hold/ to show all + * branches under refs/heads/hold/, and v0.99.9? to show + * refs/tags/v0.99.9a and friends. + */ + const char *tail; + int slash = count_slash(refname); + for (tail = refname; *tail && match_ref_slash < slash; ) + if (*tail++ == '/') + slash--; + if (!*tail) + return 0; + if (fnmatch(match_ref_pattern, tail, 0)) + return 0; + if (!strncmp("refs/heads/", refname, 11)) + return append_head_ref(refname, sha1); + if (!strncmp("refs/tags/", refname, 10)) + return append_tag_ref(refname, sha1); + return append_ref(refname, sha1); + } + + static void snarf_refs(int head, int tag) + { + if (head) { + int orig_cnt = ref_name_cnt; + for_each_ref(append_head_ref); + sort_ref_range(orig_cnt, ref_name_cnt); + } + if (tag) { + int orig_cnt = ref_name_cnt; + for_each_ref(append_tag_ref); + sort_ref_range(orig_cnt, ref_name_cnt); + } + } + + static int rev_is_head(char *head_path, int headlen, char *name, + unsigned char *head_sha1, unsigned char *sha1) + { + int namelen; + if ((!head_path[0]) || + (head_sha1 && sha1 && memcmp(head_sha1, sha1, 20))) + return 0; + namelen = strlen(name); + if ((headlen < namelen) || + memcmp(head_path + headlen - namelen, name, namelen)) + return 0; + if (headlen == namelen || + head_path[headlen - namelen - 1] == '/') + return 1; + return 0; + } + + static int show_merge_base(struct commit_list *seen, int num_rev) + { + int all_mask = ((1u << (REV_SHIFT + num_rev)) - 1); + int all_revs = all_mask & ~((1u << REV_SHIFT) - 1); + int exit_status = 1; + + while (seen) { + struct commit *commit = pop_one_commit(&seen); + int flags = commit->object.flags & all_mask; + if (!(flags & UNINTERESTING) && + ((flags & all_revs) == all_revs)) { + puts(sha1_to_hex(commit->object.sha1)); + exit_status = 0; + commit->object.flags |= UNINTERESTING; + } + } + return exit_status; + } + + static int show_independent(struct commit **rev, + int num_rev, + char **ref_name, + unsigned int *rev_mask) + { + int i; + + for (i = 0; i < num_rev; i++) { + struct commit *commit = rev[i]; + unsigned int flag = rev_mask[i]; + + if (commit->object.flags == flag) + puts(sha1_to_hex(commit->object.sha1)); + commit->object.flags |= UNINTERESTING; + } + return 0; + } + + static void append_one_rev(const char *av) + { + unsigned char revkey[20]; + if (!get_sha1(av, revkey)) { + append_ref(av, revkey); + return; + } + if (strchr(av, '*') || strchr(av, '?') || strchr(av, '[')) { + /* glob style match */ + int saved_matches = ref_name_cnt; + match_ref_pattern = av; + match_ref_slash = count_slash(av); + for_each_ref(append_matching_ref); + if (saved_matches == ref_name_cnt && + ref_name_cnt < MAX_REVS) + error("no matching refs with %s", av); + if (saved_matches + 1 < ref_name_cnt) + sort_ref_range(saved_matches, ref_name_cnt); + return; + } + die("bad sha1 reference %s", av); + } + + static int git_show_branch_config(const char *var, const char *value) + { + if (!strcmp(var, "showbranch.default")) { + if (default_alloc <= default_num + 1) { + default_alloc = default_alloc * 3 / 2 + 20; + default_arg = xrealloc(default_arg, sizeof *default_arg * default_alloc); + } + default_arg[default_num++] = strdup(value); + default_arg[default_num] = NULL; + return 0; + } + + return git_default_config(var, value); + } + + static int omit_in_dense(struct commit *commit, struct commit **rev, int n) + { + /* If the commit is tip of the named branches, do not + * omit it. + * Otherwise, if it is a merge that is reachable from only one + * tip, it is not that interesting. + */ + int i, flag, count; + for (i = 0; i < n; i++) + if (rev[i] == commit) + return 0; + flag = commit->object.flags; + for (i = count = 0; i < n; i++) { + if (flag & (1u << (i + REV_SHIFT))) + count++; + } + if (count == 1) + return 1; + return 0; + } + + int cmd_show_branch(int ac, const char **av, char **envp) + { + struct commit *rev[MAX_REVS], *commit; + struct commit_list *list = NULL, *seen = NULL; + unsigned int rev_mask[MAX_REVS]; + int num_rev, i, extra = 0; + int all_heads = 0, all_tags = 0; + int all_mask, all_revs; + int lifo = 1; + char head_path[128]; + const char *head_path_p; + int head_path_len; + unsigned char head_sha1[20]; + int merge_base = 0; + int independent = 0; + int no_name = 0; + int sha1_name = 0; + int shown_merge_point = 0; + int with_current_branch = 0; + int head_at = -1; + int topics = 0; + int dense = 1; + + setup_git_directory(); + git_config(git_show_branch_config); + + /* If nothing is specified, try the default first */ + if (ac == 1 && default_num) { + ac = default_num + 1; + av = default_arg - 1; /* ick; we would not address av[0] */ + } + + while (1 < ac && av[1][0] == '-') { + const char *arg = av[1]; + if (!strcmp(arg, "--")) { + ac--; av++; + break; + } + else if (!strcmp(arg, "--all")) + all_heads = all_tags = 1; + else if (!strcmp(arg, "--heads")) + all_heads = 1; + else if (!strcmp(arg, "--tags")) + all_tags = 1; + else if (!strcmp(arg, "--more")) + extra = 1; + else if (!strcmp(arg, "--list")) + extra = -1; + else if (!strcmp(arg, "--no-name")) + no_name = 1; + else if (!strcmp(arg, "--current")) + with_current_branch = 1; + else if (!strcmp(arg, "--sha1-name")) + sha1_name = 1; + else if (!strncmp(arg, "--more=", 7)) + extra = atoi(arg + 7); + else if (!strcmp(arg, "--merge-base")) + merge_base = 1; + else if (!strcmp(arg, "--independent")) + independent = 1; + else if (!strcmp(arg, "--topo-order")) + lifo = 1; + else if (!strcmp(arg, "--topics")) + topics = 1; + else if (!strcmp(arg, "--sparse")) + dense = 0; + else if (!strcmp(arg, "--date-order")) + lifo = 0; + else + usage(show_branch_usage); + ac--; av++; + } + ac--; av++; + + /* Only one of these is allowed */ + if (1 < independent + merge_base + (extra != 0)) + usage(show_branch_usage); + + /* If nothing is specified, show all branches by default */ + if (ac + all_heads + all_tags == 0) + all_heads = 1; + + if (all_heads + all_tags) + snarf_refs(all_heads, all_tags); + while (0 < ac) { + append_one_rev(*av); + ac--; av++; + } + + head_path_p = resolve_ref(git_path("HEAD"), head_sha1, 1); + if (head_path_p) { + head_path_len = strlen(head_path_p); + memcpy(head_path, head_path_p, head_path_len + 1); + } + else { + head_path_len = 0; + head_path[0] = 0; + } + + if (with_current_branch && head_path_p) { + int has_head = 0; + for (i = 0; !has_head && i < ref_name_cnt; i++) { + /* We are only interested in adding the branch + * HEAD points at. + */ + if (rev_is_head(head_path, + head_path_len, + ref_name[i], + head_sha1, NULL)) + has_head++; + } + if (!has_head) { + int pfxlen = strlen(git_path("refs/heads/")); + append_one_rev(head_path + pfxlen); + } + } + + if (!ref_name_cnt) { + fprintf(stderr, "No revs to be shown.\n"); + exit(0); + } + + for (num_rev = 0; ref_name[num_rev]; num_rev++) { + unsigned char revkey[20]; + unsigned int flag = 1u << (num_rev + REV_SHIFT); + + if (MAX_REVS <= num_rev) + die("cannot handle more than %d revs.", MAX_REVS); + if (get_sha1(ref_name[num_rev], revkey)) + die("'%s' is not a valid ref.", ref_name[num_rev]); + commit = lookup_commit_reference(revkey); + if (!commit) + die("cannot find commit %s (%s)", + ref_name[num_rev], revkey); + parse_commit(commit); + mark_seen(commit, &seen); + + /* rev#0 uses bit REV_SHIFT, rev#1 uses bit REV_SHIFT+1, + * and so on. REV_SHIFT bits from bit 0 are used for + * internal bookkeeping. + */ + commit->object.flags |= flag; + if (commit->object.flags == flag) + insert_by_date(commit, &list); + rev[num_rev] = commit; + } + for (i = 0; i < num_rev; i++) + rev_mask[i] = rev[i]->object.flags; + + if (0 <= extra) + join_revs(&list, &seen, num_rev, extra); + + if (merge_base) + return show_merge_base(seen, num_rev); + + if (independent) + return show_independent(rev, num_rev, ref_name, rev_mask); + + /* Show list; --more=-1 means list-only */ + if (1 < num_rev || extra < 0) { + for (i = 0; i < num_rev; i++) { + int j; + int is_head = rev_is_head(head_path, + head_path_len, + ref_name[i], + head_sha1, + rev[i]->object.sha1); + if (extra < 0) + printf("%c [%s] ", + is_head ? '*' : ' ', ref_name[i]); + else { + for (j = 0; j < i; j++) + putchar(' '); + printf("%c [%s] ", + is_head ? '*' : '!', ref_name[i]); + } + /* header lines never need name */ + show_one_commit(rev[i], 1); + if (is_head) + head_at = i; + } + if (0 <= extra) { + for (i = 0; i < num_rev; i++) + putchar('-'); + putchar('\n'); + } + } + if (extra < 0) + exit(0); + + /* Sort topologically */ + sort_in_topological_order(&seen, lifo); + + /* Give names to commits */ + if (!sha1_name && !no_name) + name_commits(seen, rev, ref_name, num_rev); + + all_mask = ((1u << (REV_SHIFT + num_rev)) - 1); + all_revs = all_mask & ~((1u << REV_SHIFT) - 1); + + while (seen) { + struct commit *commit = pop_one_commit(&seen); + int this_flag = commit->object.flags; + int is_merge_point = ((this_flag & all_revs) == all_revs); + + shown_merge_point |= is_merge_point; + + if (1 < num_rev) { + int is_merge = !!(commit->parents && + commit->parents->next); + if (topics && + !is_merge_point && + (this_flag & (1u << REV_SHIFT))) + continue; + if (dense && is_merge && + omit_in_dense(commit, rev, num_rev)) + continue; + for (i = 0; i < num_rev; i++) { + int mark; + if (!(this_flag & (1u << (i + REV_SHIFT)))) + mark = ' '; + else if (is_merge) + mark = '-'; + else if (i == head_at) + mark = '*'; + else + mark = '+'; + putchar(mark); + } + putchar(' '); + } + show_one_commit(commit, no_name); + + if (shown_merge_point && --extra < 0) + break; + } + return 0; + }