author | Junio C Hamano <junkio@cox.net> | |
Tue, 23 May 2006 21:52:45 +0000 (14:52 -0700) | ||
committer | Junio C Hamano <junkio@cox.net> | |
Tue, 23 May 2006 21:52:45 +0000 (14:52 -0700) |
* 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.
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.
1 | 2 | |||
---|---|---|---|---|
Makefile | patch | | diff1 | | diff2 | | blob | history |
builtin-apply.c | patch | | | | diff2 | | blob | history |
builtin-ls-files.c | patch | | | | diff2 | | blob | history |
builtin-read-tree.c | patch | | | | diff2 | | blob | history |
builtin-show-branch.c | patch | | | | diff2 | | blob | history |
builtin.h | patch | | diff1 | | diff2 | | blob | history |
git.c | patch | | diff1 | | diff2 | | blob | history |
diff --cc Makefile
index 677bc13344ca9141a832baba50ded4d1cf9e56c3,14873fa3591516d9aa2a8e3a4bf43341deaa8da0..355e0ccece3c5b833742c91a517e90bcc8cd808d
+++ b/Makefile
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)
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 0000000000000000000000000000000000000000,4056b9d67bc1c4e50563ceaa0ee68e94873a8e65..f16c753bee7c1a1dda1952282f448d2d6a4099e6
mode 000000,100644..100644
mode 000000,100644..100644
--- /dev/null
--- 2/builtin-apply.c
+++ b/builtin-apply.c
- create_one_file(path, mode, buf, size);
+ /*
+ * apply.c
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ *
+ * This applies patches on top of some (arbitrary) version of the SCM.
+ *
+ */
+ #include <fnmatch.h>
+ #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=<nowarn|warn|error|error-all|strip>] <patch>...";
+
+ 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);
+ 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, "<stdin>");
+ 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, "<stdin>");
+ 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 0000000000000000000000000000000000000000,3a0c5f215003bda5cfa2109953cd556765f6ea2c..8dae9f70e2f9079d521fbc80e317addf41346d0f
mode 000000,100644..100644
mode 000000,100644..100644
--- /dev/null
--- 2/builtin-ls-files.c
+++ b/builtin-ls-files.c
-#include <dirent.h>
+ /*
+ * 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
+ */
-static int show_ignored = 0;
+ #include <fnmatch.h>
+
+ #include "cache.h"
+ #include "quote.h"
++#include "dir.h"
+ #include "builtin.h"
+
+ static int abbrev = 0;
+ static int show_deleted = 0;
+ static int show_cached = 0;
+ static int show_others = 0;
-static int show_other_directories = 0;
-static int hide_empty_directories = 0;
+ static int show_stage = 0;
+ static int show_unmerged = 0;
+ static int show_modified = 0;
+ static int show_killed = 0;
-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);
-}
+ 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 void show_dir_entry(const char *tag, struct nond_on_fs *ent)
+
+ /*
+ * 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_other_files(void)
++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);
+ }
+
- for (i = 0; i < nr_dir; i++) {
++static void show_other_files(struct dir_struct *dir)
+ {
+ int i;
- struct nond_on_fs *ent = 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.
+ */
-static void show_killed_files(void)
++ 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);
+ }
+ }
+
- for (i = 0; i < nr_dir; i++) {
- struct nond_on_fs *ent = dir[i];
++static void show_killed_files(struct dir_struct *dir)
+ {
+ int i;
- show_dir_entry(tag_killed, 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)
-static void show_files(void)
++ 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);
+ }
+ }
+
- if (baselen) {
++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 (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);
++ if (baselen)
+ path = base = prefix;
- show_other_files();
++ read_directory(dir, path, base, baselen);
+ if (show_others)
- show_killed_files();
++ show_other_files(dir);
+ if (show_killed)
- if (excluded(ce->name) != show_ignored)
++ 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;
- show_ignored = 1;
++ 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=<pattern>] [--exclude-from=<file>] "
+ "[ --exclude-per-directory=<filename> ] [--full-name] [--abbrev] "
+ "[--] [<file>]*";
+
+ 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_other_directories = 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")) {
- hide_empty_directories = 1;
++ dir.show_other_directories = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--no-empty-directory")) {
- add_exclude(argv[++i], "", 0, &exclude_list[EXC_CMDL]);
++ 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(arg+10, "", 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_excludes_from_file(argv[++i]);
++ 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(arg+15);
++ add_excludes_from_file(&dir, argv[++i]);
+ continue;
+ }
+ if (!strncmp(arg, "--exclude-from=", 15)) {
+ exc_given = 1;
- exclude_per_dir = arg + 24;
++ add_excludes_from_file(&dir, arg+15);
+ continue;
+ }
+ if (!strncmp(arg, "--exclude-per-directory=", 24)) {
+ exc_given = 1;
- if (show_ignored && !exc_given) {
++ 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);
+ }
+
- show_files();
++ 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(&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 0000000000000000000000000000000000000000,ec40d013c4d8508bfd10d75e2dab3090dea64a45..629d15163951b30a7a346aa4aafbd7abef25fd06
mode 000000,100644..100644
mode 000000,100644..100644
--- /dev/null
--- 2/builtin-read-tree.c
+++ b/builtin-read-tree.c
- if (unpack_trees_rec(posns, len, "", fn, &indpos))
+ /*
+ * 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 <sys/time.h>
+ #include <signal.h>
+ #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;
+ }
- else
++ 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);
+ }
+ }
- if (!a)
++ 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 <Documentation/git-read-tree.txt>.
+ *
+ */
+ 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);
+
-static const char read_tree_usage[] = "git-read-tree (<sha> | -m [--aggressive] [-u | -i] <sha1> [<sha2> [<sha3>]])";
++ 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;
+ }
+
- if (stage || merge)
++static void prime_cache_tree_rec(struct cache_tree *it, struct tree *tree)
++{
++ struct tree_entry_list *ent;
++ int cnt;
++
++ memcpy(it->sha1, tree->object.sha1, 20);
++ for (cnt = 0, ent = tree->entries; ent; ent = ent->next) {
++ if (!ent->directory)
++ cnt++;
++ else {
++ struct cache_tree_sub *sub;
++ struct tree *subtree = (struct tree *)ent->item.tree;
++ if (!subtree->object.parsed)
++ parse_tree(subtree);
++ sub = cache_tree_sub(it, ent->name);
++ sub->cache_tree = cache_tree();
++ prime_cache_tree_rec(sub->cache_tree, subtree);
++ cnt += sub->cache_tree->entry_count;
++ }
++ }
++ it->entry_count = cnt;
++}
++
++static void prime_cache_tree(void)
++{
++ struct tree *tree = (struct tree *)trees->item;
++ if (!tree)
++ return;
++ active_cache_tree = cache_tree();
++ prime_cache_tree_rec(active_cache_tree, tree);
++
++}
++
++static const char read_tree_usage[] = "git-read-tree (<sha> | [[-m [--aggressive] | --reset | --prefix=<prefix>] [-u | -i]] <sha1> [<sha2> [<sha3>]])";
+
+ static struct cache_file cache_file;
+
+ 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=<subdirectory>/" means keep the current index
++ * entries and put the entries from the tree under the
++ * given subdirectory.
++ */
++ if (!strncmp(arg, "--prefix=", 9)) {
++ if (stage || merge || prefix)
++ usage(read_tree_usage);
++ prefix = arg + 9;
++ merge = 1;
++ stage = 1;
++ if (read_cache_unmerged())
++ die("you need to resolve your current index first");
++ continue;
++ }
++
+ /* This differs from "-m" in that we'll silently ignore unmerged entries */
+ if (!strcmp(arg, "--reset")) {
- if (stage || merge)
++ if (stage || merge || prefix)
+ usage(read_tree_usage);
+ reset = 1;
+ merge = 1;
+ 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")) {
- fn = oneway_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 = threeway_merge;
- break;
++ fn = prefix ? bind_merge : oneway_merge;
+ break;
+ case 2:
+ fn = twoway_merge;
+ break;
+ case 3:
+ 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 0000000000000000000000000000000000000000,3af24e767baae390484d2c5acec9aa25e09d9a91..2895140915cb603e2c5f971d0356f2e48106bc0e
mode 000000,100644..100644
mode 000000,100644..100644
--- /dev/null
+++ b/builtin-show-branch.c
- pretty, sizeof(pretty), 0);
+ #include <stdlib.h>
+ #include <fnmatch.h>
+ #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] [<refs>...]";
+
+ 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, 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/<asterisk> 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;
+ }
diff --cc builtin.h
Simple merge
diff --cc git.c
Simple merge