From 81b50f3ce40bfdd66e5d967bf82be001039a9a98 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Mon, 22 Feb 2010 08:42:18 -0800 Subject: Move 'builtin-*' into a 'builtin/' subdirectory This shrinks the top-level directory a bit, and makes it much more pleasant to use auto-completion on the thing. Instead of [torvalds@nehalem git]$ em buil Display all 180 possibilities? (y or n) [torvalds@nehalem git]$ em builtin-sh builtin-shortlog.c builtin-show-branch.c builtin-show-ref.c builtin-shortlog.o builtin-show-branch.o builtin-show-ref.o [torvalds@nehalem git]$ em builtin-shor builtin-shortlog.c builtin-shortlog.o [torvalds@nehalem git]$ em builtin-shortlog.c you get [torvalds@nehalem git]$ em buil [type] builtin/ builtin.h [torvalds@nehalem git]$ em builtin [auto-completes to] [torvalds@nehalem git]$ em builtin/sh [type] shortlog.c shortlog.o show-branch.c show-branch.o show-ref.c show-ref.o [torvalds@nehalem git]$ em builtin/sho [auto-completes to] [torvalds@nehalem git]$ em builtin/shor [type] shortlog.c shortlog.o [torvalds@nehalem git]$ em builtin/shortlog.c which doesn't seem all that different, but not having that annoying break in "Display all 180 possibilities?" is quite a relief. NOTE! If you do this in a clean tree (no object files etc), or using an editor that has auto-completion rules that ignores '*.o' files, you won't see that annoying 'Display all 180 possibilities?' message - it will just show the choices instead. I think bash has some cut-off around 100 choices or something. So the reason I see this is that I'm using an odd editory, and thus don't have the rules to cut down on auto-completion. But you can simulate that by using 'ls' instead, or something similar. Signed-off-by: Linus Torvalds Signed-off-by: Junio C Hamano diff --git a/Makefile b/Makefile index afedb54..f1025d5 100644 --- a/Makefile +++ b/Makefile @@ -301,7 +301,7 @@ SPARSE_FLAGS = -D__BIG_ENDIAN__ -D__powerpc__ # Those must not be GNU-specific; they are shared with perl/ which may # be built by a different compiler. (Note that this is an artifact now # but it still might be nice to keep that distinction.) -BASIC_CFLAGS = +BASIC_CFLAGS = -I. BASIC_LDFLAGS = # Guard against environment variables @@ -370,8 +370,8 @@ PROGRAMS += git-upload-pack$X PROGRAMS += git-http-backend$X # List built-in command $C whose implementation cmd_$C() is not in -# builtin-$C.o but is linked in as part of some other command. -BUILT_INS += $(patsubst builtin-%.o,git-%$X,$(BUILTIN_OBJS)) +# builtin/$C.o but is linked in as part of some other command. +BUILT_INS += $(patsubst builtin/%.o,git-%$X,$(BUILTIN_OBJS)) BUILT_INS += git-cherry$X BUILT_INS += git-cherry-pick$X @@ -594,95 +594,95 @@ LIB_OBJS += ws.o LIB_OBJS += wt-status.o LIB_OBJS += xdiff-interface.o -BUILTIN_OBJS += builtin-add.o -BUILTIN_OBJS += builtin-annotate.o -BUILTIN_OBJS += builtin-apply.o -BUILTIN_OBJS += builtin-archive.o -BUILTIN_OBJS += builtin-bisect--helper.o -BUILTIN_OBJS += builtin-blame.o -BUILTIN_OBJS += builtin-branch.o -BUILTIN_OBJS += builtin-bundle.o -BUILTIN_OBJS += builtin-cat-file.o -BUILTIN_OBJS += builtin-check-attr.o -BUILTIN_OBJS += builtin-check-ref-format.o -BUILTIN_OBJS += builtin-checkout-index.o -BUILTIN_OBJS += builtin-checkout.o -BUILTIN_OBJS += builtin-clean.o -BUILTIN_OBJS += builtin-clone.o -BUILTIN_OBJS += builtin-commit-tree.o -BUILTIN_OBJS += builtin-commit.o -BUILTIN_OBJS += builtin-config.o -BUILTIN_OBJS += builtin-count-objects.o -BUILTIN_OBJS += builtin-describe.o -BUILTIN_OBJS += builtin-diff-files.o -BUILTIN_OBJS += builtin-diff-index.o -BUILTIN_OBJS += builtin-diff-tree.o -BUILTIN_OBJS += builtin-diff.o -BUILTIN_OBJS += builtin-fast-export.o -BUILTIN_OBJS += builtin-fetch-pack.o -BUILTIN_OBJS += builtin-fetch.o -BUILTIN_OBJS += builtin-fmt-merge-msg.o -BUILTIN_OBJS += builtin-for-each-ref.o -BUILTIN_OBJS += builtin-fsck.o -BUILTIN_OBJS += builtin-gc.o -BUILTIN_OBJS += builtin-grep.o -BUILTIN_OBJS += builtin-hash-object.o -BUILTIN_OBJS += builtin-help.o -BUILTIN_OBJS += builtin-index-pack.o -BUILTIN_OBJS += builtin-init-db.o -BUILTIN_OBJS += builtin-log.o -BUILTIN_OBJS += builtin-ls-files.o -BUILTIN_OBJS += builtin-ls-remote.o -BUILTIN_OBJS += builtin-ls-tree.o -BUILTIN_OBJS += builtin-mailinfo.o -BUILTIN_OBJS += builtin-mailsplit.o -BUILTIN_OBJS += builtin-merge.o -BUILTIN_OBJS += builtin-merge-base.o -BUILTIN_OBJS += builtin-merge-file.o -BUILTIN_OBJS += builtin-merge-index.o -BUILTIN_OBJS += builtin-merge-ours.o -BUILTIN_OBJS += builtin-merge-recursive.o -BUILTIN_OBJS += builtin-merge-tree.o -BUILTIN_OBJS += builtin-mktag.o -BUILTIN_OBJS += builtin-mktree.o -BUILTIN_OBJS += builtin-mv.o -BUILTIN_OBJS += builtin-name-rev.o -BUILTIN_OBJS += builtin-pack-objects.o -BUILTIN_OBJS += builtin-pack-redundant.o -BUILTIN_OBJS += builtin-pack-refs.o -BUILTIN_OBJS += builtin-patch-id.o -BUILTIN_OBJS += builtin-prune-packed.o -BUILTIN_OBJS += builtin-prune.o -BUILTIN_OBJS += builtin-push.o -BUILTIN_OBJS += builtin-read-tree.o -BUILTIN_OBJS += builtin-receive-pack.o -BUILTIN_OBJS += builtin-reflog.o -BUILTIN_OBJS += builtin-remote.o -BUILTIN_OBJS += builtin-replace.o -BUILTIN_OBJS += builtin-rerere.o -BUILTIN_OBJS += builtin-reset.o -BUILTIN_OBJS += builtin-rev-list.o -BUILTIN_OBJS += builtin-rev-parse.o -BUILTIN_OBJS += builtin-revert.o -BUILTIN_OBJS += builtin-rm.o -BUILTIN_OBJS += builtin-send-pack.o -BUILTIN_OBJS += builtin-shortlog.o -BUILTIN_OBJS += builtin-show-branch.o -BUILTIN_OBJS += builtin-show-ref.o -BUILTIN_OBJS += builtin-stripspace.o -BUILTIN_OBJS += builtin-symbolic-ref.o -BUILTIN_OBJS += builtin-tag.o -BUILTIN_OBJS += builtin-tar-tree.o -BUILTIN_OBJS += builtin-unpack-file.o -BUILTIN_OBJS += builtin-unpack-objects.o -BUILTIN_OBJS += builtin-update-index.o -BUILTIN_OBJS += builtin-update-ref.o -BUILTIN_OBJS += builtin-update-server-info.o -BUILTIN_OBJS += builtin-upload-archive.o -BUILTIN_OBJS += builtin-var.o -BUILTIN_OBJS += builtin-verify-pack.o -BUILTIN_OBJS += builtin-verify-tag.o -BUILTIN_OBJS += builtin-write-tree.o +BUILTIN_OBJS += builtin/add.o +BUILTIN_OBJS += builtin/annotate.o +BUILTIN_OBJS += builtin/apply.o +BUILTIN_OBJS += builtin/archive.o +BUILTIN_OBJS += builtin/bisect--helper.o +BUILTIN_OBJS += builtin/blame.o +BUILTIN_OBJS += builtin/branch.o +BUILTIN_OBJS += builtin/bundle.o +BUILTIN_OBJS += builtin/cat-file.o +BUILTIN_OBJS += builtin/check-attr.o +BUILTIN_OBJS += builtin/check-ref-format.o +BUILTIN_OBJS += builtin/checkout-index.o +BUILTIN_OBJS += builtin/checkout.o +BUILTIN_OBJS += builtin/clean.o +BUILTIN_OBJS += builtin/clone.o +BUILTIN_OBJS += builtin/commit-tree.o +BUILTIN_OBJS += builtin/commit.o +BUILTIN_OBJS += builtin/config.o +BUILTIN_OBJS += builtin/count-objects.o +BUILTIN_OBJS += builtin/describe.o +BUILTIN_OBJS += builtin/diff-files.o +BUILTIN_OBJS += builtin/diff-index.o +BUILTIN_OBJS += builtin/diff-tree.o +BUILTIN_OBJS += builtin/diff.o +BUILTIN_OBJS += builtin/fast-export.o +BUILTIN_OBJS += builtin/fetch-pack.o +BUILTIN_OBJS += builtin/fetch.o +BUILTIN_OBJS += builtin/fmt-merge-msg.o +BUILTIN_OBJS += builtin/for-each-ref.o +BUILTIN_OBJS += builtin/fsck.o +BUILTIN_OBJS += builtin/gc.o +BUILTIN_OBJS += builtin/grep.o +BUILTIN_OBJS += builtin/hash-object.o +BUILTIN_OBJS += builtin/help.o +BUILTIN_OBJS += builtin/index-pack.o +BUILTIN_OBJS += builtin/init-db.o +BUILTIN_OBJS += builtin/log.o +BUILTIN_OBJS += builtin/ls-files.o +BUILTIN_OBJS += builtin/ls-remote.o +BUILTIN_OBJS += builtin/ls-tree.o +BUILTIN_OBJS += builtin/mailinfo.o +BUILTIN_OBJS += builtin/mailsplit.o +BUILTIN_OBJS += builtin/merge.o +BUILTIN_OBJS += builtin/merge-base.o +BUILTIN_OBJS += builtin/merge-file.o +BUILTIN_OBJS += builtin/merge-index.o +BUILTIN_OBJS += builtin/merge-ours.o +BUILTIN_OBJS += builtin/merge-recursive.o +BUILTIN_OBJS += builtin/merge-tree.o +BUILTIN_OBJS += builtin/mktag.o +BUILTIN_OBJS += builtin/mktree.o +BUILTIN_OBJS += builtin/mv.o +BUILTIN_OBJS += builtin/name-rev.o +BUILTIN_OBJS += builtin/pack-objects.o +BUILTIN_OBJS += builtin/pack-redundant.o +BUILTIN_OBJS += builtin/pack-refs.o +BUILTIN_OBJS += builtin/patch-id.o +BUILTIN_OBJS += builtin/prune-packed.o +BUILTIN_OBJS += builtin/prune.o +BUILTIN_OBJS += builtin/push.o +BUILTIN_OBJS += builtin/read-tree.o +BUILTIN_OBJS += builtin/receive-pack.o +BUILTIN_OBJS += builtin/reflog.o +BUILTIN_OBJS += builtin/remote.o +BUILTIN_OBJS += builtin/replace.o +BUILTIN_OBJS += builtin/rerere.o +BUILTIN_OBJS += builtin/reset.o +BUILTIN_OBJS += builtin/rev-list.o +BUILTIN_OBJS += builtin/rev-parse.o +BUILTIN_OBJS += builtin/revert.o +BUILTIN_OBJS += builtin/rm.o +BUILTIN_OBJS += builtin/send-pack.o +BUILTIN_OBJS += builtin/shortlog.o +BUILTIN_OBJS += builtin/show-branch.o +BUILTIN_OBJS += builtin/show-ref.o +BUILTIN_OBJS += builtin/stripspace.o +BUILTIN_OBJS += builtin/symbolic-ref.o +BUILTIN_OBJS += builtin/tag.o +BUILTIN_OBJS += builtin/tar-tree.o +BUILTIN_OBJS += builtin/unpack-file.o +BUILTIN_OBJS += builtin/unpack-objects.o +BUILTIN_OBJS += builtin/update-index.o +BUILTIN_OBJS += builtin/update-ref.o +BUILTIN_OBJS += builtin/update-server-info.o +BUILTIN_OBJS += builtin/upload-archive.o +BUILTIN_OBJS += builtin/var.o +BUILTIN_OBJS += builtin/verify-pack.o +BUILTIN_OBJS += builtin/verify-tag.o +BUILTIN_OBJS += builtin/write-tree.o GITLIBS = $(LIB_FILE) $(XDIFF_LIB) EXTLIBS = @@ -1447,8 +1447,8 @@ git$X: git.o $(BUILTIN_OBJS) $(GITLIBS) $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ git.o \ $(BUILTIN_OBJS) $(ALL_LDFLAGS) $(LIBS) -builtin-help.o: common-cmds.h -builtin-help.s builtin-help.o: ALL_CFLAGS += \ +builtin/help.o: common-cmds.h +builtin/help.s builtin/help.o: ALL_CFLAGS += \ '-DGIT_HTML_PATH="$(htmldir_SQ)"' \ '-DGIT_MAN_PATH="$(mandir_SQ)"' \ '-DGIT_INFO_PATH="$(infodir_SQ)"' @@ -1604,7 +1604,7 @@ exec_cmd.s exec_cmd.o: ALL_CFLAGS += \ '-DBINDIR="$(bindir_relative_SQ)"' \ '-DPREFIX="$(prefix_SQ)"' -builtin-init-db.s builtin-init-db.o: ALL_CFLAGS += \ +builtin/init-db.s builtin/init-db.o: ALL_CFLAGS += \ -DDEFAULT_GIT_TEMPLATE_DIR='"$(template_dir_SQ)"' config.s config.o: ALL_CFLAGS += -DETC_GITCONFIG='"$(ETC_GITCONFIG_SQ)"' @@ -1646,7 +1646,7 @@ $(REMOTE_CURL_PRIMARY): remote-curl.o http.o http-walker.o $(GITLIBS) $(LIB_OBJS) $(BUILTIN_OBJS): $(LIB_H) $(patsubst git-%$X,%.o,$(PROGRAMS)) git.o: $(LIB_H) $(wildcard */*.h) -builtin-revert.o wt-status.o: wt-status.h +builtin/revert.o wt-status.o: wt-status.h $(LIB_FILE): $(LIB_OBJS) $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $(LIB_OBJS) @@ -1934,7 +1934,7 @@ distclean: clean clean: $(RM) *.o block-sha1/*.o ppc/*.o compat/*.o compat/*/*.o xdiff/*.o \ - $(LIB_FILE) $(XDIFF_LIB) + builtin/*.o $(LIB_FILE) $(XDIFF_LIB) $(RM) $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) git$X $(RM) $(TEST_PROGRAMS) $(RM) -r bin-wrappers diff --git a/builtin-add.c b/builtin-add.c deleted file mode 100644 index 2705f8d..0000000 --- a/builtin-add.c +++ /dev/null @@ -1,442 +0,0 @@ -/* - * "git add" builtin command - * - * Copyright (C) 2006 Linus Torvalds - */ -#include "cache.h" -#include "builtin.h" -#include "dir.h" -#include "exec_cmd.h" -#include "cache-tree.h" -#include "run-command.h" -#include "parse-options.h" -#include "diff.h" -#include "diffcore.h" -#include "revision.h" - -static const char * const builtin_add_usage[] = { - "git add [options] [--] ...", - NULL -}; -static int patch_interactive, add_interactive, edit_interactive; -static int take_worktree_changes; - -struct update_callback_data -{ - int flags; - int add_errors; -}; - -static void update_callback(struct diff_queue_struct *q, - struct diff_options *opt, void *cbdata) -{ - int i; - struct update_callback_data *data = cbdata; - - for (i = 0; i < q->nr; i++) { - struct diff_filepair *p = q->queue[i]; - const char *path = p->one->path; - switch (p->status) { - default: - die("unexpected diff status %c", p->status); - case DIFF_STATUS_UNMERGED: - /* - * ADD_CACHE_IGNORE_REMOVAL is unset if "git - * add -u" is calling us, In such a case, a - * missing work tree file needs to be removed - * if there is an unmerged entry at stage #2, - * but such a diff record is followed by - * another with DIFF_STATUS_DELETED (and if - * there is no stage #2, we won't see DELETED - * nor MODIFIED). We can simply continue - * either way. - */ - if (!(data->flags & ADD_CACHE_IGNORE_REMOVAL)) - continue; - /* - * Otherwise, it is "git add path" is asking - * to explicitly add it; we fall through. A - * missing work tree file is an error and is - * caught by add_file_to_index() in such a - * case. - */ - case DIFF_STATUS_MODIFIED: - case DIFF_STATUS_TYPE_CHANGED: - if (add_file_to_index(&the_index, path, data->flags)) { - if (!(data->flags & ADD_CACHE_IGNORE_ERRORS)) - die("updating files failed"); - data->add_errors++; - } - break; - case DIFF_STATUS_DELETED: - if (data->flags & ADD_CACHE_IGNORE_REMOVAL) - break; - if (!(data->flags & ADD_CACHE_PRETEND)) - remove_file_from_index(&the_index, path); - if (data->flags & (ADD_CACHE_PRETEND|ADD_CACHE_VERBOSE)) - printf("remove '%s'\n", path); - break; - } - } -} - -int add_files_to_cache(const char *prefix, const char **pathspec, int flags) -{ - struct update_callback_data data; - struct rev_info rev; - init_revisions(&rev, prefix); - setup_revisions(0, NULL, &rev, NULL); - rev.prune_data = pathspec; - rev.diffopt.output_format = DIFF_FORMAT_CALLBACK; - rev.diffopt.format_callback = update_callback; - data.flags = flags; - data.add_errors = 0; - rev.diffopt.format_callback_data = &data; - run_diff_files(&rev, DIFF_RACY_IS_MODIFIED); - return !!data.add_errors; -} - -static void fill_pathspec_matches(const char **pathspec, char *seen, int specs) -{ - int num_unmatched = 0, i; - - /* - * Since we are walking the index as if we were walking the directory, - * we have to mark the matched pathspec as seen; otherwise we will - * mistakenly think that the user gave a pathspec that did not match - * anything. - */ - for (i = 0; i < specs; i++) - if (!seen[i]) - num_unmatched++; - if (!num_unmatched) - return; - for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; - match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, seen); - } -} - -static void prune_directory(struct dir_struct *dir, const char **pathspec, int prefix) -{ - char *seen; - int i, specs; - struct dir_entry **src, **dst; - - for (specs = 0; pathspec[specs]; specs++) - /* nothing */; - seen = xcalloc(specs, 1); - - src = dst = dir->entries; - i = dir->nr; - while (--i >= 0) { - struct dir_entry *entry = *src++; - if (match_pathspec(pathspec, entry->name, entry->len, - prefix, seen)) - *dst++ = entry; - } - dir->nr = dst - dir->entries; - fill_pathspec_matches(pathspec, seen, specs); - - for (i = 0; i < specs; i++) { - if (!seen[i] && pathspec[i][0] && !file_exists(pathspec[i])) - die("pathspec '%s' did not match any files", - pathspec[i]); - } - free(seen); -} - -static void treat_gitlinks(const char **pathspec) -{ - int i; - - if (!pathspec || !*pathspec) - return; - - for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; - if (S_ISGITLINK(ce->ce_mode)) { - int len = ce_namelen(ce), j; - for (j = 0; pathspec[j]; j++) { - int len2 = strlen(pathspec[j]); - if (len2 <= len || pathspec[j][len] != '/' || - memcmp(ce->name, pathspec[j], len)) - continue; - if (len2 == len + 1) - /* strip trailing slash */ - pathspec[j] = xstrndup(ce->name, len); - else - die ("Path '%s' is in submodule '%.*s'", - pathspec[j], len, ce->name); - } - } - } -} - -static void refresh(int verbose, const char **pathspec) -{ - char *seen; - int i, specs; - - for (specs = 0; pathspec[specs]; specs++) - /* nothing */; - seen = xcalloc(specs, 1); - refresh_index(&the_index, verbose ? REFRESH_IN_PORCELAIN : REFRESH_QUIET, - pathspec, seen, "Unstaged changes after refreshing the index:"); - for (i = 0; i < specs; i++) { - if (!seen[i]) - die("pathspec '%s' did not match any files", pathspec[i]); - } - free(seen); -} - -static const char **validate_pathspec(int argc, const char **argv, const char *prefix) -{ - const char **pathspec = get_pathspec(prefix, argv); - - if (pathspec) { - const char **p; - for (p = pathspec; *p; p++) { - if (has_symlink_leading_path(*p, strlen(*p))) { - int len = prefix ? strlen(prefix) : 0; - die("'%s' is beyond a symbolic link", *p + len); - } - } - } - - return pathspec; -} - -int run_add_interactive(const char *revision, const char *patch_mode, - const char **pathspec) -{ - int status, ac, pc = 0; - const char **args; - - if (pathspec) - while (pathspec[pc]) - pc++; - - args = xcalloc(sizeof(const char *), (pc + 5)); - ac = 0; - args[ac++] = "add--interactive"; - if (patch_mode) - args[ac++] = patch_mode; - if (revision) - args[ac++] = revision; - args[ac++] = "--"; - if (pc) { - memcpy(&(args[ac]), pathspec, sizeof(const char *) * pc); - ac += pc; - } - args[ac] = NULL; - - status = run_command_v_opt(args, RUN_GIT_CMD); - free(args); - return status; -} - -int interactive_add(int argc, const char **argv, const char *prefix) -{ - const char **pathspec = NULL; - - if (argc) { - pathspec = validate_pathspec(argc, argv, prefix); - if (!pathspec) - return -1; - } - - return run_add_interactive(NULL, - patch_interactive ? "--patch" : NULL, - pathspec); -} - -static int edit_patch(int argc, const char **argv, const char *prefix) -{ - char *file = xstrdup(git_path("ADD_EDIT.patch")); - const char *apply_argv[] = { "apply", "--recount", "--cached", - file, NULL }; - struct child_process child; - struct rev_info rev; - int out; - struct stat st; - - git_config(git_diff_basic_config, NULL); /* no "diff" UI options */ - - if (read_cache() < 0) - die ("Could not read the index"); - - init_revisions(&rev, prefix); - rev.diffopt.context = 7; - - argc = setup_revisions(argc, argv, &rev, NULL); - rev.diffopt.output_format = DIFF_FORMAT_PATCH; - out = open(file, O_CREAT | O_WRONLY, 0644); - if (out < 0) - die ("Could not open '%s' for writing.", file); - rev.diffopt.file = xfdopen(out, "w"); - rev.diffopt.close_file = 1; - if (run_diff_files(&rev, 0)) - die ("Could not write patch"); - - launch_editor(file, NULL, NULL); - - if (stat(file, &st)) - die_errno("Could not stat '%s'", file); - if (!st.st_size) - die("Empty patch. Aborted."); - - memset(&child, 0, sizeof(child)); - child.git_cmd = 1; - child.argv = apply_argv; - if (run_command(&child)) - die ("Could not apply '%s'", file); - - unlink(file); - return 0; -} - -static struct lock_file lock_file; - -static const char ignore_error[] = -"The following paths are ignored by one of your .gitignore files:\n"; - -static int verbose = 0, show_only = 0, ignored_too = 0, refresh_only = 0; -static int ignore_add_errors, addremove, intent_to_add; - -static struct option builtin_add_options[] = { - OPT__DRY_RUN(&show_only), - OPT__VERBOSE(&verbose), - OPT_GROUP(""), - OPT_BOOLEAN('i', "interactive", &add_interactive, "interactive picking"), - OPT_BOOLEAN('p', "patch", &patch_interactive, "interactive patching"), - OPT_BOOLEAN('e', "edit", &edit_interactive, "edit current diff and apply"), - OPT_BOOLEAN('f', "force", &ignored_too, "allow adding otherwise ignored files"), - OPT_BOOLEAN('u', "update", &take_worktree_changes, "update tracked files"), - OPT_BOOLEAN('N', "intent-to-add", &intent_to_add, "record only the fact that the path will be added later"), - OPT_BOOLEAN('A', "all", &addremove, "add all, noticing removal of tracked files"), - OPT_BOOLEAN( 0 , "refresh", &refresh_only, "don't add, only refresh the index"), - OPT_BOOLEAN( 0 , "ignore-errors", &ignore_add_errors, "just skip files which cannot be added because of errors"), - OPT_END(), -}; - -static int add_config(const char *var, const char *value, void *cb) -{ - if (!strcasecmp(var, "add.ignore-errors")) { - ignore_add_errors = git_config_bool(var, value); - return 0; - } - return git_default_config(var, value, cb); -} - -static int add_files(struct dir_struct *dir, int flags) -{ - int i, exit_status = 0; - - if (dir->ignored_nr) { - fprintf(stderr, ignore_error); - for (i = 0; i < dir->ignored_nr; i++) - fprintf(stderr, "%s\n", dir->ignored[i]->name); - fprintf(stderr, "Use -f if you really want to add them.\n"); - die("no files added"); - } - - for (i = 0; i < dir->nr; i++) - if (add_file_to_cache(dir->entries[i]->name, flags)) { - if (!ignore_add_errors) - die("adding files failed"); - exit_status = 1; - } - return exit_status; -} - -int cmd_add(int argc, const char **argv, const char *prefix) -{ - int exit_status = 0; - int newfd; - const char **pathspec; - struct dir_struct dir; - int flags; - int add_new_files; - int require_pathspec; - - git_config(add_config, NULL); - - argc = parse_options(argc, argv, prefix, builtin_add_options, - builtin_add_usage, PARSE_OPT_KEEP_ARGV0); - if (patch_interactive) - add_interactive = 1; - if (add_interactive) - exit(interactive_add(argc - 1, argv + 1, prefix)); - - if (edit_interactive) - return(edit_patch(argc, argv, prefix)); - argc--; - argv++; - - if (addremove && take_worktree_changes) - die("-A and -u are mutually incompatible"); - if ((addremove || take_worktree_changes) && !argc) { - static const char *here[2] = { ".", NULL }; - argc = 1; - argv = here; - } - - add_new_files = !take_worktree_changes && !refresh_only; - require_pathspec = !take_worktree_changes; - - newfd = hold_locked_index(&lock_file, 1); - - flags = ((verbose ? ADD_CACHE_VERBOSE : 0) | - (show_only ? ADD_CACHE_PRETEND : 0) | - (intent_to_add ? ADD_CACHE_INTENT : 0) | - (ignore_add_errors ? ADD_CACHE_IGNORE_ERRORS : 0) | - (!(addremove || take_worktree_changes) - ? ADD_CACHE_IGNORE_REMOVAL : 0)); - - if (require_pathspec && argc == 0) { - fprintf(stderr, "Nothing specified, nothing added.\n"); - fprintf(stderr, "Maybe you wanted to say 'git add .'?\n"); - return 0; - } - pathspec = validate_pathspec(argc, argv, prefix); - - if (read_cache() < 0) - die("index file corrupt"); - treat_gitlinks(pathspec); - - if (add_new_files) { - int baselen; - - /* Set up the default git porcelain excludes */ - memset(&dir, 0, sizeof(dir)); - if (!ignored_too) { - dir.flags |= DIR_COLLECT_IGNORED; - setup_standard_excludes(&dir); - } - - /* This picks up the paths that are not tracked */ - baselen = fill_directory(&dir, pathspec); - if (pathspec) - prune_directory(&dir, pathspec, baselen); - } - - if (refresh_only) { - refresh(verbose, pathspec); - goto finish; - } - - exit_status |= add_files_to_cache(prefix, pathspec, flags); - - if (add_new_files) - exit_status |= add_files(&dir, flags); - - finish: - if (active_cache_changed) { - if (write_cache(newfd, active_cache, active_nr) || - commit_locked_index(&lock_file)) - die("Unable to write new index file"); - } - - return exit_status; -} diff --git a/builtin-annotate.c b/builtin-annotate.c deleted file mode 100644 index fc43eed..0000000 --- a/builtin-annotate.c +++ /dev/null @@ -1,24 +0,0 @@ -/* - * "git annotate" builtin alias - * - * Copyright (C) 2006 Ryan Anderson - */ -#include "git-compat-util.h" -#include "builtin.h" - -int cmd_annotate(int argc, const char **argv, const char *prefix) -{ - const char **nargv; - int i; - nargv = xmalloc(sizeof(char *) * (argc + 2)); - - nargv[0] = "annotate"; - nargv[1] = "-c"; - - for (i = 1; i < argc; i++) { - nargv[i+1] = argv[i]; - } - nargv[argc + 1] = NULL; - - return cmd_blame(argc + 1, nargv, prefix); -} diff --git a/builtin-apply.c b/builtin-apply.c deleted file mode 100644 index 3af4ae0..0000000 --- a/builtin-apply.c +++ /dev/null @@ -1,3670 +0,0 @@ -/* - * apply.c - * - * Copyright (C) Linus Torvalds, 2005 - * - * This applies patches on top of some (arbitrary) version of the SCM. - * - */ -#include "cache.h" -#include "cache-tree.h" -#include "quote.h" -#include "blob.h" -#include "delta.h" -#include "builtin.h" -#include "string-list.h" -#include "dir.h" -#include "parse-options.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 unidiff_zero; -static int p_value = 1; -static int p_value_known; -static int check_index; -static int update_index; -static int cached; -static int diffstat; -static int numstat; -static int summary; -static int check; -static int apply = 1; -static int apply_in_reverse; -static int apply_with_reject; -static int apply_verbosely; -static int no_add; -static const char *fake_ancestor; -static int line_termination = '\n'; -static unsigned int p_context = UINT_MAX; -static const char * const apply_usage[] = { - "git apply [options] [...]", - NULL -}; - -static enum ws_error_action { - nowarn_ws_error, - warn_on_ws_error, - die_on_ws_error, - correct_ws_error, -} ws_error_action = warn_on_ws_error; -static int whitespace_error; -static int squelch_whitespace_errors = 5; -static int applied_after_fixing_ws; - -static enum ws_ignore { - ignore_ws_none, - ignore_ws_change, -} ws_ignore_action = ignore_ws_none; - - -static const char *patch_input_file; -static const char *root; -static int root_len; -static int read_stdin = 1; -static int options; - -static void parse_whitespace_option(const char *option) -{ - if (!option) { - ws_error_action = warn_on_ws_error; - return; - } - if (!strcmp(option, "warn")) { - ws_error_action = warn_on_ws_error; - return; - } - if (!strcmp(option, "nowarn")) { - ws_error_action = nowarn_ws_error; - return; - } - if (!strcmp(option, "error")) { - ws_error_action = die_on_ws_error; - return; - } - if (!strcmp(option, "error-all")) { - ws_error_action = die_on_ws_error; - squelch_whitespace_errors = 0; - return; - } - if (!strcmp(option, "strip") || !strcmp(option, "fix")) { - ws_error_action = correct_ws_error; - return; - } - die("unrecognized whitespace option '%s'", option); -} - -static void parse_ignorewhitespace_option(const char *option) -{ - if (!option || !strcmp(option, "no") || - !strcmp(option, "false") || !strcmp(option, "never") || - !strcmp(option, "none")) { - ws_ignore_action = ignore_ws_none; - return; - } - if (!strcmp(option, "change")) { - ws_ignore_action = ignore_ws_change; - return; - } - die("unrecognized whitespace ignore option '%s'", option); -} - -static void set_default_whitespace_mode(const char *whitespace_option) -{ - if (!whitespace_option && !apply_default_whitespace) - ws_error_action = (apply ? warn_on_ws_error : nowarn_ws_error); -} - -/* - * 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; - -/* - * This represents one "hunk" from a patch, starting with - * "@@ -oldpos,oldlines +newpos,newlines @@" marker. The - * patch text is pointed at by patch, and its byte length - * is stored in size. leading and trailing are the number - * of context lines. - */ -struct fragment { - unsigned long leading, trailing; - unsigned long oldpos, oldlines; - unsigned long newpos, newlines; - const char *patch; - int size; - int rejected; - int linenr; - struct fragment *next; -}; - -/* - * When dealing with a binary patch, we reuse "leading" field - * to store the type of the binary hunk, either deflated "delta" - * or deflated "literal". - */ -#define binary_patch_method leading -#define BINARY_DELTA_DEFLATED 1 -#define BINARY_LITERAL_DEFLATED 2 - -/* - * This represents a "patch" to a file, both metainfo changes - * such as creation/deletion, filemode and content changes represented - * as a series of fragments. - */ -struct patch { - char *new_name, *old_name, *def_name; - unsigned int old_mode, new_mode; - int is_new, is_delete; /* -1 = unknown, 0 = false, 1 = true */ - int rejected; - unsigned ws_rule; - unsigned long deflate_origlen; - int lines_added, lines_deleted; - int score; - unsigned int is_toplevel_relative:1; - unsigned int inaccurate_eof:1; - unsigned int is_binary:1; - unsigned int is_copy:1; - unsigned int is_rename:1; - unsigned int recount:1; - struct fragment *fragments; - char *result; - size_t resultsize; - char old_sha1_prefix[41]; - char new_sha1_prefix[41]; - struct patch *next; -}; - -/* - * A line in a file, len-bytes long (includes the terminating LF, - * except for an incomplete line at the end if the file ends with - * one), and its contents hashes to 'hash'. - */ -struct line { - size_t len; - unsigned hash : 24; - unsigned flag : 8; -#define LINE_COMMON 1 -}; - -/* - * This represents a "file", which is an array of "lines". - */ -struct image { - char *buf; - size_t len; - size_t nr; - size_t alloc; - struct line *line_allocated; - struct line *line; -}; - -/* - * Records filenames that have been touched, in order to handle - * the case where more than one patches touch the same file. - */ - -static struct string_list fn_table; - -static uint32_t hash_line(const char *cp, size_t len) -{ - size_t i; - uint32_t h; - for (i = 0, h = 0; i < len; i++) { - if (!isspace(cp[i])) { - h = h * 3 + (cp[i] & 0xff); - } - } - return h; -} - -/* - * Compare lines s1 of length n1 and s2 of length n2, ignoring - * whitespace difference. Returns 1 if they match, 0 otherwise - */ -static int fuzzy_matchlines(const char *s1, size_t n1, - const char *s2, size_t n2) -{ - const char *last1 = s1 + n1 - 1; - const char *last2 = s2 + n2 - 1; - int result = 0; - - if (n1 < 0 || n2 < 0) - return 0; - - /* ignore line endings */ - while ((*last1 == '\r') || (*last1 == '\n')) - last1--; - while ((*last2 == '\r') || (*last2 == '\n')) - last2--; - - /* skip leading whitespace */ - while (isspace(*s1) && (s1 <= last1)) - s1++; - while (isspace(*s2) && (s2 <= last2)) - s2++; - /* early return if both lines are empty */ - if ((s1 > last1) && (s2 > last2)) - return 1; - while (!result) { - result = *s1++ - *s2++; - /* - * Skip whitespace inside. We check for whitespace on - * both buffers because we don't want "a b" to match - * "ab" - */ - if (isspace(*s1) && isspace(*s2)) { - while (isspace(*s1) && s1 <= last1) - s1++; - while (isspace(*s2) && s2 <= last2) - s2++; - } - /* - * If we reached the end on one side only, - * lines don't match - */ - if ( - ((s2 > last2) && (s1 <= last1)) || - ((s1 > last1) && (s2 <= last2))) - return 0; - if ((s1 > last1) && (s2 > last2)) - break; - } - - return !result; -} - -static void add_line_info(struct image *img, const char *bol, size_t len, unsigned flag) -{ - ALLOC_GROW(img->line_allocated, img->nr + 1, img->alloc); - img->line_allocated[img->nr].len = len; - img->line_allocated[img->nr].hash = hash_line(bol, len); - img->line_allocated[img->nr].flag = flag; - img->nr++; -} - -static void prepare_image(struct image *image, char *buf, size_t len, - int prepare_linetable) -{ - const char *cp, *ep; - - memset(image, 0, sizeof(*image)); - image->buf = buf; - image->len = len; - - if (!prepare_linetable) - return; - - ep = image->buf + image->len; - cp = image->buf; - while (cp < ep) { - const char *next; - for (next = cp; next < ep && *next != '\n'; next++) - ; - if (next < ep) - next++; - add_line_info(image, cp, next - cp, 0); - cp = next; - } - image->line = image->line_allocated; -} - -static void clear_image(struct image *image) -{ - free(image->buf); - image->buf = NULL; - image->len = 0; -} - -static void say_patch_name(FILE *output, const char *pre, - struct patch *patch, const char *post) -{ - fputs(pre, output); - if (patch->old_name && patch->new_name && - strcmp(patch->old_name, patch->new_name)) { - quote_c_style(patch->old_name, NULL, output, 0); - fputs(" => ", output); - quote_c_style(patch->new_name, NULL, output, 0); - } else { - const char *n = patch->new_name; - if (!n) - n = patch->old_name; - quote_c_style(n, NULL, output, 0); - } - fputs(post, output); -} - -#define CHUNKSIZE (8192) -#define SLOP (16) - -static void read_patch_file(struct strbuf *sb, int fd) -{ - if (strbuf_read(sb, fd, 0) < 0) - die_errno("git apply: failed to read"); - - /* - * 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. - */ - strbuf_grow(sb, SLOP); - memset(sb->buf + sb->len, 0, SLOP); -} - -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; -} - -/* remove double slashes to make --index work with such filenames */ -static char *squash_slash(char *name) -{ - int i = 0, j = 0; - - if (!name) - return NULL; - - while (name[i]) { - if ((name[j++] = name[i++]) == '/') - while (name[i] == '/') - i++; - } - name[j] = '\0'; - return name; -} - -static char *find_name(const char *line, char *def, int p_value, int terminate) -{ - int len; - const char *start = NULL; - - if (p_value == 0) - start = line; - - if (*line == '"') { - struct strbuf name = STRBUF_INIT; - - /* - * Proposed "new-style" GNU patch/diff format; see - * http://marc.theaimsgroup.com/?l=git&m=112927316408690&w=2 - */ - if (!unquote_c_style(&name, line, NULL)) { - char *cp; - - for (cp = name.buf; p_value; p_value--) { - cp = strchr(cp, '/'); - if (!cp) - break; - cp++; - } - if (cp) { - /* name can later be freed, so we need - * to memmove, not just return cp - */ - strbuf_remove(&name, 0, cp - name.buf); - free(def); - if (root) - strbuf_insert(&name, 0, root, root_len); - return squash_slash(strbuf_detach(&name, NULL)); - } - } - strbuf_release(&name); - } - - 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 squash_slash(def); - len = line - start; - if (!len) - return squash_slash(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 squash_slash(def); - free(def); - } - - if (root) { - char *ret = xmalloc(root_len + len + 1); - strcpy(ret, root); - memcpy(ret + root_len, start, len); - ret[root_len + len] = '\0'; - return squash_slash(ret); - } - - return squash_slash(xmemdupz(start, len)); -} - -static int count_slashes(const char *cp) -{ - int cnt = 0; - char ch; - - while ((ch = *cp++)) - if (ch == '/') - cnt++; - return cnt; -} - -/* - * Given the string after "--- " or "+++ ", guess the appropriate - * p_value for the given patch. - */ -static int guess_p_value(const char *nameline) -{ - char *name, *cp; - int val = -1; - - if (is_dev_null(nameline)) - return -1; - name = find_name(nameline, NULL, 0, TERM_SPACE | TERM_TAB); - if (!name) - return -1; - cp = strchr(name, '/'); - if (!cp) - val = 0; - else if (prefix) { - /* - * Does it begin with "a/$our-prefix" and such? Then this is - * very likely to apply to our directory. - */ - if (!strncmp(name, prefix, prefix_length)) - val = count_slashes(prefix); - else { - cp++; - if (!strncmp(cp, prefix, prefix_length)) - val = count_slashes(prefix) + 1; - } - } - free(name); - return val; -} - -/* - * Does the ---/+++ line has the POSIX timestamp after the last HT? - * GNU diff puts epoch there to signal a creation/deletion event. Is - * this such a timestamp? - */ -static int has_epoch_timestamp(const char *nameline) -{ - /* - * We are only interested in epoch timestamp; any non-zero - * fraction cannot be one, hence "(\.0+)?" in the regexp below. - * For the same reason, the date must be either 1969-12-31 or - * 1970-01-01, and the seconds part must be "00". - */ - const char stamp_regexp[] = - "^(1969-12-31|1970-01-01)" - " " - "[0-2][0-9]:[0-5][0-9]:00(\\.0+)?" - " " - "([-+][0-2][0-9][0-5][0-9])\n"; - const char *timestamp = NULL, *cp; - static regex_t *stamp; - regmatch_t m[10]; - int zoneoffset; - int hourminute; - int status; - - for (cp = nameline; *cp != '\n'; cp++) { - if (*cp == '\t') - timestamp = cp + 1; - } - if (!timestamp) - return 0; - if (!stamp) { - stamp = xmalloc(sizeof(*stamp)); - if (regcomp(stamp, stamp_regexp, REG_EXTENDED)) { - warning("Cannot prepare timestamp regexp %s", - stamp_regexp); - return 0; - } - } - - status = regexec(stamp, timestamp, ARRAY_SIZE(m), m, 0); - if (status) { - if (status != REG_NOMATCH) - warning("regexec returned %d for input: %s", - status, timestamp); - return 0; - } - - zoneoffset = strtol(timestamp + m[3].rm_so + 1, NULL, 10); - zoneoffset = (zoneoffset / 100) * 60 + (zoneoffset % 100); - if (timestamp[m[3].rm_so] == '-') - zoneoffset = -zoneoffset; - - /* - * YYYY-MM-DD hh:mm:ss must be from either 1969-12-31 - * (west of GMT) or 1970-01-01 (east of GMT) - */ - if ((zoneoffset < 0 && memcmp(timestamp, "1969-12-31", 10)) || - (0 <= zoneoffset && memcmp(timestamp, "1970-01-01", 10))) - return 0; - - hourminute = (strtol(timestamp + 11, NULL, 10) * 60 + - strtol(timestamp + 14, NULL, 10) - - zoneoffset); - - return ((zoneoffset < 0 && hourminute == 1440) || - (0 <= zoneoffset && !hourminute)); -} - -/* - * Get the name etc info from the ---/+++ lines of a traditional patch header - * - * 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 (!p_value_known) { - int p, q; - p = guess_p_value(first); - q = guess_p_value(second); - if (p < 0) p = q; - if (0 <= p && p == q) { - p_value = p; - p_value_known = 1; - } - } - 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); - if (has_epoch_timestamp(first)) { - patch->is_new = 1; - patch->is_delete = 0; - patch->new_name = name; - } else if (has_epoch_timestamp(second)) { - patch->is_new = 0; - patch->is_delete = 1; - patch->old_name = name; - } else { - 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, p_value, TERM_TAB); - - 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, p_value, TERM_TAB); - if (!another || memcmp(another, name, len + 1)) - 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->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 nslash = p_value; - int i; - - for (i = 0; i < llen; i++) { - int ch = line[i]; - if (ch == '/' && --nslash <= 0) - 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) -{ - const char *name; - const char *second = NULL; - size_t len; - - line += strlen("diff --git "); - llen -= strlen("diff --git "); - - if (*line == '"') { - const char *cp; - struct strbuf first = STRBUF_INIT; - struct strbuf sp = STRBUF_INIT; - - if (unquote_c_style(&first, line, &second)) - goto free_and_fail1; - - /* advance to the first slash */ - cp = stop_at_slash(first.buf, first.len); - /* we do not accept absolute paths */ - if (!cp || cp == first.buf) - goto free_and_fail1; - strbuf_remove(&first, 0, cp + 1 - first.buf); - - /* - * 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_and_fail1; - if (*second == '"') { - if (unquote_c_style(&sp, second, NULL)) - goto free_and_fail1; - cp = stop_at_slash(sp.buf, sp.len); - if (!cp || cp == sp.buf) - goto free_and_fail1; - /* They must match, otherwise ignore */ - if (strcmp(cp + 1, first.buf)) - goto free_and_fail1; - strbuf_release(&sp); - return strbuf_detach(&first, NULL); - } - - /* unquoted second */ - cp = stop_at_slash(second, line + llen - second); - if (!cp || cp == second) - goto free_and_fail1; - cp++; - if (line + llen - cp != first.len + 1 || - memcmp(first.buf, cp, first.len)) - goto free_and_fail1; - return strbuf_detach(&first, NULL); - - free_and_fail1: - strbuf_release(&first); - strbuf_release(&sp); - return NULL; - } - - /* 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 == '"') { - struct strbuf sp = STRBUF_INIT; - const char *np; - - if (unquote_c_style(&sp, second, NULL)) - goto free_and_fail2; - - np = stop_at_slash(sp.buf, sp.len); - if (!np || np == sp.buf) - goto free_and_fail2; - np++; - - len = sp.buf + sp.len - np; - if (len < second - name && - !strncmp(np, name, len) && - isspace(name[len])) { - /* Good */ - strbuf_remove(&sp, 0, np - sp.buf); - return strbuf_detach(&sp, NULL); - } - - free_and_fail2: - strbuf_release(&sp); - return NULL; - } - } - - /* - * Accept a name only if it shows up twice, exactly the same - * form. - */ - for (len = 0 ; ; len++) { - switch (name[len]) { - 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)) { - return xmemdupz(name, len); - } - } - } -} - -/* 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); - if (patch->def_name && root) { - char *s = xmalloc(root_len + strlen(patch->def_name) + 1); - strcpy(s, root); - strcpy(s + root_len, patch->def_name); - free(patch->def_name); - patch->def_name = s; - } - - 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; -} - -static void recount_diff(char *line, int size, struct fragment *fragment) -{ - int oldlines = 0, newlines = 0, ret = 0; - - if (size < 1) { - warning("recount: ignore empty hunk"); - return; - } - - for (;;) { - int len = linelen(line, size); - size -= len; - line += len; - - if (size < 1) - break; - - switch (*line) { - case ' ': case '\n': - newlines++; - /* fall through */ - case '-': - oldlines++; - continue; - case '+': - newlines++; - continue; - case '\\': - continue; - case '@': - ret = size < 3 || prefixcmp(line, "@@ "); - break; - case 'd': - ret = size < 5 || prefixcmp(line, "diff "); - break; - default: - ret = -1; - break; - } - if (ret) { - warning("recount: unexpected line: %.*s", - (int)linelen(line, size), line); - return; - } - break; - } - fragment->oldlines = oldlines; - fragment->newlines = newlines; -} - -/* - * 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_toplevel_relative = 0; - 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 fragments. - * 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; - die("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 when removing " - "%d leading pathname components (line %d)" , p_value, linenr); - patch->old_name = patch->new_name = patch->def_name; - } - patch->is_toplevel_relative = 1; - *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 ("@@ -0,0 +1 @@\n" is the shortest). - */ - 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; -} - -static void record_ws_error(unsigned result, const char *line, int len, int linenr) -{ - char *err; - - if (!result) - return; - - whitespace_error++; - if (squelch_whitespace_errors && - squelch_whitespace_errors < whitespace_error) - return; - - err = whitespace_error_string(result); - fprintf(stderr, "%s:%d: %s.\n%.*s\n", - patch_input_file, linenr, err, len, line); - free(err); -} - -static void check_whitespace(const char *line, int len, unsigned ws_rule) -{ - unsigned result = ws_check(line + 1, len - 1, ws_rule); - - record_ws_error(result, line + 1, len - 2, linenr); -} - -/* - * 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; - if (offset > 0 && patch->recount) - recount_diff(line + offset, size - offset, fragment); - oldlines = fragment->oldlines; - newlines = fragment->newlines; - leading = 0; - trailing = 0; - - /* Parse the thing.. */ - line += len; - size -= len; - linenr++; - added = deleted = 0; - for (offset = len; - 0 < size; - 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 '\n': /* newer GNU diff, an empty context line */ - case ' ': - oldlines--; - newlines--; - if (!deleted && !added) - leading++; - trailing++; - break; - case '-': - if (apply_in_reverse && - ws_error_action != nowarn_ws_error) - check_whitespace(line, len, patch->ws_rule); - deleted++; - oldlines--; - trailing = 0; - break; - case '+': - if (!apply_in_reverse && - ws_error_action != nowarn_ws_error) - check_whitespace(line, len, patch->ws_rule); - 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; - - if (0 < patch->is_new && oldlines) - return error("new file depends on old contents"); - if (0 < patch->is_delete && newlines) - return error("deleted file still has contents"); - return offset; -} - -static int parse_single_patch(char *line, unsigned long size, struct patch *patch) -{ - unsigned long offset = 0; - unsigned long oldlines = 0, newlines = 0, context = 0; - struct fragment **fragp = &patch->fragments; - - while (size > 4 && !memcmp(line, "@@ -", 4)) { - struct fragment *fragment; - int len; - - fragment = xcalloc(1, sizeof(*fragment)); - fragment->linenr = linenr; - len = parse_fragment(line, size, patch, fragment); - if (len <= 0) - die("corrupt patch at line %d", linenr); - fragment->patch = line; - fragment->size = len; - oldlines += fragment->oldlines; - newlines += fragment->newlines; - context += fragment->leading + fragment->trailing; - - *fragp = fragment; - fragp = &fragment->next; - - offset += len; - line += len; - size -= len; - } - - /* - * If something was removed (i.e. we have old-lines) it cannot - * be creation, and if something was added it cannot be - * deletion. However, the reverse is not true; --unified=0 - * patches that only add are not necessarily creation even - * though they do not have any old lines, and ones that only - * delete are not necessarily deletion. - * - * Unfortunately, a real creation/deletion patch do _not_ have - * any context line by definition, so we cannot safely tell it - * apart with --unified=0 insanity. At least if the patch has - * more than one hunk it is not creation or deletion. - */ - if (patch->is_new < 0 && - (oldlines || (patch->fragments && patch->fragments->next))) - patch->is_new = 0; - if (patch->is_delete < 0 && - (newlines || (patch->fragments && patch->fragments->next))) - patch->is_delete = 0; - - if (0 < patch->is_new && oldlines) - die("new file %s depends on old contents", patch->new_name); - if (0 < patch->is_delete && newlines) - die("deleted file %s still has contents", patch->old_name); - if (!patch->is_delete && !newlines && context) - fprintf(stderr, "** warning: file %s becomes empty but " - "is not deleted\n", patch->new_name); - - 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 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; - git_inflate_init(&stream); - st = git_inflate(&stream, Z_FINISH); - git_inflate_end(&stream); - if ((st != Z_STREAM_END) || stream.total_out != inflated_size) { - free(out); - return NULL; - } - return out; -} - -static struct fragment *parse_binary_hunk(char **buf_p, - unsigned long *sz_p, - int *status_p, - int *used_p) -{ - /* - * Expect a line that begins with binary patch method ("literal" - * or "delta"), followed by the length of data before deflating. - * a sequence of 'length-byte' followed by base-85 encoded data - * should follow, terminated by a newline. - * - * 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. - */ - int llen, used; - unsigned long size = *sz_p; - char *buffer = *buf_p; - int patch_method; - unsigned long origlen; - char *data = NULL; - int hunk_size = 0; - struct fragment *frag; - - llen = linelen(buffer, size); - used = llen; - - *status_p = 0; - - if (!prefixcmp(buffer, "delta ")) { - patch_method = BINARY_DELTA_DEFLATED; - origlen = strtoul(buffer + 6, NULL, 10); - } - else if (!prefixcmp(buffer, "literal ")) { - patch_method = BINARY_LITERAL_DEFLATED; - origlen = strtoul(buffer + 8, NULL, 10); - } - else - return NULL; - - linenr++; - buffer += llen; - while (1) { - int byte_length, max_byte_length, newsize; - llen = linelen(buffer, size); - used += llen; - linenr++; - if (llen == 1) { - /* consume the blank line */ - buffer++; - size--; - 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 = hunk_size + byte_length; - data = xrealloc(data, newsize); - if (decode_85(data + hunk_size, buffer + 1, byte_length)) - goto corrupt; - hunk_size = newsize; - buffer += llen; - size -= llen; - } - - frag = xcalloc(1, sizeof(*frag)); - frag->patch = inflate_it(data, hunk_size, origlen); - if (!frag->patch) - goto corrupt; - free(data); - frag->size = origlen; - *buf_p = buffer; - *sz_p = size; - *used_p = used; - frag->binary_patch_method = patch_method; - return frag; - - corrupt: - free(data); - *status_p = -1; - error("corrupt binary patch at line %d: %.*s", - linenr-1, llen-1, buffer); - return NULL; -} - -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 "literal" or - * "delta") and the length of data before deflating; a - * sequence of 'length-byte' followed by base-85 encoded data - * follows. - * - * When a binary patch is reversible, there is another binary - * hunk in the same format, starting with patch method (either - * "literal" or "delta") with the length of data, and a sequence - * of length-byte + base-85 encoded data, terminated with another - * empty line. This data, when applied to the postimage, produces - * the preimage. - */ - struct fragment *forward; - struct fragment *reverse; - int status; - int used, used_1; - - forward = parse_binary_hunk(&buffer, &size, &status, &used); - if (!forward && !status) - /* there has to be one hunk (forward hunk) */ - return error("unrecognized binary patch at line %d", linenr-1); - if (status) - /* otherwise we already gave an error message */ - return status; - - reverse = parse_binary_hunk(&buffer, &size, &status, &used_1); - if (reverse) - used += used_1; - else if (status) { - /* - * Not having reverse hunk is not an error, but having - * a corrupt reverse hunk is. - */ - free((void*) forward->patch); - free(forward); - return status; - } - forward->next = reverse; - patch->fragments = forward; - patch->is_binary = 1; - return used; -} - -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; - - patch->ws_rule = whitespace_rule(patch->new_name - ? patch->new_name - : patch->old_name); - - 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 text patch - * without metadata change. A binary patch appears - * empty to us here. - */ - if ((apply || check) && - (!patch->is_binary && !metadata_changes(patch))) - die("patch with only garbage at line %d", linenr); - } - - return offset + hdrsize + patchsize; -} - -#define swap(a,b) myswap((a),(b),sizeof(a)) - -#define myswap(a, b, size) do { \ - unsigned char mytmp[size]; \ - memcpy(mytmp, &a, size); \ - memcpy(&a, &b, size); \ - memcpy(&b, mytmp, size); \ -} while (0) - -static void reverse_patches(struct patch *p) -{ - for (; p; p = p->next) { - struct fragment *frag = p->fragments; - - swap(p->new_name, p->old_name); - swap(p->new_mode, p->old_mode); - swap(p->is_new, p->is_delete); - swap(p->lines_added, p->lines_deleted); - swap(p->old_sha1_prefix, p->new_sha1_prefix); - - for (; frag; frag = frag->next) { - swap(frag->newpos, frag->oldpos); - swap(frag->newlines, frag->oldlines); - } - } -} - -static const char pluses[] = -"++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"; -static const char minuses[]= -"----------------------------------------------------------------------"; - -static void show_stats(struct patch *patch) -{ - struct strbuf qname = STRBUF_INIT; - char *cp = patch->new_name ? patch->new_name : patch->old_name; - int max, add, del; - - quote_c_style(cp, &qname, NULL, 0); - - /* - * "scale" the filename - */ - max = max_len; - if (max > 50) - max = 50; - - if (qname.len > max) { - cp = strchr(qname.buf + qname.len + 3 - max, '/'); - if (!cp) - cp = qname.buf + qname.len + 3 - max; - strbuf_splice(&qname, 0, cp - qname.buf, "...", 3); - } - - if (patch->is_binary) { - printf(" %-*s | Bin\n", max, qname.buf); - strbuf_release(&qname); - return; - } - - printf(" %-*s |", max, qname.buf); - strbuf_release(&qname); - - /* - * scale the add/delete - */ - max = max + max_change > 70 ? 70 - max : max_change; - add = patch->lines_added; - del = patch->lines_deleted; - - if (max_change > 0) { - int total = ((add + del) * max + max_change / 2) / max_change; - add = (add * max + max_change / 2) / max_change; - del = total - add; - } - printf("%5d %.*s%.*s\n", patch->lines_added + patch->lines_deleted, - add, pluses, del, minuses); -} - -static int read_old_data(struct stat *st, const char *path, struct strbuf *buf) -{ - switch (st->st_mode & S_IFMT) { - case S_IFLNK: - if (strbuf_readlink(buf, path, st->st_size) < 0) - return error("unable to read symlink %s", path); - return 0; - case S_IFREG: - if (strbuf_read_file(buf, path, st->st_size) != st->st_size) - return error("unable to open or read %s", path); - convert_to_git(path, buf->buf, buf->len, buf, 0); - return 0; - default: - return -1; - } -} - -/* - * Update the preimage, and the common lines in postimage, - * from buffer buf of length len. If postlen is 0 the postimage - * is updated in place, otherwise it's updated on a new buffer - * of length postlen - */ - -static void update_pre_post_images(struct image *preimage, - struct image *postimage, - char *buf, - size_t len, size_t postlen) -{ - int i, ctx; - char *new, *old, *fixed; - struct image fixed_preimage; - - /* - * Update the preimage with whitespace fixes. Note that we - * are not losing preimage->buf -- apply_one_fragment() will - * free "oldlines". - */ - prepare_image(&fixed_preimage, buf, len, 1); - assert(fixed_preimage.nr == preimage->nr); - for (i = 0; i < preimage->nr; i++) - fixed_preimage.line[i].flag = preimage->line[i].flag; - free(preimage->line_allocated); - *preimage = fixed_preimage; - - /* - * Adjust the common context lines in postimage. This can be - * done in-place when we are just doing whitespace fixing, - * which does not make the string grow, but needs a new buffer - * when ignoring whitespace causes the update, since in this case - * we could have e.g. tabs converted to multiple spaces. - * We trust the caller to tell us if the update can be done - * in place (postlen==0) or not. - */ - old = postimage->buf; - if (postlen) - new = postimage->buf = xmalloc(postlen); - else - new = old; - fixed = preimage->buf; - for (i = ctx = 0; i < postimage->nr; i++) { - size_t len = postimage->line[i].len; - if (!(postimage->line[i].flag & LINE_COMMON)) { - /* an added line -- no counterparts in preimage */ - memmove(new, old, len); - old += len; - new += len; - continue; - } - - /* a common context -- skip it in the original postimage */ - old += len; - - /* and find the corresponding one in the fixed preimage */ - while (ctx < preimage->nr && - !(preimage->line[ctx].flag & LINE_COMMON)) { - fixed += preimage->line[ctx].len; - ctx++; - } - if (preimage->nr <= ctx) - die("oops"); - - /* and copy it in, while fixing the line length */ - len = preimage->line[ctx].len; - memcpy(new, fixed, len); - new += len; - fixed += len; - postimage->line[i].len = len; - ctx++; - } - - /* Fix the length of the whole thing */ - postimage->len = new - postimage->buf; -} - -static int match_fragment(struct image *img, - struct image *preimage, - struct image *postimage, - unsigned long try, - int try_lno, - unsigned ws_rule, - int match_beginning, int match_end) -{ - int i; - char *fixed_buf, *buf, *orig, *target; - - if (preimage->nr + try_lno > img->nr) - return 0; - - if (match_beginning && try_lno) - return 0; - - if (match_end && preimage->nr + try_lno != img->nr) - return 0; - - /* Quick hash check */ - for (i = 0; i < preimage->nr; i++) - if (preimage->line[i].hash != img->line[try_lno + i].hash) - return 0; - - /* - * Do we have an exact match? If we were told to match - * at the end, size must be exactly at try+fragsize, - * otherwise try+fragsize must be still within the preimage, - * and either case, the old piece should match the preimage - * exactly. - */ - if ((match_end - ? (try + preimage->len == img->len) - : (try + preimage->len <= img->len)) && - !memcmp(img->buf + try, preimage->buf, preimage->len)) - return 1; - - /* - * No exact match. If we are ignoring whitespace, run a line-by-line - * fuzzy matching. We collect all the line length information because - * we need it to adjust whitespace if we match. - */ - if (ws_ignore_action == ignore_ws_change) { - size_t imgoff = 0; - size_t preoff = 0; - size_t postlen = postimage->len; - for (i = 0; i < preimage->nr; i++) { - size_t prelen = preimage->line[i].len; - size_t imglen = img->line[try_lno+i].len; - - if (!fuzzy_matchlines(img->buf + try + imgoff, imglen, - preimage->buf + preoff, prelen)) - return 0; - if (preimage->line[i].flag & LINE_COMMON) - postlen += imglen - prelen; - imgoff += imglen; - preoff += prelen; - } - - /* - * Ok, the preimage matches with whitespace fuzz. Update it and - * the common postimage lines to use the same whitespace as the - * target. imgoff now holds the true length of the target that - * matches the preimage, and we need to update the line lengths - * of the preimage to match the target ones. - */ - fixed_buf = xmalloc(imgoff); - memcpy(fixed_buf, img->buf + try, imgoff); - for (i = 0; i < preimage->nr; i++) - preimage->line[i].len = img->line[try_lno+i].len; - - /* - * Update the preimage buffer and the postimage context lines. - */ - update_pre_post_images(preimage, postimage, - fixed_buf, imgoff, postlen); - return 1; - } - - if (ws_error_action != correct_ws_error) - return 0; - - /* - * The hunk does not apply byte-by-byte, but the hash says - * it might with whitespace fuzz. We haven't been asked to - * ignore whitespace, we were asked to correct whitespace - * errors, so let's try matching after whitespace correction. - */ - fixed_buf = xmalloc(preimage->len + 1); - buf = fixed_buf; - orig = preimage->buf; - target = img->buf + try; - for (i = 0; i < preimage->nr; i++) { - size_t fixlen; /* length after fixing the preimage */ - size_t oldlen = preimage->line[i].len; - size_t tgtlen = img->line[try_lno + i].len; - size_t tgtfixlen; /* length after fixing the target line */ - char tgtfixbuf[1024], *tgtfix; - int match; - - /* Try fixing the line in the preimage */ - fixlen = ws_fix_copy(buf, orig, oldlen, ws_rule, NULL); - - /* Try fixing the line in the target */ - if (sizeof(tgtfixbuf) > tgtlen) - tgtfix = tgtfixbuf; - else - tgtfix = xmalloc(tgtlen); - tgtfixlen = ws_fix_copy(tgtfix, target, tgtlen, ws_rule, NULL); - - /* - * If they match, either the preimage was based on - * a version before our tree fixed whitespace breakage, - * or we are lacking a whitespace-fix patch the tree - * the preimage was based on already had (i.e. target - * has whitespace breakage, the preimage doesn't). - * In either case, we are fixing the whitespace breakages - * so we might as well take the fix together with their - * real change. - */ - match = (tgtfixlen == fixlen && !memcmp(tgtfix, buf, fixlen)); - - if (tgtfix != tgtfixbuf) - free(tgtfix); - if (!match) - goto unmatch_exit; - - orig += oldlen; - buf += fixlen; - target += tgtlen; - } - - /* - * Yes, the preimage is based on an older version that still - * has whitespace breakages unfixed, and fixing them makes the - * hunk match. Update the context lines in the postimage. - */ - update_pre_post_images(preimage, postimage, - fixed_buf, buf - fixed_buf, 0); - return 1; - - unmatch_exit: - free(fixed_buf); - return 0; -} - -static int find_pos(struct image *img, - struct image *preimage, - struct image *postimage, - int line, - unsigned ws_rule, - int match_beginning, int match_end) -{ - int i; - unsigned long backwards, forwards, try; - int backwards_lno, forwards_lno, try_lno; - - if (preimage->nr > img->nr) - return -1; - - /* - * If match_beginning or match_end is specified, there is no - * point starting from a wrong line that will never match and - * wander around and wait for a match at the specified end. - */ - if (match_beginning) - line = 0; - else if (match_end) - line = img->nr - preimage->nr; - - if (line > img->nr) - line = img->nr; - - try = 0; - for (i = 0; i < line; i++) - try += img->line[i].len; - - /* - * 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 = try; - backwards_lno = line; - forwards = try; - forwards_lno = line; - try_lno = line; - - for (i = 0; ; i++) { - if (match_fragment(img, preimage, postimage, - try, try_lno, ws_rule, - match_beginning, match_end)) - return try_lno; - - again: - if (backwards_lno == 0 && forwards_lno == img->nr) - break; - - if (i & 1) { - if (backwards_lno == 0) { - i++; - goto again; - } - backwards_lno--; - backwards -= img->line[backwards_lno].len; - try = backwards; - try_lno = backwards_lno; - } else { - if (forwards_lno == img->nr) { - i++; - goto again; - } - forwards += img->line[forwards_lno].len; - forwards_lno++; - try = forwards; - try_lno = forwards_lno; - } - - } - return -1; -} - -static void remove_first_line(struct image *img) -{ - img->buf += img->line[0].len; - img->len -= img->line[0].len; - img->line++; - img->nr--; -} - -static void remove_last_line(struct image *img) -{ - img->len -= img->line[--img->nr].len; -} - -static void update_image(struct image *img, - int applied_pos, - struct image *preimage, - struct image *postimage) -{ - /* - * remove the copy of preimage at offset in img - * and replace it with postimage - */ - int i, nr; - size_t remove_count, insert_count, applied_at = 0; - char *result; - - for (i = 0; i < applied_pos; i++) - applied_at += img->line[i].len; - - remove_count = 0; - for (i = 0; i < preimage->nr; i++) - remove_count += img->line[applied_pos + i].len; - insert_count = postimage->len; - - /* Adjust the contents */ - result = xmalloc(img->len + insert_count - remove_count + 1); - memcpy(result, img->buf, applied_at); - memcpy(result + applied_at, postimage->buf, postimage->len); - memcpy(result + applied_at + postimage->len, - img->buf + (applied_at + remove_count), - img->len - (applied_at + remove_count)); - free(img->buf); - img->buf = result; - img->len += insert_count - remove_count; - result[img->len] = '\0'; - - /* Adjust the line table */ - nr = img->nr + postimage->nr - preimage->nr; - if (preimage->nr < postimage->nr) { - /* - * NOTE: this knows that we never call remove_first_line() - * on anything other than pre/post image. - */ - img->line = xrealloc(img->line, nr * sizeof(*img->line)); - img->line_allocated = img->line; - } - if (preimage->nr != postimage->nr) - memmove(img->line + applied_pos + postimage->nr, - img->line + applied_pos + preimage->nr, - (img->nr - (applied_pos + preimage->nr)) * - sizeof(*img->line)); - memcpy(img->line + applied_pos, - postimage->line, - postimage->nr * sizeof(*img->line)); - img->nr = nr; -} - -static int apply_one_fragment(struct image *img, struct fragment *frag, - int inaccurate_eof, unsigned ws_rule) -{ - int match_beginning, match_end; - const char *patch = frag->patch; - int size = frag->size; - char *old, *new, *oldlines, *newlines; - int new_blank_lines_at_end = 0; - unsigned long leading, trailing; - int pos, applied_pos; - struct image preimage; - struct image postimage; - - memset(&preimage, 0, sizeof(preimage)); - memset(&postimage, 0, sizeof(postimage)); - oldlines = xmalloc(size); - newlines = xmalloc(size); - - old = oldlines; - new = newlines; - while (size > 0) { - char first; - int len = linelen(patch, size); - int plen, added; - int added_blank_line = 0; - int is_blank_context = 0; - - 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--; - first = *patch; - if (apply_in_reverse) { - if (first == '-') - first = '+'; - else if (first == '+') - first = '-'; - } - - switch (first) { - case '\n': - /* Newer GNU diff, empty context line */ - if (plen < 0) - /* ... followed by '\No newline'; nothing */ - break; - *old++ = '\n'; - *new++ = '\n'; - add_line_info(&preimage, "\n", 1, LINE_COMMON); - add_line_info(&postimage, "\n", 1, LINE_COMMON); - is_blank_context = 1; - break; - case ' ': - if (plen && (ws_rule & WS_BLANK_AT_EOF) && - ws_blank_line(patch + 1, plen, ws_rule)) - is_blank_context = 1; - case '-': - memcpy(old, patch + 1, plen); - add_line_info(&preimage, old, plen, - (first == ' ' ? LINE_COMMON : 0)); - old += plen; - if (first == '-') - break; - /* Fall-through for ' ' */ - case '+': - /* --no-add does not add new lines */ - if (first == '+' && no_add) - break; - - if (first != '+' || - !whitespace_error || - ws_error_action != correct_ws_error) { - memcpy(new, patch + 1, plen); - added = plen; - } - else { - added = ws_fix_copy(new, patch + 1, plen, ws_rule, &applied_after_fixing_ws); - } - add_line_info(&postimage, new, added, - (first == '+' ? 0 : LINE_COMMON)); - new += added; - if (first == '+' && - (ws_rule & WS_BLANK_AT_EOF) && - ws_blank_line(patch + 1, plen, ws_rule)) - added_blank_line = 1; - break; - case '@': case '\\': - /* Ignore it, we already handled it */ - break; - default: - if (apply_verbosely) - error("invalid start of line: '%c'", first); - return -1; - } - if (added_blank_line) - new_blank_lines_at_end++; - else if (is_blank_context) - ; - else - new_blank_lines_at_end = 0; - patch += len; - size -= len; - } - if (inaccurate_eof && - old > oldlines && old[-1] == '\n' && - new > newlines && new[-1] == '\n') { - old--; - new--; - } - - leading = frag->leading; - trailing = frag->trailing; - - /* - * A hunk to change lines at the beginning would begin with - * @@ -1,L +N,M @@ - * but we need to be careful. -U0 that inserts before the second - * line also has this pattern. - * - * And a hunk to add to an empty file would begin with - * @@ -0,0 +N,M @@ - * - * In other words, a hunk that is (frag->oldpos <= 1) with or - * without leading context must match at the beginning. - */ - match_beginning = (!frag->oldpos || - (frag->oldpos == 1 && !unidiff_zero)); - - /* - * A hunk without trailing lines must match at the end. - * However, we simply cannot tell if a hunk must match end - * from the lack of trailing lines if the patch was generated - * with unidiff without any context. - */ - match_end = !unidiff_zero && !trailing; - - pos = frag->newpos ? (frag->newpos - 1) : 0; - preimage.buf = oldlines; - preimage.len = old - oldlines; - postimage.buf = newlines; - postimage.len = new - newlines; - preimage.line = preimage.line_allocated; - postimage.line = postimage.line_allocated; - - for (;;) { - - applied_pos = find_pos(img, &preimage, &postimage, pos, - ws_rule, match_beginning, match_end); - - if (applied_pos >= 0) - break; - - /* Am I at my context limits? */ - if ((leading <= p_context) && (trailing <= p_context)) - break; - if (match_beginning || match_end) { - match_beginning = match_end = 0; - continue; - } - - /* - * 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(&preimage); - remove_first_line(&postimage); - pos--; - leading--; - } - if (trailing > leading) { - remove_last_line(&preimage); - remove_last_line(&postimage); - trailing--; - } - } - - if (applied_pos >= 0) { - if (new_blank_lines_at_end && - preimage.nr + applied_pos == img->nr && - (ws_rule & WS_BLANK_AT_EOF) && - ws_error_action != nowarn_ws_error) { - record_ws_error(WS_BLANK_AT_EOF, "+", 1, frag->linenr); - if (ws_error_action == correct_ws_error) { - while (new_blank_lines_at_end--) - remove_last_line(&postimage); - } - /* - * We would want to prevent write_out_results() - * from taking place in apply_patch() that follows - * the callchain led us here, which is: - * apply_patch->check_patch_list->check_patch-> - * apply_data->apply_fragments->apply_one_fragment - */ - if (ws_error_action == die_on_ws_error) - apply = 0; - } - - /* - * 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, applied_pos+1); - update_image(img, applied_pos, &preimage, &postimage); - } else { - if (apply_verbosely) - error("while searching for:\n%.*s", - (int)(old - oldlines), oldlines); - } - - free(oldlines); - free(newlines); - free(preimage.line_allocated); - free(postimage.line_allocated); - - return (applied_pos < 0); -} - -static int apply_binary_fragment(struct image *img, struct patch *patch) -{ - struct fragment *fragment = patch->fragments; - unsigned long len; - void *dst; - - /* Binary patch is irreversible without the optional second hunk */ - if (apply_in_reverse) { - if (!fragment->next) - return error("cannot reverse-apply a binary patch " - "without the reverse hunk to '%s'", - patch->new_name - ? patch->new_name : patch->old_name); - fragment = fragment->next; - } - switch (fragment->binary_patch_method) { - case BINARY_DELTA_DEFLATED: - dst = patch_delta(img->buf, img->len, fragment->patch, - fragment->size, &len); - if (!dst) - return -1; - clear_image(img); - img->buf = dst; - img->len = len; - return 0; - case BINARY_LITERAL_DEFLATED: - clear_image(img); - img->len = fragment->size; - img->buf = xmalloc(img->len+1); - memcpy(img->buf, fragment->patch, img->len); - img->buf[img->len] = '\0'; - return 0; - } - return -1; -} - -static int apply_binary(struct image *img, struct patch *patch) -{ - const char *name = patch->old_name ? patch->old_name : patch->new_name; - unsigned char sha1[20]; - - /* - * 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. - */ - hash_sha1_file(img->buf, img->len, blob_type, sha1); - 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 (img->len) - return error("the patch applies to an empty " - "'%s' but it is not empty", name); - } - - get_sha1_hex(patch->new_sha1_prefix, sha1); - if (is_null_sha1(sha1)) { - clear_image(img); - return 0; /* deletion patch */ - } - - if (has_sha1_file(sha1)) { - /* We already have the postimage */ - enum object_type type; - unsigned long size; - char *result; - - result = read_sha1_file(sha1, &type, &size); - if (!result) - return error("the necessary postimage %s for " - "'%s' cannot be read", - patch->new_sha1_prefix, name); - clear_image(img); - img->buf = result; - img->len = size; - } else { - /* - * We have verified buf matches the preimage; - * apply the patch data to it, which is stored - * in the patch->fragments->{patch,size}. - */ - if (apply_binary_fragment(img, patch)) - return error("binary patch does not apply to '%s'", - name); - - /* verify that the result matches */ - hash_sha1_file(img->buf, img->len, blob_type, sha1); - if (strcmp(sha1_to_hex(sha1), patch->new_sha1_prefix)) - return error("binary patch to '%s' creates incorrect result (expecting %s, got %s)", - name, patch->new_sha1_prefix, sha1_to_hex(sha1)); - } - - return 0; -} - -static int apply_fragments(struct image *img, struct patch *patch) -{ - struct fragment *frag = patch->fragments; - const char *name = patch->old_name ? patch->old_name : patch->new_name; - unsigned ws_rule = patch->ws_rule; - unsigned inaccurate_eof = patch->inaccurate_eof; - - if (patch->is_binary) - return apply_binary(img, patch); - - while (frag) { - if (apply_one_fragment(img, frag, inaccurate_eof, ws_rule)) { - error("patch failed: %s:%ld", name, frag->oldpos); - if (!apply_with_reject) - return -1; - frag->rejected = 1; - } - frag = frag->next; - } - return 0; -} - -static int read_file_or_gitlink(struct cache_entry *ce, struct strbuf *buf) -{ - if (!ce) - return 0; - - if (S_ISGITLINK(ce->ce_mode)) { - strbuf_grow(buf, 100); - strbuf_addf(buf, "Subproject commit %s\n", sha1_to_hex(ce->sha1)); - } else { - enum object_type type; - unsigned long sz; - char *result; - - result = read_sha1_file(ce->sha1, &type, &sz); - if (!result) - return -1; - /* XXX read_sha1_file NUL-terminates */ - strbuf_attach(buf, result, sz, sz + 1); - } - return 0; -} - -static struct patch *in_fn_table(const char *name) -{ - struct string_list_item *item; - - if (name == NULL) - return NULL; - - item = string_list_lookup(name, &fn_table); - if (item != NULL) - return (struct patch *)item->util; - - return NULL; -} - -/* - * item->util in the filename table records the status of the path. - * Usually it points at a patch (whose result records the contents - * of it after applying it), but it could be PATH_WAS_DELETED for a - * path that a previously applied patch has already removed. - */ - #define PATH_TO_BE_DELETED ((struct patch *) -2) -#define PATH_WAS_DELETED ((struct patch *) -1) - -static int to_be_deleted(struct patch *patch) -{ - return patch == PATH_TO_BE_DELETED; -} - -static int was_deleted(struct patch *patch) -{ - return patch == PATH_WAS_DELETED; -} - -static void add_to_fn_table(struct patch *patch) -{ - struct string_list_item *item; - - /* - * Always add new_name unless patch is a deletion - * This should cover the cases for normal diffs, - * file creations and copies - */ - if (patch->new_name != NULL) { - item = string_list_insert(patch->new_name, &fn_table); - item->util = patch; - } - - /* - * store a failure on rename/deletion cases because - * later chunks shouldn't patch old names - */ - if ((patch->new_name == NULL) || (patch->is_rename)) { - item = string_list_insert(patch->old_name, &fn_table); - item->util = PATH_WAS_DELETED; - } -} - -static void prepare_fn_table(struct patch *patch) -{ - /* - * store information about incoming file deletion - */ - while (patch) { - if ((patch->new_name == NULL) || (patch->is_rename)) { - struct string_list_item *item; - item = string_list_insert(patch->old_name, &fn_table); - item->util = PATH_TO_BE_DELETED; - } - patch = patch->next; - } -} - -static int apply_data(struct patch *patch, struct stat *st, struct cache_entry *ce) -{ - struct strbuf buf = STRBUF_INIT; - struct image image; - size_t len; - char *img; - struct patch *tpatch; - - if (!(patch->is_copy || patch->is_rename) && - (tpatch = in_fn_table(patch->old_name)) != NULL && !to_be_deleted(tpatch)) { - if (was_deleted(tpatch)) { - return error("patch %s has been renamed/deleted", - patch->old_name); - } - /* We have a patched copy in memory use that */ - strbuf_add(&buf, tpatch->result, tpatch->resultsize); - } else if (cached) { - if (read_file_or_gitlink(ce, &buf)) - return error("read of %s failed", patch->old_name); - } else if (patch->old_name) { - if (S_ISGITLINK(patch->old_mode)) { - if (ce) { - read_file_or_gitlink(ce, &buf); - } else { - /* - * There is no way to apply subproject - * patch without looking at the index. - */ - patch->fragments = NULL; - } - } else { - if (read_old_data(st, patch->old_name, &buf)) - return error("read of %s failed", patch->old_name); - } - } - - img = strbuf_detach(&buf, &len); - prepare_image(&image, img, len, !patch->is_binary); - - if (apply_fragments(&image, patch) < 0) - return -1; /* note with --reject this succeeds. */ - patch->result = image.buf; - patch->resultsize = image.len; - add_to_fn_table(patch); - free(image.line_allocated); - - if (0 < patch->is_delete && patch->resultsize) - return error("removal patch leaves file contents"); - - return 0; -} - -static int check_to_create_blob(const char *new_name, int ok_if_exists) -{ - struct stat nst; - if (!lstat(new_name, &nst)) { - if (S_ISDIR(nst.st_mode) || ok_if_exists) - return 0; - /* - * A leading component of new_name might be a symlink - * that is going to be removed with this patch, but - * still pointing at somewhere that has the path. - * In such a case, path "new_name" does not exist as - * far as git is concerned. - */ - if (has_symlink_leading_path(new_name, strlen(new_name))) - return 0; - - return error("%s: already exists in working directory", new_name); - } - else if ((errno != ENOENT) && (errno != ENOTDIR)) - return error("%s: %s", new_name, strerror(errno)); - return 0; -} - -static int verify_index_match(struct cache_entry *ce, struct stat *st) -{ - if (S_ISGITLINK(ce->ce_mode)) { - if (!S_ISDIR(st->st_mode)) - return -1; - return 0; - } - return ce_match_stat(ce, st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE); -} - -static int check_preimage(struct patch *patch, struct cache_entry **ce, struct stat *st) -{ - const char *old_name = patch->old_name; - struct patch *tpatch = NULL; - int stat_ret = 0; - unsigned st_mode = 0; - - /* - * Make sure that we do not have local modifications from the - * index when we are looking at the index. Also make sure - * we have the preimage file to be patched in the work tree, - * unless --cached, which tells git to apply only in the index. - */ - if (!old_name) - return 0; - - assert(patch->is_new <= 0); - - if (!(patch->is_copy || patch->is_rename) && - (tpatch = in_fn_table(old_name)) != NULL && !to_be_deleted(tpatch)) { - if (was_deleted(tpatch)) - return error("%s: has been deleted/renamed", old_name); - st_mode = tpatch->new_mode; - } else if (!cached) { - stat_ret = lstat(old_name, st); - if (stat_ret && errno != ENOENT) - return error("%s: %s", old_name, strerror(errno)); - } - - if (to_be_deleted(tpatch)) - tpatch = NULL; - - if (check_index && !tpatch) { - int pos = cache_name_pos(old_name, strlen(old_name)); - if (pos < 0) { - if (patch->is_new < 0) - goto is_new; - return error("%s: does not exist in index", old_name); - } - *ce = active_cache[pos]; - if (stat_ret < 0) { - struct checkout costate; - /* 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 && verify_index_match(*ce, st)) - return error("%s: does not match index", old_name); - if (cached) - st_mode = (*ce)->ce_mode; - } else if (stat_ret < 0) { - if (patch->is_new < 0) - goto is_new; - return error("%s: %s", old_name, strerror(errno)); - } - - if (!cached && !tpatch) - st_mode = ce_mode_from_stat(*ce, 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) - warning("%s has type %o, expected %o", - old_name, st_mode, patch->old_mode); - if (!patch->new_mode && !patch->is_delete) - patch->new_mode = st_mode; - return 0; - - is_new: - patch->is_new = 1; - patch->is_delete = 0; - patch->old_name = NULL; - 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; - struct patch *tpatch; - int ok_if_exists; - int status; - - patch->rejected = 1; /* we will drop this after we succeed */ - - status = check_preimage(patch, &ce, &st); - if (status) - return status; - old_name = patch->old_name; - - if ((tpatch = in_fn_table(new_name)) && - (was_deleted(tpatch) || to_be_deleted(tpatch))) - /* - * A type-change diff is always split into a patch to - * delete old, immediately followed by a patch to - * create new (see diff.c::run_diff()); in such a case - * it is Ok that the entry to be deleted by the - * previous patch is still in the working tree and in - * the index. - */ - ok_if_exists = 1; - else - ok_if_exists = 0; - - if (new_name && - ((0 < patch->is_new) | (0 < patch->is_rename) | patch->is_copy)) { - if (check_index && - cache_name_pos(new_name, strlen(new_name)) >= 0 && - !ok_if_exists) - return error("%s: already exists in index", new_name); - if (!cached) { - int err = check_to_create_blob(new_name, ok_if_exists); - if (err) - return err; - } - if (!patch->new_mode) { - if (0 < 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); - patch->rejected = 0; - return 0; -} - -static int check_patch_list(struct patch *patch) -{ - int err = 0; - - prepare_fn_table(patch); - while (patch) { - if (apply_verbosely) - say_patch_name(stderr, - "Checking patch ", patch, "...\n"); - err |= check_patch(patch); - patch = patch->next; - } - return err; -} - -/* This function tries to read the sha1 from the current index */ -static int get_current_sha1(const char *path, unsigned char *sha1) -{ - int pos; - - if (read_cache() < 0) - return -1; - pos = cache_name_pos(path, strlen(path)); - if (pos < 0) - return -1; - hashcpy(sha1, active_cache[pos]->sha1); - return 0; -} - -/* Build an index that contains the just the files needed for a 3way merge */ -static void build_fake_ancestor(struct patch *list, const char *filename) -{ - struct patch *patch; - struct index_state result = { NULL }; - int fd; - - /* 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]; - struct cache_entry *ce; - const char *name; - - name = patch->old_name ? patch->old_name : patch->new_name; - if (0 < patch->is_new) - continue; - else if (get_sha1(patch->old_sha1_prefix, sha1)) - /* git diff has no index line for mode/type changes */ - if (!patch->lines_added && !patch->lines_deleted) { - if (get_current_sha1(patch->new_name, sha1) || - get_current_sha1(patch->old_name, sha1)) - die("mode change for %s, which is not " - "in current HEAD", name); - sha1_ptr = sha1; - } else - die("sha1 information is lacking or useless " - "(%s).", name); - else - sha1_ptr = sha1; - - ce = make_cache_entry(patch->old_mode, sha1_ptr, name, 0, 0); - if (!ce) - die("make_cache_entry failed for path '%s'", name); - if (add_index_entry(&result, ce, ADD_CACHE_OK_TO_ADD)) - die ("Could not add %s to temporary index", name); - } - - fd = open(filename, O_WRONLY | O_CREAT, 0666); - if (fd < 0 || write_index(&result, fd) || close(fd)) - die ("Could not write temporary index to %s", filename); - - discard_index(&result); -} - -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; - if (patch->is_binary) - printf("-\t-\t"); - else - printf("%d\t%d\t", patch->lines_added, patch->lines_deleted); - write_name_quoted(name, stdout, line_termination); - } -} - -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, int rmdir_empty) -{ - if (update_index) { - if (remove_file_from_cache(patch->old_name) < 0) - die("unable to remove %s from index", patch->old_name); - } - if (!cached) { - if (S_ISGITLINK(patch->old_mode)) { - if (rmdir(patch->old_name)) - warning("unable to remove submodule %s", - patch->old_name); - } else if (!unlink_or_warn(patch->old_name) && rmdir_empty) { - remove_path(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 (!update_index) - return; - - ce = xcalloc(1, ce_size); - memcpy(ce->name, path, namelen); - ce->ce_mode = create_ce_mode(mode); - ce->ce_flags = namelen; - if (S_ISGITLINK(mode)) { - const char *s = buf; - - if (get_sha1_hex(s + strlen("Subproject commit "), ce->sha1)) - die("corrupt patch for subproject %s", path); - } else { - if (!cached) { - if (lstat(path, &st) < 0) - die_errno("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; - struct strbuf nbuf = STRBUF_INIT; - - if (S_ISGITLINK(mode)) { - struct stat st; - if (!lstat(path, &st) && S_ISDIR(st.st_mode)) - return 0; - return mkdir(path, 0777); - } - - if (has_symlinks && S_ISLNK(mode)) - /* Although buf:size is counted string, it also is NUL - * terminated. - */ - return symlink(buf, path); - - fd = open(path, O_CREAT | O_EXCL | O_WRONLY, (mode & 0100) ? 0777 : 0666); - if (fd < 0) - return -1; - - if (convert_to_working_tree(path, buf, size, &nbuf)) { - size = nbuf.len; - buf = nbuf.buf; - } - write_or_die(fd, buf, size); - strbuf_release(&nbuf); - - if (close(fd) < 0) - die_errno("closing file '%s'", path); - 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 || errno == EACCES) { - /* We may be trying to create a file where a directory - * used to be. - */ - struct stat st; - if (!lstat(path, &st) && (!S_ISDIR(st.st_mode) || !rmdir(path))) - errno = EEXIST; - } - - if (errno == EEXIST) { - unsigned int nr = getpid(); - - for (;;) { - char newpath[PATH_MAX]; - mksnpath(newpath, sizeof(newpath), "%s~%u", path, nr); - if (!try_create_file(newpath, mode, buf, size)) { - if (!rename(newpath, path)) - return; - unlink_or_warn(newpath); - break; - } - if (errno != EEXIST) - break; - ++nr; - } - } - die_errno("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); -} - -/* phase zero is to remove, phase one is to create */ -static void write_out_one_result(struct patch *patch, int phase) -{ - if (patch->is_delete > 0) { - if (phase == 0) - remove_file(patch, 1); - return; - } - if (patch->is_new > 0 || patch->is_copy) { - if (phase == 1) - create_file(patch); - return; - } - /* - * Rename or modification boils down to the same - * thing: remove the old, write the new - */ - if (phase == 0) - remove_file(patch, patch->is_rename); - if (phase == 1) - create_file(patch); -} - -static int write_out_one_reject(struct patch *patch) -{ - FILE *rej; - char namebuf[PATH_MAX]; - struct fragment *frag; - int cnt = 0; - - for (cnt = 0, frag = patch->fragments; frag; frag = frag->next) { - if (!frag->rejected) - continue; - cnt++; - } - - if (!cnt) { - if (apply_verbosely) - say_patch_name(stderr, - "Applied patch ", patch, " cleanly.\n"); - return 0; - } - - /* This should not happen, because a removal patch that leaves - * contents are marked "rejected" at the patch level. - */ - if (!patch->new_name) - die("internal error"); - - /* Say this even without --verbose */ - say_patch_name(stderr, "Applying patch ", patch, " with"); - fprintf(stderr, " %d rejects...\n", cnt); - - cnt = strlen(patch->new_name); - if (ARRAY_SIZE(namebuf) <= cnt + 5) { - cnt = ARRAY_SIZE(namebuf) - 5; - warning("truncating .rej filename to %.*s.rej", - cnt - 1, patch->new_name); - } - memcpy(namebuf, patch->new_name, cnt); - memcpy(namebuf + cnt, ".rej", 5); - - rej = fopen(namebuf, "w"); - if (!rej) - return error("cannot open %s: %s", namebuf, strerror(errno)); - - /* Normal git tools never deal with .rej, so do not pretend - * this is a git patch by saying --git nor give extended - * headers. While at it, maybe please "kompare" that wants - * the trailing TAB and some garbage at the end of line ;-). - */ - fprintf(rej, "diff a/%s b/%s\t(rejected hunks)\n", - patch->new_name, patch->new_name); - for (cnt = 1, frag = patch->fragments; - frag; - cnt++, frag = frag->next) { - if (!frag->rejected) { - fprintf(stderr, "Hunk #%d applied cleanly.\n", cnt); - continue; - } - fprintf(stderr, "Rejected hunk #%d.\n", cnt); - fprintf(rej, "%.*s", frag->size, frag->patch); - if (frag->patch[frag->size-1] != '\n') - fputc('\n', rej); - } - fclose(rej); - return -1; -} - -static int write_out_results(struct patch *list, int skipped_patch) -{ - int phase; - int errs = 0; - struct patch *l; - - if (!list && !skipped_patch) - return error("No changes"); - - for (phase = 0; phase < 2; phase++) { - l = list; - while (l) { - if (l->rejected) - errs = 1; - else { - write_out_one_result(l, phase); - if (phase == 1 && write_out_one_reject(l)) - errs = 1; - } - l = l->next; - } - } - return errs; -} - -static struct lock_file lock_file; - -static struct string_list limit_by_name; -static int has_include; -static void add_name_limit(const char *name, int exclude) -{ - struct string_list_item *it; - - it = string_list_append(name, &limit_by_name); - it->util = exclude ? NULL : (void *) 1; -} - -static int use_patch(struct patch *p) -{ - const char *pathname = p->new_name ? p->new_name : p->old_name; - int i; - - /* Paths outside are not touched regardless of "--include" */ - if (0 < prefix_length) { - int pathlen = strlen(pathname); - if (pathlen <= prefix_length || - memcmp(prefix, pathname, prefix_length)) - return 0; - } - - /* See if it matches any of exclude/include rule */ - for (i = 0; i < limit_by_name.nr; i++) { - struct string_list_item *it = &limit_by_name.items[i]; - if (!fnmatch(it->string, pathname, 0)) - return (it->util != NULL); - } - - /* - * If we had any include, a path that does not match any rule is - * not used. Otherwise, we saw bunch of exclude rules (or none) - * and such a path is used. - */ - return !has_include; -} - - -static void prefix_one(char **name) -{ - char *old_name = *name; - if (!old_name) - return; - *name = xstrdup(prefix_filename(prefix, prefix_length, *name)); - free(old_name); -} - -static void prefix_patches(struct patch *p) -{ - if (!prefix || p->is_toplevel_relative) - return; - for ( ; p; p = p->next) { - if (p->new_name == p->old_name) { - char *prefixed = p->new_name; - prefix_one(&prefixed); - p->new_name = p->old_name = prefixed; - } - else { - prefix_one(&p->new_name); - prefix_one(&p->old_name); - } - } -} - -#define INACCURATE_EOF (1<<0) -#define RECOUNT (1<<1) - -static int apply_patch(int fd, const char *filename, int options) -{ - size_t offset; - struct strbuf buf = STRBUF_INIT; - struct patch *list = NULL, **listp = &list; - int skipped_patch = 0; - - /* FIXME - memory leak when using multiple patch files as inputs */ - memset(&fn_table, 0, sizeof(struct string_list)); - patch_input_file = filename; - read_patch_file(&buf, fd); - offset = 0; - while (offset < buf.len) { - struct patch *patch; - int nr; - - patch = xcalloc(1, sizeof(*patch)); - patch->inaccurate_eof = !!(options & INACCURATE_EOF); - patch->recount = !!(options & RECOUNT); - nr = parse_chunk(buf.buf + offset, buf.len - offset, patch); - if (nr < 0) - break; - if (apply_in_reverse) - reverse_patches(patch); - if (prefix) - prefix_patches(patch); - 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; - } - - if (whitespace_error && (ws_error_action == die_on_ws_error)) - apply = 0; - - update_index = check_index && apply; - if (update_index && newfd < 0) - newfd = hold_locked_index(&lock_file, 1); - - if (check_index) { - if (read_cache() < 0) - die("unable to read index file"); - } - - if ((check || apply) && - check_patch_list(list) < 0 && - !apply_with_reject) - exit(1); - - if (apply && write_out_results(list, skipped_patch)) - exit(1); - - if (fake_ancestor) - build_fake_ancestor(list, fake_ancestor); - - if (diffstat) - stat_patch_list(list); - - if (numstat) - numstat_patch_list(list); - - if (summary) - summary_patch_list(list); - - strbuf_release(&buf); - return 0; -} - -static int git_apply_config(const char *var, const char *value, void *cb) -{ - if (!strcmp(var, "apply.whitespace")) - return git_config_string(&apply_default_whitespace, var, value); - else if (!strcmp(var, "apply.ignorewhitespace")) - return git_config_string(&apply_default_ignorewhitespace, var, value); - return git_default_config(var, value, cb); -} - -static int option_parse_exclude(const struct option *opt, - const char *arg, int unset) -{ - add_name_limit(arg, 1); - return 0; -} - -static int option_parse_include(const struct option *opt, - const char *arg, int unset) -{ - add_name_limit(arg, 0); - has_include = 1; - return 0; -} - -static int option_parse_p(const struct option *opt, - const char *arg, int unset) -{ - p_value = atoi(arg); - p_value_known = 1; - return 0; -} - -static int option_parse_z(const struct option *opt, - const char *arg, int unset) -{ - if (unset) - line_termination = '\n'; - else - line_termination = 0; - return 0; -} - -static int option_parse_space_change(const struct option *opt, - const char *arg, int unset) -{ - if (unset) - ws_ignore_action = ignore_ws_none; - else - ws_ignore_action = ignore_ws_change; - return 0; -} - -static int option_parse_whitespace(const struct option *opt, - const char *arg, int unset) -{ - const char **whitespace_option = opt->value; - - *whitespace_option = arg; - parse_whitespace_option(arg); - return 0; -} - -static int option_parse_directory(const struct option *opt, - const char *arg, int unset) -{ - root_len = strlen(arg); - if (root_len && arg[root_len - 1] != '/') { - char *new_root; - root = new_root = xmalloc(root_len + 2); - strcpy(new_root, arg); - strcpy(new_root + root_len++, "/"); - } else - root = arg; - return 0; -} - -int cmd_apply(int argc, const char **argv, const char *unused_prefix) -{ - int i; - int errs = 0; - int is_not_gitdir; - int binary; - int force_apply = 0; - - const char *whitespace_option = NULL; - - struct option builtin_apply_options[] = { - { OPTION_CALLBACK, 0, "exclude", NULL, "path", - "don't apply changes matching the given path", - 0, option_parse_exclude }, - { OPTION_CALLBACK, 0, "include", NULL, "path", - "apply changes matching the given path", - 0, option_parse_include }, - { OPTION_CALLBACK, 'p', NULL, NULL, "num", - "remove leading slashes from traditional diff paths", - 0, option_parse_p }, - OPT_BOOLEAN(0, "no-add", &no_add, - "ignore additions made by the patch"), - OPT_BOOLEAN(0, "stat", &diffstat, - "instead of applying the patch, output diffstat for the input"), - { OPTION_BOOLEAN, 0, "allow-binary-replacement", &binary, - NULL, "old option, now no-op", - PARSE_OPT_HIDDEN | PARSE_OPT_NOARG }, - { OPTION_BOOLEAN, 0, "binary", &binary, - NULL, "old option, now no-op", - PARSE_OPT_HIDDEN | PARSE_OPT_NOARG }, - OPT_BOOLEAN(0, "numstat", &numstat, - "shows number of added and deleted lines in decimal notation"), - OPT_BOOLEAN(0, "summary", &summary, - "instead of applying the patch, output a summary for the input"), - OPT_BOOLEAN(0, "check", &check, - "instead of applying the patch, see if the patch is applicable"), - OPT_BOOLEAN(0, "index", &check_index, - "make sure the patch is applicable to the current index"), - OPT_BOOLEAN(0, "cached", &cached, - "apply a patch without touching the working tree"), - OPT_BOOLEAN(0, "apply", &force_apply, - "also apply the patch (use with --stat/--summary/--check)"), - OPT_FILENAME(0, "build-fake-ancestor", &fake_ancestor, - "build a temporary index based on embedded index information"), - { OPTION_CALLBACK, 'z', NULL, NULL, NULL, - "paths are separated with NUL character", - PARSE_OPT_NOARG, option_parse_z }, - OPT_INTEGER('C', NULL, &p_context, - "ensure at least lines of context match"), - { OPTION_CALLBACK, 0, "whitespace", &whitespace_option, "action", - "detect new or modified lines that have whitespace errors", - 0, option_parse_whitespace }, - { OPTION_CALLBACK, 0, "ignore-space-change", NULL, NULL, - "ignore changes in whitespace when finding context", - PARSE_OPT_NOARG, option_parse_space_change }, - { OPTION_CALLBACK, 0, "ignore-whitespace", NULL, NULL, - "ignore changes in whitespace when finding context", - PARSE_OPT_NOARG, option_parse_space_change }, - OPT_BOOLEAN('R', "reverse", &apply_in_reverse, - "apply the patch in reverse"), - OPT_BOOLEAN(0, "unidiff-zero", &unidiff_zero, - "don't expect at least one line of context"), - OPT_BOOLEAN(0, "reject", &apply_with_reject, - "leave the rejected hunks in corresponding *.rej files"), - OPT__VERBOSE(&apply_verbosely), - OPT_BIT(0, "inaccurate-eof", &options, - "tolerate incorrectly detected missing new-line at the end of file", - INACCURATE_EOF), - OPT_BIT(0, "recount", &options, - "do not trust the line counts in the hunk headers", - RECOUNT), - { OPTION_CALLBACK, 0, "directory", NULL, "root", - "prepend to all filenames", - 0, option_parse_directory }, - OPT_END() - }; - - prefix = setup_git_directory_gently(&is_not_gitdir); - prefix_length = prefix ? strlen(prefix) : 0; - git_config(git_apply_config, NULL); - if (apply_default_whitespace) - parse_whitespace_option(apply_default_whitespace); - if (apply_default_ignorewhitespace) - parse_ignorewhitespace_option(apply_default_ignorewhitespace); - - argc = parse_options(argc, argv, prefix, builtin_apply_options, - apply_usage, 0); - - if (apply_with_reject) - apply = apply_verbosely = 1; - if (!force_apply && (diffstat || numstat || summary || check || fake_ancestor)) - apply = 0; - if (check_index && is_not_gitdir) - die("--index outside a repository"); - if (cached) { - if (is_not_gitdir) - die("--cached outside a repository"); - check_index = 1; - } - for (i = 0; i < argc; i++) { - const char *arg = argv[i]; - int fd; - - if (!strcmp(arg, "-")) { - errs |= apply_patch(0, "", options); - read_stdin = 0; - continue; - } else if (0 < prefix_length) - arg = prefix_filename(prefix, prefix_length, arg); - - fd = open(arg, O_RDONLY); - if (fd < 0) - die_errno("can't open patch '%s'", arg); - read_stdin = 0; - set_default_whitespace_mode(whitespace_option); - errs |= apply_patch(fd, arg, options); - close(fd); - } - set_default_whitespace_mode(whitespace_option); - if (read_stdin) - errs |= apply_patch(0, "", options); - if (whitespace_error) { - if (squelch_whitespace_errors && - squelch_whitespace_errors < whitespace_error) { - int squelched = - whitespace_error - squelch_whitespace_errors; - warning("squelched %d " - "whitespace error%s", - squelched, - squelched == 1 ? "" : "s"); - } - if (ws_error_action == die_on_ws_error) - die("%d line%s add%s whitespace errors.", - whitespace_error, - whitespace_error == 1 ? "" : "s", - whitespace_error == 1 ? "s" : ""); - if (applied_after_fixing_ws && apply) - warning("%d line%s applied after" - " fixing whitespace errors.", - applied_after_fixing_ws, - applied_after_fixing_ws == 1 ? "" : "s"); - else if (whitespace_error) - warning("%d line%s add%s whitespace errors.", - whitespace_error, - whitespace_error == 1 ? "" : "s", - whitespace_error == 1 ? "s" : ""); - } - - if (update_index) { - if (write_cache(newfd, active_cache, active_nr) || - commit_locked_index(&lock_file)) - die("Unable to write new index file"); - } - - return !!errs; -} diff --git a/builtin-archive.c b/builtin-archive.c deleted file mode 100644 index 6a887f5..0000000 --- a/builtin-archive.c +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (c) 2006 Franck Bui-Huu - * Copyright (c) 2006 Rene Scharfe - */ -#include "cache.h" -#include "builtin.h" -#include "archive.h" -#include "transport.h" -#include "parse-options.h" -#include "pkt-line.h" -#include "sideband.h" - -static void create_output_file(const char *output_file) -{ - int output_fd = open(output_file, O_CREAT | O_WRONLY | O_TRUNC, 0666); - if (output_fd < 0) - die_errno("could not create archive file '%s'", output_file); - if (output_fd != 1) { - if (dup2(output_fd, 1) < 0) - die_errno("could not redirect output"); - else - close(output_fd); - } -} - -static int run_remote_archiver(int argc, const char **argv, - const char *remote, const char *exec) -{ - char buf[LARGE_PACKET_MAX]; - int fd[2], i, len, rv; - struct transport *transport; - struct remote *_remote; - - _remote = remote_get(remote); - if (!_remote->url[0]) - die("git archive: Remote with no URL"); - transport = transport_get(_remote, _remote->url[0]); - transport_connect(transport, "git-upload-archive", exec, fd); - - for (i = 1; i < argc; i++) - packet_write(fd[1], "argument %s\n", argv[i]); - packet_flush(fd[1]); - - len = packet_read_line(fd[0], buf, sizeof(buf)); - if (!len) - die("git archive: expected ACK/NAK, got EOF"); - if (buf[len-1] == '\n') - buf[--len] = 0; - if (strcmp(buf, "ACK")) { - if (len > 5 && !prefixcmp(buf, "NACK ")) - die("git archive: NACK %s", buf + 5); - die("git archive: protocol error"); - } - - len = packet_read_line(fd[0], buf, sizeof(buf)); - if (len) - die("git archive: expected a flush"); - - /* Now, start reading from fd[0] and spit it out to stdout */ - rv = recv_sideband("archive", fd[0], 1); - rv |= transport_disconnect(transport); - - return !!rv; -} - -static const char *format_from_name(const char *filename) -{ - const char *ext = strrchr(filename, '.'); - if (!ext) - return NULL; - ext++; - if (!strcasecmp(ext, "zip")) - return "--format=zip"; - return NULL; -} - -#define PARSE_OPT_KEEP_ALL ( PARSE_OPT_KEEP_DASHDASH | \ - PARSE_OPT_KEEP_ARGV0 | \ - PARSE_OPT_KEEP_UNKNOWN | \ - PARSE_OPT_NO_INTERNAL_HELP ) - -int cmd_archive(int argc, const char **argv, const char *prefix) -{ - const char *exec = "git-upload-archive"; - const char *output = NULL; - const char *remote = NULL; - const char *format_option = NULL; - struct option local_opts[] = { - OPT_STRING('o', "output", &output, "file", - "write the archive to this file"), - OPT_STRING(0, "remote", &remote, "repo", - "retrieve the archive from remote repository "), - OPT_STRING(0, "exec", &exec, "cmd", - "path to the remote git-upload-archive command"), - OPT_END() - }; - - argc = parse_options(argc, argv, prefix, local_opts, NULL, - PARSE_OPT_KEEP_ALL); - - if (output) { - create_output_file(output); - format_option = format_from_name(output); - } - - /* - * We have enough room in argv[] to muck it in place, because - * --output must have been given on the original command line - * if we get to this point, and parse_options() must have eaten - * it, i.e. we can add back one element to the array. - * - * We add a fake --format option at the beginning, with the - * format inferred from our output filename. This way explicit - * --format options can override it, and the fake option is - * inserted before any "--" that might have been given. - */ - if (format_option) { - memmove(argv + 2, argv + 1, sizeof(*argv) * argc); - argv[1] = format_option; - argv[++argc] = NULL; - } - - if (remote) - return run_remote_archiver(argc, argv, remote, exec); - - setvbuf(stderr, NULL, _IOLBF, BUFSIZ); - - return write_archive(argc, argv, prefix, 1); -} diff --git a/builtin-bisect--helper.c b/builtin-bisect--helper.c deleted file mode 100644 index 5b22639..0000000 --- a/builtin-bisect--helper.c +++ /dev/null @@ -1,28 +0,0 @@ -#include "builtin.h" -#include "cache.h" -#include "parse-options.h" -#include "bisect.h" - -static const char * const git_bisect_helper_usage[] = { - "git bisect--helper --next-all", - NULL -}; - -int cmd_bisect__helper(int argc, const char **argv, const char *prefix) -{ - int next_all = 0; - struct option options[] = { - OPT_BOOLEAN(0, "next-all", &next_all, - "perform 'git bisect next'"), - OPT_END() - }; - - argc = parse_options(argc, argv, prefix, options, - git_bisect_helper_usage, 0); - - if (!next_all) - usage_with_options(git_bisect_helper_usage, options); - - /* next-all */ - return bisect_next_all(prefix); -} diff --git a/builtin-blame.c b/builtin-blame.c deleted file mode 100644 index 10f7eac..0000000 --- a/builtin-blame.c +++ /dev/null @@ -1,2477 +0,0 @@ -/* - * Blame - * - * Copyright (c) 2006, Junio C Hamano - */ - -#include "cache.h" -#include "builtin.h" -#include "blob.h" -#include "commit.h" -#include "tag.h" -#include "tree-walk.h" -#include "diff.h" -#include "diffcore.h" -#include "revision.h" -#include "quote.h" -#include "xdiff-interface.h" -#include "cache-tree.h" -#include "string-list.h" -#include "mailmap.h" -#include "parse-options.h" -#include "utf8.h" - -static char blame_usage[] = "git blame [options] [rev-opts] [rev] [--] file"; - -static const char *blame_opt_usage[] = { - blame_usage, - "", - "[rev-opts] are documented in git-rev-list(1)", - NULL -}; - -static int longest_file; -static int longest_author; -static int max_orig_digits; -static int max_digits; -static int max_score_digits; -static int show_root; -static int reverse; -static int blank_boundary; -static int incremental; -static int xdl_opts = XDF_NEED_MINIMAL; - -static enum date_mode blame_date_mode = DATE_ISO8601; -static size_t blame_date_width; - -static struct string_list mailmap; - -#ifndef DEBUG -#define DEBUG 0 -#endif - -/* stats */ -static int num_read_blob; -static int num_get_patch; -static int num_commits; - -#define PICKAXE_BLAME_MOVE 01 -#define PICKAXE_BLAME_COPY 02 -#define PICKAXE_BLAME_COPY_HARDER 04 -#define PICKAXE_BLAME_COPY_HARDEST 010 - -/* - * blame for a blame_entry with score lower than these thresholds - * is not passed to the parent using move/copy logic. - */ -static unsigned blame_move_score; -static unsigned blame_copy_score; -#define BLAME_DEFAULT_MOVE_SCORE 20 -#define BLAME_DEFAULT_COPY_SCORE 40 - -/* bits #0..7 in revision.h, #8..11 used for merge_bases() in commit.c */ -#define METAINFO_SHOWN (1u<<12) -#define MORE_THAN_ONE_PATH (1u<<13) - -/* - * One blob in a commit that is being suspected - */ -struct origin { - int refcnt; - struct origin *previous; - struct commit *commit; - mmfile_t file; - unsigned char blob_sha1[20]; - char path[FLEX_ARRAY]; -}; - -/* - * Given an origin, prepare mmfile_t structure to be used by the - * diff machinery - */ -static void fill_origin_blob(struct origin *o, mmfile_t *file) -{ - if (!o->file.ptr) { - enum object_type type; - num_read_blob++; - file->ptr = read_sha1_file(o->blob_sha1, &type, - (unsigned long *)(&(file->size))); - if (!file->ptr) - die("Cannot read blob %s for path %s", - sha1_to_hex(o->blob_sha1), - o->path); - o->file = *file; - } - else - *file = o->file; -} - -/* - * Origin is refcounted and usually we keep the blob contents to be - * reused. - */ -static inline struct origin *origin_incref(struct origin *o) -{ - if (o) - o->refcnt++; - return o; -} - -static void origin_decref(struct origin *o) -{ - if (o && --o->refcnt <= 0) { - if (o->previous) - origin_decref(o->previous); - free(o->file.ptr); - free(o); - } -} - -static void drop_origin_blob(struct origin *o) -{ - if (o->file.ptr) { - free(o->file.ptr); - o->file.ptr = NULL; - } -} - -/* - * Each group of lines is described by a blame_entry; it can be split - * as we pass blame to the parents. They form a linked list in the - * scoreboard structure, sorted by the target line number. - */ -struct blame_entry { - struct blame_entry *prev; - struct blame_entry *next; - - /* the first line of this group in the final image; - * internally all line numbers are 0 based. - */ - int lno; - - /* how many lines this group has */ - int num_lines; - - /* the commit that introduced this group into the final image */ - struct origin *suspect; - - /* true if the suspect is truly guilty; false while we have not - * checked if the group came from one of its parents. - */ - char guilty; - - /* true if the entry has been scanned for copies in the current parent - */ - char scanned; - - /* the line number of the first line of this group in the - * suspect's file; internally all line numbers are 0 based. - */ - int s_lno; - - /* how significant this entry is -- cached to avoid - * scanning the lines over and over. - */ - unsigned score; -}; - -/* - * The current state of the blame assignment. - */ -struct scoreboard { - /* the final commit (i.e. where we started digging from) */ - struct commit *final; - struct rev_info *revs; - const char *path; - - /* - * The contents in the final image. - * Used by many functions to obtain contents of the nth line, - * indexed with scoreboard.lineno[blame_entry.lno]. - */ - const char *final_buf; - unsigned long final_buf_size; - - /* linked list of blames */ - struct blame_entry *ent; - - /* look-up a line in the final buffer */ - int num_lines; - int *lineno; -}; - -static inline int same_suspect(struct origin *a, struct origin *b) -{ - if (a == b) - return 1; - if (a->commit != b->commit) - return 0; - return !strcmp(a->path, b->path); -} - -static void sanity_check_refcnt(struct scoreboard *); - -/* - * If two blame entries that are next to each other came from - * contiguous lines in the same origin (i.e. pair), - * merge them together. - */ -static void coalesce(struct scoreboard *sb) -{ - struct blame_entry *ent, *next; - - for (ent = sb->ent; ent && (next = ent->next); ent = next) { - if (same_suspect(ent->suspect, next->suspect) && - ent->guilty == next->guilty && - ent->s_lno + ent->num_lines == next->s_lno) { - ent->num_lines += next->num_lines; - ent->next = next->next; - if (ent->next) - ent->next->prev = ent; - origin_decref(next->suspect); - free(next); - ent->score = 0; - next = ent; /* again */ - } - } - - if (DEBUG) /* sanity */ - sanity_check_refcnt(sb); -} - -/* - * Given a commit and a path in it, create a new origin structure. - * The callers that add blame to the scoreboard should use - * get_origin() to obtain shared, refcounted copy instead of calling - * this function directly. - */ -static struct origin *make_origin(struct commit *commit, const char *path) -{ - struct origin *o; - o = xcalloc(1, sizeof(*o) + strlen(path) + 1); - o->commit = commit; - o->refcnt = 1; - strcpy(o->path, path); - return o; -} - -/* - * Locate an existing origin or create a new one. - */ -static struct origin *get_origin(struct scoreboard *sb, - struct commit *commit, - const char *path) -{ - struct blame_entry *e; - - for (e = sb->ent; e; e = e->next) { - if (e->suspect->commit == commit && - !strcmp(e->suspect->path, path)) - return origin_incref(e->suspect); - } - return make_origin(commit, path); -} - -/* - * Fill the blob_sha1 field of an origin if it hasn't, so that later - * call to fill_origin_blob() can use it to locate the data. blob_sha1 - * for an origin is also used to pass the blame for the entire file to - * the parent to detect the case where a child's blob is identical to - * that of its parent's. - */ -static int fill_blob_sha1(struct origin *origin) -{ - unsigned mode; - - if (!is_null_sha1(origin->blob_sha1)) - return 0; - if (get_tree_entry(origin->commit->object.sha1, - origin->path, - origin->blob_sha1, &mode)) - goto error_out; - if (sha1_object_info(origin->blob_sha1, NULL) != OBJ_BLOB) - goto error_out; - return 0; - error_out: - hashclr(origin->blob_sha1); - return -1; -} - -/* - * We have an origin -- check if the same path exists in the - * parent and return an origin structure to represent it. - */ -static struct origin *find_origin(struct scoreboard *sb, - struct commit *parent, - struct origin *origin) -{ - struct origin *porigin = NULL; - struct diff_options diff_opts; - const char *paths[2]; - - if (parent->util) { - /* - * Each commit object can cache one origin in that - * commit. This is a freestanding copy of origin and - * not refcounted. - */ - struct origin *cached = parent->util; - if (!strcmp(cached->path, origin->path)) { - /* - * The same path between origin and its parent - * without renaming -- the most common case. - */ - porigin = get_origin(sb, parent, cached->path); - - /* - * If the origin was newly created (i.e. get_origin - * would call make_origin if none is found in the - * scoreboard), it does not know the blob_sha1, - * so copy it. Otherwise porigin was in the - * scoreboard and already knows blob_sha1. - */ - if (porigin->refcnt == 1) - hashcpy(porigin->blob_sha1, cached->blob_sha1); - return porigin; - } - /* otherwise it was not very useful; free it */ - free(parent->util); - parent->util = NULL; - } - - /* See if the origin->path is different between parent - * and origin first. Most of the time they are the - * same and diff-tree is fairly efficient about this. - */ - diff_setup(&diff_opts); - DIFF_OPT_SET(&diff_opts, RECURSIVE); - diff_opts.detect_rename = 0; - diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT; - paths[0] = origin->path; - paths[1] = NULL; - - diff_tree_setup_paths(paths, &diff_opts); - if (diff_setup_done(&diff_opts) < 0) - die("diff-setup"); - - if (is_null_sha1(origin->commit->object.sha1)) - do_diff_cache(parent->tree->object.sha1, &diff_opts); - else - diff_tree_sha1(parent->tree->object.sha1, - origin->commit->tree->object.sha1, - "", &diff_opts); - diffcore_std(&diff_opts); - - if (!diff_queued_diff.nr) { - /* The path is the same as parent */ - porigin = get_origin(sb, parent, origin->path); - hashcpy(porigin->blob_sha1, origin->blob_sha1); - } else { - /* - * Since origin->path is a pathspec, if the parent - * commit had it as a directory, we will see a whole - * bunch of deletion of files in the directory that we - * do not care about. - */ - int i; - struct diff_filepair *p = NULL; - for (i = 0; i < diff_queued_diff.nr; i++) { - const char *name; - p = diff_queued_diff.queue[i]; - name = p->one->path ? p->one->path : p->two->path; - if (!strcmp(name, origin->path)) - break; - } - if (!p) - die("internal error in blame::find_origin"); - switch (p->status) { - default: - die("internal error in blame::find_origin (%c)", - p->status); - case 'M': - porigin = get_origin(sb, parent, origin->path); - hashcpy(porigin->blob_sha1, p->one->sha1); - break; - case 'A': - case 'T': - /* Did not exist in parent, or type changed */ - break; - } - } - diff_flush(&diff_opts); - diff_tree_release_paths(&diff_opts); - if (porigin) { - /* - * Create a freestanding copy that is not part of - * the refcounted origin found in the scoreboard, and - * cache it in the commit. - */ - struct origin *cached; - - cached = make_origin(porigin->commit, porigin->path); - hashcpy(cached->blob_sha1, porigin->blob_sha1); - parent->util = cached; - } - return porigin; -} - -/* - * We have an origin -- find the path that corresponds to it in its - * parent and return an origin structure to represent it. - */ -static struct origin *find_rename(struct scoreboard *sb, - struct commit *parent, - struct origin *origin) -{ - struct origin *porigin = NULL; - struct diff_options diff_opts; - int i; - const char *paths[2]; - - diff_setup(&diff_opts); - DIFF_OPT_SET(&diff_opts, RECURSIVE); - diff_opts.detect_rename = DIFF_DETECT_RENAME; - diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT; - diff_opts.single_follow = origin->path; - paths[0] = NULL; - diff_tree_setup_paths(paths, &diff_opts); - if (diff_setup_done(&diff_opts) < 0) - die("diff-setup"); - - if (is_null_sha1(origin->commit->object.sha1)) - do_diff_cache(parent->tree->object.sha1, &diff_opts); - else - diff_tree_sha1(parent->tree->object.sha1, - origin->commit->tree->object.sha1, - "", &diff_opts); - diffcore_std(&diff_opts); - - for (i = 0; i < diff_queued_diff.nr; i++) { - struct diff_filepair *p = diff_queued_diff.queue[i]; - if ((p->status == 'R' || p->status == 'C') && - !strcmp(p->two->path, origin->path)) { - porigin = get_origin(sb, parent, p->one->path); - hashcpy(porigin->blob_sha1, p->one->sha1); - break; - } - } - diff_flush(&diff_opts); - diff_tree_release_paths(&diff_opts); - return porigin; -} - -/* - * Link in a new blame entry to the scoreboard. Entries that cover the - * same line range have been removed from the scoreboard previously. - */ -static void add_blame_entry(struct scoreboard *sb, struct blame_entry *e) -{ - struct blame_entry *ent, *prev = NULL; - - origin_incref(e->suspect); - - for (ent = sb->ent; ent && ent->lno < e->lno; ent = ent->next) - prev = ent; - - /* prev, if not NULL, is the last one that is below e */ - e->prev = prev; - if (prev) { - e->next = prev->next; - prev->next = e; - } - else { - e->next = sb->ent; - sb->ent = e; - } - if (e->next) - e->next->prev = e; -} - -/* - * src typically is on-stack; we want to copy the information in it to - * a malloced blame_entry that is already on the linked list of the - * scoreboard. The origin of dst loses a refcnt while the origin of src - * gains one. - */ -static void dup_entry(struct blame_entry *dst, struct blame_entry *src) -{ - struct blame_entry *p, *n; - - p = dst->prev; - n = dst->next; - origin_incref(src->suspect); - origin_decref(dst->suspect); - memcpy(dst, src, sizeof(*src)); - dst->prev = p; - dst->next = n; - dst->score = 0; -} - -static const char *nth_line(struct scoreboard *sb, int lno) -{ - return sb->final_buf + sb->lineno[lno]; -} - -/* - * It is known that lines between tlno to same came from parent, and e - * has an overlap with that range. it also is known that parent's - * line plno corresponds to e's line tlno. - * - * <---- e -----> - * <------> - * <------------> - * <------------> - * <------------------> - * - * Split e into potentially three parts; before this chunk, the chunk - * to be blamed for the parent, and after that portion. - */ -static void split_overlap(struct blame_entry *split, - struct blame_entry *e, - int tlno, int plno, int same, - struct origin *parent) -{ - int chunk_end_lno; - memset(split, 0, sizeof(struct blame_entry [3])); - - if (e->s_lno < tlno) { - /* there is a pre-chunk part not blamed on parent */ - split[0].suspect = origin_incref(e->suspect); - split[0].lno = e->lno; - split[0].s_lno = e->s_lno; - split[0].num_lines = tlno - e->s_lno; - split[1].lno = e->lno + tlno - e->s_lno; - split[1].s_lno = plno; - } - else { - split[1].lno = e->lno; - split[1].s_lno = plno + (e->s_lno - tlno); - } - - if (same < e->s_lno + e->num_lines) { - /* there is a post-chunk part not blamed on parent */ - split[2].suspect = origin_incref(e->suspect); - split[2].lno = e->lno + (same - e->s_lno); - split[2].s_lno = e->s_lno + (same - e->s_lno); - split[2].num_lines = e->s_lno + e->num_lines - same; - chunk_end_lno = split[2].lno; - } - else - chunk_end_lno = e->lno + e->num_lines; - split[1].num_lines = chunk_end_lno - split[1].lno; - - /* - * if it turns out there is nothing to blame the parent for, - * forget about the splitting. !split[1].suspect signals this. - */ - if (split[1].num_lines < 1) - return; - split[1].suspect = origin_incref(parent); -} - -/* - * split_overlap() divided an existing blame e into up to three parts - * in split. Adjust the linked list of blames in the scoreboard to - * reflect the split. - */ -static void split_blame(struct scoreboard *sb, - struct blame_entry *split, - struct blame_entry *e) -{ - struct blame_entry *new_entry; - - if (split[0].suspect && split[2].suspect) { - /* The first part (reuse storage for the existing entry e) */ - dup_entry(e, &split[0]); - - /* The last part -- me */ - new_entry = xmalloc(sizeof(*new_entry)); - memcpy(new_entry, &(split[2]), sizeof(struct blame_entry)); - add_blame_entry(sb, new_entry); - - /* ... and the middle part -- parent */ - new_entry = xmalloc(sizeof(*new_entry)); - memcpy(new_entry, &(split[1]), sizeof(struct blame_entry)); - add_blame_entry(sb, new_entry); - } - else if (!split[0].suspect && !split[2].suspect) - /* - * The parent covers the entire area; reuse storage for - * e and replace it with the parent. - */ - dup_entry(e, &split[1]); - else if (split[0].suspect) { - /* me and then parent */ - dup_entry(e, &split[0]); - - new_entry = xmalloc(sizeof(*new_entry)); - memcpy(new_entry, &(split[1]), sizeof(struct blame_entry)); - add_blame_entry(sb, new_entry); - } - else { - /* parent and then me */ - dup_entry(e, &split[1]); - - new_entry = xmalloc(sizeof(*new_entry)); - memcpy(new_entry, &(split[2]), sizeof(struct blame_entry)); - add_blame_entry(sb, new_entry); - } - - if (DEBUG) { /* sanity */ - struct blame_entry *ent; - int lno = sb->ent->lno, corrupt = 0; - - for (ent = sb->ent; ent; ent = ent->next) { - if (lno != ent->lno) - corrupt = 1; - if (ent->s_lno < 0) - corrupt = 1; - lno += ent->num_lines; - } - if (corrupt) { - lno = sb->ent->lno; - for (ent = sb->ent; ent; ent = ent->next) { - printf("L %8d l %8d n %8d\n", - lno, ent->lno, ent->num_lines); - lno = ent->lno + ent->num_lines; - } - die("oops"); - } - } -} - -/* - * After splitting the blame, the origins used by the - * on-stack blame_entry should lose one refcnt each. - */ -static void decref_split(struct blame_entry *split) -{ - int i; - - for (i = 0; i < 3; i++) - origin_decref(split[i].suspect); -} - -/* - * Helper for blame_chunk(). blame_entry e is known to overlap with - * the patch hunk; split it and pass blame to the parent. - */ -static void blame_overlap(struct scoreboard *sb, struct blame_entry *e, - int tlno, int plno, int same, - struct origin *parent) -{ - struct blame_entry split[3]; - - split_overlap(split, e, tlno, plno, same, parent); - if (split[1].suspect) - split_blame(sb, split, e); - decref_split(split); -} - -/* - * Find the line number of the last line the target is suspected for. - */ -static int find_last_in_target(struct scoreboard *sb, struct origin *target) -{ - struct blame_entry *e; - int last_in_target = -1; - - for (e = sb->ent; e; e = e->next) { - if (e->guilty || !same_suspect(e->suspect, target)) - continue; - if (last_in_target < e->s_lno + e->num_lines) - last_in_target = e->s_lno + e->num_lines; - } - return last_in_target; -} - -/* - * Process one hunk from the patch between the current suspect for - * blame_entry e and its parent. Find and split the overlap, and - * pass blame to the overlapping part to the parent. - */ -static void blame_chunk(struct scoreboard *sb, - int tlno, int plno, int same, - struct origin *target, struct origin *parent) -{ - struct blame_entry *e; - - for (e = sb->ent; e; e = e->next) { - if (e->guilty || !same_suspect(e->suspect, target)) - continue; - if (same <= e->s_lno) - continue; - if (tlno < e->s_lno + e->num_lines) - blame_overlap(sb, e, tlno, plno, same, parent); - } -} - -struct blame_chunk_cb_data { - struct scoreboard *sb; - struct origin *target; - struct origin *parent; - long plno; - long tlno; -}; - -static void blame_chunk_cb(void *data, long same, long p_next, long t_next) -{ - struct blame_chunk_cb_data *d = data; - blame_chunk(d->sb, d->tlno, d->plno, same, d->target, d->parent); - d->plno = p_next; - d->tlno = t_next; -} - -/* - * We are looking at the origin 'target' and aiming to pass blame - * for the lines it is suspected to its parent. Run diff to find - * which lines came from parent and pass blame for them. - */ -static int pass_blame_to_parent(struct scoreboard *sb, - struct origin *target, - struct origin *parent) -{ - int last_in_target; - mmfile_t file_p, file_o; - struct blame_chunk_cb_data d = { sb, target, parent, 0, 0 }; - xpparam_t xpp; - xdemitconf_t xecfg; - - last_in_target = find_last_in_target(sb, target); - if (last_in_target < 0) - return 1; /* nothing remains for this target */ - - fill_origin_blob(parent, &file_p); - fill_origin_blob(target, &file_o); - num_get_patch++; - - memset(&xpp, 0, sizeof(xpp)); - xpp.flags = xdl_opts; - memset(&xecfg, 0, sizeof(xecfg)); - xecfg.ctxlen = 0; - xdi_diff_hunks(&file_p, &file_o, blame_chunk_cb, &d, &xpp, &xecfg); - /* The rest (i.e. anything after tlno) are the same as the parent */ - blame_chunk(sb, d.tlno, d.plno, last_in_target, target, parent); - - return 0; -} - -/* - * The lines in blame_entry after splitting blames many times can become - * very small and trivial, and at some point it becomes pointless to - * blame the parents. E.g. "\t\t}\n\t}\n\n" appears everywhere in any - * ordinary C program, and it is not worth to say it was copied from - * totally unrelated file in the parent. - * - * Compute how trivial the lines in the blame_entry are. - */ -static unsigned ent_score(struct scoreboard *sb, struct blame_entry *e) -{ - unsigned score; - const char *cp, *ep; - - if (e->score) - return e->score; - - score = 1; - cp = nth_line(sb, e->lno); - ep = nth_line(sb, e->lno + e->num_lines); - while (cp < ep) { - unsigned ch = *((unsigned char *)cp); - if (isalnum(ch)) - score++; - cp++; - } - e->score = score; - return score; -} - -/* - * best_so_far[] and this[] are both a split of an existing blame_entry - * that passes blame to the parent. Maintain best_so_far the best split - * so far, by comparing this and best_so_far and copying this into - * bst_so_far as needed. - */ -static void copy_split_if_better(struct scoreboard *sb, - struct blame_entry *best_so_far, - struct blame_entry *this) -{ - int i; - - if (!this[1].suspect) - return; - if (best_so_far[1].suspect) { - if (ent_score(sb, &this[1]) < ent_score(sb, &best_so_far[1])) - return; - } - - for (i = 0; i < 3; i++) - origin_incref(this[i].suspect); - decref_split(best_so_far); - memcpy(best_so_far, this, sizeof(struct blame_entry [3])); -} - -/* - * We are looking at a part of the final image represented by - * ent (tlno and same are offset by ent->s_lno). - * tlno is where we are looking at in the final image. - * up to (but not including) same match preimage. - * plno is where we are looking at in the preimage. - * - * <-------------- final image ----------------------> - * <------ent------> - * ^tlno ^same - * <---------preimage-----> - * ^plno - * - * All line numbers are 0-based. - */ -static void handle_split(struct scoreboard *sb, - struct blame_entry *ent, - int tlno, int plno, int same, - struct origin *parent, - struct blame_entry *split) -{ - if (ent->num_lines <= tlno) - return; - if (tlno < same) { - struct blame_entry this[3]; - tlno += ent->s_lno; - same += ent->s_lno; - split_overlap(this, ent, tlno, plno, same, parent); - copy_split_if_better(sb, split, this); - decref_split(this); - } -} - -struct handle_split_cb_data { - struct scoreboard *sb; - struct blame_entry *ent; - struct origin *parent; - struct blame_entry *split; - long plno; - long tlno; -}; - -static void handle_split_cb(void *data, long same, long p_next, long t_next) -{ - struct handle_split_cb_data *d = data; - handle_split(d->sb, d->ent, d->tlno, d->plno, same, d->parent, d->split); - d->plno = p_next; - d->tlno = t_next; -} - -/* - * Find the lines from parent that are the same as ent so that - * we can pass blames to it. file_p has the blob contents for - * the parent. - */ -static void find_copy_in_blob(struct scoreboard *sb, - struct blame_entry *ent, - struct origin *parent, - struct blame_entry *split, - mmfile_t *file_p) -{ - const char *cp; - int cnt; - mmfile_t file_o; - struct handle_split_cb_data d = { sb, ent, parent, split, 0, 0 }; - xpparam_t xpp; - xdemitconf_t xecfg; - - /* - * Prepare mmfile that contains only the lines in ent. - */ - cp = nth_line(sb, ent->lno); - file_o.ptr = (char *) cp; - cnt = ent->num_lines; - - while (cnt && cp < sb->final_buf + sb->final_buf_size) { - if (*cp++ == '\n') - cnt--; - } - file_o.size = cp - file_o.ptr; - - /* - * file_o is a part of final image we are annotating. - * file_p partially may match that image. - */ - memset(&xpp, 0, sizeof(xpp)); - xpp.flags = xdl_opts; - memset(&xecfg, 0, sizeof(xecfg)); - xecfg.ctxlen = 1; - memset(split, 0, sizeof(struct blame_entry [3])); - xdi_diff_hunks(file_p, &file_o, handle_split_cb, &d, &xpp, &xecfg); - /* remainder, if any, all match the preimage */ - handle_split(sb, ent, d.tlno, d.plno, ent->num_lines, parent, split); -} - -/* - * See if lines currently target is suspected for can be attributed to - * parent. - */ -static int find_move_in_parent(struct scoreboard *sb, - struct origin *target, - struct origin *parent) -{ - int last_in_target, made_progress; - struct blame_entry *e, split[3]; - mmfile_t file_p; - - last_in_target = find_last_in_target(sb, target); - if (last_in_target < 0) - return 1; /* nothing remains for this target */ - - fill_origin_blob(parent, &file_p); - if (!file_p.ptr) - return 0; - - made_progress = 1; - while (made_progress) { - made_progress = 0; - for (e = sb->ent; e; e = e->next) { - if (e->guilty || !same_suspect(e->suspect, target) || - ent_score(sb, e) < blame_move_score) - continue; - find_copy_in_blob(sb, e, parent, split, &file_p); - if (split[1].suspect && - blame_move_score < ent_score(sb, &split[1])) { - split_blame(sb, split, e); - made_progress = 1; - } - decref_split(split); - } - } - return 0; -} - -struct blame_list { - struct blame_entry *ent; - struct blame_entry split[3]; -}; - -/* - * Count the number of entries the target is suspected for, - * and prepare a list of entry and the best split. - */ -static struct blame_list *setup_blame_list(struct scoreboard *sb, - struct origin *target, - int min_score, - int *num_ents_p) -{ - struct blame_entry *e; - int num_ents, i; - struct blame_list *blame_list = NULL; - - for (e = sb->ent, num_ents = 0; e; e = e->next) - if (!e->scanned && !e->guilty && - same_suspect(e->suspect, target) && - min_score < ent_score(sb, e)) - num_ents++; - if (num_ents) { - blame_list = xcalloc(num_ents, sizeof(struct blame_list)); - for (e = sb->ent, i = 0; e; e = e->next) - if (!e->scanned && !e->guilty && - same_suspect(e->suspect, target) && - min_score < ent_score(sb, e)) - blame_list[i++].ent = e; - } - *num_ents_p = num_ents; - return blame_list; -} - -/* - * Reset the scanned status on all entries. - */ -static void reset_scanned_flag(struct scoreboard *sb) -{ - struct blame_entry *e; - for (e = sb->ent; e; e = e->next) - e->scanned = 0; -} - -/* - * For lines target is suspected for, see if we can find code movement - * across file boundary from the parent commit. porigin is the path - * in the parent we already tried. - */ -static int find_copy_in_parent(struct scoreboard *sb, - struct origin *target, - struct commit *parent, - struct origin *porigin, - int opt) -{ - struct diff_options diff_opts; - const char *paths[1]; - int i, j; - int retval; - struct blame_list *blame_list; - int num_ents; - - blame_list = setup_blame_list(sb, target, blame_copy_score, &num_ents); - if (!blame_list) - return 1; /* nothing remains for this target */ - - diff_setup(&diff_opts); - DIFF_OPT_SET(&diff_opts, RECURSIVE); - diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT; - - paths[0] = NULL; - diff_tree_setup_paths(paths, &diff_opts); - if (diff_setup_done(&diff_opts) < 0) - die("diff-setup"); - - /* Try "find copies harder" on new path if requested; - * we do not want to use diffcore_rename() actually to - * match things up; find_copies_harder is set only to - * force diff_tree_sha1() to feed all filepairs to diff_queue, - * and this code needs to be after diff_setup_done(), which - * usually makes find-copies-harder imply copy detection. - */ - if ((opt & PICKAXE_BLAME_COPY_HARDEST) - || ((opt & PICKAXE_BLAME_COPY_HARDER) - && (!porigin || strcmp(target->path, porigin->path)))) - DIFF_OPT_SET(&diff_opts, FIND_COPIES_HARDER); - - if (is_null_sha1(target->commit->object.sha1)) - do_diff_cache(parent->tree->object.sha1, &diff_opts); - else - diff_tree_sha1(parent->tree->object.sha1, - target->commit->tree->object.sha1, - "", &diff_opts); - - if (!DIFF_OPT_TST(&diff_opts, FIND_COPIES_HARDER)) - diffcore_std(&diff_opts); - - retval = 0; - while (1) { - int made_progress = 0; - - for (i = 0; i < diff_queued_diff.nr; i++) { - struct diff_filepair *p = diff_queued_diff.queue[i]; - struct origin *norigin; - mmfile_t file_p; - struct blame_entry this[3]; - - if (!DIFF_FILE_VALID(p->one)) - continue; /* does not exist in parent */ - if (S_ISGITLINK(p->one->mode)) - continue; /* ignore git links */ - if (porigin && !strcmp(p->one->path, porigin->path)) - /* find_move already dealt with this path */ - continue; - - norigin = get_origin(sb, parent, p->one->path); - hashcpy(norigin->blob_sha1, p->one->sha1); - fill_origin_blob(norigin, &file_p); - if (!file_p.ptr) - continue; - - for (j = 0; j < num_ents; j++) { - find_copy_in_blob(sb, blame_list[j].ent, - norigin, this, &file_p); - copy_split_if_better(sb, blame_list[j].split, - this); - decref_split(this); - } - origin_decref(norigin); - } - - for (j = 0; j < num_ents; j++) { - struct blame_entry *split = blame_list[j].split; - if (split[1].suspect && - blame_copy_score < ent_score(sb, &split[1])) { - split_blame(sb, split, blame_list[j].ent); - made_progress = 1; - } - else - blame_list[j].ent->scanned = 1; - decref_split(split); - } - free(blame_list); - - if (!made_progress) - break; - blame_list = setup_blame_list(sb, target, blame_copy_score, &num_ents); - if (!blame_list) { - retval = 1; - break; - } - } - reset_scanned_flag(sb); - diff_flush(&diff_opts); - diff_tree_release_paths(&diff_opts); - return retval; -} - -/* - * The blobs of origin and porigin exactly match, so everything - * origin is suspected for can be blamed on the parent. - */ -static void pass_whole_blame(struct scoreboard *sb, - struct origin *origin, struct origin *porigin) -{ - struct blame_entry *e; - - if (!porigin->file.ptr && origin->file.ptr) { - /* Steal its file */ - porigin->file = origin->file; - origin->file.ptr = NULL; - } - for (e = sb->ent; e; e = e->next) { - if (!same_suspect(e->suspect, origin)) - continue; - origin_incref(porigin); - origin_decref(e->suspect); - e->suspect = porigin; - } -} - -/* - * We pass blame from the current commit to its parents. We keep saying - * "parent" (and "porigin"), but what we mean is to find scapegoat to - * exonerate ourselves. - */ -static struct commit_list *first_scapegoat(struct rev_info *revs, struct commit *commit) -{ - if (!reverse) - return commit->parents; - return lookup_decoration(&revs->children, &commit->object); -} - -static int num_scapegoats(struct rev_info *revs, struct commit *commit) -{ - int cnt; - struct commit_list *l = first_scapegoat(revs, commit); - for (cnt = 0; l; l = l->next) - cnt++; - return cnt; -} - -#define MAXSG 16 - -static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt) -{ - struct rev_info *revs = sb->revs; - int i, pass, num_sg; - struct commit *commit = origin->commit; - struct commit_list *sg; - struct origin *sg_buf[MAXSG]; - struct origin *porigin, **sg_origin = sg_buf; - - num_sg = num_scapegoats(revs, commit); - if (!num_sg) - goto finish; - else if (num_sg < ARRAY_SIZE(sg_buf)) - memset(sg_buf, 0, sizeof(sg_buf)); - else - sg_origin = xcalloc(num_sg, sizeof(*sg_origin)); - - /* - * The first pass looks for unrenamed path to optimize for - * common cases, then we look for renames in the second pass. - */ - for (pass = 0; pass < 2; pass++) { - struct origin *(*find)(struct scoreboard *, - struct commit *, struct origin *); - find = pass ? find_rename : find_origin; - - for (i = 0, sg = first_scapegoat(revs, commit); - i < num_sg && sg; - sg = sg->next, i++) { - struct commit *p = sg->item; - int j, same; - - if (sg_origin[i]) - continue; - if (parse_commit(p)) - continue; - porigin = find(sb, p, origin); - if (!porigin) - continue; - if (!hashcmp(porigin->blob_sha1, origin->blob_sha1)) { - pass_whole_blame(sb, origin, porigin); - origin_decref(porigin); - goto finish; - } - for (j = same = 0; j < i; j++) - if (sg_origin[j] && - !hashcmp(sg_origin[j]->blob_sha1, - porigin->blob_sha1)) { - same = 1; - break; - } - if (!same) - sg_origin[i] = porigin; - else - origin_decref(porigin); - } - } - - num_commits++; - for (i = 0, sg = first_scapegoat(revs, commit); - i < num_sg && sg; - sg = sg->next, i++) { - struct origin *porigin = sg_origin[i]; - if (!porigin) - continue; - if (!origin->previous) { - origin_incref(porigin); - origin->previous = porigin; - } - if (pass_blame_to_parent(sb, origin, porigin)) - goto finish; - } - - /* - * Optionally find moves in parents' files. - */ - if (opt & PICKAXE_BLAME_MOVE) - for (i = 0, sg = first_scapegoat(revs, commit); - i < num_sg && sg; - sg = sg->next, i++) { - struct origin *porigin = sg_origin[i]; - if (!porigin) - continue; - if (find_move_in_parent(sb, origin, porigin)) - goto finish; - } - - /* - * Optionally find copies from parents' files. - */ - if (opt & PICKAXE_BLAME_COPY) - for (i = 0, sg = first_scapegoat(revs, commit); - i < num_sg && sg; - sg = sg->next, i++) { - struct origin *porigin = sg_origin[i]; - if (find_copy_in_parent(sb, origin, sg->item, - porigin, opt)) - goto finish; - } - - finish: - for (i = 0; i < num_sg; i++) { - if (sg_origin[i]) { - drop_origin_blob(sg_origin[i]); - origin_decref(sg_origin[i]); - } - } - drop_origin_blob(origin); - if (sg_buf != sg_origin) - free(sg_origin); -} - -/* - * Information on commits, used for output. - */ -struct commit_info -{ - const char *author; - const char *author_mail; - unsigned long author_time; - const char *author_tz; - - /* filled only when asked for details */ - const char *committer; - const char *committer_mail; - unsigned long committer_time; - const char *committer_tz; - - const char *summary; -}; - -/* - * Parse author/committer line in the commit object buffer - */ -static void get_ac_line(const char *inbuf, const char *what, - int person_len, char *person, - int mail_len, char *mail, - unsigned long *time, const char **tz) -{ - int len, tzlen, maillen; - char *tmp, *endp, *timepos, *mailpos; - - tmp = strstr(inbuf, what); - if (!tmp) - goto error_out; - tmp += strlen(what); - endp = strchr(tmp, '\n'); - if (!endp) - len = strlen(tmp); - else - len = endp - tmp; - if (person_len <= len) { - error_out: - /* Ugh */ - *tz = "(unknown)"; - strcpy(person, *tz); - strcpy(mail, *tz); - *time = 0; - return; - } - memcpy(person, tmp, len); - - tmp = person; - tmp += len; - *tmp = 0; - while (person < tmp && *tmp != ' ') - tmp--; - if (tmp <= person) - goto error_out; - *tz = tmp+1; - tzlen = (person+len)-(tmp+1); - - *tmp = 0; - while (person < tmp && *tmp != ' ') - tmp--; - if (tmp <= person) - goto error_out; - *time = strtoul(tmp, NULL, 10); - timepos = tmp; - - *tmp = 0; - while (person < tmp && *tmp != ' ') - tmp--; - if (tmp <= person) - return; - mailpos = tmp + 1; - *tmp = 0; - maillen = timepos - tmp; - memcpy(mail, mailpos, maillen); - - if (!mailmap.nr) - return; - - /* - * mailmap expansion may make the name longer. - * make room by pushing stuff down. - */ - tmp = person + person_len - (tzlen + 1); - memmove(tmp, *tz, tzlen); - tmp[tzlen] = 0; - *tz = tmp; - - /* - * Now, convert both name and e-mail using mailmap - */ - if (map_user(&mailmap, mail+1, mail_len-1, person, tmp-person-1)) { - /* Add a trailing '>' to email, since map_user returns plain emails - Note: It already has '<', since we replace from mail+1 */ - mailpos = memchr(mail, '\0', mail_len); - if (mailpos && mailpos-mail < mail_len - 1) { - *mailpos = '>'; - *(mailpos+1) = '\0'; - } - } -} - -static void get_commit_info(struct commit *commit, - struct commit_info *ret, - int detailed) -{ - int len; - char *tmp, *endp, *reencoded, *message; - static char author_name[1024]; - static char author_mail[1024]; - static char committer_name[1024]; - static char committer_mail[1024]; - static char summary_buf[1024]; - - /* - * We've operated without save_commit_buffer, so - * we now need to populate them for output. - */ - if (!commit->buffer) { - enum object_type type; - unsigned long size; - commit->buffer = - read_sha1_file(commit->object.sha1, &type, &size); - if (!commit->buffer) - die("Cannot read commit %s", - sha1_to_hex(commit->object.sha1)); - } - reencoded = reencode_commit_message(commit, NULL); - message = reencoded ? reencoded : commit->buffer; - ret->author = author_name; - ret->author_mail = author_mail; - get_ac_line(message, "\nauthor ", - sizeof(author_name), author_name, - sizeof(author_mail), author_mail, - &ret->author_time, &ret->author_tz); - - if (!detailed) { - free(reencoded); - return; - } - - ret->committer = committer_name; - ret->committer_mail = committer_mail; - get_ac_line(message, "\ncommitter ", - sizeof(committer_name), committer_name, - sizeof(committer_mail), committer_mail, - &ret->committer_time, &ret->committer_tz); - - ret->summary = summary_buf; - tmp = strstr(message, "\n\n"); - if (!tmp) { - error_out: - sprintf(summary_buf, "(%s)", sha1_to_hex(commit->object.sha1)); - free(reencoded); - return; - } - tmp += 2; - endp = strchr(tmp, '\n'); - if (!endp) - endp = tmp + strlen(tmp); - len = endp - tmp; - if (len >= sizeof(summary_buf) || len == 0) - goto error_out; - memcpy(summary_buf, tmp, len); - summary_buf[len] = 0; - free(reencoded); -} - -/* - * To allow LF and other nonportable characters in pathnames, - * they are c-style quoted as needed. - */ -static void write_filename_info(const char *path) -{ - printf("filename "); - write_name_quoted(path, stdout, '\n'); -} - -/* - * Porcelain/Incremental format wants to show a lot of details per - * commit. Instead of repeating this every line, emit it only once, - * the first time each commit appears in the output. - */ -static int emit_one_suspect_detail(struct origin *suspect) -{ - struct commit_info ci; - - if (suspect->commit->object.flags & METAINFO_SHOWN) - return 0; - - suspect->commit->object.flags |= METAINFO_SHOWN; - get_commit_info(suspect->commit, &ci, 1); - printf("author %s\n", ci.author); - printf("author-mail %s\n", ci.author_mail); - printf("author-time %lu\n", ci.author_time); - printf("author-tz %s\n", ci.author_tz); - printf("committer %s\n", ci.committer); - printf("committer-mail %s\n", ci.committer_mail); - printf("committer-time %lu\n", ci.committer_time); - printf("committer-tz %s\n", ci.committer_tz); - printf("summary %s\n", ci.summary); - if (suspect->commit->object.flags & UNINTERESTING) - printf("boundary\n"); - if (suspect->previous) { - struct origin *prev = suspect->previous; - printf("previous %s ", sha1_to_hex(prev->commit->object.sha1)); - write_name_quoted(prev->path, stdout, '\n'); - } - return 1; -} - -/* - * The blame_entry is found to be guilty for the range. Mark it - * as such, and show it in incremental output. - */ -static void found_guilty_entry(struct blame_entry *ent) -{ - if (ent->guilty) - return; - ent->guilty = 1; - if (incremental) { - struct origin *suspect = ent->suspect; - - printf("%s %d %d %d\n", - sha1_to_hex(suspect->commit->object.sha1), - ent->s_lno + 1, ent->lno + 1, ent->num_lines); - emit_one_suspect_detail(suspect); - write_filename_info(suspect->path); - maybe_flush_or_die(stdout, "stdout"); - } -} - -/* - * The main loop -- while the scoreboard has lines whose true origin - * is still unknown, pick one blame_entry, and allow its current - * suspect to pass blames to its parents. - */ -static void assign_blame(struct scoreboard *sb, int opt) -{ - struct rev_info *revs = sb->revs; - - while (1) { - struct blame_entry *ent; - struct commit *commit; - struct origin *suspect = NULL; - - /* find one suspect to break down */ - for (ent = sb->ent; !suspect && ent; ent = ent->next) - if (!ent->guilty) - suspect = ent->suspect; - if (!suspect) - return; /* all done */ - - /* - * We will use this suspect later in the loop, - * so hold onto it in the meantime. - */ - origin_incref(suspect); - commit = suspect->commit; - if (!commit->object.parsed) - parse_commit(commit); - if (reverse || - (!(commit->object.flags & UNINTERESTING) && - !(revs->max_age != -1 && commit->date < revs->max_age))) - pass_blame(sb, suspect, opt); - else { - commit->object.flags |= UNINTERESTING; - if (commit->object.parsed) - mark_parents_uninteresting(commit); - } - /* treat root commit as boundary */ - if (!commit->parents && !show_root) - commit->object.flags |= UNINTERESTING; - - /* Take responsibility for the remaining entries */ - for (ent = sb->ent; ent; ent = ent->next) - if (same_suspect(ent->suspect, suspect)) - found_guilty_entry(ent); - origin_decref(suspect); - - if (DEBUG) /* sanity */ - sanity_check_refcnt(sb); - } -} - -static const char *format_time(unsigned long time, const char *tz_str, - int show_raw_time) -{ - static char time_buf[128]; - const char *time_str; - int time_len; - int tz; - - if (show_raw_time) { - sprintf(time_buf, "%lu %s", time, tz_str); - } - else { - tz = atoi(tz_str); - time_str = show_date(time, tz, blame_date_mode); - time_len = strlen(time_str); - memcpy(time_buf, time_str, time_len); - memset(time_buf + time_len, ' ', blame_date_width - time_len); - } - return time_buf; -} - -#define OUTPUT_ANNOTATE_COMPAT 001 -#define OUTPUT_LONG_OBJECT_NAME 002 -#define OUTPUT_RAW_TIMESTAMP 004 -#define OUTPUT_PORCELAIN 010 -#define OUTPUT_SHOW_NAME 020 -#define OUTPUT_SHOW_NUMBER 040 -#define OUTPUT_SHOW_SCORE 0100 -#define OUTPUT_NO_AUTHOR 0200 - -static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent) -{ - int cnt; - const char *cp; - struct origin *suspect = ent->suspect; - char hex[41]; - - strcpy(hex, sha1_to_hex(suspect->commit->object.sha1)); - printf("%s%c%d %d %d\n", - hex, - ent->guilty ? ' ' : '*', // purely for debugging - ent->s_lno + 1, - ent->lno + 1, - ent->num_lines); - if (emit_one_suspect_detail(suspect) || - (suspect->commit->object.flags & MORE_THAN_ONE_PATH)) - write_filename_info(suspect->path); - - cp = nth_line(sb, ent->lno); - for (cnt = 0; cnt < ent->num_lines; cnt++) { - char ch; - if (cnt) - printf("%s %d %d\n", hex, - ent->s_lno + 1 + cnt, - ent->lno + 1 + cnt); - putchar('\t'); - do { - ch = *cp++; - putchar(ch); - } while (ch != '\n' && - cp < sb->final_buf + sb->final_buf_size); - } - - if (sb->final_buf_size && cp[-1] != '\n') - putchar('\n'); -} - -static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt) -{ - int cnt; - const char *cp; - struct origin *suspect = ent->suspect; - struct commit_info ci; - char hex[41]; - int show_raw_time = !!(opt & OUTPUT_RAW_TIMESTAMP); - - get_commit_info(suspect->commit, &ci, 1); - strcpy(hex, sha1_to_hex(suspect->commit->object.sha1)); - - cp = nth_line(sb, ent->lno); - for (cnt = 0; cnt < ent->num_lines; cnt++) { - char ch; - int length = (opt & OUTPUT_LONG_OBJECT_NAME) ? 40 : 8; - - if (suspect->commit->object.flags & UNINTERESTING) { - if (blank_boundary) - memset(hex, ' ', length); - else if (!(opt & OUTPUT_ANNOTATE_COMPAT)) { - length--; - putchar('^'); - } - } - - printf("%.*s", length, hex); - if (opt & OUTPUT_ANNOTATE_COMPAT) - printf("\t(%10s\t%10s\t%d)", ci.author, - format_time(ci.author_time, ci.author_tz, - show_raw_time), - ent->lno + 1 + cnt); - else { - if (opt & OUTPUT_SHOW_SCORE) - printf(" %*d %02d", - max_score_digits, ent->score, - ent->suspect->refcnt); - if (opt & OUTPUT_SHOW_NAME) - printf(" %-*.*s", longest_file, longest_file, - suspect->path); - if (opt & OUTPUT_SHOW_NUMBER) - printf(" %*d", max_orig_digits, - ent->s_lno + 1 + cnt); - - if (!(opt & OUTPUT_NO_AUTHOR)) { - int pad = longest_author - utf8_strwidth(ci.author); - printf(" (%s%*s %10s", - ci.author, pad, "", - format_time(ci.author_time, - ci.author_tz, - show_raw_time)); - } - printf(" %*d) ", - max_digits, ent->lno + 1 + cnt); - } - do { - ch = *cp++; - putchar(ch); - } while (ch != '\n' && - cp < sb->final_buf + sb->final_buf_size); - } - - if (sb->final_buf_size && cp[-1] != '\n') - putchar('\n'); -} - -static void output(struct scoreboard *sb, int option) -{ - struct blame_entry *ent; - - if (option & OUTPUT_PORCELAIN) { - for (ent = sb->ent; ent; ent = ent->next) { - struct blame_entry *oth; - struct origin *suspect = ent->suspect; - struct commit *commit = suspect->commit; - if (commit->object.flags & MORE_THAN_ONE_PATH) - continue; - for (oth = ent->next; oth; oth = oth->next) { - if ((oth->suspect->commit != commit) || - !strcmp(oth->suspect->path, suspect->path)) - continue; - commit->object.flags |= MORE_THAN_ONE_PATH; - break; - } - } - } - - for (ent = sb->ent; ent; ent = ent->next) { - if (option & OUTPUT_PORCELAIN) - emit_porcelain(sb, ent); - else { - emit_other(sb, ent, option); - } - } -} - -/* - * To allow quick access to the contents of nth line in the - * final image, prepare an index in the scoreboard. - */ -static int prepare_lines(struct scoreboard *sb) -{ - const char *buf = sb->final_buf; - unsigned long len = sb->final_buf_size; - int num = 0, incomplete = 0, bol = 1; - - if (len && buf[len-1] != '\n') - incomplete++; /* incomplete line at the end */ - while (len--) { - if (bol) { - sb->lineno = xrealloc(sb->lineno, - sizeof(int *) * (num + 1)); - sb->lineno[num] = buf - sb->final_buf; - bol = 0; - } - if (*buf++ == '\n') { - num++; - bol = 1; - } - } - sb->lineno = xrealloc(sb->lineno, - sizeof(int *) * (num + incomplete + 1)); - sb->lineno[num + incomplete] = buf - sb->final_buf; - sb->num_lines = num + incomplete; - return sb->num_lines; -} - -/* - * Add phony grafts for use with -S; this is primarily to - * support git's cvsserver that wants to give a linear history - * to its clients. - */ -static int read_ancestry(const char *graft_file) -{ - FILE *fp = fopen(graft_file, "r"); - char buf[1024]; - if (!fp) - return -1; - while (fgets(buf, sizeof(buf), fp)) { - /* The format is just "Commit Parent1 Parent2 ...\n" */ - int len = strlen(buf); - struct commit_graft *graft = read_graft_line(buf, len); - if (graft) - register_commit_graft(graft, 0); - } - fclose(fp); - return 0; -} - -/* - * How many columns do we need to show line numbers in decimal? - */ -static int lineno_width(int lines) -{ - int i, width; - - for (width = 1, i = 10; i <= lines + 1; width++) - i *= 10; - return width; -} - -/* - * How many columns do we need to show line numbers, authors, - * and filenames? - */ -static void find_alignment(struct scoreboard *sb, int *option) -{ - int longest_src_lines = 0; - int longest_dst_lines = 0; - unsigned largest_score = 0; - struct blame_entry *e; - - for (e = sb->ent; e; e = e->next) { - struct origin *suspect = e->suspect; - struct commit_info ci; - int num; - - if (strcmp(suspect->path, sb->path)) - *option |= OUTPUT_SHOW_NAME; - num = strlen(suspect->path); - if (longest_file < num) - longest_file = num; - if (!(suspect->commit->object.flags & METAINFO_SHOWN)) { - suspect->commit->object.flags |= METAINFO_SHOWN; - get_commit_info(suspect->commit, &ci, 1); - num = utf8_strwidth(ci.author); - if (longest_author < num) - longest_author = num; - } - num = e->s_lno + e->num_lines; - if (longest_src_lines < num) - longest_src_lines = num; - num = e->lno + e->num_lines; - if (longest_dst_lines < num) - longest_dst_lines = num; - if (largest_score < ent_score(sb, e)) - largest_score = ent_score(sb, e); - } - max_orig_digits = lineno_width(longest_src_lines); - max_digits = lineno_width(longest_dst_lines); - max_score_digits = lineno_width(largest_score); -} - -/* - * For debugging -- origin is refcounted, and this asserts that - * we do not underflow. - */ -static void sanity_check_refcnt(struct scoreboard *sb) -{ - int baa = 0; - struct blame_entry *ent; - - for (ent = sb->ent; ent; ent = ent->next) { - /* Nobody should have zero or negative refcnt */ - if (ent->suspect->refcnt <= 0) { - fprintf(stderr, "%s in %s has negative refcnt %d\n", - ent->suspect->path, - sha1_to_hex(ent->suspect->commit->object.sha1), - ent->suspect->refcnt); - baa = 1; - } - } - if (baa) { - int opt = 0160; - find_alignment(sb, &opt); - output(sb, opt); - die("Baa %d!", baa); - } -} - -/* - * Used for the command line parsing; check if the path exists - * in the working tree. - */ -static int has_string_in_work_tree(const char *path) -{ - struct stat st; - return !lstat(path, &st); -} - -static unsigned parse_score(const char *arg) -{ - char *end; - unsigned long score = strtoul(arg, &end, 10); - if (*end) - return 0; - return score; -} - -static const char *add_prefix(const char *prefix, const char *path) -{ - return prefix_path(prefix, prefix ? strlen(prefix) : 0, path); -} - -/* - * Parsing of (comma separated) one item in the -L option - */ -static const char *parse_loc(const char *spec, - struct scoreboard *sb, long lno, - long begin, long *ret) -{ - char *term; - const char *line; - long num; - int reg_error; - regex_t regexp; - regmatch_t match[1]; - - /* Allow "-L ,+20" to mean starting at - * for 20 lines, or "-L ,-5" for 5 lines ending at - * . - */ - if (1 < begin && (spec[0] == '+' || spec[0] == '-')) { - num = strtol(spec + 1, &term, 10); - if (term != spec + 1) { - if (spec[0] == '-') - num = 0 - num; - if (0 < num) - *ret = begin + num - 2; - else if (!num) - *ret = begin; - else - *ret = begin + num; - return term; - } - return spec; - } - num = strtol(spec, &term, 10); - if (term != spec) { - *ret = num; - return term; - } - if (spec[0] != '/') - return spec; - - /* it could be a regexp of form /.../ */ - for (term = (char *) spec + 1; *term && *term != '/'; term++) { - if (*term == '\\') - term++; - } - if (*term != '/') - return spec; - - /* try [spec+1 .. term-1] as regexp */ - *term = 0; - begin--; /* input is in human terms */ - line = nth_line(sb, begin); - - if (!(reg_error = regcomp(®exp, spec + 1, REG_NEWLINE)) && - !(reg_error = regexec(®exp, line, 1, match, 0))) { - const char *cp = line + match[0].rm_so; - const char *nline; - - while (begin++ < lno) { - nline = nth_line(sb, begin); - if (line <= cp && cp < nline) - break; - line = nline; - } - *ret = begin; - regfree(®exp); - *term++ = '/'; - return term; - } - else { - char errbuf[1024]; - regerror(reg_error, ®exp, errbuf, 1024); - die("-L parameter '%s': %s", spec + 1, errbuf); - } -} - -/* - * Parsing of -L option - */ -static void prepare_blame_range(struct scoreboard *sb, - const char *bottomtop, - long lno, - long *bottom, long *top) -{ - const char *term; - - term = parse_loc(bottomtop, sb, lno, 1, bottom); - if (*term == ',') { - term = parse_loc(term + 1, sb, lno, *bottom + 1, top); - if (*term) - usage(blame_usage); - } - if (*term) - usage(blame_usage); -} - -static int git_blame_config(const char *var, const char *value, void *cb) -{ - if (!strcmp(var, "blame.showroot")) { - show_root = git_config_bool(var, value); - return 0; - } - if (!strcmp(var, "blame.blankboundary")) { - blank_boundary = git_config_bool(var, value); - return 0; - } - if (!strcmp(var, "blame.date")) { - if (!value) - return config_error_nonbool(var); - blame_date_mode = parse_date_format(value); - return 0; - } - return git_default_config(var, value, cb); -} - -/* - * Prepare a dummy commit that represents the work tree (or staged) item. - * Note that annotating work tree item never works in the reverse. - */ -static struct commit *fake_working_tree_commit(const char *path, const char *contents_from) -{ - struct commit *commit; - struct origin *origin; - unsigned char head_sha1[20]; - struct strbuf buf = STRBUF_INIT; - const char *ident; - time_t now; - int size, len; - struct cache_entry *ce; - unsigned mode; - - if (get_sha1("HEAD", head_sha1)) - die("No such ref: HEAD"); - - time(&now); - commit = xcalloc(1, sizeof(*commit)); - commit->parents = xcalloc(1, sizeof(*commit->parents)); - commit->parents->item = lookup_commit_reference(head_sha1); - commit->object.parsed = 1; - commit->date = now; - commit->object.type = OBJ_COMMIT; - - origin = make_origin(commit, path); - - if (!contents_from || strcmp("-", contents_from)) { - struct stat st; - const char *read_from; - - if (contents_from) { - if (stat(contents_from, &st) < 0) - die_errno("Cannot stat '%s'", contents_from); - read_from = contents_from; - } - else { - if (lstat(path, &st) < 0) - die_errno("Cannot lstat '%s'", path); - read_from = path; - } - mode = canon_mode(st.st_mode); - switch (st.st_mode & S_IFMT) { - case S_IFREG: - if (strbuf_read_file(&buf, read_from, st.st_size) != st.st_size) - die_errno("cannot open or read '%s'", read_from); - break; - case S_IFLNK: - if (strbuf_readlink(&buf, read_from, st.st_size) < 0) - die_errno("cannot readlink '%s'", read_from); - break; - default: - die("unsupported file type %s", read_from); - } - } - else { - /* Reading from stdin */ - contents_from = "standard input"; - mode = 0; - if (strbuf_read(&buf, 0, 0) < 0) - die_errno("failed to read from stdin"); - } - convert_to_git(path, buf.buf, buf.len, &buf, 0); - origin->file.ptr = buf.buf; - origin->file.size = buf.len; - pretend_sha1_file(buf.buf, buf.len, OBJ_BLOB, origin->blob_sha1); - commit->util = origin; - - /* - * Read the current index, replace the path entry with - * origin->blob_sha1 without mucking with its mode or type - * bits; we are not going to write this index out -- we just - * want to run "diff-index --cached". - */ - discard_cache(); - read_cache(); - - len = strlen(path); - if (!mode) { - int pos = cache_name_pos(path, len); - if (0 <= pos) - mode = active_cache[pos]->ce_mode; - else - /* Let's not bother reading from HEAD tree */ - mode = S_IFREG | 0644; - } - size = cache_entry_size(len); - ce = xcalloc(1, size); - hashcpy(ce->sha1, origin->blob_sha1); - memcpy(ce->name, path, len); - ce->ce_flags = create_ce_flags(len, 0); - ce->ce_mode = create_ce_mode(mode); - add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE); - - /* - * We are not going to write this out, so this does not matter - * right now, but someday we might optimize diff-index --cached - * with cache-tree information. - */ - cache_tree_invalidate_path(active_cache_tree, path); - - commit->buffer = xmalloc(400); - ident = fmt_ident("Not Committed Yet", "not.committed.yet", NULL, 0); - snprintf(commit->buffer, 400, - "tree 0000000000000000000000000000000000000000\n" - "parent %s\n" - "author %s\n" - "committer %s\n\n" - "Version of %s from %s\n", - sha1_to_hex(head_sha1), - ident, ident, path, contents_from ? contents_from : path); - return commit; -} - -static const char *prepare_final(struct scoreboard *sb) -{ - int i; - const char *final_commit_name = NULL; - struct rev_info *revs = sb->revs; - - /* - * There must be one and only one positive commit in the - * revs->pending array. - */ - for (i = 0; i < revs->pending.nr; i++) { - struct object *obj = revs->pending.objects[i].item; - if (obj->flags & UNINTERESTING) - continue; - while (obj->type == OBJ_TAG) - obj = deref_tag(obj, NULL, 0); - if (obj->type != OBJ_COMMIT) - die("Non commit %s?", revs->pending.objects[i].name); - if (sb->final) - die("More than one commit to dig from %s and %s?", - revs->pending.objects[i].name, - final_commit_name); - sb->final = (struct commit *) obj; - final_commit_name = revs->pending.objects[i].name; - } - return final_commit_name; -} - -static const char *prepare_initial(struct scoreboard *sb) -{ - int i; - const char *final_commit_name = NULL; - struct rev_info *revs = sb->revs; - - /* - * There must be one and only one negative commit, and it must be - * the boundary. - */ - for (i = 0; i < revs->pending.nr; i++) { - struct object *obj = revs->pending.objects[i].item; - if (!(obj->flags & UNINTERESTING)) - continue; - while (obj->type == OBJ_TAG) - obj = deref_tag(obj, NULL, 0); - if (obj->type != OBJ_COMMIT) - die("Non commit %s?", revs->pending.objects[i].name); - if (sb->final) - die("More than one commit to dig down to %s and %s?", - revs->pending.objects[i].name, - final_commit_name); - sb->final = (struct commit *) obj; - final_commit_name = revs->pending.objects[i].name; - } - if (!final_commit_name) - die("No commit to dig down to?"); - return final_commit_name; -} - -static int blame_copy_callback(const struct option *option, const char *arg, int unset) -{ - int *opt = option->value; - - /* - * -C enables copy from removed files; - * -C -C enables copy from existing files, but only - * when blaming a new file; - * -C -C -C enables copy from existing files for - * everybody - */ - if (*opt & PICKAXE_BLAME_COPY_HARDER) - *opt |= PICKAXE_BLAME_COPY_HARDEST; - if (*opt & PICKAXE_BLAME_COPY) - *opt |= PICKAXE_BLAME_COPY_HARDER; - *opt |= PICKAXE_BLAME_COPY | PICKAXE_BLAME_MOVE; - - if (arg) - blame_copy_score = parse_score(arg); - return 0; -} - -static int blame_move_callback(const struct option *option, const char *arg, int unset) -{ - int *opt = option->value; - - *opt |= PICKAXE_BLAME_MOVE; - - if (arg) - blame_move_score = parse_score(arg); - return 0; -} - -static int blame_bottomtop_callback(const struct option *option, const char *arg, int unset) -{ - const char **bottomtop = option->value; - if (!arg) - return -1; - if (*bottomtop) - die("More than one '-L n,m' option given"); - *bottomtop = arg; - return 0; -} - -int cmd_blame(int argc, const char **argv, const char *prefix) -{ - struct rev_info revs; - const char *path; - struct scoreboard sb; - struct origin *o; - struct blame_entry *ent; - long dashdash_pos, bottom, top, lno; - const char *final_commit_name = NULL; - enum object_type type; - - static const char *bottomtop = NULL; - static int output_option = 0, opt = 0; - static int show_stats = 0; - static const char *revs_file = NULL; - static const char *contents_from = NULL; - static const struct option options[] = { - OPT_BOOLEAN(0, "incremental", &incremental, "Show blame entries as we find them, incrementally"), - OPT_BOOLEAN('b', NULL, &blank_boundary, "Show blank SHA-1 for boundary commits (Default: off)"), - OPT_BOOLEAN(0, "root", &show_root, "Do not treat root commits as boundaries (Default: off)"), - OPT_BOOLEAN(0, "show-stats", &show_stats, "Show work cost statistics"), - OPT_BIT(0, "score-debug", &output_option, "Show output score for blame entries", OUTPUT_SHOW_SCORE), - OPT_BIT('f', "show-name", &output_option, "Show original filename (Default: auto)", OUTPUT_SHOW_NAME), - OPT_BIT('n', "show-number", &output_option, "Show original linenumber (Default: off)", OUTPUT_SHOW_NUMBER), - OPT_BIT('p', "porcelain", &output_option, "Show in a format designed for machine consumption", OUTPUT_PORCELAIN), - OPT_BIT('c', NULL, &output_option, "Use the same output mode as git-annotate (Default: off)", OUTPUT_ANNOTATE_COMPAT), - OPT_BIT('t', NULL, &output_option, "Show raw timestamp (Default: off)", OUTPUT_RAW_TIMESTAMP), - OPT_BIT('l', NULL, &output_option, "Show long commit SHA1 (Default: off)", OUTPUT_LONG_OBJECT_NAME), - OPT_BIT('s', NULL, &output_option, "Suppress author name and timestamp (Default: off)", OUTPUT_NO_AUTHOR), - OPT_BIT('w', NULL, &xdl_opts, "Ignore whitespace differences", XDF_IGNORE_WHITESPACE), - OPT_STRING('S', NULL, &revs_file, "file", "Use revisions from instead of calling git-rev-list"), - OPT_STRING(0, "contents", &contents_from, "file", "Use 's contents as the final image"), - { OPTION_CALLBACK, 'C', NULL, &opt, "score", "Find line copies within and across files", PARSE_OPT_OPTARG, blame_copy_callback }, - { OPTION_CALLBACK, 'M', NULL, &opt, "score", "Find line movements within and across files", PARSE_OPT_OPTARG, blame_move_callback }, - OPT_CALLBACK('L', NULL, &bottomtop, "n,m", "Process only line range n,m, counting from 1", blame_bottomtop_callback), - OPT_END() - }; - - struct parse_opt_ctx_t ctx; - int cmd_is_annotate = !strcmp(argv[0], "annotate"); - - git_config(git_blame_config, NULL); - init_revisions(&revs, NULL); - revs.date_mode = blame_date_mode; - - save_commit_buffer = 0; - dashdash_pos = 0; - - parse_options_start(&ctx, argc, argv, prefix, PARSE_OPT_KEEP_DASHDASH | - PARSE_OPT_KEEP_ARGV0); - for (;;) { - switch (parse_options_step(&ctx, options, blame_opt_usage)) { - case PARSE_OPT_HELP: - exit(129); - case PARSE_OPT_DONE: - if (ctx.argv[0]) - dashdash_pos = ctx.cpidx; - goto parse_done; - } - - if (!strcmp(ctx.argv[0], "--reverse")) { - ctx.argv[0] = "--children"; - reverse = 1; - } - parse_revision_opt(&revs, &ctx, options, blame_opt_usage); - } -parse_done: - argc = parse_options_end(&ctx); - - if (revs_file && read_ancestry(revs_file)) - die_errno("reading graft file '%s' failed", revs_file); - - if (cmd_is_annotate) { - output_option |= OUTPUT_ANNOTATE_COMPAT; - blame_date_mode = DATE_ISO8601; - } else { - blame_date_mode = revs.date_mode; - } - - /* The maximum width used to show the dates */ - switch (blame_date_mode) { - case DATE_RFC2822: - blame_date_width = sizeof("Thu, 19 Oct 2006 16:00:04 -0700"); - break; - case DATE_ISO8601: - blame_date_width = sizeof("2006-10-19 16:00:04 -0700"); - break; - case DATE_RAW: - blame_date_width = sizeof("1161298804 -0700"); - break; - case DATE_SHORT: - blame_date_width = sizeof("2006-10-19"); - break; - case DATE_RELATIVE: - /* "normal" is used as the fallback for "relative" */ - case DATE_LOCAL: - case DATE_NORMAL: - blame_date_width = sizeof("Thu Oct 19 16:00:04 2006 -0700"); - break; - } - blame_date_width -= 1; /* strip the null */ - - if (DIFF_OPT_TST(&revs.diffopt, FIND_COPIES_HARDER)) - opt |= (PICKAXE_BLAME_COPY | PICKAXE_BLAME_MOVE | - PICKAXE_BLAME_COPY_HARDER); - - if (!blame_move_score) - blame_move_score = BLAME_DEFAULT_MOVE_SCORE; - if (!blame_copy_score) - blame_copy_score = BLAME_DEFAULT_COPY_SCORE; - - /* - * We have collected options unknown to us in argv[1..unk] - * which are to be passed to revision machinery if we are - * going to do the "bottom" processing. - * - * The remaining are: - * - * (1) if dashdash_pos != 0, its either - * "blame [revisions] -- " or - * "blame -- " - * - * (2) otherwise, its one of the two: - * "blame [revisions] " - * "blame " - * - * Note that we must strip out from the arguments: we do not - * want the path pruning but we may want "bottom" processing. - */ - if (dashdash_pos) { - switch (argc - dashdash_pos - 1) { - case 2: /* (1b) */ - if (argc != 4) - usage_with_options(blame_opt_usage, options); - /* reorder for the new way: -- */ - argv[1] = argv[3]; - argv[3] = argv[2]; - argv[2] = "--"; - /* FALLTHROUGH */ - case 1: /* (1a) */ - path = add_prefix(prefix, argv[--argc]); - argv[argc] = NULL; - break; - default: - usage_with_options(blame_opt_usage, options); - } - } else { - if (argc < 2) - usage_with_options(blame_opt_usage, options); - path = add_prefix(prefix, argv[argc - 1]); - if (argc == 3 && !has_string_in_work_tree(path)) { /* (2b) */ - path = add_prefix(prefix, argv[1]); - argv[1] = argv[2]; - } - argv[argc - 1] = "--"; - - setup_work_tree(); - if (!has_string_in_work_tree(path)) - die_errno("cannot stat path '%s'", path); - } - - revs.disable_stdin = 1; - setup_revisions(argc, argv, &revs, NULL); - memset(&sb, 0, sizeof(sb)); - - sb.revs = &revs; - if (!reverse) - final_commit_name = prepare_final(&sb); - else if (contents_from) - die("--contents and --children do not blend well."); - else - final_commit_name = prepare_initial(&sb); - - if (!sb.final) { - /* - * "--not A B -- path" without anything positive; - * do not default to HEAD, but use the working tree - * or "--contents". - */ - setup_work_tree(); - sb.final = fake_working_tree_commit(path, contents_from); - add_pending_object(&revs, &(sb.final->object), ":"); - } - else if (contents_from) - die("Cannot use --contents with final commit object name"); - - /* - * If we have bottom, this will mark the ancestors of the - * bottom commits we would reach while traversing as - * uninteresting. - */ - if (prepare_revision_walk(&revs)) - die("revision walk setup failed"); - - if (is_null_sha1(sb.final->object.sha1)) { - char *buf; - o = sb.final->util; - buf = xmalloc(o->file.size + 1); - memcpy(buf, o->file.ptr, o->file.size + 1); - sb.final_buf = buf; - sb.final_buf_size = o->file.size; - } - else { - o = get_origin(&sb, sb.final, path); - if (fill_blob_sha1(o)) - die("no such path %s in %s", path, final_commit_name); - - sb.final_buf = read_sha1_file(o->blob_sha1, &type, - &sb.final_buf_size); - if (!sb.final_buf) - die("Cannot read blob %s for path %s", - sha1_to_hex(o->blob_sha1), - path); - } - num_read_blob++; - lno = prepare_lines(&sb); - - bottom = top = 0; - if (bottomtop) - prepare_blame_range(&sb, bottomtop, lno, &bottom, &top); - if (bottom && top && top < bottom) { - long tmp; - tmp = top; top = bottom; bottom = tmp; - } - if (bottom < 1) - bottom = 1; - if (top < 1) - top = lno; - bottom--; - if (lno < top || lno < bottom) - die("file %s has only %lu lines", path, lno); - - ent = xcalloc(1, sizeof(*ent)); - ent->lno = bottom; - ent->num_lines = top - bottom; - ent->suspect = o; - ent->s_lno = bottom; - - sb.ent = ent; - sb.path = path; - - read_mailmap(&mailmap, NULL); - - if (!incremental) - setup_pager(); - - assign_blame(&sb, opt); - - if (incremental) - return 0; - - coalesce(&sb); - - if (!(output_option & OUTPUT_PORCELAIN)) - find_alignment(&sb, &output_option); - - output(&sb, output_option); - free((void *)sb.final_buf); - for (ent = sb.ent; ent; ) { - struct blame_entry *e = ent->next; - free(ent); - ent = e; - } - - if (show_stats) { - printf("num read blob: %d\n", num_read_blob); - printf("num get patch: %d\n", num_get_patch); - printf("num commits: %d\n", num_commits); - } - return 0; -} diff --git a/builtin-branch.c b/builtin-branch.c deleted file mode 100644 index a28a139..0000000 --- a/builtin-branch.c +++ /dev/null @@ -1,696 +0,0 @@ -/* - * Builtin "git branch" - * - * Copyright (c) 2006 Kristian Høgsberg - * Based on git-branch.sh by Junio C Hamano. - */ - -#include "cache.h" -#include "color.h" -#include "refs.h" -#include "commit.h" -#include "builtin.h" -#include "remote.h" -#include "parse-options.h" -#include "branch.h" -#include "diff.h" -#include "revision.h" - -static const char * const builtin_branch_usage[] = { - "git branch [options] [-r | -a] [--merged | --no-merged]", - "git branch [options] [-l] [-f] []", - "git branch [options] [-r] (-d | -D) ", - "git branch [options] (-m | -M) [] ", - NULL -}; - -#define REF_LOCAL_BRANCH 0x01 -#define REF_REMOTE_BRANCH 0x02 - -static const char *head; -static unsigned char head_sha1[20]; - -static int branch_use_color = -1; -static char branch_colors[][COLOR_MAXLEN] = { - GIT_COLOR_RESET, - GIT_COLOR_NORMAL, /* PLAIN */ - GIT_COLOR_RED, /* REMOTE */ - GIT_COLOR_NORMAL, /* LOCAL */ - GIT_COLOR_GREEN, /* CURRENT */ -}; -enum color_branch { - BRANCH_COLOR_RESET = 0, - BRANCH_COLOR_PLAIN = 1, - BRANCH_COLOR_REMOTE = 2, - BRANCH_COLOR_LOCAL = 3, - BRANCH_COLOR_CURRENT = 4, -}; - -static enum merge_filter { - NO_FILTER = 0, - SHOW_NOT_MERGED, - SHOW_MERGED, -} merge_filter; -static unsigned char merge_filter_ref[20]; - -static int parse_branch_color_slot(const char *var, int ofs) -{ - if (!strcasecmp(var+ofs, "plain")) - return BRANCH_COLOR_PLAIN; - if (!strcasecmp(var+ofs, "reset")) - return BRANCH_COLOR_RESET; - if (!strcasecmp(var+ofs, "remote")) - return BRANCH_COLOR_REMOTE; - if (!strcasecmp(var+ofs, "local")) - return BRANCH_COLOR_LOCAL; - if (!strcasecmp(var+ofs, "current")) - return BRANCH_COLOR_CURRENT; - return -1; -} - -static int git_branch_config(const char *var, const char *value, void *cb) -{ - if (!strcmp(var, "color.branch")) { - branch_use_color = git_config_colorbool(var, value, -1); - return 0; - } - if (!prefixcmp(var, "color.branch.")) { - int slot = parse_branch_color_slot(var, 13); - if (slot < 0) - return 0; - if (!value) - return config_error_nonbool(var); - color_parse(value, var, branch_colors[slot]); - return 0; - } - return git_color_default_config(var, value, cb); -} - -static const char *branch_get_color(enum color_branch ix) -{ - if (branch_use_color > 0) - return branch_colors[ix]; - return ""; -} - -static int branch_merged(int kind, const char *name, - struct commit *rev, struct commit *head_rev) -{ - /* - * This checks whether the merge bases of branch and HEAD (or - * the other branch this branch builds upon) contains the - * branch, which means that the branch has already been merged - * safely to HEAD (or the other branch). - */ - struct commit *reference_rev = NULL; - const char *reference_name = NULL; - int merged; - - if (kind == REF_LOCAL_BRANCH) { - struct branch *branch = branch_get(name); - unsigned char sha1[20]; - - if (branch && - branch->merge && - branch->merge[0] && - branch->merge[0]->dst && - (reference_name = - resolve_ref(branch->merge[0]->dst, sha1, 1, NULL)) != NULL) - reference_rev = lookup_commit_reference(sha1); - } - if (!reference_rev) - reference_rev = head_rev; - - merged = in_merge_bases(rev, &reference_rev, 1); - - /* - * After the safety valve is fully redefined to "check with - * upstream, if any, otherwise with HEAD", we should just - * return the result of the in_merge_bases() above without - * any of the following code, but during the transition period, - * a gentle reminder is in order. - */ - if ((head_rev != reference_rev) && - in_merge_bases(rev, &head_rev, 1) != merged) { - if (merged) - warning("deleting branch '%s' that has been merged to\n" - " '%s', but it is not yet merged to HEAD.", - name, reference_name); - else - warning("not deleting branch '%s' that is not yet merged to\n" - " '%s', even though it is merged to HEAD.", - name, reference_name); - } - return merged; -} - -static int delete_branches(int argc, const char **argv, int force, int kinds) -{ - struct commit *rev, *head_rev = NULL; - unsigned char sha1[20]; - char *name = NULL; - const char *fmt, *remote; - int i; - int ret = 0; - struct strbuf bname = STRBUF_INIT; - - switch (kinds) { - case REF_REMOTE_BRANCH: - fmt = "refs/remotes/%s"; - remote = "remote "; - force = 1; - break; - case REF_LOCAL_BRANCH: - fmt = "refs/heads/%s"; - remote = ""; - break; - default: - die("cannot use -a with -d"); - } - - if (!force) { - head_rev = lookup_commit_reference(head_sha1); - if (!head_rev) - die("Couldn't look up commit object for HEAD"); - } - for (i = 0; i < argc; i++, strbuf_release(&bname)) { - strbuf_branchname(&bname, argv[i]); - if (kinds == REF_LOCAL_BRANCH && !strcmp(head, bname.buf)) { - error("Cannot delete the branch '%s' " - "which you are currently on.", bname.buf); - ret = 1; - continue; - } - - free(name); - - name = xstrdup(mkpath(fmt, bname.buf)); - if (!resolve_ref(name, sha1, 1, NULL)) { - error("%sbranch '%s' not found.", - remote, bname.buf); - ret = 1; - continue; - } - - rev = lookup_commit_reference(sha1); - if (!rev) { - error("Couldn't look up commit object for '%s'", name); - ret = 1; - continue; - } - - if (!force && !branch_merged(kinds, bname.buf, rev, head_rev)) { - error("The branch '%s' is not fully merged.\n" - "If you are sure you want to delete it, " - "run 'git branch -D %s'.", bname.buf, bname.buf); - ret = 1; - continue; - } - - if (delete_ref(name, sha1, 0)) { - error("Error deleting %sbranch '%s'", remote, - bname.buf); - ret = 1; - } else { - struct strbuf buf = STRBUF_INIT; - printf("Deleted %sbranch %s (was %s).\n", remote, - bname.buf, - find_unique_abbrev(sha1, DEFAULT_ABBREV)); - strbuf_addf(&buf, "branch.%s", bname.buf); - if (git_config_rename_section(buf.buf, NULL) < 0) - warning("Update of config-file failed"); - strbuf_release(&buf); - } - } - - free(name); - - return(ret); -} - -struct ref_item { - char *name; - char *dest; - unsigned int kind, len; - struct commit *commit; -}; - -struct ref_list { - struct rev_info revs; - int index, alloc, maxwidth, verbose, abbrev; - struct ref_item *list; - struct commit_list *with_commit; - int kinds; -}; - -static char *resolve_symref(const char *src, const char *prefix) -{ - unsigned char sha1[20]; - int flag; - const char *dst, *cp; - - dst = resolve_ref(src, sha1, 0, &flag); - if (!(dst && (flag & REF_ISSYMREF))) - return NULL; - if (prefix && (cp = skip_prefix(dst, prefix))) - dst = cp; - return xstrdup(dst); -} - -static int append_ref(const char *refname, const unsigned char *sha1, int flags, void *cb_data) -{ - struct ref_list *ref_list = (struct ref_list*)(cb_data); - struct ref_item *newitem; - struct commit *commit; - int kind, i; - const char *prefix, *orig_refname = refname; - - static struct { - int kind; - const char *prefix; - int pfxlen; - } ref_kind[] = { - { REF_LOCAL_BRANCH, "refs/heads/", 11 }, - { REF_REMOTE_BRANCH, "refs/remotes/", 13 }, - }; - - /* Detect kind */ - for (i = 0; i < ARRAY_SIZE(ref_kind); i++) { - prefix = ref_kind[i].prefix; - if (strncmp(refname, prefix, ref_kind[i].pfxlen)) - continue; - kind = ref_kind[i].kind; - refname += ref_kind[i].pfxlen; - break; - } - if (ARRAY_SIZE(ref_kind) <= i) - return 0; - - /* Don't add types the caller doesn't want */ - if ((kind & ref_list->kinds) == 0) - return 0; - - commit = NULL; - if (ref_list->verbose || ref_list->with_commit || merge_filter != NO_FILTER) { - commit = lookup_commit_reference_gently(sha1, 1); - if (!commit) - return error("branch '%s' does not point at a commit", refname); - - /* Filter with with_commit if specified */ - if (!is_descendant_of(commit, ref_list->with_commit)) - return 0; - - if (merge_filter != NO_FILTER) - add_pending_object(&ref_list->revs, - (struct object *)commit, refname); - } - - /* Resize buffer */ - if (ref_list->index >= ref_list->alloc) { - ref_list->alloc = alloc_nr(ref_list->alloc); - ref_list->list = xrealloc(ref_list->list, - ref_list->alloc * sizeof(struct ref_item)); - } - - /* Record the new item */ - newitem = &(ref_list->list[ref_list->index++]); - newitem->name = xstrdup(refname); - newitem->kind = kind; - newitem->commit = commit; - newitem->len = strlen(refname); - newitem->dest = resolve_symref(orig_refname, prefix); - /* adjust for "remotes/" */ - if (newitem->kind == REF_REMOTE_BRANCH && - ref_list->kinds != REF_REMOTE_BRANCH) - newitem->len += 8; - if (newitem->len > ref_list->maxwidth) - ref_list->maxwidth = newitem->len; - - return 0; -} - -static void free_ref_list(struct ref_list *ref_list) -{ - int i; - - for (i = 0; i < ref_list->index; i++) { - free(ref_list->list[i].name); - free(ref_list->list[i].dest); - } - free(ref_list->list); -} - -static int ref_cmp(const void *r1, const void *r2) -{ - struct ref_item *c1 = (struct ref_item *)(r1); - struct ref_item *c2 = (struct ref_item *)(r2); - - if (c1->kind != c2->kind) - return c1->kind - c2->kind; - return strcmp(c1->name, c2->name); -} - -static void fill_tracking_info(struct strbuf *stat, const char *branch_name, - int show_upstream_ref) -{ - int ours, theirs; - struct branch *branch = branch_get(branch_name); - - if (!stat_tracking_info(branch, &ours, &theirs)) { - if (branch && branch->merge && branch->merge[0]->dst && - show_upstream_ref) - strbuf_addf(stat, "[%s] ", - shorten_unambiguous_ref(branch->merge[0]->dst, 0)); - return; - } - - strbuf_addch(stat, '['); - if (show_upstream_ref) - strbuf_addf(stat, "%s: ", - shorten_unambiguous_ref(branch->merge[0]->dst, 0)); - if (!ours) - strbuf_addf(stat, "behind %d] ", theirs); - else if (!theirs) - strbuf_addf(stat, "ahead %d] ", ours); - else - strbuf_addf(stat, "ahead %d, behind %d] ", ours, theirs); -} - -static int matches_merge_filter(struct commit *commit) -{ - int is_merged; - - if (merge_filter == NO_FILTER) - return 1; - - is_merged = !!(commit->object.flags & UNINTERESTING); - return (is_merged == (merge_filter == SHOW_MERGED)); -} - -static void print_ref_item(struct ref_item *item, int maxwidth, int verbose, - int abbrev, int current, char *prefix) -{ - char c; - int color; - struct commit *commit = item->commit; - struct strbuf out = STRBUF_INIT, name = STRBUF_INIT; - - if (!matches_merge_filter(commit)) - return; - - switch (item->kind) { - case REF_LOCAL_BRANCH: - color = BRANCH_COLOR_LOCAL; - break; - case REF_REMOTE_BRANCH: - color = BRANCH_COLOR_REMOTE; - break; - default: - color = BRANCH_COLOR_PLAIN; - break; - } - - c = ' '; - if (current) { - c = '*'; - color = BRANCH_COLOR_CURRENT; - } - - strbuf_addf(&name, "%s%s", prefix, item->name); - if (verbose) - strbuf_addf(&out, "%c %s%-*s%s", c, branch_get_color(color), - maxwidth, name.buf, - branch_get_color(BRANCH_COLOR_RESET)); - else - strbuf_addf(&out, "%c %s%s%s", c, branch_get_color(color), - name.buf, branch_get_color(BRANCH_COLOR_RESET)); - - if (item->dest) - strbuf_addf(&out, " -> %s", item->dest); - else if (verbose) { - struct strbuf subject = STRBUF_INIT, stat = STRBUF_INIT; - const char *sub = " **** invalid ref ****"; - - commit = item->commit; - if (commit && !parse_commit(commit)) { - struct pretty_print_context ctx = {0}; - pretty_print_commit(CMIT_FMT_ONELINE, commit, - &subject, &ctx); - sub = subject.buf; - } - - if (item->kind == REF_LOCAL_BRANCH) - fill_tracking_info(&stat, item->name, verbose > 1); - - strbuf_addf(&out, " %s %s%s", - find_unique_abbrev(item->commit->object.sha1, abbrev), - stat.buf, sub); - strbuf_release(&stat); - strbuf_release(&subject); - } - printf("%s\n", out.buf); - strbuf_release(&name); - strbuf_release(&out); -} - -static int calc_maxwidth(struct ref_list *refs) -{ - int i, w = 0; - for (i = 0; i < refs->index; i++) { - if (!matches_merge_filter(refs->list[i].commit)) - continue; - if (refs->list[i].len > w) - w = refs->list[i].len; - } - return w; -} - - -static void show_detached(struct ref_list *ref_list) -{ - struct commit *head_commit = lookup_commit_reference_gently(head_sha1, 1); - - if (head_commit && is_descendant_of(head_commit, ref_list->with_commit)) { - struct ref_item item; - item.name = xstrdup("(no branch)"); - item.len = strlen(item.name); - item.kind = REF_LOCAL_BRANCH; - item.dest = NULL; - item.commit = head_commit; - if (item.len > ref_list->maxwidth) - ref_list->maxwidth = item.len; - print_ref_item(&item, ref_list->maxwidth, ref_list->verbose, ref_list->abbrev, 1, ""); - free(item.name); - } -} - -static void print_ref_list(int kinds, int detached, int verbose, int abbrev, struct commit_list *with_commit) -{ - int i; - struct ref_list ref_list; - - memset(&ref_list, 0, sizeof(ref_list)); - ref_list.kinds = kinds; - ref_list.verbose = verbose; - ref_list.abbrev = abbrev; - ref_list.with_commit = with_commit; - if (merge_filter != NO_FILTER) - init_revisions(&ref_list.revs, NULL); - for_each_rawref(append_ref, &ref_list); - if (merge_filter != NO_FILTER) { - struct commit *filter; - filter = lookup_commit_reference_gently(merge_filter_ref, 0); - filter->object.flags |= UNINTERESTING; - add_pending_object(&ref_list.revs, - (struct object *) filter, ""); - ref_list.revs.limited = 1; - prepare_revision_walk(&ref_list.revs); - if (verbose) - ref_list.maxwidth = calc_maxwidth(&ref_list); - } - - qsort(ref_list.list, ref_list.index, sizeof(struct ref_item), ref_cmp); - - detached = (detached && (kinds & REF_LOCAL_BRANCH)); - if (detached) - show_detached(&ref_list); - - for (i = 0; i < ref_list.index; i++) { - int current = !detached && - (ref_list.list[i].kind == REF_LOCAL_BRANCH) && - !strcmp(ref_list.list[i].name, head); - char *prefix = (kinds != REF_REMOTE_BRANCH && - ref_list.list[i].kind == REF_REMOTE_BRANCH) - ? "remotes/" : ""; - print_ref_item(&ref_list.list[i], ref_list.maxwidth, verbose, - abbrev, current, prefix); - } - - free_ref_list(&ref_list); -} - -static void rename_branch(const char *oldname, const char *newname, int force) -{ - struct strbuf oldref = STRBUF_INIT, newref = STRBUF_INIT, logmsg = STRBUF_INIT; - unsigned char sha1[20]; - struct strbuf oldsection = STRBUF_INIT, newsection = STRBUF_INIT; - int recovery = 0; - - if (!oldname) - die("cannot rename the current branch while not on any."); - - if (strbuf_check_branch_ref(&oldref, oldname)) { - /* - * Bad name --- this could be an attempt to rename a - * ref that we used to allow to be created by accident. - */ - if (resolve_ref(oldref.buf, sha1, 1, NULL)) - recovery = 1; - else - die("Invalid branch name: '%s'", oldname); - } - - if (strbuf_check_branch_ref(&newref, newname)) - die("Invalid branch name: '%s'", newname); - - if (resolve_ref(newref.buf, sha1, 1, NULL) && !force) - die("A branch named '%s' already exists.", newref.buf + 11); - - strbuf_addf(&logmsg, "Branch: renamed %s to %s", - oldref.buf, newref.buf); - - if (rename_ref(oldref.buf, newref.buf, logmsg.buf)) - die("Branch rename failed"); - strbuf_release(&logmsg); - - if (recovery) - warning("Renamed a misnamed branch '%s' away", oldref.buf + 11); - - /* no need to pass logmsg here as HEAD didn't really move */ - if (!strcmp(oldname, head) && create_symref("HEAD", newref.buf, NULL)) - die("Branch renamed to %s, but HEAD is not updated!", newname); - - strbuf_addf(&oldsection, "branch.%s", oldref.buf + 11); - strbuf_release(&oldref); - strbuf_addf(&newsection, "branch.%s", newref.buf + 11); - strbuf_release(&newref); - if (git_config_rename_section(oldsection.buf, newsection.buf) < 0) - die("Branch is renamed, but update of config-file failed"); - strbuf_release(&oldsection); - strbuf_release(&newsection); -} - -static int opt_parse_merge_filter(const struct option *opt, const char *arg, int unset) -{ - merge_filter = ((opt->long_name[0] == 'n') - ? SHOW_NOT_MERGED - : SHOW_MERGED); - if (unset) - merge_filter = SHOW_NOT_MERGED; /* b/c for --no-merged */ - if (!arg) - arg = "HEAD"; - if (get_sha1(arg, merge_filter_ref)) - die("malformed object name %s", arg); - return 0; -} - -int cmd_branch(int argc, const char **argv, const char *prefix) -{ - int delete = 0, rename = 0, force_create = 0; - int verbose = 0, abbrev = DEFAULT_ABBREV, detached = 0; - int reflog = 0; - enum branch_track track; - int kinds = REF_LOCAL_BRANCH; - struct commit_list *with_commit = NULL; - - struct option options[] = { - OPT_GROUP("Generic options"), - OPT__VERBOSE(&verbose), - OPT_SET_INT('t', "track", &track, "set up tracking mode (see git-pull(1))", - BRANCH_TRACK_EXPLICIT), - OPT_SET_INT( 0, "set-upstream", &track, "change upstream info", - BRANCH_TRACK_OVERRIDE), - OPT_BOOLEAN( 0 , "color", &branch_use_color, "use colored output"), - OPT_SET_INT('r', NULL, &kinds, "act on remote-tracking branches", - REF_REMOTE_BRANCH), - { - OPTION_CALLBACK, 0, "contains", &with_commit, "commit", - "print only branches that contain the commit", - PARSE_OPT_LASTARG_DEFAULT, - parse_opt_with_commit, (intptr_t)"HEAD", - }, - { - OPTION_CALLBACK, 0, "with", &with_commit, "commit", - "print only branches that contain the commit", - PARSE_OPT_HIDDEN | PARSE_OPT_LASTARG_DEFAULT, - parse_opt_with_commit, (intptr_t) "HEAD", - }, - OPT__ABBREV(&abbrev), - - OPT_GROUP("Specific git-branch actions:"), - OPT_SET_INT('a', NULL, &kinds, "list both remote-tracking and local branches", - REF_REMOTE_BRANCH | REF_LOCAL_BRANCH), - OPT_BIT('d', NULL, &delete, "delete fully merged branch", 1), - OPT_BIT('D', NULL, &delete, "delete branch (even if not merged)", 2), - OPT_BIT('m', NULL, &rename, "move/rename a branch and its reflog", 1), - OPT_BIT('M', NULL, &rename, "move/rename a branch, even if target exists", 2), - OPT_BOOLEAN('l', NULL, &reflog, "create the branch's reflog"), - OPT_BOOLEAN('f', "force", &force_create, "force creation (when already exists)"), - { - OPTION_CALLBACK, 0, "no-merged", &merge_filter_ref, - "commit", "print only not merged branches", - PARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NONEG, - opt_parse_merge_filter, (intptr_t) "HEAD", - }, - { - OPTION_CALLBACK, 0, "merged", &merge_filter_ref, - "commit", "print only merged branches", - PARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NONEG, - opt_parse_merge_filter, (intptr_t) "HEAD", - }, - OPT_END(), - }; - - git_config(git_branch_config, NULL); - - if (branch_use_color == -1) - branch_use_color = git_use_color_default; - - track = git_branch_track; - - head = resolve_ref("HEAD", head_sha1, 0, NULL); - if (!head) - die("Failed to resolve HEAD as a valid ref."); - head = xstrdup(head); - if (!strcmp(head, "HEAD")) { - detached = 1; - } else { - if (prefixcmp(head, "refs/heads/")) - die("HEAD not found below refs/heads!"); - head += 11; - } - hashcpy(merge_filter_ref, head_sha1); - - argc = parse_options(argc, argv, prefix, options, builtin_branch_usage, - 0); - if (!!delete + !!rename + !!force_create > 1) - usage_with_options(builtin_branch_usage, options); - - if (delete) - return delete_branches(argc, argv, delete > 1, kinds); - else if (argc == 0) - print_ref_list(kinds, detached, verbose, abbrev, with_commit); - else if (rename && (argc == 1)) - rename_branch(head, argv[0], rename > 1); - else if (rename && (argc == 2)) - rename_branch(argv[0], argv[1], rename > 1); - else if (argc <= 2) { - if (kinds != REF_LOCAL_BRANCH) - die("-a and -r options to 'git branch' do not make sense with a branch name"); - create_branch(head, argv[0], (argc == 2) ? argv[1] : head, - force_create, reflog, track); - } else - usage_with_options(builtin_branch_usage, options); - - return 0; -} diff --git a/builtin-bundle.c b/builtin-bundle.c deleted file mode 100644 index 2006cc5..0000000 --- a/builtin-bundle.c +++ /dev/null @@ -1,67 +0,0 @@ -#include "builtin.h" -#include "cache.h" -#include "bundle.h" - -/* - * Basic handler for bundle files to connect repositories via sneakernet. - * Invocation must include action. - * This function can create a bundle or provide information on an existing - * bundle supporting "fetch", "pull", and "ls-remote". - */ - -static const char builtin_bundle_usage[] = - "git bundle create \n" - " or: git bundle verify \n" - " or: git bundle list-heads [refname...]\n" - " or: git bundle unbundle [refname...]"; - -int cmd_bundle(int argc, const char **argv, const char *prefix) -{ - struct bundle_header header; - int nongit; - const char *cmd, *bundle_file; - int bundle_fd = -1; - char buffer[PATH_MAX]; - - if (argc < 3) - usage(builtin_bundle_usage); - - cmd = argv[1]; - bundle_file = argv[2]; - argc -= 2; - argv += 2; - - prefix = setup_git_directory_gently(&nongit); - if (prefix && bundle_file[0] != '/') { - snprintf(buffer, sizeof(buffer), "%s/%s", prefix, bundle_file); - bundle_file = buffer; - } - - memset(&header, 0, sizeof(header)); - if (strcmp(cmd, "create") && (bundle_fd = - read_bundle_header(bundle_file, &header)) < 0) - return 1; - - if (!strcmp(cmd, "verify")) { - close(bundle_fd); - if (verify_bundle(&header, 1)) - return 1; - fprintf(stderr, "%s is okay\n", bundle_file); - return 0; - } - if (!strcmp(cmd, "list-heads")) { - close(bundle_fd); - return !!list_bundle_refs(&header, argc, argv); - } - if (!strcmp(cmd, "create")) { - if (nongit) - die("Need a repository to create a bundle."); - return !!create_bundle(&header, bundle_file, argc, argv); - } else if (!strcmp(cmd, "unbundle")) { - if (nongit) - die("Need a repository to unbundle."); - return !!unbundle(&header, bundle_fd) || - list_bundle_refs(&header, argc, argv); - } else - usage(builtin_bundle_usage); -} diff --git a/builtin-cat-file.c b/builtin-cat-file.c deleted file mode 100644 index a933eaa..0000000 --- a/builtin-cat-file.c +++ /dev/null @@ -1,258 +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" -#include "builtin.h" -#include "parse-options.h" - -#define BATCH 1 -#define BATCH_CHECK 2 - -static void 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. - */ - write_or_die(1, 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 */ - write_or_die(1, tagger, - cp - tagger); - break; - } - while (sp < cp && - !('0' <= *sp && *sp <= '9')) - sp++; - write_or_die(1, tagger, sp - tagger); - date = strtoul(sp, &ep, 10); - tz = strtol(ep, NULL, 10); - sp = show_date(date, tz, 0); - write_or_die(1, 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) - write_or_die(1, cp, endp - cp); -} - -static int cat_one_file(int opt, const char *exp_type, const char *obj_name) -{ - unsigned char sha1[20]; - enum object_type type; - void *buf; - unsigned long size; - - if (get_sha1(obj_name, sha1)) - die("Not a valid object name %s", obj_name); - - buf = NULL; - switch (opt) { - case 't': - type = sha1_object_info(sha1, NULL); - if (type > 0) { - printf("%s\n", typename(type)); - return 0; - } - break; - - case 's': - type = sha1_object_info(sha1, &size); - if (type > 0) { - printf("%lu\n", size); - return 0; - } - break; - - case 'e': - return !has_sha1_file(sha1); - - case 'p': - type = sha1_object_info(sha1, NULL); - if (type < 0) - die("Not a valid object name %s", obj_name); - - /* custom pretty-print here */ - if (type == OBJ_TREE) { - const char *ls_args[3] = {"ls-tree", obj_name, NULL}; - return cmd_ls_tree(2, ls_args, NULL); - } - - buf = read_sha1_file(sha1, &type, &size); - if (!buf) - die("Cannot read object %s", obj_name); - if (type == OBJ_TAG) { - pprint_tag(sha1, buf, size); - return 0; - } - - /* otherwise just spit out the data */ - break; - case 0: - buf = read_object_with_reference(sha1, exp_type, &size, NULL); - break; - - default: - die("git cat-file: unknown option: %s", exp_type); - } - - if (!buf) - die("git cat-file %s: bad file", obj_name); - - write_or_die(1, buf, size); - return 0; -} - -static int batch_one_object(const char *obj_name, int print_contents) -{ - unsigned char sha1[20]; - enum object_type type = 0; - unsigned long size; - void *contents = contents; - - if (!obj_name) - return 1; - - if (get_sha1(obj_name, sha1)) { - printf("%s missing\n", obj_name); - fflush(stdout); - return 0; - } - - if (print_contents == BATCH) - contents = read_sha1_file(sha1, &type, &size); - else - type = sha1_object_info(sha1, &size); - - if (type <= 0) { - printf("%s missing\n", obj_name); - fflush(stdout); - return 0; - } - - printf("%s %s %lu\n", sha1_to_hex(sha1), typename(type), size); - fflush(stdout); - - if (print_contents == BATCH) { - write_or_die(1, contents, size); - printf("\n"); - fflush(stdout); - free(contents); - } - - return 0; -} - -static int batch_objects(int print_contents) -{ - struct strbuf buf = STRBUF_INIT; - - while (strbuf_getline(&buf, stdin, '\n') != EOF) { - int error = batch_one_object(buf.buf, print_contents); - if (error) - return error; - } - - return 0; -} - -static const char * const cat_file_usage[] = { - "git cat-file (-t|-s|-e|-p|) ", - "git cat-file (--batch|--batch-check) < ", - NULL -}; - -int cmd_cat_file(int argc, const char **argv, const char *prefix) -{ - int opt = 0, batch = 0; - const char *exp_type = NULL, *obj_name = NULL; - - const struct option options[] = { - OPT_GROUP(" can be one of: blob, tree, commit, tag"), - OPT_SET_INT('t', NULL, &opt, "show object type", 't'), - OPT_SET_INT('s', NULL, &opt, "show object size", 's'), - OPT_SET_INT('e', NULL, &opt, - "exit with zero when there's no error", 'e'), - OPT_SET_INT('p', NULL, &opt, "pretty-print object's content", 'p'), - OPT_SET_INT(0, "batch", &batch, - "show info and content of objects fed from the standard input", - BATCH), - OPT_SET_INT(0, "batch-check", &batch, - "show info about objects fed from the standard input", - BATCH_CHECK), - OPT_END() - }; - - git_config(git_default_config, NULL); - - if (argc != 3 && argc != 2) - usage_with_options(cat_file_usage, options); - - argc = parse_options(argc, argv, prefix, options, cat_file_usage, 0); - - if (opt) { - if (argc == 1) - obj_name = argv[0]; - else - usage_with_options(cat_file_usage, options); - } - if (!opt && !batch) { - if (argc == 2) { - exp_type = argv[0]; - obj_name = argv[1]; - } else - usage_with_options(cat_file_usage, options); - } - if (batch && (opt || argc)) { - usage_with_options(cat_file_usage, options); - } - - if (batch) - return batch_objects(batch); - - return cat_one_file(opt, exp_type, obj_name); -} diff --git a/builtin-check-attr.c b/builtin-check-attr.c deleted file mode 100644 index 3016d29..0000000 --- a/builtin-check-attr.c +++ /dev/null @@ -1,123 +0,0 @@ -#include "builtin.h" -#include "cache.h" -#include "attr.h" -#include "quote.h" -#include "parse-options.h" - -static int stdin_paths; -static const char * const check_attr_usage[] = { -"git check-attr attr... [--] pathname...", -"git check-attr --stdin attr... < ", -NULL -}; - -static int null_term_line; - -static const struct option check_attr_options[] = { - OPT_BOOLEAN(0 , "stdin", &stdin_paths, "read file names from stdin"), - OPT_BOOLEAN('z', NULL, &null_term_line, - "input paths are terminated by a null character"), - OPT_END() -}; - -static void check_attr(int cnt, struct git_attr_check *check, - const char** name, const char *file) -{ - int j; - if (git_checkattr(file, cnt, check)) - die("git_checkattr died"); - for (j = 0; j < cnt; j++) { - const char *value = check[j].value; - - if (ATTR_TRUE(value)) - value = "set"; - else if (ATTR_FALSE(value)) - value = "unset"; - else if (ATTR_UNSET(value)) - value = "unspecified"; - - quote_c_style(file, NULL, stdout, 0); - printf(": %s: %s\n", name[j], value); - } -} - -static void check_attr_stdin_paths(int cnt, struct git_attr_check *check, - const char** name) -{ - struct strbuf buf, nbuf; - int line_termination = null_term_line ? 0 : '\n'; - - strbuf_init(&buf, 0); - strbuf_init(&nbuf, 0); - while (strbuf_getline(&buf, stdin, line_termination) != EOF) { - if (line_termination && buf.buf[0] == '"') { - strbuf_reset(&nbuf); - if (unquote_c_style(&nbuf, buf.buf, NULL)) - die("line is badly quoted"); - strbuf_swap(&buf, &nbuf); - } - check_attr(cnt, check, name, buf.buf); - maybe_flush_or_die(stdout, "attribute to stdout"); - } - strbuf_release(&buf); - strbuf_release(&nbuf); -} - -int cmd_check_attr(int argc, const char **argv, const char *prefix) -{ - struct git_attr_check *check; - int cnt, i, doubledash; - const char *errstr = NULL; - - argc = parse_options(argc, argv, prefix, check_attr_options, - check_attr_usage, PARSE_OPT_KEEP_DASHDASH); - if (!argc) - usage_with_options(check_attr_usage, check_attr_options); - - if (read_cache() < 0) { - die("invalid cache"); - } - - doubledash = -1; - for (i = 0; doubledash < 0 && i < argc; i++) { - if (!strcmp(argv[i], "--")) - doubledash = i; - } - - /* If there is no double dash, we handle only one attribute */ - if (doubledash < 0) { - cnt = 1; - doubledash = 0; - } else - cnt = doubledash; - doubledash++; - - if (cnt <= 0) - errstr = "No attribute specified"; - else if (stdin_paths && doubledash < argc) - errstr = "Can't specify files with --stdin"; - if (errstr) { - error("%s", errstr); - usage_with_options(check_attr_usage, check_attr_options); - } - - check = xcalloc(cnt, sizeof(*check)); - for (i = 0; i < cnt; i++) { - const char *name; - struct git_attr *a; - name = argv[i]; - a = git_attr(name); - if (!a) - return error("%s: not a valid attribute name", name); - check[i].attr = a; - } - - if (stdin_paths) - check_attr_stdin_paths(cnt, check, argv); - else { - for (i = doubledash; i < argc; i++) - check_attr(cnt, check, argv, argv[i]); - maybe_flush_or_die(stdout, "attribute to stdout"); - } - return 0; -} diff --git a/builtin-check-ref-format.c b/builtin-check-ref-format.c deleted file mode 100644 index b106c65..0000000 --- a/builtin-check-ref-format.c +++ /dev/null @@ -1,61 +0,0 @@ -/* - * GIT - The information manager from hell - */ - -#include "cache.h" -#include "refs.h" -#include "builtin.h" -#include "strbuf.h" - -static const char builtin_check_ref_format_usage[] = -"git check-ref-format [--print] \n" -" or: git check-ref-format --branch "; - -/* - * Replace each run of adjacent slashes in src with a single slash, - * and write the result to dst. - * - * This function is similar to normalize_path_copy(), but stripped down - * to meet check_ref_format's simpler needs. - */ -static void collapse_slashes(char *dst, const char *src) -{ - char ch; - char prev = '\0'; - - while ((ch = *src++) != '\0') { - if (prev == '/' && ch == prev) - continue; - - *dst++ = ch; - prev = ch; - } - *dst = '\0'; -} - -int cmd_check_ref_format(int argc, const char **argv, const char *prefix) -{ - if (argc == 2 && !strcmp(argv[1], "-h")) - usage(builtin_check_ref_format_usage); - - if (argc == 3 && !strcmp(argv[1], "--branch")) { - struct strbuf sb = STRBUF_INIT; - - if (strbuf_check_branch_ref(&sb, argv[2])) - die("'%s' is not a valid branch name", argv[2]); - printf("%s\n", sb.buf + 11); - exit(0); - } - if (argc == 3 && !strcmp(argv[1], "--print")) { - char *refname = xmalloc(strlen(argv[2]) + 1); - - if (check_ref_format(argv[2])) - exit(1); - collapse_slashes(refname, argv[2]); - printf("%s\n", refname); - exit(0); - } - if (argc != 2) - usage(builtin_check_ref_format_usage); - return !!check_ref_format(argv[1]); -} diff --git a/builtin-checkout-index.c b/builtin-checkout-index.c deleted file mode 100644 index a7a5ee1..0000000 --- a/builtin-checkout-index.c +++ /dev/null @@ -1,315 +0,0 @@ -/* - * Check-out files from the "current cache directory" - * - * Copyright (C) 2005 Linus Torvalds - * - * Careful: order of argument flags does matter. For example, - * - * git checkout-index -a -f file.c - * - * Will first check out all files listed in the cache (but not - * overwrite any old ones), and then force-checkout "file.c" a - * second time (ie that one _will_ overwrite any old contents - * with the same filename). - * - * Also, just doing "git checkout-index" does nothing. You probably - * meant "git checkout-index -a". And if you want to force it, you - * want "git checkout-index -f -a". - * - * Intuitiveness is not the goal here. Repeatability is. The - * reason for the "no arguments means no work" thing is that - * from scripts you are supposed to be able to do things like - * - * find . -name '*.h' -print0 | xargs -0 git checkout-index -f -- - * - * or: - * - * find . -name '*.h' -print0 | git checkout-index -f -z --stdin - * - * which will force all existing *.h files to be replaced with - * their cached copies. If an empty command line implied "all", - * then this would force-refresh everything in the cache, which - * was not the point. - * - * Oh, and the "--" is just a good idea when you know the rest - * will be filenames. Just so that you wouldn't have a filename - * of "-a" causing problems (not possible in the above example, - * but get used to it in scripting!). - */ -#include "builtin.h" -#include "cache.h" -#include "quote.h" -#include "cache-tree.h" -#include "parse-options.h" - -#define CHECKOUT_ALL 4 -static int line_termination = '\n'; -static int checkout_stage; /* default to checkout stage0 */ -static int to_tempfile; -static char topath[4][PATH_MAX + 1]; - -static struct checkout state; - -static void write_tempfile_record(const char *name, int prefix_length) -{ - int i; - - if (CHECKOUT_ALL == checkout_stage) { - for (i = 1; i < 4; i++) { - if (i > 1) - putchar(' '); - if (topath[i][0]) - fputs(topath[i], stdout); - else - putchar('.'); - } - } else - fputs(topath[checkout_stage], stdout); - - putchar('\t'); - write_name_quoted(name + prefix_length, stdout, line_termination); - - for (i = 0; i < 4; i++) { - topath[i][0] = 0; - } -} - -static int checkout_file(const char *name, int prefix_length) -{ - int namelen = strlen(name); - int pos = cache_name_pos(name, namelen); - int has_same_name = 0; - int did_checkout = 0; - int errs = 0; - - if (pos < 0) - pos = -pos - 1; - - while (pos < active_nr) { - struct cache_entry *ce = active_cache[pos]; - if (ce_namelen(ce) != namelen || - memcmp(ce->name, name, namelen)) - break; - has_same_name = 1; - pos++; - if (ce_stage(ce) != checkout_stage - && (CHECKOUT_ALL != checkout_stage || !ce_stage(ce))) - continue; - did_checkout = 1; - if (checkout_entry(ce, &state, - to_tempfile ? topath[ce_stage(ce)] : NULL) < 0) - errs++; - } - - if (did_checkout) { - if (to_tempfile) - write_tempfile_record(name, prefix_length); - return errs > 0 ? -1 : 0; - } - - if (!state.quiet) { - fprintf(stderr, "git checkout-index: %s ", name); - if (!has_same_name) - fprintf(stderr, "is not in the cache"); - else if (checkout_stage) - fprintf(stderr, "does not exist at stage %d", - checkout_stage); - else - fprintf(stderr, "is unmerged"); - fputc('\n', stderr); - } - return -1; -} - -static void checkout_all(const char *prefix, int prefix_length) -{ - int i, errs = 0; - struct cache_entry *last_ce = NULL; - - for (i = 0; i < active_nr ; i++) { - struct cache_entry *ce = active_cache[i]; - if (ce_stage(ce) != checkout_stage - && (CHECKOUT_ALL != checkout_stage || !ce_stage(ce))) - continue; - if (prefix && *prefix && - (ce_namelen(ce) <= prefix_length || - memcmp(prefix, ce->name, prefix_length))) - continue; - if (last_ce && to_tempfile) { - if (ce_namelen(last_ce) != ce_namelen(ce) - || memcmp(last_ce->name, ce->name, ce_namelen(ce))) - write_tempfile_record(last_ce->name, prefix_length); - } - if (checkout_entry(ce, &state, - to_tempfile ? topath[ce_stage(ce)] : NULL) < 0) - errs++; - last_ce = ce; - } - if (last_ce && to_tempfile) - write_tempfile_record(last_ce->name, prefix_length); - if (errs) - /* we have already done our error reporting. - * exit with the same code as die(). - */ - exit(128); -} - -static const char * const builtin_checkout_index_usage[] = { - "git checkout-index [options] [--] ...", - NULL -}; - -static struct lock_file lock_file; - -static int option_parse_u(const struct option *opt, - const char *arg, int unset) -{ - int *newfd = opt->value; - - state.refresh_cache = 1; - if (*newfd < 0) - *newfd = hold_locked_index(&lock_file, 1); - return 0; -} - -static int option_parse_z(const struct option *opt, - const char *arg, int unset) -{ - if (unset) - line_termination = '\n'; - else - line_termination = 0; - return 0; -} - -static int option_parse_prefix(const struct option *opt, - const char *arg, int unset) -{ - state.base_dir = arg; - state.base_dir_len = strlen(arg); - return 0; -} - -static int option_parse_stage(const struct option *opt, - const char *arg, int unset) -{ - if (!strcmp(arg, "all")) { - to_tempfile = 1; - checkout_stage = CHECKOUT_ALL; - } else { - int ch = arg[0]; - if ('1' <= ch && ch <= '3') - checkout_stage = arg[0] - '0'; - else - die("stage should be between 1 and 3 or all"); - } - return 0; -} - -int cmd_checkout_index(int argc, const char **argv, const char *prefix) -{ - int i; - int newfd = -1; - int all = 0; - int read_from_stdin = 0; - int prefix_length; - int force = 0, quiet = 0, not_new = 0; - struct option builtin_checkout_index_options[] = { - OPT_BOOLEAN('a', "all", &all, - "checks out all files in the index"), - OPT_BOOLEAN('f', "force", &force, - "forces overwrite of existing files"), - OPT__QUIET(&quiet), - OPT_BOOLEAN('n', "no-create", ¬_new, - "don't checkout new files"), - { OPTION_CALLBACK, 'u', "index", &newfd, NULL, - "update stat information in the index file", - PARSE_OPT_NOARG, option_parse_u }, - { OPTION_CALLBACK, 'z', NULL, NULL, NULL, - "paths are separated with NUL character", - PARSE_OPT_NOARG, option_parse_z }, - OPT_BOOLEAN(0, "stdin", &read_from_stdin, - "read list of paths from the standard input"), - OPT_BOOLEAN(0, "temp", &to_tempfile, - "write the content to temporary files"), - OPT_CALLBACK(0, "prefix", NULL, "string", - "when creating files, prepend ", - option_parse_prefix), - OPT_CALLBACK(0, "stage", NULL, NULL, - "copy out the files from named stage", - option_parse_stage), - OPT_END() - }; - - git_config(git_default_config, NULL); - state.base_dir = ""; - prefix_length = prefix ? strlen(prefix) : 0; - - if (read_cache() < 0) { - die("invalid cache"); - } - - argc = parse_options(argc, argv, prefix, builtin_checkout_index_options, - builtin_checkout_index_usage, 0); - state.force = force; - state.quiet = quiet; - state.not_new = not_new; - - if (state.base_dir_len || to_tempfile) { - /* when --prefix is specified we do not - * want to update cache. - */ - if (state.refresh_cache) { - rollback_lock_file(&lock_file); - newfd = -1; - } - state.refresh_cache = 0; - } - - /* Check out named files first */ - for (i = 0; i < argc; i++) { - const char *arg = argv[i]; - const char *p; - - if (all) - die("git checkout-index: don't mix '--all' and explicit filenames"); - if (read_from_stdin) - die("git checkout-index: don't mix '--stdin' and explicit filenames"); - p = prefix_path(prefix, prefix_length, arg); - checkout_file(p, prefix_length); - if (p < arg || p > arg + strlen(arg)) - free((char *)p); - } - - if (read_from_stdin) { - struct strbuf buf = STRBUF_INIT, nbuf = STRBUF_INIT; - - if (all) - die("git checkout-index: don't mix '--all' and '--stdin'"); - - while (strbuf_getline(&buf, stdin, line_termination) != EOF) { - const char *p; - if (line_termination && buf.buf[0] == '"') { - strbuf_reset(&nbuf); - if (unquote_c_style(&nbuf, buf.buf, NULL)) - die("line is badly quoted"); - strbuf_swap(&buf, &nbuf); - } - p = prefix_path(prefix, prefix_length, buf.buf); - checkout_file(p, prefix_length); - if (p < buf.buf || p > buf.buf + buf.len) - free((char *)p); - } - strbuf_release(&nbuf); - strbuf_release(&buf); - } - - if (all) - checkout_all(prefix, prefix_length); - - if (0 <= newfd && - (write_cache(newfd, active_cache, active_nr) || - commit_locked_index(&lock_file))) - die("Unable to write new index file"); - return 0; -} diff --git a/builtin-checkout.c b/builtin-checkout.c deleted file mode 100644 index c5ab783..0000000 --- a/builtin-checkout.c +++ /dev/null @@ -1,853 +0,0 @@ -#include "cache.h" -#include "builtin.h" -#include "parse-options.h" -#include "refs.h" -#include "commit.h" -#include "tree.h" -#include "tree-walk.h" -#include "cache-tree.h" -#include "unpack-trees.h" -#include "dir.h" -#include "run-command.h" -#include "merge-recursive.h" -#include "branch.h" -#include "diff.h" -#include "revision.h" -#include "remote.h" -#include "blob.h" -#include "xdiff-interface.h" -#include "ll-merge.h" -#include "resolve-undo.h" - -static const char * const checkout_usage[] = { - "git checkout [options] ", - "git checkout [options] [] -- ...", - NULL, -}; - -struct checkout_opts { - int quiet; - int merge; - int force; - int writeout_stage; - int writeout_error; - - const char *new_branch; - int new_branch_log; - enum branch_track track; -}; - -static int post_checkout_hook(struct commit *old, struct commit *new, - int changed) -{ - return run_hook(NULL, "post-checkout", - sha1_to_hex(old ? old->object.sha1 : null_sha1), - sha1_to_hex(new ? new->object.sha1 : null_sha1), - changed ? "1" : "0", NULL); - /* "new" can be NULL when checking out from the index before - a commit exists. */ - -} - -static int update_some(const unsigned char *sha1, const char *base, int baselen, - const char *pathname, unsigned mode, int stage, void *context) -{ - int len; - struct cache_entry *ce; - - if (S_ISDIR(mode)) - return READ_TREE_RECURSIVE; - - len = baselen + strlen(pathname); - ce = xcalloc(1, cache_entry_size(len)); - hashcpy(ce->sha1, sha1); - memcpy(ce->name, base, baselen); - memcpy(ce->name + baselen, pathname, len - baselen); - ce->ce_flags = create_ce_flags(len, 0); - ce->ce_mode = create_ce_mode(mode); - add_cache_entry(ce, ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE); - return 0; -} - -static int read_tree_some(struct tree *tree, const char **pathspec) -{ - read_tree_recursive(tree, "", 0, 0, pathspec, update_some, NULL); - - /* update the index with the given tree's info - * for all args, expanding wildcards, and exit - * with any non-zero return code. - */ - return 0; -} - -static int skip_same_name(struct cache_entry *ce, int pos) -{ - while (++pos < active_nr && - !strcmp(active_cache[pos]->name, ce->name)) - ; /* skip */ - return pos; -} - -static int check_stage(int stage, struct cache_entry *ce, int pos) -{ - while (pos < active_nr && - !strcmp(active_cache[pos]->name, ce->name)) { - if (ce_stage(active_cache[pos]) == stage) - return 0; - pos++; - } - return error("path '%s' does not have %s version", - ce->name, - (stage == 2) ? "our" : "their"); -} - -static int check_all_stages(struct cache_entry *ce, int pos) -{ - if (ce_stage(ce) != 1 || - active_nr <= pos + 2 || - strcmp(active_cache[pos+1]->name, ce->name) || - ce_stage(active_cache[pos+1]) != 2 || - strcmp(active_cache[pos+2]->name, ce->name) || - ce_stage(active_cache[pos+2]) != 3) - return error("path '%s' does not have all three versions", - ce->name); - return 0; -} - -static int checkout_stage(int stage, struct cache_entry *ce, int pos, - struct checkout *state) -{ - while (pos < active_nr && - !strcmp(active_cache[pos]->name, ce->name)) { - if (ce_stage(active_cache[pos]) == stage) - return checkout_entry(active_cache[pos], state, NULL); - pos++; - } - return error("path '%s' does not have %s version", - ce->name, - (stage == 2) ? "our" : "their"); -} - -/* NEEDSWORK: share with merge-recursive */ -static void fill_mm(const unsigned char *sha1, mmfile_t *mm) -{ - unsigned long size; - enum object_type type; - - if (!hashcmp(sha1, null_sha1)) { - mm->ptr = xstrdup(""); - mm->size = 0; - return; - } - - mm->ptr = read_sha1_file(sha1, &type, &size); - if (!mm->ptr || type != OBJ_BLOB) - die("unable to read blob object %s", sha1_to_hex(sha1)); - mm->size = size; -} - -static int checkout_merged(int pos, struct checkout *state) -{ - struct cache_entry *ce = active_cache[pos]; - const char *path = ce->name; - mmfile_t ancestor, ours, theirs; - int status; - unsigned char sha1[20]; - mmbuffer_t result_buf; - - if (ce_stage(ce) != 1 || - active_nr <= pos + 2 || - strcmp(active_cache[pos+1]->name, path) || - ce_stage(active_cache[pos+1]) != 2 || - strcmp(active_cache[pos+2]->name, path) || - ce_stage(active_cache[pos+2]) != 3) - return error("path '%s' does not have all 3 versions", path); - - fill_mm(active_cache[pos]->sha1, &ancestor); - fill_mm(active_cache[pos+1]->sha1, &ours); - fill_mm(active_cache[pos+2]->sha1, &theirs); - - status = ll_merge(&result_buf, path, &ancestor, - &ours, "ours", &theirs, "theirs", 0); - free(ancestor.ptr); - free(ours.ptr); - free(theirs.ptr); - if (status < 0 || !result_buf.ptr) { - free(result_buf.ptr); - return error("path '%s': cannot merge", path); - } - - /* - * NEEDSWORK: - * There is absolutely no reason to write this as a blob object - * and create a phony cache entry just to leak. This hack is - * primarily to get to the write_entry() machinery that massages - * the contents to work-tree format and writes out which only - * allows it for a cache entry. The code in write_entry() needs - * to be refactored to allow us to feed a - * instead of a cache entry. Such a refactoring would help - * merge_recursive as well (it also writes the merge result to the - * object database even when it may contain conflicts). - */ - if (write_sha1_file(result_buf.ptr, result_buf.size, - blob_type, sha1)) - die("Unable to add merge result for '%s'", path); - ce = make_cache_entry(create_ce_mode(active_cache[pos+1]->ce_mode), - sha1, - path, 2, 0); - if (!ce) - die("make_cache_entry failed for path '%s'", path); - status = checkout_entry(ce, state, NULL); - return status; -} - -static int checkout_paths(struct tree *source_tree, const char **pathspec, - struct checkout_opts *opts) -{ - int pos; - struct checkout state; - static char *ps_matched; - unsigned char rev[20]; - int flag; - struct commit *head; - int errs = 0; - int stage = opts->writeout_stage; - int merge = opts->merge; - int newfd; - struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file)); - - newfd = hold_locked_index(lock_file, 1); - if (read_cache_preload(pathspec) < 0) - return error("corrupt index file"); - - if (source_tree) - read_tree_some(source_tree, pathspec); - - for (pos = 0; pathspec[pos]; pos++) - ; - ps_matched = xcalloc(1, pos); - - for (pos = 0; pos < active_nr; pos++) { - struct cache_entry *ce = active_cache[pos]; - match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, ps_matched); - } - - if (report_path_error(ps_matched, pathspec, 0)) - return 1; - - /* "checkout -m path" to recreate conflicted state */ - if (opts->merge) - unmerge_cache(pathspec); - - /* Any unmerged paths? */ - for (pos = 0; pos < active_nr; pos++) { - struct cache_entry *ce = active_cache[pos]; - if (match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, NULL)) { - if (!ce_stage(ce)) - continue; - if (opts->force) { - warning("path '%s' is unmerged", ce->name); - } else if (stage) { - errs |= check_stage(stage, ce, pos); - } else if (opts->merge) { - errs |= check_all_stages(ce, pos); - } else { - errs = 1; - error("path '%s' is unmerged", ce->name); - } - pos = skip_same_name(ce, pos) - 1; - } - } - if (errs) - return 1; - - /* Now we are committed to check them out */ - memset(&state, 0, sizeof(state)); - state.force = 1; - state.refresh_cache = 1; - for (pos = 0; pos < active_nr; pos++) { - struct cache_entry *ce = active_cache[pos]; - if (match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, NULL)) { - if (!ce_stage(ce)) { - errs |= checkout_entry(ce, &state, NULL); - continue; - } - if (stage) - errs |= checkout_stage(stage, ce, pos, &state); - else if (merge) - errs |= checkout_merged(pos, &state); - pos = skip_same_name(ce, pos) - 1; - } - } - - if (write_cache(newfd, active_cache, active_nr) || - commit_locked_index(lock_file)) - die("unable to write new index file"); - - resolve_ref("HEAD", rev, 0, &flag); - head = lookup_commit_reference_gently(rev, 1); - - errs |= post_checkout_hook(head, head, 0); - return errs; -} - -static void show_local_changes(struct object *head) -{ - struct rev_info rev; - /* I think we want full paths, even if we're in a subdirectory. */ - init_revisions(&rev, NULL); - rev.abbrev = 0; - rev.diffopt.output_format |= DIFF_FORMAT_NAME_STATUS; - if (diff_setup_done(&rev.diffopt) < 0) - die("diff_setup_done failed"); - add_pending_object(&rev, head, NULL); - run_diff_index(&rev, 0); -} - -static void describe_detached_head(char *msg, struct commit *commit) -{ - struct strbuf sb = STRBUF_INIT; - struct pretty_print_context ctx = {0}; - parse_commit(commit); - pretty_print_commit(CMIT_FMT_ONELINE, commit, &sb, &ctx); - fprintf(stderr, "%s %s... %s\n", msg, - find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV), sb.buf); - strbuf_release(&sb); -} - -static int reset_tree(struct tree *tree, struct checkout_opts *o, int worktree) -{ - struct unpack_trees_options opts; - struct tree_desc tree_desc; - - memset(&opts, 0, sizeof(opts)); - opts.head_idx = -1; - opts.update = worktree; - opts.skip_unmerged = !worktree; - opts.reset = 1; - opts.merge = 1; - opts.fn = oneway_merge; - opts.verbose_update = !o->quiet; - opts.src_index = &the_index; - opts.dst_index = &the_index; - parse_tree(tree); - init_tree_desc(&tree_desc, tree->buffer, tree->size); - switch (unpack_trees(1, &tree_desc, &opts)) { - case -2: - o->writeout_error = 1; - /* - * We return 0 nevertheless, as the index is all right - * and more importantly we have made best efforts to - * update paths in the work tree, and we cannot revert - * them. - */ - case 0: - return 0; - default: - return 128; - } -} - -struct branch_info { - const char *name; /* The short name used */ - const char *path; /* The full name of a real branch */ - struct commit *commit; /* The named commit */ -}; - -static void setup_branch_path(struct branch_info *branch) -{ - struct strbuf buf = STRBUF_INIT; - - strbuf_branchname(&buf, branch->name); - if (strcmp(buf.buf, branch->name)) - branch->name = xstrdup(buf.buf); - strbuf_splice(&buf, 0, 0, "refs/heads/", 11); - branch->path = strbuf_detach(&buf, NULL); -} - -static int merge_working_tree(struct checkout_opts *opts, - struct branch_info *old, struct branch_info *new) -{ - int ret; - struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file)); - int newfd = hold_locked_index(lock_file, 1); - - if (read_cache_preload(NULL) < 0) - return error("corrupt index file"); - - resolve_undo_clear(); - if (opts->force) { - ret = reset_tree(new->commit->tree, opts, 1); - if (ret) - return ret; - } else { - struct tree_desc trees[2]; - struct tree *tree; - struct unpack_trees_options topts; - - memset(&topts, 0, sizeof(topts)); - topts.head_idx = -1; - topts.src_index = &the_index; - topts.dst_index = &the_index; - - topts.msgs.not_uptodate_file = "You have local changes to '%s'; cannot switch branches."; - - refresh_cache(REFRESH_QUIET); - - if (unmerged_cache()) { - error("you need to resolve your current index first"); - return 1; - } - - /* 2-way merge to the new branch */ - topts.initial_checkout = is_cache_unborn(); - topts.update = 1; - topts.merge = 1; - topts.gently = opts->merge && old->commit; - topts.verbose_update = !opts->quiet; - topts.fn = twoway_merge; - topts.dir = xcalloc(1, sizeof(*topts.dir)); - topts.dir->flags |= DIR_SHOW_IGNORED; - topts.dir->exclude_per_dir = ".gitignore"; - tree = parse_tree_indirect(old->commit ? - old->commit->object.sha1 : - (unsigned char *)EMPTY_TREE_SHA1_BIN); - init_tree_desc(&trees[0], tree->buffer, tree->size); - tree = parse_tree_indirect(new->commit->object.sha1); - init_tree_desc(&trees[1], tree->buffer, tree->size); - - ret = unpack_trees(2, trees, &topts); - if (ret == -1) { - /* - * Unpack couldn't do a trivial merge; either - * give up or do a real merge, depending on - * whether the merge flag was used. - */ - struct tree *result; - struct tree *work; - struct merge_options o; - if (!opts->merge) - return 1; - - /* - * Without old->commit, the below is the same as - * the two-tree unpack we already tried and failed. - */ - if (!old->commit) - return 1; - - /* Do more real merge */ - - /* - * We update the index fully, then write the - * tree from the index, then merge the new - * branch with the current tree, with the old - * branch as the base. Then we reset the index - * (but not the working tree) to the new - * branch, leaving the working tree as the - * merged version, but skipping unmerged - * entries in the index. - */ - - add_files_to_cache(NULL, NULL, 0); - init_merge_options(&o); - o.verbosity = 0; - work = write_tree_from_memory(&o); - - ret = reset_tree(new->commit->tree, opts, 1); - if (ret) - return ret; - o.branch1 = new->name; - o.branch2 = "local"; - merge_trees(&o, new->commit->tree, work, - old->commit->tree, &result); - ret = reset_tree(new->commit->tree, opts, 0); - if (ret) - return ret; - } - } - - if (write_cache(newfd, active_cache, active_nr) || - commit_locked_index(lock_file)) - die("unable to write new index file"); - - if (!opts->force && !opts->quiet) - show_local_changes(&new->commit->object); - - return 0; -} - -static void report_tracking(struct branch_info *new) -{ - struct strbuf sb = STRBUF_INIT; - struct branch *branch = branch_get(new->name); - - if (!format_tracking_info(branch, &sb)) - return; - fputs(sb.buf, stdout); - strbuf_release(&sb); -} - -static void detach_advice(const char *old_path, const char *new_name) -{ - const char fmt[] = - "Note: checking out '%s'.\n\n" - "You are in 'detached HEAD' state. You can look around, make experimental\n" - "changes and commit them, and you can discard any commits you make in this\n" - "state without impacting any branches by performing another checkout.\n\n" - "If you want to create a new branch to retain commits you create, you may\n" - "do so (now or later) by using -b with the checkout command again. Example:\n\n" - " git checkout -b new_branch_name\n\n"; - - fprintf(stderr, fmt, new_name); -} - -static void update_refs_for_switch(struct checkout_opts *opts, - struct branch_info *old, - struct branch_info *new) -{ - struct strbuf msg = STRBUF_INIT; - const char *old_desc; - if (opts->new_branch) { - create_branch(old->name, opts->new_branch, new->name, 0, - opts->new_branch_log, opts->track); - new->name = opts->new_branch; - setup_branch_path(new); - } - - old_desc = old->name; - if (!old_desc && old->commit) - old_desc = sha1_to_hex(old->commit->object.sha1); - strbuf_addf(&msg, "checkout: moving from %s to %s", - old_desc ? old_desc : "(invalid)", new->name); - - if (new->path) { - create_symref("HEAD", new->path, msg.buf); - if (!opts->quiet) { - if (old->path && !strcmp(new->path, old->path)) - fprintf(stderr, "Already on '%s'\n", - new->name); - else - fprintf(stderr, "Switched to%s branch '%s'\n", - opts->new_branch ? " a new" : "", - new->name); - } - } else if (strcmp(new->name, "HEAD")) { - update_ref(msg.buf, "HEAD", new->commit->object.sha1, NULL, - REF_NODEREF, DIE_ON_ERR); - if (!opts->quiet) { - if (old->path && advice_detached_head) - detach_advice(old->path, new->name); - describe_detached_head("HEAD is now at", new->commit); - } - } - remove_branch_state(); - strbuf_release(&msg); - if (!opts->quiet && (new->path || !strcmp(new->name, "HEAD"))) - report_tracking(new); -} - -static int switch_branches(struct checkout_opts *opts, struct branch_info *new) -{ - int ret = 0; - struct branch_info old; - unsigned char rev[20]; - int flag; - memset(&old, 0, sizeof(old)); - old.path = resolve_ref("HEAD", rev, 0, &flag); - old.commit = lookup_commit_reference_gently(rev, 1); - if (!(flag & REF_ISSYMREF)) - old.path = NULL; - - if (old.path && !prefixcmp(old.path, "refs/heads/")) - old.name = old.path + strlen("refs/heads/"); - - if (!new->name) { - new->name = "HEAD"; - new->commit = old.commit; - if (!new->commit) - die("You are on a branch yet to be born"); - parse_commit(new->commit); - } - - ret = merge_working_tree(opts, &old, new); - if (ret) - return ret; - - /* - * If we were on a detached HEAD, but have now moved to - * a new commit, we want to mention the old commit once more - * to remind the user that it might be lost. - */ - if (!opts->quiet && !old.path && old.commit && new->commit != old.commit) - describe_detached_head("Previous HEAD position was", old.commit); - - update_refs_for_switch(opts, &old, new); - - ret = post_checkout_hook(old.commit, new->commit, 1); - return ret || opts->writeout_error; -} - -static int git_checkout_config(const char *var, const char *value, void *cb) -{ - return git_xmerge_config(var, value, cb); -} - -static int interactive_checkout(const char *revision, const char **pathspec, - struct checkout_opts *opts) -{ - return run_add_interactive(revision, "--patch=checkout", pathspec); -} - -struct tracking_name_data { - const char *name; - char *remote; - int unique; -}; - -static int check_tracking_name(const char *refname, const unsigned char *sha1, - int flags, void *cb_data) -{ - struct tracking_name_data *cb = cb_data; - const char *slash; - - if (prefixcmp(refname, "refs/remotes/")) - return 0; - slash = strchr(refname + 13, '/'); - if (!slash || strcmp(slash + 1, cb->name)) - return 0; - if (cb->remote) { - cb->unique = 0; - return 0; - } - cb->remote = xstrdup(refname); - return 0; -} - -static const char *unique_tracking_name(const char *name) -{ - struct tracking_name_data cb_data = { name, NULL, 1 }; - for_each_ref(check_tracking_name, &cb_data); - if (cb_data.unique) - return cb_data.remote; - free(cb_data.remote); - return NULL; -} - -int cmd_checkout(int argc, const char **argv, const char *prefix) -{ - struct checkout_opts opts; - unsigned char rev[20]; - const char *arg; - struct branch_info new; - struct tree *source_tree = NULL; - char *conflict_style = NULL; - int patch_mode = 0; - int dwim_new_local_branch = 1; - struct option options[] = { - OPT__QUIET(&opts.quiet), - OPT_STRING('b', NULL, &opts.new_branch, "new branch", "branch"), - OPT_BOOLEAN('l', NULL, &opts.new_branch_log, "log for new branch"), - OPT_SET_INT('t', "track", &opts.track, "track", - BRANCH_TRACK_EXPLICIT), - OPT_SET_INT('2', "ours", &opts.writeout_stage, "stage", - 2), - OPT_SET_INT('3', "theirs", &opts.writeout_stage, "stage", - 3), - OPT_BOOLEAN('f', "force", &opts.force, "force"), - OPT_BOOLEAN('m', "merge", &opts.merge, "merge"), - OPT_STRING(0, "conflict", &conflict_style, "style", - "conflict style (merge or diff3)"), - OPT_BOOLEAN('p', "patch", &patch_mode, "select hunks interactively"), - { OPTION_BOOLEAN, 0, "guess", &dwim_new_local_branch, NULL, - "second guess 'git checkout no-such-branch'", - PARSE_OPT_NOARG | PARSE_OPT_HIDDEN }, - OPT_END(), - }; - int has_dash_dash; - - memset(&opts, 0, sizeof(opts)); - memset(&new, 0, sizeof(new)); - - git_config(git_checkout_config, NULL); - - opts.track = BRANCH_TRACK_UNSPECIFIED; - - argc = parse_options(argc, argv, prefix, options, checkout_usage, - PARSE_OPT_KEEP_DASHDASH); - - if (patch_mode && (opts.track > 0 || opts.new_branch - || opts.new_branch_log || opts.merge || opts.force)) - die ("--patch is incompatible with all other options"); - - /* --track without -b should DWIM */ - if (0 < opts.track && !opts.new_branch) { - const char *argv0 = argv[0]; - if (!argc || !strcmp(argv0, "--")) - die ("--track needs a branch name"); - if (!prefixcmp(argv0, "refs/")) - argv0 += 5; - if (!prefixcmp(argv0, "remotes/")) - argv0 += 8; - argv0 = strchr(argv0, '/'); - if (!argv0 || !argv0[1]) - die ("Missing branch name; try -b"); - opts.new_branch = argv0 + 1; - } - - if (conflict_style) { - opts.merge = 1; /* implied */ - git_xmerge_config("merge.conflictstyle", conflict_style, NULL); - } - - if (opts.force && opts.merge) - die("git checkout: -f and -m are incompatible"); - - /* - * case 1: git checkout -- [] - * - * must be a valid tree, everything after the '--' must be - * a path. - * - * case 2: git checkout -- [] - * - * everything after the '--' must be paths. - * - * case 3: git checkout [] - * - * With no paths, if is a commit, that is to - * switch to the branch or detach HEAD at it. As a special case, - * if is A...B (missing A or B means HEAD but you can - * omit at most one side), and if there is a unique merge base - * between A and B, A...B names that merge base. - * - * With no paths, if is _not_ a commit, no -t nor -b - * was given, and there is a tracking branch whose name is - * in one and only one remote, then this is a short-hand - * to fork local from that remote tracking branch. - * - * Otherwise shall not be ambiguous. - * - If it's *only* a reference, treat it like case (1). - * - If it's only a path, treat it like case (2). - * - else: fail. - * - */ - if (argc) { - if (!strcmp(argv[0], "--")) { /* case (2) */ - argv++; - argc--; - goto no_reference; - } - - arg = argv[0]; - has_dash_dash = (argc > 1) && !strcmp(argv[1], "--"); - - if (!strcmp(arg, "-")) - arg = "@{-1}"; - - if (get_sha1_mb(arg, rev)) { - if (has_dash_dash) /* case (1) */ - die("invalid reference: %s", arg); - if (!patch_mode && - dwim_new_local_branch && - opts.track == BRANCH_TRACK_UNSPECIFIED && - !opts.new_branch && - !check_filename(NULL, arg) && - argc == 1) { - const char *remote = unique_tracking_name(arg); - if (!remote || get_sha1(remote, rev)) - goto no_reference; - opts.new_branch = arg; - arg = remote; - /* DWIMmed to create local branch */ - } - else - goto no_reference; - } - - /* we can't end up being in (2) anymore, eat the argument */ - argv++; - argc--; - - new.name = arg; - if ((new.commit = lookup_commit_reference_gently(rev, 1))) { - setup_branch_path(&new); - - if ((check_ref_format(new.path) == CHECK_REF_FORMAT_OK) && - resolve_ref(new.path, rev, 1, NULL)) - ; - else - new.path = NULL; - parse_commit(new.commit); - source_tree = new.commit->tree; - } else - source_tree = parse_tree_indirect(rev); - - if (!source_tree) /* case (1): want a tree */ - die("reference is not a tree: %s", arg); - if (!has_dash_dash) {/* case (3 -> 1) */ - /* - * Do not complain the most common case - * git checkout branch - * even if there happen to be a file called 'branch'; - * it would be extremely annoying. - */ - if (argc) - verify_non_filename(NULL, arg); - } - else { - argv++; - argc--; - } - } - -no_reference: - - if (opts.track == BRANCH_TRACK_UNSPECIFIED) - opts.track = git_branch_track; - - if (argc) { - const char **pathspec = get_pathspec(prefix, argv); - - if (!pathspec) - die("invalid path specification"); - - if (patch_mode) - return interactive_checkout(new.name, pathspec, &opts); - - /* Checkout paths */ - if (opts.new_branch) { - if (argc == 1) { - die("git checkout: updating paths is incompatible with switching branches.\nDid you intend to checkout '%s' which can not be resolved as commit?", argv[0]); - } else { - die("git checkout: updating paths is incompatible with switching branches."); - } - } - - if (1 < !!opts.writeout_stage + !!opts.force + !!opts.merge) - die("git checkout: --ours/--theirs, --force and --merge are incompatible when\nchecking out of the index."); - - return checkout_paths(source_tree, pathspec, &opts); - } - - if (patch_mode) - return interactive_checkout(new.name, NULL, &opts); - - if (opts.new_branch) { - struct strbuf buf = STRBUF_INIT; - if (strbuf_check_branch_ref(&buf, opts.new_branch)) - die("git checkout: we do not like '%s' as a branch name.", - opts.new_branch); - if (!get_sha1(buf.buf, rev)) - die("git checkout: branch %s already exists", opts.new_branch); - strbuf_release(&buf); - } - - if (new.name && !new.commit) { - die("Cannot switch branch to a non-commit."); - } - if (opts.writeout_stage) - die("--ours/--theirs is incompatible with switching branches."); - - return switch_branches(&opts, &new); -} diff --git a/builtin-clean.c b/builtin-clean.c deleted file mode 100644 index fac64e6..0000000 --- a/builtin-clean.c +++ /dev/null @@ -1,171 +0,0 @@ -/* - * "git clean" builtin command - * - * Copyright (C) 2007 Shawn Bohrer - * - * Based on git-clean.sh by Pavel Roskin - */ - -#include "builtin.h" -#include "cache.h" -#include "dir.h" -#include "parse-options.h" -#include "quote.h" - -static int force = -1; /* unset */ - -static const char *const builtin_clean_usage[] = { - "git clean [-d] [-f] [-n] [-q] [-x | -X] [--] ...", - NULL -}; - -static int git_clean_config(const char *var, const char *value, void *cb) -{ - if (!strcmp(var, "clean.requireforce")) - force = !git_config_bool(var, value); - return git_default_config(var, value, cb); -} - -int cmd_clean(int argc, const char **argv, const char *prefix) -{ - int i; - int show_only = 0, remove_directories = 0, quiet = 0, ignored = 0; - int ignored_only = 0, baselen = 0, config_set = 0, errors = 0; - int rm_flags = REMOVE_DIR_KEEP_NESTED_GIT; - struct strbuf directory = STRBUF_INIT; - struct dir_struct dir; - static const char **pathspec; - struct strbuf buf = STRBUF_INIT; - const char *qname; - char *seen = NULL; - struct option options[] = { - OPT__QUIET(&quiet), - OPT__DRY_RUN(&show_only), - OPT_BOOLEAN('f', "force", &force, "force"), - OPT_BOOLEAN('d', NULL, &remove_directories, - "remove whole directories"), - OPT_BOOLEAN('x', NULL, &ignored, "remove ignored files, too"), - OPT_BOOLEAN('X', NULL, &ignored_only, - "remove only ignored files"), - OPT_END() - }; - - git_config(git_clean_config, NULL); - if (force < 0) - force = 0; - else - config_set = 1; - - argc = parse_options(argc, argv, prefix, options, builtin_clean_usage, - 0); - - memset(&dir, 0, sizeof(dir)); - if (ignored_only) - dir.flags |= DIR_SHOW_IGNORED; - - if (ignored && ignored_only) - die("-x and -X cannot be used together"); - - if (!show_only && !force) - die("clean.requireForce %s to true and neither -n nor -f given; " - "refusing to clean", config_set ? "set" : "defaults"); - - if (force > 1) - rm_flags = 0; - - dir.flags |= DIR_SHOW_OTHER_DIRECTORIES; - - if (read_cache() < 0) - die("index file corrupt"); - - if (!ignored) - setup_standard_excludes(&dir); - - pathspec = get_pathspec(prefix, argv); - - fill_directory(&dir, pathspec); - - if (pathspec) - seen = xmalloc(argc > 0 ? argc : 1); - - for (i = 0; i < dir.nr; i++) { - struct dir_entry *ent = dir.entries[i]; - int len, pos; - int matches = 0; - struct cache_entry *ce; - struct stat st; - - /* - * Remove the '/' at the end that directory - * walking adds for directory entries. - */ - len = ent->len; - if (len && ent->name[len-1] == '/') - len--; - pos = cache_name_pos(ent->name, len); - if (0 <= pos) - continue; /* exact match */ - pos = -pos - 1; - if (pos < active_nr) { - ce = active_cache[pos]; - if (ce_namelen(ce) == len && - !memcmp(ce->name, ent->name, len)) - continue; /* Yup, this one exists unmerged */ - } - - /* - * we might have removed this as part of earlier - * recursive directory removal, so lstat() here could - * fail with ENOENT. - */ - if (lstat(ent->name, &st)) - continue; - - if (pathspec) { - memset(seen, 0, argc > 0 ? argc : 1); - matches = match_pathspec(pathspec, ent->name, len, - baselen, seen); - } - - if (S_ISDIR(st.st_mode)) { - strbuf_addstr(&directory, ent->name); - qname = quote_path_relative(directory.buf, directory.len, &buf, prefix); - if (show_only && (remove_directories || - (matches == MATCHED_EXACTLY))) { - printf("Would remove %s\n", qname); - } else if (remove_directories || - (matches == MATCHED_EXACTLY)) { - if (!quiet) - printf("Removing %s\n", qname); - if (remove_dir_recursively(&directory, - rm_flags) != 0) { - warning("failed to remove '%s'", qname); - errors++; - } - } else if (show_only) { - printf("Would not remove %s\n", qname); - } else { - printf("Not removing %s\n", qname); - } - strbuf_reset(&directory); - } else { - if (pathspec && !matches) - continue; - qname = quote_path_relative(ent->name, -1, &buf, prefix); - if (show_only) { - printf("Would remove %s\n", qname); - continue; - } else if (!quiet) { - printf("Removing %s\n", qname); - } - if (unlink(ent->name) != 0) { - warning("failed to remove '%s'", qname); - errors++; - } - } - } - free(seen); - - strbuf_release(&directory); - return (errors != 0); -} diff --git a/builtin-clone.c b/builtin-clone.c deleted file mode 100644 index 58bacbd..0000000 --- a/builtin-clone.c +++ /dev/null @@ -1,671 +0,0 @@ -/* - * Builtin "git clone" - * - * Copyright (c) 2007 Kristian Høgsberg , - * 2008 Daniel Barkalow - * Based on git-commit.sh by Junio C Hamano and Linus Torvalds - * - * Clone a repository into a different directory that does not yet exist. - */ - -#include "cache.h" -#include "parse-options.h" -#include "fetch-pack.h" -#include "refs.h" -#include "tree.h" -#include "tree-walk.h" -#include "unpack-trees.h" -#include "transport.h" -#include "strbuf.h" -#include "dir.h" -#include "pack-refs.h" -#include "sigchain.h" -#include "branch.h" -#include "remote.h" -#include "run-command.h" - -/* - * Overall FIXMEs: - * - respect DB_ENVIRONMENT for .git/objects. - * - * Implementation notes: - * - dropping use-separate-remote and no-separate-remote compatibility - * - */ -static const char * const builtin_clone_usage[] = { - "git clone [options] [--] []", - NULL -}; - -static int option_quiet, option_no_checkout, option_bare, option_mirror; -static int option_local, option_no_hardlinks, option_shared, option_recursive; -static char *option_template, *option_reference, *option_depth; -static char *option_origin = NULL; -static char *option_branch = NULL; -static char *option_upload_pack = "git-upload-pack"; -static int option_verbose; -static int option_progress; - -static struct option builtin_clone_options[] = { - OPT__QUIET(&option_quiet), - OPT__VERBOSE(&option_verbose), - OPT_BOOLEAN(0, "progress", &option_progress, - "force progress reporting"), - OPT_BOOLEAN('n', "no-checkout", &option_no_checkout, - "don't create a checkout"), - OPT_BOOLEAN(0, "bare", &option_bare, "create a bare repository"), - { OPTION_BOOLEAN, 0, "naked", &option_bare, NULL, - "create a bare repository", - PARSE_OPT_NOARG | PARSE_OPT_HIDDEN }, - OPT_BOOLEAN(0, "mirror", &option_mirror, - "create a mirror repository (implies bare)"), - OPT_BOOLEAN('l', "local", &option_local, - "to clone from a local repository"), - OPT_BOOLEAN(0, "no-hardlinks", &option_no_hardlinks, - "don't use local hardlinks, always copy"), - OPT_BOOLEAN('s', "shared", &option_shared, - "setup as shared repository"), - OPT_BOOLEAN(0, "recursive", &option_recursive, - "initialize submodules in the clone"), - OPT_STRING(0, "template", &option_template, "path", - "path the template repository"), - OPT_STRING(0, "reference", &option_reference, "repo", - "reference repository"), - OPT_STRING('o', "origin", &option_origin, "branch", - "use instead of 'origin' to track upstream"), - OPT_STRING('b', "branch", &option_branch, "branch", - "checkout instead of the remote's HEAD"), - OPT_STRING('u', "upload-pack", &option_upload_pack, "path", - "path to git-upload-pack on the remote"), - OPT_STRING(0, "depth", &option_depth, "depth", - "create a shallow clone of that depth"), - - OPT_END() -}; - -static const char *argv_submodule[] = { - "submodule", "update", "--init", "--recursive", NULL -}; - -static char *get_repo_path(const char *repo, int *is_bundle) -{ - static char *suffix[] = { "/.git", ".git", "" }; - static char *bundle_suffix[] = { ".bundle", "" }; - struct stat st; - int i; - - for (i = 0; i < ARRAY_SIZE(suffix); i++) { - const char *path; - path = mkpath("%s%s", repo, suffix[i]); - if (is_directory(path)) { - *is_bundle = 0; - return xstrdup(make_nonrelative_path(path)); - } - } - - for (i = 0; i < ARRAY_SIZE(bundle_suffix); i++) { - const char *path; - path = mkpath("%s%s", repo, bundle_suffix[i]); - if (!stat(path, &st) && S_ISREG(st.st_mode)) { - *is_bundle = 1; - return xstrdup(make_nonrelative_path(path)); - } - } - - return NULL; -} - -static char *guess_dir_name(const char *repo, int is_bundle, int is_bare) -{ - const char *end = repo + strlen(repo), *start; - char *dir; - - /* - * Strip trailing spaces, slashes and /.git - */ - while (repo < end && (is_dir_sep(end[-1]) || isspace(end[-1]))) - end--; - if (end - repo > 5 && is_dir_sep(end[-5]) && - !strncmp(end - 4, ".git", 4)) { - end -= 5; - while (repo < end && is_dir_sep(end[-1])) - end--; - } - - /* - * Find last component, but be prepared that repo could have - * the form "remote.example.com:foo.git", i.e. no slash - * in the directory part. - */ - start = end; - while (repo < start && !is_dir_sep(start[-1]) && start[-1] != ':') - start--; - - /* - * Strip .{bundle,git}. - */ - if (is_bundle) { - if (end - start > 7 && !strncmp(end - 7, ".bundle", 7)) - end -= 7; - } else { - if (end - start > 4 && !strncmp(end - 4, ".git", 4)) - end -= 4; - } - - if (is_bare) { - struct strbuf result = STRBUF_INIT; - strbuf_addf(&result, "%.*s.git", (int)(end - start), start); - dir = strbuf_detach(&result, NULL); - } else - dir = xstrndup(start, end - start); - /* - * Replace sequences of 'control' characters and whitespace - * with one ascii space, remove leading and trailing spaces. - */ - if (*dir) { - char *out = dir; - int prev_space = 1 /* strip leading whitespace */; - for (end = dir; *end; ++end) { - char ch = *end; - if ((unsigned char)ch < '\x20') - ch = '\x20'; - if (isspace(ch)) { - if (prev_space) - continue; - prev_space = 1; - } else - prev_space = 0; - *out++ = ch; - } - *out = '\0'; - if (out > dir && prev_space) - out[-1] = '\0'; - } - return dir; -} - -static void strip_trailing_slashes(char *dir) -{ - char *end = dir + strlen(dir); - - while (dir < end - 1 && is_dir_sep(end[-1])) - end--; - *end = '\0'; -} - -static void setup_reference(const char *repo) -{ - const char *ref_git; - char *ref_git_copy; - - struct remote *remote; - struct transport *transport; - const struct ref *extra; - - ref_git = make_absolute_path(option_reference); - - if (is_directory(mkpath("%s/.git/objects", ref_git))) - ref_git = mkpath("%s/.git", ref_git); - else if (!is_directory(mkpath("%s/objects", ref_git))) - die("reference repository '%s' is not a local directory.", - option_reference); - - ref_git_copy = xstrdup(ref_git); - - add_to_alternates_file(ref_git_copy); - - remote = remote_get(ref_git_copy); - transport = transport_get(remote, ref_git_copy); - for (extra = transport_get_remote_refs(transport); extra; - extra = extra->next) - add_extra_ref(extra->name, extra->old_sha1, 0); - - transport_disconnect(transport); - - free(ref_git_copy); -} - -static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest) -{ - struct dirent *de; - struct stat buf; - int src_len, dest_len; - DIR *dir; - - dir = opendir(src->buf); - if (!dir) - die_errno("failed to open '%s'", src->buf); - - if (mkdir(dest->buf, 0777)) { - if (errno != EEXIST) - die_errno("failed to create directory '%s'", dest->buf); - else if (stat(dest->buf, &buf)) - die_errno("failed to stat '%s'", dest->buf); - else if (!S_ISDIR(buf.st_mode)) - die("%s exists and is not a directory", dest->buf); - } - - strbuf_addch(src, '/'); - src_len = src->len; - strbuf_addch(dest, '/'); - dest_len = dest->len; - - while ((de = readdir(dir)) != NULL) { - strbuf_setlen(src, src_len); - strbuf_addstr(src, de->d_name); - strbuf_setlen(dest, dest_len); - strbuf_addstr(dest, de->d_name); - if (stat(src->buf, &buf)) { - warning ("failed to stat %s\n", src->buf); - continue; - } - if (S_ISDIR(buf.st_mode)) { - if (de->d_name[0] != '.') - copy_or_link_directory(src, dest); - continue; - } - - if (unlink(dest->buf) && errno != ENOENT) - die_errno("failed to unlink '%s'", dest->buf); - if (!option_no_hardlinks) { - if (!link(src->buf, dest->buf)) - continue; - if (option_local) - die_errno("failed to create link '%s'", dest->buf); - option_no_hardlinks = 1; - } - if (copy_file_with_time(dest->buf, src->buf, 0666)) - die_errno("failed to copy file to '%s'", dest->buf); - } - closedir(dir); -} - -static const struct ref *clone_local(const char *src_repo, - const char *dest_repo) -{ - const struct ref *ret; - struct strbuf src = STRBUF_INIT; - struct strbuf dest = STRBUF_INIT; - struct remote *remote; - struct transport *transport; - - if (option_shared) - add_to_alternates_file(src_repo); - else { - strbuf_addf(&src, "%s/objects", src_repo); - strbuf_addf(&dest, "%s/objects", dest_repo); - copy_or_link_directory(&src, &dest); - strbuf_release(&src); - strbuf_release(&dest); - } - - remote = remote_get(src_repo); - transport = transport_get(remote, src_repo); - ret = transport_get_remote_refs(transport); - transport_disconnect(transport); - return ret; -} - -static const char *junk_work_tree; -static const char *junk_git_dir; -static pid_t junk_pid; - -static void remove_junk(void) -{ - struct strbuf sb = STRBUF_INIT; - if (getpid() != junk_pid) - return; - if (junk_git_dir) { - strbuf_addstr(&sb, junk_git_dir); - remove_dir_recursively(&sb, 0); - strbuf_reset(&sb); - } - if (junk_work_tree) { - strbuf_addstr(&sb, junk_work_tree); - remove_dir_recursively(&sb, 0); - strbuf_reset(&sb); - } -} - -static void remove_junk_on_signal(int signo) -{ - remove_junk(); - sigchain_pop(signo); - raise(signo); -} - -static struct ref *wanted_peer_refs(const struct ref *refs, - struct refspec *refspec) -{ - struct ref *local_refs = NULL; - struct ref **tail = &local_refs; - - get_fetch_map(refs, refspec, &tail, 0); - if (!option_mirror) - get_fetch_map(refs, tag_refspec, &tail, 0); - - return local_refs; -} - -static void write_remote_refs(const struct ref *local_refs) -{ - const struct ref *r; - - for (r = local_refs; r; r = r->next) - add_extra_ref(r->peer_ref->name, r->old_sha1, 0); - - pack_refs(PACK_REFS_ALL); - clear_extra_refs(); -} - -int cmd_clone(int argc, const char **argv, const char *prefix) -{ - int is_bundle = 0; - struct stat buf; - const char *repo_name, *repo, *work_tree, *git_dir; - char *path, *dir; - int dest_exists; - const struct ref *refs, *remote_head; - const struct ref *remote_head_points_at; - const struct ref *our_head_points_at; - struct ref *mapped_refs; - struct strbuf key = STRBUF_INIT, value = STRBUF_INIT; - struct strbuf branch_top = STRBUF_INIT, reflog_msg = STRBUF_INIT; - struct transport *transport = NULL; - char *src_ref_prefix = "refs/heads/"; - int err = 0; - - struct refspec *refspec; - const char *fetch_pattern; - - junk_pid = getpid(); - - argc = parse_options(argc, argv, prefix, builtin_clone_options, - builtin_clone_usage, 0); - - if (argc > 2) - usage_msg_opt("Too many arguments.", - builtin_clone_usage, builtin_clone_options); - - if (argc == 0) - usage_msg_opt("You must specify a repository to clone.", - builtin_clone_usage, builtin_clone_options); - - if (option_mirror) - option_bare = 1; - - if (option_bare) { - if (option_origin) - die("--bare and --origin %s options are incompatible.", - option_origin); - option_no_checkout = 1; - } - - if (!option_origin) - option_origin = "origin"; - - repo_name = argv[0]; - - path = get_repo_path(repo_name, &is_bundle); - if (path) - repo = xstrdup(make_nonrelative_path(repo_name)); - else if (!strchr(repo_name, ':')) - repo = xstrdup(make_absolute_path(repo_name)); - else - repo = repo_name; - - if (argc == 2) - dir = xstrdup(argv[1]); - else - dir = guess_dir_name(repo_name, is_bundle, option_bare); - strip_trailing_slashes(dir); - - dest_exists = !stat(dir, &buf); - if (dest_exists && !is_empty_dir(dir)) - die("destination path '%s' already exists and is not " - "an empty directory.", dir); - - strbuf_addf(&reflog_msg, "clone: from %s", repo); - - if (option_bare) - work_tree = NULL; - else { - work_tree = getenv("GIT_WORK_TREE"); - if (work_tree && !stat(work_tree, &buf)) - die("working tree '%s' already exists.", work_tree); - } - - if (option_bare || work_tree) - git_dir = xstrdup(dir); - else { - work_tree = dir; - git_dir = xstrdup(mkpath("%s/.git", dir)); - } - - if (!option_bare) { - junk_work_tree = work_tree; - if (safe_create_leading_directories_const(work_tree) < 0) - die_errno("could not create leading directories of '%s'", - work_tree); - if (!dest_exists && mkdir(work_tree, 0755)) - die_errno("could not create work tree dir '%s'.", - work_tree); - set_git_work_tree(work_tree); - } - junk_git_dir = git_dir; - atexit(remove_junk); - sigchain_push_common(remove_junk_on_signal); - - setenv(CONFIG_ENVIRONMENT, mkpath("%s/config", git_dir), 1); - - if (safe_create_leading_directories_const(git_dir) < 0) - die("could not create leading directories of '%s'", git_dir); - set_git_dir(make_absolute_path(git_dir)); - - init_db(option_template, option_quiet ? INIT_DB_QUIET : 0); - - /* - * At this point, the config exists, so we do not need the - * environment variable. We actually need to unset it, too, to - * re-enable parsing of the global configs. - */ - unsetenv(CONFIG_ENVIRONMENT); - - if (option_reference) - setup_reference(git_dir); - - git_config(git_default_config, NULL); - - if (option_bare) { - if (option_mirror) - src_ref_prefix = "refs/"; - strbuf_addstr(&branch_top, src_ref_prefix); - - git_config_set("core.bare", "true"); - } else { - strbuf_addf(&branch_top, "refs/remotes/%s/", option_origin); - } - - strbuf_addf(&value, "+%s*:%s*", src_ref_prefix, branch_top.buf); - - if (option_mirror || !option_bare) { - /* Configure the remote */ - strbuf_addf(&key, "remote.%s.fetch", option_origin); - git_config_set_multivar(key.buf, value.buf, "^$", 0); - strbuf_reset(&key); - - if (option_mirror) { - strbuf_addf(&key, "remote.%s.mirror", option_origin); - git_config_set(key.buf, "true"); - strbuf_reset(&key); - } - - strbuf_addf(&key, "remote.%s.url", option_origin); - git_config_set(key.buf, repo); - strbuf_reset(&key); - } - - fetch_pattern = value.buf; - refspec = parse_fetch_refspec(1, &fetch_pattern); - - strbuf_reset(&value); - - if (path && !is_bundle) { - refs = clone_local(path, git_dir); - mapped_refs = wanted_peer_refs(refs, refspec); - } else { - struct remote *remote = remote_get(argv[0]); - transport = transport_get(remote, remote->url[0]); - - if (!transport->get_refs_list || !transport->fetch) - die("Don't know how to clone %s", transport->url); - - transport_set_option(transport, TRANS_OPT_KEEP, "yes"); - - if (option_depth) - transport_set_option(transport, TRANS_OPT_DEPTH, - option_depth); - - if (option_quiet) - transport->verbose = -1; - else if (option_verbose) - transport->verbose = 1; - - if (option_progress) - transport->progress = 1; - - if (option_upload_pack) - transport_set_option(transport, TRANS_OPT_UPLOADPACK, - option_upload_pack); - - refs = transport_get_remote_refs(transport); - if (refs) { - mapped_refs = wanted_peer_refs(refs, refspec); - transport_fetch_refs(transport, mapped_refs); - } - } - - if (refs) { - clear_extra_refs(); - - write_remote_refs(mapped_refs); - - remote_head = find_ref_by_name(refs, "HEAD"); - remote_head_points_at = - guess_remote_head(remote_head, mapped_refs, 0); - - if (option_branch) { - struct strbuf head = STRBUF_INIT; - strbuf_addstr(&head, src_ref_prefix); - strbuf_addstr(&head, option_branch); - our_head_points_at = - find_ref_by_name(mapped_refs, head.buf); - strbuf_release(&head); - - if (!our_head_points_at) { - warning("Remote branch %s not found in " - "upstream %s, using HEAD instead", - option_branch, option_origin); - our_head_points_at = remote_head_points_at; - } - } - else - our_head_points_at = remote_head_points_at; - } - else { - warning("You appear to have cloned an empty repository."); - our_head_points_at = NULL; - remote_head_points_at = NULL; - remote_head = NULL; - option_no_checkout = 1; - if (!option_bare) - install_branch_config(0, "master", option_origin, - "refs/heads/master"); - } - - if (remote_head_points_at && !option_bare) { - struct strbuf head_ref = STRBUF_INIT; - strbuf_addstr(&head_ref, branch_top.buf); - strbuf_addstr(&head_ref, "HEAD"); - create_symref(head_ref.buf, - remote_head_points_at->peer_ref->name, - reflog_msg.buf); - } - - if (our_head_points_at) { - /* Local default branch link */ - create_symref("HEAD", our_head_points_at->name, NULL); - if (!option_bare) { - const char *head = skip_prefix(our_head_points_at->name, - "refs/heads/"); - update_ref(reflog_msg.buf, "HEAD", - our_head_points_at->old_sha1, - NULL, 0, DIE_ON_ERR); - install_branch_config(0, head, option_origin, - our_head_points_at->name); - } - } else if (remote_head) { - /* Source had detached HEAD pointing somewhere. */ - if (!option_bare) { - update_ref(reflog_msg.buf, "HEAD", - remote_head->old_sha1, - NULL, REF_NODEREF, DIE_ON_ERR); - our_head_points_at = remote_head; - } - } else { - /* Nothing to checkout out */ - if (!option_no_checkout) - warning("remote HEAD refers to nonexistent ref, " - "unable to checkout.\n"); - option_no_checkout = 1; - } - - if (transport) { - transport_unlock_pack(transport); - transport_disconnect(transport); - } - - if (!option_no_checkout) { - struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file)); - struct unpack_trees_options opts; - struct tree *tree; - struct tree_desc t; - int fd; - - /* We need to be in the new work tree for the checkout */ - setup_work_tree(); - - fd = hold_locked_index(lock_file, 1); - - memset(&opts, 0, sizeof opts); - opts.update = 1; - opts.merge = 1; - opts.fn = oneway_merge; - opts.verbose_update = !option_quiet; - opts.src_index = &the_index; - opts.dst_index = &the_index; - - tree = parse_tree_indirect(our_head_points_at->old_sha1); - parse_tree(tree); - init_tree_desc(&t, tree->buffer, tree->size); - unpack_trees(1, &t, &opts); - - if (write_cache(fd, active_cache, active_nr) || - commit_locked_index(lock_file)) - die("unable to write new index file"); - - err |= run_hook(NULL, "post-checkout", sha1_to_hex(null_sha1), - sha1_to_hex(our_head_points_at->old_sha1), "1", - NULL); - - if (!err && option_recursive) - err = run_command_v_opt(argv_submodule, RUN_GIT_CMD); - } - - strbuf_release(&reflog_msg); - strbuf_release(&branch_top); - strbuf_release(&key); - strbuf_release(&value); - junk_pid = 0; - return err; -} diff --git a/builtin-commit-tree.c b/builtin-commit-tree.c deleted file mode 100644 index 90dac34..0000000 --- a/builtin-commit-tree.c +++ /dev/null @@ -1,133 +0,0 @@ -/* - * GIT - The information manager from hell - * - * Copyright (C) Linus Torvalds, 2005 - */ -#include "cache.h" -#include "commit.h" -#include "tree.h" -#include "builtin.h" -#include "utf8.h" - -/* - * FIXME! Share the code with "write-tree.c" - */ -static void check_valid(unsigned char *sha1, enum object_type expect) -{ - enum object_type type = sha1_object_info(sha1, NULL); - if (type < 0) - die("%s is not a valid object", sha1_to_hex(sha1)); - if (type != expect) - die("%s is not a valid '%s' object", sha1_to_hex(sha1), - typename(expect)); -} - -static const char commit_tree_usage[] = "git commit-tree [-p ]* < changelog"; - -static void new_parent(struct commit *parent, struct commit_list **parents_p) -{ - unsigned char *sha1 = parent->object.sha1; - struct commit_list *parents; - for (parents = *parents_p; parents; parents = parents->next) { - if (parents->item == parent) { - error("duplicate parent %s ignored", sha1_to_hex(sha1)); - return; - } - parents_p = &parents->next; - } - commit_list_insert(parent, parents_p); -} - -static const char commit_utf8_warn[] = -"Warning: commit message does not conform to UTF-8.\n" -"You may want to amend it after fixing the message, or set the config\n" -"variable i18n.commitencoding to the encoding your project uses.\n"; - -int commit_tree(const char *msg, unsigned char *tree, - struct commit_list *parents, unsigned char *ret, - const char *author) -{ - int result; - int encoding_is_utf8; - struct strbuf buffer; - - check_valid(tree, OBJ_TREE); - - /* Not having i18n.commitencoding is the same as having utf-8 */ - encoding_is_utf8 = is_encoding_utf8(git_commit_encoding); - - strbuf_init(&buffer, 8192); /* should avoid reallocs for the headers */ - strbuf_addf(&buffer, "tree %s\n", sha1_to_hex(tree)); - - /* - * 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. - */ - while (parents) { - struct commit_list *next = parents->next; - strbuf_addf(&buffer, "parent %s\n", - sha1_to_hex(parents->item->object.sha1)); - free(parents); - parents = next; - } - - /* Person/date information */ - if (!author) - author = git_author_info(IDENT_ERROR_ON_NO_NAME); - strbuf_addf(&buffer, "author %s\n", author); - strbuf_addf(&buffer, "committer %s\n", git_committer_info(IDENT_ERROR_ON_NO_NAME)); - if (!encoding_is_utf8) - strbuf_addf(&buffer, "encoding %s\n", git_commit_encoding); - strbuf_addch(&buffer, '\n'); - - /* And add the comment */ - strbuf_addstr(&buffer, msg); - - /* And check the encoding */ - if (encoding_is_utf8 && !is_utf8(buffer.buf)) - fprintf(stderr, commit_utf8_warn); - - result = write_sha1_file(buffer.buf, buffer.len, commit_type, ret); - strbuf_release(&buffer); - return result; -} - -int cmd_commit_tree(int argc, const char **argv, const char *prefix) -{ - int i; - struct commit_list *parents = NULL; - unsigned char tree_sha1[20]; - unsigned char commit_sha1[20]; - struct strbuf buffer = STRBUF_INIT; - - git_config(git_default_config, NULL); - - if (argc < 2 || !strcmp(argv[1], "-h")) - usage(commit_tree_usage); - if (get_sha1(argv[1], tree_sha1)) - die("Not a valid object name %s", argv[1]); - - for (i = 2; i < argc; i += 2) { - unsigned char sha1[20]; - const char *a, *b; - a = argv[i]; b = argv[i+1]; - if (!b || strcmp(a, "-p")) - usage(commit_tree_usage); - - if (get_sha1(b, sha1)) - die("Not a valid object name %s", b); - check_valid(sha1, OBJ_COMMIT); - new_parent(lookup_commit(sha1), &parents); - } - - if (strbuf_read(&buffer, 0, 0) < 0) - die_errno("git commit-tree: failed to read"); - - if (!commit_tree(buffer.buf, tree_sha1, parents, commit_sha1, NULL)) { - printf("%s\n", sha1_to_hex(commit_sha1)); - return 0; - } - else - return 1; -} diff --git a/builtin-commit.c b/builtin-commit.c deleted file mode 100644 index 55676fd..0000000 --- a/builtin-commit.c +++ /dev/null @@ -1,1310 +0,0 @@ -/* - * Builtin "git commit" - * - * Copyright (c) 2007 Kristian Høgsberg - * Based on git-commit.sh by Junio C Hamano and Linus Torvalds - */ - -#include "cache.h" -#include "cache-tree.h" -#include "color.h" -#include "dir.h" -#include "builtin.h" -#include "diff.h" -#include "diffcore.h" -#include "commit.h" -#include "revision.h" -#include "wt-status.h" -#include "run-command.h" -#include "refs.h" -#include "log-tree.h" -#include "strbuf.h" -#include "utf8.h" -#include "parse-options.h" -#include "string-list.h" -#include "rerere.h" -#include "unpack-trees.h" -#include "quote.h" - -static const char * const builtin_commit_usage[] = { - "git commit [options] [--] ...", - NULL -}; - -static const char * const builtin_status_usage[] = { - "git status [options] [--] ...", - NULL -}; - -static const char implicit_ident_advice[] = -"Your name and email address were configured automatically based\n" -"on your username and hostname. Please check that they are accurate.\n" -"You can suppress this message by setting them explicitly:\n" -"\n" -" git config --global user.name Your Name\n" -" git config --global user.email you@example.com\n" -"\n" -"If the identity used for this commit is wrong, you can fix it with:\n" -"\n" -" git commit --amend --author='Your Name '\n"; - -static unsigned char head_sha1[20]; - -static char *use_message_buffer; -static const char commit_editmsg[] = "COMMIT_EDITMSG"; -static struct lock_file index_lock; /* real index */ -static struct lock_file false_lock; /* used only for partial commits */ -static enum { - COMMIT_AS_IS = 1, - COMMIT_NORMAL, - COMMIT_PARTIAL, -} commit_style; - -static const char *logfile, *force_author; -static const char *template_file; -static char *edit_message, *use_message; -static char *author_name, *author_email, *author_date; -static int all, edit_flag, also, interactive, only, amend, signoff; -static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship; -static char *untracked_files_arg, *force_date; -/* - * The default commit message cleanup mode will remove the lines - * beginning with # (shell comments) and leading and trailing - * whitespaces (empty lines or containing only whitespaces) - * if editor is used, and only the whitespaces if the message - * is specified explicitly. - */ -static enum { - CLEANUP_SPACE, - CLEANUP_NONE, - CLEANUP_ALL, -} cleanup_mode; -static char *cleanup_arg; - -static int use_editor = 1, initial_commit, in_merge, include_status = 1; -static const char *only_include_assumed; -static struct strbuf message; - -static int null_termination; -static enum { - STATUS_FORMAT_LONG, - STATUS_FORMAT_SHORT, - STATUS_FORMAT_PORCELAIN, -} status_format = STATUS_FORMAT_LONG; - -static int opt_parse_m(const struct option *opt, const char *arg, int unset) -{ - struct strbuf *buf = opt->value; - if (unset) - strbuf_setlen(buf, 0); - else { - strbuf_addstr(buf, arg); - strbuf_addstr(buf, "\n\n"); - } - return 0; -} - -static struct option builtin_commit_options[] = { - OPT__QUIET(&quiet), - OPT__VERBOSE(&verbose), - - OPT_GROUP("Commit message options"), - OPT_FILENAME('F', "file", &logfile, "read log from file"), - OPT_STRING(0, "author", &force_author, "AUTHOR", "override author for commit"), - OPT_STRING(0, "date", &force_date, "DATE", "override date for commit"), - OPT_CALLBACK('m', "message", &message, "MESSAGE", "specify commit message", opt_parse_m), - OPT_STRING('c', "reedit-message", &edit_message, "COMMIT", "reuse and edit message from specified commit"), - OPT_STRING('C', "reuse-message", &use_message, "COMMIT", "reuse message from specified commit"), - OPT_BOOLEAN(0, "reset-author", &renew_authorship, "the commit is authored by me now (used with -C-c/--amend)"), - OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"), - OPT_FILENAME('t', "template", &template_file, "use specified template file"), - OPT_BOOLEAN('e', "edit", &edit_flag, "force edit of commit"), - OPT_STRING(0, "cleanup", &cleanup_arg, "default", "how to strip spaces and #comments from message"), - OPT_BOOLEAN(0, "status", &include_status, "include status in commit message template"), - /* end commit message options */ - - OPT_GROUP("Commit contents options"), - OPT_BOOLEAN('a', "all", &all, "commit all changed files"), - OPT_BOOLEAN('i', "include", &also, "add specified files to index for commit"), - OPT_BOOLEAN(0, "interactive", &interactive, "interactively add files"), - OPT_BOOLEAN('o', "only", &only, "commit only specified files"), - OPT_BOOLEAN('n', "no-verify", &no_verify, "bypass pre-commit hook"), - OPT_BOOLEAN(0, "dry-run", &dry_run, "show what would be committed"), - OPT_SET_INT(0, "short", &status_format, "show status concisely", - STATUS_FORMAT_SHORT), - OPT_SET_INT(0, "porcelain", &status_format, - "show porcelain output format", STATUS_FORMAT_PORCELAIN), - OPT_BOOLEAN('z', "null", &null_termination, - "terminate entries with NUL"), - OPT_BOOLEAN(0, "amend", &amend, "amend previous commit"), - { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, "mode", "show untracked files, optional modes: all, normal, no. (Default: all)", PARSE_OPT_OPTARG, NULL, (intptr_t)"all" }, - OPT_BOOLEAN(0, "allow-empty", &allow_empty, "ok to record an empty change"), - /* end commit contents options */ - - OPT_END() -}; - -static void rollback_index_files(void) -{ - switch (commit_style) { - case COMMIT_AS_IS: - break; /* nothing to do */ - case COMMIT_NORMAL: - rollback_lock_file(&index_lock); - break; - case COMMIT_PARTIAL: - rollback_lock_file(&index_lock); - rollback_lock_file(&false_lock); - break; - } -} - -static int commit_index_files(void) -{ - int err = 0; - - switch (commit_style) { - case COMMIT_AS_IS: - break; /* nothing to do */ - case COMMIT_NORMAL: - err = commit_lock_file(&index_lock); - break; - case COMMIT_PARTIAL: - err = commit_lock_file(&index_lock); - rollback_lock_file(&false_lock); - break; - } - - return err; -} - -/* - * Take a union of paths in the index and the named tree (typically, "HEAD"), - * and return the paths that match the given pattern in list. - */ -static int list_paths(struct string_list *list, const char *with_tree, - const char *prefix, const char **pattern) -{ - int i; - char *m; - - for (i = 0; pattern[i]; i++) - ; - m = xcalloc(1, i); - - if (with_tree) - overlay_tree_on_cache(with_tree, prefix); - - for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; - struct string_list_item *item; - - if (ce->ce_flags & CE_UPDATE) - continue; - if (!match_pathspec(pattern, ce->name, ce_namelen(ce), 0, m)) - continue; - item = string_list_insert(ce->name, list); - if (ce_skip_worktree(ce)) - item->util = item; /* better a valid pointer than a fake one */ - } - - return report_path_error(m, pattern, prefix ? strlen(prefix) : 0); -} - -static void add_remove_files(struct string_list *list) -{ - int i; - for (i = 0; i < list->nr; i++) { - struct stat st; - struct string_list_item *p = &(list->items[i]); - - /* p->util is skip-worktree */ - if (p->util) - continue; - - if (!lstat(p->string, &st)) { - if (add_to_cache(p->string, &st, 0)) - die("updating files failed"); - } else - remove_file_from_cache(p->string); - } -} - -static void create_base_index(void) -{ - struct tree *tree; - struct unpack_trees_options opts; - struct tree_desc t; - - if (initial_commit) { - discard_cache(); - return; - } - - memset(&opts, 0, sizeof(opts)); - opts.head_idx = 1; - opts.index_only = 1; - opts.merge = 1; - opts.src_index = &the_index; - opts.dst_index = &the_index; - - opts.fn = oneway_merge; - tree = parse_tree_indirect(head_sha1); - if (!tree) - die("failed to unpack HEAD tree object"); - parse_tree(tree); - init_tree_desc(&t, tree->buffer, tree->size); - if (unpack_trees(1, &t, &opts)) - exit(128); /* We've already reported the error, finish dying */ -} - -static void refresh_cache_or_die(int refresh_flags) -{ - /* - * refresh_flags contains REFRESH_QUIET, so the only errors - * are for unmerged entries. - */ - if (refresh_cache(refresh_flags | REFRESH_IN_PORCELAIN)) - die_resolve_conflict("commit"); -} - -static char *prepare_index(int argc, const char **argv, const char *prefix, int is_status) -{ - int fd; - struct string_list partial; - const char **pathspec = NULL; - int refresh_flags = REFRESH_QUIET; - - if (is_status) - refresh_flags |= REFRESH_UNMERGED; - if (interactive) { - if (interactive_add(argc, argv, prefix) != 0) - die("interactive add failed"); - if (read_cache_preload(NULL) < 0) - die("index file corrupt"); - commit_style = COMMIT_AS_IS; - return get_index_file(); - } - - if (*argv) - pathspec = get_pathspec(prefix, argv); - - if (read_cache_preload(pathspec) < 0) - die("index file corrupt"); - - /* - * Non partial, non as-is commit. - * - * (1) get the real index; - * (2) update the_index as necessary; - * (3) write the_index out to the real index (still locked); - * (4) return the name of the locked index file. - * - * The caller should run hooks on the locked real index, and - * (A) if all goes well, commit the real index; - * (B) on failure, rollback the real index. - */ - if (all || (also && pathspec && *pathspec)) { - int fd = hold_locked_index(&index_lock, 1); - add_files_to_cache(also ? prefix : NULL, pathspec, 0); - refresh_cache_or_die(refresh_flags); - if (write_cache(fd, active_cache, active_nr) || - close_lock_file(&index_lock)) - die("unable to write new_index file"); - commit_style = COMMIT_NORMAL; - return index_lock.filename; - } - - /* - * As-is commit. - * - * (1) return the name of the real index file. - * - * The caller should run hooks on the real index, and run - * hooks on the real index, and create commit from the_index. - * We still need to refresh the index here. - */ - if (!pathspec || !*pathspec) { - fd = hold_locked_index(&index_lock, 1); - refresh_cache_or_die(refresh_flags); - if (write_cache(fd, active_cache, active_nr) || - commit_locked_index(&index_lock)) - die("unable to write new_index file"); - commit_style = COMMIT_AS_IS; - return get_index_file(); - } - - /* - * A partial commit. - * - * (0) find the set of affected paths; - * (1) get lock on the real index file; - * (2) update the_index with the given paths; - * (3) write the_index out to the real index (still locked); - * (4) get lock on the false index file; - * (5) reset the_index from HEAD; - * (6) update the_index the same way as (2); - * (7) write the_index out to the false index file; - * (8) return the name of the false index file (still locked); - * - * The caller should run hooks on the locked false index, and - * create commit from it. Then - * (A) if all goes well, commit the real index; - * (B) on failure, rollback the real index; - * In either case, rollback the false index. - */ - commit_style = COMMIT_PARTIAL; - - if (in_merge) - die("cannot do a partial commit during a merge."); - - memset(&partial, 0, sizeof(partial)); - partial.strdup_strings = 1; - if (list_paths(&partial, initial_commit ? NULL : "HEAD", prefix, pathspec)) - exit(1); - - discard_cache(); - if (read_cache() < 0) - die("cannot read the index"); - - fd = hold_locked_index(&index_lock, 1); - add_remove_files(&partial); - refresh_cache(REFRESH_QUIET); - if (write_cache(fd, active_cache, active_nr) || - close_lock_file(&index_lock)) - die("unable to write new_index file"); - - fd = hold_lock_file_for_update(&false_lock, - git_path("next-index-%"PRIuMAX, - (uintmax_t) getpid()), - LOCK_DIE_ON_ERROR); - - create_base_index(); - add_remove_files(&partial); - refresh_cache(REFRESH_QUIET); - - if (write_cache(fd, active_cache, active_nr) || - close_lock_file(&false_lock)) - die("unable to write temporary index file"); - - discard_cache(); - read_cache_from(false_lock.filename); - - return false_lock.filename; -} - -static int run_status(FILE *fp, const char *index_file, const char *prefix, int nowarn, - struct wt_status *s) -{ - unsigned char sha1[20]; - - if (s->relative_paths) - s->prefix = prefix; - - if (amend) { - s->amend = 1; - s->reference = "HEAD^1"; - } - s->verbose = verbose; - s->index_file = index_file; - s->fp = fp; - s->nowarn = nowarn; - s->is_initial = get_sha1(s->reference, sha1) ? 1 : 0; - - wt_status_collect(s); - - switch (status_format) { - case STATUS_FORMAT_SHORT: - wt_shortstatus_print(s, null_termination); - break; - case STATUS_FORMAT_PORCELAIN: - wt_porcelain_print(s, null_termination); - break; - case STATUS_FORMAT_LONG: - wt_status_print(s); - break; - } - - return s->commitable; -} - -static int is_a_merge(const unsigned char *sha1) -{ - struct commit *commit = lookup_commit(sha1); - if (!commit || parse_commit(commit)) - die("could not parse HEAD commit"); - return !!(commit->parents && commit->parents->next); -} - -static const char sign_off_header[] = "Signed-off-by: "; - -static void determine_author_info(void) -{ - char *name, *email, *date; - - name = getenv("GIT_AUTHOR_NAME"); - email = getenv("GIT_AUTHOR_EMAIL"); - date = getenv("GIT_AUTHOR_DATE"); - - if (use_message && !renew_authorship) { - const char *a, *lb, *rb, *eol; - - a = strstr(use_message_buffer, "\nauthor "); - if (!a) - die("invalid commit: %s", use_message); - - lb = strstr(a + 8, " <"); - rb = strstr(a + 8, "> "); - eol = strchr(a + 8, '\n'); - if (!lb || !rb || !eol) - die("invalid commit: %s", use_message); - - name = xstrndup(a + 8, lb - (a + 8)); - email = xstrndup(lb + 2, rb - (lb + 2)); - date = xstrndup(rb + 2, eol - (rb + 2)); - } - - if (force_author) { - const char *lb = strstr(force_author, " <"); - const char *rb = strchr(force_author, '>'); - - if (!lb || !rb) - die("malformed --author parameter"); - name = xstrndup(force_author, lb - force_author); - email = xstrndup(lb + 2, rb - (lb + 2)); - } - - if (force_date) - date = force_date; - - author_name = name; - author_email = email; - author_date = date; -} - -static int ends_rfc2822_footer(struct strbuf *sb) -{ - int ch; - int hit = 0; - int i, j, k; - int len = sb->len; - int first = 1; - const char *buf = sb->buf; - - for (i = len - 1; i > 0; i--) { - if (hit && buf[i] == '\n') - break; - hit = (buf[i] == '\n'); - } - - while (i < len - 1 && buf[i] == '\n') - i++; - - for (; i < len; i = k) { - for (k = i; k < len && buf[k] != '\n'; k++) - ; /* do nothing */ - k++; - - if ((buf[k] == ' ' || buf[k] == '\t') && !first) - continue; - - first = 0; - - for (j = 0; i + j < len; j++) { - ch = buf[i + j]; - if (ch == ':') - break; - if (isalnum(ch) || - (ch == '-')) - continue; - return 0; - } - } - return 1; -} - -static int prepare_to_commit(const char *index_file, const char *prefix, - struct wt_status *s) -{ - struct stat statbuf; - int commitable, saved_color_setting; - struct strbuf sb = STRBUF_INIT; - char *buffer; - FILE *fp; - const char *hook_arg1 = NULL; - const char *hook_arg2 = NULL; - int ident_shown = 0; - - if (!no_verify && run_hook(index_file, "pre-commit", NULL)) - return 0; - - if (message.len) { - strbuf_addbuf(&sb, &message); - hook_arg1 = "message"; - } else if (logfile && !strcmp(logfile, "-")) { - if (isatty(0)) - fprintf(stderr, "(reading log message from standard input)\n"); - if (strbuf_read(&sb, 0, 0) < 0) - die_errno("could not read log from standard input"); - hook_arg1 = "message"; - } else if (logfile) { - if (strbuf_read_file(&sb, logfile, 0) < 0) - die_errno("could not read log file '%s'", - logfile); - hook_arg1 = "message"; - } else if (use_message) { - buffer = strstr(use_message_buffer, "\n\n"); - if (!buffer || buffer[2] == '\0') - die("commit has empty message"); - strbuf_add(&sb, buffer + 2, strlen(buffer + 2)); - hook_arg1 = "commit"; - hook_arg2 = use_message; - } else if (!stat(git_path("MERGE_MSG"), &statbuf)) { - if (strbuf_read_file(&sb, git_path("MERGE_MSG"), 0) < 0) - die_errno("could not read MERGE_MSG"); - hook_arg1 = "merge"; - } else if (!stat(git_path("SQUASH_MSG"), &statbuf)) { - if (strbuf_read_file(&sb, git_path("SQUASH_MSG"), 0) < 0) - die_errno("could not read SQUASH_MSG"); - hook_arg1 = "squash"; - } else if (template_file && !stat(template_file, &statbuf)) { - if (strbuf_read_file(&sb, template_file, 0) < 0) - die_errno("could not read '%s'", template_file); - hook_arg1 = "template"; - } - - /* - * This final case does not modify the template message, - * it just sets the argument to the prepare-commit-msg hook. - */ - else if (in_merge) - hook_arg1 = "merge"; - - fp = fopen(git_path(commit_editmsg), "w"); - if (fp == NULL) - die_errno("could not open '%s'", git_path(commit_editmsg)); - - if (cleanup_mode != CLEANUP_NONE) - stripspace(&sb, 0); - - if (signoff) { - struct strbuf sob = STRBUF_INIT; - int i; - - strbuf_addstr(&sob, sign_off_header); - strbuf_addstr(&sob, fmt_name(getenv("GIT_COMMITTER_NAME"), - getenv("GIT_COMMITTER_EMAIL"))); - strbuf_addch(&sob, '\n'); - for (i = sb.len - 1; i > 0 && sb.buf[i - 1] != '\n'; i--) - ; /* do nothing */ - if (prefixcmp(sb.buf + i, sob.buf)) { - if (!i || !ends_rfc2822_footer(&sb)) - strbuf_addch(&sb, '\n'); - strbuf_addbuf(&sb, &sob); - } - strbuf_release(&sob); - } - - if (fwrite(sb.buf, 1, sb.len, fp) < sb.len) - die_errno("could not write commit template"); - - strbuf_release(&sb); - - determine_author_info(); - - /* This checks if committer ident is explicitly given */ - git_committer_info(0); - if (use_editor && include_status) { - char *author_ident; - const char *committer_ident; - - if (in_merge) - fprintf(fp, - "#\n" - "# It looks like you may be committing a MERGE.\n" - "# If this is not correct, please remove the file\n" - "# %s\n" - "# and try again.\n" - "#\n", - git_path("MERGE_HEAD")); - - fprintf(fp, - "\n" - "# Please enter the commit message for your changes."); - if (cleanup_mode == CLEANUP_ALL) - fprintf(fp, - " Lines starting\n" - "# with '#' will be ignored, and an empty" - " message aborts the commit.\n"); - else /* CLEANUP_SPACE, that is. */ - fprintf(fp, - " Lines starting\n" - "# with '#' will be kept; you may remove them" - " yourself if you want to.\n" - "# An empty message aborts the commit.\n"); - if (only_include_assumed) - fprintf(fp, "# %s\n", only_include_assumed); - - author_ident = xstrdup(fmt_name(author_name, author_email)); - committer_ident = fmt_name(getenv("GIT_COMMITTER_NAME"), - getenv("GIT_COMMITTER_EMAIL")); - if (strcmp(author_ident, committer_ident)) - fprintf(fp, - "%s" - "# Author: %s\n", - ident_shown++ ? "" : "#\n", - author_ident); - free(author_ident); - - if (!user_ident_sufficiently_given()) - fprintf(fp, - "%s" - "# Committer: %s\n", - ident_shown++ ? "" : "#\n", - committer_ident); - - if (ident_shown) - fprintf(fp, "#\n"); - - saved_color_setting = s->use_color; - s->use_color = 0; - commitable = run_status(fp, index_file, prefix, 1, s); - s->use_color = saved_color_setting; - } else { - unsigned char sha1[20]; - const char *parent = "HEAD"; - - if (!active_nr && read_cache() < 0) - die("Cannot read index"); - - if (amend) - parent = "HEAD^1"; - - if (get_sha1(parent, sha1)) - commitable = !!active_nr; - else - commitable = index_differs_from(parent, 0); - } - - fclose(fp); - - if (!commitable && !in_merge && !allow_empty && - !(amend && is_a_merge(head_sha1))) { - run_status(stdout, index_file, prefix, 0, s); - return 0; - } - - /* - * Re-read the index as pre-commit hook could have updated it, - * and write it out as a tree. We must do this before we invoke - * the editor and after we invoke run_status above. - */ - discard_cache(); - read_cache_from(index_file); - if (!active_cache_tree) - active_cache_tree = cache_tree(); - if (cache_tree_update(active_cache_tree, - active_cache, active_nr, 0, 0) < 0) { - error("Error building trees"); - return 0; - } - - if (run_hook(index_file, "prepare-commit-msg", - git_path(commit_editmsg), hook_arg1, hook_arg2, NULL)) - return 0; - - if (use_editor) { - char index[PATH_MAX]; - const char *env[2] = { index, NULL }; - snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", index_file); - if (launch_editor(git_path(commit_editmsg), NULL, env)) { - fprintf(stderr, - "Please supply the message using either -m or -F option.\n"); - exit(1); - } - } - - if (!no_verify && - run_hook(index_file, "commit-msg", git_path(commit_editmsg), NULL)) { - return 0; - } - - return 1; -} - -/* - * Find out if the message in the strbuf contains only whitespace and - * Signed-off-by lines. - */ -static int message_is_empty(struct strbuf *sb) -{ - struct strbuf tmpl = STRBUF_INIT; - const char *nl; - int eol, i, start = 0; - - if (cleanup_mode == CLEANUP_NONE && sb->len) - return 0; - - /* See if the template is just a prefix of the message. */ - if (template_file && strbuf_read_file(&tmpl, template_file, 0) > 0) { - stripspace(&tmpl, cleanup_mode == CLEANUP_ALL); - if (start + tmpl.len <= sb->len && - memcmp(tmpl.buf, sb->buf + start, tmpl.len) == 0) - start += tmpl.len; - } - strbuf_release(&tmpl); - - /* Check if the rest is just whitespace and Signed-of-by's. */ - for (i = start; i < sb->len; i++) { - nl = memchr(sb->buf + i, '\n', sb->len - i); - if (nl) - eol = nl - sb->buf; - else - eol = sb->len; - - if (strlen(sign_off_header) <= eol - i && - !prefixcmp(sb->buf + i, sign_off_header)) { - i = eol; - continue; - } - while (i < eol) - if (!isspace(sb->buf[i++])) - return 0; - } - - return 1; -} - -static const char *find_author_by_nickname(const char *name) -{ - struct rev_info revs; - struct commit *commit; - struct strbuf buf = STRBUF_INIT; - const char *av[20]; - int ac = 0; - - init_revisions(&revs, NULL); - strbuf_addf(&buf, "--author=%s", name); - av[++ac] = "--all"; - av[++ac] = "-i"; - av[++ac] = buf.buf; - av[++ac] = NULL; - setup_revisions(ac, av, &revs, NULL); - prepare_revision_walk(&revs); - commit = get_revision(&revs); - if (commit) { - struct pretty_print_context ctx = {0}; - ctx.date_mode = DATE_NORMAL; - strbuf_release(&buf); - format_commit_message(commit, "%an <%ae>", &buf, &ctx); - return strbuf_detach(&buf, NULL); - } - die("No existing author found with '%s'", name); -} - - -static void handle_untracked_files_arg(struct wt_status *s) -{ - if (!untracked_files_arg) - ; /* default already initialized */ - else if (!strcmp(untracked_files_arg, "no")) - s->show_untracked_files = SHOW_NO_UNTRACKED_FILES; - else if (!strcmp(untracked_files_arg, "normal")) - s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES; - else if (!strcmp(untracked_files_arg, "all")) - s->show_untracked_files = SHOW_ALL_UNTRACKED_FILES; - else - die("Invalid untracked files mode '%s'", untracked_files_arg); -} - -static int parse_and_validate_options(int argc, const char *argv[], - const char * const usage[], - const char *prefix, - struct wt_status *s) -{ - int f = 0; - - argc = parse_options(argc, argv, prefix, builtin_commit_options, usage, - 0); - - if (force_author && !strchr(force_author, '>')) - force_author = find_author_by_nickname(force_author); - - if (force_author && renew_authorship) - die("Using both --reset-author and --author does not make sense"); - - if (logfile || message.len || use_message) - use_editor = 0; - if (edit_flag) - use_editor = 1; - if (!use_editor) - setenv("GIT_EDITOR", ":", 1); - - if (get_sha1("HEAD", head_sha1)) - initial_commit = 1; - - /* Sanity check options */ - if (amend && initial_commit) - die("You have nothing to amend."); - if (amend && in_merge) - die("You are in the middle of a merge -- cannot amend."); - - if (use_message) - f++; - if (edit_message) - f++; - if (logfile) - f++; - if (f > 1) - die("Only one of -c/-C/-F can be used."); - if (message.len && f > 0) - die("Option -m cannot be combined with -c/-C/-F."); - if (edit_message) - use_message = edit_message; - if (amend && !use_message) - use_message = "HEAD"; - if (!use_message && renew_authorship) - die("--reset-author can be used only with -C, -c or --amend."); - if (use_message) { - unsigned char sha1[20]; - static char utf8[] = "UTF-8"; - const char *out_enc; - char *enc, *end; - struct commit *commit; - - if (get_sha1(use_message, sha1)) - die("could not lookup commit %s", use_message); - commit = lookup_commit_reference(sha1); - if (!commit || parse_commit(commit)) - die("could not parse commit %s", use_message); - - enc = strstr(commit->buffer, "\nencoding"); - if (enc) { - end = strchr(enc + 10, '\n'); - enc = xstrndup(enc + 10, end - (enc + 10)); - } else { - enc = utf8; - } - out_enc = git_commit_encoding ? git_commit_encoding : utf8; - - if (strcmp(out_enc, enc)) - use_message_buffer = - reencode_string(commit->buffer, out_enc, enc); - - /* - * If we failed to reencode the buffer, just copy it - * byte for byte so the user can try to fix it up. - * This also handles the case where input and output - * encodings are identical. - */ - if (use_message_buffer == NULL) - use_message_buffer = xstrdup(commit->buffer); - if (enc != utf8) - free(enc); - } - - if (!!also + !!only + !!all + !!interactive > 1) - die("Only one of --include/--only/--all/--interactive can be used."); - if (argc == 0 && (also || (only && !amend))) - die("No paths with --include/--only does not make sense."); - if (argc == 0 && only && amend) - only_include_assumed = "Clever... amending the last one with dirty index."; - if (argc > 0 && !also && !only) - only_include_assumed = "Explicit paths specified without -i nor -o; assuming --only paths..."; - if (!cleanup_arg || !strcmp(cleanup_arg, "default")) - cleanup_mode = use_editor ? CLEANUP_ALL : CLEANUP_SPACE; - else if (!strcmp(cleanup_arg, "verbatim")) - cleanup_mode = CLEANUP_NONE; - else if (!strcmp(cleanup_arg, "whitespace")) - cleanup_mode = CLEANUP_SPACE; - else if (!strcmp(cleanup_arg, "strip")) - cleanup_mode = CLEANUP_ALL; - else - die("Invalid cleanup mode %s", cleanup_arg); - - handle_untracked_files_arg(s); - - if (all && argc > 0) - die("Paths with -a does not make sense."); - else if (interactive && argc > 0) - die("Paths with --interactive does not make sense."); - - if (null_termination && status_format == STATUS_FORMAT_LONG) - status_format = STATUS_FORMAT_PORCELAIN; - if (status_format != STATUS_FORMAT_LONG) - dry_run = 1; - - return argc; -} - -static int dry_run_commit(int argc, const char **argv, const char *prefix, - struct wt_status *s) -{ - int commitable; - const char *index_file; - - index_file = prepare_index(argc, argv, prefix, 1); - commitable = run_status(stdout, index_file, prefix, 0, s); - rollback_index_files(); - - return commitable ? 0 : 1; -} - -static int parse_status_slot(const char *var, int offset) -{ - if (!strcasecmp(var+offset, "header")) - return WT_STATUS_HEADER; - if (!strcasecmp(var+offset, "updated") - || !strcasecmp(var+offset, "added")) - return WT_STATUS_UPDATED; - if (!strcasecmp(var+offset, "changed")) - return WT_STATUS_CHANGED; - if (!strcasecmp(var+offset, "untracked")) - return WT_STATUS_UNTRACKED; - if (!strcasecmp(var+offset, "nobranch")) - return WT_STATUS_NOBRANCH; - if (!strcasecmp(var+offset, "unmerged")) - return WT_STATUS_UNMERGED; - return -1; -} - -static int git_status_config(const char *k, const char *v, void *cb) -{ - struct wt_status *s = cb; - - if (!strcmp(k, "status.submodulesummary")) { - int is_bool; - s->submodule_summary = git_config_bool_or_int(k, v, &is_bool); - if (is_bool && s->submodule_summary) - s->submodule_summary = -1; - return 0; - } - if (!strcmp(k, "status.color") || !strcmp(k, "color.status")) { - s->use_color = git_config_colorbool(k, v, -1); - return 0; - } - if (!prefixcmp(k, "status.color.") || !prefixcmp(k, "color.status.")) { - int slot = parse_status_slot(k, 13); - if (slot < 0) - return 0; - if (!v) - return config_error_nonbool(k); - color_parse(v, k, s->color_palette[slot]); - return 0; - } - if (!strcmp(k, "status.relativepaths")) { - s->relative_paths = git_config_bool(k, v); - return 0; - } - if (!strcmp(k, "status.showuntrackedfiles")) { - if (!v) - return config_error_nonbool(k); - else if (!strcmp(v, "no")) - s->show_untracked_files = SHOW_NO_UNTRACKED_FILES; - else if (!strcmp(v, "normal")) - s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES; - else if (!strcmp(v, "all")) - s->show_untracked_files = SHOW_ALL_UNTRACKED_FILES; - else - return error("Invalid untracked files mode '%s'", v); - return 0; - } - return git_diff_ui_config(k, v, NULL); -} - -int cmd_status(int argc, const char **argv, const char *prefix) -{ - struct wt_status s; - unsigned char sha1[20]; - static struct option builtin_status_options[] = { - OPT__VERBOSE(&verbose), - OPT_SET_INT('s', "short", &status_format, - "show status concisely", STATUS_FORMAT_SHORT), - OPT_SET_INT(0, "porcelain", &status_format, - "show porcelain output format", - STATUS_FORMAT_PORCELAIN), - OPT_BOOLEAN('z', "null", &null_termination, - "terminate entries with NUL"), - { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, - "mode", - "show untracked files, optional modes: all, normal, no. (Default: all)", - PARSE_OPT_OPTARG, NULL, (intptr_t)"all" }, - OPT_END(), - }; - - if (null_termination && status_format == STATUS_FORMAT_LONG) - status_format = STATUS_FORMAT_PORCELAIN; - - wt_status_prepare(&s); - git_config(git_status_config, &s); - in_merge = file_exists(git_path("MERGE_HEAD")); - argc = parse_options(argc, argv, prefix, - builtin_status_options, - builtin_status_usage, 0); - handle_untracked_files_arg(&s); - - if (*argv) - s.pathspec = get_pathspec(prefix, argv); - - read_cache(); - refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, s.pathspec, NULL, NULL); - s.is_initial = get_sha1(s.reference, sha1) ? 1 : 0; - s.in_merge = in_merge; - wt_status_collect(&s); - - if (s.relative_paths) - s.prefix = prefix; - if (s.use_color == -1) - s.use_color = git_use_color_default; - if (diff_use_color_default == -1) - diff_use_color_default = git_use_color_default; - - switch (status_format) { - case STATUS_FORMAT_SHORT: - wt_shortstatus_print(&s, null_termination); - break; - case STATUS_FORMAT_PORCELAIN: - wt_porcelain_print(&s, null_termination); - break; - case STATUS_FORMAT_LONG: - s.verbose = verbose; - wt_status_print(&s); - break; - } - return 0; -} - -static void print_summary(const char *prefix, const unsigned char *sha1) -{ - struct rev_info rev; - struct commit *commit; - struct strbuf format = STRBUF_INIT; - unsigned char junk_sha1[20]; - const char *head = resolve_ref("HEAD", junk_sha1, 0, NULL); - struct pretty_print_context pctx = {0}; - struct strbuf author_ident = STRBUF_INIT; - struct strbuf committer_ident = STRBUF_INIT; - - commit = lookup_commit(sha1); - if (!commit) - die("couldn't look up newly created commit"); - if (!commit || parse_commit(commit)) - die("could not parse newly created commit"); - - strbuf_addstr(&format, "format:%h] %s"); - - format_commit_message(commit, "%an <%ae>", &author_ident, &pctx); - format_commit_message(commit, "%cn <%ce>", &committer_ident, &pctx); - if (strbuf_cmp(&author_ident, &committer_ident)) { - strbuf_addstr(&format, "\n Author: "); - strbuf_addbuf_percentquote(&format, &author_ident); - } - if (!user_ident_sufficiently_given()) { - strbuf_addstr(&format, "\n Committer: "); - strbuf_addbuf_percentquote(&format, &committer_ident); - if (advice_implicit_identity) { - strbuf_addch(&format, '\n'); - strbuf_addstr(&format, implicit_ident_advice); - } - } - strbuf_release(&author_ident); - strbuf_release(&committer_ident); - - init_revisions(&rev, prefix); - setup_revisions(0, NULL, &rev, NULL); - - rev.abbrev = 0; - rev.diff = 1; - rev.diffopt.output_format = - DIFF_FORMAT_SHORTSTAT | DIFF_FORMAT_SUMMARY; - - rev.verbose_header = 1; - rev.show_root_diff = 1; - get_commit_format(format.buf, &rev); - rev.always_show_header = 0; - rev.diffopt.detect_rename = 1; - rev.diffopt.rename_limit = 100; - rev.diffopt.break_opt = 0; - diff_setup_done(&rev.diffopt); - - printf("[%s%s ", - !prefixcmp(head, "refs/heads/") ? - head + 11 : - !strcmp(head, "HEAD") ? - "detached HEAD" : - head, - initial_commit ? " (root-commit)" : ""); - - if (!log_tree_commit(&rev, commit)) { - struct pretty_print_context ctx = {0}; - struct strbuf buf = STRBUF_INIT; - ctx.date_mode = DATE_NORMAL; - format_commit_message(commit, format.buf + 7, &buf, &ctx); - printf("%s\n", buf.buf); - strbuf_release(&buf); - } - strbuf_release(&format); -} - -static int git_commit_config(const char *k, const char *v, void *cb) -{ - struct wt_status *s = cb; - - if (!strcmp(k, "commit.template")) - return git_config_pathname(&template_file, k, v); - if (!strcmp(k, "commit.status")) { - include_status = git_config_bool(k, v); - return 0; - } - - return git_status_config(k, v, s); -} - -int cmd_commit(int argc, const char **argv, const char *prefix) -{ - struct strbuf sb = STRBUF_INIT; - const char *index_file, *reflog_msg; - char *nl, *p; - unsigned char commit_sha1[20]; - struct ref_lock *ref_lock; - struct commit_list *parents = NULL, **pptr = &parents; - struct stat statbuf; - int allow_fast_forward = 1; - struct wt_status s; - - wt_status_prepare(&s); - git_config(git_commit_config, &s); - in_merge = file_exists(git_path("MERGE_HEAD")); - s.in_merge = in_merge; - - if (s.use_color == -1) - s.use_color = git_use_color_default; - argc = parse_and_validate_options(argc, argv, builtin_commit_usage, - prefix, &s); - if (dry_run) { - if (diff_use_color_default == -1) - diff_use_color_default = git_use_color_default; - return dry_run_commit(argc, argv, prefix, &s); - } - index_file = prepare_index(argc, argv, prefix, 0); - - /* Set up everything for writing the commit object. This includes - running hooks, writing the trees, and interacting with the user. */ - if (!prepare_to_commit(index_file, prefix, &s)) { - rollback_index_files(); - return 1; - } - - /* Determine parents */ - if (initial_commit) { - reflog_msg = "commit (initial)"; - } else if (amend) { - struct commit_list *c; - struct commit *commit; - - reflog_msg = "commit (amend)"; - commit = lookup_commit(head_sha1); - if (!commit || parse_commit(commit)) - die("could not parse HEAD commit"); - - for (c = commit->parents; c; c = c->next) - pptr = &commit_list_insert(c->item, pptr)->next; - } else if (in_merge) { - struct strbuf m = STRBUF_INIT; - FILE *fp; - - reflog_msg = "commit (merge)"; - pptr = &commit_list_insert(lookup_commit(head_sha1), pptr)->next; - fp = fopen(git_path("MERGE_HEAD"), "r"); - if (fp == NULL) - die_errno("could not open '%s' for reading", - git_path("MERGE_HEAD")); - while (strbuf_getline(&m, fp, '\n') != EOF) { - unsigned char sha1[20]; - if (get_sha1_hex(m.buf, sha1) < 0) - die("Corrupt MERGE_HEAD file (%s)", m.buf); - pptr = &commit_list_insert(lookup_commit(sha1), pptr)->next; - } - fclose(fp); - strbuf_release(&m); - if (!stat(git_path("MERGE_MODE"), &statbuf)) { - if (strbuf_read_file(&sb, git_path("MERGE_MODE"), 0) < 0) - die_errno("could not read MERGE_MODE"); - if (!strcmp(sb.buf, "no-ff")) - allow_fast_forward = 0; - } - if (allow_fast_forward) - parents = reduce_heads(parents); - } else { - reflog_msg = "commit"; - pptr = &commit_list_insert(lookup_commit(head_sha1), pptr)->next; - } - - /* Finally, get the commit message */ - strbuf_reset(&sb); - if (strbuf_read_file(&sb, git_path(commit_editmsg), 0) < 0) { - int saved_errno = errno; - rollback_index_files(); - die("could not read commit message: %s", strerror(saved_errno)); - } - - /* Truncate the message just before the diff, if any. */ - if (verbose) { - p = strstr(sb.buf, "\ndiff --git "); - if (p != NULL) - strbuf_setlen(&sb, p - sb.buf + 1); - } - - if (cleanup_mode != CLEANUP_NONE) - stripspace(&sb, cleanup_mode == CLEANUP_ALL); - if (message_is_empty(&sb)) { - rollback_index_files(); - fprintf(stderr, "Aborting commit due to empty commit message.\n"); - exit(1); - } - - if (commit_tree(sb.buf, active_cache_tree->sha1, parents, commit_sha1, - fmt_ident(author_name, author_email, author_date, - IDENT_ERROR_ON_NO_NAME))) { - rollback_index_files(); - die("failed to write commit object"); - } - - ref_lock = lock_any_ref_for_update("HEAD", - initial_commit ? NULL : head_sha1, - 0); - - nl = strchr(sb.buf, '\n'); - if (nl) - strbuf_setlen(&sb, nl + 1 - sb.buf); - else - strbuf_addch(&sb, '\n'); - strbuf_insert(&sb, 0, reflog_msg, strlen(reflog_msg)); - strbuf_insert(&sb, strlen(reflog_msg), ": ", 2); - - if (!ref_lock) { - rollback_index_files(); - die("cannot lock HEAD ref"); - } - if (write_ref_sha1(ref_lock, commit_sha1, sb.buf) < 0) { - rollback_index_files(); - die("cannot update HEAD ref"); - } - - unlink(git_path("MERGE_HEAD")); - unlink(git_path("MERGE_MSG")); - unlink(git_path("MERGE_MODE")); - unlink(git_path("SQUASH_MSG")); - - if (commit_index_files()) - die ("Repository has been updated, but unable to write\n" - "new_index file. Check that disk is not full or quota is\n" - "not exceeded, and then \"git reset HEAD\" to recover."); - - rerere(0); - run_hook(get_index_file(), "post-commit", NULL); - if (!quiet) - print_summary(prefix, commit_sha1); - - return 0; -} diff --git a/builtin-config.c b/builtin-config.c deleted file mode 100644 index 4bc46b1..0000000 --- a/builtin-config.c +++ /dev/null @@ -1,496 +0,0 @@ -#include "builtin.h" -#include "cache.h" -#include "color.h" -#include "parse-options.h" - -static const char *const builtin_config_usage[] = { - "git config [options]", - NULL -}; - -static char *key; -static regex_t *key_regexp; -static regex_t *regexp; -static int show_keys; -static int use_key_regexp; -static int do_all; -static int do_not_match; -static int seen; -static char delim = '='; -static char key_delim = ' '; -static char term = '\n'; - -static int use_global_config, use_system_config; -static const char *given_config_file; -static int actions, types; -static const char *get_color_slot, *get_colorbool_slot; -static int end_null; - -#define ACTION_GET (1<<0) -#define ACTION_GET_ALL (1<<1) -#define ACTION_GET_REGEXP (1<<2) -#define ACTION_REPLACE_ALL (1<<3) -#define ACTION_ADD (1<<4) -#define ACTION_UNSET (1<<5) -#define ACTION_UNSET_ALL (1<<6) -#define ACTION_RENAME_SECTION (1<<7) -#define ACTION_REMOVE_SECTION (1<<8) -#define ACTION_LIST (1<<9) -#define ACTION_EDIT (1<<10) -#define ACTION_SET (1<<11) -#define ACTION_SET_ALL (1<<12) -#define ACTION_GET_COLOR (1<<13) -#define ACTION_GET_COLORBOOL (1<<14) - -#define TYPE_BOOL (1<<0) -#define TYPE_INT (1<<1) -#define TYPE_BOOL_OR_INT (1<<2) -#define TYPE_PATH (1<<3) - -static struct option builtin_config_options[] = { - OPT_GROUP("Config file location"), - OPT_BOOLEAN(0, "global", &use_global_config, "use global config file"), - OPT_BOOLEAN(0, "system", &use_system_config, "use system config file"), - OPT_STRING('f', "file", &given_config_file, "FILE", "use given config file"), - OPT_GROUP("Action"), - OPT_BIT(0, "get", &actions, "get value: name [value-regex]", ACTION_GET), - OPT_BIT(0, "get-all", &actions, "get all values: key [value-regex]", ACTION_GET_ALL), - OPT_BIT(0, "get-regexp", &actions, "get values for regexp: name-regex [value-regex]", ACTION_GET_REGEXP), - OPT_BIT(0, "replace-all", &actions, "replace all matching variables: name value [value_regex]", ACTION_REPLACE_ALL), - OPT_BIT(0, "add", &actions, "adds a new variable: name value", ACTION_ADD), - OPT_BIT(0, "unset", &actions, "removes a variable: name [value-regex]", ACTION_UNSET), - OPT_BIT(0, "unset-all", &actions, "removes all matches: name [value-regex]", ACTION_UNSET_ALL), - OPT_BIT(0, "rename-section", &actions, "rename section: old-name new-name", ACTION_RENAME_SECTION), - OPT_BIT(0, "remove-section", &actions, "remove a section: name", ACTION_REMOVE_SECTION), - OPT_BIT('l', "list", &actions, "list all", ACTION_LIST), - OPT_BIT('e', "edit", &actions, "opens an editor", ACTION_EDIT), - OPT_STRING(0, "get-color", &get_color_slot, "slot", "find the color configured: [default]"), - OPT_STRING(0, "get-colorbool", &get_colorbool_slot, "slot", "find the color setting: [stdout-is-tty]"), - OPT_GROUP("Type"), - OPT_BIT(0, "bool", &types, "value is \"true\" or \"false\"", TYPE_BOOL), - OPT_BIT(0, "int", &types, "value is decimal number", TYPE_INT), - OPT_BIT(0, "bool-or-int", &types, "value is --bool or --int", TYPE_BOOL_OR_INT), - OPT_BIT(0, "path", &types, "value is a path (file or directory name)", TYPE_PATH), - OPT_GROUP("Other"), - OPT_BOOLEAN('z', "null", &end_null, "terminate values with NUL byte"), - OPT_END(), -}; - -static void check_argc(int argc, int min, int max) { - if (argc >= min && argc <= max) - return; - error("wrong number of arguments"); - usage_with_options(builtin_config_usage, builtin_config_options); -} - -static int show_all_config(const char *key_, const char *value_, void *cb) -{ - if (value_) - printf("%s%c%s%c", key_, delim, value_, term); - else - printf("%s%c", key_, term); - return 0; -} - -static int show_config(const char *key_, const char *value_, void *cb) -{ - char value[256]; - const char *vptr = value; - int must_free_vptr = 0; - int dup_error = 0; - - if (!use_key_regexp && strcmp(key_, key)) - return 0; - if (use_key_regexp && regexec(key_regexp, key_, 0, NULL, 0)) - return 0; - if (regexp != NULL && - (do_not_match ^ !!regexec(regexp, (value_?value_:""), 0, NULL, 0))) - return 0; - - if (show_keys) { - if (value_) - printf("%s%c", key_, key_delim); - else - printf("%s", key_); - } - if (seen && !do_all) - dup_error = 1; - if (types == TYPE_INT) - sprintf(value, "%d", git_config_int(key_, value_?value_:"")); - else if (types == TYPE_BOOL) - vptr = git_config_bool(key_, value_) ? "true" : "false"; - else if (types == TYPE_BOOL_OR_INT) { - int is_bool, v; - v = git_config_bool_or_int(key_, value_, &is_bool); - if (is_bool) - vptr = v ? "true" : "false"; - else - sprintf(value, "%d", v); - } else if (types == TYPE_PATH) { - git_config_pathname(&vptr, key_, value_); - must_free_vptr = 1; - } - else - vptr = value_?value_:""; - seen++; - if (dup_error) { - error("More than one value for the key %s: %s", - key_, vptr); - } - else - printf("%s%c", vptr, term); - if (must_free_vptr) - /* If vptr must be freed, it's a pointer to a - * dynamically allocated buffer, it's safe to cast to - * const. - */ - free((char *)vptr); - - return 0; -} - -static int get_value(const char *key_, const char *regex_) -{ - int ret = -1; - char *tl; - char *global = NULL, *repo_config = NULL; - const char *system_wide = NULL, *local; - - local = config_exclusive_filename; - if (!local) { - const char *home = getenv("HOME"); - local = repo_config = git_pathdup("config"); - if (git_config_global() && home) - global = xstrdup(mkpath("%s/.gitconfig", home)); - if (git_config_system()) - system_wide = git_etc_gitconfig(); - } - - key = xstrdup(key_); - for (tl=key+strlen(key)-1; tl >= key && *tl != '.'; --tl) - *tl = tolower(*tl); - for (tl=key; *tl && *tl != '.'; ++tl) - *tl = tolower(*tl); - - if (use_key_regexp) { - key_regexp = (regex_t*)xmalloc(sizeof(regex_t)); - if (regcomp(key_regexp, key, REG_EXTENDED)) { - fprintf(stderr, "Invalid key pattern: %s\n", key_); - goto free_strings; - } - } - - if (regex_) { - if (regex_[0] == '!') { - do_not_match = 1; - regex_++; - } - - regexp = (regex_t*)xmalloc(sizeof(regex_t)); - if (regcomp(regexp, regex_, REG_EXTENDED)) { - fprintf(stderr, "Invalid pattern: %s\n", regex_); - goto free_strings; - } - } - - if (do_all && system_wide) - git_config_from_file(show_config, system_wide, NULL); - if (do_all && global) - git_config_from_file(show_config, global, NULL); - git_config_from_file(show_config, local, NULL); - if (!do_all && !seen && global) - git_config_from_file(show_config, global, NULL); - if (!do_all && !seen && system_wide) - git_config_from_file(show_config, system_wide, NULL); - - free(key); - if (regexp) { - regfree(regexp); - free(regexp); - } - - if (do_all) - ret = !seen; - else - ret = (seen == 1) ? 0 : seen > 1 ? 2 : 1; - -free_strings: - free(repo_config); - free(global); - return ret; -} - -static char *normalize_value(const char *key, const char *value) -{ - char *normalized; - - if (!value) - return NULL; - - if (types == 0 || types == TYPE_PATH) - /* - * We don't do normalization for TYPE_PATH here: If - * the path is like ~/foobar/, we prefer to store - * "~/foobar/" in the config file, and to expand the ~ - * when retrieving the value. - */ - normalized = xstrdup(value); - else { - normalized = xmalloc(64); - if (types == TYPE_INT) { - int v = git_config_int(key, value); - sprintf(normalized, "%d", v); - } - else if (types == TYPE_BOOL) - sprintf(normalized, "%s", - git_config_bool(key, value) ? "true" : "false"); - else if (types == TYPE_BOOL_OR_INT) { - int is_bool, v; - v = git_config_bool_or_int(key, value, &is_bool); - if (!is_bool) - sprintf(normalized, "%d", v); - else - sprintf(normalized, "%s", v ? "true" : "false"); - } - } - - return normalized; -} - -static int get_color_found; -static const char *get_color_slot; -static const char *get_colorbool_slot; -static char parsed_color[COLOR_MAXLEN]; - -static int git_get_color_config(const char *var, const char *value, void *cb) -{ - if (!strcmp(var, get_color_slot)) { - if (!value) - config_error_nonbool(var); - color_parse(value, var, parsed_color); - get_color_found = 1; - } - return 0; -} - -static void get_color(const char *def_color) -{ - get_color_found = 0; - parsed_color[0] = '\0'; - git_config(git_get_color_config, NULL); - - if (!get_color_found && def_color) - color_parse(def_color, "command line", parsed_color); - - fputs(parsed_color, stdout); -} - -static int stdout_is_tty; -static int get_colorbool_found; -static int get_diff_color_found; -static int git_get_colorbool_config(const char *var, const char *value, - void *cb) -{ - if (!strcmp(var, get_colorbool_slot)) { - get_colorbool_found = - git_config_colorbool(var, value, stdout_is_tty); - } - if (!strcmp(var, "diff.color")) { - get_diff_color_found = - git_config_colorbool(var, value, stdout_is_tty); - } - if (!strcmp(var, "color.ui")) { - git_use_color_default = git_config_colorbool(var, value, stdout_is_tty); - return 0; - } - return 0; -} - -static int get_colorbool(int print) -{ - get_colorbool_found = -1; - get_diff_color_found = -1; - git_config(git_get_colorbool_config, NULL); - - if (get_colorbool_found < 0) { - if (!strcmp(get_colorbool_slot, "color.diff")) - get_colorbool_found = get_diff_color_found; - if (get_colorbool_found < 0) - get_colorbool_found = git_use_color_default; - } - - if (print) { - printf("%s\n", get_colorbool_found ? "true" : "false"); - return 0; - } else - return get_colorbool_found ? 0 : 1; -} - -int cmd_config(int argc, const char **argv, const char *unused_prefix) -{ - int nongit; - char *value; - const char *prefix = setup_git_directory_gently(&nongit); - - config_exclusive_filename = getenv(CONFIG_ENVIRONMENT); - - argc = parse_options(argc, argv, prefix, builtin_config_options, - builtin_config_usage, - PARSE_OPT_STOP_AT_NON_OPTION); - - if (use_global_config + use_system_config + !!given_config_file > 1) { - error("only one config file at a time."); - usage_with_options(builtin_config_usage, builtin_config_options); - } - - if (use_global_config) { - char *home = getenv("HOME"); - if (home) { - char *user_config = xstrdup(mkpath("%s/.gitconfig", home)); - config_exclusive_filename = user_config; - } else { - die("$HOME not set"); - } - } - else if (use_system_config) - config_exclusive_filename = git_etc_gitconfig(); - else if (given_config_file) { - if (!is_absolute_path(given_config_file) && prefix) - config_exclusive_filename = prefix_filename(prefix, - strlen(prefix), - given_config_file); - else - config_exclusive_filename = given_config_file; - } - - if (end_null) { - term = '\0'; - delim = '\n'; - key_delim = '\n'; - } - - if (HAS_MULTI_BITS(types)) { - error("only one type at a time."); - usage_with_options(builtin_config_usage, builtin_config_options); - } - - if (get_color_slot) - actions |= ACTION_GET_COLOR; - if (get_colorbool_slot) - actions |= ACTION_GET_COLORBOOL; - - if ((get_color_slot || get_colorbool_slot) && types) { - error("--get-color and variable type are incoherent"); - usage_with_options(builtin_config_usage, builtin_config_options); - } - - if (HAS_MULTI_BITS(actions)) { - error("only one action at a time."); - usage_with_options(builtin_config_usage, builtin_config_options); - } - if (actions == 0) - switch (argc) { - case 1: actions = ACTION_GET; break; - case 2: actions = ACTION_SET; break; - case 3: actions = ACTION_SET_ALL; break; - default: - usage_with_options(builtin_config_usage, builtin_config_options); - } - - if (actions == ACTION_LIST) { - check_argc(argc, 0, 0); - if (git_config(show_all_config, NULL) < 0) { - if (config_exclusive_filename) - die_errno("unable to read config file '%s'", - config_exclusive_filename); - else - die("error processing config file(s)"); - } - } - else if (actions == ACTION_EDIT) { - check_argc(argc, 0, 0); - if (!config_exclusive_filename && nongit) - die("not in a git directory"); - git_config(git_default_config, NULL); - launch_editor(config_exclusive_filename ? - config_exclusive_filename : git_path("config"), - NULL, NULL); - } - else if (actions == ACTION_SET) { - check_argc(argc, 2, 2); - value = normalize_value(argv[0], argv[1]); - return git_config_set(argv[0], value); - } - else if (actions == ACTION_SET_ALL) { - check_argc(argc, 2, 3); - value = normalize_value(argv[0], argv[1]); - return git_config_set_multivar(argv[0], value, argv[2], 0); - } - else if (actions == ACTION_ADD) { - check_argc(argc, 2, 2); - value = normalize_value(argv[0], argv[1]); - return git_config_set_multivar(argv[0], value, "^$", 0); - } - else if (actions == ACTION_REPLACE_ALL) { - check_argc(argc, 2, 3); - value = normalize_value(argv[0], argv[1]); - return git_config_set_multivar(argv[0], value, argv[2], 1); - } - else if (actions == ACTION_GET) { - check_argc(argc, 1, 2); - return get_value(argv[0], argv[1]); - } - else if (actions == ACTION_GET_ALL) { - do_all = 1; - check_argc(argc, 1, 2); - return get_value(argv[0], argv[1]); - } - else if (actions == ACTION_GET_REGEXP) { - show_keys = 1; - use_key_regexp = 1; - do_all = 1; - check_argc(argc, 1, 2); - return get_value(argv[0], argv[1]); - } - else if (actions == ACTION_UNSET) { - check_argc(argc, 1, 2); - if (argc == 2) - return git_config_set_multivar(argv[0], NULL, argv[1], 0); - else - return git_config_set(argv[0], NULL); - } - else if (actions == ACTION_UNSET_ALL) { - check_argc(argc, 1, 2); - return git_config_set_multivar(argv[0], NULL, argv[1], 1); - } - else if (actions == ACTION_RENAME_SECTION) { - int ret; - check_argc(argc, 2, 2); - ret = git_config_rename_section(argv[0], argv[1]); - if (ret < 0) - return ret; - if (ret == 0) - die("No such section!"); - } - else if (actions == ACTION_REMOVE_SECTION) { - int ret; - check_argc(argc, 1, 1); - ret = git_config_rename_section(argv[0], NULL); - if (ret < 0) - return ret; - if (ret == 0) - die("No such section!"); - } - else if (actions == ACTION_GET_COLOR) { - get_color(argv[0]); - } - else if (actions == ACTION_GET_COLORBOOL) { - if (argc == 1) - stdout_is_tty = git_config_bool("command line", argv[0]); - else if (argc == 0) - stdout_is_tty = isatty(1); - return get_colorbool(argc != 0); - } - - return 0; -} diff --git a/builtin-count-objects.c b/builtin-count-objects.c deleted file mode 100644 index 2bdd8eb..0000000 --- a/builtin-count-objects.c +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Builtin "git count-objects". - * - * Copyright (c) 2006 Junio C Hamano - */ - -#include "cache.h" -#include "dir.h" -#include "builtin.h" -#include "parse-options.h" - -static void count_objects(DIR *d, char *path, int len, int verbose, - unsigned long *loose, - off_t *loose_size, - unsigned long *packed_loose, - unsigned long *garbage) -{ - struct dirent *ent; - while ((ent = readdir(d)) != NULL) { - char hex[41]; - unsigned char sha1[20]; - const char *cp; - int bad = 0; - - if (is_dot_or_dotdot(ent->d_name)) - continue; - for (cp = ent->d_name; *cp; cp++) { - int ch = *cp; - if (('0' <= ch && ch <= '9') || - ('a' <= ch && ch <= 'f')) - continue; - bad = 1; - break; - } - if (cp - ent->d_name != 38) - bad = 1; - else { - struct stat st; - memcpy(path + len + 3, ent->d_name, 38); - path[len + 2] = '/'; - path[len + 41] = 0; - if (lstat(path, &st) || !S_ISREG(st.st_mode)) - bad = 1; - else - (*loose_size) += xsize_t(on_disk_bytes(st)); - } - if (bad) { - if (verbose) { - error("garbage found: %.*s/%s", - len + 2, path, ent->d_name); - (*garbage)++; - } - continue; - } - (*loose)++; - if (!verbose) - continue; - memcpy(hex, path+len, 2); - memcpy(hex+2, ent->d_name, 38); - hex[40] = 0; - if (get_sha1_hex(hex, sha1)) - die("internal error"); - if (has_sha1_pack(sha1)) - (*packed_loose)++; - } -} - -static char const * const count_objects_usage[] = { - "git count-objects [-v]", - NULL -}; - -int cmd_count_objects(int argc, const char **argv, const char *prefix) -{ - int i, verbose = 0; - const char *objdir = get_object_directory(); - int len = strlen(objdir); - char *path = xmalloc(len + 50); - unsigned long loose = 0, packed = 0, packed_loose = 0, garbage = 0; - off_t loose_size = 0; - struct option opts[] = { - OPT__VERBOSE(&verbose), - OPT_END(), - }; - - argc = parse_options(argc, argv, prefix, opts, count_objects_usage, 0); - /* we do not take arguments other than flags for now */ - if (argc) - usage_with_options(count_objects_usage, opts); - memcpy(path, objdir, len); - if (len && objdir[len-1] != '/') - path[len++] = '/'; - for (i = 0; i < 256; i++) { - DIR *d; - sprintf(path + len, "%02x", i); - d = opendir(path); - if (!d) - continue; - count_objects(d, path, len, verbose, - &loose, &loose_size, &packed_loose, &garbage); - closedir(d); - } - if (verbose) { - struct packed_git *p; - unsigned long num_pack = 0; - off_t size_pack = 0; - if (!packed_git) - prepare_packed_git(); - for (p = packed_git; p; p = p->next) { - if (!p->pack_local) - continue; - if (open_pack_index(p)) - continue; - packed += p->num_objects; - size_pack += p->pack_size + p->index_size; - num_pack++; - } - printf("count: %lu\n", loose); - printf("size: %lu\n", (unsigned long) (loose_size / 1024)); - printf("in-pack: %lu\n", packed); - printf("packs: %lu\n", num_pack); - printf("size-pack: %lu\n", (unsigned long) (size_pack / 1024)); - printf("prune-packable: %lu\n", packed_loose); - printf("garbage: %lu\n", garbage); - } - else - printf("%lu objects, %lu kilobytes\n", - loose, (unsigned long) (loose_size / 1024)); - return 0; -} diff --git a/builtin-describe.c b/builtin-describe.c deleted file mode 100644 index 71be2a9..0000000 --- a/builtin-describe.c +++ /dev/null @@ -1,396 +0,0 @@ -#include "cache.h" -#include "commit.h" -#include "tag.h" -#include "refs.h" -#include "builtin.h" -#include "exec_cmd.h" -#include "parse-options.h" -#include "diff.h" - -#define SEEN (1u<<0) -#define MAX_TAGS (FLAG_BITS - 1) - -static const char * const describe_usage[] = { - "git describe [options] *", - "git describe [options] --dirty", - NULL -}; - -static int debug; /* Display lots of verbose info */ -static int all; /* Any valid ref can be used */ -static int tags; /* Allow lightweight tags */ -static int longformat; -static int abbrev = DEFAULT_ABBREV; -static int max_candidates = 10; -static int found_names; -static const char *pattern; -static int always; -static const char *dirty; - -/* diff-index command arguments to check if working tree is dirty. */ -static const char *diff_index_args[] = { - "diff-index", "--quiet", "HEAD", "--", NULL -}; - - -struct commit_name { - struct tag *tag; - int prio; /* annotated tag = 2, tag = 1, head = 0 */ - unsigned char sha1[20]; - char path[FLEX_ARRAY]; /* more */ -}; -static const char *prio_names[] = { - "head", "lightweight", "annotated", -}; - -static void add_to_known_names(const char *path, - struct commit *commit, - int prio, - const unsigned char *sha1) -{ - struct commit_name *e = commit->util; - if (!e || e->prio < prio) { - size_t len = strlen(path)+1; - free(e); - e = xmalloc(sizeof(struct commit_name) + len); - e->tag = NULL; - e->prio = prio; - hashcpy(e->sha1, sha1); - memcpy(e->path, path, len); - commit->util = e; - } - found_names = 1; -} - -static int get_name(const char *path, const unsigned char *sha1, int flag, void *cb_data) -{ - int might_be_tag = !prefixcmp(path, "refs/tags/"); - struct commit *commit; - struct object *object; - unsigned char peeled[20]; - int is_tag, prio; - - if (!all && !might_be_tag) - return 0; - - if (!peel_ref(path, peeled) && !is_null_sha1(peeled)) { - commit = lookup_commit_reference_gently(peeled, 1); - if (!commit) - return 0; - is_tag = !!hashcmp(sha1, commit->object.sha1); - } else { - commit = lookup_commit_reference_gently(sha1, 1); - object = parse_object(sha1); - if (!commit || !object) - return 0; - is_tag = object->type == OBJ_TAG; - } - - /* If --all, then any refs are used. - * If --tags, then any tags are used. - * Otherwise only annotated tags are used. - */ - if (might_be_tag) { - if (is_tag) - prio = 2; - else - prio = 1; - - if (pattern && fnmatch(pattern, path + 10, 0)) - prio = 0; - } - else - prio = 0; - - if (!all) { - if (!prio) - return 0; - } - add_to_known_names(all ? path + 5 : path + 10, commit, prio, sha1); - return 0; -} - -struct possible_tag { - struct commit_name *name; - int depth; - int found_order; - unsigned flag_within; -}; - -static int compare_pt(const void *a_, const void *b_) -{ - struct possible_tag *a = (struct possible_tag *)a_; - struct possible_tag *b = (struct possible_tag *)b_; - if (a->depth != b->depth) - return a->depth - b->depth; - if (a->found_order != b->found_order) - return a->found_order - b->found_order; - return 0; -} - -static unsigned long finish_depth_computation( - struct commit_list **list, - struct possible_tag *best) -{ - unsigned long seen_commits = 0; - while (*list) { - struct commit *c = pop_commit(list); - struct commit_list *parents = c->parents; - seen_commits++; - if (c->object.flags & best->flag_within) { - struct commit_list *a = *list; - while (a) { - struct commit *i = a->item; - if (!(i->object.flags & best->flag_within)) - break; - a = a->next; - } - if (!a) - break; - } else - best->depth++; - while (parents) { - struct commit *p = parents->item; - parse_commit(p); - if (!(p->object.flags & SEEN)) - insert_by_date(p, list); - p->object.flags |= c->object.flags; - parents = parents->next; - } - } - return seen_commits; -} - -static void display_name(struct commit_name *n) -{ - if (n->prio == 2 && !n->tag) { - n->tag = lookup_tag(n->sha1); - if (!n->tag || parse_tag(n->tag) || !n->tag->tag) - die("annotated tag %s not available", n->path); - if (strcmp(n->tag->tag, all ? n->path + 5 : n->path)) - warning("tag '%s' is really '%s' here", n->tag->tag, n->path); - } - - if (n->tag) - printf("%s", n->tag->tag); - else - printf("%s", n->path); -} - -static void show_suffix(int depth, const unsigned char *sha1) -{ - printf("-%d-g%s", depth, find_unique_abbrev(sha1, abbrev)); -} - -static void describe(const char *arg, int last_one) -{ - unsigned char sha1[20]; - struct commit *cmit, *gave_up_on = NULL; - struct commit_list *list; - struct commit_name *n; - struct possible_tag all_matches[MAX_TAGS]; - unsigned int match_cnt = 0, annotated_cnt = 0, cur_match; - unsigned long seen_commits = 0; - unsigned int unannotated_cnt = 0; - - if (get_sha1(arg, sha1)) - die("Not a valid object name %s", arg); - cmit = lookup_commit_reference(sha1); - if (!cmit) - die("%s is not a valid '%s' object", arg, commit_type); - - n = cmit->util; - if (n && (tags || all || n->prio == 2)) { - /* - * Exact match to an existing ref. - */ - display_name(n); - if (longformat) - show_suffix(0, n->tag ? n->tag->tagged->sha1 : sha1); - if (dirty) - printf("%s", dirty); - printf("\n"); - return; - } - - if (!max_candidates) - die("no tag exactly matches '%s'", sha1_to_hex(cmit->object.sha1)); - if (debug) - fprintf(stderr, "searching to describe %s\n", arg); - - list = NULL; - cmit->object.flags = SEEN; - commit_list_insert(cmit, &list); - while (list) { - struct commit *c = pop_commit(&list); - struct commit_list *parents = c->parents; - seen_commits++; - n = c->util; - if (n) { - if (!tags && !all && n->prio < 2) { - unannotated_cnt++; - } else if (match_cnt < max_candidates) { - struct possible_tag *t = &all_matches[match_cnt++]; - t->name = n; - t->depth = seen_commits - 1; - t->flag_within = 1u << match_cnt; - t->found_order = match_cnt; - c->object.flags |= t->flag_within; - if (n->prio == 2) - annotated_cnt++; - } - else { - gave_up_on = c; - break; - } - } - for (cur_match = 0; cur_match < match_cnt; cur_match++) { - struct possible_tag *t = &all_matches[cur_match]; - if (!(c->object.flags & t->flag_within)) - t->depth++; - } - if (annotated_cnt && !list) { - if (debug) - fprintf(stderr, "finished search at %s\n", - sha1_to_hex(c->object.sha1)); - break; - } - while (parents) { - struct commit *p = parents->item; - parse_commit(p); - if (!(p->object.flags & SEEN)) - insert_by_date(p, &list); - p->object.flags |= c->object.flags; - parents = parents->next; - } - } - - if (!match_cnt) { - const unsigned char *sha1 = cmit->object.sha1; - if (always) { - printf("%s", find_unique_abbrev(sha1, abbrev)); - if (dirty) - printf("%s", dirty); - printf("\n"); - return; - } - if (unannotated_cnt) - die("No annotated tags can describe '%s'.\n" - "However, there were unannotated tags: try --tags.", - sha1_to_hex(sha1)); - else - die("No tags can describe '%s'.\n" - "Try --always, or create some tags.", - sha1_to_hex(sha1)); - } - - qsort(all_matches, match_cnt, sizeof(all_matches[0]), compare_pt); - - if (gave_up_on) { - insert_by_date(gave_up_on, &list); - seen_commits--; - } - seen_commits += finish_depth_computation(&list, &all_matches[0]); - free_commit_list(list); - - if (debug) { - for (cur_match = 0; cur_match < match_cnt; cur_match++) { - struct possible_tag *t = &all_matches[cur_match]; - fprintf(stderr, " %-11s %8d %s\n", - prio_names[t->name->prio], - t->depth, t->name->path); - } - fprintf(stderr, "traversed %lu commits\n", seen_commits); - if (gave_up_on) { - fprintf(stderr, - "more than %i tags found; listed %i most recent\n" - "gave up search at %s\n", - max_candidates, max_candidates, - sha1_to_hex(gave_up_on->object.sha1)); - } - } - - display_name(all_matches[0].name); - if (abbrev) - show_suffix(all_matches[0].depth, cmit->object.sha1); - if (dirty) - printf("%s", dirty); - printf("\n"); - - if (!last_one) - clear_commit_marks(cmit, -1); -} - -int cmd_describe(int argc, const char **argv, const char *prefix) -{ - int contains = 0; - struct option options[] = { - OPT_BOOLEAN(0, "contains", &contains, "find the tag that comes after the commit"), - OPT_BOOLEAN(0, "debug", &debug, "debug search strategy on stderr"), - OPT_BOOLEAN(0, "all", &all, "use any ref in .git/refs"), - OPT_BOOLEAN(0, "tags", &tags, "use any tag in .git/refs/tags"), - OPT_BOOLEAN(0, "long", &longformat, "always use long format"), - OPT__ABBREV(&abbrev), - OPT_SET_INT(0, "exact-match", &max_candidates, - "only output exact matches", 0), - OPT_INTEGER(0, "candidates", &max_candidates, - "consider most recent tags (default: 10)"), - OPT_STRING(0, "match", &pattern, "pattern", - "only consider tags matching "), - OPT_BOOLEAN(0, "always", &always, - "show abbreviated commit object as fallback"), - {OPTION_STRING, 0, "dirty", &dirty, "mark", - "append on dirty working tree (default: \"-dirty\")", - PARSE_OPT_OPTARG, NULL, (intptr_t) "-dirty"}, - OPT_END(), - }; - - argc = parse_options(argc, argv, prefix, options, describe_usage, 0); - if (max_candidates < 0) - max_candidates = 0; - else if (max_candidates > MAX_TAGS) - max_candidates = MAX_TAGS; - - save_commit_buffer = 0; - - if (longformat && abbrev == 0) - die("--long is incompatible with --abbrev=0"); - - if (contains) { - const char **args = xmalloc((7 + argc) * sizeof(char *)); - int i = 0; - args[i++] = "name-rev"; - args[i++] = "--name-only"; - args[i++] = "--no-undefined"; - if (always) - args[i++] = "--always"; - if (!all) { - args[i++] = "--tags"; - if (pattern) { - char *s = xmalloc(strlen("--refs=refs/tags/") + strlen(pattern) + 1); - sprintf(s, "--refs=refs/tags/%s", pattern); - args[i++] = s; - } - } - memcpy(args + i, argv, argc * sizeof(char *)); - args[i + argc] = NULL; - return cmd_name_rev(i + argc, args, prefix); - } - - for_each_ref(get_name, NULL); - if (!found_names && !always) - die("No names found, cannot describe anything."); - - if (argc == 0) { - if (dirty && !cmd_diff_index(ARRAY_SIZE(diff_index_args) - 1, diff_index_args, prefix)) - dirty = NULL; - describe("HEAD", 1); - } else if (dirty) { - die("--dirty is incompatible with committishes"); - } else { - while (argc-- > 0) { - describe(*argv++, argc == 0); - } - } - return 0; -} diff --git a/builtin-diff-files.c b/builtin-diff-files.c deleted file mode 100644 index 5b64011..0000000 --- a/builtin-diff-files.c +++ /dev/null @@ -1,68 +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" -#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, const char *prefix) -{ - struct rev_info rev; - int result; - unsigned options = 0; - - init_revisions(&rev, prefix); - git_config(git_diff_basic_config, NULL); /* no "diff" UI options */ - 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")) - options |= DIFF_SILENT_ON_REMOVED; - else - usage(diff_files_usage); - argv++; argc--; - } - if (!rev.diffopt.output_format) - rev.diffopt.output_format = DIFF_FORMAT_RAW; - - /* - * Make sure there are NO revision (i.e. pending object) parameter, - * rev.max_count is reasonable (0 <= n <= 3), and - * there is no other revision filtering parameters. - */ - if (rev.pending.nr || - rev.min_age != -1 || rev.max_age != -1 || - 3 < rev.max_count) - usage(diff_files_usage); - - /* - * "diff-files --base -p" should not combine merges because it - * was not asked to. "diff-files -c -p" should not densify - * (the user should ask with "diff-files --cc" explicitly). - */ - if (rev.max_count == -1 && !rev.combine_merges && - (rev.diffopt.output_format & DIFF_FORMAT_PATCH)) - rev.combine_merges = rev.dense_combined_merges = 1; - - if (read_cache_preload(rev.diffopt.paths) < 0) { - perror("read_cache_preload"); - return -1; - } - result = run_diff_files(&rev, options); - return diff_result_code(&rev.diffopt, result); -} diff --git a/builtin-diff-index.c b/builtin-diff-index.c deleted file mode 100644 index 0483749..0000000 --- a/builtin-diff-index.c +++ /dev/null @@ -1,50 +0,0 @@ -#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, const char *prefix) -{ - struct rev_info rev; - int cached = 0; - int i; - int result; - - init_revisions(&rev, prefix); - git_config(git_diff_basic_config, NULL); /* no "diff" UI options */ - 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); - } - if (!rev.diffopt.output_format) - rev.diffopt.output_format = DIFF_FORMAT_RAW; - - /* - * Make sure there is one revision (i.e. pending object), - * and there is no revision filtering parameters. - */ - if (rev.pending.nr != 1 || - rev.max_count != -1 || rev.min_age != -1 || rev.max_age != -1) - usage(diff_cache_usage); - if (!cached) - setup_work_tree(); - if (read_cache() < 0) { - perror("read_cache"); - return -1; - } - result = run_diff_index(&rev, cached); - return diff_result_code(&rev.diffopt, result); -} diff --git a/builtin-diff-tree.c b/builtin-diff-tree.c deleted file mode 100644 index 2380c21..0000000 --- a/builtin-diff-tree.c +++ /dev/null @@ -1,170 +0,0 @@ -#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); -} - -/* Diff one or more commits. */ -static int stdin_diff_commit(struct commit *commit, char *line, int len) -{ - unsigned char sha1[20]; - 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); -} - -/* Diff two trees. */ -static int stdin_diff_trees(struct tree *tree1, char *line, int len) -{ - unsigned char sha1[20]; - struct tree *tree2; - if (len != 82 || !isspace(line[40]) || get_sha1_hex(line + 41, sha1)) - return error("Need exactly two trees, separated by a space"); - tree2 = lookup_tree(sha1); - if (!tree2 || parse_tree(tree2)) - return -1; - printf("%s %s\n", sha1_to_hex(tree1->object.sha1), - sha1_to_hex(tree2->object.sha1)); - diff_tree_sha1(tree1->object.sha1, tree2->object.sha1, - "", &log_tree_opt.diffopt); - log_tree_diff_flush(&log_tree_opt); - return 0; -} - -static int diff_tree_stdin(char *line) -{ - int len = strlen(line); - unsigned char sha1[20]; - struct object *obj; - - if (!len || line[len-1] != '\n') - return -1; - line[len-1] = 0; - if (get_sha1_hex(line, sha1)) - return -1; - obj = lookup_unknown_object(sha1); - if (!obj || !obj->parsed) - obj = parse_object(sha1); - if (!obj) - return -1; - if (obj->type == OBJ_COMMIT) - return stdin_diff_commit((struct commit *)obj, line, len); - if (obj->type == OBJ_TREE) - return stdin_diff_trees((struct tree *)obj, line, len); - error("Object %s is a %s, not a commit or tree", - sha1_to_hex(sha1), typename(obj->type)); - return -1; -} - -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, const char *prefix) -{ - int nr_sha1; - char line[1000]; - struct object *tree1, *tree2; - static struct rev_info *opt = &log_tree_opt; - int read_stdin = 0; - - init_revisions(opt, prefix); - git_config(git_diff_basic_config, NULL); /* no "diff" UI options */ - opt->abbrev = 0; - opt->diff = 1; - opt->disable_stdin = 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); - } - - if (!opt->diffopt.output_format) - opt->diffopt.output_format = DIFF_FORMAT_RAW; - - /* - * NOTE! We expect "a ^b" to be equal to "a..b", so we - * reverse the order of the objects if the second one - * is marked UNINTERESTING. - */ - nr_sha1 = opt->pending.nr; - switch (nr_sha1) { - case 0: - if (!read_stdin) - usage(diff_tree_usage); - break; - case 1: - tree1 = opt->pending.objects[0].item; - diff_tree_commit_sha1(tree1->sha1); - break; - case 2: - tree1 = opt->pending.objects[0].item; - tree2 = opt->pending.objects[1].item; - if (tree2->flags & UNINTERESTING) { - struct object *tmp = tree2; - tree2 = tree1; - tree1 = tmp; - } - diff_tree_sha1(tree1->sha1, - tree2->sha1, - "", &opt->diffopt); - log_tree_diff_flush(opt); - break; - } - - if (read_stdin) { - if (opt->diffopt.detect_rename) - opt->diffopt.setup |= (DIFF_SETUP_USE_SIZE_CACHE | - DIFF_SETUP_USE_CACHE); - while (fgets(line, sizeof(line), stdin)) { - unsigned char sha1[20]; - - if (get_sha1_hex(line, sha1)) { - fputs(line, stdout); - fflush(stdout); - } - else - diff_tree_stdin(line); - } - } - - return diff_result_code(&opt->diffopt, 0); -} diff --git a/builtin-diff.c b/builtin-diff.c deleted file mode 100644 index ffcdd05..0000000 --- a/builtin-diff.c +++ /dev/null @@ -1,425 +0,0 @@ -/* - * Builtin "git diff" - * - * Copyright (c) 2006 Junio C Hamano - */ -#include "cache.h" -#include "color.h" -#include "commit.h" -#include "blob.h" -#include "tag.h" -#include "diff.h" -#include "diffcore.h" -#include "revision.h" -#include "log-tree.h" -#include "builtin.h" - -struct blobinfo { - unsigned char sha1[20]; - const char *name; - unsigned mode; -}; - -static const char builtin_diff_usage[] = -"git diff {0,2} -- *"; - -static void stuff_change(struct diff_options *opt, - unsigned old_mode, unsigned new_mode, - const unsigned char *old_sha1, - const unsigned char *new_sha1, - const char *old_name, - const char *new_name) -{ - struct diff_filespec *one, *two; - - if (!is_null_sha1(old_sha1) && !is_null_sha1(new_sha1) && - !hashcmp(old_sha1, new_sha1) && (old_mode == new_mode)) - return; - - if (DIFF_OPT_TST(opt, REVERSE_DIFF)) { - unsigned tmp; - const unsigned char *tmp_u; - const char *tmp_c; - tmp = old_mode; old_mode = new_mode; new_mode = tmp; - tmp_u = old_sha1; old_sha1 = new_sha1; new_sha1 = tmp_u; - tmp_c = old_name; old_name = new_name; new_name = tmp_c; - } - - if (opt->prefix && - (strncmp(old_name, opt->prefix, opt->prefix_length) || - strncmp(new_name, opt->prefix, opt->prefix_length))) - return; - - one = alloc_filespec(old_name); - two = alloc_filespec(new_name); - fill_filespec(one, old_sha1, old_mode); - fill_filespec(two, new_sha1, new_mode); - - diff_queue(&diff_queued_diff, one, two); -} - -static int builtin_diff_b_f(struct rev_info *revs, - int argc, const char **argv, - struct blobinfo *blob, - const char *path) -{ - /* Blob vs file in the working tree*/ - struct stat st; - - if (argc > 1) - usage(builtin_diff_usage); - - if (lstat(path, &st)) - die_errno("failed to stat '%s'", path); - if (!(S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))) - die("'%s': not a regular file or symlink", path); - - diff_set_mnemonic_prefix(&revs->diffopt, "o/", "w/"); - - if (blob[0].mode == S_IFINVALID) - blob[0].mode = canon_mode(st.st_mode); - - stuff_change(&revs->diffopt, - blob[0].mode, canon_mode(st.st_mode), - blob[0].sha1, null_sha1, - path, path); - diffcore_std(&revs->diffopt); - diff_flush(&revs->diffopt); - return 0; -} - -static int builtin_diff_blobs(struct rev_info *revs, - int argc, const char **argv, - struct blobinfo *blob) -{ - unsigned mode = canon_mode(S_IFREG | 0644); - - if (argc > 1) - usage(builtin_diff_usage); - - if (blob[0].mode == S_IFINVALID) - blob[0].mode = mode; - - if (blob[1].mode == S_IFINVALID) - blob[1].mode = mode; - - stuff_change(&revs->diffopt, - blob[0].mode, blob[1].mode, - blob[0].sha1, blob[1].sha1, - blob[0].name, blob[1].name); - diffcore_std(&revs->diffopt); - diff_flush(&revs->diffopt); - return 0; -} - -static int builtin_diff_index(struct rev_info *revs, - int argc, const char **argv) -{ - int cached = 0; - while (1 < argc) { - const char *arg = argv[1]; - if (!strcmp(arg, "--cached") || !strcmp(arg, "--staged")) - cached = 1; - else - usage(builtin_diff_usage); - argv++; argc--; - } - if (!cached) - setup_work_tree(); - /* - * Make sure there is one revision (i.e. pending object), - * and there is no revision filtering parameters. - */ - if (revs->pending.nr != 1 || - revs->max_count != -1 || revs->min_age != -1 || - revs->max_age != -1) - usage(builtin_diff_usage); - if (read_cache_preload(revs->diffopt.paths) < 0) { - perror("read_cache_preload"); - return -1; - } - return run_diff_index(revs, cached); -} - -static int builtin_diff_tree(struct rev_info *revs, - int argc, const char **argv, - struct object_array_entry *ent) -{ - const unsigned char *(sha1[2]); - int swap = 0; - - if (argc > 1) - usage(builtin_diff_usage); - - /* We saw two trees, ent[0] and ent[1]. - * if ent[1] is uninteresting, they are swapped - */ - if (ent[1].item->flags & UNINTERESTING) - swap = 1; - sha1[swap] = ent[0].item->sha1; - sha1[1-swap] = ent[1].item->sha1; - diff_tree_sha1(sha1[0], sha1[1], "", &revs->diffopt); - log_tree_diff_flush(revs); - return 0; -} - -static int builtin_diff_combined(struct rev_info *revs, - int argc, const char **argv, - struct object_array_entry *ent, - int ents) -{ - const unsigned char (*parent)[20]; - int i; - - if (argc > 1) - usage(builtin_diff_usage); - - if (!revs->dense_combined_merges && !revs->combine_merges) - revs->dense_combined_merges = revs->combine_merges = 1; - parent = xmalloc(ents * sizeof(*parent)); - for (i = 0; i < ents; i++) - hashcpy((unsigned char *)(parent + i), ent[i].item->sha1); - diff_tree_combined(parent[0], parent + 1, ents - 1, - revs->dense_combined_merges, revs); - return 0; -} - -static void refresh_index_quietly(void) -{ - struct lock_file *lock_file; - int fd; - - lock_file = xcalloc(1, sizeof(struct lock_file)); - fd = hold_locked_index(lock_file, 0); - if (fd < 0) - return; - discard_cache(); - read_cache(); - refresh_cache(REFRESH_QUIET|REFRESH_UNMERGED); - - if (active_cache_changed && - !write_cache(fd, active_cache, active_nr)) - commit_locked_index(lock_file); - - rollback_lock_file(lock_file); -} - -static int builtin_diff_files(struct rev_info *revs, int argc, const char **argv) -{ - int result; - unsigned int options = 0; - - while (1 < argc && argv[1][0] == '-') { - if (!strcmp(argv[1], "--base")) - revs->max_count = 1; - else if (!strcmp(argv[1], "--ours")) - revs->max_count = 2; - else if (!strcmp(argv[1], "--theirs")) - revs->max_count = 3; - else if (!strcmp(argv[1], "-q")) - options |= DIFF_SILENT_ON_REMOVED; - else if (!strcmp(argv[1], "-h")) - usage(builtin_diff_usage); - else - return error("invalid option: %s", argv[1]); - argv++; argc--; - } - - /* - * "diff --base" should not combine merges because it was not - * asked to. "diff -c" should not densify (if the user wants - * dense one, --cc can be explicitly asked for, or just rely - * on the default). - */ - if (revs->max_count == -1 && !revs->combine_merges && - (revs->diffopt.output_format & DIFF_FORMAT_PATCH)) - revs->combine_merges = revs->dense_combined_merges = 1; - - setup_work_tree(); - if (read_cache_preload(revs->diffopt.paths) < 0) { - perror("read_cache_preload"); - return -1; - } - result = run_diff_files(revs, options); - return diff_result_code(&revs->diffopt, result); -} - -int cmd_diff(int argc, const char **argv, const char *prefix) -{ - int i; - struct rev_info rev; - struct object_array_entry ent[100]; - int ents = 0, blobs = 0, paths = 0; - const char *path = NULL; - struct blobinfo blob[2]; - int nongit; - int result = 0; - - /* - * We could get N tree-ish in the rev.pending_objects list. - * Also there could be M blobs there, and P pathspecs. - * - * N=0, M=0: - * cache vs files (diff-files) - * N=0, M=2: - * compare two random blobs. P must be zero. - * N=0, M=1, P=1: - * compare a blob with a working tree file. - * - * N=1, M=0: - * tree vs cache (diff-index --cached) - * - * N=2, M=0: - * tree vs tree (diff-tree) - * - * N=0, M=0, P=2: - * compare two filesystem entities (aka --no-index). - * - * Other cases are errors. - */ - - prefix = setup_git_directory_gently(&nongit); - git_config(git_diff_ui_config, NULL); - - if (diff_use_color_default == -1) - diff_use_color_default = git_use_color_default; - - init_revisions(&rev, prefix); - - /* If this is a no-index diff, just run it and exit there. */ - diff_no_index(&rev, argc, argv, nongit, prefix); - - /* Otherwise, we are doing the usual "git" diff */ - rev.diffopt.skip_stat_unmatch = !!diff_auto_refresh_index; - - /* Default to let external and textconv be used */ - DIFF_OPT_SET(&rev.diffopt, ALLOW_EXTERNAL); - DIFF_OPT_SET(&rev.diffopt, ALLOW_TEXTCONV); - - if (nongit) - die("Not a git repository"); - argc = setup_revisions(argc, argv, &rev, NULL); - if (!rev.diffopt.output_format) { - rev.diffopt.output_format = DIFF_FORMAT_PATCH; - if (diff_setup_done(&rev.diffopt) < 0) - die("diff_setup_done failed"); - } - - DIFF_OPT_SET(&rev.diffopt, RECURSIVE); - - /* - * If the user asked for our exit code then don't start a - * pager or we would end up reporting its exit code instead. - */ - if (!DIFF_OPT_TST(&rev.diffopt, EXIT_WITH_STATUS) && - check_pager_config("diff") != 0) - setup_pager(); - - /* - * Do we have --cached and not have a pending object, then - * default to HEAD by hand. Eek. - */ - if (!rev.pending.nr) { - int i; - for (i = 1; i < argc; i++) { - const char *arg = argv[i]; - if (!strcmp(arg, "--")) - break; - else if (!strcmp(arg, "--cached") || - !strcmp(arg, "--staged")) { - add_head_to_pending(&rev); - if (!rev.pending.nr) - die("No HEAD commit to compare with (yet)"); - break; - } - } - } - - for (i = 0; i < rev.pending.nr; i++) { - struct object_array_entry *list = rev.pending.objects+i; - struct object *obj = list->item; - const char *name = list->name; - int flags = (obj->flags & UNINTERESTING); - if (!obj->parsed) - obj = parse_object(obj->sha1); - obj = deref_tag(obj, NULL, 0); - if (!obj) - die("invalid object '%s' given.", name); - if (obj->type == OBJ_COMMIT) - obj = &((struct commit *)obj)->tree->object; - if (obj->type == OBJ_TREE) { - if (ARRAY_SIZE(ent) <= ents) - die("more than %d trees given: '%s'", - (int) ARRAY_SIZE(ent), name); - obj->flags |= flags; - ent[ents].item = obj; - ent[ents].name = name; - ents++; - continue; - } - if (obj->type == OBJ_BLOB) { - if (2 <= blobs) - die("more than two blobs given: '%s'", name); - hashcpy(blob[blobs].sha1, obj->sha1); - blob[blobs].name = name; - blob[blobs].mode = list->mode; - blobs++; - continue; - - } - die("unhandled object '%s' given.", name); - } - if (rev.prune_data) { - const char **pathspec = rev.prune_data; - while (*pathspec) { - if (!path) - path = *pathspec; - paths++; - pathspec++; - } - } - - /* - * Now, do the arguments look reasonable? - */ - if (!ents) { - switch (blobs) { - case 0: - result = builtin_diff_files(&rev, argc, argv); - break; - case 1: - if (paths != 1) - usage(builtin_diff_usage); - result = builtin_diff_b_f(&rev, argc, argv, blob, path); - break; - case 2: - if (paths) - usage(builtin_diff_usage); - result = builtin_diff_blobs(&rev, argc, argv, blob); - break; - default: - usage(builtin_diff_usage); - } - } - else if (blobs) - usage(builtin_diff_usage); - else if (ents == 1) - result = builtin_diff_index(&rev, argc, argv); - else if (ents == 2) - result = builtin_diff_tree(&rev, argc, argv, ent); - else if ((ents == 3) && (ent[0].item->flags & UNINTERESTING)) { - /* diff A...B where there is one sane merge base between - * A and B. We have ent[0] == merge-base, ent[1] == A, - * and ent[2] == B. Show diff between the base and B. - */ - ent[1] = ent[2]; - result = builtin_diff_tree(&rev, argc, argv, ent); - } - else - result = builtin_diff_combined(&rev, argc, argv, - ent, ents); - result = diff_result_code(&rev.diffopt, result); - if (1 < rev.diffopt.skip_stat_unmatch) - refresh_index_quietly(); - return result; -} diff --git a/builtin-fast-export.c b/builtin-fast-export.c deleted file mode 100644 index b0a4029..0000000 --- a/builtin-fast-export.c +++ /dev/null @@ -1,633 +0,0 @@ -/* - * "git fast-export" builtin command - * - * Copyright (C) 2007 Johannes E. Schindelin - */ -#include "builtin.h" -#include "cache.h" -#include "commit.h" -#include "object.h" -#include "tag.h" -#include "diff.h" -#include "diffcore.h" -#include "log-tree.h" -#include "revision.h" -#include "decorate.h" -#include "string-list.h" -#include "utf8.h" -#include "parse-options.h" - -static const char *fast_export_usage[] = { - "git fast-export [rev-list-opts]", - NULL -}; - -static int progress; -static enum { ABORT, VERBATIM, WARN, STRIP } signed_tag_mode = ABORT; -static enum { ERROR, DROP, REWRITE } tag_of_filtered_mode = ABORT; -static int fake_missing_tagger; -static int no_data; - -static int parse_opt_signed_tag_mode(const struct option *opt, - const char *arg, int unset) -{ - if (unset || !strcmp(arg, "abort")) - signed_tag_mode = ABORT; - else if (!strcmp(arg, "verbatim") || !strcmp(arg, "ignore")) - signed_tag_mode = VERBATIM; - else if (!strcmp(arg, "warn")) - signed_tag_mode = WARN; - else if (!strcmp(arg, "strip")) - signed_tag_mode = STRIP; - else - return error("Unknown signed-tag mode: %s", arg); - return 0; -} - -static int parse_opt_tag_of_filtered_mode(const struct option *opt, - const char *arg, int unset) -{ - if (unset || !strcmp(arg, "abort")) - tag_of_filtered_mode = ABORT; - else if (!strcmp(arg, "drop")) - tag_of_filtered_mode = DROP; - else if (!strcmp(arg, "rewrite")) - tag_of_filtered_mode = REWRITE; - else - return error("Unknown tag-of-filtered mode: %s", arg); - return 0; -} - -static struct decoration idnums; -static uint32_t last_idnum; - -static int has_unshown_parent(struct commit *commit) -{ - struct commit_list *parent; - - for (parent = commit->parents; parent; parent = parent->next) - if (!(parent->item->object.flags & SHOWN) && - !(parent->item->object.flags & UNINTERESTING)) - return 1; - return 0; -} - -/* Since intptr_t is C99, we do not use it here */ -static inline uint32_t *mark_to_ptr(uint32_t mark) -{ - return ((uint32_t *)NULL) + mark; -} - -static inline uint32_t ptr_to_mark(void * mark) -{ - return (uint32_t *)mark - (uint32_t *)NULL; -} - -static inline void mark_object(struct object *object, uint32_t mark) -{ - add_decoration(&idnums, object, mark_to_ptr(mark)); -} - -static inline void mark_next_object(struct object *object) -{ - mark_object(object, ++last_idnum); -} - -static int get_object_mark(struct object *object) -{ - void *decoration = lookup_decoration(&idnums, object); - if (!decoration) - return 0; - return ptr_to_mark(decoration); -} - -static void show_progress(void) -{ - static int counter = 0; - if (!progress) - return; - if ((++counter % progress) == 0) - printf("progress %d objects\n", counter); -} - -static void handle_object(const unsigned char *sha1) -{ - unsigned long size; - enum object_type type; - char *buf; - struct object *object; - - if (no_data) - return; - - if (is_null_sha1(sha1)) - return; - - object = parse_object(sha1); - if (!object) - die ("Could not read blob %s", sha1_to_hex(sha1)); - - if (object->flags & SHOWN) - return; - - buf = read_sha1_file(sha1, &type, &size); - if (!buf) - die ("Could not read blob %s", sha1_to_hex(sha1)); - - mark_next_object(object); - - printf("blob\nmark :%"PRIu32"\ndata %lu\n", last_idnum, size); - if (size && fwrite(buf, size, 1, stdout) != 1) - die_errno ("Could not write blob '%s'", sha1_to_hex(sha1)); - printf("\n"); - - show_progress(); - - object->flags |= SHOWN; - free(buf); -} - -static void show_filemodify(struct diff_queue_struct *q, - struct diff_options *options, void *data) -{ - int i; - for (i = 0; i < q->nr; i++) { - struct diff_filespec *ospec = q->queue[i]->one; - struct diff_filespec *spec = q->queue[i]->two; - - switch (q->queue[i]->status) { - case DIFF_STATUS_DELETED: - printf("D %s\n", spec->path); - break; - - case DIFF_STATUS_COPIED: - case DIFF_STATUS_RENAMED: - printf("%c \"%s\" \"%s\"\n", q->queue[i]->status, - ospec->path, spec->path); - - if (!hashcmp(ospec->sha1, spec->sha1) && - ospec->mode == spec->mode) - break; - /* fallthrough */ - - case DIFF_STATUS_TYPE_CHANGED: - case DIFF_STATUS_MODIFIED: - case DIFF_STATUS_ADDED: - /* - * Links refer to objects in another repositories; - * output the SHA-1 verbatim. - */ - if (no_data || S_ISGITLINK(spec->mode)) - printf("M %06o %s %s\n", spec->mode, - sha1_to_hex(spec->sha1), spec->path); - else { - struct object *object = lookup_object(spec->sha1); - printf("M %06o :%d %s\n", spec->mode, - get_object_mark(object), spec->path); - } - break; - - default: - die("Unexpected comparison status '%c' for %s, %s", - q->queue[i]->status, - ospec->path ? ospec->path : "none", - spec->path ? spec->path : "none"); - } - } -} - -static const char *find_encoding(const char *begin, const char *end) -{ - const char *needle = "\nencoding "; - char *bol, *eol; - - bol = memmem(begin, end ? end - begin : strlen(begin), - needle, strlen(needle)); - if (!bol) - return git_commit_encoding; - bol += strlen(needle); - eol = strchrnul(bol, '\n'); - *eol = '\0'; - return bol; -} - -static void handle_commit(struct commit *commit, struct rev_info *rev) -{ - int saved_output_format = rev->diffopt.output_format; - const char *author, *author_end, *committer, *committer_end; - const char *encoding, *message; - char *reencoded = NULL; - struct commit_list *p; - int i; - - rev->diffopt.output_format = DIFF_FORMAT_CALLBACK; - - parse_commit(commit); - author = strstr(commit->buffer, "\nauthor "); - if (!author) - die ("Could not find author in commit %s", - sha1_to_hex(commit->object.sha1)); - author++; - author_end = strchrnul(author, '\n'); - committer = strstr(author_end, "\ncommitter "); - if (!committer) - die ("Could not find committer in commit %s", - sha1_to_hex(commit->object.sha1)); - committer++; - committer_end = strchrnul(committer, '\n'); - message = strstr(committer_end, "\n\n"); - encoding = find_encoding(committer_end, message); - if (message) - message += 2; - - if (commit->parents && - get_object_mark(&commit->parents->item->object) != 0) { - parse_commit(commit->parents->item); - diff_tree_sha1(commit->parents->item->tree->object.sha1, - commit->tree->object.sha1, "", &rev->diffopt); - } - else - diff_root_tree_sha1(commit->tree->object.sha1, - "", &rev->diffopt); - - /* Export the referenced blobs, and remember the marks. */ - for (i = 0; i < diff_queued_diff.nr; i++) - if (!S_ISGITLINK(diff_queued_diff.queue[i]->two->mode)) - handle_object(diff_queued_diff.queue[i]->two->sha1); - - mark_next_object(&commit->object); - if (!is_encoding_utf8(encoding)) - reencoded = reencode_string(message, "UTF-8", encoding); - if (!commit->parents) - printf("reset %s\n", (const char*)commit->util); - printf("commit %s\nmark :%"PRIu32"\n%.*s\n%.*s\ndata %u\n%s", - (const char *)commit->util, last_idnum, - (int)(author_end - author), author, - (int)(committer_end - committer), committer, - (unsigned)(reencoded - ? strlen(reencoded) : message - ? strlen(message) : 0), - reencoded ? reencoded : message ? message : ""); - free(reencoded); - - for (i = 0, p = commit->parents; p; p = p->next) { - int mark = get_object_mark(&p->item->object); - if (!mark) - continue; - if (i == 0) - printf("from :%d\n", mark); - else - printf("merge :%d\n", mark); - i++; - } - - log_tree_diff_flush(rev); - rev->diffopt.output_format = saved_output_format; - - printf("\n"); - - show_progress(); -} - -static void handle_tail(struct object_array *commits, struct rev_info *revs) -{ - struct commit *commit; - while (commits->nr) { - commit = (struct commit *)commits->objects[commits->nr - 1].item; - if (has_unshown_parent(commit)) - return; - handle_commit(commit, revs); - commits->nr--; - } -} - -static void handle_tag(const char *name, struct tag *tag) -{ - unsigned long size; - enum object_type type; - char *buf; - const char *tagger, *tagger_end, *message; - size_t message_size = 0; - struct object *tagged; - int tagged_mark; - struct commit *p; - - /* Trees have no identifer in fast-export output, thus we have no way - * to output tags of trees, tags of tags of trees, etc. Simply omit - * such tags. - */ - tagged = tag->tagged; - while (tagged->type == OBJ_TAG) { - tagged = ((struct tag *)tagged)->tagged; - } - if (tagged->type == OBJ_TREE) { - warning("Omitting tag %s,\nsince tags of trees (or tags of tags of trees, etc.) are not supported.", - sha1_to_hex(tag->object.sha1)); - return; - } - - buf = read_sha1_file(tag->object.sha1, &type, &size); - if (!buf) - die ("Could not read tag %s", sha1_to_hex(tag->object.sha1)); - message = memmem(buf, size, "\n\n", 2); - if (message) { - message += 2; - message_size = strlen(message); - } - tagger = memmem(buf, message ? message - buf : size, "\ntagger ", 8); - if (!tagger) { - if (fake_missing_tagger) - tagger = "tagger Unspecified Tagger " - " 0 +0000"; - else - tagger = ""; - tagger_end = tagger + strlen(tagger); - } else { - tagger++; - tagger_end = strchrnul(tagger, '\n'); - } - - /* handle signed tags */ - if (message) { - const char *signature = strstr(message, - "\n-----BEGIN PGP SIGNATURE-----\n"); - if (signature) - switch(signed_tag_mode) { - case ABORT: - die ("Encountered signed tag %s; use " - "--signed-tag= to handle it.", - sha1_to_hex(tag->object.sha1)); - case WARN: - warning ("Exporting signed tag %s", - sha1_to_hex(tag->object.sha1)); - /* fallthru */ - case VERBATIM: - break; - case STRIP: - message_size = signature + 1 - message; - break; - } - } - - /* handle tag->tagged having been filtered out due to paths specified */ - tagged = tag->tagged; - tagged_mark = get_object_mark(tagged); - if (!tagged_mark) { - switch(tag_of_filtered_mode) { - case ABORT: - die ("Tag %s tags unexported object; use " - "--tag-of-filtered-object= to handle it.", - sha1_to_hex(tag->object.sha1)); - case DROP: - /* Ignore this tag altogether */ - return; - case REWRITE: - if (tagged->type != OBJ_COMMIT) { - die ("Tag %s tags unexported %s!", - sha1_to_hex(tag->object.sha1), - typename(tagged->type)); - } - p = (struct commit *)tagged; - for (;;) { - if (p->parents && p->parents->next) - break; - if (p->object.flags & UNINTERESTING) - break; - if (!(p->object.flags & TREESAME)) - break; - if (!p->parents) - die ("Can't find replacement commit for tag %s\n", - sha1_to_hex(tag->object.sha1)); - p = p->parents->item; - } - tagged_mark = get_object_mark(&p->object); - } - } - - if (!prefixcmp(name, "refs/tags/")) - name += 10; - printf("tag %s\nfrom :%d\n%.*s%sdata %d\n%.*s\n", - name, tagged_mark, - (int)(tagger_end - tagger), tagger, - tagger == tagger_end ? "" : "\n", - (int)message_size, (int)message_size, message ? message : ""); -} - -static void get_tags_and_duplicates(struct object_array *pending, - struct string_list *extra_refs) -{ - struct tag *tag; - int i; - - for (i = 0; i < pending->nr; i++) { - struct object_array_entry *e = pending->objects + i; - unsigned char sha1[20]; - struct commit *commit = commit; - char *full_name; - - if (dwim_ref(e->name, strlen(e->name), sha1, &full_name) != 1) - continue; - - switch (e->item->type) { - case OBJ_COMMIT: - commit = (struct commit *)e->item; - break; - case OBJ_TAG: - tag = (struct tag *)e->item; - - /* handle nested tags */ - while (tag && tag->object.type == OBJ_TAG) { - parse_object(tag->object.sha1); - string_list_append(full_name, extra_refs)->util = tag; - tag = (struct tag *)tag->tagged; - } - if (!tag) - die ("Tag %s points nowhere?", e->name); - switch(tag->object.type) { - case OBJ_COMMIT: - commit = (struct commit *)tag; - break; - case OBJ_BLOB: - handle_object(tag->object.sha1); - continue; - default: /* OBJ_TAG (nested tags) is already handled */ - warning("Tag points to object of unexpected type %s, skipping.", - typename(tag->object.type)); - continue; - } - break; - default: - warning("%s: Unexpected object of type %s, skipping.", - e->name, - typename(e->item->type)); - continue; - } - if (commit->util) - /* more than one name for the same object */ - string_list_append(full_name, extra_refs)->util = commit; - else - commit->util = full_name; - } -} - -static void handle_tags_and_duplicates(struct string_list *extra_refs) -{ - struct commit *commit; - int i; - - for (i = extra_refs->nr - 1; i >= 0; i--) { - const char *name = extra_refs->items[i].string; - struct object *object = extra_refs->items[i].util; - switch (object->type) { - case OBJ_TAG: - handle_tag(name, (struct tag *)object); - break; - case OBJ_COMMIT: - /* create refs pointing to already seen commits */ - commit = (struct commit *)object; - printf("reset %s\nfrom :%d\n\n", name, - get_object_mark(&commit->object)); - show_progress(); - break; - } - } -} - -static void export_marks(char *file) -{ - unsigned int i; - uint32_t mark; - struct object_decoration *deco = idnums.hash; - FILE *f; - int e = 0; - - f = fopen(file, "w"); - if (!f) - error("Unable to open marks file %s for writing.", file); - - for (i = 0; i < idnums.size; i++) { - if (deco->base && deco->base->type == 1) { - mark = ptr_to_mark(deco->decoration); - if (fprintf(f, ":%"PRIu32" %s\n", mark, - sha1_to_hex(deco->base->sha1)) < 0) { - e = 1; - break; - } - } - deco++; - } - - e |= ferror(f); - e |= fclose(f); - if (e) - error("Unable to write marks file %s.", file); -} - -static void import_marks(char *input_file) -{ - char line[512]; - FILE *f = fopen(input_file, "r"); - if (!f) - die_errno("cannot read '%s'", input_file); - - while (fgets(line, sizeof(line), f)) { - uint32_t mark; - char *line_end, *mark_end; - unsigned char sha1[20]; - struct object *object; - - line_end = strchr(line, '\n'); - if (line[0] != ':' || !line_end) - die("corrupt mark line: %s", line); - *line_end = '\0'; - - mark = strtoumax(line + 1, &mark_end, 10); - if (!mark || mark_end == line + 1 - || *mark_end != ' ' || get_sha1(mark_end + 1, sha1)) - die("corrupt mark line: %s", line); - - object = parse_object(sha1); - if (!object) - die ("Could not read blob %s", sha1_to_hex(sha1)); - - if (object->flags & SHOWN) - error("Object %s already has a mark", sha1); - - mark_object(object, mark); - if (last_idnum < mark) - last_idnum = mark; - - object->flags |= SHOWN; - } - fclose(f); -} - -int cmd_fast_export(int argc, const char **argv, const char *prefix) -{ - struct rev_info revs; - struct object_array commits = { 0, 0, NULL }; - struct string_list extra_refs = { NULL, 0, 0, 0 }; - struct commit *commit; - char *export_filename = NULL, *import_filename = NULL; - struct option options[] = { - OPT_INTEGER(0, "progress", &progress, - "show progress after objects"), - OPT_CALLBACK(0, "signed-tags", &signed_tag_mode, "mode", - "select handling of signed tags", - parse_opt_signed_tag_mode), - OPT_CALLBACK(0, "tag-of-filtered-object", &tag_of_filtered_mode, "mode", - "select handling of tags that tag filtered objects", - parse_opt_tag_of_filtered_mode), - OPT_STRING(0, "export-marks", &export_filename, "FILE", - "Dump marks to this file"), - OPT_STRING(0, "import-marks", &import_filename, "FILE", - "Import marks from this file"), - OPT_BOOLEAN(0, "fake-missing-tagger", &fake_missing_tagger, - "Fake a tagger when tags lack one"), - { OPTION_NEGBIT, 0, "data", &no_data, NULL, - "Skip output of blob data", - PARSE_OPT_NOARG | PARSE_OPT_NEGHELP, NULL, 1 }, - OPT_END() - }; - - if (argc == 1) - usage_with_options (fast_export_usage, options); - - /* we handle encodings */ - git_config(git_default_config, NULL); - - init_revisions(&revs, prefix); - revs.topo_order = 1; - revs.show_source = 1; - revs.rewrite_parents = 1; - argc = setup_revisions(argc, argv, &revs, NULL); - argc = parse_options(argc, argv, prefix, options, fast_export_usage, 0); - if (argc > 1) - usage_with_options (fast_export_usage, options); - - if (import_filename) - import_marks(import_filename); - - get_tags_and_duplicates(&revs.pending, &extra_refs); - - if (prepare_revision_walk(&revs)) - die("revision walk setup failed"); - revs.diffopt.format_callback = show_filemodify; - DIFF_OPT_SET(&revs.diffopt, RECURSIVE); - while ((commit = get_revision(&revs))) { - if (has_unshown_parent(commit)) { - add_object_array(&commit->object, NULL, &commits); - } - else { - handle_commit(commit, &revs); - handle_tail(&commits, &revs); - } - } - - handle_tags_and_duplicates(&extra_refs); - - if (export_filename) - export_marks(export_filename); - - return 0; -} diff --git a/builtin-fetch-pack.c b/builtin-fetch-pack.c deleted file mode 100644 index dbd8b7b..0000000 --- a/builtin-fetch-pack.c +++ /dev/null @@ -1,976 +0,0 @@ -#include "cache.h" -#include "refs.h" -#include "pkt-line.h" -#include "commit.h" -#include "tag.h" -#include "exec_cmd.h" -#include "pack.h" -#include "sideband.h" -#include "fetch-pack.h" -#include "remote.h" -#include "run-command.h" - -static int transfer_unpack_limit = -1; -static int fetch_unpack_limit = -1; -static int unpack_limit = 100; -static int prefer_ofs_delta = 1; -static struct fetch_pack_args args = { - /* .uploadpack = */ "git-upload-pack", -}; - -static const char fetch_pack_usage[] = -"git fetch-pack [--all] [--quiet|-q] [--keep|-k] [--thin] [--include-tag] [--upload-pack=] [--depth=] [--no-progress] [-v] [:] [...]"; - -#define COMPLETE (1U << 0) -#define COMMON (1U << 1) -#define COMMON_REF (1U << 2) -#define SEEN (1U << 3) -#define POPPED (1U << 4) - -static int marked; - -/* - * After sending this many "have"s if we do not get any new ACK , we - * give up traversing our history. - */ -#define MAX_IN_VAIN 256 - -static struct commit_list *rev_list; -static int non_common_revs, multi_ack, use_sideband; - -static void rev_list_push(struct commit *commit, int mark) -{ - if (!(commit->object.flags & mark)) { - commit->object.flags |= mark; - - if (!(commit->object.parsed)) - if (parse_commit(commit)) - return; - - insert_by_date(commit, &rev_list); - - if (!(commit->object.flags & COMMON)) - non_common_revs++; - } -} - -static int rev_list_insert_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data) -{ - struct object *o = deref_tag(parse_object(sha1), path, 0); - - if (o && o->type == OBJ_COMMIT) - rev_list_push((struct commit *)o, SEEN); - - return 0; -} - -static int clear_marks(const char *path, const unsigned char *sha1, int flag, void *cb_data) -{ - struct object *o = deref_tag(parse_object(sha1), path, 0); - - if (o && o->type == OBJ_COMMIT) - clear_commit_marks((struct commit *)o, - COMMON | COMMON_REF | SEEN | POPPED); - return 0; -} - -/* - This function marks a rev and its ancestors as common. - In some cases, it is desirable to mark only the ancestors (for example - when only the server does not yet know that they are common). -*/ - -static void mark_common(struct commit *commit, - int ancestors_only, int dont_parse) -{ - if (commit != NULL && !(commit->object.flags & COMMON)) { - struct object *o = (struct object *)commit; - - if (!ancestors_only) - o->flags |= COMMON; - - if (!(o->flags & SEEN)) - rev_list_push(commit, SEEN); - else { - struct commit_list *parents; - - if (!ancestors_only && !(o->flags & POPPED)) - non_common_revs--; - if (!o->parsed && !dont_parse) - if (parse_commit(commit)) - return; - - for (parents = commit->parents; - parents; - parents = parents->next) - mark_common(parents->item, 0, dont_parse); - } - } -} - -/* - Get the next rev to send, ignoring the common. -*/ - -static const unsigned char *get_rev(void) -{ - struct commit *commit = NULL; - - while (commit == NULL) { - unsigned int mark; - struct commit_list *parents; - - if (rev_list == NULL || non_common_revs == 0) - return NULL; - - commit = rev_list->item; - if (!commit->object.parsed) - parse_commit(commit); - parents = commit->parents; - - commit->object.flags |= POPPED; - if (!(commit->object.flags & COMMON)) - non_common_revs--; - - if (commit->object.flags & COMMON) { - /* do not send "have", and ignore ancestors */ - commit = NULL; - mark = COMMON | SEEN; - } else if (commit->object.flags & COMMON_REF) - /* send "have", and ignore ancestors */ - mark = COMMON | SEEN; - else - /* send "have", also for its ancestors */ - mark = SEEN; - - while (parents) { - if (!(parents->item->object.flags & SEEN)) - rev_list_push(parents->item, mark); - if (mark & COMMON) - mark_common(parents->item, 1, 0); - parents = parents->next; - } - - rev_list = rev_list->next; - } - - return commit->object.sha1; -} - -enum ack_type { - NAK = 0, - ACK, - ACK_continue, - ACK_common, - ACK_ready -}; - -static void consume_shallow_list(int fd) -{ - if (args.stateless_rpc && args.depth > 0) { - /* If we sent a depth we will get back "duplicate" - * shallow and unshallow commands every time there - * is a block of have lines exchanged. - */ - char line[1000]; - while (packet_read_line(fd, line, sizeof(line))) { - if (!prefixcmp(line, "shallow ")) - continue; - if (!prefixcmp(line, "unshallow ")) - continue; - die("git fetch-pack: expected shallow list"); - } - } -} - -static enum ack_type get_ack(int fd, unsigned char *result_sha1) -{ - static char line[1000]; - int len = packet_read_line(fd, line, sizeof(line)); - - if (!len) - die("git fetch-pack: expected ACK/NAK, got EOF"); - if (line[len-1] == '\n') - line[--len] = 0; - if (!strcmp(line, "NAK")) - return NAK; - if (!prefixcmp(line, "ACK ")) { - if (!get_sha1_hex(line+4, result_sha1)) { - if (strstr(line+45, "continue")) - return ACK_continue; - if (strstr(line+45, "common")) - return ACK_common; - if (strstr(line+45, "ready")) - return ACK_ready; - return ACK; - } - } - die("git fetch_pack: expected ACK/NAK, got '%s'", line); -} - -static void send_request(int fd, struct strbuf *buf) -{ - if (args.stateless_rpc) { - send_sideband(fd, -1, buf->buf, buf->len, LARGE_PACKET_MAX); - packet_flush(fd); - } else - safe_write(fd, buf->buf, buf->len); -} - -static int find_common(int fd[2], unsigned char *result_sha1, - struct ref *refs) -{ - int fetching; - int count = 0, flushes = 0, retval; - const unsigned char *sha1; - unsigned in_vain = 0; - int got_continue = 0; - struct strbuf req_buf = STRBUF_INIT; - size_t state_len = 0; - - if (args.stateless_rpc && multi_ack == 1) - die("--stateless-rpc requires multi_ack_detailed"); - if (marked) - for_each_ref(clear_marks, NULL); - marked = 1; - - for_each_ref(rev_list_insert_ref, NULL); - - fetching = 0; - for ( ; refs ; refs = refs->next) { - unsigned char *remote = refs->old_sha1; - const char *remote_hex; - struct object *o; - - /* - * If that object is complete (i.e. it is an ancestor of a - * local ref), we tell them we have it but do not have to - * tell them about its ancestors, which they already know - * about. - * - * We use lookup_object here because we are only - * interested in the case we *know* the object is - * reachable and we have already scanned it. - */ - if (((o = lookup_object(remote)) != NULL) && - (o->flags & COMPLETE)) { - continue; - } - - remote_hex = sha1_to_hex(remote); - if (!fetching) { - struct strbuf c = STRBUF_INIT; - if (multi_ack == 2) strbuf_addstr(&c, " multi_ack_detailed"); - if (multi_ack == 1) strbuf_addstr(&c, " multi_ack"); - if (use_sideband == 2) strbuf_addstr(&c, " side-band-64k"); - if (use_sideband == 1) strbuf_addstr(&c, " side-band"); - if (args.use_thin_pack) strbuf_addstr(&c, " thin-pack"); - if (args.no_progress) strbuf_addstr(&c, " no-progress"); - if (args.include_tag) strbuf_addstr(&c, " include-tag"); - if (prefer_ofs_delta) strbuf_addstr(&c, " ofs-delta"); - packet_buf_write(&req_buf, "want %s%s\n", remote_hex, c.buf); - strbuf_release(&c); - } else - packet_buf_write(&req_buf, "want %s\n", remote_hex); - fetching++; - } - - if (!fetching) { - strbuf_release(&req_buf); - packet_flush(fd[1]); - return 1; - } - - if (is_repository_shallow()) - write_shallow_commits(&req_buf, 1); - if (args.depth > 0) - packet_buf_write(&req_buf, "deepen %d", args.depth); - packet_buf_flush(&req_buf); - state_len = req_buf.len; - - if (args.depth > 0) { - char line[1024]; - unsigned char sha1[20]; - - send_request(fd[1], &req_buf); - while (packet_read_line(fd[0], line, sizeof(line))) { - if (!prefixcmp(line, "shallow ")) { - if (get_sha1_hex(line + 8, sha1)) - die("invalid shallow line: %s", line); - register_shallow(sha1); - continue; - } - if (!prefixcmp(line, "unshallow ")) { - if (get_sha1_hex(line + 10, sha1)) - die("invalid unshallow line: %s", line); - if (!lookup_object(sha1)) - die("object not found: %s", line); - /* make sure that it is parsed as shallow */ - if (!parse_object(sha1)) - die("error in object: %s", line); - if (unregister_shallow(sha1)) - die("no shallow found: %s", line); - continue; - } - die("expected shallow/unshallow, got %s", line); - } - } else if (!args.stateless_rpc) - send_request(fd[1], &req_buf); - - if (!args.stateless_rpc) { - /* If we aren't using the stateless-rpc interface - * we don't need to retain the headers. - */ - strbuf_setlen(&req_buf, 0); - state_len = 0; - } - - flushes = 0; - retval = -1; - while ((sha1 = get_rev())) { - packet_buf_write(&req_buf, "have %s\n", sha1_to_hex(sha1)); - if (args.verbose) - fprintf(stderr, "have %s\n", sha1_to_hex(sha1)); - in_vain++; - if (!(31 & ++count)) { - int ack; - - packet_buf_flush(&req_buf); - send_request(fd[1], &req_buf); - strbuf_setlen(&req_buf, state_len); - flushes++; - - /* - * We keep one window "ahead" of the other side, and - * will wait for an ACK only on the next one - */ - if (!args.stateless_rpc && count == 32) - continue; - - consume_shallow_list(fd[0]); - do { - ack = get_ack(fd[0], result_sha1); - if (args.verbose && ack) - fprintf(stderr, "got ack %d %s\n", ack, - sha1_to_hex(result_sha1)); - switch (ack) { - case ACK: - flushes = 0; - multi_ack = 0; - retval = 0; - goto done; - case ACK_common: - case ACK_ready: - case ACK_continue: { - struct commit *commit = - lookup_commit(result_sha1); - if (args.stateless_rpc - && ack == ACK_common - && !(commit->object.flags & COMMON)) { - /* We need to replay the have for this object - * on the next RPC request so the peer knows - * it is in common with us. - */ - const char *hex = sha1_to_hex(result_sha1); - packet_buf_write(&req_buf, "have %s\n", hex); - state_len = req_buf.len; - } - mark_common(commit, 0, 1); - retval = 0; - in_vain = 0; - got_continue = 1; - break; - } - } - } while (ack); - flushes--; - if (got_continue && MAX_IN_VAIN < in_vain) { - if (args.verbose) - fprintf(stderr, "giving up\n"); - break; /* give up */ - } - } - } -done: - packet_buf_write(&req_buf, "done\n"); - send_request(fd[1], &req_buf); - if (args.verbose) - fprintf(stderr, "done\n"); - if (retval != 0) { - multi_ack = 0; - flushes++; - } - strbuf_release(&req_buf); - - consume_shallow_list(fd[0]); - while (flushes || multi_ack) { - int ack = get_ack(fd[0], result_sha1); - if (ack) { - if (args.verbose) - fprintf(stderr, "got ack (%d) %s\n", ack, - sha1_to_hex(result_sha1)); - if (ack == ACK) - return 0; - multi_ack = 1; - continue; - } - flushes--; - } - /* it is no error to fetch into a completely empty repo */ - return count ? retval : 0; -} - -static struct commit_list *complete; - -static int mark_complete(const char *path, const unsigned char *sha1, int flag, void *cb_data) -{ - struct object *o = parse_object(sha1); - - while (o && o->type == OBJ_TAG) { - struct tag *t = (struct tag *) o; - if (!t->tagged) - break; /* broken repository */ - o->flags |= COMPLETE; - o = parse_object(t->tagged->sha1); - } - if (o && o->type == OBJ_COMMIT) { - struct commit *commit = (struct commit *)o; - commit->object.flags |= COMPLETE; - insert_by_date(commit, &complete); - } - return 0; -} - -static void mark_recent_complete_commits(unsigned long cutoff) -{ - while (complete && cutoff <= complete->item->date) { - if (args.verbose) - fprintf(stderr, "Marking %s as complete\n", - sha1_to_hex(complete->item->object.sha1)); - pop_most_recent_commit(&complete, COMPLETE); - } -} - -static void filter_refs(struct ref **refs, int nr_match, char **match) -{ - struct ref **return_refs; - struct ref *newlist = NULL; - struct ref **newtail = &newlist; - struct ref *ref, *next; - struct ref *fastarray[32]; - - if (nr_match && !args.fetch_all) { - if (ARRAY_SIZE(fastarray) < nr_match) - return_refs = xcalloc(nr_match, sizeof(struct ref *)); - else { - return_refs = fastarray; - memset(return_refs, 0, sizeof(struct ref *) * nr_match); - } - } - else - return_refs = NULL; - - for (ref = *refs; ref; ref = next) { - next = ref->next; - if (!memcmp(ref->name, "refs/", 5) && - check_ref_format(ref->name + 5)) - ; /* trash */ - else if (args.fetch_all && - (!args.depth || prefixcmp(ref->name, "refs/tags/") )) { - *newtail = ref; - ref->next = NULL; - newtail = &ref->next; - continue; - } - else { - int order = path_match(ref->name, nr_match, match); - if (order) { - return_refs[order-1] = ref; - continue; /* we will link it later */ - } - } - free(ref); - } - - if (!args.fetch_all) { - int i; - for (i = 0; i < nr_match; i++) { - ref = return_refs[i]; - if (ref) { - *newtail = ref; - ref->next = NULL; - newtail = &ref->next; - } - } - if (return_refs != fastarray) - free(return_refs); - } - *refs = newlist; -} - -static int everything_local(struct ref **refs, int nr_match, char **match) -{ - struct ref *ref; - int retval; - unsigned long cutoff = 0; - - save_commit_buffer = 0; - - for (ref = *refs; ref; ref = ref->next) { - struct object *o; - - o = parse_object(ref->old_sha1); - if (!o) - continue; - - /* We already have it -- which may mean that we were - * in sync with the other side at some time after - * that (it is OK if we guess wrong here). - */ - if (o->type == OBJ_COMMIT) { - struct commit *commit = (struct commit *)o; - if (!cutoff || cutoff < commit->date) - cutoff = commit->date; - } - } - - if (!args.depth) { - for_each_ref(mark_complete, NULL); - if (cutoff) - mark_recent_complete_commits(cutoff); - } - - /* - * Mark all complete remote refs as common refs. - * Don't mark them common yet; the server has to be told so first. - */ - for (ref = *refs; ref; ref = ref->next) { - struct object *o = deref_tag(lookup_object(ref->old_sha1), - NULL, 0); - - if (!o || o->type != OBJ_COMMIT || !(o->flags & COMPLETE)) - continue; - - if (!(o->flags & SEEN)) { - rev_list_push((struct commit *)o, COMMON_REF | SEEN); - - mark_common((struct commit *)o, 1, 1); - } - } - - filter_refs(refs, nr_match, match); - - for (retval = 1, ref = *refs; ref ; ref = ref->next) { - const unsigned char *remote = ref->old_sha1; - unsigned char local[20]; - struct object *o; - - o = lookup_object(remote); - if (!o || !(o->flags & COMPLETE)) { - retval = 0; - if (!args.verbose) - continue; - fprintf(stderr, - "want %s (%s)\n", sha1_to_hex(remote), - ref->name); - continue; - } - - hashcpy(ref->new_sha1, local); - if (!args.verbose) - continue; - fprintf(stderr, - "already have %s (%s)\n", sha1_to_hex(remote), - ref->name); - } - return retval; -} - -static int sideband_demux(int in, int out, void *data) -{ - int *xd = data; - - int ret = recv_sideband("fetch-pack", xd[0], out); - close(out); - return ret; -} - -static int get_pack(int xd[2], char **pack_lockfile) -{ - struct async demux; - const char *argv[20]; - char keep_arg[256]; - char hdr_arg[256]; - const char **av; - int do_keep = args.keep_pack; - struct child_process cmd; - - memset(&demux, 0, sizeof(demux)); - if (use_sideband) { - /* xd[] is talking with upload-pack; subprocess reads from - * xd[0], spits out band#2 to stderr, and feeds us band#1 - * through demux->out. - */ - demux.proc = sideband_demux; - demux.data = xd; - demux.out = -1; - if (start_async(&demux)) - die("fetch-pack: unable to fork off sideband" - " demultiplexer"); - } - else - demux.out = xd[0]; - - memset(&cmd, 0, sizeof(cmd)); - cmd.argv = argv; - av = argv; - *hdr_arg = 0; - if (!args.keep_pack && unpack_limit) { - struct pack_header header; - - if (read_pack_header(demux.out, &header)) - die("protocol error: bad pack header"); - snprintf(hdr_arg, sizeof(hdr_arg), - "--pack_header=%"PRIu32",%"PRIu32, - ntohl(header.hdr_version), ntohl(header.hdr_entries)); - if (ntohl(header.hdr_entries) < unpack_limit) - do_keep = 0; - else - do_keep = 1; - } - - if (do_keep) { - if (pack_lockfile) - cmd.out = -1; - *av++ = "index-pack"; - *av++ = "--stdin"; - if (!args.quiet && !args.no_progress) - *av++ = "-v"; - if (args.use_thin_pack) - *av++ = "--fix-thin"; - if (args.lock_pack || unpack_limit) { - int s = sprintf(keep_arg, - "--keep=fetch-pack %"PRIuMAX " on ", (uintmax_t) getpid()); - if (gethostname(keep_arg + s, sizeof(keep_arg) - s)) - strcpy(keep_arg + s, "localhost"); - *av++ = keep_arg; - } - } - else { - *av++ = "unpack-objects"; - if (args.quiet) - *av++ = "-q"; - } - if (*hdr_arg) - *av++ = hdr_arg; - *av++ = NULL; - - cmd.in = demux.out; - cmd.git_cmd = 1; - if (start_command(&cmd)) - die("fetch-pack: unable to fork off %s", argv[0]); - if (do_keep && pack_lockfile) { - *pack_lockfile = index_pack_lockfile(cmd.out); - close(cmd.out); - } - - if (finish_command(&cmd)) - die("%s failed", argv[0]); - if (use_sideband && finish_async(&demux)) - die("error in sideband demultiplexer"); - return 0; -} - -static struct ref *do_fetch_pack(int fd[2], - const struct ref *orig_ref, - int nr_match, - char **match, - char **pack_lockfile) -{ - struct ref *ref = copy_ref_list(orig_ref); - unsigned char sha1[20]; - - if (is_repository_shallow() && !server_supports("shallow")) - die("Server does not support shallow clients"); - if (server_supports("multi_ack_detailed")) { - if (args.verbose) - fprintf(stderr, "Server supports multi_ack_detailed\n"); - multi_ack = 2; - } - else if (server_supports("multi_ack")) { - if (args.verbose) - fprintf(stderr, "Server supports multi_ack\n"); - multi_ack = 1; - } - if (server_supports("side-band-64k")) { - if (args.verbose) - fprintf(stderr, "Server supports side-band-64k\n"); - use_sideband = 2; - } - else if (server_supports("side-band")) { - if (args.verbose) - fprintf(stderr, "Server supports side-band\n"); - use_sideband = 1; - } - if (server_supports("ofs-delta")) { - if (args.verbose) - fprintf(stderr, "Server supports ofs-delta\n"); - } else - prefer_ofs_delta = 0; - if (everything_local(&ref, nr_match, match)) { - packet_flush(fd[1]); - goto all_done; - } - if (find_common(fd, sha1, ref) < 0) - if (!args.keep_pack) - /* When cloning, it is not unusual to have - * no common commit. - */ - warning("no common commits"); - - if (args.stateless_rpc) - packet_flush(fd[1]); - if (get_pack(fd, pack_lockfile)) - die("git fetch-pack: fetch failed."); - - all_done: - return ref; -} - -static int remove_duplicates(int nr_heads, char **heads) -{ - int src, dst; - - for (src = dst = 0; src < nr_heads; src++) { - /* If heads[src] is different from any of - * heads[0..dst], push it in. - */ - int i; - for (i = 0; i < dst; i++) { - if (!strcmp(heads[i], heads[src])) - break; - } - if (i < dst) - continue; - if (src != dst) - heads[dst] = heads[src]; - dst++; - } - return dst; -} - -static int fetch_pack_config(const char *var, const char *value, void *cb) -{ - if (strcmp(var, "fetch.unpacklimit") == 0) { - fetch_unpack_limit = git_config_int(var, value); - return 0; - } - - if (strcmp(var, "transfer.unpacklimit") == 0) { - transfer_unpack_limit = git_config_int(var, value); - return 0; - } - - if (strcmp(var, "repack.usedeltabaseoffset") == 0) { - prefer_ofs_delta = git_config_bool(var, value); - return 0; - } - - return git_default_config(var, value, cb); -} - -static struct lock_file lock; - -static void fetch_pack_setup(void) -{ - static int did_setup; - if (did_setup) - return; - git_config(fetch_pack_config, NULL); - if (0 <= transfer_unpack_limit) - unpack_limit = transfer_unpack_limit; - else if (0 <= fetch_unpack_limit) - unpack_limit = fetch_unpack_limit; - did_setup = 1; -} - -int cmd_fetch_pack(int argc, const char **argv, const char *prefix) -{ - int i, ret, nr_heads; - struct ref *ref = NULL; - char *dest = NULL, **heads; - int fd[2]; - char *pack_lockfile = NULL; - char **pack_lockfile_ptr = NULL; - struct child_process *conn; - - nr_heads = 0; - heads = NULL; - for (i = 1; i < argc; i++) { - const char *arg = argv[i]; - - if (*arg == '-') { - if (!prefixcmp(arg, "--upload-pack=")) { - args.uploadpack = arg + 14; - continue; - } - if (!prefixcmp(arg, "--exec=")) { - args.uploadpack = arg + 7; - continue; - } - if (!strcmp("--quiet", arg) || !strcmp("-q", arg)) { - args.quiet = 1; - continue; - } - if (!strcmp("--keep", arg) || !strcmp("-k", arg)) { - args.lock_pack = args.keep_pack; - args.keep_pack = 1; - continue; - } - if (!strcmp("--thin", arg)) { - args.use_thin_pack = 1; - continue; - } - if (!strcmp("--include-tag", arg)) { - args.include_tag = 1; - continue; - } - if (!strcmp("--all", arg)) { - args.fetch_all = 1; - continue; - } - if (!strcmp("-v", arg)) { - args.verbose = 1; - continue; - } - if (!prefixcmp(arg, "--depth=")) { - args.depth = strtol(arg + 8, NULL, 0); - continue; - } - if (!strcmp("--no-progress", arg)) { - args.no_progress = 1; - continue; - } - if (!strcmp("--stateless-rpc", arg)) { - args.stateless_rpc = 1; - continue; - } - if (!strcmp("--lock-pack", arg)) { - args.lock_pack = 1; - pack_lockfile_ptr = &pack_lockfile; - continue; - } - usage(fetch_pack_usage); - } - dest = (char *)arg; - heads = (char **)(argv + i + 1); - nr_heads = argc - i - 1; - break; - } - if (!dest) - usage(fetch_pack_usage); - - if (args.stateless_rpc) { - conn = NULL; - fd[0] = 0; - fd[1] = 1; - } else { - conn = git_connect(fd, (char *)dest, args.uploadpack, - args.verbose ? CONNECT_VERBOSE : 0); - } - - get_remote_heads(fd[0], &ref, 0, NULL, 0, NULL); - - ref = fetch_pack(&args, fd, conn, ref, dest, - nr_heads, heads, pack_lockfile_ptr); - if (pack_lockfile) { - printf("lock %s\n", pack_lockfile); - fflush(stdout); - } - close(fd[0]); - close(fd[1]); - if (finish_connect(conn)) - ref = NULL; - ret = !ref; - - if (!ret && nr_heads) { - /* If the heads to pull were given, we should have - * consumed all of them by matching the remote. - * Otherwise, 'git fetch remote no-such-ref' would - * silently succeed without issuing an error. - */ - for (i = 0; i < nr_heads; i++) - if (heads[i] && heads[i][0]) { - error("no such remote ref %s", heads[i]); - ret = 1; - } - } - while (ref) { - printf("%s %s\n", - sha1_to_hex(ref->old_sha1), ref->name); - ref = ref->next; - } - - return ret; -} - -struct ref *fetch_pack(struct fetch_pack_args *my_args, - int fd[], struct child_process *conn, - const struct ref *ref, - const char *dest, - int nr_heads, - char **heads, - char **pack_lockfile) -{ - struct stat st; - struct ref *ref_cpy; - - fetch_pack_setup(); - if (&args != my_args) - memcpy(&args, my_args, sizeof(args)); - if (args.depth > 0) { - if (stat(git_path("shallow"), &st)) - st.st_mtime = 0; - } - - if (heads && nr_heads) - nr_heads = remove_duplicates(nr_heads, heads); - if (!ref) { - packet_flush(fd[1]); - die("no matching remote head"); - } - ref_cpy = do_fetch_pack(fd, ref, nr_heads, heads, pack_lockfile); - - if (args.depth > 0) { - struct cache_time mtime; - struct strbuf sb = STRBUF_INIT; - char *shallow = git_path("shallow"); - int fd; - - mtime.sec = st.st_mtime; - mtime.nsec = ST_MTIME_NSEC(st); - if (stat(shallow, &st)) { - if (mtime.sec) - die("shallow file was removed during fetch"); - } else if (st.st_mtime != mtime.sec -#ifdef USE_NSEC - || ST_MTIME_NSEC(st) != mtime.nsec -#endif - ) - die("shallow file was changed during fetch"); - - fd = hold_lock_file_for_update(&lock, shallow, - LOCK_DIE_ON_ERROR); - if (!write_shallow_commits(&sb, 0) - || write_in_full(fd, sb.buf, sb.len) != sb.len) { - unlink_or_warn(shallow); - rollback_lock_file(&lock); - } else { - commit_lock_file(&lock); - } - strbuf_release(&sb); - } - - reprepare_packed_git(); - return ref_cpy; -} diff --git a/builtin-fetch.c b/builtin-fetch.c deleted file mode 100644 index 8654fa7..0000000 --- a/builtin-fetch.c +++ /dev/null @@ -1,920 +0,0 @@ -/* - * "git fetch" - */ -#include "cache.h" -#include "refs.h" -#include "commit.h" -#include "builtin.h" -#include "string-list.h" -#include "remote.h" -#include "transport.h" -#include "run-command.h" -#include "parse-options.h" -#include "sigchain.h" - -static const char * const builtin_fetch_usage[] = { - "git fetch [options] [ ...]", - "git fetch [options] ", - "git fetch --multiple [options] [ | ]...", - "git fetch --all [options]", - NULL -}; - -enum { - TAGS_UNSET = 0, - TAGS_DEFAULT = 1, - TAGS_SET = 2 -}; - -static int all, append, dry_run, force, keep, multiple, prune, update_head_ok, verbosity; -static int tags = TAGS_DEFAULT; -static const char *depth; -static const char *upload_pack; -static struct strbuf default_rla = STRBUF_INIT; -static struct transport *transport; - -static struct option builtin_fetch_options[] = { - OPT__VERBOSITY(&verbosity), - OPT_BOOLEAN(0, "all", &all, - "fetch from all remotes"), - OPT_BOOLEAN('a', "append", &append, - "append to .git/FETCH_HEAD instead of overwriting"), - OPT_STRING(0, "upload-pack", &upload_pack, "PATH", - "path to upload pack on remote end"), - OPT_BOOLEAN('f', "force", &force, - "force overwrite of local branch"), - OPT_BOOLEAN('m', "multiple", &multiple, - "fetch from multiple remotes"), - OPT_SET_INT('t', "tags", &tags, - "fetch all tags and associated objects", TAGS_SET), - OPT_SET_INT('n', NULL, &tags, - "do not fetch all tags (--no-tags)", TAGS_UNSET), - OPT_BOOLEAN('p', "prune", &prune, - "prune tracking branches no longer on remote"), - OPT_BOOLEAN(0, "dry-run", &dry_run, - "dry run"), - OPT_BOOLEAN('k', "keep", &keep, "keep downloaded pack"), - OPT_BOOLEAN('u', "update-head-ok", &update_head_ok, - "allow updating of HEAD ref"), - OPT_STRING(0, "depth", &depth, "DEPTH", - "deepen history of shallow clone"), - OPT_END() -}; - -static void unlock_pack(void) -{ - if (transport) - transport_unlock_pack(transport); -} - -static void unlock_pack_on_signal(int signo) -{ - unlock_pack(); - sigchain_pop(signo); - raise(signo); -} - -static void add_merge_config(struct ref **head, - const struct ref *remote_refs, - struct branch *branch, - struct ref ***tail) -{ - int i; - - for (i = 0; i < branch->merge_nr; i++) { - struct ref *rm, **old_tail = *tail; - struct refspec refspec; - - for (rm = *head; rm; rm = rm->next) { - if (branch_merge_matches(branch, i, rm->name)) { - rm->merge = 1; - break; - } - } - if (rm) - continue; - - /* - * Not fetched to a tracking branch? We need to fetch - * it anyway to allow this branch's "branch.$name.merge" - * to be honored by 'git pull', but we do not have to - * fail if branch.$name.merge is misconfigured to point - * at a nonexisting branch. If we were indeed called by - * 'git pull', it will notice the misconfiguration because - * there is no entry in the resulting FETCH_HEAD marked - * for merging. - */ - refspec.src = branch->merge[i]->src; - refspec.dst = NULL; - refspec.pattern = 0; - refspec.force = 0; - get_fetch_map(remote_refs, &refspec, tail, 1); - for (rm = *old_tail; rm; rm = rm->next) - rm->merge = 1; - } -} - -static void find_non_local_tags(struct transport *transport, - struct ref **head, - struct ref ***tail); - -static struct ref *get_ref_map(struct transport *transport, - struct refspec *refs, int ref_count, int tags, - int *autotags) -{ - int i; - struct ref *rm; - struct ref *ref_map = NULL; - struct ref **tail = &ref_map; - - const struct ref *remote_refs = transport_get_remote_refs(transport); - - if (ref_count || tags == TAGS_SET) { - for (i = 0; i < ref_count; i++) { - get_fetch_map(remote_refs, &refs[i], &tail, 0); - if (refs[i].dst && refs[i].dst[0]) - *autotags = 1; - } - /* Merge everything on the command line, but not --tags */ - for (rm = ref_map; rm; rm = rm->next) - rm->merge = 1; - if (tags == TAGS_SET) - get_fetch_map(remote_refs, tag_refspec, &tail, 0); - } else { - /* Use the defaults */ - struct remote *remote = transport->remote; - struct branch *branch = branch_get(NULL); - int has_merge = branch_has_merge_config(branch); - if (remote && (remote->fetch_refspec_nr || has_merge)) { - for (i = 0; i < remote->fetch_refspec_nr; i++) { - get_fetch_map(remote_refs, &remote->fetch[i], &tail, 0); - if (remote->fetch[i].dst && - remote->fetch[i].dst[0]) - *autotags = 1; - if (!i && !has_merge && ref_map && - !remote->fetch[0].pattern) - ref_map->merge = 1; - } - /* - * if the remote we're fetching from is the same - * as given in branch..remote, we add the - * ref given in branch..merge, too. - */ - if (has_merge && - !strcmp(branch->remote_name, remote->name)) - add_merge_config(&ref_map, remote_refs, branch, &tail); - } else { - ref_map = get_remote_ref(remote_refs, "HEAD"); - if (!ref_map) - die("Couldn't find remote ref HEAD"); - ref_map->merge = 1; - tail = &ref_map->next; - } - } - if (tags == TAGS_DEFAULT && *autotags) - find_non_local_tags(transport, &ref_map, &tail); - ref_remove_duplicates(ref_map); - - return ref_map; -} - -#define STORE_REF_ERROR_OTHER 1 -#define STORE_REF_ERROR_DF_CONFLICT 2 - -static int s_update_ref(const char *action, - struct ref *ref, - int check_old) -{ - char msg[1024]; - char *rla = getenv("GIT_REFLOG_ACTION"); - static struct ref_lock *lock; - - if (dry_run) - return 0; - if (!rla) - rla = default_rla.buf; - snprintf(msg, sizeof(msg), "%s: %s", rla, action); - lock = lock_any_ref_for_update(ref->name, - check_old ? ref->old_sha1 : NULL, 0); - if (!lock) - return errno == ENOTDIR ? STORE_REF_ERROR_DF_CONFLICT : - STORE_REF_ERROR_OTHER; - if (write_ref_sha1(lock, ref->new_sha1, msg) < 0) - return errno == ENOTDIR ? STORE_REF_ERROR_DF_CONFLICT : - STORE_REF_ERROR_OTHER; - return 0; -} - -#define SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3) -#define REFCOL_WIDTH 10 - -static int update_local_ref(struct ref *ref, - const char *remote, - char *display) -{ - struct commit *current = NULL, *updated; - enum object_type type; - struct branch *current_branch = branch_get(NULL); - const char *pretty_ref = prettify_refname(ref->name); - - *display = 0; - type = sha1_object_info(ref->new_sha1, NULL); - if (type < 0) - die("object %s not found", sha1_to_hex(ref->new_sha1)); - - if (!hashcmp(ref->old_sha1, ref->new_sha1)) { - if (verbosity > 0) - sprintf(display, "= %-*s %-*s -> %s", SUMMARY_WIDTH, - "[up to date]", REFCOL_WIDTH, remote, - pretty_ref); - return 0; - } - - if (current_branch && - !strcmp(ref->name, current_branch->name) && - !(update_head_ok || is_bare_repository()) && - !is_null_sha1(ref->old_sha1)) { - /* - * If this is the head, and it's not okay to update - * the head, and the old value of the head isn't empty... - */ - sprintf(display, "! %-*s %-*s -> %s (can't fetch in current branch)", - SUMMARY_WIDTH, "[rejected]", REFCOL_WIDTH, remote, - pretty_ref); - return 1; - } - - if (!is_null_sha1(ref->old_sha1) && - !prefixcmp(ref->name, "refs/tags/")) { - int r; - r = s_update_ref("updating tag", ref, 0); - sprintf(display, "%c %-*s %-*s -> %s%s", r ? '!' : '-', - SUMMARY_WIDTH, "[tag update]", REFCOL_WIDTH, remote, - pretty_ref, r ? " (unable to update local ref)" : ""); - return r; - } - - current = lookup_commit_reference_gently(ref->old_sha1, 1); - updated = lookup_commit_reference_gently(ref->new_sha1, 1); - if (!current || !updated) { - const char *msg; - const char *what; - int r; - if (!strncmp(ref->name, "refs/tags/", 10)) { - msg = "storing tag"; - what = "[new tag]"; - } - else { - msg = "storing head"; - what = "[new branch]"; - } - - r = s_update_ref(msg, ref, 0); - sprintf(display, "%c %-*s %-*s -> %s%s", r ? '!' : '*', - SUMMARY_WIDTH, what, REFCOL_WIDTH, remote, pretty_ref, - r ? " (unable to update local ref)" : ""); - return r; - } - - if (in_merge_bases(current, &updated, 1)) { - char quickref[83]; - int r; - strcpy(quickref, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV)); - strcat(quickref, ".."); - strcat(quickref, find_unique_abbrev(ref->new_sha1, DEFAULT_ABBREV)); - r = s_update_ref("fast-forward", ref, 1); - sprintf(display, "%c %-*s %-*s -> %s%s", r ? '!' : ' ', - SUMMARY_WIDTH, quickref, REFCOL_WIDTH, remote, - pretty_ref, r ? " (unable to update local ref)" : ""); - return r; - } else if (force || ref->force) { - char quickref[84]; - int r; - strcpy(quickref, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV)); - strcat(quickref, "..."); - strcat(quickref, find_unique_abbrev(ref->new_sha1, DEFAULT_ABBREV)); - r = s_update_ref("forced-update", ref, 1); - sprintf(display, "%c %-*s %-*s -> %s (%s)", r ? '!' : '+', - SUMMARY_WIDTH, quickref, REFCOL_WIDTH, remote, - pretty_ref, - r ? "unable to update local ref" : "forced update"); - return r; - } else { - sprintf(display, "! %-*s %-*s -> %s (non-fast-forward)", - SUMMARY_WIDTH, "[rejected]", REFCOL_WIDTH, remote, - pretty_ref); - return 1; - } -} - -static int store_updated_refs(const char *raw_url, const char *remote_name, - struct ref *ref_map) -{ - FILE *fp; - struct commit *commit; - int url_len, i, note_len, shown_url = 0, rc = 0; - char note[1024]; - const char *what, *kind; - struct ref *rm; - char *url, *filename = dry_run ? "/dev/null" : git_path("FETCH_HEAD"); - - fp = fopen(filename, "a"); - if (!fp) - return error("cannot open %s: %s\n", filename, strerror(errno)); - - if (raw_url) - url = transport_anonymize_url(raw_url); - else - url = xstrdup("foreign"); - for (rm = ref_map; rm; rm = rm->next) { - struct ref *ref = NULL; - - if (rm->peer_ref) { - ref = xcalloc(1, sizeof(*ref) + strlen(rm->peer_ref->name) + 1); - strcpy(ref->name, rm->peer_ref->name); - hashcpy(ref->old_sha1, rm->peer_ref->old_sha1); - hashcpy(ref->new_sha1, rm->old_sha1); - ref->force = rm->peer_ref->force; - } - - commit = lookup_commit_reference_gently(rm->old_sha1, 1); - if (!commit) - rm->merge = 0; - - if (!strcmp(rm->name, "HEAD")) { - kind = ""; - what = ""; - } - else if (!prefixcmp(rm->name, "refs/heads/")) { - kind = "branch"; - what = rm->name + 11; - } - else if (!prefixcmp(rm->name, "refs/tags/")) { - kind = "tag"; - what = rm->name + 10; - } - else if (!prefixcmp(rm->name, "refs/remotes/")) { - kind = "remote branch"; - what = rm->name + 13; - } - else { - kind = ""; - what = rm->name; - } - - url_len = strlen(url); - for (i = url_len - 1; url[i] == '/' && 0 <= i; i--) - ; - url_len = i + 1; - if (4 < i && !strncmp(".git", url + i - 3, 4)) - url_len = i - 3; - - note_len = 0; - if (*what) { - if (*kind) - note_len += sprintf(note + note_len, "%s ", - kind); - note_len += sprintf(note + note_len, "'%s' of ", what); - } - note[note_len] = '\0'; - fprintf(fp, "%s\t%s\t%s", - sha1_to_hex(commit ? commit->object.sha1 : - rm->old_sha1), - rm->merge ? "" : "not-for-merge", - note); - for (i = 0; i < url_len; ++i) - if ('\n' == url[i]) - fputs("\\n", fp); - else - fputc(url[i], fp); - fputc('\n', fp); - - if (ref) - rc |= update_local_ref(ref, what, note); - else - sprintf(note, "* %-*s %-*s -> FETCH_HEAD", - SUMMARY_WIDTH, *kind ? kind : "branch", - REFCOL_WIDTH, *what ? what : "HEAD"); - if (*note) { - if (verbosity >= 0 && !shown_url) { - fprintf(stderr, "From %.*s\n", - url_len, url); - shown_url = 1; - } - if (verbosity >= 0) - fprintf(stderr, " %s\n", note); - } - } - free(url); - fclose(fp); - if (rc & STORE_REF_ERROR_DF_CONFLICT) - error("some local refs could not be updated; try running\n" - " 'git remote prune %s' to remove any old, conflicting " - "branches", remote_name); - return rc; -} - -/* - * We would want to bypass the object transfer altogether if - * everything we are going to fetch already exists and is connected - * locally. - * - * The refs we are going to fetch are in ref_map. If running - * - * $ git rev-list --objects --stdin --not --all - * - * (feeding all the refs in ref_map on its standard input) - * does not error out, that means everything reachable from the - * refs we are going to fetch exists and is connected to some of - * our existing refs. - */ -static int quickfetch(struct ref *ref_map) -{ - struct child_process revlist; - struct ref *ref; - int err; - const char *argv[] = {"rev-list", - "--quiet", "--objects", "--stdin", "--not", "--all", NULL}; - - /* - * If we are deepening a shallow clone we already have these - * objects reachable. Running rev-list here will return with - * a good (0) exit status and we'll bypass the fetch that we - * really need to perform. Claiming failure now will ensure - * we perform the network exchange to deepen our history. - */ - if (depth) - return -1; - - if (!ref_map) - return 0; - - memset(&revlist, 0, sizeof(revlist)); - revlist.argv = argv; - revlist.git_cmd = 1; - revlist.no_stdout = 1; - revlist.no_stderr = 1; - revlist.in = -1; - - err = start_command(&revlist); - if (err) { - error("could not run rev-list"); - return err; - } - - /* - * If rev-list --stdin encounters an unknown commit, it terminates, - * which will cause SIGPIPE in the write loop below. - */ - sigchain_push(SIGPIPE, SIG_IGN); - - for (ref = ref_map; ref; ref = ref->next) { - if (write_in_full(revlist.in, sha1_to_hex(ref->old_sha1), 40) < 0 || - write_str_in_full(revlist.in, "\n") < 0) { - if (errno != EPIPE && errno != EINVAL) - error("failed write to rev-list: %s", strerror(errno)); - err = -1; - break; - } - } - - if (close(revlist.in)) { - error("failed to close rev-list's stdin: %s", strerror(errno)); - err = -1; - } - - sigchain_pop(SIGPIPE); - - return finish_command(&revlist) || err; -} - -static int fetch_refs(struct transport *transport, struct ref *ref_map) -{ - int ret = quickfetch(ref_map); - if (ret) - ret = transport_fetch_refs(transport, ref_map); - if (!ret) - ret |= store_updated_refs(transport->url, - transport->remote->name, - ref_map); - transport_unlock_pack(transport); - return ret; -} - -static int prune_refs(struct transport *transport, struct ref *ref_map) -{ - int result = 0; - struct ref *ref, *stale_refs = get_stale_heads(transport->remote, ref_map); - const char *dangling_msg = dry_run - ? " (%s will become dangling)\n" - : " (%s has become dangling)\n"; - - for (ref = stale_refs; ref; ref = ref->next) { - if (!dry_run) - result |= delete_ref(ref->name, NULL, 0); - if (verbosity >= 0) { - fprintf(stderr, " x %-*s %-*s -> %s\n", - SUMMARY_WIDTH, "[deleted]", - REFCOL_WIDTH, "(none)", prettify_refname(ref->name)); - warn_dangling_symref(stderr, dangling_msg, ref->name); - } - } - free_refs(stale_refs); - return result; -} - -static int add_existing(const char *refname, const unsigned char *sha1, - int flag, void *cbdata) -{ - struct string_list *list = (struct string_list *)cbdata; - struct string_list_item *item = string_list_insert(refname, list); - item->util = (void *)sha1; - return 0; -} - -static int will_fetch(struct ref **head, const unsigned char *sha1) -{ - struct ref *rm = *head; - while (rm) { - if (!hashcmp(rm->old_sha1, sha1)) - return 1; - rm = rm->next; - } - return 0; -} - -struct tag_data { - struct ref **head; - struct ref ***tail; -}; - -static int add_to_tail(struct string_list_item *item, void *cb_data) -{ - struct tag_data *data = (struct tag_data *)cb_data; - struct ref *rm = NULL; - - /* We have already decided to ignore this item */ - if (!item->util) - return 0; - - rm = alloc_ref(item->string); - rm->peer_ref = alloc_ref(item->string); - hashcpy(rm->old_sha1, item->util); - - **data->tail = rm; - *data->tail = &rm->next; - - return 0; -} - -static void find_non_local_tags(struct transport *transport, - struct ref **head, - struct ref ***tail) -{ - struct string_list existing_refs = { NULL, 0, 0, 0 }; - struct string_list remote_refs = { NULL, 0, 0, 0 }; - struct tag_data data = {head, tail}; - const struct ref *ref; - struct string_list_item *item = NULL; - - for_each_ref(add_existing, &existing_refs); - for (ref = transport_get_remote_refs(transport); ref; ref = ref->next) { - if (prefixcmp(ref->name, "refs/tags")) - continue; - - /* - * The peeled ref always follows the matching base - * ref, so if we see a peeled ref that we don't want - * to fetch then we can mark the ref entry in the list - * as one to ignore by setting util to NULL. - */ - if (!strcmp(ref->name + strlen(ref->name) - 3, "^{}")) { - if (item && !has_sha1_file(ref->old_sha1) && - !will_fetch(head, ref->old_sha1) && - !has_sha1_file(item->util) && - !will_fetch(head, item->util)) - item->util = NULL; - item = NULL; - continue; - } - - /* - * If item is non-NULL here, then we previously saw a - * ref not followed by a peeled reference, so we need - * to check if it is a lightweight tag that we want to - * fetch. - */ - if (item && !has_sha1_file(item->util) && - !will_fetch(head, item->util)) - item->util = NULL; - - item = NULL; - - /* skip duplicates and refs that we already have */ - if (string_list_has_string(&remote_refs, ref->name) || - string_list_has_string(&existing_refs, ref->name)) - continue; - - item = string_list_insert(ref->name, &remote_refs); - item->util = (void *)ref->old_sha1; - } - string_list_clear(&existing_refs, 0); - - /* - * We may have a final lightweight tag that needs to be - * checked to see if it needs fetching. - */ - if (item && !has_sha1_file(item->util) && - !will_fetch(head, item->util)) - item->util = NULL; - - /* - * For all the tags in the remote_refs string list, call - * add_to_tail to add them to the list of refs to be fetched - */ - for_each_string_list(add_to_tail, &remote_refs, &data); - - string_list_clear(&remote_refs, 0); -} - -static void check_not_current_branch(struct ref *ref_map) -{ - struct branch *current_branch = branch_get(NULL); - - if (is_bare_repository() || !current_branch) - return; - - for (; ref_map; ref_map = ref_map->next) - if (ref_map->peer_ref && !strcmp(current_branch->refname, - ref_map->peer_ref->name)) - die("Refusing to fetch into current branch %s " - "of non-bare repository", current_branch->refname); -} - -static int do_fetch(struct transport *transport, - struct refspec *refs, int ref_count) -{ - struct string_list existing_refs = { NULL, 0, 0, 0 }; - struct string_list_item *peer_item = NULL; - struct ref *ref_map; - struct ref *rm; - int autotags = (transport->remote->fetch_tags == 1); - - for_each_ref(add_existing, &existing_refs); - - if (transport->remote->fetch_tags == 2 && tags != TAGS_UNSET) - tags = TAGS_SET; - if (transport->remote->fetch_tags == -1) - tags = TAGS_UNSET; - - if (!transport->get_refs_list || !transport->fetch) - die("Don't know how to fetch from %s", transport->url); - - /* if not appending, truncate FETCH_HEAD */ - if (!append && !dry_run) { - char *filename = git_path("FETCH_HEAD"); - FILE *fp = fopen(filename, "w"); - if (!fp) - return error("cannot open %s: %s\n", filename, strerror(errno)); - fclose(fp); - } - - ref_map = get_ref_map(transport, refs, ref_count, tags, &autotags); - if (!update_head_ok) - check_not_current_branch(ref_map); - - for (rm = ref_map; rm; rm = rm->next) { - if (rm->peer_ref) { - peer_item = string_list_lookup(rm->peer_ref->name, - &existing_refs); - if (peer_item) - hashcpy(rm->peer_ref->old_sha1, - peer_item->util); - } - } - - if (tags == TAGS_DEFAULT && autotags) - transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, "1"); - if (fetch_refs(transport, ref_map)) { - free_refs(ref_map); - return 1; - } - if (prune) - prune_refs(transport, ref_map); - free_refs(ref_map); - - /* if neither --no-tags nor --tags was specified, do automated tag - * following ... */ - if (tags == TAGS_DEFAULT && autotags) { - struct ref **tail = &ref_map; - ref_map = NULL; - find_non_local_tags(transport, &ref_map, &tail); - if (ref_map) { - transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, NULL); - transport_set_option(transport, TRANS_OPT_DEPTH, "0"); - fetch_refs(transport, ref_map); - } - free_refs(ref_map); - } - - return 0; -} - -static void set_option(const char *name, const char *value) -{ - int r = transport_set_option(transport, name, value); - if (r < 0) - die("Option \"%s\" value \"%s\" is not valid for %s", - name, value, transport->url); - if (r > 0) - warning("Option \"%s\" is ignored for %s\n", - name, transport->url); -} - -static int get_one_remote_for_fetch(struct remote *remote, void *priv) -{ - struct string_list *list = priv; - if (!remote->skip_default_update) - string_list_append(remote->name, list); - return 0; -} - -struct remote_group_data { - const char *name; - struct string_list *list; -}; - -static int get_remote_group(const char *key, const char *value, void *priv) -{ - struct remote_group_data *g = priv; - - if (!prefixcmp(key, "remotes.") && - !strcmp(key + 8, g->name)) { - /* split list by white space */ - int space = strcspn(value, " \t\n"); - while (*value) { - if (space > 1) { - string_list_append(xstrndup(value, space), - g->list); - } - value += space + (value[space] != '\0'); - space = strcspn(value, " \t\n"); - } - } - - return 0; -} - -static int add_remote_or_group(const char *name, struct string_list *list) -{ - int prev_nr = list->nr; - struct remote_group_data g = { name, list }; - - git_config(get_remote_group, &g); - if (list->nr == prev_nr) { - struct remote *remote; - if (!remote_is_configured(name)) - return 0; - remote = remote_get(name); - string_list_append(remote->name, list); - } - return 1; -} - -static int fetch_multiple(struct string_list *list) -{ - int i, result = 0; - const char *argv[] = { "fetch", NULL, NULL, NULL, NULL, NULL, NULL }; - int argc = 1; - - if (dry_run) - argv[argc++] = "--dry-run"; - if (prune) - argv[argc++] = "--prune"; - if (verbosity >= 2) - argv[argc++] = "-v"; - if (verbosity >= 1) - argv[argc++] = "-v"; - else if (verbosity < 0) - argv[argc++] = "-q"; - - for (i = 0; i < list->nr; i++) { - const char *name = list->items[i].string; - argv[argc] = name; - if (verbosity >= 0) - printf("Fetching %s\n", name); - if (run_command_v_opt(argv, RUN_GIT_CMD)) { - error("Could not fetch %s", name); - result = 1; - } - } - - return result; -} - -static int fetch_one(struct remote *remote, int argc, const char **argv) -{ - int i; - static const char **refs = NULL; - int ref_nr = 0; - int exit_code; - - if (!remote) - die("Where do you want to fetch from today?"); - - transport = transport_get(remote, NULL); - if (verbosity >= 2) - transport->verbose = verbosity <= 3 ? verbosity : 3; - if (verbosity < 0) - transport->verbose = -1; - if (upload_pack) - set_option(TRANS_OPT_UPLOADPACK, upload_pack); - if (keep) - set_option(TRANS_OPT_KEEP, "yes"); - if (depth) - set_option(TRANS_OPT_DEPTH, depth); - - if (argc > 0) { - int j = 0; - refs = xcalloc(argc + 1, sizeof(const char *)); - for (i = 0; i < argc; i++) { - if (!strcmp(argv[i], "tag")) { - char *ref; - i++; - if (i >= argc) - die("You need to specify a tag name."); - ref = xmalloc(strlen(argv[i]) * 2 + 22); - strcpy(ref, "refs/tags/"); - strcat(ref, argv[i]); - strcat(ref, ":refs/tags/"); - strcat(ref, argv[i]); - refs[j++] = ref; - } else - refs[j++] = argv[i]; - } - refs[j] = NULL; - ref_nr = j; - } - - sigchain_push_common(unlock_pack_on_signal); - atexit(unlock_pack); - exit_code = do_fetch(transport, - parse_fetch_refspec(ref_nr, refs), ref_nr); - transport_disconnect(transport); - transport = NULL; - return exit_code; -} - -int cmd_fetch(int argc, const char **argv, const char *prefix) -{ - int i; - struct string_list list = { NULL, 0, 0, 0 }; - struct remote *remote; - int result = 0; - - /* Record the command line for the reflog */ - strbuf_addstr(&default_rla, "fetch"); - for (i = 1; i < argc; i++) - strbuf_addf(&default_rla, " %s", argv[i]); - - argc = parse_options(argc, argv, prefix, - builtin_fetch_options, builtin_fetch_usage, 0); - - if (all) { - if (argc == 1) - die("fetch --all does not take a repository argument"); - else if (argc > 1) - die("fetch --all does not make sense with refspecs"); - (void) for_each_remote(get_one_remote_for_fetch, &list); - result = fetch_multiple(&list); - } else if (argc == 0) { - /* No arguments -- use default remote */ - remote = remote_get(NULL); - result = fetch_one(remote, argc, argv); - } else if (multiple) { - /* All arguments are assumed to be remotes or groups */ - for (i = 0; i < argc; i++) - if (!add_remote_or_group(argv[i], &list)) - die("No such remote or remote group: %s", argv[i]); - result = fetch_multiple(&list); - } else { - /* Single remote or group */ - (void) add_remote_or_group(argv[0], &list); - if (list.nr > 1) { - /* More than one remote */ - if (argc > 1) - die("Fetching a group and specifying refspecs does not make sense"); - result = fetch_multiple(&list); - } else { - /* Zero or one remotes */ - remote = remote_get(argv[0]); - result = fetch_one(remote, argc-1, argv+1); - } - } - - /* All names were strdup()ed or strndup()ed */ - list.strdup_strings = 1; - string_list_clear(&list, 0); - - return result; -} diff --git a/builtin-fmt-merge-msg.c b/builtin-fmt-merge-msg.c deleted file mode 100644 index 9d52400..0000000 --- a/builtin-fmt-merge-msg.c +++ /dev/null @@ -1,381 +0,0 @@ -#include "builtin.h" -#include "cache.h" -#include "commit.h" -#include "diff.h" -#include "revision.h" -#include "tag.h" - -static const char * const fmt_merge_msg_usage[] = { - "git fmt-merge-msg [--log|--no-log] [--file ]", - NULL -}; - -static int merge_summary; - -static int fmt_merge_msg_config(const char *key, const char *value, void *cb) -{ - static int found_merge_log = 0; - if (!strcmp("merge.log", key)) { - found_merge_log = 1; - merge_summary = git_config_bool(key, value); - } - if (!found_merge_log && !strcmp("merge.summary", key)) - merge_summary = git_config_bool(key, value); - return 0; -} - -struct list { - char **list; - void **payload; - unsigned nr, alloc; -}; - -static void append_to_list(struct list *list, char *value, void *payload) -{ - if (list->nr == list->alloc) { - list->alloc += 32; - list->list = xrealloc(list->list, sizeof(char *) * list->alloc); - list->payload = xrealloc(list->payload, - sizeof(char *) * list->alloc); - } - list->payload[list->nr] = payload; - list->list[list->nr++] = value; -} - -static int find_in_list(struct list *list, char *value) -{ - int i; - - for (i = 0; i < list->nr; i++) - if (!strcmp(list->list[i], value)) - return i; - - return -1; -} - -static void free_list(struct list *list) -{ - int i; - - if (list->alloc == 0) - return; - - for (i = 0; i < list->nr; i++) { - free(list->list[i]); - free(list->payload[i]); - } - free(list->list); - free(list->payload); - list->nr = list->alloc = 0; -} - -struct src_data { - struct list branch, tag, r_branch, generic; - int head_status; -}; - -static struct list srcs = { NULL, NULL, 0, 0}; -static struct list origins = { NULL, NULL, 0, 0}; - -static int handle_line(char *line) -{ - int i, len = strlen(line); - unsigned char *sha1; - char *src, *origin; - struct src_data *src_data; - int pulling_head = 0; - - if (len < 43 || line[40] != '\t') - return 1; - - if (!prefixcmp(line + 41, "not-for-merge")) - return 0; - - if (line[41] != '\t') - return 2; - - line[40] = 0; - sha1 = xmalloc(20); - i = get_sha1(line, sha1); - line[40] = '\t'; - if (i) - return 3; - - if (line[len - 1] == '\n') - line[len - 1] = 0; - line += 42; - - src = strstr(line, " of "); - if (src) { - *src = 0; - src += 4; - pulling_head = 0; - } else { - src = line; - pulling_head = 1; - } - - i = find_in_list(&srcs, src); - if (i < 0) { - i = srcs.nr; - append_to_list(&srcs, xstrdup(src), - xcalloc(1, sizeof(struct src_data))); - } - src_data = srcs.payload[i]; - - if (pulling_head) { - origin = xstrdup(src); - src_data->head_status |= 1; - } else if (!prefixcmp(line, "branch ")) { - origin = xstrdup(line + 7); - append_to_list(&src_data->branch, origin, NULL); - src_data->head_status |= 2; - } else if (!prefixcmp(line, "tag ")) { - origin = line; - append_to_list(&src_data->tag, xstrdup(origin + 4), NULL); - src_data->head_status |= 2; - } else if (!prefixcmp(line, "remote branch ")) { - origin = xstrdup(line + 14); - append_to_list(&src_data->r_branch, origin, NULL); - src_data->head_status |= 2; - } else { - origin = xstrdup(src); - append_to_list(&src_data->generic, xstrdup(line), NULL); - src_data->head_status |= 2; - } - - if (!strcmp(".", src) || !strcmp(src, origin)) { - int len = strlen(origin); - if (origin[0] == '\'' && origin[len - 1] == '\'') { - origin = xmemdupz(origin + 1, len - 2); - } else { - origin = xstrdup(origin); - } - } else { - char *new_origin = xmalloc(strlen(origin) + strlen(src) + 5); - sprintf(new_origin, "%s of %s", origin, src); - origin = new_origin; - } - append_to_list(&origins, origin, sha1); - return 0; -} - -static void print_joined(const char *singular, const char *plural, - struct list *list, struct strbuf *out) -{ - if (list->nr == 0) - return; - if (list->nr == 1) { - strbuf_addf(out, "%s%s", singular, list->list[0]); - } else { - int i; - strbuf_addstr(out, plural); - for (i = 0; i < list->nr - 1; i++) - strbuf_addf(out, "%s%s", i > 0 ? ", " : "", list->list[i]); - strbuf_addf(out, " and %s", list->list[list->nr - 1]); - } -} - -static void shortlog(const char *name, unsigned char *sha1, - struct commit *head, struct rev_info *rev, int limit, - struct strbuf *out) -{ - int i, count = 0; - struct commit *commit; - struct object *branch; - struct list subjects = { NULL, NULL, 0, 0 }; - int flags = UNINTERESTING | TREESAME | SEEN | SHOWN | ADDED; - - branch = deref_tag(parse_object(sha1), sha1_to_hex(sha1), 40); - if (!branch || branch->type != OBJ_COMMIT) - return; - - setup_revisions(0, NULL, rev, NULL); - rev->ignore_merges = 1; - add_pending_object(rev, branch, name); - add_pending_object(rev, &head->object, "^HEAD"); - head->object.flags |= UNINTERESTING; - if (prepare_revision_walk(rev)) - die("revision walk setup failed"); - while ((commit = get_revision(rev)) != NULL) { - char *oneline, *bol, *eol; - - /* ignore merges */ - if (commit->parents && commit->parents->next) - continue; - - count++; - if (subjects.nr > limit) - continue; - - bol = strstr(commit->buffer, "\n\n"); - if (bol) { - unsigned char c; - do { - c = *++bol; - } while (isspace(c)); - if (!c) - bol = NULL; - } - - if (!bol) { - append_to_list(&subjects, xstrdup(sha1_to_hex( - commit->object.sha1)), - NULL); - continue; - } - - eol = strchr(bol, '\n'); - if (eol) { - oneline = xmemdupz(bol, eol - bol); - } else { - oneline = xstrdup(bol); - } - append_to_list(&subjects, oneline, NULL); - } - - if (count > limit) - strbuf_addf(out, "\n* %s: (%d commits)\n", name, count); - else - strbuf_addf(out, "\n* %s:\n", name); - - for (i = 0; i < subjects.nr; i++) - if (i >= limit) - strbuf_addf(out, " ...\n"); - else - strbuf_addf(out, " %s\n", subjects.list[i]); - - clear_commit_marks((struct commit *)branch, flags); - clear_commit_marks(head, flags); - free_commit_list(rev->commits); - rev->commits = NULL; - rev->pending.nr = 0; - - free_list(&subjects); -} - -int fmt_merge_msg(int merge_summary, struct strbuf *in, struct strbuf *out) { - int limit = 20, i = 0, pos = 0; - char *sep = ""; - unsigned char head_sha1[20]; - const char *current_branch; - - /* get current branch */ - current_branch = resolve_ref("HEAD", head_sha1, 1, NULL); - if (!current_branch) - die("No current branch"); - if (!prefixcmp(current_branch, "refs/heads/")) - current_branch += 11; - - /* get a line */ - while (pos < in->len) { - int len; - char *newline, *p = in->buf + pos; - - newline = strchr(p, '\n'); - len = newline ? newline - p : strlen(p); - pos += len + !!newline; - i++; - p[len] = 0; - if (handle_line(p)) - die ("Error in line %d: %.*s", i, len, p); - } - - strbuf_addstr(out, "Merge "); - for (i = 0; i < srcs.nr; i++) { - struct src_data *src_data = srcs.payload[i]; - const char *subsep = ""; - - strbuf_addstr(out, sep); - sep = "; "; - - if (src_data->head_status == 1) { - strbuf_addstr(out, srcs.list[i]); - continue; - } - if (src_data->head_status == 3) { - subsep = ", "; - strbuf_addstr(out, "HEAD"); - } - if (src_data->branch.nr) { - strbuf_addstr(out, subsep); - subsep = ", "; - print_joined("branch ", "branches ", &src_data->branch, - out); - } - if (src_data->r_branch.nr) { - strbuf_addstr(out, subsep); - subsep = ", "; - print_joined("remote branch ", "remote branches ", - &src_data->r_branch, out); - } - if (src_data->tag.nr) { - strbuf_addstr(out, subsep); - subsep = ", "; - print_joined("tag ", "tags ", &src_data->tag, out); - } - if (src_data->generic.nr) { - strbuf_addstr(out, subsep); - print_joined("commit ", "commits ", &src_data->generic, - out); - } - if (strcmp(".", srcs.list[i])) - strbuf_addf(out, " of %s", srcs.list[i]); - } - - if (!strcmp("master", current_branch)) - strbuf_addch(out, '\n'); - else - strbuf_addf(out, " into %s\n", current_branch); - - if (merge_summary) { - struct commit *head; - struct rev_info rev; - - head = lookup_commit(head_sha1); - init_revisions(&rev, NULL); - rev.commit_format = CMIT_FMT_ONELINE; - rev.ignore_merges = 1; - rev.limited = 1; - - for (i = 0; i < origins.nr; i++) - shortlog(origins.list[i], origins.payload[i], - head, &rev, limit, out); - } - return 0; -} - -int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix) -{ - const char *inpath = NULL; - struct option options[] = { - OPT_BOOLEAN(0, "log", &merge_summary, "populate log with the shortlog"), - OPT_BOOLEAN(0, "summary", &merge_summary, "alias for --log"), - OPT_FILENAME('F', "file", &inpath, "file to read from"), - OPT_END() - }; - - FILE *in = stdin; - struct strbuf input = STRBUF_INIT, output = STRBUF_INIT; - int ret; - - git_config(fmt_merge_msg_config, NULL); - argc = parse_options(argc, argv, prefix, options, fmt_merge_msg_usage, - 0); - if (argc > 0) - usage_with_options(fmt_merge_msg_usage, options); - - if (inpath && strcmp(inpath, "-")) { - in = fopen(inpath, "r"); - if (!in) - die_errno("cannot open '%s'", inpath); - } - - if (strbuf_read(&input, fileno(in), 0) < 0) - die_errno("could not read input file"); - ret = fmt_merge_msg(merge_summary, &input, &output); - if (ret) - return ret; - write_in_full(STDOUT_FILENO, output.buf, output.len); - return 0; -} diff --git a/builtin-for-each-ref.c b/builtin-for-each-ref.c deleted file mode 100644 index a5a83f1..0000000 --- a/builtin-for-each-ref.c +++ /dev/null @@ -1,955 +0,0 @@ -#include "builtin.h" -#include "cache.h" -#include "refs.h" -#include "object.h" -#include "tag.h" -#include "commit.h" -#include "tree.h" -#include "blob.h" -#include "quote.h" -#include "parse-options.h" -#include "remote.h" - -/* Quoting styles */ -#define QUOTE_NONE 0 -#define QUOTE_SHELL 1 -#define QUOTE_PERL 2 -#define QUOTE_PYTHON 4 -#define QUOTE_TCL 8 - -typedef enum { FIELD_STR, FIELD_ULONG, FIELD_TIME } cmp_type; - -struct atom_value { - const char *s; - unsigned long ul; /* used for sorting when not FIELD_STR */ -}; - -struct ref_sort { - struct ref_sort *next; - int atom; /* index into used_atom array */ - unsigned reverse : 1; -}; - -struct refinfo { - char *refname; - unsigned char objectname[20]; - struct atom_value *value; -}; - -static struct { - const char *name; - cmp_type cmp_type; -} valid_atom[] = { - { "refname" }, - { "objecttype" }, - { "objectsize", FIELD_ULONG }, - { "objectname" }, - { "tree" }, - { "parent" }, - { "numparent", FIELD_ULONG }, - { "object" }, - { "type" }, - { "tag" }, - { "author" }, - { "authorname" }, - { "authoremail" }, - { "authordate", FIELD_TIME }, - { "committer" }, - { "committername" }, - { "committeremail" }, - { "committerdate", FIELD_TIME }, - { "tagger" }, - { "taggername" }, - { "taggeremail" }, - { "taggerdate", FIELD_TIME }, - { "creator" }, - { "creatordate", FIELD_TIME }, - { "subject" }, - { "body" }, - { "contents" }, - { "upstream" }, -}; - -/* - * An atom is a valid field atom listed above, possibly prefixed with - * a "*" to denote deref_tag(). - * - * We parse given format string and sort specifiers, and make a list - * of properties that we need to extract out of objects. refinfo - * structure will hold an array of values extracted that can be - * indexed with the "atom number", which is an index into this - * array. - */ -static const char **used_atom; -static cmp_type *used_atom_type; -static int used_atom_cnt, sort_atom_limit, need_tagged; - -/* - * Used to parse format string and sort specifiers - */ -static int parse_atom(const char *atom, const char *ep) -{ - const char *sp; - int i, at; - - sp = atom; - if (*sp == '*' && sp < ep) - sp++; /* deref */ - if (ep <= sp) - die("malformed field name: %.*s", (int)(ep-atom), atom); - - /* Do we have the atom already used elsewhere? */ - for (i = 0; i < used_atom_cnt; i++) { - int len = strlen(used_atom[i]); - if (len == ep - atom && !memcmp(used_atom[i], atom, len)) - return i; - } - - /* Is the atom a valid one? */ - for (i = 0; i < ARRAY_SIZE(valid_atom); i++) { - int len = strlen(valid_atom[i].name); - /* - * If the atom name has a colon, strip it and everything after - * it off - it specifies the format for this entry, and - * shouldn't be used for checking against the valid_atom - * table. - */ - const char *formatp = strchr(sp, ':'); - if (!formatp || ep < formatp) - formatp = ep; - if (len == formatp - sp && !memcmp(valid_atom[i].name, sp, len)) - break; - } - - if (ARRAY_SIZE(valid_atom) <= i) - die("unknown field name: %.*s", (int)(ep-atom), atom); - - /* Add it in, including the deref prefix */ - at = used_atom_cnt; - used_atom_cnt++; - used_atom = xrealloc(used_atom, - (sizeof *used_atom) * used_atom_cnt); - used_atom_type = xrealloc(used_atom_type, - (sizeof(*used_atom_type) * used_atom_cnt)); - used_atom[at] = xmemdupz(atom, ep - atom); - used_atom_type[at] = valid_atom[i].cmp_type; - return at; -} - -/* - * In a format string, find the next occurrence of %(atom). - */ -static const char *find_next(const char *cp) -{ - while (*cp) { - if (*cp == '%') { - /* %( is the start of an atom; - * %% is a quoted per-cent. - */ - if (cp[1] == '(') - return cp; - else if (cp[1] == '%') - cp++; /* skip over two % */ - /* otherwise this is a singleton, literal % */ - } - cp++; - } - return NULL; -} - -/* - * Make sure the format string is well formed, and parse out - * the used atoms. - */ -static int verify_format(const char *format) -{ - const char *cp, *sp; - for (cp = format; *cp && (sp = find_next(cp)); ) { - const char *ep = strchr(sp, ')'); - if (!ep) - return error("malformed format string %s", sp); - /* sp points at "%(" and ep points at the closing ")" */ - parse_atom(sp + 2, ep); - cp = ep + 1; - } - return 0; -} - -/* - * Given an object name, read the object data and size, and return a - * "struct object". If the object data we are returning is also borrowed - * by the "struct object" representation, set *eaten as well---it is a - * signal from parse_object_buffer to us not to free the buffer. - */ -static void *get_obj(const unsigned char *sha1, struct object **obj, unsigned long *sz, int *eaten) -{ - enum object_type type; - void *buf = read_sha1_file(sha1, &type, sz); - - if (buf) - *obj = parse_object_buffer(sha1, type, *sz, buf, eaten); - else - *obj = NULL; - return buf; -} - -/* See grab_values */ -static void grab_common_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz) -{ - int i; - - for (i = 0; i < used_atom_cnt; i++) { - const char *name = used_atom[i]; - struct atom_value *v = &val[i]; - if (!!deref != (*name == '*')) - continue; - if (deref) - name++; - if (!strcmp(name, "objecttype")) - v->s = typename(obj->type); - else if (!strcmp(name, "objectsize")) { - char *s = xmalloc(40); - sprintf(s, "%lu", sz); - v->ul = sz; - v->s = s; - } - else if (!strcmp(name, "objectname")) { - char *s = xmalloc(41); - strcpy(s, sha1_to_hex(obj->sha1)); - v->s = s; - } - } -} - -/* See grab_values */ -static void grab_tag_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz) -{ - int i; - struct tag *tag = (struct tag *) obj; - - for (i = 0; i < used_atom_cnt; i++) { - const char *name = used_atom[i]; - struct atom_value *v = &val[i]; - if (!!deref != (*name == '*')) - continue; - if (deref) - name++; - if (!strcmp(name, "tag")) - v->s = tag->tag; - else if (!strcmp(name, "type") && tag->tagged) - v->s = typename(tag->tagged->type); - else if (!strcmp(name, "object") && tag->tagged) { - char *s = xmalloc(41); - strcpy(s, sha1_to_hex(tag->tagged->sha1)); - v->s = s; - } - } -} - -static int num_parents(struct commit *commit) -{ - struct commit_list *parents; - int i; - - for (i = 0, parents = commit->parents; - parents; - parents = parents->next) - i++; - return i; -} - -/* See grab_values */ -static void grab_commit_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz) -{ - int i; - struct commit *commit = (struct commit *) obj; - - for (i = 0; i < used_atom_cnt; i++) { - const char *name = used_atom[i]; - struct atom_value *v = &val[i]; - if (!!deref != (*name == '*')) - continue; - if (deref) - name++; - if (!strcmp(name, "tree")) { - char *s = xmalloc(41); - strcpy(s, sha1_to_hex(commit->tree->object.sha1)); - v->s = s; - } - if (!strcmp(name, "numparent")) { - char *s = xmalloc(40); - v->ul = num_parents(commit); - sprintf(s, "%lu", v->ul); - v->s = s; - } - else if (!strcmp(name, "parent")) { - int num = num_parents(commit); - int i; - struct commit_list *parents; - char *s = xmalloc(41 * num + 1); - v->s = s; - for (i = 0, parents = commit->parents; - parents; - parents = parents->next, i = i + 41) { - struct commit *parent = parents->item; - strcpy(s+i, sha1_to_hex(parent->object.sha1)); - if (parents->next) - s[i+40] = ' '; - } - if (!i) - *s = '\0'; - } - } -} - -static const char *find_wholine(const char *who, int wholen, const char *buf, unsigned long sz) -{ - const char *eol; - while (*buf) { - if (!strncmp(buf, who, wholen) && - buf[wholen] == ' ') - return buf + wholen + 1; - eol = strchr(buf, '\n'); - if (!eol) - return ""; - eol++; - if (*eol == '\n') - return ""; /* end of header */ - buf = eol; - } - return ""; -} - -static const char *copy_line(const char *buf) -{ - const char *eol = strchrnul(buf, '\n'); - return xmemdupz(buf, eol - buf); -} - -static const char *copy_name(const char *buf) -{ - const char *cp; - for (cp = buf; *cp && *cp != '\n'; cp++) { - if (!strncmp(cp, " <", 2)) - return xmemdupz(buf, cp - buf); - } - return ""; -} - -static const char *copy_email(const char *buf) -{ - const char *email = strchr(buf, '<'); - const char *eoemail; - if (!email) - return ""; - eoemail = strchr(email, '>'); - if (!eoemail) - return ""; - return xmemdupz(email, eoemail + 1 - email); -} - -static void grab_date(const char *buf, struct atom_value *v, const char *atomname) -{ - const char *eoemail = strstr(buf, "> "); - char *zone; - unsigned long timestamp; - long tz; - enum date_mode date_mode = DATE_NORMAL; - const char *formatp; - - /* - * We got here because atomname ends in "date" or "date"; - * it's not possible that is not ":" because - * parse_atom() wouldn't have allowed it, so we can assume that no - * ":" means no format is specified, and use the default. - */ - formatp = strchr(atomname, ':'); - if (formatp != NULL) { - formatp++; - date_mode = parse_date_format(formatp); - } - - if (!eoemail) - goto bad; - timestamp = strtoul(eoemail + 2, &zone, 10); - if (timestamp == ULONG_MAX) - goto bad; - tz = strtol(zone, NULL, 10); - if ((tz == LONG_MIN || tz == LONG_MAX) && errno == ERANGE) - goto bad; - v->s = xstrdup(show_date(timestamp, tz, date_mode)); - v->ul = timestamp; - return; - bad: - v->s = ""; - v->ul = 0; -} - -/* See grab_values */ -static void grab_person(const char *who, struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz) -{ - int i; - int wholen = strlen(who); - const char *wholine = NULL; - - for (i = 0; i < used_atom_cnt; i++) { - const char *name = used_atom[i]; - struct atom_value *v = &val[i]; - if (!!deref != (*name == '*')) - continue; - if (deref) - name++; - if (strncmp(who, name, wholen)) - continue; - if (name[wholen] != 0 && - strcmp(name + wholen, "name") && - strcmp(name + wholen, "email") && - prefixcmp(name + wholen, "date")) - continue; - if (!wholine) - wholine = find_wholine(who, wholen, buf, sz); - if (!wholine) - return; /* no point looking for it */ - if (name[wholen] == 0) - v->s = copy_line(wholine); - else if (!strcmp(name + wholen, "name")) - v->s = copy_name(wholine); - else if (!strcmp(name + wholen, "email")) - v->s = copy_email(wholine); - else if (!prefixcmp(name + wholen, "date")) - grab_date(wholine, v, name); - } - - /* For a tag or a commit object, if "creator" or "creatordate" is - * requested, do something special. - */ - if (strcmp(who, "tagger") && strcmp(who, "committer")) - return; /* "author" for commit object is not wanted */ - if (!wholine) - wholine = find_wholine(who, wholen, buf, sz); - if (!wholine) - return; - for (i = 0; i < used_atom_cnt; i++) { - const char *name = used_atom[i]; - struct atom_value *v = &val[i]; - if (!!deref != (*name == '*')) - continue; - if (deref) - name++; - - if (!prefixcmp(name, "creatordate")) - grab_date(wholine, v, name); - else if (!strcmp(name, "creator")) - v->s = copy_line(wholine); - } -} - -static void find_subpos(const char *buf, unsigned long sz, const char **sub, const char **body) -{ - while (*buf) { - const char *eol = strchr(buf, '\n'); - if (!eol) - return; - if (eol[1] == '\n') { - buf = eol + 1; - break; /* found end of header */ - } - buf = eol + 1; - } - while (*buf == '\n') - buf++; - if (!*buf) - return; - *sub = buf; /* first non-empty line */ - buf = strchr(buf, '\n'); - if (!buf) { - *body = ""; - return; /* no body */ - } - while (*buf == '\n') - buf++; /* skip blank between subject and body */ - *body = buf; -} - -/* See grab_values */ -static void grab_sub_body_contents(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz) -{ - int i; - const char *subpos = NULL, *bodypos = NULL; - - for (i = 0; i < used_atom_cnt; i++) { - const char *name = used_atom[i]; - struct atom_value *v = &val[i]; - if (!!deref != (*name == '*')) - continue; - if (deref) - name++; - if (strcmp(name, "subject") && - strcmp(name, "body") && - strcmp(name, "contents")) - continue; - if (!subpos) - find_subpos(buf, sz, &subpos, &bodypos); - if (!subpos) - return; - - if (!strcmp(name, "subject")) - v->s = copy_line(subpos); - else if (!strcmp(name, "body")) - v->s = xstrdup(bodypos); - else if (!strcmp(name, "contents")) - v->s = xstrdup(subpos); - } -} - -/* We want to have empty print-string for field requests - * that do not apply (e.g. "authordate" for a tag object) - */ -static void fill_missing_values(struct atom_value *val) -{ - int i; - for (i = 0; i < used_atom_cnt; i++) { - struct atom_value *v = &val[i]; - if (v->s == NULL) - v->s = ""; - } -} - -/* - * val is a list of atom_value to hold returned values. Extract - * the values for atoms in used_atom array out of (obj, buf, sz). - * when deref is false, (obj, buf, sz) is the object that is - * pointed at by the ref itself; otherwise it is the object the - * ref (which is a tag) refers to. - */ -static void grab_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz) -{ - grab_common_values(val, deref, obj, buf, sz); - switch (obj->type) { - case OBJ_TAG: - grab_tag_values(val, deref, obj, buf, sz); - grab_sub_body_contents(val, deref, obj, buf, sz); - grab_person("tagger", val, deref, obj, buf, sz); - break; - case OBJ_COMMIT: - grab_commit_values(val, deref, obj, buf, sz); - grab_sub_body_contents(val, deref, obj, buf, sz); - grab_person("author", val, deref, obj, buf, sz); - grab_person("committer", val, deref, obj, buf, sz); - break; - case OBJ_TREE: - // grab_tree_values(val, deref, obj, buf, sz); - break; - case OBJ_BLOB: - // grab_blob_values(val, deref, obj, buf, sz); - break; - default: - die("Eh? Object of type %d?", obj->type); - } -} - -/* - * Parse the object referred by ref, and grab needed value. - */ -static void populate_value(struct refinfo *ref) -{ - void *buf; - struct object *obj; - int eaten, i; - unsigned long size; - const unsigned char *tagged; - - ref->value = xcalloc(sizeof(struct atom_value), used_atom_cnt); - - /* Fill in specials first */ - for (i = 0; i < used_atom_cnt; i++) { - const char *name = used_atom[i]; - struct atom_value *v = &ref->value[i]; - int deref = 0; - const char *refname; - const char *formatp; - - if (*name == '*') { - deref = 1; - name++; - } - - if (!prefixcmp(name, "refname")) - refname = ref->refname; - else if (!prefixcmp(name, "upstream")) { - struct branch *branch; - /* only local branches may have an upstream */ - if (prefixcmp(ref->refname, "refs/heads/")) - continue; - branch = branch_get(ref->refname + 11); - - if (!branch || !branch->merge || !branch->merge[0] || - !branch->merge[0]->dst) - continue; - refname = branch->merge[0]->dst; - } - else - continue; - - formatp = strchr(name, ':'); - /* look for "short" refname format */ - if (formatp) { - formatp++; - if (!strcmp(formatp, "short")) - refname = shorten_unambiguous_ref(refname, - warn_ambiguous_refs); - else - die("unknown %.*s format %s", - (int)(formatp - name), name, formatp); - } - - if (!deref) - v->s = refname; - else { - int len = strlen(refname); - char *s = xmalloc(len + 4); - sprintf(s, "%s^{}", refname); - v->s = s; - } - } - - for (i = 0; i < used_atom_cnt; i++) { - struct atom_value *v = &ref->value[i]; - if (v->s == NULL) - goto need_obj; - } - return; - - need_obj: - buf = get_obj(ref->objectname, &obj, &size, &eaten); - if (!buf) - die("missing object %s for %s", - sha1_to_hex(ref->objectname), ref->refname); - if (!obj) - die("parse_object_buffer failed on %s for %s", - sha1_to_hex(ref->objectname), ref->refname); - - grab_values(ref->value, 0, obj, buf, size); - if (!eaten) - free(buf); - - /* If there is no atom that wants to know about tagged - * object, we are done. - */ - if (!need_tagged || (obj->type != OBJ_TAG)) - return; - - /* If it is a tag object, see if we use a value that derefs - * the object, and if we do grab the object it refers to. - */ - tagged = ((struct tag *)obj)->tagged->sha1; - - /* NEEDSWORK: This derefs tag only once, which - * is good to deal with chains of trust, but - * is not consistent with what deref_tag() does - * which peels the onion to the core. - */ - buf = get_obj(tagged, &obj, &size, &eaten); - if (!buf) - die("missing object %s for %s", - sha1_to_hex(tagged), ref->refname); - if (!obj) - die("parse_object_buffer failed on %s for %s", - sha1_to_hex(tagged), ref->refname); - grab_values(ref->value, 1, obj, buf, size); - if (!eaten) - free(buf); -} - -/* - * Given a ref, return the value for the atom. This lazily gets value - * out of the object by calling populate value. - */ -static void get_value(struct refinfo *ref, int atom, struct atom_value **v) -{ - if (!ref->value) { - populate_value(ref); - fill_missing_values(ref->value); - } - *v = &ref->value[atom]; -} - -struct grab_ref_cbdata { - struct refinfo **grab_array; - const char **grab_pattern; - int grab_cnt; -}; - -/* - * A call-back given to for_each_ref(). It is unfortunate that we - * need to use global variables to pass extra information to this - * function. - */ -static int grab_single_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data) -{ - struct grab_ref_cbdata *cb = cb_data; - struct refinfo *ref; - int cnt; - - if (*cb->grab_pattern) { - const char **pattern; - int namelen = strlen(refname); - for (pattern = cb->grab_pattern; *pattern; pattern++) { - const char *p = *pattern; - int plen = strlen(p); - - if ((plen <= namelen) && - !strncmp(refname, p, plen) && - (refname[plen] == '\0' || - refname[plen] == '/' || - p[plen-1] == '/')) - break; - if (!fnmatch(p, refname, FNM_PATHNAME)) - break; - } - if (!*pattern) - return 0; - } - - /* We do not open the object yet; sort may only need refname - * to do its job and the resulting list may yet to be pruned - * by maxcount logic. - */ - ref = xcalloc(1, sizeof(*ref)); - ref->refname = xstrdup(refname); - hashcpy(ref->objectname, sha1); - - cnt = cb->grab_cnt; - cb->grab_array = xrealloc(cb->grab_array, - sizeof(*cb->grab_array) * (cnt + 1)); - cb->grab_array[cnt++] = ref; - cb->grab_cnt = cnt; - return 0; -} - -static int cmp_ref_sort(struct ref_sort *s, struct refinfo *a, struct refinfo *b) -{ - struct atom_value *va, *vb; - int cmp; - cmp_type cmp_type = used_atom_type[s->atom]; - - get_value(a, s->atom, &va); - get_value(b, s->atom, &vb); - switch (cmp_type) { - case FIELD_STR: - cmp = strcmp(va->s, vb->s); - break; - default: - if (va->ul < vb->ul) - cmp = -1; - else if (va->ul == vb->ul) - cmp = 0; - else - cmp = 1; - break; - } - return (s->reverse) ? -cmp : cmp; -} - -static struct ref_sort *ref_sort; -static int compare_refs(const void *a_, const void *b_) -{ - struct refinfo *a = *((struct refinfo **)a_); - struct refinfo *b = *((struct refinfo **)b_); - struct ref_sort *s; - - for (s = ref_sort; s; s = s->next) { - int cmp = cmp_ref_sort(s, a, b); - if (cmp) - return cmp; - } - return 0; -} - -static void sort_refs(struct ref_sort *sort, struct refinfo **refs, int num_refs) -{ - ref_sort = sort; - qsort(refs, num_refs, sizeof(struct refinfo *), compare_refs); -} - -static void print_value(struct refinfo *ref, int atom, int quote_style) -{ - struct atom_value *v; - get_value(ref, atom, &v); - switch (quote_style) { - case QUOTE_NONE: - fputs(v->s, stdout); - break; - case QUOTE_SHELL: - sq_quote_print(stdout, v->s); - break; - case QUOTE_PERL: - perl_quote_print(stdout, v->s); - break; - case QUOTE_PYTHON: - python_quote_print(stdout, v->s); - break; - case QUOTE_TCL: - tcl_quote_print(stdout, v->s); - break; - } -} - -static int hex1(char ch) -{ - if ('0' <= ch && ch <= '9') - return ch - '0'; - else if ('a' <= ch && ch <= 'f') - return ch - 'a' + 10; - else if ('A' <= ch && ch <= 'F') - return ch - 'A' + 10; - return -1; -} -static int hex2(const char *cp) -{ - if (cp[0] && cp[1]) - return (hex1(cp[0]) << 4) | hex1(cp[1]); - else - return -1; -} - -static void emit(const char *cp, const char *ep) -{ - while (*cp && (!ep || cp < ep)) { - if (*cp == '%') { - if (cp[1] == '%') - cp++; - else { - int ch = hex2(cp + 1); - if (0 <= ch) { - putchar(ch); - cp += 3; - continue; - } - } - } - putchar(*cp); - cp++; - } -} - -static void show_ref(struct refinfo *info, const char *format, int quote_style) -{ - const char *cp, *sp, *ep; - - for (cp = format; *cp && (sp = find_next(cp)); cp = ep + 1) { - ep = strchr(sp, ')'); - if (cp < sp) - emit(cp, sp); - print_value(info, parse_atom(sp + 2, ep), quote_style); - } - if (*cp) { - sp = cp + strlen(cp); - emit(cp, sp); - } - putchar('\n'); -} - -static struct ref_sort *default_sort(void) -{ - static const char cstr_name[] = "refname"; - - struct ref_sort *sort = xcalloc(1, sizeof(*sort)); - - sort->next = NULL; - sort->atom = parse_atom(cstr_name, cstr_name + strlen(cstr_name)); - return sort; -} - -static int opt_parse_sort(const struct option *opt, const char *arg, int unset) -{ - struct ref_sort **sort_tail = opt->value; - struct ref_sort *s; - int len; - - if (!arg) /* should --no-sort void the list ? */ - return -1; - - *sort_tail = s = xcalloc(1, sizeof(*s)); - - if (*arg == '-') { - s->reverse = 1; - arg++; - } - len = strlen(arg); - s->atom = parse_atom(arg, arg+len); - return 0; -} - -static char const * const for_each_ref_usage[] = { - "git for-each-ref [options] []", - NULL -}; - -int cmd_for_each_ref(int argc, const char **argv, const char *prefix) -{ - int i, num_refs; - const char *format = "%(objectname) %(objecttype)\t%(refname)"; - struct ref_sort *sort = NULL, **sort_tail = &sort; - int maxcount = 0, quote_style = 0; - struct refinfo **refs; - struct grab_ref_cbdata cbdata; - - struct option opts[] = { - OPT_BIT('s', "shell", "e_style, - "quote placeholders suitably for shells", QUOTE_SHELL), - OPT_BIT('p', "perl", "e_style, - "quote placeholders suitably for perl", QUOTE_PERL), - OPT_BIT(0 , "python", "e_style, - "quote placeholders suitably for python", QUOTE_PYTHON), - OPT_BIT(0 , "tcl", "e_style, - "quote placeholders suitably for tcl", QUOTE_TCL), - - OPT_GROUP(""), - OPT_INTEGER( 0 , "count", &maxcount, "show only matched refs"), - OPT_STRING( 0 , "format", &format, "format", "format to use for the output"), - OPT_CALLBACK(0 , "sort", sort_tail, "key", - "field name to sort on", &opt_parse_sort), - OPT_END(), - }; - - parse_options(argc, argv, prefix, opts, for_each_ref_usage, 0); - if (maxcount < 0) { - error("invalid --count argument: `%d'", maxcount); - usage_with_options(for_each_ref_usage, opts); - } - if (HAS_MULTI_BITS(quote_style)) { - error("more than one quoting style?"); - usage_with_options(for_each_ref_usage, opts); - } - if (verify_format(format)) - usage_with_options(for_each_ref_usage, opts); - - if (!sort) - sort = default_sort(); - sort_atom_limit = used_atom_cnt; - - /* for warn_ambiguous_refs */ - git_config(git_default_config, NULL); - - memset(&cbdata, 0, sizeof(cbdata)); - cbdata.grab_pattern = argv; - for_each_rawref(grab_single_ref, &cbdata); - refs = cbdata.grab_array; - num_refs = cbdata.grab_cnt; - - for (i = 0; i < used_atom_cnt; i++) { - if (used_atom[i][0] == '*') { - need_tagged = 1; - break; - } - } - - sort_refs(sort, refs, num_refs); - - if (!maxcount || num_refs < maxcount) - maxcount = num_refs; - for (i = 0; i < maxcount; i++) - show_ref(refs[i], format, quote_style); - return 0; -} diff --git a/builtin-fsck.c b/builtin-fsck.c deleted file mode 100644 index 0929c7f..0000000 --- a/builtin-fsck.c +++ /dev/null @@ -1,684 +0,0 @@ -#include "builtin.h" -#include "cache.h" -#include "commit.h" -#include "tree.h" -#include "blob.h" -#include "tag.h" -#include "refs.h" -#include "pack.h" -#include "cache-tree.h" -#include "tree-walk.h" -#include "fsck.h" -#include "parse-options.h" -#include "dir.h" - -#define REACHABLE 0x0001 -#define SEEN 0x0002 - -static int show_root; -static int show_tags; -static int show_unreachable; -static int include_reflogs = 1; -static int check_full = 1; -static int check_strict; -static int keep_cache_objects; -static unsigned char head_sha1[20]; -static const char *head_points_at; -static int errors_found; -static int write_lost_and_found; -static int verbose; -#define ERROR_OBJECT 01 -#define ERROR_REACHABLE 02 - -#ifdef NO_D_INO_IN_DIRENT -#define SORT_DIRENT 0 -#define DIRENT_SORT_HINT(de) 0 -#else -#define SORT_DIRENT 1 -#define DIRENT_SORT_HINT(de) ((de)->d_ino) -#endif - -static void objreport(struct object *obj, const char *severity, - const char *err, va_list params) -{ - fprintf(stderr, "%s in %s %s: ", - severity, typename(obj->type), sha1_to_hex(obj->sha1)); - vfprintf(stderr, err, params); - fputs("\n", stderr); -} - -__attribute__((format (printf, 2, 3))) -static int objerror(struct object *obj, const char *err, ...) -{ - va_list params; - va_start(params, err); - errors_found |= ERROR_OBJECT; - objreport(obj, "error", err, params); - va_end(params); - return -1; -} - -__attribute__((format (printf, 3, 4))) -static int fsck_error_func(struct object *obj, int type, const char *err, ...) -{ - va_list params; - va_start(params, err); - objreport(obj, (type == FSCK_WARN) ? "warning" : "error", err, params); - va_end(params); - return (type == FSCK_WARN) ? 0 : 1; -} - -static struct object_array pending; - -static int mark_object(struct object *obj, int type, void *data) -{ - struct object *parent = data; - - if (!obj) { - printf("broken link from %7s %s\n", - typename(parent->type), sha1_to_hex(parent->sha1)); - printf("broken link from %7s %s\n", - (type == OBJ_ANY ? "unknown" : typename(type)), "unknown"); - errors_found |= ERROR_REACHABLE; - return 1; - } - - if (type != OBJ_ANY && obj->type != type) - objerror(parent, "wrong object type in link"); - - if (obj->flags & REACHABLE) - return 0; - obj->flags |= REACHABLE; - if (!obj->parsed) { - if (parent && !has_sha1_file(obj->sha1)) { - printf("broken link from %7s %s\n", - typename(parent->type), sha1_to_hex(parent->sha1)); - printf(" to %7s %s\n", - typename(obj->type), sha1_to_hex(obj->sha1)); - errors_found |= ERROR_REACHABLE; - } - return 1; - } - - add_object_array(obj, (void *) parent, &pending); - return 0; -} - -static void mark_object_reachable(struct object *obj) -{ - mark_object(obj, OBJ_ANY, NULL); -} - -static int traverse_one_object(struct object *obj, struct object *parent) -{ - int result; - struct tree *tree = NULL; - - if (obj->type == OBJ_TREE) { - obj->parsed = 0; - tree = (struct tree *)obj; - if (parse_tree(tree) < 0) - return 1; /* error already displayed */ - } - result = fsck_walk(obj, mark_object, obj); - if (tree) { - free(tree->buffer); - tree->buffer = NULL; - } - return result; -} - -static int traverse_reachable(void) -{ - int result = 0; - while (pending.nr) { - struct object_array_entry *entry; - struct object *obj, *parent; - - entry = pending.objects + --pending.nr; - obj = entry->item; - parent = (struct object *) entry->name; - result |= traverse_one_object(obj, parent); - } - return !!result; -} - -static int mark_used(struct object *obj, int type, void *data) -{ - if (!obj) - return 1; - obj->used = 1; - return 0; -} - -/* - * Check a single reachable object - */ -static void check_reachable_object(struct object *obj) -{ - /* - * We obviously want the object to be parsed, - * except if it was in a pack-file and we didn't - * do a full fsck - */ - if (!obj->parsed) { - if (has_sha1_pack(obj->sha1)) - return; /* it is in pack - forget about it */ - printf("missing %s %s\n", typename(obj->type), sha1_to_hex(obj->sha1)); - errors_found |= ERROR_REACHABLE; - return; - } -} - -/* - * Check a single unreachable object - */ -static void check_unreachable_object(struct object *obj) -{ - /* - * Missing unreachable object? Ignore it. It's not like - * we miss it (since it can't be reached), nor do we want - * to complain about it being unreachable (since it does - * not exist). - */ - if (!obj->parsed) - return; - - /* - * Unreachable object that exists? Show it if asked to, - * since this is something that is prunable. - */ - if (show_unreachable) { - printf("unreachable %s %s\n", typename(obj->type), sha1_to_hex(obj->sha1)); - return; - } - - /* - * "!used" means that nothing at all points to it, including - * other unreachable objects. In other words, it's the "tip" - * of some set of unreachable objects, usually a commit that - * got dropped. - * - * Such starting points are more interesting than some random - * set of unreachable objects, so we show them even if the user - * hasn't asked for _all_ unreachable objects. If you have - * deleted a branch by mistake, this is a prime candidate to - * start looking at, for example. - */ - if (!obj->used) { - printf("dangling %s %s\n", typename(obj->type), - sha1_to_hex(obj->sha1)); - if (write_lost_and_found) { - char *filename = git_path("lost-found/%s/%s", - obj->type == OBJ_COMMIT ? "commit" : "other", - sha1_to_hex(obj->sha1)); - FILE *f; - - if (safe_create_leading_directories(filename)) { - error("Could not create lost-found"); - return; - } - if (!(f = fopen(filename, "w"))) - die_errno("Could not open '%s'", filename); - if (obj->type == OBJ_BLOB) { - enum object_type type; - unsigned long size; - char *buf = read_sha1_file(obj->sha1, - &type, &size); - if (buf) { - if (fwrite(buf, size, 1, f) != 1) - die_errno("Could not write '%s'", - filename); - free(buf); - } - } else - fprintf(f, "%s\n", sha1_to_hex(obj->sha1)); - if (fclose(f)) - die_errno("Could not finish '%s'", - filename); - } - return; - } - - /* - * Otherwise? It's there, it's unreachable, and some other unreachable - * object points to it. Ignore it - it's not interesting, and we showed - * all the interesting cases above. - */ -} - -static void check_object(struct object *obj) -{ - if (verbose) - fprintf(stderr, "Checking %s\n", sha1_to_hex(obj->sha1)); - - if (obj->flags & REACHABLE) - check_reachable_object(obj); - else - check_unreachable_object(obj); -} - -static void check_connectivity(void) -{ - int i, max; - - /* Traverse the pending reachable objects */ - traverse_reachable(); - - /* Look up all the requirements, warn about missing objects.. */ - max = get_max_object_index(); - if (verbose) - fprintf(stderr, "Checking connectivity (%d objects)\n", max); - - for (i = 0; i < max; i++) { - struct object *obj = get_indexed_object(i); - - if (obj) - check_object(obj); - } -} - -static int fsck_sha1(const unsigned char *sha1) -{ - struct object *obj = parse_object(sha1); - if (!obj) { - errors_found |= ERROR_OBJECT; - return error("%s: object corrupt or missing", - sha1_to_hex(sha1)); - } - if (obj->flags & SEEN) - return 0; - obj->flags |= SEEN; - - if (verbose) - fprintf(stderr, "Checking %s %s\n", - typename(obj->type), sha1_to_hex(obj->sha1)); - - if (fsck_walk(obj, mark_used, NULL)) - objerror(obj, "broken links"); - if (fsck_object(obj, check_strict, fsck_error_func)) - return -1; - - if (obj->type == OBJ_TREE) { - struct tree *item = (struct tree *) obj; - - free(item->buffer); - item->buffer = NULL; - } - - if (obj->type == OBJ_COMMIT) { - struct commit *commit = (struct commit *) obj; - - free(commit->buffer); - commit->buffer = NULL; - - if (!commit->parents && show_root) - printf("root %s\n", sha1_to_hex(commit->object.sha1)); - } - - if (obj->type == OBJ_TAG) { - struct tag *tag = (struct tag *) obj; - - if (show_tags && tag->tagged) { - printf("tagged %s %s", typename(tag->tagged->type), sha1_to_hex(tag->tagged->sha1)); - printf(" (%s) in %s\n", tag->tag, sha1_to_hex(tag->object.sha1)); - } - } - - return 0; -} - -/* - * This is the sorting chunk size: make it reasonably - * big so that we can sort well.. - */ -#define MAX_SHA1_ENTRIES (1024) - -struct sha1_entry { - unsigned long ino; - unsigned char sha1[20]; -}; - -static struct { - unsigned long nr; - struct sha1_entry *entry[MAX_SHA1_ENTRIES]; -} sha1_list; - -static int ino_compare(const void *_a, const void *_b) -{ - const struct sha1_entry *a = _a, *b = _b; - unsigned long ino1 = a->ino, ino2 = b->ino; - return ino1 < ino2 ? -1 : ino1 > ino2 ? 1 : 0; -} - -static void fsck_sha1_list(void) -{ - int i, nr = sha1_list.nr; - - if (SORT_DIRENT) - qsort(sha1_list.entry, nr, - sizeof(struct sha1_entry *), ino_compare); - for (i = 0; i < nr; i++) { - struct sha1_entry *entry = sha1_list.entry[i]; - unsigned char *sha1 = entry->sha1; - - sha1_list.entry[i] = NULL; - fsck_sha1(sha1); - free(entry); - } - sha1_list.nr = 0; -} - -static void add_sha1_list(unsigned char *sha1, unsigned long ino) -{ - struct sha1_entry *entry = xmalloc(sizeof(*entry)); - int nr; - - entry->ino = ino; - hashcpy(entry->sha1, sha1); - nr = sha1_list.nr; - if (nr == MAX_SHA1_ENTRIES) { - fsck_sha1_list(); - nr = 0; - } - sha1_list.entry[nr] = entry; - sha1_list.nr = ++nr; -} - -static void fsck_dir(int i, char *path) -{ - DIR *dir = opendir(path); - struct dirent *de; - - if (!dir) - return; - - if (verbose) - fprintf(stderr, "Checking directory %s\n", path); - - while ((de = readdir(dir)) != NULL) { - char name[100]; - unsigned char sha1[20]; - - if (is_dot_or_dotdot(de->d_name)) - continue; - if (strlen(de->d_name) == 38) { - sprintf(name, "%02x", i); - memcpy(name+2, de->d_name, 39); - if (get_sha1_hex(name, sha1) < 0) - break; - add_sha1_list(sha1, DIRENT_SORT_HINT(de)); - continue; - } - if (!prefixcmp(de->d_name, "tmp_obj_")) - continue; - fprintf(stderr, "bad sha1 file: %s/%s\n", path, de->d_name); - } - closedir(dir); -} - -static int default_refs; - -static int fsck_handle_reflog_ent(unsigned char *osha1, unsigned char *nsha1, - const char *email, unsigned long timestamp, int tz, - const char *message, void *cb_data) -{ - struct object *obj; - - if (verbose) - fprintf(stderr, "Checking reflog %s->%s\n", - sha1_to_hex(osha1), sha1_to_hex(nsha1)); - - if (!is_null_sha1(osha1)) { - obj = lookup_object(osha1); - if (obj) { - obj->used = 1; - mark_object_reachable(obj); - } - } - obj = lookup_object(nsha1); - if (obj) { - obj->used = 1; - mark_object_reachable(obj); - } - return 0; -} - -static int fsck_handle_reflog(const char *logname, const unsigned char *sha1, int flag, void *cb_data) -{ - for_each_reflog_ent(logname, fsck_handle_reflog_ent, NULL); - return 0; -} - -static int is_branch(const char *refname) -{ - return !strcmp(refname, "HEAD") || !prefixcmp(refname, "refs/heads/"); -} - -static int fsck_handle_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data) -{ - struct object *obj; - - obj = parse_object(sha1); - if (!obj) { - error("%s: invalid sha1 pointer %s", refname, sha1_to_hex(sha1)); - /* We'll continue with the rest despite the error.. */ - return 0; - } - if (obj->type != OBJ_COMMIT && is_branch(refname)) - error("%s: not a commit", refname); - default_refs++; - obj->used = 1; - mark_object_reachable(obj); - - return 0; -} - -static void get_default_heads(void) -{ - if (head_points_at && !is_null_sha1(head_sha1)) - fsck_handle_ref("HEAD", head_sha1, 0, NULL); - for_each_ref(fsck_handle_ref, NULL); - if (include_reflogs) - for_each_reflog(fsck_handle_reflog, NULL); - - /* - * Not having any default heads isn't really fatal, but - * it does mean that "--unreachable" no longer makes any - * sense (since in this case everything will obviously - * be unreachable by definition. - * - * Showing dangling objects is valid, though (as those - * dangling objects are likely lost heads). - * - * So we just print a warning about it, and clear the - * "show_unreachable" flag. - */ - if (!default_refs) { - fprintf(stderr, "notice: No default references\n"); - show_unreachable = 0; - } -} - -static void fsck_object_dir(const char *path) -{ - int i; - - if (verbose) - fprintf(stderr, "Checking object directory\n"); - - for (i = 0; i < 256; i++) { - static char dir[4096]; - sprintf(dir, "%s/%02x", path, i); - fsck_dir(i, dir); - } - fsck_sha1_list(); -} - -static int fsck_head_link(void) -{ - int flag; - int null_is_error = 0; - - if (verbose) - fprintf(stderr, "Checking HEAD link\n"); - - head_points_at = resolve_ref("HEAD", head_sha1, 0, &flag); - if (!head_points_at) - return error("Invalid HEAD"); - if (!strcmp(head_points_at, "HEAD")) - /* detached HEAD */ - null_is_error = 1; - else if (prefixcmp(head_points_at, "refs/heads/")) - return error("HEAD points to something strange (%s)", - head_points_at); - if (is_null_sha1(head_sha1)) { - if (null_is_error) - return error("HEAD: detached HEAD points at nothing"); - fprintf(stderr, "notice: HEAD points to an unborn branch (%s)\n", - head_points_at + 11); - } - return 0; -} - -static int fsck_cache_tree(struct cache_tree *it) -{ - int i; - int err = 0; - - if (verbose) - fprintf(stderr, "Checking cache tree\n"); - - if (0 <= it->entry_count) { - struct object *obj = parse_object(it->sha1); - if (!obj) { - error("%s: invalid sha1 pointer in cache-tree", - sha1_to_hex(it->sha1)); - return 1; - } - mark_object_reachable(obj); - obj->used = 1; - if (obj->type != OBJ_TREE) - err |= objerror(obj, "non-tree in cache-tree"); - } - for (i = 0; i < it->subtree_nr; i++) - err |= fsck_cache_tree(it->down[i]->cache_tree); - return err; -} - -static char const * const fsck_usage[] = { - "git fsck [options] [...]", - NULL -}; - -static struct option fsck_opts[] = { - OPT__VERBOSE(&verbose), - OPT_BOOLEAN(0, "unreachable", &show_unreachable, "show unreachable objects"), - OPT_BOOLEAN(0, "tags", &show_tags, "report tags"), - OPT_BOOLEAN(0, "root", &show_root, "report root nodes"), - OPT_BOOLEAN(0, "cache", &keep_cache_objects, "make index objects head nodes"), - OPT_BOOLEAN(0, "reflogs", &include_reflogs, "make reflogs head nodes (default)"), - OPT_BOOLEAN(0, "full", &check_full, "also consider packs and alternate objects"), - OPT_BOOLEAN(0, "strict", &check_strict, "enable more strict checking"), - OPT_BOOLEAN(0, "lost-found", &write_lost_and_found, - "write dangling objects in .git/lost-found"), - OPT_END(), -}; - -int cmd_fsck(int argc, const char **argv, const char *prefix) -{ - int i, heads; - struct alternate_object_database *alt; - - errors_found = 0; - read_replace_refs = 0; - - argc = parse_options(argc, argv, prefix, fsck_opts, fsck_usage, 0); - if (write_lost_and_found) { - check_full = 1; - include_reflogs = 0; - } - - fsck_head_link(); - fsck_object_dir(get_object_directory()); - - prepare_alt_odb(); - for (alt = alt_odb_list; alt; alt = alt->next) { - char namebuf[PATH_MAX]; - int namelen = alt->name - alt->base; - memcpy(namebuf, alt->base, namelen); - namebuf[namelen - 1] = 0; - fsck_object_dir(namebuf); - } - - if (check_full) { - struct packed_git *p; - - prepare_packed_git(); - for (p = packed_git; p; p = p->next) - /* verify gives error messages itself */ - verify_pack(p); - - for (p = packed_git; p; p = p->next) { - uint32_t j, num; - if (open_pack_index(p)) - continue; - num = p->num_objects; - for (j = 0; j < num; j++) - fsck_sha1(nth_packed_object_sha1(p, j)); - } - } - - heads = 0; - for (i = 0; i < argc; i++) { - const char *arg = argv[i]; - unsigned char sha1[20]; - if (!get_sha1(arg, sha1)) { - struct object *obj = lookup_object(sha1); - - /* Error is printed by lookup_object(). */ - if (!obj) - continue; - - obj->used = 1; - mark_object_reachable(obj); - heads++; - continue; - } - error("invalid parameter: expected sha1, got '%s'", arg); - } - - /* - * If we've not been given any explicit head information, do the - * default ones from .git/refs. We also consider the index file - * in this case (ie this implies --cache). - */ - if (!heads) { - get_default_heads(); - keep_cache_objects = 1; - } - - if (keep_cache_objects) { - read_cache(); - for (i = 0; i < active_nr; i++) { - unsigned int mode; - struct blob *blob; - struct object *obj; - - mode = active_cache[i]->ce_mode; - if (S_ISGITLINK(mode)) - continue; - blob = lookup_blob(active_cache[i]->sha1); - if (!blob) - continue; - obj = &blob->object; - obj->used = 1; - mark_object_reachable(obj); - } - if (active_cache_tree) - fsck_cache_tree(active_cache_tree); - } - - check_connectivity(); - return errors_found; -} diff --git a/builtin-gc.c b/builtin-gc.c deleted file mode 100644 index c304638..0000000 --- a/builtin-gc.c +++ /dev/null @@ -1,255 +0,0 @@ -/* - * git gc builtin command - * - * Cleanup unreachable files and optimize the repository. - * - * Copyright (c) 2007 James Bowes - * - * Based on git-gc.sh, which is - * - * Copyright (c) 2006 Shawn O. Pearce - */ - -#include "builtin.h" -#include "cache.h" -#include "parse-options.h" -#include "run-command.h" - -#define FAILED_RUN "failed to run %s" - -static const char * const builtin_gc_usage[] = { - "git gc [options]", - NULL -}; - -static int pack_refs = 1; -static int aggressive_window = 250; -static int gc_auto_threshold = 6700; -static int gc_auto_pack_limit = 50; -static const char *prune_expire = "2.weeks.ago"; - -#define MAX_ADD 10 -static const char *argv_pack_refs[] = {"pack-refs", "--all", "--prune", NULL}; -static const char *argv_reflog[] = {"reflog", "expire", "--all", NULL}; -static const char *argv_repack[MAX_ADD] = {"repack", "-d", "-l", NULL}; -static const char *argv_prune[] = {"prune", "--expire", NULL, NULL}; -static const char *argv_rerere[] = {"rerere", "gc", NULL}; - -static int gc_config(const char *var, const char *value, void *cb) -{ - if (!strcmp(var, "gc.packrefs")) { - if (value && !strcmp(value, "notbare")) - pack_refs = -1; - else - pack_refs = git_config_bool(var, value); - return 0; - } - if (!strcmp(var, "gc.aggressivewindow")) { - aggressive_window = git_config_int(var, value); - return 0; - } - if (!strcmp(var, "gc.auto")) { - gc_auto_threshold = git_config_int(var, value); - return 0; - } - if (!strcmp(var, "gc.autopacklimit")) { - gc_auto_pack_limit = git_config_int(var, value); - return 0; - } - if (!strcmp(var, "gc.pruneexpire")) { - if (value && strcmp(value, "now")) { - unsigned long now = approxidate("now"); - if (approxidate(value) >= now) - return error("Invalid %s: '%s'", var, value); - } - return git_config_string(&prune_expire, var, value); - } - return git_default_config(var, value, cb); -} - -static void append_option(const char **cmd, const char *opt, int max_length) -{ - int i; - - for (i = 0; cmd[i]; i++) - ; - - if (i + 2 >= max_length) - die("Too many options specified"); - cmd[i++] = opt; - cmd[i] = NULL; -} - -static int too_many_loose_objects(void) -{ - /* - * Quickly check if a "gc" is needed, by estimating how - * many loose objects there are. Because SHA-1 is evenly - * distributed, we can check only one and get a reasonable - * estimate. - */ - char path[PATH_MAX]; - const char *objdir = get_object_directory(); - DIR *dir; - struct dirent *ent; - int auto_threshold; - int num_loose = 0; - int needed = 0; - - if (gc_auto_threshold <= 0) - return 0; - - if (sizeof(path) <= snprintf(path, sizeof(path), "%s/17", objdir)) { - warning("insanely long object directory %.*s", 50, objdir); - return 0; - } - dir = opendir(path); - if (!dir) - return 0; - - auto_threshold = (gc_auto_threshold + 255) / 256; - while ((ent = readdir(dir)) != NULL) { - if (strspn(ent->d_name, "0123456789abcdef") != 38 || - ent->d_name[38] != '\0') - continue; - if (++num_loose > auto_threshold) { - needed = 1; - break; - } - } - closedir(dir); - return needed; -} - -static int too_many_packs(void) -{ - struct packed_git *p; - int cnt; - - if (gc_auto_pack_limit <= 0) - return 0; - - prepare_packed_git(); - for (cnt = 0, p = packed_git; p; p = p->next) { - if (!p->pack_local) - continue; - if (p->pack_keep) - continue; - /* - * Perhaps check the size of the pack and count only - * very small ones here? - */ - cnt++; - } - return gc_auto_pack_limit <= cnt; -} - -static int need_to_gc(void) -{ - /* - * Setting gc.auto to 0 or negative can disable the - * automatic gc. - */ - if (gc_auto_threshold <= 0) - return 0; - - /* - * If there are too many loose objects, but not too many - * packs, we run "repack -d -l". If there are too many packs, - * we run "repack -A -d -l". Otherwise we tell the caller - * there is no need. - */ - if (too_many_packs()) - append_option(argv_repack, - prune_expire && !strcmp(prune_expire, "now") ? - "-a" : "-A", - MAX_ADD); - else if (!too_many_loose_objects()) - return 0; - - if (run_hook(NULL, "pre-auto-gc", NULL)) - return 0; - return 1; -} - -int cmd_gc(int argc, const char **argv, const char *prefix) -{ - int aggressive = 0; - int auto_gc = 0; - int quiet = 0; - char buf[80]; - - struct option builtin_gc_options[] = { - OPT__QUIET(&quiet), - { OPTION_STRING, 0, "prune", &prune_expire, "date", - "prune unreferenced objects", - PARSE_OPT_OPTARG, NULL, (intptr_t)prune_expire }, - OPT_BOOLEAN(0, "aggressive", &aggressive, "be more thorough (increased runtime)"), - OPT_BOOLEAN(0, "auto", &auto_gc, "enable auto-gc mode"), - OPT_END() - }; - - git_config(gc_config, NULL); - - if (pack_refs < 0) - pack_refs = !is_bare_repository(); - - argc = parse_options(argc, argv, prefix, builtin_gc_options, - builtin_gc_usage, 0); - if (argc > 0) - usage_with_options(builtin_gc_usage, builtin_gc_options); - - if (aggressive) { - append_option(argv_repack, "-f", MAX_ADD); - append_option(argv_repack, "--depth=250", MAX_ADD); - if (aggressive_window > 0) { - sprintf(buf, "--window=%d", aggressive_window); - append_option(argv_repack, buf, MAX_ADD); - } - } - if (quiet) - append_option(argv_repack, "-q", MAX_ADD); - - if (auto_gc) { - /* - * Auto-gc should be least intrusive as possible. - */ - if (!need_to_gc()) - return 0; - fprintf(stderr, - "Auto packing the repository for optimum performance.%s\n", - quiet - ? "" - : (" You may also\n" - "run \"git gc\" manually. See " - "\"git help gc\" for more information.")); - } else - append_option(argv_repack, - prune_expire && !strcmp(prune_expire, "now") - ? "-a" : "-A", - MAX_ADD); - - if (pack_refs && run_command_v_opt(argv_pack_refs, RUN_GIT_CMD)) - return error(FAILED_RUN, argv_pack_refs[0]); - - if (run_command_v_opt(argv_reflog, RUN_GIT_CMD)) - return error(FAILED_RUN, argv_reflog[0]); - - if (run_command_v_opt(argv_repack, RUN_GIT_CMD)) - return error(FAILED_RUN, argv_repack[0]); - - if (prune_expire) { - argv_prune[2] = prune_expire; - if (run_command_v_opt(argv_prune, RUN_GIT_CMD)) - return error(FAILED_RUN, argv_prune[0]); - } - - if (run_command_v_opt(argv_rerere, RUN_GIT_CMD)) - return error(FAILED_RUN, argv_rerere[0]); - - if (auto_gc && too_many_loose_objects()) - warning("There are too many unreachable loose objects; " - "run 'git prune' to remove them."); - - return 0; -} diff --git a/builtin-grep.c b/builtin-grep.c deleted file mode 100644 index 552ef1f..0000000 --- a/builtin-grep.c +++ /dev/null @@ -1,1010 +0,0 @@ -/* - * Builtin "git grep" - * - * Copyright (c) 2006 Junio C Hamano - */ -#include "cache.h" -#include "blob.h" -#include "tree.h" -#include "commit.h" -#include "tag.h" -#include "tree-walk.h" -#include "builtin.h" -#include "parse-options.h" -#include "userdiff.h" -#include "grep.h" -#include "quote.h" -#include "dir.h" - -#ifndef NO_PTHREADS -#include "thread-utils.h" -#include -#endif - -static char const * const grep_usage[] = { - "git grep [options] [-e] [...] [[--] path...]", - NULL -}; - -static int use_threads = 1; - -#ifndef NO_PTHREADS -#define THREADS 8 -static pthread_t threads[THREADS]; - -static void *load_sha1(const unsigned char *sha1, unsigned long *size, - const char *name); -static void *load_file(const char *filename, size_t *sz); - -enum work_type {WORK_SHA1, WORK_FILE}; - -/* We use one producer thread and THREADS consumer - * threads. The producer adds struct work_items to 'todo' and the - * consumers pick work items from the same array. - */ -struct work_item -{ - enum work_type type; - char *name; - - /* if type == WORK_SHA1, then 'identifier' is a SHA1, - * otherwise type == WORK_FILE, and 'identifier' is a NUL - * terminated filename. - */ - void *identifier; - char done; - struct strbuf out; -}; - -/* In the range [todo_done, todo_start) in 'todo' we have work_items - * that have been or are processed by a consumer thread. We haven't - * written the result for these to stdout yet. - * - * The work_items in [todo_start, todo_end) are waiting to be picked - * up by a consumer thread. - * - * The ranges are modulo TODO_SIZE. - */ -#define TODO_SIZE 128 -static struct work_item todo[TODO_SIZE]; -static int todo_start; -static int todo_end; -static int todo_done; - -/* Has all work items been added? */ -static int all_work_added; - -/* This lock protects all the variables above. */ -static pthread_mutex_t grep_mutex; - -/* Used to serialize calls to read_sha1_file. */ -static pthread_mutex_t read_sha1_mutex; - -#define grep_lock() pthread_mutex_lock(&grep_mutex) -#define grep_unlock() pthread_mutex_unlock(&grep_mutex) -#define read_sha1_lock() pthread_mutex_lock(&read_sha1_mutex) -#define read_sha1_unlock() pthread_mutex_unlock(&read_sha1_mutex) - -/* Signalled when a new work_item is added to todo. */ -static pthread_cond_t cond_add; - -/* Signalled when the result from one work_item is written to - * stdout. - */ -static pthread_cond_t cond_write; - -/* Signalled when we are finished with everything. */ -static pthread_cond_t cond_result; - -static void add_work(enum work_type type, char *name, void *id) -{ - grep_lock(); - - while ((todo_end+1) % ARRAY_SIZE(todo) == todo_done) { - pthread_cond_wait(&cond_write, &grep_mutex); - } - - todo[todo_end].type = type; - todo[todo_end].name = name; - todo[todo_end].identifier = id; - todo[todo_end].done = 0; - strbuf_reset(&todo[todo_end].out); - todo_end = (todo_end + 1) % ARRAY_SIZE(todo); - - pthread_cond_signal(&cond_add); - grep_unlock(); -} - -static struct work_item *get_work(void) -{ - struct work_item *ret; - - grep_lock(); - while (todo_start == todo_end && !all_work_added) { - pthread_cond_wait(&cond_add, &grep_mutex); - } - - if (todo_start == todo_end && all_work_added) { - ret = NULL; - } else { - ret = &todo[todo_start]; - todo_start = (todo_start + 1) % ARRAY_SIZE(todo); - } - grep_unlock(); - return ret; -} - -static void grep_sha1_async(struct grep_opt *opt, char *name, - const unsigned char *sha1) -{ - unsigned char *s; - s = xmalloc(20); - memcpy(s, sha1, 20); - add_work(WORK_SHA1, name, s); -} - -static void grep_file_async(struct grep_opt *opt, char *name, - const char *filename) -{ - add_work(WORK_FILE, name, xstrdup(filename)); -} - -static void work_done(struct work_item *w) -{ - int old_done; - - grep_lock(); - w->done = 1; - old_done = todo_done; - for(; todo[todo_done].done && todo_done != todo_start; - todo_done = (todo_done+1) % ARRAY_SIZE(todo)) { - w = &todo[todo_done]; - write_or_die(1, w->out.buf, w->out.len); - free(w->name); - free(w->identifier); - } - - if (old_done != todo_done) - pthread_cond_signal(&cond_write); - - if (all_work_added && todo_done == todo_end) - pthread_cond_signal(&cond_result); - - grep_unlock(); -} - -static void *run(void *arg) -{ - int hit = 0; - struct grep_opt *opt = arg; - - while (1) { - struct work_item *w = get_work(); - if (!w) - break; - - opt->output_priv = w; - if (w->type == WORK_SHA1) { - unsigned long sz; - void* data = load_sha1(w->identifier, &sz, w->name); - - if (data) { - hit |= grep_buffer(opt, w->name, data, sz); - free(data); - } - } else if (w->type == WORK_FILE) { - size_t sz; - void* data = load_file(w->identifier, &sz); - if (data) { - hit |= grep_buffer(opt, w->name, data, sz); - free(data); - } - } else { - assert(0); - } - - work_done(w); - } - free_grep_patterns(arg); - free(arg); - - return (void*) (intptr_t) hit; -} - -static void strbuf_out(struct grep_opt *opt, const void *buf, size_t size) -{ - struct work_item *w = opt->output_priv; - strbuf_add(&w->out, buf, size); -} - -static void start_threads(struct grep_opt *opt) -{ - int i; - - pthread_mutex_init(&grep_mutex, NULL); - pthread_mutex_init(&read_sha1_mutex, NULL); - pthread_cond_init(&cond_add, NULL); - pthread_cond_init(&cond_write, NULL); - pthread_cond_init(&cond_result, NULL); - - for (i = 0; i < ARRAY_SIZE(todo); i++) { - strbuf_init(&todo[i].out, 0); - } - - for (i = 0; i < ARRAY_SIZE(threads); i++) { - int err; - struct grep_opt *o = grep_opt_dup(opt); - o->output = strbuf_out; - compile_grep_patterns(o); - err = pthread_create(&threads[i], NULL, run, o); - - if (err) - die("grep: failed to create thread: %s", - strerror(err)); - } -} - -static int wait_all(void) -{ - int hit = 0; - int i; - - grep_lock(); - all_work_added = 1; - - /* Wait until all work is done. */ - while (todo_done != todo_end) - pthread_cond_wait(&cond_result, &grep_mutex); - - /* Wake up all the consumer threads so they can see that there - * is no more work to do. - */ - pthread_cond_broadcast(&cond_add); - grep_unlock(); - - for (i = 0; i < ARRAY_SIZE(threads); i++) { - void *h; - pthread_join(threads[i], &h); - hit |= (int) (intptr_t) h; - } - - pthread_mutex_destroy(&grep_mutex); - pthread_mutex_destroy(&read_sha1_mutex); - pthread_cond_destroy(&cond_add); - pthread_cond_destroy(&cond_write); - pthread_cond_destroy(&cond_result); - - return hit; -} -#else /* !NO_PTHREADS */ -#define read_sha1_lock() -#define read_sha1_unlock() - -static int wait_all(void) -{ - return 0; -} -#endif - -static int grep_config(const char *var, const char *value, void *cb) -{ - struct grep_opt *opt = cb; - - switch (userdiff_config(var, value)) { - case 0: break; - case -1: return -1; - default: return 0; - } - - if (!strcmp(var, "color.grep")) { - opt->color = git_config_colorbool(var, value, -1); - return 0; - } - if (!strcmp(var, "color.grep.match")) { - if (!value) - return config_error_nonbool(var); - color_parse(value, var, opt->color_match); - return 0; - } - return git_color_default_config(var, value, cb); -} - -/* - * Return non-zero if max_depth is negative or path has no more then max_depth - * slashes. - */ -static int accept_subdir(const char *path, int max_depth) -{ - if (max_depth < 0) - return 1; - - while ((path = strchr(path, '/')) != NULL) { - max_depth--; - if (max_depth < 0) - return 0; - path++; - } - return 1; -} - -/* - * Return non-zero if name is a subdirectory of match and is not too deep. - */ -static int is_subdir(const char *name, int namelen, - const char *match, int matchlen, int max_depth) -{ - if (matchlen > namelen || strncmp(name, match, matchlen)) - return 0; - - if (name[matchlen] == '\0') /* exact match */ - return 1; - - if (!matchlen || match[matchlen-1] == '/' || name[matchlen] == '/') - return accept_subdir(name + matchlen + 1, max_depth); - - return 0; -} - -/* - * git grep pathspecs are somewhat different from diff-tree pathspecs; - * pathname wildcards are allowed. - */ -static int pathspec_matches(const char **paths, const char *name, int max_depth) -{ - int namelen, i; - if (!paths || !*paths) - return accept_subdir(name, max_depth); - namelen = strlen(name); - for (i = 0; paths[i]; i++) { - const char *match = paths[i]; - int matchlen = strlen(match); - const char *cp, *meta; - - if (is_subdir(name, namelen, match, matchlen, max_depth)) - return 1; - if (!fnmatch(match, name, 0)) - return 1; - if (name[namelen-1] != '/') - continue; - - /* We are being asked if the directory ("name") is worth - * descending into. - * - * Find the longest leading directory name that does - * not have metacharacter in the pathspec; the name - * we are looking at must overlap with that directory. - */ - for (cp = match, meta = NULL; cp - match < matchlen; cp++) { - char ch = *cp; - if (ch == '*' || ch == '[' || ch == '?') { - meta = cp; - break; - } - } - if (!meta) - meta = cp; /* fully literal */ - - if (namelen <= meta - match) { - /* Looking at "Documentation/" and - * the pattern says "Documentation/howto/", or - * "Documentation/diff*.txt". The name we - * have should match prefix. - */ - if (!memcmp(match, name, namelen)) - return 1; - continue; - } - - if (meta - match < namelen) { - /* Looking at "Documentation/howto/" and - * the pattern says "Documentation/h*"; - * match up to "Do.../h"; this avoids descending - * into "Documentation/technical/". - */ - if (!memcmp(match, name, meta - match)) - return 1; - continue; - } - } - return 0; -} - -static void *lock_and_read_sha1_file(const unsigned char *sha1, enum object_type *type, unsigned long *size) -{ - void *data; - - if (use_threads) { - read_sha1_lock(); - data = read_sha1_file(sha1, type, size); - read_sha1_unlock(); - } else { - data = read_sha1_file(sha1, type, size); - } - return data; -} - -static void *load_sha1(const unsigned char *sha1, unsigned long *size, - const char *name) -{ - enum object_type type; - void *data = lock_and_read_sha1_file(sha1, &type, size); - - if (!data) - error("'%s': unable to read %s", name, sha1_to_hex(sha1)); - - return data; -} - -static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1, - const char *filename, int tree_name_len) -{ - struct strbuf pathbuf = STRBUF_INIT; - char *name; - - if (opt->relative && opt->prefix_length) { - quote_path_relative(filename + tree_name_len, -1, &pathbuf, - opt->prefix); - strbuf_insert(&pathbuf, 0, filename, tree_name_len); - } else { - strbuf_addstr(&pathbuf, filename); - } - - name = strbuf_detach(&pathbuf, NULL); - -#ifndef NO_PTHREADS - if (use_threads) { - grep_sha1_async(opt, name, sha1); - return 0; - } else -#endif - { - int hit; - unsigned long sz; - void *data = load_sha1(sha1, &sz, name); - if (!data) - hit = 0; - else - hit = grep_buffer(opt, name, data, sz); - - free(data); - free(name); - return hit; - } -} - -static void *load_file(const char *filename, size_t *sz) -{ - struct stat st; - char *data; - int i; - - if (lstat(filename, &st) < 0) { - err_ret: - if (errno != ENOENT) - error("'%s': %s", filename, strerror(errno)); - return 0; - } - if (!S_ISREG(st.st_mode)) - return 0; - *sz = xsize_t(st.st_size); - i = open(filename, O_RDONLY); - if (i < 0) - goto err_ret; - data = xmalloc(*sz + 1); - if (st.st_size != read_in_full(i, data, *sz)) { - error("'%s': short read %s", filename, strerror(errno)); - close(i); - free(data); - return 0; - } - close(i); - data[*sz] = 0; - return data; -} - -static int grep_file(struct grep_opt *opt, const char *filename) -{ - struct strbuf buf = STRBUF_INIT; - char *name; - - if (opt->relative && opt->prefix_length) - quote_path_relative(filename, -1, &buf, opt->prefix); - else - strbuf_addstr(&buf, filename); - name = strbuf_detach(&buf, NULL); - -#ifndef NO_PTHREADS - if (use_threads) { - grep_file_async(opt, name, filename); - return 0; - } else -#endif - { - int hit; - size_t sz; - void *data = load_file(filename, &sz); - if (!data) - hit = 0; - else - hit = grep_buffer(opt, name, data, sz); - - free(data); - free(name); - return hit; - } -} - -static int grep_cache(struct grep_opt *opt, const char **paths, int cached) -{ - int hit = 0; - int nr; - read_cache(); - - for (nr = 0; nr < active_nr; nr++) { - struct cache_entry *ce = active_cache[nr]; - if (!S_ISREG(ce->ce_mode)) - continue; - if (!pathspec_matches(paths, ce->name, opt->max_depth)) - continue; - /* - * If CE_VALID is on, we assume worktree file and its cache entry - * are identical, even if worktree file has been modified, so use - * cache version instead - */ - if (cached || (ce->ce_flags & CE_VALID) || ce_skip_worktree(ce)) { - if (ce_stage(ce)) - continue; - hit |= grep_sha1(opt, ce->sha1, ce->name, 0); - } - else - hit |= grep_file(opt, ce->name); - if (ce_stage(ce)) { - do { - nr++; - } while (nr < active_nr && - !strcmp(ce->name, active_cache[nr]->name)); - nr--; /* compensate for loop control */ - } - if (hit && opt->status_only) - break; - } - free_grep_patterns(opt); - return hit; -} - -static int grep_tree(struct grep_opt *opt, const char **paths, - struct tree_desc *tree, - const char *tree_name, const char *base) -{ - int len; - int hit = 0; - struct name_entry entry; - char *down; - int tn_len = strlen(tree_name); - struct strbuf pathbuf; - - strbuf_init(&pathbuf, PATH_MAX + tn_len); - - if (tn_len) { - strbuf_add(&pathbuf, tree_name, tn_len); - strbuf_addch(&pathbuf, ':'); - tn_len = pathbuf.len; - } - strbuf_addstr(&pathbuf, base); - len = pathbuf.len; - - while (tree_entry(tree, &entry)) { - int te_len = tree_entry_len(entry.path, entry.sha1); - pathbuf.len = len; - strbuf_add(&pathbuf, entry.path, te_len); - - if (S_ISDIR(entry.mode)) - /* Match "abc/" against pathspec to - * decide if we want to descend into "abc" - * directory. - */ - strbuf_addch(&pathbuf, '/'); - - down = pathbuf.buf + tn_len; - if (!pathspec_matches(paths, down, opt->max_depth)) - ; - else if (S_ISREG(entry.mode)) - hit |= grep_sha1(opt, entry.sha1, pathbuf.buf, tn_len); - else if (S_ISDIR(entry.mode)) { - enum object_type type; - struct tree_desc sub; - void *data; - unsigned long size; - - data = lock_and_read_sha1_file(entry.sha1, &type, &size); - if (!data) - die("unable to read tree (%s)", - sha1_to_hex(entry.sha1)); - init_tree_desc(&sub, data, size); - hit |= grep_tree(opt, paths, &sub, tree_name, down); - free(data); - } - if (hit && opt->status_only) - break; - } - strbuf_release(&pathbuf); - return hit; -} - -static int grep_object(struct grep_opt *opt, const char **paths, - struct object *obj, const char *name) -{ - if (obj->type == OBJ_BLOB) - return grep_sha1(opt, obj->sha1, name, 0); - if (obj->type == OBJ_COMMIT || obj->type == OBJ_TREE) { - struct tree_desc tree; - void *data; - unsigned long size; - int hit; - data = read_object_with_reference(obj->sha1, tree_type, - &size, NULL); - if (!data) - die("unable to read tree (%s)", sha1_to_hex(obj->sha1)); - init_tree_desc(&tree, data, size); - hit = grep_tree(opt, paths, &tree, name, ""); - free(data); - return hit; - } - die("unable to grep from object of type %s", typename(obj->type)); -} - -static int grep_directory(struct grep_opt *opt, const char **paths) -{ - struct dir_struct dir; - int i, hit = 0; - - memset(&dir, 0, sizeof(dir)); - setup_standard_excludes(&dir); - - fill_directory(&dir, paths); - for (i = 0; i < dir.nr; i++) { - hit |= grep_file(opt, dir.entries[i]->name); - if (hit && opt->status_only) - break; - } - free_grep_patterns(opt); - return hit; -} - -static int context_callback(const struct option *opt, const char *arg, - int unset) -{ - struct grep_opt *grep_opt = opt->value; - int value; - const char *endp; - - if (unset) { - grep_opt->pre_context = grep_opt->post_context = 0; - return 0; - } - value = strtol(arg, (char **)&endp, 10); - if (*endp) { - return error("switch `%c' expects a numerical value", - opt->short_name); - } - grep_opt->pre_context = grep_opt->post_context = value; - return 0; -} - -static int file_callback(const struct option *opt, const char *arg, int unset) -{ - struct grep_opt *grep_opt = opt->value; - FILE *patterns; - int lno = 0; - struct strbuf sb = STRBUF_INIT; - - patterns = fopen(arg, "r"); - if (!patterns) - die_errno("cannot open '%s'", arg); - while (strbuf_getline(&sb, patterns, '\n') == 0) { - /* ignore empty line like grep does */ - if (sb.len == 0) - continue; - append_grep_pattern(grep_opt, strbuf_detach(&sb, NULL), arg, - ++lno, GREP_PATTERN); - } - fclose(patterns); - strbuf_release(&sb); - return 0; -} - -static int not_callback(const struct option *opt, const char *arg, int unset) -{ - struct grep_opt *grep_opt = opt->value; - append_grep_pattern(grep_opt, "--not", "command line", 0, GREP_NOT); - return 0; -} - -static int and_callback(const struct option *opt, const char *arg, int unset) -{ - struct grep_opt *grep_opt = opt->value; - append_grep_pattern(grep_opt, "--and", "command line", 0, GREP_AND); - return 0; -} - -static int open_callback(const struct option *opt, const char *arg, int unset) -{ - struct grep_opt *grep_opt = opt->value; - append_grep_pattern(grep_opt, "(", "command line", 0, GREP_OPEN_PAREN); - return 0; -} - -static int close_callback(const struct option *opt, const char *arg, int unset) -{ - struct grep_opt *grep_opt = opt->value; - append_grep_pattern(grep_opt, ")", "command line", 0, GREP_CLOSE_PAREN); - return 0; -} - -static int pattern_callback(const struct option *opt, const char *arg, - int unset) -{ - struct grep_opt *grep_opt = opt->value; - append_grep_pattern(grep_opt, arg, "-e option", 0, GREP_PATTERN); - return 0; -} - -static int help_callback(const struct option *opt, const char *arg, int unset) -{ - return -1; -} - -int cmd_grep(int argc, const char **argv, const char *prefix) -{ - int hit = 0; - int cached = 0; - int seen_dashdash = 0; - int external_grep_allowed__ignored; - struct grep_opt opt; - struct object_array list = { 0, 0, NULL }; - const char **paths = NULL; - int i; - int dummy; - int nongit = 0, use_index = 1; - struct option options[] = { - OPT_BOOLEAN(0, "cached", &cached, - "search in index instead of in the work tree"), - OPT_BOOLEAN(0, "index", &use_index, - "--no-index finds in contents not managed by git"), - OPT_GROUP(""), - OPT_BOOLEAN('v', "invert-match", &opt.invert, - "show non-matching lines"), - OPT_BOOLEAN('i', "ignore-case", &opt.ignore_case, - "case insensitive matching"), - OPT_BOOLEAN('w', "word-regexp", &opt.word_regexp, - "match patterns only at word boundaries"), - OPT_SET_INT('a', "text", &opt.binary, - "process binary files as text", GREP_BINARY_TEXT), - OPT_SET_INT('I', NULL, &opt.binary, - "don't match patterns in binary files", - GREP_BINARY_NOMATCH), - { OPTION_INTEGER, 0, "max-depth", &opt.max_depth, "depth", - "descend at most levels", PARSE_OPT_NONEG, - NULL, 1 }, - OPT_GROUP(""), - OPT_BIT('E', "extended-regexp", &opt.regflags, - "use extended POSIX regular expressions", REG_EXTENDED), - OPT_NEGBIT('G', "basic-regexp", &opt.regflags, - "use basic POSIX regular expressions (default)", - REG_EXTENDED), - OPT_BOOLEAN('F', "fixed-strings", &opt.fixed, - "interpret patterns as fixed strings"), - OPT_GROUP(""), - OPT_BOOLEAN('n', NULL, &opt.linenum, "show line numbers"), - OPT_NEGBIT('h', NULL, &opt.pathname, "don't show filenames", 1), - OPT_BIT('H', NULL, &opt.pathname, "show filenames", 1), - OPT_NEGBIT(0, "full-name", &opt.relative, - "show filenames relative to top directory", 1), - OPT_BOOLEAN('l', "files-with-matches", &opt.name_only, - "show only filenames instead of matching lines"), - OPT_BOOLEAN(0, "name-only", &opt.name_only, - "synonym for --files-with-matches"), - OPT_BOOLEAN('L', "files-without-match", - &opt.unmatch_name_only, - "show only the names of files without match"), - OPT_BOOLEAN('z', "null", &opt.null_following_name, - "print NUL after filenames"), - OPT_BOOLEAN('c', "count", &opt.count, - "show the number of matches instead of matching lines"), - OPT_SET_INT(0, "color", &opt.color, "highlight matches", 1), - OPT_GROUP(""), - OPT_CALLBACK('C', NULL, &opt, "n", - "show context lines before and after matches", - context_callback), - OPT_INTEGER('B', NULL, &opt.pre_context, - "show context lines before matches"), - OPT_INTEGER('A', NULL, &opt.post_context, - "show context lines after matches"), - OPT_NUMBER_CALLBACK(&opt, "shortcut for -C NUM", - context_callback), - OPT_BOOLEAN('p', "show-function", &opt.funcname, - "show a line with the function name before matches"), - OPT_GROUP(""), - OPT_CALLBACK('f', NULL, &opt, "file", - "read patterns from file", file_callback), - { OPTION_CALLBACK, 'e', NULL, &opt, "pattern", - "match ", PARSE_OPT_NONEG, pattern_callback }, - { OPTION_CALLBACK, 0, "and", &opt, NULL, - "combine patterns specified with -e", - PARSE_OPT_NOARG | PARSE_OPT_NONEG, and_callback }, - OPT_BOOLEAN(0, "or", &dummy, ""), - { OPTION_CALLBACK, 0, "not", &opt, NULL, "", - PARSE_OPT_NOARG | PARSE_OPT_NONEG, not_callback }, - { OPTION_CALLBACK, '(', NULL, &opt, NULL, "", - PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_NODASH, - open_callback }, - { OPTION_CALLBACK, ')', NULL, &opt, NULL, "", - PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_NODASH, - close_callback }, - OPT_BOOLEAN('q', "quiet", &opt.status_only, - "indicate hit with exit status without output"), - OPT_BOOLEAN(0, "all-match", &opt.all_match, - "show only matches from files that match all patterns"), - OPT_GROUP(""), - OPT_BOOLEAN(0, "ext-grep", &external_grep_allowed__ignored, - "allow calling of grep(1) (ignored by this build)"), - { OPTION_CALLBACK, 0, "help-all", &options, NULL, "show usage", - PARSE_OPT_HIDDEN | PARSE_OPT_NOARG, help_callback }, - OPT_END() - }; - - prefix = setup_git_directory_gently(&nongit); - - /* - * 'git grep -h', unlike 'git grep -h ', is a request - * to show usage information and exit. - */ - if (argc == 2 && !strcmp(argv[1], "-h")) - usage_with_options(grep_usage, options); - - memset(&opt, 0, sizeof(opt)); - opt.prefix = prefix; - opt.prefix_length = (prefix && *prefix) ? strlen(prefix) : 0; - opt.relative = 1; - opt.pathname = 1; - opt.pattern_tail = &opt.pattern_list; - opt.regflags = REG_NEWLINE; - opt.max_depth = -1; - - strcpy(opt.color_match, GIT_COLOR_RED GIT_COLOR_BOLD); - opt.color = -1; - git_config(grep_config, &opt); - if (opt.color == -1) - opt.color = git_use_color_default; - - /* - * If there is no -- then the paths must exist in the working - * tree. If there is no explicit pattern specified with -e or - * -f, we take the first unrecognized non option to be the - * pattern, but then what follows it must be zero or more - * valid refs up to the -- (if exists), and then existing - * paths. If there is an explicit pattern, then the first - * unrecognized non option is the beginning of the refs list - * that continues up to the -- (if exists), and then paths. - */ - argc = parse_options(argc, argv, prefix, options, grep_usage, - PARSE_OPT_KEEP_DASHDASH | - PARSE_OPT_STOP_AT_NON_OPTION | - PARSE_OPT_NO_INTERNAL_HELP); - - if (use_index && nongit) - /* die the same way as if we did it at the beginning */ - setup_git_directory(); - - /* - * skip a -- separator; we know it cannot be - * separating revisions from pathnames if - * we haven't even had any patterns yet - */ - if (argc > 0 && !opt.pattern_list && !strcmp(argv[0], "--")) { - argv++; - argc--; - } - - /* First unrecognized non-option token */ - if (argc > 0 && !opt.pattern_list) { - append_grep_pattern(&opt, argv[0], "command line", 0, - GREP_PATTERN); - argv++; - argc--; - } - - if (!opt.pattern_list) - die("no pattern given."); - if (!opt.fixed && opt.ignore_case) - opt.regflags |= REG_ICASE; - if ((opt.regflags != REG_NEWLINE) && opt.fixed) - die("cannot mix --fixed-strings and regexp"); - -#ifndef NO_PTHREADS - if (online_cpus() == 1 || !grep_threads_ok(&opt)) - use_threads = 0; - - if (use_threads) - start_threads(&opt); -#else - use_threads = 0; -#endif - - compile_grep_patterns(&opt); - - /* Check revs and then paths */ - for (i = 0; i < argc; i++) { - const char *arg = argv[i]; - unsigned char sha1[20]; - /* Is it a rev? */ - if (!get_sha1(arg, sha1)) { - struct object *object = parse_object(sha1); - if (!object) - die("bad object %s", arg); - add_object_array(object, arg, &list); - continue; - } - if (!strcmp(arg, "--")) { - i++; - seen_dashdash = 1; - } - break; - } - - /* The rest are paths */ - if (!seen_dashdash) { - int j; - for (j = i; j < argc; j++) - verify_filename(prefix, argv[j]); - } - - if (i < argc) - paths = get_pathspec(prefix, argv + i); - else if (prefix) { - paths = xcalloc(2, sizeof(const char *)); - paths[0] = prefix; - paths[1] = NULL; - } - - if (!use_index) { - int hit; - if (cached) - die("--cached cannot be used with --no-index."); - if (list.nr) - die("--no-index cannot be used with revs."); - hit = grep_directory(&opt, paths); - if (use_threads) - hit |= wait_all(); - return !hit; - } - - if (!list.nr) { - int hit; - if (!cached) - setup_work_tree(); - - hit = grep_cache(&opt, paths, cached); - if (use_threads) - hit |= wait_all(); - return !hit; - } - - if (cached) - die("both --cached and trees are given."); - - for (i = 0; i < list.nr; i++) { - struct object *real_obj; - real_obj = deref_tag(list.objects[i].item, NULL, 0); - if (grep_object(&opt, paths, real_obj, list.objects[i].name)) { - hit = 1; - if (opt.status_only) - break; - } - } - - if (use_threads) - hit |= wait_all(); - free_grep_patterns(&opt); - return !hit; -} diff --git a/builtin-hash-object.c b/builtin-hash-object.c deleted file mode 100644 index 6a5f5b5..0000000 --- a/builtin-hash-object.c +++ /dev/null @@ -1,134 +0,0 @@ -/* - * GIT - The information manager from hell - * - * Copyright (C) Linus Torvalds, 2005 - * Copyright (C) Junio C Hamano, 2005 - */ -#include "cache.h" -#include "blob.h" -#include "quote.h" -#include "parse-options.h" -#include "exec_cmd.h" - -static void hash_fd(int fd, const char *type, int write_object, const char *path) -{ - struct stat st; - unsigned char sha1[20]; - if (fstat(fd, &st) < 0 || - index_fd(sha1, fd, &st, write_object, type_from_string(type), path)) - die(write_object - ? "Unable to add %s to database" - : "Unable to hash %s", path); - printf("%s\n", sha1_to_hex(sha1)); - maybe_flush_or_die(stdout, "hash to stdout"); -} - -static void hash_object(const char *path, const char *type, int write_object, - const char *vpath) -{ - int fd; - fd = open(path, O_RDONLY); - if (fd < 0) - die_errno("Cannot open '%s'", path); - hash_fd(fd, type, write_object, vpath); -} - -static void hash_stdin_paths(const char *type, int write_objects) -{ - struct strbuf buf = STRBUF_INIT, nbuf = STRBUF_INIT; - - while (strbuf_getline(&buf, stdin, '\n') != EOF) { - if (buf.buf[0] == '"') { - strbuf_reset(&nbuf); - if (unquote_c_style(&nbuf, buf.buf, NULL)) - die("line is badly quoted"); - strbuf_swap(&buf, &nbuf); - } - hash_object(buf.buf, type, write_objects, buf.buf); - } - strbuf_release(&buf); - strbuf_release(&nbuf); -} - -static const char * const hash_object_usage[] = { - "git hash-object [-t ] [-w] [--path=|--no-filters] [--stdin] [--] ...", - "git hash-object --stdin-paths < ", - NULL -}; - -static const char *type; -static int write_object; -static int hashstdin; -static int stdin_paths; -static int no_filters; -static const char *vpath; - -static const struct option hash_object_options[] = { - OPT_STRING('t', NULL, &type, "type", "object type"), - OPT_BOOLEAN('w', NULL, &write_object, "write the object into the object database"), - OPT_BOOLEAN( 0 , "stdin", &hashstdin, "read the object from stdin"), - OPT_BOOLEAN( 0 , "stdin-paths", &stdin_paths, "read file names from stdin"), - OPT_BOOLEAN( 0 , "no-filters", &no_filters, "store file as is without filters"), - OPT_STRING( 0 , "path", &vpath, "file", "process file as it were from this path"), - OPT_END() -}; - -int cmd_hash_object(int argc, const char **argv, const char *prefix) -{ - int i; - int prefix_length = -1; - const char *errstr = NULL; - - type = blob_type; - - argc = parse_options(argc, argv, NULL, hash_object_options, - hash_object_usage, 0); - - if (write_object) { - prefix = setup_git_directory(); - prefix_length = prefix ? strlen(prefix) : 0; - if (vpath && prefix) - vpath = prefix_filename(prefix, prefix_length, vpath); - } - - git_config(git_default_config, NULL); - - if (stdin_paths) { - if (hashstdin) - errstr = "Can't use --stdin-paths with --stdin"; - else if (argc) - errstr = "Can't specify files with --stdin-paths"; - else if (vpath) - errstr = "Can't use --stdin-paths with --path"; - else if (no_filters) - errstr = "Can't use --stdin-paths with --no-filters"; - } - else { - if (hashstdin > 1) - errstr = "Multiple --stdin arguments are not supported"; - if (vpath && no_filters) - errstr = "Can't use --path with --no-filters"; - } - - if (errstr) { - error("%s", errstr); - usage_with_options(hash_object_usage, hash_object_options); - } - - if (hashstdin) - hash_fd(0, type, write_object, vpath); - - for (i = 0 ; i < argc; i++) { - const char *arg = argv[i]; - - if (0 <= prefix_length) - arg = prefix_filename(prefix, prefix_length, arg); - hash_object(arg, type, write_object, - no_filters ? NULL : vpath ? vpath : arg); - } - - if (stdin_paths) - hash_stdin_paths(type, write_object); - - return 0; -} diff --git a/builtin-help.c b/builtin-help.c deleted file mode 100644 index 3182a2b..0000000 --- a/builtin-help.c +++ /dev/null @@ -1,466 +0,0 @@ -/* - * builtin-help.c - * - * Builtin help command - */ -#include "cache.h" -#include "builtin.h" -#include "exec_cmd.h" -#include "common-cmds.h" -#include "parse-options.h" -#include "run-command.h" -#include "help.h" - -static struct man_viewer_list { - struct man_viewer_list *next; - char name[FLEX_ARRAY]; -} *man_viewer_list; - -static struct man_viewer_info_list { - struct man_viewer_info_list *next; - const char *info; - char name[FLEX_ARRAY]; -} *man_viewer_info_list; - -enum help_format { - HELP_FORMAT_NONE, - HELP_FORMAT_MAN, - HELP_FORMAT_INFO, - HELP_FORMAT_WEB, -}; - -static int show_all = 0; -static enum help_format help_format = HELP_FORMAT_NONE; -static struct option builtin_help_options[] = { - OPT_BOOLEAN('a', "all", &show_all, "print all available commands"), - OPT_SET_INT('m', "man", &help_format, "show man page", HELP_FORMAT_MAN), - OPT_SET_INT('w', "web", &help_format, "show manual in web browser", - HELP_FORMAT_WEB), - OPT_SET_INT('i', "info", &help_format, "show info page", - HELP_FORMAT_INFO), - OPT_END(), -}; - -static const char * const builtin_help_usage[] = { - "git help [--all] [--man|--web|--info] [command]", - NULL -}; - -static enum help_format parse_help_format(const char *format) -{ - if (!strcmp(format, "man")) - return HELP_FORMAT_MAN; - if (!strcmp(format, "info")) - return HELP_FORMAT_INFO; - if (!strcmp(format, "web") || !strcmp(format, "html")) - return HELP_FORMAT_WEB; - die("unrecognized help format '%s'", format); -} - -static const char *get_man_viewer_info(const char *name) -{ - struct man_viewer_info_list *viewer; - - for (viewer = man_viewer_info_list; viewer; viewer = viewer->next) - { - if (!strcasecmp(name, viewer->name)) - return viewer->info; - } - return NULL; -} - -static int check_emacsclient_version(void) -{ - struct strbuf buffer = STRBUF_INIT; - struct child_process ec_process; - const char *argv_ec[] = { "emacsclient", "--version", NULL }; - int version; - - /* emacsclient prints its version number on stderr */ - memset(&ec_process, 0, sizeof(ec_process)); - ec_process.argv = argv_ec; - ec_process.err = -1; - ec_process.stdout_to_stderr = 1; - if (start_command(&ec_process)) - return error("Failed to start emacsclient."); - - strbuf_read(&buffer, ec_process.err, 20); - close(ec_process.err); - - /* - * Don't bother checking return value, because "emacsclient --version" - * seems to always exits with code 1. - */ - finish_command(&ec_process); - - if (prefixcmp(buffer.buf, "emacsclient")) { - strbuf_release(&buffer); - return error("Failed to parse emacsclient version."); - } - - strbuf_remove(&buffer, 0, strlen("emacsclient")); - version = atoi(buffer.buf); - - if (version < 22) { - strbuf_release(&buffer); - return error("emacsclient version '%d' too old (< 22).", - version); - } - - strbuf_release(&buffer); - return 0; -} - -static void exec_woman_emacs(const char *path, const char *page) -{ - if (!check_emacsclient_version()) { - /* This works only with emacsclient version >= 22. */ - struct strbuf man_page = STRBUF_INIT; - - if (!path) - path = "emacsclient"; - strbuf_addf(&man_page, "(woman \"%s\")", page); - execlp(path, "emacsclient", "-e", man_page.buf, NULL); - warning("failed to exec '%s': %s", path, strerror(errno)); - } -} - -static void exec_man_konqueror(const char *path, const char *page) -{ - const char *display = getenv("DISPLAY"); - if (display && *display) { - struct strbuf man_page = STRBUF_INIT; - const char *filename = "kfmclient"; - - /* It's simpler to launch konqueror using kfmclient. */ - if (path) { - const char *file = strrchr(path, '/'); - if (file && !strcmp(file + 1, "konqueror")) { - char *new = xstrdup(path); - char *dest = strrchr(new, '/'); - - /* strlen("konqueror") == strlen("kfmclient") */ - strcpy(dest + 1, "kfmclient"); - path = new; - } - if (file) - filename = file; - } else - path = "kfmclient"; - strbuf_addf(&man_page, "man:%s(1)", page); - execlp(path, filename, "newTab", man_page.buf, NULL); - warning("failed to exec '%s': %s", path, strerror(errno)); - } -} - -static void exec_man_man(const char *path, const char *page) -{ - if (!path) - path = "man"; - execlp(path, "man", page, NULL); - warning("failed to exec '%s': %s", path, strerror(errno)); -} - -static void exec_man_cmd(const char *cmd, const char *page) -{ - struct strbuf shell_cmd = STRBUF_INIT; - strbuf_addf(&shell_cmd, "%s %s", cmd, page); - execl("/bin/sh", "sh", "-c", shell_cmd.buf, NULL); - warning("failed to exec '%s': %s", cmd, strerror(errno)); -} - -static void add_man_viewer(const char *name) -{ - struct man_viewer_list **p = &man_viewer_list; - size_t len = strlen(name); - - while (*p) - p = &((*p)->next); - *p = xcalloc(1, (sizeof(**p) + len + 1)); - strncpy((*p)->name, name, len); -} - -static int supported_man_viewer(const char *name, size_t len) -{ - return (!strncasecmp("man", name, len) || - !strncasecmp("woman", name, len) || - !strncasecmp("konqueror", name, len)); -} - -static void do_add_man_viewer_info(const char *name, - size_t len, - const char *value) -{ - struct man_viewer_info_list *new = xcalloc(1, sizeof(*new) + len + 1); - - strncpy(new->name, name, len); - new->info = xstrdup(value); - new->next = man_viewer_info_list; - man_viewer_info_list = new; -} - -static int add_man_viewer_path(const char *name, - size_t len, - const char *value) -{ - if (supported_man_viewer(name, len)) - do_add_man_viewer_info(name, len, value); - else - warning("'%s': path for unsupported man viewer.\n" - "Please consider using 'man..cmd' instead.", - name); - - return 0; -} - -static int add_man_viewer_cmd(const char *name, - size_t len, - const char *value) -{ - if (supported_man_viewer(name, len)) - warning("'%s': cmd for supported man viewer.\n" - "Please consider using 'man..path' instead.", - name); - else - do_add_man_viewer_info(name, len, value); - - return 0; -} - -static int add_man_viewer_info(const char *var, const char *value) -{ - const char *name = var + 4; - const char *subkey = strrchr(name, '.'); - - if (!subkey) - return 0; - - if (!strcmp(subkey, ".path")) { - if (!value) - return config_error_nonbool(var); - return add_man_viewer_path(name, subkey - name, value); - } - if (!strcmp(subkey, ".cmd")) { - if (!value) - return config_error_nonbool(var); - return add_man_viewer_cmd(name, subkey - name, value); - } - - return 0; -} - -static int git_help_config(const char *var, const char *value, void *cb) -{ - if (!strcmp(var, "help.format")) { - if (!value) - return config_error_nonbool(var); - help_format = parse_help_format(value); - return 0; - } - if (!strcmp(var, "man.viewer")) { - if (!value) - return config_error_nonbool(var); - add_man_viewer(value); - return 0; - } - if (!prefixcmp(var, "man.")) - return add_man_viewer_info(var, value); - - return git_default_config(var, value, cb); -} - -static struct cmdnames main_cmds, other_cmds; - -void list_common_cmds_help(void) -{ - int i, longest = 0; - - for (i = 0; i < ARRAY_SIZE(common_cmds); i++) { - if (longest < strlen(common_cmds[i].name)) - longest = strlen(common_cmds[i].name); - } - - puts("The most commonly used git commands are:"); - for (i = 0; i < ARRAY_SIZE(common_cmds); i++) { - printf(" %s ", common_cmds[i].name); - mput_char(' ', longest - strlen(common_cmds[i].name)); - puts(common_cmds[i].help); - } -} - -static int is_git_command(const char *s) -{ - return is_in_cmdlist(&main_cmds, s) || - is_in_cmdlist(&other_cmds, s); -} - -static const char *prepend(const char *prefix, const char *cmd) -{ - size_t pre_len = strlen(prefix); - size_t cmd_len = strlen(cmd); - char *p = xmalloc(pre_len + cmd_len + 1); - memcpy(p, prefix, pre_len); - strcpy(p + pre_len, cmd); - return p; -} - -static const char *cmd_to_page(const char *git_cmd) -{ - if (!git_cmd) - return "git"; - else if (!prefixcmp(git_cmd, "git")) - return git_cmd; - else if (is_git_command(git_cmd)) - return prepend("git-", git_cmd); - else - return prepend("git", git_cmd); -} - -static void setup_man_path(void) -{ - struct strbuf new_path = STRBUF_INIT; - const char *old_path = getenv("MANPATH"); - - /* We should always put ':' after our path. If there is no - * old_path, the ':' at the end will let 'man' to try - * system-wide paths after ours to find the manual page. If - * there is old_path, we need ':' as delimiter. */ - strbuf_addstr(&new_path, system_path(GIT_MAN_PATH)); - strbuf_addch(&new_path, ':'); - if (old_path) - strbuf_addstr(&new_path, old_path); - - setenv("MANPATH", new_path.buf, 1); - - strbuf_release(&new_path); -} - -static void exec_viewer(const char *name, const char *page) -{ - const char *info = get_man_viewer_info(name); - - if (!strcasecmp(name, "man")) - exec_man_man(info, page); - else if (!strcasecmp(name, "woman")) - exec_woman_emacs(info, page); - else if (!strcasecmp(name, "konqueror")) - exec_man_konqueror(info, page); - else if (info) - exec_man_cmd(info, page); - else - warning("'%s': unknown man viewer.", name); -} - -static void show_man_page(const char *git_cmd) -{ - struct man_viewer_list *viewer; - const char *page = cmd_to_page(git_cmd); - const char *fallback = getenv("GIT_MAN_VIEWER"); - - setup_man_path(); - for (viewer = man_viewer_list; viewer; viewer = viewer->next) - { - exec_viewer(viewer->name, page); /* will return when unable */ - } - if (fallback) - exec_viewer(fallback, page); - exec_viewer("man", page); - die("no man viewer handled the request"); -} - -static void show_info_page(const char *git_cmd) -{ - const char *page = cmd_to_page(git_cmd); - setenv("INFOPATH", system_path(GIT_INFO_PATH), 1); - execlp("info", "info", "gitman", page, NULL); - die("no info viewer handled the request"); -} - -static void get_html_page_path(struct strbuf *page_path, const char *page) -{ - struct stat st; - const char *html_path = system_path(GIT_HTML_PATH); - - /* Check that we have a git documentation directory. */ - if (stat(mkpath("%s/git.html", html_path), &st) - || !S_ISREG(st.st_mode)) - die("'%s': not a documentation directory.", html_path); - - strbuf_init(page_path, 0); - strbuf_addf(page_path, "%s/%s.html", html_path, page); -} - -/* - * If open_html is not defined in a platform-specific way (see for - * example compat/mingw.h), we use the script web--browse to display - * HTML. - */ -#ifndef open_html -static void open_html(const char *path) -{ - execl_git_cmd("web--browse", "-c", "help.browser", path, NULL); -} -#endif - -static void show_html_page(const char *git_cmd) -{ - const char *page = cmd_to_page(git_cmd); - struct strbuf page_path; /* it leaks but we exec bellow */ - - get_html_page_path(&page_path, page); - - open_html(page_path.buf); -} - -int cmd_help(int argc, const char **argv, const char *prefix) -{ - int nongit; - const char *alias; - enum help_format parsed_help_format; - load_command_list("git-", &main_cmds, &other_cmds); - - argc = parse_options(argc, argv, prefix, builtin_help_options, - builtin_help_usage, 0); - parsed_help_format = help_format; - - if (show_all) { - printf("usage: %s\n\n", git_usage_string); - list_commands("git commands", &main_cmds, &other_cmds); - printf("%s\n", git_more_info_string); - return 0; - } - - if (!argv[0]) { - printf("usage: %s\n\n", git_usage_string); - list_common_cmds_help(); - printf("\n%s\n", git_more_info_string); - return 0; - } - - setup_git_directory_gently(&nongit); - git_config(git_help_config, NULL); - - if (parsed_help_format != HELP_FORMAT_NONE) - help_format = parsed_help_format; - - alias = alias_lookup(argv[0]); - if (alias && !is_git_command(argv[0])) { - printf("`git %s' is aliased to `%s'\n", argv[0], alias); - return 0; - } - - switch (help_format) { - case HELP_FORMAT_NONE: - case HELP_FORMAT_MAN: - show_man_page(argv[0]); - break; - case HELP_FORMAT_INFO: - show_info_page(argv[0]); - break; - case HELP_FORMAT_WEB: - show_html_page(argv[0]); - break; - } - - return 0; -} diff --git a/builtin-index-pack.c b/builtin-index-pack.c deleted file mode 100644 index b4cf8c5..0000000 --- a/builtin-index-pack.c +++ /dev/null @@ -1,1045 +0,0 @@ -#include "cache.h" -#include "delta.h" -#include "pack.h" -#include "csum-file.h" -#include "blob.h" -#include "commit.h" -#include "tag.h" -#include "tree.h" -#include "progress.h" -#include "fsck.h" -#include "exec_cmd.h" - -static const char index_pack_usage[] = -"git index-pack [-v] [-o ] [{ ---keep | --keep= }] [--strict] { | --stdin [--fix-thin] [] }"; - -struct object_entry -{ - struct pack_idx_entry idx; - unsigned long size; - unsigned int hdr_size; - enum object_type type; - enum object_type real_type; -}; - -union delta_base { - unsigned char sha1[20]; - off_t offset; -}; - -struct base_data { - struct base_data *base; - struct base_data *child; - struct object_entry *obj; - void *data; - unsigned long size; -}; - -/* - * Even if sizeof(union delta_base) == 24 on 64-bit archs, we really want - * to memcmp() only the first 20 bytes. - */ -#define UNION_BASE_SZ 20 - -#define FLAG_LINK (1u<<20) -#define FLAG_CHECKED (1u<<21) - -struct delta_entry -{ - union delta_base base; - int obj_no; -}; - -static struct object_entry *objects; -static struct delta_entry *deltas; -static struct base_data *base_cache; -static size_t base_cache_used; -static int nr_objects; -static int nr_deltas; -static int nr_resolved_deltas; - -static int from_stdin; -static int strict; -static int verbose; - -static struct progress *progress; - -/* We always read in 4kB chunks. */ -static unsigned char input_buffer[4096]; -static unsigned int input_offset, input_len; -static off_t consumed_bytes; -static git_SHA_CTX input_ctx; -static uint32_t input_crc32; -static int input_fd, output_fd, pack_fd; - -static int mark_link(struct object *obj, int type, void *data) -{ - if (!obj) - return -1; - - if (type != OBJ_ANY && obj->type != type) - die("object type mismatch at %s", sha1_to_hex(obj->sha1)); - - obj->flags |= FLAG_LINK; - return 0; -} - -/* The content of each linked object must have been checked - or it must be already present in the object database */ -static void check_object(struct object *obj) -{ - if (!obj) - return; - - if (!(obj->flags & FLAG_LINK)) - return; - - if (!(obj->flags & FLAG_CHECKED)) { - unsigned long size; - int type = sha1_object_info(obj->sha1, &size); - if (type != obj->type || type <= 0) - die("object of unexpected type"); - obj->flags |= FLAG_CHECKED; - return; - } -} - -static void check_objects(void) -{ - unsigned i, max; - - max = get_max_object_index(); - for (i = 0; i < max; i++) - check_object(get_indexed_object(i)); -} - - -/* Discard current buffer used content. */ -static void flush(void) -{ - if (input_offset) { - if (output_fd >= 0) - write_or_die(output_fd, input_buffer, input_offset); - git_SHA1_Update(&input_ctx, input_buffer, input_offset); - memmove(input_buffer, input_buffer + input_offset, input_len); - input_offset = 0; - } -} - -/* - * Make sure at least "min" bytes are available in the buffer, and - * return the pointer to the buffer. - */ -static void *fill(int min) -{ - if (min <= input_len) - return input_buffer + input_offset; - if (min > sizeof(input_buffer)) - die("cannot fill %d bytes", min); - flush(); - do { - ssize_t ret = xread(input_fd, input_buffer + input_len, - sizeof(input_buffer) - input_len); - if (ret <= 0) { - if (!ret) - die("early EOF"); - die_errno("read error on input"); - } - input_len += ret; - if (from_stdin) - display_throughput(progress, consumed_bytes + input_len); - } while (input_len < min); - return input_buffer; -} - -static void use(int bytes) -{ - if (bytes > input_len) - die("used more bytes than were available"); - input_crc32 = crc32(input_crc32, input_buffer + input_offset, bytes); - input_len -= bytes; - input_offset += bytes; - - /* make sure off_t is sufficiently large not to wrap */ - if (consumed_bytes > consumed_bytes + bytes) - die("pack too large for current definition of off_t"); - consumed_bytes += bytes; -} - -static const char *open_pack_file(const char *pack_name) -{ - if (from_stdin) { - input_fd = 0; - if (!pack_name) { - static char tmpfile[PATH_MAX]; - output_fd = odb_mkstemp(tmpfile, sizeof(tmpfile), - "pack/tmp_pack_XXXXXX"); - pack_name = xstrdup(tmpfile); - } else - output_fd = open(pack_name, O_CREAT|O_EXCL|O_RDWR, 0600); - if (output_fd < 0) - die_errno("unable to create '%s'", pack_name); - pack_fd = output_fd; - } else { - input_fd = open(pack_name, O_RDONLY); - if (input_fd < 0) - die_errno("cannot open packfile '%s'", pack_name); - output_fd = -1; - pack_fd = input_fd; - } - git_SHA1_Init(&input_ctx); - return pack_name; -} - -static void parse_pack_header(void) -{ - struct pack_header *hdr = fill(sizeof(struct pack_header)); - - /* Header consistency check */ - if (hdr->hdr_signature != htonl(PACK_SIGNATURE)) - die("pack signature mismatch"); - if (!pack_version_ok(hdr->hdr_version)) - die("pack version %"PRIu32" unsupported", - ntohl(hdr->hdr_version)); - - nr_objects = ntohl(hdr->hdr_entries); - use(sizeof(struct pack_header)); -} - -static NORETURN void bad_object(unsigned long offset, const char *format, - ...) __attribute__((format (printf, 2, 3))); - -static void bad_object(unsigned long offset, const char *format, ...) -{ - va_list params; - char buf[1024]; - - va_start(params, format); - vsnprintf(buf, sizeof(buf), format, params); - va_end(params); - die("pack has bad object at offset %lu: %s", offset, buf); -} - -static void free_base_data(struct base_data *c) -{ - if (c->data) { - free(c->data); - c->data = NULL; - base_cache_used -= c->size; - } -} - -static void prune_base_data(struct base_data *retain) -{ - struct base_data *b; - for (b = base_cache; - base_cache_used > delta_base_cache_limit && b; - b = b->child) { - if (b->data && b != retain) - free_base_data(b); - } -} - -static void link_base_data(struct base_data *base, struct base_data *c) -{ - if (base) - base->child = c; - else - base_cache = c; - - c->base = base; - c->child = NULL; - if (c->data) - base_cache_used += c->size; - prune_base_data(c); -} - -static void unlink_base_data(struct base_data *c) -{ - struct base_data *base = c->base; - if (base) - base->child = NULL; - else - base_cache = NULL; - free_base_data(c); -} - -static void *unpack_entry_data(unsigned long offset, unsigned long size) -{ - z_stream stream; - void *buf = xmalloc(size); - - memset(&stream, 0, sizeof(stream)); - stream.next_out = buf; - stream.avail_out = size; - stream.next_in = fill(1); - stream.avail_in = input_len; - git_inflate_init(&stream); - - for (;;) { - int ret = git_inflate(&stream, 0); - use(input_len - stream.avail_in); - if (stream.total_out == size && ret == Z_STREAM_END) - break; - if (ret != Z_OK) - bad_object(offset, "inflate returned %d", ret); - stream.next_in = fill(1); - stream.avail_in = input_len; - } - git_inflate_end(&stream); - return buf; -} - -static void *unpack_raw_entry(struct object_entry *obj, union delta_base *delta_base) -{ - unsigned char *p; - unsigned long size, c; - off_t base_offset; - unsigned shift; - void *data; - - obj->idx.offset = consumed_bytes; - input_crc32 = crc32(0, Z_NULL, 0); - - p = fill(1); - c = *p; - use(1); - obj->type = (c >> 4) & 7; - size = (c & 15); - shift = 4; - while (c & 0x80) { - p = fill(1); - c = *p; - use(1); - size += (c & 0x7f) << shift; - shift += 7; - } - obj->size = size; - - switch (obj->type) { - case OBJ_REF_DELTA: - hashcpy(delta_base->sha1, fill(20)); - use(20); - break; - case OBJ_OFS_DELTA: - memset(delta_base, 0, sizeof(*delta_base)); - p = fill(1); - c = *p; - use(1); - base_offset = c & 127; - while (c & 128) { - base_offset += 1; - if (!base_offset || MSB(base_offset, 7)) - bad_object(obj->idx.offset, "offset value overflow for delta base object"); - p = fill(1); - c = *p; - use(1); - base_offset = (base_offset << 7) + (c & 127); - } - delta_base->offset = obj->idx.offset - base_offset; - if (delta_base->offset <= 0 || delta_base->offset >= obj->idx.offset) - bad_object(obj->idx.offset, "delta base offset is out of bound"); - break; - case OBJ_COMMIT: - case OBJ_TREE: - case OBJ_BLOB: - case OBJ_TAG: - break; - default: - bad_object(obj->idx.offset, "unknown object type %d", obj->type); - } - obj->hdr_size = consumed_bytes - obj->idx.offset; - - data = unpack_entry_data(obj->idx.offset, obj->size); - obj->idx.crc32 = input_crc32; - return data; -} - -static void *get_data_from_pack(struct object_entry *obj) -{ - off_t from = obj[0].idx.offset + obj[0].hdr_size; - unsigned long len = obj[1].idx.offset - from; - unsigned long rdy = 0; - unsigned char *src, *data; - z_stream stream; - int st; - - src = xmalloc(len); - data = src; - do { - ssize_t n = pread(pack_fd, data + rdy, len - rdy, from + rdy); - if (n < 0) - die_errno("cannot pread pack file"); - if (!n) - die("premature end of pack file, %lu bytes missing", - len - rdy); - rdy += n; - } while (rdy < len); - data = xmalloc(obj->size); - memset(&stream, 0, sizeof(stream)); - stream.next_out = data; - stream.avail_out = obj->size; - stream.next_in = src; - stream.avail_in = len; - git_inflate_init(&stream); - while ((st = git_inflate(&stream, Z_FINISH)) == Z_OK); - git_inflate_end(&stream); - if (st != Z_STREAM_END || stream.total_out != obj->size) - die("serious inflate inconsistency"); - free(src); - return data; -} - -static int find_delta(const union delta_base *base) -{ - int first = 0, last = nr_deltas; - - while (first < last) { - int next = (first + last) / 2; - struct delta_entry *delta = &deltas[next]; - int cmp; - - cmp = memcmp(base, &delta->base, UNION_BASE_SZ); - if (!cmp) - return next; - if (cmp < 0) { - last = next; - continue; - } - first = next+1; - } - return -first-1; -} - -static void find_delta_children(const union delta_base *base, - int *first_index, int *last_index) -{ - int first = find_delta(base); - int last = first; - int end = nr_deltas - 1; - - if (first < 0) { - *first_index = 0; - *last_index = -1; - return; - } - while (first > 0 && !memcmp(&deltas[first - 1].base, base, UNION_BASE_SZ)) - --first; - while (last < end && !memcmp(&deltas[last + 1].base, base, UNION_BASE_SZ)) - ++last; - *first_index = first; - *last_index = last; -} - -static void sha1_object(const void *data, unsigned long size, - enum object_type type, unsigned char *sha1) -{ - hash_sha1_file(data, size, typename(type), sha1); - if (has_sha1_file(sha1)) { - void *has_data; - enum object_type has_type; - unsigned long has_size; - has_data = read_sha1_file(sha1, &has_type, &has_size); - if (!has_data) - die("cannot read existing object %s", sha1_to_hex(sha1)); - if (size != has_size || type != has_type || - memcmp(data, has_data, size) != 0) - die("SHA1 COLLISION FOUND WITH %s !", sha1_to_hex(sha1)); - free(has_data); - } - if (strict) { - if (type == OBJ_BLOB) { - struct blob *blob = lookup_blob(sha1); - if (blob) - blob->object.flags |= FLAG_CHECKED; - else - die("invalid blob object %s", sha1_to_hex(sha1)); - } else { - struct object *obj; - int eaten; - void *buf = (void *) data; - - /* - * we do not need to free the memory here, as the - * buf is deleted by the caller. - */ - obj = parse_object_buffer(sha1, type, size, buf, &eaten); - if (!obj) - die("invalid %s", typename(type)); - if (fsck_object(obj, 1, fsck_error_function)) - die("Error in object"); - if (fsck_walk(obj, mark_link, NULL)) - die("Not all child objects of %s are reachable", sha1_to_hex(obj->sha1)); - - if (obj->type == OBJ_TREE) { - struct tree *item = (struct tree *) obj; - item->buffer = NULL; - } - if (obj->type == OBJ_COMMIT) { - struct commit *commit = (struct commit *) obj; - commit->buffer = NULL; - } - obj->flags |= FLAG_CHECKED; - } - } -} - -static void *get_base_data(struct base_data *c) -{ - if (!c->data) { - struct object_entry *obj = c->obj; - - if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA) { - void *base = get_base_data(c->base); - void *raw = get_data_from_pack(obj); - c->data = patch_delta( - base, c->base->size, - raw, obj->size, - &c->size); - free(raw); - if (!c->data) - bad_object(obj->idx.offset, "failed to apply delta"); - } else { - c->data = get_data_from_pack(obj); - c->size = obj->size; - } - - base_cache_used += c->size; - prune_base_data(c); - } - return c->data; -} - -static void resolve_delta(struct object_entry *delta_obj, - struct base_data *base, struct base_data *result) -{ - void *base_data, *delta_data; - - delta_obj->real_type = base->obj->real_type; - delta_data = get_data_from_pack(delta_obj); - base_data = get_base_data(base); - result->obj = delta_obj; - result->data = patch_delta(base_data, base->size, - delta_data, delta_obj->size, &result->size); - free(delta_data); - if (!result->data) - bad_object(delta_obj->idx.offset, "failed to apply delta"); - sha1_object(result->data, result->size, delta_obj->real_type, - delta_obj->idx.sha1); - nr_resolved_deltas++; -} - -static void find_unresolved_deltas(struct base_data *base, - struct base_data *prev_base) -{ - int i, ref_first, ref_last, ofs_first, ofs_last; - - /* - * This is a recursive function. Those brackets should help reducing - * stack usage by limiting the scope of the delta_base union. - */ - { - union delta_base base_spec; - - hashcpy(base_spec.sha1, base->obj->idx.sha1); - find_delta_children(&base_spec, &ref_first, &ref_last); - - memset(&base_spec, 0, sizeof(base_spec)); - base_spec.offset = base->obj->idx.offset; - find_delta_children(&base_spec, &ofs_first, &ofs_last); - } - - if (ref_last == -1 && ofs_last == -1) { - free(base->data); - return; - } - - link_base_data(prev_base, base); - - for (i = ref_first; i <= ref_last; i++) { - struct object_entry *child = objects + deltas[i].obj_no; - if (child->real_type == OBJ_REF_DELTA) { - struct base_data result; - resolve_delta(child, base, &result); - if (i == ref_last && ofs_last == -1) - free_base_data(base); - find_unresolved_deltas(&result, base); - } - } - - for (i = ofs_first; i <= ofs_last; i++) { - struct object_entry *child = objects + deltas[i].obj_no; - if (child->real_type == OBJ_OFS_DELTA) { - struct base_data result; - resolve_delta(child, base, &result); - if (i == ofs_last) - free_base_data(base); - find_unresolved_deltas(&result, base); - } - } - - unlink_base_data(base); -} - -static int compare_delta_entry(const void *a, const void *b) -{ - const struct delta_entry *delta_a = a; - const struct delta_entry *delta_b = b; - return memcmp(&delta_a->base, &delta_b->base, UNION_BASE_SZ); -} - -/* Parse all objects and return the pack content SHA1 hash */ -static void parse_pack_objects(unsigned char *sha1) -{ - int i; - struct delta_entry *delta = deltas; - struct stat st; - - /* - * First pass: - * - find locations of all objects; - * - calculate SHA1 of all non-delta objects; - * - remember base (SHA1 or offset) for all deltas. - */ - if (verbose) - progress = start_progress( - from_stdin ? "Receiving objects" : "Indexing objects", - nr_objects); - for (i = 0; i < nr_objects; i++) { - struct object_entry *obj = &objects[i]; - void *data = unpack_raw_entry(obj, &delta->base); - obj->real_type = obj->type; - if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA) { - nr_deltas++; - delta->obj_no = i; - delta++; - } else - sha1_object(data, obj->size, obj->type, obj->idx.sha1); - free(data); - display_progress(progress, i+1); - } - objects[i].idx.offset = consumed_bytes; - stop_progress(&progress); - - /* Check pack integrity */ - flush(); - git_SHA1_Final(sha1, &input_ctx); - if (hashcmp(fill(20), sha1)) - die("pack is corrupted (SHA1 mismatch)"); - use(20); - - /* If input_fd is a file, we should have reached its end now. */ - if (fstat(input_fd, &st)) - die_errno("cannot fstat packfile"); - if (S_ISREG(st.st_mode) && - lseek(input_fd, 0, SEEK_CUR) - input_len != st.st_size) - die("pack has junk at the end"); - - if (!nr_deltas) - return; - - /* Sort deltas by base SHA1/offset for fast searching */ - qsort(deltas, nr_deltas, sizeof(struct delta_entry), - compare_delta_entry); - - /* - * Second pass: - * - for all non-delta objects, look if it is used as a base for - * deltas; - * - if used as a base, uncompress the object and apply all deltas, - * recursively checking if the resulting object is used as a base - * for some more deltas. - */ - if (verbose) - progress = start_progress("Resolving deltas", nr_deltas); - for (i = 0; i < nr_objects; i++) { - struct object_entry *obj = &objects[i]; - struct base_data base_obj; - - if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA) - continue; - base_obj.obj = obj; - base_obj.data = NULL; - find_unresolved_deltas(&base_obj, NULL); - display_progress(progress, nr_resolved_deltas); - } -} - -static int write_compressed(struct sha1file *f, void *in, unsigned int size) -{ - z_stream stream; - unsigned long maxsize; - void *out; - - memset(&stream, 0, sizeof(stream)); - deflateInit(&stream, zlib_compression_level); - maxsize = deflateBound(&stream, size); - out = xmalloc(maxsize); - - /* Compress it */ - stream.next_in = in; - stream.avail_in = size; - stream.next_out = out; - stream.avail_out = maxsize; - while (deflate(&stream, Z_FINISH) == Z_OK); - deflateEnd(&stream); - - size = stream.total_out; - sha1write(f, out, size); - free(out); - return size; -} - -static struct object_entry *append_obj_to_pack(struct sha1file *f, - const unsigned char *sha1, void *buf, - unsigned long size, enum object_type type) -{ - struct object_entry *obj = &objects[nr_objects++]; - unsigned char header[10]; - unsigned long s = size; - int n = 0; - unsigned char c = (type << 4) | (s & 15); - s >>= 4; - while (s) { - header[n++] = c | 0x80; - c = s & 0x7f; - s >>= 7; - } - header[n++] = c; - crc32_begin(f); - sha1write(f, header, n); - obj[0].size = size; - obj[0].hdr_size = n; - obj[0].type = type; - obj[0].real_type = type; - obj[1].idx.offset = obj[0].idx.offset + n; - obj[1].idx.offset += write_compressed(f, buf, size); - obj[0].idx.crc32 = crc32_end(f); - sha1flush(f); - hashcpy(obj->idx.sha1, sha1); - return obj; -} - -static int delta_pos_compare(const void *_a, const void *_b) -{ - struct delta_entry *a = *(struct delta_entry **)_a; - struct delta_entry *b = *(struct delta_entry **)_b; - return a->obj_no - b->obj_no; -} - -static void fix_unresolved_deltas(struct sha1file *f, int nr_unresolved) -{ - struct delta_entry **sorted_by_pos; - int i, n = 0; - - /* - * Since many unresolved deltas may well be themselves base objects - * for more unresolved deltas, we really want to include the - * smallest number of base objects that would cover as much delta - * as possible by picking the - * trunc deltas first, allowing for other deltas to resolve without - * additional base objects. Since most base objects are to be found - * before deltas depending on them, a good heuristic is to start - * resolving deltas in the same order as their position in the pack. - */ - sorted_by_pos = xmalloc(nr_unresolved * sizeof(*sorted_by_pos)); - for (i = 0; i < nr_deltas; i++) { - if (objects[deltas[i].obj_no].real_type != OBJ_REF_DELTA) - continue; - sorted_by_pos[n++] = &deltas[i]; - } - qsort(sorted_by_pos, n, sizeof(*sorted_by_pos), delta_pos_compare); - - for (i = 0; i < n; i++) { - struct delta_entry *d = sorted_by_pos[i]; - enum object_type type; - struct base_data base_obj; - - if (objects[d->obj_no].real_type != OBJ_REF_DELTA) - continue; - base_obj.data = read_sha1_file(d->base.sha1, &type, &base_obj.size); - if (!base_obj.data) - continue; - - if (check_sha1_signature(d->base.sha1, base_obj.data, - base_obj.size, typename(type))) - die("local object %s is corrupt", sha1_to_hex(d->base.sha1)); - base_obj.obj = append_obj_to_pack(f, d->base.sha1, - base_obj.data, base_obj.size, type); - find_unresolved_deltas(&base_obj, NULL); - display_progress(progress, nr_resolved_deltas); - } - free(sorted_by_pos); -} - -static void final(const char *final_pack_name, const char *curr_pack_name, - const char *final_index_name, const char *curr_index_name, - const char *keep_name, const char *keep_msg, - unsigned char *sha1) -{ - const char *report = "pack"; - char name[PATH_MAX]; - int err; - - if (!from_stdin) { - close(input_fd); - } else { - fsync_or_die(output_fd, curr_pack_name); - err = close(output_fd); - if (err) - die_errno("error while closing pack file"); - } - - if (keep_msg) { - int keep_fd, keep_msg_len = strlen(keep_msg); - - if (!keep_name) - keep_fd = odb_pack_keep(name, sizeof(name), sha1); - else - keep_fd = open(keep_name, O_RDWR|O_CREAT|O_EXCL, 0600); - - if (keep_fd < 0) { - if (errno != EEXIST) - die_errno("cannot write keep file '%s'", - keep_name); - } else { - if (keep_msg_len > 0) { - write_or_die(keep_fd, keep_msg, keep_msg_len); - write_or_die(keep_fd, "\n", 1); - } - if (close(keep_fd) != 0) - die_errno("cannot close written keep file '%s'", - keep_name); - report = "keep"; - } - } - - if (final_pack_name != curr_pack_name) { - if (!final_pack_name) { - snprintf(name, sizeof(name), "%s/pack/pack-%s.pack", - get_object_directory(), sha1_to_hex(sha1)); - final_pack_name = name; - } - if (move_temp_to_file(curr_pack_name, final_pack_name)) - die("cannot store pack file"); - } else if (from_stdin) - chmod(final_pack_name, 0444); - - if (final_index_name != curr_index_name) { - if (!final_index_name) { - snprintf(name, sizeof(name), "%s/pack/pack-%s.idx", - get_object_directory(), sha1_to_hex(sha1)); - final_index_name = name; - } - if (move_temp_to_file(curr_index_name, final_index_name)) - die("cannot store index file"); - } else - chmod(final_index_name, 0444); - - if (!from_stdin) { - printf("%s\n", sha1_to_hex(sha1)); - } else { - char buf[48]; - int len = snprintf(buf, sizeof(buf), "%s\t%s\n", - report, sha1_to_hex(sha1)); - write_or_die(1, buf, len); - - /* - * Let's just mimic git-unpack-objects here and write - * the last part of the input buffer to stdout. - */ - while (input_len) { - err = xwrite(1, input_buffer + input_offset, input_len); - if (err <= 0) - break; - input_len -= err; - input_offset += err; - } - } -} - -static int git_index_pack_config(const char *k, const char *v, void *cb) -{ - if (!strcmp(k, "pack.indexversion")) { - pack_idx_default_version = git_config_int(k, v); - if (pack_idx_default_version > 2) - die("bad pack.indexversion=%"PRIu32, - pack_idx_default_version); - return 0; - } - return git_default_config(k, v, cb); -} - -int cmd_index_pack(int argc, const char **argv, const char *prefix) -{ - int i, fix_thin_pack = 0; - const char *curr_pack, *curr_index; - const char *index_name = NULL, *pack_name = NULL; - const char *keep_name = NULL, *keep_msg = NULL; - char *index_name_buf = NULL, *keep_name_buf = NULL; - struct pack_idx_entry **idx_objects; - unsigned char pack_sha1[20]; - - if (argc == 2 && !strcmp(argv[1], "-h")) - usage(index_pack_usage); - - /* - * We wish to read the repository's config file if any, and - * for that it is necessary to call setup_git_directory_gently(). - * However if the cwd was inside .git/objects/pack/ then we need - * to go back there or all the pack name arguments will be wrong. - * And in that case we cannot rely on any prefix returned by - * setup_git_directory_gently() either. - */ - { - char cwd[PATH_MAX+1]; - int nongit; - - if (!getcwd(cwd, sizeof(cwd)-1)) - die("Unable to get current working directory"); - setup_git_directory_gently(&nongit); - git_config(git_index_pack_config, NULL); - if (chdir(cwd)) - die("Cannot come back to cwd"); - } - - for (i = 1; i < argc; i++) { - const char *arg = argv[i]; - - if (*arg == '-') { - if (!strcmp(arg, "--stdin")) { - from_stdin = 1; - } else if (!strcmp(arg, "--fix-thin")) { - fix_thin_pack = 1; - } else if (!strcmp(arg, "--strict")) { - strict = 1; - } else if (!strcmp(arg, "--keep")) { - keep_msg = ""; - } else if (!prefixcmp(arg, "--keep=")) { - keep_msg = arg + 7; - } else if (!prefixcmp(arg, "--pack_header=")) { - struct pack_header *hdr; - char *c; - - hdr = (struct pack_header *)input_buffer; - hdr->hdr_signature = htonl(PACK_SIGNATURE); - hdr->hdr_version = htonl(strtoul(arg + 14, &c, 10)); - if (*c != ',') - die("bad %s", arg); - hdr->hdr_entries = htonl(strtoul(c + 1, &c, 10)); - if (*c) - die("bad %s", arg); - input_len = sizeof(*hdr); - } else if (!strcmp(arg, "-v")) { - verbose = 1; - } else if (!strcmp(arg, "-o")) { - if (index_name || (i+1) >= argc) - usage(index_pack_usage); - index_name = argv[++i]; - } else if (!prefixcmp(arg, "--index-version=")) { - char *c; - pack_idx_default_version = strtoul(arg + 16, &c, 10); - if (pack_idx_default_version > 2) - die("bad %s", arg); - if (*c == ',') - pack_idx_off32_limit = strtoul(c+1, &c, 0); - if (*c || pack_idx_off32_limit & 0x80000000) - die("bad %s", arg); - } else - usage(index_pack_usage); - continue; - } - - if (pack_name) - usage(index_pack_usage); - pack_name = arg; - } - - if (!pack_name && !from_stdin) - usage(index_pack_usage); - if (fix_thin_pack && !from_stdin) - die("--fix-thin cannot be used without --stdin"); - if (!index_name && pack_name) { - int len = strlen(pack_name); - if (!has_extension(pack_name, ".pack")) - die("packfile name '%s' does not end with '.pack'", - pack_name); - index_name_buf = xmalloc(len); - memcpy(index_name_buf, pack_name, len - 5); - strcpy(index_name_buf + len - 5, ".idx"); - index_name = index_name_buf; - } - if (keep_msg && !keep_name && pack_name) { - int len = strlen(pack_name); - if (!has_extension(pack_name, ".pack")) - die("packfile name '%s' does not end with '.pack'", - pack_name); - keep_name_buf = xmalloc(len); - memcpy(keep_name_buf, pack_name, len - 5); - strcpy(keep_name_buf + len - 5, ".keep"); - keep_name = keep_name_buf; - } - - curr_pack = open_pack_file(pack_name); - parse_pack_header(); - objects = xmalloc((nr_objects + 1) * sizeof(struct object_entry)); - deltas = xmalloc(nr_objects * sizeof(struct delta_entry)); - parse_pack_objects(pack_sha1); - if (nr_deltas == nr_resolved_deltas) { - stop_progress(&progress); - /* Flush remaining pack final 20-byte SHA1. */ - flush(); - } else { - if (fix_thin_pack) { - struct sha1file *f; - unsigned char read_sha1[20], tail_sha1[20]; - char msg[48]; - int nr_unresolved = nr_deltas - nr_resolved_deltas; - int nr_objects_initial = nr_objects; - if (nr_unresolved <= 0) - die("confusion beyond insanity"); - objects = xrealloc(objects, - (nr_objects + nr_unresolved + 1) - * sizeof(*objects)); - f = sha1fd(output_fd, curr_pack); - fix_unresolved_deltas(f, nr_unresolved); - sprintf(msg, "completed with %d local objects", - nr_objects - nr_objects_initial); - stop_progress_msg(&progress, msg); - sha1close(f, tail_sha1, 0); - hashcpy(read_sha1, pack_sha1); - fixup_pack_header_footer(output_fd, pack_sha1, - curr_pack, nr_objects, - read_sha1, consumed_bytes-20); - if (hashcmp(read_sha1, tail_sha1) != 0) - die("Unexpected tail checksum for %s " - "(disk corruption?)", curr_pack); - } - if (nr_deltas != nr_resolved_deltas) - die("pack has %d unresolved deltas", - nr_deltas - nr_resolved_deltas); - } - free(deltas); - if (strict) - check_objects(); - - idx_objects = xmalloc((nr_objects) * sizeof(struct pack_idx_entry *)); - for (i = 0; i < nr_objects; i++) - idx_objects[i] = &objects[i].idx; - curr_index = write_idx_file(index_name, idx_objects, nr_objects, pack_sha1); - free(idx_objects); - - final(pack_name, curr_pack, - index_name, curr_index, - keep_name, keep_msg, - pack_sha1); - free(objects); - free(index_name_buf); - free(keep_name_buf); - if (pack_name == NULL) - free((void *) curr_pack); - if (index_name == NULL) - free((void *) curr_index); - - return 0; -} diff --git a/builtin-init-db.c b/builtin-init-db.c deleted file mode 100644 index dd84cae..0000000 --- a/builtin-init-db.c +++ /dev/null @@ -1,498 +0,0 @@ -/* - * GIT - The information manager from hell - * - * Copyright (C) Linus Torvalds, 2005 - */ -#include "cache.h" -#include "builtin.h" -#include "exec_cmd.h" -#include "parse-options.h" - -#ifndef DEFAULT_GIT_TEMPLATE_DIR -#define DEFAULT_GIT_TEMPLATE_DIR "/usr/share/git-core/templates" -#endif - -#ifdef NO_TRUSTABLE_FILEMODE -#define TEST_FILEMODE 0 -#else -#define TEST_FILEMODE 1 -#endif - -static int init_is_bare_repository = 0; -static int init_shared_repository = -1; - -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", dir); -} - -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" 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_errno("cannot stat '%s'", path); - } - else - exists = 1; - - if (lstat(template, &st_template)) - die_errno("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_errno("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_errno("cannot readlink '%s'", template); - if (sizeof(lnk) <= len) - die("insanely long symlink %s", template); - lnk[len] = 0; - if (symlink(lnk, path)) - die_errno("cannot symlink '%s' '%s'", lnk, path); - } - else if (S_ISREG(st_template.st_mode)) { - if (copy_file(path, template, st_template.st_mode)) - die_errno("cannot copy '%s' to '%s'", template, - path); - } - else - error("ignoring template %s", template); - } -} - -static void copy_templates(const char *template_dir) -{ - char path[PATH_MAX]; - char template_path[PATH_MAX]; - int template_len; - DIR *dir; - const char *git_dir = get_git_dir(); - int len = strlen(git_dir); - - if (!template_dir) - template_dir = getenv(TEMPLATE_DIR_ENVIRONMENT); - if (!template_dir) - template_dir = system_path(DEFAULT_GIT_TEMPLATE_DIR); - if (!template_dir[0]) - return; - template_len = strlen(template_dir); - if (PATH_MAX <= (template_len+strlen("/config"))) - die("insanely long template path %s", template_dir); - strcpy(template_path, template_dir); - if (template_path[template_len-1] != '/') { - template_path[template_len++] = '/'; - template_path[template_len] = 0; - } - dir = opendir(template_path); - if (!dir) { - warning("templates not found %s", 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, NULL); - template_path[template_len] = 0; - - if (repository_format_version && - repository_format_version != GIT_REPO_VERSION) { - warning("not copying templates of " - "a wrong format version %d from '%s'", - repository_format_version, - template_dir); - closedir(dir); - return; - } - - memcpy(path, git_dir, len); - if (len && path[len - 1] != '/') - path[len++] = '/'; - path[len] = 0; - copy_templates_1(path, len, - template_path, template_len, - dir); - closedir(dir); -} - -static int create_default_files(const char *template_path) -{ - const char *git_dir = get_git_dir(); - unsigned len = strlen(git_dir); - static char path[PATH_MAX]; - struct stat st1; - char repo_version_string[10]; - char junk[2]; - int reinit; - int filemode; - - 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} - */ - safe_create_dir(git_path("refs"), 1); - safe_create_dir(git_path("refs/heads"), 1); - safe_create_dir(git_path("refs/tags"), 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. - */ - copy_templates(template_path); - - git_config(git_default_config, NULL); - is_bare_repository_cfg = init_is_bare_repository; - - /* reading existing config may have overwrote it */ - if (init_shared_repository != -1) - shared_repository = init_shared_repository; - - /* - * We would have created the above under user's umask -- under - * shared-repository settings, we would need to fix them up. - */ - if (shared_repository) { - adjust_shared_perm(get_git_dir()); - adjust_shared_perm(git_path("refs")); - adjust_shared_perm(git_path("refs/heads")); - adjust_shared_perm(git_path("refs/tags")); - } - - /* - * Create the default symlink from ".git/HEAD" to the "master" - * branch, if it does not exist yet. - */ - strcpy(path + len, "HEAD"); - reinit = (!access(path, R_OK) - || readlink(path, junk, sizeof(junk)-1) != -1); - if (!reinit) { - if (create_symref("HEAD", "refs/heads/master", NULL) < 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 */ - filemode = TEST_FILEMODE; - if (TEST_FILEMODE && !lstat(path, &st1)) { - struct stat st2; - 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"); - - if (is_bare_repository()) - git_config_set("core.bare", "true"); - else { - const char *work_tree = get_git_work_tree(); - git_config_set("core.bare", "false"); - /* allow template config file to override the default */ - if (log_all_ref_updates == -1) - git_config_set("core.logallrefupdates", "true"); - if (prefixcmp(git_dir, work_tree) || - strcmp(git_dir + strlen(work_tree), "/.git")) { - git_config_set("core.worktree", work_tree); - } - } - - if (!reinit) { - /* Check if symlink is supported in the work tree */ - path[len] = 0; - strcpy(path + len, "tXXXXXX"); - if (!close(xmkstemp(path)) && - !unlink(path) && - !symlink("testing", path) && - !lstat(path, &st1) && - S_ISLNK(st1.st_mode)) - unlink(path); /* good */ - else - git_config_set("core.symlinks", "false"); - - /* Check if the filesystem is case-insensitive */ - path[len] = 0; - strcpy(path + len, "CoNfIg"); - if (!access(path, F_OK)) - git_config_set("core.ignorecase", "true"); - } - - return reinit; -} - -int init_db(const char *template_dir, unsigned int flags) -{ - const char *sha1_dir; - char *path; - int len, reinit; - - safe_create_dir(get_git_dir(), 0); - - init_is_bare_repository = is_bare_repository(); - - /* 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(); - - reinit = create_default_files(template_dir); - - 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) { - char buf[10]; - /* We do not spell "group" and such, so that - * the configuration can be read by older version - * of git. Note, we use octal numbers for new share modes, - * and compatibility values for PERM_GROUP and - * PERM_EVERYBODY. - */ - if (shared_repository < 0) - /* force to the mode value */ - sprintf(buf, "0%o", -shared_repository); - else if (shared_repository == PERM_GROUP) - sprintf(buf, "%d", OLD_PERM_GROUP); - else if (shared_repository == PERM_EVERYBODY) - sprintf(buf, "%d", OLD_PERM_EVERYBODY); - else - die("oops"); - git_config_set("core.sharedrepository", buf); - git_config_set("receive.denyNonFastforwards", "true"); - } - - if (!(flags & INIT_DB_QUIET)) - printf("%s%s Git repository in %s/\n", - reinit ? "Reinitialized existing" : "Initialized empty", - shared_repository ? " shared" : "", - get_git_dir()); - - return 0; -} - -static int guess_repository_type(const char *git_dir) -{ - char cwd[PATH_MAX]; - const char *slash; - - /* - * "GIT_DIR=. git init" is always bare. - * "GIT_DIR=`pwd` git init" too. - */ - if (!strcmp(".", git_dir)) - return 1; - if (!getcwd(cwd, sizeof(cwd))) - die_errno("cannot tell cwd"); - if (!strcmp(git_dir, cwd)) - return 1; - /* - * "GIT_DIR=.git or GIT_DIR=something/.git is usually not. - */ - if (!strcmp(git_dir, ".git")) - return 0; - slash = strrchr(git_dir, '/'); - if (slash && !strcmp(slash, "/.git")) - return 0; - - /* - * Otherwise it is often bare. At this point - * we are just guessing. - */ - return 1; -} - -static int shared_callback(const struct option *opt, const char *arg, int unset) -{ - *((int *) opt->value) = (arg) ? git_config_perm("arg", arg) : PERM_GROUP; - return 0; -} - -static const char *const init_db_usage[] = { - "git init [-q | --quiet] [--bare] [--template=] [--shared[=]] [directory]", - NULL -}; - -/* - * 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, const char *prefix) -{ - const char *git_dir; - const char *template_dir = NULL; - unsigned int flags = 0; - const struct option init_db_options[] = { - OPT_STRING(0, "template", &template_dir, "template-directory", - "provide the directory from which templates will be used"), - OPT_SET_INT(0, "bare", &is_bare_repository_cfg, - "create a bare repository", 1), - { OPTION_CALLBACK, 0, "shared", &init_shared_repository, - "permissions", - "specify that the git repository is to be shared amongst several users", - PARSE_OPT_OPTARG | PARSE_OPT_NONEG, shared_callback, 0}, - OPT_BIT('q', "quiet", &flags, "be quiet", INIT_DB_QUIET), - OPT_END() - }; - - argc = parse_options(argc, argv, prefix, init_db_options, init_db_usage, 0); - - if (argc == 1) { - int mkdir_tried = 0; - retry: - if (chdir(argv[0]) < 0) { - if (!mkdir_tried) { - int saved; - /* - * At this point we haven't read any configuration, - * and we know shared_repository should always be 0; - * but just in case we play safe. - */ - saved = shared_repository; - shared_repository = 0; - switch (safe_create_leading_directories_const(argv[0])) { - case -3: - errno = EEXIST; - /* fallthru */ - case -1: - die_errno("cannot mkdir %s", argv[0]); - break; - default: - break; - } - shared_repository = saved; - if (mkdir(argv[0], 0777) < 0) - die_errno("cannot mkdir %s", argv[0]); - mkdir_tried = 1; - goto retry; - } - die_errno("cannot chdir to %s", argv[0]); - } - } else if (0 < argc) { - usage(init_db_usage[0]); - } - if (is_bare_repository_cfg == 1) { - static char git_dir[PATH_MAX+1]; - - setenv(GIT_DIR_ENVIRONMENT, - getcwd(git_dir, sizeof(git_dir)), 0); - } - - if (init_shared_repository != -1) - shared_repository = init_shared_repository; - - /* - * GIT_WORK_TREE makes sense only in conjunction with GIT_DIR - * without --bare. Catch the error early. - */ - git_dir = getenv(GIT_DIR_ENVIRONMENT); - if ((!git_dir || is_bare_repository_cfg == 1) - && getenv(GIT_WORK_TREE_ENVIRONMENT)) - die("%s (or --work-tree=) not allowed without " - "specifying %s (or --git-dir=)", - GIT_WORK_TREE_ENVIRONMENT, - GIT_DIR_ENVIRONMENT); - - /* - * Set up the default .git directory contents - */ - if (!git_dir) - git_dir = DEFAULT_GIT_DIR_ENVIRONMENT; - - if (is_bare_repository_cfg < 0) - is_bare_repository_cfg = guess_repository_type(git_dir); - - if (!is_bare_repository_cfg) { - if (git_dir) { - const char *git_dir_parent = strrchr(git_dir, '/'); - if (git_dir_parent) { - char *rel = xstrndup(git_dir, git_dir_parent - git_dir); - git_work_tree_cfg = xstrdup(make_absolute_path(rel)); - free(rel); - } - } - if (!git_work_tree_cfg) { - git_work_tree_cfg = xcalloc(PATH_MAX, 1); - if (!getcwd(git_work_tree_cfg, PATH_MAX)) - die_errno ("Cannot access current working directory"); - } - if (access(get_git_work_tree(), X_OK)) - die_errno ("Cannot access work tree '%s'", - get_git_work_tree()); - } - - set_git_dir(make_absolute_path(git_dir)); - - return init_db(template_dir, flags); -} diff --git a/builtin-log.c b/builtin-log.c deleted file mode 100644 index e0d5caa..0000000 --- a/builtin-log.c +++ /dev/null @@ -1,1352 +0,0 @@ -/* - * Builtin "git log" and related commands (show, whatchanged) - * - * (C) Copyright 2006 Linus Torvalds - * 2006 Junio Hamano - */ -#include "cache.h" -#include "color.h" -#include "commit.h" -#include "diff.h" -#include "revision.h" -#include "log-tree.h" -#include "builtin.h" -#include "tag.h" -#include "reflog-walk.h" -#include "patch-ids.h" -#include "run-command.h" -#include "shortlog.h" -#include "remote.h" -#include "string-list.h" -#include "parse-options.h" - -/* Set a default date-time format for git log ("log.date" config variable) */ -static const char *default_date_mode = NULL; - -static int default_show_root = 1; -static const char *fmt_patch_subject_prefix = "PATCH"; -static const char *fmt_pretty; - -static const char * const builtin_log_usage = - "git log [] [..] [[--] ...]\n" - " or: git show [options] ..."; - -static void cmd_log_init(int argc, const char **argv, const char *prefix, - struct rev_info *rev) -{ - int i; - int decoration_style = 0; - - rev->abbrev = DEFAULT_ABBREV; - rev->commit_format = CMIT_FMT_DEFAULT; - if (fmt_pretty) - get_commit_format(fmt_pretty, rev); - rev->verbose_header = 1; - DIFF_OPT_SET(&rev->diffopt, RECURSIVE); - rev->show_root_diff = default_show_root; - rev->subject_prefix = fmt_patch_subject_prefix; - DIFF_OPT_SET(&rev->diffopt, ALLOW_TEXTCONV); - - if (default_date_mode) - rev->date_mode = parse_date_format(default_date_mode); - - /* - * Check for -h before setup_revisions(), or "git log -h" will - * fail when run without a git directory. - */ - if (argc == 2 && !strcmp(argv[1], "-h")) - usage(builtin_log_usage); - argc = setup_revisions(argc, argv, rev, "HEAD"); - - if (!rev->show_notes_given && !rev->pretty_given) - rev->show_notes = 1; - - if (rev->diffopt.pickaxe || rev->diffopt.filter) - rev->always_show_header = 0; - if (DIFF_OPT_TST(&rev->diffopt, FOLLOW_RENAMES)) { - rev->always_show_header = 0; - if (rev->diffopt.nr_paths != 1) - usage("git logs can only follow renames on one pathname at a time"); - } - for (i = 1; i < argc; i++) { - const char *arg = argv[i]; - if (!strcmp(arg, "--decorate")) { - decoration_style = DECORATE_SHORT_REFS; - } else if (!prefixcmp(arg, "--decorate=")) { - const char *v = skip_prefix(arg, "--decorate="); - if (!strcmp(v, "full")) - decoration_style = DECORATE_FULL_REFS; - else if (!strcmp(v, "short")) - decoration_style = DECORATE_SHORT_REFS; - else - die("invalid --decorate option: %s", arg); - } else if (!strcmp(arg, "--source")) { - rev->show_source = 1; - } else if (!strcmp(arg, "-h")) { - usage(builtin_log_usage); - } else - die("unrecognized argument: %s", arg); - } - if (decoration_style) { - rev->show_decorations = 1; - load_ref_decorations(decoration_style); - } -} - -/* - * This gives a rough estimate for how many commits we - * will print out in the list. - */ -static int estimate_commit_count(struct rev_info *rev, struct commit_list *list) -{ - int n = 0; - - while (list) { - struct commit *commit = list->item; - unsigned int flags = commit->object.flags; - list = list->next; - if (!(flags & (TREESAME | UNINTERESTING))) - n++; - } - return n; -} - -static void show_early_header(struct rev_info *rev, const char *stage, int nr) -{ - if (rev->shown_one) { - rev->shown_one = 0; - if (rev->commit_format != CMIT_FMT_ONELINE) - putchar(rev->diffopt.line_termination); - } - printf("Final output: %d %s\n", nr, stage); -} - -static struct itimerval early_output_timer; - -static void log_show_early(struct rev_info *revs, struct commit_list *list) -{ - int i = revs->early_output; - int show_header = 1; - - sort_in_topological_order(&list, revs->lifo); - while (list && i) { - struct commit *commit = list->item; - switch (simplify_commit(revs, commit)) { - case commit_show: - if (show_header) { - int n = estimate_commit_count(revs, list); - show_early_header(revs, "incomplete", n); - show_header = 0; - } - log_tree_commit(revs, commit); - i--; - break; - case commit_ignore: - break; - case commit_error: - return; - } - list = list->next; - } - - /* Did we already get enough commits for the early output? */ - if (!i) - return; - - /* - * ..if no, then repeat it twice a second until we - * do. - * - * NOTE! We don't use "it_interval", because if the - * reader isn't listening, we want our output to be - * throttled by the writing, and not have the timer - * trigger every second even if we're blocked on a - * reader! - */ - early_output_timer.it_value.tv_sec = 0; - early_output_timer.it_value.tv_usec = 500000; - setitimer(ITIMER_REAL, &early_output_timer, NULL); -} - -static void early_output(int signal) -{ - show_early_output = log_show_early; -} - -static void setup_early_output(struct rev_info *rev) -{ - struct sigaction sa; - - /* - * Set up the signal handler, minimally intrusively: - * we only set a single volatile integer word (not - * using sigatomic_t - trying to avoid unnecessary - * system dependencies and headers), and using - * SA_RESTART. - */ - memset(&sa, 0, sizeof(sa)); - sa.sa_handler = early_output; - sigemptyset(&sa.sa_mask); - sa.sa_flags = SA_RESTART; - sigaction(SIGALRM, &sa, NULL); - - /* - * If we can get the whole output in less than a - * tenth of a second, don't even bother doing the - * early-output thing.. - * - * This is a one-time-only trigger. - */ - early_output_timer.it_value.tv_sec = 0; - early_output_timer.it_value.tv_usec = 100000; - setitimer(ITIMER_REAL, &early_output_timer, NULL); -} - -static void finish_early_output(struct rev_info *rev) -{ - int n = estimate_commit_count(rev, rev->commits); - signal(SIGALRM, SIG_IGN); - show_early_header(rev, "done", n); -} - -static int cmd_log_walk(struct rev_info *rev) -{ - struct commit *commit; - - if (rev->early_output) - setup_early_output(rev); - - if (prepare_revision_walk(rev)) - die("revision walk setup failed"); - - if (rev->early_output) - finish_early_output(rev); - - /* - * For --check and --exit-code, the exit code is based on CHECK_FAILED - * and HAS_CHANGES being accumulated in rev->diffopt, so be careful to - * retain that state information if replacing rev->diffopt in this loop - */ - while ((commit = get_revision(rev)) != NULL) { - log_tree_commit(rev, commit); - if (!rev->reflog_info) { - /* we allow cycles in reflog ancestry */ - free(commit->buffer); - commit->buffer = NULL; - } - free_commit_list(commit->parents); - commit->parents = NULL; - } - if (rev->diffopt.output_format & DIFF_FORMAT_CHECKDIFF && - DIFF_OPT_TST(&rev->diffopt, CHECK_FAILED)) { - return 02; - } - return diff_result_code(&rev->diffopt, 0); -} - -static int git_log_config(const char *var, const char *value, void *cb) -{ - if (!strcmp(var, "format.pretty")) - return git_config_string(&fmt_pretty, var, value); - if (!strcmp(var, "format.subjectprefix")) - return git_config_string(&fmt_patch_subject_prefix, var, value); - if (!strcmp(var, "log.date")) - return git_config_string(&default_date_mode, var, value); - if (!strcmp(var, "log.showroot")) { - default_show_root = git_config_bool(var, value); - return 0; - } - return git_diff_ui_config(var, value, cb); -} - -int cmd_whatchanged(int argc, const char **argv, const char *prefix) -{ - struct rev_info rev; - - git_config(git_log_config, NULL); - - if (diff_use_color_default == -1) - diff_use_color_default = git_use_color_default; - - init_revisions(&rev, prefix); - rev.diff = 1; - rev.simplify_history = 0; - cmd_log_init(argc, argv, prefix, &rev); - if (!rev.diffopt.output_format) - rev.diffopt.output_format = DIFF_FORMAT_RAW; - return cmd_log_walk(&rev); -} - -static void show_tagger(char *buf, int len, struct rev_info *rev) -{ - struct strbuf out = STRBUF_INIT; - - pp_user_info("Tagger", rev->commit_format, &out, buf, rev->date_mode, - git_log_output_encoding ? - git_log_output_encoding: git_commit_encoding); - printf("%s", out.buf); - strbuf_release(&out); -} - -static int show_object(const unsigned char *sha1, int show_tag_object, - struct rev_info *rev) -{ - unsigned long size; - enum object_type type; - char *buf = read_sha1_file(sha1, &type, &size); - int offset = 0; - - if (!buf) - return error("Could not read object %s", sha1_to_hex(sha1)); - - if (show_tag_object) - while (offset < size && buf[offset] != '\n') { - int new_offset = offset + 1; - while (new_offset < size && buf[new_offset++] != '\n') - ; /* do nothing */ - if (!prefixcmp(buf + offset, "tagger ")) - show_tagger(buf + offset + 7, - new_offset - offset - 7, rev); - offset = new_offset; - } - - if (offset < size) - fwrite(buf + offset, size - offset, 1, stdout); - free(buf); - return 0; -} - -static int show_tree_object(const unsigned char *sha1, - const char *base, int baselen, - const char *pathname, unsigned mode, int stage, void *context) -{ - printf("%s%s\n", pathname, S_ISDIR(mode) ? "/" : ""); - return 0; -} - -int cmd_show(int argc, const char **argv, const char *prefix) -{ - struct rev_info rev; - struct object_array_entry *objects; - int i, count, ret = 0; - - git_config(git_log_config, NULL); - - if (diff_use_color_default == -1) - diff_use_color_default = git_use_color_default; - - init_revisions(&rev, prefix); - rev.diff = 1; - rev.combine_merges = 1; - rev.dense_combined_merges = 1; - rev.always_show_header = 1; - rev.ignore_merges = 0; - rev.no_walk = 1; - cmd_log_init(argc, argv, prefix, &rev); - - count = rev.pending.nr; - objects = rev.pending.objects; - for (i = 0; i < count && !ret; i++) { - struct object *o = objects[i].item; - const char *name = objects[i].name; - switch (o->type) { - case OBJ_BLOB: - ret = show_object(o->sha1, 0, NULL); - break; - case OBJ_TAG: { - struct tag *t = (struct tag *)o; - - if (rev.shown_one) - putchar('\n'); - printf("%stag %s%s\n", - diff_get_color_opt(&rev.diffopt, DIFF_COMMIT), - t->tag, - diff_get_color_opt(&rev.diffopt, DIFF_RESET)); - ret = show_object(o->sha1, 1, &rev); - rev.shown_one = 1; - if (ret) - break; - o = parse_object(t->tagged->sha1); - if (!o) - ret = error("Could not read object %s", - sha1_to_hex(t->tagged->sha1)); - objects[i].item = o; - i--; - break; - } - case OBJ_TREE: - if (rev.shown_one) - putchar('\n'); - printf("%stree %s%s\n\n", - diff_get_color_opt(&rev.diffopt, DIFF_COMMIT), - name, - diff_get_color_opt(&rev.diffopt, DIFF_RESET)); - read_tree_recursive((struct tree *)o, "", 0, 0, NULL, - show_tree_object, NULL); - rev.shown_one = 1; - break; - case OBJ_COMMIT: - rev.pending.nr = rev.pending.alloc = 0; - rev.pending.objects = NULL; - add_object_array(o, name, &rev.pending); - ret = cmd_log_walk(&rev); - break; - default: - ret = error("Unknown type: %d", o->type); - } - } - free(objects); - return ret; -} - -/* - * This is equivalent to "git log -g --abbrev-commit --pretty=oneline" - */ -int cmd_log_reflog(int argc, const char **argv, const char *prefix) -{ - struct rev_info rev; - - git_config(git_log_config, NULL); - - if (diff_use_color_default == -1) - diff_use_color_default = git_use_color_default; - - init_revisions(&rev, prefix); - init_reflog_walk(&rev.reflog_info); - rev.abbrev_commit = 1; - rev.verbose_header = 1; - cmd_log_init(argc, argv, prefix, &rev); - - /* - * This means that we override whatever commit format the user gave - * on the cmd line. Sad, but cmd_log_init() currently doesn't - * allow us to set a different default. - */ - rev.commit_format = CMIT_FMT_ONELINE; - rev.use_terminator = 1; - rev.always_show_header = 1; - - /* - * We get called through "git reflog", so unlike the other log - * routines, we need to set up our pager manually.. - */ - setup_pager(); - - return cmd_log_walk(&rev); -} - -int cmd_log(int argc, const char **argv, const char *prefix) -{ - struct rev_info rev; - - git_config(git_log_config, NULL); - - if (diff_use_color_default == -1) - diff_use_color_default = git_use_color_default; - - init_revisions(&rev, prefix); - rev.always_show_header = 1; - cmd_log_init(argc, argv, prefix, &rev); - return cmd_log_walk(&rev); -} - -/* format-patch */ - -static const char *fmt_patch_suffix = ".patch"; -static int numbered = 0; -static int auto_number = 1; - -static char *default_attach = NULL; - -static char **extra_hdr; -static int extra_hdr_nr; -static int extra_hdr_alloc; - -static char **extra_to; -static int extra_to_nr; -static int extra_to_alloc; - -static char **extra_cc; -static int extra_cc_nr; -static int extra_cc_alloc; - -static void add_header(const char *value) -{ - int len = strlen(value); - while (len && value[len - 1] == '\n') - len--; - if (!strncasecmp(value, "to: ", 4)) { - ALLOC_GROW(extra_to, extra_to_nr + 1, extra_to_alloc); - extra_to[extra_to_nr++] = xstrndup(value + 4, len - 4); - return; - } - if (!strncasecmp(value, "cc: ", 4)) { - ALLOC_GROW(extra_cc, extra_cc_nr + 1, extra_cc_alloc); - extra_cc[extra_cc_nr++] = xstrndup(value + 4, len - 4); - return; - } - ALLOC_GROW(extra_hdr, extra_hdr_nr + 1, extra_hdr_alloc); - extra_hdr[extra_hdr_nr++] = xstrndup(value, len); -} - -#define THREAD_SHALLOW 1 -#define THREAD_DEEP 2 -static int thread = 0; -static int do_signoff = 0; - -static int git_format_config(const char *var, const char *value, void *cb) -{ - if (!strcmp(var, "format.headers")) { - if (!value) - die("format.headers without value"); - add_header(value); - return 0; - } - if (!strcmp(var, "format.suffix")) - return git_config_string(&fmt_patch_suffix, var, value); - if (!strcmp(var, "format.cc")) { - if (!value) - return config_error_nonbool(var); - ALLOC_GROW(extra_cc, extra_cc_nr + 1, extra_cc_alloc); - extra_cc[extra_cc_nr++] = xstrdup(value); - return 0; - } - if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff")) { - return 0; - } - if (!strcmp(var, "format.numbered")) { - if (value && !strcasecmp(value, "auto")) { - auto_number = 1; - return 0; - } - numbered = git_config_bool(var, value); - auto_number = auto_number && numbered; - return 0; - } - if (!strcmp(var, "format.attach")) { - if (value && *value) - default_attach = xstrdup(value); - else - default_attach = xstrdup(git_version_string); - return 0; - } - if (!strcmp(var, "format.thread")) { - if (value && !strcasecmp(value, "deep")) { - thread = THREAD_DEEP; - return 0; - } - if (value && !strcasecmp(value, "shallow")) { - thread = THREAD_SHALLOW; - return 0; - } - thread = git_config_bool(var, value) && THREAD_SHALLOW; - return 0; - } - if (!strcmp(var, "format.signoff")) { - do_signoff = git_config_bool(var, value); - return 0; - } - - return git_log_config(var, value, cb); -} - -static FILE *realstdout = NULL; -static const char *output_directory = NULL; -static int outdir_offset; - -static int reopen_stdout(struct commit *commit, struct rev_info *rev) -{ - struct strbuf filename = STRBUF_INIT; - int suffix_len = strlen(fmt_patch_suffix) + 1; - - if (output_directory) { - strbuf_addstr(&filename, output_directory); - if (filename.len >= - PATH_MAX - FORMAT_PATCH_NAME_MAX - suffix_len) - return error("name of output directory is too long"); - if (filename.buf[filename.len - 1] != '/') - strbuf_addch(&filename, '/'); - } - - get_patch_filename(commit, rev->nr, fmt_patch_suffix, &filename); - - if (!DIFF_OPT_TST(&rev->diffopt, QUICK)) - fprintf(realstdout, "%s\n", filename.buf + outdir_offset); - - if (freopen(filename.buf, "w", stdout) == NULL) - return error("Cannot open patch file %s", filename.buf); - - strbuf_release(&filename); - return 0; -} - -static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids, const char *prefix) -{ - struct rev_info check_rev; - struct commit *commit; - struct object *o1, *o2; - unsigned flags1, flags2; - - if (rev->pending.nr != 2) - die("Need exactly one range."); - - o1 = rev->pending.objects[0].item; - flags1 = o1->flags; - o2 = rev->pending.objects[1].item; - flags2 = o2->flags; - - if ((flags1 & UNINTERESTING) == (flags2 & UNINTERESTING)) - die("Not a range."); - - init_patch_ids(ids); - - /* given a range a..b get all patch ids for b..a */ - init_revisions(&check_rev, prefix); - o1->flags ^= UNINTERESTING; - o2->flags ^= UNINTERESTING; - add_pending_object(&check_rev, o1, "o1"); - add_pending_object(&check_rev, o2, "o2"); - if (prepare_revision_walk(&check_rev)) - die("revision walk setup failed"); - - while ((commit = get_revision(&check_rev)) != NULL) { - /* ignore merges */ - if (commit->parents && commit->parents->next) - continue; - - add_commit_patch_id(commit, ids); - } - - /* reset for next revision walk */ - clear_commit_marks((struct commit *)o1, - SEEN | UNINTERESTING | SHOWN | ADDED); - clear_commit_marks((struct commit *)o2, - SEEN | UNINTERESTING | SHOWN | ADDED); - o1->flags = flags1; - o2->flags = flags2; -} - -static void gen_message_id(struct rev_info *info, char *base) -{ - const char *committer = git_committer_info(IDENT_WARN_ON_NO_NAME); - const char *email_start = strrchr(committer, '<'); - const char *email_end = strrchr(committer, '>'); - struct strbuf buf = STRBUF_INIT; - if (!email_start || !email_end || email_start > email_end - 1) - die("Could not extract email from committer identity."); - strbuf_addf(&buf, "%s.%lu.git.%.*s", base, - (unsigned long) time(NULL), - (int)(email_end - email_start - 1), email_start + 1); - info->message_id = strbuf_detach(&buf, NULL); -} - -static void make_cover_letter(struct rev_info *rev, int use_stdout, - int numbered, int numbered_files, - struct commit *origin, - int nr, struct commit **list, struct commit *head) -{ - const char *committer; - const char *subject_start = NULL; - const char *body = "*** SUBJECT HERE ***\n\n*** BLURB HERE ***\n"; - const char *msg; - const char *extra_headers = rev->extra_headers; - struct shortlog log; - struct strbuf sb = STRBUF_INIT; - int i; - const char *encoding = "UTF-8"; - struct diff_options opts; - int need_8bit_cte = 0; - struct commit *commit = NULL; - - if (rev->commit_format != CMIT_FMT_EMAIL) - die("Cover letter needs email format"); - - committer = git_committer_info(0); - - if (!numbered_files) { - /* - * We fake a commit for the cover letter so we get the filename - * desired. - */ - commit = xcalloc(1, sizeof(*commit)); - commit->buffer = xmalloc(400); - snprintf(commit->buffer, 400, - "tree 0000000000000000000000000000000000000000\n" - "parent %s\n" - "author %s\n" - "committer %s\n\n" - "cover letter\n", - sha1_to_hex(head->object.sha1), committer, committer); - } - - if (!use_stdout && reopen_stdout(commit, rev)) - return; - - if (commit) { - - free(commit->buffer); - free(commit); - } - - log_write_email_headers(rev, head, &subject_start, &extra_headers, - &need_8bit_cte); - - for (i = 0; !need_8bit_cte && i < nr; i++) - if (has_non_ascii(list[i]->buffer)) - need_8bit_cte = 1; - - msg = body; - pp_user_info(NULL, CMIT_FMT_EMAIL, &sb, committer, DATE_RFC2822, - encoding); - pp_title_line(CMIT_FMT_EMAIL, &msg, &sb, subject_start, extra_headers, - encoding, need_8bit_cte); - pp_remainder(CMIT_FMT_EMAIL, &msg, &sb, 0); - printf("%s\n", sb.buf); - - strbuf_release(&sb); - - shortlog_init(&log); - log.wrap_lines = 1; - log.wrap = 72; - log.in1 = 2; - log.in2 = 4; - for (i = 0; i < nr; i++) - shortlog_add_commit(&log, list[i]); - - shortlog_output(&log); - - /* - * We can only do diffstat with a unique reference point - */ - if (!origin) - return; - - memcpy(&opts, &rev->diffopt, sizeof(opts)); - opts.output_format = DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT; - - diff_setup_done(&opts); - - diff_tree_sha1(origin->tree->object.sha1, - head->tree->object.sha1, - "", &opts); - diffcore_std(&opts); - diff_flush(&opts); - - printf("\n"); -} - -static const char *clean_message_id(const char *msg_id) -{ - char ch; - const char *a, *z, *m; - - m = msg_id; - while ((ch = *m) && (isspace(ch) || (ch == '<'))) - m++; - a = m; - z = NULL; - while ((ch = *m)) { - if (!isspace(ch) && (ch != '>')) - z = m; - m++; - } - if (!z) - die("insane in-reply-to: %s", msg_id); - if (++z == m) - return a; - return xmemdupz(a, z - a); -} - -static const char *set_outdir(const char *prefix, const char *output_directory) -{ - if (output_directory && is_absolute_path(output_directory)) - return output_directory; - - if (!prefix || !*prefix) { - if (output_directory) - return output_directory; - /* The user did not explicitly ask for "./" */ - outdir_offset = 2; - return "./"; - } - - outdir_offset = strlen(prefix); - if (!output_directory) - return prefix; - - return xstrdup(prefix_filename(prefix, outdir_offset, - output_directory)); -} - -static const char * const builtin_format_patch_usage[] = { - "git format-patch [options] [ | ]", - NULL -}; - -static int keep_subject = 0; - -static int keep_callback(const struct option *opt, const char *arg, int unset) -{ - ((struct rev_info *)opt->value)->total = -1; - keep_subject = 1; - return 0; -} - -static int subject_prefix = 0; - -static int subject_prefix_callback(const struct option *opt, const char *arg, - int unset) -{ - subject_prefix = 1; - ((struct rev_info *)opt->value)->subject_prefix = arg; - return 0; -} - -static int numbered_cmdline_opt = 0; - -static int numbered_callback(const struct option *opt, const char *arg, - int unset) -{ - *(int *)opt->value = numbered_cmdline_opt = unset ? 0 : 1; - if (unset) - auto_number = 0; - return 0; -} - -static int no_numbered_callback(const struct option *opt, const char *arg, - int unset) -{ - return numbered_callback(opt, arg, 1); -} - -static int output_directory_callback(const struct option *opt, const char *arg, - int unset) -{ - const char **dir = (const char **)opt->value; - if (*dir) - die("Two output directories?"); - *dir = arg; - return 0; -} - -static int thread_callback(const struct option *opt, const char *arg, int unset) -{ - int *thread = (int *)opt->value; - if (unset) - *thread = 0; - else if (!arg || !strcmp(arg, "shallow")) - *thread = THREAD_SHALLOW; - else if (!strcmp(arg, "deep")) - *thread = THREAD_DEEP; - else - return 1; - return 0; -} - -static int attach_callback(const struct option *opt, const char *arg, int unset) -{ - struct rev_info *rev = (struct rev_info *)opt->value; - if (unset) - rev->mime_boundary = NULL; - else if (arg) - rev->mime_boundary = arg; - else - rev->mime_boundary = git_version_string; - rev->no_inline = unset ? 0 : 1; - return 0; -} - -static int inline_callback(const struct option *opt, const char *arg, int unset) -{ - struct rev_info *rev = (struct rev_info *)opt->value; - if (unset) - rev->mime_boundary = NULL; - else if (arg) - rev->mime_boundary = arg; - else - rev->mime_boundary = git_version_string; - rev->no_inline = 0; - return 0; -} - -static int header_callback(const struct option *opt, const char *arg, int unset) -{ - add_header(arg); - return 0; -} - -static int cc_callback(const struct option *opt, const char *arg, int unset) -{ - ALLOC_GROW(extra_cc, extra_cc_nr + 1, extra_cc_alloc); - extra_cc[extra_cc_nr++] = xstrdup(arg); - return 0; -} - -int cmd_format_patch(int argc, const char **argv, const char *prefix) -{ - struct commit *commit; - struct commit **list = NULL; - struct rev_info rev; - int nr = 0, total, i; - int use_stdout = 0; - int start_number = -1; - int numbered_files = 0; /* _just_ numbers */ - int ignore_if_in_upstream = 0; - int cover_letter = 0; - int boundary_count = 0; - int no_binary_diff = 0; - struct commit *origin = NULL, *head = NULL; - const char *in_reply_to = NULL; - struct patch_ids ids; - char *add_signoff = NULL; - struct strbuf buf = STRBUF_INIT; - int use_patch_format = 0; - const struct option builtin_format_patch_options[] = { - { OPTION_CALLBACK, 'n', "numbered", &numbered, NULL, - "use [PATCH n/m] even with a single patch", - PARSE_OPT_NOARG, numbered_callback }, - { OPTION_CALLBACK, 'N', "no-numbered", &numbered, NULL, - "use [PATCH] even with multiple patches", - PARSE_OPT_NOARG, no_numbered_callback }, - OPT_BOOLEAN('s', "signoff", &do_signoff, "add Signed-off-by:"), - OPT_BOOLEAN(0, "stdout", &use_stdout, - "print patches to standard out"), - OPT_BOOLEAN(0, "cover-letter", &cover_letter, - "generate a cover letter"), - OPT_BOOLEAN(0, "numbered-files", &numbered_files, - "use simple number sequence for output file names"), - OPT_STRING(0, "suffix", &fmt_patch_suffix, "sfx", - "use instead of '.patch'"), - OPT_INTEGER(0, "start-number", &start_number, - "start numbering patches at instead of 1"), - { OPTION_CALLBACK, 0, "subject-prefix", &rev, "prefix", - "Use [] instead of [PATCH]", - PARSE_OPT_NONEG, subject_prefix_callback }, - { OPTION_CALLBACK, 'o', "output-directory", &output_directory, - "dir", "store resulting files in ", - PARSE_OPT_NONEG, output_directory_callback }, - { OPTION_CALLBACK, 'k', "keep-subject", &rev, NULL, - "don't strip/add [PATCH]", - PARSE_OPT_NOARG | PARSE_OPT_NONEG, keep_callback }, - OPT_BOOLEAN(0, "no-binary", &no_binary_diff, - "don't output binary diffs"), - OPT_BOOLEAN(0, "ignore-if-in-upstream", &ignore_if_in_upstream, - "don't include a patch matching a commit upstream"), - { OPTION_BOOLEAN, 'p', "no-stat", &use_patch_format, NULL, - "show patch format instead of default (patch + stat)", - PARSE_OPT_NONEG | PARSE_OPT_NOARG }, - OPT_GROUP("Messaging"), - { OPTION_CALLBACK, 0, "add-header", NULL, "header", - "add email header", PARSE_OPT_NONEG, - header_callback }, - { OPTION_CALLBACK, 0, "cc", NULL, "email", "add Cc: header", - PARSE_OPT_NONEG, cc_callback }, - OPT_STRING(0, "in-reply-to", &in_reply_to, "message-id", - "make first mail a reply to "), - { OPTION_CALLBACK, 0, "attach", &rev, "boundary", - "attach the patch", PARSE_OPT_OPTARG, - attach_callback }, - { OPTION_CALLBACK, 0, "inline", &rev, "boundary", - "inline the patch", - PARSE_OPT_OPTARG | PARSE_OPT_NONEG, - inline_callback }, - { OPTION_CALLBACK, 0, "thread", &thread, "style", - "enable message threading, styles: shallow, deep", - PARSE_OPT_OPTARG, thread_callback }, - OPT_END() - }; - - git_config(git_format_config, NULL); - init_revisions(&rev, prefix); - rev.commit_format = CMIT_FMT_EMAIL; - rev.verbose_header = 1; - rev.diff = 1; - rev.combine_merges = 0; - rev.ignore_merges = 1; - DIFF_OPT_SET(&rev.diffopt, RECURSIVE); - - rev.subject_prefix = fmt_patch_subject_prefix; - - if (default_attach) { - rev.mime_boundary = default_attach; - rev.no_inline = 1; - } - - /* - * Parse the arguments before setup_revisions(), or something - * like "git format-patch -o a123 HEAD^.." may fail; a123 is - * possibly a valid SHA1. - */ - argc = parse_options(argc, argv, prefix, builtin_format_patch_options, - builtin_format_patch_usage, - PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN | - PARSE_OPT_KEEP_DASHDASH); - - if (do_signoff) { - const char *committer; - const char *endpos; - committer = git_committer_info(IDENT_ERROR_ON_NO_NAME); - endpos = strchr(committer, '>'); - if (!endpos) - die("bogus committer info %s", committer); - add_signoff = xmemdupz(committer, endpos - committer + 1); - } - - for (i = 0; i < extra_hdr_nr; i++) { - strbuf_addstr(&buf, extra_hdr[i]); - strbuf_addch(&buf, '\n'); - } - - if (extra_to_nr) - strbuf_addstr(&buf, "To: "); - for (i = 0; i < extra_to_nr; i++) { - if (i) - strbuf_addstr(&buf, " "); - strbuf_addstr(&buf, extra_to[i]); - if (i + 1 < extra_to_nr) - strbuf_addch(&buf, ','); - strbuf_addch(&buf, '\n'); - } - - if (extra_cc_nr) - strbuf_addstr(&buf, "Cc: "); - for (i = 0; i < extra_cc_nr; i++) { - if (i) - strbuf_addstr(&buf, " "); - strbuf_addstr(&buf, extra_cc[i]); - if (i + 1 < extra_cc_nr) - strbuf_addch(&buf, ','); - strbuf_addch(&buf, '\n'); - } - - rev.extra_headers = strbuf_detach(&buf, NULL); - - if (start_number < 0) - start_number = 1; - - /* - * If numbered is set solely due to format.numbered in config, - * and it would conflict with --keep-subject (-k) from the - * command line, reset "numbered". - */ - if (numbered && keep_subject && !numbered_cmdline_opt) - numbered = 0; - - if (numbered && keep_subject) - die ("-n and -k are mutually exclusive."); - if (keep_subject && subject_prefix) - die ("--subject-prefix and -k are mutually exclusive."); - - argc = setup_revisions(argc, argv, &rev, "HEAD"); - if (argc > 1) - die ("unrecognized argument: %s", argv[1]); - - if (rev.diffopt.output_format & DIFF_FORMAT_NAME) - die("--name-only does not make sense"); - if (rev.diffopt.output_format & DIFF_FORMAT_NAME_STATUS) - die("--name-status does not make sense"); - if (rev.diffopt.output_format & DIFF_FORMAT_CHECKDIFF) - die("--check does not make sense"); - - if (!use_patch_format && - (!rev.diffopt.output_format || - rev.diffopt.output_format == DIFF_FORMAT_PATCH)) - rev.diffopt.output_format = DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_SUMMARY; - - /* Always generate a patch */ - rev.diffopt.output_format |= DIFF_FORMAT_PATCH; - - if (!DIFF_OPT_TST(&rev.diffopt, TEXT) && !no_binary_diff) - DIFF_OPT_SET(&rev.diffopt, BINARY); - - if (!use_stdout) - output_directory = set_outdir(prefix, output_directory); - - if (output_directory) { - if (use_stdout) - die("standard output, or directory, which one?"); - if (mkdir(output_directory, 0777) < 0 && errno != EEXIST) - die_errno("Could not create directory '%s'", - output_directory); - } - - if (rev.pending.nr == 1) { - if (rev.max_count < 0 && !rev.show_root_diff) { - /* - * This is traditional behaviour of "git format-patch - * origin" that prepares what the origin side still - * does not have. - */ - rev.pending.objects[0].item->flags |= UNINTERESTING; - add_head_to_pending(&rev); - } - /* - * Otherwise, it is "format-patch -22 HEAD", and/or - * "format-patch --root HEAD". The user wants - * get_revision() to do the usual traversal. - */ - } - - /* - * We cannot move this anywhere earlier because we do want to - * know if --root was given explicitly from the command line. - */ - rev.show_root_diff = 1; - - if (cover_letter) { - /* remember the range */ - int i; - for (i = 0; i < rev.pending.nr; i++) { - struct object *o = rev.pending.objects[i].item; - if (!(o->flags & UNINTERESTING)) - head = (struct commit *)o; - } - /* We can't generate a cover letter without any patches */ - if (!head) - return 0; - } - - if (ignore_if_in_upstream) - get_patch_ids(&rev, &ids, prefix); - - if (!use_stdout) - realstdout = xfdopen(xdup(1), "w"); - - if (prepare_revision_walk(&rev)) - die("revision walk setup failed"); - rev.boundary = 1; - while ((commit = get_revision(&rev)) != NULL) { - if (commit->object.flags & BOUNDARY) { - boundary_count++; - origin = (boundary_count == 1) ? commit : NULL; - continue; - } - - /* ignore merges */ - if (commit->parents && commit->parents->next) - continue; - - if (ignore_if_in_upstream && - has_commit_patch_id(commit, &ids)) - continue; - - nr++; - list = xrealloc(list, nr * sizeof(list[0])); - list[nr - 1] = commit; - } - total = nr; - if (!keep_subject && auto_number && total > 1) - numbered = 1; - if (numbered) - rev.total = total + start_number - 1; - if (in_reply_to || thread || cover_letter) - rev.ref_message_ids = xcalloc(1, sizeof(struct string_list)); - if (in_reply_to) { - const char *msgid = clean_message_id(in_reply_to); - string_list_append(msgid, rev.ref_message_ids); - } - rev.numbered_files = numbered_files; - rev.patch_suffix = fmt_patch_suffix; - if (cover_letter) { - if (thread) - gen_message_id(&rev, "cover"); - make_cover_letter(&rev, use_stdout, numbered, numbered_files, - origin, nr, list, head); - total++; - start_number--; - } - rev.add_signoff = add_signoff; - while (0 <= --nr) { - int shown; - commit = list[nr]; - rev.nr = total - nr + (start_number - 1); - /* Make the second and subsequent mails replies to the first */ - if (thread) { - /* Have we already had a message ID? */ - if (rev.message_id) { - /* - * For deep threading: make every mail - * a reply to the previous one, no - * matter what other options are set. - * - * For shallow threading: - * - * Without --cover-letter and - * --in-reply-to, make every mail a - * reply to the one before. - * - * With --in-reply-to but no - * --cover-letter, make every mail a - * reply to the . - * - * With --cover-letter, make every - * mail but the cover letter a reply - * to the cover letter. The cover - * letter is a reply to the - * --in-reply-to, if specified. - */ - if (thread == THREAD_SHALLOW - && rev.ref_message_ids->nr > 0 - && (!cover_letter || rev.nr > 1)) - free(rev.message_id); - else - string_list_append(rev.message_id, - rev.ref_message_ids); - } - gen_message_id(&rev, sha1_to_hex(commit->object.sha1)); - } - - if (!use_stdout && reopen_stdout(numbered_files ? NULL : commit, - &rev)) - die("Failed to create output files"); - shown = log_tree_commit(&rev, commit); - free(commit->buffer); - commit->buffer = NULL; - - /* We put one extra blank line between formatted - * patches and this flag is used by log-tree code - * to see if it needs to emit a LF before showing - * the log; when using one file per patch, we do - * not want the extra blank line. - */ - if (!use_stdout) - rev.shown_one = 0; - if (shown) { - if (rev.mime_boundary) - printf("\n--%s%s--\n\n\n", - mime_boundary_leader, - rev.mime_boundary); - else - printf("-- \n%s\n\n", git_version_string); - } - if (!use_stdout) - fclose(stdout); - } - free(list); - if (ignore_if_in_upstream) - free_patch_ids(&ids); - return 0; -} - -static int add_pending_commit(const char *arg, struct rev_info *revs, int flags) -{ - unsigned char sha1[20]; - if (get_sha1(arg, sha1) == 0) { - struct commit *commit = lookup_commit_reference(sha1); - if (commit) { - commit->object.flags |= flags; - add_pending_object(revs, &commit->object, arg); - return 0; - } - } - return -1; -} - -static const char cherry_usage[] = -"git cherry [-v] [ [ []]]"; -int cmd_cherry(int argc, const char **argv, const char *prefix) -{ - struct rev_info revs; - struct patch_ids ids; - struct commit *commit; - struct commit_list *list = NULL; - struct branch *current_branch; - const char *upstream; - const char *head = "HEAD"; - const char *limit = NULL; - int verbose = 0; - - if (argc > 1 && !strcmp(argv[1], "-v")) { - verbose = 1; - argc--; - argv++; - } - - if (argc > 1 && !strcmp(argv[1], "-h")) - usage(cherry_usage); - - switch (argc) { - case 4: - limit = argv[3]; - /* FALLTHROUGH */ - case 3: - head = argv[2]; - /* FALLTHROUGH */ - case 2: - upstream = argv[1]; - break; - default: - current_branch = branch_get(NULL); - if (!current_branch || !current_branch->merge - || !current_branch->merge[0] - || !current_branch->merge[0]->dst) { - fprintf(stderr, "Could not find a tracked" - " remote branch, please" - " specify manually.\n"); - usage(cherry_usage); - } - - upstream = current_branch->merge[0]->dst; - } - - init_revisions(&revs, prefix); - revs.diff = 1; - revs.combine_merges = 0; - revs.ignore_merges = 1; - DIFF_OPT_SET(&revs.diffopt, RECURSIVE); - - if (add_pending_commit(head, &revs, 0)) - die("Unknown commit %s", head); - if (add_pending_commit(upstream, &revs, UNINTERESTING)) - die("Unknown commit %s", upstream); - - /* Don't say anything if head and upstream are the same. */ - if (revs.pending.nr == 2) { - struct object_array_entry *o = revs.pending.objects; - if (hashcmp(o[0].item->sha1, o[1].item->sha1) == 0) - return 0; - } - - get_patch_ids(&revs, &ids, prefix); - - if (limit && add_pending_commit(limit, &revs, UNINTERESTING)) - die("Unknown commit %s", limit); - - /* reverse the list of commits */ - if (prepare_revision_walk(&revs)) - die("revision walk setup failed"); - while ((commit = get_revision(&revs)) != NULL) { - /* ignore merges */ - if (commit->parents && commit->parents->next) - continue; - - commit_list_insert(commit, &list); - } - - while (list) { - char sign = '+'; - - commit = list->item; - if (has_commit_patch_id(commit, &ids)) - sign = '-'; - - if (verbose) { - struct strbuf buf = STRBUF_INIT; - struct pretty_print_context ctx = {0}; - pretty_print_commit(CMIT_FMT_ONELINE, commit, - &buf, &ctx); - printf("%c %s %s\n", sign, - sha1_to_hex(commit->object.sha1), buf.buf); - strbuf_release(&buf); - } - else { - printf("%c %s\n", sign, - sha1_to_hex(commit->object.sha1)); - } - - list = list->next; - } - - free_patch_ids(&ids); - return 0; -} diff --git a/builtin-ls-files.c b/builtin-ls-files.c deleted file mode 100644 index b065061..0000000 --- a/builtin-ls-files.c +++ /dev/null @@ -1,606 +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 "cache.h" -#include "quote.h" -#include "dir.h" -#include "builtin.h" -#include "tree.h" -#include "parse-options.h" -#include "resolve-undo.h" -#include "string-list.h" - -static int abbrev; -static int show_deleted; -static int show_cached; -static int show_others; -static int show_stage; -static int show_unmerged; -static int show_resolve_undo; -static int show_modified; -static int show_killed; -static int show_valid_bit; -static int line_terminator = '\n'; - -static int prefix_len; -static int prefix_offset; -static const char **pathspec; -static int error_unmatch; -static char *ps_matched; -static const char *with_tree; -static int exc_given; - -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 *tag_skip_worktree = ""; -static const char *tag_resolve_undo = ""; - -static void show_dir_entry(const char *tag, struct dir_entry *ent) -{ - int len = prefix_len; - int offset = prefix_offset; - - if (len >= ent->len) - die("git ls-files: internal error - directory entry not superset of prefix"); - - if (!match_pathspec(pathspec, ent->name, ent->len, len, ps_matched)) - return; - - fputs(tag, stdout); - write_name_quoted(ent->name + offset, stdout, line_terminator); -} - -static void show_other_files(struct dir_struct *dir) -{ - int i; - - for (i = 0; i < dir->nr; i++) { - struct dir_entry *ent = dir->entries[i]; - if (!cache_name_is_other(ent->name, ent->len)) - continue; - show_dir_entry(tag_other, ent); - } -} - -static void show_killed_files(struct dir_struct *dir) -{ - int i; - for (i = 0; i < dir->nr; i++) { - struct dir_entry *ent = dir->entries[i]; - char *cp, *sp; - int pos, len, killed = 0; - - for (cp = ent->name; cp - ent->name < ent->len; cp = sp + 1) { - sp = strchr(cp, '/'); - if (!sp) { - /* If ent->name is prefix of an entry in the - * cache, it will be killed. - */ - pos = cache_name_pos(ent->name, ent->len); - if (0 <= pos) - die("bug in show-killed-files"); - pos = -pos - 1; - while (pos < active_nr && - ce_stage(active_cache[pos])) - pos++; /* skip unmerged */ - if (active_nr <= pos) - break; - /* pos points at a name immediately after - * ent->name in the cache. Does it expect - * ent->name to be a directory? - */ - len = ce_namelen(active_cache[pos]); - if ((ent->len < len) && - !strncmp(active_cache[pos]->name, - ent->name, ent->len) && - active_cache[pos]->name[ent->len] == '/') - killed = 1; - break; - } - if (0 <= cache_name_pos(ent->name, sp - ent->name)) { - /* If any of the leading directories in - * ent->name is registered in the cache, - * ent->name will be killed. - */ - killed = 1; - break; - } - } - if (killed) - show_dir_entry(tag_killed, dir->entries[i]); - } -} - -static void show_ce_entry(const char *tag, struct cache_entry *ce) -{ - int len = prefix_len; - int offset = prefix_offset; - - if (len >= ce_namelen(ce)) - die("git ls-files: internal error - cache entry not superset of prefix"); - - if (!match_pathspec(pathspec, ce->name, ce_namelen(ce), len, ps_matched)) - return; - - if (tag && *tag && show_valid_bit && - (ce->ce_flags & 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); - } else { - printf("%s%06o %s %d\t", - tag, - ce->ce_mode, - abbrev ? find_unique_abbrev(ce->sha1,abbrev) - : sha1_to_hex(ce->sha1), - ce_stage(ce)); - } - write_name_quoted(ce->name + offset, stdout, line_terminator); -} - -static int show_one_ru(struct string_list_item *item, void *cbdata) -{ - int offset = prefix_offset; - const char *path = item->string; - struct resolve_undo_info *ui = item->util; - int i, len; - - len = strlen(path); - if (len < prefix_len) - return 0; /* outside of the prefix */ - if (!match_pathspec(pathspec, path, len, prefix_len, ps_matched)) - return 0; /* uninterested */ - for (i = 0; i < 3; i++) { - if (!ui->mode[i]) - continue; - printf("%s%06o %s %d\t", tag_resolve_undo, ui->mode[i], - abbrev - ? find_unique_abbrev(ui->sha1[i], abbrev) - : sha1_to_hex(ui->sha1[i]), - i + 1); - write_name_quoted(path + offset, stdout, line_terminator); - } - return 0; -} - -static void show_ru_info(const char *prefix) -{ - if (!the_index.resolve_undo) - return; - for_each_string_list(show_one_ru, the_index.resolve_undo, NULL); -} - -static void show_files(struct dir_struct *dir, const char *prefix) -{ - int i; - - /* For cached/deleted files we don't need to even do the readdir */ - if (show_others || show_killed) { - fill_directory(dir, pathspec); - if (show_others) - show_other_files(dir); - if (show_killed) - show_killed_files(dir); - } - if (show_cached | show_stage) { - for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; - int dtype = ce_to_dtype(ce); - if (dir->flags & DIR_SHOW_IGNORED && - !excluded(dir, ce->name, &dtype)) - continue; - if (show_unmerged && !ce_stage(ce)) - continue; - if (ce->ce_flags & CE_UPDATE) - continue; - show_ce_entry(ce_stage(ce) ? tag_unmerged : - (ce_skip_worktree(ce) ? tag_skip_worktree : 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; - int dtype = ce_to_dtype(ce); - if (dir->flags & DIR_SHOW_IGNORED && - !excluded(dir, ce->name, &dtype)) - continue; - if (ce->ce_flags & CE_UPDATE) - continue; - if (ce_skip_worktree(ce)) - 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(const char *prefix) -{ - int pos = cache_name_pos(prefix, prefix_len); - unsigned int first, last; - - if (pos < 0) - pos = -pos-1; - memmove(active_cache, active_cache + pos, - (active_nr - pos) * sizeof(struct cache_entry *)); - 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 const char *verify_pathspec(const char *prefix) -{ - const char **p, *n, *prev; - 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 '..'"); - - prefix_len = max; - return max ? xmemdupz(prev, max) : NULL; -} - -static void strip_trailing_slash_from_submodules(void) -{ - const char **p; - - for (p = pathspec; *p != NULL; p++) { - int len = strlen(*p), pos; - - if (len < 1 || (*p)[len - 1] != '/') - continue; - pos = cache_name_pos(*p, len - 1); - if (pos >= 0 && S_ISGITLINK(active_cache[pos]->ce_mode)) - *p = xstrndup(*p, len - 1); - } -} - -/* - * Read the tree specified with --with-tree option - * (typically, HEAD) into stage #1 and then - * squash them down to stage #0. This is used for - * --error-unmatch to list and check the path patterns - * that were given from the command line. We are not - * going to write this index out. - */ -void overlay_tree_on_cache(const char *tree_name, const char *prefix) -{ - struct tree *tree; - unsigned char sha1[20]; - const char **match; - struct cache_entry *last_stage0 = NULL; - int i; - - if (get_sha1(tree_name, sha1)) - die("tree-ish %s not found.", tree_name); - tree = parse_tree_indirect(sha1); - if (!tree) - die("bad tree-ish %s", tree_name); - - /* Hoist the unmerged entries up to stage #3 to make room */ - for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; - if (!ce_stage(ce)) - continue; - ce->ce_flags |= CE_STAGEMASK; - } - - if (prefix) { - static const char *(matchbuf[2]); - matchbuf[0] = prefix; - matchbuf[1] = NULL; - match = matchbuf; - } else - match = NULL; - if (read_tree(tree, 1, match)) - die("unable to read tree entries %s", tree_name); - - for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; - switch (ce_stage(ce)) { - case 0: - last_stage0 = ce; - /* fallthru */ - default: - continue; - case 1: - /* - * If there is stage #0 entry for this, we do not - * need to show it. We use CE_UPDATE bit to mark - * such an entry. - */ - if (last_stage0 && - !strcmp(last_stage0->name, ce->name)) - ce->ce_flags |= CE_UPDATE; - } - } -} - -int report_path_error(const char *ps_matched, const char **pathspec, int prefix_offset) -{ - /* - * Make sure all pathspec matched; otherwise it is an error. - */ - int num, errors = 0; - for (num = 0; pathspec[num]; num++) { - int other, found_dup; - - if (ps_matched[num]) - continue; - /* - * The caller might have fed identical pathspec - * twice. Do not barf on such a mistake. - */ - for (found_dup = other = 0; - !found_dup && pathspec[other]; - other++) { - if (other == num || !ps_matched[other]) - continue; - if (!strcmp(pathspec[other], pathspec[num])) - /* - * Ok, we have a match already. - */ - found_dup = 1; - } - if (found_dup) - continue; - - error("pathspec '%s' did not match any file(s) known to git.", - pathspec[num] + prefix_offset); - errors++; - } - return errors; -} - -static const char * const ls_files_usage[] = { - "git ls-files [options] []*", - NULL -}; - -static int option_parse_z(const struct option *opt, - const char *arg, int unset) -{ - line_terminator = unset ? '\n' : '\0'; - - return 0; -} - -static int option_parse_exclude(const struct option *opt, - const char *arg, int unset) -{ - struct exclude_list *list = opt->value; - - exc_given = 1; - add_exclude(arg, "", 0, list); - - return 0; -} - -static int option_parse_exclude_from(const struct option *opt, - const char *arg, int unset) -{ - struct dir_struct *dir = opt->value; - - exc_given = 1; - add_excludes_from_file(dir, arg); - - return 0; -} - -static int option_parse_exclude_standard(const struct option *opt, - const char *arg, int unset) -{ - struct dir_struct *dir = opt->value; - - exc_given = 1; - setup_standard_excludes(dir); - - return 0; -} - -int cmd_ls_files(int argc, const char **argv, const char *prefix) -{ - int require_work_tree = 0, show_tag = 0; - struct dir_struct dir; - struct option builtin_ls_files_options[] = { - { OPTION_CALLBACK, 'z', NULL, NULL, NULL, - "paths are separated with NUL character", - PARSE_OPT_NOARG, option_parse_z }, - OPT_BOOLEAN('t', NULL, &show_tag, - "identify the file status with tags"), - OPT_BOOLEAN('v', NULL, &show_valid_bit, - "use lowercase letters for 'assume unchanged' files"), - OPT_BOOLEAN('c', "cached", &show_cached, - "show cached files in the output (default)"), - OPT_BOOLEAN('d', "deleted", &show_deleted, - "show deleted files in the output"), - OPT_BOOLEAN('m', "modified", &show_modified, - "show modified files in the output"), - OPT_BOOLEAN('o', "others", &show_others, - "show other files in the output"), - OPT_BIT('i', "ignored", &dir.flags, - "show ignored files in the output", - DIR_SHOW_IGNORED), - OPT_BOOLEAN('s', "stage", &show_stage, - "show staged contents' object name in the output"), - OPT_BOOLEAN('k', "killed", &show_killed, - "show files on the filesystem that need to be removed"), - OPT_BIT(0, "directory", &dir.flags, - "show 'other' directories' name only", - DIR_SHOW_OTHER_DIRECTORIES), - OPT_NEGBIT(0, "empty-directory", &dir.flags, - "don't show empty directories", - DIR_HIDE_EMPTY_DIRECTORIES), - OPT_BOOLEAN('u', "unmerged", &show_unmerged, - "show unmerged files in the output"), - OPT_BOOLEAN(0, "resolve-undo", &show_resolve_undo, - "show resolve-undo information"), - { OPTION_CALLBACK, 'x', "exclude", &dir.exclude_list[EXC_CMDL], "pattern", - "skip files matching pattern", - 0, option_parse_exclude }, - { OPTION_CALLBACK, 'X', "exclude-from", &dir, "file", - "exclude patterns are read from ", - 0, option_parse_exclude_from }, - OPT_STRING(0, "exclude-per-directory", &dir.exclude_per_dir, "file", - "read additional per-directory exclude patterns in "), - { OPTION_CALLBACK, 0, "exclude-standard", &dir, NULL, - "add the standard git exclusions", - PARSE_OPT_NOARG, option_parse_exclude_standard }, - { OPTION_SET_INT, 0, "full-name", &prefix_offset, NULL, - "make the output relative to the project top directory", - PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL }, - OPT_BOOLEAN(0, "error-unmatch", &error_unmatch, - "if any is not in the index, treat this as an error"), - OPT_STRING(0, "with-tree", &with_tree, "tree-ish", - "pretend that paths removed since are still present"), - OPT__ABBREV(&abbrev), - OPT_END() - }; - - memset(&dir, 0, sizeof(dir)); - if (prefix) - prefix_offset = strlen(prefix); - git_config(git_default_config, NULL); - - if (read_cache() < 0) - die("index file corrupt"); - - argc = parse_options(argc, argv, prefix, builtin_ls_files_options, - ls_files_usage, 0); - if (show_tag || show_valid_bit) { - tag_cached = "H "; - tag_unmerged = "M "; - tag_removed = "R "; - tag_modified = "C "; - tag_other = "? "; - tag_killed = "K "; - tag_skip_worktree = "S "; - tag_resolve_undo = "U "; - } - if (show_modified || show_others || show_deleted || (dir.flags & DIR_SHOW_IGNORED) || show_killed) - require_work_tree = 1; - if (show_unmerged) - /* - * There's no point in showing unmerged unless - * you also show the stage information. - */ - show_stage = 1; - if (dir.exclude_per_dir) - exc_given = 1; - - if (require_work_tree && !is_inside_work_tree()) - setup_work_tree(); - - pathspec = get_pathspec(prefix, argv); - - /* be nice with submodule paths ending in a slash */ - if (pathspec) - strip_trailing_slash_from_submodules(); - - /* Verify that the pathspec matches the prefix */ - if (pathspec) - prefix = verify_pathspec(prefix); - - /* Treat unmatching pathspec elements as errors */ - if (pathspec && error_unmatch) { - int num; - for (num = 0; pathspec[num]; num++) - ; - ps_matched = xcalloc(1, num); - } - - if ((dir.flags & DIR_SHOW_IGNORED) && !exc_given) - die("ls-files --ignored needs some exclude pattern"); - - /* With no flags, we default to showing the cached files */ - if (!(show_stage | show_deleted | show_others | show_unmerged | - show_killed | show_modified | show_resolve_undo)) - show_cached = 1; - - if (prefix) - prune_cache(prefix); - if (with_tree) { - /* - * Basic sanity check; show-stages and show-unmerged - * would not make any sense with this option. - */ - if (show_stage || show_unmerged) - die("ls-files --with-tree is incompatible with -s or -u"); - overlay_tree_on_cache(with_tree, prefix); - } - show_files(&dir, prefix); - if (show_resolve_undo) - show_ru_info(prefix); - - if (ps_matched) { - int bad; - bad = report_path_error(ps_matched, pathspec, prefix_offset); - if (bad) - fprintf(stderr, "Did you forget to 'git add'?\n"); - - return bad ? 1 : 0; - } - - return 0; -} diff --git a/builtin-ls-remote.c b/builtin-ls-remote.c deleted file mode 100644 index 70f5622..0000000 --- a/builtin-ls-remote.c +++ /dev/null @@ -1,107 +0,0 @@ -#include "builtin.h" -#include "cache.h" -#include "transport.h" -#include "remote.h" - -static const char ls_remote_usage[] = -"git ls-remote [--heads] [--tags] [-u | --upload-pack ] ..."; - -/* - * Is there one among the list of patterns that match the tail part - * of the path? - */ -static int tail_match(const char **pattern, const char *path) -{ - const char *p; - char pathbuf[PATH_MAX]; - - if (!pattern) - return 1; /* no restriction */ - - if (snprintf(pathbuf, sizeof(pathbuf), "/%s", path) > sizeof(pathbuf)) - return error("insanely long ref %.*s...", 20, path); - while ((p = *(pattern++)) != NULL) { - if (!fnmatch(p, pathbuf, 0)) - return 1; - } - return 0; -} - -int cmd_ls_remote(int argc, const char **argv, const char *prefix) -{ - int i; - const char *dest = NULL; - int nongit; - unsigned flags = 0; - const char *uploadpack = NULL; - const char **pattern = NULL; - - struct remote *remote; - struct transport *transport; - const struct ref *ref; - - setup_git_directory_gently(&nongit); - - for (i = 1; i < argc; i++) { - const char *arg = argv[i]; - - if (*arg == '-') { - if (!prefixcmp(arg, "--upload-pack=")) { - uploadpack = arg + 14; - continue; - } - if (!prefixcmp(arg, "--exec=")) { - uploadpack = arg + 7; - continue; - } - if (!strcmp("--tags", arg) || !strcmp("-t", arg)) { - flags |= REF_TAGS; - continue; - } - if (!strcmp("--heads", arg) || !strcmp("-h", arg)) { - flags |= REF_HEADS; - continue; - } - if (!strcmp("--refs", arg)) { - flags |= REF_NORMAL; - continue; - } - usage(ls_remote_usage); - } - dest = arg; - i++; - break; - } - - if (!dest) - usage(ls_remote_usage); - - if (argv[i]) { - int j; - pattern = xcalloc(sizeof(const char *), argc - i + 1); - for (j = i; j < argc; j++) { - int len = strlen(argv[j]); - char *p = xmalloc(len + 3); - sprintf(p, "*/%s", argv[j]); - pattern[j - i] = p; - } - } - remote = remote_get(dest); - if (!remote->url_nr) - die("remote %s has no configured URL", dest); - transport = transport_get(remote, NULL); - if (uploadpack != NULL) - transport_set_option(transport, TRANS_OPT_UPLOADPACK, uploadpack); - - ref = transport_get_remote_refs(transport); - if (transport_disconnect(transport)) - return 1; - for ( ; ref; ref = ref->next) { - if (!check_ref_type(ref, flags)) - continue; - if (!tail_match(pattern, ref->name)) - continue; - printf("%s %s\n", sha1_to_hex(ref->old_sha1), ref->name); - } - return 0; -} diff --git a/builtin-ls-tree.c b/builtin-ls-tree.c deleted file mode 100644 index 4484185..0000000 --- a/builtin-ls-tree.c +++ /dev/null @@ -1,176 +0,0 @@ -/* - * GIT - The information manager from hell - * - * Copyright (C) Linus Torvalds, 2005 - */ -#include "cache.h" -#include "blob.h" -#include "tree.h" -#include "commit.h" -#include "quote.h" -#include "builtin.h" -#include "parse-options.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 -#define LS_SHOW_SIZE 16 -static int abbrev; -static int ls_options; -static const char **pathspec; -static int chomp_prefix; -static const char *ls_tree_prefix; - -static const char * const ls_tree_usage[] = { - "git ls-tree [] [path...]", - NULL -}; - -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(const unsigned char *sha1, const char *base, int baselen, - const char *pathname, unsigned mode, int stage, void *context) -{ - int retval = 0; - const char *type = blob_type; - - if (S_ISGITLINK(mode)) { - /* - * Maybe we want to have some recursive version here? - * - * Something similar to this incomplete example: - * - if (show_subprojects(base, baselen, pathname)) - retval = READ_TREE_RECURSIVE; - * - */ - type = commit_type; - } else 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(ls_tree_prefix, base, chomp_prefix))) - return 0; - - if (!(ls_options & LS_NAME_ONLY)) { - if (ls_options & LS_SHOW_SIZE) { - char size_text[24]; - if (!strcmp(type, blob_type)) { - unsigned long size; - if (sha1_object_info(sha1, &size) == OBJ_BAD) - strcpy(size_text, "BAD"); - else - snprintf(size_text, sizeof(size_text), - "%lu", size); - } else - strcpy(size_text, "-"); - printf("%06o %s %s %7s\t", mode, type, - abbrev ? find_unique_abbrev(sha1, abbrev) - : sha1_to_hex(sha1), - size_text); - } else - printf("%06o %s %s\t", mode, type, - abbrev ? find_unique_abbrev(sha1, abbrev) - : sha1_to_hex(sha1)); - } - write_name_quotedpfx(base + chomp_prefix, baselen - chomp_prefix, - pathname, stdout, line_termination); - return retval; -} - -int cmd_ls_tree(int argc, const char **argv, const char *prefix) -{ - unsigned char sha1[20]; - struct tree *tree; - int full_tree = 0; - const struct option ls_tree_options[] = { - OPT_BIT('d', NULL, &ls_options, "only show trees", - LS_TREE_ONLY), - OPT_BIT('r', NULL, &ls_options, "recurse into subtrees", - LS_RECURSIVE), - OPT_BIT('t', NULL, &ls_options, "show trees when recursing", - LS_SHOW_TREES), - OPT_SET_INT('z', NULL, &line_termination, - "terminate entries with NUL byte", 0), - OPT_BIT('l', "long", &ls_options, "include object size", - LS_SHOW_SIZE), - OPT_BIT(0, "name-only", &ls_options, "list only filenames", - LS_NAME_ONLY), - OPT_BIT(0, "name-status", &ls_options, "list only filenames", - LS_NAME_ONLY), - OPT_SET_INT(0, "full-name", &chomp_prefix, - "use full path names", 0), - OPT_BOOLEAN(0, "full-tree", &full_tree, - "list entire tree; not just current directory " - "(implies --full-name)"), - OPT__ABBREV(&abbrev), - OPT_END() - }; - - git_config(git_default_config, NULL); - ls_tree_prefix = prefix; - if (prefix && *prefix) - chomp_prefix = strlen(prefix); - - argc = parse_options(argc, argv, prefix, ls_tree_options, - ls_tree_usage, 0); - if (full_tree) { - ls_tree_prefix = prefix = NULL; - chomp_prefix = 0; - } - /* -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 < 1) - usage_with_options(ls_tree_usage, ls_tree_options); - if (get_sha1(argv[0], sha1)) - die("Not a valid object name %s", argv[0]); - - pathspec = get_pathspec(prefix, argv + 1); - tree = parse_tree_indirect(sha1); - if (!tree) - die("not a tree object"); - read_tree_recursive(tree, "", 0, 0, pathspec, show_tree, NULL); - - return 0; -} diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c deleted file mode 100644 index a50ac22..0000000 --- a/builtin-mailinfo.c +++ /dev/null @@ -1,1064 +0,0 @@ -/* - * Another stupid program, this one parsing the headers of an - * email to figure out authorship and subject - */ -#include "cache.h" -#include "builtin.h" -#include "utf8.h" -#include "strbuf.h" - -static FILE *cmitmsg, *patchfile, *fin, *fout; - -static int keep_subject; -static int keep_non_patch_brackets_in_subject; -static const char *metainfo_charset; -static struct strbuf line = STRBUF_INIT; -static struct strbuf name = STRBUF_INIT; -static struct strbuf email = STRBUF_INIT; - -static enum { - TE_DONTCARE, TE_QP, TE_BASE64, -} transfer_encoding; -static enum { - TYPE_TEXT, TYPE_OTHER, -} message_type; - -static struct strbuf charset = STRBUF_INIT; -static int patch_lines; -static struct strbuf **p_hdr_data, **s_hdr_data; -static int use_scissors; -static int use_inbody_headers = 1; - -#define MAX_HDR_PARSED 10 -#define MAX_BOUNDARIES 5 - -static void cleanup_space(struct strbuf *sb); - - -static void get_sane_name(struct strbuf *out, struct strbuf *name, struct strbuf *email) -{ - struct strbuf *src = name; - if (name->len < 3 || 60 < name->len || strchr(name->buf, '@') || - strchr(name->buf, '<') || strchr(name->buf, '>')) - src = email; - else if (name == out) - return; - strbuf_reset(out); - strbuf_addbuf(out, src); -} - -static void parse_bogus_from(const struct strbuf *line) -{ - /* John Doe */ - - char *bra, *ket; - /* This is fallback, so do not bother if we already have an - * e-mail address. - */ - if (email.len) - return; - - bra = strchr(line->buf, '<'); - if (!bra) - return; - ket = strchr(bra, '>'); - if (!ket) - return; - - strbuf_reset(&email); - strbuf_add(&email, bra + 1, ket - bra - 1); - - strbuf_reset(&name); - strbuf_add(&name, line->buf, bra - line->buf); - strbuf_trim(&name); - get_sane_name(&name, &name, &email); -} - -static void handle_from(const struct strbuf *from) -{ - char *at; - size_t el; - struct strbuf f; - - strbuf_init(&f, from->len); - strbuf_addbuf(&f, from); - - at = strchr(f.buf, '@'); - if (!at) { - parse_bogus_from(from); - return; - } - - /* - * If we already have one email, don't take any confusing lines - */ - if (email.len && strchr(at + 1, '@')) { - strbuf_release(&f); - return; - } - - /* Pick up the string around '@', possibly delimited with <> - * pair; that is the email part. - */ - while (at > f.buf) { - char c = at[-1]; - if (isspace(c)) - break; - if (c == '<') { - at[-1] = ' '; - break; - } - at--; - } - el = strcspn(at, " \n\t\r\v\f>"); - strbuf_reset(&email); - strbuf_add(&email, at, el); - strbuf_remove(&f, at - f.buf, el + (at[el] ? 1 : 0)); - - /* The remainder is name. It could be - * - * - "John Doe " (a), or - * - "john.doe@xz (John Doe)" (b), or - * - "John (zzz) Doe (Comment)" (c) - * - * but we have removed the email part, so - * - * - remove extra spaces which could stay after email (case 'c'), and - * - trim from both ends, possibly removing the () pair at the end - * (cases 'a' and 'b'). - */ - cleanup_space(&f); - strbuf_trim(&f); - if (f.buf[0] == '(' && f.len && f.buf[f.len - 1] == ')') { - strbuf_remove(&f, 0, 1); - strbuf_setlen(&f, f.len - 1); - } - - get_sane_name(&name, &f, &email); - strbuf_release(&f); -} - -static void handle_header(struct strbuf **out, const struct strbuf *line) -{ - if (!*out) { - *out = xmalloc(sizeof(struct strbuf)); - strbuf_init(*out, line->len); - } else - strbuf_reset(*out); - - strbuf_addbuf(*out, line); -} - -/* NOTE NOTE NOTE. We do not claim we do full MIME. We just attempt - * to have enough heuristics to grok MIME encoded patches often found - * on our mailing lists. For example, we do not even treat header lines - * case insensitively. - */ - -static int slurp_attr(const char *line, const char *name, struct strbuf *attr) -{ - const char *ends, *ap = strcasestr(line, name); - size_t sz; - - if (!ap) { - strbuf_setlen(attr, 0); - return 0; - } - ap += strlen(name); - if (*ap == '"') { - ap++; - ends = "\""; - } - else - ends = "; \t"; - sz = strcspn(ap, ends); - strbuf_add(attr, ap, sz); - return 1; -} - -static struct strbuf *content[MAX_BOUNDARIES]; - -static struct strbuf **content_top = content; - -static void handle_content_type(struct strbuf *line) -{ - struct strbuf *boundary = xmalloc(sizeof(struct strbuf)); - strbuf_init(boundary, line->len); - - if (!strcasestr(line->buf, "text/")) - message_type = TYPE_OTHER; - if (slurp_attr(line->buf, "boundary=", boundary)) { - strbuf_insert(boundary, 0, "--", 2); - if (++content_top > &content[MAX_BOUNDARIES]) { - fprintf(stderr, "Too many boundaries to handle\n"); - exit(1); - } - *content_top = boundary; - boundary = NULL; - } - slurp_attr(line->buf, "charset=", &charset); - - if (boundary) { - strbuf_release(boundary); - free(boundary); - } -} - -static void handle_content_transfer_encoding(const struct strbuf *line) -{ - if (strcasestr(line->buf, "base64")) - transfer_encoding = TE_BASE64; - else if (strcasestr(line->buf, "quoted-printable")) - transfer_encoding = TE_QP; - else - transfer_encoding = TE_DONTCARE; -} - -static int is_multipart_boundary(const struct strbuf *line) -{ - return (((*content_top)->len <= line->len) && - !memcmp(line->buf, (*content_top)->buf, (*content_top)->len)); -} - -static void cleanup_subject(struct strbuf *subject) -{ - size_t at = 0; - - while (at < subject->len) { - char *pos; - size_t remove; - - switch (subject->buf[at]) { - case 'r': case 'R': - if (subject->len <= at + 3) - break; - if (!memcmp(subject->buf + at + 1, "e:", 2)) { - strbuf_remove(subject, at, 3); - continue; - } - at++; - break; - case ' ': case '\t': case ':': - strbuf_remove(subject, at, 1); - continue; - case '[': - pos = strchr(subject->buf + at, ']'); - if (!pos) - break; - remove = pos - subject->buf + at + 1; - if (!keep_non_patch_brackets_in_subject || - (7 <= remove && - memmem(subject->buf + at, remove, "PATCH", 5))) - strbuf_remove(subject, at, remove); - else - at += remove; - continue; - } - break; - } - strbuf_trim(subject); -} - -static void cleanup_space(struct strbuf *sb) -{ - size_t pos, cnt; - for (pos = 0; pos < sb->len; pos++) { - if (isspace(sb->buf[pos])) { - sb->buf[pos] = ' '; - for (cnt = 0; isspace(sb->buf[pos + cnt + 1]); cnt++); - strbuf_remove(sb, pos + 1, cnt); - } - } -} - -static void decode_header(struct strbuf *line); -static const char *header[MAX_HDR_PARSED] = { - "From","Subject","Date", -}; - -static inline int cmp_header(const struct strbuf *line, const char *hdr) -{ - int len = strlen(hdr); - return !strncasecmp(line->buf, hdr, len) && line->len > len && - line->buf[len] == ':' && isspace(line->buf[len + 1]); -} - -static int check_header(const struct strbuf *line, - struct strbuf *hdr_data[], int overwrite) -{ - int i, ret = 0, len; - struct strbuf sb = STRBUF_INIT; - /* search for the interesting parts */ - for (i = 0; header[i]; i++) { - int len = strlen(header[i]); - if ((!hdr_data[i] || overwrite) && cmp_header(line, header[i])) { - /* Unwrap inline B and Q encoding, and optionally - * normalize the meta information to utf8. - */ - strbuf_add(&sb, line->buf + len + 2, line->len - len - 2); - decode_header(&sb); - handle_header(&hdr_data[i], &sb); - ret = 1; - goto check_header_out; - } - } - - /* Content stuff */ - if (cmp_header(line, "Content-Type")) { - len = strlen("Content-Type: "); - strbuf_add(&sb, line->buf + len, line->len - len); - decode_header(&sb); - strbuf_insert(&sb, 0, "Content-Type: ", len); - handle_content_type(&sb); - ret = 1; - goto check_header_out; - } - if (cmp_header(line, "Content-Transfer-Encoding")) { - len = strlen("Content-Transfer-Encoding: "); - strbuf_add(&sb, line->buf + len, line->len - len); - decode_header(&sb); - handle_content_transfer_encoding(&sb); - ret = 1; - goto check_header_out; - } - - /* for inbody stuff */ - if (!prefixcmp(line->buf, ">From") && isspace(line->buf[5])) { - ret = 1; /* Should this return 0? */ - goto check_header_out; - } - if (!prefixcmp(line->buf, "[PATCH]") && isspace(line->buf[7])) { - for (i = 0; header[i]; i++) { - if (!memcmp("Subject", header[i], 7)) { - handle_header(&hdr_data[i], line); - ret = 1; - goto check_header_out; - } - } - } - -check_header_out: - strbuf_release(&sb); - return ret; -} - -static int is_rfc2822_header(const struct strbuf *line) -{ - /* - * The section that defines the loosest possible - * field name is "3.6.8 Optional fields". - * - * optional-field = field-name ":" unstructured CRLF - * field-name = 1*ftext - * ftext = %d33-57 / %59-126 - */ - int ch; - char *cp = line->buf; - - /* Count mbox From headers as headers */ - if (!prefixcmp(cp, "From ") || !prefixcmp(cp, ">From ")) - return 1; - - while ((ch = *cp++)) { - if (ch == ':') - return 1; - if ((33 <= ch && ch <= 57) || - (59 <= ch && ch <= 126)) - continue; - break; - } - return 0; -} - -static int read_one_header_line(struct strbuf *line, FILE *in) -{ - /* Get the first part of the line. */ - if (strbuf_getline(line, in, '\n')) - return 0; - - /* - * Is it an empty line or not a valid rfc2822 header? - * If so, stop here, and return false ("not a header") - */ - strbuf_rtrim(line); - if (!line->len || !is_rfc2822_header(line)) { - /* Re-add the newline */ - strbuf_addch(line, '\n'); - return 0; - } - - /* - * Now we need to eat all the continuation lines.. - * Yuck, 2822 header "folding" - */ - for (;;) { - int peek; - struct strbuf continuation = STRBUF_INIT; - - peek = fgetc(in); ungetc(peek, in); - if (peek != ' ' && peek != '\t') - break; - if (strbuf_getline(&continuation, in, '\n')) - break; - continuation.buf[0] = '\n'; - strbuf_rtrim(&continuation); - strbuf_addbuf(line, &continuation); - } - - return 1; -} - -static struct strbuf *decode_q_segment(const struct strbuf *q_seg, int rfc2047) -{ - const char *in = q_seg->buf; - int c; - struct strbuf *out = xmalloc(sizeof(struct strbuf)); - strbuf_init(out, q_seg->len); - - while ((c = *in++) != 0) { - if (c == '=') { - int d = *in++; - if (d == '\n' || !d) - break; /* drop trailing newline */ - strbuf_addch(out, (hexval(d) << 4) | hexval(*in++)); - continue; - } - if (rfc2047 && c == '_') /* rfc2047 4.2 (2) */ - c = 0x20; - strbuf_addch(out, c); - } - return out; -} - -static struct strbuf *decode_b_segment(const struct strbuf *b_seg) -{ - /* Decode in..ep, possibly in-place to ot */ - int c, pos = 0, acc = 0; - const char *in = b_seg->buf; - struct strbuf *out = xmalloc(sizeof(struct strbuf)); - strbuf_init(out, b_seg->len); - - while ((c = *in++) != 0) { - if (c == '+') - c = 62; - else if (c == '/') - c = 63; - else if ('A' <= c && c <= 'Z') - c -= 'A'; - else if ('a' <= c && c <= 'z') - c -= 'a' - 26; - else if ('0' <= c && c <= '9') - c -= '0' - 52; - else - continue; /* garbage */ - switch (pos++) { - case 0: - acc = (c << 2); - break; - case 1: - strbuf_addch(out, (acc | (c >> 4))); - acc = (c & 15) << 4; - break; - case 2: - strbuf_addch(out, (acc | (c >> 2))); - acc = (c & 3) << 6; - break; - case 3: - strbuf_addch(out, (acc | c)); - acc = pos = 0; - break; - } - } - return out; -} - -/* - * When there is no known charset, guess. - * - * Right now we assume that if the target is UTF-8 (the default), - * and it already looks like UTF-8 (which includes US-ASCII as its - * subset, of course) then that is what it is and there is nothing - * to do. - * - * Otherwise, we default to assuming it is Latin1 for historical - * reasons. - */ -static const char *guess_charset(const struct strbuf *line, const char *target_charset) -{ - if (is_encoding_utf8(target_charset)) { - if (is_utf8(line->buf)) - return NULL; - } - return "ISO8859-1"; -} - -static void convert_to_utf8(struct strbuf *line, const char *charset) -{ - char *out; - - if (!charset || !*charset) { - charset = guess_charset(line, metainfo_charset); - if (!charset) - return; - } - - if (!strcasecmp(metainfo_charset, charset)) - return; - out = reencode_string(line->buf, metainfo_charset, charset); - if (!out) - die("cannot convert from %s to %s", - charset, metainfo_charset); - strbuf_attach(line, out, strlen(out), strlen(out)); -} - -static int decode_header_bq(struct strbuf *it) -{ - char *in, *ep, *cp; - struct strbuf outbuf = STRBUF_INIT, *dec; - struct strbuf charset_q = STRBUF_INIT, piecebuf = STRBUF_INIT; - int rfc2047 = 0; - - in = it->buf; - while (in - it->buf <= it->len && (ep = strstr(in, "=?")) != NULL) { - int encoding; - strbuf_reset(&charset_q); - strbuf_reset(&piecebuf); - rfc2047 = 1; - - if (in != ep) { - /* - * We are about to process an encoded-word - * that begins at ep, but there is something - * before the encoded word. - */ - char *scan; - for (scan = in; scan < ep; scan++) - if (!isspace(*scan)) - break; - - if (scan != ep || in == it->buf) { - /* - * We should not lose that "something", - * unless we have just processed an - * encoded-word, and there is only LWS - * before the one we are about to process. - */ - strbuf_add(&outbuf, in, ep - in); - } - } - /* E.g. - * ep : "=?iso-2022-jp?B?GyR...?= foo" - * ep : "=?ISO-8859-1?Q?Foo=FCbar?= baz" - */ - ep += 2; - - if (ep - it->buf >= it->len || !(cp = strchr(ep, '?'))) - goto decode_header_bq_out; - - if (cp + 3 - it->buf > it->len) - goto decode_header_bq_out; - strbuf_add(&charset_q, ep, cp - ep); - - encoding = cp[1]; - if (!encoding || cp[2] != '?') - goto decode_header_bq_out; - ep = strstr(cp + 3, "?="); - if (!ep) - goto decode_header_bq_out; - strbuf_add(&piecebuf, cp + 3, ep - cp - 3); - switch (tolower(encoding)) { - default: - goto decode_header_bq_out; - case 'b': - dec = decode_b_segment(&piecebuf); - break; - case 'q': - dec = decode_q_segment(&piecebuf, 1); - break; - } - if (metainfo_charset) - convert_to_utf8(dec, charset_q.buf); - - strbuf_addbuf(&outbuf, dec); - strbuf_release(dec); - free(dec); - in = ep + 2; - } - strbuf_addstr(&outbuf, in); - strbuf_reset(it); - strbuf_addbuf(it, &outbuf); -decode_header_bq_out: - strbuf_release(&outbuf); - strbuf_release(&charset_q); - strbuf_release(&piecebuf); - return rfc2047; -} - -static void decode_header(struct strbuf *it) -{ - if (decode_header_bq(it)) - return; - /* otherwise "it" is a straight copy of the input. - * This can be binary guck but there is no charset specified. - */ - if (metainfo_charset) - convert_to_utf8(it, ""); -} - -static void decode_transfer_encoding(struct strbuf *line) -{ - struct strbuf *ret; - - switch (transfer_encoding) { - case TE_QP: - ret = decode_q_segment(line, 0); - break; - case TE_BASE64: - ret = decode_b_segment(line); - break; - case TE_DONTCARE: - default: - return; - } - strbuf_reset(line); - strbuf_addbuf(line, ret); - strbuf_release(ret); - free(ret); -} - -static void handle_filter(struct strbuf *line); - -static int find_boundary(void) -{ - while (!strbuf_getline(&line, fin, '\n')) { - if (*content_top && is_multipart_boundary(&line)) - return 1; - } - return 0; -} - -static int handle_boundary(void) -{ - struct strbuf newline = STRBUF_INIT; - - strbuf_addch(&newline, '\n'); -again: - if (line.len >= (*content_top)->len + 2 && - !memcmp(line.buf + (*content_top)->len, "--", 2)) { - /* we hit an end boundary */ - /* pop the current boundary off the stack */ - strbuf_release(*content_top); - free(*content_top); - *content_top = NULL; - - /* technically won't happen as is_multipart_boundary() - will fail first. But just in case.. - */ - if (--content_top < content) { - fprintf(stderr, "Detected mismatched boundaries, " - "can't recover\n"); - exit(1); - } - handle_filter(&newline); - strbuf_release(&newline); - - /* skip to the next boundary */ - if (!find_boundary()) - return 0; - goto again; - } - - /* set some defaults */ - transfer_encoding = TE_DONTCARE; - strbuf_reset(&charset); - message_type = TYPE_TEXT; - - /* slurp in this section's info */ - while (read_one_header_line(&line, fin)) - check_header(&line, p_hdr_data, 0); - - strbuf_release(&newline); - /* replenish line */ - if (strbuf_getline(&line, fin, '\n')) - return 0; - strbuf_addch(&line, '\n'); - return 1; -} - -static inline int patchbreak(const struct strbuf *line) -{ - size_t i; - - /* Beginning of a "diff -" header? */ - if (!prefixcmp(line->buf, "diff -")) - return 1; - - /* CVS "Index: " line? */ - if (!prefixcmp(line->buf, "Index: ")) - return 1; - - /* - * "--- " starts patches without headers - * "---*" is a manual separator - */ - if (line->len < 4) - return 0; - - if (!prefixcmp(line->buf, "---")) { - /* space followed by a filename? */ - if (line->buf[3] == ' ' && !isspace(line->buf[4])) - return 1; - /* Just whitespace? */ - for (i = 3; i < line->len; i++) { - unsigned char c = line->buf[i]; - if (c == '\n') - return 1; - if (!isspace(c)) - break; - } - return 0; - } - return 0; -} - -static int is_scissors_line(const struct strbuf *line) -{ - size_t i, len = line->len; - int scissors = 0, gap = 0; - int first_nonblank = -1; - int last_nonblank = 0, visible, perforation = 0, in_perforation = 0; - const char *buf = line->buf; - - for (i = 0; i < len; i++) { - if (isspace(buf[i])) { - if (in_perforation) { - perforation++; - gap++; - } - continue; - } - last_nonblank = i; - if (first_nonblank < 0) - first_nonblank = i; - if (buf[i] == '-') { - in_perforation = 1; - perforation++; - continue; - } - if (i + 1 < len && - (!memcmp(buf + i, ">8", 2) || !memcmp(buf + i, "8<", 2))) { - in_perforation = 1; - perforation += 2; - scissors += 2; - i++; - continue; - } - in_perforation = 0; - } - - /* - * The mark must be at least 8 bytes long (e.g. "-- >8 --"). - * Even though there can be arbitrary cruft on the same line - * (e.g. "cut here"), in order to avoid misidentification, the - * perforation must occupy more than a third of the visible - * width of the line, and dashes and scissors must occupy more - * than half of the perforation. - */ - - visible = last_nonblank - first_nonblank + 1; - return (scissors && 8 <= visible && - visible < perforation * 3 && - gap * 2 < perforation); -} - -static int handle_commit_msg(struct strbuf *line) -{ - static int still_looking = 1; - - if (!cmitmsg) - return 0; - - if (still_looking) { - strbuf_ltrim(line); - if (!line->len) - return 0; - } - - if (use_inbody_headers && still_looking) { - still_looking = check_header(line, s_hdr_data, 0); - if (still_looking) - return 0; - } else - /* Only trim the first (blank) line of the commit message - * when ignoring in-body headers. - */ - still_looking = 0; - - /* normalize the log message to UTF-8. */ - if (metainfo_charset) - convert_to_utf8(line, charset.buf); - - if (use_scissors && is_scissors_line(line)) { - int i; - if (fseek(cmitmsg, 0L, SEEK_SET)) - die_errno("Could not rewind output message file"); - if (ftruncate(fileno(cmitmsg), 0)) - die_errno("Could not truncate output message file at scissors"); - still_looking = 1; - - /* - * We may have already read "secondary headers"; purge - * them to give ourselves a clean restart. - */ - for (i = 0; header[i]; i++) { - if (s_hdr_data[i]) - strbuf_release(s_hdr_data[i]); - s_hdr_data[i] = NULL; - } - return 0; - } - - if (patchbreak(line)) { - fclose(cmitmsg); - cmitmsg = NULL; - return 1; - } - - fputs(line->buf, cmitmsg); - return 0; -} - -static void handle_patch(const struct strbuf *line) -{ - fwrite(line->buf, 1, line->len, patchfile); - patch_lines++; -} - -static void handle_filter(struct strbuf *line) -{ - static int filter = 0; - - /* filter tells us which part we left off on */ - switch (filter) { - case 0: - if (!handle_commit_msg(line)) - break; - filter++; - case 1: - handle_patch(line); - break; - } -} - -static void handle_body(void) -{ - struct strbuf prev = STRBUF_INIT; - - /* Skip up to the first boundary */ - if (*content_top) { - if (!find_boundary()) - goto handle_body_out; - } - - do { - /* process any boundary lines */ - if (*content_top && is_multipart_boundary(&line)) { - /* flush any leftover */ - if (prev.len) { - handle_filter(&prev); - strbuf_reset(&prev); - } - if (!handle_boundary()) - goto handle_body_out; - } - - /* Unwrap transfer encoding */ - decode_transfer_encoding(&line); - - switch (transfer_encoding) { - case TE_BASE64: - case TE_QP: - { - struct strbuf **lines, **it, *sb; - - /* Prepend any previous partial lines */ - strbuf_insert(&line, 0, prev.buf, prev.len); - strbuf_reset(&prev); - - /* binary data most likely doesn't have newlines */ - if (message_type != TYPE_TEXT) { - handle_filter(&line); - break; - } - /* - * This is a decoded line that may contain - * multiple new lines. Pass only one chunk - * at a time to handle_filter() - */ - lines = strbuf_split(&line, '\n'); - for (it = lines; (sb = *it); it++) { - if (*(it + 1) == NULL) /* The last line */ - if (sb->buf[sb->len - 1] != '\n') { - /* Partial line, save it for later. */ - strbuf_addbuf(&prev, sb); - break; - } - handle_filter(sb); - } - /* - * The partial chunk is saved in "prev" and will be - * appended by the next iteration of read_line_with_nul(). - */ - strbuf_list_free(lines); - break; - } - default: - handle_filter(&line); - } - - } while (!strbuf_getwholeline(&line, fin, '\n')); - -handle_body_out: - strbuf_release(&prev); -} - -static void output_header_lines(FILE *fout, const char *hdr, const struct strbuf *data) -{ - const char *sp = data->buf; - while (1) { - char *ep = strchr(sp, '\n'); - int len; - if (!ep) - len = strlen(sp); - else - len = ep - sp; - fprintf(fout, "%s: %.*s\n", hdr, len, sp); - if (!ep) - break; - sp = ep + 1; - } -} - -static void handle_info(void) -{ - struct strbuf *hdr; - int i; - - for (i = 0; header[i]; i++) { - /* only print inbody headers if we output a patch file */ - if (patch_lines && s_hdr_data[i]) - hdr = s_hdr_data[i]; - else if (p_hdr_data[i]) - hdr = p_hdr_data[i]; - else - continue; - - if (!memcmp(header[i], "Subject", 7)) { - if (!keep_subject) { - cleanup_subject(hdr); - cleanup_space(hdr); - } - output_header_lines(fout, "Subject", hdr); - } else if (!memcmp(header[i], "From", 4)) { - cleanup_space(hdr); - handle_from(hdr); - fprintf(fout, "Author: %s\n", name.buf); - fprintf(fout, "Email: %s\n", email.buf); - } else { - cleanup_space(hdr); - fprintf(fout, "%s: %s\n", header[i], hdr->buf); - } - } - fprintf(fout, "\n"); -} - -static int mailinfo(FILE *in, FILE *out, const char *msg, const char *patch) -{ - int peek; - fin = in; - fout = out; - - cmitmsg = fopen(msg, "w"); - if (!cmitmsg) { - perror(msg); - return -1; - } - patchfile = fopen(patch, "w"); - if (!patchfile) { - perror(patch); - fclose(cmitmsg); - return -1; - } - - p_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(*p_hdr_data)); - s_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(*s_hdr_data)); - - do { - peek = fgetc(in); - } while (isspace(peek)); - ungetc(peek, in); - - /* process the email header */ - while (read_one_header_line(&line, fin)) - check_header(&line, p_hdr_data, 1); - - handle_body(); - handle_info(); - - return 0; -} - -static int git_mailinfo_config(const char *var, const char *value, void *unused) -{ - if (prefixcmp(var, "mailinfo.")) - return git_default_config(var, value, unused); - if (!strcmp(var, "mailinfo.scissors")) { - use_scissors = git_config_bool(var, value); - return 0; - } - /* perhaps others here */ - return 0; -} - -static const char mailinfo_usage[] = - "git mailinfo [-k|-b] [-u | --encoding= | -n] [--scissors | --no-scissors] msg patch < mail >info"; - -int cmd_mailinfo(int argc, const char **argv, const char *prefix) -{ - const char *def_charset; - - /* NEEDSWORK: might want to do the optional .git/ directory - * discovery - */ - git_config(git_mailinfo_config, NULL); - - def_charset = (git_commit_encoding ? git_commit_encoding : "UTF-8"); - metainfo_charset = def_charset; - - while (1 < argc && argv[1][0] == '-') { - if (!strcmp(argv[1], "-k")) - keep_subject = 1; - else if (!strcmp(argv[1], "-b")) - keep_non_patch_brackets_in_subject = 1; - else if (!strcmp(argv[1], "-u")) - metainfo_charset = def_charset; - else if (!strcmp(argv[1], "-n")) - metainfo_charset = NULL; - else if (!prefixcmp(argv[1], "--encoding=")) - metainfo_charset = argv[1] + 11; - else if (!strcmp(argv[1], "--scissors")) - use_scissors = 1; - else if (!strcmp(argv[1], "--no-scissors")) - use_scissors = 0; - else if (!strcmp(argv[1], "--no-inbody-headers")) - use_inbody_headers = 0; - else - usage(mailinfo_usage); - argc--; argv++; - } - - if (argc != 3) - usage(mailinfo_usage); - - return !!mailinfo(stdin, stdout, argv[1], argv[2]); -} diff --git a/builtin-mailsplit.c b/builtin-mailsplit.c deleted file mode 100644 index 207e358..0000000 --- a/builtin-mailsplit.c +++ /dev/null @@ -1,309 +0,0 @@ -/* - * Totally braindamaged mbox splitter program. - * - * It just splits a mbox into a list of files: "0001" "0002" .. - * so you can process them further from there. - */ -#include "cache.h" -#include "builtin.h" -#include "string-list.h" -#include "strbuf.h" - -static const char git_mailsplit_usage[] = -"git mailsplit [-d] [-f] [-b] -o [|...]"; - -static int is_from_line(const char *line, int len) -{ - const char *colon; - - if (len < 20 || memcmp("From ", line, 5)) - return 0; - - colon = line + len - 2; - line += 5; - for (;;) { - if (colon < line) - return 0; - if (*--colon == ':') - break; - } - - if (!isdigit(colon[-4]) || - !isdigit(colon[-2]) || - !isdigit(colon[-1]) || - !isdigit(colon[ 1]) || - !isdigit(colon[ 2])) - return 0; - - /* year */ - if (strtol(colon+3, NULL, 10) <= 90) - return 0; - - /* Ok, close enough */ - return 1; -} - -static struct strbuf buf = STRBUF_INIT; -static int keep_cr; - -/* Called with the first line (potentially partial) - * already in buf[] -- normally that should begin with - * the Unix "From " line. Write it into the specified - * file. - */ -static int split_one(FILE *mbox, const char *name, int allow_bare) -{ - FILE *output = NULL; - int fd; - int status = 0; - int is_bare = !is_from_line(buf.buf, buf.len); - - if (is_bare && !allow_bare) - goto corrupt; - - fd = open(name, O_WRONLY | O_CREAT | O_EXCL, 0666); - if (fd < 0) - die_errno("cannot open output file '%s'", name); - output = xfdopen(fd, "w"); - - /* Copy it out, while searching for a line that begins with - * "From " and having something that looks like a date format. - */ - for (;;) { - if (!keep_cr && buf.len > 1 && buf.buf[buf.len-1] == '\n' && - buf.buf[buf.len-2] == '\r') { - strbuf_setlen(&buf, buf.len-2); - strbuf_addch(&buf, '\n'); - } - - if (fwrite(buf.buf, 1, buf.len, output) != buf.len) - die_errno("cannot write output"); - - if (strbuf_getwholeline(&buf, mbox, '\n')) { - if (feof(mbox)) { - status = 1; - break; - } - die_errno("cannot read mbox"); - } - if (!is_bare && is_from_line(buf.buf, buf.len)) - break; /* done with one message */ - } - fclose(output); - return status; - - corrupt: - if (output) - fclose(output); - unlink(name); - fprintf(stderr, "corrupt mailbox\n"); - exit(1); -} - -static int populate_maildir_list(struct string_list *list, const char *path) -{ - DIR *dir; - struct dirent *dent; - char name[PATH_MAX]; - char *subs[] = { "cur", "new", NULL }; - char **sub; - - for (sub = subs; *sub; ++sub) { - snprintf(name, sizeof(name), "%s/%s", path, *sub); - if ((dir = opendir(name)) == NULL) { - if (errno == ENOENT) - continue; - error("cannot opendir %s (%s)", name, strerror(errno)); - return -1; - } - - while ((dent = readdir(dir)) != NULL) { - if (dent->d_name[0] == '.') - continue; - snprintf(name, sizeof(name), "%s/%s", *sub, dent->d_name); - string_list_insert(name, list); - } - - closedir(dir); - } - - return 0; -} - -static int split_maildir(const char *maildir, const char *dir, - int nr_prec, int skip) -{ - char file[PATH_MAX]; - char name[PATH_MAX]; - int ret = -1; - int i; - struct string_list list = {NULL, 0, 0, 1}; - - if (populate_maildir_list(&list, maildir) < 0) - goto out; - - for (i = 0; i < list.nr; i++) { - FILE *f; - snprintf(file, sizeof(file), "%s/%s", maildir, list.items[i].string); - f = fopen(file, "r"); - if (!f) { - error("cannot open mail %s (%s)", file, strerror(errno)); - goto out; - } - - if (strbuf_getwholeline(&buf, f, '\n')) { - error("cannot read mail %s (%s)", file, strerror(errno)); - goto out; - } - - sprintf(name, "%s/%0*d", dir, nr_prec, ++skip); - split_one(f, name, 1); - - fclose(f); - } - - ret = skip; -out: - string_list_clear(&list, 1); - return ret; -} - -static int split_mbox(const char *file, const char *dir, int allow_bare, - int nr_prec, int skip) -{ - char name[PATH_MAX]; - int ret = -1; - int peek; - - FILE *f = !strcmp(file, "-") ? stdin : fopen(file, "r"); - int file_done = 0; - - if (!f) { - error("cannot open mbox %s", file); - goto out; - } - - do { - peek = fgetc(f); - } while (isspace(peek)); - ungetc(peek, f); - - if (strbuf_getwholeline(&buf, f, '\n')) { - /* empty stdin is OK */ - if (f != stdin) { - error("cannot read mbox %s", file); - goto out; - } - file_done = 1; - } - - while (!file_done) { - sprintf(name, "%s/%0*d", dir, nr_prec, ++skip); - file_done = split_one(f, name, allow_bare); - } - - if (f != stdin) - fclose(f); - - ret = skip; -out: - return ret; -} - -int cmd_mailsplit(int argc, const char **argv, const char *prefix) -{ - int nr = 0, nr_prec = 4, num = 0; - int allow_bare = 0; - const char *dir = NULL; - const char **argp; - static const char *stdin_only[] = { "-", NULL }; - - for (argp = argv+1; *argp; argp++) { - const char *arg = *argp; - - if (arg[0] != '-') - break; - /* do flags here */ - if ( arg[1] == 'd' ) { - nr_prec = strtol(arg+2, NULL, 10); - if (nr_prec < 3 || 10 <= nr_prec) - usage(git_mailsplit_usage); - continue; - } else if ( arg[1] == 'f' ) { - nr = strtol(arg+2, NULL, 10); - } else if ( arg[1] == 'h' ) { - usage(git_mailsplit_usage); - } else if ( arg[1] == 'b' && !arg[2] ) { - allow_bare = 1; - } else if (!strcmp(arg, "--keep-cr")) { - keep_cr = 1; - } else if ( arg[1] == 'o' && arg[2] ) { - dir = arg+2; - } else if ( arg[1] == '-' && !arg[2] ) { - argp++; /* -- marks end of options */ - break; - } else { - die("unknown option: %s", arg); - } - } - - if ( !dir ) { - /* Backwards compatibility: if no -o specified, accept - or just */ - switch (argc - (argp-argv)) { - case 1: - dir = argp[0]; - argp = stdin_only; - break; - case 2: - stdin_only[0] = argp[0]; - dir = argp[1]; - argp = stdin_only; - break; - default: - usage(git_mailsplit_usage); - } - } else { - /* New usage: if no more argument, parse stdin */ - if ( !*argp ) - argp = stdin_only; - } - - while (*argp) { - const char *arg = *argp++; - struct stat argstat; - int ret = 0; - - if (arg[0] == '-' && arg[1] == 0) { - ret = split_mbox(arg, dir, allow_bare, nr_prec, nr); - if (ret < 0) { - error("cannot split patches from stdin"); - return 1; - } - num += (ret - nr); - nr = ret; - continue; - } - - if (stat(arg, &argstat) == -1) { - error("cannot stat %s (%s)", arg, strerror(errno)); - return 1; - } - - if (S_ISDIR(argstat.st_mode)) - ret = split_maildir(arg, dir, nr_prec, nr); - else - ret = split_mbox(arg, dir, allow_bare, nr_prec, nr); - - if (ret < 0) { - error("cannot split patches from %s", arg); - return 1; - } - num += (ret - nr); - nr = ret; - } - - printf("%d\n", num); - - return 0; -} diff --git a/builtin-merge-base.c b/builtin-merge-base.c deleted file mode 100644 index 54e7ec2..0000000 --- a/builtin-merge-base.c +++ /dev/null @@ -1,63 +0,0 @@ -#include "builtin.h" -#include "cache.h" -#include "commit.h" -#include "parse-options.h" - -static int show_merge_base(struct commit **rev, int rev_nr, int show_all) -{ - struct commit_list *result; - - result = get_merge_bases_many(rev[0], rev_nr - 1, rev + 1, 0); - - if (!result) - return 1; - - while (result) { - printf("%s\n", sha1_to_hex(result->item->object.sha1)); - if (!show_all) - return 0; - result = result->next; - } - - return 0; -} - -static const char * const merge_base_usage[] = { - "git merge-base [-a|--all] ...", - NULL -}; - -static struct commit *get_commit_reference(const char *arg) -{ - unsigned char revkey[20]; - struct commit *r; - - if (get_sha1(arg, revkey)) - die("Not a valid object name %s", arg); - r = lookup_commit_reference(revkey); - if (!r) - die("Not a valid commit name %s", arg); - - return r; -} - -int cmd_merge_base(int argc, const char **argv, const char *prefix) -{ - struct commit **rev; - int rev_nr = 0; - int show_all = 0; - - struct option options[] = { - OPT_BOOLEAN('a', "all", &show_all, "outputs all common ancestors"), - OPT_END() - }; - - git_config(git_default_config, NULL); - argc = parse_options(argc, argv, prefix, options, merge_base_usage, 0); - if (argc < 2) - usage_with_options(merge_base_usage, options); - rev = xmalloc(argc * sizeof(*rev)); - while (argc-- > 0) - rev[rev_nr++] = get_commit_reference(*argv++); - return show_merge_base(rev, rev_nr, show_all); -} diff --git a/builtin-merge-file.c b/builtin-merge-file.c deleted file mode 100644 index 1e70073..0000000 --- a/builtin-merge-file.c +++ /dev/null @@ -1,96 +0,0 @@ -#include "builtin.h" -#include "cache.h" -#include "xdiff/xdiff.h" -#include "xdiff-interface.h" -#include "parse-options.h" - -static const char *const merge_file_usage[] = { - "git merge-file [options] [-L name1 [-L orig [-L name2]]] file1 orig_file file2", - NULL -}; - -static int label_cb(const struct option *opt, const char *arg, int unset) -{ - static int label_count = 0; - const char **names = (const char **)opt->value; - - if (label_count >= 3) - return error("too many labels on the command line"); - names[label_count++] = arg; - return 0; -} - -int cmd_merge_file(int argc, const char **argv, const char *prefix) -{ - const char *names[3] = { NULL, NULL, NULL }; - mmfile_t mmfs[3]; - mmbuffer_t result = {NULL, 0}; - xmparam_t xmp = {{XDF_NEED_MINIMAL}}; - int ret = 0, i = 0, to_stdout = 0; - int level = XDL_MERGE_ZEALOUS_ALNUM; - int style = 0, quiet = 0; - int favor = 0; - int nongit; - - struct option options[] = { - OPT_BOOLEAN('p', "stdout", &to_stdout, "send results to standard output"), - OPT_SET_INT(0, "diff3", &style, "use a diff3 based merge", XDL_MERGE_DIFF3), - OPT_SET_INT(0, "ours", &favor, "for conflicts, use our version", - XDL_MERGE_FAVOR_OURS), - OPT_SET_INT(0, "theirs", &favor, "for conflicts, use their version", - XDL_MERGE_FAVOR_THEIRS), - OPT__QUIET(&quiet), - OPT_CALLBACK('L', NULL, names, "name", - "set labels for file1/orig_file/file2", &label_cb), - OPT_END(), - }; - - prefix = setup_git_directory_gently(&nongit); - if (!nongit) { - /* Read the configuration file */ - git_config(git_xmerge_config, NULL); - if (0 <= git_xmerge_style) - style = git_xmerge_style; - } - - argc = parse_options(argc, argv, prefix, options, merge_file_usage, 0); - if (argc != 3) - usage_with_options(merge_file_usage, options); - if (quiet) { - if (!freopen("/dev/null", "w", stderr)) - return error("failed to redirect stderr to /dev/null: " - "%s\n", strerror(errno)); - } - - for (i = 0; i < 3; i++) { - if (!names[i]) - names[i] = argv[i]; - if (read_mmfile(mmfs + i, argv[i])) - return -1; - if (buffer_is_binary(mmfs[i].ptr, mmfs[i].size)) - return error("Cannot merge binary files: %s\n", - argv[i]); - } - - ret = xdl_merge(mmfs + 1, mmfs + 0, names[0], mmfs + 2, names[2], - &xmp, XDL_MERGE_FLAGS(level, style, favor), &result); - - for (i = 0; i < 3; i++) - free(mmfs[i].ptr); - - if (ret >= 0) { - const char *filename = argv[0]; - FILE *f = to_stdout ? stdout : fopen(filename, "wb"); - - if (!f) - ret = error("Could not open %s for writing", filename); - else if (result.size && - fwrite(result.ptr, result.size, 1, f) != 1) - ret = error("Could not write to %s", filename); - else if (fclose(f)) - ret = error("Could not close %s", filename); - free(result.ptr); - } - - return ret; -} diff --git a/builtin-merge-index.c b/builtin-merge-index.c deleted file mode 100644 index 2c4cf5e..0000000 --- a/builtin-merge-index.c +++ /dev/null @@ -1,111 +0,0 @@ -#include "cache.h" -#include "run-command.h" -#include "exec_cmd.h" - -static const char *pgm; -static int one_shot, quiet; -static int err; - -static int merge_entry(int pos, const char *path) -{ - int found; - const char *arguments[] = { pgm, "", "", "", path, "", "", "", NULL }; - char hexbuf[4][60]; - char ownbuf[4][60]; - - if (pos >= active_nr) - die("git merge-index: %s not in the cache", path); - found = 0; - do { - struct cache_entry *ce = active_cache[pos]; - int stage = ce_stage(ce); - - if (strcmp(ce->name, path)) - break; - found++; - strcpy(hexbuf[stage], sha1_to_hex(ce->sha1)); - sprintf(ownbuf[stage], "%o", ce->ce_mode); - arguments[stage] = hexbuf[stage]; - arguments[stage + 4] = ownbuf[stage]; - } while (++pos < active_nr); - if (!found) - die("git merge-index: %s not in the cache", path); - - if (run_command_v_opt(arguments, 0)) { - if (one_shot) - err++; - else { - if (!quiet) - die("merge program failed"); - exit(1); - } - } - return found; -} - -static void merge_file(const char *path) -{ - int pos = cache_name_pos(path, strlen(path)); - - /* - * If it already exists in the cache as stage0, it's - * already merged and there is nothing to do. - */ - if (pos < 0) - merge_entry(-pos-1, path); -} - -static void merge_all(void) -{ - int i; - for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; - if (!ce_stage(ce)) - continue; - i += merge_entry(i, ce->name)-1; - } -} - -int cmd_merge_index(int argc, const char **argv, const char *prefix) -{ - int i, force_file = 0; - - /* Without this we cannot rely on waitpid() to tell - * what happened to our children. - */ - signal(SIGCHLD, SIG_DFL); - - if (argc < 3) - usage("git merge-index [-o] [-q] (-a | [--] *)"); - - read_cache(); - - i = 1; - if (!strcmp(argv[i], "-o")) { - one_shot = 1; - i++; - } - if (!strcmp(argv[i], "-q")) { - quiet = 1; - i++; - } - pgm = argv[i++]; - for (; i < argc; i++) { - const char *arg = argv[i]; - if (!force_file && *arg == '-') { - if (!strcmp(arg, "--")) { - force_file = 1; - continue; - } - if (!strcmp(arg, "-a")) { - merge_all(); - continue; - } - die("git merge-index: unknown option %s", arg); - } - merge_file(arg); - } - if (err && !quiet) - die("merge program failed"); - return err; -} diff --git a/builtin-merge-ours.c b/builtin-merge-ours.c deleted file mode 100644 index 6844116..0000000 --- a/builtin-merge-ours.c +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Implementation of git-merge-ours.sh as builtin - * - * Copyright (c) 2007 Thomas Harning Jr - * Original: - * Original Copyright (c) 2005 Junio C Hamano - * - * Pretend we resolved the heads, but declare our tree trumps everybody else. - */ -#include "git-compat-util.h" -#include "builtin.h" - -static const char builtin_merge_ours_usage[] = - "git merge-ours ... -- HEAD ..."; - -static const char *diff_index_args[] = { - "diff-index", "--quiet", "--cached", "HEAD", "--", NULL -}; -#define NARGS (ARRAY_SIZE(diff_index_args) - 1) - -int cmd_merge_ours(int argc, const char **argv, const char *prefix) -{ - if (argc == 2 && !strcmp(argv[1], "-h")) - usage(builtin_merge_ours_usage); - - /* - * We need to exit with 2 if the index does not match our HEAD tree, - * because the current index is what we will be committing as the - * merge result. - */ - if (cmd_diff_index(NARGS, diff_index_args, prefix)) - exit(2); - exit(0); -} diff --git a/builtin-merge-recursive.c b/builtin-merge-recursive.c deleted file mode 100644 index d8875d5..0000000 --- a/builtin-merge-recursive.c +++ /dev/null @@ -1,84 +0,0 @@ -#include "cache.h" -#include "commit.h" -#include "tag.h" -#include "merge-recursive.h" - -static const char *better_branch_name(const char *branch) -{ - static char githead_env[8 + 40 + 1]; - char *name; - - if (strlen(branch) != 40) - return branch; - sprintf(githead_env, "GITHEAD_%s", branch); - name = getenv(githead_env); - return name ? name : branch; -} - -int cmd_merge_recursive(int argc, const char **argv, const char *prefix) -{ - const unsigned char *bases[21]; - unsigned bases_count = 0; - int i, failed; - unsigned char h1[20], h2[20]; - struct merge_options o; - struct commit *result; - - init_merge_options(&o); - if (argv[0] && !suffixcmp(argv[0], "-subtree")) - o.subtree_shift = ""; - - if (argc < 4) - usagef("%s ... -- ...", argv[0]); - - for (i = 1; i < argc; ++i) { - const char *arg = argv[i]; - - if (!prefixcmp(arg, "--")) { - if (!arg[2]) - break; - if (!strcmp(arg+2, "ours")) - o.recursive_variant = MERGE_RECURSIVE_OURS; - else if (!strcmp(arg+2, "theirs")) - o.recursive_variant = MERGE_RECURSIVE_THEIRS; - else if (!strcmp(arg+2, "subtree")) - o.subtree_shift = ""; - else if (!prefixcmp(arg+2, "subtree=")) - o.subtree_shift = arg + 10; - else - die("Unknown option %s", arg); - continue; - } - if (bases_count < ARRAY_SIZE(bases)-1) { - unsigned char *sha = xmalloc(20); - if (get_sha1(argv[i], sha)) - die("Could not parse object '%s'", argv[i]); - bases[bases_count++] = sha; - } - else - warning("Cannot handle more than %d bases. " - "Ignoring %s.", - (int)ARRAY_SIZE(bases)-1, argv[i]); - } - if (argc - i != 3) /* "--" "" "" */ - die("Not handling anything other than two heads merge."); - - o.branch1 = argv[++i]; - o.branch2 = argv[++i]; - - if (get_sha1(o.branch1, h1)) - die("Could not resolve ref '%s'", o.branch1); - if (get_sha1(o.branch2, h2)) - die("Could not resolve ref '%s'", o.branch2); - - o.branch1 = better_branch_name(o.branch1); - o.branch2 = better_branch_name(o.branch2); - - if (o.verbosity >= 3) - printf("Merging %s with %s\n", o.branch1, o.branch2); - - failed = merge_recursive_generic(&o, h1, h2, bases_count, bases, &result); - if (failed < 0) - return 128; /* die() error code */ - return failed; -} diff --git a/builtin-merge-tree.c b/builtin-merge-tree.c deleted file mode 100644 index a4a4f2c..0000000 --- a/builtin-merge-tree.c +++ /dev/null @@ -1,358 +0,0 @@ -#include "cache.h" -#include "tree-walk.h" -#include "xdiff-interface.h" -#include "blob.h" -#include "exec_cmd.h" - -static const char merge_tree_usage[] = "git merge-tree "; -static int resolve_directories = 1; - -struct merge_list { - struct merge_list *next; - struct merge_list *link; /* other stages for this object */ - - unsigned int stage : 2, - flags : 30; - unsigned int mode; - const char *path; - struct blob *blob; -}; - -static struct merge_list *merge_result, **merge_result_end = &merge_result; - -static void add_merge_entry(struct merge_list *entry) -{ - *merge_result_end = entry; - merge_result_end = &entry->next; -} - -static void merge_trees(struct tree_desc t[3], const char *base); - -static const char *explanation(struct merge_list *entry) -{ - switch (entry->stage) { - case 0: - return "merged"; - case 3: - return "added in remote"; - case 2: - if (entry->link) - return "added in both"; - return "added in local"; - } - - /* Existed in base */ - entry = entry->link; - if (!entry) - return "removed in both"; - - if (entry->link) - return "changed in both"; - - if (entry->stage == 3) - return "removed in local"; - return "removed in remote"; -} - -extern void *merge_file(const char *, struct blob *, struct blob *, struct blob *, unsigned long *); - -static void *result(struct merge_list *entry, unsigned long *size) -{ - enum object_type type; - struct blob *base, *our, *their; - - if (!entry->stage) - return read_sha1_file(entry->blob->object.sha1, &type, size); - base = NULL; - if (entry->stage == 1) { - base = entry->blob; - entry = entry->link; - } - our = NULL; - if (entry && entry->stage == 2) { - our = entry->blob; - entry = entry->link; - } - their = NULL; - if (entry) - their = entry->blob; - return merge_file(entry->path, base, our, their, size); -} - -static void *origin(struct merge_list *entry, unsigned long *size) -{ - enum object_type type; - while (entry) { - if (entry->stage == 2) - return read_sha1_file(entry->blob->object.sha1, &type, size); - entry = entry->link; - } - return NULL; -} - -static int show_outf(void *priv_, mmbuffer_t *mb, int nbuf) -{ - int i; - for (i = 0; i < nbuf; i++) - printf("%.*s", (int) mb[i].size, mb[i].ptr); - return 0; -} - -static void show_diff(struct merge_list *entry) -{ - unsigned long size; - mmfile_t src, dst; - xpparam_t xpp; - xdemitconf_t xecfg; - xdemitcb_t ecb; - - xpp.flags = XDF_NEED_MINIMAL; - memset(&xecfg, 0, sizeof(xecfg)); - xecfg.ctxlen = 3; - ecb.outf = show_outf; - ecb.priv = NULL; - - src.ptr = origin(entry, &size); - if (!src.ptr) - size = 0; - src.size = size; - dst.ptr = result(entry, &size); - if (!dst.ptr) - size = 0; - dst.size = size; - xdi_diff(&src, &dst, &xpp, &xecfg, &ecb); - free(src.ptr); - free(dst.ptr); -} - -static void show_result_list(struct merge_list *entry) -{ - printf("%s\n", explanation(entry)); - do { - struct merge_list *link = entry->link; - static const char *desc[4] = { "result", "base", "our", "their" }; - printf(" %-6s %o %s %s\n", desc[entry->stage], entry->mode, sha1_to_hex(entry->blob->object.sha1), entry->path); - entry = link; - } while (entry); -} - -static void show_result(void) -{ - struct merge_list *walk; - - walk = merge_result; - while (walk) { - show_result_list(walk); - show_diff(walk); - walk = walk->next; - } -} - -/* An empty entry never compares same, not even to another empty entry */ -static int same_entry(struct name_entry *a, struct name_entry *b) -{ - return a->sha1 && - b->sha1 && - !hashcmp(a->sha1, b->sha1) && - a->mode == b->mode; -} - -static struct merge_list *create_entry(unsigned stage, unsigned mode, const unsigned char *sha1, const char *path) -{ - struct merge_list *res = xcalloc(1, sizeof(*res)); - - res->stage = stage; - res->path = path; - res->mode = mode; - res->blob = lookup_blob(sha1); - return res; -} - -static char *traverse_path(const struct traverse_info *info, const struct name_entry *n) -{ - char *path = xmalloc(traverse_path_len(info, n) + 1); - return make_traverse_path(path, info, n); -} - -static void resolve(const struct traverse_info *info, struct name_entry *branch1, struct name_entry *result) -{ - struct merge_list *orig, *final; - const char *path; - - /* If it's already branch1, don't bother showing it */ - if (!branch1) - return; - - path = traverse_path(info, result); - orig = create_entry(2, branch1->mode, branch1->sha1, path); - final = create_entry(0, result->mode, result->sha1, path); - - final->link = orig; - - add_merge_entry(final); -} - -static int unresolved_directory(const struct traverse_info *info, struct name_entry n[3]) -{ - char *newbase; - struct name_entry *p; - struct tree_desc t[3]; - void *buf0, *buf1, *buf2; - - if (!resolve_directories) - return 0; - p = n; - if (!p->mode) { - p++; - if (!p->mode) - p++; - } - if (!S_ISDIR(p->mode)) - return 0; - newbase = traverse_path(info, p); - buf0 = fill_tree_descriptor(t+0, n[0].sha1); - buf1 = fill_tree_descriptor(t+1, n[1].sha1); - buf2 = fill_tree_descriptor(t+2, n[2].sha1); - merge_trees(t, newbase); - - free(buf0); - free(buf1); - free(buf2); - free(newbase); - return 1; -} - - -static struct merge_list *link_entry(unsigned stage, const struct traverse_info *info, struct name_entry *n, struct merge_list *entry) -{ - const char *path; - struct merge_list *link; - - if (!n->mode) - return entry; - if (entry) - path = entry->path; - else - path = traverse_path(info, n); - link = create_entry(stage, n->mode, n->sha1, path); - link->link = entry; - return link; -} - -static void unresolved(const struct traverse_info *info, struct name_entry n[3]) -{ - struct merge_list *entry = NULL; - - if (unresolved_directory(info, n)) - return; - - /* - * Do them in reverse order so that the resulting link - * list has the stages in order - link_entry adds new - * links at the front. - */ - entry = link_entry(3, info, n + 2, entry); - entry = link_entry(2, info, n + 1, entry); - entry = link_entry(1, info, n + 0, entry); - - add_merge_entry(entry); -} - -/* - * Merge two trees together (t[1] and t[2]), using a common base (t[0]) - * as the origin. - * - * This walks the (sorted) trees in lock-step, checking every possible - * name. Note that directories automatically sort differently from other - * files (see "base_name_compare"), so you'll never see file/directory - * conflicts, because they won't ever compare the same. - * - * IOW, if a directory changes to a filename, it will automatically be - * seen as the directory going away, and the filename being created. - * - * Think of this as a three-way diff. - * - * The output will be either: - * - successful merge - * "0 mode sha1 filename" - * NOTE NOTE NOTE! FIXME! We really really need to walk the index - * in parallel with this too! - * - * - conflict: - * "1 mode sha1 filename" - * "2 mode sha1 filename" - * "3 mode sha1 filename" - * where not all of the 1/2/3 lines may exist, of course. - * - * The successful merge rules are the same as for the three-way merge - * in git-read-tree. - */ -static int threeway_callback(int n, unsigned long mask, unsigned long dirmask, struct name_entry *entry, struct traverse_info *info) -{ - /* Same in both? */ - if (same_entry(entry+1, entry+2)) { - if (entry[0].sha1) { - resolve(info, NULL, entry+1); - return mask; - } - } - - if (same_entry(entry+0, entry+1)) { - if (entry[2].sha1 && !S_ISDIR(entry[2].mode)) { - resolve(info, entry+1, entry+2); - return mask; - } - } - - if (same_entry(entry+0, entry+2)) { - if (entry[1].sha1 && !S_ISDIR(entry[1].mode)) { - resolve(info, NULL, entry+1); - return mask; - } - } - - unresolved(info, entry); - return mask; -} - -static void merge_trees(struct tree_desc t[3], const char *base) -{ - struct traverse_info info; - - setup_traverse_info(&info, base); - info.fn = threeway_callback; - traverse_trees(3, t, &info); -} - -static void *get_tree_descriptor(struct tree_desc *desc, const char *rev) -{ - unsigned char sha1[20]; - void *buf; - - if (get_sha1(rev, sha1)) - die("unknown rev %s", rev); - buf = fill_tree_descriptor(desc, sha1); - if (!buf) - die("%s is not a tree", rev); - return buf; -} - -int cmd_merge_tree(int argc, const char **argv, const char *prefix) -{ - struct tree_desc t[3]; - void *buf1, *buf2, *buf3; - - if (argc != 4) - usage(merge_tree_usage); - - buf1 = get_tree_descriptor(t+0, argv[1]); - buf2 = get_tree_descriptor(t+1, argv[2]); - buf3 = get_tree_descriptor(t+2, argv[3]); - merge_trees(t, ""); - free(buf1); - free(buf2); - free(buf3); - - show_result(); - return 0; -} diff --git a/builtin-merge.c b/builtin-merge.c deleted file mode 100644 index 3aaec7b..0000000 --- a/builtin-merge.c +++ /dev/null @@ -1,1292 +0,0 @@ -/* - * Builtin "git merge" - * - * Copyright (c) 2008 Miklos Vajna - * - * Based on git-merge.sh by Junio C Hamano. - */ - -#include "cache.h" -#include "parse-options.h" -#include "builtin.h" -#include "run-command.h" -#include "diff.h" -#include "refs.h" -#include "commit.h" -#include "diffcore.h" -#include "revision.h" -#include "unpack-trees.h" -#include "cache-tree.h" -#include "dir.h" -#include "utf8.h" -#include "log-tree.h" -#include "color.h" -#include "rerere.h" -#include "help.h" -#include "merge-recursive.h" -#include "resolve-undo.h" - -#define DEFAULT_TWOHEAD (1<<0) -#define DEFAULT_OCTOPUS (1<<1) -#define NO_FAST_FORWARD (1<<2) -#define NO_TRIVIAL (1<<3) - -struct strategy { - const char *name; - unsigned attr; -}; - -static const char * const builtin_merge_usage[] = { - "git merge [options] ...", - "git merge [options] HEAD ", - NULL -}; - -static int show_diffstat = 1, option_log, squash; -static int option_commit = 1, allow_fast_forward = 1; -static int fast_forward_only; -static int allow_trivial = 1, have_message; -static struct strbuf merge_msg; -static struct commit_list *remoteheads; -static unsigned char head[20], stash[20]; -static struct strategy **use_strategies; -static size_t use_strategies_nr, use_strategies_alloc; -static const char **xopts; -static size_t xopts_nr, xopts_alloc; -static const char *branch; -static int verbosity; -static int allow_rerere_auto; - -static struct strategy all_strategy[] = { - { "recursive", DEFAULT_TWOHEAD | NO_TRIVIAL }, - { "octopus", DEFAULT_OCTOPUS }, - { "resolve", 0 }, - { "ours", NO_FAST_FORWARD | NO_TRIVIAL }, - { "subtree", NO_FAST_FORWARD | NO_TRIVIAL }, -}; - -static const char *pull_twohead, *pull_octopus; - -static int option_parse_message(const struct option *opt, - const char *arg, int unset) -{ - struct strbuf *buf = opt->value; - - if (unset) - strbuf_setlen(buf, 0); - else if (arg) { - strbuf_addf(buf, "%s%s", buf->len ? "\n\n" : "", arg); - have_message = 1; - } else - return error("switch `m' requires a value"); - return 0; -} - -static struct strategy *get_strategy(const char *name) -{ - int i; - struct strategy *ret; - static struct cmdnames main_cmds, other_cmds; - static int loaded; - - if (!name) - return NULL; - - for (i = 0; i < ARRAY_SIZE(all_strategy); i++) - if (!strcmp(name, all_strategy[i].name)) - return &all_strategy[i]; - - if (!loaded) { - struct cmdnames not_strategies; - loaded = 1; - - memset(¬_strategies, 0, sizeof(struct cmdnames)); - load_command_list("git-merge-", &main_cmds, &other_cmds); - for (i = 0; i < main_cmds.cnt; i++) { - int j, found = 0; - struct cmdname *ent = main_cmds.names[i]; - for (j = 0; j < ARRAY_SIZE(all_strategy); j++) - if (!strncmp(ent->name, all_strategy[j].name, ent->len) - && !all_strategy[j].name[ent->len]) - found = 1; - if (!found) - add_cmdname(¬_strategies, ent->name, ent->len); - } - exclude_cmds(&main_cmds, ¬_strategies); - } - if (!is_in_cmdlist(&main_cmds, name) && !is_in_cmdlist(&other_cmds, name)) { - fprintf(stderr, "Could not find merge strategy '%s'.\n", name); - fprintf(stderr, "Available strategies are:"); - for (i = 0; i < main_cmds.cnt; i++) - fprintf(stderr, " %s", main_cmds.names[i]->name); - fprintf(stderr, ".\n"); - if (other_cmds.cnt) { - fprintf(stderr, "Available custom strategies are:"); - for (i = 0; i < other_cmds.cnt; i++) - fprintf(stderr, " %s", other_cmds.names[i]->name); - fprintf(stderr, ".\n"); - } - exit(1); - } - - ret = xcalloc(1, sizeof(struct strategy)); - ret->name = xstrdup(name); - return ret; -} - -static void append_strategy(struct strategy *s) -{ - ALLOC_GROW(use_strategies, use_strategies_nr + 1, use_strategies_alloc); - use_strategies[use_strategies_nr++] = s; -} - -static int option_parse_strategy(const struct option *opt, - const char *name, int unset) -{ - if (unset) - return 0; - - append_strategy(get_strategy(name)); - return 0; -} - -static int option_parse_x(const struct option *opt, - const char *arg, int unset) -{ - if (unset) - return 0; - - ALLOC_GROW(xopts, xopts_nr + 1, xopts_alloc); - xopts[xopts_nr++] = xstrdup(arg); - return 0; -} - -static int option_parse_n(const struct option *opt, - const char *arg, int unset) -{ - show_diffstat = unset; - return 0; -} - -static struct option builtin_merge_options[] = { - { OPTION_CALLBACK, 'n', NULL, NULL, NULL, - "do not show a diffstat at the end of the merge", - PARSE_OPT_NOARG, option_parse_n }, - OPT_BOOLEAN(0, "stat", &show_diffstat, - "show a diffstat at the end of the merge"), - OPT_BOOLEAN(0, "summary", &show_diffstat, "(synonym to --stat)"), - OPT_BOOLEAN(0, "log", &option_log, - "add list of one-line log to merge commit message"), - OPT_BOOLEAN(0, "squash", &squash, - "create a single commit instead of doing a merge"), - OPT_BOOLEAN(0, "commit", &option_commit, - "perform a commit if the merge succeeds (default)"), - OPT_BOOLEAN(0, "ff", &allow_fast_forward, - "allow fast-forward (default)"), - OPT_BOOLEAN(0, "ff-only", &fast_forward_only, - "abort if fast-forward is not possible"), - OPT_RERERE_AUTOUPDATE(&allow_rerere_auto), - OPT_CALLBACK('s', "strategy", &use_strategies, "strategy", - "merge strategy to use", option_parse_strategy), - OPT_CALLBACK('X', "strategy-option", &xopts, "option=value", - "option for selected merge strategy", option_parse_x), - OPT_CALLBACK('m', "message", &merge_msg, "message", - "message to be used for the merge commit (if any)", - option_parse_message), - OPT__VERBOSITY(&verbosity), - OPT_END() -}; - -/* Cleans up metadata that is uninteresting after a succeeded merge. */ -static void drop_save(void) -{ - unlink(git_path("MERGE_HEAD")); - unlink(git_path("MERGE_MSG")); - unlink(git_path("MERGE_MODE")); -} - -static void save_state(void) -{ - int len; - struct child_process cp; - struct strbuf buffer = STRBUF_INIT; - const char *argv[] = {"stash", "create", NULL}; - - memset(&cp, 0, sizeof(cp)); - cp.argv = argv; - cp.out = -1; - cp.git_cmd = 1; - - if (start_command(&cp)) - die("could not run stash."); - len = strbuf_read(&buffer, cp.out, 1024); - close(cp.out); - - if (finish_command(&cp) || len < 0) - die("stash failed"); - else if (!len) - return; - strbuf_setlen(&buffer, buffer.len-1); - if (get_sha1(buffer.buf, stash)) - die("not a valid object: %s", buffer.buf); -} - -static void reset_hard(unsigned const char *sha1, int verbose) -{ - int i = 0; - const char *args[6]; - - args[i++] = "read-tree"; - if (verbose) - args[i++] = "-v"; - args[i++] = "--reset"; - args[i++] = "-u"; - args[i++] = sha1_to_hex(sha1); - args[i] = NULL; - - if (run_command_v_opt(args, RUN_GIT_CMD)) - die("read-tree failed"); -} - -static void restore_state(void) -{ - struct strbuf sb = STRBUF_INIT; - const char *args[] = { "stash", "apply", NULL, NULL }; - - if (is_null_sha1(stash)) - return; - - reset_hard(head, 1); - - args[2] = sha1_to_hex(stash); - - /* - * It is OK to ignore error here, for example when there was - * nothing to restore. - */ - run_command_v_opt(args, RUN_GIT_CMD); - - strbuf_release(&sb); - refresh_cache(REFRESH_QUIET); -} - -/* This is called when no merge was necessary. */ -static void finish_up_to_date(const char *msg) -{ - if (verbosity >= 0) - printf("%s%s\n", squash ? " (nothing to squash)" : "", msg); - drop_save(); -} - -static void squash_message(void) -{ - struct rev_info rev; - struct commit *commit; - struct strbuf out = STRBUF_INIT; - struct commit_list *j; - int fd; - struct pretty_print_context ctx = {0}; - - printf("Squash commit -- not updating HEAD\n"); - fd = open(git_path("SQUASH_MSG"), O_WRONLY | O_CREAT, 0666); - if (fd < 0) - die_errno("Could not write to '%s'", git_path("SQUASH_MSG")); - - init_revisions(&rev, NULL); - rev.ignore_merges = 1; - rev.commit_format = CMIT_FMT_MEDIUM; - - commit = lookup_commit(head); - commit->object.flags |= UNINTERESTING; - add_pending_object(&rev, &commit->object, NULL); - - for (j = remoteheads; j; j = j->next) - add_pending_object(&rev, &j->item->object, NULL); - - setup_revisions(0, NULL, &rev, NULL); - if (prepare_revision_walk(&rev)) - die("revision walk setup failed"); - - ctx.abbrev = rev.abbrev; - ctx.date_mode = rev.date_mode; - - strbuf_addstr(&out, "Squashed commit of the following:\n"); - while ((commit = get_revision(&rev)) != NULL) { - strbuf_addch(&out, '\n'); - strbuf_addf(&out, "commit %s\n", - sha1_to_hex(commit->object.sha1)); - pretty_print_commit(rev.commit_format, commit, &out, &ctx); - } - if (write(fd, out.buf, out.len) < 0) - die_errno("Writing SQUASH_MSG"); - if (close(fd)) - die_errno("Finishing SQUASH_MSG"); - strbuf_release(&out); -} - -static void finish(const unsigned char *new_head, const char *msg) -{ - struct strbuf reflog_message = STRBUF_INIT; - - if (!msg) - strbuf_addstr(&reflog_message, getenv("GIT_REFLOG_ACTION")); - else { - if (verbosity >= 0) - printf("%s\n", msg); - strbuf_addf(&reflog_message, "%s: %s", - getenv("GIT_REFLOG_ACTION"), msg); - } - if (squash) { - squash_message(); - } else { - if (verbosity >= 0 && !merge_msg.len) - printf("No merge message -- not updating HEAD\n"); - else { - const char *argv_gc_auto[] = { "gc", "--auto", NULL }; - update_ref(reflog_message.buf, "HEAD", - new_head, head, 0, - DIE_ON_ERR); - /* - * We ignore errors in 'gc --auto', since the - * user should see them. - */ - run_command_v_opt(argv_gc_auto, RUN_GIT_CMD); - } - } - if (new_head && show_diffstat) { - struct diff_options opts; - diff_setup(&opts); - opts.output_format |= - DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT; - opts.detect_rename = DIFF_DETECT_RENAME; - if (diff_use_color_default > 0) - DIFF_OPT_SET(&opts, COLOR_DIFF); - if (diff_setup_done(&opts) < 0) - die("diff_setup_done failed"); - diff_tree_sha1(head, new_head, "", &opts); - diffcore_std(&opts); - diff_flush(&opts); - } - - /* Run a post-merge hook */ - run_hook(NULL, "post-merge", squash ? "1" : "0", NULL); - - strbuf_release(&reflog_message); -} - -/* Get the name for the merge commit's message. */ -static void merge_name(const char *remote, struct strbuf *msg) -{ - struct object *remote_head; - unsigned char branch_head[20], buf_sha[20]; - struct strbuf buf = STRBUF_INIT; - struct strbuf bname = STRBUF_INIT; - const char *ptr; - char *found_ref; - int len, early; - - strbuf_branchname(&bname, remote); - remote = bname.buf; - - memset(branch_head, 0, sizeof(branch_head)); - remote_head = peel_to_type(remote, 0, NULL, OBJ_COMMIT); - if (!remote_head) - die("'%s' does not point to a commit", remote); - - if (dwim_ref(remote, strlen(remote), branch_head, &found_ref) > 0) { - if (!prefixcmp(found_ref, "refs/heads/")) { - strbuf_addf(msg, "%s\t\tbranch '%s' of .\n", - sha1_to_hex(branch_head), remote); - goto cleanup; - } - if (!prefixcmp(found_ref, "refs/remotes/")) { - strbuf_addf(msg, "%s\t\tremote branch '%s' of .\n", - sha1_to_hex(branch_head), remote); - goto cleanup; - } - } - - /* See if remote matches ^^^.. or ~ */ - for (len = 0, ptr = remote + strlen(remote); - remote < ptr && ptr[-1] == '^'; - ptr--) - len++; - if (len) - early = 1; - else { - early = 0; - ptr = strrchr(remote, '~'); - if (ptr) { - int seen_nonzero = 0; - - len++; /* count ~ */ - while (*++ptr && isdigit(*ptr)) { - seen_nonzero |= (*ptr != '0'); - len++; - } - if (*ptr) - len = 0; /* not ...~ */ - else if (seen_nonzero) - early = 1; - else if (len == 1) - early = 1; /* "name~" is "name~1"! */ - } - } - if (len) { - struct strbuf truname = STRBUF_INIT; - strbuf_addstr(&truname, "refs/heads/"); - strbuf_addstr(&truname, remote); - strbuf_setlen(&truname, truname.len - len); - if (resolve_ref(truname.buf, buf_sha, 0, NULL)) { - strbuf_addf(msg, - "%s\t\tbranch '%s'%s of .\n", - sha1_to_hex(remote_head->sha1), - truname.buf + 11, - (early ? " (early part)" : "")); - strbuf_release(&truname); - goto cleanup; - } - } - - if (!strcmp(remote, "FETCH_HEAD") && - !access(git_path("FETCH_HEAD"), R_OK)) { - FILE *fp; - struct strbuf line = STRBUF_INIT; - char *ptr; - - fp = fopen(git_path("FETCH_HEAD"), "r"); - if (!fp) - die_errno("could not open '%s' for reading", - git_path("FETCH_HEAD")); - strbuf_getline(&line, fp, '\n'); - fclose(fp); - ptr = strstr(line.buf, "\tnot-for-merge\t"); - if (ptr) - strbuf_remove(&line, ptr-line.buf+1, 13); - strbuf_addbuf(msg, &line); - strbuf_release(&line); - goto cleanup; - } - strbuf_addf(msg, "%s\t\tcommit '%s'\n", - sha1_to_hex(remote_head->sha1), remote); -cleanup: - strbuf_release(&buf); - strbuf_release(&bname); -} - -static int git_merge_config(const char *k, const char *v, void *cb) -{ - if (branch && !prefixcmp(k, "branch.") && - !prefixcmp(k + 7, branch) && - !strcmp(k + 7 + strlen(branch), ".mergeoptions")) { - const char **argv; - int argc; - char *buf; - - buf = xstrdup(v); - argc = split_cmdline(buf, &argv); - if (argc < 0) - die("Bad branch.%s.mergeoptions string", branch); - argv = xrealloc(argv, sizeof(*argv) * (argc + 2)); - memmove(argv + 1, argv, sizeof(*argv) * (argc + 1)); - argc++; - parse_options(argc, argv, NULL, builtin_merge_options, - builtin_merge_usage, 0); - free(buf); - } - - if (!strcmp(k, "merge.diffstat") || !strcmp(k, "merge.stat")) - show_diffstat = git_config_bool(k, v); - else if (!strcmp(k, "pull.twohead")) - return git_config_string(&pull_twohead, k, v); - else if (!strcmp(k, "pull.octopus")) - return git_config_string(&pull_octopus, k, v); - else if (!strcmp(k, "merge.log") || !strcmp(k, "merge.summary")) - option_log = git_config_bool(k, v); - return git_diff_ui_config(k, v, cb); -} - -static int read_tree_trivial(unsigned char *common, unsigned char *head, - unsigned char *one) -{ - int i, nr_trees = 0; - struct tree *trees[MAX_UNPACK_TREES]; - struct tree_desc t[MAX_UNPACK_TREES]; - struct unpack_trees_options opts; - - memset(&opts, 0, sizeof(opts)); - opts.head_idx = 2; - opts.src_index = &the_index; - opts.dst_index = &the_index; - opts.update = 1; - opts.verbose_update = 1; - opts.trivial_merges_only = 1; - opts.merge = 1; - trees[nr_trees] = parse_tree_indirect(common); - if (!trees[nr_trees++]) - return -1; - trees[nr_trees] = parse_tree_indirect(head); - if (!trees[nr_trees++]) - return -1; - trees[nr_trees] = parse_tree_indirect(one); - if (!trees[nr_trees++]) - return -1; - opts.fn = threeway_merge; - cache_tree_free(&active_cache_tree); - for (i = 0; i < nr_trees; i++) { - parse_tree(trees[i]); - init_tree_desc(t+i, trees[i]->buffer, trees[i]->size); - } - if (unpack_trees(nr_trees, t, &opts)) - return -1; - return 0; -} - -static void write_tree_trivial(unsigned char *sha1) -{ - if (write_cache_as_tree(sha1, 0, NULL)) - die("git write-tree failed to write a tree"); -} - -static int try_merge_strategy(const char *strategy, struct commit_list *common, - const char *head_arg) -{ - const char **args; - int i = 0, x = 0, ret; - struct commit_list *j; - struct strbuf buf = STRBUF_INIT; - int index_fd; - struct lock_file *lock = xcalloc(1, sizeof(struct lock_file)); - - index_fd = hold_locked_index(lock, 1); - refresh_cache(REFRESH_QUIET); - if (active_cache_changed && - (write_cache(index_fd, active_cache, active_nr) || - commit_locked_index(lock))) - return error("Unable to write index."); - rollback_lock_file(lock); - - if (!strcmp(strategy, "recursive") || !strcmp(strategy, "subtree")) { - int clean; - struct commit *result; - struct lock_file *lock = xcalloc(1, sizeof(struct lock_file)); - int index_fd; - struct commit_list *reversed = NULL; - struct merge_options o; - - if (remoteheads->next) { - error("Not handling anything other than two heads merge."); - return 2; - } - - init_merge_options(&o); - if (!strcmp(strategy, "subtree")) - o.subtree_shift = ""; - - for (x = 0; x < xopts_nr; x++) { - if (!strcmp(xopts[x], "ours")) - o.recursive_variant = MERGE_RECURSIVE_OURS; - else if (!strcmp(xopts[x], "theirs")) - o.recursive_variant = MERGE_RECURSIVE_THEIRS; - else if (!strcmp(xopts[x], "subtree")) - o.subtree_shift = ""; - else if (!prefixcmp(xopts[x], "subtree=")) - o.subtree_shift = xopts[x]+8; - else - die("Unknown option for merge-recursive: -X%s", xopts[x]); - } - - o.branch1 = head_arg; - o.branch2 = remoteheads->item->util; - - for (j = common; j; j = j->next) - commit_list_insert(j->item, &reversed); - - index_fd = hold_locked_index(lock, 1); - clean = merge_recursive(&o, lookup_commit(head), - remoteheads->item, reversed, &result); - if (active_cache_changed && - (write_cache(index_fd, active_cache, active_nr) || - commit_locked_index(lock))) - die ("unable to write %s", get_index_file()); - rollback_lock_file(lock); - return clean ? 0 : 1; - } else { - args = xmalloc((4 + xopts_nr + commit_list_count(common) + - commit_list_count(remoteheads)) * sizeof(char *)); - strbuf_addf(&buf, "merge-%s", strategy); - args[i++] = buf.buf; - for (x = 0; x < xopts_nr; x++) { - char *s = xmalloc(strlen(xopts[x])+2+1); - strcpy(s, "--"); - strcpy(s+2, xopts[x]); - args[i++] = s; - } - for (j = common; j; j = j->next) - args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1)); - args[i++] = "--"; - args[i++] = head_arg; - for (j = remoteheads; j; j = j->next) - args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1)); - args[i] = NULL; - ret = run_command_v_opt(args, RUN_GIT_CMD); - strbuf_release(&buf); - i = 1; - for (x = 0; x < xopts_nr; x++) - free((void *)args[i++]); - for (j = common; j; j = j->next) - free((void *)args[i++]); - i += 2; - for (j = remoteheads; j; j = j->next) - free((void *)args[i++]); - free(args); - discard_cache(); - if (read_cache() < 0) - die("failed to read the cache"); - resolve_undo_clear(); - return ret; - } -} - -static void count_diff_files(struct diff_queue_struct *q, - struct diff_options *opt, void *data) -{ - int *count = data; - - (*count) += q->nr; -} - -static int count_unmerged_entries(void) -{ - int i, ret = 0; - - for (i = 0; i < active_nr; i++) - if (ce_stage(active_cache[i])) - ret++; - - return ret; -} - -static int checkout_fast_forward(unsigned char *head, unsigned char *remote) -{ - struct tree *trees[MAX_UNPACK_TREES]; - struct unpack_trees_options opts; - struct tree_desc t[MAX_UNPACK_TREES]; - int i, fd, nr_trees = 0; - struct dir_struct dir; - struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file)); - - refresh_cache(REFRESH_QUIET); - - fd = hold_locked_index(lock_file, 1); - - memset(&trees, 0, sizeof(trees)); - memset(&opts, 0, sizeof(opts)); - memset(&t, 0, sizeof(t)); - memset(&dir, 0, sizeof(dir)); - dir.flags |= DIR_SHOW_IGNORED; - dir.exclude_per_dir = ".gitignore"; - opts.dir = &dir; - - opts.head_idx = 1; - opts.src_index = &the_index; - opts.dst_index = &the_index; - opts.update = 1; - opts.verbose_update = 1; - opts.merge = 1; - opts.fn = twoway_merge; - opts.msgs = get_porcelain_error_msgs(); - - trees[nr_trees] = parse_tree_indirect(head); - if (!trees[nr_trees++]) - return -1; - trees[nr_trees] = parse_tree_indirect(remote); - if (!trees[nr_trees++]) - return -1; - for (i = 0; i < nr_trees; i++) { - parse_tree(trees[i]); - init_tree_desc(t+i, trees[i]->buffer, trees[i]->size); - } - if (unpack_trees(nr_trees, t, &opts)) - return -1; - if (write_cache(fd, active_cache, active_nr) || - commit_locked_index(lock_file)) - die("unable to write new index file"); - return 0; -} - -static void split_merge_strategies(const char *string, struct strategy **list, - int *nr, int *alloc) -{ - char *p, *q, *buf; - - if (!string) - return; - - buf = xstrdup(string); - q = buf; - for (;;) { - p = strchr(q, ' '); - if (!p) { - ALLOC_GROW(*list, *nr + 1, *alloc); - (*list)[(*nr)++].name = xstrdup(q); - free(buf); - return; - } else { - *p = '\0'; - ALLOC_GROW(*list, *nr + 1, *alloc); - (*list)[(*nr)++].name = xstrdup(q); - q = ++p; - } - } -} - -static void add_strategies(const char *string, unsigned attr) -{ - struct strategy *list = NULL; - int list_alloc = 0, list_nr = 0, i; - - memset(&list, 0, sizeof(list)); - split_merge_strategies(string, &list, &list_nr, &list_alloc); - if (list) { - for (i = 0; i < list_nr; i++) - append_strategy(get_strategy(list[i].name)); - return; - } - for (i = 0; i < ARRAY_SIZE(all_strategy); i++) - if (all_strategy[i].attr & attr) - append_strategy(&all_strategy[i]); - -} - -static int merge_trivial(void) -{ - unsigned char result_tree[20], result_commit[20]; - struct commit_list *parent = xmalloc(sizeof(*parent)); - - write_tree_trivial(result_tree); - printf("Wonderful.\n"); - parent->item = lookup_commit(head); - parent->next = xmalloc(sizeof(*parent->next)); - parent->next->item = remoteheads->item; - parent->next->next = NULL; - commit_tree(merge_msg.buf, result_tree, parent, result_commit, NULL); - finish(result_commit, "In-index merge"); - drop_save(); - return 0; -} - -static int finish_automerge(struct commit_list *common, - unsigned char *result_tree, - const char *wt_strategy) -{ - struct commit_list *parents = NULL, *j; - struct strbuf buf = STRBUF_INIT; - unsigned char result_commit[20]; - - free_commit_list(common); - if (allow_fast_forward) { - parents = remoteheads; - commit_list_insert(lookup_commit(head), &parents); - parents = reduce_heads(parents); - } else { - struct commit_list **pptr = &parents; - - pptr = &commit_list_insert(lookup_commit(head), - pptr)->next; - for (j = remoteheads; j; j = j->next) - pptr = &commit_list_insert(j->item, pptr)->next; - } - free_commit_list(remoteheads); - strbuf_addch(&merge_msg, '\n'); - commit_tree(merge_msg.buf, result_tree, parents, result_commit, NULL); - strbuf_addf(&buf, "Merge made by %s.", wt_strategy); - finish(result_commit, buf.buf); - strbuf_release(&buf); - drop_save(); - return 0; -} - -static int suggest_conflicts(void) -{ - FILE *fp; - int pos; - - fp = fopen(git_path("MERGE_MSG"), "a"); - if (!fp) - die_errno("Could not open '%s' for writing", - git_path("MERGE_MSG")); - fprintf(fp, "\nConflicts:\n"); - for (pos = 0; pos < active_nr; pos++) { - struct cache_entry *ce = active_cache[pos]; - - if (ce_stage(ce)) { - fprintf(fp, "\t%s\n", ce->name); - while (pos + 1 < active_nr && - !strcmp(ce->name, - active_cache[pos + 1]->name)) - pos++; - } - } - fclose(fp); - rerere(allow_rerere_auto); - printf("Automatic merge failed; " - "fix conflicts and then commit the result.\n"); - return 1; -} - -static struct commit *is_old_style_invocation(int argc, const char **argv) -{ - struct commit *second_token = NULL; - if (argc > 2) { - unsigned char second_sha1[20]; - - if (get_sha1(argv[1], second_sha1)) - return NULL; - second_token = lookup_commit_reference_gently(second_sha1, 0); - if (!second_token) - die("'%s' is not a commit", argv[1]); - if (hashcmp(second_token->object.sha1, head)) - return NULL; - } - return second_token; -} - -static int evaluate_result(void) -{ - int cnt = 0; - struct rev_info rev; - - /* Check how many files differ. */ - init_revisions(&rev, ""); - setup_revisions(0, NULL, &rev, NULL); - rev.diffopt.output_format |= - DIFF_FORMAT_CALLBACK; - rev.diffopt.format_callback = count_diff_files; - rev.diffopt.format_callback_data = &cnt; - run_diff_files(&rev, 0); - - /* - * Check how many unmerged entries are - * there. - */ - cnt += count_unmerged_entries(); - - return cnt; -} - -int cmd_merge(int argc, const char **argv, const char *prefix) -{ - unsigned char result_tree[20]; - struct strbuf buf = STRBUF_INIT; - const char *head_arg; - int flag, head_invalid = 0, i; - int best_cnt = -1, merge_was_ok = 0, automerge_was_ok = 0; - struct commit_list *common = NULL; - const char *best_strategy = NULL, *wt_strategy = NULL; - struct commit_list **remotes = &remoteheads; - - if (read_cache_unmerged()) { - die_resolve_conflict("merge"); - } - if (file_exists(git_path("MERGE_HEAD"))) { - /* - * There is no unmerged entry, don't advise 'git - * add/rm ', just 'git commit'. - */ - if (advice_resolve_conflict) - die("You have not concluded your merge (MERGE_HEAD exists).\n" - "Please, commit your changes before you can merge."); - else - die("You have not concluded your merge (MERGE_HEAD exists)."); - } - - resolve_undo_clear(); - /* - * Check if we are _not_ on a detached HEAD, i.e. if there is a - * current branch. - */ - branch = resolve_ref("HEAD", head, 0, &flag); - if (branch && !prefixcmp(branch, "refs/heads/")) - branch += 11; - if (is_null_sha1(head)) - head_invalid = 1; - - git_config(git_merge_config, NULL); - - /* for color.ui */ - if (diff_use_color_default == -1) - diff_use_color_default = git_use_color_default; - - argc = parse_options(argc, argv, prefix, builtin_merge_options, - builtin_merge_usage, 0); - if (verbosity < 0) - show_diffstat = 0; - - if (squash) { - if (!allow_fast_forward) - die("You cannot combine --squash with --no-ff."); - option_commit = 0; - } - - if (!allow_fast_forward && fast_forward_only) - die("You cannot combine --no-ff with --ff-only."); - - if (!argc) - usage_with_options(builtin_merge_usage, - builtin_merge_options); - - /* - * This could be traditional "merge HEAD ..." and - * the way we can tell it is to see if the second token is HEAD, - * but some people might have misused the interface and used a - * committish that is the same as HEAD there instead. - * Traditional format never would have "-m" so it is an - * additional safety measure to check for it. - */ - - if (!have_message && is_old_style_invocation(argc, argv)) { - strbuf_addstr(&merge_msg, argv[0]); - head_arg = argv[1]; - argv += 2; - argc -= 2; - } else if (head_invalid) { - struct object *remote_head; - /* - * If the merged head is a valid one there is no reason - * to forbid "git merge" into a branch yet to be born. - * We do the same for "git pull". - */ - if (argc != 1) - die("Can merge only exactly one commit into " - "empty head"); - if (squash) - die("Squash commit into empty head not supported yet"); - if (!allow_fast_forward) - die("Non-fast-forward commit does not make sense into " - "an empty head"); - remote_head = peel_to_type(argv[0], 0, NULL, OBJ_COMMIT); - if (!remote_head) - die("%s - not something we can merge", argv[0]); - update_ref("initial pull", "HEAD", remote_head->sha1, NULL, 0, - DIE_ON_ERR); - reset_hard(remote_head->sha1, 0); - return 0; - } else { - struct strbuf msg = STRBUF_INIT; - - /* We are invoked directly as the first-class UI. */ - head_arg = "HEAD"; - - /* - * All the rest are the commits being merged; - * prepare the standard merge summary message to - * be appended to the given message. If remote - * is invalid we will die later in the common - * codepath so we discard the error in this - * loop. - */ - if (!have_message) { - for (i = 0; i < argc; i++) - merge_name(argv[i], &msg); - fmt_merge_msg(option_log, &msg, &merge_msg); - if (merge_msg.len) - strbuf_setlen(&merge_msg, merge_msg.len-1); - } - } - - if (head_invalid || !argc) - usage_with_options(builtin_merge_usage, - builtin_merge_options); - - strbuf_addstr(&buf, "merge"); - for (i = 0; i < argc; i++) - strbuf_addf(&buf, " %s", argv[i]); - setenv("GIT_REFLOG_ACTION", buf.buf, 0); - strbuf_reset(&buf); - - for (i = 0; i < argc; i++) { - struct object *o; - struct commit *commit; - - o = peel_to_type(argv[i], 0, NULL, OBJ_COMMIT); - if (!o) - die("%s - not something we can merge", argv[i]); - commit = lookup_commit(o->sha1); - commit->util = (void *)argv[i]; - remotes = &commit_list_insert(commit, remotes)->next; - - strbuf_addf(&buf, "GITHEAD_%s", sha1_to_hex(o->sha1)); - setenv(buf.buf, argv[i], 1); - strbuf_reset(&buf); - } - - if (!use_strategies) { - if (!remoteheads->next) - add_strategies(pull_twohead, DEFAULT_TWOHEAD); - else - add_strategies(pull_octopus, DEFAULT_OCTOPUS); - } - - for (i = 0; i < use_strategies_nr; i++) { - if (use_strategies[i]->attr & NO_FAST_FORWARD) - allow_fast_forward = 0; - if (use_strategies[i]->attr & NO_TRIVIAL) - allow_trivial = 0; - } - - if (!remoteheads->next) - common = get_merge_bases(lookup_commit(head), - remoteheads->item, 1); - else { - struct commit_list *list = remoteheads; - commit_list_insert(lookup_commit(head), &list); - common = get_octopus_merge_bases(list); - free(list); - } - - update_ref("updating ORIG_HEAD", "ORIG_HEAD", head, NULL, 0, - DIE_ON_ERR); - - if (!common) - ; /* No common ancestors found. We need a real merge. */ - else if (!remoteheads->next && !common->next && - common->item == remoteheads->item) { - /* - * If head can reach all the merge then we are up to date. - * but first the most common case of merging one remote. - */ - finish_up_to_date("Already up-to-date."); - return 0; - } else if (allow_fast_forward && !remoteheads->next && - !common->next && - !hashcmp(common->item->object.sha1, head)) { - /* Again the most common case of merging one remote. */ - struct strbuf msg = STRBUF_INIT; - struct object *o; - char hex[41]; - - strcpy(hex, find_unique_abbrev(head, DEFAULT_ABBREV)); - - if (verbosity >= 0) - printf("Updating %s..%s\n", - hex, - find_unique_abbrev(remoteheads->item->object.sha1, - DEFAULT_ABBREV)); - strbuf_addstr(&msg, "Fast-forward"); - if (have_message) - strbuf_addstr(&msg, - " (no commit created; -m option ignored)"); - o = peel_to_type(sha1_to_hex(remoteheads->item->object.sha1), - 0, NULL, OBJ_COMMIT); - if (!o) - return 1; - - if (checkout_fast_forward(head, remoteheads->item->object.sha1)) - return 1; - - finish(o->sha1, msg.buf); - drop_save(); - return 0; - } else if (!remoteheads->next && common->next) - ; - /* - * We are not doing octopus and not fast-forward. Need - * a real merge. - */ - else if (!remoteheads->next && !common->next && option_commit) { - /* - * We are not doing octopus, not fast-forward, and have - * only one common. - */ - refresh_cache(REFRESH_QUIET); - if (allow_trivial && !fast_forward_only) { - /* See if it is really trivial. */ - git_committer_info(IDENT_ERROR_ON_NO_NAME); - printf("Trying really trivial in-index merge...\n"); - if (!read_tree_trivial(common->item->object.sha1, - head, remoteheads->item->object.sha1)) - return merge_trivial(); - printf("Nope.\n"); - } - } else { - /* - * An octopus. If we can reach all the remote we are up - * to date. - */ - int up_to_date = 1; - struct commit_list *j; - - for (j = remoteheads; j; j = j->next) { - struct commit_list *common_one; - - /* - * Here we *have* to calculate the individual - * merge_bases again, otherwise "git merge HEAD^ - * HEAD^^" would be missed. - */ - common_one = get_merge_bases(lookup_commit(head), - j->item, 1); - if (hashcmp(common_one->item->object.sha1, - j->item->object.sha1)) { - up_to_date = 0; - break; - } - } - if (up_to_date) { - finish_up_to_date("Already up-to-date. Yeeah!"); - return 0; - } - } - - if (fast_forward_only) - die("Not possible to fast-forward, aborting."); - - /* We are going to make a new commit. */ - git_committer_info(IDENT_ERROR_ON_NO_NAME); - - /* - * At this point, we need a real merge. No matter what strategy - * we use, it would operate on the index, possibly affecting the - * working tree, and when resolved cleanly, have the desired - * tree in the index -- this means that the index must be in - * sync with the head commit. The strategies are responsible - * to ensure this. - */ - if (use_strategies_nr != 1) { - /* - * Stash away the local changes so that we can try more - * than one. - */ - save_state(); - } else { - memcpy(stash, null_sha1, 20); - } - - for (i = 0; i < use_strategies_nr; i++) { - int ret; - if (i) { - printf("Rewinding the tree to pristine...\n"); - restore_state(); - } - if (use_strategies_nr != 1) - printf("Trying merge strategy %s...\n", - use_strategies[i]->name); - /* - * Remember which strategy left the state in the working - * tree. - */ - wt_strategy = use_strategies[i]->name; - - ret = try_merge_strategy(use_strategies[i]->name, - common, head_arg); - if (!option_commit && !ret) { - merge_was_ok = 1; - /* - * This is necessary here just to avoid writing - * the tree, but later we will *not* exit with - * status code 1 because merge_was_ok is set. - */ - ret = 1; - } - - if (ret) { - /* - * The backend exits with 1 when conflicts are - * left to be resolved, with 2 when it does not - * handle the given merge at all. - */ - if (ret == 1) { - int cnt = evaluate_result(); - - if (best_cnt <= 0 || cnt <= best_cnt) { - best_strategy = use_strategies[i]->name; - best_cnt = cnt; - } - } - if (merge_was_ok) - break; - else - continue; - } - - /* Automerge succeeded. */ - write_tree_trivial(result_tree); - automerge_was_ok = 1; - break; - } - - /* - * If we have a resulting tree, that means the strategy module - * auto resolved the merge cleanly. - */ - if (automerge_was_ok) - return finish_automerge(common, result_tree, wt_strategy); - - /* - * Pick the result from the best strategy and have the user fix - * it up. - */ - if (!best_strategy) { - restore_state(); - if (use_strategies_nr > 1) - fprintf(stderr, - "No merge strategy handled the merge.\n"); - else - fprintf(stderr, "Merge with strategy %s failed.\n", - use_strategies[0]->name); - return 2; - } else if (best_strategy == wt_strategy) - ; /* We already have its result in the working tree. */ - else { - printf("Rewinding the tree to pristine...\n"); - restore_state(); - printf("Using the %s to prepare resolving by hand.\n", - best_strategy); - try_merge_strategy(best_strategy, common, head_arg); - } - - if (squash) - finish(NULL, NULL); - else { - int fd; - struct commit_list *j; - - for (j = remoteheads; j; j = j->next) - strbuf_addf(&buf, "%s\n", - sha1_to_hex(j->item->object.sha1)); - fd = open(git_path("MERGE_HEAD"), O_WRONLY | O_CREAT, 0666); - if (fd < 0) - die_errno("Could not open '%s' for writing", - git_path("MERGE_HEAD")); - if (write_in_full(fd, buf.buf, buf.len) != buf.len) - die_errno("Could not write to '%s'", git_path("MERGE_HEAD")); - close(fd); - strbuf_addch(&merge_msg, '\n'); - fd = open(git_path("MERGE_MSG"), O_WRONLY | O_CREAT, 0666); - if (fd < 0) - die_errno("Could not open '%s' for writing", - git_path("MERGE_MSG")); - if (write_in_full(fd, merge_msg.buf, merge_msg.len) != - merge_msg.len) - die_errno("Could not write to '%s'", git_path("MERGE_MSG")); - close(fd); - fd = open(git_path("MERGE_MODE"), O_WRONLY | O_CREAT | O_TRUNC, 0666); - if (fd < 0) - die_errno("Could not open '%s' for writing", - git_path("MERGE_MODE")); - strbuf_reset(&buf); - if (!allow_fast_forward) - strbuf_addf(&buf, "no-ff"); - if (write_in_full(fd, buf.buf, buf.len) != buf.len) - die_errno("Could not write to '%s'", git_path("MERGE_MODE")); - close(fd); - } - - if (merge_was_ok) { - fprintf(stderr, "Automatic merge went well; " - "stopped before committing as requested\n"); - return 0; - } else - return suggest_conflicts(); -} diff --git a/builtin-mktag.c b/builtin-mktag.c deleted file mode 100644 index 1cb0f3f..0000000 --- a/builtin-mktag.c +++ /dev/null @@ -1,179 +0,0 @@ -#include "cache.h" -#include "tag.h" -#include "exec_cmd.h" - -/* - * A signature file has a very simple fixed format: four lines - * of "object " + "type " + "tag " + - * "tagger ", followed by a blank line, a free-form tag - * message and a signature block that git itself doesn't care about, - * but that can be verified with gpg or similar. - * - * The first four lines are guaranteed to be at least 83 bytes: - * "object \n" is 48 bytes, "type tag\n" at 9 bytes is the - * shortest possible type-line, "tag .\n" at 6 bytes is the shortest - * single-character-tag line, and "tagger . <> 0 +0000\n" at 20 bytes is - * the shortest possible tagger-line. - */ - -/* - * We refuse to tag something we can't verify. Just because. - */ -static int verify_object(const unsigned char *sha1, const char *expected_type) -{ - int ret = -1; - enum object_type type; - unsigned long size; - const unsigned char *repl; - void *buffer = read_sha1_file_repl(sha1, &type, &size, &repl); - - if (buffer) { - if (type == type_from_string(expected_type)) - ret = check_sha1_signature(repl, buffer, size, expected_type); - free(buffer); - } - return ret; -} - -#ifdef NO_C99_FORMAT -#define PD_FMT "%d" -#else -#define PD_FMT "%td" -#endif - -static int verify_tag(char *buffer, unsigned long size) -{ - int typelen; - char type[20]; - unsigned char sha1[20]; - const char *object, *type_line, *tag_line, *tagger_line, *lb, *rb; - size_t len; - - if (size < 84) - return error("wanna fool me ? you obviously got the size wrong !"); - - buffer[size] = 0; - - /* Verify object line */ - object = buffer; - if (memcmp(object, "object ", 7)) - return error("char%d: does not start with \"object \"", 0); - - if (get_sha1_hex(object + 7, sha1)) - return error("char%d: could not get SHA1 hash", 7); - - /* Verify type line */ - type_line = object + 48; - if (memcmp(type_line - 1, "\ntype ", 6)) - return error("char%d: could not find \"\\ntype \"", 47); - - /* Verify tag-line */ - tag_line = strchr(type_line, '\n'); - if (!tag_line) - return error("char" PD_FMT ": could not find next \"\\n\"", type_line - buffer); - tag_line++; - if (memcmp(tag_line, "tag ", 4) || tag_line[4] == '\n') - return error("char" PD_FMT ": no \"tag \" found", tag_line - buffer); - - /* Get the actual type */ - typelen = tag_line - type_line - strlen("type \n"); - if (typelen >= sizeof(type)) - return error("char" PD_FMT ": type too long", type_line+5 - buffer); - - memcpy(type, type_line+5, typelen); - type[typelen] = 0; - - /* Verify that the object matches */ - if (verify_object(sha1, type)) - return error("char%d: could not verify object %s", 7, sha1_to_hex(sha1)); - - /* Verify the tag-name: we don't allow control characters or spaces in it */ - tag_line += 4; - for (;;) { - unsigned char c = *tag_line++; - if (c == '\n') - break; - if (c > ' ') - continue; - return error("char" PD_FMT ": could not verify tag name", tag_line - buffer); - } - - /* Verify the tagger line */ - tagger_line = tag_line; - - if (memcmp(tagger_line, "tagger ", 7)) - return error("char" PD_FMT ": could not find \"tagger \"", - tagger_line - buffer); - - /* - * Check for correct form for name and email - * i.e. " <" followed by "> " on _this_ line - * No angle brackets within the name or email address fields. - * No spaces within the email address field. - */ - tagger_line += 7; - if (!(lb = strstr(tagger_line, " <")) || !(rb = strstr(lb+2, "> ")) || - strpbrk(tagger_line, "<>\n") != lb+1 || - strpbrk(lb+2, "><\n ") != rb) - return error("char" PD_FMT ": malformed tagger field", - tagger_line - buffer); - - /* Check for author name, at least one character, space is acceptable */ - if (lb == tagger_line) - return error("char" PD_FMT ": missing tagger name", - tagger_line - buffer); - - /* timestamp, 1 or more digits followed by space */ - tagger_line = rb + 2; - if (!(len = strspn(tagger_line, "0123456789"))) - return error("char" PD_FMT ": missing tag timestamp", - tagger_line - buffer); - tagger_line += len; - if (*tagger_line != ' ') - return error("char" PD_FMT ": malformed tag timestamp", - tagger_line - buffer); - tagger_line++; - - /* timezone, 5 digits [+-]hhmm, max. 1400 */ - if (!((tagger_line[0] == '+' || tagger_line[0] == '-') && - strspn(tagger_line+1, "0123456789") == 4 && - tagger_line[5] == '\n' && atoi(tagger_line+1) <= 1400)) - return error("char" PD_FMT ": malformed tag timezone", - tagger_line - buffer); - tagger_line += 6; - - /* Verify the blank line separating the header from the body */ - if (*tagger_line != '\n') - return error("char" PD_FMT ": trailing garbage in tag header", - tagger_line - buffer); - - /* The actual stuff afterwards we don't care about.. */ - return 0; -} - -#undef PD_FMT - -int cmd_mktag(int argc, const char **argv, const char *prefix) -{ - struct strbuf buf = STRBUF_INIT; - unsigned char result_sha1[20]; - - if (argc != 1) - usage("git mktag < signaturefile"); - - if (strbuf_read(&buf, 0, 4096) < 0) { - die_errno("could not read from stdin"); - } - - /* Verify it for some basic sanity: it needs to start with - "object \ntype\ntagger " */ - if (verify_tag(buf.buf, buf.len) < 0) - die("invalid tag signature file"); - - if (write_sha1_file(buf.buf, buf.len, tag_type, result_sha1) < 0) - die("unable to write tag file"); - - strbuf_release(&buf); - printf("%s\n", sha1_to_hex(result_sha1)); - return 0; -} diff --git a/builtin-mktree.c b/builtin-mktree.c deleted file mode 100644 index 098395f..0000000 --- a/builtin-mktree.c +++ /dev/null @@ -1,190 +0,0 @@ -/* - * GIT - the stupid content tracker - * - * Copyright (c) Junio C Hamano, 2006, 2009 - */ -#include "builtin.h" -#include "quote.h" -#include "tree.h" -#include "parse-options.h" - -static struct treeent { - unsigned mode; - unsigned char sha1[20]; - int len; - char name[FLEX_ARRAY]; -} **entries; -static int alloc, used; - -static void append_to_tree(unsigned mode, unsigned char *sha1, char *path) -{ - struct treeent *ent; - int len = strlen(path); - if (strchr(path, '/')) - die("path %s contains slash", path); - - if (alloc <= used) { - alloc = alloc_nr(used); - entries = xrealloc(entries, sizeof(*entries) * alloc); - } - ent = entries[used++] = xmalloc(sizeof(**entries) + len + 1); - ent->mode = mode; - ent->len = len; - hashcpy(ent->sha1, sha1); - memcpy(ent->name, path, len+1); -} - -static int ent_compare(const void *a_, const void *b_) -{ - struct treeent *a = *(struct treeent **)a_; - struct treeent *b = *(struct treeent **)b_; - return base_name_compare(a->name, a->len, a->mode, - b->name, b->len, b->mode); -} - -static void write_tree(unsigned char *sha1) -{ - struct strbuf buf; - size_t size; - int i; - - qsort(entries, used, sizeof(*entries), ent_compare); - for (size = i = 0; i < used; i++) - size += 32 + entries[i]->len; - - strbuf_init(&buf, size); - for (i = 0; i < used; i++) { - struct treeent *ent = entries[i]; - strbuf_addf(&buf, "%o %s%c", ent->mode, ent->name, '\0'); - strbuf_add(&buf, ent->sha1, 20); - } - - write_sha1_file(buf.buf, buf.len, tree_type, sha1); -} - -static const char *mktree_usage[] = { - "git mktree [-z] [--missing] [--batch]", - NULL -}; - -static void mktree_line(char *buf, size_t len, int line_termination, int allow_missing) -{ - char *ptr, *ntr; - unsigned mode; - enum object_type mode_type; /* object type derived from mode */ - enum object_type obj_type; /* object type derived from sha */ - char *path; - unsigned char sha1[20]; - - ptr = buf; - /* - * Read non-recursive ls-tree output format: - * mode SP type SP sha1 TAB name - */ - mode = strtoul(ptr, &ntr, 8); - if (ptr == ntr || !ntr || *ntr != ' ') - die("input format error: %s", buf); - ptr = ntr + 1; /* type */ - ntr = strchr(ptr, ' '); - if (!ntr || buf + len <= ntr + 40 || - ntr[41] != '\t' || - get_sha1_hex(ntr + 1, sha1)) - die("input format error: %s", buf); - - /* It is perfectly normal if we do not have a commit from a submodule */ - if (S_ISGITLINK(mode)) - allow_missing = 1; - - - *ntr++ = 0; /* now at the beginning of SHA1 */ - - path = ntr + 41; /* at the beginning of name */ - if (line_termination && path[0] == '"') { - struct strbuf p_uq = STRBUF_INIT; - if (unquote_c_style(&p_uq, path, NULL)) - die("invalid quoting"); - path = strbuf_detach(&p_uq, NULL); - } - - /* - * Object type is redundantly derivable three ways. - * These should all agree. - */ - mode_type = object_type(mode); - if (mode_type != type_from_string(ptr)) { - die("entry '%s' object type (%s) doesn't match mode type (%s)", - path, ptr, typename(mode_type)); - } - - /* Check the type of object identified by sha1 */ - obj_type = sha1_object_info(sha1, NULL); - if (obj_type < 0) { - if (allow_missing) { - ; /* no problem - missing objects are presumed to be of the right type */ - } else { - die("entry '%s' object %s is unavailable", path, sha1_to_hex(sha1)); - } - } else { - if (obj_type != mode_type) { - /* - * The object exists but is of the wrong type. - * This is a problem regardless of allow_missing - * because the new tree entry will never be correct. - */ - die("entry '%s' object %s is a %s but specified type was (%s)", - path, sha1_to_hex(sha1), typename(obj_type), typename(mode_type)); - } - } - - append_to_tree(mode, sha1, path); -} - -int cmd_mktree(int ac, const char **av, const char *prefix) -{ - struct strbuf sb = STRBUF_INIT; - unsigned char sha1[20]; - int line_termination = '\n'; - int allow_missing = 0; - int is_batch_mode = 0; - int got_eof = 0; - - const struct option option[] = { - OPT_SET_INT('z', NULL, &line_termination, "input is NUL terminated", '\0'), - OPT_SET_INT( 0 , "missing", &allow_missing, "allow missing objects", 1), - OPT_SET_INT( 0 , "batch", &is_batch_mode, "allow creation of more than one tree", 1), - OPT_END() - }; - - ac = parse_options(ac, av, prefix, option, mktree_usage, 0); - - while (!got_eof) { - while (1) { - if (strbuf_getline(&sb, stdin, line_termination) == EOF) { - got_eof = 1; - break; - } - if (sb.buf[0] == '\0') { - /* empty lines denote tree boundaries in batch mode */ - if (is_batch_mode) - break; - die("input format error: (blank line only valid in batch mode)"); - } - mktree_line(sb.buf, sb.len, line_termination, allow_missing); - } - if (is_batch_mode && got_eof && used < 1) { - /* - * Execution gets here if the last tree entry is terminated with a - * new-line. The final new-line has been made optional to be - * consistent with the original non-batch behaviour of mktree. - */ - ; /* skip creating an empty tree */ - } else { - write_tree(sha1); - puts(sha1_to_hex(sha1)); - fflush(stdout); - } - used=0; /* reset tree entry buffer for re-use in batch mode */ - } - strbuf_release(&sb); - exit(0); -} diff --git a/builtin-mv.c b/builtin-mv.c deleted file mode 100644 index c07f53b..0000000 --- a/builtin-mv.c +++ /dev/null @@ -1,227 +0,0 @@ -/* - * "git mv" builtin command - * - * Copyright (C) 2006 Johannes Schindelin - */ -#include "cache.h" -#include "builtin.h" -#include "dir.h" -#include "cache-tree.h" -#include "string-list.h" -#include "parse-options.h" - -static const char * const builtin_mv_usage[] = { - "git mv [options] ... ", - NULL -}; - -static const char **copy_pathspec(const char *prefix, const char **pathspec, - int count, int base_name) -{ - int i; - const char **result = xmalloc((count + 1) * sizeof(const char *)); - memcpy(result, pathspec, count * sizeof(const char *)); - result[count] = NULL; - for (i = 0; i < count; i++) { - int length = strlen(result[i]); - int to_copy = length; - while (to_copy > 0 && is_dir_sep(result[i][to_copy - 1])) - to_copy--; - if (to_copy != length || base_name) { - char *it = xmemdupz(result[i], to_copy); - result[i] = base_name ? strdup(basename(it)) : it; - } - } - return get_pathspec(prefix, result); -} - -static const char *add_slash(const char *path) -{ - int len = strlen(path); - if (path[len - 1] != '/') { - char *with_slash = xmalloc(len + 2); - memcpy(with_slash, path, len); - with_slash[len++] = '/'; - with_slash[len] = 0; - return with_slash; - } - return path; -} - -static struct lock_file lock_file; - -int cmd_mv(int argc, const char **argv, const char *prefix) -{ - int i, newfd; - int verbose = 0, show_only = 0, force = 0, ignore_errors = 0; - struct option builtin_mv_options[] = { - OPT__DRY_RUN(&show_only), - OPT_BOOLEAN('f', "force", &force, "force move/rename even if target exists"), - OPT_BOOLEAN('k', NULL, &ignore_errors, "skip move/rename errors"), - OPT_END(), - }; - const char **source, **destination, **dest_path; - enum update_mode { BOTH = 0, WORKING_DIRECTORY, INDEX } *modes; - struct stat st; - struct string_list src_for_dst = {NULL, 0, 0, 0}; - - git_config(git_default_config, NULL); - - argc = parse_options(argc, argv, prefix, builtin_mv_options, - builtin_mv_usage, 0); - if (--argc < 1) - usage_with_options(builtin_mv_usage, builtin_mv_options); - - newfd = hold_locked_index(&lock_file, 1); - if (read_cache() < 0) - die("index file corrupt"); - - source = copy_pathspec(prefix, argv, argc, 0); - modes = xcalloc(argc, sizeof(enum update_mode)); - dest_path = copy_pathspec(prefix, argv + argc, 1, 0); - - if (dest_path[0][0] == '\0') - /* special case: "." was normalized to "" */ - destination = copy_pathspec(dest_path[0], argv, argc, 1); - else if (!lstat(dest_path[0], &st) && - S_ISDIR(st.st_mode)) { - dest_path[0] = add_slash(dest_path[0]); - destination = copy_pathspec(dest_path[0], argv, argc, 1); - } else { - if (argc != 1) - usage_with_options(builtin_mv_usage, builtin_mv_options); - destination = dest_path; - } - - /* Checking */ - for (i = 0; i < argc; i++) { - const char *src = source[i], *dst = destination[i]; - int length, src_is_dir; - const char *bad = NULL; - - if (show_only) - printf("Checking rename of '%s' to '%s'\n", src, dst); - - length = strlen(src); - if (lstat(src, &st) < 0) - bad = "bad source"; - else if (!strncmp(src, dst, length) && - (dst[length] == 0 || dst[length] == '/')) { - bad = "can not move directory into itself"; - } else if ((src_is_dir = S_ISDIR(st.st_mode)) - && lstat(dst, &st) == 0) - bad = "cannot move directory over file"; - else if (src_is_dir) { - const char *src_w_slash = add_slash(src); - int len_w_slash = length + 1; - int first, last; - - modes[i] = WORKING_DIRECTORY; - - first = cache_name_pos(src_w_slash, len_w_slash); - if (first >= 0) - die ("Huh? %.*s is in index?", - len_w_slash, src_w_slash); - - first = -1 - first; - for (last = first; last < active_nr; last++) { - const char *path = active_cache[last]->name; - if (strncmp(path, src_w_slash, len_w_slash)) - break; - } - free((char *)src_w_slash); - - if (last - first < 1) - bad = "source directory is empty"; - else { - int j, dst_len; - - if (last - first > 0) { - source = xrealloc(source, - (argc + last - first) - * sizeof(char *)); - destination = xrealloc(destination, - (argc + last - first) - * sizeof(char *)); - modes = xrealloc(modes, - (argc + last - first) - * sizeof(enum update_mode)); - } - - dst = add_slash(dst); - dst_len = strlen(dst); - - for (j = 0; j < last - first; j++) { - const char *path = - active_cache[first + j]->name; - source[argc + j] = path; - destination[argc + j] = - prefix_path(dst, dst_len, - path + length + 1); - modes[argc + j] = INDEX; - } - argc += last - first; - } - } else if (cache_name_pos(src, length) < 0) - bad = "not under version control"; - else if (lstat(dst, &st) == 0) { - bad = "destination exists"; - if (force) { - /* - * only files can overwrite each other: - * check both source and destination - */ - if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) { - warning("%s; will overwrite!", bad); - bad = NULL; - } else - bad = "Cannot overwrite"; - } - } else if (string_list_has_string(&src_for_dst, dst)) - bad = "multiple sources for the same target"; - else - string_list_insert(dst, &src_for_dst); - - if (bad) { - if (ignore_errors) { - if (--argc > 0) { - memmove(source + i, source + i + 1, - (argc - i) * sizeof(char *)); - memmove(destination + i, - destination + i + 1, - (argc - i) * sizeof(char *)); - i--; - } - } else - die ("%s, source=%s, destination=%s", - bad, src, dst); - } - } - - for (i = 0; i < argc; i++) { - const char *src = source[i], *dst = destination[i]; - enum update_mode mode = modes[i]; - int pos; - if (show_only || verbose) - printf("Renaming %s to %s\n", src, dst); - if (!show_only && mode != INDEX && - rename(src, dst) < 0 && !ignore_errors) - die_errno ("renaming '%s' failed", src); - - if (mode == WORKING_DIRECTORY) - continue; - - pos = cache_name_pos(src, strlen(src)); - assert(pos >= 0); - if (!show_only) - rename_cache_entry_at(pos, dst); - } - - if (active_cache_changed) { - if (write_cache(newfd, active_cache, active_nr) || - commit_locked_index(&lock_file)) - die("Unable to write new index file"); - } - - return 0; -} diff --git a/builtin-name-rev.c b/builtin-name-rev.c deleted file mode 100644 index 06a38ac..0000000 --- a/builtin-name-rev.c +++ /dev/null @@ -1,305 +0,0 @@ -#include "builtin.h" -#include "cache.h" -#include "commit.h" -#include "tag.h" -#include "refs.h" -#include "parse-options.h" - -#define CUTOFF_DATE_SLOP 86400 /* one day */ - -typedef struct rev_name { - const char *tip_name; - int generation; - int distance; -} rev_name; - -static long cutoff = LONG_MAX; - -/* How many generations are maximally preferred over _one_ merge traversal? */ -#define MERGE_TRAVERSAL_WEIGHT 65535 - -static void name_rev(struct commit *commit, - const char *tip_name, int generation, int distance, - int deref) -{ - struct rev_name *name = (struct rev_name *)commit->util; - struct commit_list *parents; - int parent_number = 1; - - if (!commit->object.parsed) - parse_commit(commit); - - if (commit->date < cutoff) - return; - - if (deref) { - char *new_name = xmalloc(strlen(tip_name)+3); - strcpy(new_name, tip_name); - strcat(new_name, "^0"); - tip_name = new_name; - - if (generation) - die("generation: %d, but deref?", generation); - } - - if (name == NULL) { - name = xmalloc(sizeof(rev_name)); - commit->util = name; - goto copy_data; - } else if (name->distance > distance) { -copy_data: - name->tip_name = tip_name; - name->generation = generation; - name->distance = distance; - } else - return; - - for (parents = commit->parents; - parents; - parents = parents->next, parent_number++) { - if (parent_number > 1) { - int len = strlen(tip_name); - char *new_name = xmalloc(len + - 1 + decimal_length(generation) + /* ~ */ - 1 + 2 + /* ^NN */ - 1); - - if (len > 2 && !strcmp(tip_name + len - 2, "^0")) - len -= 2; - if (generation > 0) - sprintf(new_name, "%.*s~%d^%d", len, tip_name, - generation, parent_number); - else - sprintf(new_name, "%.*s^%d", len, tip_name, - parent_number); - - name_rev(parents->item, new_name, 0, - distance + MERGE_TRAVERSAL_WEIGHT, 0); - } else { - name_rev(parents->item, tip_name, generation + 1, - distance + 1, 0); - } - } -} - -struct name_ref_data { - int tags_only; - int name_only; - const char *ref_filter; -}; - -static int name_ref(const char *path, const unsigned char *sha1, int flags, void *cb_data) -{ - struct object *o = parse_object(sha1); - struct name_ref_data *data = cb_data; - int deref = 0; - - if (data->tags_only && prefixcmp(path, "refs/tags/")) - return 0; - - if (data->ref_filter && fnmatch(data->ref_filter, path, 0)) - return 0; - - while (o && o->type == OBJ_TAG) { - struct tag *t = (struct tag *) o; - if (!t->tagged) - break; /* broken repository */ - o = parse_object(t->tagged->sha1); - deref = 1; - } - if (o && o->type == OBJ_COMMIT) { - struct commit *commit = (struct commit *)o; - - if (!prefixcmp(path, "refs/heads/")) - path = path + 11; - else if (data->tags_only - && data->name_only - && !prefixcmp(path, "refs/tags/")) - path = path + 10; - else if (!prefixcmp(path, "refs/")) - path = path + 5; - - name_rev(commit, xstrdup(path), 0, 0, deref); - } - return 0; -} - -/* returns a static buffer */ -static const char *get_rev_name(const struct object *o) -{ - static char buffer[1024]; - struct rev_name *n; - struct commit *c; - - if (o->type != OBJ_COMMIT) - return NULL; - c = (struct commit *) o; - n = c->util; - if (!n) - return NULL; - - if (!n->generation) - return n->tip_name; - else { - int len = strlen(n->tip_name); - if (len > 2 && !strcmp(n->tip_name + len - 2, "^0")) - len -= 2; - snprintf(buffer, sizeof(buffer), "%.*s~%d", len, n->tip_name, - n->generation); - - return buffer; - } -} - -static void show_name(const struct object *obj, - const char *caller_name, - int always, int allow_undefined, int name_only) -{ - const char *name; - const unsigned char *sha1 = obj->sha1; - - if (!name_only) - printf("%s ", caller_name ? caller_name : sha1_to_hex(sha1)); - name = get_rev_name(obj); - if (name) - printf("%s\n", name); - else if (allow_undefined) - printf("undefined\n"); - else if (always) - printf("%s\n", find_unique_abbrev(sha1, DEFAULT_ABBREV)); - else - die("cannot describe '%s'", sha1_to_hex(sha1)); -} - -static char const * const name_rev_usage[] = { - "git name-rev [options] ( --all | --stdin | ... )", - NULL -}; - -static void name_rev_line(char *p, struct name_ref_data *data) -{ - int forty = 0; - char *p_start; - for (p_start = p; *p; p++) { -#define ishex(x) (isdigit((x)) || ((x) >= 'a' && (x) <= 'f')) - if (!ishex(*p)) - forty = 0; - else if (++forty == 40 && - !ishex(*(p+1))) { - unsigned char sha1[40]; - const char *name = NULL; - char c = *(p+1); - int p_len = p - p_start + 1; - - forty = 0; - - *(p+1) = 0; - if (!get_sha1(p - 39, sha1)) { - struct object *o = - lookup_object(sha1); - if (o) - name = get_rev_name(o); - } - *(p+1) = c; - - if (!name) - continue; - - if (data->name_only) - printf("%.*s%s", p_len - 40, p_start, name); - else - printf("%.*s (%s)", p_len, p_start, name); - p_start = p + 1; - } - } - - /* flush */ - if (p_start != p) - fwrite(p_start, p - p_start, 1, stdout); -} - -int cmd_name_rev(int argc, const char **argv, const char *prefix) -{ - struct object_array revs = { 0, 0, NULL }; - int all = 0, transform_stdin = 0, allow_undefined = 1, always = 0; - struct name_ref_data data = { 0, 0, NULL }; - struct option opts[] = { - OPT_BOOLEAN(0, "name-only", &data.name_only, "print only names (no SHA-1)"), - OPT_BOOLEAN(0, "tags", &data.tags_only, "only use tags to name the commits"), - OPT_STRING(0, "refs", &data.ref_filter, "pattern", - "only use refs matching "), - OPT_GROUP(""), - OPT_BOOLEAN(0, "all", &all, "list all commits reachable from all refs"), - OPT_BOOLEAN(0, "stdin", &transform_stdin, "read from stdin"), - OPT_BOOLEAN(0, "undefined", &allow_undefined, "allow to print `undefined` names"), - OPT_BOOLEAN(0, "always", &always, - "show abbreviated commit object as fallback"), - OPT_END(), - }; - - git_config(git_default_config, NULL); - argc = parse_options(argc, argv, prefix, opts, name_rev_usage, 0); - if (!!all + !!transform_stdin + !!argc > 1) { - error("Specify either a list, or --all, not both!"); - usage_with_options(name_rev_usage, opts); - } - if (all || transform_stdin) - cutoff = 0; - - for (; argc; argc--, argv++) { - unsigned char sha1[20]; - struct object *o; - struct commit *commit; - - if (get_sha1(*argv, sha1)) { - fprintf(stderr, "Could not get sha1 for %s. Skipping.\n", - *argv); - continue; - } - - o = deref_tag(parse_object(sha1), *argv, 0); - if (!o || o->type != OBJ_COMMIT) { - fprintf(stderr, "Could not get commit for %s. Skipping.\n", - *argv); - continue; - } - - commit = (struct commit *)o; - if (cutoff > commit->date) - cutoff = commit->date; - add_object_array((struct object *)commit, *argv, &revs); - } - - if (cutoff) - cutoff = cutoff - CUTOFF_DATE_SLOP; - for_each_ref(name_ref, &data); - - if (transform_stdin) { - char buffer[2048]; - - while (!feof(stdin)) { - char *p = fgets(buffer, sizeof(buffer), stdin); - if (!p) - break; - name_rev_line(p, &data); - } - } else if (all) { - int i, max; - - max = get_max_object_index(); - for (i = 0; i < max; i++) { - struct object *obj = get_indexed_object(i); - if (!obj) - continue; - show_name(obj, NULL, - always, allow_undefined, data.name_only); - } - } else { - int i; - for (i = 0; i < revs.nr; i++) - show_name(revs.objects[i].item, revs.objects[i].name, - always, allow_undefined, data.name_only); - } - - return 0; -} diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c deleted file mode 100644 index e1d3adf..0000000 --- a/builtin-pack-objects.c +++ /dev/null @@ -1,2375 +0,0 @@ -#include "builtin.h" -#include "cache.h" -#include "attr.h" -#include "object.h" -#include "blob.h" -#include "commit.h" -#include "tag.h" -#include "tree.h" -#include "delta.h" -#include "pack.h" -#include "pack-revindex.h" -#include "csum-file.h" -#include "tree-walk.h" -#include "diff.h" -#include "revision.h" -#include "list-objects.h" -#include "progress.h" -#include "refs.h" - -#ifndef NO_PTHREADS -#include "thread-utils.h" -#include -#endif - -static const char pack_usage[] = - "git pack-objects [{ -q | --progress | --all-progress }]\n" - " [--all-progress-implied]\n" - " [--max-pack-size=N] [--local] [--incremental]\n" - " [--window=N] [--window-memory=N] [--depth=N]\n" - " [--no-reuse-delta] [--no-reuse-object] [--delta-base-offset]\n" - " [--threads=N] [--non-empty] [--revs [--unpacked | --all]*]\n" - " [--reflog] [--stdout | base-name] [--include-tag]\n" - " [--keep-unreachable | --unpack-unreachable \n" - " [idx.sha1, &type, &size); - if (!buf) - die("unable to read %s", sha1_to_hex(entry->idx.sha1)); - base_buf = read_sha1_file(entry->delta->idx.sha1, &type, &base_size); - if (!base_buf) - die("unable to read %s", sha1_to_hex(entry->delta->idx.sha1)); - delta_buf = diff_delta(base_buf, base_size, - buf, size, &delta_size, 0); - if (!delta_buf || delta_size != entry->delta_size) - die("delta size changed"); - free(buf); - free(base_buf); - return delta_buf; -} - -static unsigned long do_compress(void **pptr, unsigned long size) -{ - z_stream stream; - void *in, *out; - unsigned long maxsize; - - memset(&stream, 0, sizeof(stream)); - deflateInit(&stream, pack_compression_level); - maxsize = deflateBound(&stream, size); - - in = *pptr; - out = xmalloc(maxsize); - *pptr = out; - - stream.next_in = in; - stream.avail_in = size; - stream.next_out = out; - stream.avail_out = maxsize; - while (deflate(&stream, Z_FINISH) == Z_OK) - ; /* nothing */ - deflateEnd(&stream); - - free(in); - return stream.total_out; -} - -/* - * The per-object header is a pretty dense thing, which is - * - first byte: low four bits are "size", then three bits of "type", - * and the high bit is "size continues". - * - each byte afterwards: low seven bits are size continuation, - * with the high bit being "size continues" - */ -static int encode_header(enum object_type type, unsigned long size, unsigned char *hdr) -{ - int n = 1; - unsigned char c; - - if (type < OBJ_COMMIT || type > OBJ_REF_DELTA) - die("bad type %d", type); - - c = (type << 4) | (size & 15); - size >>= 4; - while (size) { - *hdr++ = c | 0x80; - c = size & 0x7f; - size >>= 7; - n++; - } - *hdr = c; - return n; -} - -/* - * we are going to reuse the existing object data as is. make - * sure it is not corrupt. - */ -static int check_pack_inflate(struct packed_git *p, - struct pack_window **w_curs, - off_t offset, - off_t len, - unsigned long expect) -{ - z_stream stream; - unsigned char fakebuf[4096], *in; - int st; - - memset(&stream, 0, sizeof(stream)); - git_inflate_init(&stream); - do { - in = use_pack(p, w_curs, offset, &stream.avail_in); - stream.next_in = in; - stream.next_out = fakebuf; - stream.avail_out = sizeof(fakebuf); - st = git_inflate(&stream, Z_FINISH); - offset += stream.next_in - in; - } while (st == Z_OK || st == Z_BUF_ERROR); - git_inflate_end(&stream); - return (st == Z_STREAM_END && - stream.total_out == expect && - stream.total_in == len) ? 0 : -1; -} - -static void copy_pack_data(struct sha1file *f, - struct packed_git *p, - struct pack_window **w_curs, - off_t offset, - off_t len) -{ - unsigned char *in; - unsigned int avail; - - while (len) { - in = use_pack(p, w_curs, offset, &avail); - if (avail > len) - avail = (unsigned int)len; - sha1write(f, in, avail); - offset += avail; - len -= avail; - } -} - -static unsigned long write_object(struct sha1file *f, - struct object_entry *entry, - off_t write_offset) -{ - unsigned long size, limit, datalen; - void *buf; - unsigned char header[10], dheader[10]; - unsigned hdrlen; - enum object_type type; - int usable_delta, to_reuse; - - if (!pack_to_stdout) - crc32_begin(f); - - type = entry->type; - - /* apply size limit if limited packsize and not first object */ - if (!pack_size_limit || !nr_written) - limit = 0; - else if (pack_size_limit <= write_offset) - /* - * the earlier object did not fit the limit; avoid - * mistaking this with unlimited (i.e. limit = 0). - */ - limit = 1; - else - limit = pack_size_limit - write_offset; - - if (!entry->delta) - usable_delta = 0; /* no delta */ - else if (!pack_size_limit) - usable_delta = 1; /* unlimited packfile */ - else if (entry->delta->idx.offset == (off_t)-1) - usable_delta = 0; /* base was written to another pack */ - else if (entry->delta->idx.offset) - usable_delta = 1; /* base already exists in this pack */ - else - usable_delta = 0; /* base could end up in another pack */ - - if (!reuse_object) - to_reuse = 0; /* explicit */ - else if (!entry->in_pack) - to_reuse = 0; /* can't reuse what we don't have */ - else if (type == OBJ_REF_DELTA || type == OBJ_OFS_DELTA) - /* check_object() decided it for us ... */ - to_reuse = usable_delta; - /* ... but pack split may override that */ - else if (type != entry->in_pack_type) - to_reuse = 0; /* pack has delta which is unusable */ - else if (entry->delta) - to_reuse = 0; /* we want to pack afresh */ - else - to_reuse = 1; /* we have it in-pack undeltified, - * and we do not need to deltify it. - */ - - if (!to_reuse) { - no_reuse: - if (!usable_delta) { - buf = read_sha1_file(entry->idx.sha1, &type, &size); - if (!buf) - die("unable to read %s", sha1_to_hex(entry->idx.sha1)); - /* - * make sure no cached delta data remains from a - * previous attempt before a pack split occurred. - */ - free(entry->delta_data); - entry->delta_data = NULL; - entry->z_delta_size = 0; - } else if (entry->delta_data) { - size = entry->delta_size; - buf = entry->delta_data; - entry->delta_data = NULL; - type = (allow_ofs_delta && entry->delta->idx.offset) ? - OBJ_OFS_DELTA : OBJ_REF_DELTA; - } else { - buf = get_delta(entry); - size = entry->delta_size; - type = (allow_ofs_delta && entry->delta->idx.offset) ? - OBJ_OFS_DELTA : OBJ_REF_DELTA; - } - - if (entry->z_delta_size) - datalen = entry->z_delta_size; - else - datalen = do_compress(&buf, size); - - /* - * The object header is a byte of 'type' followed by zero or - * more bytes of length. - */ - hdrlen = encode_header(type, size, header); - - if (type == OBJ_OFS_DELTA) { - /* - * Deltas with relative base contain an additional - * encoding of the relative offset for the delta - * base from this object's position in the pack. - */ - off_t ofs = entry->idx.offset - entry->delta->idx.offset; - unsigned pos = sizeof(dheader) - 1; - dheader[pos] = ofs & 127; - while (ofs >>= 7) - dheader[--pos] = 128 | (--ofs & 127); - if (limit && hdrlen + sizeof(dheader) - pos + datalen + 20 >= limit) { - free(buf); - return 0; - } - sha1write(f, header, hdrlen); - sha1write(f, dheader + pos, sizeof(dheader) - pos); - hdrlen += sizeof(dheader) - pos; - } else if (type == OBJ_REF_DELTA) { - /* - * Deltas with a base reference contain - * an additional 20 bytes for the base sha1. - */ - if (limit && hdrlen + 20 + datalen + 20 >= limit) { - free(buf); - return 0; - } - sha1write(f, header, hdrlen); - sha1write(f, entry->delta->idx.sha1, 20); - hdrlen += 20; - } else { - if (limit && hdrlen + datalen + 20 >= limit) { - free(buf); - return 0; - } - sha1write(f, header, hdrlen); - } - sha1write(f, buf, datalen); - free(buf); - } - else { - struct packed_git *p = entry->in_pack; - struct pack_window *w_curs = NULL; - struct revindex_entry *revidx; - off_t offset; - - if (entry->delta) - type = (allow_ofs_delta && entry->delta->idx.offset) ? - OBJ_OFS_DELTA : OBJ_REF_DELTA; - hdrlen = encode_header(type, entry->size, header); - - offset = entry->in_pack_offset; - revidx = find_pack_revindex(p, offset); - datalen = revidx[1].offset - offset; - if (!pack_to_stdout && p->index_version > 1 && - check_pack_crc(p, &w_curs, offset, datalen, revidx->nr)) { - error("bad packed object CRC for %s", sha1_to_hex(entry->idx.sha1)); - unuse_pack(&w_curs); - goto no_reuse; - } - - offset += entry->in_pack_header_size; - datalen -= entry->in_pack_header_size; - if (!pack_to_stdout && p->index_version == 1 && - check_pack_inflate(p, &w_curs, offset, datalen, entry->size)) { - error("corrupt packed object for %s", sha1_to_hex(entry->idx.sha1)); - unuse_pack(&w_curs); - goto no_reuse; - } - - if (type == OBJ_OFS_DELTA) { - off_t ofs = entry->idx.offset - entry->delta->idx.offset; - unsigned pos = sizeof(dheader) - 1; - dheader[pos] = ofs & 127; - while (ofs >>= 7) - dheader[--pos] = 128 | (--ofs & 127); - if (limit && hdrlen + sizeof(dheader) - pos + datalen + 20 >= limit) { - unuse_pack(&w_curs); - return 0; - } - sha1write(f, header, hdrlen); - sha1write(f, dheader + pos, sizeof(dheader) - pos); - hdrlen += sizeof(dheader) - pos; - reused_delta++; - } else if (type == OBJ_REF_DELTA) { - if (limit && hdrlen + 20 + datalen + 20 >= limit) { - unuse_pack(&w_curs); - return 0; - } - sha1write(f, header, hdrlen); - sha1write(f, entry->delta->idx.sha1, 20); - hdrlen += 20; - reused_delta++; - } else { - if (limit && hdrlen + datalen + 20 >= limit) { - unuse_pack(&w_curs); - return 0; - } - sha1write(f, header, hdrlen); - } - copy_pack_data(f, p, &w_curs, offset, datalen); - unuse_pack(&w_curs); - reused++; - } - if (usable_delta) - written_delta++; - written++; - if (!pack_to_stdout) - entry->idx.crc32 = crc32_end(f); - return hdrlen + datalen; -} - -static int write_one(struct sha1file *f, - struct object_entry *e, - off_t *offset) -{ - unsigned long size; - - /* offset is non zero if object is written already. */ - if (e->idx.offset || e->preferred_base) - return -1; - - /* if we are deltified, write out base object first. */ - if (e->delta && !write_one(f, e->delta, offset)) - return 0; - - e->idx.offset = *offset; - size = write_object(f, e, *offset); - if (!size) { - e->idx.offset = 0; - return 0; - } - written_list[nr_written++] = &e->idx; - - /* make sure off_t is sufficiently large not to wrap */ - if (*offset > *offset + size) - die("pack too large for current definition of off_t"); - *offset += size; - return 1; -} - -/* forward declaration for write_pack_file */ -static int adjust_perm(const char *path, mode_t mode); - -static void write_pack_file(void) -{ - uint32_t i = 0, j; - struct sha1file *f; - off_t offset; - struct pack_header hdr; - uint32_t nr_remaining = nr_result; - time_t last_mtime = 0; - - if (progress > pack_to_stdout) - progress_state = start_progress("Writing objects", nr_result); - written_list = xmalloc(nr_objects * sizeof(*written_list)); - - do { - unsigned char sha1[20]; - char *pack_tmp_name = NULL; - - if (pack_to_stdout) { - f = sha1fd_throughput(1, "", progress_state); - } else { - char tmpname[PATH_MAX]; - int fd; - fd = odb_mkstemp(tmpname, sizeof(tmpname), - "pack/tmp_pack_XXXXXX"); - pack_tmp_name = xstrdup(tmpname); - f = sha1fd(fd, pack_tmp_name); - } - - hdr.hdr_signature = htonl(PACK_SIGNATURE); - hdr.hdr_version = htonl(PACK_VERSION); - hdr.hdr_entries = htonl(nr_remaining); - sha1write(f, &hdr, sizeof(hdr)); - offset = sizeof(hdr); - nr_written = 0; - for (; i < nr_objects; i++) { - if (!write_one(f, objects + i, &offset)) - break; - display_progress(progress_state, written); - } - - /* - * Did we write the wrong # entries in the header? - * If so, rewrite it like in fast-import - */ - if (pack_to_stdout) { - sha1close(f, sha1, CSUM_CLOSE); - } else if (nr_written == nr_remaining) { - sha1close(f, sha1, CSUM_FSYNC); - } else { - int fd = sha1close(f, sha1, 0); - fixup_pack_header_footer(fd, sha1, pack_tmp_name, - nr_written, sha1, offset); - close(fd); - } - - if (!pack_to_stdout) { - mode_t mode = umask(0); - struct stat st; - const char *idx_tmp_name; - char tmpname[PATH_MAX]; - - umask(mode); - mode = 0444 & ~mode; - - idx_tmp_name = write_idx_file(NULL, written_list, - nr_written, sha1); - - snprintf(tmpname, sizeof(tmpname), "%s-%s.pack", - base_name, sha1_to_hex(sha1)); - free_pack_by_name(tmpname); - if (adjust_perm(pack_tmp_name, mode)) - die_errno("unable to make temporary pack file readable"); - if (rename(pack_tmp_name, tmpname)) - die_errno("unable to rename temporary pack file"); - - /* - * Packs are runtime accessed in their mtime - * order since newer packs are more likely to contain - * younger objects. So if we are creating multiple - * packs then we should modify the mtime of later ones - * to preserve this property. - */ - if (stat(tmpname, &st) < 0) { - warning("failed to stat %s: %s", - tmpname, strerror(errno)); - } else if (!last_mtime) { - last_mtime = st.st_mtime; - } else { - struct utimbuf utb; - utb.actime = st.st_atime; - utb.modtime = --last_mtime; - if (utime(tmpname, &utb) < 0) - warning("failed utime() on %s: %s", - tmpname, strerror(errno)); - } - - snprintf(tmpname, sizeof(tmpname), "%s-%s.idx", - base_name, sha1_to_hex(sha1)); - if (adjust_perm(idx_tmp_name, mode)) - die_errno("unable to make temporary index file readable"); - if (rename(idx_tmp_name, tmpname)) - die_errno("unable to rename temporary index file"); - - free((void *) idx_tmp_name); - free(pack_tmp_name); - puts(sha1_to_hex(sha1)); - } - - /* mark written objects as written to previous pack */ - for (j = 0; j < nr_written; j++) { - written_list[j]->offset = (off_t)-1; - } - nr_remaining -= nr_written; - } while (nr_remaining && i < nr_objects); - - free(written_list); - stop_progress(&progress_state); - if (written != nr_result) - die("wrote %"PRIu32" objects while expecting %"PRIu32, - written, nr_result); -} - -static int locate_object_entry_hash(const unsigned char *sha1) -{ - int i; - unsigned int ui; - memcpy(&ui, sha1, sizeof(unsigned int)); - i = ui % object_ix_hashsz; - while (0 < object_ix[i]) { - if (!hashcmp(sha1, objects[object_ix[i] - 1].idx.sha1)) - return i; - if (++i == object_ix_hashsz) - i = 0; - } - return -1 - i; -} - -static struct object_entry *locate_object_entry(const unsigned char *sha1) -{ - int i; - - if (!object_ix_hashsz) - return NULL; - - i = locate_object_entry_hash(sha1); - if (0 <= i) - return &objects[object_ix[i]-1]; - return NULL; -} - -static void rehash_objects(void) -{ - uint32_t i; - struct object_entry *oe; - - object_ix_hashsz = nr_objects * 3; - if (object_ix_hashsz < 1024) - object_ix_hashsz = 1024; - object_ix = xrealloc(object_ix, sizeof(int) * object_ix_hashsz); - memset(object_ix, 0, sizeof(int) * object_ix_hashsz); - for (i = 0, oe = objects; i < nr_objects; i++, oe++) { - int ix = locate_object_entry_hash(oe->idx.sha1); - if (0 <= ix) - continue; - ix = -1 - ix; - object_ix[ix] = i + 1; - } -} - -static unsigned name_hash(const char *name) -{ - unsigned c, hash = 0; - - if (!name) - return 0; - - /* - * This effectively just creates a sortable number from the - * last sixteen non-whitespace characters. Last characters - * count "most", so things that end in ".c" sort together. - */ - while ((c = *name++) != 0) { - if (isspace(c)) - continue; - hash = (hash >> 2) + (c << 24); - } - return hash; -} - -static void setup_delta_attr_check(struct git_attr_check *check) -{ - static struct git_attr *attr_delta; - - if (!attr_delta) - attr_delta = git_attr("delta"); - - check[0].attr = attr_delta; -} - -static int no_try_delta(const char *path) -{ - struct git_attr_check check[1]; - - setup_delta_attr_check(check); - if (git_checkattr(path, ARRAY_SIZE(check), check)) - return 0; - if (ATTR_FALSE(check->value)) - return 1; - return 0; -} - -static int add_object_entry(const unsigned char *sha1, enum object_type type, - const char *name, int exclude) -{ - struct object_entry *entry; - struct packed_git *p, *found_pack = NULL; - off_t found_offset = 0; - int ix; - unsigned hash = name_hash(name); - - ix = nr_objects ? locate_object_entry_hash(sha1) : -1; - if (ix >= 0) { - if (exclude) { - entry = objects + object_ix[ix] - 1; - if (!entry->preferred_base) - nr_result--; - entry->preferred_base = 1; - } - return 0; - } - - if (!exclude && local && has_loose_object_nonlocal(sha1)) - return 0; - - for (p = packed_git; p; p = p->next) { - off_t offset = find_pack_entry_one(sha1, p); - if (offset) { - if (!found_pack) { - found_offset = offset; - found_pack = p; - } - if (exclude) - break; - if (incremental) - return 0; - if (local && !p->pack_local) - return 0; - if (ignore_packed_keep && p->pack_local && p->pack_keep) - return 0; - } - } - - if (nr_objects >= nr_alloc) { - nr_alloc = (nr_alloc + 1024) * 3 / 2; - objects = xrealloc(objects, nr_alloc * sizeof(*entry)); - } - - entry = objects + nr_objects++; - memset(entry, 0, sizeof(*entry)); - hashcpy(entry->idx.sha1, sha1); - entry->hash = hash; - if (type) - entry->type = type; - if (exclude) - entry->preferred_base = 1; - else - nr_result++; - if (found_pack) { - entry->in_pack = found_pack; - entry->in_pack_offset = found_offset; - } - - if (object_ix_hashsz * 3 <= nr_objects * 4) - rehash_objects(); - else - object_ix[-1 - ix] = nr_objects; - - display_progress(progress_state, nr_objects); - - if (name && no_try_delta(name)) - entry->no_try_delta = 1; - - return 1; -} - -struct pbase_tree_cache { - unsigned char sha1[20]; - int ref; - int temporary; - void *tree_data; - unsigned long tree_size; -}; - -static struct pbase_tree_cache *(pbase_tree_cache[256]); -static int pbase_tree_cache_ix(const unsigned char *sha1) -{ - return sha1[0] % ARRAY_SIZE(pbase_tree_cache); -} -static int pbase_tree_cache_ix_incr(int ix) -{ - return (ix+1) % ARRAY_SIZE(pbase_tree_cache); -} - -static struct pbase_tree { - struct pbase_tree *next; - /* This is a phony "cache" entry; we are not - * going to evict it nor find it through _get() - * mechanism -- this is for the toplevel node that - * would almost always change with any commit. - */ - struct pbase_tree_cache pcache; -} *pbase_tree; - -static struct pbase_tree_cache *pbase_tree_get(const unsigned char *sha1) -{ - struct pbase_tree_cache *ent, *nent; - void *data; - unsigned long size; - enum object_type type; - int neigh; - int my_ix = pbase_tree_cache_ix(sha1); - int available_ix = -1; - - /* pbase-tree-cache acts as a limited hashtable. - * your object will be found at your index or within a few - * slots after that slot if it is cached. - */ - for (neigh = 0; neigh < 8; neigh++) { - ent = pbase_tree_cache[my_ix]; - if (ent && !hashcmp(ent->sha1, sha1)) { - ent->ref++; - return ent; - } - else if (((available_ix < 0) && (!ent || !ent->ref)) || - ((0 <= available_ix) && - (!ent && pbase_tree_cache[available_ix]))) - available_ix = my_ix; - if (!ent) - break; - my_ix = pbase_tree_cache_ix_incr(my_ix); - } - - /* Did not find one. Either we got a bogus request or - * we need to read and perhaps cache. - */ - data = read_sha1_file(sha1, &type, &size); - if (!data) - return NULL; - if (type != OBJ_TREE) { - free(data); - return NULL; - } - - /* We need to either cache or return a throwaway copy */ - - if (available_ix < 0) - ent = NULL; - else { - ent = pbase_tree_cache[available_ix]; - my_ix = available_ix; - } - - if (!ent) { - nent = xmalloc(sizeof(*nent)); - nent->temporary = (available_ix < 0); - } - else { - /* evict and reuse */ - free(ent->tree_data); - nent = ent; - } - hashcpy(nent->sha1, sha1); - nent->tree_data = data; - nent->tree_size = size; - nent->ref = 1; - if (!nent->temporary) - pbase_tree_cache[my_ix] = nent; - return nent; -} - -static void pbase_tree_put(struct pbase_tree_cache *cache) -{ - if (!cache->temporary) { - cache->ref--; - return; - } - free(cache->tree_data); - free(cache); -} - -static int name_cmp_len(const char *name) -{ - int i; - for (i = 0; name[i] && name[i] != '\n' && name[i] != '/'; i++) - ; - return i; -} - -static void add_pbase_object(struct tree_desc *tree, - const char *name, - int cmplen, - const char *fullname) -{ - struct name_entry entry; - int cmp; - - while (tree_entry(tree,&entry)) { - if (S_ISGITLINK(entry.mode)) - continue; - cmp = tree_entry_len(entry.path, entry.sha1) != cmplen ? 1 : - memcmp(name, entry.path, cmplen); - if (cmp > 0) - continue; - if (cmp < 0) - return; - if (name[cmplen] != '/') { - add_object_entry(entry.sha1, - object_type(entry.mode), - fullname, 1); - return; - } - if (S_ISDIR(entry.mode)) { - struct tree_desc sub; - struct pbase_tree_cache *tree; - const char *down = name+cmplen+1; - int downlen = name_cmp_len(down); - - tree = pbase_tree_get(entry.sha1); - if (!tree) - return; - init_tree_desc(&sub, tree->tree_data, tree->tree_size); - - add_pbase_object(&sub, down, downlen, fullname); - pbase_tree_put(tree); - } - } -} - -static unsigned *done_pbase_paths; -static int done_pbase_paths_num; -static int done_pbase_paths_alloc; -static int done_pbase_path_pos(unsigned hash) -{ - int lo = 0; - int hi = done_pbase_paths_num; - while (lo < hi) { - int mi = (hi + lo) / 2; - if (done_pbase_paths[mi] == hash) - return mi; - if (done_pbase_paths[mi] < hash) - hi = mi; - else - lo = mi + 1; - } - return -lo-1; -} - -static int check_pbase_path(unsigned hash) -{ - int pos = (!done_pbase_paths) ? -1 : done_pbase_path_pos(hash); - if (0 <= pos) - return 1; - pos = -pos - 1; - if (done_pbase_paths_alloc <= done_pbase_paths_num) { - done_pbase_paths_alloc = alloc_nr(done_pbase_paths_alloc); - done_pbase_paths = xrealloc(done_pbase_paths, - done_pbase_paths_alloc * - sizeof(unsigned)); - } - done_pbase_paths_num++; - if (pos < done_pbase_paths_num) - memmove(done_pbase_paths + pos + 1, - done_pbase_paths + pos, - (done_pbase_paths_num - pos - 1) * sizeof(unsigned)); - done_pbase_paths[pos] = hash; - return 0; -} - -static void add_preferred_base_object(const char *name) -{ - struct pbase_tree *it; - int cmplen; - unsigned hash = name_hash(name); - - if (!num_preferred_base || check_pbase_path(hash)) - return; - - cmplen = name_cmp_len(name); - for (it = pbase_tree; it; it = it->next) { - if (cmplen == 0) { - add_object_entry(it->pcache.sha1, OBJ_TREE, NULL, 1); - } - else { - struct tree_desc tree; - init_tree_desc(&tree, it->pcache.tree_data, it->pcache.tree_size); - add_pbase_object(&tree, name, cmplen, name); - } - } -} - -static void add_preferred_base(unsigned char *sha1) -{ - struct pbase_tree *it; - void *data; - unsigned long size; - unsigned char tree_sha1[20]; - - if (window <= num_preferred_base++) - return; - - data = read_object_with_reference(sha1, tree_type, &size, tree_sha1); - if (!data) - return; - - for (it = pbase_tree; it; it = it->next) { - if (!hashcmp(it->pcache.sha1, tree_sha1)) { - free(data); - return; - } - } - - it = xcalloc(1, sizeof(*it)); - it->next = pbase_tree; - pbase_tree = it; - - hashcpy(it->pcache.sha1, tree_sha1); - it->pcache.tree_data = data; - it->pcache.tree_size = size; -} - -static void cleanup_preferred_base(void) -{ - struct pbase_tree *it; - unsigned i; - - it = pbase_tree; - pbase_tree = NULL; - while (it) { - struct pbase_tree *this = it; - it = this->next; - free(this->pcache.tree_data); - free(this); - } - - for (i = 0; i < ARRAY_SIZE(pbase_tree_cache); i++) { - if (!pbase_tree_cache[i]) - continue; - free(pbase_tree_cache[i]->tree_data); - free(pbase_tree_cache[i]); - pbase_tree_cache[i] = NULL; - } - - free(done_pbase_paths); - done_pbase_paths = NULL; - done_pbase_paths_num = done_pbase_paths_alloc = 0; -} - -static void check_object(struct object_entry *entry) -{ - if (entry->in_pack) { - struct packed_git *p = entry->in_pack; - struct pack_window *w_curs = NULL; - const unsigned char *base_ref = NULL; - struct object_entry *base_entry; - unsigned long used, used_0; - unsigned int avail; - off_t ofs; - unsigned char *buf, c; - - buf = use_pack(p, &w_curs, entry->in_pack_offset, &avail); - - /* - * We want in_pack_type even if we do not reuse delta - * since non-delta representations could still be reused. - */ - used = unpack_object_header_buffer(buf, avail, - &entry->in_pack_type, - &entry->size); - if (used == 0) - goto give_up; - - /* - * Determine if this is a delta and if so whether we can - * reuse it or not. Otherwise let's find out as cheaply as - * possible what the actual type and size for this object is. - */ - switch (entry->in_pack_type) { - default: - /* Not a delta hence we've already got all we need. */ - entry->type = entry->in_pack_type; - entry->in_pack_header_size = used; - if (entry->type < OBJ_COMMIT || entry->type > OBJ_BLOB) - goto give_up; - unuse_pack(&w_curs); - return; - case OBJ_REF_DELTA: - if (reuse_delta && !entry->preferred_base) - base_ref = use_pack(p, &w_curs, - entry->in_pack_offset + used, NULL); - entry->in_pack_header_size = used + 20; - break; - case OBJ_OFS_DELTA: - buf = use_pack(p, &w_curs, - entry->in_pack_offset + used, NULL); - used_0 = 0; - c = buf[used_0++]; - ofs = c & 127; - while (c & 128) { - ofs += 1; - if (!ofs || MSB(ofs, 7)) { - error("delta base offset overflow in pack for %s", - sha1_to_hex(entry->idx.sha1)); - goto give_up; - } - c = buf[used_0++]; - ofs = (ofs << 7) + (c & 127); - } - ofs = entry->in_pack_offset - ofs; - if (ofs <= 0 || ofs >= entry->in_pack_offset) { - error("delta base offset out of bound for %s", - sha1_to_hex(entry->idx.sha1)); - goto give_up; - } - if (reuse_delta && !entry->preferred_base) { - struct revindex_entry *revidx; - revidx = find_pack_revindex(p, ofs); - if (!revidx) - goto give_up; - base_ref = nth_packed_object_sha1(p, revidx->nr); - } - entry->in_pack_header_size = used + used_0; - break; - } - - if (base_ref && (base_entry = locate_object_entry(base_ref))) { - /* - * If base_ref was set above that means we wish to - * reuse delta data, and we even found that base - * in the list of objects we want to pack. Goodie! - * - * Depth value does not matter - find_deltas() will - * never consider reused delta as the base object to - * deltify other objects against, in order to avoid - * circular deltas. - */ - entry->type = entry->in_pack_type; - entry->delta = base_entry; - entry->delta_size = entry->size; - entry->delta_sibling = base_entry->delta_child; - base_entry->delta_child = entry; - unuse_pack(&w_curs); - return; - } - - if (entry->type) { - /* - * This must be a delta and we already know what the - * final object type is. Let's extract the actual - * object size from the delta header. - */ - entry->size = get_size_from_delta(p, &w_curs, - entry->in_pack_offset + entry->in_pack_header_size); - if (entry->size == 0) - goto give_up; - unuse_pack(&w_curs); - return; - } - - /* - * No choice but to fall back to the recursive delta walk - * with sha1_object_info() to find about the object type - * at this point... - */ - give_up: - unuse_pack(&w_curs); - } - - entry->type = sha1_object_info(entry->idx.sha1, &entry->size); - /* - * The error condition is checked in prepare_pack(). This is - * to permit a missing preferred base object to be ignored - * as a preferred base. Doing so can result in a larger - * pack file, but the transfer will still take place. - */ -} - -static int pack_offset_sort(const void *_a, const void *_b) -{ - const struct object_entry *a = *(struct object_entry **)_a; - const struct object_entry *b = *(struct object_entry **)_b; - - /* avoid filesystem trashing with loose objects */ - if (!a->in_pack && !b->in_pack) - return hashcmp(a->idx.sha1, b->idx.sha1); - - if (a->in_pack < b->in_pack) - return -1; - if (a->in_pack > b->in_pack) - return 1; - return a->in_pack_offset < b->in_pack_offset ? -1 : - (a->in_pack_offset > b->in_pack_offset); -} - -static void get_object_details(void) -{ - uint32_t i; - struct object_entry **sorted_by_offset; - - sorted_by_offset = xcalloc(nr_objects, sizeof(struct object_entry *)); - for (i = 0; i < nr_objects; i++) - sorted_by_offset[i] = objects + i; - qsort(sorted_by_offset, nr_objects, sizeof(*sorted_by_offset), pack_offset_sort); - - for (i = 0; i < nr_objects; i++) - check_object(sorted_by_offset[i]); - - free(sorted_by_offset); -} - -/* - * We search for deltas in a list sorted by type, by filename hash, and then - * by size, so that we see progressively smaller and smaller files. - * That's because we prefer deltas to be from the bigger file - * to the smaller -- deletes are potentially cheaper, but perhaps - * more importantly, the bigger file is likely the more recent - * one. The deepest deltas are therefore the oldest objects which are - * less susceptible to be accessed often. - */ -static int type_size_sort(const void *_a, const void *_b) -{ - const struct object_entry *a = *(struct object_entry **)_a; - const struct object_entry *b = *(struct object_entry **)_b; - - if (a->type > b->type) - return -1; - if (a->type < b->type) - return 1; - if (a->hash > b->hash) - return -1; - if (a->hash < b->hash) - return 1; - if (a->preferred_base > b->preferred_base) - return -1; - if (a->preferred_base < b->preferred_base) - return 1; - if (a->size > b->size) - return -1; - if (a->size < b->size) - return 1; - return a < b ? -1 : (a > b); /* newest first */ -} - -struct unpacked { - struct object_entry *entry; - void *data; - struct delta_index *index; - unsigned depth; -}; - -static int delta_cacheable(unsigned long src_size, unsigned long trg_size, - unsigned long delta_size) -{ - if (max_delta_cache_size && delta_cache_size + delta_size > max_delta_cache_size) - return 0; - - if (delta_size < cache_max_small_delta_size) - return 1; - - /* cache delta, if objects are large enough compared to delta size */ - if ((src_size >> 20) + (trg_size >> 21) > (delta_size >> 10)) - return 1; - - return 0; -} - -#ifndef NO_PTHREADS - -static pthread_mutex_t read_mutex; -#define read_lock() pthread_mutex_lock(&read_mutex) -#define read_unlock() pthread_mutex_unlock(&read_mutex) - -static pthread_mutex_t cache_mutex; -#define cache_lock() pthread_mutex_lock(&cache_mutex) -#define cache_unlock() pthread_mutex_unlock(&cache_mutex) - -static pthread_mutex_t progress_mutex; -#define progress_lock() pthread_mutex_lock(&progress_mutex) -#define progress_unlock() pthread_mutex_unlock(&progress_mutex) - -#else - -#define read_lock() (void)0 -#define read_unlock() (void)0 -#define cache_lock() (void)0 -#define cache_unlock() (void)0 -#define progress_lock() (void)0 -#define progress_unlock() (void)0 - -#endif - -static int try_delta(struct unpacked *trg, struct unpacked *src, - unsigned max_depth, unsigned long *mem_usage) -{ - struct object_entry *trg_entry = trg->entry; - struct object_entry *src_entry = src->entry; - unsigned long trg_size, src_size, delta_size, sizediff, max_size, sz; - unsigned ref_depth; - enum object_type type; - void *delta_buf; - - /* Don't bother doing diffs between different types */ - if (trg_entry->type != src_entry->type) - return -1; - - /* - * We do not bother to try a delta that we discarded - * on an earlier try, but only when reusing delta data. - */ - if (reuse_delta && trg_entry->in_pack && - trg_entry->in_pack == src_entry->in_pack && - trg_entry->in_pack_type != OBJ_REF_DELTA && - trg_entry->in_pack_type != OBJ_OFS_DELTA) - return 0; - - /* Let's not bust the allowed depth. */ - if (src->depth >= max_depth) - return 0; - - /* Now some size filtering heuristics. */ - trg_size = trg_entry->size; - if (!trg_entry->delta) { - max_size = trg_size/2 - 20; - ref_depth = 1; - } else { - max_size = trg_entry->delta_size; - ref_depth = trg->depth; - } - max_size = (uint64_t)max_size * (max_depth - src->depth) / - (max_depth - ref_depth + 1); - if (max_size == 0) - return 0; - src_size = src_entry->size; - sizediff = src_size < trg_size ? trg_size - src_size : 0; - if (sizediff >= max_size) - return 0; - if (trg_size < src_size / 32) - return 0; - - /* Load data if not already done */ - if (!trg->data) { - read_lock(); - trg->data = read_sha1_file(trg_entry->idx.sha1, &type, &sz); - read_unlock(); - if (!trg->data) - die("object %s cannot be read", - sha1_to_hex(trg_entry->idx.sha1)); - if (sz != trg_size) - die("object %s inconsistent object length (%lu vs %lu)", - sha1_to_hex(trg_entry->idx.sha1), sz, trg_size); - *mem_usage += sz; - } - if (!src->data) { - read_lock(); - src->data = read_sha1_file(src_entry->idx.sha1, &type, &sz); - read_unlock(); - if (!src->data) - die("object %s cannot be read", - sha1_to_hex(src_entry->idx.sha1)); - if (sz != src_size) - die("object %s inconsistent object length (%lu vs %lu)", - sha1_to_hex(src_entry->idx.sha1), sz, src_size); - *mem_usage += sz; - } - if (!src->index) { - src->index = create_delta_index(src->data, src_size); - if (!src->index) { - static int warned = 0; - if (!warned++) - warning("suboptimal pack - out of memory"); - return 0; - } - *mem_usage += sizeof_delta_index(src->index); - } - - delta_buf = create_delta(src->index, trg->data, trg_size, &delta_size, max_size); - if (!delta_buf) - return 0; - - if (trg_entry->delta) { - /* Prefer only shallower same-sized deltas. */ - if (delta_size == trg_entry->delta_size && - src->depth + 1 >= trg->depth) { - free(delta_buf); - return 0; - } - } - - /* - * Handle memory allocation outside of the cache - * accounting lock. Compiler will optimize the strangeness - * away when NO_PTHREADS is defined. - */ - free(trg_entry->delta_data); - cache_lock(); - if (trg_entry->delta_data) { - delta_cache_size -= trg_entry->delta_size; - trg_entry->delta_data = NULL; - } - if (delta_cacheable(src_size, trg_size, delta_size)) { - delta_cache_size += delta_size; - cache_unlock(); - trg_entry->delta_data = xrealloc(delta_buf, delta_size); - } else { - cache_unlock(); - free(delta_buf); - } - - trg_entry->delta = src_entry; - trg_entry->delta_size = delta_size; - trg->depth = src->depth + 1; - - return 1; -} - -static unsigned int check_delta_limit(struct object_entry *me, unsigned int n) -{ - struct object_entry *child = me->delta_child; - unsigned int m = n; - while (child) { - unsigned int c = check_delta_limit(child, n + 1); - if (m < c) - m = c; - child = child->delta_sibling; - } - return m; -} - -static unsigned long free_unpacked(struct unpacked *n) -{ - unsigned long freed_mem = sizeof_delta_index(n->index); - free_delta_index(n->index); - n->index = NULL; - if (n->data) { - freed_mem += n->entry->size; - free(n->data); - n->data = NULL; - } - n->entry = NULL; - n->depth = 0; - return freed_mem; -} - -static void find_deltas(struct object_entry **list, unsigned *list_size, - int window, int depth, unsigned *processed) -{ - uint32_t i, idx = 0, count = 0; - struct unpacked *array; - unsigned long mem_usage = 0; - - array = xcalloc(window, sizeof(struct unpacked)); - - for (;;) { - struct object_entry *entry; - struct unpacked *n = array + idx; - int j, max_depth, best_base = -1; - - progress_lock(); - if (!*list_size) { - progress_unlock(); - break; - } - entry = *list++; - (*list_size)--; - if (!entry->preferred_base) { - (*processed)++; - display_progress(progress_state, *processed); - } - progress_unlock(); - - mem_usage -= free_unpacked(n); - n->entry = entry; - - while (window_memory_limit && - mem_usage > window_memory_limit && - count > 1) { - uint32_t tail = (idx + window - count) % window; - mem_usage -= free_unpacked(array + tail); - count--; - } - - /* We do not compute delta to *create* objects we are not - * going to pack. - */ - if (entry->preferred_base) - goto next; - - /* - * If the current object is at pack edge, take the depth the - * objects that depend on the current object into account - * otherwise they would become too deep. - */ - max_depth = depth; - if (entry->delta_child) { - max_depth -= check_delta_limit(entry, 0); - if (max_depth <= 0) - goto next; - } - - j = window; - while (--j > 0) { - int ret; - uint32_t other_idx = idx + j; - struct unpacked *m; - if (other_idx >= window) - other_idx -= window; - m = array + other_idx; - if (!m->entry) - break; - ret = try_delta(n, m, max_depth, &mem_usage); - if (ret < 0) - break; - else if (ret > 0) - best_base = other_idx; - } - - /* - * If we decided to cache the delta data, then it is best - * to compress it right away. First because we have to do - * it anyway, and doing it here while we're threaded will - * save a lot of time in the non threaded write phase, - * as well as allow for caching more deltas within - * the same cache size limit. - * ... - * But only if not writing to stdout, since in that case - * the network is most likely throttling writes anyway, - * and therefore it is best to go to the write phase ASAP - * instead, as we can afford spending more time compressing - * between writes at that moment. - */ - if (entry->delta_data && !pack_to_stdout) { - entry->z_delta_size = do_compress(&entry->delta_data, - entry->delta_size); - cache_lock(); - delta_cache_size -= entry->delta_size; - delta_cache_size += entry->z_delta_size; - cache_unlock(); - } - - /* if we made n a delta, and if n is already at max - * depth, leaving it in the window is pointless. we - * should evict it first. - */ - if (entry->delta && max_depth <= n->depth) - continue; - - /* - * Move the best delta base up in the window, after the - * currently deltified object, to keep it longer. It will - * be the first base object to be attempted next. - */ - if (entry->delta) { - struct unpacked swap = array[best_base]; - int dist = (window + idx - best_base) % window; - int dst = best_base; - while (dist--) { - int src = (dst + 1) % window; - array[dst] = array[src]; - dst = src; - } - array[dst] = swap; - } - - next: - idx++; - if (count + 1 < window) - count++; - if (idx >= window) - idx = 0; - } - - for (i = 0; i < window; ++i) { - free_delta_index(array[i].index); - free(array[i].data); - } - free(array); -} - -#ifndef NO_PTHREADS - -/* - * The main thread waits on the condition that (at least) one of the workers - * has stopped working (which is indicated in the .working member of - * struct thread_params). - * When a work thread has completed its work, it sets .working to 0 and - * signals the main thread and waits on the condition that .data_ready - * becomes 1. - */ - -struct thread_params { - pthread_t thread; - struct object_entry **list; - unsigned list_size; - unsigned remaining; - int window; - int depth; - int working; - int data_ready; - pthread_mutex_t mutex; - pthread_cond_t cond; - unsigned *processed; -}; - -static pthread_cond_t progress_cond; - -/* - * Mutex and conditional variable can't be statically-initialized on Windows. - */ -static void init_threaded_search(void) -{ - pthread_mutex_init(&read_mutex, NULL); - pthread_mutex_init(&cache_mutex, NULL); - pthread_mutex_init(&progress_mutex, NULL); - pthread_cond_init(&progress_cond, NULL); -} - -static void cleanup_threaded_search(void) -{ - pthread_cond_destroy(&progress_cond); - pthread_mutex_destroy(&read_mutex); - pthread_mutex_destroy(&cache_mutex); - pthread_mutex_destroy(&progress_mutex); -} - -static void *threaded_find_deltas(void *arg) -{ - struct thread_params *me = arg; - - while (me->remaining) { - find_deltas(me->list, &me->remaining, - me->window, me->depth, me->processed); - - progress_lock(); - me->working = 0; - pthread_cond_signal(&progress_cond); - progress_unlock(); - - /* - * We must not set ->data_ready before we wait on the - * condition because the main thread may have set it to 1 - * before we get here. In order to be sure that new - * work is available if we see 1 in ->data_ready, it - * was initialized to 0 before this thread was spawned - * and we reset it to 0 right away. - */ - pthread_mutex_lock(&me->mutex); - while (!me->data_ready) - pthread_cond_wait(&me->cond, &me->mutex); - me->data_ready = 0; - pthread_mutex_unlock(&me->mutex); - } - /* leave ->working 1 so that this doesn't get more work assigned */ - return NULL; -} - -static void ll_find_deltas(struct object_entry **list, unsigned list_size, - int window, int depth, unsigned *processed) -{ - struct thread_params *p; - int i, ret, active_threads = 0; - - init_threaded_search(); - - if (!delta_search_threads) /* --threads=0 means autodetect */ - delta_search_threads = online_cpus(); - if (delta_search_threads <= 1) { - find_deltas(list, &list_size, window, depth, processed); - cleanup_threaded_search(); - return; - } - if (progress > pack_to_stdout) - fprintf(stderr, "Delta compression using up to %d threads.\n", - delta_search_threads); - p = xcalloc(delta_search_threads, sizeof(*p)); - - /* Partition the work amongst work threads. */ - for (i = 0; i < delta_search_threads; i++) { - unsigned sub_size = list_size / (delta_search_threads - i); - - /* don't use too small segments or no deltas will be found */ - if (sub_size < 2*window && i+1 < delta_search_threads) - sub_size = 0; - - p[i].window = window; - p[i].depth = depth; - p[i].processed = processed; - p[i].working = 1; - p[i].data_ready = 0; - - /* try to split chunks on "path" boundaries */ - while (sub_size && sub_size < list_size && - list[sub_size]->hash && - list[sub_size]->hash == list[sub_size-1]->hash) - sub_size++; - - p[i].list = list; - p[i].list_size = sub_size; - p[i].remaining = sub_size; - - list += sub_size; - list_size -= sub_size; - } - - /* Start work threads. */ - for (i = 0; i < delta_search_threads; i++) { - if (!p[i].list_size) - continue; - pthread_mutex_init(&p[i].mutex, NULL); - pthread_cond_init(&p[i].cond, NULL); - ret = pthread_create(&p[i].thread, NULL, - threaded_find_deltas, &p[i]); - if (ret) - die("unable to create thread: %s", strerror(ret)); - active_threads++; - } - - /* - * Now let's wait for work completion. Each time a thread is done - * with its work, we steal half of the remaining work from the - * thread with the largest number of unprocessed objects and give - * it to that newly idle thread. This ensure good load balancing - * until the remaining object list segments are simply too short - * to be worth splitting anymore. - */ - while (active_threads) { - struct thread_params *target = NULL; - struct thread_params *victim = NULL; - unsigned sub_size = 0; - - progress_lock(); - for (;;) { - for (i = 0; !target && i < delta_search_threads; i++) - if (!p[i].working) - target = &p[i]; - if (target) - break; - pthread_cond_wait(&progress_cond, &progress_mutex); - } - - for (i = 0; i < delta_search_threads; i++) - if (p[i].remaining > 2*window && - (!victim || victim->remaining < p[i].remaining)) - victim = &p[i]; - if (victim) { - sub_size = victim->remaining / 2; - list = victim->list + victim->list_size - sub_size; - while (sub_size && list[0]->hash && - list[0]->hash == list[-1]->hash) { - list++; - sub_size--; - } - if (!sub_size) { - /* - * It is possible for some "paths" to have - * so many objects that no hash boundary - * might be found. Let's just steal the - * exact half in that case. - */ - sub_size = victim->remaining / 2; - list -= sub_size; - } - target->list = list; - victim->list_size -= sub_size; - victim->remaining -= sub_size; - } - target->list_size = sub_size; - target->remaining = sub_size; - target->working = 1; - progress_unlock(); - - pthread_mutex_lock(&target->mutex); - target->data_ready = 1; - pthread_cond_signal(&target->cond); - pthread_mutex_unlock(&target->mutex); - - if (!sub_size) { - pthread_join(target->thread, NULL); - pthread_cond_destroy(&target->cond); - pthread_mutex_destroy(&target->mutex); - active_threads--; - } - } - cleanup_threaded_search(); - free(p); -} - -#else -#define ll_find_deltas(l, s, w, d, p) find_deltas(l, &s, w, d, p) -#endif - -static int add_ref_tag(const char *path, const unsigned char *sha1, int flag, void *cb_data) -{ - unsigned char peeled[20]; - - if (!prefixcmp(path, "refs/tags/") && /* is a tag? */ - !peel_ref(path, peeled) && /* peelable? */ - !is_null_sha1(peeled) && /* annotated tag? */ - locate_object_entry(peeled)) /* object packed? */ - add_object_entry(sha1, OBJ_TAG, NULL, 0); - return 0; -} - -static void prepare_pack(int window, int depth) -{ - struct object_entry **delta_list; - uint32_t i, nr_deltas; - unsigned n; - - get_object_details(); - - /* - * If we're locally repacking then we need to be doubly careful - * from now on in order to make sure no stealth corruption gets - * propagated to the new pack. Clients receiving streamed packs - * should validate everything they get anyway so no need to incur - * the additional cost here in that case. - */ - if (!pack_to_stdout) - do_check_packed_object_crc = 1; - - if (!nr_objects || !window || !depth) - return; - - delta_list = xmalloc(nr_objects * sizeof(*delta_list)); - nr_deltas = n = 0; - - for (i = 0; i < nr_objects; i++) { - struct object_entry *entry = objects + i; - - if (entry->delta) - /* This happens if we decided to reuse existing - * delta from a pack. "reuse_delta &&" is implied. - */ - continue; - - if (entry->size < 50) - continue; - - if (entry->no_try_delta) - continue; - - if (!entry->preferred_base) { - nr_deltas++; - if (entry->type < 0) - die("unable to get type of object %s", - sha1_to_hex(entry->idx.sha1)); - } else { - if (entry->type < 0) { - /* - * This object is not found, but we - * don't have to include it anyway. - */ - continue; - } - } - - delta_list[n++] = entry; - } - - if (nr_deltas && n > 1) { - unsigned nr_done = 0; - if (progress) - progress_state = start_progress("Compressing objects", - nr_deltas); - qsort(delta_list, n, sizeof(*delta_list), type_size_sort); - ll_find_deltas(delta_list, n, window+1, depth, &nr_done); - stop_progress(&progress_state); - if (nr_done != nr_deltas) - die("inconsistency with delta count"); - } - free(delta_list); -} - -static int git_pack_config(const char *k, const char *v, void *cb) -{ - if (!strcmp(k, "pack.window")) { - window = git_config_int(k, v); - return 0; - } - if (!strcmp(k, "pack.windowmemory")) { - window_memory_limit = git_config_ulong(k, v); - return 0; - } - if (!strcmp(k, "pack.depth")) { - depth = git_config_int(k, v); - return 0; - } - if (!strcmp(k, "pack.compression")) { - int level = git_config_int(k, v); - if (level == -1) - level = Z_DEFAULT_COMPRESSION; - else if (level < 0 || level > Z_BEST_COMPRESSION) - die("bad pack compression level %d", level); - pack_compression_level = level; - pack_compression_seen = 1; - return 0; - } - if (!strcmp(k, "pack.deltacachesize")) { - max_delta_cache_size = git_config_int(k, v); - return 0; - } - if (!strcmp(k, "pack.deltacachelimit")) { - cache_max_small_delta_size = git_config_int(k, v); - return 0; - } - if (!strcmp(k, "pack.threads")) { - delta_search_threads = git_config_int(k, v); - if (delta_search_threads < 0) - die("invalid number of threads specified (%d)", - delta_search_threads); -#ifdef NO_PTHREADS - if (delta_search_threads != 1) - warning("no threads support, ignoring %s", k); -#endif - return 0; - } - if (!strcmp(k, "pack.indexversion")) { - pack_idx_default_version = git_config_int(k, v); - if (pack_idx_default_version > 2) - die("bad pack.indexversion=%"PRIu32, - pack_idx_default_version); - return 0; - } - if (!strcmp(k, "pack.packsizelimit")) { - pack_size_limit_cfg = git_config_ulong(k, v); - return 0; - } - return git_default_config(k, v, cb); -} - -static void read_object_list_from_stdin(void) -{ - char line[40 + 1 + PATH_MAX + 2]; - unsigned char sha1[20]; - - for (;;) { - if (!fgets(line, sizeof(line), stdin)) { - if (feof(stdin)) - break; - if (!ferror(stdin)) - die("fgets returned NULL, not EOF, not error!"); - if (errno != EINTR) - die_errno("fgets"); - clearerr(stdin); - continue; - } - if (line[0] == '-') { - if (get_sha1_hex(line+1, sha1)) - die("expected edge sha1, got garbage:\n %s", - line); - add_preferred_base(sha1); - continue; - } - if (get_sha1_hex(line, sha1)) - die("expected sha1, got garbage:\n %s", line); - - add_preferred_base_object(line+41); - add_object_entry(sha1, 0, line+41, 0); - } -} - -#define OBJECT_ADDED (1u<<20) - -static void show_commit(struct commit *commit, void *data) -{ - add_object_entry(commit->object.sha1, OBJ_COMMIT, NULL, 0); - commit->object.flags |= OBJECT_ADDED; -} - -static void show_object(struct object *obj, const struct name_path *path, const char *last) -{ - char *name = path_name(path, last); - - add_preferred_base_object(name); - add_object_entry(obj->sha1, obj->type, name, 0); - obj->flags |= OBJECT_ADDED; - - /* - * We will have generated the hash from the name, - * but not saved a pointer to it - we can free it - */ - free((char *)name); -} - -static void show_edge(struct commit *commit) -{ - add_preferred_base(commit->object.sha1); -} - -struct in_pack_object { - off_t offset; - struct object *object; -}; - -struct in_pack { - int alloc; - int nr; - struct in_pack_object *array; -}; - -static void mark_in_pack_object(struct object *object, struct packed_git *p, struct in_pack *in_pack) -{ - in_pack->array[in_pack->nr].offset = find_pack_entry_one(object->sha1, p); - in_pack->array[in_pack->nr].object = object; - in_pack->nr++; -} - -/* - * Compare the objects in the offset order, in order to emulate the - * "git rev-list --objects" output that produced the pack originally. - */ -static int ofscmp(const void *a_, const void *b_) -{ - struct in_pack_object *a = (struct in_pack_object *)a_; - struct in_pack_object *b = (struct in_pack_object *)b_; - - if (a->offset < b->offset) - return -1; - else if (a->offset > b->offset) - return 1; - else - return hashcmp(a->object->sha1, b->object->sha1); -} - -static void add_objects_in_unpacked_packs(struct rev_info *revs) -{ - struct packed_git *p; - struct in_pack in_pack; - uint32_t i; - - memset(&in_pack, 0, sizeof(in_pack)); - - for (p = packed_git; p; p = p->next) { - const unsigned char *sha1; - struct object *o; - - if (!p->pack_local || p->pack_keep) - continue; - if (open_pack_index(p)) - die("cannot open pack index"); - - ALLOC_GROW(in_pack.array, - in_pack.nr + p->num_objects, - in_pack.alloc); - - for (i = 0; i < p->num_objects; i++) { - sha1 = nth_packed_object_sha1(p, i); - o = lookup_unknown_object(sha1); - if (!(o->flags & OBJECT_ADDED)) - mark_in_pack_object(o, p, &in_pack); - o->flags |= OBJECT_ADDED; - } - } - - if (in_pack.nr) { - qsort(in_pack.array, in_pack.nr, sizeof(in_pack.array[0]), - ofscmp); - for (i = 0; i < in_pack.nr; i++) { - struct object *o = in_pack.array[i].object; - add_object_entry(o->sha1, o->type, "", 0); - } - } - free(in_pack.array); -} - -static int has_sha1_pack_kept_or_nonlocal(const unsigned char *sha1) -{ - static struct packed_git *last_found = (void *)1; - struct packed_git *p; - - p = (last_found != (void *)1) ? last_found : packed_git; - - while (p) { - if ((!p->pack_local || p->pack_keep) && - find_pack_entry_one(sha1, p)) { - last_found = p; - return 1; - } - if (p == last_found) - p = packed_git; - else - p = p->next; - if (p == last_found) - p = p->next; - } - return 0; -} - -static void loosen_unused_packed_objects(struct rev_info *revs) -{ - struct packed_git *p; - uint32_t i; - const unsigned char *sha1; - - for (p = packed_git; p; p = p->next) { - if (!p->pack_local || p->pack_keep) - continue; - - if (open_pack_index(p)) - die("cannot open pack index"); - - for (i = 0; i < p->num_objects; i++) { - sha1 = nth_packed_object_sha1(p, i); - if (!locate_object_entry(sha1) && - !has_sha1_pack_kept_or_nonlocal(sha1)) - if (force_object_loose(sha1, p->mtime)) - die("unable to force loose object"); - } - } -} - -static void get_object_list(int ac, const char **av) -{ - struct rev_info revs; - char line[1000]; - int flags = 0; - - init_revisions(&revs, NULL); - save_commit_buffer = 0; - setup_revisions(ac, av, &revs, NULL); - - while (fgets(line, sizeof(line), stdin) != NULL) { - int len = strlen(line); - if (len && line[len - 1] == '\n') - line[--len] = 0; - if (!len) - break; - if (*line == '-') { - if (!strcmp(line, "--not")) { - flags ^= UNINTERESTING; - continue; - } - die("not a rev '%s'", line); - } - if (handle_revision_arg(line, &revs, flags, 1)) - die("bad revision '%s'", line); - } - - if (prepare_revision_walk(&revs)) - die("revision walk setup failed"); - mark_edges_uninteresting(revs.commits, &revs, show_edge); - traverse_commit_list(&revs, show_commit, show_object, NULL); - - if (keep_unreachable) - add_objects_in_unpacked_packs(&revs); - if (unpack_unreachable) - loosen_unused_packed_objects(&revs); -} - -static int adjust_perm(const char *path, mode_t mode) -{ - if (chmod(path, mode)) - return -1; - return adjust_shared_perm(path); -} - -int cmd_pack_objects(int argc, const char **argv, const char *prefix) -{ - int use_internal_rev_list = 0; - int thin = 0; - int all_progress_implied = 0; - uint32_t i; - const char **rp_av; - int rp_ac_alloc = 64; - int rp_ac; - - read_replace_refs = 0; - - rp_av = xcalloc(rp_ac_alloc, sizeof(*rp_av)); - - rp_av[0] = "pack-objects"; - rp_av[1] = "--objects"; /* --thin will make it --objects-edge */ - rp_ac = 2; - - git_config(git_pack_config, NULL); - if (!pack_compression_seen && core_compression_seen) - pack_compression_level = core_compression_level; - - progress = isatty(2); - for (i = 1; i < argc; i++) { - const char *arg = argv[i]; - - if (*arg != '-') - break; - - if (!strcmp("--non-empty", arg)) { - non_empty = 1; - continue; - } - if (!strcmp("--local", arg)) { - local = 1; - continue; - } - if (!strcmp("--incremental", arg)) { - incremental = 1; - continue; - } - if (!strcmp("--honor-pack-keep", arg)) { - ignore_packed_keep = 1; - continue; - } - if (!prefixcmp(arg, "--compression=")) { - char *end; - int level = strtoul(arg+14, &end, 0); - if (!arg[14] || *end) - usage(pack_usage); - if (level == -1) - level = Z_DEFAULT_COMPRESSION; - else if (level < 0 || level > Z_BEST_COMPRESSION) - die("bad pack compression level %d", level); - pack_compression_level = level; - continue; - } - if (!prefixcmp(arg, "--max-pack-size=")) { - pack_size_limit_cfg = 0; - if (!git_parse_ulong(arg+16, &pack_size_limit)) - usage(pack_usage); - continue; - } - if (!prefixcmp(arg, "--window=")) { - char *end; - window = strtoul(arg+9, &end, 0); - if (!arg[9] || *end) - usage(pack_usage); - continue; - } - if (!prefixcmp(arg, "--window-memory=")) { - if (!git_parse_ulong(arg+16, &window_memory_limit)) - usage(pack_usage); - continue; - } - if (!prefixcmp(arg, "--threads=")) { - char *end; - delta_search_threads = strtoul(arg+10, &end, 0); - if (!arg[10] || *end || delta_search_threads < 0) - usage(pack_usage); -#ifdef NO_PTHREADS - if (delta_search_threads != 1) - warning("no threads support, " - "ignoring %s", arg); -#endif - continue; - } - if (!prefixcmp(arg, "--depth=")) { - char *end; - depth = strtoul(arg+8, &end, 0); - if (!arg[8] || *end) - usage(pack_usage); - continue; - } - if (!strcmp("--progress", arg)) { - progress = 1; - continue; - } - if (!strcmp("--all-progress", arg)) { - progress = 2; - continue; - } - if (!strcmp("--all-progress-implied", arg)) { - all_progress_implied = 1; - continue; - } - if (!strcmp("-q", arg)) { - progress = 0; - continue; - } - if (!strcmp("--no-reuse-delta", arg)) { - reuse_delta = 0; - continue; - } - if (!strcmp("--no-reuse-object", arg)) { - reuse_object = reuse_delta = 0; - continue; - } - if (!strcmp("--delta-base-offset", arg)) { - allow_ofs_delta = 1; - continue; - } - if (!strcmp("--stdout", arg)) { - pack_to_stdout = 1; - continue; - } - if (!strcmp("--revs", arg)) { - use_internal_rev_list = 1; - continue; - } - if (!strcmp("--keep-unreachable", arg)) { - keep_unreachable = 1; - continue; - } - if (!strcmp("--unpack-unreachable", arg)) { - unpack_unreachable = 1; - continue; - } - if (!strcmp("--include-tag", arg)) { - include_tag = 1; - continue; - } - if (!strcmp("--unpacked", arg) || - !strcmp("--reflog", arg) || - !strcmp("--all", arg)) { - use_internal_rev_list = 1; - if (rp_ac >= rp_ac_alloc - 1) { - rp_ac_alloc = alloc_nr(rp_ac_alloc); - rp_av = xrealloc(rp_av, - rp_ac_alloc * sizeof(*rp_av)); - } - rp_av[rp_ac++] = arg; - continue; - } - if (!strcmp("--thin", arg)) { - use_internal_rev_list = 1; - thin = 1; - rp_av[1] = "--objects-edge"; - continue; - } - if (!prefixcmp(arg, "--index-version=")) { - char *c; - pack_idx_default_version = strtoul(arg + 16, &c, 10); - if (pack_idx_default_version > 2) - die("bad %s", arg); - if (*c == ',') - pack_idx_off32_limit = strtoul(c+1, &c, 0); - if (*c || pack_idx_off32_limit & 0x80000000) - die("bad %s", arg); - continue; - } - if (!strcmp(arg, "--keep-true-parents")) { - grafts_replace_parents = 0; - continue; - } - usage(pack_usage); - } - - /* Traditionally "pack-objects [options] base extra" failed; - * we would however want to take refs parameter that would - * have been given to upstream rev-list ourselves, which means - * we somehow want to say what the base name is. So the - * syntax would be: - * - * pack-objects [options] base - * - * in other words, we would treat the first non-option as the - * base_name and send everything else to the internal revision - * walker. - */ - - if (!pack_to_stdout) - base_name = argv[i++]; - - if (pack_to_stdout != !base_name) - usage(pack_usage); - - if (!pack_to_stdout && !pack_size_limit) - pack_size_limit = pack_size_limit_cfg; - if (pack_to_stdout && pack_size_limit) - die("--max-pack-size cannot be used to build a pack for transfer."); - if (pack_size_limit && pack_size_limit < 1024*1024) { - warning("minimum pack size limit is 1 MiB"); - pack_size_limit = 1024*1024; - } - - if (!pack_to_stdout && thin) - die("--thin cannot be used to build an indexable pack."); - - if (keep_unreachable && unpack_unreachable) - die("--keep-unreachable and --unpack-unreachable are incompatible."); - - if (progress && all_progress_implied) - progress = 2; - - prepare_packed_git(); - - if (progress) - progress_state = start_progress("Counting objects", 0); - if (!use_internal_rev_list) - read_object_list_from_stdin(); - else { - rp_av[rp_ac] = NULL; - get_object_list(rp_ac, rp_av); - } - cleanup_preferred_base(); - if (include_tag && nr_result) - for_each_ref(add_ref_tag, NULL); - stop_progress(&progress_state); - - if (non_empty && !nr_result) - return 0; - if (nr_result) - prepare_pack(window, depth); - write_pack_file(); - if (progress) - fprintf(stderr, "Total %"PRIu32" (delta %"PRIu32")," - " reused %"PRIu32" (delta %"PRIu32")\n", - written, written_delta, reused, reused_delta); - return 0; -} diff --git a/builtin-pack-redundant.c b/builtin-pack-redundant.c deleted file mode 100644 index 41e1615..0000000 --- a/builtin-pack-redundant.c +++ /dev/null @@ -1,696 +0,0 @@ -/* -* -* Copyright 2005, Lukas Sandstrom -* -* This file is licensed under the GPL v2. -* -*/ - -#include "cache.h" -#include "exec_cmd.h" - -#define BLKSIZE 512 - -static const char pack_redundant_usage[] = -"git pack-redundant [ --verbose ] [ --alt-odb ] < --all | <.pack filename> ...>"; - -static int load_all_packs, verbose, alt_odb; - -struct llist_item { - struct llist_item *next; - const unsigned char *sha1; -}; -static struct llist { - struct llist_item *front; - struct llist_item *back; - size_t size; -} *all_objects; /* all objects which must be present in local packfiles */ - -static struct pack_list { - struct pack_list *next; - struct packed_git *pack; - struct llist *unique_objects; - struct llist *all_objects; -} *local_packs = NULL, *altodb_packs = NULL; - -struct pll { - struct pll *next; - struct pack_list *pl; -}; - -static struct llist_item *free_nodes; - -static inline void llist_item_put(struct llist_item *item) -{ - item->next = free_nodes; - free_nodes = item; -} - -static inline struct llist_item *llist_item_get(void) -{ - struct llist_item *new; - if ( free_nodes ) { - new = free_nodes; - free_nodes = free_nodes->next; - } else { - int i = 1; - new = xmalloc(sizeof(struct llist_item) * BLKSIZE); - for (; i < BLKSIZE; i++) - llist_item_put(&new[i]); - } - return new; -} - -static void llist_free(struct llist *list) -{ - while ((list->back = list->front)) { - list->front = list->front->next; - llist_item_put(list->back); - } - free(list); -} - -static inline void llist_init(struct llist **list) -{ - *list = xmalloc(sizeof(struct llist)); - (*list)->front = (*list)->back = NULL; - (*list)->size = 0; -} - -static struct llist * llist_copy(struct llist *list) -{ - struct llist *ret; - struct llist_item *new, *old, *prev; - - llist_init(&ret); - - if ((ret->size = list->size) == 0) - return ret; - - new = ret->front = llist_item_get(); - new->sha1 = list->front->sha1; - - old = list->front->next; - while (old) { - prev = new; - new = llist_item_get(); - prev->next = new; - new->sha1 = old->sha1; - old = old->next; - } - new->next = NULL; - ret->back = new; - - return ret; -} - -static inline struct llist_item *llist_insert(struct llist *list, - struct llist_item *after, - const unsigned char *sha1) -{ - struct llist_item *new = llist_item_get(); - new->sha1 = sha1; - new->next = NULL; - - if (after != NULL) { - new->next = after->next; - after->next = new; - if (after == list->back) - list->back = new; - } else {/* insert in front */ - if (list->size == 0) - list->back = new; - else - new->next = list->front; - list->front = new; - } - list->size++; - return new; -} - -static inline struct llist_item *llist_insert_back(struct llist *list, - const unsigned char *sha1) -{ - return llist_insert(list, list->back, sha1); -} - -static inline struct llist_item *llist_insert_sorted_unique(struct llist *list, - const unsigned char *sha1, struct llist_item *hint) -{ - struct llist_item *prev = NULL, *l; - - l = (hint == NULL) ? list->front : hint; - while (l) { - int cmp = hashcmp(l->sha1, sha1); - if (cmp > 0) { /* we insert before this entry */ - return llist_insert(list, prev, sha1); - } - if (!cmp) { /* already exists */ - return l; - } - prev = l; - l = l->next; - } - /* insert at the end */ - return llist_insert_back(list, sha1); -} - -/* returns a pointer to an item in front of sha1 */ -static inline struct llist_item * llist_sorted_remove(struct llist *list, const unsigned char *sha1, struct llist_item *hint) -{ - struct llist_item *prev, *l; - -redo_from_start: - l = (hint == NULL) ? list->front : hint; - prev = NULL; - while (l) { - int cmp = hashcmp(l->sha1, sha1); - if (cmp > 0) /* not in list, since sorted */ - return prev; - if (!cmp) { /* found */ - if (prev == NULL) { - if (hint != NULL && hint != list->front) { - /* we don't know the previous element */ - hint = NULL; - goto redo_from_start; - } - list->front = l->next; - } else - prev->next = l->next; - if (l == list->back) - list->back = prev; - llist_item_put(l); - list->size--; - return prev; - } - prev = l; - l = l->next; - } - return prev; -} - -/* computes A\B */ -static void llist_sorted_difference_inplace(struct llist *A, - struct llist *B) -{ - struct llist_item *hint, *b; - - hint = NULL; - b = B->front; - - while (b) { - hint = llist_sorted_remove(A, b->sha1, hint); - b = b->next; - } -} - -static inline struct pack_list * pack_list_insert(struct pack_list **pl, - struct pack_list *entry) -{ - struct pack_list *p = xmalloc(sizeof(struct pack_list)); - memcpy(p, entry, sizeof(struct pack_list)); - p->next = *pl; - *pl = p; - return p; -} - -static inline size_t pack_list_size(struct pack_list *pl) -{ - size_t ret = 0; - while (pl) { - ret++; - pl = pl->next; - } - return ret; -} - -static struct pack_list * pack_list_difference(const struct pack_list *A, - const struct pack_list *B) -{ - struct pack_list *ret; - const struct pack_list *pl; - - if (A == NULL) - return NULL; - - pl = B; - while (pl != NULL) { - if (A->pack == pl->pack) - return pack_list_difference(A->next, B); - pl = pl->next; - } - ret = xmalloc(sizeof(struct pack_list)); - memcpy(ret, A, sizeof(struct pack_list)); - ret->next = pack_list_difference(A->next, B); - return ret; -} - -static void cmp_two_packs(struct pack_list *p1, struct pack_list *p2) -{ - unsigned long p1_off = 0, p2_off = 0, p1_step, p2_step; - const unsigned char *p1_base, *p2_base; - struct llist_item *p1_hint = NULL, *p2_hint = NULL; - - p1_base = p1->pack->index_data; - p2_base = p2->pack->index_data; - p1_base += 256 * 4 + ((p1->pack->index_version < 2) ? 4 : 8); - p2_base += 256 * 4 + ((p2->pack->index_version < 2) ? 4 : 8); - p1_step = (p1->pack->index_version < 2) ? 24 : 20; - p2_step = (p2->pack->index_version < 2) ? 24 : 20; - - while (p1_off < p1->pack->num_objects * p1_step && - p2_off < p2->pack->num_objects * p2_step) - { - int cmp = hashcmp(p1_base + p1_off, p2_base + p2_off); - /* cmp ~ p1 - p2 */ - if (cmp == 0) { - p1_hint = llist_sorted_remove(p1->unique_objects, - p1_base + p1_off, p1_hint); - p2_hint = llist_sorted_remove(p2->unique_objects, - p1_base + p1_off, p2_hint); - p1_off += p1_step; - p2_off += p2_step; - continue; - } - if (cmp < 0) { /* p1 has the object, p2 doesn't */ - p1_off += p1_step; - } else { /* p2 has the object, p1 doesn't */ - p2_off += p2_step; - } - } -} - -static void pll_free(struct pll *l) -{ - struct pll *old; - struct pack_list *opl; - - while (l) { - old = l; - while (l->pl) { - opl = l->pl; - l->pl = opl->next; - free(opl); - } - l = l->next; - free(old); - } -} - -/* all the permutations have to be free()d at the same time, - * since they refer to each other - */ -static struct pll * get_permutations(struct pack_list *list, int n) -{ - struct pll *subset, *ret = NULL, *new_pll = NULL, *pll; - - if (list == NULL || pack_list_size(list) < n || n == 0) - return NULL; - - if (n == 1) { - while (list) { - new_pll = xmalloc(sizeof(pll)); - new_pll->pl = NULL; - pack_list_insert(&new_pll->pl, list); - new_pll->next = ret; - ret = new_pll; - list = list->next; - } - return ret; - } - - while (list->next) { - subset = get_permutations(list->next, n - 1); - while (subset) { - new_pll = xmalloc(sizeof(pll)); - new_pll->pl = subset->pl; - pack_list_insert(&new_pll->pl, list); - new_pll->next = ret; - ret = new_pll; - subset = subset->next; - } - list = list->next; - } - return ret; -} - -static int is_superset(struct pack_list *pl, struct llist *list) -{ - struct llist *diff; - - diff = llist_copy(list); - - while (pl) { - llist_sorted_difference_inplace(diff, pl->all_objects); - if (diff->size == 0) { /* we're done */ - llist_free(diff); - return 1; - } - pl = pl->next; - } - llist_free(diff); - return 0; -} - -static size_t sizeof_union(struct packed_git *p1, struct packed_git *p2) -{ - size_t ret = 0; - unsigned long p1_off = 0, p2_off = 0, p1_step, p2_step; - const unsigned char *p1_base, *p2_base; - - p1_base = p1->index_data; - p2_base = p2->index_data; - p1_base += 256 * 4 + ((p1->index_version < 2) ? 4 : 8); - p2_base += 256 * 4 + ((p2->index_version < 2) ? 4 : 8); - p1_step = (p1->index_version < 2) ? 24 : 20; - p2_step = (p2->index_version < 2) ? 24 : 20; - - while (p1_off < p1->num_objects * p1_step && - p2_off < p2->num_objects * p2_step) - { - int cmp = hashcmp(p1_base + p1_off, p2_base + p2_off); - /* cmp ~ p1 - p2 */ - if (cmp == 0) { - ret++; - p1_off += p1_step; - p2_off += p2_step; - continue; - } - if (cmp < 0) { /* p1 has the object, p2 doesn't */ - p1_off += p1_step; - } else { /* p2 has the object, p1 doesn't */ - p2_off += p2_step; - } - } - return ret; -} - -/* another O(n^2) function ... */ -static size_t get_pack_redundancy(struct pack_list *pl) -{ - struct pack_list *subset; - size_t ret = 0; - - if (pl == NULL) - return 0; - - while ((subset = pl->next)) { - while (subset) { - ret += sizeof_union(pl->pack, subset->pack); - subset = subset->next; - } - pl = pl->next; - } - return ret; -} - -static inline off_t pack_set_bytecount(struct pack_list *pl) -{ - off_t ret = 0; - while (pl) { - ret += pl->pack->pack_size; - ret += pl->pack->index_size; - pl = pl->next; - } - return ret; -} - -static void minimize(struct pack_list **min) -{ - struct pack_list *pl, *unique = NULL, - *non_unique = NULL, *min_perm = NULL; - struct pll *perm, *perm_all, *perm_ok = NULL, *new_perm; - struct llist *missing; - off_t min_perm_size = 0, perm_size; - int n; - - pl = local_packs; - while (pl) { - if (pl->unique_objects->size) - pack_list_insert(&unique, pl); - else - pack_list_insert(&non_unique, pl); - pl = pl->next; - } - /* find out which objects are missing from the set of unique packs */ - missing = llist_copy(all_objects); - pl = unique; - while (pl) { - llist_sorted_difference_inplace(missing, pl->all_objects); - pl = pl->next; - } - - /* return if there are no objects missing from the unique set */ - if (missing->size == 0) { - *min = unique; - return; - } - - /* find the permutations which contain all missing objects */ - for (n = 1; n <= pack_list_size(non_unique) && !perm_ok; n++) { - perm_all = perm = get_permutations(non_unique, n); - while (perm) { - if (is_superset(perm->pl, missing)) { - new_perm = xmalloc(sizeof(struct pll)); - memcpy(new_perm, perm, sizeof(struct pll)); - new_perm->next = perm_ok; - perm_ok = new_perm; - } - perm = perm->next; - } - if (perm_ok) - break; - pll_free(perm_all); - } - if (perm_ok == NULL) - die("Internal error: No complete sets found!"); - - /* find the permutation with the smallest size */ - perm = perm_ok; - while (perm) { - perm_size = pack_set_bytecount(perm->pl); - if (!min_perm_size || min_perm_size > perm_size) { - min_perm_size = perm_size; - min_perm = perm->pl; - } - perm = perm->next; - } - *min = min_perm; - /* add the unique packs to the list */ - pl = unique; - while (pl) { - pack_list_insert(min, pl); - pl = pl->next; - } -} - -static void load_all_objects(void) -{ - struct pack_list *pl = local_packs; - struct llist_item *hint, *l; - - llist_init(&all_objects); - - while (pl) { - hint = NULL; - l = pl->all_objects->front; - while (l) { - hint = llist_insert_sorted_unique(all_objects, - l->sha1, hint); - l = l->next; - } - pl = pl->next; - } - /* remove objects present in remote packs */ - pl = altodb_packs; - while (pl) { - llist_sorted_difference_inplace(all_objects, pl->all_objects); - pl = pl->next; - } -} - -/* this scales like O(n^2) */ -static void cmp_local_packs(void) -{ - struct pack_list *subset, *pl = local_packs; - - while ((subset = pl)) { - while ((subset = subset->next)) - cmp_two_packs(pl, subset); - pl = pl->next; - } -} - -static void scan_alt_odb_packs(void) -{ - struct pack_list *local, *alt; - - alt = altodb_packs; - while (alt) { - local = local_packs; - while (local) { - llist_sorted_difference_inplace(local->unique_objects, - alt->all_objects); - local = local->next; - } - llist_sorted_difference_inplace(all_objects, alt->all_objects); - alt = alt->next; - } -} - -static struct pack_list * add_pack(struct packed_git *p) -{ - struct pack_list l; - unsigned long off = 0, step; - const unsigned char *base; - - if (!p->pack_local && !(alt_odb || verbose)) - return NULL; - - l.pack = p; - llist_init(&l.all_objects); - - if (open_pack_index(p)) - return NULL; - - base = p->index_data; - base += 256 * 4 + ((p->index_version < 2) ? 4 : 8); - step = (p->index_version < 2) ? 24 : 20; - while (off < p->num_objects * step) { - llist_insert_back(l.all_objects, base + off); - off += step; - } - /* this list will be pruned in cmp_two_packs later */ - l.unique_objects = llist_copy(l.all_objects); - if (p->pack_local) - return pack_list_insert(&local_packs, &l); - else - return pack_list_insert(&altodb_packs, &l); -} - -static struct pack_list * add_pack_file(const char *filename) -{ - struct packed_git *p = packed_git; - - if (strlen(filename) < 40) - die("Bad pack filename: %s", filename); - - while (p) { - if (strstr(p->pack_name, filename)) - return add_pack(p); - p = p->next; - } - die("Filename %s not found in packed_git", filename); -} - -static void load_all(void) -{ - struct packed_git *p = packed_git; - - while (p) { - add_pack(p); - p = p->next; - } -} - -int cmd_pack_redundant(int argc, const char **argv, const char *prefix) -{ - int i; - struct pack_list *min, *red, *pl; - struct llist *ignore; - unsigned char *sha1; - char buf[42]; /* 40 byte sha1 + \n + \0 */ - - if (argc == 2 && !strcmp(argv[1], "-h")) - usage(pack_redundant_usage); - - for (i = 1; i < argc; i++) { - const char *arg = argv[i]; - if (!strcmp(arg, "--")) { - i++; - break; - } - if (!strcmp(arg, "--all")) { - load_all_packs = 1; - continue; - } - if (!strcmp(arg, "--verbose")) { - verbose = 1; - continue; - } - if (!strcmp(arg, "--alt-odb")) { - alt_odb = 1; - continue; - } - if (*arg == '-') - usage(pack_redundant_usage); - else - break; - } - - prepare_packed_git(); - - if (load_all_packs) - load_all(); - else - while (*(argv + i) != NULL) - add_pack_file(*(argv + i++)); - - if (local_packs == NULL) - die("Zero packs found!"); - - load_all_objects(); - - cmp_local_packs(); - if (alt_odb) - scan_alt_odb_packs(); - - /* ignore objects given on stdin */ - llist_init(&ignore); - if (!isatty(0)) { - while (fgets(buf, sizeof(buf), stdin)) { - sha1 = xmalloc(20); - if (get_sha1_hex(buf, sha1)) - die("Bad sha1 on stdin: %s", buf); - llist_insert_sorted_unique(ignore, sha1, NULL); - } - } - llist_sorted_difference_inplace(all_objects, ignore); - pl = local_packs; - while (pl) { - llist_sorted_difference_inplace(pl->unique_objects, ignore); - pl = pl->next; - } - - minimize(&min); - - if (verbose) { - fprintf(stderr, "There are %lu packs available in alt-odbs.\n", - (unsigned long)pack_list_size(altodb_packs)); - fprintf(stderr, "The smallest (bytewise) set of packs is:\n"); - pl = min; - while (pl) { - fprintf(stderr, "\t%s\n", pl->pack->pack_name); - pl = pl->next; - } - fprintf(stderr, "containing %lu duplicate objects " - "with a total size of %lukb.\n", - (unsigned long)get_pack_redundancy(min), - (unsigned long)pack_set_bytecount(min)/1024); - fprintf(stderr, "A total of %lu unique objects were considered.\n", - (unsigned long)all_objects->size); - fprintf(stderr, "Redundant packs (with indexes):\n"); - } - pl = red = pack_list_difference(local_packs, min); - while (pl) { - printf("%s\n%s\n", - sha1_pack_index_name(pl->pack->sha1), - pl->pack->pack_name); - pl = pl->next; - } - if (verbose) - fprintf(stderr, "%luMB of redundant packs in total.\n", - (unsigned long)pack_set_bytecount(red)/(1024*1024)); - - return 0; -} diff --git a/builtin-pack-refs.c b/builtin-pack-refs.c deleted file mode 100644 index 091860b..0000000 --- a/builtin-pack-refs.c +++ /dev/null @@ -1,21 +0,0 @@ -#include "cache.h" -#include "parse-options.h" -#include "pack-refs.h" - -static char const * const pack_refs_usage[] = { - "git pack-refs [options]", - NULL -}; - -int cmd_pack_refs(int argc, const char **argv, const char *prefix) -{ - unsigned int flags = PACK_REFS_PRUNE; - struct option opts[] = { - OPT_BIT(0, "all", &flags, "pack everything", PACK_REFS_ALL), - OPT_BIT(0, "prune", &flags, "prune loose refs (default)", PACK_REFS_PRUNE), - OPT_END(), - }; - if (parse_options(argc, argv, prefix, opts, pack_refs_usage, 0)) - usage_with_options(pack_refs_usage, opts); - return pack_refs(flags); -} diff --git a/builtin-patch-id.c b/builtin-patch-id.c deleted file mode 100644 index af0911e..0000000 --- a/builtin-patch-id.c +++ /dev/null @@ -1,85 +0,0 @@ -#include "cache.h" -#include "exec_cmd.h" - -static void flush_current_id(int patchlen, unsigned char *id, git_SHA_CTX *c) -{ - unsigned char result[20]; - char name[50]; - - if (!patchlen) - return; - - git_SHA1_Final(result, c); - memcpy(name, sha1_to_hex(id), 41); - printf("%s %s\n", sha1_to_hex(result), name); - git_SHA1_Init(c); -} - -static int remove_space(char *line) -{ - char *src = line; - char *dst = line; - unsigned char c; - - while ((c = *src++) != '\0') { - if (!isspace(c)) - *dst++ = c; - } - return dst - line; -} - -static void generate_id_list(void) -{ - static unsigned char sha1[20]; - static char line[1000]; - git_SHA_CTX ctx; - int patchlen = 0; - - git_SHA1_Init(&ctx); - while (fgets(line, sizeof(line), stdin) != NULL) { - unsigned char n[20]; - char *p = line; - int len; - - if (!memcmp(line, "diff-tree ", 10)) - p += 10; - else if (!memcmp(line, "commit ", 7)) - p += 7; - - if (!get_sha1_hex(p, n)) { - flush_current_id(patchlen, sha1, &ctx); - hashcpy(sha1, n); - patchlen = 0; - continue; - } - - /* Ignore commit comments */ - if (!patchlen && memcmp(line, "diff ", 5)) - continue; - - /* Ignore git-diff index header */ - if (!memcmp(line, "index ", 6)) - continue; - - /* Ignore line numbers when computing the SHA1 of the patch */ - if (!memcmp(line, "@@ -", 4)) - continue; - - /* Compute the sha without whitespace */ - len = remove_space(line); - patchlen += len; - git_SHA1_Update(&ctx, line, len); - } - flush_current_id(patchlen, sha1, &ctx); -} - -static const char patch_id_usage[] = "git patch-id < patch"; - -int cmd_patch_id(int argc, const char **argv, const char *prefix) -{ - if (argc != 1) - usage(patch_id_usage); - - generate_id_list(); - return 0; -} diff --git a/builtin-prune-packed.c b/builtin-prune-packed.c deleted file mode 100644 index f9463de..0000000 --- a/builtin-prune-packed.c +++ /dev/null @@ -1,86 +0,0 @@ -#include "builtin.h" -#include "cache.h" -#include "progress.h" -#include "parse-options.h" - -static const char * const prune_packed_usage[] = { - "git prune-packed [-n|--dry-run] [-q|--quiet]", - NULL -}; - -#define DRY_RUN 01 -#define VERBOSE 02 - -static struct progress *progress; - -static void prune_dir(int i, DIR *dir, char *pathname, int len, int opts) -{ - struct dirent *de; - char hex[40]; - - sprintf(hex, "%02x", i); - while ((de = readdir(dir)) != NULL) { - unsigned char sha1[20]; - if (strlen(de->d_name) != 38) - continue; - memcpy(hex+2, de->d_name, 38); - if (get_sha1_hex(hex, sha1)) - continue; - if (!has_sha1_pack(sha1)) - continue; - memcpy(pathname + len, de->d_name, 38); - if (opts & DRY_RUN) - printf("rm -f %s\n", pathname); - else - unlink_or_warn(pathname); - display_progress(progress, i + 1); - } - pathname[len] = 0; - rmdir(pathname); -} - -void prune_packed_objects(int opts) -{ - int i; - static char pathname[PATH_MAX]; - const char *dir = get_object_directory(); - int len = strlen(dir); - - if (opts == VERBOSE) - progress = start_progress_delay("Removing duplicate objects", - 256, 95, 2); - - if (len > PATH_MAX - 42) - die("impossible object directory"); - memcpy(pathname, dir, len); - if (len && pathname[len-1] != '/') - pathname[len++] = '/'; - for (i = 0; i < 256; i++) { - DIR *d; - - display_progress(progress, i + 1); - sprintf(pathname + len, "%02x/", i); - d = opendir(pathname); - if (!d) - continue; - prune_dir(i, d, pathname, len + 3, opts); - closedir(d); - } - stop_progress(&progress); -} - -int cmd_prune_packed(int argc, const char **argv, const char *prefix) -{ - int opts = isatty(2) ? VERBOSE : 0; - const struct option prune_packed_options[] = { - OPT_BIT('n', "dry-run", &opts, "dry run", DRY_RUN), - OPT_NEGBIT('q', "quiet", &opts, "be quiet", VERBOSE), - OPT_END() - }; - - argc = parse_options(argc, argv, prefix, prune_packed_options, - prune_packed_usage, 0); - - prune_packed_objects(opts); - return 0; -} diff --git a/builtin-prune.c b/builtin-prune.c deleted file mode 100644 index 4675f60..0000000 --- a/builtin-prune.c +++ /dev/null @@ -1,169 +0,0 @@ -#include "cache.h" -#include "commit.h" -#include "diff.h" -#include "revision.h" -#include "builtin.h" -#include "reachable.h" -#include "parse-options.h" -#include "dir.h" - -static const char * const prune_usage[] = { - "git prune [-n] [-v] [--expire