From 453ec4bdf403c2e89892266a0a660c21680d3f9d Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Tue, 16 May 2006 19:02:14 -0700 Subject: libify git-ls-files directory traversal This moves the core directory traversal and filename exclusion logic into the general git library, making it available for other users directly. If we ever want to do "git commit" or "git add" as a built-in (and we do), we want to be able to handle most of git-ls-files as a library. NOTE! Not all of git-ls-files is libified by this. The index matching and pathspec prefix calculation is still in ls-files.c, but this is a big part of it. Signed-off-by: Linus Torvalds Signed-off-by: Junio C Hamano --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'Makefile') diff --git a/Makefile b/Makefile index 9ba608c805..f43ac63c93 100644 --- a/Makefile +++ b/Makefile @@ -199,7 +199,7 @@ LIB_H = \ blob.h cache.h commit.h csum-file.h delta.h \ diff.h object.h pack.h pkt-line.h quote.h refs.h \ run-command.h strbuf.h tag.h tree.h git-compat-util.h revision.h \ - tree-walk.h log-tree.h + tree-walk.h log-tree.h dir.h DIFF_OBJS = \ diff.o diff-lib.o diffcore-break.o diffcore-order.o \ @@ -210,7 +210,7 @@ LIB_OBJS = \ blob.o commit.o connect.o csum-file.o base85.o \ date.o diff-delta.o entry.o exec_cmd.o ident.o index.o \ object.o pack-check.o patch-delta.o path.o pkt-line.o \ - quote.o read-cache.o refs.o run-command.o \ + quote.o read-cache.o refs.o run-command.o dir.o \ server-info.o setup.o sha1_file.o sha1_name.o strbuf.o \ tag.o tree.o usage.o config.o environment.o ctype.o copy.o \ fetch-clone.o revision.o pager.o tree-walk.o xdiff-interface.o \ -- cgit v1.3 From 0d78153952e70c21e94dc6b7eefcb2ac5337a902 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Wed, 17 May 2006 09:33:32 -0700 Subject: Do "git add" as a builtin First try. Let's see how well this works. In many ways, the hard parts of "git commit" are not so different from this, and a builtin commit would share a lot of the code, I think. Signed-off-by: Linus Torvalds Signed-off-by: Junio C Hamano --- Makefile | 2 +- builtin-add.c | 228 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ builtin.h | 1 + git.c | 1 + 4 files changed, 231 insertions(+), 1 deletion(-) create mode 100644 builtin-add.c (limited to 'Makefile') diff --git a/Makefile b/Makefile index f43ac63c93..e6f7794ad0 100644 --- a/Makefile +++ b/Makefile @@ -218,7 +218,7 @@ LIB_OBJS = \ BUILTIN_OBJS = \ builtin-log.o builtin-help.o builtin-count.o builtin-diff.o builtin-push.o \ - builtin-grep.o + builtin-grep.o builtin-add.o GITLIBS = $(LIB_FILE) $(XDIFF_LIB) LIBS = $(GITLIBS) -lz diff --git a/builtin-add.c b/builtin-add.c new file mode 100644 index 0000000000..d225988e53 --- /dev/null +++ b/builtin-add.c @@ -0,0 +1,228 @@ +/* + * "git add" builtin command + * + * Copyright (C) 2006 Linus Torvalds + */ +#include + +#include "cache.h" +#include "builtin.h" +#include "dir.h" + +static const char builtin_add_usage[] = +"git-add [-n] [-v] ..."; + +static int common_prefix(const char **pathspec) +{ + const char *path, *slash, *next; + int prefix; + + if (!pathspec) + return 0; + + path = *pathspec; + slash = strrchr(path, '/'); + if (!slash) + return 0; + + prefix = slash - path + 1; + while ((next = *++pathspec) != NULL) { + int len = strlen(next); + if (len >= prefix && !memcmp(path, next, len)) + continue; + for (;;) { + if (!len) + return 0; + if (next[--len] != '/') + continue; + if (memcmp(path, next, len+1)) + continue; + prefix = len + 1; + break; + } + } + return prefix; +} + +static int match(const char **pathspec, const char *name, int namelen, int prefix) +{ + const char *match; + + name += prefix; + namelen -= prefix; + + while ((match = *pathspec++) != NULL) { + int matchlen; + + match += prefix; + matchlen = strlen(match); + if (!matchlen) + return 1; + if (!strncmp(match, name, matchlen)) { + if (match[matchlen-1] == '/') + return 1; + switch (name[matchlen]) { + case '/': case '\0': + return 1; + } + } + if (!fnmatch(match, name, 0)) + return 1; + } + return 0; +} + +static void prune_directory(struct dir_struct *dir, const char **pathspec, int prefix) +{ + int i; + struct dir_entry **src, **dst; + + src = dst = dir->entries; + i = dir->nr; + while (--i >= 0) { + struct dir_entry *entry = *src++; + if (!match(pathspec, entry->name, entry->len, prefix)) { + free(entry); + continue; + } + *dst++ = entry; + } + dir->nr = dst - dir->entries; +} + +static void fill_directory(struct dir_struct *dir, const char **pathspec) +{ + const char *path, *base; + int baselen; + + /* Set up the default git porcelain excludes */ + memset(dir, 0, sizeof(*dir)); + dir->exclude_per_dir = ".gitignore"; + path = git_path("info/exclude"); + if (!access(path, R_OK)) + add_excludes_from_file(dir, path); + + /* + * Calculate common prefix for the pathspec, and + * use that to optimize the directory walk + */ + baselen = common_prefix(pathspec); + path = "."; + base = ""; + if (baselen) { + char *common = xmalloc(baselen + 1); + common = xmalloc(baselen + 1); + memcpy(common, *pathspec, baselen); + common[baselen] = 0; + path = base = common; + } + + /* Read the directory and prune it */ + read_directory(dir, path, base, baselen); + if (pathspec) + prune_directory(dir, pathspec, baselen); +} + +static int add_file_to_index(const char *path, int verbose) +{ + int size, namelen; + struct stat st; + struct cache_entry *ce; + + if (lstat(path, &st)) + die("%s: unable to stat (%s)", path, strerror(errno)); + + if (!S_ISREG(st.st_mode) && !S_ISLNK(st.st_mode)) + die("%s: can only add regular files or symbolic links", path); + + namelen = strlen(path); + size = cache_entry_size(namelen); + ce = xcalloc(1, size); + memcpy(ce->name, path, namelen); + ce->ce_flags = htons(namelen); + fill_stat_cache_info(ce, &st); + + ce->ce_mode = create_ce_mode(st.st_mode); + if (!trust_executable_bit) { + /* If there is an existing entry, pick the mode bits + * from it. + */ + int pos = cache_name_pos(path, namelen); + if (pos >= 0) + ce->ce_mode = active_cache[pos]->ce_mode; + } + + if (index_path(ce->sha1, path, &st, 1)) + die("unable to index file %s", path); + if (add_cache_entry(ce, ADD_CACHE_OK_TO_ADD)) + die("unable to add %s to index",path); + if (verbose) + printf("add '%s'\n", path); + return 0; +} + +static struct cache_file cache_file; + +int cmd_add(int argc, const char **argv, char **envp) +{ + int i, newfd; + int verbose = 0, show_only = 0; + const char *prefix = setup_git_directory(); + const char **pathspec; + struct dir_struct dir; + + git_config(git_default_config); + + newfd = hold_index_file_for_update(&cache_file, get_index_file()); + if (newfd < 0) + die("unable to create new cachefile"); + + if (read_cache() < 0) + die("index file corrupt"); + + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + + if (arg[0] != '-') + break; + if (!strcmp(arg, "--")) { + i++; + break; + } + if (!strcmp(arg, "-n")) { + show_only = 1; + continue; + } + if (!strcmp(arg, "-v")) { + verbose = 1; + continue; + } + die(builtin_add_usage); + } + git_config(git_default_config); + pathspec = get_pathspec(prefix, argv + i); + + fill_directory(&dir, pathspec); + + if (show_only) { + const char *sep = "", *eof = ""; + for (i = 0; i < dir.nr; i++) { + printf("%s%s", sep, dir.entries[i]->name); + sep = " "; + eof = "\n"; + } + fputs(eof, stdout); + return 0; + } + + for (i = 0; i < dir.nr; i++) + add_file_to_index(dir.entries[i]->name, verbose); + + if (active_cache_changed) { + if (write_cache(newfd, active_cache, active_nr) || + commit_index_file(&cache_file)) + die("Unable to write new index file"); + } + + return 0; +} diff --git a/builtin.h b/builtin.h index 7744f7d2f6..1b77f4b0ca 100644 --- a/builtin.h +++ b/builtin.h @@ -24,5 +24,6 @@ extern int cmd_count_objects(int argc, const char **argv, char **envp); extern int cmd_push(int argc, const char **argv, char **envp); extern int cmd_grep(int argc, const char **argv, char **envp); +extern int cmd_add(int argc, const char **argv, char **envp); #endif diff --git a/git.c b/git.c index a94d9ee5a7..fac46af057 100644 --- a/git.c +++ b/git.c @@ -50,6 +50,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "count-objects", cmd_count_objects }, { "diff", cmd_diff }, { "grep", cmd_grep }, + { "add", cmd_add }, }; int i; -- cgit v1.3 From c699f9b924d763b762df932769d91e3d053634a8 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Wed, 17 May 2006 21:21:04 -0700 Subject: Remove old "git-add.sh" remnants Repeat after me: "It's now a built-in" Signed-off-by: Linus Torvalds Signed-off-by: Junio C Hamano --- Makefile | 4 ++-- git-add.sh | 56 -------------------------------------------------------- 2 files changed, 2 insertions(+), 58 deletions(-) delete mode 100755 git-add.sh (limited to 'Makefile') diff --git a/Makefile b/Makefile index e6f7794ad0..48e2a9cb22 100644 --- a/Makefile +++ b/Makefile @@ -113,7 +113,7 @@ SPARSE_FLAGS = -D__BIG_ENDIAN__ -D__powerpc__ ### --- END CONFIGURATION SECTION --- SCRIPT_SH = \ - git-add.sh git-bisect.sh git-branch.sh git-checkout.sh \ + git-bisect.sh git-branch.sh git-checkout.sh \ git-cherry.sh git-clean.sh git-clone.sh git-commit.sh \ git-fetch.sh \ git-format-patch.sh git-ls-remote.sh \ @@ -170,7 +170,7 @@ PROGRAMS = \ BUILT_INS = git-log$X git-whatchanged$X git-show$X \ git-count-objects$X git-diff$X git-push$X \ - git-grep$X + git-grep$X git-add$X # what 'all' will build and 'install' will install, in gitexecdir ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS) diff --git a/git-add.sh b/git-add.sh deleted file mode 100755 index d6a4bc7d09..0000000000 --- a/git-add.sh +++ /dev/null @@ -1,56 +0,0 @@ -#!/bin/sh - -USAGE='[-n] [-v] ...' -SUBDIRECTORY_OK='Yes' -. git-sh-setup - -show_only= -verbose= -while : ; do - case "$1" in - -n) - show_only=true - ;; - -v) - verbose=--verbose - ;; - --) - shift - break - ;; - -*) - usage - ;; - *) - break - ;; - esac - shift -done - -# Check misspelled pathspec -case "$#" in -0) ;; -*) - git-ls-files --error-unmatch --others --cached -- "$@" >/dev/null || { - echo >&2 "Maybe you misspelled it?" - exit 1 - } - ;; -esac - -if test -f "$GIT_DIR/info/exclude" -then - git-ls-files -z \ - --exclude-from="$GIT_DIR/info/exclude" \ - --others --exclude-per-directory=.gitignore -- "$@" -else - git-ls-files -z \ - --others --exclude-per-directory=.gitignore -- "$@" -fi | -case "$show_only" in -true) - xargs -0 echo ;; -*) - git-update-index --add $verbose -z --stdin ;; -esac -- cgit v1.3 From 5fb61b8dcfdf7bcec0793c071813e255d1803859 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Thu, 18 May 2006 14:19:20 -0700 Subject: Make "git rev-list" be a builtin This was surprisingly easy. The diff is truly minimal: rename "main()" to "cmd_rev_list()" in rev-list.c, and rename the whole file to reflect its new built-in status. Signed-off-by: Linus Torvalds Signed-off-by: Junio C Hamano --- Makefile | 6 +- builtin-rev-list.c | 358 +++++++++++++++++++++++++++++++++++++++++++++++++++++ builtin.h | 1 + git.c | 1 + rev-list.c | 357 ---------------------------------------------------- 5 files changed, 363 insertions(+), 360 deletions(-) create mode 100644 builtin-rev-list.c delete mode 100644 rev-list.c (limited to 'Makefile') diff --git a/Makefile b/Makefile index 9ba608c805..6efc8e0275 100644 --- a/Makefile +++ b/Makefile @@ -158,7 +158,7 @@ PROGRAMS = \ git-ls-files$X git-ls-tree$X git-mailinfo$X git-merge-base$X \ git-merge-index$X git-mktag$X git-mktree$X git-pack-objects$X git-patch-id$X \ git-peek-remote$X git-prune-packed$X git-read-tree$X \ - git-receive-pack$X git-rev-list$X git-rev-parse$X \ + git-receive-pack$X git-rev-parse$X \ git-send-pack$X git-show-branch$X git-shell$X \ git-show-index$X git-ssh-fetch$X \ git-ssh-upload$X git-tar-tree$X git-unpack-file$X \ @@ -170,7 +170,7 @@ PROGRAMS = \ BUILT_INS = git-log$X git-whatchanged$X git-show$X \ git-count-objects$X git-diff$X git-push$X \ - git-grep$X + git-grep$X git-rev-list$X # what 'all' will build and 'install' will install, in gitexecdir ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS) @@ -218,7 +218,7 @@ LIB_OBJS = \ BUILTIN_OBJS = \ builtin-log.o builtin-help.o builtin-count.o builtin-diff.o builtin-push.o \ - builtin-grep.o + builtin-grep.o builtin-rev-list.o GITLIBS = $(LIB_FILE) $(XDIFF_LIB) LIBS = $(GITLIBS) -lz diff --git a/builtin-rev-list.c b/builtin-rev-list.c new file mode 100644 index 0000000000..446802d377 --- /dev/null +++ b/builtin-rev-list.c @@ -0,0 +1,358 @@ +#include "cache.h" +#include "refs.h" +#include "tag.h" +#include "commit.h" +#include "tree.h" +#include "blob.h" +#include "tree-walk.h" +#include "diff.h" +#include "revision.h" +#include "builtin.h" + +/* bits #0-15 in revision.h */ + +#define COUNTED (1u<<16) + +static const char rev_list_usage[] = +"git-rev-list [OPTION] ... [ -- paths... ]\n" +" limiting output:\n" +" --max-count=nr\n" +" --max-age=epoch\n" +" --min-age=epoch\n" +" --sparse\n" +" --no-merges\n" +" --remove-empty\n" +" --all\n" +" ordering output:\n" +" --topo-order\n" +" --date-order\n" +" formatting output:\n" +" --parents\n" +" --objects | --objects-edge\n" +" --unpacked\n" +" --header | --pretty\n" +" --abbrev=nr | --no-abbrev\n" +" --abbrev-commit\n" +" special purpose:\n" +" --bisect" +; + +static struct rev_info revs; + +static int bisect_list = 0; +static int show_timestamp = 0; +static int hdr_termination = 0; +static const char *header_prefix; + +static void show_commit(struct commit *commit) +{ + if (show_timestamp) + printf("%lu ", commit->date); + if (header_prefix) + fputs(header_prefix, stdout); + if (commit->object.flags & BOUNDARY) + putchar('-'); + if (revs.abbrev_commit && revs.abbrev) + fputs(find_unique_abbrev(commit->object.sha1, revs.abbrev), + stdout); + else + fputs(sha1_to_hex(commit->object.sha1), stdout); + if (revs.parents) { + struct commit_list *parents = commit->parents; + while (parents) { + struct object *o = &(parents->item->object); + parents = parents->next; + if (o->flags & TMP_MARK) + continue; + printf(" %s", sha1_to_hex(o->sha1)); + o->flags |= TMP_MARK; + } + /* TMP_MARK is a general purpose flag that can + * be used locally, but the user should clean + * things up after it is done with them. + */ + for (parents = commit->parents; + parents; + parents = parents->next) + parents->item->object.flags &= ~TMP_MARK; + } + if (revs.commit_format == CMIT_FMT_ONELINE) + putchar(' '); + else + putchar('\n'); + + if (revs.verbose_header) { + static char pretty_header[16384]; + pretty_print_commit(revs.commit_format, commit, ~0, + pretty_header, sizeof(pretty_header), + revs.abbrev); + printf("%s%c", pretty_header, hdr_termination); + } + fflush(stdout); +} + +static struct object_list **process_blob(struct blob *blob, + struct object_list **p, + struct name_path *path, + const char *name) +{ + struct object *obj = &blob->object; + + if (!revs.blob_objects) + return p; + if (obj->flags & (UNINTERESTING | SEEN)) + return p; + obj->flags |= SEEN; + return add_object(obj, p, path, name); +} + +static struct object_list **process_tree(struct tree *tree, + struct object_list **p, + struct name_path *path, + const char *name) +{ + struct object *obj = &tree->object; + struct tree_entry_list *entry; + struct name_path me; + + if (!revs.tree_objects) + return p; + if (obj->flags & (UNINTERESTING | SEEN)) + return p; + if (parse_tree(tree) < 0) + die("bad tree object %s", sha1_to_hex(obj->sha1)); + obj->flags |= SEEN; + p = add_object(obj, p, path, name); + me.up = path; + me.elem = name; + me.elem_len = strlen(name); + entry = tree->entries; + tree->entries = NULL; + while (entry) { + struct tree_entry_list *next = entry->next; + if (entry->directory) + p = process_tree(entry->item.tree, p, &me, entry->name); + else + p = process_blob(entry->item.blob, p, &me, entry->name); + free(entry); + entry = next; + } + return p; +} + +static void show_commit_list(struct rev_info *revs) +{ + struct commit *commit; + struct object_list *objects = NULL, **p = &objects, *pending; + + while ((commit = get_revision(revs)) != NULL) { + p = process_tree(commit->tree, p, NULL, ""); + show_commit(commit); + } + for (pending = revs->pending_objects; pending; pending = pending->next) { + struct object *obj = pending->item; + const char *name = pending->name; + if (obj->flags & (UNINTERESTING | SEEN)) + continue; + if (obj->type == tag_type) { + obj->flags |= SEEN; + p = add_object(obj, p, NULL, name); + continue; + } + if (obj->type == tree_type) { + p = process_tree((struct tree *)obj, p, NULL, name); + continue; + } + if (obj->type == blob_type) { + p = process_blob((struct blob *)obj, p, NULL, name); + continue; + } + die("unknown pending object %s (%s)", sha1_to_hex(obj->sha1), name); + } + while (objects) { + /* An object with name "foo\n0000000..." can be used to + * confuse downstream git-pack-objects very badly. + */ + const char *ep = strchr(objects->name, '\n'); + if (ep) { + printf("%s %.*s\n", sha1_to_hex(objects->item->sha1), + (int) (ep - objects->name), + objects->name); + } + else + printf("%s %s\n", sha1_to_hex(objects->item->sha1), objects->name); + objects = objects->next; + } +} + +/* + * This is a truly stupid algorithm, but it's only + * used for bisection, and we just don't care enough. + * + * We care just barely enough to avoid recursing for + * non-merge entries. + */ +static int count_distance(struct commit_list *entry) +{ + int nr = 0; + + while (entry) { + struct commit *commit = entry->item; + struct commit_list *p; + + if (commit->object.flags & (UNINTERESTING | COUNTED)) + break; + if (!revs.prune_fn || (commit->object.flags & TREECHANGE)) + nr++; + commit->object.flags |= COUNTED; + p = commit->parents; + entry = p; + if (p) { + p = p->next; + while (p) { + nr += count_distance(p); + p = p->next; + } + } + } + + return nr; +} + +static void clear_distance(struct commit_list *list) +{ + while (list) { + struct commit *commit = list->item; + commit->object.flags &= ~COUNTED; + list = list->next; + } +} + +static struct commit_list *find_bisection(struct commit_list *list) +{ + int nr, closest; + struct commit_list *p, *best; + + nr = 0; + p = list; + while (p) { + if (!revs.prune_fn || (p->item->object.flags & TREECHANGE)) + nr++; + p = p->next; + } + closest = 0; + best = list; + + for (p = list; p; p = p->next) { + int distance; + + if (revs.prune_fn && !(p->item->object.flags & TREECHANGE)) + continue; + + distance = count_distance(p); + clear_distance(list); + if (nr - distance < distance) + distance = nr - distance; + if (distance > closest) { + best = p; + closest = distance; + } + } + if (best) + best->next = NULL; + return best; +} + +static void mark_edge_parents_uninteresting(struct commit *commit) +{ + struct commit_list *parents; + + for (parents = commit->parents; parents; parents = parents->next) { + struct commit *parent = parents->item; + if (!(parent->object.flags & UNINTERESTING)) + continue; + mark_tree_uninteresting(parent->tree); + if (revs.edge_hint && !(parent->object.flags & SHOWN)) { + parent->object.flags |= SHOWN; + printf("-%s\n", sha1_to_hex(parent->object.sha1)); + } + } +} + +static void mark_edges_uninteresting(struct commit_list *list) +{ + for ( ; list; list = list->next) { + struct commit *commit = list->item; + + if (commit->object.flags & UNINTERESTING) { + mark_tree_uninteresting(commit->tree); + continue; + } + mark_edge_parents_uninteresting(commit); + } +} + +int cmd_rev_list(int argc, const char **argv, char **envp) +{ + struct commit_list *list; + int i; + + init_revisions(&revs); + revs.abbrev = 0; + revs.commit_format = CMIT_FMT_UNSPECIFIED; + argc = setup_revisions(argc, argv, &revs, NULL); + + for (i = 1 ; i < argc; i++) { + const char *arg = argv[i]; + + if (!strcmp(arg, "--header")) { + revs.verbose_header = 1; + continue; + } + if (!strcmp(arg, "--timestamp")) { + show_timestamp = 1; + continue; + } + if (!strcmp(arg, "--bisect")) { + bisect_list = 1; + continue; + } + usage(rev_list_usage); + + } + if (revs.commit_format != CMIT_FMT_UNSPECIFIED) { + /* The command line has a --pretty */ + hdr_termination = '\n'; + if (revs.commit_format == CMIT_FMT_ONELINE) + header_prefix = ""; + else + header_prefix = "commit "; + } + else if (revs.verbose_header) + /* Only --header was specified */ + revs.commit_format = CMIT_FMT_RAW; + + list = revs.commits; + + if ((!list && + (!(revs.tag_objects||revs.tree_objects||revs.blob_objects) && + !revs.pending_objects)) || + revs.diff) + usage(rev_list_usage); + + save_commit_buffer = revs.verbose_header; + track_object_refs = 0; + if (bisect_list) + revs.limited = 1; + + prepare_revision_walk(&revs); + if (revs.tree_objects) + mark_edges_uninteresting(revs.commits); + + if (bisect_list) + revs.commits = find_bisection(revs.commits); + + show_commit_list(&revs); + + return 0; +} diff --git a/builtin.h b/builtin.h index 7744f7d2f6..7dff121520 100644 --- a/builtin.h +++ b/builtin.h @@ -24,5 +24,6 @@ extern int cmd_count_objects(int argc, const char **argv, char **envp); extern int cmd_push(int argc, const char **argv, char **envp); extern int cmd_grep(int argc, const char **argv, char **envp); +extern int cmd_rev_list(int argc, const char **argv, char **envp); #endif diff --git a/git.c b/git.c index a94d9ee5a7..c94e3a531b 100644 --- a/git.c +++ b/git.c @@ -50,6 +50,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "count-objects", cmd_count_objects }, { "diff", cmd_diff }, { "grep", cmd_grep }, + { "rev-list", cmd_rev_list }, }; int i; diff --git a/rev-list.c b/rev-list.c deleted file mode 100644 index 8b0ec388fa..0000000000 --- a/rev-list.c +++ /dev/null @@ -1,357 +0,0 @@ -#include "cache.h" -#include "refs.h" -#include "tag.h" -#include "commit.h" -#include "tree.h" -#include "blob.h" -#include "tree-walk.h" -#include "diff.h" -#include "revision.h" - -/* bits #0-15 in revision.h */ - -#define COUNTED (1u<<16) - -static const char rev_list_usage[] = -"git-rev-list [OPTION] ... [ -- paths... ]\n" -" limiting output:\n" -" --max-count=nr\n" -" --max-age=epoch\n" -" --min-age=epoch\n" -" --sparse\n" -" --no-merges\n" -" --remove-empty\n" -" --all\n" -" ordering output:\n" -" --topo-order\n" -" --date-order\n" -" formatting output:\n" -" --parents\n" -" --objects | --objects-edge\n" -" --unpacked\n" -" --header | --pretty\n" -" --abbrev=nr | --no-abbrev\n" -" --abbrev-commit\n" -" special purpose:\n" -" --bisect" -; - -struct rev_info revs; - -static int bisect_list = 0; -static int show_timestamp = 0; -static int hdr_termination = 0; -static const char *header_prefix; - -static void show_commit(struct commit *commit) -{ - if (show_timestamp) - printf("%lu ", commit->date); - if (header_prefix) - fputs(header_prefix, stdout); - if (commit->object.flags & BOUNDARY) - putchar('-'); - if (revs.abbrev_commit && revs.abbrev) - fputs(find_unique_abbrev(commit->object.sha1, revs.abbrev), - stdout); - else - fputs(sha1_to_hex(commit->object.sha1), stdout); - if (revs.parents) { - struct commit_list *parents = commit->parents; - while (parents) { - struct object *o = &(parents->item->object); - parents = parents->next; - if (o->flags & TMP_MARK) - continue; - printf(" %s", sha1_to_hex(o->sha1)); - o->flags |= TMP_MARK; - } - /* TMP_MARK is a general purpose flag that can - * be used locally, but the user should clean - * things up after it is done with them. - */ - for (parents = commit->parents; - parents; - parents = parents->next) - parents->item->object.flags &= ~TMP_MARK; - } - if (revs.commit_format == CMIT_FMT_ONELINE) - putchar(' '); - else - putchar('\n'); - - if (revs.verbose_header) { - static char pretty_header[16384]; - pretty_print_commit(revs.commit_format, commit, ~0, - pretty_header, sizeof(pretty_header), - revs.abbrev); - printf("%s%c", pretty_header, hdr_termination); - } - fflush(stdout); -} - -static struct object_list **process_blob(struct blob *blob, - struct object_list **p, - struct name_path *path, - const char *name) -{ - struct object *obj = &blob->object; - - if (!revs.blob_objects) - return p; - if (obj->flags & (UNINTERESTING | SEEN)) - return p; - obj->flags |= SEEN; - return add_object(obj, p, path, name); -} - -static struct object_list **process_tree(struct tree *tree, - struct object_list **p, - struct name_path *path, - const char *name) -{ - struct object *obj = &tree->object; - struct tree_entry_list *entry; - struct name_path me; - - if (!revs.tree_objects) - return p; - if (obj->flags & (UNINTERESTING | SEEN)) - return p; - if (parse_tree(tree) < 0) - die("bad tree object %s", sha1_to_hex(obj->sha1)); - obj->flags |= SEEN; - p = add_object(obj, p, path, name); - me.up = path; - me.elem = name; - me.elem_len = strlen(name); - entry = tree->entries; - tree->entries = NULL; - while (entry) { - struct tree_entry_list *next = entry->next; - if (entry->directory) - p = process_tree(entry->item.tree, p, &me, entry->name); - else - p = process_blob(entry->item.blob, p, &me, entry->name); - free(entry); - entry = next; - } - return p; -} - -static void show_commit_list(struct rev_info *revs) -{ - struct commit *commit; - struct object_list *objects = NULL, **p = &objects, *pending; - - while ((commit = get_revision(revs)) != NULL) { - p = process_tree(commit->tree, p, NULL, ""); - show_commit(commit); - } - for (pending = revs->pending_objects; pending; pending = pending->next) { - struct object *obj = pending->item; - const char *name = pending->name; - if (obj->flags & (UNINTERESTING | SEEN)) - continue; - if (obj->type == tag_type) { - obj->flags |= SEEN; - p = add_object(obj, p, NULL, name); - continue; - } - if (obj->type == tree_type) { - p = process_tree((struct tree *)obj, p, NULL, name); - continue; - } - if (obj->type == blob_type) { - p = process_blob((struct blob *)obj, p, NULL, name); - continue; - } - die("unknown pending object %s (%s)", sha1_to_hex(obj->sha1), name); - } - while (objects) { - /* An object with name "foo\n0000000..." can be used to - * confuse downstream git-pack-objects very badly. - */ - const char *ep = strchr(objects->name, '\n'); - if (ep) { - printf("%s %.*s\n", sha1_to_hex(objects->item->sha1), - (int) (ep - objects->name), - objects->name); - } - else - printf("%s %s\n", sha1_to_hex(objects->item->sha1), objects->name); - objects = objects->next; - } -} - -/* - * This is a truly stupid algorithm, but it's only - * used for bisection, and we just don't care enough. - * - * We care just barely enough to avoid recursing for - * non-merge entries. - */ -static int count_distance(struct commit_list *entry) -{ - int nr = 0; - - while (entry) { - struct commit *commit = entry->item; - struct commit_list *p; - - if (commit->object.flags & (UNINTERESTING | COUNTED)) - break; - if (!revs.prune_fn || (commit->object.flags & TREECHANGE)) - nr++; - commit->object.flags |= COUNTED; - p = commit->parents; - entry = p; - if (p) { - p = p->next; - while (p) { - nr += count_distance(p); - p = p->next; - } - } - } - - return nr; -} - -static void clear_distance(struct commit_list *list) -{ - while (list) { - struct commit *commit = list->item; - commit->object.flags &= ~COUNTED; - list = list->next; - } -} - -static struct commit_list *find_bisection(struct commit_list *list) -{ - int nr, closest; - struct commit_list *p, *best; - - nr = 0; - p = list; - while (p) { - if (!revs.prune_fn || (p->item->object.flags & TREECHANGE)) - nr++; - p = p->next; - } - closest = 0; - best = list; - - for (p = list; p; p = p->next) { - int distance; - - if (revs.prune_fn && !(p->item->object.flags & TREECHANGE)) - continue; - - distance = count_distance(p); - clear_distance(list); - if (nr - distance < distance) - distance = nr - distance; - if (distance > closest) { - best = p; - closest = distance; - } - } - if (best) - best->next = NULL; - return best; -} - -static void mark_edge_parents_uninteresting(struct commit *commit) -{ - struct commit_list *parents; - - for (parents = commit->parents; parents; parents = parents->next) { - struct commit *parent = parents->item; - if (!(parent->object.flags & UNINTERESTING)) - continue; - mark_tree_uninteresting(parent->tree); - if (revs.edge_hint && !(parent->object.flags & SHOWN)) { - parent->object.flags |= SHOWN; - printf("-%s\n", sha1_to_hex(parent->object.sha1)); - } - } -} - -static void mark_edges_uninteresting(struct commit_list *list) -{ - for ( ; list; list = list->next) { - struct commit *commit = list->item; - - if (commit->object.flags & UNINTERESTING) { - mark_tree_uninteresting(commit->tree); - continue; - } - mark_edge_parents_uninteresting(commit); - } -} - -int main(int argc, const char **argv) -{ - struct commit_list *list; - int i; - - init_revisions(&revs); - revs.abbrev = 0; - revs.commit_format = CMIT_FMT_UNSPECIFIED; - argc = setup_revisions(argc, argv, &revs, NULL); - - for (i = 1 ; i < argc; i++) { - const char *arg = argv[i]; - - if (!strcmp(arg, "--header")) { - revs.verbose_header = 1; - continue; - } - if (!strcmp(arg, "--timestamp")) { - show_timestamp = 1; - continue; - } - if (!strcmp(arg, "--bisect")) { - bisect_list = 1; - continue; - } - usage(rev_list_usage); - - } - if (revs.commit_format != CMIT_FMT_UNSPECIFIED) { - /* The command line has a --pretty */ - hdr_termination = '\n'; - if (revs.commit_format == CMIT_FMT_ONELINE) - header_prefix = ""; - else - header_prefix = "commit "; - } - else if (revs.verbose_header) - /* Only --header was specified */ - revs.commit_format = CMIT_FMT_RAW; - - list = revs.commits; - - if ((!list && - (!(revs.tag_objects||revs.tree_objects||revs.blob_objects) && - !revs.pending_objects)) || - revs.diff) - usage(rev_list_usage); - - save_commit_buffer = revs.verbose_header; - track_object_refs = 0; - if (bisect_list) - revs.limited = 1; - - prepare_revision_walk(&revs); - if (revs.tree_objects) - mark_edges_uninteresting(revs.commits); - - if (bisect_list) - revs.commits = find_bisection(revs.commits); - - show_commit_list(&revs); - - return 0; -} -- cgit v1.3 From 9370bae2cef351617272aa142fbe4ce039833d13 Mon Sep 17 00:00:00 2001 From: Lukas Sandström Date: Thu, 18 May 2006 14:15:55 +0200 Subject: Make git-check-format-ref a builtin. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Lukas Sandström Signed-off-by: Junio C Hamano --- Makefile | 6 +++--- builtin-check-ref-format.c | 14 ++++++++++++++ builtin.h | 1 + check-ref-format.c | 17 ----------------- git.c | 1 + 5 files changed, 19 insertions(+), 20 deletions(-) create mode 100644 builtin-check-ref-format.c delete mode 100644 check-ref-format.c (limited to 'Makefile') diff --git a/Makefile b/Makefile index 6efc8e0275..2149fb8cea 100644 --- a/Makefile +++ b/Makefile @@ -164,13 +164,13 @@ PROGRAMS = \ git-ssh-upload$X git-tar-tree$X git-unpack-file$X \ git-unpack-objects$X git-update-index$X git-update-server-info$X \ git-upload-pack$X git-verify-pack$X git-write-tree$X \ - git-update-ref$X git-symbolic-ref$X git-check-ref-format$X \ + git-update-ref$X git-symbolic-ref$X \ git-name-rev$X git-pack-redundant$X git-repo-config$X git-var$X \ git-describe$X git-merge-tree$X git-blame$X git-imap-send$X 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-grep$X git-rev-list$X git-check-ref-format$X # what 'all' will build and 'install' will install, in gitexecdir ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS) @@ -218,7 +218,7 @@ LIB_OBJS = \ BUILTIN_OBJS = \ builtin-log.o builtin-help.o builtin-count.o builtin-diff.o builtin-push.o \ - builtin-grep.o builtin-rev-list.o + builtin-grep.o builtin-rev-list.o builtin-check-ref-format.o GITLIBS = $(LIB_FILE) $(XDIFF_LIB) LIBS = $(GITLIBS) -lz diff --git a/builtin-check-ref-format.c b/builtin-check-ref-format.c new file mode 100644 index 0000000000..4a23936aff --- /dev/null +++ b/builtin-check-ref-format.c @@ -0,0 +1,14 @@ +/* + * GIT - The information manager from hell + */ + +#include "cache.h" +#include "refs.h" +#include "builtin.h" + +int cmd_check_ref_format(int argc, const char **argv, char **envp) +{ + if (argc != 2) + usage("git check-ref-format refname"); + return !!check_ref_format(argv[1]); +} diff --git a/builtin.h b/builtin.h index 7dff121520..ff559dec7e 100644 --- a/builtin.h +++ b/builtin.h @@ -25,5 +25,6 @@ extern int cmd_count_objects(int argc, const char **argv, char **envp); extern int cmd_push(int argc, const char **argv, char **envp); extern int cmd_grep(int argc, const char **argv, char **envp); extern int cmd_rev_list(int argc, const char **argv, char **envp); +extern int cmd_check_ref_format(int argc, const char **argv, char **envp); #endif diff --git a/check-ref-format.c b/check-ref-format.c deleted file mode 100644 index a0adb3dcb3..0000000000 --- a/check-ref-format.c +++ /dev/null @@ -1,17 +0,0 @@ -/* - * GIT - The information manager from hell - */ - -#include "cache.h" -#include "refs.h" - -#include - -int main(int ac, char **av) -{ - if (ac != 2) - usage("git-check-ref-format refname"); - if (check_ref_format(av[1])) - exit(1); - return 0; -} diff --git a/git.c b/git.c index c94e3a531b..d0650bb409 100644 --- a/git.c +++ b/git.c @@ -51,6 +51,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "diff", cmd_diff }, { "grep", cmd_grep }, { "rev-list", cmd_rev_list }, + { "check-ref-format", cmd_check_ref_format } }; int i; -- cgit v1.3 From 52db0495dcb88f38590bc00607052fc5758b07d1 Mon Sep 17 00:00:00 2001 From: Tilman Sauerbeck Date: Thu, 18 May 2006 12:57:04 +0200 Subject: Documentation/Makefile: create tarballs for the man pages and html files [jc: rewrote by stealing from what I run to update html and man branches automatically] Signed-off-by: Tilman Sauerbeck Signed-off-by: Junio C Hamano --- Documentation/install-webdoc.sh | 8 ++++++-- Makefile | 22 +++++++++++++++++++++- 2 files changed, 27 insertions(+), 3 deletions(-) (limited to 'Makefile') diff --git a/Documentation/install-webdoc.sh b/Documentation/install-webdoc.sh index 50638c78d5..60211a5058 100755 --- a/Documentation/install-webdoc.sh +++ b/Documentation/install-webdoc.sh @@ -4,12 +4,16 @@ T="$1" for h in *.html *.txt howto/*.txt howto/*.html do - diff -u -I'Last updated [0-9][0-9]-[A-Z][a-z][a-z]-' "$T/$h" "$h" || { + if test -f "$T/$h" && + diff -u -I'Last updated [0-9][0-9]-[A-Z][a-z][a-z]-' "$T/$h" "$h" + then + :; # up to date + else echo >&2 "# install $h $T/$h" rm -f "$T/$h" mkdir -p `dirname "$T/$h"` cp "$h" "$T/$h" - } + fi done strip_leading=`echo "$T/" | sed -e 's|.|.|g'` for th in "$T"/*.html "$T"/*.txt "$T"/howto/*.txt "$T"/howto/*.html diff --git a/Makefile b/Makefile index 2149fb8cea..c33a4d2b3e 100644 --- a/Makefile +++ b/Makefile @@ -652,6 +652,25 @@ dist: git.spec git-tar-tree rpm: dist $(RPMBUILD) -ta $(GIT_TARNAME).tar.gz +htmldocs = git-htmldocs-$(GIT_VERSION) +manpages = git-manpages-$(GIT_VERSION) +dist-doc: + rm -fr .doc-tmp-dir + mkdir .doc-tmp-dir + $(MAKE) -C Documentation WEBDOC_DEST=../.doc-tmp-dir install-webdoc + cd .doc-tmp-dir && $(TAR) cf ../$(htmldocs).tar . + gzip -n -9 -f $(htmldocs).tar + : + rm -fr .doc-tmp-dir + mkdir .doc-tmp-dir .doc-tmp-dir/man1 .doc-tmp-dir/man7 + $(MAKE) -C Documentation DESTDIR=. \ + man1=../.doc-tmp-dir/man1 \ + man7=../.doc-tmp-dir/man7 \ + install + cd .doc-tmp-dir && $(TAR) cf ../$(manpages).tar . + gzip -n -9 -f $(manpages).tar + rm -fr .doc-tmp-dir + ### Cleaning rules clean: @@ -659,8 +678,9 @@ clean: $(LIB_FILE) $(XDIFF_LIB) rm -f $(ALL_PROGRAMS) $(BUILT_INS) git$X rm -f *.spec *.pyc *.pyo */*.pyc */*.pyo common-cmds.h TAGS tags - rm -rf $(GIT_TARNAME) + rm -rf $(GIT_TARNAME) .doc-tmp-dir rm -f $(GIT_TARNAME).tar.gz git-core_$(GIT_VERSION)-*.tar.gz + rm -f $(htmldocs).tar $(manpages).tar $(MAKE) -C Documentation/ clean $(MAKE) -C templates clean $(MAKE) -C t/ clean -- cgit v1.3 From d3d8f361a8c6beb5647e0d963a1460a505324494 Mon Sep 17 00:00:00 2001 From: "Eric W. Biederman" Date: Wed, 17 May 2006 12:44:40 -0600 Subject: Implement git-quiltimport Importing a quilt patch series into git is not very difficult but parsing the patch descriptions and all of the other minutia take a bit of effort to get right, so this automates it. Since git and quilt complement each other it makes sense to make it easy to go back and forth between the two. If a patch is encountered that it cannot derive the author from the user is asked. Signed-off-by: Junio C Hamano --- Documentation/git-quiltimport.txt | 55 ++++++++++++++++++++ Makefile | 2 +- git-quiltimport.sh | 106 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 162 insertions(+), 1 deletion(-) create mode 100644 Documentation/git-quiltimport.txt create mode 100755 git-quiltimport.sh (limited to 'Makefile') diff --git a/Documentation/git-quiltimport.txt b/Documentation/git-quiltimport.txt new file mode 100644 index 0000000000..c66c82c61d --- /dev/null +++ b/Documentation/git-quiltimport.txt @@ -0,0 +1,55 @@ +git-quiltimport(1) +================ + +NAME +---- +git-quiltimport - Applies a quilt patchset onto the current branch + + +SYNOPSIS +-------- +[verse] +'git-quiltimport' [--author ] [--patches ] + + +DESCRIPTION +----------- +Applies a quilt patchset onto the current git branch, preserving +the patch boundaries, patch order, and patch descriptions present +in the quilt patchset. + +For each patch the code attempts to extract the author from the +patch description. If that fails it falls back to the author +specified with --author. If the --author flag was not given +the patch description is displayed and the user is asked to +interactively enter the author of the patch. + +If a subject is not found in the patch description the patch name is +preserved as the 1 line subject in the git description. + +OPTIONS +------- +--author Author Name :: + The author name and email address to use when no author + information can be found in the patch description. + +--patches :: + The directory to find the quilt patches and the + quilt series file. + + The default for the patch directory is patches + or the value of the $QUILT_PATCHES environment + variable. + +Author +------ +Written by Eric Biederman + +Documentation +-------------- +Documentation by Eric Biederman + +GIT +--- +Part of the gitlink:git[7] suite + diff --git a/Makefile b/Makefile index 2149fb8cea..549f953b76 100644 --- a/Makefile +++ b/Makefile @@ -125,7 +125,7 @@ SCRIPT_SH = \ git-applymbox.sh git-applypatch.sh git-am.sh \ git-merge.sh git-merge-stupid.sh git-merge-octopus.sh \ git-merge-resolve.sh git-merge-ours.sh \ - git-lost-found.sh + git-lost-found.sh git-quiltimport.sh SCRIPT_PERL = \ git-archimport.perl git-cvsimport.perl git-relink.perl \ diff --git a/git-quiltimport.sh b/git-quiltimport.sh new file mode 100755 index 0000000000..dd4a198fb1 --- /dev/null +++ b/git-quiltimport.sh @@ -0,0 +1,106 @@ +#!/bin/sh +USAGE='--author --patches ' +SUBDIRECTORY_ON=Yes +. git-sh-setup + +quilt_author="" +while case "$#" in 0) break;; esac +do + case "$1" in + --au=*|--aut=*|--auth=*|--autho=*|--author=*) + quilt_author=$(expr "$1" : '-[^=]*\(.*\)') + shift + ;; + + --au|--aut|--auth|--autho|--author) + case "$#" in 1) usage ;; esac + shift + quilt_author="$1" + shift + ;; + + --pa=*|--pat=*|--patc=*|--patch=*|--patche=*|--patches=*) + QUILT_PATCHES=$(expr "$1" : '-[^=]*\(.*\)') + shift + ;; + + --pa|--pat|--patc|--patch|--patche|--patches) + case "$#" in 1) usage ;; esac + shift + QUILT_PATCHES="$1" + shift + ;; + + *) + break + ;; + esac +done + +# Quilt Author +if [ -n "$quilt_author" ] ; then + quilt_author_name=$(expr "z$quilt_author" : 'z\(.*[^ ]\) *<.*') && + quilt_author_email=$(expr "z$quilt_author" : '.*<\([^>]*\)') && + test '' != "$quilt_author_name" && + test '' != "$quilt_author_email" || + die "malformatted --author parameter" +fi + +# Quilt patch directory +: ${QUILT_PATCHES:=patches} +if ! [ -d "$QUILT_PATCHES" ] ; then + echo "The \"$QUILT_PATCHES\" directory does not exist." + exit 1 +fi + +# Temporay directories +tmp_dir=.dotest +tmp_msg="$tmp_dir/msg" +tmp_patch="$tmp_dir/patch" +tmp_info="$tmp_dir/info" + + +# Find the intial commit +commit=$(git-rev-parse HEAD) + +mkdir $tmp_dir || exit 2 +for patch_name in $(cat "$QUILT_PATCHES/series" | grep -v '^#'); do + echo $patch_name + (cat $QUILT_PATCHES/$patch_name | git-mailinfo "$tmp_msg" "$tmp_patch" > "$tmp_info") || exit 3 + + # Parse the author information + export GIT_AUTHOR_NAME=$(sed -ne 's/Author: //p' "$tmp_info") + export GIT_AUTHOR_EMAIL=$(sed -ne 's/Email: //p' "$tmp_info") + while test -z "$GIT_AUTHOR_EMAIL" && test -z "$GIT_AUTHOR_NAME" ; do + if [ -n "$quilt_author" ] ; then + GIT_AUTHOR_NAME="$quilt_author_name"; + GIT_AUTHOR_EMAIL="$quilt_author_email"; + else + echo "No author found in $patch_name"; + echo "---" + cat $tmp_msg + echo -n "Author: "; + read patch_author + + echo "$patch_author" + + patch_author_name=$(expr "z$patch_author" : 'z\(.*[^ ]\) *<.*') && + patch_author_email=$(expr "z$patch_author" : '.*<\([^>]*\)') && + test '' != "$patch_author_name" && + test '' != "$patch_author_email" && + GIT_AUTHOR_NAME="$patch_author_name" && + GIT_AUTHOR_EMAIL="$patch_author_email" + fi + done + export GIT_AUTHOR_DATE=$(sed -ne 's/Date: //p' "$tmp_info") + export SUBJECT=$(sed -ne 's/Subject: //p' "$tmp_info") + if [ -z "$SUBJECT" ] ; then + SUBJECT=$(echo $patch_name | sed -e 's/.patch$//') + fi + + git-apply --index -C1 "$tmp_patch" && + tree=$(git-write-tree) && + commit=$((echo "$SUBJECT"; echo; cat "$tmp_msg") | git-commit-tree $tree -p $commit) && + git-update-ref HEAD $commit || exit 4 +done +rm -rf $tmp_dir || exit 5 -- cgit v1.3 From c3c8835fbb182d971d71939b9a3ec7c8b86d6caf Mon Sep 17 00:00:00 2001 From: Timo Hirvonen Date: Fri, 19 May 2006 13:03:57 +0300 Subject: Builtin git-init-db Basically this just renames init-db.c to builtin-init-db.c and makes some strings const. Signed-off-by: Timo Hirvonen Signed-off-by: Junio C Hamano --- Makefile | 13 ++- builtin-init-db.c | 293 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ builtin.h | 1 + git.c | 1 + init-db.c | 291 ----------------------------------------------------- 5 files changed, 301 insertions(+), 298 deletions(-) create mode 100644 builtin-init-db.c delete mode 100644 init-db.c (limited to 'Makefile') diff --git a/Makefile b/Makefile index 2149fb8cea..a4e96435a0 100644 --- a/Makefile +++ b/Makefile @@ -154,7 +154,7 @@ PROGRAMS = \ git-convert-objects$X git-diff-files$X \ git-diff-index$X git-diff-stages$X \ git-diff-tree$X git-fetch-pack$X git-fsck-objects$X \ - git-hash-object$X git-index-pack$X git-init-db$X git-local-fetch$X \ + git-hash-object$X git-index-pack$X git-local-fetch$X \ git-ls-files$X git-ls-tree$X git-mailinfo$X git-merge-base$X \ git-merge-index$X git-mktag$X git-mktree$X git-pack-objects$X git-patch-id$X \ git-peek-remote$X git-prune-packed$X git-read-tree$X \ @@ -170,7 +170,8 @@ PROGRAMS = \ BUILT_INS = git-log$X git-whatchanged$X git-show$X \ git-count-objects$X git-diff$X git-push$X \ - git-grep$X git-rev-list$X git-check-ref-format$X + git-grep$X git-rev-list$X git-check-ref-format$X \ + git-init-db$X # what 'all' will build and 'install' will install, in gitexecdir ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS) @@ -218,7 +219,8 @@ LIB_OBJS = \ BUILTIN_OBJS = \ builtin-log.o builtin-help.o builtin-count.o builtin-diff.o builtin-push.o \ - builtin-grep.o builtin-rev-list.o builtin-check-ref-format.o + builtin-grep.o builtin-rev-list.o builtin-check-ref-format.o \ + builtin-init-db.o GITLIBS = $(LIB_FILE) $(XDIFF_LIB) LIBS = $(GITLIBS) -lz @@ -473,6 +475,7 @@ strip: $(PROGRAMS) git$X git$X: git.c common-cmds.h $(BUILTIN_OBJS) $(GITLIBS) $(CC) -DGIT_VERSION='"$(GIT_VERSION)"' \ + -DDEFAULT_GIT_TEMPLATE_DIR='"$(template_dir_SQ)"' \ $(ALL_CFLAGS) -o $@ $(filter %.c,$^) \ $(BUILTIN_OBJS) $(ALL_LDFLAGS) $(LIBS) @@ -565,10 +568,6 @@ git-http-push$X: revision.o http.o http-push.o $(LIB_FILE) $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ $(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT) -init-db.o: init-db.c - $(CC) -c $(ALL_CFLAGS) \ - -DDEFAULT_GIT_TEMPLATE_DIR='"$(template_dir_SQ)"' $*.c - $(LIB_OBJS) $(BUILTIN_OBJS): $(LIB_H) $(patsubst git-%$X,%.o,$(PROGRAMS)): $(GITLIBS) $(DIFF_OBJS): diffcore.h diff --git a/builtin-init-db.c b/builtin-init-db.c new file mode 100644 index 0000000000..2a1384ccb0 --- /dev/null +++ b/builtin-init-db.c @@ -0,0 +1,293 @@ +/* + * GIT - The information manager from hell + * + * Copyright (C) Linus Torvalds, 2005 + */ +#include "cache.h" +#include "builtin.h" + +#ifndef DEFAULT_GIT_TEMPLATE_DIR +#define DEFAULT_GIT_TEMPLATE_DIR "/usr/share/git-core/templates/" +#endif + +static void safe_create_dir(const char *dir, int share) +{ + if (mkdir(dir, 0777) < 0) { + if (errno != EEXIST) { + perror(dir); + exit(1); + } + } + else if (share && adjust_shared_perm(dir)) + die("Could not make %s writable by group\n", dir); +} + +static int copy_file(const char *dst, const char *src, int mode) +{ + int fdi, fdo, status; + + mode = (mode & 0111) ? 0777 : 0666; + if ((fdi = open(src, O_RDONLY)) < 0) + return fdi; + if ((fdo = open(dst, O_WRONLY | O_CREAT | O_EXCL, mode)) < 0) { + close(fdi); + return fdo; + } + status = copy_fd(fdi, fdo); + close(fdo); + + if (!status && adjust_shared_perm(dst)) + return -1; + + return status; +} + +static void copy_templates_1(char *path, int baselen, + char *template, int template_baselen, + DIR *dir) +{ + struct dirent *de; + + /* Note: if ".git/hooks" file exists in the repository being + * re-initialized, /etc/core-git/templates/hooks/update would + * cause git-init-db to fail here. I think this is sane but + * it means that the set of templates we ship by default, along + * with the way the namespace under .git/ is organized, should + * be really carefully chosen. + */ + safe_create_dir(path, 1); + while ((de = readdir(dir)) != NULL) { + struct stat st_git, st_template; + int namelen; + int exists = 0; + + if (de->d_name[0] == '.') + continue; + namelen = strlen(de->d_name); + if ((PATH_MAX <= baselen + namelen) || + (PATH_MAX <= template_baselen + namelen)) + die("insanely long template name %s", de->d_name); + memcpy(path + baselen, de->d_name, namelen+1); + memcpy(template + template_baselen, de->d_name, namelen+1); + if (lstat(path, &st_git)) { + if (errno != ENOENT) + die("cannot stat %s", path); + } + else + exists = 1; + + if (lstat(template, &st_template)) + die("cannot stat template %s", template); + + if (S_ISDIR(st_template.st_mode)) { + DIR *subdir = opendir(template); + int baselen_sub = baselen + namelen; + int template_baselen_sub = template_baselen + namelen; + if (!subdir) + die("cannot opendir %s", template); + path[baselen_sub++] = + template[template_baselen_sub++] = '/'; + path[baselen_sub] = + template[template_baselen_sub] = 0; + copy_templates_1(path, baselen_sub, + template, template_baselen_sub, + subdir); + closedir(subdir); + } + else if (exists) + continue; + else if (S_ISLNK(st_template.st_mode)) { + char lnk[256]; + int len; + len = readlink(template, lnk, sizeof(lnk)); + if (len < 0) + die("cannot readlink %s", template); + if (sizeof(lnk) <= len) + die("insanely long symlink %s", template); + lnk[len] = 0; + if (symlink(lnk, path)) + die("cannot symlink %s %s", lnk, path); + } + else if (S_ISREG(st_template.st_mode)) { + if (copy_file(path, template, st_template.st_mode)) + die("cannot copy %s to %s", template, path); + } + else + error("ignoring template %s", template); + } +} + +static void copy_templates(const char *git_dir, int len, const char *template_dir) +{ + char path[PATH_MAX]; + char template_path[PATH_MAX]; + int template_len; + DIR *dir; + + if (!template_dir) + template_dir = DEFAULT_GIT_TEMPLATE_DIR; + strcpy(template_path, template_dir); + template_len = strlen(template_path); + if (template_path[template_len-1] != '/') { + template_path[template_len++] = '/'; + template_path[template_len] = 0; + } + dir = opendir(template_path); + if (!dir) { + fprintf(stderr, "warning: templates not found %s\n", + template_dir); + return; + } + + /* Make sure that template is from the correct vintage */ + strcpy(template_path + template_len, "config"); + repository_format_version = 0; + git_config_from_file(check_repository_format_version, + template_path); + template_path[template_len] = 0; + + if (repository_format_version && + repository_format_version != GIT_REPO_VERSION) { + fprintf(stderr, "warning: not copying templates of " + "a wrong format version %d from '%s'\n", + repository_format_version, + template_dir); + closedir(dir); + return; + } + + memcpy(path, git_dir, len); + path[len] = 0; + copy_templates_1(path, len, + template_path, template_len, + dir); + closedir(dir); +} + +static void create_default_files(const char *git_dir, const char *template_path) +{ + unsigned len = strlen(git_dir); + static char path[PATH_MAX]; + unsigned char sha1[20]; + struct stat st1; + char repo_version_string[10]; + + if (len > sizeof(path)-50) + die("insane git directory %s", git_dir); + memcpy(path, git_dir, len); + + if (len && path[len-1] != '/') + path[len++] = '/'; + + /* + * Create .git/refs/{heads,tags} + */ + strcpy(path + len, "refs"); + safe_create_dir(path, 1); + strcpy(path + len, "refs/heads"); + safe_create_dir(path, 1); + strcpy(path + len, "refs/tags"); + safe_create_dir(path, 1); + + /* First copy the templates -- we might have the default + * config file there, in which case we would want to read + * from it after installing. + */ + path[len] = 0; + copy_templates(path, len, template_path); + + git_config(git_default_config); + + /* + * Create the default symlink from ".git/HEAD" to the "master" + * branch, if it does not exist yet. + */ + strcpy(path + len, "HEAD"); + if (read_ref(path, sha1) < 0) { + if (create_symref(path, "refs/heads/master") < 0) + exit(1); + } + + /* This forces creation of new config file */ + sprintf(repo_version_string, "%d", GIT_REPO_VERSION); + git_config_set("core.repositoryformatversion", repo_version_string); + + path[len] = 0; + strcpy(path + len, "config"); + + /* Check filemode trustability */ + if (!lstat(path, &st1)) { + struct stat st2; + int filemode = (!chmod(path, st1.st_mode ^ S_IXUSR) && + !lstat(path, &st2) && + st1.st_mode != st2.st_mode); + git_config_set("core.filemode", + filemode ? "true" : "false"); + } +} + +static const char init_db_usage[] = +"git-init-db [--template=] [--shared]"; + +/* + * If you want to, you can share the DB area with any number of branches. + * That has advantages: you can save space by sharing all the SHA1 objects. + * On the other hand, it might just make lookup slower and messier. You + * be the judge. The default case is to have one DB per managed directory. + */ +int cmd_init_db(int argc, const char **argv, char **envp) +{ + const char *git_dir; + const char *sha1_dir; + const char *template_dir = NULL; + char *path; + int len, i; + + for (i = 1; i < argc; i++, argv++) { + const char *arg = argv[1]; + if (!strncmp(arg, "--template=", 11)) + template_dir = arg+11; + else if (!strcmp(arg, "--shared")) + shared_repository = 1; + else + die(init_db_usage); + } + + /* + * Set up the default .git directory contents + */ + git_dir = getenv(GIT_DIR_ENVIRONMENT); + if (!git_dir) { + git_dir = DEFAULT_GIT_DIR_ENVIRONMENT; + fprintf(stderr, "defaulting to local storage area\n"); + } + safe_create_dir(git_dir, 0); + + /* Check to see if the repository version is right. + * Note that a newly created repository does not have + * config file, so this will not fail. What we are catching + * is an attempt to reinitialize new repository with an old tool. + */ + check_repository_format(); + + create_default_files(git_dir, template_dir); + + /* + * And set up the object store. + */ + sha1_dir = get_object_directory(); + len = strlen(sha1_dir); + path = xmalloc(len + 40); + memcpy(path, sha1_dir, len); + + safe_create_dir(sha1_dir, 1); + strcpy(path+len, "/pack"); + safe_create_dir(path, 1); + strcpy(path+len, "/info"); + safe_create_dir(path, 1); + + if (shared_repository) + git_config_set("core.sharedRepository", "true"); + + return 0; +} diff --git a/builtin.h b/builtin.h index ff559dec7e..60541262c4 100644 --- a/builtin.h +++ b/builtin.h @@ -26,5 +26,6 @@ extern int cmd_push(int argc, const char **argv, char **envp); extern int cmd_grep(int argc, const char **argv, char **envp); extern int cmd_rev_list(int argc, const char **argv, char **envp); extern int cmd_check_ref_format(int argc, const char **argv, char **envp); +extern int cmd_init_db(int argc, const char **argv, char **envp); #endif diff --git a/git.c b/git.c index d0650bb409..3216d311b2 100644 --- a/git.c +++ b/git.c @@ -51,6 +51,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "diff", cmd_diff }, { "grep", cmd_grep }, { "rev-list", cmd_rev_list }, + { "init-db", cmd_init_db }, { "check-ref-format", cmd_check_ref_format } }; int i; diff --git a/init-db.c b/init-db.c deleted file mode 100644 index ff294960f2..0000000000 --- a/init-db.c +++ /dev/null @@ -1,291 +0,0 @@ -/* - * GIT - The information manager from hell - * - * Copyright (C) Linus Torvalds, 2005 - */ -#include "cache.h" - -#ifndef DEFAULT_GIT_TEMPLATE_DIR -#define DEFAULT_GIT_TEMPLATE_DIR "/usr/share/git-core/templates/" -#endif - -static void safe_create_dir(const char *dir, int share) -{ - if (mkdir(dir, 0777) < 0) { - if (errno != EEXIST) { - perror(dir); - exit(1); - } - } - else if (share && adjust_shared_perm(dir)) - die("Could not make %s writable by group\n", dir); -} - -static int copy_file(const char *dst, const char *src, int mode) -{ - int fdi, fdo, status; - - mode = (mode & 0111) ? 0777 : 0666; - if ((fdi = open(src, O_RDONLY)) < 0) - return fdi; - if ((fdo = open(dst, O_WRONLY | O_CREAT | O_EXCL, mode)) < 0) { - close(fdi); - return fdo; - } - status = copy_fd(fdi, fdo); - close(fdo); - - if (!status && adjust_shared_perm(dst)) - return -1; - - return status; -} - -static void copy_templates_1(char *path, int baselen, - char *template, int template_baselen, - DIR *dir) -{ - struct dirent *de; - - /* Note: if ".git/hooks" file exists in the repository being - * re-initialized, /etc/core-git/templates/hooks/update would - * cause git-init-db to fail here. I think this is sane but - * it means that the set of templates we ship by default, along - * with the way the namespace under .git/ is organized, should - * be really carefully chosen. - */ - safe_create_dir(path, 1); - while ((de = readdir(dir)) != NULL) { - struct stat st_git, st_template; - int namelen; - int exists = 0; - - if (de->d_name[0] == '.') - continue; - namelen = strlen(de->d_name); - if ((PATH_MAX <= baselen + namelen) || - (PATH_MAX <= template_baselen + namelen)) - die("insanely long template name %s", de->d_name); - memcpy(path + baselen, de->d_name, namelen+1); - memcpy(template + template_baselen, de->d_name, namelen+1); - if (lstat(path, &st_git)) { - if (errno != ENOENT) - die("cannot stat %s", path); - } - else - exists = 1; - - if (lstat(template, &st_template)) - die("cannot stat template %s", template); - - if (S_ISDIR(st_template.st_mode)) { - DIR *subdir = opendir(template); - int baselen_sub = baselen + namelen; - int template_baselen_sub = template_baselen + namelen; - if (!subdir) - die("cannot opendir %s", template); - path[baselen_sub++] = - template[template_baselen_sub++] = '/'; - path[baselen_sub] = - template[template_baselen_sub] = 0; - copy_templates_1(path, baselen_sub, - template, template_baselen_sub, - subdir); - closedir(subdir); - } - else if (exists) - continue; - else if (S_ISLNK(st_template.st_mode)) { - char lnk[256]; - int len; - len = readlink(template, lnk, sizeof(lnk)); - if (len < 0) - die("cannot readlink %s", template); - if (sizeof(lnk) <= len) - die("insanely long symlink %s", template); - lnk[len] = 0; - if (symlink(lnk, path)) - die("cannot symlink %s %s", lnk, path); - } - else if (S_ISREG(st_template.st_mode)) { - if (copy_file(path, template, st_template.st_mode)) - die("cannot copy %s to %s", template, path); - } - else - error("ignoring template %s", template); - } -} - -static void copy_templates(const char *git_dir, int len, char *template_dir) -{ - char path[PATH_MAX]; - char template_path[PATH_MAX]; - int template_len; - DIR *dir; - - if (!template_dir) - template_dir = DEFAULT_GIT_TEMPLATE_DIR; - strcpy(template_path, template_dir); - template_len = strlen(template_path); - if (template_path[template_len-1] != '/') { - template_path[template_len++] = '/'; - template_path[template_len] = 0; - } - dir = opendir(template_path); - if (!dir) { - fprintf(stderr, "warning: templates not found %s\n", - template_dir); - return; - } - - /* Make sure that template is from the correct vintage */ - strcpy(template_path + template_len, "config"); - repository_format_version = 0; - git_config_from_file(check_repository_format_version, - template_path); - template_path[template_len] = 0; - - if (repository_format_version && - repository_format_version != GIT_REPO_VERSION) { - fprintf(stderr, "warning: not copying templates of " - "a wrong format version %d from '%s'\n", - repository_format_version, - template_dir); - closedir(dir); - return; - } - - memcpy(path, git_dir, len); - path[len] = 0; - copy_templates_1(path, len, - template_path, template_len, - dir); - closedir(dir); -} - -static void create_default_files(const char *git_dir, char *template_path) -{ - unsigned len = strlen(git_dir); - static char path[PATH_MAX]; - unsigned char sha1[20]; - struct stat st1; - char repo_version_string[10]; - - if (len > sizeof(path)-50) - die("insane git directory %s", git_dir); - memcpy(path, git_dir, len); - - if (len && path[len-1] != '/') - path[len++] = '/'; - - /* - * Create .git/refs/{heads,tags} - */ - strcpy(path + len, "refs"); - safe_create_dir(path, 1); - strcpy(path + len, "refs/heads"); - safe_create_dir(path, 1); - strcpy(path + len, "refs/tags"); - safe_create_dir(path, 1); - - /* First copy the templates -- we might have the default - * config file there, in which case we would want to read - * from it after installing. - */ - path[len] = 0; - copy_templates(path, len, template_path); - - git_config(git_default_config); - - /* - * Create the default symlink from ".git/HEAD" to the "master" - * branch, if it does not exist yet. - */ - strcpy(path + len, "HEAD"); - if (read_ref(path, sha1) < 0) { - if (create_symref(path, "refs/heads/master") < 0) - exit(1); - } - - /* This forces creation of new config file */ - sprintf(repo_version_string, "%d", GIT_REPO_VERSION); - git_config_set("core.repositoryformatversion", repo_version_string); - - path[len] = 0; - strcpy(path + len, "config"); - - /* Check filemode trustability */ - if (!lstat(path, &st1)) { - struct stat st2; - int filemode = (!chmod(path, st1.st_mode ^ S_IXUSR) && - !lstat(path, &st2) && - st1.st_mode != st2.st_mode); - git_config_set("core.filemode", - filemode ? "true" : "false"); - } -} - -static const char init_db_usage[] = -"git-init-db [--template=] [--shared]"; - -/* - * If you want to, you can share the DB area with any number of branches. - * That has advantages: you can save space by sharing all the SHA1 objects. - * On the other hand, it might just make lookup slower and messier. You - * be the judge. The default case is to have one DB per managed directory. - */ -int main(int argc, char **argv) -{ - const char *git_dir; - const char *sha1_dir; - char *path, *template_dir = NULL; - int len, i; - - for (i = 1; i < argc; i++, argv++) { - char *arg = argv[1]; - if (!strncmp(arg, "--template=", 11)) - template_dir = arg+11; - else if (!strcmp(arg, "--shared")) - shared_repository = 1; - else - die(init_db_usage); - } - - /* - * Set up the default .git directory contents - */ - git_dir = getenv(GIT_DIR_ENVIRONMENT); - if (!git_dir) { - git_dir = DEFAULT_GIT_DIR_ENVIRONMENT; - fprintf(stderr, "defaulting to local storage area\n"); - } - safe_create_dir(git_dir, 0); - - /* Check to see if the repository version is right. - * Note that a newly created repository does not have - * config file, so this will not fail. What we are catching - * is an attempt to reinitialize new repository with an old tool. - */ - check_repository_format(); - - create_default_files(git_dir, template_dir); - - /* - * And set up the object store. - */ - sha1_dir = get_object_directory(); - len = strlen(sha1_dir); - path = xmalloc(len + 40); - memcpy(path, sha1_dir, len); - - safe_create_dir(sha1_dir, 1); - strcpy(path+len, "/pack"); - safe_create_dir(path, 1); - strcpy(path+len, "/info"); - safe_create_dir(path, 1); - - if (shared_repository) - git_config_set("core.sharedRepository", "true"); - - return 0; -} -- cgit v1.3 From 7c4f59d181d801c6da08aeca74e2ef78efe4cd2b Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 19 May 2006 17:23:07 -0700 Subject: Fix build procedure for builtin-init-db c3c8835fbb182d971d71939b9a3ec7c8b86d6caf broke the default template location which is in builtin-init-db.o, by not supplying the compilation-time constant to the right build commands. Signed-off-by: Junio C Hamano --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Makefile') diff --git a/Makefile b/Makefile index 3a31ce0150..4fd6520b7e 100644 --- a/Makefile +++ b/Makefile @@ -461,6 +461,7 @@ PYTHON_PATH_SQ = $(subst ','\'',$(PYTHON_PATH)) GIT_PYTHON_DIR_SQ = $(subst ','\'',$(GIT_PYTHON_DIR)) ALL_CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER_SQ)' $(COMPAT_CFLAGS) +ALL_CFLAGS += -DDEFAULT_GIT_TEMPLATE_DIR='"$(template_dir_SQ)"' LIB_OBJS += $(COMPAT_OBJS) export prefix TAR INSTALL DESTDIR SHELL_PATH template_dir ### Build rules @@ -475,7 +476,6 @@ strip: $(PROGRAMS) git$X git$X: git.c common-cmds.h $(BUILTIN_OBJS) $(GITLIBS) $(CC) -DGIT_VERSION='"$(GIT_VERSION)"' \ - -DDEFAULT_GIT_TEMPLATE_DIR='"$(template_dir_SQ)"' \ $(ALL_CFLAGS) -o $@ $(filter %.c,$^) \ $(BUILTIN_OBJS) $(ALL_LDFLAGS) $(LIBS) -- cgit v1.3 From d9b814cc97f16daac06566a5340121c446136d22 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Fri, 19 May 2006 16:19:34 -0700 Subject: Add builtin "git rm" command This changes semantics very subtly, because it adds a new atomicity guarantee. In particular, if you "git rm" several files, it will now do all or nothing. The old shell-script really looped over the removed files one by one, and would basically randomly fail in the middle if "-f" was used and one of the files didn't exist in the working directory. This C builtin one will not re-write the index after each remove, but instead remove all files at once. However, that means that if "-f" is used (to also force removal of the file from the working directory), and some files have already been removed from the workspace, it won't stop in the middle in some half-way state like the old one did. So what happens is that if the _first_ file fails to be removed with "-f", we abort the whole "git rm". But once we've started removing, we don't leave anything half done. If some of the other files don't exist, we'll just ignore errors of removal from the working tree. This is only an issue with "-f", of course. I think the new behaviour is strictly an improvement, but perhaps more importantly, it is _different_. As a special case, the semantics are identical for the single-file case (which is the only one our test-suite seems to test). The other question is what to do with leading directories. The old "git rm" script didn't do anything, which is somewhat inconsistent. This one will actually clean up directories that have become empty as a result of removing the last file, but maybe we want to have a flag to decide the behaviour? Signed-off-by: Linus Torvalds Signed-off-by: Junio C Hamano --- Makefile | 8 ++-- builtin-rm.c | 150 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ builtin.h | 1 + git-rm.sh | 70 ---------------------------- git.c | 1 + 5 files changed, 157 insertions(+), 73 deletions(-) create mode 100644 builtin-rm.c delete mode 100755 git-rm.sh (limited to 'Makefile') diff --git a/Makefile b/Makefile index 48e2a9cb22..d4a91135c3 100644 --- a/Makefile +++ b/Makefile @@ -120,7 +120,7 @@ SCRIPT_SH = \ git-merge-one-file.sh git-parse-remote.sh \ git-prune.sh git-pull.sh git-rebase.sh \ git-repack.sh git-request-pull.sh git-reset.sh \ - git-resolve.sh git-revert.sh git-rm.sh git-sh-setup.sh \ + git-resolve.sh git-revert.sh git-sh-setup.sh \ git-tag.sh git-verify-tag.sh \ git-applymbox.sh git-applypatch.sh git-am.sh \ git-merge.sh git-merge-stupid.sh git-merge-octopus.sh \ @@ -170,7 +170,8 @@ PROGRAMS = \ BUILT_INS = git-log$X git-whatchanged$X git-show$X \ git-count-objects$X git-diff$X git-push$X \ - git-grep$X git-add$X + git-grep$X git-add$X git-rm$X git-rev-list$X \ + git-check-ref-format$X # what 'all' will build and 'install' will install, in gitexecdir ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS) @@ -218,7 +219,8 @@ LIB_OBJS = \ BUILTIN_OBJS = \ builtin-log.o builtin-help.o builtin-count.o builtin-diff.o builtin-push.o \ - builtin-grep.o builtin-add.o + builtin-grep.o builtin-add.o builtin-rev-list.o builtin-check-ref-format.o \ + builtin-rm.o GITLIBS = $(LIB_FILE) $(XDIFF_LIB) LIBS = $(GITLIBS) -lz diff --git a/builtin-rm.c b/builtin-rm.c new file mode 100644 index 0000000000..9014c61556 --- /dev/null +++ b/builtin-rm.c @@ -0,0 +1,150 @@ +/* + * "git rm" builtin command + * + * Copyright (C) Linus Torvalds 2006 + */ +#include "cache.h" +#include "builtin.h" +#include "dir.h" + +static const char builtin_rm_usage[] = +"git-rm [-n] [-v] [-f] ..."; + +static struct { + int nr, alloc; + const char **name; +} list; + +static void add_list(const char *name) +{ + if (list.nr >= list.alloc) { + list.alloc = alloc_nr(list.alloc); + list.name = xrealloc(list.name, list.alloc * sizeof(const char *)); + } + list.name[list.nr++] = name; +} + +static int remove_file(const char *name) +{ + int ret; + char *slash; + + ret = unlink(name); + if (!ret && (slash = strrchr(name, '/'))) { + char *n = strdup(name); + do { + n[slash - name] = 0; + name = n; + } while (!rmdir(name) && (slash = strrchr(name, '/'))); + } + return ret; +} + +static struct cache_file cache_file; + +int cmd_rm(int argc, const char **argv, char **envp) +{ + int i, newfd; + int verbose = 0, show_only = 0, force = 0; + const char *prefix = setup_git_directory(); + const char **pathspec; + char *seen; + + git_config(git_default_config); + + newfd = hold_index_file_for_update(&cache_file, get_index_file()); + if (newfd < 0) + die("unable to create new index file"); + + if (read_cache() < 0) + die("index file corrupt"); + + for (i = 1 ; i < argc ; i++) { + const char *arg = argv[i]; + + if (*arg != '-') + break; + if (!strcmp(arg, "--")) { + i++; + break; + } + if (!strcmp(arg, "-n")) { + show_only = 1; + continue; + } + if (!strcmp(arg, "-v")) { + verbose = 1; + continue; + } + if (!strcmp(arg, "-f")) { + force = 1; + continue; + } + die(builtin_rm_usage); + } + pathspec = get_pathspec(prefix, argv + i); + + seen = NULL; + if (pathspec) { + for (i = 0; pathspec[i] ; i++) + /* nothing */; + seen = xmalloc(i); + memset(seen, 0, i); + } + + for (i = 0; i < active_nr; i++) { + struct cache_entry *ce = active_cache[i]; + if (!match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, seen)) + continue; + add_list(ce->name); + } + + if (pathspec) { + const char *match; + for (i = 0; (match = pathspec[i]) != NULL ; i++) { + if (*match && !seen[i]) + die("pathspec '%s' did not match any files", match); + } + } + + /* + * First remove the names from the index: we won't commit + * the index unless all of them succeed + */ + for (i = 0; i < list.nr; i++) { + const char *path = list.name[i]; + printf("rm '%s'\n", path); + + if (remove_file_from_cache(path)) + die("git rm: unable to remove %s", path); + } + + /* + * Then, if we used "-f", remove the filenames from the + * workspace. If we fail to remove the first one, we + * abort the "git rm" (but once we've successfully removed + * any file at all, we'll go ahead and commit to it all: + * by then we've already committed ourself and can't fail + * in the middle) + */ + if (force) { + int removed = 0; + for (i = 0; i < list.nr; i++) { + const char *path = list.name[i]; + if (!remove_file(path)) { + removed = 1; + continue; + } + if (!removed) + die("git rm: %s: %s", path, strerror(errno)); + } + } + + if (active_cache_changed) { + if (write_cache(newfd, active_cache, active_nr) || + commit_index_file(&cache_file)) + die("Unable to write new index file"); + } + + return 0; +} diff --git a/builtin.h b/builtin.h index 1b77f4b0ca..c1cb765dea 100644 --- a/builtin.h +++ b/builtin.h @@ -24,6 +24,7 @@ extern int cmd_count_objects(int argc, const char **argv, char **envp); extern int cmd_push(int argc, const char **argv, char **envp); extern int cmd_grep(int argc, const char **argv, char **envp); +extern int cmd_rm(int argc, const char **argv, char **envp); extern int cmd_add(int argc, const char **argv, char **envp); #endif diff --git a/git-rm.sh b/git-rm.sh deleted file mode 100755 index fda4541c76..0000000000 --- a/git-rm.sh +++ /dev/null @@ -1,70 +0,0 @@ -#!/bin/sh - -USAGE='[-f] [-n] [-v] [--] ...' -SUBDIRECTORY_OK='Yes' -. git-sh-setup - -remove_files= -show_only= -verbose= -while : ; do - case "$1" in - -f) - remove_files=true - ;; - -n) - show_only=true - ;; - -v) - verbose=--verbose - ;; - --) - shift; break - ;; - -*) - usage - ;; - *) - break - ;; - esac - shift -done - -# This is typo-proofing. If some paths match and some do not, we want -# to do nothing. -case "$#" in -0) ;; -*) - git-ls-files --error-unmatch -- "$@" >/dev/null || { - echo >&2 "Maybe you misspelled it?" - exit 1 - } - ;; -esac - -if test -f "$GIT_DIR/info/exclude" -then - git-ls-files -z \ - --exclude-from="$GIT_DIR/info/exclude" \ - --exclude-per-directory=.gitignore -- "$@" -else - git-ls-files -z \ - --exclude-per-directory=.gitignore -- "$@" -fi | -case "$show_only,$remove_files" in -true,*) - xargs -0 echo - ;; -*,true) - xargs -0 sh -c " - while [ \$# -gt 0 ]; do - file=\$1; shift - rm -- \"\$file\" && git-update-index --remove $verbose \"\$file\" - done - " inline - ;; -*) - git-update-index --force-remove $verbose -z --stdin - ;; -esac diff --git a/git.c b/git.c index fac46af057..20c0f197a3 100644 --- a/git.c +++ b/git.c @@ -50,6 +50,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "count-objects", cmd_count_objects }, { "diff", cmd_diff }, { "grep", cmd_grep }, + { "rm", cmd_rm }, { "add", cmd_add }, }; int i; -- cgit v1.3 From 217542640ed219c980fff2b3c307c4520120f20f Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 18 May 2006 18:46:44 -0700 Subject: built-in tar-tree and remote tar-tree This makes tar-tree a built-in. As an added bonus, you can now say: git tar-tree --remote=remote-repository [] This does not work with git-daemon yet, but should work with localhost and git over ssh transports. Signed-off-by: Junio C Hamano --- Makefile | 6 +- builtin-tar-tree.c | 408 +++++++++++++++++++++++++++++++++++++++++++++++++++ builtin-upload-tar.c | 74 ++++++++++ builtin.h | 2 + git.c | 4 +- tar-tree.c | 350 ------------------------------------------- 6 files changed, 490 insertions(+), 354 deletions(-) create mode 100644 builtin-tar-tree.c create mode 100644 builtin-upload-tar.c delete mode 100644 tar-tree.c (limited to 'Makefile') diff --git a/Makefile b/Makefile index 4fd6520b7e..f4bcec496a 100644 --- a/Makefile +++ b/Makefile @@ -161,7 +161,7 @@ PROGRAMS = \ git-receive-pack$X git-rev-parse$X \ git-send-pack$X git-show-branch$X git-shell$X \ git-show-index$X git-ssh-fetch$X \ - git-ssh-upload$X git-tar-tree$X git-unpack-file$X \ + git-ssh-upload$X git-unpack-file$X \ git-unpack-objects$X git-update-index$X git-update-server-info$X \ git-upload-pack$X git-verify-pack$X git-write-tree$X \ git-update-ref$X git-symbolic-ref$X \ @@ -171,7 +171,7 @@ PROGRAMS = \ BUILT_INS = git-log$X git-whatchanged$X git-show$X \ git-count-objects$X git-diff$X git-push$X \ git-grep$X git-rev-list$X git-check-ref-format$X \ - git-init-db$X + git-init-db$X git-tar-tree$X git-upload-tar$X # what 'all' will build and 'install' will install, in gitexecdir ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS) @@ -220,7 +220,7 @@ LIB_OBJS = \ BUILTIN_OBJS = \ builtin-log.o builtin-help.o builtin-count.o builtin-diff.o builtin-push.o \ builtin-grep.o builtin-rev-list.o builtin-check-ref-format.o \ - builtin-init-db.o + builtin-init-db.o builtin-tar-tree.o builtin-upload-tar.o GITLIBS = $(LIB_FILE) $(XDIFF_LIB) LIBS = $(GITLIBS) -lz diff --git a/builtin-tar-tree.c b/builtin-tar-tree.c new file mode 100644 index 0000000000..e97e0af985 --- /dev/null +++ b/builtin-tar-tree.c @@ -0,0 +1,408 @@ +/* + * Copyright (c) 2005, 2006 Rene Scharfe + */ +#include +#include "cache.h" +#include "tree-walk.h" +#include "commit.h" +#include "strbuf.h" +#include "tar.h" +#include "builtin.h" +#include "pkt-line.h" + +#define RECORDSIZE (512) +#define BLOCKSIZE (RECORDSIZE * 20) + +static const char tar_tree_usage[] = +"git-tar-tree [--remote=] [basedir]"; + +static char block[BLOCKSIZE]; +static unsigned long offset; + +static time_t archive_time; + +/* tries hard to write, either succeeds or dies in the attempt */ +static void reliable_write(void *buf, unsigned long size) +{ + while (size > 0) { + long ret = xwrite(1, buf, size); + if (ret < 0) { + if (errno == EPIPE) + exit(0); + die("git-tar-tree: %s", strerror(errno)); + } else if (!ret) { + die("git-tar-tree: disk full?"); + } + size -= ret; + buf += ret; + } +} + +/* writes out the whole block, but only if it is full */ +static void write_if_needed(void) +{ + if (offset == BLOCKSIZE) { + reliable_write(block, BLOCKSIZE); + offset = 0; + } +} + +/* acquire the next record from the buffer; user must call write_if_needed() */ +static char *get_record(void) +{ + char *p = block + offset; + memset(p, 0, RECORDSIZE); + offset += RECORDSIZE; + return p; +} + +/* + * The end of tar archives is marked by 1024 nul bytes and after that + * follows the rest of the block (if any). + */ +static void write_trailer(void) +{ + get_record(); + write_if_needed(); + get_record(); + write_if_needed(); + while (offset) { + get_record(); + write_if_needed(); + } +} + +/* + * queues up writes, so that all our write(2) calls write exactly one + * full block; pads writes to RECORDSIZE + */ +static void write_blocked(void *buf, unsigned long size) +{ + unsigned long tail; + + if (offset) { + unsigned long chunk = BLOCKSIZE - offset; + if (size < chunk) + chunk = size; + memcpy(block + offset, buf, chunk); + size -= chunk; + offset += chunk; + buf += chunk; + write_if_needed(); + } + while (size >= BLOCKSIZE) { + reliable_write(buf, BLOCKSIZE); + size -= BLOCKSIZE; + buf += BLOCKSIZE; + } + if (size) { + memcpy(block + offset, buf, size); + offset += size; + } + tail = offset % RECORDSIZE; + if (tail) { + memset(block + offset, 0, RECORDSIZE - tail); + offset += RECORDSIZE - tail; + } + write_if_needed(); +} + +static void strbuf_append_string(struct strbuf *sb, const char *s) +{ + int slen = strlen(s); + int total = sb->len + slen; + if (total > sb->alloc) { + sb->buf = xrealloc(sb->buf, total); + sb->alloc = total; + } + memcpy(sb->buf + sb->len, s, slen); + sb->len = total; +} + +/* + * pax extended header records have the format "%u %s=%s\n". %u contains + * the size of the whole string (including the %u), the first %s is the + * keyword, the second one is the value. This function constructs such a + * string and appends it to a struct strbuf. + */ +static void strbuf_append_ext_header(struct strbuf *sb, const char *keyword, + const char *value, unsigned int valuelen) +{ + char *p; + int len, total, tmp; + + /* "%u %s=%s\n" */ + len = 1 + 1 + strlen(keyword) + 1 + valuelen + 1; + for (tmp = len; tmp > 9; tmp /= 10) + len++; + + total = sb->len + len; + if (total > sb->alloc) { + sb->buf = xrealloc(sb->buf, total); + sb->alloc = total; + } + + p = sb->buf; + p += sprintf(p, "%u %s=", len, keyword); + memcpy(p, value, valuelen); + p += valuelen; + *p = '\n'; + sb->len = total; +} + +static unsigned int ustar_header_chksum(const struct ustar_header *header) +{ + char *p = (char *)header; + unsigned int chksum = 0; + while (p < header->chksum) + chksum += *p++; + chksum += sizeof(header->chksum) * ' '; + p += sizeof(header->chksum); + while (p < (char *)header + sizeof(struct ustar_header)) + chksum += *p++; + return chksum; +} + +static int get_path_prefix(const struct strbuf *path, int maxlen) +{ + int i = path->len; + if (i > maxlen) + i = maxlen; + while (i > 0 && path->buf[i] != '/') + i--; + return i; +} + +static void write_entry(const unsigned char *sha1, struct strbuf *path, + unsigned int mode, void *buffer, unsigned long size) +{ + struct ustar_header header; + struct strbuf ext_header; + + memset(&header, 0, sizeof(header)); + ext_header.buf = NULL; + ext_header.len = ext_header.alloc = 0; + + if (!sha1) { + *header.typeflag = TYPEFLAG_GLOBAL_HEADER; + mode = 0100666; + strcpy(header.name, "pax_global_header"); + } else if (!path) { + *header.typeflag = TYPEFLAG_EXT_HEADER; + mode = 0100666; + sprintf(header.name, "%s.paxheader", sha1_to_hex(sha1)); + } else { + if (S_ISDIR(mode)) { + *header.typeflag = TYPEFLAG_DIR; + mode |= 0777; + } else if (S_ISLNK(mode)) { + *header.typeflag = TYPEFLAG_LNK; + mode |= 0777; + } else if (S_ISREG(mode)) { + *header.typeflag = TYPEFLAG_REG; + mode |= (mode & 0100) ? 0777 : 0666; + } else { + error("unsupported file mode: 0%o (SHA1: %s)", + mode, sha1_to_hex(sha1)); + return; + } + if (path->len > sizeof(header.name)) { + int plen = get_path_prefix(path, sizeof(header.prefix)); + int rest = path->len - plen - 1; + if (plen > 0 && rest <= sizeof(header.name)) { + memcpy(header.prefix, path->buf, plen); + memcpy(header.name, path->buf + plen + 1, rest); + } else { + sprintf(header.name, "%s.data", + sha1_to_hex(sha1)); + strbuf_append_ext_header(&ext_header, "path", + path->buf, path->len); + } + } else + memcpy(header.name, path->buf, path->len); + } + + if (S_ISLNK(mode) && buffer) { + if (size > sizeof(header.linkname)) { + sprintf(header.linkname, "see %s.paxheader", + sha1_to_hex(sha1)); + strbuf_append_ext_header(&ext_header, "linkpath", + buffer, size); + } else + memcpy(header.linkname, buffer, size); + } + + sprintf(header.mode, "%07o", mode & 07777); + sprintf(header.size, "%011lo", S_ISREG(mode) ? size : 0); + sprintf(header.mtime, "%011lo", archive_time); + + /* XXX: should we provide more meaningful info here? */ + sprintf(header.uid, "%07o", 0); + sprintf(header.gid, "%07o", 0); + strncpy(header.uname, "git", 31); + strncpy(header.gname, "git", 31); + sprintf(header.devmajor, "%07o", 0); + sprintf(header.devminor, "%07o", 0); + + memcpy(header.magic, "ustar", 6); + memcpy(header.version, "00", 2); + + sprintf(header.chksum, "%07o", ustar_header_chksum(&header)); + + if (ext_header.len > 0) { + write_entry(sha1, NULL, 0, ext_header.buf, ext_header.len); + free(ext_header.buf); + } + write_blocked(&header, sizeof(header)); + if (S_ISREG(mode) && buffer && size > 0) + write_blocked(buffer, size); +} + +static void write_global_extended_header(const unsigned char *sha1) +{ + struct strbuf ext_header; + ext_header.buf = NULL; + ext_header.len = ext_header.alloc = 0; + strbuf_append_ext_header(&ext_header, "comment", sha1_to_hex(sha1), 40); + write_entry(NULL, NULL, 0, ext_header.buf, ext_header.len); + free(ext_header.buf); +} + +static void traverse_tree(struct tree_desc *tree, struct strbuf *path) +{ + int pathlen = path->len; + + while (tree->size) { + const char *name; + const unsigned char *sha1; + unsigned mode; + void *eltbuf; + char elttype[20]; + unsigned long eltsize; + + sha1 = tree_entry_extract(tree, &name, &mode); + update_tree_entry(tree); + + eltbuf = read_sha1_file(sha1, elttype, &eltsize); + if (!eltbuf) + die("cannot read %s", sha1_to_hex(sha1)); + + path->len = pathlen; + strbuf_append_string(path, name); + if (S_ISDIR(mode)) + strbuf_append_string(path, "/"); + + write_entry(sha1, path, mode, eltbuf, eltsize); + + if (S_ISDIR(mode)) { + struct tree_desc subtree; + subtree.buf = eltbuf; + subtree.size = eltsize; + traverse_tree(&subtree, path); + } + free(eltbuf); + } +} + +int generate_tar(int argc, const char **argv) +{ + unsigned char sha1[20], tree_sha1[20]; + struct commit *commit; + struct tree_desc tree; + struct strbuf current_path; + + current_path.buf = xmalloc(PATH_MAX); + current_path.alloc = PATH_MAX; + current_path.len = current_path.eof = 0; + + setup_git_directory(); + git_config(git_default_config); + + switch (argc) { + case 3: + strbuf_append_string(¤t_path, argv[2]); + strbuf_append_string(¤t_path, "/"); + /* FALLTHROUGH */ + case 2: + if (get_sha1(argv[1], sha1)) + die("Not a valid object name %s", argv[1]); + break; + default: + usage(tar_tree_usage); + } + + commit = lookup_commit_reference_gently(sha1, 1); + if (commit) { + write_global_extended_header(commit->object.sha1); + archive_time = commit->date; + } else + archive_time = time(NULL); + + tree.buf = read_object_with_reference(sha1, tree_type, &tree.size, + tree_sha1); + if (!tree.buf) + die("not a reference to a tag, commit or tree object: %s", + sha1_to_hex(sha1)); + + if (current_path.len > 0) + write_entry(tree_sha1, ¤t_path, 040777, NULL, 0); + traverse_tree(&tree, ¤t_path); + write_trailer(); + free(current_path.buf); + return 0; +} + +static const char *exec = "git-upload-tar"; + +static int remote_tar(int argc, const char **argv) +{ + int fd[2], ret, len; + pid_t pid; + char buf[1024]; + char *url; + + if (argc < 3 || 4 < argc) + usage(tar_tree_usage); + + /* --remote= */ + url = strdup(argv[1]+9); + pid = git_connect(fd, url, exec); + if (pid < 0) + return 1; + + packet_write(fd[1], "want %s\n", argv[2]); + if (argv[3]) + packet_write(fd[1], "base %s\n", argv[3]); + packet_flush(fd[1]); + + len = packet_read_line(fd[0], buf, sizeof(buf)); + if (!len) + die("git-tar-tree: expected ACK/NAK, got EOF"); + if (buf[len-1] == '\n') + buf[--len] = 0; + if (strcmp(buf, "ACK")) { + if (5 < len && !strncmp(buf, "NACK ", 5)) + die("git-tar-tree: NACK %s", buf + 5); + die("git-tar-tree: protocol error"); + } + /* expect a flush */ + len = packet_read_line(fd[0], buf, sizeof(buf)); + if (len) + die("git-tar-tree: expected a flush"); + + /* Now, start reading from fd[0] and spit it out to stdout */ + ret = copy_fd(fd[0], 1); + close(fd[0]); + + ret |= finish_connect(pid); + return !!ret; +} + +int cmd_tar_tree(int argc, const char **argv, char **envp) +{ + if (argc < 2) + usage(tar_tree_usage); + if (!strncmp("--remote=", argv[1], 9)) + return remote_tar(argc, argv); + return generate_tar(argc, argv); +} diff --git a/builtin-upload-tar.c b/builtin-upload-tar.c new file mode 100644 index 0000000000..d4fa7b56c3 --- /dev/null +++ b/builtin-upload-tar.c @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2006 Junio C Hamano + */ +#include "cache.h" +#include "pkt-line.h" +#include "exec_cmd.h" +#include "builtin.h" + +static const char upload_tar_usage[] = "git-upload-tar "; + +static int nak(const char *reason) +{ + packet_write(1, "NACK %s\n", reason); + packet_flush(1); + return 1; +} + +int cmd_upload_tar(int argc, const char **argv, char **envp) +{ + int len; + const char *dir = argv[1]; + char buf[8192]; + unsigned char sha1[20]; + char *base = NULL; + char hex[41]; + int ac; + const char *av[4]; + + if (argc != 2) + usage(upload_tar_usage); + if (strlen(dir) < sizeof(buf)-1) + strcpy(buf, dir); /* enter-repo smudges its argument */ + else + packet_write(1, "NACK insanely long repository name %s\n", dir); + if (!enter_repo(buf, 0)) { + packet_write(1, "NACK not a git archive %s\n", dir); + packet_flush(1); + return 1; + } + + len = packet_read_line(0, buf, sizeof(buf)); + if (len < 5 || strncmp("want ", buf, 5)) + return nak("expected want"); + if (buf[len-1] == '\n') + buf[--len] = 0; + if (get_sha1(buf + 5, sha1)) + return nak("expected sha1"); + strcpy(hex, sha1_to_hex(sha1)); + + len = packet_read_line(0, buf, sizeof(buf)); + if (len) { + if (len < 5 || strncmp("base ", buf, 5)) + return nak("expected (optional) base"); + if (buf[len-1] == '\n') + buf[--len] = 0; + base = strdup(buf + 5); + len = packet_read_line(0, buf, sizeof(buf)); + } + if (len) + return nak("expected flush"); + + packet_write(1, "ACK\n"); + packet_flush(1); + + ac = 0; + av[ac++] = "tar-tree"; + av[ac++] = hex; + if (base) + av[ac++] = base; + av[ac++] = NULL; + execv_git_cmd(av); + /* should it return that is an error */ + return 1; +} diff --git a/builtin.h b/builtin.h index 60541262c4..f22783c499 100644 --- a/builtin.h +++ b/builtin.h @@ -27,5 +27,7 @@ extern int cmd_grep(int argc, const char **argv, char **envp); extern int cmd_rev_list(int argc, const char **argv, char **envp); extern int cmd_check_ref_format(int argc, const char **argv, char **envp); extern int cmd_init_db(int argc, const char **argv, char **envp); +extern int cmd_tar_tree(int argc, const char **argv, char **envp); +extern int cmd_upload_tar(int argc, const char **argv, char **envp); #endif diff --git a/git.c b/git.c index 3216d311b2..fd8e9bf7f2 100644 --- a/git.c +++ b/git.c @@ -50,8 +50,10 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "count-objects", cmd_count_objects }, { "diff", cmd_diff }, { "grep", cmd_grep }, - { "rev-list", cmd_rev_list }, { "init-db", cmd_init_db }, + { "tar-tree", cmd_tar_tree }, + { "upload-tar", cmd_upload_tar }, + { "rev-list", cmd_rev_list }, { "check-ref-format", cmd_check_ref_format } }; int i; diff --git a/tar-tree.c b/tar-tree.c deleted file mode 100644 index 33087366c3..0000000000 --- a/tar-tree.c +++ /dev/null @@ -1,350 +0,0 @@ -/* - * Copyright (c) 2005, 2006 Rene Scharfe - */ -#include -#include "cache.h" -#include "tree-walk.h" -#include "commit.h" -#include "strbuf.h" -#include "tar.h" - -#define RECORDSIZE (512) -#define BLOCKSIZE (RECORDSIZE * 20) - -static const char tar_tree_usage[] = "git-tar-tree [basedir]"; - -static char block[BLOCKSIZE]; -static unsigned long offset; - -static time_t archive_time; - -/* tries hard to write, either succeeds or dies in the attempt */ -static void reliable_write(void *buf, unsigned long size) -{ - while (size > 0) { - long ret = xwrite(1, buf, size); - if (ret < 0) { - if (errno == EPIPE) - exit(0); - die("git-tar-tree: %s", strerror(errno)); - } else if (!ret) { - die("git-tar-tree: disk full?"); - } - size -= ret; - buf += ret; - } -} - -/* writes out the whole block, but only if it is full */ -static void write_if_needed(void) -{ - if (offset == BLOCKSIZE) { - reliable_write(block, BLOCKSIZE); - offset = 0; - } -} - -/* acquire the next record from the buffer; user must call write_if_needed() */ -static char *get_record(void) -{ - char *p = block + offset; - memset(p, 0, RECORDSIZE); - offset += RECORDSIZE; - return p; -} - -/* - * The end of tar archives is marked by 1024 nul bytes and after that - * follows the rest of the block (if any). - */ -static void write_trailer(void) -{ - get_record(); - write_if_needed(); - get_record(); - write_if_needed(); - while (offset) { - get_record(); - write_if_needed(); - } -} - -/* - * queues up writes, so that all our write(2) calls write exactly one - * full block; pads writes to RECORDSIZE - */ -static void write_blocked(void *buf, unsigned long size) -{ - unsigned long tail; - - if (offset) { - unsigned long chunk = BLOCKSIZE - offset; - if (size < chunk) - chunk = size; - memcpy(block + offset, buf, chunk); - size -= chunk; - offset += chunk; - buf += chunk; - write_if_needed(); - } - while (size >= BLOCKSIZE) { - reliable_write(buf, BLOCKSIZE); - size -= BLOCKSIZE; - buf += BLOCKSIZE; - } - if (size) { - memcpy(block + offset, buf, size); - offset += size; - } - tail = offset % RECORDSIZE; - if (tail) { - memset(block + offset, 0, RECORDSIZE - tail); - offset += RECORDSIZE - tail; - } - write_if_needed(); -} - -static void strbuf_append_string(struct strbuf *sb, const char *s) -{ - int slen = strlen(s); - int total = sb->len + slen; - if (total > sb->alloc) { - sb->buf = xrealloc(sb->buf, total); - sb->alloc = total; - } - memcpy(sb->buf + sb->len, s, slen); - sb->len = total; -} - -/* - * pax extended header records have the format "%u %s=%s\n". %u contains - * the size of the whole string (including the %u), the first %s is the - * keyword, the second one is the value. This function constructs such a - * string and appends it to a struct strbuf. - */ -static void strbuf_append_ext_header(struct strbuf *sb, const char *keyword, - const char *value, unsigned int valuelen) -{ - char *p; - int len, total, tmp; - - /* "%u %s=%s\n" */ - len = 1 + 1 + strlen(keyword) + 1 + valuelen + 1; - for (tmp = len; tmp > 9; tmp /= 10) - len++; - - total = sb->len + len; - if (total > sb->alloc) { - sb->buf = xrealloc(sb->buf, total); - sb->alloc = total; - } - - p = sb->buf; - p += sprintf(p, "%u %s=", len, keyword); - memcpy(p, value, valuelen); - p += valuelen; - *p = '\n'; - sb->len = total; -} - -static unsigned int ustar_header_chksum(const struct ustar_header *header) -{ - char *p = (char *)header; - unsigned int chksum = 0; - while (p < header->chksum) - chksum += *p++; - chksum += sizeof(header->chksum) * ' '; - p += sizeof(header->chksum); - while (p < (char *)header + sizeof(struct ustar_header)) - chksum += *p++; - return chksum; -} - -static int get_path_prefix(const struct strbuf *path, int maxlen) -{ - int i = path->len; - if (i > maxlen) - i = maxlen; - while (i > 0 && path->buf[i] != '/') - i--; - return i; -} - -static void write_entry(const unsigned char *sha1, struct strbuf *path, - unsigned int mode, void *buffer, unsigned long size) -{ - struct ustar_header header; - struct strbuf ext_header; - - memset(&header, 0, sizeof(header)); - ext_header.buf = NULL; - ext_header.len = ext_header.alloc = 0; - - if (!sha1) { - *header.typeflag = TYPEFLAG_GLOBAL_HEADER; - mode = 0100666; - strcpy(header.name, "pax_global_header"); - } else if (!path) { - *header.typeflag = TYPEFLAG_EXT_HEADER; - mode = 0100666; - sprintf(header.name, "%s.paxheader", sha1_to_hex(sha1)); - } else { - if (S_ISDIR(mode)) { - *header.typeflag = TYPEFLAG_DIR; - mode |= 0777; - } else if (S_ISLNK(mode)) { - *header.typeflag = TYPEFLAG_LNK; - mode |= 0777; - } else if (S_ISREG(mode)) { - *header.typeflag = TYPEFLAG_REG; - mode |= (mode & 0100) ? 0777 : 0666; - } else { - error("unsupported file mode: 0%o (SHA1: %s)", - mode, sha1_to_hex(sha1)); - return; - } - if (path->len > sizeof(header.name)) { - int plen = get_path_prefix(path, sizeof(header.prefix)); - int rest = path->len - plen - 1; - if (plen > 0 && rest <= sizeof(header.name)) { - memcpy(header.prefix, path->buf, plen); - memcpy(header.name, path->buf + plen + 1, rest); - } else { - sprintf(header.name, "%s.data", - sha1_to_hex(sha1)); - strbuf_append_ext_header(&ext_header, "path", - path->buf, path->len); - } - } else - memcpy(header.name, path->buf, path->len); - } - - if (S_ISLNK(mode) && buffer) { - if (size > sizeof(header.linkname)) { - sprintf(header.linkname, "see %s.paxheader", - sha1_to_hex(sha1)); - strbuf_append_ext_header(&ext_header, "linkpath", - buffer, size); - } else - memcpy(header.linkname, buffer, size); - } - - sprintf(header.mode, "%07o", mode & 07777); - sprintf(header.size, "%011lo", S_ISREG(mode) ? size : 0); - sprintf(header.mtime, "%011lo", archive_time); - - /* XXX: should we provide more meaningful info here? */ - sprintf(header.uid, "%07o", 0); - sprintf(header.gid, "%07o", 0); - strncpy(header.uname, "git", 31); - strncpy(header.gname, "git", 31); - sprintf(header.devmajor, "%07o", 0); - sprintf(header.devminor, "%07o", 0); - - memcpy(header.magic, "ustar", 6); - memcpy(header.version, "00", 2); - - sprintf(header.chksum, "%07o", ustar_header_chksum(&header)); - - if (ext_header.len > 0) { - write_entry(sha1, NULL, 0, ext_header.buf, ext_header.len); - free(ext_header.buf); - } - write_blocked(&header, sizeof(header)); - if (S_ISREG(mode) && buffer && size > 0) - write_blocked(buffer, size); -} - -static void write_global_extended_header(const unsigned char *sha1) -{ - struct strbuf ext_header; - ext_header.buf = NULL; - ext_header.len = ext_header.alloc = 0; - strbuf_append_ext_header(&ext_header, "comment", sha1_to_hex(sha1), 40); - write_entry(NULL, NULL, 0, ext_header.buf, ext_header.len); - free(ext_header.buf); -} - -static void traverse_tree(struct tree_desc *tree, struct strbuf *path) -{ - int pathlen = path->len; - - while (tree->size) { - const char *name; - const unsigned char *sha1; - unsigned mode; - void *eltbuf; - char elttype[20]; - unsigned long eltsize; - - sha1 = tree_entry_extract(tree, &name, &mode); - update_tree_entry(tree); - - eltbuf = read_sha1_file(sha1, elttype, &eltsize); - if (!eltbuf) - die("cannot read %s", sha1_to_hex(sha1)); - - path->len = pathlen; - strbuf_append_string(path, name); - if (S_ISDIR(mode)) - strbuf_append_string(path, "/"); - - write_entry(sha1, path, mode, eltbuf, eltsize); - - if (S_ISDIR(mode)) { - struct tree_desc subtree; - subtree.buf = eltbuf; - subtree.size = eltsize; - traverse_tree(&subtree, path); - } - free(eltbuf); - } -} - -int main(int argc, char **argv) -{ - unsigned char sha1[20], tree_sha1[20]; - struct commit *commit; - struct tree_desc tree; - struct strbuf current_path; - - current_path.buf = xmalloc(PATH_MAX); - current_path.alloc = PATH_MAX; - current_path.len = current_path.eof = 0; - - setup_git_directory(); - git_config(git_default_config); - - switch (argc) { - case 3: - strbuf_append_string(¤t_path, argv[2]); - strbuf_append_string(¤t_path, "/"); - /* FALLTHROUGH */ - case 2: - if (get_sha1(argv[1], sha1)) - die("Not a valid object name %s", argv[1]); - break; - default: - usage(tar_tree_usage); - } - - commit = lookup_commit_reference_gently(sha1, 1); - if (commit) { - write_global_extended_header(commit->object.sha1); - archive_time = commit->date; - } else - archive_time = time(NULL); - - tree.buf = read_object_with_reference(sha1, tree_type, &tree.size, - tree_sha1); - if (!tree.buf) - die("not a reference to a tag, commit or tree object: %s", - sha1_to_hex(sha1)); - - if (current_path.len > 0) - write_entry(tree_sha1, ¤t_path, 040777, NULL, 0); - traverse_tree(&tree, ¤t_path); - write_trailer(); - free(current_path.buf); - return 0; -} -- cgit v1.3 From 685637381a967cd7388495f97b12b7cf177abbb4 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 21 May 2006 02:48:21 -0700 Subject: git-format-patch: now built-in. Signed-off-by: Junio C Hamano --- Makefile | 4 +- git-format-patch.sh | 344 ---------------------------------------------------- git.c | 2 +- 3 files changed, 3 insertions(+), 347 deletions(-) delete mode 100755 git-format-patch.sh (limited to 'Makefile') diff --git a/Makefile b/Makefile index 4fd6520b7e..fbb3dca2f3 100644 --- a/Makefile +++ b/Makefile @@ -116,7 +116,7 @@ SCRIPT_SH = \ git-add.sh git-bisect.sh git-branch.sh git-checkout.sh \ git-cherry.sh git-clean.sh git-clone.sh git-commit.sh \ git-fetch.sh \ - git-format-patch.sh git-ls-remote.sh \ + git-ls-remote.sh \ git-merge-one-file.sh git-parse-remote.sh \ git-prune.sh git-pull.sh git-rebase.sh \ git-repack.sh git-request-pull.sh git-reset.sh \ @@ -171,7 +171,7 @@ PROGRAMS = \ BUILT_INS = git-log$X git-whatchanged$X git-show$X \ git-count-objects$X git-diff$X git-push$X \ git-grep$X git-rev-list$X git-check-ref-format$X \ - git-init-db$X + git-init-db$X git-format-patch$X # what 'all' will build and 'install' will install, in gitexecdir ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS) diff --git a/git-format-patch.sh b/git-format-patch.sh deleted file mode 100755 index 8a16eadfbd..0000000000 --- a/git-format-patch.sh +++ /dev/null @@ -1,344 +0,0 @@ -#!/bin/sh -# -# Copyright (c) 2005 Junio C Hamano -# - -USAGE='[-n | -k] [-o | --stdout] [--signoff] [--check] [--diff-options] [--attach] []' -LONG_USAGE='Prepare each commit with its patch since head forked from - head, one file per patch formatted to resemble UNIX mailbox -format, for e-mail submission or use with git-am. - -Each output file is numbered sequentially from 1, and uses the -first line of the commit message (massaged for pathname safety) -as the filename. - -When -o is specified, output files are created in ; otherwise -they are created in the current working directory. This option -is ignored if --stdout is specified. - -When -n is specified, instead of "[PATCH] Subject", the first -line is formatted as "[PATCH N/M] Subject", unless you have only -one patch. - -When --attach is specified, patches are attached, not inlined.' - -. git-sh-setup - -# Force diff to run in C locale. -LANG=C LC_ALL=C -export LANG LC_ALL - -diff_opts= -LF=' -' - -outdir=./ -while case "$#" in 0) break;; esac -do - case "$1" in - -c|--c|--ch|--che|--chec|--check) - check=t ;; - -a|--a|--au|--aut|--auth|--autho|--author|\ - -d|--d|--da|--dat|--date|\ - -m|--m|--mb|--mbo|--mbox) # now noop - ;; - --at|--att|--atta|--attac|--attach) - attach=t ;; - -k|--k|--ke|--kee|--keep|--keep-|--keep-s|--keep-su|--keep-sub|\ - --keep-subj|--keep-subje|--keep-subjec|--keep-subject) - keep_subject=t ;; - -n|--n|--nu|--num|--numb|--numbe|--number|--numbere|--numbered) - numbered=t ;; - -s|--s|--si|--sig|--sign|--signo|--signof|--signoff) - signoff=t ;; - --st|--std|--stdo|--stdou|--stdout) - stdout=t ;; - -o=*|--o=*|--ou=*|--out=*|--outp=*|--outpu=*|--output=*|--output-=*|\ - --output-d=*|--output-di=*|--output-dir=*|--output-dire=*|\ - --output-direc=*|--output-direct=*|--output-directo=*|\ - --output-director=*|--output-directory=*) - outdir=`expr "$1" : '-[^=]*=\(.*\)'` ;; - -o|--o|--ou|--out|--outp|--outpu|--output|--output-|--output-d|\ - --output-di|--output-dir|--output-dire|--output-direc|--output-direct|\ - --output-directo|--output-director|--output-directory) - case "$#" in 1) usage ;; esac; shift - outdir="$1" ;; - -h|--h|--he|--hel|--help) - usage - ;; - -*' '* | -*"$LF"* | -*' '*) - # Ignore diff option that has whitespace for now. - ;; - -*) diff_opts="$diff_opts$1 " ;; - *) break ;; - esac - shift -done - -case "$keep_subject$numbered" in -tt) - die '--keep-subject and --numbered are incompatible.' ;; -esac - -tmp=.tmp-series$$ -trap 'rm -f $tmp-*' 0 1 2 3 15 - -series=$tmp-series -commsg=$tmp-commsg -filelist=$tmp-files - -# Backward compatible argument parsing hack. -# -# Historically, we supported: -# 1. "rev1" is equivalent to "rev1..HEAD" -# 2. "rev1..rev2" -# 3. "rev1" "rev2 is equivalent to "rev1..rev2" -# -# We want to take a sequence of "rev1..rev2" in general. -# Also, "rev1.." should mean "rev1..HEAD"; git-diff users are -# familiar with that syntax. - -case "$#,$1$2" in -1,?*..?*) - # single "rev1..rev2" - ;; -1,?*..) - # single "rev1.." should mean "rev1..HEAD" - set x "$1"HEAD - shift - ;; -1,*) - # single rev1 - set x "$1..HEAD" - shift - ;; -2,?*..?*) - # not traditional "rev1" "rev2" - ;; -2,*) - set x "$1..$2" - shift - ;; -esac - -# Now we have what we want in $@ -for revpair -do - case "$revpair" in - ?*..?*) - rev1=`expr "z$revpair" : 'z\(.*\)\.\.'` - rev2=`expr "z$revpair" : 'z.*\.\.\(.*\)'` - ;; - *) - rev1="$revpair^" - rev2="$revpair" - ;; - esac - git-rev-parse --verify "$rev1^0" >/dev/null 2>&1 || - die "Not a valid rev $rev1 ($revpair)" - git-rev-parse --verify "$rev2^0" >/dev/null 2>&1 || - die "Not a valid rev $rev2 ($revpair)" - git-cherry -v "$rev1" "$rev2" | - while read sign rev comment - do - case "$sign" in - '-') - echo >&2 "Merged already: $comment" - ;; - *) - echo $rev - ;; - esac - done -done >$series - -me=`git-var GIT_AUTHOR_IDENT | sed -e 's/>.*/>/'` -headers=`git-repo-config --get format.headers` -case "$attach" in -"") ;; -*) - mimemagic="050802040500080604070107" -esac - -case "$outdir" in -*/) ;; -*) outdir="$outdir/" ;; -esac -test -d "$outdir" || mkdir -p "$outdir" || exit - -titleScript=' - /./d - /^$/n - s/^\[PATCH[^]]*\] *// - s/[^-a-z.A-Z_0-9]/-/g - s/\.\.\.*/\./g - s/\.*$// - s/--*/-/g - s/^-// - s/-$// - s/$/./ - p - q -' - -process_one () { - perl -w -e ' -my ($keep_subject, $num, $signoff, $headers, $mimemagic, $commsg) = @ARGV; -my ($signoff_pattern, $done_header, $done_subject, $done_separator, $signoff_seen, - $last_was_signoff); - -if ($signoff) { - $signoff = "Signed-off-by: " . `git-var GIT_COMMITTER_IDENT`; - $signoff =~ s/>.*/>/; - $signoff_pattern = quotemeta($signoff); -} - -my @weekday_names = qw(Sun Mon Tue Wed Thu Fri Sat); -my @month_names = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec); - -sub show_date { - my ($time, $tz) = @_; - my $minutes = abs($tz); - $minutes = int($minutes / 100) * 60 + ($minutes % 100); - if ($tz < 0) { - $minutes = -$minutes; - } - my $t = $time + $minutes * 60; - my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday) = gmtime($t); - return sprintf("%s, %d %s %d %02d:%02d:%02d %+05d", - $weekday_names[$wday], $mday, - $month_names[$mon], $year+1900, - $hour, $min, $sec, $tz); -} - -print "From nobody Mon Sep 17 00:00:00 2001\n"; -open FH, "git stripspace <$commsg |" or die "open $commsg pipe"; -while () { - unless ($done_header) { - if (/^$/) { - $done_header = 1; - } - elsif (/^author (.*>) (.*)$/) { - my ($author_ident, $author_date) = ($1, $2); - my ($utc, $off) = ($author_date =~ /^(\d+) ([-+]?\d+)$/); - $author_date = show_date($utc, $off); - - print "From: $author_ident\n"; - print "Date: $author_date\n"; - } - next; - } - unless ($done_subject) { - unless ($keep_subject) { - s/^\[PATCH[^]]*\]\s*//; - s/^/[PATCH$num] /; - } - if ($headers) { - print "$headers\n"; - } - print "Subject: $_"; - if ($mimemagic) { - print "MIME-Version: 1.0\n"; - print "Content-Type: multipart/mixed;\n"; - print " boundary=\"------------$mimemagic\"\n"; - print "\n"; - print "This is a multi-part message in MIME format.\n"; - print "--------------$mimemagic\n"; - print "Content-Type: text/plain; charset=UTF-8; format=fixed\n"; - print "Content-Transfer-Encoding: 8bit\n"; - } - $done_subject = 1; - next; - } - unless ($done_separator) { - print "\n"; - $done_separator = 1; - next if (/^$/); - } - - $last_was_signoff = 0; - if (/Signed-off-by:/i) { - if ($signoff ne "" && /Signed-off-by:\s*$signoff_pattern$/i) { - $signoff_seen = 1; - } - } - print $_; -} -if (!$signoff_seen && $signoff ne "") { - if (!$last_was_signoff) { - print "\n"; - } - print "$signoff\n"; -} -print "\n---\n\n"; -close FH or die "close $commsg pipe"; -' "$keep_subject" "$num" "$signoff" "$headers" "$mimemagic" $commsg - - git-diff-tree -p --stat --summary $diff_opts "$commit" - echo - case "$mimemagic" in - '');; - *) - echo "--------------$mimemagic" - echo "Content-Type: text/x-patch;" - echo " name=\"$commit.diff\"" - echo "Content-Transfer-Encoding: 8bit" - echo "Content-Disposition: inline;" - echo " filename=\"$commit.diff\"" - echo - esac - git-diff-tree -p $diff_opts "$commit" - case "$mimemagic" in - '') - echo "-- " - echo "@@GIT_VERSION@@" - ;; - *) - echo - echo "--------------$mimemagic--" - echo - ;; - esac - echo -} - -total=`wc -l <$series | tr -dc "[0-9]"` -case "$total,$numbered" in -1,*) - numfmt='' ;; -*,t) - numfmt=`echo "$total" | wc -c` - numfmt=$(($numfmt-1)) - numfmt=" %0${numfmt}d/$total" -esac - -i=1 -while read commit -do - git-cat-file commit "$commit" | git-stripspace >$commsg - title=`sed -ne "$titleScript" <$commsg` - case "$numbered" in - '') num= ;; - *) - num=`printf "$numfmt" $i` ;; - esac - - file=`printf '%04d-%stxt' $i "$title"` - if test '' = "$stdout" - then - echo "$file" - process_one >"$outdir$file" - if test t = "$check" - then - # This is slightly modified from Andrew Morton's Perfect Patch. - # Lines you introduce should not have trailing whitespace. - # Also check for an indentation that has SP before a TAB. - grep -n '^+\([ ]* .*\|.*[ ]\)$' "$outdir$file" - : - fi - else - echo >&2 "$file" - process_one - fi - i=`expr "$i" + 1` -done <$series diff --git a/git.c b/git.c index f4dff02bd3..ff498e674a 100644 --- a/git.c +++ b/git.c @@ -47,7 +47,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "whatchanged", cmd_whatchanged }, { "show", cmd_show }, { "push", cmd_push }, - { "fmt-patch", cmd_format_patch }, + { "format-patch", cmd_format_patch }, { "count-objects", cmd_count_objects }, { "diff", cmd_diff }, { "grep", cmd_grep }, -- cgit v1.3 From 6ba68ab2884e6c1db942e3bff63d4aa0bf354094 Mon Sep 17 00:00:00 2001 From: Yakov Lerner Date: Mon, 22 May 2006 00:37:00 +0300 Subject: NO_INET_NTOP and compat/inet_ntop.c for some systems (e.g. old Cygwin). For systems which lack inet_ntop(), this adds compat/inet_ntop.c, and related build constant, NO_INET_NTOP. Older Cygwin(s) lack inet_ntop(). Signed-off-by: Yakov Lerner Signed-off-by: Junio C Hamano --- Makefile | 3 + compat/inet_ntop.c | 200 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 203 insertions(+) create mode 100644 compat/inet_ntop.c (limited to 'Makefile') diff --git a/Makefile b/Makefile index 4fd6520b7e..efe6b12719 100644 --- a/Makefile +++ b/Makefile @@ -420,6 +420,9 @@ else ALL_CFLAGS += -Dsockaddr_storage=sockaddr_in6 endif endif +ifdef NO_INET_NTOP + LIB_OBJS += compat/inet_ntop.o +endif ifdef NO_ICONV ALL_CFLAGS += -DNO_ICONV diff --git a/compat/inet_ntop.c b/compat/inet_ntop.c new file mode 100644 index 0000000000..ec8c1bff53 --- /dev/null +++ b/compat/inet_ntop.c @@ -0,0 +1,200 @@ +/* + * Copyright (c) 1996-1999 by Internet Software Consortium. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS + * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE + * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR + * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS + * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS + * SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef NS_INADDRSZ +#define NS_INADDRSZ 4 +#endif +#ifndef NS_IN6ADDRSZ +#define NS_IN6ADDRSZ 16 +#endif +#ifndef NS_INT16SZ +#define NS_INT16SZ 2 +#endif + +/* + * WARNING: Don't even consider trying to compile this on a system where + * sizeof(int) < 4. sizeof(int) > 4 is fine; all the world's not a VAX. + */ + +/* const char * + * inet_ntop4(src, dst, size) + * format an IPv4 address + * return: + * `dst' (as a const) + * notes: + * (1) uses no statics + * (2) takes a u_char* not an in_addr as input + * author: + * Paul Vixie, 1996. + */ +static const char * +inet_ntop4(src, dst, size) + const u_char *src; + char *dst; + size_t size; +{ + static const char fmt[] = "%u.%u.%u.%u"; + char tmp[sizeof "255.255.255.255"]; + int nprinted; + + nprinted = snprintf(tmp, sizeof(tmp), fmt, src[0], src[1], src[2], src[3]); + if (nprinted < 0) + return (NULL); /* we assume "errno" was set by "snprintf()" */ + if ((size_t)nprinted > size) { + errno = ENOSPC; + return (NULL); + } + strcpy(dst, tmp); + return (dst); +} + +#ifndef NO_IPV6 +/* const char * + * inet_ntop6(src, dst, size) + * convert IPv6 binary address into presentation (printable) format + * author: + * Paul Vixie, 1996. + */ +static const char * +inet_ntop6(src, dst, size) + const u_char *src; + char *dst; + size_t size; +{ + /* + * Note that int32_t and int16_t need only be "at least" large enough + * to contain a value of the specified size. On some systems, like + * Crays, there is no such thing as an integer variable with 16 bits. + * Keep this in mind if you think this function should have been coded + * to use pointer overlays. All the world's not a VAX. + */ + char tmp[sizeof "ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255"], *tp; + struct { int base, len; } best, cur; + u_int words[NS_IN6ADDRSZ / NS_INT16SZ]; + int i; + + /* + * Preprocess: + * Copy the input (bytewise) array into a wordwise array. + * Find the longest run of 0x00's in src[] for :: shorthanding. + */ + memset(words, '\0', sizeof words); + for (i = 0; i < NS_IN6ADDRSZ; i++) + words[i / 2] |= (src[i] << ((1 - (i % 2)) << 3)); + best.base = -1; + cur.base = -1; + for (i = 0; i < (NS_IN6ADDRSZ / NS_INT16SZ); i++) { + if (words[i] == 0) { + if (cur.base == -1) + cur.base = i, cur.len = 1; + else + cur.len++; + } else { + if (cur.base != -1) { + if (best.base == -1 || cur.len > best.len) + best = cur; + cur.base = -1; + } + } + } + if (cur.base != -1) { + if (best.base == -1 || cur.len > best.len) + best = cur; + } + if (best.base != -1 && best.len < 2) + best.base = -1; + + /* + * Format the result. + */ + tp = tmp; + for (i = 0; i < (NS_IN6ADDRSZ / NS_INT16SZ); i++) { + /* Are we inside the best run of 0x00's? */ + if (best.base != -1 && i >= best.base && + i < (best.base + best.len)) { + if (i == best.base) + *tp++ = ':'; + continue; + } + /* Are we following an initial run of 0x00s or any real hex? */ + if (i != 0) + *tp++ = ':'; + /* Is this address an encapsulated IPv4? */ + if (i == 6 && best.base == 0 && + (best.len == 6 || (best.len == 5 && words[5] == 0xffff))) { + if (!inet_ntop4(src+12, tp, sizeof tmp - (tp - tmp))) + return (NULL); + tp += strlen(tp); + break; + } + tp += snprintf(tp, sizeof tmp - (tp - tmp), "%x", words[i]); + } + /* Was it a trailing run of 0x00's? */ + if (best.base != -1 && (best.base + best.len) == + (NS_IN6ADDRSZ / NS_INT16SZ)) + *tp++ = ':'; + *tp++ = '\0'; + + /* + * Check for overflow, copy, and we're done. + */ + if ((size_t)(tp - tmp) > size) { + errno = ENOSPC; + return (NULL); + } + strcpy(dst, tmp); + return (dst); +} +#endif + +/* char * + * inet_ntop(af, src, dst, size) + * convert a network format address to presentation format. + * return: + * pointer to presentation format address (`dst'), or NULL (see errno). + * author: + * Paul Vixie, 1996. + */ +const char * +inet_ntop(af, src, dst, size) + int af; + const void *src; + char *dst; + size_t size; +{ + switch (af) { + case AF_INET: + return (inet_ntop4(src, dst, size)); +#ifndef NO_IPV6 + case AF_INET6: + return (inet_ntop6(src, dst, size)); +#endif + default: + errno = EAFNOSUPPORT; + return (NULL); + } + /* NOTREACHED */ +} -- cgit v1.3 From 7f7e6eacf999cb53771426e561589f721e6c9974 Mon Sep 17 00:00:00 2001 From: Sean Date: Mon, 22 May 2006 00:42:59 -0400 Subject: Install git builtins into gitexecdir rather than bindir. Moving "git-cmd" commands out of the path and into a special git exec path, should include the builtins. [jc: fixed the case where bindir == gitexecdir - ln -f fails with a complaint that src and dst are the same, likewise for the fallback cp.] Signed-off-by: Junio C Hamano --- Makefile | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'Makefile') diff --git a/Makefile b/Makefile index efe6b12719..5423b7a79b 100644 --- a/Makefile +++ b/Makefile @@ -627,7 +627,14 @@ install: all $(MAKE) -C templates install $(INSTALL) -d -m755 '$(DESTDIR_SQ)$(GIT_PYTHON_DIR_SQ)' $(INSTALL) $(PYMODULES) '$(DESTDIR_SQ)$(GIT_PYTHON_DIR_SQ)' - $(foreach p,$(BUILT_INS), rm -f '$(DESTDIR_SQ)$(bindir_SQ)/$p' && ln '$(DESTDIR_SQ)$(bindir_SQ)/git$X' '$(DESTDIR_SQ)$(bindir_SQ)/$p' ;) + if test 'z$(bindir_SQ)' != 'z$(gitexecdir_SQ)'; \ + then \ + ln -f '$(DESTDIR_SQ)$(bindir_SQ)/git$X' \ + '$(DESTDIR_SQ)$(gitexecdir_SQ)/git$X' || \ + cp '$(DESTDIR_SQ)$(bindir_SQ)/git$X' \ + '$(DESTDIR_SQ)$(gitexecdir_SQ)/git$X'; \ + fi + $(foreach p,$(BUILT_INS), rm -f '$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' && ln '$(DESTDIR_SQ)$(gitexecdir_SQ)/git$X' '$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' ;) install-doc: $(MAKE) -C Documentation install -- cgit v1.3 From 0864f26421b3c599b462bc867de948d14b268d76 Mon Sep 17 00:00:00 2001 From: Peter Eriksen Date: Tue, 23 May 2006 14:15:29 +0200 Subject: Builtin git-ls-files. Signed-off-by: Peter Eriksen Signed-off-by: Junio C Hamano --- Makefile | 6 +- builtin-ls-files.c | 824 +++++++++++++++++++++++++++++++++++++++++++++++++++++ builtin.h | 1 + git.c | 3 +- ls-files.c | 823 ---------------------------------------------------- 5 files changed, 830 insertions(+), 827 deletions(-) create mode 100644 builtin-ls-files.c delete mode 100644 ls-files.c (limited to 'Makefile') diff --git a/Makefile b/Makefile index 5423b7a79b..c540d7d9f1 100644 --- a/Makefile +++ b/Makefile @@ -155,7 +155,7 @@ PROGRAMS = \ git-diff-index$X git-diff-stages$X \ git-diff-tree$X git-fetch-pack$X git-fsck-objects$X \ git-hash-object$X git-index-pack$X git-local-fetch$X \ - git-ls-files$X git-ls-tree$X git-mailinfo$X git-merge-base$X \ + git-ls-tree$X git-mailinfo$X git-merge-base$X \ git-merge-index$X git-mktag$X git-mktree$X git-pack-objects$X git-patch-id$X \ git-peek-remote$X git-prune-packed$X git-read-tree$X \ git-receive-pack$X git-rev-parse$X \ @@ -171,7 +171,7 @@ PROGRAMS = \ BUILT_INS = git-log$X git-whatchanged$X git-show$X \ git-count-objects$X git-diff$X git-push$X \ git-grep$X git-rev-list$X git-check-ref-format$X \ - git-init-db$X + git-init-db$X git-ls-files$X # what 'all' will build and 'install' will install, in gitexecdir ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS) @@ -220,7 +220,7 @@ LIB_OBJS = \ BUILTIN_OBJS = \ builtin-log.o builtin-help.o builtin-count.o builtin-diff.o builtin-push.o \ builtin-grep.o builtin-rev-list.o builtin-check-ref-format.o \ - builtin-init-db.o + builtin-init-db.o builtin-ls-files.o GITLIBS = $(LIB_FILE) $(XDIFF_LIB) LIBS = $(GITLIBS) -lz diff --git a/builtin-ls-files.c b/builtin-ls-files.c new file mode 100644 index 0000000000..3a0c5f2150 --- /dev/null +++ b/builtin-ls-files.c @@ -0,0 +1,824 @@ +/* + * This merges the file listing in the directory cache index + * with the actual working directory list, and shows different + * combinations of the two. + * + * Copyright (C) Linus Torvalds, 2005 + */ +#include +#include + +#include "cache.h" +#include "quote.h" +#include "builtin.h" + +static int abbrev = 0; +static int show_deleted = 0; +static int show_cached = 0; +static int show_others = 0; +static int show_ignored = 0; +static int show_stage = 0; +static int show_unmerged = 0; +static int show_modified = 0; +static int show_killed = 0; +static int show_other_directories = 0; +static int hide_empty_directories = 0; +static int show_valid_bit = 0; +static int line_terminator = '\n'; + +static int prefix_len = 0, prefix_offset = 0; +static const char *prefix = NULL; +static const char **pathspec = NULL; +static int error_unmatch = 0; +static char *ps_matched = NULL; + +static const char *tag_cached = ""; +static const char *tag_unmerged = ""; +static const char *tag_removed = ""; +static const char *tag_other = ""; +static const char *tag_killed = ""; +static const char *tag_modified = ""; + +static const char *exclude_per_dir = NULL; + +/* We maintain three exclude pattern lists: + * EXC_CMDL lists patterns explicitly given on the command line. + * EXC_DIRS lists patterns obtained from per-directory ignore files. + * EXC_FILE lists patterns from fallback ignore files. + */ +#define EXC_CMDL 0 +#define EXC_DIRS 1 +#define EXC_FILE 2 +static struct exclude_list { + int nr; + int alloc; + struct exclude { + const char *pattern; + const char *base; + int baselen; + } **excludes; +} exclude_list[3]; + +static void add_exclude(const char *string, const char *base, + int baselen, struct exclude_list *which) +{ + struct exclude *x = xmalloc(sizeof (*x)); + + x->pattern = string; + x->base = base; + x->baselen = baselen; + if (which->nr == which->alloc) { + which->alloc = alloc_nr(which->alloc); + which->excludes = realloc(which->excludes, + which->alloc * sizeof(x)); + } + which->excludes[which->nr++] = x; +} + +static int add_excludes_from_file_1(const char *fname, + const char *base, + int baselen, + struct exclude_list *which) +{ + int fd, i; + long size; + char *buf, *entry; + + fd = open(fname, O_RDONLY); + if (fd < 0) + goto err; + size = lseek(fd, 0, SEEK_END); + if (size < 0) + goto err; + lseek(fd, 0, SEEK_SET); + if (size == 0) { + close(fd); + return 0; + } + buf = xmalloc(size+1); + if (read(fd, buf, size) != size) + goto err; + close(fd); + + buf[size++] = '\n'; + entry = buf; + for (i = 0; i < size; i++) { + if (buf[i] == '\n') { + if (entry != buf + i && entry[0] != '#') { + buf[i - (i && buf[i-1] == '\r')] = 0; + add_exclude(entry, base, baselen, which); + } + entry = buf + i + 1; + } + } + return 0; + + err: + if (0 <= fd) + close(fd); + return -1; +} + +static void add_excludes_from_file(const char *fname) +{ + if (add_excludes_from_file_1(fname, "", 0, + &exclude_list[EXC_FILE]) < 0) + die("cannot use %s as an exclude file", fname); +} + +static int push_exclude_per_directory(const char *base, int baselen) +{ + char exclude_file[PATH_MAX]; + struct exclude_list *el = &exclude_list[EXC_DIRS]; + int current_nr = el->nr; + + if (exclude_per_dir) { + memcpy(exclude_file, base, baselen); + strcpy(exclude_file + baselen, exclude_per_dir); + add_excludes_from_file_1(exclude_file, base, baselen, el); + } + return current_nr; +} + +static void pop_exclude_per_directory(int stk) +{ + struct exclude_list *el = &exclude_list[EXC_DIRS]; + + while (stk < el->nr) + free(el->excludes[--el->nr]); +} + +/* Scan the list and let the last match determines the fate. + * Return 1 for exclude, 0 for include and -1 for undecided. + */ +static int excluded_1(const char *pathname, + int pathlen, + struct exclude_list *el) +{ + int i; + + if (el->nr) { + for (i = el->nr - 1; 0 <= i; i--) { + struct exclude *x = el->excludes[i]; + const char *exclude = x->pattern; + int to_exclude = 1; + + if (*exclude == '!') { + to_exclude = 0; + exclude++; + } + + if (!strchr(exclude, '/')) { + /* match basename */ + const char *basename = strrchr(pathname, '/'); + basename = (basename) ? basename+1 : pathname; + if (fnmatch(exclude, basename, 0) == 0) + return to_exclude; + } + else { + /* match with FNM_PATHNAME: + * exclude has base (baselen long) implicitly + * in front of it. + */ + int baselen = x->baselen; + if (*exclude == '/') + exclude++; + + if (pathlen < baselen || + (baselen && pathname[baselen-1] != '/') || + strncmp(pathname, x->base, baselen)) + continue; + + if (fnmatch(exclude, pathname+baselen, + FNM_PATHNAME) == 0) + return to_exclude; + } + } + } + return -1; /* undecided */ +} + +static int excluded(const char *pathname) +{ + int pathlen = strlen(pathname); + int st; + + for (st = EXC_CMDL; st <= EXC_FILE; st++) { + switch (excluded_1(pathname, pathlen, &exclude_list[st])) { + case 0: + return 0; + case 1: + return 1; + } + } + return 0; +} + +struct nond_on_fs { + int len; + char name[FLEX_ARRAY]; /* more */ +}; + +static struct nond_on_fs **dir; +static int nr_dir; +static int dir_alloc; + +static void add_name(const char *pathname, int len) +{ + struct nond_on_fs *ent; + + if (cache_name_pos(pathname, len) >= 0) + return; + + if (nr_dir == dir_alloc) { + dir_alloc = alloc_nr(dir_alloc); + dir = xrealloc(dir, dir_alloc*sizeof(ent)); + } + ent = xmalloc(sizeof(*ent) + len + 1); + ent->len = len; + memcpy(ent->name, pathname, len); + ent->name[len] = 0; + dir[nr_dir++] = ent; +} + +static int dir_exists(const char *dirname, int len) +{ + int pos = cache_name_pos(dirname, len); + if (pos >= 0) + return 1; + pos = -pos-1; + if (pos >= active_nr) /* can't */ + return 0; + return !strncmp(active_cache[pos]->name, dirname, len); +} + +/* + * Read a directory tree. We currently ignore anything but + * directories, regular files and symlinks. That's because git + * doesn't handle them at all yet. Maybe that will change some + * day. + * + * Also, we ignore the name ".git" (even if it is not a directory). + * That likely will not change. + */ +static int read_directory(const char *path, const char *base, int baselen) +{ + DIR *fdir = opendir(path); + int contents = 0; + + if (fdir) { + int exclude_stk; + struct dirent *de; + char fullname[MAXPATHLEN + 1]; + memcpy(fullname, base, baselen); + + exclude_stk = push_exclude_per_directory(base, baselen); + + while ((de = readdir(fdir)) != NULL) { + int len; + + if ((de->d_name[0] == '.') && + (de->d_name[1] == 0 || + !strcmp(de->d_name + 1, ".") || + !strcmp(de->d_name + 1, "git"))) + continue; + len = strlen(de->d_name); + memcpy(fullname + baselen, de->d_name, len+1); + if (excluded(fullname) != show_ignored) { + if (!show_ignored || DTYPE(de) != DT_DIR) { + continue; + } + } + + switch (DTYPE(de)) { + struct stat st; + int subdir, rewind_base; + default: + continue; + case DT_UNKNOWN: + if (lstat(fullname, &st)) + continue; + if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) + break; + if (!S_ISDIR(st.st_mode)) + continue; + /* fallthrough */ + case DT_DIR: + memcpy(fullname + baselen + len, "/", 2); + len++; + rewind_base = nr_dir; + subdir = read_directory(fullname, fullname, + baselen + len); + if (show_other_directories && + (subdir || !hide_empty_directories) && + !dir_exists(fullname, baselen + len)) { + // Rewind the read subdirectory + while (nr_dir > rewind_base) + free(dir[--nr_dir]); + break; + } + contents += subdir; + continue; + case DT_REG: + case DT_LNK: + break; + } + add_name(fullname, baselen + len); + contents++; + } + closedir(fdir); + + pop_exclude_per_directory(exclude_stk); + } + + return contents; +} + +static int cmp_name(const void *p1, const void *p2) +{ + const struct nond_on_fs *e1 = *(const struct nond_on_fs **)p1; + const struct nond_on_fs *e2 = *(const struct nond_on_fs **)p2; + + return cache_name_compare(e1->name, e1->len, + e2->name, e2->len); +} + +/* + * Match a pathspec against a filename. The first "len" characters + * are the common prefix + */ +static int match(const char **spec, char *ps_matched, + const char *filename, int len) +{ + const char *m; + + while ((m = *spec++) != NULL) { + int matchlen = strlen(m + len); + + if (!matchlen) + goto matched; + if (!strncmp(m + len, filename + len, matchlen)) { + if (m[len + matchlen - 1] == '/') + goto matched; + switch (filename[len + matchlen]) { + case '/': case '\0': + goto matched; + } + } + if (!fnmatch(m + len, filename + len, 0)) + goto matched; + if (ps_matched) + ps_matched++; + continue; + matched: + if (ps_matched) + *ps_matched = 1; + return 1; + } + return 0; +} + +static void show_dir_entry(const char *tag, struct nond_on_fs *ent) +{ + int len = prefix_len; + int offset = prefix_offset; + + if (len >= ent->len) + die("git-ls-files: internal error - directory entry not superset of prefix"); + + if (pathspec && !match(pathspec, ps_matched, ent->name, len)) + return; + + fputs(tag, stdout); + write_name_quoted("", 0, ent->name + offset, line_terminator, stdout); + putchar(line_terminator); +} + +static void show_other_files(void) +{ + int i; + for (i = 0; i < nr_dir; i++) { + /* We should not have a matching entry, but we + * may have an unmerged entry for this path. + */ + struct nond_on_fs *ent = dir[i]; + int pos = cache_name_pos(ent->name, ent->len); + struct cache_entry *ce; + if (0 <= pos) + die("bug in show-other-files"); + pos = -pos - 1; + if (pos < active_nr) { + ce = active_cache[pos]; + if (ce_namelen(ce) == ent->len && + !memcmp(ce->name, ent->name, ent->len)) + continue; /* Yup, this one exists unmerged */ + } + show_dir_entry(tag_other, ent); + } +} + +static void show_killed_files(void) +{ + int i; + for (i = 0; i < nr_dir; i++) { + struct nond_on_fs *ent = dir[i]; + char *cp, *sp; + int pos, len, killed = 0; + + for (cp = ent->name; cp - ent->name < ent->len; cp = sp + 1) { + sp = strchr(cp, '/'); + if (!sp) { + /* If ent->name is prefix of an entry in the + * cache, it will be killed. + */ + pos = cache_name_pos(ent->name, ent->len); + if (0 <= pos) + die("bug in show-killed-files"); + pos = -pos - 1; + while (pos < active_nr && + ce_stage(active_cache[pos])) + pos++; /* skip unmerged */ + if (active_nr <= pos) + break; + /* pos points at a name immediately after + * ent->name in the cache. Does it expect + * ent->name to be a directory? + */ + len = ce_namelen(active_cache[pos]); + if ((ent->len < len) && + !strncmp(active_cache[pos]->name, + ent->name, ent->len) && + active_cache[pos]->name[ent->len] == '/') + killed = 1; + break; + } + if (0 <= cache_name_pos(ent->name, sp - ent->name)) { + /* If any of the leading directories in + * ent->name is registered in the cache, + * ent->name will be killed. + */ + killed = 1; + break; + } + } + if (killed) + show_dir_entry(tag_killed, dir[i]); + } +} + +static void show_ce_entry(const char *tag, struct cache_entry *ce) +{ + int len = prefix_len; + int offset = prefix_offset; + + if (len >= ce_namelen(ce)) + die("git-ls-files: internal error - cache entry not superset of prefix"); + + if (pathspec && !match(pathspec, ps_matched, ce->name, len)) + return; + + if (tag && *tag && show_valid_bit && + (ce->ce_flags & htons(CE_VALID))) { + static char alttag[4]; + memcpy(alttag, tag, 3); + if (isalpha(tag[0])) + alttag[0] = tolower(tag[0]); + else if (tag[0] == '?') + alttag[0] = '!'; + else { + alttag[0] = 'v'; + alttag[1] = tag[0]; + alttag[2] = ' '; + alttag[3] = 0; + } + tag = alttag; + } + + if (!show_stage) { + fputs(tag, stdout); + write_name_quoted("", 0, ce->name + offset, + line_terminator, stdout); + putchar(line_terminator); + } + else { + printf("%s%06o %s %d\t", + tag, + ntohl(ce->ce_mode), + abbrev ? find_unique_abbrev(ce->sha1,abbrev) + : sha1_to_hex(ce->sha1), + ce_stage(ce)); + write_name_quoted("", 0, ce->name + offset, + line_terminator, stdout); + putchar(line_terminator); + } +} + +static void show_files(void) +{ + int i; + + /* For cached/deleted files we don't need to even do the readdir */ + if (show_others || show_killed) { + const char *path = ".", *base = ""; + int baselen = prefix_len; + + if (baselen) { + path = base = prefix; + if (exclude_per_dir) { + char *p, *pp = xmalloc(baselen+1); + memcpy(pp, prefix, baselen+1); + p = pp; + while (1) { + char save = *p; + *p = 0; + push_exclude_per_directory(pp, p-pp); + *p++ = save; + if (!save) + break; + p = strchr(p, '/'); + if (p) + p++; + else + p = pp + baselen; + } + free(pp); + } + } + read_directory(path, base, baselen); + qsort(dir, nr_dir, sizeof(struct nond_on_fs *), cmp_name); + if (show_others) + show_other_files(); + if (show_killed) + show_killed_files(); + } + 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) + continue; + if (show_unmerged && !ce_stage(ce)) + continue; + show_ce_entry(ce_stage(ce) ? tag_unmerged : tag_cached, ce); + } + } + if (show_deleted | show_modified) { + for (i = 0; i < active_nr; i++) { + struct cache_entry *ce = active_cache[i]; + struct stat st; + int err; + if (excluded(ce->name) != show_ignored) + continue; + err = lstat(ce->name, &st); + if (show_deleted && err) + show_ce_entry(tag_removed, ce); + if (show_modified && ce_modified(ce, &st, 0)) + show_ce_entry(tag_modified, ce); + } + } +} + +/* + * Prune the index to only contain stuff starting with "prefix" + */ +static void prune_cache(void) +{ + int pos = cache_name_pos(prefix, prefix_len); + unsigned int first, last; + + if (pos < 0) + pos = -pos-1; + active_cache += pos; + active_nr -= pos; + first = 0; + last = active_nr; + while (last > first) { + int next = (last + first) >> 1; + struct cache_entry *ce = active_cache[next]; + if (!strncmp(ce->name, prefix, prefix_len)) { + first = next+1; + continue; + } + last = next; + } + active_nr = last; +} + +static void verify_pathspec(void) +{ + const char **p, *n, *prev; + char *real_prefix; + unsigned long max; + + prev = NULL; + max = PATH_MAX; + for (p = pathspec; (n = *p) != NULL; p++) { + int i, len = 0; + for (i = 0; i < max; i++) { + char c = n[i]; + if (prev && prev[i] != c) + break; + if (!c || c == '*' || c == '?') + break; + if (c == '/') + len = i+1; + } + prev = n; + if (len < max) { + max = len; + if (!max) + break; + } + } + + if (prefix_offset > max || memcmp(prev, prefix, prefix_offset)) + die("git-ls-files: cannot generate relative filenames containing '..'"); + + real_prefix = NULL; + prefix_len = max; + if (max) { + real_prefix = xmalloc(max + 1); + memcpy(real_prefix, prev, max); + real_prefix[max] = 0; + } + prefix = real_prefix; +} + +static const char ls_files_usage[] = + "git-ls-files [-z] [-t] [-v] (--[cached|deleted|others|stage|unmerged|killed|modified])* " + "[ --ignored ] [--exclude=] [--exclude-from=] " + "[ --exclude-per-directory= ] [--full-name] [--abbrev] " + "[--] []*"; + +int cmd_ls_files(int argc, const char **argv, char** envp) +{ + int i; + int exc_given = 0; + + prefix = setup_git_directory(); + if (prefix) + prefix_offset = strlen(prefix); + git_config(git_default_config); + + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + + if (!strcmp(arg, "--")) { + i++; + break; + } + if (!strcmp(arg, "-z")) { + line_terminator = 0; + continue; + } + if (!strcmp(arg, "-t") || !strcmp(arg, "-v")) { + tag_cached = "H "; + tag_unmerged = "M "; + tag_removed = "R "; + tag_modified = "C "; + tag_other = "? "; + tag_killed = "K "; + if (arg[1] == 'v') + show_valid_bit = 1; + continue; + } + if (!strcmp(arg, "-c") || !strcmp(arg, "--cached")) { + show_cached = 1; + continue; + } + if (!strcmp(arg, "-d") || !strcmp(arg, "--deleted")) { + show_deleted = 1; + continue; + } + if (!strcmp(arg, "-m") || !strcmp(arg, "--modified")) { + show_modified = 1; + continue; + } + if (!strcmp(arg, "-o") || !strcmp(arg, "--others")) { + show_others = 1; + continue; + } + if (!strcmp(arg, "-i") || !strcmp(arg, "--ignored")) { + show_ignored = 1; + continue; + } + if (!strcmp(arg, "-s") || !strcmp(arg, "--stage")) { + show_stage = 1; + continue; + } + if (!strcmp(arg, "-k") || !strcmp(arg, "--killed")) { + show_killed = 1; + continue; + } + if (!strcmp(arg, "--directory")) { + show_other_directories = 1; + continue; + } + if (!strcmp(arg, "--no-empty-directory")) { + hide_empty_directories = 1; + continue; + } + if (!strcmp(arg, "-u") || !strcmp(arg, "--unmerged")) { + /* There's no point in showing unmerged unless + * you also show the stage information. + */ + show_stage = 1; + show_unmerged = 1; + continue; + } + if (!strcmp(arg, "-x") && i+1 < argc) { + exc_given = 1; + add_exclude(argv[++i], "", 0, &exclude_list[EXC_CMDL]); + continue; + } + if (!strncmp(arg, "--exclude=", 10)) { + exc_given = 1; + add_exclude(arg+10, "", 0, &exclude_list[EXC_CMDL]); + continue; + } + if (!strcmp(arg, "-X") && i+1 < argc) { + exc_given = 1; + add_excludes_from_file(argv[++i]); + continue; + } + if (!strncmp(arg, "--exclude-from=", 15)) { + exc_given = 1; + add_excludes_from_file(arg+15); + continue; + } + if (!strncmp(arg, "--exclude-per-directory=", 24)) { + exc_given = 1; + exclude_per_dir = arg + 24; + continue; + } + if (!strcmp(arg, "--full-name")) { + prefix_offset = 0; + continue; + } + if (!strcmp(arg, "--error-unmatch")) { + error_unmatch = 1; + continue; + } + if (!strncmp(arg, "--abbrev=", 9)) { + abbrev = strtoul(arg+9, NULL, 10); + if (abbrev && abbrev < MINIMUM_ABBREV) + abbrev = MINIMUM_ABBREV; + else if (abbrev > 40) + abbrev = 40; + continue; + } + if (!strcmp(arg, "--abbrev")) { + abbrev = DEFAULT_ABBREV; + continue; + } + if (*arg == '-') + usage(ls_files_usage); + break; + } + + pathspec = get_pathspec(prefix, argv + i); + + /* Verify that the pathspec matches the prefix */ + if (pathspec) + verify_pathspec(); + + /* Treat unmatching pathspec elements as errors */ + if (pathspec && error_unmatch) { + int num; + for (num = 0; pathspec[num]; num++) + ; + ps_matched = xcalloc(1, num); + } + + if (show_ignored && !exc_given) { + 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(); + + 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 --git a/builtin.h b/builtin.h index 60541262c4..a0713d3747 100644 --- a/builtin.h +++ b/builtin.h @@ -27,5 +27,6 @@ extern int cmd_grep(int argc, const char **argv, char **envp); extern int cmd_rev_list(int argc, const char **argv, char **envp); extern int cmd_check_ref_format(int argc, const char **argv, char **envp); extern int cmd_init_db(int argc, const char **argv, char **envp); +extern int cmd_ls_files(int argc, const char **argv, char **envp); #endif diff --git a/git.c b/git.c index 3216d311b2..9cfa9ebced 100644 --- a/git.c +++ b/git.c @@ -52,7 +52,8 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "grep", cmd_grep }, { "rev-list", cmd_rev_list }, { "init-db", cmd_init_db }, - { "check-ref-format", cmd_check_ref_format } + { "check-ref-format", cmd_check_ref_format }, + { "ls-files", cmd_ls_files } }; int i; diff --git a/ls-files.c b/ls-files.c deleted file mode 100644 index 4a4af1ca3b..0000000000 --- a/ls-files.c +++ /dev/null @@ -1,823 +0,0 @@ -/* - * This merges the file listing in the directory cache index - * with the actual working directory list, and shows different - * combinations of the two. - * - * Copyright (C) Linus Torvalds, 2005 - */ -#include -#include - -#include "cache.h" -#include "quote.h" - -static int abbrev = 0; -static int show_deleted = 0; -static int show_cached = 0; -static int show_others = 0; -static int show_ignored = 0; -static int show_stage = 0; -static int show_unmerged = 0; -static int show_modified = 0; -static int show_killed = 0; -static int show_other_directories = 0; -static int hide_empty_directories = 0; -static int show_valid_bit = 0; -static int line_terminator = '\n'; - -static int prefix_len = 0, prefix_offset = 0; -static const char *prefix = NULL; -static const char **pathspec = NULL; -static int error_unmatch = 0; -static char *ps_matched = NULL; - -static const char *tag_cached = ""; -static const char *tag_unmerged = ""; -static const char *tag_removed = ""; -static const char *tag_other = ""; -static const char *tag_killed = ""; -static const char *tag_modified = ""; - -static const char *exclude_per_dir = NULL; - -/* We maintain three exclude pattern lists: - * EXC_CMDL lists patterns explicitly given on the command line. - * EXC_DIRS lists patterns obtained from per-directory ignore files. - * EXC_FILE lists patterns from fallback ignore files. - */ -#define EXC_CMDL 0 -#define EXC_DIRS 1 -#define EXC_FILE 2 -static struct exclude_list { - int nr; - int alloc; - struct exclude { - const char *pattern; - const char *base; - int baselen; - } **excludes; -} exclude_list[3]; - -static void add_exclude(const char *string, const char *base, - int baselen, struct exclude_list *which) -{ - struct exclude *x = xmalloc(sizeof (*x)); - - x->pattern = string; - x->base = base; - x->baselen = baselen; - if (which->nr == which->alloc) { - which->alloc = alloc_nr(which->alloc); - which->excludes = realloc(which->excludes, - which->alloc * sizeof(x)); - } - which->excludes[which->nr++] = x; -} - -static int add_excludes_from_file_1(const char *fname, - const char *base, - int baselen, - struct exclude_list *which) -{ - int fd, i; - long size; - char *buf, *entry; - - fd = open(fname, O_RDONLY); - if (fd < 0) - goto err; - size = lseek(fd, 0, SEEK_END); - if (size < 0) - goto err; - lseek(fd, 0, SEEK_SET); - if (size == 0) { - close(fd); - return 0; - } - buf = xmalloc(size+1); - if (read(fd, buf, size) != size) - goto err; - close(fd); - - buf[size++] = '\n'; - entry = buf; - for (i = 0; i < size; i++) { - if (buf[i] == '\n') { - if (entry != buf + i && entry[0] != '#') { - buf[i - (i && buf[i-1] == '\r')] = 0; - add_exclude(entry, base, baselen, which); - } - entry = buf + i + 1; - } - } - return 0; - - err: - if (0 <= fd) - close(fd); - return -1; -} - -static void add_excludes_from_file(const char *fname) -{ - if (add_excludes_from_file_1(fname, "", 0, - &exclude_list[EXC_FILE]) < 0) - die("cannot use %s as an exclude file", fname); -} - -static int push_exclude_per_directory(const char *base, int baselen) -{ - char exclude_file[PATH_MAX]; - struct exclude_list *el = &exclude_list[EXC_DIRS]; - int current_nr = el->nr; - - if (exclude_per_dir) { - memcpy(exclude_file, base, baselen); - strcpy(exclude_file + baselen, exclude_per_dir); - add_excludes_from_file_1(exclude_file, base, baselen, el); - } - return current_nr; -} - -static void pop_exclude_per_directory(int stk) -{ - struct exclude_list *el = &exclude_list[EXC_DIRS]; - - while (stk < el->nr) - free(el->excludes[--el->nr]); -} - -/* Scan the list and let the last match determines the fate. - * Return 1 for exclude, 0 for include and -1 for undecided. - */ -static int excluded_1(const char *pathname, - int pathlen, - struct exclude_list *el) -{ - int i; - - if (el->nr) { - for (i = el->nr - 1; 0 <= i; i--) { - struct exclude *x = el->excludes[i]; - const char *exclude = x->pattern; - int to_exclude = 1; - - if (*exclude == '!') { - to_exclude = 0; - exclude++; - } - - if (!strchr(exclude, '/')) { - /* match basename */ - const char *basename = strrchr(pathname, '/'); - basename = (basename) ? basename+1 : pathname; - if (fnmatch(exclude, basename, 0) == 0) - return to_exclude; - } - else { - /* match with FNM_PATHNAME: - * exclude has base (baselen long) implicitly - * in front of it. - */ - int baselen = x->baselen; - if (*exclude == '/') - exclude++; - - if (pathlen < baselen || - (baselen && pathname[baselen-1] != '/') || - strncmp(pathname, x->base, baselen)) - continue; - - if (fnmatch(exclude, pathname+baselen, - FNM_PATHNAME) == 0) - return to_exclude; - } - } - } - return -1; /* undecided */ -} - -static int excluded(const char *pathname) -{ - int pathlen = strlen(pathname); - int st; - - for (st = EXC_CMDL; st <= EXC_FILE; st++) { - switch (excluded_1(pathname, pathlen, &exclude_list[st])) { - case 0: - return 0; - case 1: - return 1; - } - } - return 0; -} - -struct nond_on_fs { - int len; - char name[FLEX_ARRAY]; /* more */ -}; - -static struct nond_on_fs **dir; -static int nr_dir; -static int dir_alloc; - -static void add_name(const char *pathname, int len) -{ - struct nond_on_fs *ent; - - if (cache_name_pos(pathname, len) >= 0) - return; - - if (nr_dir == dir_alloc) { - dir_alloc = alloc_nr(dir_alloc); - dir = xrealloc(dir, dir_alloc*sizeof(ent)); - } - ent = xmalloc(sizeof(*ent) + len + 1); - ent->len = len; - memcpy(ent->name, pathname, len); - ent->name[len] = 0; - dir[nr_dir++] = ent; -} - -static int dir_exists(const char *dirname, int len) -{ - int pos = cache_name_pos(dirname, len); - if (pos >= 0) - return 1; - pos = -pos-1; - if (pos >= active_nr) /* can't */ - return 0; - return !strncmp(active_cache[pos]->name, dirname, len); -} - -/* - * Read a directory tree. We currently ignore anything but - * directories, regular files and symlinks. That's because git - * doesn't handle them at all yet. Maybe that will change some - * day. - * - * Also, we ignore the name ".git" (even if it is not a directory). - * That likely will not change. - */ -static int read_directory(const char *path, const char *base, int baselen) -{ - DIR *fdir = opendir(path); - int contents = 0; - - if (fdir) { - int exclude_stk; - struct dirent *de; - char fullname[MAXPATHLEN + 1]; - memcpy(fullname, base, baselen); - - exclude_stk = push_exclude_per_directory(base, baselen); - - while ((de = readdir(fdir)) != NULL) { - int len; - - if ((de->d_name[0] == '.') && - (de->d_name[1] == 0 || - !strcmp(de->d_name + 1, ".") || - !strcmp(de->d_name + 1, "git"))) - continue; - len = strlen(de->d_name); - memcpy(fullname + baselen, de->d_name, len+1); - if (excluded(fullname) != show_ignored) { - if (!show_ignored || DTYPE(de) != DT_DIR) { - continue; - } - } - - switch (DTYPE(de)) { - struct stat st; - int subdir, rewind_base; - default: - continue; - case DT_UNKNOWN: - if (lstat(fullname, &st)) - continue; - if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) - break; - if (!S_ISDIR(st.st_mode)) - continue; - /* fallthrough */ - case DT_DIR: - memcpy(fullname + baselen + len, "/", 2); - len++; - rewind_base = nr_dir; - subdir = read_directory(fullname, fullname, - baselen + len); - if (show_other_directories && - (subdir || !hide_empty_directories) && - !dir_exists(fullname, baselen + len)) { - // Rewind the read subdirectory - while (nr_dir > rewind_base) - free(dir[--nr_dir]); - break; - } - contents += subdir; - continue; - case DT_REG: - case DT_LNK: - break; - } - add_name(fullname, baselen + len); - contents++; - } - closedir(fdir); - - pop_exclude_per_directory(exclude_stk); - } - - return contents; -} - -static int cmp_name(const void *p1, const void *p2) -{ - const struct nond_on_fs *e1 = *(const struct nond_on_fs **)p1; - const struct nond_on_fs *e2 = *(const struct nond_on_fs **)p2; - - return cache_name_compare(e1->name, e1->len, - e2->name, e2->len); -} - -/* - * Match a pathspec against a filename. The first "len" characters - * are the common prefix - */ -static int match(const char **spec, char *ps_matched, - const char *filename, int len) -{ - const char *m; - - while ((m = *spec++) != NULL) { - int matchlen = strlen(m + len); - - if (!matchlen) - goto matched; - if (!strncmp(m + len, filename + len, matchlen)) { - if (m[len + matchlen - 1] == '/') - goto matched; - switch (filename[len + matchlen]) { - case '/': case '\0': - goto matched; - } - } - if (!fnmatch(m + len, filename + len, 0)) - goto matched; - if (ps_matched) - ps_matched++; - continue; - matched: - if (ps_matched) - *ps_matched = 1; - return 1; - } - return 0; -} - -static void show_dir_entry(const char *tag, struct nond_on_fs *ent) -{ - int len = prefix_len; - int offset = prefix_offset; - - if (len >= ent->len) - die("git-ls-files: internal error - directory entry not superset of prefix"); - - if (pathspec && !match(pathspec, ps_matched, ent->name, len)) - return; - - fputs(tag, stdout); - write_name_quoted("", 0, ent->name + offset, line_terminator, stdout); - putchar(line_terminator); -} - -static void show_other_files(void) -{ - int i; - for (i = 0; i < nr_dir; i++) { - /* We should not have a matching entry, but we - * may have an unmerged entry for this path. - */ - struct nond_on_fs *ent = dir[i]; - int pos = cache_name_pos(ent->name, ent->len); - struct cache_entry *ce; - if (0 <= pos) - die("bug in show-other-files"); - pos = -pos - 1; - if (pos < active_nr) { - ce = active_cache[pos]; - if (ce_namelen(ce) == ent->len && - !memcmp(ce->name, ent->name, ent->len)) - continue; /* Yup, this one exists unmerged */ - } - show_dir_entry(tag_other, ent); - } -} - -static void show_killed_files(void) -{ - int i; - for (i = 0; i < nr_dir; i++) { - struct nond_on_fs *ent = dir[i]; - char *cp, *sp; - int pos, len, killed = 0; - - for (cp = ent->name; cp - ent->name < ent->len; cp = sp + 1) { - sp = strchr(cp, '/'); - if (!sp) { - /* If ent->name is prefix of an entry in the - * cache, it will be killed. - */ - pos = cache_name_pos(ent->name, ent->len); - if (0 <= pos) - die("bug in show-killed-files"); - pos = -pos - 1; - while (pos < active_nr && - ce_stage(active_cache[pos])) - pos++; /* skip unmerged */ - if (active_nr <= pos) - break; - /* pos points at a name immediately after - * ent->name in the cache. Does it expect - * ent->name to be a directory? - */ - len = ce_namelen(active_cache[pos]); - if ((ent->len < len) && - !strncmp(active_cache[pos]->name, - ent->name, ent->len) && - active_cache[pos]->name[ent->len] == '/') - killed = 1; - break; - } - if (0 <= cache_name_pos(ent->name, sp - ent->name)) { - /* If any of the leading directories in - * ent->name is registered in the cache, - * ent->name will be killed. - */ - killed = 1; - break; - } - } - if (killed) - show_dir_entry(tag_killed, dir[i]); - } -} - -static void show_ce_entry(const char *tag, struct cache_entry *ce) -{ - int len = prefix_len; - int offset = prefix_offset; - - if (len >= ce_namelen(ce)) - die("git-ls-files: internal error - cache entry not superset of prefix"); - - if (pathspec && !match(pathspec, ps_matched, ce->name, len)) - return; - - if (tag && *tag && show_valid_bit && - (ce->ce_flags & htons(CE_VALID))) { - static char alttag[4]; - memcpy(alttag, tag, 3); - if (isalpha(tag[0])) - alttag[0] = tolower(tag[0]); - else if (tag[0] == '?') - alttag[0] = '!'; - else { - alttag[0] = 'v'; - alttag[1] = tag[0]; - alttag[2] = ' '; - alttag[3] = 0; - } - tag = alttag; - } - - if (!show_stage) { - fputs(tag, stdout); - write_name_quoted("", 0, ce->name + offset, - line_terminator, stdout); - putchar(line_terminator); - } - else { - printf("%s%06o %s %d\t", - tag, - ntohl(ce->ce_mode), - abbrev ? find_unique_abbrev(ce->sha1,abbrev) - : sha1_to_hex(ce->sha1), - ce_stage(ce)); - write_name_quoted("", 0, ce->name + offset, - line_terminator, stdout); - putchar(line_terminator); - } -} - -static void show_files(void) -{ - int i; - - /* For cached/deleted files we don't need to even do the readdir */ - if (show_others || show_killed) { - const char *path = ".", *base = ""; - int baselen = prefix_len; - - if (baselen) { - path = base = prefix; - if (exclude_per_dir) { - char *p, *pp = xmalloc(baselen+1); - memcpy(pp, prefix, baselen+1); - p = pp; - while (1) { - char save = *p; - *p = 0; - push_exclude_per_directory(pp, p-pp); - *p++ = save; - if (!save) - break; - p = strchr(p, '/'); - if (p) - p++; - else - p = pp + baselen; - } - free(pp); - } - } - read_directory(path, base, baselen); - qsort(dir, nr_dir, sizeof(struct nond_on_fs *), cmp_name); - if (show_others) - show_other_files(); - if (show_killed) - show_killed_files(); - } - 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) - continue; - if (show_unmerged && !ce_stage(ce)) - continue; - show_ce_entry(ce_stage(ce) ? tag_unmerged : tag_cached, ce); - } - } - if (show_deleted | show_modified) { - for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; - struct stat st; - int err; - if (excluded(ce->name) != show_ignored) - continue; - err = lstat(ce->name, &st); - if (show_deleted && err) - show_ce_entry(tag_removed, ce); - if (show_modified && ce_modified(ce, &st, 0)) - show_ce_entry(tag_modified, ce); - } - } -} - -/* - * Prune the index to only contain stuff starting with "prefix" - */ -static void prune_cache(void) -{ - int pos = cache_name_pos(prefix, prefix_len); - unsigned int first, last; - - if (pos < 0) - pos = -pos-1; - active_cache += pos; - active_nr -= pos; - first = 0; - last = active_nr; - while (last > first) { - int next = (last + first) >> 1; - struct cache_entry *ce = active_cache[next]; - if (!strncmp(ce->name, prefix, prefix_len)) { - first = next+1; - continue; - } - last = next; - } - active_nr = last; -} - -static void verify_pathspec(void) -{ - const char **p, *n, *prev; - char *real_prefix; - unsigned long max; - - prev = NULL; - max = PATH_MAX; - for (p = pathspec; (n = *p) != NULL; p++) { - int i, len = 0; - for (i = 0; i < max; i++) { - char c = n[i]; - if (prev && prev[i] != c) - break; - if (!c || c == '*' || c == '?') - break; - if (c == '/') - len = i+1; - } - prev = n; - if (len < max) { - max = len; - if (!max) - break; - } - } - - if (prefix_offset > max || memcmp(prev, prefix, prefix_offset)) - die("git-ls-files: cannot generate relative filenames containing '..'"); - - real_prefix = NULL; - prefix_len = max; - if (max) { - real_prefix = xmalloc(max + 1); - memcpy(real_prefix, prev, max); - real_prefix[max] = 0; - } - prefix = real_prefix; -} - -static const char ls_files_usage[] = - "git-ls-files [-z] [-t] [-v] (--[cached|deleted|others|stage|unmerged|killed|modified])* " - "[ --ignored ] [--exclude=] [--exclude-from=] " - "[ --exclude-per-directory= ] [--full-name] [--abbrev] " - "[--] []*"; - -int main(int argc, const char **argv) -{ - int i; - int exc_given = 0; - - prefix = setup_git_directory(); - if (prefix) - prefix_offset = strlen(prefix); - git_config(git_default_config); - - for (i = 1; i < argc; i++) { - const char *arg = argv[i]; - - if (!strcmp(arg, "--")) { - i++; - break; - } - if (!strcmp(arg, "-z")) { - line_terminator = 0; - continue; - } - if (!strcmp(arg, "-t") || !strcmp(arg, "-v")) { - tag_cached = "H "; - tag_unmerged = "M "; - tag_removed = "R "; - tag_modified = "C "; - tag_other = "? "; - tag_killed = "K "; - if (arg[1] == 'v') - show_valid_bit = 1; - continue; - } - if (!strcmp(arg, "-c") || !strcmp(arg, "--cached")) { - show_cached = 1; - continue; - } - if (!strcmp(arg, "-d") || !strcmp(arg, "--deleted")) { - show_deleted = 1; - continue; - } - if (!strcmp(arg, "-m") || !strcmp(arg, "--modified")) { - show_modified = 1; - continue; - } - if (!strcmp(arg, "-o") || !strcmp(arg, "--others")) { - show_others = 1; - continue; - } - if (!strcmp(arg, "-i") || !strcmp(arg, "--ignored")) { - show_ignored = 1; - continue; - } - if (!strcmp(arg, "-s") || !strcmp(arg, "--stage")) { - show_stage = 1; - continue; - } - if (!strcmp(arg, "-k") || !strcmp(arg, "--killed")) { - show_killed = 1; - continue; - } - if (!strcmp(arg, "--directory")) { - show_other_directories = 1; - continue; - } - if (!strcmp(arg, "--no-empty-directory")) { - hide_empty_directories = 1; - continue; - } - if (!strcmp(arg, "-u") || !strcmp(arg, "--unmerged")) { - /* There's no point in showing unmerged unless - * you also show the stage information. - */ - show_stage = 1; - show_unmerged = 1; - continue; - } - if (!strcmp(arg, "-x") && i+1 < argc) { - exc_given = 1; - add_exclude(argv[++i], "", 0, &exclude_list[EXC_CMDL]); - continue; - } - if (!strncmp(arg, "--exclude=", 10)) { - exc_given = 1; - add_exclude(arg+10, "", 0, &exclude_list[EXC_CMDL]); - continue; - } - if (!strcmp(arg, "-X") && i+1 < argc) { - exc_given = 1; - add_excludes_from_file(argv[++i]); - continue; - } - if (!strncmp(arg, "--exclude-from=", 15)) { - exc_given = 1; - add_excludes_from_file(arg+15); - continue; - } - if (!strncmp(arg, "--exclude-per-directory=", 24)) { - exc_given = 1; - exclude_per_dir = arg + 24; - continue; - } - if (!strcmp(arg, "--full-name")) { - prefix_offset = 0; - continue; - } - if (!strcmp(arg, "--error-unmatch")) { - error_unmatch = 1; - continue; - } - if (!strncmp(arg, "--abbrev=", 9)) { - abbrev = strtoul(arg+9, NULL, 10); - if (abbrev && abbrev < MINIMUM_ABBREV) - abbrev = MINIMUM_ABBREV; - else if (abbrev > 40) - abbrev = 40; - continue; - } - if (!strcmp(arg, "--abbrev")) { - abbrev = DEFAULT_ABBREV; - continue; - } - if (*arg == '-') - usage(ls_files_usage); - break; - } - - pathspec = get_pathspec(prefix, argv + i); - - /* Verify that the pathspec matches the prefix */ - if (pathspec) - verify_pathspec(); - - /* Treat unmatching pathspec elements as errors */ - if (pathspec && error_unmatch) { - int num; - for (num = 0; pathspec[num]; num++) - ; - ps_matched = xcalloc(1, num); - } - - if (show_ignored && !exc_given) { - 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(); - - 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; -} -- cgit v1.3 From aae01bda7f6d3224cf6b2ce0aa9aa668ce35d0b7 Mon Sep 17 00:00:00 2001 From: Peter Eriksen Date: Tue, 23 May 2006 14:15:30 +0200 Subject: Builtin git-ls-tree. Signed-off-by: Peter Eriksen Signed-off-by: Junio C Hamano --- Makefile | 6 +-- builtin-ls-tree.c | 156 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ builtin.h | 1 + git.c | 3 +- ls-tree.c | 155 ----------------------------------------------------- 5 files changed, 162 insertions(+), 159 deletions(-) create mode 100644 builtin-ls-tree.c delete mode 100644 ls-tree.c (limited to 'Makefile') diff --git a/Makefile b/Makefile index c540d7d9f1..2afd089a0d 100644 --- a/Makefile +++ b/Makefile @@ -155,7 +155,7 @@ PROGRAMS = \ git-diff-index$X git-diff-stages$X \ git-diff-tree$X git-fetch-pack$X git-fsck-objects$X \ git-hash-object$X git-index-pack$X git-local-fetch$X \ - git-ls-tree$X git-mailinfo$X git-merge-base$X \ + git-mailinfo$X git-merge-base$X \ git-merge-index$X git-mktag$X git-mktree$X git-pack-objects$X git-patch-id$X \ git-peek-remote$X git-prune-packed$X git-read-tree$X \ git-receive-pack$X git-rev-parse$X \ @@ -171,7 +171,7 @@ PROGRAMS = \ BUILT_INS = git-log$X git-whatchanged$X git-show$X \ git-count-objects$X git-diff$X git-push$X \ git-grep$X git-rev-list$X git-check-ref-format$X \ - git-init-db$X git-ls-files$X + git-init-db$X git-ls-files$X git-ls-tree$X # what 'all' will build and 'install' will install, in gitexecdir ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS) @@ -220,7 +220,7 @@ LIB_OBJS = \ BUILTIN_OBJS = \ builtin-log.o builtin-help.o builtin-count.o builtin-diff.o builtin-push.o \ builtin-grep.o builtin-rev-list.o builtin-check-ref-format.o \ - builtin-init-db.o builtin-ls-files.o + builtin-init-db.o builtin-ls-files.o builtin-ls-tree.o GITLIBS = $(LIB_FILE) $(XDIFF_LIB) LIBS = $(GITLIBS) -lz diff --git a/builtin-ls-tree.c b/builtin-ls-tree.c new file mode 100644 index 0000000000..48385d59f6 --- /dev/null +++ b/builtin-ls-tree.c @@ -0,0 +1,156 @@ +/* + * GIT - The information manager from hell + * + * Copyright (C) Linus Torvalds, 2005 + */ +#include "cache.h" +#include "blob.h" +#include "tree.h" +#include "quote.h" +#include "builtin.h" + +static int line_termination = '\n'; +#define LS_RECURSIVE 1 +#define LS_TREE_ONLY 2 +#define LS_SHOW_TREES 4 +#define LS_NAME_ONLY 8 +static int abbrev = 0; +static int ls_options = 0; +static const char **pathspec; +static int chomp_prefix = 0; +static const char *prefix; + +static const char ls_tree_usage[] = + "git-ls-tree [-d] [-r] [-t] [-z] [--name-only] [--name-status] [--full-name] [--abbrev[=]] [path...]"; + +static int show_recursive(const char *base, int baselen, const char *pathname) +{ + const char **s; + + if (ls_options & LS_RECURSIVE) + return 1; + + s = pathspec; + if (!s) + return 0; + + for (;;) { + const char *spec = *s++; + int len, speclen; + + if (!spec) + return 0; + if (strncmp(base, spec, baselen)) + continue; + len = strlen(pathname); + spec += baselen; + speclen = strlen(spec); + if (speclen <= len) + continue; + if (memcmp(pathname, spec, len)) + continue; + return 1; + } +} + +static int show_tree(unsigned char *sha1, const char *base, int baselen, + const char *pathname, unsigned mode, int stage) +{ + int retval = 0; + const char *type = blob_type; + + if (S_ISDIR(mode)) { + if (show_recursive(base, baselen, pathname)) { + retval = READ_TREE_RECURSIVE; + if (!(ls_options & LS_SHOW_TREES)) + return retval; + } + type = tree_type; + } + else if (ls_options & LS_TREE_ONLY) + return 0; + + if (chomp_prefix && + (baselen < chomp_prefix || memcmp(prefix, base, chomp_prefix))) + return 0; + + if (!(ls_options & LS_NAME_ONLY)) + printf("%06o %s %s\t", mode, type, + abbrev ? find_unique_abbrev(sha1,abbrev) + : sha1_to_hex(sha1)); + write_name_quoted(base + chomp_prefix, baselen - chomp_prefix, + pathname, + line_termination, stdout); + putchar(line_termination); + return retval; +} + +int cmd_ls_tree(int argc, const char **argv, char **envp) +{ + unsigned char sha1[20]; + struct tree *tree; + + prefix = setup_git_directory(); + git_config(git_default_config); + if (prefix && *prefix) + chomp_prefix = strlen(prefix); + while (1 < argc && argv[1][0] == '-') { + switch (argv[1][1]) { + case 'z': + line_termination = 0; + break; + case 'r': + ls_options |= LS_RECURSIVE; + break; + case 'd': + ls_options |= LS_TREE_ONLY; + break; + case 't': + ls_options |= LS_SHOW_TREES; + break; + case '-': + if (!strcmp(argv[1]+2, "name-only") || + !strcmp(argv[1]+2, "name-status")) { + ls_options |= LS_NAME_ONLY; + break; + } + if (!strcmp(argv[1]+2, "full-name")) { + chomp_prefix = 0; + break; + } + if (!strncmp(argv[1]+2, "abbrev=",7)) { + abbrev = strtoul(argv[1]+9, NULL, 10); + if (abbrev && abbrev < MINIMUM_ABBREV) + abbrev = MINIMUM_ABBREV; + else if (abbrev > 40) + abbrev = 40; + break; + } + if (!strcmp(argv[1]+2, "abbrev")) { + abbrev = DEFAULT_ABBREV; + break; + } + /* otherwise fallthru */ + default: + usage(ls_tree_usage); + } + argc--; argv++; + } + /* -d -r should imply -t, but -d by itself should not have to. */ + if ( (LS_TREE_ONLY|LS_RECURSIVE) == + ((LS_TREE_ONLY|LS_RECURSIVE) & ls_options)) + ls_options |= LS_SHOW_TREES; + + if (argc < 2) + usage(ls_tree_usage); + if (get_sha1(argv[1], sha1)) + die("Not a valid object name %s", argv[1]); + + pathspec = get_pathspec(prefix, argv + 2); + tree = parse_tree_indirect(sha1); + if (!tree) + die("not a tree object"); + read_tree_recursive(tree, "", 0, 0, pathspec, show_tree); + + return 0; +} diff --git a/builtin.h b/builtin.h index a0713d3747..951f206372 100644 --- a/builtin.h +++ b/builtin.h @@ -28,5 +28,6 @@ extern int cmd_rev_list(int argc, const char **argv, char **envp); extern int cmd_check_ref_format(int argc, const char **argv, char **envp); extern int cmd_init_db(int argc, const char **argv, char **envp); extern int cmd_ls_files(int argc, const char **argv, char **envp); +extern int cmd_ls_tree(int argc, const char **argv, char **envp); #endif diff --git a/git.c b/git.c index 9cfa9ebced..8574775418 100644 --- a/git.c +++ b/git.c @@ -53,7 +53,8 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "rev-list", cmd_rev_list }, { "init-db", cmd_init_db }, { "check-ref-format", cmd_check_ref_format }, - { "ls-files", cmd_ls_files } + { "ls-files", cmd_ls_files }, + { "ls-tree", cmd_ls_tree } }; int i; diff --git a/ls-tree.c b/ls-tree.c deleted file mode 100644 index f2b3bc1231..0000000000 --- a/ls-tree.c +++ /dev/null @@ -1,155 +0,0 @@ -/* - * GIT - The information manager from hell - * - * Copyright (C) Linus Torvalds, 2005 - */ -#include "cache.h" -#include "blob.h" -#include "tree.h" -#include "quote.h" - -static int line_termination = '\n'; -#define LS_RECURSIVE 1 -#define LS_TREE_ONLY 2 -#define LS_SHOW_TREES 4 -#define LS_NAME_ONLY 8 -static int abbrev = 0; -static int ls_options = 0; -const char **pathspec; -static int chomp_prefix = 0; -static const char *prefix; - -static const char ls_tree_usage[] = - "git-ls-tree [-d] [-r] [-t] [-z] [--name-only] [--name-status] [--full-name] [--abbrev[=]] [path...]"; - -static int show_recursive(const char *base, int baselen, const char *pathname) -{ - const char **s; - - if (ls_options & LS_RECURSIVE) - return 1; - - s = pathspec; - if (!s) - return 0; - - for (;;) { - const char *spec = *s++; - int len, speclen; - - if (!spec) - return 0; - if (strncmp(base, spec, baselen)) - continue; - len = strlen(pathname); - spec += baselen; - speclen = strlen(spec); - if (speclen <= len) - continue; - if (memcmp(pathname, spec, len)) - continue; - return 1; - } -} - -static int show_tree(unsigned char *sha1, const char *base, int baselen, - const char *pathname, unsigned mode, int stage) -{ - int retval = 0; - const char *type = blob_type; - - if (S_ISDIR(mode)) { - if (show_recursive(base, baselen, pathname)) { - retval = READ_TREE_RECURSIVE; - if (!(ls_options & LS_SHOW_TREES)) - return retval; - } - type = tree_type; - } - else if (ls_options & LS_TREE_ONLY) - return 0; - - if (chomp_prefix && - (baselen < chomp_prefix || memcmp(prefix, base, chomp_prefix))) - return 0; - - if (!(ls_options & LS_NAME_ONLY)) - printf("%06o %s %s\t", mode, type, - abbrev ? find_unique_abbrev(sha1,abbrev) - : sha1_to_hex(sha1)); - write_name_quoted(base + chomp_prefix, baselen - chomp_prefix, - pathname, - line_termination, stdout); - putchar(line_termination); - return retval; -} - -int main(int argc, const char **argv) -{ - unsigned char sha1[20]; - struct tree *tree; - - prefix = setup_git_directory(); - git_config(git_default_config); - if (prefix && *prefix) - chomp_prefix = strlen(prefix); - while (1 < argc && argv[1][0] == '-') { - switch (argv[1][1]) { - case 'z': - line_termination = 0; - break; - case 'r': - ls_options |= LS_RECURSIVE; - break; - case 'd': - ls_options |= LS_TREE_ONLY; - break; - case 't': - ls_options |= LS_SHOW_TREES; - break; - case '-': - if (!strcmp(argv[1]+2, "name-only") || - !strcmp(argv[1]+2, "name-status")) { - ls_options |= LS_NAME_ONLY; - break; - } - if (!strcmp(argv[1]+2, "full-name")) { - chomp_prefix = 0; - break; - } - if (!strncmp(argv[1]+2, "abbrev=",7)) { - abbrev = strtoul(argv[1]+9, NULL, 10); - if (abbrev && abbrev < MINIMUM_ABBREV) - abbrev = MINIMUM_ABBREV; - else if (abbrev > 40) - abbrev = 40; - break; - } - if (!strcmp(argv[1]+2, "abbrev")) { - abbrev = DEFAULT_ABBREV; - break; - } - /* otherwise fallthru */ - default: - usage(ls_tree_usage); - } - argc--; argv++; - } - /* -d -r should imply -t, but -d by itself should not have to. */ - if ( (LS_TREE_ONLY|LS_RECURSIVE) == - ((LS_TREE_ONLY|LS_RECURSIVE) & ls_options)) - ls_options |= LS_SHOW_TREES; - - if (argc < 2) - usage(ls_tree_usage); - if (get_sha1(argv[1], sha1)) - die("Not a valid object name %s", argv[1]); - - pathspec = get_pathspec(prefix, argv + 2); - tree = parse_tree_indirect(sha1); - if (!tree) - die("not a tree object"); - read_tree_recursive(tree, "", 0, 0, pathspec, show_tree); - - return 0; -} -- cgit v1.3 From 56d1398ad305498faf57d6e433f97ad393d7909e Mon Sep 17 00:00:00 2001 From: Peter Eriksen Date: Tue, 23 May 2006 14:15:31 +0200 Subject: Builtin git-tar-tree. Signed-off-by: Peter Eriksen Signed-off-by: Junio C Hamano --- Makefile | 8 +- builtin-tar-tree.c | 351 +++++++++++++++++++++++++++++++++++++++++++++++++++++ builtin.h | 1 + git.c | 3 +- tar-tree.c | 350 ---------------------------------------------------- 5 files changed, 359 insertions(+), 354 deletions(-) create mode 100644 builtin-tar-tree.c delete mode 100644 tar-tree.c (limited to 'Makefile') diff --git a/Makefile b/Makefile index 2afd089a0d..a3164f8ef0 100644 --- a/Makefile +++ b/Makefile @@ -161,7 +161,7 @@ PROGRAMS = \ git-receive-pack$X git-rev-parse$X \ git-send-pack$X git-show-branch$X git-shell$X \ git-show-index$X git-ssh-fetch$X \ - git-ssh-upload$X git-tar-tree$X git-unpack-file$X \ + git-ssh-upload$X git-unpack-file$X \ git-unpack-objects$X git-update-index$X git-update-server-info$X \ git-upload-pack$X git-verify-pack$X git-write-tree$X \ git-update-ref$X git-symbolic-ref$X \ @@ -171,7 +171,8 @@ PROGRAMS = \ BUILT_INS = git-log$X git-whatchanged$X git-show$X \ git-count-objects$X git-diff$X git-push$X \ git-grep$X git-rev-list$X git-check-ref-format$X \ - git-init-db$X git-ls-files$X git-ls-tree$X + git-init-db$X git-ls-files$X git-ls-tree$X \ + git-tar-tree$X # what 'all' will build and 'install' will install, in gitexecdir ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS) @@ -220,7 +221,8 @@ LIB_OBJS = \ BUILTIN_OBJS = \ builtin-log.o builtin-help.o builtin-count.o builtin-diff.o builtin-push.o \ builtin-grep.o builtin-rev-list.o builtin-check-ref-format.o \ - builtin-init-db.o builtin-ls-files.o builtin-ls-tree.o + builtin-init-db.o builtin-ls-files.o builtin-ls-tree.o \ + builtin-tar-tree.o GITLIBS = $(LIB_FILE) $(XDIFF_LIB) LIBS = $(GITLIBS) -lz diff --git a/builtin-tar-tree.c b/builtin-tar-tree.c new file mode 100644 index 0000000000..6ada04ce38 --- /dev/null +++ b/builtin-tar-tree.c @@ -0,0 +1,351 @@ +/* + * Copyright (c) 2005, 2006 Rene Scharfe + */ +#include +#include "cache.h" +#include "tree-walk.h" +#include "commit.h" +#include "strbuf.h" +#include "tar.h" +#include "builtin.h" + +#define RECORDSIZE (512) +#define BLOCKSIZE (RECORDSIZE * 20) + +static const char tar_tree_usage[] = "git-tar-tree [basedir]"; + +static char block[BLOCKSIZE]; +static unsigned long offset; + +static time_t archive_time; + +/* tries hard to write, either succeeds or dies in the attempt */ +static void reliable_write(void *buf, unsigned long size) +{ + while (size > 0) { + long ret = xwrite(1, buf, size); + if (ret < 0) { + if (errno == EPIPE) + exit(0); + die("git-tar-tree: %s", strerror(errno)); + } else if (!ret) { + die("git-tar-tree: disk full?"); + } + size -= ret; + buf += ret; + } +} + +/* writes out the whole block, but only if it is full */ +static void write_if_needed(void) +{ + if (offset == BLOCKSIZE) { + reliable_write(block, BLOCKSIZE); + offset = 0; + } +} + +/* acquire the next record from the buffer; user must call write_if_needed() */ +static char *get_record(void) +{ + char *p = block + offset; + memset(p, 0, RECORDSIZE); + offset += RECORDSIZE; + return p; +} + +/* + * The end of tar archives is marked by 1024 nul bytes and after that + * follows the rest of the block (if any). + */ +static void write_trailer(void) +{ + get_record(); + write_if_needed(); + get_record(); + write_if_needed(); + while (offset) { + get_record(); + write_if_needed(); + } +} + +/* + * queues up writes, so that all our write(2) calls write exactly one + * full block; pads writes to RECORDSIZE + */ +static void write_blocked(void *buf, unsigned long size) +{ + unsigned long tail; + + if (offset) { + unsigned long chunk = BLOCKSIZE - offset; + if (size < chunk) + chunk = size; + memcpy(block + offset, buf, chunk); + size -= chunk; + offset += chunk; + buf += chunk; + write_if_needed(); + } + while (size >= BLOCKSIZE) { + reliable_write(buf, BLOCKSIZE); + size -= BLOCKSIZE; + buf += BLOCKSIZE; + } + if (size) { + memcpy(block + offset, buf, size); + offset += size; + } + tail = offset % RECORDSIZE; + if (tail) { + memset(block + offset, 0, RECORDSIZE - tail); + offset += RECORDSIZE - tail; + } + write_if_needed(); +} + +static void strbuf_append_string(struct strbuf *sb, const char *s) +{ + int slen = strlen(s); + int total = sb->len + slen; + if (total > sb->alloc) { + sb->buf = xrealloc(sb->buf, total); + sb->alloc = total; + } + memcpy(sb->buf + sb->len, s, slen); + sb->len = total; +} + +/* + * pax extended header records have the format "%u %s=%s\n". %u contains + * the size of the whole string (including the %u), the first %s is the + * keyword, the second one is the value. This function constructs such a + * string and appends it to a struct strbuf. + */ +static void strbuf_append_ext_header(struct strbuf *sb, const char *keyword, + const char *value, unsigned int valuelen) +{ + char *p; + int len, total, tmp; + + /* "%u %s=%s\n" */ + len = 1 + 1 + strlen(keyword) + 1 + valuelen + 1; + for (tmp = len; tmp > 9; tmp /= 10) + len++; + + total = sb->len + len; + if (total > sb->alloc) { + sb->buf = xrealloc(sb->buf, total); + sb->alloc = total; + } + + p = sb->buf; + p += sprintf(p, "%u %s=", len, keyword); + memcpy(p, value, valuelen); + p += valuelen; + *p = '\n'; + sb->len = total; +} + +static unsigned int ustar_header_chksum(const struct ustar_header *header) +{ + char *p = (char *)header; + unsigned int chksum = 0; + while (p < header->chksum) + chksum += *p++; + chksum += sizeof(header->chksum) * ' '; + p += sizeof(header->chksum); + while (p < (char *)header + sizeof(struct ustar_header)) + chksum += *p++; + return chksum; +} + +static int get_path_prefix(const struct strbuf *path, int maxlen) +{ + int i = path->len; + if (i > maxlen) + i = maxlen; + while (i > 0 && path->buf[i] != '/') + i--; + return i; +} + +static void write_entry(const unsigned char *sha1, struct strbuf *path, + unsigned int mode, void *buffer, unsigned long size) +{ + struct ustar_header header; + struct strbuf ext_header; + + memset(&header, 0, sizeof(header)); + ext_header.buf = NULL; + ext_header.len = ext_header.alloc = 0; + + if (!sha1) { + *header.typeflag = TYPEFLAG_GLOBAL_HEADER; + mode = 0100666; + strcpy(header.name, "pax_global_header"); + } else if (!path) { + *header.typeflag = TYPEFLAG_EXT_HEADER; + mode = 0100666; + sprintf(header.name, "%s.paxheader", sha1_to_hex(sha1)); + } else { + if (S_ISDIR(mode)) { + *header.typeflag = TYPEFLAG_DIR; + mode |= 0777; + } else if (S_ISLNK(mode)) { + *header.typeflag = TYPEFLAG_LNK; + mode |= 0777; + } else if (S_ISREG(mode)) { + *header.typeflag = TYPEFLAG_REG; + mode |= (mode & 0100) ? 0777 : 0666; + } else { + error("unsupported file mode: 0%o (SHA1: %s)", + mode, sha1_to_hex(sha1)); + return; + } + if (path->len > sizeof(header.name)) { + int plen = get_path_prefix(path, sizeof(header.prefix)); + int rest = path->len - plen - 1; + if (plen > 0 && rest <= sizeof(header.name)) { + memcpy(header.prefix, path->buf, plen); + memcpy(header.name, path->buf + plen + 1, rest); + } else { + sprintf(header.name, "%s.data", + sha1_to_hex(sha1)); + strbuf_append_ext_header(&ext_header, "path", + path->buf, path->len); + } + } else + memcpy(header.name, path->buf, path->len); + } + + if (S_ISLNK(mode) && buffer) { + if (size > sizeof(header.linkname)) { + sprintf(header.linkname, "see %s.paxheader", + sha1_to_hex(sha1)); + strbuf_append_ext_header(&ext_header, "linkpath", + buffer, size); + } else + memcpy(header.linkname, buffer, size); + } + + sprintf(header.mode, "%07o", mode & 07777); + sprintf(header.size, "%011lo", S_ISREG(mode) ? size : 0); + sprintf(header.mtime, "%011lo", archive_time); + + /* XXX: should we provide more meaningful info here? */ + sprintf(header.uid, "%07o", 0); + sprintf(header.gid, "%07o", 0); + strncpy(header.uname, "git", 31); + strncpy(header.gname, "git", 31); + sprintf(header.devmajor, "%07o", 0); + sprintf(header.devminor, "%07o", 0); + + memcpy(header.magic, "ustar", 6); + memcpy(header.version, "00", 2); + + sprintf(header.chksum, "%07o", ustar_header_chksum(&header)); + + if (ext_header.len > 0) { + write_entry(sha1, NULL, 0, ext_header.buf, ext_header.len); + free(ext_header.buf); + } + write_blocked(&header, sizeof(header)); + if (S_ISREG(mode) && buffer && size > 0) + write_blocked(buffer, size); +} + +static void write_global_extended_header(const unsigned char *sha1) +{ + struct strbuf ext_header; + ext_header.buf = NULL; + ext_header.len = ext_header.alloc = 0; + strbuf_append_ext_header(&ext_header, "comment", sha1_to_hex(sha1), 40); + write_entry(NULL, NULL, 0, ext_header.buf, ext_header.len); + free(ext_header.buf); +} + +static void traverse_tree(struct tree_desc *tree, struct strbuf *path) +{ + int pathlen = path->len; + + while (tree->size) { + const char *name; + const unsigned char *sha1; + unsigned mode; + void *eltbuf; + char elttype[20]; + unsigned long eltsize; + + sha1 = tree_entry_extract(tree, &name, &mode); + update_tree_entry(tree); + + eltbuf = read_sha1_file(sha1, elttype, &eltsize); + if (!eltbuf) + die("cannot read %s", sha1_to_hex(sha1)); + + path->len = pathlen; + strbuf_append_string(path, name); + if (S_ISDIR(mode)) + strbuf_append_string(path, "/"); + + write_entry(sha1, path, mode, eltbuf, eltsize); + + if (S_ISDIR(mode)) { + struct tree_desc subtree; + subtree.buf = eltbuf; + subtree.size = eltsize; + traverse_tree(&subtree, path); + } + free(eltbuf); + } +} + +int cmd_tar_tree(int argc, const char **argv, char** envp) +{ + unsigned char sha1[20], tree_sha1[20]; + struct commit *commit; + struct tree_desc tree; + struct strbuf current_path; + + current_path.buf = xmalloc(PATH_MAX); + current_path.alloc = PATH_MAX; + current_path.len = current_path.eof = 0; + + setup_git_directory(); + git_config(git_default_config); + + switch (argc) { + case 3: + strbuf_append_string(¤t_path, argv[2]); + strbuf_append_string(¤t_path, "/"); + /* FALLTHROUGH */ + case 2: + if (get_sha1(argv[1], sha1)) + die("Not a valid object name %s", argv[1]); + break; + default: + usage(tar_tree_usage); + } + + commit = lookup_commit_reference_gently(sha1, 1); + if (commit) { + write_global_extended_header(commit->object.sha1); + archive_time = commit->date; + } else + archive_time = time(NULL); + + tree.buf = read_object_with_reference(sha1, tree_type, &tree.size, + tree_sha1); + if (!tree.buf) + die("not a reference to a tag, commit or tree object: %s", + sha1_to_hex(sha1)); + + if (current_path.len > 0) + write_entry(tree_sha1, ¤t_path, 040777, NULL, 0); + traverse_tree(&tree, ¤t_path); + write_trailer(); + free(current_path.buf); + return 0; +} diff --git a/builtin.h b/builtin.h index 951f206372..d210543948 100644 --- a/builtin.h +++ b/builtin.h @@ -29,5 +29,6 @@ extern int cmd_check_ref_format(int argc, const char **argv, char **envp); extern int cmd_init_db(int argc, const char **argv, char **envp); extern int cmd_ls_files(int argc, const char **argv, char **envp); extern int cmd_ls_tree(int argc, const char **argv, char **envp); +extern int cmd_tar_tree(int argc, const char **argv, char **envp); #endif diff --git a/git.c b/git.c index 8574775418..c253e60953 100644 --- a/git.c +++ b/git.c @@ -54,7 +54,8 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "init-db", cmd_init_db }, { "check-ref-format", cmd_check_ref_format }, { "ls-files", cmd_ls_files }, - { "ls-tree", cmd_ls_tree } + { "ls-tree", cmd_ls_tree }, + { "tar-tree", cmd_tar_tree } }; int i; diff --git a/tar-tree.c b/tar-tree.c deleted file mode 100644 index 33087366c3..0000000000 --- a/tar-tree.c +++ /dev/null @@ -1,350 +0,0 @@ -/* - * Copyright (c) 2005, 2006 Rene Scharfe - */ -#include -#include "cache.h" -#include "tree-walk.h" -#include "commit.h" -#include "strbuf.h" -#include "tar.h" - -#define RECORDSIZE (512) -#define BLOCKSIZE (RECORDSIZE * 20) - -static const char tar_tree_usage[] = "git-tar-tree [basedir]"; - -static char block[BLOCKSIZE]; -static unsigned long offset; - -static time_t archive_time; - -/* tries hard to write, either succeeds or dies in the attempt */ -static void reliable_write(void *buf, unsigned long size) -{ - while (size > 0) { - long ret = xwrite(1, buf, size); - if (ret < 0) { - if (errno == EPIPE) - exit(0); - die("git-tar-tree: %s", strerror(errno)); - } else if (!ret) { - die("git-tar-tree: disk full?"); - } - size -= ret; - buf += ret; - } -} - -/* writes out the whole block, but only if it is full */ -static void write_if_needed(void) -{ - if (offset == BLOCKSIZE) { - reliable_write(block, BLOCKSIZE); - offset = 0; - } -} - -/* acquire the next record from the buffer; user must call write_if_needed() */ -static char *get_record(void) -{ - char *p = block + offset; - memset(p, 0, RECORDSIZE); - offset += RECORDSIZE; - return p; -} - -/* - * The end of tar archives is marked by 1024 nul bytes and after that - * follows the rest of the block (if any). - */ -static void write_trailer(void) -{ - get_record(); - write_if_needed(); - get_record(); - write_if_needed(); - while (offset) { - get_record(); - write_if_needed(); - } -} - -/* - * queues up writes, so that all our write(2) calls write exactly one - * full block; pads writes to RECORDSIZE - */ -static void write_blocked(void *buf, unsigned long size) -{ - unsigned long tail; - - if (offset) { - unsigned long chunk = BLOCKSIZE - offset; - if (size < chunk) - chunk = size; - memcpy(block + offset, buf, chunk); - size -= chunk; - offset += chunk; - buf += chunk; - write_if_needed(); - } - while (size >= BLOCKSIZE) { - reliable_write(buf, BLOCKSIZE); - size -= BLOCKSIZE; - buf += BLOCKSIZE; - } - if (size) { - memcpy(block + offset, buf, size); - offset += size; - } - tail = offset % RECORDSIZE; - if (tail) { - memset(block + offset, 0, RECORDSIZE - tail); - offset += RECORDSIZE - tail; - } - write_if_needed(); -} - -static void strbuf_append_string(struct strbuf *sb, const char *s) -{ - int slen = strlen(s); - int total = sb->len + slen; - if (total > sb->alloc) { - sb->buf = xrealloc(sb->buf, total); - sb->alloc = total; - } - memcpy(sb->buf + sb->len, s, slen); - sb->len = total; -} - -/* - * pax extended header records have the format "%u %s=%s\n". %u contains - * the size of the whole string (including the %u), the first %s is the - * keyword, the second one is the value. This function constructs such a - * string and appends it to a struct strbuf. - */ -static void strbuf_append_ext_header(struct strbuf *sb, const char *keyword, - const char *value, unsigned int valuelen) -{ - char *p; - int len, total, tmp; - - /* "%u %s=%s\n" */ - len = 1 + 1 + strlen(keyword) + 1 + valuelen + 1; - for (tmp = len; tmp > 9; tmp /= 10) - len++; - - total = sb->len + len; - if (total > sb->alloc) { - sb->buf = xrealloc(sb->buf, total); - sb->alloc = total; - } - - p = sb->buf; - p += sprintf(p, "%u %s=", len, keyword); - memcpy(p, value, valuelen); - p += valuelen; - *p = '\n'; - sb->len = total; -} - -static unsigned int ustar_header_chksum(const struct ustar_header *header) -{ - char *p = (char *)header; - unsigned int chksum = 0; - while (p < header->chksum) - chksum += *p++; - chksum += sizeof(header->chksum) * ' '; - p += sizeof(header->chksum); - while (p < (char *)header + sizeof(struct ustar_header)) - chksum += *p++; - return chksum; -} - -static int get_path_prefix(const struct strbuf *path, int maxlen) -{ - int i = path->len; - if (i > maxlen) - i = maxlen; - while (i > 0 && path->buf[i] != '/') - i--; - return i; -} - -static void write_entry(const unsigned char *sha1, struct strbuf *path, - unsigned int mode, void *buffer, unsigned long size) -{ - struct ustar_header header; - struct strbuf ext_header; - - memset(&header, 0, sizeof(header)); - ext_header.buf = NULL; - ext_header.len = ext_header.alloc = 0; - - if (!sha1) { - *header.typeflag = TYPEFLAG_GLOBAL_HEADER; - mode = 0100666; - strcpy(header.name, "pax_global_header"); - } else if (!path) { - *header.typeflag = TYPEFLAG_EXT_HEADER; - mode = 0100666; - sprintf(header.name, "%s.paxheader", sha1_to_hex(sha1)); - } else { - if (S_ISDIR(mode)) { - *header.typeflag = TYPEFLAG_DIR; - mode |= 0777; - } else if (S_ISLNK(mode)) { - *header.typeflag = TYPEFLAG_LNK; - mode |= 0777; - } else if (S_ISREG(mode)) { - *header.typeflag = TYPEFLAG_REG; - mode |= (mode & 0100) ? 0777 : 0666; - } else { - error("unsupported file mode: 0%o (SHA1: %s)", - mode, sha1_to_hex(sha1)); - return; - } - if (path->len > sizeof(header.name)) { - int plen = get_path_prefix(path, sizeof(header.prefix)); - int rest = path->len - plen - 1; - if (plen > 0 && rest <= sizeof(header.name)) { - memcpy(header.prefix, path->buf, plen); - memcpy(header.name, path->buf + plen + 1, rest); - } else { - sprintf(header.name, "%s.data", - sha1_to_hex(sha1)); - strbuf_append_ext_header(&ext_header, "path", - path->buf, path->len); - } - } else - memcpy(header.name, path->buf, path->len); - } - - if (S_ISLNK(mode) && buffer) { - if (size > sizeof(header.linkname)) { - sprintf(header.linkname, "see %s.paxheader", - sha1_to_hex(sha1)); - strbuf_append_ext_header(&ext_header, "linkpath", - buffer, size); - } else - memcpy(header.linkname, buffer, size); - } - - sprintf(header.mode, "%07o", mode & 07777); - sprintf(header.size, "%011lo", S_ISREG(mode) ? size : 0); - sprintf(header.mtime, "%011lo", archive_time); - - /* XXX: should we provide more meaningful info here? */ - sprintf(header.uid, "%07o", 0); - sprintf(header.gid, "%07o", 0); - strncpy(header.uname, "git", 31); - strncpy(header.gname, "git", 31); - sprintf(header.devmajor, "%07o", 0); - sprintf(header.devminor, "%07o", 0); - - memcpy(header.magic, "ustar", 6); - memcpy(header.version, "00", 2); - - sprintf(header.chksum, "%07o", ustar_header_chksum(&header)); - - if (ext_header.len > 0) { - write_entry(sha1, NULL, 0, ext_header.buf, ext_header.len); - free(ext_header.buf); - } - write_blocked(&header, sizeof(header)); - if (S_ISREG(mode) && buffer && size > 0) - write_blocked(buffer, size); -} - -static void write_global_extended_header(const unsigned char *sha1) -{ - struct strbuf ext_header; - ext_header.buf = NULL; - ext_header.len = ext_header.alloc = 0; - strbuf_append_ext_header(&ext_header, "comment", sha1_to_hex(sha1), 40); - write_entry(NULL, NULL, 0, ext_header.buf, ext_header.len); - free(ext_header.buf); -} - -static void traverse_tree(struct tree_desc *tree, struct strbuf *path) -{ - int pathlen = path->len; - - while (tree->size) { - const char *name; - const unsigned char *sha1; - unsigned mode; - void *eltbuf; - char elttype[20]; - unsigned long eltsize; - - sha1 = tree_entry_extract(tree, &name, &mode); - update_tree_entry(tree); - - eltbuf = read_sha1_file(sha1, elttype, &eltsize); - if (!eltbuf) - die("cannot read %s", sha1_to_hex(sha1)); - - path->len = pathlen; - strbuf_append_string(path, name); - if (S_ISDIR(mode)) - strbuf_append_string(path, "/"); - - write_entry(sha1, path, mode, eltbuf, eltsize); - - if (S_ISDIR(mode)) { - struct tree_desc subtree; - subtree.buf = eltbuf; - subtree.size = eltsize; - traverse_tree(&subtree, path); - } - free(eltbuf); - } -} - -int main(int argc, char **argv) -{ - unsigned char sha1[20], tree_sha1[20]; - struct commit *commit; - struct tree_desc tree; - struct strbuf current_path; - - current_path.buf = xmalloc(PATH_MAX); - current_path.alloc = PATH_MAX; - current_path.len = current_path.eof = 0; - - setup_git_directory(); - git_config(git_default_config); - - switch (argc) { - case 3: - strbuf_append_string(¤t_path, argv[2]); - strbuf_append_string(¤t_path, "/"); - /* FALLTHROUGH */ - case 2: - if (get_sha1(argv[1], sha1)) - die("Not a valid object name %s", argv[1]); - break; - default: - usage(tar_tree_usage); - } - - commit = lookup_commit_reference_gently(sha1, 1); - if (commit) { - write_global_extended_header(commit->object.sha1); - archive_time = commit->date; - } else - archive_time = time(NULL); - - tree.buf = read_object_with_reference(sha1, tree_type, &tree.size, - tree_sha1); - if (!tree.buf) - die("not a reference to a tag, commit or tree object: %s", - sha1_to_hex(sha1)); - - if (current_path.len > 0) - write_entry(tree_sha1, ¤t_path, 040777, NULL, 0); - traverse_tree(&tree, ¤t_path); - write_trailer(); - free(current_path.buf); - return 0; -} -- cgit v1.3 From d147e501f37a596e73a430ce46f125f83e06aa07 Mon Sep 17 00:00:00 2001 From: Peter Eriksen Date: Tue, 23 May 2006 14:15:32 +0200 Subject: Builtin git-read-tree. Signed-off-by: Peter Eriksen Signed-off-by: Junio C Hamano --- Makefile | 6 +- builtin-read-tree.c | 882 ++++++++++++++++++++++++++++++++++++++++++++++++++++ builtin.h | 1 + git.c | 3 +- read-tree.c | 881 --------------------------------------------------- 5 files changed, 888 insertions(+), 885 deletions(-) create mode 100644 builtin-read-tree.c delete mode 100644 read-tree.c (limited to 'Makefile') diff --git a/Makefile b/Makefile index a3164f8ef0..1aebcf5978 100644 --- a/Makefile +++ b/Makefile @@ -157,7 +157,7 @@ PROGRAMS = \ git-hash-object$X git-index-pack$X git-local-fetch$X \ git-mailinfo$X git-merge-base$X \ git-merge-index$X git-mktag$X git-mktree$X git-pack-objects$X git-patch-id$X \ - git-peek-remote$X git-prune-packed$X git-read-tree$X \ + git-peek-remote$X git-prune-packed$X \ git-receive-pack$X git-rev-parse$X \ git-send-pack$X git-show-branch$X git-shell$X \ git-show-index$X git-ssh-fetch$X \ @@ -172,7 +172,7 @@ 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-tar-tree$X + git-tar-tree$X git-read-tree$X # what 'all' will build and 'install' will install, in gitexecdir ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS) @@ -222,7 +222,7 @@ 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-tar-tree.o + builtin-tar-tree.o builtin-read-tree.o GITLIBS = $(LIB_FILE) $(XDIFF_LIB) LIBS = $(GITLIBS) -lz diff --git a/builtin-read-tree.c b/builtin-read-tree.c new file mode 100644 index 0000000000..ec40d013c4 --- /dev/null +++ b/builtin-read-tree.c @@ -0,0 +1,882 @@ +/* + * 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 +#include +#include "builtin.h" + +static int reset = 0; +static int merge = 0; +static int update = 0; +static int index_only = 0; +static int nontrivial_merge = 0; +static int trivial_merges_only = 0; +static int aggressive = 0; +static int verbose_update = 0; +static volatile int progress_update = 0; + +static int head_idx = -1; +static int merge_size = 0; + +static struct object_list *trees = NULL; + +static struct cache_entry df_conflict_entry = { +}; + +static struct tree_entry_list df_conflict_list = { + .name = NULL, + .next = &df_conflict_list +}; + +typedef int (*merge_fn_t)(struct cache_entry **src); + +static int entcmp(char *name1, int dir1, char *name2, int dir2) +{ + int len1 = strlen(name1); + int len2 = strlen(name2); + int len = len1 < len2 ? len1 : len2; + int ret = memcmp(name1, name2, len); + unsigned char c1, c2; + if (ret) + return ret; + c1 = name1[len]; + c2 = name2[len]; + if (!c1 && dir1) + c1 = '/'; + if (!c2 && dir2) + c2 = '/'; + ret = (c1 < c2) ? -1 : (c1 > c2) ? 1 : 0; + if (c1 && c2 && !ret) + ret = len1 - len2; + return ret; +} + +static int unpack_trees_rec(struct tree_entry_list **posns, int len, + const char *base, merge_fn_t fn, int *indpos) +{ + int baselen = strlen(base); + int src_size = len + 1; + do { + int i; + char *first; + int firstdir = 0; + int pathlen; + unsigned ce_size; + struct tree_entry_list **subposns; + struct cache_entry **src; + int any_files = 0; + int any_dirs = 0; + char *cache_name; + int ce_stage; + + /* Find the first name in the input. */ + + first = NULL; + cache_name = NULL; + + /* Check the cache */ + if (merge && *indpos < active_nr) { + /* This is a bit tricky: */ + /* If the index has a subdirectory (with + * contents) as the first name, it'll get a + * filename like "foo/bar". But that's after + * "foo", so the entry in trees will get + * handled first, at which point we'll go into + * "foo", and deal with "bar" from the index, + * because the base will be "foo/". The only + * way we can actually have "foo/bar" first of + * all the things is if the trees don't + * contain "foo" at all, in which case we'll + * handle "foo/bar" without going into the + * directory, but that's fine (and will return + * an error anyway, with the added unknown + * file case. + */ + + cache_name = active_cache[*indpos]->name; + if (strlen(cache_name) > baselen && + !memcmp(cache_name, base, baselen)) { + cache_name += baselen; + first = cache_name; + } else { + cache_name = NULL; + } + } + +#if DBRT_DEBUG > 1 + if (first) + printf("index %s\n", first); +#endif + for (i = 0; i < len; i++) { + if (!posns[i] || posns[i] == &df_conflict_list) + continue; +#if DBRT_DEBUG > 1 + printf("%d %s\n", i + 1, posns[i]->name); +#endif + if (!first || entcmp(first, firstdir, + posns[i]->name, + posns[i]->directory) > 0) { + first = posns[i]->name; + firstdir = posns[i]->directory; + } + } + /* No name means we're done */ + if (!first) + return 0; + + pathlen = strlen(first); + ce_size = cache_entry_size(baselen + pathlen); + + src = xcalloc(src_size, sizeof(struct cache_entry *)); + + subposns = xcalloc(len, sizeof(struct tree_list_entry *)); + + if (cache_name && !strcmp(cache_name, first)) { + any_files = 1; + src[0] = active_cache[*indpos]; + remove_cache_entry_at(*indpos); + } + + for (i = 0; i < len; i++) { + struct cache_entry *ce; + + if (!posns[i] || + (posns[i] != &df_conflict_list && + strcmp(first, posns[i]->name))) { + continue; + } + + if (posns[i] == &df_conflict_list) { + src[i + merge] = &df_conflict_entry; + continue; + } + + if (posns[i]->directory) { + any_dirs = 1; + parse_tree(posns[i]->item.tree); + subposns[i] = posns[i]->item.tree->entries; + posns[i] = posns[i]->next; + src[i + merge] = &df_conflict_entry; + continue; + } + + if (!merge) + ce_stage = 0; + else if (i + 1 < head_idx) + ce_stage = 1; + else if (i + 1 > head_idx) + ce_stage = 3; + else + ce_stage = 2; + + ce = xcalloc(1, ce_size); + ce->ce_mode = create_ce_mode(posns[i]->mode); + ce->ce_flags = create_ce_flags(baselen + pathlen, + ce_stage); + memcpy(ce->name, base, baselen); + memcpy(ce->name + baselen, first, pathlen + 1); + + any_files = 1; + + memcpy(ce->sha1, posns[i]->item.any->sha1, 20); + src[i + merge] = ce; + subposns[i] = &df_conflict_list; + posns[i] = posns[i]->next; + } + if (any_files) { + if (merge) { + int ret; + +#if DBRT_DEBUG > 1 + printf("%s:\n", first); + for (i = 0; i < src_size; i++) { + printf(" %d ", i); + if (src[i]) + printf("%s\n", sha1_to_hex(src[i]->sha1)); + else + printf("\n"); + } +#endif + ret = fn(src); + +#if DBRT_DEBUG > 1 + printf("Added %d entries\n", ret); +#endif + *indpos += ret; + } else { + for (i = 0; i < src_size; i++) { + if (src[i]) { + add_cache_entry(src[i], ADD_CACHE_OK_TO_ADD|ADD_CACHE_SKIP_DFCHECK); + } + } + } + } + if (any_dirs) { + char *newbase = xmalloc(baselen + 2 + pathlen); + memcpy(newbase, base, baselen); + memcpy(newbase + baselen, first, pathlen); + newbase[baselen + pathlen] = '/'; + newbase[baselen + pathlen + 1] = '\0'; + if (unpack_trees_rec(subposns, len, newbase, fn, + indpos)) + return -1; + free(newbase); + } + free(subposns); + free(src); + } while (1); +} + +static void reject_merge(struct cache_entry *ce) +{ + die("Entry '%s' would be overwritten by merge. Cannot merge.", + ce->name); +} + +/* Unlink the last component and attempt to remove leading + * directories, in case this unlink is the removal of the + * last entry in the directory -- empty directories are removed. + */ +static void unlink_entry(char *name) +{ + char *cp, *prev; + + if (unlink(name)) + return; + prev = NULL; + while (1) { + int status; + cp = strrchr(name, '/'); + if (prev) + *prev = '/'; + if (!cp) + break; + + *cp = 0; + status = rmdir(name); + if (status) { + *cp = '/'; + break; + } + prev = cp; + } +} + +static void progress_interval(int signum) +{ + progress_update = 1; +} + +static void setup_progress_signal(void) +{ + struct sigaction sa; + struct itimerval v; + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = progress_interval; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + sigaction(SIGALRM, &sa, NULL); + + v.it_interval.tv_sec = 1; + v.it_interval.tv_usec = 0; + v.it_value = v.it_interval; + setitimer(ITIMER_REAL, &v, NULL); +} + +static void check_updates(struct cache_entry **src, int nr) +{ + static struct checkout state = { + .base_dir = "", + .force = 1, + .quiet = 1, + .refresh_cache = 1, + }; + unsigned short mask = htons(CE_UPDATE); + unsigned last_percent = 200, cnt = 0, total = 0; + + if (update && verbose_update) { + for (total = cnt = 0; cnt < nr; cnt++) { + struct cache_entry *ce = src[cnt]; + if (!ce->ce_mode || ce->ce_flags & mask) + total++; + } + + /* Don't bother doing this for very small updates */ + if (total < 250) + total = 0; + + if (total) { + fprintf(stderr, "Checking files out...\n"); + setup_progress_signal(); + progress_update = 1; + } + cnt = 0; + } + + while (nr--) { + struct cache_entry *ce = *src++; + + if (total) { + if (!ce->ce_mode || ce->ce_flags & mask) { + unsigned percent; + cnt++; + percent = (cnt * 100) / total; + if (percent != last_percent || + progress_update) { + fprintf(stderr, "%4u%% (%u/%u) done\r", + percent, cnt, total); + last_percent = percent; + } + } + } + if (!ce->ce_mode) { + if (update) + unlink_entry(ce->name); + continue; + } + if (ce->ce_flags & mask) { + ce->ce_flags &= ~mask; + if (update) + checkout_entry(ce, &state, NULL); + } + } + if (total) { + signal(SIGALRM, SIG_IGN); + fputc('\n', stderr); + } +} + +static int unpack_trees(merge_fn_t fn) +{ + int indpos = 0; + unsigned len = object_list_length(trees); + struct tree_entry_list **posns; + int i; + struct object_list *posn = trees; + merge_size = len; + + if (len) { + posns = xmalloc(len * sizeof(struct tree_entry_list *)); + for (i = 0; i < len; i++) { + posns[i] = ((struct tree *) posn->item)->entries; + posn = posn->next; + } + if (unpack_trees_rec(posns, len, "", fn, &indpos)) + 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); +} + +/* + * 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); + } + } + else + verify_absent(merge->name, "overwritten"); + + 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); + return 1; +} + +static int keep_entry(struct cache_entry *ce) +{ + add_cache_entry(ce, ADD_CACHE_OK_TO_ADD); + return 1; +} + +#if DBRT_DEBUG +static void show_stage_entry(FILE *o, + const char *label, const struct cache_entry *ce) +{ + if (!ce) + fprintf(o, "%s (missing)\n", label); + else + fprintf(o, "%s%06o %s %d\t%s\n", + label, + ntohl(ce->ce_mode), + sha1_to_hex(ce->sha1), + ce_stage(ce), + ce->name); +} +#endif + +static int threeway_merge(struct cache_entry **stages) +{ + struct cache_entry *index; + struct cache_entry *head; + struct cache_entry *remote = stages[head_idx + 1]; + int count; + int head_match = 0; + int remote_match = 0; + const char *path = NULL; + + int df_conflict_head = 0; + int df_conflict_remote = 0; + + int any_anc_missing = 0; + int no_anc_exists = 1; + int i; + + for (i = 1; i < head_idx; i++) { + if (!stages[i]) + any_anc_missing = 1; + else { + if (!path) + path = stages[i]->name; + no_anc_exists = 0; + } + } + + index = stages[0]; + head = stages[head_idx]; + + if (head == &df_conflict_entry) { + df_conflict_head = 1; + head = NULL; + } + + if (remote == &df_conflict_entry) { + df_conflict_remote = 1; + remote = NULL; + } + + if (!path && index) + path = index->name; + if (!path && head) + path = head->name; + if (!path && remote) + path = remote->name; + + /* First, if there's a #16 situation, note that to prevent #13 + * and #14. + */ + if (!same(remote, head)) { + for (i = 1; i < head_idx; i++) { + if (same(stages[i], head)) { + head_match = i; + } + if (same(stages[i], remote)) { + remote_match = i; + } + } + } + + /* We start with cases where the index is allowed to match + * something other than the head: #14(ALT) and #2ALT, where it + * is permitted to match the result instead. + */ + /* #14, #14ALT, #2ALT */ + if (remote && !df_conflict_head && head_match && !remote_match) { + if (index && !same(index, remote) && !same(index, head)) + reject_merge(index); + return merged_entry(remote, index); + } + /* + * If we have an entry in the index cache, then we want to + * make sure that it matches head. + */ + if (index && !same(index, head)) { + reject_merge(index); + } + + if (head) { + /* #5ALT, #15 */ + if (same(head, remote)) + return merged_entry(head, index); + /* #13, #3ALT */ + if (!df_conflict_remote && remote_match && !head_match) + return merged_entry(head, index); + } + + /* #1 */ + if (!head && !remote && any_anc_missing) + return 0; + + /* Under the new "aggressive" rule, we resolve mostly trivial + * cases that we historically had git-merge-one-file resolve. + */ + if (aggressive) { + int head_deleted = !head && !df_conflict_head; + int remote_deleted = !remote && !df_conflict_remote; + /* + * Deleted in both. + * Deleted in one and unchanged in the other. + */ + if ((head_deleted && remote_deleted) || + (head_deleted && remote && remote_match) || + (remote_deleted && head && head_match)) { + if (index) + return deleted_entry(index, index); + else if (path) + verify_absent(path, "removed"); + return 0; + } + /* + * Added in both, identically. + */ + if (no_anc_exists && head && remote && same(head, remote)) + return merged_entry(head, index); + + } + + /* Below are "no merge" cases, which require that the index be + * up-to-date to avoid the files getting overwritten with + * conflict resolution files. + */ + if (index) { + verify_uptodate(index); + } + else if (path) + verify_absent(path, "overwritten"); + + nontrivial_merge = 1; + + /* #2, #3, #4, #6, #7, #9, #11. */ + count = 0; + if (!head_match || !remote_match) { + for (i = 1; i < head_idx; i++) { + if (stages[i]) { + keep_entry(stages[i]); + count++; + break; + } + } + } +#if DBRT_DEBUG + else { + fprintf(stderr, "read-tree: warning #16 detected\n"); + show_stage_entry(stderr, "head ", stages[head_match]); + show_stage_entry(stderr, "remote ", stages[remote_match]); + } +#endif + if (head) { count += keep_entry(head); } + if (remote) { count += keep_entry(remote); } + return count; +} + +/* + * Two-way merge. + * + * The rule is to "carry forward" what is in the index without losing + * information across a "fast forward", favoring a successful merge + * over a merge failure when it makes sense. For details of the + * "carry forward" rule, please see . + * + */ +static int twoway_merge(struct cache_entry **src) +{ + struct cache_entry *current = src[0]; + struct cache_entry *oldtree = src[1], *newtree = src[2]; + + if (merge_size != 2) + return error("Cannot do a twoway merge of %d trees", + merge_size); + + if (current) { + if ((!oldtree && !newtree) || /* 4 and 5 */ + (!oldtree && newtree && + same(current, newtree)) || /* 6 and 7 */ + (oldtree && newtree && + same(oldtree, newtree)) || /* 14 and 15 */ + (oldtree && newtree && + !same(oldtree, newtree) && /* 18 and 19*/ + same(current, newtree))) { + return keep_entry(current); + } + else if (oldtree && !newtree && same(current, oldtree)) { + /* 10 or 11 */ + return deleted_entry(oldtree, current); + } + else if (oldtree && newtree && + same(current, oldtree) && !same(current, newtree)) { + /* 20 or 21 */ + return merged_entry(newtree, current); + } + else { + /* all other failures */ + if (oldtree) + reject_merge(oldtree); + if (current) + reject_merge(current); + if (newtree) + reject_merge(newtree); + return -1; + } + } + else if (newtree) + return merged_entry(newtree, current); + else + return deleted_entry(oldtree, current); +} + +/* + * One-way merge. + * + * The rule is: + * - take the stat information from stage0, take the data from stage1 + */ +static int oneway_merge(struct cache_entry **src) +{ + struct cache_entry *old = src[0]; + struct cache_entry *a = src[1]; + + if (merge_size != 1) + return error("Cannot do a oneway merge of %d trees", + merge_size); + + if (!a) + 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++; + continue; + } + if (deleted) + *dst = ce; + dst++; + } + active_nr -= deleted; + return deleted; +} + +static const char read_tree_usage[] = "git-read-tree ( | -m [--aggressive] [-u | -i] [ []])"; + +static 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; + } + + /* This differs from "-m" in that we'll silently ignore unmerged entries */ + if (!strcmp(arg, "--reset")) { + if (stage || merge) + usage(read_tree_usage); + reset = 1; + merge = 1; + stage = 1; + read_cache_unmerged(); + continue; + } + + if (!strcmp(arg, "--trivial")) { + trivial_merges_only = 1; + continue; + } + + if (!strcmp(arg, "--aggressive")) { + aggressive = 1; + continue; + } + + /* "-m" stands for "merge", meaning we start in stage 1 */ + if (!strcmp(arg, "-m")) { + if (stage || merge) + 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 (merge) { + if (stage < 2) + die("just how do you expect me to merge %d trees?", stage-1); + switch (stage - 1) { + case 1: + fn = oneway_merge; + break; + case 2: + fn = twoway_merge; + break; + case 3: + fn = threeway_merge; + break; + default: + fn = threeway_merge; + break; + } + + if (stage - 1 >= 3) + head_idx = stage - 2; + else + head_idx = 1; + } + + unpack_trees(fn); + if (write_cache(newfd, active_cache, active_nr) || + commit_index_file(&cache_file)) + die("unable to write new index file"); + return 0; +} diff --git a/builtin.h b/builtin.h index d210543948..88b3523c25 100644 --- a/builtin.h +++ b/builtin.h @@ -30,5 +30,6 @@ extern int cmd_init_db(int argc, const char **argv, char **envp); extern int cmd_ls_files(int argc, const char **argv, char **envp); extern int cmd_ls_tree(int argc, const char **argv, char **envp); extern int cmd_tar_tree(int argc, const char **argv, char **envp); +extern int cmd_read_tree(int argc, const char **argv, char **envp); #endif diff --git a/git.c b/git.c index c253e60953..300e2b269c 100644 --- a/git.c +++ b/git.c @@ -55,7 +55,8 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "check-ref-format", cmd_check_ref_format }, { "ls-files", cmd_ls_files }, { "ls-tree", cmd_ls_tree }, - { "tar-tree", cmd_tar_tree } + { "tar-tree", cmd_tar_tree }, + { "read-tree", cmd_read_tree } }; int i; diff --git a/read-tree.c b/read-tree.c deleted file mode 100644 index 82e2a9a4d3..0000000000 --- a/read-tree.c +++ /dev/null @@ -1,881 +0,0 @@ -/* - * 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 -#include - -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 int head_idx = -1; -static int merge_size = 0; - -static struct object_list *trees = NULL; - -static struct cache_entry df_conflict_entry = { -}; - -static struct tree_entry_list df_conflict_list = { - .name = NULL, - .next = &df_conflict_list -}; - -typedef int (*merge_fn_t)(struct cache_entry **src); - -static int entcmp(char *name1, int dir1, char *name2, int dir2) -{ - int len1 = strlen(name1); - int len2 = strlen(name2); - int len = len1 < len2 ? len1 : len2; - int ret = memcmp(name1, name2, len); - unsigned char c1, c2; - if (ret) - return ret; - c1 = name1[len]; - c2 = name2[len]; - if (!c1 && dir1) - c1 = '/'; - if (!c2 && dir2) - c2 = '/'; - ret = (c1 < c2) ? -1 : (c1 > c2) ? 1 : 0; - if (c1 && c2 && !ret) - ret = len1 - len2; - return ret; -} - -static int unpack_trees_rec(struct tree_entry_list **posns, int len, - const char *base, merge_fn_t fn, int *indpos) -{ - int baselen = strlen(base); - int src_size = len + 1; - do { - int i; - char *first; - int firstdir = 0; - int pathlen; - unsigned ce_size; - struct tree_entry_list **subposns; - struct cache_entry **src; - int any_files = 0; - int any_dirs = 0; - char *cache_name; - int ce_stage; - - /* Find the first name in the input. */ - - first = NULL; - cache_name = NULL; - - /* Check the cache */ - if (merge && *indpos < active_nr) { - /* This is a bit tricky: */ - /* If the index has a subdirectory (with - * contents) as the first name, it'll get a - * filename like "foo/bar". But that's after - * "foo", so the entry in trees will get - * handled first, at which point we'll go into - * "foo", and deal with "bar" from the index, - * because the base will be "foo/". The only - * way we can actually have "foo/bar" first of - * all the things is if the trees don't - * contain "foo" at all, in which case we'll - * handle "foo/bar" without going into the - * directory, but that's fine (and will return - * an error anyway, with the added unknown - * file case. - */ - - cache_name = active_cache[*indpos]->name; - if (strlen(cache_name) > baselen && - !memcmp(cache_name, base, baselen)) { - cache_name += baselen; - first = cache_name; - } else { - cache_name = NULL; - } - } - -#if DBRT_DEBUG > 1 - if (first) - printf("index %s\n", first); -#endif - for (i = 0; i < len; i++) { - if (!posns[i] || posns[i] == &df_conflict_list) - continue; -#if DBRT_DEBUG > 1 - printf("%d %s\n", i + 1, posns[i]->name); -#endif - if (!first || entcmp(first, firstdir, - posns[i]->name, - posns[i]->directory) > 0) { - first = posns[i]->name; - firstdir = posns[i]->directory; - } - } - /* No name means we're done */ - if (!first) - return 0; - - pathlen = strlen(first); - ce_size = cache_entry_size(baselen + pathlen); - - src = xcalloc(src_size, sizeof(struct cache_entry *)); - - subposns = xcalloc(len, sizeof(struct tree_list_entry *)); - - if (cache_name && !strcmp(cache_name, first)) { - any_files = 1; - src[0] = active_cache[*indpos]; - remove_cache_entry_at(*indpos); - } - - for (i = 0; i < len; i++) { - struct cache_entry *ce; - - if (!posns[i] || - (posns[i] != &df_conflict_list && - strcmp(first, posns[i]->name))) { - continue; - } - - if (posns[i] == &df_conflict_list) { - src[i + merge] = &df_conflict_entry; - continue; - } - - if (posns[i]->directory) { - any_dirs = 1; - parse_tree(posns[i]->item.tree); - subposns[i] = posns[i]->item.tree->entries; - posns[i] = posns[i]->next; - src[i + merge] = &df_conflict_entry; - continue; - } - - if (!merge) - ce_stage = 0; - else if (i + 1 < head_idx) - ce_stage = 1; - else if (i + 1 > head_idx) - ce_stage = 3; - else - ce_stage = 2; - - ce = xcalloc(1, ce_size); - ce->ce_mode = create_ce_mode(posns[i]->mode); - ce->ce_flags = create_ce_flags(baselen + pathlen, - ce_stage); - memcpy(ce->name, base, baselen); - memcpy(ce->name + baselen, first, pathlen + 1); - - any_files = 1; - - memcpy(ce->sha1, posns[i]->item.any->sha1, 20); - src[i + merge] = ce; - subposns[i] = &df_conflict_list; - posns[i] = posns[i]->next; - } - if (any_files) { - if (merge) { - int ret; - -#if DBRT_DEBUG > 1 - printf("%s:\n", first); - for (i = 0; i < src_size; i++) { - printf(" %d ", i); - if (src[i]) - printf("%s\n", sha1_to_hex(src[i]->sha1)); - else - printf("\n"); - } -#endif - ret = fn(src); - -#if DBRT_DEBUG > 1 - printf("Added %d entries\n", ret); -#endif - *indpos += ret; - } else { - for (i = 0; i < src_size; i++) { - if (src[i]) { - add_cache_entry(src[i], ADD_CACHE_OK_TO_ADD|ADD_CACHE_SKIP_DFCHECK); - } - } - } - } - if (any_dirs) { - char *newbase = xmalloc(baselen + 2 + pathlen); - memcpy(newbase, base, baselen); - memcpy(newbase + baselen, first, pathlen); - newbase[baselen + pathlen] = '/'; - newbase[baselen + pathlen + 1] = '\0'; - if (unpack_trees_rec(subposns, len, newbase, fn, - indpos)) - return -1; - free(newbase); - } - free(subposns); - free(src); - } while (1); -} - -static void reject_merge(struct cache_entry *ce) -{ - die("Entry '%s' would be overwritten by merge. Cannot merge.", - ce->name); -} - -/* Unlink the last component and attempt to remove leading - * directories, in case this unlink is the removal of the - * last entry in the directory -- empty directories are removed. - */ -static void unlink_entry(char *name) -{ - char *cp, *prev; - - if (unlink(name)) - return; - prev = NULL; - while (1) { - int status; - cp = strrchr(name, '/'); - if (prev) - *prev = '/'; - if (!cp) - break; - - *cp = 0; - status = rmdir(name); - if (status) { - *cp = '/'; - break; - } - prev = cp; - } -} - -static void progress_interval(int signum) -{ - progress_update = 1; -} - -static void setup_progress_signal(void) -{ - struct sigaction sa; - struct itimerval v; - - memset(&sa, 0, sizeof(sa)); - sa.sa_handler = progress_interval; - sigemptyset(&sa.sa_mask); - sa.sa_flags = SA_RESTART; - sigaction(SIGALRM, &sa, NULL); - - v.it_interval.tv_sec = 1; - v.it_interval.tv_usec = 0; - v.it_value = v.it_interval; - setitimer(ITIMER_REAL, &v, NULL); -} - -static void check_updates(struct cache_entry **src, int nr) -{ - static struct checkout state = { - .base_dir = "", - .force = 1, - .quiet = 1, - .refresh_cache = 1, - }; - unsigned short mask = htons(CE_UPDATE); - unsigned last_percent = 200, cnt = 0, total = 0; - - if (update && verbose_update) { - for (total = cnt = 0; cnt < nr; cnt++) { - struct cache_entry *ce = src[cnt]; - if (!ce->ce_mode || ce->ce_flags & mask) - total++; - } - - /* Don't bother doing this for very small updates */ - if (total < 250) - total = 0; - - if (total) { - fprintf(stderr, "Checking files out...\n"); - setup_progress_signal(); - progress_update = 1; - } - cnt = 0; - } - - while (nr--) { - struct cache_entry *ce = *src++; - - if (total) { - if (!ce->ce_mode || ce->ce_flags & mask) { - unsigned percent; - cnt++; - percent = (cnt * 100) / total; - if (percent != last_percent || - progress_update) { - fprintf(stderr, "%4u%% (%u/%u) done\r", - percent, cnt, total); - last_percent = percent; - } - } - } - if (!ce->ce_mode) { - if (update) - unlink_entry(ce->name); - continue; - } - if (ce->ce_flags & mask) { - ce->ce_flags &= ~mask; - if (update) - checkout_entry(ce, &state, NULL); - } - } - if (total) { - signal(SIGALRM, SIG_IGN); - fputc('\n', stderr); - } -} - -static int unpack_trees(merge_fn_t fn) -{ - int indpos = 0; - unsigned len = object_list_length(trees); - struct tree_entry_list **posns; - int i; - struct object_list *posn = trees; - merge_size = len; - - if (len) { - posns = xmalloc(len * sizeof(struct tree_entry_list *)); - for (i = 0; i < len; i++) { - posns[i] = ((struct tree *) posn->item)->entries; - posn = posn->next; - } - if (unpack_trees_rec(posns, len, "", fn, &indpos)) - 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); -} - -/* - * 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); - } - } - else - verify_absent(merge->name, "overwritten"); - - 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); - return 1; -} - -static int keep_entry(struct cache_entry *ce) -{ - add_cache_entry(ce, ADD_CACHE_OK_TO_ADD); - return 1; -} - -#if DBRT_DEBUG -static void show_stage_entry(FILE *o, - const char *label, const struct cache_entry *ce) -{ - if (!ce) - fprintf(o, "%s (missing)\n", label); - else - fprintf(o, "%s%06o %s %d\t%s\n", - label, - ntohl(ce->ce_mode), - sha1_to_hex(ce->sha1), - ce_stage(ce), - ce->name); -} -#endif - -static int threeway_merge(struct cache_entry **stages) -{ - struct cache_entry *index; - struct cache_entry *head; - struct cache_entry *remote = stages[head_idx + 1]; - int count; - int head_match = 0; - int remote_match = 0; - const char *path = NULL; - - int df_conflict_head = 0; - int df_conflict_remote = 0; - - int any_anc_missing = 0; - int no_anc_exists = 1; - int i; - - for (i = 1; i < head_idx; i++) { - if (!stages[i]) - any_anc_missing = 1; - else { - if (!path) - path = stages[i]->name; - no_anc_exists = 0; - } - } - - index = stages[0]; - head = stages[head_idx]; - - if (head == &df_conflict_entry) { - df_conflict_head = 1; - head = NULL; - } - - if (remote == &df_conflict_entry) { - df_conflict_remote = 1; - remote = NULL; - } - - if (!path && index) - path = index->name; - if (!path && head) - path = head->name; - if (!path && remote) - path = remote->name; - - /* First, if there's a #16 situation, note that to prevent #13 - * and #14. - */ - if (!same(remote, head)) { - for (i = 1; i < head_idx; i++) { - if (same(stages[i], head)) { - head_match = i; - } - if (same(stages[i], remote)) { - remote_match = i; - } - } - } - - /* We start with cases where the index is allowed to match - * something other than the head: #14(ALT) and #2ALT, where it - * is permitted to match the result instead. - */ - /* #14, #14ALT, #2ALT */ - if (remote && !df_conflict_head && head_match && !remote_match) { - if (index && !same(index, remote) && !same(index, head)) - reject_merge(index); - return merged_entry(remote, index); - } - /* - * If we have an entry in the index cache, then we want to - * make sure that it matches head. - */ - if (index && !same(index, head)) { - reject_merge(index); - } - - if (head) { - /* #5ALT, #15 */ - if (same(head, remote)) - return merged_entry(head, index); - /* #13, #3ALT */ - if (!df_conflict_remote && remote_match && !head_match) - return merged_entry(head, index); - } - - /* #1 */ - if (!head && !remote && any_anc_missing) - return 0; - - /* Under the new "aggressive" rule, we resolve mostly trivial - * cases that we historically had git-merge-one-file resolve. - */ - if (aggressive) { - int head_deleted = !head && !df_conflict_head; - int remote_deleted = !remote && !df_conflict_remote; - /* - * Deleted in both. - * Deleted in one and unchanged in the other. - */ - if ((head_deleted && remote_deleted) || - (head_deleted && remote && remote_match) || - (remote_deleted && head && head_match)) { - if (index) - return deleted_entry(index, index); - else if (path) - verify_absent(path, "removed"); - return 0; - } - /* - * Added in both, identically. - */ - if (no_anc_exists && head && remote && same(head, remote)) - return merged_entry(head, index); - - } - - /* Below are "no merge" cases, which require that the index be - * up-to-date to avoid the files getting overwritten with - * conflict resolution files. - */ - if (index) { - verify_uptodate(index); - } - else if (path) - verify_absent(path, "overwritten"); - - nontrivial_merge = 1; - - /* #2, #3, #4, #6, #7, #9, #11. */ - count = 0; - if (!head_match || !remote_match) { - for (i = 1; i < head_idx; i++) { - if (stages[i]) { - keep_entry(stages[i]); - count++; - break; - } - } - } -#if DBRT_DEBUG - else { - fprintf(stderr, "read-tree: warning #16 detected\n"); - show_stage_entry(stderr, "head ", stages[head_match]); - show_stage_entry(stderr, "remote ", stages[remote_match]); - } -#endif - if (head) { count += keep_entry(head); } - if (remote) { count += keep_entry(remote); } - return count; -} - -/* - * Two-way merge. - * - * The rule is to "carry forward" what is in the index without losing - * information across a "fast forward", favoring a successful merge - * over a merge failure when it makes sense. For details of the - * "carry forward" rule, please see . - * - */ -static int twoway_merge(struct cache_entry **src) -{ - struct cache_entry *current = src[0]; - struct cache_entry *oldtree = src[1], *newtree = src[2]; - - if (merge_size != 2) - return error("Cannot do a twoway merge of %d trees", - merge_size); - - if (current) { - if ((!oldtree && !newtree) || /* 4 and 5 */ - (!oldtree && newtree && - same(current, newtree)) || /* 6 and 7 */ - (oldtree && newtree && - same(oldtree, newtree)) || /* 14 and 15 */ - (oldtree && newtree && - !same(oldtree, newtree) && /* 18 and 19*/ - same(current, newtree))) { - return keep_entry(current); - } - else if (oldtree && !newtree && same(current, oldtree)) { - /* 10 or 11 */ - return deleted_entry(oldtree, current); - } - else if (oldtree && newtree && - same(current, oldtree) && !same(current, newtree)) { - /* 20 or 21 */ - return merged_entry(newtree, current); - } - else { - /* all other failures */ - if (oldtree) - reject_merge(oldtree); - if (current) - reject_merge(current); - if (newtree) - reject_merge(newtree); - return -1; - } - } - else if (newtree) - return merged_entry(newtree, current); - else - return deleted_entry(oldtree, current); -} - -/* - * One-way merge. - * - * The rule is: - * - take the stat information from stage0, take the data from stage1 - */ -static int oneway_merge(struct cache_entry **src) -{ - struct cache_entry *old = src[0]; - struct cache_entry *a = src[1]; - - if (merge_size != 1) - return error("Cannot do a oneway merge of %d trees", - merge_size); - - if (!a) - 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++; - continue; - } - if (deleted) - *dst = ce; - dst++; - } - active_nr -= deleted; - return deleted; -} - -static const char read_tree_usage[] = "git-read-tree ( | -m [--aggressive] [-u | -i] [ []])"; - -static struct cache_file cache_file; - -int main(int argc, char **argv) -{ - 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; - } - - /* This differs from "-m" in that we'll silently ignore unmerged entries */ - if (!strcmp(arg, "--reset")) { - if (stage || merge) - usage(read_tree_usage); - reset = 1; - merge = 1; - stage = 1; - read_cache_unmerged(); - continue; - } - - if (!strcmp(arg, "--trivial")) { - trivial_merges_only = 1; - continue; - } - - if (!strcmp(arg, "--aggressive")) { - aggressive = 1; - continue; - } - - /* "-m" stands for "merge", meaning we start in stage 1 */ - if (!strcmp(arg, "-m")) { - if (stage || merge) - 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 (merge) { - if (stage < 2) - die("just how do you expect me to merge %d trees?", stage-1); - switch (stage - 1) { - case 1: - fn = oneway_merge; - break; - case 2: - fn = twoway_merge; - break; - case 3: - fn = threeway_merge; - break; - default: - fn = threeway_merge; - break; - } - - if (stage - 1 >= 3) - head_idx = stage - 2; - else - head_idx = 1; - } - - unpack_trees(fn); - if (write_cache(newfd, active_cache, active_nr) || - commit_index_file(&cache_file)) - die("unable to write new index file"); - return 0; -} -- cgit v1.3 From 6d96ac18e52aca19ff1087ffa64e2d616cc75c6f Mon Sep 17 00:00:00 2001 From: Peter Eriksen Date: Tue, 23 May 2006 14:15:33 +0200 Subject: Builtin git-commit-tree. Signed-off-by: Peter Eriksen Signed-off-by: Junio C Hamano --- Makefile | 6 +-- builtin-commit-tree.c | 140 ++++++++++++++++++++++++++++++++++++++++++++++++++ builtin.h | 1 + commit-tree.c | 139 ------------------------------------------------- git.c | 3 +- 5 files changed, 146 insertions(+), 143 deletions(-) create mode 100644 builtin-commit-tree.c delete mode 100644 commit-tree.c (limited to 'Makefile') diff --git a/Makefile b/Makefile index 1aebcf5978..a1bee61e57 100644 --- a/Makefile +++ b/Makefile @@ -150,7 +150,7 @@ SIMPLE_PROGRAMS = \ # ... and all the rest that could be moved out of bindir to gitexecdir PROGRAMS = \ git-apply$X git-cat-file$X \ - git-checkout-index$X git-clone-pack$X git-commit-tree$X \ + git-checkout-index$X git-clone-pack$X \ git-convert-objects$X git-diff-files$X \ git-diff-index$X git-diff-stages$X \ git-diff-tree$X git-fetch-pack$X git-fsck-objects$X \ @@ -172,7 +172,7 @@ 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-tar-tree$X git-read-tree$X + git-tar-tree$X git-read-tree$X git-commit-tree$X # what 'all' will build and 'install' will install, in gitexecdir ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS) @@ -222,7 +222,7 @@ 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-tar-tree.o builtin-read-tree.o + builtin-tar-tree.o builtin-read-tree.o builtin-commit-tree.o GITLIBS = $(LIB_FILE) $(XDIFF_LIB) LIBS = $(GITLIBS) -lz diff --git a/builtin-commit-tree.c b/builtin-commit-tree.c new file mode 100644 index 0000000000..ec082bf754 --- /dev/null +++ b/builtin-commit-tree.c @@ -0,0 +1,140 @@ +/* + * GIT - The information manager from hell + * + * Copyright (C) Linus Torvalds, 2005 + */ +#include "cache.h" +#include "commit.h" +#include "tree.h" +#include "builtin.h" + +#define BLOCKING (1ul << 14) + +/* + * FIXME! Share the code with "write-tree.c" + */ +static void init_buffer(char **bufp, unsigned int *sizep) +{ + char *buf = xmalloc(BLOCKING); + *sizep = 0; + *bufp = buf; +} + +static void add_buffer(char **bufp, unsigned int *sizep, const char *fmt, ...) +{ + char one_line[2048]; + va_list args; + int len; + unsigned long alloc, size, newsize; + char *buf; + + va_start(args, fmt); + len = vsnprintf(one_line, sizeof(one_line), fmt, args); + va_end(args); + size = *sizep; + newsize = size + len; + alloc = (size + 32767) & ~32767; + buf = *bufp; + if (newsize > alloc) { + alloc = (newsize + 32767) & ~32767; + buf = xrealloc(buf, alloc); + *bufp = buf; + } + *sizep = newsize; + memcpy(buf + size, one_line, len); +} + +static void check_valid(unsigned char *sha1, const char *expect) +{ + char type[20]; + + if (sha1_object_info(sha1, type, NULL)) + die("%s is not a valid object", sha1_to_hex(sha1)); + if (expect && strcmp(type, expect)) + die("%s is not a valid '%s' object", sha1_to_hex(sha1), + expect); +} + +/* + * Having more than two parents is not strange at all, and this is + * how multi-way merges are represented. + */ +#define MAXPARENT (16) +static unsigned char parent_sha1[MAXPARENT][20]; + +static const char commit_tree_usage[] = "git-commit-tree [-p ]* < changelog"; + +static int new_parent(int idx) +{ + int i; + unsigned char *sha1 = parent_sha1[idx]; + for (i = 0; i < idx; i++) { + if (!memcmp(parent_sha1[i], sha1, 20)) { + error("duplicate parent %s ignored", sha1_to_hex(sha1)); + return 0; + } + } + return 1; +} + +int cmd_commit_tree(int argc, const char **argv, char **envp) +{ + int i; + int parents = 0; + unsigned char tree_sha1[20]; + unsigned char commit_sha1[20]; + char comment[1000]; + char *buffer; + unsigned int size; + + setup_ident(); + setup_git_directory(); + + git_config(git_default_config); + + if (argc < 2) + usage(commit_tree_usage); + if (get_sha1(argv[1], tree_sha1)) + die("Not a valid object name %s", argv[1]); + + check_valid(tree_sha1, tree_type); + for (i = 2; i < argc; i += 2) { + const char *a, *b; + a = argv[i]; b = argv[i+1]; + if (!b || strcmp(a, "-p")) + usage(commit_tree_usage); + if (get_sha1(b, parent_sha1[parents])) + die("Not a valid object name %s", b); + check_valid(parent_sha1[parents], commit_type); + if (new_parent(parents)) + parents++; + } + if (!parents) + fprintf(stderr, "Committing initial tree %s\n", argv[1]); + + init_buffer(&buffer, &size); + add_buffer(&buffer, &size, "tree %s\n", sha1_to_hex(tree_sha1)); + + /* + * NOTE! This ordering means that the same exact tree merged with a + * different order of parents will be a _different_ changeset even + * if everything else stays the same. + */ + for (i = 0; i < parents; i++) + add_buffer(&buffer, &size, "parent %s\n", sha1_to_hex(parent_sha1[i])); + + /* Person/date information */ + add_buffer(&buffer, &size, "author %s\n", git_author_info(1)); + add_buffer(&buffer, &size, "committer %s\n\n", git_committer_info(1)); + + /* And add the comment */ + while (fgets(comment, sizeof(comment), stdin) != NULL) + add_buffer(&buffer, &size, "%s", comment); + + if (!write_sha1_file(buffer, size, commit_type, commit_sha1)) { + printf("%s\n", sha1_to_hex(commit_sha1)); + return 0; + } + else + return 1; +} diff --git a/builtin.h b/builtin.h index 88b3523c25..c6b07d9a65 100644 --- a/builtin.h +++ b/builtin.h @@ -31,5 +31,6 @@ extern int cmd_ls_files(int argc, const char **argv, char **envp); extern int cmd_ls_tree(int argc, const char **argv, char **envp); extern int cmd_tar_tree(int argc, const char **argv, char **envp); extern int cmd_read_tree(int argc, const char **argv, char **envp); +extern int cmd_commit_tree(int argc, const char **argv, char **envp); #endif diff --git a/commit-tree.c b/commit-tree.c deleted file mode 100644 index 0320036e80..0000000000 --- a/commit-tree.c +++ /dev/null @@ -1,139 +0,0 @@ -/* - * GIT - The information manager from hell - * - * Copyright (C) Linus Torvalds, 2005 - */ -#include "cache.h" -#include "commit.h" -#include "tree.h" - -#define BLOCKING (1ul << 14) - -/* - * FIXME! Share the code with "write-tree.c" - */ -static void init_buffer(char **bufp, unsigned int *sizep) -{ - char *buf = xmalloc(BLOCKING); - *sizep = 0; - *bufp = buf; -} - -static void add_buffer(char **bufp, unsigned int *sizep, const char *fmt, ...) -{ - char one_line[2048]; - va_list args; - int len; - unsigned long alloc, size, newsize; - char *buf; - - va_start(args, fmt); - len = vsnprintf(one_line, sizeof(one_line), fmt, args); - va_end(args); - size = *sizep; - newsize = size + len; - alloc = (size + 32767) & ~32767; - buf = *bufp; - if (newsize > alloc) { - alloc = (newsize + 32767) & ~32767; - buf = xrealloc(buf, alloc); - *bufp = buf; - } - *sizep = newsize; - memcpy(buf + size, one_line, len); -} - -static void check_valid(unsigned char *sha1, const char *expect) -{ - char type[20]; - - if (sha1_object_info(sha1, type, NULL)) - die("%s is not a valid object", sha1_to_hex(sha1)); - if (expect && strcmp(type, expect)) - die("%s is not a valid '%s' object", sha1_to_hex(sha1), - expect); -} - -/* - * Having more than two parents is not strange at all, and this is - * how multi-way merges are represented. - */ -#define MAXPARENT (16) -static unsigned char parent_sha1[MAXPARENT][20]; - -static const char commit_tree_usage[] = "git-commit-tree [-p ]* < changelog"; - -static int new_parent(int idx) -{ - int i; - unsigned char *sha1 = parent_sha1[idx]; - for (i = 0; i < idx; i++) { - if (!memcmp(parent_sha1[i], sha1, 20)) { - error("duplicate parent %s ignored", sha1_to_hex(sha1)); - return 0; - } - } - return 1; -} - -int main(int argc, char **argv) -{ - int i; - int parents = 0; - unsigned char tree_sha1[20]; - unsigned char commit_sha1[20]; - char comment[1000]; - char *buffer; - unsigned int size; - - setup_ident(); - setup_git_directory(); - - git_config(git_default_config); - - if (argc < 2) - usage(commit_tree_usage); - if (get_sha1(argv[1], tree_sha1)) - die("Not a valid object name %s", argv[1]); - - check_valid(tree_sha1, tree_type); - for (i = 2; i < argc; i += 2) { - char *a, *b; - a = argv[i]; b = argv[i+1]; - if (!b || strcmp(a, "-p")) - usage(commit_tree_usage); - if (get_sha1(b, parent_sha1[parents])) - die("Not a valid object name %s", b); - check_valid(parent_sha1[parents], commit_type); - if (new_parent(parents)) - parents++; - } - if (!parents) - fprintf(stderr, "Committing initial tree %s\n", argv[1]); - - init_buffer(&buffer, &size); - add_buffer(&buffer, &size, "tree %s\n", sha1_to_hex(tree_sha1)); - - /* - * NOTE! This ordering means that the same exact tree merged with a - * different order of parents will be a _different_ changeset even - * if everything else stays the same. - */ - for (i = 0; i < parents; i++) - add_buffer(&buffer, &size, "parent %s\n", sha1_to_hex(parent_sha1[i])); - - /* Person/date information */ - add_buffer(&buffer, &size, "author %s\n", git_author_info(1)); - add_buffer(&buffer, &size, "committer %s\n\n", git_committer_info(1)); - - /* And add the comment */ - while (fgets(comment, sizeof(comment), stdin) != NULL) - add_buffer(&buffer, &size, "%s", comment); - - if (!write_sha1_file(buffer, size, commit_type, commit_sha1)) { - printf("%s\n", sha1_to_hex(commit_sha1)); - return 0; - } - else - return 1; -} diff --git a/git.c b/git.c index 300e2b269c..4c2c062e36 100644 --- a/git.c +++ b/git.c @@ -56,7 +56,8 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "ls-files", cmd_ls_files }, { "ls-tree", cmd_ls_tree }, { "tar-tree", cmd_tar_tree }, - { "read-tree", cmd_read_tree } + { "read-tree", cmd_read_tree }, + { "commit-tree", cmd_commit_tree } }; int i; -- cgit v1.3 From ac6245e31a359200b65bfdd910bba9a0fbe90c11 Mon Sep 17 00:00:00 2001 From: Peter Eriksen Date: Tue, 23 May 2006 14:15:34 +0200 Subject: Builtin git-apply. Signed-off-by: Peter Eriksen Signed-off-by: Junio C Hamano --- Makefile | 8 +- apply.c | 2299 ------------------------------------------------------ builtin-apply.c | 2300 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ builtin.h | 1 + git.c | 3 +- 5 files changed, 2308 insertions(+), 2303 deletions(-) delete mode 100644 apply.c create mode 100644 builtin-apply.c (limited to 'Makefile') diff --git a/Makefile b/Makefile index a1bee61e57..3da576838f 100644 --- a/Makefile +++ b/Makefile @@ -149,7 +149,7 @@ SIMPLE_PROGRAMS = \ # ... and all the rest that could be moved out of bindir to gitexecdir PROGRAMS = \ - git-apply$X git-cat-file$X \ + git-cat-file$X \ git-checkout-index$X git-clone-pack$X \ git-convert-objects$X git-diff-files$X \ git-diff-index$X git-diff-stages$X \ @@ -172,7 +172,8 @@ 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-tar-tree$X git-read-tree$X git-commit-tree$X + git-tar-tree$X git-read-tree$X git-commit-tree$X \ + git-apply$X # what 'all' will build and 'install' will install, in gitexecdir ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS) @@ -222,7 +223,8 @@ 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-tar-tree.o builtin-read-tree.o builtin-commit-tree.o + builtin-tar-tree.o builtin-read-tree.o builtin-commit-tree.o \ + builtin-apply.o GITLIBS = $(LIB_FILE) $(XDIFF_LIB) LIBS = $(GITLIBS) -lz diff --git a/apply.c b/apply.c deleted file mode 100644 index 0ed9d132e8..0000000000 --- a/apply.c +++ /dev/null @@ -1,2299 +0,0 @@ -/* - * apply.c - * - * Copyright (C) Linus Torvalds, 2005 - * - * This applies patches on top of some (arbitrary) version of the SCM. - * - */ -#include -#include "cache.h" -#include "quote.h" -#include "blob.h" -#include "delta.h" - -// --check turns on checking that the working tree matches the -// files that are being modified, but doesn't apply the patch -// --stat does just a diffstat, and doesn't actually apply -// --numstat does numeric diffstat, and doesn't actually apply -// --index-info shows the old and new index info for paths if available. -// --index updates the cache as well. -// --cached updates only the cache without ever touching the working tree. -// -static const char *prefix; -static int prefix_length = -1; -static int newfd = -1; - -static int p_value = 1; -static int allow_binary_replacement = 0; -static int check_index = 0; -static int write_index = 0; -static int cached = 0; -static int diffstat = 0; -static int numstat = 0; -static int summary = 0; -static int check = 0; -static int apply = 1; -static int no_add = 0; -static int show_index_info = 0; -static int line_termination = '\n'; -static unsigned long p_context = -1; -static const char apply_usage[] = -"git-apply [--stat] [--numstat] [--summary] [--check] [--index] [--cached] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [-z] [-pNUM] [-CNUM] [--whitespace=] ..."; - -static enum whitespace_eol { - nowarn_whitespace, - warn_on_whitespace, - error_on_whitespace, - strip_whitespace, -} new_whitespace = warn_on_whitespace; -static int whitespace_error = 0; -static int squelch_whitespace_errors = 5; -static int applied_after_stripping = 0; -static const char *patch_input_file = NULL; - -static void parse_whitespace_option(const char *option) -{ - if (!option) { - new_whitespace = warn_on_whitespace; - return; - } - if (!strcmp(option, "warn")) { - new_whitespace = warn_on_whitespace; - return; - } - if (!strcmp(option, "nowarn")) { - new_whitespace = nowarn_whitespace; - return; - } - if (!strcmp(option, "error")) { - new_whitespace = error_on_whitespace; - return; - } - if (!strcmp(option, "error-all")) { - new_whitespace = error_on_whitespace; - squelch_whitespace_errors = 0; - return; - } - if (!strcmp(option, "strip")) { - new_whitespace = strip_whitespace; - return; - } - die("unrecognized whitespace option '%s'", option); -} - -static void set_default_whitespace_mode(const char *whitespace_option) -{ - if (!whitespace_option && !apply_default_whitespace) { - new_whitespace = (apply - ? warn_on_whitespace - : nowarn_whitespace); - } -} - -/* - * For "diff-stat" like behaviour, we keep track of the biggest change - * we've seen, and the longest filename. That allows us to do simple - * scaling. - */ -static int max_change, max_len; - -/* - * Various "current state", notably line numbers and what - * file (and how) we're patching right now.. The "is_xxxx" - * things are flags, where -1 means "don't know yet". - */ -static int linenr = 1; - -struct fragment { - unsigned long leading, trailing; - unsigned long oldpos, oldlines; - unsigned long newpos, newlines; - const char *patch; - int size; - struct fragment *next; -}; - -struct patch { - char *new_name, *old_name, *def_name; - unsigned int old_mode, new_mode; - int is_rename, is_copy, is_new, is_delete, is_binary; -#define BINARY_DELTA_DEFLATED 1 -#define BINARY_LITERAL_DEFLATED 2 - unsigned long deflate_origlen; - int lines_added, lines_deleted; - int score; - struct fragment *fragments; - char *result; - unsigned long resultsize; - char old_sha1_prefix[41]; - char new_sha1_prefix[41]; - struct patch *next; -}; - -#define CHUNKSIZE (8192) -#define SLOP (16) - -static void *read_patch_file(int fd, unsigned long *sizep) -{ - unsigned long size = 0, alloc = CHUNKSIZE; - void *buffer = xmalloc(alloc); - - for (;;) { - int nr = alloc - size; - if (nr < 1024) { - alloc += CHUNKSIZE; - buffer = xrealloc(buffer, alloc); - nr = alloc - size; - } - nr = xread(fd, buffer + size, nr); - if (!nr) - break; - if (nr < 0) - die("git-apply: read returned %s", strerror(errno)); - size += nr; - } - *sizep = size; - - /* - * Make sure that we have some slop in the buffer - * so that we can do speculative "memcmp" etc, and - * see to it that it is NUL-filled. - */ - if (alloc < size + SLOP) - buffer = xrealloc(buffer, size + SLOP); - memset(buffer + size, 0, SLOP); - return buffer; -} - -static unsigned long linelen(const char *buffer, unsigned long size) -{ - unsigned long len = 0; - while (size--) { - len++; - if (*buffer++ == '\n') - break; - } - return len; -} - -static int is_dev_null(const char *str) -{ - return !memcmp("/dev/null", str, 9) && isspace(str[9]); -} - -#define TERM_SPACE 1 -#define TERM_TAB 2 - -static int name_terminate(const char *name, int namelen, int c, int terminate) -{ - if (c == ' ' && !(terminate & TERM_SPACE)) - return 0; - if (c == '\t' && !(terminate & TERM_TAB)) - return 0; - - return 1; -} - -static char * find_name(const char *line, char *def, int p_value, int terminate) -{ - int len; - const char *start = line; - char *name; - - if (*line == '"') { - /* Proposed "new-style" GNU patch/diff format; see - * http://marc.theaimsgroup.com/?l=git&m=112927316408690&w=2 - */ - name = unquote_c_style(line, NULL); - if (name) { - char *cp = name; - while (p_value) { - cp = strchr(name, '/'); - if (!cp) - break; - cp++; - p_value--; - } - if (cp) { - /* name can later be freed, so we need - * to memmove, not just return cp - */ - memmove(name, cp, strlen(cp) + 1); - free(def); - return name; - } - else { - free(name); - name = NULL; - } - } - } - - for (;;) { - char c = *line; - - if (isspace(c)) { - if (c == '\n') - break; - if (name_terminate(start, line-start, c, terminate)) - break; - } - line++; - if (c == '/' && !--p_value) - start = line; - } - if (!start) - return def; - len = line - start; - if (!len) - return def; - - /* - * Generally we prefer the shorter name, especially - * if the other one is just a variation of that with - * something else tacked on to the end (ie "file.orig" - * or "file~"). - */ - if (def) { - int deflen = strlen(def); - if (deflen < len && !strncmp(start, def, deflen)) - return def; - } - - name = xmalloc(len + 1); - memcpy(name, start, len); - name[len] = 0; - free(def); - return name; -} - -/* - * Get the name etc info from the --/+++ lines of a traditional patch header - * - * NOTE! This hardcodes "-p1" behaviour in filename detection. - * - * FIXME! The end-of-filename heuristics are kind of screwy. For existing - * files, we can happily check the index for a match, but for creating a - * new file we should try to match whatever "patch" does. I have no idea. - */ -static void parse_traditional_patch(const char *first, const char *second, struct patch *patch) -{ - char *name; - - first += 4; // skip "--- " - second += 4; // skip "+++ " - if (is_dev_null(first)) { - patch->is_new = 1; - patch->is_delete = 0; - name = find_name(second, NULL, p_value, TERM_SPACE | TERM_TAB); - patch->new_name = name; - } else if (is_dev_null(second)) { - patch->is_new = 0; - patch->is_delete = 1; - name = find_name(first, NULL, p_value, TERM_SPACE | TERM_TAB); - patch->old_name = name; - } else { - name = find_name(first, NULL, p_value, TERM_SPACE | TERM_TAB); - name = find_name(second, name, p_value, TERM_SPACE | TERM_TAB); - patch->old_name = patch->new_name = name; - } - if (!name) - die("unable to find filename in patch at line %d", linenr); -} - -static int gitdiff_hdrend(const char *line, struct patch *patch) -{ - return -1; -} - -/* - * We're anal about diff header consistency, to make - * sure that we don't end up having strange ambiguous - * patches floating around. - * - * As a result, gitdiff_{old|new}name() will check - * their names against any previous information, just - * to make sure.. - */ -static char *gitdiff_verify_name(const char *line, int isnull, char *orig_name, const char *oldnew) -{ - if (!orig_name && !isnull) - return find_name(line, NULL, 1, 0); - - if (orig_name) { - int len; - const char *name; - char *another; - name = orig_name; - len = strlen(name); - if (isnull) - die("git-apply: bad git-diff - expected /dev/null, got %s on line %d", name, linenr); - another = find_name(line, NULL, 1, 0); - if (!another || memcmp(another, name, len)) - die("git-apply: bad git-diff - inconsistent %s filename on line %d", oldnew, linenr); - free(another); - return orig_name; - } - else { - /* expect "/dev/null" */ - if (memcmp("/dev/null", line, 9) || line[9] != '\n') - die("git-apply: bad git-diff - expected /dev/null on line %d", linenr); - return NULL; - } -} - -static int gitdiff_oldname(const char *line, struct patch *patch) -{ - patch->old_name = gitdiff_verify_name(line, patch->is_new, patch->old_name, "old"); - return 0; -} - -static int gitdiff_newname(const char *line, struct patch *patch) -{ - patch->new_name = gitdiff_verify_name(line, patch->is_delete, patch->new_name, "new"); - return 0; -} - -static int gitdiff_oldmode(const char *line, struct patch *patch) -{ - patch->old_mode = strtoul(line, NULL, 8); - return 0; -} - -static int gitdiff_newmode(const char *line, struct patch *patch) -{ - patch->new_mode = strtoul(line, NULL, 8); - return 0; -} - -static int gitdiff_delete(const char *line, struct patch *patch) -{ - patch->is_delete = 1; - patch->old_name = patch->def_name; - return gitdiff_oldmode(line, patch); -} - -static int gitdiff_newfile(const char *line, struct patch *patch) -{ - patch->is_new = 1; - patch->new_name = patch->def_name; - return gitdiff_newmode(line, patch); -} - -static int gitdiff_copysrc(const char *line, struct patch *patch) -{ - patch->is_copy = 1; - patch->old_name = find_name(line, NULL, 0, 0); - return 0; -} - -static int gitdiff_copydst(const char *line, struct patch *patch) -{ - patch->is_copy = 1; - patch->new_name = find_name(line, NULL, 0, 0); - return 0; -} - -static int gitdiff_renamesrc(const char *line, struct patch *patch) -{ - patch->is_rename = 1; - patch->old_name = find_name(line, NULL, 0, 0); - return 0; -} - -static int gitdiff_renamedst(const char *line, struct patch *patch) -{ - patch->is_rename = 1; - patch->new_name = find_name(line, NULL, 0, 0); - return 0; -} - -static int gitdiff_similarity(const char *line, struct patch *patch) -{ - if ((patch->score = strtoul(line, NULL, 10)) == ULONG_MAX) - patch->score = 0; - return 0; -} - -static int gitdiff_dissimilarity(const char *line, struct patch *patch) -{ - if ((patch->score = strtoul(line, NULL, 10)) == ULONG_MAX) - patch->score = 0; - return 0; -} - -static int gitdiff_index(const char *line, struct patch *patch) -{ - /* index line is N hexadecimal, "..", N hexadecimal, - * and optional space with octal mode. - */ - const char *ptr, *eol; - int len; - - ptr = strchr(line, '.'); - if (!ptr || ptr[1] != '.' || 40 < ptr - line) - return 0; - len = ptr - line; - memcpy(patch->old_sha1_prefix, line, len); - patch->old_sha1_prefix[len] = 0; - - line = ptr + 2; - ptr = strchr(line, ' '); - eol = strchr(line, '\n'); - - if (!ptr || eol < ptr) - ptr = eol; - len = ptr - line; - - if (40 < len) - return 0; - memcpy(patch->new_sha1_prefix, line, len); - patch->new_sha1_prefix[len] = 0; - if (*ptr == ' ') - patch->new_mode = patch->old_mode = strtoul(ptr+1, NULL, 8); - return 0; -} - -/* - * This is normal for a diff that doesn't change anything: we'll fall through - * into the next diff. Tell the parser to break out. - */ -static int gitdiff_unrecognized(const char *line, struct patch *patch) -{ - return -1; -} - -static const char *stop_at_slash(const char *line, int llen) -{ - int i; - - for (i = 0; i < llen; i++) { - int ch = line[i]; - if (ch == '/') - return line + i; - } - return NULL; -} - -/* This is to extract the same name that appears on "diff --git" - * line. We do not find and return anything if it is a rename - * patch, and it is OK because we will find the name elsewhere. - * We need to reliably find name only when it is mode-change only, - * creation or deletion of an empty file. In any of these cases, - * both sides are the same name under a/ and b/ respectively. - */ -static char *git_header_name(char *line, int llen) -{ - int len; - const char *name; - const char *second = NULL; - - line += strlen("diff --git "); - llen -= strlen("diff --git "); - - if (*line == '"') { - const char *cp; - char *first = unquote_c_style(line, &second); - if (!first) - return NULL; - - /* advance to the first slash */ - cp = stop_at_slash(first, strlen(first)); - if (!cp || cp == first) { - /* we do not accept absolute paths */ - free_first_and_fail: - free(first); - return NULL; - } - len = strlen(cp+1); - memmove(first, cp+1, len+1); /* including NUL */ - - /* second points at one past closing dq of name. - * find the second name. - */ - while ((second < line + llen) && isspace(*second)) - second++; - - if (line + llen <= second) - goto free_first_and_fail; - if (*second == '"') { - char *sp = unquote_c_style(second, NULL); - if (!sp) - goto free_first_and_fail; - cp = stop_at_slash(sp, strlen(sp)); - if (!cp || cp == sp) { - free_both_and_fail: - free(sp); - goto free_first_and_fail; - } - /* They must match, otherwise ignore */ - if (strcmp(cp+1, first)) - goto free_both_and_fail; - free(sp); - return first; - } - - /* unquoted second */ - cp = stop_at_slash(second, line + llen - second); - if (!cp || cp == second) - goto free_first_and_fail; - cp++; - if (line + llen - cp != len + 1 || - memcmp(first, cp, len)) - goto free_first_and_fail; - return first; - } - - /* unquoted first name */ - name = stop_at_slash(line, llen); - if (!name || name == line) - return NULL; - - name++; - - /* since the first name is unquoted, a dq if exists must be - * the beginning of the second name. - */ - for (second = name; second < line + llen; second++) { - if (*second == '"') { - const char *cp = second; - const char *np; - char *sp = unquote_c_style(second, NULL); - - if (!sp) - return NULL; - np = stop_at_slash(sp, strlen(sp)); - if (!np || np == sp) { - free_second_and_fail: - free(sp); - return NULL; - } - np++; - len = strlen(np); - if (len < cp - name && - !strncmp(np, name, len) && - isspace(name[len])) { - /* Good */ - memmove(sp, np, len + 1); - return sp; - } - goto free_second_and_fail; - } - } - - /* - * Accept a name only if it shows up twice, exactly the same - * form. - */ - for (len = 0 ; ; len++) { - char c = name[len]; - - switch (c) { - default: - continue; - case '\n': - return NULL; - case '\t': case ' ': - second = name+len; - for (;;) { - char c = *second++; - if (c == '\n') - return NULL; - if (c == '/') - break; - } - if (second[len] == '\n' && !memcmp(name, second, len)) { - char *ret = xmalloc(len + 1); - memcpy(ret, name, len); - ret[len] = 0; - return ret; - } - } - } - return NULL; -} - -/* Verify that we recognize the lines following a git header */ -static int parse_git_header(char *line, int len, unsigned int size, struct patch *patch) -{ - unsigned long offset; - - /* A git diff has explicit new/delete information, so we don't guess */ - patch->is_new = 0; - patch->is_delete = 0; - - /* - * Some things may not have the old name in the - * rest of the headers anywhere (pure mode changes, - * or removing or adding empty files), so we get - * the default name from the header. - */ - patch->def_name = git_header_name(line, len); - - line += len; - size -= len; - linenr++; - for (offset = len ; size > 0 ; offset += len, size -= len, line += len, linenr++) { - static const struct opentry { - const char *str; - int (*fn)(const char *, struct patch *); - } optable[] = { - { "@@ -", gitdiff_hdrend }, - { "--- ", gitdiff_oldname }, - { "+++ ", gitdiff_newname }, - { "old mode ", gitdiff_oldmode }, - { "new mode ", gitdiff_newmode }, - { "deleted file mode ", gitdiff_delete }, - { "new file mode ", gitdiff_newfile }, - { "copy from ", gitdiff_copysrc }, - { "copy to ", gitdiff_copydst }, - { "rename old ", gitdiff_renamesrc }, - { "rename new ", gitdiff_renamedst }, - { "rename from ", gitdiff_renamesrc }, - { "rename to ", gitdiff_renamedst }, - { "similarity index ", gitdiff_similarity }, - { "dissimilarity index ", gitdiff_dissimilarity }, - { "index ", gitdiff_index }, - { "", gitdiff_unrecognized }, - }; - int i; - - len = linelen(line, size); - if (!len || line[len-1] != '\n') - break; - for (i = 0; i < ARRAY_SIZE(optable); i++) { - const struct opentry *p = optable + i; - int oplen = strlen(p->str); - if (len < oplen || memcmp(p->str, line, oplen)) - continue; - if (p->fn(line + oplen, patch) < 0) - return offset; - break; - } - } - - return offset; -} - -static int parse_num(const char *line, unsigned long *p) -{ - char *ptr; - - if (!isdigit(*line)) - return 0; - *p = strtoul(line, &ptr, 10); - return ptr - line; -} - -static int parse_range(const char *line, int len, int offset, const char *expect, - unsigned long *p1, unsigned long *p2) -{ - int digits, ex; - - if (offset < 0 || offset >= len) - return -1; - line += offset; - len -= offset; - - digits = parse_num(line, p1); - if (!digits) - return -1; - - offset += digits; - line += digits; - len -= digits; - - *p2 = 1; - if (*line == ',') { - digits = parse_num(line+1, p2); - if (!digits) - return -1; - - offset += digits+1; - line += digits+1; - len -= digits+1; - } - - ex = strlen(expect); - if (ex > len) - return -1; - if (memcmp(line, expect, ex)) - return -1; - - return offset + ex; -} - -/* - * Parse a unified diff fragment header of the - * form "@@ -a,b +c,d @@" - */ -static int parse_fragment_header(char *line, int len, struct fragment *fragment) -{ - int offset; - - if (!len || line[len-1] != '\n') - return -1; - - /* Figure out the number of lines in a fragment */ - offset = parse_range(line, len, 4, " +", &fragment->oldpos, &fragment->oldlines); - offset = parse_range(line, len, offset, " @@", &fragment->newpos, &fragment->newlines); - - return offset; -} - -static int find_header(char *line, unsigned long size, int *hdrsize, struct patch *patch) -{ - unsigned long offset, len; - - patch->is_rename = patch->is_copy = 0; - patch->is_new = patch->is_delete = -1; - patch->old_mode = patch->new_mode = 0; - patch->old_name = patch->new_name = NULL; - for (offset = 0; size > 0; offset += len, size -= len, line += len, linenr++) { - unsigned long nextlen; - - len = linelen(line, size); - if (!len) - break; - - /* Testing this early allows us to take a few shortcuts.. */ - if (len < 6) - continue; - - /* - * Make sure we don't find any unconnected patch fragmants. - * That's a sign that we didn't find a header, and that a - * patch has become corrupted/broken up. - */ - if (!memcmp("@@ -", line, 4)) { - struct fragment dummy; - if (parse_fragment_header(line, len, &dummy) < 0) - continue; - error("patch fragment without header at line %d: %.*s", linenr, (int)len-1, line); - } - - if (size < len + 6) - break; - - /* - * Git patch? It might not have a real patch, just a rename - * or mode change, so we handle that specially - */ - if (!memcmp("diff --git ", line, 11)) { - int git_hdr_len = parse_git_header(line, len, size, patch); - if (git_hdr_len <= len) - continue; - if (!patch->old_name && !patch->new_name) { - if (!patch->def_name) - die("git diff header lacks filename information (line %d)", linenr); - patch->old_name = patch->new_name = patch->def_name; - } - *hdrsize = git_hdr_len; - return offset; - } - - /** --- followed by +++ ? */ - if (memcmp("--- ", line, 4) || memcmp("+++ ", line + len, 4)) - continue; - - /* - * We only accept unified patches, so we want it to - * at least have "@@ -a,b +c,d @@\n", which is 14 chars - * minimum - */ - nextlen = linelen(line + len, size - len); - if (size < nextlen + 14 || memcmp("@@ -", line + len + nextlen, 4)) - continue; - - /* Ok, we'll consider it a patch */ - parse_traditional_patch(line, line+len, patch); - *hdrsize = len + nextlen; - linenr += 2; - return offset; - } - return -1; -} - -/* - * Parse a unified diff. Note that this really needs - * to parse each fragment separately, since the only - * way to know the difference between a "---" that is - * part of a patch, and a "---" that starts the next - * patch is to look at the line counts.. - */ -static int parse_fragment(char *line, unsigned long size, struct patch *patch, struct fragment *fragment) -{ - int added, deleted; - int len = linelen(line, size), offset; - unsigned long oldlines, newlines; - unsigned long leading, trailing; - - offset = parse_fragment_header(line, len, fragment); - if (offset < 0) - return -1; - oldlines = fragment->oldlines; - newlines = fragment->newlines; - leading = 0; - trailing = 0; - - if (patch->is_new < 0) { - patch->is_new = !oldlines; - if (!oldlines) - patch->old_name = NULL; - } - if (patch->is_delete < 0) { - patch->is_delete = !newlines; - if (!newlines) - patch->new_name = NULL; - } - - if (patch->is_new && oldlines) - return error("new file depends on old contents"); - if (patch->is_delete != !newlines) { - if (newlines) - return error("deleted file still has contents"); - fprintf(stderr, "** warning: file %s becomes empty but is not deleted\n", patch->new_name); - } - - /* Parse the thing.. */ - line += len; - size -= len; - linenr++; - added = deleted = 0; - for (offset = len; size > 0; offset += len, size -= len, line += len, linenr++) { - if (!oldlines && !newlines) - break; - len = linelen(line, size); - if (!len || line[len-1] != '\n') - return -1; - switch (*line) { - default: - return -1; - case ' ': - oldlines--; - newlines--; - if (!deleted && !added) - leading++; - trailing++; - break; - case '-': - deleted++; - oldlines--; - trailing = 0; - break; - case '+': - /* - * We know len is at least two, since we have a '+' and - * we checked that the last character was a '\n' above. - * That is, an addition of an empty line would check - * the '+' here. Sneaky... - */ - if ((new_whitespace != nowarn_whitespace) && - isspace(line[len-2])) { - whitespace_error++; - if (squelch_whitespace_errors && - squelch_whitespace_errors < - whitespace_error) - ; - else { - fprintf(stderr, "Adds trailing whitespace.\n%s:%d:%.*s\n", - patch_input_file, - linenr, len-2, line+1); - } - } - added++; - newlines--; - trailing = 0; - break; - - /* We allow "\ No newline at end of file". Depending - * on locale settings when the patch was produced we - * don't know what this line looks like. The only - * thing we do know is that it begins with "\ ". - * Checking for 12 is just for sanity check -- any - * l10n of "\ No newline..." is at least that long. - */ - case '\\': - if (len < 12 || memcmp(line, "\\ ", 2)) - return -1; - break; - } - } - if (oldlines || newlines) - return -1; - fragment->leading = leading; - fragment->trailing = trailing; - - /* If a fragment ends with an incomplete line, we failed to include - * it in the above loop because we hit oldlines == newlines == 0 - * before seeing it. - */ - if (12 < size && !memcmp(line, "\\ ", 2)) - offset += linelen(line, size); - - patch->lines_added += added; - patch->lines_deleted += deleted; - return offset; -} - -static int parse_single_patch(char *line, unsigned long size, struct patch *patch) -{ - unsigned long offset = 0; - struct fragment **fragp = &patch->fragments; - - while (size > 4 && !memcmp(line, "@@ -", 4)) { - struct fragment *fragment; - int len; - - fragment = xcalloc(1, sizeof(*fragment)); - len = parse_fragment(line, size, patch, fragment); - if (len <= 0) - die("corrupt patch at line %d", linenr); - - fragment->patch = line; - fragment->size = len; - - *fragp = fragment; - fragp = &fragment->next; - - offset += len; - line += len; - size -= len; - } - return offset; -} - -static inline int metadata_changes(struct patch *patch) -{ - return patch->is_rename > 0 || - patch->is_copy > 0 || - patch->is_new > 0 || - patch->is_delete || - (patch->old_mode && patch->new_mode && - patch->old_mode != patch->new_mode); -} - -static int parse_binary(char *buffer, unsigned long size, struct patch *patch) -{ - /* We have read "GIT binary patch\n"; what follows is a line - * that says the patch method (currently, either "deflated - * literal" or "deflated delta") and the length of data before - * deflating; a sequence of 'length-byte' followed by base-85 - * encoded data follows. - * - * Each 5-byte sequence of base-85 encodes up to 4 bytes, - * and we would limit the patch line to 66 characters, - * so one line can fit up to 13 groups that would decode - * to 52 bytes max. The length byte 'A'-'Z' corresponds - * to 1-26 bytes, and 'a'-'z' corresponds to 27-52 bytes. - * The end of binary is signalled with an empty line. - */ - int llen, used; - struct fragment *fragment; - char *data = NULL; - - patch->fragments = fragment = xcalloc(1, sizeof(*fragment)); - - /* Grab the type of patch */ - llen = linelen(buffer, size); - used = llen; - linenr++; - - if (!strncmp(buffer, "delta ", 6)) { - patch->is_binary = BINARY_DELTA_DEFLATED; - patch->deflate_origlen = strtoul(buffer + 6, NULL, 10); - } - else if (!strncmp(buffer, "literal ", 8)) { - patch->is_binary = BINARY_LITERAL_DEFLATED; - patch->deflate_origlen = strtoul(buffer + 8, NULL, 10); - } - else - return error("unrecognized binary patch at line %d: %.*s", - linenr-1, llen-1, buffer); - buffer += llen; - while (1) { - int byte_length, max_byte_length, newsize; - llen = linelen(buffer, size); - used += llen; - linenr++; - if (llen == 1) - break; - /* Minimum line is "A00000\n" which is 7-byte long, - * and the line length must be multiple of 5 plus 2. - */ - if ((llen < 7) || (llen-2) % 5) - goto corrupt; - max_byte_length = (llen - 2) / 5 * 4; - byte_length = *buffer; - if ('A' <= byte_length && byte_length <= 'Z') - byte_length = byte_length - 'A' + 1; - else if ('a' <= byte_length && byte_length <= 'z') - byte_length = byte_length - 'a' + 27; - else - goto corrupt; - /* if the input length was not multiple of 4, we would - * have filler at the end but the filler should never - * exceed 3 bytes - */ - if (max_byte_length < byte_length || - byte_length <= max_byte_length - 4) - goto corrupt; - newsize = fragment->size + byte_length; - data = xrealloc(data, newsize); - if (decode_85(data + fragment->size, - buffer + 1, - byte_length)) - goto corrupt; - fragment->size = newsize; - buffer += llen; - size -= llen; - } - fragment->patch = data; - return used; - corrupt: - return error("corrupt binary patch at line %d: %.*s", - linenr-1, llen-1, buffer); -} - -static int parse_chunk(char *buffer, unsigned long size, struct patch *patch) -{ - int hdrsize, patchsize; - int offset = find_header(buffer, size, &hdrsize, patch); - - if (offset < 0) - return offset; - - patchsize = parse_single_patch(buffer + offset + hdrsize, size - offset - hdrsize, patch); - - if (!patchsize) { - static const char *binhdr[] = { - "Binary files ", - "Files ", - NULL, - }; - static const char git_binary[] = "GIT binary patch\n"; - int i; - int hd = hdrsize + offset; - unsigned long llen = linelen(buffer + hd, size - hd); - - if (llen == sizeof(git_binary) - 1 && - !memcmp(git_binary, buffer + hd, llen)) { - int used; - linenr++; - used = parse_binary(buffer + hd + llen, - size - hd - llen, patch); - if (used) - patchsize = used + llen; - else - patchsize = 0; - } - else if (!memcmp(" differ\n", buffer + hd + llen - 8, 8)) { - for (i = 0; binhdr[i]; i++) { - int len = strlen(binhdr[i]); - if (len < size - hd && - !memcmp(binhdr[i], buffer + hd, len)) { - linenr++; - patch->is_binary = 1; - patchsize = llen; - break; - } - } - } - - /* Empty patch cannot be applied if: - * - it is a binary patch and we do not do binary_replace, or - * - text patch without metadata change - */ - if ((apply || check) && - (patch->is_binary - ? !allow_binary_replacement - : !metadata_changes(patch))) - die("patch with only garbage at line %d", linenr); - } - - return offset + hdrsize + patchsize; -} - -static const char pluses[] = "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"; -static const char minuses[]= "----------------------------------------------------------------------"; - -static void show_stats(struct patch *patch) -{ - const char *prefix = ""; - char *name = patch->new_name; - char *qname = NULL; - int len, max, add, del, total; - - if (!name) - name = patch->old_name; - - if (0 < (len = quote_c_style(name, NULL, NULL, 0))) { - qname = xmalloc(len + 1); - quote_c_style(name, qname, NULL, 0); - name = qname; - } - - /* - * "scale" the filename - */ - len = strlen(name); - max = max_len; - if (max > 50) - max = 50; - if (len > max) { - char *slash; - prefix = "..."; - max -= 3; - name += len - max; - slash = strchr(name, '/'); - if (slash) - name = slash; - } - len = max; - - /* - * scale the add/delete - */ - max = max_change; - if (max + len > 70) - max = 70 - len; - - add = patch->lines_added; - del = patch->lines_deleted; - total = add + del; - - if (max_change > 0) { - total = (total * max + max_change / 2) / max_change; - add = (add * max + max_change / 2) / max_change; - del = total - add; - } - if (patch->is_binary) - printf(" %s%-*s | Bin\n", prefix, len, name); - else - printf(" %s%-*s |%5d %.*s%.*s\n", prefix, - len, name, patch->lines_added + patch->lines_deleted, - add, pluses, del, minuses); - if (qname) - free(qname); -} - -static int read_old_data(struct stat *st, const char *path, void *buf, unsigned long size) -{ - int fd; - unsigned long got; - - switch (st->st_mode & S_IFMT) { - case S_IFLNK: - return readlink(path, buf, size); - case S_IFREG: - fd = open(path, O_RDONLY); - if (fd < 0) - return error("unable to open %s", path); - got = 0; - for (;;) { - int ret = xread(fd, buf + got, size - got); - if (ret <= 0) - break; - got += ret; - } - close(fd); - return got; - - default: - return -1; - } -} - -static int find_offset(const char *buf, unsigned long size, const char *fragment, unsigned long fragsize, int line, int *lines) -{ - int i; - unsigned long start, backwards, forwards; - - if (fragsize > size) - return -1; - - start = 0; - if (line > 1) { - unsigned long offset = 0; - i = line-1; - while (offset + fragsize <= size) { - if (buf[offset++] == '\n') { - start = offset; - if (!--i) - break; - } - } - } - - /* Exact line number? */ - if (!memcmp(buf + start, fragment, fragsize)) - return start; - - /* - * There's probably some smart way to do this, but I'll leave - * that to the smart and beautiful people. I'm simple and stupid. - */ - backwards = start; - forwards = start; - for (i = 0; ; i++) { - unsigned long try; - int n; - - /* "backward" */ - if (i & 1) { - if (!backwards) { - if (forwards + fragsize > size) - break; - continue; - } - do { - --backwards; - } while (backwards && buf[backwards-1] != '\n'); - try = backwards; - } else { - while (forwards + fragsize <= size) { - if (buf[forwards++] == '\n') - break; - } - try = forwards; - } - - if (try + fragsize > size) - continue; - if (memcmp(buf + try, fragment, fragsize)) - continue; - n = (i >> 1)+1; - if (i & 1) - n = -n; - *lines = n; - return try; - } - - /* - * We should start searching forward and backward. - */ - return -1; -} - -static void remove_first_line(const char **rbuf, int *rsize) -{ - const char *buf = *rbuf; - int size = *rsize; - unsigned long offset; - offset = 0; - while (offset <= size) { - if (buf[offset++] == '\n') - break; - } - *rsize = size - offset; - *rbuf = buf + offset; -} - -static void remove_last_line(const char **rbuf, int *rsize) -{ - const char *buf = *rbuf; - int size = *rsize; - unsigned long offset; - offset = size - 1; - while (offset > 0) { - if (buf[--offset] == '\n') - break; - } - *rsize = offset + 1; -} - -struct buffer_desc { - char *buffer; - unsigned long size; - unsigned long alloc; -}; - -static int apply_line(char *output, const char *patch, int plen) -{ - /* plen is number of bytes to be copied from patch, - * starting at patch+1 (patch[0] is '+'). Typically - * patch[plen] is '\n'. - */ - int add_nl_to_tail = 0; - if ((new_whitespace == strip_whitespace) && - 1 < plen && isspace(patch[plen-1])) { - if (patch[plen] == '\n') - add_nl_to_tail = 1; - plen--; - while (0 < plen && isspace(patch[plen])) - plen--; - applied_after_stripping++; - } - memcpy(output, patch + 1, plen); - if (add_nl_to_tail) - output[plen++] = '\n'; - return plen; -} - -static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag) -{ - char *buf = desc->buffer; - const char *patch = frag->patch; - int offset, size = frag->size; - char *old = xmalloc(size); - char *new = xmalloc(size); - const char *oldlines, *newlines; - int oldsize = 0, newsize = 0; - unsigned long leading, trailing; - int pos, lines; - - while (size > 0) { - int len = linelen(patch, size); - int plen; - - if (!len) - break; - - /* - * "plen" is how much of the line we should use for - * the actual patch data. Normally we just remove the - * first character on the line, but if the line is - * followed by "\ No newline", then we also remove the - * last one (which is the newline, of course). - */ - plen = len-1; - if (len < size && patch[len] == '\\') - plen--; - switch (*patch) { - case ' ': - case '-': - memcpy(old + oldsize, patch + 1, plen); - oldsize += plen; - if (*patch == '-') - break; - /* Fall-through for ' ' */ - case '+': - if (*patch != '+' || !no_add) - newsize += apply_line(new + newsize, patch, - plen); - break; - case '@': case '\\': - /* Ignore it, we already handled it */ - break; - default: - return -1; - } - patch += len; - size -= len; - } - -#ifdef NO_ACCURATE_DIFF - if (oldsize > 0 && old[oldsize - 1] == '\n' && - newsize > 0 && new[newsize - 1] == '\n') { - oldsize--; - newsize--; - } -#endif - - oldlines = old; - newlines = new; - leading = frag->leading; - trailing = frag->trailing; - lines = 0; - pos = frag->newpos; - for (;;) { - offset = find_offset(buf, desc->size, oldlines, oldsize, pos, &lines); - if (offset >= 0) { - int diff = newsize - oldsize; - unsigned long size = desc->size + diff; - unsigned long alloc = desc->alloc; - - /* Warn if it was necessary to reduce the number - * of context lines. - */ - if ((leading != frag->leading) || (trailing != frag->trailing)) - fprintf(stderr, "Context reduced to (%ld/%ld) to apply fragment at %d\n", - leading, trailing, pos + lines); - - if (size > alloc) { - alloc = size + 8192; - desc->alloc = alloc; - buf = xrealloc(buf, alloc); - desc->buffer = buf; - } - desc->size = size; - memmove(buf + offset + newsize, buf + offset + oldsize, size - offset - newsize); - memcpy(buf + offset, newlines, newsize); - offset = 0; - - break; - } - - /* Am I at my context limits? */ - if ((leading <= p_context) && (trailing <= p_context)) - break; - /* Reduce the number of context lines - * Reduce both leading and trailing if they are equal - * otherwise just reduce the larger context. - */ - if (leading >= trailing) { - remove_first_line(&oldlines, &oldsize); - remove_first_line(&newlines, &newsize); - pos--; - leading--; - } - if (trailing > leading) { - remove_last_line(&oldlines, &oldsize); - remove_last_line(&newlines, &newsize); - trailing--; - } - } - - free(old); - free(new); - return offset; -} - -static char *inflate_it(const void *data, unsigned long size, - unsigned long inflated_size) -{ - z_stream stream; - void *out; - int st; - - memset(&stream, 0, sizeof(stream)); - - stream.next_in = (unsigned char *)data; - stream.avail_in = size; - stream.next_out = out = xmalloc(inflated_size); - stream.avail_out = inflated_size; - inflateInit(&stream); - st = inflate(&stream, Z_FINISH); - if ((st != Z_STREAM_END) || stream.total_out != inflated_size) { - free(out); - return NULL; - } - return out; -} - -static int apply_binary_fragment(struct buffer_desc *desc, struct patch *patch) -{ - unsigned long dst_size; - struct fragment *fragment = patch->fragments; - void *data; - void *result; - - data = inflate_it(fragment->patch, fragment->size, - patch->deflate_origlen); - if (!data) - return error("corrupt patch data"); - switch (patch->is_binary) { - case BINARY_DELTA_DEFLATED: - result = patch_delta(desc->buffer, desc->size, - data, - patch->deflate_origlen, - &dst_size); - free(desc->buffer); - desc->buffer = result; - free(data); - break; - case BINARY_LITERAL_DEFLATED: - free(desc->buffer); - desc->buffer = data; - dst_size = patch->deflate_origlen; - break; - } - if (!desc->buffer) - return -1; - desc->size = desc->alloc = dst_size; - return 0; -} - -static int apply_binary(struct buffer_desc *desc, struct patch *patch) -{ - const char *name = patch->old_name ? patch->old_name : patch->new_name; - unsigned char sha1[20]; - unsigned char hdr[50]; - int hdrlen; - - if (!allow_binary_replacement) - return error("cannot apply binary patch to '%s' " - "without --allow-binary-replacement", - name); - - /* For safety, we require patch index line to contain - * full 40-byte textual SHA1 for old and new, at least for now. - */ - if (strlen(patch->old_sha1_prefix) != 40 || - strlen(patch->new_sha1_prefix) != 40 || - get_sha1_hex(patch->old_sha1_prefix, sha1) || - get_sha1_hex(patch->new_sha1_prefix, sha1)) - return error("cannot apply binary patch to '%s' " - "without full index line", name); - - if (patch->old_name) { - /* See if the old one matches what the patch - * applies to. - */ - write_sha1_file_prepare(desc->buffer, desc->size, - blob_type, sha1, hdr, &hdrlen); - if (strcmp(sha1_to_hex(sha1), patch->old_sha1_prefix)) - return error("the patch applies to '%s' (%s), " - "which does not match the " - "current contents.", - name, sha1_to_hex(sha1)); - } - else { - /* Otherwise, the old one must be empty. */ - if (desc->size) - return error("the patch applies to an empty " - "'%s' but it is not empty", name); - } - - get_sha1_hex(patch->new_sha1_prefix, sha1); - if (!memcmp(sha1, null_sha1, 20)) { - free(desc->buffer); - desc->alloc = desc->size = 0; - desc->buffer = NULL; - return 0; /* deletion patch */ - } - - if (has_sha1_file(sha1)) { - /* We already have the postimage */ - char type[10]; - unsigned long size; - - free(desc->buffer); - desc->buffer = read_sha1_file(sha1, type, &size); - if (!desc->buffer) - return error("the necessary postimage %s for " - "'%s' cannot be read", - patch->new_sha1_prefix, name); - desc->alloc = desc->size = size; - } - else { - /* We have verified desc matches the preimage; - * apply the patch data to it, which is stored - * in the patch->fragments->{patch,size}. - */ - if (apply_binary_fragment(desc, patch)) - return error("binary patch does not apply to '%s'", - name); - - /* verify that the result matches */ - write_sha1_file_prepare(desc->buffer, desc->size, blob_type, - sha1, hdr, &hdrlen); - if (strcmp(sha1_to_hex(sha1), patch->new_sha1_prefix)) - return error("binary patch to '%s' creates incorrect result", name); - } - - return 0; -} - -static int apply_fragments(struct buffer_desc *desc, struct patch *patch) -{ - struct fragment *frag = patch->fragments; - const char *name = patch->old_name ? patch->old_name : patch->new_name; - - if (patch->is_binary) - return apply_binary(desc, patch); - - while (frag) { - if (apply_one_fragment(desc, frag) < 0) - return error("patch failed: %s:%ld", - name, frag->oldpos); - frag = frag->next; - } - return 0; -} - -static int apply_data(struct patch *patch, struct stat *st, struct cache_entry *ce) -{ - char *buf; - unsigned long size, alloc; - struct buffer_desc desc; - - size = 0; - alloc = 0; - buf = NULL; - if (cached) { - if (ce) { - char type[20]; - buf = read_sha1_file(ce->sha1, type, &size); - if (!buf) - return error("read of %s failed", - patch->old_name); - alloc = size; - } - } - else if (patch->old_name) { - size = st->st_size; - alloc = size + 8192; - buf = xmalloc(alloc); - if (read_old_data(st, patch->old_name, buf, alloc) != size) - return error("read of %s failed", patch->old_name); - } - - desc.size = size; - desc.alloc = alloc; - desc.buffer = buf; - if (apply_fragments(&desc, patch) < 0) - return -1; - patch->result = desc.buffer; - patch->resultsize = desc.size; - - if (patch->is_delete && patch->resultsize) - return error("removal patch leaves file contents"); - - return 0; -} - -static int check_patch(struct patch *patch) -{ - struct stat st; - const char *old_name = patch->old_name; - const char *new_name = patch->new_name; - const char *name = old_name ? old_name : new_name; - struct cache_entry *ce = NULL; - - if (old_name) { - int changed = 0; - int stat_ret = 0; - unsigned st_mode = 0; - - if (!cached) - stat_ret = lstat(old_name, &st); - if (check_index) { - int pos = cache_name_pos(old_name, strlen(old_name)); - if (pos < 0) - return error("%s: does not exist in index", - old_name); - ce = active_cache[pos]; - if (stat_ret < 0) { - struct checkout costate; - if (errno != ENOENT) - return error("%s: %s", old_name, - strerror(errno)); - /* checkout */ - costate.base_dir = ""; - costate.base_dir_len = 0; - costate.force = 0; - costate.quiet = 0; - costate.not_new = 0; - costate.refresh_cache = 1; - if (checkout_entry(ce, - &costate, - NULL) || - lstat(old_name, &st)) - return -1; - } - if (!cached) - changed = ce_match_stat(ce, &st, 1); - if (changed) - return error("%s: does not match index", - old_name); - if (cached) - st_mode = ntohl(ce->ce_mode); - } - else if (stat_ret < 0) - return error("%s: %s", old_name, strerror(errno)); - - if (!cached) - st_mode = ntohl(create_ce_mode(st.st_mode)); - - if (patch->is_new < 0) - patch->is_new = 0; - if (!patch->old_mode) - patch->old_mode = st_mode; - if ((st_mode ^ patch->old_mode) & S_IFMT) - return error("%s: wrong type", old_name); - if (st_mode != patch->old_mode) - fprintf(stderr, "warning: %s has type %o, expected %o\n", - old_name, st_mode, patch->old_mode); - } - - if (new_name && (patch->is_new | patch->is_rename | patch->is_copy)) { - if (check_index && cache_name_pos(new_name, strlen(new_name)) >= 0) - return error("%s: already exists in index", new_name); - if (!cached) { - if (!lstat(new_name, &st)) - return error("%s: already exists in working directory", new_name); - if (errno != ENOENT) - return error("%s: %s", new_name, strerror(errno)); - } - if (!patch->new_mode) { - if (patch->is_new) - patch->new_mode = S_IFREG | 0644; - else - patch->new_mode = patch->old_mode; - } - } - - if (new_name && old_name) { - int same = !strcmp(old_name, new_name); - if (!patch->new_mode) - patch->new_mode = patch->old_mode; - if ((patch->old_mode ^ patch->new_mode) & S_IFMT) - return error("new mode (%o) of %s does not match old mode (%o)%s%s", - patch->new_mode, new_name, patch->old_mode, - same ? "" : " of ", same ? "" : old_name); - } - - if (apply_data(patch, &st, ce) < 0) - return error("%s: patch does not apply", name); - return 0; -} - -static int check_patch_list(struct patch *patch) -{ - int error = 0; - - for (;patch ; patch = patch->next) - error |= check_patch(patch); - return error; -} - -static inline int is_null_sha1(const unsigned char *sha1) -{ - return !memcmp(sha1, null_sha1, 20); -} - -static void show_index_list(struct patch *list) -{ - struct patch *patch; - - /* Once we start supporting the reverse patch, it may be - * worth showing the new sha1 prefix, but until then... - */ - for (patch = list; patch; patch = patch->next) { - const unsigned char *sha1_ptr; - unsigned char sha1[20]; - const char *name; - - name = patch->old_name ? patch->old_name : patch->new_name; - if (patch->is_new) - sha1_ptr = null_sha1; - else if (get_sha1(patch->old_sha1_prefix, sha1)) - die("sha1 information is lacking or useless (%s).", - name); - else - sha1_ptr = sha1; - - printf("%06o %s ",patch->old_mode, sha1_to_hex(sha1_ptr)); - if (line_termination && quote_c_style(name, NULL, NULL, 0)) - quote_c_style(name, NULL, stdout, 0); - else - fputs(name, stdout); - putchar(line_termination); - } -} - -static void stat_patch_list(struct patch *patch) -{ - int files, adds, dels; - - for (files = adds = dels = 0 ; patch ; patch = patch->next) { - files++; - adds += patch->lines_added; - dels += patch->lines_deleted; - show_stats(patch); - } - - printf(" %d files changed, %d insertions(+), %d deletions(-)\n", files, adds, dels); -} - -static void numstat_patch_list(struct patch *patch) -{ - for ( ; patch; patch = patch->next) { - const char *name; - name = patch->new_name ? patch->new_name : patch->old_name; - printf("%d\t%d\t", patch->lines_added, patch->lines_deleted); - if (line_termination && quote_c_style(name, NULL, NULL, 0)) - quote_c_style(name, NULL, stdout, 0); - else - fputs(name, stdout); - putchar('\n'); - } -} - -static void show_file_mode_name(const char *newdelete, unsigned int mode, const char *name) -{ - if (mode) - printf(" %s mode %06o %s\n", newdelete, mode, name); - else - printf(" %s %s\n", newdelete, name); -} - -static void show_mode_change(struct patch *p, int show_name) -{ - if (p->old_mode && p->new_mode && p->old_mode != p->new_mode) { - if (show_name) - printf(" mode change %06o => %06o %s\n", - p->old_mode, p->new_mode, p->new_name); - else - printf(" mode change %06o => %06o\n", - p->old_mode, p->new_mode); - } -} - -static void show_rename_copy(struct patch *p) -{ - const char *renamecopy = p->is_rename ? "rename" : "copy"; - const char *old, *new; - - /* Find common prefix */ - old = p->old_name; - new = p->new_name; - while (1) { - const char *slash_old, *slash_new; - slash_old = strchr(old, '/'); - slash_new = strchr(new, '/'); - if (!slash_old || - !slash_new || - slash_old - old != slash_new - new || - memcmp(old, new, slash_new - new)) - break; - old = slash_old + 1; - new = slash_new + 1; - } - /* p->old_name thru old is the common prefix, and old and new - * through the end of names are renames - */ - if (old != p->old_name) - printf(" %s %.*s{%s => %s} (%d%%)\n", renamecopy, - (int)(old - p->old_name), p->old_name, - old, new, p->score); - else - printf(" %s %s => %s (%d%%)\n", renamecopy, - p->old_name, p->new_name, p->score); - show_mode_change(p, 0); -} - -static void summary_patch_list(struct patch *patch) -{ - struct patch *p; - - for (p = patch; p; p = p->next) { - if (p->is_new) - show_file_mode_name("create", p->new_mode, p->new_name); - else if (p->is_delete) - show_file_mode_name("delete", p->old_mode, p->old_name); - else { - if (p->is_rename || p->is_copy) - show_rename_copy(p); - else { - if (p->score) { - printf(" rewrite %s (%d%%)\n", - p->new_name, p->score); - show_mode_change(p, 0); - } - else - show_mode_change(p, 1); - } - } - } -} - -static void patch_stats(struct patch *patch) -{ - int lines = patch->lines_added + patch->lines_deleted; - - if (lines > max_change) - max_change = lines; - if (patch->old_name) { - int len = quote_c_style(patch->old_name, NULL, NULL, 0); - if (!len) - len = strlen(patch->old_name); - if (len > max_len) - max_len = len; - } - if (patch->new_name) { - int len = quote_c_style(patch->new_name, NULL, NULL, 0); - if (!len) - len = strlen(patch->new_name); - if (len > max_len) - max_len = len; - } -} - -static void remove_file(struct patch *patch) -{ - if (write_index) { - if (remove_file_from_cache(patch->old_name) < 0) - die("unable to remove %s from index", patch->old_name); - } - 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); -} - -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 main(int argc, char **argv) -{ - int i; - int read_stdin = 1; - const char *whitespace_option = NULL; - - for (i = 1; i < argc; i++) { - const char *arg = argv[i]; - char *end; - int fd; - - if (!strcmp(arg, "-")) { - apply_patch(0, ""); - read_stdin = 0; - continue; - } - if (!strncmp(arg, "--exclude=", 10)) { - struct excludes *x = xmalloc(sizeof(*x)); - x->path = arg + 10; - x->next = excludes; - excludes = x; - continue; - } - if (!strncmp(arg, "-p", 2)) { - p_value = atoi(arg + 2); - continue; - } - if (!strcmp(arg, "--no-add")) { - no_add = 1; - continue; - } - if (!strcmp(arg, "--stat")) { - apply = 0; - diffstat = 1; - continue; - } - if (!strcmp(arg, "--allow-binary-replacement") || - !strcmp(arg, "--binary")) { - allow_binary_replacement = 1; - continue; - } - if (!strcmp(arg, "--numstat")) { - apply = 0; - numstat = 1; - continue; - } - if (!strcmp(arg, "--summary")) { - apply = 0; - summary = 1; - continue; - } - if (!strcmp(arg, "--check")) { - apply = 0; - check = 1; - continue; - } - if (!strcmp(arg, "--index")) { - check_index = 1; - continue; - } - if (!strcmp(arg, "--cached")) { - check_index = 1; - cached = 1; - continue; - } - if (!strcmp(arg, "--apply")) { - apply = 1; - continue; - } - if (!strcmp(arg, "--index-info")) { - apply = 0; - show_index_info = 1; - continue; - } - if (!strcmp(arg, "-z")) { - line_termination = 0; - continue; - } - if (!strncmp(arg, "-C", 2)) { - p_context = strtoul(arg + 2, &end, 0); - if (*end != '\0') - die("unrecognized context count '%s'", arg + 2); - continue; - } - if (!strncmp(arg, "--whitespace=", 13)) { - whitespace_option = arg + 13; - parse_whitespace_option(arg + 13); - continue; - } - - if (check_index && prefix_length < 0) { - prefix = setup_git_directory(); - prefix_length = prefix ? strlen(prefix) : 0; - git_config(git_apply_config); - if (!whitespace_option && apply_default_whitespace) - parse_whitespace_option(apply_default_whitespace); - } - if (0 < prefix_length) - arg = prefix_filename(prefix, prefix_length, arg); - - fd = open(arg, O_RDONLY); - if (fd < 0) - usage(apply_usage); - read_stdin = 0; - set_default_whitespace_mode(whitespace_option); - apply_patch(fd, arg); - close(fd); - } - set_default_whitespace_mode(whitespace_option); - if (read_stdin) - apply_patch(0, ""); - if (whitespace_error) { - if (squelch_whitespace_errors && - squelch_whitespace_errors < whitespace_error) { - int squelched = - whitespace_error - squelch_whitespace_errors; - fprintf(stderr, "warning: squelched %d whitespace error%s\n", - squelched, - squelched == 1 ? "" : "s"); - } - if (new_whitespace == error_on_whitespace) - die("%d line%s add%s trailing whitespaces.", - whitespace_error, - whitespace_error == 1 ? "" : "s", - whitespace_error == 1 ? "s" : ""); - if (applied_after_stripping) - fprintf(stderr, "warning: %d line%s applied after" - " stripping trailing whitespaces.\n", - applied_after_stripping, - applied_after_stripping == 1 ? "" : "s"); - else if (whitespace_error) - fprintf(stderr, "warning: %d line%s add%s trailing" - " whitespaces.\n", - whitespace_error, - whitespace_error == 1 ? "" : "s", - whitespace_error == 1 ? "s" : ""); - } - - if (write_index) { - if (write_cache(newfd, active_cache, active_nr) || - commit_index_file(&cache_file)) - die("Unable to write new cachefile"); - } - - return 0; -} diff --git a/builtin-apply.c b/builtin-apply.c new file mode 100644 index 0000000000..4056b9d67b --- /dev/null +++ b/builtin-apply.c @@ -0,0 +1,2300 @@ +/* + * apply.c + * + * Copyright (C) Linus Torvalds, 2005 + * + * This applies patches on top of some (arbitrary) version of the SCM. + * + */ +#include +#include "cache.h" +#include "quote.h" +#include "blob.h" +#include "delta.h" +#include "builtin.h" + +// --check turns on checking that the working tree matches the +// files that are being modified, but doesn't apply the patch +// --stat does just a diffstat, and doesn't actually apply +// --numstat does numeric diffstat, and doesn't actually apply +// --index-info shows the old and new index info for paths if available. +// --index updates the cache as well. +// --cached updates only the cache without ever touching the working tree. +// +static const char *prefix; +static int prefix_length = -1; +static int newfd = -1; + +static int p_value = 1; +static int allow_binary_replacement = 0; +static int check_index = 0; +static int write_index = 0; +static int cached = 0; +static int diffstat = 0; +static int numstat = 0; +static int summary = 0; +static int check = 0; +static int apply = 1; +static int no_add = 0; +static int show_index_info = 0; +static int line_termination = '\n'; +static unsigned long p_context = -1; +static const char apply_usage[] = +"git-apply [--stat] [--numstat] [--summary] [--check] [--index] [--cached] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [-z] [-pNUM] [-CNUM] [--whitespace=] ..."; + +static enum whitespace_eol { + nowarn_whitespace, + warn_on_whitespace, + error_on_whitespace, + strip_whitespace, +} new_whitespace = warn_on_whitespace; +static int whitespace_error = 0; +static int squelch_whitespace_errors = 5; +static int applied_after_stripping = 0; +static const char *patch_input_file = NULL; + +static void parse_whitespace_option(const char *option) +{ + if (!option) { + new_whitespace = warn_on_whitespace; + return; + } + if (!strcmp(option, "warn")) { + new_whitespace = warn_on_whitespace; + return; + } + if (!strcmp(option, "nowarn")) { + new_whitespace = nowarn_whitespace; + return; + } + if (!strcmp(option, "error")) { + new_whitespace = error_on_whitespace; + return; + } + if (!strcmp(option, "error-all")) { + new_whitespace = error_on_whitespace; + squelch_whitespace_errors = 0; + return; + } + if (!strcmp(option, "strip")) { + new_whitespace = strip_whitespace; + return; + } + die("unrecognized whitespace option '%s'", option); +} + +static void set_default_whitespace_mode(const char *whitespace_option) +{ + if (!whitespace_option && !apply_default_whitespace) { + new_whitespace = (apply + ? warn_on_whitespace + : nowarn_whitespace); + } +} + +/* + * For "diff-stat" like behaviour, we keep track of the biggest change + * we've seen, and the longest filename. That allows us to do simple + * scaling. + */ +static int max_change, max_len; + +/* + * Various "current state", notably line numbers and what + * file (and how) we're patching right now.. The "is_xxxx" + * things are flags, where -1 means "don't know yet". + */ +static int linenr = 1; + +struct fragment { + unsigned long leading, trailing; + unsigned long oldpos, oldlines; + unsigned long newpos, newlines; + const char *patch; + int size; + struct fragment *next; +}; + +struct patch { + char *new_name, *old_name, *def_name; + unsigned int old_mode, new_mode; + int is_rename, is_copy, is_new, is_delete, is_binary; +#define BINARY_DELTA_DEFLATED 1 +#define BINARY_LITERAL_DEFLATED 2 + unsigned long deflate_origlen; + int lines_added, lines_deleted; + int score; + struct fragment *fragments; + char *result; + unsigned long resultsize; + char old_sha1_prefix[41]; + char new_sha1_prefix[41]; + struct patch *next; +}; + +#define CHUNKSIZE (8192) +#define SLOP (16) + +static void *read_patch_file(int fd, unsigned long *sizep) +{ + unsigned long size = 0, alloc = CHUNKSIZE; + void *buffer = xmalloc(alloc); + + for (;;) { + int nr = alloc - size; + if (nr < 1024) { + alloc += CHUNKSIZE; + buffer = xrealloc(buffer, alloc); + nr = alloc - size; + } + nr = xread(fd, buffer + size, nr); + if (!nr) + break; + if (nr < 0) + die("git-apply: read returned %s", strerror(errno)); + size += nr; + } + *sizep = size; + + /* + * Make sure that we have some slop in the buffer + * so that we can do speculative "memcmp" etc, and + * see to it that it is NUL-filled. + */ + if (alloc < size + SLOP) + buffer = xrealloc(buffer, size + SLOP); + memset(buffer + size, 0, SLOP); + return buffer; +} + +static unsigned long linelen(const char *buffer, unsigned long size) +{ + unsigned long len = 0; + while (size--) { + len++; + if (*buffer++ == '\n') + break; + } + return len; +} + +static int is_dev_null(const char *str) +{ + return !memcmp("/dev/null", str, 9) && isspace(str[9]); +} + +#define TERM_SPACE 1 +#define TERM_TAB 2 + +static int name_terminate(const char *name, int namelen, int c, int terminate) +{ + if (c == ' ' && !(terminate & TERM_SPACE)) + return 0; + if (c == '\t' && !(terminate & TERM_TAB)) + return 0; + + return 1; +} + +static char * find_name(const char *line, char *def, int p_value, int terminate) +{ + int len; + const char *start = line; + char *name; + + if (*line == '"') { + /* Proposed "new-style" GNU patch/diff format; see + * http://marc.theaimsgroup.com/?l=git&m=112927316408690&w=2 + */ + name = unquote_c_style(line, NULL); + if (name) { + char *cp = name; + while (p_value) { + cp = strchr(name, '/'); + if (!cp) + break; + cp++; + p_value--; + } + if (cp) { + /* name can later be freed, so we need + * to memmove, not just return cp + */ + memmove(name, cp, strlen(cp) + 1); + free(def); + return name; + } + else { + free(name); + name = NULL; + } + } + } + + for (;;) { + char c = *line; + + if (isspace(c)) { + if (c == '\n') + break; + if (name_terminate(start, line-start, c, terminate)) + break; + } + line++; + if (c == '/' && !--p_value) + start = line; + } + if (!start) + return def; + len = line - start; + if (!len) + return def; + + /* + * Generally we prefer the shorter name, especially + * if the other one is just a variation of that with + * something else tacked on to the end (ie "file.orig" + * or "file~"). + */ + if (def) { + int deflen = strlen(def); + if (deflen < len && !strncmp(start, def, deflen)) + return def; + } + + name = xmalloc(len + 1); + memcpy(name, start, len); + name[len] = 0; + free(def); + return name; +} + +/* + * Get the name etc info from the --/+++ lines of a traditional patch header + * + * NOTE! This hardcodes "-p1" behaviour in filename detection. + * + * FIXME! The end-of-filename heuristics are kind of screwy. For existing + * files, we can happily check the index for a match, but for creating a + * new file we should try to match whatever "patch" does. I have no idea. + */ +static void parse_traditional_patch(const char *first, const char *second, struct patch *patch) +{ + char *name; + + first += 4; // skip "--- " + second += 4; // skip "+++ " + if (is_dev_null(first)) { + patch->is_new = 1; + patch->is_delete = 0; + name = find_name(second, NULL, p_value, TERM_SPACE | TERM_TAB); + patch->new_name = name; + } else if (is_dev_null(second)) { + patch->is_new = 0; + patch->is_delete = 1; + name = find_name(first, NULL, p_value, TERM_SPACE | TERM_TAB); + patch->old_name = name; + } else { + name = find_name(first, NULL, p_value, TERM_SPACE | TERM_TAB); + name = find_name(second, name, p_value, TERM_SPACE | TERM_TAB); + patch->old_name = patch->new_name = name; + } + if (!name) + die("unable to find filename in patch at line %d", linenr); +} + +static int gitdiff_hdrend(const char *line, struct patch *patch) +{ + return -1; +} + +/* + * We're anal about diff header consistency, to make + * sure that we don't end up having strange ambiguous + * patches floating around. + * + * As a result, gitdiff_{old|new}name() will check + * their names against any previous information, just + * to make sure.. + */ +static char *gitdiff_verify_name(const char *line, int isnull, char *orig_name, const char *oldnew) +{ + if (!orig_name && !isnull) + return find_name(line, NULL, 1, 0); + + if (orig_name) { + int len; + const char *name; + char *another; + name = orig_name; + len = strlen(name); + if (isnull) + die("git-apply: bad git-diff - expected /dev/null, got %s on line %d", name, linenr); + another = find_name(line, NULL, 1, 0); + if (!another || memcmp(another, name, len)) + die("git-apply: bad git-diff - inconsistent %s filename on line %d", oldnew, linenr); + free(another); + return orig_name; + } + else { + /* expect "/dev/null" */ + if (memcmp("/dev/null", line, 9) || line[9] != '\n') + die("git-apply: bad git-diff - expected /dev/null on line %d", linenr); + return NULL; + } +} + +static int gitdiff_oldname(const char *line, struct patch *patch) +{ + patch->old_name = gitdiff_verify_name(line, patch->is_new, patch->old_name, "old"); + return 0; +} + +static int gitdiff_newname(const char *line, struct patch *patch) +{ + patch->new_name = gitdiff_verify_name(line, patch->is_delete, patch->new_name, "new"); + return 0; +} + +static int gitdiff_oldmode(const char *line, struct patch *patch) +{ + patch->old_mode = strtoul(line, NULL, 8); + return 0; +} + +static int gitdiff_newmode(const char *line, struct patch *patch) +{ + patch->new_mode = strtoul(line, NULL, 8); + return 0; +} + +static int gitdiff_delete(const char *line, struct patch *patch) +{ + patch->is_delete = 1; + patch->old_name = patch->def_name; + return gitdiff_oldmode(line, patch); +} + +static int gitdiff_newfile(const char *line, struct patch *patch) +{ + patch->is_new = 1; + patch->new_name = patch->def_name; + return gitdiff_newmode(line, patch); +} + +static int gitdiff_copysrc(const char *line, struct patch *patch) +{ + patch->is_copy = 1; + patch->old_name = find_name(line, NULL, 0, 0); + return 0; +} + +static int gitdiff_copydst(const char *line, struct patch *patch) +{ + patch->is_copy = 1; + patch->new_name = find_name(line, NULL, 0, 0); + return 0; +} + +static int gitdiff_renamesrc(const char *line, struct patch *patch) +{ + patch->is_rename = 1; + patch->old_name = find_name(line, NULL, 0, 0); + return 0; +} + +static int gitdiff_renamedst(const char *line, struct patch *patch) +{ + patch->is_rename = 1; + patch->new_name = find_name(line, NULL, 0, 0); + return 0; +} + +static int gitdiff_similarity(const char *line, struct patch *patch) +{ + if ((patch->score = strtoul(line, NULL, 10)) == ULONG_MAX) + patch->score = 0; + return 0; +} + +static int gitdiff_dissimilarity(const char *line, struct patch *patch) +{ + if ((patch->score = strtoul(line, NULL, 10)) == ULONG_MAX) + patch->score = 0; + return 0; +} + +static int gitdiff_index(const char *line, struct patch *patch) +{ + /* index line is N hexadecimal, "..", N hexadecimal, + * and optional space with octal mode. + */ + const char *ptr, *eol; + int len; + + ptr = strchr(line, '.'); + if (!ptr || ptr[1] != '.' || 40 < ptr - line) + return 0; + len = ptr - line; + memcpy(patch->old_sha1_prefix, line, len); + patch->old_sha1_prefix[len] = 0; + + line = ptr + 2; + ptr = strchr(line, ' '); + eol = strchr(line, '\n'); + + if (!ptr || eol < ptr) + ptr = eol; + len = ptr - line; + + if (40 < len) + return 0; + memcpy(patch->new_sha1_prefix, line, len); + patch->new_sha1_prefix[len] = 0; + if (*ptr == ' ') + patch->new_mode = patch->old_mode = strtoul(ptr+1, NULL, 8); + return 0; +} + +/* + * This is normal for a diff that doesn't change anything: we'll fall through + * into the next diff. Tell the parser to break out. + */ +static int gitdiff_unrecognized(const char *line, struct patch *patch) +{ + return -1; +} + +static const char *stop_at_slash(const char *line, int llen) +{ + int i; + + for (i = 0; i < llen; i++) { + int ch = line[i]; + if (ch == '/') + return line + i; + } + return NULL; +} + +/* This is to extract the same name that appears on "diff --git" + * line. We do not find and return anything if it is a rename + * patch, and it is OK because we will find the name elsewhere. + * We need to reliably find name only when it is mode-change only, + * creation or deletion of an empty file. In any of these cases, + * both sides are the same name under a/ and b/ respectively. + */ +static char *git_header_name(char *line, int llen) +{ + int len; + const char *name; + const char *second = NULL; + + line += strlen("diff --git "); + llen -= strlen("diff --git "); + + if (*line == '"') { + const char *cp; + char *first = unquote_c_style(line, &second); + if (!first) + return NULL; + + /* advance to the first slash */ + cp = stop_at_slash(first, strlen(first)); + if (!cp || cp == first) { + /* we do not accept absolute paths */ + free_first_and_fail: + free(first); + return NULL; + } + len = strlen(cp+1); + memmove(first, cp+1, len+1); /* including NUL */ + + /* second points at one past closing dq of name. + * find the second name. + */ + while ((second < line + llen) && isspace(*second)) + second++; + + if (line + llen <= second) + goto free_first_and_fail; + if (*second == '"') { + char *sp = unquote_c_style(second, NULL); + if (!sp) + goto free_first_and_fail; + cp = stop_at_slash(sp, strlen(sp)); + if (!cp || cp == sp) { + free_both_and_fail: + free(sp); + goto free_first_and_fail; + } + /* They must match, otherwise ignore */ + if (strcmp(cp+1, first)) + goto free_both_and_fail; + free(sp); + return first; + } + + /* unquoted second */ + cp = stop_at_slash(second, line + llen - second); + if (!cp || cp == second) + goto free_first_and_fail; + cp++; + if (line + llen - cp != len + 1 || + memcmp(first, cp, len)) + goto free_first_and_fail; + return first; + } + + /* unquoted first name */ + name = stop_at_slash(line, llen); + if (!name || name == line) + return NULL; + + name++; + + /* since the first name is unquoted, a dq if exists must be + * the beginning of the second name. + */ + for (second = name; second < line + llen; second++) { + if (*second == '"') { + const char *cp = second; + const char *np; + char *sp = unquote_c_style(second, NULL); + + if (!sp) + return NULL; + np = stop_at_slash(sp, strlen(sp)); + if (!np || np == sp) { + free_second_and_fail: + free(sp); + return NULL; + } + np++; + len = strlen(np); + if (len < cp - name && + !strncmp(np, name, len) && + isspace(name[len])) { + /* Good */ + memmove(sp, np, len + 1); + return sp; + } + goto free_second_and_fail; + } + } + + /* + * Accept a name only if it shows up twice, exactly the same + * form. + */ + for (len = 0 ; ; len++) { + char c = name[len]; + + switch (c) { + default: + continue; + case '\n': + return NULL; + case '\t': case ' ': + second = name+len; + for (;;) { + char c = *second++; + if (c == '\n') + return NULL; + if (c == '/') + break; + } + if (second[len] == '\n' && !memcmp(name, second, len)) { + char *ret = xmalloc(len + 1); + memcpy(ret, name, len); + ret[len] = 0; + return ret; + } + } + } + return NULL; +} + +/* Verify that we recognize the lines following a git header */ +static int parse_git_header(char *line, int len, unsigned int size, struct patch *patch) +{ + unsigned long offset; + + /* A git diff has explicit new/delete information, so we don't guess */ + patch->is_new = 0; + patch->is_delete = 0; + + /* + * Some things may not have the old name in the + * rest of the headers anywhere (pure mode changes, + * or removing or adding empty files), so we get + * the default name from the header. + */ + patch->def_name = git_header_name(line, len); + + line += len; + size -= len; + linenr++; + for (offset = len ; size > 0 ; offset += len, size -= len, line += len, linenr++) { + static const struct opentry { + const char *str; + int (*fn)(const char *, struct patch *); + } optable[] = { + { "@@ -", gitdiff_hdrend }, + { "--- ", gitdiff_oldname }, + { "+++ ", gitdiff_newname }, + { "old mode ", gitdiff_oldmode }, + { "new mode ", gitdiff_newmode }, + { "deleted file mode ", gitdiff_delete }, + { "new file mode ", gitdiff_newfile }, + { "copy from ", gitdiff_copysrc }, + { "copy to ", gitdiff_copydst }, + { "rename old ", gitdiff_renamesrc }, + { "rename new ", gitdiff_renamedst }, + { "rename from ", gitdiff_renamesrc }, + { "rename to ", gitdiff_renamedst }, + { "similarity index ", gitdiff_similarity }, + { "dissimilarity index ", gitdiff_dissimilarity }, + { "index ", gitdiff_index }, + { "", gitdiff_unrecognized }, + }; + int i; + + len = linelen(line, size); + if (!len || line[len-1] != '\n') + break; + for (i = 0; i < ARRAY_SIZE(optable); i++) { + const struct opentry *p = optable + i; + int oplen = strlen(p->str); + if (len < oplen || memcmp(p->str, line, oplen)) + continue; + if (p->fn(line + oplen, patch) < 0) + return offset; + break; + } + } + + return offset; +} + +static int parse_num(const char *line, unsigned long *p) +{ + char *ptr; + + if (!isdigit(*line)) + return 0; + *p = strtoul(line, &ptr, 10); + return ptr - line; +} + +static int parse_range(const char *line, int len, int offset, const char *expect, + unsigned long *p1, unsigned long *p2) +{ + int digits, ex; + + if (offset < 0 || offset >= len) + return -1; + line += offset; + len -= offset; + + digits = parse_num(line, p1); + if (!digits) + return -1; + + offset += digits; + line += digits; + len -= digits; + + *p2 = 1; + if (*line == ',') { + digits = parse_num(line+1, p2); + if (!digits) + return -1; + + offset += digits+1; + line += digits+1; + len -= digits+1; + } + + ex = strlen(expect); + if (ex > len) + return -1; + if (memcmp(line, expect, ex)) + return -1; + + return offset + ex; +} + +/* + * Parse a unified diff fragment header of the + * form "@@ -a,b +c,d @@" + */ +static int parse_fragment_header(char *line, int len, struct fragment *fragment) +{ + int offset; + + if (!len || line[len-1] != '\n') + return -1; + + /* Figure out the number of lines in a fragment */ + offset = parse_range(line, len, 4, " +", &fragment->oldpos, &fragment->oldlines); + offset = parse_range(line, len, offset, " @@", &fragment->newpos, &fragment->newlines); + + return offset; +} + +static int find_header(char *line, unsigned long size, int *hdrsize, struct patch *patch) +{ + unsigned long offset, len; + + patch->is_rename = patch->is_copy = 0; + patch->is_new = patch->is_delete = -1; + patch->old_mode = patch->new_mode = 0; + patch->old_name = patch->new_name = NULL; + for (offset = 0; size > 0; offset += len, size -= len, line += len, linenr++) { + unsigned long nextlen; + + len = linelen(line, size); + if (!len) + break; + + /* Testing this early allows us to take a few shortcuts.. */ + if (len < 6) + continue; + + /* + * Make sure we don't find any unconnected patch fragmants. + * That's a sign that we didn't find a header, and that a + * patch has become corrupted/broken up. + */ + if (!memcmp("@@ -", line, 4)) { + struct fragment dummy; + if (parse_fragment_header(line, len, &dummy) < 0) + continue; + error("patch fragment without header at line %d: %.*s", linenr, (int)len-1, line); + } + + if (size < len + 6) + break; + + /* + * Git patch? It might not have a real patch, just a rename + * or mode change, so we handle that specially + */ + if (!memcmp("diff --git ", line, 11)) { + int git_hdr_len = parse_git_header(line, len, size, patch); + if (git_hdr_len <= len) + continue; + if (!patch->old_name && !patch->new_name) { + if (!patch->def_name) + die("git diff header lacks filename information (line %d)", linenr); + patch->old_name = patch->new_name = patch->def_name; + } + *hdrsize = git_hdr_len; + return offset; + } + + /** --- followed by +++ ? */ + if (memcmp("--- ", line, 4) || memcmp("+++ ", line + len, 4)) + continue; + + /* + * We only accept unified patches, so we want it to + * at least have "@@ -a,b +c,d @@\n", which is 14 chars + * minimum + */ + nextlen = linelen(line + len, size - len); + if (size < nextlen + 14 || memcmp("@@ -", line + len + nextlen, 4)) + continue; + + /* Ok, we'll consider it a patch */ + parse_traditional_patch(line, line+len, patch); + *hdrsize = len + nextlen; + linenr += 2; + return offset; + } + return -1; +} + +/* + * Parse a unified diff. Note that this really needs + * to parse each fragment separately, since the only + * way to know the difference between a "---" that is + * part of a patch, and a "---" that starts the next + * patch is to look at the line counts.. + */ +static int parse_fragment(char *line, unsigned long size, struct patch *patch, struct fragment *fragment) +{ + int added, deleted; + int len = linelen(line, size), offset; + unsigned long oldlines, newlines; + unsigned long leading, trailing; + + offset = parse_fragment_header(line, len, fragment); + if (offset < 0) + return -1; + oldlines = fragment->oldlines; + newlines = fragment->newlines; + leading = 0; + trailing = 0; + + if (patch->is_new < 0) { + patch->is_new = !oldlines; + if (!oldlines) + patch->old_name = NULL; + } + if (patch->is_delete < 0) { + patch->is_delete = !newlines; + if (!newlines) + patch->new_name = NULL; + } + + if (patch->is_new && oldlines) + return error("new file depends on old contents"); + if (patch->is_delete != !newlines) { + if (newlines) + return error("deleted file still has contents"); + fprintf(stderr, "** warning: file %s becomes empty but is not deleted\n", patch->new_name); + } + + /* Parse the thing.. */ + line += len; + size -= len; + linenr++; + added = deleted = 0; + for (offset = len; size > 0; offset += len, size -= len, line += len, linenr++) { + if (!oldlines && !newlines) + break; + len = linelen(line, size); + if (!len || line[len-1] != '\n') + return -1; + switch (*line) { + default: + return -1; + case ' ': + oldlines--; + newlines--; + if (!deleted && !added) + leading++; + trailing++; + break; + case '-': + deleted++; + oldlines--; + trailing = 0; + break; + case '+': + /* + * We know len is at least two, since we have a '+' and + * we checked that the last character was a '\n' above. + * That is, an addition of an empty line would check + * the '+' here. Sneaky... + */ + if ((new_whitespace != nowarn_whitespace) && + isspace(line[len-2])) { + whitespace_error++; + if (squelch_whitespace_errors && + squelch_whitespace_errors < + whitespace_error) + ; + else { + fprintf(stderr, "Adds trailing whitespace.\n%s:%d:%.*s\n", + patch_input_file, + linenr, len-2, line+1); + } + } + added++; + newlines--; + trailing = 0; + break; + + /* We allow "\ No newline at end of file". Depending + * on locale settings when the patch was produced we + * don't know what this line looks like. The only + * thing we do know is that it begins with "\ ". + * Checking for 12 is just for sanity check -- any + * l10n of "\ No newline..." is at least that long. + */ + case '\\': + if (len < 12 || memcmp(line, "\\ ", 2)) + return -1; + break; + } + } + if (oldlines || newlines) + return -1; + fragment->leading = leading; + fragment->trailing = trailing; + + /* If a fragment ends with an incomplete line, we failed to include + * it in the above loop because we hit oldlines == newlines == 0 + * before seeing it. + */ + if (12 < size && !memcmp(line, "\\ ", 2)) + offset += linelen(line, size); + + patch->lines_added += added; + patch->lines_deleted += deleted; + return offset; +} + +static int parse_single_patch(char *line, unsigned long size, struct patch *patch) +{ + unsigned long offset = 0; + struct fragment **fragp = &patch->fragments; + + while (size > 4 && !memcmp(line, "@@ -", 4)) { + struct fragment *fragment; + int len; + + fragment = xcalloc(1, sizeof(*fragment)); + len = parse_fragment(line, size, patch, fragment); + if (len <= 0) + die("corrupt patch at line %d", linenr); + + fragment->patch = line; + fragment->size = len; + + *fragp = fragment; + fragp = &fragment->next; + + offset += len; + line += len; + size -= len; + } + return offset; +} + +static inline int metadata_changes(struct patch *patch) +{ + return patch->is_rename > 0 || + patch->is_copy > 0 || + patch->is_new > 0 || + patch->is_delete || + (patch->old_mode && patch->new_mode && + patch->old_mode != patch->new_mode); +} + +static int parse_binary(char *buffer, unsigned long size, struct patch *patch) +{ + /* We have read "GIT binary patch\n"; what follows is a line + * that says the patch method (currently, either "deflated + * literal" or "deflated delta") and the length of data before + * deflating; a sequence of 'length-byte' followed by base-85 + * encoded data follows. + * + * Each 5-byte sequence of base-85 encodes up to 4 bytes, + * and we would limit the patch line to 66 characters, + * so one line can fit up to 13 groups that would decode + * to 52 bytes max. The length byte 'A'-'Z' corresponds + * to 1-26 bytes, and 'a'-'z' corresponds to 27-52 bytes. + * The end of binary is signalled with an empty line. + */ + int llen, used; + struct fragment *fragment; + char *data = NULL; + + patch->fragments = fragment = xcalloc(1, sizeof(*fragment)); + + /* Grab the type of patch */ + llen = linelen(buffer, size); + used = llen; + linenr++; + + if (!strncmp(buffer, "delta ", 6)) { + patch->is_binary = BINARY_DELTA_DEFLATED; + patch->deflate_origlen = strtoul(buffer + 6, NULL, 10); + } + else if (!strncmp(buffer, "literal ", 8)) { + patch->is_binary = BINARY_LITERAL_DEFLATED; + patch->deflate_origlen = strtoul(buffer + 8, NULL, 10); + } + else + return error("unrecognized binary patch at line %d: %.*s", + linenr-1, llen-1, buffer); + buffer += llen; + while (1) { + int byte_length, max_byte_length, newsize; + llen = linelen(buffer, size); + used += llen; + linenr++; + if (llen == 1) + break; + /* Minimum line is "A00000\n" which is 7-byte long, + * and the line length must be multiple of 5 plus 2. + */ + if ((llen < 7) || (llen-2) % 5) + goto corrupt; + max_byte_length = (llen - 2) / 5 * 4; + byte_length = *buffer; + if ('A' <= byte_length && byte_length <= 'Z') + byte_length = byte_length - 'A' + 1; + else if ('a' <= byte_length && byte_length <= 'z') + byte_length = byte_length - 'a' + 27; + else + goto corrupt; + /* if the input length was not multiple of 4, we would + * have filler at the end but the filler should never + * exceed 3 bytes + */ + if (max_byte_length < byte_length || + byte_length <= max_byte_length - 4) + goto corrupt; + newsize = fragment->size + byte_length; + data = xrealloc(data, newsize); + if (decode_85(data + fragment->size, + buffer + 1, + byte_length)) + goto corrupt; + fragment->size = newsize; + buffer += llen; + size -= llen; + } + fragment->patch = data; + return used; + corrupt: + return error("corrupt binary patch at line %d: %.*s", + linenr-1, llen-1, buffer); +} + +static int parse_chunk(char *buffer, unsigned long size, struct patch *patch) +{ + int hdrsize, patchsize; + int offset = find_header(buffer, size, &hdrsize, patch); + + if (offset < 0) + return offset; + + patchsize = parse_single_patch(buffer + offset + hdrsize, size - offset - hdrsize, patch); + + if (!patchsize) { + static const char *binhdr[] = { + "Binary files ", + "Files ", + NULL, + }; + static const char git_binary[] = "GIT binary patch\n"; + int i; + int hd = hdrsize + offset; + unsigned long llen = linelen(buffer + hd, size - hd); + + if (llen == sizeof(git_binary) - 1 && + !memcmp(git_binary, buffer + hd, llen)) { + int used; + linenr++; + used = parse_binary(buffer + hd + llen, + size - hd - llen, patch); + if (used) + patchsize = used + llen; + else + patchsize = 0; + } + else if (!memcmp(" differ\n", buffer + hd + llen - 8, 8)) { + for (i = 0; binhdr[i]; i++) { + int len = strlen(binhdr[i]); + if (len < size - hd && + !memcmp(binhdr[i], buffer + hd, len)) { + linenr++; + patch->is_binary = 1; + patchsize = llen; + break; + } + } + } + + /* Empty patch cannot be applied if: + * - it is a binary patch and we do not do binary_replace, or + * - text patch without metadata change + */ + if ((apply || check) && + (patch->is_binary + ? !allow_binary_replacement + : !metadata_changes(patch))) + die("patch with only garbage at line %d", linenr); + } + + return offset + hdrsize + patchsize; +} + +static const char pluses[] = "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"; +static const char minuses[]= "----------------------------------------------------------------------"; + +static void show_stats(struct patch *patch) +{ + const char *prefix = ""; + char *name = patch->new_name; + char *qname = NULL; + int len, max, add, del, total; + + if (!name) + name = patch->old_name; + + if (0 < (len = quote_c_style(name, NULL, NULL, 0))) { + qname = xmalloc(len + 1); + quote_c_style(name, qname, NULL, 0); + name = qname; + } + + /* + * "scale" the filename + */ + len = strlen(name); + max = max_len; + if (max > 50) + max = 50; + if (len > max) { + char *slash; + prefix = "..."; + max -= 3; + name += len - max; + slash = strchr(name, '/'); + if (slash) + name = slash; + } + len = max; + + /* + * scale the add/delete + */ + max = max_change; + if (max + len > 70) + max = 70 - len; + + add = patch->lines_added; + del = patch->lines_deleted; + total = add + del; + + if (max_change > 0) { + total = (total * max + max_change / 2) / max_change; + add = (add * max + max_change / 2) / max_change; + del = total - add; + } + if (patch->is_binary) + printf(" %s%-*s | Bin\n", prefix, len, name); + else + printf(" %s%-*s |%5d %.*s%.*s\n", prefix, + len, name, patch->lines_added + patch->lines_deleted, + add, pluses, del, minuses); + if (qname) + free(qname); +} + +static int read_old_data(struct stat *st, const char *path, void *buf, unsigned long size) +{ + int fd; + unsigned long got; + + switch (st->st_mode & S_IFMT) { + case S_IFLNK: + return readlink(path, buf, size); + case S_IFREG: + fd = open(path, O_RDONLY); + if (fd < 0) + return error("unable to open %s", path); + got = 0; + for (;;) { + int ret = xread(fd, buf + got, size - got); + if (ret <= 0) + break; + got += ret; + } + close(fd); + return got; + + default: + return -1; + } +} + +static int find_offset(const char *buf, unsigned long size, const char *fragment, unsigned long fragsize, int line, int *lines) +{ + int i; + unsigned long start, backwards, forwards; + + if (fragsize > size) + return -1; + + start = 0; + if (line > 1) { + unsigned long offset = 0; + i = line-1; + while (offset + fragsize <= size) { + if (buf[offset++] == '\n') { + start = offset; + if (!--i) + break; + } + } + } + + /* Exact line number? */ + if (!memcmp(buf + start, fragment, fragsize)) + return start; + + /* + * There's probably some smart way to do this, but I'll leave + * that to the smart and beautiful people. I'm simple and stupid. + */ + backwards = start; + forwards = start; + for (i = 0; ; i++) { + unsigned long try; + int n; + + /* "backward" */ + if (i & 1) { + if (!backwards) { + if (forwards + fragsize > size) + break; + continue; + } + do { + --backwards; + } while (backwards && buf[backwards-1] != '\n'); + try = backwards; + } else { + while (forwards + fragsize <= size) { + if (buf[forwards++] == '\n') + break; + } + try = forwards; + } + + if (try + fragsize > size) + continue; + if (memcmp(buf + try, fragment, fragsize)) + continue; + n = (i >> 1)+1; + if (i & 1) + n = -n; + *lines = n; + return try; + } + + /* + * We should start searching forward and backward. + */ + return -1; +} + +static void remove_first_line(const char **rbuf, int *rsize) +{ + const char *buf = *rbuf; + int size = *rsize; + unsigned long offset; + offset = 0; + while (offset <= size) { + if (buf[offset++] == '\n') + break; + } + *rsize = size - offset; + *rbuf = buf + offset; +} + +static void remove_last_line(const char **rbuf, int *rsize) +{ + const char *buf = *rbuf; + int size = *rsize; + unsigned long offset; + offset = size - 1; + while (offset > 0) { + if (buf[--offset] == '\n') + break; + } + *rsize = offset + 1; +} + +struct buffer_desc { + char *buffer; + unsigned long size; + unsigned long alloc; +}; + +static int apply_line(char *output, const char *patch, int plen) +{ + /* plen is number of bytes to be copied from patch, + * starting at patch+1 (patch[0] is '+'). Typically + * patch[plen] is '\n'. + */ + int add_nl_to_tail = 0; + if ((new_whitespace == strip_whitespace) && + 1 < plen && isspace(patch[plen-1])) { + if (patch[plen] == '\n') + add_nl_to_tail = 1; + plen--; + while (0 < plen && isspace(patch[plen])) + plen--; + applied_after_stripping++; + } + memcpy(output, patch + 1, plen); + if (add_nl_to_tail) + output[plen++] = '\n'; + return plen; +} + +static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag) +{ + char *buf = desc->buffer; + const char *patch = frag->patch; + int offset, size = frag->size; + char *old = xmalloc(size); + char *new = xmalloc(size); + const char *oldlines, *newlines; + int oldsize = 0, newsize = 0; + unsigned long leading, trailing; + int pos, lines; + + while (size > 0) { + int len = linelen(patch, size); + int plen; + + if (!len) + break; + + /* + * "plen" is how much of the line we should use for + * the actual patch data. Normally we just remove the + * first character on the line, but if the line is + * followed by "\ No newline", then we also remove the + * last one (which is the newline, of course). + */ + plen = len-1; + if (len < size && patch[len] == '\\') + plen--; + switch (*patch) { + case ' ': + case '-': + memcpy(old + oldsize, patch + 1, plen); + oldsize += plen; + if (*patch == '-') + break; + /* Fall-through for ' ' */ + case '+': + if (*patch != '+' || !no_add) + newsize += apply_line(new + newsize, patch, + plen); + break; + case '@': case '\\': + /* Ignore it, we already handled it */ + break; + default: + return -1; + } + patch += len; + size -= len; + } + +#ifdef NO_ACCURATE_DIFF + if (oldsize > 0 && old[oldsize - 1] == '\n' && + newsize > 0 && new[newsize - 1] == '\n') { + oldsize--; + newsize--; + } +#endif + + oldlines = old; + newlines = new; + leading = frag->leading; + trailing = frag->trailing; + lines = 0; + pos = frag->newpos; + for (;;) { + offset = find_offset(buf, desc->size, oldlines, oldsize, pos, &lines); + if (offset >= 0) { + int diff = newsize - oldsize; + unsigned long size = desc->size + diff; + unsigned long alloc = desc->alloc; + + /* Warn if it was necessary to reduce the number + * of context lines. + */ + if ((leading != frag->leading) || (trailing != frag->trailing)) + fprintf(stderr, "Context reduced to (%ld/%ld) to apply fragment at %d\n", + leading, trailing, pos + lines); + + if (size > alloc) { + alloc = size + 8192; + desc->alloc = alloc; + buf = xrealloc(buf, alloc); + desc->buffer = buf; + } + desc->size = size; + memmove(buf + offset + newsize, buf + offset + oldsize, size - offset - newsize); + memcpy(buf + offset, newlines, newsize); + offset = 0; + + break; + } + + /* Am I at my context limits? */ + if ((leading <= p_context) && (trailing <= p_context)) + break; + /* Reduce the number of context lines + * Reduce both leading and trailing if they are equal + * otherwise just reduce the larger context. + */ + if (leading >= trailing) { + remove_first_line(&oldlines, &oldsize); + remove_first_line(&newlines, &newsize); + pos--; + leading--; + } + if (trailing > leading) { + remove_last_line(&oldlines, &oldsize); + remove_last_line(&newlines, &newsize); + trailing--; + } + } + + free(old); + free(new); + return offset; +} + +static char *inflate_it(const void *data, unsigned long size, + unsigned long inflated_size) +{ + z_stream stream; + void *out; + int st; + + memset(&stream, 0, sizeof(stream)); + + stream.next_in = (unsigned char *)data; + stream.avail_in = size; + stream.next_out = out = xmalloc(inflated_size); + stream.avail_out = inflated_size; + inflateInit(&stream); + st = inflate(&stream, Z_FINISH); + if ((st != Z_STREAM_END) || stream.total_out != inflated_size) { + free(out); + return NULL; + } + return out; +} + +static int apply_binary_fragment(struct buffer_desc *desc, struct patch *patch) +{ + unsigned long dst_size; + struct fragment *fragment = patch->fragments; + void *data; + void *result; + + data = inflate_it(fragment->patch, fragment->size, + patch->deflate_origlen); + if (!data) + return error("corrupt patch data"); + switch (patch->is_binary) { + case BINARY_DELTA_DEFLATED: + result = patch_delta(desc->buffer, desc->size, + data, + patch->deflate_origlen, + &dst_size); + free(desc->buffer); + desc->buffer = result; + free(data); + break; + case BINARY_LITERAL_DEFLATED: + free(desc->buffer); + desc->buffer = data; + dst_size = patch->deflate_origlen; + break; + } + if (!desc->buffer) + return -1; + desc->size = desc->alloc = dst_size; + return 0; +} + +static int apply_binary(struct buffer_desc *desc, struct patch *patch) +{ + const char *name = patch->old_name ? patch->old_name : patch->new_name; + unsigned char sha1[20]; + unsigned char hdr[50]; + int hdrlen; + + if (!allow_binary_replacement) + return error("cannot apply binary patch to '%s' " + "without --allow-binary-replacement", + name); + + /* For safety, we require patch index line to contain + * full 40-byte textual SHA1 for old and new, at least for now. + */ + if (strlen(patch->old_sha1_prefix) != 40 || + strlen(patch->new_sha1_prefix) != 40 || + get_sha1_hex(patch->old_sha1_prefix, sha1) || + get_sha1_hex(patch->new_sha1_prefix, sha1)) + return error("cannot apply binary patch to '%s' " + "without full index line", name); + + if (patch->old_name) { + /* See if the old one matches what the patch + * applies to. + */ + write_sha1_file_prepare(desc->buffer, desc->size, + blob_type, sha1, hdr, &hdrlen); + if (strcmp(sha1_to_hex(sha1), patch->old_sha1_prefix)) + return error("the patch applies to '%s' (%s), " + "which does not match the " + "current contents.", + name, sha1_to_hex(sha1)); + } + else { + /* Otherwise, the old one must be empty. */ + if (desc->size) + return error("the patch applies to an empty " + "'%s' but it is not empty", name); + } + + get_sha1_hex(patch->new_sha1_prefix, sha1); + if (!memcmp(sha1, null_sha1, 20)) { + free(desc->buffer); + desc->alloc = desc->size = 0; + desc->buffer = NULL; + return 0; /* deletion patch */ + } + + if (has_sha1_file(sha1)) { + /* We already have the postimage */ + char type[10]; + unsigned long size; + + free(desc->buffer); + desc->buffer = read_sha1_file(sha1, type, &size); + if (!desc->buffer) + return error("the necessary postimage %s for " + "'%s' cannot be read", + patch->new_sha1_prefix, name); + desc->alloc = desc->size = size; + } + else { + /* We have verified desc matches the preimage; + * apply the patch data to it, which is stored + * in the patch->fragments->{patch,size}. + */ + if (apply_binary_fragment(desc, patch)) + return error("binary patch does not apply to '%s'", + name); + + /* verify that the result matches */ + write_sha1_file_prepare(desc->buffer, desc->size, blob_type, + sha1, hdr, &hdrlen); + if (strcmp(sha1_to_hex(sha1), patch->new_sha1_prefix)) + return error("binary patch to '%s' creates incorrect result", name); + } + + return 0; +} + +static int apply_fragments(struct buffer_desc *desc, struct patch *patch) +{ + struct fragment *frag = patch->fragments; + const char *name = patch->old_name ? patch->old_name : patch->new_name; + + if (patch->is_binary) + return apply_binary(desc, patch); + + while (frag) { + if (apply_one_fragment(desc, frag) < 0) + return error("patch failed: %s:%ld", + name, frag->oldpos); + frag = frag->next; + } + return 0; +} + +static int apply_data(struct patch *patch, struct stat *st, struct cache_entry *ce) +{ + char *buf; + unsigned long size, alloc; + struct buffer_desc desc; + + size = 0; + alloc = 0; + buf = NULL; + if (cached) { + if (ce) { + char type[20]; + buf = read_sha1_file(ce->sha1, type, &size); + if (!buf) + return error("read of %s failed", + patch->old_name); + alloc = size; + } + } + else if (patch->old_name) { + size = st->st_size; + alloc = size + 8192; + buf = xmalloc(alloc); + if (read_old_data(st, patch->old_name, buf, alloc) != size) + return error("read of %s failed", patch->old_name); + } + + desc.size = size; + desc.alloc = alloc; + desc.buffer = buf; + if (apply_fragments(&desc, patch) < 0) + return -1; + patch->result = desc.buffer; + patch->resultsize = desc.size; + + if (patch->is_delete && patch->resultsize) + return error("removal patch leaves file contents"); + + return 0; +} + +static int check_patch(struct patch *patch) +{ + struct stat st; + const char *old_name = patch->old_name; + const char *new_name = patch->new_name; + const char *name = old_name ? old_name : new_name; + struct cache_entry *ce = NULL; + + if (old_name) { + int changed = 0; + int stat_ret = 0; + unsigned st_mode = 0; + + if (!cached) + stat_ret = lstat(old_name, &st); + if (check_index) { + int pos = cache_name_pos(old_name, strlen(old_name)); + if (pos < 0) + return error("%s: does not exist in index", + old_name); + ce = active_cache[pos]; + if (stat_ret < 0) { + struct checkout costate; + if (errno != ENOENT) + return error("%s: %s", old_name, + strerror(errno)); + /* checkout */ + costate.base_dir = ""; + costate.base_dir_len = 0; + costate.force = 0; + costate.quiet = 0; + costate.not_new = 0; + costate.refresh_cache = 1; + if (checkout_entry(ce, + &costate, + NULL) || + lstat(old_name, &st)) + return -1; + } + if (!cached) + changed = ce_match_stat(ce, &st, 1); + if (changed) + return error("%s: does not match index", + old_name); + if (cached) + st_mode = ntohl(ce->ce_mode); + } + else if (stat_ret < 0) + return error("%s: %s", old_name, strerror(errno)); + + if (!cached) + st_mode = ntohl(create_ce_mode(st.st_mode)); + + if (patch->is_new < 0) + patch->is_new = 0; + if (!patch->old_mode) + patch->old_mode = st_mode; + if ((st_mode ^ patch->old_mode) & S_IFMT) + return error("%s: wrong type", old_name); + if (st_mode != patch->old_mode) + fprintf(stderr, "warning: %s has type %o, expected %o\n", + old_name, st_mode, patch->old_mode); + } + + if (new_name && (patch->is_new | patch->is_rename | patch->is_copy)) { + if (check_index && cache_name_pos(new_name, strlen(new_name)) >= 0) + return error("%s: already exists in index", new_name); + if (!cached) { + if (!lstat(new_name, &st)) + return error("%s: already exists in working directory", new_name); + if (errno != ENOENT) + return error("%s: %s", new_name, strerror(errno)); + } + if (!patch->new_mode) { + if (patch->is_new) + patch->new_mode = S_IFREG | 0644; + else + patch->new_mode = patch->old_mode; + } + } + + if (new_name && old_name) { + int same = !strcmp(old_name, new_name); + if (!patch->new_mode) + patch->new_mode = patch->old_mode; + if ((patch->old_mode ^ patch->new_mode) & S_IFMT) + return error("new mode (%o) of %s does not match old mode (%o)%s%s", + patch->new_mode, new_name, patch->old_mode, + same ? "" : " of ", same ? "" : old_name); + } + + if (apply_data(patch, &st, ce) < 0) + return error("%s: patch does not apply", name); + return 0; +} + +static int check_patch_list(struct patch *patch) +{ + int error = 0; + + for (;patch ; patch = patch->next) + error |= check_patch(patch); + return error; +} + +static inline int is_null_sha1(const unsigned char *sha1) +{ + return !memcmp(sha1, null_sha1, 20); +} + +static void show_index_list(struct patch *list) +{ + struct patch *patch; + + /* Once we start supporting the reverse patch, it may be + * worth showing the new sha1 prefix, but until then... + */ + for (patch = list; patch; patch = patch->next) { + const unsigned char *sha1_ptr; + unsigned char sha1[20]; + const char *name; + + name = patch->old_name ? patch->old_name : patch->new_name; + if (patch->is_new) + sha1_ptr = null_sha1; + else if (get_sha1(patch->old_sha1_prefix, sha1)) + die("sha1 information is lacking or useless (%s).", + name); + else + sha1_ptr = sha1; + + printf("%06o %s ",patch->old_mode, sha1_to_hex(sha1_ptr)); + if (line_termination && quote_c_style(name, NULL, NULL, 0)) + quote_c_style(name, NULL, stdout, 0); + else + fputs(name, stdout); + putchar(line_termination); + } +} + +static void stat_patch_list(struct patch *patch) +{ + int files, adds, dels; + + for (files = adds = dels = 0 ; patch ; patch = patch->next) { + files++; + adds += patch->lines_added; + dels += patch->lines_deleted; + show_stats(patch); + } + + printf(" %d files changed, %d insertions(+), %d deletions(-)\n", files, adds, dels); +} + +static void numstat_patch_list(struct patch *patch) +{ + for ( ; patch; patch = patch->next) { + const char *name; + name = patch->new_name ? patch->new_name : patch->old_name; + printf("%d\t%d\t", patch->lines_added, patch->lines_deleted); + if (line_termination && quote_c_style(name, NULL, NULL, 0)) + quote_c_style(name, NULL, stdout, 0); + else + fputs(name, stdout); + putchar('\n'); + } +} + +static void show_file_mode_name(const char *newdelete, unsigned int mode, const char *name) +{ + if (mode) + printf(" %s mode %06o %s\n", newdelete, mode, name); + else + printf(" %s %s\n", newdelete, name); +} + +static void show_mode_change(struct patch *p, int show_name) +{ + if (p->old_mode && p->new_mode && p->old_mode != p->new_mode) { + if (show_name) + printf(" mode change %06o => %06o %s\n", + p->old_mode, p->new_mode, p->new_name); + else + printf(" mode change %06o => %06o\n", + p->old_mode, p->new_mode); + } +} + +static void show_rename_copy(struct patch *p) +{ + const char *renamecopy = p->is_rename ? "rename" : "copy"; + const char *old, *new; + + /* Find common prefix */ + old = p->old_name; + new = p->new_name; + while (1) { + const char *slash_old, *slash_new; + slash_old = strchr(old, '/'); + slash_new = strchr(new, '/'); + if (!slash_old || + !slash_new || + slash_old - old != slash_new - new || + memcmp(old, new, slash_new - new)) + break; + old = slash_old + 1; + new = slash_new + 1; + } + /* p->old_name thru old is the common prefix, and old and new + * through the end of names are renames + */ + if (old != p->old_name) + printf(" %s %.*s{%s => %s} (%d%%)\n", renamecopy, + (int)(old - p->old_name), p->old_name, + old, new, p->score); + else + printf(" %s %s => %s (%d%%)\n", renamecopy, + p->old_name, p->new_name, p->score); + show_mode_change(p, 0); +} + +static void summary_patch_list(struct patch *patch) +{ + struct patch *p; + + for (p = patch; p; p = p->next) { + if (p->is_new) + show_file_mode_name("create", p->new_mode, p->new_name); + else if (p->is_delete) + show_file_mode_name("delete", p->old_mode, p->old_name); + else { + if (p->is_rename || p->is_copy) + show_rename_copy(p); + else { + if (p->score) { + printf(" rewrite %s (%d%%)\n", + p->new_name, p->score); + show_mode_change(p, 0); + } + else + show_mode_change(p, 1); + } + } + } +} + +static void patch_stats(struct patch *patch) +{ + int lines = patch->lines_added + patch->lines_deleted; + + if (lines > max_change) + max_change = lines; + if (patch->old_name) { + int len = quote_c_style(patch->old_name, NULL, NULL, 0); + if (!len) + len = strlen(patch->old_name); + if (len > max_len) + max_len = len; + } + if (patch->new_name) { + int len = quote_c_style(patch->new_name, NULL, NULL, 0); + if (!len) + len = strlen(patch->new_name); + if (len > max_len) + max_len = len; + } +} + +static void remove_file(struct patch *patch) +{ + if (write_index) { + if (remove_file_from_cache(patch->old_name) < 0) + die("unable to remove %s from index", patch->old_name); + } + 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); +} + +static void write_out_one_result(struct patch *patch) +{ + if (patch->is_delete > 0) { + remove_file(patch); + return; + } + if (patch->is_new > 0 || patch->is_copy) { + create_file(patch); + return; + } + /* + * Rename or modification boils down to the same + * thing: remove the old, write the new + */ + remove_file(patch); + create_file(patch); +} + +static void write_out_results(struct patch *list, int skipped_patch) +{ + if (!list && !skipped_patch) + die("No changes"); + + while (list) { + write_out_one_result(list); + list = list->next; + } +} + +static struct cache_file cache_file; + +static struct excludes { + struct excludes *next; + const char *path; +} *excludes; + +static int use_patch(struct patch *p) +{ + const char *pathname = p->new_name ? p->new_name : p->old_name; + struct excludes *x = excludes; + while (x) { + if (fnmatch(x->path, pathname, 0) == 0) + return 0; + x = x->next; + } + if (0 < prefix_length) { + int pathlen = strlen(pathname); + if (pathlen <= prefix_length || + memcmp(prefix, pathname, prefix_length)) + return 0; + } + return 1; +} + +static int apply_patch(int fd, const char *filename) +{ + unsigned long offset, size; + char *buffer = read_patch_file(fd, &size); + struct patch *list = NULL, **listp = &list; + int skipped_patch = 0; + + patch_input_file = filename; + if (!buffer) + return -1; + offset = 0; + while (size > 0) { + struct patch *patch; + int nr; + + patch = xcalloc(1, sizeof(*patch)); + nr = parse_chunk(buffer + offset, size, patch); + if (nr < 0) + break; + if (use_patch(patch)) { + patch_stats(patch); + *listp = patch; + listp = &patch->next; + } else { + /* perhaps free it a bit better? */ + free(patch); + skipped_patch++; + } + offset += nr; + size -= nr; + } + + if (whitespace_error && (new_whitespace == error_on_whitespace)) + apply = 0; + + write_index = check_index && apply; + if (write_index && newfd < 0) + newfd = hold_index_file_for_update(&cache_file, get_index_file()); + if (check_index) { + if (read_cache() < 0) + die("unable to read index file"); + } + + if ((check || apply) && check_patch_list(list) < 0) + exit(1); + + if (apply) + write_out_results(list, skipped_patch); + + if (show_index_info) + show_index_list(list); + + if (diffstat) + stat_patch_list(list); + + if (numstat) + numstat_patch_list(list); + + if (summary) + summary_patch_list(list); + + free(buffer); + return 0; +} + +static int git_apply_config(const char *var, const char *value) +{ + if (!strcmp(var, "apply.whitespace")) { + apply_default_whitespace = strdup(value); + return 0; + } + return git_default_config(var, value); +} + + +int cmd_apply(int argc, const char **argv, char **envp) +{ + int i; + int read_stdin = 1; + const char *whitespace_option = NULL; + + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + char *end; + int fd; + + if (!strcmp(arg, "-")) { + apply_patch(0, ""); + read_stdin = 0; + continue; + } + if (!strncmp(arg, "--exclude=", 10)) { + struct excludes *x = xmalloc(sizeof(*x)); + x->path = arg + 10; + x->next = excludes; + excludes = x; + continue; + } + if (!strncmp(arg, "-p", 2)) { + p_value = atoi(arg + 2); + continue; + } + if (!strcmp(arg, "--no-add")) { + no_add = 1; + continue; + } + if (!strcmp(arg, "--stat")) { + apply = 0; + diffstat = 1; + continue; + } + if (!strcmp(arg, "--allow-binary-replacement") || + !strcmp(arg, "--binary")) { + allow_binary_replacement = 1; + continue; + } + if (!strcmp(arg, "--numstat")) { + apply = 0; + numstat = 1; + continue; + } + if (!strcmp(arg, "--summary")) { + apply = 0; + summary = 1; + continue; + } + if (!strcmp(arg, "--check")) { + apply = 0; + check = 1; + continue; + } + if (!strcmp(arg, "--index")) { + check_index = 1; + continue; + } + if (!strcmp(arg, "--cached")) { + check_index = 1; + cached = 1; + continue; + } + if (!strcmp(arg, "--apply")) { + apply = 1; + continue; + } + if (!strcmp(arg, "--index-info")) { + apply = 0; + show_index_info = 1; + continue; + } + if (!strcmp(arg, "-z")) { + line_termination = 0; + continue; + } + if (!strncmp(arg, "-C", 2)) { + p_context = strtoul(arg + 2, &end, 0); + if (*end != '\0') + die("unrecognized context count '%s'", arg + 2); + continue; + } + if (!strncmp(arg, "--whitespace=", 13)) { + whitespace_option = arg + 13; + parse_whitespace_option(arg + 13); + continue; + } + + if (check_index && prefix_length < 0) { + prefix = setup_git_directory(); + prefix_length = prefix ? strlen(prefix) : 0; + git_config(git_apply_config); + if (!whitespace_option && apply_default_whitespace) + parse_whitespace_option(apply_default_whitespace); + } + if (0 < prefix_length) + arg = prefix_filename(prefix, prefix_length, arg); + + fd = open(arg, O_RDONLY); + if (fd < 0) + usage(apply_usage); + read_stdin = 0; + set_default_whitespace_mode(whitespace_option); + apply_patch(fd, arg); + close(fd); + } + set_default_whitespace_mode(whitespace_option); + if (read_stdin) + apply_patch(0, ""); + if (whitespace_error) { + if (squelch_whitespace_errors && + squelch_whitespace_errors < whitespace_error) { + int squelched = + whitespace_error - squelch_whitespace_errors; + fprintf(stderr, "warning: squelched %d whitespace error%s\n", + squelched, + squelched == 1 ? "" : "s"); + } + if (new_whitespace == error_on_whitespace) + die("%d line%s add%s trailing whitespaces.", + whitespace_error, + whitespace_error == 1 ? "" : "s", + whitespace_error == 1 ? "s" : ""); + if (applied_after_stripping) + fprintf(stderr, "warning: %d line%s applied after" + " stripping trailing whitespaces.\n", + applied_after_stripping, + applied_after_stripping == 1 ? "" : "s"); + else if (whitespace_error) + fprintf(stderr, "warning: %d line%s add%s trailing" + " whitespaces.\n", + whitespace_error, + whitespace_error == 1 ? "" : "s", + whitespace_error == 1 ? "s" : ""); + } + + if (write_index) { + if (write_cache(newfd, active_cache, active_nr) || + commit_index_file(&cache_file)) + die("Unable to write new cachefile"); + } + + return 0; +} diff --git a/builtin.h b/builtin.h index c6b07d9a65..d6ff88ebc1 100644 --- a/builtin.h +++ b/builtin.h @@ -32,5 +32,6 @@ extern int cmd_ls_tree(int argc, const char **argv, char **envp); extern int cmd_tar_tree(int argc, const char **argv, char **envp); extern int cmd_read_tree(int argc, const char **argv, char **envp); extern int cmd_commit_tree(int argc, const char **argv, char **envp); +extern int cmd_apply(int argc, const char **argv, char **envp); #endif diff --git a/git.c b/git.c index 4c2c062e36..f44e08b9ce 100644 --- a/git.c +++ b/git.c @@ -57,7 +57,8 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "ls-tree", cmd_ls_tree }, { "tar-tree", cmd_tar_tree }, { "read-tree", cmd_read_tree }, - { "commit-tree", cmd_commit_tree } + { "commit-tree", cmd_commit_tree }, + { "apply", cmd_apply } }; int i; -- cgit v1.3 From 51ce34b9923d9b119ac53414584f80e05520abea Mon Sep 17 00:00:00 2001 From: Peter Eriksen Date: Tue, 23 May 2006 14:15:35 +0200 Subject: Builtin git-show-branch. Signed-off-by: Peter Eriksen Signed-off-by: Junio C Hamano --- Makefile | 6 +- builtin-show-branch.c | 789 ++++++++++++++++++++++++++++++++++++++++++++++++++ builtin.h | 1 + git.c | 3 +- show-branch.c | 788 ------------------------------------------------- 5 files changed, 795 insertions(+), 792 deletions(-) create mode 100644 builtin-show-branch.c delete mode 100644 show-branch.c (limited to 'Makefile') diff --git a/Makefile b/Makefile index 3da576838f..69377e37b4 100644 --- a/Makefile +++ b/Makefile @@ -159,7 +159,7 @@ PROGRAMS = \ git-merge-index$X git-mktag$X git-mktree$X git-pack-objects$X git-patch-id$X \ git-peek-remote$X git-prune-packed$X \ git-receive-pack$X git-rev-parse$X \ - git-send-pack$X git-show-branch$X git-shell$X \ + git-send-pack$X git-shell$X \ git-show-index$X git-ssh-fetch$X \ git-ssh-upload$X git-unpack-file$X \ git-unpack-objects$X git-update-index$X git-update-server-info$X \ @@ -173,7 +173,7 @@ BUILT_INS = git-log$X git-whatchanged$X git-show$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-tar-tree$X git-read-tree$X git-commit-tree$X \ - git-apply$X + git-apply$X git-show-branch$X # what 'all' will build and 'install' will install, in gitexecdir ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS) @@ -224,7 +224,7 @@ BUILTIN_OBJS = \ 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-tar-tree.o builtin-read-tree.o builtin-commit-tree.o \ - builtin-apply.o + builtin-apply.o builtin-show-branch.o GITLIBS = $(LIB_FILE) $(XDIFF_LIB) LIBS = $(GITLIBS) -lz diff --git a/builtin-show-branch.c b/builtin-show-branch.c new file mode 100644 index 0000000000..3af24e767b --- /dev/null +++ b/builtin-show-branch.c @@ -0,0 +1,789 @@ +#include +#include +#include "cache.h" +#include "commit.h" +#include "refs.h" +#include "builtin.h" + +static const char show_branch_usage[] = +"git-show-branch [--dense] [--current] [--all] [--heads] [--tags] [--topo-order] [--more=count | --list | --independent | --merge-base ] [--topics] [...]"; + +static int default_num = 0; +static int default_alloc = 0; +static const char **default_arg = NULL; + +#define UNINTERESTING 01 + +#define REV_SHIFT 2 +#define MAX_REVS 29 /* should not exceed bits_per_int - REV_SHIFT */ + +static struct commit *interesting(struct commit_list *list) +{ + while (list) { + struct commit *commit = list->item; + list = list->next; + if (commit->object.flags & UNINTERESTING) + continue; + return commit; + } + return NULL; +} + +static struct commit *pop_one_commit(struct commit_list **list_p) +{ + struct commit *commit; + struct commit_list *list; + list = *list_p; + commit = list->item; + *list_p = list->next; + free(list); + return commit; +} + +struct commit_name { + const char *head_name; /* which head's ancestor? */ + int generation; /* how many parents away from head_name */ +}; + +/* Name the commit as nth generation ancestor of head_name; + * we count only the first-parent relationship for naming purposes. + */ +static void name_commit(struct commit *commit, const char *head_name, int nth) +{ + struct commit_name *name; + if (!commit->object.util) + commit->object.util = xmalloc(sizeof(struct commit_name)); + name = commit->object.util; + name->head_name = head_name; + name->generation = nth; +} + +/* Parent is the first parent of the commit. We may name it + * as (n+1)th generation ancestor of the same head_name as + * commit is nth generation ancestor of, if that generation + * number is better than the name it already has. + */ +static void name_parent(struct commit *commit, struct commit *parent) +{ + struct commit_name *commit_name = commit->object.util; + struct commit_name *parent_name = parent->object.util; + if (!commit_name) + return; + if (!parent_name || + commit_name->generation + 1 < parent_name->generation) + name_commit(parent, commit_name->head_name, + commit_name->generation + 1); +} + +static int name_first_parent_chain(struct commit *c) +{ + int i = 0; + while (c) { + struct commit *p; + if (!c->object.util) + break; + if (!c->parents) + break; + p = c->parents->item; + if (!p->object.util) { + name_parent(c, p); + i++; + } + c = p; + } + return i; +} + +static void name_commits(struct commit_list *list, + struct commit **rev, + char **ref_name, + int num_rev) +{ + struct commit_list *cl; + struct commit *c; + int i; + + /* First give names to the given heads */ + for (cl = list; cl; cl = cl->next) { + c = cl->item; + if (c->object.util) + continue; + for (i = 0; i < num_rev; i++) { + if (rev[i] == c) { + name_commit(c, ref_name[i], 0); + break; + } + } + } + + /* Then commits on the first parent ancestry chain */ + do { + i = 0; + for (cl = list; cl; cl = cl->next) { + i += name_first_parent_chain(cl->item); + } + } while (i); + + /* Finally, any unnamed commits */ + do { + i = 0; + for (cl = list; cl; cl = cl->next) { + struct commit_list *parents; + struct commit_name *n; + int nth; + c = cl->item; + if (!c->object.util) + continue; + n = c->object.util; + parents = c->parents; + nth = 0; + while (parents) { + struct commit *p = parents->item; + char newname[1000], *en; + parents = parents->next; + nth++; + if (p->object.util) + continue; + en = newname; + switch (n->generation) { + case 0: + en += sprintf(en, "%s", n->head_name); + break; + case 1: + en += sprintf(en, "%s^", n->head_name); + break; + default: + en += sprintf(en, "%s~%d", + n->head_name, n->generation); + break; + } + if (nth == 1) + en += sprintf(en, "^"); + else + en += sprintf(en, "^%d", nth); + name_commit(p, strdup(newname), 0); + i++; + name_first_parent_chain(p); + } + } + } while (i); +} + +static int mark_seen(struct commit *commit, struct commit_list **seen_p) +{ + if (!commit->object.flags) { + insert_by_date(commit, seen_p); + return 1; + } + return 0; +} + +static void join_revs(struct commit_list **list_p, + struct commit_list **seen_p, + int num_rev, int extra) +{ + int all_mask = ((1u << (REV_SHIFT + num_rev)) - 1); + int all_revs = all_mask & ~((1u << REV_SHIFT) - 1); + + while (*list_p) { + struct commit_list *parents; + int still_interesting = !!interesting(*list_p); + struct commit *commit = pop_one_commit(list_p); + int flags = commit->object.flags & all_mask; + + if (!still_interesting && extra <= 0) + break; + + mark_seen(commit, seen_p); + if ((flags & all_revs) == all_revs) + flags |= UNINTERESTING; + parents = commit->parents; + + while (parents) { + struct commit *p = parents->item; + int this_flag = p->object.flags; + parents = parents->next; + if ((this_flag & flags) == flags) + continue; + if (!p->object.parsed) + parse_commit(p); + if (mark_seen(p, seen_p) && !still_interesting) + extra--; + p->object.flags |= flags; + insert_by_date(p, list_p); + } + } + + /* + * Postprocess to complete well-poisoning. + * + * At this point we have all the commits we have seen in + * seen_p list (which happens to be sorted chronologically but + * it does not really matter). Mark anything that can be + * reached from uninteresting commits not interesting. + */ + for (;;) { + int changed = 0; + struct commit_list *s; + for (s = *seen_p; s; s = s->next) { + struct commit *c = s->item; + struct commit_list *parents; + + if (((c->object.flags & all_revs) != all_revs) && + !(c->object.flags & UNINTERESTING)) + continue; + + /* The current commit is either a merge base or + * already uninteresting one. Mark its parents + * as uninteresting commits _only_ if they are + * already parsed. No reason to find new ones + * here. + */ + parents = c->parents; + while (parents) { + struct commit *p = parents->item; + parents = parents->next; + if (!(p->object.flags & UNINTERESTING)) { + p->object.flags |= UNINTERESTING; + changed = 1; + } + } + } + if (!changed) + break; + } +} + +static void show_one_commit(struct commit *commit, int no_name) +{ + char pretty[256], *cp; + struct commit_name *name = commit->object.util; + if (commit->object.parsed) + pretty_print_commit(CMIT_FMT_ONELINE, commit, ~0, + pretty, sizeof(pretty), 0); + else + strcpy(pretty, "(unavailable)"); + if (!strncmp(pretty, "[PATCH] ", 8)) + cp = pretty + 8; + else + cp = pretty; + + if (!no_name) { + if (name && name->head_name) { + printf("[%s", name->head_name); + if (name->generation) { + if (name->generation == 1) + printf("^"); + else + printf("~%d", name->generation); + } + printf("] "); + } + else + printf("[%s] ", + find_unique_abbrev(commit->object.sha1, 7)); + } + puts(cp); +} + +static char *ref_name[MAX_REVS + 1]; +static int ref_name_cnt; + +static const char *find_digit_prefix(const char *s, int *v) +{ + const char *p; + int ver; + char ch; + + for (p = s, ver = 0; + '0' <= (ch = *p) && ch <= '9'; + p++) + ver = ver * 10 + ch - '0'; + *v = ver; + return p; +} + + +static int version_cmp(const char *a, const char *b) +{ + while (1) { + int va, vb; + + a = find_digit_prefix(a, &va); + b = find_digit_prefix(b, &vb); + if (va != vb) + return va - vb; + + while (1) { + int ca = *a; + int cb = *b; + if ('0' <= ca && ca <= '9') + ca = 0; + if ('0' <= cb && cb <= '9') + cb = 0; + if (ca != cb) + return ca - cb; + if (!ca) + break; + a++; + b++; + } + if (!*a && !*b) + return 0; + } +} + +static int compare_ref_name(const void *a_, const void *b_) +{ + const char * const*a = a_, * const*b = b_; + return version_cmp(*a, *b); +} + +static void sort_ref_range(int bottom, int top) +{ + qsort(ref_name + bottom, top - bottom, sizeof(ref_name[0]), + compare_ref_name); +} + +static int append_ref(const char *refname, const unsigned char *sha1) +{ + struct commit *commit = lookup_commit_reference_gently(sha1, 1); + int i; + + if (!commit) + return 0; + /* Avoid adding the same thing twice */ + for (i = 0; i < ref_name_cnt; i++) + if (!strcmp(refname, ref_name[i])) + return 0; + + if (MAX_REVS <= ref_name_cnt) { + fprintf(stderr, "warning: ignoring %s; " + "cannot handle more than %d refs\n", + refname, MAX_REVS); + return 0; + } + ref_name[ref_name_cnt++] = strdup(refname); + ref_name[ref_name_cnt] = NULL; + return 0; +} + +static int append_head_ref(const char *refname, const unsigned char *sha1) +{ + unsigned char tmp[20]; + int ofs = 11; + if (strncmp(refname, "refs/heads/", ofs)) + return 0; + /* If both heads/foo and tags/foo exists, get_sha1 would + * get confused. + */ + if (get_sha1(refname + ofs, tmp) || memcmp(tmp, sha1, 20)) + ofs = 5; + return append_ref(refname + ofs, sha1); +} + +static int append_tag_ref(const char *refname, const unsigned char *sha1) +{ + if (strncmp(refname, "refs/tags/", 10)) + return 0; + return append_ref(refname + 5, sha1); +} + +static const char *match_ref_pattern = NULL; +static int match_ref_slash = 0; +static int count_slash(const char *s) +{ + int cnt = 0; + while (*s) + if (*s++ == '/') + cnt++; + return cnt; +} + +static int append_matching_ref(const char *refname, const unsigned char *sha1) +{ + /* we want to allow pattern hold/ to show all + * branches under refs/heads/hold/, and v0.99.9? to show + * refs/tags/v0.99.9a and friends. + */ + const char *tail; + int slash = count_slash(refname); + for (tail = refname; *tail && match_ref_slash < slash; ) + if (*tail++ == '/') + slash--; + if (!*tail) + return 0; + if (fnmatch(match_ref_pattern, tail, 0)) + return 0; + if (!strncmp("refs/heads/", refname, 11)) + return append_head_ref(refname, sha1); + if (!strncmp("refs/tags/", refname, 10)) + return append_tag_ref(refname, sha1); + return append_ref(refname, sha1); +} + +static void snarf_refs(int head, int tag) +{ + if (head) { + int orig_cnt = ref_name_cnt; + for_each_ref(append_head_ref); + sort_ref_range(orig_cnt, ref_name_cnt); + } + if (tag) { + int orig_cnt = ref_name_cnt; + for_each_ref(append_tag_ref); + sort_ref_range(orig_cnt, ref_name_cnt); + } +} + +static int rev_is_head(char *head_path, int headlen, char *name, + unsigned char *head_sha1, unsigned char *sha1) +{ + int namelen; + if ((!head_path[0]) || + (head_sha1 && sha1 && memcmp(head_sha1, sha1, 20))) + return 0; + namelen = strlen(name); + if ((headlen < namelen) || + memcmp(head_path + headlen - namelen, name, namelen)) + return 0; + if (headlen == namelen || + head_path[headlen - namelen - 1] == '/') + return 1; + return 0; +} + +static int show_merge_base(struct commit_list *seen, int num_rev) +{ + int all_mask = ((1u << (REV_SHIFT + num_rev)) - 1); + int all_revs = all_mask & ~((1u << REV_SHIFT) - 1); + int exit_status = 1; + + while (seen) { + struct commit *commit = pop_one_commit(&seen); + int flags = commit->object.flags & all_mask; + if (!(flags & UNINTERESTING) && + ((flags & all_revs) == all_revs)) { + puts(sha1_to_hex(commit->object.sha1)); + exit_status = 0; + commit->object.flags |= UNINTERESTING; + } + } + return exit_status; +} + +static int show_independent(struct commit **rev, + int num_rev, + char **ref_name, + unsigned int *rev_mask) +{ + int i; + + for (i = 0; i < num_rev; i++) { + struct commit *commit = rev[i]; + unsigned int flag = rev_mask[i]; + + if (commit->object.flags == flag) + puts(sha1_to_hex(commit->object.sha1)); + commit->object.flags |= UNINTERESTING; + } + return 0; +} + +static void append_one_rev(const char *av) +{ + unsigned char revkey[20]; + if (!get_sha1(av, revkey)) { + append_ref(av, revkey); + return; + } + if (strchr(av, '*') || strchr(av, '?') || strchr(av, '[')) { + /* glob style match */ + int saved_matches = ref_name_cnt; + match_ref_pattern = av; + match_ref_slash = count_slash(av); + for_each_ref(append_matching_ref); + if (saved_matches == ref_name_cnt && + ref_name_cnt < MAX_REVS) + error("no matching refs with %s", av); + if (saved_matches + 1 < ref_name_cnt) + sort_ref_range(saved_matches, ref_name_cnt); + return; + } + die("bad sha1 reference %s", av); +} + +static int git_show_branch_config(const char *var, const char *value) +{ + if (!strcmp(var, "showbranch.default")) { + if (default_alloc <= default_num + 1) { + default_alloc = default_alloc * 3 / 2 + 20; + default_arg = xrealloc(default_arg, sizeof *default_arg * default_alloc); + } + default_arg[default_num++] = strdup(value); + default_arg[default_num] = NULL; + return 0; + } + + return git_default_config(var, value); +} + +static int omit_in_dense(struct commit *commit, struct commit **rev, int n) +{ + /* If the commit is tip of the named branches, do not + * omit it. + * Otherwise, if it is a merge that is reachable from only one + * tip, it is not that interesting. + */ + int i, flag, count; + for (i = 0; i < n; i++) + if (rev[i] == commit) + return 0; + flag = commit->object.flags; + for (i = count = 0; i < n; i++) { + if (flag & (1u << (i + REV_SHIFT))) + count++; + } + if (count == 1) + return 1; + return 0; +} + +int cmd_show_branch(int ac, const char **av, char **envp) +{ + struct commit *rev[MAX_REVS], *commit; + struct commit_list *list = NULL, *seen = NULL; + unsigned int rev_mask[MAX_REVS]; + int num_rev, i, extra = 0; + int all_heads = 0, all_tags = 0; + int all_mask, all_revs; + int lifo = 1; + char head_path[128]; + const char *head_path_p; + int head_path_len; + unsigned char head_sha1[20]; + int merge_base = 0; + int independent = 0; + int no_name = 0; + int sha1_name = 0; + int shown_merge_point = 0; + int with_current_branch = 0; + int head_at = -1; + int topics = 0; + int dense = 1; + + setup_git_directory(); + git_config(git_show_branch_config); + + /* If nothing is specified, try the default first */ + if (ac == 1 && default_num) { + ac = default_num + 1; + av = default_arg - 1; /* ick; we would not address av[0] */ + } + + while (1 < ac && av[1][0] == '-') { + const char *arg = av[1]; + if (!strcmp(arg, "--")) { + ac--; av++; + break; + } + else if (!strcmp(arg, "--all")) + all_heads = all_tags = 1; + else if (!strcmp(arg, "--heads")) + all_heads = 1; + else if (!strcmp(arg, "--tags")) + all_tags = 1; + else if (!strcmp(arg, "--more")) + extra = 1; + else if (!strcmp(arg, "--list")) + extra = -1; + else if (!strcmp(arg, "--no-name")) + no_name = 1; + else if (!strcmp(arg, "--current")) + with_current_branch = 1; + else if (!strcmp(arg, "--sha1-name")) + sha1_name = 1; + else if (!strncmp(arg, "--more=", 7)) + extra = atoi(arg + 7); + else if (!strcmp(arg, "--merge-base")) + merge_base = 1; + else if (!strcmp(arg, "--independent")) + independent = 1; + else if (!strcmp(arg, "--topo-order")) + lifo = 1; + else if (!strcmp(arg, "--topics")) + topics = 1; + else if (!strcmp(arg, "--sparse")) + dense = 0; + else if (!strcmp(arg, "--date-order")) + lifo = 0; + else + usage(show_branch_usage); + ac--; av++; + } + ac--; av++; + + /* Only one of these is allowed */ + if (1 < independent + merge_base + (extra != 0)) + usage(show_branch_usage); + + /* If nothing is specified, show all branches by default */ + if (ac + all_heads + all_tags == 0) + all_heads = 1; + + if (all_heads + all_tags) + snarf_refs(all_heads, all_tags); + while (0 < ac) { + append_one_rev(*av); + ac--; av++; + } + + head_path_p = resolve_ref(git_path("HEAD"), head_sha1, 1); + if (head_path_p) { + head_path_len = strlen(head_path_p); + memcpy(head_path, head_path_p, head_path_len + 1); + } + else { + head_path_len = 0; + head_path[0] = 0; + } + + if (with_current_branch && head_path_p) { + int has_head = 0; + for (i = 0; !has_head && i < ref_name_cnt; i++) { + /* We are only interested in adding the branch + * HEAD points at. + */ + if (rev_is_head(head_path, + head_path_len, + ref_name[i], + head_sha1, NULL)) + has_head++; + } + if (!has_head) { + int pfxlen = strlen(git_path("refs/heads/")); + append_one_rev(head_path + pfxlen); + } + } + + if (!ref_name_cnt) { + fprintf(stderr, "No revs to be shown.\n"); + exit(0); + } + + for (num_rev = 0; ref_name[num_rev]; num_rev++) { + unsigned char revkey[20]; + unsigned int flag = 1u << (num_rev + REV_SHIFT); + + if (MAX_REVS <= num_rev) + die("cannot handle more than %d revs.", MAX_REVS); + if (get_sha1(ref_name[num_rev], revkey)) + die("'%s' is not a valid ref.", ref_name[num_rev]); + commit = lookup_commit_reference(revkey); + if (!commit) + die("cannot find commit %s (%s)", + ref_name[num_rev], revkey); + parse_commit(commit); + mark_seen(commit, &seen); + + /* rev#0 uses bit REV_SHIFT, rev#1 uses bit REV_SHIFT+1, + * and so on. REV_SHIFT bits from bit 0 are used for + * internal bookkeeping. + */ + commit->object.flags |= flag; + if (commit->object.flags == flag) + insert_by_date(commit, &list); + rev[num_rev] = commit; + } + for (i = 0; i < num_rev; i++) + rev_mask[i] = rev[i]->object.flags; + + if (0 <= extra) + join_revs(&list, &seen, num_rev, extra); + + if (merge_base) + return show_merge_base(seen, num_rev); + + if (independent) + return show_independent(rev, num_rev, ref_name, rev_mask); + + /* Show list; --more=-1 means list-only */ + if (1 < num_rev || extra < 0) { + for (i = 0; i < num_rev; i++) { + int j; + int is_head = rev_is_head(head_path, + head_path_len, + ref_name[i], + head_sha1, + rev[i]->object.sha1); + if (extra < 0) + printf("%c [%s] ", + is_head ? '*' : ' ', ref_name[i]); + else { + for (j = 0; j < i; j++) + putchar(' '); + printf("%c [%s] ", + is_head ? '*' : '!', ref_name[i]); + } + /* header lines never need name */ + show_one_commit(rev[i], 1); + if (is_head) + head_at = i; + } + if (0 <= extra) { + for (i = 0; i < num_rev; i++) + putchar('-'); + putchar('\n'); + } + } + if (extra < 0) + exit(0); + + /* Sort topologically */ + sort_in_topological_order(&seen, lifo); + + /* Give names to commits */ + if (!sha1_name && !no_name) + name_commits(seen, rev, ref_name, num_rev); + + all_mask = ((1u << (REV_SHIFT + num_rev)) - 1); + all_revs = all_mask & ~((1u << REV_SHIFT) - 1); + + while (seen) { + struct commit *commit = pop_one_commit(&seen); + int this_flag = commit->object.flags; + int is_merge_point = ((this_flag & all_revs) == all_revs); + + shown_merge_point |= is_merge_point; + + if (1 < num_rev) { + int is_merge = !!(commit->parents && + commit->parents->next); + if (topics && + !is_merge_point && + (this_flag & (1u << REV_SHIFT))) + continue; + if (dense && is_merge && + omit_in_dense(commit, rev, num_rev)) + continue; + for (i = 0; i < num_rev; i++) { + int mark; + if (!(this_flag & (1u << (i + REV_SHIFT)))) + mark = ' '; + else if (is_merge) + mark = '-'; + else if (i == head_at) + mark = '*'; + else + mark = '+'; + putchar(mark); + } + putchar(' '); + } + show_one_commit(commit, no_name); + + if (shown_merge_point && --extra < 0) + break; + } + return 0; +} diff --git a/builtin.h b/builtin.h index d6ff88ebc1..01882ec93d 100644 --- a/builtin.h +++ b/builtin.h @@ -33,5 +33,6 @@ extern int cmd_tar_tree(int argc, const char **argv, char **envp); extern int cmd_read_tree(int argc, const char **argv, char **envp); extern int cmd_commit_tree(int argc, const char **argv, char **envp); extern int cmd_apply(int argc, const char **argv, char **envp); +extern int cmd_show_branch(int argc, const char **argv, char **envp); #endif diff --git a/git.c b/git.c index f44e08b9ce..d29505c4c9 100644 --- a/git.c +++ b/git.c @@ -58,7 +58,8 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "tar-tree", cmd_tar_tree }, { "read-tree", cmd_read_tree }, { "commit-tree", cmd_commit_tree }, - { "apply", cmd_apply } + { "apply", cmd_apply }, + { "show-branch", cmd_show_branch } }; int i; diff --git a/show-branch.c b/show-branch.c deleted file mode 100644 index 268c57b180..0000000000 --- a/show-branch.c +++ /dev/null @@ -1,788 +0,0 @@ -#include -#include -#include "cache.h" -#include "commit.h" -#include "refs.h" - -static const char show_branch_usage[] = -"git-show-branch [--dense] [--current] [--all] [--heads] [--tags] [--topo-order] [--more=count | --list | --independent | --merge-base ] [--topics] [...]"; - -static int default_num = 0; -static int default_alloc = 0; -static 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); - else - strcpy(pretty, "(unavailable)"); - if (!strncmp(pretty, "[PATCH] ", 8)) - cp = pretty + 8; - else - cp = pretty; - - if (!no_name) { - if (name && name->head_name) { - printf("[%s", name->head_name); - if (name->generation) { - if (name->generation == 1) - printf("^"); - else - printf("~%d", name->generation); - } - printf("] "); - } - else - printf("[%s] ", - find_unique_abbrev(commit->object.sha1, 7)); - } - puts(cp); -} - -static char *ref_name[MAX_REVS + 1]; -static int ref_name_cnt; - -static const char *find_digit_prefix(const char *s, int *v) -{ - const char *p; - int ver; - char ch; - - for (p = s, ver = 0; - '0' <= (ch = *p) && ch <= '9'; - p++) - ver = ver * 10 + ch - '0'; - *v = ver; - return p; -} - - -static int version_cmp(const char *a, const char *b) -{ - while (1) { - int va, vb; - - a = find_digit_prefix(a, &va); - b = find_digit_prefix(b, &vb); - if (va != vb) - return va - vb; - - while (1) { - int ca = *a; - int cb = *b; - if ('0' <= ca && ca <= '9') - ca = 0; - if ('0' <= cb && cb <= '9') - cb = 0; - if (ca != cb) - return ca - cb; - if (!ca) - break; - a++; - b++; - } - if (!*a && !*b) - return 0; - } -} - -static int compare_ref_name(const void *a_, const void *b_) -{ - const char * const*a = a_, * const*b = b_; - return version_cmp(*a, *b); -} - -static void sort_ref_range(int bottom, int top) -{ - qsort(ref_name + bottom, top - bottom, sizeof(ref_name[0]), - compare_ref_name); -} - -static int append_ref(const char *refname, const unsigned char *sha1) -{ - struct commit *commit = lookup_commit_reference_gently(sha1, 1); - int i; - - if (!commit) - return 0; - /* Avoid adding the same thing twice */ - for (i = 0; i < ref_name_cnt; i++) - if (!strcmp(refname, ref_name[i])) - return 0; - - if (MAX_REVS <= ref_name_cnt) { - fprintf(stderr, "warning: ignoring %s; " - "cannot handle more than %d refs\n", - refname, MAX_REVS); - return 0; - } - ref_name[ref_name_cnt++] = strdup(refname); - ref_name[ref_name_cnt] = NULL; - return 0; -} - -static int append_head_ref(const char *refname, const unsigned char *sha1) -{ - unsigned char tmp[20]; - int ofs = 11; - if (strncmp(refname, "refs/heads/", ofs)) - return 0; - /* If both heads/foo and tags/foo exists, get_sha1 would - * get confused. - */ - if (get_sha1(refname + ofs, tmp) || memcmp(tmp, sha1, 20)) - ofs = 5; - return append_ref(refname + ofs, sha1); -} - -static int append_tag_ref(const char *refname, const unsigned char *sha1) -{ - if (strncmp(refname, "refs/tags/", 10)) - return 0; - return append_ref(refname + 5, sha1); -} - -static const char *match_ref_pattern = NULL; -static int match_ref_slash = 0; -static int count_slash(const char *s) -{ - int cnt = 0; - while (*s) - if (*s++ == '/') - cnt++; - return cnt; -} - -static int append_matching_ref(const char *refname, const unsigned char *sha1) -{ - /* we want to allow pattern hold/ to show all - * branches under refs/heads/hold/, and v0.99.9? to show - * refs/tags/v0.99.9a and friends. - */ - const char *tail; - int slash = count_slash(refname); - for (tail = refname; *tail && match_ref_slash < slash; ) - if (*tail++ == '/') - slash--; - if (!*tail) - return 0; - if (fnmatch(match_ref_pattern, tail, 0)) - return 0; - if (!strncmp("refs/heads/", refname, 11)) - return append_head_ref(refname, sha1); - if (!strncmp("refs/tags/", refname, 10)) - return append_tag_ref(refname, sha1); - return append_ref(refname, sha1); -} - -static void snarf_refs(int head, int tag) -{ - if (head) { - int orig_cnt = ref_name_cnt; - for_each_ref(append_head_ref); - sort_ref_range(orig_cnt, ref_name_cnt); - } - if (tag) { - int orig_cnt = ref_name_cnt; - for_each_ref(append_tag_ref); - sort_ref_range(orig_cnt, ref_name_cnt); - } -} - -static int rev_is_head(char *head_path, int headlen, char *name, - unsigned char *head_sha1, unsigned char *sha1) -{ - int namelen; - if ((!head_path[0]) || - (head_sha1 && sha1 && memcmp(head_sha1, sha1, 20))) - return 0; - namelen = strlen(name); - if ((headlen < namelen) || - memcmp(head_path + headlen - namelen, name, namelen)) - return 0; - if (headlen == namelen || - head_path[headlen - namelen - 1] == '/') - return 1; - return 0; -} - -static int show_merge_base(struct commit_list *seen, int num_rev) -{ - int all_mask = ((1u << (REV_SHIFT + num_rev)) - 1); - int all_revs = all_mask & ~((1u << REV_SHIFT) - 1); - int exit_status = 1; - - while (seen) { - struct commit *commit = pop_one_commit(&seen); - int flags = commit->object.flags & all_mask; - if (!(flags & UNINTERESTING) && - ((flags & all_revs) == all_revs)) { - puts(sha1_to_hex(commit->object.sha1)); - exit_status = 0; - commit->object.flags |= UNINTERESTING; - } - } - return exit_status; -} - -static int show_independent(struct commit **rev, - int num_rev, - char **ref_name, - unsigned int *rev_mask) -{ - int i; - - for (i = 0; i < num_rev; i++) { - struct commit *commit = rev[i]; - unsigned int flag = rev_mask[i]; - - if (commit->object.flags == flag) - puts(sha1_to_hex(commit->object.sha1)); - commit->object.flags |= UNINTERESTING; - } - return 0; -} - -static void append_one_rev(const char *av) -{ - unsigned char revkey[20]; - if (!get_sha1(av, revkey)) { - append_ref(av, revkey); - return; - } - if (strchr(av, '*') || strchr(av, '?') || strchr(av, '[')) { - /* glob style match */ - int saved_matches = ref_name_cnt; - match_ref_pattern = av; - match_ref_slash = count_slash(av); - for_each_ref(append_matching_ref); - if (saved_matches == ref_name_cnt && - ref_name_cnt < MAX_REVS) - error("no matching refs with %s", av); - if (saved_matches + 1 < ref_name_cnt) - sort_ref_range(saved_matches, ref_name_cnt); - return; - } - die("bad sha1 reference %s", av); -} - -static int git_show_branch_config(const char *var, const char *value) -{ - if (!strcmp(var, "showbranch.default")) { - if (default_alloc <= default_num + 1) { - default_alloc = default_alloc * 3 / 2 + 20; - default_arg = xrealloc(default_arg, sizeof *default_arg * default_alloc); - } - default_arg[default_num++] = strdup(value); - default_arg[default_num] = NULL; - return 0; - } - - return git_default_config(var, value); -} - -static int omit_in_dense(struct commit *commit, struct commit **rev, int n) -{ - /* If the commit is tip of the named branches, do not - * omit it. - * Otherwise, if it is a merge that is reachable from only one - * tip, it is not that interesting. - */ - int i, flag, count; - for (i = 0; i < n; i++) - if (rev[i] == commit) - return 0; - flag = commit->object.flags; - for (i = count = 0; i < n; i++) { - if (flag & (1u << (i + REV_SHIFT))) - count++; - } - if (count == 1) - return 1; - return 0; -} - -int main(int ac, char **av) -{ - 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] == '-') { - 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; -} -- cgit v1.3 From e8cc9cd98e2ecd7fd8bb03e725d470405c8e2b94 Mon Sep 17 00:00:00 2001 From: Peter Eriksen Date: Tue, 23 May 2006 14:15:36 +0200 Subject: Builtin git-diff-files, git-diff-index, git-diff-stages, and git-diff-tree. Signed-off-by: Peter Eriksen Signed-off-by: Junio C Hamano --- Makefile | 12 ++-- builtin-diff-files.c | 55 +++++++++++++++++++ builtin-diff-index.c | 39 +++++++++++++ builtin-diff-stages.c | 105 +++++++++++++++++++++++++++++++++++ builtin-diff-tree.c | 148 ++++++++++++++++++++++++++++++++++++++++++++++++++ builtin.h | 5 ++ diff-files.c | 54 ------------------ diff-index.c | 38 ------------- diff-stages.c | 104 ----------------------------------- diff-tree.c | 147 ------------------------------------------------- git.c | 6 +- 11 files changed, 363 insertions(+), 350 deletions(-) create mode 100644 builtin-diff-files.c create mode 100644 builtin-diff-index.c create mode 100644 builtin-diff-stages.c create mode 100644 builtin-diff-tree.c delete mode 100644 diff-files.c delete mode 100644 diff-index.c delete mode 100644 diff-stages.c delete mode 100644 diff-tree.c (limited to 'Makefile') diff --git a/Makefile b/Makefile index 69377e37b4..fc5f98b908 100644 --- a/Makefile +++ b/Makefile @@ -151,9 +151,7 @@ SIMPLE_PROGRAMS = \ PROGRAMS = \ git-cat-file$X \ git-checkout-index$X git-clone-pack$X \ - git-convert-objects$X git-diff-files$X \ - git-diff-index$X git-diff-stages$X \ - git-diff-tree$X git-fetch-pack$X git-fsck-objects$X \ + git-convert-objects$X git-fetch-pack$X git-fsck-objects$X \ git-hash-object$X git-index-pack$X git-local-fetch$X \ git-mailinfo$X git-merge-base$X \ git-merge-index$X git-mktag$X git-mktree$X git-pack-objects$X git-patch-id$X \ @@ -173,7 +171,8 @@ BUILT_INS = git-log$X git-whatchanged$X git-show$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-tar-tree$X git-read-tree$X git-commit-tree$X \ - git-apply$X git-show-branch$X + git-apply$X git-show-branch$X git-diff-files$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) @@ -223,8 +222,9 @@ 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-tar-tree.o builtin-read-tree.o builtin-commit-tree.o \ - builtin-apply.o builtin-show-branch.o + builtin-tar-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 GITLIBS = $(LIB_FILE) $(XDIFF_LIB) LIBS = $(GITLIBS) -lz diff --git a/builtin-diff-files.c b/builtin-diff-files.c new file mode 100644 index 0000000000..cebda828ee --- /dev/null +++ b/builtin-diff-files.c @@ -0,0 +1,55 @@ +/* + * GIT - The information manager from hell + * + * Copyright (C) Linus Torvalds, 2005 + */ +#include "cache.h" +#include "diff.h" +#include "commit.h" +#include "revision.h" +#include "builtin.h" + +static const char diff_files_usage[] = +"git-diff-files [-q] [-0/-1/2/3 |-c|--cc] [] [...]" +COMMON_DIFF_OPTIONS_HELP; + +int cmd_diff_files(int argc, const char **argv, char **envp) +{ + struct rev_info rev; + int silent = 0; + + git_config(git_diff_config); + init_revisions(&rev); + rev.abbrev = 0; + + argc = setup_revisions(argc, argv, &rev, NULL); + while (1 < argc && argv[1][0] == '-') { + if (!strcmp(argv[1], "--base")) + rev.max_count = 1; + else if (!strcmp(argv[1], "--ours")) + rev.max_count = 2; + else if (!strcmp(argv[1], "--theirs")) + rev.max_count = 3; + else if (!strcmp(argv[1], "-q")) + silent = 1; + else + usage(diff_files_usage); + argv++; argc--; + } + /* + * Make sure there are NO revision (i.e. pending object) parameter, + * rev.max_count is reasonable (0 <= n <= 3), + * there is no other revision filtering parameters. + */ + if (rev.pending_objects || + rev.min_age != -1 || rev.max_age != -1) + usage(diff_files_usage); + /* + * Backward compatibility wart - "diff-files -s" used to + * defeat the common diff option "-s" which asked for + * DIFF_FORMAT_NO_OUTPUT. + */ + if (rev.diffopt.output_format == DIFF_FORMAT_NO_OUTPUT) + rev.diffopt.output_format = DIFF_FORMAT_RAW; + return run_diff_files(&rev, silent); +} diff --git a/builtin-diff-index.c b/builtin-diff-index.c new file mode 100644 index 0000000000..1958580d82 --- /dev/null +++ b/builtin-diff-index.c @@ -0,0 +1,39 @@ +#include "cache.h" +#include "diff.h" +#include "commit.h" +#include "revision.h" +#include "builtin.h" + +static const char diff_cache_usage[] = +"git-diff-index [-m] [--cached] " +"[] [...]" +COMMON_DIFF_OPTIONS_HELP; + +int cmd_diff_index(int argc, const char **argv, char **envp) +{ + struct rev_info rev; + int cached = 0; + int i; + + git_config(git_diff_config); + init_revisions(&rev); + rev.abbrev = 0; + + argc = setup_revisions(argc, argv, &rev, NULL); + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + + if (!strcmp(arg, "--cached")) + cached = 1; + else + usage(diff_cache_usage); + } + /* + * Make sure there is one revision (i.e. pending object), + * and there is no revision filtering parameters. + */ + if (!rev.pending_objects || rev.pending_objects->next || + rev.max_count != -1 || rev.min_age != -1 || rev.max_age != -1) + usage(diff_cache_usage); + return run_diff_index(&rev, cached); +} diff --git a/builtin-diff-stages.c b/builtin-diff-stages.c new file mode 100644 index 0000000000..7c157ca889 --- /dev/null +++ b/builtin-diff-stages.c @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2005 Junio C Hamano + */ + +#include "cache.h" +#include "diff.h" +#include "builtin.h" + +static struct diff_options diff_options; + +static const char diff_stages_usage[] = +"git-diff-stages [] [...]" +COMMON_DIFF_OPTIONS_HELP; + +static void diff_stages(int stage1, int stage2, const char **pathspec) +{ + int i = 0; + while (i < active_nr) { + struct cache_entry *ce, *stages[4] = { NULL, }; + struct cache_entry *one, *two; + const char *name; + int len, skip; + + ce = active_cache[i]; + skip = !ce_path_match(ce, pathspec); + len = ce_namelen(ce); + name = ce->name; + for (;;) { + int stage = ce_stage(ce); + stages[stage] = ce; + if (active_nr <= ++i) + break; + ce = active_cache[i]; + if (ce_namelen(ce) != len || + memcmp(name, ce->name, len)) + break; + } + one = stages[stage1]; + two = stages[stage2]; + + if (skip || (!one && !two)) + continue; + if (!one) + diff_addremove(&diff_options, '+', ntohl(two->ce_mode), + two->sha1, name, NULL); + else if (!two) + diff_addremove(&diff_options, '-', ntohl(one->ce_mode), + one->sha1, name, NULL); + else if (memcmp(one->sha1, two->sha1, 20) || + (one->ce_mode != two->ce_mode) || + diff_options.find_copies_harder) + diff_change(&diff_options, + ntohl(one->ce_mode), ntohl(two->ce_mode), + one->sha1, two->sha1, name, NULL); + } +} + +int cmd_diff_stages(int ac, const char **av, char **envp) +{ + int stage1, stage2; + const char *prefix = setup_git_directory(); + const char **pathspec = NULL; + + git_config(git_diff_config); + read_cache(); + diff_setup(&diff_options); + while (1 < ac && av[1][0] == '-') { + const char *arg = av[1]; + if (!strcmp(arg, "-r")) + ; /* as usual */ + else { + int diff_opt_cnt; + diff_opt_cnt = diff_opt_parse(&diff_options, + av+1, ac-1); + if (diff_opt_cnt < 0) + usage(diff_stages_usage); + else if (diff_opt_cnt) { + av += diff_opt_cnt; + ac -= diff_opt_cnt; + continue; + } + else + usage(diff_stages_usage); + } + ac--; av++; + } + + if (ac < 3 || + sscanf(av[1], "%d", &stage1) != 1 || + ! (0 <= stage1 && stage1 <= 3) || + sscanf(av[2], "%d", &stage2) != 1 || + ! (0 <= stage2 && stage2 <= 3)) + usage(diff_stages_usage); + + av += 3; /* The rest from av[0] are for paths restriction. */ + pathspec = get_pathspec(prefix, av); + + if (diff_setup_done(&diff_options) < 0) + usage(diff_stages_usage); + + diff_stages(stage1, stage2, pathspec); + diffcore_std(&diff_options); + diff_flush(&diff_options); + return 0; +} diff --git a/builtin-diff-tree.c b/builtin-diff-tree.c new file mode 100644 index 0000000000..cc53b81ac4 --- /dev/null +++ b/builtin-diff-tree.c @@ -0,0 +1,148 @@ +#include "cache.h" +#include "diff.h" +#include "commit.h" +#include "log-tree.h" +#include "builtin.h" + +static struct rev_info log_tree_opt; + +static int diff_tree_commit_sha1(const unsigned char *sha1) +{ + struct commit *commit = lookup_commit_reference(sha1); + if (!commit) + return -1; + return log_tree_commit(&log_tree_opt, commit); +} + +static int diff_tree_stdin(char *line) +{ + int len = strlen(line); + unsigned char sha1[20]; + struct commit *commit; + + if (!len || line[len-1] != '\n') + return -1; + line[len-1] = 0; + if (get_sha1_hex(line, sha1)) + return -1; + commit = lookup_commit(sha1); + if (!commit || parse_commit(commit)) + return -1; + if (isspace(line[40]) && !get_sha1_hex(line+41, sha1)) { + /* Graft the fake parents locally to the commit */ + int pos = 41; + struct commit_list **pptr, *parents; + + /* Free the real parent list */ + for (parents = commit->parents; parents; ) { + struct commit_list *tmp = parents->next; + free(parents); + parents = tmp; + } + commit->parents = NULL; + pptr = &(commit->parents); + while (line[pos] && !get_sha1_hex(line + pos, sha1)) { + struct commit *parent = lookup_commit(sha1); + if (parent) { + pptr = &commit_list_insert(parent, pptr)->next; + } + pos += 41; + } + } + return log_tree_commit(&log_tree_opt, commit); +} + +static const char diff_tree_usage[] = +"git-diff-tree [--stdin] [-m] [-c] [--cc] [-s] [-v] [--pretty] [-t] [-r] [--root] " +"[] [] [...]\n" +" -r diff recursively\n" +" --root include the initial commit as diff against /dev/null\n" +COMMON_DIFF_OPTIONS_HELP; + +int cmd_diff_tree(int argc, const char **argv, char **envp) +{ + int nr_sha1; + char line[1000]; + struct object *tree1, *tree2; + static struct rev_info *opt = &log_tree_opt; + struct object_list *list; + int read_stdin = 0; + + git_config(git_diff_config); + nr_sha1 = 0; + init_revisions(opt); + opt->abbrev = 0; + opt->diff = 1; + argc = setup_revisions(argc, argv, opt, NULL); + + while (--argc > 0) { + const char *arg = *++argv; + + if (!strcmp(arg, "--stdin")) { + read_stdin = 1; + continue; + } + usage(diff_tree_usage); + } + + /* + * NOTE! "setup_revisions()" will have inserted the revisions + * it parsed in reverse order. So if you do + * + * git-diff-tree a b + * + * the commit list will be "b" -> "a" -> NULL, so we reverse + * the order of the objects if the first one is not marked + * UNINTERESTING. + */ + nr_sha1 = 0; + list = opt->pending_objects; + if (list) { + nr_sha1++; + tree1 = list->item; + list = list->next; + if (list) { + nr_sha1++; + tree2 = tree1; + tree1 = list->item; + if (list->next) + usage(diff_tree_usage); + /* Switch them around if the second one was uninteresting.. */ + if (tree2->flags & UNINTERESTING) { + struct object *tmp = tree2; + tree2 = tree1; + tree1 = tmp; + } + } + } + + switch (nr_sha1) { + case 0: + if (!read_stdin) + usage(diff_tree_usage); + break; + case 1: + diff_tree_commit_sha1(tree1->sha1); + break; + case 2: + diff_tree_sha1(tree1->sha1, + tree2->sha1, + "", &opt->diffopt); + log_tree_diff_flush(opt); + break; + } + + if (!read_stdin) + return 0; + + if (opt->diffopt.detect_rename) + opt->diffopt.setup |= (DIFF_SETUP_USE_SIZE_CACHE | + DIFF_SETUP_USE_CACHE); + while (fgets(line, sizeof(line), stdin)) + if (line[0] == '\n') + fflush(stdout); + else + diff_tree_stdin(line); + + return 0; +} diff --git a/builtin.h b/builtin.h index 01882ec93d..7620984624 100644 --- a/builtin.h +++ b/builtin.h @@ -34,5 +34,10 @@ extern int cmd_read_tree(int argc, const char **argv, char **envp); extern int cmd_commit_tree(int argc, const char **argv, char **envp); extern int cmd_apply(int argc, const char **argv, char **envp); extern int cmd_show_branch(int argc, const char **argv, char **envp); +extern int cmd_diff_files(int argc, const char **argv, char **envp); +extern int cmd_diff_index(int argc, const char **argv, char **envp); +extern int cmd_diff_stages(int argc, const char **argv, char **envp); +extern int cmd_diff_tree(int argc, const char **argv, char **envp); + #endif diff --git a/diff-files.c b/diff-files.c deleted file mode 100644 index b9d193d506..0000000000 --- a/diff-files.c +++ /dev/null @@ -1,54 +0,0 @@ -/* - * GIT - The information manager from hell - * - * Copyright (C) Linus Torvalds, 2005 - */ -#include "cache.h" -#include "diff.h" -#include "commit.h" -#include "revision.h" - -static const char diff_files_usage[] = -"git-diff-files [-q] [-0/-1/2/3 |-c|--cc] [] [...]" -COMMON_DIFF_OPTIONS_HELP; - -int main(int argc, const char **argv) -{ - struct rev_info rev; - int silent = 0; - - git_config(git_diff_config); - init_revisions(&rev); - rev.abbrev = 0; - - argc = setup_revisions(argc, argv, &rev, NULL); - while (1 < argc && argv[1][0] == '-') { - if (!strcmp(argv[1], "--base")) - rev.max_count = 1; - else if (!strcmp(argv[1], "--ours")) - rev.max_count = 2; - else if (!strcmp(argv[1], "--theirs")) - rev.max_count = 3; - else if (!strcmp(argv[1], "-q")) - silent = 1; - else - usage(diff_files_usage); - argv++; argc--; - } - /* - * Make sure there are NO revision (i.e. pending object) parameter, - * rev.max_count is reasonable (0 <= n <= 3), - * there is no other revision filtering parameters. - */ - if (rev.pending_objects || - rev.min_age != -1 || rev.max_age != -1) - usage(diff_files_usage); - /* - * Backward compatibility wart - "diff-files -s" used to - * defeat the common diff option "-s" which asked for - * DIFF_FORMAT_NO_OUTPUT. - */ - if (rev.diffopt.output_format == DIFF_FORMAT_NO_OUTPUT) - rev.diffopt.output_format = DIFF_FORMAT_RAW; - return run_diff_files(&rev, silent); -} diff --git a/diff-index.c b/diff-index.c deleted file mode 100644 index 8c9f60173b..0000000000 --- a/diff-index.c +++ /dev/null @@ -1,38 +0,0 @@ -#include "cache.h" -#include "diff.h" -#include "commit.h" -#include "revision.h" - -static const char diff_cache_usage[] = -"git-diff-index [-m] [--cached] " -"[] [...]" -COMMON_DIFF_OPTIONS_HELP; - -int main(int argc, const char **argv) -{ - struct rev_info rev; - int cached = 0; - int i; - - git_config(git_diff_config); - init_revisions(&rev); - rev.abbrev = 0; - - argc = setup_revisions(argc, argv, &rev, NULL); - for (i = 1; i < argc; i++) { - const char *arg = argv[i]; - - if (!strcmp(arg, "--cached")) - cached = 1; - else - usage(diff_cache_usage); - } - /* - * Make sure there is one revision (i.e. pending object), - * and there is no revision filtering parameters. - */ - if (!rev.pending_objects || rev.pending_objects->next || - rev.max_count != -1 || rev.min_age != -1 || rev.max_age != -1) - usage(diff_cache_usage); - return run_diff_index(&rev, cached); -} diff --git a/diff-stages.c b/diff-stages.c deleted file mode 100644 index dcd20e79e4..0000000000 --- a/diff-stages.c +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (c) 2005 Junio C Hamano - */ - -#include "cache.h" -#include "diff.h" - -static struct diff_options diff_options; - -static const char diff_stages_usage[] = -"git-diff-stages [] [...]" -COMMON_DIFF_OPTIONS_HELP; - -static void diff_stages(int stage1, int stage2, const char **pathspec) -{ - int i = 0; - while (i < active_nr) { - struct cache_entry *ce, *stages[4] = { NULL, }; - struct cache_entry *one, *two; - const char *name; - int len, skip; - - ce = active_cache[i]; - skip = !ce_path_match(ce, pathspec); - len = ce_namelen(ce); - name = ce->name; - for (;;) { - int stage = ce_stage(ce); - stages[stage] = ce; - if (active_nr <= ++i) - break; - ce = active_cache[i]; - if (ce_namelen(ce) != len || - memcmp(name, ce->name, len)) - break; - } - one = stages[stage1]; - two = stages[stage2]; - - if (skip || (!one && !two)) - continue; - if (!one) - diff_addremove(&diff_options, '+', ntohl(two->ce_mode), - two->sha1, name, NULL); - else if (!two) - diff_addremove(&diff_options, '-', ntohl(one->ce_mode), - one->sha1, name, NULL); - else if (memcmp(one->sha1, two->sha1, 20) || - (one->ce_mode != two->ce_mode) || - diff_options.find_copies_harder) - diff_change(&diff_options, - ntohl(one->ce_mode), ntohl(two->ce_mode), - one->sha1, two->sha1, name, NULL); - } -} - -int main(int ac, const char **av) -{ - int stage1, stage2; - const char *prefix = setup_git_directory(); - const char **pathspec = NULL; - - git_config(git_diff_config); - read_cache(); - diff_setup(&diff_options); - while (1 < ac && av[1][0] == '-') { - const char *arg = av[1]; - if (!strcmp(arg, "-r")) - ; /* as usual */ - else { - int diff_opt_cnt; - diff_opt_cnt = diff_opt_parse(&diff_options, - av+1, ac-1); - if (diff_opt_cnt < 0) - usage(diff_stages_usage); - else if (diff_opt_cnt) { - av += diff_opt_cnt; - ac -= diff_opt_cnt; - continue; - } - else - usage(diff_stages_usage); - } - ac--; av++; - } - - if (ac < 3 || - sscanf(av[1], "%d", &stage1) != 1 || - ! (0 <= stage1 && stage1 <= 3) || - sscanf(av[2], "%d", &stage2) != 1 || - ! (0 <= stage2 && stage2 <= 3)) - usage(diff_stages_usage); - - av += 3; /* The rest from av[0] are for paths restriction. */ - pathspec = get_pathspec(prefix, av); - - if (diff_setup_done(&diff_options) < 0) - usage(diff_stages_usage); - - diff_stages(stage1, stage2, pathspec); - diffcore_std(&diff_options); - diff_flush(&diff_options); - return 0; -} diff --git a/diff-tree.c b/diff-tree.c deleted file mode 100644 index 69bb74b310..0000000000 --- a/diff-tree.c +++ /dev/null @@ -1,147 +0,0 @@ -#include "cache.h" -#include "diff.h" -#include "commit.h" -#include "log-tree.h" - -static struct rev_info log_tree_opt; - -static int diff_tree_commit_sha1(const unsigned char *sha1) -{ - struct commit *commit = lookup_commit_reference(sha1); - if (!commit) - return -1; - return log_tree_commit(&log_tree_opt, commit); -} - -static int diff_tree_stdin(char *line) -{ - int len = strlen(line); - unsigned char sha1[20]; - struct commit *commit; - - if (!len || line[len-1] != '\n') - return -1; - line[len-1] = 0; - if (get_sha1_hex(line, sha1)) - return -1; - commit = lookup_commit(sha1); - if (!commit || parse_commit(commit)) - return -1; - if (isspace(line[40]) && !get_sha1_hex(line+41, sha1)) { - /* Graft the fake parents locally to the commit */ - int pos = 41; - struct commit_list **pptr, *parents; - - /* Free the real parent list */ - for (parents = commit->parents; parents; ) { - struct commit_list *tmp = parents->next; - free(parents); - parents = tmp; - } - commit->parents = NULL; - pptr = &(commit->parents); - while (line[pos] && !get_sha1_hex(line + pos, sha1)) { - struct commit *parent = lookup_commit(sha1); - if (parent) { - pptr = &commit_list_insert(parent, pptr)->next; - } - pos += 41; - } - } - return log_tree_commit(&log_tree_opt, commit); -} - -static const char diff_tree_usage[] = -"git-diff-tree [--stdin] [-m] [-c] [--cc] [-s] [-v] [--pretty] [-t] [-r] [--root] " -"[] [] [...]\n" -" -r diff recursively\n" -" --root include the initial commit as diff against /dev/null\n" -COMMON_DIFF_OPTIONS_HELP; - -int main(int argc, const char **argv) -{ - int nr_sha1; - char line[1000]; - struct object *tree1, *tree2; - static struct rev_info *opt = &log_tree_opt; - struct object_list *list; - int read_stdin = 0; - - git_config(git_diff_config); - nr_sha1 = 0; - init_revisions(opt); - opt->abbrev = 0; - opt->diff = 1; - argc = setup_revisions(argc, argv, opt, NULL); - - while (--argc > 0) { - const char *arg = *++argv; - - if (!strcmp(arg, "--stdin")) { - read_stdin = 1; - continue; - } - usage(diff_tree_usage); - } - - /* - * NOTE! "setup_revisions()" will have inserted the revisions - * it parsed in reverse order. So if you do - * - * git-diff-tree a b - * - * the commit list will be "b" -> "a" -> NULL, so we reverse - * the order of the objects if the first one is not marked - * UNINTERESTING. - */ - nr_sha1 = 0; - list = opt->pending_objects; - if (list) { - nr_sha1++; - tree1 = list->item; - list = list->next; - if (list) { - nr_sha1++; - tree2 = tree1; - tree1 = list->item; - if (list->next) - usage(diff_tree_usage); - /* Switch them around if the second one was uninteresting.. */ - if (tree2->flags & UNINTERESTING) { - struct object *tmp = tree2; - tree2 = tree1; - tree1 = tmp; - } - } - } - - switch (nr_sha1) { - case 0: - if (!read_stdin) - usage(diff_tree_usage); - break; - case 1: - diff_tree_commit_sha1(tree1->sha1); - break; - case 2: - diff_tree_sha1(tree1->sha1, - tree2->sha1, - "", &opt->diffopt); - log_tree_diff_flush(opt); - break; - } - - if (!read_stdin) - return 0; - - if (opt->diffopt.detect_rename) - opt->diffopt.setup |= (DIFF_SETUP_USE_SIZE_CACHE | - DIFF_SETUP_USE_CACHE); - while (fgets(line, sizeof(line), stdin)) - if (line[0] == '\n') - fflush(stdout); - else - diff_tree_stdin(line); - - return 0; -} diff --git a/git.c b/git.c index d29505c4c9..874974874b 100644 --- a/git.c +++ b/git.c @@ -59,7 +59,11 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "read-tree", cmd_read_tree }, { "commit-tree", cmd_commit_tree }, { "apply", cmd_apply }, - { "show-branch", cmd_show_branch } + { "show-branch", cmd_show_branch }, + { "diff-files", cmd_diff_files }, + { "diff-index", cmd_diff_index }, + { "diff-stages", cmd_diff_stages }, + { "diff-tree", cmd_diff_tree } }; int i; -- cgit v1.3 From f81daefe56b3c97b93a851e1ada14eeca0dea47a Mon Sep 17 00:00:00 2001 From: Timo Hirvonen Date: Wed, 24 May 2006 14:08:46 +0300 Subject: Builtin git-cat-file Signed-off-by: Timo Hirvonen Signed-off-by: Junio C Hamano --- Makefile | 6 +- builtin-cat-file.c | 167 +++++++++++++++++++++++++++++++++++++++++++++++++++++ builtin.h | 1 + cat-file.c | 166 ---------------------------------------------------- git.c | 3 +- 5 files changed, 173 insertions(+), 170 deletions(-) create mode 100644 builtin-cat-file.c delete mode 100644 cat-file.c (limited to 'Makefile') diff --git a/Makefile b/Makefile index 7e6517f62b..dbf19c6277 100644 --- a/Makefile +++ b/Makefile @@ -149,7 +149,6 @@ SIMPLE_PROGRAMS = \ # ... and all the rest that could be moved out of bindir to gitexecdir PROGRAMS = \ - git-cat-file$X \ git-checkout-index$X git-clone-pack$X \ git-convert-objects$X git-fetch-pack$X git-fsck-objects$X \ git-hash-object$X git-index-pack$X git-local-fetch$X \ @@ -174,7 +173,7 @@ BUILT_INS = git-log$X git-whatchanged$X git-show$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-diff-index$X git-diff-stages$X git-diff-tree$X git-cat-file$X # what 'all' will build and 'install' will install, in gitexecdir ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS) @@ -228,7 +227,8 @@ BUILTIN_OBJS = \ 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-diff-index.o builtin-diff-stages.o builtin-diff-tree.o \ + builtin-cat-file.o GITLIBS = $(LIB_FILE) $(XDIFF_LIB) LIBS = $(GITLIBS) -lz diff --git a/builtin-cat-file.c b/builtin-cat-file.c new file mode 100644 index 0000000000..8ab136e981 --- /dev/null +++ b/builtin-cat-file.c @@ -0,0 +1,167 @@ +/* + * GIT - The information manager from hell + * + * Copyright (C) Linus Torvalds, 2005 + */ +#include "cache.h" +#include "exec_cmd.h" +#include "tag.h" +#include "tree.h" +#include "builtin.h" + +static void flush_buffer(const char *buf, unsigned long size) +{ + while (size > 0) { + long ret = xwrite(1, buf, size); + if (ret < 0) { + /* Ignore epipe */ + if (errno == EPIPE) + break; + die("git-cat-file: %s", strerror(errno)); + } else if (!ret) { + die("git-cat-file: disk full?"); + } + size -= ret; + buf += ret; + } +} + +static int pprint_tag(const unsigned char *sha1, const char *buf, unsigned long size) +{ + /* the parser in tag.c is useless here. */ + const char *endp = buf + size; + const char *cp = buf; + + while (cp < endp) { + char c = *cp++; + if (c != '\n') + continue; + if (7 <= endp - cp && !memcmp("tagger ", cp, 7)) { + const char *tagger = cp; + + /* Found the tagger line. Copy out the contents + * of the buffer so far. + */ + flush_buffer(buf, cp - buf); + + /* + * Do something intelligent, like pretty-printing + * the date. + */ + while (cp < endp) { + if (*cp++ == '\n') { + /* tagger to cp is a line + * that has ident and time. + */ + const char *sp = tagger; + char *ep; + unsigned long date; + long tz; + while (sp < cp && *sp != '>') + sp++; + if (sp == cp) { + /* give up */ + flush_buffer(tagger, + cp - tagger); + break; + } + while (sp < cp && + !('0' <= *sp && *sp <= '9')) + sp++; + flush_buffer(tagger, sp - tagger); + date = strtoul(sp, &ep, 10); + tz = strtol(ep, NULL, 10); + sp = show_date(date, tz); + flush_buffer(sp, strlen(sp)); + xwrite(1, "\n", 1); + break; + } + } + break; + } + if (cp < endp && *cp == '\n') + /* end of header */ + break; + } + /* At this point, we have copied out the header up to the end of + * the tagger line and cp points at one past \n. It could be the + * next header line after the tagger line, or it could be another + * \n that marks the end of the headers. We need to copy out the + * remainder as is. + */ + if (cp < endp) + flush_buffer(cp, endp - cp); + return 0; +} + +int cmd_cat_file(int argc, const char **argv, char **envp) +{ + unsigned char sha1[20]; + char type[20]; + void *buf; + unsigned long size; + int opt; + + setup_git_directory(); + git_config(git_default_config); + if (argc != 3) + usage("git-cat-file [-t|-s|-e|-p|] "); + if (get_sha1(argv[2], sha1)) + die("Not a valid object name %s", argv[2]); + + opt = 0; + if ( argv[1][0] == '-' ) { + opt = argv[1][1]; + if ( !opt || argv[1][2] ) + opt = -1; /* Not a single character option */ + } + + buf = NULL; + switch (opt) { + case 't': + if (!sha1_object_info(sha1, type, NULL)) { + printf("%s\n", type); + return 0; + } + break; + + case 's': + if (!sha1_object_info(sha1, type, &size)) { + printf("%lu\n", size); + return 0; + } + break; + + case 'e': + return !has_sha1_file(sha1); + + case 'p': + if (sha1_object_info(sha1, type, NULL)) + die("Not a valid object name %s", argv[2]); + + /* custom pretty-print here */ + if (!strcmp(type, tree_type)) + return execl_git_cmd("ls-tree", argv[2], NULL); + + buf = read_sha1_file(sha1, type, &size); + if (!buf) + die("Cannot read object %s", argv[2]); + if (!strcmp(type, tag_type)) + return pprint_tag(sha1, buf, size); + + /* otherwise just spit out the data */ + break; + case 0: + buf = read_object_with_reference(sha1, argv[1], &size, NULL); + break; + + default: + die("git-cat-file: unknown option: %s\n", argv[1]); + } + + if (!buf) + die("git-cat-file %s: bad file", argv[2]); + + flush_buffer(buf, size); + return 0; +} diff --git a/builtin.h b/builtin.h index 714b97578c..738ec3d945 100644 --- a/builtin.h +++ b/builtin.h @@ -42,5 +42,6 @@ extern int cmd_diff_files(int argc, const char **argv, char **envp); extern int cmd_diff_index(int argc, const char **argv, char **envp); extern int cmd_diff_stages(int argc, const char **argv, char **envp); extern int cmd_diff_tree(int argc, const char **argv, char **envp); +extern int cmd_cat_file(int argc, const char **argv, char **envp); #endif diff --git a/cat-file.c b/cat-file.c deleted file mode 100644 index 7413feed78..0000000000 --- a/cat-file.c +++ /dev/null @@ -1,166 +0,0 @@ -/* - * GIT - The information manager from hell - * - * Copyright (C) Linus Torvalds, 2005 - */ -#include "cache.h" -#include "exec_cmd.h" -#include "tag.h" -#include "tree.h" - -static void flush_buffer(const char *buf, unsigned long size) -{ - while (size > 0) { - long ret = xwrite(1, buf, size); - if (ret < 0) { - /* Ignore epipe */ - if (errno == EPIPE) - break; - die("git-cat-file: %s", strerror(errno)); - } else if (!ret) { - die("git-cat-file: disk full?"); - } - size -= ret; - buf += ret; - } -} - -static int pprint_tag(const unsigned char *sha1, const char *buf, unsigned long size) -{ - /* the parser in tag.c is useless here. */ - const char *endp = buf + size; - const char *cp = buf; - - while (cp < endp) { - char c = *cp++; - if (c != '\n') - continue; - if (7 <= endp - cp && !memcmp("tagger ", cp, 7)) { - const char *tagger = cp; - - /* Found the tagger line. Copy out the contents - * of the buffer so far. - */ - flush_buffer(buf, cp - buf); - - /* - * Do something intelligent, like pretty-printing - * the date. - */ - while (cp < endp) { - if (*cp++ == '\n') { - /* tagger to cp is a line - * that has ident and time. - */ - const char *sp = tagger; - char *ep; - unsigned long date; - long tz; - while (sp < cp && *sp != '>') - sp++; - if (sp == cp) { - /* give up */ - flush_buffer(tagger, - cp - tagger); - break; - } - while (sp < cp && - !('0' <= *sp && *sp <= '9')) - sp++; - flush_buffer(tagger, sp - tagger); - date = strtoul(sp, &ep, 10); - tz = strtol(ep, NULL, 10); - sp = show_date(date, tz); - flush_buffer(sp, strlen(sp)); - xwrite(1, "\n", 1); - break; - } - } - break; - } - if (cp < endp && *cp == '\n') - /* end of header */ - break; - } - /* At this point, we have copied out the header up to the end of - * the tagger line and cp points at one past \n. It could be the - * next header line after the tagger line, or it could be another - * \n that marks the end of the headers. We need to copy out the - * remainder as is. - */ - if (cp < endp) - flush_buffer(cp, endp - cp); - return 0; -} - -int main(int argc, char **argv) -{ - unsigned char sha1[20]; - char type[20]; - void *buf; - unsigned long size; - int opt; - - setup_git_directory(); - git_config(git_default_config); - if (argc != 3) - usage("git-cat-file [-t|-s|-e|-p|] "); - if (get_sha1(argv[2], sha1)) - die("Not a valid object name %s", argv[2]); - - opt = 0; - if ( argv[1][0] == '-' ) { - opt = argv[1][1]; - if ( !opt || argv[1][2] ) - opt = -1; /* Not a single character option */ - } - - buf = NULL; - switch (opt) { - case 't': - if (!sha1_object_info(sha1, type, NULL)) { - printf("%s\n", type); - return 0; - } - break; - - case 's': - if (!sha1_object_info(sha1, type, &size)) { - printf("%lu\n", size); - return 0; - } - break; - - case 'e': - return !has_sha1_file(sha1); - - case 'p': - if (sha1_object_info(sha1, type, NULL)) - die("Not a valid object name %s", argv[2]); - - /* custom pretty-print here */ - if (!strcmp(type, tree_type)) - return execl_git_cmd("ls-tree", argv[2], NULL); - - buf = read_sha1_file(sha1, type, &size); - if (!buf) - die("Cannot read object %s", argv[2]); - if (!strcmp(type, tag_type)) - return pprint_tag(sha1, buf, size); - - /* otherwise just spit out the data */ - break; - case 0: - buf = read_object_with_reference(sha1, argv[1], &size, NULL); - break; - - default: - die("git-cat-file: unknown option: %s\n", argv[1]); - } - - if (!buf) - die("git-cat-file %s: bad file", argv[2]); - - flush_buffer(buf, size); - return 0; -} diff --git a/git.c b/git.c index 5a884bb07a..10ea934bcf 100644 --- a/git.c +++ b/git.c @@ -68,7 +68,8 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "diff-files", cmd_diff_files }, { "diff-index", cmd_diff_index }, { "diff-stages", cmd_diff_stages }, - { "diff-tree", cmd_diff_tree } + { "diff-tree", cmd_diff_tree }, + { "cat-file", cmd_cat_file } }; int i; -- cgit v1.3