diff options
Diffstat (limited to 'builtin')
89 files changed, 42160 insertions, 0 deletions
diff --git a/builtin/add.c b/builtin/add.c new file mode 100644 index 0000000..2705f8d --- /dev/null +++ b/builtin/add.c @@ -0,0 +1,442 @@ +/* + * "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] [--] <filepattern>...", + 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 new file mode 100644 index 0000000..fc43eed --- /dev/null +++ b/builtin/annotate.c @@ -0,0 +1,24 @@ +/* + * "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 new file mode 100644 index 0000000..3af4ae0 --- /dev/null +++ b/builtin/apply.c @@ -0,0 +1,3670 @@ +/* + * 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] [<patch>...]", + 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 <num> 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 <n> 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 <root> 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, "<stdin>", 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, "<stdin>", 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 new file mode 100644 index 0000000..6a887f5 --- /dev/null +++ b/builtin/archive.c @@ -0,0 +1,129 @@ +/* + * 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 <repo>"), + 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 new file mode 100644 index 0000000..5b22639 --- /dev/null +++ b/builtin/bisect--helper.c @@ -0,0 +1,28 @@ +#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 new file mode 100644 index 0000000..10f7eac --- /dev/null +++ b/builtin/blame.c @@ -0,0 +1,2477 @@ +/* + * 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. <commit, path> 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 <something>,+20" to mean starting at <something> + * for 20 lines, or "-L <something>,-5" for 5 lines ending at + * <something>. + */ + 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 <file> instead of calling git-rev-list"), + OPT_STRING(0, "contents", &contents_from, "file", "Use <file>'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] -- <path>" or + * "blame -- <path> <rev>" + * + * (2) otherwise, its one of the two: + * "blame [revisions] <path>" + * "blame <path> <rev>" + * + * Note that we must strip out <path> 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: <rev> -- <path> */ + 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 new file mode 100644 index 0000000..a28a139 --- /dev/null +++ b/builtin/branch.c @@ -0,0 +1,696 @@ +/* + * Builtin "git branch" + * + * Copyright (c) 2006 Kristian Høgsberg <krh@redhat.com> + * 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] <branchname> [<start-point>]", + "git branch [options] [-r] (-d | -D) <branchname>", + "git branch [options] (-m | -M) [<oldbranch>] <newbranch>", + 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 new file mode 100644 index 0000000..2006cc5 --- /dev/null +++ b/builtin/bundle.c @@ -0,0 +1,67 @@ +#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 <file> <git-rev-list args>\n" + " or: git bundle verify <file>\n" + " or: git bundle list-heads <file> [refname...]\n" + " or: git bundle unbundle <file> [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 new file mode 100644 index 0000000..a933eaa --- /dev/null +++ b/builtin/cat-file.c @@ -0,0 +1,258 @@ +/* + * 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|<type>) <object>", + "git cat-file (--batch|--batch-check) < <list_of_objects>", + 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("<type> 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 new file mode 100644 index 0000000..3016d29 --- /dev/null +++ b/builtin/check-attr.c @@ -0,0 +1,123 @@ +#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... < <list-of-paths>", +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 new file mode 100644 index 0000000..b106c65 --- /dev/null +++ b/builtin/check-ref-format.c @@ -0,0 +1,61 @@ +/* + * 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] <refname>\n" +" or: git check-ref-format --branch <branchname-shorthand>"; + +/* + * 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 new file mode 100644 index 0000000..a7a5ee1 --- /dev/null +++ b/builtin/checkout-index.c @@ -0,0 +1,315 @@ +/* + * 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] [--] <file>...", + 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 <string>", + 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 new file mode 100644 index 0000000..c5ab783 --- /dev/null +++ b/builtin/checkout.c @@ -0,0 +1,853 @@ +#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] <branch>", + "git checkout [options] [<branch>] -- <file>...", + 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 <buffer, size, mode> + * 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 <ref> -- [<paths>] + * + * <ref> must be a valid tree, everything after the '--' must be + * a path. + * + * case 2: git checkout -- [<paths>] + * + * everything after the '--' must be paths. + * + * case 3: git checkout <something> [<paths>] + * + * With no paths, if <something> is a commit, that is to + * switch to the branch or detach HEAD at it. As a special case, + * if <something> 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 <something> is _not_ a commit, no -t nor -b + * was given, and there is a tracking branch whose name is + * <something> in one and only one remote, then this is a short-hand + * to fork local <something> from that remote tracking branch. + * + * Otherwise <something> 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 new file mode 100644 index 0000000..fac64e6 --- /dev/null +++ b/builtin/clean.c @@ -0,0 +1,171 @@ +/* + * "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] [--] <paths>...", + 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 new file mode 100644 index 0000000..58bacbd --- /dev/null +++ b/builtin/clone.c @@ -0,0 +1,671 @@ +/* + * Builtin "git clone" + * + * Copyright (c) 2007 Kristian Høgsberg <krh@redhat.com>, + * 2008 Daniel Barkalow <barkalow@iabervon.org> + * 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] [--] <repo> [<dir>]", + 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 <branch> instead of 'origin' to track upstream"), + OPT_STRING('b', "branch", &option_branch, "branch", + "checkout <branch> 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 new file mode 100644 index 0000000..90dac34 --- /dev/null +++ b/builtin/commit-tree.c @@ -0,0 +1,133 @@ +/* + * 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 <sha1> [-p <sha1>]* < 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 new file mode 100644 index 0000000..55676fd --- /dev/null +++ b/builtin/commit.c @@ -0,0 +1,1310 @@ +/* + * Builtin "git commit" + * + * Copyright (c) 2007 Kristian Høgsberg <krh@redhat.com> + * 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] [--] <filepattern>...", + NULL +}; + +static const char * const builtin_status_usage[] = { + "git status [options] [--] <filepattern>...", + 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 <you@example.com>'\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 new file mode 100644 index 0000000..4bc46b1 --- /dev/null +++ b/builtin/config.c @@ -0,0 +1,496 @@ +#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 new file mode 100644 index 0000000..2bdd8eb --- /dev/null +++ b/builtin/count-objects.c @@ -0,0 +1,130 @@ +/* + * 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 new file mode 100644 index 0000000..71be2a9 --- /dev/null +++ b/builtin/describe.c @@ -0,0 +1,396 @@ +#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] <committish>*", + "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 <n> most recent tags (default: 10)"), + OPT_STRING(0, "match", &pattern, "pattern", + "only consider tags matching <pattern>"), + OPT_BOOLEAN(0, "always", &always, + "show abbreviated commit object as fallback"), + {OPTION_STRING, 0, "dirty", &dirty, "mark", + "append <mark> 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 new file mode 100644 index 0000000..5b64011 --- /dev/null +++ b/builtin/diff-files.c @@ -0,0 +1,68 @@ +/* + * 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>] [<path>...]" +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 new file mode 100644 index 0000000..0483749 --- /dev/null +++ b/builtin/diff-index.c @@ -0,0 +1,50 @@ +#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>] <tree-ish> [<path>...]" +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 new file mode 100644 index 0000000..2380c21 --- /dev/null +++ b/builtin/diff-tree.c @@ -0,0 +1,170 @@ +#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] " +"[<common diff options>] <tree-ish> [<tree-ish>] [<path>...]\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 new file mode 100644 index 0000000..ffcdd05 --- /dev/null +++ b/builtin/diff.c @@ -0,0 +1,425 @@ +/* + * 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 <options> <rev>{0,2} -- <path>*"; + +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 new file mode 100644 index 0000000..b0a4029 --- /dev/null +++ b/builtin/fast-export.c @@ -0,0 +1,633 @@ +/* + * "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 " + "<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=<mode> 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=<mode> 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 <n> 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 new file mode 100644 index 0000000..dbd8b7b --- /dev/null +++ b/builtin/fetch-pack.c @@ -0,0 +1,976 @@ +#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=<git-upload-pack>] [--depth=<n>] [--no-progress] [-v] [<host>:]<directory> [<refs>...]"; + +#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 new file mode 100644 index 0000000..8654fa7 --- /dev/null +++ b/builtin/fetch.c @@ -0,0 +1,920 @@ +/* + * "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] [<repository> <refspec>...]", + "git fetch [options] <group>", + "git fetch --multiple [options] [<repository> | <group>]...", + "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.<name>.remote, we add the + * ref given in branch.<name>.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 new file mode 100644 index 0000000..9d52400 --- /dev/null +++ b/builtin/fmt-merge-msg.c @@ -0,0 +1,381 @@ +#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 <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 new file mode 100644 index 0000000..a5a83f1 --- /dev/null +++ b/builtin/for-each-ref.c @@ -0,0 +1,955 @@ +#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<something>"; + * it's not possible that <something> is not ":<format>" 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] [<pattern>]", + 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 <n> 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 new file mode 100644 index 0000000..0929c7f --- /dev/null +++ b/builtin/fsck.c @@ -0,0 +1,684 @@ +#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] [<object>...]", + 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 new file mode 100644 index 0000000..c304638 --- /dev/null +++ b/builtin/gc.c @@ -0,0 +1,255 @@ +/* + * 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 new file mode 100644 index 0000000..552ef1f --- /dev/null +++ b/builtin/grep.c @@ -0,0 +1,1010 @@ +/* + * 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 <pthread.h> +#endif + +static char const * const grep_usage[] = { + "git grep [options] [-e] <pattern> [<rev>...] [[--] 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 <depth> 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 <n> context lines before and after matches", + context_callback), + OPT_INTEGER('B', NULL, &opt.pre_context, + "show <n> context lines before matches"), + OPT_INTEGER('A', NULL, &opt.post_context, + "show <n> 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 <pattern>", 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 <pattern>', 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 new file mode 100644 index 0000000..6a5f5b5 --- /dev/null +++ b/builtin/hash-object.c @@ -0,0 +1,134 @@ +/* + * 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 <type>] [-w] [--path=<file>|--no-filters] [--stdin] [--] <file>...", + "git hash-object --stdin-paths < <list-of-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 new file mode 100644 index 0000000..3182a2b --- /dev/null +++ b/builtin/help.c @@ -0,0 +1,466 @@ +/* + * 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.<tool>.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.<tool>.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 new file mode 100644 index 0000000..b4cf8c5 --- /dev/null +++ b/builtin/index-pack.c @@ -0,0 +1,1045 @@ +#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 <index-file>] [{ ---keep | --keep=<msg> }] [--strict] { <pack-file> | --stdin [--fix-thin] [<pack-file>] }"; + +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); + |